From 546f336fef5e3e60ee5d514710db83602556a724 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Wed, 17 Jun 2015 10:14:55 +0000 Subject: [PATCH] Add FilterUnconnected Renderer, wire up Process and ProcessName Renderers. --- app/router.go | 4 +- render/mapping.go | 63 ++++-- render/render.go | 22 +++ render/render_test.go | 393 ++------------------------------------ render/topologies.go | 7 + render/topologies_test.go | 390 +++++++++++++++++++++++++++++++++++++ 6 files changed, 482 insertions(+), 397 deletions(-) create mode 100644 render/topologies_test.go diff --git a/app/router.go b/app/router.go index e2db1f4ce..581d144b6 100644 --- a/app/router.go +++ b/app/router.go @@ -50,12 +50,12 @@ var topologyRegistry = map[string]topologyView{ "applications": { human: "Applications", parent: "", - renderer: render.LeafMap{Selector: report.SelectEndpoint, Mapper: render.ProcessPID, Pseudo: render.GenericPseudoNode}, + renderer: render.FilterUnconnected{Renderer: render.ProcessRenderer}, }, "applications-by-name": { human: "by name", parent: "applications", - renderer: render.LeafMap{Selector: report.SelectEndpoint, Mapper: render.ProcessName, Pseudo: render.GenericGroupedPseudoNode}, + renderer: render.FilterUnconnected{Renderer: render.ProcessNameRenderer}, }, "containers": { human: "Containers", diff --git a/render/mapping.go b/render/mapping.go index fad92c353..54742d4be 100644 --- a/render/mapping.go +++ b/render/mapping.go @@ -34,6 +34,9 @@ type PseudoFunc func(srcNodeID string, srcNode RenderableNode, dstNodeID string) // MapFunc is anything which can take an arbitrary RenderableNode and // return another RenderableNode. +// +// As with LeadMapFunc, if the final output parameter is false, the node +// shall be omitted from the rendered topology. type MapFunc func(RenderableNode) (RenderableNode, bool) // MapEndpointIdentity maps a endpoint topology node to endpoint RenderableNode node. @@ -41,9 +44,9 @@ type MapFunc func(RenderableNode) (RenderableNode, bool) // presences of certain keys. func MapEndpointIdentity(m report.NodeMetadata) (RenderableNode, bool) { var ( - id = fmt.Sprintf("endpoint:%s:%s:%s", m[report.HostNodeID], m["addr"], m["port"]) + id = fmt.Sprintf("endpoint:%s:%s:%s", getHostname(m), m["addr"], m["port"]) major = fmt.Sprintf("%s:%s", m["addr"], m["port"]) - minor = fmt.Sprintf("%s (%s)", m[report.HostNodeID], m["pid"]) + minor = fmt.Sprintf("%s (%s)", getHostname(m), m["pid"]) rank = m["pid"] ) return NewRenderableNode(id, major, minor, rank, m), true @@ -54,30 +57,31 @@ func MapEndpointIdentity(m report.NodeMetadata) (RenderableNode, bool) { // presences of certain keys. func MapProcessIdentity(m report.NodeMetadata) (RenderableNode, bool) { var ( - id = fmt.Sprintf("pid:%s:%s", m[report.HostNodeID], m["pid"]) + id = fmt.Sprintf("pid:%s:%s", getHostname(m), m["pid"]) major = m["comm"] - minor = fmt.Sprintf("%s (%s)", m[report.HostNodeID], m["pid"]) + minor = fmt.Sprintf("%s (%s)", getHostname(m), m["pid"]) rank = m["pid"] ) return NewRenderableNode(id, major, minor, rank, m), true } -// MapContainerIdentity maps a container topology node to container RenderableNode node. -// As it is only ever run on container topology nodes, we can safely assume the -// presences of certain keys. +// MapContainerIdentity maps a container topology node to container +// RenderableNode node. As it is only ever run on container topology +// nodes, we can safely assume the presences of certain keys. func MapContainerIdentity(m report.NodeMetadata) (RenderableNode, bool) { var ( id = m["docker_container_id"] major = m["docker_container_name"] - minor = m[report.HostNodeID] + minor = getHostname(m) rank = m["docker_image_id"] ) return NewRenderableNode(id, major, minor, rank, m), true } -// MapEndpoint2Process maps endpoint RenderableNodes to process RenderableNodes. +// MapEndpoint2Process maps endpoint RenderableNodes to process +// RenderableNodes. // // If this function is given a pseudo node, then it will just return it; // Pseudo nodes will never have pids in them, and therefore will never @@ -92,14 +96,22 @@ func MapEndpoint2Process(n RenderableNode) (RenderableNode, bool) { return n, true } - id := fmt.Sprintf("pid:%s:%s", n.NodeMetadata[report.HostNodeID], n.NodeMetadata["pid"]) + pid, ok := n.NodeMetadata["pid"] + if !ok { + // TODO: Propogate a pseudo node instead of dropping this? + return RenderableNode{}, false + } + + id := fmt.Sprintf("pid:%s:%s", getHostname(n.NodeMetadata), pid) return newDerivedNode(id, n), true } -// MapProcess2Container maps process RenderableNodes to container RenderableNodes. +// MapProcess2Container maps process RenderableNodes to container +// RenderableNodes. // -// If this function is given a node without a docker_container_id (including other -// psueod nodes), it will produce an "Uncontained" pseudo node. +// If this function is given a node without a docker_container_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. @@ -114,6 +126,31 @@ func MapProcess2Container(n RenderableNode) (RenderableNode, bool) { return newDerivedNode(id, n), true } +// MapProcess2Name maps process RenderableNodes to RenderableNodes +// for each process name. +// +// This mapper is unlike the other foo2bar mappers as the intention +// is not to join the information with another topology. Therefore +// it outputs a properly-formed node with labels etc. +func MapProcess2Name(n RenderableNode) (RenderableNode, bool) { + if n.Pseudo { + return n, true + } + + name, ok := n.NodeMetadata["comm"] + if !ok { + // TODO: Propogate a pseudo node instead of dropping this? + return RenderableNode{}, false + } + + return NewRenderableNode(name, name, "", name, n.NodeMetadata), true +} + +func getHostname(m report.NodeMetadata) string { + hostname, _, _ := report.ParseNodeID(m[report.HostNodeID]) + return hostname +} + // ProcessPID takes a node NodeMetadata from topology, and returns a // representation with the ID based on the process PID and the labels based on // the process name. diff --git a/render/render.go b/render/render.go index 2b4096390..d6b4b01ba 100644 --- a/render/render.go +++ b/render/render.go @@ -31,6 +31,11 @@ type LeafMap struct { Pseudo PseudoFunc } +// FilterUnconnected is a Renderer which filters out unconnected nodes. +type FilterUnconnected struct { + Renderer +} + // MakeReduce is the only sane way to produce a Reduce Renderer func MakeReduce(renderers ...Renderer) Renderer { return Reduce(renderers) @@ -238,3 +243,20 @@ func (m LeafMap) AggregateMetadata(rpt report.Report, srcRenderableID, dstRender } return metadata.Transform() } + +// Render produces a set of RenderableNodes given a Report +func (f FilterUnconnected) Render(rpt report.Report) RenderableNodes { + input := f.Renderer.Render(rpt) + output := RenderableNodes{} + for id, node := range input { + if len(node.Adjacency) == 0 { + continue + } + + output[id] = node + for _, id := range node.Adjacency { + output[id] = input[id] + } + } + return output +} diff --git a/render/render_test.go b/render/render_test.go index c1bcf2e73..99752b61a 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -6,15 +6,8 @@ import ( "github.com/weaveworks/scope/render" "github.com/weaveworks/scope/report" - - "github.com/davecgh/go-spew/spew" - "github.com/pmezard/go-difflib/difflib" ) -func init() { - spew.Config.SortKeys = true // :\ -} - type mockRenderer struct { render.RenderableNodes aggregateMetadata report.AggregateMetadata @@ -158,384 +151,20 @@ func TestMapEdge(t *testing.T) { } } -var ( - clientHostID = "client.hostname.com" - serverHostID = "server.hostname.com" - randomHostID = "random.hostname.com" - unknownHostID = "" - - clientHostName = clientHostID - serverHostName = serverHostID - - clientHostNodeID = report.MakeHostNodeID(clientHostID) - serverHostNodeID = report.MakeHostNodeID(serverHostID) - randomHostNodeID = report.MakeHostNodeID(randomHostID) - - client54001NodeID = report.MakeEndpointNodeID(clientHostID, "10.10.10.20", "54001") // curl (1) - client54002NodeID = report.MakeEndpointNodeID(clientHostID, "10.10.10.20", "54002") // curl (2) - unknownClient1 = report.MakeEndpointNodeID(serverHostID, "10.10.10.10", "54010") // we want to ensure two unknown clients, connnected - unknownClient2 = report.MakeEndpointNodeID(serverHostID, "10.10.10.10", "54020") // to the same server, are deduped. - unknownClient3 = report.MakeEndpointNodeID(serverHostID, "10.10.10.11", "54020") // Check this one isn't deduped - server80 = report.MakeEndpointNodeID(serverHostID, "192.168.1.1", "80") // apache - - clientAddressNodeID = report.MakeAddressNodeID(clientHostID, "10.10.10.20") - serverAddressNodeID = report.MakeAddressNodeID(serverHostID, "192.168.1.1") - randomAddressNodeID = report.MakeAddressNodeID(randomHostID, "172.16.11.9") // only in Address topology - unknownAddressNodeID = report.MakeAddressNodeID(unknownHostID, "10.10.10.10") -) - -var ( - rpt = report.Report{ - Endpoint: report.Topology{ - Adjacency: report.Adjacency{ - report.MakeAdjacencyID(client54001NodeID): report.MakeIDList(server80), - report.MakeAdjacencyID(client54002NodeID): report.MakeIDList(server80), - report.MakeAdjacencyID(server80): report.MakeIDList(client54001NodeID, client54002NodeID, unknownClient1, unknownClient2, unknownClient3), - }, - NodeMetadatas: report.NodeMetadatas{ - // NodeMetadata is arbitrary. We're free to put only precisely what we - // care to test into the fixture. Just be sure to include the bits - // that the mapping funcs extract :) - client54001NodeID: report.NodeMetadata{ - "name": "curl", - "domain": "client-54001-domain", - "pid": "10001", - report.HostNodeID: clientHostNodeID, - "host_name": clientHostName, - }, - client54002NodeID: report.NodeMetadata{ - "name": "curl", // should be same as above! - "domain": "client-54002-domain", // may be different than above - "pid": "10001", // should be same as above! - report.HostNodeID: clientHostNodeID, - }, - server80: report.NodeMetadata{ - "name": "apache", - "domain": "server-80-domain", - "pid": "215", - report.HostNodeID: serverHostNodeID, - }, - }, - EdgeMetadatas: report.EdgeMetadatas{ - report.MakeEdgeID(client54001NodeID, server80): report.EdgeMetadata{ - WithBytes: true, - BytesIngress: 100, - BytesEgress: 10, - }, - report.MakeEdgeID(client54002NodeID, server80): report.EdgeMetadata{ - WithBytes: true, - BytesIngress: 200, - BytesEgress: 20, - }, - - report.MakeEdgeID(server80, client54001NodeID): report.EdgeMetadata{ - WithBytes: true, - BytesIngress: 10, - BytesEgress: 100, - }, - report.MakeEdgeID(server80, client54002NodeID): report.EdgeMetadata{ - WithBytes: true, - BytesIngress: 20, - BytesEgress: 200, - }, - report.MakeEdgeID(server80, unknownClient1): report.EdgeMetadata{ - WithBytes: true, - BytesIngress: 30, - BytesEgress: 300, - }, - report.MakeEdgeID(server80, unknownClient2): report.EdgeMetadata{ - WithBytes: true, - BytesIngress: 40, - BytesEgress: 400, - }, - report.MakeEdgeID(server80, unknownClient3): report.EdgeMetadata{ - WithBytes: true, - BytesIngress: 50, - BytesEgress: 500, - }, - }, - }, - Process: report.Topology{ - Adjacency: report.Adjacency{}, - NodeMetadatas: report.NodeMetadatas{ - report.MakeProcessNodeID(clientHostID, "4242"): report.NodeMetadata{ - "host_name": "client.host.com", - "pid": "4242", - "comm": "curl", - "docker_container_id": "a1b2c3d4e5", - "docker_container_name": "fixture-container", - "docker_image_id": "0000000000", - "docker_image_name": "fixture/container:latest", - }, - report.MakeProcessNodeID(serverHostID, "215"): report.NodeMetadata{ - "pid": "215", - "process_name": "apache", - }, - - "no-container": report.NodeMetadata{}, - }, - EdgeMetadatas: report.EdgeMetadatas{}, - }, - Address: report.Topology{ - Adjacency: report.Adjacency{ - report.MakeAdjacencyID(clientAddressNodeID): report.MakeIDList(serverAddressNodeID), - report.MakeAdjacencyID(randomAddressNodeID): report.MakeIDList(serverAddressNodeID), - report.MakeAdjacencyID(serverAddressNodeID): report.MakeIDList(clientAddressNodeID, unknownAddressNodeID), // no backlink to random - }, - NodeMetadatas: report.NodeMetadatas{ - clientAddressNodeID: report.NodeMetadata{ - "name": "client.hostname.com", // hostname - "host_name": "client.hostname.com", - report.HostNodeID: clientHostNodeID, - }, - randomAddressNodeID: report.NodeMetadata{ - "name": "random.hostname.com", // hostname - report.HostNodeID: randomHostNodeID, - }, - serverAddressNodeID: report.NodeMetadata{ - "name": "server.hostname.com", // hostname - report.HostNodeID: serverHostNodeID, - }, - }, - EdgeMetadatas: report.EdgeMetadatas{ - report.MakeEdgeID(clientAddressNodeID, serverAddressNodeID): report.EdgeMetadata{ - WithConnCountTCP: true, - MaxConnCountTCP: 3, - }, - report.MakeEdgeID(randomAddressNodeID, serverAddressNodeID): report.EdgeMetadata{ - WithConnCountTCP: true, - MaxConnCountTCP: 20, // dangling connections, weird but possible - }, - report.MakeEdgeID(serverAddressNodeID, clientAddressNodeID): report.EdgeMetadata{ - WithConnCountTCP: true, - MaxConnCountTCP: 3, - }, - report.MakeEdgeID(serverAddressNodeID, unknownAddressNodeID): report.EdgeMetadata{ - WithConnCountTCP: true, - MaxConnCountTCP: 7, - }, - }, - }, - Host: report.Topology{ - Adjacency: report.Adjacency{}, - NodeMetadatas: report.NodeMetadatas{ - serverHostNodeID: report.NodeMetadata{ - "host_name": serverHostName, - "local_networks": "10.10.10.0/24", - "os": "Linux", - "load": "0.01 0.01 0.01", - }, - }, - EdgeMetadatas: report.EdgeMetadatas{}, - }, +func TestFilterRender(t *testing.T) { + renderer := render.FilterUnconnected{ + Renderer: mockRenderer{RenderableNodes: render.RenderableNodes{ + "foo": {ID: "foo", Adjacency: report.MakeIDList("bar")}, + "bar": {ID: "bar", Adjacency: report.MakeIDList("foo")}, + "baz": {ID: "baz", Adjacency: report.MakeIDList()}, + }}, } -) - -func trimNodeMetadata(rns render.RenderableNodes) render.RenderableNodes { - result := render.RenderableNodes{} - for id, rn := range rns { - rn.NodeMetadata = nil - result[id] = rn - } - return result -} - -func TestRenderByEndpointPID(t *testing.T) { want := render.RenderableNodes{ - "pid:client-54001-domain:10001": { - ID: "pid:client-54001-domain:10001", - LabelMajor: "curl", - LabelMinor: "client-54001-domain (10001)", - Rank: "10001", - Pseudo: false, - Adjacency: report.MakeIDList("pid:server-80-domain:215"), - Origins: report.MakeIDList(report.MakeHostNodeID("client.hostname.com"), report.MakeEndpointNodeID("client.hostname.com", "10.10.10.20", "54001")), - Metadata: report.AggregateMetadata{ - report.KeyBytesIngress: 100, - report.KeyBytesEgress: 10, - }, - }, - "pid:client-54002-domain:10001": { - ID: "pid:client-54002-domain:10001", - LabelMajor: "curl", - LabelMinor: "client-54002-domain (10001)", - Rank: "10001", // same process - Pseudo: false, - Adjacency: report.MakeIDList("pid:server-80-domain:215"), - Origins: report.MakeIDList(report.MakeHostNodeID("client.hostname.com"), report.MakeEndpointNodeID("client.hostname.com", "10.10.10.20", "54002")), - Metadata: report.AggregateMetadata{ - report.KeyBytesIngress: 200, - report.KeyBytesEgress: 20, - }, - }, - "pid:server-80-domain:215": { - ID: "pid:server-80-domain:215", - LabelMajor: "apache", - LabelMinor: "server-80-domain (215)", - Rank: "215", - Pseudo: false, - Adjacency: report.MakeIDList( - "pid:client-54001-domain:10001", - "pid:client-54002-domain:10001", - "pseudo;10.10.10.10;192.168.1.1;80", - "pseudo;10.10.10.11;192.168.1.1;80", - ), - Origins: report.MakeIDList(report.MakeHostNodeID("server.hostname.com"), report.MakeEndpointNodeID("server.hostname.com", "192.168.1.1", "80")), - Metadata: report.AggregateMetadata{ - report.KeyBytesIngress: 150, - report.KeyBytesEgress: 1500, - }, - }, - "pseudo;10.10.10.10;192.168.1.1;80": { - ID: "pseudo;10.10.10.10;192.168.1.1;80", - LabelMajor: "10.10.10.10", - Pseudo: true, - Metadata: report.AggregateMetadata{}, - }, - "pseudo;10.10.10.11;192.168.1.1;80": { - ID: "pseudo;10.10.10.11;192.168.1.1;80", - LabelMajor: "10.10.10.11", - Pseudo: true, - Metadata: report.AggregateMetadata{}, - }, + "foo": {ID: "foo", Adjacency: report.MakeIDList("bar")}, + "bar": {ID: "bar", Adjacency: report.MakeIDList("foo")}, } - have := render.LeafMap{ - Selector: report.SelectEndpoint, - Mapper: render.ProcessPID, - Pseudo: render.GenericPseudoNode, - }.Render(rpt) - have = trimNodeMetadata(have) + have := renderer.Render(report.MakeReport()) if !reflect.DeepEqual(want, have) { - t.Error("\n" + diff(want, have)) + t.Errorf("want %+v, have %+v", want, have) } } - -func TestRenderByEndpointPIDGrouped(t *testing.T) { - // For grouped, I've somewhat arbitrarily chosen to squash together all - // processes with the same name by removing the PID and domain (host) - // dimensions from the ID. That could be changed. - want := render.RenderableNodes{ - "curl": { - ID: "curl", - LabelMajor: "curl", - LabelMinor: "", - Rank: "curl", - Pseudo: false, - Adjacency: report.MakeIDList("apache"), - Origins: report.MakeIDList(report.MakeHostNodeID("client.hostname.com"), report.MakeEndpointNodeID("client.hostname.com", "10.10.10.20", "54001"), report.MakeEndpointNodeID("client.hostname.com", "10.10.10.20", "54002")), - Metadata: report.AggregateMetadata{ - report.KeyBytesIngress: 300, - report.KeyBytesEgress: 30, - }, - }, - "apache": { - ID: "apache", - LabelMajor: "apache", - LabelMinor: "", - Rank: "apache", - Pseudo: false, - Adjacency: report.MakeIDList( - "curl", - "pseudo;10.10.10.10;apache", - "pseudo;10.10.10.11;apache", - ), - Origins: report.MakeIDList(report.MakeHostNodeID("server.hostname.com"), report.MakeEndpointNodeID("server.hostname.com", "192.168.1.1", "80")), - Metadata: report.AggregateMetadata{ - report.KeyBytesIngress: 150, - report.KeyBytesEgress: 1500, - }, - }, - "pseudo;10.10.10.10;apache": { - ID: "pseudo;10.10.10.10;apache", - LabelMajor: "10.10.10.10", - Pseudo: true, - Metadata: report.AggregateMetadata{}, - }, - "pseudo;10.10.10.11;apache": { - ID: "pseudo;10.10.10.11;apache", - LabelMajor: "10.10.10.11", - Pseudo: true, - Metadata: report.AggregateMetadata{}, - }, - } - have := render.LeafMap{ - Selector: report.SelectEndpoint, - Mapper: render.ProcessName, - Pseudo: render.GenericGroupedPseudoNode, - }.Render(rpt) - have = trimNodeMetadata(have) - if !reflect.DeepEqual(want, have) { - t.Error("\n" + diff(want, have)) - } -} - -func TestRenderByNetworkHostname(t *testing.T) { - want := render.RenderableNodes{ - "host:client.hostname.com": { - ID: "host:client.hostname.com", - LabelMajor: "client", // before first . - LabelMinor: "hostname.com", // after first . - Rank: "client", - Pseudo: false, - Adjacency: report.MakeIDList("host:server.hostname.com"), - Origins: report.MakeIDList(report.MakeHostNodeID("client.hostname.com"), report.MakeAddressNodeID("client.hostname.com", "10.10.10.20")), - Metadata: report.AggregateMetadata{ - report.KeyMaxConnCountTCP: 3, - }, - }, - "host:random.hostname.com": { - ID: "host:random.hostname.com", - LabelMajor: "random", // before first . - LabelMinor: "hostname.com", // after first . - Rank: "random", - Pseudo: false, - Adjacency: report.MakeIDList("host:server.hostname.com"), - Origins: report.MakeIDList(report.MakeHostNodeID("random.hostname.com"), report.MakeAddressNodeID("random.hostname.com", "172.16.11.9")), - Metadata: report.AggregateMetadata{ - report.KeyMaxConnCountTCP: 20, - }, - }, - "host:server.hostname.com": { - ID: "host:server.hostname.com", - LabelMajor: "server", // before first . - LabelMinor: "hostname.com", // after first . - Rank: "server", - Pseudo: false, - Adjacency: report.MakeIDList("host:client.hostname.com", "pseudo;10.10.10.10;192.168.1.1;"), - Origins: report.MakeIDList(report.MakeHostNodeID("server.hostname.com"), report.MakeAddressNodeID("server.hostname.com", "192.168.1.1")), - Metadata: report.AggregateMetadata{ - report.KeyMaxConnCountTCP: 10, - }, - }, - "pseudo;10.10.10.10;192.168.1.1;": { - ID: "pseudo;10.10.10.10;192.168.1.1;", - LabelMajor: "10.10.10.10", - LabelMinor: "", // after first . - Rank: "", - Pseudo: true, - Adjacency: nil, - Origins: nil, - Metadata: report.AggregateMetadata{}, - }, - } - have := render.LeafMap{ - Selector: report.SelectAddress, - Mapper: render.NetworkHostname, - Pseudo: render.GenericPseudoNode, - }.Render(rpt) - have = trimNodeMetadata(have) - if !reflect.DeepEqual(want, have) { - t.Error("\n" + diff(want, have)) - } -} - -func diff(want, have interface{}) string { - text, _ := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{ - A: difflib.SplitLines(spew.Sdump(want)), - B: difflib.SplitLines(spew.Sdump(have)), - FromFile: "want", - ToFile: "have", - Context: 3, - }) - return "\n" + text -} diff --git a/render/topologies.go b/render/topologies.go index b93d6e9f1..d8490e7d7 100644 --- a/render/topologies.go +++ b/render/topologies.go @@ -25,6 +25,13 @@ var ProcessRenderer = MakeReduce( }, ) +// ProcessRenderer is a Renderer which produces a renderable process +// name graph by munging the progess graph. +var ProcessNameRenderer = Map{ + MapFunc: MapProcess2Name, + Renderer: ProcessRenderer, +} + // ContainerRenderer is a Renderer which produces a renderable container // graph by merging the process graph and the container topology. var ContainerRenderer = MakeReduce( diff --git a/render/topologies_test.go b/render/topologies_test.go new file mode 100644 index 000000000..10e498529 --- /dev/null +++ b/render/topologies_test.go @@ -0,0 +1,390 @@ +package render_test + +import ( + "reflect" + "testing" + + "github.com/davecgh/go-spew/spew" + "github.com/pmezard/go-difflib/difflib" + + "github.com/weaveworks/scope/render" + "github.com/weaveworks/scope/report" +) + +func init() { + spew.Config.SortKeys = true // :\ +} + +var ( + clientHostID = "client.hostname.com" + serverHostID = "server.hostname.com" + randomHostID = "random.hostname.com" + unknownHostID = "" + + clientHostName = clientHostID + serverHostName = serverHostID + + clientHostNodeID = report.MakeHostNodeID(clientHostID) + serverHostNodeID = report.MakeHostNodeID(serverHostID) + randomHostNodeID = report.MakeHostNodeID(randomHostID) + + client54001NodeID = report.MakeEndpointNodeID(clientHostID, "10.10.10.20", "54001") // curl (1) + client54002NodeID = report.MakeEndpointNodeID(clientHostID, "10.10.10.20", "54002") // curl (2) + unknownClient1 = report.MakeEndpointNodeID(serverHostID, "10.10.10.10", "54010") // we want to ensure two unknown clients, connnected + unknownClient2 = report.MakeEndpointNodeID(serverHostID, "10.10.10.10", "54020") // to the same server, are deduped. + unknownClient3 = report.MakeEndpointNodeID(serverHostID, "10.10.10.11", "54020") // Check this one isn't deduped + server80 = report.MakeEndpointNodeID(serverHostID, "192.168.1.1", "80") // apache + + clientAddressNodeID = report.MakeAddressNodeID(clientHostID, "10.10.10.20") + serverAddressNodeID = report.MakeAddressNodeID(serverHostID, "192.168.1.1") + randomAddressNodeID = report.MakeAddressNodeID(randomHostID, "172.16.11.9") // only in Address topology + unknownAddressNodeID = report.MakeAddressNodeID(unknownHostID, "10.10.10.10") +) + +var ( + rpt = report.Report{ + Endpoint: report.Topology{ + Adjacency: report.Adjacency{ + report.MakeAdjacencyID(client54001NodeID): report.MakeIDList(server80), + report.MakeAdjacencyID(client54002NodeID): report.MakeIDList(server80), + report.MakeAdjacencyID(server80): report.MakeIDList(client54001NodeID, client54002NodeID, unknownClient1, unknownClient2, unknownClient3), + }, + NodeMetadatas: report.NodeMetadatas{ + // NodeMetadata is arbitrary. We're free to put only precisely what we + // care to test into the fixture. Just be sure to include the bits + // that the mapping funcs extract :) + client54001NodeID: report.NodeMetadata{ + "name": "curl", + "domain": "client-54001-domain", + "pid": "10001", + report.HostNodeID: clientHostNodeID, + "host_name": clientHostName, + }, + client54002NodeID: report.NodeMetadata{ + "name": "curl", // should be same as above! + "domain": "client-54002-domain", // may be different than above + "pid": "10001", // should be same as above! + report.HostNodeID: clientHostNodeID, + }, + server80: report.NodeMetadata{ + "name": "apache", + "domain": "server-80-domain", + "pid": "215", + report.HostNodeID: serverHostNodeID, + }, + }, + EdgeMetadatas: report.EdgeMetadatas{ + report.MakeEdgeID(client54001NodeID, server80): report.EdgeMetadata{ + WithBytes: true, + BytesIngress: 100, + BytesEgress: 10, + }, + report.MakeEdgeID(client54002NodeID, server80): report.EdgeMetadata{ + WithBytes: true, + BytesIngress: 200, + BytesEgress: 20, + }, + + report.MakeEdgeID(server80, client54001NodeID): report.EdgeMetadata{ + WithBytes: true, + BytesIngress: 10, + BytesEgress: 100, + }, + report.MakeEdgeID(server80, client54002NodeID): report.EdgeMetadata{ + WithBytes: true, + BytesIngress: 20, + BytesEgress: 200, + }, + report.MakeEdgeID(server80, unknownClient1): report.EdgeMetadata{ + WithBytes: true, + BytesIngress: 30, + BytesEgress: 300, + }, + report.MakeEdgeID(server80, unknownClient2): report.EdgeMetadata{ + WithBytes: true, + BytesIngress: 40, + BytesEgress: 400, + }, + report.MakeEdgeID(server80, unknownClient3): report.EdgeMetadata{ + WithBytes: true, + BytesIngress: 50, + BytesEgress: 500, + }, + }, + }, + Process: report.Topology{ + Adjacency: report.Adjacency{}, + NodeMetadatas: report.NodeMetadatas{ + report.MakeProcessNodeID(clientHostID, "4242"): report.NodeMetadata{ + "host_name": "client.host.com", + "pid": "4242", + "comm": "curl", + "docker_container_id": "a1b2c3d4e5", + "docker_container_name": "fixture-container", + "docker_image_id": "0000000000", + "docker_image_name": "fixture/container:latest", + }, + report.MakeProcessNodeID(serverHostID, "215"): report.NodeMetadata{ + "pid": "215", + "process_name": "apache", + }, + + "no-container": report.NodeMetadata{}, + }, + EdgeMetadatas: report.EdgeMetadatas{}, + }, + Address: report.Topology{ + Adjacency: report.Adjacency{ + report.MakeAdjacencyID(clientAddressNodeID): report.MakeIDList(serverAddressNodeID), + report.MakeAdjacencyID(randomAddressNodeID): report.MakeIDList(serverAddressNodeID), + report.MakeAdjacencyID(serverAddressNodeID): report.MakeIDList(clientAddressNodeID, unknownAddressNodeID), // no backlink to random + }, + NodeMetadatas: report.NodeMetadatas{ + clientAddressNodeID: report.NodeMetadata{ + "name": "client.hostname.com", // hostname + "host_name": "client.hostname.com", + report.HostNodeID: clientHostNodeID, + }, + randomAddressNodeID: report.NodeMetadata{ + "name": "random.hostname.com", // hostname + report.HostNodeID: randomHostNodeID, + }, + serverAddressNodeID: report.NodeMetadata{ + "name": "server.hostname.com", // hostname + report.HostNodeID: serverHostNodeID, + }, + }, + EdgeMetadatas: report.EdgeMetadatas{ + report.MakeEdgeID(clientAddressNodeID, serverAddressNodeID): report.EdgeMetadata{ + WithConnCountTCP: true, + MaxConnCountTCP: 3, + }, + report.MakeEdgeID(randomAddressNodeID, serverAddressNodeID): report.EdgeMetadata{ + WithConnCountTCP: true, + MaxConnCountTCP: 20, // dangling connections, weird but possible + }, + report.MakeEdgeID(serverAddressNodeID, clientAddressNodeID): report.EdgeMetadata{ + WithConnCountTCP: true, + MaxConnCountTCP: 3, + }, + report.MakeEdgeID(serverAddressNodeID, unknownAddressNodeID): report.EdgeMetadata{ + WithConnCountTCP: true, + MaxConnCountTCP: 7, + }, + }, + }, + Host: report.Topology{ + Adjacency: report.Adjacency{}, + NodeMetadatas: report.NodeMetadatas{ + serverHostNodeID: report.NodeMetadata{ + "host_name": serverHostName, + "local_networks": "10.10.10.0/24", + "os": "Linux", + "load": "0.01 0.01 0.01", + }, + }, + EdgeMetadatas: report.EdgeMetadatas{}, + }, + } +) + +func trimNodeMetadata(rns render.RenderableNodes) render.RenderableNodes { + result := render.RenderableNodes{} + for id, rn := range rns { + rn.NodeMetadata = nil + result[id] = rn + } + return result +} + +func TestProcessRenderer(t *testing.T) { + want := render.RenderableNodes{ + "pid:client-54001-domain:10001": { + ID: "pid:client-54001-domain:10001", + LabelMajor: "curl", + LabelMinor: "client-54001-domain (10001)", + Rank: "10001", + Pseudo: false, + Adjacency: report.MakeIDList("pid:server-80-domain:215"), + Origins: report.MakeIDList(report.MakeHostNodeID("client.hostname.com"), report.MakeEndpointNodeID("client.hostname.com", "10.10.10.20", "54001")), + Metadata: report.AggregateMetadata{ + report.KeyBytesIngress: 100, + report.KeyBytesEgress: 10, + }, + }, + "pid:client-54002-domain:10001": { + ID: "pid:client-54002-domain:10001", + LabelMajor: "curl", + LabelMinor: "client-54002-domain (10001)", + Rank: "10001", // same process + Pseudo: false, + Adjacency: report.MakeIDList("pid:server-80-domain:215"), + Origins: report.MakeIDList(report.MakeHostNodeID("client.hostname.com"), report.MakeEndpointNodeID("client.hostname.com", "10.10.10.20", "54002")), + Metadata: report.AggregateMetadata{ + report.KeyBytesIngress: 200, + report.KeyBytesEgress: 20, + }, + }, + "pid:server-80-domain:215": { + ID: "pid:server-80-domain:215", + LabelMajor: "apache", + LabelMinor: "server-80-domain (215)", + Rank: "215", + Pseudo: false, + Adjacency: report.MakeIDList( + "pid:client-54001-domain:10001", + "pid:client-54002-domain:10001", + "pseudo;10.10.10.10;192.168.1.1;80", + "pseudo;10.10.10.11;192.168.1.1;80", + ), + Origins: report.MakeIDList(report.MakeHostNodeID("server.hostname.com"), report.MakeEndpointNodeID("server.hostname.com", "192.168.1.1", "80")), + Metadata: report.AggregateMetadata{ + report.KeyBytesIngress: 150, + report.KeyBytesEgress: 1500, + }, + }, + "pseudo;10.10.10.10;192.168.1.1;80": { + ID: "pseudo;10.10.10.10;192.168.1.1;80", + LabelMajor: "10.10.10.10", + Pseudo: true, + Metadata: report.AggregateMetadata{}, + }, + "pseudo;10.10.10.11;192.168.1.1;80": { + ID: "pseudo;10.10.10.11;192.168.1.1;80", + LabelMajor: "10.10.10.11", + Pseudo: true, + Metadata: report.AggregateMetadata{}, + }, + } + have := render.ProcessRenderer.Render(rpt) + have = trimNodeMetadata(have) + if !reflect.DeepEqual(want, have) { + t.Error("\n" + diff(want, have)) + } +} + +func TestProcessNameRenderer(t *testing.T) { + // For grouped, I've somewhat arbitrarily chosen to squash together all + // processes with the same name by removing the PID and domain (host) + // dimensions from the ID. That could be changed. + want := render.RenderableNodes{ + "curl": { + ID: "curl", + LabelMajor: "curl", + LabelMinor: "", + Rank: "curl", + Pseudo: false, + Adjacency: report.MakeIDList("apache"), + Origins: report.MakeIDList(report.MakeHostNodeID("client.hostname.com"), report.MakeEndpointNodeID("client.hostname.com", "10.10.10.20", "54001"), report.MakeEndpointNodeID("client.hostname.com", "10.10.10.20", "54002")), + Metadata: report.AggregateMetadata{ + report.KeyBytesIngress: 300, + report.KeyBytesEgress: 30, + }, + }, + "apache": { + ID: "apache", + LabelMajor: "apache", + LabelMinor: "", + Rank: "apache", + Pseudo: false, + Adjacency: report.MakeIDList( + "curl", + "pseudo;10.10.10.10;apache", + "pseudo;10.10.10.11;apache", + ), + Origins: report.MakeIDList(report.MakeHostNodeID("server.hostname.com"), report.MakeEndpointNodeID("server.hostname.com", "192.168.1.1", "80")), + Metadata: report.AggregateMetadata{ + report.KeyBytesIngress: 150, + report.KeyBytesEgress: 1500, + }, + }, + "pseudo;10.10.10.10;apache": { + ID: "pseudo;10.10.10.10;apache", + LabelMajor: "10.10.10.10", + Pseudo: true, + Metadata: report.AggregateMetadata{}, + }, + "pseudo;10.10.10.11;apache": { + ID: "pseudo;10.10.10.11;apache", + LabelMajor: "10.10.10.11", + Pseudo: true, + Metadata: report.AggregateMetadata{}, + }, + } + have := render.ProcessNameRenderer.Render(rpt) + have = trimNodeMetadata(have) + if !reflect.DeepEqual(want, have) { + t.Error("\n" + diff(want, have)) + } +} + +func TestRenderByNetworkHostname(t *testing.T) { + want := render.RenderableNodes{ + "host:client.hostname.com": { + ID: "host:client.hostname.com", + LabelMajor: "client", // before first . + LabelMinor: "hostname.com", // after first . + Rank: "client", + Pseudo: false, + Adjacency: report.MakeIDList("host:server.hostname.com"), + Origins: report.MakeIDList(report.MakeHostNodeID("client.hostname.com"), report.MakeAddressNodeID("client.hostname.com", "10.10.10.20")), + Metadata: report.AggregateMetadata{ + report.KeyMaxConnCountTCP: 3, + }, + }, + "host:random.hostname.com": { + ID: "host:random.hostname.com", + LabelMajor: "random", // before first . + LabelMinor: "hostname.com", // after first . + Rank: "random", + Pseudo: false, + Adjacency: report.MakeIDList("host:server.hostname.com"), + Origins: report.MakeIDList(report.MakeHostNodeID("random.hostname.com"), report.MakeAddressNodeID("random.hostname.com", "172.16.11.9")), + Metadata: report.AggregateMetadata{ + report.KeyMaxConnCountTCP: 20, + }, + }, + "host:server.hostname.com": { + ID: "host:server.hostname.com", + LabelMajor: "server", // before first . + LabelMinor: "hostname.com", // after first . + Rank: "server", + Pseudo: false, + Adjacency: report.MakeIDList("host:client.hostname.com", "pseudo;10.10.10.10;192.168.1.1;"), + Origins: report.MakeIDList(report.MakeHostNodeID("server.hostname.com"), report.MakeAddressNodeID("server.hostname.com", "192.168.1.1")), + Metadata: report.AggregateMetadata{ + report.KeyMaxConnCountTCP: 10, + }, + }, + "pseudo;10.10.10.10;192.168.1.1;": { + ID: "pseudo;10.10.10.10;192.168.1.1;", + LabelMajor: "10.10.10.10", + LabelMinor: "", // after first . + Rank: "", + Pseudo: true, + Adjacency: nil, + Origins: nil, + Metadata: report.AggregateMetadata{}, + }, + } + have := render.LeafMap{ + Selector: report.SelectAddress, + Mapper: render.NetworkHostname, + Pseudo: render.GenericPseudoNode, + }.Render(rpt) + have = trimNodeMetadata(have) + if !reflect.DeepEqual(want, have) { + t.Error("\n" + diff(want, have)) + } +} + +func diff(want, have interface{}) string { + text, _ := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{ + A: difflib.SplitLines(spew.Sdump(want)), + B: difflib.SplitLines(spew.Sdump(have)), + FromFile: "want", + ToFile: "have", + Context: 3, + }) + return "\n" + text +}