diff --git a/dts/conf.yaml b/dts/conf.yaml
index 1aaa593612..03696d2bab 100644
--- a/dts/conf.yaml
+++ b/dts/conf.yaml
@@ -1,9 +1,16 @@
 # SPDX-License-Identifier: BSD-3-Clause
-# Copyright 2022 The DPDK contributors
+# Copyright 2022-2023 The DPDK contributors
 
 executions:
-  - system_under_test: "SUT 1"
+  - build_targets:
+      - arch: x86_64
+        os: linux
+        cpu: native
+        compiler: gcc
+        compiler_wrapper: ccache
+    system_under_test: "SUT 1"
 nodes:
   - name: "SUT 1"
     hostname: sut1.change.me.localhost
     user: root
+    os: linux
diff --git a/dts/framework/config/__init__.py b/dts/framework/config/__init__.py
index 214be8e7f4..e3e2d74eac 100644
--- a/dts/framework/config/__init__.py
+++ b/dts/framework/config/__init__.py
@@ -1,15 +1,17 @@
 # SPDX-License-Identifier: BSD-3-Clause
 # Copyright(c) 2010-2021 Intel Corporation
-# Copyright(c) 2022 University of New Hampshire
+# Copyright(c) 2022-2023 University of New Hampshire
+# Copyright(c) 2023 PANTHEON.tech s.r.o.
 
 """
-Generic port and topology nodes configuration file load function
+Yaml config parsing methods
 """
 
 import json
 import os.path
 import pathlib
 from dataclasses import dataclass
+from enum import Enum, auto, unique
 from typing import Any
 
 import warlock  # type: ignore
@@ -18,6 +20,47 @@
 from framework.settings import SETTINGS
 
 
+class StrEnum(Enum):
+    @staticmethod
+    def _generate_next_value_(
+        name: str, start: int, count: int, last_values: object
+    ) -> str:
+        return name
+
+
+@unique
+class Architecture(StrEnum):
+    i686 = auto()
+    x86_64 = auto()
+    x86_32 = auto()
+    arm64 = auto()
+    ppc64le = auto()
+
+
+@unique
+class OS(StrEnum):
+    linux = auto()
+    freebsd = auto()
+    windows = auto()
+
+
+@unique
+class CPUType(StrEnum):
+    native = auto()
+    armv8a = auto()
+    dpaa2 = auto()
+    thunderx = auto()
+    xgene1 = auto()
+
+
+@unique
+class Compiler(StrEnum):
+    gcc = auto()
+    clang = auto()
+    icc = auto()
+    msvc = auto()
+
+
 # Slots enables some optimizations, by pre-allocating space for the defined
 # attributes in the underlying data structure.
 #
@@ -29,6 +72,7 @@ class NodeConfiguration:
     hostname: str
     user: str
     password: str | None
+    os: OS
 
     @staticmethod
     def from_dict(d: dict) -> "NodeConfiguration":
@@ -37,19 +81,44 @@ def from_dict(d: dict) -> "NodeConfiguration":
             hostname=d["hostname"],
             user=d["user"],
             password=d.get("password"),
+            os=OS(d["os"]),
+        )
+
+
+@dataclass(slots=True, frozen=True)
+class BuildTargetConfiguration:
+    arch: Architecture
+    os: OS
+    cpu: CPUType
+    compiler: Compiler
+    name: str
+
+    @staticmethod
+    def from_dict(d: dict) -> "BuildTargetConfiguration":
+        return BuildTargetConfiguration(
+            arch=Architecture(d["arch"]),
+            os=OS(d["os"]),
+            cpu=CPUType(d["cpu"]),
+            compiler=Compiler(d["compiler"]),
+            name=f"{d['arch']}-{d['os']}-{d['cpu']}-{d['compiler']}",
         )
 
 
 @dataclass(slots=True, frozen=True)
 class ExecutionConfiguration:
+    build_targets: list[BuildTargetConfiguration]
     system_under_test: NodeConfiguration
 
     @staticmethod
     def from_dict(d: dict, node_map: dict) -> "ExecutionConfiguration":
+        build_targets: list[BuildTargetConfiguration] = list(
+            map(BuildTargetConfiguration.from_dict, d["build_targets"])
+        )
         sut_name = d["system_under_test"]
         assert sut_name in node_map, f"Unknown SUT {sut_name} in execution {d}"
 
         return ExecutionConfiguration(
+            build_targets=build_targets,
             system_under_test=node_map[sut_name],
         )
 
