fix: never report "nothing pending" when the package index is gone, and load it on request
OpenWrt keeps its package index in RAM. After a reboot `apk version` only
warns that it cannot open its cached index and exits 0, and `opkg
list-upgradable` prints nothing: both read as "no updates". netOrk keeps
"pending since" per package (MVP 5), so the reader now raises instead:
- apk: on apk's own warning ("opening from cache ... No such file"), or a
failed exit status.
- opkg: when /var/opkg-lists is empty.
- refresh_available_updates(): apk update / opkg update, success from the
exit status.
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
"""Pending updates on OpenWrt: never "nothing pending" when the index is gone.
|
||||
|
||||
OpenWrt keeps its package index in RAM. After a reboot ``apk version`` only
|
||||
warns that it cannot open the cached index and exits 0, and ``opkg
|
||||
list-upgradable`` prints nothing: both looked like "no updates". netOrk keeps
|
||||
"pending since" per package, so a reader that cannot read raises instead, and
|
||||
``refresh_available_updates`` loads the index (netOrk MVP 5).
|
||||
|
||||
The apk warning is the real output of an OpenWrt 25.12.5 access point after a
|
||||
reboot.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from napalm_openwrt.openwrt import OpenWrtDriver
|
||||
|
||||
APK_INDEX_MISSING = (
|
||||
"WARNING: opening from cache https://downloads.openwrt.org/releases/25.12.5/targets/"
|
||||
"ath79/generic/packages/packages.adb: No such file or directory\n"
|
||||
"WARNING: opening from cache https://downloads.openwrt.org/releases/25.12.5/packages/"
|
||||
"mips_24kc/base/packages.adb: No such file or directory\n"
|
||||
"__OWRT_RC=0\n"
|
||||
)
|
||||
APK_PENDING = (
|
||||
"Installed: Available:\n"
|
||||
"dnsmasq-2.90-r4 < 2.90-r5\n"
|
||||
"hostapd-common-2024.09.15-r2 < 2024.09.15-r3\n"
|
||||
"__OWRT_RC=0\n"
|
||||
)
|
||||
OPKG_PENDING = "dnsmasq - 2.90-4 - 2.90-5\n"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def driver():
|
||||
with patch("napalm_openwrt.openwrt.ConnectHandler"):
|
||||
drv = OpenWrtDriver(hostname="192.168.1.1", username="root", password="")
|
||||
drv.device = MagicMock()
|
||||
yield drv
|
||||
|
||||
|
||||
def _answer(driver, pm: str, reply: str):
|
||||
"""Answer the package-manager probe with *pm* and everything else with *reply*."""
|
||||
sent: list = []
|
||||
|
||||
def send(cmd, **kwargs):
|
||||
sent.append(cmd)
|
||||
if cmd.startswith("command -v apk"):
|
||||
return "/usr/bin/apk" if pm == "apk" else "__no_apk__"
|
||||
return reply
|
||||
|
||||
driver._send_command = send
|
||||
return sent
|
||||
|
||||
|
||||
class TestAvailableUpdates:
|
||||
def test_apk_without_its_index_raises_instead_of_reporting_nothing(self, driver):
|
||||
_answer(driver, "apk", APK_INDEX_MISSING)
|
||||
|
||||
with pytest.raises(RuntimeError, match="index"):
|
||||
driver.get_available_updates()
|
||||
|
||||
def test_apk_with_its_index_lists_what_is_pending(self, driver):
|
||||
_answer(driver, "apk", APK_PENDING)
|
||||
|
||||
updates = driver.get_available_updates()
|
||||
|
||||
assert [u["name"] for u in updates] == ["dnsmasq", "hostapd-common"]
|
||||
assert updates[0]["current_version"] == "2.90-r4"
|
||||
assert updates[0]["new_version"] == "2.90-r5"
|
||||
|
||||
def test_apk_that_failed_raises(self, driver):
|
||||
_answer(driver, "apk", "ERROR: unable to lock database\n__OWRT_RC=99\n")
|
||||
|
||||
with pytest.raises(RuntimeError):
|
||||
driver.get_available_updates()
|
||||
|
||||
def test_opkg_without_its_lists_raises(self, driver):
|
||||
_answer(driver, "opkg", "__NO_INDEX__\n")
|
||||
|
||||
with pytest.raises(RuntimeError, match="index"):
|
||||
driver.get_available_updates()
|
||||
|
||||
def test_opkg_with_its_lists(self, driver):
|
||||
_answer(driver, "opkg", OPKG_PENDING)
|
||||
|
||||
assert driver.get_available_updates() == [
|
||||
{"name": "dnsmasq", "current_version": "2.90-4", "new_version": "2.90-5"}
|
||||
]
|
||||
|
||||
|
||||
class TestRefresh:
|
||||
def test_apk_loads_its_index(self, driver):
|
||||
sent = _answer(driver, "apk", "OK: 8912 distinct packages available\n__OWRT_RC=0\n")
|
||||
|
||||
assert driver.refresh_available_updates()["success"] is True
|
||||
assert sent[-1].startswith("apk update")
|
||||
|
||||
def test_opkg_loads_its_lists(self, driver):
|
||||
sent = _answer(driver, "opkg", "Updated list of available packages\n__OWRT_RC=0\n")
|
||||
|
||||
assert driver.refresh_available_updates()["success"] is True
|
||||
assert sent[-1].startswith("opkg update")
|
||||
|
||||
def test_a_failed_refresh_says_why(self, driver):
|
||||
_answer(driver, "apk", "ERROR: network unreachable\n__OWRT_RC=1\n")
|
||||
|
||||
result = driver.refresh_available_updates()
|
||||
|
||||
assert result == {"success": False, "output": "ERROR: network unreachable"}
|
||||
Reference in New Issue
Block a user