blob: c59c359bab824e402d4deea0f3e173485a17216c [file] [log] [blame]
Michael Walsh70369fd2016-11-22 11:25:57 -06001#!/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
Michael Walsh341c21e2017-01-17 16:25:20 -060011default_state:
12 default_state[power]: 1
13 default_state[bmc]: HOST_BOOTED
14 default_state[boot_progress]: FW Progress, Starting OS
15 default_state[os_ping]: 1
16 default_state[os_login]: 1
17 default_state[os_run_cmd]: 1
Michael Walsh70369fd2016-11-22 11:25:57 -060018
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
Michael Walsh341c21e2017-01-17 16:25:20 -060036from robot.utils import DotDict
Michael Walsh70369fd2016-11-22 11:25:57 -060037
38import re
Michael Walsh341c21e2017-01-17 16:25:20 -060039import os
Michael Walsh70369fd2016-11-22 11:25:57 -060040
Michael Walsh341c21e2017-01-17 16:25:20 -060041# We don't want global variable getting changed when an import is done
42# so we'll save it and restore it.
43quiet = int(BuiltIn().get_variable_value("${quiet}"))
Michael Walsh70369fd2016-11-22 11:25:57 -060044# We need utils.robot to get keywords like "Get Power State".
45BuiltIn().import_resource("utils.robot")
46
Michael Walsh341c21e2017-01-17 16:25:20 -060047###############################################################################
48# The BMC code is about to be changed as far as what states are defined and
49# what the state values can be. I am creating a means of processing both the
50# old style state (i.e. OBMC_STATES_VERSION = 0) and the new style (i.e.
51# OBMC_STATES_VERSION >= 1.
52# The caller can set environment variable OBMC_STATES_VERSION to dictate
53# whether we're processing old or new style states. If OBMC_STATES_VERSION is
54# not set it will default to 0.
55DEFAULT_OBMC_STATES_VERSION = 0
56try:
57 BuiltIn().import_resource("state_manager.robot")
58except RuntimeError:
59 pass
60
61try:
62 OBMC_STATES_VERSION = int(os.environ.get('OBMC_STATES_VERSION',
63 DEFAULT_OBMC_STATES_VERSION))
64except ValueError:
65 OBMC_STATES_VERSION = DEFAULT_OBMC_STATES_VERSION
66BuiltIn().set_global_variable("${quiet}", quiet)
67quiet = int(BuiltIn().get_variable_value("${quiet}"))
68
69if OBMC_STATES_VERSION == 0:
70 default_state = DotDict([('power', '1'),
71 ('bmc', 'HOST_BOOTED'),
72 ('boot_progress', 'FW Progress, Starting OS'),
73 ('os_ping', '1'),
74 ('os_login', '1'),
75 ('os_run_cmd', '1')])
76else:
77 default_state = DotDict([('chassis', 'On'),
78 ('bmc', 'HOST_BOOTED'),
79 ('boot_progress', 'FW Progress, Starting OS'),
80 ('host', 'Ready'),
81 ('os_ping', '1'),
82 ('os_login', '1'),
83 ('os_run_cmd', '1')])
84
85
86###############################################################################
87def return_default_state():
88
89 r"""
90 Return default state dictionary.
91 """
92
93 return default_state
94
95###############################################################################
96
Michael Walsh70369fd2016-11-22 11:25:57 -060097
98###############################################################################
99def anchor_state(state):
100
101 r"""
102 Add regular expression anchors ("^" and "$") to the beginning and end of
103 each item in the state dictionary passed in. Return the resulting
104 dictionary.
105
106 Description of Arguments:
107 state A dictionary such as the one returned by the get_state()
108 function.
109 """
110
111 anchored_state = state
112 for key, match_state_value in anchored_state.items():
113 anchored_state[key] = "^" + str(anchored_state[key]) + "$"
114
115 return anchored_state
116
117###############################################################################
118
119
120###############################################################################
121def compare_states(state,
122 match_state):
123
124 r"""
125 Compare 2 state dictionaries. Return True if the match and False if they
126 don't. Note that the match_state dictionary does not need to have an entry
127 corresponding to each entry in the state dictionary. But for each entry
128 that it does have, the corresponding state entry will be checked for a
129 match.
130
131 Description of arguments:
132 state A state dictionary such as the one returned by the
133 get_state function.
134 match_state A dictionary whose key/value pairs are "state field"/
135 "state value". The state value is interpreted as a
136 regular expression. Every value in this dictionary is
137 considered. If each and every one matches, the 2
138 dictionaries are considered to be matching.
139 """
140
141 match = True
142 for key, match_state_value in match_state.items():
143 try:
144 if not re.match(match_state_value, str(state[key])):
145 match = False
146 break
147 except KeyError:
148 match = False
149 break
150
151 return match
152
153###############################################################################
154
155
156###############################################################################
157def get_os_state(os_host="",
158 os_username="",
159 os_password="",
160 quiet=None):
161
162 r"""
163 Get component states for the operating system such as ping, login,
164 etc, put them into a dictionary and return them to the caller.
165
166 Description of arguments:
167 os_host The DNS name or IP address of the operating system.
168 This defaults to global ${OS_HOST}.
169 os_username The username to be used to login to the OS.
170 This defaults to global ${OS_USERNAME}.
171 os_password The password to be used to login to the OS.
172 This defaults to global ${OS_PASSWORD}.
173 quiet Indicates whether status details (e.g. curl commands) should
174 be written to the console.
175 Defaults to either global value of ${QUIET} or to 1.
176 """
177
178 quiet = grp.set_quiet_default(quiet, 1)
179
180 # Set parm defaults where necessary and validate all parms.
181 if os_host == "":
182 os_host = BuiltIn().get_variable_value("${OS_HOST}")
183 error_message = gv.svalid_value(os_host, var_name="os_host",
184 invalid_values=[None, ""])
185 if error_message != "":
186 BuiltIn().fail(gp.sprint_error(error_message))
187
188 if os_username == "":
189 os_username = BuiltIn().get_variable_value("${OS_USERNAME}")
190 error_message = gv.svalid_value(os_username, var_name="os_username",
191 invalid_values=[None, ""])
192 if error_message != "":
193 BuiltIn().fail(gp.sprint_error(error_message))
194
195 if os_password == "":
196 os_password = BuiltIn().get_variable_value("${OS_PASSWORD}")
197 error_message = gv.svalid_value(os_password, var_name="os_password",
198 invalid_values=[None, ""])
199 if error_message != "":
200 BuiltIn().fail(gp.sprint_error(error_message))
201
202 # See if the OS pings.
203 cmd_buf = "ping -c 1 -w 2 " + os_host
204 if not quiet:
205 grp.rpissuing(cmd_buf)
206 rc, out_buf = commands.getstatusoutput(cmd_buf)
207 if rc == 0:
208 pings = 1
209 else:
210 pings = 0
211
212 # Open SSH connection to OS.
213 cmd_buf = ["Open Connection", os_host]
214 if not quiet:
215 grp.rpissuing_keyword(cmd_buf)
216 ix = BuiltIn().run_keyword(*cmd_buf)
217
218 # Login to OS.
219 cmd_buf = ["Login", os_username, os_password]
220 if not quiet:
221 grp.rpissuing_keyword(cmd_buf)
222 status, msg = BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
223
224 if status == "PASS":
225 login = 1
226 else:
227 login = 0
228
229 if login:
230 # Try running a simple command (uptime) on the OS.
231 cmd_buf = ["Execute Command", "uptime", "return_stderr=True",
232 "return_rc=True"]
233 if not quiet:
234 grp.rpissuing_keyword(cmd_buf)
235 output, stderr_buf, rc = BuiltIn().run_keyword(*cmd_buf)
236 if rc == 0 and stderr_buf == "":
237 run_cmd = 1
238 else:
239 run_cmd = 0
240 else:
241 run_cmd = 0
242
243 # Create a dictionary containing the results of the prior commands.
244 cmd_buf = ["Create Dictionary", "ping=${" + str(pings) + "}",
245 "login=${" + str(login) + "}",
246 "run_cmd=${" + str(run_cmd) + "}"]
247 grp.rdpissuing_keyword(cmd_buf)
248 os_state = BuiltIn().run_keyword(*cmd_buf)
249
250 return os_state
251
252###############################################################################
253
254
255###############################################################################
256def get_state(openbmc_host="",
257 openbmc_username="",
258 openbmc_password="",
259 os_host="",
260 os_username="",
261 os_password="",
262 quiet=None):
263
264 r"""
265 Get component states such as power state, bmc state, etc, put them into a
266 dictionary and return them to the caller.
267
268 Description of arguments:
269 openbmc_host The DNS name or IP address of the BMC.
270 This defaults to global ${OPENBMC_HOST}.
271 openbmc_username The username to be used to login to the BMC.
272 This defaults to global ${OPENBMC_USERNAME}.
273 openbmc_password The password to be used to login to the BMC.
274 This defaults to global ${OPENBMC_PASSWORD}.
275 os_host The DNS name or IP address of the operating system.
276 This defaults to global ${OS_HOST}.
277 os_username The username to be used to login to the OS.
278 This defaults to global ${OS_USERNAME}.
279 os_password The password to be used to login to the OS.
280 This defaults to global ${OS_PASSWORD}.
281 quiet Indicates whether status details (e.g. curl commands)
282 should be written to the console.
283 Defaults to either global value of ${QUIET} or to 1.
284 """
285
286 quiet = grp.set_quiet_default(quiet, 1)
287
288 # Set parm defaults where necessary and validate all parms.
289 if openbmc_host == "":
290 openbmc_host = BuiltIn().get_variable_value("${OPENBMC_HOST}")
291 error_message = gv.svalid_value(openbmc_host,
292 var_name="openbmc_host",
293 invalid_values=[None, ""])
294 if error_message != "":
295 BuiltIn().fail(gp.sprint_error(error_message))
296
297 if openbmc_username == "":
298 openbmc_username = BuiltIn().get_variable_value("${OPENBMC_USERNAME}")
299 error_message = gv.svalid_value(openbmc_username,
300 var_name="openbmc_username",
301 invalid_values=[None, ""])
302 if error_message != "":
303 BuiltIn().fail(gp.sprint_error(error_message))
304
305 if openbmc_password == "":
306 openbmc_password = BuiltIn().get_variable_value("${OPENBMC_PASSWORD}")
307 error_message = gv.svalid_value(openbmc_password,
308 var_name="openbmc_password",
309 invalid_values=[None, ""])
310 if error_message != "":
311 BuiltIn().fail(gp.sprint_error(error_message))
312
313 # Set parm defaults where necessary and validate all parms. NOTE: OS parms
314 # are optional.
315 if os_host == "":
316 os_host = BuiltIn().get_variable_value("${OS_HOST}")
317 if os_host is None:
318 os_host = ""
319
320 if os_username is "":
321 os_username = BuiltIn().get_variable_value("${OS_USERNAME}")
322 if os_username is None:
323 os_username = ""
324
325 if os_password is "":
326 os_password = BuiltIn().get_variable_value("${OS_PASSWORD}")
327 if os_password is None:
328 os_password = ""
329
330 # Get the component states.
Michael Walsh341c21e2017-01-17 16:25:20 -0600331 if OBMC_STATES_VERSION== 0:
332 cmd_buf = ["Get Power State", "quiet=${" + str(quiet) + "}"]
333 grp.rdpissuing_keyword(cmd_buf)
334 power = BuiltIn().run_keyword(*cmd_buf)
335 else:
336 cmd_buf = ["Get Chassis Power State", "quiet=${" + str(quiet) + "}"]
337 grp.rdpissuing_keyword(cmd_buf)
338 chassis = BuiltIn().run_keyword(*cmd_buf)
339 # Strip everything up to the final period.
340 chassis = re.sub(r'.*\.', "", chassis)
Michael Walsh70369fd2016-11-22 11:25:57 -0600341
Michael Walsh78f38a62017-01-30 15:05:31 -0600342 if OBMC_STATES_VERSION== 0:
343 qualifier = "utils"
344 else:
345 qualifier = "state_manager"
346
347 cmd_buf = [qualifier + ".Get BMC State", "quiet=${" + str(quiet) + "}"]
Michael Walsh70369fd2016-11-22 11:25:57 -0600348 grp.rdpissuing_keyword(cmd_buf)
349 bmc = BuiltIn().run_keyword(*cmd_buf)
350
351 cmd_buf = ["Get Boot Progress", "quiet=${" + str(quiet) + "}"]
352 grp.rdpissuing_keyword(cmd_buf)
353 boot_progress = BuiltIn().run_keyword(*cmd_buf)
354
Michael Walsh341c21e2017-01-17 16:25:20 -0600355 if OBMC_STATES_VERSION > 0:
356 cmd_buf = ["Get Host State", "quiet=${" + str(quiet) + "}"]
357 grp.rdpissuing_keyword(cmd_buf)
358 host = BuiltIn().run_keyword(*cmd_buf)
359 # Strip everything up to the final period.
360 host = re.sub(r'.*\.', "", host)
361
Michael Walsh70369fd2016-11-22 11:25:57 -0600362 # Create composite state dictionary.
Michael Walsh341c21e2017-01-17 16:25:20 -0600363 if OBMC_STATES_VERSION == 0:
364 cmd_buf = ["Create Dictionary", "power=${" + str(power) + "}",
365 "bmc=" + bmc, "boot_progress=" + boot_progress]
366 else:
367 cmd_buf = ["Create Dictionary", "chassis=" + str(chassis),
368 "bmc=" + bmc, "boot_progress=" + boot_progress,
369 "host=" + host]
370
Michael Walsh70369fd2016-11-22 11:25:57 -0600371 grp.rdpissuing_keyword(cmd_buf)
372 state = BuiltIn().run_keyword(*cmd_buf)
373
374 if os_host != "":
375 # Create an os_up_match dictionary to test whether we are booted enough
376 # to get operating system info.
Michael Walsh341c21e2017-01-17 16:25:20 -0600377 if OBMC_STATES_VERSION == 0:
378 cmd_buf = ["Create Dictionary", "power=^${1}$",
379 "bmc=^HOST_BOOTED$",
380 "boot_progress=^FW Progress, Starting OS$"]
381 else:
382 cmd_buf = ["Create Dictionary", "chassis=^On$",
383 "bmc=^HOST_BOOTED$",
384 "boot_progress=^FW Progress, Starting OS$"]
Michael Walsh70369fd2016-11-22 11:25:57 -0600385 grp.rdpissuing_keyword(cmd_buf)
386 os_up_match = BuiltIn().run_keyword(*cmd_buf)
387 os_up = compare_states(state, os_up_match)
388
389 if os_up:
390 # Get OS information...
391 os_state = get_os_state(os_host=os_host,
392 os_username=os_username,
393 os_password=os_password,
394 quiet=quiet)
395 for key, state_value in os_state.items():
396 # Add each OS value to the state dictionary, pre-pending
397 # "os_" to each key.
398 new_key = "os_" + key
399 state[new_key] = state_value
400
401 return state
402
403###############################################################################
404
405
406###############################################################################
407def check_state(match_state,
408 invert=0,
409 print_string="",
410 openbmc_host="",
411 openbmc_username="",
412 openbmc_password="",
413 os_host="",
414 os_username="",
415 os_password="",
416 quiet=None):
417
418 r"""
419 Check that the Open BMC machine's composite state matches the specified
420 state. On success, this keyword returns the machine's composite state as a
421 dictionary.
422
423 Description of arguments:
424 match_state A dictionary whose key/value pairs are "state field"/
425 "state value". The state value is interpreted as a
426 regular expression. Example call from robot:
Michael Walsh341c21e2017-01-17 16:25:20 -0600427 ${match_state}= Create Dictionary chassis=^On$
428 ... bmc=^Ready$
Michael Walsh70369fd2016-11-22 11:25:57 -0600429 ... boot_progress=^FW Progress, Starting OS$
430 ${state}= Check State &{match_state}
431 invert If this flag is set, this function will succeed if the
432 states do NOT match.
433 print_string This function will print this string to the console prior
434 to getting the state.
435 openbmc_host The DNS name or IP address of the BMC.
436 This defaults to global ${OPENBMC_HOST}.
437 openbmc_username The username to be used to login to the BMC.
438 This defaults to global ${OPENBMC_USERNAME}.
439 openbmc_password The password to be used to login to the BMC.
440 This defaults to global ${OPENBMC_PASSWORD}.
441 os_host The DNS name or IP address of the operating system.
442 This defaults to global ${OS_HOST}.
443 os_username The username to be used to login to the OS.
444 This defaults to global ${OS_USERNAME}.
445 os_password The password to be used to login to the OS.
446 This defaults to global ${OS_PASSWORD}.
447 quiet Indicates whether status details should be written to the
448 console. Defaults to either global value of ${QUIET} or
449 to 1.
450 """
451
452 quiet = grp.set_quiet_default(quiet, 1)
453
454 grp.rprint(print_string)
455
456 # Initialize state.
457 state = get_state(openbmc_host=openbmc_host,
458 openbmc_username=openbmc_username,
459 openbmc_password=openbmc_password,
460 os_host=os_host,
461 os_username=os_username,
462 os_password=os_password,
463 quiet=quiet)
464 if not quiet:
465 grp.rprint_var(state)
466
467 match = compare_states(state, match_state)
468
469 if invert and match:
470 fail_msg = "The current state of the machine matches the match" +\
471 " state:\n" + gp.sprint_varx("state", state)
472 BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
473 elif not invert and not match:
474 fail_msg = "The current state of the machine does NOT match the" +\
475 " match state:\n" +\
476 gp.sprint_varx("state", state)
477 BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
478
479 return state
480
481###############################################################################
482
483
484###############################################################################
Michael Walshf893ba02017-01-10 10:28:05 -0600485def wait_state(match_state=(),
Michael Walsh70369fd2016-11-22 11:25:57 -0600486 wait_time="1 min",
487 interval="1 second",
488 invert=0,
489 openbmc_host="",
490 openbmc_username="",
491 openbmc_password="",
492 os_host="",
493 os_username="",
494 os_password="",
495 quiet=None):
496
497 r"""
498 Wait for the Open BMC machine's composite state to match the specified
499 state. On success, this keyword returns the machine's composite state as
500 a dictionary.
501
502 Description of arguments:
503 match_state A dictionary whose key/value pairs are "state field"/
504 "state value". See check_state (above) for details.
505 wait_time The total amount of time to wait for the desired state.
506 This value may be expressed in Robot Framework's time
507 format (e.g. 1 minute, 2 min 3 s, 4.5).
508 interval The amount of time between state checks.
509 This value may be expressed in Robot Framework's time
510 format (e.g. 1 minute, 2 min 3 s, 4.5).
511 invert If this flag is set, this function will for the state of
512 the machine to cease to match the match state.
513 openbmc_host The DNS name or IP address of the BMC.
514 This defaults to global ${OPENBMC_HOST}.
515 openbmc_username The username to be used to login to the BMC.
516 This defaults to global ${OPENBMC_USERNAME}.
517 openbmc_password The password to be used to login to the BMC.
518 This defaults to global ${OPENBMC_PASSWORD}.
519 os_host The DNS name or IP address of the operating system.
520 This defaults to global ${OS_HOST}.
521 os_username The username to be used to login to the OS.
522 This defaults to global ${OS_USERNAME}.
523 os_password The password to be used to login to the OS.
524 This defaults to global ${OS_PASSWORD}.
525 quiet Indicates whether status details should be written to the
526 console. Defaults to either global value of ${QUIET} or
527 to 1.
528 """
529
530 quiet = grp.set_quiet_default(quiet, 1)
531
532 if not quiet:
533 if invert:
534 alt_text = "cease to "
535 else:
536 alt_text = ""
537 grp.rprint_timen("Checking every " + str(interval) + " for up to " +
538 str(wait_time) + " for the state of the machine to " +
539 alt_text + "match the state shown below.")
540 grp.rprint_var(match_state)
541
Michael Walshf893ba02017-01-10 10:28:05 -0600542 if quiet:
Michael Walsh341c21e2017-01-17 16:25:20 -0600543 print_string = ""
Michael Walshf893ba02017-01-10 10:28:05 -0600544 else:
Michael Walsh341c21e2017-01-17 16:25:20 -0600545 print_string = "#"
Michael Walsh70369fd2016-11-22 11:25:57 -0600546 cmd_buf = ["Check State", match_state, "invert=${" + str(invert) + "}",
Michael Walshf893ba02017-01-10 10:28:05 -0600547 "print_string=" + print_string, "openbmc_host=" + openbmc_host,
Michael Walsh70369fd2016-11-22 11:25:57 -0600548 "openbmc_username=" + openbmc_username,
549 "openbmc_password=" + openbmc_password, "os_host=" + os_host,
550 "os_username=" + os_username, "os_password=" + os_password,
551 "quiet=${1}"]
552 grp.rdpissuing_keyword(cmd_buf)
553 state = BuiltIn().wait_until_keyword_succeeds(wait_time, interval,
554 *cmd_buf)
555
556 if not quiet:
557 grp.rprintn()
558 if invert:
559 grp.rprint_timen("The states no longer match:")
560 else:
561 grp.rprint_timen("The states match:")
562 grp.rprint_var(state)
563
564 return state
565
566###############################################################################