From 7b772101080921f5987b91e552ec88ed87ebd089 Mon Sep 17 00:00:00 2001 From: David Kaltschmidt Date: Wed, 24 Feb 2016 12:44:59 +0100 Subject: [PATCH 1/6] Render connection table as nodes table Also: - Make metadata fields sortable as numbers - Sort by selected metadata column --- client/app/scripts/components/node-details.js | 8 ++++++++ .../node-details/node-details-table.js | 18 +++++++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/client/app/scripts/components/node-details.js b/client/app/scripts/components/node-details.js index 44f1b30da6..1d8fec325e 100644 --- a/client/app/scripts/components/node-details.js +++ b/client/app/scripts/components/node-details.js @@ -174,6 +174,14 @@ export default class NodeDetails extends React.Component { } + {details.connections && details.connections.map(connections => { + return ( +
+ +
+ ); + })} + {details.children && details.children.map(children => { return (
diff --git a/client/app/scripts/components/node-details/node-details-table.js b/client/app/scripts/components/node-details/node-details-table.js index a5890f08d2..e734202587 100644 --- a/client/app/scripts/components/node-details/node-details-table.js +++ b/client/app/scripts/components/node-details/node-details-table.js @@ -5,6 +5,10 @@ import ShowMore from '../show-more'; import NodeDetailsTableNodeLink from './node-details-table-node-link'; import NodeDetailsTableNodeMetric from './node-details-table-node-metric'; +function isNumberField(field) { + return field.dataType && field.dataType === 'number'; +} + export default class NodeDetailsTable extends React.Component { constructor(props, context) { @@ -39,7 +43,16 @@ export default class NodeDetailsTable extends React.Component { getMetaDataSorters() { // returns an array of sorters that will take a node return _.get(this.props.nodes, [0, 'metadata'], []).map((field, index) => { - return node => node.metadata[index] ? node.metadata[index].value : null; + return node => { + const nodeMetadataField = node.metadata[index]; + if (nodeMetadataField) { + if (isNumberField(nodeMetadataField)) { + return parseFloat(nodeMetadataField.value); + } + return nodeMetadataField.value; + } + return null; + }; }); } @@ -49,6 +62,9 @@ export default class NodeDetailsTable extends React.Component { if (sortBy !== null) { const field = _.union(node.metrics, node.metadata).find(f => f.id === sortBy); if (field) { + if (isNumberField(field)) { + return parseFloat(field.value); + } return field.value; } } From a40b5a31a8cc7b05b644e02b4bac9eca3a690bd8 Mon Sep 17 00:00:00 2001 From: David Kaltschmidt Date: Wed, 24 Feb 2016 17:38:51 +0100 Subject: [PATCH 2/6] Default sorter specified by table columns --- .../scripts/components/node-details/node-details-table.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/client/app/scripts/components/node-details/node-details-table.js b/client/app/scripts/components/node-details/node-details-table.js index e734202587..82303eb585 100644 --- a/client/app/scripts/components/node-details/node-details-table.js +++ b/client/app/scripts/components/node-details/node-details-table.js @@ -36,7 +36,12 @@ export default class NodeDetailsTable extends React.Component { } getDefaultSortBy() { - // first metric + // default sorter specified by columns + const defaultSortColumn = _.find(this.props.columns, {defaultSort: true}); + if (defaultSortColumn) { + return defaultSortColumn.id; + } + // otherwise choose first metric return _.get(this.props.nodes, [0, 'metrics', 0, 'id']); } From f154e7a483dbe5073152a44a3e132e1c969d4ef6 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Tue, 23 Feb 2016 18:21:08 +0000 Subject: [PATCH 3/6] Add connection tables to details panel --- app/api_topology.go | 7 +- app/api_topology_test.go | 25 +- app/router.go | 2 +- integration/300_internet_edge_test.sh | 6 +- render/detailed/connections.go | 197 +++++++ render/detailed/metadata.go | 39 +- render/detailed/node.go | 39 +- render/detailed/node_test.go | 153 ++++- render/detailed/summary.go | 38 +- render/expected/expected.go | 527 ++++++++++++++---- render/id.go | 15 + render/mapping.go | 188 ++++--- render/renderable_node.go | 23 +- .../renderable_node_set.go | 94 ++-- render/renderable_node_set_test.go | 272 +++++++++ render/renderable_node_test.go | 6 +- render/short_lived_connections_test.go | 2 +- render/topologies.go | 5 +- render/topologies_test.go | 47 +- report/counters.go | 2 +- report/edge_metadatas.go | 25 +- report/id_list.go | 10 +- report/latest_map.go | 2 +- report/node_set_test.go | 231 -------- report/sets.go | 2 +- report/string_set.go | 16 + 26 files changed, 1347 insertions(+), 626 deletions(-) create mode 100644 render/detailed/connections.go rename report/node_set.go => render/renderable_node_set.go (52%) create mode 100644 render/renderable_node_set_test.go delete mode 100644 report/node_set_test.go diff --git a/app/api_topology.go b/app/api_topology.go index 16f03f192c..639ecfb5f7 100644 --- a/app/api_topology.go +++ b/app/api_topology.go @@ -51,17 +51,18 @@ func handleWs(ctx context.Context, rep Reporter, renderer render.Renderer, w htt } // Individual nodes. -func handleNode(nodeID string) func(context.Context, Reporter, render.Renderer, http.ResponseWriter, *http.Request) { +func handleNode(topologyID, nodeID string) func(context.Context, Reporter, render.Renderer, http.ResponseWriter, *http.Request) { return func(ctx context.Context, rep Reporter, renderer render.Renderer, w http.ResponseWriter, r *http.Request) { var ( rpt = rep.Report(ctx) - node, ok = renderer.Render(rep.Report(ctx))[nodeID] + rendered = renderer.Render(rep.Report(ctx)) + node, ok = rendered[nodeID] ) if !ok { http.NotFound(w, r) return } - respondWith(w, http.StatusOK, APINode{Node: detailed.MakeNode(rpt, node)}) + respondWith(w, http.StatusOK, APINode{Node: detailed.MakeNode(topologyID, rpt, rendered, node)}) } } diff --git a/app/api_topology_test.go b/app/api_topology_test.go index 7b4f58f23f..7c3ae88c02 100644 --- a/app/api_topology_test.go +++ b/app/api_topology_test.go @@ -54,27 +54,6 @@ func TestAll(t *testing.T) { } } -func TestAPITopologyContainers(t *testing.T) { - ts := topologyServer() - { - body := getRawJSON(t, ts, "/api/topology/containers") - var topo app.APITopology - decoder := codec.NewDecoderBytes(body, &codec.JsonHandle{}) - if err := decoder.Decode(&topo); err != nil { - t.Fatal(err) - } - want := expected.RenderedContainers.Copy() - for id, node := range want { - node.ControlNode = "" - want[id] = node - } - - if have := topo.Nodes.Prune(); !reflect.DeepEqual(want, have) { - t.Error(test.Diff(want, have)) - } - } -} - func TestAPITopologyProcesses(t *testing.T) { ts := topologyServer() defer ts.Close() @@ -124,13 +103,13 @@ func TestAPITopologyHosts(t *testing.T) { } } { - body := getRawJSON(t, ts, "/api/topology/hosts/"+expected.ServerHostRenderedID) + body := getRawJSON(t, ts, "/api/topology/hosts/"+expected.ServerHostID) var node app.APINode decoder := codec.NewDecoderBytes(body, &codec.JsonHandle{}) if err := decoder.Decode(&node); err != nil { t.Fatal(err) } - equals(t, expected.ServerHostRenderedID, node.Node.ID) + equals(t, expected.ServerHostID, node.Node.ID) equals(t, "server", node.Node.Label) equals(t, false, node.Node.Pseudo) // Let's not unit-test the specific content of the detail tables diff --git a/app/router.go b/app/router.go index a414950fdf..7964861f68 100644 --- a/app/router.go +++ b/app/router.go @@ -122,7 +122,7 @@ func TopologyHandler(c Reporter, preRoutes *mux.Router, postRoutes http.Handler) } handler := gzipHandler(requestContextDecorator(topologyRegistry.captureRendererWithoutFilters( - c, topologyID, handleNode(nodeID), + c, topologyID, handleNode(topologyID, nodeID), ))) handler.ServeHTTP(w, r) }) diff --git a/integration/300_internet_edge_test.sh b/integration/300_internet_edge_test.sh index 9fefa703b9..eff59e43cb 100755 --- a/integration/300_internet_edge_test.sh +++ b/integration/300_internet_edge_test.sh @@ -22,11 +22,11 @@ do_connections() { } do_connections& -wait_for_containers $HOST1 60 nginx "Inbound" +wait_for_containers $HOST1 60 nginx "The Internet" has_container $HOST1 nginx -has_container $HOST1 "Inbound" -has_connection containers $HOST1 "Inbound" nginx +has_container $HOST1 "The Internet" +has_connection containers $HOST1 "The Internet" nginx kill %do_connections diff --git a/render/detailed/connections.go b/render/detailed/connections.go new file mode 100644 index 0000000000..fe7fa00cdd --- /dev/null +++ b/render/detailed/connections.go @@ -0,0 +1,197 @@ +package detailed + +import ( + "sort" + "strconv" + + "github.com/weaveworks/scope/render" + "github.com/weaveworks/scope/report" +) + +const ( + portKey = "port" + portLabel = "Port" + countKey = "count" + countLabel = "Count" + number = "number" +) + +// Exported for testing +var ( + NormalColumns = []Column{ + {ID: portKey, Label: portLabel}, + {ID: countKey, Label: countLabel, DefaultSort: true}, + } + InternetColumns = []Column{ + {ID: "foo", Label: "Remote"}, + {ID: portKey, Label: portLabel}, + {ID: countKey, Label: countLabel, DefaultSort: true}, + } +) + +func endpointChildrenOf(n render.RenderableNode) []render.RenderableNode { + result := []render.RenderableNode{} + n.Children.ForEach(func(child render.RenderableNode) { + if _, _, _, ok := render.ParseEndpointID(child.ID); ok { + result = append(result, child) + } + }) + return result +} + +func endpointChildIDsOf(n render.RenderableNode) report.IDList { + result := report.MakeIDList() + n.Children.ForEach(func(child render.RenderableNode) { + if _, _, _, ok := render.ParseEndpointID(child.ID); ok { + result = append(result, child.ID) + } + }) + return result +} + +type connectionsRow struct { + remoteNode, localNode *render.RenderableNode + remoteAddr, localAddr string + port string // always the server-side port +} + +func buildConnectionRows(in map[connectionsRow]int, includeLocal bool) []NodeSummary { + nodes := []NodeSummary{} + for row, count := range in { + id, label, linkable := row.remoteNode.ID, row.remoteNode.LabelMajor, true + if row.remoteAddr != "" { + id, label, linkable = row.remoteAddr+":"+row.port, row.remoteAddr, false + } + metadata := []MetadataRow{} + if includeLocal { + metadata = append(metadata, + MetadataRow{ + ID: "foo", + Value: row.localAddr, + Datatype: number, + }) + } + metadata = append(metadata, + MetadataRow{ + ID: portKey, + Value: row.port, + Datatype: number, + }, + MetadataRow{ + ID: countKey, + Value: strconv.Itoa(count), + Datatype: number, + }, + ) + nodes = append(nodes, NodeSummary{ + ID: id, + Label: label, + Linkable: linkable, + Metadata: metadata, + }) + } + sort.Sort(nodeSummariesByID(nodes)) + return nodes +} + +func isInternetNode(n render.RenderableNode) bool { + return n.ID == render.IncomingInternetID || n.ID == render.OutgoingInternetID +} + +func makeIncomingConnectionsTable(topologyID string, n render.RenderableNode, ns render.RenderableNodes) NodeSummaryGroup { + localEndpointIDs := endpointChildIDsOf(n) + + // For each node which has an edge TO me + counts := map[connectionsRow]int{} + for _, node := range ns { + if !node.Adjacency.Contains(n.ID) { + continue + } + remoteNode := node.Copy() + + // Work out what port they are talking to, and count the number of + // connections to that port. + // This is complicated as for internet nodes we break out individual + // address, both when the internet node is remote (an incoming + // connection from the internet) and 'local' (ie you are loading + // details on the internet node) + for _, child := range endpointChildrenOf(node) { + for _, localEndpointID := range child.Adjacency.Intersection(localEndpointIDs) { + _, localAddr, port, ok := render.ParseEndpointID(localEndpointID) + if !ok { + continue + } + key := connectionsRow{ + localNode: &n, + remoteNode: &remoteNode, + port: port, + } + if isInternetNode(n) { + key.localAddr = localAddr + } + counts[key] = counts[key] + 1 + } + } + } + + columnHeaders := NormalColumns + if isInternetNode(n) { + columnHeaders = InternetColumns + } + return NodeSummaryGroup{ + ID: "incoming-connections", + TopologyID: topologyID, + Label: "Inbound", + Columns: columnHeaders, + Nodes: buildConnectionRows(counts, isInternetNode(n)), + } +} + +func makeOutgoingConnectionsTable(topologyID string, n render.RenderableNode, ns render.RenderableNodes) NodeSummaryGroup { + localEndpoints := endpointChildrenOf(n) + + // For each node which has an edge FROM me + counts := map[connectionsRow]int{} + for _, node := range ns { + if !n.Adjacency.Contains(node.ID) { + continue + } + remoteNode := node.Copy() + remoteEndpointIDs := endpointChildIDsOf(remoteNode) + + for _, localEndpoint := range localEndpoints { + _, localAddr, _, ok := render.ParseEndpointID(localEndpoint.ID) + if !ok { + continue + } + + for _, remoteEndpointID := range localEndpoint.Adjacency.Intersection(remoteEndpointIDs) { + _, _, port, ok := render.ParseEndpointID(remoteEndpointID) + if !ok { + continue + } + key := connectionsRow{ + localNode: &n, + remoteNode: &remoteNode, + port: port, + } + if isInternetNode(n) { + key.localAddr = localAddr + } + counts[key] = counts[key] + 1 + } + } + } + + columnHeaders := NormalColumns + if isInternetNode(n) { + columnHeaders = InternetColumns + } + return NodeSummaryGroup{ + ID: "outgoing-connections", + TopologyID: topologyID, + Label: "Outbound", + Columns: columnHeaders, + Nodes: buildConnectionRows(counts, isInternetNode(n)), + } +} diff --git a/render/detailed/metadata.go b/render/detailed/metadata.go index a7ea65d5e6..c04d6c2da0 100644 --- a/render/detailed/metadata.go +++ b/render/detailed/metadata.go @@ -98,16 +98,22 @@ type Counter struct { // MetadataRows implements MetadataRowTemplate func (c Counter) MetadataRows(n report.Node) []MetadataRow { if val, ok := n.Counters.Lookup(c.ID); ok { - return []MetadataRow{{ID: c.ID, Value: strconv.Itoa(val), Prime: c.Prime}} + return []MetadataRow{{ + ID: c.ID, + Value: strconv.Itoa(val), + Prime: c.Prime, + Datatype: number, + }} } return nil } // MetadataRow is a row for the metadata table. type MetadataRow struct { - ID string - Value string - Prime bool + ID string + Value string + Prime bool + Datatype string } // Copy returns a value copy of a metadata row. @@ -129,20 +135,22 @@ func (*MetadataRow) UnmarshalJSON(b []byte) error { } type labelledMetadataRow struct { - ID string `json:"id"` - Label string `json:"label"` - Value string `json:"value"` - Prime bool `json:"prime,omitempty"` + ID string `json:"id"` + Label string `json:"label"` + Value string `json:"value"` + Prime bool `json:"prime,omitempty"` + Datatype string `json:"dataType,omitempty"` } // CodecEncodeSelf marshals this MetadataRow. It adds a label before // rendering. func (m *MetadataRow) CodecEncodeSelf(encoder *codec.Encoder) { in := labelledMetadataRow{ - ID: m.ID, - Label: Label(m.ID), - Value: m.Value, - Prime: m.Prime, + ID: m.ID, + Label: Label(m.ID), + Value: m.Value, + Prime: m.Prime, + Datatype: m.Datatype, } encoder.Encode(in) } @@ -152,9 +160,10 @@ func (m *MetadataRow) CodecDecodeSelf(decoder *codec.Decoder) { var in labelledMetadataRow decoder.Decode(&in) *m = MetadataRow{ - ID: in.ID, - Value: in.Value, - Prime: in.Prime, + ID: in.ID, + Value: in.Value, + Prime: in.Prime, + Datatype: in.Datatype, } } diff --git a/render/detailed/node.go b/render/detailed/node.go index 38da896eb3..eb4fecca23 100644 --- a/render/detailed/node.go +++ b/render/detailed/node.go @@ -16,11 +16,12 @@ import ( // we want deep information about an individual node. type Node struct { NodeSummary - Rank string `json:"rank,omitempty"` - Pseudo bool `json:"pseudo,omitempty"` - Controls []ControlInstance `json:"controls"` - Children []NodeSummaryGroup `json:"children,omitempty"` - Parents []Parent `json:"parents,omitempty"` + Rank string `json:"rank,omitempty"` + Pseudo bool `json:"pseudo,omitempty"` + Controls []ControlInstance `json:"controls"` + Children []NodeSummaryGroup `json:"children,omitempty"` + Parents []Parent `json:"parents,omitempty"` + Connections []NodeSummaryGroup `json:"connections,omitempty"` } // ControlInstance contains a control description, and all the info @@ -78,10 +79,11 @@ func (c *ControlInstance) CodecDecodeSelf(decoder *codec.Decoder) { // MakeNode transforms a renderable node to a detailed node. It uses // aggregate metadata, plus the set of origin node IDs, to produce tables. -func MakeNode(r report.Report, n render.RenderableNode) Node { - summary, _ := MakeNodeSummary(n.Node) +func MakeNode(topologyID string, r report.Report, ns render.RenderableNodes, n render.RenderableNode) Node { + summary, _ := MakeNodeSummary(n) summary.ID = n.ID summary.Label = n.LabelMajor + return Node{ NodeSummary: summary, Rank: n.Rank, @@ -89,6 +91,10 @@ func MakeNode(r report.Report, n render.RenderableNode) Node { Controls: controls(r, n), Children: children(n), Parents: Parents(r, n), + Connections: []NodeSummaryGroup{ + makeIncomingConnectionsTable(topologyID, n, ns), + makeOutgoingConnectionsTable(topologyID, n, ns), + }, } } @@ -133,17 +139,25 @@ var ( topologyID string NodeSummaryGroup }{ - {report.Host, NodeSummaryGroup{TopologyID: "hosts", Label: "Hosts", Columns: []Column{host.CPUUsage, host.MemoryUsage}}}, + {report.Host, NodeSummaryGroup{TopologyID: "hosts", Label: "Hosts", Columns: []Column{ + MakeColumn(host.CPUUsage), MakeColumn(host.MemoryUsage), + }}}, {report.Pod, NodeSummaryGroup{TopologyID: "pods", Label: "Pods"}}, - {report.Container, NodeSummaryGroup{TopologyID: "containers", Label: "Containers", Columns: []Column{docker.CPUTotalUsage, docker.MemoryUsage}}}, - {report.Process, NodeSummaryGroup{TopologyID: "processes", Label: "Processes", Columns: []Column{process.PID, process.CPUUsage, process.MemoryUsage}}}, - {report.ContainerImage, NodeSummaryGroup{TopologyID: "containers-by-image", Label: "Container Images", Columns: []Column{render.ContainersKey}}}, + {report.Container, NodeSummaryGroup{TopologyID: "containers", Label: "Containers", Columns: []Column{ + MakeColumn(docker.CPUTotalUsage), MakeColumn(docker.MemoryUsage), + }}}, + {report.Process, NodeSummaryGroup{TopologyID: "processes", Label: "Processes", Columns: []Column{ + MakeColumn(process.PID), MakeColumn(process.CPUUsage), MakeColumn(process.MemoryUsage), + }}}, + {report.ContainerImage, NodeSummaryGroup{TopologyID: "containers-by-image", Label: "Container Images", Columns: []Column{ + MakeColumn(render.ContainersKey), + }}}, } ) func children(n render.RenderableNode) []NodeSummaryGroup { summaries := map[string][]NodeSummary{} - n.Children.ForEach(func(child report.Node) { + n.Children.ForEach(func(child render.RenderableNode) { if child.ID != n.ID { if summary, ok := MakeNodeSummary(child); ok { summaries[child.Topology] = append(summaries[child.Topology], summary) @@ -160,5 +174,6 @@ func children(n render.RenderableNode) []NodeSummaryGroup { nodeSummaryGroups = append(nodeSummaryGroups, group) } } + return nodeSummaryGroups } diff --git a/render/detailed/node_test.go b/render/detailed/node_test.go index b5fadae75b..c4df7dc665 100644 --- a/render/detailed/node_test.go +++ b/render/detailed/node_test.go @@ -10,21 +10,29 @@ import ( "github.com/weaveworks/scope/probe/process" "github.com/weaveworks/scope/render" "github.com/weaveworks/scope/render/detailed" + "github.com/weaveworks/scope/render/expected" "github.com/weaveworks/scope/test" "github.com/weaveworks/scope/test/fixture" ) func TestMakeDetailedHostNode(t *testing.T) { - renderableNode := render.HostRenderer.Render(fixture.Report)[render.MakeHostID(fixture.ClientHostID)] - have := detailed.MakeNode(fixture.Report, renderableNode) + renderableNodes := render.HostRenderer.Render(fixture.Report) + renderableNode := renderableNodes[render.MakeHostID(fixture.ClientHostID)] + have := detailed.MakeNode("hosts", fixture.Report, renderableNodes, renderableNode) containerImageNodeSummary, _ := detailed.MakeNodeSummary( - render.ContainerImageRenderer.Render(fixture.Report)[render.MakeContainerImageID(fixture.ClientContainerImageName)].Node, + render.ContainerImageRenderer.Render(fixture.Report)[expected.ClientContainerImageID], + ) + containerNodeSummary, _ := detailed.MakeNodeSummary( + render.ContainerRenderer.Render(fixture.Report)[expected.ClientContainerID], + ) + process1NodeSummary, _ := detailed.MakeNodeSummary( + render.ProcessRenderer.Render(fixture.Report)[expected.ClientProcess1ID], ) - containerNodeSummary, _ := detailed.MakeNodeSummary(fixture.Report.Container.Nodes[fixture.ClientContainerNodeID]) - process1NodeSummary, _ := detailed.MakeNodeSummary(fixture.Report.Process.Nodes[fixture.ClientProcess1NodeID]) process1NodeSummary.Linkable = true - process2NodeSummary, _ := detailed.MakeNodeSummary(fixture.Report.Process.Nodes[fixture.ClientProcess2NodeID]) + process2NodeSummary, _ := detailed.MakeNodeSummary( + render.ProcessRenderer.Render(fixture.Report)[expected.ClientProcess2ID], + ) process2NodeSummary.Linkable = true want := detailed.Node{ NodeSummary: detailed.NodeSummary{ @@ -85,22 +93,56 @@ func TestMakeDetailedHostNode(t *testing.T) { { Label: "Containers", TopologyID: "containers", - Columns: []detailed.Column{docker.CPUTotalUsage, docker.MemoryUsage}, + Columns: []detailed.Column{detailed.MakeColumn(docker.CPUTotalUsage), detailed.MakeColumn(docker.MemoryUsage)}, Nodes: []detailed.NodeSummary{containerNodeSummary}, }, { Label: "Processes", TopologyID: "processes", - Columns: []detailed.Column{process.PID, process.CPUUsage, process.MemoryUsage}, + Columns: []detailed.Column{detailed.MakeColumn(process.PID), detailed.MakeColumn(process.CPUUsage), detailed.MakeColumn(process.MemoryUsage)}, Nodes: []detailed.NodeSummary{process1NodeSummary, process2NodeSummary}, }, { Label: "Container Images", TopologyID: "containers-by-image", - Columns: []detailed.Column{render.ContainersKey}, + Columns: []detailed.Column{detailed.MakeColumn(render.ContainersKey)}, Nodes: []detailed.NodeSummary{containerImageNodeSummary}, }, }, + Connections: []detailed.NodeSummaryGroup{ + { + ID: "incoming-connections", + TopologyID: "hosts", + Label: "Inbound", + Columns: detailed.NormalColumns, + Nodes: []detailed.NodeSummary{}, + }, + { + ID: "outgoing-connections", + TopologyID: "hosts", + Label: "Outbound", + Columns: detailed.NormalColumns, + Nodes: []detailed.NodeSummary{ + { + ID: "host:server.hostname.com", + Label: "server", + Linkable: true, + Metadata: []detailed.MetadataRow{ + { + ID: "port", + Value: "80", + Datatype: "number", + }, + { + ID: "count", + Value: "2", + Datatype: "number", + }, + }, + }, + }, + }, + }, } if !reflect.DeepEqual(want, have) { t.Errorf("%s", test.Diff(want, have)) @@ -109,11 +151,12 @@ func TestMakeDetailedHostNode(t *testing.T) { func TestMakeDetailedContainerNode(t *testing.T) { id := render.MakeContainerID(fixture.ServerContainerID) - renderableNode, ok := render.ContainerRenderer.Render(fixture.Report)[id] + renderableNodes := render.ContainerRenderer.Render(fixture.Report) + renderableNode, ok := renderableNodes[id] if !ok { t.Fatalf("Node not found: %s", id) } - have := detailed.MakeNode(fixture.Report, renderableNode) + have := detailed.MakeNode("containers", fixture.Report, renderableNodes, renderableNode) want := detailed.Node{ NodeSummary: detailed.NodeSummary{ ID: id, @@ -145,14 +188,13 @@ func TestMakeDetailedContainerNode(t *testing.T) { }, }, }, - Rank: "imageid456", Pseudo: false, Controls: []detailed.ControlInstance{}, Children: []detailed.NodeSummaryGroup{ { Label: "Processes", TopologyID: "processes", - Columns: []detailed.Column{process.PID, process.CPUUsage, process.MemoryUsage}, + Columns: []detailed.Column{detailed.MakeColumn(process.PID), detailed.MakeColumn(process.CPUUsage), detailed.MakeColumn(process.MemoryUsage)}, Nodes: []detailed.NodeSummary{ { ID: fmt.Sprintf("process:%s:%s", "server.hostname.com", fixture.ServerPID), @@ -178,6 +220,91 @@ func TestMakeDetailedContainerNode(t *testing.T) { TopologyID: "hosts", }, }, + Connections: []detailed.NodeSummaryGroup{ + { + ID: "incoming-connections", + TopologyID: "containers", + Label: "Inbound", + Columns: detailed.NormalColumns, + Nodes: []detailed.NodeSummary{ + { + ID: "container:a1b2c3d4e5", + Label: "client", + Linkable: true, + Metadata: []detailed.MetadataRow{ + { + ID: "port", + Value: "80", + Datatype: "number", + }, + { + ID: "count", + Value: "2", + Datatype: "number", + }, + }, + }, + { + ID: "in-theinternet", + Label: "The Internet", + Linkable: true, + Metadata: []detailed.MetadataRow{ + { + ID: "port", + Value: "80", + Datatype: "number", + }, + { + ID: "count", + Value: "1", + Datatype: "number", + }, + }, + }, + { + ID: "pseudo:10.10.10.10:192.168.1.1:80", + Label: "10.10.10.10", + Linkable: true, + Metadata: []detailed.MetadataRow{ + { + ID: "port", + Value: "80", + Datatype: "number", + }, + { + ID: "count", + Value: "2", + Datatype: "number", + }, + }, + }, + { + ID: "pseudo:10.10.10.11:192.168.1.1:80", + Label: "10.10.10.11", + Linkable: true, + Metadata: []detailed.MetadataRow{ + { + ID: "port", + Value: "80", + Datatype: "number", + }, + { + ID: "count", + Value: "1", + Datatype: "number", + }, + }, + }, + }, + }, + { + ID: "outgoing-connections", + TopologyID: "containers", + Label: "Outbound", + Columns: detailed.NormalColumns, + Nodes: []detailed.NodeSummary{}, + }, + }, } if !reflect.DeepEqual(want, have) { t.Errorf("%s", test.Diff(want, have)) diff --git a/render/detailed/summary.go b/render/detailed/summary.go index 8cc65c0549..473db69c68 100644 --- a/render/detailed/summary.go +++ b/render/detailed/summary.go @@ -3,8 +3,6 @@ package detailed import ( "fmt" - "github.com/ugorji/go/codec" - "github.com/weaveworks/scope/probe/docker" "github.com/weaveworks/scope/probe/host" "github.com/weaveworks/scope/probe/kubernetes" @@ -15,6 +13,7 @@ import ( // NodeSummaryGroup is a topology-typed group of children for a Node. type NodeSummaryGroup struct { + ID string `json:"id"` Label string `json:"label"` Nodes []NodeSummary `json:"nodes"` TopologyID string `json:"topologyId"` @@ -36,29 +35,18 @@ func (g NodeSummaryGroup) Copy() NodeSummaryGroup { // Column provides special json serialization for column ids, so they include // their label for the frontend. -type Column string - -// CodecEncodeSelf implements codec.Selfer -func (c *Column) CodecEncodeSelf(encoder *codec.Encoder) { - in := map[string]string{"id": string(*c), "label": Label(string(*c))} - encoder.Encode(in) -} - -// CodecDecodeSelf implements codec.Selfer -func (c *Column) CodecDecodeSelf(decoder *codec.Decoder) { - m := map[string]string{} - decoder.Decode(&m) - *c = Column(m["id"]) +type Column struct { + ID string `json:"id"` + Label string `json:"label"` + DefaultSort bool `json:"defaultSort"` } -// MarshalJSON shouldn't be used, use CodecEncodeSelf instead -func (Column) MarshalJSON() ([]byte, error) { - panic("MarshalJSON shouldn't be used, use CodecEncodeSelf instead") -} - -// UnmarshalJSON shouldn't be used, use CodecDecodeSelf instead -func (*Column) UnmarshalJSON(b []byte) error { - panic("UnmarshalJSON shouldn't be used, use CodecDecodeSelf instead") +// MakeColumn makes a Column by looking up the label by id. +func MakeColumn(id string) Column { + return Column{ + ID: id, + Label: Label(id), + } } // NodeSummary is summary information about a child for a Node. @@ -72,7 +60,7 @@ type NodeSummary struct { } // MakeNodeSummary summarizes a node, if possible. -func MakeNodeSummary(n report.Node) (NodeSummary, bool) { +func MakeNodeSummary(n render.RenderableNode) (NodeSummary, bool) { renderers := map[string]func(report.Node) NodeSummary{ report.Process: processNodeSummary, report.Container: containerNodeSummary, @@ -81,7 +69,7 @@ func MakeNodeSummary(n report.Node) (NodeSummary, bool) { report.Host: hostNodeSummary, } if renderer, ok := renderers[n.Topology]; ok { - return renderer(n), true + return renderer(n.Node), true } return NodeSummary{}, false } diff --git a/render/expected/expected.go b/render/expected/expected.go index ca20539250..c0be8a194c 100644 --- a/render/expected/expected.go +++ b/render/expected/expected.go @@ -2,6 +2,7 @@ package expected import ( "fmt" + "net" "github.com/weaveworks/scope/render" "github.com/weaveworks/scope/report" @@ -16,16 +17,111 @@ var ( hexagon = "hexagon" cloud = "cloud" - uncontainedServerID = render.MakePseudoNodeID(render.UncontainedID, fixture.ServerHostName) - unknownPseudoNode1ID = render.MakePseudoNodeID("10.10.10.10", fixture.ServerIP, "80") - unknownPseudoNode2ID = render.MakePseudoNodeID("10.10.10.11", fixture.ServerIP, "80") - unknownPseudoNode1 = func(adjacent string) render.RenderableNode { + Client54001EndpointID = render.MakeEndpointID(fixture.ClientHostID, fixture.ClientIP, fixture.ClientPort54001) + Client54002EndpointID = render.MakeEndpointID(fixture.ClientHostID, fixture.ClientIP, fixture.ClientPort54002) + ServerEndpointID = render.MakeEndpointID(fixture.ServerHostID, fixture.ServerIP, fixture.ServerPort) + UnknownClient1EndpointID = render.MakeEndpointID("", fixture.UnknownClient1IP, fixture.UnknownClient1Port) + UnknownClient2EndpointID = render.MakeEndpointID("", fixture.UnknownClient2IP, fixture.UnknownClient2Port) + UnknownClient3EndpointID = render.MakeEndpointID("", fixture.UnknownClient3IP, fixture.UnknownClient3Port) + RandomClientEndpointID = render.MakeEndpointID("", fixture.RandomClientIP, fixture.RandomClientPort) + NonContainerEndpointID = render.MakeEndpointID(fixture.ServerHostID, fixture.ServerIP, fixture.NonContainerClientPort) + GoogleEndpointID = render.MakeEndpointID("", fixture.GoogleIP, fixture.GooglePort) + + RemappedEndpoints = (render.RenderableNodes{ + Client54001EndpointID: { + ID: Client54001EndpointID, + Shape: circle, + Node: report.MakeNode().WithAdjacent(ServerEndpointID), + EdgeMetadata: report.EdgeMetadata{ + EgressPacketCount: newu64(10), + EgressByteCount: newu64(100), + }, + }, + Client54002EndpointID: { + ID: Client54002EndpointID, + Shape: circle, + Node: report.MakeNode().WithAdjacent(ServerEndpointID), + EdgeMetadata: report.EdgeMetadata{ + EgressPacketCount: newu64(20), + EgressByteCount: newu64(200), + }, + }, + ServerEndpointID: { + ID: ServerEndpointID, + Shape: circle, + Node: report.MakeNode(), + EdgeMetadata: report.EdgeMetadata{ + IngressPacketCount: newu64(210), + IngressByteCount: newu64(2100), + }, + }, + UnknownClient1EndpointID: { + ID: UnknownClient1EndpointID, + Shape: circle, + Node: report.MakeNode().WithAdjacent(ServerEndpointID), + EdgeMetadata: report.EdgeMetadata{ + EgressPacketCount: newu64(30), + EgressByteCount: newu64(300), + }, + }, + UnknownClient2EndpointID: { + ID: UnknownClient2EndpointID, + Shape: circle, + Node: report.MakeNode().WithAdjacent(ServerEndpointID), + EdgeMetadata: report.EdgeMetadata{ + EgressPacketCount: newu64(40), + EgressByteCount: newu64(400), + }, + }, + UnknownClient3EndpointID: { + ID: UnknownClient3EndpointID, + Shape: circle, + Node: report.MakeNode().WithAdjacent(ServerEndpointID), + EdgeMetadata: report.EdgeMetadata{ + EgressPacketCount: newu64(50), + EgressByteCount: newu64(500), + }, + }, + RandomClientEndpointID: { + ID: RandomClientEndpointID, + Shape: circle, + Node: report.MakeNode().WithAdjacent(ServerEndpointID), + EdgeMetadata: report.EdgeMetadata{ + EgressPacketCount: newu64(60), + EgressByteCount: newu64(600), + }, + }, + NonContainerEndpointID: { + ID: NonContainerEndpointID, + Shape: circle, + Node: report.MakeNode().WithAdjacent(GoogleEndpointID), + EdgeMetadata: report.EdgeMetadata{}, + }, + GoogleEndpointID: { + ID: GoogleEndpointID, + Shape: circle, + Node: report.MakeNode(), + }, + }).Prune() + + Client54001PseudoEndpointID = render.MakePseudoEndpointID(fixture.ClientHostID, fixture.ClientIP, fixture.ClientPort54001) + Client54002PseudoEndpointID = render.MakePseudoEndpointID(fixture.ClientHostID, fixture.ClientIP, fixture.ClientPort54002) + ServerPseudoEndpointID = render.MakePseudoEndpointID(fixture.ServerHostID, fixture.ServerIP, fixture.ServerPort) + NonContainerPseudoEndpointID = render.MakePseudoEndpointID(fixture.ServerHostID, fixture.ServerIP, fixture.NonContainerClientPort) + unknownPseudoNode1ID = render.MakePseudoNodeID(fixture.UnknownClient1IP, fixture.ServerIP, fixture.ServerPort) + unknownPseudoNode2ID = render.MakePseudoNodeID(fixture.UnknownClient3IP, fixture.ServerIP, fixture.ServerPort) + + unknownPseudoNode1 = func(adjacent string) render.RenderableNode { return render.RenderableNode{ ID: unknownPseudoNode1ID, - LabelMajor: "10.10.10.10", + LabelMajor: fixture.UnknownClient1IP, Pseudo: true, Shape: circle, Node: report.MakeNode().WithAdjacent(adjacent), + Children: render.MakeRenderableNodeSet( + RemappedEndpoints[UnknownClient1EndpointID], + RemappedEndpoints[UnknownClient2EndpointID], + ), EdgeMetadata: report.EdgeMetadata{ EgressPacketCount: newu64(70), EgressByteCount: newu64(700), @@ -35,10 +131,13 @@ var ( unknownPseudoNode2 = func(adjacent string) render.RenderableNode { return render.RenderableNode{ ID: unknownPseudoNode2ID, - LabelMajor: "10.10.10.11", + LabelMajor: fixture.UnknownClient3IP, Pseudo: true, Shape: circle, Node: report.MakeNode().WithAdjacent(adjacent), + Children: render.MakeRenderableNodeSet( + RemappedEndpoints[UnknownClient3EndpointID], + ), EdgeMetadata: report.EdgeMetadata{ EgressPacketCount: newu64(50), EgressByteCount: newu64(500), @@ -49,10 +148,13 @@ var ( return render.RenderableNode{ ID: render.IncomingInternetID, LabelMajor: render.InboundMajor, - LabelMinor: render.RequestsMinor, + LabelMinor: render.InboundMinor, Pseudo: true, Shape: cloud, Node: report.MakeNode().WithAdjacent(adjacent), + Children: render.MakeRenderableNodeSet( + RemappedEndpoints[RandomClientEndpointID], + ), EdgeMetadata: report.EdgeMetadata{ EgressPacketCount: newu64(60), EgressByteCount: newu64(600), @@ -62,12 +164,75 @@ var ( theOutgoingInternetNode = render.RenderableNode{ ID: render.OutgoingInternetID, LabelMajor: render.OutboundMajor, - LabelMinor: render.RequestsMinor, + LabelMinor: render.OutboundMinor, Pseudo: true, Shape: cloud, Node: report.MakeNode(), EdgeMetadata: report.EdgeMetadata{}, + Children: render.MakeRenderableNodeSet( + RemappedEndpoints[GoogleEndpointID], + ), } + + RenderedEndpoints = (render.RenderableNodes{ + Client54001PseudoEndpointID: { + ID: Client54001PseudoEndpointID, + LabelMajor: net.JoinHostPort(fixture.ClientIP, fixture.ClientPort54001), + LabelMinor: fmt.Sprintf("%s (%s)", fixture.ClientHostID, fixture.Client1PID), + Shape: circle, + Node: report.MakeNode().WithAdjacent(ServerPseudoEndpointID), + Children: render.MakeRenderableNodeSet( + RemappedEndpoints[Client54001EndpointID], + ), + EdgeMetadata: report.EdgeMetadata{ + EgressPacketCount: newu64(10), + EgressByteCount: newu64(100), + }, + }, + Client54002PseudoEndpointID: { + ID: Client54002PseudoEndpointID, + LabelMajor: net.JoinHostPort(fixture.ClientIP, fixture.ClientPort54002), + LabelMinor: fmt.Sprintf("%s (%s)", fixture.ClientHostID, fixture.Client2PID), + Shape: circle, + Node: report.MakeNode().WithAdjacent(ServerPseudoEndpointID), + Children: render.MakeRenderableNodeSet( + RemappedEndpoints[Client54002EndpointID], + ), + EdgeMetadata: report.EdgeMetadata{ + EgressPacketCount: newu64(20), + EgressByteCount: newu64(200), + }, + }, + ServerPseudoEndpointID: { + ID: ServerPseudoEndpointID, + LabelMajor: net.JoinHostPort(fixture.ServerIP, fixture.ServerPort), + LabelMinor: fmt.Sprintf("%s (%s)", fixture.ServerHostID, fixture.ServerPID), + Shape: circle, + Node: report.MakeNode(), + Children: render.MakeRenderableNodeSet( + RemappedEndpoints[ServerEndpointID], + ), + EdgeMetadata: report.EdgeMetadata{ + IngressPacketCount: newu64(210), + IngressByteCount: newu64(2100), + }, + }, + NonContainerPseudoEndpointID: { + ID: NonContainerPseudoEndpointID, + LabelMajor: net.JoinHostPort(fixture.ServerIP, fixture.NonContainerClientPort), + LabelMinor: fmt.Sprintf("%s (%s)", fixture.ServerHostID, fixture.NonContainerPID), + Shape: circle, + Node: report.MakeNode().WithAdjacent(render.OutgoingInternetID), + Children: render.MakeRenderableNodeSet( + RemappedEndpoints[NonContainerEndpointID], + ), + }, + unknownPseudoNode1ID: unknownPseudoNode1(ServerPseudoEndpointID), + unknownPseudoNode2ID: unknownPseudoNode2(ServerPseudoEndpointID), + render.IncomingInternetID: theIncomingInternetNode(ServerPseudoEndpointID), + render.OutgoingInternetID: theOutgoingInternetNode, + }).Prune() + ClientProcess1ID = render.MakeProcessID(fixture.ClientHostID, fixture.Client1PID) ClientProcess2ID = render.MakeProcessID(fixture.ClientHostID, fixture.Client2PID) ServerProcessID = render.MakeProcessID(fixture.ServerHostID, fixture.ServerPID) @@ -82,6 +247,10 @@ var ( Pseudo: false, Shape: square, Node: report.MakeNode().WithAdjacent(ServerProcessID), + Children: render.MakeRenderableNodeSet( + RemappedEndpoints[Client54001EndpointID], + RenderedEndpoints[Client54001PseudoEndpointID], + ), EdgeMetadata: report.EdgeMetadata{ EgressPacketCount: newu64(10), EgressByteCount: newu64(100), @@ -95,6 +264,10 @@ var ( Pseudo: false, Shape: square, Node: report.MakeNode().WithAdjacent(ServerProcessID), + Children: render.MakeRenderableNodeSet( + RemappedEndpoints[Client54002EndpointID], + RenderedEndpoints[Client54002PseudoEndpointID], + ), EdgeMetadata: report.EdgeMetadata{ EgressPacketCount: newu64(20), EgressByteCount: newu64(200), @@ -108,19 +281,27 @@ var ( Pseudo: false, Shape: square, Node: report.MakeNode(), + Children: render.MakeRenderableNodeSet( + RemappedEndpoints[ServerEndpointID], + RenderedEndpoints[ServerPseudoEndpointID], + ), EdgeMetadata: report.EdgeMetadata{ IngressPacketCount: newu64(210), IngressByteCount: newu64(2100), }, }, nonContainerProcessID: { - ID: nonContainerProcessID, - LabelMajor: fixture.NonContainerName, - LabelMinor: fmt.Sprintf("%s (%s)", fixture.ServerHostID, fixture.NonContainerPID), - Rank: fixture.NonContainerName, - Pseudo: false, - Shape: square, - Node: report.MakeNode().WithAdjacent(render.OutgoingInternetID), + ID: nonContainerProcessID, + LabelMajor: fixture.NonContainerName, + LabelMinor: fmt.Sprintf("%s (%s)", fixture.ServerHostID, fixture.NonContainerPID), + Rank: fixture.NonContainerName, + Pseudo: false, + Shape: square, + Node: report.MakeNode().WithAdjacent(render.OutgoingInternetID), + Children: render.MakeRenderableNodeSet( + RemappedEndpoints[NonContainerEndpointID], + RenderedEndpoints[NonContainerPseudoEndpointID], + ), EdgeMetadata: report.EdgeMetadata{}, }, unknownPseudoNode1ID: unknownPseudoNode1(ServerProcessID), @@ -129,10 +310,6 @@ var ( render.OutgoingInternetID: theOutgoingInternetNode, }).Prune() - ServerProcessRenderedID = render.MakeProcessID(fixture.ServerHostID, fixture.ServerPID) - ClientProcess1RenderedID = render.MakeProcessID(fixture.ClientHostID, fixture.Client1PID) - ClientProcess2RenderedID = render.MakeProcessID(fixture.ClientHostID, fixture.Client2PID) - RenderedProcessNames = (render.RenderableNodes{ fixture.Client1Name: { ID: fixture.Client1Name, @@ -142,9 +319,13 @@ var ( Pseudo: false, Shape: square, Stack: true, - Children: report.MakeNodeSet( - fixture.Report.Process.Nodes[fixture.ClientProcess1NodeID], - fixture.Report.Process.Nodes[fixture.ClientProcess2NodeID], + Children: render.MakeRenderableNodeSet( + RemappedEndpoints[Client54001EndpointID], + RemappedEndpoints[Client54002EndpointID], + RenderedEndpoints[Client54001PseudoEndpointID], + RenderedEndpoints[Client54002PseudoEndpointID], + RenderedProcesses[ClientProcess1ID], + RenderedProcesses[ClientProcess2ID], ), Node: report.MakeNode().WithAdjacent(fixture.ServerName), EdgeMetadata: report.EdgeMetadata{ @@ -160,8 +341,10 @@ var ( Pseudo: false, Shape: square, Stack: true, - Children: report.MakeNodeSet( - fixture.Report.Process.Nodes[fixture.ServerProcessNodeID], + Children: render.MakeRenderableNodeSet( + RemappedEndpoints[ServerEndpointID], + RenderedEndpoints[ServerPseudoEndpointID], + RenderedProcesses[ServerProcessID], ), Node: report.MakeNode(), EdgeMetadata: report.EdgeMetadata{ @@ -177,8 +360,10 @@ var ( Pseudo: false, Shape: square, Stack: true, - Children: report.MakeNodeSet( - fixture.Report.Process.Nodes[fixture.NonContainerProcessNodeID], + Children: render.MakeRenderableNodeSet( + RemappedEndpoints[NonContainerEndpointID], + RenderedEndpoints[NonContainerPseudoEndpointID], + RenderedProcesses[nonContainerProcessID], ), Node: report.MakeNode().WithAdjacent(render.OutgoingInternetID), EdgeMetadata: report.EdgeMetadata{}, @@ -189,37 +374,42 @@ var ( render.OutgoingInternetID: theOutgoingInternetNode, }).Prune() - ServerContainerRenderedID = render.MakeContainerID(fixture.ServerContainerID) - ClientContainerRenderedID = render.MakeContainerID(fixture.ClientContainerID) + ClientContainerID = render.MakeContainerID(fixture.ClientContainerID) + ServerContainerID = render.MakeContainerID(fixture.ServerContainerID) + uncontainedServerID = render.MakePseudoNodeID(render.UncontainedID, fixture.ServerHostID) RenderedContainers = (render.RenderableNodes{ - ClientContainerRenderedID: { - ID: ClientContainerRenderedID, + ClientContainerID: { + ID: ClientContainerID, LabelMajor: "client", LabelMinor: fixture.ClientHostName, - Rank: fixture.ClientContainerImageName, Pseudo: false, Shape: hexagon, - Children: report.MakeNodeSet( - fixture.Report.Process.Nodes[fixture.ClientProcess1NodeID], - fixture.Report.Process.Nodes[fixture.ClientProcess2NodeID], + Children: render.MakeRenderableNodeSet( + RemappedEndpoints[Client54001EndpointID], + RemappedEndpoints[Client54002EndpointID], + RenderedEndpoints[Client54001PseudoEndpointID], + RenderedEndpoints[Client54002PseudoEndpointID], + RenderedProcesses[ClientProcess1ID], + RenderedProcesses[ClientProcess2ID], ), - Node: report.MakeNode().WithAdjacent(ServerContainerRenderedID), + Node: report.MakeNode().WithAdjacent(ServerContainerID), EdgeMetadata: report.EdgeMetadata{ EgressPacketCount: newu64(30), EgressByteCount: newu64(300), }, ControlNode: fixture.ClientContainerNodeID, }, - ServerContainerRenderedID: { - ID: ServerContainerRenderedID, + ServerContainerID: { + ID: ServerContainerID, LabelMajor: "server", LabelMinor: fixture.ServerHostName, - Rank: fixture.ServerContainerImageName, Pseudo: false, Shape: hexagon, - Children: report.MakeNodeSet( - fixture.Report.Process.Nodes[fixture.ServerProcessNodeID], + Children: render.MakeRenderableNodeSet( + RemappedEndpoints[ServerEndpointID], + RenderedEndpoints[ServerPseudoEndpointID], + RenderedProcesses[ServerProcessID], ), Node: report.MakeNode(), EdgeMetadata: report.EdgeMetadata{ @@ -232,54 +422,63 @@ var ( ID: uncontainedServerID, LabelMajor: render.UncontainedMajor, LabelMinor: fixture.ServerHostName, - Rank: "", Pseudo: true, Shape: square, Stack: true, - Children: report.MakeNodeSet( - fixture.Report.Process.Nodes[fixture.NonContainerProcessNodeID], + Children: render.MakeRenderableNodeSet( + RemappedEndpoints[NonContainerEndpointID], + RenderedEndpoints[NonContainerPseudoEndpointID], + RenderedProcesses[nonContainerProcessID], ), Node: report.MakeNode().WithAdjacent(render.OutgoingInternetID), EdgeMetadata: report.EdgeMetadata{}, }, - render.IncomingInternetID: theIncomingInternetNode(ServerContainerRenderedID), + unknownPseudoNode1ID: unknownPseudoNode1(ServerContainerID), + unknownPseudoNode2ID: unknownPseudoNode2(ServerContainerID), + render.IncomingInternetID: theIncomingInternetNode(ServerContainerID), render.OutgoingInternetID: theOutgoingInternetNode, }).Prune() - ClientContainerImageRenderedName = render.MakeContainerImageID(fixture.ClientContainerImageName) - ServerContainerImageRenderedName = render.MakeContainerImageID(fixture.ServerContainerImageName) + ClientContainerImageID = render.MakeContainerImageID(fixture.ClientContainerImageName) + ServerContainerImageID = render.MakeContainerImageID(fixture.ServerContainerImageName) RenderedContainerImages = (render.RenderableNodes{ - ClientContainerImageRenderedName: { - ID: ClientContainerImageRenderedName, + ClientContainerImageID: { + ID: ClientContainerImageID, LabelMajor: fixture.ClientContainerImageName, LabelMinor: "1 container", Rank: fixture.ClientContainerImageName, Pseudo: false, Shape: hexagon, Stack: true, - Children: report.MakeNodeSet( - fixture.Report.Process.Nodes[fixture.ClientProcess1NodeID], - fixture.Report.Process.Nodes[fixture.ClientProcess2NodeID], - fixture.Report.Container.Nodes[fixture.ClientContainerNodeID], + Children: render.MakeRenderableNodeSet( + RemappedEndpoints[Client54001EndpointID], + RemappedEndpoints[Client54002EndpointID], + RenderedEndpoints[Client54001PseudoEndpointID], + RenderedEndpoints[Client54002PseudoEndpointID], + RenderedProcesses[ClientProcess1ID], + RenderedProcesses[ClientProcess2ID], + RenderedContainers[ClientContainerID], ), - Node: report.MakeNode().WithAdjacent(ServerContainerImageRenderedName), + Node: report.MakeNode().WithAdjacent(ServerContainerImageID), EdgeMetadata: report.EdgeMetadata{ EgressPacketCount: newu64(30), EgressByteCount: newu64(300), }, }, - ServerContainerImageRenderedName: { - ID: ServerContainerImageRenderedName, + ServerContainerImageID: { + ID: ServerContainerImageID, LabelMajor: fixture.ServerContainerImageName, LabelMinor: "1 container", Rank: fixture.ServerContainerImageName, Pseudo: false, Shape: hexagon, Stack: true, - Children: report.MakeNodeSet( - fixture.Report.Process.Nodes[fixture.ServerProcessNodeID], - fixture.Report.Container.Nodes[fixture.ServerContainerNodeID], + Children: render.MakeRenderableNodeSet( + RemappedEndpoints[ServerEndpointID], + RenderedEndpoints[ServerPseudoEndpointID], + RenderedProcesses[ServerProcessID], + RenderedContainers[ServerContainerID], ), Node: report.MakeNode(), EdgeMetadata: report.EdgeMetadata{ @@ -291,71 +490,142 @@ var ( ID: uncontainedServerID, LabelMajor: render.UncontainedMajor, LabelMinor: fixture.ServerHostName, - Rank: "", Pseudo: true, Shape: square, Stack: true, - Children: report.MakeNodeSet( - fixture.Report.Process.Nodes[fixture.NonContainerProcessNodeID], + Children: render.MakeRenderableNodeSet( + RemappedEndpoints[NonContainerEndpointID], + RenderedEndpoints[NonContainerPseudoEndpointID], + RenderedProcesses[nonContainerProcessID], ), Node: report.MakeNode().WithAdjacent(render.OutgoingInternetID), EdgeMetadata: report.EdgeMetadata{}, }, - render.IncomingInternetID: theIncomingInternetNode(ServerContainerImageRenderedName), + unknownPseudoNode1ID: unknownPseudoNode1(ServerContainerImageID), + unknownPseudoNode2ID: unknownPseudoNode2(ServerContainerImageID), + render.IncomingInternetID: theIncomingInternetNode(ServerContainerImageID), render.OutgoingInternetID: theOutgoingInternetNode, }).Prune() - ServerHostRenderedID = render.MakeHostID(fixture.ServerHostID) - ClientHostRenderedID = render.MakeHostID(fixture.ClientHostID) - pseudoHostID1 = render.MakePseudoNodeID(fixture.UnknownClient1IP, fixture.ServerIP) - pseudoHostID2 = render.MakePseudoNodeID(fixture.UnknownClient3IP, fixture.ServerIP) + ClientAddressID = render.MakeAddressID(fixture.ClientHostID, fixture.ClientIP) + ServerAddressID = render.MakeAddressID(fixture.ServerHostID, fixture.ServerIP) + unknownPseudoAddress1ID = render.MakePseudoNodeID("10.10.10.10", fixture.ServerIP) + unknownPseudoAddress2ID = render.MakePseudoNodeID("10.10.10.11", fixture.ServerIP) + + RenderedAddresses = (render.RenderableNodes{ + ClientAddressID: { + ID: ClientAddressID, + LabelMajor: fixture.ClientIP, + LabelMinor: fixture.ClientHostID, + Shape: circle, + Node: report.MakeNode().WithAdjacent(ServerAddressID), + }, + ServerAddressID: { + ID: ServerAddressID, + LabelMajor: fixture.ServerIP, + LabelMinor: fixture.ServerHostID, + Shape: circle, + Node: report.MakeNode(), + }, + unknownPseudoAddress1ID: { + ID: unknownPseudoAddress1ID, + LabelMajor: "10.10.10.10", + Pseudo: true, + Shape: circle, + Node: report.MakeNode().WithAdjacent(ServerAddressID), + }, + unknownPseudoAddress2ID: { + ID: unknownPseudoAddress2ID, + LabelMajor: "10.10.10.11", + Pseudo: true, + Shape: circle, + Node: report.MakeNode().WithAdjacent(ServerAddressID), + }, + render.IncomingInternetID: { + ID: render.IncomingInternetID, + LabelMajor: render.InboundMajor, + LabelMinor: render.InboundMinor, + Pseudo: true, + Shape: cloud, + Node: report.MakeNode().WithAdjacent(ServerAddressID), + EdgeMetadata: report.EdgeMetadata{}, + }, + }).Prune() + + ServerHostID = render.MakeHostID(fixture.ServerHostID) + ClientHostID = render.MakeHostID(fixture.ClientHostID) + pseudoHostID1 = render.MakePseudoNodeID(fixture.UnknownClient1IP, fixture.ServerIP) + pseudoHostID2 = render.MakePseudoNodeID(fixture.UnknownClient3IP, fixture.ServerIP) RenderedHosts = (render.RenderableNodes{ - ServerHostRenderedID: { - ID: ServerHostRenderedID, - LabelMajor: "server", // before first . + ClientHostID: { + ID: ClientHostID, + LabelMajor: "client", // before first . LabelMinor: "hostname.com", // after first . Rank: "hostname.com", Pseudo: false, Shape: circle, - Children: report.MakeNodeSet( - fixture.Report.Container.Nodes[fixture.ServerContainerNodeID], - fixture.Report.Container.Nodes[fixture.ServerProcessNodeID], + Children: render.MakeRenderableNodeSet( + RemappedEndpoints[Client54001EndpointID], + RemappedEndpoints[Client54002EndpointID], + + RenderedEndpoints[Client54001PseudoEndpointID], + RenderedEndpoints[Client54002PseudoEndpointID], + + RenderedProcesses[ClientProcess1ID], + RenderedProcesses[ClientProcess2ID], + + RenderedContainers[ClientContainerID], + + RenderedContainerImages[ClientContainerImageID], + + RenderedAddresses[ClientAddressID], ), - Node: report.MakeNode(), + Node: report.MakeNode().WithAdjacent(ServerHostID), EdgeMetadata: report.EdgeMetadata{ - IngressPacketCount: newu64(210), - IngressByteCount: newu64(2100), + EgressPacketCount: newu64(30), + EgressByteCount: newu64(300), }, }, - ClientHostRenderedID: { - ID: ClientHostRenderedID, - LabelMajor: "client", // before first . + ServerHostID: { + ID: ServerHostID, + LabelMajor: "server", // before first . LabelMinor: "hostname.com", // after first . Rank: "hostname.com", Pseudo: false, Shape: circle, - Children: report.MakeNodeSet( - fixture.Report.Container.Nodes[fixture.ClientContainerNodeID], - fixture.Report.Process.Nodes[fixture.ClientProcess1NodeID], - fixture.Report.Process.Nodes[fixture.ClientProcess2NodeID], + Children: render.MakeRenderableNodeSet( + RemappedEndpoints[ServerEndpointID], + RenderedEndpoints[ServerPseudoEndpointID], + + RenderedProcesses[ServerProcessID], + RenderedContainers[ServerContainerID], + RenderedContainerImages[ServerContainerImageID], + + RenderedAddresses[ServerAddressID], + + // RemappedEndpoints[NonContainerEndpointID], + // RenderedEndpoints[NonContainerPseudoEndpointID], + // RenderedProcesses[nonContainerProcessID], ), - Node: report.MakeNode().WithAdjacent(ServerHostRenderedID), + Node: report.MakeNode(), EdgeMetadata: report.EdgeMetadata{ - EgressPacketCount: newu64(30), - EgressByteCount: newu64(300), + IngressPacketCount: newu64(210), + IngressByteCount: newu64(2100), }, }, + pseudoHostID1: { ID: pseudoHostID1, LabelMajor: fixture.UnknownClient1IP, Pseudo: true, Shape: circle, - Node: report.MakeNode().WithAdjacent(ServerHostRenderedID), + Node: report.MakeNode().WithAdjacent(ServerHostID), EdgeMetadata: report.EdgeMetadata{}, - Children: report.MakeNodeSet( - fixture.Report.Container.Nodes[fixture.ServerContainerNodeID], - fixture.Report.Process.Nodes[fixture.ServerProcessNodeID], + Children: render.MakeRenderableNodeSet( + //TODO + //RenderedEndpoints[unknownPseudoNode2ID], + //RenderedAddresses[unknownPseudoAddress1ID], ), }, pseudoHostID2: { @@ -363,17 +633,24 @@ var ( LabelMajor: fixture.UnknownClient3IP, Pseudo: true, Shape: circle, - Node: report.MakeNode().WithAdjacent(ServerHostRenderedID), + Node: report.MakeNode().WithAdjacent(ServerHostID), EdgeMetadata: report.EdgeMetadata{}, + Children: render.MakeRenderableNodeSet( + //RenderedEndpoints[unknownPseudoNode2ID], + //RenderedAddresses[unknownPseudoAddress2ID], + ), }, render.IncomingInternetID: { ID: render.IncomingInternetID, LabelMajor: render.InboundMajor, - LabelMinor: render.RequestsMinor, + LabelMinor: render.InboundMinor, Pseudo: true, Shape: cloud, - Node: report.MakeNode().WithAdjacent(ServerHostRenderedID), + Node: report.MakeNode().WithAdjacent(ServerHostID), EdgeMetadata: report.EdgeMetadata{}, + Children: render.MakeRenderableNodeSet( + //RenderedEndpoints[render.TheInternetID], + ), }, }).Prune() @@ -388,12 +665,14 @@ var ( Rank: "ping/pong-a", Pseudo: false, Shape: heptagon, - Children: report.MakeNodeSet( - fixture.Report.Process.Nodes[fixture.ClientProcess1NodeID], - fixture.Report.Process.Nodes[fixture.ClientProcess2NodeID], - fixture.Report.Container.Nodes[fixture.ClientContainerNodeID], - fixture.Report.ContainerImage.Nodes[fixture.ClientContainerImageNodeID], - fixture.Report.Pod.Nodes[fixture.ClientPodNodeID], + Children: render.MakeRenderableNodeSet( + RemappedEndpoints[Client54001EndpointID], + RemappedEndpoints[Client54002EndpointID], + RenderedEndpoints[Client54001PseudoEndpointID], + RenderedEndpoints[Client54002PseudoEndpointID], + RenderedProcesses[ClientProcess1ID], + RenderedProcesses[ClientProcess2ID], + RenderedContainers[ClientContainerID], ), Node: report.MakeNode().WithAdjacent(ServerPodRenderedID), EdgeMetadata: report.EdgeMetadata{ @@ -408,11 +687,11 @@ var ( Rank: "ping/pong-b", Pseudo: false, Shape: heptagon, - Children: report.MakeNodeSet( - fixture.Report.Process.Nodes[fixture.ServerProcessNodeID], - fixture.Report.Container.Nodes[fixture.ServerContainerNodeID], - fixture.Report.ContainerImage.Nodes[fixture.ServerContainerImageNodeID], - fixture.Report.Pod.Nodes[fixture.ServerPodNodeID], + Children: render.MakeRenderableNodeSet( + RemappedEndpoints[ServerEndpointID], + RenderedEndpoints[ServerPseudoEndpointID], + RenderedProcesses[ServerProcessID], + RenderedContainers[ServerContainerID], ), Node: report.MakeNode(), EdgeMetadata: report.EdgeMetadata{ @@ -424,16 +703,19 @@ var ( ID: uncontainedServerID, LabelMajor: render.UncontainedMajor, LabelMinor: fixture.ServerHostName, - Rank: "", Pseudo: true, Shape: square, Stack: true, - Children: report.MakeNodeSet( - fixture.Report.Process.Nodes[fixture.NonContainerProcessNodeID], + Children: render.MakeRenderableNodeSet( + RemappedEndpoints[NonContainerEndpointID], + RenderedEndpoints[NonContainerPseudoEndpointID], + RenderedProcesses[nonContainerProcessID], ), Node: report.MakeNode().WithAdjacent(render.OutgoingInternetID), EdgeMetadata: report.EdgeMetadata{}, }, + unknownPseudoNode1ID: unknownPseudoNode1(ServerPodRenderedID), + unknownPseudoNode2ID: unknownPseudoNode2(ServerPodRenderedID), render.IncomingInternetID: theIncomingInternetNode(ServerPodRenderedID), render.OutgoingInternetID: theOutgoingInternetNode, }).Prune() @@ -449,16 +731,20 @@ var ( Pseudo: false, Shape: heptagon, Stack: true, - Children: report.MakeNodeSet( - fixture.Report.Process.Nodes[fixture.ClientProcess1NodeID], - fixture.Report.Process.Nodes[fixture.ClientProcess2NodeID], - fixture.Report.Container.Nodes[fixture.ClientContainerNodeID], - fixture.Report.ContainerImage.Nodes[fixture.ClientContainerImageNodeID], - fixture.Report.Pod.Nodes[fixture.ClientPodNodeID], - fixture.Report.Process.Nodes[fixture.ServerProcessNodeID], - fixture.Report.Container.Nodes[fixture.ServerContainerNodeID], - fixture.Report.ContainerImage.Nodes[fixture.ServerContainerImageNodeID], - fixture.Report.Pod.Nodes[fixture.ServerPodNodeID], + Children: render.MakeRenderableNodeSet( + RemappedEndpoints[Client54001EndpointID], + RemappedEndpoints[Client54002EndpointID], + RemappedEndpoints[ServerEndpointID], + RenderedEndpoints[Client54001PseudoEndpointID], + RenderedEndpoints[Client54002PseudoEndpointID], + RenderedEndpoints[ServerPseudoEndpointID], + RenderedProcesses[ClientProcess1ID], + RenderedProcesses[ClientProcess2ID], + RenderedProcesses[ServerProcessID], + RenderedContainers[ClientContainerID], + RenderedContainers[ServerContainerID], + RenderedPods[ClientPodRenderedID], + RenderedPods[ServerPodRenderedID], ), Node: report.MakeNode().WithAdjacent(ServiceRenderedID), EdgeMetadata: report.EdgeMetadata{ @@ -472,16 +758,19 @@ var ( ID: uncontainedServerID, LabelMajor: render.UncontainedMajor, LabelMinor: fixture.ServerHostName, - Rank: "", Pseudo: true, Shape: square, Stack: true, - Children: report.MakeNodeSet( - fixture.Report.Process.Nodes[fixture.NonContainerProcessNodeID], + Children: render.MakeRenderableNodeSet( + RemappedEndpoints[NonContainerEndpointID], + RenderedEndpoints[NonContainerPseudoEndpointID], + RenderedProcesses[nonContainerProcessID], ), Node: report.MakeNode().WithAdjacent(render.OutgoingInternetID), EdgeMetadata: report.EdgeMetadata{}, }, + unknownPseudoNode1ID: unknownPseudoNode1(ServiceRenderedID), + unknownPseudoNode2ID: unknownPseudoNode2(ServiceRenderedID), render.IncomingInternetID: theIncomingInternetNode(ServiceRenderedID), render.OutgoingInternetID: theOutgoingInternetNode, }).Prune() diff --git a/render/id.go b/render/id.go index 952c3d3694..5acc38b903 100644 --- a/render/id.go +++ b/render/id.go @@ -4,6 +4,16 @@ import ( "strings" ) +// ParseEndpointID parses endpoint IDs +func ParseEndpointID(id string) (host, ip, port string, ok bool) { + parts := strings.SplitN(id, ":", 4) + if len(parts) != 4 || parts[0] != "endpoint" { + return + } + host, ip, port, ok = parts[1], parts[2], parts[3], true + return +} + // makeID is the generic ID maker func makeID(prefix string, parts ...string) string { return strings.Join(append([]string{prefix}, parts...), ":") @@ -14,6 +24,11 @@ func MakeEndpointID(hostID, addr, port string) string { return makeID("endpoint", hostID, addr, port) } +// MakePseudoEndpointID makes a pseudo endpoint node ID for rendered nodes. +func MakePseudoEndpointID(hostID, addr, port string) string { + return makeID("pseudo-endpoint", hostID, addr, port) +} + // MakeProcessID makes a process node ID for rendered nodes. func MakeProcessID(hostID, pid string) string { return makeID("process", hostID, pid) diff --git a/render/mapping.go b/render/mapping.go index 2c78b33aa4..808f9b976b 100644 --- a/render/mapping.go +++ b/render/mapping.go @@ -23,9 +23,10 @@ const ( TheInternetID = "theinternet" IncomingInternetID = "in-" + TheInternetID OutgoingInternetID = "out-" + TheInternetID - InboundMajor = "Inbound" - OutboundMajor = "Outbound" - RequestsMinor = "Requests" + InboundMajor = "The Internet" + OutboundMajor = "The Internet" + InboundMinor = "Inbound connections" + OutboundMinor = "Outbound connections" ContainersKey = "containers" ipsKey = "ips" @@ -50,15 +51,31 @@ func theInternetNode(m RenderableNode) RenderableNode { if len(m.Adjacency) > 0 { node.ID = IncomingInternetID node.LabelMajor = InboundMajor - node.LabelMinor = RequestsMinor + node.LabelMinor = InboundMinor } else { node.ID = OutgoingInternetID node.LabelMajor = OutboundMajor - node.LabelMinor = RequestsMinor + node.LabelMinor = OutboundMinor } return node } +// RemapEndpointIDs remaps endpoints to have the right id format. +func RemapEndpointIDs(m RenderableNode, _ report.Networks) RenderableNodes { + addr, ok := m.Latest.Lookup(endpoint.Addr) + if !ok { + return RenderableNodes{} + } + + port, ok := m.Latest.Lookup(endpoint.Port) + if !ok { + return RenderableNodes{} + } + + id := MakeEndpointID(report.ExtractHostID(m.Node), addr, port) + return RenderableNodes{id: NewRenderableNodeWith(id, "", "", "", m)} +} + // MapEndpointIdentity maps an endpoint topology node to a single endpoint // renderable node. As it is only ever run on endpoint topology nodes, we // expect that certain keys are present. @@ -81,36 +98,38 @@ func MapEndpointIdentity(m RenderableNode, local report.Networks) RenderableNode // Nodes without a hostid are treated as psuedo nodes if _, ok = m.Latest.Lookup(report.HostNodeID); !ok { - // If the dstNodeAddr is not in a network local to this report, we emit an - // internet node + var node RenderableNode + if ip := net.ParseIP(addr); ip != nil && !local.Contains(ip) { - return RenderableNodes{TheInternetID: theInternetNode(m)} - } + // If the dstNodeAddr is not in a network local to this report, we emit an + // internet node + node = theInternetNode(m) - // We are a 'client' pseudo node if the port is in the ephemeral port range. - // Linux uses 32768 to 61000, IANA suggests 49152 to 65535. - if p, err := strconv.Atoi(port); err == nil && len(m.Adjacency) > 0 && p >= 32768 && p < 65535 { + } else if p, err := strconv.Atoi(port); err == nil && len(m.Adjacency) > 0 && p >= 32768 && p < 65535 { + // We are a 'client' pseudo node if the port is in the ephemeral port range. + // Linux uses 32768 to 61000, IANA suggests 49152 to 65535. // We only exist if there is something in our adjacency // Generate a single pseudo node for every (client ip, server ip, server port) - dstNodeID := m.Adjacency[0] - serverIP, serverPort := trySplitAddr(dstNodeID) - outputID := MakePseudoNodeID(addr, serverIP, serverPort) - return RenderableNodes{outputID: newDerivedPseudoNode(outputID, addr, m)} - } + _, serverIP, serverPort, _ := ParseEndpointID(m.Adjacency[0]) + node = newDerivedPseudoNode(MakePseudoNodeID(addr, serverIP, serverPort), addr, m) + + } else if port != "" { + // Otherwise (the server node is missing), generate a pseudo node for every (server ip, server port) + node = newDerivedPseudoNode(MakePseudoNodeID(addr, port), addr+":"+port, m) - // Otherwise (the server node is missing), generate a pseudo node for every (server ip, server port) - outputID := MakePseudoNodeID(addr, port) - if port != "" { - return RenderableNodes{outputID: newDerivedPseudoNode(outputID, addr+":"+port, m)} + } else { + // Empty port for some reason... + node = newDerivedPseudoNode(MakePseudoNodeID(addr, port), addr, m) } - return RenderableNodes{outputID: newDerivedPseudoNode(outputID, addr, m)} + + node.Children = node.Children.Add(m) + return RenderableNodes{node.ID: node} } var ( - id = MakeEndpointID(report.ExtractHostID(m.Node), addr, port) + id = MakePseudoEndpointID(report.ExtractHostID(m.Node), addr, port) major = fmt.Sprintf("%s:%s", addr, port) minor = report.ExtractHostID(m.Node) - rank = major ) pid, pidOK := m.Latest.Lookup(process.PID) @@ -118,7 +137,9 @@ func MapEndpointIdentity(m RenderableNode, local report.Networks) RenderableNode minor = fmt.Sprintf("%s (%s)", minor, pid) } - return RenderableNodes{id: NewRenderableNodeWith(id, major, minor, rank, m)} + node := NewRenderableNodeWith(id, major, minor, "", m) + node.Children = node.Children.Add(m) + return RenderableNodes{id: node} } // MapProcessIdentity maps a process topology node to a process renderable @@ -155,10 +176,9 @@ func MapContainerIdentity(m RenderableNode, _ report.Networks) RenderableNodes { id = MakeContainerID(containerID) major, _ = GetRenderableContainerName(m.Node) minor = report.ExtractHostID(m.Node) - rank, _ = m.Latest.Lookup(docker.ImageID) ) - node := NewRenderableNodeWith(id, major, minor, rank, m) + node := NewRenderableNodeWith(id, major, minor, "", m) node.ControlNode = m.ID node.Shape = Hexagon return RenderableNodes{id: node} @@ -289,10 +309,9 @@ func MapAddressIdentity(m RenderableNode, local report.Networks) RenderableNodes id = MakeAddressID(report.ExtractHostID(m.Node), addr) major = addr minor = report.ExtractHostID(m.Node) - rank = major ) - return RenderableNodes{id: NewRenderableNodeWith(id, major, minor, rank, m)} + return RenderableNodes{id: NewRenderableNodeWith(id, major, minor, "", m)} } // MapHostIdentity maps a host topology node to a host renderable node. As it @@ -438,6 +457,7 @@ func MapEndpoint2Process(n RenderableNode, _ report.Networks) RenderableNodes { id := MakeProcessID(report.ExtractHostID(n.Node), pid) node := NewDerivedNode(id, n.WithParents(report.EmptySets)) node.Shape = Square + node.Children = node.Children.Add(n) return RenderableNodes{id: node} } @@ -453,14 +473,9 @@ func MapEndpoint2Process(n RenderableNode, _ report.Networks) RenderableNodes { // It does not have enough info to do that, and the resulting graph // must be merged with a container graph to get that info. func MapProcess2Container(n RenderableNode, _ report.Networks) RenderableNodes { - // Propogate the internet pseudo node - if strings.HasSuffix(n.ID, TheInternetID) { - return RenderableNodes{n.ID: n} - } - - // Don't propogate non-internet pseudo nodes + // Propogate pseudo node if n.Pseudo { - return RenderableNodes{} + return RenderableNodes{n.ID: n} } // Otherwise, if the process is not in a container, group it @@ -487,7 +502,7 @@ func MapProcess2Container(n RenderableNode, _ report.Networks) RenderableNodes { node.Stack = true } - node.Children = node.Children.Add(n.Node) + node.Children = node.Children.Add(n) return RenderableNodes{id: node} } @@ -513,7 +528,7 @@ func MapProcess2Name(n RenderableNode, _ report.Networks) RenderableNodes { node.Counters = node.Node.Counters.Add(processesKey, 1) node.Node.Topology = "process_name" node.Node.ID = name - node.Children = node.Children.Add(n.Node) + node.Children = node.Children.Add(n) node.Shape = Square node.Stack = true return RenderableNodes{name: node} @@ -567,7 +582,7 @@ func MapContainer2ContainerImage(n RenderableNode, _ report.Networks) Renderable result.Node.Counters = result.Node.Counters.Add(ContainersKey, 1) // Add the container as a child of the new image node - result.Children = result.Children.Add(n.Node) + result.Children = result.Children.Add(n) result.Node.Topology = "container_image" result.Node.ID = report.MakeContainerImageNodeID(imageID) @@ -576,42 +591,6 @@ func MapContainer2ContainerImage(n RenderableNode, _ report.Networks) Renderable return RenderableNodes{id: result} } -// MapPod2Service maps pod RenderableNodes to service RenderableNodes. -// -// If this function is given a node without a kubernetes_pod_id -// (including other pseudo nodes), it will produce an "Uncontained" -// pseudo node. -// -// Otherwise, this function will produce a node with the correct ID -// format for a container, but without any Major or Minor labels. -// It does not have enough info to do that, and the resulting graph -// must be merged with a pod graph to get that info. -func MapPod2Service(n RenderableNode, _ report.Networks) RenderableNodes { - // Propogate all pseudo nodes - if n.Pseudo { - return RenderableNodes{n.ID: n} - } - - // Otherwise, if some some reason the pod doesn't have a service_ids (maybe - // slightly out of sync reports, or its not in a service), just drop it - ids, ok := n.Node.Latest.Lookup(kubernetes.ServiceIDs) - if !ok { - return RenderableNodes{} - } - - result := RenderableNodes{} - for _, serviceID := range strings.Fields(ids) { - id := MakeServiceID(serviceID) - n := NewDerivedNode(id, n.WithParents(report.EmptySets)) - n.Node.Counters = n.Node.Counters.Add(podsKey, 1) - n.Children = n.Children.Add(n.Node) - n.Shape = Heptagon - n.Stack = true - result[id] = n - } - return result -} - // ImageNameWithoutVersion splits the image name apart, returning the name // without the version, if possible func ImageNameWithoutVersion(name string) string { @@ -671,7 +650,7 @@ func MapX2Host(n RenderableNode, _ report.Networks) RenderableNodes { } id := MakeHostID(report.ExtractHostID(n.Node)) result := NewDerivedNode(id, n.WithParents(report.EmptySets)) - result.Children = result.Children.Add(n.Node) + result.Children = result.Children.Add(n) result.Shape = Circle return RenderableNodes{id: result} } @@ -717,11 +696,47 @@ func MapContainer2Pod(n RenderableNode, _ report.Networks) RenderableNodes { }) } - result.Children = result.Children.Add(n.Node) result.Shape = Heptagon + result.Children = result.Children.Add(n) return RenderableNodes{id: result} } +// MapPod2Service maps pod RenderableNodes to service RenderableNodes. +// +// If this function is given a node without a kubernetes_pod_id +// (including other pseudo nodes), it will produce an "Uncontained" +// pseudo node. +// +// Otherwise, this function will produce a node with the correct ID +// format for a container, but without any Major or Minor labels. +// It does not have enough info to do that, and the resulting graph +// must be merged with a pod graph to get that info. +func MapPod2Service(pod RenderableNode, _ report.Networks) RenderableNodes { + // Propogate all pseudo nodes + if pod.Pseudo { + return RenderableNodes{pod.ID: pod} + } + + // Otherwise, if some some reason the pod doesn't have a service_ids (maybe + // slightly out of sync reports, or its not in a service), just drop it + ids, ok := pod.Node.Latest.Lookup(kubernetes.ServiceIDs) + if !ok { + return RenderableNodes{} + } + + result := RenderableNodes{} + for _, serviceID := range strings.Fields(ids) { + id := MakeServiceID(serviceID) + node := NewDerivedNode(id, pod.WithParents(report.EmptySets)) + node.Node.Counters = node.Node.Counters.Add(podsKey, 1) + node.Children = node.Children.Add(pod) + node.Shape = Heptagon + node.Stack = true + result[id] = node + } + return result +} + // MapContainer2Hostname maps container RenderableNodes to 'hostname' renderabled nodes.. func MapContainer2Hostname(n RenderableNode, _ report.Networks) RenderableNodes { // Propogate all pseudo nodes @@ -744,7 +759,7 @@ func MapContainer2Hostname(n RenderableNode, _ report.Networks) RenderableNodes result.Counters = result.Counters.Add(ContainersKey, 1) result.Node.Topology = "container_hostname" result.Node.ID = id - result.Children = result.Children.Add(n.Node) + result.Children = result.Children.Add(n) result.Shape = Hexagon result.Stack = true return RenderableNodes{id: result} @@ -784,20 +799,3 @@ func MapCountPods(n RenderableNode, _ report.Networks) RenderableNodes { } return RenderableNodes{output.ID: output} } - -// trySplitAddr is basically ParseArbitraryNodeID, since its callsites -// (pseudo funcs) just have opaque node IDs and don't know what topology they -// come from. Without changing how pseudo funcs work, we can't make it much -// smarter. -// -// TODO change how pseudofuncs work, and eliminate this helper. -func trySplitAddr(addr string) (string, string) { - fields := strings.SplitN(addr, report.ScopeDelim, 3) - if len(fields) == 3 { - return fields[1], fields[2] - } - if len(fields) == 2 { - return fields[1], "" - } - panic(addr) -} diff --git a/render/renderable_node.go b/render/renderable_node.go index 6d43b63ebe..847057e000 100644 --- a/render/renderable_node.go +++ b/render/renderable_node.go @@ -8,15 +8,15 @@ import ( // an element of a topology. It should contain information that's relevant // to rendering a node when there are many nodes visible at once. type RenderableNode struct { - ID string `json:"id"` // - LabelMajor string `json:"label_major"` // e.g. "process", human-readable - LabelMinor string `json:"label_minor,omitempty"` // e.g. "hostname", human-readable, optional - Rank string `json:"rank"` // to help the layout engine - Pseudo bool `json:"pseudo,omitempty"` // sort-of a placeholder node, for rendering purposes - Children report.NodeSet `json:"children,omitempty"` // Nodes which have been grouped into this one - ControlNode string `json:"-"` // ID of node from which to show the controls in the UI - Shape string `json:"shape"` // Shape node should be rendered as - Stack bool `json:"stack"` // Should UI render this node as a stack? + ID string `json:"id"` // + LabelMajor string `json:"label_major"` // e.g. "process", human-readable + LabelMinor string `json:"label_minor,omitempty"` // e.g. "hostname", human-readable, optional + Rank string `json:"rank"` // to help the layout engine + Pseudo bool `json:"pseudo,omitempty"` // sort-of a placeholder node, for rendering purposes + Children RenderableNodeSet `json:"children,omitempty"` // Nodes which have been grouped into this one + ControlNode string `json:"control_node"` // ID of node from which to show the controls in the UI + Shape string `json:"shape"` // Shape node should be rendered as + Stack bool `json:"stack"` // Should UI render this node as a stack? report.EdgeMetadata `json:"metadata"` // Numeric sums report.Node @@ -159,7 +159,10 @@ func (rn RenderableNode) Copy() RenderableNode { func (rn RenderableNode) Prune() RenderableNode { cp := rn.Copy() cp.Node = report.MakeNode().WithAdjacent(cp.Node.Adjacency...) - cp.Children = report.EmptyNodeSet + cp.Children = MakeRenderableNodeSet() + rn.Children.ForEach(func(n RenderableNode) { + cp.Children = cp.Children.Add(n.Prune()) + }) return cp } diff --git a/report/node_set.go b/render/renderable_node_set.go similarity index 52% rename from report/node_set.go rename to render/renderable_node_set.go index 25d10804dc..7cf012610a 100644 --- a/report/node_set.go +++ b/render/renderable_node_set.go @@ -1,33 +1,35 @@ -package report +package render import ( "bytes" "encoding/gob" "fmt" - "reflect" "sort" + "github.com/davecgh/go-spew/spew" "github.com/mndrix/ps" "github.com/ugorji/go/codec" + + "github.com/weaveworks/scope/test/reflect" ) -// NodeSet is a set of nodes keyed on (Topology, ID). Clients must use +// RenderableNodeSet is a set of nodes keyed on (Topology, ID). Clients must use // the Add method to add nodes -type NodeSet struct { +type RenderableNodeSet struct { psMap ps.Map } -// EmptyNodeSet is the empty set of nodes. -var EmptyNodeSet = NodeSet{ps.NewMap()} +// EmptyRenderableNodeSet is the empty set of nodes. +var EmptyRenderableNodeSet = RenderableNodeSet{ps.NewMap()} -// MakeNodeSet makes a new NodeSet with the given nodes. -func MakeNodeSet(nodes ...Node) NodeSet { - return EmptyNodeSet.Add(nodes...) +// MakeRenderableNodeSet makes a new RenderableNodeSet with the given nodes. +func MakeRenderableNodeSet(nodes ...RenderableNode) RenderableNodeSet { + return EmptyRenderableNodeSet.Add(nodes...) } -// Add adds the nodes to the NodeSet. Add is the only valid way to grow a -// NodeSet. Add returns the NodeSet to enable chaining. -func (n NodeSet) Add(nodes ...Node) NodeSet { +// Add adds the nodes to the RenderableNodeSet. Add is the only valid way to grow a +// RenderableNodeSet. Add returns the RenderableNodeSet to enable chaining. +func (n RenderableNodeSet) Add(nodes ...RenderableNode) RenderableNodeSet { result := n.psMap if result == nil { result = ps.NewMap() @@ -35,11 +37,11 @@ func (n NodeSet) Add(nodes ...Node) NodeSet { for _, node := range nodes { result = result.Set(fmt.Sprintf("%s|%s", node.Topology, node.ID), node) } - return NodeSet{result} + return RenderableNodeSet{result} } -// Merge combines the two NodeSets and returns a new result. -func (n NodeSet) Merge(other NodeSet) NodeSet { +// Merge combines the two RenderableNodeSets and returns a new result. +func (n RenderableNodeSet) Merge(other RenderableNodeSet) RenderableNodeSet { nSize, otherSize := n.Size(), other.Size() if nSize == 0 { return other @@ -54,22 +56,22 @@ func (n NodeSet) Merge(other NodeSet) NodeSet { iter.ForEach(func(key string, otherVal interface{}) { result = result.Set(key, otherVal) }) - return NodeSet{result} + return RenderableNodeSet{result} } // Lookup the node 'key' -func (n NodeSet) Lookup(key string) (Node, bool) { +func (n RenderableNodeSet) Lookup(key string) (RenderableNode, bool) { if n.psMap != nil { value, ok := n.psMap.Lookup(key) if ok { - return value.(Node), true + return value.(RenderableNode), true } } - return Node{}, false + return RenderableNode{}, false } // Keys is a list of all the keys in this set. -func (n NodeSet) Keys() []string { +func (n RenderableNodeSet) Keys() []string { if n.psMap == nil { return nil } @@ -79,7 +81,7 @@ func (n NodeSet) Keys() []string { } // Size is the number of nodes in the set -func (n NodeSet) Size() int { +func (n RenderableNodeSet) Size() int { if n.psMap == nil { return 0 } @@ -88,23 +90,23 @@ func (n NodeSet) Size() int { // ForEach executes f for each node in the set. Nodes are traversed in sorted // order. -func (n NodeSet) ForEach(f func(Node)) { +func (n RenderableNodeSet) ForEach(f func(RenderableNode)) { for _, key := range n.Keys() { if val, ok := n.psMap.Lookup(key); ok { - f(val.(Node)) + f(val.(RenderableNode)) } } } // Copy is a noop -func (n NodeSet) Copy() NodeSet { +func (n RenderableNodeSet) Copy() RenderableNodeSet { return n } -func (n NodeSet) String() string { +func (n RenderableNodeSet) String() string { keys := []string{} if n.psMap == nil { - n = EmptyNodeSet + n = EmptyRenderableNodeSet } psMap := n.psMap if psMap == nil { @@ -118,15 +120,15 @@ func (n NodeSet) String() string { buf := bytes.NewBufferString("{") for _, key := range keys { val, _ := psMap.Lookup(key) - fmt.Fprintf(buf, "%s: %v, ", key, val) + fmt.Fprintf(buf, "%s: %s, ", key, spew.Sdump(val)) } - fmt.Fprintf(buf, "}\n") + fmt.Fprintf(buf, "}") return buf.String() } -// DeepEqual tests equality with other NodeSets -func (n NodeSet) DeepEqual(i interface{}) bool { - d, ok := i.(NodeSet) +// DeepEqual tests equality with other RenderableNodeSets +func (n RenderableNodeSet) DeepEqual(i interface{}) bool { + d, ok := i.(RenderableNodeSet) if !ok { return false } @@ -149,20 +151,20 @@ func (n NodeSet) DeepEqual(i interface{}) bool { return equal } -func (n NodeSet) toIntermediate() []Node { - intermediate := make([]Node, 0, n.Size()) - n.ForEach(func(node Node) { +func (n RenderableNodeSet) toIntermediate() []RenderableNode { + intermediate := make([]RenderableNode, 0, n.Size()) + n.ForEach(func(node RenderableNode) { intermediate = append(intermediate, node) }) return intermediate } -func (n NodeSet) fromIntermediate(nodes []Node) NodeSet { - return MakeNodeSet(nodes...) +func (n RenderableNodeSet) fromIntermediate(nodes []RenderableNode) RenderableNodeSet { + return MakeRenderableNodeSet(nodes...) } // CodecEncodeSelf implements codec.Selfer -func (n *NodeSet) CodecEncodeSelf(encoder *codec.Encoder) { +func (n *RenderableNodeSet) CodecEncodeSelf(encoder *codec.Encoder) { if n.psMap != nil { encoder.Encode(n.toIntermediate()) } else { @@ -171,37 +173,37 @@ func (n *NodeSet) CodecEncodeSelf(encoder *codec.Encoder) { } // CodecDecodeSelf implements codec.Selfer -func (n *NodeSet) CodecDecodeSelf(decoder *codec.Decoder) { - in := []Node{} +func (n *RenderableNodeSet) CodecDecodeSelf(decoder *codec.Decoder) { + in := []RenderableNode{} if err := decoder.Decode(&in); err != nil { return } - *n = NodeSet{}.fromIntermediate(in) + *n = RenderableNodeSet{}.fromIntermediate(in) } // MarshalJSON shouldn't be used, use CodecEncodeSelf instead -func (NodeSet) MarshalJSON() ([]byte, error) { +func (RenderableNodeSet) MarshalJSON() ([]byte, error) { panic("MarshalJSON shouldn't be used, use CodecEncodeSelf instead") } // UnmarshalJSON shouldn't be used, use CodecDecodeSelf instead -func (*NodeSet) UnmarshalJSON(b []byte) error { +func (*RenderableNodeSet) UnmarshalJSON(b []byte) error { panic("UnmarshalJSON shouldn't be used, use CodecDecodeSelf instead") } // GobEncode implements gob.Marshaller -func (n NodeSet) GobEncode() ([]byte, error) { +func (n RenderableNodeSet) GobEncode() ([]byte, error) { buf := bytes.Buffer{} err := gob.NewEncoder(&buf).Encode(n.toIntermediate()) return buf.Bytes(), err } // GobDecode implements gob.Unmarshaller -func (n *NodeSet) GobDecode(input []byte) error { - in := []Node{} +func (n *RenderableNodeSet) GobDecode(input []byte) error { + in := []RenderableNode{} if err := gob.NewDecoder(bytes.NewBuffer(input)).Decode(&in); err != nil { return err } - *n = NodeSet{}.fromIntermediate(in) + *n = RenderableNodeSet{}.fromIntermediate(in) return nil } diff --git a/render/renderable_node_set_test.go b/render/renderable_node_set_test.go new file mode 100644 index 0000000000..9ffcdeb756 --- /dev/null +++ b/render/renderable_node_set_test.go @@ -0,0 +1,272 @@ +package render_test + +import ( + "fmt" + "testing" + + "github.com/weaveworks/scope/render" + "github.com/weaveworks/scope/report" + "github.com/weaveworks/scope/test/reflect" +) + +var benchmarkResult render.RenderableNodeSet + +type nodeSpec struct { + topology string + id string +} + +func renderableNode(n report.Node) render.RenderableNode { + node := render.NewRenderableNode(n.ID) + node.Topology = n.Topology + return node +} + +func TestMakeRenderableNodeSet(t *testing.T) { + for _, testcase := range []struct { + inputs []nodeSpec + wants []nodeSpec + }{ + {inputs: nil, wants: nil}, + {inputs: []nodeSpec{}, wants: []nodeSpec{}}, + { + inputs: []nodeSpec{{"", "a"}}, + wants: []nodeSpec{{"", "a"}}, + }, + { + inputs: []nodeSpec{{"", "a"}, {"", "a"}, {"1", "a"}}, + wants: []nodeSpec{{"", "a"}, {"1", "a"}}, + }, + { + inputs: []nodeSpec{{"", "b"}, {"", "c"}, {"", "a"}}, + wants: []nodeSpec{{"", "a"}, {"", "b"}, {"", "c"}}, + }, + { + inputs: []nodeSpec{{"2", "a"}, {"3", "a"}, {"1", "a"}}, + wants: []nodeSpec{{"1", "a"}, {"2", "a"}, {"3", "a"}}, + }, + } { + var ( + inputs []render.RenderableNode + wants []render.RenderableNode + ) + for _, spec := range testcase.inputs { + node := render.NewRenderableNode(spec.id) + node.Topology = spec.topology + inputs = append(inputs, node) + } + for _, spec := range testcase.wants { + node := render.NewRenderableNode(spec.id) + node.Topology = spec.topology + wants = append(wants, node) + } + if want, have := render.MakeRenderableNodeSet(wants...), render.MakeRenderableNodeSet(inputs...); !reflect.DeepEqual(want, have) { + t.Errorf("%#v: want %#v, have %#v", inputs, wants, have) + } + } +} + +func BenchmarkMakeRenderableNodeSet(b *testing.B) { + nodes := []render.RenderableNode{} + for i := 1000; i >= 0; i-- { + node := report.MakeNode().WithID(fmt.Sprint(i)).WithLatests(map[string]string{ + "a": "1", + "b": "2", + }) + rn := render.NewRenderableNode(node.ID) + rn.Node = node + nodes = append(nodes, rn) + } + b.ReportAllocs() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + benchmarkResult = render.MakeRenderableNodeSet(nodes...) + } +} + +func TestRenderableNodeSetAdd(t *testing.T) { + for _, testcase := range []struct { + input render.RenderableNodeSet + nodes []render.RenderableNode + want render.RenderableNodeSet + }{ + { + input: render.RenderableNodeSet{}, + nodes: []render.RenderableNode{}, + want: render.RenderableNodeSet{}, + }, + { + input: render.EmptyRenderableNodeSet, + nodes: []render.RenderableNode{}, + want: render.EmptyRenderableNodeSet, + }, + { + input: render.MakeRenderableNodeSet(renderableNode(report.MakeNode().WithID("a"))), + nodes: []render.RenderableNode{}, + want: render.MakeRenderableNodeSet(renderableNode(report.MakeNode().WithID("a"))), + }, + { + input: render.EmptyRenderableNodeSet, + nodes: []render.RenderableNode{renderableNode(report.MakeNode().WithID("a"))}, + want: render.MakeRenderableNodeSet(renderableNode(report.MakeNode().WithID("a"))), + }, + { + input: render.MakeRenderableNodeSet(renderableNode(report.MakeNode().WithID("a"))), + nodes: []render.RenderableNode{renderableNode(report.MakeNode().WithID("a"))}, + want: render.MakeRenderableNodeSet(renderableNode(report.MakeNode().WithID("a"))), + }, + { + input: render.MakeRenderableNodeSet(renderableNode(report.MakeNode().WithID("b"))), + nodes: []render.RenderableNode{ + renderableNode(report.MakeNode().WithID("a")), + renderableNode(report.MakeNode().WithID("b")), + }, + want: render.MakeRenderableNodeSet( + renderableNode(report.MakeNode().WithID("a")), + renderableNode(report.MakeNode().WithID("b")), + ), + }, + { + input: render.MakeRenderableNodeSet(renderableNode(report.MakeNode().WithID("a"))), + nodes: []render.RenderableNode{ + renderableNode(report.MakeNode().WithID("c")), + renderableNode(report.MakeNode().WithID("b")), + }, + want: render.MakeRenderableNodeSet( + renderableNode(report.MakeNode().WithID("a")), + renderableNode(report.MakeNode().WithID("b")), + renderableNode(report.MakeNode().WithID("c")), + ), + }, + { + input: render.MakeRenderableNodeSet( + renderableNode(report.MakeNode().WithID("a")), + renderableNode(report.MakeNode().WithID("c")), + ), + nodes: []render.RenderableNode{ + renderableNode(report.MakeNode().WithID("b")), + renderableNode(report.MakeNode().WithID("b")), + renderableNode(report.MakeNode().WithID("b")), + }, + want: render.MakeRenderableNodeSet( + renderableNode(report.MakeNode().WithID("a")), + renderableNode(report.MakeNode().WithID("b")), + renderableNode(report.MakeNode().WithID("c")), + ), + }, + } { + originalLen := testcase.input.Size() + if want, have := testcase.want, testcase.input.Add(testcase.nodes...); !reflect.DeepEqual(want, have) { + t.Errorf("%v + %v: want %v, have %v", testcase.input, testcase.nodes, want, have) + } + if testcase.input.Size() != originalLen { + t.Errorf("%v + %v: modified the original input!", testcase.input, testcase.nodes) + } + } +} + +func BenchmarkRenderableNodeSetAdd(b *testing.B) { + n := render.EmptyRenderableNodeSet + for i := 0; i < 600; i++ { + n = n.Add( + renderableNode(report.MakeNode().WithID(fmt.Sprint(i)).WithLatests(map[string]string{ + "a": "1", + "b": "2", + })), + ) + } + + node := renderableNode(report.MakeNode().WithID("401.5").WithLatests(map[string]string{ + "a": "1", + "b": "2", + })) + + b.ReportAllocs() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + benchmarkResult = n.Add(node) + } +} + +func TestRenderableNodeSetMerge(t *testing.T) { + for _, testcase := range []struct { + input render.RenderableNodeSet + other render.RenderableNodeSet + want render.RenderableNodeSet + }{ + {input: render.RenderableNodeSet{}, other: render.RenderableNodeSet{}, want: render.RenderableNodeSet{}}, + {input: render.EmptyRenderableNodeSet, other: render.EmptyRenderableNodeSet, want: render.EmptyRenderableNodeSet}, + { + input: render.MakeRenderableNodeSet(render.NewRenderableNode("a")), + other: render.EmptyRenderableNodeSet, + want: render.MakeRenderableNodeSet(render.NewRenderableNode("a")), + }, + { + input: render.EmptyRenderableNodeSet, + other: render.MakeRenderableNodeSet(render.NewRenderableNode("a")), + want: render.MakeRenderableNodeSet(render.NewRenderableNode("a")), + }, + { + input: render.MakeRenderableNodeSet(render.NewRenderableNode("a")), + other: render.MakeRenderableNodeSet(render.NewRenderableNode("b")), + want: render.MakeRenderableNodeSet(render.NewRenderableNode("a"), render.NewRenderableNode("b")), + }, + { + input: render.MakeRenderableNodeSet(render.NewRenderableNode("b")), + other: render.MakeRenderableNodeSet(render.NewRenderableNode("a")), + want: render.MakeRenderableNodeSet(render.NewRenderableNode("a"), render.NewRenderableNode("b")), + }, + { + input: render.MakeRenderableNodeSet(render.NewRenderableNode("a")), + other: render.MakeRenderableNodeSet(render.NewRenderableNode("a")), + want: render.MakeRenderableNodeSet(render.NewRenderableNode("a")), + }, + { + input: render.MakeRenderableNodeSet(render.NewRenderableNode("a"), render.NewRenderableNode("c")), + other: render.MakeRenderableNodeSet(render.NewRenderableNode("a"), render.NewRenderableNode("b")), + want: render.MakeRenderableNodeSet(render.NewRenderableNode("a"), render.NewRenderableNode("b"), render.NewRenderableNode("c")), + }, + { + input: render.MakeRenderableNodeSet(render.NewRenderableNode("b")), + other: render.MakeRenderableNodeSet(render.NewRenderableNode("a")), + want: render.MakeRenderableNodeSet(render.NewRenderableNode("a"), render.NewRenderableNode("b")), + }, + } { + originalLen := testcase.input.Size() + if want, have := testcase.want, testcase.input.Merge(testcase.other); !reflect.DeepEqual(want, have) { + t.Errorf("%v + %v: want %v, have %v", testcase.input, testcase.other, want, have) + } + if testcase.input.Size() != originalLen { + t.Errorf("%v + %v: modified the original input!", testcase.input, testcase.other) + } + } +} + +func BenchmarkRenderableNodeSetMerge(b *testing.B) { + n, other := render.RenderableNodeSet{}, render.RenderableNodeSet{} + for i := 0; i < 600; i++ { + n = n.Add( + renderableNode(report.MakeNode().WithID(fmt.Sprint(i)).WithLatests(map[string]string{ + "a": "1", + "b": "2", + })), + ) + } + + for i := 400; i < 1000; i++ { + other = other.Add( + renderableNode(report.MakeNode().WithID(fmt.Sprint(i)).WithLatests(map[string]string{ + "c": "1", + "d": "2", + })), + ) + } + b.ReportAllocs() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + benchmarkResult = n.Merge(other) + } +} diff --git a/render/renderable_node_test.go b/render/renderable_node_test.go index 9a9ad49a05..e6bf6745c7 100644 --- a/render/renderable_node_test.go +++ b/render/renderable_node_test.go @@ -37,7 +37,7 @@ func TestMergeRenderableNode(t *testing.T) { Rank: "", Pseudo: false, Node: report.MakeNode().WithAdjacent("a1"), - Children: report.MakeNodeSet(report.MakeNode().WithID("child1")), + Children: render.MakeRenderableNodeSet(render.NewRenderableNode("child1")), } node2 := render.RenderableNode{ ID: "foo", @@ -46,7 +46,7 @@ func TestMergeRenderableNode(t *testing.T) { Rank: "rank", Pseudo: false, Node: report.MakeNode().WithAdjacent("a2"), - Children: report.MakeNodeSet(report.MakeNode().WithID("child2")), + Children: render.MakeRenderableNodeSet(render.NewRenderableNode("child2")), } want := render.RenderableNode{ ID: "foo", @@ -55,7 +55,7 @@ func TestMergeRenderableNode(t *testing.T) { Rank: "rank", Pseudo: false, Node: report.MakeNode().WithID("foo").WithAdjacent("a1").WithAdjacent("a2"), - Children: report.MakeNodeSet(report.MakeNode().WithID("child1"), report.MakeNode().WithID("child2")), + Children: render.MakeRenderableNodeSet(render.NewRenderableNode("child1"), render.NewRenderableNode("child2")), EdgeMetadata: report.EdgeMetadata{}, }.Prune() have := node1.Merge(node2).Prune() diff --git a/render/short_lived_connections_test.go b/render/short_lived_connections_test.go index 9adf6ce4ef..71c4c6dc9d 100644 --- a/render/short_lived_connections_test.go +++ b/render/short_lived_connections_test.go @@ -73,7 +73,7 @@ var ( render.IncomingInternetID: { ID: render.IncomingInternetID, LabelMajor: render.InboundMajor, - LabelMinor: render.RequestsMinor, + LabelMinor: render.InboundMinor, Pseudo: true, Shape: "cloud", Node: report.MakeNode().WithAdjacent(render.MakeContainerID(containerID)), diff --git a/render/topologies.go b/render/topologies.go index ffe2772f59..ee9c431d34 100644 --- a/render/topologies.go +++ b/render/topologies.go @@ -13,7 +13,10 @@ import ( // EndpointRenderer is a Renderer which produces a renderable endpoint graph. var EndpointRenderer = MakeMap( MapEndpointIdentity, - SelectEndpoint, + MakeMap( + RemapEndpointIDs, + SelectEndpoint, + ), ) // ProcessRenderer is a Renderer which produces a renderable process diff --git a/render/topologies_test.go b/render/topologies_test.go index bbbcfcdf2f..449c85d762 100644 --- a/render/topologies_test.go +++ b/render/topologies_test.go @@ -1,7 +1,6 @@ package render_test import ( - "reflect" "testing" "github.com/weaveworks/scope/probe/docker" @@ -11,8 +10,28 @@ import ( "github.com/weaveworks/scope/report" "github.com/weaveworks/scope/test" "github.com/weaveworks/scope/test/fixture" + "github.com/weaveworks/scope/test/reflect" ) +func TestRemappedEndpoints(t *testing.T) { + have := render.MakeMap( + render.RemapEndpointIDs, + render.SelectEndpoint, + ).Render(fixture.Report).Prune() + want := expected.RemappedEndpoints + if !reflect.DeepEqual(want, have) { + t.Error(test.Diff(want, have)) + } +} + +func TestEndpointRenderer(t *testing.T) { + have := render.EndpointRenderer.Render(fixture.Report).Prune() + want := expected.RenderedEndpoints + if !reflect.DeepEqual(want, have) { + t.Error(test.Diff(want, have)) + } +} + func TestProcessRenderer(t *testing.T) { have := render.ProcessRenderer.Render(fixture.Report).Prune() want := expected.RenderedProcesses @@ -30,7 +49,7 @@ func TestProcessNameRenderer(t *testing.T) { } func TestContainerRenderer(t *testing.T) { - have := (render.ContainerWithImageNameRenderer.Render(fixture.Report)).Prune() + have := (render.ContainerRenderer.Render(fixture.Report)).Prune() want := expected.RenderedContainers if !reflect.DeepEqual(want, have) { t.Error(test.Diff(want, have)) @@ -44,9 +63,9 @@ func TestContainerFilterRenderer(t *testing.T) { input.Container.Nodes[fixture.ClientContainerNodeID] = input.Container.Nodes[fixture.ClientContainerNodeID].WithLatests(map[string]string{ docker.LabelPrefix + "works.weave.role": "system", }) - have := render.FilterSystem(render.ContainerWithImageNameRenderer).Render(input).Prune() + have := render.FilterSystem(render.ContainerRenderer).Render(input).Prune() want := expected.RenderedContainers.Copy() - delete(want, expected.ClientContainerRenderedID) + delete(want, expected.ClientContainerID) if !reflect.DeepEqual(want, have) { t.Error(test.Diff(want, have)) } @@ -71,23 +90,17 @@ func TestContainerWithHostIPsRenderer(t *testing.T) { } } -func TestContainerFilterRendererImageName(t *testing.T) { - // Test nodes are filtered by image name as well. - input := fixture.Report.Copy() - input.ContainerImage.Nodes[fixture.ClientContainerImageNodeID] = input.ContainerImage.Nodes[fixture.ClientContainerImageNodeID].WithLatests(map[string]string{ - docker.ImageName: "beta.gcr.io/google_containers/pause", - }) - have := render.FilterSystem(render.ContainerWithImageNameRenderer).Render(input).Prune() - want := expected.RenderedContainers.Copy() - delete(want, expected.ClientContainerRenderedID) +func TestContainerImageRenderer(t *testing.T) { + have := render.ContainerImageRenderer.Render(fixture.Report).Prune() + want := expected.RenderedContainerImages if !reflect.DeepEqual(want, have) { t.Error(test.Diff(want, have)) } } -func TestContainerImageRenderer(t *testing.T) { - have := render.ContainerImageRenderer.Render(fixture.Report).Prune() - want := expected.RenderedContainerImages +func TestAddressRenderer(t *testing.T) { + have := render.AddressRenderer.Render(fixture.Report).Prune() + want := expected.RenderedAddresses if !reflect.DeepEqual(want, have) { t.Error(test.Diff(want, have)) } @@ -124,7 +137,7 @@ func TestPodFilterRenderer(t *testing.T) { have := render.FilterSystem(render.PodRenderer).Render(input).Prune() want := expected.RenderedPods.Copy() delete(want, expected.ClientPodRenderedID) - delete(want, expected.ClientContainerRenderedID) + delete(want, expected.ClientContainerID) if !reflect.DeepEqual(want, have) { t.Error(test.Diff(want, have)) } diff --git a/report/counters.go b/report/counters.go index 71900b628a..28ce8e4795 100644 --- a/report/counters.go +++ b/report/counters.go @@ -103,7 +103,7 @@ func (c Counters) String() string { val, _ := c.psMap.Lookup(key) fmt.Fprintf(buf, "%s: %d, ", key, val) } - fmt.Fprintf(buf, "}\n") + fmt.Fprintf(buf, "}") return buf.String() } diff --git a/report/edge_metadatas.go b/report/edge_metadatas.go index 7d4ee38612..4540d4ebf8 100644 --- a/report/edge_metadatas.go +++ b/report/edge_metadatas.go @@ -6,6 +6,7 @@ import ( "fmt" "reflect" "sort" + "strconv" "github.com/mndrix/ps" "github.com/ugorji/go/codec" @@ -123,7 +124,7 @@ func (c EdgeMetadatas) String() string { val, _ := c.psMap.Lookup(key) fmt.Fprintf(buf, "%s: %v, ", key, val) } - fmt.Fprintf(buf, "}\n") + fmt.Fprintf(buf, "}") return buf.String() } @@ -219,6 +220,28 @@ type EdgeMetadata struct { IngressByteCount *uint64 `json:"ingress_byte_count,omitempty"` // Transport layer } +// String returns a string representation of this EdgeMetadata +// Helps with our use of Spew and diff. +func (e EdgeMetadata) String() string { + f := func(i *uint64) string { + if i == nil { + return "nil" + } + return strconv.FormatUint(*i, 10) + } + + return fmt.Sprintf(`{ +EgressPacketCount: %v, +IngressPacketCount: %v, +EgressByteCount: %v, +IngressByteCount: %v, +}`, + f(e.EgressPacketCount), + f(e.IngressPacketCount), + f(e.EgressByteCount), + f(e.IngressByteCount)) +} + // Copy returns a value copy of the EdgeMetadata. func (e EdgeMetadata) Copy() EdgeMetadata { return EdgeMetadata{ diff --git a/report/id_list.go b/report/id_list.go index 50149a0c29..c9778bbcdb 100644 --- a/report/id_list.go +++ b/report/id_list.go @@ -1,7 +1,5 @@ package report -import "sort" - // IDList is a list of string IDs, which are always sorted and unique. type IDList StringSet @@ -35,6 +33,10 @@ func (a IDList) Merge(b IDList) IDList { // Contains returns true if id is in the list. func (a IDList) Contains(id string) bool { - i := sort.Search(len(a), func(i int) bool { return a[i] >= id }) - return i < len(a) && a[i] == id + return StringSet(a).Contains(id) +} + +// Intersection returns the intersection of a and b +func (a IDList) Intersection(b IDList) IDList { + return IDList(StringSet(a).Intersection(StringSet(b))) } diff --git a/report/latest_map.go b/report/latest_map.go index 18af1bca90..0b407fa971 100644 --- a/report/latest_map.go +++ b/report/latest_map.go @@ -137,7 +137,7 @@ func (m LatestMap) String() string { val, _ := m.Map.Lookup(key) fmt.Fprintf(buf, "%s: %s,\n", key, val) } - fmt.Fprintf(buf, "}\n") + fmt.Fprintf(buf, "}") return buf.String() } diff --git a/report/node_set_test.go b/report/node_set_test.go deleted file mode 100644 index 05434ccb24..0000000000 --- a/report/node_set_test.go +++ /dev/null @@ -1,231 +0,0 @@ -package report_test - -import ( - "fmt" - "testing" - - "github.com/weaveworks/scope/report" - "github.com/weaveworks/scope/test/reflect" -) - -var benchmarkResult report.NodeSet - -type nodeSpec struct { - topology string - id string -} - -func TestMakeNodeSet(t *testing.T) { - for _, testcase := range []struct { - inputs []nodeSpec - wants []nodeSpec - }{ - {inputs: nil, wants: nil}, - {inputs: []nodeSpec{}, wants: []nodeSpec{}}, - { - inputs: []nodeSpec{{"", "a"}}, - wants: []nodeSpec{{"", "a"}}, - }, - { - inputs: []nodeSpec{{"", "a"}, {"", "a"}, {"1", "a"}}, - wants: []nodeSpec{{"", "a"}, {"1", "a"}}, - }, - { - inputs: []nodeSpec{{"", "b"}, {"", "c"}, {"", "a"}}, - wants: []nodeSpec{{"", "a"}, {"", "b"}, {"", "c"}}, - }, - { - inputs: []nodeSpec{{"2", "a"}, {"3", "a"}, {"1", "a"}}, - wants: []nodeSpec{{"1", "a"}, {"2", "a"}, {"3", "a"}}, - }, - } { - var ( - inputs []report.Node - wants []report.Node - ) - for _, spec := range testcase.inputs { - inputs = append(inputs, report.MakeNode().WithTopology(spec.topology).WithID(spec.id)) - } - for _, spec := range testcase.wants { - wants = append(wants, report.MakeNode().WithTopology(spec.topology).WithID(spec.id)) - } - if want, have := report.MakeNodeSet(wants...), report.MakeNodeSet(inputs...); !reflect.DeepEqual(want, have) { - t.Errorf("%#v: want %#v, have %#v", inputs, wants, have) - } - } -} - -func BenchmarkMakeNodeSet(b *testing.B) { - nodes := []report.Node{} - for i := 1000; i >= 0; i-- { - node := report.MakeNode().WithID(fmt.Sprint(i)).WithLatests(map[string]string{ - "a": "1", - "b": "2", - }) - nodes = append(nodes, node) - } - b.ReportAllocs() - b.ResetTimer() - - for i := 0; i < b.N; i++ { - benchmarkResult = report.MakeNodeSet(nodes...) - } -} - -func TestNodeSetAdd(t *testing.T) { - for _, testcase := range []struct { - input report.NodeSet - nodes []report.Node - want report.NodeSet - }{ - {input: report.NodeSet{}, nodes: []report.Node{}, want: report.NodeSet{}}, - { - input: report.EmptyNodeSet, - nodes: []report.Node{}, - want: report.EmptyNodeSet, - }, - { - input: report.MakeNodeSet(report.MakeNode().WithID("a")), - nodes: []report.Node{}, - want: report.MakeNodeSet(report.MakeNode().WithID("a")), - }, - { - input: report.EmptyNodeSet, - nodes: []report.Node{report.MakeNode().WithID("a")}, - want: report.MakeNodeSet(report.MakeNode().WithID("a")), - }, - { - input: report.MakeNodeSet(report.MakeNode().WithID("a")), - nodes: []report.Node{report.MakeNode().WithID("a")}, - want: report.MakeNodeSet(report.MakeNode().WithID("a")), - }, - { - input: report.MakeNodeSet(report.MakeNode().WithID("b")), - nodes: []report.Node{report.MakeNode().WithID("a"), report.MakeNode().WithID("b")}, - want: report.MakeNodeSet(report.MakeNode().WithID("a"), report.MakeNode().WithID("b")), - }, - { - input: report.MakeNodeSet(report.MakeNode().WithID("a")), - nodes: []report.Node{report.MakeNode().WithID("c"), report.MakeNode().WithID("b")}, - want: report.MakeNodeSet(report.MakeNode().WithID("a"), report.MakeNode().WithID("b"), report.MakeNode().WithID("c")), - }, - { - input: report.MakeNodeSet(report.MakeNode().WithID("a"), report.MakeNode().WithID("c")), - nodes: []report.Node{report.MakeNode().WithID("b"), report.MakeNode().WithID("b"), report.MakeNode().WithID("b")}, - want: report.MakeNodeSet(report.MakeNode().WithID("a"), report.MakeNode().WithID("b"), report.MakeNode().WithID("c")), - }, - } { - originalLen := testcase.input.Size() - if want, have := testcase.want, testcase.input.Add(testcase.nodes...); !reflect.DeepEqual(want, have) { - t.Errorf("%v + %v: want %v, have %v", testcase.input, testcase.nodes, want, have) - } - if testcase.input.Size() != originalLen { - t.Errorf("%v + %v: modified the original input!", testcase.input, testcase.nodes) - } - } -} - -func BenchmarkNodeSetAdd(b *testing.B) { - n := report.EmptyNodeSet - for i := 0; i < 600; i++ { - n = n.Add( - report.MakeNode().WithID(fmt.Sprint(i)).WithLatests(map[string]string{ - "a": "1", - "b": "2", - }), - ) - } - - node := report.MakeNode().WithID("401.5").WithLatests(map[string]string{ - "a": "1", - "b": "2", - }) - - b.ReportAllocs() - b.ResetTimer() - - for i := 0; i < b.N; i++ { - benchmarkResult = n.Add(node) - } -} - -func TestNodeSetMerge(t *testing.T) { - for _, testcase := range []struct { - input report.NodeSet - other report.NodeSet - want report.NodeSet - }{ - {input: report.NodeSet{}, other: report.NodeSet{}, want: report.NodeSet{}}, - {input: report.EmptyNodeSet, other: report.EmptyNodeSet, want: report.EmptyNodeSet}, - { - input: report.MakeNodeSet(report.MakeNode().WithID("a")), - other: report.EmptyNodeSet, - want: report.MakeNodeSet(report.MakeNode().WithID("a")), - }, - { - input: report.EmptyNodeSet, - other: report.MakeNodeSet(report.MakeNode().WithID("a")), - want: report.MakeNodeSet(report.MakeNode().WithID("a")), - }, - { - input: report.MakeNodeSet(report.MakeNode().WithID("a")), - other: report.MakeNodeSet(report.MakeNode().WithID("b")), - want: report.MakeNodeSet(report.MakeNode().WithID("a"), report.MakeNode().WithID("b")), - }, - { - input: report.MakeNodeSet(report.MakeNode().WithID("b")), - other: report.MakeNodeSet(report.MakeNode().WithID("a")), - want: report.MakeNodeSet(report.MakeNode().WithID("a"), report.MakeNode().WithID("b")), - }, - { - input: report.MakeNodeSet(report.MakeNode().WithID("a")), - other: report.MakeNodeSet(report.MakeNode().WithID("a")), - want: report.MakeNodeSet(report.MakeNode().WithID("a")), - }, - { - input: report.MakeNodeSet(report.MakeNode().WithID("a"), report.MakeNode().WithID("c")), - other: report.MakeNodeSet(report.MakeNode().WithID("a"), report.MakeNode().WithID("b")), - want: report.MakeNodeSet(report.MakeNode().WithID("a"), report.MakeNode().WithID("b"), report.MakeNode().WithID("c")), - }, - { - input: report.MakeNodeSet(report.MakeNode().WithID("b")), - other: report.MakeNodeSet(report.MakeNode().WithID("a")), - want: report.MakeNodeSet(report.MakeNode().WithID("a"), report.MakeNode().WithID("b")), - }, - } { - originalLen := testcase.input.Size() - if want, have := testcase.want, testcase.input.Merge(testcase.other); !reflect.DeepEqual(want, have) { - t.Errorf("%v + %v: want %v, have %v", testcase.input, testcase.other, want, have) - } - if testcase.input.Size() != originalLen { - t.Errorf("%v + %v: modified the original input!", testcase.input, testcase.other) - } - } -} - -func BenchmarkNodeSetMerge(b *testing.B) { - n, other := report.NodeSet{}, report.NodeSet{} - for i := 0; i < 600; i++ { - n = n.Add( - report.MakeNode().WithID(fmt.Sprint(i)).WithLatests(map[string]string{ - "a": "1", - "b": "2", - }), - ) - } - - for i := 400; i < 1000; i++ { - other = other.Add( - report.MakeNode().WithID(fmt.Sprint(i)).WithLatests(map[string]string{ - "c": "1", - "d": "2", - }), - ) - } - b.ReportAllocs() - b.ResetTimer() - - for i := 0; i < b.N; i++ { - benchmarkResult = n.Merge(other) - } -} diff --git a/report/sets.go b/report/sets.go index 71fdb7782a..0dd6125614 100644 --- a/report/sets.go +++ b/report/sets.go @@ -114,7 +114,7 @@ func (s Sets) String() string { val, _ := s.psMap.Lookup(key) fmt.Fprintf(buf, "%s: %v, ", key, val) } - fmt.Fprintf(buf, "}\n") + fmt.Fprintf(buf, "}") return buf.String() } diff --git a/report/string_set.go b/report/string_set.go index 6a43bee82d..42f02cea88 100644 --- a/report/string_set.go +++ b/report/string_set.go @@ -35,6 +35,22 @@ func (s StringSet) Contains(str string) bool { return i < len(s) && s[i] == str } +// Intersection returns the intersections of a and b +func (s StringSet) Intersection(b StringSet) StringSet { + result, i, j := EmptyStringSet, 0, 0 + for i < len(s) && j < len(b) { + if s[i] == b[j] { + result = result.Add(s[i]) + } + if s[i] < b[j] { + i++ + } else { + j++ + } + } + return result +} + // Add adds the strings to the StringSet. Add is the only valid way to grow a // StringSet. Add returns the StringSet to enable chaining. func (s StringSet) Add(strs ...string) StringSet { From 09de883bc6b514cd3b778cfbcf71cbbdaf388fb1 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Thu, 3 Mar 2016 17:46:35 +0000 Subject: [PATCH 4/6] Review feedback --- render/detailed/connections.go | 134 ++++++++++++++++----------------- render/detailed/node.go | 4 +- render/mapping.go | 7 +- 3 files changed, 74 insertions(+), 71 deletions(-) diff --git a/render/detailed/connections.go b/render/detailed/connections.go index fe7fa00cdd..1c48dd6d07 100644 --- a/render/detailed/connections.go +++ b/render/detailed/connections.go @@ -29,76 +29,13 @@ var ( } ) -func endpointChildrenOf(n render.RenderableNode) []render.RenderableNode { - result := []render.RenderableNode{} - n.Children.ForEach(func(child render.RenderableNode) { - if _, _, _, ok := render.ParseEndpointID(child.ID); ok { - result = append(result, child) - } - }) - return result -} - -func endpointChildIDsOf(n render.RenderableNode) report.IDList { - result := report.MakeIDList() - n.Children.ForEach(func(child render.RenderableNode) { - if _, _, _, ok := render.ParseEndpointID(child.ID); ok { - result = append(result, child.ID) - } - }) - return result -} - type connectionsRow struct { remoteNode, localNode *render.RenderableNode remoteAddr, localAddr string port string // always the server-side port } -func buildConnectionRows(in map[connectionsRow]int, includeLocal bool) []NodeSummary { - nodes := []NodeSummary{} - for row, count := range in { - id, label, linkable := row.remoteNode.ID, row.remoteNode.LabelMajor, true - if row.remoteAddr != "" { - id, label, linkable = row.remoteAddr+":"+row.port, row.remoteAddr, false - } - metadata := []MetadataRow{} - if includeLocal { - metadata = append(metadata, - MetadataRow{ - ID: "foo", - Value: row.localAddr, - Datatype: number, - }) - } - metadata = append(metadata, - MetadataRow{ - ID: portKey, - Value: row.port, - Datatype: number, - }, - MetadataRow{ - ID: countKey, - Value: strconv.Itoa(count), - Datatype: number, - }, - ) - nodes = append(nodes, NodeSummary{ - ID: id, - Label: label, - Linkable: linkable, - Metadata: metadata, - }) - } - sort.Sort(nodeSummariesByID(nodes)) - return nodes -} - -func isInternetNode(n render.RenderableNode) bool { - return n.ID == render.IncomingInternetID || n.ID == render.OutgoingInternetID -} - -func makeIncomingConnectionsTable(topologyID string, n render.RenderableNode, ns render.RenderableNodes) NodeSummaryGroup { +func incomingConnectionsTable(topologyID string, n render.RenderableNode, ns render.RenderableNodes) NodeSummaryGroup { localEndpointIDs := endpointChildIDsOf(n) // For each node which has an edge TO me @@ -143,11 +80,11 @@ func makeIncomingConnectionsTable(topologyID string, n render.RenderableNode, ns TopologyID: topologyID, Label: "Inbound", Columns: columnHeaders, - Nodes: buildConnectionRows(counts, isInternetNode(n)), + Nodes: connectionRows(counts, isInternetNode(n)), } } -func makeOutgoingConnectionsTable(topologyID string, n render.RenderableNode, ns render.RenderableNodes) NodeSummaryGroup { +func outgoingConnectionsTable(topologyID string, n render.RenderableNode, ns render.RenderableNodes) NodeSummaryGroup { localEndpoints := endpointChildrenOf(n) // For each node which has an edge FROM me @@ -192,6 +129,69 @@ func makeOutgoingConnectionsTable(topologyID string, n render.RenderableNode, ns TopologyID: topologyID, Label: "Outbound", Columns: columnHeaders, - Nodes: buildConnectionRows(counts, isInternetNode(n)), + Nodes: connectionRows(counts, isInternetNode(n)), } } + +func endpointChildrenOf(n render.RenderableNode) []render.RenderableNode { + result := []render.RenderableNode{} + n.Children.ForEach(func(child render.RenderableNode) { + if _, _, _, ok := render.ParseEndpointID(child.ID); ok { + result = append(result, child) + } + }) + return result +} + +func endpointChildIDsOf(n render.RenderableNode) report.IDList { + result := report.MakeIDList() + n.Children.ForEach(func(child render.RenderableNode) { + if _, _, _, ok := render.ParseEndpointID(child.ID); ok { + result = append(result, child.ID) + } + }) + return result +} + +func isInternetNode(n render.RenderableNode) bool { + return n.ID == render.IncomingInternetID || n.ID == render.OutgoingInternetID +} + +func connectionRows(in map[connectionsRow]int, includeLocal bool) []NodeSummary { + nodes := []NodeSummary{} + for row, count := range in { + id, label, linkable := row.remoteNode.ID, row.remoteNode.LabelMajor, true + if row.remoteAddr != "" { + id, label, linkable = row.remoteAddr+":"+row.port, row.remoteAddr, false + } + metadata := []MetadataRow{} + if includeLocal { + metadata = append(metadata, + MetadataRow{ + ID: "foo", + Value: row.localAddr, + Datatype: number, + }) + } + metadata = append(metadata, + MetadataRow{ + ID: portKey, + Value: row.port, + Datatype: number, + }, + MetadataRow{ + ID: countKey, + Value: strconv.Itoa(count), + Datatype: number, + }, + ) + nodes = append(nodes, NodeSummary{ + ID: id, + Label: label, + Linkable: linkable, + Metadata: metadata, + }) + } + sort.Sort(nodeSummariesByID(nodes)) + return nodes +} diff --git a/render/detailed/node.go b/render/detailed/node.go index eb4fecca23..057c55711f 100644 --- a/render/detailed/node.go +++ b/render/detailed/node.go @@ -92,8 +92,8 @@ func MakeNode(topologyID string, r report.Report, ns render.RenderableNodes, n r Children: children(n), Parents: Parents(r, n), Connections: []NodeSummaryGroup{ - makeIncomingConnectionsTable(topologyID, n, ns), - makeOutgoingConnectionsTable(topologyID, n, ns), + incomingConnectionsTable(topologyID, n, ns), + outgoingConnectionsTable(topologyID, n, ns), }, } } diff --git a/render/mapping.go b/render/mapping.go index 808f9b976b..39133ab026 100644 --- a/render/mapping.go +++ b/render/mapping.go @@ -60,7 +60,10 @@ func theInternetNode(m RenderableNode) RenderableNode { return node } -// RemapEndpointIDs remaps endpoints to have the right id format. +// RemapEndpointIDs remaps endpoints to have an id format consistent +// with render/id.go; no pseudo nodes are introduced in this step, so +// that pseudo nodes introduces later are guaranteed to have endpoints +// as children. This is needed to construct the connection details tables. func RemapEndpointIDs(m RenderableNode, _ report.Networks) RenderableNodes { addr, ok := m.Latest.Lookup(endpoint.Addr) if !ok { @@ -414,7 +417,7 @@ func MapIP2Container(n RenderableNode, _ report.Networks) RenderableNodes { return RenderableNodes{} } - // Propogate the internet pseudo node + // Propagate the internet pseudo node if strings.HasSuffix(n.ID, TheInternetID) { return RenderableNodes{n.ID: n} } From 7d0b1cec61eab0fc7fac69a89c66e5a14f2704ac Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Thu, 3 Mar 2016 20:22:47 +0000 Subject: [PATCH 5/6] Fix integration test --- integration/300_internet_edge_test.sh | 4 +--- integration/config.sh | 21 ++++++++++++++++----- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/integration/300_internet_edge_test.sh b/integration/300_internet_edge_test.sh index eff59e43cb..614123fcdd 100755 --- a/integration/300_internet_edge_test.sh +++ b/integration/300_internet_edge_test.sh @@ -24,9 +24,7 @@ do_connections& wait_for_containers $HOST1 60 nginx "The Internet" -has_container $HOST1 nginx -has_container $HOST1 "The Internet" -has_connection containers $HOST1 "The Internet" nginx +has_connection_by_id containers $HOST1 "in-theinternet" $(node_id containers $HOST1 nginx) kill %do_connections diff --git a/integration/config.sh b/integration/config.sh index 5e6b693647..251c436f10 100644 --- a/integration/config.sh +++ b/integration/config.sh @@ -59,14 +59,13 @@ container_id() { } # this checks we have an edge from container 1 to container 2 -has_connection() { +has_connection_by_id() { local view="$1" local host="$2" - local from="$3" - local to="$4" + local from_id="$3" + local to_id="$4" local timeout="${5:-60}" - local from_id=$(node_id "${view}" "${host}" "${from}") - local to_id=$(node_id "${view}" "${host}" "${to}") + for i in $(seq $timeout); do local nodes="$(curl -s http://$host:4040/api/topology/${view}?system=show)" local edge=$(echo "$nodes" | jq -r ".nodes[\"$from_id\"].adjacency | contains([\"$to_id\"])" 2>/dev/null) @@ -82,6 +81,18 @@ has_connection() { assert "curl -s http://$host:4040/api/topology/${view}?system=show | jq -r '.nodes[\"$from_id\"].adjacency | contains([\"$to_id\"])'" true } +has_connection() { + local view="$1" + local host="$2" + local from="$3" + local to="$4" + local timeout="${5:-60}" + local from_id="$(node_id "${view}" "${host}" "${from}")" + local to_id="$(node_id "${view}" "${host}" "${to}")" + + has_connection_by_id "${view}" "${host}" "${from_id}" "${to_id}" "${timeout}" +} + wait_for() { local view="$1" local host="$2" From 51fa8d8ce0297d93539dccebc183cb3cba04ee5c Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Fri, 4 Mar 2016 14:07:56 +0000 Subject: [PATCH 6/6] Review feedback II --- render/detailed/node_test.go | 34 ------ render/expected/expected.go | 229 +++++++++-------------------------- render/id.go | 5 - render/mapping.go | 119 ++++++++---------- render/topologies.go | 5 +- render/topologies_test.go | 11 -- 6 files changed, 109 insertions(+), 294 deletions(-) diff --git a/render/detailed/node_test.go b/render/detailed/node_test.go index c4df7dc665..ec2cbe02d7 100644 --- a/render/detailed/node_test.go +++ b/render/detailed/node_test.go @@ -261,40 +261,6 @@ func TestMakeDetailedContainerNode(t *testing.T) { }, }, }, - { - ID: "pseudo:10.10.10.10:192.168.1.1:80", - Label: "10.10.10.10", - Linkable: true, - Metadata: []detailed.MetadataRow{ - { - ID: "port", - Value: "80", - Datatype: "number", - }, - { - ID: "count", - Value: "2", - Datatype: "number", - }, - }, - }, - { - ID: "pseudo:10.10.10.11:192.168.1.1:80", - Label: "10.10.10.11", - Linkable: true, - Metadata: []detailed.MetadataRow{ - { - ID: "port", - Value: "80", - Datatype: "number", - }, - { - ID: "count", - Value: "1", - Datatype: "number", - }, - }, - }, }, }, { diff --git a/render/expected/expected.go b/render/expected/expected.go index c0be8a194c..76b763764c 100644 --- a/render/expected/expected.go +++ b/render/expected/expected.go @@ -2,7 +2,6 @@ package expected import ( "fmt" - "net" "github.com/weaveworks/scope/render" "github.com/weaveworks/scope/report" @@ -27,7 +26,7 @@ var ( NonContainerEndpointID = render.MakeEndpointID(fixture.ServerHostID, fixture.ServerIP, fixture.NonContainerClientPort) GoogleEndpointID = render.MakeEndpointID("", fixture.GoogleIP, fixture.GooglePort) - RemappedEndpoints = (render.RenderableNodes{ + RenderedEndpoints = (render.RenderableNodes{ Client54001EndpointID: { ID: Client54001EndpointID, Shape: circle, @@ -104,12 +103,12 @@ var ( }, }).Prune() - Client54001PseudoEndpointID = render.MakePseudoEndpointID(fixture.ClientHostID, fixture.ClientIP, fixture.ClientPort54001) - Client54002PseudoEndpointID = render.MakePseudoEndpointID(fixture.ClientHostID, fixture.ClientIP, fixture.ClientPort54002) - ServerPseudoEndpointID = render.MakePseudoEndpointID(fixture.ServerHostID, fixture.ServerIP, fixture.ServerPort) - NonContainerPseudoEndpointID = render.MakePseudoEndpointID(fixture.ServerHostID, fixture.ServerIP, fixture.NonContainerClientPort) - unknownPseudoNode1ID = render.MakePseudoNodeID(fixture.UnknownClient1IP, fixture.ServerIP, fixture.ServerPort) - unknownPseudoNode2ID = render.MakePseudoNodeID(fixture.UnknownClient3IP, fixture.ServerIP, fixture.ServerPort) + ClientProcess1ID = render.MakeProcessID(fixture.ClientHostID, fixture.Client1PID) + ClientProcess2ID = render.MakeProcessID(fixture.ClientHostID, fixture.Client2PID) + ServerProcessID = render.MakeProcessID(fixture.ServerHostID, fixture.ServerPID) + nonContainerProcessID = render.MakeProcessID(fixture.ServerHostID, fixture.NonContainerPID) + unknownPseudoNode1ID = render.MakePseudoNodeID(fixture.UnknownClient1IP, fixture.ServerIP, fixture.ServerPort) + unknownPseudoNode2ID = render.MakePseudoNodeID(fixture.UnknownClient3IP, fixture.ServerIP, fixture.ServerPort) unknownPseudoNode1 = func(adjacent string) render.RenderableNode { return render.RenderableNode{ @@ -119,8 +118,8 @@ var ( Shape: circle, Node: report.MakeNode().WithAdjacent(adjacent), Children: render.MakeRenderableNodeSet( - RemappedEndpoints[UnknownClient1EndpointID], - RemappedEndpoints[UnknownClient2EndpointID], + RenderedEndpoints[UnknownClient1EndpointID], + RenderedEndpoints[UnknownClient2EndpointID], ), EdgeMetadata: report.EdgeMetadata{ EgressPacketCount: newu64(70), @@ -136,7 +135,7 @@ var ( Shape: circle, Node: report.MakeNode().WithAdjacent(adjacent), Children: render.MakeRenderableNodeSet( - RemappedEndpoints[UnknownClient3EndpointID], + RenderedEndpoints[UnknownClient3EndpointID], ), EdgeMetadata: report.EdgeMetadata{ EgressPacketCount: newu64(50), @@ -153,7 +152,7 @@ var ( Shape: cloud, Node: report.MakeNode().WithAdjacent(adjacent), Children: render.MakeRenderableNodeSet( - RemappedEndpoints[RandomClientEndpointID], + RenderedEndpoints[RandomClientEndpointID], ), EdgeMetadata: report.EdgeMetadata{ EgressPacketCount: newu64(60), @@ -170,86 +169,20 @@ var ( Node: report.MakeNode(), EdgeMetadata: report.EdgeMetadata{}, Children: render.MakeRenderableNodeSet( - RemappedEndpoints[GoogleEndpointID], + RenderedEndpoints[GoogleEndpointID], ), } - RenderedEndpoints = (render.RenderableNodes{ - Client54001PseudoEndpointID: { - ID: Client54001PseudoEndpointID, - LabelMajor: net.JoinHostPort(fixture.ClientIP, fixture.ClientPort54001), - LabelMinor: fmt.Sprintf("%s (%s)", fixture.ClientHostID, fixture.Client1PID), - Shape: circle, - Node: report.MakeNode().WithAdjacent(ServerPseudoEndpointID), - Children: render.MakeRenderableNodeSet( - RemappedEndpoints[Client54001EndpointID], - ), - EdgeMetadata: report.EdgeMetadata{ - EgressPacketCount: newu64(10), - EgressByteCount: newu64(100), - }, - }, - Client54002PseudoEndpointID: { - ID: Client54002PseudoEndpointID, - LabelMajor: net.JoinHostPort(fixture.ClientIP, fixture.ClientPort54002), - LabelMinor: fmt.Sprintf("%s (%s)", fixture.ClientHostID, fixture.Client2PID), - Shape: circle, - Node: report.MakeNode().WithAdjacent(ServerPseudoEndpointID), - Children: render.MakeRenderableNodeSet( - RemappedEndpoints[Client54002EndpointID], - ), - EdgeMetadata: report.EdgeMetadata{ - EgressPacketCount: newu64(20), - EgressByteCount: newu64(200), - }, - }, - ServerPseudoEndpointID: { - ID: ServerPseudoEndpointID, - LabelMajor: net.JoinHostPort(fixture.ServerIP, fixture.ServerPort), - LabelMinor: fmt.Sprintf("%s (%s)", fixture.ServerHostID, fixture.ServerPID), - Shape: circle, - Node: report.MakeNode(), - Children: render.MakeRenderableNodeSet( - RemappedEndpoints[ServerEndpointID], - ), - EdgeMetadata: report.EdgeMetadata{ - IngressPacketCount: newu64(210), - IngressByteCount: newu64(2100), - }, - }, - NonContainerPseudoEndpointID: { - ID: NonContainerPseudoEndpointID, - LabelMajor: net.JoinHostPort(fixture.ServerIP, fixture.NonContainerClientPort), - LabelMinor: fmt.Sprintf("%s (%s)", fixture.ServerHostID, fixture.NonContainerPID), - Shape: circle, - Node: report.MakeNode().WithAdjacent(render.OutgoingInternetID), - Children: render.MakeRenderableNodeSet( - RemappedEndpoints[NonContainerEndpointID], - ), - }, - unknownPseudoNode1ID: unknownPseudoNode1(ServerPseudoEndpointID), - unknownPseudoNode2ID: unknownPseudoNode2(ServerPseudoEndpointID), - render.IncomingInternetID: theIncomingInternetNode(ServerPseudoEndpointID), - render.OutgoingInternetID: theOutgoingInternetNode, - }).Prune() - - ClientProcess1ID = render.MakeProcessID(fixture.ClientHostID, fixture.Client1PID) - ClientProcess2ID = render.MakeProcessID(fixture.ClientHostID, fixture.Client2PID) - ServerProcessID = render.MakeProcessID(fixture.ServerHostID, fixture.ServerPID) - nonContainerProcessID = render.MakeProcessID(fixture.ServerHostID, fixture.NonContainerPID) - RenderedProcesses = (render.RenderableNodes{ ClientProcess1ID: { ID: ClientProcess1ID, LabelMajor: fixture.Client1Name, LabelMinor: fmt.Sprintf("%s (%s)", fixture.ClientHostID, fixture.Client1PID), Rank: fixture.Client1Name, - Pseudo: false, Shape: square, Node: report.MakeNode().WithAdjacent(ServerProcessID), Children: render.MakeRenderableNodeSet( - RemappedEndpoints[Client54001EndpointID], - RenderedEndpoints[Client54001PseudoEndpointID], + RenderedEndpoints[Client54001EndpointID], ), EdgeMetadata: report.EdgeMetadata{ EgressPacketCount: newu64(10), @@ -261,12 +194,10 @@ var ( LabelMajor: fixture.Client2Name, LabelMinor: fmt.Sprintf("%s (%s)", fixture.ClientHostID, fixture.Client2PID), Rank: fixture.Client2Name, - Pseudo: false, Shape: square, Node: report.MakeNode().WithAdjacent(ServerProcessID), Children: render.MakeRenderableNodeSet( - RemappedEndpoints[Client54002EndpointID], - RenderedEndpoints[Client54002PseudoEndpointID], + RenderedEndpoints[Client54002EndpointID], ), EdgeMetadata: report.EdgeMetadata{ EgressPacketCount: newu64(20), @@ -278,12 +209,10 @@ var ( LabelMajor: fixture.ServerName, LabelMinor: fmt.Sprintf("%s (%s)", fixture.ServerHostID, fixture.ServerPID), Rank: fixture.ServerName, - Pseudo: false, Shape: square, Node: report.MakeNode(), Children: render.MakeRenderableNodeSet( - RemappedEndpoints[ServerEndpointID], - RenderedEndpoints[ServerPseudoEndpointID], + RenderedEndpoints[ServerEndpointID], ), EdgeMetadata: report.EdgeMetadata{ IngressPacketCount: newu64(210), @@ -295,12 +224,10 @@ var ( LabelMajor: fixture.NonContainerName, LabelMinor: fmt.Sprintf("%s (%s)", fixture.ServerHostID, fixture.NonContainerPID), Rank: fixture.NonContainerName, - Pseudo: false, Shape: square, Node: report.MakeNode().WithAdjacent(render.OutgoingInternetID), Children: render.MakeRenderableNodeSet( - RemappedEndpoints[NonContainerEndpointID], - RenderedEndpoints[NonContainerPseudoEndpointID], + RenderedEndpoints[NonContainerEndpointID], ), EdgeMetadata: report.EdgeMetadata{}, }, @@ -316,14 +243,11 @@ var ( LabelMajor: fixture.Client1Name, LabelMinor: "2 processes", Rank: fixture.Client1Name, - Pseudo: false, Shape: square, Stack: true, Children: render.MakeRenderableNodeSet( - RemappedEndpoints[Client54001EndpointID], - RemappedEndpoints[Client54002EndpointID], - RenderedEndpoints[Client54001PseudoEndpointID], - RenderedEndpoints[Client54002PseudoEndpointID], + RenderedEndpoints[Client54001EndpointID], + RenderedEndpoints[Client54002EndpointID], RenderedProcesses[ClientProcess1ID], RenderedProcesses[ClientProcess2ID], ), @@ -338,12 +262,10 @@ var ( LabelMajor: fixture.ServerName, LabelMinor: "1 process", Rank: fixture.ServerName, - Pseudo: false, Shape: square, Stack: true, Children: render.MakeRenderableNodeSet( - RemappedEndpoints[ServerEndpointID], - RenderedEndpoints[ServerPseudoEndpointID], + RenderedEndpoints[ServerEndpointID], RenderedProcesses[ServerProcessID], ), Node: report.MakeNode(), @@ -357,12 +279,10 @@ var ( LabelMajor: fixture.NonContainerName, LabelMinor: "1 process", Rank: fixture.NonContainerName, - Pseudo: false, Shape: square, Stack: true, Children: render.MakeRenderableNodeSet( - RemappedEndpoints[NonContainerEndpointID], - RenderedEndpoints[NonContainerPseudoEndpointID], + RenderedEndpoints[NonContainerEndpointID], RenderedProcesses[nonContainerProcessID], ), Node: report.MakeNode().WithAdjacent(render.OutgoingInternetID), @@ -383,13 +303,10 @@ var ( ID: ClientContainerID, LabelMajor: "client", LabelMinor: fixture.ClientHostName, - Pseudo: false, Shape: hexagon, Children: render.MakeRenderableNodeSet( - RemappedEndpoints[Client54001EndpointID], - RemappedEndpoints[Client54002EndpointID], - RenderedEndpoints[Client54001PseudoEndpointID], - RenderedEndpoints[Client54002PseudoEndpointID], + RenderedEndpoints[Client54001EndpointID], + RenderedEndpoints[Client54002EndpointID], RenderedProcesses[ClientProcess1ID], RenderedProcesses[ClientProcess2ID], ), @@ -404,11 +321,9 @@ var ( ID: ServerContainerID, LabelMajor: "server", LabelMinor: fixture.ServerHostName, - Pseudo: false, Shape: hexagon, Children: render.MakeRenderableNodeSet( - RemappedEndpoints[ServerEndpointID], - RenderedEndpoints[ServerPseudoEndpointID], + RenderedEndpoints[ServerEndpointID], RenderedProcesses[ServerProcessID], ), Node: report.MakeNode(), @@ -422,19 +337,18 @@ var ( ID: uncontainedServerID, LabelMajor: render.UncontainedMajor, LabelMinor: fixture.ServerHostName, - Pseudo: true, Shape: square, Stack: true, + Pseudo: true, Children: render.MakeRenderableNodeSet( - RemappedEndpoints[NonContainerEndpointID], - RenderedEndpoints[NonContainerPseudoEndpointID], + RenderedEndpoints[NonContainerEndpointID], RenderedProcesses[nonContainerProcessID], ), Node: report.MakeNode().WithAdjacent(render.OutgoingInternetID), EdgeMetadata: report.EdgeMetadata{}, }, - unknownPseudoNode1ID: unknownPseudoNode1(ServerContainerID), - unknownPseudoNode2ID: unknownPseudoNode2(ServerContainerID), + // unknownPseudoNode1ID: unknownPseudoNode1(ServerContainerID), + // unknownPseudoNode2ID: unknownPseudoNode2(ServerContainerID), render.IncomingInternetID: theIncomingInternetNode(ServerContainerID), render.OutgoingInternetID: theOutgoingInternetNode, }).Prune() @@ -448,14 +362,11 @@ var ( LabelMajor: fixture.ClientContainerImageName, LabelMinor: "1 container", Rank: fixture.ClientContainerImageName, - Pseudo: false, Shape: hexagon, Stack: true, Children: render.MakeRenderableNodeSet( - RemappedEndpoints[Client54001EndpointID], - RemappedEndpoints[Client54002EndpointID], - RenderedEndpoints[Client54001PseudoEndpointID], - RenderedEndpoints[Client54002PseudoEndpointID], + RenderedEndpoints[Client54001EndpointID], + RenderedEndpoints[Client54002EndpointID], RenderedProcesses[ClientProcess1ID], RenderedProcesses[ClientProcess2ID], RenderedContainers[ClientContainerID], @@ -471,12 +382,10 @@ var ( LabelMajor: fixture.ServerContainerImageName, LabelMinor: "1 container", Rank: fixture.ServerContainerImageName, - Pseudo: false, Shape: hexagon, Stack: true, Children: render.MakeRenderableNodeSet( - RemappedEndpoints[ServerEndpointID], - RenderedEndpoints[ServerPseudoEndpointID], + RenderedEndpoints[ServerEndpointID], RenderedProcesses[ServerProcessID], RenderedContainers[ServerContainerID], ), @@ -490,19 +399,18 @@ var ( ID: uncontainedServerID, LabelMajor: render.UncontainedMajor, LabelMinor: fixture.ServerHostName, - Pseudo: true, Shape: square, Stack: true, + Pseudo: true, Children: render.MakeRenderableNodeSet( - RemappedEndpoints[NonContainerEndpointID], - RenderedEndpoints[NonContainerPseudoEndpointID], + RenderedEndpoints[NonContainerEndpointID], RenderedProcesses[nonContainerProcessID], ), Node: report.MakeNode().WithAdjacent(render.OutgoingInternetID), EdgeMetadata: report.EdgeMetadata{}, }, - unknownPseudoNode1ID: unknownPseudoNode1(ServerContainerImageID), - unknownPseudoNode2ID: unknownPseudoNode2(ServerContainerImageID), + // unknownPseudoNode1ID: unknownPseudoNode1(ServerContainerImageID), + // unknownPseudoNode2ID: unknownPseudoNode2(ServerContainerImageID), render.IncomingInternetID: theIncomingInternetNode(ServerContainerImageID), render.OutgoingInternetID: theOutgoingInternetNode, }).Prune() @@ -563,22 +471,14 @@ var ( LabelMajor: "client", // before first . LabelMinor: "hostname.com", // after first . Rank: "hostname.com", - Pseudo: false, Shape: circle, Children: render.MakeRenderableNodeSet( - RemappedEndpoints[Client54001EndpointID], - RemappedEndpoints[Client54002EndpointID], - - RenderedEndpoints[Client54001PseudoEndpointID], - RenderedEndpoints[Client54002PseudoEndpointID], - + RenderedEndpoints[Client54001EndpointID], + RenderedEndpoints[Client54002EndpointID], RenderedProcesses[ClientProcess1ID], RenderedProcesses[ClientProcess2ID], - RenderedContainers[ClientContainerID], - RenderedContainerImages[ClientContainerImageID], - RenderedAddresses[ClientAddressID], ), Node: report.MakeNode().WithAdjacent(ServerHostID), @@ -592,11 +492,9 @@ var ( LabelMajor: "server", // before first . LabelMinor: "hostname.com", // after first . Rank: "hostname.com", - Pseudo: false, Shape: circle, Children: render.MakeRenderableNodeSet( - RemappedEndpoints[ServerEndpointID], - RenderedEndpoints[ServerPseudoEndpointID], + RenderedEndpoints[ServerEndpointID], RenderedProcesses[ServerProcessID], RenderedContainers[ServerContainerID], @@ -604,9 +502,10 @@ var ( RenderedAddresses[ServerAddressID], - // RemappedEndpoints[NonContainerEndpointID], - // RenderedEndpoints[NonContainerPseudoEndpointID], - // RenderedProcesses[nonContainerProcessID], + // See #1102 + // RemappedEndpoints[NonContainerEndpointID], + // RenderedEndpoints[NonContainerPseudoEndpointID], + // RenderedProcesses[nonContainerProcessID], ), Node: report.MakeNode(), EdgeMetadata: report.EdgeMetadata{ @@ -623,9 +522,8 @@ var ( Node: report.MakeNode().WithAdjacent(ServerHostID), EdgeMetadata: report.EdgeMetadata{}, Children: render.MakeRenderableNodeSet( - //TODO - //RenderedEndpoints[unknownPseudoNode2ID], - //RenderedAddresses[unknownPseudoAddress1ID], + // RenderedEndpoints[unknownPseudoNode2ID], + // RenderedAddresses[unknownPseudoAddress1ID], ), }, pseudoHostID2: { @@ -636,8 +534,8 @@ var ( Node: report.MakeNode().WithAdjacent(ServerHostID), EdgeMetadata: report.EdgeMetadata{}, Children: render.MakeRenderableNodeSet( - //RenderedEndpoints[unknownPseudoNode2ID], - //RenderedAddresses[unknownPseudoAddress2ID], + // RenderedEndpoints[unknownPseudoNode2ID], + // RenderedAddresses[unknownPseudoAddress2ID], ), }, render.IncomingInternetID: { @@ -649,7 +547,7 @@ var ( Node: report.MakeNode().WithAdjacent(ServerHostID), EdgeMetadata: report.EdgeMetadata{}, Children: render.MakeRenderableNodeSet( - //RenderedEndpoints[render.TheInternetID], + // RenderedEndpoints[render.TheInternetID], ), }, }).Prune() @@ -663,13 +561,10 @@ var ( LabelMajor: "pong-a", LabelMinor: "1 container", Rank: "ping/pong-a", - Pseudo: false, Shape: heptagon, Children: render.MakeRenderableNodeSet( - RemappedEndpoints[Client54001EndpointID], - RemappedEndpoints[Client54002EndpointID], - RenderedEndpoints[Client54001PseudoEndpointID], - RenderedEndpoints[Client54002PseudoEndpointID], + RenderedEndpoints[Client54001EndpointID], + RenderedEndpoints[Client54002EndpointID], RenderedProcesses[ClientProcess1ID], RenderedProcesses[ClientProcess2ID], RenderedContainers[ClientContainerID], @@ -685,11 +580,9 @@ var ( LabelMajor: "pong-b", LabelMinor: "1 container", Rank: "ping/pong-b", - Pseudo: false, Shape: heptagon, Children: render.MakeRenderableNodeSet( - RemappedEndpoints[ServerEndpointID], - RenderedEndpoints[ServerPseudoEndpointID], + RenderedEndpoints[ServerEndpointID], RenderedProcesses[ServerProcessID], RenderedContainers[ServerContainerID], ), @@ -707,15 +600,14 @@ var ( Shape: square, Stack: true, Children: render.MakeRenderableNodeSet( - RemappedEndpoints[NonContainerEndpointID], - RenderedEndpoints[NonContainerPseudoEndpointID], + RenderedEndpoints[NonContainerEndpointID], RenderedProcesses[nonContainerProcessID], ), Node: report.MakeNode().WithAdjacent(render.OutgoingInternetID), EdgeMetadata: report.EdgeMetadata{}, }, - unknownPseudoNode1ID: unknownPseudoNode1(ServerPodRenderedID), - unknownPseudoNode2ID: unknownPseudoNode2(ServerPodRenderedID), + // unknownPseudoNode1ID: unknownPseudoNode1(ServerPodRenderedID), + // unknownPseudoNode2ID: unknownPseudoNode2(ServerPodRenderedID), render.IncomingInternetID: theIncomingInternetNode(ServerPodRenderedID), render.OutgoingInternetID: theOutgoingInternetNode, }).Prune() @@ -728,16 +620,12 @@ var ( LabelMajor: "pongservice", LabelMinor: "2 pods", Rank: fixture.ServiceID, - Pseudo: false, Shape: heptagon, Stack: true, Children: render.MakeRenderableNodeSet( - RemappedEndpoints[Client54001EndpointID], - RemappedEndpoints[Client54002EndpointID], - RemappedEndpoints[ServerEndpointID], - RenderedEndpoints[Client54001PseudoEndpointID], - RenderedEndpoints[Client54002PseudoEndpointID], - RenderedEndpoints[ServerPseudoEndpointID], + RenderedEndpoints[Client54001EndpointID], + RenderedEndpoints[Client54002EndpointID], + RenderedEndpoints[ServerEndpointID], RenderedProcesses[ClientProcess1ID], RenderedProcesses[ClientProcess2ID], RenderedProcesses[ServerProcessID], @@ -762,15 +650,14 @@ var ( Shape: square, Stack: true, Children: render.MakeRenderableNodeSet( - RemappedEndpoints[NonContainerEndpointID], - RenderedEndpoints[NonContainerPseudoEndpointID], + RenderedEndpoints[NonContainerEndpointID], RenderedProcesses[nonContainerProcessID], ), Node: report.MakeNode().WithAdjacent(render.OutgoingInternetID), EdgeMetadata: report.EdgeMetadata{}, }, - unknownPseudoNode1ID: unknownPseudoNode1(ServiceRenderedID), - unknownPseudoNode2ID: unknownPseudoNode2(ServiceRenderedID), + // unknownPseudoNode1ID: unknownPseudoNode1(ServiceRenderedID), + // unknownPseudoNode2ID: unknownPseudoNode2(ServiceRenderedID), render.IncomingInternetID: theIncomingInternetNode(ServiceRenderedID), render.OutgoingInternetID: theOutgoingInternetNode, }).Prune() diff --git a/render/id.go b/render/id.go index 5acc38b903..03a0f693c8 100644 --- a/render/id.go +++ b/render/id.go @@ -24,11 +24,6 @@ func MakeEndpointID(hostID, addr, port string) string { return makeID("endpoint", hostID, addr, port) } -// MakePseudoEndpointID makes a pseudo endpoint node ID for rendered nodes. -func MakePseudoEndpointID(hostID, addr, port string) string { - return makeID("pseudo-endpoint", hostID, addr, port) -} - // MakeProcessID makes a process node ID for rendered nodes. func MakeProcessID(hostID, pid string) string { return makeID("process", hostID, pid) diff --git a/render/mapping.go b/render/mapping.go index 39133ab026..0ebcaf6fb1 100644 --- a/render/mapping.go +++ b/render/mapping.go @@ -60,29 +60,11 @@ func theInternetNode(m RenderableNode) RenderableNode { return node } -// RemapEndpointIDs remaps endpoints to have an id format consistent +// MapEndpointIdentity remaps endpoints to have an id format consistent // with render/id.go; no pseudo nodes are introduced in this step, so // that pseudo nodes introduces later are guaranteed to have endpoints // as children. This is needed to construct the connection details tables. -func RemapEndpointIDs(m RenderableNode, _ report.Networks) RenderableNodes { - addr, ok := m.Latest.Lookup(endpoint.Addr) - if !ok { - return RenderableNodes{} - } - - port, ok := m.Latest.Lookup(endpoint.Port) - if !ok { - return RenderableNodes{} - } - - id := MakeEndpointID(report.ExtractHostID(m.Node), addr, port) - return RenderableNodes{id: NewRenderableNodeWith(id, "", "", "", m)} -} - -// MapEndpointIdentity maps an endpoint topology node to a single endpoint -// renderable node. As it is only ever run on endpoint topology nodes, we -// expect that certain keys are present. -func MapEndpointIdentity(m RenderableNode, local report.Networks) RenderableNodes { +func MapEndpointIdentity(m RenderableNode, _ report.Networks) RenderableNodes { addr, ok := m.Latest.Lookup(endpoint.Addr) if !ok { return RenderableNodes{} @@ -99,50 +81,8 @@ func MapEndpointIdentity(m RenderableNode, local report.Networks) RenderableNode return RenderableNodes{} } - // Nodes without a hostid are treated as psuedo nodes - if _, ok = m.Latest.Lookup(report.HostNodeID); !ok { - var node RenderableNode - - if ip := net.ParseIP(addr); ip != nil && !local.Contains(ip) { - // If the dstNodeAddr is not in a network local to this report, we emit an - // internet node - node = theInternetNode(m) - - } else if p, err := strconv.Atoi(port); err == nil && len(m.Adjacency) > 0 && p >= 32768 && p < 65535 { - // We are a 'client' pseudo node if the port is in the ephemeral port range. - // Linux uses 32768 to 61000, IANA suggests 49152 to 65535. - // We only exist if there is something in our adjacency - // Generate a single pseudo node for every (client ip, server ip, server port) - _, serverIP, serverPort, _ := ParseEndpointID(m.Adjacency[0]) - node = newDerivedPseudoNode(MakePseudoNodeID(addr, serverIP, serverPort), addr, m) - - } else if port != "" { - // Otherwise (the server node is missing), generate a pseudo node for every (server ip, server port) - node = newDerivedPseudoNode(MakePseudoNodeID(addr, port), addr+":"+port, m) - - } else { - // Empty port for some reason... - node = newDerivedPseudoNode(MakePseudoNodeID(addr, port), addr, m) - } - - node.Children = node.Children.Add(m) - return RenderableNodes{node.ID: node} - } - - var ( - id = MakePseudoEndpointID(report.ExtractHostID(m.Node), addr, port) - major = fmt.Sprintf("%s:%s", addr, port) - minor = report.ExtractHostID(m.Node) - ) - - pid, pidOK := m.Latest.Lookup(process.PID) - if pidOK { - minor = fmt.Sprintf("%s (%s)", minor, pid) - } - - node := NewRenderableNodeWith(id, major, minor, "", m) - node.Children = node.Children.Add(m) - return RenderableNodes{id: node} + id := MakeEndpointID(report.ExtractHostID(m.Node), addr, port) + return RenderableNodes{id: NewRenderableNodeWith(id, "", "", "", m)} } // MapProcessIdentity maps a process topology node to a process renderable @@ -447,9 +387,45 @@ func MapIP2Container(n RenderableNode, _ report.Networks) RenderableNodes { // format for a process, but without any Major or Minor labels. // It does not have enough info to do that, and the resulting graph // must be merged with a process graph to get that info. -func MapEndpoint2Process(n RenderableNode, _ report.Networks) RenderableNodes { - if n.Pseudo { - return RenderableNodes{n.ID: n} +func MapEndpoint2Process(n RenderableNode, local report.Networks) RenderableNodes { + // Nodes without a hostid are treated as psuedo nodes + if _, ok := n.Latest.Lookup(report.HostNodeID); !ok { + var node RenderableNode + + addr, ok := n.Latest.Lookup(endpoint.Addr) + if !ok { + return RenderableNodes{} + } + + port, ok := n.Latest.Lookup(endpoint.Port) + if !ok { + return RenderableNodes{} + } + + if ip := net.ParseIP(addr); ip != nil && !local.Contains(ip) { + // If the dstNodeAddr is not in a network local to this report, we emit an + // internet node + node = theInternetNode(n) + + } else if p, err := strconv.Atoi(port); err == nil && len(n.Adjacency) > 0 && p >= 32768 && p < 65535 { + // We are a 'client' pseudo node if the port is in the ephemeral port range. + // Linux uses 32768 to 61000, IANA suggests 49152 to 65535. + // We only exist if there is something in our adjacency + // Generate a single pseudo node for every (client ip, server ip, server port) + _, serverIP, serverPort, _ := ParseEndpointID(n.Adjacency[0]) + node = newDerivedPseudoNode(MakePseudoNodeID(addr, serverIP, serverPort), addr, n) + + } else if port != "" { + // Otherwise (the server node is missing), generate a pseudo node for every (server ip, server port) + node = newDerivedPseudoNode(MakePseudoNodeID(addr, port), addr+":"+port, n) + + } else { + // Empty port for some reason... + node = newDerivedPseudoNode(MakePseudoNodeID(addr, port), addr, n) + } + + node.Children = node.Children.Add(n) + return RenderableNodes{node.ID: node} } pid, ok := n.Node.Latest.Lookup(process.PID) @@ -476,11 +452,16 @@ func MapEndpoint2Process(n RenderableNode, _ report.Networks) RenderableNodes { // It does not have enough info to do that, and the resulting graph // must be merged with a container graph to get that info. func MapProcess2Container(n RenderableNode, _ report.Networks) RenderableNodes { - // Propogate pseudo node - if n.Pseudo { + // Propogate the internet pseudo node + if strings.HasSuffix(n.ID, TheInternetID) { return RenderableNodes{n.ID: n} } + // Don't propogate non-internet pseudo nodes + if n.Pseudo { + return RenderableNodes{} + } + // Otherwise, if the process is not in a container, group it // into an per-host "Uncontained" node. If for whatever reason // this node doesn't have a host id in their nodemetadata, it'll diff --git a/render/topologies.go b/render/topologies.go index ee9c431d34..ffe2772f59 100644 --- a/render/topologies.go +++ b/render/topologies.go @@ -13,10 +13,7 @@ import ( // EndpointRenderer is a Renderer which produces a renderable endpoint graph. var EndpointRenderer = MakeMap( MapEndpointIdentity, - MakeMap( - RemapEndpointIDs, - SelectEndpoint, - ), + SelectEndpoint, ) // ProcessRenderer is a Renderer which produces a renderable process diff --git a/render/topologies_test.go b/render/topologies_test.go index 449c85d762..a41ec90407 100644 --- a/render/topologies_test.go +++ b/render/topologies_test.go @@ -13,17 +13,6 @@ import ( "github.com/weaveworks/scope/test/reflect" ) -func TestRemappedEndpoints(t *testing.T) { - have := render.MakeMap( - render.RemapEndpointIDs, - render.SelectEndpoint, - ).Render(fixture.Report).Prune() - want := expected.RemappedEndpoints - if !reflect.DeepEqual(want, have) { - t.Error(test.Diff(want, have)) - } -} - func TestEndpointRenderer(t *testing.T) { have := render.EndpointRenderer.Render(fixture.Report).Prune() want := expected.RenderedEndpoints