blob: 7cdcb8ccbce41edb44f7f1461972dde2831a8db5 [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 Walsh6a9bd142018-07-24 16:10:29 -050035import bmc_ssh_utils as bsu
Michael Walsh70369fd2016-11-22 11:25:57 -060036
Michael Walsh70369fd2016-11-22 11:25:57 -060037from 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 Walsh5a5868a2018-10-31 15:20:04 -050046# NOTE: Avoid importing utils.robot because utils.robot imports state.py
47# (indirectly) which will cause failures.
48gru.my_import_resource("rest_client.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 Walsh192d5e72018-11-01 14:09:11 -0500170 ('host', '^Running|Quiesced$')])
Michael Walsh619aa332017-04-12 15:56:51 -0500171
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
George Keishing36efbc02018-12-12 10:18:23 -0600180def return_state_constant(state_name='default_state'):
Michael Walsh619aa332017-04-12 15:56:51 -0500181 r"""
Michael Walsh7dc885b2018-03-14 17:51:59 -0500182 Return the named state dictionary constant.
Michael Walsh619aa332017-04-12 15:56:51 -0500183 """
184
George Keishing36efbc02018-12-12 10:18:23 -0600185 return eval(state_name)
Michael Walsh619aa332017-04-12 15:56:51 -0500186
Michael Walsh619aa332017-04-12 15:56:51 -0500187
Michael Walsh70369fd2016-11-22 11:25:57 -0600188def anchor_state(state):
Michael Walsh70369fd2016-11-22 11:25:57 -0600189 r"""
190 Add regular expression anchors ("^" and "$") to the beginning and end of
191 each item in the state dictionary passed in. Return the resulting
192 dictionary.
193
194 Description of Arguments:
195 state A dictionary such as the one returned by the get_state()
196 function.
197 """
198
Michael Walsh2ce067a2017-02-27 14:24:07 -0600199 anchored_state = state.copy()
Michael Walsh70369fd2016-11-22 11:25:57 -0600200 for key, match_state_value in anchored_state.items():
201 anchored_state[key] = "^" + str(anchored_state[key]) + "$"
202
203 return anchored_state
204
Michael Walsh70369fd2016-11-22 11:25:57 -0600205
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600206def strip_anchor_state(state):
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600207 r"""
208 Strip regular expression anchors ("^" and "$") from the beginning and end
209 of each item in the state dictionary passed in. Return the resulting
210 dictionary.
211
212 Description of Arguments:
213 state A dictionary such as the one returned by the get_state()
214 function.
215 """
216
Michael Walsh2ce067a2017-02-27 14:24:07 -0600217 stripped_state = state.copy()
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600218 for key, match_state_value in stripped_state.items():
219 stripped_state[key] = stripped_state[key].strip("^$")
220
221 return stripped_state
222
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600223
Michael Walsh70369fd2016-11-22 11:25:57 -0600224def compare_states(state,
Michael Walsh45ca6e42017-09-14 17:29:12 -0500225 match_state,
226 match_type='and'):
Michael Walsh70369fd2016-11-22 11:25:57 -0600227 r"""
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600228 Compare 2 state dictionaries. Return True if they match and False if they
Michael Walsh70369fd2016-11-22 11:25:57 -0600229 don't. Note that the match_state dictionary does not need to have an entry
230 corresponding to each entry in the state dictionary. But for each entry
231 that it does have, the corresponding state entry will be checked for a
232 match.
233
234 Description of arguments:
235 state A state dictionary such as the one returned by the
236 get_state function.
237 match_state A dictionary whose key/value pairs are "state field"/
238 "state value". The state value is interpreted as a
239 regular expression. Every value in this dictionary is
Michael Walsh45ca6e42017-09-14 17:29:12 -0500240 considered. When match_type is 'and', if each and every
241 comparison matches, the two dictionaries are considered to
242 be matching. If match_type is 'or', if any two of the
243 elements compared match, the two dictionaries are
244 considered to be matching.
Michael Walsh7dc885b2018-03-14 17:51:59 -0500245 This value may also be any string accepted by
246 return_state_constant (e.g. "standby_match_state").
247 In such a case this function will call
248 return_state_constant to convert it to a proper
249 dictionary as described above.
Michael Walsh45ca6e42017-09-14 17:29:12 -0500250 match_type This may be 'and' or 'or'.
Michael Walsh70369fd2016-11-22 11:25:57 -0600251 """
252
Michael Walsh45ca6e42017-09-14 17:29:12 -0500253 error_message = gv.svalid_value(match_type, var_name="match_type",
254 valid_values=['and', 'or'])
255 if error_message != "":
256 BuiltIn().fail(gp.sprint_error(error_message))
257
George Keishing36efbc02018-12-12 10:18:23 -0600258 try:
Michael Walsh7dc885b2018-03-14 17:51:59 -0500259 match_state = return_state_constant(match_state)
George Keishing36efbc02018-12-12 10:18:23 -0600260 except TypeError:
261 pass
Michael Walsh7dc885b2018-03-14 17:51:59 -0500262
Michael Walsh45ca6e42017-09-14 17:29:12 -0500263 default_match = (match_type == 'and')
Michael Walsh70369fd2016-11-22 11:25:57 -0600264 for key, match_state_value in match_state.items():
Michael Walsh97df71c2017-03-27 14:33:24 -0500265 # Blank match_state_value means "don't care".
266 if match_state_value == "":
267 continue
Michael Walsh70369fd2016-11-22 11:25:57 -0600268 try:
Michael Walsh45ca6e42017-09-14 17:29:12 -0500269 match = (re.match(match_state_value, str(state[key])) is not None)
Michael Walsh70369fd2016-11-22 11:25:57 -0600270 except KeyError:
271 match = False
Michael Walsh70369fd2016-11-22 11:25:57 -0600272
Michael Walsh45ca6e42017-09-14 17:29:12 -0500273 if match != default_match:
274 return match
275
276 return default_match
Michael Walsh70369fd2016-11-22 11:25:57 -0600277
Michael Walsh70369fd2016-11-22 11:25:57 -0600278
Michael Walsh70369fd2016-11-22 11:25:57 -0600279def get_os_state(os_host="",
280 os_username="",
281 os_password="",
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600282 req_states=default_os_req_states,
283 os_up=True,
Michael Walsh70369fd2016-11-22 11:25:57 -0600284 quiet=None):
Michael Walsh70369fd2016-11-22 11:25:57 -0600285 r"""
286 Get component states for the operating system such as ping, login,
287 etc, put them into a dictionary and return them to the caller.
288
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600289 Note that all substate values are strings.
290
Michael Walsh70369fd2016-11-22 11:25:57 -0600291 Description of arguments:
292 os_host The DNS name or IP address of the operating system.
293 This defaults to global ${OS_HOST}.
294 os_username The username to be used to login to the OS.
295 This defaults to global ${OS_USERNAME}.
296 os_password The password to be used to login to the OS.
297 This defaults to global ${OS_PASSWORD}.
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600298 req_states This is a list of states whose values are being requested by
299 the caller.
300 os_up If the caller knows that the os can't possibly be up, it can
301 improve performance by passing os_up=False. This function
302 will then simply return default values for all requested os
303 sub states.
Michael Walsh70369fd2016-11-22 11:25:57 -0600304 quiet Indicates whether status details (e.g. curl commands) should
305 be written to the console.
306 Defaults to either global value of ${QUIET} or to 1.
307 """
308
Michael Walsh619aa332017-04-12 15:56:51 -0500309 quiet = int(gp.get_var_value(quiet, 0))
Michael Walsh70369fd2016-11-22 11:25:57 -0600310
311 # Set parm defaults where necessary and validate all parms.
312 if os_host == "":
313 os_host = BuiltIn().get_variable_value("${OS_HOST}")
314 error_message = gv.svalid_value(os_host, var_name="os_host",
315 invalid_values=[None, ""])
316 if error_message != "":
317 BuiltIn().fail(gp.sprint_error(error_message))
318
319 if os_username == "":
320 os_username = BuiltIn().get_variable_value("${OS_USERNAME}")
321 error_message = gv.svalid_value(os_username, var_name="os_username",
322 invalid_values=[None, ""])
323 if error_message != "":
324 BuiltIn().fail(gp.sprint_error(error_message))
325
326 if os_password == "":
327 os_password = BuiltIn().get_variable_value("${OS_PASSWORD}")
328 error_message = gv.svalid_value(os_password, var_name="os_password",
329 invalid_values=[None, ""])
330 if error_message != "":
331 BuiltIn().fail(gp.sprint_error(error_message))
332
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600333 invalid_req_states = [sub_state for sub_state in req_states
334 if sub_state not in valid_os_req_states]
335 if len(invalid_req_states) > 0:
336 error_message = "The following req_states are not supported:\n" +\
337 gp.sprint_var(invalid_req_states)
338 BuiltIn().fail(gp.sprint_error(error_message))
Michael Walsh70369fd2016-11-22 11:25:57 -0600339
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600340 # Initialize all substate values supported by this function.
341 os_ping = 0
342 os_login = 0
343 os_run_cmd = 0
Michael Walsh70369fd2016-11-22 11:25:57 -0600344
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600345 if os_up:
346 if 'os_ping' in req_states:
347 # See if the OS pings.
George Keishing36efbc02018-12-12 10:18:23 -0600348 rc, out_buf = gc.shell_cmd("ping -c 1 -w 2 " + os_host,
349 print_output=0, show_err=0,
350 ignore_err=1)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600351 if rc == 0:
352 os_ping = 1
Michael Walsh70369fd2016-11-22 11:25:57 -0600353
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600354 # Programming note: All attributes which do not require an ssh login
355 # should have been processed by this point.
356 master_req_login = ['os_login', 'os_run_cmd']
357 req_login = [sub_state for sub_state in req_states if sub_state in
358 master_req_login]
Michael Walsh97df71c2017-03-27 14:33:24 -0500359 must_login = (len(req_login) > 0)
Michael Walsh70369fd2016-11-22 11:25:57 -0600360
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600361 if must_login:
Michael Walsh6a9bd142018-07-24 16:10:29 -0500362 output, stderr, rc = bsu.os_execute_command("uptime", quiet=quiet,
Michael Walsh7fc33972018-08-07 14:55:03 -0500363 ignore_err=1,
364 time_out=20)
Michael Walsh6a9bd142018-07-24 16:10:29 -0500365 if rc == 0:
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600366 os_login = 1
Michael Walsh6a9bd142018-07-24 16:10:29 -0500367 os_run_cmd = 1
Michael Walsh3eb50022017-03-21 11:27:30 -0500368 else:
Michael Walsh6a9bd142018-07-24 16:10:29 -0500369 gp.dprint_vars(output, stderr)
370 gp.dprint_vars(rc, 1)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600371
372 os_state = DotDict()
373 for sub_state in req_states:
374 cmd_buf = "os_state['" + sub_state + "'] = str(" + sub_state + ")"
375 exec(cmd_buf)
Michael Walsh70369fd2016-11-22 11:25:57 -0600376
377 return os_state
378
Michael Walsh70369fd2016-11-22 11:25:57 -0600379
Michael Walsh70369fd2016-11-22 11:25:57 -0600380def get_state(openbmc_host="",
381 openbmc_username="",
382 openbmc_password="",
383 os_host="",
384 os_username="",
385 os_password="",
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600386 req_states=default_req_states,
Michael Walsh70369fd2016-11-22 11:25:57 -0600387 quiet=None):
Michael Walsh70369fd2016-11-22 11:25:57 -0600388 r"""
Michael Walsh619aa332017-04-12 15:56:51 -0500389 Get component states such as chassis state, bmc state, etc, put them into a
Michael Walsh70369fd2016-11-22 11:25:57 -0600390 dictionary and return them to the caller.
391
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600392 Note that all substate values are strings.
393
Michael Walsh70369fd2016-11-22 11:25:57 -0600394 Description of arguments:
395 openbmc_host The DNS name or IP address of the BMC.
396 This defaults to global ${OPENBMC_HOST}.
397 openbmc_username The username to be used to login to the BMC.
398 This defaults to global ${OPENBMC_USERNAME}.
399 openbmc_password The password to be used to login to the BMC.
400 This defaults to global ${OPENBMC_PASSWORD}.
401 os_host The DNS name or IP address of the operating system.
402 This defaults to global ${OS_HOST}.
403 os_username The username to be used to login to the OS.
404 This defaults to global ${OS_USERNAME}.
405 os_password The password to be used to login to the OS.
406 This defaults to global ${OS_PASSWORD}.
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600407 req_states This is a list of states whose values are being requested
408 by the caller.
Michael Walsh70369fd2016-11-22 11:25:57 -0600409 quiet Indicates whether status details (e.g. curl commands)
410 should be written to the console.
411 Defaults to either global value of ${QUIET} or to 1.
412 """
413
Michael Walsh619aa332017-04-12 15:56:51 -0500414 quiet = int(gp.get_var_value(quiet, 0))
Michael Walsh70369fd2016-11-22 11:25:57 -0600415
416 # Set parm defaults where necessary and validate all parms.
417 if openbmc_host == "":
418 openbmc_host = BuiltIn().get_variable_value("${OPENBMC_HOST}")
419 error_message = gv.svalid_value(openbmc_host,
420 var_name="openbmc_host",
421 invalid_values=[None, ""])
422 if error_message != "":
423 BuiltIn().fail(gp.sprint_error(error_message))
424
425 if openbmc_username == "":
426 openbmc_username = BuiltIn().get_variable_value("${OPENBMC_USERNAME}")
427 error_message = gv.svalid_value(openbmc_username,
428 var_name="openbmc_username",
429 invalid_values=[None, ""])
430 if error_message != "":
431 BuiltIn().fail(gp.sprint_error(error_message))
432
433 if openbmc_password == "":
434 openbmc_password = BuiltIn().get_variable_value("${OPENBMC_PASSWORD}")
435 error_message = gv.svalid_value(openbmc_password,
436 var_name="openbmc_password",
437 invalid_values=[None, ""])
438 if error_message != "":
439 BuiltIn().fail(gp.sprint_error(error_message))
440
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600441 # NOTE: OS parms are optional.
Michael Walsh70369fd2016-11-22 11:25:57 -0600442 if os_host == "":
443 os_host = BuiltIn().get_variable_value("${OS_HOST}")
444 if os_host is None:
445 os_host = ""
446
447 if os_username is "":
448 os_username = BuiltIn().get_variable_value("${OS_USERNAME}")
449 if os_username is None:
450 os_username = ""
451
452 if os_password is "":
453 os_password = BuiltIn().get_variable_value("${OS_PASSWORD}")
454 if os_password is None:
455 os_password = ""
456
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600457 invalid_req_states = [sub_state for sub_state in req_states
458 if sub_state not in valid_req_states]
459 if len(invalid_req_states) > 0:
460 error_message = "The following req_states are not supported:\n" +\
461 gp.sprint_var(invalid_req_states)
462 BuiltIn().fail(gp.sprint_error(error_message))
463
464 # Initialize all substate values supported by this function.
465 ping = 0
466 packet_loss = ''
467 uptime = ''
468 epoch_seconds = ''
Michael Walsh2b269de2017-10-09 11:18:11 -0500469 rest = ''
470 chassis = ''
471 requested_chassis = ''
472 bmc = ''
473 requested_bmc = ''
474 boot_progress = ''
475 operating_system = ''
476 host = ''
477 requested_host = ''
478 attempts_left = ''
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600479
Michael Walsh70369fd2016-11-22 11:25:57 -0600480 # Get the component states.
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600481 if 'ping' in req_states:
482 # See if the OS pings.
George Keishing36efbc02018-12-12 10:18:23 -0600483 rc, out_buf = gc.shell_cmd("ping -c 1 -w 2 " + openbmc_host,
484 print_output=0, show_err=0,
485 ignore_err=1)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600486 if rc == 0:
487 ping = 1
488
489 if 'packet_loss' in req_states:
490 # See if the OS pings.
491 cmd_buf = "ping -c 5 -w 5 " + openbmc_host +\
492 " | egrep 'packet loss' | sed -re 's/.* ([0-9]+)%.*/\\1/g'"
George Keishing36efbc02018-12-12 10:18:23 -0600493 rc, out_buf = gc.shell_cmd(cmd_buf,
494 print_output=0, show_err=0,
495 ignore_err=1)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600496 if rc == 0:
497 packet_loss = out_buf.rstrip("\n")
498
Michael Walshe53e47a2017-06-30 17:03:24 -0500499 if 'uptime' in req_states:
Michael Walshfa765932017-10-13 14:07:22 -0500500 # Sometimes reading uptime results in a blank value. Call with
501 # wait_until_keyword_succeeds to ensure a non-blank value is obtained.
502 remote_cmd_buf = "read uptime filler 2>/dev/null < /proc/uptime" +\
503 " && [ ! -z \"${uptime}\" ] && echo ${uptime}"
Joy Onyerikwu004ad3c2018-06-11 16:29:56 -0500504 cmd_buf = ["BMC Execute Command",
Michael Walsh8c34eb72018-06-14 11:26:16 -0500505 re.sub('\\$', '\\$', remote_cmd_buf), 'quiet=1']
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600506 if not quiet:
Michael Walshaf722b12018-08-17 14:55:32 -0500507 # Get loc_test_mode parm for improved output on pissuing.
508 # See sprint_issuing in gen_print.py for details.
509 loc_test_mode = int(gp.get_var_value(var_name="test_mode",
510 default=0))
511 grp.rpissuing_keyword(cmd_buf, loc_test_mode)
512 gp.pissuing(remote_cmd_buf, loc_test_mode)
Michael Walshfa765932017-10-13 14:07:22 -0500513 try:
514 stdout, stderr, rc =\
Michael Walshcaccd852017-11-01 17:58:41 -0500515 BuiltIn().wait_until_keyword_succeeds("10 sec", "0 sec",
Michael Walshfa765932017-10-13 14:07:22 -0500516 *cmd_buf)
Michael Walsh97df71c2017-03-27 14:33:24 -0500517 if rc == 0 and stderr == "":
518 uptime = stdout
Michael Walshfa765932017-10-13 14:07:22 -0500519 except AssertionError as my_assertion_error:
520 pass
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600521
Michael Walshe53e47a2017-06-30 17:03:24 -0500522 if 'epoch_seconds' in req_states:
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600523 date_cmd_buf = "date -u +%s"
524 if USE_BMC_EPOCH_TIME:
Michael Walshe53e47a2017-06-30 17:03:24 -0500525 cmd_buf = ["BMC Execute Command", date_cmd_buf, 'quiet=${1}']
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600526 if not quiet:
527 grp.rpissuing_keyword(cmd_buf)
Michael Walsh97df71c2017-03-27 14:33:24 -0500528 status, ret_values = \
529 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
530 if status == "PASS":
531 stdout, stderr, rc = ret_values
532 if rc == 0 and stderr == "":
533 epoch_seconds = stdout.rstrip("\n")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600534 else:
535 shell_rc, out_buf = gc.cmd_fnc_u(date_cmd_buf,
Michael Walshfa765932017-10-13 14:07:22 -0500536 quiet=quiet,
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600537 print_output=0)
538 if shell_rc == 0:
539 epoch_seconds = out_buf.rstrip("\n")
540
Michael Walsh56749222017-09-29 15:26:07 -0500541 master_req_rest = ['rest', 'host', 'requested_host', 'operating_system',
542 'attempts_left', 'boot_progress', 'chassis',
543 'requested_chassis' 'bmc' 'requested_bmc']
544
Michael Walshb95eb542017-03-31 09:39:20 -0500545 req_rest = [sub_state for sub_state in req_states if sub_state in
546 master_req_rest]
547 need_rest = (len(req_rest) > 0)
Michael Walsh56749222017-09-29 15:26:07 -0500548 state = DotDict()
549 if need_rest:
Michael Walsh940d6912017-10-27 12:32:33 -0500550 cmd_buf = ["Read Properties", SYSTEM_STATE_URI + "enumerate",
Michael Walsh56749222017-09-29 15:26:07 -0500551 "quiet=${" + str(quiet) + "}"]
Michael Walsh341c21e2017-01-17 16:25:20 -0600552 grp.rdpissuing_keyword(cmd_buf)
Michael Walsh97df71c2017-03-27 14:33:24 -0500553 status, ret_values = \
554 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
555 if status == "PASS":
Michael Walsh56749222017-09-29 15:26:07 -0500556 state['rest'] = '1'
Michael Walsh341c21e2017-01-17 16:25:20 -0600557 else:
Michael Walsh56749222017-09-29 15:26:07 -0500558 state['rest'] = '0'
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600559
Michael Walsh2b269de2017-10-09 11:18:11 -0500560 if int(state['rest']):
561 for url_path in ret_values:
562 for attr_name in ret_values[url_path]:
563 # Create a state key value based on the attr_name.
George Keishing36efbc02018-12-12 10:18:23 -0600564 try:
Michael Walsh2b269de2017-10-09 11:18:11 -0500565 ret_values[url_path][attr_name] = \
566 re.sub(r'.*\.', "",
567 ret_values[url_path][attr_name])
George Keishing36efbc02018-12-12 10:18:23 -0600568 except TypeError:
569 pass
Michael Walsh2b269de2017-10-09 11:18:11 -0500570 # Do some key name manipulations.
571 new_attr_name = re.sub(r'^Current|(State|Transition)$',
572 "", attr_name)
573 new_attr_name = re.sub(r'BMC', r'Bmc', new_attr_name)
574 new_attr_name = re.sub(r'([A-Z][a-z])', r'_\1',
575 new_attr_name)
576 new_attr_name = new_attr_name.lower().lstrip("_")
577 new_attr_name = re.sub(r'power', r'chassis', new_attr_name)
578 if new_attr_name in req_states:
579 state[new_attr_name] = ret_values[url_path][attr_name]
Michael Walshb95eb542017-03-31 09:39:20 -0500580
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600581 for sub_state in req_states:
Michael Walsh56749222017-09-29 15:26:07 -0500582 if sub_state in state:
583 continue
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600584 if sub_state.startswith("os_"):
585 # We pass "os_" requests on to get_os_state.
586 continue
587 cmd_buf = "state['" + sub_state + "'] = str(" + sub_state + ")"
588 exec(cmd_buf)
589
590 if os_host == "":
591 # The caller has not specified an os_host so as far as we're concerned,
592 # it doesn't exist.
593 return state
594
595 os_req_states = [sub_state for sub_state in req_states
596 if sub_state.startswith('os_')]
597
598 if len(os_req_states) > 0:
599 # The caller has specified an os_host and they have requested
600 # information on os substates.
601
602 # Based on the information gathered on bmc, we'll try to make a
603 # determination of whether the os is even up. We'll pass the result
604 # of that assessment to get_os_state to enhance performance.
605 os_up_match = DotDict()
606 for sub_state in master_os_up_match:
607 if sub_state in req_states:
608 os_up_match[sub_state] = master_os_up_match[sub_state]
Michael Walsh70369fd2016-11-22 11:25:57 -0600609 os_up = compare_states(state, os_up_match)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600610 os_state = get_os_state(os_host=os_host,
611 os_username=os_username,
612 os_password=os_password,
613 req_states=os_req_states,
614 os_up=os_up,
615 quiet=quiet)
616 # Append os_state dictionary to ours.
617 state.update(os_state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600618
619 return state
620
Michael Walsh70369fd2016-11-22 11:25:57 -0600621
Michael Walsh70369fd2016-11-22 11:25:57 -0600622def check_state(match_state,
623 invert=0,
624 print_string="",
625 openbmc_host="",
626 openbmc_username="",
627 openbmc_password="",
628 os_host="",
629 os_username="",
630 os_password="",
631 quiet=None):
Michael Walsh70369fd2016-11-22 11:25:57 -0600632 r"""
633 Check that the Open BMC machine's composite state matches the specified
634 state. On success, this keyword returns the machine's composite state as a
635 dictionary.
636
637 Description of arguments:
638 match_state A dictionary whose key/value pairs are "state field"/
639 "state value". The state value is interpreted as a
640 regular expression. Example call from robot:
Michael Walsh341c21e2017-01-17 16:25:20 -0600641 ${match_state}= Create Dictionary chassis=^On$
642 ... bmc=^Ready$
Michael Walsh01975fa2017-08-20 20:51:36 -0500643 ... boot_progress=^OSStart$
Michael Walsh70369fd2016-11-22 11:25:57 -0600644 ${state}= Check State &{match_state}
645 invert If this flag is set, this function will succeed if the
646 states do NOT match.
647 print_string This function will print this string to the console prior
648 to getting the state.
649 openbmc_host The DNS name or IP address of the BMC.
650 This defaults to global ${OPENBMC_HOST}.
651 openbmc_username The username to be used to login to the BMC.
652 This defaults to global ${OPENBMC_USERNAME}.
653 openbmc_password The password to be used to login to the BMC.
654 This defaults to global ${OPENBMC_PASSWORD}.
655 os_host The DNS name or IP address of the operating system.
656 This defaults to global ${OS_HOST}.
657 os_username The username to be used to login to the OS.
658 This defaults to global ${OS_USERNAME}.
659 os_password The password to be used to login to the OS.
660 This defaults to global ${OS_PASSWORD}.
661 quiet Indicates whether status details should be written to the
662 console. Defaults to either global value of ${QUIET} or
663 to 1.
664 """
665
Michael Walsh619aa332017-04-12 15:56:51 -0500666 quiet = int(gp.get_var_value(quiet, 0))
Michael Walsh70369fd2016-11-22 11:25:57 -0600667
668 grp.rprint(print_string)
669
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600670 req_states = match_state.keys()
Michael Walsh70369fd2016-11-22 11:25:57 -0600671 # Initialize state.
672 state = get_state(openbmc_host=openbmc_host,
673 openbmc_username=openbmc_username,
674 openbmc_password=openbmc_password,
675 os_host=os_host,
676 os_username=os_username,
677 os_password=os_password,
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600678 req_states=req_states,
Michael Walsh70369fd2016-11-22 11:25:57 -0600679 quiet=quiet)
680 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500681 gp.print_var(state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600682
683 match = compare_states(state, match_state)
684
685 if invert and match:
686 fail_msg = "The current state of the machine matches the match" +\
687 " state:\n" + gp.sprint_varx("state", state)
688 BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
689 elif not invert and not match:
690 fail_msg = "The current state of the machine does NOT match the" +\
691 " match state:\n" +\
692 gp.sprint_varx("state", state)
693 BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
694
695 return state
696
Michael Walsh70369fd2016-11-22 11:25:57 -0600697
Michael Walshf893ba02017-01-10 10:28:05 -0600698def wait_state(match_state=(),
Michael Walsh70369fd2016-11-22 11:25:57 -0600699 wait_time="1 min",
700 interval="1 second",
701 invert=0,
702 openbmc_host="",
703 openbmc_username="",
704 openbmc_password="",
705 os_host="",
706 os_username="",
707 os_password="",
708 quiet=None):
Michael Walsh70369fd2016-11-22 11:25:57 -0600709 r"""
710 Wait for the Open BMC machine's composite state to match the specified
711 state. On success, this keyword returns the machine's composite state as
712 a dictionary.
713
714 Description of arguments:
715 match_state A dictionary whose key/value pairs are "state field"/
716 "state value". See check_state (above) for details.
Michael Walsh619aa332017-04-12 15:56:51 -0500717 This value may also be any string accepted by
718 return_state_constant (e.g. "standby_match_state").
719 In such a case this function will call
720 return_state_constant to convert it to a proper
721 dictionary as described above.
Michael Walsh70369fd2016-11-22 11:25:57 -0600722 wait_time The total amount of time to wait for the desired state.
723 This value may be expressed in Robot Framework's time
724 format (e.g. 1 minute, 2 min 3 s, 4.5).
725 interval The amount of time between state checks.
726 This value may be expressed in Robot Framework's time
727 format (e.g. 1 minute, 2 min 3 s, 4.5).
728 invert If this flag is set, this function will for the state of
729 the machine to cease to match the match state.
730 openbmc_host The DNS name or IP address of the BMC.
731 This defaults to global ${OPENBMC_HOST}.
732 openbmc_username The username to be used to login to the BMC.
733 This defaults to global ${OPENBMC_USERNAME}.
734 openbmc_password The password to be used to login to the BMC.
735 This defaults to global ${OPENBMC_PASSWORD}.
736 os_host The DNS name or IP address of the operating system.
737 This defaults to global ${OS_HOST}.
738 os_username The username to be used to login to the OS.
739 This defaults to global ${OS_USERNAME}.
740 os_password The password to be used to login to the OS.
741 This defaults to global ${OS_PASSWORD}.
742 quiet Indicates whether status details should be written to the
743 console. Defaults to either global value of ${QUIET} or
744 to 1.
745 """
746
Michael Walsh619aa332017-04-12 15:56:51 -0500747 quiet = int(gp.get_var_value(quiet, 0))
748
George Keishing36efbc02018-12-12 10:18:23 -0600749 try:
Michael Walsh619aa332017-04-12 15:56:51 -0500750 match_state = return_state_constant(match_state)
George Keishing36efbc02018-12-12 10:18:23 -0600751 except TypeError:
752 pass
Michael Walsh70369fd2016-11-22 11:25:57 -0600753
754 if not quiet:
755 if invert:
756 alt_text = "cease to "
757 else:
758 alt_text = ""
Joy Onyerikwu004ad3c2018-06-11 16:29:56 -0500759 gp.print_timen("Checking every " + str(interval) + " for up to "
760 + str(wait_time) + " for the state of the machine to "
761 + alt_text + "match the state shown below.")
Michael Walsh3eb50022017-03-21 11:27:30 -0500762 gp.print_var(match_state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600763
Michael Walshf893ba02017-01-10 10:28:05 -0600764 if quiet:
Michael Walsh341c21e2017-01-17 16:25:20 -0600765 print_string = ""
Michael Walshf893ba02017-01-10 10:28:05 -0600766 else:
Michael Walsh341c21e2017-01-17 16:25:20 -0600767 print_string = "#"
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600768
769 debug = int(BuiltIn().get_variable_value("${debug}", "0"))
770 if debug:
771 # In debug we print state so no need to print the "#".
772 print_string = ""
773 check_state_quiet = 1 - debug
Michael Walsh70369fd2016-11-22 11:25:57 -0600774 cmd_buf = ["Check State", match_state, "invert=${" + str(invert) + "}",
Michael Walshf893ba02017-01-10 10:28:05 -0600775 "print_string=" + print_string, "openbmc_host=" + openbmc_host,
Michael Walsh70369fd2016-11-22 11:25:57 -0600776 "openbmc_username=" + openbmc_username,
777 "openbmc_password=" + openbmc_password, "os_host=" + os_host,
778 "os_username=" + os_username, "os_password=" + os_password,
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600779 "quiet=${" + str(check_state_quiet) + "}"]
Michael Walsh70369fd2016-11-22 11:25:57 -0600780 grp.rdpissuing_keyword(cmd_buf)
Michael Walsh619aa332017-04-12 15:56:51 -0500781 try:
782 state = BuiltIn().wait_until_keyword_succeeds(wait_time, interval,
783 *cmd_buf)
784 except AssertionError as my_assertion_error:
785 gp.printn()
786 message = my_assertion_error.args[0]
787 BuiltIn().fail(message)
788
Michael Walsh70369fd2016-11-22 11:25:57 -0600789 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500790 gp.printn()
Michael Walsh70369fd2016-11-22 11:25:57 -0600791 if invert:
Michael Walsh3eb50022017-03-21 11:27:30 -0500792 gp.print_timen("The states no longer match:")
Michael Walsh70369fd2016-11-22 11:25:57 -0600793 else:
Michael Walsh3eb50022017-03-21 11:27:30 -0500794 gp.print_timen("The states match:")
795 gp.print_var(state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600796
797 return state
798
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600799
Michael Walsh619aa332017-04-12 15:56:51 -0500800def wait_for_comm_cycle(start_boot_seconds,
801 quiet=None):
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600802 r"""
803 Wait for communications to the BMC to stop working and then resume working.
804 This function is useful when you have initiated some kind of reboot.
805
806 Description of arguments:
807 start_boot_seconds The time that the boot test started. The format is the
808 epoch time in seconds, i.e. the number of seconds since
809 1970-01-01 00:00:00 UTC. This value should be obtained
810 from the BMC so that it is not dependent on any kind of
811 synchronization between this machine and the target BMC
812 This will allow this program to work correctly even in
813 a simulated environment. This value should be obtained
814 by the caller prior to initiating a reboot. It can be
815 obtained as follows:
816 state = st.get_state(req_states=['epoch_seconds'])
817 """
818
Michael Walsh619aa332017-04-12 15:56:51 -0500819 quiet = int(gp.get_var_value(quiet, 0))
820
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600821 # Validate parms.
822 error_message = gv.svalid_integer(start_boot_seconds,
823 var_name="start_boot_seconds")
824 if error_message != "":
825 BuiltIn().fail(gp.sprint_error(error_message))
826
827 match_state = anchor_state(DotDict([('packet_loss', '100')]))
828 # Wait for 100% packet loss trying to ping machine.
Michael Walsh619aa332017-04-12 15:56:51 -0500829 wait_state(match_state, wait_time="8 mins", interval="0 seconds")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600830
831 match_state['packet_loss'] = '^0$'
832 # Wait for 0% packet loss trying to ping machine.
Michael Walsh619aa332017-04-12 15:56:51 -0500833 wait_state(match_state, wait_time="8 mins", interval="0 seconds")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600834
835 # Get the uptime and epoch seconds for comparisons. We want to be sure
836 # that the uptime is less than the elapsed boot time. Further proof that
837 # a reboot has indeed occurred (vs random network instability giving a
Michael Walshc4c05d32018-05-29 11:39:39 -0500838 # false positive. We also use wait_state because the BMC may take a short
839 # while to be ready to process SSH requests.
Joy Onyerikwu004ad3c2018-06-11 16:29:56 -0500840 match_state = DotDict([('uptime', '^[0-9\\.]+$'),
Michael Walshc4c05d32018-05-29 11:39:39 -0500841 ('epoch_seconds', '^[0-9]+$')])
842 state = wait_state(match_state, wait_time="2 mins", interval="1 second")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600843
844 elapsed_boot_time = int(state['epoch_seconds']) - start_boot_seconds
Michael Walsh619aa332017-04-12 15:56:51 -0500845 gp.qprint_var(elapsed_boot_time)
Michael Walshe77585a2017-12-14 11:02:28 -0600846 if state['uptime'] == "":
847 error_message = "Unable to obtain uptime from the BMC. BMC is not" +\
848 " communicating."
849 BuiltIn().fail(gp.sprint_error(error_message))
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600850 if int(float(state['uptime'])) < elapsed_boot_time:
851 uptime = state['uptime']
Michael Walsh619aa332017-04-12 15:56:51 -0500852 gp.qprint_var(uptime)
Joy Onyerikwu004ad3c2018-06-11 16:29:56 -0500853 gp.qprint_timen("The uptime is less than the elapsed boot time,"
854 + " as expected.")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600855 else:
856 error_message = "The uptime is greater than the elapsed boot time," +\
857 " which is unexpected:\n" +\
858 gp.sprint_var(start_boot_seconds) +\
859 gp.sprint_var(state)
860 BuiltIn().fail(gp.sprint_error(error_message))
861
Michael Walsh619aa332017-04-12 15:56:51 -0500862 gp.qprint_timen("Verifying that REST API interface is working.")
Michael Walshb95eb542017-03-31 09:39:20 -0500863 match_state = DotDict([('rest', '^1$')])
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600864 state = wait_state(match_state, wait_time="5 mins", interval="2 seconds")