xref: /dpdk/dts/framework/testbed_model/node.py (revision cecfe0aabf585fe49548e19b3b5d7ed98f45783b)
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