From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mails.dpdk.org (mails.dpdk.org [217.70.189.124]) by inbox.dpdk.org (Postfix) with ESMTP id 9DCB3440EC; Tue, 28 May 2024 17:50:15 +0200 (CEST) Received: from mails.dpdk.org (localhost [127.0.0.1]) by mails.dpdk.org (Postfix) with ESMTP id 8904940A6C; Tue, 28 May 2024 17:50:15 +0200 (CEST) Received: from mail-lj1-f171.google.com (mail-lj1-f171.google.com [209.85.208.171]) by mails.dpdk.org (Postfix) with ESMTP id 4127240A6B for ; Tue, 28 May 2024 17:50:14 +0200 (CEST) Received: by mail-lj1-f171.google.com with SMTP id 38308e7fff4ca-2e95a63771fso964411fa.1 for ; Tue, 28 May 2024 08:50:14 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=iol.unh.edu; s=unh-iol; t=1716911414; x=1717516214; darn=dpdk.org; h=content-transfer-encoding:cc:to:subject:message-id:date:from :in-reply-to:references:mime-version:from:to:cc:subject:date :message-id:reply-to; bh=kBSXV4b6GqedBatCyvefMyhRxNc7oq+PF+EQnUB0EJE=; b=YhCU1NN6E0kuYH6LFwmPtqvsWahrLHGaWFPb33KZE+DiPxxa/9lhoNLOCdWhApznp/ 24AXO6fKURDZ6jcWW05GNr1jqLvu71b0hytEXiLD3MgFTRTnJugNScZPZJMOLl+Lka1u M4GZdHHnCIrU6LG8Zygx0S0lIxj6iypo2Q9nk= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1716911414; x=1717516214; h=content-transfer-encoding:cc:to:subject:message-id:date:from :in-reply-to:references:mime-version:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=kBSXV4b6GqedBatCyvefMyhRxNc7oq+PF+EQnUB0EJE=; b=fogSQKMj2O2MV2hNqz5VYkHQkGarkrDFj2t1cLMKgzsMbIkXkrNMyZUEN78DAAHwbs 2P4QyI+KjksCtJPdVcfZd9SZOnP/EAbD4AC0+irVlj4HAXRpnR/ulvJStzTlrxPDTIRz izbLMGqhOaSYca30eWsOimpg/z9Ere89KIu7CdXNloCiNKsDqhRWIs/ep/5l5c+AroHR u8lNlCPrSejEaBaOiUIlxA8HkSWX71TBo+mRlOajs8DcHkXZYlgh5FIfDLkch3umcjKh wzC5GWmSZ6MhXNkuGUDB674kmOUFJ+kbC+RXFchc4U7iwjygEq3DMpWegnAcn0eze838 Ntkg== X-Gm-Message-State: AOJu0YyoV2qOjNOL9iI5qWuNx/CjXMoDZvvQBacXrjqgu4aPjf51DvxP aYTaiNMzAl3ighRKEmmWNLysg9jDjv6m5C0LCyzpdwkRtEdvNBEBKqKAh6JMRp2rjtALduM3W8W WDaN1ugKcjQxnR9asmQylKgE/YqCwIEzDvZv2Vw== X-Google-Smtp-Source: AGHT+IHevytogzA5BlFMBBBNqm593QOdGFJiKRbd7Lfe6N/8ChSB3aY4knu+xtaCAR9RRxL/EM4ELDaopHBQdeIYaCU= X-Received: by 2002:a2e:920e:0:b0:2e9:5986:c86f with SMTP id 38308e7fff4ca-2e95b27b872mr81538901fa.3.1716911413552; Tue, 28 May 2024 08:50:13 -0700 (PDT) MIME-Version: 1.0 References: <20240326190422.577028-1-luca.vizzarro@arm.com> <20240509112057.1167947-1-luca.vizzarro@arm.com> <20240509112057.1167947-8-luca.vizzarro@arm.com> In-Reply-To: <20240509112057.1167947-8-luca.vizzarro@arm.com> From: Nicholas Pratte Date: Tue, 28 May 2024 11:50:02 -0400 Message-ID: Subject: Re: [PATCH v2 7/8] dts: rework interactive shells To: Luca Vizzarro Cc: dev@dpdk.org, =?UTF-8?Q?Juraj_Linke=C5=A1?= , Jeremy Spewock , Paul Szczepanek Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable X-BeenThere: dev@dpdk.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: DPDK patches and discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dev-bounces@dpdk.org Tested-by: Nicholas Pratte Reviewed-by: Nicholas Pratte On Thu, May 9, 2024 at 7:21=E2=80=AFAM Luca Vizzarro wrote: > > The way nodes and interactive shells interact makes it difficult to > develop for static type checking and hinting. The current system relies > on a top-down approach, attempting to give a generic interface to the > test developer, hiding the interaction of concrete shell classes as much > as possible. When working with strong typing this approach is not ideal, > as Python's implementation of generics is still rudimentary. > > This rework reverses the tests interaction to a bottom-up approach, > allowing the test developer to call concrete shell classes directly, > and let them ingest nodes independently. While also re-enforcing type > checking and making the code easier to read. > > Signed-off-by: Luca Vizzarro > Reviewed-by: Paul Szczepanek > --- > dts/framework/params/eal.py | 6 +- > dts/framework/remote_session/dpdk_shell.py | 104 ++++++++++++++++ > .../remote_session/interactive_shell.py | 75 +++++++----- > dts/framework/remote_session/python_shell.py | 4 +- > dts/framework/remote_session/testpmd_shell.py | 64 +++++----- > dts/framework/testbed_model/node.py | 36 +----- > dts/framework/testbed_model/os_session.py | 36 +----- > dts/framework/testbed_model/sut_node.py | 112 +----------------- > .../testbed_model/traffic_generator/scapy.py | 4 +- > dts/tests/TestSuite_hello_world.py | 7 +- > dts/tests/TestSuite_pmd_buffer_scatter.py | 21 ++-- > dts/tests/TestSuite_smoke_tests.py | 2 +- > 12 files changed, 201 insertions(+), 270 deletions(-) > create mode 100644 dts/framework/remote_session/dpdk_shell.py > > diff --git a/dts/framework/params/eal.py b/dts/framework/params/eal.py > index bbdbc8f334..8d7766fefc 100644 > --- a/dts/framework/params/eal.py > +++ b/dts/framework/params/eal.py > @@ -35,9 +35,9 @@ class EalParams(Params): > ``other_eal_param=3D'--single-file-segments'`` > """ > > - lcore_list: LogicalCoreList =3D field(metadata=3DParams.short("l")) > - memory_channels: int =3D field(metadata=3DParams.short("n")) > - prefix: str =3D field(metadata=3DParams.long("file-prefix")) > + lcore_list: LogicalCoreList | None =3D field(default=3DNone, metadat= a=3DParams.short("l")) > + memory_channels: int | None =3D field(default=3DNone, metadata=3DPar= ams.short("n")) > + prefix: str =3D field(default=3D"dpdk", metadata=3DParams.long("file= -prefix")) > no_pci: Switch =3D None > vdevs: list[VirtualDevice] | None =3D field( > default=3DNone, metadata=3DParams.multiple() | Params.long("vdev= ") > diff --git a/dts/framework/remote_session/dpdk_shell.py b/dts/framework/r= emote_session/dpdk_shell.py > new file mode 100644 > index 0000000000..78caae36ea > --- /dev/null > +++ b/dts/framework/remote_session/dpdk_shell.py > @@ -0,0 +1,104 @@ > +# SPDX-License-Identifier: BSD-3-Clause > +# Copyright(c) 2024 Arm Limited > + > +"""DPDK-based interactive shell. > + > +Provides a base class to create interactive shells based on DPDK. > +""" > + > + > +from abc import ABC > + > +from framework.params.eal import EalParams > +from framework.remote_session.interactive_shell import InteractiveShell > +from framework.settings import SETTINGS > +from framework.testbed_model.cpu import LogicalCoreCount, LogicalCoreLis= t > +from framework.testbed_model.sut_node import SutNode > + > + > +def compute_eal_params( > + node: SutNode, > + params: EalParams | None =3D None, > + lcore_filter_specifier: LogicalCoreCount | LogicalCoreList =3D Logic= alCoreCount(), > + ascending_cores: bool =3D True, > + append_prefix_timestamp: bool =3D True, > +) -> EalParams: > + """Compute EAL parameters based on the node's specifications. > + > + Args: > + node: The SUT node to compute the values for. > + params: The EalParams object to amend, if set to None a new obje= ct is created and returned. > + lcore_filter_specifier: A number of lcores/cores/sockets to use > + or a list of lcore ids to use. > + The default will select one lcore for each of two cores > + on one socket, in ascending order of core ids. > + ascending_cores: Sort cores in ascending order (lowest to highes= t IDs). > + If :data:`False`, sort in descending order. > + append_prefix_timestamp: If :data:`True`, will append a timestam= p to DPDK file prefix. > + """ > + if params is None: > + params =3D EalParams() > + > + if params.lcore_list is None: > + params.lcore_list =3D LogicalCoreList( > + node.filter_lcores(lcore_filter_specifier, ascending_cores) > + ) > + > + prefix =3D params.prefix > + if append_prefix_timestamp: > + prefix =3D f"{prefix}_{node._dpdk_timestamp}" > + prefix =3D node.main_session.get_dpdk_file_prefix(prefix) > + if prefix: > + node._dpdk_prefix_list.append(prefix) > + params.prefix =3D prefix > + > + if params.ports is None: > + params.ports =3D node.ports > + > + return params > + > + > +class DPDKShell(InteractiveShell, ABC): > + """The base class for managing DPDK-based interactive shells. > + > + This class shouldn't be instantiated directly, but instead be extend= ed. > + It automatically injects computed EAL parameters based on the node i= n the > + supplied app parameters. > + """ > + > + _node: SutNode > + _app_params: EalParams > + > + _lcore_filter_specifier: LogicalCoreCount | LogicalCoreList > + _ascending_cores: bool > + _append_prefix_timestamp: bool > + > + def __init__( > + self, > + node: SutNode, > + app_params: EalParams, > + privileged: bool =3D True, > + timeout: float =3D SETTINGS.timeout, > + lcore_filter_specifier: LogicalCoreCount | LogicalCoreList =3D L= ogicalCoreCount(), > + ascending_cores: bool =3D True, > + append_prefix_timestamp: bool =3D True, > + start_on_init: bool =3D True, > + ) -> None: > + """Overrides :meth:`~.interactive_shell.InteractiveShell.__init_= _`.""" > + self._lcore_filter_specifier =3D lcore_filter_specifier > + self._ascending_cores =3D ascending_cores > + self._append_prefix_timestamp =3D append_prefix_timestamp > + > + super().__init__(node, app_params, privileged, timeout, start_on= _init) > + > + def __post_init__(self): > + """Computes EAL params based on the node capabilities before sta= rt.""" > + self._app_params =3D compute_eal_params( > + self._node, > + self._app_params, > + self._lcore_filter_specifier, > + self._ascending_cores, > + self._append_prefix_timestamp, > + ) > + > + self._update_path(self._node.remote_dpdk_build_dir.joinpath(self= .path)) > diff --git a/dts/framework/remote_session/interactive_shell.py b/dts/fram= ework/remote_session/interactive_shell.py > index 9da66d1c7e..8163c8f247 100644 > --- a/dts/framework/remote_session/interactive_shell.py > +++ b/dts/framework/remote_session/interactive_shell.py > @@ -17,13 +17,14 @@ > > from abc import ABC > from pathlib import PurePath > -from typing import Callable, ClassVar > +from typing import ClassVar > > -from paramiko import Channel, SSHClient, channel # type: ignore[import-= untyped] > +from paramiko import Channel, channel # type: ignore[import-untyped] > > from framework.logger import DTSLogger > from framework.params import Params > from framework.settings import SETTINGS > +from framework.testbed_model.node import Node > > > class InteractiveShell(ABC): > @@ -36,13 +37,14 @@ class InteractiveShell(ABC): > session. > """ > > - _interactive_session: SSHClient > + _node: Node > _stdin: channel.ChannelStdinFile > _stdout: channel.ChannelFile > _ssh_channel: Channel > _logger: DTSLogger > _timeout: float > _app_params: Params > + _privileged: bool > > #: Prompt to expect at the end of output when sending a command. > #: This is often overridden by subclasses. > @@ -56,57 +58,66 @@ class InteractiveShell(ABC): > #: Path to the executable to start the interactive application. > path: ClassVar[PurePath] > > - #: Whether this application is a DPDK app. If it is, the build direc= tory > - #: for DPDK on the node will be prepended to the path to the executa= ble. > - dpdk_app: ClassVar[bool] =3D False > - > def __init__( > self, > - interactive_session: SSHClient, > - logger: DTSLogger, > - get_privileged_command: Callable[[str], str] | None, > + node: Node, > app_params: Params =3D Params(), > + privileged: bool =3D False, > timeout: float =3D SETTINGS.timeout, > + start_on_init: bool =3D True, > ) -> None: > """Create an SSH channel during initialization. > > Args: > - interactive_session: The SSH session dedicated to interactiv= e shells. > - logger: The logger instance this session will use. > - get_privileged_command: A method for modifying a command to = allow it to use > - elevated privileges. If :data:`None`, the application wi= ll not be started > - with elevated privileges. > + node: The node on which to run start the interactive shell. > app_params: The command line parameters to be passed to the = application on startup. > + privileged: Enables the shell to run as superuser. > timeout: The timeout used for the SSH channel that is dedica= ted to this interactive > shell. This timeout is for collecting output, so if read= ing from the buffer > and no output is gathered within the timeout, an excepti= on is thrown. > + start_on_init: Start interactive shell automatically after o= bject initialisation. > """ > - self._interactive_session =3D interactive_session > - self._ssh_channel =3D self._interactive_session.invoke_shell() > + self._node =3D node > + self._logger =3D node._logger > + self._app_params =3D app_params > + self._privileged =3D privileged > + self._timeout =3D timeout > + # Ensure path is properly formatted for the host > + self._update_path(self._node.main_session.join_remote_path(self.= path)) > + > + self.__post_init__() > + > + if start_on_init: > + self.start_application() > + > + def __post_init__(self): > + """Overridable. Method called after the object init and before a= pplication start.""" > + pass > + > + def _setup_ssh_channel(self): > + self._ssh_channel =3D self._node.main_session.interactive_sessio= n.session.invoke_shell() > self._stdin =3D self._ssh_channel.makefile_stdin("w") > self._stdout =3D self._ssh_channel.makefile("r") > - self._ssh_channel.settimeout(timeout) > + self._ssh_channel.settimeout(self._timeout) > self._ssh_channel.set_combine_stderr(True) # combines stdout an= d stderr streams > - self._logger =3D logger > - self._timeout =3D timeout > - self._app_params =3D app_params > - self._start_application(get_privileged_command) > > - def _start_application(self, get_privileged_command: Callable[[str],= str] | None) -> None: > + def start_application(self) -> None: > """Starts a new interactive application based on the path to the= app. > > This method is often overridden by subclasses as their process f= or > starting may look different. > - > - Args: > - get_privileged_command: A function (but could be any callabl= e) that produces > - the version of the command with elevated privileges. > """ > - start_command =3D f"{self.path} {self._app_params}" > - if get_privileged_command is not None: > - start_command =3D get_privileged_command(start_command) > + self._setup_ssh_channel() > + > + start_command =3D self._make_start_command() > + if self._privileged: > + start_command =3D self._node.main_session._get_privileged_co= mmand(start_command) > self.send_command(start_command) > > + def _make_start_command(self) -> str: > + """Makes the command that starts the interactive shell.""" > + return f"{self.path} {self._app_params or ''}" > + > def send_command(self, command: str, prompt: str | None =3D None) ->= str: > """Send `command` and get all output before the expected ending = string. > > @@ -150,3 +161,7 @@ def close(self) -> None: > def __del__(self) -> None: > """Make sure the session is properly closed before deleting the = object.""" > self.close() > + > + @classmethod > + def _update_path(cls, path: PurePath) -> None: > + cls.path =3D path > diff --git a/dts/framework/remote_session/python_shell.py b/dts/framework= /remote_session/python_shell.py > index ccfd3783e8..953ed100df 100644 > --- a/dts/framework/remote_session/python_shell.py > +++ b/dts/framework/remote_session/python_shell.py > @@ -6,9 +6,7 @@ > Typical usage example in a TestSuite:: > > from framework.remote_session import PythonShell > - python_shell =3D self.tg_node.create_interactive_shell( > - PythonShell, timeout=3D5, privileged=3DTrue > - ) > + python_shell =3D PythonShell(self.tg_node, timeout=3D5, privileged= =3DTrue) > python_shell.send_command("print('Hello World')") > python_shell.close() > """ > diff --git a/dts/framework/remote_session/testpmd_shell.py b/dts/framewor= k/remote_session/testpmd_shell.py > index ef3f23c582..92930d7fbb 100644 > --- a/dts/framework/remote_session/testpmd_shell.py > +++ b/dts/framework/remote_session/testpmd_shell.py > @@ -7,9 +7,7 @@ > > Typical usage example in a TestSuite:: > > - testpmd_shell =3D self.sut_node.create_interactive_shell( > - TestPmdShell, privileged=3DTrue > - ) > + testpmd_shell =3D TestPmdShell() > devices =3D testpmd_shell.get_devices() > for device in devices: > print(device) > @@ -18,13 +16,14 @@ > > import time > from pathlib import PurePath > -from typing import Callable, ClassVar > +from typing import ClassVar > > from framework.exception import InteractiveCommandExecutionError > from framework.params.testpmd import SimpleForwardingModes, TestPmdParam= s > +from framework.remote_session.dpdk_shell import DPDKShell > from framework.settings import SETTINGS > - > -from .interactive_shell import InteractiveShell > +from framework.testbed_model.cpu import LogicalCoreCount, LogicalCoreLis= t > +from framework.testbed_model.sut_node import SutNode > > > class TestPmdDevice(object): > @@ -49,52 +48,48 @@ def __str__(self) -> str: > return self.pci_address > > > -class TestPmdShell(InteractiveShell): > +class TestPmdShell(DPDKShell): > """Testpmd interactive shell. > > The testpmd shell users should never use > the :meth:`~.interactive_shell.InteractiveShell.send_command` method= directly, but rather > call specialized methods. If there isn't one that satisfies a need, = it should be added. > - > - Attributes: > - number_of_ports: The number of ports which were allowed on the c= ommand-line when testpmd > - was started. > """ > > - number_of_ports: int > + _app_params: TestPmdParams > > #: The path to the testpmd executable. > path: ClassVar[PurePath] =3D PurePath("app", "dpdk-testpmd") > > - #: Flag this as a DPDK app so that it's clear this is not a system a= pp and > - #: needs to be looked in a specific path. > - dpdk_app: ClassVar[bool] =3D True > - > #: The testpmd's prompt. > _default_prompt: ClassVar[str] =3D "testpmd>" > > #: This forces the prompt to appear after sending a command. > _command_extra_chars: ClassVar[str] =3D "\n" > > - def _start_application(self, get_privileged_command: Callable[[str],= str] | None) -> None: > - """Overrides :meth:`~.interactive_shell._start_application`. > - > - Add flags for starting testpmd in interactive mode and disabling= messages for link state > - change events before starting the application. Link state is ver= ified before starting > - packet forwarding and the messages create unexpected newlines in= the terminal which > - complicates output collection. > - > - Also find the number of pci addresses which were allowed on the = command line when the app > - was started. > - """ > - assert isinstance(self._app_params, TestPmdParams) > - > - self.number_of_ports =3D ( > - len(self._app_params.ports) if self._app_params.ports is not= None else 0 > + def __init__( > + self, > + node: SutNode, > + privileged: bool =3D True, > + timeout: float =3D SETTINGS.timeout, > + lcore_filter_specifier: LogicalCoreCount | LogicalCoreList =3D L= ogicalCoreCount(), > + ascending_cores: bool =3D True, > + append_prefix_timestamp: bool =3D True, > + start_on_init: bool =3D True, > + **app_params, > + ) -> None: > + """Overrides :meth:`~.dpdk_shell.DPDKShell.__init__`. Changes ap= p_params to kwargs.""" > + super().__init__( > + node, > + TestPmdParams(**app_params), > + privileged, > + timeout, > + lcore_filter_specifier, > + ascending_cores, > + append_prefix_timestamp, > + start_on_init, > ) > > - super()._start_application(get_privileged_command) > - > def start(self, verify: bool =3D True) -> None: > """Start packet forwarding with the current configuration. > > @@ -114,7 +109,8 @@ def start(self, verify: bool =3D True) -> None: > self._logger.debug(f"Failed to start packet forwarding: = \n{start_cmd_output}") > raise InteractiveCommandExecutionError("Testpmd failed t= o start packet forwarding.") > > - for port_id in range(self.number_of_ports): > + number_of_ports =3D len(self._app_params.ports or []) > + for port_id in range(number_of_ports): > if not self.wait_link_status_up(port_id): > raise InteractiveCommandExecutionError( > "Not all ports came up after starting packet for= warding in testpmd." > diff --git a/dts/framework/testbed_model/node.py b/dts/framework/testbed_= model/node.py > index 6af4f25a3c..88395faabe 100644 > --- a/dts/framework/testbed_model/node.py > +++ b/dts/framework/testbed_model/node.py > @@ -15,7 +15,7 @@ > > from abc import ABC > from ipaddress import IPv4Interface, IPv6Interface > -from typing import Any, Callable, Type, Union > +from typing import Any, Callable, Union > > from framework.config import ( > OS, > @@ -25,7 +25,6 @@ > ) > from framework.exception import ConfigurationError > from framework.logger import DTSLogger, get_dts_logger > -from framework.params import Params > from framework.settings import SETTINGS > > from .cpu import ( > @@ -36,7 +35,7 @@ > lcore_filter, > ) > from .linux_session import LinuxSession > -from .os_session import InteractiveShellType, OSSession > +from .os_session import OSSession > from .port import Port > from .virtual_device import VirtualDevice > > @@ -196,37 +195,6 @@ def create_session(self, name: str) -> OSSession: > self._other_sessions.append(connection) > return connection > > - def create_interactive_shell( > - self, > - shell_cls: Type[InteractiveShellType], > - timeout: float =3D SETTINGS.timeout, > - privileged: bool =3D False, > - app_params: Params =3D Params(), > - ) -> InteractiveShellType: > - """Factory for interactive session handlers. > - > - Instantiate `shell_cls` according to the remote OS specifics. > - > - Args: > - shell_cls: The class of the shell. > - timeout: Timeout for reading output from the SSH channel. If= you are reading from > - the buffer and don't receive any data within the timeout= it will throw an error. > - privileged: Whether to run the shell with administrative pri= vileges. > - app_args: The arguments to be passed to the application. > - > - Returns: > - An instance of the desired interactive application shell. > - """ > - if not shell_cls.dpdk_app: > - shell_cls.path =3D self.main_session.join_remote_path(shell_= cls.path) > - > - return self.main_session.create_interactive_shell( > - shell_cls, > - timeout, > - privileged, > - app_params, > - ) > - > def filter_lcores( > self, > filter_specifier: LogicalCoreCount | LogicalCoreList, > diff --git a/dts/framework/testbed_model/os_session.py b/dts/framework/te= stbed_model/os_session.py > index e5f5fcbe0e..e7e6c9d670 100644 > --- a/dts/framework/testbed_model/os_session.py > +++ b/dts/framework/testbed_model/os_session.py > @@ -26,18 +26,16 @@ > from collections.abc import Iterable > from ipaddress import IPv4Interface, IPv6Interface > from pathlib import PurePath > -from typing import Type, TypeVar, Union > +from typing import Union > > from framework.config import Architecture, NodeConfiguration, NodeInfo > from framework.logger import DTSLogger > -from framework.params import Params > from framework.remote_session import ( > InteractiveRemoteSession, > RemoteSession, > create_interactive_session, > create_remote_session, > ) > -from framework.remote_session.interactive_shell import InteractiveShell > from framework.remote_session.remote_session import CommandResult > from framework.settings import SETTINGS > from framework.utils import MesonArgs > @@ -45,8 +43,6 @@ > from .cpu import LogicalCore > from .port import Port > > -InteractiveShellType =3D TypeVar("InteractiveShellType", bound=3DInterac= tiveShell) > - > > class OSSession(ABC): > """OS-unaware to OS-aware translation API definition. > @@ -131,36 +127,6 @@ def send_command( > > return self.remote_session.send_command(command, timeout, verify= , env) > > - def create_interactive_shell( > - self, > - shell_cls: Type[InteractiveShellType], > - timeout: float, > - privileged: bool, > - app_args: Params, > - ) -> InteractiveShellType: > - """Factory for interactive session handlers. > - > - Instantiate `shell_cls` according to the remote OS specifics. > - > - Args: > - shell_cls: The class of the shell. > - timeout: Timeout for reading output from the SSH channel. If= you are > - reading from the buffer and don't receive any data withi= n the timeout > - it will throw an error. > - privileged: Whether to run the shell with administrative pri= vileges. > - app_args: The arguments to be passed to the application. > - > - Returns: > - An instance of the desired interactive application shell. > - """ > - return shell_cls( > - self.interactive_session.session, > - self._logger, > - self._get_privileged_command if privileged else None, > - app_args, > - timeout, > - ) > - > @staticmethod > @abstractmethod > def _get_privileged_command(command: str) -> str: > diff --git a/dts/framework/testbed_model/sut_node.py b/dts/framework/test= bed_model/sut_node.py > index 83ad06ae2d..727170b7fc 100644 > --- a/dts/framework/testbed_model/sut_node.py > +++ b/dts/framework/testbed_model/sut_node.py > @@ -16,7 +16,6 @@ > import tarfile > import time > from pathlib import PurePath > -from typing import Type > > from framework.config import ( > BuildTargetConfiguration, > @@ -24,17 +23,13 @@ > NodeInfo, > SutNodeConfiguration, > ) > -from framework.params import Params, Switch > from framework.params.eal import EalParams > from framework.remote_session.remote_session import CommandResult > from framework.settings import SETTINGS > from framework.utils import MesonArgs > > -from .cpu import LogicalCoreCount, LogicalCoreList > from .node import Node > -from .os_session import InteractiveShellType, OSSession > -from .port import Port > -from .virtual_device import VirtualDevice > +from .os_session import OSSession > > > class SutNode(Node): > @@ -289,68 +284,6 @@ def kill_cleanup_dpdk_apps(self) -> None: > self._dpdk_kill_session =3D self.create_session("dpdk_kill") > self._dpdk_prefix_list =3D [] > > - def create_eal_parameters( > - self, > - lcore_filter_specifier: LogicalCoreCount | LogicalCoreList =3D L= ogicalCoreCount(), > - ascending_cores: bool =3D True, > - prefix: str =3D "dpdk", > - append_prefix_timestamp: bool =3D True, > - no_pci: Switch =3D None, > - vdevs: list[VirtualDevice] | None =3D None, > - ports: list[Port] | None =3D None, > - other_eal_param: str =3D "", > - ) -> EalParams: > - """Compose the EAL parameters. > - > - Process the list of cores and the DPDK prefix and pass that alon= g with > - the rest of the arguments. > - > - Args: > - lcore_filter_specifier: A number of lcores/cores/sockets to = use > - or a list of lcore ids to use. > - The default will select one lcore for each of two cores > - on one socket, in ascending order of core ids. > - ascending_cores: Sort cores in ascending order (lowest to hi= ghest IDs). > - If :data:`False`, sort in descending order. > - prefix: Set the file prefix string with which to start DPDK,= e.g.: ``prefix=3D'vf'``. > - append_prefix_timestamp: If :data:`True`, will append a time= stamp to DPDK file prefix. > - no_pci: Switch to disable PCI bus e.g.: ``no_pci=3DTrue``. > - vdevs: Virtual devices, e.g.:: > - > - vdevs=3D[ > - VirtualDevice('net_ring0'), > - VirtualDevice('net_ring1') > - ] > - ports: The list of ports to allow. If :data:`None`, all port= s listed in `self.ports` > - will be allowed. > - other_eal_param: user defined DPDK EAL parameters, e.g.: > - ``other_eal_param=3D'--single-file-segments'``. > - > - Returns: > - An EAL param string, such as > - ``-c 0xf -a 0000:88:00.0 --file-prefix=3Ddpdk_1112_201908091= 43420``. > - """ > - lcore_list =3D LogicalCoreList(self.filter_lcores(lcore_filter_s= pecifier, ascending_cores)) > - > - if append_prefix_timestamp: > - prefix =3D f"{prefix}_{self._dpdk_timestamp}" > - prefix =3D self.main_session.get_dpdk_file_prefix(prefix) > - if prefix: > - self._dpdk_prefix_list.append(prefix) > - > - if ports is None: > - ports =3D self.ports > - > - return EalParams( > - lcore_list=3Dlcore_list, > - memory_channels=3Dself.config.memory_channels, > - prefix=3Dprefix, > - no_pci=3Dno_pci, > - vdevs=3Dvdevs, > - ports=3Dports, > - other_eal_param=3DParams.from_str(other_eal_param), > - ) > - > def run_dpdk_app( > self, app_path: PurePath, eal_params: EalParams, timeout: float = =3D 30 > ) -> CommandResult: > @@ -379,49 +312,6 @@ def configure_ipv4_forwarding(self, enable: bool) ->= None: > """ > self.main_session.configure_ipv4_forwarding(enable) > > - def create_interactive_shell( > - self, > - shell_cls: Type[InteractiveShellType], > - timeout: float =3D SETTINGS.timeout, > - privileged: bool =3D False, > - app_params: Params =3D Params(), > - eal_params: EalParams | None =3D None, > - ) -> InteractiveShellType: > - """Extend the factory for interactive session handlers. > - > - The extensions are SUT node specific: > - > - * The default for `eal_parameters`, > - * The interactive shell path `shell_cls.path` is prepended w= ith path to the remote > - DPDK build directory for DPDK apps. > - > - Args: > - shell_cls: The class of the shell. > - timeout: Timeout for reading output from the SSH channel. If= you are > - reading from the buffer and don't receive any data withi= n the timeout > - it will throw an error. > - privileged: Whether to run the shell with administrative pri= vileges. > - app_params: The parameters to be passed to the application. > - eal_params: List of EAL parameters to use to launch the app.= If this > - isn't provided or an empty string is passed, it will def= ault to calling > - :meth:`create_eal_parameters`. > - > - Returns: > - An instance of the desired interactive application shell. > - """ > - # We need to append the build directory and add EAL parameters f= or DPDK apps > - if shell_cls.dpdk_app: > - if eal_params is None: > - eal_params =3D self.create_eal_parameters() > - eal_params.append_str(str(app_params)) > - app_params =3D eal_params > - > - shell_cls.path =3D self.main_session.join_remote_path( > - self.remote_dpdk_build_dir, shell_cls.path > - ) > - > - return super().create_interactive_shell(shell_cls, timeout, priv= ileged, app_params) > - > def bind_ports_to_driver(self, for_dpdk: bool =3D True) -> None: > """Bind all ports on the SUT to a driver. > > diff --git a/dts/framework/testbed_model/traffic_generator/scapy.py b/dts= /framework/testbed_model/traffic_generator/scapy.py > index 7bc1c2cc08..bf58ad1c5e 100644 > --- a/dts/framework/testbed_model/traffic_generator/scapy.py > +++ b/dts/framework/testbed_model/traffic_generator/scapy.py > @@ -217,9 +217,7 @@ def __init__(self, tg_node: Node, config: ScapyTraffi= cGeneratorConfig): > self._tg_node.config.os =3D=3D OS.linux > ), "Linux is the only supported OS for scapy traffic generation" > > - self.session =3D self._tg_node.create_interactive_shell( > - PythonShell, timeout=3D5, privileged=3DTrue > - ) > + self.session =3D PythonShell(self._tg_node, timeout=3D5, privile= ged=3DTrue) > > # import libs in remote python console > for import_statement in SCAPY_RPC_SERVER_IMPORTS: > diff --git a/dts/tests/TestSuite_hello_world.py b/dts/tests/TestSuite_hel= lo_world.py > index 0d6995f260..d958f99030 100644 > --- a/dts/tests/TestSuite_hello_world.py > +++ b/dts/tests/TestSuite_hello_world.py > @@ -7,6 +7,7 @@ > No other EAL parameters apart from cores are used. > """ > > +from framework.remote_session.dpdk_shell import compute_eal_params > from framework.test_suite import TestSuite > from framework.testbed_model.cpu import ( > LogicalCoreCount, > @@ -38,7 +39,7 @@ def test_hello_world_single_core(self) -> None: > # get the first usable core > lcore_amount =3D LogicalCoreCount(1, 1, 1) > lcores =3D LogicalCoreCountFilter(self.sut_node.lcores, lcore_am= ount).filter() > - eal_para =3D self.sut_node.create_eal_parameters(lcore_filter_sp= ecifier=3Dlcore_amount) > + eal_para =3D compute_eal_params(self.sut_node, lcore_filter_spec= ifier=3Dlcore_amount) > result =3D self.sut_node.run_dpdk_app(self.app_helloworld_path, = eal_para) > self.verify( > f"hello from core {int(lcores[0])}" in result.stdout, > @@ -55,8 +56,8 @@ def test_hello_world_all_cores(self) -> None: > "hello from core " > """ > # get the maximum logical core number > - eal_para =3D self.sut_node.create_eal_parameters( > - lcore_filter_specifier=3DLogicalCoreList(self.sut_node.lcore= s) > + eal_para =3D compute_eal_params( > + self.sut_node, lcore_filter_specifier=3DLogicalCoreList(self= .sut_node.lcores) > ) > result =3D self.sut_node.run_dpdk_app(self.app_helloworld_path, = eal_para, 50) > for lcore in self.sut_node.lcores: > diff --git a/dts/tests/TestSuite_pmd_buffer_scatter.py b/dts/tests/TestSu= ite_pmd_buffer_scatter.py > index 6d206c1a40..43cf5c61eb 100644 > --- a/dts/tests/TestSuite_pmd_buffer_scatter.py > +++ b/dts/tests/TestSuite_pmd_buffer_scatter.py > @@ -16,14 +16,13 @@ > """ > > import struct > -from dataclasses import asdict > > from scapy.layers.inet import IP # type: ignore[import-untyped] > from scapy.layers.l2 import Ether # type: ignore[import-untyped] > from scapy.packet import Raw # type: ignore[import-untyped] > from scapy.utils import hexstr # type: ignore[import-untyped] > > -from framework.params.testpmd import SimpleForwardingModes, TestPmdParam= s > +from framework.params.testpmd import SimpleForwardingModes > from framework.remote_session.testpmd_shell import TestPmdShell > from framework.test_suite import TestSuite > > @@ -103,17 +102,13 @@ def pmd_scatter(self, mbsize: int) -> None: > Test: > Start testpmd and run functional test with preset mbsize. > """ > - testpmd =3D self.sut_node.create_interactive_shell( > - TestPmdShell, > - app_params=3DTestPmdParams( > - forward_mode=3DSimpleForwardingModes.mac, > - mbcache=3D200, > - mbuf_size=3D[mbsize], > - max_pkt_len=3D9000, > - tx_offloads=3D0x00008000, > - **asdict(self.sut_node.create_eal_parameters()), > - ), > - privileged=3DTrue, > + testpmd =3D TestPmdShell( > + self.sut_node, > + forward_mode=3DSimpleForwardingModes.mac, > + mbcache=3D200, > + mbuf_size=3D[mbsize], > + max_pkt_len=3D9000, > + tx_offloads=3D0x00008000, > ) > testpmd.start() > > diff --git a/dts/tests/TestSuite_smoke_tests.py b/dts/tests/TestSuite_smo= ke_tests.py > index ca678f662d..eca27acfd8 100644 > --- a/dts/tests/TestSuite_smoke_tests.py > +++ b/dts/tests/TestSuite_smoke_tests.py > @@ -99,7 +99,7 @@ def test_devices_listed_in_testpmd(self) -> None: > Test: > List all devices found in testpmd and verify the configured = devices are among them. > """ > - testpmd_driver =3D self.sut_node.create_interactive_shell(TestPm= dShell, privileged=3DTrue) > + testpmd_driver =3D TestPmdShell(self.sut_node) > dev_list =3D [str(x) for x in testpmd_driver.get_devices()] > for nic in self.nics_in_node: > self.verify( > -- > 2.34.1 >