DPDK patches and discussions
 help / color / mirror / Atom feed
From: Ivan Malov <ivan.malov@arknetworks.am>
To: Thomas Wilks <thomas.wilks@arm.com>
Cc: dev@dpdk.org, Paul Szczepanek <paul.szczepanek@arm.com>,
	 Luca Vizzarro <luca.vizzarro@arm.com>,
	Patrick Robb <probb@iol.unh.edu>,  Dean Marx <dmarx@iol.unh.edu>
Subject: Re: [PATCH v4 2/2] dts: add PMD RSS testsuite
Date: Wed, 30 Jul 2025 19:19:35 +0400 (+04)	[thread overview]
Message-ID: <ca9ebc2b-13b0-e955-b802-fc2f9f24b186@arknetworks.am> (raw)
In-Reply-To: <20250730125859.159185-3-thomas.wilks@arm.com>

Hi Thomas,

On Wed, 30 Jul 2025, Thomas Wilks wrote:

> 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.

This is a huge improvement, and the patch is quite neat.

Apparently, the code does not consider cases where 'l3-dst-only, 'l4-dst-only'
and similar flags get requested along with 'ipv4-udp'. Is it worth considering?

Also, the test doesn't seem to validate the literal value of the 32-bit hash to
make sure the predicted value matches the one in the packet's metadata. Though,
I admit adding such coverage on the hoof might not be good and I don't insist.

The patch is probably good as it is.

Thank you.

