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