1#!/usr/bin/env python
2
3r"""
4This module provides functions which are useful to plug-in call point programs.
5"""
6
7import sys
8import os
9import re
10import collections
11
12import gen_print as gp
13import gen_misc as gm
14import gen_cmd as gc
15
16PLUG_VAR_PREFIX = os.environ.get("PLUG_VAR_PREFIX", "AUTOBOOT")
17
18
19def get_plug_in_package_name(case=None):
20    r"""
21    Return the plug-in package name (e.g. "OS_Console", "DB_Logging").
22
23    Description of argument(s):
24    case                            Indicates whether the value returned
25                                    should be converted to upper or lower
26                                    case.  Valid values are "upper", "lower"
27                                    or None.
28    """
29
30    plug_in_package_name = os.path.basename(gp.pgm_dir_path[:-1])
31    if case == "upper":
32        return plug_in_package_name.upper()
33    elif case == "lower":
34        return plug_in_package_name.lower()
35    else:
36        return plug_in_package_name
37
38
39def return_plug_vars(general=True,
40                     custom=True):
41    r"""
42    Return an OrderedDict which is sorted by key and which contains all of the
43    plug-in environment variables.
44
45    Example excerpt of resulting dictionary:
46
47    plug_var_dict:
48      [AUTOBOOT_BASE_TOOL_DIR_PATH]:  /tmp/
49      [AUTOBOOT_BB_LEVEL]:            <blank>
50      [AUTOBOOT_BOOT_FAIL]:           0
51      ...
52
53    This function also does the following:
54    - Set a default value for environment variable
55      AUTOBOOT_OPENBMC_NICKNAME/AUTOIPL_FSP1_NICKNAME if it is not already set.
56    - Register PASSWORD variables to prevent their values from being printed.
57
58    Note: The programmer may set a default for any given environment variable
59    by declaring a global variable of the same name and setting its value.
60    For example, let's say the calling program has this global declaration:
61
62    PERF_EXERCISERS_TOTAL_TIMEOUT = '180'
63
64    If environment variable PERF_EXERCISERS_TOTAL_TIMEOUT is blank or not set,
65    this function will set it to 180.
66
67    Description of argument(s):
68    general                         Return general plug-in parms (e.g. those
69                                    beginning with "AUTOBOOT" or "AUTOGUI").
70    custom                          Return custom plug-in parms (i.e. those
71                                    beginning with the upper case name of the
72                                    plug-in package, for example
73                                    "OBMC_SAMPLE_PARM1").
74    """
75
76    regex_list = []
77    if not (general or custom):
78        return collections.OrderedDict()
79    plug_in_package_name = get_plug_in_package_name(case="upper")
80    if general:
81        regex_list = [PLUG_VAR_PREFIX, "AUTOGUI"]
82    if custom:
83        regex_list.append(plug_in_package_name)
84
85    regex = "^(" + "|".join(regex_list) + ")_"
86
87    # Set a default for nickname.
88    if os.environ.get("AUTOBOOT_OPENBMC_NICKNAME", "") == "":
89        os.environ['AUTOBOOT_OPENBMC_NICKNAME'] = \
90            os.environ.get("AUTOBOOT_OPENBMC_HOST", "")
91
92    if os.environ.get("AUTOIPL_FSP1_NICKNAME", "") == "":
93        os.environ['AUTOIPL_FSP1_NICKNAME'] = \
94            os.environ.get("AUTOIPL_FSP1_NAME", "").split(".")[0]
95
96    # For all variables specified in the parm_def file, we want them to
97    # default to "" rather than being unset.
98    # Process the parm_def file if it exists.
99    parm_def_file_path = gp.pgm_dir_path + "parm_def"
100    if os.path.exists(parm_def_file_path):
101        parm_defs = gm.my_parm_file(parm_def_file_path)
102    else:
103        parm_defs = collections.OrderedDict()
104    # Example parm_defs:
105    # parm_defs:
106    #   parm_defs[rest_fail]:           boolean
107    #   parm_defs[command]:             string
108    #   parm_defs[esel_stop_file_path]: string
109
110    # Create a list of plug-in environment variables by pre-pending <all caps
111    # plug-in package name>_<all caps var name>
112    plug_in_parm_names = [plug_in_package_name + "_" + x for x in
113                          map(str.upper, parm_defs.keys())]
114    # Example plug_in_parm_names:
115    # plug_in_parm_names:
116    #  plug_in_parm_names[0]: STOP_REST_FAIL
117    #  plug_in_parm_names[1]: STOP_COMMAND
118    #  plug_in_parm_names[2]: STOP_ESEL_STOP_FILE_PATH
119
120    # Initialize unset plug-in vars.
121    for var_name in plug_in_parm_names:
122        # If there is a global variable with the same name as the environment
123        # variable, use its value as a default.
124        default_value = gm.get_mod_global(var_name, "")
125        os.environ[var_name] = os.environ.get(var_name, default_value)
126        if os.environ[var_name] == "":
127            os.environ[var_name] = default_value
128
129    plug_var_dict = \
130        collections.OrderedDict(sorted({k: v for (k, v) in
131                                        os.environ.items()
132                                        if re.match(regex, k)}.items()))
133    # Register password values to prevent printing them out.  Any plug var
134    # whose name ends in PASSWORD will be registered.
135    password_vals = {k: v for (k, v) in plug_var_dict.items()
136                     if re.match(r".*_PASSWORD$", k)}.values()
137    map(gp.register_passwords, password_vals)
138
139    return plug_var_dict
140
141
142def sprint_plug_vars(headers=1, **kwargs):
143    r"""
144    Sprint the plug-in environment variables (i.e. those that begin with the
145    global PLUG_VAR_PREFIX value or those that begin with <plug-in
146    package_name>_ in upper case letters.).
147
148    Example excerpt of output:
149    AUTOBOOT_BASE_TOOL_DIR_PATH=/tmp/
150    AUTOBOOT_BB_LEVEL=
151    AUTOBOOT_BOOT_FAIL=0
152    AUTOBOOT_BOOT_FAIL_THRESHOLD=1000000
153
154    Description of argument(s):
155    headers                         Print a header and a footer.
156    kwargs                          These are passed directly to
157                                    return_plug_vars.  See return_plug_vars
158                                    doc string for details.
159    """
160    plug_var_dict = return_plug_vars(**kwargs)
161    buffer = ""
162    if headers:
163        buffer += "\n" + gp.sprint_dashes()
164    for key, value in plug_var_dict.items():
165        buffer += gp.sprint_varx(key, value)
166    if headers:
167        buffer += gp.sprint_dashes() + "\n"
168
169    return buffer
170
171
172def print_plug_in_header():
173    r"""
174    Print plug-in header.
175
176    When debug is set, print all plug_prefix variables (e.g.
177    AUTOBOOT_OPENBMC_HOST, etc.) and all plug-in environment variables (e.g.
178    OBMC_SAMPLE_PARM1) with surrounding dashed lines.  When debug is not set,
179    print only the plug-in environment variables (e.g. OBMC_SAMPLE_PARM1) with
180    no surrounding dashed lines.
181
182    NOTE: plug-in environment variables means any variable defined in the
183    <plug-in dir>/parm_def file plus any environment variables whose names
184    begin with the upper-case plug-in package name.
185    """
186
187    dprint_plug_vars()
188    if not debug:
189        qprint_plug_vars(headers=0, general=False, custom=True)
190
191
192def get_plug_vars(mod_name="__main__"):
193    r"""
194    Get all plug-in variables and put them in corresponding global variables.
195
196    This would include all environment variables beginning with either the
197    global PLUG_VAR_PREFIX value or with the upper case version of the plug-in
198    package name + underscore (e.g. OP_SAMPLE_VAR1 for plug-in OP_Sample).
199
200    The global variables to be set will be both with and without the global
201    PLUG_VAR_PREFIX value prefix.  For example, if the environment variable in
202    question is AUTOBOOT_OPENBMC_HOST, this function will set global variable
203    AUTOBOOT_OPENBMC_HOST and global variable OPENBMC_HOST.
204
205    Description of argument(s):
206    mod_name                        The name of the module whose global
207                                    plug-in variables should be retrieved.
208    """
209
210    module = sys.modules[mod_name]
211    plug_var_dict = return_plug_vars()
212
213    # Get all PLUG_VAR_PREFIX environment variables and put them into globals.
214    for key, value in plug_var_dict.items():
215        setattr(module, key, value)
216        setattr(module, re.sub("^" + PLUG_VAR_PREFIX + "_", "", key), value)
217
218
219def get_plug_default(var_name,
220                     default=None):
221    r"""
222    Derive and return a default value for the given parm variable.
223
224    Dependencies:
225    Global variable PLUG_VAR_PREFIX must be set.
226
227    This function will assign a default by checking the following environment
228    variables in the order shown.  The first one that has a value will be used.
229    - <upper case package_name>_<var_name>
230    - <PLUG_VAR_PREFIX>_OVERRIDE_<var_name>
231    - <PLUG_VAR_PREFIX>_<var_name>
232
233    If none of these are found, this function will return the value passed by
234    the caller in the "default" parm.
235
236    Example:
237
238    Let's say your plug-in is named "OS_Console" and you call this function as
239    follows:
240
241    get_plug_default("quiet", 0)
242
243    The first of these environment variables that is found to be set will be
244    used to provide the default value.
245    - OS_CONSOLE_QUIET
246    - AUTOBOOT_OVERRIDE_QUIET
247    - AUTOBOOT_QUIET
248
249    If none of those has a value, 0 (as specified by the caller in this
250    example) is returned.
251
252    Let's say the master driver program is named obmc_boot.  obmc_boot program
253    is responsible for calling plug-ins.  Let's further suppose that the user
254    wishes to run the master program with --debug=0 but wishes to have all
255    plug-ins run with --debug=1.  This could be accomplished with the
256    following call:
257    export AUTOBOOT_OVERRIDE_DEBUG=1 ; obmc_boot --debug=0
258    --plug_in_dir_paths=<list of plug ins>
259
260    As another example, let's suppose that the user wishes to have just the
261    OS_Console plug-in run with debug and everything else to default to
262    debug=0.  This could be accomplished as follows:
263    export OS_CONSOLE_DEBUG=1 ; obmc_boot --debug=0 --plug_in_dir_paths=<list
264    of plug ins>
265
266    And as one more example, let's say the user wishes to have obmc_boot and
267    OS_Console run without debug but have all other plug-ins run with debug:
268    export AUTOBOOT_OVERRIDE_DEBUG=1 ; export OS_CONSOLE_DEBUG=0 ; obmc_boot
269    --debug=0 --plug_in_dir_paths=<list of plug ins>
270
271    Description of argument(s):
272    var_name                        The name of the variable for which a
273                                    default value is to be calculated.
274    default                         The default value if one cannot be
275                                    determined.
276    """
277
278    var_name = var_name.upper()
279    plug_in_package_name = get_plug_in_package_name(case="upper")
280
281    package_var_name = plug_in_package_name + "_" + var_name
282    default_value = os.environ.get(package_var_name, None)
283    if default_value is not None:
284        # A package-name version of the variable was found so return its value.
285        return(default_value)
286
287    plug_var_name = PLUG_VAR_PREFIX + "_OVERRIDE_" + var_name
288    default_value = os.environ.get(plug_var_name, None)
289    if default_value is not None:
290        # A PLUG_VAR_PREFIX version of the variable was found so return its
291        # value.
292        return default_value
293
294    plug_var_name = PLUG_VAR_PREFIX + "_" + var_name
295    default_value = os.environ.get(plug_var_name, None)
296    if default_value is not None:
297        # A PLUG_VAR_PREFIX version of the variable was found so return its
298        # value.
299        return default_value
300
301    return default
302
303
304def srequired_plug_in(req_plug_in_names,
305                      plug_in_dir_paths=None):
306    r"""
307    Return an empty string if the required plug-ins are found in
308    plug_in_dir_paths.  Otherwise, return an error string.
309
310    Example call:
311    error_message = srequired_plug_in(req_plug_in_names, plug_in_dir_paths)
312
313    Description of argument(s):
314    req_plug_in_names               A list of plug_in names that the caller
315                                    requires (e.g. ['OS_Console']).
316    plug_in_dir_paths               A string which is a colon-delimited list
317                                    of plug-ins specified by the user (e.g.
318                                    DB_Logging:FFDC:OS_Console:Perf).  Path
319                                    values (e.g. "/home/robot/dir1") will be
320                                    stripped from this list to do the
321                                    analysis.  Default value is the
322                                    <PLUG_VAR_PREFIX>_PLUG_IN_DIR_PATHS
323                                    environment variable.
324    """
325
326    # Calculate default value for plug_in_dir_paths.
327    if plug_in_dir_paths is None:
328        plug_in_dir_paths = os.environ.get(PLUG_VAR_PREFIX
329                                           + "_PLUG_IN_DIR_PATHS", "")
330
331    error_message = ""
332
333    # Convert plug_in_dir_paths to a list of base names.
334    plug_in_dir_paths = \
335        list(filter(None, map(os.path.basename, plug_in_dir_paths.split(":"))))
336
337    # Check for each of the user's required plug-ins.
338    for plug_in_name in req_plug_in_names:
339        if plug_in_name not in plug_in_dir_paths:
340            error_message = "The \"" + get_plug_in_package_name() +\
341                "\" plug-in cannot run unless the user also selects the \"" +\
342                plug_in_name + "\" plug in:\n" +\
343                gp.sprint_var(plug_in_dir_paths)
344
345    return error_message
346
347
348def required_plug_in(req_plug_in_names,
349                     plug_in_dir_paths=None):
350    r"""
351    Return True if each of the plug-ins in req_plug_in_names can be found in
352    plug_in_dir_paths  Otherwise, return False and print an error message to
353    stderr.
354
355    Example call:
356    if not required_plug_in(['OS_Console'], AUTOBOOT_PLUG_IN_DIR_PATHS):
357        return False
358
359    Description of argument(s):
360    (See Description of arguments for srequired_plug_in (above)).
361    """
362
363    error_message = srequired_plug_in(req_plug_in_names, plug_in_dir_paths)
364    if not error_message == "":
365        gp.print_error_report(error_message)
366        return False
367
368    return True
369
370
371def compose_plug_in_save_dir_path(plug_in_package_name=None):
372    r"""
373    Create and return a directory path name that is suitable for saving
374    plug-in data.
375
376    The name will be comprised of things such as plug_in package name, pid,
377    etc. in order to guarantee that it is unique for a given test run.
378
379    Description of argument(s):
380    plug_in_package_name            The plug-in package name.  This defaults
381                                    to the name of the caller's plug-in
382                                    package.  However, the caller can specify
383                                    another value in order to retrieve data
384                                    saved by another plug-in package.
385    """
386
387    plug_in_package_name = gm.dft(plug_in_package_name,
388                                  get_plug_in_package_name())
389
390    BASE_TOOL_DIR_PATH = \
391        gm.add_trailing_slash(os.environ.get(PLUG_VAR_PREFIX
392                                             + "_BASE_TOOL_DIR_PATH",
393                                             "/tmp/"))
394    NICKNAME = os.environ.get("AUTOBOOT_OPENBMC_NICKNAME", "")
395    if NICKNAME == "":
396        NICKNAME = os.environ["AUTOIPL_FSP1_NICKNAME"]
397    MASTER_PID = os.environ[PLUG_VAR_PREFIX + "_MASTER_PID"]
398    gp.print_vars(BASE_TOOL_DIR_PATH, NICKNAME, plug_in_package_name,
399                  MASTER_PID)
400    return BASE_TOOL_DIR_PATH + gm.username() + "/" + NICKNAME + "/" +\
401        plug_in_package_name + "/" + str(MASTER_PID) + "/"
402
403
404def create_plug_in_save_dir(plug_in_package_name=None):
405    r"""
406    Create a directory suitable for saving plug-in processing data and return
407    its path name.
408
409    See compose_plug_in_save_dir_path for details.
410
411    Description of argument(s):
412    plug_in_package_name            See compose_plug_in_save_dir_path for
413                                    details.
414    """
415
416    plug_in_save_dir_path = compose_plug_in_save_dir_path(plug_in_package_name)
417    if os.path.isdir(plug_in_save_dir_path):
418        return plug_in_save_dir_path
419    gc.shell_cmd("mkdir -p " + plug_in_save_dir_path)
420    return plug_in_save_dir_path
421
422
423def delete_plug_in_save_dir(plug_in_package_name=None):
424    r"""
425    Delete the plug_in save directory.  See compose_plug_in_save_dir_path for
426    details.
427
428    Description of argument(s):
429    plug_in_package_name            See compose_plug_in_save_dir_path for
430                                    details.
431    """
432
433    gc.shell_cmd("rm -rf "
434                 + compose_plug_in_save_dir_path(plug_in_package_name))
435
436
437def save_plug_in_value(value, plug_in_package_name=None):
438    r"""
439    Save a value in a plug-in save file.  The value may be retrieved later via
440    a call to the restore_plug_in_value function.
441
442    This function will figure out the variable name of the value passed and
443    use that name in creating the plug-in save file.
444
445    Example call:
446
447    my_var1 = 5
448    save_plug_in_value(my_var1)
449
450    In this example, the value "5" would be saved to the "my_var1" file in the
451    plug-in save directory.
452
453    Description of argument(s):
454    value                           The value to be saved.
455    plug_in_package_name            See compose_plug_in_save_dir_path for
456                                    details.
457    """
458
459    # Get the name of the variable used as argument one to this function.
460    var_name = gp.get_arg_name(0, 1, stack_frame_ix=2)
461    plug_in_save_dir_path = create_plug_in_save_dir(plug_in_package_name)
462    save_file_path = plug_in_save_dir_path + var_name
463    gp.qprint_timen("Saving \"" + var_name + "\" value.")
464    gc.shell_cmd("echo '" + str(value) + "' > " + save_file_path)
465
466
467def restore_plug_in_value(default="", plug_in_package_name=None):
468    r"""
469    Return a value from a plug-in save file.
470
471    The name of the value to be restored will be determined by this function
472    based on the lvalue being assigned.  Consider the following example:
473
474    my_var1 = restore_plug_in_value(2)
475
476    In this example, this function would look for the "my_var1" file in the
477    plug-in save directory, read its value and return it.  If no such file
478    exists, the default value of 2 would be returned.
479
480    Description of argument(s):
481    default                         The default value to be returned if there
482                                    is no plug-in save file for the value in
483                                    question.
484    plug_in_package_name            See compose_plug_in_save_dir_path for
485                                    details.
486    """
487
488    # Get the lvalue from the caller's invocation of this function.
489    lvalue = gp.get_arg_name(0, -1, stack_frame_ix=2)
490    plug_in_save_dir_path = create_plug_in_save_dir(plug_in_package_name)
491    save_file_path = plug_in_save_dir_path + lvalue
492    if os.path.isfile(save_file_path):
493        gp.qprint_timen("Restoring " + lvalue + " value from "
494                        + save_file_path + ".")
495        value = gm.file_to_list(save_file_path, newlines=0, comments=0,
496                                trim=1)[0]
497        if type(default) is bool:
498            # Convert from string to bool.
499            value = (value == 'True')
500        if type(default) is int:
501            # Convert from string to int.
502            value = int(value)
503        gp.qprint_varx(lvalue, value)
504        return value
505    else:
506        gp.qprint_timen("Save file " + save_file_path
507                        + " does not exist so returning default value.")
508        gp.qprint_var(default)
509        return default
510
511
512def exit_not_master():
513    r"""
514    Exit the program with return code zero if this program was NOT called by
515    the master program.
516
517    There are cases where plug-ins are called by a multi-layered stack:
518
519    master_wrapper
520        obmc_boot_test.py
521            Example_plug_in/cp_setup
522
523    In a scenario like this, Example_plug_in/cp_setup may be called once
524    directly by master_wrapper (the master) and and then called again directly
525    by obmc_boot_test.py (the child).  Some plug-in programs may wish to avoid
526    doing any processing on the second such call.  This function will achieve
527    that purpose.
528
529    This function will print a standard message to stdout prior to exiting.
530    """
531
532    AUTOBOOT_MASTER_PID = gm.get_mod_global("AUTOBOOT_MASTER_PID")
533    AUTOBOOT_PROGRAM_PID = gm.get_mod_global("AUTOBOOT_PROGRAM_PID")
534
535    if AUTOBOOT_MASTER_PID != AUTOBOOT_PROGRAM_PID:
536        message = get_plug_in_package_name() + "/" + gp.pgm_name + " is not" \
537            + " being called by the master program in the stack so no action" \
538            + " will be taken."
539        gp.qprint_timen(message)
540        gp.qprint_vars(AUTOBOOT_MASTER_PID, AUTOBOOT_PROGRAM_PID)
541        exit(0)
542
543
544# Create print wrapper functions for all sprint functions defined above.
545# func_names contains a list of all print functions which should be created
546# from their sprint counterparts.
547func_names = ['print_plug_vars']
548
549# stderr_func_names is a list of functions whose output should go to stderr
550# rather than stdout.
551stderr_func_names = []
552
553replace_dict = dict(gp.replace_dict)
554replace_dict['mod_qualifier'] = 'gp.'
555func_defs = gp.create_print_wrapper_funcs(func_names, stderr_func_names,
556                                          replace_dict)
557gp.gp_debug_print(func_defs)
558exec(func_defs)
559