From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mails.dpdk.org (mails.dpdk.org [217.70.189.124]) by inbox.dpdk.org (Postfix) with ESMTP id 88C4245830; Wed, 21 Aug 2024 16:54:19 +0200 (CEST) Received: from mails.dpdk.org (localhost [127.0.0.1]) by mails.dpdk.org (Postfix) with ESMTP id EEDE642791; Wed, 21 Aug 2024 16:53:31 +0200 (CEST) Received: from mail-ej1-f47.google.com (mail-ej1-f47.google.com [209.85.218.47]) by mails.dpdk.org (Postfix) with ESMTP id E4451411F3 for ; Wed, 21 Aug 2024 16:53:27 +0200 (CEST) Received: by mail-ej1-f47.google.com with SMTP id a640c23a62f3a-a7a81bd549eso565032566b.3 for ; Wed, 21 Aug 2024 07:53:27 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=pantheon.tech; s=google; t=1724252007; x=1724856807; darn=dpdk.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=YlDITddmCLhYO9g28jHSyYFulx8QtBbP2D5HW2GgbBk=; b=G17lFe9nt/WK5V4vbVHlqgtcAwnO8hHO8W4zZ1MdF2ffZIaYQA6sW69X5YfMydQFGJ fMo6v/IEhjl5g820IVFUYxeOrNczjyiTBDhf05nNWqir1Gs5kULLa1sWAR64lgVhzgSy nu9ORena7fHeIAfDJaIc5bTCvGT2zWFssGR5yx5Saz3FhNi66i9A3iEftS8lavxAR3CP nsRoAuaFZx/v6kP1V/Bppqe1Jbjf/rnIZYIhI2oCmIT3uggbkbqhikZ6vjKmdV0fRRA3 6s0z0bvK1ms2qmQtUwVC2b5nozkfBKQJRKlbXP3w6SIrC0SfrHoqJRg/veTcrbcX1YXy PoEQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1724252007; x=1724856807; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=YlDITddmCLhYO9g28jHSyYFulx8QtBbP2D5HW2GgbBk=; b=KGV4r8zeItV/2hJc9jQjnHQW0ULTDEfW0yhomvk5hzp6qc3QiO4tpHGbtHUmn9GmCn DbLxlvky4UsRMrEgcycg3MgnOvJfg3XHg5/5ls742jTXBnX5hljuP5ldnEZr4L+f9d21 61WRcTbiXHor3eaWUaP/ILW6OhCkxEWNt4XImy8j/yZLrppIS06xCeItVoNsIgEW8SRa 4bDW22waJl5VypWu7PDlp/QCbBjjC+O1Y5Yo0cPddiM0SsvYUgQaG6oUZzMhDjDxIoy0 KZJqz5E1pf7yhkab78/k0r8xttfiJ09ozL8dgwAtUGUNsacs7hyfK6nXDP50ErwbP7cB grog== X-Gm-Message-State: AOJu0YzIaW6GaUyJyH4VFG+zTXh9/KERyNGddloRvauSU+rju53/QGFL cNgZhXFNQ7qow/mpsnRGQtlKu3BuJu3DoS+jElLIJxCbfjQ2S+A96OaJKpGVx5s= X-Google-Smtp-Source: AGHT+IGh138EvHWh1/nNWRdqs0qAoJp9mKD6Moq0D6YGhCgPj14odYjFbQmQZb4fKUhk3uIa//cRTg== X-Received: by 2002:a17:907:6d23:b0:a77:e1fb:7dec with SMTP id a640c23a62f3a-a866f27b16fmr183088566b.17.1724252007283; Wed, 21 Aug 2024 07:53:27 -0700 (PDT) Received: from localhost.localdomain ([84.245.121.236]) by smtp.gmail.com with ESMTPSA id a640c23a62f3a-a8680c4725csm52434866b.91.2024.08.21.07.53.26 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 21 Aug 2024 07:53:27 -0700 (PDT) From: =?UTF-8?q?Juraj=20Linke=C5=A1?= To: thomas@monjalon.net, Honnappa.Nagarahalli@arm.com, jspewock@iol.unh.edu, probb@iol.unh.edu, paul.szczepanek@arm.com, Luca.Vizzarro@arm.com, npratte@iol.unh.edu, dmarx@iol.unh.edu, alex.chapman@arm.com Cc: dev@dpdk.org, =?UTF-8?q?Juraj=20Linke=C5=A1?= Subject: [PATCH v3 08/12] dts: add NIC capability support Date: Wed, 21 Aug 2024 16:53:11 +0200 Message-Id: <20240821145315.97974-9-juraj.linkes@pantheon.tech> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20240821145315.97974-1-juraj.linkes@pantheon.tech> References: <20240301155416.96960-1-juraj.linkes@pantheon.tech> <20240821145315.97974-1-juraj.linkes@pantheon.tech> MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Some test cases or suites may be testing a NIC feature that is not supported on all NICs, so add support for marking test cases or suites as requiring NIC capabilities. The marking is done with a decorator, which populates the internal required_capabilities attribute of TestProtocol. The NIC capability itself is a wrapper around the NicCapability defined in testpmd_shell. The reason is twofold: 1. Enums cannot be extended and the class implements the methods of its abstract base superclass, 2. The class also stores an optional decorator function which is used before/after capability retrieval. This is needed because some capabilities may be advertised differently under different configuration. The decorator API is designed to be simple to use. The arguments passed to it are all from the testpmd shell. Everything else (even the actual capability object creation) is done internally. Signed-off-by: Juraj Linkeš --- Depends-on: patch-142276 ("dts: add methods for modifying MTU to testpmd shell") --- dts/framework/remote_session/testpmd_shell.py | 178 ++++++++++++++++- dts/framework/testbed_model/capability.py | 180 +++++++++++++++++- dts/tests/TestSuite_pmd_buffer_scatter.py | 2 + 3 files changed, 356 insertions(+), 4 deletions(-) diff --git a/dts/framework/remote_session/testpmd_shell.py b/dts/framework/remote_session/testpmd_shell.py index f0bcc918e5..48c31124d1 100644 --- a/dts/framework/remote_session/testpmd_shell.py +++ b/dts/framework/remote_session/testpmd_shell.py @@ -16,11 +16,17 @@ import re import time -from collections.abc import Callable +from collections.abc import Callable, MutableSet from dataclasses import dataclass, field from enum import Flag, auto +from functools import partial from pathlib import PurePath -from typing import ClassVar +from typing import TYPE_CHECKING, Any, ClassVar, TypeAlias + +if TYPE_CHECKING: + from enum import Enum as NoAliasEnum +else: + from aenum import NoAliasEnum from typing_extensions import Self, TypeVarTuple, Unpack @@ -34,6 +40,16 @@ from framework.testbed_model.sut_node import SutNode from framework.utils import StrEnum +TestPmdShellCapabilityMethod: TypeAlias = Callable[ + ["TestPmdShell", MutableSet["NicCapability"], MutableSet["NicCapability"]], None +] + +TestPmdShellSimpleMethod: TypeAlias = Callable[["TestPmdShell"], Any] + +TestPmdShellDecoratedMethod: TypeAlias = Callable[["TestPmdShell"], None] + +TestPmdShellDecorator: TypeAlias = Callable[[TestPmdShellSimpleMethod], TestPmdShellDecoratedMethod] + class TestPmdDevice: """The data of a device that testpmd can recognize. @@ -377,6 +393,71 @@ def _validate(info: str): return TextParser.wrap(TextParser.find(r"Device private info:\s+([\s\S]+)"), _validate) +class RxQueueState(StrEnum): + """RX queue states.""" + + #: + stopped = auto() + #: + started = auto() + #: + hairpin = auto() + #: + unknown = auto() + + @classmethod + def make_parser(cls) -> ParserFn: + """Makes a parser function. + + Returns: + ParserFn: A dictionary for the `dataclasses.field` metadata argument containing a + parser function that makes an instance of this enum from text. + """ + return TextParser.wrap(TextParser.find(r"Rx queue state: ([^\r\n]+)"), cls) + + +@dataclass +class TestPmdRxqInfo(TextParser): + """Representation of testpmd's ``show rxq info `` command.""" + + #: + port_id: int = field(metadata=TextParser.find_int(r"Infos for port (\d+)\b ?, RX queue \d+\b")) + #: + queue_id: int = field(metadata=TextParser.find_int(r"Infos for port \d+\b ?, RX queue (\d+)\b")) + #: Mempool used by that queue + mempool: str = field(metadata=TextParser.find(r"Mempool: ([^\r\n]+)")) + #: Ring prefetch threshold + rx_prefetch_threshold: int = field( + metadata=TextParser.find_int(r"RX prefetch threshold: (\d+)\b") + ) + #: Ring host threshold + rx_host_threshold: int = field(metadata=TextParser.find_int(r"RX host threshold: (\d+)\b")) + #: Ring writeback threshold + rx_writeback_threshold: int = field( + metadata=TextParser.find_int(r"RX writeback threshold: (\d+)\b") + ) + #: Drives the freeing of Rx descriptors + rx_free_threshold: int = field(metadata=TextParser.find_int(r"RX free threshold: (\d+)\b")) + #: Drop packets if no descriptors are available + rx_drop_packets: bool = field(metadata=TextParser.find(r"RX drop packets: on")) + #: Do not start queue with rte_eth_dev_start() + rx_deferred_start: bool = field(metadata=TextParser.find(r"RX deferred start: on")) + #: Scattered packets Rx enabled + rx_scattered_packets: bool = field(metadata=TextParser.find(r"RX scattered packets: on")) + #: The state of the queue + rx_queue_state: str = field(metadata=RxQueueState.make_parser()) + #: Configured number of RXDs + number_of_rxds: int = field(metadata=TextParser.find_int(r"Number of RXDs: (\d+)\b")) + #: Hardware receive buffer size + rx_buffer_size: int | None = field( + default=None, metadata=TextParser.find_int(r"RX buffer size: (\d+)\b") + ) + #: Burst mode information + burst_mode: str | None = field( + default=None, metadata=TextParser.find(r"Burst mode: ([^\r\n]+)") + ) + + @dataclass class TestPmdPort(TextParser): """Dataclass representing the result of testpmd's ``show port info`` command.""" @@ -962,3 +1043,96 @@ def _close(self) -> None: self.stop() self.send_command("quit", "Bye...") return super()._close() + + """ + ====== Capability retrieval methods ====== + """ + + def get_capabilities_rxq_info( + self, + supported_capabilities: MutableSet["NicCapability"], + unsupported_capabilities: MutableSet["NicCapability"], + ) -> None: + """Get all rxq capabilities and divide them into supported and unsupported. + + Args: + supported_capabilities: Supported capabilities will be added to this set. + unsupported_capabilities: Unsupported capabilities will be added to this set. + """ + self._logger.debug("Getting rxq capabilities.") + command = f"show rxq info {self.ports[0].id} 0" + rxq_info = TestPmdRxqInfo.parse(self.send_command(command)) + if rxq_info.rx_scattered_packets: + supported_capabilities.add(NicCapability.SCATTERED_RX_ENABLED) + else: + unsupported_capabilities.add(NicCapability.SCATTERED_RX_ENABLED) + + """ + ====== Decorator methods ====== + """ + + @staticmethod + def config_mtu_9000(testpmd_method: TestPmdShellSimpleMethod) -> TestPmdShellDecoratedMethod: + """Configure MTU to 9000 on all ports, run `testpmd_method`, then revert. + + Args: + testpmd_method: The method to decorate. + + Returns: + The method decorated with setting and reverting MTU. + """ + + def wrapper(testpmd_shell: Self): + original_mtus = [] + for port in testpmd_shell.ports: + original_mtus.append((port.id, port.mtu)) + testpmd_shell.set_port_mtu(port_id=port.id, mtu=9000, verify=False) + testpmd_method(testpmd_shell) + for port_id, mtu in original_mtus: + testpmd_shell.set_port_mtu(port_id=port_id, mtu=mtu if mtu else 1500, verify=False) + + return wrapper + + +class NicCapability(NoAliasEnum): + """A mapping between capability names and the associated :class:`TestPmdShell` methods. + + The :class:`TestPmdShell` method executes the command that checks + whether the capability is supported. + + The signature of each :class:`TestPmdShell` capability checking method must be:: + + fn(self, supported_capabilities: MutableSet, unsupported_capabilities: MutableSet) -> None + + The method must get whether a capability is supported or not from a testpmd command. + If multiple capabilities can be obtained from a testpmd command, each should be obtained + in the method. These capabilities should then be added to `supported_capabilities` or + `unsupported_capabilities` based on their support. + + The two dictionaries are shared across all capability discovery function calls in a given + test run so that we don't call the same function multiple times, + e.g. when we find a :attr:`scattered_rx` in :meth:`TestPmdShell.get_capabilities_rxq`, we don't + go looking for it again if a different test case also needs it. + """ + + #: Scattered packets Rx enabled. + SCATTERED_RX_ENABLED: TestPmdShellCapabilityMethod = partial( + TestPmdShell.get_capabilities_rxq_info + ) + + def __call__( + self, + testpmd_shell: TestPmdShell, + supported_capabilities: MutableSet[Self], + unsupported_capabilities: MutableSet[Self], + ) -> None: + """Execute the associated capability retrieval function. + + Args: + testpmd_shell: :class:`TestPmdShell` object to which the function will be bound to. + supported_capabilities: The dictionary storing the supported capabilities + of a given test run. + unsupported_capabilities: The dictionary storing the unsupported capabilities + of a given test run. + """ + self.value(testpmd_shell, supported_capabilities, unsupported_capabilities) diff --git a/dts/framework/testbed_model/capability.py b/dts/framework/testbed_model/capability.py index 8899f07f76..9a79e6ebb3 100644 --- a/dts/framework/testbed_model/capability.py +++ b/dts/framework/testbed_model/capability.py @@ -5,14 +5,40 @@ This module provides a protocol that defines the common attributes of test cases and suites and support for test environment capabilities. + +Many test cases are testing features not available on all hardware. + +The module also allows developers to mark test cases or suites a requiring certain +hardware capabilities with the :func:`requires` decorator. + +Example: + .. code:: python + + from framework.test_suite import TestSuite, func_test + from framework.testbed_model.capability import NicCapability, requires + class TestPmdBufferScatter(TestSuite): + # only the test case requires the scattered_rx capability + # other test cases may not require it + @requires(NicCapability.scattered_rx) + @func_test + def test_scatter_mbuf_2048(self): """ from abc import ABC, abstractmethod -from collections.abc import Sequence -from typing import Callable, ClassVar, Protocol +from collections.abc import MutableSet, Sequence +from dataclasses import dataclass +from typing import Callable, ClassVar, Protocol, Tuple from typing_extensions import Self +from framework.logger import get_dts_logger +from framework.remote_session.testpmd_shell import ( + NicCapability, + TestPmdShell, + TestPmdShellDecorator, + TestPmdShellSimpleMethod, +) + from .sut_node import SutNode from .topology import Topology @@ -96,6 +122,128 @@ def __hash__(self) -> int: """The subclasses must be hashable so that they can be stored in sets.""" +@dataclass +class DecoratedNicCapability(Capability): + """A wrapper around :class:`~framework.remote_session.testpmd_shell.NicCapability`. + + Some NIC capabilities are only present or listed as supported only under certain conditions, + such as when a particular configuration is in place. This is achieved by allowing users to pass + a decorator function that decorates the function that gets the support status of the capability. + + New instances should be created with the :meth:`create_unique` class method to ensure + there are no duplicate instances. + + Attributes: + nic_capability: The NIC capability that partly defines each instance. + capability_decorator: The decorator function that will be passed the function associated + with `nic_capability` when discovering the support status of the capability. + Each instance is defined by `capability_decorator` along with `nic_capability`. + """ + + nic_capability: NicCapability + capability_decorator: TestPmdShellDecorator | None + _unique_capabilities: ClassVar[ + dict[Tuple[NicCapability, TestPmdShellDecorator | None], Self] + ] = {} + + @classmethod + def get_unique( + cls, nic_capability: NicCapability, decorator_fn: TestPmdShellDecorator | None + ) -> "DecoratedNicCapability": + """Get the capability uniquely identified by `nic_capability` and `decorator_fn`. + + Args: + nic_capability: The NIC capability. + decorator_fn: The function that will be passed the function associated + with `nic_capability` when discovering the support status of the capability. + + Returns: + The capability uniquely identified by `nic_capability` and `decorator_fn`. + """ + if (nic_capability, decorator_fn) not in cls._unique_capabilities: + cls._unique_capabilities[(nic_capability, decorator_fn)] = cls( + nic_capability, decorator_fn + ) + return cls._unique_capabilities[(nic_capability, decorator_fn)] + + @classmethod + def get_supported_capabilities( + cls, sut_node: SutNode, topology: "Topology" + ) -> set["DecoratedNicCapability"]: + """Overrides :meth:`~Capability.get_supported_capabilities`. + + The capabilities are first sorted by decorators, then reduced into a single function which + is then passed to the decorator. This way we only execute each decorator only once. + """ + supported_conditional_capabilities: set["DecoratedNicCapability"] = set() + logger = get_dts_logger(f"{sut_node.name}.{cls.__name__}") + if topology.type is Topology.type.no_link: + logger.debug( + "No links available in the current topology, not getting NIC capabilities." + ) + return supported_conditional_capabilities + logger.debug( + f"Checking which NIC capabilities from {cls.capabilities_to_check} are supported." + ) + if cls.capabilities_to_check: + capabilities_to_check_map = cls._get_decorated_capabilities_map() + with TestPmdShell(sut_node, privileged=True) as testpmd_shell: + for conditional_capability_fn, capabilities in capabilities_to_check_map.items(): + supported_capabilities: set[NicCapability] = set() + unsupported_capabilities: set[NicCapability] = set() + capability_fn = cls._reduce_capabilities( + capabilities, supported_capabilities, unsupported_capabilities + ) + if conditional_capability_fn: + capability_fn = conditional_capability_fn(capability_fn) + capability_fn(testpmd_shell) + for supported_capability in supported_capabilities: + for capability in capabilities: + if supported_capability == capability.nic_capability: + supported_conditional_capabilities.add(capability) + + logger.debug(f"Found supported capabilities {supported_conditional_capabilities}.") + return supported_conditional_capabilities + + @classmethod + def _get_decorated_capabilities_map( + cls, + ) -> dict[TestPmdShellDecorator | None, set["DecoratedNicCapability"]]: + capabilities_map: dict[TestPmdShellDecorator | None, set["DecoratedNicCapability"]] = {} + for capability in cls.capabilities_to_check: + if capability.capability_decorator not in capabilities_map: + capabilities_map[capability.capability_decorator] = set() + capabilities_map[capability.capability_decorator].add(capability) + + return capabilities_map + + @classmethod + def _reduce_capabilities( + cls, + capabilities: set["DecoratedNicCapability"], + supported_capabilities: MutableSet, + unsupported_capabilities: MutableSet, + ) -> TestPmdShellSimpleMethod: + def reduced_fn(testpmd_shell: TestPmdShell) -> None: + for capability in capabilities: + capability.nic_capability( + testpmd_shell, supported_capabilities, unsupported_capabilities + ) + + return reduced_fn + + def __hash__(self) -> int: + """Instances are identified by :attr:`nic_capability` and :attr:`capability_decorator`.""" + return hash((self.nic_capability, self.capability_decorator)) + + def __repr__(self) -> str: + """Easy to read string of :attr:`nic_capability` and :attr:`capability_decorator`.""" + condition_fn_name = "" + if self.capability_decorator: + condition_fn_name = f"{self.capability_decorator.__qualname__}|" + return f"{condition_fn_name}{self.nic_capability}" + + class TestProtocol(Protocol): """Common test suite and test case attributes.""" @@ -116,6 +264,34 @@ def get_test_cases(cls, test_case_sublist: Sequence[str] | None = None) -> tuple raise NotImplementedError() +def requires( + *nic_capabilities: NicCapability, + decorator_fn: TestPmdShellDecorator | None = None, +) -> Callable[[type[TestProtocol]], type[TestProtocol]]: + """A decorator that adds the required capabilities to a test case or test suite. + + Args: + nic_capabilities: The NIC capabilities that are required by the test case or test suite. + decorator_fn: The decorator function that will be used when getting + NIC capability support status. + topology_type: The topology type the test suite or case requires. + + Returns: + The decorated test case or test suite. + """ + + def add_required_capability(test_case_or_suite: type[TestProtocol]) -> type[TestProtocol]: + for nic_capability in nic_capabilities: + decorated_nic_capability = DecoratedNicCapability.get_unique( + nic_capability, decorator_fn + ) + decorated_nic_capability.add_to_required(test_case_or_suite) + + return test_case_or_suite + + return add_required_capability + + def get_supported_capabilities( sut_node: SutNode, topology_config: Topology, diff --git a/dts/tests/TestSuite_pmd_buffer_scatter.py b/dts/tests/TestSuite_pmd_buffer_scatter.py index 178a40385e..713549a5b2 100644 --- a/dts/tests/TestSuite_pmd_buffer_scatter.py +++ b/dts/tests/TestSuite_pmd_buffer_scatter.py @@ -25,6 +25,7 @@ from framework.params.testpmd import SimpleForwardingModes from framework.remote_session.testpmd_shell import TestPmdShell from framework.test_suite import TestSuite, func_test +from framework.testbed_model.capability import NicCapability, requires class TestPmdBufferScatter(TestSuite): @@ -123,6 +124,7 @@ def pmd_scatter(self, mbsize: int) -> None: f"{offset}.", ) + @requires(NicCapability.SCATTERED_RX_ENABLED, decorator_fn=TestPmdShell.config_mtu_9000) @func_test def test_scatter_mbuf_2048(self) -> None: """Run the :meth:`pmd_scatter` test with `mbsize` set to 2048.""" -- 2.34.1