feat: report a FreeBSD host as needing a reboot when its installed kernel is not the running one #23
@@ -16,6 +16,8 @@ carries the command across.
|
||||
- A kernel newer than the running one is installed, of the same flavour. A
|
||||
Raspberry Pi carries ``rpi-v8`` and ``rpi-2712`` builds side by side, and only
|
||||
the running one's counts.
|
||||
- On FreeBSD, ``freebsd-version -k`` (the installed kernel) differs from
|
||||
``freebsd-version -r`` (the running one): a patch replaced the kernel.
|
||||
|
||||
It is ``None`` when none of these could be read, for example in a container
|
||||
without a ``/lib/modules`` of its own.
|
||||
@@ -50,6 +52,9 @@ HOST_STATUS_COMMAND = (
|
||||
"if command -v needs-restarting >/dev/null 2>&1; then echo '[needs-restarting]'; "
|
||||
"needs-restarting -r >/dev/null 2>&1; echo $?; fi; "
|
||||
"echo '[kernel]'; uname -r; echo '[modules]'; ls -1 /lib/modules 2>/dev/null; "
|
||||
"if command -v freebsd-version >/dev/null 2>&1; then "
|
||||
"echo '[freebsd-kernel]'; freebsd-version -k; "
|
||||
"echo '[freebsd-running]'; freebsd-version -r; fi; "
|
||||
"if command -v apt-config >/dev/null 2>&1; then echo '[apt-config]'; "
|
||||
"apt-config dump 2>/dev/null | grep '^APT::Periodic::Unattended-Upgrade '; fi; "
|
||||
f"echo '[timers]'; for u in {_APT_TIMER} {' '.join(_DNF_TIMERS)}; do "
|
||||
@@ -100,12 +105,26 @@ def kernel_reboot_pending(running: str, installed: List[str]) -> Optional[str]:
|
||||
return None
|
||||
|
||||
|
||||
def _freebsd_reboot(sections: Dict[str, List[str]]) -> Optional[Tuple[bool, Optional[str]]]:
|
||||
"""FreeBSD's answer from freebsd-version, or None on any other system."""
|
||||
installed = (sections.get("freebsd-kernel") or [""])[0]
|
||||
running = (sections.get("freebsd-running") or [""])[0]
|
||||
if not (installed and running):
|
||||
return None
|
||||
if installed != running:
|
||||
return True, f"kernel {installed} installed, {running} running"
|
||||
return False, None
|
||||
|
||||
|
||||
def _reboot(sections: Dict[str, List[str]]) -> Tuple[Optional[bool], Optional[str]]:
|
||||
if "reboot-required" in sections:
|
||||
return True, f"{_REBOOT_FILE} is present"
|
||||
needs = sections.get("needs-restarting")
|
||||
if needs and needs[0] == "1":
|
||||
return True, "needs-restarting -r reports a reboot"
|
||||
freebsd = _freebsd_reboot(sections)
|
||||
if freebsd is not None:
|
||||
return freebsd
|
||||
running = (sections.get("kernel") or [""])[0]
|
||||
modules = sections.get("modules") or []
|
||||
newer = kernel_reboot_pending(running, modules) if running and modules else None
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "napalm-device-types"
|
||||
version = "4.0.0"
|
||||
version = "4.1.0"
|
||||
description = "Abstract device-type base classes for NAPALM drivers"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
|
||||
@@ -198,3 +198,48 @@ class TestHostStatusMixin:
|
||||
|
||||
def test_not_every_os_driver_has_it(self):
|
||||
assert not hasattr(OSDriver, "get_host_status")
|
||||
|
||||
|
||||
class TestFreeBSD:
|
||||
"""freebsd-version -k names the installed kernel, -r the running one; a
|
||||
patch that replaced the kernel shows as the two differing until the host
|
||||
boots it. Recorded on a FreeBSD 15.1 host (NetOrk/netork#799)."""
|
||||
|
||||
#: What the command printed there before this section existed.
|
||||
BEFORE = (
|
||||
"HSTAT_BEGIN\n[kernel]\n15.1-RELEASE\n[modules]\n[timers]\n"
|
||||
"apt-daily-upgrade.timer \ndnf-automatic.timer \ndnf-automatic-install.timer \n"
|
||||
"HSTAT_END\n"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _report(installed: str, running: str) -> str:
|
||||
return (
|
||||
f"HSTAT_BEGIN\n[kernel]\n{running}\n[modules]\n"
|
||||
f"[freebsd-kernel]\n{installed}\n[freebsd-running]\n{running}\n"
|
||||
"[timers]\napt-daily-upgrade.timer \nHSTAT_END\n"
|
||||
)
|
||||
|
||||
def test_without_the_section_it_is_unknown(self):
|
||||
assert parse_host_status(self.BEFORE)["reboot_required"] is None
|
||||
|
||||
def test_the_running_kernel_is_the_installed_one(self):
|
||||
status = parse_host_status(self._report("15.1-RELEASE-p4", "15.1-RELEASE-p4"))
|
||||
assert status["reboot_required"] is False
|
||||
assert status["reboot_reason"] is None
|
||||
|
||||
def test_a_patched_kernel_waits_for_a_reboot(self):
|
||||
status = parse_host_status(self._report("15.1-RELEASE-p5", "15.1-RELEASE-p4"))
|
||||
assert status["reboot_required"] is True
|
||||
assert status["reboot_reason"] == (
|
||||
"kernel 15.1-RELEASE-p5 installed, 15.1-RELEASE-p4 running"
|
||||
)
|
||||
|
||||
def test_freebsd_does_not_patch_itself_as_far_as_netork_can_tell(self):
|
||||
status = parse_host_status(self._report("15.1-RELEASE", "15.1-RELEASE"))
|
||||
assert status["auto_updates"] is None
|
||||
|
||||
def test_the_command_asks_only_where_freebsd_version_exists(self):
|
||||
assert "command -v freebsd-version" in HOST_STATUS_COMMAND
|
||||
assert "freebsd-version -k" in HOST_STATUS_COMMAND
|
||||
assert "freebsd-version -r" in HOST_STATUS_COMMAND
|
||||
|
||||
Reference in New Issue
Block a user