xref: /dpdk/dts/framework/testbed_model/node.py (revision b935bdc3da26ab86ec775dfad3aa63a1a61f5667)
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
18f9957667STomáš Ďurovecfrom typing import Union
19680d8a24SJuraj Linkeš
20*b935bdc3SLuca Vizzarrofrom framework.config import (
21*b935bdc3SLuca Vizzarro    OS,
22*b935bdc3SLuca Vizzarro    DPDKBuildConfiguration,
23*b935bdc3SLuca Vizzarro    NodeConfiguration,
24*b935bdc3SLuca Vizzarro    TestRunConfiguration,
25*b935bdc3SLuca Vizzarro)
26840b1e01SJuraj Linkešfrom framework.exception import ConfigurationError
2704f5a5a6SJuraj Linkešfrom framework.logger import DTSLogger, get_dts_logger
28c4ef44deSJuraj Linkeš
29840b1e01SJuraj Linkešfrom .cpu import (
30c020b7ceSJuraj Linkeš    LogicalCore,
31c020b7ceSJuraj Linkeš    LogicalCoreCount,
32c020b7ceSJuraj Linkeš    LogicalCoreList,
33c020b7ceSJuraj Linkeš    LogicalCoreListFilter,
34c020b7ceSJuraj Linkeš    lcore_filter,
35c020b7ceSJuraj Linkeš)
36840b1e01SJuraj Linkešfrom .linux_session import LinuxSession
37bfad0948SLuca Vizzarrofrom .os_session import OSSession
38840b1e01SJuraj Linkešfrom .port import Port
39c020b7ceSJuraj Linkeš
40c4ef44deSJuraj Linkeš
41cecfe0aaSJuraj Linkešclass Node(ABC):
426ef07151SJuraj Linkeš    """The base class for node management.
436ef07151SJuraj Linkeš
446ef07151SJuraj Linkeš    It shouldn't be instantiated, but rather subclassed.
456ef07151SJuraj Linkeš    It implements common methods to manage any node:
466ef07151SJuraj Linkeš
476ef07151SJuraj Linkeš        * Connection to the node,
486ef07151SJuraj Linkeš        * Hugepages setup.
496ef07151SJuraj Linkeš
506ef07151SJuraj Linkeš    Attributes:
516ef07151SJuraj Linkeš        main_session: The primary OS-aware remote session used to communicate with the node.
526ef07151SJuraj Linkeš        config: The node configuration.
536ef07151SJuraj Linkeš        name: The name of the node.
546ef07151SJuraj Linkeš        lcores: The list of logical cores that DTS can use on the node.
556ef07151SJuraj Linkeš            It's derived from logical cores present on the node and the test run configuration.
566ef07151SJuraj Linkeš        ports: The ports of this node specified in the test run configuration.
57c4ef44deSJuraj Linkeš    """
58c4ef44deSJuraj Linkeš
5978534506SJuraj Linkeš    main_session: OSSession
6078534506SJuraj Linkeš    config: NodeConfiguration
61c4ef44deSJuraj Linkeš    name: str
62c020b7ceSJuraj Linkeš    lcores: list[LogicalCore]
63cecfe0aaSJuraj Linkeš    ports: list[Port]
6404f5a5a6SJuraj Linkeš    _logger: DTSLogger
6578534506SJuraj Linkeš    _other_sessions: list[OSSession]
6685ceeeceSJuraj Linkeš    _test_run_config: TestRunConfiguration
67c4ef44deSJuraj Linkeš
68c4ef44deSJuraj Linkeš    def __init__(self, node_config: NodeConfiguration):
696ef07151SJuraj Linkeš        """Connect to the node and gather info during initialization.
706ef07151SJuraj Linkeš
716ef07151SJuraj Linkeš        Extra gathered information:
726ef07151SJuraj Linkeš
736ef07151SJuraj Linkeš        * The list of available logical CPUs. This is then filtered by
746ef07151SJuraj Linkeš          the ``lcores`` configuration in the YAML test run configuration file,
756ef07151SJuraj Linkeš        * Information about ports from the YAML test run configuration file.
766ef07151SJuraj Linkeš
776ef07151SJuraj Linkeš        Args:
786ef07151SJuraj Linkeš            node_config: The node's test run configuration.
796ef07151SJuraj Linkeš        """
8078534506SJuraj Linkeš        self.config = node_config
8178534506SJuraj Linkeš        self.name = node_config.name
8204f5a5a6SJuraj Linkeš        self._logger = get_dts_logger(self.name)
8378534506SJuraj Linkeš        self.main_session = create_session(self.config, self.name, self._logger)
8478534506SJuraj Linkeš
85cecfe0aaSJuraj Linkeš        self._logger.info(f"Connected to node: {self.name}")
86cecfe0aaSJuraj Linkeš
87c020b7ceSJuraj Linkeš        self._get_remote_cpus()
886ef07151SJuraj Linkeš        # filter the node lcores according to the test run configuration
89c020b7ceSJuraj Linkeš        self.lcores = LogicalCoreListFilter(
90c020b7ceSJuraj Linkeš            self.lcores, LogicalCoreList(self.config.lcores)
91c020b7ceSJuraj Linkeš        ).filter()
92c020b7ceSJuraj Linkeš
93c4ef44deSJuraj Linkeš        self._other_sessions = []
94cecfe0aaSJuraj Linkeš        self._init_ports()
95c4ef44deSJuraj Linkeš
96cecfe0aaSJuraj Linkeš    def _init_ports(self) -> None:
97*b935bdc3SLuca Vizzarro        self.ports = [Port(self.name, port_config) for port_config in self.config.ports]
98cecfe0aaSJuraj Linkeš        self.main_session.update_ports(self.ports)
99cecfe0aaSJuraj Linkeš        for port in self.ports:
100cecfe0aaSJuraj Linkeš            self.configure_port_state(port)
101c4ef44deSJuraj Linkeš
102f9957667STomáš Ďurovec    def set_up_test_run(
103*b935bdc3SLuca Vizzarro        self,
104*b935bdc3SLuca Vizzarro        test_run_config: TestRunConfiguration,
105*b935bdc3SLuca Vizzarro        dpdk_build_config: DPDKBuildConfiguration,
106f9957667STomáš Ďurovec    ) -> None:
10785ceeeceSJuraj Linkeš        """Test run setup steps.
1086ef07151SJuraj Linkeš
109a24f9604SJuraj Linkeš        Configure hugepages on all DTS node types. Additional steps can be added by
110a24f9604SJuraj Linkeš        extending the method in subclasses with the use of super().
1116ef07151SJuraj Linkeš
1126ef07151SJuraj Linkeš        Args:
11385ceeeceSJuraj Linkeš            test_run_config: A test run configuration according to which
1146ef07151SJuraj Linkeš                the setup steps will be taken.
115*b935bdc3SLuca Vizzarro            dpdk_build_config: The build configuration of DPDK.
11678534506SJuraj Linkeš        """
117b76d80a4SJuraj Linkeš        self._setup_hugepages()
118c4ef44deSJuraj Linkeš
11985ceeeceSJuraj Linkeš    def tear_down_test_run(self) -> None:
12085ceeeceSJuraj Linkeš        """Test run teardown steps.
1216ef07151SJuraj Linkeš
122a24f9604SJuraj Linkeš        There are currently no common execution teardown steps common to all DTS node types.
123a24f9604SJuraj Linkeš        Additional steps can be added by extending the method in subclasses with the use of super().
12478534506SJuraj Linkeš        """
12578534506SJuraj Linkeš
12678534506SJuraj Linkeš    def create_session(self, name: str) -> OSSession:
1276ef07151SJuraj Linkeš        """Create and return a new OS-aware remote session.
1286ef07151SJuraj Linkeš
1296ef07151SJuraj Linkeš        The returned session won't be used by the node creating it. The session must be used by
1306ef07151SJuraj Linkeš        the caller. The session will be maintained for the entire lifecycle of the node object,
1316ef07151SJuraj Linkeš        at the end of which the session will be cleaned up automatically.
1326ef07151SJuraj Linkeš
1336ef07151SJuraj Linkeš        Note:
1346ef07151SJuraj Linkeš            Any number of these supplementary sessions may be created.
1356ef07151SJuraj Linkeš
1366ef07151SJuraj Linkeš        Args:
1376ef07151SJuraj Linkeš            name: The name of the session.
1386ef07151SJuraj Linkeš
1396ef07151SJuraj Linkeš        Returns:
1406ef07151SJuraj Linkeš            A new OS-aware remote session.
14178534506SJuraj Linkeš        """
14278534506SJuraj Linkeš        session_name = f"{self.name} {name}"
14378534506SJuraj Linkeš        connection = create_session(
14478534506SJuraj Linkeš            self.config,
14578534506SJuraj Linkeš            session_name,
14604f5a5a6SJuraj Linkeš            get_dts_logger(session_name),
147c4ef44deSJuraj Linkeš        )
148c4ef44deSJuraj Linkeš        self._other_sessions.append(connection)
149c4ef44deSJuraj Linkeš        return connection
150c4ef44deSJuraj Linkeš
151c020b7ceSJuraj Linkeš    def filter_lcores(
152c020b7ceSJuraj Linkeš        self,
153c020b7ceSJuraj Linkeš        filter_specifier: LogicalCoreCount | LogicalCoreList,
154c020b7ceSJuraj Linkeš        ascending: bool = True,
155c020b7ceSJuraj Linkeš    ) -> list[LogicalCore]:
1566ef07151SJuraj Linkeš        """Filter the node's logical cores that DTS can use.
157c020b7ceSJuraj Linkeš
1586ef07151SJuraj Linkeš        Logical cores that DTS can use are the ones that are present on the node, but filtered
1596ef07151SJuraj Linkeš        according to the test run configuration. The `filter_specifier` will filter cores from
1606ef07151SJuraj Linkeš        those logical cores.
1616ef07151SJuraj Linkeš
1626ef07151SJuraj Linkeš        Args:
1636ef07151SJuraj Linkeš            filter_specifier: Two different filters can be used, one that specifies the number
1646ef07151SJuraj Linkeš                of logical cores per core, cores per socket and the number of sockets,
1656ef07151SJuraj Linkeš                and another one that specifies a logical core list.
1666ef07151SJuraj Linkeš            ascending: If :data:`True`, use cores with the lowest numerical id first and continue
1676ef07151SJuraj Linkeš                in ascending order. If :data:`False`, start with the highest id and continue
1686ef07151SJuraj Linkeš                in descending order. This ordering affects which sockets to consider first as well.
1696ef07151SJuraj Linkeš
1706ef07151SJuraj Linkeš        Returns:
1716ef07151SJuraj Linkeš            The filtered logical cores.
172c020b7ceSJuraj Linkeš        """
173c020b7ceSJuraj Linkeš        self._logger.debug(f"Filtering {filter_specifier} from {self.lcores}.")
174c020b7ceSJuraj Linkeš        return lcore_filter(
175c020b7ceSJuraj Linkeš            self.lcores,
176c020b7ceSJuraj Linkeš            filter_specifier,
177c020b7ceSJuraj Linkeš            ascending,
178c020b7ceSJuraj Linkeš        ).filter()
179c020b7ceSJuraj Linkeš
180c020b7ceSJuraj Linkeš    def _get_remote_cpus(self) -> None:
1816ef07151SJuraj Linkeš        """Scan CPUs in the remote OS and store a list of LogicalCores."""
182c020b7ceSJuraj Linkeš        self._logger.info("Getting CPU information.")
183c020b7ceSJuraj Linkeš        self.lcores = self.main_session.get_remote_cpus(self.config.use_first_core)
184c020b7ceSJuraj Linkeš
185840b1e01SJuraj Linkeš    def _setup_hugepages(self) -> None:
1866ef07151SJuraj Linkeš        """Setup hugepages on the node.
1876ef07151SJuraj Linkeš
1886ef07151SJuraj Linkeš        Configure the hugepages only if they're specified in the node's test run configuration.
189b76d80a4SJuraj Linkeš        """
190b76d80a4SJuraj Linkeš        if self.config.hugepages:
191b76d80a4SJuraj Linkeš            self.main_session.setup_hugepages(
192c0dd39deSNicholas Pratte                self.config.hugepages.number_of,
193e5307b25SNicholas Pratte                self.main_session.hugepage_size,
194e5307b25SNicholas Pratte                self.config.hugepages.force_first_numa,
195b76d80a4SJuraj Linkeš            )
196b76d80a4SJuraj Linkeš
197cecfe0aaSJuraj Linkeš    def configure_port_state(self, port: Port, enable: bool = True) -> None:
1986ef07151SJuraj Linkeš        """Enable/disable `port`.
1996ef07151SJuraj Linkeš
2006ef07151SJuraj Linkeš        Args:
2016ef07151SJuraj Linkeš            port: The port to enable/disable.
2026ef07151SJuraj Linkeš            enable: :data:`True` to enable, :data:`False` to disable.
203cecfe0aaSJuraj Linkeš        """
204cecfe0aaSJuraj Linkeš        self.main_session.configure_port_state(port, enable)
205cecfe0aaSJuraj Linkeš
206d99250aaSJuraj Linkeš    def configure_port_ip_address(
207d99250aaSJuraj Linkeš        self,
208d99250aaSJuraj Linkeš        address: Union[IPv4Interface, IPv6Interface],
209d99250aaSJuraj Linkeš        port: Port,
210d99250aaSJuraj Linkeš        delete: bool = False,
211d99250aaSJuraj Linkeš    ) -> None:
2126ef07151SJuraj Linkeš        """Add an IP address to `port` on this node.
2136ef07151SJuraj Linkeš
2146ef07151SJuraj Linkeš        Args:
2156ef07151SJuraj Linkeš            address: The IP address with mask in CIDR format. Can be either IPv4 or IPv6.
2166ef07151SJuraj Linkeš            port: The port to which to add the address.
2176ef07151SJuraj Linkeš            delete: If :data:`True`, will delete the address from the port instead of adding it.
218d99250aaSJuraj Linkeš        """
219d99250aaSJuraj Linkeš        self.main_session.configure_port_ip_address(address, port, delete)
220d99250aaSJuraj Linkeš
22178534506SJuraj Linkeš    def close(self) -> None:
2226ef07151SJuraj Linkeš        """Close all connections and free other resources."""
223c4ef44deSJuraj Linkeš        if self.main_session:
224c4ef44deSJuraj Linkeš            self.main_session.close()
225c4ef44deSJuraj Linkeš        for session in self._other_sessions:
226c4ef44deSJuraj Linkeš            session.close()
227680d8a24SJuraj Linkeš
228840b1e01SJuraj Linkeš
22904f5a5a6SJuraj Linkešdef create_session(node_config: NodeConfiguration, name: str, logger: DTSLogger) -> OSSession:
2306ef07151SJuraj Linkeš    """Factory for OS-aware sessions.
2316ef07151SJuraj Linkeš
2326ef07151SJuraj Linkeš    Args:
2336ef07151SJuraj Linkeš        node_config: The test run configuration of the node to connect to.
2346ef07151SJuraj Linkeš        name: The name of the session.
2356ef07151SJuraj Linkeš        logger: The logger instance this session will use.
2366ef07151SJuraj Linkeš    """
237840b1e01SJuraj Linkeš    match node_config.os:
238840b1e01SJuraj Linkeš        case OS.linux:
239840b1e01SJuraj Linkeš            return LinuxSession(node_config, name, logger)
240840b1e01SJuraj Linkeš        case _:
241840b1e01SJuraj Linkeš            raise ConfigurationError(f"Unsupported OS {node_config.os}")
242