terom@399: #!/usr/bin/env python terom@399: terom@399: """ terom@399: Requirements: terom@399: pydot terom@399: """ terom@399: terom@399: import pvl.args terom@399: import pvl.hosts terom@402: from pvl.invoke import merge terom@399: terom@399: import collections terom@399: import logging; log = logging.getLogger('pvl.hosts-graph') terom@399: import optparse terom@399: terom@399: class ParseError (Exception) : terom@399: def __init__ (self, file, line, msg) : terom@399: self.file = file terom@399: self.line = line terom@399: self.msg = msg terom@399: terom@399: def __str__ (self) : terom@399: return "{self.file}:{self.line}: {self.msg}".format(self=self) terom@399: terom@400: def _parse_snmp_part (part) : terom@400: if part.isdigit() : terom@400: return int(part) terom@400: else : terom@400: return part terom@400: terom@404: def _parse_snmp_attr (line) : terom@400: for part in line.split() : terom@400: yield _parse_snmp_part(part) terom@400: terom@404: def _parse_snmp_value (line) : terom@404: if '\t' in line : terom@404: key, value = line.split('\t', 1) terom@402: terom@404: return { _parse_snmp_part(key): _parse_snmp_part(value) } terom@402: terom@402: else : terom@404: return set((_parse_snmp_part(line), )) terom@404: terom@399: def _load_snmp_data (options, file) : terom@399: """ terom@399: Load a data dict generated by pvl.hosts-snmp from a file. terom@399: terom@399: Yields (host, attr, value) terom@399: """ terom@399: terom@399: host = None terom@399: attr = None terom@399: value = None terom@399: terom@399: for idx, line in enumerate(file, 1) : terom@400: indent = 0 terom@400: terom@400: while line.startswith('\t') : terom@400: indent += 1 terom@400: line = line[1:] terom@400: terom@404: line = line.lstrip('\t').rstrip('\n') terom@400: terom@399: if indent == 0 : terom@399: host = line terom@399: attr = None terom@399: value = None terom@399: terom@399: elif indent == 1 : terom@404: attr = tuple(_parse_snmp_attr(line)) terom@404: value = None terom@400: terom@404: yield host, attr, None terom@399: terom@399: elif indent == 2 : terom@399: if not attr : terom@400: raise ParseError(file, line, "[%s] %s: value outside of attr" % (host, attr)) terom@400: terom@404: value = _parse_snmp_value(line) terom@399: terom@404: yield host, attr, value terom@399: terom@399: def load_snmp_data (options, file, hosts) : terom@399: """ terom@399: Load snmp data as dict, from given file path, or stdin. terom@399: """ terom@399: terom@399: if file : terom@399: file = open(file) terom@399: else : terom@399: file = sys.stdin terom@399: terom@399: root = { } terom@408: terom@408: hosts_by_namedomain = dict( terom@408: ( terom@408: '{host}@{domain}'.format(host=host, domain=host.domain), host terom@408: ) for host in hosts terom@408: ) terom@399: terom@408: for host_domain, attr, value in _load_snmp_data(options, file) : terom@408: host = hosts_by_namedomain.get(host_domain) terom@404: terom@404: if value : terom@405: log.debug("[%s] %s: %s", host, ' '.join(str(a) for a in attr), value) terom@404: else : terom@405: log.debug("[%s] %s", host, ' '.join(str(a) for a in attr),) terom@399: terom@399: item = root.setdefault(host, { }) terom@400: terom@399: for a in attr[:-1] : terom@399: item = item.setdefault(a, {}) terom@399: terom@399: a = attr[-1] terom@400: terom@404: if value is None : terom@404: pass terom@404: terom@404: elif isinstance(value, set) : terom@400: item.setdefault(a, set()).update(value) terom@399: terom@404: elif isinstance(value, dict) : terom@404: item.setdefault(a, dict()).update(value) terom@404: terom@399: else : terom@402: item[a] = value terom@400: terom@399: return root terom@399: terom@405: def host_vlans (host, host_vlans) : terom@405: """ terom@405: {vlan: { tagged/untagged: [port] } } -> (port, (untag, [tag])). terom@405: """ terom@402: terom@405: ports = set() terom@405: vlans_untagged = { } terom@405: vlans_tagged = collections.defaultdict(set) terom@402: terom@405: for vlan, vlan_attrs in host_vlans.iteritems() : terom@405: for port in vlan_attrs.get('tagged', ()) : terom@405: ports.add(port) terom@405: vlans_tagged[port].add(vlan) terom@405: terom@405: for port in vlan_attrs.get('untagged', ()) : terom@405: ports.add(port) terom@405: vlans_untagged[port] = vlan terom@405: terom@405: for port in ports : terom@405: untag = vlans_untagged.get(port) terom@405: tagged = vlans_tagged.get(port, ()) terom@405: terom@405: log.debug("%s: %s: untag=%s tag=%s", host, port, untag, tagged) terom@405: terom@405: yield port, (untag, tagged) terom@405: terom@409: def build_graph (options, snmp, hosts) : terom@405: """ terom@405: Combine given snmp data and { host: Host } into terom@405: { node: label } terom@405: { (remote, remote_port, local_port, local): (local_untag, tagged, remote_untag) } terom@405: """ terom@405: terom@406: nodes = { } # host: label terom@410: links = { } # (local, local_port, remote_port, remote_host): (local_untag, tagged, remote_untag) terom@406: terom@408: hosts_by_lldp = { } # chassis: host terom@408: hosts_by_ethernet = { } # ethernet: host terom@410: hosts_by_location = { } # (domain, location): host terom@410: terom@416: nodes_port = { } # (local, int(local_port)): {remote} terom@411: nodes_out = { } # local: {remote} terom@414: nodes_in = { } # remote: {local} terom@410: links_out = { } # (local, remote): local_port terom@410: links_in = { } # (remote, local): remote_port terom@405: terom@405: # first scan: lldp hosts terom@405: for host, host_attrs in snmp.iteritems() : terom@409: nodes[host] = host.location or str(host) terom@405: terom@409: if 'lldp' in host_attrs : terom@409: lldp_local = host_attrs['lldp']['local'] terom@409: terom@409: hosts_by_lldp[lldp_local['chassis']] = host terom@405: terom@408: # second scan: nodes by ethernet terom@408: for host in hosts : terom@408: for ethernet in host.ethernet.itervalues() : terom@408: hosts_by_ethernet[ethernet] = host terom@408: terom@421: if host.location and host.location_domain: terom@421: hosts_by_location[(host.location_domain, host.location)] = host terom@421: elif host.location: terom@410: hosts_by_location[(host.domain, host.location)] = host terom@410: terom@409: # first graph: lldp remotes terom@402: for host, host_attrs in snmp.iteritems() : terom@406: local_node = host terom@402: terom@405: if 'vlan' in host_attrs : terom@405: vlans = dict(host_vlans(host, host_attrs['vlan'])) terom@405: else : terom@405: vlans = None terom@408: terom@409: if 'lldp' in host_attrs : terom@409: lldp = host_attrs['lldp'] terom@408: terom@409: local_lldp = lldp['local']['chassis'] terom@409: terom@409: for port, port_attrs in lldp.get('port', { }).iteritems() : terom@409: local_port = port_attrs['local']['port'] terom@408: terom@409: for remote_lldp, remote_attrs in port_attrs['remote'].iteritems() : terom@409: # determine remote node terom@409: remote_label = remote_attrs['sys_name'] terom@409: terom@409: if remote_lldp in hosts_by_lldp : terom@409: remote_node = remote_host = hosts_by_lldp[remote_lldp] terom@409: terom@409: elif remote_lldp in hosts_by_ethernet : terom@409: remote_node = remote_host = hosts_by_ethernet[remote_lldp] terom@409: terom@409: if remote_host.location : terom@409: remote_label = remote_host.location terom@409: terom@409: log.info("%s:%s: guessing lldp host %s -> %s (%s)", host, port, remote_lldp, remote_host, remote_label) terom@409: terom@417: elif options.graph_lldp_unknown : terom@413: log.warning("%s:%s: unknown lldp remote %s (%s)", host, port, remote_lldp, remote_label) terom@413: terom@417: # by chassis id terom@417: remote_node = remote_lldp terom@417: remote_host = None terom@417: terom@417: else : terom@417: log.info("%s:%s: unknown lldp remote %s (%s)", host, port, remote_lldp, remote_label) terom@417: terom@417: remote_node = remote_host = None terom@417: terom@413: terom@413: if not remote_node : terom@413: continue terom@409: terom@409: # ensure remote node terom@409: if remote_node not in nodes : terom@410: log.debug("%s:%s: lazy-add remote %s (%s)", host, port, remote_node, remote_label) terom@409: terom@409: nodes[remote_node] = remote_label terom@409: terom@409: # local vlans terom@409: if vlans : terom@409: port_vlans = vlans.get(port) terom@409: else : terom@409: port_vlans = None terom@409: terom@409: if port_vlans : terom@409: local_untag, local_tagged = port_vlans terom@410: terom@410: # directional mapping terom@410: links_out[(local_node, remote_node)] = local_port terom@416: nodes_port.setdefault((local_node, port), set()).add(remote_node) terom@411: nodes_out.setdefault(local_node, set()).add(remote_node) terom@414: nodes_in.setdefault(remote_node, set()).add(local_node) terom@409: terom@409: # bidirectional mappings terom@409: remote_port = remote_attrs['port'] terom@409: terom@410: links_in[(remote_node, local_node)] = remote_port terom@410: terom@409: forward = (local_node, local_port, remote_port, remote_node) terom@409: reverse = (remote_node, remote_port, local_port, local_node) terom@409: terom@409: if reverse not in links : terom@409: links[forward] = (local_untag, local_tagged, None) terom@409: else : terom@409: remote_untag, remote_tagged, _ = links[reverse] terom@409: terom@409: # merge terom@409: if remote_untag != local_untag : terom@409: log.warning("%s:%s untag %s <=> %s untag %s:%s", terom@409: host, local_port, local_untag, terom@409: remote_untag, remote_node, remote_port terom@409: ) terom@409: terom@409: if remote_tagged != local_tagged : terom@409: log.warning("%s:%s tagged %s <-> %s tagged %s:%s", terom@409: host, local_port, ':'.join(str(x) for x in sorted(local_tagged)), terom@409: ':'.join(str(x) for x in sorted(remote_tagged)), remote_node, remote_port terom@409: ) terom@409: terom@409: links[reverse] = (remote_untag, remote_tagged, local_untag) terom@409: terom@410: # second graph: manual ports terom@410: for host in hosts : terom@410: local_node = host terom@410: host_links = host.extensions.get('link') terom@410: terom@418: # XXX: copy-pasta terom@418: if host in snmp and 'vlan' in snmp[host] : terom@418: vlans = dict(host_vlans(host, snmp[host]['vlan'])) terom@418: else : terom@418: vlans = None terom@418: terom@410: if host_links : terom@410: if local_node not in nodes : terom@410: # XXX: copypasta terom@410: nodes[local_node] = host.location or str(host) terom@410: terom@411: for link_port, remote in host_links.iteritems() : terom@411: if link_port.isdigit() : terom@411: port = int(link_port) terom@411: else : terom@411: port = str(link_port) terom@411: terom@410: # map remote -> remote_host terom@410: if '@' in remote : terom@410: remote_location, remote_domain = remote.split('@', 1) terom@410: else : terom@410: remote_location = remote terom@410: remote_domain = host.domain terom@410: terom@410: remote_node = remote_host = hosts_by_location.get((remote_domain, remote_location)) terom@410: terom@410: if not remote_host : terom@410: log.warning("%s:%s: unknown remote location: %s@%s", host, port, remote_location, remote_domain) terom@410: continue terom@410: terom@410: remote_label = remote_host.location or str(remote_host) terom@410: terom@410: log.info("%s:%s: link -> %s@%s (%s)", host, port, remote_host, remote_host.domain, remote_label) terom@410: terom@410: if remote_node not in nodes : terom@410: # XXX: copypasta terom@410: nodes[remote_node] = remote_label terom@410: terom@418: # local vlans terom@418: if vlans and port in vlans : terom@418: local_untag, local_tagged = vlans[port] terom@418: terom@418: log.info("%s:%s link vlans (%s) <%s>", host, port, local_untag, ':'.join(str(tag) for tag in local_tagged)) terom@418: terom@418: link_vlans = (local_untag, local_tagged, None) terom@418: else : terom@418: # unknown terom@418: link_vlans = None terom@418: terom@418: log.warning("%s:%s unknown vlans", host, port) terom@418: terom@410: # directional links terom@410: local_port = links_out.get((local_node, remote_node)) terom@410: terom@410: if not local_port : terom@410: log.info("%s:%s: unconfirmed -> %s", host, port, remote_host) terom@410: terom@410: elif local_port != port : terom@410: log.warn("%s:%s: port mismatch %s -> %s", host, port, local_port, remote_host) terom@410: terom@410: else : terom@410: log.debug("%s:%s: confirm -> %s", host, port, remote_host) terom@410: terom@410: links_out[(local_node, remote_node)] = port terom@416: nodes_port.setdefault((local_node, port), set()).add(remote_node) terom@411: nodes_out.setdefault(local_node, set()).add(remote_node) terom@414: nodes_in.setdefault(remote_node, set()).add(local_node) terom@410: terom@410: # update directional or missing links terom@410: remote_port = links_out.get((remote_node, local_node)) terom@410: reverse_port = links_in.get((local_node, remote_node)) terom@410: terom@410: if reverse_port and reverse_port != port : terom@411: # XXX: this can be caused by str vs int >_> terom@410: log.warn("%s:%s: reverse port mismatch %s <- %s", host, port, reverse_port, remote_node) terom@410: terom@410: terom@410: if local_port and remote_port : terom@410: log.debug("%s:%s link <-> %s:%s", local_node, local_port, remote_port, remote_node) terom@410: terom@410: elif local_port : terom@410: # we have the forward mapping already, so this doesn't add any new info terom@410: log.debug("%s:%s link -> %s:%s", local_node, local_port, remote_port, remote_node) terom@410: terom@410: elif remote_port and reverse_port : terom@410: # we have the bidirectional mapping already, so this doesn't add any new info terom@410: log.debug("%s:%s link <-> %s:%s", local_node, local_port, remote_port, remote_node) terom@410: terom@410: elif remote_port : terom@410: # we had the reverse mapping already, make it bidirectional terom@410: local_port = port terom@410: log.info("%s:%s link <- %s:%s", local_node, local_port, remote_port, remote_node) terom@418: terom@418: # TODO: update vlan info terom@410: links[(remote_node, remote_port, local_port, local_node)] = links.pop((remote_node, remote_port, None, local_node)) terom@410: terom@410: else : terom@410: local_port = port terom@410: terom@410: # mapping was completely missing terom@413: log.info("%s:%s link -> %s", local_node, local_port, remote_node) terom@410: terom@418: links[(local_node, local_port, None, remote_node)] = link_vlans terom@410: terom@416: terom@416: # verify non-p2p links terom@416: for (node, port), remotes in nodes_port.iteritems() : terom@416: if len(remotes) > 1 : terom@416: log.warning("%s:%s: multiple remotes: %s", node, port, ' '.join(str(host) for host in remotes)) terom@416: terom@411: if options.graph_bridge : terom@414: # scan hosts with bridges terom@414: bridge_hosts = set() terom@416: bridge_ports = { } terom@414: terom@414: for host, host_attrs in snmp.iteritems() : terom@414: if 'bridge' in host_attrs or any('bridge' in vlan_attrs for vlan_attrs in host_attrs.get('vlan', { }).itervalues()) : terom@414: bridge_hosts.add(host) terom@418: terom@411: # third graph: bridge terom@411: for host, host_attrs in snmp.iteritems() : terom@414: local_out = nodes_out.get(host) terom@411: terom@414: if not local_out : terom@411: log.warning("%s: no outgoing links, skipping bridge", host) terom@411: continue terom@414: terom@414: # scan vlan/port bridge ethers terom@414: bridge = { } # (port, vlan): {ethernet} terom@411: terom@411: for port, ethernets in host_attrs.get('bridge', { }).iteritems() : terom@411: bridge[(port, None)] = ethernets terom@411: terom@411: for vlan, vlan_attrs in host_attrs.get('vlan', { }).iteritems() : terom@411: for port, ethernets in vlan_attrs.get('bridge', { }).iteritems() : terom@411: bridge[(port, vlan)] = ethernets terom@411: terom@411: for (port, vlan), ethernets in bridge.iteritems() : terom@414: local_node = host terom@411: local_port = port terom@411: terom@416: remote_nodes = nodes_port.get((local_node, local_port)) terom@416: terom@416: if not remote_nodes : terom@416: remote_node = None terom@416: elif len(remote_nodes) == 1 : terom@416: remote_node, = remote_nodes terom@416: else : terom@416: log.warning("%s:%s: ignore port with multiple remotes: %s", host, port, ' '.join(str(host) for host in remotes)) terom@416: continue terom@414: terom@414: if remote_node : terom@414: remote_in = nodes_in.get(remote_node, set()) terom@414: else : terom@414: remote_in = set() terom@411: terom@414: remote_leaf = (remote_in == set((host, ))) terom@414: terom@414: # TODO: add ether node and link if remote node also has this ether on this link terom@414: # also do this if all remote_in's agree that the ether is on the remote_node terom@414: if not remote_node : terom@414: log.debug("%s:%s: no remote node", host, port) terom@414: terom@414: elif remote_leaf and (remote_node not in bridge_hosts) and len(ethernets) > 1 : terom@415: # map onto non-bridge leaf node terom@415: log.info("%s: <== %s:%s ", remote_node, host, port) terom@414: terom@414: # map links out of the assumed remote bridge terom@414: local_node = remote_node terom@414: local_port = None terom@414: terom@414: else : terom@411: log.debug("%s:%s/%s bridge skip -> %s", host, port, vlan, remote_node) terom@411: continue terom@411: terom@411: for ethernet in ethernets : terom@411: # remote host terom@411: if ethernet in hosts_by_ethernet : terom@411: remote_node = remote_host = hosts_by_ethernet[ethernet] terom@411: terom@411: remote_label = remote_host.location or str(remote_host) terom@411: terom@411: log.debug("%s:%s/%s bridge %s = %s (%s)", host, port, vlan, ethernet, remote_host, remote_label) terom@414: terom@417: elif options.graph_bridge_unknown : terom@417: log.warning("%s:%s/%s bridge unknown host %s", host, port, vlan, ethernet) terom@417: terom@417: remote_label = remote_node = ethernet terom@417: terom@417: nodes[remote_node] = remote_label terom@417: terom@417: remote_host = None terom@417: terom@417: else : terom@417: log.info("%s:%s/%s bridge unknown host %s", host, port, vlan, ethernet) terom@417: terom@411: continue terom@415: terom@415: # TODO: also handled multiple IP/ethers for the same host terom@417: if remote_host == host and local_node != host : terom@414: log.debug("%s:%s: skip remote-mapped self", host, port) terom@417: continue terom@414: terom@411: if remote_node not in nodes : terom@411: log.debug("%s:%s: lazy-add remote %s (%s)", host, port, remote_node, remote_label) terom@411: terom@411: nodes[remote_node] = remote_label terom@411: terom@411: # unknown vlans terom@418: if vlan : terom@418: link_vlans = (vlan, (), None) terom@418: else : terom@418: link_vlans = None terom@411: terom@411: # directional link terom@411: links_out[(local_node, remote_node)] = local_port terom@416: terom@416: if local_port : terom@416: bridge_ports.setdefault((local_node, local_port), set()).add(remote_node) terom@411: terom@411: # bidirectional link terom@411: forward = (local_node, local_port, None, remote_node) terom@411: terom@411: # scan for reverse terom@411: remote_port = links_out.get((remote_node, local_node)) terom@411: terom@411: if remote_port : terom@411: reverse = (remote_node, remote_port, None, local_node) terom@411: terom@417: log.info("%s:%s bridge <-> %s:%s", local_node, local_port, remote_port, remote_node) terom@411: terom@411: # fill in remote_port for bidirectional link terom@411: del links[reverse] terom@411: reverse = local_node, local_port, remote_port, remote_node terom@411: links[reverse] = link_vlans terom@411: terom@411: else : terom@417: log.info("%s:%s bridge -> %s", local_node, local_port, remote_node) terom@411: terom@411: links[forward] = link_vlans terom@416: terom@416: # verify unmanaged bridges terom@416: for (node, port), remotes in bridge_ports.iteritems() : terom@416: if len(remotes) > 1 : terom@416: log.warning("%s:%s: multiple bridge remotes: %s", node, port, ' '.join(str(host) for host in remotes)) terom@405: terom@405: return nodes, links terom@405: terom@405: class GraphVlans (object) : terom@405: """ terom@405: Maintain vlan -> dot style/color mappings terom@405: """ terom@405: terom@405: SERIES = 'paired12' terom@405: NONE = 'black' terom@405: terom@405: def __init__ (self, vlans=None) : terom@405: if vlans : terom@405: self.vlans = dict(vlans) terom@405: else : terom@405: self.vlans = { } terom@405: terom@405: def color (self, vlan) : terom@405: if vlan in self.vlans : terom@405: return self.vlans[vlan] terom@405: terom@405: # alloc terom@405: color = '/{series}/{index}'.format(series=self.SERIES, index=len(self.vlans) + 1) terom@405: terom@405: self.vlans[vlan] = color terom@405: terom@405: return color terom@405: terom@405: def dot_quote (value) : terom@405: """ terom@405: Quote a dot value. terom@405: """ terom@405: terom@405: return '"{value}"'.format(value=value) terom@405: terom@405: def dot (*line, **attrs) : terom@405: """ terom@405: Build dot-syntax: terom@405: *line { terom@405: *line [**attrs]; terom@405: } terom@405: """ terom@405: terom@405: if line and attrs : terom@405: return ''.join(('\t', ' '.join(str(x) for x in line), ' [', terom@409: ', '.join('{name}="{value}"'.format(name=name, value=value) for name, value in attrs.iteritems() if value is not None), terom@405: ']', ';')) terom@405: elif line : terom@405: return ' '.join(line) + ' {' terom@405: else : terom@405: return '}' terom@405: terom@405: def build_dot (options, nodes, links, type='digraph', vlans=None) : terom@405: """ terom@405: Construct a dot description of the given node/links graph. terom@405: """ terom@405: terom@405: if vlans is True : terom@405: vlans = { } terom@405: terom@405: yield dot(type, 'verkko') terom@405: terom@405: # defaults terom@405: yield dot('graph', terom@399: # XXX: breaks multi-edges? terom@399: #splines = 'true', terom@399: terom@399: sep = '+25,25', terom@399: overlap = 'scalexy', terom@399: terom@399: # only applies to loops terom@399: nodesep = 0.5, terom@399: ) terom@405: yield dot('edge', terom@405: labeldistance = 3.0, terom@405: penwidth = 2.0, terom@399: ) terom@405: yield dot('node', terom@405: fontsize = 18, terom@405: ) terom@405: terom@405: # nodes terom@405: for node, node_label in nodes.iteritems() : terom@405: yield dot(dot_quote(node), label=node_label) terom@399: terom@405: # links terom@409: for (local, local_port, remote_port, remote), link_vlans in links.iteritems() : terom@409: if link_vlans : terom@409: local_untag, tagged, remote_untag = link_vlans terom@409: terom@409: if vlans : terom@409: head_color = vlans.color(local_untag) if local_untag else None terom@409: tail_color = vlans.color(remote_untag) if remote_untag else None terom@409: line_colors = [vlans.color(tag) for tag in sorted(tagged)] terom@409: else : terom@409: head_color = GraphVlans.NONE if local_untag else None terom@409: tail_color = GraphVlans.NONE if remote_untag else None terom@409: line_colors = [] terom@405: else : terom@409: # unknown terom@409: head_color = tail_color = None terom@405: line_colors = [] terom@399: terom@405: if head_color and tail_color : terom@405: dir = 'both' terom@405: colors = [head_color, tail_color] + line_colors terom@405: elif head_color : terom@405: dir = 'forward' terom@405: colors = [head_color] + line_colors terom@405: elif tail_color : terom@405: dir = 'back' terom@405: colors = [vlans.NONE, tail_color] + line_colors terom@405: else : terom@405: dir = 'none' terom@405: colors = line_colors terom@399: terom@405: yield dot(dot_quote(local), '->', dot_quote(remote), terom@405: taillabel = local_port, terom@405: headlabel = remote_port, terom@405: dir = dir, terom@399: terom@405: fillcolor = 'black', terom@409: color = ':'.join(colors) if colors else None, terom@405: ) terom@399: terom@405: yield dot() terom@399: terom@405: def apply_dot (options, file, dot) : terom@405: """ terom@405: Output dot file for given graphbits terom@405: """ terom@405: terom@405: for line in dot : terom@405: file.write(line + '\n') terom@399: terom@399: def main (argv) : terom@399: """ terom@399: Graph network terom@399: """ terom@399: terom@399: parser = optparse.OptionParser(main.__doc__) terom@399: parser.add_option_group(pvl.args.parser(parser)) terom@399: parser.add_option_group(pvl.hosts.optparser(parser)) terom@399: terom@399: parser.add_option('--snmp-data', metavar='FILE', default=None, terom@399: help="Load snmp data from FILE") terom@399: terom@413: terom@413: parser.add_option('--graph-lldp-unknown', action='store_true', terom@413: help="Graph unknown LLDP nodes") terom@399: terom@409: terom@405: parser.add_option('--graph-vlans', action='store_true', dest='graph_vlans', terom@409: help="Graph links with VLAN information") terom@399: terom@405: parser.add_option('--no-vlans', action='store_false', dest='graph_vlans', terom@399: help="Do not color VLANs") terom@399: terom@413: terom@409: parser.add_option('--graph-bridge', action='store_true', terom@409: help="Graph bridge forwarding database links") terom@409: terom@417: parser.add_option('--graph-bridge-unknown', action='store_true', terom@417: help="Graph unknown bridge forwarding databse hosts") terom@417: terom@409: terom@413: parser.add_option('--graph-dot', metavar='FILE', terom@413: help="Output .dot graph data to file") terom@413: terom@399: # input terom@399: options, args = parser.parse_args(argv[1:]) terom@399: pvl.args.apply(options) terom@399: terom@408: # load hosts terom@408: hosts = list(pvl.hosts.apply(options, args)) terom@399: terom@399: # load raw snmp data terom@402: snmp = load_snmp_data(options, options.snmp_data, hosts) terom@399: terom@405: # process data into graph terom@409: nodes, links = build_graph(options, snmp, hosts) terom@405: terom@405: # process graph into dot terom@405: if options.graph_vlans is False : terom@405: graph_vlans = None terom@405: else : terom@405: graph_vlans = GraphVlans() terom@402: terom@405: if options.graph_dot : terom@405: # process to dot terom@405: dot = build_dot(options, nodes, links, vlans=graph_vlans) terom@405: terom@405: # write out terom@405: apply_dot(options, open(options.graph_dot, 'w'), dot) terom@399: terom@399: return 0 terom@399: terom@399: if __name__ == '__main__': terom@399: pvl.args.main(main)