1+ # Copyright (C) 2025 Intel Corporation
2+ # SPDX-License-Identifier: MIT
3+ import logging
4+ import sys
5+ import time
6+ import typing
7+ from ipaddress import IPv4Address , IPv6Address
8+ from subprocess import CalledProcessError
9+
10+ import requests
11+ from mfd_common_libs import add_logging_level , log_levels , TimeoutCounter
12+ from mfd_typing .cpu_values import CPUArchitecture
13+ from mfd_typing .os_values import OSBitness , OSName , OSType
14+
15+ from mfd_connect .local import LocalConnection
16+ from mfd_connect .pathlib .path import CustomPath , custom_path_factory
17+ from mfd_connect .process .base import RemoteProcess
18+
19+ from .base import Connection , ConnectionCompletedProcess
20+
21+ if typing .TYPE_CHECKING :
22+ from pydantic import (
23+ BaseModel , # from pytest_mfd_config.models.topology import ConnectionModel
24+ )
25+
26+
27+ logger = logging .getLogger (__name__ )
28+ add_logging_level (level_name = "MODULE_DEBUG" , level_value = log_levels .MODULE_DEBUG )
29+ add_logging_level (level_name = "CMD" , level_value = log_levels .CMD )
30+ add_logging_level (level_name = "OUT" , level_value = log_levels .OUT )
31+
32+
33+ class RShellConnection (Connection ):
34+ def __init__ (
35+ self ,
36+ ip : str | IPv4Address | IPv6Address ,
37+ server_ip : str | IPv4Address | IPv6Address | None = "127.0.0.1" ,
38+ model : "BaseModel | None" = None ,
39+ cache_system_data : bool = True ,
40+ connection_timeout : int = 60 ,
41+ ):
42+ """
43+ Initialize RShellConnection.
44+
45+ :param ip: The IP address of the RShell server.
46+ :param server_ip: The IP address of the server to connect to (optional).
47+ :param model: The Pydantic model to use for the connection (optional).
48+ :param cache_system_data: Whether to cache system data (default: True).
49+ """
50+ super ().__init__ (model = model , cache_system_data = cache_system_data )
51+ self ._ip = ip
52+ self .server_ip = server_ip if server_ip else "127.0.0.1"
53+ self .server_process = None
54+ if server_ip == "127.0.0.1" :
55+ # start Rshell server
56+ self .server_process = self ._run_server ()
57+ time .sleep (5 )
58+ timeout = TimeoutCounter (connection_timeout )
59+ while not timeout :
60+ logger .log (level = log_levels .MODULE_DEBUG , msg = "Checking RShell server health" )
61+ status_code = requests .get (f"http://{ self .server_ip } /health/{ self ._ip } " , proxies = {"no_proxy" :"*" }).status_code
62+ if status_code == 200 :
63+ logger .log (level = log_levels .MODULE_DEBUG , msg = "RShell server is healthy" )
64+ break
65+ time .sleep (5 )
66+ else :
67+ raise TimeoutError ("Connection of Client to RShell server timed out" )
68+
69+
70+ def disconnect (self , stop_client : bool = False ) -> None :
71+ """
72+ Disconnect connection.
73+
74+ Stop local RShell server if established.
75+
76+ :param stop_client: Whether to stop the RShell client (default: False).
77+ """
78+ if stop_client :
79+ logger .log (level = log_levels .MODULE_DEBUG , msg = "Stopping RShell client" )
80+ self .execute_command ("end" )
81+ if self .server_process :
82+ logger .log (level = log_levels .MODULE_DEBUG , msg = "Stopping RShell server" )
83+ self .server_process .kill ()
84+ logger .log (level = log_levels .MODULE_DEBUG , msg = "RShell server stopped" )
85+ logger .log (level = log_levels .MODULE_DEBUG , msg = self .server_process .stdout_text )
86+
87+ def _run_server (self ) -> RemoteProcess :
88+ """Run RShell server locally."""
89+ conn = LocalConnection ()
90+ server_file = conn .path (__file__ ).parent / "rshell_server.py"
91+ return conn .start_process (f"{ conn .modules ().sys .executable } { server_file } " )
92+
93+ def execute_command (
94+ self ,
95+ command : str ,
96+ * ,
97+ input_data : str | None = None ,
98+ cwd : str | None = None ,
99+ timeout : int | None = None ,
100+ env : dict | None = None ,
101+ stderr_to_stdout : bool = False ,
102+ discard_stdout : bool = False ,
103+ discard_stderr : bool = False ,
104+ skip_logging : bool = False ,
105+ expected_return_codes = ...,
106+ shell : bool = False ,
107+ custom_exception : type [CalledProcessError ] | None = None ,
108+ ) -> ConnectionCompletedProcess :
109+ """
110+ Execute a command on the remote server.
111+
112+ :param command: The command to execute.
113+ :param timeout: The timeout for the command execution (optional).
114+ :return: The result of the command execution.
115+ """
116+ if input_data is not None :
117+ logger .log (
118+ level = log_levels .MODULE_DEBUG ,
119+ msg = "Input data is not supported for RShellConnection and will be ignored." ,
120+ )
121+
122+ if cwd is not None :
123+ logger .log (
124+ level = log_levels .MODULE_DEBUG ,
125+ msg = "CWD is not supported for RShellConnection and will be ignored." ,
126+ )
127+
128+ if env is not None :
129+ logger .log (
130+ level = log_levels .MODULE_DEBUG ,
131+ msg = "Environment variables are not supported for RShellConnection and will be ignored." ,
132+ )
133+
134+ if env is not None :
135+ logger .log (
136+ level = log_levels .MODULE_DEBUG ,
137+ msg = "Environment variables are not supported for RShellConnection and will be ignored." ,
138+ )
139+
140+ if stderr_to_stdout :
141+ logger .log (
142+ level = log_levels .MODULE_DEBUG ,
143+ msg = "Redirecting stderr to stdout is not supported for RShellConnection and will be ignored." ,
144+ )
145+
146+ if discard_stdout :
147+ logger .log (
148+ level = log_levels .MODULE_DEBUG ,
149+ msg = "Discarding stdout is not supported for RShellConnection and will be ignored." ,
150+ )
151+
152+ if discard_stderr :
153+ logger .log (
154+ level = log_levels .MODULE_DEBUG ,
155+ msg = "Discarding stderr is not supported for RShellConnection and will be ignored." ,
156+ )
157+
158+ if skip_logging :
159+ logger .log (
160+ level = log_levels .MODULE_DEBUG ,
161+ msg = "Skipping logging is not supported for RShellConnection and will be ignored." ,
162+ )
163+
164+ if expected_return_codes is not None :
165+ logger .log (
166+ level = log_levels .MODULE_DEBUG ,
167+ msg = "Expected return codes are not supported for RShellConnection and will be ignored." ,
168+ )
169+
170+ if shell :
171+ logger .log (
172+ level = log_levels .MODULE_DEBUG ,
173+ msg = "Shell execution is not supported for RShellConnection and will be ignored." ,
174+ )
175+
176+ if custom_exception :
177+ logger .log (
178+ level = log_levels .MODULE_DEBUG ,
179+ msg = "Custom exceptions are not supported for RShellConnection and will be ignored." ,
180+ )
181+ timeout_string = " " if timeout is None else f" with timeout { timeout } seconds"
182+ logger .log (level = log_levels .CMD , msg = f"Executing >{ self ._ip } > '{ command } ', { timeout_string } " )
183+
184+ response = requests .post (
185+ f"http://{ self .server_ip } /execute_command" ,
186+ data = {"command" : command , "timeout" : timeout , "ip" : self ._ip },proxies = {"no_proxy" :"*" },
187+ )
188+ completed_process = ConnectionCompletedProcess (
189+ args = command ,
190+ stdout = response .text ,
191+ return_code = int (response .headers .get ("rc" , - 1 )),
192+ )
193+ logger .log (
194+ level = log_levels .MODULE_DEBUG ,
195+ msg = f"Finished executing '{ command } ', rc={ completed_process .return_code } " ,
196+ )
197+ if skip_logging :
198+ return completed_process
199+
200+ stdout = completed_process .stdout
201+ if stdout :
202+ logger .log (level = log_levels .OUT , msg = f"stdout>>\n { stdout } " )
203+
204+ return completed_process
205+
206+
207+ def path (self , * args , ** kwargs ) -> CustomPath :
208+ """Path represents a filesystem path."""
209+ if sys .version_info >= (3 , 12 ):
210+ kwargs ["owner" ] = self
211+ return custom_path_factory (* args , ** kwargs )
212+
213+ return CustomPath (* args , owner = self , ** kwargs )
214+
215+ def get_os_name (self ) -> OSName :
216+ raise NotImplementedError
217+
218+ def get_os_type (self ) -> OSType :
219+ raise NotImplementedError
220+
221+ def get_os_bitness (self ) -> OSBitness :
222+ raise NotImplementedError
223+
224+ def get_cpu_architecture (self ) -> CPUArchitecture :
225+ raise NotImplementedError
226+
227+ def restart_platform (self ) -> None :
228+ raise NotImplementedError
229+
230+ def shutdown_platform (self ) -> None :
231+ raise NotImplementedError
232+
233+ def wait_for_host (self , timeout : int = 60 ) -> None :
234+ raise NotImplementedError
0 commit comments