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