xref: /openbmc/openbmc-test-automation/bin/process_plug_in_packages.py (revision aca557110becf3f4d45391b6f70d0f52f1046f4e)
1#!/usr/bin/env python
2
3import sys
4import __builtin__
5import subprocess
6import os
7import argparse
8
9# python puts the program's directory path in sys.path[0].  In other words,
10# the user ordinarily has no way to override python's choice of a module from
11# its own dir.  We want to have that ability in our environment.  However, we
12# don't want to break any established python modules that depend on this
13# behavior.  So, we'll save the value from sys.path[0], delete it, import our
14# modules and then restore sys.path to its original value.
15
16save_path_0 = sys.path[0]
17del sys.path[0]
18
19from gen_print import *
20from gen_valid import *
21from gen_arg import *
22from gen_plug_in import *
23
24# Restore sys.path[0].
25sys.path.insert(0, save_path_0)
26# I use this variable in calls to print_var.
27hex = 1
28
29###############################################################################
30# Create parser object to process command line parameters and args.
31
32# Create parser object.
33parser = argparse.ArgumentParser(
34    usage='%(prog)s [OPTIONS]',
35    description="%(prog)s will process the plug-in packages passed to it." +
36                "  A plug-in package is essentially a directory containing" +
37                " one or more call point programs.  Each of these call point" +
38                " programs must have a prefix of \"cp_\".  When calling" +
39                " %(prog)s, a user must provide a call_point parameter" +
40                " (described below).  For each plug-in package passed," +
41                " %(prog)s will check for the presence of the specified call" +
42                " point program in the plug-in directory.  If it is found," +
43                " %(prog)s will run it.  It is the responsibility of the" +
44                " caller to set any environment variables needed by the call" +
45                " point programs.\n\nAfter each call point program" +
46                " has been run, %(prog)s will print the following values in" +
47                " the following formats for use by the calling program:\n" +
48                "  failed_plug_in_name:               <failed plug-in value," +
49                " if any>\n  shell_rc:                          " +
50                "<shell return code value of last call point program - this" +
51                " will be printed in hexadecimal format.  Also, be aware" +
52                " that if a call point program returns a value it will be" +
53                " shifted left 2 bytes (e.g. rc of 2 will be printed as" +
54                " 0x00000200).  That is because the rightmost byte is" +
55                " reserverd for errors in calling the call point program" +
56                " rather than errors generated by the call point program.>",
57    formatter_class=argparse.RawTextHelpFormatter,
58    prefix_chars='-+')
59
60# Create arguments.
61parser.add_argument(
62    'plug_in_dir_paths',
63    nargs='?',
64    default="",
65    help=plug_in_dir_paths_help_text + default_string)
66
67parser.add_argument(
68    '--call_point',
69    default="setup",
70    required=True,
71    help='The call point program name.  This value must not include the' +
72         ' "cp_" prefix.  For each plug-in package passed to this program,' +
73         ' the specified call_point program will be called if it exists in' +
74         ' the plug-in directory.' + default_string)
75
76parser.add_argument(
77    '--shell_rc',
78    default="0x00000000",
79    help='The user may supply a value other than zero to indicate an' +
80         ' acceptable non-zero return code.  For example, if this value' +
81         ' equals 0x00000200, it means that for each plug-in call point that' +
82         ' runs, a 0x00000200 will not be counted as a failure.  See note' +
83         ' above regarding left-shifting of return codes.' + default_string)
84
85parser.add_argument(
86    '--stop_on_plug_in_failure',
87    default=1,
88    type=int,
89    choices=[1, 0],
90    help='If this parameter is set to 1, this program will stop and return ' +
91         'non-zero if the call point program from any plug-in directory ' +
92         'fails.  Conversely, if it is set to false, this program will run ' +
93         'the call point program from each and every plug-in directory ' +
94         'regardless of their return values.  Typical example cases where ' +
95         'you\'d want to run all plug-in call points regardless of success ' +
96         'or failure would be "cleanup" or "ffdc" call points.')
97
98parser.add_argument(
99    '--stop_on_non_zero_rc',
100    default=0,
101    type=int,
102    choices=[1, 0],
103    help='If this parm is set to 1 and a plug-in call point program returns ' +
104         'a valid non-zero return code (see "shell_rc" parm above), this' +
105         ' program will stop processing and return 0 (success).  Since this' +
106         ' constitutes a successful exit, this would normally be used where' +
107         ' the caller wishes to stop processing if one of the plug-in' +
108         ' directory call point programs returns a special value indicating' +
109         ' that some special case has been found.  An example might be in' +
110         ' calling some kind of "check_errl" call point program.  Such a' +
111         ' call point program might return a 2 (i.e. 0x00000200) to indicate' +
112         ' that a given error log entry was found in an "ignore" list and is' +
113         ' therefore to be ignored.  That being the case, no other' +
114         ' "check_errl" call point program would need to be called.' +
115         default_string)
116
117parser.add_argument(
118    '--mch_class',
119    default="obmc",
120    help=mch_class_help_text + default_string)
121
122# The stock_list will be passed to gen_get_options.  We populate it with the
123# names of stock parm options we want.  These stock parms are pre-defined by
124# gen_get_options.
125stock_list = [("test_mode", 0), ("quiet", 1), ("debug", 0)]
126###############################################################################
127
128
129###############################################################################
130def exit_function(signal_number=0,
131                  frame=None):
132
133    r"""
134    Execute whenever the program ends normally or with the signals that we
135    catch (i.e. TERM, INT).
136    """
137
138    dprint_executing()
139    dprint_var(signal_number)
140
141    qprint_pgm_footer()
142
143###############################################################################
144
145
146###############################################################################
147def signal_handler(signal_number, frame):
148
149    r"""
150    Handle signals.  Without a function to catch a SIGTERM or SIGINT, our
151    program would terminate immediately with return code 143 and without
152    calling our exit_function.
153    """
154
155    # Our convention is to set up exit_function with atexit.registr() so
156    # there is no need to explicitly call exit_function from here.
157
158    dprint_executing()
159
160    # Calling exit prevents us from returning to the code that was running
161    # when we received the signal.
162    exit(0)
163
164###############################################################################
165
166
167###############################################################################
168def validate_parms():
169
170    r"""
171    Validate program parameters, etc.  Return True or False accordingly.
172    """
173
174    if not valid_value(call_point):
175        return False
176
177    global shell_rc
178    if not valid_integer(shell_rc):
179        return False
180
181    # Convert to hex string for consistency in printout.
182    shell_rc = "0x%08x" % int(shell_rc, 0)
183    set_pgm_arg(shell_rc)
184
185    gen_post_validation(exit_function, signal_handler)
186
187    return True
188
189###############################################################################
190
191
192###############################################################################
193def run_pgm(plug_in_dir_path,
194            call_point,
195            caller_shell_rc):
196
197    r"""
198    Run the call point program in the given plug_in_dir_path.  Return the
199    following:
200    rc                              The return code - 0 = PASS, 1 = FAIL.
201    shell_rc                        The shell return code returned by
202                                    process_plug_in_packages.py.
203    failed_plug_in_name             The failed plug in name (if any).
204
205    Description of arguments:
206    plug_in_dir_path                The directory path where the call_point
207                                    program may be located.
208    call_point                      The call point (e.g. "setup").  This
209                                    program will look for a program named
210                                    "cp_" + call_point in the
211                                    plug_in_dir_path.  If no such call point
212                                    program is found, this function returns an
213                                    rc of 0 (i.e. success).
214    caller_shell_rc                 The user may supply a value other than
215                                    zero to indicate an acceptable non-zero
216                                    return code.  For example, if this value
217                                    equals 0x00000200, it means that for each
218                                    plug-in call point that runs, a 0x00000200
219                                    will not be counted as a failure.  See
220                                    note above regarding left-shifting of
221                                    return codes.
222    """
223
224    rc = 0
225    failed_plug_in_name = ""
226    shell_rc = 0x00000000
227
228    cp_prefix = "cp_"
229    plug_in_pgm_path = plug_in_dir_path + cp_prefix + call_point
230    if not os.path.exists(plug_in_pgm_path):
231        # No such call point in this plug in dir path.  This is legal so we
232        # return 0, etc.
233        return rc, shell_rc, failed_plug_in_name
234
235    # Get some stats on the file.
236    cmd_buf = "stat -c '%n %s %z' " + plug_in_pgm_path
237    dpissuing(cmd_buf)
238    sub_proc = subprocess.Popen(cmd_buf, shell=True, stdout=subprocess.PIPE,
239                                stderr=subprocess.STDOUT)
240    out_buf, err_buf = sub_proc.communicate()
241    shell_rc = sub_proc.returncode
242    if shell_rc != 0:
243        rc = 1
244        print_var(shell_rc, hex)
245        failed_plug_in_name = \
246            os.path.basename(os.path.normpath(plug_in_dir_path))
247        print(out_buf)
248        return rc, shell_rc, failed_plug_in_name
249
250    print("------------------------------------------------- Starting plug-" +
251          "in -----------------------------------------------")
252    print(out_buf)
253    cmd_buf = "PATH=" + plug_in_dir_path + ":${PATH} ; " + cp_prefix +\
254              call_point
255    pissuing(cmd_buf)
256
257    sub_proc = subprocess.Popen(cmd_buf, shell=True)
258    sub_proc.communicate()
259    shell_rc = sub_proc.returncode
260    # Shift to left.
261    shell_rc *= 0x100
262    if shell_rc != 0 and shell_rc != caller_shell_rc:
263        rc = 1
264        failed_plug_in_name = \
265            os.path.basename(os.path.normpath(plug_in_dir_path))
266
267    print("------------------------------------------------- Ending plug-in" +
268          " -------------------------------------------------")
269
270    return rc, shell_rc, failed_plug_in_name
271
272###############################################################################
273
274
275###############################################################################
276def main():
277
278    r"""
279    This is the "main" function.  The advantage of having this function vs
280    just doing this in the true mainline is that you can:
281    - Declare local variables
282    - Use "return" instead of "exit".
283    - Indent 4 chars like you would in any function.
284    This makes coding more consistent, i.e. it's easy to move code from here
285    into a function and vice versa.
286    """
287
288    if not gen_get_options(parser, stock_list):
289        return False
290
291    if not validate_parms():
292        return False
293
294    qprint_pgm_header()
295
296    # Access program parameter globals.
297    global plug_in_dir_paths
298    global mch_class
299    global shell_rc
300    global stop_on_plug_in_failure
301    global stop_on_non_zero_rc
302
303    plug_in_packages_list = return_plug_in_packages_list(plug_in_dir_paths,
304                                                         mch_class)
305
306    qpvar(plug_in_packages_list)
307    qprint("\n")
308
309    caller_shell_rc = int(shell_rc, 0)
310    shell_rc = 0
311    failed_plug_in_name = ""
312
313    ret_code = 0
314    for plug_in_dir_path in plug_in_packages_list:
315        rc, shell_rc, failed_plug_in_name = \
316            run_pgm(plug_in_dir_path, call_point, caller_shell_rc)
317        print_var(failed_plug_in_name)
318        print_var(shell_rc, hex)
319        if rc != 0:
320            ret_code = 1
321            if stop_on_plug_in_failure:
322                break
323        if shell_rc != 0 and stop_on_non_zero_rc:
324            qprint_time("Stopping on non-zero shell return code as requested" +
325                        " by caller.\n")
326            break
327
328    if ret_code == 0:
329        return True
330    else:
331        if not stop_on_plug_in_failure:
332            # We print a summary error message to make the failure more
333            # obvious.
334            print_error_report("At least one plug-in failed.\n")
335        return False
336
337###############################################################################
338
339
340###############################################################################
341# Main
342
343if not main():
344    exit(1)
345
346###############################################################################
347