Merge pull request #425 from weaveworks/357-no-more-leaf-map

Remove LeafMap - have a generic function which converts a Topology to RenderableNodes
This commit is contained in:
Tom Wilkie
2015-09-03 11:29:03 +01:00
12 changed files with 416 additions and 467 deletions

View File

@@ -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),
},

View File

@@ -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-<id> 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

View File

@@ -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())

View File

@@ -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 {

View File

@@ -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) {

View File

@@ -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
}

View File

@@ -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))
}
}

89
render/selectors.go Normal file
View File

@@ -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)
})
)

View File

@@ -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,
},
)

View File

@@ -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{

View File

@@ -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
}

View File

@@ -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 {