Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
snmpclient is written and maintained by Dennis Kaarsemaker and
various contributors:

Development Lead
````````````````

- Dennis Kaarsemaker <dennis@kaarsemaker.net>

Patches and Suggestions
```````````````````````

- Robert Wlodarczyk <robert@simplicityguy.com>
14 changes: 10 additions & 4 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,11 @@ The SnmpClient class
This class wraps arround pysnmp's cmdgen.CommandGenerator to make it easier to
address an snmp daemon.

snmpclient.SnmpClient(host, port, authorizations)
----------------------------------------
The constructor takes a hostname/ip address, UDP port and a list of
authorization objects.
snmpclient.SnmpClient(host, port, read_authorizations, write_authorizations, timeout, retries)
----------------------------------------------------------------------------------------------
The constructor takes a hostname/ip address, UDP port, and a list of
read authorization objects. Optionally, a list of write authorization objects,
timeout, and number of retries may be provided as well.

These objects are created as follows:

Expand All @@ -60,6 +61,11 @@ snmpclient.SnmpClient.get(oid)
Takes a named oid, queries the server and returns the value of that oid on the
server.

snmpclient.SnmpClient.set(oid, value)
------------------------------
Takes a named oid and value, queries the server to determine its type, and
subsequently sets value to the newly provided one.

snmpclient.SnmpClient.gettable(oid)
-----------------------------------
Takes a named oid, walks that table and returns a list of (oid, value) pairs in
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@
"Topic :: System :: Monitoring",
"Topic :: Software Development"
],
#install_requires='pysnmp',
install_requires=['pysnmp'],
)
114 changes: 101 additions & 13 deletions snmpclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,25 @@
import pysnmp.entity.rfc3413.oneliner.cmdgen as cmdgen
from pysnmp.smi import builder, view
from pysnmp.smi.error import SmiError
from pysnmp.proto import rfc1902

__all__ = ['V1', 'V2', 'V2C', 'add_mib_path', 'load_mibs',
'nodeinfo', 'nodename', 'nodeid', 'SnmpClient', 'cmdgen']

# Snmp version constants
# SNMP version constants
V1 = 0
V2 = V2C = 1

# The internal mib builder
__mibBuilder = builder.MibBuilder()
__mibViewController = view.MibViewController(__mibBuilder)


def add_mib_path(*path):
"""Add a directory to the MIB search path"""
__mibBuilder.setMibPath(*(__mibBuilder.getMibPath() + path))
mibPath = __mibBuilder.getMibPath() + path
__mibBuilder.setMibPath(*mibPath)


def load_mibs(*modules):
"""Load one or more mibs"""
Expand All @@ -45,16 +49,15 @@ def load_mibs(*modules):
continue
raise

# Load basic mibs that come with pysnmp
load_mibs('SNMPv2-MIB','IF-MIB','IP-MIB','HOST-RESOURCES-MIB','FIBRE-CHANNEL-FE-MIB')

def nodeinfo(oid):
"""Translate dotted-decimal oid to a tuple with symbolic info"""
if isinstance(oid, basestring):
oid = tuple([int(x) for x in oid.split('.') if x])
return (__mibViewController.getNodeLocation(oid),
return (__mibViewController.getNodeLocation(oid),
__mibViewController.getNodeName(oid))


def nodename(oid):
"""Translate dotted-decimal oid or oid tuple to symbolic name"""
oid = __mibViewController.getNodeLocation(oid)
Expand All @@ -63,7 +66,8 @@ def nodename(oid):
if noid:
name += '.' + noid
return name



def nodeid(oid):
"""Translate named oid to dotted-decimal format"""
ids = oid.split('.')
Expand All @@ -73,48 +77,132 @@ def nodeid(oid):
oid = mibnode.getName() + ids
return oid


# Load basic mibs that come with pysnmp
load_mibs('SNMPv2-MIB',
'IF-MIB',
'IP-MIB',
'HOST-RESOURCES-MIB',
'FIBRE-CHANNEL-FE-MIB')


class SnmpClient(object):
"""Easy access to an snmp deamon on a host"""

def __init__(self, host, port, authorizations):
def __init__(self, host, port, read_authorizations, write_authorizations,
timeout=1, retries=2):
"""Set up the client and detect the community to use"""
self.host = host
self.port = port
self.timeout = timeout
self.retries = retries
self.alive = False
self.transport = cmdgen.UdpTransportTarget((self.host, self.port),
timeout=self.timeout,
retries=self.retries,)
self.readauth = None
self.writeauth = None

# Which community to use
# Determine which community to use for reading values
noid = nodeid('SNMPv2-MIB::sysName.0')
for auth in authorizations:
for auth in read_authorizations:
(errorIndication, errorStatus, errorIndex, varBinds) = \
cmdgen.CommandGenerator().getCmd(auth, cmdgen.UdpTransportTarget((self.host, self.port)), noid)
cmdgen.CommandGenerator().getCmd(
cmdgen.CommunityData(auth['community'],
mpModel=auth['version']),
self.transport,
noid)
if errorIndication == 'requestTimedOut':
continue
else:
self.alive = True
self.auth = auth
self.readauth = cmdgen.CommunityData(auth['community'],
mpModel=auth['version'])
break

# Don't determine the write authorization since there's no temporary
# location within SNMP to write to. Choose the first authorization.
for auth in write_authorizations:
self.writeauth = cmdgen.CommunityData(auth['community'],
mpModel=auth['version'])
break

def get(self, oid):
"""Get a specific node in the tree"""
if not self.readauth:
return

noid = nodeid(oid)
(errorIndication, errorStatus, errorIndex, varBinds) = \
cmdgen.CommandGenerator().getCmd(self.auth, cmdgen.UdpTransportTarget((self.host, self.port)), noid)
cmdgen.CommandGenerator().getCmd(
self.readauth,
self.transport,
noid)
if errorIndication:
raise RuntimeError("SNMPget of %s on %s failed" % (oid, self.host))
return varBinds[0][1]

def set(self, oid, value):
"""Set a specific value to a node in the tree"""
if not self.writeauth:
return

initial_value = self.get(oid)

# Types from RFC-1902
if isinstance(initial_value, rfc1902.Counter32):
set_value = rfc1902.Counter32(str(value))
elif isinstance(initial_value, rfc1902.Counter64):
set_value = rfc1902.Counter64(str(value))
elif isinstance(initial_value, rfc1902.Gauge32):
set_value = rfc1902.Gauge32(str(value))
elif isinstance(initial_value, rfc1902.Integer):
set_value = rfc1902.Integer(str(value))
elif isinstance(initial_value, rfc1902.Integer32):
set_value = rfc1902.Integer32(str(value))
elif isinstance(initial_value, rfc1902.IpAddress):
set_value = rfc1902.IpAddress(str(value))
elif isinstance(initial_value, rfc1902.OctetString):
set_value = rfc1902.OctetString(str(value))
elif isinstance(initial_value, rfc1902.TimeTicks):
set_value = rfc1902.TimeTicks(str(value))
elif isinstance(initial_value, rfc1902.Unsigned32):
set_value = rfc1902.Unsigned32(str(value))
else:
raise RuntimeError("Unknown type %s" % type(initial_value))

noid = nodeid(oid)
(errorIndication, errorStatus, errorIndex, varBinds) = \
cmdgen.CommandGenerator().setCmd(
self.writeauth,
self.transport,
(noid, set_value)
)
if errorIndication:
raise RuntimeError("SNMPset of %s on %s failed" % (oid, self.host))
return varBinds[0][1]

def gettable(self, oid):
"""Get a complete subtable"""
if not self.readauth:
return

noid = nodeid(oid)
(errorIndication, errorStatus, errorIndex, varBinds) = \
cmdgen.CommandGenerator().nextCmd(self.auth, cmdgen.UdpTransportTarget((self.host, self.port)), noid)
cmdgen.CommandGenerator().nextCmd(
self.readauth,
self.transport,
noid)
if errorIndication:
raise RuntimeError("SNMPget of %s on %s failed" % (oid, self.host))
return [x[0] for x in varBinds]

def matchtables(self, index, tables):
"""Match a list of tables using either a specific index table or the
common tail of the OIDs in the tables"""
if not self.readauth:
return

oid_to_index = {}
result = {}
indexlen = 1
Expand Down
15 changes: 15 additions & 0 deletions test_snmpclient.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import snmpclient

router = '10.100.184.62'
port = 161
public_auth = [{'community': 'public', 'version': snmpclient.V2C}]
private_auth = [{'community': 'private', 'version': snmpclient.V2C}]

client = snmpclient.SnmpClient(router, port, public_auth, private_auth)
print client.alive
print client.get('SNMPv2-MIB::sysName.0')
print client.gettable('UDP-MIB::udpLocalAddress')
print client.matchtables('IF-MIB::ifIndex',
('IF-MIB::ifDescr',
'IF-MIB::ifPhysAddress',
'IF-MIB::ifOperStatus'))
38 changes: 38 additions & 0 deletions test_snmpclient_apcpdu.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from os import getcwd
import snmpclient

router = '10.100.184.62'
port = 161
public_auth = [{'community': 'public', 'version': snmpclient.V2C}]
private_auth = [{'community': 'private', 'version': snmpclient.V2C}]

snmpclient.add_mib_path(getcwd())

# NOTE: In order to use this, download the APC PowerNet-MIB from:
# ftp://ftp.apc.com/apc/public/software/pnetmib/mib
# and run:
# build-pysnmp-mib -o PowerNet-MIB.py powernetZZZ.mib
# There appears to be a bug in either the MIB or build-pysnmp-mib whereby
# Unsigned32 is undefined. Despite this being an awful hack, modify the
# generated PowerNet-MIB.py running:
# sed -i '10 a ( Unsigned32, ) = mibBuilder.importSymbols("SNMPv2-SMI", "Unsigned32")' PowerNet-MIB.py
snmpclient.load_mibs('PowerNet-MIB')
client = snmpclient.SnmpClient(router, port, public_auth, private_auth)

print client.alive
print "APC Model Number: %s" % \
client.get('PowerNet-MIB::rPDU2IdentModelNumber.1')
print "Outlet 5 status: %s" % \
client.get('PowerNet-MIB::rPDU2OutletSwitchedStatusState.5')
print "Outlet 5 on: %s" % \
client.set('PowerNet-MIB::rPDU2OutletSwitchedControlCommand.5', 1)
print "Outlet 5 command pending: %s" % \
client.get('PowerNet-MIB::rPDU2OutletSwitchedStatusCommandPending.5')
print "Outlet 5 status: %s" % \
client.get('PowerNet-MIB::rPDU2OutletSwitchedStatusState.5')
print "Outlet 5 off: %s" % \
client.set('PowerNet-MIB::rPDU2OutletSwitchedControlCommand.5', 1)
print "Outlet 5 command pending: %s" % \
client.get('PowerNet-MIB::rPDU2OutletSwitchedStatusCommandPending.5')
print "Outlet 5 status: %s" % \
client.get('PowerNet-MIB::rPDU2OutletSwitchedStatusState.5')
10 changes: 0 additions & 10 deletions test_snmplient.py

This file was deleted.