From 10199737e449ea95002cab0ca8222f1c66fe3964 Mon Sep 17 00:00:00 2001 From: Alfonso Acosta Date: Fri, 21 Aug 2015 15:19:26 +0000 Subject: [PATCH] Add host context in the details pane In situations when the details can refer to entities from multiple hosts (e.g. container image) make explicit to what host we are referring to. --- app/api_topology.go | 2 +- app/router.go | 21 ++++++++++-------- render/detailed_node.go | 22 ++++++++++-------- render/detailed_node_test.go | 43 ++++++++++++++++++++++++++++++++---- 4 files changed, 65 insertions(+), 23 deletions(-) diff --git a/app/api_topology.go b/app/api_topology.go index 204ddaa65..198d1b162 100644 --- a/app/api_topology.go +++ b/app/api_topology.go @@ -68,7 +68,7 @@ func handleNode(rep xfer.Reporter, t topologyView, w http.ResponseWriter, r *htt http.NotFound(w, r) return } - respondWith(w, http.StatusOK, APINode{Node: render.MakeDetailedNode(rpt, node)}) + respondWith(w, http.StatusOK, APINode{Node: render.MakeDetailedNode(rpt, node, t.isMultiHost)}) } // Individual edges. diff --git a/app/router.go b/app/router.go index 570f223f6..7eabfc8f7 100644 --- a/app/router.go +++ b/app/router.go @@ -107,9 +107,10 @@ var topologyRegistry = map[string]topologyView{ renderer: render.FilterUnconnected{Renderer: render.ProcessWithContainerNameRenderer{}}, }, "applications-by-name": { - human: "by name", - parent: "applications", - renderer: render.FilterUnconnected{Renderer: render.ProcessNameRenderer}, + human: "by name", + parent: "applications", + renderer: render.FilterUnconnected{Renderer: render.ProcessNameRenderer}, + isMultiHost: true, }, "containers": { human: "Containers", @@ -117,9 +118,10 @@ var topologyRegistry = map[string]topologyView{ renderer: render.ContainerRenderer, }, "containers-by-image": { - human: "by image", - parent: "containers", - renderer: render.ContainerImageRenderer, + human: "by image", + parent: "containers", + renderer: render.ContainerImageRenderer, + isMultiHost: true, }, "hosts": { human: "Hosts", @@ -129,7 +131,8 @@ var topologyRegistry = map[string]topologyView{ } type topologyView struct { - human string - parent string - renderer render.Renderer + human string + parent string + renderer render.Renderer + isMultiHost bool // does the view involve information from multiple hosts? } diff --git a/render/detailed_node.go b/render/detailed_node.go index 2d7e9f34b..abb097c24 100644 --- a/render/detailed_node.go +++ b/render/detailed_node.go @@ -72,7 +72,7 @@ func (t tables) Less(i, j int) bool { return t[i].Rank > t[j].Rank } // MakeDetailedNode transforms a renderable node to a detailed node. It uses // aggregate metadata, plus the set of origin node IDs, to produce tables. -func MakeDetailedNode(r report.Report, n RenderableNode) DetailedNode { +func MakeDetailedNode(r report.Report, n RenderableNode, addHostTags bool) DetailedNode { tables := tables{} // RenderableNode may be the result of merge operation(s), and so may have // multiple origins. The ultimate goal here is to generate tables to view @@ -80,7 +80,7 @@ func MakeDetailedNode(r report.Report, n RenderableNode) DetailedNode { // add them later. connections := []Row{} for _, id := range n.Origins { - if table, ok := OriginTable(r, id); ok { + if table, ok := OriginTable(r, id, addHostTags); ok { tables = append(tables, table) } else if _, ok := r.Endpoint.NodeMetadatas[id]; ok { connections = append(connections, connectionDetailsRows(r.Endpoint, id)...) @@ -155,12 +155,12 @@ func addConnectionsTable(tables *tables, connections []Row, r report.Report, n R // OriginTable produces a table (to be consumed directly by the UI) based on // an origin ID, which is (optimistically) a node ID in one of our topologies. -func OriginTable(r report.Report, originID string) (Table, bool) { +func OriginTable(r report.Report, originID string, addHostTags bool) (Table, bool) { if nmd, ok := r.Process.NodeMetadatas[originID]; ok { - return processOriginTable(nmd) + return processOriginTable(nmd, addHostTags) } if nmd, ok := r.Container.NodeMetadatas[originID]; ok { - return containerOriginTable(nmd) + return containerOriginTable(nmd, addHostTags) } if nmd, ok := r.ContainerImage.NodeMetadatas[originID]; ok { return containerImageOriginTable(nmd) @@ -224,7 +224,7 @@ func connectionDetailsRows(topology report.Topology, originID string) []Row { return rows } -func processOriginTable(nmd report.NodeMetadata) (Table, bool) { +func processOriginTable(nmd report.NodeMetadata, addHostTag bool) (Table, bool) { rows := []Row{} for _, tuple := range []struct{ key, human string }{ {process.Comm, "Name"}, @@ -237,7 +237,9 @@ func processOriginTable(nmd report.NodeMetadata) (Table, bool) { rows = append(rows, Row{Key: tuple.human, ValueMajor: val, ValueMinor: ""}) } } - + if addHostTag { + rows = append([]Row{{Key: "Host", ValueMajor: report.ExtractHostID(nmd)}}, rows...) + } return Table{ Title: "Origin Process", Numeric: false, @@ -246,7 +248,7 @@ func processOriginTable(nmd report.NodeMetadata) (Table, bool) { }, len(rows) > 0 } -func containerOriginTable(nmd report.NodeMetadata) (Table, bool) { +func containerOriginTable(nmd report.NodeMetadata, addHostTag bool) (Table, bool) { rows := []Row{} for _, tuple := range []struct{ key, human string }{ {docker.ContainerID, "ID"}, @@ -268,7 +270,9 @@ func containerOriginTable(nmd report.NodeMetadata) (Table, bool) { rows = append(rows, Row{Key: "Memory Usage (MB):", ValueMajor: memoryStr, ValueMinor: ""}) } } - + if addHostTag { + rows = append([]Row{{Key: "Host", ValueMajor: report.ExtractHostID(nmd)}}, rows...) + } return Table{ Title: "Origin Container", Numeric: false, diff --git a/render/detailed_node_test.go b/render/detailed_node_test.go index 11742d62e..4cba8359a 100644 --- a/render/detailed_node_test.go +++ b/render/detailed_node_test.go @@ -10,7 +10,7 @@ import ( ) func TestOriginTable(t *testing.T) { - if _, ok := render.OriginTable(test.Report, "not-found"); ok { + if _, ok := render.OriginTable(test.Report, "not-found", false); ok { t.Errorf("unknown origin ID gave unexpected success") } for originID, want := range map[string]render.Table{ @@ -34,7 +34,7 @@ func TestOriginTable(t *testing.T) { }, }, } { - have, ok := render.OriginTable(test.Report, originID) + have, ok := render.OriginTable(test.Report, originID, false) if !ok { t.Errorf("%q: not OK", originID) continue @@ -43,11 +43,46 @@ func TestOriginTable(t *testing.T) { t.Errorf("%q: %s", originID, test.Diff(want, have)) } } + + // Test host tags + for originID, want := range map[string]render.Table{ + test.ServerProcessNodeID: { + Title: "Origin Process", + Numeric: false, + Rank: 2, + Rows: []render.Row{ + {"Host", test.ServerHostID, "", false}, + {"Name", "apache", "", false}, + {"PID", test.ServerPID, "", false}, + }, + }, + test.ServerContainerNodeID: { + Title: "Origin Container", + Numeric: false, + Rank: 3, + Rows: []render.Row{ + {"Host", test.ServerHostID, "", false}, + {"ID", test.ServerContainerID, "", false}, + {"Name", "server", "", false}, + {"Image ID", test.ServerContainerImageID, "", false}, + }, + }, + } { + have, ok := render.OriginTable(test.Report, originID, true) + if !ok { + t.Errorf("%q: not OK", originID) + continue + } + if !reflect.DeepEqual(want, have) { + t.Errorf("%q: %s", originID, test.Diff(want, have)) + } + } + } func TestMakeDetailedHostNode(t *testing.T) { renderableNode := render.HostRenderer.Render(test.Report)[render.MakeHostID(test.ClientHostID)] - have := render.MakeDetailedNode(test.Report, renderableNode) + have := render.MakeDetailedNode(test.Report, renderableNode, false) want := render.DetailedNode{ ID: render.MakeHostID(test.ClientHostID), LabelMajor: "client", @@ -109,7 +144,7 @@ func TestMakeDetailedHostNode(t *testing.T) { func TestMakeDetailedContainerNode(t *testing.T) { renderableNode := render.ContainerRenderer.Render(test.Report)[test.ServerContainerID] - have := render.MakeDetailedNode(test.Report, renderableNode) + have := render.MakeDetailedNode(test.Report, renderableNode, false) want := render.DetailedNode{ ID: test.ServerContainerID, LabelMajor: "server",