diff --git a/lisa/operating_system.py b/lisa/operating_system.py index f94980dd5f..5d6a312d2a 100644 --- a/lisa/operating_system.py +++ b/lisa/operating_system.py @@ -404,13 +404,24 @@ def uninstall_packages( package_names = self._get_package_list(packages) self._uninstall_packages(package_names, signed, timeout, extra_args) - def package_exists(self, package: Union[str, Tool, Type[Tool]]) -> bool: + def package_exists( + self, + package: Union[str, Tool, Type[Tool]], + minimum_version: Optional[VersionInfo] = None, + ) -> bool: """ Query if a package/tool is installed on the node. Return Value - bool """ package_name = self.__resolve_package_name(package) - return self._package_exists(package_name) + exists = self._package_exists(package_name) + if exists and minimum_version: + return ( + self.get_package_information(package_name=package_name) + >= minimum_version + ) + else: + return exists def is_package_in_repo(self, package: Union[str, Tool, Type[Tool]]) -> bool: """ @@ -2094,6 +2105,31 @@ def _initialize_package_installation(self) -> None: "There are no enabled repositories defined in this image.", ) + def _uninstall_packages( + self, + packages: List[str], + signed: bool = True, + timeout: int = 600, + extra_args: Optional[List[str]] = None, + ) -> None: + add_args = self._process_extra_package_args(extra_args) + command = f"zypper --non-interactive {add_args}" + if not signed: + command += " --no-gpg-checks " + command += f" rm {' '.join(packages)}" + self.wait_running_process("zypper") + install_result = self._node.execute( + command, shell=True, sudo=True, timeout=timeout + ) + + if install_result.exit_code == 0: + self._log.debug(f"{packages} is/are removed successfully.") + else: + raise LisaException( + f"Failed to remove {packages}. exit_code: {install_result.exit_code}, " + f"stderr: {install_result.stderr}" + ) + def _install_packages( self, packages: List[str], diff --git a/lisa/tools/meson.py b/lisa/tools/meson.py index 9ca4d37ee6..59c2a019f5 100644 --- a/lisa/tools/meson.py +++ b/lisa/tools/meson.py @@ -15,13 +15,18 @@ class Meson(Tool): + _minimum_version = VersionInfo(major=0, minor=52, patch=0) + @property def command(self) -> str: return "meson" def _check_exists(self) -> bool: result = self.node.execute("meson --version", shell=True) - return result.exit_code == 0 and VersionInfo.parse(result.stdout) >= "0.52.0" + return ( + result.exit_code == 0 + and VersionInfo.parse(result.stdout) >= self._minimum_version + ) @property def can_install(self) -> bool: @@ -30,12 +35,39 @@ def can_install(self) -> bool: def _install(self) -> bool: posix_os: Posix = cast(Posix, self.node.os) # use pip to make sure we install a recent version - if (not posix_os.package_exists("meson")) or posix_os.get_package_information( - "meson", use_cached=False - ) < "0.52.0": + + package_installed = "" + package_available = "" + # packaged as 'meson' on older systems and 'python3-meson' on newer ones, + # since it's actually just a python package. + # So check for both + for pkg in [ + "python3-meson", + "meson", + ]: + if posix_os.package_exists(pkg, minimum_version=self._minimum_version): + package_installed = pkg + break + elif posix_os.is_package_in_repo(pkg): + package_available = pkg + break + + # prefer the packaged version as long as it's the right version + if package_installed: + return self._check_exists() + if package_available: + posix_os.install_packages(package_available) + # verify version is correct if it's installed from pkg manager + if not posix_os.package_exists(pkg, minimum_version=self._minimum_version): + posix_os.uninstall_packages(pkg) + package_available = "" + + # otherwise, install with pip + # this can get weird on some systems since they have started + # returning an error code if you try to use pip without a venv + if not (package_available or package_installed): username = self.node.tools[Whoami].get_username() self.node.tools[Pip].install_packages("meson", install_to_user=True) - # environment variables won't expand even when using shell=True :\ self.node.tools[Ln].create_link( f"/home/{username}/.local/bin/meson", "/usr/bin/meson", force=True ) @@ -48,6 +80,7 @@ def _install(self) -> bool: no_info_log=True, no_error_log=True, ) + return self._check_exists() def setup(self, args: str, cwd: PurePath, build_dir: str = "build") -> PurePath: diff --git a/microsoft/testsuites/dpdk/common.py b/microsoft/testsuites/dpdk/common.py index 2a7be6a511..4ab62e9d6b 100644 --- a/microsoft/testsuites/dpdk/common.py +++ b/microsoft/testsuites/dpdk/common.py @@ -39,10 +39,12 @@ def __init__( matcher: Callable[[Posix], bool], packages: Optional[Sequence[Union[str, Tool, Type[Tool]]]] = None, stop_on_match: bool = False, + requires_reboot: bool = False, ) -> None: self.matcher = matcher self.packages = packages self.stop_on_match = stop_on_match + self.requires_reboot = requires_reboot class DependencyInstaller: @@ -65,6 +67,10 @@ def install_required_packages( for requirement in self.requirements: if requirement.matcher(os) and requirement.packages: packages += requirement.packages + if requirement.requires_reboot: + os.install_packages(packages=packages, extra_args=extra_args) + packages = [] + node.reboot() if requirement.stop_on_match: break os.install_packages(packages=packages, extra_args=extra_args) diff --git a/microsoft/testsuites/dpdk/dpdktestpmd.py b/microsoft/testsuites/dpdk/dpdktestpmd.py index 8d5f8629d2..f67f9d645d 100644 --- a/microsoft/testsuites/dpdk/dpdktestpmd.py +++ b/microsoft/testsuites/dpdk/dpdktestpmd.py @@ -63,6 +63,7 @@ and bool(x.get_kernel_information().version >= "5.15.0") and x.is_package_in_repo("linux-modules-extra-azure"), packages=["linux-modules-extra-azure"], + requires_reboot=True, ), OsPackageDependencies( matcher=lambda x: isinstance(x, Debian), @@ -105,8 +106,6 @@ "dpkg-dev", "pkg-config", "python3-pip", - "python3-pyelftools", - "python-pyelftools", # 18.04 doesn't need linux-modules-extra-azure # since it will never have MANA support ], @@ -127,7 +126,6 @@ "build-essential", "libnuma-dev", "libmnl-dev", - "python3-pyelftools", "libelf-dev", "pkg-config", ], @@ -226,7 +224,10 @@ def _setup_node(self) -> None: # which breaks when another tool checks for it's existence before building... # like cmake, meson, make, autoconf, etc. self._node.tools[Ninja].install() - self._node.tools[Pip].install_packages("pyelftools") + if not isinstance(self._os, Debian): + self._os.install_packages("python3-pyelftools") + else: + self._node.tools[Pip].install_packages("pyelftools") def _uninstall(self) -> None: # undo source installation (thanks ninja) diff --git a/microsoft/testsuites/dpdk/rdmacore.py b/microsoft/testsuites/dpdk/rdmacore.py index 0a0d650e1b..44e3efb18f 100644 --- a/microsoft/testsuites/dpdk/rdmacore.py +++ b/microsoft/testsuites/dpdk/rdmacore.py @@ -149,7 +149,8 @@ def _check_if_installed(self) -> bool: def _setup_node(self) -> None: if isinstance(self._os, (Debian, Fedora, Suse)): - self._os.uninstall_packages("rdma-core") + if self._os.package_exists("rdma-core"): + self._os.uninstall_packages("rdma-core") if isinstance(self._os, Fedora): self._os.group_install_packages("Development Tools") super()._setup_node()