blob: 0aae066016c1622b3f97ddab68c6508485a88bf8 [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
37import commands
38from robot.libraries.BuiltIn import BuiltIn
Michael Walsh341c21e2017-01-17 16:25:20 -060039from robot.utils import DotDict
Michael Walsh70369fd2016-11-22 11:25:57 -060040
41import re
Michael Walsh341c21e2017-01-17 16:25:20 -060042import os
Michael Walsh56749222017-09-29 15:26:07 -050043import sys
44import imp
45
Michael Walsh70369fd2016-11-22 11:25:57 -060046
Michael Walsh5a5868a2018-10-31 15:20:04 -050047# NOTE: Avoid importing utils.robot because utils.robot imports state.py
48# (indirectly) which will cause failures.
49gru.my_import_resource("rest_client.robot")
Michael Walsh70369fd2016-11-22 11:25:57 -060050
Michael Walsh56749222017-09-29 15:26:07 -050051base_path = os.path.dirname(os.path.dirname(
52 imp.find_module("gen_robot_print")[1])) + os.sep
53sys.path.append(base_path + "data/")
Michael Walsh56749222017-09-29 15:26:07 -050054
Michael Walsh940d6912017-10-27 12:32:33 -050055# Previously, I had this coded:
56# import variables as var
57# However, we ran into a problem where a robot program did this...
58# Variables ../../lib/ras/variables.py
59# Prior to doing this...
60# Library ../lib/state.py
61
62# This caused the wrong variables.py file to be selected. Attempts to fix this
63# have failed so far. For the moment, we will hard-code the value we need from
64# the file.
65
66SYSTEM_STATE_URI = "/xyz/openbmc_project/state/"
Michael Walsh56749222017-09-29 15:26:07 -050067
Michael Walsh8fae6ea2017-02-20 16:14:44 -060068# The BMC code has recently been changed as far as what states are defined and
69# what the state values can be. This module now has a means of processing both
70# the old style state (i.e. OBMC_STATES_VERSION = 0) and the new style (i.e.
Michael Walsh16cbb7f2017-02-02 15:54:16 -060071# OBMC_STATES_VERSION = 1).
Michael Walsh341c21e2017-01-17 16:25:20 -060072# The caller can set environment variable OBMC_STATES_VERSION to dictate
73# whether we're processing old or new style states. If OBMC_STATES_VERSION is
Michael Walsh8fae6ea2017-02-20 16:14:44 -060074# not set it will default to 1.
Michael Walsh341c21e2017-01-17 16:25:20 -060075
Michael Walsh619aa332017-04-12 15:56:51 -050076# As of the present moment, OBMC_STATES_VERSION of 0 is for cold that is so old
77# that it is no longer worthwhile to maintain. The OBMC_STATES_VERSION 0 code
78# is being removed but the OBMC_STATES_VERSION value will stay for now in the
79# event that it is needed in the future.
80
Michael Walsh8fae6ea2017-02-20 16:14:44 -060081OBMC_STATES_VERSION = int(os.environ.get('OBMC_STATES_VERSION', 1))
Michael Walsh341c21e2017-01-17 16:25:20 -060082
Michael Walsh619aa332017-04-12 15:56:51 -050083# When a user calls get_state w/o specifying req_states, default_req_states
84# is used as its value.
85default_req_states = ['rest',
86 'chassis',
87 'bmc',
88 'boot_progress',
Michael Walsh56749222017-09-29 15:26:07 -050089 'operating_system',
Michael Walsh619aa332017-04-12 15:56:51 -050090 'host',
91 'os_ping',
92 'os_login',
93 'os_run_cmd']
Michael Walsh8fae6ea2017-02-20 16:14:44 -060094
Michael Walsh619aa332017-04-12 15:56:51 -050095# valid_req_states is a list of sub states supported by the get_state function.
96# valid_req_states, default_req_states and master_os_up_match are used by the
97# get_state function.
98valid_req_states = ['ping',
99 'packet_loss',
100 'uptime',
101 'epoch_seconds',
102 'rest',
103 'chassis',
Michael Walsh56749222017-09-29 15:26:07 -0500104 'requested_chassis',
Michael Walsh619aa332017-04-12 15:56:51 -0500105 'bmc',
Michael Walsh56749222017-09-29 15:26:07 -0500106 'requested_bmc',
Michael Walsh619aa332017-04-12 15:56:51 -0500107 'boot_progress',
Michael Walsh56749222017-09-29 15:26:07 -0500108 'operating_system',
Michael Walsh619aa332017-04-12 15:56:51 -0500109 'host',
Michael Walsh56749222017-09-29 15:26:07 -0500110 'requested_host',
111 'attempts_left',
Michael Walsh619aa332017-04-12 15:56:51 -0500112 'os_ping',
113 'os_login',
114 'os_run_cmd']
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600115
116# valid_os_req_states and default_os_req_states are used by the os_get_state
117# function.
118# valid_os_req_states is a list of state information supported by the
119# get_os_state function.
120valid_os_req_states = ['os_ping',
121 'os_login',
122 'os_run_cmd']
123# When a user calls get_os_state w/o specifying req_states,
124# default_os_req_states is used as its value.
125default_os_req_states = ['os_ping',
126 'os_login',
127 'os_run_cmd']
128
129# Presently, some BMCs appear to not keep time very well. This environment
130# variable directs the get_state function to use either the BMC's epoch time
131# or the local epoch time.
132USE_BMC_EPOCH_TIME = int(os.environ.get('USE_BMC_EPOCH_TIME', 0))
Michael Walsh341c21e2017-01-17 16:25:20 -0600133
Michael Walsh619aa332017-04-12 15:56:51 -0500134# Useful state constant definition(s).
Michael Walsh619aa332017-04-12 15:56:51 -0500135# default_state is an initial value which may be of use to callers.
136default_state = DotDict([('rest', '1'),
137 ('chassis', 'On'),
138 ('bmc', 'Ready'),
Michael Walsh01975fa2017-08-20 20:51:36 -0500139 ('boot_progress', 'OSStart'),
Michael Walsh56749222017-09-29 15:26:07 -0500140 ('operating_system', 'BootComplete'),
Michael Walsh619aa332017-04-12 15:56:51 -0500141 ('host', 'Running'),
142 ('os_ping', '1'),
143 ('os_login', '1'),
144 ('os_run_cmd', '1')])
145
Michael Walsh7dc885b2018-03-14 17:51:59 -0500146# A match state for checking that the system is at "standby".
147standby_match_state = DotDict([('rest', '^1$'),
148 ('chassis', '^Off$'),
149 ('bmc', '^Ready$'),
150 ('boot_progress', '^$'),
151 ('operating_system', '^$'),
152 ('host', '^$')])
153
154# A match state for checking that the system is at "os running".
155os_running_match_state = DotDict([('chassis', '^On$'),
156 ('bmc', '^Ready$'),
157 ('boot_progress',
158 'FW Progress, Starting OS|OSStart'),
159 ('operating_system', 'BootComplete'),
160 ('host', '^Running$'),
161 ('os_ping', '^1$'),
162 ('os_login', '^1$'),
163 ('os_run_cmd', '^1$')])
164
Michael Walsh619aa332017-04-12 15:56:51 -0500165# A master dictionary to determine whether the os may be up.
166master_os_up_match = DotDict([('chassis', '^On$'),
167 ('bmc', '^Ready$'),
168 ('boot_progress',
Michael Walsh01975fa2017-08-20 20:51:36 -0500169 'FW Progress, Starting OS|OSStart'),
Michael Walsh56749222017-09-29 15:26:07 -0500170 ('operating_system', 'BootComplete'),
Michael Walsh192d5e72018-11-01 14:09:11 -0500171 ('host', '^Running|Quiesced$')])
Michael Walsh619aa332017-04-12 15:56:51 -0500172
Michael Walsh45ca6e42017-09-14 17:29:12 -0500173invalid_state_match = DotDict([('rest', '^$'),
174 ('chassis', '^$'),
175 ('bmc', '^$'),
176 ('boot_progress', '^$'),
Michael Walsh56749222017-09-29 15:26:07 -0500177 ('operating_system', '^$'),
Michael Walsh45ca6e42017-09-14 17:29:12 -0500178 ('host', '^$')])
179
Michael Walsh341c21e2017-01-17 16:25:20 -0600180
Michael Walsh619aa332017-04-12 15:56:51 -0500181def return_state_constant(state_name='default'):
Michael Walsh619aa332017-04-12 15:56:51 -0500182 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):
Michael Walsh70369fd2016-11-22 11:25:57 -0600192 r"""
193 Add regular expression anchors ("^" and "$") to the beginning and end of
194 each item in the state dictionary passed in. Return the resulting
195 dictionary.
196
197 Description of Arguments:
198 state A dictionary such as the one returned by the get_state()
199 function.
200 """
201
Michael Walsh2ce067a2017-02-27 14:24:07 -0600202 anchored_state = state.copy()
Michael Walsh70369fd2016-11-22 11:25:57 -0600203 for key, match_state_value in anchored_state.items():
204 anchored_state[key] = "^" + str(anchored_state[key]) + "$"
205
206 return anchored_state
207
Michael Walsh70369fd2016-11-22 11:25:57 -0600208
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600209def strip_anchor_state(state):
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600210 r"""
211 Strip regular expression anchors ("^" and "$") from the beginning and end
212 of each item in the state dictionary passed in. Return the resulting
213 dictionary.
214
215 Description of Arguments:
216 state A dictionary such as the one returned by the get_state()
217 function.
218 """
219
Michael Walsh2ce067a2017-02-27 14:24:07 -0600220 stripped_state = state.copy()
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600221 for key, match_state_value in stripped_state.items():
222 stripped_state[key] = stripped_state[key].strip("^$")
223
224 return stripped_state
225
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600226
Michael Walsh70369fd2016-11-22 11:25:57 -0600227def compare_states(state,
Michael Walsh45ca6e42017-09-14 17:29:12 -0500228 match_state,
229 match_type='and'):
Michael Walsh70369fd2016-11-22 11:25:57 -0600230 r"""
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600231 Compare 2 state dictionaries. Return True if they match and False if they
Michael Walsh70369fd2016-11-22 11:25:57 -0600232 don't. Note that the match_state dictionary does not need to have an entry
233 corresponding to each entry in the state dictionary. But for each entry
234 that it does have, the corresponding state entry will be checked for a
235 match.
236
237 Description of arguments:
238 state A state dictionary such as the one returned by the
239 get_state function.
240 match_state A dictionary whose key/value pairs are "state field"/
241 "state value". The state value is interpreted as a
242 regular expression. Every value in this dictionary is
Michael Walsh45ca6e42017-09-14 17:29:12 -0500243 considered. When match_type is 'and', if each and every
244 comparison matches, the two dictionaries are considered to
245 be matching. If match_type is 'or', if any two of the
246 elements compared match, the two dictionaries are
247 considered to be matching.
Michael Walsh7dc885b2018-03-14 17:51:59 -0500248 This value may also be any string accepted by
249 return_state_constant (e.g. "standby_match_state").
250 In such a case this function will call
251 return_state_constant to convert it to a proper
252 dictionary as described above.
Michael Walsh45ca6e42017-09-14 17:29:12 -0500253 match_type This may be 'and' or 'or'.
Michael Walsh70369fd2016-11-22 11:25:57 -0600254 """
255
Michael Walsh45ca6e42017-09-14 17:29:12 -0500256 error_message = gv.svalid_value(match_type, var_name="match_type",
257 valid_values=['and', 'or'])
258 if error_message != "":
259 BuiltIn().fail(gp.sprint_error(error_message))
260
Michael Walsh7dc885b2018-03-14 17:51:59 -0500261 if type(match_state) in (str, unicode):
262 match_state = return_state_constant(match_state)
263
Michael Walsh45ca6e42017-09-14 17:29:12 -0500264 default_match = (match_type == 'and')
Michael Walsh70369fd2016-11-22 11:25:57 -0600265 for key, match_state_value in match_state.items():
Michael Walsh97df71c2017-03-27 14:33:24 -0500266 # Blank match_state_value means "don't care".
267 if match_state_value == "":
268 continue
Michael Walsh70369fd2016-11-22 11:25:57 -0600269 try:
Michael Walsh45ca6e42017-09-14 17:29:12 -0500270 match = (re.match(match_state_value, str(state[key])) is not None)
Michael Walsh70369fd2016-11-22 11:25:57 -0600271 except KeyError:
272 match = False
Michael Walsh70369fd2016-11-22 11:25:57 -0600273
Michael Walsh45ca6e42017-09-14 17:29:12 -0500274 if match != default_match:
275 return match
276
277 return default_match
Michael Walsh70369fd2016-11-22 11:25:57 -0600278
Michael Walsh70369fd2016-11-22 11:25:57 -0600279
Michael Walsh70369fd2016-11-22 11:25:57 -0600280def get_os_state(os_host="",
281 os_username="",
282 os_password="",
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600283 req_states=default_os_req_states,
284 os_up=True,
Michael Walsh70369fd2016-11-22 11:25:57 -0600285 quiet=None):
Michael Walsh70369fd2016-11-22 11:25:57 -0600286 r"""
287 Get component states for the operating system such as ping, login,
288 etc, put them into a dictionary and return them to the caller.
289
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600290 Note that all substate values are strings.
291
Michael Walsh70369fd2016-11-22 11:25:57 -0600292 Description of arguments:
293 os_host The DNS name or IP address of the operating system.
294 This defaults to global ${OS_HOST}.
295 os_username The username to be used to login to the OS.
296 This defaults to global ${OS_USERNAME}.
297 os_password The password to be used to login to the OS.
298 This defaults to global ${OS_PASSWORD}.
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600299 req_states This is a list of states whose values are being requested by
300 the caller.
301 os_up If the caller knows that the os can't possibly be up, it can
302 improve performance by passing os_up=False. This function
303 will then simply return default values for all requested os
304 sub states.
Michael Walsh70369fd2016-11-22 11:25:57 -0600305 quiet Indicates whether status details (e.g. curl commands) should
306 be written to the console.
307 Defaults to either global value of ${QUIET} or to 1.
308 """
309
Michael Walsh619aa332017-04-12 15:56:51 -0500310 quiet = int(gp.get_var_value(quiet, 0))
Michael Walsh70369fd2016-11-22 11:25:57 -0600311
312 # Set parm defaults where necessary and validate all parms.
313 if os_host == "":
314 os_host = BuiltIn().get_variable_value("${OS_HOST}")
315 error_message = gv.svalid_value(os_host, var_name="os_host",
316 invalid_values=[None, ""])
317 if error_message != "":
318 BuiltIn().fail(gp.sprint_error(error_message))
319
320 if os_username == "":
321 os_username = BuiltIn().get_variable_value("${OS_USERNAME}")
322 error_message = gv.svalid_value(os_username, var_name="os_username",
323 invalid_values=[None, ""])
324 if error_message != "":
325 BuiltIn().fail(gp.sprint_error(error_message))
326
327 if os_password == "":
328 os_password = BuiltIn().get_variable_value("${OS_PASSWORD}")
329 error_message = gv.svalid_value(os_password, var_name="os_password",
330 invalid_values=[None, ""])
331 if error_message != "":
332 BuiltIn().fail(gp.sprint_error(error_message))
333
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600334 invalid_req_states = [sub_state for sub_state in req_states
335 if sub_state not in valid_os_req_states]
336 if len(invalid_req_states) > 0:
337 error_message = "The following req_states are not supported:\n" +\
338 gp.sprint_var(invalid_req_states)
339 BuiltIn().fail(gp.sprint_error(error_message))
Michael Walsh70369fd2016-11-22 11:25:57 -0600340
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600341 # Initialize all substate values supported by this function.
342 os_ping = 0
343 os_login = 0
344 os_run_cmd = 0
Michael Walsh70369fd2016-11-22 11:25:57 -0600345
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600346 if os_up:
347 if 'os_ping' in req_states:
348 # See if the OS pings.
349 cmd_buf = "ping -c 1 -w 2 " + os_host
350 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500351 gp.pissuing(cmd_buf)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600352 rc, out_buf = commands.getstatusoutput(cmd_buf)
353 if rc == 0:
354 os_ping = 1
Michael Walsh70369fd2016-11-22 11:25:57 -0600355
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600356 # Programming note: All attributes which do not require an ssh login
357 # should have been processed by this point.
358 master_req_login = ['os_login', 'os_run_cmd']
359 req_login = [sub_state for sub_state in req_states if sub_state in
360 master_req_login]
Michael Walsh97df71c2017-03-27 14:33:24 -0500361 must_login = (len(req_login) > 0)
Michael Walsh70369fd2016-11-22 11:25:57 -0600362
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600363 if must_login:
Michael Walsh6a9bd142018-07-24 16:10:29 -0500364 output, stderr, rc = bsu.os_execute_command("uptime", quiet=quiet,
Michael Walsh7fc33972018-08-07 14:55:03 -0500365 ignore_err=1,
366 time_out=20)
Michael Walsh6a9bd142018-07-24 16:10:29 -0500367 if rc == 0:
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600368 os_login = 1
Michael Walsh6a9bd142018-07-24 16:10:29 -0500369 os_run_cmd = 1
Michael Walsh3eb50022017-03-21 11:27:30 -0500370 else:
Michael Walsh6a9bd142018-07-24 16:10:29 -0500371 gp.dprint_vars(output, stderr)
372 gp.dprint_vars(rc, 1)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600373
374 os_state = DotDict()
375 for sub_state in req_states:
376 cmd_buf = "os_state['" + sub_state + "'] = str(" + sub_state + ")"
377 exec(cmd_buf)
Michael Walsh70369fd2016-11-22 11:25:57 -0600378
379 return os_state
380
Michael Walsh70369fd2016-11-22 11:25:57 -0600381
Michael Walsh70369fd2016-11-22 11:25:57 -0600382def get_state(openbmc_host="",
383 openbmc_username="",
384 openbmc_password="",
385 os_host="",
386 os_username="",
387 os_password="",
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600388 req_states=default_req_states,
Michael Walsh70369fd2016-11-22 11:25:57 -0600389 quiet=None):
Michael Walsh70369fd2016-11-22 11:25:57 -0600390 r"""
Michael Walsh619aa332017-04-12 15:56:51 -0500391 Get component states such as chassis state, bmc state, etc, put them into a
Michael Walsh70369fd2016-11-22 11:25:57 -0600392 dictionary and return them to the caller.
393
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600394 Note that all substate values are strings.
395
Michael Walsh70369fd2016-11-22 11:25:57 -0600396 Description of arguments:
397 openbmc_host The DNS name or IP address of the BMC.
398 This defaults to global ${OPENBMC_HOST}.
399 openbmc_username The username to be used to login to the BMC.
400 This defaults to global ${OPENBMC_USERNAME}.
401 openbmc_password The password to be used to login to the BMC.
402 This defaults to global ${OPENBMC_PASSWORD}.
403 os_host The DNS name or IP address of the operating system.
404 This defaults to global ${OS_HOST}.
405 os_username The username to be used to login to the OS.
406 This defaults to global ${OS_USERNAME}.
407 os_password The password to be used to login to the OS.
408 This defaults to global ${OS_PASSWORD}.
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600409 req_states This is a list of states whose values are being requested
410 by the caller.
Michael Walsh70369fd2016-11-22 11:25:57 -0600411 quiet Indicates whether status details (e.g. curl commands)
412 should be written to the console.
413 Defaults to either global value of ${QUIET} or to 1.
414 """
415
Michael Walsh619aa332017-04-12 15:56:51 -0500416 quiet = int(gp.get_var_value(quiet, 0))
Michael Walsh70369fd2016-11-22 11:25:57 -0600417
418 # Set parm defaults where necessary and validate all parms.
419 if openbmc_host == "":
420 openbmc_host = BuiltIn().get_variable_value("${OPENBMC_HOST}")
421 error_message = gv.svalid_value(openbmc_host,
422 var_name="openbmc_host",
423 invalid_values=[None, ""])
424 if error_message != "":
425 BuiltIn().fail(gp.sprint_error(error_message))
426
427 if openbmc_username == "":
428 openbmc_username = BuiltIn().get_variable_value("${OPENBMC_USERNAME}")
429 error_message = gv.svalid_value(openbmc_username,
430 var_name="openbmc_username",
431 invalid_values=[None, ""])
432 if error_message != "":
433 BuiltIn().fail(gp.sprint_error(error_message))
434
435 if openbmc_password == "":
436 openbmc_password = BuiltIn().get_variable_value("${OPENBMC_PASSWORD}")
437 error_message = gv.svalid_value(openbmc_password,
438 var_name="openbmc_password",
439 invalid_values=[None, ""])
440 if error_message != "":
441 BuiltIn().fail(gp.sprint_error(error_message))
442
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600443 # NOTE: OS parms are optional.
Michael Walsh70369fd2016-11-22 11:25:57 -0600444 if os_host == "":
445 os_host = BuiltIn().get_variable_value("${OS_HOST}")
446 if os_host is None:
447 os_host = ""
448
449 if os_username is "":
450 os_username = BuiltIn().get_variable_value("${OS_USERNAME}")
451 if os_username is None:
452 os_username = ""
453
454 if os_password is "":
455 os_password = BuiltIn().get_variable_value("${OS_PASSWORD}")
456 if os_password is None:
457 os_password = ""
458
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600459 invalid_req_states = [sub_state for sub_state in req_states
460 if sub_state not in valid_req_states]
461 if len(invalid_req_states) > 0:
462 error_message = "The following req_states are not supported:\n" +\
463 gp.sprint_var(invalid_req_states)
464 BuiltIn().fail(gp.sprint_error(error_message))
465
466 # Initialize all substate values supported by this function.
467 ping = 0
468 packet_loss = ''
469 uptime = ''
470 epoch_seconds = ''
Michael Walsh2b269de2017-10-09 11:18:11 -0500471 rest = ''
472 chassis = ''
473 requested_chassis = ''
474 bmc = ''
475 requested_bmc = ''
476 boot_progress = ''
477 operating_system = ''
478 host = ''
479 requested_host = ''
480 attempts_left = ''
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600481
Michael Walsh70369fd2016-11-22 11:25:57 -0600482 # Get the component states.
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600483 if 'ping' in req_states:
484 # See if the OS pings.
485 cmd_buf = "ping -c 1 -w 2 " + openbmc_host
486 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500487 gp.pissuing(cmd_buf)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600488 rc, out_buf = commands.getstatusoutput(cmd_buf)
489 if rc == 0:
490 ping = 1
491
492 if 'packet_loss' in req_states:
493 # See if the OS pings.
494 cmd_buf = "ping -c 5 -w 5 " + openbmc_host +\
495 " | egrep 'packet loss' | sed -re 's/.* ([0-9]+)%.*/\\1/g'"
496 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500497 gp.pissuing(cmd_buf)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600498 rc, out_buf = commands.getstatusoutput(cmd_buf)
499 if rc == 0:
500 packet_loss = out_buf.rstrip("\n")
501
Michael Walshe53e47a2017-06-30 17:03:24 -0500502 if 'uptime' in req_states:
Michael Walshfa765932017-10-13 14:07:22 -0500503 # Sometimes reading uptime results in a blank value. Call with
504 # wait_until_keyword_succeeds to ensure a non-blank value is obtained.
505 remote_cmd_buf = "read uptime filler 2>/dev/null < /proc/uptime" +\
506 " && [ ! -z \"${uptime}\" ] && echo ${uptime}"
Joy Onyerikwu004ad3c2018-06-11 16:29:56 -0500507 cmd_buf = ["BMC Execute Command",
Michael Walsh8c34eb72018-06-14 11:26:16 -0500508 re.sub('\\$', '\\$', remote_cmd_buf), 'quiet=1']
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600509 if not quiet:
Michael Walshaf722b12018-08-17 14:55:32 -0500510 # Get loc_test_mode parm for improved output on pissuing.
511 # See sprint_issuing in gen_print.py for details.
512 loc_test_mode = int(gp.get_var_value(var_name="test_mode",
513 default=0))
514 grp.rpissuing_keyword(cmd_buf, loc_test_mode)
515 gp.pissuing(remote_cmd_buf, loc_test_mode)
Michael Walshfa765932017-10-13 14:07:22 -0500516 try:
517 stdout, stderr, rc =\
Michael Walshcaccd852017-11-01 17:58:41 -0500518 BuiltIn().wait_until_keyword_succeeds("10 sec", "0 sec",
Michael Walshfa765932017-10-13 14:07:22 -0500519 *cmd_buf)
Michael Walsh97df71c2017-03-27 14:33:24 -0500520 if rc == 0 and stderr == "":
521 uptime = stdout
Michael Walshfa765932017-10-13 14:07:22 -0500522 except AssertionError as my_assertion_error:
523 pass
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600524
Michael Walshe53e47a2017-06-30 17:03:24 -0500525 if 'epoch_seconds' in req_states:
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600526 date_cmd_buf = "date -u +%s"
527 if USE_BMC_EPOCH_TIME:
Michael Walshe53e47a2017-06-30 17:03:24 -0500528 cmd_buf = ["BMC Execute Command", date_cmd_buf, 'quiet=${1}']
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600529 if not quiet:
530 grp.rpissuing_keyword(cmd_buf)
Michael Walsh97df71c2017-03-27 14:33:24 -0500531 status, ret_values = \
532 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
533 if status == "PASS":
534 stdout, stderr, rc = ret_values
535 if rc == 0 and stderr == "":
536 epoch_seconds = stdout.rstrip("\n")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600537 else:
538 shell_rc, out_buf = gc.cmd_fnc_u(date_cmd_buf,
Michael Walshfa765932017-10-13 14:07:22 -0500539 quiet=quiet,
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600540 print_output=0)
541 if shell_rc == 0:
542 epoch_seconds = out_buf.rstrip("\n")
543
Michael Walsh56749222017-09-29 15:26:07 -0500544 master_req_rest = ['rest', 'host', 'requested_host', 'operating_system',
545 'attempts_left', 'boot_progress', 'chassis',
546 'requested_chassis' 'bmc' 'requested_bmc']
547
Michael Walshb95eb542017-03-31 09:39:20 -0500548 req_rest = [sub_state for sub_state in req_states if sub_state in
549 master_req_rest]
550 need_rest = (len(req_rest) > 0)
Michael Walsh56749222017-09-29 15:26:07 -0500551 state = DotDict()
552 if need_rest:
Michael Walsh940d6912017-10-27 12:32:33 -0500553 cmd_buf = ["Read Properties", SYSTEM_STATE_URI + "enumerate",
Michael Walsh56749222017-09-29 15:26:07 -0500554 "quiet=${" + str(quiet) + "}"]
Michael Walsh341c21e2017-01-17 16:25:20 -0600555 grp.rdpissuing_keyword(cmd_buf)
Michael Walsh97df71c2017-03-27 14:33:24 -0500556 status, ret_values = \
557 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
558 if status == "PASS":
Michael Walsh56749222017-09-29 15:26:07 -0500559 state['rest'] = '1'
Michael Walsh341c21e2017-01-17 16:25:20 -0600560 else:
Michael Walsh56749222017-09-29 15:26:07 -0500561 state['rest'] = '0'
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600562
Michael Walsh2b269de2017-10-09 11:18:11 -0500563 if int(state['rest']):
564 for url_path in ret_values:
565 for attr_name in ret_values[url_path]:
566 # Create a state key value based on the attr_name.
Joy Onyerikwu004ad3c2018-06-11 16:29:56 -0500567 if isinstance(ret_values[url_path][attr_name], unicode):
Michael Walsh2b269de2017-10-09 11:18:11 -0500568 ret_values[url_path][attr_name] = \
569 re.sub(r'.*\.', "",
570 ret_values[url_path][attr_name])
571 # Do some key name manipulations.
572 new_attr_name = re.sub(r'^Current|(State|Transition)$',
573 "", attr_name)
574 new_attr_name = re.sub(r'BMC', r'Bmc', new_attr_name)
575 new_attr_name = re.sub(r'([A-Z][a-z])', r'_\1',
576 new_attr_name)
577 new_attr_name = new_attr_name.lower().lstrip("_")
578 new_attr_name = re.sub(r'power', r'chassis', new_attr_name)
579 if new_attr_name in req_states:
580 state[new_attr_name] = ret_values[url_path][attr_name]
Michael Walshb95eb542017-03-31 09:39:20 -0500581
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600582 for sub_state in req_states:
Michael Walsh56749222017-09-29 15:26:07 -0500583 if sub_state in state:
584 continue
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600585 if sub_state.startswith("os_"):
586 # We pass "os_" requests on to get_os_state.
587 continue
588 cmd_buf = "state['" + sub_state + "'] = str(" + sub_state + ")"
589 exec(cmd_buf)
590
591 if os_host == "":
592 # The caller has not specified an os_host so as far as we're concerned,
593 # it doesn't exist.
594 return state
595
596 os_req_states = [sub_state for sub_state in req_states
597 if sub_state.startswith('os_')]
598
599 if len(os_req_states) > 0:
600 # The caller has specified an os_host and they have requested
601 # information on os substates.
602
603 # Based on the information gathered on bmc, we'll try to make a
604 # determination of whether the os is even up. We'll pass the result
605 # of that assessment to get_os_state to enhance performance.
606 os_up_match = DotDict()
607 for sub_state in master_os_up_match:
608 if sub_state in req_states:
609 os_up_match[sub_state] = master_os_up_match[sub_state]
Michael Walsh70369fd2016-11-22 11:25:57 -0600610 os_up = compare_states(state, os_up_match)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600611 os_state = get_os_state(os_host=os_host,
612 os_username=os_username,
613 os_password=os_password,
614 req_states=os_req_states,
615 os_up=os_up,
616 quiet=quiet)
617 # Append os_state dictionary to ours.
618 state.update(os_state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600619
620 return state
621
Michael Walsh70369fd2016-11-22 11:25:57 -0600622
Michael Walsh70369fd2016-11-22 11:25:57 -0600623def check_state(match_state,
624 invert=0,
625 print_string="",
626 openbmc_host="",
627 openbmc_username="",
628 openbmc_password="",
629 os_host="",
630 os_username="",
631 os_password="",
632 quiet=None):
Michael Walsh70369fd2016-11-22 11:25:57 -0600633 r"""
634 Check that the Open BMC machine's composite state matches the specified
635 state. On success, this keyword returns the machine's composite state as a
636 dictionary.
637
638 Description of arguments:
639 match_state A dictionary whose key/value pairs are "state field"/
640 "state value". The state value is interpreted as a
641 regular expression. Example call from robot:
Michael Walsh341c21e2017-01-17 16:25:20 -0600642 ${match_state}= Create Dictionary chassis=^On$
643 ... bmc=^Ready$
Michael Walsh01975fa2017-08-20 20:51:36 -0500644 ... boot_progress=^OSStart$
Michael Walsh70369fd2016-11-22 11:25:57 -0600645 ${state}= Check State &{match_state}
646 invert If this flag is set, this function will succeed if the
647 states do NOT match.
648 print_string This function will print this string to the console prior
649 to getting the state.
650 openbmc_host The DNS name or IP address of the BMC.
651 This defaults to global ${OPENBMC_HOST}.
652 openbmc_username The username to be used to login to the BMC.
653 This defaults to global ${OPENBMC_USERNAME}.
654 openbmc_password The password to be used to login to the BMC.
655 This defaults to global ${OPENBMC_PASSWORD}.
656 os_host The DNS name or IP address of the operating system.
657 This defaults to global ${OS_HOST}.
658 os_username The username to be used to login to the OS.
659 This defaults to global ${OS_USERNAME}.
660 os_password The password to be used to login to the OS.
661 This defaults to global ${OS_PASSWORD}.
662 quiet Indicates whether status details should be written to the
663 console. Defaults to either global value of ${QUIET} or
664 to 1.
665 """
666
Michael Walsh619aa332017-04-12 15:56:51 -0500667 quiet = int(gp.get_var_value(quiet, 0))
Michael Walsh70369fd2016-11-22 11:25:57 -0600668
669 grp.rprint(print_string)
670
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600671 req_states = match_state.keys()
Michael Walsh70369fd2016-11-22 11:25:57 -0600672 # Initialize state.
673 state = get_state(openbmc_host=openbmc_host,
674 openbmc_username=openbmc_username,
675 openbmc_password=openbmc_password,
676 os_host=os_host,
677 os_username=os_username,
678 os_password=os_password,
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600679 req_states=req_states,
Michael Walsh70369fd2016-11-22 11:25:57 -0600680 quiet=quiet)
681 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500682 gp.print_var(state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600683
684 match = compare_states(state, match_state)
685
686 if invert and match:
687 fail_msg = "The current state of the machine matches the match" +\
688 " state:\n" + gp.sprint_varx("state", state)
689 BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
690 elif not invert and not match:
691 fail_msg = "The current state of the machine does NOT match the" +\
692 " match state:\n" +\
693 gp.sprint_varx("state", state)
694 BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
695
696 return state
697
Michael Walsh70369fd2016-11-22 11:25:57 -0600698
Michael Walshf893ba02017-01-10 10:28:05 -0600699def wait_state(match_state=(),
Michael Walsh70369fd2016-11-22 11:25:57 -0600700 wait_time="1 min",
701 interval="1 second",
702 invert=0,
703 openbmc_host="",
704 openbmc_username="",
705 openbmc_password="",
706 os_host="",
707 os_username="",
708 os_password="",
709 quiet=None):
Michael Walsh70369fd2016-11-22 11:25:57 -0600710 r"""
711 Wait for the Open BMC machine's composite state to match the specified
712 state. On success, this keyword returns the machine's composite state as
713 a dictionary.
714
715 Description of arguments:
716 match_state A dictionary whose key/value pairs are "state field"/
717 "state value". See check_state (above) for details.
Michael Walsh619aa332017-04-12 15:56:51 -0500718 This value may also be any string accepted by
719 return_state_constant (e.g. "standby_match_state").
720 In such a case this function will call
721 return_state_constant to convert it to a proper
722 dictionary as described above.
Michael Walsh70369fd2016-11-22 11:25:57 -0600723 wait_time The total amount of time to wait for the desired state.
724 This value may be expressed in Robot Framework's time
725 format (e.g. 1 minute, 2 min 3 s, 4.5).
726 interval The amount of time between state checks.
727 This value may be expressed in Robot Framework's time
728 format (e.g. 1 minute, 2 min 3 s, 4.5).
729 invert If this flag is set, this function will for the state of
730 the machine to cease to match the match state.
731 openbmc_host The DNS name or IP address of the BMC.
732 This defaults to global ${OPENBMC_HOST}.
733 openbmc_username The username to be used to login to the BMC.
734 This defaults to global ${OPENBMC_USERNAME}.
735 openbmc_password The password to be used to login to the BMC.
736 This defaults to global ${OPENBMC_PASSWORD}.
737 os_host The DNS name or IP address of the operating system.
738 This defaults to global ${OS_HOST}.
739 os_username The username to be used to login to the OS.
740 This defaults to global ${OS_USERNAME}.
741 os_password The password to be used to login to the OS.
742 This defaults to global ${OS_PASSWORD}.
743 quiet Indicates whether status details should be written to the
744 console. Defaults to either global value of ${QUIET} or
745 to 1.
746 """
747
Michael Walsh619aa332017-04-12 15:56:51 -0500748 quiet = int(gp.get_var_value(quiet, 0))
749
750 if type(match_state) in (str, unicode):
751 match_state = return_state_constant(match_state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600752
753 if not quiet:
754 if invert:
755 alt_text = "cease to "
756 else:
757 alt_text = ""
Joy Onyerikwu004ad3c2018-06-11 16:29:56 -0500758 gp.print_timen("Checking every " + str(interval) + " for up to "
759 + str(wait_time) + " for the state of the machine to "
760 + alt_text + "match the state shown below.")
Michael Walsh3eb50022017-03-21 11:27:30 -0500761 gp.print_var(match_state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600762
Michael Walshf893ba02017-01-10 10:28:05 -0600763 if quiet:
Michael Walsh341c21e2017-01-17 16:25:20 -0600764 print_string = ""
Michael Walshf893ba02017-01-10 10:28:05 -0600765 else:
Michael Walsh341c21e2017-01-17 16:25:20 -0600766 print_string = "#"
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600767
768 debug = int(BuiltIn().get_variable_value("${debug}", "0"))
769 if debug:
770 # In debug we print state so no need to print the "#".
771 print_string = ""
772 check_state_quiet = 1 - debug
Michael Walsh70369fd2016-11-22 11:25:57 -0600773 cmd_buf = ["Check State", match_state, "invert=${" + str(invert) + "}",
Michael Walshf893ba02017-01-10 10:28:05 -0600774 "print_string=" + print_string, "openbmc_host=" + openbmc_host,
Michael Walsh70369fd2016-11-22 11:25:57 -0600775 "openbmc_username=" + openbmc_username,
776 "openbmc_password=" + openbmc_password, "os_host=" + os_host,
777 "os_username=" + os_username, "os_password=" + os_password,
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600778 "quiet=${" + str(check_state_quiet) + "}"]
Michael Walsh70369fd2016-11-22 11:25:57 -0600779 grp.rdpissuing_keyword(cmd_buf)
Michael Walsh619aa332017-04-12 15:56:51 -0500780 try:
781 state = BuiltIn().wait_until_keyword_succeeds(wait_time, interval,
782 *cmd_buf)
783 except AssertionError as my_assertion_error:
784 gp.printn()
785 message = my_assertion_error.args[0]
786 BuiltIn().fail(message)
787
Michael Walsh70369fd2016-11-22 11:25:57 -0600788 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500789 gp.printn()
Michael Walsh70369fd2016-11-22 11:25:57 -0600790 if invert:
Michael Walsh3eb50022017-03-21 11:27:30 -0500791 gp.print_timen("The states no longer match:")
Michael Walsh70369fd2016-11-22 11:25:57 -0600792 else:
Michael Walsh3eb50022017-03-21 11:27:30 -0500793 gp.print_timen("The states match:")
794 gp.print_var(state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600795
796 return state
797
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600798
Michael Walsh619aa332017-04-12 15:56:51 -0500799def wait_for_comm_cycle(start_boot_seconds,
800 quiet=None):
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600801 r"""
802 Wait for communications to the BMC to stop working and then resume working.
803 This function is useful when you have initiated some kind of reboot.
804
805 Description of arguments:
806 start_boot_seconds The time that the boot test started. The format is the
807 epoch time in seconds, i.e. the number of seconds since
808 1970-01-01 00:00:00 UTC. This value should be obtained
809 from the BMC so that it is not dependent on any kind of
810 synchronization between this machine and the target BMC
811 This will allow this program to work correctly even in
812 a simulated environment. This value should be obtained
813 by the caller prior to initiating a reboot. It can be
814 obtained as follows:
815 state = st.get_state(req_states=['epoch_seconds'])
816 """
817
Michael Walsh619aa332017-04-12 15:56:51 -0500818 quiet = int(gp.get_var_value(quiet, 0))
819
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600820 # Validate parms.
821 error_message = gv.svalid_integer(start_boot_seconds,
822 var_name="start_boot_seconds")
823 if error_message != "":
824 BuiltIn().fail(gp.sprint_error(error_message))
825
826 match_state = anchor_state(DotDict([('packet_loss', '100')]))
827 # Wait for 100% packet loss trying to ping machine.
Michael Walsh619aa332017-04-12 15:56:51 -0500828 wait_state(match_state, wait_time="8 mins", interval="0 seconds")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600829
830 match_state['packet_loss'] = '^0$'
831 # Wait for 0% packet loss trying to ping machine.
Michael Walsh619aa332017-04-12 15:56:51 -0500832 wait_state(match_state, wait_time="8 mins", interval="0 seconds")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600833
834 # Get the uptime and epoch seconds for comparisons. We want to be sure
835 # that the uptime is less than the elapsed boot time. Further proof that
836 # a reboot has indeed occurred (vs random network instability giving a
Michael Walshc4c05d32018-05-29 11:39:39 -0500837 # false positive. We also use wait_state because the BMC may take a short
838 # while to be ready to process SSH requests.
Joy Onyerikwu004ad3c2018-06-11 16:29:56 -0500839 match_state = DotDict([('uptime', '^[0-9\\.]+$'),
Michael Walshc4c05d32018-05-29 11:39:39 -0500840 ('epoch_seconds', '^[0-9]+$')])
841 state = wait_state(match_state, wait_time="2 mins", interval="1 second")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600842
843 elapsed_boot_time = int(state['epoch_seconds']) - start_boot_seconds
Michael Walsh619aa332017-04-12 15:56:51 -0500844 gp.qprint_var(elapsed_boot_time)
Michael Walshe77585a2017-12-14 11:02:28 -0600845 if state['uptime'] == "":
846 error_message = "Unable to obtain uptime from the BMC. BMC is not" +\
847 " communicating."
848 BuiltIn().fail(gp.sprint_error(error_message))
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600849 if int(float(state['uptime'])) < elapsed_boot_time:
850 uptime = state['uptime']
Michael Walsh619aa332017-04-12 15:56:51 -0500851 gp.qprint_var(uptime)
Joy Onyerikwu004ad3c2018-06-11 16:29:56 -0500852 gp.qprint_timen("The uptime is less than the elapsed boot time,"
853 + " as expected.")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600854 else:
855 error_message = "The uptime is greater than the elapsed boot time," +\
856 " which is unexpected:\n" +\
857 gp.sprint_var(start_boot_seconds) +\
858 gp.sprint_var(state)
859 BuiltIn().fail(gp.sprint_error(error_message))
860
Michael Walsh619aa332017-04-12 15:56:51 -0500861 gp.qprint_timen("Verifying that REST API interface is working.")
Michael Walshb95eb542017-03-31 09:39:20 -0500862 match_state = DotDict([('rest', '^1$')])
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600863 state = wait_state(match_state, wait_time="5 mins", interval="2 seconds")