--- a/bin/pvl.hosts-lldp Mon Mar 17 15:51:08 2014 +0200
+++ b/bin/pvl.hosts-lldp Mon Mar 17 15:52:42 2014 +0200
@@ -15,6 +15,7 @@
import pvl.args
import pvl.hosts
+from pvl.invoke import merge
import logging; log = logging.getLogger('pvl.hosts-snmp')
import optparse
@@ -39,10 +40,13 @@
snmp_cmdgen = pysnmp.CommandGenerator()
@classmethod
- def apply (cls, options, host) :
- community = options.snmp_community
+ def apply (cls, options, host, community=None) :
port = cls.SNMP_PORT
+ if community is None :
+ community = options.snmp_community
+
+
if '@' in host :
community, host = host.split('@', 1)
@@ -113,28 +117,22 @@
# for name, value in response :
# yield name.getMibSymbol(), value.prettyPrint()
- def table (self, table) :
+ def table (self, *columns) :
"""
- Load a simple table with a running index.
-
- Given [(attr, oid, type)] yields { attr: type(value) }
+ Given [oid] returns { idx: { oid: value } }
"""
data = collections.defaultdict(dict)
- for row in self.walk(*[oid for attr, oid, type in table]) :
+ for row in self.walk(*columns) :
log.debug("%s", row)
- for (attr, type), (field, value) in zip([(attr, type) for attr, oid, type in table], row) :
+ for column, (field, value) in zip(columns, row) :
mib, sym, idx = field.getMibSymbol()
- # convert
- if type :
- value = type(value)
-
- log.debug("%s::%s.%s: %s: %s", mib, sym, idx, attr, value)
+ log.debug("%s::%s[%s]: %s: %s", mib, sym, ', '.join(str(x) for x in idx), column, value)
- data[idx][attr] = value
+ data[idx][column] = value
return data.items()
@@ -152,23 +150,55 @@
Query LLDP info from a remote agent.
"""
- LOCAL = (
- ( 'chassis_id', pysnmp.MibVariable('LLDP-MIB', 'lldpLocChassisId'), macaddr ),
- ( 'sys_name', pysnmp.MibVariable('LLDP-MIB', 'lldpLocSysName'), str ),
- )
-
+
+ @classmethod
+ def _chassis_id (cls, chassis_id, subtype) :
+ log.debug("%s: %r", subtype, chassis_id)
+
+ # XXX: reference names from LLDP-MIB.py
+ if subtype == 4:
+ return macaddr(chassis_id)
+ else :
+ return chassis_id
+
+ @classmethod
+ def _port_id (cls, port_id, subtype) :
+ log.debug("%s: %r", subtype, port_id)
+
+ # XXX: reference names from LLDP-MIB.py
+ if subtype == 5: # interfaceName -> IF-MIB::ifName ?
+ return str(port_id)
+ elif subtype == 3 : # macAddress
+ return macaddr(port_id)
+ elif subtype == 7 : # local
+ return str(port_id) # XXX: integer?
+ else :
+ log.warn("unknown subtype=%d: %r", subtype, port_id)
+
+ return port_id
+
+ LLDP_LOC_CHASSIS_ID = pysnmp.MibVariable('LLDP-MIB', 'lldpLocChassisId')
+ LLDP_LOC_CHASSIS_ID_SUBTYPE = pysnmp.MibVariable('LLDP-MIB', 'lldpLocChassisIdSubtype')
+ LLDP_LOC_SYS_NAME = pysnmp.MibVariable('LLDP-MIB', 'lldpLocSysName')
+
@memoized_property
def local (self) :
"""
Describe the local system.
"""
- for idx, data in self.table(self.LOCAL) :
- return data
+ for idx, data in self.table(
+ self.LLDP_LOC_CHASSIS_ID,
+ self.LLDP_LOC_CHASSIS_ID_SUBTYPE,
+ self.LLDP_LOC_SYS_NAME,
+ ) :
+ return {
+ 'chassis': self._chassis_id(data[self.LLDP_LOC_CHASSIS_ID], data[self.LLDP_LOC_CHASSIS_ID_SUBTYPE]),
+ 'sys_name': str(data[self.LLDP_LOC_SYS_NAME]),
+ }
- PORTS = (
- ( 'port_id', pysnmp.MibVariable('LLDP-MIB', 'lldpLocPortId'), str ),
- )
+ LLDP_LOC_PORT_ID = pysnmp.MibVariable('LLDP-MIB', 'lldpLocPortId')
+ LLDP_LOC_PORT_ID_SUBTYPE = pysnmp.MibVariable('LLDP-MIB', 'lldpLocPortIdSubtype')
@memoized_property
def ports (self) :
@@ -178,31 +208,89 @@
ports = { }
- for idx, data in self.table(self.PORTS) :
+ for idx, data in self.table(
+ self.LLDP_LOC_PORT_ID,
+ self.LLDP_LOC_PORT_ID_SUBTYPE,
+ ) :
port, = idx
- ports[int(port)] = data
+ ports[int(port)] = {
+ 'port': self._port_id(data[self.LLDP_LOC_PORT_ID], data[self.LLDP_LOC_PORT_ID_SUBTYPE]),
+ }
return ports
def port (self, port) :
- return self.ports[port]
+ return self.ports[int(port)]
- REMOTE_DATA = (
- ( 'chassis_id', pysnmp.MibVariable('LLDP-MIB', 'lldpRemChassisId'), macaddr ),
- ( 'sys_name', pysnmp.MibVariable('LLDP-MIB', 'lldpRemSysName'), str ),
- ( 'remote_port', pysnmp.MibVariable('LLDP-MIB', 'lldpRemPortId'), str ),
- )
+ LLDP_REM_CHASSIS_ID = pysnmp.MibVariable('LLDP-MIB', 'lldpRemChassisId')
+ LLDP_REM_CHASSIS_ID_SUBTYPE = pysnmp.MibVariable('LLDP-MIB', 'lldpRemChassisIdSubtype')
+ LLDP_REM_SYS_NAME = pysnmp.MibVariable('LLDP-MIB', 'lldpRemSysName')
+ LLDP_REM_PORT_ID = pysnmp.MibVariable('LLDP-MIB', 'lldpRemPortId')
+ LLDP_REM_PORT_ID_SUBTYPE = pysnmp.MibVariable('LLDP-MIB', 'lldpRemPortIdSubtype')
def remotes (self) :
"""
Describe remote systems, indexed by local port.
"""
- for idx, data in self.table(self.REMOTE_DATA) :
+ for idx, data in self.table(
+ self.LLDP_REM_CHASSIS_ID,
+ self.LLDP_REM_CHASSIS_ID_SUBTYPE,
+ self.LLDP_REM_SYS_NAME,
+ self.LLDP_REM_PORT_ID,
+ self.LLDP_REM_PORT_ID_SUBTYPE,
+ ) :
time, port, idx = idx
- yield int(port), data
+ yield int(port), {
+ 'chassis': self._chassis_id(data[self.LLDP_REM_CHASSIS_ID], data[self.LLDP_REM_CHASSIS_ID_SUBTYPE]),
+ 'sys_name': str(data[self.LLDP_REM_SYS_NAME]),
+ 'port': self._port_id(data[self.LLDP_REM_PORT_ID], data[self.LLDP_REM_PORT_ID_SUBTYPE]),
+ }
+
+def hosts_lldp (options, hosts) :
+ """
+ Discover LLDP-supporting hosts.
+
+ Yields Host, LLDPAgent
+ """
+
+ for host in hosts :
+ snmp = host.extensions.get('snmp')
+
+ log.debug("%s: %s", host, snmp)
+
+ if not snmp :
+ continue
+
+ lldp = LLDPAgent.apply(options, host.fqdn(), community=snmp.get('community'))
+
+ try :
+ local = lldp.local
+ except SNMPError as ex :
+ log.warning("%s: %s", host, ex)
+ continue
+
+ if local['sys_name'] != host.host :
+ log.warning("%s: SNMP sys_name mismatch: %s", host, local['sys_name'])
+
+ yield host, lldp
+
+def apply_hosts_lldp (options, hosts) :
+ """
+ Query host LLDP info.
+ """
+
+ for host, lldp in hosts_lldp(options, hosts) :
+ log.info("%s: %s", host, lldp.local)
+
+ for port, remote in lldp.remotes() :
+ port = lldp.port(port)
+
+ log.info("%s: %s: %s", host, port, remote)
+
+ yield host, merge(lldp.local, port), remote
def main (argv) :
"""
@@ -211,7 +299,7 @@
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.hosts.optparser(parser))
parser.add_option('--snmp-community',
help="SNMPv2 read community")
@@ -220,15 +308,15 @@
pvl.args.apply(options)
# input
- #hosts = pvl.hosts.apply(options, args)
-
- for arg in args :
- lldp = LLDPAgent.apply(options, arg)
-
- print arg, "{chassis_id}({sys_name})".format(**lldp.local)
-
- for port, remote in lldp.remotes() :
- print '\t', lldp.port(port), remote
+ hosts = pvl.hosts.apply(options, args)
+
+ # apply
+ for host, local, remote in apply_hosts_lldp(options, hosts) :
+ print "{host:30} {local:40} <- {remote:40}".format(
+ host = host,
+ local = "{local[chassis]:15}[{local[port]}]".format(local=local),
+ remote = "{remote[chassis]:15}[{remote[port]}]".format(remote=remote),
+ )
if __name__ == '__main__':
pvl.args.main(main)