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 if rc == 0: 367 os_login = 1 368 os_run_cmd = 1 369 else: 370 gp.dprint_vars(output, stderr) 371 gp.dprint_vars(rc, 1) 372 373 os_state = DotDict() 374 for sub_state in req_states: 375 cmd_buf = "os_state['" + sub_state + "'] = str(" + sub_state + ")" 376 exec(cmd_buf) 377 378 return os_state 379 380 381def get_state(openbmc_host="", 382 openbmc_username="", 383 openbmc_password="", 384 os_host="", 385 os_username="", 386 os_password="", 387 req_states=default_req_states, 388 quiet=None): 389 r""" 390 Get component states such as chassis state, bmc state, etc, put them into a 391 dictionary and return them to the caller. 392 393 Note that all substate values are strings. 394 395 Description of arguments: 396 openbmc_host The DNS name or IP address of the BMC. 397 This defaults to global ${OPENBMC_HOST}. 398 openbmc_username The username to be used to login to the BMC. 399 This defaults to global ${OPENBMC_USERNAME}. 400 openbmc_password The password to be used to login to the BMC. 401 This defaults to global ${OPENBMC_PASSWORD}. 402 os_host The DNS name or IP address of the operating system. 403 This defaults to global ${OS_HOST}. 404 os_username The username to be used to login to the OS. 405 This defaults to global ${OS_USERNAME}. 406 os_password The password to be used to login to the OS. 407 This defaults to global ${OS_PASSWORD}. 408 req_states This is a list of states whose values are being requested 409 by the caller. 410 quiet Indicates whether status details (e.g. curl commands) 411 should be written to the console. 412 Defaults to either global value of ${QUIET} or to 1. 413 """ 414 415 quiet = int(gp.get_var_value(quiet, 0)) 416 417 # Set parm defaults where necessary and validate all parms. 418 if openbmc_host == "": 419 openbmc_host = BuiltIn().get_variable_value("${OPENBMC_HOST}") 420 error_message = gv.svalid_value(openbmc_host, 421 var_name="openbmc_host", 422 invalid_values=[None, ""]) 423 if error_message != "": 424 BuiltIn().fail(gp.sprint_error(error_message)) 425 426 if openbmc_username == "": 427 openbmc_username = BuiltIn().get_variable_value("${OPENBMC_USERNAME}") 428 error_message = gv.svalid_value(openbmc_username, 429 var_name="openbmc_username", 430 invalid_values=[None, ""]) 431 if error_message != "": 432 BuiltIn().fail(gp.sprint_error(error_message)) 433 434 if openbmc_password == "": 435 openbmc_password = BuiltIn().get_variable_value("${OPENBMC_PASSWORD}") 436 error_message = gv.svalid_value(openbmc_password, 437 var_name="openbmc_password", 438 invalid_values=[None, ""]) 439 if error_message != "": 440 BuiltIn().fail(gp.sprint_error(error_message)) 441 442 # NOTE: OS parms are optional. 443 if os_host == "": 444 os_host = BuiltIn().get_variable_value("${OS_HOST}") 445 if os_host is None: 446 os_host = "" 447 448 if os_username is "": 449 os_username = BuiltIn().get_variable_value("${OS_USERNAME}") 450 if os_username is None: 451 os_username = "" 452 453 if os_password is "": 454 os_password = BuiltIn().get_variable_value("${OS_PASSWORD}") 455 if os_password is None: 456 os_password = "" 457 458 invalid_req_states = [sub_state for sub_state in req_states 459 if sub_state not in valid_req_states] 460 if len(invalid_req_states) > 0: 461 error_message = "The following req_states are not supported:\n" +\ 462 gp.sprint_var(invalid_req_states) 463 BuiltIn().fail(gp.sprint_error(error_message)) 464 465 # Initialize all substate values supported by this function. 466 ping = 0 467 packet_loss = '' 468 uptime = '' 469 epoch_seconds = '' 470 rest = '' 471 chassis = '' 472 requested_chassis = '' 473 bmc = '' 474 requested_bmc = '' 475 boot_progress = '' 476 operating_system = '' 477 host = '' 478 requested_host = '' 479 attempts_left = '' 480 481 # Get the component states. 482 if 'ping' in req_states: 483 # See if the OS pings. 484 cmd_buf = "ping -c 1 -w 2 " + openbmc_host 485 if not quiet: 486 gp.pissuing(cmd_buf) 487 rc, out_buf = commands.getstatusoutput(cmd_buf) 488 if rc == 0: 489 ping = 1 490 491 if 'packet_loss' in req_states: 492 # See if the OS pings. 493 cmd_buf = "ping -c 5 -w 5 " + openbmc_host +\ 494 " | egrep 'packet loss' | sed -re 's/.* ([0-9]+)%.*/\\1/g'" 495 if not quiet: 496 gp.pissuing(cmd_buf) 497 rc, out_buf = commands.getstatusoutput(cmd_buf) 498 if rc == 0: 499 packet_loss = out_buf.rstrip("\n") 500 501 if 'uptime' in req_states: 502 # Sometimes reading uptime results in a blank value. Call with 503 # wait_until_keyword_succeeds to ensure a non-blank value is obtained. 504 remote_cmd_buf = "read uptime filler 2>/dev/null < /proc/uptime" +\ 505 " && [ ! -z \"${uptime}\" ] && echo ${uptime}" 506 cmd_buf = ["BMC Execute Command", 507 re.sub('\\$', '\\$', remote_cmd_buf), 'quiet=1'] 508 if not quiet: 509 grp.rpissuing_keyword(cmd_buf) 510 grp.rpissuing(remote_cmd_buf) 511 try: 512 stdout, stderr, rc =\ 513 BuiltIn().wait_until_keyword_succeeds("10 sec", "0 sec", 514 *cmd_buf) 515 if rc == 0 and stderr == "": 516 uptime = stdout 517 except AssertionError as my_assertion_error: 518 pass 519 520 if 'epoch_seconds' in req_states: 521 date_cmd_buf = "date -u +%s" 522 if USE_BMC_EPOCH_TIME: 523 cmd_buf = ["BMC Execute Command", date_cmd_buf, 'quiet=${1}'] 524 if not quiet: 525 grp.rpissuing_keyword(cmd_buf) 526 status, ret_values = \ 527 BuiltIn().run_keyword_and_ignore_error(*cmd_buf) 528 if status == "PASS": 529 stdout, stderr, rc = ret_values 530 if rc == 0 and stderr == "": 531 epoch_seconds = stdout.rstrip("\n") 532 else: 533 shell_rc, out_buf = gc.cmd_fnc_u(date_cmd_buf, 534 quiet=quiet, 535 print_output=0) 536 if shell_rc == 0: 537 epoch_seconds = out_buf.rstrip("\n") 538 539 master_req_rest = ['rest', 'host', 'requested_host', 'operating_system', 540 'attempts_left', 'boot_progress', 'chassis', 541 'requested_chassis' 'bmc' 'requested_bmc'] 542 543 req_rest = [sub_state for sub_state in req_states if sub_state in 544 master_req_rest] 545 need_rest = (len(req_rest) > 0) 546 state = DotDict() 547 if need_rest: 548 cmd_buf = ["Read Properties", SYSTEM_STATE_URI + "enumerate", 549 "quiet=${" + str(quiet) + "}"] 550 grp.rdpissuing_keyword(cmd_buf) 551 status, ret_values = \ 552 BuiltIn().run_keyword_and_ignore_error(*cmd_buf) 553 if status == "PASS": 554 state['rest'] = '1' 555 else: 556 state['rest'] = '0' 557 558 if int(state['rest']): 559 for url_path in ret_values: 560 for attr_name in ret_values[url_path]: 561 # Create a state key value based on the attr_name. 562 if isinstance(ret_values[url_path][attr_name], unicode): 563 ret_values[url_path][attr_name] = \ 564 re.sub(r'.*\.', "", 565 ret_values[url_path][attr_name]) 566 # Do some key name manipulations. 567 new_attr_name = re.sub(r'^Current|(State|Transition)$', 568 "", attr_name) 569 new_attr_name = re.sub(r'BMC', r'Bmc', new_attr_name) 570 new_attr_name = re.sub(r'([A-Z][a-z])', r'_\1', 571 new_attr_name) 572 new_attr_name = new_attr_name.lower().lstrip("_") 573 new_attr_name = re.sub(r'power', r'chassis', new_attr_name) 574 if new_attr_name in req_states: 575 state[new_attr_name] = ret_values[url_path][attr_name] 576 577 for sub_state in req_states: 578 if sub_state in state: 579 continue 580 if sub_state.startswith("os_"): 581 # We pass "os_" requests on to get_os_state. 582 continue 583 cmd_buf = "state['" + sub_state + "'] = str(" + sub_state + ")" 584 exec(cmd_buf) 585 586 if os_host == "": 587 # The caller has not specified an os_host so as far as we're concerned, 588 # it doesn't exist. 589 return state 590 591 os_req_states = [sub_state for sub_state in req_states 592 if sub_state.startswith('os_')] 593 594 if len(os_req_states) > 0: 595 # The caller has specified an os_host and they have requested 596 # information on os substates. 597 598 # Based on the information gathered on bmc, we'll try to make a 599 # determination of whether the os is even up. We'll pass the result 600 # of that assessment to get_os_state to enhance performance. 601 os_up_match = DotDict() 602 for sub_state in master_os_up_match: 603 if sub_state in req_states: 604 os_up_match[sub_state] = master_os_up_match[sub_state] 605 os_up = compare_states(state, os_up_match) 606 os_state = get_os_state(os_host=os_host, 607 os_username=os_username, 608 os_password=os_password, 609 req_states=os_req_states, 610 os_up=os_up, 611 quiet=quiet) 612 # Append os_state dictionary to ours. 613 state.update(os_state) 614 615 return state 616 617 618def check_state(match_state, 619 invert=0, 620 print_string="", 621 openbmc_host="", 622 openbmc_username="", 623 openbmc_password="", 624 os_host="", 625 os_username="", 626 os_password="", 627 quiet=None): 628 r""" 629 Check that the Open BMC machine's composite state matches the specified 630 state. On success, this keyword returns the machine's composite state as a 631 dictionary. 632 633 Description of arguments: 634 match_state A dictionary whose key/value pairs are "state field"/ 635 "state value". The state value is interpreted as a 636 regular expression. Example call from robot: 637 ${match_state}= Create Dictionary chassis=^On$ 638 ... bmc=^Ready$ 639 ... boot_progress=^OSStart$ 640 ${state}= Check State &{match_state} 641 invert If this flag is set, this function will succeed if the 642 states do NOT match. 643 print_string This function will print this string to the console prior 644 to getting the state. 645 openbmc_host The DNS name or IP address of the BMC. 646 This defaults to global ${OPENBMC_HOST}. 647 openbmc_username The username to be used to login to the BMC. 648 This defaults to global ${OPENBMC_USERNAME}. 649 openbmc_password The password to be used to login to the BMC. 650 This defaults to global ${OPENBMC_PASSWORD}. 651 os_host The DNS name or IP address of the operating system. 652 This defaults to global ${OS_HOST}. 653 os_username The username to be used to login to the OS. 654 This defaults to global ${OS_USERNAME}. 655 os_password The password to be used to login to the OS. 656 This defaults to global ${OS_PASSWORD}. 657 quiet Indicates whether status details should be written to the 658 console. Defaults to either global value of ${QUIET} or 659 to 1. 660 """ 661 662 quiet = int(gp.get_var_value(quiet, 0)) 663 664 grp.rprint(print_string) 665 666 req_states = match_state.keys() 667 # Initialize state. 668 state = get_state(openbmc_host=openbmc_host, 669 openbmc_username=openbmc_username, 670 openbmc_password=openbmc_password, 671 os_host=os_host, 672 os_username=os_username, 673 os_password=os_password, 674 req_states=req_states, 675 quiet=quiet) 676 if not quiet: 677 gp.print_var(state) 678 679 match = compare_states(state, match_state) 680 681 if invert and match: 682 fail_msg = "The current state of the machine matches the match" +\ 683 " state:\n" + gp.sprint_varx("state", state) 684 BuiltIn().fail("\n" + gp.sprint_error(fail_msg)) 685 elif not invert and not match: 686 fail_msg = "The current state of the machine does NOT match the" +\ 687 " match state:\n" +\ 688 gp.sprint_varx("state", state) 689 BuiltIn().fail("\n" + gp.sprint_error(fail_msg)) 690 691 return state 692 693 694def wait_state(match_state=(), 695 wait_time="1 min", 696 interval="1 second", 697 invert=0, 698 openbmc_host="", 699 openbmc_username="", 700 openbmc_password="", 701 os_host="", 702 os_username="", 703 os_password="", 704 quiet=None): 705 r""" 706 Wait for the Open BMC machine's composite state to match the specified 707 state. On success, this keyword returns the machine's composite state as 708 a dictionary. 709 710 Description of arguments: 711 match_state A dictionary whose key/value pairs are "state field"/ 712 "state value". See check_state (above) for details. 713 This value may also be any string accepted by 714 return_state_constant (e.g. "standby_match_state"). 715 In such a case this function will call 716 return_state_constant to convert it to a proper 717 dictionary as described above. 718 wait_time The total amount of time to wait for the desired state. 719 This value may be expressed in Robot Framework's time 720 format (e.g. 1 minute, 2 min 3 s, 4.5). 721 interval The amount of time between state checks. 722 This value may be expressed in Robot Framework's time 723 format (e.g. 1 minute, 2 min 3 s, 4.5). 724 invert If this flag is set, this function will for the state of 725 the machine to cease to match the match state. 726 openbmc_host The DNS name or IP address of the BMC. 727 This defaults to global ${OPENBMC_HOST}. 728 openbmc_username The username to be used to login to the BMC. 729 This defaults to global ${OPENBMC_USERNAME}. 730 openbmc_password The password to be used to login to the BMC. 731 This defaults to global ${OPENBMC_PASSWORD}. 732 os_host The DNS name or IP address of the operating system. 733 This defaults to global ${OS_HOST}. 734 os_username The username to be used to login to the OS. 735 This defaults to global ${OS_USERNAME}. 736 os_password The password to be used to login to the OS. 737 This defaults to global ${OS_PASSWORD}. 738 quiet Indicates whether status details should be written to the 739 console. Defaults to either global value of ${QUIET} or 740 to 1. 741 """ 742 743 quiet = int(gp.get_var_value(quiet, 0)) 744 745 if type(match_state) in (str, unicode): 746 match_state = return_state_constant(match_state) 747 748 if not quiet: 749 if invert: 750 alt_text = "cease to " 751 else: 752 alt_text = "" 753 gp.print_timen("Checking every " + str(interval) + " for up to " 754 + str(wait_time) + " for the state of the machine to " 755 + alt_text + "match the state shown below.") 756 gp.print_var(match_state) 757 758 if quiet: 759 print_string = "" 760 else: 761 print_string = "#" 762 763 debug = int(BuiltIn().get_variable_value("${debug}", "0")) 764 if debug: 765 # In debug we print state so no need to print the "#". 766 print_string = "" 767 check_state_quiet = 1 - debug 768 cmd_buf = ["Check State", match_state, "invert=${" + str(invert) + "}", 769 "print_string=" + print_string, "openbmc_host=" + openbmc_host, 770 "openbmc_username=" + openbmc_username, 771 "openbmc_password=" + openbmc_password, "os_host=" + os_host, 772 "os_username=" + os_username, "os_password=" + os_password, 773 "quiet=${" + str(check_state_quiet) + "}"] 774 grp.rdpissuing_keyword(cmd_buf) 775 try: 776 state = BuiltIn().wait_until_keyword_succeeds(wait_time, interval, 777 *cmd_buf) 778 except AssertionError as my_assertion_error: 779 gp.printn() 780 message = my_assertion_error.args[0] 781 BuiltIn().fail(message) 782 783 if not quiet: 784 gp.printn() 785 if invert: 786 gp.print_timen("The states no longer match:") 787 else: 788 gp.print_timen("The states match:") 789 gp.print_var(state) 790 791 return state 792 793 794def wait_for_comm_cycle(start_boot_seconds, 795 quiet=None): 796 r""" 797 Wait for communications to the BMC to stop working and then resume working. 798 This function is useful when you have initiated some kind of reboot. 799 800 Description of arguments: 801 start_boot_seconds The time that the boot test started. The format is the 802 epoch time in seconds, i.e. the number of seconds since 803 1970-01-01 00:00:00 UTC. This value should be obtained 804 from the BMC so that it is not dependent on any kind of 805 synchronization between this machine and the target BMC 806 This will allow this program to work correctly even in 807 a simulated environment. This value should be obtained 808 by the caller prior to initiating a reboot. It can be 809 obtained as follows: 810 state = st.get_state(req_states=['epoch_seconds']) 811 """ 812 813 quiet = int(gp.get_var_value(quiet, 0)) 814 815 # Validate parms. 816 error_message = gv.svalid_integer(start_boot_seconds, 817 var_name="start_boot_seconds") 818 if error_message != "": 819 BuiltIn().fail(gp.sprint_error(error_message)) 820 821 match_state = anchor_state(DotDict([('packet_loss', '100')])) 822 # Wait for 100% packet loss trying to ping machine. 823 wait_state(match_state, wait_time="8 mins", interval="0 seconds") 824 825 match_state['packet_loss'] = '^0$' 826 # Wait for 0% packet loss trying to ping machine. 827 wait_state(match_state, wait_time="8 mins", interval="0 seconds") 828 829 # Get the uptime and epoch seconds for comparisons. We want to be sure 830 # that the uptime is less than the elapsed boot time. Further proof that 831 # a reboot has indeed occurred (vs random network instability giving a 832 # false positive. We also use wait_state because the BMC may take a short 833 # while to be ready to process SSH requests. 834 match_state = DotDict([('uptime', '^[0-9\\.]+$'), 835 ('epoch_seconds', '^[0-9]+$')]) 836 state = wait_state(match_state, wait_time="2 mins", interval="1 second") 837 838 elapsed_boot_time = int(state['epoch_seconds']) - start_boot_seconds 839 gp.qprint_var(elapsed_boot_time) 840 if state['uptime'] == "": 841 error_message = "Unable to obtain uptime from the BMC. BMC is not" +\ 842 " communicating." 843 BuiltIn().fail(gp.sprint_error(error_message)) 844 if int(float(state['uptime'])) < elapsed_boot_time: 845 uptime = state['uptime'] 846 gp.qprint_var(uptime) 847 gp.qprint_timen("The uptime is less than the elapsed boot time," 848 + " as expected.") 849 else: 850 error_message = "The uptime is greater than the elapsed boot time," +\ 851 " which is unexpected:\n" +\ 852 gp.sprint_var(start_boot_seconds) +\ 853 gp.sprint_var(state) 854 BuiltIn().fail(gp.sprint_error(error_message)) 855 856 gp.qprint_timen("Verifying that REST API interface is working.") 857 match_state = DotDict([('rest', '^1$')]) 858 state = wait_state(match_state, wait_time="5 mins", interval="2 seconds") 859