From a6c1d77791f089a73efafcda910998d8d8a2f90d Mon Sep 17 00:00:00 2001 From: Christian Manivong Date: Tue, 6 Oct 2026 13:05:27 +0200 Subject: [PATCH] feat: restart the firewall (reboot_host) netOrk asks a driver for reboot_host before it offers a restart, and the OPNsense driver had none, so a firewall could not be restarted from netOrk (netOrk #637). reboot_host asks the firmware API (POST /api/core/firmware/reboot); a refusal raises. --- napalm_opnsense/opnsense.py | 10 ++++++++++ tests/unit/test_updates.py | 15 +++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/napalm_opnsense/opnsense.py b/napalm_opnsense/opnsense.py index c3276cc..9213903 100644 --- a/napalm_opnsense/opnsense.py +++ b/napalm_opnsense/opnsense.py @@ -2050,6 +2050,16 @@ class OPNsenseDriver(OPNsensePingMixin, FirewallDriver): "output": f"The update check did not finish within {_REFRESH_POLLS * _REFRESH_INTERVAL} s", } + def reboot_host(self) -> None: + """Restart the firewall through its firmware API (``HostRebootMixin``). + + :raises RuntimeError: when the API refuses the request. + """ + try: + self._post("/api/core/firmware/reboot") + except Exception as exc: + raise RuntimeError(f"The firewall refused to reboot: {exc}") from exc + def get_host_status(self) -> dict[str, Any]: """Whether the firewall needs a reboot to finish an update; it does not patch itself as far as netOrk can tell.""" diff --git a/tests/unit/test_updates.py b/tests/unit/test_updates.py index 9d82732..4f04b5a 100644 --- a/tests/unit/test_updates.py +++ b/tests/unit/test_updates.py @@ -123,3 +123,18 @@ class TestHostStatus: def test_no_pending_reboot(self, driver): with patch.object(driver, "_get", return_value=_status()): assert driver.get_host_status()["reboot_required"] is False + + +class TestRebootHost: + """``reboot_host`` restarts the firewall through its firmware API (netOrk #637).""" + + def test_the_firmware_api_is_asked(self, driver): + with patch.object(driver, "_post", return_value={"status": "ok"}) as post: + driver.reboot_host() + + post.assert_called_once_with("/api/core/firmware/reboot") + + def test_a_refusal_is_raised(self, driver): + with patch.object(driver, "_post", side_effect=ConnectionError("403 Forbidden")): + with pytest.raises(RuntimeError, match="403"): + driver.reboot_host() -- 2.54.0