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 5c4ef44deSJuraj Linkeš 6c4ef44deSJuraj Linkeš""" 7c4ef44deSJuraj LinkešA node is a generic host that DTS connects to and manages. 8c4ef44deSJuraj Linkeš""" 9c4ef44deSJuraj Linkeš 10*cecfe0aaSJuraj Linkešfrom abc import ABC 1188489c05SJeremy Spewockfrom typing import Any, Callable, Type 12680d8a24SJuraj Linkeš 1378534506SJuraj Linkešfrom framework.config import ( 1478534506SJuraj Linkeš BuildTargetConfiguration, 1578534506SJuraj Linkeš ExecutionConfiguration, 1678534506SJuraj Linkeš NodeConfiguration, 1778534506SJuraj Linkeš) 18c4ef44deSJuraj Linkešfrom framework.logger import DTSLOG, getLogger 1988489c05SJeremy Spewockfrom framework.remote_session import InteractiveShellType, OSSession, create_session 20680d8a24SJuraj Linkešfrom framework.settings import SETTINGS 21c4ef44deSJuraj Linkeš 22c020b7ceSJuraj Linkešfrom .hw import ( 23c020b7ceSJuraj Linkeš LogicalCore, 24c020b7ceSJuraj Linkeš LogicalCoreCount, 25c020b7ceSJuraj Linkeš LogicalCoreList, 26c020b7ceSJuraj Linkeš LogicalCoreListFilter, 2788489c05SJeremy Spewock VirtualDevice, 28c020b7ceSJuraj Linkeš lcore_filter, 29c020b7ceSJuraj Linkeš) 30*cecfe0aaSJuraj Linkešfrom .hw.port import Port 31c020b7ceSJuraj Linkeš 32c4ef44deSJuraj Linkeš 33*cecfe0aaSJuraj Linkešclass Node(ABC): 34c4ef44deSJuraj Linkeš """ 3578534506SJuraj Linkeš Basic class for node management. This class implements methods that 36c4ef44deSJuraj Linkeš manage a node, such as information gathering (of CPU/PCI/NIC) and 37c4ef44deSJuraj Linkeš environment setup. 38c4ef44deSJuraj Linkeš """ 39c4ef44deSJuraj Linkeš 4078534506SJuraj Linkeš main_session: OSSession 4178534506SJuraj Linkeš config: NodeConfiguration 42c4ef44deSJuraj Linkeš name: str 43c020b7ceSJuraj Linkeš lcores: list[LogicalCore] 44*cecfe0aaSJuraj Linkeš ports: list[Port] 4578534506SJuraj Linkeš _logger: DTSLOG 4678534506SJuraj Linkeš _other_sessions: list[OSSession] 4788489c05SJeremy Spewock _execution_config: ExecutionConfiguration 4888489c05SJeremy Spewock virtual_devices: list[VirtualDevice] 49c4ef44deSJuraj Linkeš 50c4ef44deSJuraj Linkeš def __init__(self, node_config: NodeConfiguration): 5178534506SJuraj Linkeš self.config = node_config 5278534506SJuraj Linkeš self.name = node_config.name 5378534506SJuraj Linkeš self._logger = getLogger(self.name) 5478534506SJuraj Linkeš self.main_session = create_session(self.config, self.name, self._logger) 5578534506SJuraj Linkeš 56*cecfe0aaSJuraj Linkeš self._logger.info(f"Connected to node: {self.name}") 57*cecfe0aaSJuraj Linkeš 58c020b7ceSJuraj Linkeš self._get_remote_cpus() 59c020b7ceSJuraj Linkeš # filter the node lcores according to user config 60c020b7ceSJuraj Linkeš self.lcores = LogicalCoreListFilter( 61c020b7ceSJuraj Linkeš self.lcores, LogicalCoreList(self.config.lcores) 62c020b7ceSJuraj Linkeš ).filter() 63c020b7ceSJuraj Linkeš 64c4ef44deSJuraj Linkeš self._other_sessions = [] 6588489c05SJeremy Spewock self.virtual_devices = [] 66*cecfe0aaSJuraj Linkeš self._init_ports() 67c4ef44deSJuraj Linkeš 68*cecfe0aaSJuraj Linkeš def _init_ports(self) -> None: 69*cecfe0aaSJuraj Linkeš self.ports = [Port(self.name, port_config) for port_config in self.config.ports] 70*cecfe0aaSJuraj Linkeš self.main_session.update_ports(self.ports) 71*cecfe0aaSJuraj Linkeš for port in self.ports: 72*cecfe0aaSJuraj Linkeš self.configure_port_state(port) 73c4ef44deSJuraj Linkeš 7478534506SJuraj Linkeš def set_up_execution(self, execution_config: ExecutionConfiguration) -> None: 75c4ef44deSJuraj Linkeš """ 7678534506SJuraj Linkeš Perform the execution setup that will be done for each execution 7778534506SJuraj Linkeš this node is part of. 7878534506SJuraj Linkeš """ 79b76d80a4SJuraj Linkeš self._setup_hugepages() 8078534506SJuraj Linkeš self._set_up_execution(execution_config) 8188489c05SJeremy Spewock self._execution_config = execution_config 8288489c05SJeremy Spewock for vdev in execution_config.vdevs: 8388489c05SJeremy Spewock self.virtual_devices.append(VirtualDevice(vdev)) 8478534506SJuraj Linkeš 8578534506SJuraj Linkeš def _set_up_execution(self, execution_config: ExecutionConfiguration) -> None: 8678534506SJuraj Linkeš """ 8778534506SJuraj Linkeš This method exists to be optionally overwritten by derived classes and 8878534506SJuraj Linkeš is not decorated so that the derived class doesn't have to use the decorator. 89c4ef44deSJuraj Linkeš """ 90c4ef44deSJuraj Linkeš 9178534506SJuraj Linkeš def tear_down_execution(self) -> None: 9278534506SJuraj Linkeš """ 9378534506SJuraj Linkeš Perform the execution teardown that will be done after each execution 9478534506SJuraj Linkeš this node is part of concludes. 9578534506SJuraj Linkeš """ 9688489c05SJeremy Spewock self.virtual_devices = [] 9778534506SJuraj Linkeš self._tear_down_execution() 98c4ef44deSJuraj Linkeš 9978534506SJuraj Linkeš def _tear_down_execution(self) -> None: 10078534506SJuraj Linkeš """ 10178534506SJuraj Linkeš This method exists to be optionally overwritten by derived classes and 10278534506SJuraj Linkeš is not decorated so that the derived class doesn't have to use the decorator. 10378534506SJuraj Linkeš """ 10478534506SJuraj Linkeš 10578534506SJuraj Linkeš def set_up_build_target( 10678534506SJuraj Linkeš self, build_target_config: BuildTargetConfiguration 10778534506SJuraj Linkeš ) -> None: 10878534506SJuraj Linkeš """ 10978534506SJuraj Linkeš Perform the build target setup that will be done for each build target 11078534506SJuraj Linkeš tested on this node. 11178534506SJuraj Linkeš """ 11278534506SJuraj Linkeš self._set_up_build_target(build_target_config) 11378534506SJuraj Linkeš 11478534506SJuraj Linkeš def _set_up_build_target( 11578534506SJuraj Linkeš self, build_target_config: BuildTargetConfiguration 11678534506SJuraj Linkeš ) -> None: 11778534506SJuraj Linkeš """ 11878534506SJuraj Linkeš This method exists to be optionally overwritten by derived classes and 11978534506SJuraj Linkeš is not decorated so that the derived class doesn't have to use the decorator. 12078534506SJuraj Linkeš """ 12178534506SJuraj Linkeš 12278534506SJuraj Linkeš def tear_down_build_target(self) -> None: 12378534506SJuraj Linkeš """ 12478534506SJuraj Linkeš Perform the build target teardown that will be done after each build target 12578534506SJuraj Linkeš tested on this node. 12678534506SJuraj Linkeš """ 12778534506SJuraj Linkeš self._tear_down_build_target() 12878534506SJuraj Linkeš 12978534506SJuraj Linkeš def _tear_down_build_target(self) -> None: 13078534506SJuraj Linkeš """ 13178534506SJuraj Linkeš This method exists to be optionally overwritten by derived classes and 13278534506SJuraj Linkeš is not decorated so that the derived class doesn't have to use the decorator. 13378534506SJuraj Linkeš """ 13478534506SJuraj Linkeš 13578534506SJuraj Linkeš def create_session(self, name: str) -> OSSession: 13678534506SJuraj Linkeš """ 13778534506SJuraj Linkeš Create and return a new OSSession tailored to the remote OS. 13878534506SJuraj Linkeš """ 13978534506SJuraj Linkeš session_name = f"{self.name} {name}" 14078534506SJuraj Linkeš connection = create_session( 14178534506SJuraj Linkeš self.config, 14278534506SJuraj Linkeš session_name, 14378534506SJuraj Linkeš getLogger(session_name, node=self.name), 144c4ef44deSJuraj Linkeš ) 145c4ef44deSJuraj Linkeš self._other_sessions.append(connection) 146c4ef44deSJuraj Linkeš return connection 147c4ef44deSJuraj Linkeš 14888489c05SJeremy Spewock def create_interactive_shell( 14988489c05SJeremy Spewock self, 15088489c05SJeremy Spewock shell_cls: Type[InteractiveShellType], 15188489c05SJeremy Spewock timeout: float = SETTINGS.timeout, 15288489c05SJeremy Spewock privileged: bool = False, 15388489c05SJeremy Spewock app_args: str = "", 15488489c05SJeremy Spewock ) -> InteractiveShellType: 15588489c05SJeremy Spewock """Create a handler for an interactive session. 15688489c05SJeremy Spewock 15788489c05SJeremy Spewock Instantiate shell_cls according to the remote OS specifics. 15888489c05SJeremy Spewock 15988489c05SJeremy Spewock Args: 16088489c05SJeremy Spewock shell_cls: The class of the shell. 16188489c05SJeremy Spewock timeout: Timeout for reading output from the SSH channel. If you are 16288489c05SJeremy Spewock reading from the buffer and don't receive any data within the timeout 16388489c05SJeremy Spewock it will throw an error. 16488489c05SJeremy Spewock privileged: Whether to run the shell with administrative privileges. 16588489c05SJeremy Spewock app_args: The arguments to be passed to the application. 16688489c05SJeremy Spewock Returns: 16788489c05SJeremy Spewock Instance of the desired interactive application. 16888489c05SJeremy Spewock """ 16988489c05SJeremy Spewock if not shell_cls.dpdk_app: 17088489c05SJeremy Spewock shell_cls.path = self.main_session.join_remote_path(shell_cls.path) 17188489c05SJeremy Spewock 17288489c05SJeremy Spewock return self.main_session.create_interactive_shell( 17388489c05SJeremy Spewock shell_cls, 17488489c05SJeremy Spewock app_args, 17588489c05SJeremy Spewock timeout, 17688489c05SJeremy Spewock privileged, 17788489c05SJeremy Spewock ) 17888489c05SJeremy Spewock 179c020b7ceSJuraj Linkeš def filter_lcores( 180c020b7ceSJuraj Linkeš self, 181c020b7ceSJuraj Linkeš filter_specifier: LogicalCoreCount | LogicalCoreList, 182c020b7ceSJuraj Linkeš ascending: bool = True, 183c020b7ceSJuraj Linkeš ) -> list[LogicalCore]: 184c020b7ceSJuraj Linkeš """ 185c020b7ceSJuraj Linkeš Filter the LogicalCores found on the Node according to 186c020b7ceSJuraj Linkeš a LogicalCoreCount or a LogicalCoreList. 187c020b7ceSJuraj Linkeš 188c020b7ceSJuraj Linkeš If ascending is True, use cores with the lowest numerical id first 189c020b7ceSJuraj Linkeš and continue in ascending order. If False, start with the highest 190c020b7ceSJuraj Linkeš id and continue in descending order. This ordering affects which 191c020b7ceSJuraj Linkeš sockets to consider first as well. 192c020b7ceSJuraj Linkeš """ 193c020b7ceSJuraj Linkeš self._logger.debug(f"Filtering {filter_specifier} from {self.lcores}.") 194c020b7ceSJuraj Linkeš return lcore_filter( 195c020b7ceSJuraj Linkeš self.lcores, 196c020b7ceSJuraj Linkeš filter_specifier, 197c020b7ceSJuraj Linkeš ascending, 198c020b7ceSJuraj Linkeš ).filter() 199c020b7ceSJuraj Linkeš 200c020b7ceSJuraj Linkeš def _get_remote_cpus(self) -> None: 201c020b7ceSJuraj Linkeš """ 202c020b7ceSJuraj Linkeš Scan CPUs in the remote OS and store a list of LogicalCores. 203c020b7ceSJuraj Linkeš """ 204c020b7ceSJuraj Linkeš self._logger.info("Getting CPU information.") 205c020b7ceSJuraj Linkeš self.lcores = self.main_session.get_remote_cpus(self.config.use_first_core) 206c020b7ceSJuraj Linkeš 207b76d80a4SJuraj Linkeš def _setup_hugepages(self): 208b76d80a4SJuraj Linkeš """ 209b76d80a4SJuraj Linkeš Setup hugepages on the Node. Different architectures can supply different 210b76d80a4SJuraj Linkeš amounts of memory for hugepages and numa-based hugepage allocation may need 211b76d80a4SJuraj Linkeš to be considered. 212b76d80a4SJuraj Linkeš """ 213b76d80a4SJuraj Linkeš if self.config.hugepages: 214b76d80a4SJuraj Linkeš self.main_session.setup_hugepages( 215b76d80a4SJuraj Linkeš self.config.hugepages.amount, self.config.hugepages.force_first_numa 216b76d80a4SJuraj Linkeš ) 217b76d80a4SJuraj Linkeš 218*cecfe0aaSJuraj Linkeš def configure_port_state(self, port: Port, enable: bool = True) -> None: 219*cecfe0aaSJuraj Linkeš """ 220*cecfe0aaSJuraj Linkeš Enable/disable port. 221*cecfe0aaSJuraj Linkeš """ 222*cecfe0aaSJuraj Linkeš self.main_session.configure_port_state(port, enable) 223*cecfe0aaSJuraj Linkeš 22478534506SJuraj Linkeš def close(self) -> None: 225c4ef44deSJuraj Linkeš """ 22678534506SJuraj Linkeš Close all connections and free other resources. 227c4ef44deSJuraj Linkeš """ 228c4ef44deSJuraj Linkeš if self.main_session: 229c4ef44deSJuraj Linkeš self.main_session.close() 230c4ef44deSJuraj Linkeš for session in self._other_sessions: 231c4ef44deSJuraj Linkeš session.close() 23278534506SJuraj Linkeš self._logger.logger_exit() 233680d8a24SJuraj Linkeš 234680d8a24SJuraj Linkeš @staticmethod 235680d8a24SJuraj Linkeš def skip_setup(func: Callable[..., Any]) -> Callable[..., Any]: 236680d8a24SJuraj Linkeš if SETTINGS.skip_setup: 237680d8a24SJuraj Linkeš return lambda *args: None 238680d8a24SJuraj Linkeš else: 239680d8a24SJuraj Linkeš return func 240