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