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