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