diff --git a/render/mapping.go b/render/mapping.go index 153c33743..c92243a87 100644 --- a/render/mapping.go +++ b/render/mapping.go @@ -168,6 +168,78 @@ func MapHostIdentity(m report.NodeMetadata) RenderableNodes { return RenderableNodes{id: NewRenderableNode(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) RenderableNodes { + _, ok := m.Metadata[process.PID] + if ok { + return RenderableNodes{} + } + addr, ok := m.Metadata[endpoint.Addr] + if !ok { + return RenderableNodes{} + } + return RenderableNodes{addr: NewRenderableNode(addr, "", "", "", m)} +} + +// IPPseudoNode maps endpoint pseudo nodes to regular IP address nodes, or +// the internet node. +func IPPseudoNode(srcNodeID, _ string, _ bool, local report.Networks) (RenderableNode, bool) { + // Use the addresser to extract the IP of the missing node + srcNodeAddr := report.EndpointIDAddresser(srcNodeID) + // If the dstNodeAddr is not in a network local to this report, we emit an + // internet node + if !local.Contains(srcNodeAddr) { + return newPseudoNode(TheInternetID, TheInternetMajor, ""), true + } + return NewRenderableNode(srcNodeAddr.String(), "", "", "", report.MakeNodeMetadata()), true +} + +// 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) RenderableNodes { + result := RenderableNodes{} + addrs, ok := m.Metadata[docker.ContainerIPs] + if !ok { + return result + } + for _, addr := range strings.Fields(addrs) { + n := NewRenderableNode(addr, "", "", "", m) + n.NodeMetadata.Counters[containersKey] = 1 + result[addr] = n + } + return result +} + +// 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 { + // 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 { + return RenderableNodes{} + } + + // Propogate the internet pseudo node. + if n.ID == TheInternetID { + return RenderableNodes{n.ID: n} + } + + // If this node is not a container, exclude it. + // This excludes all the nodes we've dragged in from endpoint + // that we failed to join to a container. + id, ok := n.NodeMetadata.Metadata[docker.ContainerID] + if !ok { + return RenderableNodes{} + } + + return RenderableNodes{id: newDerivedNode(id, n)} +} + // MapEndpoint2Process maps endpoint RenderableNodes to process // RenderableNodes. // diff --git a/render/render.go b/render/render.go index e0f5a5b1f..d1db1ed9c 100644 --- a/render/render.go +++ b/render/render.go @@ -195,16 +195,14 @@ func (m LeafMap) Render(rpt report.Report) RenderableNodes { srcRenderableIDs, ok := source2mapped[srcNodeID] if !ok { - // One of the entries in dsts must be a non-pseudo node - var existingDstNodeID string + // One of the entries in dsts must be a non-pseudo node, unless + // it was dropped by the mapping function. for _, dstNodeID := range dsts { if _, ok := source2mapped[dstNodeID]; ok { - existingDstNodeID = dstNodeID + srcRenderableIDs = mkPseudoNode(srcNodeID, dstNodeID, true) break } } - - srcRenderableIDs = mkPseudoNode(srcNodeID, existingDstNodeID, true) } if len(srcRenderableIDs) == 0 { continue diff --git a/render/topologies.go b/render/topologies.go index f078dfeea..99c83f108 100644 --- a/render/topologies.go +++ b/render/topologies.go @@ -99,11 +99,34 @@ var ContainerRenderer = MakeReduce( }, }, }, + LeafMap{ Selector: report.SelectContainer, Mapper: MapContainerIdentity, Pseudo: PanicPseudoNode, }, + + // This mapper brings in short lived connections by joining with container IPs. + // We need to be careful to ensure we only include each edge once. Edges brought in + // by the above renders will have a pid, so its enough to filter out any nodes with + // pids. + Map{ + MapFunc: MapIP2Container, + Renderer: FilterUnconnected( + MakeReduce( + LeafMap{ + Selector: report.SelectContainer, + Mapper: MapContainer2IP, + Pseudo: PanicPseudoNode, + }, + LeafMap{ + Selector: report.SelectEndpoint, + Mapper: MapEndpoint2IP, + Pseudo: IPPseudoNode, + }, + ), + ), + }, ) // ContainerImageRenderer is a Renderer which produces a renderable container