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