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