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