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