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