diff --git a/dts/framework/config/conf_yaml_schema.json b/dts/framework/config/conf_yaml_schema.json
index 6b8d6ccd05..9170307fbe 100644
--- a/dts/framework/config/conf_yaml_schema.json
+++ b/dts/framework/config/conf_yaml_schema.json
@@ -5,6 +5,68 @@
     "node_name": {
       "type": "string",
       "description": "A unique identifier for a node"
+    },
+    "OS": {
+      "type": "string",
+      "enum": [
+        "linux"
+      ]
+    },
+    "cpu": {
+      "type": "string",
+      "description": "Native should be the default on x86",
+      "enum": [
+        "native",
+        "armv8a",
+        "dpaa2",
+        "thunderx",
+        "xgene1"
+      ]
+    },
+    "compiler": {
+      "type": "string",
+      "enum": [
+        "gcc",
+        "clang",
+        "icc",
+        "mscv"
+      ]
+    },
+    "build_target": {
+      "type": "object",
+      "description": "Targets supported by DTS",
+      "properties": {
+        "arch": {
+          "type": "string",
+          "enum": [
+            "ALL",
+            "x86_64",
+            "arm64",
+            "ppc64le",
+            "other"
+          ]
+        },
+        "os": {
+          "$ref": "#/definitions/OS"
+        },
+        "cpu": {
+          "$ref": "#/definitions/cpu"
+        },
+        "compiler": {
+          "$ref": "#/definitions/compiler"
+        },
+          "compiler_wrapper": {
+          "type": "string",
+          "description": "This will be added before compiler to the CC variable when building DPDK. Optional."
+        }
+      },
+      "additionalProperties": false,
+      "required": [
+        "arch",
+        "os",
+        "cpu",
+        "compiler"
+      ]
     }
   },
   "type": "object",
@@ -29,13 +91,17 @@
           "password": {
             "type": "string",
             "description": "The password to use on this node. Use only as a last resort. SSH keys are STRONGLY preferred."
+          },
+          "os": {
+            "$ref": "#/definitions/OS"
           }
         },
         "additionalProperties": false,
         "required": [
           "name",
           "hostname",
-          "user"
+          "user",
+          "os"
         ]
       },
       "minimum": 1
@@ -45,12 +111,20 @@
       "items": {
         "type": "object",
         "properties": {
+          "build_targets": {
+            "type": "array",
+            "items": {
+              "$ref": "#/definitions/build_target"
+            },
+            "minimum": 1
+          },
           "system_under_test": {
             "$ref": "#/definitions/node_name"
           }
         },
         "additionalProperties": false,
         "required": [
+          "build_targets",
           "system_under_test"
         ]
       },
diff --git a/dts/framework/dts.py b/dts/framework/dts.py
index d23cfc4526..6ea7c6e736 100644
--- a/dts/framework/dts.py
+++ b/dts/framework/dts.py
@@ -1,67 +1,157 @@
 # SPDX-License-Identifier: BSD-3-Clause
 # Copyright(c) 2010-2019 Intel Corporation
-# Copyright(c) 2022 PANTHEON.tech s.r.o.
-# Copyright(c) 2022 University of New Hampshire
+# Copyright(c) 2022-2023 PANTHEON.tech s.r.o.
+# Copyright(c) 2022-2023 University of New Hampshire
 
 import sys
-import traceback
-from collections.abc import Iterable
 
-from framework.testbed_model.node import Node
-
-from .config import CONFIGURATION
+from .config import CONFIGURATION, BuildTargetConfiguration, ExecutionConfiguration
+from .exception import DTSError, ErrorSeverity
 from .logger import DTSLOG, getLogger
+from .testbed_model import SutNode
 from .utils import check_dts_python_version
 
-dts_logger: DTSLOG | None = None
+dts_logger: DTSLOG = getLogger("dts_runner")
+errors = []
 
 
 def run_all() -> None:
     """
-    Main process of DTS, it will run all test suites in the config file.
+    The main process of DTS. Runs all build targets in all executions from the main
+    config file.
     """
-
     global dts_logger
+    global errors
 
     # check the python version of the server that run dts
     check_dts_python_version()
 
-    dts_logger = getLogger("dts")
-
-    nodes = {}
-    # This try/finally block means "Run the try block, if there is an exception,
-    # run the finally block before passing it upward. If there is not an exception,
-    # run the finally block after the try block is finished." This helps avoid the
-    # problem of python's interpreter exit context, which essentially prevents you
-    # from making certain system calls. This makes cleaning up resources difficult,
-    # since most of the resources in DTS are network-based, which is restricted.
+    nodes: dict[str, SutNode] = {}
     try:
         # for all Execution sections
         for execution in CONFIGURATION.executions:
-            sut_config = execution.system_under_test
-            if sut_config.name not in nodes:
-                node = Node(sut_config)
-                nodes[sut_config.name] = node
-                node.send_command("echo Hello World")
+            sut_node = None
+            if execution.system_under_test.name in nodes:
+                # a Node with the same name already exists
+                sut_node = nodes[execution.system_under_test.name]
+            else:
+                # the SUT has not been initialized yet
+                try:
+                    sut_node = SutNode(execution.system_under_test)
+                except Exception as e:
+                    dts_logger.exception(
+                        f"Connection to node {execution.system_under_test} failed."
+                    )
+                    errors.append(e)
+                else:
+                    nodes[sut_node.name] = sut_node
+
+            if sut_node:
+                _run_execution(sut_node, execution)
+
+    except Exception as e:
+        dts_logger.exception("An unexpected error has occurred.")
+        errors.append(e)
+        raise
+
+    finally:
+        try:
+            for node in nodes.values():
+                node.close()
+        except Exception as e:
+            dts_logger.exception("Final cleanup of nodes failed.")
+            errors.append(e)
 
