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