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 -f " + 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    r"""
384    Run the robot command string.
385
386    This function will set the various PATH variables correctly so that you
387    are running the proper version of 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
392                                    jail" meaning without visibility to any
393                                    apolloxxx import files, programs, etc.
394    """
395
396    if not gv.valid_value(robot_cmd_buf):
397        return False
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    # 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        PYTHONPATH = ROBOT_TEST_BASE_DIR_PATH + "lib"
432        NEW_PATH_LIST = [ROBOT_TEST_BASE_DIR_PATH + "bin"]
433        # Coding special case to preserve python27_path.
434        python27_path = "/opt/rh/python27/root/usr/bin"
435        PATH_LIST = os.environ.get("PATH", "").split(":")
436        if python27_path in PATH_LIST:
437            NEW_PATH_LIST.append(python27_path)
438        NEW_PATH_LIST.extend(["/usr/local/sbin", "/usr/local/bin", "/usr/sbin",
439                              "/usr/bin", "/sbin", "/bin"])
440        PATH = ":".join(NEW_PATH_LIST)
441    else:
442        PYTHONPATH = os.environ.get('PYTHONPATH', '') + ":" +\
443            ROBOT_TEST_BASE_DIR_PATH + "lib/"
444        PATH = os.environ.get('PATH', '') + ":" + ROBOT_TEST_BASE_DIR_PATH +\
445            "bin/"
446
447    os.environ['PYTHONPATH'] = PYTHONPATH
448    os.environ['PATH'] = PATH
449    gp.dprint_vars(PATH, PYTHONPATH)
450
451    os.environ['FFDC_DIR_PATH_STYLE'] = os.environ.get('FFDC_DIR_PATH_STYLE',
452                                                       '1')
453    test_mode = getattr(module, "test_mode")
454
455    gp.qpissuing(robot_cmd_buf, test_mode)
456    if test_mode:
457        os.environ["PATH"] = os.environ.get("SAVED_PATH", "")
458        os.environ["PYTHONPATH"] = os.environ.get("SAVED_PYTHONPATH", "")
459        return True
460
461    if quiet:
462        DEVNULL = open(os.devnull, 'wb')
463        stdout = DEVNULL
464    else:
465        stdout = None
466    sub_proc = subprocess.Popen(robot_cmd_buf, stdout=stdout, shell=True)
467    sub_proc.communicate()
468    shell_rc = sub_proc.returncode
469    os.environ["PATH"] = os.environ.get("SAVED_PATH", "")
470    os.environ["PYTHONPATH"] = os.environ.get("SAVED_PYTHONPATH", "")
471    gcr_last_robot_rc = shell_rc
472    process_robot_output_files()
473    if shell_rc != 0:
474        hex = 1
475        gp.print_var(shell_rc, hex)
476        return False
477
478    return True
479