+    # we need to put the sys.exit call outside the finally clause to make sure
+    # that unexpected exceptions will propagate
+    # in that case, the error that should be reported is the uncaught exception as
+    # that is a severe error originating from the framework
+    # at that point, we'll only have partial results which could be impacted by the
+    # error causing the uncaught exception, making them uninterpretable
+    _exit_dts()
+
+
+def _run_execution(sut_node: SutNode, execution: ExecutionConfiguration) -> None:
+    """
+    Run the given execution. This involves running the execution setup as well as
+    running all build targets in the given execution.
+    """
+    dts_logger.info(f"Running execution with SUT '{execution.system_under_test.name}'.")
+
+    try:
+        sut_node.set_up_execution(execution)
     except Exception as e:
-        # sys.exit() doesn't produce a stack trace, need to print it explicitly
-        traceback.print_exc()
-        raise e
+        dts_logger.exception("Execution setup failed.")
+        errors.append(e)
+
+    else:
+        for build_target in execution.build_targets:
+            _run_build_target(sut_node, build_target, execution)
 
     finally:
-        quit_execution(nodes.values())
+        try:
+            sut_node.tear_down_execution()
+        except Exception as e:
+            dts_logger.exception("Execution teardown failed.")
+            errors.append(e)
 
 
-def quit_execution(sut_nodes: Iterable[Node]) -> None:
+def _run_build_target(
+    sut_node: SutNode,
+    build_target: BuildTargetConfiguration,
+    execution: ExecutionConfiguration,
+) -> None:
     """
-    Close session to SUT and TG before quit.
-    Return exit status when failure occurred.
+    Run the given build target.
     """
-    for sut_node in sut_nodes:
-        # close all session
-        sut_node.node_exit()
+    dts_logger.info(f"Running build target '{build_target.name}'.")
+
+    try:
+        sut_node.set_up_build_target(build_target)
+    except Exception as e:
+        dts_logger.exception("Build target setup failed.")
+        errors.append(e)
+
+    else:
+        _run_suites(sut_node, execution)
+
+    finally:
+        try:
+            sut_node.tear_down_build_target()
+        except Exception as e:
+            dts_logger.exception("Build target teardown failed.")
+            errors.append(e)
+
+
+def _run_suites(
+    sut_node: SutNode,
+    execution: ExecutionConfiguration,
+) -> None:
+    """
+    Use the given build_target to run execution's test suites
+    with possibly only a subset of test cases.
+    If no subset is specified, run all test cases.
+    """
+
+
+def _exit_dts() -> None:
+    """
+    Process all errors and exit with the proper exit code.
+    """
+    if errors and dts_logger:
+        dts_logger.debug("Summary of errors:")
+        for error in errors:
+            dts_logger.debug(repr(error))
+
+    return_code = ErrorSeverity.NO_ERR
+    for error in errors:
+        error_return_code = ErrorSeverity.GENERIC_ERR
+        if isinstance(error, DTSError):
+            error_return_code = error.severity
+
+        if error_return_code > return_code:
+            return_code = error_return_code
 
-    if dts_logger is not None:
+    if dts_logger:
         dts_logger.info("DTS execution has ended.")
-    sys.exit(0)
+    sys.exit(return_code)
diff --git a/dts/framework/exception.py b/dts/framework/exception.py
index 8b2f08a8f0..121a0f7296 100644
--- a/dts/framework/exception.py
+++ b/dts/framework/exception.py
@@ -1,20 +1,46 @@
 # SPDX-License-Identifier: BSD-3-Clause
 # Copyright(c) 2010-2014 Intel Corporation
-# Copyright(c) 2022 PANTHEON.tech s.r.o.
-# Copyright(c) 2022 University of New Hampshire
+# Copyright(c) 2022-2023 PANTHEON.tech s.r.o.
+# Copyright(c) 2022-2023 University of New Hampshire
 
 """
 User-defined exceptions used across the framework.
 """
 
+from enum import IntEnum, unique
+from typing import ClassVar
 
-class SSHTimeoutError(Exception):
+
+@unique
+class ErrorSeverity(IntEnum):
+    """
+    The severity of errors that occur during DTS execution.
+    All exceptions are caught and the most severe error is used as return code.
+    """
+
+    NO_ERR = 0
+    GENERIC_ERR = 1
+    CONFIG_ERR = 2
+    SSH_ERR = 3
+
+
+class DTSError(Exception):
+    """
+    The base exception from which all DTS exceptions are derived.
+    Stores error severity.
+    """
+
+    severity: ClassVar[ErrorSeverity] = ErrorSeverity.GENERIC_ERR
+
+
+class SSHTimeoutError(DTSError):
     """
     Command execution timeout.
     """
 
     command: str
     output: str
