blob: 67b8d2b573b59f24f3db5a12bd1dbaf2e3e471dd [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
342 cmd_buf = ["Get BMC State", "quiet=${" + str(quiet) + "}"]
343 grp.rdpissuing_keyword(cmd_buf)
344 bmc = BuiltIn().run_keyword(*cmd_buf)
345
346 cmd_buf = ["Get Boot Progress", "quiet=${" + str(quiet) + "}"]
347 grp.rdpissuing_keyword(cmd_buf)
348 boot_progress = BuiltIn().run_keyword(*cmd_buf)
349
Michael Walsh341c21e2017-01-17 16:25:20 -0600350 if OBMC_STATES_VERSION > 0:
351 cmd_buf = ["Get Host State", "quiet=${" + str(quiet) + "}"]
352 grp.rdpissuing_keyword(cmd_buf)
353 host = BuiltIn().run_keyword(*cmd_buf)
354 # Strip everything up to the final period.
355 host = re.sub(r'.*\.', "", host)
356
Michael Walsh70369fd2016-11-22 11:25:57 -0600357 # Create composite state dictionary.
Michael Walsh341c21e2017-01-17 16:25:20 -0600358 if OBMC_STATES_VERSION == 0:
359 cmd_buf = ["Create Dictionary", "power=${" + str(power) + "}",
360 "bmc=" + bmc, "boot_progress=" + boot_progress]
361 else:
362 cmd_buf = ["Create Dictionary", "chassis=" + str(chassis),
363 "bmc=" + bmc, "boot_progress=" + boot_progress,
364 "host=" + host]
365
Michael Walsh70369fd2016-11-22 11:25:57 -0600366 grp.rdpissuing_keyword(cmd_buf)
367 state = BuiltIn().run_keyword(*cmd_buf)
368
369 if os_host != "":
370 # Create an os_up_match dictionary to test whether we are booted enough
371 # to get operating system info.
Michael Walsh341c21e2017-01-17 16:25:20 -0600372 if OBMC_STATES_VERSION == 0:
373 cmd_buf = ["Create Dictionary", "power=^${1}$",
374 "bmc=^HOST_BOOTED$",
375 "boot_progress=^FW Progress, Starting OS$"]
376 else:
377 cmd_buf = ["Create Dictionary", "chassis=^On$",
378 "bmc=^HOST_BOOTED$",
379 "boot_progress=^FW Progress, Starting OS$"]
Michael Walsh70369fd2016-11-22 11:25:57 -0600380 grp.rdpissuing_keyword(cmd_buf)
381 os_up_match = BuiltIn().run_keyword(*cmd_buf)
382 os_up = compare_states(state, os_up_match)
383
384 if os_up:
385 # Get OS information...
386 os_state = get_os_state(os_host=os_host,
387 os_username=os_username,
388 os_password=os_password,
389 quiet=quiet)
390 for key, state_value in os_state.items():
391 # Add each OS value to the state dictionary, pre-pending
392 # "os_" to each key.
393 new_key = "os_" + key
394 state[new_key] = state_value
395
396 return state
397
398###############################################################################
399
400
401###############################################################################
402def check_state(match_state,
403 invert=0,
404 print_string="",
405 openbmc_host="",
406 openbmc_username="",
407 openbmc_password="",
408 os_host="",
409 os_username="",
410 os_password="",
411 quiet=None):
412
413 r"""
414 Check that the Open BMC machine's composite state matches the specified
415 state. On success, this keyword returns the machine's composite state as a
416 dictionary.
417
418 Description of arguments:
419 match_state A dictionary whose key/value pairs are "state field"/
420 "state value". The state value is interpreted as a
421 regular expression. Example call from robot:
Michael Walsh341c21e2017-01-17 16:25:20 -0600422 ${match_state}= Create Dictionary chassis=^On$
423 ... bmc=^Ready$
Michael Walsh70369fd2016-11-22 11:25:57 -0600424 ... boot_progress=^FW Progress, Starting OS$
425 ${state}= Check State &{match_state}
426 invert If this flag is set, this function will succeed if the
427 states do NOT match.
428 print_string This function will print this string to the console prior
429 to getting the state.
430 openbmc_host The DNS name or IP address of the BMC.
431 This defaults to global ${OPENBMC_HOST}.
432 openbmc_username The username to be used to login to the BMC.
433 This defaults to global ${OPENBMC_USERNAME}.
434 openbmc_password The password to be used to login to the BMC.
435 This defaults to global ${OPENBMC_PASSWORD}.
436 os_host The DNS name or IP address of the operating system.
437 This defaults to global ${OS_HOST}.
438 os_username The username to be used to login to the OS.
439 This defaults to global ${OS_USERNAME}.
440 os_password The password to be used to login to the OS.
441 This defaults to global ${OS_PASSWORD}.
442 quiet Indicates whether status details should be written to the
443 console. Defaults to either global value of ${QUIET} or
444 to 1.
445 """
446
447 quiet = grp.set_quiet_default(quiet, 1)
448
449 grp.rprint(print_string)
450
451 # Initialize state.
452 state = get_state(openbmc_host=openbmc_host,
453 openbmc_username=openbmc_username,
454 openbmc_password=openbmc_password,
455 os_host=os_host,
456 os_username=os_username,
457 os_password=os_password,
458 quiet=quiet)
459 if not quiet:
460 grp.rprint_var(state)
461
462 match = compare_states(state, match_state)
463
464 if invert and match:
465 fail_msg = "The current state of the machine matches the match" +\
466 " state:\n" + gp.sprint_varx("state", state)
467 BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
468 elif not invert and not match:
469 fail_msg = "The current state of the machine does NOT match the" +\
470 " match state:\n" +\
471 gp.sprint_varx("state", state)
472 BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
473
474 return state
475
476###############################################################################
477
478
479###############################################################################
Michael Walshf893ba02017-01-10 10:28:05 -0600480def wait_state(match_state=(),
Michael Walsh70369fd2016-11-22 11:25:57 -0600481 wait_time="1 min",
482 interval="1 second",
483 invert=0,
484 openbmc_host="",
485 openbmc_username="",
486 openbmc_password="",
487 os_host="",
488 os_username="",
489 os_password="",
490 quiet=None):
491
492 r"""
493 Wait for the Open BMC machine's composite state to match the specified
494 state. On success, this keyword returns the machine's composite state as
495 a dictionary.
496
497 Description of arguments:
498 match_state A dictionary whose key/value pairs are "state field"/
499 "state value". See check_state (above) for details.
500 wait_time The total amount of time to wait for the desired state.
501 This value may be expressed in Robot Framework's time
502 format (e.g. 1 minute, 2 min 3 s, 4.5).
503 interval The amount of time between state checks.
504 This value may be expressed in Robot Framework's time
505 format (e.g. 1 minute, 2 min 3 s, 4.5).
506 invert If this flag is set, this function will for the state of
507 the machine to cease to match the match state.
508 openbmc_host The DNS name or IP address of the BMC.
509 This defaults to global ${OPENBMC_HOST}.
510 openbmc_username The username to be used to login to the BMC.
511 This defaults to global ${OPENBMC_USERNAME}.
512 openbmc_password The password to be used to login to the BMC.
513 This defaults to global ${OPENBMC_PASSWORD}.
514 os_host The DNS name or IP address of the operating system.
515 This defaults to global ${OS_HOST}.
516 os_username The username to be used to login to the OS.
517 This defaults to global ${OS_USERNAME}.
518 os_password The password to be used to login to the OS.
519 This defaults to global ${OS_PASSWORD}.
520 quiet Indicates whether status details should be written to the
521 console. Defaults to either global value of ${QUIET} or
522 to 1.
523 """
524
525 quiet = grp.set_quiet_default(quiet, 1)
526
527 if not quiet:
528 if invert:
529 alt_text = "cease to "
530 else:
531 alt_text = ""
532 grp.rprint_timen("Checking every " + str(interval) + " for up to " +
533 str(wait_time) + " for the state of the machine to " +
534 alt_text + "match the state shown below.")
535 grp.rprint_var(match_state)
536
Michael Walshf893ba02017-01-10 10:28:05 -0600537 if quiet:
Michael Walsh341c21e2017-01-17 16:25:20 -0600538 print_string = ""
Michael Walshf893ba02017-01-10 10:28:05 -0600539 else:
Michael Walsh341c21e2017-01-17 16:25:20 -0600540 print_string = "#"
Michael Walsh70369fd2016-11-22 11:25:57 -0600541 cmd_buf = ["Check State", match_state, "invert=${" + str(invert) + "}",
Michael Walshf893ba02017-01-10 10:28:05 -0600542 "print_string=" + print_string, "openbmc_host=" + openbmc_host,
Michael Walsh70369fd2016-11-22 11:25:57 -0600543 "openbmc_username=" + openbmc_username,
544 "openbmc_password=" + openbmc_password, "os_host=" + os_host,
545 "os_username=" + os_username, "os_password=" + os_password,
546 "quiet=${1}"]
547 grp.rdpissuing_keyword(cmd_buf)
548 state = BuiltIn().wait_until_keyword_succeeds(wait_time, interval,
549 *cmd_buf)
550
551 if not quiet:
552 grp.rprintn()
553 if invert:
554 grp.rprint_timen("The states no longer match:")
555 else:
556 grp.rprint_timen("The states match:")
557 grp.rprint_var(state)
558
559 return state
560
561###############################################################################