xref: /dpdk/dts/framework/remote_session/testpmd_shell.py (revision bfad0948df75e95e04cba1804a2749cfc91c56fb)
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š
10*bfad0948SLuca 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š
1761d5bc9bSLuca Vizzarroimport re
18369d34b8SJeremy Spewockimport time
1961d5bc9bSLuca Vizzarrofrom dataclasses import dataclass, field
2061d5bc9bSLuca Vizzarrofrom enum import Flag, auto
21840b1e01SJuraj Linkešfrom pathlib import PurePath
22*bfad0948SLuca Vizzarrofrom typing import ClassVar
23840b1e01SJuraj Linkeš
2461d5bc9bSLuca Vizzarrofrom typing_extensions import Self
2561d5bc9bSLuca Vizzarro
26369d34b8SJeremy Spewockfrom framework.exception import InteractiveCommandExecutionError
27fc0f7dc4SLuca Vizzarrofrom framework.params.testpmd import SimpleForwardingModes, TestPmdParams
2861d5bc9bSLuca Vizzarrofrom framework.parser import ParserFn, TextParser
29*bfad0948SLuca Vizzarrofrom framework.remote_session.dpdk_shell import DPDKShell
30369d34b8SJeremy Spewockfrom framework.settings import SETTINGS
31*bfad0948SLuca Vizzarrofrom framework.testbed_model.cpu import LogicalCoreCount, LogicalCoreList
32*bfad0948SLuca Vizzarrofrom framework.testbed_model.sut_node import SutNode
33369d34b8SJeremy Spewockfrom framework.utils import StrEnum
34369d34b8SJeremy Spewock
35840b1e01SJuraj Linkeš
363e967643SJuraj Linkešclass TestPmdDevice:
376ef07151SJuraj Linkeš    """The data of a device that testpmd can recognize.
386ef07151SJuraj Linkeš
396ef07151SJuraj Linkeš    Attributes:
406ef07151SJuraj Linkeš        pci_address: The PCI address of the device.
416ef07151SJuraj Linkeš    """
426ef07151SJuraj Linkeš
43840b1e01SJuraj Linkeš    pci_address: str
44840b1e01SJuraj Linkeš
45840b1e01SJuraj Linkeš    def __init__(self, pci_address_line: str):
466ef07151SJuraj Linkeš        """Initialize the device from the testpmd output line string.
476ef07151SJuraj Linkeš
486ef07151SJuraj Linkeš        Args:
496ef07151SJuraj Linkeš            pci_address_line: A line of testpmd output that contains a device.
506ef07151SJuraj Linkeš        """
51840b1e01SJuraj Linkeš        self.pci_address = pci_address_line.strip().split(": ")[1].strip()
52840b1e01SJuraj Linkeš
53840b1e01SJuraj Linkeš    def __str__(self) -> str:
546ef07151SJuraj Linkeš        """The PCI address captures what the device is."""
55840b1e01SJuraj Linkeš        return self.pci_address
56840b1e01SJuraj Linkeš
57840b1e01SJuraj Linkeš
5861d5bc9bSLuca Vizzarroclass VLANOffloadFlag(Flag):
5961d5bc9bSLuca Vizzarro    """Flag representing the VLAN offload settings of a NIC port."""
6061d5bc9bSLuca Vizzarro
6161d5bc9bSLuca Vizzarro    #:
6261d5bc9bSLuca Vizzarro    STRIP = auto()
6361d5bc9bSLuca Vizzarro    #:
6461d5bc9bSLuca Vizzarro    FILTER = auto()
6561d5bc9bSLuca Vizzarro    #:
6661d5bc9bSLuca Vizzarro    EXTEND = auto()
6761d5bc9bSLuca Vizzarro    #:
6861d5bc9bSLuca Vizzarro    QINQ_STRIP = auto()
6961d5bc9bSLuca Vizzarro
7061d5bc9bSLuca Vizzarro    @classmethod
7161d5bc9bSLuca Vizzarro    def from_str_dict(cls, d):
7261d5bc9bSLuca Vizzarro        """Makes an instance from a dict containing the flag member names with an "on" value.
7361d5bc9bSLuca Vizzarro
7461d5bc9bSLuca Vizzarro        Args:
7561d5bc9bSLuca Vizzarro            d: A dictionary containing the flag members as keys and any string value.
7661d5bc9bSLuca Vizzarro
7761d5bc9bSLuca Vizzarro        Returns:
7861d5bc9bSLuca Vizzarro            A new instance of the flag.
7961d5bc9bSLuca Vizzarro        """
8061d5bc9bSLuca Vizzarro        flag = cls(0)
8161d5bc9bSLuca Vizzarro        for name in cls.__members__:
8261d5bc9bSLuca Vizzarro            if d.get(name) == "on":
8361d5bc9bSLuca Vizzarro                flag |= cls[name]
8461d5bc9bSLuca Vizzarro        return flag
8561d5bc9bSLuca Vizzarro
8661d5bc9bSLuca Vizzarro    @classmethod
8761d5bc9bSLuca Vizzarro    def make_parser(cls) -> ParserFn:
8861d5bc9bSLuca Vizzarro        """Makes a parser function.
8961d5bc9bSLuca Vizzarro
9061d5bc9bSLuca Vizzarro        Returns:
9161d5bc9bSLuca Vizzarro            ParserFn: A dictionary for the `dataclasses.field` metadata argument containing a
9261d5bc9bSLuca Vizzarro                parser function that makes an instance of this flag from text.
9361d5bc9bSLuca Vizzarro        """
9461d5bc9bSLuca Vizzarro        return TextParser.wrap(
9561d5bc9bSLuca Vizzarro            TextParser.find(
9661d5bc9bSLuca Vizzarro                r"VLAN offload:\s+"
9761d5bc9bSLuca Vizzarro                r"strip (?P<STRIP>on|off), "
9861d5bc9bSLuca Vizzarro                r"filter (?P<FILTER>on|off), "
9961d5bc9bSLuca Vizzarro                r"extend (?P<EXTEND>on|off), "
10061d5bc9bSLuca Vizzarro                r"qinq strip (?P<QINQ_STRIP>on|off)$",
10161d5bc9bSLuca Vizzarro                re.MULTILINE,
10261d5bc9bSLuca Vizzarro                named=True,
10361d5bc9bSLuca Vizzarro            ),
10461d5bc9bSLuca Vizzarro            cls.from_str_dict,
10561d5bc9bSLuca Vizzarro        )
10661d5bc9bSLuca Vizzarro
10761d5bc9bSLuca Vizzarro
10861d5bc9bSLuca Vizzarroclass RSSOffloadTypesFlag(Flag):
10961d5bc9bSLuca Vizzarro    """Flag representing the RSS offload flow types supported by the NIC port."""
11061d5bc9bSLuca Vizzarro
11161d5bc9bSLuca Vizzarro    #:
11261d5bc9bSLuca Vizzarro    ipv4 = auto()
11361d5bc9bSLuca Vizzarro    #:
11461d5bc9bSLuca Vizzarro    ipv4_frag = auto()
11561d5bc9bSLuca Vizzarro    #:
11661d5bc9bSLuca Vizzarro    ipv4_tcp = auto()
11761d5bc9bSLuca Vizzarro    #:
11861d5bc9bSLuca Vizzarro    ipv4_udp = auto()
11961d5bc9bSLuca Vizzarro    #:
12061d5bc9bSLuca Vizzarro    ipv4_sctp = auto()
12161d5bc9bSLuca Vizzarro    #:
12261d5bc9bSLuca Vizzarro    ipv4_other = auto()
12361d5bc9bSLuca Vizzarro    #:
12461d5bc9bSLuca Vizzarro    ipv6 = auto()
12561d5bc9bSLuca Vizzarro    #:
12661d5bc9bSLuca Vizzarro    ipv6_frag = auto()
12761d5bc9bSLuca Vizzarro    #:
12861d5bc9bSLuca Vizzarro    ipv6_tcp = auto()
12961d5bc9bSLuca Vizzarro    #:
13061d5bc9bSLuca Vizzarro    ipv6_udp = auto()
13161d5bc9bSLuca Vizzarro    #:
13261d5bc9bSLuca Vizzarro    ipv6_sctp = auto()
13361d5bc9bSLuca Vizzarro    #:
13461d5bc9bSLuca Vizzarro    ipv6_other = auto()
13561d5bc9bSLuca Vizzarro    #:
13661d5bc9bSLuca Vizzarro    l2_payload = auto()
13761d5bc9bSLuca Vizzarro    #:
13861d5bc9bSLuca Vizzarro    ipv6_ex = auto()
13961d5bc9bSLuca Vizzarro    #:
14061d5bc9bSLuca Vizzarro    ipv6_tcp_ex = auto()
14161d5bc9bSLuca Vizzarro    #:
14261d5bc9bSLuca Vizzarro    ipv6_udp_ex = auto()
14361d5bc9bSLuca Vizzarro    #:
14461d5bc9bSLuca Vizzarro    port = auto()
14561d5bc9bSLuca Vizzarro    #:
14661d5bc9bSLuca Vizzarro    vxlan = auto()
14761d5bc9bSLuca Vizzarro    #:
14861d5bc9bSLuca Vizzarro    geneve = auto()
14961d5bc9bSLuca Vizzarro    #:
15061d5bc9bSLuca Vizzarro    nvgre = auto()
15161d5bc9bSLuca Vizzarro    #:
15261d5bc9bSLuca Vizzarro    user_defined_22 = auto()
15361d5bc9bSLuca Vizzarro    #:
15461d5bc9bSLuca Vizzarro    gtpu = auto()
15561d5bc9bSLuca Vizzarro    #:
15661d5bc9bSLuca Vizzarro    eth = auto()
15761d5bc9bSLuca Vizzarro    #:
15861d5bc9bSLuca Vizzarro    s_vlan = auto()
15961d5bc9bSLuca Vizzarro    #:
16061d5bc9bSLuca Vizzarro    c_vlan = auto()
16161d5bc9bSLuca Vizzarro    #:
16261d5bc9bSLuca Vizzarro    esp = auto()
16361d5bc9bSLuca Vizzarro    #:
16461d5bc9bSLuca Vizzarro    ah = auto()
16561d5bc9bSLuca Vizzarro    #:
16661d5bc9bSLuca Vizzarro    l2tpv3 = auto()
16761d5bc9bSLuca Vizzarro    #:
16861d5bc9bSLuca Vizzarro    pfcp = auto()
16961d5bc9bSLuca Vizzarro    #:
17061d5bc9bSLuca Vizzarro    pppoe = auto()
17161d5bc9bSLuca Vizzarro    #:
17261d5bc9bSLuca Vizzarro    ecpri = auto()
17361d5bc9bSLuca Vizzarro    #:
17461d5bc9bSLuca Vizzarro    mpls = auto()
17561d5bc9bSLuca Vizzarro    #:
17661d5bc9bSLuca Vizzarro    ipv4_chksum = auto()
17761d5bc9bSLuca Vizzarro    #:
17861d5bc9bSLuca Vizzarro    l4_chksum = auto()
17961d5bc9bSLuca Vizzarro    #:
18061d5bc9bSLuca Vizzarro    l2tpv2 = auto()
18161d5bc9bSLuca Vizzarro    #:
18261d5bc9bSLuca Vizzarro    ipv6_flow_label = auto()
18361d5bc9bSLuca Vizzarro    #:
18461d5bc9bSLuca Vizzarro    user_defined_38 = auto()
18561d5bc9bSLuca Vizzarro    #:
18661d5bc9bSLuca Vizzarro    user_defined_39 = auto()
18761d5bc9bSLuca Vizzarro    #:
18861d5bc9bSLuca Vizzarro    user_defined_40 = auto()
18961d5bc9bSLuca Vizzarro    #:
19061d5bc9bSLuca Vizzarro    user_defined_41 = auto()
19161d5bc9bSLuca Vizzarro    #:
19261d5bc9bSLuca Vizzarro    user_defined_42 = auto()
19361d5bc9bSLuca Vizzarro    #:
19461d5bc9bSLuca Vizzarro    user_defined_43 = auto()
19561d5bc9bSLuca Vizzarro    #:
19661d5bc9bSLuca Vizzarro    user_defined_44 = auto()
19761d5bc9bSLuca Vizzarro    #:
19861d5bc9bSLuca Vizzarro    user_defined_45 = auto()
19961d5bc9bSLuca Vizzarro    #:
20061d5bc9bSLuca Vizzarro    user_defined_46 = auto()
20161d5bc9bSLuca Vizzarro    #:
20261d5bc9bSLuca Vizzarro    user_defined_47 = auto()
20361d5bc9bSLuca Vizzarro    #:
20461d5bc9bSLuca Vizzarro    user_defined_48 = auto()
20561d5bc9bSLuca Vizzarro    #:
20661d5bc9bSLuca Vizzarro    user_defined_49 = auto()
20761d5bc9bSLuca Vizzarro    #:
20861d5bc9bSLuca Vizzarro    user_defined_50 = auto()
20961d5bc9bSLuca Vizzarro    #:
21061d5bc9bSLuca Vizzarro    user_defined_51 = auto()
21161d5bc9bSLuca Vizzarro    #:
21261d5bc9bSLuca Vizzarro    l3_pre96 = auto()
21361d5bc9bSLuca Vizzarro    #:
21461d5bc9bSLuca Vizzarro    l3_pre64 = auto()
21561d5bc9bSLuca Vizzarro    #:
21661d5bc9bSLuca Vizzarro    l3_pre56 = auto()
21761d5bc9bSLuca Vizzarro    #:
21861d5bc9bSLuca Vizzarro    l3_pre48 = auto()
21961d5bc9bSLuca Vizzarro    #:
22061d5bc9bSLuca Vizzarro    l3_pre40 = auto()
22161d5bc9bSLuca Vizzarro    #:
22261d5bc9bSLuca Vizzarro    l3_pre32 = auto()
22361d5bc9bSLuca Vizzarro    #:
22461d5bc9bSLuca Vizzarro    l2_dst_only = auto()
22561d5bc9bSLuca Vizzarro    #:
22661d5bc9bSLuca Vizzarro    l2_src_only = auto()
22761d5bc9bSLuca Vizzarro    #:
22861d5bc9bSLuca Vizzarro    l4_dst_only = auto()
22961d5bc9bSLuca Vizzarro    #:
23061d5bc9bSLuca Vizzarro    l4_src_only = auto()
23161d5bc9bSLuca Vizzarro    #:
23261d5bc9bSLuca Vizzarro    l3_dst_only = auto()
23361d5bc9bSLuca Vizzarro    #:
23461d5bc9bSLuca Vizzarro    l3_src_only = auto()
23561d5bc9bSLuca Vizzarro
23661d5bc9bSLuca Vizzarro    #:
23761d5bc9bSLuca Vizzarro    ip = ipv4 | ipv4_frag | ipv4_other | ipv6 | ipv6_frag | ipv6_other | ipv6_ex
23861d5bc9bSLuca Vizzarro    #:
23961d5bc9bSLuca Vizzarro    udp = ipv4_udp | ipv6_udp | ipv6_udp_ex
24061d5bc9bSLuca Vizzarro    #:
24161d5bc9bSLuca Vizzarro    tcp = ipv4_tcp | ipv6_tcp | ipv6_tcp_ex
24261d5bc9bSLuca Vizzarro    #:
24361d5bc9bSLuca Vizzarro    sctp = ipv4_sctp | ipv6_sctp
24461d5bc9bSLuca Vizzarro    #:
24561d5bc9bSLuca Vizzarro    tunnel = vxlan | geneve | nvgre
24661d5bc9bSLuca Vizzarro    #:
24761d5bc9bSLuca Vizzarro    vlan = s_vlan | c_vlan
24861d5bc9bSLuca Vizzarro    #:
24961d5bc9bSLuca Vizzarro    all = (
25061d5bc9bSLuca Vizzarro        eth
25161d5bc9bSLuca Vizzarro        | vlan
25261d5bc9bSLuca Vizzarro        | ip
25361d5bc9bSLuca Vizzarro        | tcp
25461d5bc9bSLuca Vizzarro        | udp
25561d5bc9bSLuca Vizzarro        | sctp
25661d5bc9bSLuca Vizzarro        | l2_payload
25761d5bc9bSLuca Vizzarro        | l2tpv3
25861d5bc9bSLuca Vizzarro        | esp
25961d5bc9bSLuca Vizzarro        | ah
26061d5bc9bSLuca Vizzarro        | pfcp
26161d5bc9bSLuca Vizzarro        | gtpu
26261d5bc9bSLuca Vizzarro        | ecpri
26361d5bc9bSLuca Vizzarro        | mpls
26461d5bc9bSLuca Vizzarro        | l2tpv2
26561d5bc9bSLuca Vizzarro    )
26661d5bc9bSLuca Vizzarro
26761d5bc9bSLuca Vizzarro    @classmethod
26861d5bc9bSLuca Vizzarro    def from_list_string(cls, names: str) -> Self:
26961d5bc9bSLuca Vizzarro        """Makes a flag from a whitespace-separated list of names.
27061d5bc9bSLuca Vizzarro
27161d5bc9bSLuca Vizzarro        Args:
27261d5bc9bSLuca Vizzarro            names: a whitespace-separated list containing the members of this flag.
27361d5bc9bSLuca Vizzarro
27461d5bc9bSLuca Vizzarro        Returns:
27561d5bc9bSLuca Vizzarro            An instance of this flag.
27661d5bc9bSLuca Vizzarro        """
27761d5bc9bSLuca Vizzarro        flag = cls(0)
27861d5bc9bSLuca Vizzarro        for name in names.split():
27961d5bc9bSLuca Vizzarro            flag |= cls.from_str(name)
28061d5bc9bSLuca Vizzarro        return flag
28161d5bc9bSLuca Vizzarro
28261d5bc9bSLuca Vizzarro    @classmethod
28361d5bc9bSLuca Vizzarro    def from_str(cls, name: str) -> Self:
28461d5bc9bSLuca Vizzarro        """Makes a flag matching the supplied name.
28561d5bc9bSLuca Vizzarro
28661d5bc9bSLuca Vizzarro        Args:
28761d5bc9bSLuca Vizzarro            name: a valid member of this flag in text
28861d5bc9bSLuca Vizzarro        Returns:
28961d5bc9bSLuca Vizzarro            An instance of this flag.
29061d5bc9bSLuca Vizzarro        """
29161d5bc9bSLuca Vizzarro        member_name = name.strip().replace("-", "_")
29261d5bc9bSLuca Vizzarro        return cls[member_name]
29361d5bc9bSLuca Vizzarro
29461d5bc9bSLuca Vizzarro    @classmethod
29561d5bc9bSLuca Vizzarro    def make_parser(cls) -> ParserFn:
29661d5bc9bSLuca Vizzarro        """Makes a parser function.
29761d5bc9bSLuca Vizzarro
29861d5bc9bSLuca Vizzarro        Returns:
29961d5bc9bSLuca Vizzarro            ParserFn: A dictionary for the `dataclasses.field` metadata argument containing a
30061d5bc9bSLuca Vizzarro                parser function that makes an instance of this flag from text.
30161d5bc9bSLuca Vizzarro        """
30261d5bc9bSLuca Vizzarro        return TextParser.wrap(
30361d5bc9bSLuca Vizzarro            TextParser.find(r"Supported RSS offload flow types:((?:\r?\n?  \S+)+)", re.MULTILINE),
30461d5bc9bSLuca Vizzarro            RSSOffloadTypesFlag.from_list_string,
30561d5bc9bSLuca Vizzarro        )
30661d5bc9bSLuca Vizzarro
30761d5bc9bSLuca Vizzarro
30861d5bc9bSLuca Vizzarroclass DeviceCapabilitiesFlag(Flag):
30961d5bc9bSLuca Vizzarro    """Flag representing the device capabilities."""
31061d5bc9bSLuca Vizzarro
31161d5bc9bSLuca Vizzarro    #: Device supports Rx queue setup after device started.
31261d5bc9bSLuca Vizzarro    RUNTIME_RX_QUEUE_SETUP = auto()
31361d5bc9bSLuca Vizzarro    #: Device supports Tx queue setup after device started.
31461d5bc9bSLuca Vizzarro    RUNTIME_TX_QUEUE_SETUP = auto()
31561d5bc9bSLuca Vizzarro    #: Device supports shared Rx queue among ports within Rx domain and switch domain.
31661d5bc9bSLuca Vizzarro    RXQ_SHARE = auto()
31761d5bc9bSLuca Vizzarro    #: Device supports keeping flow rules across restart.
31861d5bc9bSLuca Vizzarro    FLOW_RULE_KEEP = auto()
31961d5bc9bSLuca Vizzarro    #: Device supports keeping shared flow objects across restart.
32061d5bc9bSLuca Vizzarro    FLOW_SHARED_OBJECT_KEEP = auto()
32161d5bc9bSLuca Vizzarro
32261d5bc9bSLuca Vizzarro    @classmethod
32361d5bc9bSLuca Vizzarro    def make_parser(cls) -> ParserFn:
32461d5bc9bSLuca Vizzarro        """Makes a parser function.
32561d5bc9bSLuca Vizzarro
32661d5bc9bSLuca Vizzarro        Returns:
32761d5bc9bSLuca Vizzarro            ParserFn: A dictionary for the `dataclasses.field` metadata argument containing a
32861d5bc9bSLuca Vizzarro                parser function that makes an instance of this flag from text.
32961d5bc9bSLuca Vizzarro        """
33061d5bc9bSLuca Vizzarro        return TextParser.wrap(
33161d5bc9bSLuca Vizzarro            TextParser.find_int(r"Device capabilities: (0x[A-Fa-f\d]+)"),
33261d5bc9bSLuca Vizzarro            cls,
33361d5bc9bSLuca Vizzarro        )
33461d5bc9bSLuca Vizzarro
33561d5bc9bSLuca Vizzarro
33661d5bc9bSLuca Vizzarroclass DeviceErrorHandlingMode(StrEnum):
33761d5bc9bSLuca Vizzarro    """Enum representing the device error handling mode."""
33861d5bc9bSLuca Vizzarro
33961d5bc9bSLuca Vizzarro    #:
34061d5bc9bSLuca Vizzarro    none = auto()
34161d5bc9bSLuca Vizzarro    #:
34261d5bc9bSLuca Vizzarro    passive = auto()
34361d5bc9bSLuca Vizzarro    #:
34461d5bc9bSLuca Vizzarro    proactive = auto()
34561d5bc9bSLuca Vizzarro    #:
34661d5bc9bSLuca Vizzarro    unknown = auto()
34761d5bc9bSLuca Vizzarro
34861d5bc9bSLuca Vizzarro    @classmethod
34961d5bc9bSLuca Vizzarro    def make_parser(cls) -> ParserFn:
35061d5bc9bSLuca Vizzarro        """Makes a parser function.
35161d5bc9bSLuca Vizzarro
35261d5bc9bSLuca Vizzarro        Returns:
35361d5bc9bSLuca Vizzarro            ParserFn: A dictionary for the `dataclasses.field` metadata argument containing a
35461d5bc9bSLuca Vizzarro                parser function that makes an instance of this enum from text.
35561d5bc9bSLuca Vizzarro        """
35661d5bc9bSLuca Vizzarro        return TextParser.wrap(TextParser.find(r"Device error handling mode: (\w+)"), cls)
35761d5bc9bSLuca Vizzarro
35861d5bc9bSLuca Vizzarro
35961d5bc9bSLuca Vizzarrodef make_device_private_info_parser() -> ParserFn:
36061d5bc9bSLuca Vizzarro    """Device private information parser.
36161d5bc9bSLuca Vizzarro
36261d5bc9bSLuca Vizzarro    Ensures that we are not parsing invalid device private info output.
36361d5bc9bSLuca Vizzarro
36461d5bc9bSLuca Vizzarro    Returns:
36561d5bc9bSLuca Vizzarro        ParserFn: A dictionary for the `dataclasses.field` metadata argument containing a parser
36661d5bc9bSLuca Vizzarro            function that parses the device private info from the TestPmd port info output.
36761d5bc9bSLuca Vizzarro    """
36861d5bc9bSLuca Vizzarro
36961d5bc9bSLuca Vizzarro    def _validate(info: str):
37061d5bc9bSLuca Vizzarro        info = info.strip()
37161d5bc9bSLuca Vizzarro        if info == "none" or info.startswith("Invalid file") or info.startswith("Failed to dump"):
37261d5bc9bSLuca Vizzarro            return None
37361d5bc9bSLuca Vizzarro        return info
37461d5bc9bSLuca Vizzarro
37561d5bc9bSLuca Vizzarro    return TextParser.wrap(TextParser.find(r"Device private info:\s+([\s\S]+)"), _validate)
37661d5bc9bSLuca Vizzarro
37761d5bc9bSLuca Vizzarro
37861d5bc9bSLuca Vizzarro@dataclass
37961d5bc9bSLuca Vizzarroclass TestPmdPort(TextParser):
38061d5bc9bSLuca Vizzarro    """Dataclass representing the result of testpmd's ``show port info`` command."""
38161d5bc9bSLuca Vizzarro
38261d5bc9bSLuca Vizzarro    #:
38361d5bc9bSLuca Vizzarro    id: int = field(metadata=TextParser.find_int(r"Infos for port (\d+)\b"))
38461d5bc9bSLuca Vizzarro    #:
38561d5bc9bSLuca Vizzarro    device_name: str = field(metadata=TextParser.find(r"Device name: ([^\r\n]+)"))
38661d5bc9bSLuca Vizzarro    #:
38761d5bc9bSLuca Vizzarro    driver_name: str = field(metadata=TextParser.find(r"Driver name: ([^\r\n]+)"))
38861d5bc9bSLuca Vizzarro    #:
38961d5bc9bSLuca Vizzarro    socket_id: int = field(metadata=TextParser.find_int(r"Connect to socket: (\d+)"))
39061d5bc9bSLuca Vizzarro    #:
39161d5bc9bSLuca Vizzarro    is_link_up: bool = field(metadata=TextParser.find("Link status: up"))
39261d5bc9bSLuca Vizzarro    #:
39361d5bc9bSLuca Vizzarro    link_speed: str = field(metadata=TextParser.find(r"Link speed: ([^\r\n]+)"))
39461d5bc9bSLuca Vizzarro    #:
39561d5bc9bSLuca Vizzarro    is_link_full_duplex: bool = field(metadata=TextParser.find("Link duplex: full-duplex"))
39661d5bc9bSLuca Vizzarro    #:
39761d5bc9bSLuca Vizzarro    is_link_autonegotiated: bool = field(metadata=TextParser.find("Autoneg status: On"))
39861d5bc9bSLuca Vizzarro    #:
39961d5bc9bSLuca Vizzarro    is_promiscuous_mode_enabled: bool = field(metadata=TextParser.find("Promiscuous mode: enabled"))
40061d5bc9bSLuca Vizzarro    #:
40161d5bc9bSLuca Vizzarro    is_allmulticast_mode_enabled: bool = field(
40261d5bc9bSLuca Vizzarro        metadata=TextParser.find("Allmulticast mode: enabled")
40361d5bc9bSLuca Vizzarro    )
40461d5bc9bSLuca Vizzarro    #: Maximum number of MAC addresses
40561d5bc9bSLuca Vizzarro    max_mac_addresses_num: int = field(
40661d5bc9bSLuca Vizzarro        metadata=TextParser.find_int(r"Maximum number of MAC addresses: (\d+)")
40761d5bc9bSLuca Vizzarro    )
40861d5bc9bSLuca Vizzarro    #: Maximum configurable length of RX packet
40961d5bc9bSLuca Vizzarro    max_hash_mac_addresses_num: int = field(
41061d5bc9bSLuca Vizzarro        metadata=TextParser.find_int(r"Maximum number of MAC addresses of hash filtering: (\d+)")
41161d5bc9bSLuca Vizzarro    )
41261d5bc9bSLuca Vizzarro    #: Minimum size of RX buffer
41361d5bc9bSLuca Vizzarro    min_rx_bufsize: int = field(metadata=TextParser.find_int(r"Minimum size of RX buffer: (\d+)"))
41461d5bc9bSLuca Vizzarro    #: Maximum configurable length of RX packet
41561d5bc9bSLuca Vizzarro    max_rx_packet_length: int = field(
41661d5bc9bSLuca Vizzarro        metadata=TextParser.find_int(r"Maximum configurable length of RX packet: (\d+)")
41761d5bc9bSLuca Vizzarro    )
41861d5bc9bSLuca Vizzarro    #: Maximum configurable size of LRO aggregated packet
41961d5bc9bSLuca Vizzarro    max_lro_packet_size: int = field(
42061d5bc9bSLuca Vizzarro        metadata=TextParser.find_int(r"Maximum configurable size of LRO aggregated packet: (\d+)")
42161d5bc9bSLuca Vizzarro    )
42261d5bc9bSLuca Vizzarro
42361d5bc9bSLuca Vizzarro    #: Current number of RX queues
42461d5bc9bSLuca Vizzarro    rx_queues_num: int = field(metadata=TextParser.find_int(r"Current number of RX queues: (\d+)"))
42561d5bc9bSLuca Vizzarro    #: Max possible RX queues
42661d5bc9bSLuca Vizzarro    max_rx_queues_num: int = field(metadata=TextParser.find_int(r"Max possible RX queues: (\d+)"))
42761d5bc9bSLuca Vizzarro    #: Max possible number of RXDs per queue
42861d5bc9bSLuca Vizzarro    max_queue_rxd_num: int = field(
42961d5bc9bSLuca Vizzarro        metadata=TextParser.find_int(r"Max possible number of RXDs per queue: (\d+)")
43061d5bc9bSLuca Vizzarro    )
43161d5bc9bSLuca Vizzarro    #: Min possible number of RXDs per queue
43261d5bc9bSLuca Vizzarro    min_queue_rxd_num: int = field(
43361d5bc9bSLuca Vizzarro        metadata=TextParser.find_int(r"Min possible number of RXDs per queue: (\d+)")
43461d5bc9bSLuca Vizzarro    )
43561d5bc9bSLuca Vizzarro    #: RXDs number alignment
43661d5bc9bSLuca Vizzarro    rxd_alignment_num: int = field(metadata=TextParser.find_int(r"RXDs number alignment: (\d+)"))
43761d5bc9bSLuca Vizzarro
43861d5bc9bSLuca Vizzarro    #: Current number of TX queues
43961d5bc9bSLuca Vizzarro    tx_queues_num: int = field(metadata=TextParser.find_int(r"Current number of TX queues: (\d+)"))
44061d5bc9bSLuca Vizzarro    #: Max possible TX queues
44161d5bc9bSLuca Vizzarro    max_tx_queues_num: int = field(metadata=TextParser.find_int(r"Max possible TX queues: (\d+)"))
44261d5bc9bSLuca Vizzarro    #: Max possible number of TXDs per queue
44361d5bc9bSLuca Vizzarro    max_queue_txd_num: int = field(
44461d5bc9bSLuca Vizzarro        metadata=TextParser.find_int(r"Max possible number of TXDs per queue: (\d+)")
44561d5bc9bSLuca Vizzarro    )
44661d5bc9bSLuca Vizzarro    #: Min possible number of TXDs per queue
44761d5bc9bSLuca Vizzarro    min_queue_txd_num: int = field(
44861d5bc9bSLuca Vizzarro        metadata=TextParser.find_int(r"Min possible number of TXDs per queue: (\d+)")
44961d5bc9bSLuca Vizzarro    )
45061d5bc9bSLuca Vizzarro    #: TXDs number alignment
45161d5bc9bSLuca Vizzarro    txd_alignment_num: int = field(metadata=TextParser.find_int(r"TXDs number alignment: (\d+)"))
45261d5bc9bSLuca Vizzarro    #: Max segment number per packet
45361d5bc9bSLuca Vizzarro    max_packet_segment_num: int = field(
45461d5bc9bSLuca Vizzarro        metadata=TextParser.find_int(r"Max segment number per packet: (\d+)")
45561d5bc9bSLuca Vizzarro    )
45661d5bc9bSLuca Vizzarro    #: Max segment number per MTU/TSO
45761d5bc9bSLuca Vizzarro    max_mtu_segment_num: int = field(
45861d5bc9bSLuca Vizzarro        metadata=TextParser.find_int(r"Max segment number per MTU\/TSO: (\d+)")
45961d5bc9bSLuca Vizzarro    )
46061d5bc9bSLuca Vizzarro
46161d5bc9bSLuca Vizzarro    #:
46261d5bc9bSLuca Vizzarro    device_capabilities: DeviceCapabilitiesFlag = field(
46361d5bc9bSLuca Vizzarro        metadata=DeviceCapabilitiesFlag.make_parser(),
46461d5bc9bSLuca Vizzarro    )
46561d5bc9bSLuca Vizzarro    #:
46661d5bc9bSLuca Vizzarro    device_error_handling_mode: DeviceErrorHandlingMode = field(
46761d5bc9bSLuca Vizzarro        metadata=DeviceErrorHandlingMode.make_parser()
46861d5bc9bSLuca Vizzarro    )
46961d5bc9bSLuca Vizzarro    #:
47061d5bc9bSLuca Vizzarro    device_private_info: str | None = field(
47161d5bc9bSLuca Vizzarro        default=None,
47261d5bc9bSLuca Vizzarro        metadata=make_device_private_info_parser(),
47361d5bc9bSLuca Vizzarro    )
47461d5bc9bSLuca Vizzarro
47561d5bc9bSLuca Vizzarro    #:
47661d5bc9bSLuca Vizzarro    hash_key_size: int | None = field(
47761d5bc9bSLuca Vizzarro        default=None, metadata=TextParser.find_int(r"Hash key size in bytes: (\d+)")
47861d5bc9bSLuca Vizzarro    )
47961d5bc9bSLuca Vizzarro    #:
48061d5bc9bSLuca Vizzarro    redirection_table_size: int | None = field(
48161d5bc9bSLuca Vizzarro        default=None, metadata=TextParser.find_int(r"Redirection table size: (\d+)")
48261d5bc9bSLuca Vizzarro    )
48361d5bc9bSLuca Vizzarro    #:
48461d5bc9bSLuca Vizzarro    supported_rss_offload_flow_types: RSSOffloadTypesFlag = field(
48561d5bc9bSLuca Vizzarro        default=RSSOffloadTypesFlag(0), metadata=RSSOffloadTypesFlag.make_parser()
48661d5bc9bSLuca Vizzarro    )
48761d5bc9bSLuca Vizzarro
48861d5bc9bSLuca Vizzarro    #:
48961d5bc9bSLuca Vizzarro    mac_address: str | None = field(
49061d5bc9bSLuca Vizzarro        default=None, metadata=TextParser.find(r"MAC address: ([A-Fa-f0-9:]+)")
49161d5bc9bSLuca Vizzarro    )
49261d5bc9bSLuca Vizzarro    #:
49361d5bc9bSLuca Vizzarro    fw_version: str | None = field(
49461d5bc9bSLuca Vizzarro        default=None, metadata=TextParser.find(r"Firmware-version: ([^\r\n]+)")
49561d5bc9bSLuca Vizzarro    )
49661d5bc9bSLuca Vizzarro    #:
49761d5bc9bSLuca Vizzarro    dev_args: str | None = field(default=None, metadata=TextParser.find(r"Devargs: ([^\r\n]+)"))
49861d5bc9bSLuca Vizzarro    #: Socket id of the memory allocation
49961d5bc9bSLuca Vizzarro    mem_alloc_socket_id: int | None = field(
50061d5bc9bSLuca Vizzarro        default=None,
50161d5bc9bSLuca Vizzarro        metadata=TextParser.find_int(r"memory allocation on the socket: (\d+)"),
50261d5bc9bSLuca Vizzarro    )
50361d5bc9bSLuca Vizzarro    #:
50461d5bc9bSLuca Vizzarro    mtu: int | None = field(default=None, metadata=TextParser.find_int(r"MTU: (\d+)"))
50561d5bc9bSLuca Vizzarro
50661d5bc9bSLuca Vizzarro    #:
50761d5bc9bSLuca Vizzarro    vlan_offload: VLANOffloadFlag | None = field(
50861d5bc9bSLuca Vizzarro        default=None,
50961d5bc9bSLuca Vizzarro        metadata=VLANOffloadFlag.make_parser(),
51061d5bc9bSLuca Vizzarro    )
51161d5bc9bSLuca Vizzarro
51261d5bc9bSLuca Vizzarro    #: Maximum size of RX buffer
51361d5bc9bSLuca Vizzarro    max_rx_bufsize: int | None = field(
51461d5bc9bSLuca Vizzarro        default=None, metadata=TextParser.find_int(r"Maximum size of RX buffer: (\d+)")
51561d5bc9bSLuca Vizzarro    )
51661d5bc9bSLuca Vizzarro    #: Maximum number of VFs
51761d5bc9bSLuca Vizzarro    max_vfs_num: int | None = field(
51861d5bc9bSLuca Vizzarro        default=None, metadata=TextParser.find_int(r"Maximum number of VFs: (\d+)")
51961d5bc9bSLuca Vizzarro    )
52061d5bc9bSLuca Vizzarro    #: Maximum number of VMDq pools
52161d5bc9bSLuca Vizzarro    max_vmdq_pools_num: int | None = field(
52261d5bc9bSLuca Vizzarro        default=None, metadata=TextParser.find_int(r"Maximum number of VMDq pools: (\d+)")
52361d5bc9bSLuca Vizzarro    )
52461d5bc9bSLuca Vizzarro
52561d5bc9bSLuca Vizzarro    #:
52661d5bc9bSLuca Vizzarro    switch_name: str | None = field(
52761d5bc9bSLuca Vizzarro        default=None, metadata=TextParser.find(r"Switch name: ([\r\n]+)")
52861d5bc9bSLuca Vizzarro    )
52961d5bc9bSLuca Vizzarro    #:
53061d5bc9bSLuca Vizzarro    switch_domain_id: int | None = field(
53161d5bc9bSLuca Vizzarro        default=None, metadata=TextParser.find_int(r"Switch domain Id: (\d+)")
53261d5bc9bSLuca Vizzarro    )
53361d5bc9bSLuca Vizzarro    #:
53461d5bc9bSLuca Vizzarro    switch_port_id: int | None = field(
53561d5bc9bSLuca Vizzarro        default=None, metadata=TextParser.find_int(r"Switch Port Id: (\d+)")
53661d5bc9bSLuca Vizzarro    )
53761d5bc9bSLuca Vizzarro    #:
53861d5bc9bSLuca Vizzarro    switch_rx_domain: int | None = field(
53961d5bc9bSLuca Vizzarro        default=None, metadata=TextParser.find_int(r"Switch Rx domain: (\d+)")
54061d5bc9bSLuca Vizzarro    )
54161d5bc9bSLuca Vizzarro
54261d5bc9bSLuca Vizzarro
54353eacf3dSLuca Vizzarro@dataclass
54453eacf3dSLuca Vizzarroclass TestPmdPortStats(TextParser):
54553eacf3dSLuca Vizzarro    """Port statistics."""
54653eacf3dSLuca Vizzarro
54753eacf3dSLuca Vizzarro    #:
54853eacf3dSLuca Vizzarro    port_id: int = field(metadata=TextParser.find_int(r"NIC statistics for port (\d+)"))
54953eacf3dSLuca Vizzarro
55053eacf3dSLuca Vizzarro    #:
55153eacf3dSLuca Vizzarro    rx_packets: int = field(metadata=TextParser.find_int(r"RX-packets:\s+(\d+)"))
55253eacf3dSLuca Vizzarro    #:
55353eacf3dSLuca Vizzarro    rx_missed: int = field(metadata=TextParser.find_int(r"RX-missed:\s+(\d+)"))
55453eacf3dSLuca Vizzarro    #:
55553eacf3dSLuca Vizzarro    rx_bytes: int = field(metadata=TextParser.find_int(r"RX-bytes:\s+(\d+)"))
55653eacf3dSLuca Vizzarro    #:
55753eacf3dSLuca Vizzarro    rx_errors: int = field(metadata=TextParser.find_int(r"RX-errors:\s+(\d+)"))
55853eacf3dSLuca Vizzarro    #:
55953eacf3dSLuca Vizzarro    rx_nombuf: int = field(metadata=TextParser.find_int(r"RX-nombuf:\s+(\d+)"))
56053eacf3dSLuca Vizzarro
56153eacf3dSLuca Vizzarro    #:
56253eacf3dSLuca Vizzarro    tx_packets: int = field(metadata=TextParser.find_int(r"TX-packets:\s+(\d+)"))
56353eacf3dSLuca Vizzarro    #:
56453eacf3dSLuca Vizzarro    tx_errors: int = field(metadata=TextParser.find_int(r"TX-errors:\s+(\d+)"))
56553eacf3dSLuca Vizzarro    #:
56653eacf3dSLuca Vizzarro    tx_bytes: int = field(metadata=TextParser.find_int(r"TX-bytes:\s+(\d+)"))
56753eacf3dSLuca Vizzarro
56853eacf3dSLuca Vizzarro    #:
56953eacf3dSLuca Vizzarro    rx_pps: int = field(metadata=TextParser.find_int(r"Rx-pps:\s+(\d+)"))
57053eacf3dSLuca Vizzarro    #:
57153eacf3dSLuca Vizzarro    rx_bps: int = field(metadata=TextParser.find_int(r"Rx-bps:\s+(\d+)"))
57253eacf3dSLuca Vizzarro
57353eacf3dSLuca Vizzarro    #:
57453eacf3dSLuca Vizzarro    tx_pps: int = field(metadata=TextParser.find_int(r"Tx-pps:\s+(\d+)"))
57553eacf3dSLuca Vizzarro    #:
57653eacf3dSLuca Vizzarro    tx_bps: int = field(metadata=TextParser.find_int(r"Tx-bps:\s+(\d+)"))
57753eacf3dSLuca Vizzarro
57853eacf3dSLuca Vizzarro
579*bfad0948SLuca Vizzarroclass TestPmdShell(DPDKShell):
5806ef07151SJuraj Linkeš    """Testpmd interactive shell.
5816ef07151SJuraj Linkeš
5826ef07151SJuraj Linkeš    The testpmd shell users should never use
5836ef07151SJuraj Linkeš    the :meth:`~.interactive_shell.InteractiveShell.send_command` method directly, but rather
5846ef07151SJuraj Linkeš    call specialized methods. If there isn't one that satisfies a need, it should be added.
5856ef07151SJuraj Linkeš    """
5866ef07151SJuraj Linkeš
587*bfad0948SLuca Vizzarro    _app_params: TestPmdParams
588369d34b8SJeremy Spewock
5896ef07151SJuraj Linkeš    #: The path to the testpmd executable.
5906ef07151SJuraj Linkeš    path: ClassVar[PurePath] = PurePath("app", "dpdk-testpmd")
5916ef07151SJuraj Linkeš
5926ef07151SJuraj Linkeš    #: The testpmd's prompt.
5936ef07151SJuraj Linkeš    _default_prompt: ClassVar[str] = "testpmd>"
5946ef07151SJuraj Linkeš
5956ef07151SJuraj Linkeš    #: This forces the prompt to appear after sending a command.
5966ef07151SJuraj Linkeš    _command_extra_chars: ClassVar[str] = "\n"
597840b1e01SJuraj Linkeš
598*bfad0948SLuca Vizzarro    def __init__(
599*bfad0948SLuca Vizzarro        self,
600*bfad0948SLuca Vizzarro        node: SutNode,
601*bfad0948SLuca Vizzarro        privileged: bool = True,
602*bfad0948SLuca Vizzarro        timeout: float = SETTINGS.timeout,
603*bfad0948SLuca Vizzarro        lcore_filter_specifier: LogicalCoreCount | LogicalCoreList = LogicalCoreCount(),
604*bfad0948SLuca Vizzarro        ascending_cores: bool = True,
605*bfad0948SLuca Vizzarro        append_prefix_timestamp: bool = True,
606*bfad0948SLuca Vizzarro        start_on_init: bool = True,
607*bfad0948SLuca Vizzarro        **app_params,
608*bfad0948SLuca Vizzarro    ) -> None:
609*bfad0948SLuca Vizzarro        """Overrides :meth:`~.dpdk_shell.DPDKShell.__init__`. Changes app_params to kwargs."""
610*bfad0948SLuca Vizzarro        super().__init__(
611*bfad0948SLuca Vizzarro            node,
612*bfad0948SLuca Vizzarro            privileged,
613*bfad0948SLuca Vizzarro            timeout,
614*bfad0948SLuca Vizzarro            lcore_filter_specifier,
615*bfad0948SLuca Vizzarro            ascending_cores,
616*bfad0948SLuca Vizzarro            append_prefix_timestamp,
617*bfad0948SLuca Vizzarro            start_on_init,
618*bfad0948SLuca Vizzarro            TestPmdParams(**app_params),
619fd8cd8eeSLuca Vizzarro        )
620fd8cd8eeSLuca Vizzarro
621369d34b8SJeremy Spewock    def start(self, verify: bool = True) -> None:
622369d34b8SJeremy Spewock        """Start packet forwarding with the current configuration.
623369d34b8SJeremy Spewock
624369d34b8SJeremy Spewock        Args:
625369d34b8SJeremy Spewock            verify: If :data:`True` , a second start command will be sent in an attempt to verify
626369d34b8SJeremy Spewock                packet forwarding started as expected.
627369d34b8SJeremy Spewock
628369d34b8SJeremy Spewock        Raises:
629369d34b8SJeremy Spewock            InteractiveCommandExecutionError: If `verify` is :data:`True` and forwarding fails to
630369d34b8SJeremy Spewock                start or ports fail to come up.
631369d34b8SJeremy Spewock        """
632369d34b8SJeremy Spewock        self.send_command("start")
633369d34b8SJeremy Spewock        if verify:
634369d34b8SJeremy Spewock            # If forwarding was already started, sending "start" again should tell us
635369d34b8SJeremy Spewock            start_cmd_output = self.send_command("start")
636369d34b8SJeremy Spewock            if "Packet forwarding already started" not in start_cmd_output:
637369d34b8SJeremy Spewock                self._logger.debug(f"Failed to start packet forwarding: \n{start_cmd_output}")
638369d34b8SJeremy Spewock                raise InteractiveCommandExecutionError("Testpmd failed to start packet forwarding.")
639369d34b8SJeremy Spewock
640*bfad0948SLuca Vizzarro            number_of_ports = len(self._app_params.ports or [])
641*bfad0948SLuca Vizzarro            for port_id in range(number_of_ports):
642369d34b8SJeremy Spewock                if not self.wait_link_status_up(port_id):
643369d34b8SJeremy Spewock                    raise InteractiveCommandExecutionError(
644369d34b8SJeremy Spewock                        "Not all ports came up after starting packet forwarding in testpmd."
645369d34b8SJeremy Spewock                    )
646369d34b8SJeremy Spewock
647369d34b8SJeremy Spewock    def stop(self, verify: bool = True) -> None:
648369d34b8SJeremy Spewock        """Stop packet forwarding.
649369d34b8SJeremy Spewock
650369d34b8SJeremy Spewock        Args:
651369d34b8SJeremy Spewock            verify: If :data:`True` , the output of the stop command is scanned to verify that
652369d34b8SJeremy Spewock                forwarding was stopped successfully or not started. If neither is found, it is
653369d34b8SJeremy Spewock                considered an error.
654369d34b8SJeremy Spewock
655369d34b8SJeremy Spewock        Raises:
656369d34b8SJeremy Spewock            InteractiveCommandExecutionError: If `verify` is :data:`True` and the command to stop
657369d34b8SJeremy Spewock                forwarding results in an error.
658369d34b8SJeremy Spewock        """
659369d34b8SJeremy Spewock        stop_cmd_output = self.send_command("stop")
660369d34b8SJeremy Spewock        if verify:
661369d34b8SJeremy Spewock            if (
662369d34b8SJeremy Spewock                "Done." not in stop_cmd_output
663369d34b8SJeremy Spewock                and "Packet forwarding not started" not in stop_cmd_output
664369d34b8SJeremy Spewock            ):
665369d34b8SJeremy Spewock                self._logger.debug(f"Failed to stop packet forwarding: \n{stop_cmd_output}")
666369d34b8SJeremy Spewock                raise InteractiveCommandExecutionError("Testpmd failed to stop packet forwarding.")
667369d34b8SJeremy Spewock
668840b1e01SJuraj Linkeš    def get_devices(self) -> list[TestPmdDevice]:
6696ef07151SJuraj Linkeš        """Get a list of device names that are known to testpmd.
670840b1e01SJuraj Linkeš
6716ef07151SJuraj Linkeš        Uses the device info listed in testpmd and then parses the output.
672840b1e01SJuraj Linkeš
673840b1e01SJuraj Linkeš        Returns:
6746ef07151SJuraj Linkeš            A list of devices.
675840b1e01SJuraj Linkeš        """
676840b1e01SJuraj Linkeš        dev_info: str = self.send_command("show device info all")
677840b1e01SJuraj Linkeš        dev_list: list[TestPmdDevice] = []
678840b1e01SJuraj Linkeš        for line in dev_info.split("\n"):
679840b1e01SJuraj Linkeš            if "device name:" in line.lower():
680840b1e01SJuraj Linkeš                dev_list.append(TestPmdDevice(line))
681840b1e01SJuraj Linkeš        return dev_list
682369d34b8SJeremy Spewock
683369d34b8SJeremy Spewock    def wait_link_status_up(self, port_id: int, timeout=SETTINGS.timeout) -> bool:
684369d34b8SJeremy Spewock        """Wait until the link status on the given port is "up".
685369d34b8SJeremy Spewock
686369d34b8SJeremy Spewock        Arguments:
687369d34b8SJeremy Spewock            port_id: Port to check the link status on.
688369d34b8SJeremy Spewock            timeout: Time to wait for the link to come up. The default value for this
689369d34b8SJeremy Spewock                argument may be modified using the :option:`--timeout` command-line argument
690369d34b8SJeremy Spewock                or the :envvar:`DTS_TIMEOUT` environment variable.
691369d34b8SJeremy Spewock
692369d34b8SJeremy Spewock        Returns:
693369d34b8SJeremy Spewock            Whether the link came up in time or not.
694369d34b8SJeremy Spewock        """
695369d34b8SJeremy Spewock        time_to_stop = time.time() + timeout
696369d34b8SJeremy Spewock        port_info: str = ""
697369d34b8SJeremy Spewock        while time.time() < time_to_stop:
698369d34b8SJeremy Spewock            port_info = self.send_command(f"show port info {port_id}")
699369d34b8SJeremy Spewock            if "Link status: up" in port_info:
700369d34b8SJeremy Spewock                break
701369d34b8SJeremy Spewock            time.sleep(0.5)
702369d34b8SJeremy Spewock        else:
703369d34b8SJeremy Spewock            self._logger.error(f"The link for port {port_id} did not come up in the given timeout.")
704369d34b8SJeremy Spewock        return "Link status: up" in port_info
705369d34b8SJeremy Spewock
706fc0f7dc4SLuca Vizzarro    def set_forward_mode(self, mode: SimpleForwardingModes, verify: bool = True):
707369d34b8SJeremy Spewock        """Set packet forwarding mode.
708369d34b8SJeremy Spewock
709369d34b8SJeremy Spewock        Args:
710369d34b8SJeremy Spewock            mode: The forwarding mode to use.
711369d34b8SJeremy Spewock            verify: If :data:`True` the output of the command will be scanned in an attempt to
712369d34b8SJeremy Spewock                verify that the forwarding mode was set to `mode` properly.
713369d34b8SJeremy Spewock
714369d34b8SJeremy Spewock        Raises:
715369d34b8SJeremy Spewock            InteractiveCommandExecutionError: If `verify` is :data:`True` and the forwarding mode
716369d34b8SJeremy Spewock                fails to update.
717369d34b8SJeremy Spewock        """
718369d34b8SJeremy Spewock        set_fwd_output = self.send_command(f"set fwd {mode.value}")
719369d34b8SJeremy Spewock        if f"Set {mode.value} packet forwarding mode" not in set_fwd_output:
720369d34b8SJeremy Spewock            self._logger.debug(f"Failed to set fwd mode to {mode.value}:\n{set_fwd_output}")
721369d34b8SJeremy Spewock            raise InteractiveCommandExecutionError(
722369d34b8SJeremy Spewock                f"Test pmd failed to set fwd mode to {mode.value}"
723369d34b8SJeremy Spewock            )
724369d34b8SJeremy Spewock
72561d5bc9bSLuca Vizzarro    def show_port_info_all(self) -> list[TestPmdPort]:
72661d5bc9bSLuca Vizzarro        """Returns the information of all the ports.
72761d5bc9bSLuca Vizzarro
72861d5bc9bSLuca Vizzarro        Returns:
72961d5bc9bSLuca Vizzarro            list[TestPmdPort]: A list containing all the ports information as `TestPmdPort`.
73061d5bc9bSLuca Vizzarro        """
73161d5bc9bSLuca Vizzarro        output = self.send_command("show port info all")
73261d5bc9bSLuca Vizzarro
73361d5bc9bSLuca Vizzarro        # Sample output of the "all" command looks like:
73461d5bc9bSLuca Vizzarro        #
73561d5bc9bSLuca Vizzarro        # <start>
73661d5bc9bSLuca Vizzarro        #
73761d5bc9bSLuca Vizzarro        #   ********************* Infos for port 0 *********************
73861d5bc9bSLuca Vizzarro        #   Key: value
73961d5bc9bSLuca Vizzarro        #
74061d5bc9bSLuca Vizzarro        #   ********************* Infos for port 1 *********************
74161d5bc9bSLuca Vizzarro        #   Key: value
74261d5bc9bSLuca Vizzarro        # <end>
74361d5bc9bSLuca Vizzarro        #
74461d5bc9bSLuca Vizzarro        # Takes advantage of the double new line in between ports as end delimiter. But we need to
74561d5bc9bSLuca Vizzarro        # artificially add a new line at the end to pick up the last port. Because commands are
74661d5bc9bSLuca Vizzarro        # executed on a pseudo-terminal created by paramiko on the remote node, lines end with CRLF.
74761d5bc9bSLuca Vizzarro        # Therefore we also need to take the carriage return into account.
74861d5bc9bSLuca Vizzarro        iter = re.finditer(r"\*{21}.*?[\r\n]{4}", output + "\r\n", re.S)
74961d5bc9bSLuca Vizzarro        return [TestPmdPort.parse(block.group(0)) for block in iter]
75061d5bc9bSLuca Vizzarro
75161d5bc9bSLuca Vizzarro    def show_port_info(self, port_id: int) -> TestPmdPort:
75261d5bc9bSLuca Vizzarro        """Returns the given port information.
75361d5bc9bSLuca Vizzarro
75461d5bc9bSLuca Vizzarro        Args:
75561d5bc9bSLuca Vizzarro            port_id: The port ID to gather information for.
75661d5bc9bSLuca Vizzarro
75761d5bc9bSLuca Vizzarro        Raises:
75861d5bc9bSLuca Vizzarro            InteractiveCommandExecutionError: If `port_id` is invalid.
75961d5bc9bSLuca Vizzarro
76061d5bc9bSLuca Vizzarro        Returns:
76161d5bc9bSLuca Vizzarro            TestPmdPort: An instance of `TestPmdPort` containing the given port's information.
76261d5bc9bSLuca Vizzarro        """
76361d5bc9bSLuca Vizzarro        output = self.send_command(f"show port info {port_id}", skip_first_line=True)
76461d5bc9bSLuca Vizzarro        if output.startswith("Invalid port"):
76561d5bc9bSLuca Vizzarro            raise InteractiveCommandExecutionError("invalid port given")
76661d5bc9bSLuca Vizzarro
76761d5bc9bSLuca Vizzarro        return TestPmdPort.parse(output)
76861d5bc9bSLuca Vizzarro
76953eacf3dSLuca Vizzarro    def show_port_stats_all(self) -> list[TestPmdPortStats]:
77053eacf3dSLuca Vizzarro        """Returns the statistics of all the ports.
77153eacf3dSLuca Vizzarro
77253eacf3dSLuca Vizzarro        Returns:
77353eacf3dSLuca Vizzarro            list[TestPmdPortStats]: A list containing all the ports stats as `TestPmdPortStats`.
77453eacf3dSLuca Vizzarro        """
77553eacf3dSLuca Vizzarro        output = self.send_command("show port stats all")
77653eacf3dSLuca Vizzarro
77753eacf3dSLuca Vizzarro        # Sample output of the "all" command looks like:
77853eacf3dSLuca Vizzarro        #
77953eacf3dSLuca Vizzarro        #   ########### NIC statistics for port 0 ###########
78053eacf3dSLuca Vizzarro        #   values...
78153eacf3dSLuca Vizzarro        #   #################################################
78253eacf3dSLuca Vizzarro        #
78353eacf3dSLuca Vizzarro        #   ########### NIC statistics for port 1 ###########
78453eacf3dSLuca Vizzarro        #   values...
78553eacf3dSLuca Vizzarro        #   #################################################
78653eacf3dSLuca Vizzarro        #
78753eacf3dSLuca Vizzarro        iter = re.finditer(r"(^  #*.+#*$[^#]+)^  #*\r$", output, re.MULTILINE)
78853eacf3dSLuca Vizzarro        return [TestPmdPortStats.parse(block.group(1)) for block in iter]
78953eacf3dSLuca Vizzarro
79053eacf3dSLuca Vizzarro    def show_port_stats(self, port_id: int) -> TestPmdPortStats:
79153eacf3dSLuca Vizzarro        """Returns the given port statistics.
79253eacf3dSLuca Vizzarro
79353eacf3dSLuca Vizzarro        Args:
79453eacf3dSLuca Vizzarro            port_id: The port ID to gather information for.
79553eacf3dSLuca Vizzarro
79653eacf3dSLuca Vizzarro        Raises:
79753eacf3dSLuca Vizzarro            InteractiveCommandExecutionError: If `port_id` is invalid.
79853eacf3dSLuca Vizzarro
79953eacf3dSLuca Vizzarro        Returns:
80053eacf3dSLuca Vizzarro            TestPmdPortStats: An instance of `TestPmdPortStats` containing the given port's stats.
80153eacf3dSLuca Vizzarro        """
80253eacf3dSLuca Vizzarro        output = self.send_command(f"show port stats {port_id}", skip_first_line=True)
80353eacf3dSLuca Vizzarro        if output.startswith("Invalid port"):
80453eacf3dSLuca Vizzarro            raise InteractiveCommandExecutionError("invalid port given")
80553eacf3dSLuca Vizzarro
80653eacf3dSLuca Vizzarro        return TestPmdPortStats.parse(output)
80753eacf3dSLuca Vizzarro
808369d34b8SJeremy Spewock    def close(self) -> None:
809369d34b8SJeremy Spewock        """Overrides :meth:`~.interactive_shell.close`."""
810369d34b8SJeremy Spewock        self.send_command("quit", "")
811369d34b8SJeremy Spewock        return super().close()
812