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