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 3297C42E60; Tue, 18 Jul 2023 17:55:49 +0200 (CEST) Received: from mails.dpdk.org (localhost [127.0.0.1]) by mails.dpdk.org (Postfix) with ESMTP id C39E2410D3; Tue, 18 Jul 2023 17:55:48 +0200 (CEST) Received: from mail-oo1-f46.google.com (mail-oo1-f46.google.com [209.85.161.46]) by mails.dpdk.org (Postfix) with ESMTP id 9556440E0F for ; Tue, 18 Jul 2023 17:55:46 +0200 (CEST) Received: by mail-oo1-f46.google.com with SMTP id 006d021491bc7-5636ab8240cso2659199eaf.3 for ; Tue, 18 Jul 2023 08:55:46 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=iol.unh.edu; s=unh-iol; t=1689695746; x=1692287746; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:from:to:cc:subject:date:message-id:reply-to; bh=2fY5pC1Cg+SC8SuPNKmmCfF2TZ4rE8uMEkavcj4GLnU=; b=H3r8fRCPW817SsS+EKc0Za6VqqFicY83/CogH3duAs88VBHwgTsa+RRdmzqjAeL5y2 1+wT9G0HOwvmk94D3RCtS901uYjqMqUwHuGLmVuPCjI+T/RvlVwEDRBYseP3rJkj2cID 9RfKZNYDOdBmHi+iyUMmgjq+yKR723h3lNvLg= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1689695746; x=1692287746; h=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=2fY5pC1Cg+SC8SuPNKmmCfF2TZ4rE8uMEkavcj4GLnU=; b=gNzCqYlNx6M6T/g3oRS1EEzu3ZlbBB2XEZAqZuYx6EIMn2ZnrLO2dgNnn8RaI/IF4O Y+sROrTo4ZLjf3yMAdVsancuaCBq3qlUos4uTtCAR7buaJq49OHs6bl3cZg9+vVZMhK1 s4Di5NvnIqwuihjdb85aPtivyJiapjKoXqSTZNdYesjcA23B0DN/YjxTVQsKAvOMgQj8 Mbi+TVLYU56fhBxmJrv4Th3iEeYydkTJOrQlpo0UOw6nzilR9jT0t0WvjrAXjZ/FHOIK 6o0WJhdtBlI3lVADciwWoqlvxMRjG7ytuo02F6otwFBu9bM+6fStH9u9vFuSmW1a5mFT /frg== X-Gm-Message-State: ABy/qLZGfFzOAEfCBBXExpviYUEafPJW8f9gKq41Yp1cwFXLcXJrcMou Og7Bnp23i1Pgcj9YCYbx1ONLVm0zN/WmcIsNuq1DFA== X-Google-Smtp-Source: APBJJlFSMWxyuofRDcoqwAklJAC0FbZ48gz9iUPQd6T2pj7qcrnlKc1gOKVmy5X8+rLf+uzlIEbJ/W/nf2mi4lyk4xs= X-Received: by 2002:a05:6358:5e07:b0:134:d27d:a338 with SMTP id q7-20020a0563585e0700b00134d27da338mr4665599rwn.25.1689695745599; Tue, 18 Jul 2023 08:55:45 -0700 (PDT) MIME-Version: 1.0 References: <20230420093109.594704-1-juraj.linkes@pantheon.tech> <20230717110709.39220-1-juraj.linkes@pantheon.tech> <20230717110709.39220-3-juraj.linkes@pantheon.tech> In-Reply-To: <20230717110709.39220-3-juraj.linkes@pantheon.tech> From: Jeremy Spewock Date: Tue, 18 Jul 2023 11:55:34 -0400 Message-ID: Subject: Re: [PATCH v2 2/6] dts: add traffic generator config To: =?UTF-8?Q?Juraj_Linke=C5=A1?= Cc: thomas@monjalon.net, Honnappa.Nagarahalli@arm.com, lijuan.tu@intel.com, probb@iol.unh.edu, dev@dpdk.org Content-Type: multipart/alternative; boundary="000000000000b209790600c4f265" 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 --000000000000b209790600c4f265 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable Hey Juraj, These changes look good to me, I just had one question. Is the plan to make specifying a TG always required or optional for cases where it isn't required? It seems like it is written now as something that is required for every execution. I don't think this is necessarily a bad thing, I just wonder if it would be beneficial at all to make it optional to allow for executions that don't utilize a TG to just not specify it. On Mon, Jul 17, 2023 at 7:07=E2=80=AFAM Juraj Linke=C5=A1 wrote: > Node configuration - where to connect, what ports to use and what TG to > use. > > Signed-off-by: Juraj Linke=C5=A1 > --- > dts/conf.yaml | 26 ++++++- > dts/framework/config/__init__.py | 87 +++++++++++++++++++--- > dts/framework/config/conf_yaml_schema.json | 29 +++++++- > dts/framework/dts.py | 12 +-- > dts/framework/testbed_model/node.py | 4 +- > dts/framework/testbed_model/sut_node.py | 6 +- > 6 files changed, 141 insertions(+), 23 deletions(-) > > diff --git a/dts/conf.yaml b/dts/conf.yaml > index 3a5d87cb49..7f089022ba 100644 > --- a/dts/conf.yaml > +++ b/dts/conf.yaml > @@ -13,10 +13,11 @@ executions: > skip_smoke_tests: false # optional flag that allow you to skip smoke > tests > test_suites: > - hello_world > - system_under_test: > + system_under_test_node: > node_name: "SUT 1" > vdevs: # optional; if removed, vdevs won't be used in the executio= n > - "crypto_openssl" > + traffic_generator_node: "TG 1" > nodes: > - name: "SUT 1" > hostname: sut1.change.me.localhost > @@ -40,3 +41,26 @@ nodes: > os_driver: i40e > peer_node: "TG 1" > peer_pci: "0000:00:08.1" > + - name: "TG 1" > + hostname: tg1.change.me.localhost > + user: dtsuser > + arch: x86_64 > + os: linux > + lcores: "" > + ports: > + - pci: "0000:00:08.0" > + os_driver_for_dpdk: rdma > + os_driver: rdma > + peer_node: "SUT 1" > + peer_pci: "0000:00:08.0" > + - pci: "0000:00:08.1" > + os_driver_for_dpdk: rdma > + os_driver: rdma > + peer_node: "SUT 1" > + peer_pci: "0000:00:08.1" > + use_first_core: false > + hugepages: # optional; if removed, will use system hugepage > configuration > + amount: 256 > + force_first_numa: false > + traffic_generator: > + type: SCAPY > diff --git a/dts/framework/config/__init__.py > b/dts/framework/config/__init__.py > index fad56cc520..72aa021b97 100644 > --- a/dts/framework/config/__init__.py > +++ b/dts/framework/config/__init__.py > @@ -13,7 +13,7 @@ > from dataclasses import dataclass > from enum import Enum, auto, unique > from pathlib import PurePath > -from typing import Any, TypedDict > +from typing import Any, TypedDict, Union > > import warlock # type: ignore > import yaml > @@ -55,6 +55,11 @@ class Compiler(StrEnum): > msvc =3D auto() > > > +@unique > +class TrafficGeneratorType(StrEnum): > + SCAPY =3D auto() > + > + > # Slots enables some optimizations, by pre-allocating space for the > defined > # attributes in the underlying data structure. > # > @@ -79,6 +84,29 @@ class PortConfig: > def from_dict(node: str, d: dict) -> "PortConfig": > return PortConfig(node=3Dnode, **d) > > + def __str__(self) -> str: > + return f"Port {self.pci} on node {self.node}" > + > + > +@dataclass(slots=3DTrue, frozen=3DTrue) > +class TrafficGeneratorConfig: > + traffic_generator_type: TrafficGeneratorType > + > + @staticmethod > + def from_dict(d: dict): > + # This looks useless now, but is designed to allow expansion to > traffic > + # generators that require more configuration later. > + match TrafficGeneratorType(d["type"]): > + case TrafficGeneratorType.SCAPY: > + return ScapyTrafficGeneratorConfig( > + traffic_generator_type=3DTrafficGeneratorType.SCAPY > + ) > + > + > +@dataclass(slots=3DTrue, frozen=3DTrue) > +class ScapyTrafficGeneratorConfig(TrafficGeneratorConfig): > + pass > + > > @dataclass(slots=3DTrue, frozen=3DTrue) > class NodeConfiguration: > @@ -90,17 +118,17 @@ class NodeConfiguration: > os: OS > lcores: str > use_first_core: bool > - memory_channels: int > hugepages: HugepageConfiguration | None > ports: list[PortConfig] > > @staticmethod > - def from_dict(d: dict) -> "NodeConfiguration": > + def from_dict(d: dict) -> Union["SutNodeConfiguration", > "TGNodeConfiguration"]: > 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_config) > + > common_config =3D { > "name": d["name"], > "hostname": d["hostname"], > @@ -110,12 +138,31 @@ def from_dict(d: dict) -> "NodeConfiguration": > "os": OS(d["os"]), > "lcores": d.get("lcores", "1"), > "use_first_core": d.get("use_first_core", False), > - "memory_channels": d.get("memory_channels", 1), > "hugepages": hugepage_config, > "ports": [PortConfig.from_dict(d["name"], port) for port in > d["ports"]], > } > > - return NodeConfiguration(**common_config) > + if "traffic_generator" in d: > + return TGNodeConfiguration( > + traffic_generator=3DTrafficGeneratorConfig.from_dict( > + d["traffic_generator"] > + ), > + **common_config, > + ) > + else: > + return SutNodeConfiguration( > + memory_channels=3Dd.get("memory_channels", 1), > **common_config > + ) > + > + > +@dataclass(slots=3DTrue, frozen=3DTrue) > +class SutNodeConfiguration(NodeConfiguration): > + memory_channels: int > + > + > +@dataclass(slots=3DTrue, frozen=3DTrue) > +class TGNodeConfiguration(NodeConfiguration): > + traffic_generator: ScapyTrafficGeneratorConfig > > > @dataclass(slots=3DTrue, frozen=3DTrue) > @@ -194,23 +241,40 @@ class ExecutionConfiguration: > perf: bool > func: bool > test_suites: list[TestSuiteConfig] > - system_under_test: NodeConfiguration > + 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) -> "ExecutionConfiguration": > + def from_dict( > + d: dict, node_map: dict[str, Union[SutNodeConfiguration | > TGNodeConfiguration]] > + ) -> "ExecutionConfiguration": > build_targets: list[BuildTargetConfiguration] =3D list( > map(BuildTargetConfiguration.from_dict, d["build_targets"]) > ) > test_suites: list[TestSuiteConfig] =3D list( > map(TestSuiteConfig.from_dict, d["test_suites"]) > ) > - sut_name =3D d["system_under_test"]["node_name"] > + sut_name =3D d["system_under_test_node"]["node_name"] > skip_smoke_tests =3D d.get("skip_smoke_tests", False) > assert sut_name in node_map, f"Unknown SUT {sut_name} in > execution {d}" > + system_under_test_node =3D node_map[sut_name] > + assert isinstance( > + system_under_test_node, SutNodeConfiguration > + ), f"Invalid SUT configuration {system_under_test_node}" > + > + tg_name =3D d["traffic_generator_node"] > + assert tg_name in node_map, f"Unknown TG {tg_name} in execution > {d}" > + traffic_generator_node =3D node_map[tg_name] > + assert isinstance( > + traffic_generator_node, TGNodeConfiguration > + ), f"Invalid TG configuration {traffic_generator_node}" > + > vdevs =3D ( > - d["system_under_test"]["vdevs"] if "vdevs" in > d["system_under_test"] else [] > + d["system_under_test_node"]["vdevs"] > + if "vdevs" in d["system_under_test_node"] > + else [] > ) > return ExecutionConfiguration( > build_targets=3Dbuild_targets, > @@ -218,7 +282,8 @@ def from_dict(d: dict, node_map: dict) -> > "ExecutionConfiguration": > func=3Dd["func"], > skip_smoke_tests=3Dskip_smoke_tests, > test_suites=3Dtest_suites, > - system_under_test=3Dnode_map[sut_name], > + system_under_test_node=3Dsystem_under_test_node, > + traffic_generator_node=3Dtraffic_generator_node, > vdevs=3Dvdevs, > ) > > @@ -229,7 +294,7 @@ class Configuration: > > @staticmethod > def from_dict(d: dict) -> "Configuration": > - nodes: list[NodeConfiguration] =3D list( > + nodes: list[Union[SutNodeConfiguration | TGNodeConfiguration]] = =3D > list( > map(NodeConfiguration.from_dict, d["nodes"]) > ) > assert len(nodes) > 0, "There must be a node to test" > diff --git a/dts/framework/config/conf_yaml_schema.json > b/dts/framework/config/conf_yaml_schema.json > index 61f52b4365..76df84840a 100644 > --- a/dts/framework/config/conf_yaml_schema.json > +++ b/dts/framework/config/conf_yaml_schema.json > @@ -164,6 +164,11 @@ > "amount" > ] > }, > + "mac_address": { > + "type": "string", > + "description": "A MAC address", > + "pattern": "^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$" > + }, > "pci_address": { > "type": "string", > "pattern": > "^[\\da-fA-F]{4}:[\\da-fA-F]{2}:[\\da-fA-F]{2}.\\d:?\\w*$" > @@ -286,6 +291,22 @@ > ] > }, > "minimum": 1 > + }, > + "traffic_generator": { > + "oneOf": [ > + { > + "type": "object", > + "description": "Scapy traffic generator. Used for > functional testing.", > + "properties": { > + "type": { > + "type": "string", > + "enum": [ > + "SCAPY" > + ] > + } > + } > + } > + ] > } > }, > "additionalProperties": false, > @@ -336,7 +357,7 @@ > "description": "Optional field that allows you to skip smoke > testing", > "type": "boolean" > }, > - "system_under_test": { > + "system_under_test_node": { > "type":"object", > "properties": { > "node_name": { > @@ -353,6 +374,9 @@ > "required": [ > "node_name" > ] > + }, > + "traffic_generator_node": { > + "$ref": "#/definitions/node_name" > } > }, > "additionalProperties": false, > @@ -361,7 +385,8 @@ > "perf", > "func", > "test_suites", > - "system_under_test" > + "system_under_test_node", > + "traffic_generator_node" > ] > }, > "minimum": 1 > diff --git a/dts/framework/dts.py b/dts/framework/dts.py > index 7b09d8fba8..372bc72787 100644 > --- a/dts/framework/dts.py > +++ b/dts/framework/dts.py > @@ -38,17 +38,17 @@ def run_all() -> None: > # for all Execution sections > for execution in CONFIGURATION.executions: > sut_node =3D None > - if execution.system_under_test.name in nodes: > + if execution.system_under_test_node.name in nodes: > # a Node with the same name already exists > - sut_node =3D nodes[execution.system_under_test.name] > + sut_node =3D nodes[execution.system_under_test_node.name= ] > else: > # the SUT has not been initialized yet > try: > - sut_node =3D SutNode(execution.system_under_test) > + sut_node =3D SutNode(execution.system_under_test_nod= e) > result.update_setup(Result.PASS) > except Exception as e: > dts_logger.exception( > - f"Connection to node > {execution.system_under_test} failed." > + f"Connection to node > {execution.system_under_test_node} failed." > ) > result.update_setup(Result.FAIL, e) > else: > @@ -87,7 +87,9 @@ def _run_execution( > Run the given execution. This involves running the execution setup a= s > well as > running all build targets in the given execution. > """ > - dts_logger.info(f"Running execution with SUT '{ > execution.system_under_test.name}'.") > + dts_logger.info( > + f"Running execution with SUT '{ > execution.system_under_test_node.name}'." > + ) > execution_result =3D result.add_execution(sut_node.config, > sut_node.node_info) > > try: > diff --git a/dts/framework/testbed_model/node.py > b/dts/framework/testbed_model/node.py > index c5147e0ee6..d2d55d904e 100644 > --- a/dts/framework/testbed_model/node.py > +++ b/dts/framework/testbed_model/node.py > @@ -48,6 +48,8 @@ def __init__(self, node_config: NodeConfiguration): > self._logger =3D getLogger(self.name) > self.main_session =3D create_session(self.config, self.name, > self._logger) > > + self._logger.info(f"Connected to node: {self.name}") > + > self._get_remote_cpus() > # filter the node lcores according to user config > self.lcores =3D LogicalCoreListFilter( > @@ -56,8 +58,6 @@ def __init__(self, node_config: NodeConfiguration): > > self._other_sessions =3D [] > > - self._logger.info(f"Created node: {self.name}") > - > def set_up_execution(self, execution_config: ExecutionConfiguration) > -> None: > """ > Perform the execution setup that will be done for each execution > diff --git a/dts/framework/testbed_model/sut_node.py > b/dts/framework/testbed_model/sut_node.py > index 53953718a1..bcad364435 100644 > --- a/dts/framework/testbed_model/sut_node.py > +++ b/dts/framework/testbed_model/sut_node.py > @@ -13,8 +13,8 @@ > BuildTargetConfiguration, > BuildTargetInfo, > InteractiveApp, > - NodeConfiguration, > NodeInfo, > + SutNodeConfiguration, > ) > from framework.remote_session import ( > CommandResult, > @@ -83,6 +83,7 @@ class SutNode(Node): > Another key capability is building DPDK according to given build > target. > """ > > + config: SutNodeConfiguration > _dpdk_prefix_list: list[str] > _dpdk_timestamp: str > _build_target_config: BuildTargetConfiguration | None > @@ -95,7 +96,7 @@ class SutNode(Node): > _node_info: NodeInfo | None > _compiler_version: str | None > > - def __init__(self, node_config: NodeConfiguration): > + def __init__(self, node_config: SutNodeConfiguration): > super(SutNode, self).__init__(node_config) > self._dpdk_prefix_list =3D [] > self._build_target_config =3D None > @@ -110,6 +111,7 @@ def __init__(self, node_config: NodeConfiguration): > self._dpdk_version =3D None > self._node_info =3D None > self._compiler_version =3D None > + self._logger.info(f"Created node: {self.name}") > > @property > def _remote_dpdk_dir(self) -> PurePath: > -- > 2.34.1 > > --000000000000b209790600c4f265 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable
Hey Juraj,

