1#!/usr/bin/env python
2
3r"""
4This module provides functions which are useful for running plug-ins from a
5robot program.
6"""
7
8import sys
9import subprocess
10from robot.libraries.BuiltIn import BuiltIn
11import commands
12import os
13import tempfile
14
15import gen_print as gp
16import gen_misc as gm
17import gen_cmd as gc
18
19
20def rvalidate_plug_ins(plug_in_dir_paths,
21                       quiet=1):
22    r"""
23    Call the external validate_plug_ins.py program which validates the plug-in
24    dir paths given to it.  Return a list containing a normalized path for
25    each plug-in selected.
26
27    Description of arguments:
28    plug_in_dir_paths               A colon-separated list of plug-in
29                                    directory paths.
30    quiet                           If quiet is set to 1, this function will
31                                    NOT write status messages to stdout.
32    """
33
34    cmd_buf = "validate_plug_ins.py \"" + plug_in_dir_paths + "\""
35    if int(quiet) != 1:
36        gp.print_issuing(cmd_buf)
37    rc, out_buf = commands.getstatusoutput(cmd_buf)
38    if rc != 0:
39        message = gp.sprint_varx("rc", rc, 1) + out_buf
40        gp.printn(out_buf, 'STDERR')
41        BuiltIn().fail(gp.sprint_error("Validate plug ins call failed.  See"
42                                       + " stderr text for details.\n"))
43
44    plug_in_packages_list = out_buf.split("\n")
45    if len(plug_in_packages_list) == 1 and plug_in_packages_list[0] == "":
46        return []
47
48    return plug_in_packages_list
49
50
51def rprocess_plug_in_packages(plug_in_packages_list=None,
52                              call_point="setup",
53                              shell_rc="0x00000000",
54                              stop_on_plug_in_failure=1,
55                              stop_on_non_zero_rc=0,
56                              release_type="obmc",
57                              quiet=None,
58                              debug=None,
59                              return_history=False):
60    r"""
61    Call the external process_plug_in_packages.py to process the plug-in
62    packages.  Return the following:
63    rc                              The return code - 0 = PASS, 1 = FAIL.
64    shell_rc                        The shell return code returned by
65                                    process_plug_in_packages.py.
66    failed_plug_in_name             The failed plug in name (if any).
67
68    Description of arguments:
69    plug_in_packages_list           A python list of plug-in directory paths.
70    call_point                      The call point program to be called for
71                                    each plug-in package (e.g. post_boot).
72                                    This name should not include the "cp_"
73                                    prefix.
74    shell_rc                        The user may supply a value other than
75                                    zero to indicate an acceptable non-zero
76                                    return code.  For example, if this value
77                                    equals 0x00000200, it means that for each
78                                    plug-in call point that runs, a 0x00000200
79                                    will not be counted as a failure.
80    stop_on_plug_in_failure         If this parameter is set to 1, this
81                                    program will stop and return non-zero if
82                                    the call point program from any plug-in
83                                    directory fails.  Conversely, if it is set
84                                    to false, this program will run the call
85                                    point program from each and every plug-in
86                                    directory regardless of their return
87                                    values.  Typical example cases where you'd
88                                    want to run all plug-in call points
89                                    regardless of success or failure would be
90                                    "cleanup" or "ffdc" call points.
91    stop_on_non_zero_rc             If this parm is set to 1 and a plug-in
92                                    call point program returns a valid
93                                    non-zero return code (see "shell_rc" parm
94                                    above), this program will stop processing
95                                    and return 0 (success).  Since this
96                                    constitutes a successful exit, this would
97                                    normally be used where the caller wishes
98                                    to stop processing if one of the plug-in
99                                    directory call point programs returns a
100                                    special value indicating that some special
101                                    case has been found.  An example might be
102                                    in calling some kind of "check_errl" call
103                                    point program.  Such a call point program
104                                    might return a 2 (i.e. 0x00000200) to
105                                    indicate that a given error log entry was
106                                    found in an "ignore" list and is therefore
107                                    to be ignored.  That being the case, no
108                                    other "check_errl" call point program
109                                    would need to be called.
110    release_type                    The type of release being tested (e.g.
111                                    "obmc", "op", "fips").  This influences
112                                    which integrated plug-ins are selected.
113    quiet                           If quiet is set to 1, this function will
114                                    NOT write status messages to stdout.  This
115                                    will default to the global quiet program
116                                    parm or to 0.
117    debug                           If this parameter is set to 1, this
118                                    function will print additional debug
119                                    information.  This is mainly to be used by
120                                    the developer of this function.  This will
121                                    default to the global quiet program parm
122                                    or to 0.
123    return_history                  In addition to rc, shell_rc and
124                                    failed_plug_in_name, return a list
125                                    containing historical output that looks
126                                    like the following:
127
128    history:
129      history[0]:                   #(CDT) 2018/10/30 12:25:49 - Running
130      OBMC_Sample/cp_post_stack
131    """
132
133    rc = 0
134
135    plug_in_packages_list = gp.get_var_value(plug_in_packages_list, [])
136
137    # If there are no plug-in packages to process, return successfully.
138    if len(plug_in_packages_list) == 0:
139        if return_history:
140            return 0, 0, "", []
141        else:
142            return 0, 0, ""
143
144    quiet = int(gp.get_var_value(quiet, 0))
145    debug = int(gp.get_var_value(debug, 0))
146
147    # Create string from list.
148    plug_in_dir_paths = ':'.join(plug_in_packages_list)
149
150    temp = tempfile.NamedTemporaryFile()
151    temp_file_path = temp.name
152    temp2 = tempfile.NamedTemporaryFile()
153    temp_properties_file_path = temp2.name
154
155    if debug:
156        os.environ["PERF_TRACE"] = "1"
157        debug_string = " --quiet=0"
158    else:
159        debug_string = ""
160
161    loc_shell_rc = 0
162
163    sub_cmd_buf = "process_plug_in_packages.py" + debug_string +\
164                  " --call_point=" + call_point + " --allow_shell_rc=" +\
165                  str(shell_rc) + " --stop_on_plug_in_failure=" +\
166                  str(stop_on_plug_in_failure) + " --stop_on_non_zero_rc=" +\
167                  str(stop_on_non_zero_rc) + " " + plug_in_dir_paths
168    if quiet:
169        cmd_buf = sub_cmd_buf + " > " + temp_file_path + " 2>&1"
170    else:
171        cmd_buf = "set -o pipefail ; " + sub_cmd_buf + " 2>&1 | tee " +\
172                  temp_file_path
173        if debug:
174            gp.print_issuing(cmd_buf)
175        else:
176            gp.print_timen("Processing " + call_point
177                           + " call point programs.")
178
179    proc_plug_pkg_rc = subprocess.call(cmd_buf, shell=True,
180                                       executable='/bin/bash')
181
182    if return_history:
183        # Get the "Running" statements from the output.
184        regex = " Running [^/]+/cp_"
185        cmd_buf = "egrep '" + regex + "' " + temp_file_path
186        _, history = gc.shell_cmd(cmd_buf, quiet=(not debug), print_output=0,
187                                  show_err=0, ignore_err=1)
188        history = [x + "\n" for x in filter(None, history.split("\n"))]
189    else:
190        history = []
191
192    # As process_plug_in_packages.py help text states, it will print the
193    # values of failed_plug_in_name and shell_rc in the following format:
194    # failed_plug_in_name:               <failed plug-in value, if any>
195    # shell_rc:                          <shell return code value of last
196    # call point program>
197
198    # We want to obtain those values from the output.  To make the task
199    # simpler, we'll start by grepping the output for lines that might fit
200    # such a format:
201    # A valid bash variable against the left margin followed by...
202    # - A colon followed by...
203    # - Zero or more spaces
204    bash_var_regex = "[_[:alpha:]][_[:alnum:]]*"
205    regex = "^" + bash_var_regex + ":[ ]*"
206    cmd_buf = "egrep '" + regex + "' " + temp_file_path + " > " +\
207              temp_properties_file_path
208    gp.dprint_issuing(cmd_buf)
209    grep_rc = os.system(cmd_buf)
210
211    # Next we call my_parm_file to create a properties dictionary.
212    properties = gm.my_parm_file(temp_properties_file_path)
213
214    # Finally, we access the 2 values that we need.
215    shell_rc = int(properties.get('shell_rc', '0x0000000000000000'), 16)
216    failed_plug_in_name = properties.get('failed_plug_in_name', '')
217
218    if proc_plug_pkg_rc != 0:
219        hex = 1
220        if grep_rc != 0:
221            gp.print_varx("grep_rc", grep_rc, hex)
222        gp.print_varx("proc_plug_pkg_rc", proc_plug_pkg_rc, hex)
223        gp.print_timen("Re-cap of plug-in failures:")
224        gc.cmd_fnc_u("egrep -A 1 '^failed_plug_in_name:[ ]+' "
225                     + temp_properties_file_path + " | egrep -v '^\\--'",
226                     quiet=1, show_err=0)
227        rc = 1
228
229    if return_history:
230        return rc, shell_rc, failed_plug_in_name, history
231    else:
232        return rc, shell_rc, failed_plug_in_name
233