Skip to content

Commit

Permalink
Dpdk: 32bit test
Browse files Browse the repository at this point in the history
Allow building rdma-core and dpdk as 32bit applications before test.
Add the 32bit test.

This is a beefy commit because we must extend the Installer class
to support 32bit builds. This means adding invalid arch checks,
adding arch checks to the DependencyInstaller, adding
changes to PKG_CONFIG_PATH and allowing updating environment variables
everywhere.

The result is that we can install DPDK and RDMA core and run their
32bit versions to test basic send/receive stuff.
  • Loading branch information
mcgov committed Jan 10, 2025
1 parent c3599a8 commit 3ca23a2
Show file tree
Hide file tree
Showing 7 changed files with 469 additions and 112 deletions.
13 changes: 10 additions & 3 deletions lisa/tools/meson.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# Licensed under the MIT license.

from pathlib import PurePath
from typing import cast
from typing import Dict, Optional, cast

from semver import VersionInfo

Expand Down Expand Up @@ -50,15 +50,22 @@ def _install(self) -> bool:
)
return self._check_exists()

def setup(self, args: str, cwd: PurePath, build_dir: str = "build") -> PurePath:
def setup(
self,
args: str,
cwd: PurePath,
build_dir: str = "build",
update_envs: Optional[Dict[str, str]] = None,
) -> PurePath:
self.run(
f"{args} {build_dir}",
parameters=f"{args} {build_dir}",
force_run=True,
shell=True,
cwd=cwd,
expected_exit_code=0,
expected_exit_code_failure_message=(
f"Could not configure {str(cwd)} with meson using args {args}"
),
update_envs=update_envs,
)
return cwd.joinpath(build_dir)
143 changes: 122 additions & 21 deletions microsoft/testsuites/dpdk/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,24 @@
from lisa.operating_system import Debian, Fedora, Oracle, Posix, Suse, Ubuntu
from lisa.tools import Git, Lscpu, Tar, Wget
from lisa.tools.lscpu import CpuArchitecture
from lisa.util import UnsupportedDistroException
from lisa.util import UnsupportedCpuArchitectureException, UnsupportedDistroException

DPDK_STABLE_GIT_REPO = "https://dpdk.org/git/dpdk-stable"

# 32bit test relies on newer versions of DPDK.
# Release candidates are not in stable, so use the github mirror.
DPDK_32BIT_DEFAULT_BRANCH = "v24.11"
# MANA testing requires newer versions of DPDK.
DPDK_MANA_DEFAULT_BRANCH = "v24.11"
# azure routing table magic subnet prefix
# signals 'route all traffic on this subnet'
AZ_ROUTE_ALL_TRAFFIC = "0.0.0.0/0"

ARCH_COMPATIBILITY_MATRIX = {
CpuArchitecture.X64: [CpuArchitecture.X64],
CpuArchitecture.I386: [CpuArchitecture.I386, CpuArchitecture.X64],
CpuArchitecture.ARM64: [CpuArchitecture.ARM64],
}


