blob: 029ed7d3b4c208afd7cc2e692c87195287802d94 [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
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###############################################################################