diff --git a/dts/conf.yaml b/dts/conf.yaml
index 1aaa593612..6b0bc5c2bf 100644
--- a/dts/conf.yaml
+++ b/dts/conf.yaml
@@ -2,8 +2,14 @@
 # Copyright 2022 The DPDK contributors
 
 executions:
-  - system_under_test: "SUT 1"
+  - build_targets:
+      - arch: x86_64
+        os: linux
+        cpu: native
+        compiler: gcc
+    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..1b97dc3ab9 100644
--- a/dts/framework/config/__init__.py
+++ b/dts/framework/config/__init__.py
@@ -3,13 +3,14 @@
 # Copyright(c) 2022 University of New Hampshire
 
 """
-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 +19,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 +71,7 @@ class NodeConfiguration:
     hostname: str
     user: str
     password: str | None
+    os: OS
 
     @staticmethod
     def from_dict(d: dict) -> "NodeConfiguration":
@@ -37,19 +80,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..409ce7ac74 100644
--- a/dts/framework/config/conf_yaml_schema.json
+++ b/dts/framework/config/conf_yaml_schema.json
@@ -5,6 +5,58 @@
     "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"
+        }
+      },
+      "additionalProperties": false
     }
   },
   "type": "object",
@@ -29,13 +81,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 +101,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..262c392d8e 100644
--- a/dts/framework/dts.py
+++ b/dts/framework/dts.py
@@ -3,32 +3,38 @@
 # Copyright(c) 2022 PANTHEON.tech s.r.o.
 # Copyright(c) 2022 University of New Hampshire
 
+import os
 import sys
 import traceback
 from collections.abc import Iterable
 
-from framework.testbed_model.node import Node
+from framework.testbed_model import Node, SutNode
 
-from .config import CONFIGURATION
+from .config import CONFIGURATION, BuildTargetConfiguration, ExecutionConfiguration
+from .exception import DTSError, ReturnCode
 from .logger import DTSLOG, getLogger
+from .settings import SETTINGS
 from .utils import check_dts_python_version
 
-dts_logger: DTSLOG | None = None
+dts_logger: DTSLOG = getLogger("dts")
 
 
 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.
     """
-
+    return_code = ReturnCode.NO_ERR
     global dts_logger
 
     # check the python version of the server that run dts
     check_dts_python_version()
 
-    dts_logger = getLogger("dts")
+    # prepare the output folder
+    if not os.path.exists(SETTINGS.output_dir):
+        os.mkdir(SETTINGS.output_dir)
 
-    nodes = {}
+    nodes: dict[str, Node] = {}
     # 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
@@ -38,30 +44,92 @@ def run_all() -> None:
     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")
-
-    except Exception as e:
-        # sys.exit() doesn't produce a stack trace, need to print it explicitly
-        traceback.print_exc()
+            sut_node = init_nodes(execution, nodes)
+            run_execution(sut_node, execution)
+
+    except DTSError as e:
+        dts_logger.error(traceback.format_exc())
+        return_code = e.return_code
         raise e
 
+    except Exception:
+        # sys.exit() doesn't produce a stack trace, need to produce it explicitly
+        dts_logger.error(traceback.format_exc())
+        return_code = ReturnCode.GENERIC_ERR
+        raise
+
+    finally:
+        quit_execution(nodes.values(), return_code)
+
+
+def init_nodes(
+    execution: ExecutionConfiguration, existing_nodes: dict[str, Node]
+) -> SutNode:
+    """
+    Create DTS SUT instance used in the given execution and initialize it. If already
+    initialized (in a previous execution), return the existing SUT.
+    """
+    if execution.system_under_test.name in existing_nodes:
+        # a Node with the same name already exists
+        sut_node = existing_nodes[execution.system_under_test.name]
+    else:
+        # the SUT has not been initialized yet
+        sut_node = SutNode(execution.system_under_test)
+        existing_nodes[sut_node.name] = sut_node
+
+    return sut_node
+
+
+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.setup_execution(execution)
+        for build_target in execution.build_targets:
+            run_build_target(sut_node, build_target, execution)
+
     finally:
