From fd5fa7a96263176f256f85cb3dd7d382f924cd18 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Sat, 6 Jan 2018 16:23:53 +0000 Subject: [PATCH] refactor: extract common code in endpoint mapping This isn't quite as neat as we'd want to make it, because two of the three call sites still require a closure, but it's still an improvement. Note that every instance of MapEndpoints only ever maps to one topology, which, furthermore, is a core report topology. Hence we can just parameterise MapEndpoints with that topology, and then the map functions can return just the node ids. --- render/container.go | 67 +++++++++++++++++++-------------------------- render/endpoint.go | 49 +++++++++++++++++++++++++++++++++ render/host.go | 27 ++++-------------- render/pod.go | 2 +- render/process.go | 40 ++++++++------------------- 5 files changed, 95 insertions(+), 90 deletions(-) create mode 100644 render/endpoint.go diff --git a/render/container.go b/render/container.go index 71b889d85..2256b0582 100644 --- a/render/container.go +++ b/render/container.go @@ -35,32 +35,29 @@ var ContainerRenderer = Memoise(MakeFilter( MapProcess2Container, ProcessRenderer, ), - ConnectionJoin(MapContainer2IP, SelectContainer), + ConnectionJoin(MapContainer2IP, report.Container), ), )) const originalNodeID = "original_node_id" -// ConnectionJoin joins the given renderer with connections from the +// ConnectionJoin joins the given topology with connections from the // endpoints topology, using the toIPs function to extract IPs from // the nodes. -func ConnectionJoin(toIPs func(report.Node) []string, r Renderer) Renderer { - return connectionJoin{toIPs: toIPs, r: r} +func ConnectionJoin(toIPs func(report.Node) []string, topology string) Renderer { + return connectionJoin{toIPs: toIPs, topology: topology} } type connectionJoin struct { - toIPs func(report.Node) []string - r Renderer + toIPs func(report.Node) []string + topology string } func (c connectionJoin) Render(rpt report.Report) Nodes { - local := LocalNetworks(rpt) - inputNodes := c.r.Render(rpt) - endpoints := SelectEndpoint.Render(rpt) - + inputNodes := TopologySelector(c.topology).Render(rpt).Nodes // Collect all the IPs we are trying to map to, and which ID they map from var ipNodes = map[string]string{} - for _, n := range inputNodes.Nodes { + for _, n := range inputNodes { for _, ip := range c.toIPs(n) { if _, exists := ipNodes[ip]; exists { // If an IP is shared between multiple nodes, we can't reliably @@ -71,35 +68,27 @@ func (c connectionJoin) Render(rpt report.Report) Nodes { } } } - ret := newJoinResults(inputNodes.Nodes) - - // Now look at all the endpoints and see which map to IP nodes - for _, m := range endpoints.Nodes { - scope, addr, port, ok := report.ParseEndpointNodeID(m.ID) - if !ok { - continue - } - // Nodes without a hostid may be pseudo nodes - if so, pass through to result - if _, ok := m.Latest.Lookup(report.HostNodeID); !ok { - if id, ok := externalNodeID(m, addr, local); ok { - ret.addChild(m, id, Pseudo) - continue + return MapEndpoints( + func(m report.Node) string { + scope, addr, port, ok := report.ParseEndpointNodeID(m.ID) + if !ok { + return "" } - } - id, found := ipNodes[report.MakeScopedEndpointNodeID(scope, addr, "")] - // We also allow for joining on ip:port pairs. This is useful for - // connections to the host IPs which have been port mapped to a - // container can only be unambiguously identified with the port. - if !found { - id, found = ipNodes[report.MakeScopedEndpointNodeID(scope, addr, port)] - } - if found && id != "" { // not one we blanked out earlier - // We are guaranteed to find the id, so really this should - // never end up creating a node. - ret.addChild(m, id, report.Container) - } - } - return ret.result(endpoints) + id, found := ipNodes[report.MakeScopedEndpointNodeID(scope, addr, "")] + // We also allow for joining on ip:port pairs. This is + // useful for connections to the host IPs which have been + // port mapped to a container can only be unambiguously + // identified with the port. + if !found { + id, found = ipNodes[report.MakeScopedEndpointNodeID(scope, addr, port)] + } + if found && id != "" { // not one we blanked out earlier + // We are guaranteed to find the id, so really this + // should never end up creating a node. + return id + } + return "" + }, c.topology).Render(rpt) } // FilterEmpty is a Renderer which filters out nodes which have no children diff --git a/render/endpoint.go b/render/endpoint.go new file mode 100644 index 000000000..113ab7eaa --- /dev/null +++ b/render/endpoint.go @@ -0,0 +1,49 @@ +package render + +import ( + "github.com/weaveworks/scope/report" +) + +// Pseudo is the topology for nodes that aren't "real" nodes inside a +// cluster, such as nodes representing the internet, external +// services, and artificial grouping such as "uncontained processes" +// and "unmanaged containers". +const Pseudo = "pseudo" + +// EndpointRenderer is a Renderer which produces a renderable endpoint graph. +var EndpointRenderer = SelectEndpoint + +type endpointMapFunc func(report.Node) string + +type mapEndpoints struct { + f endpointMapFunc + topology string +} + +// MapEndpoints creates a renderer for the endpoint topology. Each +// endpoint is either turned into a pseudo node, or mapped to a node +// in the specified topology by the supplied function. +func MapEndpoints(f endpointMapFunc, topology string) Renderer { + return mapEndpoints{f: f, topology: topology} +} + +func (e mapEndpoints) Render(rpt report.Report) Nodes { + local := LocalNetworks(rpt) + endpoints := SelectEndpoint.Render(rpt) + ret := newJoinResults(TopologySelector(e.topology).Render(rpt).Nodes) + + for _, n := range endpoints.Nodes { + // Nodes without a hostid are mapped to pseudo nodes, if + // possible. + if _, ok := n.Latest.Lookup(report.HostNodeID); !ok { + if id, ok := pseudoNodeID(n, local); ok { + ret.addChild(n, id, Pseudo) + continue + } + } + if id := e.f(n); id != "" { + ret.addChild(n, id, e.topology) + } + } + return ret.result(endpoints) +} diff --git a/render/host.go b/render/host.go index f368ff181..26a4c5cc7 100644 --- a/render/host.go +++ b/render/host.go @@ -13,7 +13,7 @@ var HostRenderer = MakeReduce( CustomRenderer{RenderFunc: nodes2Hosts, Renderer: ContainerRenderer}, CustomRenderer{RenderFunc: nodes2Hosts, Renderer: ContainerImageRenderer}, CustomRenderer{RenderFunc: nodes2Hosts, Renderer: PodRenderer}, - endpoints2Hosts{}, + MapEndpoints(endpoint2Host, report.Host), ) // nodes2Hosts maps any Nodes to host Nodes. @@ -50,26 +50,9 @@ func nodes2Hosts(nodes Nodes) Nodes { return ret.result(nodes) } -// endpoints2Hosts takes nodes from the endpoint topology and produces -// host nodes or pseudo nodes. -type endpoints2Hosts struct { -} - -func (e endpoints2Hosts) Render(rpt report.Report) Nodes { - local := LocalNetworks(rpt) - hosts := SelectHost.Render(rpt) - endpoints := SelectEndpoint.Render(rpt) - ret := newJoinResults(hosts.Nodes) - - for _, n := range endpoints.Nodes { - // Nodes without a hostid are treated as pseudo nodes - if hostNodeID, ok := n.Latest.Lookup(report.HostNodeID); !ok { - if id, ok := pseudoNodeID(n, local); ok { - ret.addChild(n, id, Pseudo) - } - } else { - ret.addChild(n, hostNodeID, report.Host) - } +func endpoint2Host(n report.Node) string { + if hostNodeID, ok := n.Latest.Lookup(report.HostNodeID); ok { + return hostNodeID } - return ret.result(endpoints) + return "" } diff --git a/render/pod.go b/render/pod.go index 642903a8b..0651ca141 100644 --- a/render/pod.go +++ b/render/pod.go @@ -61,7 +61,7 @@ var PodRenderer = Memoise(ConditionalRenderer(renderKubernetesTopologies, ), ), ), - ConnectionJoin(MapPod2IP, SelectPod), + ConnectionJoin(MapPod2IP, report.Pod), ), ), )) diff --git a/render/process.go b/render/process.go index 0ef12832f..3648d986b 100644 --- a/render/process.go +++ b/render/process.go @@ -13,18 +13,12 @@ const ( OutboundMajor = "The Internet" InboundMinor = "Inbound connections" OutboundMinor = "Outbound connections" - - // Topology for pseudo-nodes and IPs so we can differentiate them at the end - Pseudo = "pseudo" ) func renderProcesses(rpt report.Report) bool { return len(rpt.Process.Nodes) >= 1 } -// EndpointRenderer is a Renderer which produces a renderable endpoint graph. -var EndpointRenderer = SelectEndpoint - // ProcessRenderer is a Renderer which produces a renderable process // graph by merging the endpoint graph and the process topology. It // also colors connected nodes, so we can apply a filter to show/hide @@ -79,32 +73,22 @@ func (e endpoints2Processes) Render(rpt report.Report) Nodes { if len(rpt.Process.Nodes) == 0 { return Nodes{} } - local := LocalNetworks(rpt) - processes := SelectProcess.Render(rpt) - endpoints := SelectEndpoint.Render(rpt) - ret := newJoinResults(processes.Nodes) - - for _, n := range endpoints.Nodes { - // Nodes without a hostid are treated as pseudo nodes - if hostNodeID, ok := n.Latest.Lookup(report.HostNodeID); !ok { - if id, ok := pseudoNodeID(n, local); ok { - ret.addChild(n, id, Pseudo) - } - } else { + endpoints := SelectEndpoint.Render(rpt).Nodes + return MapEndpoints( + func(n report.Node) string { pid, ok := n.Latest.Lookup(process.PID) if !ok { - continue + return "" } - if hasMoreThanOneConnection(n, endpoints.Nodes) { - continue + if hasMoreThanOneConnection(n, endpoints) { + return "" } - - hostID, _ := report.ParseHostNodeID(hostNodeID) - id := report.MakeProcessNodeID(hostID, pid) - ret.addChild(n, id, report.Process) - } - } - return ret.result(endpoints) + hostID := report.ExtractHostID(n) + if hostID == "" { + return "" + } + return report.MakeProcessNodeID(hostID, pid) + }, report.Process).Render(rpt) } // When there is more than one connection originating from a source