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 11state: 12 state[power]: 1 13 state[bmc]: HOST_BOOTED 14 state[boot_progress]: FW Progress, Starting OS 15 state[os_ping]: 1 16 state[os_login]: 1 17 state[os_run_cmd]: 1 18 19Different users may very well have different needs when inquiring about 20state. In the future, we can add code to allow a user to specify which 21pieces of info they need in the state dictionary. Examples of such data 22might include uptime, state timestamps, boot side, etc. 23 24By using the wait_state function, a caller can start a boot and then wait for 25a precisely defined state to indicate that the boot has succeeded. If 26the boot fails, they can see exactly why by looking at the current state as 27compared with the expected state. 28""" 29 30import gen_print as gp 31import gen_robot_print as grp 32import gen_valid as gv 33 34import commands 35from robot.libraries.BuiltIn import BuiltIn 36 37import re 38 39# We need utils.robot to get keywords like "Get Power State". 40BuiltIn().import_resource("utils.robot") 41 42 43############################################################################### 44def anchor_state(state): 45 46 r""" 47 Add regular expression anchors ("^" and "$") to the beginning and end of 48 each item in the state dictionary passed in. Return the resulting 49 dictionary. 50 51 Description of Arguments: 52 state A dictionary such as the one returned by the get_state() 53 function. 54 """ 55 56 anchored_state = state 57 for key, match_state_value in anchored_state.items(): 58 anchored_state[key] = "^" + str(anchored_state[key]) + "$" 59 60 return anchored_state 61 62############################################################################### 63 64 65############################################################################### 66def compare_states(state, 67 match_state): 68 69 r""" 70 Compare 2 state dictionaries. Return True if the match and False if they 71 don't. Note that the match_state dictionary does not need to have an entry 72 corresponding to each entry in the state dictionary. But for each entry 73 that it does have, the corresponding state entry will be checked for a 74 match. 75 76 Description of arguments: 77 state A state dictionary such as the one returned by the 78 get_state function. 79 match_state A dictionary whose key/value pairs are "state field"/ 80 "state value". The state value is interpreted as a 81 regular expression. Every value in this dictionary is 82 considered. If each and every one matches, the 2 83 dictionaries are considered to be matching. 84 """ 85 86 match = True 87 for key, match_state_value in match_state.items(): 88 try: 89 if not re.match(match_state_value, str(state[key])): 90 match = False 91 break 92 except KeyError: 93 match = False 94 break 95 96 return match 97 98############################################################################### 99 100 101############################################################################### 102def get_os_state(os_host="", 103 os_username="", 104 os_password="", 105 quiet=None): 106 107 r""" 108 Get component states for the operating system such as ping, login, 109 etc, put them into a dictionary and return them to the caller. 110 111 Description of arguments: 112 os_host The DNS name or IP address of the operating system. 113 This defaults to global ${OS_HOST}. 114 os_username The username to be used to login to the OS. 115 This defaults to global ${OS_USERNAME}. 116 os_password The password to be used to login to the OS. 117 This defaults to global ${OS_PASSWORD}. 118 quiet Indicates whether status details (e.g. curl commands) should 119 be written to the console. 120 Defaults to either global value of ${QUIET} or to 1. 121 """ 122 123 quiet = grp.set_quiet_default(quiet, 1) 124 125 # Set parm defaults where necessary and validate all parms. 126 if os_host == "": 127 os_host = BuiltIn().get_variable_value("${OS_HOST}") 128 error_message = gv.svalid_value(os_host, var_name="os_host", 129 invalid_values=[None, ""]) 130 if error_message != "": 131 BuiltIn().fail(gp.sprint_error(error_message)) 132 133 if os_username == "": 134 os_username = BuiltIn().get_variable_value("${OS_USERNAME}") 135 error_message = gv.svalid_value(os_username, var_name="os_username", 136 invalid_values=[None, ""]) 137 if error_message != "": 138 BuiltIn().fail(gp.sprint_error(error_message)) 139 140 if os_password == "": 141 os_password = BuiltIn().get_variable_value("${OS_PASSWORD}") 142 error_message = gv.svalid_value(os_password, var_name="os_password", 143 invalid_values=[None, ""]) 144 if error_message != "": 145 BuiltIn().fail(gp.sprint_error(error_message)) 146 147 # See if the OS pings. 148 cmd_buf = "ping -c 1 -w 2 " + os_host 149 if not quiet: 150 grp.rpissuing(cmd_buf) 151 rc, out_buf = commands.getstatusoutput(cmd_buf) 152 if rc == 0: 153 pings = 1 154 else: 155 pings = 0 156 157 # Open SSH connection to OS. 158 cmd_buf = ["Open Connection", os_host] 159 if not quiet: 160 grp.rpissuing_keyword(cmd_buf) 161 ix = BuiltIn().run_keyword(*cmd_buf) 162 163 # Login to OS. 164 cmd_buf = ["Login", os_username, os_password] 165 if not quiet: 166 grp.rpissuing_keyword(cmd_buf) 167 status, msg = BuiltIn().run_keyword_and_ignore_error(*cmd_buf) 168 169 if status == "PASS": 170 login = 1 171 else: 172 login = 0 173 174 if login: 175 # Try running a simple command (uptime) on the OS. 176 cmd_buf = ["Execute Command", "uptime", "return_stderr=True", 177 "return_rc=True"] 178 if not quiet: 179 grp.rpissuing_keyword(cmd_buf) 180 output, stderr_buf, rc = BuiltIn().run_keyword(*cmd_buf) 181 if rc == 0 and stderr_buf == "": 182 run_cmd = 1 183 else: 184 run_cmd = 0 185 else: 186 run_cmd = 0 187 188 # Create a dictionary containing the results of the prior commands. 189 cmd_buf = ["Create Dictionary", "ping=${" + str(pings) + "}", 190 "login=${" + str(login) + "}", 191 "run_cmd=${" + str(run_cmd) + "}"] 192 grp.rdpissuing_keyword(cmd_buf) 193 os_state = BuiltIn().run_keyword(*cmd_buf) 194 195 return os_state 196 197############################################################################### 198 199 200############################################################################### 201def get_state(openbmc_host="", 202 openbmc_username="", 203 openbmc_password="", 204 os_host="", 205 os_username="", 206 os_password="", 207 quiet=None): 208 209 r""" 210 Get component states such as power state, bmc state, etc, put them into a 211 dictionary and return them to the caller. 212 213 Description of arguments: 214 openbmc_host The DNS name or IP address of the BMC. 215 This defaults to global ${OPENBMC_HOST}. 216 openbmc_username The username to be used to login to the BMC. 217 This defaults to global ${OPENBMC_USERNAME}. 218 openbmc_password The password to be used to login to the BMC. 219 This defaults to global ${OPENBMC_PASSWORD}. 220 os_host The DNS name or IP address of the operating system. 221 This defaults to global ${OS_HOST}. 222 os_username The username to be used to login to the OS. 223 This defaults to global ${OS_USERNAME}. 224 os_password The password to be used to login to the OS. 225 This defaults to global ${OS_PASSWORD}. 226 quiet Indicates whether status details (e.g. curl commands) 227 should be written to the console. 228 Defaults to either global value of ${QUIET} or to 1. 229 """ 230 231 quiet = grp.set_quiet_default(quiet, 1) 232 233 # Set parm defaults where necessary and validate all parms. 234 if openbmc_host == "": 235 openbmc_host = BuiltIn().get_variable_value("${OPENBMC_HOST}") 236 error_message = gv.svalid_value(openbmc_host, 237 var_name="openbmc_host", 238 invalid_values=[None, ""]) 239 if error_message != "": 240 BuiltIn().fail(gp.sprint_error(error_message)) 241 242 if openbmc_username == "": 243 openbmc_username = BuiltIn().get_variable_value("${OPENBMC_USERNAME}") 244 error_message = gv.svalid_value(openbmc_username, 245 var_name="openbmc_username", 246 invalid_values=[None, ""]) 247 if error_message != "": 248 BuiltIn().fail(gp.sprint_error(error_message)) 249 250 if openbmc_password == "": 251 openbmc_password = BuiltIn().get_variable_value("${OPENBMC_PASSWORD}") 252 error_message = gv.svalid_value(openbmc_password, 253 var_name="openbmc_password", 254 invalid_values=[None, ""]) 255 if error_message != "": 256 BuiltIn().fail(gp.sprint_error(error_message)) 257 258 # Set parm defaults where necessary and validate all parms. NOTE: OS parms 259 # are optional. 260 if os_host == "": 261 os_host = BuiltIn().get_variable_value("${OS_HOST}") 262 if os_host is None: 263 os_host = "" 264 265 if os_username is "": 266 os_username = BuiltIn().get_variable_value("${OS_USERNAME}") 267 if os_username is None: 268 os_username = "" 269 270 if os_password is "": 271 os_password = BuiltIn().get_variable_value("${OS_PASSWORD}") 272 if os_password is None: 273 os_password = "" 274 275 # Get the component states. 276 cmd_buf = ["Get Power State", "quiet=${" + str(quiet) + "}"] 277 grp.rdpissuing_keyword(cmd_buf) 278 power = BuiltIn().run_keyword(*cmd_buf) 279 280 cmd_buf = ["Get BMC State", "quiet=${" + str(quiet) + "}"] 281 grp.rdpissuing_keyword(cmd_buf) 282 bmc = BuiltIn().run_keyword(*cmd_buf) 283 284 cmd_buf = ["Get Boot Progress", "quiet=${" + str(quiet) + "}"] 285 grp.rdpissuing_keyword(cmd_buf) 286 boot_progress = BuiltIn().run_keyword(*cmd_buf) 287 288 # Create composite state dictionary. 289 cmd_buf = ["Create Dictionary", "power=${" + str(power) + "}", 290 "bmc=" + bmc, "boot_progress=" + boot_progress] 291 grp.rdpissuing_keyword(cmd_buf) 292 state = BuiltIn().run_keyword(*cmd_buf) 293 294 if os_host != "": 295 # Create an os_up_match dictionary to test whether we are booted enough 296 # to get operating system info. 297 cmd_buf = ["Create Dictionary", "power=^${1}$", "bmc=^HOST_BOOTED$", 298 "boot_progress=^FW Progress, Starting OS$"] 299 grp.rdpissuing_keyword(cmd_buf) 300 os_up_match = BuiltIn().run_keyword(*cmd_buf) 301 os_up = compare_states(state, os_up_match) 302 303 if os_up: 304 # Get OS information... 305 os_state = get_os_state(os_host=os_host, 306 os_username=os_username, 307 os_password=os_password, 308 quiet=quiet) 309 for key, state_value in os_state.items(): 310 # Add each OS value to the state dictionary, pre-pending 311 # "os_" to each key. 312 new_key = "os_" + key 313 state[new_key] = state_value 314 315 return state 316 317############################################################################### 318 319 320############################################################################### 321def check_state(match_state, 322 invert=0, 323 print_string="", 324 openbmc_host="", 325 openbmc_username="", 326 openbmc_password="", 327 os_host="", 328 os_username="", 329 os_password="", 330 quiet=None): 331 332 r""" 333 Check that the Open BMC machine's composite state matches the specified 334 state. On success, this keyword returns the machine's composite state as a 335 dictionary. 336 337 Description of arguments: 338 match_state A dictionary whose key/value pairs are "state field"/ 339 "state value". The state value is interpreted as a 340 regular expression. Example call from robot: 341 ${match_state}= Create Dictionary power=^1$ 342 ... bmc=^HOST_BOOTED$ 343 ... boot_progress=^FW Progress, Starting OS$ 344 ${state}= Check State &{match_state} 345 invert If this flag is set, this function will succeed if the 346 states do NOT match. 347 print_string This function will print this string to the console prior 348 to getting the state. 349 openbmc_host The DNS name or IP address of the BMC. 350 This defaults to global ${OPENBMC_HOST}. 351 openbmc_username The username to be used to login to the BMC. 352 This defaults to global ${OPENBMC_USERNAME}. 353 openbmc_password The password to be used to login to the BMC. 354 This defaults to global ${OPENBMC_PASSWORD}. 355 os_host The DNS name or IP address of the operating system. 356 This defaults to global ${OS_HOST}. 357 os_username The username to be used to login to the OS. 358 This defaults to global ${OS_USERNAME}. 359 os_password The password to be used to login to the OS. 360 This defaults to global ${OS_PASSWORD}. 361 quiet Indicates whether status details should be written to the 362 console. Defaults to either global value of ${QUIET} or 363 to 1. 364 """ 365 366 quiet = grp.set_quiet_default(quiet, 1) 367 368 grp.rprint(print_string) 369 370 # Initialize state. 371 state = get_state(openbmc_host=openbmc_host, 372 openbmc_username=openbmc_username, 373 openbmc_password=openbmc_password, 374 os_host=os_host, 375 os_username=os_username, 376 os_password=os_password, 377 quiet=quiet) 378 if not quiet: 379 grp.rprint_var(state) 380 381 match = compare_states(state, match_state) 382 383 if invert and match: 384 fail_msg = "The current state of the machine matches the match" +\ 385 " state:\n" + gp.sprint_varx("state", state) 386 BuiltIn().fail("\n" + gp.sprint_error(fail_msg)) 387 elif not invert and not match: 388 fail_msg = "The current state of the machine does NOT match the" +\ 389 " match state:\n" +\ 390 gp.sprint_varx("state", state) 391 BuiltIn().fail("\n" + gp.sprint_error(fail_msg)) 392 393 return state 394 395############################################################################### 396 397 398############################################################################### 399def wait_state(match_state, 400 wait_time="1 min", 401 interval="1 second", 402 invert=0, 403 openbmc_host="", 404 openbmc_username="", 405 openbmc_password="", 406 os_host="", 407 os_username="", 408 os_password="", 409 quiet=None): 410 411 r""" 412 Wait for the Open BMC machine's composite state to match the specified 413 state. On success, this keyword returns the machine's composite state as 414 a dictionary. 415 416 Description of arguments: 417 match_state A dictionary whose key/value pairs are "state field"/ 418 "state value". See check_state (above) for details. 419 wait_time The total amount of time to wait for the desired state. 420 This value may be expressed in Robot Framework's time 421 format (e.g. 1 minute, 2 min 3 s, 4.5). 422 interval The amount of time between state checks. 423 This value may be expressed in Robot Framework's time 424 format (e.g. 1 minute, 2 min 3 s, 4.5). 425 invert If this flag is set, this function will for the state of 426 the machine to cease to match the match state. 427 openbmc_host The DNS name or IP address of the BMC. 428 This defaults to global ${OPENBMC_HOST}. 429 openbmc_username The username to be used to login to the BMC. 430 This defaults to global ${OPENBMC_USERNAME}. 431 openbmc_password The password to be used to login to the BMC. 432 This defaults to global ${OPENBMC_PASSWORD}. 433 os_host The DNS name or IP address of the operating system. 434 This defaults to global ${OS_HOST}. 435 os_username The username to be used to login to the OS. 436 This defaults to global ${OS_USERNAME}. 437 os_password The password to be used to login to the OS. 438 This defaults to global ${OS_PASSWORD}. 439 quiet Indicates whether status details should be written to the 440 console. Defaults to either global value of ${QUIET} or 441 to 1. 442 """ 443 444 quiet = grp.set_quiet_default(quiet, 1) 445 446 if not quiet: 447 if invert: 448 alt_text = "cease to " 449 else: 450 alt_text = "" 451 grp.rprint_timen("Checking every " + str(interval) + " for up to " + 452 str(wait_time) + " for the state of the machine to " + 453 alt_text + "match the state shown below.") 454 grp.rprint_var(match_state) 455 456 cmd_buf = ["Check State", match_state, "invert=${" + str(invert) + "}", 457 "print_string=#", "openbmc_host=" + openbmc_host, 458 "openbmc_username=" + openbmc_username, 459 "openbmc_password=" + openbmc_password, "os_host=" + os_host, 460 "os_username=" + os_username, "os_password=" + os_password, 461 "quiet=${1}"] 462 grp.rdpissuing_keyword(cmd_buf) 463 state = BuiltIn().wait_until_keyword_succeeds(wait_time, interval, 464 *cmd_buf) 465 466 if not quiet: 467 grp.rprintn() 468 if invert: 469 grp.rprint_timen("The states no longer match:") 470 else: 471 grp.rprint_timen("The states match:") 472 grp.rprint_var(state) 473 474 return state 475 476############################################################################### 477