-        quit_execution(nodes.values())
+        sut_node.cleanup_execution()
+
+
+def run_build_target(
+    sut_node: SutNode,
+    build_target: BuildTargetConfiguration,
+    execution: ExecutionConfiguration,
+) -> None:
+    """
+    Run the given build target.
+    """
+    dts_logger.info(f"Running target '{build_target.name}'.")
+    try:
+        sut_node.setup_build_target(build_target)
+        run_suite(sut_node, build_target, execution)
+
+    finally:
+        sut_node.teardown_build_target()
+
+
+def run_suite(
+    sut_node: SutNode,
+    build_target: BuildTargetConfiguration,
+    execution: ExecutionConfiguration,
+) -> None:
+    """
+    Use the given build_target to run the test suite with possibly only a subset
+    of tests. If no subset is specified, run all tests.
+    """
 
 
-def quit_execution(sut_nodes: Iterable[Node]) -> None:
+def quit_execution(nodes: Iterable[Node], return_code: ReturnCode) -> None:
     """
-    Close session to SUT and TG before quit.
-    Return exit status when failure occurred.
+    Close all node resources before quitting.
     """
-    for sut_node in sut_nodes:
-        # close all session
-        sut_node.node_exit()
+    for node in nodes:
+        node.close()
 
     if dts_logger is not None:
         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..cac8d84416 100644
