diff options
Diffstat (limited to 'pysnmp/smi/instrum.py')
-rw-r--r-- | pysnmp/smi/instrum.py | 410 |
1 files changed, 316 insertions, 94 deletions
diff --git a/pysnmp/smi/instrum.py b/pysnmp/smi/instrum.py index cec737d6..7d6336e2 100644 --- a/pysnmp/smi/instrum.py +++ b/pysnmp/smi/instrum.py @@ -8,6 +8,7 @@ import sys import traceback import functools from pysnmp import nextid +from pysnmp.proto import rfc1905 from pysnmp.smi import error from pysnmp import debug @@ -15,13 +16,13 @@ __all__ = ['AbstractMibInstrumController', 'MibInstrumController'] class AbstractMibInstrumController(object): - def readVars(self, *varBinds, **context): + def readMibObjects(self, *varBinds, **context): raise error.NoSuchInstanceError(idx=0) - def readNextVars(self, *varBinds, **context): + def readNextMibObjects(self, *varBinds, **context): raise error.EndOfMibViewError(idx=0) - def writeVars(self, *varBinds, **context): + def writeMibObjects(self, *varBinds, **context): raise error.NoSuchObjectError(idx=0) @@ -43,21 +44,21 @@ class MibInstrumController(AbstractMibInstrumController): STATE_WRITE_UNDO = 'writeUndo' fsmReadVar = { - # ( state, status ) -> newState + # (state, status) -> newState (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 + # (state, status) -> newState (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 + # (state, status) -> newState (STATE_START, STATUS_OK): STATE_WRITE_TEST, (STATE_WRITE_TEST, STATUS_OK): STATE_WRITE_COMMIT, (STATE_WRITE_COMMIT, STATUS_OK): STATE_WRITE_CLEANUP, @@ -75,10 +76,6 @@ class MibInstrumController(AbstractMibInstrumController): (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 @@ -87,10 +84,32 @@ class MibInstrumController(AbstractMibInstrumController): def getMibBuilder(self): return self.mibBuilder - # MIB indexing - def __indexMib(self): - # Build a tree from MIB objects found at currently loaded modules + """Rebuild a tree from MIB objects found at currently loaded modules. + + If currently existing tree is out of date, walk over all Managed Objects + and Instances to structure Management Instrumentation objects into a tree + of the following layout: + + MibTree + | + +----MibScalar + | | + | +-----MibScalarInstance + | + +----MibTable + | + +----MibTableRow + | + +-------MibTableColumn + | + +------MibScalarInstance(s) + + Notes + ----- + Only Managed Objects (i.e. `OBJECT-TYPE`) get indexed here, various MIB + definitions and constants can't be SNMP managed so we drop them. + """ if self.lastBuildId == self.mibBuilder.lastBuildId: return @@ -102,26 +121,6 @@ class MibInstrumController(AbstractMibInstrumController): mibTree, = self.mibBuilder.importSymbols('SNMPv2-SMI', 'iso') - # - # Management Instrumentation gets organized as follows: - # - # MibTree - # | - # +----MibScalar - # | | - # | +-----MibScalarInstance - # | - # +----MibTable - # | - # +----MibTableRow - # | - # +-------MibTableColumn - # | - # +------MibScalarInstance(s) - # - # Mind you, only Managed Objects get indexed here, various MIB defs and - # constants can't be SNMP managed so we drop them. - # scalars = {} instances = {} tables = {} @@ -203,53 +202,109 @@ class MibInstrumController(AbstractMibInstrumController): debug.logger & debug.flagIns and debug.logger('__indexMib: rebuilt') - # MIB instrumentation + def flipFlopFsm(self, fsmTable, *varBinds, **context): + """Read, modify, create or remove Managed Objects Instances. + + Given one or more py:class:`~pysnmp.smi.rfc1902.ObjectType`, recursively + transitions corresponding Managed Objects Instances through the Finite State + Machine (FSM) states till it reaches its final stop state. + + Parameters + ---------- + fsmTable: :py:class:`dict` + A map of (`state`, `status`) -> `state` representing FSM transition matrix. + See :py:class:`RowStatus` for FSM transition logic. + + varBinds: :py:class:`tuple` of :py:class:`~pysnmp.smi.rfc1902.ObjectType` objects + representing Managed Objects Instances to work with. + + Other Parameters + ---------------- + \*\*context: + + Query parameters: + + * `cbFun` (callable) - user-supplied callable that is invoked to + pass the new value of the Managed Object Instance or an error. + + * `acFun` (callable) - user-supplied callable that is invoked to + authorize access to the requested Managed Object Instance. If + not supplied, no access control will be performed. + + Notes + ----- + The callback functions (e.g. `cbFun`, `acFun`) have the same signature + as this method where `varBind` contains the new Managed Object Instance + value. + + In case of errors, the `errors` key in the `context` dict will contain + a sequence of `dict` objects describing one or more errors that occur. + + Such error `dict` will have the `error`, `idx` and `state` keys providing + the details concerning the error, for which variable-binding and in what + state the system has failed. + """ + count = [0] + + cbFun = context.get('cbFun') + + def _cbFun(varBind, **context): + idx = context.pop('idx', None) + + err = context.pop('error', None) + if err: + # Move other errors into the errors sequence + errors = context['errors'] + errors.append( + {'error': err, + 'idx': idx, + 'varbind': varBind, + 'state': context['state']} + ) - def _flipFlopFsmCb(self, varBind, **context): - fsmContext = context[self.FSM_CONTEXT] + context['status'] = self.STATUS_ERROR - varBinds = fsmContext['varBinds'] + if idx is None: + if cbFun: + cbFun((), **context) + return - idx = context.pop('idx') + _varBinds = context['varBinds'] - if idx >= 0: - fsmContext['count'] += 1 + _varBinds[idx] = varBind - varBinds[idx] = varBind + count[0] += 1 debug.logger & debug.flagIns and debug.logger( - '_flipFlopFsmCb: var-bind %d, processed %d, expected %d' % (idx, fsmContext['count'], len(varBinds))) + '_cbFun: var-bind %d, processed %d, expected %d' % ( + idx, count[0], len(varBinds))) - if fsmContext['count'] < len(varBinds): + if count[0] < len(varBinds): return - debug.logger & debug.flagIns and debug.logger( - '_flipFlopFsmCb: finished, output %r' % (varBinds,)) + debug.logger & debug.flagIns and debug.logger( + '_cbFun: finished, output var-binds %r' % (_varBinds,)) - fsmCallable = fsmContext['fsmCallable'] + self.flipFlopFsm(fsmTable, *varBinds, **dict(context, cbFun=cbFun)) - fsmCallable(**context) + debug.logger & debug.flagIns and debug.logger('flipFlopFsm: input var-binds %r' % (varBinds,)) + + mibTree, = self.mibBuilder.importSymbols('SNMPv2-SMI', 'iso') - def flipFlopFsm(self, fsmTable, *varBinds, **context): try: - fsmContext = context[self.FSM_CONTEXT] + state = context['state'] + status = context['status'] + instances = context['instances'] + errors = context['errors'] + _varBinds = context['varBinds'] except KeyError: - self.__indexMib() - - 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') + state, status = self.STATE_START, self.STATUS_OK + instances = {} + errors = [] + _varBinds = list(varBinds) - state = fsmContext['state'] - status = fsmContext['status'] + self.__indexMib() debug.logger & debug.flagIns and debug.logger( 'flipFlopFsm: current state %s, status %s' % (state, status)) @@ -265,61 +320,228 @@ class MibInstrumController(AbstractMibInstrumController): 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)) + 'flipFlopFsm: state %s status %s -> transitioned into state %s' % (state, status, newState)) state = newState if state == self.STATE_STOP: - context.pop(self.FSM_CONTEXT, None) - - cbFun = context.get('cbFun') + context.pop('state', None) + context.pop('status', None) + context.pop('instances', None) + context.pop('varBinds', None) if cbFun: - varBinds = fsmContext['varBinds'] - cbFun(varBinds, **context) - + 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) + _cbFun(None, **context) + return - mgmtFun = getattr(mibTree, state, None) - if not mgmtFun: + actionFun = getattr(mibTree, state, None) + if not actionFun: 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) + actionFun(varBind, + **dict(context, cbFun=_cbFun, + state=state, status=status, + idx=idx, total=len(varBinds), + instances=instances, errors=errors, + varBinds=_varBinds, nextName=None)) - 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))) + debug.logger & debug.flagIns and debug.logger( + 'flipFlopFsm: func %s initiated for %r' % (actionFun, varBind)) - varBind = varBind[0], exc + @staticmethod + def _defaultErrorHandler(varBinds, **context): + """Raise exception on any error if user callback is missing""" + errors = context.get('errors') + if errors: + error = errors[-1] + raise error['error'] - fsmContext['status'] = self.STATUS_ERROR + def readMibObjects(self, *varBinds, **context): + """Read Managed Objects Instances. - self._flipFlopFsmCb(varBind, idx=idx, **context) + Given one or more py:class:`~pysnmp.smi.rfc1902.ObjectType` objects, read + all or none of the referenced Managed Objects Instances. - return + Parameters + ---------- + varBinds: :py:class:`tuple` of :py:class:`~pysnmp.smi.rfc1902.ObjectType` objects + representing Managed Objects Instances to read. - else: - debug.logger & debug.flagIns and debug.logger( - 'flipFlopFsm: func %s initiated for %r' % (mgmtFun, varBind)) + Other Parameters + ---------------- + \*\*context: + + Query parameters: + + * `cbFun` (callable) - user-supplied callable that is invoked to + pass the new value of the Managed Object Instance or an error. + If not provided, default function will raise exception in case + of an error. + + * `acFun` (callable) - user-supplied callable that is invoked to + authorize access to the requested Managed Object Instance. If + not supplied, no access control will be performed. + + Notes + ----- + The signature of the callback functions (e.g. `cbFun`, `acFun`) is this: + + .. code-block: python + + def cbFun(varBinds, **context): + errors = context.get(errors) + if errors: + print(errors[0].error) + + else: + print(', '.join('%s = %s' % varBind for varBind in varBinds)) + + In case of errors, the `errors` key in the `context` dict will contain + a sequence of `dict` objects describing one or more errors that occur. + + If a non-existing Managed Object is referenced, no error will be + reported, but the values returned in the `varBinds` would be either + :py:class:`NoSuchObject` (indicating non-existent Managed Object) or + :py:class:`NoSuchInstance` (if Managed Object exists, but is not + instantiated). + """ + if 'cbFun' not in context: + context['cbFun'] = self._defaultErrorHandler - def readVars(self, *varBinds, **context): self.flipFlopFsm(self.fsmReadVar, *varBinds, **context) - def readNextVars(self, *varBinds, **context): + def readNextMibObjects(self, *varBinds, **context): + """Read Managed Objects Instances next to the given ones. + + Given one or more py:class:`~pysnmp.smi.rfc1902.ObjectType` objects, read + all or none of the Managed Objects Instances next to the referenced ones. + + Parameters + ---------- + varBinds: :py:class:`tuple` of :py:class:`~pysnmp.smi.rfc1902.ObjectType` objects + representing Managed Objects Instances to read next to. + + Other Parameters + ---------------- + \*\*context: + + Query parameters: + + * `cbFun` (callable) - user-supplied callable that is invoked to + pass the new value of the Managed Object Instance or an error. + If not provided, default function will raise exception in case + of an error. + + * `acFun` (callable) - user-supplied callable that is invoked to + authorize access to the requested Managed Object Instance. If + not supplied, no access control will be performed. + + Notes + ----- + The signature of the callback functions (e.g. `cbFun`, `acFun`) is this: + + .. code-block: python + + def cbFun(varBinds, **context): + errors = context.get(errors) + if errors: + print(errors[0].error) + + else: + print(', '.join('%s = %s' % varBind for varBind in varBinds)) + + In case of errors, the `errors` key in the `context` dict will contain + a sequence of `dict` objects describing one or more errors that occur. + + If a non-existing Managed Object is referenced, no error will be + reported, but the values returned in the `varBinds` would be one of: + :py:class:`NoSuchObject` (indicating non-existent Managed Object) or + :py:class:`NoSuchInstance` (if Managed Object exists, but is not + instantiated) or :py:class:`EndOfMibView` (when the last Managed Object + Instance has been read). + + When :py:class:`NoSuchObject` or :py:class:`NoSuchInstance` values are + returned, the caller is expected to repeat the same call with some + or all `varBinds` returned to progress towards the end of the + implemented MIB. + """ + if 'cbFun' not in context: + context['cbFun'] = self._defaultErrorHandler + self.flipFlopFsm(self.fsmReadNextVar, *varBinds, **context) - def writeVars(self, *varBinds, **context): + def writeMibObjects(self, *varBinds, **context): + """Create, destroy or modify Managed Objects Instances. + + Given one or more py:class:`~pysnmp.smi.rfc1902.ObjectType` objects, create, + destroy or modify all or none of the referenced Managed Objects Instances. + + If a non-existing Managed Object Instance is written, the new Managed Object + Instance will be created with the value given in the `varBinds`. + + If existing Managed Object Instance is being written, its value is changed + to the new one. + + Unless it's a :py:class:`RowStatus` object of a SMI table, in which case the + outcome of the *write* operation depends on the :py:class:`RowStatus` + transition. The whole table row could be created or destroyed or brought + on/offline. + + When SMI table row is brought online (i.e. into the *active* state), all + columns will be checked for consistency. Error will be reported and write + operation will fail if inconsistency is found. + + Parameters + ---------- + varBinds: :py:class:`tuple` of :py:class:`~pysnmp.smi.rfc1902.ObjectType` objects + representing Managed Objects Instances to modify. + + Other Parameters + ---------------- + \*\*context: + + Query parameters: + + * `cbFun` (callable) - user-supplied callable that is invoked to + pass the new value of the Managed Object Instance or an error. + If not provided, default function will raise exception in case + of an error. + + * `acFun` (callable) - user-supplied callable that is invoked to + authorize access to the requested Managed Object Instance. If + not supplied, no access control will be performed. + + Notes + ----- + The signature of the callback functions (e.g. `cbFun`, `acFun`) is this: + + .. code-block: python + + def cbFun(varBinds, **context): + errors = context.get(errors) + if errors: + print(errors[0].error) + + else: + print(', '.join('%s = %s' % varBind for varBind in varBinds)) + + In case of errors, the `errors` key in the `context` dict will contain + a sequence of `dict` objects describing one or more errors that occur. + + If a non-existing Managed Object is referenced, no error will be + reported, but the values returned in the `varBinds` would be one of: + :py:class:`NoSuchObject` (indicating non-existent Managed Object) or + :py:class:`NoSuchInstance` (if Managed Object exists, but can't be + modified. + """ + if 'cbFun' not in context: + context['cbFun'] = self._defaultErrorHandler + self.flipFlopFsm(self.fsmWriteVar, *varBinds, **context) |