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 521 # Get the component states. 522 if 'ping' in req_states: 523 # See if the OS pings. 524 cmd_buf = "ping -c 1 -w 2 " + openbmc_host 525 if not quiet: 526 gp.pissuing(cmd_buf) 527 rc, out_buf = commands.getstatusoutput(cmd_buf) 528 if rc == 0: 529 ping = 1 530 531 if 'packet_loss' in req_states: 532 # See if the OS pings. 533 cmd_buf = "ping -c 5 -w 5 " + openbmc_host +\ 534 " | egrep 'packet loss' | sed -re 's/.* ([0-9]+)%.*/\\1/g'" 535 if not quiet: 536 gp.pissuing(cmd_buf) 537 rc, out_buf = commands.getstatusoutput(cmd_buf) 538 if rc == 0: 539 packet_loss = out_buf.rstrip("\n") 540 541 if 'uptime' in req_states: 542 cmd_buf = ["BMC Execute Command", "cat /proc/uptime | cut -f 1 -d ' '", 543 'quiet=${1}'] 544 if not quiet: 545 grp.rpissuing_keyword(cmd_buf) 546 status, ret_values = \ 547 BuiltIn().run_keyword_and_ignore_error(*cmd_buf) 548 if status == "PASS": 549 stdout, stderr, rc = ret_values 550 if rc == 0 and stderr == "": 551 uptime = stdout 552 553 if 'epoch_seconds' in req_states: 554 date_cmd_buf = "date -u +%s" 555 if USE_BMC_EPOCH_TIME: 556 cmd_buf = ["BMC Execute Command", date_cmd_buf, 'quiet=${1}'] 557 if not quiet: 558 grp.rpissuing_keyword(cmd_buf) 559 status, ret_values = \ 560 BuiltIn().run_keyword_and_ignore_error(*cmd_buf) 561 if status == "PASS": 562 stdout, stderr, rc = ret_values 563 if rc == 0 and stderr == "": 564 epoch_seconds = stdout.rstrip("\n") 565 else: 566 shell_rc, out_buf = gc.cmd_fnc_u(date_cmd_buf, 567 quiet=1, 568 print_output=0) 569 if shell_rc == 0: 570 epoch_seconds = out_buf.rstrip("\n") 571 572 master_req_rest = ['rest', 'host', 'requested_host', 'operating_system', 573 'attempts_left', 'boot_progress', 'chassis', 574 'requested_chassis' 'bmc' 'requested_bmc'] 575 576 req_rest = [sub_state for sub_state in req_states if sub_state in 577 master_req_rest] 578 need_rest = (len(req_rest) > 0) 579 state = DotDict() 580 if need_rest: 581 cmd_buf = ["Read Properties", var.SYSTEM_STATE_URI + "enumerate", 582 "quiet=${" + str(quiet) + "}"] 583 grp.rdpissuing_keyword(cmd_buf) 584 status, ret_values = \ 585 BuiltIn().run_keyword_and_ignore_error(*cmd_buf) 586 if status == "PASS": 587 state['rest'] = '1' 588 else: 589 state['rest'] = '0' 590 591 for url_path in ret_values: 592 for attr_name in ret_values[url_path]: 593 # Create a state key value based on the attr_name. 594 if type(ret_values[url_path][attr_name]) is unicode: 595 ret_values[url_path][attr_name] = \ 596 re.sub(r'.*\.', "", ret_values[url_path][attr_name]) 597 # Do some key name manipulations. 598 new_attr_name = re.sub(r'^Current|(State|Transition)$', 599 "", attr_name) 600 new_attr_name = re.sub(r'BMC', r'Bmc', new_attr_name) 601 new_attr_name = re.sub(r'([A-Z][a-z])', r'_\1', new_attr_name) 602 new_attr_name = new_attr_name.lower().lstrip("_") 603 new_attr_name = re.sub(r'power', r'chassis', new_attr_name) 604 if new_attr_name in req_states: 605 state[new_attr_name] = ret_values[url_path][attr_name] 606 607 for sub_state in req_states: 608 if sub_state in state: 609 continue 610 if sub_state.startswith("os_"): 611 # We pass "os_" requests on to get_os_state. 612 continue 613 cmd_buf = "state['" + sub_state + "'] = str(" + sub_state + ")" 614 exec(cmd_buf) 615 616 if os_host == "": 617 # The caller has not specified an os_host so as far as we're concerned, 618 # it doesn't exist. 619 return state 620 621 os_req_states = [sub_state for sub_state in req_states 622 if sub_state.startswith('os_')] 623 624 if len(os_req_states) > 0: 625 # The caller has specified an os_host and they have requested 626 # information on os substates. 627 628 # Based on the information gathered on bmc, we'll try to make a 629 # determination of whether the os is even up. We'll pass the result 630 # of that assessment to get_os_state to enhance performance. 631 os_up_match = DotDict() 632 for sub_state in master_os_up_match: 633 if sub_state in req_states: 634 os_up_match[sub_state] = master_os_up_match[sub_state] 635 os_up = compare_states(state, os_up_match) 636 os_state = get_os_state(os_host=os_host, 637 os_username=os_username, 638 os_password=os_password, 639 req_states=os_req_states, 640 os_up=os_up, 641 quiet=quiet) 642 # Append os_state dictionary to ours. 643 state.update(os_state) 644 645 return state 646 647############################################################################### 648 649 650############################################################################### 651def check_state(match_state, 652 invert=0, 653 print_string="", 654 openbmc_host="", 655 openbmc_username="", 656 openbmc_password="", 657 os_host="", 658 os_username="", 659 os_password="", 660 quiet=None): 661 662 r""" 663 Check that the Open BMC machine's composite state matches the specified 664 state. On success, this keyword returns the machine's composite state as a 665 dictionary. 666 667 Description of arguments: 668 match_state A dictionary whose key/value pairs are "state field"/ 669 "state value". The state value is interpreted as a 670 regular expression. Example call from robot: 671 ${match_state}= Create Dictionary chassis=^On$ 672 ... bmc=^Ready$ 673 ... boot_progress=^OSStart$ 674 ${state}= Check State &{match_state} 675 invert If this flag is set, this function will succeed if the 676 states do NOT match. 677 print_string This function will print this string to the console prior 678 to getting the state. 679 openbmc_host The DNS name or IP address of the BMC. 680 This defaults to global ${OPENBMC_HOST}. 681 openbmc_username The username to be used to login to the BMC. 682 This defaults to global ${OPENBMC_USERNAME}. 683 openbmc_password The password to be used to login to the BMC. 684 This defaults to global ${OPENBMC_PASSWORD}. 685 os_host The DNS name or IP address of the operating system. 686 This defaults to global ${OS_HOST}. 687 os_username The username to be used to login to the OS. 688 This defaults to global ${OS_USERNAME}. 689 os_password The password to be used to login to the OS. 690 This defaults to global ${OS_PASSWORD}. 691 quiet Indicates whether status details should be written to the 692 console. Defaults to either global value of ${QUIET} or 693 to 1. 694 """ 695 696 quiet = int(gp.get_var_value(quiet, 0)) 697 698 grp.rprint(print_string) 699 700 req_states = match_state.keys() 701 # Initialize state. 702 state = get_state(openbmc_host=openbmc_host, 703 openbmc_username=openbmc_username, 704 openbmc_password=openbmc_password, 705 os_host=os_host, 706 os_username=os_username, 707 os_password=os_password, 708 req_states=req_states, 709 quiet=quiet) 710 if not quiet: 711 gp.print_var(state) 712 713 match = compare_states(state, match_state) 714 715 if invert and match: 716 fail_msg = "The current state of the machine matches the match" +\ 717 " state:\n" + gp.sprint_varx("state", state) 718 BuiltIn().fail("\n" + gp.sprint_error(fail_msg)) 719 elif not invert and not match: 720 fail_msg = "The current state of the machine does NOT match the" +\ 721 " match state:\n" +\ 722 gp.sprint_varx("state", state) 723 BuiltIn().fail("\n" + gp.sprint_error(fail_msg)) 724 725 return state 726 727############################################################################### 728 729 730############################################################################### 731def wait_state(match_state=(), 732 wait_time="1 min", 733 interval="1 second", 734 invert=0, 735 openbmc_host="", 736 openbmc_username="", 737 openbmc_password="", 738 os_host="", 739 os_username="", 740 os_password="", 741 quiet=None): 742 743 r""" 744 Wait for the Open BMC machine's composite state to match the specified 745 state. On success, this keyword returns the machine's composite state as 746 a dictionary. 747 748 Description of arguments: 749 match_state A dictionary whose key/value pairs are "state field"/ 750 "state value". See check_state (above) for details. 751 This value may also be any string accepted by 752 return_state_constant (e.g. "standby_match_state"). 753 In such a case this function will call 754 return_state_constant to convert it to a proper 755 dictionary as described above. 756 wait_time The total amount of time to wait for the desired state. 757 This value may be expressed in Robot Framework's time 758 format (e.g. 1 minute, 2 min 3 s, 4.5). 759 interval The amount of time between state checks. 760 This value may be expressed in Robot Framework's time 761 format (e.g. 1 minute, 2 min 3 s, 4.5). 762 invert If this flag is set, this function will for the state of 763 the machine to cease to match the match state. 764 openbmc_host The DNS name or IP address of the BMC. 765 This defaults to global ${OPENBMC_HOST}. 766 openbmc_username The username to be used to login to the BMC. 767 This defaults to global ${OPENBMC_USERNAME}. 768 openbmc_password The password to be used to login to the BMC. 769 This defaults to global ${OPENBMC_PASSWORD}. 770 os_host The DNS name or IP address of the operating system. 771 This defaults to global ${OS_HOST}. 772 os_username The username to be used to login to the OS. 773 This defaults to global ${OS_USERNAME}. 774 os_password The password to be used to login to the OS. 775 This defaults to global ${OS_PASSWORD}. 776 quiet Indicates whether status details should be written to the 777 console. Defaults to either global value of ${QUIET} or 778 to 1. 779 """ 780 781 quiet = int(gp.get_var_value(quiet, 0)) 782 783 if type(match_state) in (str, unicode): 784 match_state = return_state_constant(match_state) 785 786 if not quiet: 787 if invert: 788 alt_text = "cease to " 789 else: 790 alt_text = "" 791 gp.print_timen("Checking every " + str(interval) + " for up to " + 792 str(wait_time) + " for the state of the machine to " + 793 alt_text + "match the state shown below.") 794 gp.print_var(match_state) 795 796 if quiet: 797 print_string = "" 798 else: 799 print_string = "#" 800 801 debug = int(BuiltIn().get_variable_value("${debug}", "0")) 802 if debug: 803 # In debug we print state so no need to print the "#". 804 print_string = "" 805 check_state_quiet = 1 - debug 806 cmd_buf = ["Check State", match_state, "invert=${" + str(invert) + "}", 807 "print_string=" + print_string, "openbmc_host=" + openbmc_host, 808 "openbmc_username=" + openbmc_username, 809 "openbmc_password=" + openbmc_password, "os_host=" + os_host, 810 "os_username=" + os_username, "os_password=" + os_password, 811 "quiet=${" + str(check_state_quiet) + "}"] 812 grp.rdpissuing_keyword(cmd_buf) 813 try: 814 state = BuiltIn().wait_until_keyword_succeeds(wait_time, interval, 815 *cmd_buf) 816 except AssertionError as my_assertion_error: 817 gp.printn() 818 message = my_assertion_error.args[0] 819 BuiltIn().fail(message) 820 821 if not quiet: 822 gp.printn() 823 if invert: 824 gp.print_timen("The states no longer match:") 825 else: 826 gp.print_timen("The states match:") 827 gp.print_var(state) 828 829 return state 830 831############################################################################### 832 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 int(float(state['uptime'])) < elapsed_boot_time: 880 uptime = state['uptime'] 881 gp.qprint_var(uptime) 882 gp.qprint_timen("The uptime is less than the elapsed boot time," + 883 " as expected.") 884 else: 885 error_message = "The uptime is greater than the elapsed boot time," +\ 886 " which is unexpected:\n" +\ 887 gp.sprint_var(start_boot_seconds) +\ 888 gp.sprint_var(state) 889 BuiltIn().fail(gp.sprint_error(error_message)) 890 891 gp.qprint_timen("Verifying that REST API interface is working.") 892 match_state = DotDict([('rest', '^1$')]) 893 state = wait_state(match_state, wait_time="5 mins", interval="2 seconds") 894 895############################################################################### 896