[PATCH v2] dts: add l2fwd test suite

2024-10-30 Thread Thomas Wilks
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

2024-12-10 Thread Thomas Wilks
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

2024-12-10 Thread Thomas Wilks
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

2024-12-12 Thread Thomas Wilks
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

2024-12-12 Thread Thomas Wilks
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

2024-12-12 Thread Thomas Wilks
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

2025-01-16 Thread Thomas Wilks
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

2025-01-16 Thread Thomas Wilks
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

2025-01-16 Thread Thomas Wilks
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

2025-01-09 Thread Thomas Wilks
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

2025-01-09 Thread Thomas Wilks
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

2025-01-09 Thread Thomas Wilks
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

2025-04-15 Thread Thomas Wilks
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

2025-04-17 Thread Thomas Wilks
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

2025-04-15 Thread Thomas Wilks
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

2025-04-15 Thread Thomas Wilks
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.

2025-02-25 Thread Thomas Wilks
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

2025-02-25 Thread Thomas Wilks
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

2025-02-25 Thread Thomas Wilks
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

2025-02-25 Thread Thomas Wilks
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

2025-02-25 Thread Thomas Wilks
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

2025-02-25 Thread Thomas Wilks
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

2025-02-25 Thread Thomas Wilks
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

2025-03-31 Thread Thomas Wilks
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

2025-03-31 Thread Thomas Wilks
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

2025-03-31 Thread Thomas Wilks
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

2025-06-27 Thread Thomas Wilks
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

2025-06-27 Thread Thomas Wilks
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

2025-06-27 Thread Thomas Wilks
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

2025-07-18 Thread Thomas Wilks
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

2025-07-18 Thread Thomas Wilks
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

2025-07-18 Thread Thomas Wilks
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

2025-07-30 Thread Thomas Wilks
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

2025-07-30 Thread Thomas Wilks
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

2025-07-30 Thread Thomas Wilks
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