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