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