These changes look good to me, I jus= t had one question. Is the plan to make specifying a TG always required or = optional for cases where it isn't required? It seems like it is written= now as something that is required for every execution. I don't think t= his is necessarily a bad thing, I just wonder if it would be beneficial at = all to make it optional to allow for executions that don't utilize a TG= to just not specify it.

On Mon, Jul 17, 2023 at 7:07=E2=80=AFAM J= uraj Linke=C5=A1 <juraj.linkes@pantheon.tech> wrote:
Node configuration - where to co= nnect, what ports to use and what TG to
use.

Signed-off-by: Juraj Linke=C5=A1 <juraj.linkes@pantheon.tech>
---
=C2=A0dts/conf.yaml=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 | 26 ++++++-
=C2=A0dts/framework/config/__init__.py=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0| 87 +++++++++++++++++++---
=C2=A0dts/framework/config/conf_yaml_schema.json | 29 +++++++-
=C2=A0dts/framework/dts.py=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0| 12 +--
=C2=A0dts/framework/testbed_model/node.py=C2=A0 =C2=A0 =C2=A0 =C2=A0 |=C2= =A0 4 +-
=C2=A0dts/framework/testbed_model/sut_node.py=C2=A0 =C2=A0 |=C2=A0 6 +-
=C2=A06 files changed, 141 insertions(+), 23 deletions(-)

