Refactor MIB state machine into asynchronous operations (#210)
Convert to async MIB instrumentation API (#210) MIB instrumentation API changed to allow for asynchronous managed objects access. The MIB instrumentation methods called by the state machine now return immediately and resume once the callback is called. The built-in SNMPv2-SMI objects are still synchronous. This change is a prerequisite for fully asynchronous managed objects implementation.async-managed-objects
parent
534a5bb810
commit
0c0d054e8e
|
@ -25,15 +25,17 @@ Revision 5.0.0, released 2018-10-??
|
||||||
just var-binds (as var-arg), the rest of the parameters packed
|
just var-binds (as var-arg), the rest of the parameters packed
|
||||||
into opaque kwargs
|
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
|
* CommandResponder application passes `snmpEngine` and optionally
|
||||||
user-supplied `cbCtx` object throughout the MIB instrumentation
|
user-supplied `cbCtx` object throughout the MIB instrumentation
|
||||||
methods. The goal is to let MIB objects access/modify whatever
|
methods. The goal is to let MIB objects access/modify whatever
|
||||||
custom Python objects they need while being called back.
|
custom Python objects they need while being called back.
|
||||||
|
|
||||||
* CommandResponder refactored to facilitate asynchronous
|
* CommandResponder refactored to facilitate asynchronous
|
||||||
MIB instrumentation routines. The `readVars`, `readNextVars` and
|
MIB instrumentation routines.
|
||||||
`writeVars` MIB controller methods return immediately and
|
|
||||||
deliver their results via a call back.
|
|
||||||
|
|
||||||
- The high-level API (`hlapi`) extended to cover lightweight SNMP v1arch
|
- The high-level API (`hlapi`) extended to cover lightweight SNMP v1arch
|
||||||
in hope to ease the use of packet-level SNMP API.
|
in hope to ease the use of packet-level SNMP API.
|
||||||
|
|
|
@ -56,7 +56,7 @@ MibScalar, MibScalarInstance = mibBuilder.importSymbols(
|
||||||
|
|
||||||
class MyStaticMibScalarInstance(MibScalarInstance):
|
class MyStaticMibScalarInstance(MibScalarInstance):
|
||||||
# noinspection PyUnusedLocal,PyUnusedLocal
|
# noinspection PyUnusedLocal,PyUnusedLocal
|
||||||
def getValue(self, name, idx):
|
def getValue(self, name, idx, **context):
|
||||||
return self.getSyntax().clone(
|
return self.getSyntax().clone(
|
||||||
'Python %s running on a %s platform' % (sys.version, sys.platform)
|
'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.GetNextRequestPDU.tagSet: cmdgen.NextCommandGeneratorSingleRun(),
|
||||||
v2c.GetBulkRequestPDU.tagSet: cmdgen.BulkCommandGeneratorSingleRun()
|
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
|
# SNMP request relay
|
||||||
def handleMgmtOperation(self, snmpEngine, stateReference, contextName,
|
def handleMgmtOperation(self, snmpEngine, stateReference, contextName,
|
||||||
|
|
|
@ -95,7 +95,7 @@ class CommandResponder(cmdrsp.CommandResponderBase):
|
||||||
v2c.GetNextRequestPDU.tagSet: cmdgen.NextCommandGeneratorSingleRun(),
|
v2c.GetNextRequestPDU.tagSet: cmdgen.NextCommandGeneratorSingleRun(),
|
||||||
v2c.GetBulkRequestPDU.tagSet: cmdgen.BulkCommandGeneratorSingleRun()
|
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
|
# SNMP request relay
|
||||||
def handleMgmtOperation(self, snmpEngine, stateReference, contextName,
|
def handleMgmtOperation(self, snmpEngine, stateReference, contextName,
|
||||||
|
|
|
@ -95,7 +95,7 @@ class CommandResponder(cmdrsp.CommandResponderBase):
|
||||||
v2c.GetNextRequestPDU.tagSet: cmdgen.NextCommandGeneratorSingleRun(),
|
v2c.GetNextRequestPDU.tagSet: cmdgen.NextCommandGeneratorSingleRun(),
|
||||||
v2c.GetBulkRequestPDU.tagSet: cmdgen.BulkCommandGeneratorSingleRun()
|
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
|
# SNMP request relay
|
||||||
def handleMgmtOperation(self, snmpEngine, stateReference, contextName,
|
def handleMgmtOperation(self, snmpEngine, stateReference, contextName,
|
||||||
|
|
|
@ -98,7 +98,7 @@ class CommandResponder(cmdrsp.CommandResponderBase):
|
||||||
v2c.GetNextRequestPDU.tagSet: cmdgen.NextCommandGeneratorSingleRun(),
|
v2c.GetNextRequestPDU.tagSet: cmdgen.NextCommandGeneratorSingleRun(),
|
||||||
v2c.GetBulkRequestPDU.tagSet: cmdgen.BulkCommandGeneratorSingleRun()
|
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
|
# SNMP request relay
|
||||||
def handleMgmtOperation(self, snmpEngine, stateReference, contextName,
|
def handleMgmtOperation(self, snmpEngine, stateReference, contextName,
|
||||||
|
|
|
@ -15,25 +15,41 @@ from pysnmp import debug
|
||||||
# 3.2
|
# 3.2
|
||||||
class CommandResponderBase(object):
|
class CommandResponderBase(object):
|
||||||
acmID = 3 # default MIB access control method to use
|
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):
|
def __init__(self, snmpEngine, snmpContext, cbCtx=None):
|
||||||
snmpEngine.msgAndPduDsp.registerContextEngineId(
|
snmpEngine.msgAndPduDsp.registerContextEngineId(
|
||||||
snmpContext.contextEngineId, self.pduTypes, self.processPdu
|
snmpContext.contextEngineId, self.SUPPORTED_PDU_TYPES, self.processPdu
|
||||||
)
|
)
|
||||||
self.snmpContext = snmpContext
|
self.snmpContext = snmpContext
|
||||||
self.cbCtx = cbCtx
|
self.cbCtx = cbCtx
|
||||||
self.__pendingReqs = {}
|
self.__pendingReqs = {}
|
||||||
|
|
||||||
def initiateMgmtOperation(self, snmpEngine, stateReference, contextName, PDU):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def close(self, snmpEngine):
|
def close(self, snmpEngine):
|
||||||
snmpEngine.msgAndPduDsp.unregisterContextEngineId(
|
snmpEngine.msgAndPduDsp.unregisterContextEngineId(
|
||||||
self.snmpContext.contextEngineId, self.pduTypes
|
self.snmpContext.contextEngineId, self.SUPPORTED_PDU_TYPES
|
||||||
)
|
)
|
||||||
self.snmpContext = self.__pendingReqs = None
|
self.snmpContext = self.__pendingReqs = None
|
||||||
|
|
||||||
|
def releaseStateInformation(self, stateReference):
|
||||||
|
if stateReference in self.__pendingReqs:
|
||||||
|
del self.__pendingReqs[stateReference]
|
||||||
|
|
||||||
def sendVarBinds(self, snmpEngine, stateReference,
|
def sendVarBinds(self, snmpEngine, stateReference,
|
||||||
errorStatus, errorIndex, varBinds):
|
errorStatus, errorIndex, varBinds):
|
||||||
(messageProcessingModel,
|
(messageProcessingModel,
|
||||||
|
@ -105,10 +121,6 @@ class CommandResponderBase(object):
|
||||||
_setRequestType = rfc1905.SetRequestPDU.tagSet
|
_setRequestType = rfc1905.SetRequestPDU.tagSet
|
||||||
_counter64Type = rfc1902.Counter64.tagSet
|
_counter64Type = rfc1902.Counter64.tagSet
|
||||||
|
|
||||||
def releaseStateInformation(self, stateReference):
|
|
||||||
if stateReference in self.__pendingReqs:
|
|
||||||
del self.__pendingReqs[stateReference]
|
|
||||||
|
|
||||||
def processPdu(self, snmpEngine, messageProcessingModel, securityModel,
|
def processPdu(self, snmpEngine, messageProcessingModel, securityModel,
|
||||||
securityName, securityLevel, contextEngineId, contextName,
|
securityName, securityLevel, contextEngineId, contextName,
|
||||||
pduVersion, PDU, maxSizeResponseScopedPDU, stateReference):
|
pduVersion, PDU, maxSizeResponseScopedPDU, stateReference):
|
||||||
|
@ -140,63 +152,11 @@ class CommandResponderBase(object):
|
||||||
|
|
||||||
# 3.2.5
|
# 3.2.5
|
||||||
varBinds = v2c.apiPDU.getVarBinds(PDU)
|
varBinds = v2c.apiPDU.getVarBinds(PDU)
|
||||||
errorStatus, errorIndex = 'noError', 0
|
|
||||||
|
|
||||||
debug.logger & debug.flagApp and debug.logger(
|
debug.logger & debug.flagApp and debug.logger(
|
||||||
'processPdu: stateReference %s, varBinds %s' % (stateReference, varBinds))
|
'processPdu: stateReference %s, varBinds %s' % (stateReference, varBinds))
|
||||||
|
|
||||||
try:
|
self.initiateMgmtOperation(snmpEngine, stateReference, contextName, PDU)
|
||||||
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)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def verifyAccess(cls, viewType, varBind, **context):
|
def verifyAccess(cls, viewType, varBind, **context):
|
||||||
|
@ -258,21 +218,86 @@ class CommandResponderBase(object):
|
||||||
# This will cause MibTree to skip this OID-value
|
# This will cause MibTree to skip this OID-value
|
||||||
raise pysnmp.smi.error.NoAccessError(name=name, idx=context.get('idx'))
|
raise pysnmp.smi.error.NoAccessError(name=name, idx=context.get('idx'))
|
||||||
|
|
||||||
|
def _getMgmtFun(self, contextName):
|
||||||
|
return lambda *args, **kwargs: None
|
||||||
|
|
||||||
class GetCommandResponder(CommandResponderBase):
|
def _checkSmiErrors(self, varBinds):
|
||||||
pduTypes = (rfc1905.GetRequestPDU.tagSet,)
|
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):
|
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):
|
def initiateMgmtOperation(self, snmpEngine, stateReference, contextName, PDU):
|
||||||
# rfc1905: 4.2.1.1
|
|
||||||
mgmtFun = self.snmpContext.getMibInstrum(contextName).readVars
|
|
||||||
varBinds = v2c.apiPDU.getVarBinds(PDU)
|
varBinds = v2c.apiPDU.getVarBinds(PDU)
|
||||||
|
|
||||||
|
mgmtFun = self._getMgmtFun(contextName)
|
||||||
|
|
||||||
context = dict(snmpEngine=snmpEngine,
|
context = dict(snmpEngine=snmpEngine,
|
||||||
stateReference=stateReference,
|
stateReference=stateReference,
|
||||||
acFun=self.verifyAccess,
|
acFun=self.verifyAccess,
|
||||||
|
@ -282,56 +307,54 @@ class GetCommandResponder(CommandResponderBase):
|
||||||
mgmtFun(*varBinds, **context)
|
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):
|
class NextCommandResponder(CommandResponderBase):
|
||||||
pduTypes = (rfc1905.GetNextRequestPDU.tagSet,)
|
SUPPORTED_PDU_TYPES = (rfc1905.GetNextRequestPDU.tagSet,)
|
||||||
|
|
||||||
def completeMgmtOperation(self, varBinds, **context):
|
|
||||||
self.sendVarBinds(context['snmpEngine'], context['stateReference'],
|
|
||||||
0, 0, varBinds)
|
|
||||||
self.releaseStateInformation(context['stateReference'])
|
|
||||||
|
|
||||||
# rfc1905: 4.2.2
|
# rfc1905: 4.2.2
|
||||||
def initiateMgmtOperation(self, snmpEngine, stateReference, contextName, PDU):
|
def _getMgmtFun(self, contextName):
|
||||||
# rfc1905: 4.2.2.1
|
return self.snmpContext.getMibInstrum(contextName).readNextVars
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
class BulkCommandResponder(CommandResponderBase):
|
class BulkCommandResponder(CommandResponderBase):
|
||||||
pduTypes = (rfc1905.GetBulkRequestPDU.tagSet,)
|
SUPPORTED_PDU_TYPES = (rfc1905.GetBulkRequestPDU.tagSet,)
|
||||||
maxVarBinds = 64
|
maxVarBinds = 64
|
||||||
|
|
||||||
|
def _getMgmtFun(self, contextName):
|
||||||
|
return self.snmpContext.getMibInstrum(contextName).readNextVars
|
||||||
|
|
||||||
def _completeNonRepeaters(self, varBinds, **context):
|
def _completeNonRepeaters(self, varBinds, **context):
|
||||||
context['rspVarBinds'][:] = varBinds
|
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):
|
def completeMgmtOperation(self, varBinds, **context):
|
||||||
context['rspVarBinds'].extend(varBinds)
|
context['rspVarBinds'].extend(varBinds)
|
||||||
context['counters']['M'] -= 1
|
context['counters']['M'] -= 1
|
||||||
|
|
||||||
if context['counters']['M'] and context['counters']['R']:
|
if context['counters']['M'] and context['counters']['R']:
|
||||||
mgmtFun = self.snmpContext.getMibInstrum(context['contextName']).readNextVars
|
mgmtFun = self._getMgmtFun(context['contextName'])
|
||||||
|
|
||||||
context['cbFun'] = self.completeMgmtOperation
|
context['cbFun'] = self.completeMgmtOperation
|
||||||
mgmtFun(*varBinds[-context['counters']['R']:], **context)
|
mgmtFun(*varBinds[-context['counters']['R']:], **context)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.sendVarBinds(context['snmpEngine'], context['stateReference'],
|
CommandResponderBase.completeMgmtOperation(self, context['rspVarBinds'], **context)
|
||||||
0, 0, varBinds)
|
|
||||||
self.releaseStateInformation(context['stateReference'])
|
|
||||||
|
|
||||||
# rfc1905: 4.2.3
|
# rfc1905: 4.2.3
|
||||||
def initiateMgmtOperation(self, snmpEngine, stateReference, contextName, PDU):
|
def initiateMgmtOperation(self, snmpEngine, stateReference, contextName, PDU):
|
||||||
|
@ -355,7 +378,7 @@ class BulkCommandResponder(CommandResponderBase):
|
||||||
debug.logger & debug.flagApp and debug.logger(
|
debug.logger & debug.flagApp and debug.logger(
|
||||||
'initiateMgmtOperation: N %d, M %d, R %d' % (N, M, R))
|
'initiateMgmtOperation: N %d, M %d, R %d' % (N, M, R))
|
||||||
|
|
||||||
mgmtFun = self.snmpContext.getMibInstrum(contextName).readNextVars
|
mgmtFun = self._getMgmtFun(contextName)
|
||||||
|
|
||||||
context = dict(snmpEngine=snmpEngine,
|
context = dict(snmpEngine=snmpEngine,
|
||||||
stateReference=stateReference,
|
stateReference=stateReference,
|
||||||
|
@ -363,7 +386,7 @@ class BulkCommandResponder(CommandResponderBase):
|
||||||
acFun=self.verifyAccess,
|
acFun=self.verifyAccess,
|
||||||
cbFun=self._completeNonRepeaters,
|
cbFun=self._completeNonRepeaters,
|
||||||
cbCtx=self.cbCtx,
|
cbCtx=self.cbCtx,
|
||||||
varBinds=varBinds[-R:],
|
reqVarBinds=varBinds[N:],
|
||||||
counters={'M': M, 'R': R},
|
counters={'M': M, 'R': R},
|
||||||
rspVarBinds=[])
|
rspVarBinds=[])
|
||||||
|
|
||||||
|
@ -371,32 +394,14 @@ class BulkCommandResponder(CommandResponderBase):
|
||||||
|
|
||||||
|
|
||||||
class SetCommandResponder(CommandResponderBase):
|
class SetCommandResponder(CommandResponderBase):
|
||||||
pduTypes = (rfc1905.SetRequestPDU.tagSet,)
|
SUPPORTED_PDU_TYPES = (rfc1905.SetRequestPDU.tagSet,)
|
||||||
|
|
||||||
def completeMgmtOperation(self, varBinds, **context):
|
SMI_ERROR_MAP = CommandResponderBase.SMI_ERROR_MAP.copy()
|
||||||
self.sendVarBinds(context['snmpEngine'], context['stateReference'],
|
|
||||||
0, 0, varBinds)
|
|
||||||
self.releaseStateInformation(context['stateReference'])
|
|
||||||
|
|
||||||
# rfc1905: 4.2.5
|
# turn missing OIDs into access denial
|
||||||
def initiateMgmtOperation(self, snmpEngine, stateReference, contextName, PDU):
|
SMI_ERROR_MAP[pysnmp.smi.error.NoSuchObjectError] = 'notWritable'
|
||||||
mgmtFun = self.snmpContext.getMibInstrum(contextName).writeVars
|
SMI_ERROR_MAP[pysnmp.smi.error.NoSuchInstanceError] = 'notWritable'
|
||||||
|
|
||||||
varBinds = v2c.apiPDU.getVarBinds(PDU)
|
# rfc1905: 4.2.5.1-13
|
||||||
|
def _getMgmtFun(self, contextName):
|
||||||
context = dict(snmpEngine=snmpEngine,
|
return self.snmpContext.getMibInstrum(contextName).writeVars
|
||||||
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
|
|
||||||
|
|
|
@ -14,12 +14,12 @@ from pysnmp import debug
|
||||||
|
|
||||||
# 3.4
|
# 3.4
|
||||||
class NotificationReceiver(object):
|
class NotificationReceiver(object):
|
||||||
pduTypes = (v1.TrapPDU.tagSet, v2c.SNMPv2TrapPDU.tagSet,
|
SUPPORTED_PDU_TYPES = (v1.TrapPDU.tagSet, v2c.SNMPv2TrapPDU.tagSet,
|
||||||
v2c.InformRequestPDU.tagSet)
|
v2c.InformRequestPDU.tagSet)
|
||||||
|
|
||||||
def __init__(self, snmpEngine, cbFun, cbCtx=None):
|
def __init__(self, snmpEngine, cbFun, cbCtx=None):
|
||||||
snmpEngine.msgAndPduDsp.registerContextEngineId(
|
snmpEngine.msgAndPduDsp.registerContextEngineId(
|
||||||
null, self.pduTypes, self.processPdu # '' is a wildcard
|
null, self.SUPPORTED_PDU_TYPES, self.processPdu # '' is a wildcard
|
||||||
)
|
)
|
||||||
|
|
||||||
self.__snmpTrapCommunity = ''
|
self.__snmpTrapCommunity = ''
|
||||||
|
@ -33,7 +33,7 @@ class NotificationReceiver(object):
|
||||||
|
|
||||||
def close(self, snmpEngine):
|
def close(self, snmpEngine):
|
||||||
snmpEngine.msgAndPduDsp.unregisterContextEngineId(
|
snmpEngine.msgAndPduDsp.unregisterContextEngineId(
|
||||||
null, self.pduTypes
|
null, self.SUPPORTED_PDU_TYPES
|
||||||
)
|
)
|
||||||
self.__cbFun = self.__cbCtx = None
|
self.__cbFun = self.__cbCtx = None
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
#
|
#
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
|
import functools
|
||||||
|
from pysnmp import nextid
|
||||||
from pysnmp.smi import error
|
from pysnmp.smi import error
|
||||||
from pysnmp import debug
|
from pysnmp import debug
|
||||||
|
|
||||||
|
@ -24,39 +26,59 @@ class AbstractMibInstrumController(object):
|
||||||
|
|
||||||
|
|
||||||
class MibInstrumController(AbstractMibInstrumController):
|
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 = {
|
fsmReadVar = {
|
||||||
# ( state, status ) -> newState
|
# ( state, status ) -> newState
|
||||||
('start', 'ok'): 'readTest',
|
(STATE_START, STATUS_OK): STATE_READ_TEST,
|
||||||
('readTest', 'ok'): 'readGet',
|
(STATE_READ_TEST, STATUS_OK): STATE_READ_GET,
|
||||||
('readGet', 'ok'): 'stop',
|
(STATE_READ_GET, STATUS_OK): STATE_STOP,
|
||||||
('*', 'err'): 'stop'
|
(STATE_ANY, STATUS_ERROR): STATE_STOP
|
||||||
}
|
}
|
||||||
fsmReadNextVar = {
|
fsmReadNextVar = {
|
||||||
# ( state, status ) -> newState
|
# ( state, status ) -> newState
|
||||||
('start', 'ok'): 'readTestNext',
|
(STATE_START, STATUS_OK): STATE_READ_TEST_NEXT,
|
||||||
('readTestNext', 'ok'): 'readGetNext',
|
(STATE_READ_TEST_NEXT, STATUS_OK): STATE_READ_GET_NEXT,
|
||||||
('readGetNext', 'ok'): 'stop',
|
(STATE_READ_GET_NEXT, STATUS_OK): STATE_STOP,
|
||||||
('*', 'err'): 'stop'
|
(STATE_ANY, STATUS_ERROR): STATE_STOP
|
||||||
}
|
}
|
||||||
fsmWriteVar = {
|
fsmWriteVar = {
|
||||||
# ( state, status ) -> newState
|
# ( state, status ) -> newState
|
||||||
('start', 'ok'): 'writeTest',
|
(STATE_START, STATUS_OK): STATE_WRITE_TEST,
|
||||||
('writeTest', 'ok'): 'writeCommit',
|
(STATE_WRITE_TEST, STATUS_OK): STATE_WRITE_COMMIT,
|
||||||
('writeCommit', 'ok'): 'writeCleanup',
|
(STATE_WRITE_COMMIT, STATUS_OK): STATE_WRITE_CLEANUP,
|
||||||
('writeCleanup', 'ok'): 'readTest',
|
(STATE_WRITE_CLEANUP, STATUS_OK): STATE_READ_TEST,
|
||||||
# Do read after successful write
|
# Do read after successful write
|
||||||
('readTest', 'ok'): 'readGet',
|
(STATE_READ_TEST, STATUS_OK): STATE_READ_GET,
|
||||||
('readGet', 'ok'): 'stop',
|
(STATE_READ_GET, STATUS_OK): STATE_STOP,
|
||||||
# Error handling
|
# Error handling
|
||||||
('writeTest', 'err'): 'writeCleanup',
|
(STATE_WRITE_TEST, STATUS_ERROR): STATE_WRITE_CLEANUP,
|
||||||
('writeCommit', 'err'): 'writeUndo',
|
(STATE_WRITE_COMMIT, STATUS_ERROR): STATE_WRITE_UNDO,
|
||||||
('writeUndo', 'ok'): 'readTest',
|
(STATE_WRITE_UNDO, STATUS_OK): STATE_READ_TEST,
|
||||||
# Ignore read errors (removed columns)
|
# Ignore read errors (removed columns)
|
||||||
('readTest', 'err'): 'stop',
|
(STATE_READ_TEST, STATUS_ERROR): STATE_STOP,
|
||||||
('readGet', 'err'): 'stop',
|
(STATE_READ_GET, STATUS_ERROR): STATE_STOP,
|
||||||
('*', 'err'): 'stop'
|
(STATE_ANY, STATUS_ERROR): STATE_STOP
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FSM_CONTEXT = '_fsmContext'
|
||||||
|
|
||||||
|
FSM_SESSION_ID = nextid.Integer(0xffffffff)
|
||||||
|
|
||||||
def __init__(self, mibBuilder):
|
def __init__(self, mibBuilder):
|
||||||
self.mibBuilder = mibBuilder
|
self.mibBuilder = mibBuilder
|
||||||
self.lastBuildId = -1
|
self.lastBuildId = -1
|
||||||
|
@ -183,88 +205,115 @@ class MibInstrumController(AbstractMibInstrumController):
|
||||||
|
|
||||||
# MIB instrumentation
|
# 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:
|
try:
|
||||||
fsmContext = context['fsmState']
|
fsmContext = context[self.FSM_CONTEXT]
|
||||||
|
|
||||||
except KeyError:
|
except KeyError:
|
||||||
self.__indexMib()
|
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,))
|
debug.logger & debug.flagIns and debug.logger('flipFlopFsm: input var-binds %r' % (varBinds,))
|
||||||
|
|
||||||
mibTree, = self.mibBuilder.importSymbols('SNMPv2-SMI', 'iso')
|
mibTree, = self.mibBuilder.importSymbols('SNMPv2-SMI', 'iso')
|
||||||
|
|
||||||
outputVarBinds = fsmContext['varBinds']
|
|
||||||
state = fsmContext['state']
|
state = fsmContext['state']
|
||||||
status = fsmContext['status']
|
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:
|
else:
|
||||||
k = '*', status
|
debug.logger & debug.flagIns and debug.logger(
|
||||||
if k in fsmTable:
|
'flipFlopFsm: func %s initiated for %r' % (mgmtFun, varBind))
|
||||||
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)
|
|
||||||
|
|
||||||
def readVars(self, *varBinds, **context):
|
def readVars(self, *varBinds, **context):
|
||||||
self.flipFlopFsm(self.fsmReadVar, *varBinds, **context)
|
self.flipFlopFsm(self.fsmReadVar, *varBinds, **context)
|
||||||
|
|
Loading…
Reference in New Issue