mirror of
https://github.com/kernelkit/infix.git
synced 2026-08-04 06:43:00 +02:00
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:
committed by
Joachim Wiberg
parent
175f7e1c58
commit
aa8e11a58b
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user