On Wed, Jul 30, 2025 at 8:59 AM Thomas Wilks <thomas.wi...@arm.com> wrote:

>
> +    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)
>

I just did my testrun with a connectx5 NIC. It looks like it returned 4 for
redirection_table_size.

2025/08/25 22:04:04 - test_case - dts.lucian-cx5-SUT.TestPmdShell - INFO -
Sending: 'port config 0 rss reta (0,2)'
2025/08/25 22:04:04 - test_case - dts.lucian-cx5-SUT.TestPmdShell - INFO -
Sending: 'port config 0 rss reta (1,1)'
2025/08/25 22:04:04 - test_case - dts.lucian-cx5-SUT.TestPmdShell - INFO -
Sending: 'port config 0 rss reta (2,0)'
2025/08/25 22:04:04 - test_case - dts.lucian-cx5-SUT.TestPmdShell - INFO -
Sending: 'port config 0 rss reta (3,2)'

So, if the default queue size is 4, and the redirection_table_size is 4 in
this case, then there is a 1/(4^3), or 1/64 chance that all of the reta
table entries will be directed to the same queue. Should we enforce some
diversity of queues in the implementation?

I guess in principle even if all the hashes get associated with the same
queue the test would still be valid (it would show that all the packets get
directed to the same queue) but that seems moreso like testing an edge case
(which again, is valid) rather than testing the primary use case of RSS.
Another strategy might be like:

test1: enforce diversity of queue destinations for different hashes via the
implementation
test2: set random queue destinations for each hash (your current strategy)
test3: set the same queue destination for all hashes

What are your thoughts on this? And let me know if I'm misunderstanding
your strategy in any way.

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

Test should have lowercase t


> +
> +        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:
>

I am a little confused by how this ran - maybe you can help. :)

The first iteration through, the reta size was 2, and it used a queue size
of 2.
The second iteration through, the reta size was 512, and the queue size was
9.
The third iteration through, the reta size was 16 and the queue size was 16.

Is this what you expect? I can't quite tell why the reta size was 512 in
the second case. If it matters, I didn't provide any tests_config.yaml for
this testsuite, so I was running from the default config.


> +            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.",
>

If we are going to have such a test I think this verify fail message should
be an f string which inserts the reported reta size and the reta size from
the config file. I think that's an improvement in general, and it also will
help with the fact that some users may not be accustomed to usage of
tests_config.yaml (yet).

At the same time, I want to thank you for making effective use of
tests_config in this testsuite. :)


> +            )
> --
> 2.43.0
>
>
Thanks a bunch Thomas - this looks good overall. Let me know what your
thoughts are on the comments and otherwise I think we are close to pushing
this.

Reply via email to