mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-22 01:13:00 +02:00
One to verify it is the correct version on the duts and one that check that it is the correct bootorder.
183 lines
5.0 KiB
Python
183 lines
5.0 KiB
Python
import networkx as nx
|
|
from networkx.algorithms import isomorphism
|
|
|
|
from itertools import permutations
|
|
import json
|
|
|
|
def _qstrip(text):
|
|
if text is None:
|
|
return None
|
|
|
|
if text.startswith("\"") and text.endswith("\""):
|
|
return text[1:-1]
|
|
|
|
return text
|
|
|
|
def compatible(physical, logical):
|
|
return logical["requires"].issubset(physical["provides"])
|
|
|
|
def edge_mappings(les, pes):
|
|
les = les.values()
|
|
pes = pes.values()
|
|
|
|
for perm in permutations(pes, len(les)):
|
|
candidate = tuple(zip(les, perm))
|
|
if all(map(lambda pair: compatible(pair[1], pair[0]), candidate)):
|
|
yield candidate
|
|
|
|
|
|
class Topology:
|
|
def __init__(self, dotg):
|
|
self.dotg = dotg
|
|
self.g = nx.MultiGraph()
|
|
|
|
for n in self.dotg.get_nodes():
|
|
name = n.get_name()
|
|
if name in ("node", "edge"):
|
|
continue
|
|
|
|
repr(n.get_attributes())
|
|
attrs = { _qstrip(k): _qstrip(v) for k, v in n.get_attributes().items() if k != "label" }
|
|
for attr in ("requires", "provides"):
|
|
attrs[attr] = set(attrs.get(attr, "").split())
|
|
|
|
self.g.add_node(name, **attrs)
|
|
|
|
for e in self.dotg.get_edges():
|
|
sn, sp = e.get_source().split(":")
|
|
dn, dp = e.get_destination().split(":")
|
|
|
|
attrs = {_qstrip(k): _qstrip(v) for k, v in e.get_attributes().items()}
|
|
attrs[sn] = sp
|
|
attrs[dn] = dp
|
|
|
|
for attr in ("requires", "provides"):
|
|
attrs[attr] = set(attrs.get(attr, "").split())
|
|
|
|
self.g.add_edge(sn, dn, **attrs)
|
|
|
|
def __repr__(self):
|
|
if not self.mapping:
|
|
return ""
|
|
|
|
out = ""
|
|
|
|
for n in self.mapping:
|
|
out += f"{n + ':':<8} {self.mapping[n][None]}\n"
|
|
for e in self.mapping[n]:
|
|
if not e:
|
|
continue
|
|
|
|
out += f" {e + ':':<8} {self.mapping[n][e]}\n"
|
|
|
|
return out
|
|
|
|
def map_to(self, phy,
|
|
nodes_compatible=compatible, edge_mappings=edge_mappings):
|
|
mapper = isomorphism.MultiGraphMatcher(phy.g, self.g,
|
|
edge_match=lambda pes, les: any(edge_mappings(les, pes)),
|
|
node_match=nodes_compatible)
|
|
if not mapper.subgraph_is_monomorphic():
|
|
return False
|
|
|
|
# breakpoint()
|
|
self.phy = phy
|
|
self.mapping = {}
|
|
|
|
for pn, ln in mapper.mapping.items():
|
|
self.mapping.setdefault(ln, { None: pn })
|
|
|
|
for lsrc, ldst in set(self.g.edges()):
|
|
psrc = self.mapping[lsrc][None]
|
|
pdst = self.mapping[ldst][None]
|
|
|
|
les = self.g.get_edge_data(lsrc, ldst)
|
|
pes = self.phy.g.get_edge_data(psrc, pdst)
|
|
|
|
for le, pe in next(edge_mappings(les, pes)):
|
|
self.mapping[lsrc][le[lsrc]] = pe[psrc]
|
|
self.mapping[ldst][le[ldst]] = pe[pdst]
|
|
|
|
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_password(self, node):
|
|
n = self.dotg.get_node(node)
|
|
b = n[0] if n else {}
|
|
password = b.get("password")
|
|
|
|
return qstrip(password) if password is not None else "admin"
|
|
|
|
def get_expected_boot(self, node):
|
|
n = self.dotg.get_node(node)
|
|
b = n[0] if n else {}
|
|
boot = b.get("expected_boot")
|
|
|
|
return _qstrip(boot)
|
|
|
|
def get_link(self, src, dst, flt=lambda _: True):
|
|
es = self.g.get_edge_data(src, dst)
|
|
for e in es.values():
|
|
if flt(e):
|
|
return e[src], e[dst]
|
|
|
|
return None
|
|
|
|
def get_mgmt_link(self, src, dst):
|
|
return self.get_link(src, dst, lambda e: compatible(e, {"requires": {"mgmt"}}))
|
|
|
|
def get_ctrl(self):
|
|
ns = self.get_nodes(lambda _, attrs: compatible(attrs, {"requires": {"controller"}}))
|
|
assert len(ns) == 1
|
|
return ns[0]
|
|
|
|
def get_infixen(self):
|
|
return self.get_nodes(lambda _, attrs: compatible(attrs, {"requires": {"infix"}}))
|
|
|
|
def get_attr(self, name, default=None):
|
|
return _qstrip(self.dotg.get_attributes().get(name, default))
|
|
|
|
|
|
# Support calling this script like so...
|
|
#
|
|
# python3 topology.py <physical> <logical>
|
|
#
|
|
# to inspect the graph matcher's results in isolation from the rest of
|
|
# the system.
|
|
if __name__ == "__main__":
|
|
import json
|
|
import pydot
|
|
import sys
|
|
|
|
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))
|
|
sys.exit(0)
|
|
|
|
print("{}")
|
|
sys.exit(1)
|