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.
This commit is contained in:
Matthias Radestock
2018-01-06 16:23:53 +00:00
parent 53793717d5
commit fd5fa7a962
5 changed files with 95 additions and 90 deletions

View File

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

49
render/endpoint.go Normal file
View File

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

View File

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

View File

@@ -61,7 +61,7 @@ var PodRenderer = Memoise(ConditionalRenderer(renderKubernetesTopologies,
),
),
),
ConnectionJoin(MapPod2IP, SelectPod),
ConnectionJoin(MapPod2IP, report.Pod),
),
),
))

View File

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