Commit 5d786098 authored by Robert Schmidt's avatar Robert Schmidt

RemoteCmd/LocalCmd: various fixes

- allow executing in background
- less logs
- no use of split()
- common implementation of legacy command()
parent 0a0cd884
...@@ -32,10 +32,17 @@ import os ...@@ -32,10 +32,17 @@ import os
import paramiko import paramiko
import uuid import uuid
# helper that returns either LocalCmd or RemoteCmd based on passed host name
def getConnection(host, d=None):
if host is None or host.lower() in ["", "none", "localhost"]:
return LocalCmd(d=d)
else:
return RemoteCmd(host, d=d)
# provides a partial interface for the legacy SSHconnection class (getBefore(), command()) # provides a partial interface for the legacy SSHconnection class (getBefore(), command())
class Cmd(metaclass=abc.ABCMeta): class Cmd(metaclass=abc.ABCMeta):
def cd(self, d, silent=False): def cd(self, d, silent=False):
if d == None or d == '' or d == []: if d == None or d == '':
self.cwd = None self.cwd = None
elif d[0] == '/': elif d[0] == '/':
self.cwd = d self.cwd = d
...@@ -51,9 +58,13 @@ class Cmd(metaclass=abc.ABCMeta): ...@@ -51,9 +58,13 @@ class Cmd(metaclass=abc.ABCMeta):
def run(self, line, timeout=300, silent=False): def run(self, line, timeout=300, silent=False):
return return
@abc.abstractmethod def command(self, commandline, expectedline=None, timeout=300, silent=False, resync=False):
def command(self, commandline, expectedline, timeout, silent=False, resync=False): splitted = commandline.split(' ')
return if splitted[0] == 'cd':
self.cd(' '.join(splitted[1:]), silent)
else:
self.run(commandline, timeout, silent)
return 0
@abc.abstractmethod @abc.abstractmethod
def close(self): def close(self):
...@@ -82,7 +93,12 @@ class LocalCmd(Cmd): ...@@ -82,7 +93,12 @@ class LocalCmd(Cmd):
if not silent: if not silent:
logging.info(line) logging.info(line)
try: try:
ret = sp.run(line, shell=True, cwd=self.cwd, stdout=sp.PIPE, stderr=sp.STDOUT, timeout=timeout) if line.strip().endswith('&'):
# if we wait for stdout, subprocess does not return before the end of the command
# however, we don't want to wait for commands with &, so just return fake command
ret = sp.run(line, shell=True, cwd=self.cwd, timeout=5)
else:
ret = sp.run(line, shell=True, cwd=self.cwd, stdout=sp.PIPE, stderr=sp.STDOUT, timeout=timeout)
except Exception as e: except Exception as e:
ret = sp.CompletedProcess(args=line, returncode=255, stdout=f'Exception: {str(e)}'.encode('utf-8')) ret = sp.CompletedProcess(args=line, returncode=255, stdout=f'Exception: {str(e)}'.encode('utf-8'))
if ret.stdout is None: if ret.stdout is None:
...@@ -93,14 +109,6 @@ class LocalCmd(Cmd): ...@@ -93,14 +109,6 @@ class LocalCmd(Cmd):
self.cp = ret self.cp = ret
return ret return ret
def command(self, commandline, expectedline=None, timeout=300, silent=False, resync=False):
line = [s for s in commandline.split(' ') if len(s) > 0]
if line[0] == 'cd':
self.cd(line[1], silent)
else:
self.run(line, timeout, silent)
return 0
def close(self): def close(self):
pass pass
...@@ -108,16 +116,14 @@ class LocalCmd(Cmd): ...@@ -108,16 +116,14 @@ class LocalCmd(Cmd):
return self.cp.stdout return self.cp.stdout
def copyin(self, scpIp, scpUser, scpPw, src, tgt): def copyin(self, scpIp, scpUser, scpPw, src, tgt):
logging.warning("LocalCmd emulating sshconnection.copyin() function")
self.run(f'cp -r {src} {tgt}') self.run(f'cp -r {src} {tgt}')
def copyout(self, scpIp, scpUser, scpPw, src, tgt): def copyout(self, scpIp, scpUser, scpPw, src, tgt):
logging.warning("LocalCmd emulating sshconnection.copyout() function")
self.run(f'cp -r {src} {tgt}') self.run(f'cp -r {src} {tgt}')
class RemoteCmd(Cmd): class RemoteCmd(Cmd):
def __init__(self, hostname, d=None): def __init__(self, hostname, d=None):
logging.getLogger('paramiko').setLevel(logging.INFO) # prevent spamming through Paramiko logging.getLogger('paramiko').setLevel(logging.ERROR) # prevent spamming through Paramiko
self.client = paramiko.SSHClient() self.client = paramiko.SSHClient()
self.client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) self.client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
cfg = RemoteCmd._lookup_ssh_config(hostname) cfg = RemoteCmd._lookup_ssh_config(hostname)
...@@ -146,33 +152,27 @@ class RemoteCmd(Cmd): ...@@ -146,33 +152,27 @@ class RemoteCmd(Cmd):
return cfg return cfg
def run(self, line, timeout=300, silent=False, reportNonZero=True): def run(self, line, timeout=300, silent=False, reportNonZero=True):
if type(line) is list:
line = ' '.join(line)
if not silent: if not silent:
logging.debug(line) logging.info(line)
if self.cwd: if self.cwd:
line = f"cd {self.cwd} && {line}" line = f"cd {self.cwd} && {line}"
args = line.split(' ')
try: try:
stdin, stdout, stderr = self.client.exec_command(line, timeout=timeout) if line.strip().endswith('&'):
ret = sp.CompletedProcess(args=args, returncode=stdout.channel.recv_exit_status(), stdout=stdout.read(size=None) + stderr.read(size=None)) # if we wait for stdout, Paramiko does not return before the end of the command
# however, we don't want to wait for commands with &, so just return fake command
self.client.exec_command(line, timeout = 5)
ret = sp.CompletedProcess(args=line, returncode=0, stdout=b'')
else:
stdin, stdout, stderr = self.client.exec_command(line, timeout=timeout)
ret = sp.CompletedProcess(args=line, returncode=stdout.channel.recv_exit_status(), stdout=stdout.read(size=None) + stderr.read(size=None))
except Exception as e: except Exception as e:
ret = sp.CompletedProcess(args=args, returncode=255, stdout=f'Exception: {str(e)}'.encode('utf-8')) ret = sp.CompletedProcess(args=line, returncode=255, stdout=f'Exception: {str(e)}'.encode('utf-8'))
ret.stdout = ret.stdout.decode('utf-8').strip() ret.stdout = ret.stdout.decode('utf-8').strip()
if reportNonZero and ret.returncode != 0: if reportNonZero and ret.returncode != 0:
cmd = ' '.join(ret.args) logging.warning(f'command "{line}" returned non-zero returncode {ret.returncode}: output:\n{ret.stdout}')
logging.warning(f'command "{cmd}" returned non-zero returncode {ret.returncode}: output:\n{ret.stdout}')
self.cp = ret self.cp = ret
return ret return ret
def command(self, commandline, expectedline=None, timeout=300, silent=False, resync=False):
line = [s for s in commandline.split(' ') if len(s) > 0]
if line[0] == 'cd':
self.cd(line[1], silent)
else:
self.run(line, timeout, silent)
return 0
def close(self): def close(self):
self.client.close() self.client.close()
...@@ -180,7 +180,6 @@ class RemoteCmd(Cmd): ...@@ -180,7 +180,6 @@ class RemoteCmd(Cmd):
return self.cp.stdout return self.cp.stdout
def copyout(self, src, tgt, recursive=False): def copyout(self, src, tgt, recursive=False):
logging.warning("RemoteCmd emulating sshconnection.copyout() function, ignoring scpIp")
logging.debug(f"copyout: local:{src} -> remote:{tgt}") logging.debug(f"copyout: local:{src} -> remote:{tgt}")
if recursive: if recursive:
tmpfile = f"{uuid.uuid4()}.tar" tmpfile = f"{uuid.uuid4()}.tar"
...@@ -198,7 +197,6 @@ class RemoteCmd(Cmd): ...@@ -198,7 +197,6 @@ class RemoteCmd(Cmd):
sftp.close() sftp.close()
def copyin(self, src, tgt, recursive=False): def copyin(self, src, tgt, recursive=False):
logging.warning("RemoteCmd emulating sshconnection.copyout() function")
logging.debug(f"copyin: remote:{src} -> local:{tgt}") logging.debug(f"copyin: remote:{src} -> local:{tgt}")
if recursive: if recursive:
tmpfile = f"{uuid.uuid4()}.tar" tmpfile = f"{uuid.uuid4()}.tar"
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment