X-Git-Url: https://gerrit.fd.io/r/gitweb?p=csit.git;a=blobdiff_plain;f=resources%2Flibraries%2Fpython%2Fssh.py;h=e4ac93fb1b3356539b56c3782dbe409f7cfaf30e;hp=3523d5730290a5ee790a73f24f33f8fb7802d698;hb=cd635521219e7d7988ccfd9d0a173ba07217cd00;hpb=a1d5958923570116e19f6153ae2a35c74f881b13 diff --git a/resources/libraries/python/ssh.py b/resources/libraries/python/ssh.py index 3523d57302..e4ac93fb1b 100644 --- a/resources/libraries/python/ssh.py +++ b/resources/libraries/python/ssh.py @@ -16,7 +16,6 @@ import socket import StringIO - from time import time, sleep from paramiko import RSAKey, SSHClient, AutoAddPolicy @@ -24,12 +23,32 @@ from paramiko.ssh_exception import SSHException, NoValidConnectionsError from robot.api import logger from scp import SCPClient, SCPException +from resources.libraries.python.OptionString import OptionString __all__ = ["exec_cmd", "exec_cmd_no_error"] # TODO: load priv key +def raise_from(raising, excepted): + """Function to be replaced by "raise from" in Python 3. + + Neither "six" nor "future" offer good enough implementation right now. + chezsoi.org/lucas/blog/displaying-chained-exceptions-stacktraces-in-python-2 + + Current implementation just logs excepted error, and raises the new one. + + :param raising: The exception to raise. + :param excepted: The exception we excepted and want to log. + :type raising: BaseException + :type excepted: BaseException + :raises: raising + """ + logger.error("Excepted: {exc!r}\nRaising: {rai!r}".format( + exc=excepted, rai=raising)) + raise raising + + class SSHTimeout(Exception): """This exception is raised when a timeout occurs.""" pass @@ -103,13 +122,13 @@ class SSH(object): peer=self._ssh.get_transport().getpeername(), total=(time() - start), ssh=self._ssh)) - except SSHException: - raise IOError('Cannot connect to {host}'. - format(host=node['host'])) + except SSHException as exc: + raise_from(IOError('Cannot connect to {host}'.format( + host=node['host'])), exc) except NoValidConnectionsError as err: - logger.error(repr(err)) - raise IOError('Unable to connect to port {port} on {host}'. - format(port=node['port'], host=node['host'])) + raise_from(IOError( + 'Unable to connect to port {port} on {host}'.format( + port=node['port'], host=node['host'])), err) def disconnect(self, node=None): """Close SSH connection to the node. @@ -156,17 +175,15 @@ class SSH(object): :rtype: tuple(int, str, str) :raises SSHTimeout: If command is not finished in timeout time. """ + if isinstance(cmd, (list, tuple)): + cmd = OptionString(cmd) cmd = str(cmd) stdout = StringIO.StringIO() stderr = StringIO.StringIO() try: chan = self._ssh.get_transport().open_session(timeout=5) peer = self._ssh.get_transport().getpeername() - except AttributeError: - self._reconnect() - chan = self._ssh.get_transport().open_session(timeout=5) - peer = self._ssh.get_transport().getpeername() - except SSHException: + except (AttributeError, SSHException): self._reconnect() chan = self._ssh.get_transport().open_session(timeout=5) peer = self._ssh.get_transport().getpeername() @@ -239,6 +256,8 @@ class SSH(object): >>> # Execute command with input (sudo -S cmd <<< "input") >>> ssh.exec_command_sudo("vpp_api_test", "dump_interface_table") """ + if isinstance(cmd, (list, tuple)): + cmd = OptionString(cmd) if cmd_input is None: command = 'sudo -S {c}'.format(c=cmd) else: @@ -298,9 +317,8 @@ class SSH(object): if chan.exit_status_ready(): logger.error('Channel exit status ready') break - except socket.timeout: - logger.error('Socket timeout: {0}'.format(buf)) - raise Exception('Socket timeout: {0}'.format(buf)) + except socket.timeout as exc: + raise_from(Exception('Socket timeout: {0}'.format(buf)), exc) return chan def interactive_terminal_exec_command(self, chan, cmd, prompt): @@ -332,11 +350,10 @@ class SSH(object): if chan.exit_status_ready(): logger.error('Channel exit status ready') break - except socket.timeout: - logger.error('Socket timeout during execution of command: ' - '{0}\nBuffer content:\n{1}'.format(cmd, buf)) - raise Exception('Socket timeout during execution of command: ' - '{0}\nBuffer content:\n{1}'.format(cmd, buf)) + except socket.timeout as exc: + raise_from(Exception( + 'Socket timeout during execution of command: ' + '{0}\nBuffer content:\n{1}'.format(cmd, buf)), exc) tmp = buf.replace(cmd.replace('\n', ''), '') for item in prompt: tmp.replace(item, '') @@ -468,7 +485,7 @@ def exec_cmd(node, cmd, timeout=600, sudo=False, disconnect=False): def exec_cmd_no_error( node, cmd, timeout=600, sudo=False, message=None, disconnect=False, - retries=0): + retries=0, include_reason=False): """Convenience function to ssh/exec/return out & err. Verifies that return code is zero. @@ -483,6 +500,7 @@ def exec_cmd_no_error( :param message: Error message in case of failure. Default: None. :param disconnect: Close the opened SSH connection if True. :param retries: How many times to retry on failure. + :param include_reason: Whether default info should be appended to message. :type node: dict :type cmd: str or OptionString :type timeout: int @@ -490,6 +508,7 @@ def exec_cmd_no_error( :type message: str :type disconnect: bool :type retries: int + :type include_reason: bool :returns: Stdout, Stderr. :rtype: tuple(str, str) :raises RuntimeError: If bash return code is not 0. @@ -501,8 +520,14 @@ def exec_cmd_no_error( break sleep(1) else: - msg = ('Command execution failed: "{cmd}"\n{stderr}'. - format(cmd=cmd, stderr=stderr) if message is None else message) + msg = 'Command execution failed: "{cmd}"\nRC: {rc}\n{stderr}'.format( + cmd=cmd, rc=ret_code, stderr=stderr) + logger.info(msg) + if message: + if include_reason: + msg = message + '\n' + msg + else: + msg = message raise RuntimeError(msg) return stdout, stderr @@ -532,14 +557,14 @@ def scp_node( try: ssh.connect(node) - except SSHException: - raise RuntimeError('Failed to connect to {host}!' - .format(host=node['host'])) + except SSHException as exc: + raise_from(RuntimeError( + 'Failed to connect to {host}!'.format(host=node['host'])), exc) try: ssh.scp(local_path, remote_path, get, timeout) - except SCPException: - raise RuntimeError('SCP execution failed on {host}!' - .format(host=node['host'])) + except SCPException as exc: + raise_from(RuntimeError( + 'SCP execution failed on {host}!'.format(host=node['host'])), exc) finally: if disconnect: ssh.disconnect()