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