Skip to content

Commit 5c40128

Browse files
committed
snmpagent: Implement an asyncio-based SNMP trap infrastructure supporting link up/down, PSU, and fan traps
1 parent 6dac47c commit 5c40128

File tree

8 files changed

+831
-12
lines changed

8 files changed

+831
-12
lines changed

src/ax_interface/agent.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@
22

33
from .mib import MIBTable, MIBMeta
44
from .socket_io import SocketManager
5+
from .trap import TrapInfra
56

67
# how long to wait before forcibly killing background task(s) during the shutdown procedure.
78
BACKGROUND_WAIT_TIMEOUT = 10 # seconds
89

910

1011
class Agent:
11-
def __init__(self, mib_cls, enable_dynamic_frequency, update_frequency, loop):
12+
def __init__(self, mib_cls, enable_dynamic_frequency, update_frequency, loop, trap_handlers=None):
1213
if not type(mib_cls) is MIBMeta:
1314
raise ValueError("Expected a class with type: {}".format(MIBMeta))
1415

@@ -19,8 +20,14 @@ def __init__(self, mib_cls, enable_dynamic_frequency, update_frequency, loop):
1920
self.oid_updaters_enabled = asyncio.Event()
2021
self.stopped = asyncio.Event()
2122

23+
# Initialize our Trap infra
24+
self.trap_infra = TrapInfra(loop, trap_handlers)
25+
2226
# Initialize our MIB
23-
self.mib_table = MIBTable(mib_cls, enable_dynamic_frequency, update_frequency)
27+
if trap_handlers:
28+
self.mib_table = MIBTable(mib_cls, enable_dynamic_frequency, update_frequency, self.trap_infra)
29+
else:
30+
self.mib_table = MIBTable(mib_cls, enable_dynamic_frequency, update_frequency)
2431

2532
# containers
2633
self.socket_mgr = SocketManager(self.mib_table, self.run_enabled, self.loop)
@@ -54,6 +61,8 @@ async def run_in_event_loop(self):
5461
async def shutdown(self):
5562
# allow the agent to quit
5663
self.run_enabled.clear()
64+
# shutdown trap infra
65+
await self.trap_infra.shutdown()
5766
# close the socket
5867
self.socket_mgr.close()
5968
# wait for the agent to signal back

src/ax_interface/mib.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -313,14 +313,16 @@ class MIBTable(dict):
313313

314314
def __init__(self, mib_cls,
315315
enable_dynamic_frequency=DEFAULT_ENABLE_DYNAMIC_FREQUENCY,
316-
update_frequency=DEFAULT_UPDATE_FREQUENCY):
316+
update_frequency=DEFAULT_UPDATE_FREQUENCY,
317+
trap_infra_obj=None):
317318
if type(mib_cls) is not MIBMeta:
318319
raise ValueError("Supplied object is not a MIB class instance.")
319320
super().__init__(getattr(mib_cls, MIBMeta.KEYSTORE))
320321
self.enable_dynamic_frequency = enable_dynamic_frequency
321322
self.update_frequency = update_frequency
322323
self.updater_instances = getattr(mib_cls, MIBMeta.UPDATERS)
323324
self.prefixes = getattr(mib_cls, MIBMeta.PREFIXES)
325+
self.trap_infra_obj = trap_infra_obj
324326

325327
@staticmethod
326328
def _done_background_task_callback(fut):
@@ -340,6 +342,8 @@ def start_background_tasks(self, event):
340342
fut = asyncio.ensure_future(updater.start())
341343
fut.add_done_callback(MIBTable._done_background_task_callback)
342344
tasks.append(fut)
345+
if self.trap_infra_obj:
346+
asyncio.create_task(self.trap_infra_obj.db_listener())
343347
return asyncio.gather(*tasks)
344348

345349
def _find_parent_prefix(self, item):

src/ax_interface/pdu_implementations.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -362,12 +362,26 @@ def __init__(self, *args, **kwargs):
362362
# header only.
363363

364364

365-
# class NotifyPDU(ContextOptionalPDU):
366-
# """
367-
# https://tools.ietf.org/html/rfc2741#section-6.2.10
368-
# """
369-
# header_type_ = PduTypes.NOTIFY
370-
# TODO: Impl
365+
class NotifyPDU(ContextOptionalPDU):
366+
"""
367+
https://tools.ietf.org/html/rfc2741#section-6.2.10
368+
"""
369+
header_type_ = PduTypes.NOTIFY
370+
371+
def __init__(self, header=None, context=None, payload=None, varBinds=[]):
372+
super().__init__(header=header, context=context, payload=payload)
373+
374+
self.varBinds = varBinds
375+
# Reducing the header length as per RFC 2741
376+
# https://tools.ietf.org/html/rfc2741#section-6.1
377+
payload_len = len(self.encode()) - constants.AGENTX_HEADER_LENGTH
378+
self.header = self.header._replace(payload_length=payload_len)
379+
380+
def encode(self):
381+
ret = super().encode()
382+
for value in self.varBinds:
383+
ret += value.to_bytes(self.header.endianness)
384+
return ret
371385

372386

373387

src/ax_interface/protocol.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from .encodings import ObjectIdentifier
55
from .pdu import PDUHeader, PDUStream
66
from .pdu_implementations import RegisterPDU, ResponsePDU, OpenPDU
7+
from .trap import TrapInfra
78

89

910
class AgentX(asyncio.Protocol):
@@ -49,6 +50,7 @@ def opening_handshake(self):
4950

5051
def register_subtrees(self, pdu):
5152
self.session_id = pdu.header.session_id
53+
TrapInfra.protocol_obj = self
5254
logger.info("AgentX session starting with ID: {}".format(self.session_id))
5355

5456
for idx, subtree in enumerate(self.mib_table.prefixes):

0 commit comments

Comments
 (0)