Tested-by: Nicholas Pratte <npra...@iol.unh.edu>
Reviewed-by: Nicholas Pratte <npra...@iol.unh.edu>

On Thu, May 9, 2024 at 7:21 AM Luca Vizzarro <luca.vizza...@arm.com> 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 <luca.vizza...@arm.com>
> Reviewed-by: Paul Szczepanek <paul.szczepa...@arm.com>
> ---
>  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='--single-file-segments'``
>      """
>
> -    lcore_list: LogicalCoreList = field(metadata=Params.short("l"))
> -    memory_channels: int = field(metadata=Params.short("n"))
> -    prefix: str = field(metadata=Params.long("file-prefix"))
> +    lcore_list: LogicalCoreList | None = field(default=None, 
> metadata=Params.short("l"))
> +    memory_channels: int | None = field(default=None, 
> metadata=Params.short("n"))
> +    prefix: str = field(default="dpdk", metadata=Params.long("file-prefix"))
>      no_pci: Switch = None
>      vdevs: list[VirtualDevice] | None = field(
>          default=None, metadata=Params.multiple() | Params.long("vdev")
> diff --git a/dts/framework/remote_session/dpdk_shell.py 
> b/dts/framework/remote_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, LogicalCoreList
> +from framework.testbed_model.sut_node import SutNode
> +
> +
> +def compute_eal_params(
> +    node: SutNode,
> +    params: EalParams | None = None,
> +    lcore_filter_specifier: LogicalCoreCount | LogicalCoreList = 
> LogicalCoreCount(),
> +    ascending_cores: bool = True,
> +    append_prefix_timestamp: bool = 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 object 
> 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 highest 
> IDs).
> +            If :data:`False`, sort in descending order.
> +        append_prefix_timestamp: If :data:`True`, will append a timestamp to 
> DPDK file prefix.
> +    """
> +    if params is None:
> +        params = EalParams()
> +
> +    if params.lcore_list is None:
> +        params.lcore_list = LogicalCoreList(
> +            node.filter_lcores(lcore_filter_specifier, ascending_cores)
> +        )
> +
> +    prefix = params.prefix
> +    if append_prefix_timestamp:
> +        prefix = f"{prefix}_{node._dpdk_timestamp}"
> +    prefix = node.main_session.get_dpdk_file_prefix(prefix)
> +    if prefix:
> +        node._dpdk_prefix_list.append(prefix)
> +    params.prefix = prefix
> +
> +    if params.ports is None:
> +        params.ports = 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 extended.
> +    It automatically injects computed EAL parameters based on the node in 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 = True,
> +        timeout: float = SETTINGS.timeout,
> +        lcore_filter_specifier: LogicalCoreCount | LogicalCoreList = 
> LogicalCoreCount(),
> +        ascending_cores: bool = True,
> +        append_prefix_timestamp: bool = True,
> +        start_on_init: bool = True,
> +    ) -> None:
> +        """Overrides 
> :meth:`~.interactive_shell.InteractiveShell.__init__`."""
> +        self._lcore_filter_specifier = lcore_filter_specifier
> +        self._ascending_cores = ascending_cores
> +        self._append_prefix_timestamp = 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 
> start."""
> +        self._app_params = 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/framework/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 directory
> -    #: for DPDK on the node will be prepended to the path to the executable.
> -    dpdk_app: ClassVar[bool] = False
> -
>      def __init__(
>          self,
> -        interactive_session: SSHClient,
> -        logger: DTSLogger,
> -        get_privileged_command: Callable[[str], str] | None,
> +        node: Node,
>          app_params: Params = Params(),
> +        privileged: bool = False,
>          timeout: float = SETTINGS.timeout,
> +        start_on_init: bool = True,
>      ) -> None:
>          """Create an SSH channel during initialization.
>
>          Args:
> -            interactive_session: The SSH session dedicated to interactive 
> 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 will 
> 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 dedicated 
> to this interactive
>                  shell. This timeout is for collecting output, so if reading 
> from the buffer
>                  and no output is gathered within the timeout, an exception 
> is thrown.
> +            start_on_init: Start interactive shell automatically after 
> object initialisation.
>          """
> -        self._interactive_session = interactive_session
> -        self._ssh_channel = self._interactive_session.invoke_shell()
> +        self._node = node
> +        self._logger = node._logger
> +        self._app_params = app_params
> +        self._privileged = privileged
> +        self._timeout = 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 
> application start."""
> +        pass
> +
> +    def _setup_ssh_channel(self):
> +        self._ssh_channel = 
> self._node.main_session.interactive_session.session.invoke_shell()
>          self._stdin = self._ssh_channel.makefile_stdin("w")
>          self._stdout = 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 and 
> stderr streams
> -        self._logger = logger
> -        self._timeout = timeout
> -        self._app_params = 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 for
>          starting may look different.
> -
> -        Args:
> -            get_privileged_command: A function (but could be any callable) 
> that produces
> -                the version of the command with elevated privileges.
>          """
> -        start_command = f"{self.path} {self._app_params}"
> -        if get_privileged_command is not None:
> -            start_command = get_privileged_command(start_command)
> +        self._setup_ssh_channel()
> +
> +        start_command = self._make_start_command()
> +        if self._privileged:
> +            start_command = 
> self._node.main_session._get_privileged_command(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 = 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 = 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 = self.tg_node.create_interactive_shell(
> -        PythonShell, timeout=5, privileged=True
> -    )
> +    python_shell = PythonShell(self.tg_node, timeout=5, privileged=True)
>      python_shell.send_command("print('Hello World')")
>      python_shell.close()
>  """
> diff --git a/dts/framework/remote_session/testpmd_shell.py 
> b/dts/framework/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 = self.sut_node.create_interactive_shell(
> -            TestPmdShell, privileged=True
> -        )
> +    testpmd_shell = TestPmdShell()
>      devices = 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, TestPmdParams
> +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, LogicalCoreList
> +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 
> command-line when testpmd
> -            was started.
>      """
>
> -    number_of_ports: int
> +    _app_params: TestPmdParams
>
>      #: The path to the testpmd executable.
>      path: ClassVar[PurePath] = PurePath("app", "dpdk-testpmd")
>
> -    #: Flag this as a DPDK app so that it's clear this is not a system app 
> and
> -    #: needs to be looked in a specific path.
> -    dpdk_app: ClassVar[bool] = True
> -
>      #: The testpmd's prompt.
>      _default_prompt: ClassVar[str] = "testpmd>"
>
>      #: This forces the prompt to appear after sending a command.
>      _command_extra_chars: ClassVar[str] = "\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 
> verified 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 = (
> -            len(self._app_params.ports) if self._app_params.ports is not 
> None else 0
> +    def __init__(
> +        self,
> +        node: SutNode,
> +        privileged: bool = True,
> +        timeout: float = SETTINGS.timeout,
> +        lcore_filter_specifier: LogicalCoreCount | LogicalCoreList = 
> LogicalCoreCount(),
> +        ascending_cores: bool = True,
> +        append_prefix_timestamp: bool = True,
> +        start_on_init: bool = True,
> +        **app_params,
> +    ) -> None:
> +        """Overrides :meth:`~.dpdk_shell.DPDKShell.__init__`. Changes 
> app_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 = True) -> None:
>          """Start packet forwarding with the current configuration.
>
> @@ -114,7 +109,8 @@ def start(self, verify: bool = True) -> None:
>                  self._logger.debug(f"Failed to start packet forwarding: 
> \n{start_cmd_output}")
>                  raise InteractiveCommandExecutionError("Testpmd failed to 
> start packet forwarding.")
>
> -            for port_id in range(self.number_of_ports):
> +            number_of_ports = 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 
> forwarding 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 = SETTINGS.timeout,
> -        privileged: bool = False,
> -        app_params: Params = 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 
> privileges.
> -            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 = 
> 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/testbed_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 = TypeVar("InteractiveShellType", 
> bound=InteractiveShell)
> -
>
>  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 within 
> the timeout
> -                it will throw an error.
> -            privileged: Whether to run the shell with administrative 
> privileges.
> -            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/testbed_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 = self.create_session("dpdk_kill")
>          self._dpdk_prefix_list = []
>
> -    def create_eal_parameters(
> -        self,
> -        lcore_filter_specifier: LogicalCoreCount | LogicalCoreList = 
> LogicalCoreCount(),
> -        ascending_cores: bool = True,
> -        prefix: str = "dpdk",
> -        append_prefix_timestamp: bool = True,
> -        no_pci: Switch = None,
> -        vdevs: list[VirtualDevice] | None = None,
> -        ports: list[Port] | None = None,
> -        other_eal_param: str = "",
> -    ) -> EalParams:
> -        """Compose the EAL parameters.
> -
> -        Process the list of cores and the DPDK prefix and pass that along 
> 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 
> highest IDs).
> -                If :data:`False`, sort in descending order.
> -            prefix: Set the file prefix string with which to start DPDK, 
> e.g.: ``prefix='vf'``.
> -            append_prefix_timestamp: If :data:`True`, will append a 
> timestamp to DPDK file prefix.
> -            no_pci: Switch to disable PCI bus e.g.: ``no_pci=True``.
> -            vdevs: Virtual devices, e.g.::
> -
> -                vdevs=[
> -                    VirtualDevice('net_ring0'),
> -                    VirtualDevice('net_ring1')
> -                ]
> -            ports: The list of ports to allow. If :data:`None`, all ports 
> listed in `self.ports`
> -                will be allowed.
> -            other_eal_param: user defined DPDK EAL parameters, e.g.:
> -                ``other_eal_param='--single-file-segments'``.
> -
> -        Returns:
> -            An EAL param string, such as
> -            ``-c 0xf -a 0000:88:00.0 
> --file-prefix=dpdk_1112_20190809143420``.
> -        """
> -        lcore_list = 
> LogicalCoreList(self.filter_lcores(lcore_filter_specifier, ascending_cores))
> -
> -        if append_prefix_timestamp:
> -            prefix = f"{prefix}_{self._dpdk_timestamp}"
> -        prefix = self.main_session.get_dpdk_file_prefix(prefix)
> -        if prefix:
> -            self._dpdk_prefix_list.append(prefix)
> -
> -        if ports is None:
> -            ports = self.ports
> -
> -        return EalParams(
> -            lcore_list=lcore_list,
> -            memory_channels=self.config.memory_channels,
> -            prefix=prefix,
> -            no_pci=no_pci,
> -            vdevs=vdevs,
> -            ports=ports,
> -            other_eal_param=Params.from_str(other_eal_param),
> -        )
> -
>      def run_dpdk_app(
>          self, app_path: PurePath, eal_params: EalParams, timeout: float = 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 = SETTINGS.timeout,
> -        privileged: bool = False,
> -        app_params: Params = Params(),
> -        eal_params: EalParams | None = 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 with 
> 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 within 
> the timeout
> -                it will throw an error.
> -            privileged: Whether to run the shell with administrative 
> privileges.
> -            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 default 
> 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 for 
> DPDK apps
> -        if shell_cls.dpdk_app:
> -            if eal_params is None:
> -                eal_params = self.create_eal_parameters()
> -            eal_params.append_str(str(app_params))
> -            app_params = eal_params
> -
> -            shell_cls.path = self.main_session.join_remote_path(
> -                self.remote_dpdk_build_dir, shell_cls.path
> -            )
> -
> -        return super().create_interactive_shell(shell_cls, timeout, 
> privileged, app_params)
> -
>      def bind_ports_to_driver(self, for_dpdk: bool = 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: 
> ScapyTrafficGeneratorConfig):
>              self._tg_node.config.os == OS.linux
>          ), "Linux is the only supported OS for scapy traffic generation"
>
> -        self.session = self._tg_node.create_interactive_shell(
> -            PythonShell, timeout=5, privileged=True
> -        )
> +        self.session = PythonShell(self._tg_node, timeout=5, privileged=True)
>
>          # 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_hello_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 = LogicalCoreCount(1, 1, 1)
>          lcores = LogicalCoreCountFilter(self.sut_node.lcores, 
> lcore_amount).filter()
> -        eal_para = 
> self.sut_node.create_eal_parameters(lcore_filter_specifier=lcore_amount)
> +        eal_para = compute_eal_params(self.sut_node, 
> lcore_filter_specifier=lcore_amount)
>          result = 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 <core_id>"
>          """
>          # get the maximum logical core number
> -        eal_para = self.sut_node.create_eal_parameters(
> -            lcore_filter_specifier=LogicalCoreList(self.sut_node.lcores)
> +        eal_para = compute_eal_params(
> +            self.sut_node, 
> lcore_filter_specifier=LogicalCoreList(self.sut_node.lcores)
>          )
>          result = 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/TestSuite_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, TestPmdParams
> +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 = self.sut_node.create_interactive_shell(
> -            TestPmdShell,
> -            app_params=TestPmdParams(
> -                forward_mode=SimpleForwardingModes.mac,
> -                mbcache=200,
> -                mbuf_size=[mbsize],
> -                max_pkt_len=9000,
> -                tx_offloads=0x00008000,
> -                **asdict(self.sut_node.create_eal_parameters()),
> -            ),
> -            privileged=True,
> +        testpmd = TestPmdShell(
> +            self.sut_node,
> +            forward_mode=SimpleForwardingModes.mac,
> +            mbcache=200,
> +            mbuf_size=[mbsize],
> +            max_pkt_len=9000,
> +            tx_offloads=0x00008000,
>          )
>          testpmd.start()
>
> diff --git a/dts/tests/TestSuite_smoke_tests.py 
> b/dts/tests/TestSuite_smoke_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 = 
> self.sut_node.create_interactive_shell(TestPmdShell, privileged=True)
> +        testpmd_driver = TestPmdShell(self.sut_node)
>          dev_list = [str(x) for x in testpmd_driver.get_devices()]
>          for nic in self.nics_in_node:
>              self.verify(
> --
> 2.34.1
>

Reply via email to