diff --git a/dts/conf.yaml b/dts/conf.yaml
index 3a5d87cb49..7f089022ba 100644
--- a/dts/conf.yaml
+++ b/dts/conf.yaml
@@ -13,10 +13,11 @@ executions:
=C2=A0 =C2=A0 =C2=A0skip_smoke_tests: false # optional flag that allow you = to skip smoke tests
=C2=A0 =C2=A0 =C2=A0test_suites:
=C2=A0 =C2=A0 =C2=A0 =C2=A0- hello_world
-=C2=A0 =C2=A0 system_under_test:
+=C2=A0 =C2=A0 system_under_test_node:
=C2=A0 =C2=A0 =C2=A0 =C2=A0node_name: "SUT 1"
=C2=A0 =C2=A0 =C2=A0 =C2=A0vdevs: # optional; if removed, vdevs won't b= e used in the execution
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0- "crypto_openssl"
+=C2=A0 =C2=A0 traffic_generator_node: "TG 1"
=C2=A0nodes:
=C2=A0 =C2=A0- name: "SUT 1"
=C2=A0 =C2=A0 =C2=A0hostname: sut1.change.me.localhost
@@ -40,3 +41,26 @@ nodes:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0os_driver: i40e
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0peer_node: "TG 1"
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0peer_pci: "0000:00:08.1"
+=C2=A0 - name: "TG 1"
+=C2=A0 =C2=A0 hostname: tg1.change.me.localhost
+=C2=A0 =C2=A0 user: dtsuser
+=C2=A0 =C2=A0 arch: x86_64
+=C2=A0 =C2=A0 os: linux
+=C2=A0 =C2=A0 lcores: ""
+=C2=A0 =C2=A0 ports:
+=C2=A0 =C2=A0 =C2=A0 - pci: "0000:00:08.0"
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 os_driver_for_dpdk: rdma
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 os_driver: rdma
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 peer_node: "SUT 1"
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 peer_pci: "0000:00:08.0"
+=C2=A0 =C2=A0 =C2=A0 - pci: "0000:00:08.1"
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 os_driver_for_dpdk: rdma
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 os_driver: rdma
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 peer_node: "SUT 1"
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 peer_pci: "0000:00:08.1"
+=C2=A0 =C2=A0 use_first_core: false
+=C2=A0 =C2=A0 hugepages:=C2=A0 # optional; if removed, will use system hug= epage configuration
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 amount: 256
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 force_first_numa: false
+=C2=A0 =C2=A0 traffic_generator:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 type: SCAPY
diff --git a/dts/framework/config/__init__.py b/dts/framework/config/__init= __.py
index fad56cc520..72aa021b97 100644
--- a/dts/framework/config/__init__.py
+++ b/dts/framework/config/__init__.py
@@ -13,7 +13,7 @@
=C2=A0from dataclasses import dataclass
=C2=A0from enum import Enum, auto, unique
=C2=A0from pathlib import PurePath
-from typing import Any, TypedDict
+from typing import Any, TypedDict, Union

