From 0c0d054e8e949cf7645fd3fe26640ee52893f18a Mon Sep 17 00:00:00 2001 From: Ilya Etingof Date: Wed, 24 Oct 2018 10:14:33 +0200 Subject: 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. --- pysnmp/entity/rfc3413/cmdrsp.py | 255 ++++++++++++++++++++-------------------- pysnmp/entity/rfc3413/ntfrcv.py | 8 +- pysnmp/smi/instrum.py | 221 ++++++++++++++++++++-------------- 3 files changed, 269 insertions(+), 215 deletions(-) (limited to 'pysnmp') diff --git a/pysnmp/entity/rfc3413/cmdrsp.py b/pysnmp/entity/rfc3413/cmdrsp.py index 8f6bbab5..e2d47f38 100644 --- a/pysnmp/entity/rfc3413/cmdrsp.py +++ b/pysnmp/entity/rfc3413/cmdrsp.py @@ -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,45 +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 - def completeMgmtOperation(self, varBinds, **context): - self.sendVarBinds(context['snmpEngine'], context['stateReference'], - 0, 0, varBinds) - self.releaseStateInformation(context['stateReference']) + exception = None - # rfc1905: 4.2.1 - def initiateMgmtOperation(self, snmpEngine, stateReference, contextName, PDU): - # rfc1905: 4.2.1.1 - mgmtFun = self.snmpContext.getMibInstrum(contextName).readVars - varBinds = v2c.apiPDU.getVarBinds(PDU) + 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)) - context = dict(snmpEngine=snmpEngine, - stateReference=stateReference, - acFun=self.verifyAccess, - cbFun=self.completeMgmtOperation, - cbCtx=self.cbCtx) + if not exception: + exception = value - mgmtFun(*varBinds, **context) + # 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]) -class NextCommandResponder(CommandResponderBase): - pduTypes = (rfc1905.GetNextRequestPDU.tagSet,) + # 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.2 - def initiateMgmtOperation(self, snmpEngine, stateReference, contextName, PDU): - # rfc1905: 4.2.2.1 - mgmtFun = self.snmpContext.getMibInstrum(contextName).readNextVars + 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): varBinds = v2c.apiPDU.getVarBinds(PDU) + mgmtFun = self._getMgmtFun(contextName) + context = dict(snmpEngine=snmpEngine, stateReference=stateReference, acFun=self.verifyAccess, @@ -306,32 +307,54 @@ class NextCommandResponder(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): + SUPPORTED_PDU_TYPES = (rfc1905.GetNextRequestPDU.tagSet,) + + # rfc1905: 4.2.2 + 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 = self._getMgmtFun(context['contextName']) + + mgmtFun(*context['reqVarBinds'], **context) - mgmtFun(*context['varBinds'], **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,) - - def completeMgmtOperation(self, varBinds, **context): - self.sendVarBinds(context['snmpEngine'], context['stateReference'], - 0, 0, varBinds) - self.releaseStateInformation(context['stateReference']) + SUPPORTED_PDU_TYPES = (rfc1905.SetRequestPDU.tagSet,) - # rfc1905: 4.2.5 - def initiateMgmtOperation(self, snmpEngine, stateReference, contextName, PDU): - mgmtFun = self.snmpContext.getMibInstrum(contextName).writeVars - - varBinds = v2c.apiPDU.getVarBinds(PDU) + SMI_ERROR_MAP = CommandResponderBase.SMI_ERROR_MAP.copy() - context = dict(snmpEngine=snmpEngine, - stateReference=stateReference, - acFun=self.verifyAccess, - cbFun=self.completeMgmtOperation, - cbCtx=self.cbCtx) + # turn missing OIDs into access denial + SMI_ERROR_MAP[pysnmp.smi.error.NoSuchObjectError] = 'notWritable' + SMI_ERROR_MAP[pysnmp.smi.error.NoSuchInstanceError] = 'notWritable' - # 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 diff --git a/pysnmp/entity/rfc3413/ntfrcv.py b/pysnmp/entity/rfc3413/ntfrcv.py index 34192ad3..df394a0c 100644 --- a/pysnmp/entity/rfc3413/ntfrcv.py +++ b/pysnmp/entity/rfc3413/ntfrcv.py @@ -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 diff --git a/pysnmp/smi/instrum.py b/pysnmp/smi/instrum.py index d84493ac..cec737d6 100644 --- a/pysnmp/smi/instrum.py +++ b/pysnmp/smi/instrum.py @@ -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) -- cgit v1.2.1