+    severity: ClassVar[ErrorSeverity] = ErrorSeverity.SSH_ERR
 
     def __init__(self, command: str, output: str):
         self.command = command
@@ -27,12 +53,13 @@ def get_output(self) -> str:
         return self.output
 
 
-class SSHConnectionError(Exception):
+class SSHConnectionError(DTSError):
     """
     SSH connection error.
     """
 
     host: str
+    severity: ClassVar[ErrorSeverity] = ErrorSeverity.SSH_ERR
 
     def __init__(self, host: str):
         self.host = host
@@ -41,16 +68,25 @@ def __str__(self) -> str:
         return f"Error trying to connect with {self.host}"
 
 
-class SSHSessionDeadError(Exception):
+class SSHSessionDeadError(DTSError):
     """
     SSH session is not alive.
     It can no longer be used.
     """
 
     host: str
+    severity: ClassVar[ErrorSeverity] = ErrorSeverity.SSH_ERR
 
     def __init__(self, host: str):
         self.host = host
 
     def __str__(self) -> str:
         return f"SSH session with {self.host} has died"
+
+
+class ConfigurationError(DTSError):
+    """
+    Raised when an invalid configuration is encountered.
+    """
+
+    severity: ClassVar[ErrorSeverity] = ErrorSeverity.CONFIG_ERR
diff --git a/dts/framework/logger.py b/dts/framework/logger.py
index a31fcc8242..bb2991e994 100644
--- a/dts/framework/logger.py
+++ b/dts/framework/logger.py
@@ -1,7 +1,7 @@
 # SPDX-License-Identifier: BSD-3-Clause
 # Copyright(c) 2010-2014 Intel Corporation
-# Copyright(c) 2022 PANTHEON.tech s.r.o.
-# Copyright(c) 2022 University of New Hampshire
+# Copyright(c) 2022-2023 PANTHEON.tech s.r.o.
+# Copyright(c) 2022-2023 University of New Hampshire
 
 """
 DTS logger module with several log level. DTS framework and TestSuite logs
@@ -33,17 +33,17 @@ class DTSLOG(logging.LoggerAdapter):
     DTS log class for framework and testsuite.
     """
 
-    logger: logging.Logger
+    _logger: logging.Logger
     node: str
     sh: logging.StreamHandler
     fh: logging.FileHandler
     verbose_fh: logging.FileHandler
 
     def __init__(self, logger: logging.Logger, node: str = "suite"):
-        self.logger = logger
+        self._logger = logger
         # 1 means log everything, this will be used by file handlers if their level
         # is not set
-        self.logger.setLevel(1)
+        self._logger.setLevel(1)
 
         self.node = node
 
@@ -55,9 +55,13 @@ def __init__(self, logger: logging.Logger, node: str = "suite"):
         if SETTINGS.verbose is True:
             sh.setLevel(logging.DEBUG)
 
-        self.logger.addHandler(sh)
+        self._logger.addHandler(sh)
         self.sh = sh
 
+        # prepare the output folder
+        if not os.path.exists(SETTINGS.output_dir):
+            os.mkdir(SETTINGS.output_dir)
+
         logging_path_prefix = os.path.join(SETTINGS.output_dir, node)
 
         fh = logging.FileHandler(f"{logging_path_prefix}.log")
@@ -68,7 +72,7 @@ def __init__(self, logger: logging.Logger, node: str = "suite"):
             )
         )
 
-        self.logger.addHandler(fh)
+        self._logger.addHandler(fh)
         self.fh = fh
 
         # This outputs EVERYTHING, intended for post-mortem debugging
@@ -82,10 +86,10 @@ def __init__(self, logger: logging.Logger, node: str = "suite"):
             )
         )
 
-        self.logger.addHandler(verbose_fh)
+        self._logger.addHandler(verbose_fh)
         self.verbose_fh = verbose_fh
 
-        super(DTSLOG, self).__init__(self.logger, dict(node=self.node))
+        super(DTSLOG, self).__init__(self._logger, dict(node=self.node))
 
     def logger_exit(self) -> None:
         """
@@ -93,7 +97,7 @@ def logger_exit(self) -> None:
         """
         for handler in (self.sh, self.fh, self.verbose_fh):
             handler.flush()
-            self.logger.removeHandler(handler)
+            self._logger.removeHandler(handler)
 
 
 def getLogger(name: str, node: str = "suite") -> DTSLOG:
diff --git a/dts/framework/remote_session/__init__.py b/dts/framework/remote_session/__init__.py
index a227d8db22..747316c78a 100644
--- a/dts/framework/remote_session/__init__.py
+++ b/dts/framework/remote_session/__init__.py
@@ -1,14 +1,30 @@
 # SPDX-License-Identifier: BSD-3-Clause
