1#!/usr/bin/env python
2
3r"""
4This module provides functions which are useful to plug-ins call-point programs that wish to make external
5robot program calls.
6"""
7
8import sys
9import os
10import subprocess
11import re
12import time
13import imp
14
15import gen_print as gp
16import gen_valid as gv
17import gen_misc as gm
18import gen_cmd as gc
19
20base_path = \
21    os.path.dirname(os.path.dirname(imp.find_module("gen_robot_print")[1])) +\
22    os.sep
23
24
25def init_robot_out_parms(extra_prefix=""):
26    r"""
27    Initialize robot output parms such as outputdir, output, etc.
28
29    This function will set global values for the following robot output parms.
30
31    outputdir, output, log, report, loglevel
32
33    This function would typically be called prior to calling create_robot_cmd_string.
34
35    Description of argument(s):
36    extra_prefix                    An extra prefix to be appended to the default prefix for output file
37                                    names.
38    """
39
40    gp.dprint_executing()
41    AUTOBOOT_OPENBMC_NICKNAME = gm.get_mod_global("AUTOBOOT_OPENBMC_NICKNAME")
42
43    # Set values for call to create_robot_cmd_string.
44    # Environment variable TMP_ROBOT_DIR_PATH can be set by the user to indicate that robot-generated output
45    # should initially be written to the specified temporary directory and then moved to the normal output
46    # location after completion.
47    outputdir =\
48        os.environ.get("TMP_ROBOT_DIR_PATH",
49                       os.environ.get("STATUS_DIR_PATH",
50                                      os.environ.get("HOME", ".")
51                                      + "/status"))
52    outputdir = gm.add_trailing_slash(outputdir)
53    seconds = time.time()
54    loc_time = time.localtime(seconds)
55    time_string = time.strftime("%y%m%d.%H%M%S", loc_time)
56    file_prefix = AUTOBOOT_OPENBMC_NICKNAME + "." + extra_prefix +\
57        time_string + "."
58    # Environment variable SAVE_STATUS_POLICY governs when robot-generated output files (e.g. the log.html)
59    # will be moved from TMP_ROBOT_DIR_PATH to FFDC_DIR_PATH.  Valid values are "ALWAYS", "NEVER" and "FAIL".
60    SAVE_STATUS_POLICY = os.environ.get("SAVE_STATUS_POLICY", "ALWAYS")
61    if SAVE_STATUS_POLICY == "NEVER":
62        output = "NONE"
63        log = "NONE"
64        report = "NONE"
65    else:
66        output = file_prefix + "output.xml"
67        log = file_prefix + "log.html"
68        report = file_prefix + "report.html"
69    loglevel = "TRACE"
70
71    # Make create_robot_cmd_string values global.
72    gm.set_mod_global(outputdir)
73    gm.set_mod_global(output)
74    gm.set_mod_global(log)
75    gm.set_mod_global(report)
76    gm.set_mod_global(loglevel)
77
78
79def init_robot_test_base_dir_path():
80    r"""
81    Initialize and validate the environment variable, ROBOT_TEST_BASE_DIR_PATH and set corresponding global
82    variable ROBOT_TEST_RUNNING_FROM_SB.
83
84    If ROBOT_TEST_BASE_DIR_PATH is already set, this function will merely validate it.  This function will
85    also set environment variable ROBOT_TEST_RUNNING_FROM_SB when ROBOT_TEST_BASE_DIR_PATH is not pre-set.
86    """
87
88    # ROBOT_TEST_BASE_DIR_PATH will be set as follows:
89    # This function will determine whether we are running in a user sandbox or from a standard apolloxxx
90    # environment.
91    # - User sandbox:
92    # If there is a <developer's home dir>/git/openbmc-test-automation/, ROBOT_TEST_BASE_DIR_PATH will be
93    # set to that path.  Otherwise, we set it to <program dir path>/git/openbmc-test-automation/
94    # - Not in user sandbox:
95    #   ROBOT_TEST_BASE_DIR_PATH will be set to <program dir path>/git/openbmc-test-automation/
96
97    ROBOT_TEST_BASE_DIR_PATH = os.environ.get('ROBOT_TEST_BASE_DIR_PATH', "")
98    ROBOT_TEST_RUNNING_FROM_SB = \
99        int(os.environ.get('ROBOT_TEST_RUNNING_FROM_SB', "0"))
100    if ROBOT_TEST_BASE_DIR_PATH == "":
101        # ROBOT_TEST_BASE_DIR_PATH was not set by user/caller.
102        AUTOIPL_VERSION = os.environ.get('AUTOIPL_VERSION', '')
103        if AUTOIPL_VERSION == "":
104            ROBOT_TEST_BASE_DIR_PATH = base_path
105        else:
106            suffix = "git/openbmc-test-automation/"
107
108            # Determine whether we're running out of a developer sandbox or simply out of an apolloxxx/bin
109            # path.
110            shell_rc, out_buf = gc.shell_cmd('dirname $(which gen_print.py)',
111                                             quiet=(not debug), print_output=0)
112            executable_base_dir_path = os.path.realpath(out_buf.rstrip()) + "/"
113            apollo_dir_path = os.environ['AUTO_BASE_PATH'] + AUTOIPL_VERSION +\
114                "/bin/"
115            developer_home_dir_path = re.sub('/sandbox.*', '',
116                                             executable_base_dir_path)
117            developer_home_dir_path = \
118                gm.add_trailing_slash(developer_home_dir_path)
119            gp.dprint_vars(executable_base_dir_path, developer_home_dir_path,
120                           apollo_dir_path)
121
122            ROBOT_TEST_RUNNING_FROM_SB = 0
123            if executable_base_dir_path != apollo_dir_path:
124                ROBOT_TEST_RUNNING_FROM_SB = 1
125                gp.dprint_vars(ROBOT_TEST_RUNNING_FROM_SB)
126                ROBOT_TEST_BASE_DIR_PATH = developer_home_dir_path + suffix
127                if not os.path.isdir(ROBOT_TEST_BASE_DIR_PATH):
128                    gp.dprint_timen("NOTE: Sandbox directory "
129                                    + ROBOT_TEST_BASE_DIR_PATH + " does not"
130                                    + " exist.")
131                    # Fall back to the apollo dir path.
132                    ROBOT_TEST_BASE_DIR_PATH = apollo_dir_path + suffix
133            else:
134                # Use to the apollo dir path.
135                ROBOT_TEST_BASE_DIR_PATH = apollo_dir_path + suffix
136
137    OBMC_TOOLS_BASE_DIR_PATH = \
138        os.path.dirname(ROBOT_TEST_BASE_DIR_PATH.rstrip("/")) \
139        + "/openbmc-tools/"
140    OPENBMCTOOL_DIR_PATH = OBMC_TOOLS_BASE_DIR_PATH + "thalerj/"
141    MSBARTH_TOOLS_DIR_PATH = OBMC_TOOLS_BASE_DIR_PATH + "msbarth/"
142
143    gv.valid_value(ROBOT_TEST_BASE_DIR_PATH)
144    gp.dprint_vars(ROBOT_TEST_RUNNING_FROM_SB, ROBOT_TEST_BASE_DIR_PATH, OBMC_TOOLS_BASE_DIR_PATH,
145                   OPENBMCTOOL_DIR_PATH, MSBARTH_TOOLS_DIR_PATH)
146    gv.valid_dir_path(ROBOT_TEST_BASE_DIR_PATH)
147
148    ROBOT_TEST_BASE_DIR_PATH = gm.add_trailing_slash(ROBOT_TEST_BASE_DIR_PATH)
149    gm.set_mod_global(ROBOT_TEST_BASE_DIR_PATH)
150    os.environ['ROBOT_TEST_BASE_DIR_PATH'] = ROBOT_TEST_BASE_DIR_PATH
151
152    gm.set_mod_global(ROBOT_TEST_RUNNING_FROM_SB)
153    os.environ['ROBOT_TEST_RUNNING_FROM_SB'] = str(ROBOT_TEST_RUNNING_FROM_SB)
154
155    gm.set_mod_global(OBMC_TOOLS_BASE_DIR_PATH)
156    os.environ['OBMC_TOOLS_BASE_DIR_PATH'] = str(OBMC_TOOLS_BASE_DIR_PATH)
157
158    gm.set_mod_global(OPENBMCTOOL_DIR_PATH)
159    os.environ['OPENBMCTOOL_DIR_PATH'] = str(OPENBMCTOOL_DIR_PATH)
160
161    gm.set_mod_global(MSBARTH_TOOLS_DIR_PATH)
162    os.environ['MSBARTH_TOOLS_DIR_PATH'] = str(MSBARTH_TOOLS_DIR_PATH)
163
164
165raw_robot_file_search_path = "${ROBOT_TEST_BASE_DIR_PATH}:" +\
166    "${ROBOT_TEST_BASE_DIR_PATH}tests:${ROBOT_TEST_BASE_DIR_PATH}extended:" +\
167    "${ROBOT_TEST_BASE_DIR_PATH}scratch:${PATH}"
168
169
170def init_robot_file_path(robot_file_path):
171    r"""
172    Determine full path name for the file path passed in robot_file_path and return it.
173
174    If robot_file_path contains a fully qualified path name, this function will verify that the file exists.
175    If robot_file_path contains a relative path, this function will search for the file and set
176    robot_file_path so that it contains the absolute path to the robot file.  This function will search for
177    the robot file using the raw_robot_file_search_path (defined above).  Note that if
178    ROBOT_TEST_BASE_DIR_PATH is not set, this function will call init_robot_test_base_dir_path to set it.
179
180    Description of arguments:
181    robot_file_path                 The absolute or relative path to a robot file.
182    """
183
184    gv.valid_value(robot_file_path)
185
186    try:
187        if ROBOT_TEST_BASE_DIR_PATH is NONE:
188            init_robot_test_base_dir_path()
189    except NameError:
190        init_robot_test_base_dir_path()
191
192    if not re.match(r".*\.(robot|py)$", robot_file_path):
193        # No suffix so we'll assign one of "\.robot".
194        robot_file_path = robot_file_path + ".robot"
195
196    abs_path = 0
197    if robot_file_path[0:1] == "/":
198        abs_path = 1
199
200    gp.dprint_vars(abs_path, robot_file_path)
201
202    if not abs_path:
203        cmd_buf = "echo -n \"" + raw_robot_file_search_path + "\""
204        shell_rc, out_buf = gc.shell_cmd(cmd_buf, quiet=(not debug),
205                                         print_output=0)
206        robot_file_search_paths = out_buf
207        gp.dprint_var(robot_file_search_paths)
208        robot_file_search_paths_list = robot_file_search_paths.split(':')
209        for search_path in robot_file_search_paths_list:
210            search_path = gm.add_trailing_slash(search_path)
211            candidate_file_path = search_path + robot_file_path
212            gp.dprint_var(candidate_file_path)
213            if os.path.isfile(candidate_file_path):
214                gp.dprint_timen("Found full path to " + robot_file_path + ".")
215                robot_file_path = candidate_file_path
216                break
217
218    gp.dprint_var(robot_file_path)
219    gv.valid_file_path(robot_file_path)
220
221    return robot_file_path
222
223
224def get_robot_parm_names():
225    r"""
226    Return a list containing all of the long parm names (e.g. --outputdir) supported by the robot program.
227    Double dashes are not included in the names returned.
228    """
229
230    cmd_buf = "robot -h | egrep " +\
231        "'^([ ]\\-[a-zA-Z0-9])?[ ]+--[a-zA-Z0-9]+[ ]+' | sed -re" +\
232        " s'/.*\\-\\-//g' -e s'/ .*//g' | sort -u"
233    shell_rc, out_buf = gc.shell_cmd(cmd_buf, quiet=1, print_output=0)
234
235    return out_buf.split("\n")
236
237
238def create_robot_cmd_string(robot_file_path, *parms):
239    r"""
240    Create a robot command string and return it.  On failure, return an empty string.
241
242    Description of arguments:
243    robot_file_path                 The path to the robot file to be run.
244    parms                           The list of parms to be included in the command string.  The name of each
245                                    variable in this list must be the same as the name of the corresponding
246                                    parm.  This function figures out that name.  This function is also able
247                                    to distinguish robot parms (e.g. --outputdir) from robot program parms
248                                    (all other parms which will be passed as "-v PARM_NAME:parm_value")..
249
250    Example:
251
252    The following call to this function...
253    cmd_buf = create_robot_cmd_string("tools/start_sol_console.robot", OPENBMC_HOST, quiet, test_mode, debug,
254    outputdir, output, log, report)
255
256    Would return a string something like this.
257    robot -v OPENBMC_HOST:beye6 -v quiet:0 -v test_mode:1 -v debug:1
258    --outputdir=/gsa/ausgsa/projects/a/status --output=beye6.OS_Console.output.xml
259    --log=beye6.OS_Console.log.html --report=beye6.OS_Console.report.html tools/start_sol_console.robot
260    """
261
262    robot_file_path = init_robot_file_path(robot_file_path)
263
264    robot_parm_names = get_robot_parm_names()
265
266    robot_parm_list = []
267
268    stack_frame = 2
269    ix = 2
270    for arg in parms:
271        parm = arg
272        parm = gm.quote_bash_parm(gm.escape_bash_quotes(str(parm)))
273        var_name = gp.get_arg_name(None, ix, stack_frame)
274        if var_name in robot_parm_names:
275            p_string = "--" + var_name + "=" + str(parm)
276            robot_parm_list.append(p_string)
277        else:
278            p_string = "-v " + var_name + ":" + str(parm)
279            robot_parm_list.append(p_string)
280        ix += 1
281
282    robot_cmd_buf = "robot " + ' '.join(robot_parm_list) + " " +\
283        robot_file_path
284
285    return robot_cmd_buf
286
287
288# Global variables to aid in cleanup after running robot_cmd_fnc.
289gcr_last_robot_cmd_buf = ""
290gcr_last_robot_rc = 0
291
292
293def process_robot_output_files(robot_cmd_buf=None,
294                               robot_rc=None,
295                               gzip=None):
296    r"""
297    Process robot output files which can involve several operations:
298    - If the files are in a temporary location, using SAVE_STATUS_POLICY to decide whether to move them to a
299      permanent location or to delete them.
300    - Gzipping them.
301
302    Description of argument(s):
303    robot_cmd_buf                   The complete command string used to invoke robot.
304    robot_rc                        The return code from running the robot command string.
305    gzip                            Indicates whether robot-generated output should be gzipped.
306    """
307
308    robot_cmd_buf = gm.dft(robot_cmd_buf, gcr_last_robot_cmd_buf)
309    robot_rc = gm.dft(robot_rc, gcr_last_robot_rc)
310    gzip = gm.dft(gzip, int(os.environ.get("GZIP_ROBOT", "1")))
311
312    if robot_cmd_buf == "":
313        # This can legitimately occur if this function is called from an exit_function without the program
314        # having ever run robot_cmd_fnc.
315        return
316
317    SAVE_STATUS_POLICY = os.environ.get("SAVE_STATUS_POLICY", "ALWAYS")
318    gp.qprint_vars(SAVE_STATUS_POLICY)
319
320    # When SAVE_STATUS_POLICY is "NEVER" robot output files don't even get generated.
321    if SAVE_STATUS_POLICY == "NEVER":
322        return
323
324    # Compose file_list based on robot command buffer passed in.
325    robot_cmd_buf_dict = gc.parse_command_string(robot_cmd_buf)
326    outputdir = robot_cmd_buf_dict['outputdir']
327    outputdir = gm.add_trailing_slash(outputdir)
328    file_list = outputdir + robot_cmd_buf_dict['output'] + " " + outputdir\
329        + robot_cmd_buf_dict['log'] + " " + outputdir\
330        + robot_cmd_buf_dict['report']
331
332    # Double checking that files are present.
333    shell_rc, out_buf = gc.shell_cmd("ls -1 " + file_list + " 2>/dev/null",
334                                     show_err=0)
335    file_list = re.sub("\n", " ", out_buf.rstrip("\n"))
336
337    if file_list == "":
338        gp.qprint_timen("No robot output files were found in " + outputdir
339                        + ".")
340        return
341    gp.qprint_var(robot_rc, gp.hexa())
342    if SAVE_STATUS_POLICY == "FAIL" and robot_rc == 0:
343        gp.qprint_timen("The call to robot produced no failures."
344                        + "  Deleting robot output files.")
345        gc.shell_cmd("rm -rf " + file_list)
346        return
347
348    if gzip:
349        gc.shell_cmd("gzip -f " + file_list)
350        # Update the values in file_list.
351        file_list = re.sub(" ", ".gz ", file_list) + ".gz"
352
353    # It TMP_ROBOT_DIR_PATH is set, it means the caller wanted the robot output initially directed to
354    # TMP_ROBOT_DIR_PATH but later moved to FFDC_DIR_PATH.  Otherwise, we're done.
355
356    if os.environ.get("TMP_ROBOT_DIR_PATH", "") is "":
357        return
358
359    # We're directing these to the FFDC dir path so that they'll be subjected to FFDC cleanup.
360    target_dir_path = os.environ.get("FFDC_DIR_PATH",
361                                     os.environ.get("HOME", ".")
362                                     + "/ffdc")
363    target_dir_path = gm.add_trailing_slash(target_dir_path)
364
365    targ_file_list = [re.sub(".*/", target_dir_path, x)
366                      for x in file_list.split(" ")]
367
368    gc.shell_cmd("mv " + file_list + " " + target_dir_path + " >/dev/null",
369                 time_out=600)
370
371    gp.qprint_timen("New robot log file locations:")
372    gp.qprintn('\n'.join(targ_file_list))
373
374
375def robot_cmd_fnc(robot_cmd_buf,
376                  robot_jail=os.environ.get('ROBOT_JAIL', ''), test_mode=0):
377    r"""
378    Run the robot command string.
379
380    This function will set the various PATH variables correctly so that you are running the proper version of
381    all imported files, etc.
382
383    Description of argument(s):
384    robot_cmd_buf                   The complete robot command string.
385    robot_jail                      Indicates that this is to run in "robot jail" meaning without visibility
386                                    to any apolloxxx import files, programs, etc.
387    test_mode                       If test_mode is set, this function will not actually run the command.
388    """
389
390    gv.valid_value(robot_cmd_buf)
391
392    # Set global variables to aid in cleanup with process_robot_output_files.
393    global gcr_last_robot_cmd_buf
394    global gcr_last_robot_rc
395    gcr_last_robot_cmd_buf = robot_cmd_buf
396
397    # Get globals set by init_robot_test_base_dir_path().
398    module = sys.modules["__main__"]
399    try:
400        ROBOT_TEST_BASE_DIR_PATH = getattr(module, "ROBOT_TEST_BASE_DIR_PATH")
401    except NameError:
402        init_robot_test_base_dir_path()
403        ROBOT_TEST_BASE_DIR_PATH = getattr(module, "ROBOT_TEST_BASE_DIR_PATH")
404
405    ROBOT_TEST_RUNNING_FROM_SB = gm.get_mod_global("ROBOT_TEST_RUNNING_FROM_SB")
406    OPENBMCTOOL_DIR_PATH = gm.get_mod_global("OPENBMCTOOL_DIR_PATH")
407
408    if robot_jail == "":
409        if ROBOT_TEST_RUNNING_FROM_SB:
410            robot_jail = 0
411        else:
412            robot_jail = 1
413
414    robot_jail = int(robot_jail)
415    ROBOT_JAIL = os.environ.get('ROBOT_JAIL', '')
416    gp.dprint_vars(ROBOT_TEST_BASE_DIR_PATH, ROBOT_TEST_RUNNING_FROM_SB,
417                   ROBOT_JAIL, robot_jail)
418
419    # Save PATH and PYTHONPATH to be restored later.
420    os.environ["SAVED_PYTHONPATH"] = os.environ.get("PYTHONPATH", "")
421    os.environ["SAVED_PATH"] = os.environ.get("PATH", "")
422
423    if robot_jail:
424        # Make sure required programs like python and robot can be found in the new restricted PATH.
425        required_programs = "python robot"
426        # It is expected that there will be a "python" program in the tool base bin path which is really a
427        # link to select_version.  Ditto for "robot".  Call each with the --print_only option to get the
428        # paths to the "real" programs.
429        cmd_buf = "for program in " + required_programs \
430            + " ; do dirname $(${program} --print_only) ; done 2>/dev/null"
431        rc, out_buf = gc.shell_cmd(cmd_buf, quiet=1, print_output=0)
432        PYTHONPATH = ROBOT_TEST_BASE_DIR_PATH + "lib"
433        NEW_PATH_LIST = [ROBOT_TEST_BASE_DIR_PATH + "bin"]
434        NEW_PATH_LIST.extend(list(set(out_buf.rstrip("\n").split("\n"))))
435        NEW_PATH_LIST.extend(["/usr/local/sbin", "/usr/local/bin", "/usr/sbin",
436                              "/usr/bin", "/sbin", "/bin",
437                              OPENBMCTOOL_DIR_PATH.rstrip('/')])
438        PATH = ":".join(NEW_PATH_LIST)
439    else:
440        PYTHONPATH = os.environ.get('PYTHONPATH', '') + ":" +\
441            ROBOT_TEST_BASE_DIR_PATH + "lib"
442        PATH = os.environ.get('PATH', '') + ":" + ROBOT_TEST_BASE_DIR_PATH +\
443            "bin" + ":" + OPENBMCTOOL_DIR_PATH.rstrip('/')
444
445    os.environ['PYTHONPATH'] = PYTHONPATH
446    os.environ['PATH'] = PATH
447    gp.dprint_vars(PATH, PYTHONPATH)
448
449    os.environ['FFDC_DIR_PATH_STYLE'] = os.environ.get('FFDC_DIR_PATH_STYLE',
450                                                       '1')
451    gp.qpissuing(robot_cmd_buf, test_mode)
452    if test_mode:
453        os.environ["PATH"] = os.environ.get("SAVED_PATH", "")
454        os.environ["PYTHONPATH"] = os.environ.get("SAVED_PYTHONPATH", "")
455        return True
456
457    if quiet:
458        DEVNULL = open(os.devnull, 'wb')
459        stdout = DEVNULL
460    else:
461        stdout = None
462    sub_proc = subprocess.Popen(robot_cmd_buf, stdout=stdout, shell=True)
463    sub_proc.communicate()
464    shell_rc = sub_proc.returncode
465    os.environ["PATH"] = os.environ.get("SAVED_PATH", "")
466    os.environ["PYTHONPATH"] = os.environ.get("SAVED_PYTHONPATH", "")
467    gcr_last_robot_rc = shell_rc
468    process_robot_output_files()
469    if shell_rc != 0:
470        gp.print_var(shell_rc, gp.hexa())
471        return False
472
473    return True
474