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