diff --git a/render/expected/expected.go b/render/expected/expected.go index 6ce50bd9b..c0b0dd0dd 100644 --- a/render/expected/expected.go +++ b/render/expected/expected.go @@ -10,22 +10,14 @@ import ( // Sterilize cleans up RenderableNodes test fixtures func Sterilize(r render.RenderableNodes) render.RenderableNodes { - // Since introducing new map fields to the report.NodeMetadata type, its - // zero value is •not valid• -- every time you need one, you need to use - // the report.MakeNodeMetadata constructor. (Similarly, but not exactly - // the same, is that a zero-value Adjacency is not the same as a created - // but empty Adjacency.) - // - // But we're not doing this in tests. We also don't particularly care - // about the output of NodeMetadata for the rendering pipeline, as this - // is never serialised to json. So this function sterilizes invalid - // RenderableNodes by setting an empty NodeMetadata from the proper - // constructor. + // RenderableNodes contain NodeMetadatas, but generally we + // only care about the Adjacency field - the rest is internal + // state not sent to the client. So in the tests we ignore + // this state. for id, n := range r { - if n.Adjacency == nil { - n.Adjacency = report.MakeIDList() - } - n.NodeMetadata = report.MakeNodeMetadata() + n.NodeMetadata.Metadata = report.Metadata{} + n.NodeMetadata.Counters = report.Counters{} + n.NodeMetadata.Edges = report.EdgeMetadatas{} r[id] = n } return r @@ -41,12 +33,11 @@ var ( ID: unknownPseudoNode1ID, LabelMajor: "10.10.10.10", Pseudo: true, - NodeMetadata: report.MakeNodeMetadata(), + NodeMetadata: report.MakeNodeMetadata().WithAdjacency(adjacency), EdgeMetadata: report.EdgeMetadata{ EgressPacketCount: newu64(70), EgressByteCount: newu64(700), }, - Adjacency: adjacency, Origins: report.MakeIDList( test.UnknownClient1NodeID, test.UnknownClient2NodeID, @@ -58,12 +49,11 @@ var ( ID: unknownPseudoNode2ID, LabelMajor: "10.10.10.11", Pseudo: true, - NodeMetadata: report.MakeNodeMetadata(), + NodeMetadata: report.MakeNodeMetadata().WithAdjacency(adjacency), EdgeMetadata: report.EdgeMetadata{ EgressPacketCount: newu64(50), EgressByteCount: newu64(500), }, - Adjacency: adjacency, Origins: report.MakeIDList( test.UnknownClient3NodeID, ), @@ -74,12 +64,11 @@ var ( ID: render.TheInternetID, LabelMajor: render.TheInternetMajor, Pseudo: true, - NodeMetadata: report.MakeNodeMetadata(), + NodeMetadata: report.MakeNodeMetadata().WithAdjacency(adjacency), EdgeMetadata: report.EdgeMetadata{ EgressPacketCount: newu64(60), EgressByteCount: newu64(600), }, - Adjacency: adjacency, Origins: report.MakeIDList( test.RandomClientNodeID, test.GoogleEndpointNodeID, @@ -98,13 +87,12 @@ var ( LabelMinor: fmt.Sprintf("%s (%s)", test.ClientHostID, test.Client1PID), Rank: test.Client1Comm, Pseudo: false, - Adjacency: report.MakeIDList(ServerProcessID), Origins: report.MakeIDList( test.Client54001NodeID, test.ClientProcess1NodeID, test.ClientHostNodeID, ), - NodeMetadata: report.MakeNodeMetadata(), + NodeMetadata: report.MakeNodeMetadata().WithAdjacent(ServerProcessID), EdgeMetadata: report.EdgeMetadata{ EgressPacketCount: newu64(10), EgressByteCount: newu64(100), @@ -116,13 +104,12 @@ var ( LabelMinor: fmt.Sprintf("%s (%s)", test.ClientHostID, test.Client2PID), Rank: test.Client2Comm, Pseudo: false, - Adjacency: report.MakeIDList(ServerProcessID), Origins: report.MakeIDList( test.Client54002NodeID, test.ClientProcess2NodeID, test.ClientHostNodeID, ), - NodeMetadata: report.MakeNodeMetadata(), + NodeMetadata: report.MakeNodeMetadata().WithAdjacent(ServerProcessID), EdgeMetadata: report.EdgeMetadata{ EgressPacketCount: newu64(20), EgressByteCount: newu64(200), @@ -134,7 +121,6 @@ var ( LabelMinor: fmt.Sprintf("%s (%s)", test.ServerHostID, test.ServerPID), Rank: test.ServerComm, Pseudo: false, - Adjacency: report.MakeIDList(), Origins: report.MakeIDList( test.Server80NodeID, test.ServerProcessNodeID, @@ -152,13 +138,12 @@ var ( LabelMinor: fmt.Sprintf("%s (%s)", test.ServerHostID, test.NonContainerPID), Rank: test.NonContainerComm, Pseudo: false, - Adjacency: report.MakeIDList(render.TheInternetID), Origins: report.MakeIDList( test.NonContainerProcessNodeID, test.ServerHostNodeID, test.NonContainerNodeID, ), - NodeMetadata: report.MakeNodeMetadata(), + NodeMetadata: report.MakeNodeMetadata().WithAdjacent(render.TheInternetID), EdgeMetadata: report.EdgeMetadata{}, }, unknownPseudoNode1ID: unknownPseudoNode1(report.MakeIDList(ServerProcessID)), @@ -173,7 +158,6 @@ var ( LabelMinor: "2 processes", Rank: "curl", Pseudo: false, - Adjacency: report.MakeIDList("apache"), Origins: report.MakeIDList( test.Client54001NodeID, test.Client54002NodeID, @@ -181,7 +165,7 @@ var ( test.ClientProcess2NodeID, test.ClientHostNodeID, ), - NodeMetadata: report.MakeNodeMetadata(), + NodeMetadata: report.MakeNodeMetadata().WithAdjacent("apache"), EdgeMetadata: report.EdgeMetadata{ EgressPacketCount: newu64(30), EgressByteCount: newu64(300), @@ -193,7 +177,6 @@ var ( LabelMinor: "1 process", Rank: "apache", Pseudo: false, - Adjacency: report.MakeIDList(), Origins: report.MakeIDList( test.Server80NodeID, test.ServerProcessNodeID, @@ -211,13 +194,12 @@ var ( LabelMinor: "1 process", Rank: test.NonContainerComm, Pseudo: false, - Adjacency: report.MakeIDList(render.TheInternetID), Origins: report.MakeIDList( test.NonContainerProcessNodeID, test.ServerHostNodeID, test.NonContainerNodeID, ), - NodeMetadata: report.MakeNodeMetadata(), + NodeMetadata: report.MakeNodeMetadata().WithAdjacent(render.TheInternetID), EdgeMetadata: report.EdgeMetadata{}, }, unknownPseudoNode1ID: unknownPseudoNode1(report.MakeIDList("apache")), @@ -232,7 +214,6 @@ var ( LabelMinor: test.ClientHostName, Rank: test.ClientContainerImageID, Pseudo: false, - Adjacency: report.MakeIDList(test.ServerContainerID), Origins: report.MakeIDList( test.ClientContainerNodeID, test.Client54001NodeID, @@ -241,7 +222,7 @@ var ( test.ClientProcess2NodeID, test.ClientHostNodeID, ), - NodeMetadata: report.MakeNodeMetadata(), + NodeMetadata: report.MakeNodeMetadata().WithAdjacent(test.ServerContainerID), EdgeMetadata: report.EdgeMetadata{ EgressPacketCount: newu64(30), EgressByteCount: newu64(300), @@ -253,7 +234,6 @@ var ( LabelMinor: test.ServerHostName, Rank: test.ServerContainerImageID, Pseudo: false, - Adjacency: report.MakeIDList(), Origins: report.MakeIDList( test.ServerContainerNodeID, test.Server80NodeID, @@ -272,13 +252,12 @@ var ( LabelMinor: test.ServerHostName, Rank: "", Pseudo: true, - Adjacency: report.MakeIDList(render.TheInternetID), Origins: report.MakeIDList( test.NonContainerProcessNodeID, test.ServerHostNodeID, test.NonContainerNodeID, ), - NodeMetadata: report.MakeNodeMetadata(), + NodeMetadata: report.MakeNodeMetadata().WithAdjacent(render.TheInternetID), EdgeMetadata: report.EdgeMetadata{}, }, render.TheInternetID: theInternetNode(report.MakeIDList(test.ServerContainerID)), @@ -291,7 +270,6 @@ var ( LabelMinor: "1 container", Rank: test.ClientContainerImageName, Pseudo: false, - Adjacency: report.MakeIDList(test.ServerContainerImageName), Origins: report.MakeIDList( test.ClientContainerImageNodeID, test.ClientContainerNodeID, @@ -301,7 +279,7 @@ var ( test.ClientProcess2NodeID, test.ClientHostNodeID, ), - NodeMetadata: report.MakeNodeMetadata(), + NodeMetadata: report.MakeNodeMetadata().WithAdjacent(test.ServerContainerImageName), EdgeMetadata: report.EdgeMetadata{ EgressPacketCount: newu64(30), EgressByteCount: newu64(300), @@ -313,7 +291,6 @@ var ( LabelMinor: "1 container", Rank: test.ServerContainerImageName, Pseudo: false, - Adjacency: report.MakeIDList(), Origins: report.MakeIDList( test.ServerContainerImageNodeID, test.ServerContainerNodeID, @@ -332,13 +309,12 @@ var ( LabelMinor: test.ServerHostName, Rank: "", Pseudo: true, - Adjacency: report.MakeIDList(render.TheInternetID), Origins: report.MakeIDList( test.NonContainerNodeID, test.NonContainerProcessNodeID, test.ServerHostNodeID, ), - NodeMetadata: report.MakeNodeMetadata(), + NodeMetadata: report.MakeNodeMetadata().WithAdjacent(render.TheInternetID), EdgeMetadata: report.EdgeMetadata{}, }, render.TheInternetID: theInternetNode(report.MakeIDList(test.ServerContainerImageName)), @@ -356,7 +332,6 @@ var ( LabelMinor: "hostname.com", // after first . Rank: "hostname.com", Pseudo: false, - Adjacency: report.MakeIDList(), Origins: report.MakeIDList( test.ServerHostNodeID, test.ServerAddressNodeID, @@ -372,12 +347,11 @@ var ( LabelMinor: "hostname.com", // after first . Rank: "hostname.com", Pseudo: false, - Adjacency: report.MakeIDList(ServerHostRenderedID), Origins: report.MakeIDList( test.ClientHostNodeID, test.ClientAddressNodeID, ), - NodeMetadata: report.MakeNodeMetadata(), + NodeMetadata: report.MakeNodeMetadata().WithAdjacent(ServerHostRenderedID), EdgeMetadata: report.EdgeMetadata{ MaxConnCountTCP: newu64(3), }, @@ -386,8 +360,7 @@ var ( ID: pseudoHostID1, LabelMajor: test.UnknownClient1IP, Pseudo: true, - Adjacency: report.MakeIDList(ServerHostRenderedID), - NodeMetadata: report.MakeNodeMetadata(), + NodeMetadata: report.MakeNodeMetadata().WithAdjacent(ServerHostRenderedID), EdgeMetadata: report.EdgeMetadata{}, Origins: report.MakeIDList(test.UnknownAddress1NodeID, test.UnknownAddress2NodeID), }, @@ -395,8 +368,7 @@ var ( ID: pseudoHostID2, LabelMajor: test.UnknownClient3IP, Pseudo: true, - Adjacency: report.MakeIDList(ServerHostRenderedID), - NodeMetadata: report.MakeNodeMetadata(), + NodeMetadata: report.MakeNodeMetadata().WithAdjacent(ServerHostRenderedID), EdgeMetadata: report.EdgeMetadata{}, Origins: report.MakeIDList(test.UnknownAddress3NodeID), }, @@ -404,8 +376,7 @@ var ( ID: render.TheInternetID, LabelMajor: render.TheInternetMajor, Pseudo: true, - Adjacency: report.MakeIDList(ServerHostRenderedID), - NodeMetadata: report.MakeNodeMetadata(), + NodeMetadata: report.MakeNodeMetadata().WithAdjacent(ServerHostRenderedID), EdgeMetadata: report.EdgeMetadata{}, Origins: report.MakeIDList(test.RandomAddressNodeID), }, diff --git a/render/mapping.go b/render/mapping.go index 75f44bff5..7c13d82dd 100644 --- a/render/mapping.go +++ b/render/mapping.go @@ -25,26 +25,16 @@ const ( processesKey = "processes" ) -// LeafMapFunc is anything which can take an arbitrary NodeMetadata, which is -// always one-to-one with nodes in a topology, and return a set of RenderableNodes -// - specific representations of the referenced node, in the form of a map of node -// ID to a human-readable major and minor labels. -// -// A single NodeMetadata can yield arbitrary many representations, including -// representations that reduce (or even increase) the cardinality of the set of nodes. -type LeafMapFunc func(report.NodeMetadata, report.Networks) RenderableNodes - // MapFunc is anything which can take an arbitrary RenderableNode and // return a set of other RenderableNodes. // -// As with LeafMapFunc, if the output is empty, the node -// shall be omitted from the rendered topology. -type MapFunc func(RenderableNode) RenderableNodes +// If the output is empty, the node shall be omitted from the rendered topology. +type MapFunc func(RenderableNode, report.Networks) RenderableNodes // 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 report.NodeMetadata, local report.Networks) RenderableNodes { +func MapEndpointIdentity(m RenderableNode, local report.Networks) RenderableNodes { addr, ok := m.Metadata[endpoint.Addr] if !ok { return RenderableNodes{} @@ -60,8 +50,8 @@ func MapEndpointIdentity(m report.NodeMetadata, local report.Networks) Renderabl if !ok { // If the dstNodeAddr is not in a network local to this report, we emit an // internet node - if !local.Contains(net.ParseIP(addr)) { - return RenderableNodes{TheInternetID: newPseudoNode(TheInternetID, TheInternetMajor, "")} + if ip := net.ParseIP(addr); ip != nil && !local.Contains(ip) { + return RenderableNodes{TheInternetID: newDerivedPseudoNode(TheInternetID, TheInternetMajor, m)} } // We are a 'client' pseudo node if the port is in the ephemeral port range. @@ -72,21 +62,21 @@ func MapEndpointIdentity(m report.NodeMetadata, local report.Networks) Renderabl dstNodeID := m.Adjacency[0] serverIP, serverPort := trySplitAddr(dstNodeID) outputID := MakePseudoNodeID(addr, serverIP, serverPort) - return RenderableNodes{outputID: newPseudoNode(outputID, addr, "")} + return RenderableNodes{outputID: newDerivedPseudoNode(outputID, addr, 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: newPseudoNode(outputID, addr+":"+port, "")} + return RenderableNodes{outputID: newDerivedPseudoNode(outputID, addr+":"+port, m)} } - return RenderableNodes{outputID: newPseudoNode(outputID, addr, "")} + return RenderableNodes{outputID: newDerivedPseudoNode(outputID, addr, m)} } var ( - id = MakeEndpointID(report.ExtractHostID(m), addr, port) + id = MakeEndpointID(report.ExtractHostID(m.NodeMetadata), addr, port) major = fmt.Sprintf("%s:%s", addr, port) - minor = report.ExtractHostID(m) + minor = report.ExtractHostID(m.NodeMetadata) rank = major ) @@ -94,32 +84,32 @@ func MapEndpointIdentity(m report.NodeMetadata, local report.Networks) Renderabl minor = fmt.Sprintf("%s (%s)", minor, pid) } - return RenderableNodes{id: NewRenderableNode(id, major, minor, rank, m)} + return RenderableNodes{id: NewRenderableNodeWith(id, major, minor, rank, m)} } // MapProcessIdentity maps a process topology node to a process renderable // node. As it is only ever run on process topology nodes, we expect that // certain keys are present. -func MapProcessIdentity(m report.NodeMetadata, _ report.Networks) RenderableNodes { +func MapProcessIdentity(m RenderableNode, _ report.Networks) RenderableNodes { pid, ok := m.Metadata[process.PID] if !ok { return RenderableNodes{} } var ( - id = MakeProcessID(report.ExtractHostID(m), pid) + id = MakeProcessID(report.ExtractHostID(m.NodeMetadata), pid) major = m.Metadata["comm"] - minor = fmt.Sprintf("%s (%s)", report.ExtractHostID(m), pid) + minor = fmt.Sprintf("%s (%s)", report.ExtractHostID(m.NodeMetadata), pid) rank = m.Metadata["comm"] ) - return RenderableNodes{id: NewRenderableNode(id, major, minor, rank, m)} + return RenderableNodes{id: NewRenderableNodeWith(id, major, minor, rank, m)} } // MapContainerIdentity maps a container topology node to a container // renderable node. As it is only ever run on container topology nodes, we // expect that certain keys are present. -func MapContainerIdentity(m report.NodeMetadata, _ report.Networks) RenderableNodes { +func MapContainerIdentity(m RenderableNode, _ report.Networks) RenderableNodes { id, ok := m.Metadata[docker.ContainerID] if !ok { return RenderableNodes{} @@ -127,17 +117,17 @@ func MapContainerIdentity(m report.NodeMetadata, _ report.Networks) RenderableNo var ( major = m.Metadata[docker.ContainerName] - minor = report.ExtractHostID(m) + minor = report.ExtractHostID(m.NodeMetadata) rank = m.Metadata[docker.ImageID] ) - return RenderableNodes{id: NewRenderableNode(id, major, minor, rank, m)} + return RenderableNodes{id: NewRenderableNodeWith(id, major, minor, rank, m)} } // MapContainerImageIdentity maps a container image topology node to container // image renderable node. As it is only ever run on container image topology // nodes, we expect that certain keys are present. -func MapContainerImageIdentity(m report.NodeMetadata, _ report.Networks) RenderableNodes { +func MapContainerImageIdentity(m RenderableNode, _ report.Networks) RenderableNodes { id, ok := m.Metadata[docker.ImageID] if !ok { return RenderableNodes{} @@ -148,13 +138,13 @@ func MapContainerImageIdentity(m report.NodeMetadata, _ report.Networks) Rendera rank = m.Metadata[docker.ImageID] ) - return RenderableNodes{id: NewRenderableNode(id, major, "", rank, m)} + return RenderableNodes{id: NewRenderableNodeWith(id, major, "", rank, m)} } // MapAddressIdentity maps an address topology node to an address renderable // node. As it is only ever run on address topology nodes, we expect that // certain keys are present. -func MapAddressIdentity(m report.NodeMetadata, local report.Networks) RenderableNodes { +func MapAddressIdentity(m RenderableNode, local report.Networks) RenderableNodes { addr, ok := m.Metadata[endpoint.Addr] if !ok { return RenderableNodes{} @@ -166,7 +156,7 @@ func MapAddressIdentity(m report.NodeMetadata, local report.Networks) Renderable // If the addr is not in a network local to this report, we emit an // internet node if !local.Contains(net.ParseIP(addr)) { - return RenderableNodes{TheInternetID: newPseudoNode(TheInternetID, TheInternetMajor, "")} + return RenderableNodes{TheInternetID: newDerivedPseudoNode(TheInternetID, TheInternetMajor, m)} } // Otherwise generate a pseudo node for every @@ -175,25 +165,25 @@ func MapAddressIdentity(m report.NodeMetadata, local report.Networks) Renderable _, dstAddr, _ := report.ParseAddressNodeID(m.Adjacency[0]) outputID = MakePseudoNodeID(addr, dstAddr) } - return RenderableNodes{outputID: newPseudoNode(outputID, addr, "")} + return RenderableNodes{outputID: newDerivedPseudoNode(outputID, addr, m)} } var ( - id = MakeAddressID(report.ExtractHostID(m), addr) + id = MakeAddressID(report.ExtractHostID(m.NodeMetadata), addr) major = addr - minor = report.ExtractHostID(m) + minor = report.ExtractHostID(m.NodeMetadata) rank = major ) - return RenderableNodes{id: NewRenderableNode(id, major, minor, rank, m)} + return RenderableNodes{id: NewRenderableNodeWith(id, major, minor, rank, m)} } // MapHostIdentity maps a host topology node to a host renderable node. As it // is only ever run on host topology nodes, we expect that certain keys are // present. -func MapHostIdentity(m report.NodeMetadata, _ report.Networks) RenderableNodes { +func MapHostIdentity(m RenderableNode, _ report.Networks) RenderableNodes { var ( - id = MakeHostID(report.ExtractHostID(m)) + id = MakeHostID(report.ExtractHostID(m.NodeMetadata)) hostname = m.Metadata[host.HostName] parts = strings.SplitN(hostname, ".", 2) major, minor, rank = "", "", "" @@ -205,14 +195,14 @@ func MapHostIdentity(m report.NodeMetadata, _ report.Networks) RenderableNodes { major = hostname } - return RenderableNodes{id: NewRenderableNode(id, major, minor, rank, m)} + return RenderableNodes{id: NewRenderableNodeWith(id, major, minor, rank, m)} } // MapEndpoint2IP maps endpoint nodes to their IP address, for joining // with container nodes. We drop endpoint nodes with pids, as they // will be joined to containers through the process topology, and we // don't want to double count edges. -func MapEndpoint2IP(m report.NodeMetadata, local report.Networks) RenderableNodes { +func MapEndpoint2IP(m RenderableNode, local report.Networks) RenderableNodes { _, ok := m.Metadata[process.PID] if ok { return RenderableNodes{} @@ -222,22 +212,22 @@ func MapEndpoint2IP(m report.NodeMetadata, local report.Networks) RenderableNode return RenderableNodes{} } if !local.Contains(net.ParseIP(addr)) { - return RenderableNodes{TheInternetID: newPseudoNode(TheInternetID, TheInternetMajor, "")} + return RenderableNodes{TheInternetID: newDerivedPseudoNode(TheInternetID, TheInternetMajor, m)} } - return RenderableNodes{addr: NewRenderableNode(addr, "", "", "", m)} + return RenderableNodes{addr: NewRenderableNodeWith(addr, "", "", "", m)} } // MapContainer2IP maps container nodes to their IP addresses (outputs // multiple nodes). This allows container to be joined directly with // the endpoint topology. -func MapContainer2IP(m report.NodeMetadata, _ report.Networks) RenderableNodes { +func MapContainer2IP(m RenderableNode, _ report.Networks) RenderableNodes { result := RenderableNodes{} addrs, ok := m.Metadata[docker.ContainerIPs] if !ok { return result } for _, addr := range strings.Fields(addrs) { - n := NewRenderableNode(addr, "", "", "", m) + n := NewRenderableNodeWith(addr, "", "", "", m) n.NodeMetadata.Counters[containersKey] = 1 result[addr] = n } @@ -247,7 +237,7 @@ func MapContainer2IP(m report.NodeMetadata, _ report.Networks) RenderableNodes { // MapIP2Container maps IP nodes produced from MapContainer2IP back to // container nodes. If there is more than one container with a given // IP, it is dropped. -func MapIP2Container(n RenderableNode) RenderableNodes { +func MapIP2Container(n RenderableNode, _ report.Networks) RenderableNodes { // If an IP is shared between multiple containers, we can't // reliably attribute an connection based on its IP if n.NodeMetadata.Counters[containersKey] > 1 { @@ -267,7 +257,7 @@ func MapIP2Container(n RenderableNode) RenderableNodes { return RenderableNodes{} } - return RenderableNodes{id: newDerivedNode(id, n)} + return RenderableNodes{id: NewDerivedNode(id, n)} } // MapEndpoint2Process maps endpoint RenderableNodes to process @@ -281,7 +271,7 @@ func MapIP2Container(n RenderableNode) 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) RenderableNodes { +func MapEndpoint2Process(n RenderableNode, _ report.Networks) RenderableNodes { if n.Pseudo { return RenderableNodes{n.ID: n} } @@ -292,7 +282,7 @@ func MapEndpoint2Process(n RenderableNode) RenderableNodes { } id := MakeProcessID(report.ExtractHostID(n.NodeMetadata), pid) - return RenderableNodes{id: newDerivedNode(id, n)} + return RenderableNodes{id: NewDerivedNode(id, n)} } // MapProcess2Container maps process RenderableNodes to container @@ -306,7 +296,7 @@ func MapEndpoint2Process(n RenderableNode) RenderableNodes { // 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 container graph to get that info. -func MapProcess2Container(n RenderableNode) RenderableNodes { +func MapProcess2Container(n RenderableNode, _ report.Networks) RenderableNodes { // Propogate the internet pseudo node if n.ID == TheInternetID { return RenderableNodes{n.ID: n} @@ -330,7 +320,7 @@ func MapProcess2Container(n RenderableNode) RenderableNodes { return RenderableNodes{id: node} } - return RenderableNodes{id: newDerivedNode(id, n)} + return RenderableNodes{id: NewDerivedNode(id, n)} } // MapProcess2Name maps process RenderableNodes to RenderableNodes @@ -339,7 +329,7 @@ func MapProcess2Container(n RenderableNode) RenderableNodes { // 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) RenderableNodes { +func MapProcess2Name(n RenderableNode, _ report.Networks) RenderableNodes { if n.Pseudo { return RenderableNodes{n.ID: n} } @@ -349,7 +339,7 @@ func MapProcess2Name(n RenderableNode) RenderableNodes { return RenderableNodes{} } - node := newDerivedNode(name, n) + node := NewDerivedNode(name, n) node.LabelMajor = name node.Rank = name node.NodeMetadata.Counters[processesKey] = 1 @@ -359,7 +349,7 @@ func MapProcess2Name(n RenderableNode) RenderableNodes { // MapCountProcessName maps 1:1 process name nodes, counting // the number of processes grouped together and putting // that info in the minor label. -func MapCountProcessName(n RenderableNode) RenderableNodes { +func MapCountProcessName(n RenderableNode, _ report.Networks) RenderableNodes { if n.Pseudo { return RenderableNodes{n.ID: n} } @@ -384,7 +374,7 @@ func MapCountProcessName(n RenderableNode) RenderableNodes { // 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 container graph to get that info. -func MapContainer2ContainerImage(n RenderableNode) RenderableNodes { +func MapContainer2ContainerImage(n RenderableNode, _ report.Networks) RenderableNodes { // Propogate all pseudo nodes if n.Pseudo { return RenderableNodes{n.ID: n} @@ -398,7 +388,7 @@ func MapContainer2ContainerImage(n RenderableNode) RenderableNodes { } // Add container- key to NMD, which will later be counted to produce the minor label - result := newDerivedNode(id, n) + result := NewDerivedNode(id, n) result.NodeMetadata.Counters[containersKey] = 1 return RenderableNodes{id: result} } @@ -409,7 +399,7 @@ func MapContainer2ContainerImage(n RenderableNode) RenderableNodes { // 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 MapContainerImage2Name(n RenderableNode) RenderableNodes { +func MapContainerImage2Name(n RenderableNode, _ report.Networks) RenderableNodes { if n.Pseudo { return RenderableNodes{n.ID: n} } @@ -424,7 +414,7 @@ func MapContainerImage2Name(n RenderableNode) RenderableNodes { name = parts[0] } - node := newDerivedNode(name, n) + node := NewDerivedNode(name, n) node.LabelMajor = name node.Rank = name node.NodeMetadata = n.NodeMetadata.Copy() // Propagate NMD for container counting. @@ -434,7 +424,7 @@ func MapContainerImage2Name(n RenderableNode) RenderableNodes { // MapCountContainers maps 1:1 container image nodes, counting // the number of containers grouped together and putting // that info in the minor label. -func MapCountContainers(n RenderableNode) RenderableNodes { +func MapCountContainers(n RenderableNode, _ report.Networks) RenderableNodes { if n.Pseudo { return RenderableNodes{n.ID: n} } @@ -451,13 +441,13 @@ func MapCountContainers(n RenderableNode) RenderableNodes { // MapAddress2Host maps address RenderableNodes to host RenderableNodes. // // Otherthan pseudo nodes, we can assume all nodes have a HostID -func MapAddress2Host(n RenderableNode) RenderableNodes { +func MapAddress2Host(n RenderableNode, _ report.Networks) RenderableNodes { if n.Pseudo { return RenderableNodes{n.ID: n} } id := MakeHostID(report.ExtractHostID(n.NodeMetadata)) - return RenderableNodes{id: newDerivedNode(id, n)} + return RenderableNodes{id: NewDerivedNode(id, n)} } // trySplitAddr is basically ParseArbitraryNodeID, since its callsites diff --git a/render/mapping_test.go b/render/mapping_test.go index 6c73c597f..8b337041c 100644 --- a/render/mapping_test.go +++ b/render/mapping_test.go @@ -11,14 +11,18 @@ import ( "github.com/weaveworks/scope/report" ) +func nrn(nmd report.NodeMetadata) render.RenderableNode { + return render.NewRenderableNode("").WithNodeMetadata(nmd) +} + func TestMapEndpointIdentity(t *testing.T) { for _, input := range []testcase{ - {report.MakeNodeMetadata(), false}, - {report.MakeNodeMetadataWith(map[string]string{endpoint.Addr: "1.2.3.4"}), false}, - {report.MakeNodeMetadataWith(map[string]string{endpoint.Port: "1234"}), false}, - {report.MakeNodeMetadataWith(map[string]string{endpoint.Addr: "1.2.3.4", endpoint.Port: "1234"}), true}, - {report.MakeNodeMetadataWith(map[string]string{endpoint.Addr: "1.2.3.4", endpoint.Port: "40000"}), true}, - {report.MakeNodeMetadataWith(map[string]string{report.HostNodeID: report.MakeHostNodeID("foo"), endpoint.Addr: "10.0.0.1", endpoint.Port: "20001"}), true}, + {nrn(report.MakeNodeMetadata()), false}, + {nrn(report.MakeNodeMetadataWith(map[string]string{endpoint.Addr: "1.2.3.4"})), false}, + {nrn(report.MakeNodeMetadataWith(map[string]string{endpoint.Port: "1234"})), false}, + {nrn(report.MakeNodeMetadataWith(map[string]string{endpoint.Addr: "1.2.3.4", endpoint.Port: "1234"})), true}, + {nrn(report.MakeNodeMetadataWith(map[string]string{endpoint.Addr: "1.2.3.4", endpoint.Port: "40000"})), true}, + {nrn(report.MakeNodeMetadataWith(map[string]string{report.HostNodeID: report.MakeHostNodeID("foo"), endpoint.Addr: "10.0.0.1", endpoint.Port: "20001"})), true}, } { testMap(t, render.MapEndpointIdentity, input) } @@ -26,8 +30,8 @@ func TestMapEndpointIdentity(t *testing.T) { func TestMapProcessIdentity(t *testing.T) { for _, input := range []testcase{ - {report.MakeNodeMetadata(), false}, - {report.MakeNodeMetadataWith(map[string]string{process.PID: "201"}), true}, + {nrn(report.MakeNodeMetadata()), false}, + {nrn(report.MakeNodeMetadataWith(map[string]string{process.PID: "201"})), true}, } { testMap(t, render.MapProcessIdentity, input) } @@ -35,8 +39,8 @@ func TestMapProcessIdentity(t *testing.T) { func TestMapContainerIdentity(t *testing.T) { for _, input := range []testcase{ - {report.MakeNodeMetadata(), false}, - {report.MakeNodeMetadataWith(map[string]string{docker.ContainerID: "a1b2c3"}), true}, + {nrn(report.MakeNodeMetadata()), false}, + {nrn(report.MakeNodeMetadataWith(map[string]string{docker.ContainerID: "a1b2c3"})), true}, } { testMap(t, render.MapContainerIdentity, input) } @@ -44,8 +48,8 @@ func TestMapContainerIdentity(t *testing.T) { func TestMapContainerImageIdentity(t *testing.T) { for _, input := range []testcase{ - {report.MakeNodeMetadata(), false}, - {report.MakeNodeMetadataWith(map[string]string{docker.ImageID: "a1b2c3"}), true}, + {nrn(report.MakeNodeMetadata()), false}, + {nrn(report.MakeNodeMetadataWith(map[string]string{docker.ImageID: "a1b2c3"})), true}, } { testMap(t, render.MapContainerImageIdentity, input) } @@ -53,8 +57,8 @@ func TestMapContainerImageIdentity(t *testing.T) { func TestMapAddressIdentity(t *testing.T) { for _, input := range []testcase{ - {report.MakeNodeMetadata(), false}, - {report.MakeNodeMetadataWith(map[string]string{endpoint.Addr: "192.168.1.1"}), true}, + {nrn(report.MakeNodeMetadata()), false}, + {nrn(report.MakeNodeMetadataWith(map[string]string{endpoint.Addr: "192.168.1.1"})), true}, } { testMap(t, render.MapAddressIdentity, input) } @@ -62,18 +66,18 @@ func TestMapAddressIdentity(t *testing.T) { func TestMapHostIdentity(t *testing.T) { for _, input := range []testcase{ - {report.MakeNodeMetadata(), true}, // TODO it's questionable if this is actually correct + {nrn(report.MakeNodeMetadata()), true}, // TODO it's questionable if this is actually correct } { testMap(t, render.MapHostIdentity, input) } } type testcase struct { - md report.NodeMetadata + md render.RenderableNode ok bool } -func testMap(t *testing.T, f render.LeafMapFunc, input testcase) { +func testMap(t *testing.T, f render.MapFunc, input testcase) { _, ipNet, err := net.ParseCIDR("1.2.3.0/16") if err != nil { t.Fatalf(err.Error()) diff --git a/render/render.go b/render/render.go index 66cee9f11..a21452596 100644 --- a/render/render.go +++ b/render/render.go @@ -23,7 +23,7 @@ func MakeReduce(renderers ...Renderer) Renderer { func (r Reduce) Render(rpt report.Report) RenderableNodes { result := RenderableNodes{} for _, renderer := range r { - result.Merge(renderer.Render(rpt)) + result = result.Merge(renderer.Render(rpt)) } return result } @@ -52,17 +52,20 @@ func (m Map) Render(rpt report.Report) RenderableNodes { } func (m Map) render(rpt report.Report) (RenderableNodes, map[string]report.IDList) { - input := m.Renderer.Render(rpt) - output := RenderableNodes{} - mapped := map[string]report.IDList{} // input node ID -> output node IDs - adjacencies := map[string]report.IDList{} // output node ID -> input node Adjacencies + var ( + input = m.Renderer.Render(rpt) + output = RenderableNodes{} + mapped = map[string]report.IDList{} // input node ID -> output node IDs + adjacencies = map[string]report.IDList{} // output node ID -> input node Adjacencies + localNetworks = LocalNetworks(rpt) + ) + // Rewrite all the nodes according to the map function for _, inRenderable := range input { - outRenderables := m.MapFunc(inRenderable) - for _, outRenderable := range outRenderables { + for _, outRenderable := range m.MapFunc(inRenderable, localNetworks) { existing, ok := output[outRenderable.ID] if ok { - outRenderable.Merge(existing) + outRenderable = outRenderable.Merge(existing) } output[outRenderable.ID] = outRenderable @@ -72,9 +75,6 @@ func (m Map) render(rpt report.Report) (RenderableNodes, map[string]report.IDLis } // Rewrite Adjacency for new node IDs. - // NB we don't do pseudo nodes here; we assume the input graph - // is properly-connected, and if the map func dropped a node, - // we drop links to it. for outNodeID, inAdjacency := range adjacencies { outAdjacency := report.MakeIDList() for _, inAdjacent := range inAdjacency { @@ -123,128 +123,6 @@ func (m Map) EdgeMetadata(rpt report.Report, srcRenderableID, dstRenderableID st return output } -// LeafMap is a Renderer which produces a set of RenderableNodes from a report.Topology -// by using a map function and topology selector. -type LeafMap struct { - Selector report.TopologySelector - Mapper LeafMapFunc -} - -// Render transforms a given Report into a set of RenderableNodes, which -// the UI will render collectively as a graph. Note that a RenderableNode will -// always be rendered with other nodes, and therefore contains limited detail. -// -// Nodes with the same mapped IDs will be merged. -func (m LeafMap) Render(rpt report.Report) RenderableNodes { - var ( - t = m.Selector(rpt) - nodes = RenderableNodes{} - source2mapped = map[string]report.IDList{} // input node ID -> output node IDs - adjacencies = map[string]report.IDList{} // input node ID -> input node Adjacencies - localNetworks = LocalNetworks(rpt) - ) - - // Build a set of RenderableNodes for all non-pseudo probes, and an - // addressID to nodeID lookup map. Multiple addressIDs can map to the same - // RenderableNodes. - for nodeID, metadata := range t.NodeMetadatas { - for _, mapped := range m.Mapper(metadata, localNetworks) { - // mapped.ID needs not be unique over all addressIDs. If not, we merge with - // the existing data, on the assumption that the MapFunc returns the same - // data. - existing, ok := nodes[mapped.ID] - if ok { - mapped.Merge(existing) - } - - origins := mapped.Origins - origins = origins.Add(nodeID) - if hostNodeID, ok := metadata.Metadata[report.HostNodeID]; ok { - origins = origins.Add(hostNodeID) - } - mapped.Origins = origins - nodes[mapped.ID] = mapped - source2mapped[nodeID] = source2mapped[nodeID].Add(mapped.ID) - adjacencies[nodeID] = metadata.Adjacency - } - } - - // We propagate edge metadata to nodes on both ends of the edges. - // TODO we should 'reverse' one end of the edge meta data - ingress -> egress etc. - for srcNodeID, nmd := range t.NodeMetadatas { - for _, srcRenderableID := range source2mapped[srcNodeID] { - srcRenderableNode := nodes[srcRenderableID] - - for dstNodeID, emd := range nmd.Edges { - for _, dstRenderableID := range source2mapped[dstNodeID] { - dstRenderableNode := nodes[dstRenderableID] - - srcRenderableNode.EdgeMetadata = srcRenderableNode.EdgeMetadata.Merge(emd) - dstRenderableNode.EdgeMetadata = dstRenderableNode.EdgeMetadata.Merge(emd) - - nodes[dstRenderableID] = dstRenderableNode - } - } - - nodes[srcRenderableID] = srcRenderableNode - } - } - - // Walk the graph and make connections. - for srcNodeID, dstNodeIDs := range adjacencies { - for _, srcRenderableID := range source2mapped[srcNodeID] { - srcRenderableNode := nodes[srcRenderableID] - - for _, dstNodeID := range dstNodeIDs { - for _, dstRenderableID := range source2mapped[dstNodeID] { - srcRenderableNode.Adjacency = srcRenderableNode.Adjacency.Add(dstRenderableID) - } - } - - nodes[srcRenderableID] = srcRenderableNode - } - } - - return nodes -} - -func ids(nodes RenderableNodes) report.IDList { - result := report.MakeIDList() - for id := range nodes { - result = result.Add(id) - } - return result -} - -// EdgeMetadata gives the metadata of an edge from the perspective of the -// srcRenderableID. Since an edgeID can have multiple edges on the address -// level, it uses the supplied mapping function to translate address IDs to -// renderable node (mapped) IDs. -func (m LeafMap) EdgeMetadata(rpt report.Report, srcRenderableID, dstRenderableID string) report.EdgeMetadata { - var ( - t = m.Selector(rpt) - localNetworks = LocalNetworks(rpt) - metadata = report.EdgeMetadata{} - ) - for src, nmd := range t.NodeMetadatas { - for dst, edgeMeta := range nmd.Edges { - srcs, dsts := report.MakeIDList(src), report.MakeIDList(dst) - if src != report.TheInternet { - mapped := m.Mapper(t.NodeMetadatas[src], localNetworks) - srcs = ids(mapped) - } - if dst != report.TheInternet { - mapped := m.Mapper(t.NodeMetadatas[dst], localNetworks) - dsts = ids(mapped) - } - if srcs.Contains(srcRenderableID) && dsts.Contains(dstRenderableID) { - metadata = metadata.Flatten(edgeMeta) - } - } - } - return metadata -} - // CustomRenderer allow for mapping functions that recived the entire topology // in one call - useful for functions that need to consider the entire graph type CustomRenderer struct { diff --git a/render/render_test.go b/render/render_test.go index 22e84d013..d3802c2d5 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -24,13 +24,15 @@ func (m mockRenderer) EdgeMetadata(rpt report.Report, localID, remoteID string) func TestReduceRender(t *testing.T) { renderer := render.Reduce([]render.Renderer{ - mockRenderer{RenderableNodes: render.RenderableNodes{"foo": {ID: "foo"}}}, - mockRenderer{RenderableNodes: render.RenderableNodes{"bar": {ID: "bar"}}}, + mockRenderer{RenderableNodes: render.RenderableNodes{"foo": render.NewRenderableNode("foo")}}, + mockRenderer{RenderableNodes: render.RenderableNodes{"bar": render.NewRenderableNode("bar")}}, }) - want := render.RenderableNodes{"foo": {ID: "foo"}, "bar": {ID: "bar"}} + want := render.RenderableNodes{ + "foo": render.NewRenderableNode("foo"), + "bar": render.NewRenderableNode("bar"), + } have := renderer.Render(report.MakeReport()) - if !reflect.DeepEqual(want, have) { t.Errorf("want %+v, have %+v", want, have) } @@ -44,7 +46,6 @@ func TestReduceEdge(t *testing.T) { want := report.EdgeMetadata{EgressPacketCount: newu64(3)} have := renderer.EdgeMetadata(report.MakeReport(), "", "") - if !reflect.DeepEqual(want, have) { t.Errorf("want %+v, have %+v", want, have) } @@ -53,11 +54,11 @@ func TestReduceEdge(t *testing.T) { func TestMapRender1(t *testing.T) { // 1. Check when we return false, the node gets filtered out mapper := render.Map{ - MapFunc: func(nodes render.RenderableNode) render.RenderableNodes { + MapFunc: func(nodes render.RenderableNode, _ report.Networks) render.RenderableNodes { return render.RenderableNodes{} }, Renderer: mockRenderer{RenderableNodes: render.RenderableNodes{ - "foo": {ID: "foo"}, + "foo": render.NewRenderableNode("foo"), }}, } want := render.RenderableNodes{} @@ -70,17 +71,19 @@ func TestMapRender1(t *testing.T) { func TestMapRender2(t *testing.T) { // 2. Check we can remap two nodes into one mapper := render.Map{ - MapFunc: func(nodes render.RenderableNode) render.RenderableNodes { - return render.RenderableNodes{"bar": render.RenderableNode{ID: "bar"}} + MapFunc: func(nodes render.RenderableNode, _ report.Networks) render.RenderableNodes { + return render.RenderableNodes{ + "bar": render.NewRenderableNode("bar"), + } }, Renderer: mockRenderer{RenderableNodes: render.RenderableNodes{ - "foo": {ID: "foo"}, - "baz": {ID: "baz"}, + "foo": render.NewRenderableNode("foo"), + "baz": render.NewRenderableNode("baz"), }}, } - want := expected.Sterilize(render.RenderableNodes{ - "bar": render.RenderableNode{ID: "bar"}, - }) + want := render.RenderableNodes{ + "bar": render.NewRenderableNode("bar"), + } have := mapper.Render(report.MakeReport()) if !reflect.DeepEqual(want, have) { t.Error(test.Diff(want, have)) @@ -90,28 +93,28 @@ func TestMapRender2(t *testing.T) { func TestMapRender3(t *testing.T) { // 3. Check we can remap adjacencies mapper := render.Map{ - MapFunc: func(nodes render.RenderableNode) render.RenderableNodes { + MapFunc: func(nodes render.RenderableNode, _ report.Networks) render.RenderableNodes { id := "_" + nodes.ID - return render.RenderableNodes{id: render.RenderableNode{ID: id}} + return render.RenderableNodes{id: render.NewRenderableNode(id)} }, Renderer: mockRenderer{RenderableNodes: render.RenderableNodes{ - "foo": {ID: "foo", Adjacency: report.MakeIDList("baz")}, - "baz": {ID: "baz", Adjacency: report.MakeIDList("foo")}, + "foo": render.NewRenderableNode("foo").WithNodeMetadata(report.MakeNodeMetadata().WithAdjacent("baz")), + "baz": render.NewRenderableNode("baz").WithNodeMetadata(report.MakeNodeMetadata().WithAdjacent("foo")), }}, } want := render.RenderableNodes{ - "_foo": {ID: "_foo", Adjacency: report.MakeIDList("_baz")}, - "_baz": {ID: "_baz", Adjacency: report.MakeIDList("_foo")}, + "_foo": render.NewRenderableNode("_foo").WithNodeMetadata(report.MakeNodeMetadata().WithAdjacent("_baz")), + "_baz": render.NewRenderableNode("_baz").WithNodeMetadata(report.MakeNodeMetadata().WithAdjacent("_foo")), } have := mapper.Render(report.MakeReport()) if !reflect.DeepEqual(want, have) { - t.Errorf("want %+v, have %+v", want, have) + t.Error(test.Diff(want, have)) } } func TestMapEdge(t *testing.T) { - selector := func(_ report.Report) report.Topology { - return report.Topology{ + selector := render.TopologySelector(func(_ report.Report) render.RenderableNodes { + return render.MakeRenderableNodes(report.Topology{ NodeMetadatas: report.NodeMetadatas{ "foo": report.MakeNodeMetadata().WithMetadata(map[string]string{ "id": "foo", @@ -127,29 +130,38 @@ func TestMapEdge(t *testing.T) { EgressByteCount: newu64(4), }), }, - } - } - - identity := func(nmd report.NodeMetadata, _ report.Networks) render.RenderableNodes { - return render.RenderableNodes{nmd.Metadata["id"]: render.NewRenderableNode(nmd.Metadata["id"], "", "", "", nmd)} - } + }) + }) mapper := render.Map{ - MapFunc: func(nodes render.RenderableNode) render.RenderableNodes { - id := "_" + nodes.ID - return render.RenderableNodes{id: render.RenderableNode{ID: id}} - }, - Renderer: render.LeafMap{ - Selector: selector, - Mapper: identity, + MapFunc: func(node render.RenderableNode, _ report.Networks) render.RenderableNodes { + id := "_" + node.ID + return render.RenderableNodes{id: render.NewDerivedNode(id, node)} }, + Renderer: selector, } - have := mapper.Render(report.MakeReport()) - want := render.RenderableNodes{ - "_foo": {ID: "_foo", Adjacency: report.MakeIDList("_bar")}, - "_bar": {ID: "_bar", Adjacency: report.MakeIDList("_foo")}, - } + have := expected.Sterilize(mapper.Render(report.MakeReport())) + want := expected.Sterilize(render.RenderableNodes{ + "_foo": { + ID: "_foo", + Origins: report.MakeIDList("foo"), + NodeMetadata: report.MakeNodeMetadata().WithAdjacent("_bar"), + EdgeMetadata: report.EdgeMetadata{ + EgressPacketCount: newu64(4), + EgressByteCount: newu64(6), + }, + }, + "_bar": { + ID: "_bar", + Origins: report.MakeIDList("bar"), + NodeMetadata: report.MakeNodeMetadata().WithAdjacent("_foo"), + EdgeMetadata: report.EdgeMetadata{ + EgressPacketCount: newu64(4), + EgressByteCount: newu64(6), + }, + }, + }) if !reflect.DeepEqual(want, have) { t.Error(test.Diff(want, have)) } @@ -165,13 +177,13 @@ func TestMapEdge(t *testing.T) { func TestFilterRender(t *testing.T) { renderer := render.FilterUnconnected( mockRenderer{RenderableNodes: render.RenderableNodes{ - "foo": {ID: "foo", Adjacency: report.MakeIDList("bar"), NodeMetadata: report.MakeNodeMetadata()}, - "bar": {ID: "bar", Adjacency: report.MakeIDList("foo"), NodeMetadata: report.MakeNodeMetadata()}, - "baz": {ID: "baz", Adjacency: report.MakeIDList(), NodeMetadata: report.MakeNodeMetadata()}, + "foo": {ID: "foo", NodeMetadata: report.MakeNodeMetadata().WithAdjacent("bar")}, + "bar": {ID: "bar", NodeMetadata: report.MakeNodeMetadata().WithAdjacent("foo")}, + "baz": {ID: "baz", NodeMetadata: report.MakeNodeMetadata()}, }}) want := render.RenderableNodes{ - "foo": {ID: "foo", Adjacency: report.MakeIDList("bar"), NodeMetadata: report.MakeNodeMetadata()}, - "bar": {ID: "bar", Adjacency: report.MakeIDList("foo"), NodeMetadata: report.MakeNodeMetadata()}, + "foo": {ID: "foo", NodeMetadata: report.MakeNodeMetadata().WithAdjacent("bar")}, + "bar": {ID: "bar", NodeMetadata: report.MakeNodeMetadata().WithAdjacent("foo")}, } have := expected.Sterilize(renderer.Render(report.MakeReport())) if !reflect.DeepEqual(want, have) { diff --git a/render/renderable_node.go b/render/renderable_node.go index 525a032b2..c95de26ad 100644 --- a/render/renderable_node.go +++ b/render/renderable_node.go @@ -13,67 +13,42 @@ type RenderableNode struct { 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 - Adjacency report.IDList `json:"adjacency,omitempty"` // Node IDs (in the same topology domain) Origins report.IDList `json:"origins,omitempty"` // Core node IDs that contributed information report.EdgeMetadata `json:"metadata"` // Numeric sums - report.NodeMetadata `json:"-"` // merged NodeMetadata of the nodes used to build this -} - -// RenderableNodes is a set of RenderableNodes -type RenderableNodes map[string]RenderableNode - -// Merge merges two sets of RenderableNodes -func (rns RenderableNodes) Merge(other RenderableNodes) { - for key, value := range other { - if existing, ok := rns[key]; ok { - existing.Merge(value) - rns[key] = existing - } else { - rns[key] = value - } - } -} - -// Merge merges in another RenderableNode -func (rn *RenderableNode) Merge(other RenderableNode) { - if rn.LabelMajor == "" { - rn.LabelMajor = other.LabelMajor - } - - if rn.LabelMinor == "" { - rn.LabelMinor = other.LabelMinor - } - - if rn.Rank == "" { - rn.Rank = other.Rank - } - - if rn.Pseudo != other.Pseudo { - panic(rn.ID) - } - - rn.Adjacency = rn.Adjacency.Merge(other.Adjacency) - rn.Origins = rn.Origins.Merge(other.Origins) - - rn.EdgeMetadata = rn.EdgeMetadata.Merge(other.EdgeMetadata) - rn.NodeMetadata = rn.NodeMetadata.Merge(other.NodeMetadata) + report.NodeMetadata } // NewRenderableNode makes a new RenderableNode -func NewRenderableNode(id, major, minor, rank string, nmd report.NodeMetadata) RenderableNode { +func NewRenderableNode(id string) RenderableNode { + return RenderableNode{ + ID: id, + LabelMajor: "", + LabelMinor: "", + Rank: "", + Pseudo: false, + Origins: report.MakeIDList(), + EdgeMetadata: report.EdgeMetadata{}, + NodeMetadata: report.MakeNodeMetadata(), + } +} + +// NewRenderableNodeWith makes a new RenderableNode with some fields filled in +func NewRenderableNodeWith(id, major, minor, rank string, rn RenderableNode) RenderableNode { return RenderableNode{ ID: id, LabelMajor: major, LabelMinor: minor, Rank: rank, Pseudo: false, - EdgeMetadata: report.EdgeMetadata{}, - NodeMetadata: nmd.Copy(), + Origins: rn.Origins.Copy(), + EdgeMetadata: rn.EdgeMetadata.Copy(), + NodeMetadata: rn.NodeMetadata.Copy(), } } -func newDerivedNode(id string, node RenderableNode) RenderableNode { +// NewDerivedNode create a renderable node based on node, but with a new ID +func NewDerivedNode(id string, node RenderableNode) RenderableNode { return RenderableNode{ ID: id, LabelMajor: "", @@ -82,19 +57,7 @@ func newDerivedNode(id string, node RenderableNode) RenderableNode { Pseudo: node.Pseudo, Origins: node.Origins.Copy(), EdgeMetadata: node.EdgeMetadata.Copy(), - NodeMetadata: report.MakeNodeMetadata(), - } -} - -func newPseudoNode(id, major, minor string) RenderableNode { - return RenderableNode{ - ID: id, - LabelMajor: major, - LabelMinor: minor, - Rank: "", - Pseudo: true, - EdgeMetadata: report.EdgeMetadata{}, - NodeMetadata: report.MakeNodeMetadata(), + NodeMetadata: node.NodeMetadata.Copy(), } } @@ -107,6 +70,73 @@ func newDerivedPseudoNode(id, major string, node RenderableNode) RenderableNode Pseudo: true, Origins: node.Origins.Copy(), EdgeMetadata: node.EdgeMetadata.Copy(), - NodeMetadata: report.MakeNodeMetadata(), + NodeMetadata: node.NodeMetadata.Copy(), } } + +// WithNodeMetadata creates a new RenderableNode based on rn, with n +func (rn RenderableNode) WithNodeMetadata(n report.NodeMetadata) RenderableNode { + result := rn.Copy() + result.NodeMetadata = result.NodeMetadata.Merge(n) + return result +} + +// Merge merges rn with other and returns a new RenderableNode +func (rn RenderableNode) Merge(other RenderableNode) RenderableNode { + result := rn.Copy() + + if result.LabelMajor == "" { + result.LabelMajor = other.LabelMajor + } + + if result.LabelMinor == "" { + result.LabelMinor = other.LabelMinor + } + + if result.Rank == "" { + result.Rank = other.Rank + } + + if result.Pseudo != other.Pseudo { + panic(result.ID) + } + + result.Origins = rn.Origins.Merge(other.Origins) + result.EdgeMetadata = rn.EdgeMetadata.Merge(other.EdgeMetadata) + result.NodeMetadata = rn.NodeMetadata.Merge(other.NodeMetadata) + + return result +} + +// Copy makes a deep copy of rn +func (rn RenderableNode) Copy() RenderableNode { + return RenderableNode{ + ID: rn.ID, + LabelMajor: rn.LabelMajor, + LabelMinor: rn.LabelMinor, + Rank: rn.Rank, + Pseudo: rn.Pseudo, + Origins: rn.Origins.Copy(), + EdgeMetadata: rn.EdgeMetadata.Copy(), + NodeMetadata: rn.NodeMetadata.Copy(), + } +} + +// RenderableNodes is a set of RenderableNodes +type RenderableNodes map[string]RenderableNode + +// Merge merges two sets of RenderableNodes, returning a new set. +func (rns RenderableNodes) Merge(other RenderableNodes) RenderableNodes { + result := RenderableNodes{} + for key, value := range rns { + result[key] = value + } + for key, value := range other { + existing, ok := result[key] + if ok { + value = value.Merge(existing) + } + result[key] = value + } + return result +} diff --git a/render/renderable_node_test.go b/render/renderable_node_test.go index bbc39c6d1..63b736747 100644 --- a/render/renderable_node_test.go +++ b/render/renderable_node_test.go @@ -12,42 +12,42 @@ import ( func TestMergeRenderableNodes(t *testing.T) { nodes1 := render.RenderableNodes{ - "foo": render.RenderableNode{ID: "foo"}, - "bar": render.RenderableNode{ID: "bar"}, + "foo": render.NewRenderableNode("foo"), + "bar": render.NewRenderableNode("bar"), } nodes2 := render.RenderableNodes{ - "bar": render.RenderableNode{ID: "bar"}, - "baz": render.RenderableNode{ID: "baz"}, + "bar": render.NewRenderableNode("bar"), + "baz": render.NewRenderableNode("baz"), } want := expected.Sterilize(render.RenderableNodes{ - "foo": render.RenderableNode{ID: "foo"}, - "bar": render.RenderableNode{ID: "bar"}, - "baz": render.RenderableNode{ID: "baz"}, + "foo": render.NewRenderableNode("foo"), + "bar": render.NewRenderableNode("bar"), + "baz": render.NewRenderableNode("baz"), }) - nodes1.Merge(nodes2) - if have := expected.Sterilize(nodes1); !reflect.DeepEqual(want, have) { + have := nodes1.Merge(nodes2) + if !reflect.DeepEqual(want, have) { t.Error(test.Diff(want, have)) } } func TestMergeRenderableNode(t *testing.T) { node1 := render.RenderableNode{ - ID: "foo", - LabelMajor: "", - LabelMinor: "minor", - Rank: "", - Pseudo: false, - Adjacency: report.MakeIDList("a1"), - Origins: report.MakeIDList("o1"), + ID: "foo", + LabelMajor: "", + LabelMinor: "minor", + Rank: "", + Pseudo: false, + NodeMetadata: report.MakeNodeMetadata().WithAdjacent("a1"), + Origins: report.MakeIDList("o1"), } node2 := render.RenderableNode{ - ID: "foo", - LabelMajor: "major", - LabelMinor: "", - Rank: "rank", - Pseudo: false, - Adjacency: report.MakeIDList("a2"), - Origins: report.MakeIDList("o2"), + ID: "foo", + LabelMajor: "major", + LabelMinor: "", + Rank: "rank", + Pseudo: false, + NodeMetadata: report.MakeNodeMetadata().WithAdjacent("a2"), + Origins: report.MakeIDList("o2"), } want := render.RenderableNode{ ID: "foo", @@ -55,13 +55,12 @@ func TestMergeRenderableNode(t *testing.T) { LabelMinor: "minor", Rank: "rank", Pseudo: false, - Adjacency: report.MakeIDList("a1", "a2"), + NodeMetadata: report.MakeNodeMetadata().WithAdjacency(report.MakeIDList("a1", "a2")), Origins: report.MakeIDList("o1", "o2"), - NodeMetadata: report.MakeNodeMetadata(), EdgeMetadata: report.EdgeMetadata{}, } - node1.Merge(node2) - if have := node1; !reflect.DeepEqual(want, have) { + have := node1.Merge(node2) + if !reflect.DeepEqual(want, have) { t.Error(test.Diff(want, have)) } } diff --git a/render/selectors.go b/render/selectors.go new file mode 100644 index 000000000..b3866744c --- /dev/null +++ b/render/selectors.go @@ -0,0 +1,89 @@ +package render + +import ( + "github.com/weaveworks/scope/report" +) + +// TopologySelector selects a single topology from a report. +// NB it is also a Renderer! +type TopologySelector func(r report.Report) RenderableNodes + +// Render implements Renderer +func (t TopologySelector) Render(r report.Report) RenderableNodes { + return t(r) +} + +// EdgeMetadata implements Renderer +func (t TopologySelector) EdgeMetadata(rpt report.Report, srcID, dstID string) report.EdgeMetadata { + var ( + nodes = t(rpt) + metadata = report.EdgeMetadata{} + ) + for src, node := range nodes { + for dst, edgeMeta := range node.Edges { + if src == srcID && dst == dstID { + return edgeMeta + } + } + } + return metadata +} + +// MakeRenderableNodes converts a topology to a set of RenderableNodes +func MakeRenderableNodes(t report.Topology) RenderableNodes { + result := RenderableNodes{} + for id, nmd := range t.NodeMetadatas { + rn := NewRenderableNode(id).WithNodeMetadata(nmd) + rn.Origins = report.MakeIDList(id) + if hostNodeID, ok := nmd.Metadata[report.HostNodeID]; ok { + rn.Origins = rn.Origins.Add(hostNodeID) + } + result[id] = rn + } + + // Push EdgeMetadata to both ends of the edges + for srcID, srcNode := range result { + for dstID, emd := range srcNode.Edges { + srcNode.EdgeMetadata = srcNode.EdgeMetadata.Flatten(emd) + + dstNode := result[dstID] + dstNode.EdgeMetadata = dstNode.EdgeMetadata.Flatten(emd) + result[dstID] = dstNode + } + + result[srcID] = srcNode + } + return result +} + +var ( + // SelectEndpoint selects the endpoint topology. + SelectEndpoint = TopologySelector(func(r report.Report) RenderableNodes { + return MakeRenderableNodes(r.Endpoint) + }) + + // SelectProcess selects the process topology. + SelectProcess = TopologySelector(func(r report.Report) RenderableNodes { + return MakeRenderableNodes(r.Process) + }) + + // SelectContainer selects the container topology. + SelectContainer = TopologySelector(func(r report.Report) RenderableNodes { + return MakeRenderableNodes(r.Container) + }) + + // SelectContainerImage selects the container image topology. + SelectContainerImage = TopologySelector(func(r report.Report) RenderableNodes { + return MakeRenderableNodes(r.ContainerImage) + }) + + // SelectAddress selects the address topology. + SelectAddress = TopologySelector(func(r report.Report) RenderableNodes { + return MakeRenderableNodes(r.Address) + }) + + // SelectHost selects the address topology. + SelectHost = TopologySelector(func(r report.Report) RenderableNodes { + return MakeRenderableNodes(r.Host) + }) +) diff --git a/render/topologies.go b/render/topologies.go index 7d7aab809..0cc0c31d3 100644 --- a/render/topologies.go +++ b/render/topologies.go @@ -9,9 +9,9 @@ import ( ) // EndpointRenderer is a Renderer which produces a renderable endpoint graph. -var EndpointRenderer = LeafMap{ - Selector: report.SelectEndpoint, - Mapper: MapEndpointIdentity, +var EndpointRenderer = Map{ + MapFunc: MapEndpointIdentity, + Renderer: SelectEndpoint, } // ProcessRenderer is a Renderer which produces a renderable process @@ -21,9 +21,9 @@ var ProcessRenderer = MakeReduce( MapFunc: MapEndpoint2Process, Renderer: EndpointRenderer, }, - LeafMap{ - Selector: report.SelectProcess, - Mapper: MapProcessIdentity, + Map{ + MapFunc: MapProcessIdentity, + Renderer: SelectProcess, }, ) @@ -35,9 +35,9 @@ type ProcessWithContainerNameRenderer struct{} // container name, if found. func (r ProcessWithContainerNameRenderer) Render(rpt report.Report) RenderableNodes { processes := ProcessRenderer.Render(rpt) - containers := LeafMap{ - Selector: report.SelectContainer, - Mapper: MapContainerIdentity, + containers := Map{ + MapFunc: MapContainerIdentity, + Renderer: SelectContainer, }.Render(rpt) for id, p := range processes { @@ -97,9 +97,9 @@ var ContainerRenderer = MakeReduce( }, }, - LeafMap{ - Selector: report.SelectContainer, - Mapper: MapContainerIdentity, + Map{ + MapFunc: MapContainerIdentity, + Renderer: SelectContainer, }, // This mapper brings in short lived connections by joining with container IPs. @@ -110,13 +110,13 @@ var ContainerRenderer = MakeReduce( MapFunc: MapIP2Container, Renderer: FilterUnconnected( MakeReduce( - LeafMap{ - Selector: report.SelectContainer, - Mapper: MapContainer2IP, + Map{ + MapFunc: MapContainer2IP, + Renderer: SelectContainer, }, - LeafMap{ - Selector: report.SelectEndpoint, - Mapper: MapEndpoint2IP, + Map{ + MapFunc: MapEndpoint2IP, + Renderer: SelectEndpoint, }, ), ), @@ -134,9 +134,9 @@ var ContainerImageRenderer = Map{ MapFunc: MapContainer2ContainerImage, Renderer: ContainerRenderer, }, - LeafMap{ - Selector: report.SelectContainerImage, - Mapper: MapContainerImageIdentity, + Map{ + MapFunc: MapContainerImageIdentity, + Renderer: SelectContainerImage, }, ), }, @@ -144,9 +144,9 @@ var ContainerImageRenderer = Map{ // AddressRenderer is a Renderer which produces a renderable address // graph from the address topology. -var AddressRenderer = LeafMap{ - Selector: report.SelectAddress, - Mapper: MapAddressIdentity, +var AddressRenderer = Map{ + MapFunc: MapAddressIdentity, + Renderer: SelectAddress, } // HostRenderer is a Renderer which produces a renderable host @@ -156,8 +156,8 @@ var HostRenderer = MakeReduce( MapFunc: MapAddress2Host, Renderer: AddressRenderer, }, - LeafMap{ - Selector: report.SelectHost, - Mapper: MapHostIdentity, + Map{ + MapFunc: MapHostIdentity, + Renderer: SelectHost, }, ) diff --git a/render/topology_diff_test.go b/render/topology_diff_test.go index cd22e95c1..6760b5d52 100644 --- a/render/topology_diff_test.go +++ b/render/topology_diff_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/weaveworks/scope/render" + "github.com/weaveworks/scope/report" "github.com/weaveworks/scope/test" ) @@ -18,13 +19,11 @@ func (r ByID) Less(i, j int) bool { return r[i].ID < r[j].ID } func TestTopoDiff(t *testing.T) { nodea := render.RenderableNode{ - ID: "nodea", - LabelMajor: "Node A", - LabelMinor: "'ts an a", - Pseudo: false, - Adjacency: []string{ - "nodeb", - }, + ID: "nodea", + LabelMajor: "Node A", + LabelMinor: "'ts an a", + Pseudo: false, + NodeMetadata: report.MakeNodeMetadata().WithAdjacent("nodeb"), } nodeap := nodea nodeap.Adjacency = []string{ diff --git a/report/report.go b/report/report.go index 99716a634..8368b5bac 100644 --- a/report/report.go +++ b/report/report.go @@ -164,36 +164,3 @@ const ( // the node was originally detected. HostNodeID = "host_node_id" ) - -// TopologySelector selects a single topology from a report. -type TopologySelector func(r Report) Topology - -// SelectEndpoint selects the endpoint topology. -func SelectEndpoint(r Report) Topology { - return r.Endpoint -} - -// SelectProcess selects the process topology. -func SelectProcess(r Report) Topology { - return r.Process -} - -// SelectContainer selects the container topology. -func SelectContainer(r Report) Topology { - return r.Container -} - -// SelectContainerImage selects the container image topology. -func SelectContainerImage(r Report) Topology { - return r.ContainerImage -} - -// SelectAddress selects the address topology. -func SelectAddress(r Report) Topology { - return r.Address -} - -// SelectHost selects the address topology. -func SelectHost(r Report) Topology { - return r.Host -} diff --git a/report/topology.go b/report/topology.go index c30fd49d0..bf0691c65 100644 --- a/report/topology.go +++ b/report/topology.go @@ -74,10 +74,10 @@ func (n NodeMetadatas) Merge(other NodeMetadatas) NodeMetadatas { // NodeMetadata describes a superset of the metadata that probes can collect // about a given node in a given topology. type NodeMetadata struct { - Metadata - Counters - Adjacency IDList - Edges EdgeMetadatas + Metadata `json:"-"` + Counters `json:"-"` + Adjacency IDList `json:"adjacency"` + Edges EdgeMetadatas `json:"-"` } // MakeNodeMetadata creates a new NodeMetadata with no initial metadata. @@ -148,7 +148,7 @@ func (n NodeMetadata) Merge(other NodeMetadata) NodeMetadata { cp.Metadata = cp.Metadata.Merge(other.Metadata) cp.Counters = cp.Counters.Merge(other.Counters) cp.Adjacency = cp.Adjacency.Merge(other.Adjacency) - cp.Edges = cp.Edges.Merge(n.Edges) + cp.Edges = cp.Edges.Merge(other.Edges) return cp } @@ -220,6 +220,16 @@ func (e EdgeMetadatas) Merge(other EdgeMetadatas) EdgeMetadatas { return cp } +// Flatten flattens all the EdgeMetadatas in this set and returns the result. +// The original is not modified. +func (e EdgeMetadatas) Flatten() EdgeMetadata { + result := EdgeMetadata{} + for _, v := range e { + result = result.Flatten(v) + } + return result +} + // EdgeMetadata describes a superset of the metadata that probes can possibly // collect about a directed edge between two nodes in any topology. type EdgeMetadata struct {