--- a/dts/framework/exception.py
+++ b/dts/framework/exception.py
@@ -7,14 +7,45 @@
 User-defined exceptions used across the framework.
 """
 
+from enum import IntEnum, unique
+from typing import Callable, ClassVar
 
-class SSHTimeoutError(Exception):
+
+@unique
+class ReturnCode(IntEnum):
+    """
+    The various return codes that DTS exists with.
+    There are four categories of return codes:
+    0-9 DTS Framework errors
+    10-19 DPDK/Traffic Generator errors
+    20-29 Node errors
+    30-39 Test errors
+    """
+
+    NO_ERR = 0
+    GENERIC_ERR = 1
+    SSH_ERR = 2
+    NODE_SETUP_ERR = 20
+    NODE_CLEANUP_ERR = 21
+
+
+class DTSError(Exception):
+    """
+    The base exception from which all DTS exceptions are derived. Servers to hold
+    the return code with which DTS should exit.
+    """
+
+    return_code: ClassVar[ReturnCode] = ReturnCode.GENERIC_ERR
+
+
+class SSHTimeoutError(DTSError):
     """
     Command execution timeout.
     """
 
     command: str
     output: str
+    return_code: ClassVar[ReturnCode] = ReturnCode.SSH_ERR
 
     def __init__(self, command: str, output: str):
         self.command = command
@@ -27,12 +58,13 @@ def get_output(self) -> str:
         return self.output
 
 
-class SSHConnectionError(Exception):
+class SSHConnectionError(DTSError):
     """
     SSH connection error.
     """
 
     host: str
+    return_code: ClassVar[ReturnCode] = ReturnCode.SSH_ERR
 
     def __init__(self, host: str):
         self.host = host
@@ -41,16 +73,65 @@ 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
+    return_code: ClassVar[ReturnCode] = ReturnCode.SSH_ERR
 
     def __init__(self, host: str):
         self.host = host
 
     def __str__(self) -> str:
         return f"SSH session with {self.host} has died"
+
+
+class NodeSetupError(DTSError):
+    """
+    Raised when setting up a node.
+    """
+
+    return_code: ClassVar[ReturnCode] = ReturnCode.NODE_SETUP_ERR
+
+    def __init__(self):
+        super(NodeSetupError, self).__init__(
+            "An error occurred during node execution setup."
+        )
+
+
+class NodeCleanupError(DTSError):
+    """
+    Raised when cleaning up node.
+    """
+
+    return_code: ClassVar[ReturnCode] = ReturnCode.NODE_CLEANUP_ERR
+
+    def __init__(self):
+        super(NodeCleanupError, self).__init__(
+            "An error occurred during node execution cleanup."
+        )
+
+
+def convert_exception(exception: type[DTSError]) -> Callable[..., Callable[..., None]]:
+    """
+    When a non-DTS exception is raised while executing the decorated function,
+    convert it to the supplied exception.
+    """
+
+    def convert_exception_wrapper(func) -> Callable[..., None]:
+        def convert(*args, **kwargs) -> None:
+            try:
+                func(*args, **kwargs)
+
+            except DTSError:
+                raise
+
+            except Exception as e:
+                raise exception() from e
+
+        return convert
+
+    return convert_exception_wrapper
diff --git a/dts/framework/remote_session/__init__.py b/dts/framework/remote_session/__init__.py
index a227d8db22..f2339b20bd 100644
--- a/dts/framework/remote_session/__init__.py
+++ b/dts/framework/remote_session/__init__.py
@@ -1,14 +1,14 @@
 # SPDX-License-Identifier: BSD-3-Clause
 # Copyright(c) 2022 PANTHEON.tech s.r.o.
 
-from framework.config import NodeConfiguration
-from framework.logger import DTSLOG
+"""
+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).
+"""
 
-from .remote_session import RemoteSession
-from .ssh_session import SSHSession
+# pylama:ignore=W0611
 
-
-def create_remote_session(
-    node_config: NodeConfiguration, name: str, logger: DTSLOG
-) -> RemoteSession:
-    return SSHSession(node_config, name, logger)
+from .os import OSSession, create_session
diff --git a/dts/framework/remote_session/factory.py b/dts/framework/remote_session/factory.py
new file mode 100644
index 0000000000..a227d8db22
--- /dev/null
+++ b/dts/framework/remote_session/factory.py
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2022 PANTHEON.tech s.r.o.
+
+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/os/__init__.py b/dts/framework/remote_session/os/__init__.py
new file mode 100644
index 0000000000..9d2ec7fca2
--- /dev/null
+++ b/dts/framework/remote_session/os/__init__.py
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2022 PANTHEON.tech s.r.o.
+# Copyright(c) 2022 University of New Hampshire
+
+from framework.config import OS, NodeConfiguration
+from framework.logger import DTSLOG
+
+from .linux_session import LinuxSession
+from .os_session import OSSession
+
+
+def create_session(
+    node_config: NodeConfiguration, name: str, logger: DTSLOG
+) -> OSSession:
+    match node_config.os:
+        case OS.linux:
+            return LinuxSession(node_config, name, logger)
diff --git a/dts/framework/remote_session/os/linux_session.py b/dts/framework/remote_session/os/linux_session.py
new file mode 100644
index 0000000000..39e80631dd
--- /dev/null
+++ b/dts/framework/remote_session/os/linux_session.py
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2022 PANTHEON.tech s.r.o.
+# Copyright(c) 2022 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/os_session.py b/dts/framework/remote_session/os/os_session.py
new file mode 100644
index 0000000000..2a72082628
--- /dev/null
+++ b/dts/framework/remote_session/os/os_session.py
@@ -0,0 +1,46 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2022 PANTHEON.tech s.r.o.
+# Copyright(c) 2022 University of New Hampshire
+
+from abc import ABC
+
+from framework.config import NodeConfiguration
+from framework.logger import DTSLOG
+from framework.remote_session.factory import create_remote_session
+from framework.remote_session.remote_session import RemoteSession
+
+
+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,
+    ) -> None:
+        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/os/posix_session.py b/dts/framework/remote_session/os/posix_session.py
new file mode 100644
index 0000000000..9622a4ea30
--- /dev/null
+++ b/dts/framework/remote_session/os/posix_session.py
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2022 PANTHEON.tech s.r.o.
+# Copyright(c) 2022 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_session.py b/dts/framework/remote_session/remote_session.py
index 33047d9d0a..4095e02c1b 100644
--- a/dts/framework/remote_session/remote_session.py
+++ b/dts/framework/remote_session/remote_session.py
@@ -19,6 +19,15 @@ 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
@@ -58,9 +67,11 @@ def _connect(self) -> None:
         """
         Create connection to assigned node.
         """
-        pass
 
     def send_command(self, command: str, timeout: float = SETTINGS.timeout) -> str:
+        """
+        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}")
@@ -70,7 +81,8 @@ def send_command(self, command: str, timeout: float = SETTINGS.timeout) -> str:
     @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:
+        """
+        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/ssh_session.py
index 7ec327054d..5816b1ce6b 100644
--- a/dts/framework/remote_session/ssh_session.py
+++ b/dts/framework/remote_session/ssh_session.py
@@ -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
diff --git a/dts/framework/testbed_model/__init__.py b/dts/framework/testbed_model/__init__.py
index c5512e5812..13c29c59c8 100644
--- a/dts/framework/testbed_model/__init__.py
+++ b/dts/framework/testbed_model/__init__.py
@@ -2,6 +2,10 @@
 # Copyright(c) 2022 University of New Hampshire
 
 """
