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 try: 733 status, ret_values = \ 734 BuiltIn().run_keyword_and_ignore_error(*cmd_buf) 735 except Exception as ex: 736 # Robot raised UserKeywordExecutionFailed error exception. 737 gp.dprint_issuing("Retrying Redfish Get States") 738 status, ret_values = \ 739 BuiltIn().run_keyword_and_ignore_error(*cmd_buf) 740 741 gp.dprint_vars(status, ret_values) 742 if status == "PASS": 743 state['redfish'] = '1' 744 else: 745 state['redfish'] = '0' 746 747 if int(state['redfish']): 748 state['chassis'] = ret_values['chassis'] 749 state['boot_progress'] = ret_values['boot_progress'] 750 state['host'] = ret_values['host'] 751 state['bmc'] = ret_values['bmc'] 752 753 for sub_state in req_states: 754 if sub_state in state: 755 continue 756 if sub_state.startswith("os_"): 757 # We pass "os_" requests on to get_os_state. 758 continue 759 cmd_buf = "state['" + sub_state + "'] = str(" + sub_state + ")" 760 exec(cmd_buf) 761 762 if os_host == "": 763 # The caller has not specified an os_host so as far as we're concerned, 764 # it doesn't exist. 765 return state 766 767 os_req_states = [sub_state for sub_state in req_states 768 if sub_state.startswith('os_')] 769 770 if len(os_req_states) > 0: 771 # The caller has specified an os_host and they have requested 772 # information on os substates. 773 774 # Based on the information gathered on bmc, we'll try to make a 775 # determination of whether the os is even up. We'll pass the result 776 # of that assessment to get_os_state to enhance performance. 777 os_up_match = DotDict() 778 for sub_state in master_os_up_match: 779 if sub_state in req_states: 780 os_up_match[sub_state] = master_os_up_match[sub_state] 781 os_up = compare_states(state, os_up_match) 782 os_state = get_os_state(os_host=os_host, 783 os_username=os_username, 784 os_password=os_password, 785 req_states=os_req_states, 786 os_up=os_up, 787 quiet=quiet) 788 # Append os_state dictionary to ours. 789 state.update(os_state) 790 791 return state 792 793 794exit_wait_early_message = "" 795 796 797def set_exit_wait_early_message(value): 798 r""" 799 Set global exit_wait_early_message to the indicated value. 800 801 This is a mechanism by which the programmer can do an early exit from 802 wait_until_keyword_succeeds() based on some special condition. 803 804 Description of argument(s): 805 value The value to assign to the global 806 exit_wait_early_message. 807 """ 808 809 global exit_wait_early_message 810 exit_wait_early_message = value 811 812 813def check_state(match_state, 814 invert=0, 815 print_string="", 816 openbmc_host="", 817 openbmc_username="", 818 openbmc_password="", 819 os_host="", 820 os_username="", 821 os_password="", 822 quiet=None): 823 r""" 824 Check that the Open BMC machine's composite state matches the specified 825 state. On success, this keyword returns the machine's composite state as a 826 dictionary. 827 828 Description of argument(s): 829 match_state A dictionary whose key/value pairs are "state field"/ 830 "state value". The state value is interpreted as a 831 regular expression. Example call from robot: 832 ${match_state}= Create Dictionary chassis=^On$ 833 ... bmc=^Ready$ 834 ... boot_progress=^OSStart$ 835 ${state}= Check State &{match_state} 836 invert If this flag is set, this function will succeed if the 837 states do NOT match. 838 print_string This function will print this string to the console prior 839 to getting the state. 840 openbmc_host The DNS name or IP address of the BMC. 841 This defaults to global ${OPENBMC_HOST}. 842 openbmc_username The username to be used to login to the BMC. 843 This defaults to global ${OPENBMC_USERNAME}. 844 openbmc_password The password to be used to login to the BMC. 845 This defaults to global ${OPENBMC_PASSWORD}. 846 os_host The DNS name or IP address of the operating system. 847 This defaults to global ${OS_HOST}. 848 os_username The username to be used to login to the OS. 849 This defaults to global ${OS_USERNAME}. 850 os_password The password to be used to login to the OS. 851 This defaults to global ${OS_PASSWORD}. 852 quiet Indicates whether status details should be written to the 853 console. Defaults to either global value of ${QUIET} or 854 to 1. 855 """ 856 857 quiet = int(gp.get_var_value(quiet, 0)) 858 859 gp.gp_print(print_string) 860 861 try: 862 match_state = return_state_constant(match_state) 863 except TypeError: 864 pass 865 866 req_states = list(match_state.keys()) 867 # Remove special-case match key from req_states. 868 if expressions_key() in req_states: 869 req_states.remove(expressions_key()) 870 # Initialize state. 871 state = get_state(openbmc_host=openbmc_host, 872 openbmc_username=openbmc_username, 873 openbmc_password=openbmc_password, 874 os_host=os_host, 875 os_username=os_username, 876 os_password=os_password, 877 req_states=req_states, 878 quiet=quiet) 879 if not quiet: 880 gp.print_var(state) 881 882 if exit_wait_early_message != "": 883 # The exit_wait_early_message has been set by a signal handler so we 884 # will exit "successfully". It is incumbent upon the calling function 885 # (e.g. wait_state) to check/clear this variable and to fail 886 # appropriately. 887 return state 888 889 match = compare_states(state, match_state) 890 891 if invert and match: 892 fail_msg = "The current state of the machine matches the match" +\ 893 " state:\n" + gp.sprint_varx("state", state) 894 BuiltIn().fail("\n" + gp.sprint_error(fail_msg)) 895 elif not invert and not match: 896 fail_msg = "The current state of the machine does NOT match the" +\ 897 " match state:\n" +\ 898 gp.sprint_varx("state", state) 899 BuiltIn().fail("\n" + gp.sprint_error(fail_msg)) 900 901 return state 902 903 904def wait_state(match_state=(), 905 wait_time="1 min", 906 interval="1 second", 907 invert=0, 908 openbmc_host="", 909 openbmc_username="", 910 openbmc_password="", 911 os_host="", 912 os_username="", 913 os_password="", 914 quiet=None): 915 r""" 916 Wait for the Open BMC machine's composite state to match the specified 917 state. On success, this keyword returns the machine's composite state as 918 a dictionary. 919 920 Description of argument(s): 921 match_state A dictionary whose key/value pairs are "state field"/ 922 "state value". See check_state (above) for details. 923 This value may also be any string accepted by 924 return_state_constant (e.g. "standby_match_state"). 925 In such a case this function will call 926 return_state_constant to convert it to a proper 927 dictionary as described above. 928 wait_time The total amount of time to wait for the desired state. 929 This value may be expressed in Robot Framework's time 930 format (e.g. 1 minute, 2 min 3 s, 4.5). 931 interval The amount of time between state checks. 932 This value may be expressed in Robot Framework's time 933 format (e.g. 1 minute, 2 min 3 s, 4.5). 934 invert If this flag is set, this function will for the state of 935 the machine to cease to match the match state. 936 openbmc_host The DNS name or IP address of the BMC. 937 This defaults to global ${OPENBMC_HOST}. 938 openbmc_username The username to be used to login to the BMC. 939 This defaults to global ${OPENBMC_USERNAME}. 940 openbmc_password The password to be used to login to the BMC. 941 This defaults to global ${OPENBMC_PASSWORD}. 942 os_host The DNS name or IP address of the operating system. 943 This defaults to global ${OS_HOST}. 944 os_username The username to be used to login to the OS. 945 This defaults to global ${OS_USERNAME}. 946 os_password The password to be used to login to the OS. 947 This defaults to global ${OS_PASSWORD}. 948 quiet Indicates whether status details should be written to the 949 console. Defaults to either global value of ${QUIET} or 950 to 1. 951 """ 952 953 quiet = int(gp.get_var_value(quiet, 0)) 954 955 try: 956 match_state = return_state_constant(match_state) 957 except TypeError: 958 pass 959 960 if not quiet: 961 if invert: 962 alt_text = "cease to " 963 else: 964 alt_text = "" 965 gp.print_timen("Checking every " + str(interval) + " for up to " 966 + str(wait_time) + " for the state of the machine to " 967 + alt_text + "match the state shown below.") 968 gp.print_var(match_state) 969 970 if quiet: 971 print_string = "" 972 else: 973 print_string = "#" 974 975 debug = int(BuiltIn().get_variable_value("${debug}", "0")) 976 if debug: 977 # In debug we print state so no need to print the "#". 978 print_string = "" 979 check_state_quiet = 1 - debug 980 cmd_buf = ["Check State", match_state, "invert=${" + str(invert) + "}", 981 "print_string=" + print_string, "openbmc_host=" + openbmc_host, 982 "openbmc_username=" + openbmc_username, 983 "openbmc_password=" + openbmc_password, "os_host=" + os_host, 984 "os_username=" + os_username, "os_password=" + os_password, 985 "quiet=${" + str(check_state_quiet) + "}"] 986 gp.dprint_issuing(cmd_buf) 987 try: 988 state = BuiltIn().wait_until_keyword_succeeds(wait_time, interval, 989 *cmd_buf) 990 except AssertionError as my_assertion_error: 991 gp.printn() 992 message = my_assertion_error.args[0] 993 BuiltIn().fail(message) 994 995 if exit_wait_early_message: 996 # The global exit_wait_early_message was set by a signal handler 997 # indicating that we should fail. 998 message = exit_wait_early_message 999 # Clear the exit_wait_early_message variable for future use. 1000 set_exit_wait_early_message("") 1001 BuiltIn().fail(gp.sprint_error(message)) 1002 1003 if not quiet: 1004 gp.printn() 1005 if invert: 1006 gp.print_timen("The states no longer match:") 1007 else: 1008 gp.print_timen("The states match:") 1009 gp.print_var(state) 1010 1011 return state 1012 1013 1014def set_start_boot_seconds(value=0): 1015 global start_boot_seconds 1016 start_boot_seconds = int(value) 1017 1018 1019set_start_boot_seconds(0) 1020 1021 1022def wait_for_comm_cycle(start_boot_seconds, 1023 quiet=None): 1024 r""" 1025 Wait for the BMC uptime to be less than elapsed_boot_time. 1026 1027 This function will tolerate an expected loss of communication to the BMC. 1028 This function is useful when some kind of reboot has been initiated by the 1029 caller. 1030 1031 Description of argument(s): 1032 start_boot_seconds The time that the boot test started. The format is the 1033 epoch time in seconds, i.e. the number of seconds since 1034 1970-01-01 00:00:00 UTC. This value should be obtained 1035 from the BMC so that it is not dependent on any kind of 1036 synchronization between this machine and the target BMC 1037 This will allow this program to work correctly even in 1038 a simulated environment. This value should be obtained 1039 by the caller prior to initiating a reboot. It can be 1040 obtained as follows: 1041 state = st.get_state(req_states=['epoch_seconds']) 1042 """ 1043 1044 quiet = int(gp.get_var_value(quiet, 0)) 1045 1046 # Validate parms. 1047 error_message = gv.valid_integer(start_boot_seconds) 1048 if error_message: 1049 BuiltIn().fail(gp.sprint_error(error_message)) 1050 1051 # Wait for uptime to be less than elapsed_boot_time. 1052 set_start_boot_seconds(start_boot_seconds) 1053 expr = 'int(float(state[\'uptime\'])) < int(state[\'elapsed_boot_time\'])' 1054 match_state = DotDict([('uptime', '^[0-9\\.]+$'), 1055 ('elapsed_boot_time', '^[0-9]+$'), 1056 (expressions_key(), [expr])]) 1057 wait_state(match_state, wait_time="12 mins", interval="5 seconds") 1058 1059 gp.qprint_timen("Verifying that REST/Redfish API interface is working.") 1060 if not redfish_support_trans_state: 1061 match_state = DotDict([('rest', '^1$')]) 1062 else: 1063 match_state = DotDict([('redfish', '^1$')]) 1064 state = wait_state(match_state, wait_time="5 mins", interval="2 seconds") 1065