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