blob: 712d31063dda8c114152096660adeebefa1e6466 [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
42# We need utils.robot to get keywords like "Get Power State".
Michael Walsh16cbb7f2017-02-02 15:54:16 -060043gru.my_import_resource("utils.robot")
44gru.my_import_resource("state_manager.robot")
Michael Walsh70369fd2016-11-22 11:25:57 -060045
Michael 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 Walsh8fae6ea2017-02-20 16:14:44 -060054OBMC_STATES_VERSION = int(os.environ.get('OBMC_STATES_VERSION', 1))
Michael Walsh341c21e2017-01-17 16:25:20 -060055
56if OBMC_STATES_VERSION == 0:
Michael Walsh8fae6ea2017-02-20 16:14:44 -060057 # default_state is an initial value which may be of use to callers.
Michael Walsh341c21e2017-01-17 16:25:20 -060058 default_state = DotDict([('power', '1'),
Michael Walsh78bb9622017-03-10 14:13:58 -060059 ('bmc', 'HOST_BOOTED'),
Michael Walsh341c21e2017-01-17 16:25:20 -060060 ('boot_progress', 'FW Progress, Starting OS'),
61 ('os_ping', '1'),
62 ('os_login', '1'),
63 ('os_run_cmd', '1')])
Michael Walsh8fae6ea2017-02-20 16:14:44 -060064 # valid_req_states, default_req_states and master_os_up_match are used by
65 # the get_state function.
66 # valid_req_states is a list of state information supported by the
67 # get_state function.
68 valid_req_states = ['ping',
69 'packet_loss',
70 'uptime',
71 'epoch_seconds',
72 'power',
Michael Walsh78bb9622017-03-10 14:13:58 -060073 'bmc',
Michael Walsh8fae6ea2017-02-20 16:14:44 -060074 'boot_progress',
75 'os_ping',
76 'os_login',
77 'os_run_cmd']
78 # When a user calls get_state w/o specifying req_states, default_req_states
79 # is used as its value.
80 default_req_states = ['power',
Michael Walsh78bb9622017-03-10 14:13:58 -060081 'bmc',
Michael Walsh8fae6ea2017-02-20 16:14:44 -060082 'boot_progress',
83 'os_ping',
84 'os_login',
85 'os_run_cmd']
86 # A master dictionary to determine whether the os may be up.
87 master_os_up_match = DotDict([('power', 'On'),
Michael Walsh78bb9622017-03-10 14:13:58 -060088 ('bmc', '^HOST_BOOTED$'),
Michael Walsh8fae6ea2017-02-20 16:14:44 -060089 ('boot_progress',
90 'FW Progress, Starting OS')])
91
Michael Walsh341c21e2017-01-17 16:25:20 -060092else:
Michael Walsh8fae6ea2017-02-20 16:14:44 -060093 # default_state is an initial value which may be of use to callers.
Michael Walsh341c21e2017-01-17 16:25:20 -060094 default_state = DotDict([('chassis', 'On'),
Michael Walsh341c21e2017-01-17 16:25:20 -060095 ('boot_progress', 'FW Progress, Starting OS'),
Michael Walsh65b12542017-02-03 15:34:38 -060096 ('host', 'Running'),
Michael Walsh341c21e2017-01-17 16:25:20 -060097 ('os_ping', '1'),
98 ('os_login', '1'),
99 ('os_run_cmd', '1')])
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600100 # valid_req_states is a list of state information supported by the
101 # get_state function.
102 # valid_req_states, default_req_states and master_os_up_match are used by
103 # the get_state function.
104 valid_req_states = ['ping',
105 'packet_loss',
106 'uptime',
107 'epoch_seconds',
108 'chassis',
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600109 'boot_progress',
110 'host',
111 'os_ping',
112 'os_login',
113 'os_run_cmd']
114 # When a user calls get_state w/o specifying req_states, default_req_states
115 # is used as its value.
116 default_req_states = ['chassis',
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600117 'boot_progress',
118 'host',
119 'os_ping',
120 'os_login',
121 'os_run_cmd']
122
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600123 # A master dictionary to determine whether the os may be up.
Michael Walsh78bb9622017-03-10 14:13:58 -0600124 master_os_up_match = DotDict([('chassis', '^On$'),
125 ('boot_progress',
126 'FW Progress, Starting OS'),
127 ('host', '^Running$')])
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600128
129# valid_os_req_states and default_os_req_states are used by the os_get_state
130# function.
131# valid_os_req_states is a list of state information supported by the
132# get_os_state function.
133valid_os_req_states = ['os_ping',
134 'os_login',
135 'os_run_cmd']
136# When a user calls get_os_state w/o specifying req_states,
137# default_os_req_states is used as its value.
138default_os_req_states = ['os_ping',
139 'os_login',
140 'os_run_cmd']
141
142# Presently, some BMCs appear to not keep time very well. This environment
143# variable directs the get_state function to use either the BMC's epoch time
144# or the local epoch time.
145USE_BMC_EPOCH_TIME = int(os.environ.get('USE_BMC_EPOCH_TIME', 0))
Michael Walsh341c21e2017-01-17 16:25:20 -0600146
147
148###############################################################################
149def return_default_state():
150
151 r"""
152 Return default state dictionary.
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600153
154 default_state is an initial value which may be of use to callers.
Michael Walsh341c21e2017-01-17 16:25:20 -0600155 """
156
157 return default_state
158
159###############################################################################
160
Michael Walsh70369fd2016-11-22 11:25:57 -0600161
162###############################################################################
163def anchor_state(state):
164
165 r"""
166 Add regular expression anchors ("^" and "$") to the beginning and end of
167 each item in the state dictionary passed in. Return the resulting
168 dictionary.
169
170 Description of Arguments:
171 state A dictionary such as the one returned by the get_state()
172 function.
173 """
174
Michael Walsh2ce067a2017-02-27 14:24:07 -0600175 anchored_state = state.copy()
Michael Walsh70369fd2016-11-22 11:25:57 -0600176 for key, match_state_value in anchored_state.items():
177 anchored_state[key] = "^" + str(anchored_state[key]) + "$"
178
179 return anchored_state
180
181###############################################################################
182
183
184###############################################################################
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600185def strip_anchor_state(state):
186
187 r"""
188 Strip regular expression anchors ("^" and "$") from the beginning and end
189 of each item in the state dictionary passed in. Return the resulting
190 dictionary.
191
192 Description of Arguments:
193 state A dictionary such as the one returned by the get_state()
194 function.
195 """
196
Michael Walsh2ce067a2017-02-27 14:24:07 -0600197 stripped_state = state.copy()
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600198 for key, match_state_value in stripped_state.items():
199 stripped_state[key] = stripped_state[key].strip("^$")
200
201 return stripped_state
202
203###############################################################################
204
205
206###############################################################################
Michael Walsh70369fd2016-11-22 11:25:57 -0600207def compare_states(state,
208 match_state):
209
210 r"""
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600211 Compare 2 state dictionaries. Return True if they match and False if they
Michael Walsh70369fd2016-11-22 11:25:57 -0600212 don't. Note that the match_state dictionary does not need to have an entry
213 corresponding to each entry in the state dictionary. But for each entry
214 that it does have, the corresponding state entry will be checked for a
215 match.
216
217 Description of arguments:
218 state A state dictionary such as the one returned by the
219 get_state function.
220 match_state A dictionary whose key/value pairs are "state field"/
221 "state value". The state value is interpreted as a
222 regular expression. Every value in this dictionary is
223 considered. If each and every one matches, the 2
224 dictionaries are considered to be matching.
225 """
226
227 match = True
228 for key, match_state_value in match_state.items():
Michael Walsh97df71c2017-03-27 14:33:24 -0500229 # Blank match_state_value means "don't care".
230 if match_state_value == "":
231 continue
Michael Walsh70369fd2016-11-22 11:25:57 -0600232 try:
233 if not re.match(match_state_value, str(state[key])):
234 match = False
235 break
236 except KeyError:
237 match = False
238 break
239
240 return match
241
242###############################################################################
243
244
245###############################################################################
246def get_os_state(os_host="",
247 os_username="",
248 os_password="",
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600249 req_states=default_os_req_states,
250 os_up=True,
Michael Walsh70369fd2016-11-22 11:25:57 -0600251 quiet=None):
252
253 r"""
254 Get component states for the operating system such as ping, login,
255 etc, put them into a dictionary and return them to the caller.
256
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600257 Note that all substate values are strings.
258
Michael Walsh70369fd2016-11-22 11:25:57 -0600259 Description of arguments:
260 os_host The DNS name or IP address of the operating system.
261 This defaults to global ${OS_HOST}.
262 os_username The username to be used to login to the OS.
263 This defaults to global ${OS_USERNAME}.
264 os_password The password to be used to login to the OS.
265 This defaults to global ${OS_PASSWORD}.
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600266 req_states This is a list of states whose values are being requested by
267 the caller.
268 os_up If the caller knows that the os can't possibly be up, it can
269 improve performance by passing os_up=False. This function
270 will then simply return default values for all requested os
271 sub states.
Michael Walsh70369fd2016-11-22 11:25:57 -0600272 quiet Indicates whether status details (e.g. curl commands) should
273 be written to the console.
274 Defaults to either global value of ${QUIET} or to 1.
275 """
276
277 quiet = grp.set_quiet_default(quiet, 1)
278
279 # Set parm defaults where necessary and validate all parms.
280 if os_host == "":
281 os_host = BuiltIn().get_variable_value("${OS_HOST}")
282 error_message = gv.svalid_value(os_host, var_name="os_host",
283 invalid_values=[None, ""])
284 if error_message != "":
285 BuiltIn().fail(gp.sprint_error(error_message))
286
287 if os_username == "":
288 os_username = BuiltIn().get_variable_value("${OS_USERNAME}")
289 error_message = gv.svalid_value(os_username, var_name="os_username",
290 invalid_values=[None, ""])
291 if error_message != "":
292 BuiltIn().fail(gp.sprint_error(error_message))
293
294 if os_password == "":
295 os_password = BuiltIn().get_variable_value("${OS_PASSWORD}")
296 error_message = gv.svalid_value(os_password, var_name="os_password",
297 invalid_values=[None, ""])
298 if error_message != "":
299 BuiltIn().fail(gp.sprint_error(error_message))
300
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600301 invalid_req_states = [sub_state for sub_state in req_states
302 if sub_state not in valid_os_req_states]
303 if len(invalid_req_states) > 0:
304 error_message = "The following req_states are not supported:\n" +\
305 gp.sprint_var(invalid_req_states)
306 BuiltIn().fail(gp.sprint_error(error_message))
Michael Walsh70369fd2016-11-22 11:25:57 -0600307
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600308 # Initialize all substate values supported by this function.
309 os_ping = 0
310 os_login = 0
311 os_run_cmd = 0
Michael Walsh70369fd2016-11-22 11:25:57 -0600312
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600313 if os_up:
314 if 'os_ping' in req_states:
315 # See if the OS pings.
316 cmd_buf = "ping -c 1 -w 2 " + os_host
317 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500318 gp.pissuing(cmd_buf)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600319 rc, out_buf = commands.getstatusoutput(cmd_buf)
320 if rc == 0:
321 os_ping = 1
Michael Walsh70369fd2016-11-22 11:25:57 -0600322
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600323 # Programming note: All attributes which do not require an ssh login
324 # should have been processed by this point.
325 master_req_login = ['os_login', 'os_run_cmd']
326 req_login = [sub_state for sub_state in req_states if sub_state in
327 master_req_login]
Michael Walsh97df71c2017-03-27 14:33:24 -0500328 must_login = (len(req_login) > 0)
Michael Walsh70369fd2016-11-22 11:25:57 -0600329
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600330 if must_login:
Michael Walsh3eb50022017-03-21 11:27:30 -0500331 # Open SSH connection to OS. Note that this doesn't fail even when
332 # the OS is not up.
Michael Walshac275512017-03-07 11:39:28 -0600333 cmd_buf = ["SSHLibrary.Open Connection", os_host]
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600334 if not quiet:
335 grp.rpissuing_keyword(cmd_buf)
336 ix = BuiltIn().run_keyword(*cmd_buf)
337
338 # Login to OS.
339 cmd_buf = ["Login", os_username, os_password]
340 if not quiet:
341 grp.rpissuing_keyword(cmd_buf)
Michael Walsh3eb50022017-03-21 11:27:30 -0500342 status, ret_values = \
343 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600344 if status == "PASS":
345 os_login = 1
Michael Walsh3eb50022017-03-21 11:27:30 -0500346 else:
347 gp.dprint_var(status)
348 gp.dprint_var(ret_values)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600349
350 if os_login:
351 if 'os_run_cmd' in req_states:
Michael Walsh3eb50022017-03-21 11:27:30 -0500352 # Try running a simple command (uptime) on the OS.
353 cmd_buf = ["Execute Command", "uptime",
354 "return_stderr=True", "return_rc=True"]
355 if not quiet:
356 grp.rpissuing_keyword(cmd_buf)
357 # Note that in spite of its name, there are occasions
358 # where run_keyword_and_ignore_error can fail.
359 status, ret_values = \
360 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
361 if status == "PASS":
362 stdout, stderr, rc = ret_values
363 if rc == 0 and stderr == "":
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600364 os_run_cmd = 1
Michael Walsh3eb50022017-03-21 11:27:30 -0500365 else:
366 gp.dprint_var(status)
367 gp.dprint_var(stdout)
368 gp.dprint_var(stderr)
369 gp.dprint_var(rc)
370 else:
371 gp.dprint_var(status)
372 gp.dprint_var(ret_values)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600373
374 os_state = DotDict()
375 for sub_state in req_states:
376 cmd_buf = "os_state['" + sub_state + "'] = str(" + sub_state + ")"
377 exec(cmd_buf)
Michael Walsh70369fd2016-11-22 11:25:57 -0600378
379 return os_state
380
381###############################################################################
382
383
384###############################################################################
385def get_state(openbmc_host="",
386 openbmc_username="",
387 openbmc_password="",
388 os_host="",
389 os_username="",
390 os_password="",
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600391 req_states=default_req_states,
Michael Walsh70369fd2016-11-22 11:25:57 -0600392 quiet=None):
393
394 r"""
395 Get component states such as power state, bmc state, etc, put them into a
396 dictionary and return them to the caller.
397
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600398 Note that all substate values are strings.
399
Michael Walsh70369fd2016-11-22 11:25:57 -0600400 Description of arguments:
401 openbmc_host The DNS name or IP address of the BMC.
402 This defaults to global ${OPENBMC_HOST}.
403 openbmc_username The username to be used to login to the BMC.
404 This defaults to global ${OPENBMC_USERNAME}.
405 openbmc_password The password to be used to login to the BMC.
406 This defaults to global ${OPENBMC_PASSWORD}.
407 os_host The DNS name or IP address of the operating system.
408 This defaults to global ${OS_HOST}.
409 os_username The username to be used to login to the OS.
410 This defaults to global ${OS_USERNAME}.
411 os_password The password to be used to login to the OS.
412 This defaults to global ${OS_PASSWORD}.
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600413 req_states This is a list of states whose values are being requested
414 by the caller.
Michael Walsh70369fd2016-11-22 11:25:57 -0600415 quiet Indicates whether status details (e.g. curl commands)
416 should be written to the console.
417 Defaults to either global value of ${QUIET} or to 1.
418 """
419
420 quiet = grp.set_quiet_default(quiet, 1)
421
422 # Set parm defaults where necessary and validate all parms.
423 if openbmc_host == "":
424 openbmc_host = BuiltIn().get_variable_value("${OPENBMC_HOST}")
425 error_message = gv.svalid_value(openbmc_host,
426 var_name="openbmc_host",
427 invalid_values=[None, ""])
428 if error_message != "":
429 BuiltIn().fail(gp.sprint_error(error_message))
430
431 if openbmc_username == "":
432 openbmc_username = BuiltIn().get_variable_value("${OPENBMC_USERNAME}")
433 error_message = gv.svalid_value(openbmc_username,
434 var_name="openbmc_username",
435 invalid_values=[None, ""])
436 if error_message != "":
437 BuiltIn().fail(gp.sprint_error(error_message))
438
439 if openbmc_password == "":
440 openbmc_password = BuiltIn().get_variable_value("${OPENBMC_PASSWORD}")
441 error_message = gv.svalid_value(openbmc_password,
442 var_name="openbmc_password",
443 invalid_values=[None, ""])
444 if error_message != "":
445 BuiltIn().fail(gp.sprint_error(error_message))
446
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600447 # NOTE: OS parms are optional.
Michael Walsh70369fd2016-11-22 11:25:57 -0600448 if os_host == "":
449 os_host = BuiltIn().get_variable_value("${OS_HOST}")
450 if os_host is None:
451 os_host = ""
452
453 if os_username is "":
454 os_username = BuiltIn().get_variable_value("${OS_USERNAME}")
455 if os_username is None:
456 os_username = ""
457
458 if os_password is "":
459 os_password = BuiltIn().get_variable_value("${OS_PASSWORD}")
460 if os_password is None:
461 os_password = ""
462
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600463 invalid_req_states = [sub_state for sub_state in req_states
464 if sub_state not in valid_req_states]
465 if len(invalid_req_states) > 0:
466 error_message = "The following req_states are not supported:\n" +\
467 gp.sprint_var(invalid_req_states)
468 BuiltIn().fail(gp.sprint_error(error_message))
469
470 # Initialize all substate values supported by this function.
471 ping = 0
472 packet_loss = ''
473 uptime = ''
474 epoch_seconds = ''
Michael Walsh97df71c2017-03-27 14:33:24 -0500475 power = ''
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600476 chassis = ''
477 bmc = ''
478 boot_progress = ''
479 host = ''
480
Michael Walsh70369fd2016-11-22 11:25:57 -0600481 # Get the component states.
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600482 if 'ping' in req_states:
483 # See if the OS pings.
484 cmd_buf = "ping -c 1 -w 2 " + openbmc_host
485 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500486 gp.pissuing(cmd_buf)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600487 rc, out_buf = commands.getstatusoutput(cmd_buf)
488 if rc == 0:
489 ping = 1
490
491 if 'packet_loss' in req_states:
492 # See if the OS pings.
493 cmd_buf = "ping -c 5 -w 5 " + openbmc_host +\
494 " | egrep 'packet loss' | sed -re 's/.* ([0-9]+)%.*/\\1/g'"
495 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500496 gp.pissuing(cmd_buf)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600497 rc, out_buf = commands.getstatusoutput(cmd_buf)
498 if rc == 0:
499 packet_loss = out_buf.rstrip("\n")
500
501 master_req_login = ['uptime', 'epoch_seconds']
502 req_login = [sub_state for sub_state in req_states if sub_state in
503 master_req_login]
Michael Walsh97df71c2017-03-27 14:33:24 -0500504 must_login = (len(req_login) > 0)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600505
Michael Walsh97df71c2017-03-27 14:33:24 -0500506 bmc_login = 0
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600507 if must_login:
508 cmd_buf = ["Open Connection And Log In"]
509 if not quiet:
510 grp.rpissuing_keyword(cmd_buf)
Michael Walsh97df71c2017-03-27 14:33:24 -0500511 status, ret_values = \
512 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
513 if status == "PASS":
514 bmc_login = 1
515 else:
516 if re.match('^Authentication failed for user', ret_values):
517 # An authentication failure is worth failing on.
518 BuiltIn().fail(gp.sprint_error(ret_values))
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600519
Michael Walsh97df71c2017-03-27 14:33:24 -0500520 if 'uptime' in req_states and bmc_login:
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600521 cmd_buf = ["Execute Command", "cat /proc/uptime | cut -f 1 -d ' '",
522 "return_stderr=True", "return_rc=True"]
523 if not quiet:
524 grp.rpissuing_keyword(cmd_buf)
Michael Walsh97df71c2017-03-27 14:33:24 -0500525 status, ret_values = \
526 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
527 if status == "PASS":
528 stdout, stderr, rc = ret_values
529 if rc == 0 and stderr == "":
530 uptime = stdout
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600531
Michael Walsh97df71c2017-03-27 14:33:24 -0500532 if 'epoch_seconds' in req_states and bmc_login:
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600533 date_cmd_buf = "date -u +%s"
534 if USE_BMC_EPOCH_TIME:
535 cmd_buf = ["Execute Command", date_cmd_buf, "return_stderr=True",
536 "return_rc=True"]
537 if not quiet:
538 grp.rpissuing_keyword(cmd_buf)
Michael Walsh97df71c2017-03-27 14:33:24 -0500539 status, ret_values = \
540 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
541 if status == "PASS":
542 stdout, stderr, rc = ret_values
543 if rc == 0 and stderr == "":
544 epoch_seconds = stdout.rstrip("\n")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600545 else:
546 shell_rc, out_buf = gc.cmd_fnc_u(date_cmd_buf,
547 quiet=1,
548 print_output=0)
549 if shell_rc == 0:
550 epoch_seconds = out_buf.rstrip("\n")
551
552 if 'power' in req_states:
Michael Walsh341c21e2017-01-17 16:25:20 -0600553 cmd_buf = ["Get Power State", "quiet=${" + str(quiet) + "}"]
554 grp.rdpissuing_keyword(cmd_buf)
Michael Walsh97df71c2017-03-27 14:33:24 -0500555 status, ret_values = \
556 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
557 if status == "PASS":
558 power = ret_values
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600559 if 'chassis' in req_states:
Michael Walsh341c21e2017-01-17 16:25:20 -0600560 cmd_buf = ["Get Chassis Power State", "quiet=${" + str(quiet) + "}"]
561 grp.rdpissuing_keyword(cmd_buf)
Michael Walsh97df71c2017-03-27 14:33:24 -0500562 status, ret_values = \
563 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
564 if status == "PASS":
565 chassis = ret_values
566 chassis = re.sub(r'.*\.', "", chassis)
Michael Walsh70369fd2016-11-22 11:25:57 -0600567
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600568 if 'bmc' in req_states:
Michael Walsh341c21e2017-01-17 16:25:20 -0600569 if OBMC_STATES_VERSION == 0:
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600570 qualifier = "utils"
Michael Walsh341c21e2017-01-17 16:25:20 -0600571 else:
Michael Walsh78bb9622017-03-10 14:13:58 -0600572 # This will not be supported much longer.
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600573 qualifier = "state_manager"
574
575 cmd_buf = [qualifier + ".Get BMC State", "quiet=${" + str(quiet) + "}"]
Michael Walsh78bb9622017-03-10 14:13:58 -0600576 grp.rdpissuing_keyword(cmd_buf)
Michael Walsh97df71c2017-03-27 14:33:24 -0500577 status, ret_values = \
578 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
579 if status == "PASS":
580 bmc = ret_values
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600581
582 if 'boot_progress' in req_states:
583 cmd_buf = ["Get Boot Progress", "quiet=${" + str(quiet) + "}"]
Michael Walsh70369fd2016-11-22 11:25:57 -0600584 grp.rdpissuing_keyword(cmd_buf)
Michael Walsh97df71c2017-03-27 14:33:24 -0500585 status, ret_values = \
586 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
587 if status == "PASS":
588 boot_progress = ret_values
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600589
590 if 'host' in req_states:
591 if OBMC_STATES_VERSION > 0:
592 cmd_buf = ["Get Host State", "quiet=${" + str(quiet) + "}"]
593 grp.rdpissuing_keyword(cmd_buf)
Michael Walsh97df71c2017-03-27 14:33:24 -0500594 status, ret_values = \
595 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
596 if status == "PASS":
597 host = ret_values
598 # Strip everything up to the final period.
599 host = re.sub(r'.*\.', "", host)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600600
601 state = DotDict()
602 for sub_state in req_states:
603 if sub_state.startswith("os_"):
604 # We pass "os_" requests on to get_os_state.
605 continue
606 cmd_buf = "state['" + sub_state + "'] = str(" + sub_state + ")"
607 exec(cmd_buf)
608
609 if os_host == "":
610 # The caller has not specified an os_host so as far as we're concerned,
611 # it doesn't exist.
612 return state
613
614 os_req_states = [sub_state for sub_state in req_states
615 if sub_state.startswith('os_')]
616
617 if len(os_req_states) > 0:
618 # The caller has specified an os_host and they have requested
619 # information on os substates.
620
621 # Based on the information gathered on bmc, we'll try to make a
622 # determination of whether the os is even up. We'll pass the result
623 # of that assessment to get_os_state to enhance performance.
624 os_up_match = DotDict()
625 for sub_state in master_os_up_match:
626 if sub_state in req_states:
627 os_up_match[sub_state] = master_os_up_match[sub_state]
Michael Walsh70369fd2016-11-22 11:25:57 -0600628 os_up = compare_states(state, os_up_match)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600629 os_state = get_os_state(os_host=os_host,
630 os_username=os_username,
631 os_password=os_password,
632 req_states=os_req_states,
633 os_up=os_up,
634 quiet=quiet)
635 # Append os_state dictionary to ours.
636 state.update(os_state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600637
638 return state
639
640###############################################################################
641
642
643###############################################################################
644def check_state(match_state,
645 invert=0,
646 print_string="",
647 openbmc_host="",
648 openbmc_username="",
649 openbmc_password="",
650 os_host="",
651 os_username="",
652 os_password="",
653 quiet=None):
654
655 r"""
656 Check that the Open BMC machine's composite state matches the specified
657 state. On success, this keyword returns the machine's composite state as a
658 dictionary.
659
660 Description of arguments:
661 match_state A dictionary whose key/value pairs are "state field"/
662 "state value". The state value is interpreted as a
663 regular expression. Example call from robot:
Michael Walsh341c21e2017-01-17 16:25:20 -0600664 ${match_state}= Create Dictionary chassis=^On$
665 ... bmc=^Ready$
Michael Walsh70369fd2016-11-22 11:25:57 -0600666 ... boot_progress=^FW Progress, Starting OS$
667 ${state}= Check State &{match_state}
668 invert If this flag is set, this function will succeed if the
669 states do NOT match.
670 print_string This function will print this string to the console prior
671 to getting the state.
672 openbmc_host The DNS name or IP address of the BMC.
673 This defaults to global ${OPENBMC_HOST}.
674 openbmc_username The username to be used to login to the BMC.
675 This defaults to global ${OPENBMC_USERNAME}.
676 openbmc_password The password to be used to login to the BMC.
677 This defaults to global ${OPENBMC_PASSWORD}.
678 os_host The DNS name or IP address of the operating system.
679 This defaults to global ${OS_HOST}.
680 os_username The username to be used to login to the OS.
681 This defaults to global ${OS_USERNAME}.
682 os_password The password to be used to login to the OS.
683 This defaults to global ${OS_PASSWORD}.
684 quiet Indicates whether status details should be written to the
685 console. Defaults to either global value of ${QUIET} or
686 to 1.
687 """
688
689 quiet = grp.set_quiet_default(quiet, 1)
690
691 grp.rprint(print_string)
692
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600693 req_states = match_state.keys()
Michael Walsh70369fd2016-11-22 11:25:57 -0600694 # Initialize state.
695 state = get_state(openbmc_host=openbmc_host,
696 openbmc_username=openbmc_username,
697 openbmc_password=openbmc_password,
698 os_host=os_host,
699 os_username=os_username,
700 os_password=os_password,
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600701 req_states=req_states,
Michael Walsh70369fd2016-11-22 11:25:57 -0600702 quiet=quiet)
703 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500704 gp.print_var(state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600705
706 match = compare_states(state, match_state)
707
708 if invert and match:
709 fail_msg = "The current state of the machine matches the match" +\
710 " state:\n" + gp.sprint_varx("state", state)
711 BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
712 elif not invert and not match:
713 fail_msg = "The current state of the machine does NOT match the" +\
714 " match state:\n" +\
715 gp.sprint_varx("state", state)
716 BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
717
718 return state
719
720###############################################################################
721
722
723###############################################################################
Michael Walshf893ba02017-01-10 10:28:05 -0600724def wait_state(match_state=(),
Michael Walsh70369fd2016-11-22 11:25:57 -0600725 wait_time="1 min",
726 interval="1 second",
727 invert=0,
728 openbmc_host="",
729 openbmc_username="",
730 openbmc_password="",
731 os_host="",
732 os_username="",
733 os_password="",
734 quiet=None):
735
736 r"""
737 Wait for the Open BMC machine's composite state to match the specified
738 state. On success, this keyword returns the machine's composite state as
739 a dictionary.
740
741 Description of arguments:
742 match_state A dictionary whose key/value pairs are "state field"/
743 "state value". See check_state (above) for details.
744 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
769 quiet = grp.set_quiet_default(quiet, 1)
770
771 if not quiet:
772 if invert:
773 alt_text = "cease to "
774 else:
775 alt_text = ""
Michael Walsh3eb50022017-03-21 11:27:30 -0500776 gp.print_timen("Checking every " + str(interval) + " for up to " +
777 str(wait_time) + " for the state of the machine to " +
778 alt_text + "match the state shown below.")
779 gp.print_var(match_state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600780
Michael Walshf893ba02017-01-10 10:28:05 -0600781 if quiet:
Michael Walsh341c21e2017-01-17 16:25:20 -0600782 print_string = ""
Michael Walshf893ba02017-01-10 10:28:05 -0600783 else:
Michael Walsh341c21e2017-01-17 16:25:20 -0600784 print_string = "#"
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600785
786 debug = int(BuiltIn().get_variable_value("${debug}", "0"))
787 if debug:
788 # In debug we print state so no need to print the "#".
789 print_string = ""
790 check_state_quiet = 1 - debug
Michael Walsh70369fd2016-11-22 11:25:57 -0600791 cmd_buf = ["Check State", match_state, "invert=${" + str(invert) + "}",
Michael Walshf893ba02017-01-10 10:28:05 -0600792 "print_string=" + print_string, "openbmc_host=" + openbmc_host,
Michael Walsh70369fd2016-11-22 11:25:57 -0600793 "openbmc_username=" + openbmc_username,
794 "openbmc_password=" + openbmc_password, "os_host=" + os_host,
795 "os_username=" + os_username, "os_password=" + os_password,
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600796 "quiet=${" + str(check_state_quiet) + "}"]
Michael Walsh70369fd2016-11-22 11:25:57 -0600797 grp.rdpissuing_keyword(cmd_buf)
798 state = BuiltIn().wait_until_keyword_succeeds(wait_time, interval,
799 *cmd_buf)
Michael Walsh70369fd2016-11-22 11:25:57 -0600800 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500801 gp.printn()
Michael Walsh70369fd2016-11-22 11:25:57 -0600802 if invert:
Michael Walsh3eb50022017-03-21 11:27:30 -0500803 gp.print_timen("The states no longer match:")
Michael Walsh70369fd2016-11-22 11:25:57 -0600804 else:
Michael Walsh3eb50022017-03-21 11:27:30 -0500805 gp.print_timen("The states match:")
806 gp.print_var(state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600807
808 return state
809
810###############################################################################
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600811
812
813###############################################################################
814def wait_for_comm_cycle(start_boot_seconds):
815
816 r"""
817 Wait for communications to the BMC to stop working and then resume working.
818 This function is useful when you have initiated some kind of reboot.
819
820 Description of arguments:
821 start_boot_seconds The time that the boot test started. The format is the
822 epoch time in seconds, i.e. the number of seconds since
823 1970-01-01 00:00:00 UTC. This value should be obtained
824 from the BMC so that it is not dependent on any kind of
825 synchronization between this machine and the target BMC
826 This will allow this program to work correctly even in
827 a simulated environment. This value should be obtained
828 by the caller prior to initiating a reboot. It can be
829 obtained as follows:
830 state = st.get_state(req_states=['epoch_seconds'])
831 """
832
833 # Validate parms.
834 error_message = gv.svalid_integer(start_boot_seconds,
835 var_name="start_boot_seconds")
836 if error_message != "":
837 BuiltIn().fail(gp.sprint_error(error_message))
838
839 match_state = anchor_state(DotDict([('packet_loss', '100')]))
840 # Wait for 100% packet loss trying to ping machine.
841 wait_state(match_state, wait_time="3 mins", interval="0 seconds")
842
843 match_state['packet_loss'] = '^0$'
844 # Wait for 0% packet loss trying to ping machine.
845 wait_state(match_state, wait_time="4 mins", interval="0 seconds")
846
847 # Get the uptime and epoch seconds for comparisons. We want to be sure
848 # that the uptime is less than the elapsed boot time. Further proof that
849 # a reboot has indeed occurred (vs random network instability giving a
850 # false positive.
851 state = get_state(req_states=['uptime', 'epoch_seconds'])
852
853 elapsed_boot_time = int(state['epoch_seconds']) - start_boot_seconds
Michael Walsh3eb50022017-03-21 11:27:30 -0500854 gp.print_var(elapsed_boot_time)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600855 if int(float(state['uptime'])) < elapsed_boot_time:
856 uptime = state['uptime']
Michael Walsh3eb50022017-03-21 11:27:30 -0500857 gp.print_var(uptime)
858 gp.print_timen("The uptime is less than the elapsed boot time," +
859 " as expected.")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600860 else:
861 error_message = "The uptime is greater than the elapsed boot time," +\
862 " which is unexpected:\n" +\
863 gp.sprint_var(start_boot_seconds) +\
864 gp.sprint_var(state)
865 BuiltIn().fail(gp.sprint_error(error_message))
866
Michael Walsh3eb50022017-03-21 11:27:30 -0500867 gp.print_timen("Verifying that REST API interface is working.")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600868 match_state = DotDict([('chassis', '.*')])
869 state = wait_state(match_state, wait_time="5 mins", interval="2 seconds")
870
871###############################################################################