xref: /dpdk/dts/framework/testbed_model/sut_node.py (revision e3ab9dd5cd5d5e7cb117507ba9580dae9706c1f5)
178534506SJuraj Linkeš# SPDX-License-Identifier: BSD-3-Clause
278534506SJuraj Linkeš# Copyright(c) 2010-2014 Intel Corporation
378534506SJuraj Linkeš# Copyright(c) 2023 PANTHEON.tech s.r.o.
488489c05SJeremy Spewock# Copyright(c) 2023 University of New Hampshire
5fd8cd8eeSLuca Vizzarro# Copyright(c) 2024 Arm Limited
678534506SJuraj Linkeš
76ef07151SJuraj Linkeš"""System under test (DPDK + hardware) node.
86ef07151SJuraj Linkeš
96ef07151SJuraj LinkešA system under test (SUT) is the combination of DPDK
106ef07151SJuraj Linkešand the hardware we're testing with DPDK (NICs, crypto and other devices).
116ef07151SJuraj LinkešAn SUT node is where this SUT runs.
126ef07151SJuraj Linkeš"""
136ef07151SJuraj Linkeš
146ef07151SJuraj Linkeš
15680d8a24SJuraj Linkešimport os
16c020b7ceSJuraj Linkešimport time
17c72ff85dSLuca Vizzarrofrom dataclasses import dataclass
18*b935bdc3SLuca Vizzarrofrom pathlib import Path, PurePath
19680d8a24SJuraj Linkeš
2088489c05SJeremy Spewockfrom framework.config import (
21ecaff610STomáš Ďurovec    DPDKBuildConfiguration,
22*b935bdc3SLuca Vizzarro    DPDKBuildOptionsConfiguration,
23*b935bdc3SLuca Vizzarro    DPDKPrecompiledBuildConfiguration,
24*b935bdc3SLuca Vizzarro    DPDKUncompiledBuildConfiguration,
25*b935bdc3SLuca Vizzarro    LocalDPDKTarballLocation,
26*b935bdc3SLuca Vizzarro    LocalDPDKTreeLocation,
27*b935bdc3SLuca Vizzarro    RemoteDPDKTarballLocation,
28*b935bdc3SLuca Vizzarro    RemoteDPDKTreeLocation,
29634bed13SJuraj Linkeš    SutNodeConfiguration,
30a24f9604SJuraj Linkeš    TestRunConfiguration,
3188489c05SJeremy Spewock)
32f9957667STomáš Ďurovecfrom framework.exception import ConfigurationError, RemoteFileNotFoundError
33967fc62bSLuca Vizzarrofrom framework.params.eal import EalParams
342b2f5a8aSLuca Vizzarrofrom framework.remote_session.remote_session import CommandResult
35f9957667STomáš Ďurovecfrom framework.utils import MesonArgs, TarCompressionFormat
36680d8a24SJuraj Linkeš
3778534506SJuraj Linkešfrom .node import Node
38c72ff85dSLuca Vizzarrofrom .os_session import OSSession, OSSessionInfo
39840b1e01SJuraj Linkešfrom .virtual_device import VirtualDevice
4078534506SJuraj Linkeš
4178534506SJuraj Linkeš
42c72ff85dSLuca Vizzarro@dataclass(slots=True, frozen=True)
43c72ff85dSLuca Vizzarroclass DPDKBuildInfo:
44c72ff85dSLuca Vizzarro    """Various versions and other information about a DPDK build.
45c72ff85dSLuca Vizzarro
46c72ff85dSLuca Vizzarro    Attributes:
47c72ff85dSLuca Vizzarro        dpdk_version: The DPDK version that was built.
48c72ff85dSLuca Vizzarro        compiler_version: The version of the compiler used to build DPDK.
49c72ff85dSLuca Vizzarro    """
50c72ff85dSLuca Vizzarro
51c72ff85dSLuca Vizzarro    dpdk_version: str | None
52c72ff85dSLuca Vizzarro    compiler_version: str | None
53c72ff85dSLuca Vizzarro
54c72ff85dSLuca Vizzarro
5578534506SJuraj Linkešclass SutNode(Node):
566ef07151SJuraj Linkeš    """The system under test node.
576ef07151SJuraj Linkeš
586ef07151SJuraj Linkeš    The SUT node extends :class:`Node` with DPDK specific features:
596ef07151SJuraj Linkeš
60f9957667STomáš Ďurovec        * Managing DPDK source tree on the remote SUT,
61f9957667STomáš Ďurovec        * Building the DPDK from source or using a pre-built version,
626ef07151SJuraj Linkeš        * Gathering of DPDK build info,
636ef07151SJuraj Linkeš        * The running of DPDK apps, interactively or one-time execution,
646ef07151SJuraj Linkeš        * DPDK apps cleanup.
656ef07151SJuraj Linkeš
66f9957667STomáš Ďurovec    Building DPDK from source uses `build` configuration inside `dpdk_build` of configuration.
676ef07151SJuraj Linkeš
686ef07151SJuraj Linkeš    Attributes:
69a24f9604SJuraj Linkeš        config: The SUT node configuration.
70a24f9604SJuraj Linkeš        virtual_devices: The virtual devices used on the node.
7178534506SJuraj Linkeš    """
72680d8a24SJuraj Linkeš
73634bed13SJuraj Linkeš    config: SutNodeConfiguration
74a24f9604SJuraj Linkeš    virtual_devices: list[VirtualDevice]
75bfad0948SLuca Vizzarro    dpdk_prefix_list: list[str]
76bfad0948SLuca Vizzarro    dpdk_timestamp: str
77b8bdc4c5SJuraj Linkeš    _env_vars: dict
78680d8a24SJuraj Linkeš    _remote_tmp_dir: PurePath
79f9957667STomáš Ďurovec    __remote_dpdk_tree_path: str | PurePath | None
80f9957667STomáš Ďurovec    _remote_dpdk_build_dir: PurePath | None
81680d8a24SJuraj Linkeš    _app_compile_timeout: float
82c020b7ceSJuraj Linkeš    _dpdk_kill_session: OSSession | None
8388489c05SJeremy Spewock    _dpdk_version: str | None
84c72ff85dSLuca Vizzarro    _node_info: OSSessionInfo | None
8588489c05SJeremy Spewock    _compiler_version: str | None
8668010b2aSJeremy Spewock    _path_to_devbind_script: PurePath | None
87f9957667STomáš Ďurovec    _ports_bound_to_dpdk: bool
88680d8a24SJuraj Linkeš
89634bed13SJuraj Linkeš    def __init__(self, node_config: SutNodeConfiguration):
906ef07151SJuraj Linkeš        """Extend the constructor with SUT node specifics.
916ef07151SJuraj Linkeš
926ef07151SJuraj Linkeš        Args:
936ef07151SJuraj Linkeš            node_config: The SUT node's test run configuration.
946ef07151SJuraj Linkeš        """
95f614e737SJuraj Linkeš        super().__init__(node_config)
96a24f9604SJuraj Linkeš        self.virtual_devices = []
97bfad0948SLuca Vizzarro        self.dpdk_prefix_list = []
98b8bdc4c5SJuraj Linkeš        self._env_vars = {}
99680d8a24SJuraj Linkeš        self._remote_tmp_dir = self.main_session.get_remote_tmp_dir()
100f9957667STomáš Ďurovec        self.__remote_dpdk_tree_path = None
101f9957667STomáš Ďurovec        self._remote_dpdk_build_dir = None
102680d8a24SJuraj Linkeš        self._app_compile_timeout = 90
103c020b7ceSJuraj Linkeš        self._dpdk_kill_session = None
104bfad0948SLuca Vizzarro        self.dpdk_timestamp = (
105c020b7ceSJuraj Linkeš            f"{str(os.getpid())}_{time.strftime('%Y%m%d%H%M%S', time.localtime())}"
106c020b7ceSJuraj Linkeš        )
10788489c05SJeremy Spewock        self._dpdk_version = None
10888489c05SJeremy Spewock        self._node_info = None
10988489c05SJeremy Spewock        self._compiler_version = None
11068010b2aSJeremy Spewock        self._path_to_devbind_script = None
111f9957667STomáš Ďurovec        self._ports_bound_to_dpdk = False
112cecfe0aaSJuraj Linkeš        self._logger.info(f"Created node: {self.name}")
113680d8a24SJuraj Linkeš
114680d8a24SJuraj Linkeš    @property
115f9957667STomáš Ďurovec    def _remote_dpdk_tree_path(self) -> str | PurePath:
116f9957667STomáš Ďurovec        """The remote DPDK tree path."""
117f9957667STomáš Ďurovec        if self.__remote_dpdk_tree_path:
118f9957667STomáš Ďurovec            return self.__remote_dpdk_tree_path
1196ef07151SJuraj Linkeš
120f9957667STomáš Ďurovec        self._logger.warning(
121f9957667STomáš Ďurovec            "Failed to get remote dpdk tree path because we don't know the "
122f9957667STomáš Ďurovec            "location on the SUT node."
123680d8a24SJuraj Linkeš        )
124f9957667STomáš Ďurovec        return ""
125680d8a24SJuraj Linkeš
126680d8a24SJuraj Linkeš    @property
127f9957667STomáš Ďurovec    def remote_dpdk_build_dir(self) -> str | PurePath:
128f9957667STomáš Ďurovec        """The remote DPDK build dir path."""
129f9957667STomáš Ďurovec        if self._remote_dpdk_build_dir:
130f9957667STomáš Ďurovec            return self._remote_dpdk_build_dir
131f9957667STomáš Ďurovec
132f9957667STomáš Ďurovec        self._logger.warning(
133f9957667STomáš Ďurovec            "Failed to get remote dpdk build dir because we don't know the "
134f9957667STomáš Ďurovec            "location on the SUT node."
135f9957667STomáš Ďurovec        )
136f9957667STomáš Ďurovec        return ""
137f9957667STomáš Ďurovec
138f9957667STomáš Ďurovec    @property
139f9957667STomáš Ďurovec    def dpdk_version(self) -> str | None:
1406ef07151SJuraj Linkeš        """Last built DPDK version."""
141680d8a24SJuraj Linkeš        if self._dpdk_version is None:
142f9957667STomáš Ďurovec            self._dpdk_version = self.main_session.get_dpdk_version(self._remote_dpdk_tree_path)
143680d8a24SJuraj Linkeš        return self._dpdk_version
144680d8a24SJuraj Linkeš
14588489c05SJeremy Spewock    @property
146c72ff85dSLuca Vizzarro    def node_info(self) -> OSSessionInfo:
1476ef07151SJuraj Linkeš        """Additional node information."""
14888489c05SJeremy Spewock        if self._node_info is None:
14988489c05SJeremy Spewock            self._node_info = self.main_session.get_node_info()
15088489c05SJeremy Spewock        return self._node_info
15188489c05SJeremy Spewock
15288489c05SJeremy Spewock    @property
153f9957667STomáš Ďurovec    def compiler_version(self) -> str | None:
1546ef07151SJuraj Linkeš        """The node's compiler version."""
15588489c05SJeremy Spewock        if self._compiler_version is None:
156f9957667STomáš Ďurovec            self._logger.warning("The `compiler_version` is None because a pre-built DPDK is used.")
157f9957667STomáš Ďurovec
15888489c05SJeremy Spewock        return self._compiler_version
15988489c05SJeremy Spewock
160f9957667STomáš Ďurovec    @compiler_version.setter
161f9957667STomáš Ďurovec    def compiler_version(self, value: str) -> None:
162f9957667STomáš Ďurovec        """Set the `compiler_version` used on the SUT node.
163f9957667STomáš Ďurovec
164f9957667STomáš Ďurovec        Args:
165f9957667STomáš Ďurovec            value: The node's compiler version.
166f9957667STomáš Ďurovec        """
167f9957667STomáš Ďurovec        self._compiler_version = value
168f9957667STomáš Ďurovec
16968010b2aSJeremy Spewock    @property
170f9957667STomáš Ďurovec    def path_to_devbind_script(self) -> PurePath | str:
1716ef07151SJuraj Linkeš        """The path to the dpdk-devbind.py script on the node."""
17268010b2aSJeremy Spewock        if self._path_to_devbind_script is None:
17368010b2aSJeremy Spewock            self._path_to_devbind_script = self.main_session.join_remote_path(
174f9957667STomáš Ďurovec                self._remote_dpdk_tree_path, "usertools", "dpdk-devbind.py"
17568010b2aSJeremy Spewock            )
17668010b2aSJeremy Spewock        return self._path_to_devbind_script
17768010b2aSJeremy Spewock
178ecaff610STomáš Ďurovec    def get_dpdk_build_info(self) -> DPDKBuildInfo:
179ecaff610STomáš Ďurovec        """Get additional DPDK build information.
1806ef07151SJuraj Linkeš
1816ef07151SJuraj Linkeš        Returns:
182ecaff610STomáš Ďurovec            The DPDK build information,
1836ef07151SJuraj Linkeš        """
184ecaff610STomáš Ďurovec        return DPDKBuildInfo(dpdk_version=self.dpdk_version, compiler_version=self.compiler_version)
18588489c05SJeremy Spewock
186f9957667STomáš Ďurovec    def set_up_test_run(
187*b935bdc3SLuca Vizzarro        self,
188*b935bdc3SLuca Vizzarro        test_run_config: TestRunConfiguration,
189*b935bdc3SLuca Vizzarro        dpdk_build_config: DPDKBuildConfiguration,
190f9957667STomáš Ďurovec    ) -> None:
191f9957667STomáš Ďurovec        """Extend the test run setup with vdev config and DPDK build set up.
192680d8a24SJuraj Linkeš
193f9957667STomáš Ďurovec        This method extends the setup process by configuring virtual devices and preparing the DPDK
194f9957667STomáš Ďurovec        environment based on the provided configuration.
1956ef07151SJuraj Linkeš
196a24f9604SJuraj Linkeš        Args:
197a24f9604SJuraj Linkeš            test_run_config: A test run configuration according to which
198a24f9604SJuraj Linkeš                the setup steps will be taken.
199*b935bdc3SLuca Vizzarro            dpdk_build_config: The build configuration of DPDK.
200680d8a24SJuraj Linkeš        """
201*b935bdc3SLuca Vizzarro        super().set_up_test_run(test_run_config, dpdk_build_config)
202*b935bdc3SLuca Vizzarro        for vdev in test_run_config.system_under_test_node.vdevs:
203a24f9604SJuraj Linkeš            self.virtual_devices.append(VirtualDevice(vdev))
204*b935bdc3SLuca Vizzarro        self._set_up_dpdk(dpdk_build_config)
205a24f9604SJuraj Linkeš
206a24f9604SJuraj Linkeš    def tear_down_test_run(self) -> None:
207f9957667STomáš Ďurovec        """Extend the test run teardown with virtual device teardown and DPDK teardown."""
208a24f9604SJuraj Linkeš        super().tear_down_test_run()
209a24f9604SJuraj Linkeš        self.virtual_devices = []
21011b2279aSTomáš Ďurovec        self._tear_down_dpdk()
211a24f9604SJuraj Linkeš
212f9957667STomáš Ďurovec    def _set_up_dpdk(
213*b935bdc3SLuca Vizzarro        self,
214*b935bdc3SLuca Vizzarro        dpdk_build_config: DPDKBuildConfiguration,
215f9957667STomáš Ďurovec    ) -> None:
216a24f9604SJuraj Linkeš        """Set up DPDK the SUT node and bind ports.
217a24f9604SJuraj Linkeš
218f9957667STomáš Ďurovec        DPDK setup includes setting all internals needed for the build, the copying of DPDK
219f9957667STomáš Ďurovec        sources and then building DPDK or using the exist ones from the `dpdk_location`. The drivers
220f9957667STomáš Ďurovec        are bound to those that DPDK needs.
221a24f9604SJuraj Linkeš
222a24f9604SJuraj Linkeš        Args:
223*b935bdc3SLuca Vizzarro            dpdk_build_config: A DPDK build configuration to test.
224a24f9604SJuraj Linkeš        """
225*b935bdc3SLuca Vizzarro        match dpdk_build_config.dpdk_location:
226*b935bdc3SLuca Vizzarro            case RemoteDPDKTreeLocation(dpdk_tree=dpdk_tree):
227*b935bdc3SLuca Vizzarro                self._set_remote_dpdk_tree_path(dpdk_tree)
228*b935bdc3SLuca Vizzarro            case LocalDPDKTreeLocation(dpdk_tree=dpdk_tree):
229*b935bdc3SLuca Vizzarro                self._copy_dpdk_tree(dpdk_tree)
230*b935bdc3SLuca Vizzarro            case RemoteDPDKTarballLocation(tarball=tarball):
231*b935bdc3SLuca Vizzarro                self._validate_remote_dpdk_tarball(tarball)
232*b935bdc3SLuca Vizzarro                self._prepare_and_extract_dpdk_tarball(tarball)
233*b935bdc3SLuca Vizzarro            case LocalDPDKTarballLocation(tarball=tarball):
234*b935bdc3SLuca Vizzarro                remote_tarball = self._copy_dpdk_tarball_to_remote(tarball)
235*b935bdc3SLuca Vizzarro                self._prepare_and_extract_dpdk_tarball(remote_tarball)
236f9957667STomáš Ďurovec
237*b935bdc3SLuca Vizzarro        match dpdk_build_config:
238*b935bdc3SLuca Vizzarro            case DPDKPrecompiledBuildConfiguration(precompiled_build_dir=build_dir):
239*b935bdc3SLuca Vizzarro                self._set_remote_dpdk_build_dir(build_dir)
240*b935bdc3SLuca Vizzarro            case DPDKUncompiledBuildConfiguration(build_options=build_options):
241*b935bdc3SLuca Vizzarro                self._configure_dpdk_build(build_options)
242680d8a24SJuraj Linkeš                self._build_dpdk()
243f9957667STomáš Ďurovec
24468010b2aSJeremy Spewock        self.bind_ports_to_driver()
24568010b2aSJeremy Spewock
24611b2279aSTomáš Ďurovec    def _tear_down_dpdk(self) -> None:
247a24f9604SJuraj Linkeš        """Reset DPDK variables and bind port driver to the OS driver."""
248a24f9604SJuraj Linkeš        self._env_vars = {}
249f9957667STomáš Ďurovec        self.__remote_dpdk_tree_path = None
250f9957667STomáš Ďurovec        self._remote_dpdk_build_dir = None
251a24f9604SJuraj Linkeš        self._dpdk_version = None
252f9957667STomáš Ďurovec        self.compiler_version = None
25368010b2aSJeremy Spewock        self.bind_ports_to_driver(for_dpdk=False)
254680d8a24SJuraj Linkeš
255*b935bdc3SLuca Vizzarro    def _set_remote_dpdk_tree_path(self, dpdk_tree: PurePath):
256f9957667STomáš Ďurovec        """Set the path to the remote DPDK source tree based on the provided DPDK location.
257f9957667STomáš Ďurovec
258f9957667STomáš Ďurovec        Verify DPDK source tree existence on the SUT node, if exists sets the
259f9957667STomáš Ďurovec        `_remote_dpdk_tree_path` property, otherwise sets nothing.
260f9957667STomáš Ďurovec
261f9957667STomáš Ďurovec        Args:
262f9957667STomáš Ďurovec            dpdk_tree: The path to the DPDK source tree directory.
263f9957667STomáš Ďurovec
264f9957667STomáš Ďurovec        Raises:
265f9957667STomáš Ďurovec            RemoteFileNotFoundError: If the DPDK source tree is expected to be on the SUT node but
266f9957667STomáš Ďurovec                is not found.
267f9957667STomáš Ďurovec        """
268f9957667STomáš Ďurovec        if not self.main_session.remote_path_exists(dpdk_tree):
269f9957667STomáš Ďurovec            raise RemoteFileNotFoundError(
270f9957667STomáš Ďurovec                f"Remote DPDK source tree '{dpdk_tree}' not found in SUT node."
271f9957667STomáš Ďurovec            )
272f9957667STomáš Ďurovec        if not self.main_session.is_remote_dir(dpdk_tree):
273*b935bdc3SLuca Vizzarro            raise ConfigurationError(f"Remote DPDK source tree '{dpdk_tree}' must be a directory.")
274f9957667STomáš Ďurovec
275*b935bdc3SLuca Vizzarro        self.__remote_dpdk_tree_path = dpdk_tree
276f9957667STomáš Ďurovec
277*b935bdc3SLuca Vizzarro    def _copy_dpdk_tree(self, dpdk_tree_path: Path) -> None:
278f9957667STomáš Ďurovec        """Copy the DPDK source tree to the SUT.
279f9957667STomáš Ďurovec
280f9957667STomáš Ďurovec        Args:
281f9957667STomáš Ďurovec            dpdk_tree_path: The path to DPDK source tree on local filesystem.
282f9957667STomáš Ďurovec        """
283f9957667STomáš Ďurovec        self._logger.info(
284f9957667STomáš Ďurovec            f"Copying DPDK source tree to SUT: '{dpdk_tree_path}' into '{self._remote_tmp_dir}'."
285f9957667STomáš Ďurovec        )
286f9957667STomáš Ďurovec        self.main_session.copy_dir_to(
287f9957667STomáš Ďurovec            dpdk_tree_path,
288f9957667STomáš Ďurovec            self._remote_tmp_dir,
289f9957667STomáš Ďurovec            exclude=[".git", "*.o"],
290f9957667STomáš Ďurovec            compress_format=TarCompressionFormat.gzip,
291f9957667STomáš Ďurovec        )
292f9957667STomáš Ďurovec
293f9957667STomáš Ďurovec        self.__remote_dpdk_tree_path = self.main_session.join_remote_path(
294f9957667STomáš Ďurovec            self._remote_tmp_dir, PurePath(dpdk_tree_path).name
295f9957667STomáš Ďurovec        )
296f9957667STomáš Ďurovec
297*b935bdc3SLuca Vizzarro    def _validate_remote_dpdk_tarball(self, dpdk_tarball: PurePath) -> None:
298*b935bdc3SLuca Vizzarro        """Validate the DPDK tarball on the SUT node.
299f9957667STomáš Ďurovec
300f9957667STomáš Ďurovec        Args:
301*b935bdc3SLuca Vizzarro            dpdk_tarball: The path to the DPDK tarball on the SUT node.
302f9957667STomáš Ďurovec
303f9957667STomáš Ďurovec        Raises:
304*b935bdc3SLuca Vizzarro            RemoteFileNotFoundError: If the `dpdk_tarball` is expected to be on the SUT node but is
305*b935bdc3SLuca Vizzarro                not found.
306*b935bdc3SLuca Vizzarro            ConfigurationError: If the `dpdk_tarball` is a valid path but not a valid tar archive.
307*b935bdc3SLuca Vizzarro        """
308*b935bdc3SLuca Vizzarro        if not self.main_session.remote_path_exists(dpdk_tarball):
309*b935bdc3SLuca Vizzarro            raise RemoteFileNotFoundError(f"Remote DPDK tarball '{dpdk_tarball}' not found in SUT.")
310*b935bdc3SLuca Vizzarro        if not self.main_session.is_remote_tarfile(dpdk_tarball):
311*b935bdc3SLuca Vizzarro            raise ConfigurationError(f"Remote DPDK tarball '{dpdk_tarball}' must be a tar archive.")
312*b935bdc3SLuca Vizzarro
313*b935bdc3SLuca Vizzarro    def _copy_dpdk_tarball_to_remote(self, dpdk_tarball: Path) -> PurePath:
314*b935bdc3SLuca Vizzarro        """Copy the local DPDK tarball to the SUT node.
315*b935bdc3SLuca Vizzarro
316*b935bdc3SLuca Vizzarro        Args:
317*b935bdc3SLuca Vizzarro            dpdk_tarball: The local path to the DPDK tarball.
318*b935bdc3SLuca Vizzarro
319*b935bdc3SLuca Vizzarro        Returns:
320*b935bdc3SLuca Vizzarro            The path of the copied tarball on the SUT node.
321*b935bdc3SLuca Vizzarro        """
322*b935bdc3SLuca Vizzarro        self._logger.info(
323*b935bdc3SLuca Vizzarro            f"Copying DPDK tarball to SUT: '{dpdk_tarball}' into '{self._remote_tmp_dir}'."
324*b935bdc3SLuca Vizzarro        )
325*b935bdc3SLuca Vizzarro        self.main_session.copy_to(dpdk_tarball, self._remote_tmp_dir)
326*b935bdc3SLuca Vizzarro        return self.main_session.join_remote_path(self._remote_tmp_dir, dpdk_tarball.name)
327*b935bdc3SLuca Vizzarro
328*b935bdc3SLuca Vizzarro    def _prepare_and_extract_dpdk_tarball(self, remote_tarball_path: PurePath) -> None:
329*b935bdc3SLuca Vizzarro        """Prepare the remote DPDK tree path and extract the tarball.
330*b935bdc3SLuca Vizzarro
331*b935bdc3SLuca Vizzarro        This method extracts the remote tarball and sets the `_remote_dpdk_tree_path` property to
332*b935bdc3SLuca Vizzarro        the path of the extracted DPDK tree on the SUT node.
333*b935bdc3SLuca Vizzarro
334*b935bdc3SLuca Vizzarro        Args:
335*b935bdc3SLuca Vizzarro            remote_tarball_path: The path to the DPDK tarball on the SUT node.
336f9957667STomáš Ďurovec        """
337f9957667STomáš Ďurovec
338f9957667STomáš Ďurovec        def remove_tarball_suffix(remote_tarball_path: PurePath) -> PurePath:
339f9957667STomáš Ďurovec            """Remove the tarball suffix from the path.
340f9957667STomáš Ďurovec
341f9957667STomáš Ďurovec            Args:
342f9957667STomáš Ďurovec                remote_tarball_path: The path to the remote tarball.
343f9957667STomáš Ďurovec
344f9957667STomáš Ďurovec            Returns:
345f9957667STomáš Ďurovec                The path without the tarball suffix.
346f9957667STomáš Ďurovec            """
347f9957667STomáš Ďurovec            if len(remote_tarball_path.suffixes) > 1:
348f9957667STomáš Ďurovec                if remote_tarball_path.suffixes[-2] == ".tar":
349f9957667STomáš Ďurovec                    suffixes_to_remove = "".join(remote_tarball_path.suffixes[-2:])
350f9957667STomáš Ďurovec                    return PurePath(str(remote_tarball_path).replace(suffixes_to_remove, ""))
351f9957667STomáš Ďurovec            return remote_tarball_path.with_suffix("")
352f9957667STomáš Ďurovec
353f9957667STomáš Ďurovec        tarball_top_dir = self.main_session.get_tarball_top_dir(remote_tarball_path)
354f9957667STomáš Ďurovec        self.__remote_dpdk_tree_path = self.main_session.join_remote_path(
355*b935bdc3SLuca Vizzarro            remote_tarball_path.parent,
356f9957667STomáš Ďurovec            tarball_top_dir or remove_tarball_suffix(remote_tarball_path),
357f9957667STomáš Ďurovec        )
358f9957667STomáš Ďurovec
359f9957667STomáš Ďurovec        self._logger.info(
360f9957667STomáš Ďurovec            "Extracting DPDK tarball on SUT: "
361f9957667STomáš Ďurovec            f"'{remote_tarball_path}' into '{self._remote_dpdk_tree_path}'."
362f9957667STomáš Ďurovec        )
363f9957667STomáš Ďurovec        self.main_session.extract_remote_tarball(
364f9957667STomáš Ďurovec            remote_tarball_path,
365f9957667STomáš Ďurovec            self._remote_dpdk_tree_path,
366f9957667STomáš Ďurovec        )
367f9957667STomáš Ďurovec
368*b935bdc3SLuca Vizzarro    def _set_remote_dpdk_build_dir(self, build_dir: str):
369f9957667STomáš Ďurovec        """Set the `remote_dpdk_build_dir` on the SUT.
370f9957667STomáš Ďurovec
371*b935bdc3SLuca Vizzarro        Check existence on the SUT node and sets the
372f9957667STomáš Ďurovec        `remote_dpdk_build_dir` property by joining the `_remote_dpdk_tree_path` and `build_dir`.
373f9957667STomáš Ďurovec        Otherwise, sets nothing.
374f9957667STomáš Ďurovec
375f9957667STomáš Ďurovec        Args:
376*b935bdc3SLuca Vizzarro            build_dir: DPDK has been pre-built and the build directory is located
377f9957667STomáš Ďurovec                in a subdirectory of `dpdk_tree` or `tarball` root directory.
378f9957667STomáš Ďurovec
379f9957667STomáš Ďurovec        Raises:
380f9957667STomáš Ďurovec            RemoteFileNotFoundError: If the `build_dir` is expected but does not exist on the SUT
381f9957667STomáš Ďurovec                node.
382f9957667STomáš Ďurovec        """
383f9957667STomáš Ďurovec        remote_dpdk_build_dir = self.main_session.join_remote_path(
384f9957667STomáš Ďurovec            self._remote_dpdk_tree_path, build_dir
385f9957667STomáš Ďurovec        )
386f9957667STomáš Ďurovec        if not self.main_session.remote_path_exists(remote_dpdk_build_dir):
387f9957667STomáš Ďurovec            raise RemoteFileNotFoundError(
388f9957667STomáš Ďurovec                f"Remote DPDK build dir '{remote_dpdk_build_dir}' not found in SUT node."
389f9957667STomáš Ďurovec            )
390f9957667STomáš Ďurovec
391f9957667STomáš Ďurovec        self._remote_dpdk_build_dir = PurePath(remote_dpdk_build_dir)
392f9957667STomáš Ďurovec
393*b935bdc3SLuca Vizzarro    def _configure_dpdk_build(self, dpdk_build_config: DPDKBuildOptionsConfiguration) -> None:
394f9957667STomáš Ďurovec        """Populate common environment variables and set the DPDK build related properties.
395f9957667STomáš Ďurovec
396f9957667STomáš Ďurovec        This method sets `compiler_version` for additional information and `remote_dpdk_build_dir`
397f9957667STomáš Ďurovec        from DPDK build config name.
398f9957667STomáš Ďurovec
399f9957667STomáš Ďurovec        Args:
400f9957667STomáš Ďurovec            dpdk_build_config: A DPDK build configuration to test.
401f9957667STomáš Ďurovec        """
402b8bdc4c5SJuraj Linkeš        self._env_vars = {}
403ecaff610STomáš Ďurovec        self._env_vars.update(self.main_session.get_dpdk_build_env_vars(dpdk_build_config.arch))
404ecaff610STomáš Ďurovec        if compiler_wrapper := dpdk_build_config.compiler_wrapper:
405ecaff610STomáš Ďurovec            self._env_vars["CC"] = f"'{compiler_wrapper} {dpdk_build_config.compiler.name}'"
406ecaff610STomáš Ďurovec        else:
407ecaff610STomáš Ďurovec            self._env_vars["CC"] = dpdk_build_config.compiler.name
408680d8a24SJuraj Linkeš
409f9957667STomáš Ďurovec        self.compiler_version = self.main_session.get_compiler_version(
410f9957667STomáš Ďurovec            dpdk_build_config.compiler.name
411680d8a24SJuraj Linkeš        )
412680d8a24SJuraj Linkeš
413f9957667STomáš Ďurovec        self._remote_dpdk_build_dir = self.main_session.join_remote_path(
414f9957667STomáš Ďurovec            self._remote_dpdk_tree_path, dpdk_build_config.name
415680d8a24SJuraj Linkeš        )
416680d8a24SJuraj Linkeš
417680d8a24SJuraj Linkeš    def _build_dpdk(self) -> None:
4186ef07151SJuraj Linkeš        """Build DPDK.
4196ef07151SJuraj Linkeš
420f9957667STomáš Ďurovec        Uses the already configured DPDK build configuration. Assumes that the
421f9957667STomáš Ďurovec        `_remote_dpdk_tree_path` has already been set on the SUT node.
422680d8a24SJuraj Linkeš        """
423680d8a24SJuraj Linkeš        self.main_session.build_dpdk(
424680d8a24SJuraj Linkeš            self._env_vars,
425680d8a24SJuraj Linkeš            MesonArgs(default_library="static", enable_kmods=True, libdir="lib"),
426f9957667STomáš Ďurovec            self._remote_dpdk_tree_path,
427680d8a24SJuraj Linkeš            self.remote_dpdk_build_dir,
428680d8a24SJuraj Linkeš        )
429680d8a24SJuraj Linkeš
430680d8a24SJuraj Linkeš    def build_dpdk_app(self, app_name: str, **meson_dpdk_args: str | bool) -> PurePath:
4316ef07151SJuraj Linkeš        """Build one or all DPDK apps.
4326ef07151SJuraj Linkeš
4336ef07151SJuraj Linkeš        Requires DPDK to be already built on the SUT node.
4346ef07151SJuraj Linkeš
4356ef07151SJuraj Linkeš        Args:
4366ef07151SJuraj Linkeš            app_name: The name of the DPDK app to build.
4376ef07151SJuraj Linkeš                When `app_name` is ``all``, build all example apps.
4386ef07151SJuraj Linkeš            meson_dpdk_args: The arguments found in ``meson_options.txt`` in root DPDK directory.
4396ef07151SJuraj Linkeš                Do not use ``-D`` with them.
4406ef07151SJuraj Linkeš
4416ef07151SJuraj Linkeš        Returns:
4426ef07151SJuraj Linkeš            The directory path of the built app. If building all apps, return
443680d8a24SJuraj Linkeš            the path to the examples directory (where all apps reside).
444680d8a24SJuraj Linkeš        """
445680d8a24SJuraj Linkeš        self.main_session.build_dpdk(
446680d8a24SJuraj Linkeš            self._env_vars,
447680d8a24SJuraj Linkeš            MesonArgs(examples=app_name, **meson_dpdk_args),  # type: ignore [arg-type]
448680d8a24SJuraj Linkeš            # ^^ https://github.com/python/mypy/issues/11583
449f9957667STomáš Ďurovec            self._remote_dpdk_tree_path,
450680d8a24SJuraj Linkeš            self.remote_dpdk_build_dir,
451680d8a24SJuraj Linkeš            rebuild=True,
452680d8a24SJuraj Linkeš            timeout=self._app_compile_timeout,
453680d8a24SJuraj Linkeš        )
454680d8a24SJuraj Linkeš
455680d8a24SJuraj Linkeš        if app_name == "all":
456517b4b26SJuraj Linkeš            return self.main_session.join_remote_path(self.remote_dpdk_build_dir, "examples")
457680d8a24SJuraj Linkeš        return self.main_session.join_remote_path(
458680d8a24SJuraj Linkeš            self.remote_dpdk_build_dir, "examples", f"dpdk-{app_name}"
459680d8a24SJuraj Linkeš        )
460c020b7ceSJuraj Linkeš
461c020b7ceSJuraj Linkeš    def kill_cleanup_dpdk_apps(self) -> None:
4626ef07151SJuraj Linkeš        """Kill all dpdk applications on the SUT, then clean up hugepages."""
463c020b7ceSJuraj Linkeš        if self._dpdk_kill_session and self._dpdk_kill_session.is_alive():
464c020b7ceSJuraj Linkeš            # we can use the session if it exists and responds
465bfad0948SLuca Vizzarro            self._dpdk_kill_session.kill_cleanup_dpdk_apps(self.dpdk_prefix_list)
466c020b7ceSJuraj Linkeš        else:
467c020b7ceSJuraj Linkeš            # otherwise, we need to (re)create it
468c020b7ceSJuraj Linkeš            self._dpdk_kill_session = self.create_session("dpdk_kill")
469bfad0948SLuca Vizzarro        self.dpdk_prefix_list = []
470c020b7ceSJuraj Linkeš
471444833c0SJuraj Linkeš    def run_dpdk_app(
472fd8cd8eeSLuca Vizzarro        self, app_path: PurePath, eal_params: EalParams, timeout: float = 30
473444833c0SJuraj Linkeš    ) -> CommandResult:
4746ef07151SJuraj Linkeš        """Run DPDK application on the remote node.
4756ef07151SJuraj Linkeš
4766ef07151SJuraj Linkeš        The application is not run interactively - the command that starts the application
4776ef07151SJuraj Linkeš        is executed and then the call waits for it to finish execution.
4786ef07151SJuraj Linkeš
4796ef07151SJuraj Linkeš        Args:
4806ef07151SJuraj Linkeš            app_path: The remote path to the DPDK application.
481fd8cd8eeSLuca Vizzarro            eal_params: EAL parameters to run the DPDK application with.
4826ef07151SJuraj Linkeš            timeout: Wait at most this long in seconds for `command` execution to complete.
4836ef07151SJuraj Linkeš
4846ef07151SJuraj Linkeš        Returns:
4856ef07151SJuraj Linkeš            The result of the DPDK app execution.
486444833c0SJuraj Linkeš        """
487444833c0SJuraj Linkeš        return self.main_session.send_command(
488fd8cd8eeSLuca Vizzarro            f"{app_path} {eal_params}", timeout, privileged=True, verify=True
489444833c0SJuraj Linkeš        )
490444833c0SJuraj Linkeš
49168010b2aSJeremy Spewock    def bind_ports_to_driver(self, for_dpdk: bool = True) -> None:
49268010b2aSJeremy Spewock        """Bind all ports on the SUT to a driver.
49368010b2aSJeremy Spewock
49468010b2aSJeremy Spewock        Args:
4956ef07151SJuraj Linkeš            for_dpdk: If :data:`True`, binds ports to os_driver_for_dpdk.
4966ef07151SJuraj Linkeš                If :data:`False`, binds to os_driver.
49768010b2aSJeremy Spewock        """
498f9957667STomáš Ďurovec        if self._ports_bound_to_dpdk == for_dpdk:
499f9957667STomáš Ďurovec            return
500f9957667STomáš Ďurovec
50168010b2aSJeremy Spewock        for port in self.ports:
50268010b2aSJeremy Spewock            driver = port.os_driver_for_dpdk if for_dpdk else port.os_driver
50368010b2aSJeremy Spewock            self.main_session.send_command(
50468010b2aSJeremy Spewock                f"{self.path_to_devbind_script} -b {driver} --force {port.pci}",
50568010b2aSJeremy Spewock                privileged=True,
50668010b2aSJeremy Spewock                verify=True,
50768010b2aSJeremy Spewock            )
508f9957667STomáš Ďurovec        self._ports_bound_to_dpdk = for_dpdk
509