11import logging
22import socket
3+ import time
34from io import BytesIO , StringIO
45
56import paramiko
1011from scp import SCPClient
1112
1213from .. import settings as app_settings
13- from .exceptions import CommandFailedException
14+ from .exceptions import CommandFailedException , CommandTimeoutException
1415
1516logger = logging .getLogger (__name__ )
1617
@@ -148,7 +149,7 @@ def _connect(self, address):
148149 auth_timeout = app_settings .SSH_AUTH_TIMEOUT ,
149150 banner_timeout = app_settings .SSH_BANNER_TIMEOUT ,
150151 timeout = app_settings .SSH_CONNECTION_TIMEOUT ,
151- ** params
152+ ** params ,
152153 )
153154 except paramiko .ssh_exception .AuthenticationException as e :
154155 # the authentication failure may be caused by the issue
@@ -183,9 +184,12 @@ def exec_command(
183184 - aborts on exceptions
184185 - raises socket.timeout exceptions
185186 """
187+ # paramiko expects timeout as a float
188+ timeout = float (timeout )
186189 logger .info ("Executing command: {0}" .format (command ))
187190 # execute commmand
188191 try :
192+ start_cmd = time .perf_counter ()
189193 stdin , stdout , stderr = self .shell .exec_command (command , timeout = timeout )
190194 # re-raise socket.timeout to avoid being catched
191195 # by the subsequent `except Exception as e` block
@@ -195,8 +199,16 @@ def exec_command(
195199 except Exception as e :
196200 logger .exception (e )
197201 raise e
198- # store command exit status
199- exit_status = stdout .channel .recv_exit_status ()
202+ # workaround https://github.com/paramiko/paramiko/issues/1815
203+ # workaround https://github.com/paramiko/paramiko/issues/1787
204+ # Ref. https://docs.paramiko.org/en/stable/api/channel.html#paramiko.channel.Channel.recv_exit_status # noqa
205+ if not stdout .channel .status_event .wait (
206+ timeout = timeout - (time .perf_counter () - start_cmd )
207+ ):
208+ log_message = f"Command timed out after { timeout } seconds."
209+ logger .info (log_message )
210+ raise CommandTimeoutException (log_message )
211+ exit_status = stdout .channel .exit_status
200212 # log standard output
201213 # try to decode to UTF-8, ignoring unconvertible characters
202214 # https://docs.python.org/3/howto/unicode.html#the-string-type
0 commit comments