diff --git a/test/case/ietf_interfaces/vlan_ping.py b/test/case/ietf_interfaces/vlan_ping.py index 555f47f5..635a4877 100755 --- a/test/case/ietf_interfaces/vlan_ping.py +++ b/test/case/ietf_interfaces/vlan_ping.py @@ -8,7 +8,7 @@ with infamy.Test() as test: target = env.attach("target", "mgmt") with test.step("Configure VLAN 10 on target:data with IP 10.0.0.2"): - _, tport = env.xlate("target", "data") + _, tport = env.ltop.xlate("target", "data") target.put_config_dict("ietf-interfaces", { "interfaces": { @@ -45,7 +45,7 @@ with infamy.Test() as test: }) with test.step("Ping 10.0.0.2 from VLAN 10 on host:data with IP 10.0.0.1"): - _, hport = env.xlate("host", "data") + _, hport = env.ltop.xlate("host", "data") with infamy.IsolatedMacVlan(hport) as ns: pingtest = ns.runsh(""" diff --git a/test/infamy/env.py b/test/infamy/env.py index 2605d769..90e5d990 100644 --- a/test/infamy/env.py +++ b/test/infamy/env.py @@ -4,7 +4,6 @@ import os import pydot import shlex import sys -import time from . import neigh, netconf, tap, topology @@ -26,72 +25,29 @@ class Env(object): self.args = ArgumentParser(ltop).parse_args(argv) pdot = pydot.graph_from_dot_file(self.args.ptop[0])[0] - self.ptop = networkx.nx_pydot.read_dot(self.args.ptop[0]) + self.ptop = topology.Topology(pdot) if self.args.ltop: - self.ltop = networkx.nx_pydot.read_dot(self.args.ltop) ldot = pydot.graph_from_dot_file(self.args.ltop)[0] - mapping = topology.find_mapping(pdot, ldot) - if not mapping: + self.ltop = topology.Topology(ldot) + if not self.ltop.map_to(self.ptop): raise tap.TestSkip() - self.mapping = {} - for (log, phy) in mapping.items(): - if ":" in log: - lnode, lport = log.split(":") - pnode, pport = phy.split(":") - - self._map_node(lnode, pnode) - self._map_port((lnode, lport), (pnode, pport)) - else: - self._map_node(log, phy) - - def _map_node(self, lnode, pnode): - if lnode in self.mapping: - assert(self.mapping[lnode][None] == pnode) - else: - self.mapping[lnode] = { None: pnode } - - def _map_port(self, log, phy): - (lnode, lport) = log - (pnode, pport) = phy - - if lport in self.mapping[lnode]: - assert(self.mapping[lnode][lport] == pport) - else: - self.mapping[lnode][lport] = pport - - def xlate(self, lnode, lport=None): - if lnode not in self.mapping: - return None - - nodemap = self.mapping[lnode] - - if lport not in nodemap: - return None - - if not lport: - return nodemap[None] - - return (nodemap[None], nodemap[lport]) - - def attach(self, node, port): - if self.mapping: - mapping = self.mapping[node] - node, port = mapping[None], mapping[port] + if self.ltop: + mapping = self.ltop.mapping[node] + node, port = self.ltop.xlate(node, port) else: mapping = None - hostport = list(self.ptop.neighbors(f"{node}:{port}"))[0] - hnode, hport = hostport.split(":") + ctrl = self.ptop.get_ctrl() + _, cport = self.ptop.get_path(ctrl, (node, port))[0] - print(f"Probing {node} on port {hport} for IPv6LL mgmt address ...") - mgmtip = neigh.ll6ping(hport) + print(f"Probing {node} on port {cport} for IPv6LL mgmt address ...") + mgmtip = neigh.ll6ping(cport) if not mgmtip: raise Exception(f"Failed, cannot find mgmt IP for {node}") - time.sleep(10) return netconf.Device( location=netconf.Location(mgmtip), mapping=mapping, diff --git a/test/infamy/topology.py b/test/infamy/topology.py index 1814af06..e6c42ad9 100644 --- a/test/infamy/topology.py +++ b/test/infamy/topology.py @@ -6,51 +6,122 @@ def qstrip(text): return text[1:-1] return text -def match_kind(n1, n2): - return qstrip(n1["kind"]) == qstrip(n2["kind"]) +def match_kind(n1attrs, n2attrs): + return n1attrs.get("kind") == n2attrs.get("kind") -def find_mapping(phy, log, node_match=match_kind): - def annotate(nxg, dotg): - for e in list(nxg.edges): +class Topology: + def __init__(self, dotg): + self.dotg = dotg + edges = { tuple(e.get_source().split(":")): { tuple(e.get_destination().split(":")): { "weight": 1 } } \ + for e in self.dotg.get_edges() \ + } + self.g = nx.Graph(edges, weight=1) + for e in list(self.g.edges): s, d = e - nxg.nodes[s]["kind"] = "port" - nxg.nodes[d]["kind"] = "port" + self.g.nodes[s]["kind"] = "port" + self.g.nodes[d]["kind"] = "port" - sn, sp = s.split(":") - dn, dp = d.split(":") + sn, sp = s + dn, dp = d try: - sk = dotg.get_node(sn)[0].get_attributes()["kind"] + sk = qstrip(self.dotg.get_node(sn)[0].get_attributes()["kind"]) except: raise ValueError("\"{}\"'s kind is not known".format(sn)) try: - dk = dotg.get_node(dn)[0].get_attributes()["kind"] + dk = qstrip(self.dotg.get_node(dn)[0].get_attributes()["kind"]) except: raise ValueError("\"{}\"'s kind is not known".format(dn)) - nxg.add_node(sn, kind=sk) - nxg.add_edge(sn, s) - nxg.add_node(dn, kind=dk) - nxg.add_edge(dn, d) + self.g.add_node(sn, kind=sk) + self.g.add_edge(sn, s, weight=0) + self.g.add_node(dn, kind=dk) + self.g.add_edge(dn, d, weight=0) - phyedges = [(e.get_source(), e.get_destination()) for e in phy.get_edges()] - logedges = [(e.get_source(), e.get_destination()) for e in log.get_edges()] + def map_to(self, phy, node_match=match_kind): + def _map_node(lnode, pnode): + if lnode in self.mapping: + assert(self.mapping[lnode][None] == pnode) + else: + self.mapping[lnode] = { None: pnode } - phyedges.sort() - logedges.sort() - nxphy = nx.Graph(phyedges) - nxlog = nx.Graph(logedges) - annotate(nxphy, phy) - annotate(nxlog, log) + def _map_port(log, phy): + (lnode, lport) = log + (pnode, pport) = phy - nxmap = isomorphism.GraphMatcher(nxphy, nxlog, node_match=node_match) - if nxmap.subgraph_is_isomorphic(): - return { v: k for (k, v) in nxmap.mapping.items() } + if lport in self.mapping[lnode]: + assert(self.mapping[lnode][lport] == pport) + else: + self.mapping[lnode][lport] = pport - return None + nxmap = isomorphism.GraphMatcher(phy.g, self.g, node_match=node_match) + if not nxmap.subgraph_is_isomorphic(): + return False -# This let's us call this script like so... + self.phy = phy + self.mapping = {} + for (phy, log) in nxmap.mapping.items(): + if isinstance(log, tuple): + lnode, lport = log + pnode, pport = phy + + _map_node(lnode, pnode) + _map_port((lnode, lport), (pnode, pport)) + else: + _map_node(log, phy) + + return True + + def xlate(self, lnode, lport=None): + assert(self.mapping) + + if lnode not in self.mapping: + return None + + nodemap = self.mapping[lnode] + + if lport not in nodemap: + return None + + if not lport: + return nodemap[None] + + return (nodemap[None], nodemap[lport]) + + def get_nodes(self, flt): + out = [] + for name in self.g.nodes: + if flt(name, self.g.nodes[name]): + out.append(name) + + return out + + def get_ports(self, node): + ports = self.get_nodes(lambda name, _: name.startswith(f"{node}:")) + return { p.removeprefix(f"{node}:") for p in ports } + + def get_path(self, src, dst): + path = nx.shortest_path(self.g, src, dst) + return path[1:-1] if path else None + + def get_paths(self, src, dst): + paths = nx.all_shortest_paths(self.g, src, dst) + if not paths: + return None + + return map(lambda path: path[1:-1], paths) + + def get_ctrl(self): + ns = self.get_nodes(lambda _, attrs: attrs.get("kind") == "controller") + assert(len(ns) == 1) + return ns[0] + + def get_infixen(self): + return self.get_nodes(lambda _, attrs: attrs.get("kind") == "infix") + + +# Support calling this script like so... # # python3 topology.py # @@ -61,8 +132,12 @@ if __name__ == "__main__": import pydot import sys - pdot = pydot.graph_from_dot_file(sys.argv[1])[0] - ldot = pydot.graph_from_dot_file(sys.argv[2])[0] - mapping = find_mapping(pdot, ldot) + phy = Topology(pydot.graph_from_dot_file(sys.argv[1])[0]) + log = Topology(pydot.graph_from_dot_file(sys.argv[2])[0]) + if log.map_to(phy): + print(json.dumps(log.mapping)) + print(json.dumps(tuple(log.get_paths("host", "target")))) + sys.exit(0) - print(json.dumps(mapping)) + print("{}") + sys.exit(1)