1# SPDX-License-Identifier: BSD-3-Clause 2# Copyright(c) 2010-2014 Intel Corporation 3# Copyright(c) 2022-2023 PANTHEON.tech s.r.o. 4# Copyright(c) 2022-2023 University of New Hampshire 5# Copyright(c) 2024 Arm Limited 6 7"""Common functionality for node management. 8 9A node is any host/server DTS connects to. 10 11The base class, :class:`Node`, provides features common to all nodes and is supposed 12to be extended by subclasses with features specific to each node type. 13The :func:`~Node.skip_setup` decorator can be used without subclassing. 14""" 15 16from abc import ABC 17 18from framework.config import ( 19 OS, 20 DPDKBuildConfiguration, 21 NodeConfiguration, 22 TestRunConfiguration, 23) 24from framework.exception import ConfigurationError 25from framework.logger import DTSLogger, get_dts_logger 26 27from .cpu import ( 28 LogicalCore, 29 LogicalCoreCount, 30 LogicalCoreList, 31 LogicalCoreListFilter, 32 lcore_filter, 33) 34from .linux_session import LinuxSession 35from .os_session import OSSession 36from .port import Port 37 38 39class Node(ABC): 40 """The base class for node management. 41 42 It shouldn't be instantiated, but rather subclassed. 43 It implements common methods to manage any node: 44 45 * Connection to the node, 46 * Hugepages setup. 47 48 Attributes: 49 main_session: The primary OS-aware remote session used to communicate with the node. 50 config: The node configuration. 51 name: The name of the node. 52 lcores: The list of logical cores that DTS can use on the node. 53 It's derived from logical cores present on the node and the test run configuration. 54 ports: The ports of this node specified in the test run configuration. 55 """ 56 57 main_session: OSSession 58 config: NodeConfiguration 59 name: str 60 lcores: list[LogicalCore] 61 ports: list[Port] 62 _logger: DTSLogger 63 _other_sessions: list[OSSession] 64 _test_run_config: TestRunConfiguration 65 66 def __init__(self, node_config: NodeConfiguration): 67 """Connect to the node and gather info during initialization. 68 69 Extra gathered information: 70 71 * The list of available logical CPUs. This is then filtered by 72 the ``lcores`` configuration in the YAML test run configuration file, 73 * Information about ports from the YAML test run configuration file. 74 75 Args: 76 node_config: The node's test run configuration. 77 """ 78 self.config = node_config 79 self.name = node_config.name 80 self._logger = get_dts_logger(self.name) 81 self.main_session = create_session(self.config, self.name, self._logger) 82 83 self._logger.info(f"Connected to node: {self.name}") 84 85 self._get_remote_cpus() 86 # filter the node lcores according to the test run configuration 87 self.lcores = LogicalCoreListFilter( 88 self.lcores, LogicalCoreList(self.config.lcores) 89 ).filter() 90 91 self._other_sessions = [] 92 self._init_ports() 93 94 def _init_ports(self) -> None: 95 self.ports = [Port(self.name, port_config) for port_config in self.config.ports] 96 self.main_session.update_ports(self.ports) 97 98 def set_up_test_run( 99 self, 100 test_run_config: TestRunConfiguration, 101 dpdk_build_config: DPDKBuildConfiguration, 102 ) -> None: 103 """Test run setup steps. 104 105 Configure hugepages on all DTS node types. Additional steps can be added by 106 extending the method in subclasses with the use of super(). 107 108 Args: 109 test_run_config: A test run configuration according to which 110 the setup steps will be taken. 111 dpdk_build_config: The build configuration of DPDK. 112 """ 113 self._setup_hugepages() 114 115 def tear_down_test_run(self) -> None: 116 """Test run teardown steps. 117 118 There are currently no common execution teardown steps common to all DTS node types. 119 Additional steps can be added by extending the method in subclasses with the use of super(). 120 """ 121 122 def create_session(self, name: str) -> OSSession: 123 """Create and return a new OS-aware remote session. 124 125 The returned session won't be used by the node creating it. The session must be used by 126 the caller. The session will be maintained for the entire lifecycle of the node object, 127 at the end of which the session will be cleaned up automatically. 128 129 Note: 130 Any number of these supplementary sessions may be created. 131 132 Args: 133 name: The name of the session. 134 135 Returns: 136 A new OS-aware remote session. 137 """ 138 session_name = f"{self.name} {name}" 139 connection = create_session( 140 self.config, 141 session_name, 142 get_dts_logger(session_name), 143 ) 144 self._other_sessions.append(connection) 145 return connection 146 147 def filter_lcores( 148 self, 149 filter_specifier: LogicalCoreCount | LogicalCoreList, 150 ascending: bool = True, 151 ) -> list[LogicalCore]: 152 """Filter the node's logical cores that DTS can use. 153 154 Logical cores that DTS can use are the ones that are present on the node, but filtered 155 according to the test run configuration. The `filter_specifier` will filter cores from 156 those logical cores. 157 158 Args: 159 filter_specifier: Two different filters can be used, one that specifies the number 160 of logical cores per core, cores per socket and the number of sockets, 161 and another one that specifies a logical core list. 162 ascending: If :data:`True`, use cores with the lowest numerical id first and continue 163 in ascending order. If :data:`False`, start with the highest id and continue 164 in descending order. This ordering affects which sockets to consider first as well. 165 166 Returns: 167 The filtered logical cores. 168 """ 169 self._logger.debug(f"Filtering {filter_specifier} from {self.lcores}.") 170 return lcore_filter( 171 self.lcores, 172 filter_specifier, 173 ascending, 174 ).filter() 175 176 def _get_remote_cpus(self) -> None: 177 """Scan CPUs in the remote OS and store a list of LogicalCores.""" 178 self._logger.info("Getting CPU information.") 179 self.lcores = self.main_session.get_remote_cpus(self.config.use_first_core) 180 181 def _setup_hugepages(self) -> None: 182 """Setup hugepages on the node. 183 184 Configure the hugepages only if they're specified in the node's test run configuration. 185 """ 186 if self.config.hugepages: 187 self.main_session.setup_hugepages( 188 self.config.hugepages.number_of, 189 self.main_session.hugepage_size, 190 self.config.hugepages.force_first_numa, 191 ) 192 193 def close(self) -> None: 194 """Close all connections and free other resources.""" 195 if self.main_session: 196 self.main_session.close() 197 for session in self._other_sessions: 198 session.close() 199 200 201def create_session(node_config: NodeConfiguration, name: str, logger: DTSLogger) -> OSSession: 202 """Factory for OS-aware sessions. 203 204 Args: 205 node_config: The test run configuration of the node to connect to. 206 name: The name of the session. 207 logger: The logger instance this session will use. 208 """ 209 match node_config.os: 210 case OS.linux: 211 return LinuxSession(node_config, name, logger) 212 case _: 213 raise ConfigurationError(f"Unsupported OS {node_config.os}") 214