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
37    gp.dprint_executing()
38    AUTOBOOT_OPENBMC_NICKNAME = gm.get_mod_global("AUTOBOOT_OPENBMC_NICKNAME")
39
40    # Set values for call to create_robot_cmd_string.
41    # Environment variable TMP_ROBOT_DIR_PATH can be set by the user to
42    # indicate that robot-generated output should initially be written to the
43    # specified temporary directory and then moved to the normal output
44    # location after completion.
45    outputdir =\
46        os.environ.get("TMP_ROBOT_DIR_PATH",
47                       os.environ.get("STATUS_DIR_PATH",
48                                      os.environ.get("HOME", ".")
49                                      + "/autoipl/status"))
50    outputdir = gm.add_trailing_slash(outputdir)
51    seconds = time.time()
52    loc_time = time.localtime(seconds)
53    time_string = time.strftime("%y%m%d.%H%M%S", loc_time)
54    file_prefix = AUTOBOOT_OPENBMC_NICKNAME + "." + extra_prefix +\
55        time_string + "."
56    # Environment variable SAVE_STATUS_POLICY governs when robot-generated
57    # output files (e.g. the log.html) will be moved from TMP_ROBOT_DIR_PATH
58    # to FFDC_DIR_PATH.  Valid values are "ALWAYS", "NEVER" and "FAIL".
59    SAVE_STATUS_POLICY = os.environ.get("SAVE_STATUS_POLICY", "ALWAYS")
60    if SAVE_STATUS_POLICY == "NEVER":
61        output = "NONE"
62        log = "NONE"
63        report = "NONE"
64    else:
65        output = file_prefix + "output.xml"
66        log = file_prefix + "log.html"
67        report = file_prefix + "report.html"
68    loglevel = "TRACE"
69
70    # Make create_robot_cmd_string values global.
71    gm.set_mod_global(outputdir)
72    gm.set_mod_global(output)
73    gm.set_mod_global(log)
74    gm.set_mod_global(report)
75    gm.set_mod_global(loglevel)
76
77
78def init_robot_test_base_dir_path():
79    r"""
80    Initialize and validate the environment variable, ROBOT_TEST_BASE_DIR_PATH
81    and set corresponding global variable ROBOT_TEST_RUNNING_FROM_SB.
82
83    If ROBOT_TEST_BASE_DIR_PATH is already set, this function will merely
84    validate it.  This function will also set environment variable
85    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
90    # or from a standard apolloxxx environment.
91    # - User sandbox:
92    # If there is a <developer's home dir>/git/openbmc-test-automation/,
93    # ROBOT_TEST_BASE_DIR_PATH will be set to that path.  Otherwise, we set it
94    # to <program dir path>/git/openbmc-test-automation/
95    # - Not in user sandbox:
96    # ROBOT_TEST_BASE_DIR_PATH will be set to <program dir
97    # path>/git/openbmc-test-automation/
98
99    ROBOT_TEST_BASE_DIR_PATH = os.environ.get('ROBOT_TEST_BASE_DIR_PATH', "")
100    ROBOT_TEST_RUNNING_FROM_SB = \
101        int(os.environ.get('ROBOT_TEST_RUNNING_FROM_SB', "0"))
102    if ROBOT_TEST_BASE_DIR_PATH == "":
103        # ROBOT_TEST_BASE_DIR_PATH was not set by user/caller.
104        AUTOIPL_VERSION = os.environ.get('AUTOIPL_VERSION', '')
105        if AUTOIPL_VERSION == "":
106            ROBOT_TEST_BASE_DIR_PATH = base_path
107        else:
108            suffix = "git/openbmc-test-automation/"
109
110            # Determine whether we're running out of a developer sandbox or
111            # simply out of an apolloxxx/bin path.
112            shell_rc, out_buf = gc.shell_cmd('dirname $(which gen_print.py)',
113                                             quiet=(not debug), print_output=0)
114            executable_base_dir_path = os.path.realpath(out_buf.rstrip()) + "/"
115            apollo_dir_path = os.environ['AUTO_BASE_PATH'] + AUTOIPL_VERSION +\
116                "/bin/"
117            developer_home_dir_path = re.sub('/sandbox.*', '',
118                                             executable_base_dir_path)
119            developer_home_dir_path = \
120                gm.add_trailing_slash(developer_home_dir_path)
121            gp.dprint_vars(executable_base_dir_path, developer_home_dir_path,
122                           apollo_dir_path)
123
124            ROBOT_TEST_RUNNING_FROM_SB = 0
125            if executable_base_dir_path != apollo_dir_path:
126                ROBOT_TEST_RUNNING_FROM_SB = 1
127                gp.dprint_vars(ROBOT_TEST_RUNNING_FROM_SB)
128                ROBOT_TEST_BASE_DIR_PATH = developer_home_dir_path + suffix
129                if not os.path.isdir(ROBOT_TEST_BASE_DIR_PATH):
130                    gp.dprint_timen("NOTE: Sandbox directory "
131                                    + ROBOT_TEST_BASE_DIR_PATH + " does not"
132                                    + " exist.")
133                    # Fall back to the apollo dir path.
134                    ROBOT_TEST_BASE_DIR_PATH = apollo_dir_path + suffix
135            else:
136                # Use to the apollo dir path.
137                ROBOT_TEST_BASE_DIR_PATH = apollo_dir_path + suffix
138
139    if not gv.valid_value(ROBOT_TEST_BASE_DIR_PATH):
140        return False
141    gp.dprint_vars(ROBOT_TEST_RUNNING_FROM_SB, ROBOT_TEST_BASE_DIR_PATH)
142    if not gv.valid_dir_path(ROBOT_TEST_BASE_DIR_PATH):
143        return False
144
145    ROBOT_TEST_BASE_DIR_PATH = gm.add_trailing_slash(ROBOT_TEST_BASE_DIR_PATH)
146    gm.set_mod_global(ROBOT_TEST_BASE_DIR_PATH)
147    os.environ['ROBOT_TEST_BASE_DIR_PATH'] = ROBOT_TEST_BASE_DIR_PATH
148
149    gm.set_mod_global(ROBOT_TEST_RUNNING_FROM_SB)
150    os.environ['ROBOT_TEST_RUNNING_FROM_SB'] = str(ROBOT_TEST_RUNNING_FROM_SB)
151
152
153raw_robot_file_search_path = "${ROBOT_TEST_BASE_DIR_PATH}:" +\
154    "${ROBOT_TEST_BASE_DIR_PATH}tests:${ROBOT_TEST_BASE_DIR_PATH}extended:" +\
155    "${ROBOT_TEST_BASE_DIR_PATH}scratch:${PATH}"
156
157
158def init_robot_file_path(robot_file_path):
159    r"""
160    Determine full path name for the file path passed in robot_file_path and
161    return it.
162
163    If robot_file_path contains a fully qualified path name, this function
164    will verify that the file exists.  If robot_file_path contains a relative
165    path, this function will search for the file and set robot_file_path so
166    that it contains the absolute path to the robot file.  This function will
167    search for the robot file using the raw_robot_file_search_path (defined
168    above).  Note that if ROBOT_TEST_BASE_DIR_PATH is not set, this function
169    will call init_robot_test_base_dir_path to set it.
170
171    Description of arguments:
172    robot_file_path                 The absolute or relative path to a robot
173                                    file.
174    """
175
176    if not gv.valid_value(robot_file_path):
177        raise ValueError('Programmer error.')
178
179    try:
180        if ROBOT_TEST_BASE_DIR_PATH is NONE:
181            init_robot_test_base_dir_path()
182    except NameError:
183        init_robot_test_base_dir_path()
184
185    if not re.match(r".*\.(robot|py)$", robot_file_path):
186        # No suffix so we'll assign one of "\.robot".
187        robot_file_path = robot_file_path + ".robot"
188
189    abs_path = 0
190    if robot_file_path[0:1] == "/":
191        abs_path = 1
192
193    gp.dprint_vars(abs_path, robot_file_path)
194
195    if not abs_path:
196        cmd_buf = "echo -n \"" + raw_robot_file_search_path + "\""
197        shell_rc, out_buf = gc.shell_cmd(cmd_buf, quiet=(not debug),
198                                         print_output=0)
199        robot_file_search_paths = out_buf
200        gp.dprint_var(robot_file_search_paths)
201        robot_file_search_paths_list = robot_file_search_paths.split(':')
202        for search_path in robot_file_search_paths_list:
203            search_path = gm.add_trailing_slash(search_path)
204            candidate_file_path = search_path + robot_file_path
205            gp.dprint_var(candidate_file_path)
206            if os.path.isfile(candidate_file_path):
207                gp.dprint_timen("Found full path to " + robot_file_path + ".")
208                robot_file_path = candidate_file_path
209                break
210
211    gp.dprint_var(robot_file_path)
212    if not gv.valid_file_path(robot_file_path):
213        raise ValueError('Programmer error.')
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/autoipl/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=1):
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
315    if robot_cmd_buf == "":
316        # This can legitimately occur if this function is called from an
317        # exit_function without the program having ever run robot_cmd_fnc.
318        return
319
320    SAVE_STATUS_POLICY = os.environ.get("SAVE_STATUS_POLICY", "ALWAYS")
321    gp.qprint_vars(SAVE_STATUS_POLICY)
322
323    # When SAVE_STATUS_POLICY is "NEVER" robot output files don't even get
324    # generated.
325    if SAVE_STATUS_POLICY == "NEVER":
326        return
327
328    # Compose file_list based on robot command buffer passed in.
329    robot_cmd_buf_dict = gc.parse_command_string(robot_cmd_buf)
330    outputdir = robot_cmd_buf_dict['outputdir']
331    outputdir = gm.add_trailing_slash(outputdir)
332    file_list = outputdir + robot_cmd_buf_dict['output'] + " " + outputdir\
333        + robot_cmd_buf_dict['log'] + " " + outputdir\
334        + robot_cmd_buf_dict['report']
335
336    # Double checking that files are present.
337    shell_rc, out_buf = gc.shell_cmd("ls -1 " + file_list + " 2>/dev/null",
338                                     show_err=0)
339    file_list = re.sub("\n", " ", out_buf.rstrip("\n"))
340
341    if file_list == "":
342        gp.qprint_timen("No robot output files were found in " + outputdir
343                        + ".")
344        return
345    gp.qprint_var(robot_rc, 1)
346    if SAVE_STATUS_POLICY == "FAIL" and robot_rc == 0:
347        gp.qprint_timen("The call to robot produced no failures."
348                        + "  Deleting robot output files.")
349        gc.shell_cmd("rm -rf " + file_list)
350        return
351
352    if gzip:
353        gc.shell_cmd("gzip " + file_list)
354        # Update the values in file_list.
355        file_list = re.sub(" ", ".gz ", file_list) + ".gz"
356
357    # It TMP_ROBOT_DIR_PATH is set, it means the caller wanted the robot
358    # output initially directed to TMP_ROBOT_DIR_PATH but later moved to
359    # FFDC_DIR_PATH.  Otherwise, we're done.
360
361    if os.environ.get("TMP_ROBOT_DIR_PATH", "") is "":
362        return
363
364    # We're directing these to the FFDC dir path so that they'll be subjected
365    # to FFDC cleanup.
366    target_dir_path = os.environ.get("FFDC_DIR_PATH",
367                                     os.environ.get("HOME", ".")
368                                     + "/autoipl/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', ''),
383                  gzip=1):
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    gqip                            This indicates that the log, report and
396                                    output files produced by robot should be
397                                    gzipped to save space.
398    """
399
400    if not gv.valid_value(robot_cmd_buf):
401        return False
402
403    # Set global variables to aid in cleanup with process_robot_output_files.
404    global gcr_last_robot_cmd_buf
405    global gcr_last_robot_rc
406    gcr_last_robot_cmd_buf = robot_cmd_buf
407
408    # Get globals set by init_robot_test_base_dir_path().
409    module = sys.modules["__main__"]
410    try:
411        ROBOT_TEST_BASE_DIR_PATH = getattr(module, "ROBOT_TEST_BASE_DIR_PATH")
412    except NameError:
413        init_robot_test_base_dir_path()
414        ROBOT_TEST_BASE_DIR_PATH = getattr(module, "ROBOT_TEST_BASE_DIR_PATH")
415
416    ROBOT_TEST_RUNNING_FROM_SB = \
417        gm.get_mod_global("ROBOT_TEST_RUNNING_FROM_SB")
418
419    if robot_jail == "":
420        if ROBOT_TEST_RUNNING_FROM_SB:
421            robot_jail = 0
422        else:
423            robot_jail = 1
424
425    robot_jail = int(robot_jail)
426    ROBOT_JAIL = os.environ.get('ROBOT_JAIL', '')
427    gp.dprint_vars(ROBOT_TEST_BASE_DIR_PATH, ROBOT_TEST_RUNNING_FROM_SB,
428                   ROBOT_JAIL, robot_jail)
429
430    # Save PATH and PYTHONPATH to be restored later.
431    os.environ["SAVED_PYTHONPATH"] = os.environ.get("PYTHONPATH", "")
432    os.environ["SAVED_PATH"] = os.environ.get("PATH", "")
433
434    if robot_jail:
435        PYTHONPATH = ROBOT_TEST_BASE_DIR_PATH + "lib"
436        NEW_PATH_LIST = [ROBOT_TEST_BASE_DIR_PATH + "bin"]
437        # Coding special case to preserve python27_path.
438        python27_path = "/opt/rh/python27/root/usr/bin"
439        PATH_LIST = os.environ.get("PATH", "").split(":")
440        if python27_path in PATH_LIST:
441            NEW_PATH_LIST.append(python27_path)
442        NEW_PATH_LIST.extend(["/usr/local/sbin", "/usr/local/bin", "/usr/sbin",
443                              "/usr/bin", "/sbin", "/bin"])
444        PATH = ":".join(NEW_PATH_LIST)
445    else:
446        PYTHONPATH = os.environ.get('PYTHONPATH', '') + ":" +\
447            ROBOT_TEST_BASE_DIR_PATH + "lib/"
448        PATH = os.environ.get('PATH', '') + ":" + ROBOT_TEST_BASE_DIR_PATH +\
449            "bin/"
450
451    os.environ['PYTHONPATH'] = PYTHONPATH
452    os.environ['PATH'] = PATH
453    gp.dprint_vars(PATH, PYTHONPATH)
454
455    os.environ['FFDC_DIR_PATH_STYLE'] = os.environ.get('FFDC_DIR_PATH_STYLE',
456                                                       '1')
457    test_mode = getattr(module, "test_mode")
458
459    gp.qpissuing(robot_cmd_buf, test_mode)
460    if test_mode:
461        os.environ["PATH"] = os.environ.get("SAVED_PATH", "")
462        os.environ["PYTHONPATH"] = os.environ.get("SAVED_PYTHONPATH", "")
463        return True
464
465    if quiet:
466        DEVNULL = open(os.devnull, 'wb')
467        stdout = DEVNULL
468    else:
469        stdout = None
470    sub_proc = subprocess.Popen(robot_cmd_buf, stdout=stdout, shell=True)
471    sub_proc.communicate()
472    shell_rc = sub_proc.returncode
473    os.environ["PATH"] = os.environ.get("SAVED_PATH", "")
474    os.environ["PYTHONPATH"] = os.environ.get("SAVED_PYTHONPATH", "")
475    gcr_last_robot_rc = shell_rc
476    process_robot_output_files()
477    if shell_rc != 0:
478        hex = 1
479        gp.print_var(shell_rc, hex)
480        return False
481
482    return True
483