1995fb337SOwen Hilyard# SPDX-License-Identifier: BSD-3-Clause 2995fb337SOwen Hilyard# Copyright(c) 2010-2021 Intel Corporation 378534506SJuraj Linkeš# Copyright(c) 2022-2023 University of New Hampshire 478534506SJuraj Linkeš# Copyright(c) 2023 PANTHEON.tech s.r.o. 5b935bdc3SLuca Vizzarro# Copyright(c) 2024 Arm Limited 6995fb337SOwen Hilyard 76ef07151SJuraj Linkeš"""Testbed configuration and test suite specification. 86ef07151SJuraj Linkeš 96ef07151SJuraj LinkešThis package offers classes that hold real-time information about the testbed, hold test run 106ef07151SJuraj Linkešconfiguration describing the tested testbed and a loader function, :func:`load_config`, which loads 11b935bdc3SLuca Vizzarrothe YAML test run configuration file and validates it against the :class:`Configuration` Pydantic 12b935bdc3SLuca Vizzarromodel. 136ef07151SJuraj Linkeš 146ef07151SJuraj LinkešThe YAML test run configuration file is parsed into a dictionary, parts of which are used throughout 15b935bdc3SLuca Vizzarrothis package. The allowed keys and types inside this dictionary map directly to the 16b935bdc3SLuca Vizzarro:class:`Configuration` model, its fields and sub-models. 176ef07151SJuraj Linkeš 186ef07151SJuraj LinkešThe test run configuration has two main sections: 196ef07151SJuraj Linkeš 2085ceeeceSJuraj Linkeš * The :class:`TestRunConfiguration` which defines what tests are going to be run 216ef07151SJuraj Linkeš and how DPDK will be built. It also references the testbed where these tests and DPDK 226ef07151SJuraj Linkeš are going to be run, 236ef07151SJuraj Linkeš * The nodes of the testbed are defined in the other section, 246ef07151SJuraj Linkeš a :class:`list` of :class:`NodeConfiguration` objects. 256ef07151SJuraj Linkeš 266ef07151SJuraj LinkešThe real-time information about testbed is supposed to be gathered at runtime. 276ef07151SJuraj Linkeš 28b935bdc3SLuca VizzarroThe classes defined in this package make heavy use of :mod:`pydantic`. 29b935bdc3SLuca VizzarroNearly all of them are frozen: 306ef07151SJuraj Linkeš 316ef07151SJuraj Linkeš * Frozen makes the object immutable. This enables further optimizations, 326ef07151SJuraj Linkeš and makes it thread safe should we ever want to move in that direction. 33995fb337SOwen Hilyard""" 34995fb337SOwen Hilyard 35f9957667STomáš Ďurovecimport tarfile 36b935bdc3SLuca Vizzarrofrom enum import Enum, auto, unique 37b935bdc3SLuca Vizzarrofrom functools import cached_property 38b935bdc3SLuca Vizzarrofrom pathlib import Path, PurePath 39b935bdc3SLuca Vizzarrofrom typing import TYPE_CHECKING, Annotated, Any, Literal, NamedTuple 40995fb337SOwen Hilyard 41995fb337SOwen Hilyardimport yaml 42b935bdc3SLuca Vizzarrofrom pydantic import ( 43b935bdc3SLuca Vizzarro BaseModel, 44b935bdc3SLuca Vizzarro ConfigDict, 45b935bdc3SLuca Vizzarro Field, 46b935bdc3SLuca Vizzarro ValidationError, 47b935bdc3SLuca Vizzarro field_validator, 48b935bdc3SLuca Vizzarro model_validator, 49b935bdc3SLuca Vizzarro) 5034ea6db2SLuca Vizzarrofrom typing_extensions import Self 51995fb337SOwen Hilyard 52840b1e01SJuraj Linkešfrom framework.exception import ConfigurationError 53b935bdc3SLuca Vizzarrofrom framework.utils import REGEX_FOR_PCI_ADDRESS, StrEnum 54b935bdc3SLuca Vizzarro 55b935bdc3SLuca Vizzarroif TYPE_CHECKING: 56b935bdc3SLuca Vizzarro from framework.test_suite import TestSuiteSpec 57b935bdc3SLuca Vizzarro 58b935bdc3SLuca Vizzarro 59b935bdc3SLuca Vizzarroclass FrozenModel(BaseModel): 60b935bdc3SLuca Vizzarro """A pre-configured :class:`~pydantic.BaseModel`.""" 61b935bdc3SLuca Vizzarro 62b935bdc3SLuca Vizzarro #: Fields are set as read-only and any extra fields are forbidden. 63b935bdc3SLuca Vizzarro model_config = ConfigDict(frozen=True, extra="forbid") 6478534506SJuraj Linkeš 6578534506SJuraj Linkeš 6678534506SJuraj Linkeš@unique 6778534506SJuraj Linkešclass Architecture(StrEnum): 686ef07151SJuraj Linkeš r"""The supported architectures of :class:`~framework.testbed_model.node.Node`\s.""" 696ef07151SJuraj Linkeš 706ef07151SJuraj Linkeš #: 7178534506SJuraj Linkeš i686 = auto() 726ef07151SJuraj Linkeš #: 7378534506SJuraj Linkeš x86_64 = auto() 746ef07151SJuraj Linkeš #: 7578534506SJuraj Linkeš x86_32 = auto() 766ef07151SJuraj Linkeš #: 7778534506SJuraj Linkeš arm64 = auto() 786ef07151SJuraj Linkeš #: 7978534506SJuraj Linkeš ppc64le = auto() 8078534506SJuraj Linkeš 8178534506SJuraj Linkeš 8278534506SJuraj Linkeš@unique 8378534506SJuraj Linkešclass OS(StrEnum): 846ef07151SJuraj Linkeš r"""The supported operating systems of :class:`~framework.testbed_model.node.Node`\s.""" 856ef07151SJuraj Linkeš 866ef07151SJuraj Linkeš #: 8778534506SJuraj Linkeš linux = auto() 886ef07151SJuraj Linkeš #: 8978534506SJuraj Linkeš freebsd = auto() 906ef07151SJuraj Linkeš #: 9178534506SJuraj Linkeš windows = auto() 9278534506SJuraj Linkeš 9378534506SJuraj Linkeš 9478534506SJuraj Linkeš@unique 9578534506SJuraj Linkešclass CPUType(StrEnum): 966ef07151SJuraj Linkeš r"""The supported CPUs of :class:`~framework.testbed_model.node.Node`\s.""" 976ef07151SJuraj Linkeš 986ef07151SJuraj Linkeš #: 9978534506SJuraj Linkeš native = auto() 1006ef07151SJuraj Linkeš #: 10178534506SJuraj Linkeš armv8a = auto() 1026ef07151SJuraj Linkeš #: 10378534506SJuraj Linkeš dpaa2 = auto() 1046ef07151SJuraj Linkeš #: 10578534506SJuraj Linkeš thunderx = auto() 1066ef07151SJuraj Linkeš #: 10778534506SJuraj Linkeš xgene1 = auto() 10878534506SJuraj Linkeš 10978534506SJuraj Linkeš 11078534506SJuraj Linkeš@unique 11178534506SJuraj Linkešclass Compiler(StrEnum): 1126ef07151SJuraj Linkeš r"""The supported compilers of :class:`~framework.testbed_model.node.Node`\s.""" 1136ef07151SJuraj Linkeš 1146ef07151SJuraj Linkeš #: 11578534506SJuraj Linkeš gcc = auto() 1166ef07151SJuraj Linkeš #: 11778534506SJuraj Linkeš clang = auto() 1186ef07151SJuraj Linkeš #: 11978534506SJuraj Linkeš icc = auto() 1206ef07151SJuraj Linkeš #: 12178534506SJuraj Linkeš msvc = auto() 12278534506SJuraj Linkeš 12378534506SJuraj Linkeš 124634bed13SJuraj Linkeš@unique 125b935bdc3SLuca Vizzarroclass TrafficGeneratorType(str, Enum): 1266ef07151SJuraj Linkeš """The supported traffic generators.""" 1276ef07151SJuraj Linkeš 1286ef07151SJuraj Linkeš #: 129b935bdc3SLuca Vizzarro SCAPY = "SCAPY" 130634bed13SJuraj Linkeš 131634bed13SJuraj Linkeš 132b935bdc3SLuca Vizzarroclass HugepageConfiguration(FrozenModel): 133*3fbb93cfSLuca Vizzarro r"""The hugepage configuration of :class:`~framework.testbed_model.node.Node`\s.""" 1346ef07151SJuraj Linkeš 135*3fbb93cfSLuca Vizzarro #: The number of hugepages to allocate. 136c0dd39deSNicholas Pratte number_of: int 137*3fbb93cfSLuca Vizzarro #: If :data:`True`, the hugepages will be configured on the first NUMA node. 138b76d80a4SJuraj Linkeš force_first_numa: bool 139b76d80a4SJuraj Linkeš 140b76d80a4SJuraj Linkeš 141b935bdc3SLuca Vizzarroclass PortConfig(FrozenModel): 142*3fbb93cfSLuca Vizzarro r"""The port configuration of :class:`~framework.testbed_model.node.Node`\s.""" 1436ef07151SJuraj Linkeš 144*3fbb93cfSLuca Vizzarro #: The PCI address of the port. 145*3fbb93cfSLuca Vizzarro pci: str = Field(pattern=REGEX_FOR_PCI_ADDRESS) 146*3fbb93cfSLuca Vizzarro #: The driver that the kernel should bind this device to for DPDK to use it. 147*3fbb93cfSLuca Vizzarro os_driver_for_dpdk: str = Field(examples=["vfio-pci", "mlx5_core"]) 148*3fbb93cfSLuca Vizzarro #: The operating system driver name when the operating system controls the port. 149*3fbb93cfSLuca Vizzarro os_driver: str = Field(examples=["i40e", "ice", "mlx5_core"]) 150*3fbb93cfSLuca Vizzarro #: The name of the peer node this port is connected to. 151*3fbb93cfSLuca Vizzarro peer_node: str 152*3fbb93cfSLuca Vizzarro #: The PCI address of the peer port connected to this port. 153*3fbb93cfSLuca Vizzarro peer_pci: str = Field(pattern=REGEX_FOR_PCI_ADDRESS) 15488489c05SJeremy Spewock 15588489c05SJeremy Spewock 156b935bdc3SLuca Vizzarroclass TrafficGeneratorConfig(FrozenModel): 157*3fbb93cfSLuca Vizzarro """A protocol required to define traffic generator types.""" 1586ef07151SJuraj Linkeš 159*3fbb93cfSLuca Vizzarro #: The traffic generator type the child class is required to define to be distinguished among 160*3fbb93cfSLuca Vizzarro #: others. 161b935bdc3SLuca Vizzarro type: TrafficGeneratorType 162634bed13SJuraj Linkeš 163634bed13SJuraj Linkeš 164634bed13SJuraj Linkešclass ScapyTrafficGeneratorConfig(TrafficGeneratorConfig): 1656ef07151SJuraj Linkeš """Scapy traffic generator specific configuration.""" 1666ef07151SJuraj Linkeš 167b935bdc3SLuca Vizzarro type: Literal[TrafficGeneratorType.SCAPY] 168634bed13SJuraj Linkeš 169634bed13SJuraj Linkeš 170b935bdc3SLuca Vizzarro#: A union type discriminating traffic generators by the `type` field. 171b935bdc3SLuca VizzarroTrafficGeneratorConfigTypes = Annotated[ScapyTrafficGeneratorConfig, Field(discriminator="type")] 172b935bdc3SLuca Vizzarro 173*3fbb93cfSLuca Vizzarro#: Comma-separated list of logical cores to use. An empty string means use all lcores. 174b935bdc3SLuca VizzarroLogicalCores = Annotated[ 175b935bdc3SLuca Vizzarro str, 176b935bdc3SLuca Vizzarro Field( 177b935bdc3SLuca Vizzarro examples=["1,2,3,4,5,18-22", "10-15"], 178b935bdc3SLuca Vizzarro pattern=r"^(([0-9]+|([0-9]+-[0-9]+))(,([0-9]+|([0-9]+-[0-9]+)))*)?$", 179b935bdc3SLuca Vizzarro ), 180b935bdc3SLuca Vizzarro] 181b935bdc3SLuca Vizzarro 182b935bdc3SLuca Vizzarro 183b935bdc3SLuca Vizzarroclass NodeConfiguration(FrozenModel): 184*3fbb93cfSLuca Vizzarro r"""The configuration of :class:`~framework.testbed_model.node.Node`\s.""" 1856ef07151SJuraj Linkeš 186*3fbb93cfSLuca Vizzarro #: The name of the :class:`~framework.testbed_model.node.Node`. 187*3fbb93cfSLuca Vizzarro name: str 188*3fbb93cfSLuca Vizzarro #: The hostname of the :class:`~framework.testbed_model.node.Node`. Can also be an IP address. 189*3fbb93cfSLuca Vizzarro hostname: str 190*3fbb93cfSLuca Vizzarro #: The name of the user used to connect to the :class:`~framework.testbed_model.node.Node`. 191*3fbb93cfSLuca Vizzarro user: str 192*3fbb93cfSLuca Vizzarro #: The password of the user. The use of passwords is heavily discouraged, please use SSH keys. 193*3fbb93cfSLuca Vizzarro password: str | None = None 194*3fbb93cfSLuca Vizzarro #: The architecture of the :class:`~framework.testbed_model.node.Node`. 195c020b7ceSJuraj Linkeš arch: Architecture 196*3fbb93cfSLuca Vizzarro #: The operating system of the :class:`~framework.testbed_model.node.Node`. 19778534506SJuraj Linkeš os: OS 198*3fbb93cfSLuca Vizzarro #: A comma delimited list of logical cores to use when running DPDK. 199b935bdc3SLuca Vizzarro lcores: LogicalCores = "1" 200*3fbb93cfSLuca Vizzarro #: If :data:`True`, the first logical core won't be used. 201*3fbb93cfSLuca Vizzarro use_first_core: bool = False 202*3fbb93cfSLuca Vizzarro #: An optional hugepage configuration. 203b935bdc3SLuca Vizzarro hugepages: HugepageConfiguration | None = Field(None, alias="hugepages_2mb") 204*3fbb93cfSLuca Vizzarro #: The ports that can be used in testing. 205b935bdc3SLuca Vizzarro ports: list[PortConfig] = Field(min_length=1) 206634bed13SJuraj Linkeš 207634bed13SJuraj Linkeš 208634bed13SJuraj Linkešclass SutNodeConfiguration(NodeConfiguration): 209*3fbb93cfSLuca Vizzarro """:class:`~framework.testbed_model.sut_node.SutNode` specific configuration.""" 2106ef07151SJuraj Linkeš 211*3fbb93cfSLuca Vizzarro #: The number of memory channels to use when running DPDK. 212*3fbb93cfSLuca Vizzarro memory_channels: int = 1 213634bed13SJuraj Linkeš 214634bed13SJuraj Linkeš 215634bed13SJuraj Linkešclass TGNodeConfiguration(NodeConfiguration): 216*3fbb93cfSLuca Vizzarro """:class:`~framework.testbed_model.tg_node.TGNode` specific configuration.""" 2176ef07151SJuraj Linkeš 218*3fbb93cfSLuca Vizzarro #: The configuration of the traffic generator present on the TG node. 219b935bdc3SLuca Vizzarro traffic_generator: TrafficGeneratorConfigTypes 22088489c05SJeremy Spewock 22188489c05SJeremy Spewock 222b935bdc3SLuca Vizzarro#: Union type for all the node configuration types. 223b935bdc3SLuca VizzarroNodeConfigurationTypes = TGNodeConfiguration | SutNodeConfiguration 2246ef07151SJuraj Linkeš 225b935bdc3SLuca Vizzarro 226b935bdc3SLuca Vizzarrodef resolve_path(path: Path) -> Path: 227b935bdc3SLuca Vizzarro """Resolve a path into a real path.""" 228b935bdc3SLuca Vizzarro return path.resolve() 229b935bdc3SLuca Vizzarro 230b935bdc3SLuca Vizzarro 231b935bdc3SLuca Vizzarroclass BaseDPDKLocation(FrozenModel): 232*3fbb93cfSLuca Vizzarro """DPDK location base class. 233b935bdc3SLuca Vizzarro 234*3fbb93cfSLuca Vizzarro The path to the DPDK sources and type of location. 235b935bdc3SLuca Vizzarro """ 236b935bdc3SLuca Vizzarro 237*3fbb93cfSLuca Vizzarro #: Specifies whether to find DPDK on the SUT node or on the local host. Which are respectively 238*3fbb93cfSLuca Vizzarro #: represented by :class:`RemoteDPDKLocation` and :class:`LocalDPDKTreeLocation`. 239b935bdc3SLuca Vizzarro remote: bool = False 240b935bdc3SLuca Vizzarro 241b935bdc3SLuca Vizzarro 242b935bdc3SLuca Vizzarroclass LocalDPDKLocation(BaseDPDKLocation): 243*3fbb93cfSLuca Vizzarro """Local DPDK location base class. 244b935bdc3SLuca Vizzarro 245b935bdc3SLuca Vizzarro This class is meant to represent any location that is present only locally. 246b935bdc3SLuca Vizzarro """ 247b935bdc3SLuca Vizzarro 248b935bdc3SLuca Vizzarro remote: Literal[False] = False 249b935bdc3SLuca Vizzarro 250b935bdc3SLuca Vizzarro 251b935bdc3SLuca Vizzarroclass LocalDPDKTreeLocation(LocalDPDKLocation): 252b935bdc3SLuca Vizzarro """Local DPDK tree location. 253b935bdc3SLuca Vizzarro 254b935bdc3SLuca Vizzarro This class makes a distinction from :class:`RemoteDPDKTreeLocation` by enforcing on the fly 255b935bdc3SLuca Vizzarro validation. 256b935bdc3SLuca Vizzarro """ 257b935bdc3SLuca Vizzarro 258*3fbb93cfSLuca Vizzarro #: The path to the DPDK source tree directory on the local host passed as string. 259b935bdc3SLuca Vizzarro dpdk_tree: Path 260b935bdc3SLuca Vizzarro 261*3fbb93cfSLuca Vizzarro #: Resolve the local DPDK tree path. 262b935bdc3SLuca Vizzarro resolve_dpdk_tree_path = field_validator("dpdk_tree")(resolve_path) 263b935bdc3SLuca Vizzarro 264b935bdc3SLuca Vizzarro @model_validator(mode="after") 265b935bdc3SLuca Vizzarro def validate_dpdk_tree_path(self) -> Self: 266b935bdc3SLuca Vizzarro """Validate the provided DPDK tree path.""" 267b935bdc3SLuca Vizzarro assert self.dpdk_tree.exists(), "DPDK tree not found in local filesystem." 268b935bdc3SLuca Vizzarro assert self.dpdk_tree.is_dir(), "The DPDK tree path must be a directory." 269b935bdc3SLuca Vizzarro return self 270b935bdc3SLuca Vizzarro 271b935bdc3SLuca Vizzarro 272b935bdc3SLuca Vizzarroclass LocalDPDKTarballLocation(LocalDPDKLocation): 273b935bdc3SLuca Vizzarro """Local DPDK tarball location. 274b935bdc3SLuca Vizzarro 275b935bdc3SLuca Vizzarro This class makes a distinction from :class:`RemoteDPDKTarballLocation` by enforcing on the fly 276b935bdc3SLuca Vizzarro validation. 277b935bdc3SLuca Vizzarro """ 278b935bdc3SLuca Vizzarro 279*3fbb93cfSLuca Vizzarro #: The path to the DPDK tarball on the local host passed as string. 280b935bdc3SLuca Vizzarro tarball: Path 281b935bdc3SLuca Vizzarro 282*3fbb93cfSLuca Vizzarro #: Resolve the local tarball path. 283b935bdc3SLuca Vizzarro resolve_tarball_path = field_validator("tarball")(resolve_path) 284b935bdc3SLuca Vizzarro 285b935bdc3SLuca Vizzarro @model_validator(mode="after") 286b935bdc3SLuca Vizzarro def validate_tarball_path(self) -> Self: 287b935bdc3SLuca Vizzarro """Validate the provided tarball.""" 288b935bdc3SLuca Vizzarro assert self.tarball.exists(), "DPDK tarball not found in local filesystem." 289b935bdc3SLuca Vizzarro assert tarfile.is_tarfile(self.tarball), "The DPDK tarball must be a valid tar archive." 290b935bdc3SLuca Vizzarro return self 291b935bdc3SLuca Vizzarro 292b935bdc3SLuca Vizzarro 293b935bdc3SLuca Vizzarroclass RemoteDPDKLocation(BaseDPDKLocation): 294*3fbb93cfSLuca Vizzarro """Remote DPDK location base class. 295b935bdc3SLuca Vizzarro 296b935bdc3SLuca Vizzarro This class is meant to represent any location that is present only remotely. 297b935bdc3SLuca Vizzarro """ 298b935bdc3SLuca Vizzarro 299b935bdc3SLuca Vizzarro remote: Literal[True] = True 300b935bdc3SLuca Vizzarro 301b935bdc3SLuca Vizzarro 302b935bdc3SLuca Vizzarroclass RemoteDPDKTreeLocation(RemoteDPDKLocation): 303b935bdc3SLuca Vizzarro """Remote DPDK tree location. 304b935bdc3SLuca Vizzarro 305b935bdc3SLuca Vizzarro This class is distinct from :class:`LocalDPDKTreeLocation` which enforces on the fly validation. 306b935bdc3SLuca Vizzarro """ 307b935bdc3SLuca Vizzarro 308*3fbb93cfSLuca Vizzarro #: The path to the DPDK source tree directory on the remote node passed as string. 309b935bdc3SLuca Vizzarro dpdk_tree: PurePath 310b935bdc3SLuca Vizzarro 311b935bdc3SLuca Vizzarro 312b935bdc3SLuca Vizzarroclass RemoteDPDKTarballLocation(RemoteDPDKLocation): 313b935bdc3SLuca Vizzarro """Remote DPDK tarball location. 314b935bdc3SLuca Vizzarro 315b935bdc3SLuca Vizzarro This class is distinct from :class:`LocalDPDKTarballLocation` which enforces on the fly 316b935bdc3SLuca Vizzarro validation. 317b935bdc3SLuca Vizzarro """ 318b935bdc3SLuca Vizzarro 319*3fbb93cfSLuca Vizzarro #: The path to the DPDK tarball on the remote node passed as string. 320b935bdc3SLuca Vizzarro tarball: PurePath 321b935bdc3SLuca Vizzarro 322b935bdc3SLuca Vizzarro 323b935bdc3SLuca Vizzarro#: Union type for different DPDK locations. 324b935bdc3SLuca VizzarroDPDKLocation = ( 325b935bdc3SLuca Vizzarro LocalDPDKTreeLocation 326b935bdc3SLuca Vizzarro | LocalDPDKTarballLocation 327b935bdc3SLuca Vizzarro | RemoteDPDKTreeLocation 328b935bdc3SLuca Vizzarro | RemoteDPDKTarballLocation 329b935bdc3SLuca Vizzarro) 330b935bdc3SLuca Vizzarro 331b935bdc3SLuca Vizzarro 332b935bdc3SLuca Vizzarroclass BaseDPDKBuildConfiguration(FrozenModel): 333b935bdc3SLuca Vizzarro """The base configuration for different types of build. 334b935bdc3SLuca Vizzarro 335b935bdc3SLuca Vizzarro The configuration contain the location of the DPDK and configuration used for building it. 336b935bdc3SLuca Vizzarro """ 337b935bdc3SLuca Vizzarro 338*3fbb93cfSLuca Vizzarro #: The location of the DPDK tree. 339b935bdc3SLuca Vizzarro dpdk_location: DPDKLocation 340b935bdc3SLuca Vizzarro 341b935bdc3SLuca Vizzarro 342b935bdc3SLuca Vizzarroclass DPDKPrecompiledBuildConfiguration(BaseDPDKBuildConfiguration): 343*3fbb93cfSLuca Vizzarro """DPDK precompiled build configuration.""" 344b935bdc3SLuca Vizzarro 345*3fbb93cfSLuca Vizzarro #: If it's defined, DPDK has been pre-compiled and the build directory is located in a 346*3fbb93cfSLuca Vizzarro #: subdirectory of `~dpdk_location.dpdk_tree` or `~dpdk_location.tarball` root directory. 347b935bdc3SLuca Vizzarro precompiled_build_dir: str = Field(min_length=1) 348b935bdc3SLuca Vizzarro 349b935bdc3SLuca Vizzarro 350b935bdc3SLuca Vizzarroclass DPDKBuildOptionsConfiguration(FrozenModel): 351b935bdc3SLuca Vizzarro """DPDK build options configuration. 352b935bdc3SLuca Vizzarro 353b935bdc3SLuca Vizzarro The build options used for building DPDK. 3546ef07151SJuraj Linkeš """ 3556ef07151SJuraj Linkeš 356*3fbb93cfSLuca Vizzarro #: The target architecture to build for. 35778534506SJuraj Linkeš arch: Architecture 358*3fbb93cfSLuca Vizzarro #: The target OS to build for. 35978534506SJuraj Linkeš os: OS 360*3fbb93cfSLuca Vizzarro #: The target CPU to build for. 36178534506SJuraj Linkeš cpu: CPUType 362*3fbb93cfSLuca Vizzarro #: The compiler executable to use. 36378534506SJuraj Linkeš compiler: Compiler 364*3fbb93cfSLuca Vizzarro #: This string will be put in front of the compiler when executing the build. Useful for adding 365*3fbb93cfSLuca Vizzarro #: wrapper commands, such as ``ccache``. 366b935bdc3SLuca Vizzarro compiler_wrapper: str = "" 36778534506SJuraj Linkeš 368b935bdc3SLuca Vizzarro @cached_property 369b935bdc3SLuca Vizzarro def name(self) -> str: 370b935bdc3SLuca Vizzarro """The name of the compiler.""" 371b935bdc3SLuca Vizzarro return f"{self.arch}-{self.os}-{self.cpu}-{self.compiler}" 372995fb337SOwen Hilyard 373995fb337SOwen Hilyard 374b935bdc3SLuca Vizzarroclass DPDKUncompiledBuildConfiguration(BaseDPDKBuildConfiguration): 375*3fbb93cfSLuca Vizzarro """DPDK uncompiled build configuration.""" 376f9957667STomáš Ďurovec 377*3fbb93cfSLuca Vizzarro #: The build options to compiled DPDK with. 378b935bdc3SLuca Vizzarro build_options: DPDKBuildOptionsConfiguration 379f9957667STomáš Ďurovec 380f9957667STomáš Ďurovec 381b935bdc3SLuca Vizzarro#: Union type for different build configurations. 382b935bdc3SLuca VizzarroDPDKBuildConfiguration = DPDKPrecompiledBuildConfiguration | DPDKUncompiledBuildConfiguration 383f9957667STomáš Ďurovec 384f9957667STomáš Ďurovec 385b935bdc3SLuca Vizzarroclass TestSuiteConfig(FrozenModel): 3866ef07151SJuraj Linkeš """Test suite configuration. 3876ef07151SJuraj Linkeš 388b935bdc3SLuca Vizzarro Information about a single test suite to be executed. This can also be represented as a string 389b935bdc3SLuca Vizzarro instead of a mapping, example: 390b935bdc3SLuca Vizzarro 391b935bdc3SLuca Vizzarro .. code:: yaml 392b935bdc3SLuca Vizzarro 393b935bdc3SLuca Vizzarro test_runs: 394b935bdc3SLuca Vizzarro - test_suites: 395b935bdc3SLuca Vizzarro # As string representation: 396b935bdc3SLuca Vizzarro - hello_world # test all of `hello_world`, or 397b935bdc3SLuca Vizzarro - hello_world hello_world_single_core # test only `hello_world_single_core` 398b935bdc3SLuca Vizzarro # or as model fields: 399b935bdc3SLuca Vizzarro - test_suite: hello_world 400b935bdc3SLuca Vizzarro test_cases: [hello_world_single_core] # without this field all test cases are run 4016ef07151SJuraj Linkeš """ 4026ef07151SJuraj Linkeš 403*3fbb93cfSLuca Vizzarro #: The name of the test suite module without the starting ``TestSuite_``. 404*3fbb93cfSLuca Vizzarro test_suite_name: str = Field(alias="test_suite") 405*3fbb93cfSLuca Vizzarro #: The names of test cases from this test suite to execute. If empty, all test cases will be 406*3fbb93cfSLuca Vizzarro #: executed. 407*3fbb93cfSLuca Vizzarro test_cases_names: list[str] = Field(default_factory=list, alias="test_cases") 4081512a0d1SJuraj Linkeš 409b935bdc3SLuca Vizzarro @cached_property 410b935bdc3SLuca Vizzarro def test_suite_spec(self) -> "TestSuiteSpec": 411b935bdc3SLuca Vizzarro """The specification of the requested test suite.""" 412b935bdc3SLuca Vizzarro from framework.test_suite import find_by_name 413b935bdc3SLuca Vizzarro 414b935bdc3SLuca Vizzarro test_suite_spec = find_by_name(self.test_suite_name) 415b935bdc3SLuca Vizzarro assert ( 416b935bdc3SLuca Vizzarro test_suite_spec is not None 417b935bdc3SLuca Vizzarro ), f"{self.test_suite_name} is not a valid test suite module name." 418b935bdc3SLuca Vizzarro return test_suite_spec 419b935bdc3SLuca Vizzarro 420b935bdc3SLuca Vizzarro @model_validator(mode="before") 42134ea6db2SLuca Vizzarro @classmethod 422b935bdc3SLuca Vizzarro def convert_from_string(cls, data: Any) -> Any: 423b935bdc3SLuca Vizzarro """Convert the string representation of the model into a valid mapping.""" 424b935bdc3SLuca Vizzarro if isinstance(data, str): 425b935bdc3SLuca Vizzarro [test_suite, *test_cases] = data.split() 426b935bdc3SLuca Vizzarro return dict(test_suite=test_suite, test_cases=test_cases) 427b935bdc3SLuca Vizzarro return data 4286ef07151SJuraj Linkeš 429b935bdc3SLuca Vizzarro @model_validator(mode="after") 430b935bdc3SLuca Vizzarro def validate_names(self) -> Self: 431b935bdc3SLuca Vizzarro """Validate the supplied test suite and test cases names. 4326ef07151SJuraj Linkeš 433b935bdc3SLuca Vizzarro This validator relies on the cached property `test_suite_spec` to run for the first 434b935bdc3SLuca Vizzarro time in this call, therefore triggering the assertions if needed. 4356ef07151SJuraj Linkeš """ 436b935bdc3SLuca Vizzarro available_test_cases = map( 437b935bdc3SLuca Vizzarro lambda t: t.name, self.test_suite_spec.class_obj.get_test_cases() 438b935bdc3SLuca Vizzarro ) 439b935bdc3SLuca Vizzarro for requested_test_case in self.test_cases_names: 440b935bdc3SLuca Vizzarro assert requested_test_case in available_test_cases, ( 441b935bdc3SLuca Vizzarro f"{requested_test_case} is not a valid test case " 442b935bdc3SLuca Vizzarro f"of test suite {self.test_suite_name}." 443b935bdc3SLuca Vizzarro ) 444b935bdc3SLuca Vizzarro 445b935bdc3SLuca Vizzarro return self 4461512a0d1SJuraj Linkeš 4471512a0d1SJuraj Linkeš 448b935bdc3SLuca Vizzarroclass TestRunSUTNodeConfiguration(FrozenModel): 449*3fbb93cfSLuca Vizzarro """The SUT node configuration of a test run.""" 450b935bdc3SLuca Vizzarro 451*3fbb93cfSLuca Vizzarro #: The SUT node to use in this test run. 452b935bdc3SLuca Vizzarro node_name: str 453*3fbb93cfSLuca Vizzarro #: The names of virtual devices to test. 454b935bdc3SLuca Vizzarro vdevs: list[str] = Field(default_factory=list) 455b935bdc3SLuca Vizzarro 456b935bdc3SLuca Vizzarro 457b935bdc3SLuca Vizzarroclass TestRunConfiguration(FrozenModel): 45885ceeeceSJuraj Linkeš """The configuration of a test run. 4596ef07151SJuraj Linkeš 4606ef07151SJuraj Linkeš The configuration contains testbed information, what tests to execute 4616ef07151SJuraj Linkeš and with what DPDK build. 4626ef07151SJuraj Linkeš """ 4636ef07151SJuraj Linkeš 464*3fbb93cfSLuca Vizzarro #: The DPDK configuration used to test. 465b935bdc3SLuca Vizzarro dpdk_config: DPDKBuildConfiguration = Field(alias="dpdk_build") 466*3fbb93cfSLuca Vizzarro #: Whether to run performance tests. 467*3fbb93cfSLuca Vizzarro perf: bool 468*3fbb93cfSLuca Vizzarro #: Whether to run functional tests. 469*3fbb93cfSLuca Vizzarro func: bool 470*3fbb93cfSLuca Vizzarro #: Whether to skip smoke tests. 471b935bdc3SLuca Vizzarro skip_smoke_tests: bool = False 472*3fbb93cfSLuca Vizzarro #: The names of test suites and/or test cases to execute. 473b935bdc3SLuca Vizzarro test_suites: list[TestSuiteConfig] = Field(min_length=1) 474*3fbb93cfSLuca Vizzarro #: The SUT node configuration to use in this test run. 475b935bdc3SLuca Vizzarro system_under_test_node: TestRunSUTNodeConfiguration 476*3fbb93cfSLuca Vizzarro #: The TG node name to use in this test run. 477b935bdc3SLuca Vizzarro traffic_generator_node: str 478*3fbb93cfSLuca Vizzarro #: The seed to use for pseudo-random generation. 479b935bdc3SLuca Vizzarro random_seed: int | None = None 4805d094f9fSJuraj Linkeš 481995fb337SOwen Hilyard 482b935bdc3SLuca Vizzarroclass TestRunWithNodesConfiguration(NamedTuple): 483b935bdc3SLuca Vizzarro """Tuple containing the configuration of the test run and its associated nodes.""" 484b935bdc3SLuca Vizzarro 485b935bdc3SLuca Vizzarro #: 486b935bdc3SLuca Vizzarro test_run_config: TestRunConfiguration 487b935bdc3SLuca Vizzarro #: 488b935bdc3SLuca Vizzarro sut_node_config: SutNodeConfiguration 489b935bdc3SLuca Vizzarro #: 490b935bdc3SLuca Vizzarro tg_node_config: TGNodeConfiguration 491b935bdc3SLuca Vizzarro 492b935bdc3SLuca Vizzarro 493b935bdc3SLuca Vizzarroclass Configuration(FrozenModel): 494*3fbb93cfSLuca Vizzarro """DTS testbed and test configuration.""" 4956ef07151SJuraj Linkeš 496*3fbb93cfSLuca Vizzarro #: Test run configurations. 497b935bdc3SLuca Vizzarro test_runs: list[TestRunConfiguration] = Field(min_length=1) 498*3fbb93cfSLuca Vizzarro #: Node configurations. 499b935bdc3SLuca Vizzarro nodes: list[NodeConfigurationTypes] = Field(min_length=1) 500995fb337SOwen Hilyard 501b935bdc3SLuca Vizzarro @cached_property 502b935bdc3SLuca Vizzarro def test_runs_with_nodes(self) -> list[TestRunWithNodesConfiguration]: 503b935bdc3SLuca Vizzarro """List of test runs with the associated nodes.""" 504b935bdc3SLuca Vizzarro test_runs_with_nodes = [] 505b935bdc3SLuca Vizzarro 506b935bdc3SLuca Vizzarro for test_run_no, test_run in enumerate(self.test_runs): 507b935bdc3SLuca Vizzarro sut_node_name = test_run.system_under_test_node.node_name 508b935bdc3SLuca Vizzarro sut_node = next(filter(lambda n: n.name == sut_node_name, self.nodes), None) 509b935bdc3SLuca Vizzarro 510b935bdc3SLuca Vizzarro assert sut_node is not None, ( 511b935bdc3SLuca Vizzarro f"test_runs.{test_run_no}.sut_node_config.node_name " 512b935bdc3SLuca Vizzarro f"({test_run.system_under_test_node.node_name}) is not a valid node name" 513b935bdc3SLuca Vizzarro ) 514b935bdc3SLuca Vizzarro assert isinstance(sut_node, SutNodeConfiguration), ( 515b935bdc3SLuca Vizzarro f"test_runs.{test_run_no}.sut_node_config.node_name is a valid node name, " 516b935bdc3SLuca Vizzarro "but it is not a valid SUT node" 517b935bdc3SLuca Vizzarro ) 518b935bdc3SLuca Vizzarro 519b935bdc3SLuca Vizzarro tg_node_name = test_run.traffic_generator_node 520b935bdc3SLuca Vizzarro tg_node = next(filter(lambda n: n.name == tg_node_name, self.nodes), None) 521b935bdc3SLuca Vizzarro 522b935bdc3SLuca Vizzarro assert tg_node is not None, ( 523b935bdc3SLuca Vizzarro f"test_runs.{test_run_no}.tg_node_name " 524b935bdc3SLuca Vizzarro f"({test_run.traffic_generator_node}) is not a valid node name" 525b935bdc3SLuca Vizzarro ) 526b935bdc3SLuca Vizzarro assert isinstance(tg_node, TGNodeConfiguration), ( 527b935bdc3SLuca Vizzarro f"test_runs.{test_run_no}.tg_node_name is a valid node name, " 528b935bdc3SLuca Vizzarro "but it is not a valid TG node" 529b935bdc3SLuca Vizzarro ) 530b935bdc3SLuca Vizzarro 531b935bdc3SLuca Vizzarro test_runs_with_nodes.append(TestRunWithNodesConfiguration(test_run, sut_node, tg_node)) 532b935bdc3SLuca Vizzarro 533b935bdc3SLuca Vizzarro return test_runs_with_nodes 534b935bdc3SLuca Vizzarro 535b935bdc3SLuca Vizzarro @field_validator("nodes") 53634ea6db2SLuca Vizzarro @classmethod 537b935bdc3SLuca Vizzarro def validate_node_names(cls, nodes: list[NodeConfiguration]) -> list[NodeConfiguration]: 538b935bdc3SLuca Vizzarro """Validate that the node names are unique.""" 539b935bdc3SLuca Vizzarro nodes_by_name: dict[str, int] = {} 540b935bdc3SLuca Vizzarro for node_no, node in enumerate(nodes): 541b935bdc3SLuca Vizzarro assert node.name not in nodes_by_name, ( 542b935bdc3SLuca Vizzarro f"node {node_no} cannot have the same name as node {nodes_by_name[node.name]} " 543b935bdc3SLuca Vizzarro f"({node.name})" 544b935bdc3SLuca Vizzarro ) 545b935bdc3SLuca Vizzarro nodes_by_name[node.name] = node_no 5466ef07151SJuraj Linkeš 547b935bdc3SLuca Vizzarro return nodes 5486ef07151SJuraj Linkeš 549b935bdc3SLuca Vizzarro @model_validator(mode="after") 550b935bdc3SLuca Vizzarro def validate_ports(self) -> Self: 551b935bdc3SLuca Vizzarro """Validate that the ports are all linked to valid ones.""" 552b935bdc3SLuca Vizzarro port_links: dict[tuple[str, str], Literal[False] | tuple[int, int]] = { 553b935bdc3SLuca Vizzarro (node.name, port.pci): False for node in self.nodes for port in node.ports 554b935bdc3SLuca Vizzarro } 5556ef07151SJuraj Linkeš 556b935bdc3SLuca Vizzarro for node_no, node in enumerate(self.nodes): 557b935bdc3SLuca Vizzarro for port_no, port in enumerate(node.ports): 558b935bdc3SLuca Vizzarro peer_port_identifier = (port.peer_node, port.peer_pci) 559b935bdc3SLuca Vizzarro peer_port = port_links.get(peer_port_identifier, None) 560b935bdc3SLuca Vizzarro assert peer_port is not None, ( 561b935bdc3SLuca Vizzarro "invalid peer port specified for " f"nodes.{node_no}.ports.{port_no}" 562b935bdc3SLuca Vizzarro ) 563b935bdc3SLuca Vizzarro assert peer_port is False, ( 564b935bdc3SLuca Vizzarro f"the peer port specified for nodes.{node_no}.ports.{port_no} " 565b935bdc3SLuca Vizzarro f"is already linked to nodes.{peer_port[0]}.ports.{peer_port[1]}" 566b935bdc3SLuca Vizzarro ) 567b935bdc3SLuca Vizzarro port_links[peer_port_identifier] = (node_no, port_no) 568b935bdc3SLuca Vizzarro 569b935bdc3SLuca Vizzarro return self 570b935bdc3SLuca Vizzarro 571b935bdc3SLuca Vizzarro @model_validator(mode="after") 572b935bdc3SLuca Vizzarro def validate_test_runs_with_nodes(self) -> Self: 573b935bdc3SLuca Vizzarro """Validate the test runs to nodes associations. 574b935bdc3SLuca Vizzarro 575b935bdc3SLuca Vizzarro This validator relies on the cached property `test_runs_with_nodes` to run for the first 576b935bdc3SLuca Vizzarro time in this call, therefore triggering the assertions if needed. 5776ef07151SJuraj Linkeš """ 578b935bdc3SLuca Vizzarro if self.test_runs_with_nodes: 579b935bdc3SLuca Vizzarro pass 580b935bdc3SLuca Vizzarro return self 581995fb337SOwen Hilyard 582995fb337SOwen Hilyard 5834a4678c7SJuraj Linkešdef load_config(config_file_path: Path) -> Configuration: 5846ef07151SJuraj Linkeš """Load DTS test run configuration from a file. 5856ef07151SJuraj Linkeš 586b935bdc3SLuca Vizzarro Load the YAML test run configuration file, validate it, and create a test run configuration 587b935bdc3SLuca Vizzarro object. 5886ef07151SJuraj Linkeš 5896ef07151SJuraj Linkeš The YAML test run configuration file is specified in the :option:`--config-file` command line 5906ef07151SJuraj Linkeš argument or the :envvar:`DTS_CFG_FILE` environment variable. 5916ef07151SJuraj Linkeš 5924a4678c7SJuraj Linkeš Args: 5934a4678c7SJuraj Linkeš config_file_path: The path to the YAML test run configuration file. 5944a4678c7SJuraj Linkeš 5956ef07151SJuraj Linkeš Returns: 5966ef07151SJuraj Linkeš The parsed test run configuration. 597b935bdc3SLuca Vizzarro 598b935bdc3SLuca Vizzarro Raises: 599b935bdc3SLuca Vizzarro ConfigurationError: If the supplied configuration file is invalid. 600995fb337SOwen Hilyard """ 6014a4678c7SJuraj Linkeš with open(config_file_path, "r") as f: 602995fb337SOwen Hilyard config_data = yaml.safe_load(f) 603995fb337SOwen Hilyard 604b935bdc3SLuca Vizzarro try: 605b935bdc3SLuca Vizzarro return Configuration.model_validate(config_data) 606b935bdc3SLuca Vizzarro except ValidationError as e: 607b935bdc3SLuca Vizzarro raise ConfigurationError("failed to load the supplied configuration") from e 608