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