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