1#!/usr/bin/env python3 2 3r""" 4This module contains functions having to do with machine state: get_state, 5check_state, wait_state, etc. 6 7The 'State' is a composite of many pieces of data. Therefore, the functions 8in this module define state as an ordered dictionary. Here is an example of 9some test output showing machine state: 10 11default_state: 12 default_state[chassis]: On 13 default_state[boot_progress]: OSStart 14 default_state[operating_system]: BootComplete 15 default_state[host]: Running 16 default_state[os_ping]: 1 17 default_state[os_login]: 1 18 default_state[os_run_cmd]: 1 19 20Different users may very well have different needs when inquiring about 21state. Support for new pieces of state information may be added to this 22module as needed. 23 24By using the wait_state function, a caller can start a boot and then wait for 25a precisely defined state to indicate that the boot has succeeded. If 26the boot fails, they can see exactly why by looking at the current state as 27compared with the expected state. 28""" 29 30import imp 31import os 32import re 33import sys 34 35import bmc_ssh_utils as bsu 36import gen_cmd as gc 37import gen_print as gp 38import gen_robot_utils as gru 39import gen_valid as gv 40from robot.libraries.BuiltIn import BuiltIn 41from robot.utils import DotDict 42 43# NOTE: Avoid importing utils.robot because utils.robot imports state.py 44# (indirectly) which will cause failures. 45gru.my_import_resource("rest_client.robot") 46 47base_path = ( 48 os.path.dirname(os.path.dirname(imp.find_module("gen_robot_print")[1])) 49 + os.sep 50) 51sys.path.append(base_path + "data/") 52 53# Previously, I had this coded: 54# import variables as var 55# However, we ran into a problem where a robot program did this... 56# Variables ../../lib/ras/variables.py 57# Prior to doing this... 58# Library ../lib/state.py 59 60# This caused the wrong variables.py file to be selected. Attempts to fix this 61# have failed so far. For the moment, we will hard-code the value we need from 62# the file. 63 64SYSTEM_STATE_URI = "/xyz/openbmc_project/state/" 65 66# The BMC code has recently been changed as far as what states are defined and 67# what the state values can be. This module now has a means of processing both 68# the old style state (i.e. OBMC_STATES_VERSION = 0) and the new style (i.e. 69# OBMC_STATES_VERSION = 1). 70# The caller can set environment variable OBMC_STATES_VERSION to dictate 71# whether we're processing old or new style states. If OBMC_STATES_VERSION is 72# not set it will default to 1. 73 74# As of the present moment, OBMC_STATES_VERSION of 0 is for cold that is so old 75# that it is no longer worthwhile to maintain. The OBMC_STATES_VERSION 0 code 76# is being removed but the OBMC_STATES_VERSION value will stay for now in the 77# event that it is needed in the future. 78 79OBMC_STATES_VERSION = int(os.environ.get("OBMC_STATES_VERSION", 1)) 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) 86 87platform_arch_type = os.environ.get( 88 "PLATFORM_ARCH_TYPE", "" 89) or BuiltIn().get_variable_value("${PLATFORM_ARCH_TYPE}", default="power") 90 91# valid_os_req_states and default_os_req_states are used by the os_get_state 92# function. 93# valid_os_req_states is a list of state information supported by the 94# get_os_state function. 95valid_os_req_states = ["os_ping", "os_login", "os_run_cmd"] 96 97# When a user calls get_os_state w/o specifying req_states, 98# default_os_req_states is used as its value. 99default_os_req_states = ["os_ping", "os_login", "os_run_cmd"] 100 101# Presently, some BMCs appear to not keep time very well. This environment 102# variable directs the get_state function to use either the BMC's epoch time 103# or the local epoch time. 104USE_BMC_EPOCH_TIME = int(os.environ.get("USE_BMC_EPOCH_TIME", 0)) 105 106# Useful state constant definition(s). 107if not redfish_support_trans_state: 108 # When a user calls get_state w/o specifying req_states, default_req_states 109 # is used as its value. 110 default_req_states = [ 111 "rest", 112 "chassis", 113 "bmc", 114 "boot_progress", 115 "operating_system", 116 "host", 117 "os_ping", 118 "os_login", 119 "os_run_cmd", 120 ] 121 122 # valid_req_states is a list of sub states supported by the get_state function. 123 # valid_req_states, default_req_states and master_os_up_match are used by the 124 # get_state function. 125 126 valid_req_states = [ 127 "ping", 128 "packet_loss", 129 "uptime", 130 "epoch_seconds", 131 "elapsed_boot_time", 132 "rest", 133 "chassis", 134 "requested_chassis", 135 "bmc", 136 "requested_bmc", 137 "boot_progress", 138 "operating_system", 139 "host", 140 "requested_host", 141 "attempts_left", 142 "os_ping", 143 "os_login", 144 "os_run_cmd", 145 ] 146 147 # default_state is an initial value which may be of use to callers. 148 default_state = DotDict( 149 [ 150 ("rest", "1"), 151 ("chassis", "On"), 152 ("bmc", "Ready"), 153 ("boot_progress", "OSStart"), 154 ("operating_system", "BootComplete"), 155 ("host", "Running"), 156 ("os_ping", "1"), 157 ("os_login", "1"), 158 ("os_run_cmd", "1"), 159 ] 160 ) 161 162 # A match state for checking that the system is at "standby". 163 standby_match_state = DotDict( 164 [ 165 ("rest", "^1$"), 166 ("chassis", "^Off$"), 167 ("bmc", "^Ready$"), 168 ("boot_progress", "^Off|Unspecified$"), 169 ("operating_system", "^Inactive$"), 170 ("host", "^Off$"), 171 ] 172 ) 173 174 # A match state for checking that the system is at "os running". 175 os_running_match_state = DotDict( 176 [ 177 ("chassis", "^On$"), 178 ("bmc", "^Ready$"), 179 ("boot_progress", "FW Progress, Starting OS|OSStart"), 180 ("operating_system", "BootComplete"), 181 ("host", "^Running$"), 182 ("os_ping", "^1$"), 183 ("os_login", "^1$"), 184 ("os_run_cmd", "^1$"), 185 ] 186 ) 187 188 # A master dictionary to determine whether the os may be up. 189 master_os_up_match = DotDict( 190 [ 191 ("chassis", "^On$"), 192 ("bmc", "^Ready$"), 193 ("boot_progress", "FW Progress, Starting OS|OSStart"), 194 ("operating_system", "BootComplete"), 195 ("host", "^Running|Quiesced$"), 196 ] 197 ) 198 199 invalid_state_match = DotDict( 200 [ 201 ("rest", "^$"), 202 ("chassis", "^$"), 203 ("bmc", "^$"), 204 ("boot_progress", "^$"), 205 ("operating_system", "^$"), 206 ("host", "^$"), 207 ] 208 ) 209else: 210 # When a user calls get_state w/o specifying req_states, default_req_states 211 # is used as its value. 212 default_req_states = [ 213 "redfish", 214 "chassis", 215 "bmc", 216 "boot_progress", 217 "host", 218 "os_ping", 219 "os_login", 220 "os_run_cmd", 221 ] 222 223 # valid_req_states is a list of sub states supported by the get_state function. 224 # valid_req_states, default_req_states and master_os_up_match are used by the 225 # get_state function. 226 227 valid_req_states = [ 228 "ping", 229 "packet_loss", 230 "uptime", 231 "epoch_seconds", 232 "elapsed_boot_time", 233 "redfish", 234 "chassis", 235 "requested_chassis", 236 "bmc", 237 "requested_bmc", 238 "boot_progress", 239 "host", 240 "requested_host", 241 "attempts_left", 242 "os_ping", 243 "os_login", 244 "os_run_cmd", 245 ] 246 247 # default_state is an initial value which may be of use to callers. 248 default_state = DotDict( 249 [ 250 ("redfish", "1"), 251 ("chassis", "On"), 252 ("bmc", "Enabled"), 253 ( 254 "boot_progress", 255 "SystemHardwareInitializationComplete|OSBootStarted|OSRunning", 256 ), 257 ("host", "Enabled"), 258 ("os_ping", "1"), 259 ("os_login", "1"), 260 ("os_run_cmd", "1"), 261 ] 262 ) 263 264 # A match state for checking that the system is at "standby". 265 standby_match_state = DotDict( 266 [ 267 ("redfish", "^1$"), 268 ("chassis", "^Off$"), 269 ("bmc", "^Enabled$"), 270 ("boot_progress", "^None$"), 271 ("host", "^Disabled$"), 272 ] 273 ) 274 275 # A match state for checking that the system is at "os running". 276 os_running_match_state = DotDict( 277 [ 278 ("chassis", "^On$"), 279 ("bmc", "^Enabled$"), 280 ( 281 "boot_progress", 282 "SystemHardwareInitializationComplete|OSBootStarted|OSRunning", 283 ), 284 ("host", "^Enabled$"), 285 ("os_ping", "^1$"), 286 ("os_login", "^1$"), 287 ("os_run_cmd", "^1$"), 288 ] 289 ) 290 291 # A master dictionary to determine whether the os may be up. 292 master_os_up_match = DotDict( 293 [ 294 ("chassis", "^On$"), 295 ("bmc", "^Enabled$"), 296 ( 297 "boot_progress", 298 "SystemHardwareInitializationComplete|OSBootStarted|OSRunning", 299 ), 300 ("host", "^Enabled$"), 301 ] 302 ) 303 304 invalid_state_match = DotDict( 305 [ 306 ("redfish", "^$"), 307 ("chassis", "^$"), 308 ("bmc", "^$"), 309 ("boot_progress", "^$"), 310 ("host", "^$"), 311 ] 312 ) 313 314# Filter the states based on platform type. 315if platform_arch_type == "x86": 316 if not redfish_support_trans_state: 317 default_req_states.remove("operating_system") 318 valid_req_states.remove("operating_system") 319 del default_state["operating_system"] 320 del standby_match_state["operating_system"] 321 del os_running_match_state["operating_system"] 322 del master_os_up_match["operating_system"] 323 del invalid_state_match["operating_system"] 324 325 default_req_states.remove("boot_progress") 326 valid_req_states.remove("boot_progress") 327 del default_state["boot_progress"] 328 del standby_match_state["boot_progress"] 329 del os_running_match_state["boot_progress"] 330 del master_os_up_match["boot_progress"] 331 del invalid_state_match["boot_progress"] 332 333 334def return_state_constant(state_name="default_state"): 335 r""" 336 Return the named state dictionary constant. 337 """ 338 339 return eval(state_name) 340 341 342def anchor_state(state): 343 r""" 344 Add regular expression anchors ("^" and "$") to the beginning and end of 345 each item in the state dictionary passed in. Return the resulting 346 dictionary. 347 348 Description of argument(s): 349 state A dictionary such as the one returned by the get_state() 350 function. 351 """ 352 353 anchored_state = state.copy() 354 for key in anchored_state.keys(): 355 anchored_state[key] = "^" + str(anchored_state[key]) + "$" 356 357 return anchored_state 358 359 360def strip_anchor_state(state): 361 r""" 362 Strip regular expression anchors ("^" and "$") from the beginning and end 363 of each item in the state dictionary passed in. Return the resulting 364 dictionary. 365 366 Description of argument(s): 367 state A dictionary such as the one returned by the get_state() 368 function. 369 """ 370 371 stripped_state = state.copy() 372 for key in stripped_state.keys(): 373 stripped_state[key] = stripped_state[key].strip("^$") 374 375 return stripped_state 376 377 378def expressions_key(): 379 r""" 380 Return expressions key constant. 381 """ 382 return "<expressions>" 383 384 385def compare_states(state, match_state, match_type="and"): 386 r""" 387 Compare 2 state dictionaries. Return True if they match and False if they 388 don't. Note that the match_state dictionary does not need to have an entry 389 corresponding to each entry in the state dictionary. But for each entry 390 that it does have, the corresponding state entry will be checked for a 391 match. 392 393 Description of argument(s): 394 state A state dictionary such as the one returned by the 395 get_state function. 396 match_state A dictionary whose key/value pairs are "state field"/ 397 "state value". The state value is interpreted as a 398 regular expression. Every value in this dictionary is 399 considered. When match_type is 'and', if each and every 400 comparison matches, the two dictionaries are considered to 401 be matching. If match_type is 'or', if any two of the 402 elements compared match, the two dictionaries are 403 considered to be matching. 404 405 This value may also be any string accepted by 406 return_state_constant (e.g. "standby_match_state"). In 407 such a case this function will call return_state_constant 408 to convert it to a proper dictionary as described above. 409 410 Finally, one special value is accepted for the key field: 411 expression_key(). If such an entry exists, its value is 412 taken to be a list of expressions to be evaluated. These 413 expressions may reference state dictionary entries by 414 simply coding them in standard python syntax (e.g. 415 state['key1']). What follows is an example expression: 416 417 "int(float(state['uptime'])) < int(state['elapsed_boot_time'])" 418 419 In this example, if the state dictionary's 'uptime' entry 420 is less than its 'elapsed_boot_time' entry, it would 421 qualify as a match. 422 match_type This may be 'and' or 'or'. 423 """ 424 425 error_message = gv.valid_value(match_type, valid_values=["and", "or"]) 426 if error_message != "": 427 BuiltIn().fail(gp.sprint_error(error_message)) 428 429 try: 430 match_state = return_state_constant(match_state) 431 except TypeError: 432 pass 433 434 default_match = match_type == "and" 435 for key, match_state_value in match_state.items(): 436 # Blank match_state_value means "don't care". 437 if match_state_value == "": 438 continue 439 if key == expressions_key(): 440 for expr in match_state_value: 441 # Use python interpreter to evaluate the expression. 442 match = eval(expr) 443 if match != default_match: 444 return match 445 else: 446 try: 447 match = ( 448 re.match(match_state_value, str(state[key])) is not None 449 ) 450 except KeyError: 451 match = False 452 if match != default_match: 453 return match 454 455 return default_match 456 457 458def get_os_state( 459 os_host="", 460 os_username="", 461 os_password="", 462 req_states=default_os_req_states, 463 os_up=True, 464 quiet=None, 465): 466 r""" 467 Get component states for the operating system such as ping, login, 468 etc, put them into a dictionary and return them to the caller. 469 470 Note that all substate values are strings. 471 472 Description of argument(s): 473 os_host The DNS name or IP address of the operating system. 474 This defaults to global ${OS_HOST}. 475 os_username The username to be used to login to the OS. 476 This defaults to global ${OS_USERNAME}. 477 os_password The password to be used to login to the OS. 478 This defaults to global ${OS_PASSWORD}. 479 req_states This is a list of states whose values are being requested by 480 the caller. 481 os_up If the caller knows that the os can't possibly be up, it can 482 improve performance by passing os_up=False. This function 483 will then simply return default values for all requested os 484 sub states. 485 quiet Indicates whether status details (e.g. curl commands) should 486 be written to the console. 487 Defaults to either global value of ${QUIET} or to 1. 488 """ 489 490 quiet = int(gp.get_var_value(quiet, 0)) 491 492 # Set parm defaults where necessary and validate all parms. 493 if os_host == "": 494 os_host = BuiltIn().get_variable_value("${OS_HOST}") 495 error_message = gv.valid_value(os_host, invalid_values=[None, ""]) 496 if error_message != "": 497 BuiltIn().fail(gp.sprint_error(error_message)) 498 499 if os_username == "": 500 os_username = BuiltIn().get_variable_value("${OS_USERNAME}") 501 error_message = gv.valid_value(os_username, invalid_values=[None, ""]) 502 if error_message != "": 503 BuiltIn().fail(gp.sprint_error(error_message)) 504 505 if os_password == "": 506 os_password = BuiltIn().get_variable_value("${OS_PASSWORD}") 507 error_message = gv.valid_value(os_password, invalid_values=[None, ""]) 508 if error_message != "": 509 BuiltIn().fail(gp.sprint_error(error_message)) 510 511 invalid_req_states = [ 512 sub_state 513 for sub_state in req_states 514 if sub_state not in valid_os_req_states 515 ] 516 if len(invalid_req_states) > 0: 517 error_message = ( 518 "The following req_states are not supported:\n" 519 + gp.sprint_var(invalid_req_states) 520 ) 521 BuiltIn().fail(gp.sprint_error(error_message)) 522 523 # Initialize all substate values supported by this function. 524 os_ping = 0 525 os_login = 0 526 os_run_cmd = 0 527 528 if os_up: 529 if "os_ping" in req_states: 530 # See if the OS pings. 531 rc, out_buf = gc.shell_cmd( 532 "ping -c 1 -w 2 " + os_host, 533 print_output=0, 534 show_err=0, 535 ignore_err=1, 536 ) 537 if rc == 0: 538 os_ping = 1 539 540 # Programming note: All attributes which do not require an ssh login 541 # should have been processed by this point. 542 master_req_login = ["os_login", "os_run_cmd"] 543 req_login = [ 544 sub_state 545 for sub_state in req_states 546 if sub_state in master_req_login 547 ] 548 must_login = len(req_login) > 0 549 550 if must_login: 551 output, stderr, rc = bsu.os_execute_command( 552 "uptime", 553 quiet=quiet, 554 ignore_err=1, 555 time_out=20, 556 os_host=os_host, 557 os_username=os_username, 558 os_password=os_password, 559 ) 560 if rc == 0: 561 os_login = 1 562 os_run_cmd = 1 563 else: 564 gp.dprint_vars(output, stderr) 565 gp.dprint_vars(rc, 1) 566 567 os_state = DotDict() 568 for sub_state in req_states: 569 cmd_buf = "os_state['" + sub_state + "'] = str(" + sub_state + ")" 570 exec(cmd_buf) 571 572 return os_state 573 574 575def get_state( 576 openbmc_host="", 577 openbmc_username="", 578 openbmc_password="", 579 os_host="", 580 os_username="", 581 os_password="", 582 req_states=default_req_states, 583 quiet=None, 584): 585 r""" 586 Get component states such as chassis state, bmc state, etc, put them into a 587 dictionary and return them to the caller. 588 589 Note that all substate values are strings. 590 591 Note: If elapsed_boot_time is included in req_states, it is the caller's 592 duty to call set_start_boot_seconds() in order to set global 593 start_boot_seconds. elapsed_boot_time is the current time minus 594 start_boot_seconds. 595 596 Description of argument(s): 597 openbmc_host The DNS name or IP address of the BMC. 598 This defaults to global ${OPENBMC_HOST}. 599 openbmc_username The username to be used to login to the BMC. 600 This defaults to global ${OPENBMC_USERNAME}. 601 openbmc_password The password to be used to login to the BMC. 602 This defaults to global ${OPENBMC_PASSWORD}. 603 os_host The DNS name or IP address of the operating system. 604 This defaults to global ${OS_HOST}. 605 os_username The username to be used to login to the OS. 606 This defaults to global ${OS_USERNAME}. 607 os_password The password to be used to login to the OS. 608 This defaults to global ${OS_PASSWORD}. 609 req_states This is a list of states whose values are being requested 610 by the caller. 611 quiet Indicates whether status details (e.g. curl commands) 612 should be written to the console. 613 Defaults to either global value of ${QUIET} or to 1. 614 """ 615 616 quiet = int(gp.get_var_value(quiet, 0)) 617 618 # Set parm defaults where necessary and validate all parms. 619 if openbmc_host == "": 620 openbmc_host = BuiltIn().get_variable_value("${OPENBMC_HOST}") 621 error_message = gv.valid_value(openbmc_host, invalid_values=[None, ""]) 622 if error_message != "": 623 BuiltIn().fail(gp.sprint_error(error_message)) 624 625 if openbmc_username == "": 626 openbmc_username = BuiltIn().get_variable_value("${OPENBMC_USERNAME}") 627 error_message = gv.valid_value(openbmc_username, invalid_values=[None, ""]) 628 if error_message != "": 629 BuiltIn().fail(gp.sprint_error(error_message)) 630 631 if openbmc_password == "": 632 openbmc_password = BuiltIn().get_variable_value("${OPENBMC_PASSWORD}") 633 error_message = gv.valid_value(openbmc_password, invalid_values=[None, ""]) 634 if error_message != "": 635 BuiltIn().fail(gp.sprint_error(error_message)) 636 637 # NOTE: OS parms are optional. 638 if os_host == "": 639 os_host = BuiltIn().get_variable_value("${OS_HOST}") 640 if os_host is None: 641 os_host = "" 642 643 if os_username == "": 644 os_username = BuiltIn().get_variable_value("${OS_USERNAME}") 645 if os_username is None: 646 os_username = "" 647 648 if os_password == "": 649 os_password = BuiltIn().get_variable_value("${OS_PASSWORD}") 650 if os_password is None: 651 os_password = "" 652 653 invalid_req_states = [ 654 sub_state 655 for sub_state in req_states 656 if sub_state not in valid_req_states 657 ] 658 if len(invalid_req_states) > 0: 659 error_message = ( 660 "The following req_states are not supported:\n" 661 + gp.sprint_var(invalid_req_states) 662 ) 663 BuiltIn().fail(gp.sprint_error(error_message)) 664 665 # Initialize all substate values supported by this function. 666 ping = 0 667 packet_loss = "" 668 uptime = "" 669 epoch_seconds = "" 670 elapsed_boot_time = "" 671 rest = "" 672 redfish = "" 673 chassis = "" 674 requested_chassis = "" 675 bmc = "" 676 requested_bmc = "" 677 # BootProgress state will get populated when state logic enumerates the 678 # state URI. This is to prevent state dictionary boot_progress value 679 # getting empty when the BootProgress is NOT found, making it optional. 680 boot_progress = "NA" 681 operating_system = "" 682 host = "" 683 requested_host = "" 684 attempts_left = "" 685 686 # Get the component states. 687 if "ping" in req_states: 688 # See if the OS pings. 689 rc, out_buf = gc.shell_cmd( 690 "ping -c 1 -w 2 " + openbmc_host, 691 print_output=0, 692 show_err=0, 693 ignore_err=1, 694 ) 695 if rc == 0: 696 ping = 1 697 698 if "packet_loss" in req_states: 699 # See if the OS pings. 700 cmd_buf = ( 701 "ping -c 5 -w 5 " 702 + openbmc_host 703 + " | egrep 'packet loss' | sed -re 's/.* ([0-9]+)%.*/\\1/g'" 704 ) 705 rc, out_buf = gc.shell_cmd( 706 cmd_buf, print_output=0, show_err=0, ignore_err=1 707 ) 708 if rc == 0: 709 packet_loss = out_buf.rstrip("\n") 710 711 if "uptime" in req_states: 712 # Sometimes reading uptime results in a blank value. Call with 713 # wait_until_keyword_succeeds to ensure a non-blank value is obtained. 714 remote_cmd_buf = ( 715 "bash -c 'read uptime filler 2>/dev/null < /proc/uptime" 716 + ' && [ ! -z "${uptime}" ] && echo ${uptime}\'' 717 ) 718 cmd_buf = [ 719 "BMC Execute Command", 720 re.sub("\\$", "\\$", remote_cmd_buf), 721 "quiet=1", 722 "test_mode=0", 723 "time_out=5", 724 ] 725 gp.qprint_issuing(cmd_buf, 0) 726 gp.qprint_issuing(remote_cmd_buf, 0) 727 try: 728 stdout, stderr, rc = BuiltIn().wait_until_keyword_succeeds( 729 "10 sec", "5 sec", *cmd_buf 730 ) 731 if rc == 0 and stderr == "": 732 uptime = stdout 733 except AssertionError as my_assertion_error: 734 pass 735 736 if "epoch_seconds" in req_states or "elapsed_boot_time" in req_states: 737 date_cmd_buf = "date -u +%s" 738 if USE_BMC_EPOCH_TIME: 739 cmd_buf = ["BMC Execute Command", date_cmd_buf, "quiet=${1}"] 740 if not quiet: 741 gp.print_issuing(cmd_buf) 742 status, ret_values = BuiltIn().run_keyword_and_ignore_error( 743 *cmd_buf 744 ) 745 if status == "PASS": 746 stdout, stderr, rc = ret_values 747 if rc == 0 and stderr == "": 748 epoch_seconds = stdout.rstrip("\n") 749 else: 750 shell_rc, out_buf = gc.cmd_fnc_u( 751 date_cmd_buf, quiet=quiet, print_output=0 752 ) 753 if shell_rc == 0: 754 epoch_seconds = out_buf.rstrip("\n") 755 756 if "elapsed_boot_time" in req_states: 757 global start_boot_seconds 758 elapsed_boot_time = int(epoch_seconds) - start_boot_seconds 759 760 if not redfish_support_trans_state: 761 master_req_rest = [ 762 "rest", 763 "host", 764 "requested_host", 765 "operating_system", 766 "attempts_left", 767 "boot_progress", 768 "chassis", 769 "requested_chassisbmcrequested_bmc", 770 ] 771 772 req_rest = [ 773 sub_state 774 for sub_state in req_states 775 if sub_state in master_req_rest 776 ] 777 need_rest = len(req_rest) > 0 778 state = DotDict() 779 if need_rest: 780 cmd_buf = [ 781 "Read Properties", 782 SYSTEM_STATE_URI + "enumerate", 783 "quiet=${" + str(quiet) + "}", 784 "timeout=30", 785 ] 786 gp.dprint_issuing(cmd_buf) 787 status, ret_values = BuiltIn().run_keyword_and_ignore_error( 788 *cmd_buf 789 ) 790 if status == "PASS": 791 state["rest"] = "1" 792 else: 793 state["rest"] = "0" 794 795 if int(state["rest"]): 796 for url_path in ret_values: 797 # Skip conflicting "CurrentHostState" URL from the enum 798 # /xyz/openbmc_project/state/hypervisor0 799 if "hypervisor0" in url_path: 800 continue 801 802 if platform_arch_type == "x86": 803 # Skip conflicting "CurrentPowerState" URL from the enum 804 # /xyz/openbmc_project/state/chassis_system0 805 if "chassis_system0" in url_path: 806 continue 807 808 for attr_name in ret_values[url_path]: 809 # Create a state key value based on the attr_name. 810 try: 811 ret_values[url_path][attr_name] = re.sub( 812 r".*\.", "", ret_values[url_path][attr_name] 813 ) 814 except TypeError: 815 pass 816 # Do some key name manipulations. 817 new_attr_name = re.sub( 818 r"^Current|(State|Transition)$", "", attr_name 819 ) 820 new_attr_name = re.sub(r"BMC", r"Bmc", new_attr_name) 821 new_attr_name = re.sub( 822 r"([A-Z][a-z])", r"_\1", new_attr_name 823 ) 824 new_attr_name = new_attr_name.lower().lstrip("_") 825 new_attr_name = re.sub( 826 r"power", r"chassis", new_attr_name 827 ) 828 if new_attr_name in req_states: 829 state[new_attr_name] = ret_values[url_path][ 830 attr_name 831 ] 832 else: 833 master_req_rf = [ 834 "redfish", 835 "host", 836 "requested_host", 837 "attempts_left", 838 "boot_progress", 839 "chassis", 840 "requested_chassisbmcrequested_bmc", 841 ] 842 843 req_rf = [ 844 sub_state for sub_state in req_states if sub_state in master_req_rf 845 ] 846 need_rf = len(req_rf) > 0 847 state = DotDict() 848 if need_rf: 849 cmd_buf = ["Redfish Get States"] 850 gp.dprint_issuing(cmd_buf) 851 try: 852 status, ret_values = BuiltIn().run_keyword_and_ignore_error( 853 *cmd_buf 854 ) 855 except Exception as ex: 856 # Robot raised UserKeywordExecutionFailed error exception. 857 gp.dprint_issuing("Retrying Redfish Get States") 858 status, ret_values = BuiltIn().run_keyword_and_ignore_error( 859 *cmd_buf 860 ) 861 862 gp.dprint_vars(status, ret_values) 863 if status == "PASS": 864 state["redfish"] = "1" 865 else: 866 state["redfish"] = "0" 867 868 if int(state["redfish"]): 869 state["chassis"] = ret_values["chassis"] 870 state["host"] = ret_values["host"] 871 state["bmc"] = ret_values["bmc"] 872 if platform_arch_type != "x86": 873 state["boot_progress"] = ret_values["boot_progress"] 874 875 for sub_state in req_states: 876 if sub_state in state: 877 continue 878 if sub_state.startswith("os_"): 879 # We pass "os_" requests on to get_os_state. 880 continue 881 cmd_buf = "state['" + sub_state + "'] = str(" + sub_state + ")" 882 exec(cmd_buf) 883 884 if os_host == "": 885 # The caller has not specified an os_host so as far as we're concerned, 886 # it doesn't exist. 887 return state 888 889 os_req_states = [ 890 sub_state for sub_state in req_states if sub_state.startswith("os_") 891 ] 892 893 if len(os_req_states) > 0: 894 # The caller has specified an os_host and they have requested 895 # information on os substates. 896 897 # Based on the information gathered on bmc, we'll try to make a 898 # determination of whether the os is even up. We'll pass the result 899 # of that assessment to get_os_state to enhance performance. 900 os_up_match = DotDict() 901 for sub_state in master_os_up_match: 902 if sub_state in req_states: 903 os_up_match[sub_state] = master_os_up_match[sub_state] 904 os_up = compare_states(state, os_up_match) 905 os_state = get_os_state( 906 os_host=os_host, 907 os_username=os_username, 908 os_password=os_password, 909 req_states=os_req_states, 910 os_up=os_up, 911 quiet=quiet, 912 ) 913 # Append os_state dictionary to ours. 914 state.update(os_state) 915 916 return state 917 918 919exit_wait_early_message = "" 920 921 922def set_exit_wait_early_message(value): 923 r""" 924 Set global exit_wait_early_message to the indicated value. 925 926 This is a mechanism by which the programmer can do an early exit from 927 wait_until_keyword_succeeds() based on some special condition. 928 929 Description of argument(s): 930 value The value to assign to the global 931 exit_wait_early_message. 932 """ 933 934 global exit_wait_early_message 935 exit_wait_early_message = value 936 937 938def check_state( 939 match_state, 940 invert=0, 941 print_string="", 942 openbmc_host="", 943 openbmc_username="", 944 openbmc_password="", 945 os_host="", 946 os_username="", 947 os_password="", 948 quiet=None, 949): 950 r""" 951 Check that the Open BMC machine's composite state matches the specified 952 state. On success, this keyword returns the machine's composite state as a 953 dictionary. 954 955 Description of argument(s): 956 match_state A dictionary whose key/value pairs are "state field"/ 957 "state value". The state value is interpreted as a 958 regular expression. Example call from robot: 959 ${match_state}= Create Dictionary chassis=^On$ 960 ... bmc=^Ready$ 961 ... boot_progress=^OSStart$ 962 ${state}= Check State &{match_state} 963 invert If this flag is set, this function will succeed if the 964 states do NOT match. 965 print_string This function will print this string to the console prior 966 to getting the state. 967 openbmc_host The DNS name or IP address of the BMC. 968 This defaults to global ${OPENBMC_HOST}. 969 openbmc_username The username to be used to login to the BMC. 970 This defaults to global ${OPENBMC_USERNAME}. 971 openbmc_password The password to be used to login to the BMC. 972 This defaults to global ${OPENBMC_PASSWORD}. 973 os_host The DNS name or IP address of the operating system. 974 This defaults to global ${OS_HOST}. 975 os_username The username to be used to login to the OS. 976 This defaults to global ${OS_USERNAME}. 977 os_password The password to be used to login to the OS. 978 This defaults to global ${OS_PASSWORD}. 979 quiet Indicates whether status details should be written to the 980 console. Defaults to either global value of ${QUIET} or 981 to 1. 982 """ 983 984 quiet = int(gp.get_var_value(quiet, 0)) 985 986 gp.gp_print(print_string) 987 988 try: 989 match_state = return_state_constant(match_state) 990 except TypeError: 991 pass 992 993 req_states = list(match_state.keys()) 994 # Remove special-case match key from req_states. 995 if expressions_key() in req_states: 996 req_states.remove(expressions_key()) 997 # Initialize state. 998 state = get_state( 999 openbmc_host=openbmc_host, 1000 openbmc_username=openbmc_username, 1001 openbmc_password=openbmc_password, 1002 os_host=os_host, 1003 os_username=os_username, 1004 os_password=os_password, 1005 req_states=req_states, 1006 quiet=quiet, 1007 ) 1008 if not quiet: 1009 gp.print_var(state) 1010 1011 if exit_wait_early_message != "": 1012 # The exit_wait_early_message has been set by a signal handler so we 1013 # will exit "successfully". It is incumbent upon the calling function 1014 # (e.g. wait_state) to check/clear this variable and to fail 1015 # appropriately. 1016 return state 1017 1018 match = compare_states(state, match_state) 1019 1020 if invert and match: 1021 fail_msg = ( 1022 "The current state of the machine matches the match" 1023 + " state:\n" 1024 + gp.sprint_varx("state", state) 1025 ) 1026 BuiltIn().fail("\n" + gp.sprint_error(fail_msg)) 1027 elif not invert and not match: 1028 fail_msg = ( 1029 "The current state of the machine does NOT match the" 1030 + " match state:\n" 1031 + gp.sprint_varx("state", state) 1032 ) 1033 BuiltIn().fail("\n" + gp.sprint_error(fail_msg)) 1034 1035 return state 1036 1037 1038def wait_state( 1039 match_state=(), 1040 wait_time="1 min", 1041 interval="1 second", 1042 invert=0, 1043 openbmc_host="", 1044 openbmc_username="", 1045 openbmc_password="", 1046 os_host="", 1047 os_username="", 1048 os_password="", 1049 quiet=None, 1050): 1051 r""" 1052 Wait for the Open BMC machine's composite state to match the specified 1053 state. On success, this keyword returns the machine's composite state as 1054 a dictionary. 1055 1056 Description of argument(s): 1057 match_state A dictionary whose key/value pairs are "state field"/ 1058 "state value". See check_state (above) for details. 1059 This value may also be any string accepted by 1060 return_state_constant (e.g. "standby_match_state"). 1061 In such a case this function will call 1062 return_state_constant to convert it to a proper 1063 dictionary as described above. 1064 wait_time The total amount of time to wait for the desired state. 1065 This value may be expressed in Robot Framework's time 1066 format (e.g. 1 minute, 2 min 3 s, 4.5). 1067 interval The amount of time between state checks. 1068 This value may be expressed in Robot Framework's time 1069 format (e.g. 1 minute, 2 min 3 s, 4.5). 1070 invert If this flag is set, this function will for the state of 1071 the machine to cease to match the match state. 1072 openbmc_host The DNS name or IP address of the BMC. 1073 This defaults to global ${OPENBMC_HOST}. 1074 openbmc_username The username to be used to login to the BMC. 1075 This defaults to global ${OPENBMC_USERNAME}. 1076 openbmc_password The password to be used to login to the BMC. 1077 This defaults to global ${OPENBMC_PASSWORD}. 1078 os_host The DNS name or IP address of the operating system. 1079 This defaults to global ${OS_HOST}. 1080 os_username The username to be used to login to the OS. 1081 This defaults to global ${OS_USERNAME}. 1082 os_password The password to be used to login to the OS. 1083 This defaults to global ${OS_PASSWORD}. 1084 quiet Indicates whether status details should be written to the 1085 console. Defaults to either global value of ${QUIET} or 1086 to 1. 1087 """ 1088 1089 quiet = int(gp.get_var_value(quiet, 0)) 1090 1091 try: 1092 match_state = return_state_constant(match_state) 1093 except TypeError: 1094 pass 1095 1096 if not quiet: 1097 if invert: 1098 alt_text = "cease to " 1099 else: 1100 alt_text = "" 1101 gp.print_timen( 1102 "Checking every " 1103 + str(interval) 1104 + " for up to " 1105 + str(wait_time) 1106 + " for the state of the machine to " 1107 + alt_text 1108 + "match the state shown below." 1109 ) 1110 gp.print_var(match_state) 1111 1112 if quiet: 1113 print_string = "" 1114 else: 1115 print_string = "#" 1116 1117 debug = int(BuiltIn().get_variable_value("${debug}", "0")) 1118 if debug: 1119 # In debug we print state so no need to print the "#". 1120 print_string = "" 1121 check_state_quiet = 1 - debug 1122 cmd_buf = [ 1123 "Check State", 1124 match_state, 1125 "invert=${" + str(invert) + "}", 1126 "print_string=" + print_string, 1127 "openbmc_host=" + openbmc_host, 1128 "openbmc_username=" + openbmc_username, 1129 "openbmc_password=" + openbmc_password, 1130 "os_host=" + os_host, 1131 "os_username=" + os_username, 1132 "os_password=" + os_password, 1133 "quiet=${" + str(check_state_quiet) + "}", 1134 ] 1135 gp.dprint_issuing(cmd_buf) 1136 try: 1137 state = BuiltIn().wait_until_keyword_succeeds( 1138 wait_time, interval, *cmd_buf 1139 ) 1140 except AssertionError as my_assertion_error: 1141 gp.printn() 1142 message = my_assertion_error.args[0] 1143 BuiltIn().fail(message) 1144 1145 if exit_wait_early_message: 1146 # The global exit_wait_early_message was set by a signal handler 1147 # indicating that we should fail. 1148 message = exit_wait_early_message 1149 # Clear the exit_wait_early_message variable for future use. 1150 set_exit_wait_early_message("") 1151 BuiltIn().fail(gp.sprint_error(message)) 1152 1153 if not quiet: 1154 gp.printn() 1155 if invert: 1156 gp.print_timen("The states no longer match:") 1157 else: 1158 gp.print_timen("The states match:") 1159 gp.print_var(state) 1160 1161 return state 1162 1163 1164def set_start_boot_seconds(value=0): 1165 global start_boot_seconds 1166 start_boot_seconds = int(value) 1167 1168 1169set_start_boot_seconds(0) 1170 1171 1172def wait_for_comm_cycle(start_boot_seconds, quiet=None): 1173 r""" 1174 Wait for the BMC uptime to be less than elapsed_boot_time. 1175 1176 This function will tolerate an expected loss of communication to the BMC. 1177 This function is useful when some kind of reboot has been initiated by the 1178 caller. 1179 1180 Description of argument(s): 1181 start_boot_seconds The time that the boot test started. The format is the 1182 epoch time in seconds, i.e. the number of seconds since 1183 1970-01-01 00:00:00 UTC. This value should be obtained 1184 from the BMC so that it is not dependent on any kind of 1185 synchronization between this machine and the target BMC 1186 This will allow this program to work correctly even in 1187 a simulated environment. This value should be obtained 1188 by the caller prior to initiating a reboot. It can be 1189 obtained as follows: 1190 state = st.get_state(req_states=['epoch_seconds']) 1191 """ 1192 1193 quiet = int(gp.get_var_value(quiet, 0)) 1194 1195 # Validate parms. 1196 error_message = gv.valid_integer(start_boot_seconds) 1197 if error_message: 1198 BuiltIn().fail(gp.sprint_error(error_message)) 1199 1200 # Wait for uptime to be less than elapsed_boot_time. 1201 set_start_boot_seconds(start_boot_seconds) 1202 expr = "int(float(state['uptime'])) < int(state['elapsed_boot_time'])" 1203 match_state = DotDict( 1204 [ 1205 ("uptime", "^[0-9\\.]+$"), 1206 ("elapsed_boot_time", "^[0-9]+$"), 1207 (expressions_key(), [expr]), 1208 ] 1209 ) 1210 wait_state(match_state, wait_time="12 mins", interval="5 seconds") 1211 1212 gp.qprint_timen("Verifying that REST/Redfish API interface is working.") 1213 if not redfish_support_trans_state: 1214 match_state = DotDict([("rest", "^1$")]) 1215 else: 1216 match_state = DotDict([("redfish", "^1$")]) 1217 state = wait_state(match_state, wait_time="5 mins", interval="2 seconds") 1218