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 17680d8a24SJuraj Linkeš 18*b935bdc3SLuca Vizzarrofrom framework.config import ( 19*b935bdc3SLuca Vizzarro OS, 20*b935bdc3SLuca Vizzarro DPDKBuildConfiguration, 21*b935bdc3SLuca Vizzarro NodeConfiguration, 22*b935bdc3SLuca Vizzarro TestRunConfiguration, 23*b935bdc3SLuca Vizzarro) 24840b1e01SJuraj Linkešfrom framework.exception import ConfigurationError 2504f5a5a6SJuraj Linkešfrom framework.logger import DTSLogger, get_dts_logger 26c4ef44deSJuraj Linkeš 27840b1e01SJuraj Linkešfrom .cpu import ( 28c020b7ceSJuraj Linkeš LogicalCore, 29c020b7ceSJuraj Linkeš LogicalCoreCount, 30c020b7ceSJuraj Linkeš LogicalCoreList, 31c020b7ceSJuraj Linkeš LogicalCoreListFilter, 32c020b7ceSJuraj Linkeš lcore_filter, 33c020b7ceSJuraj Linkeš) 34840b1e01SJuraj Linkešfrom .linux_session import LinuxSession 35bfad0948SLuca Vizzarrofrom .os_session import OSSession 36840b1e01SJuraj Linkešfrom .port import Port 37c020b7ceSJuraj Linkeš 38c4ef44deSJuraj Linkeš 39cecfe0aaSJuraj Linkešclass Node(ABC): 406ef07151SJuraj Linkeš """The base class for node management. 416ef07151SJuraj Linkeš 426ef07151SJuraj Linkeš It shouldn't be instantiated, but rather subclassed. 436ef07151SJuraj Linkeš It implements common methods to manage any node: 446ef07151SJuraj Linkeš 456ef07151SJuraj Linkeš * Connection to the node, 466ef07151SJuraj Linkeš * Hugepages setup. 476ef07151SJuraj Linkeš 486ef07151SJuraj Linkeš Attributes: 496ef07151SJuraj Linkeš main_session: The primary OS-aware remote session used to communicate with the node. 506ef07151SJuraj Linkeš config: The node configuration. 516ef07151SJuraj Linkeš name: The name of the node. 526ef07151SJuraj Linkeš lcores: The list of logical cores that DTS can use on the node. 536ef07151SJuraj Linkeš It's derived from logical cores present on the node and the test run configuration. 546ef07151SJuraj Linkeš ports: The ports of this node specified in the test run configuration. 55c4ef44deSJuraj Linkeš """ 56c4ef44deSJuraj Linkeš 5778534506SJuraj Linkeš main_session: OSSession 5878534506SJuraj Linkeš config: NodeConfiguration 59c4ef44deSJuraj Linkeš name: str 60c020b7ceSJuraj Linkeš lcores: list[LogicalCore] 61cecfe0aaSJuraj Linkeš ports: list[Port] 6204f5a5a6SJuraj Linkeš _logger: DTSLogger 6378534506SJuraj Linkeš _other_sessions: list[OSSession] 6485ceeeceSJuraj Linkeš _test_run_config: TestRunConfiguration 65c4ef44deSJuraj Linkeš 66c4ef44deSJuraj Linkeš def __init__(self, node_config: NodeConfiguration): 676ef07151SJuraj Linkeš """Connect to the node and gather info during initialization. 686ef07151SJuraj Linkeš 696ef07151SJuraj Linkeš Extra gathered information: 706ef07151SJuraj Linkeš 716ef07151SJuraj Linkeš * The list of available logical CPUs. This is then filtered by 726ef07151SJuraj Linkeš the ``lcores`` configuration in the YAML test run configuration file, 736ef07151SJuraj Linkeš * Information about ports from the YAML test run configuration file. 746ef07151SJuraj Linkeš 756ef07151SJuraj Linkeš Args: 766ef07151SJuraj Linkeš node_config: The node's test run configuration. 776ef07151SJuraj Linkeš """ 7878534506SJuraj Linkeš self.config = node_config 7978534506SJuraj Linkeš self.name = node_config.name 8004f5a5a6SJuraj Linkeš self._logger = get_dts_logger(self.name) 8178534506SJuraj Linkeš self.main_session = create_session(self.config, self.name, self._logger) 8278534506SJuraj Linkeš 83cecfe0aaSJuraj Linkeš self._logger.info(f"Connected to node: {self.name}") 84cecfe0aaSJuraj Linkeš 85c020b7ceSJuraj Linkeš self._get_remote_cpus() 866ef07151SJuraj Linkeš # filter the node lcores according to the test run configuration 87c020b7ceSJuraj Linkeš self.lcores = LogicalCoreListFilter( 88c020b7ceSJuraj Linkeš self.lcores, LogicalCoreList(self.config.lcores) 89c020b7ceSJuraj Linkeš ).filter() 90c020b7ceSJuraj Linkeš 91c4ef44deSJuraj Linkeš self._other_sessions = [] 92cecfe0aaSJuraj Linkeš self._init_ports() 93c4ef44deSJuraj Linkeš 94cecfe0aaSJuraj Linkeš def _init_ports(self) -> None: 95*b935bdc3SLuca Vizzarro self.ports = [Port(self.name, port_config) for port_config in self.config.ports] 96cecfe0aaSJuraj Linkeš self.main_session.update_ports(self.ports) 97c4ef44deSJuraj Linkeš 98f9957667STomáš Ďurovec def set_up_test_run( 99*b935bdc3SLuca Vizzarro self, 100*b935bdc3SLuca Vizzarro test_run_config: TestRunConfiguration, 101*b935bdc3SLuca Vizzarro dpdk_build_config: DPDKBuildConfiguration, 102f9957667STomáš Ďurovec ) -> None: 10385ceeeceSJuraj Linkeš """Test run setup steps. 1046ef07151SJuraj Linkeš 105a24f9604SJuraj Linkeš Configure hugepages on all DTS node types. Additional steps can be added by 106a24f9604SJuraj Linkeš extending the method in subclasses with the use of super(). 1076ef07151SJuraj Linkeš 1086ef07151SJuraj Linkeš Args: 10985ceeeceSJuraj Linkeš test_run_config: A test run configuration according to which 1106ef07151SJuraj Linkeš the setup steps will be taken. 111*b935bdc3SLuca Vizzarro dpdk_build_config: The build configuration of DPDK. 11278534506SJuraj Linkeš """ 113b76d80a4SJuraj Linkeš self._setup_hugepages() 114c4ef44deSJuraj Linkeš 11585ceeeceSJuraj Linkeš def tear_down_test_run(self) -> None: 11685ceeeceSJuraj Linkeš """Test run teardown steps. 1176ef07151SJuraj Linkeš 118a24f9604SJuraj Linkeš There are currently no common execution teardown steps common to all DTS node types. 119a24f9604SJuraj Linkeš Additional steps can be added by extending the method in subclasses with the use of super(). 12078534506SJuraj Linkeš """ 12178534506SJuraj Linkeš 12278534506SJuraj Linkeš def create_session(self, name: str) -> OSSession: 1236ef07151SJuraj Linkeš """Create and return a new OS-aware remote session. 1246ef07151SJuraj Linkeš 1256ef07151SJuraj Linkeš The returned session won't be used by the node creating it. The session must be used by 1266ef07151SJuraj Linkeš the caller. The session will be maintained for the entire lifecycle of the node object, 1276ef07151SJuraj Linkeš at the end of which the session will be cleaned up automatically. 1286ef07151SJuraj Linkeš 1296ef07151SJuraj Linkeš Note: 1306ef07151SJuraj Linkeš Any number of these supplementary sessions may be created. 1316ef07151SJuraj Linkeš 1326ef07151SJuraj Linkeš Args: 1336ef07151SJuraj Linkeš name: The name of the session. 1346ef07151SJuraj Linkeš 1356ef07151SJuraj Linkeš Returns: 1366ef07151SJuraj Linkeš A new OS-aware remote session. 13778534506SJuraj Linkeš """ 13878534506SJuraj Linkeš session_name = f"{self.name} {name}" 13978534506SJuraj Linkeš connection = create_session( 14078534506SJuraj Linkeš self.config, 14178534506SJuraj Linkeš session_name, 14204f5a5a6SJuraj Linkeš get_dts_logger(session_name), 143c4ef44deSJuraj Linkeš ) 144c4ef44deSJuraj Linkeš self._other_sessions.append(connection) 145c4ef44deSJuraj Linkeš return connection 146c4ef44deSJuraj Linkeš 147c020b7ceSJuraj Linkeš def filter_lcores( 148c020b7ceSJuraj Linkeš self, 149c020b7ceSJuraj Linkeš filter_specifier: LogicalCoreCount | LogicalCoreList, 150c020b7ceSJuraj Linkeš ascending: bool = True, 151c020b7ceSJuraj Linkeš ) -> list[LogicalCore]: 1526ef07151SJuraj Linkeš """Filter the node's logical cores that DTS can use. 153c020b7ceSJuraj Linkeš 1546ef07151SJuraj Linkeš Logical cores that DTS can use are the ones that are present on the node, but filtered 1556ef07151SJuraj Linkeš according to the test run configuration. The `filter_specifier` will filter cores from 1566ef07151SJuraj Linkeš those logical cores. 1576ef07151SJuraj Linkeš 1586ef07151SJuraj Linkeš Args: 1596ef07151SJuraj Linkeš filter_specifier: Two different filters can be used, one that specifies the number 1606ef07151SJuraj Linkeš of logical cores per core, cores per socket and the number of sockets, 1616ef07151SJuraj Linkeš and another one that specifies a logical core list. 1626ef07151SJuraj Linkeš ascending: If :data:`True`, use cores with the lowest numerical id first and continue 1636ef07151SJuraj Linkeš in ascending order. If :data:`False`, start with the highest id and continue 1646ef07151SJuraj Linkeš in descending order. This ordering affects which sockets to consider first as well. 1656ef07151SJuraj Linkeš 1666ef07151SJuraj Linkeš Returns: 1676ef07151SJuraj Linkeš The filtered logical cores. 168c020b7ceSJuraj Linkeš """ 169c020b7ceSJuraj Linkeš self._logger.debug(f"Filtering {filter_specifier} from {self.lcores}.") 170c020b7ceSJuraj Linkeš return lcore_filter( 171c020b7ceSJuraj Linkeš self.lcores, 172c020b7ceSJuraj Linkeš filter_specifier, 173c020b7ceSJuraj Linkeš ascending, 174c020b7ceSJuraj Linkeš ).filter() 175c020b7ceSJuraj Linkeš 176c020b7ceSJuraj Linkeš def _get_remote_cpus(self) -> None: 1776ef07151SJuraj Linkeš """Scan CPUs in the remote OS and store a list of LogicalCores.""" 178c020b7ceSJuraj Linkeš self._logger.info("Getting CPU information.") 179c020b7ceSJuraj Linkeš self.lcores = self.main_session.get_remote_cpus(self.config.use_first_core) 180c020b7ceSJuraj Linkeš 181840b1e01SJuraj Linkeš def _setup_hugepages(self) -> None: 1826ef07151SJuraj Linkeš """Setup hugepages on the node. 1836ef07151SJuraj Linkeš 1846ef07151SJuraj Linkeš Configure the hugepages only if they're specified in the node's test run configuration. 185b76d80a4SJuraj Linkeš """ 186b76d80a4SJuraj Linkeš if self.config.hugepages: 187b76d80a4SJuraj Linkeš self.main_session.setup_hugepages( 188c0dd39deSNicholas Pratte self.config.hugepages.number_of, 189e5307b25SNicholas Pratte self.main_session.hugepage_size, 190e5307b25SNicholas Pratte self.config.hugepages.force_first_numa, 191b76d80a4SJuraj Linkeš ) 192b76d80a4SJuraj Linkeš 19378534506SJuraj Linkeš def close(self) -> None: 1946ef07151SJuraj Linkeš """Close all connections and free other resources.""" 195c4ef44deSJuraj Linkeš if self.main_session: 196c4ef44deSJuraj Linkeš self.main_session.close() 197c4ef44deSJuraj Linkeš for session in self._other_sessions: 198c4ef44deSJuraj Linkeš session.close() 199680d8a24SJuraj Linkeš 200840b1e01SJuraj Linkeš 20104f5a5a6SJuraj Linkešdef create_session(node_config: NodeConfiguration, name: str, logger: DTSLogger) -> OSSession: 2026ef07151SJuraj Linkeš """Factory for OS-aware sessions. 2036ef07151SJuraj Linkeš 2046ef07151SJuraj Linkeš Args: 2056ef07151SJuraj Linkeš node_config: The test run configuration of the node to connect to. 2066ef07151SJuraj Linkeš name: The name of the session. 2076ef07151SJuraj Linkeš logger: The logger instance this session will use. 2086ef07151SJuraj Linkeš """ 209840b1e01SJuraj Linkeš match node_config.os: 210840b1e01SJuraj Linkeš case OS.linux: 211840b1e01SJuraj Linkeš return LinuxSession(node_config, name, logger) 212840b1e01SJuraj Linkeš case _: 213840b1e01SJuraj Linkeš raise ConfigurationError(f"Unsupported OS {node_config.os}") 214