# Attempt to clean up the DPDK package dependency mess
# Make a Installer class that implements the common steps
Expand All @@ -36,7 +46,7 @@ class OsPackageDependencies:
# the packages to install on that OS.
def __init__(
self,
matcher: Callable[[Posix], bool],
matcher: Callable[[Posix, Optional[CpuArchitecture]], bool],
packages: Optional[Sequence[Union[str, Tool, Type[Tool]]]] = None,
stop_on_match: bool = False,
) -> None:
Expand All @@ -47,14 +57,21 @@ def __init__(

class DependencyInstaller:
# provide a list of OsPackageDependencies for a project
def __init__(self, requirements: List[OsPackageDependencies]) -> None:
def __init__(
self,
requirements: List[OsPackageDependencies],
arch: Optional[CpuArchitecture] = None,
) -> None:
self.requirements = requirements
self._arch = arch

# evaluate the list of package dependencies,
def install_required_packages(
self, node: Node, extra_args: Union[List[str], None]
self,
os: Posix,
extra_args: Union[List[str], None],
arch: Optional[CpuArchitecture] = None,
) -> None:
os = node.os
assert isinstance(os, Posix), (
"DependencyInstaller is not compatible with this OS: "
f"{os.information.vendor} {os.information.release}"
Expand All @@ -63,13 +80,16 @@ def install_required_packages(
# stop on list end or if exclusive_match parameter is true.
packages: List[Union[str, Tool, Type[Tool]]] = []
for requirement in self.requirements:
if requirement.matcher(os) and requirement.packages:
packages += requirement.packages
if requirement.matcher(os, arch):
if requirement.packages is not None and len(requirement.packages) > 0:
packages += requirement.packages
if requirement.stop_on_match:
break

os.install_packages(packages=packages, extra_args=extra_args)

# NOTE: It is up to the caller to raise an exception on an invalid OS
# see unsupported_os_thrower as a catch-all 'list end' function


class Downloader:
Expand Down Expand Up @@ -201,23 +221,43 @@ def _uninstall(self) -> None:
def _install_dependencies(self) -> None:
if self._os_dependencies is not None:
self._os_dependencies.install_required_packages(
self._node, extra_args=self._package_manager_extra_args
self._os, extra_args=self._package_manager_extra_args, arch=self._arch
)

# define how to check the installed version
def get_installed_version(self) -> VersionInfo:
raise NotImplementedError(f"get_installed_version {self._err_msg}")

def _should_install(self, required_version: Optional[VersionInfo] = None) -> bool:
return (not self._check_if_installed()) or (
required_version is not None
and required_version > self.get_installed_version()
def _should_install(
self,
required_version: Optional[VersionInfo] = None,
required_arch: Optional[CpuArchitecture] = None,
) -> bool:
return (
(not self._check_if_installed())
# NOTE: Taking advantage of longer-than-expected lifetimes here.
# If the tool still exists we ~should~ be able to check the old version
# from a previous test environment.
# At the moment, we use create() to force re-initialization.
# If we ever fix things so that we use .get,
# we will need this check. So add it now.q
or bool(self._arch and required_arch != self._arch)
or (
required_version is not None
and required_version > self.get_installed_version()
)
)

# run the defined setup and installation steps.
def do_installation(self, required_version: Optional[VersionInfo] = None) -> None:
def do_installation(
self,
required_version: Optional[VersionInfo] = None,
required_arch: Optional[CpuArchitecture] = None,
) -> None:
self._setup_node()
if self._should_install():
if self._should_install(
required_version=required_version, required_arch=required_arch
):
self._uninstall()
self._install_dependencies()
self._install()
Expand All @@ -227,6 +267,7 @@ def __init__(
node: Node,
os_dependencies: Optional[DependencyInstaller] = None,
downloader: Optional[Downloader] = None,
arch: Optional[CpuArchitecture] = None,
) -> None:
self._node = node
if not isinstance(self._node.os, Posix):
Expand All @@ -237,11 +278,22 @@ def __init__(
self._package_manager_extra_args: List[str] = []
self._os_dependencies = os_dependencies
self._downloader = downloader
self._arch = arch
if self._arch:
# avoid building/running arm64 on i386, etc
system_arch = self._node.tools[Lscpu].get_architecture()
if system_arch not in ARCH_COMPATIBILITY_MATRIX[self._arch]:
raise UnsupportedCpuArchitectureException(system_arch)


# Base class for package manager installation
class PackageManagerInstall(Installer):
def __init__(self, node: Node, os_dependencies: DependencyInstaller) -> None:
def __init__(
self,
node: Node,
os_dependencies: DependencyInstaller,
arch: Optional[CpuArchitecture],
) -> None:
super().__init__(node, os_dependencies)

# uninstall from the package manager
Expand All @@ -250,7 +302,10 @@ def _uninstall(self) -> None:
return
if self._os_dependencies is not None:
for os_package_check in self._os_dependencies.requirements:
if os_package_check.matcher(self._os) and os_package_check.packages:
if (
os_package_check.matcher(self._os, self._arch)
and os_package_check.packages
):
self._os.uninstall_packages(os_package_check.packages)
if os_package_check.stop_on_match:
break
Expand All @@ -263,7 +318,10 @@ def _check_if_installed(self) -> bool:
# This will take too long if it's more than a few packages.
if self._os_dependencies is not None:
for os_package_check in self._os_dependencies.requirements:
if os_package_check.matcher(self._os) and os_package_check.packages:
if (
os_package_check.matcher(self._os, self._arch)
and os_package_check.packages
):
for pkg in os_package_check.packages:
if not self._os.package_exists(pkg):
return False
Expand All @@ -272,11 +330,40 @@ def _check_if_installed(self) -> bool:
return True


def force_dpdk_default_source(variables: Dict[str, Any]) -> None:
# force specific default sources for arch tests (os-independent)
def force_dpdk_default_source_variables(
variables: Dict[str, Any], build_arch: Optional[CpuArchitecture] = None
) -> None:
if build_arch:
variables["build_arch"] = build_arch

if build_arch == CpuArchitecture.I386:
if not variables.get("dpdk_branch", None):
# assign a default branch with needed MANA commits for 32bit test
variables["dpdk_branch"] = DPDK_32BIT_DEFAULT_BRANCH
if not variables.get("dpdk_source", None):
variables["dpdk_source"] = DPDK_STABLE_GIT_REPO


# force source builds for distros and environments which need a later verison.
# ie. ubuntu 18.04 and MANA
def set_default_dpdk_source(node: Node, variables: Dict[str, Any]) -> None:
# DPDK packages 17.11 which is EOL and doesn't have the
# net_vdev_netvsc pmd used for simple handling of hyper-v
# guests. Force stable source build on this platform.
# Default to 20.11 unless another version is provided by the
# user. 20.11 is the latest dpdk version for 18.04.
if isinstance(node.os, Ubuntu) and node.os.information.version < "20.4.0":
variables["dpdk_source"] = variables.get("dpdk_source", DPDK_STABLE_GIT_REPO)
variables["dpdk_branch"] = variables.get("dpdk_branch", "v20.11")
# MANA runs need a later version of DPDK
if node.nics.is_mana_device_present():
variables["dpdk_source"] = variables.get("dpdk_source", DPDK_STABLE_GIT_REPO)
variables["dpdk_branch"] = variables.get(
"dpdk_branch", DPDK_MANA_DEFAULT_BRANCH
)


_UBUNTU_LTS_VERSIONS = ["24.4.0", "22.4.0", "20.4.0", "18.4.0"]


Expand Down Expand Up @@ -383,13 +470,27 @@ def is_url_for_git_repo(url: str) -> bool:
return scheme == "git" or check_for_git_https


def unsupported_os_thrower(os: Posix) -> bool:
# utility matcher to throw an OS error type for a match.
def unsupported_os_thrower(os: Posix, arch: Optional[CpuArchitecture]) -> bool:
if arch:
message_suffix = f"OS and Architecture ({arch})"
else:
message_suffix = "OS"
raise UnsupportedDistroException(
os,
message=("Installer did not define dependencies for this os."),
message=f"Installer did not define dependencies for this {message_suffix}",
)


# utility matcher to throw an OS error when i386 is not supported
def i386_not_implemented_thrower(os: Posix, arch: Optional[CpuArchitecture]) -> bool:
if arch and arch == CpuArchitecture.I386:
raise NotImplementedError(
"i386 is not implemented for this (installer,OS) combo."
)
return False


def get_debian_backport_repo_args(os: Debian) -> List[str]:
# ex: 'bionic-backports' or 'buster-backports'
# these backport repos are available for the older OS's
Expand Down
4 changes: 2 additions & 2 deletions microsoft/testsuites/dpdk/dpdkperf.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from lisa.tools import Lscpu
from lisa.tools.hugepages import HugePageSize
from lisa.util import constants
from microsoft.testsuites.dpdk.common import force_dpdk_default_source
from microsoft.testsuites.dpdk.common import force_dpdk_default_source_variables
from microsoft.testsuites.dpdk.dpdkutil import (
DpdkTestResources,
SkippedException,
Expand Down Expand Up @@ -256,7 +256,7 @@ def perf_dpdk_multi_queue_netvsc_pmd(
def perf_dpdk_l3fwd_ntttcp_tcp(
self, environment: Environment, log: Logger, variables: Dict[str, Any]
) -> None:
force_dpdk_default_source(variables)
force_dpdk_default_source_variables(variables)
verify_dpdk_l3fwd_ntttcp_tcp(
environment,
log,
Expand Down
Loading

0 comments on commit 3ca23a2

Please sign in to comment.