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