--- 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 :