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