[PATCH v2] dts: add l2fwd test suite
Add a basic L2 forwarding test suite which tests the correct functionality of the forwarding facility built-in in the DPDK. The tests are performed with different queues numbers per port. Bugzilla ID: 1481 Signed-off-by: Luca Vizzarro Signed-off-by: Thomas Wilks Reviewed-by: Paul Szczepanek --- dts/tests/TestSuite_l2fwd.py | 62 1 file changed, 62 insertions(+) create mode 100644 dts/tests/TestSuite_l2fwd.py diff --git a/dts/tests/TestSuite_l2fwd.py b/dts/tests/TestSuite_l2fwd.py new file mode 100644 index 00..07467946e7 --- /dev/null +++ b/dts/tests/TestSuite_l2fwd.py @@ -0,0 +1,62 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright(c) 2024 Arm Limited + +"""Basic L2 forwarding test suite. + +This testing suites runs basic L2 forwarding on testpmd with different queue sizes per port. +The forwarding test is performed with several packets being sent at once. +""" + +from framework.params.testpmd import EthPeer, SimpleForwardingModes +from framework.remote_session.testpmd_shell import TestPmdShell +from framework.test_suite import TestSuite, func_test +from framework.testbed_model.cpu import LogicalCoreCount +from framework.utils import generate_random_packets + + +class TestL2fwd(TestSuite): +"""L2 forwarding test suite.""" + +#: The total number of packets to generate and send for forwarding. +NUMBER_OF_PACKETS_TO_SEND = 50 +#: The payload size to use for the generated packets in bytes. +PAYLOAD_SIZE = 100 + +def set_up_suite(self) -> None: +"""Set up the test suite. + +Setup: +Verify that we have at least 2 ports in the current test. Generate the random packets +that will be sent and spawn a reusable testpmd shell. +""" +self.verify(len(self.sut_node.ports) >= 2, "At least 2 ports are required for this test.") +self.packets = generate_random_packets(self.NUMBER_OF_PACKETS_TO_SEND, self.PAYLOAD_SIZE) + +@func_test +def test_l2fwd_integrity(self) -> None: +"""Test the L2 forwarding integrity. + +Test: +Configure a testpmd shell with a different numbers of queues per run. Start up L2 +forwarding, send random packets from the TG and verify they were all received back. +""" +queues = [1, 2, 4, 8] + +with TestPmdShell( +self.sut_node, +lcore_filter_specifier=LogicalCoreCount(cores_per_socket=4), +forward_mode=SimpleForwardingModes.mac, +eth_peer=[EthPeer(1, self.tg_node.ports[1].mac_address)], +disable_device_start=True, +) as shell: +for queues_num in queues: +self._logger.info(f"Testing L2 forwarding with {queues_num} queue(s)") +shell.set_ports_queues(queues_num) +shell.start() + +received_packets = self.send_packets_and_capture(self.packets) + +expected_packets = [self.get_expected_packet(packet) for packet in self.packets] +self.match_all_packets(expected_packets, received_packets) + +shell.stop() -- 2.43.0
[PATCH v1 1/2] dts: add new testpmd shell functions
Add support for setting Mac address, set flow control, VF mode in the testpmd shell. Signed-off-by: Thomas Wilks Reviewed-by: Paul Szczepanek --- dts/framework/remote_session/testpmd_shell.py | 120 ++ 1 file changed, 120 insertions(+) diff --git a/dts/framework/remote_session/testpmd_shell.py b/dts/framework/remote_session/testpmd_shell.py index d187eaea94..14a7fae281 100644 --- a/dts/framework/remote_session/testpmd_shell.py +++ b/dts/framework/remote_session/testpmd_shell.py @@ -1932,6 +1932,78 @@ def set_vlan_filter(self, port: int, enable: bool, verify: bool = True) -> None: filter on port {port}""" ) +def set_mac_address(self, port: int, mac_address: str): +"""Sets Mac Address + +Args: +port (int): Port Id +mac_address (str): MAC Address to be set +""" +self.send_command(f"mac_addr set {port} {mac_address}") + +def set_flow_control( +self, +port: int, +rx: bool = True, +tx: bool = True, +high_water: int = 0, +low_water: int = 0, +pause_time: int = 0, +send_xon: bool = False, +mac_ctrl_frame_fwd: bool = False, +autoneg: bool = False, +): +"""Set Flow Control. + +Args: +rx (bool): Enable Reactive Extensions +tx (bool): Enable Transmit +high_water (int): High threshold value to trigger XOFF, +low_water (int): Low threshold value to trigger XON. +pause_time (int): Pause quota in the Pause frame. +send_xon (bool): Send XON frame. +mac_ctrl_frame_fwd (bool): Enable receiving MAC control frames. +autoneg (bool): _description_ +port (int): Change the auto-negotiation parameter. +""" + +self.send_command( +f"set flow_ctrl rx {'on' if rx else 'off'} tx {'on' if tx else 'off'} {high_water} {low_water} {pause_time} {1 if send_xon else 0} mac_ctrl_frame_fwd {'on' if mac_ctrl_frame_fwd else 'off'} autoneg {'on' if autoneg else 'off'} {port}" +) + +def show_port_flow_info(self, port: int): +"""Show port info flow + +Args: +port (int): port id + +Returns: +str: port flow control config +""" +output = self.send_command(f"show port {port} flow_ctrl") +output = TestPmdPortFlowCtrl.parse(output) +return output + +def set_port_VF_mode(self, port: int, vf_id: int, rxmode: str, enable: bool): +"""Set VF receive mode of a port + +Args: +port (int): Port id +vf_id (int): Virtual Function id +rxmode (str): AUPE Accepts untagged VLAN. + ROPE Accepts unicast hash. + BAM Accepts broadcast packets. + MPE Accepts all multicast packets. +enable (bool): Enables vf mode +""" +rxmode_valid = ["AUPE", "ROPE", "BAM", "MPE"] +if rxmode in rxmode_valid: +self.send_command( +f"set port {port} vf {vf_id} rxmode {rxmode} {'on' if enable else 'off'}" +) +else: +raise InteractiveCommandExecutionError(f"{rxmode} is an invlaid rxmode") + def rx_vlan(self, vlan: int, port: int, add: bool, verify: bool = True) -> None: """Add specified vlan tag to the filter list on a port. Requires vlan filter to be on. @@ -2315,6 +2387,50 @@ def get_capabilities_mcast_filtering( command = str.replace(command, "add", "remove", 1) self.send_command(command) +def get_capabilities_flow_ctrl( +self, +supported_capabilities: MutableSet["NicCapability"], +unsupported_capabilities: MutableSet["NicCapability"], +) -> None: +"""Get Flow control capability from `show port flow_ctrl` and check for a testpmd error code + +Args: +supported_capabilities: Supported capabilities will be added to this set. +unsupported_capabilities: Unsupported capabilities will be added to this set. +""" +self._logger.debug("Getting flow ctrl capabilities.") +command = f"show port {self.ports[0].id} flow_ctrl" +output = self.send_command(command) +if "Flow control infos" in output: +supported_capabilities.add(NicCapability.FLOW_CTRL) +else: +unsupported_capabilities.add(NicCapabilit
[PATCH v1 2/2] dts: add port restart configuration persistency test
Added test that sets various port settings and verifies that they persist after a port restart. Signed-off-by: Thomas Wilks Reviewed-by: Paul Szczepanek --- ...stSuite_port_restart_config_persistency.py | 117 ++ 1 file changed, 117 insertions(+) create mode 100644 dts/tests/TestSuite_port_restart_config_persistency.py diff --git a/dts/tests/TestSuite_port_restart_config_persistency.py b/dts/tests/TestSuite_port_restart_config_persistency.py new file mode 100644 index 00..969b96d422 --- /dev/null +++ b/dts/tests/TestSuite_port_restart_config_persistency.py @@ -0,0 +1,117 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright(c) 2024 Arm Limited + +"""Port config persistency Test suite. + +Changes configuration of ports and verifies that the configuration persists after a +port is restarted. +""" + +from framework.remote_session.testpmd_shell import TestPmdShell +from framework.test_suite import TestSuite, func_test +from framework.testbed_model.capability import NicCapability, requires + +ALTERNATIVE_MTU: int = 800 +STANDARD_MTU: int = 1500 +ALTERNATITVE_MAC_ADDRESS: str = "40:A6:B7:9E:B4:81" + + +class TestPortRestartConfigPersistency(TestSuite): +"""Port config persistency Test suite.""" + +def restart_port_and_verify(self, id, testpmd, changed_value) -> None: +"""Fetches all of the port configs, restarts all of the ports, fetches all the port +configs again and then compares the two the configs and varifies that they are the same. +""" + +testpmd.start_all_ports() +testpmd.wait_link_status_up(port_id=id, timeout=10) + +port_info_before = testpmd.show_port_info(id) +all_info_before = port_info_before.__dict__ +try: +flow_info_before = testpmd.show_port_flow_info(id) +all_info_before.update(flow_info_before.__dict__) +except: +pass + +testpmd.stop_all_ports() +testpmd.start_all_ports() +testpmd.wait_link_status_up(port_id=id, timeout=10) + +port_info_after = testpmd.show_port_info(id) +all_info_after = port_info_after.__dict__ +try: +flow_info_after = testpmd.show_port_flow_info(id) +all_info_after.update(flow_info_after.__dict__) +except: +pass + +self.verify( +all_info_before == all_info_after, +f"Port configuration for {changed_value} was not retained through port restart", +) +testpmd.stop_all_ports() + +@func_test +def port_configuration_persistence(self) -> None: +"""Port restart configuration Persistency Test. + +Steps: +For each port set the port MTU, VLAN Filter, Mac Address, VF mode, and Promiscuous Mode. + +Verify: +Check that the configuration persists after the port is restarted. +""" + +with TestPmdShell(self.sut_node) as testpmd: +testpmd.stop_all_ports() +all_ports = [port.id for port in testpmd.show_port_info_all()] +for port_id in all_ports: +testpmd.set_port_mtu(port_id=port_id, mtu=STANDARD_MTU, verify=True) + +self.restart_port_and_verify(port_id, testpmd, "mtu") + +testpmd.set_port_mtu(port_id=port_id, mtu=ALTERNATIVE_MTU, verify=True) + +self.restart_port_and_verify(port_id, testpmd, "mtu") + +testpmd.set_vlan_filter(port=port_id, enable=True, verify=True) + +self.restart_port_and_verify(port_id, testpmd, "VLAN_filter") + +testpmd.set_mac_address(port=port_id, mac_address=ALTERNATITVE_MAC_ADDRESS) + +self.restart_port_and_verify(port_id, testpmd, "mac_address") + +testpmd.set_port_VF_mode(port=port_id, vf_id=port_id, rxmode="AUPE", enable=True) + +self.restart_port_and_verify(port_id, testpmd, "VF_mode") + +testpmd.set_promisc(port=port_id, enable=True, verify=False) + +self.restart_port_and_verify(port_id, testpmd, "Promiscuous_Mode") + +@requires(NicCapability.FLOW_CTRL) +@func_test +def flow_ctrl_port_configuration_persistence(self) -> None: +"""Flow Control port restart configuration Persistency Test. + +Steps: +For each port enable flow control for RX and TX individualy. +Verify: +Check that the configuration persists after the port is restarted. +""" + +with TestPmdShell(self.sut_node) as testpmd: +testpmd.stop_all_ports() +all_ports = [port.id for port in testpmd.show_port_info_all()] +for port
[PATCH v2 1/2] dts: add new testpmd shell functions
Add support for setting Mac address, set flow control in the testpmd shell. Signed-off-by: Thomas Wilks Reviewed-by: Paul Szczepanek --- dts/framework/remote_session/testpmd_shell.py | 126 ++ 1 file changed, 126 insertions(+) diff --git a/dts/framework/remote_session/testpmd_shell.py b/dts/framework/remote_session/testpmd_shell.py index d187eaea94..689603ceef 100644 --- a/dts/framework/remote_session/testpmd_shell.py +++ b/dts/framework/remote_session/testpmd_shell.py @@ -1348,6 +1348,49 @@ class RxOffloadCapabilities(TextParser): per_port: RxOffloadCapability = field(metadata=RxOffloadCapability.make_parser(True)) +@dataclass +class TestPmdPortFlowCtrl(TextParser): +"""Class representing a port's flow control parameters. + +The parameters can also be parsed from the output of ``show port flow_ctrl``. +""" + +#: Enable Reactive Extensions. +rx: bool = field(default=False, metadata=TextParser.find(r"Rx pause: on")) +#: Enable Transmit. +tx: bool = field(default=False, metadata=TextParser.find(r"Tx pause: on")) +#: High threshold value to trigger XOFF. +high_water: int = field( +default=0, metadata=TextParser.find_int(r"High waterline: (0x[a-fA-F\d]+)") +) +#: Low threshold value to trigger XON. +low_water: int = field( +default=0, metadata=TextParser.find_int(r"Low waterline: (0x[a-fA-F\d]+)") +) +#: Pause quota in the Pause frame. +pause_time: int = field(default=0, metadata=TextParser.find_int(r"Pause time: (0x[a-fA-F\d]+)")) +#: Send XON frame. +send_xon: bool = field(default=False, metadata=TextParser.find(r"Tx pause: on")) +#: Enable receiving MAC control frames. +mac_ctrl_frame_fwd: bool = field(default=False, metadata=TextParser.find(r"Tx pause: on")) +#: Change the auto-negotiation parameter. +autoneg: bool = field(default=False, metadata=TextParser.find(r"Autoneg: on")) + +def __str__(self) -> str: +"""Returns the string representation of this instance.""" +ret = ( +f"rx {'on' if self.rx else 'off'} " +f"tx {'on' if self.tx else 'off'} " +f"{self.high_water} " +f"{self.low_water} " +f"{self.pause_time} " +f"{1 if self.send_xon else 0} " +f"mac_ctrl_frame_fwd {'on' if self.mac_ctrl_frame_fwd else 'off'} " +f"autoneg {'on' if self.autoneg else 'off'}" +) +return ret + + def requires_stopped_ports(func: TestPmdShellMethod) -> TestPmdShellMethod: """Decorator for :class:`TestPmdShell` commands methods that require stopped ports. @@ -1932,6 +1975,66 @@ def set_vlan_filter(self, port: int, enable: bool, verify: bool = True) -> None: filter on port {port}""" ) +def set_mac_address(self, port: int, mac_address: str, verify: bool = True) -> None: +"""Set port's MAC address. + +Args: +port: The number of the requested port. +mac_address: The MAC address to set. +verify: If :data:`True`, the output of the command is scanned to verify that +the mac address is set in the specified port. + +Raises: +InteractiveCommandExecutionError: If `verify` is :data:`True` and the command +fails to execute. +""" +output = self.send_command(f"mac_addr set {port} {mac_address}", skip_first_line=True) +if verify: +if output.strip(): +self._logger.debug( +f"Testpmd failed to set MAC address {mac_address} on port {port}." +) +raise InteractiveCommandExecutionError( +f"Testpmd failed to set MAC address {mac_address} on port {port}." +) + +def set_flow_control( +self, port: int, flow_ctrl: TestPmdPortFlowCtrl, verify: bool = True +) -> None: +"""Set the given `port`'s flow control. + +Args: +port: The number of the requested port. +flow_ctrl: The requested flow control parameters. +verify: If :data:`True`, the output of the command is scanned to verify that +the flow control in the specified port is set. + +Raises: +InteractiveCommandExecutionError: If `verify` is :data:`True` and the command +fails to execute. +""" +output = self.send_command(f"set flow_ctrl {flow_ctrl} {port
[PATCH v2 0/2] dts: Add port restart configuration persistency testsuite
v2: - Removed Vf check - Fixed spelling - Fixed Docstrings - Added verification to testpmd commands - Extracted flow control parameters into a dataclass - Fixed syntax in testsuite Thomas Wilks (2): dts: add new testpmd shell functions dts: add port restart configuration persistency test dts/framework/remote_session/testpmd_shell.py | 126 ++ ...stSuite_port_restart_config_persistency.py | 101 ++ 2 files changed, 227 insertions(+) create mode 100644 dts/tests/TestSuite_port_restart_config_persistency.py -- 2.43.0
[PATCH v2 2/2] dts: add port restart configuration persistency test
Added test that sets various port settings and verifies that they persist after a port restart. Signed-off-by: Thomas Wilks Reviewed-by: Paul Szczepanek --- ...stSuite_port_restart_config_persistency.py | 101 ++ 1 file changed, 101 insertions(+) create mode 100644 dts/tests/TestSuite_port_restart_config_persistency.py diff --git a/dts/tests/TestSuite_port_restart_config_persistency.py b/dts/tests/TestSuite_port_restart_config_persistency.py new file mode 100644 index 00..ad42c6c2e6 --- /dev/null +++ b/dts/tests/TestSuite_port_restart_config_persistency.py @@ -0,0 +1,101 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright(c) 2024 Arm Limited + +"""Port config persistency test suite. + +Changes configuration of ports and verifies that the configuration persists after a +port is restarted. +""" + +from dataclasses import asdict + +from framework.remote_session.testpmd_shell import TestPmdPortFlowCtrl, TestPmdShell +from framework.test_suite import TestSuite, func_test +from framework.testbed_model.capability import NicCapability, requires + +ALTERNATIVE_MTU: int = 800 +STANDARD_MTU: int = 1500 +ALTERNATIVE_MAC_ADDRESS: str = "42:A6:B7:9E:B4:81" + + +class TestPortRestartConfigPersistency(TestSuite): +"""Port config persistency test suite.""" + +def restart_port_and_verify(self, id, testpmd, changed_value) -> None: +"""Fetch port config, restart and verify persistency.""" +testpmd.start_all_ports() +testpmd.wait_link_status_up(port_id=id, timeout=10) + +port_info_before = testpmd.show_port_info(id) +all_info_before = asdict(port_info_before) + +flow_info_before = testpmd.show_port_flow_info(id) + +if flow_info_before: +all_info_before.update(asdict(flow_info_before)) + +testpmd.stop_all_ports() +testpmd.start_all_ports() +testpmd.wait_link_status_up(port_id=id, timeout=10) + +port_info_after = testpmd.show_port_info(id) +all_info_after = asdict(port_info_after) + +flow_info_after = testpmd.show_port_flow_info(id) +if flow_info_after: +all_info_after.update(asdict(flow_info_after)) + +self.verify( +all_info_before == all_info_after, +f"Port configuration for {changed_value} was not retained through port restart.", +) +testpmd.stop_all_ports() + +@func_test +def port_configuration_persistence(self) -> None: +"""Port restart configuration persistency test. + +Steps: +For each port set the port MTU, VLAN filter, mac address, and promiscuous mode. + +Verify: +The configuration persists after the port is restarted. +""" +with TestPmdShell(self.sut_node, disable_device_start=True) as testpmd: +for port_id in range(len(self.sut_node.ports)): +testpmd.set_port_mtu(port_id=port_id, mtu=STANDARD_MTU, verify=True) +self.restart_port_and_verify(port_id, testpmd, "MTU") + +testpmd.set_port_mtu(port_id=port_id, mtu=ALTERNATIVE_MTU, verify=True) +self.restart_port_and_verify(port_id, testpmd, "MTU") + +testpmd.set_vlan_filter(port=port_id, enable=True, verify=True) +self.restart_port_and_verify(port_id, testpmd, "VLAN filter") + +testpmd.set_mac_address( +port=port_id, mac_address=ALTERNATIVE_MAC_ADDRESS, verify=True +) +self.restart_port_and_verify(port_id, testpmd, "MAC address") + +testpmd.set_promisc(port=port_id, enable=True, verify=True) +self.restart_port_and_verify(port_id, testpmd, "promiscuous mode") + +@requires(NicCapability.FLOW_CTRL) +@func_test +def flow_ctrl_port_configuration_persistence(self) -> None: +"""Flow control port configuration persistency test. + +Steps: +For each port enable flow control for RX and TX individually. +Verify: +The configuration persists after the port is restarted. +""" +with TestPmdShell(self.sut_node, disable_device_start=True) as testpmd: +for port_id in range(len(self.sut_node.ports)): +flow_ctrl = TestPmdPortFlowCtrl(rx=True) +testpmd.set_flow_control(port=port_id, flow_ctrl=flow_ctrl) +self.restart_port_and_verify(port_id, testpmd, "flow_ctrl") + +flow_ctrl = TestPmdPortFlowCtrl(tx=True) +testpmd.set_flow_control(port=port_id, flow_ctrl=flow_ctrl) +self.restart_port_and_verify(port_id, testpmd, "flow_ctrl") -- 2.43.0
[PATCH v1 1/2] dts: add promiscuous mode verification test
Added verification that enables promiscuous mode, sends a packet with a different destination mac address and then disables promiscuous mode, sends the same packet and checks if the packet was filtered out. Signed-off-by: Thomas Wilks --- Reviewed-by: Luca Vizzarro Reviewed-by: Paul Szczepanek --- dts/tests/TestSuite_promisc_support.py | 63 ++ 1 file changed, 63 insertions(+) create mode 100644 dts/tests/TestSuite_promisc_support.py diff --git a/dts/tests/TestSuite_promisc_support.py b/dts/tests/TestSuite_promisc_support.py new file mode 100644 index 00..a3ea2461f0 --- /dev/null +++ b/dts/tests/TestSuite_promisc_support.py @@ -0,0 +1,63 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright(c) 2025 Arm Limited + +"""Promiscuous mode support test suite. + +Test promiscuous support by sending a packet with a different destination +mac address from the TG to the SUT. +""" + +from scapy.layers.inet import IP +from scapy.layers.l2 import Ether +from scapy.packet import Raw + +from framework.remote_session.testpmd_shell import TestPmdShell +from framework.test_suite import TestSuite, func_test + + +class TestPromiscSupport(TestSuite): +"""Promiscuous mode support test suite.""" + +#: Alternate MAC address. +ALTERNATIVE_MAC_ADDRESS: str = "02:00:00:00:00:00" + +@func_test +def test_promisc_packets(self) -> None: +"""Verify that promiscuous mode works. + +Steps: +Create a packet with a different mac address to the destination. +Enable promiscuous mode. +Send and receive packet. +Disable promiscuous mode. +Send and receive packet. +Verify: +Packet sent with the wrong address is received in promiscuous mode and filtered out +otherwise. + +""" +packet = [Ether(dst=self.ALTERNATIVE_MAC_ADDRESS) / IP() / Raw(load=b"\x00" * 64)] + +with TestPmdShell( +self.sut_node, +) as testpmd: +for port_id in range(len(self.sut_node.ports)): +testpmd.set_promisc(port=port_id, enable=True, verify=True) +testpmd.start() + +received_packets = self.send_packets_and_capture(packet) +expected_packets = self.get_expected_packets(packet, sent_from_tg=True) +self.match_all_packets(expected_packets, received_packets) + +testpmd.stop() + +for port_id in range(len(self.sut_node.ports)): +testpmd.set_promisc(port=port_id, enable=False, verify=True) +testpmd.start() + +received_packets = self.send_packets_and_capture(packet) +expected_packets = self.get_expected_packets(packet, sent_from_tg=True) +self.verify( +not self.match_all_packets(expected_packets, received_packets, verify=False), +"Invalid packet wasn't filtered out.", +) -- 2.43.0
[PATCH v1 0/2] dts: add promiscuous mode verification test
v1: - Added testsuite that verifies promiscuous mode. - Added verify to match_all_packets function. Depends-on: patch-149773 ("dts: allow expected packets to come from the TG") Thomas Wilks (2): dts: add promiscuous mode verification test dts: add verify to match all packets dts/framework/test_suite.py| 23 +++--- dts/tests/TestSuite_promisc_support.py | 63 ++ 2 files changed, 80 insertions(+), 6 deletions(-) create mode 100644 dts/tests/TestSuite_promisc_support.py -- 2.43.0
[PATCH v1 2/2] dts: add verify to match all packets
Added verify to match_all_packets function that if False and there are missing packets causes the function to return False, if all packets are present it returns True. Signed-off-by: Thomas Wilks --- Reviewed-by: Luca Vizzarro Reviewed-by: Paul Szczepanek --- dts/framework/test_suite.py | 23 +-- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/dts/framework/test_suite.py b/dts/framework/test_suite.py index de9f871b3f..f5d3e3e545 100644 --- a/dts/framework/test_suite.py +++ b/dts/framework/test_suite.py @@ -422,8 +422,11 @@ def verify_packets(self, expected_packet: Packet, received_packets: list[Packet] self._fail_test_case_verify("An expected packet not found among received packets.") def match_all_packets( -self, expected_packets: list[Packet], received_packets: list[Packet] -) -> None: +self, +expected_packets: list[Packet], +received_packets: list[Packet], +verify: bool = True, +) -> bool: """Matches all the expected packets against the received ones. Matching is performed by counting down the occurrences in a dictionary which keys are the @@ -433,10 +436,14 @@ def match_all_packets( Args: expected_packets: The packets we are expecting to receive. received_packets: All the packets that were received. +verify: If :data:`True`, and there are missing packets an exception will be raised. Raises: TestCaseVerifyError: if and not all the `expected_packets` were found in `received_packets`. + +Returns: +:data:`True` If there are no missing packets. """ expected_packets_counters = Counter(map(raw, expected_packets)) received_packets_counters = Counter(map(raw, received_packets)) @@ -450,10 +457,14 @@ def match_all_packets( ) if missing_packets_count != 0: -self._fail_test_case_verify( -f"Not all packets were received, expected {len(expected_packets)} " -f"but {missing_packets_count} were missing." -) +if verify: +self._fail_test_case_verify( +f"Not all packets were received, expected {len(expected_packets)} " +f"but {missing_packets_count} were missing." +) +return False + +return True def _compare_packets(self, expected_packet: Packet, received_packet: Packet) -> bool: self._logger.debug( -- 2.43.0
[PATCH v3 2/2] dts: add softnic test
From: Paul Szczepanek Add test that uses a softnic virtual device to forward packets. Signed-off-by: Thomas Wilks Signed-off-by: Paul Szczepanek --- dts/tests/TestSuite_softnic.py | 119 + 1 file changed, 119 insertions(+) create mode 100644 dts/tests/TestSuite_softnic.py diff --git a/dts/tests/TestSuite_softnic.py b/dts/tests/TestSuite_softnic.py new file mode 100644 index 00..07480db392 --- /dev/null +++ b/dts/tests/TestSuite_softnic.py @@ -0,0 +1,119 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright(c) 2024 Arm Limited + +"""Softnic test suite. + +Create a softnic virtual device and verify it successfully forwards packets. +""" + +from pathlib import Path, PurePath + +from framework.params.testpmd import EthPeer +from framework.remote_session.testpmd_shell import TestPmdShell +from framework.test_suite import TestSuite, func_test +from framework.testbed_model.capability import requires +from framework.testbed_model.topology import TopologyType +from framework.testbed_model.virtual_device import VirtualDevice +from framework.utils import generate_random_packets + + +@requires(topology_type=TopologyType.two_links) +class TestSoftnic(TestSuite): +"""Softnic test suite.""" + +#: The total number of packets to generate and send for forwarding. +NUMBER_OF_PACKETS_TO_SEND = 10 +#: The payload size to use for the generated packets in bytes. +PAYLOAD_SIZE = 100 + +def set_up_suite(self) -> None: +"""Set up the test suite. + +Setup: +Generate the random packets that will be sent and create the softnic config files. +""" +self.packets = generate_random_packets(self.NUMBER_OF_PACKETS_TO_SEND, self.PAYLOAD_SIZE) +self.cli_file = self.prepare_softnic_files() + +def prepare_softnic_files(self) -> PurePath: +"""Creates the config files that are required for the creation of the softnic. + +The config files are created at runtime to accommodate paths and device addresses. +""" +# paths of files needed by softnic +cli_file = Path("rx_tx.cli") +spec_file = Path("rx_tx.spec") +rx_tx_1_file = Path("rx_tx_1.io") +rx_tx_2_file = Path("rx_tx_2.io") +path_sut = self.sut_node.remote_dpdk_build_dir +cli_file_sut = self.sut_node.main_session.join_remote_path(path_sut, cli_file) +spec_file_sut = self.sut_node.main_session.join_remote_path(path_sut, spec_file) +rx_tx_1_file_sut = self.sut_node.main_session.join_remote_path(path_sut, rx_tx_1_file) +rx_tx_2_file_sut = self.sut_node.main_session.join_remote_path(path_sut, rx_tx_2_file) +firmware_c_file_sut = self.sut_node.main_session.join_remote_path(path_sut, "firmware.c") +firmware_so_file_sut = self.sut_node.main_session.join_remote_path(path_sut, "firmware.so") + +# write correct remote paths to local files +with open(cli_file, "w+") as fh: +fh.write(f"pipeline codegen {spec_file_sut} {firmware_c_file_sut}\n") +fh.write(f"pipeline libbuild {firmware_c_file_sut} {firmware_so_file_sut}\n") +fh.write(f"pipeline RX build lib {firmware_so_file_sut} io {rx_tx_1_file_sut} numa 0\n") +fh.write(f"pipeline TX build lib {firmware_so_file_sut} io {rx_tx_2_file_sut} numa 0\n") +fh.write("thread 2 pipeline RX enable\n") +fh.write("thread 2 pipeline TX enable\n") +with open(spec_file, "w+") as fh: +fh.write("struct metadata_t {{\n") +fh.write(" bit<32> port\n") +fh.write("}}\n") +fh.write("metadata instanceof metadata_t\n") +fh.write("apply {{\n") +fh.write(" rx m.port\n") +fh.write(" tx m.port\n") +fh.write("}}\n") +with open(rx_tx_1_file, "w+") as fh: +fh.write(f"port in 0 ethdev {self.sut_node.config.ports[0].pci} rxq 0 bsz 32\n") +fh.write("port out 0 ring RXQ0 bsz 32\n") +with open(rx_tx_2_file, "w+") as fh: +fh.write("port in 0 ring TXQ0 bsz 32\n") +fh.write(f"port out 1 ethdev {self.sut_node.config.ports[1].pci} txq 0 bsz 32\n") + +# copy files over to SUT +self.sut_node.main_session.copy_to(cli_file, cli_file_sut) +self.sut_node.main_session.copy_to(spec_file, spec_file_sut) +self.sut_node.main_session.copy_to(rx_tx_1_file, rx_tx_1_file_sut) +self.sut_node.main_session.copy_to(rx_tx_2_file, rx_tx_2_file_sut) +# and cle
[PATCH v3 1/2] dts: allow expected packets to come from the TG
From: Paul Szczepanek Add sent_from_tg variable to get_expected_packets for when packets are sent from the TG. Signed-off-by: Thomas Wilks Signed-off-by: Paul Szczepanek --- dts/framework/test_suite.py | 18 ++ 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/dts/framework/test_suite.py b/dts/framework/test_suite.py index 16012bfc79..144fc78e07 100644 --- a/dts/framework/test_suite.py +++ b/dts/framework/test_suite.py @@ -274,31 +274,41 @@ def send_packets( packets = self._adjust_addresses(packets) self.tg_node.send_packets(packets, self._tg_port_egress) -def get_expected_packets(self, packets: list[Packet]) -> list[Packet]: +def get_expected_packets( +self, +packets: list[Packet], +sent_from_tg: bool = False, +) -> list[Packet]: """Inject the proper L2/L3 addresses into `packets`. Inject the L2/L3 addresses expected at the receiving end of the traffic generator. Args: packets: The packets to modify. +sent_from_tg: If :data:`True` packet was sent from the TG. Returns: `packets` with injected L2/L3 addresses. """ -return self._adjust_addresses(packets, expected=True) +return self._adjust_addresses(packets, not sent_from_tg) -def get_expected_packet(self, packet: Packet) -> Packet: +def get_expected_packet( +self, +packet: Packet, +sent_from_tg: bool = False, +) -> Packet: """Inject the proper L2/L3 addresses into `packet`. Inject the L2/L3 addresses expected at the receiving end of the traffic generator. Args: packet: The packet to modify. +sent_from_tg: If :data:`True` packet was sent from the TG. Returns: `packet` with injected L2/L3 addresses. """ -return self.get_expected_packets([packet])[0] +return self.get_expected_packets([packet], sent_from_tg)[0] def _adjust_addresses(self, packets: list[Packet], expected: bool = False) -> list[Packet]: """L2 and L3 address additions in both directions. -- 2.43.0
[PATCH v3 0/2] dts: add softnic test
v3: - Remove disable_device_start=True from shell start - Fixed Docstrings Paul Szczepanek (2): dts: allow expected packets to come from the TG dts: add softnic test dts/framework/test_suite.py| 18 +++-- dts/tests/TestSuite_softnic.py | 119 + 2 files changed, 133 insertions(+), 4 deletions(-) create mode 100644 dts/tests/TestSuite_softnic.py -- 2.43.0
[PATCH 2/2] dts: use tmp dir and DPDK tree dir
File operations were not consistently using the same dedicated directory for the test run. Moreover the DPDK tree temporary directory was inconsistent and relying on the name chosen by the user. Update all the file operations to use the dedicated temporary directory, and make sure that the DPDK tree temporary directory is fixed to a predefined directory name. Make extraction verbose to avoid timeout for big tarballs. Signed-off-by: Thomas Wilks Reviewed-by: Luca Vizzarro Reviewed-by: Paul Szczepanek --- dts/framework/remote_session/dpdk.py | 74 ++-- dts/framework/testbed_model/os_session.py| 10 +-- dts/framework/testbed_model/posix_session.py | 22 +++--- dts/tests/TestSuite_softnic.py | 2 +- 4 files changed, 41 insertions(+), 67 deletions(-) diff --git a/dts/framework/remote_session/dpdk.py b/dts/framework/remote_session/dpdk.py index 401e3a7277..59fc50c3af 100644 --- a/dts/framework/remote_session/dpdk.py +++ b/dts/framework/remote_session/dpdk.py @@ -11,7 +11,7 @@ from dataclasses import dataclass from functools import cached_property from pathlib import Path, PurePath -from typing import Final +from typing import ClassVar, Final from framework.config.test_run import ( DPDKBuildConfiguration, @@ -56,10 +56,9 @@ class DPDKBuildEnvironment: _node: Node _session: OSSession _logger: DTSLogger -_remote_tmp_dir: PurePath -_remote_dpdk_tree_path: str | PurePath | None _remote_dpdk_build_dir: PurePath | None _app_compile_timeout: float +_remote_tmp_dpdk_tree_dir: ClassVar[str] = "dpdk" compiler_version: str | None @@ -70,8 +69,6 @@ def __init__(self, config: DPDKBuildConfiguration, node: Node): self._logger = get_dts_logger() self._session = node.create_session("dpdk_build") -self._remote_tmp_dir = node.main_session.get_remote_tmp_dir() -self._remote_dpdk_tree_path = None self._remote_dpdk_build_dir = None self._app_compile_timeout = 90 @@ -84,6 +81,9 @@ def setup(self): sources and then building DPDK or using the exist ones from the `dpdk_location`. The drivers are bound to those that DPDK needs. """ +if not isinstance(self.config.dpdk_location, RemoteDPDKTreeLocation): + self._node.main_session.create_directory(self.remote_dpdk_tree_path) + match self.config.dpdk_location: case RemoteDPDKTreeLocation(dpdk_tree=dpdk_tree): self._set_remote_dpdk_tree_path(dpdk_tree) @@ -114,7 +114,7 @@ def teardown(self) -> None: case LocalDPDKTarballLocation(tarball=tarball): self._node.main_session.remove_remote_dir(self.remote_dpdk_tree_path) tarball_path = self._node.main_session.join_remote_path( -self._remote_tmp_dir, tarball.name +self._node.tmp_dir, tarball.name ) self._node.main_session.remove_remote_file(tarball_path) @@ -122,7 +122,7 @@ def _set_remote_dpdk_tree_path(self, dpdk_tree: PurePath): """Set the path to the remote DPDK source tree based on the provided DPDK location. Verify DPDK source tree existence on the SUT node, if exists sets the -`_remote_dpdk_tree_path` property, otherwise sets nothing. +`remote_dpdk_tree_path` property, otherwise sets nothing. Args: dpdk_tree: The path to the DPDK source tree directory. @@ -139,7 +139,7 @@ def _set_remote_dpdk_tree_path(self, dpdk_tree: PurePath): if not self._session.is_remote_dir(dpdk_tree): raise ConfigurationError(f"Remote DPDK source tree '{dpdk_tree}' must be a directory.") -self._remote_dpdk_tree_path = dpdk_tree +self.remote_dpdk_tree_path = dpdk_tree def _copy_dpdk_tree(self, dpdk_tree_path: Path) -> None: """Copy the DPDK source tree to the SUT. @@ -148,19 +148,16 @@ def _copy_dpdk_tree(self, dpdk_tree_path: Path) -> None: dpdk_tree_path: The path to DPDK source tree on local filesystem. """ self._logger.info( -f"Copying DPDK source tree to SUT: '{dpdk_tree_path}' into '{self._remote_tmp_dir}'." +f"Copying DPDK source tree to SUT: '{dpdk_tree_path}' into " +f"'{self.remote_dpdk_tree_path}'." ) self._session.copy_dir_to( dpdk_tree_path, -self._remote_tmp_dir, +self.remote_dpdk_tree_path, exclude=[".git", "*.o"], compress_format=TarCompressionFormat.gzip, ) -self._remote_dpdk_tree_path = self._session.join_remote_path( -self._remote_tmp_dir, PurePath(dpdk_tree_path).name -) - def
Re: [PATCH 0/2] dts: use tmp dir and DPDK tree dir
Recheck-request: iol-unit-amd64-testing From: Thomas Wilks Sent: 15 April 2025 14:09 To: dev@dpdk.org Cc: Paul Szczepanek ; Luca Vizzarro ; Patrick Robb ; Thomas Wilks Subject: [PATCH 0/2] dts: use tmp dir and DPDK tree dir Hi, Sending this update to extend the use of the tmp and DPDK tree dir. Best regards, Thomas Thomas Wilks (2): dts: add remote create dir function dts: use tmp dir and DPDK tree dir dts/framework/remote_session/dpdk.py | 74 ++-- dts/framework/testbed_model/os_session.py| 14 ++-- dts/framework/testbed_model/posix_session.py | 26 --- dts/tests/TestSuite_softnic.py | 2 +- 4 files changed, 49 insertions(+), 67 deletions(-) -- 2.43.0 IMPORTANT NOTICE: The contents of this email and any attachments are confidential and may also be privileged. If you are not the intended recipient, please notify the sender immediately and do not disclose the contents to any other person, use it for any purpose, or store or copy the information in any medium. Thank you.
[PATCH 1/2] dts: add remote create dir function
Add a function that creates a directory on the remote. Signed-off-by: Thomas Wilks Reviewed-by: Luca Vizzarro Reviewed-by: Paul Szczepanek --- dts/framework/testbed_model/os_session.py| 4 dts/framework/testbed_model/posix_session.py | 4 2 files changed, 8 insertions(+) diff --git a/dts/framework/testbed_model/os_session.py b/dts/framework/testbed_model/os_session.py index 354c607357..2be4f78ac0 100644 --- a/dts/framework/testbed_model/os_session.py +++ b/dts/framework/testbed_model/os_session.py @@ -360,6 +360,10 @@ def create_remote_tarball( The path to the created tarball on the remote node. """ +@abstractmethod +def create_directory(self, path: PurePath) -> None: +"""Create a directory at a specified `path`.""" + @abstractmethod def extract_remote_tarball( self, diff --git a/dts/framework/testbed_model/posix_session.py b/dts/framework/testbed_model/posix_session.py index 2d2701e1cf..57d58030b4 100644 --- a/dts/framework/testbed_model/posix_session.py +++ b/dts/framework/testbed_model/posix_session.py @@ -195,6 +195,10 @@ def generate_tar_exclude_args(exclude_patterns) -> str: return target_tarball_path +def create_directory(self, path: PurePath) -> None: +"""Overrides :meth:`~.os_session.OSSession.create_directory`.""" +self.send_command(f"mkdir -p {path}") + def extract_remote_tarball( self, remote_tarball_path: str | PurePath, -- 2.43.0
[PATCH 0/2] dts: use tmp dir and DPDK tree dir
Hi, Sending this update to extend the use of the tmp and DPDK tree dir. Best regards, Thomas Thomas Wilks (2): dts: add remote create dir function dts: use tmp dir and DPDK tree dir dts/framework/remote_session/dpdk.py | 74 ++-- dts/framework/testbed_model/os_session.py| 14 ++-- dts/framework/testbed_model/posix_session.py | 26 --- dts/tests/TestSuite_softnic.py | 2 +- 4 files changed, 49 insertions(+), 67 deletions(-) -- 2.43.0
[PATCH v2 0/6] Added RSS functions and tests.
v2: - Added RSS capabilities check. - Added testsuite that tests RSS hashes. - Added testsuite that tests updating the RSS hash key. - Added testsuite that verifies the RETA size. Alex Chapman (5): dts: add RSS functions to testpmd dts: add utils for PMD RSS testsuites dts: add PMD RSS hash testsuite dts: add PMD RSS RETA testsuite dts: add PMD RSS key update testsuite Thomas Wilks (1): dts: add NIC capabilities for hash algorithms dts/framework/remote_session/testpmd_shell.py | 283 +- dts/tests/TestSuite_pmd_rss_hash.py | 121 dts/tests/TestSuite_pmd_rss_key_update.py | 168 +++ dts/tests/TestSuite_pmd_rss_reta.py | 101 +++ dts/tests/pmd_rss_utils.py| 195 5 files changed, 854 insertions(+), 14 deletions(-) create mode 100644 dts/tests/TestSuite_pmd_rss_hash.py create mode 100644 dts/tests/TestSuite_pmd_rss_key_update.py create mode 100644 dts/tests/TestSuite_pmd_rss_reta.py create mode 100644 dts/tests/pmd_rss_utils.py -- 2.43.0
[PATCH v2 1/6] dts: add RSS functions to testpmd
From: Alex Chapman This patch adds the required functionality for the RSS key_update, RETA, and hash test suites. This includes: The setting of custom RETA values for routing packets to specific queues. The setting of the RSS mode on all ports, to specify how to hash the packets. The updating of the RSS hash key used during the hashing process. Alongside this, there is the addition of a __str__ method to the RSSOffloadTypesFlags class, so that when flag names are cast to a string they will use '-' as separators, instead of '_'. This allows them to be directly used within testpmd RSS commands without any further changes. Signed-off-by: Alex Chapman Signed-off-by: Thomas Wilks Reviewed-by: Paul Szczepanek --- dts/framework/remote_session/testpmd_shell.py | 137 -- 1 file changed, 123 insertions(+), 14 deletions(-) diff --git a/dts/framework/remote_session/testpmd_shell.py b/dts/framework/remote_session/testpmd_shell.py index 1f291fcb68..0e1f29f2f3 100644 --- a/dts/framework/remote_session/testpmd_shell.py +++ b/dts/framework/remote_session/testpmd_shell.py @@ -350,6 +350,12 @@ def make_parser(cls) -> ParserFn: RSSOffloadTypesFlag.from_list_string, ) +def __str__(self): +"""Replaces underscores with hyphens to produce valid testpmd value.""" +if self.name is None: +return "" +return self.name.replace("_", "-") + class DeviceCapabilitiesFlag(Flag): """Flag representing the device capabilities.""" @@ -655,7 +661,8 @@ class TestPmdPort(TextParser): ) #: Maximum number of VMDq pools max_vmdq_pools_num: int | None = field( -default=None, metadata=TextParser.find_int(r"Maximum number of VMDq pools: (\d+)") +default=None, +metadata=TextParser.find_int(r"Maximum number of VMDq pools: (\d+)"), ) #: @@ -1482,7 +1489,9 @@ def _wrapper(self: "TestPmdShell", *args: P.args, **kwargs: P.kwargs): return _wrapper -def add_remove_mtu(mtu: int = 1500) -> Callable[[TestPmdShellMethod], TestPmdShellMethod]: +def add_remove_mtu( +mtu: int = 1500, +) -> Callable[[TestPmdShellMethod], TestPmdShellMethod]: """Configure MTU to `mtu` on all ports, run the decorated function, then revert. Args: @@ -1723,6 +1732,82 @@ def set_ports_queues(self, number_of: int) -> None: self.send_command(f"port config all rxq {number_of}") self.send_command(f"port config all txq {number_of}") +def port_config_rss_reta( +self, port_id: int, hash_index: int, queue_id: int, verify: bool = True +) -> None: +"""Configure a port's RSS redirection table. + +Args: +port_id: The port where the redirection table will be configured. +hash_index: The index into the redirection table associated with the destination queue. +queue_id: The destination queue of the packet. +verify: If :data:`True`, verifies if a port's redirection table +was correctly configured. + +Raises: +InteractiveCommandExecutionError: If `verify` is :data:`True` +Testpmd failed to config RSS reta. +""" +out = self.send_command(f"port config {port_id} rss reta ({hash_index},{queue_id})") +if verify: +if f"The reta size of port {port_id} is" not in out: +self._logger.debug(f"Failed to config RSS reta: \n{out}") +raise InteractiveCommandExecutionError("Testpmd failed to config RSS reta.") + +def port_config_all_rss_offload_type( +self, flag: RSSOffloadTypesFlag, verify: bool = True +) -> None: +"""Set the RSS mode on all ports. + +Args: +flag: The RSS iptype all ports will be configured to. +verify: If :data:`True`, it verifies if all ports RSS offload type +was correctly configured. + +Raises: +InteractiveCommandExecutionError: If `verify` is :data:`True` +Testpmd failed to config the RSS mode on all ports. +""" +out = self.send_command(f"port config all rss {flag.name}") +if verify: +if "error" in out: +self._logger.debug(f"Failed to config the RSS mode on all ports: \n{out}") +raise InteractiveCommandExecutionError( +f"Testpmd failed to change RSS mode to {flag.name}" +) + +def port_config_rss_hash_key( +self, +port_id: int, +offload_type: RSSOffloadTypesFlag, +hex_str: str, +verify: bool = True, +
[PATCH v2 2/6] dts: add utils for PMD RSS testsuites
From: Alex Chapman To reduce the amount of maintenance and code duplication, common functionality between the rss_key_update, pmd_rss_reta and pmd_rss_hash test suites has been collated into a single file called rss_utils. It contains 3 main functions: 1. verify that a packets RSS hash correctly associates with the packets RSS queue. 2. Send test packets specific to RSS, such as symmetric packets that have the L4 port src and dst swapped. 3. The setting up of the RSS environment which is common between all 3 tets suites. Signed-off-by: Alex Chapman Signed-off-by: Thomas Wilks Reviewed-by: Paul Szczepanek --- dts/tests/pmd_rss_utils.py | 195 + 1 file changed, 195 insertions(+) create mode 100644 dts/tests/pmd_rss_utils.py diff --git a/dts/tests/pmd_rss_utils.py b/dts/tests/pmd_rss_utils.py new file mode 100644 index 00..2d9b333859 --- /dev/null +++ b/dts/tests/pmd_rss_utils.py @@ -0,0 +1,195 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright(c) 2025 Arm Limited + +"""PMD RSS Test Suite Utils. + +Utility functions for the pmd_rss_... test suite series +""" + +import random + +from scapy.layers.inet import IP, UDP +from scapy.layers.l2 import Ether + +from framework.exception import InteractiveCommandExecutionError +from framework.params.testpmd import SimpleForwardingModes +from framework.remote_session.testpmd_shell import ( +FlowRule, +RSSOffloadTypesFlag, +TestPmdShell, +TestPmdVerbosePacket, +) +from framework.test_suite import TestSuite + + +def VerifyHashQueue( +test_suite: TestSuite, +reta: list[int], +received_packets: list[TestPmdVerbosePacket], +verify_packet_pairs: bool, +) -> None: +"""Verifies the packet hash corresponds to the packet queue. + +Given the received packets in the verbose output, iterate through each packet. +Lookup the packet hash in the RETA and get its intended queue. +Verify the intended queue is the same as the actual queue the packet was received in. +If the hash algorithm is symmetric, verify that pairs of packets have the same hash, +as the pairs of packets sent have "mirrored" L4 ports. +e.g. received_packets[0, 1, 2, 3, ...] hash(0) = hash(1), hash(2) = hash(3), ... + +Args: +test_suite: The reference to the currently running test suite. +reta: Used to get predicted queue based on hash. +received_packets: Packets received in the verbose output of testpmd. +verify_packet_pairs: Verify pairs of packets have the same hash. + +Raises: +InteractiveCommandExecutionError: If packet_hash is None. +""" +# List of packet hashes, used for symmetric algorithms +hash_list = [] +for packet in received_packets: +if packet.port_id != 0 or packet.src_mac != "02:00:00:00:00:00": +continue + +# Get packet hash +packet_hash = packet.rss_hash +if packet_hash is None: +raise InteractiveCommandExecutionError( +"Packet sent by the Traffic Generator has no RSS hash attribute." +) + +packet_queue = packet.rss_queue + +# Calculate the predicted packet queue +predicted_reta_index = packet_hash % len(reta) +predicted_queue = reta[predicted_reta_index] + +# Verify packets are in the correct queue +test_suite.verify( +predicted_queue == packet_queue, +"Packet sent by the Traffic Generator has no RSS queue attribute.", +) + +if verify_packet_pairs: +hash_list.append(packet_hash) + +if verify_packet_pairs: +# Go through pairs of hashes in list and verify they are the same +for odd_hash, even_hash in zip(hash_list[0::2], hash_list[1::2]): +test_suite.verify( +odd_hash == even_hash, +"Packet pair do not have same hash. Hash algorithm is not symmetric.", +) + + +def SendTestPackets( +TestSuite: TestSuite, +testpmd: TestPmdShell, +send_additional_mirrored_packet: bool, +) -> list[TestPmdVerbosePacket]: +"""Sends test packets. + +Send 10 packets from the TG to SUT, parsing the verbose output and returning it. +If the algorithm chosen is symmetric, send an additional packet for each initial +packet sent, which has the L4 src and dst swapped. + +Args: +TestSuite: The reference to the currently running test suite. +testpmd: Used to send packets and send commands to testpmd. +send_additional_mirrored_packet: Send an additional mirrored packet for each packet sent. + +Returns: +TestPmdVerbosePacket: List of packets. +""" +# Create test packets +packets = [] +for i in range(10): +packets.append( +
[PATCH v2 5/6] dts: add PMD RSS key update testsuite
From: Alex Chapman Port over the rss_key_update test suite from old DTS. This suite verifies that setting a new hash key when Receive Side Scaling (RSS) will result in a change in the packets destination queue. This test suite also verifies that the reported key size of the NIC is correct. Signed-off-by: Alex Chapman Signed-off-by: Thomas Wilks Reviewed-by: Paul Szczepanek --- dts/tests/TestSuite_pmd_rss_key_update.py | 168 ++ 1 file changed, 168 insertions(+) create mode 100644 dts/tests/TestSuite_pmd_rss_key_update.py diff --git a/dts/tests/TestSuite_pmd_rss_key_update.py b/dts/tests/TestSuite_pmd_rss_key_update.py new file mode 100644 index 00..8a88d44e77 --- /dev/null +++ b/dts/tests/TestSuite_pmd_rss_key_update.py @@ -0,0 +1,168 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright(c) 2025 Arm Limited + +"""RSS Key Update testing suite. + +RSS hash keys are used in conjunction with a hashing algorithm to hash packets. +This test suite verifies that updating the RSS hash key will change the hash +generated if the hashing algorithm stays the same. +""" + +import random + +from framework.exception import InteractiveCommandExecutionError +from framework.remote_session.testpmd_shell import ( +FlowRule, +RSSOffloadTypesFlag, +TestPmdShell, +) +from framework.test_suite import TestSuite, func_test +from framework.testbed_model.capability import requires +from framework.testbed_model.topology import TopologyType + +from .pmd_rss_utils import ( # type: ignore[import-untyped] +SendTestPackets, +SetupRssEnvironment, +VerifyHashQueue, +) + +NUM_QUEUES = 16 + +ACTUAL_KEY_SIZE = 52 + + +@requires(topology_type=TopologyType.one_link) +class TestPmdRssKeyUpdate(TestSuite): +"""The DPDK RSS key update test suite. + +Configure the redirection table to have random entries. +Create a flow rule so ipv4-udp packets utalise the desired RSS hash algorithm. +Send packets, and verify they are in the desired queue based on the redirection table. +Update the RSS hash key. +Send packets, and verify they are in the desired queue based on the redirection table. +Verify the packet hashes before and after the key update are different. +""" + +@func_test +def TestKeyUpdate( +self, +) -> None: +"""Update RSS hash key test. + +Steps: +Setup the RSS environment, send test packet verify the hash queue based on the +RETA table, Reset the flow rules and update the hash key. +Create the flow and send/verify the hash/queue of the packets again. + +Verify: +Verify the packet hashes before and after the hash key was updated are not the same. +to show the key update was successful. +""" +# Create flow rule +flow_rule = FlowRule( +group_id=0, +direction="ingress", +pattern=["eth / ipv4 / udp"], +actions=["rss types ipv4-udp end queues end func default"], +) + +with TestPmdShell( +memory_channels=4, +rx_queues=NUM_QUEUES, +tx_queues=NUM_QUEUES, +) as testpmd: +# Setup testpmd environment for RSS, create RETA table, return RETA table and key_size +reta, key_size = SetupRssEnvironment(self, testpmd, NUM_QUEUES, flow_rule) + +# Send UDP packets and ensure hash corresponds with queue +pre_update_output = SendTestPackets(self, testpmd, False) + +VerifyHashQueue(self, reta, pre_update_output, False) + +# Reset RSS settings and only RSS UDP packets +testpmd.port_config_all_rss_offload_type(RSSOffloadTypesFlag.udp) + +# Create new hash key and update it +new_hash_key = "".join([random.choice("0123456789ABCDEF") for n in range(key_size * 2)]) +testpmd.port_config_rss_hash_key(0, RSSOffloadTypesFlag.ipv4_udp, new_hash_key) + +# Create flow rule + +for port_id, _ in enumerate(self.topology.sut_ports): +testpmd.flow_create(flow_rule, port_id) + +# Send UDP packets and ensure hash corresponds with queue +post_update_output = SendTestPackets(self, testpmd, False) +VerifyHashQueue(self, reta, pre_update_output, False) + +self.verify( +pre_update_output != post_update_output, +"The hash key had no effect on the packets hash.", +) + +@func_test +def TestSetHashKeyShortLong(self) -> None: +"""Set hash key short long test. + +Steps: +Fetch the hash key size, create two random hash keys one key that is too short and one +that is too long.
[PATCH v2 3/6] dts: add PMD RSS hash testsuite
From: Alex Chapman Port over the pmd_rss_hash test suite from old DTS. This suite verifies that the 4 supported types of hashing algorithm used in Receive Side Scaling (RSS) function correctly. Them being DEFAULT, TOEPLITZ SYMMETRIC_TOEPLITZ and SIMPLE_XOR. This test suite also verifies the supported hashing algorithms reported by the NIC are correct. Signed-off-by: Alex Chapman Signed-off-by: Thomas Wilks Reviewed-by: Paul Szczepanek --- dts/tests/TestSuite_pmd_rss_hash.py | 118 1 file changed, 118 insertions(+) create mode 100644 dts/tests/TestSuite_pmd_rss_hash.py diff --git a/dts/tests/TestSuite_pmd_rss_hash.py b/dts/tests/TestSuite_pmd_rss_hash.py new file mode 100644 index 00..d21e33456e --- /dev/null +++ b/dts/tests/TestSuite_pmd_rss_hash.py @@ -0,0 +1,118 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright(c) 2025 Arm Limited + +"""RSS Hash testing suite. + +Hashing algorithms are used in conjunction with a RSS hash keys to hash packets. +This test suite verifies that updating the Hashing algorithms will +continue to correctly hash packets. + +Symmetric_toeplitz_sort hasn't been included due to it not being supported by +the rss func actions in the flow rule. +""" + +from framework.remote_session.testpmd_shell import FlowRule, TestPmdShell +from framework.test_suite import TestSuite, func_test +from framework.testbed_model.capability import NicCapability, requires +from framework.testbed_model.topology import TopologyType +from framework.utils import StrEnum + +from .pmd_rss_utils import ( # type: ignore[import-untyped] +SendTestPackets, +SetupRssEnvironment, +VerifyHashQueue, +) + +NUM_QUEUES = 16 + + +class HashAlgorithm(StrEnum): +"""Enum of hashing algorithms.""" + +DEFAULT = "default" +SIMPLE_XOR = "simple_xor" +TOEPLITZ = "toeplitz" +SYMMETRIC_TOEPLITZ = "symmetric_toeplitz" + + +@requires(topology_type=TopologyType.one_link) +class TestPmdRssHash(TestSuite): +"""PMD RSS Hash test suite. + +Verifies the redirection table when updating the size. +The suite contains four tests, one for each hash algorithms. +""" + +def VerifyHashFunction(self, hash_algorithm: HashAlgorithm) -> None: +"""Verifies the hash function is supported by the NIC. + +Args: +hash_algorithm: The hash algorithm to be tested. +""" +is_symmetric = hash_algorithm == HashAlgorithm.SYMMETRIC_TOEPLITZ +# Build flow rule +flow_rule = FlowRule( +group_id=0, +direction="ingress", +pattern=["eth / ipv4 / udp"], +actions=[f"rss types ipv4-udp end queues end func {str(hash_algorithm).lower()}"], +) + +# Run the key update test suite with an asymmetric hash algorithm +with TestPmdShell( +rx_queues=NUM_QUEUES, +tx_queues=NUM_QUEUES, +) as testpmd: +# Setup testpmd environment for RSS, create RETA table, return RETA table and key_size +reta, _ = SetupRssEnvironment(self, testpmd, NUM_QUEUES, flow_rule) +# Send udp packets and ensure hash corresponds with queue +parsed_output = SendTestPackets(self, testpmd, is_symmetric) +VerifyHashQueue(self, reta, parsed_output, is_symmetric) + +@func_test +def TestDefaultHashAlgorithm(self) -> None: +"""Default hashing algorithm test. + +Steps: +Setup RSS environment using the default RSS hashing algorithm +and send test packets. +Verify: +Packet hash corresponds to the packet queue. +""" +self.VerifyHashFunction(HashAlgorithm.DEFAULT) + +@func_test +def TestToeplitzHashAlgorithm(self) -> None: +"""Toeplitz hashing algorithm test. + +Steps: +Setup RSS environment using the toeplitz RSS hashing algorithm and send test packets. +Verify: +Packet hash corresponds to the packet queue. +""" +self.VerifyHashFunction(HashAlgorithm.TOEPLITZ) + +@func_test +def TestSymmetricToeplitzHashAlgorithm(self) -> None: +"""Symmetric toeplitz hashing algorithm test. + +Steps: +Setup RSS environment using the symmetric_toeplitz RSS hashing algorithm +and send test packets. +Verify: +Packet hash corresponds to the packet queue. +""" +self.VerifyHashFunction(HashAlgorithm.SYMMETRIC_TOEPLITZ) + +@requires(NicCapability.XOR_SUPPORT) +@func_test +def TestSimpleXorHashAlgorithm(self) -> None: +"""Simple xor hashing algorithm test. + +Steps: +Setup RSS environment using the simple xor RSS hashing algorithm +and send test packets. +Verify: +Packet hash corresponds to the packet queue. +""" +self.VerifyHashFunction(HashAlgorithm.SIMPLE_XOR) -- 2.43.0
[PATCH v2 4/6] dts: add PMD RSS RETA testsuite
From: Alex Chapman Port over the pmd_rss_reta test suite from old DTS. This suite verifies that Redirection Tables (RETAs) of different sizes function correctly in Receive Side Scaling (RSS). This test suite also verifies that the reported reta size of the NIC is correct. Signed-off-by: Alex Chapman Signed-off-by: Thomas Wilks Reviewed-by: Paul Szczepanek --- dts/tests/TestSuite_pmd_rss_reta.py | 101 1 file changed, 101 insertions(+) create mode 100644 dts/tests/TestSuite_pmd_rss_reta.py diff --git a/dts/tests/TestSuite_pmd_rss_reta.py b/dts/tests/TestSuite_pmd_rss_reta.py new file mode 100644 index 00..31df817e75 --- /dev/null +++ b/dts/tests/TestSuite_pmd_rss_reta.py @@ -0,0 +1,101 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright(c) 2025 Arm Limited + +"""RSS RETA (redirection table) Test Suite. + +The RETA is used in RSS to redirect packets to different queues based on the +least significant bits of the packets hash. +This suite tests updating the size of the RETA and verifying the reported RETA size. +""" + +from framework.remote_session.testpmd_shell import TestPmdShell +from framework.test_suite import TestSuite, func_test +from framework.testbed_model.capability import requires +from framework.testbed_model.topology import TopologyType + +from .pmd_rss_utils import ( # type: ignore[import-untyped] +SendTestPackets, +SetupRssEnvironment, +VerifyHashQueue, +) + +ACTUAL_RETA_SIZE = 512 + + +@requires(topology_type=TopologyType.one_link) +class TestPmdRssReta(TestSuite): +"""PMD RSS Reta test suite. + +Verifies the redirection table when updating the size. +The suite contains four tests, three for different RETA sizes +and one for verifying the reported RETA size. +""" + +def CheckRetaNQueues(self, num_queues: int) -> None: +"""Create RETA of size n, send packets, verify packets end up in correct queue. + +Args: +num_queues: Number of rx/tx queues. +""" +with TestPmdShell( +rx_queues=num_queues, +tx_queues=num_queues, +) as testpmd: +# Setup testpmd for RSS, create RETA table, return RETA table and key_size +reta, _ = SetupRssEnvironment(self, testpmd, num_queues, None) + +# Send UDP packets and ensure hash corresponds with queue +parsed_output = SendTestPackets(self, testpmd, False) +VerifyHashQueue(self, reta, parsed_output, False) + +@func_test +def TestReta2Queues(self) -> None: +"""RETA rx/tx queues 2 test. + +Steps: +Setup RSS environment and send Test packets. +Verify: +Packet hash corresponds to hash queue. +""" +self.CheckRetaNQueues(2) + +@func_test +def TestReta9Queues(self) -> None: +"""RETA rx/tx queues 9 test. + +Steps: +Setup RSS environment and send Test packets. +Verify: +Packet hash corresponds to hash queue. +""" +self.CheckRetaNQueues(9) + +@func_test +def TestReta16Queues(self) -> None: +"""RETA rx/tx queues 16 test. + +Steps: +Setup RSS environment and send Test packets. +Verify: +Packet hash corresponds to hash queue. +""" +self.CheckRetaNQueues(16) + +@func_test +def TestReportedRetaSize(self) -> None: +"""Reported RETA size test. + +Steps: +Fetch reported reta size. +Verify: +Reported RETA size is equal to the actual RETA size. +""" +with TestPmdShell() as testpmd: +self.topology.sut_port_egress.config +# Get RETA table size +port_info = testpmd.show_port_info(0) +reported_reta_size = port_info.redirection_table_size +self.verify( +reported_reta_size == ACTUAL_RETA_SIZE, +"Reported RETA size is not the same as the config file.", +) -- 2.43.0
[PATCH v2 6/6] dts: add NIC capabilities for hash algorithms
Added checks for if a nic supports the simple_xor, symmetric_toeplitz, symmetric_toeplitz_sort, toeplitz, and default hashing algorithms. Signed-off-by: Thomas Wilks Reviewed-by: Paul Szczepanek --- dts/framework/remote_session/testpmd_shell.py | 146 ++ dts/tests/TestSuite_pmd_rss_hash.py | 5 +- 2 files changed, 150 insertions(+), 1 deletion(-) diff --git a/dts/framework/remote_session/testpmd_shell.py b/dts/framework/remote_session/testpmd_shell.py index 0e1f29f2f3..4a5b31574c 100644 --- a/dts/framework/remote_session/testpmd_shell.py +++ b/dts/framework/remote_session/testpmd_shell.py @@ -2678,6 +2678,127 @@ def get_capabilities_flow_ctrl( else: unsupported_capabilities.add(NicCapability.FLOW_CTRL) +def get_capabilities_xor_rss_hash_algorithms( +self, +supported_capabilities: MutableSet["NicCapability"], +unsupported_capabilities: MutableSet["NicCapability"], +) -> None: +"""Get simple_xor rss hash algorithm capability and check for testpmd failure. + +Args: +supported_capabilities: Supported capabilities will be added to this set. +unsupported_capabilities: Unsupported capabilities will be added to this set. +""" +self.get_capabilities_rss_hash_algorithms( +"simple_xor", +NicCapability.RSS_HASH_XOR, +supported_capabilities, +unsupported_capabilities, +) + +def get_capabilities_symmetric_toeplitz_rss_hash_algorithms( +self, +supported_capabilities: MutableSet["NicCapability"], +unsupported_capabilities: MutableSet["NicCapability"], +) -> None: +"""Get symmetric_toeplitz rss hash algorithm capability and check for testpmd failure. + +Args: +supported_capabilities: Supported capabilities will be added to this set. +unsupported_capabilities: Unsupported capabilities will be added to this set. +""" +self.get_capabilities_rss_hash_algorithms( +"symmetric Toeplitz", +NicCapability.RSS_HASH_SYMMETRIC_TOEPLITZ, +supported_capabilities, +unsupported_capabilities, +) + +def get_capabilities_toeplitz_rss_hash_algorithms( +self, +supported_capabilities: MutableSet["NicCapability"], +unsupported_capabilities: MutableSet["NicCapability"], +) -> None: +"""Get toeplitz rss hash algorithm capability and check for testpmd failure. + +Args: +supported_capabilities: Supported capabilities will be added to this set. +unsupported_capabilities: Unsupported capabilities will be added to this set. +""" +self.get_capabilities_rss_hash_algorithms( +"toeplitz", +NicCapability.RSS_HASH_TOEPLITZ, +supported_capabilities, +unsupported_capabilities, +) + +def get_capabilities_default_rss_hash_algorithms( +self, +supported_capabilities: MutableSet["NicCapability"], +unsupported_capabilities: MutableSet["NicCapability"], +) -> None: +"""Get default rss hash algorithm capability and check for testpmd failure. + +Args: +supported_capabilities: Supported capabilities will be added to this set. +unsupported_capabilities: Unsupported capabilities will be added to this set. +""" +self.get_capabilities_rss_hash_algorithms( +"default", +NicCapability.RSS_HASH_DEFAULT, +supported_capabilities, +unsupported_capabilities, +) + +def get_capabilities_symmetric_toeplitz_sort_rss_hash_algorithms( +self, +supported_capabilities: MutableSet["NicCapability"], +unsupported_capabilities: MutableSet["NicCapability"], +) -> None: +"""Get symmetric_toeplitz_sort rss hash algorithm capability and check for testpmd failure. + +Args: +supported_capabilities: Supported capabilities will be added to this set. +unsupported_capabilities: Unsupported capabilities will be added to this set. +""" +self.get_capabilities_rss_hash_algorithms( +"symmetric_toeplitz_sort", +NicCapability.RSS_HASH_SYMMETRIC_TOEPLITZ_SORT, +supported_capabilities, +unsupported_capabilities, +) + +def get_capabilities_rss_hash_algorithms( +self, +algorithm: str, +NicCapability, +supported_capabilities: MutableSet["NicCapability"], +unsupported_c
[PATCH 1/2] dts: add packet capture test suite
Add a test suite that tests the packet capture framework through the use of dpdk-pdump. Signed-off-by: Thomas Wilks Reviewed-by: Luca Vizzarro --- .../dts/tests.TestSuite_packet_capture.rst| 8 + dts/tests/TestSuite_packet_capture.py | 358 ++ 2 files changed, 366 insertions(+) create mode 100644 doc/api/dts/tests.TestSuite_packet_capture.rst create mode 100644 dts/tests/TestSuite_packet_capture.py diff --git a/doc/api/dts/tests.TestSuite_packet_capture.rst b/doc/api/dts/tests.TestSuite_packet_capture.rst new file mode 100644 index 00..3d760d3ae4 --- /dev/null +++ b/doc/api/dts/tests.TestSuite_packet_capture.rst @@ -0,0 +1,8 @@ +.. SPDX-License-Identifier: BSD-3-Clause + +packet_capture Test Suite += + +.. automodule:: tests.TestSuite_packet_capture + :members: + :show-inheritance: diff --git a/dts/tests/TestSuite_packet_capture.py b/dts/tests/TestSuite_packet_capture.py new file mode 100644 index 00..bdc3d008a3 --- /dev/null +++ b/dts/tests/TestSuite_packet_capture.py @@ -0,0 +1,358 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright(c) 2025 Arm Limited + +"""Packet capture TestSuite. + +Tests pdump by sending packets, changing the arguments of pdump +and verifying the received packets. +""" + +from dataclasses import asdict, dataclass +from pathlib import Path, PurePath +from random import randint +from typing import Union + +from scapy.contrib.lldp import ( +LLDPDUChassisID, +LLDPDUEndOfLLDPDU, +LLDPDUPortID, +LLDPDUSystemCapabilities, +LLDPDUSystemDescription, +LLDPDUSystemName, +LLDPDUTimeToLive, +) +from scapy.layers.inet import IP, TCP, UDP +from scapy.layers.inet6 import IPv6 +from scapy.layers.l2 import Dot1Q, Ether +from scapy.layers.sctp import SCTP +from scapy.packet import Packet, Raw +from scapy.utils import rdpcap +from typing_extensions import Literal + +from framework.params.eal import EalParams +from framework.params.testpmd import PortTopology +from framework.remote_session.dpdk_app import BlockingDPDKApp +from framework.remote_session.testpmd_shell import TestPmdShell +from framework.settings import SETTINGS +from framework.test_suite import TestSuite, func_test +from framework.testbed_model.traffic_generator.capturing_traffic_generator import ( +PacketFilteringConfig, +) + + +@dataclass(order=True, kw_only=True) +class PdumpParameters: +"""Parameters for --pdump. + +Attributes: +device_id: The PCIE address of the device to capture packets. +port: The port id of the device to capture packets. +queue: The ID of the RX and TX queue. +tx_dev: The path or iface that the TX packets captured on TX will be outputted at. +rx_dev: The path or iface that the RX packets captured on RX will be outputted at. +ring_size: The size of the ring. +mbuf_size: The size of the mbuf data. +total_num_mbufs: Total number of the mbufs in the memory pool. +""" + +device_id: str | None = None +port: int | None = None +queue: Union[int, Literal["*"]] = "*" +tx_dev: PurePath | str | None = None +rx_dev: PurePath | str | None = None +ring_size: int | None = None +mbuf_size: int | None = None +total_num_mbufs: int | None = None + +def __str__(self) -> str: +"""Parameters as string for --pdump. + +Returns: +String of the pdump commands +""" + +def pair_to_str(field, value): +if field != "device_id": +field = field.replace("_", "-") +return f"{field}={value}" + +arg = ",".join( +[ +pair_to_str(field, value) +for field, value in asdict(self).items() +if value is not None +] +) + +return arg + + +class TestPacketCapture(TestSuite): +"""Packet Capture TestSuite. + +Attributes: +packets: List of packets to send for testing pdump. +rx_pcap_path: The remote path where to create the RX packets pcap with pdump. +tx_pcap_path: The remote path where to create the TX packets pcap with pdump. +pdump_params: The list of pdump parameters. +""" + +packets: list[Packet] +rx_pcap_path: PurePath +tx_pcap_path: PurePath +pdump_params: list[PdumpParameters] + +def set_up_suite(self) -> None: +"""Test suite setup. + +Prepare the packets, file paths and queue range to be used in the test suite. +""" +self.packets = [ +Ether() / IP() / Raw(b"\0" * 60), +Ether() / IP() / TCP() / Raw(b"\0" * 60), +Ether() / IP() / UDP() / Raw(b"\0" * 60), +
[PATCH 0/2] dts: add packet capture test suite
Hi, Sending this new test suite that tests the packet capture framework. Best regards, Thomas Depends-on: series-34865 ("dts: shell improvements") Thomas Wilks (2): dts: add packet capture test suite dts: import lldp package in scapy .../dts/tests.TestSuite_packet_capture.rst| 8 + .../testbed_model/traffic_generator/scapy.py | 1 + dts/tests/TestSuite_packet_capture.py | 357 ++ 3 files changed, 366 insertions(+) create mode 100644 doc/api/dts/tests.TestSuite_packet_capture.rst create mode 100644 dts/tests/TestSuite_packet_capture.py -- 2.43.0
[PATCH 2/2] dts: import lldp package in scapy
Add import for lldp scapy package to enable lldp packet creation and handling. Signed-off-by: Thomas Wilks Reviewed-by: Luca Vizzarro --- dts/framework/testbed_model/traffic_generator/scapy.py | 1 + 1 file changed, 1 insertion(+) diff --git a/dts/framework/testbed_model/traffic_generator/scapy.py b/dts/framework/testbed_model/traffic_generator/scapy.py index 78a6ded74c..c7e8fc42e9 100644 --- a/dts/framework/testbed_model/traffic_generator/scapy.py +++ b/dts/framework/testbed_model/traffic_generator/scapy.py @@ -95,6 +95,7 @@ def setup(self, ports: Iterable[Port]): self._tg_node.main_session.bring_up_link(ports) self._shell.start_application() self._shell.send_command("from scapy.all import *") +self._shell.send_command("from scapy.contrib.lldp import *") def close(self): """Close traffic generator.""" -- 2.43.0
[PATCH 0/2] dts: rework test results
Hi, This patch reworks DTS test run results. Regards, Thomas Luca Vizzarro (1): dts: change test suite name property Thomas Wilks (1): dts: rework test results dts/framework/runner.py | 33 +- dts/framework/test_result.py | 882 +-- dts/framework/test_run.py| 137 +-- dts/framework/test_suite.py | 7 +- dts/framework/testbed_model/posix_session.py | 4 +- 5 files changed, 342 insertions(+), 721 deletions(-) -- 2.43.0
[PATCH 2/2] dts: rework test results
Refactor the DTS result recording system to use a hierarchical tree structure based on `ResultNode` and `ResultLeaf`, replacing the prior flat model of DTSResult, TestRunResult, and TestSuiteResult. This improves clarity, composability, and enables consistent traversal and aggregation of test outcomes. Update all FSM states and the runner to build results directly into the tree, capturing setup, teardown, and test outcomes uniformly. Errors are now stored directly as exceptions and reduced into an exit code, and summaries are generated using Pydantic-based serializers for JSON and text output. Finally, a new textual result summary is generated showing the result of all the steps. Signed-off-by: Thomas Wilks Signed-off-by: Luca Vizzarro --- dts/framework/runner.py | 33 +- dts/framework/test_result.py | 882 +-- dts/framework/test_run.py| 137 +-- dts/framework/testbed_model/posix_session.py | 4 +- 4 files changed, 337 insertions(+), 719 deletions(-) diff --git a/dts/framework/runner.py b/dts/framework/runner.py index f20aa3576a..0a3d92b0c8 100644 --- a/dts/framework/runner.py +++ b/dts/framework/runner.py @@ -18,16 +18,10 @@ from framework.test_run import TestRun from framework.testbed_model.node import Node -from .config import ( -Configuration, -load_config, -) +from .config import Configuration, load_config from .logger import DTSLogger, get_dts_logger from .settings import SETTINGS -from .test_result import ( -DTSResult, -Result, -) +from .test_result import ResultNode, TestRunResult class DTSRunner: @@ -35,7 +29,7 @@ class DTSRunner: _configuration: Configuration _logger: DTSLogger -_result: DTSResult +_result: TestRunResult def __init__(self): """Initialize the instance with configuration, logger, result and string constants.""" @@ -54,7 +48,9 @@ def __init__(self): if not os.path.exists(SETTINGS.output_dir): os.makedirs(SETTINGS.output_dir) self._logger.add_dts_root_logger_handlers(SETTINGS.verbose, SETTINGS.output_dir) -self._result = DTSResult(SETTINGS.output_dir, self._logger) + +test_suites_result = ResultNode(label="test_suites") +self._result = TestRunResult(test_suites=test_suites_result) def run(self) -> None: """Run DTS. @@ -66,34 +62,30 @@ def run(self) -> None: try: # check the python version of the server that runs dts self._check_dts_python_version() -self._result.update_setup(Result.PASS) for node_config in self._configuration.nodes: nodes.append(Node(node_config)) -test_run_result = self._result.add_test_run(self._configuration.test_run) test_run = TestRun( self._configuration.test_run, self._configuration.tests_config, nodes, -test_run_result, +self._result, ) test_run.spin() except Exception as e: -self._logger.exception("An unexpected error has occurred.") +self._logger.exception("An unexpected error has occurred.", e) self._result.add_error(e) -# raise finally: try: self._logger.set_stage("post_run") for node in nodes: node.close() -self._result.update_teardown(Result.PASS) except Exception as e: -self._logger.exception("The final cleanup of nodes failed.") -self._result.update_teardown(Result.ERROR, e) +self._logger.exception("The final cleanup of nodes failed.", e) +self._result.add_error(e) # we need to put the sys.exit call outside the finally clause to make sure # that unexpected exceptions will propagate @@ -116,9 +108,6 @@ def _check_dts_python_version(self) -> None: def _exit_dts(self) -> None: """Process all errors and exit with the proper exit code.""" -self._result.process() - if self._logger: self._logger.info("DTS execution has ended.") - -sys.exit(self._result.get_return_code()) +sys.exit(self._result.process()) diff --git a/dts/framework/test_result.py b/dts/framework/test_result.py index 7f576022c7..8ce6cc8fbf 100644 --- a/dts/framework/test_result.py +++ b/dts/framework/test_result.py @@ -7,723 +7,323 @@ The results are recorded in a hierarchical manner: -* :class:`DTSResult` contains * :class:`TestRunResult` contains -* :class:`TestSuiteResult` contains -* :class:`TestCaseResult` +* :class:`ResultNode` may contain itself or +* :class:`Resul
[PATCH 1/2] dts: change test suite name property
From: Luca Vizzarro The test suite name property was previously returning the class name instead of the way that test suite are actually named, e.g. TestHelloWorld instead of hello_world. This change rectifies this inconsistency. Signed-off-by: Luca Vizzarro --- dts/framework/test_suite.py | 7 +-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/dts/framework/test_suite.py b/dts/framework/test_suite.py index 145b79496f..d4e06a567a 100644 --- a/dts/framework/test_suite.py +++ b/dts/framework/test_suite.py @@ -110,8 +110,11 @@ def __init__(self, config: BaseConfig): @property def name(self) -> str: -"""The name of the test suite class.""" -return type(self).__name__ +"""The name of the test suite.""" +module_prefix = ( + f"{TestSuiteSpec.TEST_SUITES_PACKAGE_NAME}.{TestSuiteSpec.TEST_SUITE_MODULE_PREFIX}" +) +return type(self).__module__[len(module_prefix) :] @property def topology(self) -> Topology: -- 2.43.0
[RFC PATCH v3 0/2] dts: add RSS functions and test suite
Hi all, This is v3 of the RSS test suites which has been rebased onto the latest patches. I’m also requesting comments and some help with an issue where the packet RSS queue and the predicted RSS queue differ when running on ConnectX-6 NICs while being the same on E810-C NICs. I’m currently in the process of refactoring the test suites and addressing the review comments from v2. Below is a summary of the changes made so far that are included in this version: Changes in v3: - Merged the RSS test suites into a single file for better maintainability. - Moved the supporting functions into the same test suite file and removed the previous separate function file. - Combined the reta_key_reta_queues test cases into a single test case. - Added new test cases to support flow rule creation on ConnectX-6 NICs. - Removed requirements for specific hashing algorithms which has been replaced the flow_validate function. Regards, Thomas Wilks Alex Chapman (1): dts: add RSS functions to testpmd Thomas Wilks (1): dts: add PMD RSS testsuite dts/framework/remote_session/testpmd_shell.py | 132 +++- dts/tests/TestSuite_pmd_rss.py| 610 ++ 2 files changed, 729 insertions(+), 13 deletions(-) create mode 100644 dts/tests/TestSuite_pmd_rss.py -- 2.43.0
[RFC PATCH v3 1/2] dts: add RSS functions to testpmd
From: Alex Chapman This patch adds the required functionality for the RSS key_update, RETA, and hash test suites. This includes: The setting of custom RETA values for routing packets to specific queues. The setting of the RSS mode on all ports, to specify how to hash the packets. The updating of the RSS hash key used during the hashing process. Alongside this, there is the addition of a __str__ method to the RSSOffloadTypesFlags class, so that when flag names are cast to a string they will use '-' as separators, instead of '_'. This allows them to be directly used within testpmd RSS commands without any further changes. Signed-off-by: Alex Chapman Signed-off-by: Thomas Wilks Reviewed-by: Paul Szczepanek Reviewed-by: Patrick Robb Tested-by: Patrick Robb --- dts/framework/remote_session/testpmd_shell.py | 132 -- 1 file changed, 119 insertions(+), 13 deletions(-) diff --git a/dts/framework/remote_session/testpmd_shell.py b/dts/framework/remote_session/testpmd_shell.py index ad8cb273dc..bc44027d0a 100644 --- a/dts/framework/remote_session/testpmd_shell.py +++ b/dts/framework/remote_session/testpmd_shell.py @@ -343,6 +343,12 @@ def make_parser(cls) -> ParserFn: RSSOffloadTypesFlag.from_list_string, ) +def __str__(self): +"""Replaces underscores with hyphens to produce valid testpmd value.""" +if self.name is None: +return "" +return self.name.replace("_", "-") + class DeviceCapabilitiesFlag(Flag): """Flag representing the device capabilities.""" @@ -648,7 +654,8 @@ class TestPmdPort(TextParser): ) #: Maximum number of VMDq pools max_vmdq_pools_num: int | None = field( -default=None, metadata=TextParser.find_int(r"Maximum number of VMDq pools: (\d+)") +default=None, +metadata=TextParser.find_int(r"Maximum number of VMDq pools: (\d+)"), ) #: @@ -1480,7 +1487,9 @@ def _wrapper(self: "TestPmdShell", *args: P.args, **kwargs: P.kwargs): return _wrapper -def add_remove_mtu(mtu: int = 1500) -> Callable[[TestPmdShellMethod], TestPmdShellMethod]: +def add_remove_mtu( +mtu: int = 1500, +) -> Callable[[TestPmdShellMethod], TestPmdShellMethod]: """Configure MTU to `mtu` on all ports, run the decorated function, then revert. Args: @@ -1734,6 +1743,82 @@ def close_all_ports(self, verify: bool = True) -> None: if not all(f"Port {p_id} is closed" in port_close_output for p_id in range(num_ports)): raise InteractiveCommandExecutionError("Ports were not closed successfully.") +def port_config_rss_reta( +self, port_id: int, hash_index: int, queue_id: int, verify: bool = True +) -> None: +"""Configure a port's RSS redirection table. + +Args: +port_id: The port where the redirection table will be configured. +hash_index: The index into the redirection table associated with the destination queue. +queue_id: The destination queue of the packet. +verify: If :data:`True`, verifies if a port's redirection table +was correctly configured. + +Raises: +InteractiveCommandExecutionError: If `verify` is :data:`True` +Testpmd failed to config RSS reta. +""" +out = self.send_command(f"port config {port_id} rss reta ({hash_index},{queue_id})") +if verify: +if f"The reta size of port {port_id} is" not in out: +self._logger.debug(f"Failed to config RSS reta: \n{out}") +raise InteractiveCommandExecutionError("Testpmd failed to config RSS reta.") + +def port_config_all_rss_offload_type( +self, flag: RSSOffloadTypesFlag, verify: bool = True +) -> None: +"""Set the RSS mode on all ports. + +Args: +flag: The RSS iptype all ports will be configured to. +verify: If :data:`True`, it verifies if all ports RSS offload type +was correctly configured. + +Raises: +InteractiveCommandExecutionError: If `verify` is :data:`True` +Testpmd failed to config the RSS mode on all ports. +""" +out = self.send_command(f"port config all rss {flag.name}") +if verify: +if "error" in out: +self._logger.debug(f"Failed to config the RSS mode on all ports: \n{out}") +raise InteractiveCommandExecutionError( +f"Testpmd failed to change RSS mode to {flag.name}" +) + +def port_config_rss_hash_key( +self, +p
[RFC PATCH v3 2/2] dts: add PMD RSS testsuite
Port over the rss_key_update, pmd_rss_reta and pmd_rss_hash test suites from old DTS into one file including all of the helper functions that are required by all of the test suites to work. The rss_key_update test cases verify that setting a new hash key when Receive Side Scaling (RSS) will result in a change in the packets destination queue. These test cases also verifies that the reported key size of the NIC is correct. The pmd_rss_reta test cases verify that Redirection Tables (RETAs) of different sizes function correctly in RSS. These test cases also verify that the reported reta size of the NIC is correct. The pmd_rss_hash test cases verify that the 4 supported types of hashing algorithm used in RSS function correctly and that the nic supports them. The four hashing algorithms being DEFAULT, TOEPLITZ, SYMMETRIC_TOEPLITZ and SIMPLE_XOR. These test cases also verify that the supported hashing algorithms reported by the NIC are correct. Signed-off-by: Alex Chapman Signed-off-by: Thomas Wilks --- dts/tests/TestSuite_pmd_rss.py | 610 + 1 file changed, 610 insertions(+) create mode 100644 dts/tests/TestSuite_pmd_rss.py diff --git a/dts/tests/TestSuite_pmd_rss.py b/dts/tests/TestSuite_pmd_rss.py new file mode 100644 index 00..8ca78c1e43 --- /dev/null +++ b/dts/tests/TestSuite_pmd_rss.py @@ -0,0 +1,610 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright(c) 2025 Arm Limited + +"""RSS testing suite. + +This TestSuite tests the following. + +RSS Hash Test Cases: +Hashing algorithms are used in conjunction with a RSS hash keys to hash packets. +This test suite verifies that updating the Hashing algorithms will +continue to correctly hash packets. + +Symmetric_toeplitz_sort hasn't been included due to it not being supported by +the rss func actions in the flow rule. + +RSS Key Update Test Cases: +RSS hash keys are used in conjunction with a hashing algorithm to hash packets. +This test suite verifies that updating the RSS hash key will change the hash +generated if the hashing algorithm stays the same. + +RSS RETA (redirection table) Test Cases: +The RETA is used in RSS to redirect packets to different queues based on the +least significant bits of the packets hash. +This suite tests updating the size of the RETA and verifying the reported RETA size. +""" + +import random + +from scapy.layers.inet import IP, UDP +from scapy.layers.l2 import Ether + +from framework.exception import InteractiveCommandExecutionError +from framework.params.testpmd import SimpleForwardingModes +from framework.remote_session.testpmd_shell import ( +FlowRule, +RSSOffloadTypesFlag, +TestPmdShell, +TestPmdVerbosePacket, +) +from framework.test_suite import BaseConfig, TestSuite, func_test +from framework.testbed_model.capability import requires +from framework.testbed_model.topology import TopologyType +from framework.utils import StrEnum + + +class Config(BaseConfig): +"""Default configuration for Per Test Suite config.""" + +NUM_QUEUES: int = 4 + +ACTUAL_KEY_SIZE: int = 52 + +ACTUAL_RETA_SIZE: int = 512 + + +class HashAlgorithm(StrEnum): +"""Enum of hashing algorithms.""" + +DEFAULT = "default" +SIMPLE_XOR = "simple_xor" +TOEPLITZ = "toeplitz" +SYMMETRIC_TOEPLITZ = "symmetric_toeplitz" + + +@requires(topology_type=TopologyType.one_link) +class TestPmdRss(TestSuite): +"""PMD RSS test suite. + +Hash: +Verifies the redirection table when updating the size. +The suite contains four tests, one for each hash algorithms. +Configure the redirection table to have random entries. + +Update: +Create a flow rule so ipv4-udp packets utilize the desired RSS hash algorithm. +Send packets, and verify they are in the desired queue based on the redirection table. +Update the RSS hash key. +Send packets, and verify they are in the desired queue based on the redirection table. +Verify the packet hashes before and after the key update are different. + +RSS: +Verifies the redirection table when updating the size. +The suite contains four tests, three for different RETA sizes +and one for verifying the reported RETA size. +""" + +config: Config + +def set_up_suite(self): +"""Generates the queues for the flow rule.""" +self.queue = ( +str([x for x in range(self.config.NUM_QUEUES)]) +.replace(",", "") +.replace("[", "") +.replace("]", "") +) + +def verify_hash_queue( +self, +reta: list[int], +received_packets: list[TestPmdVerbosePacket], +
[PATCH v4 1/2] dts: add RSS functions to testpmd
From: Alex Chapman This patch adds the required functionality for the RSS key_update, RETA, and hash test suites. This includes: The setting of custom RETA values for routing packets to specific queues. The setting of the RSS mode on all ports, to specify how to hash the packets. The updating of the RSS hash key used during the hashing process. Alongside this, there is the addition of a __str__ method to the RSSOffloadTypesFlags class, so that when flag names are cast to a string they will use '-' as separators, instead of '_'. This allows them to be directly used within testpmd RSS commands without any further changes. Signed-off-by: Alex Chapman Signed-off-by: Thomas Wilks Reviewed-by: Paul Szczepanek Reviewed-by: Patrick Robb Tested-by: Patrick Robb --- dts/framework/remote_session/testpmd_shell.py | 104 -- 1 file changed, 97 insertions(+), 7 deletions(-) diff --git a/dts/framework/remote_session/testpmd_shell.py b/dts/framework/remote_session/testpmd_shell.py index ad8cb273dc..47f8fb 100644 --- a/dts/framework/remote_session/testpmd_shell.py +++ b/dts/framework/remote_session/testpmd_shell.py @@ -343,6 +343,12 @@ def make_parser(cls) -> ParserFn: RSSOffloadTypesFlag.from_list_string, ) +def __str__(self): +"""Replaces underscores with hyphens to produce valid testpmd value.""" +if self.name is None: +return "" +return self.name.replace("_", "-") + class DeviceCapabilitiesFlag(Flag): """Flag representing the device capabilities.""" @@ -644,11 +650,13 @@ class TestPmdPort(TextParser): ) #: Maximum number of VFs max_vfs_num: int | None = field( -default=None, metadata=TextParser.find_int(r"Maximum number of VFs: (\d+)") +default=None, +metadata=TextParser.find_int(r"Maximum number of VFs: (\d+)"), ) #: Maximum number of VMDq pools max_vmdq_pools_num: int | None = field( -default=None, metadata=TextParser.find_int(r"Maximum number of VMDq pools: (\d+)") +default=None, +metadata=TextParser.find_int(r"Maximum number of VMDq pools: (\d+)"), ) #: @@ -1734,6 +1742,82 @@ def close_all_ports(self, verify: bool = True) -> None: if not all(f"Port {p_id} is closed" in port_close_output for p_id in range(num_ports)): raise InteractiveCommandExecutionError("Ports were not closed successfully.") +def port_config_rss_reta( +self, port_id: int, hash_index: int, queue_id: int, verify: bool = True +) -> None: +"""Configure a port's RSS redirection table. + +Args: +port_id: The port where the redirection table will be configured. +hash_index: The index into the redirection table associated with the destination queue. +queue_id: The destination queue of the packet. +verify: If :data:`True`, verifies if a port's redirection table +was correctly configured. + +Raises: +InteractiveCommandExecutionError: If `verify` is :data:`True` +Testpmd failed to config RSS reta. +""" +out = self.send_command(f"port config {port_id} rss reta ({hash_index},{queue_id})") +if verify: +if f"The reta size of port {port_id} is" not in out: +self._logger.debug(f"Failed to config RSS reta: \n{out}") +raise InteractiveCommandExecutionError("Testpmd failed to config RSS reta.") + +def port_config_all_rss_offload_type( +self, flag: RSSOffloadTypesFlag, verify: bool = True +) -> None: +"""Set the RSS mode on all ports. + +Args: +flag: The RSS iptype all ports will be configured to. +verify: If :data:`True`, it verifies if all ports RSS offload type +was correctly configured. + +Raises: +InteractiveCommandExecutionError: If `verify` is :data:`True` +Testpmd failed to config the RSS mode on all ports. +""" +out = self.send_command(f"port config all rss {flag.name}") +if verify: +if "error" in out: +self._logger.debug(f"Failed to config the RSS mode on all ports: \n{out}") +raise InteractiveCommandExecutionError( +f"Testpmd failed to change RSS mode to {flag.name}" +) + +def port_config_rss_hash_key( +self, +port_id: int, +offload_type: RSSOffloadTypesFlag, +hex_str: str, +verify: bool = True, +) -> str: +"
[PATCH v4 2/2] dts: add PMD RSS testsuite
Port over the rss_key_update, pmd_rss_reta and pmd_rss_hash test suites from old DTS into one file including all of the helper functions that are required by all of the test suites to work. The rss_key_update test cases verify that setting a new hash key when Receive Side Scaling (RSS) will result in a change in the packets destination queue. These test cases also verifies that the reported key size of the NIC is correct. The pmd_rss_reta test cases verify that Redirection Tables (RETAs) of different sizes function correctly in RSS. These test cases also verify that the reported reta size of the NIC is correct. The pmd_rss_hash test case verifies that the 4 supported types of hashing algorithm used in RSS function correctly and that the nic supports them. The four hashing algorithms being DEFAULT, TOEPLITZ, SYMMETRIC_TOEPLITZ and SIMPLE_XOR. These test cases also verify that the supported hashing algorithms reported by the NIC are correct. Signed-off-by: Thomas Wilks --- dts/tests/TestSuite_pmd_rss.py | 383 + 1 file changed, 383 insertions(+) create mode 100644 dts/tests/TestSuite_pmd_rss.py diff --git a/dts/tests/TestSuite_pmd_rss.py b/dts/tests/TestSuite_pmd_rss.py new file mode 100644 index 00..4c1b026307 --- /dev/null +++ b/dts/tests/TestSuite_pmd_rss.py @@ -0,0 +1,383 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright(c) 2025 Arm Limited + +"""RSS testing suite. + +Tests different hashing algorithms by checking if packets are routed to correct queues. +Tests updating the RETA (Redirection Table) key to verify it takes effect and follows +set size constraints. +Tests RETA behavior under changing number of queues. +""" + +import random + +from scapy.layers.inet import IP, UDP +from scapy.layers.l2 import Ether + +from framework.exception import InteractiveCommandExecutionError +from framework.params.testpmd import SimpleForwardingModes +from framework.remote_session.testpmd_shell import ( +FlowRule, +RSSOffloadTypesFlag, +TestPmdShell, +TestPmdVerbosePacket, +) +from framework.test_suite import BaseConfig, TestSuite, func_test +from framework.testbed_model.capability import requires +from framework.testbed_model.topology import TopologyType +from framework.utils import StrEnum + + +class Config(BaseConfig): +"""Default configuration for Per Test Suite config.""" + +NUM_QUEUES: int = 4 + +ACTUAL_KEY_SIZE: int = 52 + +ACTUAL_RETA_SIZE: int = 512 + + +class HashAlgorithm(StrEnum): +"""Enum of hashing algorithms.""" + +DEFAULT = "default" +SIMPLE_XOR = "simple_xor" +TOEPLITZ = "toeplitz" +SYMMETRIC_TOEPLITZ = "symmetric_toeplitz" + + +@requires(topology_type=TopologyType.one_link) +class TestPmdRss(TestSuite): +"""PMD RSS test suite.""" + +config: Config + +def verify_hash_queue( +self, +reta: list[int], +received_packets: list[TestPmdVerbosePacket], +verify_packet_pairs: bool, +) -> None: +"""Verifies the packet hash corresponds to the packet queue. + +Given the received packets in the verbose output, iterate through each packet. +Use the hash to index into RETA and get its intended queue. +Verify the intended queue is the same as the actual queue the packet was received in. +If the hash algorithm is symmetric, verify that pairs of packets have the same hash, +as the pairs of packets sent have "mirrored" L4 ports. +e.g. received_packets[0, 1, 2, 3, ...] hash(0) = hash(1), hash(2) = hash(3), ... + +Args: +reta: Used to get predicted queue based on hash. +received_packets: Packets received in the verbose output of testpmd. +verify_packet_pairs: Verify pairs of packets have the same hash. + +Raises: +InteractiveCommandExecutionError: If packet_hash is None. +""" +# List of packet hashes, used for symmetric algorithms +hash_list = [] +for packet in received_packets: +# Ignore stray packets +if packet.port_id != 0 or packet.src_mac != "02:00:00:00:00:00": +continue +# Get packet hash +packet_hash = packet.rss_hash +if packet_hash is None: +raise InteractiveCommandExecutionError( +"Packet sent by the Traffic Generator has no RSS hash attribute." +) + +packet_queue = packet.rss_queue + +# Calculate the predicted packet queue +predicted_queue = reta[packet_hash % len(reta)] +self.verify( +predicted_queue == packet_queue, +"Packet sent by the Traffic Generator assigned to inco
[PATCH v4 0/2] dts: add RSS functions and test suite
Hi all, This is v4 of the RSS test suites which addresses the issues with running the hashing algorithm test cases on ConnectX nics. The patches have also been rebased onto the latest upstream changes. Also thank you for your help and feedback with my previous RFC patch. Changes in v4: - Merged the RSS hashing algorithms tests into a single test case. - Resolved issues with packet RSS queues when being ran on ConnectX nics. - Updated doc strings to better reflect the test suites functionality. - Moved RETA configuration into a dedicated function. Regards, Thomas Wilks Alex Chapman (1): dts: add RSS functions to testpmd Thomas Wilks (1): dts: add PMD RSS testsuite dts/framework/remote_session/testpmd_shell.py | 104 - dts/tests/TestSuite_pmd_rss.py| 383 ++ 2 files changed, 480 insertions(+), 7 deletions(-) create mode 100644 dts/tests/TestSuite_pmd_rss.py -- 2.43.0