def init_robot_file_path(robot_file_path): r""" Determine full path name for the file path passed in robot_file_path and return it. If robot_file_path contains a fully qualified path name, this function will verify that the file exists. If robot_file_path contains a relative path, this function will search for the file and set robot_file_path so that it contains the absolute path to the robot file. This function will search for the robot file using the raw_robot_file_search_path (defined above). Note that if ROBOT_TEST_BASE_DIR_PATH is not set, this function will call init_robot_test_base_dir_path to set it. Description of arguments: robot_file_path The absolute or relative path to a robot file. """ if not gv.valid_value(robot_file_path): raise ValueError('Programmer error.') try: if ROBOT_TEST_BASE_DIR_PATH is NONE: init_robot_test_base_dir_path() except NameError: init_robot_test_base_dir_path() if not re.match(r".*\.(robot|py)$", robot_file_path): # No suffix so we'll assign one of "\.robot". robot_file_path = robot_file_path + ".robot" abs_path = 0 if robot_file_path[0:1] == "/": abs_path = 1 gp.dprint_vars(abs_path, robot_file_path) if not abs_path: cmd_buf = "echo -n \"" + raw_robot_file_search_path + "\"" shell_rc, out_buf = gc.shell_cmd(cmd_buf, quiet=(not debug), print_output=0) robot_file_search_paths = out_buf gp.dprint_var(robot_file_search_paths) robot_file_search_paths_list = robot_file_search_paths.split(':') for search_path in robot_file_search_paths_list: search_path = gm.add_trailing_slash(search_path) candidate_file_path = search_path + robot_file_path gp.dprint_var(candidate_file_path) if os.path.isfile(candidate_file_path): gp.dprint_timen("Found full path to " + robot_file_path + ".") robot_file_path = candidate_file_path break gp.dprint_var(robot_file_path) if not gv.valid_file_path(robot_file_path): raise ValueError('Programmer error.') return robot_file_path
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). """ global sshlib # Get connection data for debug output. connection = sshlib.get_connection() gp.dprintn(sprint_connection(connection)) for login_attempt_num in range(1, max_login_attempts + 1): gp.dprint_timen("Logging in to " + connection.host + ".") gp.dprint_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.dprint_var(except_type) gp.dprint_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.dpvar(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 init_robot_test_base_dir_path(): r""" Initialize and validate the environment variable, ROBOT_TEST_BASE_DIR_PATH and set corresponding global variable ROBOT_TEST_RUNNING_FROM_SB. If ROBOT_TEST_BASE_DIR_PATH is already set, this function will merely validate it. This function will also set environment variable ROBOT_TEST_RUNNING_FROM_SB when ROBOT_TEST_BASE_DIR_PATH is not pre-set. """ # ROBOT_TEST_BASE_DIR_PATH will be set as follows: # This function will determine whether we are running in a user sandbox # or from a standard apolloxxx environment. # - User sandbox: # If there is a <developer's home dir>/git/openbmc-test-automation/, # ROBOT_TEST_BASE_DIR_PATH will be set to that path. Otherwise, we set it # to <program dir path>/git/openbmc-test-automation/ # - Not in user sandbox: # ROBOT_TEST_BASE_DIR_PATH will be set to <program dir # path>/git/openbmc-test-automation/ ROBOT_TEST_BASE_DIR_PATH = os.environ.get('ROBOT_TEST_BASE_DIR_PATH', "") ROBOT_TEST_RUNNING_FROM_SB = \ int(os.environ.get('ROBOT_TEST_RUNNING_FROM_SB', "0")) if ROBOT_TEST_BASE_DIR_PATH == "": # ROBOT_TEST_BASE_DIR_PATH was not set by user/caller. AUTOIPL_VERSION = os.environ.get('AUTOIPL_VERSION', '') if AUTOIPL_VERSION == "": ROBOT_TEST_BASE_DIR_PATH = base_path else: suffix = "git/openbmc-test-automation/" # Determine whether we're running out of a developer sandbox or # simply out of an apolloxxx/bin path. shell_rc, out_buf = gc.shell_cmd('dirname $(which gen_print.py)', quiet=(not debug), print_output=0) executable_base_dir_path = os.path.realpath(out_buf.rstrip()) + "/" apollo_dir_path = os.environ['AUTO_BASE_PATH'] + AUTOIPL_VERSION +\ "/bin/" developer_home_dir_path = re.sub('/sandbox.*', '', executable_base_dir_path) developer_home_dir_path = \ gm.add_trailing_slash(developer_home_dir_path) gp.dprint_vars(executable_base_dir_path, developer_home_dir_path, apollo_dir_path) ROBOT_TEST_RUNNING_FROM_SB = 0 if executable_base_dir_path != apollo_dir_path: ROBOT_TEST_RUNNING_FROM_SB = 1 gp.dprint_vars(ROBOT_TEST_RUNNING_FROM_SB) ROBOT_TEST_BASE_DIR_PATH = developer_home_dir_path + suffix if not os.path.isdir(ROBOT_TEST_BASE_DIR_PATH): gp.dprint_timen("NOTE: Sandbox directory " + ROBOT_TEST_BASE_DIR_PATH + " does not" + " exist.") # Fall back to the apollo dir path. ROBOT_TEST_BASE_DIR_PATH = apollo_dir_path + suffix else: # Use to the apollo dir path. ROBOT_TEST_BASE_DIR_PATH = apollo_dir_path + suffix if not gv.valid_value(ROBOT_TEST_BASE_DIR_PATH): return False gp.dprint_vars(ROBOT_TEST_RUNNING_FROM_SB, ROBOT_TEST_BASE_DIR_PATH) if not gv.valid_dir_path(ROBOT_TEST_BASE_DIR_PATH): return False ROBOT_TEST_BASE_DIR_PATH = gm.add_trailing_slash(ROBOT_TEST_BASE_DIR_PATH) gm.set_mod_global(ROBOT_TEST_BASE_DIR_PATH) os.environ['ROBOT_TEST_BASE_DIR_PATH'] = ROBOT_TEST_BASE_DIR_PATH gm.set_mod_global(ROBOT_TEST_RUNNING_FROM_SB) os.environ['ROBOT_TEST_RUNNING_FROM_SB'] = str(ROBOT_TEST_RUNNING_FROM_SB)
def run(self, func, *args, **kwargs): r""" Run the indicated function with the given args and kwargs and return the value that the function returns. If the time_out value expires, raise a ValueError exception with a detailed error message. This method passes all of the args and kwargs directly to the child function with the following important exception: If kwargs contains a 'time_out' value, it will be used to set the func timer object's time_out value and then the kwargs['time_out'] entry will be removed. If the time-out expires before the function finishes running, this method will raise a ValueError. Example: func_timer = func_timer_class() func_timer.run(run_key, "sleep 3", time_out=2) Example: try: result = func_timer.run(func1, "parm1", time_out=2) print_var(result) except ValueError: print("The func timed out but we're handling it.") Description of argument(s): func The function object which is to be called. args The arguments which are to be passed to the function object. kwargs The keyword arguments which are to be passed to the function object. As noted above, kwargs['time_out'] will get special treatment. """ gp.lprint_executing() # Store method parms as object parms. self.__func = func # Get self.__time_out value from kwargs. If kwargs['time_out'] is # not present, self.__time_out will default to None. self.__time_out = None if 'time_out' in kwargs: self.__time_out = kwargs['time_out'] del kwargs['time_out'] # Convert "none" string to None. try: if self.__time_out.lower() == "none": self.__time_out = None except AttributeError: pass if self.__time_out is not None: self.__time_out = int(self.__time_out) # Ensure that time_out is non-negative. message = gv.svalid_range(self.__time_out, [0], "time_out") if message != "": raise ValueError("\n" + gp.sprint_error_report(message, format='long')) gp.lprint_varx("time_out", self.__time_out) self.__child_pid = 0 if self.__time_out is not None: # Save the original SIGUSR1 handler for later restoration by this # class' methods. self.__original_SIGUSR1_handler = signal.getsignal(signal.SIGUSR1) # Designate a SIGUSR1 handling function. signal.signal(signal.SIGUSR1, self.timed_out) parent_pid = os.getpid() self.__child_pid = os.fork() if self.__child_pid == 0: gp.dprint_timen("Child timer pid " + str(os.getpid()) + ": Sleeping for " + str(self.__time_out) + " seconds.") time.sleep(self.__time_out) gp.dprint_timen("Child timer pid " + str(os.getpid()) + ": Sending SIGUSR1 to parent pid " + str(parent_pid) + ".") os.kill(parent_pid, signal.SIGUSR1) os._exit(0) # Call the user's function with the user's arguments. children = gm.get_child_pids() gp.lprint_var(children) gp.lprint_timen("Calling the user's function.") gp.lprint_varx("func_name", func.__name__) gp.lprint_vars(args, kwargs) try: result = func(*args, **kwargs) except Exception as func_exception: # We must handle all exceptions so that we have the chance to # cleanup before re-raising the exception. gp.lprint_timen("Encountered exception in user's function.") self.cleanup() raise(func_exception) gp.lprint_timen("Returned from the user's function.") self.cleanup() return result
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): 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. 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. """ gp.dprint_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) if test_mode: return "", "", 0 global sshlib # 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.dprint_timen("Found the following existing connection:") gp.dprintn(sprint_connection(connection)) if connection.alias == "": index_or_alias = connection.index else: index_or_alias = connection.alias gp.dprint_timen("Switching to existing connection: \"" + str(index_or_alias) + "\".") sshlib.switch_connection(index_or_alias) else: gp.dprint_timen("Connecting to " + open_connection_args['host'] + ".") cix = sshlib.open_connection(**open_connection_args) login_ssh(login_args) max_exec_cmd_attempts = 2 for exec_cmd_attempt_num in range(1, max_exec_cmd_attempts + 1): gp.dprint_var(exec_cmd_attempt_num) try: if fork: sshlib.start_command(cmd_buf) else: stdout, stderr, rc = sshlib.execute_command(cmd_buf, return_stdout=True, return_stderr=True, return_rc=True) except Exception as execute_exception: except_type, except_value, except_traceback = sys.exc_info() gp.dprint_var(except_type) gp.dprint_varx("except_value", str(except_value)) if except_type is exceptions.AssertionError and\ re.match(r"Connection not open", str(except_value)): login_ssh(login_args) # Now we must continue to next loop iteration to retry the # execute_command. continue 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.dprint_timen("Closing all connections.") sshlib.close_all_connections() gp.dprint_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. 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) return stdout, stderr, rc