Merge branch 'async-mib-instrumentation' of github.com:etingof/pysnmp into async-mib-instrumentation

async-mib-instrumentation
Ilya Etingof 2018-10-13 09:21:11 +02:00
commit 86ade02826
6 changed files with 140 additions and 161 deletions

View File

@ -24,11 +24,17 @@ Revision 5.0.0, released 2018-10-??
* MIB instrumentation methods signatures simplified to accept
just var-binds (as var-arg), the rest of the parameters packed
into opaque kwargs
* CommandResponder application passes `snmpEngine` and optionally
user-supplied `cbCtx` object throughout the MIB instrumentation
methods. The goal is to let MIB objects access/modify whatever
custom Python objects they need while being called back.
* CommandResponder refactored to facilitate asynchronous
MIB instrumentation routines. The `readVars`, `readNextVars` and
`writeVars` MIB controller methods return immediately and
deliver their results via a call back.
- The high-level API (`hlapi`) extended to cover lightweight SNMP v1arch
in hope to ease the use of packet-level SNMP API.

View File

@ -51,13 +51,23 @@ if __name__ == '__main__':
mibInstrum = instrum.MibInstrumController(mibBuilder)
def cbFun(varBinds, **context):
for oid, val in varBinds:
if exval.endOfMib.isSameTypeWith(val):
context['state']['stop'] = True
print('%s = %s' % ('.'.join([str(x) for x in oid]), not val.isValue and 'N/A' or val.prettyPrint()))
context['state']['varBinds'] = varBinds
context = {
'cbFun': cbFun,
'state': {
'varBinds': [((1, 3, 6), None)],
'stop': False
}
}
print('Remote manager read access to MIB instrumentation (table walk)')
varBinds = [((), None)]
while True:
varBinds = mibInstrum.readNextVars(*varBinds)
oid, val = varBinds[0]
if exval.endOfMib.isSameTypeWith(val):
break
print(oid, val.prettyPrint())
while not context['state']['stop']:
mibInstrum.readNextVars(*context['state']['varBinds'], **context)
print('done')

View File

