xref: /dpdk/dts/framework/testbed_model/topology.py (revision 21a66096bb44a4468353782c36fc85913520dc6c)
1# SPDX-License-Identifier: BSD-3-Clause
2# Copyright(c) 2024 PANTHEON.tech s.r.o.
3
4"""Testbed topology representation.
5
6A topology of a testbed captures what links are available between the testbed's nodes.
7The link information then implies what type of topology is available.
8"""
9
10from dataclasses import dataclass
11from typing import TYPE_CHECKING, Iterable
12
13if TYPE_CHECKING:
14    from enum import Enum as NoAliasEnum
15else:
16    from aenum import NoAliasEnum
17
18from framework.config import PortConfig
19from framework.exception import ConfigurationError
20
21from .port import Port
22
23
24class TopologyType(int, NoAliasEnum):
25    """Supported topology types."""
26
27    #: A topology with no Traffic Generator.
28    no_link = 0
29    #: A topology with one physical link between the SUT node and the TG node.
30    one_link = 1
31    #: A topology with two physical links between the Sut node and the TG node.
32    two_links = 2
33    #: The default topology required by test cases if not specified otherwise.
34    default = 2
35
36    @classmethod
37    def get_from_value(cls, value: int) -> "TopologyType":
38        r"""Get the corresponding instance from value.
39
40        :class:`~enum.Enum`\s that don't allow aliases don't know which instance should be returned
41        as there could be multiple valid instances. Except for the :attr:`default` value,
42        :class:`TopologyType` is a regular :class:`~enum.Enum`.
43        When getting an instance from value, we're not interested in the default,
44        since we already know the value, allowing us to remove the ambiguity.
45        """
46        match value:
47            case 0:
48                return TopologyType.no_link
49            case 1:
50                return TopologyType.one_link
51            case 2:
52                return TopologyType.two_links
53            case _:
54                raise ConfigurationError("More than two links in a topology are not supported.")
55
56
57class Topology:
58    """Testbed topology.
59
60    The topology contains ports processed into ingress and egress ports.
61    If there are no ports on a node, dummy ports (ports with no actual values) are stored.
62    If there is only one link available, the ports of this link are stored
63    as both ingress and egress ports.
64
65    The dummy ports shouldn't be used. It's up to :class:`~framework.runner.DTSRunner`
66    to ensure no test case or suite requiring actual links is executed
67    when the topology prohibits it and up to the developers to make sure that test cases
68    not requiring any links don't use any ports. Otherwise, the underlying methods
69    using the ports will fail.
70
71    Attributes:
72        type: The type of the topology.
73        tg_port_egress: The egress port of the TG node.
74        sut_port_ingress: The ingress port of the SUT node.
75        sut_port_egress: The egress port of the SUT node.
76        tg_port_ingress: The ingress port of the TG node.
77    """
78
79    type: TopologyType
80    tg_port_egress: Port
81    sut_port_ingress: Port
82    sut_port_egress: Port
83    tg_port_ingress: Port
84
85    def __init__(self, sut_ports: Iterable[Port], tg_ports: Iterable[Port]):
86        """Create the topology from `sut_ports` and `tg_ports`.
87
88        Args:
89            sut_ports: The SUT node's ports.
90            tg_ports: The TG node's ports.
91        """
92        port_links = []
93        for sut_port in sut_ports:
94            for tg_port in tg_ports:
95                if (sut_port.identifier, sut_port.peer) == (
96                    tg_port.peer,
97                    tg_port.identifier,
98                ):
99                    port_links.append(PortLink(sut_port=sut_port, tg_port=tg_port))
100
101        self.type = TopologyType.get_from_value(len(port_links))
102        dummy_port = Port(PortConfig("", "", "", "", "", ""))
103        self.tg_port_egress = dummy_port
104        self.sut_port_ingress = dummy_port
105        self.sut_port_egress = dummy_port
106        self.tg_port_ingress = dummy_port
107        if self.type > TopologyType.no_link:
108            self.tg_port_egress = port_links[0].tg_port
109            self.sut_port_ingress = port_links[0].sut_port
110            self.sut_port_egress = self.sut_port_ingress
111            self.tg_port_ingress = self.tg_port_egress
112        if self.type > TopologyType.one_link:
113            self.sut_port_egress = port_links[1].sut_port
114            self.tg_port_ingress = port_links[1].tg_port
115
116
117@dataclass(slots=True, frozen=True)
118class PortLink:
119    """The physical, cabled connection between the ports.
120
121    Attributes:
122        sut_port: The port on the SUT node connected to `tg_port`.
123        tg_port: The port on the TG node connected to `sut_port`.
124    """
125
126    sut_port: Port
127    tg_port: Port
128