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) + "/"
Пример #2
0
    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))
Пример #3
0
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
Пример #6
0
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
Пример #7
0
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)