pvl.hosts-graph: --graph-bridge for very crude fdb ethernet nodes/links
authorTero Marttila <terom@paivola.fi>
Mon, 31 Mar 2014 15:31:22 +0300
changeset 409 b2b1bc488195
parent 408 32b7a0f2e7dc
child 410 afe9cc8032ec
pvl.hosts-graph: --graph-bridge for very crude fdb ethernet nodes/links
bin/pvl.hosts-graph
--- a/bin/pvl.hosts-graph	Mon Mar 31 14:47:53 2014 +0300
+++ b/bin/pvl.hosts-graph	Mon Mar 31 15:31:22 2014 +0300
@@ -168,7 +168,7 @@
         
         yield port, (untag, tagged)
 
-def build_graph (snmp, hosts) :
+def build_graph (options, snmp, hosts) :
     """
         Combine given snmp data and { host: Host } into
             { node: label }
@@ -183,99 +183,130 @@
     
     # first scan: lldp hosts
     for host, host_attrs in snmp.iteritems() :
-        lldp = host_attrs.get('lldp')
+        nodes[host] = host.location or str(host)
 
-        if lldp :
-            lldp_local = lldp['local']
-            local = lldp_local['chassis']
-            
-            nodes[host] = host.location or str(host)
-            hosts_by_lldp[local] = host
+        if 'lldp' in host_attrs :
+            lldp_local = host_attrs['lldp']['local']
+
+            hosts_by_lldp[lldp_local['chassis']] = host
     
     # second scan: nodes by ethernet
     for host in hosts :
         for ethernet in host.ethernet.itervalues() :
             hosts_by_ethernet[ethernet] = host
 
-    # second scan: lldp remotes
+    # first graph: lldp remotes
     for host, host_attrs in snmp.iteritems() :
-        lldp = host_attrs.get('lldp')
-
-        if not lldp :
-            continue
-
-        local = lldp['local']['chassis']
         local_node = host
 
         if 'vlan' in host_attrs :
             vlans = dict(host_vlans(host, host_attrs['vlan']))
         else :
             vlans = None
-        
-        for port, port_attrs in lldp.get('port', { }).iteritems() :
-            local_port = port_attrs['local']['port']
-
-            for remote, remote_attrs in port_attrs['remote'].iteritems() :
-                # determine remote node
-                remote_label = remote_attrs['sys_name']
-
-                if remote in hosts_by_lldp :
-                    remote_node = remote_host = hosts_by_lldp[remote]
 
-                elif remote in hosts_by_ethernet :
-                    remote_node = remote_host = hosts_by_ethernet[remote]
-
-                    if remote_host.location :
-                        remote_label = remote_host.location
+        if 'lldp' in host_attrs :
+            lldp = host_attrs['lldp']
 
-                    log.info("%s:%s: guessing lldp host %s -> %s (%s)", host, port, remote, remote_host, remote_label)
+            local_lldp = lldp['local']['chassis']
+            
+            for port, port_attrs in lldp.get('port', { }).iteritems() :
+                local_port = port_attrs['local']['port']
 
+                for remote_lldp, remote_attrs in port_attrs['remote'].iteritems() :
+                    # determine remote node
+                    remote_label = remote_attrs['sys_name']
+
+                    if remote_lldp in hosts_by_lldp :
+                        remote_node = remote_host = hosts_by_lldp[remote_lldp]
+
+                    elif remote_lldp in hosts_by_ethernet :
+                        remote_node = remote_host = hosts_by_ethernet[remote_lldp]
+
+                        if remote_host.location :
+                            remote_label = remote_host.location
+
+                        log.info("%s:%s: guessing lldp host %s -> %s (%s)", host, port, remote_lldp, remote_host, remote_label)
+
+                    else :
+                        # by chassis id
+                        remote_node = remote_lldp
+                        remote_host = None
+
+                        log.warning("%s:%s: unknown lldp remote %s (%s)", host, port, remote_lldp, remote_label)
+                    
+                    # ensure remote node
+                    if remote_node not in nodes :
+                        log.info("%s:%s: lazy-add remote %s (%s)", host, port, remote_node, remote_label)
+
+                        nodes[remote_node] = remote_label
+                    
+                    # local vlans
+                    if vlans :
+                        port_vlans = vlans.get(port)
+                    else :
+                        port_vlans = None
+
+                    if port_vlans :
+                        local_untag, local_tagged = port_vlans
+                    
+                    # bidirectional mappings
+                    remote_port = remote_attrs['port']
+
+                    forward = (local_node, local_port, remote_port, remote_node)
+                    reverse = (remote_node, remote_port, local_port, local_node)
+
+                    if reverse not in links :
+                        links[forward] = (local_untag, local_tagged, None)
+                    else :
+                        remote_untag, remote_tagged, _ = links[reverse]
+
+                        # merge
+                        if remote_untag != local_untag :
+                            log.warning("%s:%s untag %s <=> %s untag %s:%s",
+                                    host, local_port, local_untag,
+                                    remote_untag, remote_node, remote_port
+                            )
+
+                        if remote_tagged != local_tagged :
+                            log.warning("%s:%s tagged %s <-> %s tagged %s:%s",
+                                    host, local_port, ':'.join(str(x) for x in sorted(local_tagged)),
+                                    ':'.join(str(x) for x in sorted(remote_tagged)), remote_node, remote_port
+                            )
+
+                        links[reverse] = (remote_untag, remote_tagged, local_untag)
+
+        # second graph: bridge
+        if not options.graph_bridge :
+            bridge = { }
+        elif 'bridge' in host_attrs :
+            bridge = host_attrs['bridge']
+        else :
+            bridge = { }
+
+        for port, ethernets in bridge.iteritems() :
+            local_node = host
+
+            for ethernet in ethernets :
+                if ethernet in hosts_by_ethernet :
+                    remote_node = remote_host = hosts_by_ethernet[ethernet]
+
+                    remote_label = remote_host.location or str(remote_host)
+
+                    log.info("%s:%s: bridge ethernet %s -> %s (%s)", host, port, ethernet, remote_host, remote_label)
                 else :
-                    remote_node = remote
-                    remote_host = None
+                    continue
 
-                    log.warning("%s:%s: unknown remote %s (%s)", host, port, remote, remote_label)
-                
-                # ensure remote node
                 if remote_node not in nodes :
                     log.info("%s:%s: lazy-add remote %s (%s)", host, port, remote_node, remote_label)
 
                     nodes[remote_node] = remote_label
                 
-                # local vlans
-                if vlans :
-                    port_vlans = vlans.get(port)
-                else :
-                    port_vlans = None
-
-                if port_vlans :
-                    local_untag, local_tagged = port_vlans
+                link = (host, port, None, remote_node)
                 
-                # bidirectional mappings
-                remote_port = remote_attrs['port']
-
-                forward = (local_node, local_port, remote_port, remote_node)
-                reverse = (remote_node, remote_port, local_port, local_node)
-
-                if reverse not in links :
-                    links[forward] = (local_untag, local_tagged, None)
-                else :
-                    remote_untag, remote_tagged, _ = links[reverse]
-
-                    # merge
-                    if remote_untag != local_untag :
-                        log.warning("%s:%s untag %s <=> %s untag %s:%s",
-                                host, local_port, local_untag,
-                                remote_untag, remote_node, remote_port
-                        )
-
-                    if remote_tagged != local_tagged :
-                        log.warning("%s:%s tagged %s <-> %s tagged %s:%s",
-                                host, local_port, ':'.join(str(x) for x in sorted(local_tagged)),
-                                ':'.join(str(x) for x in sorted(remote_tagged)), remote_node, remote_port
-                        )
-
-                    links[reverse] = (remote_untag, remote_tagged, local_untag)
+                # unknown vlans
+                link_vlans = None
+                
+                links[link] = link_vlans
 
     return nodes, links
 
@@ -321,7 +352,7 @@
 
     if line and attrs :
         return ''.join(('\t', ' '.join(str(x) for x in line), ' [',
-            ', '.join('{name}="{value}"'.format(name=name, value=value) for name, value in attrs.iteritems()),
+            ', '.join('{name}="{value}"'.format(name=name, value=value) for name, value in attrs.iteritems() if value is not None),
         ']', ';'))
     elif line :
         return ' '.join(line) + ' {'
@@ -362,14 +393,21 @@
         yield dot(dot_quote(node), label=node_label)
 
     # links
-    for (local, local_port, remote_port, remote), (local_untag, tagged, remote_untag) in links.iteritems() :
-        if vlans :
-            head_color = vlans.color(local_untag) if local_untag else None
-            tail_color = vlans.color(remote_untag) if remote_untag else None
-            line_colors = [vlans.color(tag) for tag in sorted(tagged)]
+    for (local, local_port, remote_port, remote), link_vlans in links.iteritems() :
+        if link_vlans :
+            local_untag, tagged, remote_untag = link_vlans
+
+            if vlans :
+                head_color = vlans.color(local_untag) if local_untag else None
+                tail_color = vlans.color(remote_untag) if remote_untag else None
+                line_colors = [vlans.color(tag) for tag in sorted(tagged)]
+            else :
+                head_color = GraphVlans.NONE if local_untag else None
+                tail_color = GraphVlans.NONE if remote_untag else None
+                line_colors = []
         else :
-            head_color = GraphVlans.NONE if local_untag else None
-            tail_color = GraphVlans.NONE if remote_untag else None
+            # unknown
+            head_color = tail_color = None
             line_colors = []
 
         if head_color and tail_color :
@@ -391,7 +429,7 @@
             dir         = dir,
 
             fillcolor   = 'black',
-            color       = ':'.join(colors),
+            color       = ':'.join(colors) if colors else None,
         )
 
     yield dot()
@@ -419,12 +457,17 @@
     parser.add_option('--graph-dot', metavar='FILE',
             help="Output .dot graph data to file")
 
+
     parser.add_option('--graph-vlans', action='store_true', dest='graph_vlans', 
-            help="Graph all VLANs")
+            help="Graph links with VLAN information")
 
     parser.add_option('--no-vlans', action='store_false', dest='graph_vlans',
             help="Do not color VLANs")
 
+    parser.add_option('--graph-bridge', action='store_true',
+            help="Graph bridge forwarding database links")
+
+
     # input
     options, args = parser.parse_args(argv[1:])
     pvl.args.apply(options)
@@ -436,7 +479,7 @@
     snmp = load_snmp_data(options, options.snmp_data, hosts)
 
     # process data into graph
-    nodes, links = build_graph(snmp, hosts)
+    nodes, links = build_graph(options, snmp, hosts)
     
     # process graph into dot
     if options.graph_vlans is False :