blob: 6ac103c3c572410ab5c9ede78b716196542cc7a5 [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
Michael Walsh341c21e2017-01-17 16:25:20 -060013 default_state[boot_progress]: FW Progress, Starting OS
Michael Walsh65b12542017-02-03 15:34:38 -060014 default_state[host]: Running
Michael Walsh341c21e2017-01-17 16:25:20 -060015 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
Michael Walsh8fae6ea2017-02-20 16:14:44 -060020state. Support for new pieces of state information may be added to this
21module as needed.
Michael Walsh70369fd2016-11-22 11:25:57 -060022
23By using the wait_state function, a caller can start a boot and then wait for
24a precisely defined state to indicate that the boot has succeeded. If
25the boot fails, they can see exactly why by looking at the current state as
26compared with the expected state.
27"""
28
29import gen_print as gp
30import gen_robot_print as grp
31import gen_valid as gv
Michael Walsh16cbb7f2017-02-02 15:54:16 -060032import gen_robot_utils as gru
Michael Walsh8fae6ea2017-02-20 16:14:44 -060033import gen_cmd as gc
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
Michael Walsh619aa332017-04-12 15:56:51 -050042# We need utils.robot to get keywords like "Get Chassis 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 Walsh8fae6ea2017-02-20 16:14:44 -060046# The BMC code has recently been changed as far as what states are defined and
47# what the state values can be. This module now has a means of processing both
48# the 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
Michael Walsh8fae6ea2017-02-20 16:14:44 -060052# not set it will default to 1.
Michael Walsh341c21e2017-01-17 16:25:20 -060053
Michael Walsh619aa332017-04-12 15:56:51 -050054# As of the present moment, OBMC_STATES_VERSION of 0 is for cold that is so old
55# that it is no longer worthwhile to maintain. The OBMC_STATES_VERSION 0 code
56# is being removed but the OBMC_STATES_VERSION value will stay for now in the
57# event that it is needed in the future.
58
Michael Walsh8fae6ea2017-02-20 16:14:44 -060059OBMC_STATES_VERSION = int(os.environ.get('OBMC_STATES_VERSION', 1))
Michael Walsh341c21e2017-01-17 16:25:20 -060060
Michael Walsh619aa332017-04-12 15:56:51 -050061# When a user calls get_state w/o specifying req_states, default_req_states
62# is used as its value.
63default_req_states = ['rest',
64 'chassis',
65 'bmc',
66 'boot_progress',
67 'host',
68 'os_ping',
69 'os_login',
70 'os_run_cmd']
Michael Walsh8fae6ea2017-02-20 16:14:44 -060071
Michael Walsh619aa332017-04-12 15:56:51 -050072# valid_req_states is a list of sub states supported by the get_state function.
73# valid_req_states, default_req_states and master_os_up_match are used by the
74# get_state function.
75valid_req_states = ['ping',
76 'packet_loss',
77 'uptime',
78 'epoch_seconds',
79 'rest',
80 'chassis',
81 'bmc',
82 'boot_progress',
83 'host',
84 'os_ping',
85 'os_login',
86 'os_run_cmd']
Michael Walsh8fae6ea2017-02-20 16:14:44 -060087
88# valid_os_req_states and default_os_req_states are used by the os_get_state
89# function.
90# valid_os_req_states is a list of state information supported by the
91# get_os_state function.
92valid_os_req_states = ['os_ping',
93 'os_login',
94 'os_run_cmd']
95# When a user calls get_os_state w/o specifying req_states,
96# default_os_req_states is used as its value.
97default_os_req_states = ['os_ping',
98 'os_login',
99 'os_run_cmd']
100
101# Presently, some BMCs appear to not keep time very well. This environment
102# variable directs the get_state function to use either the BMC's epoch time
103# or the local epoch time.
104USE_BMC_EPOCH_TIME = int(os.environ.get('USE_BMC_EPOCH_TIME', 0))
Michael Walsh341c21e2017-01-17 16:25:20 -0600105
Michael Walsh619aa332017-04-12 15:56:51 -0500106# Useful state constant definition(s).
107# A match state for checking that the system is at "standby".
108standby_match_state = DotDict([('rest', '^1$'),
109 ('chassis', '^Off$'),
110 ('bmc', '^Ready$'),
111 ('boot_progress', ''),
112 ('host', '')])
113
114# default_state is an initial value which may be of use to callers.
115default_state = DotDict([('rest', '1'),
116 ('chassis', 'On'),
117 ('bmc', 'Ready'),
118 ('boot_progress', 'FW Progress, Starting OS'),
119 ('host', 'Running'),
120 ('os_ping', '1'),
121 ('os_login', '1'),
122 ('os_run_cmd', '1')])
123
124# A master dictionary to determine whether the os may be up.
125master_os_up_match = DotDict([('chassis', '^On$'),
126 ('bmc', '^Ready$'),
127 ('boot_progress',
128 'FW Progress, Starting OS'),
129 ('host', '^Running$')])
130
Michael Walsh341c21e2017-01-17 16:25:20 -0600131
132###############################################################################
133def return_default_state():
134
135 r"""
136 Return default state dictionary.
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600137
138 default_state is an initial value which may be of use to callers.
Michael Walsh341c21e2017-01-17 16:25:20 -0600139 """
140
141 return default_state
142
143###############################################################################
144
Michael Walsh70369fd2016-11-22 11:25:57 -0600145
Michael Walsh619aa332017-04-12 15:56:51 -0500146valid_state_constants = ['default', 'standby_match_state']
147
148
149###############################################################################
150def return_state_constant(state_name='default'):
151
152 r"""
153 Return default state dictionary.
154
155 default_state is an initial value which may be of use to callers.
156 """
157
158 error_message = gv.svalid_value(state_name, var_name='state_name',
159 valid_values=valid_state_constants)
160 if error_message != "":
161 BuiltIn().fail(gp.sprint_error(error_message))
162
163 if state_name == 'default':
164 return default_state
165 elif state_name == 'standby_match_state':
166 return standby_match_state
167
168###############################################################################
169
170
Michael Walsh70369fd2016-11-22 11:25:57 -0600171###############################################################################
172def anchor_state(state):
173
174 r"""
175 Add regular expression anchors ("^" and "$") to the beginning and end of
176 each item in the state dictionary passed in. Return the resulting
177 dictionary.
178
179 Description of Arguments:
180 state A dictionary such as the one returned by the get_state()
181 function.
182 """
183
Michael Walsh2ce067a2017-02-27 14:24:07 -0600184 anchored_state = state.copy()
Michael Walsh70369fd2016-11-22 11:25:57 -0600185 for key, match_state_value in anchored_state.items():
186 anchored_state[key] = "^" + str(anchored_state[key]) + "$"
187
188 return anchored_state
189
190###############################################################################
191
192
193###############################################################################
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600194def strip_anchor_state(state):
195
196 r"""
197 Strip regular expression anchors ("^" and "$") from the beginning and end
198 of each item in the state dictionary passed in. Return the resulting
199 dictionary.
200
201 Description of Arguments:
202 state A dictionary such as the one returned by the get_state()
203 function.
204 """
205
Michael Walsh2ce067a2017-02-27 14:24:07 -0600206 stripped_state = state.copy()
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600207 for key, match_state_value in stripped_state.items():
208 stripped_state[key] = stripped_state[key].strip("^$")
209
210 return stripped_state
211
212###############################################################################
213
214
215###############################################################################
Michael Walsh70369fd2016-11-22 11:25:57 -0600216def compare_states(state,
217 match_state):
218
219 r"""
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600220 Compare 2 state dictionaries. Return True if they match and False if they
Michael Walsh70369fd2016-11-22 11:25:57 -0600221 don't. Note that the match_state dictionary does not need to have an entry
222 corresponding to each entry in the state dictionary. But for each entry
223 that it does have, the corresponding state entry will be checked for a
224 match.
225
226 Description of arguments:
227 state A state dictionary such as the one returned by the
228 get_state function.
229 match_state A dictionary whose key/value pairs are "state field"/
230 "state value". The state value is interpreted as a
231 regular expression. Every value in this dictionary is
232 considered. If each and every one matches, the 2
233 dictionaries are considered to be matching.
234 """
235
236 match = True
237 for key, match_state_value in match_state.items():
Michael Walsh97df71c2017-03-27 14:33:24 -0500238 # Blank match_state_value means "don't care".
239 if match_state_value == "":
240 continue
Michael Walsh70369fd2016-11-22 11:25:57 -0600241 try:
242 if not re.match(match_state_value, str(state[key])):
243 match = False
244 break
245 except KeyError:
246 match = False
247 break
248
249 return match
250
251###############################################################################
252
253
254###############################################################################
255def get_os_state(os_host="",
256 os_username="",
257 os_password="",
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600258 req_states=default_os_req_states,
259 os_up=True,
Michael Walsh70369fd2016-11-22 11:25:57 -0600260 quiet=None):
261
262 r"""
263 Get component states for the operating system such as ping, login,
264 etc, put them into a dictionary and return them to the caller.
265
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600266 Note that all substate values are strings.
267
Michael Walsh70369fd2016-11-22 11:25:57 -0600268 Description of arguments:
269 os_host The DNS name or IP address of the operating system.
270 This defaults to global ${OS_HOST}.
271 os_username The username to be used to login to the OS.
272 This defaults to global ${OS_USERNAME}.
273 os_password The password to be used to login to the OS.
274 This defaults to global ${OS_PASSWORD}.
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600275 req_states This is a list of states whose values are being requested by
276 the caller.
277 os_up If the caller knows that the os can't possibly be up, it can
278 improve performance by passing os_up=False. This function
279 will then simply return default values for all requested os
280 sub states.
Michael Walsh70369fd2016-11-22 11:25:57 -0600281 quiet Indicates whether status details (e.g. curl commands) should
282 be written to the console.
283 Defaults to either global value of ${QUIET} or to 1.
284 """
285
Michael Walsh619aa332017-04-12 15:56:51 -0500286 quiet = int(gp.get_var_value(quiet, 0))
Michael Walsh70369fd2016-11-22 11:25:57 -0600287
288 # Set parm defaults where necessary and validate all parms.
289 if os_host == "":
290 os_host = BuiltIn().get_variable_value("${OS_HOST}")
291 error_message = gv.svalid_value(os_host, var_name="os_host",
292 invalid_values=[None, ""])
293 if error_message != "":
294 BuiltIn().fail(gp.sprint_error(error_message))
295
296 if os_username == "":
297 os_username = BuiltIn().get_variable_value("${OS_USERNAME}")
298 error_message = gv.svalid_value(os_username, var_name="os_username",
299 invalid_values=[None, ""])
300 if error_message != "":
301 BuiltIn().fail(gp.sprint_error(error_message))
302
303 if os_password == "":
304 os_password = BuiltIn().get_variable_value("${OS_PASSWORD}")
305 error_message = gv.svalid_value(os_password, var_name="os_password",
306 invalid_values=[None, ""])
307 if error_message != "":
308 BuiltIn().fail(gp.sprint_error(error_message))
309
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600310 invalid_req_states = [sub_state for sub_state in req_states
311 if sub_state not in valid_os_req_states]
312 if len(invalid_req_states) > 0:
313 error_message = "The following req_states are not supported:\n" +\
314 gp.sprint_var(invalid_req_states)
315 BuiltIn().fail(gp.sprint_error(error_message))
Michael Walsh70369fd2016-11-22 11:25:57 -0600316
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600317 # Initialize all substate values supported by this function.
318 os_ping = 0
319 os_login = 0
320 os_run_cmd = 0
Michael Walsh70369fd2016-11-22 11:25:57 -0600321
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600322 if os_up:
323 if 'os_ping' in req_states:
324 # See if the OS pings.
325 cmd_buf = "ping -c 1 -w 2 " + os_host
326 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500327 gp.pissuing(cmd_buf)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600328 rc, out_buf = commands.getstatusoutput(cmd_buf)
329 if rc == 0:
330 os_ping = 1
Michael Walsh70369fd2016-11-22 11:25:57 -0600331
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600332 # Programming note: All attributes which do not require an ssh login
333 # should have been processed by this point.
334 master_req_login = ['os_login', 'os_run_cmd']
335 req_login = [sub_state for sub_state in req_states if sub_state in
336 master_req_login]
Michael Walsh97df71c2017-03-27 14:33:24 -0500337 must_login = (len(req_login) > 0)
Michael Walsh70369fd2016-11-22 11:25:57 -0600338
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600339 if must_login:
Michael Walsh3eb50022017-03-21 11:27:30 -0500340 # Open SSH connection to OS. Note that this doesn't fail even when
341 # the OS is not up.
Michael Walshac275512017-03-07 11:39:28 -0600342 cmd_buf = ["SSHLibrary.Open Connection", os_host]
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600343 if not quiet:
344 grp.rpissuing_keyword(cmd_buf)
345 ix = BuiltIn().run_keyword(*cmd_buf)
346
347 # Login to OS.
348 cmd_buf = ["Login", os_username, os_password]
349 if not quiet:
350 grp.rpissuing_keyword(cmd_buf)
Michael Walsh3eb50022017-03-21 11:27:30 -0500351 status, ret_values = \
352 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600353 if status == "PASS":
354 os_login = 1
Michael Walsh3eb50022017-03-21 11:27:30 -0500355 else:
356 gp.dprint_var(status)
357 gp.dprint_var(ret_values)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600358
359 if os_login:
360 if 'os_run_cmd' in req_states:
Michael Walsh3eb50022017-03-21 11:27:30 -0500361 # Try running a simple command (uptime) on the OS.
362 cmd_buf = ["Execute Command", "uptime",
363 "return_stderr=True", "return_rc=True"]
364 if not quiet:
365 grp.rpissuing_keyword(cmd_buf)
366 # Note that in spite of its name, there are occasions
367 # where run_keyword_and_ignore_error can fail.
368 status, ret_values = \
369 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
370 if status == "PASS":
371 stdout, stderr, rc = ret_values
372 if rc == 0 and stderr == "":
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600373 os_run_cmd = 1
Michael Walsh3eb50022017-03-21 11:27:30 -0500374 else:
375 gp.dprint_var(status)
376 gp.dprint_var(stdout)
377 gp.dprint_var(stderr)
378 gp.dprint_var(rc)
379 else:
380 gp.dprint_var(status)
381 gp.dprint_var(ret_values)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600382
383 os_state = DotDict()
384 for sub_state in req_states:
385 cmd_buf = "os_state['" + sub_state + "'] = str(" + sub_state + ")"
386 exec(cmd_buf)
Michael Walsh70369fd2016-11-22 11:25:57 -0600387
388 return os_state
389
390###############################################################################
391
392
393###############################################################################
394def get_state(openbmc_host="",
395 openbmc_username="",
396 openbmc_password="",
397 os_host="",
398 os_username="",
399 os_password="",
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600400 req_states=default_req_states,
Michael Walsh70369fd2016-11-22 11:25:57 -0600401 quiet=None):
402
403 r"""
Michael Walsh619aa332017-04-12 15:56:51 -0500404 Get component states such as chassis state, bmc state, etc, put them into a
Michael Walsh70369fd2016-11-22 11:25:57 -0600405 dictionary and return them to the caller.
406
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600407 Note that all substate values are strings.
408
Michael Walsh70369fd2016-11-22 11:25:57 -0600409 Description of arguments:
410 openbmc_host The DNS name or IP address of the BMC.
411 This defaults to global ${OPENBMC_HOST}.
412 openbmc_username The username to be used to login to the BMC.
413 This defaults to global ${OPENBMC_USERNAME}.
414 openbmc_password The password to be used to login to the BMC.
415 This defaults to global ${OPENBMC_PASSWORD}.
416 os_host The DNS name or IP address of the operating system.
417 This defaults to global ${OS_HOST}.
418 os_username The username to be used to login to the OS.
419 This defaults to global ${OS_USERNAME}.
420 os_password The password to be used to login to the OS.
421 This defaults to global ${OS_PASSWORD}.
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600422 req_states This is a list of states whose values are being requested
423 by the caller.
Michael Walsh70369fd2016-11-22 11:25:57 -0600424 quiet Indicates whether status details (e.g. curl commands)
425 should be written to the console.
426 Defaults to either global value of ${QUIET} or to 1.
427 """
428
Michael Walsh619aa332017-04-12 15:56:51 -0500429 quiet = int(gp.get_var_value(quiet, 0))
Michael Walsh70369fd2016-11-22 11:25:57 -0600430
431 # Set parm defaults where necessary and validate all parms.
432 if openbmc_host == "":
433 openbmc_host = BuiltIn().get_variable_value("${OPENBMC_HOST}")
434 error_message = gv.svalid_value(openbmc_host,
435 var_name="openbmc_host",
436 invalid_values=[None, ""])
437 if error_message != "":
438 BuiltIn().fail(gp.sprint_error(error_message))
439
440 if openbmc_username == "":
441 openbmc_username = BuiltIn().get_variable_value("${OPENBMC_USERNAME}")
442 error_message = gv.svalid_value(openbmc_username,
443 var_name="openbmc_username",
444 invalid_values=[None, ""])
445 if error_message != "":
446 BuiltIn().fail(gp.sprint_error(error_message))
447
448 if openbmc_password == "":
449 openbmc_password = BuiltIn().get_variable_value("${OPENBMC_PASSWORD}")
450 error_message = gv.svalid_value(openbmc_password,
451 var_name="openbmc_password",
452 invalid_values=[None, ""])
453 if error_message != "":
454 BuiltIn().fail(gp.sprint_error(error_message))
455
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600456 # NOTE: OS parms are optional.
Michael Walsh70369fd2016-11-22 11:25:57 -0600457 if os_host == "":
458 os_host = BuiltIn().get_variable_value("${OS_HOST}")
459 if os_host is None:
460 os_host = ""
461
462 if os_username is "":
463 os_username = BuiltIn().get_variable_value("${OS_USERNAME}")
464 if os_username is None:
465 os_username = ""
466
467 if os_password is "":
468 os_password = BuiltIn().get_variable_value("${OS_PASSWORD}")
469 if os_password is None:
470 os_password = ""
471
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600472 invalid_req_states = [sub_state for sub_state in req_states
473 if sub_state not in valid_req_states]
474 if len(invalid_req_states) > 0:
475 error_message = "The following req_states are not supported:\n" +\
476 gp.sprint_var(invalid_req_states)
477 BuiltIn().fail(gp.sprint_error(error_message))
478
479 # Initialize all substate values supported by this function.
480 ping = 0
481 packet_loss = ''
482 uptime = ''
483 epoch_seconds = ''
Michael Walshb95eb542017-03-31 09:39:20 -0500484 rest = '1'
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600485 chassis = ''
486 bmc = ''
487 boot_progress = ''
488 host = ''
489
Michael Walsh70369fd2016-11-22 11:25:57 -0600490 # Get the component states.
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600491 if 'ping' in req_states:
492 # See if the OS pings.
493 cmd_buf = "ping -c 1 -w 2 " + openbmc_host
494 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500495 gp.pissuing(cmd_buf)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600496 rc, out_buf = commands.getstatusoutput(cmd_buf)
497 if rc == 0:
498 ping = 1
499
500 if 'packet_loss' in req_states:
501 # See if the OS pings.
502 cmd_buf = "ping -c 5 -w 5 " + openbmc_host +\
503 " | egrep 'packet loss' | sed -re 's/.* ([0-9]+)%.*/\\1/g'"
504 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500505 gp.pissuing(cmd_buf)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600506 rc, out_buf = commands.getstatusoutput(cmd_buf)
507 if rc == 0:
508 packet_loss = out_buf.rstrip("\n")
509
510 master_req_login = ['uptime', 'epoch_seconds']
511 req_login = [sub_state for sub_state in req_states if sub_state in
512 master_req_login]
Michael Walsh97df71c2017-03-27 14:33:24 -0500513 must_login = (len(req_login) > 0)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600514
Michael Walsh97df71c2017-03-27 14:33:24 -0500515 bmc_login = 0
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600516 if must_login:
517 cmd_buf = ["Open Connection And Log In"]
518 if not quiet:
519 grp.rpissuing_keyword(cmd_buf)
Michael Walsh97df71c2017-03-27 14:33:24 -0500520 status, ret_values = \
521 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
522 if status == "PASS":
523 bmc_login = 1
524 else:
525 if re.match('^Authentication failed for user', ret_values):
526 # An authentication failure is worth failing on.
527 BuiltIn().fail(gp.sprint_error(ret_values))
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600528
Michael Walsh97df71c2017-03-27 14:33:24 -0500529 if 'uptime' in req_states and bmc_login:
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600530 cmd_buf = ["Execute Command", "cat /proc/uptime | cut -f 1 -d ' '",
531 "return_stderr=True", "return_rc=True"]
532 if not quiet:
533 grp.rpissuing_keyword(cmd_buf)
Michael Walsh97df71c2017-03-27 14:33:24 -0500534 status, ret_values = \
535 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
536 if status == "PASS":
537 stdout, stderr, rc = ret_values
538 if rc == 0 and stderr == "":
539 uptime = stdout
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600540
Michael Walsh97df71c2017-03-27 14:33:24 -0500541 if 'epoch_seconds' in req_states and bmc_login:
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600542 date_cmd_buf = "date -u +%s"
543 if USE_BMC_EPOCH_TIME:
544 cmd_buf = ["Execute Command", date_cmd_buf, "return_stderr=True",
545 "return_rc=True"]
546 if not quiet:
547 grp.rpissuing_keyword(cmd_buf)
Michael Walsh97df71c2017-03-27 14:33:24 -0500548 status, ret_values = \
549 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
550 if status == "PASS":
551 stdout, stderr, rc = ret_values
552 if rc == 0 and stderr == "":
553 epoch_seconds = stdout.rstrip("\n")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600554 else:
555 shell_rc, out_buf = gc.cmd_fnc_u(date_cmd_buf,
556 quiet=1,
557 print_output=0)
558 if shell_rc == 0:
559 epoch_seconds = out_buf.rstrip("\n")
560
Michael Walsh619aa332017-04-12 15:56:51 -0500561 master_req_rest = ['rest', 'chassis', 'bmc', 'boot_progress',
Michael Walshb95eb542017-03-31 09:39:20 -0500562 'host']
563 req_rest = [sub_state for sub_state in req_states if sub_state in
564 master_req_rest]
565 need_rest = (len(req_rest) > 0)
566
567 # Though we could try to determine 'rest' state on any of several calls,
568 # for simplicity, we'll use 'chassis' to figure it out (even if the caller
569 # hasn't explicitly asked for 'chassis').
570 if 'chassis' in req_states or need_rest:
Michael Walsh341c21e2017-01-17 16:25:20 -0600571 cmd_buf = ["Get Chassis Power State", "quiet=${" + str(quiet) + "}"]
572 grp.rdpissuing_keyword(cmd_buf)
Michael Walsh97df71c2017-03-27 14:33:24 -0500573 status, ret_values = \
574 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
575 if status == "PASS":
576 chassis = ret_values
577 chassis = re.sub(r'.*\.', "", chassis)
Michael Walshb95eb542017-03-31 09:39:20 -0500578 rest = '1'
Michael Walsh341c21e2017-01-17 16:25:20 -0600579 else:
Michael Walshb95eb542017-03-31 09:39:20 -0500580 rest = ret_values
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600581
Michael Walshb95eb542017-03-31 09:39:20 -0500582 if rest == '1':
Michael Walshb95eb542017-03-31 09:39:20 -0500583 if 'bmc' in req_states:
584 if OBMC_STATES_VERSION == 0:
585 qualifier = "utils"
586 else:
587 # This will not be supported much longer.
588 qualifier = "state_manager"
589 cmd_buf = [qualifier + ".Get BMC State",
590 "quiet=${" + str(quiet) + "}"]
591 grp.rdpissuing_keyword(cmd_buf)
592 status, ret_values = \
593 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
594 if status == "PASS":
595 bmc = ret_values
596
597 if 'boot_progress' in req_states:
598 cmd_buf = ["Get Boot Progress", "quiet=${" + str(quiet) + "}"]
599 grp.rdpissuing_keyword(cmd_buf)
600 status, ret_values = \
601 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
602 if status == "PASS":
603 boot_progress = ret_values
604
605 if 'host' in req_states:
606 if OBMC_STATES_VERSION > 0:
607 cmd_buf = ["Get Host State", "quiet=${" + str(quiet) + "}"]
608 grp.rdpissuing_keyword(cmd_buf)
609 status, ret_values = \
610 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
611 if status == "PASS":
612 host = ret_values
613 # Strip everything up to the final period.
614 host = re.sub(r'.*\.', "", host)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600615
616 state = DotDict()
617 for sub_state in req_states:
618 if sub_state.startswith("os_"):
619 # We pass "os_" requests on to get_os_state.
620 continue
621 cmd_buf = "state['" + sub_state + "'] = str(" + sub_state + ")"
622 exec(cmd_buf)
623
624 if os_host == "":
625 # The caller has not specified an os_host so as far as we're concerned,
626 # it doesn't exist.
627 return state
628
629 os_req_states = [sub_state for sub_state in req_states
630 if sub_state.startswith('os_')]
631
632 if len(os_req_states) > 0:
633 # The caller has specified an os_host and they have requested
634 # information on os substates.
635
636 # Based on the information gathered on bmc, we'll try to make a
637 # determination of whether the os is even up. We'll pass the result
638 # of that assessment to get_os_state to enhance performance.
639 os_up_match = DotDict()
640 for sub_state in master_os_up_match:
641 if sub_state in req_states:
642 os_up_match[sub_state] = master_os_up_match[sub_state]
Michael Walsh70369fd2016-11-22 11:25:57 -0600643 os_up = compare_states(state, os_up_match)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600644 os_state = get_os_state(os_host=os_host,
645 os_username=os_username,
646 os_password=os_password,
647 req_states=os_req_states,
648 os_up=os_up,
649 quiet=quiet)
650 # Append os_state dictionary to ours.
651 state.update(os_state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600652
653 return state
654
655###############################################################################
656
657
658###############################################################################
659def check_state(match_state,
660 invert=0,
661 print_string="",
662 openbmc_host="",
663 openbmc_username="",
664 openbmc_password="",
665 os_host="",
666 os_username="",
667 os_password="",
668 quiet=None):
669
670 r"""
671 Check that the Open BMC machine's composite state matches the specified
672 state. On success, this keyword returns the machine's composite state as a
673 dictionary.
674
675 Description of arguments:
676 match_state A dictionary whose key/value pairs are "state field"/
677 "state value". The state value is interpreted as a
678 regular expression. Example call from robot:
Michael Walsh341c21e2017-01-17 16:25:20 -0600679 ${match_state}= Create Dictionary chassis=^On$
680 ... bmc=^Ready$
Michael Walsh70369fd2016-11-22 11:25:57 -0600681 ... boot_progress=^FW Progress, Starting OS$
682 ${state}= Check State &{match_state}
683 invert If this flag is set, this function will succeed if the
684 states do NOT match.
685 print_string This function will print this string to the console prior
686 to getting the state.
687 openbmc_host The DNS name or IP address of the BMC.
688 This defaults to global ${OPENBMC_HOST}.
689 openbmc_username The username to be used to login to the BMC.
690 This defaults to global ${OPENBMC_USERNAME}.
691 openbmc_password The password to be used to login to the BMC.
692 This defaults to global ${OPENBMC_PASSWORD}.
693 os_host The DNS name or IP address of the operating system.
694 This defaults to global ${OS_HOST}.
695 os_username The username to be used to login to the OS.
696 This defaults to global ${OS_USERNAME}.
697 os_password The password to be used to login to the OS.
698 This defaults to global ${OS_PASSWORD}.
699 quiet Indicates whether status details should be written to the
700 console. Defaults to either global value of ${QUIET} or
701 to 1.
702 """
703
Michael Walsh619aa332017-04-12 15:56:51 -0500704 quiet = int(gp.get_var_value(quiet, 0))
Michael Walsh70369fd2016-11-22 11:25:57 -0600705
706 grp.rprint(print_string)
707
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600708 req_states = match_state.keys()
Michael Walsh70369fd2016-11-22 11:25:57 -0600709 # Initialize state.
710 state = get_state(openbmc_host=openbmc_host,
711 openbmc_username=openbmc_username,
712 openbmc_password=openbmc_password,
713 os_host=os_host,
714 os_username=os_username,
715 os_password=os_password,
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600716 req_states=req_states,
Michael Walsh70369fd2016-11-22 11:25:57 -0600717 quiet=quiet)
718 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500719 gp.print_var(state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600720
721 match = compare_states(state, match_state)
722
723 if invert and match:
724 fail_msg = "The current state of the machine matches the match" +\
725 " state:\n" + gp.sprint_varx("state", state)
726 BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
727 elif not invert and not match:
728 fail_msg = "The current state of the machine does NOT match the" +\
729 " match state:\n" +\
730 gp.sprint_varx("state", state)
731 BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
732
733 return state
734
735###############################################################################
736
737
738###############################################################################
Michael Walshf893ba02017-01-10 10:28:05 -0600739def wait_state(match_state=(),
Michael Walsh70369fd2016-11-22 11:25:57 -0600740 wait_time="1 min",
741 interval="1 second",
742 invert=0,
743 openbmc_host="",
744 openbmc_username="",
745 openbmc_password="",
746 os_host="",
747 os_username="",
748 os_password="",
749 quiet=None):
750
751 r"""
752 Wait for the Open BMC machine's composite state to match the specified
753 state. On success, this keyword returns the machine's composite state as
754 a dictionary.
755
756 Description of arguments:
757 match_state A dictionary whose key/value pairs are "state field"/
758 "state value". See check_state (above) for details.
Michael Walsh619aa332017-04-12 15:56:51 -0500759 This value may also be any string accepted by
760 return_state_constant (e.g. "standby_match_state").
761 In such a case this function will call
762 return_state_constant to convert it to a proper
763 dictionary as described above.
Michael Walsh70369fd2016-11-22 11:25:57 -0600764 wait_time The total amount of time to wait for the desired state.
765 This value may be expressed in Robot Framework's time
766 format (e.g. 1 minute, 2 min 3 s, 4.5).
767 interval The amount of time between state checks.
768 This value may be expressed in Robot Framework's time
769 format (e.g. 1 minute, 2 min 3 s, 4.5).
770 invert If this flag is set, this function will for the state of
771 the machine to cease to match the match state.
772 openbmc_host The DNS name or IP address of the BMC.
773 This defaults to global ${OPENBMC_HOST}.
774 openbmc_username The username to be used to login to the BMC.
775 This defaults to global ${OPENBMC_USERNAME}.
776 openbmc_password The password to be used to login to the BMC.
777 This defaults to global ${OPENBMC_PASSWORD}.
778 os_host The DNS name or IP address of the operating system.
779 This defaults to global ${OS_HOST}.
780 os_username The username to be used to login to the OS.
781 This defaults to global ${OS_USERNAME}.
782 os_password The password to be used to login to the OS.
783 This defaults to global ${OS_PASSWORD}.
784 quiet Indicates whether status details should be written to the
785 console. Defaults to either global value of ${QUIET} or
786 to 1.
787 """
788
Michael Walsh619aa332017-04-12 15:56:51 -0500789 quiet = int(gp.get_var_value(quiet, 0))
790
791 if type(match_state) in (str, unicode):
792 match_state = return_state_constant(match_state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600793
794 if not quiet:
795 if invert:
796 alt_text = "cease to "
797 else:
798 alt_text = ""
Michael Walsh3eb50022017-03-21 11:27:30 -0500799 gp.print_timen("Checking every " + str(interval) + " for up to " +
800 str(wait_time) + " for the state of the machine to " +
801 alt_text + "match the state shown below.")
802 gp.print_var(match_state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600803
Michael Walshf893ba02017-01-10 10:28:05 -0600804 if quiet:
Michael Walsh341c21e2017-01-17 16:25:20 -0600805 print_string = ""
Michael Walshf893ba02017-01-10 10:28:05 -0600806 else:
Michael Walsh341c21e2017-01-17 16:25:20 -0600807 print_string = "#"
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600808
809 debug = int(BuiltIn().get_variable_value("${debug}", "0"))
810 if debug:
811 # In debug we print state so no need to print the "#".
812 print_string = ""
813 check_state_quiet = 1 - debug
Michael Walsh70369fd2016-11-22 11:25:57 -0600814 cmd_buf = ["Check State", match_state, "invert=${" + str(invert) + "}",
Michael Walshf893ba02017-01-10 10:28:05 -0600815 "print_string=" + print_string, "openbmc_host=" + openbmc_host,
Michael Walsh70369fd2016-11-22 11:25:57 -0600816 "openbmc_username=" + openbmc_username,
817 "openbmc_password=" + openbmc_password, "os_host=" + os_host,
818 "os_username=" + os_username, "os_password=" + os_password,
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600819 "quiet=${" + str(check_state_quiet) + "}"]
Michael Walsh70369fd2016-11-22 11:25:57 -0600820 grp.rdpissuing_keyword(cmd_buf)
Michael Walsh619aa332017-04-12 15:56:51 -0500821 try:
822 state = BuiltIn().wait_until_keyword_succeeds(wait_time, interval,
823 *cmd_buf)
824 except AssertionError as my_assertion_error:
825 gp.printn()
826 message = my_assertion_error.args[0]
827 BuiltIn().fail(message)
828
Michael Walsh70369fd2016-11-22 11:25:57 -0600829 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500830 gp.printn()
Michael Walsh70369fd2016-11-22 11:25:57 -0600831 if invert:
Michael Walsh3eb50022017-03-21 11:27:30 -0500832 gp.print_timen("The states no longer match:")
Michael Walsh70369fd2016-11-22 11:25:57 -0600833 else:
Michael Walsh3eb50022017-03-21 11:27:30 -0500834 gp.print_timen("The states match:")
835 gp.print_var(state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600836
837 return state
838
839###############################################################################
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600840
841
842###############################################################################
Michael Walsh619aa332017-04-12 15:56:51 -0500843def wait_for_comm_cycle(start_boot_seconds,
844 quiet=None):
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600845
846 r"""
847 Wait for communications to the BMC to stop working and then resume working.
848 This function is useful when you have initiated some kind of reboot.
849
850 Description of arguments:
851 start_boot_seconds The time that the boot test started. The format is the
852 epoch time in seconds, i.e. the number of seconds since
853 1970-01-01 00:00:00 UTC. This value should be obtained
854 from the BMC so that it is not dependent on any kind of
855 synchronization between this machine and the target BMC
856 This will allow this program to work correctly even in
857 a simulated environment. This value should be obtained
858 by the caller prior to initiating a reboot. It can be
859 obtained as follows:
860 state = st.get_state(req_states=['epoch_seconds'])
861 """
862
Michael Walsh619aa332017-04-12 15:56:51 -0500863 quiet = int(gp.get_var_value(quiet, 0))
864
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600865 # Validate parms.
866 error_message = gv.svalid_integer(start_boot_seconds,
867 var_name="start_boot_seconds")
868 if error_message != "":
869 BuiltIn().fail(gp.sprint_error(error_message))
870
871 match_state = anchor_state(DotDict([('packet_loss', '100')]))
872 # Wait for 100% packet loss trying to ping machine.
Michael Walsh619aa332017-04-12 15:56:51 -0500873 wait_state(match_state, wait_time="8 mins", interval="0 seconds")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600874
875 match_state['packet_loss'] = '^0$'
876 # Wait for 0% packet loss trying to ping machine.
Michael Walsh619aa332017-04-12 15:56:51 -0500877 wait_state(match_state, wait_time="8 mins", interval="0 seconds")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600878
879 # Get the uptime and epoch seconds for comparisons. We want to be sure
880 # that the uptime is less than the elapsed boot time. Further proof that
881 # a reboot has indeed occurred (vs random network instability giving a
882 # false positive.
Michael Walsh619aa332017-04-12 15:56:51 -0500883 state = get_state(req_states=['uptime', 'epoch_seconds'], quiet=quiet)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600884
885 elapsed_boot_time = int(state['epoch_seconds']) - start_boot_seconds
Michael Walsh619aa332017-04-12 15:56:51 -0500886 gp.qprint_var(elapsed_boot_time)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600887 if int(float(state['uptime'])) < elapsed_boot_time:
888 uptime = state['uptime']
Michael Walsh619aa332017-04-12 15:56:51 -0500889 gp.qprint_var(uptime)
890 gp.qprint_timen("The uptime is less than the elapsed boot time," +
891 " as expected.")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600892 else:
893 error_message = "The uptime is greater than the elapsed boot time," +\
894 " which is unexpected:\n" +\
895 gp.sprint_var(start_boot_seconds) +\
896 gp.sprint_var(state)
897 BuiltIn().fail(gp.sprint_error(error_message))
898
Michael Walsh619aa332017-04-12 15:56:51 -0500899 gp.qprint_timen("Verifying that REST API interface is working.")
Michael Walshb95eb542017-03-31 09:39:20 -0500900 match_state = DotDict([('rest', '^1$')])
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600901 state = wait_state(match_state, wait_time="5 mins", interval="2 seconds")
902
903###############################################################################