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 6""" 7A node is a generic host that DTS connects to and manages. 8""" 9 10from abc import ABC 11from typing import Any, Callable, Type 12 13from framework.config import ( 14 BuildTargetConfiguration, 15 ExecutionConfiguration, 16 NodeConfiguration, 17) 18from framework.logger import DTSLOG, getLogger 19from framework.remote_session import InteractiveShellType, OSSession, create_session 20from framework.settings import SETTINGS 21 22from .hw import ( 23 LogicalCore, 24 LogicalCoreCount, 25 LogicalCoreList, 26 LogicalCoreListFilter, 27 VirtualDevice, 28 lcore_filter, 29) 30from .hw.port import Port 31 32 33class Node(ABC): 34 """ 35 Basic class for node management. This class implements methods that 36 manage a node, such as information gathering (of CPU/PCI/NIC) and 37 environment setup. 38 """ 39 40 main_session: OSSession 41 config: NodeConfiguration 42 name: str 43 lcores: list[LogicalCore] 44 ports: list[Port] 45 _logger: DTSLOG 46 _other_sessions: list[OSSession] 47 _execution_config: ExecutionConfiguration 48 virtual_devices: list[VirtualDevice] 49 50 def __init__(self, node_config: NodeConfiguration): 51 self.config = node_config 52 self.name = node_config.name 53 self._logger = getLogger(self.name) 54 self.main_session = create_session(self.config, self.name, self._logger) 55 56 self._logger.info(f"Connected to node: {self.name}") 57 58 self._get_remote_cpus() 59 # filter the node lcores according to user config 60 self.lcores = LogicalCoreListFilter( 61 self.lcores, LogicalCoreList(self.config.lcores) 62 ).filter() 63 64 self._other_sessions = [] 65 self.virtual_devices = [] 66 self._init_ports() 67 68 def _init_ports(self) -> None: 69 self.ports = [Port(self.name, port_config) for port_config in self.config.ports] 70 self.main_session.update_ports(self.ports) 71 for port in self.ports: 72 self.configure_port_state(port) 73 74 def set_up_execution(self, execution_config: ExecutionConfiguration) -> None: 75 """ 76 Perform the execution setup that will be done for each execution 77 this node is part of. 78 """ 79 self._setup_hugepages() 80 self._set_up_execution(execution_config) 81 self._execution_config = execution_config 82 for vdev in execution_config.vdevs: 83 self.virtual_devices.append(VirtualDevice(vdev)) 84 85 def _set_up_execution(self, execution_config: ExecutionConfiguration) -> None: 86 """ 87 This method exists to be optionally overwritten by derived classes and 88 is not decorated so that the derived class doesn't have to use the decorator. 89 """ 90 91 def tear_down_execution(self) -> None: 92 """ 93 Perform the execution teardown that will be done after each execution 94 this node is part of concludes. 95 """ 96 self.virtual_devices = [] 97 self._tear_down_execution() 98 99 def _tear_down_execution(self) -> None: 100 """ 101 This method exists to be optionally overwritten by derived classes and 102 is not decorated so that the derived class doesn't have to use the decorator. 103 """ 104 105 def set_up_build_target( 106 self, build_target_config: BuildTargetConfiguration 107 ) -> None: 108 """ 109 Perform the build target setup that will be done for each build target 110 tested on this node. 111 """ 112 self._set_up_build_target(build_target_config) 113 114 def _set_up_build_target( 115 self, build_target_config: BuildTargetConfiguration 116 ) -> None: 117 """ 118 This method exists to be optionally overwritten by derived classes and 119 is not decorated so that the derived class doesn't have to use the decorator. 120 """ 121 122 def tear_down_build_target(self) -> None: 123 """ 124 Perform the build target teardown that will be done after each build target 125 tested on this node. 126 """ 127 self._tear_down_build_target() 128 129 def _tear_down_build_target(self) -> None: 130 """ 131 This method exists to be optionally overwritten by derived classes and 132 is not decorated so that the derived class doesn't have to use the decorator. 133 """ 134 135 def create_session(self, name: str) -> OSSession: 136 """ 137 Create and return a new OSSession tailored to the remote OS. 138 """ 139 session_name = f"{self.name} {name}" 140 connection = create_session( 141 self.config, 142 session_name, 143 getLogger(session_name, node=self.name), 144 ) 145 self._other_sessions.append(connection) 146 return connection 147 148 def create_interactive_shell( 149 self, 150 shell_cls: Type[InteractiveShellType], 151 timeout: float = SETTINGS.timeout, 152 privileged: bool = False, 153 app_args: str = "", 154 ) -> InteractiveShellType: 155 """Create a handler for an interactive session. 156 157 Instantiate shell_cls according to the remote OS specifics. 158 159 Args: 160 shell_cls: The class of the shell. 161 timeout: Timeout for reading output from the SSH channel. If you are 162 reading from the buffer and don't receive any data within the timeout 163 it will throw an error. 164 privileged: Whether to run the shell with administrative privileges. 165 app_args: The arguments to be passed to the application. 166 Returns: 167 Instance of the desired interactive application. 168 """ 169 if not shell_cls.dpdk_app: 170 shell_cls.path = self.main_session.join_remote_path(shell_cls.path) 171 172 return self.main_session.create_interactive_shell( 173 shell_cls, 174 app_args, 175 timeout, 176 privileged, 177 ) 178 179 def filter_lcores( 180 self, 181 filter_specifier: LogicalCoreCount | LogicalCoreList, 182 ascending: bool = True, 183 ) -> list[LogicalCore]: 184 """ 185 Filter the LogicalCores found on the Node according to 186 a LogicalCoreCount or a LogicalCoreList. 187 188 If ascending is True, use cores with the lowest numerical id first 189 and continue in ascending order. If False, start with the highest 190 id and continue in descending order. This ordering affects which 191 sockets to consider first as well. 192 """ 193 self._logger.debug(f"Filtering {filter_specifier} from {self.lcores}.") 194 return lcore_filter( 195 self.lcores, 196 filter_specifier, 197 ascending, 198 ).filter() 199 200 def _get_remote_cpus(self) -> None: 201 """ 202 Scan CPUs in the remote OS and store a list of LogicalCores. 203 """ 204 self._logger.info("Getting CPU information.") 205 self.lcores = self.main_session.get_remote_cpus(self.config.use_first_core) 206 207 def _setup_hugepages(self): 208 """ 209 Setup hugepages on the Node. Different architectures can supply different 210 amounts of memory for hugepages and numa-based hugepage allocation may need 211 to be considered. 212 """ 213 if self.config.hugepages: 214 self.main_session.setup_hugepages( 215 self.config.hugepages.amount, self.config.hugepages.force_first_numa 216 ) 217 218 def configure_port_state(self, port: Port, enable: bool = True) -> None: 219 """ 220 Enable/disable port. 221 """ 222 self.main_session.configure_port_state(port, enable) 223 224 def close(self) -> None: 225 """ 226 Close all connections and free other resources. 227 """ 228 if self.main_session: 229 self.main_session.close() 230 for session in self._other_sessions: 231 session.close() 232 self._logger.logger_exit() 233 234 @staticmethod 235 def skip_setup(func: Callable[..., Any]) -> Callable[..., Any]: 236 if SETTINGS.skip_setup: 237 return lambda *args: None 238 else: 239 return func 240