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