=C2=A0import warlock=C2=A0 # type: ignore
=C2=A0import yaml
@@ -55,6 +55,11 @@ class Compiler(StrEnum):
=C2=A0 =C2=A0 =C2=A0msvc =3D auto()


+@unique
+class TrafficGeneratorType(StrEnum):
+=C2=A0 =C2=A0 SCAPY =3D auto()
+
+
=C2=A0# Slots enables some optimizations, by pre-allocating space for the d= efined
=C2=A0# attributes in the underlying data structure.
=C2=A0#
@@ -79,6 +84,29 @@ class PortConfig:
=C2=A0 =C2=A0 =C2=A0def from_dict(node: str, d: dict) -> "PortConfi= g":
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return PortConfig(node=3Dnode, **d)

+=C2=A0 =C2=A0 def __str__(self) -> str:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 return f"Port {self.pci} on node {self.no= de}"
+
+
+@dataclass(slots=3DTrue, frozen=3DTrue)
+class TrafficGeneratorConfig:
+=C2=A0 =C2=A0 traffic_generator_type: TrafficGeneratorType
+
+=C2=A0 =C2=A0 @staticmethod
+=C2=A0 =C2=A0 def from_dict(d: dict):
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 # This looks useless now, but is designed to a= llow expansion to traffic
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 # generators that require more configuration l= ater.
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 match TrafficGeneratorType(d["type"]= ):
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 case TrafficGeneratorType.SCAPY:=
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 return ScapyTraffi= cGeneratorConfig(
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 traf= fic_generator_type=3DTrafficGeneratorType.SCAPY
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 )
+
+
+@dataclass(slots=3DTrue, frozen=3DTrue)
+class ScapyTrafficGeneratorConfig(TrafficGeneratorConfig):
+=C2=A0 =C2=A0 pass
+

