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