test/infamy: Create an infamy specific topology object

Create topology objects that are tailored to represent topologies on
the expected format. This let's us make convenient accessors for
things like the controller node, infix devices, getting the ports used
to reach a certain device from another one, etc.
This commit is contained in:
Tobias Waldekranz
2023-06-14 17:40:16 +02:00
committed by Joachim Wiberg
parent 175f7e1c58
commit aa8e11a58b
3 changed files with 119 additions and 88 deletions
+2 -2
View File
@@ -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("""
+10 -54
View File
@@ -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,
+107 -32
View File
@@ -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 <physical> <logical>
#
@@ -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)