diff --git a/render/detailed/links_test.go b/render/detailed/links_test.go index b88e0456d..9552e1da6 100644 --- a/render/detailed/links_test.go +++ b/render/detailed/links_test.go @@ -31,8 +31,17 @@ var ( } ) +func nodeSummaryWithMetrics(label string, metrics []report.MetricRow) detailed.NodeSummary { + return detailed.NodeSummary{ + BasicNodeSummary: detailed.BasicNodeSummary{ + Label: label, + }, + Metrics: metrics, + } +} + func TestRenderMetricURLs_Disabled(t *testing.T) { - s := detailed.NodeSummary{Label: "foo", Metrics: sampleMetrics} + s := nodeSummaryWithMetrics("foo", sampleMetrics) result := detailed.RenderMetricURLs(s, samplePodNode, "") assert.Empty(t, result.Metrics[0].URL) @@ -40,7 +49,7 @@ func TestRenderMetricURLs_Disabled(t *testing.T) { } func TestRenderMetricURLs_UnknownTopology(t *testing.T) { - s := detailed.NodeSummary{Label: "foo", Metrics: sampleMetrics} + s := nodeSummaryWithMetrics("foo", sampleMetrics) result := detailed.RenderMetricURLs(s, sampleUnknownNode, sampleMetricsGraphURL) assert.Empty(t, result.Metrics[0].URL) @@ -48,7 +57,7 @@ func TestRenderMetricURLs_UnknownTopology(t *testing.T) { } func TestRenderMetricURLs_Pod(t *testing.T) { - s := detailed.NodeSummary{Label: "foo", Metrics: sampleMetrics} + s := nodeSummaryWithMetrics("foo", sampleMetrics) result := detailed.RenderMetricURLs(s, samplePodNode, sampleMetricsGraphURL) checkURL(t, result.Metrics[0].URL, sampleMetricsGraphURL, @@ -58,7 +67,7 @@ func TestRenderMetricURLs_Pod(t *testing.T) { } func TestRenderMetricURLs_Container(t *testing.T) { - s := detailed.NodeSummary{Label: "foo", Metrics: sampleMetrics} + s := nodeSummaryWithMetrics("foo", sampleMetrics) result := detailed.RenderMetricURLs(s, sampleContainerNode, sampleMetricsGraphURL) checkURL(t, result.Metrics[0].URL, sampleMetricsGraphURL, @@ -84,10 +93,7 @@ func TestRenderMetricURLs_EmptyMetrics(t *testing.T) { } func TestRenderMetricURLs_CombinedEmptyMetrics(t *testing.T) { - s := detailed.NodeSummary{ - Label: "foo", - Metrics: []report.MetricRow{{ID: docker.MemoryUsage, Priority: 1}}, - } + s := nodeSummaryWithMetrics("foo", []report.MetricRow{{ID: docker.MemoryUsage, Priority: 1}}) result := detailed.RenderMetricURLs(s, samplePodNode, sampleMetricsGraphURL) assert.NotEmpty(t, result.Metrics[0].URL) @@ -99,7 +105,7 @@ func TestRenderMetricURLs_CombinedEmptyMetrics(t *testing.T) { } func TestRenderMetricURLs_QueryReplacement(t *testing.T) { - s := detailed.NodeSummary{Label: "foo", Metrics: sampleMetrics} + s := nodeSummaryWithMetrics("foo", sampleMetrics) result := detailed.RenderMetricURLs(s, samplePodNode, "http://example.test/?q=:query") checkURL(t, result.Metrics[0].URL, "http://example.test/?q=", diff --git a/render/detailed/node_test.go b/render/detailed/node_test.go index 719625241..63c9ce710 100644 --- a/render/detailed/node_test.go +++ b/render/detailed/node_test.go @@ -43,14 +43,16 @@ func TestMakeDetailedHostNode(t *testing.T) { podNodeSummary := child(t, render.PodRenderer, fixture.ClientPodNodeID) want := detailed.Node{ NodeSummary: detailed.NodeSummary{ - ID: fixture.ClientHostNodeID, - Label: "client", - LabelMinor: "hostname.com", - Rank: "hostname.com", - Pseudo: false, - Shape: "circle", - Linkable: true, - Adjacency: report.MakeIDList(fixture.ServerHostNodeID), + BasicNodeSummary: detailed.BasicNodeSummary{ + ID: fixture.ClientHostNodeID, + Label: "client", + LabelMinor: "hostname.com", + Rank: "hostname.com", + Pseudo: false, + Shape: "circle", + Linkable: true, + }, + Adjacency: report.MakeIDList(fixture.ServerHostNodeID), Metadata: []report.MetadataRow{ { ID: "host_name", @@ -189,13 +191,15 @@ func TestMakeDetailedContainerNode(t *testing.T) { serverProcessNodeSummary.Linkable = true want := detailed.Node{ NodeSummary: detailed.NodeSummary{ - ID: id, - Label: "server", - LabelMinor: "server.hostname.com", - Rank: fixture.ServerContainerImageName, - Shape: "hexagon", - Linkable: true, - Pseudo: false, + BasicNodeSummary: detailed.BasicNodeSummary{ + ID: id, + Label: "server", + LabelMinor: "server.hostname.com", + Rank: fixture.ServerContainerImageName, + Shape: "hexagon", + Linkable: true, + Pseudo: false, + }, Metadata: []report.MetadataRow{ {ID: "docker_image_name", Label: "Image", Value: fixture.ServerContainerImageName, Priority: 1}, {ID: "docker_container_state_human", Label: "State", Value: "running", Priority: 3}, @@ -320,13 +324,15 @@ func TestMakeDetailedPodNode(t *testing.T) { serverProcessNodeSummary.Linkable = true // Temporary workaround for: https://github.com/weaveworks/scope/issues/1295 want := detailed.Node{ NodeSummary: detailed.NodeSummary{ - ID: id, - Label: "pong-b", - LabelMinor: "1 container", - Rank: "ping/pong-b", - Shape: "heptagon", - Linkable: true, - Pseudo: false, + BasicNodeSummary: detailed.BasicNodeSummary{ + ID: id, + Label: "pong-b", + LabelMinor: "1 container", + Rank: "ping/pong-b", + Shape: "heptagon", + Linkable: true, + Pseudo: false, + }, Metadata: []report.MetadataRow{ {ID: "kubernetes_state", Label: "State", Value: "running", Priority: 2}, {ID: "container", Label: "# Containers", Value: "1", Priority: 4, Datatype: report.Number}, diff --git a/render/detailed/summary.go b/render/detailed/summary.go index 7d05eae17..25bd05a77 100644 --- a/render/detailed/summary.go +++ b/render/detailed/summary.go @@ -41,24 +41,30 @@ type Column struct { Datatype string `json:"dataType"` } -// NodeSummary is summary information about a child for a Node. -type NodeSummary struct { - ID string `json:"id"` - Label string `json:"label"` - LabelMinor string `json:"labelMinor"` - Rank string `json:"rank"` - Shape string `json:"shape,omitempty"` - Stack bool `json:"stack,omitempty"` - Linkable bool `json:"linkable,omitempty"` // Whether this node can be linked-to - Pseudo bool `json:"pseudo,omitempty"` - Metadata []report.MetadataRow `json:"metadata,omitempty"` - Parents []Parent `json:"parents,omitempty"` - Metrics []report.MetricRow `json:"metrics,omitempty"` - Tables []report.Table `json:"tables,omitempty"` - Adjacency report.IDList `json:"adjacency,omitempty"` +// BasicNodeSummary is basic summary information about a Node, +// sufficient for rendering links to the node. +type BasicNodeSummary struct { + ID string `json:"id"` + Label string `json:"label"` + LabelMinor string `json:"labelMinor"` + Rank string `json:"rank"` + Shape string `json:"shape,omitempty"` + Stack bool `json:"stack,omitempty"` + Linkable bool `json:"linkable,omitempty"` // Whether this node can be linked-to + Pseudo bool `json:"pseudo,omitempty"` } -var renderers = map[string]func(NodeSummary, report.Node) NodeSummary{ +// NodeSummary is summary information about a Node. +type NodeSummary struct { + BasicNodeSummary + Metadata []report.MetadataRow `json:"metadata,omitempty"` + Parents []Parent `json:"parents,omitempty"` + Metrics []report.MetricRow `json:"metrics,omitempty"` + Tables []report.Table `json:"tables,omitempty"` + Adjacency report.IDList `json:"adjacency,omitempty"` +} + +var renderers = map[string]func(BasicNodeSummary, report.Node) BasicNodeSummary{ render.Pseudo: pseudoNodeSummary, report.Process: processNodeSummary, report.Container: containerNodeSummary, @@ -99,25 +105,51 @@ var primaryAPITopology = map[string]string{ report.Host: "hosts", } -// MakeNodeSummary summarizes a node, if possible. -func MakeNodeSummary(rc report.RenderContext, n report.Node) (NodeSummary, bool) { - r := rc.Report +// MakeBasicNodeSummary returns a basic summary of a node, if +// possible. This summary is sufficient for rendering links to the node. +func MakeBasicNodeSummary(r report.Report, n report.Node) (BasicNodeSummary, bool) { + summary := BasicNodeSummary{ + ID: n.ID, + Linkable: true, + } + if t, ok := r.Topology(n.Topology); ok { + summary.Shape = t.GetShape() + } if renderer, ok := renderers[n.Topology]; ok { // Skip (and don't fall through to fallback) if renderer maps to nil if renderer != nil { - summary := renderer(baseNodeSummary(r, n), n) - return RenderMetricURLs(summary, n, rc.MetricsGraphURL), true + return renderer(summary, n), true } - } else if _, ok := rc.Topology(n.Topology); ok { - summary := baseNodeSummary(r, n) + } else if _, ok := r.Topology(n.Topology); ok { summary.Label = n.ID // This is unlikely to look very good, but is a reasonable fallback - return RenderMetricURLs(summary, n, rc.MetricsGraphURL), true + return summary, true } if strings.HasPrefix(n.Topology, "group:") { - summary := groupNodeSummary(baseNodeSummary(r, n), r, n) - return RenderMetricURLs(summary, n, rc.MetricsGraphURL), true + return groupNodeSummary(summary, r, n), true } - return NodeSummary{}, false + return summary, false +} + +// MakeNodeSummary summarizes a node, if possible. +func MakeNodeSummary(rc report.RenderContext, n report.Node) (NodeSummary, bool) { + base, ok := MakeBasicNodeSummary(rc.Report, n) + if !ok { + return NodeSummary{}, false + } + summary := NodeSummary{ + BasicNodeSummary: base, + Parents: Parents(rc.Report, n), + Adjacency: n.Adjacency, + } + // Only include metadata, metrics, tables when it's not a group node + if _, ok := n.Counters.Lookup(n.Topology); !ok { + if topology, ok := rc.Topology(n.Topology); ok { + summary.Metadata = topology.MetadataTemplates.MetadataRows(n) + summary.Metrics = topology.MetricTemplates.MetricRows(n) + summary.Tables = topology.TableTemplates.Tables(n) + } + } + return RenderMetricURLs(summary, n, rc.MetricsGraphURL), true } // SummarizeMetrics returns a copy of the NodeSummary where the metrics are @@ -131,29 +163,7 @@ func (n NodeSummary) SummarizeMetrics() NodeSummary { return n } -func baseNodeSummary(r report.Report, n report.Node) NodeSummary { - summary := NodeSummary{ - ID: n.ID, - Linkable: true, - Parents: Parents(r, n), - Adjacency: n.Adjacency, - } - if t, ok := r.Topology(n.Topology); ok { - summary.Shape = t.GetShape() - } - if _, ok := n.Counters.Lookup(n.Topology); ok { - // This is a group of nodes, so no metadata, metrics, tables - return summary - } - if topology, ok := r.Topology(n.Topology); ok { - summary.Metadata = topology.MetadataTemplates.MetadataRows(n) - summary.Metrics = topology.MetricTemplates.MetricRows(n) - summary.Tables = topology.TableTemplates.Tables(n) - } - return summary -} - -func pseudoNodeSummary(base NodeSummary, n report.Node) NodeSummary { +func pseudoNodeSummary(base BasicNodeSummary, n report.Node) BasicNodeSummary { pseudoID, _ := render.ParsePseudoNodeID(n.ID) base.Pseudo = true base.Rank = pseudoID @@ -195,7 +205,7 @@ func pseudoNodeSummary(base NodeSummary, n report.Node) NodeSummary { return base } -func processNodeSummary(base NodeSummary, n report.Node) NodeSummary { +func processNodeSummary(base BasicNodeSummary, n report.Node) BasicNodeSummary { var ( hostID, pid, _ = report.ParseProcessNodeID(n.ID) processName, _ = n.Latest.Lookup(process.Name) @@ -223,7 +233,7 @@ func processNodeSummary(base NodeSummary, n report.Node) NodeSummary { return base } -func containerNodeSummary(base NodeSummary, n report.Node) NodeSummary { +func containerNodeSummary(base BasicNodeSummary, n report.Node) BasicNodeSummary { var ( containerName = getRenderableContainerName(n) hostName = report.ExtractHostID(n) @@ -241,7 +251,7 @@ func containerNodeSummary(base NodeSummary, n report.Node) NodeSummary { return base } -func containerImageNodeSummary(base NodeSummary, n report.Node) NodeSummary { +func containerImageNodeSummary(base BasicNodeSummary, n report.Node) BasicNodeSummary { var ( imageName, _ = n.Latest.Lookup(docker.ImageName) imageNameWithoutVersion = docker.ImageNameWithoutVersion(imageName) @@ -263,7 +273,7 @@ func containerImageNodeSummary(base NodeSummary, n report.Node) NodeSummary { return base } -func addKubernetesLabelAndRank(base NodeSummary, n report.Node) NodeSummary { +func addKubernetesLabelAndRank(base BasicNodeSummary, n report.Node) BasicNodeSummary { var ( name, _ = n.Latest.Lookup(kubernetes.Name) namespace, _ = n.Latest.Lookup(kubernetes.Namespace) @@ -277,7 +287,7 @@ func addKubernetesLabelAndRank(base NodeSummary, n report.Node) NodeSummary { return base } -func podNodeSummary(base NodeSummary, n report.Node) NodeSummary { +func podNodeSummary(base BasicNodeSummary, n report.Node) BasicNodeSummary { base = addKubernetesLabelAndRank(base, n) base.LabelMinor = pluralize(n.Counters, report.Container, "container", "containers") return base @@ -290,7 +300,7 @@ var podGroupNodeTypeName = map[string]string{ report.CronJob: "CronJob", } -func podGroupNodeSummary(base NodeSummary, n report.Node) NodeSummary { +func podGroupNodeSummary(base BasicNodeSummary, n report.Node) BasicNodeSummary { base = addKubernetesLabelAndRank(base, n) base.Stack = true // NB: pods are the highest aggregation level for which we display @@ -304,7 +314,7 @@ func podGroupNodeSummary(base NodeSummary, n report.Node) NodeSummary { return base } -func ecsTaskNodeSummary(base NodeSummary, n report.Node) NodeSummary { +func ecsTaskNodeSummary(base BasicNodeSummary, n report.Node) BasicNodeSummary { base.Label, _ = n.Latest.Lookup(awsecs.TaskFamily) if base.Label == "" { base.Label, _ = report.ParseECSTaskNodeID(n.ID) @@ -312,13 +322,13 @@ func ecsTaskNodeSummary(base NodeSummary, n report.Node) NodeSummary { return base } -func ecsServiceNodeSummary(base NodeSummary, n report.Node) NodeSummary { +func ecsServiceNodeSummary(base BasicNodeSummary, n report.Node) BasicNodeSummary { _, base.Label, _ = report.ParseECSServiceNodeID(n.ID) base.Stack = true return base } -func swarmServiceNodeSummary(base NodeSummary, n report.Node) NodeSummary { +func swarmServiceNodeSummary(base BasicNodeSummary, n report.Node) BasicNodeSummary { base.Label, _ = n.Latest.Lookup(docker.ServiceName) if base.Label == "" { base.Label, _ = report.ParseSwarmServiceNodeID(n.ID) @@ -326,7 +336,7 @@ func swarmServiceNodeSummary(base NodeSummary, n report.Node) NodeSummary { return base } -func hostNodeSummary(base NodeSummary, n report.Node) NodeSummary { +func hostNodeSummary(base BasicNodeSummary, n report.Node) BasicNodeSummary { var ( hostname, _ = report.ParseHostNodeID(n.ID) parts = strings.SplitN(hostname, ".", 2) @@ -339,7 +349,7 @@ func hostNodeSummary(base NodeSummary, n report.Node) NodeSummary { return base } -func weaveNodeSummary(base NodeSummary, n report.Node) NodeSummary { +func weaveNodeSummary(base BasicNodeSummary, n report.Node) BasicNodeSummary { var ( nickname, _ = n.Latest.Lookup(overlay.WeavePeerNickName) _, peerName = report.ParseOverlayNodeID(n.ID) @@ -355,7 +365,7 @@ func weaveNodeSummary(base NodeSummary, n report.Node) NodeSummary { // groupNodeSummary renders the summary for a group node. n.Topology is // expected to be of the form: group:container:hostname -func groupNodeSummary(base NodeSummary, r report.Report, n report.Node) NodeSummary { +func groupNodeSummary(base BasicNodeSummary, r report.Report, n report.Node) BasicNodeSummary { base.Label, base.Rank = n.ID, n.ID if topology, _, ok := render.ParseGroupNodeTopology(n.Topology); ok { if t, ok := r.Topology(topology); ok { diff --git a/render/detailed/summary_test.go b/render/detailed/summary_test.go index 3926f2013..05f9c2b73 100644 --- a/render/detailed/summary_test.go +++ b/render/detailed/summary_test.go @@ -106,11 +106,13 @@ func TestMakeNodeSummary(t *testing.T) { input: expected.RenderedProcesses[fixture.ClientProcess1NodeID], ok: true, want: detailed.NodeSummary{ - ID: fixture.ClientProcess1NodeID, - Label: fixture.Client1Name, - LabelMinor: "client.hostname.com (10001)", - Rank: fixture.Client1Name, - Shape: "square", + BasicNodeSummary: detailed.BasicNodeSummary{ + ID: fixture.ClientProcess1NodeID, + Label: fixture.Client1Name, + LabelMinor: "client.hostname.com (10001)", + Rank: fixture.Client1Name, + Shape: "square", + }, Metadata: []report.MetadataRow{ {ID: process.PID, Label: "PID", Value: fixture.Client1PID, Priority: 1, Datatype: report.Number}, }, @@ -122,12 +124,14 @@ func TestMakeNodeSummary(t *testing.T) { input: expected.RenderedContainers[fixture.ClientContainerNodeID], ok: true, want: detailed.NodeSummary{ - ID: fixture.ClientContainerNodeID, - Label: fixture.ClientContainerName, - LabelMinor: fixture.ClientHostName, - Rank: fixture.ClientContainerImageName, - Shape: "hexagon", - Linkable: true, + BasicNodeSummary: detailed.BasicNodeSummary{ + ID: fixture.ClientContainerNodeID, + Label: fixture.ClientContainerName, + LabelMinor: fixture.ClientHostName, + Rank: fixture.ClientContainerImageName, + Shape: "hexagon", + Linkable: true, + }, Metadata: []report.MetadataRow{ {ID: docker.ImageName, Label: "Image", Value: fixture.ClientContainerImageName, Priority: 1}, {ID: docker.ContainerID, Label: "ID", Value: fixture.ClientContainerID, Priority: 10, Truncate: 12}, @@ -140,13 +144,15 @@ func TestMakeNodeSummary(t *testing.T) { input: expected.RenderedContainerImages[expected.ClientContainerImageNodeID], ok: true, want: detailed.NodeSummary{ - ID: expected.ClientContainerImageNodeID, - Label: fixture.ClientContainerImageName, - LabelMinor: "1 container", - Rank: fixture.ClientContainerImageName, - Shape: "hexagon", - Linkable: true, - Stack: true, + BasicNodeSummary: detailed.BasicNodeSummary{ + ID: expected.ClientContainerImageNodeID, + Label: fixture.ClientContainerImageName, + LabelMinor: "1 container", + Rank: fixture.ClientContainerImageName, + Shape: "hexagon", + Linkable: true, + Stack: true, + }, Metadata: []report.MetadataRow{ {ID: report.Container, Label: "# Containers", Value: "1", Priority: 2, Datatype: report.Number}, }, @@ -158,12 +164,14 @@ func TestMakeNodeSummary(t *testing.T) { input: expected.RenderedHosts[fixture.ClientHostNodeID], ok: true, want: detailed.NodeSummary{ - ID: fixture.ClientHostNodeID, - Label: "client", - LabelMinor: "hostname.com", - Rank: "hostname.com", - Shape: "circle", - Linkable: true, + BasicNodeSummary: detailed.BasicNodeSummary{ + ID: fixture.ClientHostNodeID, + Label: "client", + LabelMinor: "hostname.com", + Rank: "hostname.com", + Shape: "circle", + Linkable: true, + }, Metadata: []report.MetadataRow{ {ID: host.HostName, Label: "Hostname", Value: fixture.ClientHostName, Priority: 11}, }, @@ -175,13 +183,15 @@ func TestMakeNodeSummary(t *testing.T) { input: expected.RenderedProcessNames[fixture.ServerName], ok: true, want: detailed.NodeSummary{ - ID: "apache", - Label: "apache", - LabelMinor: "1 process", - Rank: "apache", - Shape: "square", - Stack: true, - Linkable: true, + BasicNodeSummary: detailed.BasicNodeSummary{ + ID: "apache", + Label: "apache", + LabelMinor: "1 process", + Rank: "apache", + Shape: "square", + Stack: true, + Linkable: true, + }, }, }, } diff --git a/render/detailed/topology_diff_test.go b/render/detailed/topology_diff_test.go index 3a630031a..dbceef44f 100644 --- a/render/detailed/topology_diff_test.go +++ b/render/detailed/topology_diff_test.go @@ -19,17 +19,21 @@ func (r ByID) Less(i, j int) bool { return r[i].ID < r[j].ID } func TestTopoDiff(t *testing.T) { nodea := detailed.NodeSummary{ - ID: "nodea", - Label: "Node A", - LabelMinor: "'ts an a", - Pseudo: false, - Adjacency: report.MakeIDList("nodeb"), + BasicNodeSummary: detailed.BasicNodeSummary{ + ID: "nodea", + Label: "Node A", + LabelMinor: "'ts an a", + Pseudo: false, + }, + Adjacency: report.MakeIDList("nodeb"), } nodeap := nodea nodeap.Adjacency = report.MakeIDList("nodeb", "nodeq") // not the same anymore nodeb := detailed.NodeSummary{ - ID: "nodeb", - Label: "Node B", + BasicNodeSummary: detailed.BasicNodeSummary{ + ID: "nodeb", + Label: "Node B", + }, } // Helper to make RenderableNode maps.