-# Copyright(c) 2022 PANTHEON.tech s.r.o.
+# Copyright(c) 2023 PANTHEON.tech s.r.o.
 
-from framework.config import NodeConfiguration
+"""
+The package provides modules for managing remote connections to a remote host (node),
+differentiated by OS.
+The package provides a factory function, create_session, that returns the appropriate
+remote connection based on the passed configuration. The differences are in the
+underlying transport protocol (e.g. SSH) and remote OS (e.g. Linux).
+"""
+
+# pylama:ignore=W0611
+
+from framework.config import OS, NodeConfiguration
+from framework.exception import ConfigurationError
 from framework.logger import DTSLOG
 
-from .remote_session import RemoteSession
-from .ssh_session import SSHSession
+from .linux_session import LinuxSession
+from .os_session import OSSession
+from .remote import RemoteSession, SSHSession
 
 
-def create_remote_session(
+def create_session(
     node_config: NodeConfiguration, name: str, logger: DTSLOG
-) -> RemoteSession:
-    return SSHSession(node_config, name, logger)
+) -> 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/linux_session.py b/dts/framework/remote_session/linux_session.py
new file mode 100644
index 0000000000..9d14166077
--- /dev/null
+++ b/dts/framework/remote_session/linux_session.py
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2023 PANTHEON.tech s.r.o.
+# Copyright(c) 2023 University of New Hampshire
+
+from .posix_session import PosixSession
+
+
+class LinuxSession(PosixSession):
+    """
+    The implementation of non-Posix compliant parts of Linux remote sessions.
+    """
diff --git a/dts/framework/remote_session/os_session.py b/dts/framework/remote_session/os_session.py
new file mode 100644
index 0000000000..7a4cc5e669
--- /dev/null
+++ b/dts/framework/remote_session/os_session.py
@@ -0,0 +1,46 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2023 PANTHEON.tech s.r.o.
+# Copyright(c) 2023 University of New Hampshire
+
+from abc import ABC
+
+from framework.config import NodeConfiguration
+from framework.logger import DTSLOG
+
+from .remote import RemoteSession, create_remote_session
+
+
+class OSSession(ABC):
+    """
+    The OS classes create a DTS node remote session and implement OS specific
+    behavior. There a few control methods implemented by the base class, the rest need
+    to be implemented by derived classes.
+    """
+
+    _config: NodeConfiguration
+    name: str
+    _logger: DTSLOG
+    remote_session: RemoteSession
+
+    def __init__(
+        self,
+        node_config: NodeConfiguration,
+        name: str,
+        logger: DTSLOG,
+    ):
+        self._config = node_config
+        self.name = name
+        self._logger = logger
+        self.remote_session = create_remote_session(node_config, name, logger)
+
+    def close(self, force: bool = False) -> None:
+        """
+        Close the remote session.
+        """
+        self.remote_session.close(force)
+
+    def is_alive(self) -> bool:
+        """
+        Check whether the remote session is still responding.
+        """
+        return self.remote_session.is_alive()
diff --git a/dts/framework/remote_session/posix_session.py b/dts/framework/remote_session/posix_session.py
new file mode 100644
index 0000000000..110b6a4804
--- /dev/null
+++ b/dts/framework/remote_session/posix_session.py
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2023 PANTHEON.tech s.r.o.
+# Copyright(c) 2023 University of New Hampshire
+
+from .os_session import OSSession
+
+
+class PosixSession(OSSession):
+    """
+    An intermediary class implementing the Posix compliant parts of
+    Linux and other OS remote sessions.
+    """
diff --git a/dts/framework/remote_session/remote/__init__.py b/dts/framework/remote_session/remote/__init__.py
new file mode 100644
index 0000000000..f3092f8bbe
--- /dev/null
+++ b/dts/framework/remote_session/remote/__init__.py
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2023 PANTHEON.tech s.r.o.
+
+# pylama:ignore=W0611
+
+from framework.config import NodeConfiguration
+from framework.logger import DTSLOG
+
+from .remote_session import RemoteSession
+from .ssh_session import SSHSession
+
+
+def create_remote_session(
+    node_config: NodeConfiguration, name: str, logger: DTSLOG
+) -> RemoteSession:
+    return SSHSession(node_config, name, logger)
diff --git a/dts/framework/remote_session/remote_session.py b/dts/framework/remote_session/remote/remote_session.py
similarity index 61%
rename from dts/framework/remote_session/remote_session.py
rename to dts/framework/remote_session/remote/remote_session.py
index 33047d9d0a..7c7b30225f 100644
--- a/dts/framework/remote_session/remote_session.py
+++ b/dts/framework/remote_session/remote/remote_session.py
@@ -1,7 +1,7 @@
 # SPDX-License-Identifier: BSD-3-Clause
 # Copyright(c) 2010-2014 Intel Corporation
