terom@380: #!/usr/bin/env python terom@380: terom@380: import pvl.args terom@380: import pvl.hosts terom@395: from pvl.snmp import snmp, lldp, vlan, bridge terom@380: terom@399: import collections terom@384: import logging; log = logging.getLogger('pvl.hosts-lldp') terom@384: import optparse terom@382: terom@391: def hosts_snmp (options, hosts) : terom@382: """ terom@391: Discover SNMP-supporting hosts. terom@382: terom@391: Yields Host, snmpdata terom@382: """ terom@382: terom@382: for host in hosts : terom@384: host_snmp = host.extensions.get('snmp') terom@382: terom@384: if not host_snmp : terom@384: log.debug("%s: skip non-snmp host", host) terom@382: continue terom@382: terom@384: elif host.down : terom@384: log.debug("%s: skip down host", host) terom@384: continue terom@384: terom@384: else : terom@384: log.debug("%s: %s", host, host_snmp) terom@384: terom@391: yield host, host_snmp terom@391: terom@391: def hosts_lldp (options, hosts) : terom@391: """ terom@391: Discover LLDP-supporting hosts. terom@391: terom@391: Yields Host, LLDPAgent terom@391: """ terom@391: terom@391: for host, host_snmp in hosts_snmp(options, hosts) : terom@384: agent = lldp.LLDPAgent.apply(options, host.fqdn(), community=host_snmp.get('community')) terom@382: terom@382: try : terom@384: local = agent.local terom@384: except snmp.SNMPError as ex : terom@382: log.warning("%s: %s", host, ex) terom@382: continue terom@398: terom@398: if not local : terom@398: log.info("%s: no lldp support", host) terom@398: continue terom@382: terom@384: log.info("%s: %s", host, local) terom@384: terom@382: if local['sys_name'] != host.host : terom@382: log.warning("%s: SNMP sys_name mismatch: %s", host, local['sys_name']) terom@382: terom@384: yield host, agent terom@382: terom@391: def hosts_vlan (options, hosts) : terom@391: """ terom@391: Discover VLAN-supporting hosts. terom@391: terom@391: Yields Host, VLANAgent terom@391: """ terom@391: terom@391: for host, host_snmp in hosts_snmp(options, hosts) : terom@391: agent = vlan.VLANAgent.apply(options, host.fqdn(), community=host_snmp.get('community')) terom@391: terom@391: try : terom@391: count = agent.count terom@391: except snmp.SNMPError as ex : terom@391: log.warning("%s: %s", host, ex) terom@391: continue terom@391: terom@391: log.info("%s: %s", host, count) terom@391: terom@391: yield host, agent terom@391: terom@395: def hosts_bridge (options, hosts) : terom@395: """ terom@395: Discover Bridge-supporting hosts. terom@395: terom@395: Yields Host, BridgeAgent terom@395: """ terom@395: terom@395: for host, host_snmp in hosts_snmp(options, hosts) : terom@395: agent = bridge.BridgeAgent.apply(options, host.fqdn(), community=host_snmp.get('community')) terom@395: terom@395: try : terom@395: agent.ping() terom@395: except snmp.SNMPError as ex : terom@395: log.warning("%s: %s", host, ex) terom@395: continue terom@395: terom@395: log.info("%s", host) terom@395: terom@395: yield host, agent terom@395: terom@395: terom@382: def apply_hosts_lldp (options, hosts) : terom@382: """ terom@382: Query host LLDP info. terom@382: """ terom@384: terom@384: # second pass to discver links terom@399: for host, agent in hosts_lldp(options, hosts) : terom@398: try : terom@398: remotes = list(agent.remotes()) terom@398: except snmp.SNMPError as ex : terom@398: log.warn("%s: broken lldp remotes: %s", host, ex) terom@398: continue terom@398: terom@398: for port, remote in remotes : terom@384: port = agent.port(port) terom@384: terom@399: log.info("%s: %s: %s", host, port, remote) terom@382: terom@399: yield host, agent.local, port, remote terom@391: terom@391: def apply_hosts_vlan (options, hosts) : terom@391: """ terom@391: Query host VLAN ports. terom@395: terom@395: Yields host, { port: (untagged, [tagged]) } terom@391: """ terom@391: terom@391: _hosts_vlan = list(hosts_vlan(options, hosts)) terom@391: terom@391: for host, agent in _hosts_vlan : terom@391: # only one untagged vlan / port terom@391: vlan_untagged = { } terom@391: terom@391: # multiple taggd vlans / port terom@391: vlan_tagged = collections.defaultdict(set) terom@391: terom@398: for vlan, (tagged, untagged) in agent.vlan_ports() : terom@391: log.info("%s: %s: %s + %s", host, vlan, tagged, untagged) terom@391: terom@391: for port in tagged : terom@391: vlan_tagged[port].add(vlan) terom@391: terom@391: for port in untagged : terom@391: if port in vlan_untagged : terom@391: log.warning("%s: duplicate untagged vlan %s for port %s on vlan %s", host, vlan, port, vlan_untagged[port]) terom@391: terom@391: vlan_untagged[port] = vlan terom@391: terom@399: for port in set(vlan_untagged) | set(vlan_tagged) : terom@399: yield host, port, vlan_untagged.get(port), tuple(vlan_tagged[port]) terom@391: terom@395: def apply_hosts_bridge (options, hosts) : terom@395: """ terom@395: Query host bridge tables. terom@395: terom@395: Yields host, { port: (macs) } terom@395: """ terom@395: terom@395: for host, agent in hosts_bridge(options, hosts) : terom@395: ports = collections.defaultdict(list) terom@395: terom@398: try : terom@398: vlan_fdb_ports = list(agent.vlan_fdb_ports()) terom@398: except snmp.SNMPError as ex : terom@398: log.warn("%s: broken dot1q fdb: %s", host, ex) terom@398: continue terom@398: terom@398: if vlan_fdb_ports : terom@398: log.info("%s: have dot1q ports", host) terom@398: terom@398: for ether, port, vlan in agent.vlan_fdb_ports() : terom@398: if not port : terom@398: # XXX: unknown? terom@398: continue terom@398: terom@398: ports[(port, vlan)].append(ether) terom@398: else : terom@398: try : terom@398: fdb_ports = list(agent.fdb_ports()) terom@398: except snmp.SNMPError as ex : terom@398: log.warn("%s: broken dot1q fdb: %s", host, ex) terom@398: continue terom@398: terom@398: # fallback to dot1d fdb terom@398: log.info("%s: fallback to dot1d", host) terom@398: terom@398: for ether, port in agent.fdb_ports() : terom@398: if not port : terom@398: # XXX: unknown? terom@398: continue terom@398: terom@398: ports[(port, None)].append(ether) terom@387: terom@399: for (port, vlan), ethers in ports.iteritems() : terom@399: yield host, vlan, port, ethers terom@387: terom@399: def apply_hosts (options, hosts) : terom@399: """ terom@399: Gather data on given hosts... terom@391: terom@399: (host, key, value) terom@399: """ terom@399: terom@399: if options.scan or options.scan_lldp : terom@399: # discover node/port graph terom@399: for host, local, port, remote in apply_hosts_lldp(options, hosts) : terom@401: # XXX: duplicates galore terom@401: yield host, ('lldp', 'local'), { terom@401: 'chassis': local['chassis'], terom@401: 'sys_name': local['sys_name'], terom@401: } terom@401: terom@401: # XXX: duplicates galore terom@401: yield host, ('lldp', 'port', port['port_id'], 'local'), { terom@401: 'port': port['port'], terom@401: } terom@401: terom@401: yield host, ('lldp', 'port', port['port_id'], 'remote', remote['chassis']), { terom@401: 'sys_name': remote['sys_name'], terom@401: 'port': remote['port'], terom@401: } terom@399: terom@399: if options.scan or options.scan_vlan : terom@399: # discover vlan ports terom@399: for host, port, untag, tagged in apply_hosts_vlan(options, hosts) : terom@391: if untag : terom@401: yield host, ('vlan', untag, 'untagged'), set((port, )) terom@387: terom@401: for tag in tagged : terom@401: yield host, ('vlan', tag, 'tagged'), set((port, )) terom@399: terom@399: if options.scan or options.scan_bridge : terom@399: # discover edge nodes terom@399: for host, vlan, port, ethers in apply_hosts_bridge(options, hosts) : terom@399: if vlan : terom@401: yield host, ('vlan', vlan, 'bridge', port), set(ethers) terom@399: else : terom@401: yield host, ('bridge', port), set(ethers) terom@387: terom@380: def main (argv) : terom@380: """ terom@380: SNMP polling. terom@380: """ terom@380: terom@380: parser = optparse.OptionParser(main.__doc__) terom@380: parser.add_option_group(pvl.args.parser(parser)) terom@382: parser.add_option_group(pvl.hosts.optparser(parser)) terom@384: parser.add_option_group(pvl.snmp.snmp.options(parser)) terom@380: terom@399: parser.add_option('--scan', action='store_true') terom@399: parser.add_option('--scan-lldp', action='store_true') terom@399: parser.add_option('--scan-vlan', action='store_true') terom@399: parser.add_option('--scan-bridge', action='store_true') terom@393: terom@391: # input terom@380: options, args = parser.parse_args(argv[1:]) terom@380: pvl.args.apply(options) terom@380: terom@399: # gather SNMP data from hosts terom@382: hosts = pvl.hosts.apply(options, args) terom@382: terom@399: data = collections.defaultdict(dict) terom@395: terom@399: for host, attr, values in apply_hosts(options, hosts) : terom@401: if isinstance(values, set) : terom@401: log.info("[%s] %s + %s", host, attr, values) terom@401: terom@401: data[host].setdefault(attr, set()).update(values) terom@401: terom@401: elif isinstance(values, dict) : terom@401: log.info("[%s] %s = %s", host, attr, values) terom@401: terom@401: data[host][attr] = values terom@401: terom@399: else : terom@401: raise Exception("No value for [%s] %s" % (host, attr)) terom@398: terom@399: # output terom@399: for host, attrs in sorted(data.items()) : terom@407: print "{host}@{domain}".format(host=host, domain=host.domain) terom@399: terom@399: for attr, value in sorted(attrs.items()) : terom@403: print "\t{attr}".format(attr=' '.join(str(a) for a in attr)) terom@399: terom@401: if isinstance(value, set) : terom@399: for v in sorted(value) : terom@399: print "\t\t{value}".format(value=v) terom@401: elif isinstance(value, dict) : terom@401: for k, v in sorted(value.items()) : terom@403: print "\t\t{key}\t{value}".format(key=k, value=v) terom@401: else : terom@401: raise Exception("[%s] %s: invalid value: %s" % (host, attr, value)) terom@399: terom@399: return 0 terom@380: terom@380: if __name__ == '__main__': terom@380: pvl.args.main(main) terom@399: