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