fix(get_config): use paged send for show running-config/startup-config
"v7" CLI firmware has no "terminal length 0" equivalent (see
_send_paged_command's docstring), so a config long enough to paginate
emits "--More--" prompts that _send_command's expect_string=base_prompt
match never sees. On real hardware (GS110TPv3, 7 VLANs, 10 interfaces)
this hung every scheduled config-backup poll for 30s and failed with
"Pattern not detected: '<prompt>[>#]' in output.", so the config was
never actually backed up. get_mac_address_table()/get_vlans() already
use _send_paged_command() for the same reason on other long outputs;
get_config() now does too.
Also fixed tests/unit/test_driver.py's import/patch target
(napalm_netgear_plus -> napalm_netgear, the actual package name) —
the whole file has been uncollectable since its initial commit, no CI
was wired up here to catch it. And removed a dead unreachable
`return {"success": ..., "output": ...}` line after get_health_metrics's
real return (undefined names, ruff F821), unrelated leftover found
while fixing the above.
This commit is contained in:
+60
-40
@@ -3,7 +3,7 @@
|
||||
import pytest
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from napalm_netgear_plus.netgear_smart import NetgearSmartDriver
|
||||
from napalm_netgear.netgear_smart import NetgearSmartDriver
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -14,7 +14,7 @@ from napalm_netgear_plus.netgear_smart import NetgearSmartDriver
|
||||
@pytest.fixture
|
||||
def driver():
|
||||
"""Return a driver instance with a mocked Netmiko connection."""
|
||||
with patch("napalm_netgear_plus.netgear_smart.ConnectHandler"):
|
||||
with patch("napalm_netgear.netgear_smart.ConnectHandler"):
|
||||
drv = NetgearSmartDriver(
|
||||
hostname="192.168.0.239",
|
||||
username="admin",
|
||||
@@ -153,54 +153,50 @@ class TestGetFacts:
|
||||
driver._send_command = lambda cmd: SHOW_SYSINFO
|
||||
driver._get_interface_list = lambda: []
|
||||
facts = driver.get_facts()
|
||||
for key in ("vendor", "model", "hostname", "os_version", "serial_number",
|
||||
"uptime", "interface_list", "fqdn"):
|
||||
for key in (
|
||||
"vendor",
|
||||
"model",
|
||||
"hostname",
|
||||
"os_version",
|
||||
"serial_number",
|
||||
"uptime",
|
||||
"interface_list",
|
||||
"fqdn",
|
||||
):
|
||||
assert key in facts
|
||||
|
||||
def test_vendor(self, driver):
|
||||
driver._send_command = lambda cmd: (
|
||||
SHOW_SYSINFO if "sysinfo" in cmd else SHOW_VERSION
|
||||
)
|
||||
driver._send_command = lambda cmd: SHOW_SYSINFO if "sysinfo" in cmd else SHOW_VERSION
|
||||
driver._get_interface_list = lambda: []
|
||||
facts = driver.get_facts()
|
||||
assert facts["vendor"] == "Netgear"
|
||||
|
||||
def test_model(self, driver):
|
||||
driver._send_command = lambda cmd: (
|
||||
SHOW_SYSINFO if "sysinfo" in cmd else SHOW_VERSION
|
||||
)
|
||||
driver._send_command = lambda cmd: SHOW_SYSINFO if "sysinfo" in cmd else SHOW_VERSION
|
||||
driver._get_interface_list = lambda: []
|
||||
facts = driver.get_facts()
|
||||
assert facts["model"] == "GS110TP"
|
||||
|
||||
def test_hostname(self, driver):
|
||||
driver._send_command = lambda cmd: (
|
||||
SHOW_SYSINFO if "sysinfo" in cmd else SHOW_VERSION
|
||||
)
|
||||
driver._send_command = lambda cmd: SHOW_SYSINFO if "sysinfo" in cmd else SHOW_VERSION
|
||||
driver._get_interface_list = lambda: []
|
||||
facts = driver.get_facts()
|
||||
assert facts["hostname"] == "myswitch"
|
||||
|
||||
def test_os_version(self, driver):
|
||||
driver._send_command = lambda cmd: (
|
||||
SHOW_SYSINFO if "sysinfo" in cmd else SHOW_VERSION
|
||||
)
|
||||
driver._send_command = lambda cmd: SHOW_SYSINFO if "sysinfo" in cmd else SHOW_VERSION
|
||||
driver._get_interface_list = lambda: []
|
||||
facts = driver.get_facts()
|
||||
assert facts["os_version"] == "6.6.3"
|
||||
|
||||
def test_serial_number(self, driver):
|
||||
driver._send_command = lambda cmd: (
|
||||
SHOW_SYSINFO if "sysinfo" in cmd else SHOW_VERSION
|
||||
)
|
||||
driver._send_command = lambda cmd: SHOW_SYSINFO if "sysinfo" in cmd else SHOW_VERSION
|
||||
driver._get_interface_list = lambda: []
|
||||
facts = driver.get_facts()
|
||||
assert facts["serial_number"] == "1FE2A0B1C2"
|
||||
|
||||
def test_uptime_parsing(self, driver):
|
||||
driver._send_command = lambda cmd: (
|
||||
SHOW_SYSINFO if "sysinfo" in cmd else SHOW_VERSION
|
||||
)
|
||||
driver._send_command = lambda cmd: SHOW_SYSINFO if "sysinfo" in cmd else SHOW_VERSION
|
||||
driver._get_interface_list = lambda: []
|
||||
facts = driver.get_facts()
|
||||
expected = 2 * 86400 + 3 * 3600 + 15 * 60 + 42
|
||||
@@ -214,14 +210,12 @@ class TestGetFacts:
|
||||
|
||||
class TestParseUptimeSeconds:
|
||||
def test_full(self):
|
||||
assert NetgearSmartDriver._parse_uptime_seconds(
|
||||
"2 days 3 hrs 15 mins 42 secs"
|
||||
) == float(2 * 86400 + 3 * 3600 + 15 * 60 + 42)
|
||||
assert NetgearSmartDriver._parse_uptime_seconds("2 days 3 hrs 15 mins 42 secs") == float(
|
||||
2 * 86400 + 3 * 3600 + 15 * 60 + 42
|
||||
)
|
||||
|
||||
def test_zero(self):
|
||||
assert NetgearSmartDriver._parse_uptime_seconds(
|
||||
"0 days 0 hrs 0 mins 0 secs"
|
||||
) == 0.0
|
||||
assert NetgearSmartDriver._parse_uptime_seconds("0 days 0 hrs 0 mins 0 secs") == 0.0
|
||||
|
||||
def test_hours_only(self):
|
||||
assert NetgearSmartDriver._parse_uptime_seconds("1 hrs 0 mins 0 secs") == 3600.0
|
||||
@@ -256,6 +250,7 @@ class TestGetInterfaces:
|
||||
if "port" in cmd:
|
||||
return SHOW_PORT_ALL
|
||||
return SHOW_INTERFACE_ALL
|
||||
|
||||
driver._send_command = _send
|
||||
|
||||
def test_all_ports_present(self, driver):
|
||||
@@ -381,14 +376,10 @@ class TestParseVlanPorts:
|
||||
assert NetgearSmartDriver._parse_vlan_ports("0/1") == ["0/1"]
|
||||
|
||||
def test_range(self):
|
||||
assert NetgearSmartDriver._parse_vlan_ports("0/1-0/4") == [
|
||||
"0/1", "0/2", "0/3", "0/4"
|
||||
]
|
||||
assert NetgearSmartDriver._parse_vlan_ports("0/1-0/4") == ["0/1", "0/2", "0/3", "0/4"]
|
||||
|
||||
def test_short_range(self):
|
||||
assert NetgearSmartDriver._parse_vlan_ports("0/1-4") == [
|
||||
"0/1", "0/2", "0/3", "0/4"
|
||||
]
|
||||
assert NetgearSmartDriver._parse_vlan_ports("0/1-4") == ["0/1", "0/2", "0/3", "0/4"]
|
||||
|
||||
def test_comma_separated(self):
|
||||
result = NetgearSmartDriver._parse_vlan_ports("0/1, 0/3, 0/5")
|
||||
@@ -447,6 +438,7 @@ class TestGetSnmpInformation:
|
||||
if "sysinfo" in cmd:
|
||||
return SHOW_SYSINFO
|
||||
return SHOW_SNMP
|
||||
|
||||
driver._send_command = _send
|
||||
snmp = driver.get_snmp_information()
|
||||
assert "public" in snmp["community"]
|
||||
@@ -458,6 +450,7 @@ class TestGetSnmpInformation:
|
||||
if "sysinfo" in cmd:
|
||||
return SHOW_SYSINFO
|
||||
return SHOW_SNMP
|
||||
|
||||
driver._send_command = _send
|
||||
snmp = driver.get_snmp_information()
|
||||
assert snmp["location"] == "Server Room"
|
||||
@@ -540,16 +533,19 @@ class TestConfigManagement:
|
||||
|
||||
def test_load_merge_raises_without_input(self, driver):
|
||||
from napalm.base.exceptions import MergeConfigException
|
||||
|
||||
with pytest.raises(MergeConfigException):
|
||||
driver.load_merge_candidate()
|
||||
|
||||
def test_load_replace_raises_without_input(self, driver):
|
||||
from napalm.base.exceptions import ReplaceConfigException
|
||||
|
||||
with pytest.raises(ReplaceConfigException):
|
||||
driver.load_replace_candidate()
|
||||
|
||||
def test_rollback_raises_without_backup(self, driver):
|
||||
from napalm.base.exceptions import CommandErrorException
|
||||
|
||||
with pytest.raises(CommandErrorException):
|
||||
driver.rollback()
|
||||
|
||||
@@ -580,9 +576,7 @@ exit
|
||||
"""
|
||||
|
||||
def test_diff_detects_change(self):
|
||||
cmds = NetgearSmartDriver._diff_to_commands(
|
||||
TestConfigDiff.BACKUP, TestConfigDiff.CURRENT
|
||||
)
|
||||
cmds = NetgearSmartDriver._diff_to_commands(TestConfigDiff.BACKUP, TestConfigDiff.CURRENT)
|
||||
# Should contain a "no description" and "description uplink" line
|
||||
joined = " ".join(cmds)
|
||||
assert "description" in joined
|
||||
@@ -595,19 +589,45 @@ exit
|
||||
|
||||
class TestGetConfig:
|
||||
def test_running_retrieved(self, driver):
|
||||
driver._send_command = lambda cmd: "! running config"
|
||||
driver._send_paged_command = lambda cmd: "! running config"
|
||||
cfg = driver.get_config(retrieve="running")
|
||||
assert cfg["running"] == "! running config"
|
||||
assert cfg["startup"] == ""
|
||||
|
||||
def test_startup_retrieved(self, driver):
|
||||
driver._send_command = lambda cmd: "! startup config"
|
||||
driver._send_paged_command = lambda cmd: "! startup config"
|
||||
cfg = driver.get_config(retrieve="startup")
|
||||
assert cfg["startup"] == "! startup config"
|
||||
assert cfg["running"] == ""
|
||||
|
||||
def test_uses_paged_command_not_plain_send_command(self, driver):
|
||||
"""Regression: get_config() called _send_command() (Netmiko
|
||||
send_command with expect_string=<base_prompt>[>#]) for
|
||||
"show running-config"/"show startup-config". On "v7" CLI firmware
|
||||
there is no "terminal length 0" equivalent (see
|
||||
_send_paged_command's docstring), so a config long enough to
|
||||
paginate emits "--More--" prompts that expect_string never matches
|
||||
— send_command hangs until read_timeout and fails with
|
||||
"Pattern not detected: '<prompt>[>#]' in output.". Real device:
|
||||
Netgear GS110TPv3 (7 VLANs, 10 interfaces) hit this on every
|
||||
scheduled config-backup poll, 30s timeout, no config ever saved.
|
||||
get_mac_address_table()/get_vlans() already use
|
||||
_send_paged_command() for exactly this reason — get_config() must
|
||||
too."""
|
||||
driver._send_command = MagicMock(
|
||||
side_effect=AssertionError(
|
||||
"get_config() must use _send_paged_command(), not _send_command()"
|
||||
)
|
||||
)
|
||||
driver._send_paged_command = MagicMock(return_value="! config")
|
||||
|
||||
driver.get_config()
|
||||
|
||||
assert driver._send_paged_command.call_count == 2
|
||||
driver._send_command.assert_not_called()
|
||||
|
||||
def test_candidate_always_empty(self, driver):
|
||||
driver._send_command = lambda cmd: ""
|
||||
driver._send_paged_command = lambda cmd: ""
|
||||
cfg = driver.get_config()
|
||||
assert cfg["candidate"] == ""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user