xref: /dpdk/dts/tests/TestSuite_dynamic_queue_conf.py (revision 2eef9a80df4a2662f60ae313d779ef1e02227844)
1# SPDX-License-Identifier: BSD-3-Clause
2# Copyright(c) 2024 University of New Hampshire
3
4"""Dynamic configuration of port queues test suite.
5
6This test suite tests the support of being able to either stop or reconfigure port queues at
7runtime without stopping the entire device. Previously, to configure a DPDK ethdev, the application
8first specifies how many Tx and Rx queues to include in the ethdev and then application sets up
9each queue individually. Only once all the queues have been set up can the application then start
10the device, and at this point traffic can flow. If device stops, this halts the flow of traffic on
11all queues in the ethdev completely. Dynamic queue is a capability present on some NICs that
12specifies whether the NIC is able to delay the configuration of queues on its port. This capability
13allows for the support of stopping and reconfiguring queues on a port at runtime without stopping
14the entire device.
15
16Support of this capability is shown by starting the Poll Mode Driver with multiple Rx and Tx queues
17configured and stopping some prior to forwarding packets, then examining whether or not the stopped
18ports and the unmodified ports were able to handle traffic. In addition to just stopping the ports,
19the ports must also show that they support configuration changes on their queues at runtime without
20stopping the entire device. This is shown by changing the ring size of the queues.
21
22If the Poll Mode Driver is able to stop some queues on a port and modify them then handle traffic
23on the unmodified queues while the others are stopped, then it is the case that the device properly
24supports dynamic configuration of its queues.
25"""
26
27import random
28from typing import Callable, ClassVar, MutableSet
29
30from scapy.layers.inet import IP  # type: ignore[import-untyped]
31from scapy.layers.l2 import Ether  # type: ignore[import-untyped]
32from scapy.packet import Raw  # type: ignore[import-untyped]
33
34from framework.exception import InteractiveCommandExecutionError
35from framework.params.testpmd import PortTopology, SimpleForwardingModes
36from framework.remote_session.testpmd_shell import TestPmdShell
37from framework.test_suite import TestSuite, func_test
38from framework.testbed_model.capability import NicCapability, requires
39
40
41def setup_and_teardown_test(
42    test_meth: Callable[
43        ["TestDynamicQueueConf", int, MutableSet, MutableSet, TestPmdShell, bool], None
44    ],
45) -> Callable[["TestDynamicQueueConf", bool], None]:
46    """Decorator that provides a setup and teardown for testing methods.
47
48    This decorator provides a method that sets up the environment for testing, runs the test
49    method, and then does a clean-up verification step after the queues are started again. The
50    decorated method will be provided with all the variables it should need to run testing
51    including: The ID of the port where the queues for testing reside, disjoint sets of IDs for
52    queues that are/aren't modified, a testpmd session to run testing with, and a flag that
53    indicates whether or not testing should be done on Rx or Tx queues.
54
55    Args:
56        test_meth: The decorated method that tests configuration of port queues at runtime.
57            This method must have the following parameters in order: An int that represents a
58            port ID, a set of queues for testing, a set of unmodified queues, a testpmd
59            interactive shell, and a boolean that, when :data:`True`, does Rx testing,
60            otherwise does Tx testing. This method must also be a member of the
61            :class:`TestDynamicQueueConf` class.
62
63    Returns:
64        A method that sets up the environment, runs the decorated method, then re-enables all
65        queues and validates they can still handle traffic.
66    """
67
68    def wrap(self: "TestDynamicQueueConf", is_rx_testing: bool) -> None:
69        """Setup environment, run test function, then cleanup.
70
71        Start a testpmd shell and stop ports for testing, then call the decorated function that
72        performs the testing. After the decorated function is finished running its testing,
73        start the stopped queues and send packets to validate that these ports can properly
74        handle traffic after being started again.
75
76        Args:
77            self: Instance of :class:`TestDynamicQueueConf` `test_meth` belongs to.
78            is_rx_testing: If :data:`True` then Rx queues will be the ones modified throughout
79                the test, otherwise Tx queues will be modified.
80        """
81        port_id = self.rx_port_num if is_rx_testing else self.tx_port_num
82        queues_to_config: set[int] = set()
83        while len(queues_to_config) < self.num_ports_to_modify:
84            queues_to_config.add(random.randint(1, self.number_of_queues - 1))
85        unchanged_queues = set(range(self.number_of_queues)) - queues_to_config
86        with TestPmdShell(
87            self.sut_node,
88            port_topology=PortTopology.chained,
89            rx_queues=self.number_of_queues,
90            tx_queues=self.number_of_queues,
91        ) as testpmd:
92            for q in queues_to_config:
93                testpmd.stop_port_queue(port_id, q, is_rx_testing)
94            testpmd.set_forward_mode(SimpleForwardingModes.mac)
95
96            test_meth(self, port_id, queues_to_config, unchanged_queues, testpmd, is_rx_testing)
97
98            for queue_id in queues_to_config:
99                testpmd.start_port_queue(port_id, queue_id, is_rx_testing)
100
101            testpmd.start()
102            self.send_packets_with_different_addresses(self.number_of_packets_to_send)
103            forwarding_stats = testpmd.stop()
104            for queue_id in queues_to_config:
105                self.verify(
106                    self.port_queue_in_stats(port_id, is_rx_testing, queue_id, forwarding_stats),
107                    f"Modified queue {queue_id} on port {port_id} failed to receive traffic after"
108                    "being started again.",
109                )
110
111    return wrap
112
113
114class TestDynamicQueueConf(TestSuite):
115    """DPDK dynamic queue configuration test suite.
116
117    Testing for the support of dynamic queue configuration is done by splitting testing by the type
118    of queue (either Rx or Tx) and the type of testing (testing for stopping a port at runtime vs
119    testing configuration changes at runtime). Testing is done by first stopping a finite number of
120    port queues (3 is sufficient) and either modifying the configuration or sending packets to
121    verify that the unmodified queues can handle traffic. Specifically, the following cases are
122    tested:
123
124    1. The application should be able to start the device with only some of the
125       queues set up.
126    2. The application should be able to reconfigure existing queues at runtime
127       without calling dev_stop().
128    """
129
130    #:
131    num_ports_to_modify: ClassVar[int] = 3
132    #: Source IP address to use when sending packets.
133    src_addr: ClassVar[str] = "192.168.0.1"
134    #: Subnet to use for all of the destination addresses of the packets being sent.
135    dst_address_subnet: ClassVar[str] = "192.168.1"
136    #: ID of the port to modify Rx queues on.
137    rx_port_num: ClassVar[int] = 0
138    #: ID of the port to modify Tx queues on.
139    tx_port_num: ClassVar[int] = 1
140    #: Number of queues to start testpmd with. There will be the same number of Rx and Tx queues.
141    #: 8 was chosen as a number that is low enough for most NICs to accommodate while also being
142    #: enough to validate the usage of the queues.
143    number_of_queues: ClassVar[int] = 8
144    #: The number of packets to send while testing. The test calls for well over the ring size - 1
145    #: packets in the modification test case and the only options for ring size are 256 or 512,
146    #: therefore 1024 will be more than enough.
147    number_of_packets_to_send: ClassVar[int] = 1024
148
149    def send_packets_with_different_addresses(self, number_of_packets: int) -> None:
150        """Send a set number of packets each with different dst addresses.
151
152        Different destination addresses are required to ensure that each queue is used. If every
153        packet had the same address, then they would all be processed by the same queue. Note that
154        this means the current implementation of this method is limited to only work for up to 254
155        queues. A smaller subnet would be required to handle an increased number of queues.
156
157        Args:
158            number_of_packets: The number of packets to generate and then send using the traffic
159                generator.
160        """
161        packets_to_send = [
162            Ether()
163            / IP(src=self.src_addr, dst=f"{self.dst_address_subnet}.{(i % 254) + 1}")
164            / Raw()
165            for i in range(number_of_packets)
166        ]
167        self.send_packets(packets_to_send)
168
169    def port_queue_in_stats(
170        self, port_id: int, is_rx_queue: bool, queue_id: int, stats: str
171    ) -> bool:
172        """Verify if stats for a queue are in the provided output.
173
174        Args:
175            port_id: ID of the port that the queue resides on.
176            is_rx_queue: Type of queue to scan for, if :data:`True` then search for an Rx queue,
177                otherwise search for a Tx queue.
178            queue_id: ID of the queue.
179            stats: Testpmd forwarding statistics to scan for the given queue.
180
181        Returns:
182            If the queue appeared in the forwarding statistics.
183        """
184        type_of_queue = "RX" if is_rx_queue else "TX"
185        return f"{type_of_queue} Port= {port_id}/Queue={queue_id:2d}" in stats
186
187    @setup_and_teardown_test
188    def modify_ring_size(
189        self,
190        port_id: int,
191        queues_to_modify: MutableSet[int],
192        unchanged_queues: MutableSet[int],
193        testpmd: TestPmdShell,
194        is_rx_testing: bool,
195    ) -> None:
196        """Verify ring size of port queues can be configured at runtime.
197
198        Ring size of queues in `queues_to_modify` are set to 512 unless that is already their
199        configured size, in which case they are instead set to 256. Queues in `queues_to_modify`
200        are expected to already be stopped before calling this method. `testpmd` is also expected
201        to already be started.
202
203        Args:
204            port_id: Port where the queues reside.
205            queues_to_modify: IDs of stopped queues to configure in the test.
206            unchanged_queues: IDs of running, unmodified queues.
207            testpmd: Running interactive testpmd application.
208            is_rx_testing: If :data:`True` Rx queues will be modified in the test, otherwise Tx
209                queues will be modified.
210        """
211        for queue_id in queues_to_modify:
212            curr_ring_size = testpmd.get_queue_ring_size(port_id, queue_id, is_rx_testing)
213            new_ring_size = 256 if curr_ring_size == 512 else 512
214            try:
215                testpmd.set_queue_ring_size(
216                    port_id, queue_id, new_ring_size, is_rx_testing, verify=True
217                )
218            # The testpmd method verifies that the modification worked, so we catch that error
219            # and just re-raise it as a test case failure
220            except InteractiveCommandExecutionError:
221                self.verify(
222                    False,
223                    f"Failed to update the ring size of queue {queue_id} on port "
224                    f"{port_id} at runtime",
225                )
226
227    @setup_and_teardown_test
228    def stop_queues(
229        self,
230        port_id: int,
231        queues_to_modify: MutableSet[int],
232        unchanged_queues: MutableSet[int],
233        testpmd: TestPmdShell,
234        is_rx_testing: bool,
235    ) -> None:
236        """Verify stopped queues do not handle traffic and do not block traffic on other queues.
237
238        Queues in `queues_to_modify` are expected to already be stopped before calling this method.
239        `testpmd` is also expected to already be started.
240
241        Args:
242            port_id: Port where the queues reside.
243            queues_to_modify: IDs of stopped queues to configure in the test.
244            unchanged_queues: IDs of running, unmodified queues.
245            testpmd: Running interactive testpmd application.
246            is_rx_testing: If :data:`True` Rx queues will be modified in the test, otherwise Tx
247                queues will be modified.
248        """
249        testpmd.start()
250        self.send_packets_with_different_addresses(self.number_of_packets_to_send)
251        forwarding_stats = testpmd.stop()
252
253        # Checking that all unmodified queues handled some packets is important because this
254        # test case checks for the absence of stopped queues to validate that they cannot
255        # receive traffic. If there are some unchanged queues that also didn't receive traffic,
256        # it means there could be another reason for the packets not transmitting and,
257        # therefore, a false positive result.
258        for unchanged_q_id in unchanged_queues:
259            self.verify(
260                self.port_queue_in_stats(port_id, is_rx_testing, unchanged_q_id, forwarding_stats),
261                f"Queue {unchanged_q_id} failed to receive traffic.",
262            )
263        for stopped_q_id in queues_to_modify:
264            self.verify(
265                not self.port_queue_in_stats(
266                    port_id, is_rx_testing, stopped_q_id, forwarding_stats
267                ),
268                f"Queue {stopped_q_id} should be stopped but still received traffic.",
269            )
270
271    @requires(NicCapability.RUNTIME_RX_QUEUE_SETUP)
272    @func_test
273    def test_rx_queue_stop(self):
274        """Run method for stopping queues with flag for Rx testing set to :data:`True`."""
275        self.stop_queues(True)
276
277    @requires(NicCapability.RUNTIME_RX_QUEUE_SETUP)
278    @func_test
279    def test_rx_queue_configuration(self):
280        """Run method for configuring queues with flag for Rx testing set to :data:`True`."""
281        self.modify_ring_size(True)
282
283    @requires(NicCapability.RUNTIME_TX_QUEUE_SETUP)
284    @func_test
285    def test_tx_queue_stop(self):
286        """Run method for stopping queues with flag for Rx testing set to :data:`False`."""
287        self.stop_queues(False)
288
289    @requires(NicCapability.RUNTIME_TX_QUEUE_SETUP)
290    @func_test
291    def test_tx_queue_configuration(self):
292        """Run method for configuring queues with flag for Rx testing set to :data:`False`."""
293        self.modify_ring_size(False)
294