blob: 1e117b98cb974ffc255c0ecc67d0149cef039740 [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 Walsh619aa332017-04-12 15:56:51 -050047# We need utils.robot to get keywords like "Get Chassis Power State".
Michael Walsh16cbb7f2017-02-02 15:54:16 -060048gru.my_import_resource("utils.robot")
49gru.my_import_resource("state_manager.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 Walsh619aa332017-04-12 15:56:51 -0500171 ('host', '^Running$')])
172
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:
510 grp.rpissuing_keyword(cmd_buf)
Michael Walshfa765932017-10-13 14:07:22 -0500511 grp.rpissuing(remote_cmd_buf)
512 try:
513 stdout, stderr, rc =\
Michael Walshcaccd852017-11-01 17:58:41 -0500514 BuiltIn().wait_until_keyword_succeeds("10 sec", "0 sec",
Michael Walshfa765932017-10-13 14:07:22 -0500515 *cmd_buf)
Michael Walsh97df71c2017-03-27 14:33:24 -0500516 if rc == 0 and stderr == "":
517 uptime = stdout
Michael Walshfa765932017-10-13 14:07:22 -0500518 except AssertionError as my_assertion_error:
519 pass
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600520
Michael Walshe53e47a2017-06-30 17:03:24 -0500521 if 'epoch_seconds' in req_states:
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600522 date_cmd_buf = "date -u +%s"
523 if USE_BMC_EPOCH_TIME:
Michael Walshe53e47a2017-06-30 17:03:24 -0500524 cmd_buf = ["BMC Execute Command", date_cmd_buf, 'quiet=${1}']
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600525 if not quiet:
526 grp.rpissuing_keyword(cmd_buf)
Michael Walsh97df71c2017-03-27 14:33:24 -0500527 status, ret_values = \
528 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
529 if status == "PASS":
530 stdout, stderr, rc = ret_values
531 if rc == 0 and stderr == "":
532 epoch_seconds = stdout.rstrip("\n")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600533 else:
534 shell_rc, out_buf = gc.cmd_fnc_u(date_cmd_buf,
Michael Walshfa765932017-10-13 14:07:22 -0500535 quiet=quiet,
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600536 print_output=0)
537 if shell_rc == 0:
538 epoch_seconds = out_buf.rstrip("\n")
539
Michael Walsh56749222017-09-29 15:26:07 -0500540 master_req_rest = ['rest', 'host', 'requested_host', 'operating_system',
541 'attempts_left', 'boot_progress', 'chassis',
542 'requested_chassis' 'bmc' 'requested_bmc']
543
Michael Walshb95eb542017-03-31 09:39:20 -0500544 req_rest = [sub_state for sub_state in req_states if sub_state in
545 master_req_rest]
546 need_rest = (len(req_rest) > 0)
Michael Walsh56749222017-09-29 15:26:07 -0500547 state = DotDict()
548 if need_rest:
Michael Walsh940d6912017-10-27 12:32:33 -0500549 cmd_buf = ["Read Properties", SYSTEM_STATE_URI + "enumerate",
Michael Walsh56749222017-09-29 15:26:07 -0500550 "quiet=${" + str(quiet) + "}"]
Michael Walsh341c21e2017-01-17 16:25:20 -0600551 grp.rdpissuing_keyword(cmd_buf)
Michael Walsh97df71c2017-03-27 14:33:24 -0500552 status, ret_values = \
553 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
554 if status == "PASS":
Michael Walsh56749222017-09-29 15:26:07 -0500555 state['rest'] = '1'
Michael Walsh341c21e2017-01-17 16:25:20 -0600556 else:
Michael Walsh56749222017-09-29 15:26:07 -0500557 state['rest'] = '0'
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600558
Michael Walsh2b269de2017-10-09 11:18:11 -0500559 if int(state['rest']):
560 for url_path in ret_values:
561 for attr_name in ret_values[url_path]:
562 # Create a state key value based on the attr_name.
Joy Onyerikwu004ad3c2018-06-11 16:29:56 -0500563 if isinstance(ret_values[url_path][attr_name], unicode):
Michael Walsh2b269de2017-10-09 11:18:11 -0500564 ret_values[url_path][attr_name] = \
565 re.sub(r'.*\.', "",
566 ret_values[url_path][attr_name])
567 # Do some key name manipulations.
568 new_attr_name = re.sub(r'^Current|(State|Transition)$',
569 "", attr_name)
570 new_attr_name = re.sub(r'BMC', r'Bmc', new_attr_name)
571 new_attr_name = re.sub(r'([A-Z][a-z])', r'_\1',
572 new_attr_name)
573 new_attr_name = new_attr_name.lower().lstrip("_")
574 new_attr_name = re.sub(r'power', r'chassis', new_attr_name)
575 if new_attr_name in req_states:
576 state[new_attr_name] = ret_values[url_path][attr_name]
Michael Walshb95eb542017-03-31 09:39:20 -0500577
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600578 for sub_state in req_states:
Michael Walsh56749222017-09-29 15:26:07 -0500579 if sub_state in state:
580 continue
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600581 if sub_state.startswith("os_"):
582 # We pass "os_" requests on to get_os_state.
583 continue
584 cmd_buf = "state['" + sub_state + "'] = str(" + sub_state + ")"
585 exec(cmd_buf)
586
587 if os_host == "":
588 # The caller has not specified an os_host so as far as we're concerned,
589 # it doesn't exist.
590 return state
591
592 os_req_states = [sub_state for sub_state in req_states
593 if sub_state.startswith('os_')]
594
595 if len(os_req_states) > 0:
596 # The caller has specified an os_host and they have requested
597 # information on os substates.
598
599 # Based on the information gathered on bmc, we'll try to make a
600 # determination of whether the os is even up. We'll pass the result
601 # of that assessment to get_os_state to enhance performance.
602 os_up_match = DotDict()
603 for sub_state in master_os_up_match:
604 if sub_state in req_states:
605 os_up_match[sub_state] = master_os_up_match[sub_state]
Michael Walsh70369fd2016-11-22 11:25:57 -0600606 os_up = compare_states(state, os_up_match)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600607 os_state = get_os_state(os_host=os_host,
608 os_username=os_username,
609 os_password=os_password,
610 req_states=os_req_states,
611 os_up=os_up,
612 quiet=quiet)
613 # Append os_state dictionary to ours.
614 state.update(os_state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600615
616 return state
617
Michael Walsh70369fd2016-11-22 11:25:57 -0600618
Michael Walsh70369fd2016-11-22 11:25:57 -0600619def check_state(match_state,
620 invert=0,
621 print_string="",
622 openbmc_host="",
623 openbmc_username="",
624 openbmc_password="",
625 os_host="",
626 os_username="",
627 os_password="",
628 quiet=None):
Michael Walsh70369fd2016-11-22 11:25:57 -0600629 r"""
630 Check that the Open BMC machine's composite state matches the specified
631 state. On success, this keyword returns the machine's composite state as a
632 dictionary.
633
634 Description of arguments:
635 match_state A dictionary whose key/value pairs are "state field"/
636 "state value". The state value is interpreted as a
637 regular expression. Example call from robot:
Michael Walsh341c21e2017-01-17 16:25:20 -0600638 ${match_state}= Create Dictionary chassis=^On$
639 ... bmc=^Ready$
Michael Walsh01975fa2017-08-20 20:51:36 -0500640 ... boot_progress=^OSStart$
Michael Walsh70369fd2016-11-22 11:25:57 -0600641 ${state}= Check State &{match_state}
642 invert If this flag is set, this function will succeed if the
643 states do NOT match.
644 print_string This function will print this string to the console prior
645 to getting the state.
646 openbmc_host The DNS name or IP address of the BMC.
647 This defaults to global ${OPENBMC_HOST}.
648 openbmc_username The username to be used to login to the BMC.
649 This defaults to global ${OPENBMC_USERNAME}.
650 openbmc_password The password to be used to login to the BMC.
651 This defaults to global ${OPENBMC_PASSWORD}.
652 os_host The DNS name or IP address of the operating system.
653 This defaults to global ${OS_HOST}.
654 os_username The username to be used to login to the OS.
655 This defaults to global ${OS_USERNAME}.
656 os_password The password to be used to login to the OS.
657 This defaults to global ${OS_PASSWORD}.
658 quiet Indicates whether status details should be written to the
659 console. Defaults to either global value of ${QUIET} or
660 to 1.
661 """
662
Michael Walsh619aa332017-04-12 15:56:51 -0500663 quiet = int(gp.get_var_value(quiet, 0))
Michael Walsh70369fd2016-11-22 11:25:57 -0600664
665 grp.rprint(print_string)
666
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600667 req_states = match_state.keys()
Michael Walsh70369fd2016-11-22 11:25:57 -0600668 # Initialize state.
669 state = get_state(openbmc_host=openbmc_host,
670 openbmc_username=openbmc_username,
671 openbmc_password=openbmc_password,
672 os_host=os_host,
673 os_username=os_username,
674 os_password=os_password,
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600675 req_states=req_states,
Michael Walsh70369fd2016-11-22 11:25:57 -0600676 quiet=quiet)
677 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500678 gp.print_var(state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600679
680 match = compare_states(state, match_state)
681
682 if invert and match:
683 fail_msg = "The current state of the machine matches the match" +\
684 " state:\n" + gp.sprint_varx("state", state)
685 BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
686 elif not invert and not match:
687 fail_msg = "The current state of the machine does NOT match the" +\
688 " match state:\n" +\
689 gp.sprint_varx("state", state)
690 BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
691
692 return state
693
Michael Walsh70369fd2016-11-22 11:25:57 -0600694
Michael Walshf893ba02017-01-10 10:28:05 -0600695def wait_state(match_state=(),
Michael Walsh70369fd2016-11-22 11:25:57 -0600696 wait_time="1 min",
697 interval="1 second",
698 invert=0,
699 openbmc_host="",
700 openbmc_username="",
701 openbmc_password="",
702 os_host="",
703 os_username="",
704 os_password="",
705 quiet=None):
Michael Walsh70369fd2016-11-22 11:25:57 -0600706 r"""
707 Wait for the Open BMC machine's composite state to match the specified
708 state. On success, this keyword returns the machine's composite state as
709 a dictionary.
710
711 Description of arguments:
712 match_state A dictionary whose key/value pairs are "state field"/
713 "state value". See check_state (above) for details.
Michael Walsh619aa332017-04-12 15:56:51 -0500714 This value may also be any string accepted by
715 return_state_constant (e.g. "standby_match_state").
716 In such a case this function will call
717 return_state_constant to convert it to a proper
718 dictionary as described above.
Michael Walsh70369fd2016-11-22 11:25:57 -0600719 wait_time The total amount of time to wait for the desired state.
720 This value may be expressed in Robot Framework's time
721 format (e.g. 1 minute, 2 min 3 s, 4.5).
722 interval The amount of time between state checks.
723 This value may be expressed in Robot Framework's time
724 format (e.g. 1 minute, 2 min 3 s, 4.5).
725 invert If this flag is set, this function will for the state of
726 the machine to cease to match the match state.
727 openbmc_host The DNS name or IP address of the BMC.
728 This defaults to global ${OPENBMC_HOST}.
729 openbmc_username The username to be used to login to the BMC.
730 This defaults to global ${OPENBMC_USERNAME}.
731 openbmc_password The password to be used to login to the BMC.
732 This defaults to global ${OPENBMC_PASSWORD}.
733 os_host The DNS name or IP address of the operating system.
734 This defaults to global ${OS_HOST}.
735 os_username The username to be used to login to the OS.
736 This defaults to global ${OS_USERNAME}.
737 os_password The password to be used to login to the OS.
738 This defaults to global ${OS_PASSWORD}.
739 quiet Indicates whether status details should be written to the
740 console. Defaults to either global value of ${QUIET} or
741 to 1.
742 """
743
Michael Walsh619aa332017-04-12 15:56:51 -0500744 quiet = int(gp.get_var_value(quiet, 0))
745
746 if type(match_state) in (str, unicode):
747 match_state = return_state_constant(match_state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600748
749 if not quiet:
750 if invert:
751 alt_text = "cease to "
752 else:
753 alt_text = ""
Joy Onyerikwu004ad3c2018-06-11 16:29:56 -0500754 gp.print_timen("Checking every " + str(interval) + " for up to "
755 + str(wait_time) + " for the state of the machine to "
756 + alt_text + "match the state shown below.")
Michael Walsh3eb50022017-03-21 11:27:30 -0500757 gp.print_var(match_state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600758
Michael Walshf893ba02017-01-10 10:28:05 -0600759 if quiet:
Michael Walsh341c21e2017-01-17 16:25:20 -0600760 print_string = ""
Michael Walshf893ba02017-01-10 10:28:05 -0600761 else:
Michael Walsh341c21e2017-01-17 16:25:20 -0600762 print_string = "#"
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600763
764 debug = int(BuiltIn().get_variable_value("${debug}", "0"))
765 if debug:
766 # In debug we print state so no need to print the "#".
767 print_string = ""
768 check_state_quiet = 1 - debug
Michael Walsh70369fd2016-11-22 11:25:57 -0600769 cmd_buf = ["Check State", match_state, "invert=${" + str(invert) + "}",
Michael Walshf893ba02017-01-10 10:28:05 -0600770 "print_string=" + print_string, "openbmc_host=" + openbmc_host,
Michael Walsh70369fd2016-11-22 11:25:57 -0600771 "openbmc_username=" + openbmc_username,
772 "openbmc_password=" + openbmc_password, "os_host=" + os_host,
773 "os_username=" + os_username, "os_password=" + os_password,
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600774 "quiet=${" + str(check_state_quiet) + "}"]
Michael Walsh70369fd2016-11-22 11:25:57 -0600775 grp.rdpissuing_keyword(cmd_buf)
Michael Walsh619aa332017-04-12 15:56:51 -0500776 try:
777 state = BuiltIn().wait_until_keyword_succeeds(wait_time, interval,
778 *cmd_buf)
779 except AssertionError as my_assertion_error:
780 gp.printn()
781 message = my_assertion_error.args[0]
782 BuiltIn().fail(message)
783
Michael Walsh70369fd2016-11-22 11:25:57 -0600784 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500785 gp.printn()
Michael Walsh70369fd2016-11-22 11:25:57 -0600786 if invert:
Michael Walsh3eb50022017-03-21 11:27:30 -0500787 gp.print_timen("The states no longer match:")
Michael Walsh70369fd2016-11-22 11:25:57 -0600788 else:
Michael Walsh3eb50022017-03-21 11:27:30 -0500789 gp.print_timen("The states match:")
790 gp.print_var(state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600791
792 return state
793
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600794
Michael Walsh619aa332017-04-12 15:56:51 -0500795def wait_for_comm_cycle(start_boot_seconds,
796 quiet=None):
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600797 r"""
798 Wait for communications to the BMC to stop working and then resume working.
799 This function is useful when you have initiated some kind of reboot.
800
801 Description of arguments:
802 start_boot_seconds The time that the boot test started. The format is the
803 epoch time in seconds, i.e. the number of seconds since
804 1970-01-01 00:00:00 UTC. This value should be obtained
805 from the BMC so that it is not dependent on any kind of
806 synchronization between this machine and the target BMC
807 This will allow this program to work correctly even in
808 a simulated environment. This value should be obtained
809 by the caller prior to initiating a reboot. It can be
810 obtained as follows:
811 state = st.get_state(req_states=['epoch_seconds'])
812 """
813
Michael Walsh619aa332017-04-12 15:56:51 -0500814 quiet = int(gp.get_var_value(quiet, 0))
815
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600816 # Validate parms.
817 error_message = gv.svalid_integer(start_boot_seconds,
818 var_name="start_boot_seconds")
819 if error_message != "":
820 BuiltIn().fail(gp.sprint_error(error_message))
821
822 match_state = anchor_state(DotDict([('packet_loss', '100')]))
823 # Wait for 100% packet loss trying to ping machine.
Michael Walsh619aa332017-04-12 15:56:51 -0500824 wait_state(match_state, wait_time="8 mins", interval="0 seconds")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600825
826 match_state['packet_loss'] = '^0$'
827 # Wait for 0% 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 # Get the uptime and epoch seconds for comparisons. We want to be sure
831 # that the uptime is less than the elapsed boot time. Further proof that
832 # a reboot has indeed occurred (vs random network instability giving a
Michael Walshc4c05d32018-05-29 11:39:39 -0500833 # false positive. We also use wait_state because the BMC may take a short
834 # while to be ready to process SSH requests.
Joy Onyerikwu004ad3c2018-06-11 16:29:56 -0500835 match_state = DotDict([('uptime', '^[0-9\\.]+$'),
Michael Walshc4c05d32018-05-29 11:39:39 -0500836 ('epoch_seconds', '^[0-9]+$')])
837 state = wait_state(match_state, wait_time="2 mins", interval="1 second")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600838
839 elapsed_boot_time = int(state['epoch_seconds']) - start_boot_seconds
Michael Walsh619aa332017-04-12 15:56:51 -0500840 gp.qprint_var(elapsed_boot_time)
Michael Walshe77585a2017-12-14 11:02:28 -0600841 if state['uptime'] == "":
842 error_message = "Unable to obtain uptime from the BMC. BMC is not" +\
843 " communicating."
844 BuiltIn().fail(gp.sprint_error(error_message))
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600845 if int(float(state['uptime'])) < elapsed_boot_time:
846 uptime = state['uptime']
Michael Walsh619aa332017-04-12 15:56:51 -0500847 gp.qprint_var(uptime)
Joy Onyerikwu004ad3c2018-06-11 16:29:56 -0500848 gp.qprint_timen("The uptime is less than the elapsed boot time,"
849 + " as expected.")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600850 else:
851 error_message = "The uptime is greater than the elapsed boot time," +\
852 " which is unexpected:\n" +\
853 gp.sprint_var(start_boot_seconds) +\
854 gp.sprint_var(state)
855 BuiltIn().fail(gp.sprint_error(error_message))
856
Michael Walsh619aa332017-04-12 15:56:51 -0500857 gp.qprint_timen("Verifying that REST API interface is working.")
Michael Walshb95eb542017-03-31 09:39:20 -0500858 match_state = DotDict([('rest', '^1$')])
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600859 state = wait_state(match_state, wait_time="5 mins", interval="2 seconds")