-# Copyright(c) 2022 PANTHEON.tech s.r.o.
-# Copyright(c) 2022 University of New Hampshire
+# Copyright(c) 2022-2023 PANTHEON.tech s.r.o.
+# Copyright(c) 2022-2023 University of New Hampshire
 
 import dataclasses
 from abc import ABC, abstractmethod
@@ -19,14 +19,23 @@ class HistoryRecord:
 
 
 class RemoteSession(ABC):
+    """
+    The base class for defining which methods must be implemented in order to connect
+    to a remote host (node) and maintain a remote session. The derived classes are
+    supposed to implement/use some underlying transport protocol (e.g. SSH) to
+    implement the methods. On top of that, it provides some basic services common to
+    all derived classes, such as keeping history and logging what's being executed
+    on the remote node.
+    """
+
     name: str
     hostname: str
     ip: str
     port: int | None
     username: str
     password: str
-    logger: DTSLOG
     history: list[HistoryRecord]
+    _logger: DTSLOG
     _node_config: NodeConfiguration
 
     def __init__(
@@ -46,31 +55,34 @@ def __init__(
             self.port = int(port)
         self.username = node_config.user
         self.password = node_config.password or ""
-        self.logger = logger
         self.history = []
 
-        self.logger.info(f"Connecting to {self.username}@{self.hostname}.")
+        self._logger = logger
+        self._logger.info(f"Connecting to {self.username}@{self.hostname}.")
         self._connect()
-        self.logger.info(f"Connection to {self.username}@{self.hostname} successful.")
+        self._logger.info(f"Connection to {self.username}@{self.hostname} successful.")
 
     @abstractmethod
     def _connect(self) -> None:
         """
         Create connection to assigned node.
         """
-        pass
 
     def send_command(self, command: str, timeout: float = SETTINGS.timeout) -> str:
-        self.logger.info(f"Sending: {command}")
+        """
+        Send a command and return the output.
+        """
+        self._logger.info(f"Sending: {command}")
         out = self._send_command(command, timeout)
-        self.logger.debug(f"Received from {command}: {out}")
+        self._logger.debug(f"Received from {command}: {out}")
         self._history_add(command=command, output=out)
         return out
 
     @abstractmethod
     def _send_command(self, command: str, timeout: float) -> str:
         """
-        Send a command and return the output.
+        Use the underlying protocol to execute the command and return the output
+        of the command.
         """
 
     def _history_add(self, command: str, output: str) -> None:
@@ -79,17 +91,20 @@ def _history_add(self, command: str, output: str) -> None:
         )
 
     def close(self, force: bool = False) -> None:
-        self.logger.logger_exit()
+        """
+        Close the remote session and free all used resources.
+        """
+        self._logger.logger_exit()
         self._close(force)
 
     @abstractmethod
     def _close(self, force: bool = False) -> None:
         """
-        Close the remote session, freeing all used resources.
+        Execute protocol specific steps needed to close the session properly.
         """
 
     @abstractmethod
     def is_alive(self) -> bool:
         """
-        Check whether the session is still responding.
+        Check whether the remote session is still responding.
         """
diff --git a/dts/framework/remote_session/ssh_session.py b/dts/framework/remote_session/remote/ssh_session.py
similarity index 91%
rename from dts/framework/remote_session/ssh_session.py
rename to dts/framework/remote_session/remote/ssh_session.py
index 7ec327054d..96175f5284 100644
--- a/dts/framework/remote_session/ssh_session.py
+++ b/dts/framework/remote_session/remote/ssh_session.py
@@ -1,7 +1,7 @@
 # SPDX-License-Identifier: BSD-3-Clause
 # Copyright(c) 2010-2014 Intel Corporation
-# Copyright(c) 2022 PANTHEON.tech s.r.o.
-# Copyright(c) 2022 University of New Hampshire
+# Copyright(c) 2022-2023 PANTHEON.tech s.r.o.
+# Copyright(c) 2022-2023 University of New Hampshire
 
 import time
 
@@ -17,7 +17,7 @@
 
 class SSHSession(RemoteSession):
     """
-    Module for creating Pexpect SSH sessions to a node.
+    Module for creating Pexpect SSH remote sessions.
     """
 
     session: pxssh.pxssh
@@ -56,9 +56,9 @@ def _connect(self) -> None:
                     )
                     break
                 except Exception as e:
-                    self.logger.warning(e)
+                    self._logger.warning(e)
                     time.sleep(2)
