xref: /dpdk/dts/framework/remote_session/testpmd_shell.py (revision 618d9140e5efd9c59a8143b95ac6007961d4db7d)
1840b1e01SJuraj Linkeš# SPDX-License-Identifier: BSD-3-Clause
2840b1e01SJuraj Linkeš# Copyright(c) 2023 University of New Hampshire
36ef07151SJuraj Linkeš# Copyright(c) 2023 PANTHEON.tech s.r.o.
461d5bc9bSLuca Vizzarro# Copyright(c) 2024 Arm Limited
56ef07151SJuraj Linkeš
66ef07151SJuraj Linkeš"""Testpmd interactive shell.
76ef07151SJuraj Linkeš
86ef07151SJuraj LinkešTypical usage example in a TestSuite::
96ef07151SJuraj Linkeš
10bfad0948SLuca Vizzarro    testpmd_shell = TestPmdShell(self.sut_node)
116ef07151SJuraj Linkeš    devices = testpmd_shell.get_devices()
126ef07151SJuraj Linkeš    for device in devices:
136ef07151SJuraj Linkeš        print(device)
146ef07151SJuraj Linkeš    testpmd_shell.close()
156ef07151SJuraj Linkeš"""
16840b1e01SJuraj Linkeš
172ecfd126SLuca Vizzarroimport functools
1861d5bc9bSLuca Vizzarroimport re
19369d34b8SJeremy Spewockimport time
2061d5bc9bSLuca Vizzarrofrom dataclasses import dataclass, field
2161d5bc9bSLuca Vizzarrofrom enum import Flag, auto
22840b1e01SJuraj Linkešfrom pathlib import PurePath
232ecfd126SLuca Vizzarrofrom typing import Any, Callable, ClassVar, Concatenate, ParamSpec
24840b1e01SJuraj Linkeš
2587ba4cdcSLuca Vizzarrofrom typing_extensions import Self, Unpack
2661d5bc9bSLuca Vizzarro
272ecfd126SLuca Vizzarrofrom framework.exception import InteractiveCommandExecutionError, InternalError
28fc0f7dc4SLuca Vizzarrofrom framework.params.testpmd import SimpleForwardingModes, TestPmdParams
2987ba4cdcSLuca Vizzarrofrom framework.params.types import TestPmdParamsDict
3061d5bc9bSLuca Vizzarrofrom framework.parser import ParserFn, TextParser
31bfad0948SLuca Vizzarrofrom framework.remote_session.dpdk_shell import DPDKShell
32369d34b8SJeremy Spewockfrom framework.settings import SETTINGS
33bfad0948SLuca Vizzarrofrom framework.testbed_model.cpu import LogicalCoreCount, LogicalCoreList
34bfad0948SLuca Vizzarrofrom framework.testbed_model.sut_node import SutNode
35369d34b8SJeremy Spewockfrom framework.utils import StrEnum
36369d34b8SJeremy Spewock
372ecfd126SLuca VizzarroP = ParamSpec("P")
382ecfd126SLuca VizzarroTestPmdShellMethod = Callable[Concatenate["TestPmdShell", P], Any]
392ecfd126SLuca Vizzarro
40840b1e01SJuraj Linkeš
413e967643SJuraj Linkešclass TestPmdDevice:
426ef07151SJuraj Linkeš    """The data of a device that testpmd can recognize.
436ef07151SJuraj Linkeš
446ef07151SJuraj Linkeš    Attributes:
456ef07151SJuraj Linkeš        pci_address: The PCI address of the device.
466ef07151SJuraj Linkeš    """
476ef07151SJuraj Linkeš
48840b1e01SJuraj Linkeš    pci_address: str
49840b1e01SJuraj Linkeš
50840b1e01SJuraj Linkeš    def __init__(self, pci_address_line: str):
516ef07151SJuraj Linkeš        """Initialize the device from the testpmd output line string.
526ef07151SJuraj Linkeš
536ef07151SJuraj Linkeš        Args:
546ef07151SJuraj Linkeš            pci_address_line: A line of testpmd output that contains a device.
556ef07151SJuraj Linkeš        """
56840b1e01SJuraj Linkeš        self.pci_address = pci_address_line.strip().split(": ")[1].strip()
57840b1e01SJuraj Linkeš
58840b1e01SJuraj Linkeš    def __str__(self) -> str:
596ef07151SJuraj Linkeš        """The PCI address captures what the device is."""
60840b1e01SJuraj Linkeš        return self.pci_address
61840b1e01SJuraj Linkeš
62840b1e01SJuraj Linkeš
6361d5bc9bSLuca Vizzarroclass VLANOffloadFlag(Flag):
6461d5bc9bSLuca Vizzarro    """Flag representing the VLAN offload settings of a NIC port."""
6561d5bc9bSLuca Vizzarro
6661d5bc9bSLuca Vizzarro    #:
6761d5bc9bSLuca Vizzarro    STRIP = auto()
6861d5bc9bSLuca Vizzarro    #:
6961d5bc9bSLuca Vizzarro    FILTER = auto()
7061d5bc9bSLuca Vizzarro    #:
7161d5bc9bSLuca Vizzarro    EXTEND = auto()
7261d5bc9bSLuca Vizzarro    #:
7361d5bc9bSLuca Vizzarro    QINQ_STRIP = auto()
7461d5bc9bSLuca Vizzarro
7561d5bc9bSLuca Vizzarro    @classmethod
7661d5bc9bSLuca Vizzarro    def from_str_dict(cls, d):
7761d5bc9bSLuca Vizzarro        """Makes an instance from a dict containing the flag member names with an "on" value.
7861d5bc9bSLuca Vizzarro
7961d5bc9bSLuca Vizzarro        Args:
8061d5bc9bSLuca Vizzarro            d: A dictionary containing the flag members as keys and any string value.
8161d5bc9bSLuca Vizzarro
8261d5bc9bSLuca Vizzarro        Returns:
8361d5bc9bSLuca Vizzarro            A new instance of the flag.
8461d5bc9bSLuca Vizzarro        """
8561d5bc9bSLuca Vizzarro        flag = cls(0)
8661d5bc9bSLuca Vizzarro        for name in cls.__members__:
8761d5bc9bSLuca Vizzarro            if d.get(name) == "on":
8861d5bc9bSLuca Vizzarro                flag |= cls[name]
8961d5bc9bSLuca Vizzarro        return flag
9061d5bc9bSLuca Vizzarro
9161d5bc9bSLuca Vizzarro    @classmethod
9261d5bc9bSLuca Vizzarro    def make_parser(cls) -> ParserFn:
9361d5bc9bSLuca Vizzarro        """Makes a parser function.
9461d5bc9bSLuca Vizzarro
9561d5bc9bSLuca Vizzarro        Returns:
9661d5bc9bSLuca Vizzarro            ParserFn: A dictionary for the `dataclasses.field` metadata argument containing a
9761d5bc9bSLuca Vizzarro                parser function that makes an instance of this flag from text.
9861d5bc9bSLuca Vizzarro        """
9961d5bc9bSLuca Vizzarro        return TextParser.wrap(
10061d5bc9bSLuca Vizzarro            TextParser.find(
10161d5bc9bSLuca Vizzarro                r"VLAN offload:\s+"
10261d5bc9bSLuca Vizzarro                r"strip (?P<STRIP>on|off), "
10361d5bc9bSLuca Vizzarro                r"filter (?P<FILTER>on|off), "
10461d5bc9bSLuca Vizzarro                r"extend (?P<EXTEND>on|off), "
10561d5bc9bSLuca Vizzarro                r"qinq strip (?P<QINQ_STRIP>on|off)$",
10661d5bc9bSLuca Vizzarro                re.MULTILINE,
10761d5bc9bSLuca Vizzarro                named=True,
10861d5bc9bSLuca Vizzarro            ),
10961d5bc9bSLuca Vizzarro            cls.from_str_dict,
11061d5bc9bSLuca Vizzarro        )
11161d5bc9bSLuca Vizzarro
11261d5bc9bSLuca Vizzarro
11361d5bc9bSLuca Vizzarroclass RSSOffloadTypesFlag(Flag):
11461d5bc9bSLuca Vizzarro    """Flag representing the RSS offload flow types supported by the NIC port."""
11561d5bc9bSLuca Vizzarro
11661d5bc9bSLuca Vizzarro    #:
11761d5bc9bSLuca Vizzarro    ipv4 = auto()
11861d5bc9bSLuca Vizzarro    #:
11961d5bc9bSLuca Vizzarro    ipv4_frag = auto()
12061d5bc9bSLuca Vizzarro    #:
12161d5bc9bSLuca Vizzarro    ipv4_tcp = auto()
12261d5bc9bSLuca Vizzarro    #:
12361d5bc9bSLuca Vizzarro    ipv4_udp = auto()
12461d5bc9bSLuca Vizzarro    #:
12561d5bc9bSLuca Vizzarro    ipv4_sctp = auto()
12661d5bc9bSLuca Vizzarro    #:
12761d5bc9bSLuca Vizzarro    ipv4_other = auto()
12861d5bc9bSLuca Vizzarro    #:
12961d5bc9bSLuca Vizzarro    ipv6 = auto()
13061d5bc9bSLuca Vizzarro    #:
13161d5bc9bSLuca Vizzarro    ipv6_frag = auto()
13261d5bc9bSLuca Vizzarro    #:
13361d5bc9bSLuca Vizzarro    ipv6_tcp = auto()
13461d5bc9bSLuca Vizzarro    #:
13561d5bc9bSLuca Vizzarro    ipv6_udp = auto()
13661d5bc9bSLuca Vizzarro    #:
13761d5bc9bSLuca Vizzarro    ipv6_sctp = auto()
13861d5bc9bSLuca Vizzarro    #:
13961d5bc9bSLuca Vizzarro    ipv6_other = auto()
14061d5bc9bSLuca Vizzarro    #:
14161d5bc9bSLuca Vizzarro    l2_payload = auto()
14261d5bc9bSLuca Vizzarro    #:
14361d5bc9bSLuca Vizzarro    ipv6_ex = auto()
14461d5bc9bSLuca Vizzarro    #:
14561d5bc9bSLuca Vizzarro    ipv6_tcp_ex = auto()
14661d5bc9bSLuca Vizzarro    #:
14761d5bc9bSLuca Vizzarro    ipv6_udp_ex = auto()
14861d5bc9bSLuca Vizzarro    #:
14961d5bc9bSLuca Vizzarro    port = auto()
15061d5bc9bSLuca Vizzarro    #:
15161d5bc9bSLuca Vizzarro    vxlan = auto()
15261d5bc9bSLuca Vizzarro    #:
15361d5bc9bSLuca Vizzarro    geneve = auto()
15461d5bc9bSLuca Vizzarro    #:
15561d5bc9bSLuca Vizzarro    nvgre = auto()
15661d5bc9bSLuca Vizzarro    #:
15761d5bc9bSLuca Vizzarro    user_defined_22 = auto()
15861d5bc9bSLuca Vizzarro    #:
15961d5bc9bSLuca Vizzarro    gtpu = auto()
16061d5bc9bSLuca Vizzarro    #:
16161d5bc9bSLuca Vizzarro    eth = auto()
16261d5bc9bSLuca Vizzarro    #:
16361d5bc9bSLuca Vizzarro    s_vlan = auto()
16461d5bc9bSLuca Vizzarro    #:
16561d5bc9bSLuca Vizzarro    c_vlan = auto()
16661d5bc9bSLuca Vizzarro    #:
16761d5bc9bSLuca Vizzarro    esp = auto()
16861d5bc9bSLuca Vizzarro    #:
16961d5bc9bSLuca Vizzarro    ah = auto()
17061d5bc9bSLuca Vizzarro    #:
17161d5bc9bSLuca Vizzarro    l2tpv3 = auto()
17261d5bc9bSLuca Vizzarro    #:
17361d5bc9bSLuca Vizzarro    pfcp = auto()
17461d5bc9bSLuca Vizzarro    #:
17561d5bc9bSLuca Vizzarro    pppoe = auto()
17661d5bc9bSLuca Vizzarro    #:
17761d5bc9bSLuca Vizzarro    ecpri = auto()
17861d5bc9bSLuca Vizzarro    #:
17961d5bc9bSLuca Vizzarro    mpls = auto()
18061d5bc9bSLuca Vizzarro    #:
18161d5bc9bSLuca Vizzarro    ipv4_chksum = auto()
18261d5bc9bSLuca Vizzarro    #:
18361d5bc9bSLuca Vizzarro    l4_chksum = auto()
18461d5bc9bSLuca Vizzarro    #:
18561d5bc9bSLuca Vizzarro    l2tpv2 = auto()
18661d5bc9bSLuca Vizzarro    #:
18761d5bc9bSLuca Vizzarro    ipv6_flow_label = auto()
18861d5bc9bSLuca Vizzarro    #:
18961d5bc9bSLuca Vizzarro    user_defined_38 = auto()
19061d5bc9bSLuca Vizzarro    #:
19161d5bc9bSLuca Vizzarro    user_defined_39 = auto()
19261d5bc9bSLuca Vizzarro    #:
19361d5bc9bSLuca Vizzarro    user_defined_40 = auto()
19461d5bc9bSLuca Vizzarro    #:
19561d5bc9bSLuca Vizzarro    user_defined_41 = auto()
19661d5bc9bSLuca Vizzarro    #:
19761d5bc9bSLuca Vizzarro    user_defined_42 = auto()
19861d5bc9bSLuca Vizzarro    #:
19961d5bc9bSLuca Vizzarro    user_defined_43 = auto()
20061d5bc9bSLuca Vizzarro    #:
20161d5bc9bSLuca Vizzarro    user_defined_44 = auto()
20261d5bc9bSLuca Vizzarro    #:
20361d5bc9bSLuca Vizzarro    user_defined_45 = auto()
20461d5bc9bSLuca Vizzarro    #:
20561d5bc9bSLuca Vizzarro    user_defined_46 = auto()
20661d5bc9bSLuca Vizzarro    #:
20761d5bc9bSLuca Vizzarro    user_defined_47 = auto()
20861d5bc9bSLuca Vizzarro    #:
20961d5bc9bSLuca Vizzarro    user_defined_48 = auto()
21061d5bc9bSLuca Vizzarro    #:
21161d5bc9bSLuca Vizzarro    user_defined_49 = auto()
21261d5bc9bSLuca Vizzarro    #:
21361d5bc9bSLuca Vizzarro    user_defined_50 = auto()
21461d5bc9bSLuca Vizzarro    #:
21561d5bc9bSLuca Vizzarro    user_defined_51 = auto()
21661d5bc9bSLuca Vizzarro    #:
21761d5bc9bSLuca Vizzarro    l3_pre96 = auto()
21861d5bc9bSLuca Vizzarro    #:
21961d5bc9bSLuca Vizzarro    l3_pre64 = auto()
22061d5bc9bSLuca Vizzarro    #:
22161d5bc9bSLuca Vizzarro    l3_pre56 = auto()
22261d5bc9bSLuca Vizzarro    #:
22361d5bc9bSLuca Vizzarro    l3_pre48 = auto()
22461d5bc9bSLuca Vizzarro    #:
22561d5bc9bSLuca Vizzarro    l3_pre40 = auto()
22661d5bc9bSLuca Vizzarro    #:
22761d5bc9bSLuca Vizzarro    l3_pre32 = auto()
22861d5bc9bSLuca Vizzarro    #:
22961d5bc9bSLuca Vizzarro    l2_dst_only = auto()
23061d5bc9bSLuca Vizzarro    #:
23161d5bc9bSLuca Vizzarro    l2_src_only = auto()
23261d5bc9bSLuca Vizzarro    #:
23361d5bc9bSLuca Vizzarro    l4_dst_only = auto()
23461d5bc9bSLuca Vizzarro    #:
23561d5bc9bSLuca Vizzarro    l4_src_only = auto()
23661d5bc9bSLuca Vizzarro    #:
23761d5bc9bSLuca Vizzarro    l3_dst_only = auto()
23861d5bc9bSLuca Vizzarro    #:
23961d5bc9bSLuca Vizzarro    l3_src_only = auto()
24061d5bc9bSLuca Vizzarro
24161d5bc9bSLuca Vizzarro    #:
24261d5bc9bSLuca Vizzarro    ip = ipv4 | ipv4_frag | ipv4_other | ipv6 | ipv6_frag | ipv6_other | ipv6_ex
24361d5bc9bSLuca Vizzarro    #:
24461d5bc9bSLuca Vizzarro    udp = ipv4_udp | ipv6_udp | ipv6_udp_ex
24561d5bc9bSLuca Vizzarro    #:
24661d5bc9bSLuca Vizzarro    tcp = ipv4_tcp | ipv6_tcp | ipv6_tcp_ex
24761d5bc9bSLuca Vizzarro    #:
24861d5bc9bSLuca Vizzarro    sctp = ipv4_sctp | ipv6_sctp
24961d5bc9bSLuca Vizzarro    #:
25061d5bc9bSLuca Vizzarro    tunnel = vxlan | geneve | nvgre
25161d5bc9bSLuca Vizzarro    #:
25261d5bc9bSLuca Vizzarro    vlan = s_vlan | c_vlan
25361d5bc9bSLuca Vizzarro    #:
25461d5bc9bSLuca Vizzarro    all = (
25561d5bc9bSLuca Vizzarro        eth
25661d5bc9bSLuca Vizzarro        | vlan
25761d5bc9bSLuca Vizzarro        | ip
25861d5bc9bSLuca Vizzarro        | tcp
25961d5bc9bSLuca Vizzarro        | udp
26061d5bc9bSLuca Vizzarro        | sctp
26161d5bc9bSLuca Vizzarro        | l2_payload
26261d5bc9bSLuca Vizzarro        | l2tpv3
26361d5bc9bSLuca Vizzarro        | esp
26461d5bc9bSLuca Vizzarro        | ah
26561d5bc9bSLuca Vizzarro        | pfcp
26661d5bc9bSLuca Vizzarro        | gtpu
26761d5bc9bSLuca Vizzarro        | ecpri
26861d5bc9bSLuca Vizzarro        | mpls
26961d5bc9bSLuca Vizzarro        | l2tpv2
27061d5bc9bSLuca Vizzarro    )
27161d5bc9bSLuca Vizzarro
27261d5bc9bSLuca Vizzarro    @classmethod
27361d5bc9bSLuca Vizzarro    def from_list_string(cls, names: str) -> Self:
27461d5bc9bSLuca Vizzarro        """Makes a flag from a whitespace-separated list of names.
27561d5bc9bSLuca Vizzarro
27661d5bc9bSLuca Vizzarro        Args:
27761d5bc9bSLuca Vizzarro            names: a whitespace-separated list containing the members of this flag.
27861d5bc9bSLuca Vizzarro
27961d5bc9bSLuca Vizzarro        Returns:
28061d5bc9bSLuca Vizzarro            An instance of this flag.
28161d5bc9bSLuca Vizzarro        """
28261d5bc9bSLuca Vizzarro        flag = cls(0)
28361d5bc9bSLuca Vizzarro        for name in names.split():
28461d5bc9bSLuca Vizzarro            flag |= cls.from_str(name)
28561d5bc9bSLuca Vizzarro        return flag
28661d5bc9bSLuca Vizzarro
28761d5bc9bSLuca Vizzarro    @classmethod
28861d5bc9bSLuca Vizzarro    def from_str(cls, name: str) -> Self:
28961d5bc9bSLuca Vizzarro        """Makes a flag matching the supplied name.
29061d5bc9bSLuca Vizzarro
29161d5bc9bSLuca Vizzarro        Args:
29261d5bc9bSLuca Vizzarro            name: a valid member of this flag in text
29361d5bc9bSLuca Vizzarro        Returns:
29461d5bc9bSLuca Vizzarro            An instance of this flag.
29561d5bc9bSLuca Vizzarro        """
29661d5bc9bSLuca Vizzarro        member_name = name.strip().replace("-", "_")
29761d5bc9bSLuca Vizzarro        return cls[member_name]
29861d5bc9bSLuca Vizzarro
29961d5bc9bSLuca Vizzarro    @classmethod
30061d5bc9bSLuca Vizzarro    def make_parser(cls) -> ParserFn:
30161d5bc9bSLuca Vizzarro        """Makes a parser function.
30261d5bc9bSLuca Vizzarro
30361d5bc9bSLuca Vizzarro        Returns:
30461d5bc9bSLuca Vizzarro            ParserFn: A dictionary for the `dataclasses.field` metadata argument containing a
30561d5bc9bSLuca Vizzarro                parser function that makes an instance of this flag from text.
30661d5bc9bSLuca Vizzarro        """
30761d5bc9bSLuca Vizzarro        return TextParser.wrap(
30861d5bc9bSLuca Vizzarro            TextParser.find(r"Supported RSS offload flow types:((?:\r?\n?  \S+)+)", re.MULTILINE),
30961d5bc9bSLuca Vizzarro            RSSOffloadTypesFlag.from_list_string,
31061d5bc9bSLuca Vizzarro        )
31161d5bc9bSLuca Vizzarro
31261d5bc9bSLuca Vizzarro
31361d5bc9bSLuca Vizzarroclass DeviceCapabilitiesFlag(Flag):
31461d5bc9bSLuca Vizzarro    """Flag representing the device capabilities."""
31561d5bc9bSLuca Vizzarro
31661d5bc9bSLuca Vizzarro    #: Device supports Rx queue setup after device started.
31761d5bc9bSLuca Vizzarro    RUNTIME_RX_QUEUE_SETUP = auto()
31861d5bc9bSLuca Vizzarro    #: Device supports Tx queue setup after device started.
31961d5bc9bSLuca Vizzarro    RUNTIME_TX_QUEUE_SETUP = auto()
32061d5bc9bSLuca Vizzarro    #: Device supports shared Rx queue among ports within Rx domain and switch domain.
32161d5bc9bSLuca Vizzarro    RXQ_SHARE = auto()
32261d5bc9bSLuca Vizzarro    #: Device supports keeping flow rules across restart.
32361d5bc9bSLuca Vizzarro    FLOW_RULE_KEEP = auto()
32461d5bc9bSLuca Vizzarro    #: Device supports keeping shared flow objects across restart.
32561d5bc9bSLuca Vizzarro    FLOW_SHARED_OBJECT_KEEP = auto()
32661d5bc9bSLuca Vizzarro
32761d5bc9bSLuca Vizzarro    @classmethod
32861d5bc9bSLuca Vizzarro    def make_parser(cls) -> ParserFn:
32961d5bc9bSLuca Vizzarro        """Makes a parser function.
33061d5bc9bSLuca Vizzarro
33161d5bc9bSLuca Vizzarro        Returns:
33261d5bc9bSLuca Vizzarro            ParserFn: A dictionary for the `dataclasses.field` metadata argument containing a
33361d5bc9bSLuca Vizzarro                parser function that makes an instance of this flag from text.
33461d5bc9bSLuca Vizzarro        """
33561d5bc9bSLuca Vizzarro        return TextParser.wrap(
33661d5bc9bSLuca Vizzarro            TextParser.find_int(r"Device capabilities: (0x[A-Fa-f\d]+)"),
33761d5bc9bSLuca Vizzarro            cls,
33861d5bc9bSLuca Vizzarro        )
33961d5bc9bSLuca Vizzarro
34061d5bc9bSLuca Vizzarro
34161d5bc9bSLuca Vizzarroclass DeviceErrorHandlingMode(StrEnum):
34261d5bc9bSLuca Vizzarro    """Enum representing the device error handling mode."""
34361d5bc9bSLuca Vizzarro
34461d5bc9bSLuca Vizzarro    #:
34561d5bc9bSLuca Vizzarro    none = auto()
34661d5bc9bSLuca Vizzarro    #:
34761d5bc9bSLuca Vizzarro    passive = auto()
34861d5bc9bSLuca Vizzarro    #:
34961d5bc9bSLuca Vizzarro    proactive = auto()
35061d5bc9bSLuca Vizzarro    #:
35161d5bc9bSLuca Vizzarro    unknown = auto()
35261d5bc9bSLuca Vizzarro
35361d5bc9bSLuca Vizzarro    @classmethod
35461d5bc9bSLuca Vizzarro    def make_parser(cls) -> ParserFn:
35561d5bc9bSLuca Vizzarro        """Makes a parser function.
35661d5bc9bSLuca Vizzarro
35761d5bc9bSLuca Vizzarro        Returns:
35861d5bc9bSLuca Vizzarro            ParserFn: A dictionary for the `dataclasses.field` metadata argument containing a
35961d5bc9bSLuca Vizzarro                parser function that makes an instance of this enum from text.
36061d5bc9bSLuca Vizzarro        """
36161d5bc9bSLuca Vizzarro        return TextParser.wrap(TextParser.find(r"Device error handling mode: (\w+)"), cls)
36261d5bc9bSLuca Vizzarro
36361d5bc9bSLuca Vizzarro
36461d5bc9bSLuca Vizzarrodef make_device_private_info_parser() -> ParserFn:
36561d5bc9bSLuca Vizzarro    """Device private information parser.
36661d5bc9bSLuca Vizzarro
36761d5bc9bSLuca Vizzarro    Ensures that we are not parsing invalid device private info output.
36861d5bc9bSLuca Vizzarro
36961d5bc9bSLuca Vizzarro    Returns:
37061d5bc9bSLuca Vizzarro        ParserFn: A dictionary for the `dataclasses.field` metadata argument containing a parser
37161d5bc9bSLuca Vizzarro            function that parses the device private info from the TestPmd port info output.
37261d5bc9bSLuca Vizzarro    """
37361d5bc9bSLuca Vizzarro
37461d5bc9bSLuca Vizzarro    def _validate(info: str):
37561d5bc9bSLuca Vizzarro        info = info.strip()
37661d5bc9bSLuca Vizzarro        if info == "none" or info.startswith("Invalid file") or info.startswith("Failed to dump"):
37761d5bc9bSLuca Vizzarro            return None
37861d5bc9bSLuca Vizzarro        return info
37961d5bc9bSLuca Vizzarro
38061d5bc9bSLuca Vizzarro    return TextParser.wrap(TextParser.find(r"Device private info:\s+([\s\S]+)"), _validate)
38161d5bc9bSLuca Vizzarro
38261d5bc9bSLuca Vizzarro
38361d5bc9bSLuca Vizzarro@dataclass
38461d5bc9bSLuca Vizzarroclass TestPmdPort(TextParser):
38561d5bc9bSLuca Vizzarro    """Dataclass representing the result of testpmd's ``show port info`` command."""
38661d5bc9bSLuca Vizzarro
38761d5bc9bSLuca Vizzarro    #:
38861d5bc9bSLuca Vizzarro    id: int = field(metadata=TextParser.find_int(r"Infos for port (\d+)\b"))
38961d5bc9bSLuca Vizzarro    #:
39061d5bc9bSLuca Vizzarro    device_name: str = field(metadata=TextParser.find(r"Device name: ([^\r\n]+)"))
39161d5bc9bSLuca Vizzarro    #:
39261d5bc9bSLuca Vizzarro    driver_name: str = field(metadata=TextParser.find(r"Driver name: ([^\r\n]+)"))
39361d5bc9bSLuca Vizzarro    #:
39461d5bc9bSLuca Vizzarro    socket_id: int = field(metadata=TextParser.find_int(r"Connect to socket: (\d+)"))
39561d5bc9bSLuca Vizzarro    #:
39661d5bc9bSLuca Vizzarro    is_link_up: bool = field(metadata=TextParser.find("Link status: up"))
39761d5bc9bSLuca Vizzarro    #:
39861d5bc9bSLuca Vizzarro    link_speed: str = field(metadata=TextParser.find(r"Link speed: ([^\r\n]+)"))
39961d5bc9bSLuca Vizzarro    #:
40061d5bc9bSLuca Vizzarro    is_link_full_duplex: bool = field(metadata=TextParser.find("Link duplex: full-duplex"))
40161d5bc9bSLuca Vizzarro    #:
40261d5bc9bSLuca Vizzarro    is_link_autonegotiated: bool = field(metadata=TextParser.find("Autoneg status: On"))
40361d5bc9bSLuca Vizzarro    #:
40461d5bc9bSLuca Vizzarro    is_promiscuous_mode_enabled: bool = field(metadata=TextParser.find("Promiscuous mode: enabled"))
40561d5bc9bSLuca Vizzarro    #:
40661d5bc9bSLuca Vizzarro    is_allmulticast_mode_enabled: bool = field(
40761d5bc9bSLuca Vizzarro        metadata=TextParser.find("Allmulticast mode: enabled")
40861d5bc9bSLuca Vizzarro    )
40961d5bc9bSLuca Vizzarro    #: Maximum number of MAC addresses
41061d5bc9bSLuca Vizzarro    max_mac_addresses_num: int = field(
41161d5bc9bSLuca Vizzarro        metadata=TextParser.find_int(r"Maximum number of MAC addresses: (\d+)")
41261d5bc9bSLuca Vizzarro    )
41361d5bc9bSLuca Vizzarro    #: Maximum configurable length of RX packet
41461d5bc9bSLuca Vizzarro    max_hash_mac_addresses_num: int = field(
41561d5bc9bSLuca Vizzarro        metadata=TextParser.find_int(r"Maximum number of MAC addresses of hash filtering: (\d+)")
41661d5bc9bSLuca Vizzarro    )
41761d5bc9bSLuca Vizzarro    #: Minimum size of RX buffer
41861d5bc9bSLuca Vizzarro    min_rx_bufsize: int = field(metadata=TextParser.find_int(r"Minimum size of RX buffer: (\d+)"))
41961d5bc9bSLuca Vizzarro    #: Maximum configurable length of RX packet
42061d5bc9bSLuca Vizzarro    max_rx_packet_length: int = field(
42161d5bc9bSLuca Vizzarro        metadata=TextParser.find_int(r"Maximum configurable length of RX packet: (\d+)")
42261d5bc9bSLuca Vizzarro    )
42361d5bc9bSLuca Vizzarro    #: Maximum configurable size of LRO aggregated packet
42461d5bc9bSLuca Vizzarro    max_lro_packet_size: int = field(
42561d5bc9bSLuca Vizzarro        metadata=TextParser.find_int(r"Maximum configurable size of LRO aggregated packet: (\d+)")
42661d5bc9bSLuca Vizzarro    )
42761d5bc9bSLuca Vizzarro
42861d5bc9bSLuca Vizzarro    #: Current number of RX queues
42961d5bc9bSLuca Vizzarro    rx_queues_num: int = field(metadata=TextParser.find_int(r"Current number of RX queues: (\d+)"))
43061d5bc9bSLuca Vizzarro    #: Max possible RX queues
43161d5bc9bSLuca Vizzarro    max_rx_queues_num: int = field(metadata=TextParser.find_int(r"Max possible RX queues: (\d+)"))
43261d5bc9bSLuca Vizzarro    #: Max possible number of RXDs per queue
43361d5bc9bSLuca Vizzarro    max_queue_rxd_num: int = field(
43461d5bc9bSLuca Vizzarro        metadata=TextParser.find_int(r"Max possible number of RXDs per queue: (\d+)")
43561d5bc9bSLuca Vizzarro    )
43661d5bc9bSLuca Vizzarro    #: Min possible number of RXDs per queue
43761d5bc9bSLuca Vizzarro    min_queue_rxd_num: int = field(
43861d5bc9bSLuca Vizzarro        metadata=TextParser.find_int(r"Min possible number of RXDs per queue: (\d+)")
43961d5bc9bSLuca Vizzarro    )
44061d5bc9bSLuca Vizzarro    #: RXDs number alignment
44161d5bc9bSLuca Vizzarro    rxd_alignment_num: int = field(metadata=TextParser.find_int(r"RXDs number alignment: (\d+)"))
44261d5bc9bSLuca Vizzarro
44361d5bc9bSLuca Vizzarro    #: Current number of TX queues
44461d5bc9bSLuca Vizzarro    tx_queues_num: int = field(metadata=TextParser.find_int(r"Current number of TX queues: (\d+)"))
44561d5bc9bSLuca Vizzarro    #: Max possible TX queues
44661d5bc9bSLuca Vizzarro    max_tx_queues_num: int = field(metadata=TextParser.find_int(r"Max possible TX queues: (\d+)"))
44761d5bc9bSLuca Vizzarro    #: Max possible number of TXDs per queue
44861d5bc9bSLuca Vizzarro    max_queue_txd_num: int = field(
44961d5bc9bSLuca Vizzarro        metadata=TextParser.find_int(r"Max possible number of TXDs per queue: (\d+)")
45061d5bc9bSLuca Vizzarro    )
45161d5bc9bSLuca Vizzarro    #: Min possible number of TXDs per queue
45261d5bc9bSLuca Vizzarro    min_queue_txd_num: int = field(
45361d5bc9bSLuca Vizzarro        metadata=TextParser.find_int(r"Min possible number of TXDs per queue: (\d+)")
45461d5bc9bSLuca Vizzarro    )
45561d5bc9bSLuca Vizzarro    #: TXDs number alignment
45661d5bc9bSLuca Vizzarro    txd_alignment_num: int = field(metadata=TextParser.find_int(r"TXDs number alignment: (\d+)"))
45761d5bc9bSLuca Vizzarro    #: Max segment number per packet
45861d5bc9bSLuca Vizzarro    max_packet_segment_num: int = field(
45961d5bc9bSLuca Vizzarro        metadata=TextParser.find_int(r"Max segment number per packet: (\d+)")
46061d5bc9bSLuca Vizzarro    )
46161d5bc9bSLuca Vizzarro    #: Max segment number per MTU/TSO
46261d5bc9bSLuca Vizzarro    max_mtu_segment_num: int = field(
46361d5bc9bSLuca Vizzarro        metadata=TextParser.find_int(r"Max segment number per MTU\/TSO: (\d+)")
46461d5bc9bSLuca Vizzarro    )
46561d5bc9bSLuca Vizzarro
46661d5bc9bSLuca Vizzarro    #:
46761d5bc9bSLuca Vizzarro    device_capabilities: DeviceCapabilitiesFlag = field(
46861d5bc9bSLuca Vizzarro        metadata=DeviceCapabilitiesFlag.make_parser(),
46961d5bc9bSLuca Vizzarro    )
47061d5bc9bSLuca Vizzarro    #:
471*618d9140SJuraj Linkeš    device_error_handling_mode: DeviceErrorHandlingMode | None = field(
472*618d9140SJuraj Linkeš        default=None, metadata=DeviceErrorHandlingMode.make_parser()
47361d5bc9bSLuca Vizzarro    )
47461d5bc9bSLuca Vizzarro    #:
47561d5bc9bSLuca Vizzarro    device_private_info: str | None = field(
47661d5bc9bSLuca Vizzarro        default=None,
47761d5bc9bSLuca Vizzarro        metadata=make_device_private_info_parser(),
47861d5bc9bSLuca Vizzarro    )
47961d5bc9bSLuca Vizzarro
48061d5bc9bSLuca Vizzarro    #:
48161d5bc9bSLuca Vizzarro    hash_key_size: int | None = field(
48261d5bc9bSLuca Vizzarro        default=None, metadata=TextParser.find_int(r"Hash key size in bytes: (\d+)")
48361d5bc9bSLuca Vizzarro    )
48461d5bc9bSLuca Vizzarro    #:
48561d5bc9bSLuca Vizzarro    redirection_table_size: int | None = field(
48661d5bc9bSLuca Vizzarro        default=None, metadata=TextParser.find_int(r"Redirection table size: (\d+)")
48761d5bc9bSLuca Vizzarro    )
48861d5bc9bSLuca Vizzarro    #:
48961d5bc9bSLuca Vizzarro    supported_rss_offload_flow_types: RSSOffloadTypesFlag = field(
49061d5bc9bSLuca Vizzarro        default=RSSOffloadTypesFlag(0), metadata=RSSOffloadTypesFlag.make_parser()
49161d5bc9bSLuca Vizzarro    )
49261d5bc9bSLuca Vizzarro
49361d5bc9bSLuca Vizzarro    #:
49461d5bc9bSLuca Vizzarro    mac_address: str | None = field(
49561d5bc9bSLuca Vizzarro        default=None, metadata=TextParser.find(r"MAC address: ([A-Fa-f0-9:]+)")
49661d5bc9bSLuca Vizzarro    )
49761d5bc9bSLuca Vizzarro    #:
49861d5bc9bSLuca Vizzarro    fw_version: str | None = field(
49961d5bc9bSLuca Vizzarro        default=None, metadata=TextParser.find(r"Firmware-version: ([^\r\n]+)")
50061d5bc9bSLuca Vizzarro    )
50161d5bc9bSLuca Vizzarro    #:
50261d5bc9bSLuca Vizzarro    dev_args: str | None = field(default=None, metadata=TextParser.find(r"Devargs: ([^\r\n]+)"))
50361d5bc9bSLuca Vizzarro    #: Socket id of the memory allocation
50461d5bc9bSLuca Vizzarro    mem_alloc_socket_id: int | None = field(
50561d5bc9bSLuca Vizzarro        default=None,
50661d5bc9bSLuca Vizzarro        metadata=TextParser.find_int(r"memory allocation on the socket: (\d+)"),
50761d5bc9bSLuca Vizzarro    )
50861d5bc9bSLuca Vizzarro    #:
50961d5bc9bSLuca Vizzarro    mtu: int | None = field(default=None, metadata=TextParser.find_int(r"MTU: (\d+)"))
51061d5bc9bSLuca Vizzarro
51161d5bc9bSLuca Vizzarro    #:
51261d5bc9bSLuca Vizzarro    vlan_offload: VLANOffloadFlag | None = field(
51361d5bc9bSLuca Vizzarro        default=None,
51461d5bc9bSLuca Vizzarro        metadata=VLANOffloadFlag.make_parser(),
51561d5bc9bSLuca Vizzarro    )
51661d5bc9bSLuca Vizzarro
51761d5bc9bSLuca Vizzarro    #: Maximum size of RX buffer
51861d5bc9bSLuca Vizzarro    max_rx_bufsize: int | None = field(
51961d5bc9bSLuca Vizzarro        default=None, metadata=TextParser.find_int(r"Maximum size of RX buffer: (\d+)")
52061d5bc9bSLuca Vizzarro    )
52161d5bc9bSLuca Vizzarro    #: Maximum number of VFs
52261d5bc9bSLuca Vizzarro    max_vfs_num: int | None = field(
52361d5bc9bSLuca Vizzarro        default=None, metadata=TextParser.find_int(r"Maximum number of VFs: (\d+)")
52461d5bc9bSLuca Vizzarro    )
52561d5bc9bSLuca Vizzarro    #: Maximum number of VMDq pools
52661d5bc9bSLuca Vizzarro    max_vmdq_pools_num: int | None = field(
52761d5bc9bSLuca Vizzarro        default=None, metadata=TextParser.find_int(r"Maximum number of VMDq pools: (\d+)")
52861d5bc9bSLuca Vizzarro    )
52961d5bc9bSLuca Vizzarro
53061d5bc9bSLuca Vizzarro    #:
53161d5bc9bSLuca Vizzarro    switch_name: str | None = field(
53261d5bc9bSLuca Vizzarro        default=None, metadata=TextParser.find(r"Switch name: ([\r\n]+)")
53361d5bc9bSLuca Vizzarro    )
53461d5bc9bSLuca Vizzarro    #:
53561d5bc9bSLuca Vizzarro    switch_domain_id: int | None = field(
53661d5bc9bSLuca Vizzarro        default=None, metadata=TextParser.find_int(r"Switch domain Id: (\d+)")
53761d5bc9bSLuca Vizzarro    )
53861d5bc9bSLuca Vizzarro    #:
53961d5bc9bSLuca Vizzarro    switch_port_id: int | None = field(
54061d5bc9bSLuca Vizzarro        default=None, metadata=TextParser.find_int(r"Switch Port Id: (\d+)")
54161d5bc9bSLuca Vizzarro    )
54261d5bc9bSLuca Vizzarro    #:
54361d5bc9bSLuca Vizzarro    switch_rx_domain: int | None = field(
54461d5bc9bSLuca Vizzarro        default=None, metadata=TextParser.find_int(r"Switch Rx domain: (\d+)")
54561d5bc9bSLuca Vizzarro    )
54661d5bc9bSLuca Vizzarro
54761d5bc9bSLuca Vizzarro
54853eacf3dSLuca Vizzarro@dataclass
54953eacf3dSLuca Vizzarroclass TestPmdPortStats(TextParser):
55053eacf3dSLuca Vizzarro    """Port statistics."""
55153eacf3dSLuca Vizzarro
55253eacf3dSLuca Vizzarro    #:
55353eacf3dSLuca Vizzarro    port_id: int = field(metadata=TextParser.find_int(r"NIC statistics for port (\d+)"))
55453eacf3dSLuca Vizzarro
55553eacf3dSLuca Vizzarro    #:
55653eacf3dSLuca Vizzarro    rx_packets: int = field(metadata=TextParser.find_int(r"RX-packets:\s+(\d+)"))
55753eacf3dSLuca Vizzarro    #:
55853eacf3dSLuca Vizzarro    rx_missed: int = field(metadata=TextParser.find_int(r"RX-missed:\s+(\d+)"))
55953eacf3dSLuca Vizzarro    #:
56053eacf3dSLuca Vizzarro    rx_bytes: int = field(metadata=TextParser.find_int(r"RX-bytes:\s+(\d+)"))
56153eacf3dSLuca Vizzarro    #:
56253eacf3dSLuca Vizzarro    rx_errors: int = field(metadata=TextParser.find_int(r"RX-errors:\s+(\d+)"))
56353eacf3dSLuca Vizzarro    #:
56453eacf3dSLuca Vizzarro    rx_nombuf: int = field(metadata=TextParser.find_int(r"RX-nombuf:\s+(\d+)"))
56553eacf3dSLuca Vizzarro
56653eacf3dSLuca Vizzarro    #:
56753eacf3dSLuca Vizzarro    tx_packets: int = field(metadata=TextParser.find_int(r"TX-packets:\s+(\d+)"))
56853eacf3dSLuca Vizzarro    #:
56953eacf3dSLuca Vizzarro    tx_errors: int = field(metadata=TextParser.find_int(r"TX-errors:\s+(\d+)"))
57053eacf3dSLuca Vizzarro    #:
57153eacf3dSLuca Vizzarro    tx_bytes: int = field(metadata=TextParser.find_int(r"TX-bytes:\s+(\d+)"))
57253eacf3dSLuca Vizzarro
57353eacf3dSLuca Vizzarro    #:
57453eacf3dSLuca Vizzarro    rx_pps: int = field(metadata=TextParser.find_int(r"Rx-pps:\s+(\d+)"))
57553eacf3dSLuca Vizzarro    #:
57653eacf3dSLuca Vizzarro    rx_bps: int = field(metadata=TextParser.find_int(r"Rx-bps:\s+(\d+)"))
57753eacf3dSLuca Vizzarro
57853eacf3dSLuca Vizzarro    #:
57953eacf3dSLuca Vizzarro    tx_pps: int = field(metadata=TextParser.find_int(r"Tx-pps:\s+(\d+)"))
58053eacf3dSLuca Vizzarro    #:
58153eacf3dSLuca Vizzarro    tx_bps: int = field(metadata=TextParser.find_int(r"Tx-bps:\s+(\d+)"))
58253eacf3dSLuca Vizzarro
58353eacf3dSLuca Vizzarro
5842ecfd126SLuca Vizzarrodef requires_stopped_ports(func: TestPmdShellMethod) -> TestPmdShellMethod:
5852ecfd126SLuca Vizzarro    """Decorator for :class:`TestPmdShell` commands methods that require stopped ports.
5862ecfd126SLuca Vizzarro
5872ecfd126SLuca Vizzarro    If the decorated method is called while the ports are started, then these are stopped before
5882ecfd126SLuca Vizzarro    continuing.
5892ecfd126SLuca Vizzarro
5902ecfd126SLuca Vizzarro    Args:
5912ecfd126SLuca Vizzarro        func: The :class:`TestPmdShell` method to decorate.
5922ecfd126SLuca Vizzarro    """
5932ecfd126SLuca Vizzarro
5942ecfd126SLuca Vizzarro    @functools.wraps(func)
5952ecfd126SLuca Vizzarro    def _wrapper(self: "TestPmdShell", *args: P.args, **kwargs: P.kwargs):
5962ecfd126SLuca Vizzarro        if self.ports_started:
5972ecfd126SLuca Vizzarro            self._logger.debug("Ports need to be stopped to continue.")
5982ecfd126SLuca Vizzarro            self.stop_all_ports()
5992ecfd126SLuca Vizzarro
6002ecfd126SLuca Vizzarro        return func(self, *args, **kwargs)
6012ecfd126SLuca Vizzarro
6022ecfd126SLuca Vizzarro    return _wrapper
6032ecfd126SLuca Vizzarro
6042ecfd126SLuca Vizzarro
6052ecfd126SLuca Vizzarrodef requires_started_ports(func: TestPmdShellMethod) -> TestPmdShellMethod:
6062ecfd126SLuca Vizzarro    """Decorator for :class:`TestPmdShell` commands methods that require started ports.
6072ecfd126SLuca Vizzarro
6082ecfd126SLuca Vizzarro    If the decorated method is called while the ports are stopped, then these are started before
6092ecfd126SLuca Vizzarro    continuing.
6102ecfd126SLuca Vizzarro
6112ecfd126SLuca Vizzarro    Args:
6122ecfd126SLuca Vizzarro        func: The :class:`TestPmdShell` method to decorate.
6132ecfd126SLuca Vizzarro    """
6142ecfd126SLuca Vizzarro
6152ecfd126SLuca Vizzarro    @functools.wraps(func)
6162ecfd126SLuca Vizzarro    def _wrapper(self: "TestPmdShell", *args: P.args, **kwargs: P.kwargs):
6172ecfd126SLuca Vizzarro        if not self.ports_started:
6182ecfd126SLuca Vizzarro            self._logger.debug("Ports need to be started to continue.")
6192ecfd126SLuca Vizzarro            self.start_all_ports()
6202ecfd126SLuca Vizzarro
6212ecfd126SLuca Vizzarro        return func(self, *args, **kwargs)
6222ecfd126SLuca Vizzarro
6232ecfd126SLuca Vizzarro    return _wrapper
6242ecfd126SLuca Vizzarro
6252ecfd126SLuca Vizzarro
626bfad0948SLuca Vizzarroclass TestPmdShell(DPDKShell):
6276ef07151SJuraj Linkeš    """Testpmd interactive shell.
6286ef07151SJuraj Linkeš
6296ef07151SJuraj Linkeš    The testpmd shell users should never use
6306ef07151SJuraj Linkeš    the :meth:`~.interactive_shell.InteractiveShell.send_command` method directly, but rather
6316ef07151SJuraj Linkeš    call specialized methods. If there isn't one that satisfies a need, it should be added.
6322ecfd126SLuca Vizzarro
6332ecfd126SLuca Vizzarro    Attributes:
6342ecfd126SLuca Vizzarro        ports_started: Indicates whether the ports are started.
6356ef07151SJuraj Linkeš    """
6366ef07151SJuraj Linkeš
637bfad0948SLuca Vizzarro    _app_params: TestPmdParams
638369d34b8SJeremy Spewock
6396ef07151SJuraj Linkeš    #: The path to the testpmd executable.
6406ef07151SJuraj Linkeš    path: ClassVar[PurePath] = PurePath("app", "dpdk-testpmd")
6416ef07151SJuraj Linkeš
6426ef07151SJuraj Linkeš    #: The testpmd's prompt.
6436ef07151SJuraj Linkeš    _default_prompt: ClassVar[str] = "testpmd>"
6446ef07151SJuraj Linkeš
6456ef07151SJuraj Linkeš    #: This forces the prompt to appear after sending a command.
6466ef07151SJuraj Linkeš    _command_extra_chars: ClassVar[str] = "\n"
647840b1e01SJuraj Linkeš
6482ecfd126SLuca Vizzarro    ports_started: bool
6492ecfd126SLuca Vizzarro
650bfad0948SLuca Vizzarro    def __init__(
651bfad0948SLuca Vizzarro        self,
652bfad0948SLuca Vizzarro        node: SutNode,
653bfad0948SLuca Vizzarro        privileged: bool = True,
654bfad0948SLuca Vizzarro        timeout: float = SETTINGS.timeout,
655bfad0948SLuca Vizzarro        lcore_filter_specifier: LogicalCoreCount | LogicalCoreList = LogicalCoreCount(),
656bfad0948SLuca Vizzarro        ascending_cores: bool = True,
657bfad0948SLuca Vizzarro        append_prefix_timestamp: bool = True,
65865a1b4e8SJeremy Spewock        name: str | None = None,
65987ba4cdcSLuca Vizzarro        **app_params: Unpack[TestPmdParamsDict],
660bfad0948SLuca Vizzarro    ) -> None:
661bfad0948SLuca Vizzarro        """Overrides :meth:`~.dpdk_shell.DPDKShell.__init__`. Changes app_params to kwargs."""
662bfad0948SLuca Vizzarro        super().__init__(
663bfad0948SLuca Vizzarro            node,
664bfad0948SLuca Vizzarro            privileged,
665bfad0948SLuca Vizzarro            timeout,
666bfad0948SLuca Vizzarro            lcore_filter_specifier,
667bfad0948SLuca Vizzarro            ascending_cores,
668bfad0948SLuca Vizzarro            append_prefix_timestamp,
669bfad0948SLuca Vizzarro            TestPmdParams(**app_params),
67065a1b4e8SJeremy Spewock            name,
671fd8cd8eeSLuca Vizzarro        )
672fd8cd8eeSLuca Vizzarro
6732ecfd126SLuca Vizzarro        self.ports_started = not self._app_params.disable_device_start
6742ecfd126SLuca Vizzarro
6752ecfd126SLuca Vizzarro    @requires_started_ports
676369d34b8SJeremy Spewock    def start(self, verify: bool = True) -> None:
677369d34b8SJeremy Spewock        """Start packet forwarding with the current configuration.
678369d34b8SJeremy Spewock
679369d34b8SJeremy Spewock        Args:
680369d34b8SJeremy Spewock            verify: If :data:`True` , a second start command will be sent in an attempt to verify
681369d34b8SJeremy Spewock                packet forwarding started as expected.
682369d34b8SJeremy Spewock
683369d34b8SJeremy Spewock        Raises:
684369d34b8SJeremy Spewock            InteractiveCommandExecutionError: If `verify` is :data:`True` and forwarding fails to
685369d34b8SJeremy Spewock                start or ports fail to come up.
686369d34b8SJeremy Spewock        """
687369d34b8SJeremy Spewock        self.send_command("start")
688369d34b8SJeremy Spewock        if verify:
689369d34b8SJeremy Spewock            # If forwarding was already started, sending "start" again should tell us
690369d34b8SJeremy Spewock            start_cmd_output = self.send_command("start")
691369d34b8SJeremy Spewock            if "Packet forwarding already started" not in start_cmd_output:
692369d34b8SJeremy Spewock                self._logger.debug(f"Failed to start packet forwarding: \n{start_cmd_output}")
693369d34b8SJeremy Spewock                raise InteractiveCommandExecutionError("Testpmd failed to start packet forwarding.")
694369d34b8SJeremy Spewock
695bfad0948SLuca Vizzarro            number_of_ports = len(self._app_params.ports or [])
696bfad0948SLuca Vizzarro            for port_id in range(number_of_ports):
697369d34b8SJeremy Spewock                if not self.wait_link_status_up(port_id):
698369d34b8SJeremy Spewock                    raise InteractiveCommandExecutionError(
699369d34b8SJeremy Spewock                        "Not all ports came up after starting packet forwarding in testpmd."
700369d34b8SJeremy Spewock                    )
701369d34b8SJeremy Spewock
702369d34b8SJeremy Spewock    def stop(self, verify: bool = True) -> None:
703369d34b8SJeremy Spewock        """Stop packet forwarding.
704369d34b8SJeremy Spewock
705369d34b8SJeremy Spewock        Args:
706369d34b8SJeremy Spewock            verify: If :data:`True` , the output of the stop command is scanned to verify that
707369d34b8SJeremy Spewock                forwarding was stopped successfully or not started. If neither is found, it is
708369d34b8SJeremy Spewock                considered an error.
709369d34b8SJeremy Spewock
710369d34b8SJeremy Spewock        Raises:
711369d34b8SJeremy Spewock            InteractiveCommandExecutionError: If `verify` is :data:`True` and the command to stop
712369d34b8SJeremy Spewock                forwarding results in an error.
713369d34b8SJeremy Spewock        """
714369d34b8SJeremy Spewock        stop_cmd_output = self.send_command("stop")
715369d34b8SJeremy Spewock        if verify:
716369d34b8SJeremy Spewock            if (
717369d34b8SJeremy Spewock                "Done." not in stop_cmd_output
718369d34b8SJeremy Spewock                and "Packet forwarding not started" not in stop_cmd_output
719369d34b8SJeremy Spewock            ):
720369d34b8SJeremy Spewock                self._logger.debug(f"Failed to stop packet forwarding: \n{stop_cmd_output}")
721369d34b8SJeremy Spewock                raise InteractiveCommandExecutionError("Testpmd failed to stop packet forwarding.")
722369d34b8SJeremy Spewock
723840b1e01SJuraj Linkeš    def get_devices(self) -> list[TestPmdDevice]:
7246ef07151SJuraj Linkeš        """Get a list of device names that are known to testpmd.
725840b1e01SJuraj Linkeš
7266ef07151SJuraj Linkeš        Uses the device info listed in testpmd and then parses the output.
727840b1e01SJuraj Linkeš
728840b1e01SJuraj Linkeš        Returns:
7296ef07151SJuraj Linkeš            A list of devices.
730840b1e01SJuraj Linkeš        """
731840b1e01SJuraj Linkeš        dev_info: str = self.send_command("show device info all")
732840b1e01SJuraj Linkeš        dev_list: list[TestPmdDevice] = []
733840b1e01SJuraj Linkeš        for line in dev_info.split("\n"):
734840b1e01SJuraj Linkeš            if "device name:" in line.lower():
735840b1e01SJuraj Linkeš                dev_list.append(TestPmdDevice(line))
736840b1e01SJuraj Linkeš        return dev_list
737369d34b8SJeremy Spewock
738369d34b8SJeremy Spewock    def wait_link_status_up(self, port_id: int, timeout=SETTINGS.timeout) -> bool:
739369d34b8SJeremy Spewock        """Wait until the link status on the given port is "up".
740369d34b8SJeremy Spewock
741369d34b8SJeremy Spewock        Arguments:
742369d34b8SJeremy Spewock            port_id: Port to check the link status on.
743369d34b8SJeremy Spewock            timeout: Time to wait for the link to come up. The default value for this
744369d34b8SJeremy Spewock                argument may be modified using the :option:`--timeout` command-line argument
745369d34b8SJeremy Spewock                or the :envvar:`DTS_TIMEOUT` environment variable.
746369d34b8SJeremy Spewock
747369d34b8SJeremy Spewock        Returns:
748369d34b8SJeremy Spewock            Whether the link came up in time or not.
749369d34b8SJeremy Spewock        """
750369d34b8SJeremy Spewock        time_to_stop = time.time() + timeout
751369d34b8SJeremy Spewock        port_info: str = ""
752369d34b8SJeremy Spewock        while time.time() < time_to_stop:
753369d34b8SJeremy Spewock            port_info = self.send_command(f"show port info {port_id}")
754369d34b8SJeremy Spewock            if "Link status: up" in port_info:
755369d34b8SJeremy Spewock                break
756369d34b8SJeremy Spewock            time.sleep(0.5)
757369d34b8SJeremy Spewock        else:
758369d34b8SJeremy Spewock            self._logger.error(f"The link for port {port_id} did not come up in the given timeout.")
759369d34b8SJeremy Spewock        return "Link status: up" in port_info
760369d34b8SJeremy Spewock
761fc0f7dc4SLuca Vizzarro    def set_forward_mode(self, mode: SimpleForwardingModes, verify: bool = True):
762369d34b8SJeremy Spewock        """Set packet forwarding mode.
763369d34b8SJeremy Spewock
764369d34b8SJeremy Spewock        Args:
765369d34b8SJeremy Spewock            mode: The forwarding mode to use.
766369d34b8SJeremy Spewock            verify: If :data:`True` the output of the command will be scanned in an attempt to
767369d34b8SJeremy Spewock                verify that the forwarding mode was set to `mode` properly.
768369d34b8SJeremy Spewock
769369d34b8SJeremy Spewock        Raises:
770369d34b8SJeremy Spewock            InteractiveCommandExecutionError: If `verify` is :data:`True` and the forwarding mode
771369d34b8SJeremy Spewock                fails to update.
772369d34b8SJeremy Spewock        """
773369d34b8SJeremy Spewock        set_fwd_output = self.send_command(f"set fwd {mode.value}")
774369d34b8SJeremy Spewock        if f"Set {mode.value} packet forwarding mode" not in set_fwd_output:
775369d34b8SJeremy Spewock            self._logger.debug(f"Failed to set fwd mode to {mode.value}:\n{set_fwd_output}")
776369d34b8SJeremy Spewock            raise InteractiveCommandExecutionError(
777369d34b8SJeremy Spewock                f"Test pmd failed to set fwd mode to {mode.value}"
778369d34b8SJeremy Spewock            )
779369d34b8SJeremy Spewock
7802ecfd126SLuca Vizzarro    def stop_all_ports(self, verify: bool = True) -> None:
7812ecfd126SLuca Vizzarro        """Stops all the ports.
7822ecfd126SLuca Vizzarro
7832ecfd126SLuca Vizzarro        Args:
7842ecfd126SLuca Vizzarro            verify: If :data:`True`, the output of the command will be checked for a successful
7852ecfd126SLuca Vizzarro                execution.
7862ecfd126SLuca Vizzarro
7872ecfd126SLuca Vizzarro        Raises:
7882ecfd126SLuca Vizzarro            InteractiveCommandExecutionError: If `verify` is :data:`True` and the ports were not
7892ecfd126SLuca Vizzarro                stopped successfully.
7902ecfd126SLuca Vizzarro        """
7912ecfd126SLuca Vizzarro        self._logger.debug("Stopping all the ports...")
7922ecfd126SLuca Vizzarro        output = self.send_command("port stop all")
7932ecfd126SLuca Vizzarro        if verify and not output.strip().endswith("Done"):
7942ecfd126SLuca Vizzarro            raise InteractiveCommandExecutionError("Ports were not stopped successfully.")
7952ecfd126SLuca Vizzarro
7962ecfd126SLuca Vizzarro        self.ports_started = False
7972ecfd126SLuca Vizzarro
7982ecfd126SLuca Vizzarro    def start_all_ports(self, verify: bool = True) -> None:
7992ecfd126SLuca Vizzarro        """Starts all the ports.
8002ecfd126SLuca Vizzarro
8012ecfd126SLuca Vizzarro        Args:
8022ecfd126SLuca Vizzarro            verify: If :data:`True`, the output of the command will be checked for a successful
8032ecfd126SLuca Vizzarro                execution.
8042ecfd126SLuca Vizzarro
8052ecfd126SLuca Vizzarro        Raises:
8062ecfd126SLuca Vizzarro            InteractiveCommandExecutionError: If `verify` is :data:`True` and the ports were not
8072ecfd126SLuca Vizzarro                started successfully.
8082ecfd126SLuca Vizzarro        """
8092ecfd126SLuca Vizzarro        self._logger.debug("Starting all the ports...")
8102ecfd126SLuca Vizzarro        output = self.send_command("port start all")
8112ecfd126SLuca Vizzarro        if verify and not output.strip().endswith("Done"):
8122ecfd126SLuca Vizzarro            raise InteractiveCommandExecutionError("Ports were not started successfully.")
8132ecfd126SLuca Vizzarro
8142ecfd126SLuca Vizzarro        self.ports_started = True
8152ecfd126SLuca Vizzarro
81607816eadSLuca Vizzarro    @requires_stopped_ports
81707816eadSLuca Vizzarro    def set_ports_queues(self, number_of: int) -> None:
81807816eadSLuca Vizzarro        """Sets the number of queues per port.
81907816eadSLuca Vizzarro
82007816eadSLuca Vizzarro        Args:
82107816eadSLuca Vizzarro            number_of: The number of RX/TX queues to create per port.
82207816eadSLuca Vizzarro
82307816eadSLuca Vizzarro        Raises:
82407816eadSLuca Vizzarro            InternalError: If `number_of` is invalid.
82507816eadSLuca Vizzarro        """
82607816eadSLuca Vizzarro        if number_of < 1:
82707816eadSLuca Vizzarro            raise InternalError("The number of queues must be positive and non-zero.")
82807816eadSLuca Vizzarro
82907816eadSLuca Vizzarro        self.send_command(f"port config all rxq {number_of}")
83007816eadSLuca Vizzarro        self.send_command(f"port config all txq {number_of}")
83107816eadSLuca Vizzarro
83261d5bc9bSLuca Vizzarro    def show_port_info_all(self) -> list[TestPmdPort]:
83361d5bc9bSLuca Vizzarro        """Returns the information of all the ports.
83461d5bc9bSLuca Vizzarro
83561d5bc9bSLuca Vizzarro        Returns:
83661d5bc9bSLuca Vizzarro            list[TestPmdPort]: A list containing all the ports information as `TestPmdPort`.
83761d5bc9bSLuca Vizzarro        """
83861d5bc9bSLuca Vizzarro        output = self.send_command("show port info all")
83961d5bc9bSLuca Vizzarro
84061d5bc9bSLuca Vizzarro        # Sample output of the "all" command looks like:
84161d5bc9bSLuca Vizzarro        #
84261d5bc9bSLuca Vizzarro        # <start>
84361d5bc9bSLuca Vizzarro        #
84461d5bc9bSLuca Vizzarro        #   ********************* Infos for port 0 *********************
84561d5bc9bSLuca Vizzarro        #   Key: value
84661d5bc9bSLuca Vizzarro        #
84761d5bc9bSLuca Vizzarro        #   ********************* Infos for port 1 *********************
84861d5bc9bSLuca Vizzarro        #   Key: value
84961d5bc9bSLuca Vizzarro        # <end>
85061d5bc9bSLuca Vizzarro        #
85161d5bc9bSLuca Vizzarro        # Takes advantage of the double new line in between ports as end delimiter. But we need to
85261d5bc9bSLuca Vizzarro        # artificially add a new line at the end to pick up the last port. Because commands are
85361d5bc9bSLuca Vizzarro        # executed on a pseudo-terminal created by paramiko on the remote node, lines end with CRLF.
85461d5bc9bSLuca Vizzarro        # Therefore we also need to take the carriage return into account.
85561d5bc9bSLuca Vizzarro        iter = re.finditer(r"\*{21}.*?[\r\n]{4}", output + "\r\n", re.S)
85661d5bc9bSLuca Vizzarro        return [TestPmdPort.parse(block.group(0)) for block in iter]
85761d5bc9bSLuca Vizzarro
85861d5bc9bSLuca Vizzarro    def show_port_info(self, port_id: int) -> TestPmdPort:
85961d5bc9bSLuca Vizzarro        """Returns the given port information.
86061d5bc9bSLuca Vizzarro
86161d5bc9bSLuca Vizzarro        Args:
86261d5bc9bSLuca Vizzarro            port_id: The port ID to gather information for.
86361d5bc9bSLuca Vizzarro
86461d5bc9bSLuca Vizzarro        Raises:
86561d5bc9bSLuca Vizzarro            InteractiveCommandExecutionError: If `port_id` is invalid.
86661d5bc9bSLuca Vizzarro
86761d5bc9bSLuca Vizzarro        Returns:
86861d5bc9bSLuca Vizzarro            TestPmdPort: An instance of `TestPmdPort` containing the given port's information.
86961d5bc9bSLuca Vizzarro        """
87061d5bc9bSLuca Vizzarro        output = self.send_command(f"show port info {port_id}", skip_first_line=True)
87161d5bc9bSLuca Vizzarro        if output.startswith("Invalid port"):
87261d5bc9bSLuca Vizzarro            raise InteractiveCommandExecutionError("invalid port given")
87361d5bc9bSLuca Vizzarro
87461d5bc9bSLuca Vizzarro        return TestPmdPort.parse(output)
87561d5bc9bSLuca Vizzarro
87653eacf3dSLuca Vizzarro    def show_port_stats_all(self) -> list[TestPmdPortStats]:
87753eacf3dSLuca Vizzarro        """Returns the statistics of all the ports.
87853eacf3dSLuca Vizzarro
87953eacf3dSLuca Vizzarro        Returns:
88053eacf3dSLuca Vizzarro            list[TestPmdPortStats]: A list containing all the ports stats as `TestPmdPortStats`.
88153eacf3dSLuca Vizzarro        """
88253eacf3dSLuca Vizzarro        output = self.send_command("show port stats all")
88353eacf3dSLuca Vizzarro
88453eacf3dSLuca Vizzarro        # Sample output of the "all" command looks like:
88553eacf3dSLuca Vizzarro        #
88653eacf3dSLuca Vizzarro        #   ########### NIC statistics for port 0 ###########
88753eacf3dSLuca Vizzarro        #   values...
88853eacf3dSLuca Vizzarro        #   #################################################
88953eacf3dSLuca Vizzarro        #
89053eacf3dSLuca Vizzarro        #   ########### NIC statistics for port 1 ###########
89153eacf3dSLuca Vizzarro        #   values...
89253eacf3dSLuca Vizzarro        #   #################################################
89353eacf3dSLuca Vizzarro        #
89453eacf3dSLuca Vizzarro        iter = re.finditer(r"(^  #*.+#*$[^#]+)^  #*\r$", output, re.MULTILINE)
89553eacf3dSLuca Vizzarro        return [TestPmdPortStats.parse(block.group(1)) for block in iter]
89653eacf3dSLuca Vizzarro
89753eacf3dSLuca Vizzarro    def show_port_stats(self, port_id: int) -> TestPmdPortStats:
89853eacf3dSLuca Vizzarro        """Returns the given port statistics.
89953eacf3dSLuca Vizzarro
90053eacf3dSLuca Vizzarro        Args:
90153eacf3dSLuca Vizzarro            port_id: The port ID to gather information for.
90253eacf3dSLuca Vizzarro
90353eacf3dSLuca Vizzarro        Raises:
90453eacf3dSLuca Vizzarro            InteractiveCommandExecutionError: If `port_id` is invalid.
90553eacf3dSLuca Vizzarro
90653eacf3dSLuca Vizzarro        Returns:
90753eacf3dSLuca Vizzarro            TestPmdPortStats: An instance of `TestPmdPortStats` containing the given port's stats.
90853eacf3dSLuca Vizzarro        """
90953eacf3dSLuca Vizzarro        output = self.send_command(f"show port stats {port_id}", skip_first_line=True)
91053eacf3dSLuca Vizzarro        if output.startswith("Invalid port"):
91153eacf3dSLuca Vizzarro            raise InteractiveCommandExecutionError("invalid port given")
91253eacf3dSLuca Vizzarro
91353eacf3dSLuca Vizzarro        return TestPmdPortStats.parse(output)
91453eacf3dSLuca Vizzarro
9152b648cd4SJeremy Spewock    def _close(self) -> None:
916369d34b8SJeremy Spewock        """Overrides :meth:`~.interactive_shell.close`."""
9172b648cd4SJeremy Spewock        self.stop()
91892439dc9SJeremy Spewock        self.send_command("quit", "Bye...")
9192b648cd4SJeremy Spewock        return super()._close()
920