>
> Signed-off-by: Thomas Wilks <thomas.wilks@arm.com>
> ---
> 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 0000000000..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 incorrect queue by the RSS.",
> +            )
> +
> +            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]):
> +                self.verify(
> +                    odd_hash == even_hash,
> +                    "Packet pair do not have same hash. Hash algorithm is not symmetric.",
> +                )
> +
> +    def send_test_packets(
> +        self,
> +        testpmd: TestPmdShell,
> +        send_additional_mirrored_packet: bool = False,
> +    ) -> 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:
> +            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(
> +                Ether(src="02:00:00:00:00:00", dst="11:00:00:00:00:00")
> +                / IP()
> +                / UDP(sport=i, dport=i + 1),
> +            )
> +            if send_additional_mirrored_packet:  # If symmetric, send the inverse packets
> +                packets.append(
> +                    Ether(src="02:00:00:00:00:00", dst="11:00:00:00:00:00")
> +                    / IP()
> +                    / UDP(sport=i + 1, dport=i),
> +                )
> +
> +        # Set verbose packet information and start packet capture
> +        testpmd.set_verbose(level=3)
> +        testpmd.start()
> +        testpmd.start_all_ports()
> +        self.send_packets_and_capture(packets)
> +
> +        # Stop packet capture and revert verbose packet information
> +        testpmd_shell_out = testpmd.stop()
> +        testpmd.set_verbose(level=0)
> +        # Parse the packets and return them
> +        return testpmd.extract_verbose_output(testpmd_shell_out)
> +
> +    def setup_rss_environment(
> +        self,
> +        testpmd: TestPmdShell,
> +    ) -> None:
> +        """Sets up the testpmd environment for RSS test suites.
> +
> +        Sets the testpmd forward mode to rx_only and RSS on the NIC to UDP.
> +
> +        Args:
> +            testpmd: Where the environment will be set.
> +        """
> +        # Set forward mode to receive only, to remove forwarded packets from verbose output
> +        testpmd.set_forward_mode(SimpleForwardingModes.rxonly)
> +
> +        # Reset RSS settings and only RSS udp packets
> +        testpmd.port_config_all_rss_offload_type(RSSOffloadTypesFlag.udp)
> +
> +    def configure_random_reta(self, testpmd: TestPmdShell, queue_number: int) -> list[int]:
> +        """Configure RETA to have random order of queues.
> +
> +        Args:
> +            testpmd: The testpmd instance that will be used to set the rss environment.
> +            queue_number: Number of queues that will be randomly inserted into the RETA.
> +
> +        Returns:
> +            List of ids matching the configured RETA table
> +
> +        Raises:
> +            InteractiveCommandExecutionError: If size of RETA table for driver is None.
> +        """
> +        reta_size = testpmd.show_port_info(port_id=0).redirection_table_size
> +        if reta_size is None:
> +            raise InteractiveCommandExecutionError("Size of RETA table for driver is None.")
> +        reta_table: list[int] = []
> +
> +        for i in range(reta_size):
> +            random_id = random.randint(0, queue_number - 1)
> +            reta_table.insert(i, random_id)
> +            testpmd.port_config_rss_reta(port_id=0, hash_index=i, queue_id=random_id)
> +        return reta_table
> +
> +    def verify_rss_hash_function(
> +        self,
> +        testpmd: TestPmdShell,
> +        hash_algorithm: HashAlgorithm,
> +        flow_rule: FlowRule,
> +        reta: list[int],
> +    ) -> None:
> +        """Verifies hash function are working by sending test packets and checking the packet queue.
> +
> +        Args:
> +            testpmd: The testpmd instance that will be used to set the rss environment.
> +            hash_algorithm: The hash algorithm to be tested.
> +            flow_rule: The flow rule that is to be validated and then created.
> +            reta: Will be used to calculate the predicted packet queues.
> +        """
> +        is_symmetric = hash_algorithm == HashAlgorithm.SYMMETRIC_TOEPLITZ
> +        self.setup_rss_environment(testpmd)
> +        testpmd.flow_create(flow_rule, port_id=0)
> +        # Send udp packets and ensure hash corresponds with queue
> +        parsed_output = self.send_test_packets(
> +            testpmd, send_additional_mirrored_packet=is_symmetric
> +        )
> +        self.verify_hash_queue(reta, parsed_output, is_symmetric)
> +
> +    @func_test
> +    def test_key_hash_algorithm(self) -> None:
> +        """Hashing algorithm test.
> +
> +        Steps:
> +            Setup RSS environment using the chosen algorithm.
> +            Send test packets for each flow rule.
> +
> +        Verify:
> +            Packet hash corresponds to the packet queue.
> +
> +        Raises:
> +            InteractiveCommandExecutionError: If size of RETA table for driver is None.
> +            InteractiveCommandExecutionError: If there are no valid flow rules that can be created.
> +        """
> +        failed_attempts: int = 0
> +        for algorithm in HashAlgorithm:
> +            flow_rule = FlowRule(
> +                group_id=0,
> +                direction="ingress",
> +                pattern=["eth / ipv4 / udp"],
> +                actions=[f"rss types ipv4-udp end queues end func {algorithm.name.lower()}"],
> +            )
> +            with TestPmdShell(
> +                rx_queues=self.config.NUM_QUEUES,
> +                tx_queues=self.config.NUM_QUEUES,
> +            ) as testpmd:
> +                reta_table = self.configure_random_reta(testpmd, self.config.NUM_QUEUES)
> +
> +                if not testpmd.flow_validate(flow_rule, port_id=0):
> +                    # Queues need to be specified in the flow rule on some NICs
> +                    queue_ids = " ".join([str(x) for x in reta_table])
> +                    flow_rule.actions = [
> +                        f"rss types ipv4-udp end queues {queue_ids} end func "
> +                        + algorithm.name.lower()
> +                    ]
> +
> +                    if not testpmd.flow_validate(flow_rule, port_id=0):
> +                        failed_attempts += 1
> +                        if failed_attempts == len(HashAlgorithm):
> +                            raise InteractiveCommandExecutionError(
> +                                "No Valid flow rule could be created."
> +                            )
> +                        # if neither rule format is valid then the algorithm is not supported,
> +                        # move to next one
> +                        continue
> +                self.verify_rss_hash_function(testpmd, algorithm, flow_rule, reta_table)
> +
> +    @func_test
> +    def test_update_key_set_hash_key_short_long(self) -> None:
> +        """Set hash key short long test.
> +
> +        Steps:
> +            Fetch the hash key size.
> +            Create two random hash keys one key too short and one too long.
> +
> +        Verify:
> +            Verify that it is not possible to set the shorter hash key.
> +            Verify that it is not possible to set the longer hash key.
> +
> +        Raises:
> +            InteractiveCommandExecutionError: If port info dose not contain hash key size.
> +        """
> +        with TestPmdShell(
> +            memory_channels=4,
> +            rx_queues=self.config.NUM_QUEUES,
> +            tx_queues=self.config.NUM_QUEUES,
> +        ) as testpmd:
> +            # Get RETA and key size
> +            port_info = testpmd.show_port_info(port_id=0)
> +
> +            # Get hash key size
> +            key_size = port_info.hash_key_size
> +            if key_size is None:
> +                raise InteractiveCommandExecutionError("Port info does not contain hash key size.")
> +
> +            # Create 2 hash keys based on the NIC capabilities
> +            short_key = "".join(
> +                [random.choice("0123456789ABCDEF") for n in range(key_size * 2 - 2)]
> +            )
> +            long_key = "".join([random.choice("0123456789ABCDEF") for n in range(key_size * 2 + 2)])
> +
> +            # Verify a short key cannot be set
> +            short_key_out = testpmd.port_config_rss_hash_key(
> +                0, RSSOffloadTypesFlag.ipv4_udp, short_key, False
> +            )
> +            self.verify(
> +                "invalid" in short_key_out,
> +                "Able to set hash key shorter than specified.",
> +            )
> +
> +            # Verify a long key cannot be set
> +            long_key_out = testpmd.port_config_rss_hash_key(
> +                0, RSSOffloadTypesFlag.ipv4_udp, long_key, False
> +            )
> +            self.verify("invalid" in long_key_out, "Able to set hash key longer than specified.")
> +
> +    @func_test
> +    def test_update_key_reported_key_size(self) -> None:
> +        """Verify reported hash key size is the same as the NIC capabilities.
> +
> +        Steps:
> +            Fetch the hash key size and compare to the actual key size.
> +
> +        Verify:
> +            Reported key size is the same as the actual key size.
> +        """
> +        with TestPmdShell() as testpmd:
> +            reported_key_size = testpmd.show_port_info(port_id=0).hash_key_size
> +            self.verify(
> +                reported_key_size == self.config.ACTUAL_KEY_SIZE,
> +                "Reported key size is not the same as the config file.",
> +            )
> +
> +    @func_test
> +    def test_reta_key_reta_queues(self) -> None:
> +        """RETA rx/tx queues test.
> +
> +        Steps:
> +            For each queue size setup RSS environment and send Test packets.
> +
> +        Verify:
> +            Packet hash corresponds to hash queue.
> +
> +        Raises:
> +            InteractiveCommandExecutionError: If size of RETA table for driver is None.
> +        """
> +        queues_numbers = [2, 9, 16]
> +        for queue_number in queues_numbers:
> +            with TestPmdShell(
> +                rx_queues=queue_number,
> +                tx_queues=queue_number,
> +            ) as testpmd:
> +                # Configure the RETA with random queues
> +                reta = self.configure_random_reta(testpmd, queue_number)
> +
> +                self.setup_rss_environment(testpmd)
> +
> +                # Send UDP packets and ensure hash corresponds with queue
> +                parsed_output = self.send_test_packets(testpmd)
> +                self.verify_hash_queue(reta, parsed_output, False)
> +
> +    @func_test
> +    def test_reta_key_reported_reta_size(self) -> None:
> +        """Reported RETA size test.
> +
> +        Steps:
> +            Fetch reported reta size.
> +
> +        Verify:
> +            Reported RETA size is equal to the actual RETA size.
> +        """
> +        with TestPmdShell(
> +            rx_queues=self.config.NUM_QUEUES,
> +            tx_queues=self.config.NUM_QUEUES,
> +        ) as testpmd:
> +            reported_reta_size = testpmd.show_port_info(port_id=0).redirection_table_size
> +            self.verify(
> +                reported_reta_size == self.config.ACTUAL_RETA_SIZE,
> +                "Reported RETA size is not the same as the config file.",
> +            )
> -- 
> 2.43.0
>
>

      reply	other threads:[~2025-07-30 15:19 UTC|newest]

