From patchwork Tue Aug 6 12:46:38 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Luca Vizzarro X-Patchwork-Id: 142949 X-Patchwork-Delegate: juraj.linkes@pantheon.tech Return-Path: X-Original-To: patchwork@inbox.dpdk.org Delivered-To: patchwork@inbox.dpdk.org Received: from mails.dpdk.org (mails.dpdk.org [217.70.189.124]) by inbox.dpdk.org (Postfix) with ESMTP id C85C74574D; Tue, 6 Aug 2024 14:49:37 +0200 (CEST) Received: from mails.dpdk.org (localhost [127.0.0.1]) by mails.dpdk.org (Postfix) with ESMTP id 823E542EF1; Tue, 6 Aug 2024 14:49:35 +0200 (CEST) Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by mails.dpdk.org (Postfix) with ESMTP id 8CA5342EED for ; Tue, 6 Aug 2024 14:49:33 +0200 (CEST) Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id AB4D81063; Tue, 6 Aug 2024 05:49:58 -0700 (PDT) Received: from localhost.localdomain (JR4XG4HTQC.cambridge.arm.com [10.1.36.46]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPA id 0F6A13F766; Tue, 6 Aug 2024 05:49:31 -0700 (PDT) From: Luca Vizzarro To: dev@dpdk.org Cc: Jeremy Spewock , =?utf-8?q?Juraj_Linke=C5=A1?= , Honnappa Nagarahalli , Luca Vizzarro , Paul Szczepanek , Alex Chapman Subject: [PATCH v2 1/5] dts: add ability to send/receive multiple packets Date: Tue, 6 Aug 2024 13:46:38 +0100 Message-Id: <20240806124642.2580828-2-luca.vizzarro@arm.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20240806124642.2580828-1-luca.vizzarro@arm.com> References: <20240806121417.2567708-1-Luca.Vizzarro@arm.com> <20240806124642.2580828-1-luca.vizzarro@arm.com> MIME-Version: 1.0 X-BeenThere: dev@dpdk.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: DPDK patches and discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dev-bounces@dpdk.org The framework allows only to send one packet at once via Scapy. This change adds the ability to send multiple packets, and also introduces a new fast way to verify if we received several expected packets. Moreover, it reduces code duplication by keeping a single packet sending method only at the test suite level. Signed-off-by: Luca Vizzarro Reviewed-by: Paul Szczepanek Reviewed-by: Alex Chapman Reviewed-by: Jeremy Spewock Reviewed-by: Juraj Linkeš --- dts/framework/test_suite.py | 68 +++++++++++++++++-- dts/framework/testbed_model/tg_node.py | 14 ++-- .../capturing_traffic_generator.py | 31 --------- 3 files changed, 71 insertions(+), 42 deletions(-) diff --git a/dts/framework/test_suite.py b/dts/framework/test_suite.py index 694b2eba65..051509fb86 100644 --- a/dts/framework/test_suite.py +++ b/dts/framework/test_suite.py @@ -13,12 +13,13 @@ * Test case verification. """ +from collections import Counter from ipaddress import IPv4Interface, IPv6Interface, ip_interface from typing import ClassVar, Union from scapy.layers.inet import IP # type: ignore[import-untyped] from scapy.layers.l2 import Ether # type: ignore[import-untyped] -from scapy.packet import Packet, Padding # type: ignore[import-untyped] +from scapy.packet import Packet, Padding, raw # type: ignore[import-untyped] from framework.testbed_model.port import Port, PortLink from framework.testbed_model.sut_node import SutNode @@ -199,9 +200,34 @@ def send_packet_and_capture( Returns: A list of received packets. """ - packet = self._adjust_addresses(packet) - return self.tg_node.send_packet_and_capture( - packet, + return self.send_packets_and_capture( + [packet], + filter_config, + duration, + ) + + def send_packets_and_capture( + self, + packets: list[Packet], + filter_config: PacketFilteringConfig = PacketFilteringConfig(), + duration: float = 1, + ) -> list[Packet]: + """Send and receive `packets` using the associated TG. + + Send `packets` through the appropriate interface and receive on the appropriate interface. + Modify the packets with l3/l2 addresses corresponding to the testbed and desired traffic. + + Args: + packets: The packets to send. + filter_config: The filter to use when capturing packets. + duration: Capture traffic for this amount of time after sending `packet`. + + Returns: + A list of received packets. + """ + packets = [self._adjust_addresses(packet) for packet in packets] + return self.tg_node.send_packets_and_capture( + packets, self._tg_port_egress, self._tg_port_ingress, filter_config, @@ -303,6 +329,40 @@ 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: + """Matches all the expected packets against the received ones. + + Matching is performed by counting down the occurrences in a dictionary which keys are the + raw packet bytes. No deep packet comparison is performed. All the unexpected packets (noise) + are automatically ignored. + + Args: + expected_packets: The packets we are expecting to receive. + received_packets: All the packets that were received. + + Raises: + TestCaseVerifyError: if and not all the `expected_packets` were found in + `received_packets`. + """ + expected_packets_counters = Counter(map(raw, expected_packets)) + received_packets_counters = Counter(map(raw, received_packets)) + # The number of expected packets is subtracted by the number of received packets, ignoring + # any unexpected packets and capping at zero. + missing_packets_counters = expected_packets_counters - received_packets_counters + missing_packets_count = missing_packets_counters.total() + self._logger.debug( + f"match_all_packets: expected {len(expected_packets)}, " + f"received {len(received_packets)}, missing {missing_packets_count}" + ) + + 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." + ) + def _compare_packets(self, expected_packet: Packet, received_packet: Packet) -> bool: self._logger.debug( f"Comparing packets: \n{expected_packet.summary()}\n{received_packet.summary()}" diff --git a/dts/framework/testbed_model/tg_node.py b/dts/framework/testbed_model/tg_node.py index 4ee326e99c..19b5b6e74c 100644 --- a/dts/framework/testbed_model/tg_node.py +++ b/dts/framework/testbed_model/tg_node.py @@ -51,22 +51,22 @@ def __init__(self, node_config: TGNodeConfiguration): self.traffic_generator = create_traffic_generator(self, node_config.traffic_generator) self._logger.info(f"Created node: {self.name}") - def send_packet_and_capture( + def send_packets_and_capture( self, - packet: Packet, + packets: list[Packet], send_port: Port, receive_port: Port, filter_config: PacketFilteringConfig = PacketFilteringConfig(), duration: float = 1, ) -> list[Packet]: - """Send `packet`, return received traffic. + """Send `packets`, return received traffic. - Send `packet` on `send_port` and then return all traffic captured + Send `packets` on `send_port` and then return all traffic captured on `receive_port` for the given duration. Also record the captured traffic in a pcap file. Args: - packet: The packet to send. + packets: The packets to send. send_port: The egress port on the TG node. receive_port: The ingress port in the TG node. filter_config: The filter to use when capturing packets. @@ -75,8 +75,8 @@ def send_packet_and_capture( Returns: A list of received packets. May be empty if no packets are captured. """ - return self.traffic_generator.send_packet_and_capture( - packet, + return self.traffic_generator.send_packets_and_capture( + packets, send_port, receive_port, filter_config, diff --git a/dts/framework/testbed_model/traffic_generator/capturing_traffic_generator.py b/dts/framework/testbed_model/traffic_generator/capturing_traffic_generator.py index c8380b7d57..66a77da9c4 100644 --- a/dts/framework/testbed_model/traffic_generator/capturing_traffic_generator.py +++ b/dts/framework/testbed_model/traffic_generator/capturing_traffic_generator.py @@ -63,37 +63,6 @@ def is_capturing(self) -> bool: """This traffic generator can capture traffic.""" return True - def send_packet_and_capture( - self, - packet: Packet, - send_port: Port, - receive_port: Port, - filter_config: PacketFilteringConfig, - duration: float, - capture_name: str = _get_default_capture_name(), - ) -> list[Packet]: - """Send `packet` and capture received traffic. - - Send `packet` on `send_port` and then return all traffic captured - on `receive_port` for the given `duration`. - - The captured traffic is recorded in the `capture_name`.pcap file. - - Args: - packet: The packet to send. - send_port: The egress port on the TG node. - receive_port: The ingress port in the TG node. - filter_config: Filters to apply when capturing packets. - duration: Capture traffic for this amount of time after sending the packet. - capture_name: The name of the .pcap file where to store the capture. - - Returns: - The received packets. May be empty if no packets are captured. - """ - return self.send_packets_and_capture( - [packet], send_port, receive_port, filter_config, duration, capture_name - ) - def send_packets_and_capture( self, packets: list[Packet], From patchwork Tue Aug 6 12:46:39 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Luca Vizzarro X-Patchwork-Id: 142950 X-Patchwork-Delegate: juraj.linkes@pantheon.tech Return-Path: X-Original-To: patchwork@inbox.dpdk.org Delivered-To: patchwork@inbox.dpdk.org Received: from mails.dpdk.org (mails.dpdk.org [217.70.189.124]) by inbox.dpdk.org (Postfix) with ESMTP id 6FDCA4574D; Tue, 6 Aug 2024 14:49:43 +0200 (CEST) Received: from mails.dpdk.org (localhost [127.0.0.1]) by mails.dpdk.org (Postfix) with ESMTP id BEB4E42EF6; Tue, 6 Aug 2024 14:49:37 +0200 (CEST) Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by mails.dpdk.org (Postfix) with ESMTP id 855BA42EEF for ; Tue, 6 Aug 2024 14:49:34 +0200 (CEST) Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id C87F1113E; Tue, 6 Aug 2024 05:49:59 -0700 (PDT) Received: from localhost.localdomain (JR4XG4HTQC.cambridge.arm.com [10.1.36.46]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPA id 2BE963F766; Tue, 6 Aug 2024 05:49:33 -0700 (PDT) From: Luca Vizzarro To: dev@dpdk.org Cc: Jeremy Spewock , =?utf-8?q?Juraj_Linke=C5=A1?= , Honnappa Nagarahalli , Luca Vizzarro , Paul Szczepanek , Alex Chapman Subject: [PATCH v2 2/5] dts: add random generation seed setting Date: Tue, 6 Aug 2024 13:46:39 +0100 Message-Id: <20240806124642.2580828-3-luca.vizzarro@arm.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20240806124642.2580828-1-luca.vizzarro@arm.com> References: <20240806121417.2567708-1-Luca.Vizzarro@arm.com> <20240806124642.2580828-1-luca.vizzarro@arm.com> MIME-Version: 1.0 X-BeenThere: dev@dpdk.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: DPDK patches and discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dev-bounces@dpdk.org When introducing pseudo-random generation in the test runs we need to ensure that these can be reproduced by setting a pre-defined seed. This commits adds the ability to set one or allow for one to be generated and reported back to the user. Signed-off-by: Luca Vizzarro Reviewed-by: Paul Szczepanek Reviewed-by: Alex Chapman Reviewed-by: Jeremy Spewock Reviewed-by: Juraj Linkeš --- doc/guides/tools/dts.rst | 5 +++++ dts/framework/config/__init__.py | 4 ++++ dts/framework/config/conf_yaml_schema.json | 4 ++++ dts/framework/config/types.py | 2 ++ dts/framework/runner.py | 8 ++++++++ dts/framework/settings.py | 17 +++++++++++++++++ 6 files changed, 40 insertions(+) diff --git a/doc/guides/tools/dts.rst b/doc/guides/tools/dts.rst index 515b15e4d8..9b5ea9779c 100644 --- a/doc/guides/tools/dts.rst +++ b/doc/guides/tools/dts.rst @@ -251,6 +251,8 @@ DTS is run with ``main.py`` located in the ``dts`` directory after entering Poet ... | DTS_TEST_SUITES='suite, suite case, ...' (default: []) --re-run N_TIMES, --re_run N_TIMES [DTS_RERUN] Re-run each test case the specified number of times if a test failure occurs. (default: 0) + --random-seed NUMBER [DTS_RANDOM_SEED] The seed to use with the pseudo-random generator. If not specified, the configuration value is + used instead. If that's also not specified, a random seed is generated. (default: None) The brackets contain the names of environment variables that set the same thing. @@ -548,6 +550,9 @@ involved in the testing. These can be defined with the following mappings: +----------------------------+---------------+---------------------------------------------------+ | ``traffic_generator_node`` | Node name for the traffic generator node. | +----------------------------+-------------------------------------------------------------------+ + | ``random_seed`` | (*optional*) *int* – Allows you to set a seed for pseudo-random | + | | generation. | + +----------------------------+-------------------------------------------------------------------+ ``nodes`` `sequence `_ listing diff --git a/dts/framework/config/__init__.py b/dts/framework/config/__init__.py index df60a5030e..269d9ec318 100644 --- a/dts/framework/config/__init__.py +++ b/dts/framework/config/__init__.py @@ -445,6 +445,7 @@ class TestRunConfiguration: system_under_test_node: The SUT node to use in this test run. traffic_generator_node: The TG node to use in this test run. vdevs: The names of virtual devices to test. + random_seed: The seed to use for pseudo-random generation. """ build_targets: list[BuildTargetConfiguration] @@ -455,6 +456,7 @@ class TestRunConfiguration: system_under_test_node: SutNodeConfiguration traffic_generator_node: TGNodeConfiguration vdevs: list[str] + random_seed: int | None @classmethod def from_dict( @@ -497,6 +499,7 @@ def from_dict( vdevs = ( d["system_under_test_node"]["vdevs"] if "vdevs" in d["system_under_test_node"] else [] ) + random_seed = d.get("random_seed", None) return cls( build_targets=build_targets, perf=d["perf"], @@ -506,6 +509,7 @@ def from_dict( system_under_test_node=system_under_test_node, traffic_generator_node=traffic_generator_node, vdevs=vdevs, + random_seed=random_seed, ) def copy_and_modify(self, **kwargs) -> Self: diff --git a/dts/framework/config/conf_yaml_schema.json b/dts/framework/config/conf_yaml_schema.json index f02a310bb5..df390e8ae2 100644 --- a/dts/framework/config/conf_yaml_schema.json +++ b/dts/framework/config/conf_yaml_schema.json @@ -379,6 +379,10 @@ }, "traffic_generator_node": { "$ref": "#/definitions/node_name" + }, + "random_seed": { + "type": "integer", + "description": "Optional field. Allows you to set a seed for pseudo-random generation." } }, "additionalProperties": false, diff --git a/dts/framework/config/types.py b/dts/framework/config/types.py index cf16556403..ce7b784ac8 100644 --- a/dts/framework/config/types.py +++ b/dts/framework/config/types.py @@ -121,6 +121,8 @@ class TestRunConfigDict(TypedDict): system_under_test_node: TestRunSUTConfigDict #: traffic_generator_node: str + #: + random_seed: int class ConfigurationDict(TypedDict): diff --git a/dts/framework/runner.py b/dts/framework/runner.py index 6b6f6a05f5..34b1dad5c4 100644 --- a/dts/framework/runner.py +++ b/dts/framework/runner.py @@ -20,6 +20,7 @@ import importlib import inspect import os +import random import re import sys from pathlib import Path @@ -147,6 +148,7 @@ def run(self) -> None: self._logger.info( f"Running test run with SUT '{test_run_config.system_under_test_node.name}'." ) + self._init_random_seed(test_run_config) test_run_result = self._result.add_test_run(test_run_config) # we don't want to modify the original config, so create a copy test_run_test_suites = list( @@ -723,3 +725,9 @@ def _exit_dts(self) -> None: self._logger.info("DTS execution has ended.") sys.exit(self._result.get_return_code()) + + def _init_random_seed(self, conf: TestRunConfiguration) -> None: + """Initialize the random seed to use for the test run.""" + seed = SETTINGS.random_seed or conf.random_seed or random.randrange(0xFFFF_FFFF) + self._logger.info(f"Initializing test run with random seed {seed}") + random.seed(seed) diff --git a/dts/framework/settings.py b/dts/framework/settings.py index f6303066d4..7744e37f54 100644 --- a/dts/framework/settings.py +++ b/dts/framework/settings.py @@ -66,6 +66,12 @@ Re-run each test case this many times in case of a failure. +.. option:: --random-seed +.. envvar:: DTS_RANDOM_SEED + + The seed to use with the pseudo-random generator. If not specified, the configuration value is + used instead. If that's also not specified, a random seed is generated. + The module provides one key module-level variable: Attributes: @@ -115,6 +121,8 @@ class Settings: test_suites: list[TestSuiteConfig] = field(default_factory=list) #: re_run: int = 0 + #: + random_seed: int | None = None SETTINGS: Settings = Settings() @@ -375,6 +383,15 @@ def _get_parser() -> _DTSArgumentParser: ) _add_env_var_to_action(action, "RERUN") + action = parser.add_argument( + "--random-seed", + type=int, + help="The seed to use with the pseudo-random generator. If not specified, the configuration" + " value is used instead. If that's also not specified, a random seed is generated.", + metavar="NUMBER", + ) + _add_env_var_to_action(action) + return parser From patchwork Tue Aug 6 12:46:40 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Luca Vizzarro X-Patchwork-Id: 142951 X-Patchwork-Delegate: juraj.linkes@pantheon.tech Return-Path: X-Original-To: patchwork@inbox.dpdk.org Delivered-To: patchwork@inbox.dpdk.org Received: from mails.dpdk.org (mails.dpdk.org [217.70.189.124]) by inbox.dpdk.org (Postfix) with ESMTP id ADB8F4574D; Tue, 6 Aug 2024 14:49:49 +0200 (CEST) Received: from mails.dpdk.org (localhost [127.0.0.1]) by mails.dpdk.org (Postfix) with ESMTP id 1B8BB42F00; Tue, 6 Aug 2024 14:49:39 +0200 (CEST) Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by mails.dpdk.org (Postfix) with ESMTP id E622242EF3 for ; Tue, 6 Aug 2024 14:49:35 +0200 (CEST) Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id E43C5FEC; Tue, 6 Aug 2024 05:50:00 -0700 (PDT) Received: from localhost.localdomain (JR4XG4HTQC.cambridge.arm.com [10.1.36.46]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPA id 4A0F53F766; Tue, 6 Aug 2024 05:49:34 -0700 (PDT) From: Luca Vizzarro To: dev@dpdk.org Cc: Jeremy Spewock , =?utf-8?q?Juraj_Linke=C5=A1?= , Honnappa Nagarahalli , Luca Vizzarro , Paul Szczepanek , Alex Chapman Subject: [PATCH v2 3/5] dts: add random packet generator Date: Tue, 6 Aug 2024 13:46:40 +0100 Message-Id: <20240806124642.2580828-4-luca.vizzarro@arm.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20240806124642.2580828-1-luca.vizzarro@arm.com> References: <20240806121417.2567708-1-Luca.Vizzarro@arm.com> <20240806124642.2580828-1-luca.vizzarro@arm.com> MIME-Version: 1.0 X-BeenThere: dev@dpdk.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: DPDK patches and discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dev-bounces@dpdk.org Add a basic utility that can create random L3 and L4 packets with random payloads and port numbers (if L4). Signed-off-by: Luca Vizzarro Reviewed-by: Paul Szczepanek Reviewed-by: Alex Chapman Reviewed-by: Jeremy Spewock Reviewed-by: Juraj Linkeš --- dts/framework/utils.py | 79 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 77 insertions(+), 2 deletions(-) diff --git a/dts/framework/utils.py b/dts/framework/utils.py index 6b5d5a805f..c768dd0c99 100644 --- a/dts/framework/utils.py +++ b/dts/framework/utils.py @@ -17,14 +17,16 @@ import atexit import json import os +import random import subprocess -from enum import Enum +from enum import Enum, Flag from pathlib import Path from subprocess import SubprocessError +from scapy.layers.inet import IP, TCP, UDP, Ether # type: ignore[import-untyped] from scapy.packet import Packet # type: ignore[import-untyped] -from .exception import ConfigurationError +from .exception import ConfigurationError, InternalError REGEX_FOR_PCI_ADDRESS: str = "/[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}.[0-9]{1}/" @@ -244,3 +246,76 @@ def _delete_tarball(self) -> None: def __fspath__(self) -> str: """The os.PathLike protocol implementation.""" return str(self._tarball_path) + + +class PacketProtocols(Flag): + """Flag specifying which protocols to use for packet generation.""" + + #: + IP = 1 + #: + TCP = 2 | IP + #: + UDP = 4 | IP + #: + ALL = TCP | UDP + + +def generate_random_packets( + number_of: int, + payload_size: int = 1500, + protocols: PacketProtocols = PacketProtocols.ALL, + ports_range: range = range(1024, 49152), + mtu: int = 1500, +) -> list[Packet]: + """Generate a number of random packets. + + The payload of the packets will consist of random bytes. If `payload_size` is too big, then the + maximum payload size allowed for the specific packet type is used. The size is calculated based + on the specified `mtu`, therefore it is essential that `mtu` is set correctly to match the MTU + of the port that will send out the generated packets. + + If `protocols` has any L4 protocol enabled then all the packets are generated with any of + the specified L4 protocols chosen at random. If only :attr:`~PacketProtocols.IP` is set, then + only L3 packets are generated. + + If L4 packets will be generated, then the TCP/UDP ports to be used will be chosen at random from + `ports_range`. + + Args: + number_of: The number of packets to generate. + payload_size: The packet payload size to generate, capped based on `mtu`. + protocols: The protocols to use for the generated packets. + ports_range: The range of L4 port numbers to use. Used only if `protocols` has L4 protocols. + mtu: The MTU of the NIC port that will send out the generated packets. + + Raises: + InternalError: If the `payload_size` is invalid. + + Returns: + A list containing the randomly generated packets. + """ + if payload_size < 0: + raise InternalError(f"An invalid payload_size of {payload_size} was given.") + + l4_factories = [] + if protocols & PacketProtocols.TCP: + l4_factories.append(TCP) + if protocols & PacketProtocols.UDP: + l4_factories.append(UDP) + + def _make_packet() -> Packet: + packet = Ether() + + if protocols & PacketProtocols.IP: + packet /= IP() + + if len(l4_factories) > 0: + src_port, dst_port = random.choices(ports_range, k=2) + packet /= random.choice(l4_factories)(sport=src_port, dport=dst_port) + + max_payload_size = mtu - len(packet) + usable_payload_size = payload_size if payload_size < max_payload_size else max_payload_size + return packet / random.randbytes(usable_payload_size) + + return [_make_packet() for _ in range(number_of)] From patchwork Tue Aug 6 12:46:41 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Luca Vizzarro X-Patchwork-Id: 142952 X-Patchwork-Delegate: juraj.linkes@pantheon.tech Return-Path: X-Original-To: patchwork@inbox.dpdk.org Delivered-To: patchwork@inbox.dpdk.org Received: from mails.dpdk.org (mails.dpdk.org [217.70.189.124]) by inbox.dpdk.org (Postfix) with ESMTP id D5B4C4574D; Tue, 6 Aug 2024 14:49:58 +0200 (CEST) Received: from mails.dpdk.org (localhost [127.0.0.1]) by mails.dpdk.org (Postfix) with ESMTP id 20A7F42F09; Tue, 6 Aug 2024 14:49:41 +0200 (CEST) Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by mails.dpdk.org (Postfix) with ESMTP id BE73F42EF5 for ; Tue, 6 Aug 2024 14:49:36 +0200 (CEST) Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id EC5F11063; Tue, 6 Aug 2024 05:50:01 -0700 (PDT) Received: from localhost.localdomain (JR4XG4HTQC.cambridge.arm.com [10.1.36.46]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPA id 65C8D3F766; Tue, 6 Aug 2024 05:49:35 -0700 (PDT) From: Luca Vizzarro To: dev@dpdk.org Cc: Jeremy Spewock , =?utf-8?q?Juraj_Linke=C5=A1?= , Honnappa Nagarahalli , Luca Vizzarro , Paul Szczepanek Subject: [PATCH v2 4/5] dts: add ability to start/stop testpmd ports Date: Tue, 6 Aug 2024 13:46:41 +0100 Message-Id: <20240806124642.2580828-5-luca.vizzarro@arm.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20240806124642.2580828-1-luca.vizzarro@arm.com> References: <20240806121417.2567708-1-Luca.Vizzarro@arm.com> <20240806124642.2580828-1-luca.vizzarro@arm.com> MIME-Version: 1.0 X-BeenThere: dev@dpdk.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: DPDK patches and discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dev-bounces@dpdk.org Add testpmd commands to start and stop all the ports, so that they can be configured. Because there is a distinction of commands that require the ports to be stopped and started, also add decorators for commands that require a specific state, removing this logic from the test writer's duty. Signed-off-by: Luca Vizzarro Reviewed-by: Paul Szczepanek --- dts/framework/remote_session/testpmd_shell.py | 86 ++++++++++++++++++- 1 file changed, 84 insertions(+), 2 deletions(-) diff --git a/dts/framework/remote_session/testpmd_shell.py b/dts/framework/remote_session/testpmd_shell.py index 43e9f56517..ca24b28070 100644 --- a/dts/framework/remote_session/testpmd_shell.py +++ b/dts/framework/remote_session/testpmd_shell.py @@ -14,16 +14,17 @@ testpmd_shell.close() """ +import functools import re import time from dataclasses import dataclass, field from enum import Flag, auto from pathlib import PurePath -from typing import ClassVar +from typing import Any, Callable, ClassVar, Concatenate, ParamSpec from typing_extensions import Self, Unpack -from framework.exception import InteractiveCommandExecutionError +from framework.exception import InteractiveCommandExecutionError, InternalError from framework.params.testpmd import SimpleForwardingModes, TestPmdParams from framework.params.types import TestPmdParamsDict from framework.parser import ParserFn, TextParser @@ -33,6 +34,9 @@ from framework.testbed_model.sut_node import SutNode from framework.utils import StrEnum +P = ParamSpec("P") +TestPmdShellMethod = Callable[Concatenate["TestPmdShell", P], Any] + class TestPmdDevice: """The data of a device that testpmd can recognize. @@ -577,12 +581,51 @@ class TestPmdPortStats(TextParser): tx_bps: int = field(metadata=TextParser.find_int(r"Tx-bps:\s+(\d+)")) +def requires_stopped_ports(func: TestPmdShellMethod) -> TestPmdShellMethod: + """Decorator for :class:`TestPmdShell` commands methods that require stopped ports. + + If the decorated method is called while the ports are started, then these are stopped before + continuing. + """ + + @functools.wraps(func) + def _wrapper(self: "TestPmdShell", *args: P.args, **kwargs: P.kwargs): + if self.ports_started: + self._logger.debug("Ports need to be stopped to continue") + self.stop_all_ports() + + return func(self, *args, **kwargs) + + return _wrapper + + +def requires_started_ports(func: TestPmdShellMethod) -> TestPmdShellMethod: + """Decorator for :class:`TestPmdShell` commands methods that require started ports. + + If the decorated method is called while the ports are stopped, then these are started before + continuing. + """ + + @functools.wraps(func) + def _wrapper(self: "TestPmdShell", *args: P.args, **kwargs: P.kwargs): + if not self.ports_started: + self._logger.debug("Ports need to be started to continue") + self.start_all_ports() + + return func(self, *args, **kwargs) + + return _wrapper + + class TestPmdShell(DPDKShell): """Testpmd interactive shell. The testpmd shell users should never use the :meth:`~.interactive_shell.InteractiveShell.send_command` method directly, but rather call specialized methods. If there isn't one that satisfies a need, it should be added. + + Attributes: + ports_started: Indicates whether the ports are started. """ _app_params: TestPmdParams @@ -619,6 +662,9 @@ def __init__( name, ) + self.ports_started = not self._app_params.disable_device_start + + @requires_started_ports def start(self, verify: bool = True) -> None: """Start packet forwarding with the current configuration. @@ -723,6 +769,42 @@ def set_forward_mode(self, mode: SimpleForwardingModes, verify: bool = True): f"Test pmd failed to set fwd mode to {mode.value}" ) + def stop_all_ports(self, verify: bool = True) -> None: + """Stops all the ports. + + Args: + verify: If :data:`True`, the output of the command will be checked for a successful + execution. + + Raises: + InteractiveCommandExecutionError: If `verify` is :data:`True` and the ports were not + stopped successfully. + """ + self._logger.debug("Stopping all the ports...") + output = self.send_command("port stop all") + if verify and not output.strip().endswith("Done"): + raise InteractiveCommandExecutionError("Ports were not stopped successfully") + + self.ports_started = False + + def start_all_ports(self, verify: bool = True) -> None: + """Starts all the ports. + + Args: + verify: If :data:`True`, the output of the command will be checked for a successful + execution. + + Raises: + InteractiveCommandExecutionError: If `verify` is :data:`True` and the ports were not + started successfully. + """ + self._logger.debug("Starting all the ports...") + output = self.send_command("port start all") + if verify and not output.strip().endswith("Done"): + raise InteractiveCommandExecutionError("Ports were not started successfully") + + self.ports_started = True + def show_port_info_all(self) -> list[TestPmdPort]: """Returns the information of all the ports. From patchwork Tue Aug 6 12:46:42 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Luca Vizzarro X-Patchwork-Id: 142953 X-Patchwork-Delegate: juraj.linkes@pantheon.tech Return-Path: X-Original-To: patchwork@inbox.dpdk.org Delivered-To: patchwork@inbox.dpdk.org Received: from mails.dpdk.org (mails.dpdk.org [217.70.189.124]) by inbox.dpdk.org (Postfix) with ESMTP id B5C9A4574D; Tue, 6 Aug 2024 14:50:04 +0200 (CEST) Received: from mails.dpdk.org (localhost [127.0.0.1]) by mails.dpdk.org (Postfix) with ESMTP id 6F37942F0D; Tue, 6 Aug 2024 14:49:42 +0200 (CEST) Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by mails.dpdk.org (Postfix) with ESMTP id 9DF3940DDD for ; Tue, 6 Aug 2024 14:49:37 +0200 (CEST) Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id E68A9FEC; Tue, 6 Aug 2024 05:50:02 -0700 (PDT) Received: from localhost.localdomain (JR4XG4HTQC.cambridge.arm.com [10.1.36.46]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPA id 6740E3F766; Tue, 6 Aug 2024 05:49:36 -0700 (PDT) From: Luca Vizzarro To: dev@dpdk.org Cc: Jeremy Spewock , =?utf-8?q?Juraj_Linke=C5=A1?= , Honnappa Nagarahalli , Luca Vizzarro , Paul Szczepanek Subject: [PATCH v2 5/5] dts: add testpmd set ports queues Date: Tue, 6 Aug 2024 13:46:42 +0100 Message-Id: <20240806124642.2580828-6-luca.vizzarro@arm.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20240806124642.2580828-1-luca.vizzarro@arm.com> References: <20240806121417.2567708-1-Luca.Vizzarro@arm.com> <20240806124642.2580828-1-luca.vizzarro@arm.com> MIME-Version: 1.0 X-BeenThere: dev@dpdk.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: DPDK patches and discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dev-bounces@dpdk.org Add a facility to update the number of TX/RX queues during the runtime of testpmd. Signed-off-by: Luca Vizzarro Reviewed-by: Paul Szczepanek --- dts/framework/remote_session/testpmd_shell.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/dts/framework/remote_session/testpmd_shell.py b/dts/framework/remote_session/testpmd_shell.py index ca24b28070..85fbc42696 100644 --- a/dts/framework/remote_session/testpmd_shell.py +++ b/dts/framework/remote_session/testpmd_shell.py @@ -805,6 +805,22 @@ def start_all_ports(self, verify: bool = True) -> None: self.ports_started = True + @requires_stopped_ports + def set_ports_queues(self, number_of: int) -> None: + """Sets the number of queues per port. + + Args: + number_of: The number of RX/TX queues to create per port. + + Raises: + InternalError: If `number_of` is invalid. + """ + if number_of < 1: + raise InternalError("The number of queues must be positive and non-zero") + + self.send_command(f"port config all rxq {number_of}") + self.send_command(f"port config all txq {number_of}") + def show_port_info_all(self) -> list[TestPmdPort]: """Returns the information of all the ports.