--- a/bin/pvl.hosts-lldp Mon Mar 17 17:19:30 2014 +0200
+++ b/bin/pvl.hosts-lldp Mon Mar 17 17:31:45 2014 +0200
@@ -16,238 +16,10 @@
import pvl.args
import pvl.hosts
from pvl.invoke import merge
-
-import logging; log = logging.getLogger('pvl.hosts-snmp')
-import optparse
-
-from pysnmp.entity.rfc3413.oneliner import cmdgen as pysnmp
-import collections
-
-class SNMPError (Exception) :
- pass
-
-class SNMPEngineError (SNMPError) :
- """
- Internal SNMP Engine error (?)
- """
-
-class SNMPAgent (object) :
- """
- GET SNMP shit from a remote host.
- """
-
- SNMP_PORT = 161
- snmp_cmdgen = pysnmp.CommandGenerator()
-
- @classmethod
- 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)
-
- if ':' in host :
- host, port = host.rsplit(':', 1)
-
- return cls(
- pysnmp.CommunityData(community),
- pysnmp.UdpTransportTarget((host, port))
- )
-
- def __init__ (self, security, transport) :
- self.security = security
- self.transport = transport
-
- def get (self, *request) :
- """
- request = (
- pysnmp.MibVariable('IF-MIB', 'ifInOctets', 1),
- )
- """
-
- opts = dict(
- lookupNames = True,
- lookupValues = True,
- )
-
- try :
- error, error_status, error_index, response = self.snmp_cmdgen.getCmd(self.security, self.transport, *request, **opts)
- except pysnmp.error.PySnmpError as ex :
- raise SNMPEngineError(ex)
-
- if error :
- raise SNMPEngineError(error)
-
- if error_status :
- raise SNMPError(errorStatus.prettyPrint())
-
- return response
- #for name, value in response :
- # yield name.getMibSymbol(), value.prettyPrint()
-
- def walk (self, *request) :
- """
- request = (
- pysnmp.MibVariable('IF-MIB', 'ifInOctets'),
- )
- """
-
- opts = dict(
- lookupNames = True,
- lookupValues = True,
- )
-
- try :
- error, error_status, error_index, responses = self.snmp_cmdgen.nextCmd(self.security, self.transport, *request, **opts)
- except pysnmp.error.PySnmpError as ex :
- raise SNMPEngineError(ex)
-
- if error :
- raise SNMPEngineError(error)
-
- if error_status :
- raise SNMPError(errorStatus.prettyPrint())
-
- return responses
- #for response in responses:
- # for name, value in response :
- # yield name.getMibSymbol(), value.prettyPrint()
-
- def table (self, *columns) :
- """
- Given [oid] returns { idx: { oid: value } }
- """
-
- data = collections.defaultdict(dict)
-
- for row in self.walk(*columns) :
- log.debug("%s", row)
-
- for column, (field, value) in zip(columns, row) :
- mib, sym, idx = field.getMibSymbol()
+from pvl.snmp import snmp, lldp
- log.debug("%s::%s[%s]: %s: %s", mib, sym, ', '.join(str(x) for x in idx), column, value)
-
- data[idx][column] = value
-
- return data.items()
-
-from memoized_property import memoized_property
-
-def macaddr (value) :
- """
- Excepts a MAC address from an SNMP OctetString.
- """
-
- return ':'.join('{octet:02x}'.format(octet=c) for c in value.asNumbers())
-
-class LLDPAgent (SNMPAgent) :
- """
- Query LLDP info from a remote agent.
- """
-
-
- @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.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]),
- }
-
- LLDP_LOC_PORT_ID = pysnmp.MibVariable('LLDP-MIB', 'lldpLocPortId')
- LLDP_LOC_PORT_ID_SUBTYPE = pysnmp.MibVariable('LLDP-MIB', 'lldpLocPortIdSubtype')
-
- @memoized_property
- def ports (self) :
- """
- Describe the local ports.
- """
-
- ports = { }
-
- for idx, data in self.table(
- self.LLDP_LOC_PORT_ID,
- self.LLDP_LOC_PORT_ID_SUBTYPE,
- ) :
- port, = idx
-
- 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[int(port)]
-
- 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.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), {
- '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]),
- }
+import logging; log = logging.getLogger('pvl.hosts-lldp')
+import optparse
def hosts_lldp (options, hosts) :
"""
@@ -257,40 +29,61 @@
"""
for host in hosts :
- snmp = host.extensions.get('snmp')
+ host_snmp = host.extensions.get('snmp')
- log.debug("%s: %s", host, snmp)
- if not snmp :
+ if not host_snmp :
+ log.debug("%s: skip non-snmp host", host)
continue
- lldp = LLDPAgent.apply(options, host.fqdn(), community=snmp.get('community'))
+ elif host.down :
+ log.debug("%s: skip down host", host)
+ continue
+
+ else :
+ log.debug("%s: %s", host, host_snmp)
+
+ agent = lldp.LLDPAgent.apply(options, host.fqdn(), community=host_snmp.get('community'))
try :
- local = lldp.local
- except SNMPError as ex :
+ local = agent.local
+ except snmp.SNMPError as ex :
log.warning("%s: %s", host, ex)
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, lldp
+ yield host, agent
def apply_hosts_lldp (options, hosts) :
"""
Query host LLDP info.
"""
+
+ _hosts_lldp = list(hosts_lldp(options, hosts))
+ hosts_by_chassis = { }
+
+ # first pass to discover hosts
+ for host, agent in _hosts_lldp :
+ chassis = agent.local['chassis']
+ log.info("%s: %s", host, chassis)
- 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)
+ hosts_by_chassis[chassis] = host
+
+ # second pass to discver links
+ for host, agent in _hosts_lldp :
+ for port, remote in agent.remotes() :
+ port = agent.port(port)
+
+ remote_chassis = remote['chassis']
+ remote_host = hosts_by_chassis.get(remote_chassis)
- log.info("%s: %s: %s", host, port, remote)
+ log.info("%s: %s: %s (%s)", host, port, remote, remote_host)
- yield host, merge(lldp.local, port), remote
+ yield host, merge(agent.local, port), remote, remote_host
def main (argv) :
"""
@@ -300,9 +93,8 @@
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('--snmp-community',
- help="SNMPv2 read community")
options, args = parser.parse_args(argv[1:])
pvl.args.apply(options)
@@ -311,12 +103,11 @@
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),
- )
+ for host, local, remote, remote_host in apply_hosts_lldp(options, hosts) :
+ if remote_host :
+ print "{host:30} {local[port]:25} <-> {remote[port]:25} {remote_host:30}".format(host=host, local=local, remote=remote, remote_host=remote_host)
+ else :
+ print "{host:30} {local[port]:25} <-- {remote[port]:25} # {remote[chassis]} ({remote[sys_name]})".format(host=host, local=local, remote=remote)
if __name__ == '__main__':
pvl.args.main(main)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/pvl/snmp/snmp.py Mon Mar 17 17:31:45 2014 +0200
@@ -0,0 +1,133 @@
+"""
+ Requirements:
+ pysnmp
+ pysnmp-mibs
+"""
+
+import logging; log = logging.getLogger('pvl.snmp.snmp')
+import optparse
+
+from pysnmp.entity.rfc3413.oneliner import cmdgen as pysnmp
+import collections
+
+def options (parser) :
+ parser = optparse.OptionGroup(parser, "SNMP Agent")
+
+ parser.add_option('--snmp-community',
+ help="SNMPv2 read community")
+
+ return parser
+
+class SNMPError (Exception) :
+ pass
+
+class SNMPEngineError (SNMPError) :
+ """
+ Internal SNMP Engine error (?)
+ """
+
+class SNMPAgent (object) :
+ """
+ GET SNMP shit from a remote host.
+ """
+
+ SNMP_PORT = 161
+ snmp_cmdgen = pysnmp.CommandGenerator()
+
+ @classmethod
+ 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)
+
+ if ':' in host :
+ host, port = host.rsplit(':', 1)
+
+ return cls(
+ pysnmp.CommunityData(community),
+ pysnmp.UdpTransportTarget((host, port))
+ )
+
+ def __init__ (self, security, transport) :
+ self.security = security
+ self.transport = transport
+
+ def get (self, *request) :
+ """
+ request = (
+ pysnmp.MibVariable('IF-MIB', 'ifInOctets', 1),
+ )
+ """
+
+ opts = dict(
+ lookupNames = True,
+ lookupValues = True,
+ )
+
+ try :
+ error, error_status, error_index, response = self.snmp_cmdgen.getCmd(self.security, self.transport, *request, **opts)
+ except pysnmp.error.PySnmpError as ex :
+ raise SNMPEngineError(ex)
+
+ if error :
+ raise SNMPEngineError(error)
+
+ if error_status :
+ raise SNMPError(errorStatus.prettyPrint())
+
+ return response
+ #for name, value in response :
+ # yield name.getMibSymbol(), value.prettyPrint()
+
+ def walk (self, *request) :
+ """
+ request = (
+ pysnmp.MibVariable('IF-MIB', 'ifInOctets'),
+ )
+ """
+
+ opts = dict(
+ lookupNames = True,
+ lookupValues = True,
+ )
+
+ try :
+ error, error_status, error_index, responses = self.snmp_cmdgen.nextCmd(self.security, self.transport, *request, **opts)
+ except pysnmp.error.PySnmpError as ex :
+ raise SNMPEngineError(ex)
+
+ if error :
+ raise SNMPEngineError(error)
+
+ if error_status :
+ raise SNMPError(errorStatus.prettyPrint())
+
+ return responses
+ #for response in responses:
+ # for name, value in response :
+ # yield name.getMibSymbol(), value.prettyPrint()
+
+ def table (self, *columns) :
+ """
+ Given [oid] returns { idx: { oid: value } }
+ """
+
+ data = collections.defaultdict(dict)
+
+ for row in self.walk(*columns) :
+ log.debug("%s", row)
+
+ for column, (field, value) in zip(columns, row) :
+ mib, sym, idx = field.getMibSymbol()
+
+ log.debug("%s::%s[%s]: %s: %s", mib, sym, ', '.join(str(x) for x in idx), column, value)
+
+ data[idx][column] = value
+
+ return data.items()
+