blob: e321aa7f1773e163494e29de48c3f9aac8189e75 [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/")
Michael Walsh56749222017-09-29 15:26:07 -050053
Michael Walsh940d6912017-10-27 12:32:33 -050054# Previously, I had this coded:
55# import variables as var
56# However, we ran into a problem where a robot program did this...
57# Variables ../../lib/ras/variables.py
58# Prior to doing this...
59# Library ../lib/state.py
60
61# This caused the wrong variables.py file to be selected. Attempts to fix this
62# have failed so far. For the moment, we will hard-code the value we need from
63# the file.
64
65SYSTEM_STATE_URI = "/xyz/openbmc_project/state/"
Michael Walsh56749222017-09-29 15:26:07 -050066
Michael Walsh8fae6ea2017-02-20 16:14:44 -060067# The BMC code has recently been changed as far as what states are defined and
68# what the state values can be. This module now has a means of processing both
69# the old style state (i.e. OBMC_STATES_VERSION = 0) and the new style (i.e.
Michael Walsh16cbb7f2017-02-02 15:54:16 -060070# OBMC_STATES_VERSION = 1).
Michael Walsh341c21e2017-01-17 16:25:20 -060071# The caller can set environment variable OBMC_STATES_VERSION to dictate
72# whether we're processing old or new style states. If OBMC_STATES_VERSION is
Michael Walsh8fae6ea2017-02-20 16:14:44 -060073# not set it will default to 1.
Michael Walsh341c21e2017-01-17 16:25:20 -060074
Michael Walsh619aa332017-04-12 15:56:51 -050075# As of the present moment, OBMC_STATES_VERSION of 0 is for cold that is so old
76# that it is no longer worthwhile to maintain. The OBMC_STATES_VERSION 0 code
77# is being removed but the OBMC_STATES_VERSION value will stay for now in the
78# event that it is needed in the future.
79
Michael Walsh8fae6ea2017-02-20 16:14:44 -060080OBMC_STATES_VERSION = int(os.environ.get('OBMC_STATES_VERSION', 1))
Michael Walsh341c21e2017-01-17 16:25:20 -060081
Michael Walsh619aa332017-04-12 15:56:51 -050082# When a user calls get_state w/o specifying req_states, default_req_states
83# is used as its value.
84default_req_states = ['rest',
85 'chassis',
86 'bmc',
87 'boot_progress',
Michael Walsh56749222017-09-29 15:26:07 -050088 'operating_system',
Michael Walsh619aa332017-04-12 15:56:51 -050089 'host',
90 'os_ping',
91 'os_login',
92 'os_run_cmd']
Michael Walsh8fae6ea2017-02-20 16:14:44 -060093
Michael Walsh619aa332017-04-12 15:56:51 -050094# valid_req_states is a list of sub states supported by the get_state function.
95# valid_req_states, default_req_states and master_os_up_match are used by the
96# get_state function.
97valid_req_states = ['ping',
98 'packet_loss',
99 'uptime',
100 'epoch_seconds',
101 'rest',
102 'chassis',
Michael Walsh56749222017-09-29 15:26:07 -0500103 'requested_chassis',
Michael Walsh619aa332017-04-12 15:56:51 -0500104 'bmc',
Michael Walsh56749222017-09-29 15:26:07 -0500105 'requested_bmc',
Michael Walsh619aa332017-04-12 15:56:51 -0500106 'boot_progress',
Michael Walsh56749222017-09-29 15:26:07 -0500107 'operating_system',
Michael Walsh619aa332017-04-12 15:56:51 -0500108 'host',
Michael Walsh56749222017-09-29 15:26:07 -0500109 'requested_host',
110 'attempts_left',
Michael Walsh619aa332017-04-12 15:56:51 -0500111 'os_ping',
112 'os_login',
113 'os_run_cmd']
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600114
115# valid_os_req_states and default_os_req_states are used by the os_get_state
116# function.
117# valid_os_req_states is a list of state information supported by the
118# get_os_state function.
119valid_os_req_states = ['os_ping',
120 'os_login',
121 'os_run_cmd']
122# When a user calls get_os_state w/o specifying req_states,
123# default_os_req_states is used as its value.
124default_os_req_states = ['os_ping',
125 'os_login',
126 'os_run_cmd']
127
128# Presently, some BMCs appear to not keep time very well. This environment
129# variable directs the get_state function to use either the BMC's epoch time
130# or the local epoch time.
131USE_BMC_EPOCH_TIME = int(os.environ.get('USE_BMC_EPOCH_TIME', 0))
Michael Walsh341c21e2017-01-17 16:25:20 -0600132
Michael Walsh619aa332017-04-12 15:56:51 -0500133# Useful state constant definition(s).
Michael Walsh619aa332017-04-12 15:56:51 -0500134# default_state is an initial value which may be of use to callers.
135default_state = DotDict([('rest', '1'),
136 ('chassis', 'On'),
137 ('bmc', 'Ready'),
Michael Walsh01975fa2017-08-20 20:51:36 -0500138 ('boot_progress', 'OSStart'),
Michael Walsh56749222017-09-29 15:26:07 -0500139 ('operating_system', 'BootComplete'),
Michael Walsh619aa332017-04-12 15:56:51 -0500140 ('host', 'Running'),
141 ('os_ping', '1'),
142 ('os_login', '1'),
143 ('os_run_cmd', '1')])
144
Michael Walsh7dc885b2018-03-14 17:51:59 -0500145# A match state for checking that the system is at "standby".
146standby_match_state = DotDict([('rest', '^1$'),
147 ('chassis', '^Off$'),
148 ('bmc', '^Ready$'),
149 ('boot_progress', '^$'),
150 ('operating_system', '^$'),
151 ('host', '^$')])
152
153# A match state for checking that the system is at "os running".
154os_running_match_state = DotDict([('chassis', '^On$'),
155 ('bmc', '^Ready$'),
156 ('boot_progress',
157 'FW Progress, Starting OS|OSStart'),
158 ('operating_system', 'BootComplete'),
159 ('host', '^Running$'),
160 ('os_ping', '^1$'),
161 ('os_login', '^1$'),
162 ('os_run_cmd', '^1$')])
163
Michael Walsh619aa332017-04-12 15:56:51 -0500164# A master dictionary to determine whether the os may be up.
165master_os_up_match = DotDict([('chassis', '^On$'),
166 ('bmc', '^Ready$'),
167 ('boot_progress',
Michael Walsh01975fa2017-08-20 20:51:36 -0500168 'FW Progress, Starting OS|OSStart'),
Michael Walsh56749222017-09-29 15:26:07 -0500169 ('operating_system', 'BootComplete'),
Michael Walsh619aa332017-04-12 15:56:51 -0500170 ('host', '^Running$')])
171
Michael Walsh45ca6e42017-09-14 17:29:12 -0500172invalid_state_match = DotDict([('rest', '^$'),
173 ('chassis', '^$'),
174 ('bmc', '^$'),
175 ('boot_progress', '^$'),
Michael Walsh56749222017-09-29 15:26:07 -0500176 ('operating_system', '^$'),
Michael Walsh45ca6e42017-09-14 17:29:12 -0500177 ('host', '^$')])
178
Michael Walsh341c21e2017-01-17 16:25:20 -0600179
Michael Walsh619aa332017-04-12 15:56:51 -0500180def return_state_constant(state_name='default'):
181
182 r"""
Michael Walsh7dc885b2018-03-14 17:51:59 -0500183 Return the named state dictionary constant.
Michael Walsh619aa332017-04-12 15:56:51 -0500184 """
185
Michael Walsh7dc885b2018-03-14 17:51:59 -0500186 cmd_buf = "state = " + state_name
187 exec(cmd_buf)
188 return state
Michael Walsh619aa332017-04-12 15:56:51 -0500189
Michael Walsh619aa332017-04-12 15:56:51 -0500190
Michael Walsh70369fd2016-11-22 11:25:57 -0600191def anchor_state(state):
192
193 r"""
194 Add regular expression anchors ("^" and "$") to the beginning and end of
195 each item in the state dictionary passed in. Return the resulting
196 dictionary.
197
198 Description of Arguments:
199 state A dictionary such as the one returned by the get_state()
200 function.
201 """
202
Michael Walsh2ce067a2017-02-27 14:24:07 -0600203 anchored_state = state.copy()
Michael Walsh70369fd2016-11-22 11:25:57 -0600204 for key, match_state_value in anchored_state.items():
205 anchored_state[key] = "^" + str(anchored_state[key]) + "$"
206
207 return anchored_state
208
Michael Walsh70369fd2016-11-22 11:25:57 -0600209
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600210def strip_anchor_state(state):
211
212 r"""
213 Strip regular expression anchors ("^" and "$") from the beginning and end
214 of each item in the state dictionary passed in. Return the resulting
215 dictionary.
216
217 Description of Arguments:
218 state A dictionary such as the one returned by the get_state()
219 function.
220 """
221
Michael Walsh2ce067a2017-02-27 14:24:07 -0600222 stripped_state = state.copy()
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600223 for key, match_state_value in stripped_state.items():
224 stripped_state[key] = stripped_state[key].strip("^$")
225
226 return stripped_state
227
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600228
Michael Walsh70369fd2016-11-22 11:25:57 -0600229def compare_states(state,
Michael Walsh45ca6e42017-09-14 17:29:12 -0500230 match_state,
231 match_type='and'):
Michael Walsh70369fd2016-11-22 11:25:57 -0600232
233 r"""
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600234 Compare 2 state dictionaries. Return True if they match and False if they
Michael Walsh70369fd2016-11-22 11:25:57 -0600235 don't. Note that the match_state dictionary does not need to have an entry
236 corresponding to each entry in the state dictionary. But for each entry
237 that it does have, the corresponding state entry will be checked for a
238 match.
239
240 Description of arguments:
241 state A state dictionary such as the one returned by the
242 get_state function.
243 match_state A dictionary whose key/value pairs are "state field"/
244 "state value". The state value is interpreted as a
245 regular expression. Every value in this dictionary is
Michael Walsh45ca6e42017-09-14 17:29:12 -0500246 considered. When match_type is 'and', if each and every
247 comparison matches, the two dictionaries are considered to
248 be matching. If match_type is 'or', if any two of the
249 elements compared match, the two dictionaries are
250 considered to be matching.
Michael Walsh7dc885b2018-03-14 17:51:59 -0500251 This value may also be any string accepted by
252 return_state_constant (e.g. "standby_match_state").
253 In such a case this function will call
254 return_state_constant to convert it to a proper
255 dictionary as described above.
Michael Walsh45ca6e42017-09-14 17:29:12 -0500256 match_type This may be 'and' or 'or'.
Michael Walsh70369fd2016-11-22 11:25:57 -0600257 """
258
Michael Walsh45ca6e42017-09-14 17:29:12 -0500259 error_message = gv.svalid_value(match_type, var_name="match_type",
260 valid_values=['and', 'or'])
261 if error_message != "":
262 BuiltIn().fail(gp.sprint_error(error_message))
263
Michael Walsh7dc885b2018-03-14 17:51:59 -0500264 if type(match_state) in (str, unicode):
265 match_state = return_state_constant(match_state)
266
Michael Walsh45ca6e42017-09-14 17:29:12 -0500267 default_match = (match_type == 'and')
Michael Walsh70369fd2016-11-22 11:25:57 -0600268 for key, match_state_value in match_state.items():
Michael Walsh97df71c2017-03-27 14:33:24 -0500269 # Blank match_state_value means "don't care".
270 if match_state_value == "":
271 continue
Michael Walsh70369fd2016-11-22 11:25:57 -0600272 try:
Michael Walsh45ca6e42017-09-14 17:29:12 -0500273 match = (re.match(match_state_value, str(state[key])) is not None)
Michael Walsh70369fd2016-11-22 11:25:57 -0600274 except KeyError:
275 match = False
Michael Walsh70369fd2016-11-22 11:25:57 -0600276
Michael Walsh45ca6e42017-09-14 17:29:12 -0500277 if match != default_match:
278 return match
279
280 return default_match
Michael Walsh70369fd2016-11-22 11:25:57 -0600281
Michael Walsh70369fd2016-11-22 11:25:57 -0600282
Michael Walsh70369fd2016-11-22 11:25:57 -0600283def get_os_state(os_host="",
284 os_username="",
285 os_password="",
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600286 req_states=default_os_req_states,
287 os_up=True,
Michael Walsh70369fd2016-11-22 11:25:57 -0600288 quiet=None):
289
290 r"""
291 Get component states for the operating system such as ping, login,
292 etc, put them into a dictionary and return them to the caller.
293
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600294 Note that all substate values are strings.
295
Michael Walsh70369fd2016-11-22 11:25:57 -0600296 Description of arguments:
297 os_host The DNS name or IP address of the operating system.
298 This defaults to global ${OS_HOST}.
299 os_username The username to be used to login to the OS.
300 This defaults to global ${OS_USERNAME}.
301 os_password The password to be used to login to the OS.
302 This defaults to global ${OS_PASSWORD}.
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600303 req_states This is a list of states whose values are being requested by
304 the caller.
305 os_up If the caller knows that the os can't possibly be up, it can
306 improve performance by passing os_up=False. This function
307 will then simply return default values for all requested os
308 sub states.
Michael Walsh70369fd2016-11-22 11:25:57 -0600309 quiet Indicates whether status details (e.g. curl commands) should
310 be written to the console.
311 Defaults to either global value of ${QUIET} or to 1.
312 """
313
Michael Walsh619aa332017-04-12 15:56:51 -0500314 quiet = int(gp.get_var_value(quiet, 0))
Michael Walsh70369fd2016-11-22 11:25:57 -0600315
316 # Set parm defaults where necessary and validate all parms.
317 if os_host == "":
318 os_host = BuiltIn().get_variable_value("${OS_HOST}")
319 error_message = gv.svalid_value(os_host, var_name="os_host",
320 invalid_values=[None, ""])
321 if error_message != "":
322 BuiltIn().fail(gp.sprint_error(error_message))
323
324 if os_username == "":
325 os_username = BuiltIn().get_variable_value("${OS_USERNAME}")
326 error_message = gv.svalid_value(os_username, var_name="os_username",
327 invalid_values=[None, ""])
328 if error_message != "":
329 BuiltIn().fail(gp.sprint_error(error_message))
330
331 if os_password == "":
332 os_password = BuiltIn().get_variable_value("${OS_PASSWORD}")
333 error_message = gv.svalid_value(os_password, var_name="os_password",
334 invalid_values=[None, ""])
335 if error_message != "":
336 BuiltIn().fail(gp.sprint_error(error_message))
337
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600338 invalid_req_states = [sub_state for sub_state in req_states
339 if sub_state not in valid_os_req_states]
340 if len(invalid_req_states) > 0:
341 error_message = "The following req_states are not supported:\n" +\
342 gp.sprint_var(invalid_req_states)
343 BuiltIn().fail(gp.sprint_error(error_message))
Michael Walsh70369fd2016-11-22 11:25:57 -0600344
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600345 # Initialize all substate values supported by this function.
346 os_ping = 0
347 os_login = 0
348 os_run_cmd = 0
Michael Walsh70369fd2016-11-22 11:25:57 -0600349
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600350 if os_up:
351 if 'os_ping' in req_states:
352 # See if the OS pings.
353 cmd_buf = "ping -c 1 -w 2 " + os_host
354 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500355 gp.pissuing(cmd_buf)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600356 rc, out_buf = commands.getstatusoutput(cmd_buf)
357 if rc == 0:
358 os_ping = 1
Michael Walsh70369fd2016-11-22 11:25:57 -0600359
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600360 # Programming note: All attributes which do not require an ssh login
361 # should have been processed by this point.
362 master_req_login = ['os_login', 'os_run_cmd']
363 req_login = [sub_state for sub_state in req_states if sub_state in
364 master_req_login]
Michael Walsh97df71c2017-03-27 14:33:24 -0500365 must_login = (len(req_login) > 0)
Michael Walsh70369fd2016-11-22 11:25:57 -0600366
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600367 if must_login:
Michael Walsh3eb50022017-03-21 11:27:30 -0500368 # Open SSH connection to OS. Note that this doesn't fail even when
369 # the OS is not up.
Michael Walshac275512017-03-07 11:39:28 -0600370 cmd_buf = ["SSHLibrary.Open Connection", os_host]
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600371 if not quiet:
372 grp.rpissuing_keyword(cmd_buf)
373 ix = BuiltIn().run_keyword(*cmd_buf)
374
375 # Login to OS.
376 cmd_buf = ["Login", os_username, os_password]
377 if not quiet:
378 grp.rpissuing_keyword(cmd_buf)
Michael Walsh3eb50022017-03-21 11:27:30 -0500379 status, ret_values = \
380 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600381 if status == "PASS":
382 os_login = 1
Michael Walsh3eb50022017-03-21 11:27:30 -0500383 else:
384 gp.dprint_var(status)
385 gp.dprint_var(ret_values)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600386
387 if os_login:
388 if 'os_run_cmd' in req_states:
Michael Walsh3eb50022017-03-21 11:27:30 -0500389 # Try running a simple command (uptime) on the OS.
390 cmd_buf = ["Execute Command", "uptime",
391 "return_stderr=True", "return_rc=True"]
392 if not quiet:
393 grp.rpissuing_keyword(cmd_buf)
394 # Note that in spite of its name, there are occasions
395 # where run_keyword_and_ignore_error can fail.
396 status, ret_values = \
397 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
398 if status == "PASS":
399 stdout, stderr, rc = ret_values
400 if rc == 0 and stderr == "":
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600401 os_run_cmd = 1
Michael Walsh3eb50022017-03-21 11:27:30 -0500402 else:
403 gp.dprint_var(status)
404 gp.dprint_var(stdout)
405 gp.dprint_var(stderr)
406 gp.dprint_var(rc)
407 else:
408 gp.dprint_var(status)
409 gp.dprint_var(ret_values)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600410
411 os_state = DotDict()
412 for sub_state in req_states:
413 cmd_buf = "os_state['" + sub_state + "'] = str(" + sub_state + ")"
414 exec(cmd_buf)
Michael Walsh70369fd2016-11-22 11:25:57 -0600415
416 return os_state
417
Michael Walsh70369fd2016-11-22 11:25:57 -0600418
Michael Walsh70369fd2016-11-22 11:25:57 -0600419def get_state(openbmc_host="",
420 openbmc_username="",
421 openbmc_password="",
422 os_host="",
423 os_username="",
424 os_password="",
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600425 req_states=default_req_states,
Michael Walsh70369fd2016-11-22 11:25:57 -0600426 quiet=None):
427
428 r"""
Michael Walsh619aa332017-04-12 15:56:51 -0500429 Get component states such as chassis state, bmc state, etc, put them into a
Michael Walsh70369fd2016-11-22 11:25:57 -0600430 dictionary and return them to the caller.
431
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600432 Note that all substate values are strings.
433
Michael Walsh70369fd2016-11-22 11:25:57 -0600434 Description of arguments:
435 openbmc_host The DNS name or IP address of the BMC.
436 This defaults to global ${OPENBMC_HOST}.
437 openbmc_username The username to be used to login to the BMC.
438 This defaults to global ${OPENBMC_USERNAME}.
439 openbmc_password The password to be used to login to the BMC.
440 This defaults to global ${OPENBMC_PASSWORD}.
441 os_host The DNS name or IP address of the operating system.
442 This defaults to global ${OS_HOST}.
443 os_username The username to be used to login to the OS.
444 This defaults to global ${OS_USERNAME}.
445 os_password The password to be used to login to the OS.
446 This defaults to global ${OS_PASSWORD}.
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600447 req_states This is a list of states whose values are being requested
448 by the caller.
Michael Walsh70369fd2016-11-22 11:25:57 -0600449 quiet Indicates whether status details (e.g. curl commands)
450 should be written to the console.
451 Defaults to either global value of ${QUIET} or to 1.
452 """
453
Michael Walsh619aa332017-04-12 15:56:51 -0500454 quiet = int(gp.get_var_value(quiet, 0))
Michael Walsh70369fd2016-11-22 11:25:57 -0600455
456 # Set parm defaults where necessary and validate all parms.
457 if openbmc_host == "":
458 openbmc_host = BuiltIn().get_variable_value("${OPENBMC_HOST}")
459 error_message = gv.svalid_value(openbmc_host,
460 var_name="openbmc_host",
461 invalid_values=[None, ""])
462 if error_message != "":
463 BuiltIn().fail(gp.sprint_error(error_message))
464
465 if openbmc_username == "":
466 openbmc_username = BuiltIn().get_variable_value("${OPENBMC_USERNAME}")
467 error_message = gv.svalid_value(openbmc_username,
468 var_name="openbmc_username",
469 invalid_values=[None, ""])
470 if error_message != "":
471 BuiltIn().fail(gp.sprint_error(error_message))
472
473 if openbmc_password == "":
474 openbmc_password = BuiltIn().get_variable_value("${OPENBMC_PASSWORD}")
475 error_message = gv.svalid_value(openbmc_password,
476 var_name="openbmc_password",
477 invalid_values=[None, ""])
478 if error_message != "":
479 BuiltIn().fail(gp.sprint_error(error_message))
480
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600481 # NOTE: OS parms are optional.
Michael Walsh70369fd2016-11-22 11:25:57 -0600482 if os_host == "":
483 os_host = BuiltIn().get_variable_value("${OS_HOST}")
484 if os_host is None:
485 os_host = ""
486
487 if os_username is "":
488 os_username = BuiltIn().get_variable_value("${OS_USERNAME}")
489 if os_username is None:
490 os_username = ""
491
492 if os_password is "":
493 os_password = BuiltIn().get_variable_value("${OS_PASSWORD}")
494 if os_password is None:
495 os_password = ""
496
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600497 invalid_req_states = [sub_state for sub_state in req_states
498 if sub_state not in valid_req_states]
499 if len(invalid_req_states) > 0:
500 error_message = "The following req_states are not supported:\n" +\
501 gp.sprint_var(invalid_req_states)
502 BuiltIn().fail(gp.sprint_error(error_message))
503
504 # Initialize all substate values supported by this function.
505 ping = 0
506 packet_loss = ''
507 uptime = ''
508 epoch_seconds = ''
Michael Walsh2b269de2017-10-09 11:18:11 -0500509 rest = ''
510 chassis = ''
511 requested_chassis = ''
512 bmc = ''
513 requested_bmc = ''
514 boot_progress = ''
515 operating_system = ''
516 host = ''
517 requested_host = ''
518 attempts_left = ''
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600519
Michael Walsh70369fd2016-11-22 11:25:57 -0600520 # Get the component states.
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600521 if 'ping' in req_states:
522 # See if the OS pings.
523 cmd_buf = "ping -c 1 -w 2 " + openbmc_host
524 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500525 gp.pissuing(cmd_buf)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600526 rc, out_buf = commands.getstatusoutput(cmd_buf)
527 if rc == 0:
528 ping = 1
529
530 if 'packet_loss' in req_states:
531 # See if the OS pings.
532 cmd_buf = "ping -c 5 -w 5 " + openbmc_host +\
533 " | egrep 'packet loss' | sed -re 's/.* ([0-9]+)%.*/\\1/g'"
534 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500535 gp.pissuing(cmd_buf)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600536 rc, out_buf = commands.getstatusoutput(cmd_buf)
537 if rc == 0:
538 packet_loss = out_buf.rstrip("\n")
539
Michael Walshe53e47a2017-06-30 17:03:24 -0500540 if 'uptime' in req_states:
Michael Walshfa765932017-10-13 14:07:22 -0500541 # Sometimes reading uptime results in a blank value. Call with
542 # wait_until_keyword_succeeds to ensure a non-blank value is obtained.
543 remote_cmd_buf = "read uptime filler 2>/dev/null < /proc/uptime" +\
544 " && [ ! -z \"${uptime}\" ] && echo ${uptime}"
545 cmd_buf = ["BMC Execute Command", re.sub(r'\$', '\$', remote_cmd_buf),
546 'quiet=1']
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600547 if not quiet:
548 grp.rpissuing_keyword(cmd_buf)
Michael Walshfa765932017-10-13 14:07:22 -0500549 grp.rpissuing(remote_cmd_buf)
550 try:
551 stdout, stderr, rc =\
Michael Walshcaccd852017-11-01 17:58:41 -0500552 BuiltIn().wait_until_keyword_succeeds("10 sec", "0 sec",
Michael Walshfa765932017-10-13 14:07:22 -0500553 *cmd_buf)
Michael Walsh97df71c2017-03-27 14:33:24 -0500554 if rc == 0 and stderr == "":
555 uptime = stdout
Michael Walshfa765932017-10-13 14:07:22 -0500556 except AssertionError as my_assertion_error:
557 pass
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600558
Michael Walshe53e47a2017-06-30 17:03:24 -0500559 if 'epoch_seconds' in req_states:
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600560 date_cmd_buf = "date -u +%s"
561 if USE_BMC_EPOCH_TIME:
Michael Walshe53e47a2017-06-30 17:03:24 -0500562 cmd_buf = ["BMC Execute Command", date_cmd_buf, 'quiet=${1}']
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600563 if not quiet:
564 grp.rpissuing_keyword(cmd_buf)
Michael Walsh97df71c2017-03-27 14:33:24 -0500565 status, ret_values = \
566 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
567 if status == "PASS":
568 stdout, stderr, rc = ret_values
569 if rc == 0 and stderr == "":
570 epoch_seconds = stdout.rstrip("\n")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600571 else:
572 shell_rc, out_buf = gc.cmd_fnc_u(date_cmd_buf,
Michael Walshfa765932017-10-13 14:07:22 -0500573 quiet=quiet,
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600574 print_output=0)
575 if shell_rc == 0:
576 epoch_seconds = out_buf.rstrip("\n")
577
Michael Walsh56749222017-09-29 15:26:07 -0500578 master_req_rest = ['rest', 'host', 'requested_host', 'operating_system',
579 'attempts_left', 'boot_progress', 'chassis',
580 'requested_chassis' 'bmc' 'requested_bmc']
581
Michael Walshb95eb542017-03-31 09:39:20 -0500582 req_rest = [sub_state for sub_state in req_states if sub_state in
583 master_req_rest]
584 need_rest = (len(req_rest) > 0)
Michael Walsh56749222017-09-29 15:26:07 -0500585 state = DotDict()
586 if need_rest:
Michael Walsh940d6912017-10-27 12:32:33 -0500587 cmd_buf = ["Read Properties", SYSTEM_STATE_URI + "enumerate",
Michael Walsh56749222017-09-29 15:26:07 -0500588 "quiet=${" + str(quiet) + "}"]
Michael Walsh341c21e2017-01-17 16:25:20 -0600589 grp.rdpissuing_keyword(cmd_buf)
Michael Walsh97df71c2017-03-27 14:33:24 -0500590 status, ret_values = \
591 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
592 if status == "PASS":
Michael Walsh56749222017-09-29 15:26:07 -0500593 state['rest'] = '1'
Michael Walsh341c21e2017-01-17 16:25:20 -0600594 else:
Michael Walsh56749222017-09-29 15:26:07 -0500595 state['rest'] = '0'
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600596
Michael Walsh2b269de2017-10-09 11:18:11 -0500597 if int(state['rest']):
598 for url_path in ret_values:
599 for attr_name in ret_values[url_path]:
600 # Create a state key value based on the attr_name.
601 if type(ret_values[url_path][attr_name]) is unicode:
602 ret_values[url_path][attr_name] = \
603 re.sub(r'.*\.', "",
604 ret_values[url_path][attr_name])
605 # Do some key name manipulations.
606 new_attr_name = re.sub(r'^Current|(State|Transition)$',
607 "", attr_name)
608 new_attr_name = re.sub(r'BMC', r'Bmc', new_attr_name)
609 new_attr_name = re.sub(r'([A-Z][a-z])', r'_\1',
610 new_attr_name)
611 new_attr_name = new_attr_name.lower().lstrip("_")
612 new_attr_name = re.sub(r'power', r'chassis', new_attr_name)
613 if new_attr_name in req_states:
614 state[new_attr_name] = ret_values[url_path][attr_name]
Michael Walshb95eb542017-03-31 09:39:20 -0500615
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600616 for sub_state in req_states:
Michael Walsh56749222017-09-29 15:26:07 -0500617 if sub_state in state:
618 continue
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600619 if sub_state.startswith("os_"):
620 # We pass "os_" requests on to get_os_state.
621 continue
622 cmd_buf = "state['" + sub_state + "'] = str(" + sub_state + ")"
623 exec(cmd_buf)
624
625 if os_host == "":
626 # The caller has not specified an os_host so as far as we're concerned,
627 # it doesn't exist.
628 return state
629
630 os_req_states = [sub_state for sub_state in req_states
631 if sub_state.startswith('os_')]
632
633 if len(os_req_states) > 0:
634 # The caller has specified an os_host and they have requested
635 # information on os substates.
636
637 # Based on the information gathered on bmc, we'll try to make a
638 # determination of whether the os is even up. We'll pass the result
639 # of that assessment to get_os_state to enhance performance.
640 os_up_match = DotDict()
641 for sub_state in master_os_up_match:
642 if sub_state in req_states:
643 os_up_match[sub_state] = master_os_up_match[sub_state]
Michael Walsh70369fd2016-11-22 11:25:57 -0600644 os_up = compare_states(state, os_up_match)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600645 os_state = get_os_state(os_host=os_host,
646 os_username=os_username,
647 os_password=os_password,
648 req_states=os_req_states,
649 os_up=os_up,
650 quiet=quiet)
651 # Append os_state dictionary to ours.
652 state.update(os_state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600653
654 return state
655
Michael Walsh70369fd2016-11-22 11:25:57 -0600656
Michael Walsh70369fd2016-11-22 11:25:57 -0600657def check_state(match_state,
658 invert=0,
659 print_string="",
660 openbmc_host="",
661 openbmc_username="",
662 openbmc_password="",
663 os_host="",
664 os_username="",
665 os_password="",
666 quiet=None):
667
668 r"""
669 Check that the Open BMC machine's composite state matches the specified
670 state. On success, this keyword returns the machine's composite state as a
671 dictionary.
672
673 Description of arguments:
674 match_state A dictionary whose key/value pairs are "state field"/
675 "state value". The state value is interpreted as a
676 regular expression. Example call from robot:
Michael Walsh341c21e2017-01-17 16:25:20 -0600677 ${match_state}= Create Dictionary chassis=^On$
678 ... bmc=^Ready$
Michael Walsh01975fa2017-08-20 20:51:36 -0500679 ... boot_progress=^OSStart$
Michael Walsh70369fd2016-11-22 11:25:57 -0600680 ${state}= Check State &{match_state}
681 invert If this flag is set, this function will succeed if the
682 states do NOT match.
683 print_string This function will print this string to the console prior
684 to getting the state.
685 openbmc_host The DNS name or IP address of the BMC.
686 This defaults to global ${OPENBMC_HOST}.
687 openbmc_username The username to be used to login to the BMC.
688 This defaults to global ${OPENBMC_USERNAME}.
689 openbmc_password The password to be used to login to the BMC.
690 This defaults to global ${OPENBMC_PASSWORD}.
691 os_host The DNS name or IP address of the operating system.
692 This defaults to global ${OS_HOST}.
693 os_username The username to be used to login to the OS.
694 This defaults to global ${OS_USERNAME}.
695 os_password The password to be used to login to the OS.
696 This defaults to global ${OS_PASSWORD}.
697 quiet Indicates whether status details should be written to the
698 console. Defaults to either global value of ${QUIET} or
699 to 1.
700 """
701
Michael Walsh619aa332017-04-12 15:56:51 -0500702 quiet = int(gp.get_var_value(quiet, 0))
Michael Walsh70369fd2016-11-22 11:25:57 -0600703
704 grp.rprint(print_string)
705
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600706 req_states = match_state.keys()
Michael Walsh70369fd2016-11-22 11:25:57 -0600707 # Initialize state.
708 state = get_state(openbmc_host=openbmc_host,
709 openbmc_username=openbmc_username,
710 openbmc_password=openbmc_password,
711 os_host=os_host,
712 os_username=os_username,
713 os_password=os_password,
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600714 req_states=req_states,
Michael Walsh70369fd2016-11-22 11:25:57 -0600715 quiet=quiet)
716 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500717 gp.print_var(state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600718
719 match = compare_states(state, match_state)
720
721 if invert and match:
722 fail_msg = "The current state of the machine matches the match" +\
723 " state:\n" + gp.sprint_varx("state", state)
724 BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
725 elif not invert and not match:
726 fail_msg = "The current state of the machine does NOT match the" +\
727 " match state:\n" +\
728 gp.sprint_varx("state", state)
729 BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
730
731 return state
732
Michael Walsh70369fd2016-11-22 11:25:57 -0600733
Michael Walshf893ba02017-01-10 10:28:05 -0600734def wait_state(match_state=(),
Michael Walsh70369fd2016-11-22 11:25:57 -0600735 wait_time="1 min",
736 interval="1 second",
737 invert=0,
738 openbmc_host="",
739 openbmc_username="",
740 openbmc_password="",
741 os_host="",
742 os_username="",
743 os_password="",
744 quiet=None):
745
746 r"""
747 Wait for the Open BMC machine's composite state to match the specified
748 state. On success, this keyword returns the machine's composite state as
749 a dictionary.
750
751 Description of arguments:
752 match_state A dictionary whose key/value pairs are "state field"/
753 "state value". See check_state (above) for details.
Michael Walsh619aa332017-04-12 15:56:51 -0500754 This value may also be any string accepted by
755 return_state_constant (e.g. "standby_match_state").
756 In such a case this function will call
757 return_state_constant to convert it to a proper
758 dictionary as described above.
Michael Walsh70369fd2016-11-22 11:25:57 -0600759 wait_time The total amount of time to wait for the desired state.
760 This value may be expressed in Robot Framework's time
761 format (e.g. 1 minute, 2 min 3 s, 4.5).
762 interval The amount of time between state checks.
763 This value may be expressed in Robot Framework's time
764 format (e.g. 1 minute, 2 min 3 s, 4.5).
765 invert If this flag is set, this function will for the state of
766 the machine to cease to match the match state.
767 openbmc_host The DNS name or IP address of the BMC.
768 This defaults to global ${OPENBMC_HOST}.
769 openbmc_username The username to be used to login to the BMC.
770 This defaults to global ${OPENBMC_USERNAME}.
771 openbmc_password The password to be used to login to the BMC.
772 This defaults to global ${OPENBMC_PASSWORD}.
773 os_host The DNS name or IP address of the operating system.
774 This defaults to global ${OS_HOST}.
775 os_username The username to be used to login to the OS.
776 This defaults to global ${OS_USERNAME}.
777 os_password The password to be used to login to the OS.
778 This defaults to global ${OS_PASSWORD}.
779 quiet Indicates whether status details should be written to the
780 console. Defaults to either global value of ${QUIET} or
781 to 1.
782 """
783
Michael Walsh619aa332017-04-12 15:56:51 -0500784 quiet = int(gp.get_var_value(quiet, 0))
785
786 if type(match_state) in (str, unicode):
787 match_state = return_state_constant(match_state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600788
789 if not quiet:
790 if invert:
791 alt_text = "cease to "
792 else:
793 alt_text = ""
Michael Walsh3eb50022017-03-21 11:27:30 -0500794 gp.print_timen("Checking every " + str(interval) + " for up to " +
795 str(wait_time) + " for the state of the machine to " +
796 alt_text + "match the state shown below.")
797 gp.print_var(match_state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600798
Michael Walshf893ba02017-01-10 10:28:05 -0600799 if quiet:
Michael Walsh341c21e2017-01-17 16:25:20 -0600800 print_string = ""
Michael Walshf893ba02017-01-10 10:28:05 -0600801 else:
Michael Walsh341c21e2017-01-17 16:25:20 -0600802 print_string = "#"
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600803
804 debug = int(BuiltIn().get_variable_value("${debug}", "0"))
805 if debug:
806 # In debug we print state so no need to print the "#".
807 print_string = ""
808 check_state_quiet = 1 - debug
Michael Walsh70369fd2016-11-22 11:25:57 -0600809 cmd_buf = ["Check State", match_state, "invert=${" + str(invert) + "}",
Michael Walshf893ba02017-01-10 10:28:05 -0600810 "print_string=" + print_string, "openbmc_host=" + openbmc_host,
Michael Walsh70369fd2016-11-22 11:25:57 -0600811 "openbmc_username=" + openbmc_username,
812 "openbmc_password=" + openbmc_password, "os_host=" + os_host,
813 "os_username=" + os_username, "os_password=" + os_password,
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600814 "quiet=${" + str(check_state_quiet) + "}"]
Michael Walsh70369fd2016-11-22 11:25:57 -0600815 grp.rdpissuing_keyword(cmd_buf)
Michael Walsh619aa332017-04-12 15:56:51 -0500816 try:
817 state = BuiltIn().wait_until_keyword_succeeds(wait_time, interval,
818 *cmd_buf)
819 except AssertionError as my_assertion_error:
820 gp.printn()
821 message = my_assertion_error.args[0]
822 BuiltIn().fail(message)
823
Michael Walsh70369fd2016-11-22 11:25:57 -0600824 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500825 gp.printn()
Michael Walsh70369fd2016-11-22 11:25:57 -0600826 if invert:
Michael Walsh3eb50022017-03-21 11:27:30 -0500827 gp.print_timen("The states no longer match:")
Michael Walsh70369fd2016-11-22 11:25:57 -0600828 else:
Michael Walsh3eb50022017-03-21 11:27:30 -0500829 gp.print_timen("The states match:")
830 gp.print_var(state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600831
832 return state
833
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600834
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 Walshe77585a2017-12-14 11:02:28 -0600879 if state['uptime'] == "":
880 error_message = "Unable to obtain uptime from the BMC. BMC is not" +\
881 " communicating."
882 BuiltIn().fail(gp.sprint_error(error_message))
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600883 if int(float(state['uptime'])) < elapsed_boot_time:
884 uptime = state['uptime']
Michael Walsh619aa332017-04-12 15:56:51 -0500885 gp.qprint_var(uptime)
886 gp.qprint_timen("The uptime is less than the elapsed boot time," +
887 " as expected.")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600888 else:
889 error_message = "The uptime is greater than the elapsed boot time," +\
890 " which is unexpected:\n" +\
891 gp.sprint_var(start_boot_seconds) +\
892 gp.sprint_var(state)
893 BuiltIn().fail(gp.sprint_error(error_message))
894
Michael Walsh619aa332017-04-12 15:56:51 -0500895 gp.qprint_timen("Verifying that REST API interface is working.")
Michael Walshb95eb542017-03-31 09:39:20 -0500896 match_state = DotDict([('rest', '^1$')])
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600897 state = wait_state(match_state, wait_time="5 mins", interval="2 seconds")
898