The abstraction allows for easy switching of implementations of remote
connections (ssh, telnet, etc.). It implements some common features,
such as logging of commands and their outputs and history bookkeeping
and defines methods that must be implemented by derived classes.
Signed-off-by: Owen Hilyard <ohilyard@iol.unh.edu>
Signed-off-by: Juraj Linkeš <juraj.linkes@pantheon.tech>
---
dts/framework/remote_session/__init__.py | 5 +
.../remote_session/remote_session.py | 100 ++++++++++++++++++
dts/framework/settings.py | 12 +++
3 files changed, 117 insertions(+)
create mode 100644 dts/framework/remote_session/__init__.py
create mode 100644 dts/framework/remote_session/remote_session.py
new file mode 100644
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2022 PANTHEON.tech s.r.o.
+#
+
+from .remote_session import RemoteSession
new file mode 100644
@@ -0,0 +1,100 @@
+# 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
+#
+
+import dataclasses
+from abc import ABC, abstractmethod
+from typing import Optional
+
+from framework.config import NodeConfiguration
+from framework.logger import DTSLOG
+from framework.settings import SETTINGS
+
+
+@dataclasses.dataclass(slots=True, frozen=True)
+class HistoryRecord:
+ name: str
+ command: str
+ output: str | int
+
+
+class RemoteSession(ABC):
+ name: str
+ hostname: str
+ ip: str
+ port: Optional[int]
+ username: str
+ password: str
+ logger: DTSLOG
+ history: list[HistoryRecord]
+ _node_config: NodeConfiguration
+
+ def __init__(
+ self,
+ node_config: NodeConfiguration,
+ session_name: str,
+ logger: DTSLOG,
+ ):
+ self._node_config = node_config
+
+ self.name = session_name
+ self.hostname = node_config.hostname
+ self.ip = self.hostname
+ self.port = None
+ if ":" in self.hostname:
+ self.ip, port = self.hostname.split(":")
+ 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._connect()
+ self.logger.info(f"Connection to {self.username}@{self.hostname} successful.")
+
+ def send_command(self, command: str, timeout: float = SETTINGS.timeout) -> str:
+ self.logger.info(f"Sending: {command}")
+ out = self._send_command(command, timeout)
+ self.logger.debug(f"Received from {command}: {out}")
+ self._history_add(command=command, output=out)
+ return out
+
+ def close(self, force: bool = False) -> None:
+ self.logger.logger_exit()
+ self._close(force)
+
+ def _history_add(self, command: str, output: str) -> None:
+ self.history.append(
+ HistoryRecord(name=self.name, command=command, output=output)
+ )
+
+ @abstractmethod
+ def is_alive(self) -> bool:
+ """
+ Check whether the session is still responding.
+ """
+ pass
+
+ @abstractmethod
+ def _connect(self) -> None:
+ """
+ Create connection to assigned node.
+ """
+ pass
+
+ @abstractmethod
+ def _send_command(self, command: str, timeout: float) -> str:
+ """
+ Send a command and return the output.
+ """
+ pass
+
+ @abstractmethod
+ def _close(self, force: bool = False) -> None:
+ """
+ Close the remote session, freeing all used resources.
+ """
+ pass
@@ -58,6 +58,7 @@ def __call__(
class _Settings:
config_file_path: str
output_dir: str
+ timeout: float
verbose: bool
@@ -82,6 +83,16 @@ def _get_parser() -> argparse.ArgumentParser:
help="[DTS_OUTPUT_DIR] Output directory where dts logs and results are saved.",
)
+ parser.add_argument(
+ "-t",
+ "--timeout",
+ action=_env_arg("DTS_TIMEOUT"),
+ default=15,
+ required=False,
+ help="[DTS_TIMEOUT] The default timeout for all DTS operations except for "
+ "compiling DPDK.",
+ )
+
parser.add_argument(
"-v",
"--verbose",
@@ -100,6 +111,7 @@ def _get_settings() -> _Settings:
return _Settings(
config_file_path=parsed_args.config_file,
output_dir=parsed_args.output_dir,
+ timeout=float(parsed_args.timeout),
verbose=(parsed_args.verbose == "Y"),
)