mfd-connect 7.13.0
Module for running commands on remote machine.
Requires Python: <3.14,>=3.10

> [!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 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
    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` 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("<mng_ip_ssh>", username="***", password="***"),
                    process_name="tcpdump")
```

- Kill the process by Name:
```python
kill_process_by_name(conn=SSHCConnection("<mng_ip_ssh>", username="***", password="***"),
                    process_name="tcpdump")
```

- Stop the process by Name:
```python
stop_process_by_name(conn=SSHCConnection("<mng_ip_ssh>", username="***", password="***"),
                    process_name="tcpdump")
```

- Kill all the process by Image Name:
```python
kill_all_processes_by_name(conn=SSHCConnection("<mng_ip_ssh>", 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("<mng_ip_ssh>", username="***", password="***"),
     dst_conn=RPyCConnection("<mng_ip_rpyc>"),
     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("<mng_ip_ssh>", 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("<mng_ip_ssh>", 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 '*.<file_extension>' such as '*.pkg, *.txt, *.json' to target using SSHConnection and RPyCConnection.
```python
copy(src_conn=RPyCConnection("<mng_ip_rpyc>"),
     dst_conn=RPyCConnection("<mng_ip_rpyc>"),
     source="/tmp/iperf/*.pkg", target="/tmp/tools/")

copy(src_conn=SSHConnection("<mng_ip_ssh>", username="***", password="***"),
     dst_conn=SSHConnection("<mng_ip_ssh>", username="***", password="***"),
     source="/tmp/iperf/*.pkg", target="/tmp/tools/")

copy(src_conn=RPyCConnection("<mng_ip_rpyc>"),
     dst_conn=SSHConnection("<mng_ip_ssh>", username="***", password="***"),
     source="/tmp/iperf/*.pkg", target="/tmp/tools/")
```

Outcome: Specific extension files starts with "*.<file_extension>" 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 <PORT_NUMBER> -l/--log <PATH_TO_LOG_FILE> --ssl-keyfile <PATH_TO_SSL_KEY_FILE> --ssl-certfile <PATH_TO_SSL_CERT_FILE>
```
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.

### 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 initializ