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