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 if rc == 0: 467 os_login = 1 468 os_run_cmd = 1 469 else: 470 gp.dprint_vars(output, stderr) 471 gp.dprint_vars(rc, 1) 472 473 os_state = DotDict() 474 for sub_state in req_states: 475 cmd_buf = "os_state['" + sub_state + "'] = str(" + sub_state + ")" 476 exec(cmd_buf) 477 478 return os_state 479 480 481def get_state(openbmc_host="", 482 openbmc_username="", 483 openbmc_password="", 484 os_host="", 485 os_username="", 486 os_password="", 487 req_states=default_req_states, 488 quiet=None): 489 r""" 490 Get component states such as chassis state, bmc state, etc, put them into a 491 dictionary and return them to the caller. 492 493 Note that all substate values are strings. 494 495 Note: If elapsed_boot_time is included in req_states, it is the caller's 496 duty to call set_start_boot_seconds() in order to set global 497 start_boot_seconds. elapsed_boot_time is the current time minus 498 start_boot_seconds. 499 500 Description of argument(s): 501 openbmc_host The DNS name or IP address of the BMC. 502 This defaults to global ${OPENBMC_HOST}. 503 openbmc_username The username to be used to login to the BMC. 504 This defaults to global ${OPENBMC_USERNAME}. 505 openbmc_password The password to be used to login to the BMC. 506 This defaults to global ${OPENBMC_PASSWORD}. 507 os_host The DNS name or IP address of the operating system. 508 This defaults to global ${OS_HOST}. 509 os_username The username to be used to login to the OS. 510 This defaults to global ${OS_USERNAME}. 511 os_password The password to be used to login to the OS. 512 This defaults to global ${OS_PASSWORD}. 513 req_states This is a list of states whose values are being requested 514 by the caller. 515 quiet Indicates whether status details (e.g. curl commands) 516 should be written to the console. 517 Defaults to either global value of ${QUIET} or to 1. 518 """ 519 520 quiet = int(gp.get_var_value(quiet, 0)) 521 522 # Set parm defaults where necessary and validate all parms. 523 if openbmc_host == "": 524 openbmc_host = BuiltIn().get_variable_value("${OPENBMC_HOST}") 525 error_message = gv.valid_value(openbmc_host, invalid_values=[None, ""]) 526 if error_message != "": 527 BuiltIn().fail(gp.sprint_error(error_message)) 528 529 if openbmc_username == "": 530 openbmc_username = BuiltIn().get_variable_value("${OPENBMC_USERNAME}") 531 error_message = gv.valid_value(openbmc_username, invalid_values=[None, ""]) 532 if error_message != "": 533 BuiltIn().fail(gp.sprint_error(error_message)) 534 535 if openbmc_password == "": 536 openbmc_password = BuiltIn().get_variable_value("${OPENBMC_PASSWORD}") 537 error_message = gv.valid_value(openbmc_password, invalid_values=[None, ""]) 538 if error_message != "": 539 BuiltIn().fail(gp.sprint_error(error_message)) 540 541 # NOTE: OS parms are optional. 542 if os_host == "": 543 os_host = BuiltIn().get_variable_value("${OS_HOST}") 544 if os_host is None: 545 os_host = "" 546 547 if os_username is "": 548 os_username = BuiltIn().get_variable_value("${OS_USERNAME}") 549 if os_username is None: 550 os_username = "" 551 552 if os_password is "": 553 os_password = BuiltIn().get_variable_value("${OS_PASSWORD}") 554 if os_password is None: 555 os_password = "" 556 557 invalid_req_states = [sub_state for sub_state in req_states 558 if sub_state not in valid_req_states] 559 if len(invalid_req_states) > 0: 560 error_message = "The following req_states are not supported:\n" +\ 561 gp.sprint_var(invalid_req_states) 562 BuiltIn().fail(gp.sprint_error(error_message)) 563 564 # Initialize all substate values supported by this function. 565 ping = 0 566 packet_loss = '' 567 uptime = '' 568 epoch_seconds = '' 569 elapsed_boot_time = '' 570 rest = '' 571 redfish = '' 572 chassis = '' 573 requested_chassis = '' 574 bmc = '' 575 requested_bmc = '' 576 boot_progress = '' 577 operating_system = '' 578 host = '' 579 requested_host = '' 580 attempts_left = '' 581 582 # Get the component states. 583 if 'ping' in req_states: 584 # See if the OS pings. 585 rc, out_buf = gc.shell_cmd("ping -c 1 -w 2 " + openbmc_host, 586 print_output=0, show_err=0, 587 ignore_err=1) 588 if rc == 0: 589 ping = 1 590 591 if 'packet_loss' in req_states: 592 # See if the OS pings. 593 cmd_buf = "ping -c 5 -w 5 " + openbmc_host +\ 594 " | egrep 'packet loss' | sed -re 's/.* ([0-9]+)%.*/\\1/g'" 595 rc, out_buf = gc.shell_cmd(cmd_buf, 596 print_output=0, show_err=0, 597 ignore_err=1) 598 if rc == 0: 599 packet_loss = out_buf.rstrip("\n") 600 601 if 'uptime' in req_states: 602 # Sometimes reading uptime results in a blank value. Call with 603 # wait_until_keyword_succeeds to ensure a non-blank value is obtained. 604 remote_cmd_buf = "read uptime filler 2>/dev/null < /proc/uptime" +\ 605 " && [ ! -z \"${uptime}\" ] && echo ${uptime}" 606 cmd_buf = ["BMC Execute Command", 607 re.sub('\\$', '\\$', remote_cmd_buf), 'quiet=1', 608 'test_mode=0'] 609 gp.qprint_issuing(cmd_buf, 0) 610 gp.qprint_issuing(remote_cmd_buf, 0) 611 try: 612 stdout, stderr, rc =\ 613 BuiltIn().wait_until_keyword_succeeds("10 sec", "0 sec", 614 *cmd_buf) 615 if rc == 0 and stderr == "": 616 uptime = stdout 617 except AssertionError as my_assertion_error: 618 pass 619 620 if 'epoch_seconds' in req_states or 'elapsed_boot_time' in req_states: 621 date_cmd_buf = "date -u +%s" 622 if USE_BMC_EPOCH_TIME: 623 cmd_buf = ["BMC Execute Command", date_cmd_buf, 'quiet=${1}'] 624 if not quiet: 625 gp.print_issuing(cmd_buf) 626 status, ret_values = \ 627 BuiltIn().run_keyword_and_ignore_error(*cmd_buf) 628 if status == "PASS": 629 stdout, stderr, rc = ret_values 630 if rc == 0 and stderr == "": 631 epoch_seconds = stdout.rstrip("\n") 632 else: 633 shell_rc, out_buf = gc.cmd_fnc_u(date_cmd_buf, 634 quiet=quiet, 635 print_output=0) 636 if shell_rc == 0: 637 epoch_seconds = out_buf.rstrip("\n") 638 639 if 'elapsed_boot_time' in req_states: 640 global start_boot_seconds 641 elapsed_boot_time = int(epoch_seconds) - start_boot_seconds 642 643 if not redfish_support_trans_state: 644 master_req_rest = ['rest', 'host', 'requested_host', 'operating_system', 645 'attempts_left', 'boot_progress', 'chassis', 646 'requested_chassis' 'bmc' 'requested_bmc'] 647 648 req_rest = [sub_state for sub_state in req_states if sub_state in 649 master_req_rest] 650 need_rest = (len(req_rest) > 0) 651 state = DotDict() 652 if need_rest: 653 cmd_buf = ["Read Properties", SYSTEM_STATE_URI + "enumerate", 654 "quiet=${" + str(quiet) + "}", "timeout=30"] 655 gp.dprint_issuing(cmd_buf) 656 status, ret_values = \ 657 BuiltIn().run_keyword_and_ignore_error(*cmd_buf) 658 if status == "PASS": 659 state['rest'] = '1' 660 else: 661 state['rest'] = '0' 662 663 if int(state['rest']): 664 for url_path in ret_values: 665 for attr_name in ret_values[url_path]: 666 # Create a state key value based on the attr_name. 667 try: 668 ret_values[url_path][attr_name] = \ 669 re.sub(r'.*\.', "", 670 ret_values[url_path][attr_name]) 671 except TypeError: 672 pass 673 # Do some key name manipulations. 674 new_attr_name = re.sub(r'^Current|(State|Transition)$', 675 "", attr_name) 676 new_attr_name = re.sub(r'BMC', r'Bmc', new_attr_name) 677 new_attr_name = re.sub(r'([A-Z][a-z])', r'_\1', 678 new_attr_name) 679 new_attr_name = new_attr_name.lower().lstrip("_") 680 new_attr_name = re.sub(r'power', r'chassis', new_attr_name) 681 if new_attr_name in req_states: 682 state[new_attr_name] = ret_values[url_path][attr_name] 683 else: 684 master_req_rf = ['redfish', 'host', 'requested_host', 685 'attempts_left', 'boot_progress', 'chassis', 686 'requested_chassis' 'bmc' 'requested_bmc'] 687 688 req_rf = [sub_state for sub_state in req_states if sub_state in 689 master_req_rf] 690 need_rf = (len(req_rf) > 0) 691 state = DotDict() 692 if need_rf: 693 cmd_buf = ["Redfish Get States"] 694 gp.dprint_issuing(cmd_buf) 695 status, ret_values = \ 696 BuiltIn().run_keyword_and_ignore_error(*cmd_buf) 697 gp.dprint_vars(status, ret_values) 698 if status == "PASS": 699 state['redfish'] = '1' 700 else: 701 state['redfish'] = '0' 702 703 if int(state['redfish']): 704 state['chassis'] = ret_values['chassis'] 705 state['boot_progress'] = ret_values['boot_progress'] 706 state['host'] = ret_values['host'] 707 state['bmc'] = ret_values['bmc'] 708 709 for sub_state in req_states: 710 if sub_state in state: 711 continue 712 if sub_state.startswith("os_"): 713 # We pass "os_" requests on to get_os_state. 714 continue 715 cmd_buf = "state['" + sub_state + "'] = str(" + sub_state + ")" 716 exec(cmd_buf) 717 718 if os_host == "": 719 # The caller has not specified an os_host so as far as we're concerned, 720 # it doesn't exist. 721 return state 722 723 os_req_states = [sub_state for sub_state in req_states 724 if sub_state.startswith('os_')] 725 726 if len(os_req_states) > 0: 727 # The caller has specified an os_host and they have requested 728 # information on os substates. 729 730 # Based on the information gathered on bmc, we'll try to make a 731 # determination of whether the os is even up. We'll pass the result 732 # of that assessment to get_os_state to enhance performance. 733 os_up_match = DotDict() 734 for sub_state in master_os_up_match: 735 if sub_state in req_states: 736 os_up_match[sub_state] = master_os_up_match[sub_state] 737 os_up = compare_states(state, os_up_match) 738 os_state = get_os_state(os_host=os_host, 739 os_username=os_username, 740 os_password=os_password, 741 req_states=os_req_states, 742 os_up=os_up, 743 quiet=quiet) 744 # Append os_state dictionary to ours. 745 state.update(os_state) 746 747 return state 748 749 750exit_wait_early_message = "" 751 752 753def set_exit_wait_early_message(value): 754 r""" 755 Set global exit_wait_early_message to the indicated value. 756 757 This is a mechanism by which the programmer can do an early exit from 758 wait_until_keyword_succeeds() based on some special condition. 759 760 Description of argument(s): 761 value The value to assign to the global 762 exit_wait_early_message. 763 """ 764 765 global exit_wait_early_message 766 exit_wait_early_message = value 767 768 769def check_state(match_state, 770 invert=0, 771 print_string="", 772 openbmc_host="", 773 openbmc_username="", 774 openbmc_password="", 775 os_host="", 776 os_username="", 777 os_password="", 778 quiet=None): 779 r""" 780 Check that the Open BMC machine's composite state matches the specified 781 state. On success, this keyword returns the machine's composite state as a 782 dictionary. 783 784 Description of argument(s): 785 match_state A dictionary whose key/value pairs are "state field"/ 786 "state value". The state value is interpreted as a 787 regular expression. Example call from robot: 788 ${match_state}= Create Dictionary chassis=^On$ 789 ... bmc=^Ready$ 790 ... boot_progress=^OSStart$ 791 ${state}= Check State &{match_state} 792 invert If this flag is set, this function will succeed if the 793 states do NOT match. 794 print_string This function will print this string to the console prior 795 to getting the state. 796 openbmc_host The DNS name or IP address of the BMC. 797 This defaults to global ${OPENBMC_HOST}. 798 openbmc_username The username to be used to login to the BMC. 799 This defaults to global ${OPENBMC_USERNAME}. 800 openbmc_password The password to be used to login to the BMC. 801 This defaults to global ${OPENBMC_PASSWORD}. 802 os_host The DNS name or IP address of the operating system. 803 This defaults to global ${OS_HOST}. 804 os_username The username to be used to login to the OS. 805 This defaults to global ${OS_USERNAME}. 806 os_password The password to be used to login to the OS. 807 This defaults to global ${OS_PASSWORD}. 808 quiet Indicates whether status details should be written to the 809 console. Defaults to either global value of ${QUIET} or 810 to 1. 811 """ 812 813 quiet = int(gp.get_var_value(quiet, 0)) 814 815 gp.gp_print(print_string) 816 817 try: 818 match_state = return_state_constant(match_state) 819 except TypeError: 820 pass 821 822 req_states = list(match_state.keys()) 823 # Remove special-case match key from req_states. 824 if expressions_key() in req_states: 825 req_states.remove(expressions_key()) 826 # Initialize state. 827 state = get_state(openbmc_host=openbmc_host, 828 openbmc_username=openbmc_username, 829 openbmc_password=openbmc_password, 830 os_host=os_host, 831 os_username=os_username, 832 os_password=os_password, 833 req_states=req_states, 834 quiet=quiet) 835 if not quiet: 836 gp.print_var(state) 837 838 if exit_wait_early_message != "": 839 # The exit_wait_early_message has been set by a signal handler so we 840 # will exit "successfully". It is incumbent upon the calling function 841 # (e.g. wait_state) to check/clear this variable and to fail 842 # appropriately. 843 return state 844 845 match = compare_states(state, match_state) 846 847 if invert and match: 848 fail_msg = "The current state of the machine matches the match" +\ 849 " state:\n" + gp.sprint_varx("state", state) 850 BuiltIn().fail("\n" + gp.sprint_error(fail_msg)) 851 elif not invert and not match: 852 fail_msg = "The current state of the machine does NOT match the" +\ 853 " match state:\n" +\ 854 gp.sprint_varx("state", state) 855 BuiltIn().fail("\n" + gp.sprint_error(fail_msg)) 856 857 return state 858 859 860def wait_state(match_state=(), 861 wait_time="1 min", 862 interval="1 second", 863 invert=0, 864 openbmc_host="", 865 openbmc_username="", 866 openbmc_password="", 867 os_host="", 868 os_username="", 869 os_password="", 870 quiet=None): 871 r""" 872 Wait for the Open BMC machine's composite state to match the specified 873 state. On success, this keyword returns the machine's composite state as 874 a dictionary. 875 876 Description of argument(s): 877 match_state A dictionary whose key/value pairs are "state field"/ 878 "state value". See check_state (above) for details. 879 This value may also be any string accepted by 880 return_state_constant (e.g. "standby_match_state"). 881 In such a case this function will call 882 return_state_constant to convert it to a proper 883 dictionary as described above. 884 wait_time The total amount of time to wait for the desired state. 885 This value may be expressed in Robot Framework's time 886 format (e.g. 1 minute, 2 min 3 s, 4.5). 887 interval The amount of time between state checks. 888 This value may be expressed in Robot Framework's time 889 format (e.g. 1 minute, 2 min 3 s, 4.5). 890 invert If this flag is set, this function will for the state of 891 the machine to cease to match the match state. 892 openbmc_host The DNS name or IP address of the BMC. 893 This defaults to global ${OPENBMC_HOST}. 894 openbmc_username The username to be used to login to the BMC. 895 This defaults to global ${OPENBMC_USERNAME}. 896 openbmc_password The password to be used to login to the BMC. 897 This defaults to global ${OPENBMC_PASSWORD}. 898 os_host The DNS name or IP address of the operating system. 899 This defaults to global ${OS_HOST}. 900 os_username The username to be used to login to the OS. 901 This defaults to global ${OS_USERNAME}. 902 os_password The password to be used to login to the OS. 903 This defaults to global ${OS_PASSWORD}. 904 quiet Indicates whether status details should be written to the 905 console. Defaults to either global value of ${QUIET} or 906 to 1. 907 """ 908 909 quiet = int(gp.get_var_value(quiet, 0)) 910 911 try: 912 match_state = return_state_constant(match_state) 913 except TypeError: 914 pass 915 916 if not quiet: 917 if invert: 918 alt_text = "cease to " 919 else: 920 alt_text = "" 921 gp.print_timen("Checking every " + str(interval) + " for up to " 922 + str(wait_time) + " for the state of the machine to " 923 + alt_text + "match the state shown below.") 924 gp.print_var(match_state) 925 926 if quiet: 927 print_string = "" 928 else: 929 print_string = "#" 930 931 debug = int(BuiltIn().get_variable_value("${debug}", "0")) 932 if debug: 933 # In debug we print state so no need to print the "#". 934 print_string = "" 935 check_state_quiet = 1 - debug 936 cmd_buf = ["Check State", match_state, "invert=${" + str(invert) + "}", 937 "print_string=" + print_string, "openbmc_host=" + openbmc_host, 938 "openbmc_username=" + openbmc_username, 939 "openbmc_password=" + openbmc_password, "os_host=" + os_host, 940 "os_username=" + os_username, "os_password=" + os_password, 941 "quiet=${" + str(check_state_quiet) + "}"] 942 gp.dprint_issuing(cmd_buf) 943 try: 944 state = BuiltIn().wait_until_keyword_succeeds(wait_time, interval, 945 *cmd_buf) 946 except AssertionError as my_assertion_error: 947 gp.printn() 948 message = my_assertion_error.args[0] 949 BuiltIn().fail(message) 950 951 if exit_wait_early_message: 952 # The global exit_wait_early_message was set by a signal handler 953 # indicating that we should fail. 954 message = exit_wait_early_message 955 # Clear the exit_wait_early_message variable for future use. 956 set_exit_wait_early_message("") 957 BuiltIn().fail(gp.sprint_error(message)) 958 959 if not quiet: 960 gp.printn() 961 if invert: 962 gp.print_timen("The states no longer match:") 963 else: 964 gp.print_timen("The states match:") 965 gp.print_var(state) 966 967 return state 968 969 970def set_start_boot_seconds(value=0): 971 global start_boot_seconds 972 start_boot_seconds = int(value) 973 974 975set_start_boot_seconds(0) 976 977 978def wait_for_comm_cycle(start_boot_seconds, 979 quiet=None): 980 r""" 981 Wait for the BMC uptime to be less than elapsed_boot_time. 982 983 This function will tolerate an expected loss of communication to the BMC. 984 This function is useful when some kind of reboot has been initiated by the 985 caller. 986 987 Description of argument(s): 988 start_boot_seconds The time that the boot test started. The format is the 989 epoch time in seconds, i.e. the number of seconds since 990 1970-01-01 00:00:00 UTC. This value should be obtained 991 from the BMC so that it is not dependent on any kind of 992 synchronization between this machine and the target BMC 993 This will allow this program to work correctly even in 994 a simulated environment. This value should be obtained 995 by the caller prior to initiating a reboot. It can be 996 obtained as follows: 997 state = st.get_state(req_states=['epoch_seconds']) 998 """ 999 1000 quiet = int(gp.get_var_value(quiet, 0)) 1001 1002 # Validate parms. 1003 error_message = gv.valid_integer(start_boot_seconds) 1004 if error_message: 1005 BuiltIn().fail(gp.sprint_error(error_message)) 1006 1007 # Wait for uptime to be less than elapsed_boot_time. 1008 set_start_boot_seconds(start_boot_seconds) 1009 expr = 'int(float(state[\'uptime\'])) < int(state[\'elapsed_boot_time\'])' 1010 match_state = DotDict([('uptime', '^[0-9\\.]+$'), 1011 ('elapsed_boot_time', '^[0-9]+$'), 1012 (expressions_key(), [expr])]) 1013 wait_state(match_state, wait_time="12 mins", interval="5 seconds") 1014 1015 gp.qprint_timen("Verifying that REST/Redfish API interface is working.") 1016 if not redfish_support_trans_state: 1017 match_state = DotDict([('rest', '^1$')]) 1018 else: 1019 match_state = DotDict([('redfish', '^1$')]) 1020 state = wait_state(match_state, wait_time="5 mins", interval="2 seconds") 1021