-                    self.logger.info(
+                    self._logger.info(
                         f"Retrying connection: retry number {retry_attempt + 1}."
                     )
             else:
@@ -67,13 +67,13 @@ def _connect(self) -> None:
             self.send_expect("stty -echo", "#")
             self.send_expect("stty columns 1000", "#")
         except Exception as e:
-            self.logger.error(RED(str(e)))
+            self._logger.error(RED(str(e)))
             if getattr(self, "port", None):
                 suggestion = (
                     f"\nSuggestion: Check if the firewall on {self.hostname} is "
                     f"stopped.\n"
                 )
-                self.logger.info(GREEN(suggestion))
+                self._logger.info(GREEN(suggestion))
 
             raise SSHConnectionError(self.hostname)
 
@@ -87,8 +87,8 @@ def send_expect(
                 try:
                     retval = int(ret_status)
                     if retval:
-                        self.logger.error(f"Command: {command} failure!")
-                        self.logger.error(ret)
+                        self._logger.error(f"Command: {command} failure!")
+                        self._logger.error(ret)
                         return retval
                     else:
                         return ret
@@ -97,7 +97,7 @@ def send_expect(
             else:
                 return ret
         except Exception as e:
-            self.logger.error(
+            self._logger.error(
                 f"Exception happened in [{command}] and output is "
                 f"[{self._get_output()}]"
             )
diff --git a/dts/framework/settings.py b/dts/framework/settings.py
index 800f2c7b7f..6422b23499 100644
--- a/dts/framework/settings.py
+++ b/dts/framework/settings.py
@@ -1,6 +1,6 @@
 # SPDX-License-Identifier: BSD-3-Clause
 # Copyright(c) 2010-2021 Intel Corporation
-# Copyright(c) 2022 PANTHEON.tech s.r.o.
+# Copyright(c) 2022-2023 PANTHEON.tech s.r.o.
 # Copyright(c) 2022 University of New Hampshire
 
 import argparse
@@ -23,7 +23,7 @@ def __init__(
             default: str = None,
             type: Callable[[str], _T | argparse.FileType | None] = None,
             choices: Iterable[_T] | None = None,
-            required: bool = True,
+            required: bool = False,
             help: str | None = None,
             metavar: str | tuple[str, ...] | None = None,
         ) -> None:
@@ -63,13 +63,17 @@ class _Settings:
 
 
 def _get_parser() -> argparse.ArgumentParser:
-    parser = argparse.ArgumentParser(description="DPDK test framework.")
+    parser = argparse.ArgumentParser(
+        description="Run DPDK test suites. All options may be specified with "
+        "the environment variables provided in brackets. "
+        "Command line arguments have higher priority.",
+        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
+    )
 
     parser.add_argument(
         "--config-file",
         action=_env_arg("DTS_CFG_FILE"),
         default="conf.yaml",
-        required=False,
         help="[DTS_CFG_FILE] configuration file that describes the test cases, SUTs "
         "and targets.",
     )
@@ -79,7 +83,6 @@ def _get_parser() -> argparse.ArgumentParser:
         "--output",
         action=_env_arg("DTS_OUTPUT_DIR"),
         default="output",
-        required=False,
         help="[DTS_OUTPUT_DIR] Output directory where dts logs and results are saved.",
     )
 
@@ -88,7 +91,6 @@ def _get_parser() -> argparse.ArgumentParser:
         "--timeout",
         action=_env_arg("DTS_TIMEOUT"),
         default=15,
-        required=False,
         help="[DTS_TIMEOUT] The default timeout for all DTS operations except for "
         "compiling DPDK.",
     )
@@ -98,7 +100,6 @@ def _get_parser() -> argparse.ArgumentParser:
         "--verbose",
         action=_env_arg("DTS_VERBOSE"),
         default="N",
-        required=False,
         help="[DTS_VERBOSE] Set to 'Y' to enable verbose output, logging all messages "
         "to the console.",
     )
diff --git a/dts/framework/testbed_model/__init__.py b/dts/framework/testbed_model/__init__.py
index c5512e5812..8ead9db482 100644
--- a/dts/framework/testbed_model/__init__.py
+++ b/dts/framework/testbed_model/__init__.py
@@ -1,7 +1,13 @@
 # SPDX-License-Identifier: BSD-3-Clause
-# Copyright(c) 2022 University of New Hampshire
+# Copyright(c) 2022-2023 University of New Hampshire
+# Copyright(c) 2023 PANTHEON.tech s.r.o.
 
 """
-This module contains the classes used to model the physical traffic generator,
+This package contains the classes used to model the physical traffic generator,
 system under test and any other components that need to be interacted with.
 """
+
+# pylama:ignore=W0611
+
+from .node import Node
+from .sut_node import SutNode
diff --git a/dts/framework/testbed_model/node.py b/dts/framework/testbed_model/node.py
index 8437975416..a37f7921e0 100644
--- a/dts/framework/testbed_model/node.py
+++ b/dts/framework/testbed_model/node.py
@@ -1,62 +1,118 @@
 # SPDX-License-Identifier: BSD-3-Clause
 # Copyright(c) 2010-2014 Intel Corporation
-# Copyright(c) 2022 PANTHEON.tech s.r.o.
-# Copyright(c) 2022 University of New Hampshire
+# Copyright(c) 2022-2023 PANTHEON.tech s.r.o.
+# Copyright(c) 2022-2023 University of New Hampshire
 
 """
 A node is a generic host that DTS connects to and manages.
 """
 
-from framework.config import NodeConfiguration
+from framework.config import (
+    BuildTargetConfiguration,
+    ExecutionConfiguration,
+    NodeConfiguration,
+)
 from framework.logger import DTSLOG, getLogger
-from framework.remote_session import RemoteSession, create_remote_session
-from framework.settings import SETTINGS
+from framework.remote_session import OSSession, create_session
 
 
 class Node(object):
     """
-    Basic module for node management. This module implements methods that
+    Basic class for node management. This class implements methods that
     manage a node, such as information gathering (of CPU/PCI/NIC) and
     environment setup.
     """
 
+    main_session: OSSession
+    config: NodeConfiguration
     name: str
-    main_session: RemoteSession
-    logger: DTSLOG
-    _config: NodeConfiguration
-    _other_sessions: list[RemoteSession]
+    _logger: DTSLOG
+    _other_sessions: list[OSSession]
 
     def __init__(self, node_config: NodeConfiguration):
-        self._config = node_config
+        self.config = node_config
+        self.name = node_config.name
+        self._logger = getLogger(self.name)
+        self.main_session = create_session(self.config, self.name, self._logger)
+
         self._other_sessions = []
 
-        self.name = node_config.name
-        self.logger = getLogger(self.name)
-        self.logger.info(f"Created node: {self.name}")
-        self.main_session = create_remote_session(self._config, self.name, self.logger)
+        self._logger.info(f"Created node: {self.name}")
 
-    def send_command(self, cmds: str, timeout: float = SETTINGS.timeout) -> str:
+    def set_up_execution(self, execution_config: ExecutionConfiguration) -> None:
         """
-        Send commands to node and return string before timeout.
+        Perform the execution setup that will be done for each execution
+        this node is part of.
         """
+        self._set_up_execution(execution_config)
 
-        return self.main_session.send_command(cmds, timeout)
+    def _set_up_execution(self, execution_config: ExecutionConfiguration) -> None:
+        """
+        This method exists to be optionally overwritten by derived classes and
+        is not decorated so that the derived class doesn't have to use the decorator.
+        """
 
-    def create_session(self, name: str) -> RemoteSession:
-        connection = create_remote_session(
-            self._config,
+    def tear_down_execution(self) -> None:
+        """
+        Perform the execution teardown that will be done after each execution
+        this node is part of concludes.
+        """
+        self._tear_down_execution()
+
+    def _tear_down_execution(self) -> None:
+        """
+        This method exists to be optionally overwritten by derived classes and
+        is not decorated so that the derived class doesn't have to use the decorator.
+        """
+
+    def set_up_build_target(
+        self, build_target_config: BuildTargetConfiguration
+    ) -> None:
+        """
+        Perform the build target setup that will be done for each build target
+        tested on this node.
+        """
+        self._set_up_build_target(build_target_config)
+
+    def _set_up_build_target(
+        self, build_target_config: BuildTargetConfiguration
+    ) -> None:
+        """
+        This method exists to be optionally overwritten by derived classes and
+        is not decorated so that the derived class doesn't have to use the decorator.
+        """
+
+    def tear_down_build_target(self) -> None:
+        """
+        Perform the build target teardown that will be done after each build target
+        tested on this node.
+        """
+        self._tear_down_build_target()
+
+    def _tear_down_build_target(self) -> None:
+        """
+        This method exists to be optionally overwritten by derived classes and
+        is not decorated so that the derived class doesn't have to use the decorator.
+        """
+
+    def create_session(self, name: str) -> OSSession:
+        """
+        Create and return a new OSSession tailored to the remote OS.
+        """
+        connection = create_session(
+            self.config,
             name,
             getLogger(name, node=self.name),
         )
         self._other_sessions.append(connection)
         return connection
 
-    def node_exit(self) -> None:
+    def close(self) -> None:
         """
-        Recover all resource before node exit
+        Close all connections and free other resources.
         """
         if self.main_session:
             self.main_session.close()
         for session in self._other_sessions:
             session.close()
-        self.logger.logger_exit()
+        self._logger.logger_exit()
diff --git a/dts/framework/testbed_model/sut_node.py b/dts/framework/testbed_model/sut_node.py
new file mode 100644
index 0000000000..42acb6f9b2
--- /dev/null
+++ b/dts/framework/testbed_model/sut_node.py
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2010-2014 Intel Corporation
+# Copyright(c) 2023 PANTHEON.tech s.r.o.
+
+from .node import Node
+
+
+class SutNode(Node):
+    """
+    A class for managing connections to the System under Test, providing
+    methods that retrieve the necessary information about the node (such as
+    CPU, memory and NIC details) and configuration capabilities.
+    """
