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 if 'uptime' in req_states: 511 cmd_buf = ["BMC Execute Command", "cat /proc/uptime | cut -f 1 -d ' '", 512 'quiet=${1}'] 513 if not quiet: 514 grp.rpissuing_keyword(cmd_buf) 515 status, ret_values = \ 516 BuiltIn().run_keyword_and_ignore_error(*cmd_buf) 517 if status == "PASS": 518 stdout, stderr, rc = ret_values 519 if rc == 0 and stderr == "": 520 uptime = stdout 521 522 if 'epoch_seconds' in req_states: 523 date_cmd_buf = "date -u +%s" 524 if USE_BMC_EPOCH_TIME: 525 cmd_buf = ["BMC Execute Command", date_cmd_buf, 'quiet=${1}'] 526 if not quiet: 527 grp.rpissuing_keyword(cmd_buf) 528 status, ret_values = \ 529 BuiltIn().run_keyword_and_ignore_error(*cmd_buf) 530 if status == "PASS": 531 stdout, stderr, rc = ret_values 532 if rc == 0 and stderr == "": 533 epoch_seconds = stdout.rstrip("\n") 534 else: 535 shell_rc, out_buf = gc.cmd_fnc_u(date_cmd_buf, 536 quiet=1, 537 print_output=0) 538 if shell_rc == 0: 539 epoch_seconds = out_buf.rstrip("\n") 540 541 master_req_rest = ['rest', 'chassis', 'bmc', 'boot_progress', 542 'host'] 543 req_rest = [sub_state for sub_state in req_states if sub_state in 544 master_req_rest] 545 need_rest = (len(req_rest) > 0) 546 547 # Though we could try to determine 'rest' state on any of several calls, 548 # for simplicity, we'll use 'chassis' to figure it out (even if the caller 549 # hasn't explicitly asked for 'chassis'). 550 if 'chassis' in req_states or need_rest: 551 cmd_buf = ["Get Chassis Power State", "quiet=${" + str(quiet) + "}"] 552 grp.rdpissuing_keyword(cmd_buf) 553 status, ret_values = \ 554 BuiltIn().run_keyword_and_ignore_error(*cmd_buf) 555 if status == "PASS": 556 chassis = ret_values 557 chassis = re.sub(r'.*\.', "", chassis) 558 rest = '1' 559 else: 560 rest = ret_values 561 562 if rest == '1': 563 if 'bmc' in req_states: 564 if OBMC_STATES_VERSION == 0: 565 qualifier = "utils" 566 else: 567 # This will not be supported much longer. 568 qualifier = "state_manager" 569 cmd_buf = [qualifier + ".Get BMC State", 570 "quiet=${" + str(quiet) + "}"] 571 grp.rdpissuing_keyword(cmd_buf) 572 status, ret_values = \ 573 BuiltIn().run_keyword_and_ignore_error(*cmd_buf) 574 if status == "PASS": 575 bmc = ret_values 576 577 if 'boot_progress' in req_states: 578 cmd_buf = ["Get Boot Progress", "quiet=${" + str(quiet) + "}"] 579 grp.rdpissuing_keyword(cmd_buf) 580 status, ret_values = \ 581 BuiltIn().run_keyword_and_ignore_error(*cmd_buf) 582 if status == "PASS": 583 boot_progress = ret_values 584 585 if 'host' in req_states: 586 if OBMC_STATES_VERSION > 0: 587 cmd_buf = ["Get Host State", "quiet=${" + str(quiet) + "}"] 588 grp.rdpissuing_keyword(cmd_buf) 589 status, ret_values = \ 590 BuiltIn().run_keyword_and_ignore_error(*cmd_buf) 591 if status == "PASS": 592 host = ret_values 593 # Strip everything up to the final period. 594 host = re.sub(r'.*\.', "", host) 595 596 state = DotDict() 597 for sub_state in req_states: 598 if sub_state.startswith("os_"): 599 # We pass "os_" requests on to get_os_state. 600 continue 601 cmd_buf = "state['" + sub_state + "'] = str(" + sub_state + ")" 602 exec(cmd_buf) 603 604 if os_host == "": 605 # The caller has not specified an os_host so as far as we're concerned, 606 # it doesn't exist. 607 return state 608 609 os_req_states = [sub_state for sub_state in req_states 610 if sub_state.startswith('os_')] 611 612 if len(os_req_states) > 0: 613 # The caller has specified an os_host and they have requested 614 # information on os substates. 615 616 # Based on the information gathered on bmc, we'll try to make a 617 # determination of whether the os is even up. We'll pass the result 618 # of that assessment to get_os_state to enhance performance. 619 os_up_match = DotDict() 620 for sub_state in master_os_up_match: 621 if sub_state in req_states: 622 os_up_match[sub_state] = master_os_up_match[sub_state] 623 os_up = compare_states(state, os_up_match) 624 os_state = get_os_state(os_host=os_host, 625 os_username=os_username, 626 os_password=os_password, 627 req_states=os_req_states, 628 os_up=os_up, 629 quiet=quiet) 630 # Append os_state dictionary to ours. 631 state.update(os_state) 632 633 return state 634 635############################################################################### 636 637 638############################################################################### 639def check_state(match_state, 640 invert=0, 641 print_string="", 642 openbmc_host="", 643 openbmc_username="", 644 openbmc_password="", 645 os_host="", 646 os_username="", 647 os_password="", 648 quiet=None): 649 650 r""" 651 Check that the Open BMC machine's composite state matches the specified 652 state. On success, this keyword returns the machine's composite state as a 653 dictionary. 654 655 Description of arguments: 656 match_state A dictionary whose key/value pairs are "state field"/ 657 "state value". The state value is interpreted as a 658 regular expression. Example call from robot: 659 ${match_state}= Create Dictionary chassis=^On$ 660 ... bmc=^Ready$ 661 ... boot_progress=^FW Progress, Starting OS$ 662 ${state}= Check State &{match_state} 663 invert If this flag is set, this function will succeed if the 664 states do NOT match. 665 print_string This function will print this string to the console prior 666 to getting the state. 667 openbmc_host The DNS name or IP address of the BMC. 668 This defaults to global ${OPENBMC_HOST}. 669 openbmc_username The username to be used to login to the BMC. 670 This defaults to global ${OPENBMC_USERNAME}. 671 openbmc_password The password to be used to login to the BMC. 672 This defaults to global ${OPENBMC_PASSWORD}. 673 os_host The DNS name or IP address of the operating system. 674 This defaults to global ${OS_HOST}. 675 os_username The username to be used to login to the OS. 676 This defaults to global ${OS_USERNAME}. 677 os_password The password to be used to login to the OS. 678 This defaults to global ${OS_PASSWORD}. 679 quiet Indicates whether status details should be written to the 680 console. Defaults to either global value of ${QUIET} or 681 to 1. 682 """ 683 684 quiet = int(gp.get_var_value(quiet, 0)) 685 686 grp.rprint(print_string) 687 688 req_states = match_state.keys() 689 # Initialize state. 690 state = get_state(openbmc_host=openbmc_host, 691 openbmc_username=openbmc_username, 692 openbmc_password=openbmc_password, 693 os_host=os_host, 694 os_username=os_username, 695 os_password=os_password, 696 req_states=req_states, 697 quiet=quiet) 698 if not quiet: 699 gp.print_var(state) 700 701 match = compare_states(state, match_state) 702 703 if invert and match: 704 fail_msg = "The current state of the machine matches the match" +\ 705 " state:\n" + gp.sprint_varx("state", state) 706 BuiltIn().fail("\n" + gp.sprint_error(fail_msg)) 707 elif not invert and not match: 708 fail_msg = "The current state of the machine does NOT match the" +\ 709 " match state:\n" +\ 710 gp.sprint_varx("state", state) 711 BuiltIn().fail("\n" + gp.sprint_error(fail_msg)) 712 713 return state 714 715############################################################################### 716 717 718############################################################################### 719def wait_state(match_state=(), 720 wait_time="1 min", 721 interval="1 second", 722 invert=0, 723 openbmc_host="", 724 openbmc_username="", 725 openbmc_password="", 726 os_host="", 727 os_username="", 728 os_password="", 729 quiet=None): 730 731 r""" 732 Wait for the Open BMC machine's composite state to match the specified 733 state. On success, this keyword returns the machine's composite state as 734 a dictionary. 735 736 Description of arguments: 737 match_state A dictionary whose key/value pairs are "state field"/ 738 "state value". See check_state (above) for details. 739 This value may also be any string accepted by 740 return_state_constant (e.g. "standby_match_state"). 741 In such a case this function will call 742 return_state_constant to convert it to a proper 743 dictionary as described above. 744 wait_time The total amount of time to wait for the desired state. 745 This value may be expressed in Robot Framework's time 746 format (e.g. 1 minute, 2 min 3 s, 4.5). 747 interval The amount of time between state checks. 748 This value may be expressed in Robot Framework's time 749 format (e.g. 1 minute, 2 min 3 s, 4.5). 750 invert If this flag is set, this function will for the state of 751 the machine to cease to match the match state. 752 openbmc_host The DNS name or IP address of the BMC. 753 This defaults to global ${OPENBMC_HOST}. 754 openbmc_username The username to be used to login to the BMC. 755 This defaults to global ${OPENBMC_USERNAME}. 756 openbmc_password The password to be used to login to the BMC. 757 This defaults to global ${OPENBMC_PASSWORD}. 758 os_host The DNS name or IP address of the operating system. 759 This defaults to global ${OS_HOST}. 760 os_username The username to be used to login to the OS. 761 This defaults to global ${OS_USERNAME}. 762 os_password The password to be used to login to the OS. 763 This defaults to global ${OS_PASSWORD}. 764 quiet Indicates whether status details should be written to the 765 console. Defaults to either global value of ${QUIET} or 766 to 1. 767 """ 768 769 quiet = int(gp.get_var_value(quiet, 0)) 770 771 if type(match_state) in (str, unicode): 772 match_state = return_state_constant(match_state) 773 774 if not quiet: 775 if invert: 776 alt_text = "cease to " 777 else: 778 alt_text = "" 779 gp.print_timen("Checking every " + str(interval) + " for up to " + 780 str(wait_time) + " for the state of the machine to " + 781 alt_text + "match the state shown below.") 782 gp.print_var(match_state) 783 784 if quiet: 785 print_string = "" 786 else: 787 print_string = "#" 788 789 debug = int(BuiltIn().get_variable_value("${debug}", "0")) 790 if debug: 791 # In debug we print state so no need to print the "#". 792 print_string = "" 793 check_state_quiet = 1 - debug 794 cmd_buf = ["Check State", match_state, "invert=${" + str(invert) + "}", 795 "print_string=" + print_string, "openbmc_host=" + openbmc_host, 796 "openbmc_username=" + openbmc_username, 797 "openbmc_password=" + openbmc_password, "os_host=" + os_host, 798 "os_username=" + os_username, "os_password=" + os_password, 799 "quiet=${" + str(check_state_quiet) + "}"] 800 grp.rdpissuing_keyword(cmd_buf) 801 try: 802 state = BuiltIn().wait_until_keyword_succeeds(wait_time, interval, 803 *cmd_buf) 804 except AssertionError as my_assertion_error: 805 gp.printn() 806 message = my_assertion_error.args[0] 807 BuiltIn().fail(message) 808 809 if not quiet: 810 gp.printn() 811 if invert: 812 gp.print_timen("The states no longer match:") 813 else: 814 gp.print_timen("The states match:") 815 gp.print_var(state) 816 817 return state 818 819############################################################################### 820 821 822############################################################################### 823def wait_for_comm_cycle(start_boot_seconds, 824 quiet=None): 825 826 r""" 827 Wait for communications to the BMC to stop working and then resume working. 828 This function is useful when you have initiated some kind of reboot. 829 830 Description of arguments: 831 start_boot_seconds The time that the boot test started. The format is the 832 epoch time in seconds, i.e. the number of seconds since 833 1970-01-01 00:00:00 UTC. This value should be obtained 834 from the BMC so that it is not dependent on any kind of 835 synchronization between this machine and the target BMC 836 This will allow this program to work correctly even in 837 a simulated environment. This value should be obtained 838 by the caller prior to initiating a reboot. It can be 839 obtained as follows: 840 state = st.get_state(req_states=['epoch_seconds']) 841 """ 842 843 quiet = int(gp.get_var_value(quiet, 0)) 844 845 # Validate parms. 846 error_message = gv.svalid_integer(start_boot_seconds, 847 var_name="start_boot_seconds") 848 if error_message != "": 849 BuiltIn().fail(gp.sprint_error(error_message)) 850 851 match_state = anchor_state(DotDict([('packet_loss', '100')])) 852 # Wait for 100% packet loss trying to ping machine. 853 wait_state(match_state, wait_time="8 mins", interval="0 seconds") 854 855 match_state['packet_loss'] = '^0$' 856 # Wait for 0% packet loss trying to ping machine. 857 wait_state(match_state, wait_time="8 mins", interval="0 seconds") 858 859 # Get the uptime and epoch seconds for comparisons. We want to be sure 860 # that the uptime is less than the elapsed boot time. Further proof that 861 # a reboot has indeed occurred (vs random network instability giving a 862 # false positive. 863 state = get_state(req_states=['uptime', 'epoch_seconds'], quiet=quiet) 864 865 elapsed_boot_time = int(state['epoch_seconds']) - start_boot_seconds 866 gp.qprint_var(elapsed_boot_time) 867 if int(float(state['uptime'])) < elapsed_boot_time: 868 uptime = state['uptime'] 869 gp.qprint_var(uptime) 870 gp.qprint_timen("The uptime is less than the elapsed boot time," + 871 " as expected.") 872 else: 873 error_message = "The uptime is greater than the elapsed boot time," +\ 874 " which is unexpected:\n" +\ 875 gp.sprint_var(start_boot_seconds) +\ 876 gp.sprint_var(state) 877 BuiltIn().fail(gp.sprint_error(error_message)) 878 879 gp.qprint_timen("Verifying that REST API interface is working.") 880 match_state = DotDict([('rest', '^1$')]) 881 state = wait_state(match_state, wait_time="5 mins", interval="2 seconds") 882 883############################################################################### 884