blob: e9b9fab9fbbb497cc9e85948b94b6d674cfc5e8f [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 Walsh8fae6ea2017-02-20 16:14:44 -0600520
Michael Walsh70369fd2016-11-22 11:25:57 -0600521 # Get the component states.
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600522 if 'ping' in req_states:
523 # See if the OS pings.
524 cmd_buf = "ping -c 1 -w 2 " + openbmc_host
525 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500526 gp.pissuing(cmd_buf)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600527 rc, out_buf = commands.getstatusoutput(cmd_buf)
528 if rc == 0:
529 ping = 1
530
531 if 'packet_loss' in req_states:
532 # See if the OS pings.
533 cmd_buf = "ping -c 5 -w 5 " + openbmc_host +\
534 " | egrep 'packet loss' | sed -re 's/.* ([0-9]+)%.*/\\1/g'"
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 packet_loss = out_buf.rstrip("\n")
540
Michael Walshe53e47a2017-06-30 17:03:24 -0500541 if 'uptime' in req_states:
542 cmd_buf = ["BMC Execute Command", "cat /proc/uptime | cut -f 1 -d ' '",
543 'quiet=${1}']
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600544 if not quiet:
545 grp.rpissuing_keyword(cmd_buf)
Michael Walsh97df71c2017-03-27 14:33:24 -0500546 status, ret_values = \
547 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
548 if status == "PASS":
549 stdout, stderr, rc = ret_values
550 if rc == 0 and stderr == "":
551 uptime = stdout
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600552
Michael Walshe53e47a2017-06-30 17:03:24 -0500553 if 'epoch_seconds' in req_states:
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600554 date_cmd_buf = "date -u +%s"
555 if USE_BMC_EPOCH_TIME:
Michael Walshe53e47a2017-06-30 17:03:24 -0500556 cmd_buf = ["BMC Execute Command", date_cmd_buf, 'quiet=${1}']
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600557 if not quiet:
558 grp.rpissuing_keyword(cmd_buf)
Michael Walsh97df71c2017-03-27 14:33:24 -0500559 status, ret_values = \
560 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
561 if status == "PASS":
562 stdout, stderr, rc = ret_values
563 if rc == 0 and stderr == "":
564 epoch_seconds = stdout.rstrip("\n")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600565 else:
566 shell_rc, out_buf = gc.cmd_fnc_u(date_cmd_buf,
567 quiet=1,
568 print_output=0)
569 if shell_rc == 0:
570 epoch_seconds = out_buf.rstrip("\n")
571
Michael Walsh56749222017-09-29 15:26:07 -0500572 master_req_rest = ['rest', 'host', 'requested_host', 'operating_system',
573 'attempts_left', 'boot_progress', 'chassis',
574 'requested_chassis' 'bmc' 'requested_bmc']
575
Michael Walshb95eb542017-03-31 09:39:20 -0500576 req_rest = [sub_state for sub_state in req_states if sub_state in
577 master_req_rest]
578 need_rest = (len(req_rest) > 0)
Michael Walsh56749222017-09-29 15:26:07 -0500579 state = DotDict()
580 if need_rest:
581 cmd_buf = ["Read Properties", var.SYSTEM_STATE_URI + "enumerate",
582 "quiet=${" + str(quiet) + "}"]
Michael Walsh341c21e2017-01-17 16:25:20 -0600583 grp.rdpissuing_keyword(cmd_buf)
Michael Walsh97df71c2017-03-27 14:33:24 -0500584 status, ret_values = \
585 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
586 if status == "PASS":
Michael Walsh56749222017-09-29 15:26:07 -0500587 state['rest'] = '1'
Michael Walsh341c21e2017-01-17 16:25:20 -0600588 else:
Michael Walsh56749222017-09-29 15:26:07 -0500589 state['rest'] = '0'
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600590
Michael Walsh56749222017-09-29 15:26:07 -0500591 for url_path in ret_values:
592 for attr_name in ret_values[url_path]:
593 # Create a state key value based on the attr_name.
594 if type(ret_values[url_path][attr_name]) is unicode:
595 ret_values[url_path][attr_name] = \
596 re.sub(r'.*\.', "", ret_values[url_path][attr_name])
597 # Do some key name manipulations.
598 new_attr_name = re.sub(r'^Current|(State|Transition)$',
599 "", attr_name)
600 new_attr_name = re.sub(r'BMC', r'Bmc', new_attr_name)
601 new_attr_name = re.sub(r'([A-Z][a-z])', r'_\1', new_attr_name)
602 new_attr_name = new_attr_name.lower().lstrip("_")
603 new_attr_name = re.sub(r'power', r'chassis', new_attr_name)
604 if new_attr_name in req_states:
605 state[new_attr_name] = ret_values[url_path][attr_name]
Michael Walshb95eb542017-03-31 09:39:20 -0500606
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600607 for sub_state in req_states:
Michael Walsh56749222017-09-29 15:26:07 -0500608 if sub_state in state:
609 continue
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600610 if sub_state.startswith("os_"):
611 # We pass "os_" requests on to get_os_state.
612 continue
613 cmd_buf = "state['" + sub_state + "'] = str(" + sub_state + ")"
614 exec(cmd_buf)
615
616 if os_host == "":
617 # The caller has not specified an os_host so as far as we're concerned,
618 # it doesn't exist.
619 return state
620
621 os_req_states = [sub_state for sub_state in req_states
622 if sub_state.startswith('os_')]
623
624 if len(os_req_states) > 0:
625 # The caller has specified an os_host and they have requested
626 # information on os substates.
627
628 # Based on the information gathered on bmc, we'll try to make a
629 # determination of whether the os is even up. We'll pass the result
630 # of that assessment to get_os_state to enhance performance.
631 os_up_match = DotDict()
632 for sub_state in master_os_up_match:
633 if sub_state in req_states:
634 os_up_match[sub_state] = master_os_up_match[sub_state]
Michael Walsh70369fd2016-11-22 11:25:57 -0600635 os_up = compare_states(state, os_up_match)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600636 os_state = get_os_state(os_host=os_host,
637 os_username=os_username,
638 os_password=os_password,
639 req_states=os_req_states,
640 os_up=os_up,
641 quiet=quiet)
642 # Append os_state dictionary to ours.
643 state.update(os_state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600644
645 return state
646
647###############################################################################
648
649
650###############################################################################
651def check_state(match_state,
652 invert=0,
653 print_string="",
654 openbmc_host="",
655 openbmc_username="",
656 openbmc_password="",
657 os_host="",
658 os_username="",
659 os_password="",
660 quiet=None):
661
662 r"""
663 Check that the Open BMC machine's composite state matches the specified
664 state. On success, this keyword returns the machine's composite state as a
665 dictionary.
666
667 Description of arguments:
668 match_state A dictionary whose key/value pairs are "state field"/
669 "state value". The state value is interpreted as a
670 regular expression. Example call from robot:
Michael Walsh341c21e2017-01-17 16:25:20 -0600671 ${match_state}= Create Dictionary chassis=^On$
672 ... bmc=^Ready$
Michael Walsh01975fa2017-08-20 20:51:36 -0500673 ... boot_progress=^OSStart$
Michael Walsh70369fd2016-11-22 11:25:57 -0600674 ${state}= Check State &{match_state}
675 invert If this flag is set, this function will succeed if the
676 states do NOT match.
677 print_string This function will print this string to the console prior
678 to getting the state.
679 openbmc_host The DNS name or IP address of the BMC.
680 This defaults to global ${OPENBMC_HOST}.
681 openbmc_username The username to be used to login to the BMC.
682 This defaults to global ${OPENBMC_USERNAME}.
683 openbmc_password The password to be used to login to the BMC.
684 This defaults to global ${OPENBMC_PASSWORD}.
685 os_host The DNS name or IP address of the operating system.
686 This defaults to global ${OS_HOST}.
687 os_username The username to be used to login to the OS.
688 This defaults to global ${OS_USERNAME}.
689 os_password The password to be used to login to the OS.
690 This defaults to global ${OS_PASSWORD}.
691 quiet Indicates whether status details should be written to the
692 console. Defaults to either global value of ${QUIET} or
693 to 1.
694 """
695
Michael Walsh619aa332017-04-12 15:56:51 -0500696 quiet = int(gp.get_var_value(quiet, 0))
Michael Walsh70369fd2016-11-22 11:25:57 -0600697
698 grp.rprint(print_string)
699
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600700 req_states = match_state.keys()
Michael Walsh70369fd2016-11-22 11:25:57 -0600701 # Initialize state.
702 state = get_state(openbmc_host=openbmc_host,
703 openbmc_username=openbmc_username,
704 openbmc_password=openbmc_password,
705 os_host=os_host,
706 os_username=os_username,
707 os_password=os_password,
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600708 req_states=req_states,
Michael Walsh70369fd2016-11-22 11:25:57 -0600709 quiet=quiet)
710 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500711 gp.print_var(state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600712
713 match = compare_states(state, match_state)
714
715 if invert and match:
716 fail_msg = "The current state of the machine matches the match" +\
717 " state:\n" + gp.sprint_varx("state", state)
718 BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
719 elif not invert and not match:
720 fail_msg = "The current state of the machine does NOT match the" +\
721 " match state:\n" +\
722 gp.sprint_varx("state", state)
723 BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
724
725 return state
726
727###############################################################################
728
729
730###############################################################################
Michael Walshf893ba02017-01-10 10:28:05 -0600731def wait_state(match_state=(),
Michael Walsh70369fd2016-11-22 11:25:57 -0600732 wait_time="1 min",
733 interval="1 second",
734 invert=0,
735 openbmc_host="",
736 openbmc_username="",
737 openbmc_password="",
738 os_host="",
739 os_username="",
740 os_password="",
741 quiet=None):
742
743 r"""
744 Wait for the Open BMC machine's composite state to match the specified
745 state. On success, this keyword returns the machine's composite state as
746 a dictionary.
747
748 Description of arguments:
749 match_state A dictionary whose key/value pairs are "state field"/
750 "state value". See check_state (above) for details.
Michael Walsh619aa332017-04-12 15:56:51 -0500751 This value may also be any string accepted by
752 return_state_constant (e.g. "standby_match_state").
753 In such a case this function will call
754 return_state_constant to convert it to a proper
755 dictionary as described above.
Michael Walsh70369fd2016-11-22 11:25:57 -0600756 wait_time The total amount of time to wait for the desired state.
757 This value may be expressed in Robot Framework's time
758 format (e.g. 1 minute, 2 min 3 s, 4.5).
759 interval The amount of time between state checks.
760 This value may be expressed in Robot Framework's time
761 format (e.g. 1 minute, 2 min 3 s, 4.5).
762 invert If this flag is set, this function will for the state of
763 the machine to cease to match the match state.
764 openbmc_host The DNS name or IP address of the BMC.
765 This defaults to global ${OPENBMC_HOST}.
766 openbmc_username The username to be used to login to the BMC.
767 This defaults to global ${OPENBMC_USERNAME}.
768 openbmc_password The password to be used to login to the BMC.
769 This defaults to global ${OPENBMC_PASSWORD}.
770 os_host The DNS name or IP address of the operating system.
771 This defaults to global ${OS_HOST}.
772 os_username The username to be used to login to the OS.
773 This defaults to global ${OS_USERNAME}.
774 os_password The password to be used to login to the OS.
775 This defaults to global ${OS_PASSWORD}.
776 quiet Indicates whether status details should be written to the
777 console. Defaults to either global value of ${QUIET} or
778 to 1.
779 """
780
Michael Walsh619aa332017-04-12 15:56:51 -0500781 quiet = int(gp.get_var_value(quiet, 0))
782
783 if type(match_state) in (str, unicode):
784 match_state = return_state_constant(match_state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600785
786 if not quiet:
787 if invert:
788 alt_text = "cease to "
789 else:
790 alt_text = ""
Michael Walsh3eb50022017-03-21 11:27:30 -0500791 gp.print_timen("Checking every " + str(interval) + " for up to " +
792 str(wait_time) + " for the state of the machine to " +
793 alt_text + "match the state shown below.")
794 gp.print_var(match_state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600795
Michael Walshf893ba02017-01-10 10:28:05 -0600796 if quiet:
Michael Walsh341c21e2017-01-17 16:25:20 -0600797 print_string = ""
Michael Walshf893ba02017-01-10 10:28:05 -0600798 else:
Michael Walsh341c21e2017-01-17 16:25:20 -0600799 print_string = "#"
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600800
801 debug = int(BuiltIn().get_variable_value("${debug}", "0"))
802 if debug:
803 # In debug we print state so no need to print the "#".
804 print_string = ""
805 check_state_quiet = 1 - debug
Michael Walsh70369fd2016-11-22 11:25:57 -0600806 cmd_buf = ["Check State", match_state, "invert=${" + str(invert) + "}",
Michael Walshf893ba02017-01-10 10:28:05 -0600807 "print_string=" + print_string, "openbmc_host=" + openbmc_host,
Michael Walsh70369fd2016-11-22 11:25:57 -0600808 "openbmc_username=" + openbmc_username,
809 "openbmc_password=" + openbmc_password, "os_host=" + os_host,
810 "os_username=" + os_username, "os_password=" + os_password,
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600811 "quiet=${" + str(check_state_quiet) + "}"]
Michael Walsh70369fd2016-11-22 11:25:57 -0600812 grp.rdpissuing_keyword(cmd_buf)
Michael Walsh619aa332017-04-12 15:56:51 -0500813 try:
814 state = BuiltIn().wait_until_keyword_succeeds(wait_time, interval,
815 *cmd_buf)
816 except AssertionError as my_assertion_error:
817 gp.printn()
818 message = my_assertion_error.args[0]
819 BuiltIn().fail(message)
820
Michael Walsh70369fd2016-11-22 11:25:57 -0600821 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500822 gp.printn()
Michael Walsh70369fd2016-11-22 11:25:57 -0600823 if invert:
Michael Walsh3eb50022017-03-21 11:27:30 -0500824 gp.print_timen("The states no longer match:")
Michael Walsh70369fd2016-11-22 11:25:57 -0600825 else:
Michael Walsh3eb50022017-03-21 11:27:30 -0500826 gp.print_timen("The states match:")
827 gp.print_var(state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600828
829 return state
830
831###############################################################################
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600832
833
834###############################################################################
Michael Walsh619aa332017-04-12 15:56:51 -0500835def wait_for_comm_cycle(start_boot_seconds,
836 quiet=None):
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600837
838 r"""
839 Wait for communications to the BMC to stop working and then resume working.
840 This function is useful when you have initiated some kind of reboot.
841
842 Description of arguments:
843 start_boot_seconds The time that the boot test started. The format is the
844 epoch time in seconds, i.e. the number of seconds since
845 1970-01-01 00:00:00 UTC. This value should be obtained
846 from the BMC so that it is not dependent on any kind of
847 synchronization between this machine and the target BMC
848 This will allow this program to work correctly even in
849 a simulated environment. This value should be obtained
850 by the caller prior to initiating a reboot. It can be
851 obtained as follows:
852 state = st.get_state(req_states=['epoch_seconds'])
853 """
854
Michael Walsh619aa332017-04-12 15:56:51 -0500855 quiet = int(gp.get_var_value(quiet, 0))
856
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600857 # Validate parms.
858 error_message = gv.svalid_integer(start_boot_seconds,
859 var_name="start_boot_seconds")
860 if error_message != "":
861 BuiltIn().fail(gp.sprint_error(error_message))
862
863 match_state = anchor_state(DotDict([('packet_loss', '100')]))
864 # Wait for 100% packet loss trying to ping machine.
Michael Walsh619aa332017-04-12 15:56:51 -0500865 wait_state(match_state, wait_time="8 mins", interval="0 seconds")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600866
867 match_state['packet_loss'] = '^0$'
868 # Wait for 0% packet loss trying to ping machine.
Michael Walsh619aa332017-04-12 15:56:51 -0500869 wait_state(match_state, wait_time="8 mins", interval="0 seconds")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600870
871 # Get the uptime and epoch seconds for comparisons. We want to be sure
872 # that the uptime is less than the elapsed boot time. Further proof that
873 # a reboot has indeed occurred (vs random network instability giving a
874 # false positive.
Michael Walsh01975fa2017-08-20 20:51:36 -0500875 state = get_state(req_states=['uptime', 'epoch_seconds'], quiet=quiet)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600876
877 elapsed_boot_time = int(state['epoch_seconds']) - start_boot_seconds
Michael Walsh619aa332017-04-12 15:56:51 -0500878 gp.qprint_var(elapsed_boot_time)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600879 if int(float(state['uptime'])) < elapsed_boot_time:
880 uptime = state['uptime']
Michael Walsh619aa332017-04-12 15:56:51 -0500881 gp.qprint_var(uptime)
882 gp.qprint_timen("The uptime is less than the elapsed boot time," +
883 " as expected.")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600884 else:
885 error_message = "The uptime is greater than the elapsed boot time," +\
886 " which is unexpected:\n" +\
887 gp.sprint_var(start_boot_seconds) +\
888 gp.sprint_var(state)
889 BuiltIn().fail(gp.sprint_error(error_message))
890
Michael Walsh619aa332017-04-12 15:56:51 -0500891 gp.qprint_timen("Verifying that REST API interface is working.")
Michael Walshb95eb542017-03-31 09:39:20 -0500892 match_state = DotDict([('rest', '^1$')])
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600893 state = wait_state(match_state, wait_time="5 mins", interval="2 seconds")
894
895###############################################################################