pvl.hosts-lldp: --graph-dot support
authorTero Marttila <terom@paivola.fi>
Mon, 17 Mar 2014 20:01:31 +0200
changeset 387 75158fd28784
parent 386 9e1abcf47d27
child 388 5a034dbd641c
pvl.hosts-lldp: --graph-dot support
bin/pvl.hosts-lldp
--- a/bin/pvl.hosts-lldp	Mon Mar 17 17:46:48 2014 +0200
+++ b/bin/pvl.hosts-lldp	Mon Mar 17 20:01:31 2014 +0200
@@ -2,15 +2,7 @@
 
 """
     Requirements:
-        pysnmp
-        pysnmp-mibs
-        memoized-property
-
-    Setup:
-        ./opt/bin/build-pysnmp-mib -o usr/mibs/LLDP-MIB.py etc/mibs/LLDP-MIB
-
-    Run:
-        PYSNMP_MIB_DIRS=usr/mibs/ ./opt/bin/python ...
+        pydot
 """
 
 import pvl.args
@@ -85,6 +77,90 @@
 
             yield host, merge(agent.local, port), remote, remote_host
 
+def apply_graph (options, items) :
+    import pydot
+
+    dot = pydot.Dot(graph_name='lldp_hosts', graph_type='digraph',
+            splines     = 'true',
+
+            sep             = '+25,25',
+            overlap         = 'scalexy',
+
+            # only applies to loops
+            nodesep     = 0.5,
+    )
+    dot.set_edge_defaults(
+            labeldistance   = 3.0,
+    )
+
+    nodes = { }
+    edges = { }
+
+    for host, local, remote, remote_host in items :
+        # src
+        src_name = str(host)
+        src_label = '"{host.location}"'.format(host=host)
+
+        if src_name in nodes :
+            src = nodes[src_name]
+        else :
+            src = nodes[src_name] = pydot.Node(src_name, label=src_label)
+            dot.add_node(src)
+        
+        # dst
+        if remote_host :
+            dst_name = str(remote_host)
+            dst_label = '"{host.location}"'.format(host=remote_host)
+        else :
+            dst_name = remote['chassis'].replace(':', '-')
+
+            # XXX: pydot is not smart enough to quote this
+            if remote['sys_name'] :
+                dst_label = '"{remote[chassis]} ({remote[sys_name]})"'.format(remote=remote)
+            else :
+                dst_label = '"{remote[chassis]}"'.format(remote=remote)
+
+        
+        if dst_name in nodes :
+            dst = nodes[dst_name]
+        else :
+            dst = nodes[dst_name] = pydot.Node(dst_name, label=dst_label)
+            dot.add_node(dst)
+
+        # edge
+        headlabel = '"{remote[port]}"'.format(remote=remote)
+        taillabel = '"{local[port]}"'.format(local=local)
+
+        if (src_name, dst_name) in edges :
+            log.warning("%s <- %s: duplicate", src_name, dst_name)
+        
+        elif (dst_name, src_name) in edges :
+            log.info("%s <-> %s", src_name, dst_name)
+            edge = edges[(dst_name, src_name)]
+
+            if edge.get('headlabel') != taillabel :
+                log.warn("%s -> %s: local port mismatch: %s vs %s", src_name, dst_name, local['port'], edge.get('headlabel'))
+            
+            if edge.get('taillabel') != headlabel :
+                log.warn("%s -> %s: remote port mismatch: %s vs %s", src_name, dst_name, remote['port'], edge.get('taillabel'))
+
+            # mark as bidirectional
+            edges[(src_name, dst_name)] = edge
+            
+            edge.set('dir', 'both')
+
+        else :
+            edge = edges[(src_name, dst_name)] = pydot.Edge(src, dst,
+                    dir         = 'back',
+                    headlabel   = headlabel,
+                    taillabel   = taillabel,
+            )
+
+            dot.add_edge(edge)
+
+    if options.graph_dot :
+        dot.write(options.graph_dot)
+
 def main (argv) :
     """
         SNMP polling.
@@ -95,6 +171,8 @@
     parser.add_option_group(pvl.hosts.optparser(parser))
     parser.add_option_group(pvl.snmp.snmp.options(parser))
 
+    parser.add_option('--graph-dot',
+            help="Output .dot graph data")
 
     options, args = parser.parse_args(argv[1:])
     pvl.args.apply(options)
@@ -103,11 +181,17 @@
     hosts = pvl.hosts.apply(options, args)
     
     # apply
-    for host, local, remote, remote_host in apply_hosts_lldp(options, hosts) :
-        if remote_host :
-            print "{host:30} {host.location:>30} {local[port]:>25} <-> {remote[port]:<25} {remote_host.location:>30} # {remote[chassis]} ({remote_host})".format(host=host, local=local, remote=remote, remote_host=remote_host)
-        else :
-            print "{host:30} {host.location:>30} {local[port]:>25} <-- {remote[port]:<25} {empty:30} # {remote[chassis]} ({remote[sys_name]})".format(host=host, local=local, remote=remote, empty='')
+    items = apply_hosts_lldp(options, hosts)
+
+    # print
+    if options.graph_dot :
+        apply_graph(options, items)
+    else :
+        for host, local, remote, remote_host in items :
+            if remote_host :
+                print "{host:30} {host.location:>30} {local[port]:>25} <-> {remote[port]:<25} {remote_host.location:>30} # {remote[chassis]} ({remote_host})".format(host=host, local=local, remote=remote, remote_host=remote_host)
+            else :
+                print "{host:30} {host.location:>30} {local[port]:>25} <-- {remote[port]:<25} {empty:30} # {remote[chassis]} ({remote[sys_name]})".format(host=host, local=local, remote=remote, empty='')
 
 if __name__ == '__main__':
     pvl.args.main(main)