pysnmp-sky/pysnmp/smi/instrum.py

326 lines
11 KiB
Python

#
# This file is part of pysnmp software.
#
# Copyright (c) 2005-2018, Ilya Etingof <etingof@gmail.com>
# License: http://snmplabs.com/pysnmp/license.html
#
import sys
import traceback
import functools
from pysnmp import nextid
from pysnmp.smi import error
from pysnmp import debug
__all__ = ['AbstractMibInstrumController', 'MibInstrumController']
class AbstractMibInstrumController(object):
def readVars(self, *varBinds, **context):
raise error.NoSuchInstanceError(idx=0)
def readNextVars(self, *varBinds, **context):
raise error.EndOfMibViewError(idx=0)
def writeVars(self, *varBinds, **context):
raise error.NoSuchObjectError(idx=0)
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
(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_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_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
(STATE_READ_TEST, STATUS_OK): STATE_READ_GET,
(STATE_READ_GET, STATUS_OK): STATE_STOP,
# Error handling
(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)
(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
self.lastBuildSyms = {}
def getMibBuilder(self):
return self.mibBuilder
# MIB indexing
def __indexMib(self):
# Build a tree from MIB objects found at currently loaded modules
if self.lastBuildId == self.mibBuilder.lastBuildId:
return
(MibScalarInstance, MibScalar, MibTableColumn, MibTableRow,
MibTable) = self.mibBuilder.importSymbols(
'SNMPv2-SMI', 'MibScalarInstance', 'MibScalar',
'MibTableColumn', 'MibTableRow', 'MibTable'
)
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 = {}
rows = {}
cols = {}
# Sort by module name to give user a chance to slip-in
# custom MIB modules (that would be sorted out first)
mibSymbols = list(self.mibBuilder.mibSymbols.items())
mibSymbols.sort(key=lambda x: x[0], reverse=True)
for modName, mibMod in mibSymbols:
for symObj in mibMod.values():
if isinstance(symObj, MibTable):
tables[symObj.name] = symObj
elif isinstance(symObj, MibTableRow):
rows[symObj.name] = symObj
elif isinstance(symObj, MibTableColumn):
cols[symObj.name] = symObj
elif isinstance(symObj, MibScalarInstance):
instances[symObj.name] = symObj
elif isinstance(symObj, MibScalar):
scalars[symObj.name] = symObj
# Detach items from each other
for symName, parentName in self.lastBuildSyms.items():
if parentName in scalars:
scalars[parentName].unregisterSubtrees(symName)
elif parentName in cols:
cols[parentName].unregisterSubtrees(symName)
elif parentName in rows:
rows[parentName].unregisterSubtrees(symName)
else:
mibTree.unregisterSubtrees(symName)
lastBuildSyms = {}
# Attach Managed Objects Instances to Managed Objects
for inst in instances.values():
if inst.typeName in scalars:
scalars[inst.typeName].registerSubtrees(inst)
elif inst.typeName in cols:
cols[inst.typeName].registerSubtrees(inst)
else:
raise error.SmiError(
'Orphan MIB scalar instance %r at %r' % (inst, self)
)
lastBuildSyms[inst.name] = inst.typeName
# Attach Table Columns to Table Rows
for col in cols.values():
rowName = col.name[:-1] # XXX
if rowName in rows:
rows[rowName].registerSubtrees(col)
else:
raise error.SmiError(
'Orphan MIB table column %r at %r' % (col, self)
)
lastBuildSyms[col.name] = rowName
# Attach Table Rows to MIB tree
for row in rows.values():
mibTree.registerSubtrees(row)
lastBuildSyms[row.name] = mibTree.name
# Attach Tables to MIB tree
for table in tables.values():
mibTree.registerSubtrees(table)
lastBuildSyms[table.name] = mibTree.name
# Attach Scalars to MIB tree
for scalar in scalars.values():
mibTree.registerSubtrees(scalar)
lastBuildSyms[scalar.name] = mibTree.name
self.lastBuildSyms = lastBuildSyms
self.lastBuildId = self.mibBuilder.lastBuildId
debug.logger & debug.flagIns and debug.logger('__indexMib: rebuilt')
# MIB instrumentation
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[self.FSM_CONTEXT]
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 = fsmContext['state']
status = fsmContext['status']
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
else:
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)
def readNextVars(self, *varBinds, **context):
self.flipFlopFsm(self.fsmReadNextVar, *varBinds, **context)
def writeVars(self, *varBinds, **context):
self.flipFlopFsm(self.fsmWriteVar, *varBinds, **context)