def compose_plug_in_save_dir_path(plug_in_package_name=None): r""" Create and return a directory path name that is suitable for saving plug-in data. The name will be comprised of things such as plug_in package name, pid, etc. in order to guarantee that it is unique for a given test run. Description of argument(s): plug_in_package_name The plug-in package name. This defaults to the name of the caller's plug-in package. However, the caller can specify another value in order to retrieve data saved by another plug-in package. """ plug_in_package_name = gm.dft(plug_in_package_name, get_plug_in_package_name()) BASE_TOOL_DIR_PATH = \ gm.add_trailing_slash(os.environ.get(PLUG_VAR_PREFIX + "_BASE_TOOL_DIR_PATH", "/tmp/")) NICKNAME = os.environ.get("AUTOBOOT_OPENBMC_NICKNAME", "") if NICKNAME == "": NICKNAME = os.environ["AUTOIPL_FSP1_NICKNAME"] MASTER_PID = os.environ[PLUG_VAR_PREFIX + "_MASTER_PID"] gp.dprint_vars(BASE_TOOL_DIR_PATH, NICKNAME, plug_in_package_name, MASTER_PID) return BASE_TOOL_DIR_PATH + gm.username() + "/" + NICKNAME + "/" +\ plug_in_package_name + "/" + str(MASTER_PID) + "/"
def print_report(self, header_footer="\n", quiet=None): r""" Print the formatted boot_resuls_table to the console. Description of argument(s): See sprint_report for details. quiet Only print if this value is 0. This function will search upward in the stack to get the default value. """ quiet = int(gm.dft(quiet, gp.get_stack_var('quiet', 0))) gp.qprint(self.sprint_report(header_footer))
def print_boot_history(boot_history, quiet=None): r""" Print the last ten boots done with their time stamps. Description of argument(s): quiet Only print if this value is 0. This function will search upward in the stack to get the default value. """ quiet = int(gm.dft(quiet, gp.get_stack_var('quiet', 0))) # indent 0, 90 chars wide, linefeed, char is "=" gp.qprint_dashes(0, 90) gp.qprintn("Last 10 boots:\n") for boot_entry in boot_history: gp.qprint(boot_entry) gp.qprint_dashes(0, 90)
def required_plug_in(required_plug_in_names, plug_in_dir_paths=None): r""" Determine whether the required_plug_in_names are in plug_in_dir_paths, construct an error_message and call gv.process_error_message(error_message). In addition, for each plug-in in required_plug_in_names, set the global plug-in variables. This is useful for callers who then want to validate certain values from other plug-ins. Example call: required_plug_in(required_plug_in_names) Description of argument(s): required_plug_in_names A list of plug_in names that the caller requires (e.g. ['OS_Console']). plug_in_dir_paths A string which is a colon-delimited list of plug-ins specified by the user (e.g. DB_Logging:FFDC:OS_Console:Perf). Path values (e.g. "/home/robot/dir1") will be stripped from this list to do the analysis. Default value is the AUTOGUI_PLUG_IN_DIR_PATHS or <PLUG_VAR_PREFIX>_PLUG_IN_DIR_PATHS environment variable. """ # Calculate default value for plug_in_dir_paths. plug_in_dir_paths = gm.dft( plug_in_dir_paths, os.environ.get( 'AUTOGUI_PLUG_IN_DIR_PATHS', os.environ.get(PLUG_VAR_PREFIX + "_PLUG_IN_DIR_PATHS", ""))) # Convert plug_in_dir_paths to a list of base names. plug_in_dir_paths = \ list(filter(None, map(os.path.basename, plug_in_dir_paths.split(":")))) error_message = gv.valid_list(plug_in_dir_paths, required_values=required_plug_in_names) if error_message: return gv.process_error_message(error_message) for plug_in_package_name in required_plug_in_names: get_plug_vars(general=False, plug_in_package_name=plug_in_package_name)
def return_plug_vars(general=True, custom=True, plug_in_package_name=None): r""" Return an OrderedDict which is sorted by key and which contains all of the plug-in environment variables. Example excerpt of resulting dictionary: plug_var_dict: [AUTOBOOT_BASE_TOOL_DIR_PATH]: /tmp/ [AUTOBOOT_BB_LEVEL]: <blank> [AUTOBOOT_BOOT_FAIL]: 0 ... This function also does the following: - Set a default value for environment variable AUTOBOOT_OPENBMC_NICKNAME/AUTOIPL_FSP1_NICKNAME if it is not already set. - Register PASSWORD variables to prevent their values from being printed. Note: The programmer may set a default for any given environment variable by declaring a global variable of the same name and setting its value. For example, let's say the calling program has this global declaration: PERF_EXERCISERS_TOTAL_TIMEOUT = '180' If environment variable PERF_EXERCISERS_TOTAL_TIMEOUT is blank or not set, this function will set it to '180'. Furthermore, if such a default variable declaration is not a string, this function will preserve that non-string type in setting global variables (with the exception of os.environ values which must be string). Example: NVDIMM_ENCRYPT = 0 Description of argument(s): general Return general plug-in parms (e.g. those beginning with "AUTOBOOT" or "AUTOGUI"). custom Return custom plug-in parms (i.e. those beginning with the upper case name of the plug-in package, for example "OBMC_SAMPLE_PARM1"). plug_in_package_name The name of the plug-in package for which custom parms are to be returned. The default is the current plug in package name. """ regex_list = [] if not (general or custom): return collections.OrderedDict() plug_in_package_name = gm.dft(plug_in_package_name, get_plug_in_package_name()) if general: regex_list = [PLUG_VAR_PREFIX, "AUTOGUI"] if custom: regex_list.append(plug_in_package_name.upper()) regex = "^(" + "|".join(regex_list) + ")_" # Set a default for nickname. if os.environ.get("AUTOBOOT_OPENBMC_NICKNAME", "") == "": os.environ['AUTOBOOT_OPENBMC_NICKNAME'] = \ os.environ.get("AUTOBOOT_OPENBMC_HOST", "") if os.environ.get("AUTOIPL_FSP1_NICKNAME", "") == "": os.environ['AUTOIPL_FSP1_NICKNAME'] = \ os.environ.get("AUTOIPL_FSP1_NAME", "").split(".")[0] # For all variables specified in the parm_def file, we want them to default to "" rather than being unset. # Process the parm_def file if it exists. parm_def_file_path = os.path.dirname(gp.pgm_dir_path.rstrip("/")) + "/" + plug_in_package_name \ + "/parm_def" if os.path.exists(parm_def_file_path): parm_defs = gm.my_parm_file(parm_def_file_path) else: parm_defs = collections.OrderedDict() # Example parm_defs: # parm_defs: # parm_defs[rest_fail]: boolean # parm_defs[command]: string # parm_defs[esel_stop_file_path]: string # Create a list of plug-in environment variables by pre-pending <all caps plug-in package name>_<all # caps var name> plug_in_parm_names = [ plug_in_package_name.upper() + "_" + x for x in map(str.upper, parm_defs.keys()) ] # Example plug_in_parm_names: # plug_in_parm_names: # plug_in_parm_names[0]: STOP_REST_FAIL # plug_in_parm_names[1]: STOP_COMMAND # plug_in_parm_names[2]: STOP_ESEL_STOP_FILE_PATH # os.environ only accepts string values. However, if the user defines default values of other types # (e.g. int), we wish to preserve the type. non_string_defaults = {} # Initialize unset plug-in vars. for var_name in plug_in_parm_names: # If there is a global variable with the same name as the environment variable, use its value as a # default. default_value = gm.get_mod_global(var_name, "") if type(default_value) is not str: non_string_defaults[var_name] = type(default_value) os.environ[var_name] = os.environ.get(var_name, str(default_value)) if os.environ[var_name] == "": os.environ[var_name] = str(default_value) plug_var_dict = \ collections.OrderedDict(sorted({k: v for (k, v) in os.environ.items() if re.match(regex, k)}.items())) # Restore the types of any variables where the caller had defined default values. for key, value in non_string_defaults.items(): cmd_buf = "plug_var_dict[key] = " + str(value).split( "'")[1] + "(plug_var_dict[key]" if value is int: # Use int base argument of 0 to allow it to interpret hex strings. cmd_buf += ", 0)" else: cmd_buf += ")" exec(cmd_buf) in globals(), locals() # Register password values to prevent printing them out. Any plug var whose name ends in PASSWORD will # be registered. password_vals = { k: v for (k, v) in plug_var_dict.items() if re.match(r".*_PASSWORD$", k) }.values() map(gp.register_passwords, password_vals) return plug_var_dict
def shell_cmd(command_string, quiet=None, print_output=None, show_err=1, test_mode=0, time_out=None, max_attempts=1, retry_sleep_time=5, allowed_shell_rcs=[0], ignore_err=None, return_stderr=0, fork=0): r""" Run the given command string in a shell and return a tuple consisting of the shell return code and the output. Description of argument(s): command_string The command string to be run in a shell (e.g. "ls /tmp"). quiet If set to 0, this function will print "Issuing: <cmd string>" to stdout. When the quiet argument is set to None, this function will assign a default value by searching upward in the stack for the quiet variable value. If no such value is found, quiet is set to 0. print_output If this is set, this function will print the stdout/stderr generated by the shell command to stdout. show_err If show_err is set, this function will print a standardized error report if the shell command fails (i.e. if the shell command returns a shell_rc that is not in allowed_shell_rcs). Note: Error text is only printed if ALL attempts to run the command_string fail. In other words, if the command execution is ultimately successful, initial failures are hidden. test_mode If test_mode is set, this function will not actually run the command. If print_output is also set, this function will print "(test_mode) Issuing: <cmd string>" to stdout. A caller should call shell_cmd directly if they wish to have the command string run unconditionally. They should call the t_shell_cmd wrapper (defined below) if they wish to run the command string only if the prevailing test_mode variable is set to 0. time_out A time-out value expressed in seconds. If the command string has not finished executing within <time_out> seconds, it will be halted and counted as an error. max_attempts The max number of attempts that should be made to run the command string. retry_sleep_time The number of seconds to sleep between attempts. allowed_shell_rcs A list of integers indicating which shell_rc values are not to be considered errors. ignore_err Ignore error means that a failure encountered by running the command string will not be raised as a python exception. When the ignore_err argument is set to None, this function will assign a default value by searching upward in the stack for the ignore_err variable value. If no such value is found, ignore_err is set to 1. return_stderr If return_stderr is set, this function will process the stdout and stderr streams from the shell command separately. In such a case, the tuple returned by this function will consist of three values rather than just two: rc, stdout, stderr. fork Run the command string asynchronously (i.e. don't wait for status of the child process and don't try to get stdout/stderr). """ # Assign default values to some of the arguments to this function. quiet = int(gm.dft(quiet, gp.get_stack_var('quiet', 0))) print_output = int(gm.dft(print_output, not quiet)) show_err = int(show_err) global_ignore_err = gp.get_var_value(ignore_err, 1) stack_ignore_err = gp.get_stack_var('ignore_err', global_ignore_err) ignore_err = int(gm.dft(ignore_err, gm.dft(stack_ignore_err, 1))) err_msg = gv.valid_value(command_string) if err_msg != "": raise ValueError(err_msg) if not quiet: gp.print_issuing(command_string, test_mode) if test_mode: if return_stderr: return 0, "", "" else: return 0, "" # Convert each list entry to a signed value. allowed_shell_rcs = fa.source_to_object(allowed_shell_rcs) allowed_shell_rcs = [gm.to_signed(x) for x in allowed_shell_rcs] if return_stderr: stderr = subprocess.PIPE else: stderr = subprocess.STDOUT shell_rc = 0 out_buf = "" err_buf = "" # Write all output to func_history_stdout rather than directly to stdout. # This allows us to decide what to print after all attempts to run the # command string have been made. func_history_stdout will contain the # complete stdout history from the current invocation of this function. func_history_stdout = "" for attempt_num in range(1, max_attempts + 1): sub_proc = subprocess.Popen(command_string, bufsize=1, shell=True, executable='/bin/bash', stdout=subprocess.PIPE, stderr=stderr) out_buf = "" err_buf = "" # Output from this loop iteration is written to func_stdout for later # processing. func_stdout = "" if fork: break command_timed_out = False if time_out is not None: # Designate a SIGALRM handling function and set alarm. signal.signal(signal.SIGALRM, shell_cmd_timed_out) signal.alarm(time_out) try: if return_stderr: for line in sub_proc.stderr: try: err_buf += line except TypeError: line = line.decode("utf-8") err_buf += line if not print_output: continue func_stdout += line for line in sub_proc.stdout: try: out_buf += line except TypeError: line = line.decode("utf-8") out_buf += line if not print_output: continue func_stdout += line except IOError: command_timed_out = True sub_proc.communicate() shell_rc = sub_proc.returncode # Restore the original SIGALRM handler and clear the alarm. signal.signal(signal.SIGALRM, original_sigalrm_handler) signal.alarm(0) if shell_rc in allowed_shell_rcs: break err_msg = "The prior shell command failed.\n" if quiet: err_msg += gp.sprint_var(command_string) if command_timed_out: err_msg += gp.sprint_var(command_timed_out) err_msg += gp.sprint_var(time_out) err_msg += gp.sprint_varx("child_pid", sub_proc.pid) err_msg += gp.sprint_var(attempt_num) err_msg += gp.sprint_var(shell_rc, gp.hexa()) err_msg += gp.sprint_var(allowed_shell_rcs, gp.hexa()) if not print_output: if return_stderr: err_msg += "err_buf:\n" + err_buf err_msg += "out_buf:\n" + out_buf if show_err: func_stdout += gp.sprint_error_report(err_msg) func_history_stdout += func_stdout if attempt_num < max_attempts: func_history_stdout += gp.sprint_issuing("time.sleep(" + str(retry_sleep_time) + ")") time.sleep(retry_sleep_time) if shell_rc not in allowed_shell_rcs: func_stdout = func_history_stdout gp.gp_print(func_stdout) if shell_rc not in allowed_shell_rcs: if not ignore_err: if robot_env: BuiltIn().fail(err_msg) else: raise ValueError("The prior shell command failed.\n") if return_stderr: return shell_rc, out_buf, err_buf else: return shell_rc, out_buf
def process_robot_output_files(robot_cmd_buf=None, robot_rc=None, gzip=1): r""" Process robot output files which can involve several operations: - If the files are in a temporary location, using SAVE_STATUS_POLICY to decide whether to move them to a permanent location or to delete them. - Gzipping them. Description of argument(s): robot_cmd_buf The complete command string used to invoke robot. robot_rc The return code from running the robot command string. gzip Indicates whether robot-generated output should be gzipped. """ robot_cmd_buf = gm.dft(robot_cmd_buf, gcr_last_robot_cmd_buf) robot_rc = gm.dft(robot_rc, gcr_last_robot_rc) if robot_cmd_buf == "": # This can legitimately occur if this function is called from an # exit_function without the program having ever run robot_cmd_fnc. return SAVE_STATUS_POLICY = os.environ.get("SAVE_STATUS_POLICY", "ALWAYS") gp.qprint_vars(SAVE_STATUS_POLICY) # When SAVE_STATUS_POLICY is "NEVER" robot output files don't even get # generated. if SAVE_STATUS_POLICY == "NEVER": return # Compose file_list based on robot command buffer passed in. robot_cmd_buf_dict = gc.parse_command_string(robot_cmd_buf) outputdir = robot_cmd_buf_dict['outputdir'] outputdir = gm.add_trailing_slash(outputdir) file_list = outputdir + robot_cmd_buf_dict['output'] + " " + outputdir\ + robot_cmd_buf_dict['log'] + " " + outputdir\ + robot_cmd_buf_dict['report'] # Double checking that files are present. shell_rc, out_buf = gc.shell_cmd("ls -1 " + file_list + " 2>/dev/null", show_err=0) file_list = re.sub("\n", " ", out_buf.rstrip("\n")) if file_list == "": gp.qprint_timen("No robot output files were found in " + outputdir + ".") return gp.qprint_var(robot_rc, 1) if SAVE_STATUS_POLICY == "FAIL" and robot_rc == 0: gp.qprint_timen("The call to robot produced no failures." + " Deleting robot output files.") gc.shell_cmd("rm -rf " + file_list) return if gzip: gc.shell_cmd("gzip " + file_list) # Update the values in file_list. file_list = re.sub(" ", ".gz ", file_list) + ".gz" # It TMP_ROBOT_DIR_PATH is set, it means the caller wanted the robot # output initially directed to TMP_ROBOT_DIR_PATH but later moved to # FFDC_DIR_PATH. Otherwise, we're done. if os.environ.get("TMP_ROBOT_DIR_PATH", "") is "": return # We're directing these to the FFDC dir path so that they'll be subjected # to FFDC cleanup. target_dir_path = os.environ.get("FFDC_DIR_PATH", os.environ.get("HOME", ".") + "/autoipl/ffdc") target_dir_path = gm.add_trailing_slash(target_dir_path) targ_file_list = [re.sub(".*/", target_dir_path, x) for x in file_list.split(" ")] gc.shell_cmd("mv " + file_list + " " + target_dir_path + " >/dev/null", time_out=600) gp.qprint_timen("New robot log file locations:") gp.qprintn('\n'.join(targ_file_list))
def robot_cmd_fnc(robot_cmd_buf, robot_jail=os.environ.get('ROBOT_JAIL', ''), quiet=None, test_mode=0): r""" Run the robot command string. This function will set the various PATH variables correctly so that you are running the proper version of all imported files, etc. Description of argument(s): robot_cmd_buf The complete robot command string. robot_jail Indicates that this is to run in "robot jail" meaning without visibility to any apolloxxx import files, programs, etc. test_mode If test_mode is set, this function will not actually run the command. """ quiet = int(gm.dft(quiet, gp.get_stack_var('quiet', 0))) gv.valid_value(robot_cmd_buf) # Set global variables to aid in cleanup with process_robot_output_files. global gcr_last_robot_cmd_buf global gcr_last_robot_rc gcr_last_robot_cmd_buf = robot_cmd_buf # Get globals set by init_robot_test_base_dir_path(). module = sys.modules["__main__"] try: ROBOT_TEST_BASE_DIR_PATH = getattr(module, "ROBOT_TEST_BASE_DIR_PATH") except NameError: init_robot_test_base_dir_path() ROBOT_TEST_BASE_DIR_PATH = getattr(module, "ROBOT_TEST_BASE_DIR_PATH") ROBOT_TEST_RUNNING_FROM_SB = gm.get_mod_global( "ROBOT_TEST_RUNNING_FROM_SB") OPENBMCTOOL_DIR_PATH = gm.get_mod_global("OPENBMCTOOL_DIR_PATH") if robot_jail == "": if ROBOT_TEST_RUNNING_FROM_SB: robot_jail = 0 else: robot_jail = 1 robot_jail = int(robot_jail) ROBOT_JAIL = os.environ.get('ROBOT_JAIL', '') gp.dprint_vars(ROBOT_TEST_BASE_DIR_PATH, ROBOT_TEST_RUNNING_FROM_SB, ROBOT_JAIL, robot_jail) # Save PATH and PYTHONPATH to be restored later. os.environ["SAVED_PYTHONPATH"] = os.environ.get("PYTHONPATH", "") os.environ["SAVED_PATH"] = os.environ.get("PATH", "") if robot_jail: # Make sure required programs like python and robot can be found in the new restricted PATH. required_programs = "python robot" # It is expected that there will be a "python" program in the tool base bin path which is really a # link to select_version. Ditto for "robot". Call each with the --print_only option to get the # paths to the "real" programs. cmd_buf = "for program in " + required_programs \ + " ; do dirname $(${program} --print_only) ; done 2>/dev/null" rc, out_buf = gc.shell_cmd(cmd_buf, quiet=1, print_output=0) PYTHONPATH = ROBOT_TEST_BASE_DIR_PATH + "lib" NEW_PATH_LIST = [ROBOT_TEST_BASE_DIR_PATH + "bin"] NEW_PATH_LIST.extend(list(set(out_buf.rstrip("\n").split("\n")))) NEW_PATH_LIST.extend([ "/usr/local/sbin", "/usr/local/bin", "/usr/sbin", "/usr/bin", "/sbin", "/bin", OPENBMCTOOL_DIR_PATH.rstrip('/') ]) PATH = ":".join(NEW_PATH_LIST) else: PYTHONPATH = os.environ.get('PYTHONPATH', '') + ":" +\ ROBOT_TEST_BASE_DIR_PATH + "lib" PATH = os.environ.get('PATH', '') + ":" + ROBOT_TEST_BASE_DIR_PATH +\ "bin" + ":" + OPENBMCTOOL_DIR_PATH.rstrip('/') os.environ['PYTHONPATH'] = PYTHONPATH os.environ['PATH'] = PATH gp.dprint_vars(PATH, PYTHONPATH) os.environ['FFDC_DIR_PATH_STYLE'] = os.environ.get('FFDC_DIR_PATH_STYLE', '1') gp.qpissuing(robot_cmd_buf, test_mode) if test_mode: os.environ["PATH"] = os.environ.get("SAVED_PATH", "") os.environ["PYTHONPATH"] = os.environ.get("SAVED_PYTHONPATH", "") return True if quiet: DEVNULL = open(os.devnull, 'wb') stdout = DEVNULL else: stdout = None sub_proc = subprocess.Popen(robot_cmd_buf, stdout=stdout, shell=True) sub_proc.communicate() shell_rc = sub_proc.returncode os.environ["PATH"] = os.environ.get("SAVED_PATH", "") os.environ["PYTHONPATH"] = os.environ.get("SAVED_PYTHONPATH", "") gcr_last_robot_rc = shell_rc process_robot_output_files() if shell_rc != 0: gp.print_var(shell_rc, gp.hexa()) return False return True
def shell_cmd(command_string, quiet=None, print_output=None, show_err=1, test_mode=0, time_out=None, max_attempts=1, retry_sleep_time=5, valid_rcs=[0], ignore_err=None, return_stderr=0, fork=0, error_regexes=None): r""" Run the given command string in a shell and return a tuple consisting of the shell return code and the output. Description of argument(s): command_string The command string to be run in a shell (e.g. "ls /tmp"). quiet If set to 0, this function will print "Issuing: <cmd string>" to stdout. When the quiet argument is set to None, this function will assign a default value by searching upward in the stack for the quiet variable value. If no such value is found, quiet is set to 0. print_output If this is set, this function will print the stdout/stderr generated by the shell command to stdout. show_err If show_err is set, this function will print a standardized error report if the shell command fails (i.e. if the shell command returns a shell_rc that is not in valid_rcs). Note: Error text is only printed if ALL attempts to run the command_string fail. In other words, if the command execution is ultimately successful, initial failures are hidden. test_mode If test_mode is set, this function will not actually run the command. If print_output is also set, this function will print "(test_mode) Issuing: <cmd string>" to stdout. A caller should call shell_cmd directly if they wish to have the command string run unconditionally. They should call the t_shell_cmd wrapper (defined below) if they wish to run the command string only if the prevailing test_mode variable is set to 0. time_out A time-out value expressed in seconds. If the command string has not finished executing within <time_out> seconds, it will be halted and counted as an error. max_attempts The max number of attempts that should be made to run the command string. retry_sleep_time The number of seconds to sleep between attempts. valid_rcs A list of integers indicating which shell_rc values are not to be considered errors. ignore_err Ignore error means that a failure encountered by running the command string will not be raised as a python exception. When the ignore_err argument is set to None, this function will assign a default value by searching upward in the stack for the ignore_err variable value. If no such value is found, ignore_err is set to 1. return_stderr If return_stderr is set, this function will process the stdout and stderr streams from the shell command separately. In such a case, the tuple returned by this function will consist of three values rather than just two: rc, stdout, stderr. fork Run the command string asynchronously (i.e. don't wait for status of the child process and don't try to get stdout/stderr) and return the Popen object created by the subprocess.popen() function. See the kill_cmd function for details on how to process the popen object. error_regexes A list of regular expressions to be used to identify errors in the command output. If there is a match for any of these regular expressions, the command will be considered a failure and the shell_rc will be set to -1. For example, if error_regexes = ['ERROR:'] and the command output contains 'ERROR: Unrecognized option', it will be counted as an error even if the command returned 0. This is useful when running commands that do not always return non-zero on error. """ err_msg = gv.valid_value(command_string) if err_msg: raise ValueError(err_msg) # Assign default values to some of the arguments to this function. quiet = int(gm.dft(quiet, gp.get_stack_var('quiet', 0))) print_output = int(gm.dft(print_output, not quiet)) show_err = int(show_err) ignore_err = int(gm.dft(ignore_err, gp.get_stack_var('ignore_err', 1))) gp.qprint_issuing(command_string, test_mode) if test_mode: return (0, "", "") if return_stderr else (0, "") # Convert a string python dictionary definition to a dictionary. valid_rcs = fa.source_to_object(valid_rcs) # Convert each list entry to a signed value. valid_rcs = [gm.to_signed(x) for x in valid_rcs] stderr = subprocess.PIPE if return_stderr else subprocess.STDOUT # Write all output to func_out_history_buf rather than directly to stdout. This allows us to decide # what to print after all attempts to run the command string have been made. func_out_history_buf will # contain the complete history from the current invocation of this function. global command_timed_out command_timed_out = False func_out_history_buf = "" for attempt_num in range(1, max_attempts + 1): sub_proc = subprocess.Popen(command_string, preexec_fn=os.setsid, bufsize=1, shell=True, universal_newlines=True, executable='/bin/bash', stdout=subprocess.PIPE, stderr=stderr) if fork: return sub_proc if time_out: command_timed_out = False # Designate a SIGALRM handling function and set alarm. signal.signal(signal.SIGALRM, shell_cmd_timed_out) signal.alarm(time_out) try: stdout_buf, stderr_buf = sub_proc.communicate() except IOError: command_timed_out = True # Restore the original SIGALRM handler and clear the alarm. signal.signal(signal.SIGALRM, original_sigalrm_handler) signal.alarm(0) # Output from this loop iteration is written to func_out_buf for later processing. This can include # stdout, stderr and our own error messages. func_out_buf = "" if print_output: if return_stderr: func_out_buf += stderr_buf func_out_buf += stdout_buf shell_rc = sub_proc.returncode if shell_rc in valid_rcs: # Check output for text indicating there is an error. if error_regexes and re.match('|'.join(error_regexes), stdout_buf): shell_rc = -1 else: break err_msg = "The prior shell command failed.\n" err_msg += gp.sprint_var(attempt_num) err_msg += gp.sprint_vars(command_string, command_timed_out, time_out) err_msg += gp.sprint_varx("child_pid", sub_proc.pid) err_msg += gp.sprint_vars(shell_rc, valid_rcs, fmt=gp.hexa()) if error_regexes: err_msg += gp.sprint_vars(error_regexes) if not print_output: if return_stderr: err_msg += "stderr_buf:\n" + stderr_buf err_msg += "stdout_buf:\n" + stdout_buf if show_err: func_out_buf += gp.sprint_error_report(err_msg) if attempt_num < max_attempts: cmd_buf = "time.sleep(" + str(retry_sleep_time) + ")" if show_err: func_out_buf += gp.sprint_issuing(cmd_buf) exec(cmd_buf) func_out_history_buf += func_out_buf if shell_rc in valid_rcs: gp.gp_print(func_out_buf) else: if show_err: gp.gp_print(func_out_history_buf, stream='stderr') else: # There is no error information to show so just print output from last loop iteration. gp.gp_print(func_out_buf) if not ignore_err: # If the caller has already asked to show error info, avoid repeating that in the failure message. err_msg = "The prior shell command failed.\n" if show_err \ else err_msg if robot_env: BuiltIn().fail(err_msg) else: raise ValueError(err_msg) return (shell_rc, stdout_buf, stderr_buf) if return_stderr \ else (shell_rc, stdout_buf)