SET Handler
The SetHandler class processes SNMP SET requests with proper test/commit/undo semantics as defined by the AgentX protocol.
How SNMP SET Works
SNMP SET is a two-phase commit:
- TestSet - Validate all values (can reject)
- CommitSet - Apply the changes
- UndoSet - Rollback if any commit fails
- CleanupSet - Release resources
This ensures atomicity when setting multiple values in one request.
Basic Usage
from snmpkit.agent import SetHandler
class ConfigHandler(SetHandler):
async def test(self, oid: str, value) -> None:
"""Validate the SET request. Raise exception to reject."""
if oid == "1.0" and len(str(value)) > 255:
raise ValueError("Value too long")
async def commit(self, oid: str, value) -> None:
"""Apply the value. Called after successful test."""
if oid == "1.0":
await save_config("hostname", str(value))
async def undo(self, oid: str) -> None:
"""Rollback if commit failed elsewhere in transaction."""
if oid == "1.0":
await restore_config("hostname")
async def cleanup(self, oid: str) -> None:
"""Cleanup after transaction (success or failure)."""
passRegistration
Register a SetHandler separately from your Updater:
from snmpkit.agent import Agent, Updater, SetHandler
class ConfigUpdater(Updater):
async def update(self):
hostname = await get_config("hostname")
self.set_OCTETSTRING("1.0", hostname)
class ConfigSetHandler(SetHandler):
async def test(self, oid, value):
pass # Validate
async def commit(self, oid, value):
if oid == "1.0":
await set_config("hostname", str(value))
agent = Agent()
agent.register("1.3.6.1.4.1.12345.1", ConfigUpdater())
agent.register_set("1.3.6.1.4.1.12345.1", ConfigSetHandler())Method Reference
test(oid, value)
async def test(self, oid: str, value: Any) -> NoneCalled during the TestSet phase. Validate the value and raise an exception to reject:
async def test(self, oid, value):
if oid == "1.0":
if not isinstance(value, str):
raise TypeError("Expected string")
if len(value) > 64:
raise ValueError("Hostname too long (max 64 chars)")
if not value.isalnum():
raise ValueError("Hostname must be alphanumeric")If test() raises an exception, the entire SET transaction is rejected. No commit() or undo() will be called.
commit(oid, value)
async def commit(self, oid: str, value: Any) -> NoneCalled after all tests pass. Apply the change:
async def commit(self, oid, value):
if oid == "1.0":
self._old_hostname = await get_config("hostname")
await set_config("hostname", str(value))undo(oid)
async def undo(self, oid: str) -> NoneCalled if any commit() fails in a multi-value SET. Rollback your change:
async def undo(self, oid):
if oid == "1.0" and hasattr(self, '_old_hostname'):
await set_config("hostname", self._old_hostname)cleanup(oid)
async def cleanup(self, oid: str) -> NoneCalled after the transaction completes (success or failure). Release any resources:
async def cleanup(self, oid):
if hasattr(self, '_old_hostname'):
del self._old_hostnameComplete Example
import snmpkit
from snmpkit.agent import Agent, Updater, SetHandler
# In-memory config store
config = {
"hostname": "localhost",
"port": 8080,
}
class ConfigUpdater(Updater):
async def update(self):
self.set_OCTETSTRING("1.0", config["hostname"])
self.set_INTEGER("2.0", config["port"])
class ConfigSetHandler(SetHandler):
def __init__(self):
super().__init__()
self._rollback = {}
async def test(self, oid, value):
if oid == "1.0":
if len(str(value)) > 64:
raise ValueError("Hostname too long")
elif oid == "2.0":
port = int(value)
if not (1 <= port <= 65535):
raise ValueError("Port must be 1-65535")
async def commit(self, oid, value):
if oid == "1.0":
self._rollback["hostname"] = config["hostname"]
config["hostname"] = str(value)
elif oid == "2.0":
self._rollback["port"] = config["port"]
config["port"] = int(value)
async def undo(self, oid):
if oid == "1.0" and "hostname" in self._rollback:
config["hostname"] = self._rollback["hostname"]
elif oid == "2.0" and "port" in self._rollback:
config["port"] = self._rollback["port"]
async def cleanup(self, oid):
self._rollback.clear()
async def main():
agent = Agent()
agent.register("1.3.6.1.4.1.12345.1", ConfigUpdater())
agent.register_set("1.3.6.1.4.1.12345.1", ConfigSetHandler())
await agent.start()
snmpkit.run(main())Test with snmpset:
# Set hostname
snmpset -v2c -c private localhost 1.3.6.1.4.1.12345.1.1.0 s "newhost"
# Set port
snmpset -v2c -c private localhost 1.3.6.1.4.1.12345.1.2.0 i 9090Make sure your snmpd.conf allows write access with a community like private.
Contexts
SET handlers support contexts just like updaters:
agent.register_set(
"1.3.6.1.4.1.12345.1",
TenantConfigHandler("tenant-a"),
context="tenant-a",
)