X-Git-Url: https://gerrit.opnfv.org/gerrit/gitweb?a=blobdiff_plain;f=yardstick%2Fssh.py;h=6bc6010f7fc02fbb7dd179363f2096202f12645d;hb=ea8429bfe1d130d2d7ee6e4202d6548019a1b135;hp=d7adc0d05580b69823d5c74d7cbc94692581e408;hpb=9c50ed0e9bdb4dec5a95af40eb0e89ee962fcd40;p=yardstick.git diff --git a/yardstick/ssh.py b/yardstick/ssh.py index d7adc0d05..6bc6010f7 100644 --- a/yardstick/ssh.py +++ b/yardstick/ssh.py @@ -62,15 +62,13 @@ Eventlet: sshclient = eventlet.import_patched("yardstick.ssh") """ -from __future__ import absolute_import -import os import io +import logging +import os +import re import select import socket import time -import re - -import logging import paramiko from chainmap import ChainMap @@ -78,9 +76,11 @@ from oslo_utils import encodeutils from scp import SCPClient import six +from yardstick.common import exceptions from yardstick.common.utils import try_int, NON_NONE_DEFAULT, make_dict_from_map from yardstick.network_services.utils import provision_tool +LOG = logging.getLogger(__name__) def convert_key_to_str(key): if not isinstance(key, (paramiko.RSAKey, paramiko.DSSKey)): @@ -90,14 +90,6 @@ def convert_key_to_str(key): return k.getvalue() -class SSHError(Exception): - pass - - -class SSHTimeout(SSHError): - pass - - class SSH(object): """Represent ssh connection.""" @@ -193,7 +185,7 @@ class SSH(object): return key_class.from_private_key(key) except paramiko.SSHException as e: errors.append(e) - raise SSHError("Invalid pkey: %s" % errors) + raise exceptions.SSHError(error_msg='Invalid pkey: %s' % errors) @property def is_connected(self): @@ -214,10 +206,10 @@ class SSH(object): return self._client except Exception as e: message = ("Exception %(exception_type)s was raised " - "during connect. Exception value is: %(exception)r") + "during connect. Exception value is: %(exception)r" % + {"exception": e, "exception_type": type(e)}) self._client = False - raise SSHError(message % {"exception": e, - "exception_type": type(e)}) + raise exceptions.SSHError(error_msg=message) def _make_dict(self): return { @@ -334,11 +326,11 @@ class SSH(object): break if timeout and (time.time() - timeout) > start_time: - args = {"cmd": cmd, "host": self.host} - raise SSHTimeout("Timeout executing command " - "'%(cmd)s' on host %(host)s" % args) + message = ('Timeout executing command %(cmd)s on host %(host)s' + % {"cmd": cmd, "host": self.host}) + raise exceptions.SSHTimeout(error_msg=message) if e: - raise SSHError("Socket error.") + raise exceptions.SSHError(error_msg='Socket error') exit_status = session.recv_exit_status() if exit_status != 0 and raise_on_error: @@ -346,15 +338,18 @@ class SSH(object): details = fmt % {"cmd": cmd, "status": exit_status} if stderr_data: details += " Last stderr data: '%s'." % stderr_data - raise SSHError(details) + LOG.critical("PROX ERROR: %s", details) + raise exceptions.SSHError(error_msg=details) return exit_status - def execute(self, cmd, stdin=None, timeout=3600): + def execute(self, cmd, stdin=None, timeout=3600, raise_on_error=False): """Execute the specified command on the server. - :param cmd: Command to be executed. - :param stdin: Open file to be sent on process stdin. - :param timeout: Timeout for execution of the command. + :param cmd: (str) Command to be executed. + :param stdin: (StringIO) Open file to be sent on process stdin. + :param timeout: (int) Timeout for execution of the command. + :param raise_on_error: (bool) If True, then an SSHError will be raised + when non-zero exit code. :returns: tuple (exit_status, stdout, stderr) """ @@ -363,7 +358,7 @@ class SSH(object): exit_status = self.run(cmd, stderr=stderr, stdout=stdout, stdin=stdin, - timeout=timeout, raise_on_error=False) + timeout=timeout, raise_on_error=raise_on_error) stdout.seek(0) stderr.seek(0) return exit_status, stdout.read(), stderr.read() @@ -377,11 +372,12 @@ class SSH(object): while True: try: return self.execute("uname") - except (socket.error, SSHError) as e: + except (socket.error, exceptions.SSHError) as e: self.log.debug("Ssh is still unavailable: %r", e) time.sleep(interval) if time.time() > end_time: - raise SSHTimeout("Timeout waiting for '%s'" % self.host) + raise exceptions.SSHTimeout( + error_msg='Timeout waiting for "%s"' % self.host) def put(self, files, remote_path=b'.', recursive=False): client = self._get_client() @@ -454,6 +450,86 @@ class SSH(object): with client.open_sftp() as sftp: sftp.getfo(remotepath, file_obj) + def interactive_terminal_open(self, time_out=45): + """Open interactive terminal on a SSH channel. + + :param time_out: Timeout in seconds. + :returns: SSH channel with opened terminal. + + .. warning:: Interruptingcow is used here, and it uses + signal(SIGALRM) to let the operating system interrupt program + execution. This has the following limitations: Python signal + handlers only apply to the main thread, so you cannot use this + from other threads. You must not use this in a program that + uses SIGALRM itself (this includes certain profilers) + """ + chan = self._get_client().get_transport().open_session() + chan.get_pty() + chan.invoke_shell() + chan.settimeout(int(time_out)) + chan.set_combine_stderr(True) + + buf = '' + while not buf.endswith((":~# ", ":~$ ", "~]$ ", "~]# ")): + try: + chunk = chan.recv(10 * 1024 * 1024) + if not chunk: + break + buf += chunk + if chan.exit_status_ready(): + self.log.error('Channel exit status ready') + break + except socket.timeout: + raise exceptions.SSHTimeout(error_msg='Socket timeout: %s' % buf) + return chan + + def interactive_terminal_exec_command(self, chan, cmd, prompt): + """Execute command on interactive terminal. + + interactive_terminal_open() method has to be called first! + + :param chan: SSH channel with opened terminal. + :param cmd: Command to be executed. + :param prompt: Command prompt, sequence of characters used to + indicate readiness to accept commands. + :returns: Command output. + + .. warning:: Interruptingcow is used here, and it uses + signal(SIGALRM) to let the operating system interrupt program + execution. This has the following limitations: Python signal + handlers only apply to the main thread, so you cannot use this + from other threads. You must not use this in a program that + uses SIGALRM itself (this includes certain profilers) + """ + chan.sendall('{c}\n'.format(c=cmd)) + buf = '' + while not buf.endswith(prompt): + try: + chunk = chan.recv(10 * 1024 * 1024) + if not chunk: + break + buf += chunk + if chan.exit_status_ready(): + self.log.error('Channel exit status ready') + break + except socket.timeout: + message = ("Socket timeout during execution of command: " + "%(cmd)s\nBuffer content:\n%(buf)s" % {"cmd": cmd, + "buf": buf}) + raise exceptions.SSHTimeout(error_msg=message) + tmp = buf.replace(cmd.replace('\n', ''), '') + for item in prompt: + tmp.replace(item, '') + return tmp + + @staticmethod + def interactive_terminal_close(chan): + """Close interactive terminal SSH channel. + + :param: chan: SSH channel to be closed. + """ + chan.close() + class AutoConnectSSH(SSH): @@ -486,19 +562,21 @@ class AutoConnectSSH(SSH): while True: try: return self._get_client() - except (socket.error, SSHError) as e: + except (socket.error, exceptions.SSHError) as e: self.log.debug("Ssh is still unavailable: %r", e) time.sleep(interval) if time.time() > end_time: - raise SSHTimeout("Timeout waiting for '%s'" % self.host) + raise exceptions.SSHTimeout( + error_msg='Timeout waiting for "%s"' % self.host) def drop_connection(self): """ Don't close anything, just force creation of a new client """ self._client = False - def execute(self, cmd, stdin=None, timeout=3600): + def execute(self, cmd, stdin=None, timeout=3600, raise_on_error=False): self._connect() - return super(AutoConnectSSH, self).execute(cmd, stdin, timeout) + return super(AutoConnectSSH, self).execute(cmd, stdin, timeout, + raise_on_error) def run(self, cmd, stdin=None, stdout=None, stderr=None, raise_on_error=True, timeout=3600,