From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mails.dpdk.org (mails.dpdk.org [217.70.189.124]) by inbox.dpdk.org (Postfix) with ESMTP id A43214339B; Wed, 22 Nov 2023 11:42:24 +0100 (CET) Received: from mails.dpdk.org (localhost [127.0.0.1]) by mails.dpdk.org (Postfix) with ESMTP id 69ED440A76; Wed, 22 Nov 2023 11:42:24 +0100 (CET) Received: from mail-ej1-f43.google.com (mail-ej1-f43.google.com [209.85.218.43]) by mails.dpdk.org (Postfix) with ESMTP id 8EBBC402CB for ; Wed, 22 Nov 2023 11:42:22 +0100 (CET) Received: by mail-ej1-f43.google.com with SMTP id a640c23a62f3a-a0029289b1bso382591766b.1 for ; Wed, 22 Nov 2023 02:42:22 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=pantheon.tech; s=google; t=1700649742; x=1701254542; darn=dpdk.org; h=content-transfer-encoding:cc:to:subject:message-id:date:from :in-reply-to:references:mime-version:from:to:cc:subject:date :message-id:reply-to; bh=UNIUPLmbJLtIEsoOme9EzuypLlzLdxU7I0Tz9NbvmZE=; b=YKNY4K8kBbuu6Q2Adombs/yh8ff7bLbw0FF9IcOIQKaG9tXF6Sts0LN2zXojTfBxio 9gHI3SEk/LORfTu/fghgKIvWU/et7zKotzTOb9mkU03mzv6rDEiukrexe06GqdqQdDzq ZSw0RRFvxPK6Dd/SPje88uIGHTtyrCmGHVE1m4dtat3YGfS/b6SnGVxgFGHSogXIxU/4 5Y4qDX9Zb2VVZIkvoLFYPysVdjUloMs7LbrbLLFhHbDEjv/0h1gjADvV2Sd1yghmlSzp cIXnQ1Uaea4Ni+hH3q8x2CX1j4twtZRAhvBDU45FI/qVM6ArCzFhMnZF4RO7AsKJYmjt k1ew== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1700649742; x=1701254542; h=content-transfer-encoding:cc:to:subject:message-id:date:from :in-reply-to:references:mime-version:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=UNIUPLmbJLtIEsoOme9EzuypLlzLdxU7I0Tz9NbvmZE=; b=gMdNqhzQZ9fIYbt3jtPQIKbPkNOlD6u7Enh/7D9GiZiLoXhADI6u5gMAIPCTOtH90F kNE6WKN5yNsuFo7WobzRXkcTvmH88DUGk4C+ogie+husySMvg1oPhxd2jTJ5O2ao63Tw zCLnMwFDk7pCkkQ7cyRp86Nbpt82V6e3d8ZFrGpnk6CwSxewle1JAaH4L9hQiOXMXRXZ CRrNCHEli59vu/DpfxEoXIUmq7fs+3KReiiHxf0CZNzBTNVIFp7iy1NxVqflsTftnQCr GxmJTMMOju0eDcAJqkYtqXykKi834JPv+jRAdgggK8jxzSmR5y/CozI60SjByUhWimCM i4mg== X-Gm-Message-State: AOJu0Yzvk3x8Xd50R3x6DGBg9T6oMP1CMIvBOnOuYgnjpKpWfaVuMF/a F93Zrr2Jdae/zwWvLdBrsnlt9/wG3IvFBut2UwrbMA== X-Google-Smtp-Source: AGHT+IEyL7PGosgB6TVwMrK3PECwigx4+4GoVaCK1oEuWIRfGq5zvjs8rlavh59rfyrPji3OPX6VO+iwI213fuPEHi4= X-Received: by 2002:a17:906:51d8:b0:9bd:dfaa:3f3 with SMTP id v24-20020a17090651d800b009bddfaa03f3mr1155481ejk.7.1700649741961; Wed, 22 Nov 2023 02:42:21 -0800 (PST) MIME-Version: 1.0 References: <20231108125324.191005-23-juraj.linkes@pantheon.tech> <20231115130959.39420-1-juraj.linkes@pantheon.tech> <20231115130959.39420-11-juraj.linkes@pantheon.tech> <6f683d61-996c-47de-b7f6-e1213a32c4c9@foss.arm.com> In-Reply-To: <6f683d61-996c-47de-b7f6-e1213a32c4c9@foss.arm.com> From: =?UTF-8?Q?Juraj_Linke=C5=A1?= Date: Wed, 22 Nov 2023 11:42:10 +0100 Message-ID: Subject: Re: [PATCH v7 10/21] dts: config docstring update To: Yoan Picchi Cc: thomas@monjalon.net, Honnappa.Nagarahalli@arm.com, jspewock@iol.unh.edu, probb@iol.unh.edu, paul.szczepanek@arm.com, dev@dpdk.org Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable X-BeenThere: dev@dpdk.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: DPDK patches and discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dev-bounces@dpdk.org Thanks, Yoan, I'll make these changes in v8. On Tue, Nov 21, 2023 at 4:08=E2=80=AFPM Yoan Picchi wrote: > > On 11/15/23 13:09, Juraj Linke=C5=A1 wrote: > > Format according to the Google format and PEP257, with slight > > deviations. > > > > Signed-off-by: Juraj Linke=C5=A1 > > --- > > dts/framework/config/__init__.py | 371 ++++++++++++++++++++++++++----= - > > dts/framework/config/types.py | 132 +++++++++++ > > 2 files changed, 446 insertions(+), 57 deletions(-) > > create mode 100644 dts/framework/config/types.py > > > > diff --git a/dts/framework/config/__init__.py b/dts/framework/config/__= init__.py > > index 2044c82611..0aa149a53d 100644 > > --- a/dts/framework/config/__init__.py > > +++ b/dts/framework/config/__init__.py > > @@ -3,8 +3,34 @@ > > # Copyright(c) 2022-2023 University of New Hampshire > > # Copyright(c) 2023 PANTHEON.tech s.r.o. > > > > -""" > > -Yaml config parsing methods > > +"""Testbed configuration and test suite specification. > > + > > +This package offers classes that hold real-time information about the = testbed, hold test run > > +configuration describing the tested testbed and a loader function, :fu= nc:`load_config`, which loads > > +the YAML test run configuration file > > +and validates it according to :download:`the schema `. > > + > > +The YAML test run configuration file is parsed into a dictionary, part= s of which are used throughout > > +this package. The allowed keys and types inside this dictionary are de= fined in > > +the :doc:`types ` module. > > + > > +The test run configuration has two main sections: > > + > > + * The :class:`ExecutionConfiguration` which defines what tests are= going to be run > > + and how DPDK will be built. It also references the testbed where= these tests and DPDK > > + are going to be run, > > + * The nodes of the testbed are defined in the other section, > > + a :class:`list` of :class:`NodeConfiguration` objects. > > + > > +The real-time information about testbed is supposed to be gathered at = runtime. > > + > > +The classes defined in this package make heavy use of :mod:`dataclasse= s`. > > +All of them use slots and are frozen: > > + > > + * Slots enables some optimizations, by pre-allocating space for th= e defined > > + attributes in the underlying data structure, > > + * Frozen makes the object immutable. This enables further optimiza= tions, > > + and makes it thread safe should we every want to move in that di= rection. > > every -> ever ? > > > """ > > > > import json > > @@ -12,11 +38,20 @@ > > import pathlib > > from dataclasses import dataclass > > from enum import auto, unique > > -from typing import Any, TypedDict, Union > > +from typing import Union > > > > import warlock # type: ignore[import] > > import yaml > > > > +from framework.config.types import ( > > + BuildTargetConfigDict, > > + ConfigurationDict, > > + ExecutionConfigDict, > > + NodeConfigDict, > > + PortConfigDict, > > + TestSuiteConfigDict, > > + TrafficGeneratorConfigDict, > > +) > > from framework.exception import ConfigurationError > > from framework.settings import SETTINGS > > from framework.utils import StrEnum > > @@ -24,55 +59,97 @@ > > > > @unique > > class Architecture(StrEnum): > > + r"""The supported architectures of :class:`~framework.testbed_mode= l.node.Node`\s.""" > > + > > + #: > > i686 =3D auto() > > + #: > > x86_64 =3D auto() > > + #: > > x86_32 =3D auto() > > + #: > > arm64 =3D auto() > > + #: > > ppc64le =3D auto() > > > > > > @unique > > class OS(StrEnum): > > + r"""The supported operating systems of :class:`~framework.testbed_= model.node.Node`\s.""" > > + > > + #: > > linux =3D auto() > > + #: > > freebsd =3D auto() > > + #: > > windows =3D auto() > > > > > > @unique > > class CPUType(StrEnum): > > + r"""The supported CPUs of :class:`~framework.testbed_model.node.No= de`\s.""" > > + > > + #: > > native =3D auto() > > + #: > > armv8a =3D auto() > > + #: > > dpaa2 =3D auto() > > + #: > > thunderx =3D auto() > > + #: > > xgene1 =3D auto() > > > > > > @unique > > class Compiler(StrEnum): > > + r"""The supported compilers of :class:`~framework.testbed_model.no= de.Node`\s.""" > > + > > + #: > > gcc =3D auto() > > + #: > > clang =3D auto() > > + #: > > icc =3D auto() > > + #: > > msvc =3D auto() > > > > > > @unique > > class TrafficGeneratorType(StrEnum): > > + """The supported traffic generators.""" > > + > > + #: > > SCAPY =3D auto() > > > > > > -# Slots enables some optimizations, by pre-allocating space for the de= fined > > -# attributes in the underlying data structure. > > -# > > -# Frozen makes the object immutable. This enables further optimization= s, > > -# and makes it thread safe should we every want to move in that direct= ion. > > @dataclass(slots=3DTrue, frozen=3DTrue) > > class HugepageConfiguration: > > + r"""The hugepage configuration of :class:`~framework.testbed_model= .node.Node`\s. > > + > > + Attributes: > > + amount: The number of hugepages. > > + force_first_numa: If :data:`True`, the hugepages will be confi= gured on the first NUMA node. > > + """ > > + > > amount: int > > force_first_numa: bool > > > > > > @dataclass(slots=3DTrue, frozen=3DTrue) > > class PortConfig: > > + r"""The port configuration of :class:`~framework.testbed_model.nod= e.Node`\s. > > + > > + Attributes: > > + node: The :class:`~framework.testbed_model.node.Node` where th= is port exists. > > + pci: The PCI address of the port. > > + os_driver_for_dpdk: The operating system driver name for use w= ith DPDK. > > + os_driver: The operating system driver name when the operating= system controls the port. > > + peer_node: The :class:`~framework.testbed_model.node.Node` of = the port > > + connected to this port. > > + peer_pci: The PCI address of the port connected to this port. > > + """ > > + > > node: str > > pci: str > > os_driver_for_dpdk: str > > @@ -81,18 +158,44 @@ class PortConfig: > > peer_pci: str > > > > @staticmethod > > - def from_dict(node: str, d: dict) -> "PortConfig": > > + def from_dict(node: str, d: PortConfigDict) -> "PortConfig": > > + """A convenience method that creates the object from fewer inp= uts. > > + > > + Args: > > + node: The node where this port exists. > > + d: The configuration dictionary. > > + > > + Returns: > > + The port configuration instance. > > + """ > > return PortConfig(node=3Dnode, **d) > > > > > > @dataclass(slots=3DTrue, frozen=3DTrue) > > class TrafficGeneratorConfig: > > + """The configuration of traffic generators. > > + > > + The class will be expanded when more configuration is needed. > > + > > + Attributes: > > + traffic_generator_type: The type of the traffic generator. > > + """ > > + > > traffic_generator_type: TrafficGeneratorType > > > > @staticmethod > > - def from_dict(d: dict) -> "ScapyTrafficGeneratorConfig": > > - # This looks useless now, but is designed to allow expansion t= o traffic > > - # generators that require more configuration later. > > + def from_dict(d: TrafficGeneratorConfigDict) -> "ScapyTrafficGener= atorConfig": > > + """A convenience method that produces traffic generator config= of the proper type. > > + > > + Args: > > + d: The configuration dictionary. > > + > > + Returns: > > + The traffic generator configuration instance. > > + > > + Raises: > > + ConfigurationError: An unknown traffic generator type was = encountered. > > + """ > > match TrafficGeneratorType(d["type"]): > > case TrafficGeneratorType.SCAPY: > > return ScapyTrafficGeneratorConfig( > > @@ -106,11 +209,31 @@ def from_dict(d: dict) -> "ScapyTrafficGeneratorC= onfig": > > > > @dataclass(slots=3DTrue, frozen=3DTrue) > > class ScapyTrafficGeneratorConfig(TrafficGeneratorConfig): > > + """Scapy traffic generator specific configuration.""" > > + > > pass > > > > > > @dataclass(slots=3DTrue, frozen=3DTrue) > > class NodeConfiguration: > > + r"""The configuration of :class:`~framework.testbed_model.node.Nod= e`\s. > > + > > + Attributes: > > + name: The name of the :class:`~framework.testbed_model.node.No= de`. > > + hostname: The hostname of the :class:`~framework.testbed_model= .node.Node`. > > + Can be an IP or a domain name. > > + user: The name of the user used to connect to > > + the :class:`~framework.testbed_model.node.Node`. > > + password: The password of the user. The use of passwords is he= avily discouraged. > > + Please use keys instead. > > + arch: The architecture of the :class:`~framework.testbed_model= .node.Node`. > > + os: The operating system of the :class:`~framework.testbed_mod= el.node.Node`. > > + lcores: A comma delimited list of logical cores to use when ru= nning DPDK. > > + use_first_core: If :data:`True`, the first logical core won't = be used. > > + hugepages: An optional hugepage configuration. > > + ports: The ports that can be used in testing. > > + """ > > + > > name: str > > hostname: str > > user: str > > @@ -123,57 +246,91 @@ class NodeConfiguration: > > ports: list[PortConfig] > > > > @staticmethod > > - def from_dict(d: dict) -> Union["SutNodeConfiguration", "TGNodeCon= figuration"]: > > - hugepage_config =3D d.get("hugepages") > > - if hugepage_config: > > - if "force_first_numa" not in hugepage_config: > > - hugepage_config["force_first_numa"] =3D False > > - hugepage_config =3D HugepageConfiguration(**hugepage_confi= g) > > - > > - common_config =3D { > > - "name": d["name"], > > - "hostname": d["hostname"], > > - "user": d["user"], > > - "password": d.get("password"), > > - "arch": Architecture(d["arch"]), > > - "os": OS(d["os"]), > > - "lcores": d.get("lcores", "1"), > > - "use_first_core": d.get("use_first_core", False), > > - "hugepages": hugepage_config, > > - "ports": [PortConfig.from_dict(d["name"], port) for port i= n d["ports"]], > > - } > > - > > + def from_dict( > > + d: NodeConfigDict, > > + ) -> Union["SutNodeConfiguration", "TGNodeConfiguration"]: > > + """A convenience method that processes the inputs before creat= ing a specialized instance. > > + > > + Args: > > + d: The configuration dictionary. > > + > > + Returns: > > + Either an SUT or TG configuration instance. > > + """ > > + hugepage_config =3D None > > + if "hugepages" in d: > > + hugepage_config_dict =3D d["hugepages"] > > + if "force_first_numa" not in hugepage_config_dict: > > + hugepage_config_dict["force_first_numa"] =3D False > > + hugepage_config =3D HugepageConfiguration(**hugepage_confi= g_dict) > > + > > + # The calls here contain duplicated code which is here because= Mypy doesn't > > + # properly support dictionary unpacking with TypedDicts > > if "traffic_generator" in d: > > return TGNodeConfiguration( > > + name=3Dd["name"], > > + hostname=3Dd["hostname"], > > + user=3Dd["user"], > > + password=3Dd.get("password"), > > + arch=3DArchitecture(d["arch"]), > > + os=3DOS(d["os"]), > > + lcores=3Dd.get("lcores", "1"), > > + use_first_core=3Dd.get("use_first_core", False), > > + hugepages=3Dhugepage_config, > > + ports=3D[PortConfig.from_dict(d["name"], port) for por= t in d["ports"]], > > traffic_generator=3DTrafficGeneratorConfig.from_dict( > > d["traffic_generator"] > > ), > > - **common_config, > > ) > > else: > > return SutNodeConfiguration( > > - memory_channels=3Dd.get("memory_channels", 1), **commo= n_config > > + name=3Dd["name"], > > + hostname=3Dd["hostname"], > > + user=3Dd["user"], > > + password=3Dd.get("password"), > > + arch=3DArchitecture(d["arch"]), > > + os=3DOS(d["os"]), > > + lcores=3Dd.get("lcores", "1"), > > + use_first_core=3Dd.get("use_first_core", False), > > + hugepages=3Dhugepage_config, > > + ports=3D[PortConfig.from_dict(d["name"], port) for por= t in d["ports"]], > > + memory_channels=3Dd.get("memory_channels", 1), > > ) > > > > > > @dataclass(slots=3DTrue, frozen=3DTrue) > > class SutNodeConfiguration(NodeConfiguration): > > + """:class:`~framework.testbed_model.sut_node.SutNode` specific con= figuration. > > + > > + Attributes: > > + memory_channels: The number of memory channels to use when run= ning DPDK. > > + """ > > + > > memory_channels: int > > > > > > @dataclass(slots=3DTrue, frozen=3DTrue) > > class TGNodeConfiguration(NodeConfiguration): > > + """:class:`~framework.testbed_model.tg_node.TGNode` specific confi= guration. > > + > > + Attributes: > > + traffic_generator: The configuration of the traffic generator = present on the TG node. > > + """ > > + > > traffic_generator: ScapyTrafficGeneratorConfig > > > > > > @dataclass(slots=3DTrue, frozen=3DTrue) > > class NodeInfo: > > - """Class to hold important versions within the node. > > - > > - This class, unlike the NodeConfiguration class, cannot be generate= d at the start. > > - This is because we need to initialize a connection with the node b= efore we can > > - collect the information needed in this class. Therefore, it cannot= be a part of > > - the configuration class above. > > + """Supplemental node information. > > + > > + Attributes: > > + os_name: The name of the running operating system of > > + the :class:`~framework.testbed_model.node.Node`. > > + os_version: The version of the running operating system of > > + the :class:`~framework.testbed_model.node.Node`. > > + kernel_version: The kernel version of the running operating sy= stem of > > + the :class:`~framework.testbed_model.node.Node`. > > """ > > > > os_name: str > > @@ -183,6 +340,20 @@ class NodeInfo: > > > > @dataclass(slots=3DTrue, frozen=3DTrue) > > class BuildTargetConfiguration: > > + """DPDK build configuration. > > + > > + The configuration used for building DPDK. > > + > > + Attributes: > > + arch: The target architecture to build for. > > + os: The target os to build for. > > + cpu: The target CPU to build for. > > + compiler: The compiler executable to use. > > + compiler_wrapper: This string will be put in front of the comp= iler when > > + executing the build. Useful for adding wrapper commands, s= uch as ``ccache``. > > + name: The name of the compiler. > > + """ > > + > > arch: Architecture > > os: OS > > cpu: CPUType > > @@ -191,7 +362,18 @@ class BuildTargetConfiguration: > > name: str > > > > @staticmethod > > - def from_dict(d: dict) -> "BuildTargetConfiguration": > > + def from_dict(d: BuildTargetConfigDict) -> "BuildTargetConfigurati= on": > > + r"""A convenience method that processes the inputs before crea= ting an instance. > > + > > + `arch`, `os`, `cpu` and `compiler` are converted to :class:`En= um`\s and > > + `name` is constructed from `arch`, `os`, `cpu` and `compiler`. > > + > > + Args: > > + d: The configuration dictionary. > > + > > + Returns: > > + The build target configuration instance. > > + """ > > return BuildTargetConfiguration( > > arch=3DArchitecture(d["arch"]), > > os=3DOS(d["os"]), > > @@ -204,23 +386,29 @@ def from_dict(d: dict) -> "BuildTargetConfigurati= on": > > > > @dataclass(slots=3DTrue, frozen=3DTrue) > > class BuildTargetInfo: > > - """Class to hold important versions within the build target. > > + """Various versions and other information about a build target. > > > > - This is very similar to the NodeInfo class, it just instead holds = information > > - for the build target. > > + Attributes: > > + dpdk_version: The DPDK version that was built. > > + compiler_version: The version of the compiler used to build DP= DK. > > """ > > > > dpdk_version: str > > compiler_version: str > > > > > > -class TestSuiteConfigDict(TypedDict): > > - suite: str > > - cases: list[str] > > - > > - > > @dataclass(slots=3DTrue, frozen=3DTrue) > > class TestSuiteConfig: > > + """Test suite configuration. > > + > > + Information about a single test suite to be executed. > > + > > + Attributes: > > + test_suite: The name of the test suite module without the star= ting ``TestSuite_``. > > + test_cases: The names of test cases from this test suite to ex= ecute. > > + If empty, all test cases will be executed. > > + """ > > + > > test_suite: str > > test_cases: list[str] > > > > @@ -228,6 +416,14 @@ class TestSuiteConfig: > > def from_dict( > > entry: str | TestSuiteConfigDict, > > ) -> "TestSuiteConfig": > > + """Create an instance from two different types. > > + > > + Args: > > + entry: Either a suite name or a dictionary containing the = config. > > + > > + Returns: > > + The test suite configuration instance. > > + """ > > if isinstance(entry, str): > > return TestSuiteConfig(test_suite=3Dentry, test_cases=3D[= ]) > > elif isinstance(entry, dict): > > @@ -238,19 +434,49 @@ def from_dict( > > > > @dataclass(slots=3DTrue, frozen=3DTrue) > > class ExecutionConfiguration: > > + """The configuration of an execution. > > + > > + The configuration contains testbed information, what tests to exec= ute > > + and with what DPDK build. > > + > > + Attributes: > > + build_targets: A list of DPDK builds to test. > > + perf: Whether to run performance tests. > > + func: Whether to run functional tests. > > + skip_smoke_tests: Whether to skip smoke tests. > > + test_suites: The names of test suites and/or test cases to exe= cute. > > + system_under_test_node: The SUT node to use in this execution. > > + traffic_generator_node: The TG node to use in this execution. > > + vdevs: The names of virtual devices to test. > > + """ > > + > > build_targets: list[BuildTargetConfiguration] > > perf: bool > > func: bool > > + skip_smoke_tests: bool > > test_suites: list[TestSuiteConfig] > > system_under_test_node: SutNodeConfiguration > > traffic_generator_node: TGNodeConfiguration > > vdevs: list[str] > > - skip_smoke_tests: bool > > > > @staticmethod > > def from_dict( > > - d: dict, node_map: dict[str, Union[SutNodeConfiguration | TGNo= deConfiguration]] > > + d: ExecutionConfigDict, > > + node_map: dict[str, Union[SutNodeConfiguration | TGNodeConfigu= ration]], > > ) -> "ExecutionConfiguration": > > + """A convenience method that processes the inputs before creat= ing an instance. > > + > > + The build target and the test suite config is transformed into= their respective objects. > > is -> are > > > + SUT and TG configuration are taken from `node_map`. The other = (:class:`bool`) attributes are > > configuration*s* > > > + just stored. > > + > > + Args: > > + d: The configuration dictionary. > > + node_map: A dictionary mapping node names to their config = objects. > > + > > + Returns: > > + The execution configuration instance. > > + """ > > build_targets: list[BuildTargetConfiguration] =3D list( > > map(BuildTargetConfiguration.from_dict, d["build_targets"= ]) > > ) > > @@ -291,10 +517,31 @@ def from_dict( > > > > @dataclass(slots=3DTrue, frozen=3DTrue) > > class Configuration: > > + """DTS testbed and test configuration. > > + > > + The node configuration is not stored in this object. Rather, all u= sed node configurations > > + are stored inside the execution configuration where the nodes are = actually used. > > + > > + Attributes: > > + executions: Execution configurations. > > + """ > > + > > executions: list[ExecutionConfiguration] > > > > @staticmethod > > - def from_dict(d: dict) -> "Configuration": > > + def from_dict(d: ConfigurationDict) -> "Configuration": > > + """A convenience method that processes the inputs before creat= ing an instance. > > + > > + Build target and test suite config is transformed into their r= espective objects. > > is -> are > > > + SUT and TG configuration are taken from `node_map`. The other = (:class:`bool`) attributes are > > configuration*s* > > > + just stored. > > + > > + Args: > > + d: The configuration dictionary. > > + > > + Returns: > > + The whole configuration instance. > > + """ > > nodes: list[Union[SutNodeConfiguration | TGNodeConfiguration]= ] =3D list( > > map(NodeConfiguration.from_dict, d["nodes"]) > > ) > > @@ -313,9 +560,17 @@ def from_dict(d: dict) -> "Configuration": > > > > > > def load_config() -> Configuration: > > - """ > > - Loads the configuration file and the configuration file schema, > > - validates the configuration file, and creates a configuration obje= ct. > > + """Load DTS test run configuration from a file. > > + > > + Load the YAML test run configuration file > > + and :download:`the configuration file schema `, > > + validate the test run configuration file, and create a test run co= nfiguration object. > > + > > + The YAML test run configuration file is specified in the :option:`= --config-file` command line > > + argument or the :envvar:`DTS_CFG_FILE` environment variable. > > + > > + Returns: > > + The parsed test run configuration. > > """ > > with open(SETTINGS.config_file_path, "r") as f: > > config_data =3D yaml.safe_load(f) > > @@ -326,6 +581,8 @@ def load_config() -> Configuration: > > > > with open(schema_path, "r") as f: > > schema =3D json.load(f) > > - config: dict[str, Any] =3D warlock.model_factory(schema, name=3D"_= Config")(config_data) > > - config_obj: Configuration =3D Configuration.from_dict(dict(config)= ) > > + config =3D warlock.model_factory(schema, name=3D"_Config")(config_= data) > > + config_obj: Configuration =3D Configuration.from_dict( > > + dict(config) # type: ignore[arg-type] > > + ) > > return config_obj > > diff --git a/dts/framework/config/types.py b/dts/framework/config/types= .py > > new file mode 100644 > > index 0000000000..1927910d88 > > --- /dev/null > > +++ b/dts/framework/config/types.py > > @@ -0,0 +1,132 @@ > > +# SPDX-License-Identifier: BSD-3-Clause > > +# Copyright(c) 2023 PANTHEON.tech s.r.o. > > + > > +"""Configuration dictionary contents specification. > > + > > +These type definitions serve as documentation of the configuration dic= tionary contents. > > + > > +The definitions use the built-in :class:`~typing.TypedDict` construct. > > +""" > > + > > +from typing import TypedDict > > + > > + > > +class PortConfigDict(TypedDict): > > + """Allowed keys and values.""" > > + > > + #: > > + pci: str > > + #: > > + os_driver_for_dpdk: str > > + #: > > + os_driver: str > > + #: > > + peer_node: str > > + #: > > + peer_pci: str > > + > > + > > +class TrafficGeneratorConfigDict(TypedDict): > > + """Allowed keys and values.""" > > + > > + #: > > + type: str > > + > > + > > +class HugepageConfigurationDict(TypedDict): > > + """Allowed keys and values.""" > > + > > + #: > > + amount: int > > + #: > > + force_first_numa: bool > > + > > + > > +class NodeConfigDict(TypedDict): > > + """Allowed keys and values.""" > > + > > + #: > > + hugepages: HugepageConfigurationDict > > + #: > > + name: str > > + #: > > + hostname: str > > + #: > > + user: str > > + #: > > + password: str > > + #: > > + arch: str > > + #: > > + os: str > > + #: > > + lcores: str > > + #: > > + use_first_core: bool > > + #: > > + ports: list[PortConfigDict] > > + #: > > + memory_channels: int > > + #: > > + traffic_generator: TrafficGeneratorConfigDict > > + > > + > > +class BuildTargetConfigDict(TypedDict): > > + """Allowed keys and values.""" > > + > > + #: > > + arch: str > > + #: > > + os: str > > + #: > > + cpu: str > > + #: > > + compiler: str > > + #: > > + compiler_wrapper: str > > + > > + > > +class TestSuiteConfigDict(TypedDict): > > + """Allowed keys and values.""" > > + > > + #: > > + suite: str > > + #: > > + cases: list[str] > > + > > + > > +class ExecutionSUTConfigDict(TypedDict): > > + """Allowed keys and values.""" > > + > > + #: > > + node_name: str > > + #: > > + vdevs: list[str] > > + > > + > > +class ExecutionConfigDict(TypedDict): > > + """Allowed keys and values.""" > > + > > + #: > > + build_targets: list[BuildTargetConfigDict] > > + #: > > + perf: bool > > + #: > > + func: bool > > + #: > > + skip_smoke_tests: bool > > + #: > > + test_suites: TestSuiteConfigDict > > + #: > > + system_under_test_node: ExecutionSUTConfigDict > > + #: > > + traffic_generator_node: str > > + > > + > > +class ConfigurationDict(TypedDict): > > + """Allowed keys and values.""" > > + > > + #: > > + nodes: list[NodeConfigDict] > > + #: > > + executions: list[ExecutionConfigDict] >