blob: 33690d4f0d2065eaff2678dbc502ec8003b68d89 [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:
Michael Walsh65b12542017-02-03 15:34:38 -060012 default_state[chassis]: On
13 default_state[bmc]: Ready
Michael Walsh341c21e2017-01-17 16:25:20 -060014 default_state[boot_progress]: FW Progress, Starting OS
Michael Walsh65b12542017-02-03 15:34:38 -060015 default_state[host]: Running
Michael Walsh341c21e2017-01-17 16:25:20 -060016 default_state[os_ping]: 1
17 default_state[os_login]: 1
18 default_state[os_run_cmd]: 1
Michael Walsh70369fd2016-11-22 11:25:57 -060019
20Different users may very well have different needs when inquiring about
21state. In the future, we can add code to allow a user to specify which
22pieces of info they need in the state dictionary. Examples of such data
23might include uptime, state timestamps, boot side, etc.
24
25By using the wait_state function, a caller can start a boot and then wait for
26a precisely defined state to indicate that the boot has succeeded. If
27the boot fails, they can see exactly why by looking at the current state as
28compared with the expected state.
29"""
30
31import gen_print as gp
32import gen_robot_print as grp
33import gen_valid as gv
Michael Walsh16cbb7f2017-02-02 15:54:16 -060034import gen_robot_utils as gru
Michael Walsh70369fd2016-11-22 11:25:57 -060035
36import commands
37from robot.libraries.BuiltIn import BuiltIn
Michael Walsh341c21e2017-01-17 16:25:20 -060038from robot.utils import DotDict
Michael Walsh70369fd2016-11-22 11:25:57 -060039
40import re
Michael Walsh341c21e2017-01-17 16:25:20 -060041import os
Michael Walsh70369fd2016-11-22 11:25:57 -060042
43# We need utils.robot to get keywords like "Get Power State".
Michael Walsh16cbb7f2017-02-02 15:54:16 -060044gru.my_import_resource("utils.robot")
45gru.my_import_resource("state_manager.robot")
Michael Walsh70369fd2016-11-22 11:25:57 -060046
Michael Walsh341c21e2017-01-17 16:25:20 -060047# The BMC code is about to be changed as far as what states are defined and
48# what the state values can be. I am creating a means of processing both the
49# old style state (i.e. OBMC_STATES_VERSION = 0) and the new style (i.e.
Michael Walsh16cbb7f2017-02-02 15:54:16 -060050# OBMC_STATES_VERSION = 1).
Michael Walsh341c21e2017-01-17 16:25:20 -060051# The caller can set environment variable OBMC_STATES_VERSION to dictate
52# whether we're processing old or new style states. If OBMC_STATES_VERSION is
53# not set it will default to 0.
Michael Walsh341c21e2017-01-17 16:25:20 -060054
Michael Walsh16cbb7f2017-02-02 15:54:16 -060055OBMC_STATES_VERSION = int(os.environ.get('OBMC_STATES_VERSION', 0))
Michael Walsh341c21e2017-01-17 16:25:20 -060056
57if OBMC_STATES_VERSION == 0:
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')])
64else:
65 default_state = DotDict([('chassis', 'On'),
Michael Walsh65b12542017-02-03 15:34:38 -060066 ('bmc', 'Ready'),
Michael Walsh341c21e2017-01-17 16:25:20 -060067 ('boot_progress', 'FW Progress, Starting OS'),
Michael Walsh65b12542017-02-03 15:34:38 -060068 ('host', 'Running'),
Michael Walsh341c21e2017-01-17 16:25:20 -060069 ('os_ping', '1'),
70 ('os_login', '1'),
71 ('os_run_cmd', '1')])
72
73
74###############################################################################
75def return_default_state():
76
77 r"""
78 Return default state dictionary.
79 """
80
81 return default_state
82
83###############################################################################
84
Michael Walsh70369fd2016-11-22 11:25:57 -060085
86###############################################################################
87def anchor_state(state):
88
89 r"""
90 Add regular expression anchors ("^" and "$") to the beginning and end of
91 each item in the state dictionary passed in. Return the resulting
92 dictionary.
93
94 Description of Arguments:
95 state A dictionary such as the one returned by the get_state()
96 function.
97 """
98
99 anchored_state = state
100 for key, match_state_value in anchored_state.items():
101 anchored_state[key] = "^" + str(anchored_state[key]) + "$"
102
103 return anchored_state
104
105###############################################################################
106
107
108###############################################################################
109def compare_states(state,
110 match_state):
111
112 r"""
113 Compare 2 state dictionaries. Return True if the match and False if they
114 don't. Note that the match_state dictionary does not need to have an entry
115 corresponding to each entry in the state dictionary. But for each entry
116 that it does have, the corresponding state entry will be checked for a
117 match.
118
119 Description of arguments:
120 state A state dictionary such as the one returned by the
121 get_state function.
122 match_state A dictionary whose key/value pairs are "state field"/
123 "state value". The state value is interpreted as a
124 regular expression. Every value in this dictionary is
125 considered. If each and every one matches, the 2
126 dictionaries are considered to be matching.
127 """
128
129 match = True
130 for key, match_state_value in match_state.items():
131 try:
132 if not re.match(match_state_value, str(state[key])):
133 match = False
134 break
135 except KeyError:
136 match = False
137 break
138
139 return match
140
141###############################################################################
142
143
144###############################################################################
145def get_os_state(os_host="",
146 os_username="",
147 os_password="",
148 quiet=None):
149
150 r"""
151 Get component states for the operating system such as ping, login,
152 etc, put them into a dictionary and return them to the caller.
153
154 Description of arguments:
155 os_host The DNS name or IP address of the operating system.
156 This defaults to global ${OS_HOST}.
157 os_username The username to be used to login to the OS.
158 This defaults to global ${OS_USERNAME}.
159 os_password The password to be used to login to the OS.
160 This defaults to global ${OS_PASSWORD}.
161 quiet Indicates whether status details (e.g. curl commands) should
162 be written to the console.
163 Defaults to either global value of ${QUIET} or to 1.
164 """
165
166 quiet = grp.set_quiet_default(quiet, 1)
167
168 # Set parm defaults where necessary and validate all parms.
169 if os_host == "":
170 os_host = BuiltIn().get_variable_value("${OS_HOST}")
171 error_message = gv.svalid_value(os_host, var_name="os_host",
172 invalid_values=[None, ""])
173 if error_message != "":
174 BuiltIn().fail(gp.sprint_error(error_message))
175
176 if os_username == "":
177 os_username = BuiltIn().get_variable_value("${OS_USERNAME}")
178 error_message = gv.svalid_value(os_username, var_name="os_username",
179 invalid_values=[None, ""])
180 if error_message != "":
181 BuiltIn().fail(gp.sprint_error(error_message))
182
183 if os_password == "":
184 os_password = BuiltIn().get_variable_value("${OS_PASSWORD}")
185 error_message = gv.svalid_value(os_password, var_name="os_password",
186 invalid_values=[None, ""])
187 if error_message != "":
188 BuiltIn().fail(gp.sprint_error(error_message))
189
190 # See if the OS pings.
191 cmd_buf = "ping -c 1 -w 2 " + os_host
192 if not quiet:
193 grp.rpissuing(cmd_buf)
194 rc, out_buf = commands.getstatusoutput(cmd_buf)
195 if rc == 0:
196 pings = 1
197 else:
198 pings = 0
199
200 # Open SSH connection to OS.
201 cmd_buf = ["Open Connection", os_host]
202 if not quiet:
203 grp.rpissuing_keyword(cmd_buf)
204 ix = BuiltIn().run_keyword(*cmd_buf)
205
206 # Login to OS.
207 cmd_buf = ["Login", os_username, os_password]
208 if not quiet:
209 grp.rpissuing_keyword(cmd_buf)
210 status, msg = BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
211
212 if status == "PASS":
213 login = 1
214 else:
215 login = 0
216
217 if login:
218 # Try running a simple command (uptime) on the OS.
219 cmd_buf = ["Execute Command", "uptime", "return_stderr=True",
220 "return_rc=True"]
221 if not quiet:
222 grp.rpissuing_keyword(cmd_buf)
223 output, stderr_buf, rc = BuiltIn().run_keyword(*cmd_buf)
224 if rc == 0 and stderr_buf == "":
225 run_cmd = 1
226 else:
227 run_cmd = 0
228 else:
229 run_cmd = 0
230
231 # Create a dictionary containing the results of the prior commands.
232 cmd_buf = ["Create Dictionary", "ping=${" + str(pings) + "}",
233 "login=${" + str(login) + "}",
234 "run_cmd=${" + str(run_cmd) + "}"]
235 grp.rdpissuing_keyword(cmd_buf)
236 os_state = BuiltIn().run_keyword(*cmd_buf)
237
238 return os_state
239
240###############################################################################
241
242
243###############################################################################
244def get_state(openbmc_host="",
245 openbmc_username="",
246 openbmc_password="",
247 os_host="",
248 os_username="",
249 os_password="",
250 quiet=None):
251
252 r"""
253 Get component states such as power state, bmc state, etc, put them into a
254 dictionary and return them to the caller.
255
256 Description of arguments:
257 openbmc_host The DNS name or IP address of the BMC.
258 This defaults to global ${OPENBMC_HOST}.
259 openbmc_username The username to be used to login to the BMC.
260 This defaults to global ${OPENBMC_USERNAME}.
261 openbmc_password The password to be used to login to the BMC.
262 This defaults to global ${OPENBMC_PASSWORD}.
263 os_host The DNS name or IP address of the operating system.
264 This defaults to global ${OS_HOST}.
265 os_username The username to be used to login to the OS.
266 This defaults to global ${OS_USERNAME}.
267 os_password The password to be used to login to the OS.
268 This defaults to global ${OS_PASSWORD}.
269 quiet Indicates whether status details (e.g. curl commands)
270 should be written to the console.
271 Defaults to either global value of ${QUIET} or to 1.
272 """
273
274 quiet = grp.set_quiet_default(quiet, 1)
275
276 # Set parm defaults where necessary and validate all parms.
277 if openbmc_host == "":
278 openbmc_host = BuiltIn().get_variable_value("${OPENBMC_HOST}")
279 error_message = gv.svalid_value(openbmc_host,
280 var_name="openbmc_host",
281 invalid_values=[None, ""])
282 if error_message != "":
283 BuiltIn().fail(gp.sprint_error(error_message))
284
285 if openbmc_username == "":
286 openbmc_username = BuiltIn().get_variable_value("${OPENBMC_USERNAME}")
287 error_message = gv.svalid_value(openbmc_username,
288 var_name="openbmc_username",
289 invalid_values=[None, ""])
290 if error_message != "":
291 BuiltIn().fail(gp.sprint_error(error_message))
292
293 if openbmc_password == "":
294 openbmc_password = BuiltIn().get_variable_value("${OPENBMC_PASSWORD}")
295 error_message = gv.svalid_value(openbmc_password,
296 var_name="openbmc_password",
297 invalid_values=[None, ""])
298 if error_message != "":
299 BuiltIn().fail(gp.sprint_error(error_message))
300
301 # Set parm defaults where necessary and validate all parms. NOTE: OS parms
302 # are optional.
303 if os_host == "":
304 os_host = BuiltIn().get_variable_value("${OS_HOST}")
305 if os_host is None:
306 os_host = ""
307
308 if os_username is "":
309 os_username = BuiltIn().get_variable_value("${OS_USERNAME}")
310 if os_username is None:
311 os_username = ""
312
313 if os_password is "":
314 os_password = BuiltIn().get_variable_value("${OS_PASSWORD}")
315 if os_password is None:
316 os_password = ""
317
318 # Get the component states.
Michael Walsh16cbb7f2017-02-02 15:54:16 -0600319 if OBMC_STATES_VERSION == 0:
Michael Walsh341c21e2017-01-17 16:25:20 -0600320 cmd_buf = ["Get Power State", "quiet=${" + str(quiet) + "}"]
321 grp.rdpissuing_keyword(cmd_buf)
322 power = BuiltIn().run_keyword(*cmd_buf)
323 else:
324 cmd_buf = ["Get Chassis Power State", "quiet=${" + str(quiet) + "}"]
325 grp.rdpissuing_keyword(cmd_buf)
326 chassis = BuiltIn().run_keyword(*cmd_buf)
327 # Strip everything up to the final period.
328 chassis = re.sub(r'.*\.', "", chassis)
Michael Walsh70369fd2016-11-22 11:25:57 -0600329
Michael Walsh16cbb7f2017-02-02 15:54:16 -0600330 if OBMC_STATES_VERSION == 0:
Michael Walsh78f38a62017-01-30 15:05:31 -0600331 qualifier = "utils"
332 else:
333 qualifier = "state_manager"
334
335 cmd_buf = [qualifier + ".Get BMC State", "quiet=${" + str(quiet) + "}"]
Michael Walsh70369fd2016-11-22 11:25:57 -0600336 grp.rdpissuing_keyword(cmd_buf)
337 bmc = BuiltIn().run_keyword(*cmd_buf)
338
339 cmd_buf = ["Get Boot Progress", "quiet=${" + str(quiet) + "}"]
340 grp.rdpissuing_keyword(cmd_buf)
341 boot_progress = BuiltIn().run_keyword(*cmd_buf)
342
Michael Walsh341c21e2017-01-17 16:25:20 -0600343 if OBMC_STATES_VERSION > 0:
344 cmd_buf = ["Get Host State", "quiet=${" + str(quiet) + "}"]
345 grp.rdpissuing_keyword(cmd_buf)
346 host = BuiltIn().run_keyword(*cmd_buf)
347 # Strip everything up to the final period.
348 host = re.sub(r'.*\.', "", host)
349
Michael Walsh70369fd2016-11-22 11:25:57 -0600350 # Create composite state dictionary.
Michael Walsh341c21e2017-01-17 16:25:20 -0600351 if OBMC_STATES_VERSION == 0:
352 cmd_buf = ["Create Dictionary", "power=${" + str(power) + "}",
353 "bmc=" + bmc, "boot_progress=" + boot_progress]
354 else:
355 cmd_buf = ["Create Dictionary", "chassis=" + str(chassis),
356 "bmc=" + bmc, "boot_progress=" + boot_progress,
357 "host=" + host]
358
Michael Walsh70369fd2016-11-22 11:25:57 -0600359 grp.rdpissuing_keyword(cmd_buf)
360 state = BuiltIn().run_keyword(*cmd_buf)
361
362 if os_host != "":
363 # Create an os_up_match dictionary to test whether we are booted enough
364 # to get operating system info.
Michael Walsh341c21e2017-01-17 16:25:20 -0600365 if OBMC_STATES_VERSION == 0:
366 cmd_buf = ["Create Dictionary", "power=^${1}$",
367 "bmc=^HOST_BOOTED$",
368 "boot_progress=^FW Progress, Starting OS$"]
369 else:
Michael Walsh65b12542017-02-03 15:34:38 -0600370 # TODO: Add back boot_progress when ipmi is enabled on
371 # Witherspoon.
Michael Walsh341c21e2017-01-17 16:25:20 -0600372 cmd_buf = ["Create Dictionary", "chassis=^On$",
Michael Walsh65b12542017-02-03 15:34:38 -0600373 "bmc=^Ready$"]
374 # "boot_progress=^FW Progress, Starting OS$"]
Michael Walsh70369fd2016-11-22 11:25:57 -0600375 grp.rdpissuing_keyword(cmd_buf)
376 os_up_match = BuiltIn().run_keyword(*cmd_buf)
377 os_up = compare_states(state, os_up_match)
378
379 if os_up:
380 # Get OS information...
381 os_state = get_os_state(os_host=os_host,
382 os_username=os_username,
383 os_password=os_password,
384 quiet=quiet)
385 for key, state_value in os_state.items():
386 # Add each OS value to the state dictionary, pre-pending
387 # "os_" to each key.
388 new_key = "os_" + key
389 state[new_key] = state_value
390
391 return state
392
393###############################################################################
394
395
396###############################################################################
397def check_state(match_state,
398 invert=0,
399 print_string="",
400 openbmc_host="",
401 openbmc_username="",
402 openbmc_password="",
403 os_host="",
404 os_username="",
405 os_password="",
406 quiet=None):
407
408 r"""
409 Check that the Open BMC machine's composite state matches the specified
410 state. On success, this keyword returns the machine's composite state as a
411 dictionary.
412
413 Description of arguments:
414 match_state A dictionary whose key/value pairs are "state field"/
415 "state value". The state value is interpreted as a
416 regular expression. Example call from robot:
Michael Walsh341c21e2017-01-17 16:25:20 -0600417 ${match_state}= Create Dictionary chassis=^On$
418 ... bmc=^Ready$
Michael Walsh70369fd2016-11-22 11:25:57 -0600419 ... boot_progress=^FW Progress, Starting OS$
420 ${state}= Check State &{match_state}
421 invert If this flag is set, this function will succeed if the
422 states do NOT match.
423 print_string This function will print this string to the console prior
424 to getting the state.
425 openbmc_host The DNS name or IP address of the BMC.
426 This defaults to global ${OPENBMC_HOST}.
427 openbmc_username The username to be used to login to the BMC.
428 This defaults to global ${OPENBMC_USERNAME}.
429 openbmc_password The password to be used to login to the BMC.
430 This defaults to global ${OPENBMC_PASSWORD}.
431 os_host The DNS name or IP address of the operating system.
432 This defaults to global ${OS_HOST}.
433 os_username The username to be used to login to the OS.
434 This defaults to global ${OS_USERNAME}.
435 os_password The password to be used to login to the OS.
436 This defaults to global ${OS_PASSWORD}.
437 quiet Indicates whether status details should be written to the
438 console. Defaults to either global value of ${QUIET} or
439 to 1.
440 """
441
442 quiet = grp.set_quiet_default(quiet, 1)
443
444 grp.rprint(print_string)
445
446 # Initialize state.
447 state = get_state(openbmc_host=openbmc_host,
448 openbmc_username=openbmc_username,
449 openbmc_password=openbmc_password,
450 os_host=os_host,
451 os_username=os_username,
452 os_password=os_password,
453 quiet=quiet)
454 if not quiet:
455 grp.rprint_var(state)
456
457 match = compare_states(state, match_state)
458
459 if invert and match:
460 fail_msg = "The current state of the machine matches the match" +\
461 " state:\n" + gp.sprint_varx("state", state)
462 BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
463 elif not invert and not match:
464 fail_msg = "The current state of the machine does NOT match the" +\
465 " match state:\n" +\
466 gp.sprint_varx("state", state)
467 BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
468
469 return state
470
471###############################################################################
472
473
474###############################################################################
Michael Walshf893ba02017-01-10 10:28:05 -0600475def wait_state(match_state=(),
Michael Walsh70369fd2016-11-22 11:25:57 -0600476 wait_time="1 min",
477 interval="1 second",
478 invert=0,
479 openbmc_host="",
480 openbmc_username="",
481 openbmc_password="",
482 os_host="",
483 os_username="",
484 os_password="",
485 quiet=None):
486
487 r"""
488 Wait for the Open BMC machine's composite state to match the specified
489 state. On success, this keyword returns the machine's composite state as
490 a dictionary.
491
492 Description of arguments:
493 match_state A dictionary whose key/value pairs are "state field"/
494 "state value". See check_state (above) for details.
495 wait_time The total amount of time to wait for the desired state.
496 This value may be expressed in Robot Framework's time
497 format (e.g. 1 minute, 2 min 3 s, 4.5).
498 interval The amount of time between state checks.
499 This value may be expressed in Robot Framework's time
500 format (e.g. 1 minute, 2 min 3 s, 4.5).
501 invert If this flag is set, this function will for the state of
502 the machine to cease to match the match state.
503 openbmc_host The DNS name or IP address of the BMC.
504 This defaults to global ${OPENBMC_HOST}.
505 openbmc_username The username to be used to login to the BMC.
506 This defaults to global ${OPENBMC_USERNAME}.
507 openbmc_password The password to be used to login to the BMC.
508 This defaults to global ${OPENBMC_PASSWORD}.
509 os_host The DNS name or IP address of the operating system.
510 This defaults to global ${OS_HOST}.
511 os_username The username to be used to login to the OS.
512 This defaults to global ${OS_USERNAME}.
513 os_password The password to be used to login to the OS.
514 This defaults to global ${OS_PASSWORD}.
515 quiet Indicates whether status details should be written to the
516 console. Defaults to either global value of ${QUIET} or
517 to 1.
518 """
519
520 quiet = grp.set_quiet_default(quiet, 1)
521
522 if not quiet:
523 if invert:
524 alt_text = "cease to "
525 else:
526 alt_text = ""
527 grp.rprint_timen("Checking every " + str(interval) + " for up to " +
528 str(wait_time) + " for the state of the machine to " +
529 alt_text + "match the state shown below.")
530 grp.rprint_var(match_state)
531
Michael Walshf893ba02017-01-10 10:28:05 -0600532 if quiet:
Michael Walsh341c21e2017-01-17 16:25:20 -0600533 print_string = ""
Michael Walshf893ba02017-01-10 10:28:05 -0600534 else:
Michael Walsh341c21e2017-01-17 16:25:20 -0600535 print_string = "#"
Michael Walsh70369fd2016-11-22 11:25:57 -0600536 cmd_buf = ["Check State", match_state, "invert=${" + str(invert) + "}",
Michael Walshf893ba02017-01-10 10:28:05 -0600537 "print_string=" + print_string, "openbmc_host=" + openbmc_host,
Michael Walsh70369fd2016-11-22 11:25:57 -0600538 "openbmc_username=" + openbmc_username,
539 "openbmc_password=" + openbmc_password, "os_host=" + os_host,
540 "os_username=" + os_username, "os_password=" + os_password,
541 "quiet=${1}"]
542 grp.rdpissuing_keyword(cmd_buf)
543 state = BuiltIn().wait_until_keyword_succeeds(wait_time, interval,
544 *cmd_buf)
545
546 if not quiet:
547 grp.rprintn()
548 if invert:
549 grp.rprint_timen("The states no longer match:")
550 else:
551 grp.rprint_timen("The states match:")
552 grp.rprint_var(state)
553
554 return state
555
556###############################################################################