Patch Detail
get:
Show a patch.
patch:
Update a patch.
put:
Update a patch.
GET /api/patches/133902/?format=api
http://patches.dpdk.org/api/patches/133902/?format=api", "web_url": "http://patches.dpdk.org/project/dpdk/patch/20231106171601.160749-2-juraj.linkes@pantheon.tech/", "project": { "id": 1, "url": "http://patches.dpdk.org/api/projects/1/?format=api", "name": "DPDK", "link_name": "dpdk", "list_id": "dev.dpdk.org", "list_email": "dev@dpdk.org", "web_url": "http://core.dpdk.org", "scm_url": "git://dpdk.org/dpdk", "webscm_url": "http://git.dpdk.org/dpdk", "list_archive_url": "https://inbox.dpdk.org/dev", "list_archive_url_format": "https://inbox.dpdk.org/dev/{}", "commit_url_format": "" }, "msgid": "<20231106171601.160749-2-juraj.linkes@pantheon.tech>", "list_archive_url": "https://inbox.dpdk.org/dev/20231106171601.160749-2-juraj.linkes@pantheon.tech", "date": "2023-11-06T17:15:39", "name": "[v5,01/23] dts: code adjustments for doc generation", "commit_ref": null, "pull_url": null, "state": "superseded", "archived": true, "hash": "3f8978f8aee980aab66e96c54ab098d4feee8594", "submitter": { "id": 1626, "url": "http://patches.dpdk.org/api/people/1626/?format=api", "name": "Juraj Linkeš", "email": "juraj.linkes@pantheon.tech" }, "delegate": { "id": 2642, "url": "http://patches.dpdk.org/api/users/2642/?format=api", "username": "mcoquelin", "first_name": "Maxime", "last_name": "Coquelin", "email": "maxime.coquelin@redhat.com" }, "mbox": "http://patches.dpdk.org/project/dpdk/patch/20231106171601.160749-2-juraj.linkes@pantheon.tech/mbox/", "series": [ { "id": 30173, "url": "http://patches.dpdk.org/api/series/30173/?format=api", "web_url": "http://patches.dpdk.org/project/dpdk/list/?series=30173", "date": "2023-11-06T17:15:38", "name": "dts: add dts api docs", "version": 5, "mbox": "http://patches.dpdk.org/series/30173/mbox/" } ], "comments": "http://patches.dpdk.org/api/patches/133902/comments/", "check": "fail", "checks": "http://patches.dpdk.org/api/patches/133902/checks/", "tags": {}, "related": [], "headers": { "Return-Path": "<dev-bounces@dpdk.org>", "X-Original-To": "patchwork@inbox.dpdk.org", "Delivered-To": "patchwork@inbox.dpdk.org", "Received": [ "from mails.dpdk.org (mails.dpdk.org [217.70.189.124])\n\tby inbox.dpdk.org (Postfix) with ESMTP id F2844432BB;\n\tMon, 6 Nov 2023 18:16:12 +0100 (CET)", "from mails.dpdk.org (localhost [127.0.0.1])\n\tby mails.dpdk.org (Postfix) with ESMTP id B2E5E40DCD;\n\tMon, 6 Nov 2023 18:16:07 +0100 (CET)", "from mail-ej1-f47.google.com (mail-ej1-f47.google.com\n [209.85.218.47]) by mails.dpdk.org (Postfix) with ESMTP id CBB6340633\n for <dev@dpdk.org>; Mon, 6 Nov 2023 18:16:06 +0100 (CET)", "by mail-ej1-f47.google.com with SMTP id\n a640c23a62f3a-991c786369cso697034466b.1\n for <dev@dpdk.org>; Mon, 06 Nov 2023 09:16:06 -0800 (PST)", "from jlinkes-PT-Latitude-5530.. (ip-46.34.243.197.o2inet.sk.\n [46.34.243.197]) by smtp.gmail.com with ESMTPSA id\n s10-20020a170906354a00b009b947aacb4bsm47016eja.191.2023.11.06.09.16.04\n (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n Mon, 06 Nov 2023 09:16:05 -0800 (PST)" ], "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=pantheon.tech; s=google; t=1699290966; x=1699895766; darn=dpdk.org;\n h=content-transfer-encoding:mime-version:references:in-reply-to\n :message-id:date:subject:cc:to:from:from:to:cc:subject:date\n :message-id:reply-to;\n bh=o5OgTVzJODANDOBqIUh4rKdOwoKz/WvRfL+p6mvGZAE=;\n b=B3pJ7/qWJQ8fmfppypTzCQ8LjHd0dkWAc0DTyPqCqncoE4tJoyDxryQ4+UUxifOTKq\n 78K4fmLflRkkm+qts/eyHXi0mskcIx9lqcO7BE046wN4HceczkDX6TykA+FS+G1FL10G\n kknW86KvJyOC7Qw652hxK9YdxrgvGXBZ9REpguI3/k0hl/BhN0gpAjEXK0wIurpVCWiO\n FmvziZuOhj70UTlSgsoTx7dxW+YDX0lDzj94N8AxDaNVBeJmBAwJHBAIDMscBs1RurSn\n jW1nUZPgPVQyJbOdaif4p2jkPumIQNL1pun11fKOGfsjnMlB9MmkXYx8kuPN179gVCRu\n +acA==", "X-Google-DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=1e100.net; s=20230601; t=1699290966; x=1699895766;\n h=content-transfer-encoding:mime-version:references:in-reply-to\n :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc\n :subject:date:message-id:reply-to;\n bh=o5OgTVzJODANDOBqIUh4rKdOwoKz/WvRfL+p6mvGZAE=;\n b=eLTTOMLQxINgDB3RGZGQguHCac6p6SovolNZRqJoqdwChRzMyNKEt6O7XZnnvRjDWH\n Vc04N6hAasmUlz2XIfghyUYwlCdGMlRcTCq/1vdgkY1qQ7AtBEECLSm6MYnvmg0Tc5e+\n JKBxPs/+mty8A7c2MtjloZR08/7+i8Xp2AnVP7YoTLrY8yJ8Rjux093+g6b0YG9exjKZ\n OCoA0a35v29KH6WVkgxJod+q/cb7wUeuc+S5+5KKFk4NEzNVuklWfnZD/MIAQbqs+Zvp\n /JDlJrw017lECF4dRU+8J6pkryA+QG6PpXW149th/6ZURY7ES4NU9KfPmaCU9pq/xBLU\n EoyA==", "X-Gm-Message-State": "AOJu0YwlyASOzcNaoC3jE8/ODgA6hKOZaByqbTpp27H88UYagwSNtiWE\n 06IqJlLvXZoohYr0BKUdIeX1wg==", "X-Google-Smtp-Source": "\n AGHT+IHc9BIeoPAr1HtJebwtIPUV2XTojDeEl9GKME00i3L3N4oPkVAXwj/FLnd9sBW4NM6hPmJPVg==", "X-Received": "by 2002:a17:906:dc91:b0:9c5:7f8b:bafc with SMTP id\n cs17-20020a170906dc9100b009c57f8bbafcmr14264568ejc.22.1699290966187;\n Mon, 06 Nov 2023 09:16:06 -0800 (PST)", "From": "=?utf-8?q?Juraj_Linke=C5=A1?= <juraj.linkes@pantheon.tech>", "To": "thomas@monjalon.net, Honnappa.Nagarahalli@arm.com,\n bruce.richardson@intel.com, jspewock@iol.unh.edu, probb@iol.unh.edu,\n paul.szczepanek@arm.com, yoan.picchi@foss.arm.com", "Cc": "dev@dpdk.org, =?utf-8?q?Juraj_Linke=C5=A1?= <juraj.linkes@pantheon.tech>", "Subject": "[PATCH v5 01/23] dts: code adjustments for doc generation", "Date": "Mon, 6 Nov 2023 18:15:39 +0100", "Message-Id": "<20231106171601.160749-2-juraj.linkes@pantheon.tech>", "X-Mailer": "git-send-email 2.34.1", "In-Reply-To": "<20231106171601.160749-1-juraj.linkes@pantheon.tech>", "References": "<20230831100407.59865-1-juraj.linkes@pantheon.tech>\n <20231106171601.160749-1-juraj.linkes@pantheon.tech>", "MIME-Version": "1.0", "Content-Type": "text/plain; charset=UTF-8", "Content-Transfer-Encoding": "8bit", "X-BeenThere": "dev@dpdk.org", "X-Mailman-Version": "2.1.29", "Precedence": "list", "List-Id": "DPDK patches and discussions <dev.dpdk.org>", "List-Unsubscribe": "<https://mails.dpdk.org/options/dev>,\n <mailto:dev-request@dpdk.org?subject=unsubscribe>", "List-Archive": "<http://mails.dpdk.org/archives/dev/>", "List-Post": "<mailto:dev@dpdk.org>", "List-Help": "<mailto:dev-request@dpdk.org?subject=help>", "List-Subscribe": "<https://mails.dpdk.org/listinfo/dev>,\n <mailto:dev-request@dpdk.org?subject=subscribe>", "Errors-To": "dev-bounces@dpdk.org" }, "content": "The standard Python tool for generating API documentation, Sphinx,\nimports modules one-by-one when generating the documentation. This\nrequires code changes:\n* properly guarding argument parsing in the if __name__ == '__main__'\n block,\n* the logger used by DTS runner underwent the same treatment so that it\n doesn't create log files outside of a DTS run,\n* however, DTS uses the arguments to construct an object holding global\n variables. The defaults for the global variables needed to be moved\n from argument parsing elsewhere,\n* importing the remote_session module from framework resulted in\n circular imports because of one module trying to import another\n module. This is fixed by reorganizing the code,\n* some code reorganization was done because the resulting structure\n makes more sense, improving documentation clarity.\n\nThe are some other changes which are documentation related:\n* added missing type annotation so they appear in the generated docs,\n* reordered arguments in some methods,\n* removed superfluous arguments and attributes,\n* change private functions/methods/attributes to private and vice-versa.\n\nThe above all appear in the generated documentation and the with them,\nthe documentation is improved.\n\nSigned-off-by: Juraj Linkeš <juraj.linkes@pantheon.tech>\n---\n dts/framework/config/__init__.py | 10 ++-\n dts/framework/dts.py | 33 +++++--\n dts/framework/exception.py | 54 +++++-------\n dts/framework/remote_session/__init__.py | 41 ++++-----\n .../interactive_remote_session.py | 0\n .../{remote => }/interactive_shell.py | 0\n .../{remote => }/python_shell.py | 0\n .../remote_session/remote/__init__.py | 27 ------\n .../{remote => }/remote_session.py | 0\n .../{remote => }/ssh_session.py | 12 +--\n .../{remote => }/testpmd_shell.py | 0\n dts/framework/settings.py | 87 +++++++++++--------\n dts/framework/test_result.py | 4 +-\n dts/framework/test_suite.py | 7 +-\n dts/framework/testbed_model/__init__.py | 12 +--\n dts/framework/testbed_model/{hw => }/cpu.py | 13 +++\n dts/framework/testbed_model/hw/__init__.py | 27 ------\n .../linux_session.py | 6 +-\n dts/framework/testbed_model/node.py | 26 ++++--\n .../os_session.py | 22 ++---\n dts/framework/testbed_model/{hw => }/port.py | 0\n .../posix_session.py | 4 +-\n dts/framework/testbed_model/sut_node.py | 8 +-\n dts/framework/testbed_model/tg_node.py | 30 +------\n .../traffic_generator/__init__.py | 24 +++++\n .../capturing_traffic_generator.py | 6 +-\n .../{ => traffic_generator}/scapy.py | 23 ++---\n .../traffic_generator.py | 16 +++-\n .../testbed_model/{hw => }/virtual_device.py | 0\n dts/framework/utils.py | 46 +++-------\n dts/main.py | 9 +-\n 31 files changed, 259 insertions(+), 288 deletions(-)\n rename dts/framework/remote_session/{remote => }/interactive_remote_session.py (100%)\n rename dts/framework/remote_session/{remote => }/interactive_shell.py (100%)\n rename dts/framework/remote_session/{remote => }/python_shell.py (100%)\n delete mode 100644 dts/framework/remote_session/remote/__init__.py\n rename dts/framework/remote_session/{remote => }/remote_session.py (100%)\n rename dts/framework/remote_session/{remote => }/ssh_session.py (91%)\n rename dts/framework/remote_session/{remote => }/testpmd_shell.py (100%)\n rename dts/framework/testbed_model/{hw => }/cpu.py (95%)\n delete mode 100644 dts/framework/testbed_model/hw/__init__.py\n rename dts/framework/{remote_session => testbed_model}/linux_session.py (97%)\n rename dts/framework/{remote_session => testbed_model}/os_session.py (95%)\n rename dts/framework/testbed_model/{hw => }/port.py (100%)\n rename dts/framework/{remote_session => testbed_model}/posix_session.py (98%)\n create mode 100644 dts/framework/testbed_model/traffic_generator/__init__.py\n rename dts/framework/testbed_model/{ => traffic_generator}/capturing_traffic_generator.py (96%)\n rename dts/framework/testbed_model/{ => traffic_generator}/scapy.py (95%)\n rename dts/framework/testbed_model/{ => traffic_generator}/traffic_generator.py (80%)\n rename dts/framework/testbed_model/{hw => }/virtual_device.py (100%)", "diff": "diff --git a/dts/framework/config/__init__.py b/dts/framework/config/__init__.py\nindex cb7e00ba34..2044c82611 100644\n--- a/dts/framework/config/__init__.py\n+++ b/dts/framework/config/__init__.py\n@@ -17,6 +17,7 @@\n import warlock # type: ignore[import]\n import yaml\n \n+from framework.exception import ConfigurationError\n from framework.settings import SETTINGS\n from framework.utils import StrEnum\n \n@@ -89,7 +90,7 @@ class TrafficGeneratorConfig:\n traffic_generator_type: TrafficGeneratorType\n \n @staticmethod\n- def from_dict(d: dict):\n+ def from_dict(d: dict) -> \"ScapyTrafficGeneratorConfig\":\n # This looks useless now, but is designed to allow expansion to traffic\n # generators that require more configuration later.\n match TrafficGeneratorType(d[\"type\"]):\n@@ -97,6 +98,10 @@ def from_dict(d: dict):\n return ScapyTrafficGeneratorConfig(\n traffic_generator_type=TrafficGeneratorType.SCAPY\n )\n+ case _:\n+ raise ConfigurationError(\n+ f'Unknown traffic generator type \"{d[\"type\"]}\".'\n+ )\n \n \n @dataclass(slots=True, frozen=True)\n@@ -324,6 +329,3 @@ def load_config() -> Configuration:\n config: dict[str, Any] = warlock.model_factory(schema, name=\"_Config\")(config_data)\n config_obj: Configuration = Configuration.from_dict(dict(config))\n return config_obj\n-\n-\n-CONFIGURATION = load_config()\ndiff --git a/dts/framework/dts.py b/dts/framework/dts.py\nindex f773f0c38d..4c7fb0c40a 100644\n--- a/dts/framework/dts.py\n+++ b/dts/framework/dts.py\n@@ -6,19 +6,19 @@\n import sys\n \n from .config import (\n- CONFIGURATION,\n BuildTargetConfiguration,\n ExecutionConfiguration,\n TestSuiteConfig,\n+ load_config,\n )\n from .exception import BlockingTestSuiteError\n from .logger import DTSLOG, getLogger\n from .test_result import BuildTargetResult, DTSResult, ExecutionResult, Result\n from .test_suite import get_test_suites\n from .testbed_model import SutNode, TGNode\n-from .utils import check_dts_python_version\n \n-dts_logger: DTSLOG = getLogger(\"DTSRunner\")\n+# dummy defaults to satisfy linters\n+dts_logger: DTSLOG = None # type: ignore[assignment]\n result: DTSResult = DTSResult(dts_logger)\n \n \n@@ -30,14 +30,18 @@ def run_all() -> None:\n global dts_logger\n global result\n \n+ # create a regular DTS logger and create a new result with it\n+ dts_logger = getLogger(\"DTSRunner\")\n+ result = DTSResult(dts_logger)\n+\n # check the python version of the server that run dts\n- check_dts_python_version()\n+ _check_dts_python_version()\n \n sut_nodes: dict[str, SutNode] = {}\n tg_nodes: dict[str, TGNode] = {}\n try:\n # for all Execution sections\n- for execution in CONFIGURATION.executions:\n+ for execution in load_config().executions:\n sut_node = sut_nodes.get(execution.system_under_test_node.name)\n tg_node = tg_nodes.get(execution.traffic_generator_node.name)\n \n@@ -82,6 +86,25 @@ def run_all() -> None:\n _exit_dts()\n \n \n+def _check_dts_python_version() -> None:\n+ def RED(text: str) -> str:\n+ return f\"\\u001B[31;1m{str(text)}\\u001B[0m\"\n+\n+ if sys.version_info.major < 3 or (\n+ sys.version_info.major == 3 and sys.version_info.minor < 10\n+ ):\n+ print(\n+ RED(\n+ (\n+ \"WARNING: DTS execution node's python version is lower than\"\n+ \"python 3.10, is deprecated and will not work in future releases.\"\n+ )\n+ ),\n+ file=sys.stderr,\n+ )\n+ print(RED(\"Please use Python >= 3.10 instead\"), file=sys.stderr)\n+\n+\n def _run_execution(\n sut_node: SutNode,\n tg_node: TGNode,\ndiff --git a/dts/framework/exception.py b/dts/framework/exception.py\nindex 001a5a5496..7489c03570 100644\n--- a/dts/framework/exception.py\n+++ b/dts/framework/exception.py\n@@ -42,19 +42,14 @@ class SSHTimeoutError(DTSError):\n Command execution timeout.\n \"\"\"\n \n- command: str\n- output: str\n severity: ClassVar[ErrorSeverity] = ErrorSeverity.SSH_ERR\n+ _command: str\n \n- def __init__(self, command: str, output: str):\n- self.command = command\n- self.output = output\n+ def __init__(self, command: str):\n+ self._command = command\n \n def __str__(self) -> str:\n- return f\"TIMEOUT on {self.command}\"\n-\n- def get_output(self) -> str:\n- return self.output\n+ return f\"TIMEOUT on {self._command}\"\n \n \n class SSHConnectionError(DTSError):\n@@ -62,18 +57,18 @@ class SSHConnectionError(DTSError):\n SSH connection error.\n \"\"\"\n \n- host: str\n- errors: list[str]\n severity: ClassVar[ErrorSeverity] = ErrorSeverity.SSH_ERR\n+ _host: str\n+ _errors: list[str]\n \n def __init__(self, host: str, errors: list[str] | None = None):\n- self.host = host\n- self.errors = [] if errors is None else errors\n+ self._host = host\n+ self._errors = [] if errors is None else errors\n \n def __str__(self) -> str:\n- message = f\"Error trying to connect with {self.host}.\"\n- if self.errors:\n- message += f\" Errors encountered while retrying: {', '.join(self.errors)}\"\n+ message = f\"Error trying to connect with {self._host}.\"\n+ if self._errors:\n+ message += f\" Errors encountered while retrying: {', '.join(self._errors)}\"\n \n return message\n \n@@ -84,14 +79,14 @@ class SSHSessionDeadError(DTSError):\n It can no longer be used.\n \"\"\"\n \n- host: str\n severity: ClassVar[ErrorSeverity] = ErrorSeverity.SSH_ERR\n+ _host: str\n \n def __init__(self, host: str):\n- self.host = host\n+ self._host = host\n \n def __str__(self) -> str:\n- return f\"SSH session with {self.host} has died\"\n+ return f\"SSH session with {self._host} has died\"\n \n \n class ConfigurationError(DTSError):\n@@ -107,18 +102,18 @@ class RemoteCommandExecutionError(DTSError):\n Raised when a command executed on a Node returns a non-zero exit status.\n \"\"\"\n \n- command: str\n- command_return_code: int\n severity: ClassVar[ErrorSeverity] = ErrorSeverity.REMOTE_CMD_EXEC_ERR\n+ command: str\n+ _command_return_code: int\n \n def __init__(self, command: str, command_return_code: int):\n self.command = command\n- self.command_return_code = command_return_code\n+ self._command_return_code = command_return_code\n \n def __str__(self) -> str:\n return (\n f\"Command {self.command} returned a non-zero exit code: \"\n- f\"{self.command_return_code}\"\n+ f\"{self._command_return_code}\"\n )\n \n \n@@ -143,22 +138,15 @@ class TestCaseVerifyError(DTSError):\n Used in test cases to verify the expected behavior.\n \"\"\"\n \n- value: str\n severity: ClassVar[ErrorSeverity] = ErrorSeverity.TESTCASE_VERIFY_ERR\n \n- def __init__(self, value: str):\n- self.value = value\n-\n- def __str__(self) -> str:\n- return repr(self.value)\n-\n \n class BlockingTestSuiteError(DTSError):\n- suite_name: str\n severity: ClassVar[ErrorSeverity] = ErrorSeverity.BLOCKING_TESTSUITE_ERR\n+ _suite_name: str\n \n def __init__(self, suite_name: str) -> None:\n- self.suite_name = suite_name\n+ self._suite_name = suite_name\n \n def __str__(self) -> str:\n- return f\"Blocking suite {self.suite_name} failed.\"\n+ return f\"Blocking suite {self._suite_name} failed.\"\ndiff --git a/dts/framework/remote_session/__init__.py b/dts/framework/remote_session/__init__.py\nindex 00b6d1f03a..5e7ddb2b05 100644\n--- a/dts/framework/remote_session/__init__.py\n+++ b/dts/framework/remote_session/__init__.py\n@@ -12,29 +12,24 @@\n \n # pylama:ignore=W0611\n \n-from framework.config import OS, NodeConfiguration\n-from framework.exception import ConfigurationError\n+from framework.config import NodeConfiguration\n from framework.logger import DTSLOG\n \n-from .linux_session import LinuxSession\n-from .os_session import InteractiveShellType, OSSession\n-from .remote import (\n- CommandResult,\n- InteractiveRemoteSession,\n- InteractiveShell,\n- PythonShell,\n- RemoteSession,\n- SSHSession,\n- TestPmdDevice,\n- TestPmdShell,\n-)\n-\n-\n-def create_session(\n+from .interactive_remote_session import InteractiveRemoteSession\n+from .interactive_shell import InteractiveShell\n+from .python_shell import PythonShell\n+from .remote_session import CommandResult, RemoteSession\n+from .ssh_session import SSHSession\n+from .testpmd_shell import TestPmdShell\n+\n+\n+def create_remote_session(\n node_config: NodeConfiguration, name: str, logger: DTSLOG\n-) -> OSSession:\n- match node_config.os:\n- case OS.linux:\n- return LinuxSession(node_config, name, logger)\n- case _:\n- raise ConfigurationError(f\"Unsupported OS {node_config.os}\")\n+) -> RemoteSession:\n+ return SSHSession(node_config, name, logger)\n+\n+\n+def create_interactive_session(\n+ node_config: NodeConfiguration, logger: DTSLOG\n+) -> InteractiveRemoteSession:\n+ return InteractiveRemoteSession(node_config, logger)\ndiff --git a/dts/framework/remote_session/remote/interactive_remote_session.py b/dts/framework/remote_session/interactive_remote_session.py\nsimilarity index 100%\nrename from dts/framework/remote_session/remote/interactive_remote_session.py\nrename to dts/framework/remote_session/interactive_remote_session.py\ndiff --git a/dts/framework/remote_session/remote/interactive_shell.py b/dts/framework/remote_session/interactive_shell.py\nsimilarity index 100%\nrename from dts/framework/remote_session/remote/interactive_shell.py\nrename to dts/framework/remote_session/interactive_shell.py\ndiff --git a/dts/framework/remote_session/remote/python_shell.py b/dts/framework/remote_session/python_shell.py\nsimilarity index 100%\nrename from dts/framework/remote_session/remote/python_shell.py\nrename to dts/framework/remote_session/python_shell.py\ndiff --git a/dts/framework/remote_session/remote/__init__.py b/dts/framework/remote_session/remote/__init__.py\ndeleted file mode 100644\nindex 06403691a5..0000000000\n--- a/dts/framework/remote_session/remote/__init__.py\n+++ /dev/null\n@@ -1,27 +0,0 @@\n-# SPDX-License-Identifier: BSD-3-Clause\n-# Copyright(c) 2023 PANTHEON.tech s.r.o.\n-# Copyright(c) 2023 University of New Hampshire\n-\n-# pylama:ignore=W0611\n-\n-from framework.config import NodeConfiguration\n-from framework.logger import DTSLOG\n-\n-from .interactive_remote_session import InteractiveRemoteSession\n-from .interactive_shell import InteractiveShell\n-from .python_shell import PythonShell\n-from .remote_session import CommandResult, RemoteSession\n-from .ssh_session import SSHSession\n-from .testpmd_shell import TestPmdDevice, TestPmdShell\n-\n-\n-def create_remote_session(\n- node_config: NodeConfiguration, name: str, logger: DTSLOG\n-) -> RemoteSession:\n- return SSHSession(node_config, name, logger)\n-\n-\n-def create_interactive_session(\n- node_config: NodeConfiguration, logger: DTSLOG\n-) -> InteractiveRemoteSession:\n- return InteractiveRemoteSession(node_config, logger)\ndiff --git a/dts/framework/remote_session/remote/remote_session.py b/dts/framework/remote_session/remote_session.py\nsimilarity index 100%\nrename from dts/framework/remote_session/remote/remote_session.py\nrename to dts/framework/remote_session/remote_session.py\ndiff --git a/dts/framework/remote_session/remote/ssh_session.py b/dts/framework/remote_session/ssh_session.py\nsimilarity index 91%\nrename from dts/framework/remote_session/remote/ssh_session.py\nrename to dts/framework/remote_session/ssh_session.py\nindex 8d127f1601..cee11d14d6 100644\n--- a/dts/framework/remote_session/remote/ssh_session.py\n+++ b/dts/framework/remote_session/ssh_session.py\n@@ -18,9 +18,7 @@\n SSHException,\n )\n \n-from framework.config import NodeConfiguration\n from framework.exception import SSHConnectionError, SSHSessionDeadError, SSHTimeoutError\n-from framework.logger import DTSLOG\n \n from .remote_session import CommandResult, RemoteSession\n \n@@ -45,14 +43,6 @@ class SSHSession(RemoteSession):\n \n session: Connection\n \n- def __init__(\n- self,\n- node_config: NodeConfiguration,\n- session_name: str,\n- logger: DTSLOG,\n- ):\n- super(SSHSession, self).__init__(node_config, session_name, logger)\n-\n def _connect(self) -> None:\n errors = []\n retry_attempts = 10\n@@ -117,7 +107,7 @@ def _send_command(\n \n except CommandTimedOut as e:\n self._logger.exception(e)\n- raise SSHTimeoutError(command, e.result.stderr) from e\n+ raise SSHTimeoutError(command) from e\n \n return CommandResult(\n self.name, command, output.stdout, output.stderr, output.return_code\ndiff --git a/dts/framework/remote_session/remote/testpmd_shell.py b/dts/framework/remote_session/testpmd_shell.py\nsimilarity index 100%\nrename from dts/framework/remote_session/remote/testpmd_shell.py\nrename to dts/framework/remote_session/testpmd_shell.py\ndiff --git a/dts/framework/settings.py b/dts/framework/settings.py\nindex cfa39d011b..7f5841d073 100644\n--- a/dts/framework/settings.py\n+++ b/dts/framework/settings.py\n@@ -6,7 +6,7 @@\n import argparse\n import os\n from collections.abc import Callable, Iterable, Sequence\n-from dataclasses import dataclass\n+from dataclasses import dataclass, field\n from pathlib import Path\n from typing import Any, TypeVar\n \n@@ -22,8 +22,8 @@ def __init__(\n option_strings: Sequence[str],\n dest: str,\n nargs: str | int | None = None,\n- const: str | None = None,\n- default: str = None,\n+ const: bool | None = None,\n+ default: Any = None,\n type: Callable[[str], _T | argparse.FileType | None] = None,\n choices: Iterable[_T] | None = None,\n required: bool = False,\n@@ -32,6 +32,12 @@ def __init__(\n ) -> None:\n env_var_value = os.environ.get(env_var)\n default = env_var_value or default\n+ if const is not None:\n+ nargs = 0\n+ default = const if env_var_value else default\n+ type = None\n+ choices = None\n+ metavar = None\n super(_EnvironmentArgument, self).__init__(\n option_strings,\n dest,\n@@ -52,22 +58,28 @@ def __call__(\n values: Any,\n option_string: str = None,\n ) -> None:\n- setattr(namespace, self.dest, values)\n+ if self.const is not None:\n+ setattr(namespace, self.dest, self.const)\n+ else:\n+ setattr(namespace, self.dest, values)\n \n return _EnvironmentArgument\n \n \n-@dataclass(slots=True, frozen=True)\n-class _Settings:\n- config_file_path: str\n- output_dir: str\n- timeout: float\n- verbose: bool\n- skip_setup: bool\n- dpdk_tarball_path: Path\n- compile_timeout: float\n- test_cases: list\n- re_run: int\n+@dataclass(slots=True)\n+class Settings:\n+ config_file_path: Path = Path(__file__).parent.parent.joinpath(\"conf.yaml\")\n+ output_dir: str = \"output\"\n+ timeout: float = 15\n+ verbose: bool = False\n+ skip_setup: bool = False\n+ dpdk_tarball_path: Path | str = \"dpdk.tar.xz\"\n+ compile_timeout: float = 1200\n+ test_cases: list[str] = field(default_factory=list)\n+ re_run: int = 0\n+\n+\n+SETTINGS: Settings = Settings()\n \n \n def _get_parser() -> argparse.ArgumentParser:\n@@ -81,7 +93,8 @@ def _get_parser() -> argparse.ArgumentParser:\n parser.add_argument(\n \"--config-file\",\n action=_env_arg(\"DTS_CFG_FILE\"),\n- default=\"conf.yaml\",\n+ default=SETTINGS.config_file_path,\n+ type=Path,\n help=\"[DTS_CFG_FILE] configuration file that describes the test cases, SUTs \"\n \"and targets.\",\n )\n@@ -90,7 +103,7 @@ def _get_parser() -> argparse.ArgumentParser:\n \"--output-dir\",\n \"--output\",\n action=_env_arg(\"DTS_OUTPUT_DIR\"),\n- default=\"output\",\n+ default=SETTINGS.output_dir,\n help=\"[DTS_OUTPUT_DIR] Output directory where dts logs and results are saved.\",\n )\n \n@@ -98,7 +111,7 @@ def _get_parser() -> argparse.ArgumentParser:\n \"-t\",\n \"--timeout\",\n action=_env_arg(\"DTS_TIMEOUT\"),\n- default=15,\n+ default=SETTINGS.timeout,\n type=float,\n help=\"[DTS_TIMEOUT] The default timeout for all DTS operations except for \"\n \"compiling DPDK.\",\n@@ -108,8 +121,9 @@ def _get_parser() -> argparse.ArgumentParser:\n \"-v\",\n \"--verbose\",\n action=_env_arg(\"DTS_VERBOSE\"),\n- default=\"N\",\n- help=\"[DTS_VERBOSE] Set to 'Y' to enable verbose output, logging all messages \"\n+ default=SETTINGS.verbose,\n+ const=True,\n+ help=\"[DTS_VERBOSE] Specify to enable verbose output, logging all messages \"\n \"to the console.\",\n )\n \n@@ -117,8 +131,8 @@ def _get_parser() -> argparse.ArgumentParser:\n \"-s\",\n \"--skip-setup\",\n action=_env_arg(\"DTS_SKIP_SETUP\"),\n- default=\"N\",\n- help=\"[DTS_SKIP_SETUP] Set to 'Y' to skip all setup steps on SUT and TG nodes.\",\n+ const=True,\n+ help=\"[DTS_SKIP_SETUP] Specify to skip all setup steps on SUT and TG nodes.\",\n )\n \n parser.add_argument(\n@@ -126,7 +140,7 @@ def _get_parser() -> argparse.ArgumentParser:\n \"--snapshot\",\n \"--git-ref\",\n action=_env_arg(\"DTS_DPDK_TARBALL\"),\n- default=\"dpdk.tar.xz\",\n+ default=SETTINGS.dpdk_tarball_path,\n type=Path,\n help=\"[DTS_DPDK_TARBALL] Path to DPDK source code tarball or a git commit ID, \"\n \"tag ID or tree ID to test. To test local changes, first commit them, \"\n@@ -136,7 +150,7 @@ def _get_parser() -> argparse.ArgumentParser:\n parser.add_argument(\n \"--compile-timeout\",\n action=_env_arg(\"DTS_COMPILE_TIMEOUT\"),\n- default=1200,\n+ default=SETTINGS.compile_timeout,\n type=float,\n help=\"[DTS_COMPILE_TIMEOUT] The timeout for compiling DPDK.\",\n )\n@@ -153,7 +167,7 @@ def _get_parser() -> argparse.ArgumentParser:\n \"--re-run\",\n \"--re_run\",\n action=_env_arg(\"DTS_RERUN\"),\n- default=0,\n+ default=SETTINGS.re_run,\n type=int,\n help=\"[DTS_RERUN] Re-run each test case the specified amount of times \"\n \"if a test failure occurs\",\n@@ -162,23 +176,22 @@ def _get_parser() -> argparse.ArgumentParser:\n return parser\n \n \n-def _get_settings() -> _Settings:\n+def get_settings() -> Settings:\n parsed_args = _get_parser().parse_args()\n- return _Settings(\n+ return Settings(\n config_file_path=parsed_args.config_file,\n output_dir=parsed_args.output_dir,\n timeout=parsed_args.timeout,\n- verbose=(parsed_args.verbose == \"Y\"),\n- skip_setup=(parsed_args.skip_setup == \"Y\"),\n+ verbose=parsed_args.verbose,\n+ skip_setup=parsed_args.skip_setup,\n dpdk_tarball_path=Path(\n- DPDKGitTarball(parsed_args.tarball, parsed_args.output_dir)\n- )\n- if not os.path.exists(parsed_args.tarball)\n- else Path(parsed_args.tarball),\n+ Path(DPDKGitTarball(parsed_args.tarball, parsed_args.output_dir))\n+ if not os.path.exists(parsed_args.tarball)\n+ else Path(parsed_args.tarball)\n+ ),\n compile_timeout=parsed_args.compile_timeout,\n- test_cases=parsed_args.test_cases.split(\",\") if parsed_args.test_cases else [],\n+ test_cases=(\n+ parsed_args.test_cases.split(\",\") if parsed_args.test_cases else []\n+ ),\n re_run=parsed_args.re_run,\n )\n-\n-\n-SETTINGS: _Settings = _get_settings()\ndiff --git a/dts/framework/test_result.py b/dts/framework/test_result.py\nindex f0fbe80f6f..603e18872c 100644\n--- a/dts/framework/test_result.py\n+++ b/dts/framework/test_result.py\n@@ -254,7 +254,7 @@ def add_build_target(\n self._inner_results.append(build_target_result)\n return build_target_result\n \n- def add_sut_info(self, sut_info: NodeInfo):\n+ def add_sut_info(self, sut_info: NodeInfo) -> None:\n self.sut_os_name = sut_info.os_name\n self.sut_os_version = sut_info.os_version\n self.sut_kernel_version = sut_info.kernel_version\n@@ -297,7 +297,7 @@ def add_execution(self, sut_node: NodeConfiguration) -> ExecutionResult:\n self._inner_results.append(execution_result)\n return execution_result\n \n- def add_error(self, error) -> None:\n+ def add_error(self, error: Exception) -> None:\n self._errors.append(error)\n \n def process(self) -> None:\ndiff --git a/dts/framework/test_suite.py b/dts/framework/test_suite.py\nindex 3b890c0451..d53553bf34 100644\n--- a/dts/framework/test_suite.py\n+++ b/dts/framework/test_suite.py\n@@ -11,7 +11,7 @@\n import re\n from ipaddress import IPv4Interface, IPv6Interface, ip_interface\n from types import MethodType\n-from typing import Union\n+from typing import Any, Union\n \n from scapy.layers.inet import IP # type: ignore[import]\n from scapy.layers.l2 import Ether # type: ignore[import]\n@@ -26,8 +26,7 @@\n from .logger import DTSLOG, getLogger\n from .settings import SETTINGS\n from .test_result import BuildTargetResult, Result, TestCaseResult, TestSuiteResult\n-from .testbed_model import SutNode, TGNode\n-from .testbed_model.hw.port import Port, PortLink\n+from .testbed_model import Port, PortLink, SutNode, TGNode\n from .utils import get_packet_summaries\n \n \n@@ -453,7 +452,7 @@ def _execute_test_case(\n \n \n def get_test_suites(testsuite_module_path: str) -> list[type[TestSuite]]:\n- def is_test_suite(object) -> bool:\n+ def is_test_suite(object: Any) -> bool:\n try:\n if issubclass(object, TestSuite) and object is not TestSuite:\n return True\ndiff --git a/dts/framework/testbed_model/__init__.py b/dts/framework/testbed_model/__init__.py\nindex 5cbb859e47..8ced05653b 100644\n--- a/dts/framework/testbed_model/__init__.py\n+++ b/dts/framework/testbed_model/__init__.py\n@@ -9,15 +9,9 @@\n \n # pylama:ignore=W0611\n \n-from .hw import (\n- LogicalCore,\n- LogicalCoreCount,\n- LogicalCoreCountFilter,\n- LogicalCoreList,\n- LogicalCoreListFilter,\n- VirtualDevice,\n- lcore_filter,\n-)\n+from .cpu import LogicalCoreCount, LogicalCoreCountFilter, LogicalCoreList\n from .node import Node\n+from .port import Port, PortLink\n from .sut_node import SutNode\n from .tg_node import TGNode\n+from .virtual_device import VirtualDevice\ndiff --git a/dts/framework/testbed_model/hw/cpu.py b/dts/framework/testbed_model/cpu.py\nsimilarity index 95%\nrename from dts/framework/testbed_model/hw/cpu.py\nrename to dts/framework/testbed_model/cpu.py\nindex d1918a12dc..8fe785dfe4 100644\n--- a/dts/framework/testbed_model/hw/cpu.py\n+++ b/dts/framework/testbed_model/cpu.py\n@@ -272,3 +272,16 @@ def filter(self) -> list[LogicalCore]:\n )\n \n return filtered_lcores\n+\n+\n+def lcore_filter(\n+ core_list: list[LogicalCore],\n+ filter_specifier: LogicalCoreCount | LogicalCoreList,\n+ ascending: bool,\n+) -> LogicalCoreFilter:\n+ if isinstance(filter_specifier, LogicalCoreList):\n+ return LogicalCoreListFilter(core_list, filter_specifier, ascending)\n+ elif isinstance(filter_specifier, LogicalCoreCount):\n+ return LogicalCoreCountFilter(core_list, filter_specifier, ascending)\n+ else:\n+ raise ValueError(f\"Unsupported filter r{filter_specifier}\")\ndiff --git a/dts/framework/testbed_model/hw/__init__.py b/dts/framework/testbed_model/hw/__init__.py\ndeleted file mode 100644\nindex 88ccac0b0e..0000000000\n--- a/dts/framework/testbed_model/hw/__init__.py\n+++ /dev/null\n@@ -1,27 +0,0 @@\n-# SPDX-License-Identifier: BSD-3-Clause\n-# Copyright(c) 2023 PANTHEON.tech s.r.o.\n-\n-# pylama:ignore=W0611\n-\n-from .cpu import (\n- LogicalCore,\n- LogicalCoreCount,\n- LogicalCoreCountFilter,\n- LogicalCoreFilter,\n- LogicalCoreList,\n- LogicalCoreListFilter,\n-)\n-from .virtual_device import VirtualDevice\n-\n-\n-def lcore_filter(\n- core_list: list[LogicalCore],\n- filter_specifier: LogicalCoreCount | LogicalCoreList,\n- ascending: bool,\n-) -> LogicalCoreFilter:\n- if isinstance(filter_specifier, LogicalCoreList):\n- return LogicalCoreListFilter(core_list, filter_specifier, ascending)\n- elif isinstance(filter_specifier, LogicalCoreCount):\n- return LogicalCoreCountFilter(core_list, filter_specifier, ascending)\n- else:\n- raise ValueError(f\"Unsupported filter r{filter_specifier}\")\ndiff --git a/dts/framework/remote_session/linux_session.py b/dts/framework/testbed_model/linux_session.py\nsimilarity index 97%\nrename from dts/framework/remote_session/linux_session.py\nrename to dts/framework/testbed_model/linux_session.py\nindex a3f1a6bf3b..f472bb8f0f 100644\n--- a/dts/framework/remote_session/linux_session.py\n+++ b/dts/framework/testbed_model/linux_session.py\n@@ -9,10 +9,10 @@\n from typing_extensions import NotRequired\n \n from framework.exception import RemoteCommandExecutionError\n-from framework.testbed_model import LogicalCore\n-from framework.testbed_model.hw.port import Port\n from framework.utils import expand_range\n \n+from .cpu import LogicalCore\n+from .port import Port\n from .posix_session import PosixSession\n \n \n@@ -64,7 +64,7 @@ def get_remote_cpus(self, use_first_core: bool) -> list[LogicalCore]:\n lcores.append(LogicalCore(lcore, core, socket, node))\n return lcores\n \n- def get_dpdk_file_prefix(self, dpdk_prefix) -> str:\n+ def get_dpdk_file_prefix(self, dpdk_prefix: str) -> str:\n return dpdk_prefix\n \n def setup_hugepages(self, hugepage_amount: int, force_first_numa: bool) -> None:\ndiff --git a/dts/framework/testbed_model/node.py b/dts/framework/testbed_model/node.py\nindex fc01e0bf8e..7571e7b98d 100644\n--- a/dts/framework/testbed_model/node.py\n+++ b/dts/framework/testbed_model/node.py\n@@ -12,23 +12,26 @@\n from typing import Any, Callable, Type, Union\n \n from framework.config import (\n+ OS,\n BuildTargetConfiguration,\n ExecutionConfiguration,\n NodeConfiguration,\n )\n+from framework.exception import ConfigurationError\n from framework.logger import DTSLOG, getLogger\n-from framework.remote_session import InteractiveShellType, OSSession, create_session\n from framework.settings import SETTINGS\n \n-from .hw import (\n+from .cpu import (\n LogicalCore,\n LogicalCoreCount,\n LogicalCoreList,\n LogicalCoreListFilter,\n- VirtualDevice,\n lcore_filter,\n )\n-from .hw.port import Port\n+from .linux_session import LinuxSession\n+from .os_session import InteractiveShellType, OSSession\n+from .port import Port\n+from .virtual_device import VirtualDevice\n \n \n class Node(ABC):\n@@ -69,6 +72,7 @@ def __init__(self, node_config: NodeConfiguration):\n def _init_ports(self) -> None:\n self.ports = [Port(self.name, port_config) for port_config in self.config.ports]\n self.main_session.update_ports(self.ports)\n+\n for port in self.ports:\n self.configure_port_state(port)\n \n@@ -172,9 +176,9 @@ def create_interactive_shell(\n \n return self.main_session.create_interactive_shell(\n shell_cls,\n- app_args,\n timeout,\n privileged,\n+ app_args,\n )\n \n def filter_lcores(\n@@ -205,7 +209,7 @@ def _get_remote_cpus(self) -> None:\n self._logger.info(\"Getting CPU information.\")\n self.lcores = self.main_session.get_remote_cpus(self.config.use_first_core)\n \n- def _setup_hugepages(self):\n+ def _setup_hugepages(self) -> None:\n \"\"\"\n Setup hugepages on the Node. Different architectures can supply different\n amounts of memory for hugepages and numa-based hugepage allocation may need\n@@ -249,3 +253,13 @@ def skip_setup(func: Callable[..., Any]) -> Callable[..., Any]:\n return lambda *args: None\n else:\n return func\n+\n+\n+def create_session(\n+ node_config: NodeConfiguration, name: str, logger: DTSLOG\n+) -> OSSession:\n+ match node_config.os:\n+ case OS.linux:\n+ return LinuxSession(node_config, name, logger)\n+ case _:\n+ raise ConfigurationError(f\"Unsupported OS {node_config.os}\")\ndiff --git a/dts/framework/remote_session/os_session.py b/dts/framework/testbed_model/os_session.py\nsimilarity index 95%\nrename from dts/framework/remote_session/os_session.py\nrename to dts/framework/testbed_model/os_session.py\nindex 8a709eac1c..76e595a518 100644\n--- a/dts/framework/remote_session/os_session.py\n+++ b/dts/framework/testbed_model/os_session.py\n@@ -10,19 +10,19 @@\n \n from framework.config import Architecture, NodeConfiguration, NodeInfo\n from framework.logger import DTSLOG\n-from framework.remote_session.remote import InteractiveShell\n-from framework.settings import SETTINGS\n-from framework.testbed_model import LogicalCore\n-from framework.testbed_model.hw.port import Port\n-from framework.utils import MesonArgs\n-\n-from .remote import (\n+from framework.remote_session import (\n CommandResult,\n InteractiveRemoteSession,\n+ InteractiveShell,\n RemoteSession,\n create_interactive_session,\n create_remote_session,\n )\n+from framework.settings import SETTINGS\n+from framework.utils import MesonArgs\n+\n+from .cpu import LogicalCore\n+from .port import Port\n \n InteractiveShellType = TypeVar(\"InteractiveShellType\", bound=InteractiveShell)\n \n@@ -85,9 +85,9 @@ def send_command(\n def create_interactive_shell(\n self,\n shell_cls: Type[InteractiveShellType],\n- eal_parameters: str,\n timeout: float,\n privileged: bool,\n+ app_args: str,\n ) -> InteractiveShellType:\n \"\"\"\n See \"create_interactive_shell\" in SutNode\n@@ -96,7 +96,7 @@ def create_interactive_shell(\n self.interactive_session.session,\n self._logger,\n self._get_privileged_command if privileged else None,\n- eal_parameters,\n+ app_args,\n timeout,\n )\n \n@@ -113,7 +113,7 @@ def _get_privileged_command(command: str) -> str:\n \"\"\"\n \n @abstractmethod\n- def guess_dpdk_remote_dir(self, remote_dir) -> PurePath:\n+ def guess_dpdk_remote_dir(self, remote_dir: str | PurePath) -> PurePath:\n \"\"\"\n Try to find DPDK remote dir in remote_dir.\n \"\"\"\n@@ -227,7 +227,7 @@ def kill_cleanup_dpdk_apps(self, dpdk_prefix_list: Iterable[str]) -> None:\n \"\"\"\n \n @abstractmethod\n- def get_dpdk_file_prefix(self, dpdk_prefix) -> str:\n+ def get_dpdk_file_prefix(self, dpdk_prefix: str) -> str:\n \"\"\"\n Get the DPDK file prefix that will be used when running DPDK apps.\n \"\"\"\ndiff --git a/dts/framework/testbed_model/hw/port.py b/dts/framework/testbed_model/port.py\nsimilarity index 100%\nrename from dts/framework/testbed_model/hw/port.py\nrename to dts/framework/testbed_model/port.py\ndiff --git a/dts/framework/remote_session/posix_session.py b/dts/framework/testbed_model/posix_session.py\nsimilarity index 98%\nrename from dts/framework/remote_session/posix_session.py\nrename to dts/framework/testbed_model/posix_session.py\nindex 5da0516e05..1d1d5b1b26 100644\n--- a/dts/framework/remote_session/posix_session.py\n+++ b/dts/framework/testbed_model/posix_session.py\n@@ -32,7 +32,7 @@ def combine_short_options(**opts: bool) -> str:\n \n return ret_opts\n \n- def guess_dpdk_remote_dir(self, remote_dir) -> PurePosixPath:\n+ def guess_dpdk_remote_dir(self, remote_dir: str | PurePath) -> PurePosixPath:\n remote_guess = self.join_remote_path(remote_dir, \"dpdk-*\")\n result = self.send_command(f\"ls -d {remote_guess} | tail -1\")\n return PurePosixPath(result.stdout)\n@@ -219,7 +219,7 @@ def _remove_dpdk_runtime_dirs(\n for dpdk_runtime_dir in dpdk_runtime_dirs:\n self.remove_remote_dir(dpdk_runtime_dir)\n \n- def get_dpdk_file_prefix(self, dpdk_prefix) -> str:\n+ def get_dpdk_file_prefix(self, dpdk_prefix: str) -> str:\n return \"\"\n \n def get_compiler_version(self, compiler_name: str) -> str:\ndiff --git a/dts/framework/testbed_model/sut_node.py b/dts/framework/testbed_model/sut_node.py\nindex 202aebfd06..4e33cf02ea 100644\n--- a/dts/framework/testbed_model/sut_node.py\n+++ b/dts/framework/testbed_model/sut_node.py\n@@ -15,12 +15,14 @@\n NodeInfo,\n SutNodeConfiguration,\n )\n-from framework.remote_session import CommandResult, InteractiveShellType, OSSession\n+from framework.remote_session import CommandResult\n from framework.settings import SETTINGS\n from framework.utils import MesonArgs\n \n-from .hw import LogicalCoreCount, LogicalCoreList, VirtualDevice\n+from .cpu import LogicalCoreCount, LogicalCoreList\n from .node import Node\n+from .os_session import InteractiveShellType, OSSession\n+from .virtual_device import VirtualDevice\n \n \n class EalParameters(object):\n@@ -289,7 +291,7 @@ def create_eal_parameters(\n prefix: str = \"dpdk\",\n append_prefix_timestamp: bool = True,\n no_pci: bool = False,\n- vdevs: list[VirtualDevice] = None,\n+ vdevs: list[VirtualDevice] | None = None,\n other_eal_param: str = \"\",\n ) -> \"EalParameters\":\n \"\"\"\ndiff --git a/dts/framework/testbed_model/tg_node.py b/dts/framework/testbed_model/tg_node.py\nindex 27025cfa31..166eb8430e 100644\n--- a/dts/framework/testbed_model/tg_node.py\n+++ b/dts/framework/testbed_model/tg_node.py\n@@ -16,16 +16,11 @@\n \n from scapy.packet import Packet # type: ignore[import]\n \n-from framework.config import (\n- ScapyTrafficGeneratorConfig,\n- TGNodeConfiguration,\n- TrafficGeneratorType,\n-)\n-from framework.exception import ConfigurationError\n-\n-from .capturing_traffic_generator import CapturingTrafficGenerator\n-from .hw.port import Port\n+from framework.config import TGNodeConfiguration\n+\n from .node import Node\n+from .port import Port\n+from .traffic_generator import CapturingTrafficGenerator, create_traffic_generator\n \n \n class TGNode(Node):\n@@ -80,20 +75,3 @@ def close(self) -> None:\n \"\"\"Free all resources used by the node\"\"\"\n self.traffic_generator.close()\n super(TGNode, self).close()\n-\n-\n-def create_traffic_generator(\n- tg_node: TGNode, traffic_generator_config: ScapyTrafficGeneratorConfig\n-) -> CapturingTrafficGenerator:\n- \"\"\"A factory function for creating traffic generator object from user config.\"\"\"\n-\n- from .scapy import ScapyTrafficGenerator\n-\n- match traffic_generator_config.traffic_generator_type:\n- case TrafficGeneratorType.SCAPY:\n- return ScapyTrafficGenerator(tg_node, traffic_generator_config)\n- case _:\n- raise ConfigurationError(\n- \"Unknown traffic generator: \"\n- f\"{traffic_generator_config.traffic_generator_type}\"\n- )\ndiff --git a/dts/framework/testbed_model/traffic_generator/__init__.py b/dts/framework/testbed_model/traffic_generator/__init__.py\nnew file mode 100644\nindex 0000000000..11bfa1ee0f\n--- /dev/null\n+++ b/dts/framework/testbed_model/traffic_generator/__init__.py\n@@ -0,0 +1,24 @@\n+# SPDX-License-Identifier: BSD-3-Clause\n+# Copyright(c) 2023 PANTHEON.tech s.r.o.\n+\n+from framework.config import ScapyTrafficGeneratorConfig, TrafficGeneratorType\n+from framework.exception import ConfigurationError\n+from framework.testbed_model.node import Node\n+\n+from .capturing_traffic_generator import CapturingTrafficGenerator\n+from .scapy import ScapyTrafficGenerator\n+\n+\n+def create_traffic_generator(\n+ tg_node: Node, traffic_generator_config: ScapyTrafficGeneratorConfig\n+) -> CapturingTrafficGenerator:\n+ \"\"\"A factory function for creating traffic generator object from user config.\"\"\"\n+\n+ match traffic_generator_config.traffic_generator_type:\n+ case TrafficGeneratorType.SCAPY:\n+ return ScapyTrafficGenerator(tg_node, traffic_generator_config)\n+ case _:\n+ raise ConfigurationError(\n+ \"Unknown traffic generator: \"\n+ f\"{traffic_generator_config.traffic_generator_type}\"\n+ )\ndiff --git a/dts/framework/testbed_model/capturing_traffic_generator.py b/dts/framework/testbed_model/traffic_generator/capturing_traffic_generator.py\nsimilarity index 96%\nrename from dts/framework/testbed_model/capturing_traffic_generator.py\nrename to dts/framework/testbed_model/traffic_generator/capturing_traffic_generator.py\nindex ab98987f8e..e521211ef0 100644\n--- a/dts/framework/testbed_model/capturing_traffic_generator.py\n+++ b/dts/framework/testbed_model/traffic_generator/capturing_traffic_generator.py\n@@ -16,9 +16,9 @@\n from scapy.packet import Packet # type: ignore[import]\n \n from framework.settings import SETTINGS\n+from framework.testbed_model.port import Port\n from framework.utils import get_packet_summaries\n \n-from .hw.port import Port\n from .traffic_generator import TrafficGenerator\n \n \n@@ -130,7 +130,9 @@ def _send_packets_and_capture(\n for the specified duration. It must be able to handle no received packets.\n \"\"\"\n \n- def _write_capture_from_packets(self, capture_name: str, packets: list[Packet]):\n+ def _write_capture_from_packets(\n+ self, capture_name: str, packets: list[Packet]\n+ ) -> None:\n file_name = f\"{SETTINGS.output_dir}/{capture_name}.pcap\"\n self._logger.debug(f\"Writing packets to {file_name}.\")\n scapy.utils.wrpcap(file_name, packets)\ndiff --git a/dts/framework/testbed_model/scapy.py b/dts/framework/testbed_model/traffic_generator/scapy.py\nsimilarity index 95%\nrename from dts/framework/testbed_model/scapy.py\nrename to dts/framework/testbed_model/traffic_generator/scapy.py\nindex af0d4dbb25..51864b6e6b 100644\n--- a/dts/framework/testbed_model/scapy.py\n+++ b/dts/framework/testbed_model/traffic_generator/scapy.py\n@@ -24,16 +24,15 @@\n from scapy.packet import Packet # type: ignore[import]\n \n from framework.config import OS, ScapyTrafficGeneratorConfig\n-from framework.logger import DTSLOG, getLogger\n from framework.remote_session import PythonShell\n from framework.settings import SETTINGS\n+from framework.testbed_model.node import Node\n+from framework.testbed_model.port import Port\n \n from .capturing_traffic_generator import (\n CapturingTrafficGenerator,\n _get_default_capture_name,\n )\n-from .hw.port import Port\n-from .tg_node import TGNode\n \n \"\"\"\n ========= BEGIN RPC FUNCTIONS =========\n@@ -146,7 +145,7 @@ def quit(self) -> None:\n self._BaseServer__shutdown_request = True\n return None\n \n- def add_rpc_function(self, name: str, function_bytes: xmlrpc.client.Binary):\n+ def add_rpc_function(self, name: str, function_bytes: xmlrpc.client.Binary) -> None:\n \"\"\"Add a function to the server.\n \n This is meant to be executed remotely.\n@@ -191,15 +190,9 @@ class ScapyTrafficGenerator(CapturingTrafficGenerator):\n session: PythonShell\n rpc_server_proxy: xmlrpc.client.ServerProxy\n _config: ScapyTrafficGeneratorConfig\n- _tg_node: TGNode\n- _logger: DTSLOG\n-\n- def __init__(self, tg_node: TGNode, config: ScapyTrafficGeneratorConfig):\n- self._config = config\n- self._tg_node = tg_node\n- self._logger = getLogger(\n- f\"{self._tg_node.name} {self._config.traffic_generator_type}\"\n- )\n+\n+ def __init__(self, tg_node: Node, config: ScapyTrafficGeneratorConfig):\n+ super().__init__(tg_node, config)\n \n assert (\n self._tg_node.config.os == OS.linux\n@@ -235,7 +228,7 @@ def __init__(self, tg_node: TGNode, config: ScapyTrafficGeneratorConfig):\n function_bytes = marshal.dumps(function.__code__)\n self.rpc_server_proxy.add_rpc_function(function.__name__, function_bytes)\n \n- def _start_xmlrpc_server_in_remote_python(self, listen_port: int):\n+ def _start_xmlrpc_server_in_remote_python(self, listen_port: int) -> None:\n # load the source of the function\n src = inspect.getsource(QuittableXMLRPCServer)\n # Lines with only whitespace break the repl if in the middle of a function\n@@ -280,7 +273,7 @@ def _send_packets_and_capture(\n scapy_packets = [Ether(packet.data) for packet in xmlrpc_packets]\n return scapy_packets\n \n- def close(self):\n+ def close(self) -> None:\n try:\n self.rpc_server_proxy.quit()\n except ConnectionRefusedError:\ndiff --git a/dts/framework/testbed_model/traffic_generator.py b/dts/framework/testbed_model/traffic_generator/traffic_generator.py\nsimilarity index 80%\nrename from dts/framework/testbed_model/traffic_generator.py\nrename to dts/framework/testbed_model/traffic_generator/traffic_generator.py\nindex 28c35d3ce4..ea7c3963da 100644\n--- a/dts/framework/testbed_model/traffic_generator.py\n+++ b/dts/framework/testbed_model/traffic_generator/traffic_generator.py\n@@ -12,11 +12,12 @@\n \n from scapy.packet import Packet # type: ignore[import]\n \n-from framework.logger import DTSLOG\n+from framework.config import TrafficGeneratorConfig\n+from framework.logger import DTSLOG, getLogger\n+from framework.testbed_model.node import Node\n+from framework.testbed_model.port import Port\n from framework.utils import get_packet_summaries\n \n-from .hw.port import Port\n-\n \n class TrafficGenerator(ABC):\n \"\"\"The base traffic generator.\n@@ -24,8 +25,17 @@ class TrafficGenerator(ABC):\n Defines the few basic methods that each traffic generator must implement.\n \"\"\"\n \n+ _config: TrafficGeneratorConfig\n+ _tg_node: Node\n _logger: DTSLOG\n \n+ def __init__(self, tg_node: Node, config: TrafficGeneratorConfig):\n+ self._config = config\n+ self._tg_node = tg_node\n+ self._logger = getLogger(\n+ f\"{self._tg_node.name} {self._config.traffic_generator_type}\"\n+ )\n+\n def send_packet(self, packet: Packet, port: Port) -> None:\n \"\"\"Send a packet and block until it is fully sent.\n \ndiff --git a/dts/framework/testbed_model/hw/virtual_device.py b/dts/framework/testbed_model/virtual_device.py\nsimilarity index 100%\nrename from dts/framework/testbed_model/hw/virtual_device.py\nrename to dts/framework/testbed_model/virtual_device.py\ndiff --git a/dts/framework/utils.py b/dts/framework/utils.py\nindex d27c2c5b5f..f0c916471c 100644\n--- a/dts/framework/utils.py\n+++ b/dts/framework/utils.py\n@@ -7,7 +7,6 @@\n import json\n import os\n import subprocess\n-import sys\n from enum import Enum\n from pathlib import Path\n from subprocess import SubprocessError\n@@ -16,35 +15,7 @@\n \n from .exception import ConfigurationError\n \n-\n-class StrEnum(Enum):\n- @staticmethod\n- def _generate_next_value_(\n- name: str, start: int, count: int, last_values: object\n- ) -> str:\n- return name\n-\n- def __str__(self) -> str:\n- return self.name\n-\n-\n-REGEX_FOR_PCI_ADDRESS = \"/[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}.[0-9]{1}/\"\n-\n-\n-def check_dts_python_version() -> None:\n- if sys.version_info.major < 3 or (\n- sys.version_info.major == 3 and sys.version_info.minor < 10\n- ):\n- print(\n- RED(\n- (\n- \"WARNING: DTS execution node's python version is lower than\"\n- \"python 3.10, is deprecated and will not work in future releases.\"\n- )\n- ),\n- file=sys.stderr,\n- )\n- print(RED(\"Please use Python >= 3.10 instead\"), file=sys.stderr)\n+REGEX_FOR_PCI_ADDRESS: str = \"/[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}.[0-9]{1}/\"\n \n \n def expand_range(range_str: str) -> list[int]:\n@@ -67,7 +38,7 @@ def expand_range(range_str: str) -> list[int]:\n return expanded_range\n \n \n-def get_packet_summaries(packets: list[Packet]):\n+def get_packet_summaries(packets: list[Packet]) -> str:\n if len(packets) == 1:\n packet_summaries = packets[0].summary()\n else:\n@@ -77,8 +48,15 @@ def get_packet_summaries(packets: list[Packet]):\n return f\"Packet contents: \\n{packet_summaries}\"\n \n \n-def RED(text: str) -> str:\n- return f\"\\u001B[31;1m{str(text)}\\u001B[0m\"\n+class StrEnum(Enum):\n+ @staticmethod\n+ def _generate_next_value_(\n+ name: str, start: int, count: int, last_values: object\n+ ) -> str:\n+ return name\n+\n+ def __str__(self) -> str:\n+ return self.name\n \n \n class MesonArgs(object):\n@@ -225,5 +203,5 @@ def _delete_tarball(self) -> None:\n if self._tarball_path and os.path.exists(self._tarball_path):\n os.remove(self._tarball_path)\n \n- def __fspath__(self):\n+ def __fspath__(self) -> str:\n return str(self._tarball_path)\ndiff --git a/dts/main.py b/dts/main.py\nindex 43311fa847..5d4714b0c3 100755\n--- a/dts/main.py\n+++ b/dts/main.py\n@@ -10,10 +10,17 @@\n \n import logging\n \n-from framework import dts\n+from framework import settings\n \n \n def main() -> None:\n+ \"\"\"Set DTS settings, then run DTS.\n+\n+ The DTS settings are taken from the command line arguments and the environment variables.\n+ \"\"\"\n+ settings.SETTINGS = settings.get_settings()\n+ from framework import dts\n+\n dts.run_all()\n \n \n", "prefixes": [ "v5", "01/23" ] }{ "id": 133902, "url": "