diff --git a/lisa/tools/wget.py b/lisa/base_tools/wget.py similarity index 63% rename from lisa/tools/wget.py rename to lisa/base_tools/wget.py index 373bb70988..4ceafe700c 100644 --- a/lisa/tools/wget.py +++ b/lisa/base_tools/wget.py @@ -1,17 +1,33 @@ import pathlib import re -from typing import cast +from typing import TYPE_CHECKING from lisa.executable import Tool -from lisa.operating_system import Posix from lisa.util import LisaException +if TYPE_CHECKING: + from lisa.operating_system import Posix + class Wget(Tool): __pattern_path = re.compile( r"([\w\W]*?)(-|File) (‘|')(?P.+?)(’|') (saved|already there)" ) + # regex to validate url + # source - + # https://github.com/django/django/blob/stable/1.3.x/django/core/validators.py#L45 + __url_pattern = re.compile( + r"^(?:http|ftp)s?://" # http:// or https:// + r"(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)" + r"+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|" # ...domain + r"localhost|" # localhost... + r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})" # ...or ip + r"(?::\d+)?" # optional port + r"(?:/?|[/?]\S+)$", + re.IGNORECASE, + ) + @property def command(self) -> str: return "wget" @@ -21,13 +37,20 @@ def can_install(self) -> bool: return True def install(self) -> bool: - posix_os: Posix = cast(Posix, self.node.os) + posix_os: Posix = self.node.os # type: ignore posix_os.install_packages([self]) return self._check_exists() def get( - self, url: str, file_path: str = "", filename: str = "", overwrite: bool = True + self, + url: str, + file_path: str = "", + filename: str = "", + overwrite: bool = True, + executable: bool = False, ) -> str: + if re.match(self.__url_pattern, url) is None: + raise LisaException(f"Invalid URL '{url}'") # create folder when it doesn't exist self.node.execute(f"mkdir -p {file_path}", shell=True) # combine download file path @@ -52,4 +75,7 @@ def get( actual_file_path = self.node.execute(f"ls {download_file_path}", shell=True) if actual_file_path.exit_code != 0: raise LisaException(f"File {actual_file_path} doesn't exist.") + if executable: + self.node.execute(f"chmod +x {actual_file_path}") + return actual_file_path.stdout diff --git a/lisa/features/__init__.py b/lisa/features/__init__.py index f860c7a298..7e07b0dbf8 100644 --- a/lisa/features/__init__.py +++ b/lisa/features/__init__.py @@ -1,7 +1,8 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +from .gpu import Gpu from .serial_console import SerialConsole from .startstop import StartStop -__all__ = ["SerialConsole", "StartStop"] +__all__ = ["Gpu", "SerialConsole", "StartStop"] diff --git a/lisa/features/gpu.py b/lisa/features/gpu.py new file mode 100644 index 0000000000..ffc177d660 --- /dev/null +++ b/lisa/features/gpu.py @@ -0,0 +1,148 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +import re +from enum import Enum +from typing import Any + +from lisa.base_tools.wget import Wget +from lisa.feature import Feature +from lisa.operating_system import Redhat, Ubuntu +from lisa.tools import Uname +from lisa.util import LisaException, SkippedException +from lisa.util.logger import get_logger + +FEATURE_NAME_GPU = "Gpu" + +ComputeSDK = Enum( + "ComputeSDK", + [ + # GRID Driver + "GRID", + # CUDA Driver + "CUDA", + ], +) + +# Link to the latest GRID driver +# The DIR link is +# https://download.microsoft.com/download/9/5/c/95c667ff-ab95-4c56-89e0-e13e9a76782d/NVIDIA-Linux-x86_64-460.32.03-grid-azure.run +DEFAULT_GRID_DRIVER_URL = "https://go.microsoft.com/fwlink/?linkid=874272" + + +class Gpu(Feature): + def __init__(self, node: Any, platform: Any) -> None: + super().__init__(node, platform) + self._log = get_logger("feature", self.name(), self._node.log) + + @classmethod + def name(cls) -> str: + return FEATURE_NAME_GPU + + def _is_supported(self) -> bool: + raise NotImplementedError() + + # download and install NVIDIA grid driver + def _install_grid_driver(self, driver_url: str) -> None: + self._log.debug("Starting GRID driver installation") + # download and install the NVIDIA GRID driver + wget_tool = self._node.tools[Wget] + grid_file_path = wget_tool.get( + driver_url, + str(self._node.working_path), + "NVIDIA-Linux-x86_64-grid.run", + executable=True, + ) + result = self._node.execute( + f"{grid_file_path} --no-nouveau-check --silent --no-cc-version-check" + ) + if result.exit_code != 0: + raise LisaException( + "Failed to install the GRID driver! " + f"exit-code: {result.exit_code} stderr: {result.stderr}" + ) + + self._log.debug("Successfully installed the GRID driver") + + # download and install CUDA Driver + def _install_cuda_driver(self, version: str) -> None: + self._log.debug("Starting CUDA driver installation") + cuda_repo = "" + os_version = self._node.os.os_version + + if isinstance(self._node.os, Redhat): + release = os_version.release.split(".")[0] + cuda_repo_pkg = f"cuda-repo-rhel{release}-{version}.x86_64.rpm" + cuda_repo = ( + "http://developer.download.nvidia.com/" + f"compute/cuda/repos/rhel{release}/x86_64/{cuda_repo_pkg}" + ) + elif isinstance(self._node.os, Ubuntu): + release = re.sub("[^0-9]+", "", os_version.release) + cuda_repo_pkg = f"cuda-repo-ubuntu{release}_{version}_amd64.deb" + cuda_repo = ( + "http://developer.download.nvidia.com/compute/" + f"cuda/repos/ubuntu{release}/x86_64/{cuda_repo_pkg}" + ) + else: + raise LisaException( + f"Distro {self._node.os.__class__.__name__}" + "not supported to install CUDA driver." + ) + + # download and install the cuda driver package from the repo + self._node.os._install_package_from_url(f"{cuda_repo}", signed=False) + + def _install_gpu_dep(self) -> None: + uname_tool = self._node.tools[Uname] + uname_ver = uname_tool.get_linux_information().uname_version + + # install dependency libraries for distros + if isinstance(self._node.os, Redhat): + # install the kernel-devel and kernel-header packages + package_name = f"kernel-devel-{uname_ver} kernel-headers-{uname_ver}" + self._node.os.install_packages(package_name) + # mesa-libEGL install/update is require to avoid a conflict between + # libraries - bugzilla.redhat 1584740 + package_name = "mesa-libGL mesa-libEGL libglvnd-devel" + self._node.os.install_packages(package_name) + # install dkms + package_name = "dkms" + self._node.os.install_packages(package_name, signed=False) + elif isinstance(self._node.os, Ubuntu): + package_name = ( + f"build-essential libelf-dev linux-tools-{uname_ver}" + f" linux-cloud-tools-{uname_ver} python libglvnd-dev ubuntu-desktop" + ) + self._node.os.install_packages(package_name) + else: + raise LisaException( + f"Distro {self._node.os.__class__.__name__}" + " is not supported for GPU." + ) + + def check_support(self) -> None: + # TODO: more supportability can be defined here + if not self._is_supported(): + raise SkippedException(f"GPU is not supported with distro {self._node.os}") + + def install_compute_sdk( + self, driver: ComputeSDK = ComputeSDK.CUDA, version: str = "" + ) -> None: + # install GPU dependencies before installing driver + self._install_gpu_dep() + + # install the driver + if driver == ComputeSDK.GRID: + if version == "": + version = DEFAULT_GRID_DRIVER_URL + self._install_grid_driver(version) + elif driver == ComputeSDK.CUDA: + if version == "": + version = "10.1.105-1" + self._install_cuda_driver(version) + else: + raise LisaException( + f"{ComputeSDK} is invalid." + "No valid driver SDK name provided to install." + ) diff --git a/lisa/features/startstop.py b/lisa/features/startstop.py index 02e1f9c41f..2dd2a15692 100644 --- a/lisa/features/startstop.py +++ b/lisa/features/startstop.py @@ -4,6 +4,7 @@ from typing import Any from lisa.feature import Feature +from lisa.util.logger import get_logger FEATURE_NAME_STARTSTOP = "StartStop" @@ -11,7 +12,7 @@ class StartStop(Feature): def __init__(self, node: Any, platform: Any) -> None: super().__init__(node, platform) - self._log = self._node.log + self._log = get_logger("feature", self.name(), self._node.log) @classmethod def name(cls) -> str: diff --git a/lisa/operating_system.py b/lisa/operating_system.py index b2bde79aed..2ed4a72308 100644 --- a/lisa/operating_system.py +++ b/lisa/operating_system.py @@ -2,9 +2,11 @@ # Licensed under the MIT license. import re +from dataclasses import dataclass from functools import partial from typing import TYPE_CHECKING, Any, Iterable, List, Optional, Pattern, Type, Union +from lisa.base_tools.wget import Wget from lisa.executable import Tool from lisa.util import BaseClassMixin, LisaException, get_matched_str from lisa.util.logger import get_logger @@ -17,6 +19,24 @@ _get_init_logger = partial(get_logger, name="os") +@dataclass +# OsVersion - To have full distro info. +# GetOSVersion() method at below link was useful to get distro info. +# https://github.com/microsoft/lisa/blob/master/Testscripts/Linux/utils.sh +class OsVersion: + # Vendor/Distributor + vendor: str + # Release/Version + release: str = "" + # Codename for the release + codename: str = "" + # Update available + update: str = "" + + def __str__(self) -> str: + return self.vendor + + class OperatingSystem: __lsb_release_pattern = re.compile(r"^Description:[ \t]+([\w]+)[ ]+$", re.M) __os_release_pattern_name = re.compile( @@ -36,6 +56,7 @@ def __init__(self, node: Any, is_posix: bool) -> None: self._node: Node = node self._is_posix = is_posix self._log = get_logger(name="os", parent=self._node.log) + self._os_version: Optional[OsVersion] = None @classmethod def create(cls, node: Any) -> Any: @@ -93,6 +114,13 @@ def is_windows(self) -> bool: def is_posix(self) -> bool: return self._is_posix + @property + def os_version(self) -> OsVersion: + if not self._os_version: + self._os_version = self._get_os_version() + + return self._os_version + @classmethod def _get_detect_string(cls, node: Any) -> Iterable[str]: typed_node: Node = node @@ -131,13 +159,56 @@ def _get_detect_string(cls, node: Any) -> Iterable[str]: cmd_result = typed_node.execute(cmd="cat /etc/SuSE-release", no_error_log=True) yield get_matched_str(cmd_result.stdout, cls.__suse_release_pattern) + def _get_os_version(self) -> OsVersion: + raise NotImplementedError + class Windows(OperatingSystem): + __windows_version_pattern = re.compile( + r"^OS Version:[\"\']?\s+(?P.*?)[\"\']?$" + ) + def __init__(self, node: Any) -> None: super().__init__(node, is_posix=False) + def _get_os_version(self) -> OsVersion: + os_version = OsVersion("Microsoft Corporation") + cmd_result = self._node.execute( + cmd='systeminfo | findstr /B /C:"OS Version"', + no_error_log=True, + ) + if cmd_result.exit_code == 0 and cmd_result.stdout != "": + os_version.release = get_matched_str( + cmd_result.stdout, self.__windows_version_pattern + ) + if os_version.release == "": + raise LisaException("OS version information not found") + else: + raise LisaException( + "Error getting OS version info from systeminfo command" + f"exit_code: {cmd_result.exit_code} stderr: {cmd_result.stderr}" + ) + return os_version + class Posix(OperatingSystem, BaseClassMixin): + __os_info_pattern = re.compile( + r"^(?P.*)=[\"\']?(?P.*?)[\"\']?$", re.MULTILINE + ) + # output of /etc/fedora-release - Fedora release 22 (Twenty Two) + # output of /etc/redhat-release - Scientific Linux release 7.1 (Nitrogen) + # output of /etc/os-release - + # NAME="Debian GNU/Linux" + # VERSION_ID="7" + # VERSION="7 (wheezy)" + # output of lsb_release -a + # LSB Version: :base-4.0-amd64:base-4.0-noarch:core-4.0-amd64:core-4.0-noarch + # Distributor ID: Scientific + # Description: Scientific Linux release 6.7 (Carbon) + # In most of the distros, the text in the brackets is the codename. + # This regex gets the codename for the ditsro + __distro_codename_pattern = re.compile(r"^.*\(([^)]+)") + def __init__(self, node: Any) -> None: super().__init__(node, is_posix=True) self._first_time_installation: bool = True @@ -150,15 +221,64 @@ def type_name(cls) -> str: def name_pattern(cls) -> Pattern[str]: return re.compile(f"^{cls.type_name()}$") - def _install_packages(self, packages: Union[List[str]]) -> None: + def _install_packages( + self, packages: Union[List[str]], signed: bool = True + ) -> None: + raise NotImplementedError() + + def _package_exists(self, package: str, signed: bool = True) -> bool: raise NotImplementedError() def _initialize_package_installation(self) -> None: # sub os can override it, but it's optional pass + def _get_os_version(self) -> OsVersion: + os_version = OsVersion("") + # try to set OsVersion from info in /etc/os-release. + cmd_result = self._node.execute(cmd="cat /etc/os-release", no_error_log=True) + if cmd_result.exit_code != 0: + raise LisaException( + "Error in running command 'cat /etc/os-release'" + f"exit_code: {cmd_result.exit_code} stderr: {cmd_result.stderr}" + ) + + for row in cmd_result.stdout.splitlines(): + os_release_info = self.__os_info_pattern.match(row) + if not os_release_info: + continue + if os_release_info.group("name") == "NAME": + os_version.vendor = os_release_info.group("value") + elif os_release_info.group("name") == "VERSION_ID": + os_version.release = os_release_info.group("value") + elif os_release_info.group("name") == "VERSION": + os_version.codename = get_matched_str( + os_release_info.group("value"), + self.__distro_codename_pattern, + ) + + if os_version.vendor == "": + raise LisaException("OS version information not found") + + return os_version + + def _install_package_from_url( + self, + package: str, + signed: bool = True, + ) -> None: + """ + Used if the package to be installed needs to be downloaded from a url first. + """ + # when package is URL, download the package first at the working path. + wget_tool = self._node.tools[Wget] + pkg = wget_tool.get(package, str(self._node.working_path)) + self.install_packages(pkg, signed) + def install_packages( - self, packages: Union[str, Tool, Type[Tool], List[Union[str, Tool, Type[Tool]]]] + self, + packages: Union[str, Tool, Type[Tool], List[Union[str, Tool, Type[Tool]]]], + signed: bool = True, ) -> None: package_names: List[str] = [] if not isinstance(packages, list): @@ -166,20 +286,43 @@ def install_packages( assert isinstance(packages, list), f"actual:{type(packages)}" for item in packages: - if isinstance(item, str): - package_names.append(item) - elif isinstance(item, Tool): - package_names.append(item.package_name) - else: - assert isinstance(item, type), f"actual:{type(item)}" - # Create a temp object, it doesn't trigger install. - # So they can be installed together. - tool = item.create(self._node) - package_names.append(tool.package_name) + package_names.append(self.__resolve_package_name(item)) if self._first_time_installation: self._first_time_installation = False self._initialize_package_installation() - self._install_packages(package_names) + + self._install_packages(package_names, signed) + + def package_exists( + self, package: Union[str, Tool, Type[Tool]], signed: bool = True + ) -> 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) + + def update_packages(self, packages: Union[str, Tool, Type[Tool]]) -> None: + raise NotImplementedError + + def __resolve_package_name(self, package: Union[str, Tool, Type[Tool]]) -> str: + """ + A package can be a string or a tool or a type of tool. + Resolve it to a standard package_name so it can be installed. + """ + if isinstance(package, str): + package_name = package + elif isinstance(package, Tool): + package_name = package.package_name + else: + assert isinstance(package, type), f"actual:{type(package)}" + # Create a temp object, it doesn't query. + # So they can be queried together. + tool = package.create(self._node) + package_name = tool.package_name + + return package_name class BSD(Posix): @@ -191,6 +334,10 @@ class Linux(Posix): class Debian(Linux): + __lsb_os_info_pattern = re.compile( + r"^(?P.*):(\s+)(?P.*?)?$", re.MULTILINE + ) + @classmethod def name_pattern(cls) -> Pattern[str]: return re.compile("^debian|Forcepoint|Kali$") @@ -198,12 +345,54 @@ def name_pattern(cls) -> Pattern[str]: def _initialize_package_installation(self) -> None: self._node.execute("apt-get update", sudo=True) - def _install_packages(self, packages: Union[List[str]]) -> None: + def _install_packages( + self, packages: Union[List[str]], signed: bool = True + ) -> None: command = ( f"DEBIAN_FRONTEND=noninteractive " f"apt-get -y install {' '.join(packages)}" ) - self._node.execute(command, sudo=True) + if not signed: + command += " --allow-unauthenticated" + + install_result = self._node.execute(command, sudo=True) + if install_result.exit_code != 0: + raise LisaException( + f"Failed to install {packages}. exit_code: {install_result.exit_code}" + f"stderr: {install_result.stderr}" + ) + + def _package_exists(self, package: str, signed: bool = True) -> bool: + command = f"apt list --installed | grep -Ei {package}" + result = self._node.execute(command, sudo=True) + if result.exit_code == 0: + for row in result.stdout.splitlines(): + if package in row: + return True + return False + + def _get_os_version(self) -> OsVersion: + os_version = OsVersion("") + cmd_result = self._node.execute(cmd="lsb_release -as", no_error_log=True) + if cmd_result.exit_code == 0 and cmd_result.stdout != "": + for row in cmd_result.stdout.splitlines(): + os_release_info = self.__lsb_os_info_pattern.match(row) + if os_release_info: + if os_release_info.group("name") == "Distributor ID": + os_version.vendor = os_release_info.group("value") + elif os_release_info.group("name") == "Release": + os_version.release = os_release_info.group("value") + elif os_release_info.group("name") == "Codename": + os_version.codename = os_release_info.group("value") + if os_version.vendor == "": + raise LisaException("OS version information not found") + else: + raise LisaException( + f"Command 'lsb_release -as' failed. exit_code:{cmd_result.exit_code}" + f"stderr: {cmd_result.stderr}" + ) + + return os_version class Ubuntu(Debian): @@ -221,15 +410,67 @@ class OpenBSD(BSD): class Fedora(Linux): + __fedora_release_pattern_version = re.compile(r"^.*release\s+([0-9\.]+).*$") + @classmethod def name_pattern(cls) -> Pattern[str]: return re.compile("^Fedora|fedora$") - def _install_packages(self, packages: Union[List[str]]) -> None: - self._node.execute( - f"dnf install -y {' '.join(packages)}", - sudo=True, + def _install_packages( + self, packages: Union[List[str]], signed: bool = True + ) -> None: + command = f"dnf install -y {' '.join(packages)}" + if not signed: + command += " --nogpgcheck" + + install_result = self._node.execute(command, sudo=True) + if install_result.exit_code != 0: + raise LisaException( + f"Failed to install {packages}. exit_code: {install_result.exit_code} " + f"stderr: {install_result.stderr}" + ) + else: + self._log.debug(f"{packages} is/are installed successfully.") + + def _package_exists(self, package: str, signed: bool = True) -> bool: + command = f"dnf list installed | grep -Ei {package}" + result = self._node.execute(command, sudo=True) + if result.exit_code == 0: + for row in result.stdout.splitlines(): + if package in row: + return True + + return False + + def _get_os_version(self) -> OsVersion: + os_version = OsVersion("") + cmd_result = self._node.execute( + # Typical output of 'cat /etc/fedora-release' is - + # Fedora release 22 (Twenty Two) + cmd="cat /etc/fedora-release", + no_error_log=True, ) + if cmd_result.exit_code == 0 and cmd_result.stdout != "": + for vendor in ["Fedora", "CentOS", "Red Hat", "XenServer"]: + if vendor not in cmd_result.stdout: + continue + os_version.vendor = vendor + os_version.release = get_matched_str( + cmd_result.stdout, self.__fedora_release_pattern_version + ) + os_version.codename = get_matched_str( + cmd_result.stdout, self.__distro_codename_pattern + ) + break + if os_version.vendor == "": + raise LisaException("OS version information not found") + else: + raise LisaException( + "Error in running command 'cat /etc/fedora-release'" + f"exit_code: {cmd_result.exit_code} stderr: {cmd_result.stderr}" + ) + + return os_version class Redhat(Fedora): @@ -261,8 +502,60 @@ def _initialize_package_installation(self) -> None: timeout=3600, ) - def _install_packages(self, packages: Union[List[str]]) -> None: - self._node.execute(f"yum install -y {' '.join(packages)}", sudo=True) + def _install_packages( + self, packages: Union[List[str]], signed: bool = True + ) -> None: + command = f"yum install -y {' '.join(packages)}" + if not signed: + command += " --nogpgcheck" + + install_result = self._node.execute(command, sudo=True) + # yum returns exit_code=1 if package is already installed. + # We do not want to fail if exit_code=1. + if install_result.exit_code == 1: + self._log.debug(f"{packages} is/are already installed.") + elif install_result.exit_code == 0: + self._log.debug(f"{packages} is/are installed successfully.") + else: + raise LisaException( + f"Failed to install {packages}. exit_code: {install_result.exit_code} " + f"stderr: {install_result.stderr}" + ) + + def _package_exists(self, package: str, signed: bool = True) -> bool: + command = f"yum list installed | grep -Ei {package}" + result = self._node.execute(command, sudo=True) + if result.exit_code == 0: + return True + + return False + + def _get_os_version(self) -> OsVersion: + os_version = OsVersion("") + cmd_result = self._node.execute( + cmd="cat /etc/redhat-release", no_error_log=True + ) + if cmd_result.exit_code == 0 and cmd_result.stdout != "": + for vendor in ["Red Hat", "CentOS", "XenServer"]: + if vendor not in cmd_result.stdout: + continue + os_version.vendor = vendor + os_version.release = get_matched_str( + cmd_result.stdout, self.__fedora_release_pattern_version + ) + os_version.codename = get_matched_str( + cmd_result.stdout, self.__redhat_release_pattern_bracket + ) + break + if os_version.vendor == "": + raise LisaException("OS version information not found") + else: + raise LisaException( + "Error in running command 'cat /etc/redhat-release'" + f"exit_code: {cmd_result.exit_code} stderr: {cmd_result.stderr}" + ) + + return os_version class CentOs(Redhat): @@ -289,9 +582,25 @@ def name_pattern(cls) -> Pattern[str]: def _initialize_package_installation(self) -> None: self._node.execute("zypper --non-interactive --gpg-auto-import-keys update") - def _install_packages(self, packages: Union[List[str]]) -> None: - command = f"zypper --non-interactive in {' '.join(packages)}" - self._node.execute(command, sudo=True) + def _install_packages( + self, packages: Union[List[str]], signed: bool = True + ) -> None: + command = f"zypper --non-interactive in {' '.join(packages)}" + if not signed: + command += " --no-gpg-checks" + install_result = self._node.execute(command, sudo=True) + if install_result.exit_code in (1, 100): + raise LisaException( + f"Failed to install {packages}. exit_code: {install_result.exit_code}" + f"stderr: {install_result.stderr}" + ) + elif install_result.exit_code == 0: + self._log.debug(f"{packages} is/are installed successfully.") + else: + self._log.debug( + f"{packages} is/are installed." + " A system reboot or package manager restart might be required." + ) class NixOS(Linux): diff --git a/lisa/sut_orchestrator/azure/features.py b/lisa/sut_orchestrator/azure/features.py index 64620c5bd4..e58a93c3e5 100644 --- a/lisa/sut_orchestrator/azure/features.py +++ b/lisa/sut_orchestrator/azure/features.py @@ -8,6 +8,7 @@ from lisa import features from lisa.node import Node +from lisa.operating_system import CentOs, Redhat, Suse, Ubuntu from .common import get_compute_client, get_node_context, wait_operation @@ -67,3 +68,15 @@ def _get_console_log(self, saved_path: Optional[Path]) -> bytes: log_response = requests.get(diagnostic_data.serial_console_log_blob_uri) return log_response.content + + +class Gpu(AzureFeatureMixin, features.Gpu): + def _initialize(self, *args: Any, **kwargs: Any) -> None: + super()._initialize(*args, **kwargs) + self._initialize_information(self._node) + + def _is_supported(self) -> bool: + supported_distro = (CentOs, Redhat, Ubuntu, Suse) + if not isinstance(self._node.os, supported_distro): + return False + return True diff --git a/lisa/sut_orchestrator/azure/platform_.py b/lisa/sut_orchestrator/azure/platform_.py index 5269d8fd5d..770095d70f 100644 --- a/lisa/sut_orchestrator/azure/platform_.py +++ b/lisa/sut_orchestrator/azure/platform_.py @@ -1164,6 +1164,7 @@ def _resource_sku_to_capability( excluded_features=search_space.SetSpace[str](is_allow_set=False), ) node_space.name = f"{location}_{resource_sku.name}" + node_space.features = search_space.SetSpace[str](is_allow_set=True) for sku_capability in resource_sku.capabilities: name = sku_capability.name if name == "vCPUs": @@ -1180,9 +1181,10 @@ def _resource_sku_to_capability( ) elif name == "GPUs": node_space.gpu_count = int(sku_capability.value) + # update features list if gpu feature is supported + node_space.features.update(features.Gpu.name()) # all nodes support following features - node_space.features = search_space.SetSpace[str](is_allow_set=True) node_space.features.update( [features.StartStop.name(), features.SerialConsole.name()] ) diff --git a/lisa/tools/__init__.py b/lisa/tools/__init__.py index 72e41c6a35..a91a817cf8 100644 --- a/lisa/tools/__init__.py +++ b/lisa/tools/__init__.py @@ -1,6 +1,8 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +from lisa.base_tools.wget import Wget + from .cat import Cat from .date import Date from .dmesg import Dmesg @@ -15,7 +17,6 @@ from .reboot import Reboot from .uname import Uname from .uptime import Uptime -from .wget import Wget from .who import Who __all__ = [ diff --git a/lisa/tools/lsvmbus.py b/lisa/tools/lsvmbus.py index a81a06c7c5..e2271e4993 100644 --- a/lisa/tools/lsvmbus.py +++ b/lisa/tools/lsvmbus.py @@ -1,12 +1,11 @@ import re from typing import Any, List +from lisa.base_tools.wget import Wget from lisa.executable import Tool from lisa.operating_system import Redhat, Suse, Ubuntu from lisa.util import LisaException -from .wget import Wget - # segment output of lsvmbus -vv # VMBUS ID 1: Class_ID = {525074dc-8985-46e2-8057-a307dc18a502} # - [Dynamic Memory] @@ -151,9 +150,9 @@ def _check_exists(self) -> bool: def _install_from_src(self) -> None: wget_tool = self.node.tools[Wget] - file_path = wget_tool.get(self._lsvmbus_repo, "$HOME/.local/bin") - # make the download file executable - self.node.execute(f"chmod +x {file_path}") + file_path = wget_tool.get( + self._lsvmbus_repo, "$HOME/.local/bin", executable=True + ) self._command = file_path def install(self) -> bool: diff --git a/lisa/tools/make.py b/lisa/tools/make.py index b41283a647..3dcf548440 100644 --- a/lisa/tools/make.py +++ b/lisa/tools/make.py @@ -11,8 +11,6 @@ class Make(Tool): - repo = "https://github.com/microsoft/ntttcp-for-linux" - @property def command(self) -> str: return "make"