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