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