=C2=A0@dataclass(slots=3DTrue, frozen=3DTrue)
=C2=A0class NodeConfiguration:
@@ -90,17 +118,17 @@ class NodeConfiguration:
=C2=A0 =C2=A0 =C2=A0os: OS
=C2=A0 =C2=A0 =C2=A0lcores: str
=C2=A0 =C2=A0 =C2=A0use_first_core: bool
-=C2=A0 =C2=A0 memory_channels: int
=C2=A0 =C2=A0 =C2=A0hugepages: HugepageConfiguration | None
=C2=A0 =C2=A0 =C2=A0ports: list[PortConfig]

=C2=A0 =C2=A0 =C2=A0@staticmethod
-=C2=A0 =C2=A0 def from_dict(d: dict) -> "NodeConfiguration":<= br> +=C2=A0 =C2=A0 def from_dict(d: dict) -> Union["SutNodeConfiguratio= n", "TGNodeConfiguration"]:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0hugepage_config =3D d.get("hugepages= ")
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0if hugepage_config:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0if "force_first_numa&q= uot; not in hugepage_config:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0hugepage_conf= ig["force_first_numa"] =3D False
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0hugepage_config =3D Hugepag= eConfiguration(**hugepage_config)
+
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0common_config =3D {
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"name": d["n= ame"],
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"hostname": d[&qu= ot;hostname"],
@@ -110,12 +138,31 @@ def from_dict(d: dict) -> "NodeConfiguration&= quot;:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"os": OS(d["= os"]),
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"lcores": d.get(&= quot;lcores", "1"),
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"use_first_core":= d.get("use_first_core", False),
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 "memory_channels": d.g= et("memory_channels", 1),
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"hugepages": huge= page_config,
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"ports": [PortCon= fig.from_dict(d["name"], port) for port in d["ports"]],=
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0}

-=C2=A0 =C2=A0 =C2=A0 =C2=A0 return NodeConfiguration(**common_config)
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 if "traffic_generator" in d:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 return TGNodeConfiguration(
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 traffic_generator= =3DTrafficGeneratorConfig.from_dict(
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 d[&q= uot;traffic_generator"]
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 ),
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 **common_config, +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 )
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 else:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 return SutNodeConfiguration(
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 memory_channels=3D= d.get("memory_channels", 1), **common_config
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 )
+
+
+@dataclass(slots=3DTrue, frozen=3DTrue)
+class SutNodeConfiguration(NodeConfiguration):
+=C2=A0 =C2=A0 memory_channels: int
+
+
+@dataclass(slots=3DTrue, frozen=3DTrue)
+class TGNodeConfiguration(NodeConfiguration):
+=C2=A0 =C2=A0 traffic_generator: ScapyTrafficGeneratorConfig


=C2=A0@dataclass(slots=3DTrue, frozen=3DTrue)
@@ -194,23 +241,40 @@ class ExecutionConfiguration:
=C2=A0 =C2=A0 =C2=A0perf: bool
=C2=A0 =C2=A0 =C2=A0func: bool
=C2=A0 =C2=A0 =C2=A0test_suites: list[TestSuiteConfig]
-=C2=A0 =C2=A0 system_under_test: NodeConfiguration
+=C2=A0 =C2=A0 system_under_test_node: SutNodeConfiguration
+=C2=A0 =C2=A0 traffic_generator_node: TGNodeConfiguration
=C2=A0 =C2=A0 =C2=A0vdevs: list[str]
=C2=A0 =C2=A0 =C2=A0skip_smoke_tests: bool

=C2=A0 =C2=A0 =C2=A0@staticmethod
-=C2=A0 =C2=A0 def from_dict(d: dict, node_map: dict) -> "Execution= Configuration":
+=C2=A0 =C2=A0 def from_dict(
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 d: dict, node_map: dict[str, Union[SutNodeConf= iguration | TGNodeConfiguration]]
+=C2=A0 =C2=A0 ) -> "ExecutionConfiguration":
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0build_targets: list[BuildTargetConfigurat= ion] =3D list(
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0map(BuildTargetConfiguratio= n.from_dict, d["build_targets"])
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0)
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0test_suites: list[TestSuiteConfig] =3D li= st(
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0map(TestSuiteConfig.from_di= ct, d["test_suites"])
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0)
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 sut_name =3D d["system_under_test"][= "node_name"]
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 sut_name =3D d["system_under_test_node&qu= ot;]["node_name"]
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0skip_smoke_tests =3D d.get("skip_smo= ke_tests", False)
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0assert sut_name in node_map, f"Unkno= wn SUT {sut_name} in execution {d}"
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 system_under_test_node =3D node_map[sut_name]<= br> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 assert isinstance(
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 system_under_test_node, SutNodeC= onfiguration
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 ), f"Invalid SUT configuration {system_un= der_test_node}"
+
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 tg_name =3D d["traffic_generator_node&quo= t;]
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 assert tg_name in node_map, f"Unknown TG = {tg_name} in execution {d}"
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 traffic_generator_node =3D node_map[tg_name] +=C2=A0 =C2=A0 =C2=A0 =C2=A0 assert isinstance(
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 traffic_generator_node, TGNodeCo= nfiguration
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 ), f"Invalid TG configuration {traffic_ge= nerator_node}"
+
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0vdevs =3D (
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 d["system_under_test"]= ["vdevs"] if "vdevs" in d["system_under_test"= ] else []
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 d["system_under_test_node&q= uot;]["vdevs"]
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 if "vdevs" in d["= system_under_test_node"]
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 else []
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0)
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return ExecutionConfiguration(
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0build_targets=3Dbuild_targe= ts,
@@ -218,7 +282,8 @@ def from_dict(d: dict, node_map: dict) -> "Exec= utionConfiguration":
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0func=3Dd["func"],=
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0skip_smoke_tests=3Dskip_smo= ke_tests,
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0test_suites=3Dtest_suites,<= br> -=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 system_under_test=3Dnode_map[sut= _name],
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 system_under_test_node=3Dsystem_= under_test_node,
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 traffic_generator_node=3Dtraffic= _generator_node,
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0vdevs=3Dvdevs,
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0)

@@ -229,7 +294,7 @@ class Configuration:

=C2=A0 =C2=A0 =C2=A0@staticmethod
=C2=A0 =C2=A0 =C2=A0def from_dict(d: dict) -> "Configuration":=
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 nodes: list[NodeConfiguration] =3D list(
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 nodes: list[Union[SutNodeConfiguration | TGNod= eConfiguration]] =3D list(
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0map(NodeConfiguration.from_= dict, d["nodes"])
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0)
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0assert len(nodes) > 0, "There mus= t be a node to test"
diff --git a/dts/framework/config/conf_yaml_schema.json b/dts/framework/con= fig/conf_yaml_schema.json
index 61f52b4365..76df84840a 100644
--- a/dts/framework/config/conf_yaml_schema.json
+++ b/dts/framework/config/conf_yaml_schema.json
@@ -164,6 +164,11 @@
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"amount"
=C2=A0 =C2=A0 =C2=A0 =C2=A0]
=C2=A0 =C2=A0 =C2=A0},
+=C2=A0 =C2=A0 "mac_address": {
+=C2=A0 =C2=A0 =C2=A0 "type": "string",
+=C2=A0 =C2=A0 =C2=A0 "description": "A MAC address", +=C2=A0 =C2=A0 =C2=A0 "pattern": "^([0-9A-Fa-f]{2}[:-]){5}([= 0-9A-Fa-f]{2})$"
+=C2=A0 =C2=A0 },
=C2=A0 =C2=A0 =C2=A0"pci_address": {
=C2=A0 =C2=A0 =C2=A0 =C2=A0"type": "string",
=C2=A0 =C2=A0 =C2=A0 =C2=A0"pattern": "^[\\da-fA-F]{4}:[\\da= -fA-F]{2}:[\\da-fA-F]{2}.\\d:?\\w*$"
@@ -286,6 +291,22 @@
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0]
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0},
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"minimum": 1
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 },
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 "traffic_generator": {
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 "oneOf": [
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 {
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 "type": = "object",
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 "description&= quot;: "Scapy traffic generator. Used for functional testing.", +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 "properties&q= uot;: {
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 "type&= quot;: {
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 &quo= t;type": "string",
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 &quo= t;enum": [
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 "SCAPY"
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 ] +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 }
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 }
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 }
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 ]
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0}
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0},
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"additionalProperties": false,<= br> @@ -336,7 +357,7 @@
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"description": &q= uot;Optional field that allows you to skip smoke testing",
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"type": "boo= lean"
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0},
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 "system_under_test": {
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 "system_under_test_node": { =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"type":"obje= ct",
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"properties": { =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"node_name"= ;: {
@@ -353,6 +374,9 @@
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"required": [
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"node_name"= ;
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0]
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 },
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 "traffic_generator_node": { +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 "$ref": "#/defini= tions/node_name"
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0}
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0},
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"additionalProperties": false,<= br> @@ -361,7 +385,8 @@
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"perf",
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"func",
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"test_suites",
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 "system_under_test"
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 "system_under_test_node",
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 "traffic_generator_node"
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0]
=C2=A0 =C2=A0 =C2=A0 =C2=A0},
=C2=A0 =C2=A0 =C2=A0 =C2=A0"minimum": 1
diff --git a/dts/framework/dts.py b/dts/framework/dts.py
index 7b09d8fba8..372bc72787 100644
--- a/dts/framework/dts.py
+++ b/dts/framework/dts.py
@@ -38,17 +38,17 @@ def run_all() -> None:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0# for all Execution sections
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0for execution in CONFIGURATION.executions= :
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0sut_node =3D None
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 if execution.syste= m_under_test.name in nodes:
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 if execution.= system_under_test_node.name in nodes:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0# a Node with= the same name already exists
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 sut_node =3D nodes= [execution.system_under_test.name]
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 sut_node =3D nodes= [execution.system_under_test_node.name]
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0else:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0# the SUT has= not been initialized yet
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0try:
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 sut_= node =3D SutNode(execution.system_under_test)
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 sut_= node =3D SutNode(execution.system_under_test_node)
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0result.update_setup(Result.PASS)
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0except Except= ion as e:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0dts_logger.exception(
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 f"Connection to node {execution.system_under_test} failed.&= quot;
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 f"Connection to node {execution.system_under_test_node} fai= led."
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0)
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0result.update_setup(Result.FAIL, e)
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0else:
@@ -87,7 +87,9 @@ def _run_execution(
=C2=A0 =C2=A0 =C2=A0Run the given execution. This involves running the exec= ution setup as well as
=C2=A0 =C2=A0 =C2=A0running all build targets in the given execution.
=C2=A0 =C2=A0 =C2=A0"""
-=C2=A0 =C2=A0 dts_logger.info(f"Running execution with SUT '{execution.system_under_test.name}'.")
+=C2=A0 =C2=A0 dts_logger.info(
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 f"Running execution with SUT '{execution.system_under_test_node.name}'."
+=C2=A0 =C2=A0 )
=C2=A0 =C2=A0 =C2=A0execution_result =3D result.add_execution(sut_node.conf= ig, sut_node.node_info)

=C2=A0 =C2=A0 =C2=A0try:
diff --git a/dts/framework/testbed_model/node.py b/dts/framework/testbed_mo= del/node.py
index c5147e0ee6..d2d55d904e 100644
--- a/dts/framework/testbed_model/node.py
+++ b/dts/framework/testbed_model/node.py
@@ -48,6 +48,8 @@ def __init__(self, node_config: NodeConfiguration):
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0self._logger =3D getLogger(self.name)
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0self.main_session =3D create_session(self= .config, = self.name, self._logger)

+=C2=A0 =C2=A0 =C2=A0 =C2=A0 self._logger.info(f"Connected to node: {self.name= }")
+
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0self._get_remote_cpus()
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0# filter the node lcores according to use= r config
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0self.lcores =3D LogicalCoreListFilter( @@ -56,8 +58,6 @@ def __init__(self, node_config: NodeConfiguration):

=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0self._other_sessions =3D []

-=C2=A0 =C2=A0 =C2=A0 =C2=A0 self._logger.info(f"Created node: {self.name}&q= uot;)
-
=C2=A0 =C2=A0 =C2=A0def set_up_execution(self, execution_config: ExecutionC= onfiguration) -> None:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"""
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0Perform the execution setup that will be = done for each execution
diff --git a/dts/framework/testbed_model/sut_node.py b/dts/framework/testbe= d_model/sut_node.py
index 53953718a1..bcad364435 100644
--- a/dts/framework/testbed_model/sut_node.py
+++ b/dts/framework/testbed_model/sut_node.py
@@ -13,8 +13,8 @@
=C2=A0 =C2=A0 =C2=A0BuildTargetConfiguration,
=C2=A0 =C2=A0 =C2=A0BuildTargetInfo,
=C2=A0 =C2=A0 =C2=A0InteractiveApp,
-=C2=A0 =C2=A0 NodeConfiguration,
=C2=A0 =C2=A0 =C2=A0NodeInfo,
+=C2=A0 =C2=A0 SutNodeConfiguration,
=C2=A0)
=C2=A0from framework.remote_session import (
=C2=A0 =C2=A0 =C2=A0CommandResult,
@@ -83,6 +83,7 @@ class SutNode(Node):
=C2=A0 =C2=A0 =C2=A0Another key capability is building DPDK according to gi= ven build target.
=C2=A0 =C2=A0 =C2=A0"""

+=C2=A0 =C2=A0 config: SutNodeConfiguration
=C2=A0 =C2=A0 =C2=A0_dpdk_prefix_list: list[str]
=C2=A0 =C2=A0 =C2=A0_dpdk_timestamp: str
=C2=A0 =C2=A0 =C2=A0_build_target_config: BuildTargetConfiguration | None @@ -95,7 +96,7 @@ class SutNode(Node):
=C2=A0 =C2=A0 =C2=A0_node_info: NodeInfo | None
=C2=A0 =C2=A0 =C2=A0_compiler_version: str | None

-=C2=A0 =C2=A0 def __init__(self, node_config: NodeConfiguration):
+=C2=A0 =C2=A0 def __init__(self, node_config: SutNodeConfiguration):
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0super(SutNode, self).__init__(node_config= )
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0self._dpdk_prefix_list =3D []
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0self._build_target_config =3D None
@@ -110,6 +111,7 @@ def __init__(self, node_config: NodeConfiguration):
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0self._dpdk_version =3D None
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0self._node_info =3D None
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0self._compiler_version =3D None
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 self._logger.info(f"Created node: {self.name}&q= uot;)

=C2=A0 =C2=A0 =C2=A0@property
=C2=A0 =C2=A0 =C2=A0def _remote_dpdk_dir(self) -> PurePath:
--
2.34.1

--000000000000b209790600c4f265--