blob: 6c5ec27f29a77e83893d92f39c6219515b089006 [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:
552 cmd_buf = ["BMC Execute Command", "cat /proc/uptime | cut -f 1 -d ' '",
553 'quiet=${1}']
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600554 if not quiet:
555 grp.rpissuing_keyword(cmd_buf)
Michael Walsh97df71c2017-03-27 14:33:24 -0500556 status, ret_values = \
557 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
558 if status == "PASS":
559 stdout, stderr, rc = ret_values
560 if rc == 0 and stderr == "":
561 uptime = stdout
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600562
Michael Walshe53e47a2017-06-30 17:03:24 -0500563 if 'epoch_seconds' in req_states:
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600564 date_cmd_buf = "date -u +%s"
565 if USE_BMC_EPOCH_TIME:
Michael Walshe53e47a2017-06-30 17:03:24 -0500566 cmd_buf = ["BMC Execute Command", date_cmd_buf, 'quiet=${1}']
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600567 if not quiet:
568 grp.rpissuing_keyword(cmd_buf)
Michael Walsh97df71c2017-03-27 14:33:24 -0500569 status, ret_values = \
570 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
571 if status == "PASS":
572 stdout, stderr, rc = ret_values
573 if rc == 0 and stderr == "":
574 epoch_seconds = stdout.rstrip("\n")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600575 else:
576 shell_rc, out_buf = gc.cmd_fnc_u(date_cmd_buf,
577 quiet=1,
578 print_output=0)
579 if shell_rc == 0:
580 epoch_seconds = out_buf.rstrip("\n")
581
Michael Walsh56749222017-09-29 15:26:07 -0500582 master_req_rest = ['rest', 'host', 'requested_host', 'operating_system',
583 'attempts_left', 'boot_progress', 'chassis',
584 'requested_chassis' 'bmc' 'requested_bmc']
585
Michael Walshb95eb542017-03-31 09:39:20 -0500586 req_rest = [sub_state for sub_state in req_states if sub_state in
587 master_req_rest]
588 need_rest = (len(req_rest) > 0)
Michael Walsh56749222017-09-29 15:26:07 -0500589 state = DotDict()
590 if need_rest:
591 cmd_buf = ["Read Properties", var.SYSTEM_STATE_URI + "enumerate",
592 "quiet=${" + str(quiet) + "}"]
Michael Walsh341c21e2017-01-17 16:25:20 -0600593 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":
Michael Walsh56749222017-09-29 15:26:07 -0500597 state['rest'] = '1'
Michael Walsh341c21e2017-01-17 16:25:20 -0600598 else:
Michael Walsh56749222017-09-29 15:26:07 -0500599 state['rest'] = '0'
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600600
Michael Walsh2b269de2017-10-09 11:18:11 -0500601 if int(state['rest']):
602 for url_path in ret_values:
603 for attr_name in ret_values[url_path]:
604 # Create a state key value based on the attr_name.
605 if type(ret_values[url_path][attr_name]) is unicode:
606 ret_values[url_path][attr_name] = \
607 re.sub(r'.*\.', "",
608 ret_values[url_path][attr_name])
609 # Do some key name manipulations.
610 new_attr_name = re.sub(r'^Current|(State|Transition)$',
611 "", attr_name)
612 new_attr_name = re.sub(r'BMC', r'Bmc', new_attr_name)
613 new_attr_name = re.sub(r'([A-Z][a-z])', r'_\1',
614 new_attr_name)
615 new_attr_name = new_attr_name.lower().lstrip("_")
616 new_attr_name = re.sub(r'power', r'chassis', new_attr_name)
617 if new_attr_name in req_states:
618 state[new_attr_name] = ret_values[url_path][attr_name]
Michael Walshb95eb542017-03-31 09:39:20 -0500619
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600620 for sub_state in req_states:
Michael Walsh56749222017-09-29 15:26:07 -0500621 if sub_state in state:
622 continue
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600623 if sub_state.startswith("os_"):
624 # We pass "os_" requests on to get_os_state.
625 continue
626 cmd_buf = "state['" + sub_state + "'] = str(" + sub_state + ")"
627 exec(cmd_buf)
628
629 if os_host == "":
630 # The caller has not specified an os_host so as far as we're concerned,
631 # it doesn't exist.
632 return state
633
634 os_req_states = [sub_state for sub_state in req_states
635 if sub_state.startswith('os_')]
636
637 if len(os_req_states) > 0:
638 # The caller has specified an os_host and they have requested
639 # information on os substates.
640
641 # Based on the information gathered on bmc, we'll try to make a
642 # determination of whether the os is even up. We'll pass the result
643 # of that assessment to get_os_state to enhance performance.
644 os_up_match = DotDict()
645 for sub_state in master_os_up_match:
646 if sub_state in req_states:
647 os_up_match[sub_state] = master_os_up_match[sub_state]
Michael Walsh70369fd2016-11-22 11:25:57 -0600648 os_up = compare_states(state, os_up_match)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600649 os_state = get_os_state(os_host=os_host,
650 os_username=os_username,
651 os_password=os_password,
652 req_states=os_req_states,
653 os_up=os_up,
654 quiet=quiet)
655 # Append os_state dictionary to ours.
656 state.update(os_state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600657
658 return state
659
660###############################################################################
661
662
663###############################################################################
664def check_state(match_state,
665 invert=0,
666 print_string="",
667 openbmc_host="",
668 openbmc_username="",
669 openbmc_password="",
670 os_host="",
671 os_username="",
672 os_password="",
673 quiet=None):
674
675 r"""
676 Check that the Open BMC machine's composite state matches the specified
677 state. On success, this keyword returns the machine's composite state as a
678 dictionary.
679
680 Description of arguments:
681 match_state A dictionary whose key/value pairs are "state field"/
682 "state value". The state value is interpreted as a
683 regular expression. Example call from robot:
Michael Walsh341c21e2017-01-17 16:25:20 -0600684 ${match_state}= Create Dictionary chassis=^On$
685 ... bmc=^Ready$
Michael Walsh01975fa2017-08-20 20:51:36 -0500686 ... boot_progress=^OSStart$
Michael Walsh70369fd2016-11-22 11:25:57 -0600687 ${state}= Check State &{match_state}
688 invert If this flag is set, this function will succeed if the
689 states do NOT match.
690 print_string This function will print this string to the console prior
691 to getting the state.
692 openbmc_host The DNS name or IP address of the BMC.
693 This defaults to global ${OPENBMC_HOST}.
694 openbmc_username The username to be used to login to the BMC.
695 This defaults to global ${OPENBMC_USERNAME}.
696 openbmc_password The password to be used to login to the BMC.
697 This defaults to global ${OPENBMC_PASSWORD}.
698 os_host The DNS name or IP address of the operating system.
699 This defaults to global ${OS_HOST}.
700 os_username The username to be used to login to the OS.
701 This defaults to global ${OS_USERNAME}.
702 os_password The password to be used to login to the OS.
703 This defaults to global ${OS_PASSWORD}.
704 quiet Indicates whether status details should be written to the
705 console. Defaults to either global value of ${QUIET} or
706 to 1.
707 """
708
Michael Walsh619aa332017-04-12 15:56:51 -0500709 quiet = int(gp.get_var_value(quiet, 0))
Michael Walsh70369fd2016-11-22 11:25:57 -0600710
711 grp.rprint(print_string)
712
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600713 req_states = match_state.keys()
Michael Walsh70369fd2016-11-22 11:25:57 -0600714 # Initialize state.
715 state = get_state(openbmc_host=openbmc_host,
716 openbmc_username=openbmc_username,
717 openbmc_password=openbmc_password,
718 os_host=os_host,
719 os_username=os_username,
720 os_password=os_password,
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600721 req_states=req_states,
Michael Walsh70369fd2016-11-22 11:25:57 -0600722 quiet=quiet)
723 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500724 gp.print_var(state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600725
726 match = compare_states(state, match_state)
727
728 if invert and match:
729 fail_msg = "The current state of the machine matches the match" +\
730 " state:\n" + gp.sprint_varx("state", state)
731 BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
732 elif not invert and not match:
733 fail_msg = "The current state of the machine does NOT match the" +\
734 " match state:\n" +\
735 gp.sprint_varx("state", state)
736 BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
737
738 return state
739
740###############################################################################
741
742
743###############################################################################
Michael Walshf893ba02017-01-10 10:28:05 -0600744def wait_state(match_state=(),
Michael Walsh70369fd2016-11-22 11:25:57 -0600745 wait_time="1 min",
746 interval="1 second",
747 invert=0,
748 openbmc_host="",
749 openbmc_username="",
750 openbmc_password="",
751 os_host="",
752 os_username="",
753 os_password="",
754 quiet=None):
755
756 r"""
757 Wait for the Open BMC machine's composite state to match the specified
758 state. On success, this keyword returns the machine's composite state as
759 a dictionary.
760
761 Description of arguments:
762 match_state A dictionary whose key/value pairs are "state field"/
763 "state value". See check_state (above) for details.
Michael Walsh619aa332017-04-12 15:56:51 -0500764 This value may also be any string accepted by
765 return_state_constant (e.g. "standby_match_state").
766 In such a case this function will call
767 return_state_constant to convert it to a proper
768 dictionary as described above.
Michael Walsh70369fd2016-11-22 11:25:57 -0600769 wait_time The total amount of time to wait for the desired state.
770 This value may be expressed in Robot Framework's time
771 format (e.g. 1 minute, 2 min 3 s, 4.5).
772 interval The amount of time between state checks.
773 This value may be expressed in Robot Framework's time
774 format (e.g. 1 minute, 2 min 3 s, 4.5).
775 invert If this flag is set, this function will for the state of
776 the machine to cease to match the match state.
777 openbmc_host The DNS name or IP address of the BMC.
778 This defaults to global ${OPENBMC_HOST}.
779 openbmc_username The username to be used to login to the BMC.
780 This defaults to global ${OPENBMC_USERNAME}.
781 openbmc_password The password to be used to login to the BMC.
782 This defaults to global ${OPENBMC_PASSWORD}.
783 os_host The DNS name or IP address of the operating system.
784 This defaults to global ${OS_HOST}.
785 os_username The username to be used to login to the OS.
786 This defaults to global ${OS_USERNAME}.
787 os_password The password to be used to login to the OS.
788 This defaults to global ${OS_PASSWORD}.
789 quiet Indicates whether status details should be written to the
790 console. Defaults to either global value of ${QUIET} or
791 to 1.
792 """
793
Michael Walsh619aa332017-04-12 15:56:51 -0500794 quiet = int(gp.get_var_value(quiet, 0))
795
796 if type(match_state) in (str, unicode):
797 match_state = return_state_constant(match_state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600798
799 if not quiet:
800 if invert:
801 alt_text = "cease to "
802 else:
803 alt_text = ""
Michael Walsh3eb50022017-03-21 11:27:30 -0500804 gp.print_timen("Checking every " + str(interval) + " for up to " +
805 str(wait_time) + " for the state of the machine to " +
806 alt_text + "match the state shown below.")
807 gp.print_var(match_state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600808
Michael Walshf893ba02017-01-10 10:28:05 -0600809 if quiet:
Michael Walsh341c21e2017-01-17 16:25:20 -0600810 print_string = ""
Michael Walshf893ba02017-01-10 10:28:05 -0600811 else:
Michael Walsh341c21e2017-01-17 16:25:20 -0600812 print_string = "#"
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600813
814 debug = int(BuiltIn().get_variable_value("${debug}", "0"))
815 if debug:
816 # In debug we print state so no need to print the "#".
817 print_string = ""
818 check_state_quiet = 1 - debug
Michael Walsh70369fd2016-11-22 11:25:57 -0600819 cmd_buf = ["Check State", match_state, "invert=${" + str(invert) + "}",
Michael Walshf893ba02017-01-10 10:28:05 -0600820 "print_string=" + print_string, "openbmc_host=" + openbmc_host,
Michael Walsh70369fd2016-11-22 11:25:57 -0600821 "openbmc_username=" + openbmc_username,
822 "openbmc_password=" + openbmc_password, "os_host=" + os_host,
823 "os_username=" + os_username, "os_password=" + os_password,
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600824 "quiet=${" + str(check_state_quiet) + "}"]
Michael Walsh70369fd2016-11-22 11:25:57 -0600825 grp.rdpissuing_keyword(cmd_buf)
Michael Walsh619aa332017-04-12 15:56:51 -0500826 try:
827 state = BuiltIn().wait_until_keyword_succeeds(wait_time, interval,
828 *cmd_buf)
829 except AssertionError as my_assertion_error:
830 gp.printn()
831 message = my_assertion_error.args[0]
832 BuiltIn().fail(message)
833
Michael Walsh70369fd2016-11-22 11:25:57 -0600834 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500835 gp.printn()
Michael Walsh70369fd2016-11-22 11:25:57 -0600836 if invert:
Michael Walsh3eb50022017-03-21 11:27:30 -0500837 gp.print_timen("The states no longer match:")
Michael Walsh70369fd2016-11-22 11:25:57 -0600838 else:
Michael Walsh3eb50022017-03-21 11:27:30 -0500839 gp.print_timen("The states match:")
840 gp.print_var(state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600841
842 return state
843
844###############################################################################
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600845
846
847###############################################################################
Michael Walsh619aa332017-04-12 15:56:51 -0500848def wait_for_comm_cycle(start_boot_seconds,
849 quiet=None):
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600850
851 r"""
852 Wait for communications to the BMC to stop working and then resume working.
853 This function is useful when you have initiated some kind of reboot.
854
855 Description of arguments:
856 start_boot_seconds The time that the boot test started. The format is the
857 epoch time in seconds, i.e. the number of seconds since
858 1970-01-01 00:00:00 UTC. This value should be obtained
859 from the BMC so that it is not dependent on any kind of
860 synchronization between this machine and the target BMC
861 This will allow this program to work correctly even in
862 a simulated environment. This value should be obtained
863 by the caller prior to initiating a reboot. It can be
864 obtained as follows:
865 state = st.get_state(req_states=['epoch_seconds'])
866 """
867
Michael Walsh619aa332017-04-12 15:56:51 -0500868 quiet = int(gp.get_var_value(quiet, 0))
869
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600870 # Validate parms.
871 error_message = gv.svalid_integer(start_boot_seconds,
872 var_name="start_boot_seconds")
873 if error_message != "":
874 BuiltIn().fail(gp.sprint_error(error_message))
875
876 match_state = anchor_state(DotDict([('packet_loss', '100')]))
877 # Wait for 100% packet loss trying to ping machine.
Michael Walsh619aa332017-04-12 15:56:51 -0500878 wait_state(match_state, wait_time="8 mins", interval="0 seconds")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600879
880 match_state['packet_loss'] = '^0$'
881 # Wait for 0% packet loss trying to ping machine.
Michael Walsh619aa332017-04-12 15:56:51 -0500882 wait_state(match_state, wait_time="8 mins", interval="0 seconds")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600883
884 # Get the uptime and epoch seconds for comparisons. We want to be sure
885 # that the uptime is less than the elapsed boot time. Further proof that
886 # a reboot has indeed occurred (vs random network instability giving a
887 # false positive.
Michael Walsh01975fa2017-08-20 20:51:36 -0500888 state = get_state(req_states=['uptime', 'epoch_seconds'], quiet=quiet)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600889
890 elapsed_boot_time = int(state['epoch_seconds']) - start_boot_seconds
Michael Walsh619aa332017-04-12 15:56:51 -0500891 gp.qprint_var(elapsed_boot_time)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600892 if int(float(state['uptime'])) < elapsed_boot_time:
893 uptime = state['uptime']
Michael Walsh619aa332017-04-12 15:56:51 -0500894 gp.qprint_var(uptime)
895 gp.qprint_timen("The uptime is less than the elapsed boot time," +
896 " as expected.")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600897 else:
898 error_message = "The uptime is greater than the elapsed boot time," +\
899 " which is unexpected:\n" +\
900 gp.sprint_var(start_boot_seconds) +\
901 gp.sprint_var(state)
902 BuiltIn().fail(gp.sprint_error(error_message))
903
Michael Walsh619aa332017-04-12 15:56:51 -0500904 gp.qprint_timen("Verifying that REST API interface is working.")
Michael Walshb95eb542017-03-31 09:39:20 -0500905 match_state = DotDict([('rest', '^1$')])
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600906 state = wait_state(match_state, wait_time="5 mins", interval="2 seconds")
907
908###############################################################################