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
61# Create arguments.
62parser.add_argument(
63    'plug_in_dir_paths',
64    nargs='?',
65    default="",
66    help=plug_in_dir_paths_help_text + default_string
67    )
68
69parser.add_argument(
70    '--call_point',
71    default="setup",
72    required=True,
73    help='The call point program name.  This value must not include the' +
74         ' "cp_" prefix.  For each plug-in package passed to this program,' +
75         ' the specified call_point program will be called if it exists in' +
76         ' the plug-in directory.' + default_string
77    )
78
79parser.add_argument(
80    '--shell_rc',
81    default="0x00000000",
82    help='The user may supply a value other than zero to indicate an' +
83         ' acceptable non-zero return code.  For example, if this value' +
84         ' equals 0x00000200, it means that for each plug-in call point that' +
85         ' runs, a 0x00000200 will not be counted as a failure.  See note' +
86         ' above regarding left-shifting of return codes.' + default_string
87    )
88
89parser.add_argument(
90    '--stop_on_plug_in_failure',
91    default=1,
92    type=int,
93    choices=[1, 0],
94    help='If this parameter is set to 1, this program will stop and return ' +
95         'non-zero if the call point program from any plug-in directory ' +
96         'fails.  Conversely, if it is set to false, this program will run ' +
97         'the call point program from each and every plug-in directory ' +
98         'regardless of their return values.  Typical example cases where ' +
99         'you\'d want to run all plug-in call points regardless of success ' +
100         'or failure would be "cleanup" or "ffdc" call points.'
101    )
102
103parser.add_argument(
104    '--stop_on_non_zero_rc',
105    default=0,
106    type=int,
107    choices=[1, 0],
108    help='If this parm is set to 1 and a plug-in call point program returns ' +
109         'a valid non-zero return code (see "shell_rc" parm above), this' +
110         ' program will stop processing and return 0 (success).  Since this' +
111         ' constitutes a successful exit, this would normally be used where' +
112         ' the caller wishes to stop processing if one of the plug-in' +
113         ' directory call point programs returns a special value indicating' +
114         ' that some special case has been found.  An example might be in' +
115         ' calling some kind of "check_errl" call point program.  Such a' +
116         ' call point program might return a 2 (i.e. 0x00000200) to indicate' +
117         ' that a given error log entry was found in an "ignore" list and is' +
118         ' therefore to be ignored.  That being the case, no other' +
119         ' "check_errl" call point program would need to be called.' +
120         default_string
121    )
122
123parser.add_argument(
124    '--mch_class',
125    default="obmc",
126    help=mch_class_help_text + default_string
127    )
128
129# The stock_list will be passed to gen_get_options.  We populate it with the
130# names of stock parm options we want.  These stock parms are pre-defined by
131# gen_get_options.
132stock_list = [("test_mode", 0), ("quiet", 1), ("debug", 0)]
133###############################################################################
134
135
136###############################################################################
137def exit_function(signal_number=0,
138                  frame=None):
139
140    r"""
141    Execute whenever the program ends normally or with the signals that we
142    catch (i.e. TERM, INT).
143    """
144
145    dprint_executing()
146    dprint_var(signal_number)
147
148    qprint_pgm_footer()
149
150###############################################################################
151
152
153###############################################################################
154def signal_handler(signal_number, frame):
155
156    r"""
157    Handle signals.  Without a function to catch a SIGTERM or SIGINT, our
158    program would terminate immediately with return code 143 and without
159    calling our exit_function.
160    """
161
162    # Our convention is to set up exit_function with atexit.registr() so
163    # there is no need to explicitly call exit_function from here.
164
165    dprint_executing()
166
167    # Calling exit prevents us from returning to the code that was running
168    # when we received the signal.
169    exit(0)
170
171###############################################################################
172
173
174###############################################################################
175def validate_parms():
176
177    r"""
178    Validate program parameters, etc.  Return True or False accordingly.
179    """
180
181    if not valid_value(call_point):
182        return False
183
184    gen_post_validation(exit_function, signal_handler)
185
186    return True
187
188###############################################################################
189
190
191###############################################################################
192def run_pgm(plug_in_dir_path,
193            call_point,
194            caller_shell_rc):
195
196    r"""
197    Run the call point program in the given plug_in_dir_path.  Return the
198    following:
199    rc                              The return code - 0 = PASS, 1 = FAIL.
200    shell_rc                        The shell return code returned by
201                                    process_plug_in_packages.py.
202    failed_plug_in_name             The failed plug in name (if any).
203
204    Description of arguments:
205    plug_in_dir_path                The directory path where the call_point
206                                    program may be located.
207    call_point                      The call point (e.g. "setup").  This
208                                    program will look for a program named
209                                    "cp_" + call_point in the
210                                    plug_in_dir_path.  If no such call point
211                                    program is found, this function returns an
212                                    rc of 0 (i.e. success).
213    caller_shell_rc                 The user may supply a value other than
214                                    zero to indicate an acceptable non-zero
215                                    return code.  For example, if this value
216                                    equals 0x00000200, it means that for each
217                                    plug-in call point that runs, a 0x00000200
218                                    will not be counted as a failure.  See
219                                    note above regarding left-shifting of
220                                    return codes.
221    """
222
223    rc = 0
224    failed_plug_in_name = ""
225    shell_rc = 0x00000000
226
227    cp_prefix = "cp_"
228    plug_in_pgm_path = plug_in_dir_path + cp_prefix + call_point
229    if not os.path.exists(plug_in_pgm_path):
230        # No such call point in this plug in dir path.  This is legal so we
231        # return 0, etc.
232        return rc, shell_rc, failed_plug_in_name
233
234    # Get some stats on the file.
235    cmd_buf = "stat -c '%n %s %z' " + plug_in_pgm_path
236    dpissuing(cmd_buf)
237    sub_proc = subprocess.Popen(cmd_buf, shell=True, stdout=subprocess.PIPE,
238                                stderr=subprocess.STDOUT)
239    out_buf, err_buf = sub_proc.communicate()
240    shell_rc = sub_proc.returncode
241    if shell_rc != 0:
242        rc = 1
243        print_var(shell_rc, hex)
244        failed_plug_in_name = \
245            os.path.basename(os.path.normpath(plug_in_dir_path))
246        print(out_buf)
247        return rc, shell_rc, failed_plug_in_name
248
249    print("------------------------------------------------- Starting plug-" +
250          "in -----------------------------------------------")
251    print(out_buf)
252    cmd_buf = "PATH=" + plug_in_dir_path + ":${PATH} ; " + cp_prefix +\
253              call_point
254    pissuing(cmd_buf)
255
256    sub_proc = subprocess.Popen(cmd_buf, shell=True)
257    sub_proc.communicate()
258    shell_rc = sub_proc.returncode
259    if shell_rc != 0 and shell_rc != int(caller_shell_rc, 16):
260        rc = 1
261        failed_plug_in_name = \
262            os.path.basename(os.path.normpath(plug_in_dir_path))
263
264    print("------------------------------------------------- Ending plug-in" +
265          " -------------------------------------------------")
266
267    return rc, shell_rc, failed_plug_in_name
268
269###############################################################################
270
271
272###############################################################################
273def main():
274
275    r"""
276    This is the "main" function.  The advantage of having this function vs
277    just doing this in the true mainline is that you can:
278    - Declare local variables
279    - Use "return" instead of "exit".
280    - Indent 4 chars like you would in any function.
281    This makes coding more consistent, i.e. it's easy to move code from here
282    into a function and vice versa.
283    """
284
285    if not gen_get_options(parser, stock_list):
286        return False
287
288    if not validate_parms():
289        return False
290
291    qprint_pgm_header()
292
293    # Access program parameter globals.
294    global plug_in_dir_paths
295    global mch_class
296    global shell_rc
297    global stop_on_plug_in_failure
298    global stop_on_non_zero_rc
299
300    plug_in_packages_list = return_plug_in_packages_list(plug_in_dir_paths,
301                                                         mch_class)
302
303    qpvar(plug_in_packages_list)
304    qprint("\n")
305
306    caller_shell_rc = shell_rc
307    shell_rc = 0
308    failed_plug_in_name = ""
309
310    ret_code = 0
311    for plug_in_dir_path in plug_in_packages_list:
312        rc, shell_rc, failed_plug_in_name = \
313            run_pgm(plug_in_dir_path, call_point, caller_shell_rc)
314        print_var(failed_plug_in_name)
315        print_var(shell_rc, hex)
316        if rc != 0:
317            ret_code = 1
318            if stop_on_plug_in_failure:
319                break
320        if shell_rc != 0 and stop_on_non_zero_rc:
321            qprint_time("Stopping on non-zero shell return code as requested" +
322                        " by caller.\n")
323            break
324
325    if ret_code == 0:
326        return True
327    else:
328        if not stop_on_plug_in_failure:
329            # We print a summary error message to make the failure more
330            # obvious.
331            print_error_report("At least one plug-in failed.\n")
332        return False
333
334###############################################################################
335
336
337###############################################################################
338# Main
339
340if not main():
341    exit(1)
342
343###############################################################################
344