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