From 9d79a0edb28a482677f3a7068cf0d742c0da3703 Mon Sep 17 00:00:00 2001 From: Bhagyashri Date: Wed, 1 Jul 2026 14:15:56 +0530 Subject: [PATCH 1/2] Apply AZL4 support fixes to chrony, kdump, kill, modprobe, dhclient --- lisa/tools/chrony.py | 6 ++---- lisa/tools/dhclient.py | 30 ++++++++++++++++++++++++++---- lisa/tools/kdump.py | 4 +++- lisa/tools/kill.py | 20 ++++++++++++-------- lisa/tools/modprobe.py | 5 +++++ 5 files changed, 48 insertions(+), 17 deletions(-) diff --git a/lisa/tools/chrony.py b/lisa/tools/chrony.py index 34cdf01d30..fe62147451 100644 --- a/lisa/tools/chrony.py +++ b/lisa/tools/chrony.py @@ -26,9 +26,7 @@ class Chrony(Tool): # Leap status : Normal - __leap_status_pattern = re.compile( - r"([\w\W]*?)Leap status.*:.*Normal$", re.MULTILINE - ) + __leap_status_pattern = re.compile(r"Leap status\s*:\s*Normal") __no_server_set = "Number of sources = 0" __service_not_ready = "503 No such source" @@ -69,7 +67,7 @@ def restart(self) -> None: def check_tracking(self) -> None: cmd_result = self.run("tracking", force_run=True, sudo=True) cmd_result.assert_exit_code() - if not self.__leap_status_pattern.match(cmd_result.stdout): + if not self.__leap_status_pattern.search(cmd_result.stdout): raise LisaException( f"Leap status of {self.command} tracking is not expected," " please check the service status of chrony." diff --git a/lisa/tools/dhclient.py b/lisa/tools/dhclient.py index a4c1978b3a..1c49f6c40a 100644 --- a/lisa/tools/dhclient.py +++ b/lisa/tools/dhclient.py @@ -5,7 +5,7 @@ from lisa.base_tools import Cat from lisa.executable import Tool -from lisa.operating_system import Debian, Fedora, Redhat, Suse +from lisa.operating_system import CBLMariner, Debian, Fedora, Redhat, Suse from lisa.util import LisaException, UnsupportedDistroException, find_group_in_lines from .ls import Ls @@ -71,10 +71,32 @@ def get_timeout(self) -> int: if group and not group["default"]: value = int(group["number"]) is_default_value = False - elif isinstance(self.node.os, Fedora): - # the default value in fedora is 300 + elif isinstance(self.node.os, (Fedora, CBLMariner)): + # Fedora and AZL4+ use NetworkManager for DHCP; default timeout is 300. + # CBLMariner < 4 is not supported here and raises below. + if isinstance(self.node.os, CBLMariner) and ( + self.node.os.information.version.major < 4 + ): + raise UnsupportedDistroException( + os=self.node.os, + message=( + "CBLMariner < 4 (AZL2/3) uses dhcpcd rather than " + "NetworkManager, so this NetworkManager-based timeout " + "lookup does not apply. To add support, read the timeout " + "from the dhcpcd configuration (e.g. /etc/dhcpcd.conf) " + "instead." + ), + ) value = 300 - result = self.node.execute("NetworkManager --print-config", sudo=True) + result = self.node.execute( + "NetworkManager --print-config", + sudo=True, + expected_exit_code=0, + expected_exit_code_failure_message=( + "Failed to read DHCP timeout from NetworkManager. " + "Ensure NetworkManager is installed and accessible." + ), + ) group = find_group_in_lines(result.stdout, self._fedora_pattern) if group and value != int(group["number"]): value = int(group["number"]) diff --git a/lisa/tools/kdump.py b/lisa/tools/kdump.py index 8aa0c1891b..af0c510309 100644 --- a/lisa/tools/kdump.py +++ b/lisa/tools/kdump.py @@ -226,6 +226,8 @@ def create(cls, node: "Node", *args: Any, **kwargs: Any) -> Tool: elif isinstance(node.os, Suse): return KdumpSuse(node) elif isinstance(node.os, CBLMariner): + if node.os.information.version.major >= 4: + return KdumpFedora(node) return KdumpCBLMariner(node) elif type(node.os) is Fedora: return KdumpFedora(node) @@ -585,7 +587,7 @@ def command(self) -> str: return "kdumpctl" def _install(self) -> bool: - assert isinstance(self.node.os, Fedora) + assert isinstance(self.node.os, (Fedora, CBLMariner)) self.node.os.install_packages("kdump-utils") return self._check_exists() diff --git a/lisa/tools/kill.py b/lisa/tools/kill.py index e26cbbb470..4b5c2b6ed1 100644 --- a/lisa/tools/kill.py +++ b/lisa/tools/kill.py @@ -6,6 +6,7 @@ from lisa.util import LisaException from lisa.util.constants import SIGKILL +from .pgrep import Pgrep from .pidof import Pidof @@ -21,17 +22,20 @@ def can_install(self) -> bool: def by_name( self, process_name: str, signum: int = SIGKILL, ignore_not_exist: bool = False ) -> None: - # attempt kill by name first - kill_by_name = self.run( - f"-s {signum} {process_name}", sudo=True, shell=True, force_run=True - ) - if kill_by_name.exit_code == 0: + # Find PIDs via pgrep (matches by process name), then kill each PID + # individually to avoid the shell interpreting the name as a job spec. + pgrep = self.node.tools[Pgrep] + processes = pgrep.get_processes(process_name) + if processes: + for proc in processes: + self.by_pid(proc.id, signum, ignore_not_exist) return - # fallback to kill by pid if first attempt fails for some reason + # Fallback: try pidof in case pgrep missed it pids = self.node.tools[Pidof].get_pids(process_name, sudo=True) - for pid in pids: - self.by_pid(pid, signum, ignore_not_exist) + if pids: + for pid in pids: + self.by_pid(pid, signum, ignore_not_exist) else: self._log.debug( f"Kill for {process_name} did not find any processes to kill." diff --git a/lisa/tools/modprobe.py b/lisa/tools/modprobe.py index 7b8a0b2cb1..3ebcca169e 100644 --- a/lisa/tools/modprobe.py +++ b/lisa/tools/modprobe.py @@ -242,6 +242,11 @@ def reload( "\nTrying to reconnect to the remote node in 2 sec..." ) time.sleep(2) + # Reset the stale SSH transport so the next retry establishes + # a fresh connection. Without this, the retry loop keeps + # reusing the broken session and every attempt fails with + # "cannot connect to TCP port 22". + self.node.close() self._log.debug( f"Time taken to reload {mod_name}: {timer.elapsed(False)} seconds" From c49f3a486dca1a27dc18af0b4f3f85922b1aee7b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 1 Jul 2026 08:49:23 +0000 Subject: [PATCH 2/2] fix: prefer pidof over pgrep in Kill.by_name for exact matching --- lisa/tools/kill.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/lisa/tools/kill.py b/lisa/tools/kill.py index 4b5c2b6ed1..15fe0dc355 100644 --- a/lisa/tools/kill.py +++ b/lisa/tools/kill.py @@ -22,20 +22,19 @@ def can_install(self) -> bool: def by_name( self, process_name: str, signum: int = SIGKILL, ignore_not_exist: bool = False ) -> None: - # Find PIDs via pgrep (matches by process name), then kill each PID - # individually to avoid the shell interpreting the name as a job spec. - pgrep = self.node.tools[Pgrep] - processes = pgrep.get_processes(process_name) - if processes: - for proc in processes: - self.by_pid(proc.id, signum, ignore_not_exist) - return - - # Fallback: try pidof in case pgrep missed it + # Prefer pidof for exact-name matching to avoid regex side-effects, + # then fall back to pgrep for processes started via sudo/shell wrappers. pids = self.node.tools[Pidof].get_pids(process_name, sudo=True) if pids: for pid in pids: self.by_pid(pid, signum, ignore_not_exist) + return + + # Fallback: try pgrep in case pidof missed it (e.g. wrapper processes) + processes = self.node.tools[Pgrep].get_processes(process_name) + if processes: + for proc in processes: + self.by_pid(proc.id, signum, ignore_not_exist) else: self._log.debug( f"Kill for {process_name} did not find any processes to kill."