diff --git a/experimental/demoprobe/generate.go b/experimental/demoprobe/generate.go index 83577aab72..0ea7827f98 100644 --- a/experimental/demoprobe/generate.go +++ b/experimental/demoprobe/generate.go @@ -7,6 +7,7 @@ import ( "strconv" "time" + "github.com/weaveworks/scope/probe/docker" "github.com/weaveworks/scope/report" ) @@ -70,17 +71,17 @@ func DemoReport(nodeCount int) report.Report { // Endpoint topology if _, ok := r.Endpoint.NodeMetadatas[srcPortID]; !ok { r.Endpoint.NodeMetadatas[srcPortID] = report.NodeMetadata{ - "pid": "4000", - "name": c.srcProc, - "domain": "node-" + src, + docker.PID: "4000", + docker.Name: c.srcProc, + docker.Domain: "node-" + src, } } r.Endpoint.Adjacency[srcID] = r.Endpoint.Adjacency[srcID].Add(dstPortID) if _, ok := r.Endpoint.NodeMetadatas[dstPortID]; !ok { r.Endpoint.NodeMetadatas[dstPortID] = report.NodeMetadata{ - "pid": "4000", - "name": c.dstProc, - "domain": "node-" + dst, + docker.PID: "4000", + docker.Name: c.dstProc, + docker.Domain: "node-" + dst, } } r.Endpoint.Adjacency[dstID] = r.Endpoint.Adjacency[dstID].Add(srcPortID) @@ -100,13 +101,13 @@ func DemoReport(nodeCount int) report.Report { // Address topology if _, ok := r.Address.NodeMetadatas[srcAddressID]; !ok { r.Address.NodeMetadatas[srcAddressID] = report.NodeMetadata{ - "name": src, + docker.Name: src, } } r.Address.Adjacency[nodeSrcAddressID] = r.Address.Adjacency[nodeSrcAddressID].Add(dstAddressID) if _, ok := r.Address.NodeMetadatas[dstAddressID]; !ok { r.Address.NodeMetadatas[dstAddressID] = report.NodeMetadata{ - "name": dst, + docker.Name: dst, } } r.Address.Adjacency[nodeDstAddressID] = r.Address.Adjacency[nodeDstAddressID].Add(srcAddressID) diff --git a/probe/docker/tagger.go b/probe/docker/tagger.go index d925436c22..9b039f5d35 100644 --- a/probe/docker/tagger.go +++ b/probe/docker/tagger.go @@ -10,7 +10,12 @@ import ( // These constants are keys used in node metadata // TODO: use these constants in report/{mapping.go, detailed_node.go} - pending some circular references const ( + Addr = "addr" ContainerID = "docker_container_id" + Domain = "domain" + Name = "name" + PID = "pid" + Port = "port" ) // These vars are exported for testing. diff --git a/probe/endpoint/reporter.go b/probe/endpoint/reporter.go index 87acdc0311..7fb21ac5cc 100644 --- a/probe/endpoint/reporter.go +++ b/probe/endpoint/reporter.go @@ -8,6 +8,7 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/weaveworks/procspy" + "github.com/weaveworks/scope/probe/docker" "github.com/weaveworks/scope/report" ) @@ -80,16 +81,12 @@ func (r *Reporter) addConnection(rpt *report.Report, c *procspy.Connection) { if _, ok := rpt.Address.NodeMetadatas[scopedLocal]; !ok { rpt.Address.NodeMetadatas[scopedLocal] = report.NodeMetadata{ - "name": r.hostName, - "addr": c.LocalAddress.String(), + docker.Name: r.hostName, + docker.Addr: c.LocalAddress.String(), } } - // Count the TCP connection. - edgeMeta := rpt.Address.EdgeMetadatas[edgeKey] - edgeMeta.WithConnCountTCP = true - edgeMeta.MaxConnCountTCP++ - rpt.Address.EdgeMetadatas[edgeKey] = edgeMeta + countTCPConnection(rpt.Address.EdgeMetadatas, edgeKey) if c.Proc.PID > 0 { var ( @@ -111,10 +108,14 @@ func (r *Reporter) addConnection(rpt *report.Report, c *procspy.Connection) { rpt.Endpoint.NodeMetadatas[scopedLocal] = md } - // Count the TCP connection. - edgeMeta := rpt.Endpoint.EdgeMetadatas[edgeKey] - edgeMeta.WithConnCountTCP = true - edgeMeta.MaxConnCountTCP++ - rpt.Endpoint.EdgeMetadatas[edgeKey] = edgeMeta + + countTCPConnection(rpt.Endpoint.EdgeMetadatas, edgeKey) } } + +func countTCPConnection(m report.EdgeMetadatas, edgeKey string) { + edgeMeta := m[edgeKey] + edgeMeta.WithConnCountTCP = true + edgeMeta.MaxConnCountTCP++ + m[edgeKey] = edgeMeta +} diff --git a/probe/endpoint/reporter_test.go b/probe/endpoint/reporter_test.go index 88fcb9f3b4..44f47462c6 100644 --- a/probe/endpoint/reporter_test.go +++ b/probe/endpoint/reporter_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/weaveworks/procspy" + "github.com/weaveworks/scope/probe/docker" "github.com/weaveworks/scope/probe/endpoint" "github.com/weaveworks/scope/report" ) @@ -95,7 +96,7 @@ func TestSpyNoProcesses(t *testing.T) { t.Fatalf("want %q, have %q", want, have) } - if want, have := nodeName, r.Address.NodeMetadatas[scopedLocal]["name"]; want != have { + if want, have := nodeName, r.Address.NodeMetadatas[scopedLocal][docker.Name]; want != have { t.Fatalf("want %q, have %q", want, have) } } diff --git a/render/detailed_node.go b/render/detailed_node.go index 59d8b6ac90..0ee7943563 100644 --- a/render/detailed_node.go +++ b/render/detailed_node.go @@ -2,7 +2,6 @@ package render import ( "fmt" - "reflect" "sort" "strconv" @@ -79,18 +78,17 @@ func MakeDetailedNode(r report.Report, n RenderableNode) DetailedNode { // multiple origins. The ultimate goal here is to generate tables to view // in the UI, so we skip the intermediate representations, but we could // add them later. -outer: + connections := []Row{} for _, id := range n.Origins { if table, ok := OriginTable(r, id); ok { - // TODO there's a bug that yields duplicate tables. Quick fix. - for _, existing := range tables { - if reflect.DeepEqual(existing, table) { - continue outer - } - } tables = append(tables, table) + } else if nmd, ok := r.Endpoint.NodeMetadatas[id]; ok { + connections = append(connections, connectionDetailsRows(r.Endpoint, id, nmd)...) } } + if len(connections) > 0 { + tables = append(tables, connectionDetailsTable(connections)) + } // Sort tables by rank sort.Sort(tables) @@ -107,9 +105,6 @@ outer: // OriginTable produces a table (to be consumed directly by the UI) based on // an origin ID, which is (optimistically) a node ID in one of our topologies. func OriginTable(r report.Report, originID string) (Table, bool) { - if nmd, ok := r.Endpoint.NodeMetadatas[originID]; ok { - return endpointOriginTable(nmd) - } if nmd, ok := r.Address.NodeMetadatas[originID]; ok { return addressOriginTable(nmd) } @@ -128,22 +123,29 @@ func OriginTable(r report.Report, originID string) (Table, bool) { return Table{}, false } -func endpointOriginTable(nmd report.NodeMetadata) (Table, bool) { +func connectionDetailsRows(endpointTopology report.Topology, originID string, nmd report.NodeMetadata) []Row { rows := []Row{} - for _, tuple := range []struct{ key, human string }{ - {"addr", "Endpoint"}, - {"port", "Port"}, - } { - if val, ok := nmd[tuple.key]; ok { - rows = append(rows, Row{Key: tuple.human, ValueMajor: val, ValueMinor: ""}) + local := fmt.Sprintf("%s:%s", nmd[docker.Addr], nmd[docker.Port]) + adjacencies := endpointTopology.Adjacency[report.MakeAdjacencyID(originID)] + sort.Strings(adjacencies) + for _, adj := range adjacencies { + if _, address, port, ok := report.ParseEndpointNodeID(adj); ok { + rows = append(rows, Row{ + Key: local, + ValueMajor: fmt.Sprintf("%s:%s", address, port), + }) } } + return rows +} + +func connectionDetailsTable(connectionRows []Row) Table { return Table{ - Title: "Origin Endpoint", + Title: "Connection Details", Numeric: false, - Rows: rows, + Rows: append([]Row{{Key: "Local", ValueMajor: "Remote"}}, connectionRows...), Rank: endpointRank, - }, len(rows) > 0 + } } func addressOriginTable(nmd report.NodeMetadata) (Table, bool) { diff --git a/render/detailed_node_test.go b/render/detailed_node_test.go index e2dc91183f..bc754b4e1c 100644 --- a/render/detailed_node_test.go +++ b/render/detailed_node_test.go @@ -1,6 +1,7 @@ package render_test import ( + "fmt" "reflect" "testing" @@ -13,14 +14,6 @@ func TestOriginTable(t *testing.T) { t.Errorf("unknown origin ID gave unexpected success") } for originID, want := range map[string]render.Table{ - test.Client54001NodeID: { - Title: "Origin Endpoint", - Numeric: false, - Rows: []render.Row{ - {"Endpoint", test.ClientIP, ""}, - {"Port", test.ClientPort54001, ""}, - }, - }, test.ClientAddressNodeID: { Title: "Origin Address", Numeric: false, @@ -107,11 +100,40 @@ func TestMakeDetailedNode(t *testing.T) { }, }, { - Title: "Origin Endpoint", + Title: "Connection Details", Numeric: false, Rows: []render.Row{ - {"Endpoint", test.ServerIP, ""}, - {"Port", test.ServerPort, ""}, + {"Local", "Remote", ""}, + { + fmt.Sprintf("%s:%s", test.ServerIP, test.ServerPort), + fmt.Sprintf("%s:%s", test.UnknownClient1IP, test.ClientPort54010), + "", + }, + { + fmt.Sprintf("%s:%s", test.ServerIP, test.ServerPort), + fmt.Sprintf("%s:%s", test.UnknownClient1IP, test.ClientPort54020), + "", + }, + { + fmt.Sprintf("%s:%s", test.ServerIP, test.ServerPort), + fmt.Sprintf("%s:%s", test.UnknownClient3IP, test.ClientPort54020), + "", + }, + { + fmt.Sprintf("%s:%s", test.ServerIP, test.ServerPort), + fmt.Sprintf("%s:%s", test.ClientIP, test.ClientPort54001), + "", + }, + { + fmt.Sprintf("%s:%s", test.ServerIP, test.ServerPort), + fmt.Sprintf("%s:%s", test.ClientIP, test.ClientPort54002), + "", + }, + { + fmt.Sprintf("%s:%s", test.ServerIP, test.ServerPort), + fmt.Sprintf("%s:%s", test.RandomClientIP, test.ClientPort12345), + "", + }, }, }, }, diff --git a/report/id.go b/report/id.go index f6205a21d3..b975eb9bd8 100644 --- a/report/id.go +++ b/report/id.go @@ -103,6 +103,17 @@ func ParseNodeID(nodeID string) (hostID string, remainder string, ok bool) { return fields[0], fields[1], true } +// ParseEndpointNodeID produces the host ID, address, and port and remainder +// (typically an address) from an endpoint node ID. Note that hostID may be +// blank. +func ParseEndpointNodeID(endpointNodeID string) (hostID, address, port string, ok bool) { + fields := strings.SplitN(endpointNodeID, ScopeDelim, 3) + if len(fields) != 3 { + return "", "", "", false + } + return fields[0], fields[1], fields[2], true +} + // ExtractHostID extracts the host id from NodeMetadata func ExtractHostID(m NodeMetadata) string { hostid, _, _ := ParseNodeID(m[HostNodeID]) diff --git a/report/id_test.go b/report/id_test.go index 8b1984afe1..6ec9158b6b 100644 --- a/report/id_test.go +++ b/report/id_test.go @@ -75,6 +75,42 @@ func TestAdjacencyID(t *testing.T) { } } +func TestEndpointNodeID(t *testing.T) { + for _, bad := range []string{ + clientAddressNodeID, + serverAddressNodeID, + unknownAddressNodeID, + clientHostNodeID, + serverHostNodeID, + "host.com;1.2.3.4", + "a;b", + "a;", + ";b", + ";", + "", + } { + if haveName, haveAddress, havePort, ok := report.ParseEndpointNodeID(bad); ok { + t.Errorf("%q: expected failure, but got {%q, %q, %q}", bad, haveName, haveAddress, havePort) + } + } + + for input, want := range map[string]struct{ name, address, port string }{ + report.MakeEndpointNodeID("host.com", "1.2.3.4", "c"): {"", "1.2.3.4", "c"}, + "a;b;c": {"a", "b", "c"}, + } { + haveName, haveAddress, havePort, ok := report.ParseEndpointNodeID(input) + if !ok { + t.Errorf("%q: not OK", input) + continue + } + if want.name != haveName || + want.address != haveAddress || + want.port != havePort { + t.Errorf("%q: want %q, have {%q, %q, %q}", input, want, haveName, haveAddress, havePort) + } + } +} + func TestEdgeID(t *testing.T) { for _, bad := range []string{ client54001EndpointNodeID, diff --git a/report/merge_test.go b/report/merge_test.go index 80f2d0b153..13ba5ffab9 100644 --- a/report/merge_test.go +++ b/report/merge_test.go @@ -4,6 +4,7 @@ import ( "reflect" "testing" + "github.com/weaveworks/scope/probe/docker" "github.com/weaveworks/scope/report" ) @@ -223,84 +224,84 @@ func TestMergeNodeMetadatas(t *testing.T) { a: report.NodeMetadatas{}, b: report.NodeMetadatas{ ":192.168.1.1:12345": report.NodeMetadata{ - "pid": "23128", - "name": "curl", - "domain": "node-a.local", + docker.PID: "23128", + docker.Name: "curl", + docker.Domain: "node-a.local", }, }, want: report.NodeMetadatas{ ":192.168.1.1:12345": report.NodeMetadata{ - "pid": "23128", - "name": "curl", - "domain": "node-a.local", + docker.PID: "23128", + docker.Name: "curl", + docker.Domain: "node-a.local", }, }, }, "Empty b": { a: report.NodeMetadatas{ ":192.168.1.1:12345": report.NodeMetadata{ - "pid": "23128", - "name": "curl", - "domain": "node-a.local", + docker.PID: "23128", + docker.Name: "curl", + docker.Domain: "node-a.local", }, }, b: report.NodeMetadatas{}, want: report.NodeMetadatas{ ":192.168.1.1:12345": report.NodeMetadata{ - "pid": "23128", - "name": "curl", - "domain": "node-a.local", + docker.PID: "23128", + docker.Name: "curl", + docker.Domain: "node-a.local", }, }, }, "Simple merge": { a: report.NodeMetadatas{ ":192.168.1.1:12345": report.NodeMetadata{ - "pid": "23128", - "name": "curl", - "domain": "node-a.local", + docker.PID: "23128", + docker.Name: "curl", + docker.Domain: "node-a.local", }, }, b: report.NodeMetadatas{ ":192.168.1.2:12345": report.NodeMetadata{ - "pid": "42", - "name": "curl", - "domain": "node-a.local", + docker.PID: "42", + docker.Name: "curl", + docker.Domain: "node-a.local", }, }, want: report.NodeMetadatas{ ":192.168.1.1:12345": report.NodeMetadata{ - "pid": "23128", - "name": "curl", - "domain": "node-a.local", + docker.PID: "23128", + docker.Name: "curl", + docker.Domain: "node-a.local", }, ":192.168.1.2:12345": report.NodeMetadata{ - "pid": "42", - "name": "curl", - "domain": "node-a.local", + docker.PID: "42", + docker.Name: "curl", + docker.Domain: "node-a.local", }, }, }, "Merge conflict": { a: report.NodeMetadatas{ ":192.168.1.1:12345": report.NodeMetadata{ - "pid": "23128", - "name": "curl", - "domain": "node-a.local", + docker.PID: "23128", + docker.Name: "curl", + docker.Domain: "node-a.local", }, }, b: report.NodeMetadatas{ ":192.168.1.1:12345": report.NodeMetadata{ // <-- same ID - "pid": "0", - "name": "curl", - "domain": "node-a.local", + docker.PID: "0", + docker.Name: "curl", + docker.Domain: "node-a.local", }, }, want: report.NodeMetadatas{ ":192.168.1.1:12345": report.NodeMetadata{ - "pid": "23128", - "name": "curl", - "domain": "node-a.local", + docker.PID: "23128", + docker.Name: "curl", + docker.Domain: "node-a.local", }, }, }, diff --git a/test/report_fixture.go b/test/report_fixture.go index 189cfbecd8..1c84a31161 100644 --- a/test/report_fixture.go +++ b/test/report_fixture.go @@ -12,11 +12,18 @@ var ( ServerHostID = "server.hostname.com" UnknownHostID = "" - ClientIP = "10.10.10.20" - ServerIP = "192.168.1.1" - ClientPort54001 = "54001" - ClientPort54002 = "54002" - ServerPort = "80" + ClientIP = "10.10.10.20" + ServerIP = "192.168.1.1" + ClientPort54001 = "54001" + ClientPort54010 = "54010" + ClientPort54002 = "54002" + ClientPort54020 = "54020" + ClientPort12345 = "12345" + ServerPort = "80" + UnknownClient1IP = "10.10.10.10" + UnknownClient2IP = "10.10.10.10" + UnknownClient3IP = "10.10.10.11" + RandomClientIP = "51.52.53.54" ClientHostName = ClientHostID ServerHostName = ServerHostID @@ -32,10 +39,10 @@ var ( Client54001NodeID = report.MakeEndpointNodeID(ClientHostID, ClientIP, ClientPort54001) // curl (1) Client54002NodeID = report.MakeEndpointNodeID(ClientHostID, ClientIP, ClientPort54002) // curl (2) Server80NodeID = report.MakeEndpointNodeID(ServerHostID, ServerIP, ServerPort) // apache - UnknownClient1NodeID = report.MakeEndpointNodeID(ServerHostID, "10.10.10.10", "54010") // we want to ensure two unknown clients, connnected - UnknownClient2NodeID = report.MakeEndpointNodeID(ServerHostID, "10.10.10.10", "54020") // to the same server, are deduped. - UnknownClient3NodeID = report.MakeEndpointNodeID(ServerHostID, "10.10.10.11", "54020") // Check this one isn't deduped - RandomClientNodeID = report.MakeEndpointNodeID(ServerHostID, "51.52.53.54", "12345") // this should become an internet node + UnknownClient1NodeID = report.MakeEndpointNodeID(ServerHostID, UnknownClient1IP, "54010") // we want to ensure two unknown clients, connnected + UnknownClient2NodeID = report.MakeEndpointNodeID(ServerHostID, UnknownClient2IP, "54020") // to the same server, are deduped. + UnknownClient3NodeID = report.MakeEndpointNodeID(ServerHostID, UnknownClient3IP, "54020") // Check this one isn't deduped + RandomClientNodeID = report.MakeEndpointNodeID(ServerHostID, RandomClientIP, "12345") // this should become an internet node ClientProcess1NodeID = report.MakeProcessNodeID(ClientHostID, Client1PID) ClientProcess2NodeID = report.MakeProcessNodeID(ClientHostID, Client2PID)