I ended up solving it by directly interacting with the channel, which you can set a timeout on:
def run_script(self, script_name):
""" Run a script on the remote array and return the stdout
"""
try:
chan = self._ssh.get_transport().open_session()
# five minute timeout on the channel communication
chan.settimeout(5*60.0)
common_functions_file = os.path.join(SCRIPT_DIR, 'zfs_common_functions.js')
common_functions = open(common_functions_file).read().splitlines()
# add common_functions to the top of the script
script_contents = common_functions + open(script_name).read().splitlines()
chan.exec_command('script')
if chan.send_ready():
chan.sendall("\n".join(script_contents))
chan.send("\n.\n")
results = StringIO()
error = StringIO()
bufsize = 1024
while not chan.exit_status_ready():
if chan.recv_ready():
data = chan.recv(bufsize)
while data:
results.write(data)
data = chan.recv(bufsize)
if chan.recv_stderr_ready():
error_buf = chan.recv_stderr(bufsize)
while error_buf:
error.write(error_buf)
error_buf = chan.recv_stderr(bufsize)
exit_status = chan.recv_exit_status()
if exit_status == 0:
return results.getvalue()
else:
raise ZfsScriptError(results.getvalue())
except socket.timeout:
logger.warn("%s: Timeout running %s" %(self.hostname, script_name))
return None
except paramiko.SSHException as e:
logger.warn(
"Couldn't execute script on array %s: %s" % (self.hostname, e))
raise
except AttributeError as e:
logger.exception(e)
raise
except Exception:
raise
finally:
results.close()
error.close()
chan.close()