[PATCH v1] dts: add support for no link topology
Andrew Bailey
abailey at iol.unh.edu
Fri Feb 20 20:25:10 CET 2026
Add support for running DTS with no traffic generator node and no ethdev
interfaces. Some applications, like dpdk-test-crypto-perf run without
ethdev interfaces and no traffic generator. In these cases, it is
beneficial to remove the overhead of creating a node and ports that are
not used.
Bugzilla ID: 1870
Signed-off-by: Andrew Bailey <abailey at iol.unh.edu>
---
dts/api/test.py | 8 +++++---
dts/configurations/test_run.example.yaml | 4 ++--
dts/framework/config/__init__.py | 16 ++++++++-------
dts/framework/config/node.py | 2 +-
dts/framework/config/test_run.py | 4 ++--
dts/framework/context.py | 2 +-
dts/framework/remote_session/dpdk.py | 3 ++-
dts/framework/test_run.py | 25 ++++++++++++++----------
dts/framework/testbed_model/topology.py | 17 +++++++++++-----
9 files changed, 49 insertions(+), 32 deletions(-)
diff --git a/dts/api/test.py b/dts/api/test.py
index e17babe0ca..046f1778a5 100644
--- a/dts/api/test.py
+++ b/dts/api/test.py
@@ -109,12 +109,14 @@ def fail(failure_description: str) -> None:
Raises:
TestCaseVerifyError: Always raised to indicate the test case failed.
"""
+ ctx = get_ctx()
get_logger().debug("A test case failed, showing the last 10 commands executed on SUT:")
- for command_res in get_ctx().sut_node.main_session.remote_session.history[-10:]:
+ for command_res in ctx.sut_node.main_session.remote_session.history[-10:]:
get_logger().debug(command_res.command)
get_logger().debug("A test case failed, showing the last 10 commands executed on TG:")
- for command_res in get_ctx().tg_node.main_session.remote_session.history[-10:]:
- get_logger().debug(command_res.command)
+ if ctx.tg_node:
+ for command_res in ctx.tg_node.main_session.remote_session.history[-10:]:
+ get_logger().debug(command_res.command)
raise TestCaseVerifyError(failure_description)
diff --git a/dts/configurations/test_run.example.yaml b/dts/configurations/test_run.example.yaml
index c8035fccf0..f6ca09545e 100644
--- a/dts/configurations/test_run.example.yaml
+++ b/dts/configurations/test_run.example.yaml
@@ -23,7 +23,7 @@ dpdk:
# in a subdirectory of DPDK tree root directory. Otherwise, will be using the `build_options`
# to build the DPDK from source. Either `precompiled_build_dir` or `build_options` can be
# defined, but not both.
-func_traffic_generator:
+func_traffic_generator: # Remove both traffic generators when running a no-link topology
type: SCAPY
# perf_traffic_generator:
# type: TREX
@@ -42,7 +42,7 @@ vdevs: # optional; if removed, vdevs won't be used in the execution
system_under_test_node: "SUT 1"
# Traffic generator node to use for this execution environment
traffic_generator_node: "TG 1"
-port_topology:
+port_topology: # provide empty list for no-link environment
- sut.port-0 <-> tg.port-0 # explicit link. `sut` and `tg` are special identifiers that refer
# to the respective test run's configured nodes.
- port-1 <-> port-1 # implicit link, left side is always SUT, right side is always TG.
diff --git a/dts/framework/config/__init__.py b/dts/framework/config/__init__.py
index d2f0138e4a..59d566aa0b 100644
--- a/dts/framework/config/__init__.py
+++ b/dts/framework/config/__init__.py
@@ -94,9 +94,10 @@ def validate_port_links(self) -> Self:
f"already linked to port {sut_node_port_peer[0]}.{sut_node_port_peer[1]}."
)
- tg_node_port_peer = existing_port_links.get(
- (self.test_run.traffic_generator_node, link.tg_port), None
- )
+ if self.test_run.traffic_generator_node:
+ tg_node_port_peer = existing_port_links.get(
+ (self.test_run.traffic_generator_node, link.tg_port), None
+ )
assert (
tg_node_port_peer is not None
), f"Invalid TG node port specified for link port_topology.{link_idx}."
@@ -122,11 +123,12 @@ def validate_test_run_against_nodes(self) -> Self:
), f"The system_under_test_node {sut_node_name} is not a valid node name."
tg_node_name = self.test_run.traffic_generator_node
- tg_node = next((n for n in self.nodes if n.name == tg_node_name), None)
+ if self.test_run.port_topology:
+ tg_node = next((n for n in self.nodes if n.name == tg_node_name), None)
- assert (
- tg_node is not None
- ), f"The traffic_generator_name {tg_node_name} is not a valid node name."
+ assert (
+ tg_node is not None
+ ), f"The traffic_generator_name {tg_node_name} is not a valid node name."
return self
diff --git a/dts/framework/config/node.py b/dts/framework/config/node.py
index 438a1bdc8f..d91c0835e4 100644
--- a/dts/framework/config/node.py
+++ b/dts/framework/config/node.py
@@ -69,7 +69,7 @@ class NodeConfiguration(FrozenModel):
#: An optional hugepage configuration.
hugepages: HugepageConfiguration | None = Field(None, alias="hugepages_2mb")
#: The ports that can be used in testing.
- ports: list[PortConfig] = Field(min_length=1)
+ ports: list[PortConfig] = Field(default=[], min_length=0)
@model_validator(mode="after")
def verify_unique_port_names(self) -> Self:
diff --git a/dts/framework/config/test_run.py b/dts/framework/config/test_run.py
index 6c292a3675..07440c5250 100644
--- a/dts/framework/config/test_run.py
+++ b/dts/framework/config/test_run.py
@@ -490,11 +490,11 @@ class TestRunConfiguration(FrozenModel):
#: The SUT node name to use in this test run.
system_under_test_node: str
#: The TG node name to use in this test run.
- traffic_generator_node: str
+ traffic_generator_node: str | None = Field(default=None)
#: The seed to use for pseudo-random generation.
random_seed: int | None = None
#: The port links between the specified nodes to use.
- port_topology: list[PortLinkConfig] = Field(max_length=2)
+ port_topology: list[PortLinkConfig] = Field(default=[], max_length=2)
fields_from_settings = model_validator(mode="before")(
load_fields_from_settings("test_suites", "random_seed")
diff --git a/dts/framework/context.py b/dts/framework/context.py
index 8f1021dc96..371473f61c 100644
--- a/dts/framework/context.py
+++ b/dts/framework/context.py
@@ -72,7 +72,7 @@ class Context:
"""Runtime context."""
sut_node: Node
- tg_node: Node
+ tg_node: Node | None
topology: Topology
dpdk_build: "DPDKBuildEnvironment"
dpdk: "DPDKRuntimeEnvironment"
diff --git a/dts/framework/remote_session/dpdk.py b/dts/framework/remote_session/dpdk.py
index c3575cfcaf..d63114b0e2 100644
--- a/dts/framework/remote_session/dpdk.py
+++ b/dts/framework/remote_session/dpdk.py
@@ -13,6 +13,7 @@
from pathlib import Path, PurePath
from typing import ClassVar, Final
+from api.capabilities import LinkTopology
from framework.config.test_run import (
DPDKBuildConfiguration,
DPDKBuildOptionsConfiguration,
@@ -263,7 +264,7 @@ def _build_dpdk(self) -> None:
ctx = get_ctx()
# If the SUT is an ice driver device, make sure to build with 16B descriptors.
if (
- ctx.topology.sut_port_ingress
+ ctx.topology.type is not LinkTopology.NO_LINK and not ctx.topology.sut_port_ingress
and ctx.topology.sut_port_ingress.config.os_driver == "ice"
):
meson_args = MesonArgs(
diff --git a/dts/framework/test_run.py b/dts/framework/test_run.py
index ff0a12c9ce..536d0f33fb 100644
--- a/dts/framework/test_run.py
+++ b/dts/framework/test_run.py
@@ -189,25 +189,28 @@ def __init__(
self.config = config
self.logger = get_dts_logger()
+ tg_node = None
sut_node = next(n for n in nodes if n.name == config.system_under_test_node)
- tg_node = next(n for n in nodes if n.name == config.traffic_generator_node)
-
- topology = Topology.from_port_links(
- PortLink(sut_node.ports_by_name[link.sut_port], tg_node.ports_by_name[link.tg_port])
- for link in self.config.port_topology
- )
+ if config.traffic_generator_node:
+ tg_node = next(n for n in nodes if n.name == config.traffic_generator_node)
+ topology = Topology.from_port_links(
+ PortLink(sut_node.ports_by_name[link.sut_port], tg_node.ports_by_name[link.tg_port])
+ for link in self.config.port_topology
+ )
+ else:
+ topology = Topology.from_port_links(iter([]))
dpdk_build_env = DPDKBuildEnvironment(config.dpdk.build, sut_node)
dpdk_runtime_env = DPDKRuntimeEnvironment(config.dpdk, sut_node, dpdk_build_env)
func_traffic_generator = (
create_traffic_generator(config.func_traffic_generator, tg_node)
- if config.func and config.func_traffic_generator
+ if config.func and config.func_traffic_generator and tg_node
else None
)
perf_traffic_generator = (
create_traffic_generator(config.perf_traffic_generator, tg_node)
- if config.perf and config.perf_traffic_generator
+ if config.perf and config.perf_traffic_generator and tg_node
else None
)
@@ -343,7 +346,8 @@ def next(self) -> State | None:
test_run.remaining_tests = deque(test_run.selected_tests)
test_run.ctx.sut_node.setup()
- test_run.ctx.tg_node.setup()
+ if test_run.ctx.tg_node:
+ test_run.ctx.tg_node.setup()
test_run.ctx.dpdk.setup()
test_run.ctx.topology.setup()
@@ -450,7 +454,8 @@ def next(self) -> State | None:
self.test_run.ctx.perf_tg.teardown()
self.test_run.ctx.topology.teardown()
self.test_run.ctx.dpdk.teardown()
- self.test_run.ctx.tg_node.teardown()
+ if self.test_run.ctx.tg_node:
+ self.test_run.ctx.tg_node.teardown()
self.test_run.ctx.sut_node.teardown()
return None
diff --git a/dts/framework/testbed_model/topology.py b/dts/framework/testbed_model/topology.py
index 13b4b58a74..186ef6bad6 100644
--- a/dts/framework/testbed_model/topology.py
+++ b/dts/framework/testbed_model/topology.py
@@ -70,6 +70,8 @@ def from_port_links(cls, port_links: Iterator[PortLink]) -> Self:
ConfigurationError: If an unsupported link topology is supplied.
"""
type = LinkTopology.NO_LINK
+ sut_ports = []
+ tg_ports = []
if port_link := next(port_links, None):
type = LinkTopology.ONE_LINK
@@ -100,16 +102,18 @@ def node_and_ports_from_id(self, node_identifier: NodeIdentifier) -> tuple[Node,
case "sut":
return ctx.sut_node, self.sut_ports
case "tg":
- return ctx.tg_node, self.tg_ports
- case _:
- msg = f"Invalid node `{node_identifier}` given."
- raise InternalError(msg)
+ if self.type is not LinkTopology.NO_LINK:
+ return ctx.tg_node, self.tg_ports
+ msg = f"Invalid node `{node_identifier}` given."
+ raise InternalError(msg)
def setup(self) -> None:
"""Setup topology ports.
Binds all the ports to the right kernel driver to retrieve MAC addresses and logical names.
"""
+ if self.type is LinkTopology.NO_LINK:
+ return
self._prepare_devbind_script()
self._setup_ports("sut")
self._setup_ports("tg")
@@ -119,6 +123,8 @@ def teardown(self) -> None:
Restores all the ports to their original drivers before the test run.
"""
+ if self.type is LinkTopology.NO_LINK:
+ return
self._restore_ports_original_drivers("sut")
self._restore_ports_original_drivers("tg")
@@ -264,7 +270,8 @@ def prepare_node(node: Node) -> None:
node.main_session.devbind_script_path = devbind_script_path
ctx = get_ctx()
- prepare_node(ctx.tg_node)
+ if self.type is not LinkTopology.NO_LINK:
+ prepare_node(ctx.tg_node)
prepare_node(ctx.sut_node)
@property
--
2.50.1
More information about the dev
mailing list