blob: d482f81a0591c4b17c3ceaa6b262a839aa50ae4e [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 Walsh56749222017-09-29 15:26:07 -050014 default_state[operating_system]: BootComplete
Michael Walsh65b12542017-02-03 15:34:38 -060015 default_state[host]: Running
Michael Walsh341c21e2017-01-17 16:25:20 -060016 default_state[os_ping]: 1
17 default_state[os_login]: 1
18 default_state[os_run_cmd]: 1
Michael Walsh70369fd2016-11-22 11:25:57 -060019
20Different users may very well have different needs when inquiring about
Michael Walsh8fae6ea2017-02-20 16:14:44 -060021state. Support for new pieces of state information may be added to this
22module as needed.
Michael Walsh70369fd2016-11-22 11:25:57 -060023
24By using the wait_state function, a caller can start a boot and then wait for
25a precisely defined state to indicate that the boot has succeeded. If
26the boot fails, they can see exactly why by looking at the current state as
27compared with the expected state.
28"""
29
30import gen_print as gp
31import gen_robot_print as grp
32import gen_valid as gv
Michael Walsh16cbb7f2017-02-02 15:54:16 -060033import gen_robot_utils as gru
Michael Walsh8fae6ea2017-02-20 16:14:44 -060034import gen_cmd as gc
Michael Walsh70369fd2016-11-22 11:25:57 -060035
36import commands
37from robot.libraries.BuiltIn import BuiltIn
Michael Walsh341c21e2017-01-17 16:25:20 -060038from robot.utils import DotDict
Michael Walsh70369fd2016-11-22 11:25:57 -060039
40import re
Michael Walsh341c21e2017-01-17 16:25:20 -060041import os
Michael Walsh56749222017-09-29 15:26:07 -050042import sys
43import imp
44
Michael Walsh70369fd2016-11-22 11:25:57 -060045
Michael Walsh619aa332017-04-12 15:56:51 -050046# We need utils.robot to get keywords like "Get Chassis Power State".
Michael Walsh16cbb7f2017-02-02 15:54:16 -060047gru.my_import_resource("utils.robot")
48gru.my_import_resource("state_manager.robot")
Michael Walsh70369fd2016-11-22 11:25:57 -060049
Michael Walsh56749222017-09-29 15:26:07 -050050base_path = os.path.dirname(os.path.dirname(
51 imp.find_module("gen_robot_print")[1])) + os.sep
52sys.path.append(base_path + "data/")
53import variables as var
54
55
Michael Walsh8fae6ea2017-02-20 16:14:44 -060056# The BMC code has recently been changed as far as what states are defined and
57# what the state values can be. This module now has a means of processing both
58# the old style state (i.e. OBMC_STATES_VERSION = 0) and the new style (i.e.
Michael Walsh16cbb7f2017-02-02 15:54:16 -060059# OBMC_STATES_VERSION = 1).
Michael Walsh341c21e2017-01-17 16:25:20 -060060# The caller can set environment variable OBMC_STATES_VERSION to dictate
61# whether we're processing old or new style states. If OBMC_STATES_VERSION is
Michael Walsh8fae6ea2017-02-20 16:14:44 -060062# not set it will default to 1.
Michael Walsh341c21e2017-01-17 16:25:20 -060063
Michael Walsh619aa332017-04-12 15:56:51 -050064# As of the present moment, OBMC_STATES_VERSION of 0 is for cold that is so old
65# that it is no longer worthwhile to maintain. The OBMC_STATES_VERSION 0 code
66# is being removed but the OBMC_STATES_VERSION value will stay for now in the
67# event that it is needed in the future.
68
Michael Walsh8fae6ea2017-02-20 16:14:44 -060069OBMC_STATES_VERSION = int(os.environ.get('OBMC_STATES_VERSION', 1))
Michael Walsh341c21e2017-01-17 16:25:20 -060070
Michael Walsh619aa332017-04-12 15:56:51 -050071# When a user calls get_state w/o specifying req_states, default_req_states
72# is used as its value.
73default_req_states = ['rest',
74 'chassis',
75 'bmc',
76 'boot_progress',
Michael Walsh56749222017-09-29 15:26:07 -050077 'operating_system',
Michael Walsh619aa332017-04-12 15:56:51 -050078 'host',
79 'os_ping',
80 'os_login',
81 'os_run_cmd']
Michael Walsh8fae6ea2017-02-20 16:14:44 -060082
Michael Walsh619aa332017-04-12 15:56:51 -050083# valid_req_states is a list of sub states supported by the get_state function.
84# valid_req_states, default_req_states and master_os_up_match are used by the
85# get_state function.
86valid_req_states = ['ping',
87 'packet_loss',
88 'uptime',
89 'epoch_seconds',
90 'rest',
91 'chassis',
Michael Walsh56749222017-09-29 15:26:07 -050092 'requested_chassis',
Michael Walsh619aa332017-04-12 15:56:51 -050093 'bmc',
Michael Walsh56749222017-09-29 15:26:07 -050094 'requested_bmc',
Michael Walsh619aa332017-04-12 15:56:51 -050095 'boot_progress',
Michael Walsh56749222017-09-29 15:26:07 -050096 'operating_system',
Michael Walsh619aa332017-04-12 15:56:51 -050097 'host',
Michael Walsh56749222017-09-29 15:26:07 -050098 'requested_host',
99 'attempts_left',
Michael Walsh619aa332017-04-12 15:56:51 -0500100 'os_ping',
101 'os_login',
102 'os_run_cmd']
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600103
104# valid_os_req_states and default_os_req_states are used by the os_get_state
105# function.
106# valid_os_req_states is a list of state information supported by the
107# get_os_state function.
108valid_os_req_states = ['os_ping',
109 'os_login',
110 'os_run_cmd']
111# When a user calls get_os_state w/o specifying req_states,
112# default_os_req_states is used as its value.
113default_os_req_states = ['os_ping',
114 'os_login',
115 'os_run_cmd']
116
117# Presently, some BMCs appear to not keep time very well. This environment
118# variable directs the get_state function to use either the BMC's epoch time
119# or the local epoch time.
120USE_BMC_EPOCH_TIME = int(os.environ.get('USE_BMC_EPOCH_TIME', 0))
Michael Walsh341c21e2017-01-17 16:25:20 -0600121
Michael Walsh619aa332017-04-12 15:56:51 -0500122# Useful state constant definition(s).
123# A match state for checking that the system is at "standby".
124standby_match_state = DotDict([('rest', '^1$'),
125 ('chassis', '^Off$'),
126 ('bmc', '^Ready$'),
127 ('boot_progress', ''),
Michael Walsh56749222017-09-29 15:26:07 -0500128 ('operating_system', ''),
Michael Walsh619aa332017-04-12 15:56:51 -0500129 ('host', '')])
130
131# default_state is an initial value which may be of use to callers.
132default_state = DotDict([('rest', '1'),
133 ('chassis', 'On'),
134 ('bmc', 'Ready'),
Michael Walsh01975fa2017-08-20 20:51:36 -0500135 ('boot_progress', 'OSStart'),
Michael Walsh56749222017-09-29 15:26:07 -0500136 ('operating_system', 'BootComplete'),
Michael Walsh619aa332017-04-12 15:56:51 -0500137 ('host', 'Running'),
138 ('os_ping', '1'),
139 ('os_login', '1'),
140 ('os_run_cmd', '1')])
141
142# A master dictionary to determine whether the os may be up.
143master_os_up_match = DotDict([('chassis', '^On$'),
144 ('bmc', '^Ready$'),
145 ('boot_progress',
Michael Walsh01975fa2017-08-20 20:51:36 -0500146 'FW Progress, Starting OS|OSStart'),
Michael Walsh56749222017-09-29 15:26:07 -0500147 ('operating_system', 'BootComplete'),
Michael Walsh619aa332017-04-12 15:56:51 -0500148 ('host', '^Running$')])
149
Michael Walsh45ca6e42017-09-14 17:29:12 -0500150invalid_state_match = DotDict([('rest', '^$'),
151 ('chassis', '^$'),
152 ('bmc', '^$'),
153 ('boot_progress', '^$'),
Michael Walsh56749222017-09-29 15:26:07 -0500154 ('operating_system', '^$'),
Michael Walsh45ca6e42017-09-14 17:29:12 -0500155 ('host', '^$')])
156
Michael Walsh341c21e2017-01-17 16:25:20 -0600157
158###############################################################################
159def return_default_state():
160
161 r"""
162 Return default state dictionary.
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600163
164 default_state is an initial value which may be of use to callers.
Michael Walsh341c21e2017-01-17 16:25:20 -0600165 """
166
167 return default_state
168
169###############################################################################
170
Michael Walsh70369fd2016-11-22 11:25:57 -0600171
Michael Walsh619aa332017-04-12 15:56:51 -0500172valid_state_constants = ['default', 'standby_match_state']
173
174
175###############################################################################
176def return_state_constant(state_name='default'):
177
178 r"""
179 Return default state dictionary.
180
181 default_state is an initial value which may be of use to callers.
182 """
183
184 error_message = gv.svalid_value(state_name, var_name='state_name',
185 valid_values=valid_state_constants)
186 if error_message != "":
187 BuiltIn().fail(gp.sprint_error(error_message))
188
189 if state_name == 'default':
190 return default_state
191 elif state_name == 'standby_match_state':
192 return standby_match_state
193
194###############################################################################
195
196
Michael Walsh70369fd2016-11-22 11:25:57 -0600197###############################################################################
198def anchor_state(state):
199
200 r"""
201 Add regular expression anchors ("^" and "$") to the beginning and end of
202 each item in the state dictionary passed in. Return the resulting
203 dictionary.
204
205 Description of Arguments:
206 state A dictionary such as the one returned by the get_state()
207 function.
208 """
209
Michael Walsh2ce067a2017-02-27 14:24:07 -0600210 anchored_state = state.copy()
Michael Walsh70369fd2016-11-22 11:25:57 -0600211 for key, match_state_value in anchored_state.items():
212 anchored_state[key] = "^" + str(anchored_state[key]) + "$"
213
214 return anchored_state
215
216###############################################################################
217
218
219###############################################################################
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600220def strip_anchor_state(state):
221
222 r"""
223 Strip regular expression anchors ("^" and "$") from the beginning and end
224 of each item in the state dictionary passed in. Return the resulting
225 dictionary.
226
227 Description of Arguments:
228 state A dictionary such as the one returned by the get_state()
229 function.
230 """
231
Michael Walsh2ce067a2017-02-27 14:24:07 -0600232 stripped_state = state.copy()
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600233 for key, match_state_value in stripped_state.items():
234 stripped_state[key] = stripped_state[key].strip("^$")
235
236 return stripped_state
237
238###############################################################################
239
240
241###############################################################################
Michael Walsh70369fd2016-11-22 11:25:57 -0600242def compare_states(state,
Michael Walsh45ca6e42017-09-14 17:29:12 -0500243 match_state,
244 match_type='and'):
Michael Walsh70369fd2016-11-22 11:25:57 -0600245
246 r"""
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600247 Compare 2 state dictionaries. Return True if they match and False if they
Michael Walsh70369fd2016-11-22 11:25:57 -0600248 don't. Note that the match_state dictionary does not need to have an entry
249 corresponding to each entry in the state dictionary. But for each entry
250 that it does have, the corresponding state entry will be checked for a
251 match.
252
253 Description of arguments:
254 state A state dictionary such as the one returned by the
255 get_state function.
256 match_state A dictionary whose key/value pairs are "state field"/
257 "state value". The state value is interpreted as a
258 regular expression. Every value in this dictionary is
Michael Walsh45ca6e42017-09-14 17:29:12 -0500259 considered. When match_type is 'and', if each and every
260 comparison matches, the two dictionaries are considered to
261 be matching. If match_type is 'or', if any two of the
262 elements compared match, the two dictionaries are
263 considered to be matching.
264 match_type This may be 'and' or 'or'.
Michael Walsh70369fd2016-11-22 11:25:57 -0600265 """
266
Michael Walsh45ca6e42017-09-14 17:29:12 -0500267 error_message = gv.svalid_value(match_type, var_name="match_type",
268 valid_values=['and', 'or'])
269 if error_message != "":
270 BuiltIn().fail(gp.sprint_error(error_message))
271
272 default_match = (match_type == 'and')
Michael Walsh70369fd2016-11-22 11:25:57 -0600273 for key, match_state_value in match_state.items():
Michael Walsh97df71c2017-03-27 14:33:24 -0500274 # Blank match_state_value means "don't care".
275 if match_state_value == "":
276 continue
Michael Walsh70369fd2016-11-22 11:25:57 -0600277 try:
Michael Walsh45ca6e42017-09-14 17:29:12 -0500278 match = (re.match(match_state_value, str(state[key])) is not None)
Michael Walsh70369fd2016-11-22 11:25:57 -0600279 except KeyError:
280 match = False
Michael Walsh70369fd2016-11-22 11:25:57 -0600281
Michael Walsh45ca6e42017-09-14 17:29:12 -0500282 if match != default_match:
283 return match
284
285 return default_match
Michael Walsh70369fd2016-11-22 11:25:57 -0600286
287###############################################################################
288
289
290###############################################################################
291def get_os_state(os_host="",
292 os_username="",
293 os_password="",
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600294 req_states=default_os_req_states,
295 os_up=True,
Michael Walsh70369fd2016-11-22 11:25:57 -0600296 quiet=None):
297
298 r"""
299 Get component states for the operating system such as ping, login,
300 etc, put them into a dictionary and return them to the caller.
301
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600302 Note that all substate values are strings.
303
Michael Walsh70369fd2016-11-22 11:25:57 -0600304 Description of arguments:
305 os_host The DNS name or IP address of the operating system.
306 This defaults to global ${OS_HOST}.
307 os_username The username to be used to login to the OS.
308 This defaults to global ${OS_USERNAME}.
309 os_password The password to be used to login to the OS.
310 This defaults to global ${OS_PASSWORD}.
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600311 req_states This is a list of states whose values are being requested by
312 the caller.
313 os_up If the caller knows that the os can't possibly be up, it can
314 improve performance by passing os_up=False. This function
315 will then simply return default values for all requested os
316 sub states.
Michael Walsh70369fd2016-11-22 11:25:57 -0600317 quiet Indicates whether status details (e.g. curl commands) should
318 be written to the console.
319 Defaults to either global value of ${QUIET} or to 1.
320 """
321
Michael Walsh619aa332017-04-12 15:56:51 -0500322 quiet = int(gp.get_var_value(quiet, 0))
Michael Walsh70369fd2016-11-22 11:25:57 -0600323
324 # Set parm defaults where necessary and validate all parms.
325 if os_host == "":
326 os_host = BuiltIn().get_variable_value("${OS_HOST}")
327 error_message = gv.svalid_value(os_host, var_name="os_host",
328 invalid_values=[None, ""])
329 if error_message != "":
330 BuiltIn().fail(gp.sprint_error(error_message))
331
332 if os_username == "":
333 os_username = BuiltIn().get_variable_value("${OS_USERNAME}")
334 error_message = gv.svalid_value(os_username, var_name="os_username",
335 invalid_values=[None, ""])
336 if error_message != "":
337 BuiltIn().fail(gp.sprint_error(error_message))
338
339 if os_password == "":
340 os_password = BuiltIn().get_variable_value("${OS_PASSWORD}")
341 error_message = gv.svalid_value(os_password, var_name="os_password",
342 invalid_values=[None, ""])
343 if error_message != "":
344 BuiltIn().fail(gp.sprint_error(error_message))
345
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600346 invalid_req_states = [sub_state for sub_state in req_states
347 if sub_state not in valid_os_req_states]
348 if len(invalid_req_states) > 0:
349 error_message = "The following req_states are not supported:\n" +\
350 gp.sprint_var(invalid_req_states)
351 BuiltIn().fail(gp.sprint_error(error_message))
Michael Walsh70369fd2016-11-22 11:25:57 -0600352
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600353 # Initialize all substate values supported by this function.
354 os_ping = 0
355 os_login = 0
356 os_run_cmd = 0
Michael Walsh70369fd2016-11-22 11:25:57 -0600357
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600358 if os_up:
359 if 'os_ping' in req_states:
360 # See if the OS pings.
361 cmd_buf = "ping -c 1 -w 2 " + os_host
362 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500363 gp.pissuing(cmd_buf)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600364 rc, out_buf = commands.getstatusoutput(cmd_buf)
365 if rc == 0:
366 os_ping = 1
Michael Walsh70369fd2016-11-22 11:25:57 -0600367
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600368 # Programming note: All attributes which do not require an ssh login
369 # should have been processed by this point.
370 master_req_login = ['os_login', 'os_run_cmd']
371 req_login = [sub_state for sub_state in req_states if sub_state in
372 master_req_login]
Michael Walsh97df71c2017-03-27 14:33:24 -0500373 must_login = (len(req_login) > 0)
Michael Walsh70369fd2016-11-22 11:25:57 -0600374
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600375 if must_login:
Michael Walsh3eb50022017-03-21 11:27:30 -0500376 # Open SSH connection to OS. Note that this doesn't fail even when
377 # the OS is not up.
Michael Walshac275512017-03-07 11:39:28 -0600378 cmd_buf = ["SSHLibrary.Open Connection", os_host]
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600379 if not quiet:
380 grp.rpissuing_keyword(cmd_buf)
381 ix = BuiltIn().run_keyword(*cmd_buf)
382
383 # Login to OS.
384 cmd_buf = ["Login", os_username, os_password]
385 if not quiet:
386 grp.rpissuing_keyword(cmd_buf)
Michael Walsh3eb50022017-03-21 11:27:30 -0500387 status, ret_values = \
388 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600389 if status == "PASS":
390 os_login = 1
Michael Walsh3eb50022017-03-21 11:27:30 -0500391 else:
392 gp.dprint_var(status)
393 gp.dprint_var(ret_values)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600394
395 if os_login:
396 if 'os_run_cmd' in req_states:
Michael Walsh3eb50022017-03-21 11:27:30 -0500397 # Try running a simple command (uptime) on the OS.
398 cmd_buf = ["Execute Command", "uptime",
399 "return_stderr=True", "return_rc=True"]
400 if not quiet:
401 grp.rpissuing_keyword(cmd_buf)
402 # Note that in spite of its name, there are occasions
403 # where run_keyword_and_ignore_error can fail.
404 status, ret_values = \
405 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
406 if status == "PASS":
407 stdout, stderr, rc = ret_values
408 if rc == 0 and stderr == "":
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600409 os_run_cmd = 1
Michael Walsh3eb50022017-03-21 11:27:30 -0500410 else:
411 gp.dprint_var(status)
412 gp.dprint_var(stdout)
413 gp.dprint_var(stderr)
414 gp.dprint_var(rc)
415 else:
416 gp.dprint_var(status)
417 gp.dprint_var(ret_values)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600418
419 os_state = DotDict()
420 for sub_state in req_states:
421 cmd_buf = "os_state['" + sub_state + "'] = str(" + sub_state + ")"
422 exec(cmd_buf)
Michael Walsh70369fd2016-11-22 11:25:57 -0600423
424 return os_state
425
426###############################################################################
427
428
429###############################################################################
430def get_state(openbmc_host="",
431 openbmc_username="",
432 openbmc_password="",
433 os_host="",
434 os_username="",
435 os_password="",
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600436 req_states=default_req_states,
Michael Walsh70369fd2016-11-22 11:25:57 -0600437 quiet=None):
438
439 r"""
Michael Walsh619aa332017-04-12 15:56:51 -0500440 Get component states such as chassis state, bmc state, etc, put them into a
Michael Walsh70369fd2016-11-22 11:25:57 -0600441 dictionary and return them to the caller.
442
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600443 Note that all substate values are strings.
444
Michael Walsh70369fd2016-11-22 11:25:57 -0600445 Description of arguments:
446 openbmc_host The DNS name or IP address of the BMC.
447 This defaults to global ${OPENBMC_HOST}.
448 openbmc_username The username to be used to login to the BMC.
449 This defaults to global ${OPENBMC_USERNAME}.
450 openbmc_password The password to be used to login to the BMC.
451 This defaults to global ${OPENBMC_PASSWORD}.
452 os_host The DNS name or IP address of the operating system.
453 This defaults to global ${OS_HOST}.
454 os_username The username to be used to login to the OS.
455 This defaults to global ${OS_USERNAME}.
456 os_password The password to be used to login to the OS.
457 This defaults to global ${OS_PASSWORD}.
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600458 req_states This is a list of states whose values are being requested
459 by the caller.
Michael Walsh70369fd2016-11-22 11:25:57 -0600460 quiet Indicates whether status details (e.g. curl commands)
461 should be written to the console.
462 Defaults to either global value of ${QUIET} or to 1.
463 """
464
Michael Walsh619aa332017-04-12 15:56:51 -0500465 quiet = int(gp.get_var_value(quiet, 0))
Michael Walsh70369fd2016-11-22 11:25:57 -0600466
467 # Set parm defaults where necessary and validate all parms.
468 if openbmc_host == "":
469 openbmc_host = BuiltIn().get_variable_value("${OPENBMC_HOST}")
470 error_message = gv.svalid_value(openbmc_host,
471 var_name="openbmc_host",
472 invalid_values=[None, ""])
473 if error_message != "":
474 BuiltIn().fail(gp.sprint_error(error_message))
475
476 if openbmc_username == "":
477 openbmc_username = BuiltIn().get_variable_value("${OPENBMC_USERNAME}")
478 error_message = gv.svalid_value(openbmc_username,
479 var_name="openbmc_username",
480 invalid_values=[None, ""])
481 if error_message != "":
482 BuiltIn().fail(gp.sprint_error(error_message))
483
484 if openbmc_password == "":
485 openbmc_password = BuiltIn().get_variable_value("${OPENBMC_PASSWORD}")
486 error_message = gv.svalid_value(openbmc_password,
487 var_name="openbmc_password",
488 invalid_values=[None, ""])
489 if error_message != "":
490 BuiltIn().fail(gp.sprint_error(error_message))
491
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600492 # NOTE: OS parms are optional.
Michael Walsh70369fd2016-11-22 11:25:57 -0600493 if os_host == "":
494 os_host = BuiltIn().get_variable_value("${OS_HOST}")
495 if os_host is None:
496 os_host = ""
497
498 if os_username is "":
499 os_username = BuiltIn().get_variable_value("${OS_USERNAME}")
500 if os_username is None:
501 os_username = ""
502
503 if os_password is "":
504 os_password = BuiltIn().get_variable_value("${OS_PASSWORD}")
505 if os_password is None:
506 os_password = ""
507
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600508 invalid_req_states = [sub_state for sub_state in req_states
509 if sub_state not in valid_req_states]
510 if len(invalid_req_states) > 0:
511 error_message = "The following req_states are not supported:\n" +\
512 gp.sprint_var(invalid_req_states)
513 BuiltIn().fail(gp.sprint_error(error_message))
514
515 # Initialize all substate values supported by this function.
516 ping = 0
517 packet_loss = ''
518 uptime = ''
519 epoch_seconds = ''
Michael Walsh2b269de2017-10-09 11:18:11 -0500520 rest = ''
521 chassis = ''
522 requested_chassis = ''
523 bmc = ''
524 requested_bmc = ''
525 boot_progress = ''
526 operating_system = ''
527 host = ''
528 requested_host = ''
529 attempts_left = ''
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600530
Michael Walsh70369fd2016-11-22 11:25:57 -0600531 # Get the component states.
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600532 if 'ping' in req_states:
533 # See if the OS pings.
534 cmd_buf = "ping -c 1 -w 2 " + openbmc_host
535 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500536 gp.pissuing(cmd_buf)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600537 rc, out_buf = commands.getstatusoutput(cmd_buf)
538 if rc == 0:
539 ping = 1
540
541 if 'packet_loss' in req_states:
542 # See if the OS pings.
543 cmd_buf = "ping -c 5 -w 5 " + openbmc_host +\
544 " | egrep 'packet loss' | sed -re 's/.* ([0-9]+)%.*/\\1/g'"
545 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500546 gp.pissuing(cmd_buf)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600547 rc, out_buf = commands.getstatusoutput(cmd_buf)
548 if rc == 0:
549 packet_loss = out_buf.rstrip("\n")
550
Michael Walshe53e47a2017-06-30 17:03:24 -0500551 if 'uptime' in req_states:
Michael Walshfa765932017-10-13 14:07:22 -0500552 # Sometimes reading uptime results in a blank value. Call with
553 # wait_until_keyword_succeeds to ensure a non-blank value is obtained.
554 remote_cmd_buf = "read uptime filler 2>/dev/null < /proc/uptime" +\
555 " && [ ! -z \"${uptime}\" ] && echo ${uptime}"
556 cmd_buf = ["BMC Execute Command", re.sub(r'\$', '\$', remote_cmd_buf),
557 'quiet=1']
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600558 if not quiet:
559 grp.rpissuing_keyword(cmd_buf)
Michael Walshfa765932017-10-13 14:07:22 -0500560 grp.rpissuing(remote_cmd_buf)
561 try:
562 stdout, stderr, rc =\
563 BuiltIn().wait_until_keyword_succeeds("5 sec", "0 sec",
564 *cmd_buf)
Michael Walsh97df71c2017-03-27 14:33:24 -0500565 if rc == 0 and stderr == "":
566 uptime = stdout
Michael Walshfa765932017-10-13 14:07:22 -0500567 except AssertionError as my_assertion_error:
568 pass
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600569
Michael Walshe53e47a2017-06-30 17:03:24 -0500570 if 'epoch_seconds' in req_states:
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600571 date_cmd_buf = "date -u +%s"
572 if USE_BMC_EPOCH_TIME:
Michael Walshe53e47a2017-06-30 17:03:24 -0500573 cmd_buf = ["BMC Execute Command", date_cmd_buf, 'quiet=${1}']
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600574 if not quiet:
575 grp.rpissuing_keyword(cmd_buf)
Michael Walsh97df71c2017-03-27 14:33:24 -0500576 status, ret_values = \
577 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
578 if status == "PASS":
579 stdout, stderr, rc = ret_values
580 if rc == 0 and stderr == "":
581 epoch_seconds = stdout.rstrip("\n")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600582 else:
583 shell_rc, out_buf = gc.cmd_fnc_u(date_cmd_buf,
Michael Walshfa765932017-10-13 14:07:22 -0500584 quiet=quiet,
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600585 print_output=0)
586 if shell_rc == 0:
587 epoch_seconds = out_buf.rstrip("\n")
588
Michael Walsh56749222017-09-29 15:26:07 -0500589 master_req_rest = ['rest', 'host', 'requested_host', 'operating_system',
590 'attempts_left', 'boot_progress', 'chassis',
591 'requested_chassis' 'bmc' 'requested_bmc']
592
Michael Walshb95eb542017-03-31 09:39:20 -0500593 req_rest = [sub_state for sub_state in req_states if sub_state in
594 master_req_rest]
595 need_rest = (len(req_rest) > 0)
Michael Walsh56749222017-09-29 15:26:07 -0500596 state = DotDict()
597 if need_rest:
598 cmd_buf = ["Read Properties", var.SYSTEM_STATE_URI + "enumerate",
599 "quiet=${" + str(quiet) + "}"]
Michael Walsh341c21e2017-01-17 16:25:20 -0600600 grp.rdpissuing_keyword(cmd_buf)
Michael Walsh97df71c2017-03-27 14:33:24 -0500601 status, ret_values = \
602 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
603 if status == "PASS":
Michael Walsh56749222017-09-29 15:26:07 -0500604 state['rest'] = '1'
Michael Walsh341c21e2017-01-17 16:25:20 -0600605 else:
Michael Walsh56749222017-09-29 15:26:07 -0500606 state['rest'] = '0'
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600607
Michael Walsh2b269de2017-10-09 11:18:11 -0500608 if int(state['rest']):
609 for url_path in ret_values:
610 for attr_name in ret_values[url_path]:
611 # Create a state key value based on the attr_name.
612 if type(ret_values[url_path][attr_name]) is unicode:
613 ret_values[url_path][attr_name] = \
614 re.sub(r'.*\.', "",
615 ret_values[url_path][attr_name])
616 # Do some key name manipulations.
617 new_attr_name = re.sub(r'^Current|(State|Transition)$',
618 "", attr_name)
619 new_attr_name = re.sub(r'BMC', r'Bmc', new_attr_name)
620 new_attr_name = re.sub(r'([A-Z][a-z])', r'_\1',
621 new_attr_name)
622 new_attr_name = new_attr_name.lower().lstrip("_")
623 new_attr_name = re.sub(r'power', r'chassis', new_attr_name)
624 if new_attr_name in req_states:
625 state[new_attr_name] = ret_values[url_path][attr_name]
Michael Walshb95eb542017-03-31 09:39:20 -0500626
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600627 for sub_state in req_states:
Michael Walsh56749222017-09-29 15:26:07 -0500628 if sub_state in state:
629 continue
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600630 if sub_state.startswith("os_"):
631 # We pass "os_" requests on to get_os_state.
632 continue
633 cmd_buf = "state['" + sub_state + "'] = str(" + sub_state + ")"
634 exec(cmd_buf)
635
636 if os_host == "":
637 # The caller has not specified an os_host so as far as we're concerned,
638 # it doesn't exist.
639 return state
640
641 os_req_states = [sub_state for sub_state in req_states
642 if sub_state.startswith('os_')]
643
644 if len(os_req_states) > 0:
645 # The caller has specified an os_host and they have requested
646 # information on os substates.
647
648 # Based on the information gathered on bmc, we'll try to make a
649 # determination of whether the os is even up. We'll pass the result
650 # of that assessment to get_os_state to enhance performance.
651 os_up_match = DotDict()
652 for sub_state in master_os_up_match:
653 if sub_state in req_states:
654 os_up_match[sub_state] = master_os_up_match[sub_state]
Michael Walsh70369fd2016-11-22 11:25:57 -0600655 os_up = compare_states(state, os_up_match)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600656 os_state = get_os_state(os_host=os_host,
657 os_username=os_username,
658 os_password=os_password,
659 req_states=os_req_states,
660 os_up=os_up,
661 quiet=quiet)
662 # Append os_state dictionary to ours.
663 state.update(os_state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600664
665 return state
666
667###############################################################################
668
669
670###############################################################################
671def check_state(match_state,
672 invert=0,
673 print_string="",
674 openbmc_host="",
675 openbmc_username="",
676 openbmc_password="",
677 os_host="",
678 os_username="",
679 os_password="",
680 quiet=None):
681
682 r"""
683 Check that the Open BMC machine's composite state matches the specified
684 state. On success, this keyword returns the machine's composite state as a
685 dictionary.
686
687 Description of arguments:
688 match_state A dictionary whose key/value pairs are "state field"/
689 "state value". The state value is interpreted as a
690 regular expression. Example call from robot:
Michael Walsh341c21e2017-01-17 16:25:20 -0600691 ${match_state}= Create Dictionary chassis=^On$
692 ... bmc=^Ready$
Michael Walsh01975fa2017-08-20 20:51:36 -0500693 ... boot_progress=^OSStart$
Michael Walsh70369fd2016-11-22 11:25:57 -0600694 ${state}= Check State &{match_state}
695 invert If this flag is set, this function will succeed if the
696 states do NOT match.
697 print_string This function will print this string to the console prior
698 to getting the state.
699 openbmc_host The DNS name or IP address of the BMC.
700 This defaults to global ${OPENBMC_HOST}.
701 openbmc_username The username to be used to login to the BMC.
702 This defaults to global ${OPENBMC_USERNAME}.
703 openbmc_password The password to be used to login to the BMC.
704 This defaults to global ${OPENBMC_PASSWORD}.
705 os_host The DNS name or IP address of the operating system.
706 This defaults to global ${OS_HOST}.
707 os_username The username to be used to login to the OS.
708 This defaults to global ${OS_USERNAME}.
709 os_password The password to be used to login to the OS.
710 This defaults to global ${OS_PASSWORD}.
711 quiet Indicates whether status details should be written to the
712 console. Defaults to either global value of ${QUIET} or
713 to 1.
714 """
715
Michael Walsh619aa332017-04-12 15:56:51 -0500716 quiet = int(gp.get_var_value(quiet, 0))
Michael Walsh70369fd2016-11-22 11:25:57 -0600717
718 grp.rprint(print_string)
719
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600720 req_states = match_state.keys()
Michael Walsh70369fd2016-11-22 11:25:57 -0600721 # Initialize state.
722 state = get_state(openbmc_host=openbmc_host,
723 openbmc_username=openbmc_username,
724 openbmc_password=openbmc_password,
725 os_host=os_host,
726 os_username=os_username,
727 os_password=os_password,
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600728 req_states=req_states,
Michael Walsh70369fd2016-11-22 11:25:57 -0600729 quiet=quiet)
730 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500731 gp.print_var(state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600732
733 match = compare_states(state, match_state)
734
735 if invert and match:
736 fail_msg = "The current state of the machine matches the match" +\
737 " state:\n" + gp.sprint_varx("state", state)
738 BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
739 elif not invert and not match:
740 fail_msg = "The current state of the machine does NOT match the" +\
741 " match state:\n" +\
742 gp.sprint_varx("state", state)
743 BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
744
745 return state
746
747###############################################################################
748
749
750###############################################################################
Michael Walshf893ba02017-01-10 10:28:05 -0600751def wait_state(match_state=(),
Michael Walsh70369fd2016-11-22 11:25:57 -0600752 wait_time="1 min",
753 interval="1 second",
754 invert=0,
755 openbmc_host="",
756 openbmc_username="",
757 openbmc_password="",
758 os_host="",
759 os_username="",
760 os_password="",
761 quiet=None):
762
763 r"""
764 Wait for the Open BMC machine's composite state to match the specified
765 state. On success, this keyword returns the machine's composite state as
766 a dictionary.
767
768 Description of arguments:
769 match_state A dictionary whose key/value pairs are "state field"/
770 "state value". See check_state (above) for details.
Michael Walsh619aa332017-04-12 15:56:51 -0500771 This value may also be any string accepted by
772 return_state_constant (e.g. "standby_match_state").
773 In such a case this function will call
774 return_state_constant to convert it to a proper
775 dictionary as described above.
Michael Walsh70369fd2016-11-22 11:25:57 -0600776 wait_time The total amount of time to wait for the desired state.
777 This value may be expressed in Robot Framework's time
778 format (e.g. 1 minute, 2 min 3 s, 4.5).
779 interval The amount of time between state checks.
780 This value may be expressed in Robot Framework's time
781 format (e.g. 1 minute, 2 min 3 s, 4.5).
782 invert If this flag is set, this function will for the state of
783 the machine to cease to match the match state.
784 openbmc_host The DNS name or IP address of the BMC.
785 This defaults to global ${OPENBMC_HOST}.
786 openbmc_username The username to be used to login to the BMC.
787 This defaults to global ${OPENBMC_USERNAME}.
788 openbmc_password The password to be used to login to the BMC.
789 This defaults to global ${OPENBMC_PASSWORD}.
790 os_host The DNS name or IP address of the operating system.
791 This defaults to global ${OS_HOST}.
792 os_username The username to be used to login to the OS.
793 This defaults to global ${OS_USERNAME}.
794 os_password The password to be used to login to the OS.
795 This defaults to global ${OS_PASSWORD}.
796 quiet Indicates whether status details should be written to the
797 console. Defaults to either global value of ${QUIET} or
798 to 1.
799 """
800
Michael Walsh619aa332017-04-12 15:56:51 -0500801 quiet = int(gp.get_var_value(quiet, 0))
802
803 if type(match_state) in (str, unicode):
804 match_state = return_state_constant(match_state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600805
806 if not quiet:
807 if invert:
808 alt_text = "cease to "
809 else:
810 alt_text = ""
Michael Walsh3eb50022017-03-21 11:27:30 -0500811 gp.print_timen("Checking every " + str(interval) + " for up to " +
812 str(wait_time) + " for the state of the machine to " +
813 alt_text + "match the state shown below.")
814 gp.print_var(match_state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600815
Michael Walshf893ba02017-01-10 10:28:05 -0600816 if quiet:
Michael Walsh341c21e2017-01-17 16:25:20 -0600817 print_string = ""
Michael Walshf893ba02017-01-10 10:28:05 -0600818 else:
Michael Walsh341c21e2017-01-17 16:25:20 -0600819 print_string = "#"
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600820
821 debug = int(BuiltIn().get_variable_value("${debug}", "0"))
822 if debug:
823 # In debug we print state so no need to print the "#".
824 print_string = ""
825 check_state_quiet = 1 - debug
Michael Walsh70369fd2016-11-22 11:25:57 -0600826 cmd_buf = ["Check State", match_state, "invert=${" + str(invert) + "}",
Michael Walshf893ba02017-01-10 10:28:05 -0600827 "print_string=" + print_string, "openbmc_host=" + openbmc_host,
Michael Walsh70369fd2016-11-22 11:25:57 -0600828 "openbmc_username=" + openbmc_username,
829 "openbmc_password=" + openbmc_password, "os_host=" + os_host,
830 "os_username=" + os_username, "os_password=" + os_password,
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600831 "quiet=${" + str(check_state_quiet) + "}"]
Michael Walsh70369fd2016-11-22 11:25:57 -0600832 grp.rdpissuing_keyword(cmd_buf)
Michael Walsh619aa332017-04-12 15:56:51 -0500833 try:
834 state = BuiltIn().wait_until_keyword_succeeds(wait_time, interval,
835 *cmd_buf)
836 except AssertionError as my_assertion_error:
837 gp.printn()
838 message = my_assertion_error.args[0]
839 BuiltIn().fail(message)
840
Michael Walsh70369fd2016-11-22 11:25:57 -0600841 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500842 gp.printn()
Michael Walsh70369fd2016-11-22 11:25:57 -0600843 if invert:
Michael Walsh3eb50022017-03-21 11:27:30 -0500844 gp.print_timen("The states no longer match:")
Michael Walsh70369fd2016-11-22 11:25:57 -0600845 else:
Michael Walsh3eb50022017-03-21 11:27:30 -0500846 gp.print_timen("The states match:")
847 gp.print_var(state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600848
849 return state
850
851###############################################################################
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600852
853
854###############################################################################
Michael Walsh619aa332017-04-12 15:56:51 -0500855def wait_for_comm_cycle(start_boot_seconds,
856 quiet=None):
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600857
858 r"""
859 Wait for communications to the BMC to stop working and then resume working.
860 This function is useful when you have initiated some kind of reboot.
861
862 Description of arguments:
863 start_boot_seconds The time that the boot test started. The format is the
864 epoch time in seconds, i.e. the number of seconds since
865 1970-01-01 00:00:00 UTC. This value should be obtained
866 from the BMC so that it is not dependent on any kind of
867 synchronization between this machine and the target BMC
868 This will allow this program to work correctly even in
869 a simulated environment. This value should be obtained
870 by the caller prior to initiating a reboot. It can be
871 obtained as follows:
872 state = st.get_state(req_states=['epoch_seconds'])
873 """
874
Michael Walsh619aa332017-04-12 15:56:51 -0500875 quiet = int(gp.get_var_value(quiet, 0))
876
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600877 # Validate parms.
878 error_message = gv.svalid_integer(start_boot_seconds,
879 var_name="start_boot_seconds")
880 if error_message != "":
881 BuiltIn().fail(gp.sprint_error(error_message))
882
883 match_state = anchor_state(DotDict([('packet_loss', '100')]))
884 # Wait for 100% packet loss trying to ping machine.
Michael Walsh619aa332017-04-12 15:56:51 -0500885 wait_state(match_state, wait_time="8 mins", interval="0 seconds")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600886
887 match_state['packet_loss'] = '^0$'
888 # Wait for 0% packet loss trying to ping machine.
Michael Walsh619aa332017-04-12 15:56:51 -0500889 wait_state(match_state, wait_time="8 mins", interval="0 seconds")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600890
891 # Get the uptime and epoch seconds for comparisons. We want to be sure
892 # that the uptime is less than the elapsed boot time. Further proof that
893 # a reboot has indeed occurred (vs random network instability giving a
894 # false positive.
Michael Walsh01975fa2017-08-20 20:51:36 -0500895 state = get_state(req_states=['uptime', 'epoch_seconds'], quiet=quiet)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600896
897 elapsed_boot_time = int(state['epoch_seconds']) - start_boot_seconds
Michael Walsh619aa332017-04-12 15:56:51 -0500898 gp.qprint_var(elapsed_boot_time)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600899 if int(float(state['uptime'])) < elapsed_boot_time:
900 uptime = state['uptime']
Michael Walsh619aa332017-04-12 15:56:51 -0500901 gp.qprint_var(uptime)
902 gp.qprint_timen("The uptime is less than the elapsed boot time," +
903 " as expected.")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600904 else:
905 error_message = "The uptime is greater than the elapsed boot time," +\
906 " which is unexpected:\n" +\
907 gp.sprint_var(start_boot_seconds) +\
908 gp.sprint_var(state)
909 BuiltIn().fail(gp.sprint_error(error_message))
910
Michael Walsh619aa332017-04-12 15:56:51 -0500911 gp.qprint_timen("Verifying that REST API interface is working.")
Michael Walshb95eb542017-03-31 09:39:20 -0500912 match_state = DotDict([('rest', '^1$')])
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600913 state = wait_state(match_state, wait_time="5 mins", interval="2 seconds")
914
915###############################################################################