xref: /dpdk/dts/framework/testbed_model/node.py (revision bfad0948df75e95e04cba1804a2749cfc91c56fb)
1c4ef44deSJuraj Linkeš# SPDX-License-Identifier: BSD-3-Clause
2c4ef44deSJuraj Linkeš# Copyright(c) 2010-2014 Intel Corporation
378534506SJuraj Linkeš# Copyright(c) 2022-2023 PANTHEON.tech s.r.o.
478534506SJuraj Linkeš# Copyright(c) 2022-2023 University of New Hampshire
5fd8cd8eeSLuca Vizzarro# Copyright(c) 2024 Arm Limited
6c4ef44deSJuraj Linkeš
76ef07151SJuraj Linkeš"""Common functionality for node management.
86ef07151SJuraj Linkeš
96ef07151SJuraj LinkešA node is any host/server DTS connects to.
106ef07151SJuraj Linkeš
116ef07151SJuraj LinkešThe base class, :class:`Node`, provides features common to all nodes and is supposed
126ef07151SJuraj Linkešto be extended by subclasses with features specific to each node type.
136ef07151SJuraj LinkešThe :func:`~Node.skip_setup` decorator can be used without subclassing.
14c4ef44deSJuraj Linkeš"""
15c4ef44deSJuraj Linkeš
16cecfe0aaSJuraj Linkešfrom abc import ABC
17d99250aaSJuraj Linkešfrom ipaddress import IPv4Interface, IPv6Interface
18*bfad0948SLuca Vizzarrofrom typing import Any, Callable, Union
19680d8a24SJuraj Linkeš
20a24f9604SJuraj Linkešfrom framework.config import OS, NodeConfiguration, TestRunConfiguration
21840b1e01SJuraj Linkešfrom framework.exception import ConfigurationError
2204f5a5a6SJuraj Linkešfrom framework.logger import DTSLogger, get_dts_logger
23680d8a24SJuraj Linkešfrom framework.settings import SETTINGS
24c4ef44deSJuraj Linkeš
25840b1e01SJuraj Linkešfrom .cpu import (
26c020b7ceSJuraj Linkeš    LogicalCore,
27c020b7ceSJuraj Linkeš    LogicalCoreCount,
28c020b7ceSJuraj Linkeš    LogicalCoreList,
29c020b7ceSJuraj Linkeš    LogicalCoreListFilter,
30c020b7ceSJuraj Linkeš    lcore_filter,
31c020b7ceSJuraj Linkeš)
32840b1e01SJuraj Linkešfrom .linux_session import LinuxSession
33*bfad0948SLuca Vizzarrofrom .os_session import OSSession
34840b1e01SJuraj Linkešfrom .port import Port
35c020b7ceSJuraj Linkeš
36c4ef44deSJuraj Linkeš
37cecfe0aaSJuraj Linkešclass Node(ABC):
386ef07151SJuraj Linkeš    """The base class for node management.
396ef07151SJuraj Linkeš
406ef07151SJuraj Linkeš    It shouldn't be instantiated, but rather subclassed.
416ef07151SJuraj Linkeš    It implements common methods to manage any node:
426ef07151SJuraj Linkeš
436ef07151SJuraj Linkeš        * Connection to the node,
446ef07151SJuraj Linkeš        * Hugepages setup.
456ef07151SJuraj Linkeš
466ef07151SJuraj Linkeš    Attributes:
476ef07151SJuraj Linkeš        main_session: The primary OS-aware remote session used to communicate with the node.
486ef07151SJuraj Linkeš        config: The node configuration.
496ef07151SJuraj Linkeš        name: The name of the node.
506ef07151SJuraj Linkeš        lcores: The list of logical cores that DTS can use on the node.
516ef07151SJuraj Linkeš            It's derived from logical cores present on the node and the test run configuration.
526ef07151SJuraj Linkeš        ports: The ports of this node specified in the test run configuration.
53c4ef44deSJuraj Linkeš    """
54c4ef44deSJuraj Linkeš
5578534506SJuraj Linkeš    main_session: OSSession
5678534506SJuraj Linkeš    config: NodeConfiguration
57c4ef44deSJuraj Linkeš    name: str
58c020b7ceSJuraj Linkeš    lcores: list[LogicalCore]
59cecfe0aaSJuraj Linkeš    ports: list[Port]
6004f5a5a6SJuraj Linkeš    _logger: DTSLogger
6178534506SJuraj Linkeš    _other_sessions: list[OSSession]
6285ceeeceSJuraj Linkeš    _test_run_config: TestRunConfiguration
63c4ef44deSJuraj Linkeš
64c4ef44deSJuraj Linkeš    def __init__(self, node_config: NodeConfiguration):
656ef07151SJuraj Linkeš        """Connect to the node and gather info during initialization.
666ef07151SJuraj Linkeš
676ef07151SJuraj Linkeš        Extra gathered information:
686ef07151SJuraj Linkeš
696ef07151SJuraj Linkeš        * The list of available logical CPUs. This is then filtered by
706ef07151SJuraj Linkeš          the ``lcores`` configuration in the YAML test run configuration file,
716ef07151SJuraj Linkeš        * Information about ports from the YAML test run configuration file.
726ef07151SJuraj Linkeš
736ef07151SJuraj Linkeš        Args:
746ef07151SJuraj Linkeš            node_config: The node's test run configuration.
756ef07151SJuraj Linkeš        """
7678534506SJuraj Linkeš        self.config = node_config
7778534506SJuraj Linkeš        self.name = node_config.name
7804f5a5a6SJuraj Linkeš        self._logger = get_dts_logger(self.name)
7978534506SJuraj Linkeš        self.main_session = create_session(self.config, self.name, self._logger)
8078534506SJuraj Linkeš
81cecfe0aaSJuraj Linkeš        self._logger.info(f"Connected to node: {self.name}")
82cecfe0aaSJuraj Linkeš
83c020b7ceSJuraj Linkeš        self._get_remote_cpus()
846ef07151SJuraj Linkeš        # filter the node lcores according to the test run configuration
85c020b7ceSJuraj Linkeš        self.lcores = LogicalCoreListFilter(
86c020b7ceSJuraj Linkeš            self.lcores, LogicalCoreList(self.config.lcores)
87c020b7ceSJuraj Linkeš        ).filter()
88c020b7ceSJuraj Linkeš
89c4ef44deSJuraj Linkeš        self._other_sessions = []
90cecfe0aaSJuraj Linkeš        self._init_ports()
91c4ef44deSJuraj Linkeš
92cecfe0aaSJuraj Linkeš    def _init_ports(self) -> None:
93cecfe0aaSJuraj Linkeš        self.ports = [Port(self.name, port_config) for port_config in self.config.ports]
94cecfe0aaSJuraj Linkeš        self.main_session.update_ports(self.ports)
95cecfe0aaSJuraj Linkeš        for port in self.ports:
96cecfe0aaSJuraj Linkeš            self.configure_port_state(port)
97c4ef44deSJuraj Linkeš
9885ceeeceSJuraj Linkeš    def set_up_test_run(self, test_run_config: TestRunConfiguration) -> None:
9985ceeeceSJuraj Linkeš        """Test run setup steps.
1006ef07151SJuraj Linkeš
101a24f9604SJuraj Linkeš        Configure hugepages on all DTS node types. Additional steps can be added by
102a24f9604SJuraj Linkeš        extending the method in subclasses with the use of super().
1036ef07151SJuraj Linkeš
1046ef07151SJuraj Linkeš        Args:
10585ceeeceSJuraj Linkeš            test_run_config: A test run configuration according to which
1066ef07151SJuraj Linkeš                the setup steps will be taken.
10778534506SJuraj Linkeš        """
108b76d80a4SJuraj Linkeš        self._setup_hugepages()
109c4ef44deSJuraj Linkeš
11085ceeeceSJuraj Linkeš    def tear_down_test_run(self) -> None:
11185ceeeceSJuraj Linkeš        """Test run teardown steps.
1126ef07151SJuraj Linkeš
113a24f9604SJuraj Linkeš        There are currently no common execution teardown steps common to all DTS node types.
114a24f9604SJuraj Linkeš        Additional steps can be added by extending the method in subclasses with the use of super().
11578534506SJuraj Linkeš        """
11678534506SJuraj Linkeš
11778534506SJuraj Linkeš    def create_session(self, name: str) -> OSSession:
1186ef07151SJuraj Linkeš        """Create and return a new OS-aware remote session.
1196ef07151SJuraj Linkeš
1206ef07151SJuraj Linkeš        The returned session won't be used by the node creating it. The session must be used by
1216ef07151SJuraj Linkeš        the caller. The session will be maintained for the entire lifecycle of the node object,
1226ef07151SJuraj Linkeš        at the end of which the session will be cleaned up automatically.
1236ef07151SJuraj Linkeš
1246ef07151SJuraj Linkeš        Note:
1256ef07151SJuraj Linkeš            Any number of these supplementary sessions may be created.
1266ef07151SJuraj Linkeš
1276ef07151SJuraj Linkeš        Args:
1286ef07151SJuraj Linkeš            name: The name of the session.
1296ef07151SJuraj Linkeš
1306ef07151SJuraj Linkeš        Returns:
1316ef07151SJuraj Linkeš            A new OS-aware remote session.
13278534506SJuraj Linkeš        """
13378534506SJuraj Linkeš        session_name = f"{self.name} {name}"
13478534506SJuraj Linkeš        connection = create_session(
13578534506SJuraj Linkeš            self.config,
13678534506SJuraj Linkeš            session_name,
13704f5a5a6SJuraj Linkeš            get_dts_logger(session_name),
138c4ef44deSJuraj Linkeš        )
139c4ef44deSJuraj Linkeš        self._other_sessions.append(connection)
140c4ef44deSJuraj Linkeš        return connection
141c4ef44deSJuraj Linkeš
142c020b7ceSJuraj Linkeš    def filter_lcores(
143c020b7ceSJuraj Linkeš        self,
144c020b7ceSJuraj Linkeš        filter_specifier: LogicalCoreCount | LogicalCoreList,
145c020b7ceSJuraj Linkeš        ascending: bool = True,
146c020b7ceSJuraj Linkeš    ) -> list[LogicalCore]:
1476ef07151SJuraj Linkeš        """Filter the node's logical cores that DTS can use.
148c020b7ceSJuraj Linkeš
1496ef07151SJuraj Linkeš        Logical cores that DTS can use are the ones that are present on the node, but filtered
1506ef07151SJuraj Linkeš        according to the test run configuration. The `filter_specifier` will filter cores from
1516ef07151SJuraj Linkeš        those logical cores.
1526ef07151SJuraj Linkeš
1536ef07151SJuraj Linkeš        Args:
1546ef07151SJuraj Linkeš            filter_specifier: Two different filters can be used, one that specifies the number
1556ef07151SJuraj Linkeš                of logical cores per core, cores per socket and the number of sockets,
1566ef07151SJuraj Linkeš                and another one that specifies a logical core list.
1576ef07151SJuraj Linkeš            ascending: If :data:`True`, use cores with the lowest numerical id first and continue
1586ef07151SJuraj Linkeš                in ascending order. If :data:`False`, start with the highest id and continue
1596ef07151SJuraj Linkeš                in descending order. This ordering affects which sockets to consider first as well.
1606ef07151SJuraj Linkeš
1616ef07151SJuraj Linkeš        Returns:
1626ef07151SJuraj Linkeš            The filtered logical cores.
163c020b7ceSJuraj Linkeš        """
164c020b7ceSJuraj Linkeš        self._logger.debug(f"Filtering {filter_specifier} from {self.lcores}.")
165c020b7ceSJuraj Linkeš        return lcore_filter(
166c020b7ceSJuraj Linkeš            self.lcores,
167c020b7ceSJuraj Linkeš            filter_specifier,
168c020b7ceSJuraj Linkeš            ascending,
169c020b7ceSJuraj Linkeš        ).filter()
170c020b7ceSJuraj Linkeš
171c020b7ceSJuraj Linkeš    def _get_remote_cpus(self) -> None:
1726ef07151SJuraj Linkeš        """Scan CPUs in the remote OS and store a list of LogicalCores."""
173c020b7ceSJuraj Linkeš        self._logger.info("Getting CPU information.")
174c020b7ceSJuraj Linkeš        self.lcores = self.main_session.get_remote_cpus(self.config.use_first_core)
175c020b7ceSJuraj Linkeš
176840b1e01SJuraj Linkeš    def _setup_hugepages(self) -> None:
1776ef07151SJuraj Linkeš        """Setup hugepages on the node.
1786ef07151SJuraj Linkeš
1796ef07151SJuraj Linkeš        Configure the hugepages only if they're specified in the node's test run configuration.
180b76d80a4SJuraj Linkeš        """
181b76d80a4SJuraj Linkeš        if self.config.hugepages:
182b76d80a4SJuraj Linkeš            self.main_session.setup_hugepages(
183c0dd39deSNicholas Pratte                self.config.hugepages.number_of,
184e5307b25SNicholas Pratte                self.main_session.hugepage_size,
185e5307b25SNicholas Pratte                self.config.hugepages.force_first_numa,
186b76d80a4SJuraj Linkeš            )
187b76d80a4SJuraj Linkeš
188cecfe0aaSJuraj Linkeš    def configure_port_state(self, port: Port, enable: bool = True) -> None:
1896ef07151SJuraj Linkeš        """Enable/disable `port`.
1906ef07151SJuraj Linkeš
1916ef07151SJuraj Linkeš        Args:
1926ef07151SJuraj Linkeš            port: The port to enable/disable.
1936ef07151SJuraj Linkeš            enable: :data:`True` to enable, :data:`False` to disable.
194cecfe0aaSJuraj Linkeš        """
195cecfe0aaSJuraj Linkeš        self.main_session.configure_port_state(port, enable)
196cecfe0aaSJuraj Linkeš
197d99250aaSJuraj Linkeš    def configure_port_ip_address(
198d99250aaSJuraj Linkeš        self,
199d99250aaSJuraj Linkeš        address: Union[IPv4Interface, IPv6Interface],
200d99250aaSJuraj Linkeš        port: Port,
201d99250aaSJuraj Linkeš        delete: bool = False,
202d99250aaSJuraj Linkeš    ) -> None:
2036ef07151SJuraj Linkeš        """Add an IP address to `port` on this node.
2046ef07151SJuraj Linkeš
2056ef07151SJuraj Linkeš        Args:
2066ef07151SJuraj Linkeš            address: The IP address with mask in CIDR format. Can be either IPv4 or IPv6.
2076ef07151SJuraj Linkeš            port: The port to which to add the address.
2086ef07151SJuraj Linkeš            delete: If :data:`True`, will delete the address from the port instead of adding it.
209d99250aaSJuraj Linkeš        """
210d99250aaSJuraj Linkeš        self.main_session.configure_port_ip_address(address, port, delete)
211d99250aaSJuraj Linkeš
21278534506SJuraj Linkeš    def close(self) -> None:
2136ef07151SJuraj Linkeš        """Close all connections and free other resources."""
214c4ef44deSJuraj Linkeš        if self.main_session:
215c4ef44deSJuraj Linkeš            self.main_session.close()
216c4ef44deSJuraj Linkeš        for session in self._other_sessions:
217c4ef44deSJuraj Linkeš            session.close()
218680d8a24SJuraj Linkeš
219680d8a24SJuraj Linkeš    @staticmethod
220680d8a24SJuraj Linkeš    def skip_setup(func: Callable[..., Any]) -> Callable[..., Any]:
2216ef07151SJuraj Linkeš        """Skip the decorated function.
2226ef07151SJuraj Linkeš
2236ef07151SJuraj Linkeš        The :option:`--skip-setup` command line argument and the :envvar:`DTS_SKIP_SETUP`
2246ef07151SJuraj Linkeš        environment variable enable the decorator.
2256ef07151SJuraj Linkeš        """
226680d8a24SJuraj Linkeš        if SETTINGS.skip_setup:
227680d8a24SJuraj Linkeš            return lambda *args: None
228680d8a24SJuraj Linkeš        else:
229680d8a24SJuraj Linkeš            return func
230840b1e01SJuraj Linkeš
231840b1e01SJuraj Linkeš
23204f5a5a6SJuraj Linkešdef create_session(node_config: NodeConfiguration, name: str, logger: DTSLogger) -> OSSession:
2336ef07151SJuraj Linkeš    """Factory for OS-aware sessions.
2346ef07151SJuraj Linkeš
2356ef07151SJuraj Linkeš    Args:
2366ef07151SJuraj Linkeš        node_config: The test run configuration of the node to connect to.
2376ef07151SJuraj Linkeš        name: The name of the session.
2386ef07151SJuraj Linkeš        logger: The logger instance this session will use.
2396ef07151SJuraj Linkeš    """
240840b1e01SJuraj Linkeš    match node_config.os:
241840b1e01SJuraj Linkeš        case OS.linux:
242840b1e01SJuraj Linkeš            return LinuxSession(node_config, name, logger)
243840b1e01SJuraj Linkeš        case _:
244840b1e01SJuraj Linkeš            raise ConfigurationError(f"Unsupported OS {node_config.os}")
245