Thread overview: 23+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-08-29 12:50 [PATCH] dts: add RSS functions to testpmd Alex Chapman
2024-09-06 14:29 ` Juraj Linkeš
2025-02-25 15:33 ` [PATCH v2 0/6] Added RSS functions and tests Thomas Wilks
2025-02-25 15:33   ` [PATCH v2 1/6] dts: add RSS functions to testpmd Thomas Wilks
2025-04-14  3:11     ` Patrick Robb
2025-02-25 15:33   ` [PATCH v2 2/6] dts: add utils for PMD RSS testsuites Thomas Wilks
2025-04-14  3:11     ` Patrick Robb
2025-02-25 15:33   ` [PATCH v2 3/6] dts: add PMD RSS hash testsuite Thomas Wilks
2025-04-14  3:30     ` Patrick Robb
2025-02-25 15:33   ` [PATCH v2 4/6] dts: add PMD RSS RETA testsuite Thomas Wilks
2025-02-25 15:33   ` [PATCH v2 5/6] dts: add PMD RSS key update testsuite Thomas Wilks
2025-02-25 15:33   ` [PATCH v2 6/6] dts: add NIC capabilities for hash algorithms Thomas Wilks
2025-07-18 15:03   ` [RFC PATCH v3 0/2] dts: add RSS functions and test suite Thomas Wilks
2025-07-18 15:04     ` [RFC PATCH v3 1/2] dts: add RSS functions to testpmd Thomas Wilks
2025-07-18 15:04     ` [RFC PATCH v3 2/2] dts: add PMD RSS testsuite Thomas Wilks
2025-07-18 18:37       ` Dean Marx
2025-07-20  8:00         ` Ivan Malov
2025-07-18 17:00     ` [RFC PATCH v3 0/2] dts: add RSS functions and test suite Ivan Malov
2025-07-18 18:22       ` Ivan Malov
2025-07-30 12:58     ` [PATCH v4 " Thomas Wilks
2025-07-30 12:58       ` [PATCH v4 1/2] dts: add RSS functions to testpmd Thomas Wilks
2025-07-30 12:58       ` [PATCH v4 2/2] dts: add PMD RSS testsuite Thomas Wilks
2025-07-30 15:19         ` Ivan Malov [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=ca9ebc2b-13b0-e955-b802-fc2f9f24b186@arknetworks.am \
    --to=ivan.malov@arknetworks.am \
    --cc=dev@dpdk.org \
    --cc=dmarx@iol.unh.edu \
    --cc=luca.vizzarro@arm.com \
    --cc=paul.szczepanek@arm.com \
    --cc=probb@iol.unh.edu \
    --cc=thomas.wilks@arm.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).