Compare commits

...
3 Commits
Author SHA1 Message Date
Christian Manivong 799d1ce749 feat: declare REBOOT_SETTLE_SECONDS = 90
A general-purpose host runs through a full init sequence before it is worth
polling again. netOrk kept this in a hardcoded driver-name set duplicated across
two files (netork#113).

Worth knowing: the NAS drivers that inherit from here — OpenMediaVault and QNAP
— were not in that set and waited 45 seconds. They inherit 90 now, which is the
more honest number for a device that also brings storage up on boot.
2026-08-21 13:07:14 +07:00
Christian Manivong ce40299033 fix(tests): repair the fixture and doubles that made seven tests fail
Closes netork#110.

The seven failures had two causes, neither of them in the driver.

The `driver` fixture builds a LinuxDriver with `__new__`, bypassing `__init__`,
and never set `_sudo_password`. Every call through `_sudo()` therefore raised
AttributeError, which the callers' broad `except Exception` reported as
`{"success": False}` — so six apply_updates tests failed for a reason unrelated
to what they were asserting.

`test_apply_updates_apt_all_packages` had a second one: its `capture_send(cmd)`
double accepted no keyword arguments, while `_sudo()` passes `read_timeout`.

The seventh, the apt update listing test, supplied `side_effect=["",
APT_UPGRADABLE]` — two values for a cache refresh that never existed.
`_get_updates_apt` has made exactly one `_send` call since it was written in
2712389, so the empty first value was consumed and parsed as the package list.
Checked out that commit and ran it: the test failed there too. It was committed
red and never passed.

That settles the open question in netork#110: the implementation was not changed,
the tests were written against one that never existed. `get_available_updates`
reads the local apt cache deliberately — refreshing it needs sudo and would cost
a round trip on every poll — so there is no stale-cache bug behind the
`updates_available` warning.
2026-08-21 12:57:11 +07:00
Christian Manivong 27027eec56 refactor!: keep one name for pending updates, drop the alias
This driver implemented get_pending_updates and then carried
get_available_updates as a one-line alias, because netOrk's API only ever called
the latter. Two names for one thing, with the driver bridging the gap.

napalm-device-types v1.0 collapses them onto get_available_updates — the name
four drivers and every netOrk call site already used — so the alias has nothing
left to bridge.

BREAKING CHANGE: get_pending_updates is gone; call get_available_updates.

The seven pre-existing test failures in this repo are untouched and unrelated;
see netork#110.
2026-08-21 12:50:10 +07:00
2 changed files with 17 additions and 10 deletions
+4 -5
View File
@@ -126,6 +126,9 @@ class LinuxDriver(OSDriver):
TYPE_LABEL = "Linux"
VENDOR = "Linux"
DRIVER_NAME = "linux"
# A general-purpose host runs through a full init sequence; NAS derivatives
# (OpenMediaVault, QNAP) inherit this and are, if anything, slower.
REBOOT_SETTLE_SECONDS = 90
SNMP_FINGERPRINT = [
FingerprintRule("linux", weight=5.0),
]
@@ -1019,7 +1022,7 @@ class LinuxDriver(OSDriver):
success = not any(kw in low for kw in ("error:", "failed", "not found", "is not installed", "no packages"))
return {"success": success, "output": raw.strip()}
def get_pending_updates(self) -> list[UpdateDict]:
def get_available_updates(self) -> list[UpdateDict]:
if self._pkg_manager == "apt":
return self._get_updates_apt()
if self._pkg_manager in ("dnf", "yum"):
@@ -1032,10 +1035,6 @@ class LinuxDriver(OSDriver):
f"Package manager '{self._pkg_manager}' is not supported"
)
def get_available_updates(self) -> list[UpdateDict]:
"""Alias for get_pending_updates(); called by the netork API backend."""
return self.get_pending_updates()
def get_device_warnings(self) -> List[dict[str, Any]]:
"""Return warning dicts for issues detected on this device.
+13 -5
View File
@@ -22,6 +22,11 @@ def driver():
d._secret = "pass" # noqa: S105
d._forced_pkg_manager = None
d._pkg_manager = "apt"
# Set by __init__, which this fixture bypasses via __new__. Without it every
# call through _sudo() raises AttributeError, which the callers' broad
# `except Exception` turns into a plain {"success": False} -- so the tests
# failed for a reason that had nothing to do with what they were testing.
d._sudo_password = None
d.netmiko_optional_args = {}
d._device = MagicMock()
return d
@@ -166,7 +171,7 @@ def test_get_packages_apt(driver):
# ---------------------------------------------------------------------------
# get_pending_updates (apt)
# get_available_updates (apt)
# ---------------------------------------------------------------------------
@@ -177,10 +182,10 @@ APT_UPGRADABLE = (
)
def test_get_pending_updates_apt(driver):
def test_get_available_updates_apt(driver):
driver._pkg_manager = "apt"
with patch.object(driver, "_send", side_effect=["", APT_UPGRADABLE]):
updates = driver.get_pending_updates()
with patch.object(driver, "_send", return_value=APT_UPGRADABLE):
updates = driver.get_available_updates()
assert len(updates) == 2
assert updates[0]["name"] == "openssh-server"
assert updates[0]["current_version"] == "1:9.2p1-2+deb12u1"
@@ -312,7 +317,10 @@ def test_apply_updates_apt_all_packages(driver):
driver._pkg_manager = "apt"
sent_commands = []
def capture_send(cmd):
def capture_send(cmd, **kwargs):
# _sudo() passes read_timeout as a keyword; without **kwargs this raises
# TypeError, which the caller's `except Exception` reports as a failed
# upgrade rather than a broken test double.
sent_commands.append(cmd)
return APT_UPGRADE_SUCCESS