xref: /openbmc/openbmc-test-automation/lib/obmc_boot_test.py (revision b4e3ad47639e6aa11987957b3cf597a39836d22e)
1#!/usr/bin/env python3
2
3r"""
4This module is the python counterpart to obmc_boot_test.
5"""
6
7import glob
8import importlib.util
9import os
10import random
11import re
12import signal
13import time
14
15try:
16    import cPickle as pickle
17except ImportError:
18    import pickle
19
20import socket
21
22import gen_arg as ga
23import gen_cmd as gc
24import gen_misc as gm
25import gen_plug_in_utils as gpu
26import gen_print as gp
27import gen_robot_keyword as grk
28import gen_robot_plug_in as grpi
29import gen_valid as gv
30import logging_utils as log
31import pel_utils as pel
32import state as st
33import var_stack as vs
34from boot_data import *
35from robot.libraries.BuiltIn import BuiltIn
36from robot.utils import DotDict
37
38base_path = (
39    os.path.dirname(
40        os.path.dirname(importlib.util.find_spec("gen_robot_print").origin)
41    )
42    + os.sep
43)
44sys.path.append(base_path + "extended/")
45import run_keyword as rk  # NOQA
46
47# Setting master_pid correctly influences the behavior of plug-ins like
48# DB_Logging
49program_pid = os.getpid()
50master_pid = os.environ.get("AUTOBOOT_MASTER_PID", program_pid)
51pgm_name = re.sub("\\.py$", "", os.path.basename(__file__))
52
53# Set up boot data structures.
54os_host = BuiltIn().get_variable_value("${OS_HOST}", default="")
55
56boot_lists = read_boot_lists()
57
58# The maximum number of entries that can be in the boot_history global variable.
59max_boot_history = 10
60boot_history = []
61
62state = st.return_state_constant("default_state")
63cp_setup_called = 0
64next_boot = ""
65base_tool_dir_path = (
66    os.path.normpath(os.environ.get("AUTOBOOT_BASE_TOOL_DIR_PATH", "/tmp"))
67    + os.sep
68)
69
70ffdc_dir_path = os.path.normpath(os.environ.get("FFDC_DIR_PATH", "")) + os.sep
71boot_success = 0
72
73status_dir_path = os.environ.get(
74    "STATUS_DIR_PATH", ""
75) or BuiltIn().get_variable_value("${STATUS_DIR_PATH}", default="")
76if status_dir_path != "":
77    status_dir_path = os.path.normpath(status_dir_path) + os.sep
78    # For plugin expecting env gen_call_robot.py
79    os.environ["STATUS_DIR_PATH"] = status_dir_path
80
81redfish_support_trans_state = int(
82    os.environ.get("REDFISH_SUPPORT_TRANS_STATE", 0)
83) or int(
84    BuiltIn().get_variable_value("${REDFISH_SUPPORT_TRANS_STATE}", default=0)
85)
86redfish_supported = BuiltIn().get_variable_value(
87    "${REDFISH_SUPPORTED}", default=False
88)
89redfish_delete_sessions = int(
90    BuiltIn().get_variable_value("${REDFISH_DELETE_SESSIONS}", default=1)
91)
92if redfish_supported:
93    redfish = BuiltIn().get_library_instance("redfish")
94    default_power_on = "Redfish Power On"
95    default_power_off = "Redfish Power Off"
96    if not redfish_support_trans_state:
97        delete_errlogs_cmd = "Delete Error Logs  ${quiet}=${1}"
98        delete_bmcdump_cmd = "Delete All BMC Dump"
99        default_set_power_policy = "Set BMC Power Policy  ALWAYS_POWER_OFF"
100    else:
101        delete_errlogs_cmd = "Redfish Purge Event Log"
102        delete_bmcdump_cmd = "Redfish Delete All BMC Dumps"
103        delete_sysdump_cmd = "Redfish Delete All System Dumps"
104        default_set_power_policy = (
105            "Redfish Set Power Restore Policy  AlwaysOff"
106        )
107else:
108    default_power_on = "REST Power On"
109    default_power_off = "REST Power Off"
110    delete_errlogs_cmd = "Delete Error Logs  ${quiet}=${1}"
111    delete_bmcdump_cmd = "Delete All BMC Dump"
112    default_set_power_policy = "Set BMC Power Policy  ALWAYS_POWER_OFF"
113boot_count = 0
114
115LOG_LEVEL = BuiltIn().get_variable_value("${LOG_LEVEL}")
116AUTOBOOT_FFDC_PREFIX = os.environ.get("AUTOBOOT_FFDC_PREFIX", "")
117ffdc_prefix = AUTOBOOT_FFDC_PREFIX
118boot_start_time = ""
119boot_end_time = ""
120save_stack = vs.var_stack("save_stack")
121main_func_parm_list = ["boot_stack", "stack_mode", "quiet"]
122
123
124def dump_ffdc_rc():
125    r"""
126    Return the constant dump ffdc test return code value.
127
128    When a plug-in call point program returns this value, it indicates that
129    this program should collect FFDC.
130    """
131
132    return 0x00000200
133
134
135def stop_test_rc():
136    r"""
137    Return the constant stop test return code value.
138
139    When a plug-in call point program returns this value, it indicates that
140    this program should stop running.
141    """
142
143    return 0x00000200
144
145
146def process_host(host, host_var_name=""):
147    r"""
148    Process a host by getting the associated host name and IP address and
149    setting them in global variables.
150
151    If the caller does not pass the host_var_name, this function will try to
152    figure out the name of the variable used by the caller for the host parm.
153    Callers are advised to explicitly specify the host_var_name when calling
154    with an exec command.  In such cases, the get_arg_name cannot figure out
155    the host variable name.
156
157    This function will then create similar global variable names by
158    removing "_host" and appending "_host_name" or "_ip" to the host variable
159    name.
160
161    Example:
162
163    If a call is made like this:
164    process_host(openbmc_host)
165
166    Global variables openbmc_host_name and openbmc_ip will be set.
167
168    Description of argument(s):
169    host           A host name or IP.  The name of the variable used should
170                   have a suffix of "_host".
171    host_var_name  The name of the variable being used as the host parm.
172    """
173
174    if host_var_name == "":
175        host_var_name = gp.get_arg_name(0, 1, stack_frame_ix=2)
176
177    host_name_var_name = re.sub("host", "host_name", host_var_name)
178    ip_var_name = re.sub("host", "ip", host_var_name)
179    cmd_buf = (
180        "global "
181        + host_name_var_name
182        + ", "
183        + ip_var_name
184        + " ; "
185        + host_name_var_name
186        + ", "
187        + ip_var_name
188        + " = gm.get_host_name_ip('"
189        + host
190        + "')"
191    )
192    exec(cmd_buf)
193
194
195def process_pgm_parms():
196    r"""
197    Process the program parameters by assigning them all to corresponding
198    globals.  Also, set some global values that depend on program parameters.
199    """
200
201    # Program parameter processing.
202    # Assign all program parms to python variables which are global to this
203    # module.
204
205    global parm_list
206    parm_list = BuiltIn().get_variable_value("${parm_list}")
207    # The following subset of parms should be processed as integers.
208    int_list = [
209        "max_num_tests",
210        "boot_pass",
211        "boot_fail",
212        "ffdc_only",
213        "boot_fail_threshold",
214        "delete_errlogs",
215        "call_post_stack_plug",
216        "do_pre_boot_plug_in_setup",
217        "quiet",
218        "test_mode",
219        "debug",
220    ]
221    for parm in parm_list:
222        if parm in int_list:
223            sub_cmd = (
224                'int(BuiltIn().get_variable_value("${' + parm + '}", "0"))'
225            )
226        else:
227            sub_cmd = 'BuiltIn().get_variable_value("${' + parm + '}")'
228        cmd_buf = "global " + parm + " ; " + parm + " = " + sub_cmd
229        gp.dpissuing(cmd_buf)
230        exec(cmd_buf)
231        if re.match(r".*_host$", parm):
232            cmd_buf = "process_host(" + parm + ", '" + parm + "')"
233            exec(cmd_buf)
234        if re.match(r".*_password$", parm):
235            # Register the value of any parm whose name ends in _password.
236            # This will cause the print functions to replace passwords with
237            # asterisks in the output.
238            cmd_buf = "gp.register_passwords(" + parm + ")"
239            exec(cmd_buf)
240
241    global ffdc_dir_path_style
242    global boot_list
243    global boot_stack
244    global boot_results_file_path
245    global boot_results
246    global boot_history
247    global ffdc_list_file_path
248    global ffdc_report_list_path
249    global ffdc_summary_list_path
250    global boot_table
251    global valid_boot_types
252
253    if ffdc_dir_path_style == "":
254        ffdc_dir_path_style = int(os.environ.get("FFDC_DIR_PATH_STYLE", "0"))
255
256    # Convert these program parms to lists for easier processing..
257    boot_list = list(filter(None, boot_list.split(":")))
258    boot_stack = list(filter(None, boot_stack.split(":")))
259
260    boot_table = create_boot_table(boot_table_path, os_host=os_host)
261    valid_boot_types = create_valid_boot_list(boot_table)
262
263    cleanup_boot_results_file()
264    boot_results_file_path = create_boot_results_file_path(
265        pgm_name, openbmc_nickname, master_pid
266    )
267
268    if os.path.isfile(boot_results_file_path):
269        # We've been called before in this run so we'll load the saved
270        # boot_results and boot_history objects.
271        boot_results, boot_history = pickle.load(
272            open(boot_results_file_path, "rb")
273        )
274    else:
275        boot_results = boot_results(boot_table, boot_pass, boot_fail)
276
277    ffdc_list_file_path = (
278        base_tool_dir_path + openbmc_nickname + "/FFDC_FILE_LIST"
279    )
280    ffdc_report_list_path = (
281        base_tool_dir_path + openbmc_nickname + "/FFDC_REPORT_FILE_LIST"
282    )
283
284    ffdc_summary_list_path = (
285        base_tool_dir_path + openbmc_nickname + "/FFDC_SUMMARY_FILE_LIST"
286    )
287
288
289def initial_plug_in_setup():
290    r"""
291    Initialize all plug-in environment variables which do not change for the
292    duration of the program.
293
294    """
295
296    global LOG_LEVEL
297    BuiltIn().set_log_level("NONE")
298
299    BuiltIn().set_global_variable("${master_pid}", master_pid)
300    BuiltIn().set_global_variable("${FFDC_DIR_PATH}", ffdc_dir_path)
301    BuiltIn().set_global_variable("${STATUS_DIR_PATH}", status_dir_path)
302    BuiltIn().set_global_variable("${BASE_TOOL_DIR_PATH}", base_tool_dir_path)
303    BuiltIn().set_global_variable(
304        "${FFDC_LIST_FILE_PATH}", ffdc_list_file_path
305    )
306    BuiltIn().set_global_variable(
307        "${FFDC_REPORT_LIST_PATH}", ffdc_report_list_path
308    )
309    BuiltIn().set_global_variable(
310        "${FFDC_SUMMARY_LIST_PATH}", ffdc_summary_list_path
311    )
312
313    BuiltIn().set_global_variable(
314        "${FFDC_DIR_PATH_STYLE}", ffdc_dir_path_style
315    )
316    BuiltIn().set_global_variable("${FFDC_CHECK}", ffdc_check)
317
318    # For each program parameter, set the corresponding AUTOBOOT_ environment
319    # variable value.  Also, set an AUTOBOOT_ environment variable for every
320    # element in additional_values.
321    additional_values = [
322        "program_pid",
323        "master_pid",
324        "ffdc_dir_path",
325        "status_dir_path",
326        "base_tool_dir_path",
327        "ffdc_list_file_path",
328        "ffdc_report_list_path",
329        "ffdc_summary_list_path",
330        "execdir",
331        "redfish_supported",
332        "redfish_support_trans_state",
333    ]
334
335    plug_in_vars = parm_list + additional_values
336
337    for var_name in plug_in_vars:
338        var_value = BuiltIn().get_variable_value("${" + var_name + "}")
339        var_name = var_name.upper()
340        if var_value is None:
341            var_value = ""
342        os.environ["AUTOBOOT_" + var_name] = str(var_value)
343
344    BuiltIn().set_log_level(LOG_LEVEL)
345
346    # Make sure the ffdc list directory exists.
347    ffdc_list_dir_path = os.path.dirname(ffdc_list_file_path) + os.sep
348    if not os.path.exists(ffdc_list_dir_path):
349        os.makedirs(ffdc_list_dir_path)
350
351
352def plug_in_setup():
353    r"""
354    Initialize all changing plug-in environment variables for use by the
355    plug-in programs.
356    """
357
358    global LOG_LEVEL
359    global test_really_running
360
361    BuiltIn().set_log_level("NONE")
362
363    boot_pass, boot_fail = boot_results.return_total_pass_fail()
364    if boot_pass > 1:
365        test_really_running = 1
366    else:
367        test_really_running = 0
368
369    BuiltIn().set_global_variable(
370        "${test_really_running}", test_really_running
371    )
372    BuiltIn().set_global_variable("${boot_type_desc}", next_boot)
373    BuiltIn().set_global_variable("${boot_pass}", boot_pass)
374    BuiltIn().set_global_variable("${boot_fail}", boot_fail)
375    BuiltIn().set_global_variable("${boot_success}", boot_success)
376    BuiltIn().set_global_variable("${ffdc_prefix}", ffdc_prefix)
377    BuiltIn().set_global_variable("${boot_start_time}", boot_start_time)
378    BuiltIn().set_global_variable("${boot_end_time}", boot_end_time)
379
380    # For each program parameter, set the corresponding AUTOBOOT_ environment
381    # variable value.  Also, set an AUTOBOOT_ environment variable for every
382    # element in additional_values.
383    additional_values = [
384        "boot_type_desc",
385        "boot_success",
386        "boot_pass",
387        "boot_fail",
388        "test_really_running",
389        "ffdc_prefix",
390        "boot_start_time",
391        "boot_end_time",
392    ]
393
394    plug_in_vars = additional_values
395
396    for var_name in plug_in_vars:
397        var_value = BuiltIn().get_variable_value("${" + var_name + "}")
398        var_name = var_name.upper()
399        if var_value is None:
400            var_value = ""
401        os.environ["AUTOBOOT_" + var_name] = str(var_value)
402
403    if debug:
404        shell_rc, out_buf = gc.cmd_fnc_u(
405            "printenv | egrep AUTOBOOT_ | sort -u"
406        )
407
408    BuiltIn().set_log_level(LOG_LEVEL)
409
410
411def pre_boot_plug_in_setup():
412    # Clear the ffdc_list_file_path file.  Plug-ins may now write to it.
413    try:
414        os.remove(ffdc_list_file_path)
415    except OSError:
416        pass
417
418    # Clear the ffdc_report_list_path file.  Plug-ins may now write to it.
419    try:
420        os.remove(ffdc_report_list_path)
421    except OSError:
422        pass
423
424    # Clear the ffdc_summary_list_path file.  Plug-ins may now write to it.
425    try:
426        os.remove(ffdc_summary_list_path)
427    except OSError:
428        pass
429
430    global ffdc_prefix
431
432    seconds = time.time()
433    loc_time = time.localtime(seconds)
434    time_string = time.strftime("%y%m%d.%H%M%S.", loc_time)
435
436    ffdc_prefix = openbmc_nickname + "." + time_string
437
438
439def default_sigusr1(signal_number=0, frame=None):
440    r"""
441    Handle SIGUSR1 by doing nothing.
442
443    This function assists in debugging SIGUSR1 processing by printing messages
444    to stdout and to the log.html file.
445
446    Description of argument(s):
447    signal_number  The signal number (should always be 10 for SIGUSR1).
448    frame          The frame data.
449    """
450
451    gp.qprintn()
452    gp.qprint_executing()
453    gp.lprint_executing()
454
455
456def set_default_siguser1():
457    r"""
458    Set the default_sigusr1 function to be the SIGUSR1 handler.
459    """
460
461    gp.qprintn()
462    gp.qprint_executing()
463    gp.lprint_executing()
464    signal.signal(signal.SIGUSR1, default_sigusr1)
465
466
467def setup():
468    r"""
469    Do general program setup tasks.
470    """
471
472    global cp_setup_called
473    global transitional_boot_selected
474
475    gp.qprintn()
476
477    if redfish_supported:
478        redfish.login()
479
480    set_default_siguser1()
481    transitional_boot_selected = False
482
483    robot_pgm_dir_path = os.path.dirname(__file__) + os.sep
484    repo_bin_path = robot_pgm_dir_path.replace("/lib/", "/bin/")
485    # If we can't find process_plug_in_packages.py, ssh_pw or
486    # validate_plug_ins.py, then we don't have our repo bin in PATH.
487    shell_rc, out_buf = gc.cmd_fnc_u(
488        "which process_plug_in_packages.py" + " ssh_pw validate_plug_ins.py",
489        quiet=1,
490        print_output=0,
491        show_err=0,
492    )
493    if shell_rc != 0:
494        os.environ["PATH"] = repo_bin_path + ":" + os.environ.get("PATH", "")
495    # Likewise, our repo lib subdir needs to be in sys.path and PYTHONPATH.
496    if robot_pgm_dir_path not in sys.path:
497        sys.path.append(robot_pgm_dir_path)
498        PYTHONPATH = os.environ.get("PYTHONPATH", "")
499        if PYTHONPATH == "":
500            os.environ["PYTHONPATH"] = robot_pgm_dir_path
501        else:
502            os.environ["PYTHONPATH"] = robot_pgm_dir_path + ":" + PYTHONPATH
503
504    validate_parms()
505
506    gp.qprint_pgm_header()
507
508    grk.run_key_u(default_set_power_policy, ignore=1)
509
510    initial_plug_in_setup()
511
512    plug_in_setup()
513    rc, shell_rc, failed_plug_in_name = grpi.rprocess_plug_in_packages(
514        call_point="setup"
515    )
516    if rc != 0:
517        error_message = "Plug-in setup failed.\n"
518        gp.print_error_report(error_message)
519        BuiltIn().fail(error_message)
520    # Setting cp_setup_called lets our Teardown know that it needs to call
521    # the cleanup plug-in call point.
522    cp_setup_called = 1
523
524    # Keyword "FFDC" will fail if TEST_MESSAGE is not set.
525    BuiltIn().set_global_variable("${TEST_MESSAGE}", "${EMPTY}")
526    # FFDC_LOG_PATH is used by "FFDC" keyword.
527    BuiltIn().set_global_variable("${FFDC_LOG_PATH}", ffdc_dir_path)
528
529    # Also printed by FFDC.
530    global host_name
531    global host_ip
532    host = socket.gethostname()
533    host_name, host_ip = gm.get_host_name_ip(host)
534
535    gp.dprint_var(boot_table)
536    gp.dprint_var(boot_lists)
537
538
539def validate_parms():
540    r"""
541    Validate all program parameters.
542    """
543
544    process_pgm_parms()
545
546    gp.qprintn()
547
548    global openbmc_model
549    if openbmc_model == "":
550        status, ret_values = grk.run_key_u("Get BMC System Model", ignore=1)
551        # Set the model to default "OPENBMC" if getting it from BMC fails.
552        if status == "FAIL":
553            openbmc_model = "OPENBMC"
554        else:
555            openbmc_model = ret_values
556        BuiltIn().set_global_variable("${openbmc_model}", openbmc_model)
557    gv.set_exit_on_error(True)
558    gv.valid_value(openbmc_host)
559    gv.valid_value(openbmc_username)
560    gv.valid_value(openbmc_password)
561    gv.valid_value(rest_username)
562    gv.valid_value(rest_password)
563    gv.valid_value(ipmi_username)
564    gv.valid_value(ipmi_password)
565    if os_host != "":
566        gv.valid_value(os_username)
567        gv.valid_value(os_password)
568    if pdu_host != "":
569        gv.valid_value(pdu_username)
570        gv.valid_value(pdu_password)
571        gv.valid_integer(pdu_slot_no)
572    if openbmc_serial_host != "":
573        gv.valid_integer(openbmc_serial_port)
574    gv.valid_value(openbmc_model)
575    gv.valid_integer(max_num_tests)
576    gv.valid_integer(boot_pass)
577    gv.valid_integer(boot_fail)
578    plug_in_packages_list = grpi.rvalidate_plug_ins(plug_in_dir_paths)
579    BuiltIn().set_global_variable(
580        "${plug_in_packages_list}", plug_in_packages_list
581    )
582    gv.valid_value(stack_mode, valid_values=["normal", "skip"])
583    gv.set_exit_on_error(False)
584    if len(boot_list) == 0 and len(boot_stack) == 0 and not ffdc_only:
585        error_message = (
586            "You must provide either a value for either the"
587            + " boot_list or the boot_stack parm.\n"
588        )
589        BuiltIn().fail(gp.sprint_error(error_message))
590    valid_boot_list(boot_list, valid_boot_types)
591    valid_boot_list(boot_stack, valid_boot_types)
592    selected_PDU_boots = list(
593        set(boot_list + boot_stack) & set(boot_lists["PDU_reboot"])
594    )
595    if len(selected_PDU_boots) > 0 and pdu_host == "":
596        error_message = (
597            "You have selected the following boots which"
598            + " require a PDU host but no value for pdu_host:\n"
599        )
600        error_message += gp.sprint_var(selected_PDU_boots)
601        error_message += gp.sprint_var(pdu_host, fmt=gp.blank())
602        BuiltIn().fail(gp.sprint_error(error_message))
603
604    return
605
606
607def my_get_state():
608    r"""
609    Get the system state plus a little bit of wrapping.
610    """
611
612    global state
613
614    req_states = ["epoch_seconds"] + st.default_req_states
615
616    gp.qprint_timen("Getting system state.")
617    if test_mode:
618        state["epoch_seconds"] = int(time.time())
619    else:
620        state = st.get_state(req_states=req_states, quiet=quiet)
621    gp.qprint_var(state)
622
623
624def valid_state():
625    r"""
626    Verify that our state dictionary contains no blank values.  If we don't get
627    valid state data, we cannot continue to work.
628    """
629
630    if st.compare_states(state, st.invalid_state_match, "or"):
631        error_message = (
632            "The state dictionary contains blank fields which"
633            + " is illegal.\n"
634            + gp.sprint_var(state)
635        )
636        BuiltIn().fail(gp.sprint_error(error_message))
637
638
639def select_boot():
640    r"""
641    Select a boot test to be run based on our current state and return the
642    chosen boot type.
643
644    Description of arguments:
645    state  The state of the machine.
646    """
647
648    global transitional_boot_selected
649    global boot_stack
650
651    gp.qprint_timen("Selecting a boot test.")
652
653    if transitional_boot_selected and not boot_success:
654        prior_boot = next_boot
655        boot_candidate = boot_stack.pop()
656        gp.qprint_timen(
657            "The prior '"
658            + next_boot
659            + "' was chosen to"
660            + " transition to a valid state for '"
661            + boot_candidate
662            + "' which was at the top of the boot_stack.  Since"
663            + " the '"
664            + next_boot
665            + "' failed, the '"
666            + boot_candidate
667            + "' has been removed from the stack"
668            + " to avoid and endless failure loop."
669        )
670        if len(boot_stack) == 0:
671            return ""
672
673    my_get_state()
674    valid_state()
675
676    transitional_boot_selected = False
677    stack_popped = 0
678    if len(boot_stack) > 0:
679        stack_popped = 1
680        gp.qprint_dashes()
681        gp.qprint_var(boot_stack)
682        gp.qprint_dashes()
683        skip_boot_printed = 0
684        while len(boot_stack) > 0:
685            boot_candidate = boot_stack.pop()
686            if stack_mode == "normal":
687                break
688            else:
689                if st.compare_states(state, boot_table[boot_candidate]["end"]):
690                    if not skip_boot_printed:
691                        gp.qprint_var(stack_mode)
692                        gp.qprintn()
693                        gp.qprint_timen(
694                            "Skipping the following boot tests"
695                            + " which are unnecessary since their"
696                            + " required end states match the"
697                            + " current machine state:"
698                        )
699                        skip_boot_printed = 1
700                    gp.qprint_var(boot_candidate)
701                    boot_candidate = ""
702        if boot_candidate == "":
703            gp.qprint_dashes()
704            gp.qprint_var(boot_stack)
705            gp.qprint_dashes()
706            return boot_candidate
707        if st.compare_states(state, boot_table[boot_candidate]["start"]):
708            gp.qprint_timen(
709                "The machine state is valid for a '"
710                + boot_candidate
711                + "' boot test."
712            )
713            gp.qprint_dashes()
714            gp.qprint_var(boot_stack)
715            gp.qprint_dashes()
716            return boot_candidate
717        else:
718            gp.qprint_timen(
719                "The machine state does not match the required"
720                + " starting state for a '"
721                + boot_candidate
722                + "' boot test:"
723            )
724            gp.qprint_varx(
725                "boot_table_start_entry", boot_table[boot_candidate]["start"]
726            )
727            boot_stack.append(boot_candidate)
728            transitional_boot_selected = True
729            popped_boot = boot_candidate
730
731    # Loop through your list selecting a boot_candidates
732    boot_candidates = []
733    for boot_candidate in boot_list:
734        if st.compare_states(state, boot_table[boot_candidate]["start"]):
735            if stack_popped:
736                if st.compare_states(
737                    boot_table[boot_candidate]["end"],
738                    boot_table[popped_boot]["start"],
739                ):
740                    boot_candidates.append(boot_candidate)
741            else:
742                boot_candidates.append(boot_candidate)
743
744    if len(boot_candidates) == 0:
745        gp.qprint_timen(
746            "The user's boot list contained no boot tests"
747            + " which are valid for the current machine state."
748        )
749        boot_candidate = default_power_on
750        if not st.compare_states(state, boot_table[default_power_on]["start"]):
751            boot_candidate = default_power_off
752        boot_candidates.append(boot_candidate)
753        gp.qprint_timen(
754            "Using default '"
755            + boot_candidate
756            + "' boot type to transition to valid state."
757        )
758
759    gp.dprint_var(boot_candidates)
760
761    # Randomly select a boot from the candidate list.
762    boot = random.choice(boot_candidates)
763
764    return boot
765
766
767def print_defect_report(ffdc_file_list):
768    r"""
769    Print a defect report.
770
771    Description of argument(s):
772    ffdc_file_list  A list of files which were collected by our ffdc functions.
773    """
774
775    # Making deliberate choice to NOT run plug_in_setup().  We don't want
776    # ffdc_prefix updated.
777    rc, shell_rc, failed_plug_in_name = grpi.rprocess_plug_in_packages(
778        call_point="ffdc_report", stop_on_plug_in_failure=0
779    )
780
781    # Get additional header data which may have been created by ffdc plug-ins.
782    # Also, delete the individual header files to cleanup.
783    cmd_buf = (
784        "file_list=$(cat "
785        + ffdc_report_list_path
786        + " 2>/dev/null)"
787        + ' ; [ ! -z "${file_list}" ] && cat ${file_list}'
788        + " 2>/dev/null ; rm -rf ${file_list} 2>/dev/null || :"
789    )
790    shell_rc, more_header_info = gc.cmd_fnc_u(
791        cmd_buf, print_output=0, show_err=0
792    )
793
794    # Get additional summary data which may have been created by ffdc plug-ins.
795    # Also, delete the individual header files to cleanup.
796    cmd_buf = (
797        "file_list=$(cat "
798        + ffdc_summary_list_path
799        + " 2>/dev/null)"
800        + ' ; [ ! -z "${file_list}" ] && cat ${file_list}'
801        + " 2>/dev/null ; rm -rf ${file_list} 2>/dev/null || :"
802    )
803    shell_rc, ffdc_summary_info = gc.cmd_fnc_u(
804        cmd_buf, print_output=0, show_err=0
805    )
806
807    # ffdc_list_file_path contains a list of any ffdc files created by plug-
808    # ins, etc.  Read that data into a list.
809    try:
810        plug_in_ffdc_list = (
811            open(ffdc_list_file_path, "r").read().rstrip("\n").split("\n")
812        )
813        plug_in_ffdc_list = list(filter(None, plug_in_ffdc_list))
814    except IOError:
815        plug_in_ffdc_list = []
816
817    # Combine the files from plug_in_ffdc_list with the ffdc_file_list passed
818    # in.  Eliminate duplicates and sort the list.
819    ffdc_file_list = sorted(set(ffdc_file_list + plug_in_ffdc_list))
820
821    if status_file_path != "":
822        ffdc_file_list.insert(0, status_file_path)
823
824    # Convert the list to a printable list.
825    printable_ffdc_file_list = "\n".join(ffdc_file_list)
826
827    # Open ffdc_file_list for writing.  We will write a complete list of
828    # FFDC files to it for possible use by plug-ins like cp_stop_check.
829    ffdc_list_file = open(ffdc_list_file_path, "w")
830    ffdc_list_file.write(printable_ffdc_file_list + "\n")
831    ffdc_list_file.close()
832
833    indent = 0
834    width = 90
835    linefeed = 1
836    char = "="
837
838    gp.qprintn()
839    gp.qprint_dashes(indent, width, linefeed, char)
840    gp.qprintn("Copy this data to the defect:\n")
841
842    if len(more_header_info) > 0:
843        gp.qprintn(more_header_info)
844    gp.qpvars(
845        host_name,
846        host_ip,
847        openbmc_nickname,
848        openbmc_host,
849        openbmc_host_name,
850        openbmc_ip,
851        openbmc_username,
852        openbmc_password,
853        rest_username,
854        rest_password,
855        ipmi_username,
856        ipmi_password,
857        os_host,
858        os_host_name,
859        os_ip,
860        os_username,
861        os_password,
862        pdu_host,
863        pdu_host_name,
864        pdu_ip,
865        pdu_username,
866        pdu_password,
867        pdu_slot_no,
868        openbmc_serial_host,
869        openbmc_serial_host_name,
870        openbmc_serial_ip,
871        openbmc_serial_port,
872    )
873
874    gp.qprintn()
875    print_boot_history(boot_history)
876    gp.qprintn()
877    gp.qprint_var(state)
878    gp.qprintn()
879    gp.qprintn("FFDC data files:")
880    gp.qprintn(printable_ffdc_file_list)
881    gp.qprintn()
882
883    if len(ffdc_summary_info) > 0:
884        gp.qprintn(ffdc_summary_info)
885
886    gp.qprint_dashes(indent, width, linefeed, char)
887
888
889def my_ffdc():
890    r"""
891    Collect FFDC data.
892    """
893
894    global state
895
896    plug_in_setup()
897    rc, shell_rc, failed_plug_in_name = grpi.rprocess_plug_in_packages(
898        call_point="ffdc", stop_on_plug_in_failure=0
899    )
900
901    AUTOBOOT_FFDC_PREFIX = os.environ["AUTOBOOT_FFDC_PREFIX"]
902    status, ffdc_file_list = grk.run_key_u(
903        "FFDC  ffdc_prefix="
904        + AUTOBOOT_FFDC_PREFIX
905        + "  ffdc_function_list="
906        + ffdc_function_list,
907        ignore=1,
908    )
909    if status != "PASS":
910        gp.qprint_error("Call to ffdc failed.\n")
911        if type(ffdc_file_list) is not list:
912            ffdc_file_list = []
913        # Leave a record for caller that "soft" errors occurred.
914        soft_errors = 1
915        gpu.save_plug_in_value(soft_errors, pgm_name)
916
917    my_get_state()
918
919    print_defect_report(ffdc_file_list)
920
921
922def print_test_start_message(boot_keyword):
923    r"""
924    Print a message indicating what boot test is about to run.
925
926    Description of arguments:
927    boot_keyword  The name of the boot which is to be run
928                  (e.g. "BMC Power On").
929    """
930
931    global boot_history
932    global boot_start_time
933
934    doing_msg = gp.sprint_timen('Doing "' + boot_keyword + '".')
935
936    # Set boot_start_time for use by plug-ins.
937    boot_start_time = doing_msg[1:33]
938    gp.qprint_var(boot_start_time)
939
940    gp.qprint(doing_msg)
941
942    update_boot_history(boot_history, doing_msg, max_boot_history)
943
944
945def stop_boot_test(signal_number=0, frame=None):
946    r"""
947    Handle SIGUSR1 by aborting the boot test that is running.
948
949    Description of argument(s):
950    signal_number  The signal number (should always be 10 for SIGUSR1).
951    frame          The frame data.
952    """
953
954    gp.qprintn()
955    gp.qprint_executing()
956    gp.lprint_executing()
957
958    # Restore original sigusr1 handler.
959    set_default_siguser1()
960
961    message = "The caller has asked that the boot test be stopped and marked"
962    message += " as a failure."
963
964    function_stack = gm.get_function_stack()
965    if "wait_state" in function_stack:
966        st.set_exit_wait_early_message(message)
967    else:
968        BuiltIn().fail(gp.sprint_error(message))
969
970
971def run_boot(boot):
972    r"""
973    Run the specified boot.
974
975    Description of arguments:
976    boot  The name of the boot test to be performed.
977    """
978
979    global state
980
981    signal.signal(signal.SIGUSR1, stop_boot_test)
982    gp.qprint_timen("stop_boot_test is armed.")
983
984    print_test_start_message(boot)
985
986    plug_in_setup()
987    rc, shell_rc, failed_plug_in_name = grpi.rprocess_plug_in_packages(
988        call_point="pre_boot"
989    )
990    if rc != 0:
991        error_message = (
992            "Plug-in failed with non-zero return code.\n"
993            + gp.sprint_var(rc, fmt=gp.hexa())
994        )
995        set_default_siguser1()
996        BuiltIn().fail(gp.sprint_error(error_message))
997
998    if test_mode:
999        # In test mode, we'll pretend the boot worked by assigning its
1000        # required end state to the default state value.
1001        state = st.strip_anchor_state(boot_table[boot]["end"])
1002    else:
1003        # Assertion:  We trust that the state data was made fresh by the
1004        # caller.
1005
1006        gp.qprintn()
1007
1008        if boot_table[boot]["method_type"] == "keyword":
1009            rk.my_run_keywords(
1010                boot_table[boot].get("lib_file_path", ""),
1011                boot_table[boot]["method"],
1012                quiet=quiet,
1013            )
1014
1015        if boot_table[boot]["bmc_reboot"]:
1016            st.wait_for_comm_cycle(int(state["epoch_seconds"]))
1017            plug_in_setup()
1018            rc, shell_rc, failed_plug_in_name = grpi.rprocess_plug_in_packages(
1019                call_point="post_reboot"
1020            )
1021            if rc != 0:
1022                error_message = "Plug-in failed with non-zero return code.\n"
1023                error_message += gp.sprint_var(rc, fmt=gp.hexa())
1024                set_default_siguser1()
1025                BuiltIn().fail(gp.sprint_error(error_message))
1026        else:
1027            match_state = st.anchor_state(state)
1028            del match_state["epoch_seconds"]
1029            # Wait for the state to change in any way.
1030            st.wait_state(
1031                match_state,
1032                wait_time=state_change_timeout,
1033                interval="10 seconds",
1034                invert=1,
1035            )
1036
1037        gp.qprintn()
1038        if boot_table[boot]["end"]["chassis"] == "Off":
1039            boot_timeout = power_off_timeout
1040        else:
1041            boot_timeout = power_on_timeout
1042        st.wait_state(
1043            boot_table[boot]["end"],
1044            wait_time=boot_timeout,
1045            interval="10 seconds",
1046        )
1047
1048    plug_in_setup()
1049    rc, shell_rc, failed_plug_in_name = grpi.rprocess_plug_in_packages(
1050        call_point="post_boot"
1051    )
1052    if rc != 0:
1053        error_message = (
1054            "Plug-in failed with non-zero return code.\n"
1055            + gp.sprint_var(rc, fmt=gp.hexa())
1056        )
1057        set_default_siguser1()
1058        BuiltIn().fail(gp.sprint_error(error_message))
1059
1060    # Restore original sigusr1 handler.
1061    set_default_siguser1()
1062
1063
1064def test_loop_body():
1065    r"""
1066    The main loop body for the loop in main_py.
1067
1068    Description of arguments:
1069    boot_count  The iteration number (starts at 1).
1070    """
1071
1072    global boot_count
1073    global state
1074    global next_boot
1075    global boot_success
1076    global boot_end_time
1077
1078    # The flag can be enabled or disabled on the go
1079    redfish_delete_sessions = int(
1080        BuiltIn().get_variable_value("${REDFISH_DELETE_SESSIONS}", default=1)
1081    )
1082
1083    gp.qprintn()
1084
1085    next_boot = select_boot()
1086    if next_boot == "":
1087        return True
1088
1089    boot_count += 1
1090    gp.qprint_timen("Starting boot " + str(boot_count) + ".")
1091
1092    pre_boot_plug_in_setup()
1093
1094    cmd_buf = ["run_boot", next_boot]
1095    boot_status, msg = BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
1096    if boot_status == "FAIL":
1097        gp.qprint(msg)
1098
1099    gp.qprintn()
1100    if boot_status == "PASS":
1101        boot_success = 1
1102        completion_msg = gp.sprint_timen(
1103            'BOOT_SUCCESS: "' + next_boot + '" succeeded.'
1104        )
1105    else:
1106        boot_success = 0
1107        completion_msg = gp.sprint_timen(
1108            'BOOT_FAILED: "' + next_boot + '" failed.'
1109        )
1110
1111    # Set boot_end_time for use by plug-ins.
1112    boot_end_time = completion_msg[1:33]
1113    gp.qprint_var(boot_end_time)
1114
1115    gp.qprint(completion_msg)
1116
1117    boot_results.update(next_boot, boot_status)
1118
1119    plug_in_setup()
1120    # NOTE: A post_test_case call point failure is NOT counted as a boot
1121    # failure.
1122    rc, shell_rc, failed_plug_in_name = grpi.rprocess_plug_in_packages(
1123        call_point="post_test_case", stop_on_plug_in_failure=0
1124    )
1125
1126    plug_in_setup()
1127    rc, shell_rc, failed_plug_in_name = grpi.rprocess_plug_in_packages(
1128        call_point="ffdc_check",
1129        shell_rc=dump_ffdc_rc(),
1130        stop_on_plug_in_failure=1,
1131        stop_on_non_zero_rc=1,
1132    )
1133    if ffdc_check == "All" or shell_rc == dump_ffdc_rc():
1134        status, ret_values = grk.run_key_u("my_ffdc", ignore=1)
1135        if status != "PASS":
1136            gp.qprint_error("Call to my_ffdc failed.\n")
1137            # Leave a record for caller that "soft" errors occurred.
1138            soft_errors = 1
1139            gpu.save_plug_in_value(soft_errors, pgm_name)
1140
1141    if delete_errlogs:
1142        # print error logs before delete
1143        if redfish_support_trans_state:
1144            status, error_logs = grk.run_key_u("Get Redfish Event Logs")
1145            log.print_error_logs(
1146                error_logs, "AdditionalDataURI Message Severity"
1147            )
1148        else:
1149            status, error_logs = grk.run_key_u("Get Error Logs")
1150            log.print_error_logs(error_logs, "AdditionalData Message Severity")
1151        pels = pel.peltool("-l", ignore_err=1)
1152        gp.qprint_var(pels)
1153
1154        # We need to purge error logs between boots or they build up.
1155        grk.run_key(delete_errlogs_cmd, ignore=1)
1156        grk.run_key(delete_bmcdump_cmd, ignore=1)
1157        if redfish_support_trans_state:
1158            grk.run_key(delete_sysdump_cmd, ignore=1)
1159
1160    boot_results.print_report()
1161    gp.qprint_timen("Finished boot " + str(boot_count) + ".")
1162
1163    plug_in_setup()
1164    rc, shell_rc, failed_plug_in_name = grpi.rprocess_plug_in_packages(
1165        call_point="stop_check", shell_rc=stop_test_rc(), stop_on_non_zero_rc=1
1166    )
1167    if shell_rc == stop_test_rc():
1168        message = "Stopping as requested by user.\n"
1169        gp.qprint_time(message)
1170        BuiltIn().fail(message)
1171
1172    # This should help prevent ConnectionErrors.
1173    # Purge all redfish and REST connection sessions.
1174    if redfish_delete_sessions:
1175        grk.run_key_u("Close All Connections", ignore=1)
1176        grk.run_key_u("Delete All Redfish Sessions", ignore=1)
1177
1178    return True
1179
1180
1181def obmc_boot_test_teardown():
1182    r"""
1183    Clean up after the main keyword.
1184    """
1185    gp.qprint_executing()
1186
1187    if ga.psutil_imported:
1188        ga.terminate_descendants()
1189
1190    if cp_setup_called:
1191        plug_in_setup()
1192        rc, shell_rc, failed_plug_in_name = grpi.rprocess_plug_in_packages(
1193            call_point="cleanup", stop_on_plug_in_failure=0
1194        )
1195
1196    if "boot_results_file_path" in globals():
1197        # Save boot_results and boot_history objects to a file in case they are
1198        # needed again.
1199        gp.qprint_timen("Saving boot_results to the following path.")
1200        gp.qprint_var(boot_results_file_path)
1201        pickle.dump(
1202            (boot_results, boot_history),
1203            open(boot_results_file_path, "wb"),
1204            pickle.HIGHEST_PROTOCOL,
1205        )
1206
1207    global save_stack
1208    # Restore any global values saved on the save_stack.
1209    for parm_name in main_func_parm_list:
1210        # Get the parm_value if it was saved on the stack.
1211        try:
1212            parm_value = save_stack.pop(parm_name)
1213        except BaseException:
1214            # If it was not saved, no further action is required.
1215            continue
1216
1217        # Restore the saved value.
1218        cmd_buf = (
1219            'BuiltIn().set_global_variable("${' + parm_name + '}", parm_value)'
1220        )
1221        gp.dpissuing(cmd_buf)
1222        exec(cmd_buf)
1223
1224    gp.dprintn(save_stack.sprint_obj())
1225
1226
1227def test_teardown():
1228    r"""
1229    Clean up after this test case.
1230    """
1231
1232    gp.qprintn()
1233    gp.qprint_executing()
1234
1235    if ga.psutil_imported:
1236        ga.terminate_descendants()
1237
1238    cmd_buf = [
1239        "Print Error",
1240        "A keyword timeout occurred ending this program.\n",
1241    ]
1242    BuiltIn().run_keyword_if_timeout_occurred(*cmd_buf)
1243
1244    if redfish_supported:
1245        redfish.logout()
1246
1247    gp.qprint_pgm_footer()
1248
1249
1250def post_stack():
1251    r"""
1252    Process post_stack plug-in programs.
1253    """
1254
1255    if not call_post_stack_plug:
1256        # The caller does not wish to have post_stack plug-in processing done.
1257        return
1258
1259    global boot_success
1260
1261    # NOTE: A post_stack call-point failure is NOT counted as a boot failure.
1262    pre_boot_plug_in_setup()
1263    # For the purposes of the following plug-ins, mark the "boot" as a success.
1264    boot_success = 1
1265    plug_in_setup()
1266    (
1267        rc,
1268        shell_rc,
1269        failed_plug_in_name,
1270        history,
1271    ) = grpi.rprocess_plug_in_packages(
1272        call_point="post_stack", stop_on_plug_in_failure=0, return_history=True
1273    )
1274    for doing_msg in history:
1275        update_boot_history(boot_history, doing_msg, max_boot_history)
1276    if rc != 0:
1277        boot_success = 0
1278
1279    plug_in_setup()
1280    rc, shell_rc, failed_plug_in_name = grpi.rprocess_plug_in_packages(
1281        call_point="ffdc_check",
1282        shell_rc=dump_ffdc_rc(),
1283        stop_on_plug_in_failure=1,
1284        stop_on_non_zero_rc=1,
1285    )
1286    if shell_rc == dump_ffdc_rc():
1287        status, ret_values = grk.run_key_u("my_ffdc", ignore=1)
1288        if status != "PASS":
1289            gp.qprint_error("Call to my_ffdc failed.\n")
1290            # Leave a record for caller that "soft" errors occurred.
1291            soft_errors = 1
1292            gpu.save_plug_in_value(soft_errors, pgm_name)
1293
1294    plug_in_setup()
1295    rc, shell_rc, failed_plug_in_name = grpi.rprocess_plug_in_packages(
1296        call_point="stop_check", shell_rc=stop_test_rc(), stop_on_non_zero_rc=1
1297    )
1298    if shell_rc == stop_test_rc():
1299        message = "Stopping as requested by user.\n"
1300        gp.qprint_time(message)
1301        BuiltIn().fail(message)
1302
1303
1304def obmc_boot_test_py(
1305    loc_boot_stack=None, loc_stack_mode=None, loc_quiet=None
1306):
1307    r"""
1308    Do main program processing.
1309    """
1310
1311    global save_stack
1312
1313    ga.set_term_options(
1314        term_requests={"pgm_names": ["process_plug_in_packages.py"]}
1315    )
1316
1317    gp.dprintn()
1318    # Process function parms.
1319    for parm_name in main_func_parm_list:
1320        # Get parm's value.
1321        parm_value = eval("loc_" + parm_name)
1322        gp.dpvars(parm_name, parm_value)
1323
1324        if parm_value is not None:
1325            # Save the global value on a stack.
1326            cmd_buf = (
1327                'save_stack.push(BuiltIn().get_variable_value("${'
1328                + parm_name
1329                + '}"), "'
1330                + parm_name
1331                + '")'
1332            )
1333            gp.dpissuing(cmd_buf)
1334            exec(cmd_buf)
1335
1336            # Set the global value to the passed value.
1337            cmd_buf = (
1338                'BuiltIn().set_global_variable("${'
1339                + parm_name
1340                + '}", loc_'
1341                + parm_name
1342                + ")"
1343            )
1344            gp.dpissuing(cmd_buf)
1345            exec(cmd_buf)
1346
1347    gp.dprintn(save_stack.sprint_obj())
1348
1349    setup()
1350
1351    init_boot_pass, init_boot_fail = boot_results.return_total_pass_fail()
1352
1353    if ffdc_only:
1354        gp.qprint_timen("Caller requested ffdc_only.")
1355        if do_pre_boot_plug_in_setup:
1356            pre_boot_plug_in_setup()
1357        grk.run_key_u("my_ffdc")
1358        return
1359
1360    if delete_errlogs:
1361        # print error logs before delete
1362        if redfish_support_trans_state:
1363            status, error_logs = grk.run_key_u("Get Redfish Event Logs")
1364            log.print_error_logs(
1365                error_logs, "AdditionalDataURI Message Severity"
1366            )
1367        else:
1368            status, error_logs = grk.run_key_u("Get Error Logs")
1369            log.print_error_logs(error_logs, "AdditionalData Message Severity")
1370        pels = pel.peltool("-l", ignore_err=1)
1371        gp.qprint_var(pels)
1372
1373        # Delete errlogs prior to doing any boot tests.
1374        grk.run_key(delete_errlogs_cmd, ignore=1)
1375        grk.run_key(delete_bmcdump_cmd, ignore=1)
1376        if redfish_support_trans_state:
1377            grk.run_key(delete_sysdump_cmd, ignore=1)
1378
1379    # Process caller's boot_stack.
1380    while len(boot_stack) > 0:
1381        test_loop_body()
1382
1383    gp.qprint_timen("Finished processing stack.")
1384
1385    post_stack()
1386
1387    # Process caller's boot_list.
1388    if len(boot_list) > 0:
1389        for ix in range(1, max_num_tests + 1):
1390            test_loop_body()
1391
1392    gp.qprint_timen("Completed all requested boot tests.")
1393
1394    boot_pass, boot_fail = boot_results.return_total_pass_fail()
1395    new_fail = boot_fail - init_boot_fail
1396    if new_fail > boot_fail_threshold:
1397        error_message = (
1398            "Boot failures exceed the boot failure"
1399            + " threshold:\n"
1400            + gp.sprint_var(new_fail)
1401            + gp.sprint_var(boot_fail_threshold)
1402        )
1403        BuiltIn().fail(gp.sprint_error(error_message))
1404