Skip to content

Commit

Permalink
WIP: Calculate and log state change event times
Browse files Browse the repository at this point in the history
Instead of passing the SNMP handle around to methods, we keep it as
an instance variable.

More of the code flow needs to be async methods, as we may now suddenly
need to run SNMP queries for sysuptime while processing data.

Could potentially be simplified by collecting the sysuptime value only
once for all collected interfaces.
  • Loading branch information
lunkwill42 committed Oct 4, 2023
1 parent 6102d77 commit bc3ff6e
Showing 1 changed file with 26 additions and 12 deletions.
38 changes: 26 additions & 12 deletions src/zino/tasks/linkstatetask.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import datetime
import logging
import re
from dataclasses import dataclass
Expand Down Expand Up @@ -39,19 +40,21 @@ class LinkStateTask(Task):
case: Descriptions are mandated for both physical ports and their sub-units.
"""

snmp: SNMP = None

async def run(self):
snmp = SNMP(self.device)
self.snmp = SNMP(self.device)
poll_list = [("IF-MIB", column) for column in BASE_POLL_LIST]
attrs = await snmp.sparsewalk(*poll_list)
attrs = await self.snmp.sparsewalk(*poll_list)
_logger.debug("%s ifattrs: %r", self.device.name, attrs)

self._update_interfaces(attrs)
await self._update_interfaces(attrs)

def _update_interfaces(self, new_attrs: SparseWalkResponse):
async def _update_interfaces(self, new_attrs: SparseWalkResponse):
for index, row in new_attrs.items():
self._update_single_interface(row)
await self._update_single_interface(row)

def _update_single_interface(self, row: dict[str, Any]):
async def _update_single_interface(self, row: dict[str, Any]):
data = BaseInterfaceRow(*(row.get(attr) for attr in BASE_POLL_LIST))
if not data.is_sane():
return

Check warning on line 60 in src/zino/tasks/linkstatetask.py

View check run for this annotation

Codecov / codecov/patch

src/zino/tasks/linkstatetask.py#L60

Added line #L60 was not covered by tests
Expand All @@ -63,9 +66,9 @@ def _update_single_interface(self, row: dict[str, Any]):
if not self._is_interface_watched(data):
return

Check warning on line 67 in src/zino/tasks/linkstatetask.py

View check run for this annotation

Codecov / codecov/patch

src/zino/tasks/linkstatetask.py#L67

Added line #L67 was not covered by tests

self._update_state(data, port, row)
await self._update_state(data, port, row)

def _update_state(self, data: BaseInterfaceRow, port: Port, row: dict[str, Any]):
async def _update_state(self, data: BaseInterfaceRow, port: Port, row: dict[str, Any]):
for attr in ("ifAdminStatus", "ifOperStatus"):
if not row.get(attr):
raise MissingInterfaceTableData(self.device.name, data.index, attr)

Check warning on line 74 in src/zino/tasks/linkstatetask.py

View check run for this annotation

Codecov / codecov/patch

src/zino/tasks/linkstatetask.py#L74

Added line #L74 was not covered by tests
Expand All @@ -78,10 +81,10 @@ def _update_state(self, data: BaseInterfaceRow, port: Port, row: dict[str, Any])
state = data.oper_status
state = InterfaceState(state)
if port.state and port.state != state:
self._make_or_update_state_event(port, state)
await self._make_or_update_state_event(port, state, data.last_change)
port.state = state

def _make_or_update_state_event(self, port: Port, new_state: InterfaceState):
async def _make_or_update_state_event(self, port: Port, new_state: InterfaceState, last_change: int):
event, created = self.state.events.get_or_create_event(self.device.name, port.ifindex, PortStateEvent)
if created:
event.state = EventState.OPEN
Expand All @@ -93,10 +96,14 @@ def _make_or_update_state_event(self, port: Port, new_state: InterfaceState):
event.priority = self.device.priority
event.descr = port.ifdescr

# this is where we need to use sysUpTime and ifLastChange to calculate a timestamp for the change
sysuptime = await self._get_uptime()
if not sysuptime:
sysuptime = last_change

Check warning on line 101 in src/zino/tasks/linkstatetask.py

View check run for this annotation

Codecov / codecov/patch

src/zino/tasks/linkstatetask.py#L101

Added line #L101 was not covered by tests
event_time = datetime.datetime.now() - datetime.timedelta(seconds=(sysuptime - last_change) / 100)

log = (
f'{event.router}: port "{port.ifdescr}" ix {port.ifindex} ({port.ifalias}) '
f"changed state from {port.state} to {new_state} on TIMESTAMP"
f"changed state from {port.state} to {new_state} on {event_time}"
)
_logger.info(log)
event.add_log(log)
Expand Down Expand Up @@ -135,6 +142,13 @@ def _update_ifalias(self, port: Port, data: BaseInterfaceRow):
_logger.info("%s: setting desc for %s to %s", self.device.name, data.index, data.alias)
port.ifalias = data.alias

async def _get_uptime(self) -> int:
device_state = self.state.devices.get(self.device.name)
response = await self.snmp.get("SNMPv2-MIB", "sysUpTime", 0)
uptime = response.value
device_state.set_boot_time_from_uptime(uptime)
return uptime


class MissingInterfaceTableData(Exception):
def __init__(self, router, port, variable):
Expand Down

0 comments on commit bc3ff6e

Please sign in to comment.