pysnmp-sky/pysnmp/smi/instrum.py

277 lines
9.4 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
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):
fsmReadVar = {
# ( state, status ) -> newState
('start', 'ok'): 'readTest',
('readTest', 'ok'): 'readGet',
('readGet', 'ok'): 'stop',
('*', 'err'): 'stop'
}
fsmReadNextVar = {
# ( state, status ) -> newState
('start', 'ok'): 'readTestNext',
('readTestNext', 'ok'): 'readGetNext',
('readGetNext', 'ok'): 'stop',
('*', 'err'): 'stop'
}
fsmWriteVar = {
# ( state, status ) -> newState
('start', 'ok'): 'writeTest',
('writeTest', 'ok'): 'writeCommit',
('writeCommit', 'ok'): 'writeCleanup',
('writeCleanup', 'ok'): 'readTest',
# Do read after successful write
('readTest', 'ok'): 'readGet',
('readGet', 'ok'): 'stop',
# Error handling
('writeTest', 'err'): 'writeCleanup',
('writeCommit', 'err'): 'writeUndo',
('writeUndo', 'ok'): 'readTest',
# Ignore read errors (removed columns)
('readTest', 'err'): 'stop',
('readGet', 'err'): 'stop',
('*', 'err'): 'stop'
}
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 flipFlopFsm(self, fsmTable, *varBinds, **context):
try:
fsmContext = context['fsmState']
except KeyError:
self.__indexMib()
fsmContext = context['fsmState'] = dict(varBinds=[], state='start', 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
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)
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)