Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
james-scripts/netmap-graphviz.py
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
executable file
106 lines (87 sloc)
3 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
import glob | |
import re | |
import xml.etree.ElementTree as et | |
# import json | |
# from datetime import datetime | |
# from collections import namedtuple | |
from collections import defaultdict | |
from itertools import combinations | |
from pathlib import Path | |
import subprocess | |
SCALE = 1/400.0 | |
GV_EPSILON = 0.000001 | |
def main(): | |
# Load router configuration | |
configs = load_configs() | |
# Extract router pairs | |
links = defaultdict(set) # cid => set(r1, r2) | |
metrics = defaultdict(set) # cid => set(metric1, metric2) | |
for rtr_name, config in configs.items(): | |
print(rtr_name) | |
for iface in config.findall('.//interface'): | |
desc = iface.findtext('./description', default="") | |
name = iface.findtext('./name', default="") | |
# Get Circuit ID | |
m = re.match(r'^BACKBONE: .* I2-.*-(\d+)$', desc) | |
if not m: | |
continue | |
cid = m.group(1) | |
# Get metric | |
isis_metric = config.findtext(f'.//interface[name="{name}.4079"]/level[name="2"]/metric', None) | |
# Router is connected to CID | |
links[cid].add(rtr_name) | |
if isis_metric is not None: | |
metrics[cid].add(int(isis_metric)) | |
dot = generate_graph(links, metrics) | |
print(dot) | |
# Run through graphviz | |
ps = subprocess.Popen( | |
f"docker run -i --rm nshine/dot neato -Tpng -Gepsilon={GV_EPSILON}".split(" "), | |
stdout=subprocess.PIPE, | |
stdin=subprocess.PIPE | |
) | |
ps.stdin.write(dot.encode("utf-8")) | |
ps.stdin.close() | |
png = ps.stdout.read() | |
Path("netmap-graphviz.png").write_bytes(png) | |
def generate_graph(links, metrics): | |
# Generate graph | |
rv = [] | |
rv.extend([ | |
"graph G {", | |
# " orientation = landscape;", | |
" overlap = false;", | |
" splines = true;", | |
" node [shape=box; fontname=\"helvetica\"];", | |
" edge [fontname=\"helvetica\"];", | |
]) | |
for cid, node_sets in links.items(): | |
if len(node_sets) < 2: | |
print(f"Didn't find two nodes for CID {cid}. Nodes: {node_sets}") | |
for (a,b) in combinations(node_sets, 2): | |
m = max(metrics[cid], default=1) * SCALE | |
rv.append(f" \"{a}\" -- \"{b}\" [len={m}];") | |
rv.append("}") | |
return "\n".join(rv) | |
def load_configs(): | |
rv = {} | |
for config_file in glob.glob("../internet2-configs/rtsw/xml/*.xml"): | |
# print("parsing file {}".format(config_file)) | |
try: | |
rtr_config = et.parse(config_file) | |
rtr_name = extract_hostname(rtr_config) | |
rv[rtr_name] = rtr_config | |
except et.ParseError: | |
pass | |
return rv | |
RE_HOSTNAME_CLEANUP = re.compile(r'-re\d', re.IGNORECASE) | |
def extract_hostname(config_root): | |
"""Return the sanitized router hostname""" | |
rtr_name = config_root.findtext('.//system/host-name') | |
rtr_name = RE_HOSTNAME_CLEANUP.sub('', rtr_name) | |
return rtr_name | |
SKIP_IF_NAMES = set(["all", "fxp0.0", "dsc", "dsc.0"]) | |
# Main program | |
if __name__ == "__main__": | |
main() |