xref: /dpdk/dts/framework/testbed_model/topology.py (revision 949d1488e50f0516df065232640617b5a921ed95)
10b5ee16dSJuraj Linkeš# SPDX-License-Identifier: BSD-3-Clause
20b5ee16dSJuraj Linkeš# Copyright(c) 2024 PANTHEON.tech s.r.o.
30b5ee16dSJuraj Linkeš
40b5ee16dSJuraj Linkeš"""Testbed topology representation.
50b5ee16dSJuraj Linkeš
60b5ee16dSJuraj LinkešA topology of a testbed captures what links are available between the testbed's nodes.
70b5ee16dSJuraj LinkešThe link information then implies what type of topology is available.
80b5ee16dSJuraj Linkeš"""
90b5ee16dSJuraj Linkeš
100b5ee16dSJuraj Linkešfrom dataclasses import dataclass
11*949d1488SLuca Vizzarrofrom os import environ
12039256daSJuraj Linkešfrom typing import TYPE_CHECKING, Iterable
13039256daSJuraj Linkeš
14*949d1488SLuca Vizzarroif TYPE_CHECKING or environ.get("DTS_DOC_BUILD"):
15039256daSJuraj Linkeš    from enum import Enum as NoAliasEnum
16039256daSJuraj Linkešelse:
17039256daSJuraj Linkeš    from aenum import NoAliasEnum
180b5ee16dSJuraj Linkeš
190b5ee16dSJuraj Linkešfrom framework.config import PortConfig
20039256daSJuraj Linkešfrom framework.exception import ConfigurationError
210b5ee16dSJuraj Linkeš
220b5ee16dSJuraj Linkešfrom .port import Port
230b5ee16dSJuraj Linkeš
240b5ee16dSJuraj Linkeš
25039256daSJuraj Linkešclass TopologyType(int, NoAliasEnum):
260b5ee16dSJuraj Linkeš    """Supported topology types."""
270b5ee16dSJuraj Linkeš
280b5ee16dSJuraj Linkeš    #: A topology with no Traffic Generator.
290b5ee16dSJuraj Linkeš    no_link = 0
300b5ee16dSJuraj Linkeš    #: A topology with one physical link between the SUT node and the TG node.
310b5ee16dSJuraj Linkeš    one_link = 1
320b5ee16dSJuraj Linkeš    #: A topology with two physical links between the Sut node and the TG node.
330b5ee16dSJuraj Linkeš    two_links = 2
34039256daSJuraj Linkeš    #: The default topology required by test cases if not specified otherwise.
35039256daSJuraj Linkeš    default = 2
36039256daSJuraj Linkeš
37039256daSJuraj Linkeš    @classmethod
38039256daSJuraj Linkeš    def get_from_value(cls, value: int) -> "TopologyType":
39039256daSJuraj Linkeš        r"""Get the corresponding instance from value.
40039256daSJuraj Linkeš
41039256daSJuraj Linkeš        :class:`~enum.Enum`\s that don't allow aliases don't know which instance should be returned
42039256daSJuraj Linkeš        as there could be multiple valid instances. Except for the :attr:`default` value,
43039256daSJuraj Linkeš        :class:`TopologyType` is a regular :class:`~enum.Enum`.
44039256daSJuraj Linkeš        When getting an instance from value, we're not interested in the default,
45039256daSJuraj Linkeš        since we already know the value, allowing us to remove the ambiguity.
46039256daSJuraj Linkeš        """
47039256daSJuraj Linkeš        match value:
48039256daSJuraj Linkeš            case 0:
49039256daSJuraj Linkeš                return TopologyType.no_link
50039256daSJuraj Linkeš            case 1:
51039256daSJuraj Linkeš                return TopologyType.one_link
52039256daSJuraj Linkeš            case 2:
53039256daSJuraj Linkeš                return TopologyType.two_links
54039256daSJuraj Linkeš            case _:
55039256daSJuraj Linkeš                raise ConfigurationError("More than two links in a topology are not supported.")
560b5ee16dSJuraj Linkeš
570b5ee16dSJuraj Linkeš
580b5ee16dSJuraj Linkešclass Topology:
590b5ee16dSJuraj Linkeš    """Testbed topology.
600b5ee16dSJuraj Linkeš
610b5ee16dSJuraj Linkeš    The topology contains ports processed into ingress and egress ports.
620b5ee16dSJuraj Linkeš    If there are no ports on a node, dummy ports (ports with no actual values) are stored.
630b5ee16dSJuraj Linkeš    If there is only one link available, the ports of this link are stored
640b5ee16dSJuraj Linkeš    as both ingress and egress ports.
650b5ee16dSJuraj Linkeš
660b5ee16dSJuraj Linkeš    The dummy ports shouldn't be used. It's up to :class:`~framework.runner.DTSRunner`
670b5ee16dSJuraj Linkeš    to ensure no test case or suite requiring actual links is executed
680b5ee16dSJuraj Linkeš    when the topology prohibits it and up to the developers to make sure that test cases
690b5ee16dSJuraj Linkeš    not requiring any links don't use any ports. Otherwise, the underlying methods
700b5ee16dSJuraj Linkeš    using the ports will fail.
710b5ee16dSJuraj Linkeš
720b5ee16dSJuraj Linkeš    Attributes:
730b5ee16dSJuraj Linkeš        type: The type of the topology.
740b5ee16dSJuraj Linkeš        tg_port_egress: The egress port of the TG node.
750b5ee16dSJuraj Linkeš        sut_port_ingress: The ingress port of the SUT node.
760b5ee16dSJuraj Linkeš        sut_port_egress: The egress port of the SUT node.
770b5ee16dSJuraj Linkeš        tg_port_ingress: The ingress port of the TG node.
780b5ee16dSJuraj Linkeš    """
790b5ee16dSJuraj Linkeš
800b5ee16dSJuraj Linkeš    type: TopologyType
810b5ee16dSJuraj Linkeš    tg_port_egress: Port
820b5ee16dSJuraj Linkeš    sut_port_ingress: Port
830b5ee16dSJuraj Linkeš    sut_port_egress: Port
840b5ee16dSJuraj Linkeš    tg_port_ingress: Port
850b5ee16dSJuraj Linkeš
860b5ee16dSJuraj Linkeš    def __init__(self, sut_ports: Iterable[Port], tg_ports: Iterable[Port]):
870b5ee16dSJuraj Linkeš        """Create the topology from `sut_ports` and `tg_ports`.
880b5ee16dSJuraj Linkeš
890b5ee16dSJuraj Linkeš        Args:
900b5ee16dSJuraj Linkeš            sut_ports: The SUT node's ports.
910b5ee16dSJuraj Linkeš            tg_ports: The TG node's ports.
920b5ee16dSJuraj Linkeš        """
930b5ee16dSJuraj Linkeš        port_links = []
940b5ee16dSJuraj Linkeš        for sut_port in sut_ports:
950b5ee16dSJuraj Linkeš            for tg_port in tg_ports:
960b5ee16dSJuraj Linkeš                if (sut_port.identifier, sut_port.peer) == (
970b5ee16dSJuraj Linkeš                    tg_port.peer,
980b5ee16dSJuraj Linkeš                    tg_port.identifier,
990b5ee16dSJuraj Linkeš                ):
1000b5ee16dSJuraj Linkeš                    port_links.append(PortLink(sut_port=sut_port, tg_port=tg_port))
1010b5ee16dSJuraj Linkeš
102039256daSJuraj Linkeš        self.type = TopologyType.get_from_value(len(port_links))
103b935bdc3SLuca Vizzarro        dummy_port = Port(
104b935bdc3SLuca Vizzarro            "",
105b935bdc3SLuca Vizzarro            PortConfig(
106b935bdc3SLuca Vizzarro                pci="0000:00:00.0",
107b935bdc3SLuca Vizzarro                os_driver_for_dpdk="",
108b935bdc3SLuca Vizzarro                os_driver="",
109b935bdc3SLuca Vizzarro                peer_node="",
110b935bdc3SLuca Vizzarro                peer_pci="0000:00:00.0",
111b935bdc3SLuca Vizzarro            ),
112b935bdc3SLuca Vizzarro        )
1130b5ee16dSJuraj Linkeš        self.tg_port_egress = dummy_port
1140b5ee16dSJuraj Linkeš        self.sut_port_ingress = dummy_port
1150b5ee16dSJuraj Linkeš        self.sut_port_egress = dummy_port
1160b5ee16dSJuraj Linkeš        self.tg_port_ingress = dummy_port
1170b5ee16dSJuraj Linkeš        if self.type > TopologyType.no_link:
1180b5ee16dSJuraj Linkeš            self.tg_port_egress = port_links[0].tg_port
1190b5ee16dSJuraj Linkeš            self.sut_port_ingress = port_links[0].sut_port
1200b5ee16dSJuraj Linkeš            self.sut_port_egress = self.sut_port_ingress
1210b5ee16dSJuraj Linkeš            self.tg_port_ingress = self.tg_port_egress
1220b5ee16dSJuraj Linkeš        if self.type > TopologyType.one_link:
1230b5ee16dSJuraj Linkeš            self.sut_port_egress = port_links[1].sut_port
1240b5ee16dSJuraj Linkeš            self.tg_port_ingress = port_links[1].tg_port
1250b5ee16dSJuraj Linkeš
1260b5ee16dSJuraj Linkeš
1270b5ee16dSJuraj Linkeš@dataclass(slots=True, frozen=True)
1280b5ee16dSJuraj Linkešclass PortLink:
1290b5ee16dSJuraj Linkeš    """The physical, cabled connection between the ports.
1300b5ee16dSJuraj Linkeš
1310b5ee16dSJuraj Linkeš    Attributes:
1320b5ee16dSJuraj Linkeš        sut_port: The port on the SUT node connected to `tg_port`.
1330b5ee16dSJuraj Linkeš        tg_port: The port on the TG node connected to `sut_port`.
1340b5ee16dSJuraj Linkeš    """
1350b5ee16dSJuraj Linkeš
1360b5ee16dSJuraj Linkeš    sut_port: Port
1370b5ee16dSJuraj Linkeš    tg_port: Port
138