# HG changeset patch # User Tero Marttila # Date 1396269082 -10800 # Node ID b2b1bc48819577ca619a44ebe88433f0040a8b3d # Parent 32b7a0f2e7dc5dce2c0716407f989b4deed5332c pvl.hosts-graph: --graph-bridge for very crude fdb ethernet nodes/links diff -r 32b7a0f2e7dc -r b2b1bc488195 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 :