diff --git a/src/zino/tasks/linkstatetask.py b/src/zino/tasks/linkstatetask.py index ab995102b..a91933408 100644 --- a/src/zino/tasks/linkstatetask.py +++ b/src/zino/tasks/linkstatetask.py @@ -4,6 +4,8 @@ from dataclasses import dataclass 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 @@ -42,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] @@ -51,6 +57,19 @@ 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) + def _update_interfaces(self, new_attrs: SparseWalkResponse): for index, row in new_attrs.items(): try: @@ -110,7 +129,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 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):