-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, SutNode
diff --git a/dts/framework/testbed_model/node.py b/dts/framework/testbed_model/node.py
deleted file mode 100644
index 8437975416..0000000000
--- a/dts/framework/testbed_model/node.py
+++ /dev/null
@@ -1,62 +0,0 @@
-# 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
-
-"""
-A node is a generic host that DTS connects to and manages.
-"""
-
-from framework.config import NodeConfiguration
-from framework.logger import DTSLOG, getLogger
-from framework.remote_session import RemoteSession, create_remote_session
-from framework.settings import SETTINGS
-
-
-class Node(object):
-    """
-    Basic module for node management. This module implements methods that
-    manage a node, such as information gathering (of CPU/PCI/NIC) and
-    environment setup.
-    """
-
-    name: str
-    main_session: RemoteSession
-    logger: DTSLOG
-    _config: NodeConfiguration
-    _other_sessions: list[RemoteSession]
-
-    def __init__(self, node_config: NodeConfiguration):
-        self._config = node_config
-        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)
-
-    def send_command(self, cmds: str, timeout: float = SETTINGS.timeout) -> str:
-        """
-        Send commands to node and return string before timeout.
-        """
-
-        return self.main_session.send_command(cmds, timeout)
-
-    def create_session(self, name: str) -> RemoteSession:
-        connection = create_remote_session(
-            self._config,
-            name,
-            getLogger(name, node=self.name),
-        )
-        self._other_sessions.append(connection)
-        return connection
-
-    def node_exit(self) -> None:
-        """
-        Recover all resource before node exit
-        """
-        if self.main_session:
-            self.main_session.close()
-        for session in self._other_sessions:
-            session.close()
-        self.logger.logger_exit()
diff --git a/dts/framework/testbed_model/node/__init__.py b/dts/framework/testbed_model/node/__init__.py
new file mode 100644
index 0000000000..a179056f1f
--- /dev/null
+++ b/dts/framework/testbed_model/node/__init__.py
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2022 PANTHEON.tech s.r.o.
+
+# pylama:ignore=W0611
+
+from .node import Node
+from .sut_node import SutNode
diff --git a/dts/framework/testbed_model/node/node.py b/dts/framework/testbed_model/node/node.py
new file mode 100644
index 0000000000..86654e55ae
--- /dev/null
+++ b/dts/framework/testbed_model/node/node.py
@@ -0,0 +1,120 @@
+# 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
+
+"""
+A node is a generic host that DTS connects to and manages.
+"""
+
+from framework.config import (
+    BuildTargetConfiguration,
+    ExecutionConfiguration,
+    NodeConfiguration,
+)
+from framework.exception import NodeCleanupError, NodeSetupError, convert_exception
+from framework.logger import DTSLOG, getLogger
+from framework.remote_session import OSSession, create_session
+
+
+class Node(object):
+    """
+    Basic class for node management. This class implements methods that
+    manage a node, such as information gathering (of CPU/PCI/NIC) and
+    environment setup.
+    """
+
+    name: str
+    main_session: OSSession
+    logger: DTSLOG
+    config: NodeConfiguration
+    _other_sessions: list[OSSession]
+
+    def __init__(self, node_config: NodeConfiguration):
+        self.config = node_config
+        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_session(self.config, self.name, self.logger)
+
+    @convert_exception(NodeSetupError)
+    def setup_execution(self, execution_config: ExecutionConfiguration) -> None:
+        """
+        Perform the execution setup that will be done for each execution
+        this node is part of.
+        """
+        self._setup_execution(execution_config)
+
+    def _setup_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.
+        """
+
+    @convert_exception(NodeSetupError)
+    def setup_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._setup_build_target(build_target_config)
+
+    def _setup_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.
+        """
+
+    @convert_exception(NodeCleanupError)
+    def teardown_build_target(self) -> None:
+        """
+        Perform the build target cleanup that will be done after each build target
+        tested on this node.
+        """
+        self._cleanup_build_target()
+
+    def _cleanup_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.
+        """
+
+    @convert_exception(NodeCleanupError)
+    def cleanup_execution(self) -> None:
+        """
+        Perform the execution cleanup that will be done after each execution
+        this node is part of concludes.
+        """
+        self._cleanup_execution()
+
+    def _cleanup_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 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 close(self) -> None:
+        """
+        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()
diff --git a/dts/framework/testbed_model/node/sut_node.py b/dts/framework/testbed_model/node/sut_node.py
new file mode 100644
index 0000000000..79d54585c9
--- /dev/null
+++ b/dts/framework/testbed_model/node/sut_node.py
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2010-2014 Intel Corporation
+# Copyright(c) 2022 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.
+    """
