diff options
| author | Yuriy Andamasov <yuriy@vyos.io> | 2026-07-03 19:28:22 +0300 |
|---|---|---|
| committer | Yuriy Andamasov <yuriy@vyos.io> | 2026-07-03 19:28:22 +0300 |
| commit | 11cd6c125673d9e426aeedf64b0083b258c242b1 (patch) | |
| tree | 7e6669f9fc0176397de47050a3e16d03d883f4a2 | |
| parent | 238894482e89cce374ca98763f74a424aa81e2fe (diff) | |
| download | vyos-1x-11cd6c125673d9e426aeedf64b0083b258c242b1.tar.gz vyos-1x-11cd6c125673d9e426aeedf64b0083b258c242b1.zip | |
smoketest: T9048: keep VRF and cmdline assertions on the same PID
Adversarial review findings on the previous head:
* get_process_cmdline() could re-resolve a NEW PID after the
'ip vrf pids' membership assertion had already run against the old
one, so the two assertions could validate different processes. The
helper now returns the (pid, cmdline) pair it actually validated and
the VRF checks moved after it, targeting the returned PID.
* The one-shot retry left a residual double-race window. The helper
now polls PID discovery and the /proc read together under the same
PROCESS_WAIT_TIMEOUT deadline.
🤖 Generated by [robots](https://vyos.io)
| -rw-r--r-- | smoketest/scripts/cli/base_interfaces_test.py | 64 |
1 files changed, 40 insertions, 24 deletions
diff --git a/smoketest/scripts/cli/base_interfaces_test.py b/smoketest/scripts/cli/base_interfaces_test.py index 4772c7202..e0b26f525 100644 --- a/smoketest/scripts/cli/base_interfaces_test.py +++ b/smoketest/scripts/cli/base_interfaces_test.py @@ -16,6 +16,8 @@ import re import jmespath from json import loads +from time import sleep +from time import time from netifaces import ifaddresses # pylint: disable = no-name-in-module from socket import AF_INET from socket import AF_INET6 @@ -238,18 +240,26 @@ class BasicInterfaceTest: def get_process_cmdline(self, process_name, interface, pid): # dhclient re-executes itself while daemonizing - the PID found # right after commit may already be gone when /proc is read. - # Re-resolve the PID once instead of failing on the transient one. - # NB: read_file() re-raises on failure unless defaultonfailure is - # set to a non-None value. + # Poll PID discovery and the /proc read together until the + # deadline instead of failing on a transient PID. Returns the + # (pid, cmdline) pair that was actually validated so callers + # can assert against the same process. + # NB: read_file() re-raises on failure unless defaultonfailure + # is set to a non-None value. + time_expire = time() + PROCESS_WAIT_TIMEOUT cmdline = read_file(f'/proc/{pid}/cmdline', defaultonfailure='') - if not cmdline: - pid = process_named_running(process_name, cmdline=interface, - timeout=PROCESS_WAIT_TIMEOUT) - self.assertTrue(pid) + while not cmdline: + if time() > time_expire: + break + sleep(0.250) + tmp = process_named_running(process_name, cmdline=interface) + if not tmp: + continue + pid = tmp cmdline = read_file(f'/proc/{pid}/cmdline', defaultonfailure='') - self.assertTrue(cmdline, - f'{process_name} cmdline unreadable after PID re-resolve') - return cmdline + self.assertTrue(cmdline, + f'{process_name} cmdline unreadable for interface {interface}') + return pid, cmdline def test_dhcp_disable_interface(self): if not self._test_dhcp: @@ -312,8 +322,8 @@ class BasicInterfaceTest: self.assertIn(f'send user-class "{user_class}";', dhclient_config) # and the commandline has the appropriate options - cmdline = self.get_process_cmdline(dhclient_process_name, - interface, dhclient_pid) + dhclient_pid, cmdline = self.get_process_cmdline( + dhclient_process_name, interface, dhclient_pid) self.assertIn(f'-e\x00IF_METRIC={distance}', cmdline) def test_dhcp_vrf(self): @@ -344,13 +354,16 @@ class BasicInterfaceTest: dhclient_pid = process_named_running(dhclient_process_name, cmdline=interface, timeout=PROCESS_WAIT_TIMEOUT) self.assertTrue(dhclient_pid) - # .. inside the appropriate VRF instance + # The commandline must carry the appropriate options .. + # (get_process_cmdline() may re-resolve the PID, so it runs + # first and the VRF check below targets the same process) + dhclient_pid, cmdline = self.get_process_cmdline( + dhclient_process_name, interface, dhclient_pid) + self.assertIn(f'-e\x00IF_METRIC={cli_default_metric}', cmdline) + # .. and the process must run inside the appropriate VRF + # instance vrf_pids = cmd(f'ip vrf pids {vrf_name}') self.assertIn(str(dhclient_pid), vrf_pids) - # and the commandline has the appropriate options - cmdline = self.get_process_cmdline(dhclient_process_name, - interface, dhclient_pid) - self.assertIn(f'-e\x00IF_METRIC={cli_default_metric}', cmdline) # T5103: remove interface from VRF instance and move DHCP client # back to default VRF. This must restart the DHCP client process @@ -367,13 +380,16 @@ class BasicInterfaceTest: dhclient_pid = process_named_running(dhclient_process_name, cmdline=interface, timeout=PROCESS_WAIT_TIMEOUT) self.assertTrue(dhclient_pid) - # .. inside the appropriate VRF instance + # The commandline must carry the appropriate options .. + # (get_process_cmdline() may re-resolve the PID, so it runs + # first and the VRF check below targets the same process) + dhclient_pid, cmdline = self.get_process_cmdline( + dhclient_process_name, interface, dhclient_pid) + self.assertIn(f'-e\x00IF_METRIC={cli_default_metric}', cmdline) + # .. and the process must no longer run inside the VRF + # instance vrf_pids = cmd(f'ip vrf pids {vrf_name}') self.assertNotIn(str(dhclient_pid), vrf_pids) - # and the commandline has the appropriate options - cmdline = self.get_process_cmdline(dhclient_process_name, - interface, dhclient_pid) - self.assertIn(f'-e\x00IF_METRIC={cli_default_metric}', cmdline) self.cli_delete(['vrf', 'name', vrf_name]) @@ -1238,8 +1254,8 @@ class BasicInterfaceTest: self.assertTrue(pid) # DHCPv6 option "no-release" requires "-n" daemon startup option - dhcp6c_options = self.get_process_cmdline(dhcp6c_process_name, - interface, pid) + pid, dhcp6c_options = self.get_process_cmdline( + dhcp6c_process_name, interface, pid) self.assertIn('-n', dhcp6c_options) def test_dhcpv6pd_auto_sla_id(self): |
