From: "Juraj Linkeš" <juraj.linkes@pantheon.tech>
To: thomas@monjalon.net, Honnappa.Nagarahalli@arm.com,
lijuan.tu@intel.com, jspewock@iol.unh.edu, probb@iol.unh.edu
Cc: dev@dpdk.org, "Juraj Linkeš" <juraj.linkes@pantheon.tech>
Subject: [PATCH v2 4/6] dts: add python remote interactive shell
Date: Mon, 17 Jul 2023 13:07:07 +0200 [thread overview]
Message-ID: <20230717110709.39220-5-juraj.linkes@pantheon.tech> (raw)
In-Reply-To: <20230717110709.39220-1-juraj.linkes@pantheon.tech>
The shell can be used to remotely run any Python code interactively.
Signed-off-by: Juraj Linkeš <juraj.linkes@pantheon.tech>
---
dts/framework/config/__init__.py | 28 +-----------
dts/framework/remote_session/__init__.py | 2 +-
dts/framework/remote_session/os_session.py | 42 +++++++++---------
.../remote/interactive_shell.py | 18 +++++---
.../remote_session/remote/python_shell.py | 24 +++++++++++
.../remote_session/remote/testpmd_shell.py | 33 +++-----------
dts/framework/testbed_model/node.py | 35 ++++++++++++++-
dts/framework/testbed_model/sut_node.py | 43 ++++++++-----------
dts/tests/TestSuite_smoke_tests.py | 6 +--
9 files changed, 119 insertions(+), 112 deletions(-)
create mode 100644 dts/framework/remote_session/remote/python_shell.py
diff --git a/dts/framework/config/__init__.py b/dts/framework/config/__init__.py
index 72aa021b97..b5830f6301 100644
--- a/dts/framework/config/__init__.py
+++ b/dts/framework/config/__init__.py
@@ -11,8 +11,7 @@
import os.path
import pathlib
from dataclasses import dataclass
-from enum import Enum, auto, unique
-from pathlib import PurePath
+from enum import auto, unique
from typing import Any, TypedDict, Union
import warlock # type: ignore
@@ -331,28 +330,3 @@ def load_config() -> Configuration:
CONFIGURATION = load_config()
-
-
-@unique
-class InteractiveApp(Enum):
- """An enum that represents different supported interactive applications.
-
- The values in this enum must all be set to objects that have a key called
- "default_path" where "default_path" represents a PurePath object for the path
- to the application. This default path will be passed into the handler class
- for the application so that it can start the application.
- """
-
- testpmd = {"default_path": PurePath("app", "dpdk-testpmd")}
-
- @property
- def path(self) -> PurePath:
- """Default path of the application.
-
- For DPDK apps, this will be appended to the DPDK build directory.
- """
- return self.value["default_path"]
-
- @path.setter
- def path(self, path: PurePath) -> None:
- self.value["default_path"] = path
diff --git a/dts/framework/remote_session/__init__.py b/dts/framework/remote_session/__init__.py
index 2c408c2557..1155dd8318 100644
--- a/dts/framework/remote_session/__init__.py
+++ b/dts/framework/remote_session/__init__.py
@@ -17,7 +17,7 @@
from framework.logger import DTSLOG
from .linux_session import LinuxSession
-from .os_session import OSSession
+from .os_session import InteractiveShellType, OSSession
from .remote import (
CommandResult,
InteractiveRemoteSession,
diff --git a/dts/framework/remote_session/os_session.py b/dts/framework/remote_session/os_session.py
index 633d06eb5d..c17a17a267 100644
--- a/dts/framework/remote_session/os_session.py
+++ b/dts/framework/remote_session/os_session.py
@@ -5,11 +5,11 @@
from abc import ABC, abstractmethod
from collections.abc import Iterable
from pathlib import PurePath
-from typing import Union
+from typing import Type, TypeVar
-from framework.config import Architecture, InteractiveApp, NodeConfiguration, NodeInfo
+from framework.config import Architecture, NodeConfiguration, NodeInfo
from framework.logger import DTSLOG
-from framework.remote_session.remote import InteractiveShell, TestPmdShell
+from framework.remote_session.remote import InteractiveShell
from framework.settings import SETTINGS
from framework.testbed_model import LogicalCore
from framework.testbed_model.hw.port import Port
@@ -23,6 +23,8 @@
create_remote_session,
)
+InteractiveShellType = TypeVar("InteractiveShellType", bound=InteractiveShell)
+
class OSSession(ABC):
"""
@@ -81,30 +83,26 @@ def send_command(
def create_interactive_shell(
self,
- shell_type: InteractiveApp,
- path_to_app: PurePath,
+ shell_cls: Type[InteractiveShellType],
eal_parameters: str,
timeout: float,
- ) -> Union[InteractiveShell, TestPmdShell]:
+ privileged: bool,
+ ) -> InteractiveShellType:
"""
See "create_interactive_shell" in SutNode
"""
- match (shell_type):
- case InteractiveApp.testpmd:
- return TestPmdShell(
- self.interactive_session.session,
- self._logger,
- path_to_app,
- timeout=timeout,
- eal_flags=eal_parameters,
- )
- case _:
- self._logger.info(
- f"Unhandled app type {shell_type.name}, defaulting to shell."
- )
- return InteractiveShell(
- self.interactive_session.session, self._logger, path_to_app, timeout
- )
+ app_command = (
+ self._get_privileged_command(str(shell_cls.path))
+ if privileged
+ else str(shell_cls.path)
+ )
+ return shell_cls(
+ self.interactive_session.session,
+ self._logger,
+ app_command,
+ eal_parameters,
+ timeout,
+ )
@abstractmethod
def _get_privileged_command(self, command: str) -> str:
diff --git a/dts/framework/remote_session/remote/interactive_shell.py b/dts/framework/remote_session/remote/interactive_shell.py
index 2cabe9edca..1211d91aa9 100644
--- a/dts/framework/remote_session/remote/interactive_shell.py
+++ b/dts/framework/remote_session/remote/interactive_shell.py
@@ -17,13 +17,18 @@ class InteractiveShell:
_ssh_channel: Channel
_logger: DTSLOG
_timeout: float
- _path_to_app: PurePath
+ _startup_command: str
+ _app_args: str
+ _default_prompt: str = ""
+ path: PurePath
+ dpdk_app: bool = False
def __init__(
self,
interactive_session: SSHClient,
logger: DTSLOG,
- path_to_app: PurePath,
+ startup_command: str,
+ app_args: str = "",
timeout: float = SETTINGS.timeout,
) -> None:
self._interactive_session = interactive_session
@@ -34,16 +39,19 @@ def __init__(
self._ssh_channel.set_combine_stderr(True) # combines stdout and stderr streams
self._logger = logger
self._timeout = timeout
- self._path_to_app = path_to_app
+ self._startup_command = startup_command
+ self._app_args = app_args
self._start_application()
def _start_application(self) -> None:
- """Starts a new interactive application based on _path_to_app.
+ """Starts a new interactive application based on _startup_command.
This method is often overridden by subclasses as their process for
starting may look different.
"""
- self.send_command_get_output(f"{self._path_to_app}", "")
+ self.send_command_get_output(
+ f"{self._startup_command} {self._app_args}", self._default_prompt
+ )
def send_command_get_output(self, command: str, prompt: str) -> str:
"""Send a command and get all output before the expected ending string.
diff --git a/dts/framework/remote_session/remote/python_shell.py b/dts/framework/remote_session/remote/python_shell.py
new file mode 100644
index 0000000000..66d5787c86
--- /dev/null
+++ b/dts/framework/remote_session/remote/python_shell.py
@@ -0,0 +1,24 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2023 PANTHEON.tech s.r.o.
+
+from pathlib import PurePath
+
+from .interactive_shell import InteractiveShell
+
+
+class PythonShell(InteractiveShell):
+ _startup_command: str
+ _default_prompt: str = ">>>"
+ path: PurePath = PurePath("python3")
+
+ def _start_application(self) -> None:
+ self._startup_command = f"{self._startup_command}\n"
+ super()._start_application()
+
+ def send_command(self, command: str, prompt: str = _default_prompt) -> str:
+ """Specific way of handling the command for python
+
+ An extra newline character is consumed in order to force the current line into
+ the stdout buffer.
+ """
+ return self.send_command_get_output(f"{command}\n", prompt)
diff --git a/dts/framework/remote_session/remote/testpmd_shell.py b/dts/framework/remote_session/remote/testpmd_shell.py
index c0261c00f6..1288cfd10c 100644
--- a/dts/framework/remote_session/remote/testpmd_shell.py
+++ b/dts/framework/remote_session/remote/testpmd_shell.py
@@ -3,11 +3,6 @@
from pathlib import PurePath
-from paramiko import SSHClient # type: ignore
-
-from framework.logger import DTSLOG
-from framework.settings import SETTINGS
-
from .interactive_shell import InteractiveShell
@@ -22,34 +17,18 @@ def __str__(self) -> str:
class TestPmdShell(InteractiveShell):
- expected_prompt: str = "testpmd>"
+ path: PurePath = PurePath("app", "dpdk-testpmd")
+ dpdk_app: bool = True
+ _default_prompt: str = "testpmd>"
_eal_flags: str
- def __init__(
- self,
- interactive_session: SSHClient,
- logger: DTSLOG,
- path_to_testpmd: PurePath,
- eal_flags: str,
- timeout: float = SETTINGS.timeout,
- ) -> None:
- """Initializes an interactive testpmd session using specified parameters."""
- self._eal_flags = eal_flags
-
- super(TestPmdShell, self).__init__(
- interactive_session,
- logger=logger,
- path_to_app=path_to_testpmd,
- timeout=timeout,
- )
-
def _start_application(self) -> None:
- """Starts a new interactive testpmd shell using _path_to_app."""
+ """Starts a new interactive testpmd shell using _startup_command."""
self.send_command(
- f"{self._path_to_app} {self._eal_flags} -- -i",
+ f"{self._startup_command} {self._app_args} -- -i",
)
- def send_command(self, command: str, prompt: str = expected_prompt) -> str:
+ def send_command(self, command: str, prompt: str = _default_prompt) -> str:
"""Specific way of handling the command for testpmd
An extra newline character is consumed in order to force the current line into
diff --git a/dts/framework/testbed_model/node.py b/dts/framework/testbed_model/node.py
index e09931cedf..f70e4d5ce6 100644
--- a/dts/framework/testbed_model/node.py
+++ b/dts/framework/testbed_model/node.py
@@ -7,7 +7,7 @@
A node is a generic host that DTS connects to and manages.
"""
-from typing import Any, Callable
+from typing import Any, Callable, Type
from framework.config import (
BuildTargetConfiguration,
@@ -15,7 +15,7 @@
NodeConfiguration,
)
from framework.logger import DTSLOG, getLogger
-from framework.remote_session import OSSession, create_session
+from framework.remote_session import InteractiveShellType, OSSession, create_session
from framework.settings import SETTINGS
from .hw import (
@@ -138,6 +138,37 @@ 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_args: str = "",
+ ) -> InteractiveShellType:
+ """Create a handler for an interactive session.
+
+ 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:
+ Instance of the desired interactive application.
+ """
+ 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,
+ app_args,
+ timeout,
+ privileged,
+ )
+
def filter_lcores(
self,
filter_specifier: LogicalCoreCount | LogicalCoreList,
diff --git a/dts/framework/testbed_model/sut_node.py b/dts/framework/testbed_model/sut_node.py
index bcad364435..f0b017a383 100644
--- a/dts/framework/testbed_model/sut_node.py
+++ b/dts/framework/testbed_model/sut_node.py
@@ -7,21 +7,15 @@
import tarfile
import time
from pathlib import PurePath
-from typing import Union
+from typing import Type
from framework.config import (
BuildTargetConfiguration,
BuildTargetInfo,
- InteractiveApp,
NodeInfo,
SutNodeConfiguration,
)
-from framework.remote_session import (
- CommandResult,
- InteractiveShell,
- OSSession,
- TestPmdShell,
-)
+from framework.remote_session import CommandResult, InteractiveShellType, OSSession
from framework.settings import SETTINGS
from framework.utils import MesonArgs
@@ -359,23 +353,24 @@ def run_dpdk_app(
def create_interactive_shell(
self,
- shell_type: InteractiveApp,
+ shell_cls: Type[InteractiveShellType],
timeout: float = SETTINGS.timeout,
- eal_parameters: EalParameters | None = None,
- ) -> Union[InteractiveShell, TestPmdShell]:
- """Create a handler for an interactive session.
+ privileged: bool = False,
+ eal_parameters: EalParameters | str | None = None,
+ ) -> InteractiveShellType:
+ """Factory method for creating a handler for an interactive session.
- This method is a factory that calls a method in OSSession to create shells for
- different DPDK applications.
+ Instantiate shell_cls according to the remote OS specifics.
Args:
- shell_type: Enum value representing the desired application.
+ 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.
eal_parameters: List of EAL parameters to use to launch the app. If this
- isn't provided, it will default to calling create_eal_parameters().
- This is ignored for base "shell" types.
+ isn't provided or an empty string is passed, it will default to calling
+ create_eal_parameters().
Returns:
Instance of the desired interactive application.
"""
@@ -383,11 +378,11 @@ def create_interactive_shell(
eal_parameters = self.create_eal_parameters()
# We need to append the build directory for DPDK apps
- shell_type.path = self.remote_dpdk_build_dir.joinpath(shell_type.path)
- default_path = self.main_session.join_remote_path(shell_type.path)
- return self.main_session.create_interactive_shell(
- shell_type,
- default_path,
- str(eal_parameters),
- timeout,
+ if shell_cls.dpdk_app:
+ 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, str(eal_parameters)
)
diff --git a/dts/tests/TestSuite_smoke_tests.py b/dts/tests/TestSuite_smoke_tests.py
index 9cf547205f..e73d015bc7 100644
--- a/dts/tests/TestSuite_smoke_tests.py
+++ b/dts/tests/TestSuite_smoke_tests.py
@@ -3,7 +3,7 @@
import re
-from framework.config import InteractiveApp, PortConfig
+from framework.config import PortConfig
from framework.remote_session import TestPmdDevice, TestPmdShell
from framework.settings import SETTINGS
from framework.test_suite import TestSuite
@@ -67,9 +67,7 @@ def test_devices_listed_in_testpmd(self) -> None:
Test:
Uses testpmd driver to verify that devices have been found by testpmd.
"""
- testpmd_driver = self.sut_node.create_interactive_shell(InteractiveApp.testpmd)
- # We know it should always be a TestPmdShell but mypy doesn't
- assert isinstance(testpmd_driver, TestPmdShell)
+ testpmd_driver = self.sut_node.create_interactive_shell(TestPmdShell)
dev_list: list[TestPmdDevice] = testpmd_driver.get_devices()
for nic in self.nics_in_node:
self.verify(
--
2.34.1
next prev parent reply other threads:[~2023-07-17 11:07 UTC|newest]
Thread overview: 29+ messages / expand[flat|nested] mbox.gz Atom feed top
2023-04-20 9:31 [RFC PATCH v1 0/5] dts: add tg abstractions and scapy Juraj Linkeš
2023-04-20 9:31 ` [RFC PATCH v1 1/5] dts: add scapy dependency Juraj Linkeš
2023-04-20 9:31 ` [RFC PATCH v1 2/5] dts: add traffic generator config Juraj Linkeš
2023-04-20 9:31 ` [RFC PATCH v1 3/5] dts: traffic generator abstractions Juraj Linkeš
2023-04-20 9:31 ` [RFC PATCH v1 4/5] dts: scapy traffic generator implementation Juraj Linkeš
2023-04-20 9:31 ` [RFC PATCH v1 5/5] dts: add traffic generator node to dts runner Juraj Linkeš
2023-05-03 18:02 ` Jeremy Spewock
2023-07-17 11:07 ` [PATCH v2 0/6] dts: tg abstractions and scapy tg Juraj Linkeš
2023-07-17 11:07 ` [PATCH v2 1/6] dts: add scapy dependency Juraj Linkeš
2023-07-17 11:07 ` [PATCH v2 2/6] dts: add traffic generator config Juraj Linkeš
2023-07-18 15:55 ` Jeremy Spewock
2023-07-19 12:57 ` Juraj Linkeš
2023-07-19 13:18 ` Jeremy Spewock
2023-07-17 11:07 ` [PATCH v2 3/6] dts: traffic generator abstractions Juraj Linkeš
2023-07-18 19:56 ` Jeremy Spewock
2023-07-19 13:23 ` Juraj Linkeš
2023-07-17 11:07 ` Juraj Linkeš [this message]
2023-07-17 11:07 ` [PATCH v2 5/6] dts: scapy traffic generator implementation Juraj Linkeš
2023-07-17 11:07 ` [PATCH v2 6/6] dts: add basic UDP test case Juraj Linkeš
2023-07-18 21:04 ` [PATCH v2 0/6] dts: tg abstractions and scapy tg Jeremy Spewock
2023-07-19 14:12 ` [PATCH v3 " Juraj Linkeš
2023-07-19 14:12 ` [PATCH v3 1/6] dts: add scapy dependency Juraj Linkeš
2023-07-19 14:12 ` [PATCH v3 2/6] dts: add traffic generator config Juraj Linkeš
2023-07-19 14:13 ` [PATCH v3 3/6] dts: traffic generator abstractions Juraj Linkeš
2023-07-19 14:13 ` [PATCH v3 4/6] dts: add python remote interactive shell Juraj Linkeš
2023-07-19 14:13 ` [PATCH v3 5/6] dts: scapy traffic generator implementation Juraj Linkeš
2023-07-19 14:13 ` [PATCH v3 6/6] dts: add basic UDP test case Juraj Linkeš
2023-07-20 15:21 ` Jeremy Spewock
2023-07-24 14:23 ` [PATCH v3 0/6] dts: tg abstractions and scapy tg Thomas Monjalon
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20230717110709.39220-5-juraj.linkes@pantheon.tech \
--to=juraj.linkes@pantheon.tech \
--cc=Honnappa.Nagarahalli@arm.com \
--cc=dev@dpdk.org \
--cc=jspewock@iol.unh.edu \
--cc=lijuan.tu@intel.com \
--cc=probb@iol.unh.edu \
--cc=thomas@monjalon.net \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).