blob: ac53e565127d7f2326025767d0ea8b59a07a4e15 [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
Michael Walsh8fae6ea2017-02-20 16:14:44 -060021state. Support for new pieces of state information may be added to this
22module as needed.
Michael Walsh70369fd2016-11-22 11:25:57 -060023
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 Walsh8fae6ea2017-02-20 16:14:44 -060034import gen_cmd as gc
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 Walsh8fae6ea2017-02-20 16:14:44 -060047# The BMC code has recently been changed as far as what states are defined and
48# what the state values can be. This module now has a means of processing both
49# the 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
Michael Walsh8fae6ea2017-02-20 16:14:44 -060053# not set it will default to 1.
Michael Walsh341c21e2017-01-17 16:25:20 -060054
Michael Walsh8fae6ea2017-02-20 16:14:44 -060055OBMC_STATES_VERSION = int(os.environ.get('OBMC_STATES_VERSION', 1))
Michael Walsh341c21e2017-01-17 16:25:20 -060056
Michael Walsh8fae6ea2017-02-20 16:14:44 -060057# TODO: Re-enable 'bmc' once it is working again.
Michael Walsh341c21e2017-01-17 16:25:20 -060058if OBMC_STATES_VERSION == 0:
Michael Walsh8fae6ea2017-02-20 16:14:44 -060059 # default_state is an initial value which may be of use to callers.
Michael Walsh341c21e2017-01-17 16:25:20 -060060 default_state = DotDict([('power', '1'),
Michael Walsh8fae6ea2017-02-20 16:14:44 -060061 # ('bmc', 'HOST_BOOTED'),
Michael Walsh341c21e2017-01-17 16:25:20 -060062 ('boot_progress', 'FW Progress, Starting OS'),
63 ('os_ping', '1'),
64 ('os_login', '1'),
65 ('os_run_cmd', '1')])
Michael Walsh8fae6ea2017-02-20 16:14:44 -060066 # valid_req_states, default_req_states and master_os_up_match are used by
67 # the get_state function.
68 # valid_req_states is a list of state information supported by the
69 # get_state function.
70 valid_req_states = ['ping',
71 'packet_loss',
72 'uptime',
73 'epoch_seconds',
74 'power',
75 # 'bmc',
76 'boot_progress',
77 'os_ping',
78 'os_login',
79 'os_run_cmd']
80 # When a user calls get_state w/o specifying req_states, default_req_states
81 # is used as its value.
82 default_req_states = ['power',
83 # 'bmc',
84 'boot_progress',
85 'os_ping',
86 'os_login',
87 'os_run_cmd']
88 # A master dictionary to determine whether the os may be up.
89 master_os_up_match = DotDict([('power', 'On'),
90 # ('bmc', '^HOST_BOOTED$'),
91 ('boot_progress',
92 'FW Progress, Starting OS')])
93
Michael Walsh341c21e2017-01-17 16:25:20 -060094else:
Michael Walsh8fae6ea2017-02-20 16:14:44 -060095 # default_state is an initial value which may be of use to callers.
Michael Walsh341c21e2017-01-17 16:25:20 -060096 default_state = DotDict([('chassis', 'On'),
Michael Walsh8fae6ea2017-02-20 16:14:44 -060097 # ('bmc', 'Ready'),
Michael Walsh341c21e2017-01-17 16:25:20 -060098 ('boot_progress', 'FW Progress, Starting OS'),
Michael Walsh65b12542017-02-03 15:34:38 -060099 ('host', 'Running'),
Michael Walsh341c21e2017-01-17 16:25:20 -0600100 ('os_ping', '1'),
101 ('os_login', '1'),
102 ('os_run_cmd', '1')])
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600103 # valid_req_states is a list of state information supported by the
104 # get_state function.
105 # valid_req_states, default_req_states and master_os_up_match are used by
106 # the get_state function.
107 valid_req_states = ['ping',
108 'packet_loss',
109 'uptime',
110 'epoch_seconds',
111 'chassis',
112 # 'bmc',
113 'boot_progress',
114 'host',
115 'os_ping',
116 'os_login',
117 'os_run_cmd']
118 # When a user calls get_state w/o specifying req_states, default_req_states
119 # is used as its value.
120 default_req_states = ['chassis',
121 # 'bmc',
122 'boot_progress',
123 'host',
124 'os_ping',
125 'os_login',
126 'os_run_cmd']
127
128 # TODO: Add back boot_progress when ipmi is enabled on Witherspoon.
129 # A master dictionary to determine whether the os may be up.
130 master_os_up_match = DotDict([('chassis', '^On$'), ('host', '^Running$')])
131 # ('bmc', '^Ready$'),
132 # ('boot_progress',
133 # 'FW Progress, Starting OS')])
134
135# valid_os_req_states and default_os_req_states are used by the os_get_state
136# function.
137# valid_os_req_states is a list of state information supported by the
138# get_os_state function.
139valid_os_req_states = ['os_ping',
140 'os_login',
141 'os_run_cmd']
142# When a user calls get_os_state w/o specifying req_states,
143# default_os_req_states is used as its value.
144default_os_req_states = ['os_ping',
145 'os_login',
146 'os_run_cmd']
147
148# Presently, some BMCs appear to not keep time very well. This environment
149# variable directs the get_state function to use either the BMC's epoch time
150# or the local epoch time.
151USE_BMC_EPOCH_TIME = int(os.environ.get('USE_BMC_EPOCH_TIME', 0))
Michael Walsh341c21e2017-01-17 16:25:20 -0600152
153
154###############################################################################
155def return_default_state():
156
157 r"""
158 Return default state dictionary.
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600159
160 default_state is an initial value which may be of use to callers.
Michael Walsh341c21e2017-01-17 16:25:20 -0600161 """
162
163 return default_state
164
165###############################################################################
166
Michael Walsh70369fd2016-11-22 11:25:57 -0600167
168###############################################################################
169def anchor_state(state):
170
171 r"""
172 Add regular expression anchors ("^" and "$") to the beginning and end of
173 each item in the state dictionary passed in. Return the resulting
174 dictionary.
175
176 Description of Arguments:
177 state A dictionary such as the one returned by the get_state()
178 function.
179 """
180
Michael Walsh2ce067a2017-02-27 14:24:07 -0600181 anchored_state = state.copy()
Michael Walsh70369fd2016-11-22 11:25:57 -0600182 for key, match_state_value in anchored_state.items():
183 anchored_state[key] = "^" + str(anchored_state[key]) + "$"
184
185 return anchored_state
186
187###############################################################################
188
189
190###############################################################################
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600191def strip_anchor_state(state):
192
193 r"""
194 Strip regular expression anchors ("^" and "$") from the beginning and end
195 of each item in the state dictionary passed in. Return the resulting
196 dictionary.
197
198 Description of Arguments:
199 state A dictionary such as the one returned by the get_state()
200 function.
201 """
202
Michael Walsh2ce067a2017-02-27 14:24:07 -0600203 stripped_state = state.copy()
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600204 for key, match_state_value in stripped_state.items():
205 stripped_state[key] = stripped_state[key].strip("^$")
206
207 return stripped_state
208
209###############################################################################
210
211
212###############################################################################
Michael Walsh70369fd2016-11-22 11:25:57 -0600213def compare_states(state,
214 match_state):
215
216 r"""
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600217 Compare 2 state dictionaries. Return True if they match and False if they
Michael Walsh70369fd2016-11-22 11:25:57 -0600218 don't. Note that the match_state dictionary does not need to have an entry
219 corresponding to each entry in the state dictionary. But for each entry
220 that it does have, the corresponding state entry will be checked for a
221 match.
222
223 Description of arguments:
224 state A state dictionary such as the one returned by the
225 get_state function.
226 match_state A dictionary whose key/value pairs are "state field"/
227 "state value". The state value is interpreted as a
228 regular expression. Every value in this dictionary is
229 considered. If each and every one matches, the 2
230 dictionaries are considered to be matching.
231 """
232
233 match = True
234 for key, match_state_value in match_state.items():
235 try:
236 if not re.match(match_state_value, str(state[key])):
237 match = False
238 break
239 except KeyError:
240 match = False
241 break
242
243 return match
244
245###############################################################################
246
247
248###############################################################################
249def get_os_state(os_host="",
250 os_username="",
251 os_password="",
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600252 req_states=default_os_req_states,
253 os_up=True,
Michael Walsh70369fd2016-11-22 11:25:57 -0600254 quiet=None):
255
256 r"""
257 Get component states for the operating system such as ping, login,
258 etc, put them into a dictionary and return them to the caller.
259
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600260 Note that all substate values are strings.
261
Michael Walsh70369fd2016-11-22 11:25:57 -0600262 Description of arguments:
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}.
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600269 req_states This is a list of states whose values are being requested by
270 the caller.
271 os_up If the caller knows that the os can't possibly be up, it can
272 improve performance by passing os_up=False. This function
273 will then simply return default values for all requested os
274 sub states.
Michael Walsh70369fd2016-11-22 11:25:57 -0600275 quiet Indicates whether status details (e.g. curl commands) should
276 be written to the console.
277 Defaults to either global value of ${QUIET} or to 1.
278 """
279
280 quiet = grp.set_quiet_default(quiet, 1)
281
282 # Set parm defaults where necessary and validate all parms.
283 if os_host == "":
284 os_host = BuiltIn().get_variable_value("${OS_HOST}")
285 error_message = gv.svalid_value(os_host, var_name="os_host",
286 invalid_values=[None, ""])
287 if error_message != "":
288 BuiltIn().fail(gp.sprint_error(error_message))
289
290 if os_username == "":
291 os_username = BuiltIn().get_variable_value("${OS_USERNAME}")
292 error_message = gv.svalid_value(os_username, var_name="os_username",
293 invalid_values=[None, ""])
294 if error_message != "":
295 BuiltIn().fail(gp.sprint_error(error_message))
296
297 if os_password == "":
298 os_password = BuiltIn().get_variable_value("${OS_PASSWORD}")
299 error_message = gv.svalid_value(os_password, var_name="os_password",
300 invalid_values=[None, ""])
301 if error_message != "":
302 BuiltIn().fail(gp.sprint_error(error_message))
303
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600304 invalid_req_states = [sub_state for sub_state in req_states
305 if sub_state not in valid_os_req_states]
306 if len(invalid_req_states) > 0:
307 error_message = "The following req_states are not supported:\n" +\
308 gp.sprint_var(invalid_req_states)
309 BuiltIn().fail(gp.sprint_error(error_message))
Michael Walsh70369fd2016-11-22 11:25:57 -0600310
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600311 # Initialize all substate values supported by this function.
312 os_ping = 0
313 os_login = 0
314 os_run_cmd = 0
Michael Walsh70369fd2016-11-22 11:25:57 -0600315
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600316 if os_up:
317 if 'os_ping' in req_states:
318 # See if the OS pings.
319 cmd_buf = "ping -c 1 -w 2 " + os_host
320 if not quiet:
321 grp.rpissuing(cmd_buf)
322 rc, out_buf = commands.getstatusoutput(cmd_buf)
323 if rc == 0:
324 os_ping = 1
Michael Walsh70369fd2016-11-22 11:25:57 -0600325
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600326 # Programming note: All attributes which do not require an ssh login
327 # should have been processed by this point.
328 master_req_login = ['os_login', 'os_run_cmd']
329 req_login = [sub_state for sub_state in req_states if sub_state in
330 master_req_login]
Michael Walsh70369fd2016-11-22 11:25:57 -0600331
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600332 must_login = (len([sub_state for sub_state in req_states
333 if sub_state in master_req_login]) > 0)
Michael Walsh70369fd2016-11-22 11:25:57 -0600334
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600335 if must_login:
336 # Open SSH connection to OS.
Michael Walshac275512017-03-07 11:39:28 -0600337 cmd_buf = ["SSHLibrary.Open Connection", os_host]
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600338 if not quiet:
339 grp.rpissuing_keyword(cmd_buf)
340 ix = BuiltIn().run_keyword(*cmd_buf)
341
342 # Login to OS.
343 cmd_buf = ["Login", os_username, os_password]
344 if not quiet:
345 grp.rpissuing_keyword(cmd_buf)
346 status, msg = BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
347 if status == "PASS":
348 os_login = 1
349
350 if os_login:
351 if 'os_run_cmd' in req_states:
352 if os_login:
353 # Try running a simple command (uptime) on the OS.
354 cmd_buf = ["Execute Command", "uptime",
355 "return_stderr=True", "return_rc=True"]
356 if not quiet:
357 grp.rpissuing_keyword(cmd_buf)
358 output, stderr_buf, rc = \
359 BuiltIn().run_keyword(*cmd_buf)
360 if rc == 0 and stderr_buf == "":
361 os_run_cmd = 1
362
363 os_state = DotDict()
364 for sub_state in req_states:
365 cmd_buf = "os_state['" + sub_state + "'] = str(" + sub_state + ")"
366 exec(cmd_buf)
Michael Walsh70369fd2016-11-22 11:25:57 -0600367
368 return os_state
369
370###############################################################################
371
372
373###############################################################################
374def get_state(openbmc_host="",
375 openbmc_username="",
376 openbmc_password="",
377 os_host="",
378 os_username="",
379 os_password="",
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600380 req_states=default_req_states,
Michael Walsh70369fd2016-11-22 11:25:57 -0600381 quiet=None):
382
383 r"""
384 Get component states such as power state, bmc state, etc, put them into a
385 dictionary and return them to the caller.
386
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600387 Note that all substate values are strings.
388
Michael Walsh70369fd2016-11-22 11:25:57 -0600389 Description of arguments:
390 openbmc_host The DNS name or IP address of the BMC.
391 This defaults to global ${OPENBMC_HOST}.
392 openbmc_username The username to be used to login to the BMC.
393 This defaults to global ${OPENBMC_USERNAME}.
394 openbmc_password The password to be used to login to the BMC.
395 This defaults to global ${OPENBMC_PASSWORD}.
396 os_host The DNS name or IP address of the operating system.
397 This defaults to global ${OS_HOST}.
398 os_username The username to be used to login to the OS.
399 This defaults to global ${OS_USERNAME}.
400 os_password The password to be used to login to the OS.
401 This defaults to global ${OS_PASSWORD}.
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600402 req_states This is a list of states whose values are being requested
403 by the caller.
Michael Walsh70369fd2016-11-22 11:25:57 -0600404 quiet Indicates whether status details (e.g. curl commands)
405 should be written to the console.
406 Defaults to either global value of ${QUIET} or to 1.
407 """
408
409 quiet = grp.set_quiet_default(quiet, 1)
410
411 # Set parm defaults where necessary and validate all parms.
412 if openbmc_host == "":
413 openbmc_host = BuiltIn().get_variable_value("${OPENBMC_HOST}")
414 error_message = gv.svalid_value(openbmc_host,
415 var_name="openbmc_host",
416 invalid_values=[None, ""])
417 if error_message != "":
418 BuiltIn().fail(gp.sprint_error(error_message))
419
420 if openbmc_username == "":
421 openbmc_username = BuiltIn().get_variable_value("${OPENBMC_USERNAME}")
422 error_message = gv.svalid_value(openbmc_username,
423 var_name="openbmc_username",
424 invalid_values=[None, ""])
425 if error_message != "":
426 BuiltIn().fail(gp.sprint_error(error_message))
427
428 if openbmc_password == "":
429 openbmc_password = BuiltIn().get_variable_value("${OPENBMC_PASSWORD}")
430 error_message = gv.svalid_value(openbmc_password,
431 var_name="openbmc_password",
432 invalid_values=[None, ""])
433 if error_message != "":
434 BuiltIn().fail(gp.sprint_error(error_message))
435
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600436 # NOTE: OS parms are optional.
Michael Walsh70369fd2016-11-22 11:25:57 -0600437 if os_host == "":
438 os_host = BuiltIn().get_variable_value("${OS_HOST}")
439 if os_host is None:
440 os_host = ""
441
442 if os_username is "":
443 os_username = BuiltIn().get_variable_value("${OS_USERNAME}")
444 if os_username is None:
445 os_username = ""
446
447 if os_password is "":
448 os_password = BuiltIn().get_variable_value("${OS_PASSWORD}")
449 if os_password is None:
450 os_password = ""
451
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600452 invalid_req_states = [sub_state for sub_state in req_states
453 if sub_state not in valid_req_states]
454 if len(invalid_req_states) > 0:
455 error_message = "The following req_states are not supported:\n" +\
456 gp.sprint_var(invalid_req_states)
457 BuiltIn().fail(gp.sprint_error(error_message))
458
459 # Initialize all substate values supported by this function.
460 ping = 0
461 packet_loss = ''
462 uptime = ''
463 epoch_seconds = ''
464 power = '0'
465 chassis = ''
466 bmc = ''
467 boot_progress = ''
468 host = ''
469
Michael Walsh70369fd2016-11-22 11:25:57 -0600470 # Get the component states.
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600471 if 'ping' in req_states:
472 # See if the OS pings.
473 cmd_buf = "ping -c 1 -w 2 " + openbmc_host
474 if not quiet:
475 grp.rpissuing(cmd_buf)
476 rc, out_buf = commands.getstatusoutput(cmd_buf)
477 if rc == 0:
478 ping = 1
479
480 if 'packet_loss' in req_states:
481 # See if the OS pings.
482 cmd_buf = "ping -c 5 -w 5 " + openbmc_host +\
483 " | egrep 'packet loss' | sed -re 's/.* ([0-9]+)%.*/\\1/g'"
484 if not quiet:
485 grp.rpissuing(cmd_buf)
486 rc, out_buf = commands.getstatusoutput(cmd_buf)
487 if rc == 0:
488 packet_loss = out_buf.rstrip("\n")
489
490 master_req_login = ['uptime', 'epoch_seconds']
491 req_login = [sub_state for sub_state in req_states if sub_state in
492 master_req_login]
493
494 must_login = (len([sub_state for sub_state in req_states
495 if sub_state in master_req_login]) > 0)
496
497 if must_login:
498 cmd_buf = ["Open Connection And Log In"]
499 if not quiet:
500 grp.rpissuing_keyword(cmd_buf)
501 BuiltIn().run_keyword(*cmd_buf)
502
503 if 'uptime' in req_states:
504 cmd_buf = ["Execute Command", "cat /proc/uptime | cut -f 1 -d ' '",
505 "return_stderr=True", "return_rc=True"]
506 if not quiet:
507 grp.rpissuing_keyword(cmd_buf)
508 stdout_buf, stderr_buf, rc = BuiltIn().run_keyword(*cmd_buf)
509 if rc == 0 and stderr_buf == "":
510 uptime = stdout_buf
511
512 if 'epoch_seconds' in req_states:
513 date_cmd_buf = "date -u +%s"
514 if USE_BMC_EPOCH_TIME:
515 cmd_buf = ["Execute Command", date_cmd_buf, "return_stderr=True",
516 "return_rc=True"]
517 if not quiet:
518 grp.rpissuing_keyword(cmd_buf)
519 stdout_buf, stderr_buf, rc = BuiltIn().run_keyword(*cmd_buf)
520 if rc == 0 and stderr_buf == "":
521 epoch_seconds = stdout_buf.rstrip("\n")
522 else:
523 shell_rc, out_buf = gc.cmd_fnc_u(date_cmd_buf,
524 quiet=1,
525 print_output=0)
526 if shell_rc == 0:
527 epoch_seconds = out_buf.rstrip("\n")
528
529 if 'power' in req_states:
Michael Walsh341c21e2017-01-17 16:25:20 -0600530 cmd_buf = ["Get Power State", "quiet=${" + str(quiet) + "}"]
531 grp.rdpissuing_keyword(cmd_buf)
532 power = BuiltIn().run_keyword(*cmd_buf)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600533 if 'chassis' in req_states:
Michael Walsh341c21e2017-01-17 16:25:20 -0600534 cmd_buf = ["Get Chassis Power State", "quiet=${" + str(quiet) + "}"]
535 grp.rdpissuing_keyword(cmd_buf)
536 chassis = BuiltIn().run_keyword(*cmd_buf)
537 # Strip everything up to the final period.
538 chassis = re.sub(r'.*\.', "", chassis)
Michael Walsh70369fd2016-11-22 11:25:57 -0600539
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600540 if 'bmc' in req_states:
Michael Walsh341c21e2017-01-17 16:25:20 -0600541 if OBMC_STATES_VERSION == 0:
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600542 qualifier = "utils"
Michael Walsh341c21e2017-01-17 16:25:20 -0600543 else:
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600544 qualifier = "state_manager"
545
546 cmd_buf = [qualifier + ".Get BMC State", "quiet=${" + str(quiet) + "}"]
547 # TODO: Re-enable this code once bmc status is working.
548 # grp.rdpissuing_keyword(cmd_buf)
549 # bmc = BuiltIn().run_keyword(*cmd_buf)
550
551 if 'boot_progress' in req_states:
552 cmd_buf = ["Get Boot Progress", "quiet=${" + str(quiet) + "}"]
Michael Walsh70369fd2016-11-22 11:25:57 -0600553 grp.rdpissuing_keyword(cmd_buf)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600554 boot_progress = BuiltIn().run_keyword(*cmd_buf)
555
556 if 'host' in req_states:
557 if OBMC_STATES_VERSION > 0:
558 cmd_buf = ["Get Host State", "quiet=${" + str(quiet) + "}"]
559 grp.rdpissuing_keyword(cmd_buf)
560 host = BuiltIn().run_keyword(*cmd_buf)
561 # Strip everything up to the final period.
562 host = re.sub(r'.*\.', "", host)
563
564 state = DotDict()
565 for sub_state in req_states:
566 if sub_state.startswith("os_"):
567 # We pass "os_" requests on to get_os_state.
568 continue
569 cmd_buf = "state['" + sub_state + "'] = str(" + sub_state + ")"
570 exec(cmd_buf)
571
572 if os_host == "":
573 # The caller has not specified an os_host so as far as we're concerned,
574 # it doesn't exist.
575 return state
576
577 os_req_states = [sub_state for sub_state in req_states
578 if sub_state.startswith('os_')]
579
580 if len(os_req_states) > 0:
581 # The caller has specified an os_host and they have requested
582 # information on os substates.
583
584 # Based on the information gathered on bmc, we'll try to make a
585 # determination of whether the os is even up. We'll pass the result
586 # of that assessment to get_os_state to enhance performance.
587 os_up_match = DotDict()
588 for sub_state in master_os_up_match:
589 if sub_state in req_states:
590 os_up_match[sub_state] = master_os_up_match[sub_state]
Michael Walsh70369fd2016-11-22 11:25:57 -0600591 os_up = compare_states(state, os_up_match)
592
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600593 os_state = get_os_state(os_host=os_host,
594 os_username=os_username,
595 os_password=os_password,
596 req_states=os_req_states,
597 os_up=os_up,
598 quiet=quiet)
599 # Append os_state dictionary to ours.
600 state.update(os_state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600601
602 return state
603
604###############################################################################
605
606
607###############################################################################
608def check_state(match_state,
609 invert=0,
610 print_string="",
611 openbmc_host="",
612 openbmc_username="",
613 openbmc_password="",
614 os_host="",
615 os_username="",
616 os_password="",
617 quiet=None):
618
619 r"""
620 Check that the Open BMC machine's composite state matches the specified
621 state. On success, this keyword returns the machine's composite state as a
622 dictionary.
623
624 Description of arguments:
625 match_state A dictionary whose key/value pairs are "state field"/
626 "state value". The state value is interpreted as a
627 regular expression. Example call from robot:
Michael Walsh341c21e2017-01-17 16:25:20 -0600628 ${match_state}= Create Dictionary chassis=^On$
629 ... bmc=^Ready$
Michael Walsh70369fd2016-11-22 11:25:57 -0600630 ... boot_progress=^FW Progress, Starting OS$
631 ${state}= Check State &{match_state}
632 invert If this flag is set, this function will succeed if the
633 states do NOT match.
634 print_string This function will print this string to the console prior
635 to getting the state.
636 openbmc_host The DNS name or IP address of the BMC.
637 This defaults to global ${OPENBMC_HOST}.
638 openbmc_username The username to be used to login to the BMC.
639 This defaults to global ${OPENBMC_USERNAME}.
640 openbmc_password The password to be used to login to the BMC.
641 This defaults to global ${OPENBMC_PASSWORD}.
642 os_host The DNS name or IP address of the operating system.
643 This defaults to global ${OS_HOST}.
644 os_username The username to be used to login to the OS.
645 This defaults to global ${OS_USERNAME}.
646 os_password The password to be used to login to the OS.
647 This defaults to global ${OS_PASSWORD}.
648 quiet Indicates whether status details should be written to the
649 console. Defaults to either global value of ${QUIET} or
650 to 1.
651 """
652
653 quiet = grp.set_quiet_default(quiet, 1)
654
655 grp.rprint(print_string)
656
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600657 req_states = match_state.keys()
Michael Walsh70369fd2016-11-22 11:25:57 -0600658 # Initialize state.
659 state = get_state(openbmc_host=openbmc_host,
660 openbmc_username=openbmc_username,
661 openbmc_password=openbmc_password,
662 os_host=os_host,
663 os_username=os_username,
664 os_password=os_password,
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600665 req_states=req_states,
Michael Walsh70369fd2016-11-22 11:25:57 -0600666 quiet=quiet)
667 if not quiet:
668 grp.rprint_var(state)
669
670 match = compare_states(state, match_state)
671
672 if invert and match:
673 fail_msg = "The current state of the machine matches the match" +\
674 " state:\n" + gp.sprint_varx("state", state)
675 BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
676 elif not invert and not match:
677 fail_msg = "The current state of the machine does NOT match the" +\
678 " match state:\n" +\
679 gp.sprint_varx("state", state)
680 BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
681
682 return state
683
684###############################################################################
685
686
687###############################################################################
Michael Walshf893ba02017-01-10 10:28:05 -0600688def wait_state(match_state=(),
Michael Walsh70369fd2016-11-22 11:25:57 -0600689 wait_time="1 min",
690 interval="1 second",
691 invert=0,
692 openbmc_host="",
693 openbmc_username="",
694 openbmc_password="",
695 os_host="",
696 os_username="",
697 os_password="",
698 quiet=None):
699
700 r"""
701 Wait for the Open BMC machine's composite state to match the specified
702 state. On success, this keyword returns the machine's composite state as
703 a dictionary.
704
705 Description of arguments:
706 match_state A dictionary whose key/value pairs are "state field"/
707 "state value". See check_state (above) for details.
708 wait_time The total amount of time to wait for the desired state.
709 This value may be expressed in Robot Framework's time
710 format (e.g. 1 minute, 2 min 3 s, 4.5).
711 interval The amount of time between state checks.
712 This value may be expressed in Robot Framework's time
713 format (e.g. 1 minute, 2 min 3 s, 4.5).
714 invert If this flag is set, this function will for the state of
715 the machine to cease to match the match state.
716 openbmc_host The DNS name or IP address of the BMC.
717 This defaults to global ${OPENBMC_HOST}.
718 openbmc_username The username to be used to login to the BMC.
719 This defaults to global ${OPENBMC_USERNAME}.
720 openbmc_password The password to be used to login to the BMC.
721 This defaults to global ${OPENBMC_PASSWORD}.
722 os_host The DNS name or IP address of the operating system.
723 This defaults to global ${OS_HOST}.
724 os_username The username to be used to login to the OS.
725 This defaults to global ${OS_USERNAME}.
726 os_password The password to be used to login to the OS.
727 This defaults to global ${OS_PASSWORD}.
728 quiet Indicates whether status details should be written to the
729 console. Defaults to either global value of ${QUIET} or
730 to 1.
731 """
732
733 quiet = grp.set_quiet_default(quiet, 1)
734
735 if not quiet:
736 if invert:
737 alt_text = "cease to "
738 else:
739 alt_text = ""
740 grp.rprint_timen("Checking every " + str(interval) + " for up to " +
741 str(wait_time) + " for the state of the machine to " +
742 alt_text + "match the state shown below.")
743 grp.rprint_var(match_state)
744
Michael Walshf893ba02017-01-10 10:28:05 -0600745 if quiet:
Michael Walsh341c21e2017-01-17 16:25:20 -0600746 print_string = ""
Michael Walshf893ba02017-01-10 10:28:05 -0600747 else:
Michael Walsh341c21e2017-01-17 16:25:20 -0600748 print_string = "#"
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600749
750 debug = int(BuiltIn().get_variable_value("${debug}", "0"))
751 if debug:
752 # In debug we print state so no need to print the "#".
753 print_string = ""
754 check_state_quiet = 1 - debug
Michael Walsh70369fd2016-11-22 11:25:57 -0600755 cmd_buf = ["Check State", match_state, "invert=${" + str(invert) + "}",
Michael Walshf893ba02017-01-10 10:28:05 -0600756 "print_string=" + print_string, "openbmc_host=" + openbmc_host,
Michael Walsh70369fd2016-11-22 11:25:57 -0600757 "openbmc_username=" + openbmc_username,
758 "openbmc_password=" + openbmc_password, "os_host=" + os_host,
759 "os_username=" + os_username, "os_password=" + os_password,
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600760 "quiet=${" + str(check_state_quiet) + "}"]
Michael Walsh70369fd2016-11-22 11:25:57 -0600761 grp.rdpissuing_keyword(cmd_buf)
762 state = BuiltIn().wait_until_keyword_succeeds(wait_time, interval,
763 *cmd_buf)
Michael Walsh70369fd2016-11-22 11:25:57 -0600764 if not quiet:
765 grp.rprintn()
766 if invert:
767 grp.rprint_timen("The states no longer match:")
768 else:
769 grp.rprint_timen("The states match:")
770 grp.rprint_var(state)
771
772 return state
773
774###############################################################################
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600775
776
777###############################################################################
778def wait_for_comm_cycle(start_boot_seconds):
779
780 r"""
781 Wait for communications to the BMC to stop working and then resume working.
782 This function is useful when you have initiated some kind of reboot.
783
784 Description of arguments:
785 start_boot_seconds The time that the boot test started. The format is the
786 epoch time in seconds, i.e. the number of seconds since
787 1970-01-01 00:00:00 UTC. This value should be obtained
788 from the BMC so that it is not dependent on any kind of
789 synchronization between this machine and the target BMC
790 This will allow this program to work correctly even in
791 a simulated environment. This value should be obtained
792 by the caller prior to initiating a reboot. It can be
793 obtained as follows:
794 state = st.get_state(req_states=['epoch_seconds'])
795 """
796
797 # Validate parms.
798 error_message = gv.svalid_integer(start_boot_seconds,
799 var_name="start_boot_seconds")
800 if error_message != "":
801 BuiltIn().fail(gp.sprint_error(error_message))
802
803 match_state = anchor_state(DotDict([('packet_loss', '100')]))
804 # Wait for 100% packet loss trying to ping machine.
805 wait_state(match_state, wait_time="3 mins", interval="0 seconds")
806
807 match_state['packet_loss'] = '^0$'
808 # Wait for 0% packet loss trying to ping machine.
809 wait_state(match_state, wait_time="4 mins", interval="0 seconds")
810
811 # Get the uptime and epoch seconds for comparisons. We want to be sure
812 # that the uptime is less than the elapsed boot time. Further proof that
813 # a reboot has indeed occurred (vs random network instability giving a
814 # false positive.
815 state = get_state(req_states=['uptime', 'epoch_seconds'])
816
817 elapsed_boot_time = int(state['epoch_seconds']) - start_boot_seconds
818 grp.rprint_var(elapsed_boot_time)
819 if int(float(state['uptime'])) < elapsed_boot_time:
820 uptime = state['uptime']
821 grp.rprint_var(uptime)
822 grp.rprint_timen("The uptime is less than the elapsed boot time," +
823 " as expected.")
824 else:
825 error_message = "The uptime is greater than the elapsed boot time," +\
826 " which is unexpected:\n" +\
827 gp.sprint_var(start_boot_seconds) +\
828 gp.sprint_var(state)
829 BuiltIn().fail(gp.sprint_error(error_message))
830
831 grp.rprint_timen("Verifying that REST API interface is working.")
832 match_state = DotDict([('chassis', '.*')])
833 state = wait_state(match_state, wait_time="5 mins", interval="2 seconds")
834
835###############################################################################