summaryrefslogtreecommitdiff
path: root/pysnmp/smi/instrum.py
diff options
context:
space:
mode:
Diffstat (limited to 'pysnmp/smi/instrum.py')
-rw-r--r--pysnmp/smi/instrum.py410
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)