|
| 1 | +""" |
| 2 | +Unit tests for Nokia 7215 / USB management restart helpers in config.main |
| 3 | +""" |
| 4 | +import builtins |
| 5 | +import json |
| 6 | +import subprocess |
| 7 | +from unittest import mock |
| 8 | +from unittest.mock import patch, mock_open |
| 9 | + |
| 10 | +from config.main import ( |
| 11 | + get_device_name, |
| 12 | + get_mgmt_interface, |
| 13 | + reset_mgmt_interface_if_usb_not_running, |
| 14 | + _restart_services, |
| 15 | +) |
| 16 | + |
| 17 | + |
| 18 | +def _open_mock_operstate(read_data): |
| 19 | + """Patch only /operstate reads; delegate other opens to the real builtin.""" |
| 20 | + |
| 21 | + def _side_effect(path, *args, **kwargs): |
| 22 | + path_str = path if isinstance(path, str) else str(path) |
| 23 | + if path_str.replace("\\", "/").rstrip("/").endswith("operstate"): |
| 24 | + return mock_open(read_data=read_data)() |
| 25 | + return builtins.open(path, *args, **kwargs) |
| 26 | + |
| 27 | + return _side_effect |
| 28 | + |
| 29 | + |
| 30 | +class TestGetDeviceName(object): |
| 31 | + def test_reads_onie_platform(self): |
| 32 | + content = "foo=1\nonie_platform=armhf-nokia_ixs7215_52x-r0\n" |
| 33 | + with patch("builtins.open", mock_open(read_data=content)): |
| 34 | + assert get_device_name() == "armhf-nokia_ixs7215_52x-r0" |
| 35 | + |
| 36 | + def test_missing_file_returns_none(self): |
| 37 | + with patch("builtins.open", side_effect=OSError("no file")): |
| 38 | + assert get_device_name() is None |
| 39 | + |
| 40 | + def test_no_onie_line_returns_none(self): |
| 41 | + with patch("builtins.open", mock_open(read_data="foo=bar\n")): |
| 42 | + assert get_device_name() is None |
| 43 | + |
| 44 | + |
| 45 | +class TestGetMgmtInterface(object): |
| 46 | + def test_parses_first_mgmt_key(self): |
| 47 | + cfg = {"MGMT_INTERFACE": {"eth0|10.0.0.1/24": {}}} |
| 48 | + with patch("builtins.open", mock_open(read_data=json.dumps(cfg))): |
| 49 | + assert get_mgmt_interface() == "eth0" |
| 50 | + |
| 51 | + def test_empty_mgmt_returns_none(self): |
| 52 | + cfg = {"MGMT_INTERFACE": {}} |
| 53 | + with patch("builtins.open", mock_open(read_data=json.dumps(cfg))): |
| 54 | + assert get_mgmt_interface() is None |
| 55 | + |
| 56 | + def test_invalid_json_returns_none(self): |
| 57 | + with patch("builtins.open", mock_open(read_data="not json")): |
| 58 | + assert get_mgmt_interface() is None |
| 59 | + |
| 60 | + |
| 61 | +class TestResetMgmtInterfaceIfUsbNotRunning(object): |
| 62 | + @patch("config.main.subprocess.run") |
| 63 | + @patch("config.main.get_mgmt_interface", return_value=None) |
| 64 | + def test_no_iface_no_subprocess(self, _gm, _sub): |
| 65 | + reset_mgmt_interface_if_usb_not_running() |
| 66 | + _sub.assert_not_called() |
| 67 | + |
| 68 | + @patch("config.main.subprocess.run") |
| 69 | + @patch("config.main.os.path.realpath", return_value="/sys/devices/platform/eth") |
| 70 | + @patch("config.main.get_mgmt_interface", return_value="eth0") |
| 71 | + def test_skips_non_usb_device(self, _gm, _rp, _sub): |
| 72 | + reset_mgmt_interface_if_usb_not_running() |
| 73 | + _sub.assert_not_called() |
| 74 | + |
| 75 | + @patch("config.main.subprocess.run") |
| 76 | + @patch("builtins.open", side_effect=_open_mock_operstate("up\n")) |
| 77 | + @patch("config.main.os.path.realpath", return_value="/sys/bus/usb/devices/usb1") |
| 78 | + @patch("config.main.get_mgmt_interface", return_value="eth0") |
| 79 | + def test_skips_when_operstate_up(self, _gm, _rp, _op, _sub): |
| 80 | + reset_mgmt_interface_if_usb_not_running() |
| 81 | + _sub.assert_not_called() |
| 82 | + |
| 83 | + @patch("config.main.subprocess.run") |
| 84 | + @patch("config.main.click.echo") |
| 85 | + @patch("builtins.open", side_effect=_open_mock_operstate("down\n")) |
| 86 | + @patch("config.main.os.path.realpath", return_value="/sys/bus/usb/devices/usb1") |
| 87 | + @patch("config.main.get_mgmt_interface", return_value="eth0") |
| 88 | + def test_runs_ip_link_when_usb_and_not_up(self, _gm, _rp, _op, _echo, sub): |
| 89 | + reset_mgmt_interface_if_usb_not_running() |
| 90 | + sub.assert_any_call(["ip", "link", "set", "eth0", "down"], check=True) |
| 91 | + sub.assert_any_call(["ip", "link", "set", "eth0", "up"], check=True) |
| 92 | + |
| 93 | + @patch("config.main.log.log_warning") |
| 94 | + @patch( |
| 95 | + "config.main.subprocess.run", |
| 96 | + side_effect=subprocess.CalledProcessError(1, ["ip"]), |
| 97 | + ) |
| 98 | + @patch("config.main.click.echo") |
| 99 | + @patch("builtins.open", side_effect=_open_mock_operstate("down\n")) |
| 100 | + @patch("config.main.os.path.realpath", return_value="/sys/bus/usb/devices/usb1") |
| 101 | + @patch("config.main.get_mgmt_interface", return_value="eth0") |
| 102 | + def test_logs_warning_on_subprocess_failure( |
| 103 | + self, _gm, _rp, _op, _echo, _sub, log_warning |
| 104 | + ): |
| 105 | + reset_mgmt_interface_if_usb_not_running() |
| 106 | + log_warning.assert_called_once() |
| 107 | + assert "eth0" in str(log_warning.call_args) |
| 108 | + |
| 109 | + |
| 110 | +class TestRestartServicesNokiaExtension(object): |
| 111 | + @patch("config.main.reset_mgmt_interface_if_usb_not_running") |
| 112 | + @patch("config.main.get_device_name", return_value=None) |
| 113 | + @patch("config.main.clicommon.run_command") |
| 114 | + @patch("config.main.subprocess.check_call") |
| 115 | + @patch("config.main.wait_service_restart_finish") |
| 116 | + @patch("config.main.get_service_finish_timestamp", return_value=0) |
| 117 | + @patch("config.main.click.echo") |
| 118 | + @patch("config.main._wait_for_monit_service_monitored") |
| 119 | + def test_always_calls_usb_mgmt_reset( |
| 120 | + self, |
| 121 | + _monit, |
| 122 | + _echo, |
| 123 | + _gst, |
| 124 | + _wsrf, |
| 125 | + _check, |
| 126 | + run_cmd, |
| 127 | + get_dev, |
| 128 | + reset_usb, |
| 129 | + ): |
| 130 | + _restart_services() |
| 131 | + reset_usb.assert_called_once() |
| 132 | + get_dev.assert_called() |
| 133 | + |
| 134 | + @patch("config.main.reset_mgmt_interface_if_usb_not_running") |
| 135 | + @patch("config.main.time.sleep", autospec=True) |
| 136 | + @patch("config.main.get_device_name", return_value="armhf-nokia_ixs7215_52x-r0") |
| 137 | + @patch("config.main.clicommon.run_command") |
| 138 | + @patch("config.main.subprocess.check_call") |
| 139 | + @patch("config.main.wait_service_restart_finish") |
| 140 | + @patch("config.main.get_service_finish_timestamp", return_value=0) |
| 141 | + @patch("config.main.click.echo") |
| 142 | + @patch("config.main._wait_for_monit_service_monitored") |
| 143 | + def test_nokia7215_runs_swss_syncd_restart( |
| 144 | + self, |
| 145 | + _monit, |
| 146 | + echo, |
| 147 | + _gst, |
| 148 | + _wsrf, |
| 149 | + _check, |
| 150 | + run_cmd, |
| 151 | + get_dev, |
| 152 | + _sleep, |
| 153 | + reset_usb, |
| 154 | + ): |
| 155 | + _restart_services() |
| 156 | + reset_usb.assert_called_once() |
| 157 | + stop_swss = mock.call(["sudo", "systemctl", "stop", "swss"]) |
| 158 | + stop_syncd = mock.call(["sudo", "systemctl", "stop", "syncd"]) |
| 159 | + assert stop_swss in run_cmd.call_args_list |
| 160 | + assert stop_syncd in run_cmd.call_args_list |
| 161 | + restart_swss = mock.call(["sudo", "systemctl", "restart", "swss"]) |
| 162 | + assert restart_swss in run_cmd.call_args_list |
| 163 | + echo.assert_any_call( |
| 164 | + "ARMHF/Nokia-7215: force restart swss and syncd" |
| 165 | + ) |
0 commit comments