blob: 83ca5bcaa85e14d93eb9f1fe55dae992136f7e48 [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 Walsh01975fa2017-08-20 20:51:36 -050013 default_state[boot_progress]: OSStart
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'),
Michael Walsh01975fa2017-08-20 20:51:36 -0500118 ('boot_progress', 'OSStart'),
Michael Walsh619aa332017-04-12 15:56:51 -0500119 ('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',
Michael Walsh01975fa2017-08-20 20:51:36 -0500128 'FW Progress, Starting OS|OSStart'),
Michael Walsh619aa332017-04-12 15:56:51 -0500129 ('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
Michael Walshe53e47a2017-06-30 17:03:24 -0500510 if 'uptime' in req_states:
511 cmd_buf = ["BMC Execute Command", "cat /proc/uptime | cut -f 1 -d ' '",
512 'quiet=${1}']
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600513 if not quiet:
514 grp.rpissuing_keyword(cmd_buf)
Michael Walsh97df71c2017-03-27 14:33:24 -0500515 status, ret_values = \
516 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
517 if status == "PASS":
518 stdout, stderr, rc = ret_values
519 if rc == 0 and stderr == "":
520 uptime = stdout
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600521
Michael Walshe53e47a2017-06-30 17:03:24 -0500522 if 'epoch_seconds' in req_states:
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600523 date_cmd_buf = "date -u +%s"
524 if USE_BMC_EPOCH_TIME:
Michael Walshe53e47a2017-06-30 17:03:24 -0500525 cmd_buf = ["BMC Execute Command", date_cmd_buf, 'quiet=${1}']
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600526 if not quiet:
527 grp.rpissuing_keyword(cmd_buf)
Michael Walsh97df71c2017-03-27 14:33:24 -0500528 status, ret_values = \
529 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
530 if status == "PASS":
531 stdout, stderr, rc = ret_values
532 if rc == 0 and stderr == "":
533 epoch_seconds = stdout.rstrip("\n")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600534 else:
535 shell_rc, out_buf = gc.cmd_fnc_u(date_cmd_buf,
536 quiet=1,
537 print_output=0)
538 if shell_rc == 0:
539 epoch_seconds = out_buf.rstrip("\n")
540
Michael Walsh619aa332017-04-12 15:56:51 -0500541 master_req_rest = ['rest', 'chassis', 'bmc', 'boot_progress',
Michael Walshb95eb542017-03-31 09:39:20 -0500542 'host']
543 req_rest = [sub_state for sub_state in req_states if sub_state in
544 master_req_rest]
545 need_rest = (len(req_rest) > 0)
546
547 # Though we could try to determine 'rest' state on any of several calls,
548 # for simplicity, we'll use 'chassis' to figure it out (even if the caller
549 # hasn't explicitly asked for 'chassis').
550 if 'chassis' in req_states or need_rest:
Michael Walsh341c21e2017-01-17 16:25:20 -0600551 cmd_buf = ["Get Chassis Power State", "quiet=${" + str(quiet) + "}"]
552 grp.rdpissuing_keyword(cmd_buf)
Michael Walsh97df71c2017-03-27 14:33:24 -0500553 status, ret_values = \
554 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
555 if status == "PASS":
556 chassis = ret_values
557 chassis = re.sub(r'.*\.', "", chassis)
Michael Walshb95eb542017-03-31 09:39:20 -0500558 rest = '1'
Michael Walsh341c21e2017-01-17 16:25:20 -0600559 else:
Michael Walshb95eb542017-03-31 09:39:20 -0500560 rest = ret_values
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600561
Michael Walshb95eb542017-03-31 09:39:20 -0500562 if rest == '1':
Michael Walshb95eb542017-03-31 09:39:20 -0500563 if 'bmc' in req_states:
564 if OBMC_STATES_VERSION == 0:
565 qualifier = "utils"
566 else:
567 # This will not be supported much longer.
568 qualifier = "state_manager"
569 cmd_buf = [qualifier + ".Get BMC State",
570 "quiet=${" + str(quiet) + "}"]
571 grp.rdpissuing_keyword(cmd_buf)
572 status, ret_values = \
573 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
574 if status == "PASS":
575 bmc = ret_values
576
577 if 'boot_progress' in req_states:
578 cmd_buf = ["Get Boot Progress", "quiet=${" + str(quiet) + "}"]
579 grp.rdpissuing_keyword(cmd_buf)
580 status, ret_values = \
581 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
582 if status == "PASS":
583 boot_progress = ret_values
584
585 if 'host' in req_states:
586 if OBMC_STATES_VERSION > 0:
587 cmd_buf = ["Get Host State", "quiet=${" + str(quiet) + "}"]
588 grp.rdpissuing_keyword(cmd_buf)
589 status, ret_values = \
590 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
591 if status == "PASS":
592 host = ret_values
593 # Strip everything up to the final period.
594 host = re.sub(r'.*\.', "", host)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600595
596 state = DotDict()
597 for sub_state in req_states:
598 if sub_state.startswith("os_"):
599 # We pass "os_" requests on to get_os_state.
600 continue
601 cmd_buf = "state['" + sub_state + "'] = str(" + sub_state + ")"
602 exec(cmd_buf)
603
604 if os_host == "":
605 # The caller has not specified an os_host so as far as we're concerned,
606 # it doesn't exist.
607 return state
608
609 os_req_states = [sub_state for sub_state in req_states
610 if sub_state.startswith('os_')]
611
612 if len(os_req_states) > 0:
613 # The caller has specified an os_host and they have requested
614 # information on os substates.
615
616 # Based on the information gathered on bmc, we'll try to make a
617 # determination of whether the os is even up. We'll pass the result
618 # of that assessment to get_os_state to enhance performance.
619 os_up_match = DotDict()
620 for sub_state in master_os_up_match:
621 if sub_state in req_states:
622 os_up_match[sub_state] = master_os_up_match[sub_state]
Michael Walsh70369fd2016-11-22 11:25:57 -0600623 os_up = compare_states(state, os_up_match)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600624 os_state = get_os_state(os_host=os_host,
625 os_username=os_username,
626 os_password=os_password,
627 req_states=os_req_states,
628 os_up=os_up,
629 quiet=quiet)
630 # Append os_state dictionary to ours.
631 state.update(os_state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600632
633 return state
634
635###############################################################################
636
637
638###############################################################################
639def check_state(match_state,
640 invert=0,
641 print_string="",
642 openbmc_host="",
643 openbmc_username="",
644 openbmc_password="",
645 os_host="",
646 os_username="",
647 os_password="",
648 quiet=None):
649
650 r"""
651 Check that the Open BMC machine's composite state matches the specified
652 state. On success, this keyword returns the machine's composite state as a
653 dictionary.
654
655 Description of arguments:
656 match_state A dictionary whose key/value pairs are "state field"/
657 "state value". The state value is interpreted as a
658 regular expression. Example call from robot:
Michael Walsh341c21e2017-01-17 16:25:20 -0600659 ${match_state}= Create Dictionary chassis=^On$
660 ... bmc=^Ready$
Michael Walsh01975fa2017-08-20 20:51:36 -0500661 ... boot_progress=^OSStart$
Michael Walsh70369fd2016-11-22 11:25:57 -0600662 ${state}= Check State &{match_state}
663 invert If this flag is set, this function will succeed if the
664 states do NOT match.
665 print_string This function will print this string to the console prior
666 to getting the state.
667 openbmc_host The DNS name or IP address of the BMC.
668 This defaults to global ${OPENBMC_HOST}.
669 openbmc_username The username to be used to login to the BMC.
670 This defaults to global ${OPENBMC_USERNAME}.
671 openbmc_password The password to be used to login to the BMC.
672 This defaults to global ${OPENBMC_PASSWORD}.
673 os_host The DNS name or IP address of the operating system.
674 This defaults to global ${OS_HOST}.
675 os_username The username to be used to login to the OS.
676 This defaults to global ${OS_USERNAME}.
677 os_password The password to be used to login to the OS.
678 This defaults to global ${OS_PASSWORD}.
679 quiet Indicates whether status details should be written to the
680 console. Defaults to either global value of ${QUIET} or
681 to 1.
682 """
683
Michael Walsh619aa332017-04-12 15:56:51 -0500684 quiet = int(gp.get_var_value(quiet, 0))
Michael Walsh70369fd2016-11-22 11:25:57 -0600685
686 grp.rprint(print_string)
687
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600688 req_states = match_state.keys()
Michael Walsh70369fd2016-11-22 11:25:57 -0600689 # Initialize state.
690 state = get_state(openbmc_host=openbmc_host,
691 openbmc_username=openbmc_username,
692 openbmc_password=openbmc_password,
693 os_host=os_host,
694 os_username=os_username,
695 os_password=os_password,
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600696 req_states=req_states,
Michael Walsh70369fd2016-11-22 11:25:57 -0600697 quiet=quiet)
698 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500699 gp.print_var(state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600700
701 match = compare_states(state, match_state)
702
703 if invert and match:
704 fail_msg = "The current state of the machine matches the match" +\
705 " state:\n" + gp.sprint_varx("state", state)
706 BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
707 elif not invert and not match:
708 fail_msg = "The current state of the machine does NOT match the" +\
709 " match state:\n" +\
710 gp.sprint_varx("state", state)
711 BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
712
713 return state
714
715###############################################################################
716
717
718###############################################################################
Michael Walshf893ba02017-01-10 10:28:05 -0600719def wait_state(match_state=(),
Michael Walsh70369fd2016-11-22 11:25:57 -0600720 wait_time="1 min",
721 interval="1 second",
722 invert=0,
723 openbmc_host="",
724 openbmc_username="",
725 openbmc_password="",
726 os_host="",
727 os_username="",
728 os_password="",
729 quiet=None):
730
731 r"""
732 Wait for the Open BMC machine's composite state to match the specified
733 state. On success, this keyword returns the machine's composite state as
734 a dictionary.
735
736 Description of arguments:
737 match_state A dictionary whose key/value pairs are "state field"/
738 "state value". See check_state (above) for details.
Michael Walsh619aa332017-04-12 15:56:51 -0500739 This value may also be any string accepted by
740 return_state_constant (e.g. "standby_match_state").
741 In such a case this function will call
742 return_state_constant to convert it to a proper
743 dictionary as described above.
Michael Walsh70369fd2016-11-22 11:25:57 -0600744 wait_time The total amount of time to wait for the desired state.
745 This value may be expressed in Robot Framework's time
746 format (e.g. 1 minute, 2 min 3 s, 4.5).
747 interval The amount of time between state checks.
748 This value may be expressed in Robot Framework's time
749 format (e.g. 1 minute, 2 min 3 s, 4.5).
750 invert If this flag is set, this function will for the state of
751 the machine to cease to match the match state.
752 openbmc_host The DNS name or IP address of the BMC.
753 This defaults to global ${OPENBMC_HOST}.
754 openbmc_username The username to be used to login to the BMC.
755 This defaults to global ${OPENBMC_USERNAME}.
756 openbmc_password The password to be used to login to the BMC.
757 This defaults to global ${OPENBMC_PASSWORD}.
758 os_host The DNS name or IP address of the operating system.
759 This defaults to global ${OS_HOST}.
760 os_username The username to be used to login to the OS.
761 This defaults to global ${OS_USERNAME}.
762 os_password The password to be used to login to the OS.
763 This defaults to global ${OS_PASSWORD}.
764 quiet Indicates whether status details should be written to the
765 console. Defaults to either global value of ${QUIET} or
766 to 1.
767 """
768
Michael Walsh619aa332017-04-12 15:56:51 -0500769 quiet = int(gp.get_var_value(quiet, 0))
770
771 if type(match_state) in (str, unicode):
772 match_state = return_state_constant(match_state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600773
774 if not quiet:
775 if invert:
776 alt_text = "cease to "
777 else:
778 alt_text = ""
Michael Walsh3eb50022017-03-21 11:27:30 -0500779 gp.print_timen("Checking every " + str(interval) + " for up to " +
780 str(wait_time) + " for the state of the machine to " +
781 alt_text + "match the state shown below.")
782 gp.print_var(match_state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600783
Michael Walshf893ba02017-01-10 10:28:05 -0600784 if quiet:
Michael Walsh341c21e2017-01-17 16:25:20 -0600785 print_string = ""
Michael Walshf893ba02017-01-10 10:28:05 -0600786 else:
Michael Walsh341c21e2017-01-17 16:25:20 -0600787 print_string = "#"
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600788
789 debug = int(BuiltIn().get_variable_value("${debug}", "0"))
790 if debug:
791 # In debug we print state so no need to print the "#".
792 print_string = ""
793 check_state_quiet = 1 - debug
Michael Walsh70369fd2016-11-22 11:25:57 -0600794 cmd_buf = ["Check State", match_state, "invert=${" + str(invert) + "}",
Michael Walshf893ba02017-01-10 10:28:05 -0600795 "print_string=" + print_string, "openbmc_host=" + openbmc_host,
Michael Walsh70369fd2016-11-22 11:25:57 -0600796 "openbmc_username=" + openbmc_username,
797 "openbmc_password=" + openbmc_password, "os_host=" + os_host,
798 "os_username=" + os_username, "os_password=" + os_password,
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600799 "quiet=${" + str(check_state_quiet) + "}"]
Michael Walsh70369fd2016-11-22 11:25:57 -0600800 grp.rdpissuing_keyword(cmd_buf)
Michael Walsh619aa332017-04-12 15:56:51 -0500801 try:
802 state = BuiltIn().wait_until_keyword_succeeds(wait_time, interval,
803 *cmd_buf)
804 except AssertionError as my_assertion_error:
805 gp.printn()
806 message = my_assertion_error.args[0]
807 BuiltIn().fail(message)
808
Michael Walsh70369fd2016-11-22 11:25:57 -0600809 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500810 gp.printn()
Michael Walsh70369fd2016-11-22 11:25:57 -0600811 if invert:
Michael Walsh3eb50022017-03-21 11:27:30 -0500812 gp.print_timen("The states no longer match:")
Michael Walsh70369fd2016-11-22 11:25:57 -0600813 else:
Michael Walsh3eb50022017-03-21 11:27:30 -0500814 gp.print_timen("The states match:")
815 gp.print_var(state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600816
817 return state
818
819###############################################################################
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600820
821
822###############################################################################
Michael Walsh619aa332017-04-12 15:56:51 -0500823def wait_for_comm_cycle(start_boot_seconds,
824 quiet=None):
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600825
826 r"""
827 Wait for communications to the BMC to stop working and then resume working.
828 This function is useful when you have initiated some kind of reboot.
829
830 Description of arguments:
831 start_boot_seconds The time that the boot test started. The format is the
832 epoch time in seconds, i.e. the number of seconds since
833 1970-01-01 00:00:00 UTC. This value should be obtained
834 from the BMC so that it is not dependent on any kind of
835 synchronization between this machine and the target BMC
836 This will allow this program to work correctly even in
837 a simulated environment. This value should be obtained
838 by the caller prior to initiating a reboot. It can be
839 obtained as follows:
840 state = st.get_state(req_states=['epoch_seconds'])
841 """
842
Michael Walsh619aa332017-04-12 15:56:51 -0500843 quiet = int(gp.get_var_value(quiet, 0))
844
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600845 # Validate parms.
846 error_message = gv.svalid_integer(start_boot_seconds,
847 var_name="start_boot_seconds")
848 if error_message != "":
849 BuiltIn().fail(gp.sprint_error(error_message))
850
851 match_state = anchor_state(DotDict([('packet_loss', '100')]))
852 # Wait for 100% packet loss trying to ping machine.
Michael Walsh619aa332017-04-12 15:56:51 -0500853 wait_state(match_state, wait_time="8 mins", interval="0 seconds")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600854
855 match_state['packet_loss'] = '^0$'
856 # Wait for 0% packet loss trying to ping machine.
Michael Walsh619aa332017-04-12 15:56:51 -0500857 wait_state(match_state, wait_time="8 mins", interval="0 seconds")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600858
859 # Get the uptime and epoch seconds for comparisons. We want to be sure
860 # that the uptime is less than the elapsed boot time. Further proof that
861 # a reboot has indeed occurred (vs random network instability giving a
862 # false positive.
Michael Walsh01975fa2017-08-20 20:51:36 -0500863 state = get_state(req_states=['uptime', 'epoch_seconds'], quiet=quiet)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600864
865 elapsed_boot_time = int(state['epoch_seconds']) - start_boot_seconds
Michael Walsh619aa332017-04-12 15:56:51 -0500866 gp.qprint_var(elapsed_boot_time)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600867 if int(float(state['uptime'])) < elapsed_boot_time:
868 uptime = state['uptime']
Michael Walsh619aa332017-04-12 15:56:51 -0500869 gp.qprint_var(uptime)
870 gp.qprint_timen("The uptime is less than the elapsed boot time," +
871 " as expected.")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600872 else:
873 error_message = "The uptime is greater than the elapsed boot time," +\
874 " which is unexpected:\n" +\
875 gp.sprint_var(start_boot_seconds) +\
876 gp.sprint_var(state)
877 BuiltIn().fail(gp.sprint_error(error_message))
878
Michael Walsh619aa332017-04-12 15:56:51 -0500879 gp.qprint_timen("Verifying that REST API interface is working.")
Michael Walshb95eb542017-03-31 09:39:20 -0500880 match_state = DotDict([('rest', '^1$')])
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600881 state = wait_state(match_state, wait_time="5 mins", interval="2 seconds")
882
883###############################################################################