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. The specified build option for ice devices is removed since the query to sut port ingress causes python to crash when there are no ports. Notably, since this is only the case in which there are no ports, traffic will not be sent and therefore the build argument is not required. For these reasons it is skipped when running a no-link topology.
Signed-off-by: Andrew Bailey <[email protected]> --- v4: * Made it possible to run no link topology even if a TG node is present in the config. * Updated the test run example config file to properly document how no link topology is run. dts/api/test.py | 11 ++- dts/configurations/test_run.example.yaml | 7 +- dts/framework/config/__init__.py | 36 ++++----- dts/framework/config/test_run.py | 4 +- dts/framework/context.py | 2 +- dts/framework/remote_session/dpdk.py | 4 +- dts/framework/runner.py | 6 +- dts/framework/test_run.py | 95 ++++++++++++++---------- dts/framework/testbed_model/topology.py | 17 ++++- 9 files changed, 109 insertions(+), 73 deletions(-) diff --git a/dts/api/test.py b/dts/api/test.py index e17babe0ca..7947c407d2 100644 --- a/dts/api/test.py +++ b/dts/api/test.py @@ -10,6 +10,7 @@ from datetime import datetime from api.artifact import Artifact +from api.capabilities import LinkTopology from framework.context import get_ctx from framework.exception import InternalError, SkippedTestException, TestCaseVerifyError from framework.logger import DTSLogger @@ -109,12 +110,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:]: - 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:]: + for command_res in ctx.sut_node.main_session.remote_session.history[-10:]: get_logger().debug(command_res.command) + if ctx.topology.type is not LinkTopology.NO_LINK and ctx.tg_node is not None: + get_logger().debug("A test case failed, showing the last 10 commands executed on TG:") + 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 ee641f5dce..127aa51152 100644 --- a/dts/configurations/test_run.example.yaml +++ b/dts/configurations/test_run.example.yaml @@ -25,6 +25,9 @@ # By removing the `test_suites` field, this test run will run every test suite available. # `vdevs`: # Uncomment to add a specific virtual device to run on the SUT node. +# `port_topology`: +# By providing an empty list, DTS will run in no link topology mode and will not allocate a TG +# node. # Define the test run environment dpdk: @@ -58,8 +61,8 @@ test_suites: # see `Optional Fields`; the following test suites will be run in t # The machine running the DPDK test executable system_under_test_node: "SUT 1" # Traffic generator node to use for this execution environment -traffic_generator_node: "TG 1" -port_topology: +traffic_generator_node: "TG 1" # see +port_topology: # see `Optional Fields` - 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..abcd9b525f 100644 --- a/dts/framework/config/__init__.py +++ b/dts/framework/config/__init__.py @@ -94,17 +94,19 @@ 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 - ) - assert ( - tg_node_port_peer is not None - ), f"Invalid TG node port specified for link port_topology.{link_idx}." - - assert tg_node_port_peer is False or sut_node_port_peer == link.left, ( - f"The TG node port for link port_topology.{link_idx} is " - f"already linked to port {tg_node_port_peer[0]}.{tg_node_port_peer[1]}." - ) + if self.test_run.port_topology != []: + assert self.test_run.traffic_generator_node is not None, "No TG node specified." + 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}." + + assert tg_node_port_peer is False or sut_node_port_peer == link.left, ( + f"The TG node port for link port_topology.{link_idx} is " + f"already linked to port {tg_node_port_peer[0]}.{tg_node_port_peer[1]}." + ) existing_port_links[link.left] = link.right existing_port_links[link.right] = link.left @@ -121,13 +123,13 @@ def validate_test_run_against_nodes(self) -> Self: sut_node is not None ), 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) - - assert ( - tg_node is not None - ), f"The traffic_generator_name {tg_node_name} is not a valid node name." + if self.test_run.port_topology != []: + 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) + 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/test_run.py b/dts/framework/config/test_run.py index 76e24d1785..f9143dfc4e 100644 --- a/dts/framework/config/test_run.py +++ b/dts/framework/config/test_run.py @@ -492,11 +492,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..e43e1f2123 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,8 @@ 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 ctx.topology.sut_port_ingress and ctx.topology.sut_port_ingress.config.os_driver == "ice" ): meson_args = MesonArgs( diff --git a/dts/framework/runner.py b/dts/framework/runner.py index 6ea4749ff4..fa4f06844e 100644 --- a/dts/framework/runner.py +++ b/dts/framework/runner.py @@ -61,7 +61,11 @@ def run(self) -> None: self._check_dts_python_version() for node_config in self._configuration.nodes: - nodes.append(Node(node_config)) + if self._configuration.test_run.port_topology == []: + if node_config.name == self._configuration.test_run.system_under_test_node: + nodes.append(Node(node_config)) + else: + nodes.append(Node(node_config)) test_run = TestRun( self._configuration.test_run, diff --git a/dts/framework/test_run.py b/dts/framework/test_run.py index 94dc6023a7..9b973532da 100644 --- a/dts/framework/test_run.py +++ b/dts/framework/test_run.py @@ -106,6 +106,7 @@ from types import MethodType from typing import ClassVar, Protocol, Union +from api.capabilities import LinkTopology from framework.config.test_run import TestRunConfiguration from framework.context import Context, init_ctx from framework.exception import InternalError, SkippedTestException, TestCaseVerifyError @@ -190,24 +191,33 @@ def __init__( self.logger = get_dts_logger() 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.port_topology != []: + 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: + tg_node = None + 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 topology.type is not LinkTopology.NO_LINK + and tg_node is not None 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 topology.type is not LinkTopology.NO_LINK + and tg_node is not None else None ) @@ -336,39 +346,40 @@ def description(self) -> str: def next(self) -> State | None: """Process state and return the next one.""" test_run = self.test_run - init_ctx(test_run.ctx) + ctx = test_run.ctx + init_ctx(ctx) - self.logger.info(f"Running on SUT node '{test_run.ctx.sut_node.name}'.") + self.logger.info(f"Running on SUT node '{ctx.sut_node.name}'.") test_run.init_random_seed() test_run.remaining_tests = deque(test_run.selected_tests) - test_run.ctx.sut_node.setup() - test_run.ctx.tg_node.setup() - test_run.ctx.dpdk.setup() - test_run.ctx.topology.setup() + ctx.sut_node.setup() + if ctx.topology.type is not LinkTopology.NO_LINK and ctx.tg_node is not None: + ctx.tg_node.setup() + ctx.dpdk.setup() + ctx.topology.setup() if test_run.config.use_virtual_functions: - test_run.ctx.topology.instantiate_vf_ports() - if test_run.ctx.sut_node.cryptodevs and test_run.config.crypto: - test_run.ctx.topology.instantiate_crypto_ports() - test_run.ctx.topology.bind_cryptodevs("dpdk") + ctx.topology.instantiate_vf_ports() + if ctx.sut_node.cryptodevs and test_run.config.crypto: + ctx.topology.instantiate_crypto_ports() + ctx.topology.bind_cryptodevs("dpdk") - test_run.ctx.topology.configure_ports("sut", "dpdk") - if test_run.ctx.func_tg: - test_run.ctx.func_tg.setup(test_run.ctx.topology) - if test_run.ctx.perf_tg: - test_run.ctx.perf_tg.setup(test_run.ctx.topology) + ctx.topology.configure_ports("sut", "dpdk") + if ctx.func_tg and ctx.topology.type is not LinkTopology.NO_LINK: + ctx.func_tg.setup(ctx.topology) + if ctx.perf_tg and ctx.topology.type is not LinkTopology.NO_LINK: + ctx.perf_tg.setup(ctx.topology) self.result.ports = [ - port.to_dict() - for port in test_run.ctx.topology.sut_ports + test_run.ctx.topology.tg_ports + port.to_dict() for port in ctx.topology.sut_ports + ctx.topology.tg_ports ] - self.result.sut_session_info = test_run.ctx.sut_node.node_info - self.result.dpdk_build_info = test_run.ctx.dpdk_build.get_dpdk_build_info() + self.result.sut_session_info = ctx.sut_node.node_info + self.result.dpdk_build_info = ctx.dpdk_build.get_dpdk_build_info() self.logger.debug(f"Found capabilities to check: {test_run.required_capabilities}") test_run.supported_capabilities = get_supported_capabilities( - test_run.ctx.sut_node, test_run.ctx.topology, test_run.required_capabilities + ctx.sut_node, ctx.topology, test_run.required_capabilities ) return TestRunExecution(test_run, self.result) @@ -443,20 +454,22 @@ def description(self) -> str: def next(self) -> State | None: """Next state.""" + ctx = self.test_run.ctx if self.test_run.config.use_virtual_functions: - self.test_run.ctx.topology.delete_vf_ports() - if self.test_run.ctx.sut_node.cryptodevs: - self.test_run.ctx.topology.delete_crypto_vf_ports() - - self.test_run.ctx.shell_pool.terminate_current_pool() - if self.test_run.ctx.func_tg and self.test_run.ctx.func_tg.is_setup: - self.test_run.ctx.func_tg.teardown() - if self.test_run.ctx.perf_tg and self.test_run.ctx.perf_tg.is_setup: - 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() - self.test_run.ctx.sut_node.teardown() + ctx.topology.delete_vf_ports() + if ctx.sut_node.cryptodevs: + ctx.topology.delete_crypto_vf_ports() + + ctx.shell_pool.terminate_current_pool() + if ctx.func_tg is not None and ctx.func_tg.is_setup: + ctx.func_tg.teardown() + if ctx.perf_tg is not None and ctx.perf_tg.is_setup: + ctx.perf_tg.teardown() + ctx.topology.teardown() + ctx.dpdk.teardown() + if ctx.topology.type is not LinkTopology.NO_LINK and ctx.tg_node is not None: + ctx.tg_node.teardown() + ctx.sut_node.teardown() return None def on_error(self, ex: BaseException) -> State | None: diff --git a/dts/framework/testbed_model/topology.py b/dts/framework/testbed_model/topology.py index 34862c4d2e..1db444fc01 100644 --- a/dts/framework/testbed_model/topology.py +++ b/dts/framework/testbed_model/topology.py @@ -73,6 +73,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 @@ -103,10 +105,12 @@ 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." + if ctx.tg_node is not None: + return ctx.tg_node, self.tg_ports + msg = "node tg does not exist with current topology." raise InternalError(msg) + msg = f"Invalid node `{node_identifier}` given." + raise InternalError(msg) def get_crypto_vfs(self, num_vfs: int) -> list[Port]: """Retrieve virtual functions from active crypto vfs. @@ -139,6 +143,8 @@ def setup(self) -> None: 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") @@ -148,6 +154,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") @@ -338,7 +346,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 ctx.tg_node: + prepare_node(ctx.tg_node) prepare_node(ctx.sut_node) @property -- 2.54.0