@ -26,24 +26,40 @@ snmpCommunityEntry, = mibBuilder.importSymbols(
instanceId = snmpCommunityEntry.getInstIdFromIndices('my-router')
print('done')
def cbFun(varBinds, **context):
for oid, val in varBinds:
print('%s = %s' % ('.'.join([str(x) for x in oid]), not val.isValue and 'N/A' or val.prettyPrint()))
print('Create/update SNMP-COMMUNITY-MIB::snmpCommunityEntry table row: ')
varBinds = mibInstrum.writeVars(
mibInstrum.writeVars(
(snmpCommunityEntry.name + (2,) + instanceId, 'mycomm'),
(snmpCommunityEntry.name + (3,) + instanceId, 'mynmsname'),
(snmpCommunityEntry.name + (7,) + instanceId, 'volatile')
(snmpCommunityEntry.name + (7,) + instanceId, 'volatile'),
cbFun=cbFun
)
for oid, val in varBinds:
print('%s = %s' % ('.'.join([str(x) for x in oid]), not val.isValue and 'N/A' or val.prettyPrint()))
print('done')
def cbFun(varBinds, **context):
for oid, val in varBinds:
if exval.endOfMib.isSameTypeWith(val):
context['state']['stop'] = True
print('%s = %s' % ('.'.join([str(x) for x in oid]), not val.isValue and 'N/A' or val.prettyPrint()))
context['state']['varBinds'] = varBinds
context = {
'cbFun': cbFun,
'state': {
'varBinds': [((1, 3, 6), None)],
'stop': False
}
}
print('Read whole MIB (table walk)')
varBinds = [((), None)]
while True:
varBinds = mibInstrum.readNextVars(*varBinds)
oid, val = varBinds[0]
if exval.endOfMib.isSameTypeWith(val):
break
print('%s = %s' % ('.'.join([str(x) for x in oid]), not val.isValue and 'N/A' or val.prettyPrint()))
while not context['state']['stop']:
mibInstrum.readNextVars(*context['state']['varBinds'], **context)
print('done')
print('Unloading MIB modules...'),

View File

@ -54,7 +54,9 @@ snmpContext = context.SnmpContext(snmpEngine)
# always echos request var-binds in response.
class EchoMibInstrumController(instrum.AbstractMibInstrumController):
def readVars(self, *varBinds, **context):
return [(ov[0], v2c.OctetString('You queried OID %s' % ov[0])) for ov in varBinds]
cbFun = context.get('cbFun')
if cbFun:
cbFun([(ov[0], v2c.OctetString('You queried OID %s' % ov[0])) for ov in varBinds], **context)
# Create a custom Management Instrumentation Controller and register at

View File

@ -25,8 +25,7 @@ class CommandResponderBase(object):
self.cbCtx = cbCtx
self.__pendingReqs = {}
def handleMgmtOperation(self, snmpEngine, stateReference,
contextName, PDU, acCtx):
def initiateMgmtOperation(self, snmpEngine, stateReference, contextName, PDU):
pass
def close(self, snmpEngine):
@ -147,7 +146,7 @@ class CommandResponderBase(object):
'processPdu: stateReference %s, varBinds %s' % (stateReference, varBinds))
try:
self.handleMgmtOperation(snmpEngine, stateReference, contextName, PDU)
self.initiateMgmtOperation(snmpEngine, stateReference, contextName, PDU)
# SNMPv2 SMI exceptions
except pysnmp.smi.error.GenError:
@ -263,53 +262,79 @@ class CommandResponderBase(object):
class GetCommandResponder(CommandResponderBase):
pduTypes = (rfc1905.GetRequestPDU.tagSet,)
def completeMgmtOperation(self, varBinds, **context):
self.sendVarBinds(context['snmpEngine'], context['stateReference'],
0, 0, varBinds)
self.releaseStateInformation(context['stateReference'])
# rfc1905: 4.2.1
def handleMgmtOperation(self, snmpEngine, stateReference, contextName, PDU):
def initiateMgmtOperation(self, snmpEngine, stateReference, contextName, PDU):
# rfc1905: 4.2.1.1
mgmtFun = self.snmpContext.getMibInstrum(contextName).readVars
varBinds = v2c.apiPDU.getVarBinds(PDU)
context = dict(snmpEngine=snmpEngine, acFun=self.verifyAccess, cbCtx=self.cbCtx)
context = dict(snmpEngine=snmpEngine,
stateReference=stateReference,
acFun=self.verifyAccess,
cbFun=self.completeMgmtOperation,
cbCtx=self.cbCtx)
rspVarBinds = mgmtFun(*varBinds, **context)
self.sendVarBinds(snmpEngine, stateReference, 0, 0, rspVarBinds)
self.releaseStateInformation(stateReference)
mgmtFun(*varBinds, **context)
class NextCommandResponder(CommandResponderBase):
pduTypes = (rfc1905.GetNextRequestPDU.tagSet,)
def completeMgmtOperation(self, varBinds, **context):
self.sendVarBinds(context['snmpEngine'], context['stateReference'],
0, 0, varBinds)
self.releaseStateInformation(context['stateReference'])
# rfc1905: 4.2.2
def handleMgmtOperation(self, snmpEngine, stateReference, contextName, PDU):
def initiateMgmtOperation(self, snmpEngine, stateReference, contextName, PDU):
# rfc1905: 4.2.2.1
mgmtFun = self.snmpContext.getMibInstrum(contextName).readNextVars
varBinds = v2c.apiPDU.getVarBinds(PDU)
context = dict(snmpEngine=snmpEngine, acFun=self.verifyAccess, cbCtx=self.cbCtx)
context = dict(snmpEngine=snmpEngine,
stateReference=stateReference,
acFun=self.verifyAccess,
cbFun=self.completeMgmtOperation,
cbCtx=self.cbCtx)
while True:
rspVarBinds = mgmtFun(*varBinds, **context)
try:
self.sendVarBinds(snmpEngine, stateReference, 0, 0, rspVarBinds)
except error.StatusInformation:
idx = sys.exc_info()[1]['idx']
varBinds[idx] = (rspVarBinds[idx][0], varBinds[idx][1])
else:
break
self.releaseStateInformation(stateReference)
mgmtFun(*varBinds, **context)
class BulkCommandResponder(CommandResponderBase):
pduTypes = (rfc1905.GetBulkRequestPDU.tagSet,)
maxVarBinds = 64
def _completeNonRepeaters(self, varBinds, **context):
context['rspVarBinds'][:] = varBinds
context['cbFun'] = self.completeMgmtOperation
mgmtFun = self.snmpContext.getMibInstrum(context['contextName']).readNextVars
mgmtFun(*context['varBinds'], **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
context['cbFun'] = self.completeMgmtOperation
mgmtFun(*varBinds[-context['counters']['R']:], **context)
else:
self.sendVarBinds(context['snmpEngine'], context['stateReference'],
0, 0, varBinds)
self.releaseStateInformation(context['stateReference'])
# rfc1905: 4.2.3
def handleMgmtOperation(self, snmpEngine, stateReference, contextName, PDU):
def initiateMgmtOperation(self, snmpEngine, stateReference, contextName, PDU):
nonRepeaters = v2c.apiBulkPDU.getNonRepeaters(PDU)
if nonRepeaters < 0:
nonRepeaters = 0
@ -318,68 +343,60 @@ class BulkCommandResponder(CommandResponderBase):
if maxRepetitions < 0:
maxRepetitions = 0
reqVarBinds = v2c.apiPDU.getVarBinds(PDU)
varBinds = v2c.apiPDU.getVarBinds(PDU)
N = min(int(nonRepeaters), len(reqVarBinds))
N = min(int(nonRepeaters), len(varBinds))
M = int(maxRepetitions)
R = max(len(reqVarBinds) - N, 0)
R = max(len(varBinds) - N, 0)
if R:
M = min(M, self.maxVarBinds // R)
debug.logger & debug.flagApp and debug.logger('handleMgmtOperation: N %d, M %d, R %d' % (N, M, R))
debug.logger & debug.flagApp and debug.logger(
'initiateMgmtOperation: N %d, M %d, R %d' % (N, M, R))
mgmtFun = self.snmpContext.getMibInstrum(contextName).readNextVars
context = dict(snmpEngine=snmpEngine, acFun=self.verifyAccess, cbCtx=self.cbCtx)
context = dict(snmpEngine=snmpEngine,
stateReference=stateReference,
contextName=contextName,
acFun=self.verifyAccess,
cbFun=self._completeNonRepeaters,
cbCtx=self.cbCtx,
varBinds=varBinds[-R:],
counters={'M': M, 'R': R},
rspVarBinds=[])
if N:
# TODO(etingof): manage all PDU var-binds in a single call
rspVarBinds = mgmtFun(*reqVarBinds[:N], **context)
else:
rspVarBinds = []
varBinds = reqVarBinds[-R:]
while M and R:
rspVarBinds.extend(mgmtFun(*varBinds, **context))
varBinds = rspVarBinds[-R:]
M -= 1
if len(rspVarBinds):
self.sendVarBinds(snmpEngine, stateReference, 0, 0, rspVarBinds)
self.releaseStateInformation(stateReference)
else:
raise pysnmp.smi.error.SmiError()
mgmtFun(*varBinds[:N], **context)
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'])
# rfc1905: 4.2.5
def handleMgmtOperation(self, snmpEngine, stateReference, contextName, PDU):
def initiateMgmtOperation(self, snmpEngine, stateReference, contextName, PDU):
mgmtFun = self.snmpContext.getMibInstrum(contextName).writeVars
varBinds = v2c.apiPDU.getVarBinds(PDU)
instrumError = None
context = dict(snmpEngine=snmpEngine, acFun=self.verifyAccess, cbCtx=self.cbCtx)
context = dict(snmpEngine=snmpEngine,
stateReference=stateReference,
acFun=self.verifyAccess,
cbFun=self.completeMgmtOperation,
cbCtx=self.cbCtx)
# rfc1905: 4.2.5.1-13
try:
rspVarBinds = mgmtFun(*varBinds, **context)
mgmtFun(*varBinds, **context)
except (pysnmp.smi.error.NoSuchObjectError,
pysnmp.smi.error.NoSuchInstanceError):
instrumError = pysnmp.smi.error.NotWritableError()
instrumError.update(sys.exc_info()[1])
else:
self.sendVarBinds(snmpEngine, stateReference, 0, 0, rspVarBinds)
self.releaseStateInformation(stateReference)
if instrumError:
self.releaseStateInformation(stateReference)
raise instrumError

View File

@ -183,80 +183,6 @@ class MibInstrumController(AbstractMibInstrumController):
# MIB instrumentation
@staticmethod
def _collectVarBindsCb(varBind, cbCtx, **context):
cbFun, (cbCtx, idx, varBindsLen, collectedVarBinds) = cbCtx
if len(collectedVarBinds) < varBindsLen:
collectedVarBinds[idx] = varBind
return
varBinds = [vb[1] for vb in sorted(collectedVarBinds.items(), key=lambda x: x[0])]
cbFun(varBinds, cbCtx, **context)
@staticmethod
def _flipFlopFsmCb(varBinds, cbCtx, **context):
cbFun, (cbCtx, state) = cbCtx
if any([varBind for varBind in varBinds if isinstance(varBind[1], Exception)]):
status = 'err'
debug.logger & debug.flagIns and debug.logger(
'_flipFlopFsmCb: current state %s, status %s' % (state, status))
try:
newState = fsmTable[(state, status)]
except KeyError:
try:
newState = fsmTable[('*', status)]
except KeyError:
raise error.SmiError(
'Unresolved FSM state %s, %s' % (state, status)
)
debug.logger & debug.flagIns and debug.logger(
'_flipFlopFsmCb: state %s status %s -> new state %s' % (state, status, newState))
state = newState
status = 'ok'
if state == 'stop':
cbFun(varBinds, cbCtx, **context)
return
mgmtFun = getattr(mibTree, state, None)
if not mgmtFun:
raise error.SmiError(
'Unsupported state handler %s at %s' % (state, self)
)
collectedVarBinds = {}
for idx, (name, val) in enumerate(varBinds):
_cbCtx = self._flipFlopFsmCb, (cbCtx, idx, len(varBinds), collectedVarBinds)
varBind = (tuple(name), val)
try:
mgmtFun(varBind, self._collectVarBindsCb, _cbCtx, **context)
except error.SmiError:
exc = sys.exc_info()
debug.logger & debug.flagIns and debug.logger(
'_flipFlopFsmCb: fun %s exception %s for %r with traceback: %s' % (
mgmtFun, exc[0], varBind, traceback.format_exception(*exc)))
varBind = name, exc
self._collectVarBindsCb(varBind, _cbCtx, **context)
else:
debug.logger & debug.flagIns and debug.logger(
'_flipFlopFsmCb: func %s succeeded for %r' % (mgmtFun, varBind))
def flipFlopFsm(self, fsmTable, *varBinds, **context):
try:
@ -321,7 +247,7 @@ class MibInstrumController(AbstractMibInstrumController):
break
else:
debug.logger & debug.flagIns and debug.logger(
'flipFlopFsm: fun %s suceeded for %s=%r' % (mgmtFun, name, val))
'flipFlopFsm: fun %s succeeded for %s=%r' % (mgmtFun, name, val))
if rval is not None:
outputVarBinds.append((rval[0], rval[1]))
@ -336,13 +262,15 @@ class MibInstrumController(AbstractMibInstrumController):
# (seems to be irrelevant on Py3 but just in case)
del origTraceback
return outputVarBinds
cbFun = context.get('cbFun')
if cbFun:
cbFun(outputVarBinds, **context)
def readVars(self, *varBinds, **context):
return self.flipFlopFsm(self.fsmReadVar, *varBinds, **context)
self.flipFlopFsm(self.fsmReadVar, *varBinds, **context)
def readNextVars(self, *varBinds, **context):
return self.flipFlopFsm(self.fsmReadNextVar, *varBinds, **context)
self.flipFlopFsm(self.fsmReadNextVar, *varBinds, **context)
def writeVars(self, *varBinds, **context):
return self.flipFlopFsm(self.fsmWriteVar, *varBinds, **context)
self.flipFlopFsm(self.fsmWriteVar, *varBinds, **context)