New stop plug-in

A means of controlling when OBMC Boot Test stops.

Also, added additional plug-in support.

Change-Id: I77051805451ecf4431b79b54b090860ac858ec0b
Signed-off-by: Michael Walsh <micwalsh@us.ibm.com>
diff --git a/lib/gen_call_robot.py b/lib/gen_call_robot.py
new file mode 100755
index 0000000..5b4b01c
--- /dev/null
+++ b/lib/gen_call_robot.py
@@ -0,0 +1,430 @@
+#!/usr/bin/env python
+
+r"""
+This module provides functions which are useful to plug-ins call-point
+programs that wish to make external robot program calls.
+"""
+
+import sys
+import os
+import subprocess
+import re
+import time
+import imp
+
+import gen_print as gp
+import gen_valid as gv
+import gen_misc as gm
+import gen_cmd as gc
+
+auto_base_path = "/afs/rchland.ibm.com/projects/esw/dvt/autoipl/"
+
+base_path = \
+    os.path.dirname(os.path.dirname(imp.find_module("gen_robot_print")[1])) +\
+    os.sep
+
+
+def init_robot_out_parms(extra_prefix=""):
+
+    r"""
+    Initialize robot output parms such as outputdir, output, etc.
+
+    This function will set global values for the following robot output parms.
+
+    outputdir, output, log, report, loglevel
+
+    This function would typically be called prior to calling
+    create_robot_cmd_string.
+    """
+
+    AUTOBOOT_OPENBMC_NICKNAME = gm.get_mod_global("AUTOBOOT_OPENBMC_NICKNAME")
+
+    FFDC_DIR_PATH_STYLE = os.environ.get('FFDC_DIR_PATH_STYLE', '0')
+    if FFDC_DIR_PATH_STYLE == '1':
+        default_ffdc_dir_path = "/tmp/"
+    else:
+        default_ffdc_dir_path = base_path
+    # Set values for call to create_robot_cmd_string.
+    outputdir = gm.add_trailing_slash(os.environ.get("FFDC_DIR_PATH",
+                                                     default_ffdc_dir_path))
+    seconds = time.time()
+    loc_time = time.localtime(seconds)
+    time_string = time.strftime("%y%m%d.%H%M%S", loc_time)
+    file_prefix = AUTOBOOT_OPENBMC_NICKNAME + "." + extra_prefix +\
+        time_string + "."
+    output = file_prefix + "output.xml"
+    log = file_prefix + "log.html"
+    report = file_prefix + "report.html"
+    loglevel = "TRACE"
+
+    # Make create_robot_cmd_string values global.
+    gm.set_mod_global(outputdir)
+    gm.set_mod_global(output)
+    gm.set_mod_global(log)
+    gm.set_mod_global(report)
+    gm.set_mod_global(loglevel)
+
+
+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.
+            cmd_buf = 'dirname $(which gen_print.py)'
+            gp.dpissuing(cmd_buf)
+            sub_proc = \
+                subprocess.Popen(cmd_buf, shell=True, stdout=subprocess.PIPE,
+                                 stderr=subprocess.STDOUT)
+            out_buf, err_buf = sub_proc.communicate()
+            shell_rc = sub_proc.returncode
+            executable_base_dir_path = out_buf.rstrip() + "/"
+
+            # Strip the "land.ibm.com" for truer comparison with
+            # apollo_dir_path.
+            executable_base_dir_path = \
+                executable_base_dir_path.replace('land.ibm.com', '')
+
+            apollo_dir_path = re.sub('land\.ibm\.com', '', 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)
+
+
+raw_robot_file_search_path = "${ROBOT_TEST_BASE_DIR_PATH}:" +\
+    "${ROBOT_TEST_BASE_DIR_PATH}tests:${ROBOT_TEST_BASE_DIR_PATH}extended:" +\
+    "${ROBOT_TEST_BASE_DIR_PATH}scratch:${PATH}"
+
+
+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 + "\""
+        gp.dpissuing(cmd_buf)
+        sub_proc = subprocess.Popen(cmd_buf, shell=True,
+                                    stdout=subprocess.PIPE,
+                                    stderr=subprocess.STDOUT)
+        out_buf, err_buf = sub_proc.communicate()
+        shell_rc = sub_proc.returncode
+        robot_file_search_paths = out_buf
+        gp.dpvar(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 get_robot_parm_names():
+
+    r"""
+    Return a list containing all of the long parm names (e.g. --outputdir)
+    supported by the robot program.  Double dashes are not included in the
+    names returned.
+    """
+
+    cmd_buf = "robot -h | egrep " +\
+        "'^([ ]\-[a-zA-Z0-9])?[ ]+--[a-zA-Z0-9]+[ ]+' | sed -re" +\
+        " s'/.*\-\-//g' -e s'/ .*//g' | sort -u"
+    sub_proc = subprocess.Popen(cmd_buf, shell=True, stdout=subprocess.PIPE,
+                                stderr=subprocess.STDOUT)
+    out_buf, err_buf = sub_proc.communicate()
+    shell_rc = sub_proc.returncode
+    if shell_rc != 0:
+        hex = 1
+        print_var(shell_rc, hex)
+        print(out_buf)
+
+    return out_buf.split("\n")
+
+
+def create_robot_cmd_string(robot_file_path, *parms):
+
+    r"""
+    Create a robot command string and return it.  On failure, return an empty
+    string.
+
+    Description of arguments:
+    robot_file_path                 The path to the robot file to be run.
+    parms                           The list of parms to be included in the
+                                    command string.  The name of each variable
+                                    in this list must be the same as the name
+                                    of the corresponding parm.  This function
+                                    figures out that name.  This function is
+                                    also able to distinguish robot parms (e.g.
+                                    --outputdir) from robot program parms (all
+                                    other parms which will be passed as "-v
+                                    PARM_NAME:parm_value")..
+
+    Example:
+
+    The following call to this function...
+    cmd_buf = create_robot_cmd_string("tools/start_sol_console.robot",
+    OPENBMC_HOST, quiet, test_mode, debug, outputdir, output, log, report)
+
+    Would return a string something like this.
+    robot -v OPENBMC_HOST:beye6 -v quiet:0 -v test_mode:1 -v debug:1
+    --outputdir=/gsa/ausgsa/projects/a/autoipl/status
+    --output=beye6.OS_Console.output.xml --log=beye6.OS_Console.log.html
+    --report=beye6.OS_Console.report.html tools/start_sol_console.robot
+    """
+
+    robot_file_path = init_robot_file_path(robot_file_path)
+
+    robot_parm_names = get_robot_parm_names()
+
+    robot_parm_list = []
+
+    stack_frame = 2
+    ix = 2
+    for arg in parms:
+        parm = arg
+        parm = gm.quote_bash_parm(gm.escape_bash_quotes(str(parm)))
+        var_name = gp.get_arg_name(None, ix, stack_frame)
+        if var_name in robot_parm_names:
+            p_string = "--" + var_name + "=" + str(parm)
+            robot_parm_list.append(p_string)
+        else:
+            p_string = "-v " + var_name + ":" + str(parm)
+            robot_parm_list.append(p_string)
+        ix += 1
+
+    robot_cmd_buf = "robot " + ' '.join(robot_parm_list) + " " +\
+        robot_file_path
+
+    return robot_cmd_buf
+
+
+def robot_cmd_fnc(robot_cmd_buf,
+                  robot_jail=os.environ.get('ROBOT_JAIL', ''),
+                  gzip=1):
+
+    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.
+    gqip                            This indicates that the log, report and
+                                    output files produced by robot should be
+                                    gzipped to save space.
+    """
+
+    if not gv.valid_value(robot_cmd_buf):
+        return False
+
+    # 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")
+
+    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:
+        PYTHONPATH = ROBOT_TEST_BASE_DIR_PATH + "lib"
+        NEW_PATH_LIST = [ROBOT_TEST_BASE_DIR_PATH + "bin"]
+        # Coding special case to preserve python27_path.
+        python27_path = "/opt/rh/python27/root/usr/bin"
+        PATH_LIST = os.environ.get("PATH", "").split(":")
+        if python27_path in PATH_LIST:
+            NEW_PATH_LIST.append(python27_path)
+        NEW_PATH_LIST.extend(["/usr/local/sbin", "/usr/local/bin", "/usr/sbin",
+                              "/usr/bin", "/sbin", "/bin"])
+        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/"
+
+    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')
+
+    test_mode = getattr(module, "test_mode")
+
+    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
+    if shell_rc != 0:
+        hex = 1
+        gp.pvar(shell_rc, hex)
+        os.environ["PATH"] = os.environ.get("SAVED_PATH", "")
+        os.environ["PYTHONPATH"] = os.environ.get("SAVED_PYTHONPATH", "")
+        return False
+
+    os.environ["PATH"] = os.environ.get("SAVED_PATH", "")
+    os.environ["PYTHONPATH"] = os.environ.get("SAVED_PYTHONPATH", "")
+
+    if not gzip:
+        return True
+
+    # gzip the output files.
+    # Retrieve the parms from the robot command buffer.
+    robot_cmd_buf_dict = gc.parse_command_string(robot_cmd_buf)
+    # Get prefix from the log parm.
+    prefix = re.sub('log\.html$', '', robot_cmd_buf_dict['log'])
+    gp.qprintn()
+    rc, outbuf = gc.cmd_fnc("cd " + robot_cmd_buf_dict['outputdir'] +
+                            " ; gzip " + robot_cmd_buf_dict['output'] +
+                            " " + robot_cmd_buf_dict['log'] +
+                            " " + robot_cmd_buf_dict['report'])
+
+    outputdir = gm.add_trailing_slash(robot_cmd_buf_dict['outputdir'])
+    Output = outputdir + robot_cmd_buf_dict['output'] + ".gz"
+    Log = outputdir + robot_cmd_buf_dict['log'] + ".gz"
+    Report = outputdir + robot_cmd_buf_dict['report'] + ".gz"
+    gp.qprintn("\ngzipped output:")
+    gp.qpvars(0, 9, Output, Log, Report)
+
+    return True
diff --git a/lib/gen_cmd.py b/lib/gen_cmd.py
index 8f0c249..f9bd3cf 100644
--- a/lib/gen_cmd.py
+++ b/lib/gen_cmd.py
@@ -85,6 +85,7 @@
     sub_proc = subprocess.Popen(cmd_buf,
                                 bufsize=1,
                                 shell=True,
+                                executable='/bin/bash',
                                 stdout=subprocess.PIPE,
                                 stderr=stderr)
     out_buf = ""
diff --git a/lib/gen_plug_in_utils.py b/lib/gen_plug_in_utils.py
index 4c378fc..f1d647f 100755
--- a/lib/gen_plug_in_utils.py
+++ b/lib/gen_plug_in_utils.py
@@ -10,6 +10,7 @@
 import collections
 
 import gen_print as gp
+import gen_misc as gm
 
 
 def get_plug_in_package_name(case=None):
@@ -59,6 +60,34 @@
         os.environ['AUTOBOOT_OPENBMC_NICKNAME'] = \
             os.environ.get("AUTOBOOT_OPENBMC_HOST", "")
 
+    # 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 = gp.pgm_dir_path + "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 + "_" + 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
+
+    # Initialize unset plug-in vars.
+    for var_name in plug_in_parm_names:
+        os.environ[var_name] = os.environ.get(var_name, "")
+
     plug_var_dict = \
         collections.OrderedDict(sorted({k: v for (k, v) in
                                         os.environ.items()
diff --git a/lib/gen_print.py b/lib/gen_print.py
index 90f78be..61cb726 100755
--- a/lib/gen_print.py
+++ b/lib/gen_print.py
@@ -38,7 +38,8 @@
 # importing this module.
 pgm_file_path = sys.argv[0]
 pgm_name = os.path.basename(pgm_file_path)
-pgm_dir_path = re.sub("/" + pgm_name, "", pgm_file_path) + "/"
+pgm_dir_path = os.path.normpath(re.sub("/" + pgm_name, "", pgm_file_path)) +\
+    os.path.sep
 
 
 # Some functions (e.g. sprint_pgm_header) have need of a program name value
diff --git a/lib/gen_robot_plug_in.py b/lib/gen_robot_plug_in.py
index ec79d6b..1dc8983 100755
--- a/lib/gen_robot_plug_in.py
+++ b/lib/gen_robot_plug_in.py
@@ -177,7 +177,8 @@
             grp.rprint_timen("Processing " + call_point +
                              " call point programs.")
 
-    proc_plug_pkg_rc = subprocess.call(cmd_buf, shell=True)
+    proc_plug_pkg_rc = subprocess.call(cmd_buf, shell=True,
+                                       executable='/bin/bash')
 
     # As process_plug_in_packages.py help text states, it will print the
     # values of failed_plug_in_name and shell_rc in the following format:
diff --git a/lib/obmc_boot_test.py b/lib/obmc_boot_test.py
index e431804..8b6040a 100755
--- a/lib/obmc_boot_test.py
+++ b/lib/obmc_boot_test.py
@@ -859,11 +859,11 @@
 
     plug_in_setup()
     rc, shell_rc, failed_plug_in_name = grpi.rprocess_plug_in_packages(
-        call_point='stop_check')
-    if rc != 0:
-        error_message = "Stopping as requested by user.\n"
-        grp.rprint_error_report(error_message)
-        BuiltIn().fail(error_message)
+        call_point='stop_check', shell_rc=0x00000200, stop_on_non_zero_rc=1)
+    if shell_rc == 0x00000200:
+        message = "Stopping as requested by user.\n"
+        gp.print_time(message)
+        BuiltIn().fail(message)
 
     # This should help prevent ConnectionErrors.
     grk.run_key_u("Close All Connections")