# mfd-connect **Repository Path**: mirrors_intel/mfd-connect ## Basic Information - **Project Name**: mfd-connect - **Description**: MFD Connect is a versatile module that supports SSH, RPyC, and other connection types for executing commands on remote machines, offering features like process management, file copying, and system information utilities. - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-07-04 - **Last Updated**: 2026-05-09 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README > [!IMPORTANT] > This project is under development. All source code and features on the main branch are for the purpose of testing or evaluation and not production ready. # MFD Connect **Module for running commands on remote machine.** ## Usage ```python from mfd_connect import LocalConnection conn = LocalConnection() proc = conn.start_process(command="dir C:\\", shell=True) for line in proc.get_stdout_iter(): print(line.strip()) ``` You can find more usage examples in `examples` directory. Recommend using same `python` version and version of `rpyc` library on server and client side. Otherwise, error could be raised due to inconsistency. # Table of Content - [Connection supported](#connection-supported) - [Connection types and method availability](#connection-types-and-method-availability) - [`RemoteProcess` operations](#remoteprocess-operations) - [Process Fetch/Kill Utility](#process-fetchkill-utility) - [Common copy function](#common-copy-function) - [Permissions utility functions](#permissions-utility-functions) - [Powershell utility functions](#powershell-utility-functions) - [System info utility functions](#system-info-utility-functions) - [Affinity setting](#affinity-setting) - [Path manipulation](#path-manipulation) - [Additional info about specific Connection implementation](#additional-info-about-specific-connection-implementation) - [Execute command with sudo](#execute-command-with-sudo) - [Execute background command with SSHConnection](#execute-background-command-with-sshconnection) - [Recommendation for Connection choice](#recommendation-for-connection-choice) - [Deployment setup tool](#deployment-setup-tool) - [RPyCConnection python deployment](#rpycconnection-python-deployment) - [OS supported](#os-supported) - [Telemetry](#telemetry) - [Auto versioning](#auto-versioning) - [Issue reporting](#issue-reporting) ## Connection supported ```mermaid classDiagram class LocalConnection{ } class SolConnection{ } class SSHConnection{ } class TunneledSSHConnection{ } class SSHConfigConnection{ } class RPyCConnection{ } class TunneledRPyCConnection{ } class TelnetConnection{ } class SerialConnection{ } class RPyCZeroDeployConnection{ } class ABC{ } class Connection{ } class AsyncConnection{ } class PythonConnection{ } class WinRmConnection{ } class PxsshConnection{ } class InteractiveSSHConnection{ } ABC <|-- Connection Connection <|-- AsyncConnection Connection <|-- SolConnection Connection <|-- SerialConnection Connection <|-- TelnetConnection AsyncConnection <|-- WinRmConnection AsyncConnection <|-- PythonConnection AsyncConnection <|-- InteractiveSSHConnection PythonConnection <|-- RPyCConnection PythonConnection <|-- LocalConnection RPyCConnection <|-- TunneledRPyCConnection RPyCConnection <|-- RPyCZeroDeployConnection AsyncConnection <|-- SSHConnection SSHConnection <|-- TunneledSSHConnection SSHConnection <|-- SSHConfigConnection AsyncConnection <|-- PxsshConnection ``` > :warning: On ESXi module must be deployed on remote side in case of `PythonConnection`. > :warning: On ESXi as a test controller `SSHConnection`, `TunneledSSHConnection`, `SSHConfigConnection` and `RPyCZeroDeployConnection` are not supported. ## Connection types and method availability Each Connection implementation inherits after one of 3 abstract Connection types: ### Connection It's a basic synchronous Connection. Methods supported: `execute_command` - it runs command and wait till command will be not finished. **WARNING: Supported only by SSHConnection** `execute_command` with reconnect and retry feature The execute_command method in SSHConnection includes a feature that attempts to reconnect to the session in case of failure. If the connection drops during command execution, the method will: 1. Attempt to reconnect to the SSH session. 2. Execute a test command (```hostname```) to verify the connection. 3. Retry executing the original command, number of retries and timeout can be set in the method with parameters: 1. `reconnect_attempts: int = 5` - Number of retries to attempt. 2. `attempt_delay: int = 6` - delay in seconds between retries. This feature ensures that transient connection issues do not cause command execution to fail permanently. * `execute_command` have argument `custom_exception`, which able us to raise our exception if program exit with unexpected return code. Exception must have the same attributes as `CalledProcessError` from `subprocess`: * `returncode` * `cmd` * `output` * `stderr` * `execute_command` returns `ConnectionCompletedProcess` object, which has fields: * `args` - The list or str args passed to `execute_command`. * `stdout` - The standard output. * `stderr` - The standard error. * `stdout_bytes` - Raw bytes from standard output. Currently, supported only on RPyC, SSH and Local. * `stderr_bytes` - Raw bytes from standard error. Currently, supported only on RPyC, SSH and Local. * `return_code` - The return code of command. * `execute_with_timeout` - runs command and wait till it finishes even if connection times out during execution. Same arguements and return type as `execute_command` `get_os_type` - it runs check command and return type of client OS, available OS Types are in [mfd-typing](https://github.com/intel/mfd-typing) module in `os_values.py` `get_os_name` - it runs check command and return name of client OS, available OS Names are in [mfd-typing](https://github.com/intel/mfd-typing) module in `os_values.py` `get_os_bitness` - it runs check command and return bit-ness of client OS, available OS Bitnesses are in [mfd-typing](https://github.com/intel/mfd-typing) module in `os_values.py` `get_cpu_architecture` - it runs check command and return CPU architecture of client OS, available CPU Architectures are in [mfd-typing](https://github.com/intel/mfd-typing) module in `cpu_values.py` `get_system_info -> SystemInfo` - gathers details about the remote host. It can be used for logging general information about the system under test. `SystemInfo` dataclass is declared in [mfd-typing](https://github.com/intel/mfd-typing) module in `os_values.py` file. `restart_platform`- it reboot platform via `shutdown -r` `shutdown_platform` - it turn off platform via `shutdown -s` `wait_for_host` arguments - `timeout: int` - it wait `timeout` seconds for platform availability, method is trying to connect via rpyc, if cannot, raises exception - `retry_time: int` - wait `retry_time` for next check `disconnect` - closes connection with client, able us to disconnect before planned reboot via other module `model` - attribute, contains Pydantic model of connection, model from `pytest-mfd-config`. Allows to access data about connection passed in topology, e.g.: ```python connection.model.connection_options ``` `default_timeout` - property for default timeout of `execute_command` for this connection. Can be overwritten for a single command by passing `timeout` in `execute_command` - `conn = RPyCConnection(ip = "x.x.x.x", default_timeout = 10)` to set `default_timeout` upon initialization. - `conn.default_timeout = 15` to change or set `default_timeout` after initialization. - `del conn.default_timeout` to remove `default_timeout` Below methods are supported by: * RPyCConnection * SSHConnection * SerialConnection `cache_system_data` - property for caching system data. If set to True, system data will be gathered once and cached for a lifetime of connection object. By default, system-related data are cached for improving performance (limiting calls to the system asking about: OS type, OS name, OS bitness or CPU architecture). - Affected methods: - `get_os_type`, - `get_os_name`, - `get_os_bitness`, - `get_cpu_architecture` are decorated by `@conditional_cache` decorator created for this purpose. For disabling `cache_system_data`, set: ```python connection.cache_system_data = False ``` The cache is also cleared after disconnecting from the connection, e.g. in case of reboot. Due to saving OS type to the class attribute, extra internal parameter `self._cached_os_type` is required for controlling if `self._os_type` is cached or not. This feature is supported in all connection types where those methods are implemented or at least one of them. [DEPRECATED] `copy_from_test_controller(src: Union[str, Path], dst: Union[str, Path]) -> None` - Copy file/directory from test controller to machine for which connection object is created. **WARNING: From v5.0.0 version please use copy() from rpc_copy_utils.py instead.** [DEPRECATED] `copy_to_test_controller(src: Union[str, Path], dst: Union[str, Path]) -> None` - Copy file/directory from machine for which connection object is created to test controller. **WARNING: From v5.0.0 version please use copy() from rpc_copy_utils.py instead.** Example usage of `disconnect` ```python connection.disconnect() pdu.reboot() connection.wait_for_host(timeout=360) ``` **For restart/shutdown purpose SSH user require permission for shutdown / RPyC server requires run via user with permission for shutdown** ### AsyncConnection It's an asynchronous Connection. Methods supported: `execute_command` - it runs command and wait till command will be not finished. `get_os_type` - it runs check command and return type of client OS, available OS Types are in [mfd-typing](https://github.com/intel/mfd-typing) module in `os_values.py` `get_os_name` - it runs check command and return name of client OS, available OS Names are in [mfd-typing](https://github.com/intel/mfd-typing) module in `os_values.py` `get_os_bitness` - it runs check command and return bit-ness of client OS, available OS Bitnesses are in [mfd-typing](https://github.com/intel/mfd-typing) module in `os_values.py` `get_cpu_architecture` - it runs check command and return CPU architecture of client OS, available CPU Architectures are in [mfd-typing](https://github.com/intel/mfd-typing) module in `cpu_values.py` `start_process` - it runs process in the background and can be accessed in the future. Returns `RemoteProcess` object `start_processes` - it runs processes in the background that can be accessed in the future. You should consider using it, when your command line will trigger more than one process. For now, implemented for ssh, tunneled_ssh and local connection. `download_file_from_url` - it downloads file from url. Implementation allows on hiding passed credentials (username & password or headers). However, due to need of extra configuration for some kinds of connections (SSH), this feature is limited. Please, read details below for more information. By default, hiding credentials is enabled (input parameter `hide_credentials=True`). Hiding credentials is supported for following type of connections and OS: **RPyCConnection:** - Linux - Windows - ESXi **LocalConnection:** - Linux - Windows - ESXi Hiding credentials is **not supported** for SSHConnection for following OS: - Linux - Windows - ESXi ### PythonConnection It's a Connection with access to python module-space. Methods supported: `execute_command` - it runs command and wait till command will be not finished. `get_os_type` - it runs check command and return type of client OS, available OS Types are in [mfd-typing](https://github.com/intel/mfd-typing) module in `os_values.py` `get_os_name` - it runs check command and return name of client OS, available OS Names are in [mfd-typing](https://github.com/intel/mfd-typing) module in `os_values.py` `get_os_bitness` - it runs check command and return bit-ness of client OS, available OS Bitnesses are in [mfd-typing](https://github.com/intel/mfd-typing) module in `os_values.py` `get_cpu_architecture` - it runs check command and return CPU architecture of client OS, available CPU Architectures are in [mfd-typing](https://github.com/intel/mfd-typing) module in `cpu_values.py` `start_process` - it runs process in the background and can be accessed in the future. `modules` - it returns python module-space. `get_requirements_version` - it reads Portable Python SHA from `requirements_version` file if code was executed using Portable Python, None is returned otherwise. `start_process_by_start_tool` - it runs process using start tool in the background and can be accessed in the future. Returns `WindowsRPyCProcessByStart` object, works on Windows only Additional parameters in compare to `start_process`: * `numa_node: Optional[int]` - Specifies the preferred Non-Uniform Memory Architecture (NUMA) node as a decimal integer. Of course each Connection implementation could have specific methods for themselves. However, those methods above are mandatory according to chosen Connection type. ### SSHConnection It's a Connection done by paramiko library. Constructor: `SSHConnection(ip: str, port: int = 22, username: str, password: Optional[str], key_path: Optional[Union[List[Union[str, "Path"]], str, "Path"]] = None, skip_key_verification: bool = False, model: "BaseModel | None" = None, default_timeout: int | None = None,)` ```python """ Initialise SSHConnection. :param ip: IP address of SSH server :param port: port of SSH server :param username: Username for connection :param password: Password for connection, optional for using key, pass None for no password :param key_path: the filename, or list of filenames, of optional private key(s) and/or certs to try for authentication, supported types: RSAKey, DSSKey, ECDSAKey, Ed25519Key :param skip_key_verification: To skip checking of host's key, equivalent of StrictHostKeyChecking=no :param model: pydantic model of connection :param default_timeout: Set default_timeout property. """ ``` ## `RemoteProcess` operations `start_process` method of `Connection` returns `RemoteProcess` object, which we can do plenty things with. Available methods are: `running` - returns whenever the process is running or not. `stdin_stream` - Process stdin stream. `stdout_stream` - Process stdout stream. `stderr_stream` - Process stderr stream. `stdout_text` - Full process stdout text. `stderr_text` - Full process stderr text. `get_stdout_iter` - Get iterator over stdout lines of the process. `get_stderr_iter` - Get iterator over stderr lines of the process. `return_code` - Return code of the process. `wait(timeout: int = 60)` - Wait for the process to conclude on its own. `stop(wait: Optional[int] = 60)` - Signal the process to stop gracefully. `kill(wait: Optional[int] = 60, with_signal: Union[Signals, str, int] = SIGTERM)` - Kill the process forcefully. `with_signal` parameter allows you to kill process with specified system signal. Signals are available in `signals` library. You can pass string name of signal (corresponding to library) or int value of signal. **For RPyCConnection process operations stop/kill, on the remote machine `psutil` module is required.** **But, ESXi OS uses pure python, so `psutil` is not required here.** #### Process class inheritance: ```mermaid classDiagram class SSHProcess{ } class RemoteProcess{ } class ESXiRemoteProcess{ } class ESXiRPyCProcess{ } class ESXiSSHProcess{ } class RPyCProcess{ } class LocalProcess{ } class SSHProcess{ } class WindowsLocalProcess{ } class WindowsRPyCProcess{ } class WindowsRPyCProcessByStart{ } class WindowsSSHProcess{ } class PosixLocalProcess{ } class PosixRPyCProcess{ } class PosixSSHProcess{ } class WinRmProcess{ } class InteractiveSSHProcess{ } RemoteProcess<|--SSHProcess RemoteProcess<|--ESXiRemoteProcess RemoteProcess<|--RPyCProcess RemoteProcess<|--LocalProcess RemoteProcess<|--WinRmProcess RemoteProcess<|--InteractiveSSHProcess SSHProcess<|--WindowsSSHProcess SSHProcess<|--PosixSSHProcess SSHProcess <|--ESXiSSHProcess ESXiRemoteProcess <|--ESXiSSHProcess RPyCProcess<|--PosixRPyCProcess RPyCProcess<|--WindowsRPyCProcess WindowsRPyCProcess <|-- WindowsRPyCProcessByStart RPyCProcess <|-- ESXiRPyCProcess ESXiRemoteProcess <|-- ESXiRPyCProcess LocalProcess <|-- WindowsLocalProcess LocalProcess <|-- PosixLocalProcess ``` ## Process Fetch/Kill Utility To get the process Id's and to kill a particular process. ### Supported Connection types: * RPyCConnection * SSHConnection * LocalConnection * SerialConnection ### Supported OS type: * Linux * Windows * FreeBSD * ESXI - Fetch the process ID: ```python get_process_by_name(conn=SSHCConnection("", username="***", password="***"), process_name="tcpdump") ``` - Kill the process by Name: ```python kill_process_by_name(conn=SSHCConnection("", username="***", password="***"), process_name="tcpdump") ``` - Stop the process by Name: ```python stop_process_by_name(conn=SSHCConnection("", username="***", password="***"), process_name="tcpdump") ``` - Kill all the process by Image Name: ```python kill_all_processes_by_name(conn=SSHCConnection("", username="***", password="***"), process_name="tcpdump") ``` **Note, only available for Windows OS** ## Common copy function For copy operations there is dedicated function under util/rpc_copy_utils.py. It covers local copying (within the same host) as well as remote one (between different hosts) ### Supported Connection types: * RPyCConnection * SSHConnection * LocalConnection * SerialConnection * TunneledSSHConnection **Please also note that copying between not all possible combinations of connection types of connection is permitted.** **Acceptable combinations**: * RPyCConnection <-> RPyCConnection * LocalConnection <-> LocalConnection * SSHConnection <-> SSHConnection * SerialConnection <-> LocalConnection * RPyCConnection <-> LocalConnection * SSHConnection <-> LocalConnection * RPyCConnection <-> SSHConnection * SSHConnection <-> TunneledSSHConnection * RPyCConnection <-> TunneledSSHConnection * LocalConnection <-> TunneledSSHConnection * TunneledSSHConnection <-> SSHConnection * TunneledSSHConnection <-> RPyCConnection * TunneledSSHConnection <-> LocalConnection * Hosts must be reachable from each other via jump host **To copy large files MFD-FTP>=1.0.0 is required on both sides (source and destination machine).** `copy(src_conn: "Connection", dst_conn: "Connection", source: Union[str, Path], target: Union[str, Path], timeout: int = 600) -> None` ```python """ Copy file/directory to the target. :param src_conn: Source connection object :param dst_conn: Destination connection object :param source: Path to file or directory to be copied :param target: Path to where file or directory should be copied. :param timeout: Timeout to wait for copying (used in rpyc remote only) """ ``` In case of remote rpyc big file copy, temporary RPyCConnection with extended connection timeout will be created. Allowed copy operations: - Copy file to file: ```python copy(src_conn=SSHCConnection("", username="***", password="***"), dst_conn=RPyCConnection(""), source="/tmp/iperf/iperf.exe", target="/tmp/tools/iperf.exe") ``` **Note: calling as `source="/tmp/iperf/iperf.exe"` and `target="/tmp/tools"` causes that `iperf.exe` will be copied to new file `tools`.** - Copy content of directory to target directory by adding `*` at the end of source path: ```python copy(src_conn=SSHCConnection("", username="***", password="***"), dst_conn=LocalConnection, source="/tmp/iperf/*", target="/tmp/tools") ``` Outcome: Content of `iperf` directory will be copied to `tools` directory - Copy source directory to target directory: ```python copy(src_conn=SSHCConnection("", username="***", password="***"), dst_conn=LocalConnection, source="/tmp/iperf", target="/tmp/tools") ``` Outcome: Directory `iperf` will be copied to `tools` directory, so we will have created new directory and copied content from it to: `/tmp/tools/iperf` on `dst_conn`. - Wildcard support: copy files with specific extensions starts with '*.' such as '*.pkg, *.txt, *.json' to target using SSHConnection and RPyCConnection. ```python copy(src_conn=RPyCConnection(""), dst_conn=RPyCConnection(""), source="/tmp/iperf/*.pkg", target="/tmp/tools/") copy(src_conn=SSHConnection("", username="***", password="***"), dst_conn=SSHConnection("", username="***", password="***"), source="/tmp/iperf/*.pkg", target="/tmp/tools/") copy(src_conn=RPyCConnection(""), dst_conn=SSHConnection("", username="***", password="***"), source="/tmp/iperf/*.pkg", target="/tmp/tools/") ``` Outcome: Specific extension files starts with "*." such as "*.pkg, *.txt, *.json" will be copied to target directory Not allowed: - Copy directory to file ## Permissions utility functions For changing permissions and ownership of files and directories, there are dedicated functions under util/rpc_permission_utils.py Both functions return Exception if path does not exist. ## Pathlib additional operations For operations, which doesn't fit into a pathlib library, there is a dedicated utility `pathlib_utils`. Available API: * `append_file(connection: "Connection", file_path: "Path | CustomPath | str", content: str) -> None` - Append content to the file. * `remove_all_from_path(connection: "Connection", path: "Path | CustomPath | str") -> None` - Remove all files/directories together with provided path. ### Supported os * Linux * ESXI * FreeBSD ### Supported connection types * LocalConnection * SSHConnection * TunneledSSHConnection * TunneledRPyCConnection * RPyCConnection * WinRmConnection ### change_mode ```python """ Change access permission of a file or directory. :param connection: Connection object :param path: Path to file or directory :param mode: Operating-system mode bitfield. eg. 0o775 """ ``` ### change_owner ```python """ Change ownership of a file or directory. :param connection: Connection object :param path: Path to file or directory :param user: Username to transfer ownership to :param group: Group to transfer ownership to """ ``` ## Powershell utility functions For parsing outputs dictionary-like outputs of powershell cmdlets there is dedicated function under util/powershell_utils.py. It returns list of dictionaries where output is given in key-value format. `parse_powershell_list(output: str) -> List[Dict[str, str]]` ```python """Parse the full output of a powershell command into a list of dicts, e.g. ServiceName : tunnel MACAddress : AdapterType : Tunnel DeviceID : 13 Name : Microsoft ISATAP Adapter #2 NetworkAddresses : Speed : 100000 ServiceName : VBoxNetFlt MACAddress : A0:48:1C:AA:BB:CC AdapterType : Ethernet 802.3 DeviceID : 14 Name : VirtualBox Bridged Networking Driver Miniport NetworkAddresses : Speed : :param output: powershell cmdlet output """ ``` ## System info utility functions It collects helper functions needed for `SystemInfo` datastructure creation. Most of the API is private and called as part of Connection public API. Public API from the utility: - `get_kernel_version_linux(connection: "Connection") -> str` - it gets Kernel version of remote Linux Host (uname -r). - `is_current_kernel_version_equal_or_higher(connection: "Connection", version: str) -> bool` - checks whether current kernel version is higher than provided one.` ## Connection utility functions It collects helper functions related to Connections. Public API from the utility: - `check_ssh_active_and_return_conn(conn: "Connection | None", kwargs) -> Tuple[bool, "Connection | None"]` - it checks if the SSH Connection is active by using existing connection or spawning a new one and also returns connection handle if active. Parameters: kwargs - ssh_ip, ssh_user, ssh_pwd to be given when existing connection is not SSH. ## Affinity setting LocalConnection and RPyCConnection provide support for binding the started process to a specific CPU or range of CPUs. The implementation differs between Linux and Windows - for Linux the process is started with affinity mask already assigned, while for Windows the process is started in traditional way, and then it is assigned the requested affinity mask. In order to bind process to specific cpus cpu_affinity argument needs to be passed to start_process() command. The expected format can be int (e.g. cpu_affinity=1), list of ints (e.g. cpu_affinity=[0, 4, 7]), string of comma-separated numbers (e.g. cpu_affinity="2, 4, 5"), string of cpus range (e.g. cpu_affinity="3-6") or string with combination of comma-separated numbers and ranges (e.g. cpu_affinity="1, 3-6"). ```python from mfd_connect import LocalConnection conn = LocalConnection() proc = conn.start_process(command="iperf -s -B 127.0.0.1", cpu_affinity=[1, 3, 7]) ``` In order to confirm the proper binding of the process to the cpus, run: - under Linux: `taskset -pc pid` - under Windows: `taskmgr`, find started process by its pid, right-click, select `Set Affinity` ## Path manipulation Any `Connection` (except `SerialConnection` and `TelnetConnection`) can handle library for Path manipulation: `PythonConnection`(`LocalConnection` and `RPyCConnection`) uses python library **pathlib** https://docs.python.org/3/library/pathlib.html ### Some APIs: #### path.**read_text**(encoding=None, errors=None) Return the decoded contents of the pointed-to file as a string: ```shell connection = RPyCConnection(...) p = connection.path('my_text_file.txt') p.write_text('Text file contents') 18 p.read_text() 'Text file contents' ``` The file is opened and then closed. The optional parameters have the same meaning as in `open()`. ___ Rest of Connection uses custom pathlib, which have got the same interface as official python library. Documentation of custom pathlib [here](mfd_connect/pathlib/PATHLIB.md) ## Additional info about specific Connection implementation ### RPyCConnection ### Constructor params ```python def __init__( self, ip: Union["IPAddress", str], *, port: int = None, path_extension: str = None, connection_timeout: int = 360, default_timeout: int | None = None, retry_timeout: Optional[int] = None, retry_time: int = 5, enable_bg_serving_thread: bool = True, model: Optional["BaseModel"] = None, enable_deploy: bool = False, deploy_username: str = None, deploy_password: str = None, share_url: str = None, share_username: str = None, share_password: str = None, ssl_keyfile: str | None = None, ssl_certfile: str | None = None, **kwargs, ) -> None: # noqa: D200 ``` - `ip` : IP Address of the remote host - `port` : TCP port to be used for establishing connection - `path_extension` : PATH environment variable extension for calling commands. - `connection_timeout` : Timeout value, if timeout last without response from server, client raises AsyncResultTimeout - `default_timeout` : Set default_timeout property. - `retry_timeout` : Time for try of connection, in secs - `retry_time` : Time between next try of connection, in secs - `enable_bg_serving_thread` : Set to True if background serving thread must be activated, otherwise False - `model` : pydantic model of connection - `enable_deploy` : Decide whether to check SHA of remote PP and deploy responder if needed - `deploy_username` : Username used for deployment of PP onto remote host - `deploy_password` : Password used for deployment of PP onto remote host - `share_url` : Location of PP tarball on fileserver - `share_username` : Username used for downloading PP tarball from fileserver - `share_password` : Password used for downloading PP tarball from fileserver - `ssl_keyfile` : Path to SSL key file, if not provided, client will run without SSL - `ssl_certfile` : Path to SSL cert file, if not provided, client will run without SSL It's PythonConnection realized via RPyC using rpyc_classic.py server, which is located in python sources /scripts folder. Here are tips how to make it running: https://rpyc.readthedocs.io/en/latest/docs/servers.html#classic-server Example of RPyC server has been implemented and can be used: ```bash python -m mfd_connect.rpyc_server -p/--port -l/--log --ssl-keyfile --ssl-certfile ``` log file and port are optional: default port will be used from module logs will be written into console output ssl key and cert files are optional, if not provided, server will run without SSL **This is only example of RPyC server implementation** RPyCConnection have got `connection_timeout` parameter, which able us to change time to response from remote system, default 360s. There is also available `enable_bg_serving_thread` parameter (default value True), which starts BgServingThread if enabled. Below some known issues correlated with this functionality: * Windows: BgServingThread **must be enabled** for async to properly read process output * Linux: BgServingThread **must be enabled** to read process output * Simics: There is knows issue with long connection time if BgServingThread is enabled, **recommend disabling it** #### PythonConnection/SSHConnection output redirection to file For start process methods there are 2 parameters: * `log_file` - bool parameter specifying if we want to redirect output of process to file (file will be generated from command SHA value in `~/execution_logs` directory) * `output_file` - optional string/Path parameter specifying path to file which we want to use for redirection Parameters are interchangeably, so we use `log_file` or `output_file`, if we want to just use redirection or use redirection to specific file. Process returned by `start_process` contains: * `log_path` variable - Path object directs to log file used for redirection. Possible to read output from process using [Path method](https://docs.python.org/3/library/pathlib.html#pathlib.Path.read_text) `process.log_path.read_text()` * `log_file_stream` variable - opened file stream for `log_path`, require to `process.log_file_stream.close()` after using output from process. ### TunneledRPyCConnection Allows to create an RPyC connection to a target via jump host that has running RPyC responder. Example of usage: To connect to a target with IP `10.10.10.100` via jump host with IP `10.10.10.101` create `TunneledRPyCConnection(ip="10.10.10.100", jump_host_ip="10.10.10.101")` :warning: It is recommended to set `enable_bg_serving_thread` to True as in following example (by default it is disabled). ```python conn = TunneledRPyCConnection(ip=client_ip, jump_host_ip=jump_host_ip, enable_bg_serving_thread=True) ``` `TunneledRPyCConnection` supports all `RPyCConnection` methods. **Constructor:** **Mandatory parameters:** `ip: Union["IPAddress", str]` - Host identifier - IP address `jump_host_ip: Union["IPAddress", str]` - Jump Host identifier - IP address **Optional parameters:** `port: Optional[int] = None` - TCP port to use while connecting to host's responder `jump_host_port: Optional[int] = None` - TCP port to use while connecting to jump host's responder `path_extension: Optional[str] = None` - PATH environment variable extension for calling commands `connection_timeout: int = 360` - Timeout value, if timeout last without response from server, client raises AsyncResultTimeout `default_timeout` : Set default_timeout property. `jump_host_retry_timeout: Optional[int] = None` - Time for try of connection, in secs `jump_host_retry_time: int = 5` - Time between next try of connection, in secs `skip_key_verification` - To skip checking of host's key of tunneled connection, equivalent to StrictHostKeyChecking=no ### RPyCZeroDeployConnection It's `RPyCConnection` realized via RPyC using deployed via SSH `rpyc_classic.py` server. To establish connection via `RPyCZeroDeployConnection` requires passing additional `username` and `password`/`keyfile` for SSH connection used to send and start RPyC server. Constructor have got optional `python_executable` parameter, which specifies python interpreter used for starting RPyC server, otherwise default python interpreter in system will be used. `RPycZeroDeployConnection` has disadvantage in compare to `RPyCConnection`. It supports only Posix system as remote. `RPycZeroDeployConnection` supports usage as context manager, which closes server after exit of manager, otherwise, is required to close server via `.close()` method from connection. ### SolConnection It's a Connection for Serial over LAN. Methods supported: `methods from Connection` `wait_for_string` - it waits for given string or list of strings till timeout. `go_to_option_on_screen` - it moves on screen till found given option `get_output_after_user_action` - it returns output after user action, eg. pressing key `send_key` - it sends given Serial Key Code from `SerialKeyCode`. Available keys are in `SerialKeyCode` class in `serial_utils.py` `get_cwd` - returns current working directory, raises SolException if it's unavailable ### SolConnection, LocalConnection Not implemented methods: `restart_platform` `shutdown_platform` `wait_for_host` ### RPyCConnection, LocalConnection `execute_powershell` - it runs powershell.exe commands and waits for command to finish ### TunneledSSHConnection Allows to create an SSH connection to target via jump host. Currently, it has only been tested on Linux. If you want to connect to target `192.168.0.1` (credentials `user`:`***`) using tunneling via jump host `10.10.10.10` (credentials `root`:`***`), parameters should be set as follows: - `ip`, `username`, `password` - IP, username and password of target i.e. `ip="192.168.0.1"`, `username="user"`, `password="***"` - `jump_host_ip`, `jump_host_username`, `jump_host_password` - IP, username and password of jump host i.e. `jump_host_ip="10.10.10.10"`, `jump_host_username="root"`, `jump_host_password="***"` > Note: To establish proper connection `local_bind_port` must be unique for each `TunneledSSHConnection` object. > The code is written the way it will look for the first available and unique free port so the user does not have to worry to provide unique port. ### SSHConfigConnection Allows to create an SSH connection using Host mnemonics from `~/.ssh/config`. Resolves hostname, user, port, identity file, StrictHostKeyChecking and ProxyJump chains automatically. For ProxyJump hosts, it builds a paramiko transport chain through the intermediate jump hosts. Supports arbitrary hop depth and devices that accept unauthenticated (`auth_none`) connections. ```python # Direct host conn = SSHConfigConnection(host="b1.a.host") # ProxyJump host (resolved automatically from config) conn = SSHConfigConnection(host="imc") # With password override conn = SSHConfigConnection(host="sut", password="my_password") # Custom config path conn = SSHConfigConnection(host="my-host", config_path="/path/to/config") ``` ### TelnetConnection TelnetConnection was created mainly with the purpose of enabling SerialConnection to work (using port redirection for telnet). However, this type of connection is functional as a standalone type as well. Due to potential security issues though, we suggest to use other Connection classes e.g. SSHConnection when possible. ### SerialConnection Allows to create a connection to serial device. Unlike other connections, this class requires an established connection to host where serial device is present. This is caused by the fact that SerialConnection first connects to the host, redirects the serial device through netcat and only then creates a TelnetConnection to redirected port on the host. If you want to connect to serial on the same setup, use `LocalConnection`, otherwise choose best appropriate Connection class based on your specific use case. When connecting to serial devices running on Veloce emulation, please make sure to set `is_veloce` parameter accordingly. This causes SerialConnection to increase its timeout for executing commands due to very high response time of Veloce setups. Parameter `baudrate` is used to initialize connection to serial device using appropriate baudrate if it has not been used on the setup previously. If device was already configured (e.g. user connected to it using `screen` command), previous settings will be used instead. You can also pass `serial_logs_path` parameter to init of SerialConnection - if you'll provide such parameter tee with the help of netcat will dump all serial movement to a log file. In `self.disconnect()` you can stop netcat server on host and disconnect from it. This method calls also `self.stop_logging()` - a public method which is responsible for killing logging in a serial movement. Methods supported: `methods from Connection` `get_screen_field_value(field_regex: str, group_name: Optional[str]) -> Optional[str]:` **POSIX & UEFI Shell are supported.** ### TelnetConnection and SerialConnection Methods supported: `fire_and_forget` - execute command without getting output, parsing return code or waiting for prompt to reappear `execute_command` - it runs command and waits for command to finish `get_os_type` - it runs check command and return type of client OS, available OSes are in [mfd-typing](https://github.com/intel/mfd-typing) module in `os_values.py` `get_os_name` - it runs check command and return name of client OS, available OSes are in [mfd-typing](https://github.com/intel/mfd-typing) module in `os_values.py` `wait_for_string` - it waits for given string or list of strings till timeout. `go_to_option_on_screen` - it moves on screen till found given option `send_key` - it sends given Serial Key Code from `SerialKeyCode`. Available keys are in `SerialKeyCode` class in `serial_utils.py` `get_output_after_user_action` - it returns output after user action, eg. pressing key Invoking any other command from Connection on these classes may result in raising `NotImplementedError`. ### WinRmConnection Connection uses `pywinrm` library to communicate with server via Windows Remote Management protocol. To use connection `ip`, `username` and `password` attributes are required. In `execute_command` method parameters are not functional for WinRM: * `input_data` * `cwd` * `timeout` * `env` * `discard_stdout` * `discard_stderr` * `shell` `restart_platform`, `shutdown_platform` and `wait_for_host` APIs are not implemented In process objects `stdin_stream`, `stdout_stream`, `stderr_stream`, `get_stdout_iter`,`get_stderr_iter`, `wait` APIs are not implemented ### PxsshConnection PxsshConnection is a Python module that leverages the pxssh class from the pexpect library to establish and manage SSH (Secure Shell) connections. This module provides a high-level interface for interacting with remote servers over SSH. The PxsshConnection module is designed to accept a prompts parameter as input. This parameter represents the expected prompt that will appear after the execution of a command. The module then matches this expected prompt with the actual prompt that is returned. This functionality allows for precise control and handling of command execution, ensuring that the operation has completed as expected before proceeding. https://pexpect.readthedocs.io/en/stable/api/pxssh.html https://pexpect.sourceforge.net/pxssh.html Additional Constructor param: `prompts: str = "# $"` ### InteractiveSSHConnection `InteractiveSSHConnection` is a connection type that leverages `paramiko` library to establish and manage SSH (Secure Shell) connections using `invoke_shell` API. Technical details: Connection work in interactive mode, which means that it reads and writes to the single channel and waits for the prompt (in case of executing command). Prompt is used for checking if process is finished (in case of starting process). #### Public APIs `__init__(self, ip, port, username, password, key_path, skip_key_verification, model, default_timeout)` This method initializes the `InteractiveSSHConnection` instance. It requires the IP address, port, username, and password for the SSH server. The `key_path` parameter is optional and is used for authentication. If `skip_key_verification` is set to True, the host's key verification is skipped. `disconnect(self)` This method is used to close the SSH connection. `get_os_type(self)` This method returns the type of the operating system of the remote machine. `get_os_name(self)` This method returns the name of the operating system of the remote machine. `execute_command(self, command, input_data, cwd, timeout, env, stderr_to_stdout, discard_stdout, discard_stderr, skip_logging, expected_return_codes, shell, custom_exception)` This method runs a command on the remote machine and waits for its completion. It returns a `ConnectionCompletedProcess` object. **In case of not failed command execution on Switches, return code is not available.** `start_process(self, command, cwd, env, stderr_to_stdout, discard_stdout, discard_stderr, cpu_affinity, shell, enable_input, log_file, output_file, get_pty)` This method starts a process on the remote machine. It returns an `InteractiveSSHProcess` object. **It's possible to run only one process in the same time.** `wait_for_host(self, timeout)` This method waits for the host to become available. It tries to connect via SSH and raises a `TimeoutError` if the host does not wake up within the specified timeout. #### InteractiveSSHProcess `InteractiveSSHProcess` is a class that represents a process started on the remote machine using the `InteractiveSSHConnection` class. It provides methods to interact with the process. ##### Public APIs `running(self)` - This method returns True if the process is running, otherwise False. It's based on the prompt in the channel. `stop(self, wait)` - This method stops the process gracefully. The `wait` parameter specifies the time to wait for the process to stop. It uses CTRL+C to stop the process. `kill(self, wait, with_signal)` - This method kills the process forcefully. The `wait` parameter specifies the time to wait for the process to stop. The `with_signal` parameter specifies the signal to use for killing the process. It uses the `CTRL+C` x3 to kill the process. `wait(self, timeout)` - This method waits for the process to finish. The `timeout` parameter specifies the time to wait for the process to finish. `stdout_text(self)` - This method returns the standard output of the process as a string. `return_code(self)` - This method returns the return code of the process. It uses `echo $? / %errorlevel%` to get the return code from last command. `get_stdout_iter(self)` - This method returns an iterator over the standard output of the process. ## Execute command with sudo There is an option to execute command with sudo. Use `enable_sudo` to execute command with sudo. Use `disable_sudo` to execute command without sudo. In default sudo is **disabled**. **Do not** pass command with sudo, just use `enable_sudo` method and sudo will be added to command automatically. ### Supported Connection types: * SSHConnection * TunneledSSHConnection * PxsshConnection * LocalConnection - only `execute_command` method, `path`, `modules` won't use sudo ### Supported OS types: * POSIX ### Available methods: `enable_sudo` - enable sudo for command execution `disable_sudo` - disable sudo for command execution ### Affected methods: `execute_command`, `start_process`, `start_processes`, `restart_platform` and `shutdown_platform` for LocalConnection only `execute_command` method is affected ## Execute background command with SSHConnection ### How to To trigger background command there are 2 possible options: - `start_process` - saving returned `SSHProcess` object will keep process alive to the moment of garbage collection. - `execute_command` and some command with `&` (`Posix`) - will start background process and kill it immediately. To keep process alive in OS it requires redirection of stream, for example stdout. Easiest way is to pass `True` to `discard_stdout` parameter, then command will be automatically extended with system discard and OS will not kill process. You can also apply redirections by yourself to the command (for example `>/dev/null`). **Recommended way is to use `start_process` to be able to kill process in any moment with `SSHProcess` API and to do it explicitly when the time comes if you're using redirections.** ### Some internals about Paramiko, PTY, Channels, timeouts Both `start_process` and `execute_command` under the hood are creating new Paramiko channel for each command. This is expected behaviour, recommended by Paramiko. After channel is closed processes triggered in it are killed. To keep them alive we need some connection with "outside world", which can be done via redirections of streams or keeping `SSHProcess` object saved. Why object is also keeping process alive? Because it is holding a references to the stdout/err/in. So basically it's doing the same thing. Also, few words about timeout and pseudo terminal (PTY). Paramiko has no timeout mechanism. To be honest it has, but only to check if opening a channel takes too long, not to check time of command execution. That's why when `timeout` parameter is passed to `execute_command` we're internally creating `SSHProcess` object to be able to kill it. We gave up an idea of using PTY, because PTY is way more isolated than normal channel. So no redirection can keep process alive. Channel is getting closed immediately after command execution and all processes are being killed. ## Recommendation for Connection choice Choice should be made according to need and machine type of which you want to connect. However, we recommend to choose Connection implementation of types in order below: 1. PythonConnection 2. AsyncConnection 3. Connection For ex. if you want to connect with some appliance (not host), where there is only SSH connection available. There will be no possibility to use PythonConnection, so it's natural that you should choose `SSHConnection`(AsyncConnection). ## Deployment setup tool > [!WARNING] > This feature is under development. All source code and features on the main branch are for the purpose of testing or evaluation and not production ready. Python deployment contains a class responsible for preparing python with RPyC server based on Artifactory package url. `SetupPythonForResponder` requires `ip` address for machine, credentials (`username`, `password`) and `artifactory_url` for package. Credentials for artifactory are optionals if auth is not required. Tool connects via `SSH` or `WinRM` to the machine using credentials, checks if server is already running, if not downloads python from artifactory, unpacks and starts rpyc server with 18816 port. Server is running in background. ### Expected structure of artifactory package `artifactory_url`: ```bash ├── wrapper_interpreter/ │ ├── FreeBSD/ │ │ ├── 12/ │ │ │ └── PP_FreeBSD_12_54345.zip │ │ ├── 13/ │ │ │ └── PP_FreeBSD_13_54345.zip │ │ └── 14/ │ │ └── PP_FreeBSD_14_54345.zip │ ├── Linux/ │ │ ├── x86_64/ │ │ │ └── PP_Linux_54345.zip │ │ ├── aarch64/ │ │ │ └── PP_Linux_54345.zip │ │ └── PP_Linux_54345.zip │ └── Windows/ │ └── PP_Windows_54345.zip └── light_interpreter/ └── ESXi/ └── PP_ESXi_54345.zip ``` In the case of Linux, it's possible to have optional python per architecture. Both structures are supported (directly located zip in `Linux` or with subdirectories) ## RPyCConnection python deployment `RPyCConnection` has constructor arguments related to deployment: * enable_deploy: bool = False, * deploy_username: Optional[str] = None, * deploy_password: Optional[str] = None, * share_url: Optional[str] = None, * share_username: Optional[str] = None, * share_password: Optional[str] = None, `share_url` - URL for the package supported by [Deployment setup tool](#deployment-setup-tool) in paragraph [structure](#expected-structure-of-artifactory-package-artifactory_url). When `enable_deploy` is set as `True`, code will check correctness of SHA for local and remote python interpreter (should be the same). Code is going to connect using `port` value, if code cannot establish connection, it will retry using deploy port (+1 for default port) If code cannot establish connection, it will start deployment of python using [Deployment setup tool](#deployment-setup-tool) and establish connection again. ## RShell connection `RShellConnection` is a connection type that leverages `httplib.client` library to establish and manage connections using RESTful API over HTTP protocol in EFI Shell environment. `rshell_client.py` must be present on the EFI Shell target system. `rshell_server.py` is a server script that needs to be executed on the host machine to facilitate communication between the host and the EFI Shell target system. Server works on queue with address ip -> command to execute, it provides a RESTful API that allows the host to send commands and receive responses from the EFI Shell: * `/execute_command` - Endpoint to execute commands on the EFI Shell target system: Form fields: * `timeout` - Timeout for command execution. * `command` - Command to be executed. * `ip` - IP address of the EFI Shell target system. * `/post_result` - Endpoint to post results back to the host. Headers fields: * `CommandID` - Unique identifier for the command. * `rc` - Return code of the executed command. Body: * Command output. * `/exception` - Endpoint to handle exceptions that may occur during communication. Headers fields: * `CommandID` - Unique identifier for the command. Body: * Exception details. * `/getCommandToExecute` - Endpoint to retrieve commands to be executed on the EFI Shell target system. Returns commandline with generated CommandID. * `/health/` - Endpoint to check the health status of the connection. `rshell.py` is a Connection class that calls RESTful API endpoints provided by `rshell_server.py` to execute commands on the EFI Shell target system. If required, starts `rshell_server.py` on the host machine. ## OS supported: * LNX * WINDOWS * ESXI * FREEBSD * EFI shell support * Switches support ## Issue reporting If you encounter any bugs or have suggestions for improvements, you're welcome to contribute directly or open an issue [here](https://github.com/intel/mfd-connect/issues).