xref: /dpdk/dts/framework/testbed_model/node.py (revision 0c81db58701c244638757ee342f38be002b53a83)
1# SPDX-License-Identifier: BSD-3-Clause
2# Copyright(c) 2010-2014 Intel Corporation
3# Copyright(c) 2022-2023 PANTHEON.tech s.r.o.
4# Copyright(c) 2022-2023 University of New Hampshire
5# Copyright(c) 2024 Arm Limited
6
7"""Common functionality for node management.
8
9A node is any host/server DTS connects to.
10
11The base class, :class:`Node`, provides features common to all nodes and is supposed
12to be extended by subclasses with features specific to each node type.
13The :func:`~Node.skip_setup` decorator can be used without subclassing.
14"""
15
16from abc import ABC
17
18from framework.config import (
19    OS,
20    DPDKBuildConfiguration,
21    NodeConfiguration,
22    TestRunConfiguration,
23)
24from framework.exception import ConfigurationError
25from framework.logger import DTSLogger, get_dts_logger
26
27from .cpu import (
28    LogicalCore,
29    LogicalCoreCount,
30    LogicalCoreList,
31    LogicalCoreListFilter,
32    lcore_filter,
33)
34from .linux_session import LinuxSession
35from .os_session import OSSession
36from .port import Port
37
38
39class Node(ABC):
40    """The base class for node management.
41
42    It shouldn't be instantiated, but rather subclassed.
43    It implements common methods to manage any node:
44
45        * Connection to the node,
46        * Hugepages setup.
47
48    Attributes:
49        main_session: The primary OS-aware remote session used to communicate with the node.
50        config: The node configuration.
51        name: The name of the node.
52        lcores: The list of logical cores that DTS can use on the node.
53            It's derived from logical cores present on the node and the test run configuration.
54        ports: The ports of this node specified in the test run configuration.
55    """
56
57    main_session: OSSession
58    config: NodeConfiguration
59    name: str
60    lcores: list[LogicalCore]
61    ports: list[Port]
62    _logger: DTSLogger
63    _other_sessions: list[OSSession]
64    _test_run_config: TestRunConfiguration
65
66    def __init__(self, node_config: NodeConfiguration):
67        """Connect to the node and gather info during initialization.
68
69        Extra gathered information:
70
71        * The list of available logical CPUs. This is then filtered by
72          the ``lcores`` configuration in the YAML test run configuration file,
73        * Information about ports from the YAML test run configuration file.
74
75        Args:
76            node_config: The node's test run configuration.
77        """
78        self.config = node_config
79        self.name = node_config.name
80        self._logger = get_dts_logger(self.name)
81        self.main_session = create_session(self.config, self.name, self._logger)
82
83        self._logger.info(f"Connected to node: {self.name}")
84
85        self._get_remote_cpus()
86        # filter the node lcores according to the test run configuration
87        self.lcores = LogicalCoreListFilter(
88            self.lcores, LogicalCoreList(self.config.lcores)
89        ).filter()
90
91        self._other_sessions = []
92        self._init_ports()
93
94    def _init_ports(self) -> None:
95        self.ports = [Port(self.name, port_config) for port_config in self.config.ports]
96        self.main_session.update_ports(self.ports)
97
98    def set_up_test_run(
99        self,
100        test_run_config: TestRunConfiguration,
101        dpdk_build_config: DPDKBuildConfiguration,
102    ) -> None:
103        """Test run setup steps.
104
105        Configure hugepages on all DTS node types. Additional steps can be added by
106        extending the method in subclasses with the use of super().
107
108        Args:
109            test_run_config: A test run configuration according to which
110                the setup steps will be taken.
111            dpdk_build_config: The build configuration of DPDK.
112        """
113        self._setup_hugepages()
114
115    def tear_down_test_run(self) -> None:
116        """Test run teardown steps.
117
118        There are currently no common execution teardown steps common to all DTS node types.
119        Additional steps can be added by extending the method in subclasses with the use of super().
120        """
121
122    def create_session(self, name: str) -> OSSession:
123        """Create and return a new OS-aware remote session.
124
125        The returned session won't be used by the node creating it. The session must be used by
126        the caller. The session will be maintained for the entire lifecycle of the node object,
127        at the end of which the session will be cleaned up automatically.
128
129        Note:
130            Any number of these supplementary sessions may be created.
131
132        Args:
133            name: The name of the session.
134
135        Returns:
136            A new OS-aware remote session.
137        """
138        session_name = f"{self.name} {name}"
139        connection = create_session(
140            self.config,
141            session_name,
142            get_dts_logger(session_name),
143        )
144        self._other_sessions.append(connection)
145        return connection
146
147    def filter_lcores(
148        self,
149        filter_specifier: LogicalCoreCount | LogicalCoreList,
150        ascending: bool = True,
151    ) -> list[LogicalCore]:
152        """Filter the node's logical cores that DTS can use.
153
154        Logical cores that DTS can use are the ones that are present on the node, but filtered
155        according to the test run configuration. The `filter_specifier` will filter cores from
156        those logical cores.
157
158        Args:
159            filter_specifier: Two different filters can be used, one that specifies the number
160                of logical cores per core, cores per socket and the number of sockets,
161                and another one that specifies a logical core list.
162            ascending: If :data:`True`, use cores with the lowest numerical id first and continue
163                in ascending order. If :data:`False`, start with the highest id and continue
164                in descending order. This ordering affects which sockets to consider first as well.
165
166        Returns:
167            The filtered logical cores.
168        """
169        self._logger.debug(f"Filtering {filter_specifier} from {self.lcores}.")
170        return lcore_filter(
171            self.lcores,
172            filter_specifier,
173            ascending,
174        ).filter()
175
176    def _get_remote_cpus(self) -> None:
177        """Scan CPUs in the remote OS and store a list of LogicalCores."""
178        self._logger.info("Getting CPU information.")
179        self.lcores = self.main_session.get_remote_cpus(self.config.use_first_core)
180
181    def _setup_hugepages(self) -> None:
182        """Setup hugepages on the node.
183
184        Configure the hugepages only if they're specified in the node's test run configuration.
185        """
186        if self.config.hugepages:
187            self.main_session.setup_hugepages(
188                self.config.hugepages.number_of,
189                self.main_session.hugepage_size,
190                self.config.hugepages.force_first_numa,
191            )
192
193    def close(self) -> None:
194        """Close all connections and free other resources."""
195        if self.main_session:
196            self.main_session.close()
197        for session in self._other_sessions:
198            session.close()
199
200
201def create_session(node_config: NodeConfiguration, name: str, logger: DTSLogger) -> OSSession:
202    """Factory for OS-aware sessions.
203
204    Args:
205        node_config: The test run configuration of the node to connect to.
206        name: The name of the session.
207        logger: The logger instance this session will use.
208    """
209    match node_config.os:
210        case OS.linux:
211            return LinuxSession(node_config, name, logger)
212        case _:
213            raise ConfigurationError(f"Unsupported OS {node_config.os}")
214