bin/pvl.hosts-snmp
changeset 399 aadf76a05ec1
parent 398 de275bf6db70
child 401 e57c200f3e26
equal deleted inserted replaced
398:de275bf6db70 399:aadf76a05ec1
       
     1 #!/usr/bin/env python
       
     2 
       
     3 import pvl.args
       
     4 import pvl.hosts
       
     5 from pvl.snmp import snmp, lldp, vlan, bridge
       
     6 
       
     7 import collections
       
     8 import logging; log = logging.getLogger('pvl.hosts-lldp')
       
     9 import optparse
       
    10 
       
    11 def hosts_snmp (options, hosts) :
       
    12     """
       
    13         Discover SNMP-supporting hosts.
       
    14 
       
    15         Yields Host, snmpdata
       
    16     """
       
    17 
       
    18     for host in hosts :
       
    19         host_snmp = host.extensions.get('snmp')
       
    20 
       
    21         if not host_snmp :
       
    22             log.debug("%s: skip non-snmp host", host)
       
    23             continue
       
    24 
       
    25         elif host.down :
       
    26             log.debug("%s: skip down host", host)
       
    27             continue
       
    28 
       
    29         else :
       
    30             log.debug("%s: %s", host, host_snmp)
       
    31 
       
    32         yield host, host_snmp
       
    33 
       
    34 def hosts_lldp (options, hosts) :
       
    35     """
       
    36         Discover LLDP-supporting hosts.
       
    37 
       
    38         Yields Host, LLDPAgent
       
    39     """
       
    40 
       
    41     for host, host_snmp in hosts_snmp(options, hosts) :
       
    42         agent = lldp.LLDPAgent.apply(options, host.fqdn(), community=host_snmp.get('community'))
       
    43 
       
    44         try :
       
    45             local = agent.local
       
    46         except snmp.SNMPError as ex :
       
    47             log.warning("%s: %s", host, ex)
       
    48             continue
       
    49         
       
    50         if not local :
       
    51             log.info("%s: no lldp support", host)
       
    52             continue
       
    53 
       
    54         log.info("%s: %s", host, local)
       
    55 
       
    56         if local['sys_name'] != host.host :
       
    57             log.warning("%s: SNMP sys_name mismatch: %s", host, local['sys_name'])
       
    58 
       
    59         yield host, agent
       
    60 
       
    61 def hosts_vlan (options, hosts) :
       
    62     """
       
    63         Discover VLAN-supporting hosts.
       
    64 
       
    65         Yields Host, VLANAgent
       
    66     """
       
    67 
       
    68     for host, host_snmp in hosts_snmp(options, hosts) :
       
    69         agent = vlan.VLANAgent.apply(options, host.fqdn(), community=host_snmp.get('community'))
       
    70 
       
    71         try :
       
    72             count = agent.count
       
    73         except snmp.SNMPError as ex :
       
    74             log.warning("%s: %s", host, ex)
       
    75             continue
       
    76 
       
    77         log.info("%s: %s", host, count)
       
    78 
       
    79         yield host, agent
       
    80 
       
    81 def hosts_bridge (options, hosts) :
       
    82     """
       
    83         Discover Bridge-supporting hosts.
       
    84 
       
    85         Yields Host, BridgeAgent
       
    86     """
       
    87 
       
    88     for host, host_snmp in hosts_snmp(options, hosts) :
       
    89         agent = bridge.BridgeAgent.apply(options, host.fqdn(), community=host_snmp.get('community'))
       
    90 
       
    91         try :
       
    92             agent.ping()
       
    93         except snmp.SNMPError as ex :
       
    94             log.warning("%s: %s", host, ex)
       
    95             continue
       
    96 
       
    97         log.info("%s", host)
       
    98 
       
    99         yield host, agent
       
   100 
       
   101 
       
   102 def apply_hosts_lldp (options, hosts) :
       
   103     """
       
   104         Query host LLDP info.
       
   105     """
       
   106     
       
   107     # second pass to discver links
       
   108     for host, agent in hosts_lldp(options, hosts) :
       
   109         try :
       
   110             remotes = list(agent.remotes())
       
   111         except snmp.SNMPError as ex :
       
   112             log.warn("%s: broken lldp remotes: %s", host, ex)
       
   113             continue
       
   114 
       
   115         for port, remote in remotes :
       
   116             port = agent.port(port)
       
   117 
       
   118             log.info("%s: %s: %s", host, port, remote)
       
   119 
       
   120             yield host, agent.local, port, remote
       
   121 
       
   122 def apply_hosts_vlan (options, hosts) :
       
   123     """
       
   124         Query host VLAN ports.
       
   125 
       
   126         Yields host, { port: (untagged, [tagged]) }
       
   127     """
       
   128 
       
   129     _hosts_vlan = list(hosts_vlan(options, hosts))
       
   130 
       
   131     for host, agent in _hosts_vlan :
       
   132         # only one untagged vlan / port
       
   133         vlan_untagged = { }
       
   134             
       
   135         # multiple taggd vlans / port
       
   136         vlan_tagged = collections.defaultdict(set)
       
   137 
       
   138         for vlan, (tagged, untagged) in agent.vlan_ports() :
       
   139             log.info("%s: %s: %s + %s", host, vlan, tagged, untagged)
       
   140             
       
   141             for port in tagged :
       
   142                 vlan_tagged[port].add(vlan)
       
   143             
       
   144             for port in untagged :
       
   145                 if port in vlan_untagged :
       
   146                     log.warning("%s: duplicate untagged vlan %s for port %s on vlan %s", host, vlan, port, vlan_untagged[port])
       
   147 
       
   148                 vlan_untagged[port] = vlan
       
   149             
       
   150         for port in set(vlan_untagged) | set(vlan_tagged) :
       
   151             yield host, port, vlan_untagged.get(port), tuple(vlan_tagged[port])
       
   152 
       
   153 def apply_hosts_bridge (options, hosts) :
       
   154     """
       
   155         Query host bridge tables.
       
   156 
       
   157         Yields host, { port: (macs) }
       
   158     """
       
   159 
       
   160     for host, agent in hosts_bridge(options, hosts) :
       
   161         ports = collections.defaultdict(list)
       
   162 
       
   163         try :
       
   164             vlan_fdb_ports = list(agent.vlan_fdb_ports())
       
   165         except snmp.SNMPError as ex :
       
   166             log.warn("%s: broken dot1q fdb: %s", host, ex)
       
   167             continue
       
   168 
       
   169         if vlan_fdb_ports :
       
   170             log.info("%s: have dot1q ports", host)
       
   171 
       
   172             for ether, port, vlan in agent.vlan_fdb_ports() :
       
   173                 if not port :
       
   174                     # XXX: unknown?
       
   175                     continue
       
   176 
       
   177                 ports[(port, vlan)].append(ether)
       
   178         else :
       
   179             try :
       
   180                 fdb_ports = list(agent.fdb_ports())
       
   181             except snmp.SNMPError as ex :
       
   182                 log.warn("%s: broken dot1q fdb: %s", host, ex)
       
   183                 continue
       
   184             
       
   185             # fallback to dot1d fdb
       
   186             log.info("%s: fallback to dot1d", host)
       
   187 
       
   188             for ether, port in agent.fdb_ports() :
       
   189                 if not port :
       
   190                     # XXX: unknown?
       
   191                     continue
       
   192 
       
   193                 ports[(port, None)].append(ether)
       
   194         
       
   195         for (port, vlan), ethers in ports.iteritems() :
       
   196             yield host, vlan, port, ethers
       
   197 
       
   198 def apply_hosts (options, hosts) :
       
   199     """
       
   200         Gather data on given hosts...
       
   201 
       
   202             (host, key, value)
       
   203     """
       
   204     
       
   205     if options.scan or options.scan_lldp :
       
   206         # discover node/port graph
       
   207         for host, local, port, remote in apply_hosts_lldp(options, hosts) :
       
   208             yield host, ('lldp', ), set((local['chassis'], ))
       
   209 
       
   210             yield host, ('port', port['port'], 'lldp', remote['chassis'], 'port', remote['port']), None
       
   211     
       
   212     if options.scan or options.scan_vlan :
       
   213         # discover vlan ports
       
   214         for host, port, untag, tagged in apply_hosts_vlan(options, hosts) :
       
   215             if untag :
       
   216                 yield host, ('port', port, 'untagged', untag), None
       
   217 
       
   218             if tagged :
       
   219                 yield host, ('port', port, 'tagged'), set(tagged)
       
   220     
       
   221     if options.scan or options.scan_bridge :
       
   222         # discover edge nodes
       
   223         for host, vlan, port, ethers in apply_hosts_bridge(options, hosts) :
       
   224             if vlan :
       
   225                 yield host, ('port', port, 'bridge', 'vlan', vlan), set(ethers)
       
   226             else :
       
   227                 yield host, ('port', port, 'bridge'), set(ethers)
       
   228 
       
   229 def main (argv) :
       
   230     """
       
   231         SNMP polling.
       
   232     """
       
   233 
       
   234     parser = optparse.OptionParser(main.__doc__)
       
   235     parser.add_option_group(pvl.args.parser(parser))
       
   236     parser.add_option_group(pvl.hosts.optparser(parser))
       
   237     parser.add_option_group(pvl.snmp.snmp.options(parser))
       
   238 
       
   239     parser.add_option('--scan', action='store_true')
       
   240     parser.add_option('--scan-lldp', action='store_true')
       
   241     parser.add_option('--scan-vlan', action='store_true')
       
   242     parser.add_option('--scan-bridge', action='store_true')
       
   243 
       
   244     # input
       
   245     options, args = parser.parse_args(argv[1:])
       
   246     pvl.args.apply(options)
       
   247 
       
   248     # gather SNMP data from hosts
       
   249     hosts = pvl.hosts.apply(options, args)
       
   250     
       
   251     data = collections.defaultdict(dict)
       
   252 
       
   253     for host, attr, values in apply_hosts(options, hosts) :
       
   254         log.info("[%s] %s%s", host, ' '.join(str(a) for a in attr), (': ' + ' '.join(str(value) for value in values)) if values else '')
       
   255         
       
   256         if values is None :
       
   257             data[host][attr] = None
       
   258         else :
       
   259             # merge
       
   260             data[host].setdefault(attr, set()).update(values)
       
   261 
       
   262     # output
       
   263     for host, attrs in sorted(data.items()) :
       
   264         print "{host}".format(host=host)
       
   265 
       
   266         for attr, value in sorted(attrs.items()) :
       
   267             print "\t{attr}".format(attr=' '.join(str(a) for a in attr))
       
   268 
       
   269             if value :
       
   270                 for v in sorted(value) :
       
   271                     print "\t\t{value}".format(value=v)
       
   272 
       
   273     return 0
       
   274 
       
   275 if __name__ == '__main__':
       
   276     pvl.args.main(main)
       
   277