From: Jeremy Spewock <jspew...@iol.unh.edu>

The DTS framework in its current state supports binding ports to
different drivers on the SUT node but not the TG node. The TG node
already has the information that it needs about the different drivers
that it has available in the configuration file, but it did not
previously have access to the devbind script, so it did not use that
information for anything.

This patch moves the location of the tmp directory as well as the method
for binding ports into the node class rather than the SUT node class and
adds an abstract method for getting the path to the devbind script into
the node class. Then, binding ports to the correct drivers is moved into
the build target setup and run on both nodes.

Bugzilla ID: 1420

Signed-off-by: Jeremy Spewock <jspew...@iol.unh.edu>
---
 dts/framework/runner.py                 |  2 +
 dts/framework/testbed_model/node.py     | 49 ++++++++++++++++++++++++-
 dts/framework/testbed_model/sut_node.py | 39 ++++++--------------
 dts/framework/testbed_model/tg_node.py  | 21 +++++++++++
 dts/framework/utils.py                  |  4 +-
 5 files changed, 85 insertions(+), 30 deletions(-)

diff --git a/dts/framework/runner.py b/dts/framework/runner.py
index ab98de8353..4c884fbcd4 100644
--- a/dts/framework/runner.py
+++ b/dts/framework/runner.py
@@ -486,6 +486,7 @@ def _run_build_target(
 
         try:
             sut_node.set_up_build_target(build_target_config)
+            tg_node.set_up_build_target(build_target_config)
             self._result.dpdk_version = sut_node.dpdk_version
             
build_target_result.add_build_target_info(sut_node.get_build_target_info())
             build_target_result.update_setup(Result.PASS)
@@ -500,6 +501,7 @@ def _run_build_target(
             try:
                 self._logger.set_stage(DtsStage.build_target_teardown)
                 sut_node.tear_down_build_target()
+                tg_node.tear_down_build_target()
                 build_target_result.update_teardown(Result.PASS)
             except Exception as e:
                 self._logger.exception("Build target teardown failed.")
diff --git a/dts/framework/testbed_model/node.py 
b/dts/framework/testbed_model/node.py
index 12a40170ac..6484e16a0f 100644
--- a/dts/framework/testbed_model/node.py
+++ b/dts/framework/testbed_model/node.py
@@ -13,11 +13,18 @@
 The :func:`~Node.skip_setup` decorator can be used without subclassing.
 """
 
-from abc import ABC
+
+from abc import ABC, abstractmethod
 from ipaddress import IPv4Interface, IPv6Interface
+from pathlib import PurePath
 from typing import Any, Callable, Union
 
-from framework.config import OS, NodeConfiguration, TestRunConfiguration
+from framework.config import (
+    OS,
+    BuildTargetConfiguration,
+    NodeConfiguration,
+    TestRunConfiguration,
+)
 from framework.exception import ConfigurationError
 from framework.logger import DTSLogger, get_dts_logger
 from framework.settings import SETTINGS
@@ -58,8 +65,10 @@ class Node(ABC):
     lcores: list[LogicalCore]
     ports: list[Port]
     _logger: DTSLogger
+    _remote_tmp_dir: PurePath
     _other_sessions: list[OSSession]
     _test_run_config: TestRunConfiguration
+    _path_to_devbind_script: PurePath | None
 
     def __init__(self, node_config: NodeConfiguration):
         """Connect to the node and gather info during initialization.
@@ -88,6 +97,8 @@ def __init__(self, node_config: NodeConfiguration):
 
         self._other_sessions = []
         self._init_ports()
+        self._remote_tmp_dir = self.main_session.get_remote_tmp_dir()
+        self._path_to_devbind_script = None
 
     def _init_ports(self) -> None:
         self.ports = [Port(self.name, port_config) for port_config in 
self.config.ports]
@@ -95,6 +106,11 @@ def _init_ports(self) -> None:
         for port in self.ports:
             self.configure_port_state(port)
 
+    @property
+    @abstractmethod
+    def path_to_devbind_script(self) -> PurePath:
+        """The path to the dpdk-devbind.py script on the node."""
+
     def set_up_test_run(self, test_run_config: TestRunConfiguration) -> None:
         """Test run setup steps.
 
@@ -114,6 +130,20 @@ def tear_down_test_run(self) -> None:
         Additional steps can be added by extending the method in subclasses 
with the use of super().
         """
 
+    def set_up_build_target(self, build_target_config: 
BuildTargetConfiguration) -> None:
+        """Bind ports to their DPDK drivers.
+
+        Args:
+            build_target_config: The build target test run configuration 
according to which
+                the setup steps will be taken. This is unused in this method, 
but subclasses that
+                extend this method may need it.
+        """
+        self.bind_ports_to_driver()
+
+    def tear_down_build_target(self) -> None:
+        """Bind ports to their OS drivers."""
+        self.bind_ports_to_driver(for_dpdk=False)
+
     def create_session(self, name: str) -> OSSession:
         """Create and return a new OS-aware remote session.
 
@@ -228,6 +258,21 @@ def skip_setup(func: Callable[..., Any]) -> Callable[..., 
Any]:
         else:
             return func
 
+    def bind_ports_to_driver(self, for_dpdk: bool = True) -> None:
+        """Bind all ports on the node to a driver.
+
+        Args:
+            for_dpdk: If :data:`True`, binds ports to os_driver_for_dpdk.
+                If :data:`False`, binds to os_driver.
+        """
+        for port in self.ports:
+            driver = port.os_driver_for_dpdk if for_dpdk else port.os_driver
+            self.main_session.send_command(
+                f"{self.path_to_devbind_script} -b {driver} --force 
{port.pci}",
+                privileged=True,
+                verify=True,
+            )
+
 
 def create_session(node_config: NodeConfiguration, name: str, logger: 
DTSLogger) -> OSSession:
     """Factory for OS-aware sessions.
diff --git a/dts/framework/testbed_model/sut_node.py 
b/dts/framework/testbed_model/sut_node.py
index 2855fe0276..d68270eee1 100644
--- a/dts/framework/testbed_model/sut_node.py
+++ b/dts/framework/testbed_model/sut_node.py
@@ -59,14 +59,12 @@ class SutNode(Node):
     dpdk_timestamp: str
     _build_target_config: BuildTargetConfiguration | None
     _env_vars: dict
-    _remote_tmp_dir: PurePath
     __remote_dpdk_dir: PurePath | None
     _app_compile_timeout: float
     _dpdk_kill_session: OSSession | None
     _dpdk_version: str | None
     _node_info: NodeInfo | None
     _compiler_version: str | None
-    _path_to_devbind_script: PurePath | None
 
     def __init__(self, node_config: SutNodeConfiguration):
         """Extend the constructor with SUT node specifics.
@@ -79,7 +77,6 @@ def __init__(self, node_config: SutNodeConfiguration):
         self.dpdk_prefix_list = []
         self._build_target_config = None
         self._env_vars = {}
-        self._remote_tmp_dir = self.main_session.get_remote_tmp_dir()
         self.__remote_dpdk_dir = None
         self._app_compile_timeout = 90
         self._dpdk_kill_session = None
@@ -89,7 +86,6 @@ def __init__(self, node_config: SutNodeConfiguration):
         self._dpdk_version = None
         self._node_info = None
         self._compiler_version = None
-        self._path_to_devbind_script = None
         self._logger.info(f"Created node: {self.name}")
 
     @property
@@ -153,7 +149,11 @@ def compiler_version(self) -> str:
 
     @property
     def path_to_devbind_script(self) -> PurePath:
-        """The path to the dpdk-devbind.py script on the node."""
+        """Implements :meth:`Node.path_to_devbind_script` on the SUT node.
+
+        It is expected that the DPDK directory will be available on this host 
before this property
+        is accessed.
+        """
         if self._path_to_devbind_script is None:
             self._path_to_devbind_script = self.main_session.join_remote_path(
                 self._remote_dpdk_dir, "usertools", "dpdk-devbind.py"
@@ -190,28 +190,28 @@ def tear_down_test_run(self) -> None:
         self.virtual_devices = []
 
     def set_up_build_target(self, build_target_config: 
BuildTargetConfiguration) -> None:
-        """Set up DPDK the SUT node and bind ports.
+        """Extend :meth:`Node.set_up_build_target` with DPDK setup steps.
 
         DPDK setup includes setting all internals needed for the build, the 
copying of DPDK tarball
-        and then building DPDK. The drivers are bound to those that DPDK needs.
+        and then building DPDK.
 
         Args:
             build_target_config: The build target test run configuration 
according to which
                 the setup steps will be taken.
         """
-        self._configure_build_target(build_target_config)
         self._copy_dpdk_tarball()
+        super().set_up_build_target(build_target_config)
+        self._configure_build_target(build_target_config)
         self._build_dpdk()
-        self.bind_ports_to_driver()
 
     def tear_down_build_target(self) -> None:
-        """Reset DPDK variables and bind port driver to the OS driver."""
+        """Extend :meth:`Node.tear_down_build_target` with the resetting of 
DPDK variables."""
+        super().tear_down_build_target()
         self._env_vars = {}
-        self._build_target_config = None
         self.__remote_dpdk_dir = None
+        self._build_target_config = None
         self._dpdk_version = None
         self._compiler_version = None
-        self.bind_ports_to_driver(for_dpdk=False)
 
     def _configure_build_target(self, build_target_config: 
BuildTargetConfiguration) -> None:
         """Populate common environment variables and set build target 
config."""
@@ -335,18 +335,3 @@ def configure_ipv4_forwarding(self, enable: bool) -> None:
             enable: If :data:`True`, enable the forwarding, otherwise disable 
it.
         """
         self.main_session.configure_ipv4_forwarding(enable)
-
-    def bind_ports_to_driver(self, for_dpdk: bool = True) -> None:
-        """Bind all ports on the SUT to a driver.
-
-        Args:
-            for_dpdk: If :data:`True`, binds ports to os_driver_for_dpdk.
-                If :data:`False`, binds to os_driver.
-        """
-        for port in self.ports:
-            driver = port.os_driver_for_dpdk if for_dpdk else port.os_driver
-            self.main_session.send_command(
-                f"{self.path_to_devbind_script} -b {driver} --force 
{port.pci}",
-                privileged=True,
-                verify=True,
-            )
diff --git a/dts/framework/testbed_model/tg_node.py 
b/dts/framework/testbed_model/tg_node.py
index 19b5b6e74c..25facb5e67 100644
--- a/dts/framework/testbed_model/tg_node.py
+++ b/dts/framework/testbed_model/tg_node.py
@@ -9,12 +9,15 @@
 A TG node is where the TG runs.
 """
 
+from pathlib import PurePath
+
 from scapy.packet import Packet  # type: ignore[import-untyped]
 
 from framework.config import TGNodeConfiguration
 from framework.testbed_model.traffic_generator.capturing_traffic_generator 
import (
     PacketFilteringConfig,
 )
+from framework.utils import LOCAL_DPDK_DIR
 
 from .node import Node
 from .port import Port
@@ -51,6 +54,24 @@ def __init__(self, node_config: TGNodeConfiguration):
         self.traffic_generator = create_traffic_generator(self, 
node_config.traffic_generator)
         self._logger.info(f"Created node: {self.name}")
 
+    @property
+    def path_to_devbind_script(self) -> PurePath:
+        """Implements :meth:`Node.path_to_devbind_script` on the TG node.
+
+        For traffic generators this script is only copied onto the host when 
needed by the
+        framework.
+        """
+        if self._path_to_devbind_script is None:
+            self._logger.info(f"Copying dpdk-devbind script into 
{self._remote_tmp_dir}")
+            self.main_session.copy_to(
+                PurePath(LOCAL_DPDK_DIR, "usertools", "dpdk-devbind.py"),
+                PurePath(self._remote_tmp_dir, "dpdk-devbind.py"),
+            )
+            self._path_to_devbind_script = self.main_session.join_remote_path(
+                self._remote_tmp_dir, "dpdk-devbind.py"
+            )
+        return self._path_to_devbind_script
+
     def send_packets_and_capture(
         self,
         packets: list[Packet],
diff --git a/dts/framework/utils.py b/dts/framework/utils.py
index c768dd0c99..fcc178e600 100644
--- a/dts/framework/utils.py
+++ b/dts/framework/utils.py
@@ -20,7 +20,7 @@
 import random
 import subprocess
 from enum import Enum, Flag
-from pathlib import Path
+from pathlib import Path, PurePath
 from subprocess import SubprocessError
 
 from scapy.layers.inet import IP, TCP, UDP, Ether  # type: 
ignore[import-untyped]
@@ -29,6 +29,8 @@
 from .exception import ConfigurationError, InternalError
 
 REGEX_FOR_PCI_ADDRESS: str = 
"/[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}.[0-9]{1}/"
+#: Path to DPDK directory on the host where DTS is being run.
+LOCAL_DPDK_DIR: PurePath = PurePath(__file__).parents[2]
 
 
 def expand_range(range_str: str) -> list[int]:
-- 
2.46.0


-- 



*Let's Connect!*

...  *October Webinars*

Ask Us Anything: IOL Services 
Open Q&A 
<https://unh.zoom.us/webinar/register/9017265932716/WN_OUo5S7iQRLmKKY7CsmwZhw#/registration>Your
 
questions. Our answers. Let's get started.


Oct 3rd


Live Tour of INTACT(R) 
for IPv6 Testing and Validation 
<https://unh.zoom.us/webinar/register/7117231236474/WN_I2zfyi_2S2yEiXkxBRi8sA#/registration>
Open tour. Open Q&A. See why we think you'll love INTACT.

Oct 9th


How to 
Prep for Our NVMe(R) Plugfest #21 
<https://unh.zoom.us/webinar/register/4017266809553/WN_X1iA2SZ8QhmcGboF2DImNg#/registration>
Checklists. Conversation. Let's get ready to plugin! 
Oct 15th


... * 
Newsletter*

*
*
Get the IOL Connector 
<https://www.iol.unh.edu/news/email-newsletters> for our latest news and 
event info.



.

Reply via email to