Merge branch 'master' of github.com:etingof/pysnmp
commit
602c4dd304
|
@ -25,15 +25,17 @@ Revision 5.0.0, released 2018-10-??
|
|||
just var-binds (as var-arg), the rest of the parameters packed
|
||||
into opaque kwargs
|
||||
|
||||
* The `readVars`, `readNextVars` and `writeVars` methods of MIB
|
||||
instrumentation controller return immediately and deliver their
|
||||
results via a call back.
|
||||
|
||||
* CommandResponder application passes `snmpEngine` and optionally
|
||||
user-supplied `cbCtx` object throughout the MIB instrumentation
|
||||
methods. The goal is to let MIB objects access/modify whatever
|
||||
custom Python objects they need while being called back.
|
||||
|
||||
* CommandResponder refactored to facilitate asynchronous
|
||||
MIB instrumentation routines. The `readVars`, `readNextVars` and
|
||||
`writeVars` MIB controller methods return immediately and
|
||||
deliver their results via a call back.
|
||||
MIB instrumentation routines.
|
||||
|
||||
- The high-level API (`hlapi`) extended to cover lightweight SNMP v1arch
|
||||
in hope to ease the use of packet-level SNMP API.
|
||||
|
|
|
@ -56,7 +56,7 @@ MibScalar, MibScalarInstance = mibBuilder.importSymbols(
|
|||
|
||||
class MyStaticMibScalarInstance(MibScalarInstance):
|
||||
# noinspection PyUnusedLocal,PyUnusedLocal
|
||||
def getValue(self, name, idx):
|
||||
def getValue(self, name, idx, **context):
|
||||
return self.getSyntax().clone(
|
||||
'Python %s running on a %s platform' % (sys.version, sys.platform)
|
||||
)
|
||||
|
|
|
@ -96,7 +96,7 @@ class CommandResponder(cmdrsp.CommandResponderBase):
|
|||
v2c.GetNextRequestPDU.tagSet: cmdgen.NextCommandGeneratorSingleRun(),
|
||||
v2c.GetBulkRequestPDU.tagSet: cmdgen.BulkCommandGeneratorSingleRun()
|
||||
}
|
||||
pduTypes = cmdGenMap.keys() # This app will handle these PDUs
|
||||
SUPPORTED_PDU_TYPES = cmdGenMap.keys() # This app will handle these PDUs
|
||||
|
||||
# SNMP request relay
|
||||
def handleMgmtOperation(self, snmpEngine, stateReference, contextName,
|
||||
|
|
|
@ -95,7 +95,7 @@ class CommandResponder(cmdrsp.CommandResponderBase):
|
|||
v2c.GetNextRequestPDU.tagSet: cmdgen.NextCommandGeneratorSingleRun(),
|
||||
v2c.GetBulkRequestPDU.tagSet: cmdgen.BulkCommandGeneratorSingleRun()
|
||||
}
|
||||
pduTypes = cmdGenMap.keys() # This app will handle these PDUs
|
||||
SUPPORTED_PDU_TYPES = cmdGenMap.keys() # This app will handle these PDUs
|
||||
|
||||
# SNMP request relay
|
||||
def handleMgmtOperation(self, snmpEngine, stateReference, contextName,
|
||||
|
|
|
@ -95,7 +95,7 @@ class CommandResponder(cmdrsp.CommandResponderBase):
|
|||
v2c.GetNextRequestPDU.tagSet: cmdgen.NextCommandGeneratorSingleRun(),
|
||||
v2c.GetBulkRequestPDU.tagSet: cmdgen.BulkCommandGeneratorSingleRun()
|
||||
}
|
||||
pduTypes = cmdGenMap.keys() # This app will handle these PDUs
|
||||
SUPPORTED_PDU_TYPES = cmdGenMap.keys() # This app will handle these PDUs
|
||||
|
||||
# SNMP request relay
|
||||
def handleMgmtOperation(self, snmpEngine, stateReference, contextName,
|
||||
|
|
|
@ -98,7 +98,7 @@ class CommandResponder(cmdrsp.CommandResponderBase):
|
|||
v2c.GetNextRequestPDU.tagSet: cmdgen.NextCommandGeneratorSingleRun(),
|
||||
v2c.GetBulkRequestPDU.tagSet: cmdgen.BulkCommandGeneratorSingleRun()
|
||||
}
|
||||
pduTypes = cmdGenMap.keys() # This app will handle these PDUs
|
||||
SUPPORTED_PDU_TYPES = cmdGenMap.keys() # This app will handle these PDUs
|
||||
|
||||
# SNMP request relay
|
||||
def handleMgmtOperation(self, snmpEngine, stateReference, contextName,
|
||||
|
|
|
@ -15,25 +15,41 @@ from pysnmp import debug
|
|||
# 3.2
|
||||
class CommandResponderBase(object):
|
||||
acmID = 3 # default MIB access control method to use
|
||||
pduTypes = ()
|
||||
SUPPORTED_PDU_TYPES = ()
|
||||
SMI_ERROR_MAP = {
|
||||
pysnmp.smi.error.NoAccessError: 'noAccess',
|
||||
pysnmp.smi.error.WrongTypeError: 'wrongType',
|
||||
pysnmp.smi.error.WrongLengthError: 'wrongLength',
|
||||
pysnmp.smi.error.WrongEncodingError: 'wrongEncoding',
|
||||
pysnmp.smi.error.WrongValueError: 'wrongValue',
|
||||
pysnmp.smi.error.NoCreationError: 'noCreation',
|
||||
pysnmp.smi.error.InconsistentValueError: 'inconsistentValue',
|
||||
pysnmp.smi.error.ResourceUnavailableError: 'resourceUnavailable',
|
||||
pysnmp.smi.error.CommitFailedError: 'commitFailed',
|
||||
pysnmp.smi.error.UndoFailedError: 'undoFailed',
|
||||
pysnmp.smi.error.AuthorizationError: 'authorizationError',
|
||||
pysnmp.smi.error.NotWritableError: 'notWritable',
|
||||
pysnmp.smi.error.InconsistentNameError: 'inconsistentName'
|
||||
}
|
||||
|
||||
def __init__(self, snmpEngine, snmpContext, cbCtx=None):
|
||||
snmpEngine.msgAndPduDsp.registerContextEngineId(
|
||||
snmpContext.contextEngineId, self.pduTypes, self.processPdu
|
||||
snmpContext.contextEngineId, self.SUPPORTED_PDU_TYPES, self.processPdu
|
||||
)
|
||||
self.snmpContext = snmpContext
|
||||
self.cbCtx = cbCtx
|
||||
self.__pendingReqs = {}
|
||||
|
||||
def initiateMgmtOperation(self, snmpEngine, stateReference, contextName, PDU):
|
||||
pass
|
||||
|
||||
def close(self, snmpEngine):
|
||||
snmpEngine.msgAndPduDsp.unregisterContextEngineId(
|
||||
self.snmpContext.contextEngineId, self.pduTypes
|
||||
self.snmpContext.contextEngineId, self.SUPPORTED_PDU_TYPES
|
||||
)
|
||||
self.snmpContext = self.__pendingReqs = None
|
||||
|
||||
def releaseStateInformation(self, stateReference):
|
||||
if stateReference in self.__pendingReqs:
|
||||
del self.__pendingReqs[stateReference]
|
||||
|
||||
def sendVarBinds(self, snmpEngine, stateReference,
|
||||
errorStatus, errorIndex, varBinds):
|
||||
(messageProcessingModel,
|
||||
|
@ -105,10 +121,6 @@ class CommandResponderBase(object):
|
|||
_setRequestType = rfc1905.SetRequestPDU.tagSet
|
||||
_counter64Type = rfc1902.Counter64.tagSet
|
||||
|
||||
def releaseStateInformation(self, stateReference):
|
||||
if stateReference in self.__pendingReqs:
|
||||
del self.__pendingReqs[stateReference]
|
||||
|
||||
def processPdu(self, snmpEngine, messageProcessingModel, securityModel,
|
||||
securityName, securityLevel, contextEngineId, contextName,
|
||||
pduVersion, PDU, maxSizeResponseScopedPDU, stateReference):
|
||||
|
@ -140,63 +152,11 @@ class CommandResponderBase(object):
|
|||
|
||||
# 3.2.5
|
||||
varBinds = v2c.apiPDU.getVarBinds(PDU)
|
||||
errorStatus, errorIndex = 'noError', 0
|
||||
|
||||
debug.logger & debug.flagApp and debug.logger(
|
||||
'processPdu: stateReference %s, varBinds %s' % (stateReference, varBinds))
|
||||
|
||||
try:
|
||||
self.initiateMgmtOperation(snmpEngine, stateReference, contextName, PDU)
|
||||
|
||||
# SNMPv2 SMI exceptions
|
||||
except pysnmp.smi.error.GenError:
|
||||
errorIndication = sys.exc_info()[1]
|
||||
debug.logger & debug.flagApp and debug.logger(
|
||||
'processPdu: stateReference %s, errorIndication %s' % (stateReference, errorIndication))
|
||||
if 'oid' in errorIndication:
|
||||
# Request REPORT generation
|
||||
statusInformation['oid'] = errorIndication['oid']
|
||||
statusInformation['val'] = errorIndication['val']
|
||||
|
||||
# PDU-level SMI errors
|
||||
except pysnmp.smi.error.NoAccessError:
|
||||
errorStatus, errorIndex = 'noAccess', sys.exc_info()[1]['idx'] + 1
|
||||
except pysnmp.smi.error.WrongTypeError:
|
||||
errorStatus, errorIndex = 'wrongType', sys.exc_info()[1]['idx'] + 1
|
||||
except pysnmp.smi.error.WrongLengthError:
|
||||
errorStatus, errorIndex = 'wrongLength', sys.exc_info()[1]['idx'] + 1
|
||||
except pysnmp.smi.error.WrongEncodingError:
|
||||
errorStatus, errorIndex = 'wrongEncoding', sys.exc_info()[1]['idx'] + 1
|
||||
except pysnmp.smi.error.WrongValueError:
|
||||
errorStatus, errorIndex = 'wrongValue', sys.exc_info()[1]['idx'] + 1
|
||||
except pysnmp.smi.error.NoCreationError:
|
||||
errorStatus, errorIndex = 'noCreation', sys.exc_info()[1]['idx'] + 1
|
||||
except pysnmp.smi.error.InconsistentValueError:
|
||||
errorStatus, errorIndex = 'inconsistentValue', sys.exc_info()[1]['idx'] + 1
|
||||
except pysnmp.smi.error.ResourceUnavailableError:
|
||||
errorStatus, errorIndex = 'resourceUnavailable', sys.exc_info()[1]['idx'] + 1
|
||||
except pysnmp.smi.error.CommitFailedError:
|
||||
errorStatus, errorIndex = 'commitFailed', sys.exc_info()[1]['idx'] + 1
|
||||
except pysnmp.smi.error.UndoFailedError:
|
||||
errorStatus, errorIndex = 'undoFailed', sys.exc_info()[1]['idx'] + 1
|
||||
except pysnmp.smi.error.AuthorizationError:
|
||||
errorStatus, errorIndex = 'authorizationError', sys.exc_info()[1]['idx'] + 1
|
||||
except pysnmp.smi.error.NotWritableError:
|
||||
errorStatus, errorIndex = 'notWritable', sys.exc_info()[1]['idx'] + 1
|
||||
except pysnmp.smi.error.InconsistentNameError:
|
||||
errorStatus, errorIndex = 'inconsistentName', sys.exc_info()[1]['idx'] + 1
|
||||
except pysnmp.smi.error.SmiError:
|
||||
errorStatus, errorIndex = 'genErr', len(varBinds) and 1 or 0
|
||||
except pysnmp.error.PySnmpError:
|
||||
self.releaseStateInformation(stateReference)
|
||||
return
|
||||
else: # successful request processor must release state info
|
||||
return
|
||||
|
||||
self.sendVarBinds(snmpEngine, stateReference, errorStatus,
|
||||
errorIndex, varBinds)
|
||||
|
||||
self.releaseStateInformation(stateReference)
|
||||
self.initiateMgmtOperation(snmpEngine, stateReference, contextName, PDU)
|
||||
|
||||
@classmethod
|
||||
def verifyAccess(cls, viewType, varBind, **context):
|
||||
|
@ -258,21 +218,86 @@ class CommandResponderBase(object):
|
|||
# This will cause MibTree to skip this OID-value
|
||||
raise pysnmp.smi.error.NoAccessError(name=name, idx=context.get('idx'))
|
||||
|
||||
def _getMgmtFun(self, contextName):
|
||||
return lambda *args, **kwargs: None
|
||||
|
||||
class GetCommandResponder(CommandResponderBase):
|
||||
pduTypes = (rfc1905.GetRequestPDU.tagSet,)
|
||||
def _checkSmiErrors(self, varBinds):
|
||||
errorIndication = None
|
||||
errorStatus = errorIndex = 0
|
||||
|
||||
exception = None
|
||||
|
||||
for idx, varBind in enumerate(varBinds):
|
||||
name, value = varBind
|
||||
if isinstance(value, tuple): # expect exception tuple
|
||||
debug.logger & debug.flagApp and debug.logger(
|
||||
'_checkSmiErrors: exception reported for OID %s exception %s' % (name, value))
|
||||
|
||||
if not exception:
|
||||
exception = value
|
||||
|
||||
# reset exception object
|
||||
varBinds[idx] = name, v2c.null
|
||||
|
||||
try:
|
||||
# TODO: perhaps chain exceptions
|
||||
if exception:
|
||||
debug.logger & debug.flagApp and debug.logger(
|
||||
'_checkSmiErrors: re-raising exception %s' % (exception,))
|
||||
raise exception[1].with_traceback(exception[2])
|
||||
|
||||
# SNMPv2 SMI exceptions
|
||||
except pysnmp.smi.error.GenError:
|
||||
errorIndication = sys.exc_info()[1]
|
||||
debug.logger & debug.flagApp and debug.logger(
|
||||
'_checkSmiErrors: errorIndication %s' % (errorIndication,))
|
||||
|
||||
except pysnmp.smi.error.SmiError:
|
||||
exc_type, exc_obj, trb = sys.exc_info()
|
||||
|
||||
errorStatus = self.SMI_ERROR_MAP.get(exc_type, 'genErr')
|
||||
|
||||
try:
|
||||
errorIndex = exc_obj['idx'] + 1
|
||||
|
||||
except IndexError:
|
||||
errorIndex = len(varBinds) and 1 or 0
|
||||
|
||||
return errorIndication, errorStatus, errorIndex
|
||||
|
||||
def completeMgmtOperation(self, varBinds, **context):
|
||||
self.sendVarBinds(context['snmpEngine'], context['stateReference'],
|
||||
0, 0, varBinds)
|
||||
self.releaseStateInformation(context['stateReference'])
|
||||
|
||||
# rfc1905: 4.2.1
|
||||
try:
|
||||
(errorIndication,
|
||||
errorStatus, errorIndex) = self._checkSmiErrors(varBinds)
|
||||
|
||||
except pysnmp.error.PySnmpError:
|
||||
self.releaseStateInformation(context['stateReference'])
|
||||
return
|
||||
|
||||
stateReference = context['stateReference']
|
||||
|
||||
if errorIndication:
|
||||
statusInformation = self.__pendingReqs[stateReference]['statusInformation']
|
||||
|
||||
try:
|
||||
# Request REPORT generation
|
||||
statusInformation['oid'] = errorIndication['oid']
|
||||
statusInformation['val'] = errorIndication['val']
|
||||
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
self.sendVarBinds(context['snmpEngine'], stateReference,
|
||||
errorStatus, errorIndex, varBinds)
|
||||
|
||||
self.releaseStateInformation(stateReference)
|
||||
|
||||
def initiateMgmtOperation(self, snmpEngine, stateReference, contextName, PDU):
|
||||
# rfc1905: 4.2.1.1
|
||||
mgmtFun = self.snmpContext.getMibInstrum(contextName).readVars
|
||||
varBinds = v2c.apiPDU.getVarBinds(PDU)
|
||||
|
||||
mgmtFun = self._getMgmtFun(contextName)
|
||||
|
||||
context = dict(snmpEngine=snmpEngine,
|
||||
stateReference=stateReference,
|
||||
acFun=self.verifyAccess,
|
||||
|
@ -282,56 +307,54 @@ class GetCommandResponder(CommandResponderBase):
|
|||
mgmtFun(*varBinds, **context)
|
||||
|
||||
|
||||
class GetCommandResponder(CommandResponderBase):
|
||||
SUPPORTED_PDU_TYPES = (rfc1905.GetRequestPDU.tagSet,)
|
||||
|
||||
# rfc1905: 4.2.1
|
||||
def _getMgmtFun(self, contextName):
|
||||
return self.snmpContext.getMibInstrum(contextName).readVars
|
||||
|
||||
|
||||
class NextCommandResponder(CommandResponderBase):
|
||||
pduTypes = (rfc1905.GetNextRequestPDU.tagSet,)
|
||||
|
||||
def completeMgmtOperation(self, varBinds, **context):
|
||||
self.sendVarBinds(context['snmpEngine'], context['stateReference'],
|
||||
0, 0, varBinds)
|
||||
self.releaseStateInformation(context['stateReference'])
|
||||
SUPPORTED_PDU_TYPES = (rfc1905.GetNextRequestPDU.tagSet,)
|
||||
|
||||
# rfc1905: 4.2.2
|
||||
def initiateMgmtOperation(self, snmpEngine, stateReference, contextName, PDU):
|
||||
# rfc1905: 4.2.2.1
|
||||
mgmtFun = self.snmpContext.getMibInstrum(contextName).readNextVars
|
||||
|
||||
varBinds = v2c.apiPDU.getVarBinds(PDU)
|
||||
|
||||
context = dict(snmpEngine=snmpEngine,
|
||||
stateReference=stateReference,
|
||||
acFun=self.verifyAccess,
|
||||
cbFun=self.completeMgmtOperation,
|
||||
cbCtx=self.cbCtx)
|
||||
|
||||
mgmtFun(*varBinds, **context)
|
||||
def _getMgmtFun(self, contextName):
|
||||
return self.snmpContext.getMibInstrum(contextName).readNextVars
|
||||
|
||||
|
||||
class BulkCommandResponder(CommandResponderBase):
|
||||
pduTypes = (rfc1905.GetBulkRequestPDU.tagSet,)
|
||||
SUPPORTED_PDU_TYPES = (rfc1905.GetBulkRequestPDU.tagSet,)
|
||||
maxVarBinds = 64
|
||||
|
||||
def _getMgmtFun(self, contextName):
|
||||
return self.snmpContext.getMibInstrum(contextName).readNextVars
|
||||
|
||||
def _completeNonRepeaters(self, varBinds, **context):
|
||||
context['rspVarBinds'][:] = varBinds
|
||||
context['cbFun'] = self.completeMgmtOperation
|
||||
|
||||
mgmtFun = self.snmpContext.getMibInstrum(context['contextName']).readNextVars
|
||||
if context['counters']['M'] and context['counters']['R']:
|
||||
context['cbFun'] = self.completeMgmtOperation
|
||||
|
||||
mgmtFun(*context['varBinds'], **context)
|
||||
mgmtFun = self._getMgmtFun(context['contextName'])
|
||||
|
||||
mgmtFun(*context['reqVarBinds'], **context)
|
||||
|
||||
else:
|
||||
CommandResponderBase.completeMgmtOperation(self, context['rspVarBinds'], **context)
|
||||
|
||||
def completeMgmtOperation(self, varBinds, **context):
|
||||
context['rspVarBinds'].extend(varBinds)
|
||||
context['counters']['M'] -= 1
|
||||
|
||||
if context['counters']['M'] and context['counters']['R']:
|
||||
mgmtFun = self.snmpContext.getMibInstrum(context['contextName']).readNextVars
|
||||
mgmtFun = self._getMgmtFun(context['contextName'])
|
||||
|
||||
context['cbFun'] = self.completeMgmtOperation
|
||||
mgmtFun(*varBinds[-context['counters']['R']:], **context)
|
||||
|
||||
else:
|
||||
self.sendVarBinds(context['snmpEngine'], context['stateReference'],
|
||||
0, 0, varBinds)
|
||||
self.releaseStateInformation(context['stateReference'])
|
||||
CommandResponderBase.completeMgmtOperation(self, context['rspVarBinds'], **context)
|
||||
|
||||
# rfc1905: 4.2.3
|
||||
def initiateMgmtOperation(self, snmpEngine, stateReference, contextName, PDU):
|
||||
|
@ -355,7 +378,7 @@ class BulkCommandResponder(CommandResponderBase):
|
|||
debug.logger & debug.flagApp and debug.logger(
|
||||
'initiateMgmtOperation: N %d, M %d, R %d' % (N, M, R))
|
||||
|
||||
mgmtFun = self.snmpContext.getMibInstrum(contextName).readNextVars
|
||||
mgmtFun = self._getMgmtFun(contextName)
|
||||
|
||||
context = dict(snmpEngine=snmpEngine,
|
||||
stateReference=stateReference,
|
||||
|
@ -363,7 +386,7 @@ class BulkCommandResponder(CommandResponderBase):
|
|||
acFun=self.verifyAccess,
|
||||
cbFun=self._completeNonRepeaters,
|
||||
cbCtx=self.cbCtx,
|
||||
varBinds=varBinds[-R:],
|
||||
reqVarBinds=varBinds[N:],
|
||||
counters={'M': M, 'R': R},
|
||||
rspVarBinds=[])
|
||||
|
||||
|
@ -371,32 +394,14 @@ class BulkCommandResponder(CommandResponderBase):
|
|||
|
||||
|
||||
class SetCommandResponder(CommandResponderBase):
|
||||
pduTypes = (rfc1905.SetRequestPDU.tagSet,)
|
||||
SUPPORTED_PDU_TYPES = (rfc1905.SetRequestPDU.tagSet,)
|
||||
|
||||
def completeMgmtOperation(self, varBinds, **context):
|
||||
self.sendVarBinds(context['snmpEngine'], context['stateReference'],
|
||||
0, 0, varBinds)
|
||||
self.releaseStateInformation(context['stateReference'])
|
||||
SMI_ERROR_MAP = CommandResponderBase.SMI_ERROR_MAP.copy()
|
||||
|
||||
# rfc1905: 4.2.5
|
||||
def initiateMgmtOperation(self, snmpEngine, stateReference, contextName, PDU):
|
||||
mgmtFun = self.snmpContext.getMibInstrum(contextName).writeVars
|
||||
# turn missing OIDs into access denial
|
||||
SMI_ERROR_MAP[pysnmp.smi.error.NoSuchObjectError] = 'notWritable'
|
||||
SMI_ERROR_MAP[pysnmp.smi.error.NoSuchInstanceError] = 'notWritable'
|
||||
|
||||
varBinds = v2c.apiPDU.getVarBinds(PDU)
|
||||
|
||||
context = dict(snmpEngine=snmpEngine,
|
||||
stateReference=stateReference,
|
||||
acFun=self.verifyAccess,
|
||||
cbFun=self.completeMgmtOperation,
|
||||
cbCtx=self.cbCtx)
|
||||
|
||||
# rfc1905: 4.2.5.1-13
|
||||
try:
|
||||
mgmtFun(*varBinds, **context)
|
||||
|
||||
except (pysnmp.smi.error.NoSuchObjectError,
|
||||
pysnmp.smi.error.NoSuchInstanceError):
|
||||
instrumError = pysnmp.smi.error.NotWritableError()
|
||||
instrumError.update(sys.exc_info()[1])
|
||||
self.releaseStateInformation(stateReference)
|
||||
raise instrumError
|
||||
# rfc1905: 4.2.5.1-13
|
||||
def _getMgmtFun(self, contextName):
|
||||
return self.snmpContext.getMibInstrum(contextName).writeVars
|
||||
|
|
|
@ -14,12 +14,12 @@ from pysnmp import debug
|
|||
|
||||
# 3.4
|
||||
class NotificationReceiver(object):
|
||||
pduTypes = (v1.TrapPDU.tagSet, v2c.SNMPv2TrapPDU.tagSet,
|
||||
v2c.InformRequestPDU.tagSet)
|
||||
SUPPORTED_PDU_TYPES = (v1.TrapPDU.tagSet, v2c.SNMPv2TrapPDU.tagSet,
|
||||
v2c.InformRequestPDU.tagSet)
|
||||
|
||||
def __init__(self, snmpEngine, cbFun, cbCtx=None):
|
||||
snmpEngine.msgAndPduDsp.registerContextEngineId(
|
||||
null, self.pduTypes, self.processPdu # '' is a wildcard
|
||||
null, self.SUPPORTED_PDU_TYPES, self.processPdu # '' is a wildcard
|
||||
)
|
||||
|
||||
self.__snmpTrapCommunity = ''
|
||||
|
@ -33,7 +33,7 @@ class NotificationReceiver(object):
|
|||
|
||||
def close(self, snmpEngine):
|
||||
snmpEngine.msgAndPduDsp.unregisterContextEngineId(
|
||||
null, self.pduTypes
|
||||
null, self.SUPPORTED_PDU_TYPES
|
||||
)
|
||||
self.__cbFun = self.__cbCtx = None
|
||||
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
#
|
||||
import sys
|
||||
import traceback
|
||||
import functools
|
||||
from pysnmp import nextid
|
||||
from pysnmp.smi import error
|
||||
from pysnmp import debug
|
||||
|
||||
|
@ -24,39 +26,59 @@ class AbstractMibInstrumController(object):
|
|||
|
||||
|
||||
class MibInstrumController(AbstractMibInstrumController):
|
||||
STATUS_OK = 'ok'
|
||||
STATUS_ERROR = 'err'
|
||||
|
||||
STATE_START = 'start'
|
||||
STATE_STOP = 'stop'
|
||||
STATE_ANY = '*'
|
||||
# These states are actually methods of the MIB objects
|
||||
STATE_READ_TEST = 'readTest'
|
||||
STATE_READ_GET = 'readGet'
|
||||
STATE_READ_TEST_NEXT = 'readTestNext'
|
||||
STATE_READ_GET_NEXT = 'readGetNext'
|
||||
STATE_WRITE_TEST = 'writeTest'
|
||||
STATE_WRITE_COMMIT = 'writeCommit'
|
||||
STATE_WRITE_CLEANUP = 'writeCleanup'
|
||||
STATE_WRITE_UNDO = 'writeUndo'
|
||||
|
||||
fsmReadVar = {
|
||||
# ( state, status ) -> newState
|
||||
('start', 'ok'): 'readTest',
|
||||
('readTest', 'ok'): 'readGet',
|
||||
('readGet', 'ok'): 'stop',
|
||||
('*', 'err'): 'stop'
|
||||
(STATE_START, STATUS_OK): STATE_READ_TEST,
|
||||
(STATE_READ_TEST, STATUS_OK): STATE_READ_GET,
|
||||
(STATE_READ_GET, STATUS_OK): STATE_STOP,
|
||||
(STATE_ANY, STATUS_ERROR): STATE_STOP
|
||||
}
|
||||
fsmReadNextVar = {
|
||||
# ( state, status ) -> newState
|
||||
('start', 'ok'): 'readTestNext',
|
||||
('readTestNext', 'ok'): 'readGetNext',
|
||||
('readGetNext', 'ok'): 'stop',
|
||||
('*', 'err'): 'stop'
|
||||
(STATE_START, STATUS_OK): STATE_READ_TEST_NEXT,
|
||||
(STATE_READ_TEST_NEXT, STATUS_OK): STATE_READ_GET_NEXT,
|
||||
(STATE_READ_GET_NEXT, STATUS_OK): STATE_STOP,
|
||||
(STATE_ANY, STATUS_ERROR): STATE_STOP
|
||||
}
|
||||
fsmWriteVar = {
|
||||
# ( state, status ) -> newState
|
||||
('start', 'ok'): 'writeTest',
|
||||
('writeTest', 'ok'): 'writeCommit',
|
||||
('writeCommit', 'ok'): 'writeCleanup',
|
||||
('writeCleanup', 'ok'): 'readTest',
|
||||
(STATE_START, STATUS_OK): STATE_WRITE_TEST,
|
||||
(STATE_WRITE_TEST, STATUS_OK): STATE_WRITE_COMMIT,
|
||||
(STATE_WRITE_COMMIT, STATUS_OK): STATE_WRITE_CLEANUP,
|
||||
(STATE_WRITE_CLEANUP, STATUS_OK): STATE_READ_TEST,
|
||||
# Do read after successful write
|
||||
('readTest', 'ok'): 'readGet',
|
||||
('readGet', 'ok'): 'stop',
|
||||
(STATE_READ_TEST, STATUS_OK): STATE_READ_GET,
|
||||
(STATE_READ_GET, STATUS_OK): STATE_STOP,
|
||||
# Error handling
|
||||
('writeTest', 'err'): 'writeCleanup',
|
||||
('writeCommit', 'err'): 'writeUndo',
|
||||
('writeUndo', 'ok'): 'readTest',
|
||||
(STATE_WRITE_TEST, STATUS_ERROR): STATE_WRITE_CLEANUP,
|
||||
(STATE_WRITE_COMMIT, STATUS_ERROR): STATE_WRITE_UNDO,
|
||||
(STATE_WRITE_UNDO, STATUS_OK): STATE_READ_TEST,
|
||||
# Ignore read errors (removed columns)
|
||||
('readTest', 'err'): 'stop',
|
||||
('readGet', 'err'): 'stop',
|
||||
('*', 'err'): 'stop'
|
||||
(STATE_READ_TEST, STATUS_ERROR): STATE_STOP,
|
||||
(STATE_READ_GET, STATUS_ERROR): STATE_STOP,
|
||||
(STATE_ANY, STATUS_ERROR): STATE_STOP
|
||||
}
|
||||
|
||||
FSM_CONTEXT = '_fsmContext'
|
||||
|
||||
FSM_SESSION_ID = nextid.Integer(0xffffffff)
|
||||
|
||||
def __init__(self, mibBuilder):
|
||||
self.mibBuilder = mibBuilder
|
||||
self.lastBuildId = -1
|
||||
|
@ -183,88 +205,115 @@ class MibInstrumController(AbstractMibInstrumController):
|
|||
|
||||
# MIB instrumentation
|
||||
|
||||
def flipFlopFsm(self, fsmTable, *varBinds, **context):
|
||||
def _flipFlopFsmCb(self, varBind, **context):
|
||||
fsmContext = context[self.FSM_CONTEXT]
|
||||
|
||||
varBinds = fsmContext['varBinds']
|
||||
|
||||
idx = context.pop('idx')
|
||||
|
||||
if idx >= 0:
|
||||
fsmContext['count'] += 1
|
||||
|
||||
varBinds[idx] = varBind
|
||||
|
||||
debug.logger & debug.flagIns and debug.logger(
|
||||
'_flipFlopFsmCb: var-bind %d, processed %d, expected %d' % (idx, fsmContext['count'], len(varBinds)))
|
||||
|
||||
if fsmContext['count'] < len(varBinds):
|
||||
return
|
||||
|
||||
debug.logger & debug.flagIns and debug.logger(
|
||||
'_flipFlopFsmCb: finished, output %r' % (varBinds,))
|
||||
|
||||
fsmCallable = fsmContext['fsmCallable']
|
||||
|
||||
fsmCallable(**context)
|
||||
|
||||
def flipFlopFsm(self, fsmTable, *varBinds, **context):
|
||||
try:
|
||||
fsmContext = context['fsmState']
|
||||
fsmContext = context[self.FSM_CONTEXT]
|
||||
|
||||
except KeyError:
|
||||
self.__indexMib()
|
||||
|
||||
fsmContext = context['fsmState'] = dict(varBinds=[], state='start', status='ok')
|
||||
fsmContext = context[self.FSM_CONTEXT] = dict(
|
||||
sessionId=self.FSM_SESSION_ID(),
|
||||
varBinds=list(varBinds[:]),
|
||||
fsmCallable=functools.partial(self.flipFlopFsm, fsmTable, *varBinds),
|
||||
state=self.STATE_START, status=self.STATUS_OK
|
||||
)
|
||||
|
||||
debug.logger & debug.flagIns and debug.logger('flipFlopFsm: input var-binds %r' % (varBinds,))
|
||||
|
||||
mibTree, = self.mibBuilder.importSymbols('SNMPv2-SMI', 'iso')
|
||||
|
||||
outputVarBinds = fsmContext['varBinds']
|
||||
state = fsmContext['state']
|
||||
status = fsmContext['status']
|
||||
|
||||
origExc = origTraceback = None
|
||||
debug.logger & debug.flagIns and debug.logger(
|
||||
'flipFlopFsm: current state %s, status %s' % (state, status))
|
||||
|
||||
try:
|
||||
newState = fsmTable[(state, status)]
|
||||
|
||||
except KeyError:
|
||||
try:
|
||||
newState = fsmTable[(self.STATE_ANY, status)]
|
||||
|
||||
except KeyError:
|
||||
raise error.SmiError('Unresolved FSM state %s, %s' % (state, status))
|
||||
|
||||
debug.logger & debug.flagIns and debug.logger(
|
||||
'flipFlopFsm: state %s status %s -> new state %s' % (state, status, newState))
|
||||
|
||||
state = newState
|
||||
|
||||
if state == self.STATE_STOP:
|
||||
context.pop(self.FSM_CONTEXT, None)
|
||||
|
||||
cbFun = context.get('cbFun')
|
||||
if cbFun:
|
||||
varBinds = fsmContext['varBinds']
|
||||
cbFun(varBinds, **context)
|
||||
|
||||
return
|
||||
|
||||
fsmContext.update(state=state, count=0)
|
||||
|
||||
# the case of no var-binds
|
||||
if not varBinds:
|
||||
return self._flipFlopFsmCb(None, idx=-1, **context)
|
||||
|
||||
mgmtFun = getattr(mibTree, state, None)
|
||||
if not mgmtFun:
|
||||
raise error.SmiError(
|
||||
'Unsupported state handler %s at %s' % (state, self)
|
||||
)
|
||||
|
||||
for idx, varBind in enumerate(varBinds):
|
||||
try:
|
||||
# TODO: managed objects to run asynchronously
|
||||
#mgmtFun(varBind, idx=idx, **context)
|
||||
self._flipFlopFsmCb(mgmtFun(varBind, idx=idx, **context), idx=idx, **context)
|
||||
|
||||
except error.SmiError:
|
||||
exc = sys.exc_info()
|
||||
debug.logger & debug.flagIns and debug.logger(
|
||||
'flipFlopFsm: fun %s exception %s for %r with traceback: %s' % (
|
||||
mgmtFun, exc[0], varBind, traceback.format_exception(*exc)))
|
||||
|
||||
varBind = varBind[0], exc
|
||||
|
||||
fsmContext['status'] = self.STATUS_ERROR
|
||||
|
||||
self._flipFlopFsmCb(varBind, idx=idx, **context)
|
||||
|
||||
return
|
||||
|
||||
while True:
|
||||
k = state, status
|
||||
if k in fsmTable:
|
||||
fsmState = fsmTable[k]
|
||||
else:
|
||||
k = '*', status
|
||||
if k in fsmTable:
|
||||
fsmState = fsmTable[k]
|
||||
else:
|
||||
raise error.SmiError(
|
||||
'Unresolved FSM state %s, %s' % (state, status)
|
||||
)
|
||||
debug.logger & debug.flagIns and debug.logger(
|
||||
'flipFlopFsm: state %s status %s -> fsmState %s' % (state, status, fsmState))
|
||||
state = fsmState
|
||||
status = 'ok'
|
||||
if state == 'stop':
|
||||
break
|
||||
|
||||
for idx, (name, val) in enumerate(varBinds):
|
||||
mgmtFun = getattr(mibTree, state, None)
|
||||
if not mgmtFun:
|
||||
raise error.SmiError(
|
||||
'Unsupported state handler %s at %s' % (state, self)
|
||||
)
|
||||
|
||||
context['idx'] = idx
|
||||
|
||||
try:
|
||||
# Convert to tuple to avoid ObjectName instantiation
|
||||
# on subscription
|
||||
rval = mgmtFun((tuple(name), val), **context)
|
||||
|
||||
except error.SmiError:
|
||||
exc_t, exc_v, exc_tb = sys.exc_info()
|
||||
debug.logger & debug.flagIns and debug.logger(
|
||||
'flipFlopFsm: fun %s exception %s for %s=%r with traceback: %s' % (
|
||||
mgmtFun, exc_t, name, val, traceback.format_exception(exc_t, exc_v, exc_tb)))
|
||||
if origExc is None: # Take the first exception
|
||||
origExc, origTraceback = exc_v, exc_tb
|
||||
status = 'err'
|
||||
break
|
||||
else:
|
||||
debug.logger & debug.flagIns and debug.logger(
|
||||
'flipFlopFsm: fun %s succeeded for %s=%r' % (mgmtFun, name, val))
|
||||
if rval is not None:
|
||||
outputVarBinds.append((rval[0], rval[1]))
|
||||
|
||||
if origExc:
|
||||
if sys.version_info[0] <= 2:
|
||||
raise origExc
|
||||
else:
|
||||
try:
|
||||
raise origExc.with_traceback(origTraceback)
|
||||
finally:
|
||||
# Break cycle between locals and traceback object
|
||||
# (seems to be irrelevant on Py3 but just in case)
|
||||
del origTraceback
|
||||
|
||||
cbFun = context.get('cbFun')
|
||||
if cbFun:
|
||||
cbFun(outputVarBinds, **context)
|
||||
debug.logger & debug.flagIns and debug.logger(
|
||||
'flipFlopFsm: func %s initiated for %r' % (mgmtFun, varBind))
|
||||
|
||||
def readVars(self, *varBinds, **context):
|
||||
self.flipFlopFsm(self.fsmReadVar, *varBinds, **context)
|
||||
|
|
Loading…
Reference in New Issue