| #!/usr/bin/env python |
| |
| r""" |
| This module provides many valuable ssh functions such as sprint_connection, |
| execute_ssh_command, etc. |
| """ |
| |
| import sys |
| import re |
| import socket |
| import paramiko |
| import exceptions |
| |
| import gen_print as gp |
| import func_timer as ft |
| func_timer = ft.func_timer_class() |
| |
| from robot.libraries.BuiltIn import BuiltIn |
| from SSHLibrary import SSHLibrary |
| sshlib = SSHLibrary() |
| |
| |
| def sprint_connection(connection, |
| indent=0): |
| r""" |
| sprint data from the connection object to a string and return it. |
| |
| connection A connection object which is created by |
| the SSHlibrary open_connection() function. |
| indent The number of characters to indent the |
| output. |
| """ |
| |
| buffer = gp.sindent("", indent) |
| buffer += "connection:\n" |
| indent += 2 |
| buffer += gp.sprint_varx("index", connection.index, 0, indent) |
| buffer += gp.sprint_varx("host", connection.host, 0, indent) |
| buffer += gp.sprint_varx("alias", connection.alias, 0, indent) |
| buffer += gp.sprint_varx("port", connection.port, 0, indent) |
| buffer += gp.sprint_varx("timeout", connection.timeout, 0, indent) |
| buffer += gp.sprint_varx("newline", connection.newline, 0, indent) |
| buffer += gp.sprint_varx("prompt", connection.prompt, 0, indent) |
| buffer += gp.sprint_varx("term_type", connection.term_type, 0, indent) |
| buffer += gp.sprint_varx("width", connection.width, 0, indent) |
| buffer += gp.sprint_varx("height", connection.height, 0, indent) |
| buffer += gp.sprint_varx("path_separator", connection.path_separator, 0, |
| indent) |
| buffer += gp.sprint_varx("encoding", connection.encoding, 0, indent) |
| |
| return buffer |
| |
| |
| def sprint_connections(connections=None, |
| indent=0): |
| r""" |
| sprint data from the connections list to a string and return it. |
| |
| connections A list of connection objects which are |
| created by the SSHlibrary open_connection |
| function. If this value is null, this |
| function will populate with a call to the |
| SSHlibrary get_connections() function. |
| indent The number of characters to indent the |
| output. |
| """ |
| |
| if connections is None: |
| connections = sshlib.get_connections() |
| |
| buffer = "" |
| for connection in connections: |
| buffer += sprint_connection(connection, indent) |
| |
| return buffer |
| |
| |
| def find_connection(open_connection_args={}): |
| r""" |
| Find connection that matches the given connection arguments and return |
| connection object. Return False if no matching connection is found. |
| |
| Description of argument(s): |
| open_connection_args A dictionary of arg names and values which |
| are legal to pass to the SSHLibrary |
| open_connection function as parms/args. |
| For a match to occur, the value for each |
| item in open_connection_args must match |
| the corresponding value in the connection |
| being examined. |
| """ |
| |
| global sshlib |
| |
| for connection in sshlib.get_connections(): |
| # Create connection_dict from connection object. |
| connection_dict = dict((key, str(value)) for key, value in |
| connection._config.iteritems()) |
| if dict(connection_dict, **open_connection_args) == connection_dict: |
| return connection |
| |
| return False |
| |
| |
| def login_ssh(login_args={}, |
| max_login_attempts=5): |
| r""" |
| Login on the latest open SSH connection. Retry on failure up to |
| max_login_attempts. |
| |
| The caller is responsible for making sure there is an open SSH connection. |
| |
| Description of argument(s): |
| login_args A dictionary containing the key/value |
| pairs which are acceptable to the |
| SSHLibrary login function as parms/args. |
| At a minimum, this should contain a |
| 'username' and a 'password' entry. |
| max_login_attempts The max number of times to try logging in |
| (in the event of login failures). |
| """ |
| |
| gp.lprint_executing() |
| |
| global sshlib |
| |
| # Get connection data for debug output. |
| connection = sshlib.get_connection() |
| gp.lprintn(sprint_connection(connection)) |
| for login_attempt_num in range(1, max_login_attempts + 1): |
| gp.lprint_timen("Logging in to " + connection.host + ".") |
| gp.lprint_var(login_attempt_num) |
| try: |
| out_buf = sshlib.login(**login_args) |
| except Exception as login_exception: |
| # Login will sometimes fail if the connection is new. |
| except_type, except_value, except_traceback = sys.exc_info() |
| gp.lprint_var(except_type) |
| gp.lprint_varx("except_value", str(except_value)) |
| if except_type is paramiko.ssh_exception.SSHException and\ |
| re.match(r"No existing session", str(except_value)): |
| continue |
| else: |
| # We don't tolerate any other error so break from loop and |
| # re-raise exception. |
| break |
| # If we get to this point, the login has worked and we can return. |
| gp.lpvar(out_buf) |
| return |
| |
| # If we get to this point, the login has failed on all attempts so the |
| # exception will be raised again. |
| raise(login_exception) |
| |
| |
| def execute_ssh_command(cmd_buf, |
| open_connection_args={}, |
| login_args={}, |
| print_out=0, |
| print_err=0, |
| ignore_err=1, |
| fork=0, |
| quiet=None, |
| test_mode=None, |
| time_out=None): |
| r""" |
| Run the given command in an SSH session and return the stdout, stderr and |
| the return code. |
| |
| If there is no open SSH connection, this function will connect and login. |
| Likewise, if the caller has not yet logged in to the connection, this |
| function will do the login. |
| |
| NOTE: There is special handling when open_connection_args['alias'] equals |
| "device_connection". |
| - A write, rather than an execute_command, is done. |
| - Only stdout is returned (no stderr or rc). |
| - print_err, ignore_err and fork are not supported. |
| |
| Description of arguments: |
| cmd_buf The command string to be run in an SSH |
| session. |
| open_connection_args A dictionary of arg names and values which |
| are legal to pass to the SSHLibrary |
| open_connection function as parms/args. |
| At a minimum, this should contain a 'host' |
| entry. |
| login_args A dictionary containing the key/value |
| pairs which are acceptable to the |
| SSHLibrary login function as parms/args. |
| At a minimum, this should contain a |
| 'username' and a 'password' entry. |
| print_out If this is set, this function will print |
| the stdout/stderr generated by the shell |
| command. |
| print_err If show_err is set, this function will |
| print a standardized error report if the |
| shell command returns non-zero. |
| ignore_err Indicates that errors encountered on the |
| sshlib.execute_command are to be ignored. |
| fork Indicates that sshlib.start is to be used |
| rather than sshlib.execute_command. |
| quiet Indicates whether this function should run |
| the pissuing() function which prints an |
| "Issuing: <cmd string>" to stdout. This |
| defaults to the global quiet value. |
| test_mode If test_mode is set, this function will |
| not actually run the command. This |
| defaults to the global test_mode value. |
| time_out The amount of time to allow for the |
| execution of cmd_buf. A value of None |
| means that there is no limit to how long |
| the command may take. |
| """ |
| |
| gp.lprint_executing() |
| |
| # Obtain default values. |
| quiet = int(gp.get_var_value(quiet, 0)) |
| test_mode = int(gp.get_var_value(test_mode, 0)) |
| |
| if not quiet: |
| gp.pissuing(cmd_buf, test_mode) |
| gp.lpissuing(cmd_buf, test_mode) |
| |
| if test_mode: |
| return "", "", 0 |
| |
| global sshlib |
| |
| max_exec_cmd_attempts = 2 |
| # Look for existing SSH connection. |
| # Prepare a search connection dictionary. |
| search_connection_args = open_connection_args.copy() |
| # Remove keys that don't work well for searches. |
| search_connection_args.pop("timeout", None) |
| connection = find_connection(search_connection_args) |
| if connection: |
| gp.lprint_timen("Found the following existing connection:") |
| gp.lprintn(sprint_connection(connection)) |
| if connection.alias == "": |
| index_or_alias = connection.index |
| else: |
| index_or_alias = connection.alias |
| gp.lprint_timen("Switching to existing connection: \"" |
| + str(index_or_alias) + "\".") |
| sshlib.switch_connection(index_or_alias) |
| else: |
| gp.lprint_timen("Connecting to " + open_connection_args['host'] + ".") |
| cix = sshlib.open_connection(**open_connection_args) |
| try: |
| login_ssh(login_args) |
| except Exception as login_exception: |
| except_type, except_value, except_traceback = sys.exc_info() |
| rc = 1 |
| stderr = str(except_value) |
| stdout = "" |
| max_exec_cmd_attempts = 0 |
| |
| for exec_cmd_attempt_num in range(1, max_exec_cmd_attempts + 1): |
| gp.lprint_var(exec_cmd_attempt_num) |
| try: |
| if fork: |
| sshlib.start_command(cmd_buf) |
| else: |
| if open_connection_args['alias'] == "device_connection": |
| stdout = sshlib.write(cmd_buf) |
| stderr = "" |
| rc = 0 |
| else: |
| stdout, stderr, rc = \ |
| func_timer.run(sshlib.execute_command, |
| cmd_buf, |
| return_stdout=True, |
| return_stderr=True, |
| return_rc=True, |
| time_out=time_out) |
| except Exception as execute_exception: |
| except_type, except_value, except_traceback = sys.exc_info() |
| gp.lprint_var(except_type) |
| gp.lprint_varx("except_value", str(except_value)) |
| |
| if except_type is exceptions.AssertionError and\ |
| re.match(r"Connection not open", str(except_value)): |
| try: |
| login_ssh(login_args) |
| # Now we must continue to next loop iteration to retry the |
| # execute_command. |
| continue |
| except Exception as login_exception: |
| except_type, except_value, except_traceback =\ |
| sys.exc_info() |
| rc = 1 |
| stderr = str(except_value) |
| stdout = "" |
| break |
| |
| if (except_type is paramiko.ssh_exception.SSHException |
| and re.match(r"SSH session not active", str(except_value))) or\ |
| (except_type is socket.error |
| and re.match(r"\[Errno 104\] Connection reset by peer", |
| str(except_value))): |
| # Close and re-open a connection. |
| # Note: close_connection() doesn't appear to get rid of the |
| # connection. It merely closes it. Since there is a concern |
| # about over-consumption of resources, we use |
| # close_all_connections() which also gets rid of all |
| # connections. |
| gp.lprint_timen("Closing all connections.") |
| sshlib.close_all_connections() |
| gp.lprint_timen("Connecting to " |
| + open_connection_args['host'] + ".") |
| cix = sshlib.open_connection(**open_connection_args) |
| login_ssh(login_args) |
| continue |
| |
| # We do not handle any other RuntimeErrors so we will raise the |
| # exception again. |
| sshlib.close_all_connections() |
| raise(execute_exception) |
| |
| # If we get to this point, the command was executed. |
| break |
| |
| if fork: |
| return |
| |
| if rc != 0 and print_err: |
| gp.print_var(rc, 1) |
| if not print_out: |
| gp.print_var(stderr) |
| gp.print_var(stdout) |
| |
| if print_out: |
| gp.printn(stderr + stdout) |
| |
| if not ignore_err: |
| message = gp.sprint_error("The prior SSH" |
| + " command returned a non-zero return" |
| + " code:\n" + gp.sprint_var(rc, 1) + stderr |
| + "\n") |
| BuiltIn().should_be_equal(rc, 0, message) |
| |
| if open_connection_args['alias'] == "device_connection": |
| return stdout |
| return stdout, stderr, rc |