xref: /dpdk/dts/framework/config/__init__.py (revision 3fbb93cff3be23a45fc1ec524f83d001a30df273)
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