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