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 DAB3443380; Mon, 20 Nov 2023 17:10:58 +0100 (CET) Received: from mails.dpdk.org (localhost [127.0.0.1]) by mails.dpdk.org (Postfix) with ESMTP id 6675342DDE; Mon, 20 Nov 2023 17:10:58 +0100 (CET) Received: from mail-ej1-f54.google.com (mail-ej1-f54.google.com [209.85.218.54]) by mails.dpdk.org (Postfix) with ESMTP id 2CFEB42DD2 for ; Mon, 20 Nov 2023 17:10:56 +0100 (CET) Received: by mail-ej1-f54.google.com with SMTP id a640c23a62f3a-9c41e95efcbso622641166b.3 for ; Mon, 20 Nov 2023 08:10:56 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=pantheon.tech; s=google; t=1700496656; x=1701101456; 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=43LZQAjeB7o0svnZMi1JMIgWgNVqi7YEXAGrjAQI87U=; b=WrEodfWtVAzFf5bHaLBOUnhePvlIVRGz3qFbxx9LV17CxirK1AtL/7Jp+lPC9+I3zQ 5WUPp8oKqmeSzlrpgE2/66FcKM90HHScVaMIwdmvE0e/38kJBfwAdH81EvD7VSeqyIni k06/OxN9/EzprQVpbSC6frq9BF1e/A4LJ8D+JlhT06ZPwQkTfhObi+NJhV0VldERyN5T s9ay39+eOcQ8HwFTa348mIszf1V3F3ozAMaOIFM9yjSgEybcUZK95zBlJVm3YjnUTex5 bDBRcVVJa/MGVk+TVazbwKaf8XawpFxcRn4dyzi5o6O/qRY3x1NEJr+J2XXykg1GVN0X pcZQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1700496656; x=1701101456; 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=43LZQAjeB7o0svnZMi1JMIgWgNVqi7YEXAGrjAQI87U=; b=FEMVBUVVNgkhtFN6jbTq0/iVJbKKMPbIWPpiYzzYqWVYv9gbaBGq1tg1WnXOvfnuKu hNrb5mEJHaD096mYGLSdAlqGJaZsvK4ds37VUim6fl2lrdlNYnNOuDGCW64hu8uRlhw/ RGS2Ggl729ODTQA0cd6s5c9mW7GZFmRSZ9xkptO4OD69wBYr4YASLJDlFnpUieHc4ASn gKTLDPp/bv2/6u5ALnV+APFrH/Yzvecs34GIU3dzJgVxyhHZiqpHBTuuH01NM2os5lQz 76NKyXuI0jMv4gFagvdamxm5BntmnxLpy9OHdIvvs+KDMUjhgF8cslIbgdtQiLtClIKe KCfw== X-Gm-Message-State: AOJu0YxHdCh0cPb5gt3d1dTdD8DjGDAbf1mnuUGNTxshQ2yZw03gZcOL NMsVSYxkmd8abvzyHv0U95F8SqGnfbScJ/Wg+Vas2g== X-Google-Smtp-Source: AGHT+IEqcrtK33aF54niMUOaT5tog9MRw97EEDKzhVGVgb2nYAgz0APNAdTpzeYMDI1zy7EfOj6jY7ORkvB9JXCag5w= X-Received: by 2002:a17:906:388:b0:9fd:a469:b367 with SMTP id b8-20020a170906038800b009fda469b367mr2967171eja.39.1700496655320; Mon, 20 Nov 2023 08:10:55 -0800 (PST) MIME-Version: 1.0 References: <20231108125324.191005-23-juraj.linkes@pantheon.tech> <20231115130959.39420-1-juraj.linkes@pantheon.tech> <20231115130959.39420-2-juraj.linkes@pantheon.tech> In-Reply-To: From: =?UTF-8?Q?Juraj_Linke=C5=A1?= Date: Mon, 20 Nov 2023 17:10:44 +0100 Message-ID: Subject: Re: [PATCH v7 01/21] dts: code adjustments for doc generation To: Jeremy Spewock Cc: thomas@monjalon.net, Honnappa.Nagarahalli@arm.com, probb@iol.unh.edu, paul.szczepanek@arm.com, yoan.picchi@foss.arm.com, dev@dpdk.org 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 On Thu, Nov 16, 2023 at 10:05=E2=80=AFPM Jeremy Spewock wrote: > > > > On Wed, Nov 15, 2023 at 8:11=E2=80=AFAM Juraj Linke=C5=A1 wrote: >> >> The standard Python tool for generating API documentation, Sphinx, >> imports modules one-by-one when generating the documentation. This >> requires code changes: >> * properly guarding argument parsing in the if __name__ =3D=3D '__main__= ' >> block, >> * the logger used by DTS runner underwent the same treatment so that it >> doesn't create log files outside of a DTS run, >> * however, DTS uses the arguments to construct an object holding global >> variables. The defaults for the global variables needed to be moved >> from argument parsing elsewhere, >> * importing the remote_session module from framework resulted in >> circular imports because of one module trying to import another >> module. This is fixed by reorganizing the code, >> * some code reorganization was done because the resulting structure >> makes more sense, improving documentation clarity. >> >> The are some other changes which are documentation related: >> * added missing type annotation so they appear in the generated docs, >> * reordered arguments in some methods, >> * removed superfluous arguments and attributes, >> * change private functions/methods/attributes to private and vice-versa. >> >> The above all appear in the generated documentation and the with them, >> the documentation is improved. >> >> Signed-off-by: Juraj Linke=C5=A1 >> --- >> dts/framework/config/__init__.py | 10 ++- >> dts/framework/dts.py | 33 +++++-- >> dts/framework/exception.py | 54 +++++------- >> dts/framework/remote_session/__init__.py | 41 ++++----- >> .../interactive_remote_session.py | 0 >> .../{remote =3D> }/interactive_shell.py | 0 >> .../{remote =3D> }/python_shell.py | 0 >> .../remote_session/remote/__init__.py | 27 ------ >> .../{remote =3D> }/remote_session.py | 0 >> .../{remote =3D> }/ssh_session.py | 12 +-- >> .../{remote =3D> }/testpmd_shell.py | 0 >> dts/framework/settings.py | 87 +++++++++++-------- >> dts/framework/test_result.py | 4 +- >> dts/framework/test_suite.py | 7 +- >> dts/framework/testbed_model/__init__.py | 12 +-- >> dts/framework/testbed_model/{hw =3D> }/cpu.py | 13 +++ >> dts/framework/testbed_model/hw/__init__.py | 27 ------ >> .../linux_session.py | 6 +- >> dts/framework/testbed_model/node.py | 25 ++++-- >> .../os_session.py | 22 ++--- >> dts/framework/testbed_model/{hw =3D> }/port.py | 0 >> .../posix_session.py | 4 +- >> dts/framework/testbed_model/sut_node.py | 8 +- >> dts/framework/testbed_model/tg_node.py | 30 +------ >> .../traffic_generator/__init__.py | 24 +++++ >> .../capturing_traffic_generator.py | 6 +- >> .../{ =3D> traffic_generator}/scapy.py | 23 ++--- >> .../traffic_generator.py | 16 +++- >> .../testbed_model/{hw =3D> }/virtual_device.py | 0 >> dts/framework/utils.py | 46 +++------- >> dts/main.py | 9 +- >> 31 files changed, 258 insertions(+), 288 deletions(-) >> rename dts/framework/remote_session/{remote =3D> }/interactive_remote_s= ession.py (100%) >> rename dts/framework/remote_session/{remote =3D> }/interactive_shell.py= (100%) >> rename dts/framework/remote_session/{remote =3D> }/python_shell.py (100= %) >> delete mode 100644 dts/framework/remote_session/remote/__init__.py >> rename dts/framework/remote_session/{remote =3D> }/remote_session.py (1= 00%) >> rename dts/framework/remote_session/{remote =3D> }/ssh_session.py (91%) >> rename dts/framework/remote_session/{remote =3D> }/testpmd_shell.py (10= 0%) >> rename dts/framework/testbed_model/{hw =3D> }/cpu.py (95%) >> delete mode 100644 dts/framework/testbed_model/hw/__init__.py >> rename dts/framework/{remote_session =3D> testbed_model}/linux_session.= py (97%) >> rename dts/framework/{remote_session =3D> testbed_model}/os_session.py = (95%) >> rename dts/framework/testbed_model/{hw =3D> }/port.py (100%) >> rename dts/framework/{remote_session =3D> testbed_model}/posix_session.= py (98%) >> create mode 100644 dts/framework/testbed_model/traffic_generator/__init= __.py >> rename dts/framework/testbed_model/{ =3D> traffic_generator}/capturing_= traffic_generator.py (96%) >> rename dts/framework/testbed_model/{ =3D> traffic_generator}/scapy.py (= 95%) >> rename dts/framework/testbed_model/{ =3D> traffic_generator}/traffic_ge= nerator.py (80%) >> rename dts/framework/testbed_model/{hw =3D> }/virtual_device.py (100%) >> >> diff --git a/dts/framework/config/__init__.py b/dts/framework/config/__i= nit__.py >> index cb7e00ba34..2044c82611 100644 >> --- a/dts/framework/config/__init__.py >> +++ b/dts/framework/config/__init__.py >> @@ -17,6 +17,7 @@ >> import warlock # type: ignore[import] >> import yaml >> >> +from framework.exception import ConfigurationError >> from framework.settings import SETTINGS >> from framework.utils import StrEnum >> >> @@ -89,7 +90,7 @@ class TrafficGeneratorConfig: >> traffic_generator_type: TrafficGeneratorType >> >> @staticmethod >> - def from_dict(d: dict): >> + def from_dict(d: dict) -> "ScapyTrafficGeneratorConfig": >> # This looks useless now, but is designed to allow expansion to= traffic >> # generators that require more configuration later. >> match TrafficGeneratorType(d["type"]): >> @@ -97,6 +98,10 @@ def from_dict(d: dict): >> return ScapyTrafficGeneratorConfig( >> traffic_generator_type=3DTrafficGeneratorType.SCAPY >> ) >> + case _: >> + raise ConfigurationError( >> + f'Unknown traffic generator type "{d["type"]}".' >> + ) >> >> >> @dataclass(slots=3DTrue, frozen=3DTrue) >> @@ -324,6 +329,3 @@ def load_config() -> Configuration: >> config: dict[str, Any] =3D warlock.model_factory(schema, name=3D"_C= onfig")(config_data) >> config_obj: Configuration =3D Configuration.from_dict(dict(config)) >> return config_obj >> - >> - >> -CONFIGURATION =3D load_config() >> diff --git a/dts/framework/dts.py b/dts/framework/dts.py >> index f773f0c38d..4c7fb0c40a 100644 >> --- a/dts/framework/dts.py >> +++ b/dts/framework/dts.py >> @@ -6,19 +6,19 @@ >> import sys >> >> from .config import ( >> - CONFIGURATION, >> BuildTargetConfiguration, >> ExecutionConfiguration, >> TestSuiteConfig, >> + load_config, >> ) >> from .exception import BlockingTestSuiteError >> from .logger import DTSLOG, getLogger >> from .test_result import BuildTargetResult, DTSResult, ExecutionResult,= Result >> from .test_suite import get_test_suites >> from .testbed_model import SutNode, TGNode >> -from .utils import check_dts_python_version >> >> -dts_logger: DTSLOG =3D getLogger("DTSRunner") >> +# dummy defaults to satisfy linters >> +dts_logger: DTSLOG =3D None # type: ignore[assignment] >> result: DTSResult =3D DTSResult(dts_logger) >> >> >> @@ -30,14 +30,18 @@ def run_all() -> None: >> global dts_logger >> global result >> >> + # create a regular DTS logger and create a new result with it >> + dts_logger =3D getLogger("DTSRunner") >> + result =3D DTSResult(dts_logger) >> + >> # check the python version of the server that run dts >> - check_dts_python_version() >> + _check_dts_python_version() >> >> sut_nodes: dict[str, SutNode] =3D {} >> tg_nodes: dict[str, TGNode] =3D {} >> try: >> # for all Execution sections >> - for execution in CONFIGURATION.executions: >> + for execution in load_config().executions: >> sut_node =3D sut_nodes.get(execution.system_under_test_node= .name) >> tg_node =3D tg_nodes.get(execution.traffic_generator_node.n= ame) >> >> @@ -82,6 +86,25 @@ def run_all() -> None: >> _exit_dts() >> >> >> +def _check_dts_python_version() -> None: >> + def RED(text: str) -> str: >> + return f"\u001B[31;1m{str(text)}\u001B[0m" >> + >> + if sys.version_info.major < 3 or ( >> + sys.version_info.major =3D=3D 3 and sys.version_info.minor < 10 >> + ): >> + print( >> + RED( >> + ( >> + "WARNING: DTS execution node's python version is lo= wer than" >> + "python 3.10, is deprecated and will not work in fu= ture releases." >> + ) >> + ), >> + file=3Dsys.stderr, >> + ) >> + print(RED("Please use Python >=3D 3.10 instead"), file=3Dsys.st= derr) >> + >> + >> def _run_execution( >> sut_node: SutNode, >> tg_node: TGNode, >> diff --git a/dts/framework/exception.py b/dts/framework/exception.py >> index 001a5a5496..7489c03570 100644 >> --- a/dts/framework/exception.py >> +++ b/dts/framework/exception.py >> @@ -42,19 +42,14 @@ class SSHTimeoutError(DTSError): >> Command execution timeout. >> """ >> >> - command: str >> - output: str >> severity: ClassVar[ErrorSeverity] =3D ErrorSeverity.SSH_ERR >> + _command: str >> >> - def __init__(self, command: str, output: str): >> - self.command =3D command >> - self.output =3D output >> + def __init__(self, command: str): >> + self._command =3D command >> >> def __str__(self) -> str: >> - return f"TIMEOUT on {self.command}" >> - >> - def get_output(self) -> str: >> - return self.output >> + return f"TIMEOUT on {self._command}" >> >> >> class SSHConnectionError(DTSError): >> @@ -62,18 +57,18 @@ class SSHConnectionError(DTSError): >> SSH connection error. >> """ >> >> - host: str >> - errors: list[str] >> severity: ClassVar[ErrorSeverity] =3D ErrorSeverity.SSH_ERR >> + _host: str >> + _errors: list[str] >> >> def __init__(self, host: str, errors: list[str] | None =3D None): >> - self.host =3D host >> - self.errors =3D [] if errors is None else errors >> + self._host =3D host >> + self._errors =3D [] if errors is None else errors >> >> def __str__(self) -> str: >> - message =3D f"Error trying to connect with {self.host}." >> - if self.errors: >> - message +=3D f" Errors encountered while retrying: {', '.jo= in(self.errors)}" >> + message =3D f"Error trying to connect with {self._host}." >> + if self._errors: >> + message +=3D f" Errors encountered while retrying: {', '.jo= in(self._errors)}" >> >> return message >> >> @@ -84,14 +79,14 @@ class SSHSessionDeadError(DTSError): >> It can no longer be used. >> """ >> >> - host: str >> severity: ClassVar[ErrorSeverity] =3D ErrorSeverity.SSH_ERR >> + _host: str >> >> def __init__(self, host: str): >> - self.host =3D host >> + self._host =3D host >> >> def __str__(self) -> str: >> - return f"SSH session with {self.host} has died" >> + return f"SSH session with {self._host} has died" >> >> >> class ConfigurationError(DTSError): >> @@ -107,18 +102,18 @@ class RemoteCommandExecutionError(DTSError): >> Raised when a command executed on a Node returns a non-zero exit st= atus. >> """ >> >> - command: str >> - command_return_code: int >> severity: ClassVar[ErrorSeverity] =3D ErrorSeverity.REMOTE_CMD_EXEC= _ERR >> + command: str >> + _command_return_code: int >> >> def __init__(self, command: str, command_return_code: int): >> self.command =3D command >> - self.command_return_code =3D command_return_code >> + self._command_return_code =3D command_return_code >> >> def __str__(self) -> str: >> return ( >> f"Command {self.command} returned a non-zero exit code: " >> - f"{self.command_return_code}" >> + f"{self._command_return_code}" >> ) >> >> >> @@ -143,22 +138,15 @@ class TestCaseVerifyError(DTSError): >> Used in test cases to verify the expected behavior. >> """ >> >> - value: str >> severity: ClassVar[ErrorSeverity] =3D ErrorSeverity.TESTCASE_VERIFY= _ERR >> >> - def __init__(self, value: str): >> - self.value =3D value >> - >> - def __str__(self) -> str: >> - return repr(self.value) >> - > > > Does this change mean we are no longer providing descriptions for what fa= iling the verification means? I guess there isn't really harm in removing t= hat functionality, but I'm not sure I see the value in removing the extra i= nformation either. > This shouldn't have any impact on the existing functionality. The error message will be stored even without the variable (that's the default behavior of exceptions) and the string representation is not used anywhere in code and even if it was, the only difference is the self.value string would be in quotes. This just removes unnecessary code, which I didn't want to document as that would be just confusing. >> >> >> class BlockingTestSuiteError(DTSError): >> - suite_name: str >> severity: ClassVar[ErrorSeverity] =3D ErrorSeverity.BLOCKING_TESTSU= ITE_ERR >> + _suite_name: str >> >> def __init__(self, suite_name: str) -> None: >> - self.suite_name =3D suite_name >> + self._suite_name =3D suite_name >> >> def __str__(self) -> str: >> - return f"Blocking suite {self.suite_name} failed." >> + return f"Blocking suite {self._suite_name} failed." >> diff --git a/dts/framework/remote_session/__init__.py b/dts/framework/re= mote_session/__init__.py >> index 00b6d1f03a..5e7ddb2b05 100644 >> --- a/dts/framework/remote_session/__init__.py >> +++ b/dts/framework/remote_session/__init__.py >> @@ -12,29 +12,24 @@ >> >> # pylama:ignore=3DW0611 >> >> -from framework.config import OS, NodeConfiguration >> -from framework.exception import ConfigurationError >> +from framework.config import NodeConfiguration >> from framework.logger import DTSLOG >> >> -from .linux_session import LinuxSession >> -from .os_session import InteractiveShellType, OSSession >> -from .remote import ( >> - CommandResult, >> - InteractiveRemoteSession, >> - InteractiveShell, >> - PythonShell, >> - RemoteSession, >> - SSHSession, >> - TestPmdDevice, >> - TestPmdShell, >> -) >> - >> - >> -def create_session( >> +from .interactive_remote_session import InteractiveRemoteSession >> +from .interactive_shell import InteractiveShell >> +from .python_shell import PythonShell >> +from .remote_session import CommandResult, RemoteSession >> +from .ssh_session import SSHSession >> +from .testpmd_shell import TestPmdShell >> + >> + >> +def create_remote_session( >> node_config: NodeConfiguration, name: str, logger: DTSLOG >> -) -> OSSession: >> - match node_config.os: >> - case OS.linux: >> - return LinuxSession(node_config, name, logger) >> - case _: >> - raise ConfigurationError(f"Unsupported OS {node_config.os}"= ) >> +) -> RemoteSession: >> + return SSHSession(node_config, name, logger) >> + >> + >> +def create_interactive_session( >> + node_config: NodeConfiguration, logger: DTSLOG >> +) -> InteractiveRemoteSession: >> + return InteractiveRemoteSession(node_config, logger) >> diff --git a/dts/framework/remote_session/remote/interactive_remote_sess= ion.py b/dts/framework/remote_session/interactive_remote_session.py >> similarity index 100% >> rename from dts/framework/remote_session/remote/interactive_remote_sessi= on.py >> rename to dts/framework/remote_session/interactive_remote_session.py >> diff --git a/dts/framework/remote_session/remote/interactive_shell.py b/= dts/framework/remote_session/interactive_shell.py >> similarity index 100% >> rename from dts/framework/remote_session/remote/interactive_shell.py >> rename to dts/framework/remote_session/interactive_shell.py >> diff --git a/dts/framework/remote_session/remote/python_shell.py b/dts/f= ramework/remote_session/python_shell.py >> similarity index 100% >> rename from dts/framework/remote_session/remote/python_shell.py >> rename to dts/framework/remote_session/python_shell.py >> diff --git a/dts/framework/remote_session/remote/__init__.py b/dts/frame= work/remote_session/remote/__init__.py >> deleted file mode 100644 >> index 06403691a5..0000000000 >> --- a/dts/framework/remote_session/remote/__init__.py >> +++ /dev/null >> @@ -1,27 +0,0 @@ >> -# SPDX-License-Identifier: BSD-3-Clause >> -# Copyright(c) 2023 PANTHEON.tech s.r.o. >> -# Copyright(c) 2023 University of New Hampshire >> - >> -# pylama:ignore=3DW0611 >> - >> -from framework.config import NodeConfiguration >> -from framework.logger import DTSLOG >> - >> -from .interactive_remote_session import InteractiveRemoteSession >> -from .interactive_shell import InteractiveShell >> -from .python_shell import PythonShell >> -from .remote_session import CommandResult, RemoteSession >> -from .ssh_session import SSHSession >> -from .testpmd_shell import TestPmdDevice, TestPmdShell >> - >> - >> -def create_remote_session( >> - node_config: NodeConfiguration, name: str, logger: DTSLOG >> -) -> RemoteSession: >> - return SSHSession(node_config, name, logger) >> - >> - >> -def create_interactive_session( >> - node_config: NodeConfiguration, logger: DTSLOG >> -) -> InteractiveRemoteSession: >> - return InteractiveRemoteSession(node_config, logger) >> diff --git a/dts/framework/remote_session/remote/remote_session.py b/dts= /framework/remote_session/remote_session.py >> similarity index 100% >> rename from dts/framework/remote_session/remote/remote_session.py >> rename to dts/framework/remote_session/remote_session.py >> diff --git a/dts/framework/remote_session/remote/ssh_session.py b/dts/fr= amework/remote_session/ssh_session.py >> similarity index 91% >> rename from dts/framework/remote_session/remote/ssh_session.py >> rename to dts/framework/remote_session/ssh_session.py >> index 8d127f1601..cee11d14d6 100644 >> --- a/dts/framework/remote_session/remote/ssh_session.py >> +++ b/dts/framework/remote_session/ssh_session.py >> @@ -18,9 +18,7 @@ >> SSHException, >> ) >> >> -from framework.config import NodeConfiguration >> from framework.exception import SSHConnectionError, SSHSessionDeadError= , SSHTimeoutError >> -from framework.logger import DTSLOG >> >> from .remote_session import CommandResult, RemoteSession >> >> @@ -45,14 +43,6 @@ class SSHSession(RemoteSession): >> >> session: Connection >> >> - def __init__( >> - self, >> - node_config: NodeConfiguration, >> - session_name: str, >> - logger: DTSLOG, >> - ): >> - super(SSHSession, self).__init__(node_config, session_name, log= ger) >> - >> def _connect(self) -> None: >> errors =3D [] >> retry_attempts =3D 10 >> @@ -117,7 +107,7 @@ def _send_command( >> >> except CommandTimedOut as e: >> self._logger.exception(e) >> - raise SSHTimeoutError(command, e.result.stderr) from e >> + raise SSHTimeoutError(command) from e >> >> return CommandResult( >> self.name, command, output.stdout, output.stderr, output.re= turn_code >> diff --git a/dts/framework/remote_session/remote/testpmd_shell.py b/dts/= framework/remote_session/testpmd_shell.py >> similarity index 100% >> rename from dts/framework/remote_session/remote/testpmd_shell.py >> rename to dts/framework/remote_session/testpmd_shell.py >> diff --git a/dts/framework/settings.py b/dts/framework/settings.py >> index cfa39d011b..7f5841d073 100644 >> --- a/dts/framework/settings.py >> +++ b/dts/framework/settings.py >> @@ -6,7 +6,7 @@ >> import argparse >> import os >> from collections.abc import Callable, Iterable, Sequence >> -from dataclasses import dataclass >> +from dataclasses import dataclass, field >> from pathlib import Path >> from typing import Any, TypeVar >> >> @@ -22,8 +22,8 @@ def __init__( >> option_strings: Sequence[str], >> dest: str, >> nargs: str | int | None =3D None, >> - const: str | None =3D None, >> - default: str =3D None, >> + const: bool | None =3D None, >> + default: Any =3D None, >> type: Callable[[str], _T | argparse.FileType | None] =3D No= ne, >> choices: Iterable[_T] | None =3D None, >> required: bool =3D False, >> @@ -32,6 +32,12 @@ def __init__( >> ) -> None: >> env_var_value =3D os.environ.get(env_var) >> default =3D env_var_value or default >> + if const is not None: >> + nargs =3D 0 >> + default =3D const if env_var_value else default >> + type =3D None >> + choices =3D None >> + metavar =3D None >> super(_EnvironmentArgument, self).__init__( >> option_strings, >> dest, >> @@ -52,22 +58,28 @@ def __call__( >> values: Any, >> option_string: str =3D None, >> ) -> None: >> - setattr(namespace, self.dest, values) >> + if self.const is not None: >> + setattr(namespace, self.dest, self.const) >> + else: >> + setattr(namespace, self.dest, values) >> >> return _EnvironmentArgument >> >> >> -@dataclass(slots=3DTrue, frozen=3DTrue) >> -class _Settings: >> - config_file_path: str >> - output_dir: str >> - timeout: float >> - verbose: bool >> - skip_setup: bool >> - dpdk_tarball_path: Path >> - compile_timeout: float >> - test_cases: list >> - re_run: int >> +@dataclass(slots=3DTrue) >> +class Settings: >> + config_file_path: Path =3D Path(__file__).parent.parent.joinpath("c= onf.yaml") >> + output_dir: str =3D "output" >> + timeout: float =3D 15 >> + verbose: bool =3D False >> + skip_setup: bool =3D False >> + dpdk_tarball_path: Path | str =3D "dpdk.tar.xz" >> + compile_timeout: float =3D 1200 >> + test_cases: list[str] =3D field(default_factory=3Dlist) >> + re_run: int =3D 0 >> + >> + >> +SETTINGS: Settings =3D Settings() >> >> >> def _get_parser() -> argparse.ArgumentParser: >> @@ -81,7 +93,8 @@ def _get_parser() -> argparse.ArgumentParser: >> parser.add_argument( >> "--config-file", >> action=3D_env_arg("DTS_CFG_FILE"), >> - default=3D"conf.yaml", >> + default=3DSETTINGS.config_file_path, >> + type=3DPath, >> help=3D"[DTS_CFG_FILE] configuration file that describes the te= st cases, SUTs " >> "and targets.", >> ) >> @@ -90,7 +103,7 @@ def _get_parser() -> argparse.ArgumentParser: >> "--output-dir", >> "--output", >> action=3D_env_arg("DTS_OUTPUT_DIR"), >> - default=3D"output", >> + default=3DSETTINGS.output_dir, >> help=3D"[DTS_OUTPUT_DIR] Output directory where dts logs and re= sults are saved.", >> ) >> >> @@ -98,7 +111,7 @@ def _get_parser() -> argparse.ArgumentParser: >> "-t", >> "--timeout", >> action=3D_env_arg("DTS_TIMEOUT"), >> - default=3D15, >> + default=3DSETTINGS.timeout, >> type=3Dfloat, >> help=3D"[DTS_TIMEOUT] The default timeout for all DTS operation= s except for " >> "compiling DPDK.", >> @@ -108,8 +121,9 @@ def _get_parser() -> argparse.ArgumentParser: >> "-v", >> "--verbose", >> action=3D_env_arg("DTS_VERBOSE"), >> - default=3D"N", >> - help=3D"[DTS_VERBOSE] Set to 'Y' to enable verbose output, logg= ing all messages " >> + default=3DSETTINGS.verbose, >> + const=3DTrue, >> + help=3D"[DTS_VERBOSE] Specify to enable verbose output, logging= all messages " >> "to the console.", >> ) >> >> @@ -117,8 +131,8 @@ def _get_parser() -> argparse.ArgumentParser: >> "-s", >> "--skip-setup", >> action=3D_env_arg("DTS_SKIP_SETUP"), >> - default=3D"N", >> - help=3D"[DTS_SKIP_SETUP] Set to 'Y' to skip all setup steps on = SUT and TG nodes.", >> + const=3DTrue, >> + help=3D"[DTS_SKIP_SETUP] Specify to skip all setup steps on SUT= and TG nodes.", >> ) >> >> parser.add_argument( >> @@ -126,7 +140,7 @@ def _get_parser() -> argparse.ArgumentParser: >> "--snapshot", >> "--git-ref", >> action=3D_env_arg("DTS_DPDK_TARBALL"), >> - default=3D"dpdk.tar.xz", >> + default=3DSETTINGS.dpdk_tarball_path, >> type=3DPath, >> help=3D"[DTS_DPDK_TARBALL] Path to DPDK source code tarball or = a git commit ID, " >> "tag ID or tree ID to test. To test local changes, first commit= them, " >> @@ -136,7 +150,7 @@ def _get_parser() -> argparse.ArgumentParser: >> parser.add_argument( >> "--compile-timeout", >> action=3D_env_arg("DTS_COMPILE_TIMEOUT"), >> - default=3D1200, >> + default=3DSETTINGS.compile_timeout, >> type=3Dfloat, >> help=3D"[DTS_COMPILE_TIMEOUT] The timeout for compiling DPDK.", >> ) >> @@ -153,7 +167,7 @@ def _get_parser() -> argparse.ArgumentParser: >> "--re-run", >> "--re_run", >> action=3D_env_arg("DTS_RERUN"), >> - default=3D0, >> + default=3DSETTINGS.re_run, >> type=3Dint, >> help=3D"[DTS_RERUN] Re-run each test case the specified amount = of times " >> "if a test failure occurs", >> @@ -162,23 +176,22 @@ def _get_parser() -> argparse.ArgumentParser: >> return parser >> >> >> -def _get_settings() -> _Settings: >> +def get_settings() -> Settings: >> parsed_args =3D _get_parser().parse_args() >> - return _Settings( >> + return Settings( >> config_file_path=3Dparsed_args.config_file, >> output_dir=3Dparsed_args.output_dir, >> timeout=3Dparsed_args.timeout, >> - verbose=3D(parsed_args.verbose =3D=3D "Y"), >> - skip_setup=3D(parsed_args.skip_setup =3D=3D "Y"), >> + verbose=3Dparsed_args.verbose, >> + skip_setup=3Dparsed_args.skip_setup, >> dpdk_tarball_path=3DPath( >> - DPDKGitTarball(parsed_args.tarball, parsed_args.output_dir) >> - ) >> - if not os.path.exists(parsed_args.tarball) >> - else Path(parsed_args.tarball), >> + Path(DPDKGitTarball(parsed_args.tarball, parsed_args.output= _dir)) >> + if not os.path.exists(parsed_args.tarball) >> + else Path(parsed_args.tarball) >> + ), >> compile_timeout=3Dparsed_args.compile_timeout, >> - test_cases=3Dparsed_args.test_cases.split(",") if parsed_args.t= est_cases else [], >> + test_cases=3D( >> + parsed_args.test_cases.split(",") if parsed_args.test_cases= else [] >> + ), >> re_run=3Dparsed_args.re_run, >> ) >> - >> - >> -SETTINGS: _Settings =3D _get_settings() >> diff --git a/dts/framework/test_result.py b/dts/framework/test_result.py >> index f0fbe80f6f..603e18872c 100644 >> --- a/dts/framework/test_result.py >> +++ b/dts/framework/test_result.py >> @@ -254,7 +254,7 @@ def add_build_target( >> self._inner_results.append(build_target_result) >> return build_target_result >> >> - def add_sut_info(self, sut_info: NodeInfo): >> + def add_sut_info(self, sut_info: NodeInfo) -> None: >> self.sut_os_name =3D sut_info.os_name >> self.sut_os_version =3D sut_info.os_version >> self.sut_kernel_version =3D sut_info.kernel_version >> @@ -297,7 +297,7 @@ def add_execution(self, sut_node: NodeConfiguration)= -> ExecutionResult: >> self._inner_results.append(execution_result) >> return execution_result >> >> - def add_error(self, error) -> None: >> + def add_error(self, error: Exception) -> None: >> self._errors.append(error) >> >> def process(self) -> None: >> diff --git a/dts/framework/test_suite.py b/dts/framework/test_suite.py >> index 3b890c0451..d53553bf34 100644 >> --- a/dts/framework/test_suite.py >> +++ b/dts/framework/test_suite.py >> @@ -11,7 +11,7 @@ >> import re >> from ipaddress import IPv4Interface, IPv6Interface, ip_interface >> from types import MethodType >> -from typing import Union >> +from typing import Any, Union >> >> from scapy.layers.inet import IP # type: ignore[import] >> from scapy.layers.l2 import Ether # type: ignore[import] >> @@ -26,8 +26,7 @@ >> from .logger import DTSLOG, getLogger >> from .settings import SETTINGS >> from .test_result import BuildTargetResult, Result, TestCaseResult, Tes= tSuiteResult >> -from .testbed_model import SutNode, TGNode >> -from .testbed_model.hw.port import Port, PortLink >> +from .testbed_model import Port, PortLink, SutNode, TGNode >> from .utils import get_packet_summaries >> >> >> @@ -453,7 +452,7 @@ def _execute_test_case( >> >> >> def get_test_suites(testsuite_module_path: str) -> list[type[TestSuite]= ]: >> - def is_test_suite(object) -> bool: >> + def is_test_suite(object: Any) -> bool: >> try: >> if issubclass(object, TestSuite) and object is not TestSuit= e: >> return True >> diff --git a/dts/framework/testbed_model/__init__.py b/dts/framework/tes= tbed_model/__init__.py >> index 5cbb859e47..8ced05653b 100644 >> --- a/dts/framework/testbed_model/__init__.py >> +++ b/dts/framework/testbed_model/__init__.py >> @@ -9,15 +9,9 @@ >> >> # pylama:ignore=3DW0611 >> >> -from .hw import ( >> - LogicalCore, >> - LogicalCoreCount, >> - LogicalCoreCountFilter, >> - LogicalCoreList, >> - LogicalCoreListFilter, >> - VirtualDevice, >> - lcore_filter, >> -) >> +from .cpu import LogicalCoreCount, LogicalCoreCountFilter, LogicalCoreL= ist >> from .node import Node >> +from .port import Port, PortLink >> from .sut_node import SutNode >> from .tg_node import TGNode >> +from .virtual_device import VirtualDevice >> diff --git a/dts/framework/testbed_model/hw/cpu.py b/dts/framework/testb= ed_model/cpu.py >> similarity index 95% >> rename from dts/framework/testbed_model/hw/cpu.py >> rename to dts/framework/testbed_model/cpu.py >> index d1918a12dc..8fe785dfe4 100644 >> --- a/dts/framework/testbed_model/hw/cpu.py >> +++ b/dts/framework/testbed_model/cpu.py >> @@ -272,3 +272,16 @@ def filter(self) -> list[LogicalCore]: >> ) >> >> return filtered_lcores >> + >> + >> +def lcore_filter( >> + core_list: list[LogicalCore], >> + filter_specifier: LogicalCoreCount | LogicalCoreList, >> + ascending: bool, >> +) -> LogicalCoreFilter: >> + if isinstance(filter_specifier, LogicalCoreList): >> + return LogicalCoreListFilter(core_list, filter_specifier, ascen= ding) >> + elif isinstance(filter_specifier, LogicalCoreCount): >> + return LogicalCoreCountFilter(core_list, filter_specifier, asce= nding) >> + else: >> + raise ValueError(f"Unsupported filter r{filter_specifier}") >> diff --git a/dts/framework/testbed_model/hw/__init__.py b/dts/framework/= testbed_model/hw/__init__.py >> deleted file mode 100644 >> index 88ccac0b0e..0000000000 >> --- a/dts/framework/testbed_model/hw/__init__.py >> +++ /dev/null >> @@ -1,27 +0,0 @@ >> -# SPDX-License-Identifier: BSD-3-Clause >> -# Copyright(c) 2023 PANTHEON.tech s.r.o. >> - >> -# pylama:ignore=3DW0611 >> - >> -from .cpu import ( >> - LogicalCore, >> - LogicalCoreCount, >> - LogicalCoreCountFilter, >> - LogicalCoreFilter, >> - LogicalCoreList, >> - LogicalCoreListFilter, >> -) >> -from .virtual_device import VirtualDevice >> - >> - >> -def lcore_filter( >> - core_list: list[LogicalCore], >> - filter_specifier: LogicalCoreCount | LogicalCoreList, >> - ascending: bool, >> -) -> LogicalCoreFilter: >> - if isinstance(filter_specifier, LogicalCoreList): >> - return LogicalCoreListFilter(core_list, filter_specifier, ascen= ding) >> - elif isinstance(filter_specifier, LogicalCoreCount): >> - return LogicalCoreCountFilter(core_list, filter_specifier, asce= nding) >> - else: >> - raise ValueError(f"Unsupported filter r{filter_specifier}") >> diff --git a/dts/framework/remote_session/linux_session.py b/dts/framewo= rk/testbed_model/linux_session.py >> similarity index 97% >> rename from dts/framework/remote_session/linux_session.py >> rename to dts/framework/testbed_model/linux_session.py >> index a3f1a6bf3b..f472bb8f0f 100644 >> --- a/dts/framework/remote_session/linux_session.py >> +++ b/dts/framework/testbed_model/linux_session.py >> @@ -9,10 +9,10 @@ >> from typing_extensions import NotRequired >> >> from framework.exception import RemoteCommandExecutionError >> -from framework.testbed_model import LogicalCore >> -from framework.testbed_model.hw.port import Port >> from framework.utils import expand_range >> >> +from .cpu import LogicalCore >> +from .port import Port >> from .posix_session import PosixSession >> >> >> @@ -64,7 +64,7 @@ def get_remote_cpus(self, use_first_core: bool) -> lis= t[LogicalCore]: >> lcores.append(LogicalCore(lcore, core, socket, node)) >> return lcores >> >> - def get_dpdk_file_prefix(self, dpdk_prefix) -> str: >> + def get_dpdk_file_prefix(self, dpdk_prefix: str) -> str: >> return dpdk_prefix >> >> def setup_hugepages(self, hugepage_amount: int, force_first_numa: b= ool) -> None: >> diff --git a/dts/framework/testbed_model/node.py b/dts/framework/testbed= _model/node.py >> index fc01e0bf8e..fa5b143cdd 100644 >> --- a/dts/framework/testbed_model/node.py >> +++ b/dts/framework/testbed_model/node.py >> @@ -12,23 +12,26 @@ >> from typing import Any, Callable, Type, Union >> >> from framework.config import ( >> + OS, >> BuildTargetConfiguration, >> ExecutionConfiguration, >> NodeConfiguration, >> ) >> +from framework.exception import ConfigurationError >> from framework.logger import DTSLOG, getLogger >> -from framework.remote_session import InteractiveShellType, OSSession, c= reate_session >> from framework.settings import SETTINGS >> >> -from .hw import ( >> +from .cpu import ( >> LogicalCore, >> LogicalCoreCount, >> LogicalCoreList, >> LogicalCoreListFilter, >> - VirtualDevice, >> lcore_filter, >> ) >> -from .hw.port import Port >> +from .linux_session import LinuxSession >> +from .os_session import InteractiveShellType, OSSession >> +from .port import Port >> +from .virtual_device import VirtualDevice >> >> >> class Node(ABC): >> @@ -172,9 +175,9 @@ def create_interactive_shell( >> >> return self.main_session.create_interactive_shell( >> shell_cls, >> - app_args, >> timeout, >> privileged, >> + app_args, >> ) >> >> def filter_lcores( >> @@ -205,7 +208,7 @@ def _get_remote_cpus(self) -> None: >> self._logger.info("Getting CPU information.") >> self.lcores =3D self.main_session.get_remote_cpus(self.config.u= se_first_core) >> >> - def _setup_hugepages(self): >> + def _setup_hugepages(self) -> None: >> """ >> Setup hugepages on the Node. Different architectures can supply= different >> amounts of memory for hugepages and numa-based hugepage allocat= ion may need >> @@ -249,3 +252,13 @@ def skip_setup(func: Callable[..., Any]) -> Callabl= e[..., Any]: >> return lambda *args: None >> else: >> return func >> + >> + >> +def create_session( >> + node_config: NodeConfiguration, name: str, logger: DTSLOG >> +) -> OSSession: >> + match node_config.os: >> + case OS.linux: >> + return LinuxSession(node_config, name, logger) >> + case _: >> + raise ConfigurationError(f"Unsupported OS {node_config.os}"= ) >> diff --git a/dts/framework/remote_session/os_session.py b/dts/framework/= testbed_model/os_session.py >> similarity index 95% >> rename from dts/framework/remote_session/os_session.py >> rename to dts/framework/testbed_model/os_session.py >> index 8a709eac1c..76e595a518 100644 >> --- a/dts/framework/remote_session/os_session.py >> +++ b/dts/framework/testbed_model/os_session.py >> @@ -10,19 +10,19 @@ >> >> from framework.config import Architecture, NodeConfiguration, NodeInfo >> from framework.logger import DTSLOG >> -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 >> -from framework.utils import MesonArgs >> - >> -from .remote import ( >> +from framework.remote_session import ( >> CommandResult, >> InteractiveRemoteSession, >> + InteractiveShell, >> RemoteSession, >> create_interactive_session, >> create_remote_session, >> ) >> +from framework.settings import SETTINGS >> +from framework.utils import MesonArgs >> + >> +from .cpu import LogicalCore >> +from .port import Port >> >> InteractiveShellType =3D TypeVar("InteractiveShellType", bound=3DIntera= ctiveShell) >> >> @@ -85,9 +85,9 @@ def send_command( >> def create_interactive_shell( >> self, >> shell_cls: Type[InteractiveShellType], >> - eal_parameters: str, >> timeout: float, >> privileged: bool, >> + app_args: str, >> ) -> InteractiveShellType: >> """ >> See "create_interactive_shell" in SutNode >> @@ -96,7 +96,7 @@ def create_interactive_shell( >> self.interactive_session.session, >> self._logger, >> self._get_privileged_command if privileged else None, >> - eal_parameters, >> + app_args, >> timeout, >> ) >> >> @@ -113,7 +113,7 @@ def _get_privileged_command(command: str) -> str: >> """ >> >> @abstractmethod >> - def guess_dpdk_remote_dir(self, remote_dir) -> PurePath: >> + def guess_dpdk_remote_dir(self, remote_dir: str | PurePath) -> Pure= Path: >> """ >> Try to find DPDK remote dir in remote_dir. >> """ >> @@ -227,7 +227,7 @@ def kill_cleanup_dpdk_apps(self, dpdk_prefix_list: I= terable[str]) -> None: >> """ >> >> @abstractmethod >> - def get_dpdk_file_prefix(self, dpdk_prefix) -> str: >> + def get_dpdk_file_prefix(self, dpdk_prefix: str) -> str: >> """ >> Get the DPDK file prefix that will be used when running DPDK ap= ps. >> """ >> diff --git a/dts/framework/testbed_model/hw/port.py b/dts/framework/test= bed_model/port.py >> similarity index 100% >> rename from dts/framework/testbed_model/hw/port.py >> rename to dts/framework/testbed_model/port.py >> diff --git a/dts/framework/remote_session/posix_session.py b/dts/framewo= rk/testbed_model/posix_session.py >> similarity index 98% >> rename from dts/framework/remote_session/posix_session.py >> rename to dts/framework/testbed_model/posix_session.py >> index 5da0516e05..1d1d5b1b26 100644 >> --- a/dts/framework/remote_session/posix_session.py >> +++ b/dts/framework/testbed_model/posix_session.py >> @@ -32,7 +32,7 @@ def combine_short_options(**opts: bool) -> str: >> >> return ret_opts >> >> - def guess_dpdk_remote_dir(self, remote_dir) -> PurePosixPath: >> + def guess_dpdk_remote_dir(self, remote_dir: str | PurePath) -> Pure= PosixPath: >> remote_guess =3D self.join_remote_path(remote_dir, "dpdk-*") >> result =3D self.send_command(f"ls -d {remote_guess} | tail -1") >> return PurePosixPath(result.stdout) >> @@ -219,7 +219,7 @@ def _remove_dpdk_runtime_dirs( >> for dpdk_runtime_dir in dpdk_runtime_dirs: >> self.remove_remote_dir(dpdk_runtime_dir) >> >> - def get_dpdk_file_prefix(self, dpdk_prefix) -> str: >> + def get_dpdk_file_prefix(self, dpdk_prefix: str) -> str: >> return "" >> >> def get_compiler_version(self, compiler_name: str) -> str: >> diff --git a/dts/framework/testbed_model/sut_node.py b/dts/framework/tes= tbed_model/sut_node.py >> index 4161d3a4d5..17deea06e2 100644 >> --- a/dts/framework/testbed_model/sut_node.py >> +++ b/dts/framework/testbed_model/sut_node.py >> @@ -15,12 +15,14 @@ >> NodeInfo, >> SutNodeConfiguration, >> ) >> -from framework.remote_session import CommandResult, InteractiveShellTyp= e, OSSession >> +from framework.remote_session import CommandResult >> from framework.settings import SETTINGS >> from framework.utils import MesonArgs >> >> -from .hw import LogicalCoreCount, LogicalCoreList, VirtualDevice >> +from .cpu import LogicalCoreCount, LogicalCoreList >> from .node import Node >> +from .os_session import InteractiveShellType, OSSession >> +from .virtual_device import VirtualDevice >> >> >> class EalParameters(object): >> @@ -307,7 +309,7 @@ def create_eal_parameters( >> prefix: str =3D "dpdk", >> append_prefix_timestamp: bool =3D True, >> no_pci: bool =3D False, >> - vdevs: list[VirtualDevice] =3D None, >> + vdevs: list[VirtualDevice] | None =3D None, >> other_eal_param: str =3D "", >> ) -> "EalParameters": >> """ >> diff --git a/dts/framework/testbed_model/tg_node.py b/dts/framework/test= bed_model/tg_node.py >> index 27025cfa31..166eb8430e 100644 >> --- a/dts/framework/testbed_model/tg_node.py >> +++ b/dts/framework/testbed_model/tg_node.py >> @@ -16,16 +16,11 @@ >> >> from scapy.packet import Packet # type: ignore[import] >> >> -from framework.config import ( >> - ScapyTrafficGeneratorConfig, >> - TGNodeConfiguration, >> - TrafficGeneratorType, >> -) >> -from framework.exception import ConfigurationError >> - >> -from .capturing_traffic_generator import CapturingTrafficGenerator >> -from .hw.port import Port >> +from framework.config import TGNodeConfiguration >> + >> from .node import Node >> +from .port import Port >> +from .traffic_generator import CapturingTrafficGenerator, create_traffi= c_generator >> >> >> class TGNode(Node): >> @@ -80,20 +75,3 @@ def close(self) -> None: >> """Free all resources used by the node""" >> self.traffic_generator.close() >> super(TGNode, self).close() >> - >> - >> -def create_traffic_generator( >> - tg_node: TGNode, traffic_generator_config: ScapyTrafficGeneratorCon= fig >> -) -> CapturingTrafficGenerator: >> - """A factory function for creating traffic generator object from us= er config.""" >> - >> - from .scapy import ScapyTrafficGenerator >> - >> - match traffic_generator_config.traffic_generator_type: >> - case TrafficGeneratorType.SCAPY: >> - return ScapyTrafficGenerator(tg_node, traffic_generator_con= fig) >> - case _: >> - raise ConfigurationError( >> - "Unknown traffic generator: " >> - f"{traffic_generator_config.traffic_generator_type}" >> - ) >> diff --git a/dts/framework/testbed_model/traffic_generator/__init__.py b= /dts/framework/testbed_model/traffic_generator/__init__.py >> new file mode 100644 >> index 0000000000..11bfa1ee0f >> --- /dev/null >> +++ b/dts/framework/testbed_model/traffic_generator/__init__.py >> @@ -0,0 +1,24 @@ >> +# SPDX-License-Identifier: BSD-3-Clause >> +# Copyright(c) 2023 PANTHEON.tech s.r.o. >> + >> +from framework.config import ScapyTrafficGeneratorConfig, TrafficGenera= torType >> +from framework.exception import ConfigurationError >> +from framework.testbed_model.node import Node >> + >> +from .capturing_traffic_generator import CapturingTrafficGenerator >> +from .scapy import ScapyTrafficGenerator >> + >> + >> +def create_traffic_generator( >> + tg_node: Node, traffic_generator_config: ScapyTrafficGeneratorConfi= g >> +) -> CapturingTrafficGenerator: >> + """A factory function for creating traffic generator object from us= er config.""" >> + >> + match traffic_generator_config.traffic_generator_type: >> + case TrafficGeneratorType.SCAPY: >> + return ScapyTrafficGenerator(tg_node, traffic_generator_con= fig) >> + case _: >> + raise ConfigurationError( >> + "Unknown traffic generator: " >> + f"{traffic_generator_config.traffic_generator_type}" >> + ) >> diff --git a/dts/framework/testbed_model/capturing_traffic_generator.py = b/dts/framework/testbed_model/traffic_generator/capturing_traffic_generator= .py >> similarity index 96% >> rename from dts/framework/testbed_model/capturing_traffic_generator.py >> rename to dts/framework/testbed_model/traffic_generator/capturing_traffi= c_generator.py >> index ab98987f8e..e521211ef0 100644 >> --- a/dts/framework/testbed_model/capturing_traffic_generator.py >> +++ b/dts/framework/testbed_model/traffic_generator/capturing_traffic_ge= nerator.py >> @@ -16,9 +16,9 @@ >> from scapy.packet import Packet # type: ignore[import] >> >> from framework.settings import SETTINGS >> +from framework.testbed_model.port import Port >> from framework.utils import get_packet_summaries >> >> -from .hw.port import Port >> from .traffic_generator import TrafficGenerator >> >> >> @@ -130,7 +130,9 @@ def _send_packets_and_capture( >> for the specified duration. It must be able to handle no receiv= ed packets. >> """ >> >> - def _write_capture_from_packets(self, capture_name: str, packets: l= ist[Packet]): >> + def _write_capture_from_packets( >> + self, capture_name: str, packets: list[Packet] >> + ) -> None: >> file_name =3D f"{SETTINGS.output_dir}/{capture_name}.pcap" >> self._logger.debug(f"Writing packets to {file_name}.") >> scapy.utils.wrpcap(file_name, packets) >> diff --git a/dts/framework/testbed_model/scapy.py b/dts/framework/testbe= d_model/traffic_generator/scapy.py >> similarity index 95% >> rename from dts/framework/testbed_model/scapy.py >> rename to dts/framework/testbed_model/traffic_generator/scapy.py >> index af0d4dbb25..51864b6e6b 100644 >> --- a/dts/framework/testbed_model/scapy.py >> +++ b/dts/framework/testbed_model/traffic_generator/scapy.py >> @@ -24,16 +24,15 @@ >> from scapy.packet import Packet # type: ignore[import] >> >> from framework.config import OS, ScapyTrafficGeneratorConfig >> -from framework.logger import DTSLOG, getLogger >> from framework.remote_session import PythonShell >> from framework.settings import SETTINGS >> +from framework.testbed_model.node import Node >> +from framework.testbed_model.port import Port >> >> from .capturing_traffic_generator import ( >> CapturingTrafficGenerator, >> _get_default_capture_name, >> ) >> -from .hw.port import Port >> -from .tg_node import TGNode >> >> """ >> =3D=3D=3D=3D=3D=3D=3D=3D=3D BEGIN RPC FUNCTIONS =3D=3D=3D=3D=3D=3D=3D= =3D=3D >> @@ -146,7 +145,7 @@ def quit(self) -> None: >> self._BaseServer__shutdown_request =3D True >> return None >> >> - def add_rpc_function(self, name: str, function_bytes: xmlrpc.client= .Binary): >> + def add_rpc_function(self, name: str, function_bytes: xmlrpc.client= .Binary) -> None: >> """Add a function to the server. >> >> This is meant to be executed remotely. >> @@ -191,15 +190,9 @@ class ScapyTrafficGenerator(CapturingTrafficGenerat= or): >> session: PythonShell >> rpc_server_proxy: xmlrpc.client.ServerProxy >> _config: ScapyTrafficGeneratorConfig >> - _tg_node: TGNode >> - _logger: DTSLOG >> - >> - def __init__(self, tg_node: TGNode, config: ScapyTrafficGeneratorCo= nfig): >> - self._config =3D config >> - self._tg_node =3D tg_node >> - self._logger =3D getLogger( >> - f"{self._tg_node.name} {self._config.traffic_generator_type= }" >> - ) >> + >> + def __init__(self, tg_node: Node, config: ScapyTrafficGeneratorConf= ig): >> + super().__init__(tg_node, config) >> >> assert ( >> self._tg_node.config.os =3D=3D OS.linux >> @@ -235,7 +228,7 @@ def __init__(self, tg_node: TGNode, config: ScapyTra= fficGeneratorConfig): >> function_bytes =3D marshal.dumps(function.__code__) >> self.rpc_server_proxy.add_rpc_function(function.__name__, f= unction_bytes) >> >> - def _start_xmlrpc_server_in_remote_python(self, listen_port: int): >> + def _start_xmlrpc_server_in_remote_python(self, listen_port: int) -= > None: >> # load the source of the function >> src =3D inspect.getsource(QuittableXMLRPCServer) >> # Lines with only whitespace break the repl if in the middle of= a function >> @@ -280,7 +273,7 @@ def _send_packets_and_capture( >> scapy_packets =3D [Ether(packet.data) for packet in xmlrpc_pack= ets] >> return scapy_packets >> >> - def close(self): >> + def close(self) -> None: >> try: >> self.rpc_server_proxy.quit() >> except ConnectionRefusedError: >> diff --git a/dts/framework/testbed_model/traffic_generator.py b/dts/fram= ework/testbed_model/traffic_generator/traffic_generator.py >> similarity index 80% >> rename from dts/framework/testbed_model/traffic_generator.py >> rename to dts/framework/testbed_model/traffic_generator/traffic_generato= r.py >> index 28c35d3ce4..ea7c3963da 100644 >> --- a/dts/framework/testbed_model/traffic_generator.py >> +++ b/dts/framework/testbed_model/traffic_generator/traffic_generator.py >> @@ -12,11 +12,12 @@ >> >> from scapy.packet import Packet # type: ignore[import] >> >> -from framework.logger import DTSLOG >> +from framework.config import TrafficGeneratorConfig >> +from framework.logger import DTSLOG, getLogger >> +from framework.testbed_model.node import Node >> +from framework.testbed_model.port import Port >> from framework.utils import get_packet_summaries >> >> -from .hw.port import Port >> - >> >> class TrafficGenerator(ABC): >> """The base traffic generator. >> @@ -24,8 +25,17 @@ class TrafficGenerator(ABC): >> Defines the few basic methods that each traffic generator must impl= ement. >> """ >> >> + _config: TrafficGeneratorConfig >> + _tg_node: Node > > > Is there a benefit to changing this to be a node instead of a TGNode? Wou= ldn't we want the capabilities of the TGNode to be accessible in the Traffi= cGenerator class? > The benefit is that it works :-). If this was TGNode there would be circular imports. It's possible this should be done differently, but I wanted to do as little as possible to make the doc generation work. Anything more would be out of scope of this patch I feel. >> >> _logger: DTSLOG >> >> + def __init__(self, tg_node: Node, config: TrafficGeneratorConfig): >> + self._config =3D config >> + self._tg_node =3D tg_node >> + self._logger =3D getLogger( >> + f"{self._tg_node.name} {self._config.traffic_generator_type= }" >> + ) >> + >> def send_packet(self, packet: Packet, port: Port) -> None: >> """Send a packet and block until it is fully sent. >> >> diff --git a/dts/framework/testbed_model/hw/virtual_device.py b/dts/fram= ework/testbed_model/virtual_device.py >> similarity index 100% >> rename from dts/framework/testbed_model/hw/virtual_device.py >> rename to dts/framework/testbed_model/virtual_device.py >> diff --git a/dts/framework/utils.py b/dts/framework/utils.py >> index d27c2c5b5f..f0c916471c 100644 >> --- a/dts/framework/utils.py >> +++ b/dts/framework/utils.py >> @@ -7,7 +7,6 @@ >> import json >> import os >> import subprocess >> -import sys >> from enum import Enum >> from pathlib import Path >> from subprocess import SubprocessError >> @@ -16,35 +15,7 @@ >> >> from .exception import ConfigurationError >> >> - >> -class StrEnum(Enum): >> - @staticmethod >> - def _generate_next_value_( >> - name: str, start: int, count: int, last_values: object >> - ) -> str: >> - return name >> - >> - def __str__(self) -> str: >> - return self.name >> - >> - >> -REGEX_FOR_PCI_ADDRESS =3D "/[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2= }.[0-9]{1}/" >> - >> - >> -def check_dts_python_version() -> None: >> - if sys.version_info.major < 3 or ( >> - sys.version_info.major =3D=3D 3 and sys.version_info.minor < 10 >> - ): >> - print( >> - RED( >> - ( >> - "WARNING: DTS execution node's python version is lo= wer than" >> - "python 3.10, is deprecated and will not work in fu= ture releases." >> - ) >> - ), >> - file=3Dsys.stderr, >> - ) >> - print(RED("Please use Python >=3D 3.10 instead"), file=3Dsys.st= derr) >> +REGEX_FOR_PCI_ADDRESS: str =3D "/[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA= -F]{2}.[0-9]{1}/" >> >> >> def expand_range(range_str: str) -> list[int]: >> @@ -67,7 +38,7 @@ def expand_range(range_str: str) -> list[int]: >> return expanded_range >> >> >> -def get_packet_summaries(packets: list[Packet]): >> +def get_packet_summaries(packets: list[Packet]) -> str: >> if len(packets) =3D=3D 1: >> packet_summaries =3D packets[0].summary() >> else: >> @@ -77,8 +48,15 @@ def get_packet_summaries(packets: list[Packet]): >> return f"Packet contents: \n{packet_summaries}" >> >> >> -def RED(text: str) -> str: >> - return f"\u001B[31;1m{str(text)}\u001B[0m" >> +class StrEnum(Enum): >> + @staticmethod >> + def _generate_next_value_( >> + name: str, start: int, count: int, last_values: object >> + ) -> str: >> + return name >> + >> + def __str__(self) -> str: >> + return self.name >> >> >> class MesonArgs(object): >> @@ -225,5 +203,5 @@ def _delete_tarball(self) -> None: >> if self._tarball_path and os.path.exists(self._tarball_path): >> os.remove(self._tarball_path) >> >> - def __fspath__(self): >> + def __fspath__(self) -> str: >> return str(self._tarball_path) >> diff --git a/dts/main.py b/dts/main.py >> index 43311fa847..5d4714b0c3 100755 >> --- a/dts/main.py >> +++ b/dts/main.py >> @@ -10,10 +10,17 @@ >> >> import logging >> >> -from framework import dts >> +from framework import settings >> >> >> def main() -> None: >> + """Set DTS settings, then run DTS. >> + >> + The DTS settings are taken from the command line arguments and the = environment variables. >> + """ >> + settings.SETTINGS =3D settings.get_settings() >> + from framework import dts >> + >> dts.run_all() >> >> >> -- >> 2.34.1 >>