From 404d9d956cf05311fd66ff0c5f6ce87fee35f9f4 Mon Sep 17 00:00:00 2001 From: Morten Brekkevold Date: Fri, 6 Oct 2023 15:24:28 +0200 Subject: [PATCH 1/3] Add method for polling single interfaces --- src/zino/tasks/linkstatetask.py | 15 +++++++++++++++ tests/tasks/test_linkstatetask.py | 14 +++++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/zino/tasks/linkstatetask.py b/src/zino/tasks/linkstatetask.py index ab995102b..fbc51d8f1 100644 --- a/src/zino/tasks/linkstatetask.py +++ b/src/zino/tasks/linkstatetask.py @@ -4,6 +4,7 @@ from dataclasses import dataclass from typing import Any +from zino.oid import OID from zino.snmp import SNMP, SparseWalkResponse from zino.statemodels import EventState, InterfaceState, Port, PortStateEvent from zino.tasks.task import Task @@ -51,6 +52,20 @@ async def run(self): self._update_interfaces(attrs) + async def poll_single_interface(self, ifindex: int): + """Polls and updates a single interface""" + snmp = SNMP(self.device) + poll_list = [("IF-MIB", column, str(ifindex - 1)) for column in BASE_POLL_LIST] + result = await snmp.getnext2(*poll_list) + self.sysuptime = await self._get_uptime(snmp) + _logger.debug("poll_single_interface %s result: %r", self.device.name, result) + + assert all(ident.index == OID(f".{ifindex}") for ident, value in result) + row = {ident.object: value for ident, value in result} + + self._update_single_interface(row) + return row + def _update_interfaces(self, new_attrs: SparseWalkResponse): for index, row in new_attrs.items(): try: diff --git a/tests/tasks/test_linkstatetask.py b/tests/tasks/test_linkstatetask.py index a093c4b01..65ece1116 100644 --- a/tests/tasks/test_linkstatetask.py +++ b/tests/tasks/test_linkstatetask.py @@ -3,7 +3,7 @@ from zino.config.models import PollDevice from zino.oid import OID from zino.state import ZinoState -from zino.statemodels import Port +from zino.statemodels import InterfaceState, Port from zino.tasks.linkstatetask import ( BaseInterfaceRow, CollectedInterfaceDataIsNotSaneError, @@ -90,6 +90,18 @@ def test_when_descr_and_index_are_present_is_sane_should_return_true(self): row = BaseInterfaceRow(index=42, descr="x", alias="x", admin_status="x", oper_status="x", last_change=0) assert row.is_sane() + @pytest.mark.asyncio + async def test_poll_single_interface_should_update_state(self, linkstatetask_with_one_link_down): + target_index = 2 + await linkstatetask_with_one_link_down.poll_single_interface(target_index) + device_state = linkstatetask_with_one_link_down.state.devices.get(linkstatetask_with_one_link_down.device.name) + + assert target_index in device_state.ports, f"no state for port {target_index} was stored" + port = device_state.ports[target_index] + assert port.state == InterfaceState.DOWN + assert port.ifdescr == "2" + assert port.ifalias == "from a famous" + @pytest.fixture def linkstatetask_with_links_up(snmpsim, snmp_test_port): From f2c856ce9b35f39670d1a536b7b0601c8ed3a7be Mon Sep 17 00:00:00 2001 From: Morten Brekkevold Date: Fri, 6 Oct 2023 15:25:21 +0200 Subject: [PATCH 2/3] Schedule verification for links that change state As per the original Zino code, whenever a port state change is detected, an extra job to poll that single interface is scheduled. --- src/zino/tasks/linkstatetask.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/zino/tasks/linkstatetask.py b/src/zino/tasks/linkstatetask.py index fbc51d8f1..5d769ac35 100644 --- a/src/zino/tasks/linkstatetask.py +++ b/src/zino/tasks/linkstatetask.py @@ -5,6 +5,7 @@ from typing import Any from zino.oid import OID +from zino.scheduler import get_scheduler from zino.snmp import SNMP, SparseWalkResponse from zino.statemodels import EventState, InterfaceState, Port, PortStateEvent from zino.tasks.task import Task @@ -43,6 +44,10 @@ class LinkStateTask(Task): sysuptime: int = 0 + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._scheduler = get_scheduler() + async def run(self): snmp = SNMP(self.device) poll_list = [("IF-MIB", column) for column in BASE_POLL_LIST] @@ -125,7 +130,19 @@ def _make_or_update_state_event(self, port: Port, new_state: InterfaceState, las _logger.info(log) event.add_log(log) - # at this point we should re-schedule a new job in 2 minutes to verify the state change + self._schedule_verification_of_single_port(port.ifindex) + + def _schedule_verification_of_single_port(self, ifindex: int): + in_two_minutes = datetime.datetime.now() + datetime.timedelta(minutes=2) + job_name = f"{self.device.name}-verify-{ifindex}-state" + self._scheduler.add_job( + func=self.poll_single_interface, + args=(ifindex,), + trigger="date", + run_date=in_two_minutes, + name=job_name, + id=job_name, + ) def _get_or_create_port(self, ifindex: int): ports = self.state.devices.get(self.device.name).ports From a30dc9c34c845940f55447c00e7674edf15f3070 Mon Sep 17 00:00:00 2001 From: Morten Brekkevold Date: Fri, 20 Oct 2023 15:16:07 +0200 Subject: [PATCH 3/3] Remove unused return value. As pointed out in review. --- src/zino/tasks/linkstatetask.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/zino/tasks/linkstatetask.py b/src/zino/tasks/linkstatetask.py index 5d769ac35..a91933408 100644 --- a/src/zino/tasks/linkstatetask.py +++ b/src/zino/tasks/linkstatetask.py @@ -69,7 +69,6 @@ async def poll_single_interface(self, ifindex: int): row = {ident.object: value for ident, value in result} self._update_single_interface(row) - return row def _update_interfaces(self, new_attrs: SparseWalkResponse): for index, row in new_attrs.items():