fix!: reboot_host instead of reboot; drop run_device_action (0.2.0)
CI / test (3.10) (push) Successful in 3m5s
CI / test (3.11) (push) Successful in 23s
CI / test (3.12) (push) Successful in 24s
CI / test (3.10) (pull_request) Successful in 3m3s
CI / test (3.11) (pull_request) Successful in 23s
CI / test (3.12) (pull_request) Successful in 24s

The gateway had reboot(), but the contract (HostRebootMixin) and the
application look for reboot_host(), so the gateway could not be
restarted from it. reboot_host() returns None and raises when the
gateway refuses, as the contract says.

run_device_action dispatched reboot, restart_wifi[:band] and
get_diagnostics by string, with a result shape other than its
contract's. restart_wifi(band) and get_diagnostics() are methods now;
napalm-device-types deprecates run_device_action.

BREAKING CHANGE: reboot() and run_device_action are removed.

Closes #5. Part of #6.
This commit is contained in:
Christian Manivong committed 2026-10-17 08:40:28 +02:00
1 parent efdf62c8be
commit 210f00f5e7
3 files changed
+28 -48

No files matched your search

+13 -38
View File
@@ -24,7 +24,6 @@ from napalm.base.exceptions import ConnectionClosedException, ConnectionExceptio
from napalm_device_types import FingerprintRule, ResidentialGatewayDriver from napalm_device_types import FingerprintRule, ResidentialGatewayDriver
from napalm_device_types.models import ( from napalm_device_types.models import (
DeviceActionResultDict,
HostDict, HostDict,
NATTranslationDict, NATTranslationDict,
PortForwardDict, PortForwardDict,
@@ -806,42 +805,18 @@ class ZyxelVMGDriver(ResidentialGatewayDriver):
} }
return radios return radios
def reboot(self) -> DeviceActionResultDict: def reboot_host(self) -> None:
"""Reboot the device via DAL.""" """Restart the gateway (``HostRebootMixin``); raises when it refuses."""
result = self._dal_query("REBOOT", {"Command": "reboot"}) result = self._dal_query("REBOOT", {"Command": "reboot"})
if result is not None and result.get("result") == "ZCFG_SUCCESS": if result is None or result.get("result") != "ZCFG_SUCCESS":
return {"success": True, "action": "reboot", "message": "Device is rebooting"} raise RuntimeError(f"the gateway refused to reboot: {result}")
return {"success": False, "action": "reboot", "message": "Reboot command failed"}
def run_device_action(self, action: str) -> DeviceActionResultDict: def restart_wifi(self, band: str = "both") -> None:
"""Run a device action such as reboot or WiFi restart.""" """Restart the radios of *band* (``2.4G``, ``5G`` or ``both``); raises when refused."""
result: DeviceActionResultDict = { result = self._dal_query("WIFI_RESTART", {"Band": band})
"success": False, if result is None or result.get("result") != "ZCFG_SUCCESS":
"action": action, raise RuntimeError(f"the gateway refused to restart Wi-Fi ({band}): {result}")
"message": "",
} def get_diagnostics(self) -> dict:
try: """What the gateway's diagnostics page reports; empty when it reports nothing."""
if action == "reboot": return self._dal_query("DIAGNOSTICS", {}) or {}
return self.reboot()
elif action.startswith("restart_wifi"):
band = "both"
if ":" in action:
band = action.split(":", 1)[1]
dal = self._dal_query("WIFI_RESTART", {"Band": band})
if dal is not None and dal.get("result") == "ZCFG_SUCCESS":
result["success"] = True
result["message"] = f"WiFi restarting ({band})"
else:
result["message"] = "WiFi restart command failed"
elif action == "get_diagnostics":
diag = self._dal_query("DIAGNOSTICS", {})
if diag:
result["success"] = True
result["message"] = json.dumps(diag)
else:
result["message"] = "Diagnostics not available"
else:
result["message"] = f"Unknown action: {action}"
except Exception as e:
result["message"] = f"Action '{action}' failed: {e}"
return result
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "napalm-zyxel" name = "napalm-zyxel"
version = "0.1.0" version = "0.2.0"
description = "NAPALM driver for Zyxel VMG series residential gateways (VMG4005-B50A and similar)." description = "NAPALM driver for Zyxel VMG series residential gateways (VMG4005-B50A and similar)."
readme = "README.md" readme = "README.md"
license = { text = "Apache-2.0" } license = { text = "Apache-2.0" }
+14 -9
View File
@@ -1,6 +1,7 @@
import json import json
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
import pytest
import requests import requests
from napalm.base.exceptions import ConnectionException from napalm.base.exceptions import ConnectionException
@@ -285,16 +286,20 @@ class TestZyxelVMGDriver:
self._setup_mocked_http() self._setup_mocked_http()
assert self.driver.get_arp_table() == [] assert self.driver.get_arp_table() == []
def test_reboot(self): def test_reboot_host_is_the_contract_the_application_calls(self):
self._setup_mocked_http() """It was reboot(), which nothing looks for: the gateway could not be
result = self.driver.run_device_action("reboot") restarted from the application (#5)."""
assert result["action"] == "reboot" self.driver._dal_query = lambda name, data: {"result": "ZCFG_SUCCESS"}
assert self.driver.reboot_host() is None
def test_run_device_action_unknown(self): def test_a_refused_reboot_raises(self):
self._setup_mocked_http() self.driver._dal_query = lambda name, data: {"result": "ZCFG_FAIL"}
result = self.driver.run_device_action("foobar") with pytest.raises(RuntimeError):
assert result["success"] is False self.driver.reboot_host()
assert "Unknown" in result["message"]
def test_run_device_action_and_reboot_are_gone(self):
assert not hasattr(type(self.driver), "run_device_action")
assert not hasattr(type(self.driver), "reboot")
def test_load_replace_candidate_raises(self): def test_load_replace_candidate_raises(self):
self._setup_mocked_http() self._setup_mocked_http()