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