summaryrefslogtreecommitdiff
path: root/pysnmp
diff options
context:
space:
mode:
authorIlya Etingof <etingof@gmail.com>2018-12-29 12:48:29 +0100
committerGitHub <noreply@github.com>2018-12-29 12:48:29 +0100
commitd7534580837445553d8ffdc86fd5a161b9d57af1 (patch)
tree9f0223047bd118f00baefdaaea448d1e87890c0c /pysnmp
parent6dd1d0bceb8a0d9bce603f2095a7971374e81f6f (diff)
downloadpysnmp-git-d7534580837445553d8ffdc86fd5a161b9d57af1.tar.gz
Redesigned SMI objects management model (#214)
The primary motivation behind this redesign is to allow asynchronous operations between SNMP responder and the data source feeding its MIB. This is achieved by redesigning all `read*`, `write*`, `create*` and `destroy*` methods of the `SNMPv2-SMI` MIB objects to return immediately and deliver their results via a call back. This modification brings significant and backward incompatible changes to the low-level MIB operations. The pysnmp MIB modules compiled for older pysnmp remain compatible.
Diffstat (limited to 'pysnmp')
-rw-r--r--pysnmp/entity/config.py117
-rw-r--r--pysnmp/entity/rfc3413/cmdrsp.py219
-rw-r--r--pysnmp/hlapi/v1arch/asyncore/cmdgen.py2
-rw-r--r--pysnmp/proto/rfc3412.py4
-rw-r--r--pysnmp/proto/secmod/rfc3414/service.py11
-rw-r--r--pysnmp/smi/error.py12
-rw-r--r--pysnmp/smi/instrum.py410
-rw-r--r--pysnmp/smi/mibs/INET-ADDRESS-MIB.py4
-rw-r--r--pysnmp/smi/mibs/PYSNMP-SOURCE-MIB.py2
-rw-r--r--pysnmp/smi/mibs/PYSNMP-USM-MIB.py2
-rw-r--r--pysnmp/smi/mibs/SNMP-COMMUNITY-MIB.py2
-rw-r--r--pysnmp/smi/mibs/SNMPv2-SMI.py2964
-rw-r--r--pysnmp/smi/mibs/SNMPv2-TC.py8
13 files changed, 3002 insertions, 755 deletions
diff --git a/pysnmp/entity/config.py b/pysnmp/entity/config.py
index 65883104..6aed74c4 100644
--- a/pysnmp/entity/config.py
+++ b/pysnmp/entity/config.py
@@ -60,6 +60,9 @@ privServices = {des.Des.serviceID: des.Des(),
aes256.Aes256.serviceID: aes256.Aes256(), # non-standard
nopriv.NoPriv.serviceID: nopriv.NoPriv()}
+# This module uses Management Instrumentation subsystem in purely
+# synchronous manner. The assumption is that the Management
+# Instrumentation calls never yield control but block.
def __cookV1SystemInfo(snmpEngine, communityIndex):
mibBuilder = snmpEngine.msgAndPduDsp.mibInstrumController.mibBuilder
@@ -84,11 +87,11 @@ def addV1System(snmpEngine, communityIndex, communityName,
if contextName is None:
contextName = null
- snmpEngine.msgAndPduDsp.mibInstrumController.writeVars(
+ snmpEngine.msgAndPduDsp.mibInstrumController.writeMibObjects(
(snmpCommunityEntry.name + (8,) + tblIdx, 'destroy'),
**dict(snmpEngine=snmpEngine)
)
- snmpEngine.msgAndPduDsp.mibInstrumController.writeVars(
+ snmpEngine.msgAndPduDsp.mibInstrumController.writeMibObjects(
(snmpCommunityEntry.name + (1,) + tblIdx, communityIndex),
(snmpCommunityEntry.name + (2,) + tblIdx, communityName),
(snmpCommunityEntry.name + (3,) + tblIdx, securityName is not None and securityName or communityIndex),
@@ -104,7 +107,7 @@ def addV1System(snmpEngine, communityIndex, communityName,
def delV1System(snmpEngine, communityIndex):
(snmpCommunityEntry, tblIdx,
snmpEngineID) = __cookV1SystemInfo(snmpEngine, communityIndex)
- snmpEngine.msgAndPduDsp.mibInstrumController.writeVars(
+ snmpEngine.msgAndPduDsp.mibInstrumController.writeMibObjects(
(snmpCommunityEntry.name + (8,) + tblIdx, 'destroy'),
**dict(snmpEngine=snmpEngine)
)
@@ -138,6 +141,7 @@ def addV3User(snmpEngine, userName,
if securityName is None:
securityName = userName
+
(snmpEngineID, usmUserEntry, tblIdx1,
pysnmpUsmSecretEntry, tblIdx2) = __cookV3UserInfo(snmpEngine, userName, securityEngineId)
@@ -147,11 +151,11 @@ def addV3User(snmpEngine, userName,
# Load clone-from (may not be needed)
zeroDotZero, = mibBuilder.importSymbols('SNMPv2-SMI', 'zeroDotZero')
- snmpEngine.msgAndPduDsp.mibInstrumController.writeVars(
+ snmpEngine.msgAndPduDsp.mibInstrumController.writeMibObjects(
(usmUserEntry.name + (13,) + tblIdx1, 'destroy'),
**dict(snmpEngine=snmpEngine)
)
- snmpEngine.msgAndPduDsp.mibInstrumController.writeVars(
+ snmpEngine.msgAndPduDsp.mibInstrumController.writeMibObjects(
(usmUserEntry.name + (2,) + tblIdx1, userName),
(usmUserEntry.name + (3,) + tblIdx1, securityName),
(usmUserEntry.name + (4,) + tblIdx1, zeroDotZero.name),
@@ -183,7 +187,7 @@ def addV3User(snmpEngine, userName,
raise error.PySnmpError('Unknown priv protocol %s' % (privProtocol,))
# Commit localized keys
- snmpEngine.msgAndPduDsp.mibInstrumController.writeVars(
+ snmpEngine.msgAndPduDsp.mibInstrumController.writeMibObjects(
(pysnmpUsmKeyEntry.name + (1,) + tblIdx1, localAuthKey),
(pysnmpUsmKeyEntry.name + (2,) + tblIdx1, localPrivKey),
(pysnmpUsmKeyEntry.name + (3,) + tblIdx1, hashedAuthPassphrase),
@@ -193,11 +197,11 @@ def addV3User(snmpEngine, userName,
# Commit passphrases
- snmpEngine.msgAndPduDsp.mibInstrumController.writeVars(
+ snmpEngine.msgAndPduDsp.mibInstrumController.writeMibObjects(
(pysnmpUsmSecretEntry.name + (4,) + tblIdx2, 'destroy'),
**dict(snmpEngine=snmpEngine)
)
- snmpEngine.msgAndPduDsp.mibInstrumController.writeVars(
+ snmpEngine.msgAndPduDsp.mibInstrumController.writeMibObjects(
(pysnmpUsmSecretEntry.name + (1,) + tblIdx2, userName),
(pysnmpUsmSecretEntry.name + (2,) + tblIdx2, authKey),
(pysnmpUsmSecretEntry.name + (3,) + tblIdx2, privKey),
@@ -211,32 +215,46 @@ def delV3User(snmpEngine,
securityEngineId=None):
(snmpEngineID, usmUserEntry, tblIdx1, pysnmpUsmSecretEntry,
tblIdx2) = __cookV3UserInfo(snmpEngine, userName, securityEngineId)
- snmpEngine.msgAndPduDsp.mibInstrumController.writeVars(
+ snmpEngine.msgAndPduDsp.mibInstrumController.writeMibObjects(
(usmUserEntry.name + (13,) + tblIdx1, 'destroy'),
**dict(snmpEngine=snmpEngine)
)
- snmpEngine.msgAndPduDsp.mibInstrumController.writeVars(
+ snmpEngine.msgAndPduDsp.mibInstrumController.writeMibObjects(
(pysnmpUsmSecretEntry.name + (4,) + tblIdx2, 'destroy'),
**dict(snmpEngine=snmpEngine)
)
# Drop all derived rows
+
+ def _cbFun(varBinds, **context):
+ name, val = varBinds[0]
+
+ if exval.endOfMib.isSameTypeWith(val):
+ context['user']['varBinds'] = ()
+
+ elif not (exval.noSuchInstance.isSameTypeWith(val) or
+ exval.noSuchObject.isSameTypeWith(val)):
+ context['user']['varBinds'] = varBinds
+
+ elif varBinds[0][0][:len(initialVarBinds[0][0])] != initialVarBinds[0][0]:
+ context['user']['varBinds'] = ()
+
+ else:
+ delV3User(snmpEngine, varBinds[1][1], varBinds[0][1])
+ context['user']['varBinds'] = initialVarBinds
+
varBinds = initialVarBinds = (
(usmUserEntry.name + (1,), None), # usmUserEngineID
(usmUserEntry.name + (2,), None), # usmUserName
(usmUserEntry.name + (4,), None) # usmUserCloneFrom
)
- while varBinds:
- varBinds = snmpEngine.msgAndPduDsp.mibInstrumController.readNextVars(
- *varBinds, **dict(snmpEngine=snmpEngine)
+
+ user = {'varBinds': varBinds}
+
+ while user['varBinds']:
+ snmpEngine.msgAndPduDsp.mibInstrumController.readNextMibObjects(
+ *user['varBinds'], **dict(snmpEngine=snmpEngine, user=user, cbFun=_cbFun)
)
- if varBinds[0][1].isSameTypeWith(rfc1905.endOfMibView):
- break
- if varBinds[0][0][:len(initialVarBinds[0][0])] != initialVarBinds[0][0]:
- break
- elif varBinds[2][1] == tblIdx1: # cloned from this entry
- delV3User(snmpEngine, varBinds[1][1], varBinds[0][1])
- varBinds = initialVarBinds
def __cookTargetParamsInfo(snmpEngine, name):
@@ -260,11 +278,11 @@ def addTargetParams(snmpEngine, name, securityName, securityLevel, mpModel=3):
snmpTargetParamsEntry, tblIdx = __cookTargetParamsInfo(snmpEngine, name)
- snmpEngine.msgAndPduDsp.mibInstrumController.writeVars(
+ snmpEngine.msgAndPduDsp.mibInstrumController.writeMibObjects(
(snmpTargetParamsEntry.name + (7,) + tblIdx, 'destroy'),
**dict(snmpEngine=snmpEngine)
)
- snmpEngine.msgAndPduDsp.mibInstrumController.writeVars(
+ snmpEngine.msgAndPduDsp.mibInstrumController.writeMibObjects(
(snmpTargetParamsEntry.name + (1,) + tblIdx, name),
(snmpTargetParamsEntry.name + (2,) + tblIdx, mpModel),
(snmpTargetParamsEntry.name + (3,) + tblIdx, securityModel),
@@ -277,7 +295,7 @@ def addTargetParams(snmpEngine, name, securityName, securityLevel, mpModel=3):
def delTargetParams(snmpEngine, name):
snmpTargetParamsEntry, tblIdx = __cookTargetParamsInfo(snmpEngine, name)
- snmpEngine.msgAndPduDsp.mibInstrumController.writeVars(
+ snmpEngine.msgAndPduDsp.mibInstrumController.writeMibObjects(
(snmpTargetParamsEntry.name + (7,) + tblIdx, 'destroy'),
**dict(snmpEngine=snmpEngine)
)
@@ -313,11 +331,11 @@ def addTargetAddr(snmpEngine, addrName, transportDomain, transportAddress,
sourceAddress = ('::', 0)
sourceAddress = TransportAddressIPv6(sourceAddress)
- snmpEngine.msgAndPduDsp.mibInstrumController.writeVars(
+ snmpEngine.msgAndPduDsp.mibInstrumController.writeMibObjects(
(snmpTargetAddrEntry.name + (9,) + tblIdx, 'destroy'),
**dict(snmpEngine=snmpEngine)
)
- snmpEngine.msgAndPduDsp.mibInstrumController.writeVars(
+ snmpEngine.msgAndPduDsp.mibInstrumController.writeMibObjects(
(snmpTargetAddrEntry.name + (1,) + tblIdx, addrName),
(snmpTargetAddrEntry.name + (2,) + tblIdx, transportDomain),
(snmpTargetAddrEntry.name + (3,) + tblIdx, transportAddress),
@@ -334,7 +352,7 @@ def addTargetAddr(snmpEngine, addrName, transportDomain, transportAddress,
def delTargetAddr(snmpEngine, addrName):
(snmpTargetAddrEntry, snmpSourceAddrEntry,
tblIdx) = __cookTargetAddrInfo(snmpEngine, addrName)
- snmpEngine.msgAndPduDsp.mibInstrumController.writeVars(
+ snmpEngine.msgAndPduDsp.mibInstrumController.writeMibObjects(
(snmpTargetAddrEntry.name + (9,) + tblIdx, 'destroy'),
**dict(snmpEngine=snmpEngine)
)
@@ -408,10 +426,11 @@ def __cookVacmContextInfo(snmpEngine, contextName):
def addContext(snmpEngine, contextName):
vacmContextEntry, tblIdx = __cookVacmContextInfo(snmpEngine, contextName)
- snmpEngine.msgAndPduDsp.mibInstrumController.writeVars(
- (vacmContextEntry.name + (2,) + tblIdx, 'destroy')
+ snmpEngine.msgAndPduDsp.mibInstrumController.writeMibObjects(
+ (vacmContextEntry.name + (2,) + tblIdx, 'destroy'),
+ **dict(snmpEngine=snmpEngine)
)
- snmpEngine.msgAndPduDsp.mibInstrumController.writeVars(
+ snmpEngine.msgAndPduDsp.mibInstrumController.writeMibObjects(
(vacmContextEntry.name + (1,) + tblIdx, contextName),
(vacmContextEntry.name + (2,) + tblIdx, 'createAndGo'),
**dict(snmpEngine=snmpEngine)
@@ -421,9 +440,9 @@ def addContext(snmpEngine, contextName):
def delContext(snmpEngine, contextName):
vacmContextEntry, tblIdx = __cookVacmContextInfo(snmpEngine, contextName)
- snmpEngine.msgAndPduDsp.mibInstrumController.writeVars(
+ snmpEngine.msgAndPduDsp.mibInstrumController.writeMibObjects(
(vacmContextEntry.name + (2,) + tblIdx, 'destroy'),
- ** dict(snmpEngine=snmpEngine)
+ **dict(snmpEngine=snmpEngine)
)
@@ -440,11 +459,11 @@ def __cookVacmGroupInfo(snmpEngine, securityModel, securityName):
def addVacmGroup(snmpEngine, groupName, securityModel, securityName):
(vacmSecurityToGroupEntry,
tblIdx) = __cookVacmGroupInfo(snmpEngine, securityModel, securityName)
- snmpEngine.msgAndPduDsp.mibInstrumController.writeVars(
+ snmpEngine.msgAndPduDsp.mibInstrumController.writeMibObjects(
(vacmSecurityToGroupEntry.name + (5,) + tblIdx, 'destroy'),
**dict(snmpEngine=snmpEngine)
)
- snmpEngine.msgAndPduDsp.mibInstrumController.writeVars(
+ snmpEngine.msgAndPduDsp.mibInstrumController.writeMibObjects(
(vacmSecurityToGroupEntry.name + (1,) + tblIdx, securityModel),
(vacmSecurityToGroupEntry.name + (2,) + tblIdx, securityName),
(vacmSecurityToGroupEntry.name + (3,) + tblIdx, groupName),
@@ -457,7 +476,7 @@ def delVacmGroup(snmpEngine, securityModel, securityName):
vacmSecurityToGroupEntry, tblIdx = __cookVacmGroupInfo(
snmpEngine, securityModel, securityName
)
- snmpEngine.msgAndPduDsp.mibInstrumController.writeVars(
+ snmpEngine.msgAndPduDsp.mibInstrumController.writeMibObjects(
(vacmSecurityToGroupEntry.name + (5,) + tblIdx, 'destroy'),
**dict(snmpEngine=snmpEngine)
)
@@ -481,11 +500,11 @@ def addVacmAccess(snmpEngine, groupName, contextName, securityModel,
addContext(snmpEngine, contextName)
- snmpEngine.msgAndPduDsp.mibInstrumController.writeVars(
+ snmpEngine.msgAndPduDsp.mibInstrumController.writeMibObjects(
(vacmAccessEntry.name + (9,) + tblIdx, 'destroy'),
**dict(snmpEngine=snmpEngine)
)
- snmpEngine.msgAndPduDsp.mibInstrumController.writeVars(
+ snmpEngine.msgAndPduDsp.mibInstrumController.writeMibObjects(
(vacmAccessEntry.name + (1,) + tblIdx, contextName),
(vacmAccessEntry.name + (2,) + tblIdx, securityModel),
(vacmAccessEntry.name + (3,) + tblIdx, securityLevel),
@@ -506,7 +525,7 @@ def delVacmAccess(snmpEngine, groupName, contextName, securityModel,
delContext(snmpEngine, contextName)
- snmpEngine.msgAndPduDsp.mibInstrumController.writeVars(
+ snmpEngine.msgAndPduDsp.mibInstrumController.writeMibObjects(
(vacmAccessEntry.name + (9,) + tblIdx, 'destroy'),
**dict(snmpEngine=snmpEngine)
)
@@ -525,11 +544,11 @@ def __cookVacmViewInfo(snmpEngine, viewName, subTree):
def addVacmView(snmpEngine, viewName, viewType, subTree, mask):
vacmViewTreeFamilyEntry, tblIdx = __cookVacmViewInfo(snmpEngine, viewName,
subTree)
- snmpEngine.msgAndPduDsp.mibInstrumController.writeVars(
+ snmpEngine.msgAndPduDsp.mibInstrumController.writeMibObjects(
(vacmViewTreeFamilyEntry.name + (6,) + tblIdx, 'destroy'),
**dict(snmpEngine=snmpEngine)
)
- snmpEngine.msgAndPduDsp.mibInstrumController.writeVars(
+ snmpEngine.msgAndPduDsp.mibInstrumController.writeMibObjects(
(vacmViewTreeFamilyEntry.name + (1,) + tblIdx, viewName),
(vacmViewTreeFamilyEntry.name + (2,) + tblIdx, subTree),
(vacmViewTreeFamilyEntry.name + (3,) + tblIdx, mask),
@@ -542,7 +561,7 @@ def addVacmView(snmpEngine, viewName, viewType, subTree, mask):
def delVacmView(snmpEngine, viewName, subTree):
vacmViewTreeFamilyEntry, tblIdx = __cookVacmViewInfo(snmpEngine, viewName,
subTree)
- snmpEngine.msgAndPduDsp.mibInstrumController.writeVars(
+ snmpEngine.msgAndPduDsp.mibInstrumController.writeMibObjects(
(vacmViewTreeFamilyEntry.name + (6,) + tblIdx, 'destroy'),
**dict(snmpEngine=snmpEngine)
)
@@ -634,22 +653,22 @@ def addNotificationTarget(snmpEngine, notificationName, paramsName,
tblIdx3) = __cookNotificationTargetInfo(snmpEngine, notificationName,
paramsName, filterSubtree)
- snmpEngine.msgAndPduDsp.mibInstrumController.writeVars(
+ snmpEngine.msgAndPduDsp.mibInstrumController.writeMibObjects(
(snmpNotifyEntry.name + (5,) + tblIdx1, 'destroy'),
**dict(snmpEngine=snmpEngine)
)
- snmpEngine.msgAndPduDsp.mibInstrumController.writeVars(
+ snmpEngine.msgAndPduDsp.mibInstrumController.writeMibObjects(
(snmpNotifyEntry.name + (2,) + tblIdx1, transportTag),
(snmpNotifyEntry.name + (3,) + tblIdx1, notifyType),
(snmpNotifyEntry.name + (5,) + tblIdx1, 'createAndGo'),
**dict(snmpEngine=snmpEngine)
)
- snmpEngine.msgAndPduDsp.mibInstrumController.writeVars(
+ snmpEngine.msgAndPduDsp.mibInstrumController.writeMibObjects(
(snmpNotifyFilterProfileEntry.name + (3,) + tblIdx2, 'destroy'),
**dict(snmpEngine=snmpEngine)
)
- snmpEngine.msgAndPduDsp.mibInstrumController.writeVars(
+ snmpEngine.msgAndPduDsp.mibInstrumController.writeMibObjects(
(snmpNotifyFilterProfileEntry.name + (1,) + tblIdx2, profileName),
(snmpNotifyFilterProfileEntry.name + (3,) + tblIdx2, 'createAndGo'),
**dict(snmpEngine=snmpEngine)
@@ -658,11 +677,11 @@ def addNotificationTarget(snmpEngine, notificationName, paramsName,
if not snmpNotifyFilterEntry:
return
- snmpEngine.msgAndPduDsp.mibInstrumController.writeVars(
+ snmpEngine.msgAndPduDsp.mibInstrumController.writeMibObjects(
(snmpNotifyFilterEntry.name + (5,) + tblIdx3, 'destroy'),
**dict(snmpEngine=snmpEngine)
)
- snmpEngine.msgAndPduDsp.mibInstrumController.writeVars(
+ snmpEngine.msgAndPduDsp.mibInstrumController.writeMibObjects(
(snmpNotifyFilterEntry.name + (1,) + tblIdx3, filterSubtree),
(snmpNotifyFilterEntry.name + (2,) + tblIdx3, filterMask),
(snmpNotifyFilterEntry.name + (3,) + tblIdx3, filterType),
@@ -678,12 +697,12 @@ def delNotificationTarget(snmpEngine, notificationName, paramsName,
tblIdx3) = __cookNotificationTargetInfo(snmpEngine, notificationName,
paramsName, filterSubtree)
- snmpEngine.msgAndPduDsp.mibInstrumController.writeVars(
+ snmpEngine.msgAndPduDsp.mibInstrumController.writeMibObjects(
(snmpNotifyEntry.name + (5,) + tblIdx1, 'destroy'),
**dict(snmpEngine=snmpEngine)
)
- snmpEngine.msgAndPduDsp.mibInstrumController.writeVars(
+ snmpEngine.msgAndPduDsp.mibInstrumController.writeMibObjects(
(snmpNotifyFilterProfileEntry.name + (3,) + tblIdx2, 'destroy'),
**dict(snmpEngine=snmpEngine)
)
@@ -691,7 +710,7 @@ def delNotificationTarget(snmpEngine, notificationName, paramsName,
if not snmpNotifyFilterEntry:
return
- snmpEngine.msgAndPduDsp.mibInstrumController.writeVars(
+ snmpEngine.msgAndPduDsp.mibInstrumController.writeMibObjects(
(snmpNotifyFilterEntry.name + (5,) + tblIdx3, 'destroy'),
**dict(snmpEngine=snmpEngine)
)
diff --git a/pysnmp/entity/rfc3413/cmdrsp.py b/pysnmp/entity/rfc3413/cmdrsp.py
index e2d47f38..0aa3b1e1 100644
--- a/pysnmp/entity/rfc3413/cmdrsp.py
+++ b/pysnmp/entity/rfc3413/cmdrsp.py
@@ -9,6 +9,7 @@ from pysnmp.proto import rfc1902, rfc1905, rfc3411, errind, error
from pysnmp.proto.api import v2c # backend is always SMIv2 compliant
from pysnmp.proto.proxy import rfc2576
import pysnmp.smi.error
+from pysnmp.smi import exval
from pysnmp import debug
@@ -158,22 +159,33 @@ class CommandResponderBase(object):
self.initiateMgmtOperation(snmpEngine, stateReference, contextName, PDU)
+ @staticmethod
+ def _storeAccessContext(snmpEngine):
+ """Copy received message metadata while it lasts"""
+ execCtx = snmpEngine.observer.getExecutionContext('rfc3412.receiveMessage:request')
+ return {
+ 'securityModel': execCtx['securityModel'],
+ 'securityName': execCtx['securityName'],
+ 'securityLevel': execCtx['securityLevel'],
+ 'contextName': execCtx['contextName'],
+ 'pduType': execCtx['pdu'].getTagSet()
+ }
+
@classmethod
def verifyAccess(cls, viewType, varBind, **context):
name, val = varBind
snmpEngine = context['snmpEngine']
- execCtx = snmpEngine.observer.getExecutionContext('rfc3412.receiveMessage:request')
(securityModel,
securityName,
securityLevel,
contextName,
- pduType) = (execCtx['securityModel'],
- execCtx['securityName'],
- execCtx['securityLevel'],
- execCtx['contextName'],
- execCtx['pdu'].getTagSet())
+ pduType) = (context['securityModel'],
+ context['securityName'],
+ context['securityLevel'],
+ context['contextName'],
+ context['pduType'])
try:
snmpEngine.accessControlModel[cls.acmID].isAccessAllowed(
@@ -221,44 +233,25 @@ class CommandResponderBase(object):
def _getMgmtFun(self, contextName):
return lambda *args, **kwargs: None
- def _checkSmiErrors(self, varBinds):
+ def _mapSmiErrors(self, varBinds, **context):
errorIndication = None
errorStatus = errorIndex = 0
- exception = None
-
- for idx, varBind in enumerate(varBinds):
- name, value = varBind
- if isinstance(value, tuple): # expect exception tuple
- debug.logger & debug.flagApp and debug.logger(
- '_checkSmiErrors: exception reported for OID %s exception %s' % (name, value))
-
- if not exception:
- exception = value
-
- # reset exception object
- varBinds[idx] = name, v2c.null
+ errors = context.get('errors')
+ if not errors:
+ return errorIndication, errorStatus, errorIndex
- try:
- # TODO: perhaps chain exceptions
- if exception:
- debug.logger & debug.flagApp and debug.logger(
- '_checkSmiErrors: re-raising exception %s' % (exception,))
- raise exception[1].with_traceback(exception[2])
-
- # SNMPv2 SMI exceptions
- except pysnmp.smi.error.GenError:
- errorIndication = sys.exc_info()[1]
- debug.logger & debug.flagApp and debug.logger(
- '_checkSmiErrors: errorIndication %s' % (errorIndication,))
+ # Take the latest exception
+ err = errors[-1]
- except pysnmp.smi.error.SmiError:
- exc_type, exc_obj, trb = sys.exc_info()
+ if isinstance(err, pysnmp.smi.error.GenError):
+ errorIndication = str(err)
- errorStatus = self.SMI_ERROR_MAP.get(exc_type, 'genErr')
+ elif isinstance(err, pysnmp.smi.error.SmiError):
+ errorStatus = self.SMI_ERROR_MAP.get(err.__class__, 'genErr')
try:
- errorIndex = exc_obj['idx'] + 1
+ errorIndex = err['idx'] + 1
except IndexError:
errorIndex = len(varBinds) and 1 or 0
@@ -267,13 +260,8 @@ class CommandResponderBase(object):
def completeMgmtOperation(self, varBinds, **context):
- try:
- (errorIndication,
- errorStatus, errorIndex) = self._checkSmiErrors(varBinds)
-
- except pysnmp.error.PySnmpError:
- self.releaseStateInformation(context['stateReference'])
- return
+ (errorIndication,
+ errorStatus, errorIndex) = self._mapSmiErrors(varBinds, **context)
stateReference = context['stateReference']
@@ -304,6 +292,8 @@ class CommandResponderBase(object):
cbFun=self.completeMgmtOperation,
cbCtx=self.cbCtx)
+ context.update(self._storeAccessContext(snmpEngine))
+
mgmtFun(*varBinds, **context)
@@ -312,7 +302,7 @@ class GetCommandResponder(CommandResponderBase):
# rfc1905: 4.2.1
def _getMgmtFun(self, contextName):
- return self.snmpContext.getMibInstrum(contextName).readVars
+ return self.snmpContext.getMibInstrum(contextName).readMibObjects
class NextCommandResponder(CommandResponderBase):
@@ -320,41 +310,133 @@ class NextCommandResponder(CommandResponderBase):
# rfc1905: 4.2.2
def _getMgmtFun(self, contextName):
- return self.snmpContext.getMibInstrum(contextName).readNextVars
+ return self.snmpContext.getMibInstrum(contextName).readNextMibObjects
+
+ def _getManagedObjectsInstances(self, varBinds, **context):
+ """Iterate over Managed Objects fulfilling SNMP query.
+
+ Parameters
+ ----------
+ varBinds
+ context
+
+ Returns
+ -------
+ :py:class:`list` - List of Managed Objects Instances to respond with or
+ `None` to indicate that not all objects have been gathered
+ so far.
+ """
+ rspVarBinds = context['rspVarBinds']
+ varBindsMap = context['varBindsMap']
+
+ rtrVarBinds = []
+
+ for idx, varBind in enumerate(varBinds):
+ name, val = varBind
+ if (exval.noSuchObject.isSameTypeWith(val) or
+ exval.noSuchInstance.isSameTypeWith(val)):
+ varBindsMap[len(rtrVarBinds)] = varBindsMap.pop(idx, idx)
+ rtrVarBinds.append(varBind)
+
+ else:
+ rspVarBinds[varBindsMap.pop(idx, idx)] = varBind
+
+ if rtrVarBinds:
+ snmpEngine = context['snmpEngine']
+
+ # Need to unwind stack, can't recurse any more
+ def callLater(*args):
+ snmpEngine.transportDispatcher.unregisterTimerCbFun(callLater)
+ mgmtFun = context['mgmtFun']
+ mgmtFun(*varBinds, **context)
+
+ snmpEngine.transportDispatcher.registerTimerCbFun(callLater, 0.01)
+
+ else:
+ return rspVarBinds
+
+ def completeMgmtOperation(self, varBinds, **context):
+ rspVarBinds = self._getManagedObjectsInstances(varBinds, **context)
+ if rspVarBinds:
+ CommandResponderBase.completeMgmtOperation(self, rspVarBinds, **context)
+
+ def initiateMgmtOperation(self, snmpEngine, stateReference, contextName, PDU):
+ varBinds = v2c.apiPDU.getVarBinds(PDU)
+
+ mgmtFun = self._getMgmtFun(contextName)
+
+ context = dict(snmpEngine=snmpEngine,
+ stateReference=stateReference,
+ acFun=self.verifyAccess,
+ cbFun=self.completeMgmtOperation,
+ cbCtx=self.cbCtx,
+ rspVarBinds=varBinds[:],
+ varBindsMap={},
+ mgmtFun=mgmtFun)
+
+ context.update(self._storeAccessContext(snmpEngine))
+ mgmtFun(*varBinds, **context)
-class BulkCommandResponder(CommandResponderBase):
+
+class BulkCommandResponder(NextCommandResponder):
SUPPORTED_PDU_TYPES = (rfc1905.GetBulkRequestPDU.tagSet,)
maxVarBinds = 64
- def _getMgmtFun(self, contextName):
- return self.snmpContext.getMibInstrum(contextName).readNextVars
-
def _completeNonRepeaters(self, varBinds, **context):
- context['rspVarBinds'][:] = varBinds
+ mgmtFun = context['mgmtFun']
- if context['counters']['M'] and context['counters']['R']:
- context['cbFun'] = self.completeMgmtOperation
+ if not varBinds:
+ # No non-repeaters requested, proceed with repeaters
+ mgmtFun(*context['reqVarBinds'],
+ **dict(context, cbFun=self.completeMgmtOperation,
+ varBinds=context['reqVarBinds'][:]))
+ return
- mgmtFun = self._getMgmtFun(context['contextName'])
+ rspVarBinds = self._getManagedObjectsInstances(varBinds, **context)
+ if rspVarBinds:
+ context['allVarBinds'].extend(rspVarBinds)
- mgmtFun(*context['reqVarBinds'], **context)
+ if context['counters']['M'] and context['counters']['R']:
- else:
- CommandResponderBase.completeMgmtOperation(self, context['rspVarBinds'], **context)
+ rspVarBinds = self._getManagedObjectsInstances(varBinds, **context)
+ if rspVarBinds:
+ # Done with non-repeaters, proceed with repeaters
+ mgmtFun(*context['reqVarBinds'],
+ **dict(context,
+ cbFun=self.completeMgmtOperation,
+ varBindsMap={},
+ rspVarBinds=context['reqVarBinds'][:]))
+ return
+
+ else:
+ CommandResponderBase.completeMgmtOperation(self, context['allVarBinds'], **context)
def completeMgmtOperation(self, varBinds, **context):
- context['rspVarBinds'].extend(varBinds)
- context['counters']['M'] -= 1
+ rspVarBinds = self._getManagedObjectsInstances(varBinds, **context)
+ if rspVarBinds:
+ context['counters']['M'] -= 1
- if context['counters']['M'] and context['counters']['R']:
- mgmtFun = self._getMgmtFun(context['contextName'])
+ context['allVarBinds'].extend(rspVarBinds)
- context['cbFun'] = self.completeMgmtOperation
- mgmtFun(*varBinds[-context['counters']['R']:], **context)
+ eom = all(exval.endOfMibView.isSameTypeWith(value) for name, value in rspVarBinds)
- else:
- CommandResponderBase.completeMgmtOperation(self, context['rspVarBinds'], **context)
+ if not eom and context['counters']['M'] and context['counters']['R']:
+ snmpEngine = context['snmpEngine']
+
+ # Need to unwind stack, can't recurse any more
+ def callLater(*args):
+ snmpEngine.transportDispatcher.unregisterTimerCbFun(callLater)
+ mgmtFun = context['mgmtFun']
+ reqVarBinds = varBinds[-context['counters']['R']:]
+ mgmtFun(*reqVarBinds,
+ **dict(context, cbFun=self.completeMgmtOperation,
+ varBindsMap={}, rspVarBinds=reqVarBinds[:]))
+
+ snmpEngine.transportDispatcher.registerTimerCbFun(callLater, 0.01)
+
+ else:
+ CommandResponderBase.completeMgmtOperation(self, context['allVarBinds'], **context)
# rfc1905: 4.2.3
def initiateMgmtOperation(self, snmpEngine, stateReference, contextName, PDU):
@@ -388,7 +470,12 @@ class BulkCommandResponder(CommandResponderBase):
cbCtx=self.cbCtx,
reqVarBinds=varBinds[N:],
counters={'M': M, 'R': R},
- rspVarBinds=[])
+ rspVarBinds=varBinds[N:],
+ allVarBinds=[],
+ varBindsMap={},
+ mgmtFun=mgmtFun)
+
+ context.update(self._storeAccessContext(snmpEngine))
mgmtFun(*varBinds[:N], **context)
@@ -404,4 +491,4 @@ class SetCommandResponder(CommandResponderBase):
# rfc1905: 4.2.5.1-13
def _getMgmtFun(self, contextName):
- return self.snmpContext.getMibInstrum(contextName).writeVars
+ return self.snmpContext.getMibInstrum(contextName).writeMibObjects
diff --git a/pysnmp/hlapi/v1arch/asyncore/cmdgen.py b/pysnmp/hlapi/v1arch/asyncore/cmdgen.py
index ddce3b7e..e178efe3 100644
--- a/pysnmp/hlapi/v1arch/asyncore/cmdgen.py
+++ b/pysnmp/hlapi/v1arch/asyncore/cmdgen.py
@@ -43,7 +43,7 @@ def getCmd(snmpDispatcher, authData, transportTarget, *varBinds, **options):
Other Parameters
----------------
- \*\*options :
+ \*\*options:
Request options:
* `lookupMib` - load MIB and resolve response MIB variables at
diff --git a/pysnmp/proto/rfc3412.py b/pysnmp/proto/rfc3412.py
index 386d1aaa..629dfcce 100644
--- a/pysnmp/proto/rfc3412.py
+++ b/pysnmp/proto/rfc3412.py
@@ -426,7 +426,7 @@ class MsgAndPduDispatcher(object):
transportDomain, transportAddress
)
- # 4.2.2.1.3
+ # 4.2.2.1.3 (asynchronous function)
processPdu(snmpEngine, messageProcessingModel,
securityModel, securityName, securityLevel,
contextEngineId, contextName, pduVersion,
@@ -440,7 +440,7 @@ class MsgAndPduDispatcher(object):
if stateReference is not None:
del self.__transportInfo[stateReference]
- debug.logger & debug.flagDsp and debug.logger('receiveMessage: processPdu succeeded')
+ debug.logger & debug.flagDsp and debug.logger('receiveMessage: processPdu initiated')
return restOfWholeMsg
else:
# 4.2.2.2 (response)
diff --git a/pysnmp/proto/secmod/rfc3414/service.py b/pysnmp/proto/secmod/rfc3414/service.py
index f3324961..ad14dd63 100644
--- a/pysnmp/proto/secmod/rfc3414/service.py
+++ b/pysnmp/proto/secmod/rfc3414/service.py
@@ -168,9 +168,9 @@ class SnmpUSMSecurityModel(AbstractSecurityModel):
tblIdx2 = usmUserEntry.getInstIdFromIndices(securityEngineID, userName)
- # New row
- mibInstrumController.writeVars(
- (usmUserEntry.name + (13,) + tblIdx2, 4), **dict(snmpEngine=snmpEngine)
+ # New inactive row
+ mibInstrumController.writeMibObjects(
+ (usmUserEntry.name + (13,) + tblIdx2, 5), **dict(snmpEngine=snmpEngine)
)
# Set user&securityNames
@@ -184,6 +184,11 @@ class SnmpUSMSecurityModel(AbstractSecurityModel):
usmUserEntry.getNode(usmUserEntry.name + (5,) + tblIdx2).syntax = usmUserAuthProtocol.syntax
usmUserEntry.getNode(usmUserEntry.name + (8,) + tblIdx2).syntax = usmUserPrivProtocol.syntax
+ # Activate row
+ mibInstrumController.writeMibObjects(
+ (usmUserEntry.name + (13,) + tblIdx2, 1), **dict(snmpEngine=snmpEngine)
+ )
+
# Localize and set keys
pysnmpUsmKeyEntry, = mibInstrumController.mibBuilder.importSymbols(
'PYSNMP-USM-MIB', 'pysnmpUsmKeyEntry'
diff --git a/pysnmp/smi/error.py b/pysnmp/smi/error.py
index 8f1c509d..1b1901f4 100644
--- a/pysnmp/smi/error.py
+++ b/pysnmp/smi/error.py
@@ -101,6 +101,7 @@ class InconsistentNameError(MibOperationError):
# Aligned with SNMPv2 Var-Bind exceptions
+
class NoSuchObjectError(MibOperationError):
pass
@@ -114,6 +115,7 @@ class EndOfMibViewError(MibOperationError):
# Row management
+
class TableRowManagement(MibOperationError):
pass
@@ -124,3 +126,13 @@ class RowCreationWanted(TableRowManagement):
class RowDestructionWanted(TableRowManagement):
pass
+
+
+class RowConsistencyWanted(TableRowManagement):
+ pass
+
+
+# MIB instrumentation support for GETNEXT
+
+class SuggestedNextObject(MibOperationError):
+ pass
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)
diff --git a/pysnmp/smi/mibs/INET-ADDRESS-MIB.py b/pysnmp/smi/mibs/INET-ADDRESS-MIB.py
index 5e40017c..f8ea8acf 100644
--- a/pysnmp/smi/mibs/INET-ADDRESS-MIB.py
+++ b/pysnmp/smi/mibs/INET-ADDRESS-MIB.py
@@ -80,7 +80,7 @@ class InetAddress(TextualConvention, OctetString):
for parentIndex in reversed(parentIndices):
if isinstance(parentIndex, InetAddressType):
try:
- return parentRow.setFromName(cls.typeMap[int(parentIndex)], value, impliedFlag, parentIndices)
+ return parentRow.oidToValue(cls.typeMap[int(parentIndex)], value, impliedFlag, parentIndices)
except KeyError:
pass
@@ -90,7 +90,7 @@ class InetAddress(TextualConvention, OctetString):
for parentIndex in reversed(parentIndices):
if isinstance(parentIndex, InetAddressType):
try:
- return parentRow.getAsName(self.typeMap[int(parentIndex)].clone(self.asOctets().decode('ascii')), impliedFlag, parentIndices)
+ return parentRow.valueToOid(self.typeMap[int(parentIndex)].clone(self.asOctets().decode('ascii')), impliedFlag, parentIndices)
except KeyError:
pass
diff --git a/pysnmp/smi/mibs/PYSNMP-SOURCE-MIB.py b/pysnmp/smi/mibs/PYSNMP-SOURCE-MIB.py
index 216bcca1..525891a4 100644
--- a/pysnmp/smi/mibs/PYSNMP-SOURCE-MIB.py
+++ b/pysnmp/smi/mibs/PYSNMP-SOURCE-MIB.py
@@ -30,7 +30,7 @@ snmpSourceAddrTable = MibTable((1, 3, 6, 1, 4, 1, 20408, 3, 1, 8, 1, 1), )
if mibBuilder.loadTexts: snmpSourceAddrTable.setStatus('current')
if mibBuilder.loadTexts: snmpSourceAddrTable.setDescription('A table of transport addresses to be used as a source in the generation of SNMP messages. This table contains additional objects for the SNMP-TRANSPORT-ADDRESS::snmpSourceAddressTable.')
snmpSourceAddrEntry = MibTableRow((1, 3, 6, 1, 4, 1, 20408, 3, 1, 8, 1, 1, 1), )
-snmpTargetAddrEntry.registerAugmentions(("PYSNMP-SOURCE-MIB", "snmpSourceAddrEntry"))
+snmpTargetAddrEntry.registerAugmentation(("PYSNMP-SOURCE-MIB", "snmpSourceAddrEntry"))
snmpSourceAddrEntry.setIndexNames(*snmpTargetAddrEntry.getIndexNames())
if mibBuilder.loadTexts: snmpSourceAddrEntry.setStatus('current')
if mibBuilder.loadTexts: snmpSourceAddrEntry.setDescription('A transport address to be used as a source in the generation of SNMP operations. An entry containing additional management information applicable to a particular target.')
diff --git a/pysnmp/smi/mibs/PYSNMP-USM-MIB.py b/pysnmp/smi/mibs/PYSNMP-USM-MIB.py
index 5dac372f..321bafe5 100644
--- a/pysnmp/smi/mibs/PYSNMP-USM-MIB.py
+++ b/pysnmp/smi/mibs/PYSNMP-USM-MIB.py
@@ -57,7 +57,7 @@ pysnmpUsmKeyTable = MibTable((1, 3, 6, 1, 4, 1, 20408, 3, 1, 1, 1, 3), )
if mibBuilder.loadTexts: pysnmpUsmKeyTable.setStatus('current')
if mibBuilder.loadTexts: pysnmpUsmKeyTable.setDescription("The table of USM users localized keys configured in the SNMP engine's Local Configuration Datastore (LCD).")
pysnmpUsmKeyEntry = MibTableRow((1, 3, 6, 1, 4, 1, 20408, 3, 1, 1, 1, 3, 1), )
-usmUserEntry.registerAugmentions(("PYSNMP-USM-MIB", "pysnmpUsmKeyEntry"))
+usmUserEntry.registerAugmentation(("PYSNMP-USM-MIB", "pysnmpUsmKeyEntry"))
pysnmpUsmKeyEntry.setIndexNames(*usmUserEntry.getIndexNames())
if mibBuilder.loadTexts: pysnmpUsmKeyEntry.setStatus('current')
if mibBuilder.loadTexts: pysnmpUsmKeyEntry.setDescription('Information about a particular USM user credentials.')
diff --git a/pysnmp/smi/mibs/SNMP-COMMUNITY-MIB.py b/pysnmp/smi/mibs/SNMP-COMMUNITY-MIB.py
index 4e9d0aea..4ed234f7 100644
--- a/pysnmp/smi/mibs/SNMP-COMMUNITY-MIB.py
+++ b/pysnmp/smi/mibs/SNMP-COMMUNITY-MIB.py
@@ -60,7 +60,7 @@ snmpTargetAddrExtTable = MibTable((1, 3, 6, 1, 6, 3, 18, 1, 2), )
if mibBuilder.loadTexts: snmpTargetAddrExtTable.setStatus('current')
if mibBuilder.loadTexts: snmpTargetAddrExtTable.setDescription('The table of mask and maximum message size (mms) values associated with the snmpTargetAddrTable. The snmpTargetAddrExtTable augments the snmpTargetAddrTable with a transport address mask value and a maximum message size value. The transport address mask allows entries in the snmpTargetAddrTable to define a set of addresses instead of just a single address. The maximum message size value allows the maximum message size of another SNMP entity to be configured for use in SNMPv1 (and SNMPv2c) transactions, where the message format does not specify a maximum message size.')
snmpTargetAddrExtEntry = MibTableRow((1, 3, 6, 1, 6, 3, 18, 1, 2, 1), )
-snmpTargetAddrEntry.registerAugmentions(("SNMP-COMMUNITY-MIB", "snmpTargetAddrExtEntry"))
+snmpTargetAddrEntry.registerAugmentation(("SNMP-COMMUNITY-MIB", "snmpTargetAddrExtEntry"))
snmpTargetAddrExtEntry.setIndexNames(*snmpTargetAddrEntry.getIndexNames())
if mibBuilder.loadTexts: snmpTargetAddrExtEntry.setStatus('current')
if mibBuilder.loadTexts: snmpTargetAddrExtEntry.setDescription('Information about a particular mask and mms value.')
diff --git a/pysnmp/smi/mibs/SNMPv2-SMI.py b/pysnmp/smi/mibs/SNMPv2-SMI.py
index b5d69cf0..63a601a7 100644
--- a/pysnmp/smi/mibs/SNMPv2-SMI.py
+++ b/pysnmp/smi/mibs/SNMPv2-SMI.py
@@ -35,15 +35,25 @@ Unsigned32 = rfc1902.Unsigned32
TimeTicks = rfc1902.TimeTicks
Opaque = rfc1902.Opaque
Counter64 = rfc1902.Counter64
+Null = rfc1902.Null
class ExtUTCTime(OctetString):
- subtypeSpec = OctetString.subtypeSpec + ConstraintsUnion(ValueSizeConstraint(11, 11), ValueSizeConstraint(13, 13))
+ subtypeSpec = (OctetString.subtypeSpec +
+ ConstraintsUnion(ValueSizeConstraint(11, 11),
+ ValueSizeConstraint(13, 13)))
# MIB tree foundation class
class MibNode(object):
+ """MIB object base.
+
+ Logically binds object identifier, which addresses MIB object in MIB tree,
+ with MIB symbol which identifies MIB object within its MIB module.
+
+ Serves as a foundation for more specialized MIB objects.
+ """
label = ''
def __init__(self, name):
@@ -271,9 +281,13 @@ class ObjectType(MibNode):
return self.syntax >= other
def __repr__(self):
- return '%s(%r, %r)' % (
- self.__class__.__name__, self.name, self.syntax
- )
+ representation = '%s(%s' % (self.__class__.__name__, self.name)
+
+ if self.syntax is not None:
+ representation += ', %r' % self.syntax
+
+ representation += ')'
+ return representation
def getSyntax(self):
return self.syntax
@@ -333,10 +347,48 @@ OBJECT-TYPE
self.getReference())
-class MibTree(ObjectType):
- branchVersionId = 0 # cnanges on tree structure change
+class ManagedMibObject(ObjectType):
+ """Managed MIB object.
+
+ Implement management instrumentation access protocol which allows for
+ MIB instantiation and operations on Managed Objects Instances.
+
+ Management instrumentation protocol is typically used by SNMP Agent
+ serving Managed Objects to SNMP Managers.
+
+ The :class:`AbstractManagedMibObject` class serves as a basis
+ for a handful of other classes representing various kinds of
+ MIB objects. In the context of management instrumentation these
+ objects are organized into a tree of the following layout:
+
+
+ MibTree
+ |
+ +----MibScalar
+ | |
+ | +-----MibScalarInstance
+ |
+ +----MibTable
+ |
+ +----MibTableRow
+ |
+ +-------MibTableColumn
+ |
+ +------MibScalarInstance(s)
+
+ Management instrumentation queries always come to the top of the
+ tree propagating downwards.
+
+ The basic management instrumentation operations are *read*, *readnext*
+ and *write* of Managed Objects Instances. The latter covers creation
+ and removal of the columnar Managed Objects Instances.
+ """
+ branchVersionId = 0 # changes on tree structure change
maxAccess = 'not-accessible'
+ ST_CREATE = 'create'
+ ST_DESTROY = 'destroy'
+
def __init__(self, name, syntax=None):
ObjectType.__init__(self, name, syntax)
self._vars = OidOrderedDict()
@@ -421,155 +473,609 @@ class MibTree(ObjectType):
# Read operation
def readTest(self, varBind, **context):
+ """Test the ability to read Managed Object Instance.
+
+ Implements the first of the two phases of the SNMP GET command
+ processing (:RFC:`1905#section-4.2.1`).
+
+ The goal of the first phase is to make sure that requested Managed
+ Object Instance could potentially be read. When multiple Managed
+ Objects Instances are read at once (likely coming all in one SNMP PDU),
+ each of them has to run through the first (*test*) phase successfully
+ for the system to transition to the second (*get*) phase.
+
+ The role of this object in the MIB tree is non-terminal. It does not
+ access the actual Managed Object Instance, but just traverses one level
+ down the MIB tree and hands off the query to the underlying objects.
+
+ Parameters
+ ----------
+ varBind: :py:class:`~pysnmp.smi.rfc1902.ObjectType` object representing
+ Managed Object Instance to read
+
+ Other Parameters
+ ----------------
+ \*\*context:
+
+ Query parameters:
+
+ * `cbFun` (callable) - user-supplied callable that is invoked to
+ pass read Managed Object Instance or an error.
+
+ Notes
+ -----
+ The callback functions (e.g. `cbFun`) have the same signature as this
+ method where `varBind` contains read Managed Object Instance value.
+
+ In case of an error, the `error` key in the `context` dict will contain
+ an exception object.
+ """
name, val = varBind
+ (debug.logger & debug.flagIns and
+ debug.logger('%s: readTest(%s, %r)' % (self, name, val)))
+
+ cbFun = context['cbFun']
+
if name == self.name:
- acFun = context.get('acFun')
- if acFun:
- if (self.maxAccess not in ('readonly', 'readwrite', 'readcreate') or
- acFun('read', (name, self.syntax), **context)):
- raise error.NoAccessError(name=name, idx=context.get('idx'))
- else:
- try:
- node = self.getBranch(name, **context)
+ cbFun((name, exval.noSuchInstance), **context)
+ return
- except (error.NoSuchInstanceError, error.NoSuchObjectError):
- return # missing object is not an error here
+ node = exc = None
- else:
- node.readTest(varBind, **context)
+ try:
+ node = self.getBranch(name, **context)
+
+ except error.NoSuchObjectError:
+ val = exval.noSuchObject
+
+ except error.NoSuchInstanceError:
+ val = exval.noSuchInstance
+
+ except error.SmiError:
+ exc = sys.exc_info()[1]
+
+ (debug.logger & debug.flagIns and
+ debug.logger('%s: exception %r' % (self, exc)))
+
+ if not node:
+ cbFun((name, val), **dict(context, error=exc))
+ return
+
+ node.readTest(varBind, **context)
def readGet(self, varBind, **context):
+ """Read Managed Object Instance.
+
+ Implements the second of the two phases of the SNMP GET command
+ processing (:RFC:`1905#section-4.2.1`).
+
+ The goal of the second phase is to actually read the requested Managed
+ Object Instance. When multiple Managed Objects Instances are read at
+ once (likely coming all in one SNMP PDU), each of them has to run through
+ the first (*test*) and second (*read) phases successfully for the whole
+ read operation to succeed.
+
+ The role of this object in the MIB tree is non-terminal. It does not
+ access the actual Managed Object Instance, but just traverses one level
+ down the MIB tree and hands off the query to the underlying objects.
+
+
+ Parameters
+ ----------
+ varBind: :py:class:`~pysnmp.smi.rfc1902.ObjectType` object representing
+ Managed Object Instance to read
+
+ Other Parameters
+ ----------------
+ \*\*context:
+
+ Query parameters:
+
+ * `cbFun` (callable) - user-supplied callable that is invoked to
+ pass read Managed Object Instance or an error.
+
+ Notes
+ -----
+ The callback functions (e.g. `cbFun`) has the same signature as
+ this method where `varBind` contains read Managed Object Instance value.
+
+ In case of an error, the `error` key in the `context` dict will contain
+ an exception object.
+ """
name, val = varBind
+ (debug.logger & debug.flagIns and
+ debug.logger('%s: readGet(%s, %r)' % (self, name, val)))
+
+ cbFun = context['cbFun']
+
+ if name == self.name:
+ cbFun((name, exval.noSuchInstance), **context)
+ return
+
+ node = exc = None
+
try:
node = self.getBranch(name, **context)
+ except error.NoSuchObjectError:
+ val = exval.noSuchObject
+
+ except error.NoSuchInstanceError:
+ val = exval.noSuchInstance
+
+ except error.SmiError:
+ exc = sys.exc_info()[1]
+
+ (debug.logger & debug.flagIns and
+ debug.logger('%s: exception %r' % (self, exc)))
+
+ if not node:
+ cbFun((name, val), **dict(context, error=exc))
+ return
+
+ node.readGet(varBind, **context)
+
+ def _getNextName(self, name):
+ try:
+ nextNode = self.getNextBranch(name)
+
except (error.NoSuchInstanceError, error.NoSuchObjectError):
- return name, exval.noSuchObject
+ return
else:
- return node.readGet(varBind, **context)
-
- # Read next operation is subtree-specific
+ return nextNode.name
depthFirst, breadthFirst = 0, 1
- def readTestNext(self, varBind, **context):
+ def _readNext(self, meth, varBind, **context):
name, val = varBind
- topOfTheMib = context.get('oName') is None
- if topOfTheMib:
- context['oName'] = name
+ cbFun = context['cbFun']
- nextName = name
- direction = self.depthFirst
+ try:
+ node = self.getBranch(name, **context)
- while True: # NOTE(etingof): linear search here
- if direction == self.depthFirst:
- direction = self.breadthFirst
- try:
- node = self.getBranch(nextName, **context)
+ except (error.NoSuchInstanceError, error.NoSuchObjectError):
- except (error.NoSuchInstanceError, error.NoSuchObjectError):
- continue
+ node = exc = None
- else:
- try:
- node = self.getNextBranch(nextName, **context)
+ try:
+ node = self.getNextBranch(name, **context)
- except (error.NoSuchInstanceError, error.NoSuchObjectError):
- if topOfTheMib:
- return
- raise
+ except error.NoSuchObjectError:
+ val = exval.noSuchObject
- direction = self.depthFirst
- nextName = node.name
+ except error.NoSuchInstanceError:
+ val = exval.noSuchInstance
- try:
- return node.readTestNext(varBind, **context)
+ except error.SmiError:
+ exc = sys.exc_info()[1]
+
+ (debug.logger & debug.flagIns and
+ debug.logger('%s: exception %r' % (self, exc)))
+
+ if not node:
+ nextName = context.get('nextName')
+ if nextName:
+ varBind = nextName, val
+
+ else:
+ varBind = name, exval.endOfMibView
+
+ cbFun(varBind, **dict(context, error=exc))
+ return
+
+ nextName = self._getNextName(node.name)
+ if nextName:
+ context['nextName'] = nextName
+
+ actionFun = getattr(node, meth)
+ actionFun(varBind, **context)
+
+ def readTestNext(self, varBind, **context):
+ """Test the ability to read the next Managed Object Instance.
+
+ Implements the first of the two phases of the SNMP GETNEXT command
+ processing (:RFC:`1905#section-4.2.2`).
+
+ The goal of the first phase is to make sure that requested Managed
+ Object Instance could potentially be read. When multiple Managed
+ Objects Instances are read at once (likely coming all in one SNMP PDU),
+ each of them has to run through the first (*testnext*) phase
+ successfully for the system to transition to the second (*getnext*)
+ phase.
- except (error.NoAccessError, error.NoSuchInstanceError, error.NoSuchObjectError):
- pass
+ The role of this object in the MIB tree is non-terminal. It does not
+ access the actual Managed Object Instance, but just traverses one level
+ down the MIB tree and hands off the query to the underlying objects.
+
+ Parameters
+ ----------
+ varBind: :py:class:`~pysnmp.smi.rfc1902.ObjectType` object representing
+ Managed Object Instance next to which to read
+
+ Other Parameters
+ ----------------
+ \*\*context:
+
+ Query parameters:
+
+ * `cbFun` (callable) - user-supplied callable that is invoked to
+ pass read Managed Object Instance (the *next* one in the MIB tree
+ relative to the one being requested) or an error.
+
+ * `acFun` (callable) - user-supplied callable that is invoked to
+ authorize access to the Managed Object Instance which is *next*
+ to the one being requested. 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 read Managed Object Instance
+ value.
+
+ In case of an error, the `error` key in the `context` dict will contain
+ an exception object.
+ """
+ (debug.logger & debug.flagIns and
+ debug.logger('%s: readTestNext(%s, %r)' % (self, name, val)))
+
+ self._readNext('readTestNext', varBind, **context)
def readGetNext(self, varBind, **context):
- name, val = varBind
+ """Read the next Managed Object Instance.
- topOfTheMib = context.get('oName') is None
- if topOfTheMib:
- context['oName'] = name
+ Implements the second of the two phases of the SNMP GETNEXT command
+ processing (:RFC:`1905#section-4.2.2`).
- nextName = name
- direction = self.depthFirst
+ The goal of the second phase is to actually read the Managed Object
+ Instance which is next in the MIB tree to the one being requested.
+ When multiple Managed Objects Instances are read at once (likely coming
+ all in one SNMP PDU), each of them has to run through the first
+ (*testnext*) and second (*getnext*) phases successfully for the whole
+ read operation to succeed.
- while True: # NOTE(etingof): linear search ahead!
- if direction == self.depthFirst:
- direction = self.breadthFirst
- try:
- node = self.getBranch(nextName, **context)
+ The role of this object in the MIB tree is non-terminal. It does not
+ access the actual Managed Object Instance, but just traverses one level
+ down the MIB tree and hands off the query to the underlying objects.
- except (error.NoSuchInstanceError, error.NoSuchObjectError):
- continue
+ Parameters
+ ----------
+ varBind: :py:class:`~pysnmp.smi.rfc1902.ObjectType` object representing
+ Managed Object Instance to read
- else:
- try:
- node = self.getNextBranch(nextName, **context)
+ Other Parameters
+ ----------------
+ \*\*context:
- except (error.NoSuchInstanceError, error.NoSuchObjectError):
- if topOfTheMib:
- return name, exval.endOfMib
- raise
+ Query parameters:
- direction = self.depthFirst
- nextName = node.name
+ * `cbFun` (callable) - user-supplied callable that is invoked to
+ pass read Managed Object Instance (the *next* one in the MIB tree
+ relative to the one being requested) or an error.
- try:
- return node.readGetNext((nextName, val), **context)
+ Notes
+ -----
+ The callback functions (e.g. `cbFun`) have the same signature as this
+ method where `varBind` contains read Managed Object Instance value.
+
+ In case of an error, the `error` key in the `context` dict will contain
+ an exception object.
+ """
+ (debug.logger & debug.flagIns and
+ debug.logger('%s: readGetNext(%s, %r)' % (self, name, val)))
- except (error.NoAccessError, error.NoSuchInstanceError, error.NoSuchObjectError):
- pass
+ self._readNext('readGetNext', varBind, **context)
# Write operation
def writeTest(self, varBind, **context):
+ """Test the ability to modify Managed Object Instance.
+
+ Implements the first of the multi-step workflow of the SNMP SET
+ command processing (:RFC:`1905#section-4.2.5`).
+
+ The goal of the first phase is to make sure that requested Managed
+ Object Instance could potentially be changed. When multiple Managed
+ Objects Instances are modified at once (likely coming all in one SNMP
+ PDU), each of them has to run through the first (*test*) phase
+ successfully for the system to transition to the second (*commit*)
+ phase.
+
+ The role of this object in the MIB tree is non-terminal. It does not
+ access the actual Managed Object Instance, but just traverses one level
+ down the MIB tree and hands off the query to the underlying objects.
+
+ Parameters
+ ----------
+ varBind: :py:class:`~pysnmp.smi.rfc1902.ObjectType` object representing
+ new Managed Object Instance value to set
+
+ 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 an error, the `error` key in the `context` dict will contain
+ an exception object.
+ """
name, val = varBind
- if name == self.name:
- # Make sure variable is writable
- acFun = context.get('acFun')
- if acFun:
- if (self.maxAccess not in ('readwrite', 'readcreate') or
- acFun('write', (name, self.syntax), **context)):
- raise error.NotWritableError(name=name, idx=context.get('idx'))
- else:
+ (debug.logger & debug.flagIns and
+ debug.logger('%s: writeTest(%s, %r)' % (self, name, val)))
+
+ try:
node = self.getBranch(name, **context)
+
+ except (error.NoSuchInstanceError, error.NoSuchObjectError):
+ self.createTest(varBind, **context)
+
+ else:
node.writeTest(varBind, **context)
def writeCommit(self, varBind, **context):
+ """Commit new value of the Managed Object Instance.
+
+ Implements the second of the multi-step workflow of the SNMP SET
+ command processing (:RFC:`1905#section-4.2.5`).
+
+ The goal of the second phase is to actually modify the requested Managed
+ Object Instance. When multiple Managed Objects Instances are modified at
+ once (likely coming all in one SNMP PDU), each of them has to run through
+ the second (*commit*) phase successfully for the system to transition to
+ the third (*cleanup*) phase. If any single *commit* step fails, the system
+ transitions into the *undo* state for each of Managed Objects Instances
+ being processed at once.
+
+ The role of this object in the MIB tree is non-terminal. It does not
+ access the actual Managed Object Instance, but just traverses one level
+ down the MIB tree and hands off the query to the underlying objects.
+
+ Parameters
+ ----------
+ varBind: :py:class:`~pysnmp.smi.rfc1902.ObjectType` object representing
+ new Managed Object Instance value to set
+
+ 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.
+
+ Notes
+ -----
+ The callback functions (e.g. `cbFun`) have the same signature as this
+ method where `varBind` contains the new Managed Object Instance value.
+
+ In case of an error, the `error` key in the `context` dict will contain
+ an exception object.
+ """
name, val = varBind
- node = self.getBranch(name, **context)
- node.writeCommit(varBind, **context)
+ (debug.logger & debug.flagIns and
+ debug.logger('%s: writeCommit(%s, %r)' % (self, name, val)))
+
+ cbFun = context['cbFun']
+
+ instances = context['instances'].setdefault(self.name, {self.ST_CREATE: {}, self.ST_DESTROY: {}})
+ idx = context['idx']
+
+ if idx in instances[self.ST_CREATE]:
+ self.createCommit(varBind, **context)
+ return
+
+ if idx in instances[self.ST_DESTROY]:
+ self.destroyCommit(varBind, **context)
+ return
+
+ try:
+ node = self.getBranch(name, **context)
+
+ except (error.NoSuchInstanceError, error.NoSuchObjectError):
+ exc = sys.exc_info()[1]
+ cbFun(varBind, **dict(context, error=exc))
+
+ else:
+ node.writeCommit(varBind, **context)
def writeCleanup(self, varBind, **context):
+ """Finalize Managed Object Instance modification.
+
+ Implements the successful third step of the multi-step workflow of the
+ SNMP SET command processing (:RFC:`1905#section-4.2.5`).
+
+ The goal of the third (successful) phase is to seal the new state of the
+ requested Managed Object Instance. Once the system transition into the
+ *cleanup* state, no roll back to the previous Managed Object Instance
+ state is possible.
+
+ The role of this object in the MIB tree is non-terminal. It does not
+ access the actual Managed Object Instance, but just traverses one level
+ down the MIB tree and hands off the query to the underlying objects.
+
+ Parameters
+ ----------
+ varBind: :py:class:`~pysnmp.smi.rfc1902.ObjectType` object representing
+ new Managed Object Instance value to set
+
+ 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.
+
+ Notes
+ -----
+ The callback functions (e.g. `cbFun`) have the same signature as this
+ method where `varBind` contains the new Managed Object Instance value.
+
+ In case of an error, the `error` key in the `context` dict will contain
+ an exception object.
+ """
name, val = varBind
+ (debug.logger & debug.flagIns and
+ debug.logger('%s: writeCleanup(%s, %r)' % (self, name, val)))
+
+ cbFun = context['cbFun']
+
self.branchVersionId += 1
- node = self.getBranch(name, **context)
- node.writeCleanup(varBind, **context)
+ instances = context['instances'].setdefault(self.name, {self.ST_CREATE: {}, self.ST_DESTROY: {}})
+ idx = context['idx']
+
+ if idx in instances[self.ST_CREATE]:
+ self.createCleanup(varBind, **context)
+ return
+
+ if idx in instances[self.ST_DESTROY]:
+ self.destroyCleanup(varBind, **context)
+ return
+
+ try:
+ node = self.getBranch(name, **context)
+
+ except (error.NoSuchInstanceError, error.NoSuchObjectError):
+ exc = sys.exc_info()[1]
+ cbFun(varBind, **dict(context, error=exc))
+
+ else:
+ node.writeCleanup(varBind, **context)
def writeUndo(self, varBind, **context):
+ """Finalize Managed Object Instance modification.
+
+ Implements the third (unsuccessful) step of the multi-step workflow
+ of the SNMP SET command processing (:RFC:`1905#section-4.2.5`).
+
+ The goal of the third phase is to roll the Managed Object Instance
+ being modified back into its previous state. The system transitions
+ into the *undo* state whenever any of the simultaneously modified
+ Managed Objects Instances fail on the *commit* state transitioning.
+
+ The role of this object in the MIB tree is non-terminal. It does not
+ access the actual Managed Object Instance, but just traverses one level
+ down the MIB tree and hands off the query to the underlying objects.
+
+ Parameters
+ ----------
+ varBind: :py:class:`~pysnmp.smi.rfc1902.ObjectType` object representing
+ new Managed Object Instance value to set
+
+ 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.
+
+ Notes
+ -----
+ The callback functions (e.g. `cbFun`) have the same signature as this
+ method where `varBind` contains the new Managed Object Instance value.
+
+ In case of an error, the `error` key in the `context` dict will contain
+ an exception object.
+ """
name, val = varBind
- node = self.getBranch(name, **context)
- node.writeUndo(varBind, **context)
+ (debug.logger & debug.flagIns and
+ debug.logger('%s: writeUndo(%s, %r)' % (self, name, val)))
+
+ cbFun = context['cbFun']
+
+ instances = context['instances'].setdefault(self.name, {self.ST_CREATE: {}, self.ST_DESTROY: {}})
+ idx = context['idx']
+
+ if idx in instances[self.ST_CREATE]:
+ self.createUndo(varBind, **context)
+ return
+
+ if idx in instances[self.ST_DESTROY]:
+ self.destroyUndo(varBind, **context)
+ return
+
+ try:
+ node = self.getBranch(name, **context)
+
+ except (error.NoSuchInstanceError, error.NoSuchObjectError):
+ exc = sys.exc_info()[1]
+ cbFun(varBind, **dict(context, error=exc))
+
+ else:
+ node.writeUndo(varBind, **context)
+
+
+class MibTree(ManagedMibObject):
+ """Managed MIB Tree root object.
+ Represents the root node of the MIB tree implementing management
+ instrumentation.
-class MibScalar(MibTree):
- """Scalar MIB variable. Implements access control checking."""
+ Objects of this type can't carry any value of their own, they serve
+ for holding and ordering other (children) nodes such as
+ :class:`MibScalar`, :class:`MibTable`, :class:`MibTableRowcalar` objects.
+
+ In the MIB tree, :class:`MibScalar` objects reside right under the tree
+ top, each can have a single :class:`MibScalarInstance` object attached:
+
+ MibTree
+ |
+ +----MibScalar
+ |
+ +----MibTable
+ |
+ +----MibTableRow
+ """
+
+
+class MibScalar(ManagedMibObject):
+ """Managed scalar MIB object.
+
+ Represents scalar SMI OBJECT-TYPE object implementing management
+ instrumentation.
+
+ Objects of this type can't carry any value of their own, they serve
+ as structural "blueprints" for :class:`MibScalarInstance` objects.
+
+ In the MIB tree, :class:`MibScalar` objects reside right under the tree
+ top, each can have a single :class:`MibScalarInstance` object attached:
+
+ MibTree
+ |
+ +----MibScalar
+ |
+ +-----MibScalarInstance
+ """
maxAccess = 'readonly'
+ _suffix = (0,)
+
#
# Subtree traversal
#
@@ -578,118 +1084,517 @@ class MibScalar(MibTree):
def getBranch(self, name, **context):
try:
- return MibTree.getBranch(self, name, **context)
+ return ManagedMibObject.getBranch(self, name, **context)
except (error.NoSuchInstanceError, error.NoSuchObjectError):
raise error.NoSuchInstanceError(name=name, idx=context.get('idx'))
def getNextBranch(self, name, **context):
try:
- return MibTree.getNextBranch(self, name, **context)
+ return ManagedMibObject.getNextBranch(self, name, **context)
except (error.NoSuchInstanceError, error.NoSuchObjectError):
raise error.NoSuchInstanceError(name=name, idx=context.get('idx'))
def getNode(self, name, **context):
try:
- return MibTree.getNode(self, name, **context)
+ return ManagedMibObject.getNode(self, name, **context)
except (error.NoSuchInstanceError, error.NoSuchObjectError):
raise error.NoSuchInstanceError(name=name, idx=context.get('idx'))
def getNextNode(self, name, **context):
try:
- return MibTree.getNextNode(self, name, **context)
+ return ManagedMibObject.getNextNode(self, name, **context)
except (error.NoSuchInstanceError, error.NoSuchObjectError):
raise error.NoSuchInstanceError(name=name, idx=context.get('idx'))
# MIB instrumentation methods
- # Read operation
+ def readGet(self, varBind, **context):
+ """Read Managed Object Instance.
- def readTest(self, varBind, **context):
- name, val = varBind
+ Implements the second of the two phases of the SNMP GET command
+ processing (:RFC:`1905#section-4.2.1`).
- if name == self.name:
- raise error.NoAccessError(name=name, idx=context.get('idx'))
+ The goal of the second phase is to actually read the requested Managed
+ Object Instance. When multiple Managed Objects Instances are read at
+ once (likely coming all in one SNMP PDU), each of them has to run through
+ the first (*test*) and second (*read) phases successfully for the whole
+ read operation to succeed.
- acFun = context.get('acFun')
- if acFun:
- if (self.maxAccess not in ('readonly', 'readwrite', 'readcreate') or
- acFun('read', (name, self.syntax), **context)):
- raise error.NoAccessError(name=name, idx=context.get('idx'))
+ The role of this object in the MIB tree is non-terminal. It does not
+ access the actual Managed Object Instance, but just traverses one level
+ down the MIB tree and hands off the query to the underlying objects.
- MibTree.readTest(self, varBind, **context)
+ Beyond that, this object imposes access control logic towards the
+ underlying :class:`MibScalarInstance` objects by invoking the `acFun`
+ callable.
- def readGet(self, varBind, **context):
- name, val = varBind
+ Parameters
+ ----------
+ varBind: :py:class:`~pysnmp.smi.rfc1902.ObjectType` object representing
+ Managed Object Instance to read
- try:
- node = self.getBranch(name, **context)
+ Other Parameters
+ ----------------
+ \*\*context:
- except error.NoSuchInstanceError:
- return name, exval.noSuchInstance
+ Query parameters:
- else:
- return node.readGet(varBind, **context)
+ * `cbFun` (callable) - user-supplied callable that is invoked to
+ pass read Managed Object Instance or an error.
- def readTestNext(self, varBind, **context):
+ * `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`) has the same signature as
+ this method where `varBind` contains read Managed Object Instance value.
+
+ In case of an error, the `error` key in the `context` dict will contain
+ an exception object.
+ """
name, val = varBind
+ (debug.logger & debug.flagIns and
+ debug.logger('%s: readGet(%s, %r)' % (self, name, val)))
+
+ cbFun = context['cbFun']
+
+ if name == self.name:
+ cbFun((name, exval.noSuchInstance), **context)
+ return
+
acFun = context.get('acFun')
if acFun:
if (self.maxAccess not in ('readonly', 'readwrite', 'readcreate') or
acFun('read', (name, self.syntax), **context)):
- raise error.NoAccessError(name=name, idx=context.get('idx'))
+ cbFun((name, exval.noSuchInstance), **context)
+ return
- MibTree.readTestNext(self, varBind, **context)
+ ManagedMibObject.readGet(self, varBind, **context)
def readGetNext(self, varBind, **context):
+ """Read the next Managed Object Instance.
+
+ Implements the second of the two phases of the SNMP GETNEXT command
+ processing (:RFC:`1905#section-4.2.2`).
+
+ The goal of the second phase is to actually read the Managed Object
+ Instance which is next in the MIB tree to the one being requested.
+ When multiple Managed Objects Instances are read at once (likely coming
+ all in one SNMP PDU), each of them has to run through the first
+ (*testnext*) and second (*getnext*) phases successfully for the whole
+ read operation to succeed.
+
+ The role of this object in the MIB tree is non-terminal. It does not
+ access the actual Managed Object Instance, but just traverses one level
+ down the MIB tree and hands off the query to the underlying objects.
+
+ Beyond that, this object imposes access control logic towards the
+ underlying :class:`MibScalarInstance` objects by invoking the `acFun`
+ callable.
+
+ Parameters
+ ----------
+ varBind: :py:class:`~pysnmp.smi.rfc1902.ObjectType` object representing
+ Managed Object Instance to read
+
+ Other Parameters
+ ----------------
+ \*\*context:
+
+ Query parameters:
+
+ * `cbFun` (callable) - user-supplied callable that is invoked to
+ pass read Managed Object Instance (the *next* one in the MIB tree
+ relative to the one being requested) or an error.
+
+ * `acFun` (callable) - user-supplied callable that is invoked to
+ authorize access to the Managed Object Instance which is *next*
+ to the one being requested. 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 read Managed Object Instance
+ value.
+
+ In case of an error, the `error` key in the `context` dict will contain
+ an exception object.
+ """
name, val = varBind
- # have to duplicate AC here as *Next code above treats
- # noAccess as a noSuchObject at the Test stage, goes on
- # to Reading
+ (debug.logger & debug.flagIns and
+ debug.logger('%s: readGetNext(%s, %r)' % (self, name, val)))
+
acFun = context.get('acFun')
if acFun:
if (self.maxAccess not in ('readonly', 'readwrite', 'readcreate') or
acFun('read', (name, self.syntax), **context)):
- raise error.NoAccessError(name=name, idx=context.get('idx'))
+ nextName = context.get('nextName')
+ if nextName:
+ varBind = nextName, exval.noSuchInstance
+ else:
+ varBind = name, exval.endOfMibView
- return MibTree.readGetNext(self, varBind, **context)
+ cbFun = context['cbFun']
+ cbFun(varBind, **context)
+ return
- # Two-phase commit implementation
+ ManagedMibObject.readGetNext(self, varBind, **context)
def writeTest(self, varBind, **context):
+ """Test the ability to modify Managed Object Instance.
+
+ Implements the first of the multi-step workflow of the SNMP SET
+ command processing (:RFC:`1905#section-4.2.5`).
+
+ The goal of the first phase is to make sure that requested Managed
+ Object Instance could potentially be changed. When multiple Managed
+ Objects Instances are modified at once (likely coming all in one SNMP
+ PDU), each of them has to run through the first (*test*) phase
+ successfully for the system to transition to the second (*commit*)
+ phase.
+
+ The role of this object in the MIB tree is non-terminal. It does not
+ access the actual Managed Object Instance, but just traverses one level
+ down the MIB tree and hands off the query to the underlying objects.
+
+ Beyond that, this object imposes access control logic towards the
+ underlying :class:`MibScalarInstance` objects by invoking the `acFun`
+ callable.
+
+ Parameters
+ ----------
+ varBind: :py:class:`~pysnmp.smi.rfc1902.ObjectType` object representing
+ new Managed Object Instance value to set
+
+ 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 an error, the `error` key in the `context` dict will contain
+ an exception object.
+ """
name, val = varBind
- if name == self.name:
- raise error.NoAccessError(name=name, idx=context.get('idx'))
+ (debug.logger & debug.flagIns and
+ debug.logger('%s: writeTest(%s, %r)' % (self, name, val)))
acFun = context.get('acFun')
if acFun:
if (self.maxAccess not in ('readwrite', 'readcreate') or
acFun('write', (name, self.syntax), **context)):
- raise error.NotWritableError(name=name, idx=context.get('idx'))
+ exc = error.NotWritableError(name=name, idx=context.get('idx'))
+ cbFun = context['cbFun']
+ cbFun(varBind, **dict(context, error=exc))
+ return
+
+ ManagedMibObject.writeTest(self, varBind, **context)
+
+ def _checkSuffix(self, name):
+ suffix = name[:len(self.name)]
+ return suffix == (0,)
+
+ def createTest(self, varBind, **context):
+ """Test the ability to create a Managed Object Instance.
+
+ Implements the first of the multi-step workflow similar to the SNMP SET
+ command processing (:RFC:`1905#section-4.2.5`).
+
+ The goal of the first phase is to make sure that requested Managed
+ Object Instance could potentially be created. When multiple Managed
+ Objects Instances are modified at once (likely coming all in one SNMP
+ PDU), each of them has to run through the first (*test*) phase
+ successfully for the system to transition to the second (*commit*)
+ phase.
+
+ The role of this object in the MIB tree is non-terminal. It does not
+ access the actual Managed Object Instance, but just traverses one level
+ down the MIB tree and hands off the query to the underlying objects.
+
+ Parameters
+ ----------
+ varBind: :py:class:`~pysnmp.smi.rfc1902.ObjectType` object representing
+ new Managed Object Instance value to create
+
+ 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.
+
+ * `instances` (dict): user-supplied dict for temporarily holding
+ Managed Objects Instances being created.
+
+ Notes
+ -----
+ The callback functions (e.g. `cbFun`) have the same signature as this method
+ where `varBind` contains the new Managed Object Instance value.
+
+ In case of an error, the `error` key in the `context` dict will contain
+ an exception object.
+ """
+ name, val = varBind
+
+ (debug.logger & debug.flagIns and
+ debug.logger('%s: createTest(%s, %r)' % (self, name, val)))
+
+ cbFun = context['cbFun']
+
+ if not self._checkSuffix(name):
+ exc = error.NoCreationError(name=name, idx=context.get('idx'))
+ cbFun(varBind, **dict(context, error=exc))
+ return
+
+ acFun = context.get('acFun')
+ if acFun:
+ if self.maxAccess != 'readcreate' or acFun('write', varBind, **context):
+ debug.logger & debug.flagACL and debug.logger(
+ 'createTest: %s=%r %s at %s' % (name, val, self.maxAccess, self.name))
+ exc = error.NoCreationError(name=name, idx=context.get('idx'))
+ cbFun(varBind, **dict(context, error=exc))
+ return
+
+ instances = context['instances'].setdefault(self.name, {self.ST_CREATE: {}, self.ST_DESTROY: {}})
+ idx = context['idx']
+
+ instId = name[len(self.name):]
+
+ if name in self._vars:
+ cbFun(varBind, **context)
+ return
- MibTree.writeTest(self, varBind, **context)
+ instances[self.ST_CREATE][idx] = MibScalarInstance(self.name, instId, self.syntax.clone())
+ instances[self.ST_CREATE][idx].writeTest((name, val), **context)
-class MibScalarInstance(MibTree):
- """Scalar MIB variable instance. Implements read/write operations."""
+ def createCommit(self, varBind, **context):
+ """Create Managed Object Instance.
+
+ Implements the second of the multi-step workflow similar to the SNMP SET
+ command processing (:RFC:`1905#section-4.2.5`).
+
+ The goal of the second phase is to actually create requested Managed
+ Object Instance. When multiple Managed Objects Instances are created/modified
+ at once (likely coming all in one SNMP PDU), each of them has to run through
+ the second (*commit*) phase successfully for the system to transition to
+ the third (*cleanup*) phase. If any single *commit* step fails, the system
+ transitions into the *undo* state for each of Managed Objects Instances
+ being processed at once.
+
+ The role of this object in the MIB tree is non-terminal. It does not
+ access the actual Managed Object Instance, but just traverses one level
+ down the MIB tree and hands off the query to the underlying objects.
+
+ Parameters
+ ----------
+ varBind: :py:class:`~pysnmp.smi.rfc1902.ObjectType` object representing
+ new Managed Object Instance value to create
+
+ 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.
+
+ * `instances` (dict): user-supplied dict for temporarily holding
+ Managed Objects Instances being created.
+
+ Notes
+ -----
+ The callback functions (e.g. `cbFun`) have the same signature as this
+ method where `varBind` contains the new Managed Object Instance value.
+
+ In case of an error, the `error` key in the `context` dict will contain
+ an exception object.
+ """
+ name, val = varBind
+
+ (debug.logger & debug.flagIns and
+ debug.logger('%s: writeCommit(%s, %r)' % (self, name, val)))
+
+ cbFun = context['cbFun']
+
+ instances = context['instances'].setdefault(self.name, {self.ST_CREATE: {}, self.ST_DESTROY: {}})
+ idx = context['idx']
+
+ if name in self._vars:
+ cbFun(varBind, **context)
+ return
+
+ # NOTE: multiple names are possible in a single PDU, that could collide
+ # Therefore let's keep old object indexed by (negative) var-bind index
+ self._vars[name], instances[self.ST_CREATE][-idx - 1] = instances[self.ST_CREATE][idx], self._vars.get(name)
+
+ instances[self.ST_CREATE][idx].writeCommit(varBind, **context)
+
+ def createCleanup(self, varBind, **context):
+ """Finalize Managed Object Instance creation.
+
+ Implements the successful third step of the multi-step workflow similar to
+ the SNMP SET command processing (:RFC:`1905#section-4.2.5`).
+
+ The goal of the third (successful) phase is to seal the new Managed Object
+ Instance. Once the system transitions into the *cleanup* state, no roll back
+ to the previous Managed Object Instance state is possible.
+
+ The role of this object in the MIB tree is non-terminal. It does not
+ access the actual Managed Object Instance, but just traverses one level
+ down the MIB tree and hands off the query to the underlying objects.
+
+ Parameters
+ ----------
+ varBind: :py:class:`~pysnmp.smi.rfc1902.ObjectType` object representing
+ new Managed Object Instance value to create
+
+ 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.
+
+ * `instances` (dict): user-supplied dict for temporarily holding
+ Managed Objects Instances being created.
+
+ Notes
+ -----
+ The callback functions (e.g. `cbFun`) have the same signature as this
+ method where `varBind` contains the new Managed Object Instance value.
+
+ In case of an error, the `error` key in the `context` dict will contain
+ an exception object.
+ """
+ name, val = varBind
+
+ (debug.logger & debug.flagIns and
+ debug.logger('%s: createCleanup(%s, %r)' % (self, name, val)))
+
+ instances = context['instances'].setdefault(self.name, {self.ST_CREATE: {}, self.ST_DESTROY: {}})
+ idx = context['idx']
+
+ self.branchVersionId += 1
+
+ instances[self.ST_CREATE].pop(-idx - 1, None)
+
+ self._vars[name].writeCleanup(varBind, **context)
+
+ def createUndo(self, varBind, **context):
+ """Undo Managed Object Instance creation.
+
+ Implements the third (unsuccessful) step of the multi-step workflow
+ similar to the SNMP SET command processing (:RFC:`1905#section-4.2.5`).
+
+ The goal of the third phase is to delete the Managed Object Instance
+ being created. The system transitions into the *undo* state whenever
+ any of the simultaneously modified Managed Objects Instances fail on the
+ *commit* state transitioning.
+
+ The role of this object in the MIB tree is non-terminal. It does not
+ access the actual Managed Object Instance, but just traverses one level
+ down the MIB tree and hands off the query to the underlying objects.
+
+ Parameters
+ ----------
+ varBind: :py:class:`~pysnmp.smi.rfc1902.ObjectType` object representing
+ new Managed Object Instance value to create
+ 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.
+
+ * `instances` (dict): user-supplied dict for temporarily holding
+ Managed Objects Instances being created.
+
+ Notes
+ -----
+ The callback functions (e.g. `cbFun`) have the same signature as this
+ method where `varBind` contains the new Managed Object Instance value.
+
+ In case of an error, the `error` key in the `context` dict will contain
+ an exception object.
+ """
+ name, val = varBind
+
+ (debug.logger & debug.flagIns and
+ debug.logger('%s: createUndo(%s, %r)' % (self, name, val)))
+
+ instances = context['instances'].setdefault(self.name, {self.ST_CREATE: {}, self.ST_DESTROY: {}})
+ idx = context['idx']
+
+ instances[self.ST_CREATE].pop(-idx - 1, None)
+
+ obj = self._vars.pop(name, None)
+ if obj:
+ obj.writeUndo(varBind, **context)
+
+ else:
+ cbFun = context['cbFun']
+ cbFun(varBind, **context)
+
+
+class MibScalarInstance(ManagedMibObject):
+ """Managed scalar instance MIB object.
+
+ Represents an instance of a scalar SMI OBJECT-TYPE object implementing
+ management instrumentation.
+
+ Objects of this type carry the actual value or somehow interface the
+ data source.
+
+ In the MIB tree, :class:`MibScalarInstance` objects reside right under their
+ :class:`MibScalarInstance` parent object:
+
+ MibTree
+ |
+ +----MibScalar
+ |
+ +-----MibScalarInstance
+ """
def __init__(self, typeName, instId, syntax):
- MibTree.__init__(self, typeName + instId, syntax)
+ ManagedMibObject.__init__(self, typeName + instId, syntax)
self.typeName = typeName
self.instId = instId
- self.__oldSyntax = None
#
# Managed object value access methods
#
- # noinspection PyUnusedLocal
def getValue(self, name, **context):
debug.logger & debug.flagIns and debug.logger('getValue: returning %r for %s' % (self.syntax, self.name))
return self.syntax.clone()
@@ -721,14 +1626,14 @@ class MibScalarInstance(MibTree):
def getBranch(self, name, **context):
try:
- return MibTree.getBranch(self, name, **context)
+ return ManagedMibObject.getBranch(self, name, **context)
except (error.NoSuchInstanceError, error.NoSuchObjectError):
raise error.NoSuchInstanceError(name=name, idx=context.get('idx'))
def getNextBranch(self, name, **context):
try:
- return MibTree.getNextBranch(self, name, **context)
+ return ManagedMibObject.getNextBranch(self, name, **context)
except (error.NoSuchInstanceError, error.NoSuchObjectError):
raise error.NoSuchInstanceError(name=name, idx=context.get('idx'))
@@ -744,169 +1649,500 @@ class MibScalarInstance(MibTree):
# MIB instrumentation methods
- # Read operation
-
def readTest(self, varBind, **context):
+ """Test the ability to read Managed Object Instance.
+
+ Implements the first of the two phases of the SNMP GET command
+ processing (:RFC:`1905#section-4.2.1`).
+
+ The goal of the first phase is to make sure that requested Managed
+ Object Instance could potentially be read. When multiple Managed
+ Objects Instances are read at once (likely coming all in one SNMP PDU),
+ each of them has to run through the first (*test*) phase successfully
+ for the system to transition to the second (*get*) phase.
+
+ The role of this object in the MIB tree is terminal. It does access the
+ actual Managed Object Instance.
+
+ Parameters
+ ----------
+ varBind: :py:class:`~pysnmp.smi.rfc1902.ObjectType` object representing
+ Managed Object Instance to read
+
+ Other Parameters
+ ----------------
+ \*\*context:
+
+ Query parameters:
+
+ * `cbFun` (callable) - user-supplied callable that is invoked to
+ pass read Managed Object Instance or an error.
+
+ Notes
+ -----
+ The callback functions (e.g. `cbFun`) have the same signature as this
+ method where `varBind` contains read Managed Object Instance value.
+
+ In case of an error, the `error` key in the `context` dict will contain
+ an exception object.
+ """
name, val = varBind
+ (debug.logger & debug.flagIns and
+ debug.logger('%s: readTest(%s, %r)' % (self, name, val)))
+
+ cbFun = context['cbFun']
+
if name != self.name:
- raise error.NoSuchInstanceError(name=name, idx=context.get('idx'))
+ exc = error.NoSuchInstanceError(name=name, idx=context.get('idx'))
+ cbFun(varBind, **dict(context, error=exc))
+ return
+
+ cbFun((self.name, self.syntax), **context)
def readGet(self, varBind, **context):
+ """Read Managed Object Instance.
+
+ Implements the second of the two phases of the SNMP GET command
+ processing (:RFC:`1905#section-4.2.1`).
+
+ The goal of the second phase is to actually read the requested Managed
+ Object Instance. When multiple Managed Objects Instances are read at
+ once (likely coming all in one SNMP PDU), each of them has to run through
+ the first (*test*) and second (*read) phases successfully for the whole
+ read operation to succeed.
+
+ The role of this object in the MIB tree is terminal. It does access the
+ actual Managed Object Instance.
+
+ Parameters
+ ----------
+ varBind: :py:class:`~pysnmp.smi.rfc1902.ObjectType` object representing
+ Managed Object Instance to read
+
+ Other Parameters
+ ----------------
+ \*\*context:
+
+ Query parameters:
+
+ * `cbFun` (callable) - user-supplied callable that is invoked to
+ pass read Managed Object Instance or an error.
+
+ Notes
+ -----
+ The callback functions (e.g. `cbFun`) has the same signature as
+ this method where `varBind` contains read Managed Object Instance value.
+
+ In case of an error, the `error` key in the `context` dict will contain
+ an exception object.
+ """
name, val = varBind
- # Return current variable (name, value)
- if name == self.name:
- debug.logger & debug.flagIns and debug.logger('readGet: %s=%r' % (self.name, self.syntax))
- return self.name, self.getValue(name, **context)
- else:
- raise error.NoSuchInstanceError(name=name, idx=context.get('idx'))
+ (debug.logger & debug.flagIns and
+ debug.logger('%s: readGet(%s, %r)' % (self, name, val)))
+
+ cbFun = context['cbFun']
+
+ if name != self.name:
+ exc = error.NoSuchInstanceError(name=name, idx=context.get('idx'))
+ cbFun(varBind, **dict(context, error=exc))
+ return
+
+ cbFun((self.name, self.getValue(name, **context)), **context)
def readTestNext(self, varBind, **context):
+ """Test the ability to read the next Managed Object Instance.
+
+ Implements the first of the two phases of the SNMP GETNEXT command
+ processing (:RFC:`1905#section-4.2.2`).
+
+ The goal of the first phase is to make sure that requested Managed
+ Object Instance could potentially be read. When multiple Managed
+ Objects Instances are read at once (likely coming all in one SNMP PDU),
+ each of them has to run through the first (*testnext*) phase
+ successfully for the system to transition to the second (*getnext*)
+ phase.
+
+ The role of this object in the MIB tree is terminal. It does access the
+ actual Managed Object Instance.
+
+ Parameters
+ ----------
+ varBind: :py:class:`~pysnmp.smi.rfc1902.ObjectType` object representing
+ Managed Object Instance next to which to read
+
+ Other Parameters
+ ----------------
+ \*\*context:
+
+ Query parameters:
+
+ * `cbFun` (callable) - user-supplied callable that is invoked to
+ pass read Managed Object Instance (the *next* one in the MIB tree
+ relative to the one being requested) or an error.
+
+ * `acFun` (callable) - user-supplied callable that is invoked to
+ authorize access to the Managed Object Instance which is *next*
+ to the one being requested. 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 read Managed Object Instance
+ value.
+
+ In case of an error, the `error` key in the `context` dict will contain
+ an exception object.
+ """
name, val = varBind
- oName = context.get('oName')
+ (debug.logger & debug.flagIns and
+ debug.logger('%s: readTestNext(%s, %r)' % (self, name, val)))
- if name != self.name or name <= oName:
- raise error.NoSuchInstanceError(name=name, idx=context.get('idx'))
+ cbFun = context['cbFun']
+
+ if name >= self.name:
+ nextName = context.get('nextName')
+ if nextName:
+ varBind = nextName, exval.noSuchInstance
+ else:
+ varBind = name, exval.endOfMibView
+
+ cbFun(varBind, **context)
+ return
+
+ cbFun((self.name, self.syntax), **context)
def readGetNext(self, varBind, **context):
+ """Read the next Managed Object Instance.
+
+ Implements the second of the two phases of the SNMP GETNEXT command
+ processing (:RFC:`1905#section-4.2.2`).
+
+ The goal of the second phase is to actually read the Managed Object
+ Instance which is next in the MIB tree to the one being requested.
+ When multiple Managed Objects Instances are read at once (likely coming
+ all in one SNMP PDU), each of them has to run through the first
+ (*testnext*) and second (*getnext*) phases successfully for the whole
+ read operation to succeed.
+
+ The role of this object in the MIB tree is terminal. It does access the
+ actual Managed Object Instance.
+
+ Parameters
+ ----------
+ varBind: :py:class:`~pysnmp.smi.rfc1902.ObjectType` object representing
+ Managed Object Instance to read
+
+ Other Parameters
+ ----------------
+ \*\*context:
+
+ Query parameters:
+
+ * `cbFun` (callable) - user-supplied callable that is invoked to
+ pass read Managed Object Instance (the *next* one in the MIB tree
+ relative to the one being requested) or an error.
+
+ Notes
+ -----
+ The callback functions (e.g. `cbFun`) have the same signature as this
+ method where `varBind` contains read Managed Object Instance value.
+
+ In case of an error, the `error` key in the `context` dict will contain
+ an exception object.
+ """
name, val = varBind
- oName = context.get('oName')
+ (debug.logger & debug.flagIns and
+ debug.logger('%s: readGetNext(%s, %r)' % (self, name, val)))
- if name == self.name and name > oName:
- debug.logger & debug.flagIns and debug.logger('readGetNext: %s=%r' % (self.name, self.syntax))
- return self.readGet(varBind, **context)
+ cbFun = context['cbFun']
- else:
- raise error.NoSuchInstanceError(name=name, idx=context.get('idx'))
+ if name >= self.name:
+ nextName = context.get('nextName')
+ if nextName:
+ varBind = nextName, exval.noSuchInstance
+ else:
+ varBind = name, exval.endOfMibView
- # Write operation: two-phase commit
+ cbFun(varBind, **context)
+ return
+
+ cbFun((self.name, self.getValue(self.name, **context)), **context)
- # noinspection PyAttributeOutsideInit
def writeTest(self, varBind, **context):
+ """Test the ability to modify Managed Object Instance.
+
+ Implements the first of the multi-step workflow of the SNMP SET
+ command processing (:RFC:`1905#section-4.2.5`).
+
+ The goal of the first phase is to make sure that requested Managed
+ Object Instance could potentially be changed. When multiple Managed
+ Objects Instances are modified at once (likely coming all in one SNMP
+ PDU), each of them has to run through the first (*test*) phase
+ successfully for the system to transition to the second (*commit*)
+ phase.
+
+ The role of this object in the MIB tree is terminal. It does access the
+ actual Managed Object Instance.
+
+ Parameters
+ ----------
+ varBind: :py:class:`~pysnmp.smi.rfc1902.ObjectType` object representing
+ new Managed Object Instance value to set
+
+ 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.
+
+ * `instances` (dict): user-supplied dict for temporarily holding
+ the values of the Managed Objects Instances being modified.
+
+ 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 an error, the `error` key in the `context` dict will contain
+ an exception object.
+ """
name, val = varBind
+ (debug.logger & debug.flagIns and
+ debug.logger('%s: writeTest(%s, %r)' % (self, name, val)))
+
+ cbFun = context['cbFun']
+
+ instances = context['instances'].setdefault(self.name, {self.ST_CREATE: {}, self.ST_DESTROY: {}})
+ idx = context['idx']
+
+ if name != self.name:
+ exc = error.NoSuchInstanceError(name=name, idx=context.get('idx'))
+ cbFun(varBind, **dict(context, error=exc))
+
# Make sure write's allowed
- if name == self.name:
- try:
- self.__newSyntax = self.setValue(val, name, **context)
-
- except error.MibOperationError:
- # SMI exceptions may carry additional content
- why = sys.exc_info()[1]
- if 'syntax' in why:
- self.__newSyntax = why['syntax']
- raise why
- else:
- raise error.WrongValueError(name=name, idx=context.get('idx'), msg=sys.exc_info()[1])
- else:
- raise error.NoSuchInstanceError(name=name, idx=context.get('idx'))
+ try:
+ instances[self.ST_CREATE][idx] = self.setValue(val, name, **context)
+
+ except error.MibOperationError:
+ # SMI exceptions may carry additional content
+ exc = sys.exc_info()[1]
+ if 'syntax' in exc:
+ instances[self.ST_CREATE][idx] = exc['syntax']
+ cbFun(varBind, **dict(context, error=exc))
+ return
+
+ else:
+ exc = sys.exc_info()[1]
+ exc = error.WrongValueError(name=name, idx=context.get('idx'), msg=exc)
+ cbFun(varBind, **dict(context, error=exc))
+ return
+
+ cbFun((self.name, self.syntax), **context)
def writeCommit(self, varBind, **context):
- # Backup original value
- if self.__oldSyntax is None:
- self.__oldSyntax = self.syntax
- # Commit new value
- self.syntax = self.__newSyntax
+ """Commit new value of the Managed Object Instance.
- # noinspection PyAttributeOutsideInit
- def writeCleanup(self, varBind, **context):
- self.branchVersionId += 1
- debug.logger & debug.flagIns and debug.logger('writeCleanup: %s=%r' % (name, val))
- # Drop previous value
- self.__newSyntax = self.__oldSyntax = None
+ Implements the second of the multi-step workflow of the SNMP SET
+ command processing (:RFC:`1905#section-4.2.5`).
- # noinspection PyAttributeOutsideInit
- def writeUndo(self, varBind, **context):
- # Revive previous value
- self.syntax = self.__oldSyntax
- self.__newSyntax = self.__oldSyntax = None
+ The goal of the second phase is to actually modify the requested Managed
+ Object Instance. When multiple Managed Objects Instances are modified at
+ once (likely coming all in one SNMP PDU), each of them has to run through
+ the second (*commit*) phase successfully for the system to transition to
+ the third (*cleanup*) phase. If any single *commit* step fails, the system
+ transitions into the *undo* state for each of Managed Objects Instances
+ being processed at once.
- # Table column instance specifics
+ The role of this object in the MIB tree is terminal. It does access the
+ actual Managed Object Instance.
- # Create operation
+ Parameters
+ ----------
+ varBind: :py:class:`~pysnmp.smi.rfc1902.ObjectType` object representing
+ new Managed Object Instance value to set
- # noinspection PyUnusedLocal,PyAttributeOutsideInit
- def createTest(self, varBind, **context):
+ 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.
+
+ * `instances` (dict): user-supplied dict for temporarily holding
+ the values of the Managed Objects Instances being modified.
+
+ Notes
+ -----
+ The callback functions (e.g. `cbFun`) have the same signature as this
+ method where `varBind` contains the new Managed Object Instance value.
+
+ In case of an error, the `error` key in the `context` dict will contain
+ an exception object.
+ """
name, val = varBind
- if name == self.name:
- try:
- self.__newSyntax = self.setValue(val, name, **context)
+ (debug.logger & debug.flagIns and
+ debug.logger('%s: writeCommit(%s, %r)' % (self, name, val)))
- except error.MibOperationError:
- # SMI exceptions may carry additional content
- why = sys.exc_info()[1]
- if 'syntax' in why:
- self.__newSyntax = why['syntax']
- else:
- raise error.WrongValueError(name=name, idx=context.get('idx'), msg=sys.exc_info()[1])
- else:
- raise error.NoSuchInstanceError(name=name, idx=context.get('idx'))
+ instances = context['instances'].setdefault(self.name, {self.ST_CREATE: {}, self.ST_DESTROY: {}})
+ idx = context['idx']
- def createCommit(self, varBind, **context):
+ instances[self.ST_CREATE][-idx - 1], self.syntax = self.syntax, instances[self.ST_CREATE][idx]
+
+ cbFun = context['cbFun']
+ cbFun((self.name, self.syntax), **context)
+
+ def writeCleanup(self, varBind, **context):
+ """Finalize Managed Object Instance modification.
+
+ Implements the successful third step of the multi-step workflow of the
+ SNMP SET command processing (:RFC:`1905#section-4.2.5`).
+
+ The goal of the third (successful) phase is to seal the new state of the
+ requested Managed Object Instance. Once the system transition into the
+ *cleanup* state, no roll back to the previous Managed Object Instance
+ state is possible.
+
+ The role of this object in the MIB tree is terminal. It does access the
+ actual Managed Object Instance.
+
+ Parameters
+ ----------
+ varBind: :py:class:`~pysnmp.smi.rfc1902.ObjectType` object representing
+ new Managed Object Instance value to set
+
+ 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.
+
+ * `instances` (dict): user-supplied dict for temporarily holding
+ the values of the Managed Objects Instances being modified.
+
+ Notes
+ -----
+ The callback functions (e.g. `cbFun`) have the same signature as this
+ method where `varBind` contains the new Managed Object Instance value.
+
+ In case of an error, the `error` key in the `context` dict will contain
+ an exception object.
+ """
name, val = varBind
- if val is not None:
- self.writeCommit(varBind, **context)
+ (debug.logger & debug.flagIns and
+ debug.logger('%s: writeCleanup(%s, %r)' % (self, name, val)))
+
+ instances = context['instances'].setdefault(self.name, {self.ST_CREATE: {}, self.ST_DESTROY: {}})
+ idx = context['idx']
- def createCleanup(self, varBind, **context):
self.branchVersionId += 1
- name, val = varBind
- debug.logger & debug.flagIns and debug.logger('createCleanup: %s=%r' % (name, val))
+ instances[self.ST_CREATE].pop(idx, None)
+ instances[self.ST_CREATE].pop(-idx - 1, None)
- if val is not None:
- self.writeCleanup(varBind, **context)
+ cbFun = context['cbFun']
+ cbFun((self.name, self.syntax), **context)
- def createUndo(self, varBind, **context):
- name, val = varBind
+ def writeUndo(self, varBind, **context):
+ """Undo Managed Object Instance modification.
- if val is not None:
- self.writeUndo(varBind, **context)
+ Implements the third (unsuccessful) step of the multi-step workflow
+ of the SNMP SET command processing (:RFC:`1905#section-4.2.5`).
- # Destroy operation
+ The goal of the third phase is to roll the Managed Object Instance
+ being modified back into its previous state. The system transitions
+ into the *undo* state whenever any of the simultaneously modified
+ Managed Objects Instances fail on the *commit* state transitioning.
- # noinspection PyUnusedLocal,PyAttributeOutsideInit
- def destroyTest(self, varBind, **context):
- name, val = varBind
+ The role of this object in the MIB tree is terminal. It does access the
+ actual Managed Object Instance.
- if name == self.name:
- try:
- self.__newSyntax = self.setValue(val, name, **context)
+ Parameters
+ ----------
+ varBind: :py:class:`~pysnmp.smi.rfc1902.ObjectType` object representing
+ new Managed Object Instance value to set
- except error.MibOperationError:
- # SMI exceptions may carry additional content
- why = sys.exc_info()[1]
- if 'syntax' in why:
- self.__newSyntax = why['syntax']
- else:
- raise error.NoSuchInstanceError(name=name, idx=context.get('idx'))
+ Other Parameters
+ ----------------
+ \*\*context:
- def destroyCommit(self, varBind, **context):
- pass
+ Query parameters:
- # noinspection PyUnusedLocal
- def destroyCleanup(self, varBind, **context):
- self.branchVersionId += 1
+ * `cbFun` (callable) - user-supplied callable that is invoked to
+ pass the new value of the Managed Object Instance or an error.
- def destroyUndo(self, varBind, **context):
- pass
+ * `instances` (dict): user-supplied dict for temporarily holding
+ the values of the Managed Objects Instances being modified.
+ Notes
+ -----
+ The callback functions (e.g. `cbFun`) have the same signature as this
+ method where `varBind` contains the new Managed Object Instance value.
-# Conceptual table classes
+ In case of an error, the `error` key in the `context` dict will contain
+ an exception object.
+ """
+ name, val = varBind
+
+ (debug.logger & debug.flagIns and
+ debug.logger('%s: writeUndo(%s, %r)' % (self, name, val)))
-class MibTableColumn(MibScalar):
- """MIB table column. Manages a set of column instance variables"""
- protoInstance = MibScalarInstance
+ instances = context['instances'].setdefault(self.name, {self.ST_CREATE: {}, self.ST_DESTROY: {}})
+ idx = context['idx']
- def __init__(self, name, syntax):
- MibScalar.__init__(self, name, syntax)
- self.__createdInstances = {}
- self.__destroyedInstances = {}
- self.__rowOpWanted = {}
+ self.syntax = instances[self.ST_CREATE].pop(-idx - 1, None)
+ instances[self.ST_CREATE].pop(idx, None)
+
+ cbFun = context['cbFun']
+ cbFun((self.name, self.syntax), **context)
+
+
+# Conceptual table classes
+
+class MibTableColumn(MibScalar, ObjectType):
+ """Managed columnar instance MIB object.
+
+ Represents columnar object (`OBJECT-TYPE`) of the SMI table implementing
+ management instrumentation.
+
+ Objects of this type do not carry the actual value, but can create or
+ destroy underlying :class:`MibScalarInstance` objects.
+
+ In the MIB tree, :class:`MibTableColumn` objects reside right under their
+ :class:`MibTableRow` parent object, each :class:`MibTableColumn` can have
+ zero or more children :class:`MibScalarInstance` objects representing SNMP
+ table cells:
+
+ MibTree
+ |
+ +----MibTableRow
+ |
+ +-------MibTableColumn
+ |
+ +------MibScalarInstance
+ +------MibScalarInstance
+ ...
+ """
#
# Subtree traversal
@@ -919,308 +2155,493 @@ class MibTableColumn(MibScalar):
return self._vars[name]
raise error.NoSuchInstanceError(name=name, idx=context.get('idx'))
- def setProtoInstance(self, protoInstance):
- self.protoInstance = protoInstance
-
# Column creation (this should probably be converted into some state
- # machine for clarity). Also, it might be a good idea to inidicate
+ # machine for clarity). Also, it might be a good idea to indicate
# defaulted cols creation in a clearer way than just a val == None.
- def createTest(self, varBind, **context):
+ def _checkSuffix(self, name):
+ # NOTE: we could have verified the index validity
+ return name[:len(self.name)]
+
+ # Column destruction
+
+ def destroyTest(self, varBind, **context):
+ """Test the ability to destroy a Managed Object Instance.
+
+ Implements the first of the multi-step workflow similar to SNMP SET
+ command processing (:RFC:`1905#section-4.2.5`).
+
+ The goal of the first phase is to make sure that requested Managed
+ Object Instance could potentially be destroyed. When multiple Managed
+ Objects Instances are modified at once (likely coming all in one SNMP
+ PDU), each of them has to run through the first (*test*) phase
+ successfully for the system to transition to the second (*commit*)
+ phase.
+
+ The role of this object in the MIB tree is non-terminal. It does not
+ access the actual Managed Object Instance, but just traverses one level
+ down the MIB tree and hands off the query to the underlying objects.
+
+ Parameters
+ ----------
+ varBind: :py:class:`~pysnmp.smi.rfc1902.ObjectType` object representing
+ new Managed Object Instance value to destroy
+
+ 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.
+
+ * `instances` (dict): user-supplied dict for temporarily holding
+ Managed Objects Instances being destroyed.
+
+ Notes
+ -----
+ The callback functions (e.g. `cbFun`) have the same signature as this method
+ where `varBind` contains the new Managed Object Instance value.
+
+ In case of an error, the `error` key in the `context` dict will contain
+ an exception object.
+ """
name, val = varBind
- # Make sure creation allowed, create a new column instance but
- # do not replace the old one
- if name == self.name:
- raise error.NoAccessError(name=name, idx=context.get('idx'))
+ (debug.logger & debug.flagIns and
+ debug.logger('%s: destroyTest(%s, %r)' % (self, name, val)))
+
+ cbFun = context['cbFun']
+
+ instances = context['instances'].setdefault(self.name, {self.ST_CREATE: {}, self.ST_DESTROY: {}})
+ idx = context['idx']
+
+ if not self._checkSuffix(name):
+ exc = error.NotWritableError(name=name, idx=context.get('idx'))
+ cbFun(varBind, **dict(context, error=exc))
+ return
acFun = context.get('acFun')
if acFun:
- if (val is not None and self.maxAccess != 'readcreate' or
- acFun('write', (name, self.syntax), **context)):
+ if self.maxAccess != 'readcreate' or acFun('write', varBind, **context):
debug.logger & debug.flagACL and debug.logger(
- 'createTest: %s=%r %s at %s' % (name, val, self.maxAccess, self.name))
- raise error.NoCreationError(name=name, idx=context.get('idx'))
+ 'destroyTest: %s=%r %s at %s' % (name, val, self.maxAccess, self.name))
+ exc = error.NotWritableError(name=name, idx=context.get('idx'))
+ cbFun(varBind, **dict(context, error=exc))
+ return
- # Create instances if either it does not yet exist (row creation)
- # or a value is passed (multiple OIDs in SET PDU)
- if val is None and name in self.__createdInstances:
- return
+ try:
+ instances[self.ST_DESTROY][idx] = instances[self.ST_CREATE].pop(idx)
- self.__createdInstances[name] = self.protoInstance(
- self.name, name[len(self.name):], self.syntax.clone()
- )
+ except KeyError:
+ pass
- self.__createdInstances[name].createTest(varBind, **context)
+ else:
+ (debug.logger & debug.flagIns and
+ debug.logger('%s: terminated columnar instance %s creation' % (self, name)))
- def createCommit(self, varBind, **context):
- name, val = varBind
+ cbFun(varBind, **context)
- # Commit new instance value
- if name in self._vars: # XXX
- if name in self.__createdInstances:
- self._vars[name].createCommit(varBind, **context)
- return
+ def destroyCommit(self, varBind, **context):
+ """Destroy Managed Object Instance.
- self.__createdInstances[name].createCommit(varBind, **context)
+ Implements the second of the multi-step workflow similar to the SNMP SET
+ command processing (:RFC:`1905#section-4.2.5`).
- # ...commit new column instance
- self._vars[name], self.__createdInstances[name] = self.__createdInstances[name], self._vars.get(name)
+ The goal of the second phase is to actually remove requested Managed
+ Object Instance from the MIB tree. When multiple Managed Objects Instances
+ are destroyed/modified at once (likely coming all in one SNMP PDU), each
+ of them has to run through the second (*commit*) phase successfully for
+ the system to transition to the third (*cleanup*) phase. If any single
+ *commit* step fails, the system transitions into the *undo* state for
+ each of Managed Objects Instances being processed at once.
- def createCleanup(self, varBind, **context):
- name, val = varBind
+ The role of this object in the MIB tree is non-terminal. It does not
+ access the actual Managed Object Instance, but just traverses one level
+ down the MIB tree and hands off the query to the underlying objects.
- # Drop previous column instance
- self.branchVersionId += 1
- if name in self.__createdInstances:
- if self.__createdInstances[name] is not None:
- self.__createdInstances[name].createCleanup(varBind, **context)
- del self.__createdInstances[name]
+ Parameters
+ ----------
+ varBind: :py:class:`~pysnmp.smi.rfc1902.ObjectType` object representing
+ new Managed Object Instance value to destroy
- elif name in self._vars:
- self._vars[name].createCleanup(varBind, **context)
+ Other Parameters
+ ----------------
+ \*\*context:
- def createUndo(self, varBind, **context):
+ Query parameters:
+
+ * `cbFun` (callable) - user-supplied callable that is invoked to
+ pass the new value of the Managed Object Instance or an error.
+
+ * `instances` (dict): user-supplied dict for temporarily holding
+ Managed Objects Instances being destroyed.
+
+ Notes
+ -----
+ The callback functions (e.g. `cbFun`) have the same signature as this
+ method where `varBind` contains the new Managed Object Instance value.
+
+ In case of an error, the `error` key in the `context` dict will contain
+ an exception object.
+ """
name, val = varBind
- # Set back previous column instance, drop the new one
- if name in self.__createdInstances:
- self._vars[name] = self.__createdInstances[name]
- del self.__createdInstances[name]
- # Remove new instance on rollback
- if self._vars[name] is None:
- del self._vars[name]
+ (debug.logger & debug.flagIns and
+ debug.logger('%s: destroyCommit(%s, %r)' % (self, name, val)))
- else:
- # Catch half-created instances (hackerish)
- try:
- self._vars[name] == 0
+ instances = context['instances'].setdefault(self.name, {self.ST_CREATE: {}, self.ST_DESTROY: {}})
+ idx = context['idx']
- except PyAsn1Error:
- del self._vars[name]
+ # NOTE: multiple names are possible in a single PDU, that could collide
+ # Therefore let's keep old object indexed by (negative) var-bind index
+ try:
+ instances[self.ST_DESTROY][-idx - 1] = self._vars.pop(name)
- else:
- self._vars[name].createUndo(varBind, **context)
+ except KeyError:
+ pass
- # Column destruction
+ cbFun = context['cbFun']
+ cbFun(varBind, **context)
- def destroyTest(self, varBind, **context):
- name, val = varBind
+ def destroyCleanup(self, varBind, **context):
+ """Finalize Managed Object Instance destruction.
- # Make sure destruction is allowed
- if name == self.name:
- raise error.NoAccessError(name=name, idx=context.get('idx'))
+ Implements the successful third step of the multi-step workflow similar to
+ the SNMP SET command processing (:RFC:`1905#section-4.2.5`).
- if name not in self._vars:
- return
+ The goal of the third (successful) phase is to finalize the destruction
+ of the Managed Object Instance. Once the system transitions into the
+ *cleanup* state, no roll back to the previous Managed Object Instance
+ state is possible.
- acFun = context.get('acFun')
- if acFun:
- if (val is not None and self.maxAccess != 'readcreate' or
- acFun('write', (name, self.syntax), **context)):
- raise error.NoAccessError(name=name, idx=context.get('idx'))
+ The role of this object in the MIB tree is non-terminal. It does not
+ access the actual Managed Object Instance, but just traverses one level
+ down the MIB tree and hands off the query to the underlying objects.
- self._vars[name].destroyTest(varBind, **context)
+ Parameters
+ ----------
+ varBind: :py:class:`~pysnmp.smi.rfc1902.ObjectType` object representing
+ new Managed Object Instance value to destroy
- def destroyCommit(self, varBind, **context):
- name, val = varBind
+ Other Parameters
+ ----------------
+ \*\*context:
- # Make a copy of column instance and take it off the tree
- if name in self._vars:
- self._vars[name].destroyCommit(varBind, **context)
- self.__destroyedInstances[name] = self._vars[name]
- del self._vars[name]
+ Query parameters:
- def destroyCleanup(self, varBind, **context):
+ * `cbFun` (callable) - user-supplied callable that is invoked to
+ pass the new value of the Managed Object Instance or an error.
+
+ * `instances` (dict): user-supplied dict for temporarily holding
+ Managed Objects Instances being destroyed.
+
+ Notes
+ -----
+ The callback functions (e.g. `cbFun`) have the same signature as this
+ method where `varBind` contains the new Managed Object Instance value.
+
+ In case of an error, the `error` key in the `context` dict will contain
+ an exception object.
+ """
name, val = varBind
- # Drop instance copy
+ (debug.logger & debug.flagIns and
+ debug.logger('%s: destroyCleanup(%s, %r)' % (self, name, val)))
+
self.branchVersionId += 1
- if name in self.__destroyedInstances:
- self.__destroyedInstances[name].destroyCleanup(varBind, **context)
- debug.logger & debug.flagIns and debug.logger('destroyCleanup: %s=%r' % (name, val))
- del self.__destroyedInstances[name]
+ instances = context['instances'].setdefault(self.name, {self.ST_CREATE: {}, self.ST_DESTROY: {}})
+ idx = context['idx']
- def destroyUndo(self, varBind, **context):
- name, val = varBind
+ instances[self.ST_DESTROY].pop(idx, None)
+ instances[self.ST_DESTROY].pop(-idx - 1, None)
- # Set back column instance
- if name in self.__destroyedInstances:
- self._vars[name] = self.__destroyedInstances[name]
- self._vars[name].destroyUndo(varBind, **context)
- del self.__destroyedInstances[name]
+ cbFun = context['cbFun']
+ cbFun(varBind, **context)
- # Set/modify column
+ def destroyUndo(self, varBind, **context):
+ """Undo Managed Object Instance destruction.
- def writeTest(self, varBind, **context):
- name, val = varBind
+ Implements the third (unsuccessful) step of the multi-step workflow
+ similar to the SNMP SET command processing (:RFC:`1905#section-4.2.5`).
- # Besides common checks, request row creation on no-instance
- try:
- # First try the instance
- MibScalar.writeTest(self, varBind, **context)
-
- # ...otherwise proceed with creating new column
- except (error.NoSuchInstanceError, error.RowCreationWanted):
- excValue = sys.exc_info()[1]
- if isinstance(excValue, error.RowCreationWanted):
- self.__rowOpWanted[name] = excValue
- else:
- self.__rowOpWanted[name] = error.RowCreationWanted()
- self.createTest(varBind, **context)
+ The goal of the third phase is to revive the Managed Object Instance
+ being destroyed. The system transitions into the *undo* state whenever
+ any of the simultaneously modified Managed Objects Instances fail on the
+ *commit* state transitioning.
- except error.RowDestructionWanted:
- self.__rowOpWanted[name] = error.RowDestructionWanted()
- self.destroyTest(varBind, **context)
+ The role of this object in the MIB tree is non-terminal. It does not
+ access the actual Managed Object Instance, but just traverses one level
+ down the MIB tree and hands off the query to the underlying objects.
- if name in self.__rowOpWanted:
- debug.logger & debug.flagIns and debug.logger(
- '%s flagged by %s=%r, exception %s' % (self.__rowOpWanted[name], name, val, sys.exc_info()[1]))
- raise self.__rowOpWanted[name]
+ Parameters
+ ----------
+ varBind: :py:class:`~pysnmp.smi.rfc1902.ObjectType` object representing
+ new Managed Object Instance value to destroy
- def __delegateWrite(self, subAction, varBind, **context):
- name, val = varBind
+ Other Parameters
+ ----------------
+ \*\*context:
- if name not in self.__rowOpWanted:
- actionFun = getattr(MibScalar, 'write' + subAction)
- return actionFun(self, varBind, **context)
+ Query parameters:
- if isinstance(self.__rowOpWanted[name], error.RowCreationWanted):
- actionFun = getattr(self, 'create' + subAction)
- return actionFun(varBind, **context)
+ * `cbFun` (callable) - user-supplied callable that is invoked to
+ pass the new value of the Managed Object Instance or an error.
- if isinstance(self.__rowOpWanted[name], error.RowDestructionWanted):
- actionFun = getattr(self, 'destroy' + subAction)
- return actionFun(varBind, **context)
+ * `instances` (dict): user-supplied dict for temporarily holding
+ Managed Objects Instances being destroyed.
- def writeCommit(self, varBind, **context):
+ Notes
+ -----
+ The callback functions (e.g. `cbFun`) have the same signature as this
+ method where `varBind` contains the new Managed Object Instance value.
+
+ In case of an error, the `error` key in the `context` dict will contain
+ an exception object.
+ """
name, val = varBind
- self.__delegateWrite('Commit', varBind, **context)
- if name in self.__rowOpWanted:
- raise self.__rowOpWanted[name]
+ (debug.logger & debug.flagIns and
+ debug.logger('%s: destroyUndo(%s, %r)' % (self, name, val)))
- def writeCleanup(self, varBind, **context):
- name, val = varBind
+ instances = context['instances'].setdefault(self.name, {self.ST_CREATE: {}, self.ST_DESTROY: {}})
+ idx = context['idx']
- self.branchVersionId += 1
+ try:
+ self._vars[name] = instances[self.ST_DESTROY].pop(-idx - 1)
- self.__delegateWrite('Cleanup', varBind, **context)
+ except KeyError:
+ self._vars.pop(name, None)
- if name in self.__rowOpWanted:
- e = self.__rowOpWanted[name]
- del self.__rowOpWanted[name]
- debug.logger & debug.flagIns and debug.logger('%s dropped by %s=%r' % (e, name, val))
- raise e
+ instances[self.ST_DESTROY].pop(idx, None)
+
+ cbFun = context['cbFun']
+ cbFun(varBind, **context)
- def writeUndo(self, varBind, **context):
- name, val = varBind
- if name in self.__rowOpWanted:
- self.__rowOpWanted[name] = error.RowDestructionWanted()
+class MibTableRow(ManagedMibObject):
+ """Managed table row MIB object.
- self.__delegateWrite('Undo', varBind, **context)
+ Represents SMI table row object (`OBJECT-TYPE`) implementing
+ management instrumentation.
- if name in self.__rowOpWanted:
- e = self.__rowOpWanted[name]
- del self.__rowOpWanted[name]
- debug.logger & debug.flagIns and debug.logger('%s dropped by %s=%r' % (e, name, val))
- raise e
+ Objects of this type can't carry any value of their own, their major
+ role is to ensure table row consistency by catching and propagating
+ columnar events (such as column creation or destruction coming from
+ :class:`RowStatus` via :class:`MibTableColumn`) across the whole row.
+ In the MIB tree, :class:`MibTableRow` objects reside right under the tree
+ top, each can have one or more :class:`MibTableColumn` objects attached:
-class MibTableRow(MibTree):
- """MIB table row (SMI 'Entry'). Manages a set of table columns.
- Implements row creation/destruction.
+ MibTree
+ |
+ +----MibTableRow
+ |
+ +-----MibTableColumn
"""
def __init__(self, name):
- MibTree.__init__(self, name)
- self.__idToIdxCache = cache.Cache()
- self.__idxToIdCache = cache.Cache()
- self.indexNames = ()
- self.augmentingRows = {}
+ ManagedMibObject.__init__(self, name)
+ self._idToIdxCache = cache.Cache()
+ self._idxToIdCache = cache.Cache()
+ self._indexNames = ()
+ self._augmentingRows = set()
# Table indices resolution. Handle almost all possible rfc1902 types
# explicitly rather than by means of isSuperTypeOf() method because
# some subtypes may be implicitly tagged what renders base tag
# unavailable.
- __intBaseTag = Integer.tagSet.getBaseTag()
- __strBaseTag = OctetString.tagSet.getBaseTag()
- __oidBaseTag = ObjectIdentifier.tagSet.getBaseTag()
- __ipaddrTagSet = IpAddress.tagSet
- __bitsBaseTag = Bits.tagSet.getBaseTag()
-
- def setFromName(self, obj, value, impliedFlag=None, parentIndices=None):
- if not value:
- raise error.SmiError('Short OID for index %r' % (obj,))
- if hasattr(obj, 'cloneFromName'):
- return obj.cloneFromName(value, impliedFlag, parentRow=self, parentIndices=parentIndices)
- baseTag = obj.getTagSet().getBaseTag()
- if baseTag == self.__intBaseTag:
- return obj.clone(value[0]), value[1:]
- elif self.__ipaddrTagSet.isSuperTagSetOf(obj.getTagSet()):
- return obj.clone('.'.join([str(x) for x in value[:4]])), value[4:]
- elif baseTag == self.__strBaseTag:
+ def oidToValue(self, syntax, identifier, impliedFlag=False, parentIndices=None):
+ """Turn SMI table instance identifier into a value object.
+
+ SNMP SMI table objects are identified by OIDs composed of columnar
+ object ID and instance index. The index part can be composed
+ from the values of one or more tabular objects.
+
+ This method takes sequence of integers, representing the tail piece
+ of a tabular object identifier, and turns it into a value object.
+
+ Parameters
+ ----------
+ syntax: :py:class:`Integer`, :py:class:`OctetString`, :py:class:`ObjectIdentifier`, :py:class:`IpAddress` or :py:class:`Bits` -
+ one of the SNMP data types that can be used in SMI table indices.
+
+ identifier: :py:class:`tuple` - tuple of integers representing the tail
+ piece of an OBJECT IDENTIFIER (i.e. tabular object instance ID)
+
+ impliedFlag: :py:class:`bool` - if `False`, the length of the
+ serialized value is expected to be present as the first integer of
+ the sequence. Otherwise the length is not included (which is
+ frequently the case for the last index in the series or a
+ fixed-length value).
+
+ Returns
+ -------
+ :py:class:`object` - Initialized instance of `syntax`
+ """
+ if not identifier:
+ raise error.SmiError('Short OID for index %r' % (syntax,))
+
+ if hasattr(syntax, 'cloneFromName'):
+ return syntax.cloneFromName(
+ identifier, impliedFlag, parentRow=self, parentIndices=parentIndices)
+
+ baseTag = syntax.getTagSet().getBaseTag()
+ if baseTag == Integer.tagSet.getBaseTag():
+ return syntax.clone(identifier[0]), identifier[1:]
+
+ elif IpAddress.tagSet.isSuperTagSetOf(syntax.getTagSet()):
+ return syntax.clone(
+ '.'.join([str(x) for x in identifier[:4]])), identifier[4:]
+
+ elif baseTag == OctetString.tagSet.getBaseTag():
# rfc1902, 7.7
if impliedFlag:
- return obj.clone(tuple(value)), ()
- elif obj.isFixedLength():
- l = obj.getFixedLength()
- return obj.clone(tuple(value[:l])), value[l:]
+ return syntax.clone(tuple(identifier)), ()
+
+ elif syntax.isFixedLength():
+ l = syntax.getFixedLength()
+ return syntax.clone(tuple(identifier[:l])), identifier[l:]
+
else:
- return obj.clone(tuple(value[1:value[0] + 1])), value[value[0] + 1:]
- elif baseTag == self.__oidBaseTag:
+ return syntax.clone(
+ tuple(identifier[1:identifier[0] + 1])), identifier[identifier[0] + 1:]
+
+ elif baseTag == ObjectIdentifier.tagSet.getBaseTag():
if impliedFlag:
- return obj.clone(value), ()
+ return syntax.clone(identifier), ()
+
else:
- return obj.clone(value[1:value[0] + 1]), value[value[0] + 1:]
+ return syntax.clone(
+ identifier[1:identifier[0] + 1]), identifier[identifier[0] + 1:]
+
# rfc2578, 7.1
- elif baseTag == self.__bitsBaseTag:
- return obj.clone(tuple(value[1:value[0] + 1])), value[value[0] + 1:]
+ elif baseTag == Bits.tagSet.getBaseTag():
+ return syntax.clone(
+ tuple(identifier[1:identifier[0] + 1])), identifier[identifier[0] + 1:]
+
else:
- raise error.SmiError('Unknown value type for index %r' % (obj,))
-
- def getAsName(self, obj, impliedFlag=None, parentIndices=None):
- if hasattr(obj, 'cloneAsName'):
- return obj.cloneAsName(impliedFlag, parentRow=self, parentIndices=parentIndices)
- baseTag = obj.getTagSet().getBaseTag()
- if baseTag == self.__intBaseTag:
- # noinspection PyRedundantParentheses
- return (int(obj),)
- elif self.__ipaddrTagSet.isSuperTagSetOf(obj.getTagSet()):
- return obj.asNumbers()
- elif baseTag == self.__strBaseTag:
- if impliedFlag or obj.isFixedLength():
+ raise error.SmiError('Unknown value type for index %r' % (syntax,))
+
+ setFromName = oidToValue
+
+ def valueToOid(self, value, impliedFlag=False, parentIndices=None):
+ """Turn value object into SMI table instance identifier.
+
+ SNMP SMI table objects are identified by OIDs composed of columnar
+ object ID and instance index. The index part can be composed
+ from the values of one or more tabular objects.
+
+ This method takes an arbitrary value object and turns it into a
+ sequence of integers representing the tail piece of a tabular
+ object identifier.
+
+ Parameters
+ ----------
+ value: one of the SNMP data types that can be used in SMI table
+ indices. Allowed types are: :py:class:`Integer`,
+ :py:class:`OctetString`, :py:class:`ObjectIdentifier`,
+ :py:class:`IpAddress` and :py:class:`Bits`.
+
+ impliedFlag: :py:class:`bool` - if `False`, the length of the
+ serialized value is included as the first integer of the sequence.
+ Otherwise the length is not included (which is frequently the
+ case for the last index in the series or a fixed-length value).
+
+ Returns
+ -------
+ :py:class:`tuple` - tuple of integers representing the tail piece
+ of an OBJECT IDENTIFIER (i.e. tabular object instance ID)
+ """
+ if hasattr(value, 'cloneAsName'):
+ return value.cloneAsName(impliedFlag, parentRow=self, parentIndices=parentIndices)
+
+ baseTag = value.getTagSet().getBaseTag()
+ if baseTag == Integer.tagSet.getBaseTag():
+ return int(value),
+
+ elif IpAddress.tagSet.isSuperTagSetOf(value.getTagSet()):
+ return value.asNumbers()
+
+ elif baseTag == OctetString.tagSet.getBaseTag():
+ if impliedFlag or value.isFixedLength():
initial = ()
else:
- initial = (len(obj),)
- return initial + obj.asNumbers()
- elif baseTag == self.__oidBaseTag:
+ initial = (len(value),)
+ return initial + value.asNumbers()
+
+ elif baseTag == ObjectIdentifier.tagSet.getBaseTag():
if impliedFlag:
- return tuple(obj)
+ return tuple(value)
else:
- return (len(obj),) + tuple(obj)
+ return (len(value),) + tuple(value)
+
# rfc2578, 7.1
- elif baseTag == self.__bitsBaseTag:
- return (len(obj),) + obj.asNumbers()
+ elif baseTag == Bits.tagSet.getBaseTag():
+ return (len(value),) + value.asNumbers()
+
else:
- raise error.SmiError('Unknown value type for index %r' % (obj,))
+ raise error.SmiError('Unknown value type for index %r' % (value,))
- # Fate sharing mechanics
+ getAsName = valueToOid
def announceManagementEvent(self, action, varBind, **context):
+ """Announce mass operation on parent table's row.
+
+ SNMP SMI provides a way to extend already existing SMI table with
+ another table. Whenever a mass operation on parent table's column
+ is performed (e.g. row creation or destruction), this operation
+ has to be propagated over all the extending tables.
+
+ This method gets invoked on parent :py:class:`MibTableRow` whenever
+ row modification is performed on the parent table.
+
+ Parameters
+ ----------
+ action: :py:class:`str` any of :py:class:`MibInstrumController`'s states
+ being applied on the parent table's row.
+
+ varBind: :py:class:`~pysnmp.smi.rfc1902.ObjectType` object representing
+ new :py:class:`RowStatus` Managed Object Instance value being set
+ on parent table row
+
+ Other Parameters
+ ----------------
+ \*\*context:
+
+ Query parameters:
+
+ * `cbFun` (callable) - user-supplied callable that is invoked once
+ all the consumers of this notifications finished with their stuff
+ or an error occurs
+
+ Notes
+ -----
+ The callback functions (e.g. `cbFun`) expects two parameters: `varBind`
+ and `**context`.
+
+ In case of an error, the `error` key in the `context` dict will contain
+ an exception object.
+ """
name, val = varBind
- # Convert OID suffix into index vals
+ cbFun = context['cbFun']
+
+ if not self._augmentingRows:
+ cbFun(varBind, **context)
+ return
+
+ # Convert OID suffix into index values
instId = name[len(self.name) + 1:]
baseIndices = []
indices = []
- for impliedFlag, modName, symName in self.indexNames:
+ for impliedFlag, modName, symName in self._indexNames:
mibObj, = mibBuilder.importSymbols(modName, symName)
- syntax, instId = self.setFromName(mibObj.syntax, instId,
- impliedFlag, indices)
+ syntax, instId = self.oidToValue(mibObj.syntax, instId,
+ impliedFlag, indices)
if self.name == mibObj.name[:-1]:
baseIndices.append((mibObj.name, syntax))
@@ -1228,135 +2649,601 @@ class MibTableRow(MibTree):
indices.append(syntax)
if instId:
- raise error.SmiError('Excessive instance identifier sub-OIDs left at %s: %s' % (self, instId))
+ exc = error.SmiError('Excessive instance identifier sub-OIDs left at %s: %s' % (self, instId))
+ cbFun(varBind, **dict(context, error=exc))
+ return
if not baseIndices:
+ cbFun(varBind, **context)
return
- for modName, mibSym in self.augmentingRows:
+ count = [len(self._augmentingRows)]
+
+ def _cbFun(varBind, **context):
+ count[0] -= 1
+
+ if not count[0]:
+ cbFun(varBind, **context)
+
+ for modName, mibSym in self._augmentingRows:
mibObj, = mibBuilder.importSymbols(modName, mibSym)
+ mibObj.receiveManagementEvent(action, (baseIndices, val), **dict(context, cbFun=_cbFun))
+
debug.logger & debug.flagIns and debug.logger('announceManagementEvent %s to %s' % (action, mibObj))
- mibObj.receiveManagementEvent(action, (baseIndices, val), **context)
def receiveManagementEvent(self, action, varBind, **context):
+ """Apply mass operation on extending table's row.
+
+ SNMP SMI provides a way to extend already existing SMI table with
+ another table. Whenever a mass operation on parent table's column
+ is performed (e.g. row creation or destruction), this operation
+ has to be propagated over all the extending tables.
+
+ This method gets invoked on the extending :py:class:`MibTableRow`
+ object whenever row modification is performed on the parent table.
+
+ Parameters
+ ----------
+ action: :py:class:`str` any of :py:class:`MibInstrumController`'s states
+ to apply on extending table's row.
+
+ varBind: :py:class:`~pysnmp.smi.rfc1902.ObjectType` object representing
+ new :py:class:`RowStatus` Managed Object Instance value being set
+ on parent table row
+
+ Other Parameters
+ ----------------
+ \*\*context:
+
+ Query parameters:
+
+ * `cbFun` (callable) - user-supplied callable that is invoked once
+ the requested operation has been applied on all columns of the
+ extending table or an error occurs
+
+ Notes
+ -----
+ The callback functions (e.g. `cbFun`) expects two parameters: `varBind`
+ and `**context`.
+
+ In case of an error, the `error` key in the `context` dict will contain
+ an exception object.
+ """
baseIndices, val = varBind
# The default implementation supports one-to-one rows dependency
- newSuffix = ()
+ instId = ()
+
# Resolve indices intersection
- for impliedFlag, modName, symName in self.indexNames:
+ for impliedFlag, modName, symName in self._indexNames:
mibObj, = mibBuilder.importSymbols(modName, symName)
parentIndices = []
for name, syntax in baseIndices:
if name == mibObj.name:
- newSuffix += self.getAsName(syntax, impliedFlag, parentIndices)
+ instId += self.valueToOid(syntax, impliedFlag, parentIndices)
parentIndices.append(syntax)
- if newSuffix:
+ if instId:
debug.logger & debug.flagIns and debug.logger(
- 'receiveManagementEvent %s for suffix %s' % (action, newSuffix))
- self.__manageColumns(action, (), (newSuffix, val), **context)
+ 'receiveManagementEvent %s for suffix %s' % (action, instId))
+
+ self._manageColumns(action, (self.name + (0,) + instId, val), **context)
+
+ def registerAugmentation(self, *names):
+ """Register table extension.
+
+ SNMP SMI provides a way to extend already existing SMI table with
+ another table. This method registers dependent (extending) table
+ (or type :py:class:`MibTableRow`) to already existing table.
- def registerAugmentions(self, *names):
- for modName, symName in names:
- if (modName, symName) in self.augmentingRows:
+ Whenever a row of the parent table is created or destroyed, the
+ same mass columnar operation is applied on the extending table
+ row.
+
+ Parameters
+ ----------
+ names: :py:class:`tuple`
+ One or more `tuple`'s of `str` referring to the extending table by
+ MIB module name (first `str`) and `:py:class:`MibTableRow` object
+ name (second `str`).
+ """
+ for name in names:
+ if name in self._augmentingRows:
raise error.SmiError(
- 'Row %s already augmented by %s::%s' % (self.name, modName, symName)
+ 'Row %s already augmented by %s::%s' % (self.name, name[0], name[1])
)
- self.augmentingRows[(modName, symName)] = 1
+
+ self._augmentingRows.add(name)
+
return self
+ registerAugmentions = registerAugmentation
+
def setIndexNames(self, *names):
for name in names:
- self.indexNames += (name,)
+ self._indexNames += (name,)
return self
def getIndexNames(self):
- return self.indexNames
+ return self._indexNames
- def __manageColumns(self, action, excludeName, varBind, **context):
- nameSuffix, val = varBind
+ def _manageColumns(self, action, varBind, **context):
+ """Apply a management action on all columns
+
+ Parameters
+ ----------
+ action: :py:class:`str` any of :py:class:`MibInstrumController`'s states
+ to apply on all columns but the one passed in `varBind`
+
+ varBind: :py:class:`~pysnmp.smi.rfc1902.ObjectType` object representing
+ new :py:class:`RowStatus` Managed Object Instance value being set
+ on table row
+
+ Other Parameters
+ ----------------
+ \*\*context:
+
+ Query parameters:
+
+ * `cbFun` (callable) - user-supplied callable that is invoked once
+ all columns have been processed or an error occurs
+
+ Notes
+ -----
+ The callback functions (e.g. `cbFun`) expects two parameters: `varBind`
+ and `**context`.
+
+ In case of an error, the `error` key in the `context` dict will contain
+ an exception object.
+
+ Assumes that row consistency check has been triggered by RowStatus
+ columnar object transition into `active` state.
+ """
+ name, val = varBind
+
+ (debug.logger & debug.flagIns and
+ debug.logger('%s: _manageColumns(%s, %s, %r)' % (self, action, name, val)))
+
+ cbFun = context['cbFun']
+
+ colLen = len(self.name) + 1
# Build a map of index names and values for automatic initialization
indexVals = {}
- instId = nameSuffix
+
+ instId = name[colLen:]
indices = []
- for impliedFlag, modName, symName in self.indexNames:
+
+ for impliedFlag, modName, symName in self._indexNames:
mibObj, = mibBuilder.importSymbols(modName, symName)
- syntax, instId = self.setFromName(mibObj.syntax, instId,
- impliedFlag, indices)
+ syntax, instId = self.oidToValue(mibObj.syntax, instId, impliedFlag, indices)
indexVals[mibObj.name] = syntax
indices.append(syntax)
- for name, var in self._vars.items():
- if name == excludeName:
- continue
+ count = [len(self._vars)]
+
+ if name[:colLen] in self._vars:
+ count[0] -= 1
- actionFun = getattr(var, action)
+ def _cbFun(varBind, **context):
+ count[0] -= 1
- if name in indexVals:
- # NOTE(etingof): disable VACM call
- _context = context.copy()
- _context.pop('acFun', None)
+ if not count[0]:
+ cbFun(varBind, **context)
+
+ for colName, colObj in self._vars.items():
+ acFun = context.get('acFun')
+
+ if colName in indexVals:
+ colInstanceValue = indexVals[colName]
+ # Index column is usually read-only
+ acFun = None
+
+ elif name[:colLen] == colName:
+ # status column is following `write` path
+ continue
- actionFun((name + nameSuffix, indexVals[name]), **_context)
else:
- actionFun((name + nameSuffix, val), **context)
+ colInstanceValue = None
+
+ actionFun = getattr(colObj, action)
- debug.logger & debug.flagIns and debug.logger('__manageColumns: action %s name %s suffix %s %svalue %r' % (
- action, name, nameSuffix, name in indexVals and "index " or "", indexVals.get(name, val)))
+ colInstanceName = colName + name[colLen:]
- def __delegate(self, subAction, varBind, **context):
+ actionFun((colInstanceName, colInstanceValue),
+ **dict(context, acFun=acFun, cbFun=_cbFun))
+
+ debug.logger & debug.flagIns and debug.logger(
+ '_manageColumns: action %s name %s instance %s %svalue %r' % (
+ action, name, instId, name in indexVals and "index " or "", indexVals.get(name, val)))
+
+ def _checkColumns(self, varBind, **context):
+ """Check the consistency of all columns.
+
+ Parameters
+ ----------
+ varBind: :py:class:`~pysnmp.smi.rfc1902.ObjectType` object representing
+ new :py:class:`RowStatus` Managed Object Instance value being set
+ on table row
+
+ 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.
+
+ Notes
+ -----
+ The callback functions (e.g. `cbFun`) have the same signature as this
+ method where `varBind` contains the new Managed Object Instance value.
+
+ In case of an error, the `error` key in the `context` dict will contain
+ an exception object.
+
+ Assume that row consistency check has been triggered by RowStatus
+ columnar object transition into `active` state.
+ """
name, val = varBind
- # Relay operation request to column, expect row operation request.
- rowIsActive = False
+ (debug.logger & debug.flagIns and
+ debug.logger('%s: _checkColumns(%s, %r)' % (self, name, val)))
- try:
- writeFun = getattr(self.getBranch(name, **context), 'write' + subAction)
- writeFun(varBind, **context)
+ cbFun = context['cbFun']
- except error.RowCreationWanted:
- createAction = 'create' + subAction
+ # RowStatus != active
+ if val != 1:
+ cbFun(varBind, **context)
+ return
- self.__manageColumns(
- createAction, name[:len(self.name) + 1], (name[len(self.name) + 1:], None), **context
- )
+ count = [len(self._vars)]
- self.announceManagementEvent(createAction, (name, None), **context)
+ def _cbFun(varBind, **context):
+ count[0] -= 1
- # watch for RowStatus == 'stActive'
- rowIsActive = sys.exc_info()[1].get('syntax', 0) == 1
+ name, val = varBind
- except error.RowDestructionWanted:
- destroyAction = 'destroy' + subAction
+ if count[0] >= 0:
+ exc = context.get('error')
+ if exc or not val.hasValue():
+ count[0] = -1 # ignore the rest of callbacks
+ exc = error.InconsistentValueError(msg='Inconsistent column %s: %s' % (name, exc))
+ cbFun(varBind, **dict(context, error=exc))
+ return
- self.__manageColumns(
- destroyAction, name[:len(self.name) + 1], (name[len(self.name) + 1:], None), **context
- )
+ if not count[0]:
+ cbFun(varBind, **context)
+ return
- self.announceManagementEvent(destroyAction, (name, None), **context)
+ colLen = len(self.name) + 1
- return rowIsActive
+ for colName, colObj in self._vars.items():
+ instName = colName + name[colLen:]
+
+ colObj.readGet((instName, None), **dict(context, cbFun=_cbFun))
+
+ debug.logger & debug.flagIns and debug.logger(
+ '%s: _checkColumns: checking instance %s' % (self, instName))
def writeTest(self, varBind, **context):
- self.__delegate('Test', varBind, **context)
+ """Test the ability to create/destroy or modify Managed Object Instance.
+
+ Implements the first of the multi-step workflow of the SNMP SET
+ command processing (:RFC:`1905#section-4.2.5`). On top of that,
+ handles possible SMI table management events i.e. row creation
+ and destruction via :class:`RowStatus` columnar object.
+
+ The goal of the first phase is to make sure that requested Managed
+ Object Instance could potentially be changed or created or destroyed.
+ When multiple Managed Objects Instances are modified at once (likely
+ coming all in one SNMP PDU), each of them has to run through the first
+ (*test*) phase successfully for the system to transition to the second
+ (*commit*) phase.
+
+ The role of this object in the MIB tree is non-terminal. It does not
+ access the actual Managed Object Instance, but just traverses one level
+ down the MIB tree and hands off the query to the underlying objects.
+
+ Parameters
+ ----------
+ varBind: :py:class:`~pysnmp.smi.rfc1902.ObjectType` object representing
+ new Managed Object Instance value to set
+
+ 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 an error, the `error` key in the `context` dict will contain
+ an exception object.
+ """
+ name, val = varBind
+
+ (debug.logger & debug.flagIns and
+ debug.logger('%s: writeTest(%s, %r)' % (self, name, val)))
+
+ cbFun = context['cbFun']
+
+ instances = context['instances'].setdefault(self.name, {self.ST_CREATE: {}, self.ST_DESTROY: {}})
+ idx = context['idx']
+
+ def _cbFun(varBind, **context):
+ exc = context.get('error')
+ if exc:
+ instances[idx] = exc
+
+ if isinstance(exc, error.RowCreationWanted):
+ def _cbFun(*args, **context):
+ self.announceManagementEvent('createTest', varBind, **dict(context, cbFun=cbFun))
+
+ self._manageColumns('createTest', varBind, **dict(context, cbFun=_cbFun, error=None))
+ return
+
+ if isinstance(exc, error.RowDestructionWanted):
+ def _cbFun(*args, **context):
+ self.announceManagementEvent('destroyTest', varBind, **dict(context, cbFun=cbFun))
+
+ self._manageColumns('destroyTest', varBind, **dict(context, cbFun=_cbFun, error=None))
+ return
+
+ if isinstance(exc, error.RowConsistencyWanted):
+ context['error'] = None
+
+ cbFun(varBind, **context)
+
+ ManagedMibObject.writeTest(self, varBind, **dict(context, cbFun=_cbFun))
def writeCommit(self, varBind, **context):
+ """Create/destroy or modify Managed Object Instance.
+
+ Implements the second of the multi-step workflow of the SNMP SET
+ command processing (:RFC:`1905#section-4.2.5`). On top of that,
+ handles possible SMI table management events i.e. row creation
+ and destruction via :class:`RowStatus` columnar object.
+
+ The goal of the second phase is to actually modify the requested Managed
+ Object Instance. When multiple Managed Objects Instances are modified at
+ once (likely coming all in one SNMP PDU), each of them has to run through
+ the second (*commit*) phase successfully for the system to transition to
+ the third (*cleanup*) phase. If any single *commit* step fails, the system
+ transitions into the *undo* state for each of Managed Objects Instances
+ being processed at once.
+
+ The role of this object in the MIB tree is non-terminal. It does not
+ access the actual Managed Object Instance, but just traverses one level
+ down the MIB tree and hands off the query to the underlying objects.
+
+ Parameters
+ ----------
+ varBind: :py:class:`~pysnmp.smi.rfc1902.ObjectType` object representing
+ new Managed Object Instance value to set
+
+ 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.
+
+ * `instances` (dict): user-supplied dict for temporarily holding
+ the values of the Managed Objects Instances being modified.
+
+ Notes
+ -----
+ The callback functions (e.g. `cbFun`) have the same signature
+ as this method where `varBind` contains the new Managed Object Instance
+ value.
+
+ In case of an error, the `error` key in the `context` dict will contain
+ an exception object.
+ """
name, val = varBind
- rowIsActive = self.__delegate('Commit', varBind, **context)
- if rowIsActive:
- for mibNode in self._vars.values():
- colNode = mibNode.getNode(mibNode.name + name[len(self.name) + 1:], **context)
- if not colNode.syntax.hasValue():
- raise error.InconsistentValueError(msg='Row consistency check failed for %r' % colNode)
+
+ (debug.logger & debug.flagIns and
+ debug.logger('%s: writeCommit(%s, %r)' % (self, name, val)))
+
+ cbFun = context['cbFun']
+
+ instances = context['instances'].setdefault(self.name, {self.ST_CREATE: {}, self.ST_DESTROY: {}})
+ idx = context['idx']
+
+ def _cbFun(varBind, **context):
+ if idx in instances:
+ exc = instances[idx]
+ if isinstance(exc, error.RowCreationWanted):
+
+ def _cbFun(*args, **context):
+ exc = context.get('error')
+ if exc:
+ cbFun(varBind, **context)
+ return
+
+ def _cbFun(*args, **context):
+ self.announceManagementEvent('createCommit', varBind, **dict(context, cbFun=cbFun))
+
+ self._checkColumns(varBind, **dict(context, cbFun=_cbFun))
+
+ self._manageColumns('createCommit', varBind, **dict(context, cbFun=_cbFun))
+ return
+
+ if isinstance(exc, error.RowDestructionWanted):
+ def _cbFun(*args, **context):
+ self.announceManagementEvent('destroyCommit', varBind, **dict(context, cbFun=cbFun))
+
+ self._manageColumns('destroyCommit', varBind, **dict(context, cbFun=_cbFun))
+ return
+
+ if isinstance(exc, error.RowConsistencyWanted):
+ self._checkColumns(varBind, **dict(context, cbFun=cbFun))
+ return
+
+ cbFun(varBind, **context)
+
+ ManagedMibObject.writeCommit(self, varBind, **dict(context, cbFun=_cbFun))
def writeCleanup(self, varBind, **context):
- self.branchVersionId += 1
- self.__delegate('Cleanup', varBind, **context)
+ """Finalize Managed Object Instance modification.
+
+ Implements the successful third step of the multi-step workflow of the
+ SNMP SET command processing (:RFC:`1905#section-4.2.5`). On top of that,
+ handles possible SMI table management events i.e. row creation and
+ destruction via :class:`RowStatus` columnar object.
+
+ The goal of the third (successful) phase is to seal the new state of the
+ requested Managed Object Instance. Once the system transition into the
+ *cleanup* state, no roll back to the previous Managed Object Instance
+ state is possible.
+
+ The role of this object in the MIB tree is non-terminal. It does not
+ access the actual Managed Object Instance, but just traverses one level
+ down the MIB tree and hands off the query to the underlying objects.
+
+ Parameters
+ ----------
+ varBind: :py:class:`~pysnmp.smi.rfc1902.ObjectType` object representing
+ new Managed Object Instance value to set
+
+ 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.
+
+ * `instances` (dict): user-supplied dict for temporarily holding
+ the values of the Managed Objects Instances being modified.
+
+ Notes
+ -----
+ The callback functions (e.g. `cbFun`) have the same signature as this
+ method where `varBind` contains the new Managed Object Instance value.
+
+ In case of an error, the `error` key in the `context` dict will contain
+ an exception object.
+ """
+ name, val = varBind
+
+ (debug.logger & debug.flagIns and
+ debug.logger('%s: writeCleanup(%s, %r)' % (self, name, val)))
+
+ cbFun = context['cbFun']
+
+ instances = context['instances'].setdefault(self.name, {self.ST_CREATE: {}, self.ST_DESTROY: {}})
+ idx = context['idx']
+
+ def _cbFun(varBind, **context):
+ if idx in instances:
+ exc = instances.pop(idx)
+ if isinstance(exc, error.RowCreationWanted):
+ def _cbFun(*args, **context):
+ self.announceManagementEvent('createCleanup', varBind, **dict(context, cbFun=cbFun))
+
+ self._manageColumns('createCleanup', varBind, **dict(context, cbFun=_cbFun))
+ return
+
+ if isinstance(exc, error.RowDestructionWanted):
+ def _cbFun(*args, **context):
+ self.announceManagementEvent('destroyCleanup', varBind, **dict(context, cbFun=cbFun))
+
+ self._manageColumns('destroyCleanup', varBind, **dict(context, cbFun=_cbFun))
+ return
+
+ cbFun(varBind, **context)
+
+ ManagedMibObject.writeCleanup(self, varBind, **dict(context, cbFun=_cbFun))
def writeUndo(self, varBind, **context):
- self.__delegate('Undo', varBind, **context)
+ """Undo Managed Object Instance modification.
+
+ Implements the third (unsuccessful) step of the multi-step workflow
+ of the SNMP SET command processing (:RFC:`1905#section-4.2.5`). On top
+ of that, handles possible SMI table management events i.e. row creation
+ and destruction via :class:`RowStatus` columnar object.
+
+ The goal of the third phase is to roll the Managed Object Instance
+ being modified back into its previous state. The system transitions
+ into the *undo* state whenever any of the simultaneously modified
+ Managed Objects Instances fail on the *commit* state transitioning.
+
+ The role of this object in the MIB tree is non-terminal. It does not
+ access the actual Managed Object Instance, but just traverses one level
+ down the MIB tree and hands off the query to the underlying objects.
+
+ Parameters
+ ----------
+ varBind: :py:class:`~pysnmp.smi.rfc1902.ObjectType` object representing
+ new Managed Object Instance value to set
+
+ 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.
+
+ * `instances` (dict): user-supplied dict for temporarily holding
+ the values of the Managed Objects Instances being modified.
+
+ Notes
+ -----
+ The callback functions (e.g. `cbFun`) have the same signature as this
+ method where `varBind` contains the new Managed Object Instance value.
+
+ In case of an error, the `error` key in the `context` dict will contain
+ an exception object.
+ """
+ name, val = varBind
+
+ (debug.logger & debug.flagIns and
+ debug.logger('%s: writeUndo(%s, %r)' % (self, name, val)))
+
+ cbFun = context['cbFun']
+
+ instances = context['instances'].setdefault(self.name, {self.ST_CREATE: {}, self.ST_DESTROY: {}})
+ idx = context['idx']
+
+ def _cbFun(varBind, **context):
+ if idx in instances:
+ exc = instances.pop(idx)
+ if isinstance(exc, error.RowCreationWanted):
+ def _cbFun(*args, **context):
+ self.announceManagementEvent('createUndo', varBind, **dict(context, cbFun=cbFun))
+
+ self._manageColumns('createUndo', varBind, **dict(context, cbFun=_cbFun))
+ return
+
+ if isinstance(exc, error.RowDestructionWanted):
+ def _cbFun(*args, **context):
+ self.announceManagementEvent('destroyUndo', varBind, **dict(context, cbFun=cbFun))
+
+ self._manageColumns('destroyUndo', varBind, **dict(context, cbFun=_cbFun))
+ return
+
+ cbFun(varBind, **context)
+
+ ManagedMibObject.writeUndo(self, varBind, **dict(context, cbFun=_cbFun))
# Table row management
@@ -1369,14 +3256,14 @@ class MibTableRow(MibTree):
def getIndicesFromInstId(self, instId):
"""Return index values for instance identification"""
- if instId in self.__idToIdxCache:
- return self.__idToIdxCache[instId]
+ if instId in self._idToIdxCache:
+ return self._idToIdxCache[instId]
indices = []
- for impliedFlag, modName, symName in self.indexNames:
+ for impliedFlag, modName, symName in self._indexNames:
mibObj, = mibBuilder.importSymbols(modName, symName)
try:
- syntax, instId = self.setFromName(mibObj.syntax, instId, impliedFlag, indices)
+ syntax, instId = self.oidToValue(mibObj.syntax, instId, impliedFlag, indices)
except PyAsn1Error:
debug.logger & debug.flagIns and debug.logger('error resolving table indices at %s, %s: %s' % (self.__class__.__name__, instId, sys.exc_info()[1]))
indices = [instId]
@@ -1392,14 +3279,14 @@ class MibTableRow(MibTree):
)
indices = tuple(indices)
- self.__idToIdxCache[instId] = indices
+ self._idToIdxCache[instId] = indices
return indices
def getInstIdFromIndices(self, *indices):
"""Return column instance identification from indices"""
try:
- return self.__idxToIdCache[indices]
+ return self._idxToIdCache[indices]
except TypeError:
cacheable = False
except KeyError:
@@ -1407,16 +3294,16 @@ class MibTableRow(MibTree):
idx = 0
instId = ()
parentIndices = []
- for impliedFlag, modName, symName in self.indexNames:
+ for impliedFlag, modName, symName in self._indexNames:
if idx >= len(indices):
break
mibObj, = mibBuilder.importSymbols(modName, symName)
syntax = mibObj.syntax.clone(indices[idx])
- instId += self.getAsName(syntax, impliedFlag, parentIndices)
+ instId += self.valueToOid(syntax, impliedFlag, parentIndices)
parentIndices.append(syntax)
idx += 1
if cacheable:
- self.__idxToIdCache[indices] = instId
+ self._idxToIdCache[indices] = instId
return instId
# Table access by index
@@ -1436,19 +3323,34 @@ class MibTableRow(MibTree):
return tuple(instNames)
-class MibTable(MibTree):
- """MIB table. Manages a set of TableRow's"""
+class MibTable(ManagedMibObject):
+ """Managed MIB table object.
- def __init__(self, name):
- MibTree.__init__(self, name)
+ Represents SMI table object (`OBJECT-TYPE`) implementing
+ management instrumentation.
+
+ Objects of this type can't carry any value of their own and do not play
+ any part in table management.
+
+ In the MIB tree, :class:`MibTable` objects reside right under the tree
+ top and do not have any children.
+
+ MibTree
+ |
+ +----MibTable
+ |
+ +----MibTableRow
+ |
+ +-----MibTableColumn
+ """
zeroDotZero = ObjectIdentity((0, 0))
# OID tree
-itu_t = MibTree((0,)).setLabel('itu-t')
+itu_t = MibScalar((0,)).setLabel('itu-t')
iso = MibTree((1,))
-joint_iso_itu_t = MibTree((2,)).setLabel('joint-iso-itu-t')
+#joint_iso_itu_t = MibScalar((2,)).setLabel('joint-iso-itu-t')
org = MibIdentifier(iso.name + (3,))
dod = MibIdentifier(org.name + (6,))
internet = MibIdentifier(dod.name + (1,))
@@ -1478,7 +3380,7 @@ mibBuilder.exportSymbols(
MibIdentifier=MibIdentifier, MibTree=MibTree,
MibTableColumn=MibTableColumn, MibTableRow=MibTableRow,
MibTable=MibTable, zeroDotZero=zeroDotZero,
- itu_t=itu_t, iso=iso, joint_iso_itu_t=joint_iso_itu_t, org=org, dod=dod,
+ itu_t=itu_t, iso=iso, org=org, dod=dod,
internet=internet, directory=directory, mgmt=mgmt, mib_2=mib_2,
transmission=transmission, experimental=experimental, private=private,
enterprises=enterprises, security=security, snmpV2=snmpV2,
diff --git a/pysnmp/smi/mibs/SNMPv2-TC.py b/pysnmp/smi/mibs/SNMPv2-TC.py
index c07e7e2c..22a78874 100644
--- a/pysnmp/smi/mibs/SNMPv2-TC.py
+++ b/pysnmp/smi/mibs/SNMPv2-TC.py
@@ -459,18 +459,18 @@ class RowStatus(TextualConvention, Integer):
(stCreateAndGo, stNotInService): (InconsistentValueError, stNotInService),
(stCreateAndGo, stActive): (InconsistentValueError, stActive),
#
- (stCreateAndWait, stNotExists): (RowCreationWanted, stActive),
+ (stCreateAndWait, stNotExists): (RowCreationWanted, stNotReady),
(stCreateAndWait, stNotReady): (InconsistentValueError, stNotReady),
(stCreateAndWait, stNotInService): (InconsistentValueError, stNotInService),
(stCreateAndWait, stActive): (InconsistentValueError, stActive),
#
(stActive, stNotExists): (InconsistentValueError, stNotExists),
- (stActive, stNotReady): (InconsistentValueError, stNotReady),
- (stActive, stNotInService): (None, stActive),
+ (stActive, stNotReady): (RowConsistencyWanted, stActive),
+ (stActive, stNotInService): (RowConsistencyWanted, stActive),
(stActive, stActive): (None, stActive),
#
(stNotInService, stNotExists): (InconsistentValueError, stNotExists),
- (stNotInService, stNotReady): (InconsistentValueError, stNotReady),
+ (stNotInService, stNotReady): (None, stNotReady),
(stNotInService, stNotInService): (None, stNotInService),
(stNotInService, stActive): (None, stActive),
#