func TestAdjacencyID(t *testing.T) { for _, bad := range []string{ client54001EndpointNodeID, client54002EndpointNodeID, unknown1EndpointNodeID, unknown2EndpointNodeID, unknown3EndpointNodeID, clientAddressNodeID, serverAddressNodeID, unknownAddressNodeID, clientHostNodeID, serverHostNodeID, ";", "", } { if srcNodeID, ok := report.ParseAdjacencyID(bad); ok { t.Errorf("%q: expected failure, but got (%q)", bad, srcNodeID) } } for input, want := range map[string]struct{ srcNodeID string }{ report.MakeAdjacencyID(report.MakeEndpointNodeID("a", "b", "c")): {report.MakeEndpointNodeID("a", "b", "c")}, report.MakeAdjacencyID(report.MakeAddressNodeID("a", "b")): {report.MakeAddressNodeID("a", "b")}, report.MakeAdjacencyID(report.MakeProcessNodeID("a", "b")): {report.MakeProcessNodeID("a", "b")}, report.MakeAdjacencyID(report.MakeHostNodeID("a")): {report.MakeHostNodeID("a")}, ">host.com;1.2.3.4": {"host.com;1.2.3.4"}, ">a;b;c": {"a;b;c"}, ">a;b": {"a;b"}, ">a;": {"a;"}, ">;b": {";b"}, ">;": {";"}, } { srcNodeID, ok := report.ParseAdjacencyID(input) if !ok { t.Errorf("%q: not OK", input) continue } if want, have := want.srcNodeID, srcNodeID; want != have { t.Errorf("%q: want %q, have %q", input, want, have) } } }
func connectionDetailsRows(topology report.Topology, originID string) []Row { rows := []Row{} labeler := func(nodeID string) (string, bool) { if _, addr, port, ok := report.ParseEndpointNodeID(nodeID); ok { return fmt.Sprintf("%s:%s", addr, port), true } if _, addr, ok := report.ParseAddressNodeID(nodeID); ok { return addr, true } return "", false } local, ok := labeler(originID) if !ok { return rows } // Firstly, collection outgoing connections from this node. originAdjID := report.MakeAdjacencyID(originID) for _, serverNodeID := range topology.Adjacency[originAdjID] { remote, ok := labeler(serverNodeID) if !ok { continue } rows = append(rows, Row{ Key: local, ValueMajor: remote, Expandable: true, }) } // Next, scan the topology for incoming connections to this node. for clientAdjID, serverNodeIDs := range topology.Adjacency { if clientAdjID == originAdjID { continue } if !serverNodeIDs.Contains(originID) { continue } clientNodeID, ok := report.ParseAdjacencyID(clientAdjID) if !ok { continue } remote, ok := labeler(clientNodeID) if !ok { continue } rows = append(rows, Row{ Key: remote, ValueMajor: local, Expandable: true, }) } return rows }
// 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{} 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. source2mapped := map[string]string{} // source node ID -> mapped node ID for nodeID, metadata := range t.NodeMetadatas { mapped, ok := m.Mapper(metadata) if !ok { continue } // 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) origins = origins.Add(metadata[report.HostNodeID]) mapped.Origins = origins nodes[mapped.ID] = mapped source2mapped[nodeID] = mapped.ID } // Walk the graph and make connections. for src, dsts := range t.Adjacency { var ( srcNodeID, ok = report.ParseAdjacencyID(src) srcRenderableID = source2mapped[srcNodeID] // must exist srcRenderableNode = nodes[srcRenderableID] // must exist ) if !ok { log.Printf("bad adjacency ID %q", src) continue } for _, dstNodeID := range dsts { dstRenderableID, ok := source2mapped[dstNodeID] if !ok { pseudoNode, ok := m.Pseudo(srcNodeID, srcRenderableNode, dstNodeID, localNetworks) if !ok { continue } dstRenderableID = pseudoNode.ID nodes[dstRenderableID] = pseudoNode source2mapped[dstNodeID] = dstRenderableID } srcRenderableNode.Adjacency = srcRenderableNode.Adjacency.Add(dstRenderableID) srcRenderableNode.Origins = srcRenderableNode.Origins.Add(srcNodeID) edgeID := report.MakeEdgeID(srcNodeID, dstNodeID) if md, ok := t.EdgeMetadatas[edgeID]; ok { srcRenderableNode.AggregateMetadata.Merge(AggregateMetadataOf(md)) } } nodes[srcRenderableID] = srcRenderableNode } return nodes }
// 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{} 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. source2mapped := map[string]report.IDList{} // source node ID -> mapped node IDs for nodeID, metadata := range t.NodeMetadatas { for _, mapped := range m.Mapper(metadata) { // 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) origins = origins.Add(metadata.Metadata[report.HostNodeID]) mapped.Origins = origins nodes[mapped.ID] = mapped source2mapped[nodeID] = source2mapped[nodeID].Add(mapped.ID) } } mkPseudoNode := func(srcNodeID, dstNodeID string, srcIsClient bool) report.IDList { pseudoNode, ok := m.Pseudo(srcNodeID, dstNodeID, srcIsClient, localNetworks) if !ok { return report.MakeIDList() } pseudoNode.Origins = pseudoNode.Origins.Add(srcNodeID) existing, ok := nodes[pseudoNode.ID] if ok { pseudoNode.Merge(existing) } nodes[pseudoNode.ID] = pseudoNode source2mapped[pseudoNode.ID] = source2mapped[pseudoNode.ID].Add(srcNodeID) return report.MakeIDList(pseudoNode.ID) } // Walk the graph and make connections. for src, dsts := range t.Adjacency { srcNodeID, ok := report.ParseAdjacencyID(src) if !ok { log.Printf("bad adjacency ID %q", src) continue } srcRenderableIDs, ok := source2mapped[srcNodeID] if !ok { // 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 { srcRenderableIDs = mkPseudoNode(srcNodeID, dstNodeID, true) break } } } if len(srcRenderableIDs) == 0 { continue } for _, srcRenderableID := range srcRenderableIDs { srcRenderableNode := nodes[srcRenderableID] for _, dstNodeID := range dsts { dstRenderableIDs, ok := source2mapped[dstNodeID] if !ok { dstRenderableIDs = mkPseudoNode(dstNodeID, srcNodeID, false) } if len(dstRenderableIDs) == 0 { continue } for _, dstRenderableID := range dstRenderableIDs { dstRenderableNode := nodes[dstRenderableID] srcRenderableNode.Adjacency = srcRenderableNode.Adjacency.Add(dstRenderableID) // 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. if md, ok := t.EdgeMetadatas[report.MakeEdgeID(srcNodeID, dstNodeID)]; ok { srcRenderableNode.EdgeMetadata = srcRenderableNode.EdgeMetadata.Merge(md) dstRenderableNode.EdgeMetadata = dstRenderableNode.EdgeMetadata.Merge(md) nodes[dstRenderableID] = dstRenderableNode } } } nodes[srcRenderableID] = srcRenderableNode } } return nodes }