blob: 93c0eb682f27aea39e563b15d7e119102f37127e [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,
365 ignore_err=1)
366 if rc == 0:
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600367 os_login = 1
Michael Walsh6a9bd142018-07-24 16:10:29 -0500368 os_run_cmd = 1
Michael Walsh3eb50022017-03-21 11:27:30 -0500369 else:
Michael Walsh6a9bd142018-07-24 16:10:29 -0500370 gp.dprint_vars(output, stderr)
371 gp.dprint_vars(rc, 1)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600372
373 os_state = DotDict()
374 for sub_state in req_states:
375 cmd_buf = "os_state['" + sub_state + "'] = str(" + sub_state + ")"
376 exec(cmd_buf)
Michael Walsh70369fd2016-11-22 11:25:57 -0600377
378 return os_state
379
Michael Walsh70369fd2016-11-22 11:25:57 -0600380
Michael Walsh70369fd2016-11-22 11:25:57 -0600381def get_state(openbmc_host="",
382 openbmc_username="",
383 openbmc_password="",
384 os_host="",
385 os_username="",
386 os_password="",
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600387 req_states=default_req_states,
Michael Walsh70369fd2016-11-22 11:25:57 -0600388 quiet=None):
Michael Walsh70369fd2016-11-22 11:25:57 -0600389 r"""
Michael Walsh619aa332017-04-12 15:56:51 -0500390 Get component states such as chassis state, bmc state, etc, put them into a
Michael Walsh70369fd2016-11-22 11:25:57 -0600391 dictionary and return them to the caller.
392
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600393 Note that all substate values are strings.
394
Michael Walsh70369fd2016-11-22 11:25:57 -0600395 Description of arguments:
396 openbmc_host The DNS name or IP address of the BMC.
397 This defaults to global ${OPENBMC_HOST}.
398 openbmc_username The username to be used to login to the BMC.
399 This defaults to global ${OPENBMC_USERNAME}.
400 openbmc_password The password to be used to login to the BMC.
401 This defaults to global ${OPENBMC_PASSWORD}.
402 os_host The DNS name or IP address of the operating system.
403 This defaults to global ${OS_HOST}.
404 os_username The username to be used to login to the OS.
405 This defaults to global ${OS_USERNAME}.
406 os_password The password to be used to login to the OS.
407 This defaults to global ${OS_PASSWORD}.
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600408 req_states This is a list of states whose values are being requested
409 by the caller.
Michael Walsh70369fd2016-11-22 11:25:57 -0600410 quiet Indicates whether status details (e.g. curl commands)
411 should be written to the console.
412 Defaults to either global value of ${QUIET} or to 1.
413 """
414
Michael Walsh619aa332017-04-12 15:56:51 -0500415 quiet = int(gp.get_var_value(quiet, 0))
Michael Walsh70369fd2016-11-22 11:25:57 -0600416
417 # Set parm defaults where necessary and validate all parms.
418 if openbmc_host == "":
419 openbmc_host = BuiltIn().get_variable_value("${OPENBMC_HOST}")
420 error_message = gv.svalid_value(openbmc_host,
421 var_name="openbmc_host",
422 invalid_values=[None, ""])
423 if error_message != "":
424 BuiltIn().fail(gp.sprint_error(error_message))
425
426 if openbmc_username == "":
427 openbmc_username = BuiltIn().get_variable_value("${OPENBMC_USERNAME}")
428 error_message = gv.svalid_value(openbmc_username,
429 var_name="openbmc_username",
430 invalid_values=[None, ""])
431 if error_message != "":
432 BuiltIn().fail(gp.sprint_error(error_message))
433
434 if openbmc_password == "":
435 openbmc_password = BuiltIn().get_variable_value("${OPENBMC_PASSWORD}")
436 error_message = gv.svalid_value(openbmc_password,
437 var_name="openbmc_password",
438 invalid_values=[None, ""])
439 if error_message != "":
440 BuiltIn().fail(gp.sprint_error(error_message))
441
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600442 # NOTE: OS parms are optional.
Michael Walsh70369fd2016-11-22 11:25:57 -0600443 if os_host == "":
444 os_host = BuiltIn().get_variable_value("${OS_HOST}")
445 if os_host is None:
446 os_host = ""
447
448 if os_username is "":
449 os_username = BuiltIn().get_variable_value("${OS_USERNAME}")
450 if os_username is None:
451 os_username = ""
452
453 if os_password is "":
454 os_password = BuiltIn().get_variable_value("${OS_PASSWORD}")
455 if os_password is None:
456 os_password = ""
457
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600458 invalid_req_states = [sub_state for sub_state in req_states
459 if sub_state not in valid_req_states]
460 if len(invalid_req_states) > 0:
461 error_message = "The following req_states are not supported:\n" +\
462 gp.sprint_var(invalid_req_states)
463 BuiltIn().fail(gp.sprint_error(error_message))
464
465 # Initialize all substate values supported by this function.
466 ping = 0
467 packet_loss = ''
468 uptime = ''
469 epoch_seconds = ''
Michael Walsh2b269de2017-10-09 11:18:11 -0500470 rest = ''
471 chassis = ''
472 requested_chassis = ''
473 bmc = ''
474 requested_bmc = ''
475 boot_progress = ''
476 operating_system = ''
477 host = ''
478 requested_host = ''
479 attempts_left = ''
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600480
Michael Walsh70369fd2016-11-22 11:25:57 -0600481 # Get the component states.
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600482 if 'ping' in req_states:
483 # See if the OS pings.
484 cmd_buf = "ping -c 1 -w 2 " + openbmc_host
485 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500486 gp.pissuing(cmd_buf)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600487 rc, out_buf = commands.getstatusoutput(cmd_buf)
488 if rc == 0:
489 ping = 1
490
491 if 'packet_loss' in req_states:
492 # See if the OS pings.
493 cmd_buf = "ping -c 5 -w 5 " + openbmc_host +\
494 " | egrep 'packet loss' | sed -re 's/.* ([0-9]+)%.*/\\1/g'"
495 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500496 gp.pissuing(cmd_buf)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600497 rc, out_buf = commands.getstatusoutput(cmd_buf)
498 if rc == 0:
499 packet_loss = out_buf.rstrip("\n")
500
Michael Walshe53e47a2017-06-30 17:03:24 -0500501 if 'uptime' in req_states:
Michael Walshfa765932017-10-13 14:07:22 -0500502 # Sometimes reading uptime results in a blank value. Call with
503 # wait_until_keyword_succeeds to ensure a non-blank value is obtained.
504 remote_cmd_buf = "read uptime filler 2>/dev/null < /proc/uptime" +\
505 " && [ ! -z \"${uptime}\" ] && echo ${uptime}"
Joy Onyerikwu004ad3c2018-06-11 16:29:56 -0500506 cmd_buf = ["BMC Execute Command",
Michael Walsh8c34eb72018-06-14 11:26:16 -0500507 re.sub('\\$', '\\$', remote_cmd_buf), 'quiet=1']
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600508 if not quiet:
509 grp.rpissuing_keyword(cmd_buf)
Michael Walshfa765932017-10-13 14:07:22 -0500510 grp.rpissuing(remote_cmd_buf)
511 try:
512 stdout, stderr, rc =\
Michael Walshcaccd852017-11-01 17:58:41 -0500513 BuiltIn().wait_until_keyword_succeeds("10 sec", "0 sec",
Michael Walshfa765932017-10-13 14:07:22 -0500514 *cmd_buf)
Michael Walsh97df71c2017-03-27 14:33:24 -0500515 if rc == 0 and stderr == "":
516 uptime = stdout
Michael Walshfa765932017-10-13 14:07:22 -0500517 except AssertionError as my_assertion_error:
518 pass
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600519
Michael Walshe53e47a2017-06-30 17:03:24 -0500520 if 'epoch_seconds' in req_states:
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600521 date_cmd_buf = "date -u +%s"
522 if USE_BMC_EPOCH_TIME:
Michael Walshe53e47a2017-06-30 17:03:24 -0500523 cmd_buf = ["BMC Execute Command", date_cmd_buf, 'quiet=${1}']
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600524 if not quiet:
525 grp.rpissuing_keyword(cmd_buf)
Michael Walsh97df71c2017-03-27 14:33:24 -0500526 status, ret_values = \
527 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
528 if status == "PASS":
529 stdout, stderr, rc = ret_values
530 if rc == 0 and stderr == "":
531 epoch_seconds = stdout.rstrip("\n")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600532 else:
533 shell_rc, out_buf = gc.cmd_fnc_u(date_cmd_buf,
Michael Walshfa765932017-10-13 14:07:22 -0500534 quiet=quiet,
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600535 print_output=0)
536 if shell_rc == 0:
537 epoch_seconds = out_buf.rstrip("\n")
538
Michael Walsh56749222017-09-29 15:26:07 -0500539 master_req_rest = ['rest', 'host', 'requested_host', 'operating_system',
540 'attempts_left', 'boot_progress', 'chassis',
541 'requested_chassis' 'bmc' 'requested_bmc']
542
Michael Walshb95eb542017-03-31 09:39:20 -0500543 req_rest = [sub_state for sub_state in req_states if sub_state in
544 master_req_rest]
545 need_rest = (len(req_rest) > 0)
Michael Walsh56749222017-09-29 15:26:07 -0500546 state = DotDict()
547 if need_rest:
Michael Walsh940d6912017-10-27 12:32:33 -0500548 cmd_buf = ["Read Properties", SYSTEM_STATE_URI + "enumerate",
Michael Walsh56749222017-09-29 15:26:07 -0500549 "quiet=${" + str(quiet) + "}"]
Michael Walsh341c21e2017-01-17 16:25:20 -0600550 grp.rdpissuing_keyword(cmd_buf)
Michael Walsh97df71c2017-03-27 14:33:24 -0500551 status, ret_values = \
552 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
553 if status == "PASS":
Michael Walsh56749222017-09-29 15:26:07 -0500554 state['rest'] = '1'
Michael Walsh341c21e2017-01-17 16:25:20 -0600555 else:
Michael Walsh56749222017-09-29 15:26:07 -0500556 state['rest'] = '0'
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600557
Michael Walsh2b269de2017-10-09 11:18:11 -0500558 if int(state['rest']):
559 for url_path in ret_values:
560 for attr_name in ret_values[url_path]:
561 # Create a state key value based on the attr_name.
Joy Onyerikwu004ad3c2018-06-11 16:29:56 -0500562 if isinstance(ret_values[url_path][attr_name], unicode):
Michael Walsh2b269de2017-10-09 11:18:11 -0500563 ret_values[url_path][attr_name] = \
564 re.sub(r'.*\.', "",
565 ret_values[url_path][attr_name])
566 # Do some key name manipulations.
567 new_attr_name = re.sub(r'^Current|(State|Transition)$',
568 "", attr_name)
569 new_attr_name = re.sub(r'BMC', r'Bmc', new_attr_name)
570 new_attr_name = re.sub(r'([A-Z][a-z])', r'_\1',
571 new_attr_name)
572 new_attr_name = new_attr_name.lower().lstrip("_")
573 new_attr_name = re.sub(r'power', r'chassis', new_attr_name)
574 if new_attr_name in req_states:
575 state[new_attr_name] = ret_values[url_path][attr_name]
Michael Walshb95eb542017-03-31 09:39:20 -0500576
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600577 for sub_state in req_states:
Michael Walsh56749222017-09-29 15:26:07 -0500578 if sub_state in state:
579 continue
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600580 if sub_state.startswith("os_"):
581 # We pass "os_" requests on to get_os_state.
582 continue
583 cmd_buf = "state['" + sub_state + "'] = str(" + sub_state + ")"
584 exec(cmd_buf)
585
586 if os_host == "":
587 # The caller has not specified an os_host so as far as we're concerned,
588 # it doesn't exist.
589 return state
590
591 os_req_states = [sub_state for sub_state in req_states
592 if sub_state.startswith('os_')]
593
594 if len(os_req_states) > 0:
595 # The caller has specified an os_host and they have requested
596 # information on os substates.
597
598 # Based on the information gathered on bmc, we'll try to make a
599 # determination of whether the os is even up. We'll pass the result
600 # of that assessment to get_os_state to enhance performance.
601 os_up_match = DotDict()
602 for sub_state in master_os_up_match:
603 if sub_state in req_states:
604 os_up_match[sub_state] = master_os_up_match[sub_state]
Michael Walsh70369fd2016-11-22 11:25:57 -0600605 os_up = compare_states(state, os_up_match)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600606 os_state = get_os_state(os_host=os_host,
607 os_username=os_username,
608 os_password=os_password,
609 req_states=os_req_states,
610 os_up=os_up,
611 quiet=quiet)
612 # Append os_state dictionary to ours.
613 state.update(os_state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600614
615 return state
616
Michael Walsh70369fd2016-11-22 11:25:57 -0600617
Michael Walsh70369fd2016-11-22 11:25:57 -0600618def check_state(match_state,
619 invert=0,
620 print_string="",
621 openbmc_host="",
622 openbmc_username="",
623 openbmc_password="",
624 os_host="",
625 os_username="",
626 os_password="",
627 quiet=None):
Michael Walsh70369fd2016-11-22 11:25:57 -0600628 r"""
629 Check that the Open BMC machine's composite state matches the specified
630 state. On success, this keyword returns the machine's composite state as a
631 dictionary.
632
633 Description of arguments:
634 match_state A dictionary whose key/value pairs are "state field"/
635 "state value". The state value is interpreted as a
636 regular expression. Example call from robot:
Michael Walsh341c21e2017-01-17 16:25:20 -0600637 ${match_state}= Create Dictionary chassis=^On$
638 ... bmc=^Ready$
Michael Walsh01975fa2017-08-20 20:51:36 -0500639 ... boot_progress=^OSStart$
Michael Walsh70369fd2016-11-22 11:25:57 -0600640 ${state}= Check State &{match_state}
641 invert If this flag is set, this function will succeed if the
642 states do NOT match.
643 print_string This function will print this string to the console prior
644 to getting the state.
645 openbmc_host The DNS name or IP address of the BMC.
646 This defaults to global ${OPENBMC_HOST}.
647 openbmc_username The username to be used to login to the BMC.
648 This defaults to global ${OPENBMC_USERNAME}.
649 openbmc_password The password to be used to login to the BMC.
650 This defaults to global ${OPENBMC_PASSWORD}.
651 os_host The DNS name or IP address of the operating system.
652 This defaults to global ${OS_HOST}.
653 os_username The username to be used to login to the OS.
654 This defaults to global ${OS_USERNAME}.
655 os_password The password to be used to login to the OS.
656 This defaults to global ${OS_PASSWORD}.
657 quiet Indicates whether status details should be written to the
658 console. Defaults to either global value of ${QUIET} or
659 to 1.
660 """
661
Michael Walsh619aa332017-04-12 15:56:51 -0500662 quiet = int(gp.get_var_value(quiet, 0))
Michael Walsh70369fd2016-11-22 11:25:57 -0600663
664 grp.rprint(print_string)
665
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600666 req_states = match_state.keys()
Michael Walsh70369fd2016-11-22 11:25:57 -0600667 # Initialize state.
668 state = get_state(openbmc_host=openbmc_host,
669 openbmc_username=openbmc_username,
670 openbmc_password=openbmc_password,
671 os_host=os_host,
672 os_username=os_username,
673 os_password=os_password,
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600674 req_states=req_states,
Michael Walsh70369fd2016-11-22 11:25:57 -0600675 quiet=quiet)
676 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500677 gp.print_var(state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600678
679 match = compare_states(state, match_state)
680
681 if invert and match:
682 fail_msg = "The current state of the machine matches the match" +\
683 " state:\n" + gp.sprint_varx("state", state)
684 BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
685 elif not invert and not match:
686 fail_msg = "The current state of the machine does NOT match the" +\
687 " match state:\n" +\
688 gp.sprint_varx("state", state)
689 BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
690
691 return state
692
Michael Walsh70369fd2016-11-22 11:25:57 -0600693
Michael Walshf893ba02017-01-10 10:28:05 -0600694def wait_state(match_state=(),
Michael Walsh70369fd2016-11-22 11:25:57 -0600695 wait_time="1 min",
696 interval="1 second",
697 invert=0,
698 openbmc_host="",
699 openbmc_username="",
700 openbmc_password="",
701 os_host="",
702 os_username="",
703 os_password="",
704 quiet=None):
Michael Walsh70369fd2016-11-22 11:25:57 -0600705 r"""
706 Wait for the Open BMC machine's composite state to match the specified
707 state. On success, this keyword returns the machine's composite state as
708 a dictionary.
709
710 Description of arguments:
711 match_state A dictionary whose key/value pairs are "state field"/
712 "state value". See check_state (above) for details.
Michael Walsh619aa332017-04-12 15:56:51 -0500713 This value may also be any string accepted by
714 return_state_constant (e.g. "standby_match_state").
715 In such a case this function will call
716 return_state_constant to convert it to a proper
717 dictionary as described above.
Michael Walsh70369fd2016-11-22 11:25:57 -0600718 wait_time The total amount of time to wait for the desired state.
719 This value may be expressed in Robot Framework's time
720 format (e.g. 1 minute, 2 min 3 s, 4.5).
721 interval The amount of time between state checks.
722 This value may be expressed in Robot Framework's time
723 format (e.g. 1 minute, 2 min 3 s, 4.5).
724 invert If this flag is set, this function will for the state of
725 the machine to cease to match the match state.
726 openbmc_host The DNS name or IP address of the BMC.
727 This defaults to global ${OPENBMC_HOST}.
728 openbmc_username The username to be used to login to the BMC.
729 This defaults to global ${OPENBMC_USERNAME}.
730 openbmc_password The password to be used to login to the BMC.
731 This defaults to global ${OPENBMC_PASSWORD}.
732 os_host The DNS name or IP address of the operating system.
733 This defaults to global ${OS_HOST}.
734 os_username The username to be used to login to the OS.
735 This defaults to global ${OS_USERNAME}.
736 os_password The password to be used to login to the OS.
737 This defaults to global ${OS_PASSWORD}.
738 quiet Indicates whether status details should be written to the
739 console. Defaults to either global value of ${QUIET} or
740 to 1.
741 """
742
Michael Walsh619aa332017-04-12 15:56:51 -0500743 quiet = int(gp.get_var_value(quiet, 0))
744
745 if type(match_state) in (str, unicode):
746 match_state = return_state_constant(match_state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600747
748 if not quiet:
749 if invert:
750 alt_text = "cease to "
751 else:
752 alt_text = ""
Joy Onyerikwu004ad3c2018-06-11 16:29:56 -0500753 gp.print_timen("Checking every " + str(interval) + " for up to "
754 + str(wait_time) + " for the state of the machine to "
755 + alt_text + "match the state shown below.")
Michael Walsh3eb50022017-03-21 11:27:30 -0500756 gp.print_var(match_state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600757
Michael Walshf893ba02017-01-10 10:28:05 -0600758 if quiet:
Michael Walsh341c21e2017-01-17 16:25:20 -0600759 print_string = ""
Michael Walshf893ba02017-01-10 10:28:05 -0600760 else:
Michael Walsh341c21e2017-01-17 16:25:20 -0600761 print_string = "#"
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600762
763 debug = int(BuiltIn().get_variable_value("${debug}", "0"))
764 if debug:
765 # In debug we print state so no need to print the "#".
766 print_string = ""
767 check_state_quiet = 1 - debug
Michael Walsh70369fd2016-11-22 11:25:57 -0600768 cmd_buf = ["Check State", match_state, "invert=${" + str(invert) + "}",
Michael Walshf893ba02017-01-10 10:28:05 -0600769 "print_string=" + print_string, "openbmc_host=" + openbmc_host,
Michael Walsh70369fd2016-11-22 11:25:57 -0600770 "openbmc_username=" + openbmc_username,
771 "openbmc_password=" + openbmc_password, "os_host=" + os_host,
772 "os_username=" + os_username, "os_password=" + os_password,
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600773 "quiet=${" + str(check_state_quiet) + "}"]
Michael Walsh70369fd2016-11-22 11:25:57 -0600774 grp.rdpissuing_keyword(cmd_buf)
Michael Walsh619aa332017-04-12 15:56:51 -0500775 try:
776 state = BuiltIn().wait_until_keyword_succeeds(wait_time, interval,
777 *cmd_buf)
778 except AssertionError as my_assertion_error:
779 gp.printn()
780 message = my_assertion_error.args[0]
781 BuiltIn().fail(message)
782
Michael Walsh70369fd2016-11-22 11:25:57 -0600783 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500784 gp.printn()
Michael Walsh70369fd2016-11-22 11:25:57 -0600785 if invert:
Michael Walsh3eb50022017-03-21 11:27:30 -0500786 gp.print_timen("The states no longer match:")
Michael Walsh70369fd2016-11-22 11:25:57 -0600787 else:
Michael Walsh3eb50022017-03-21 11:27:30 -0500788 gp.print_timen("The states match:")
789 gp.print_var(state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600790
791 return state
792
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600793
Michael Walsh619aa332017-04-12 15:56:51 -0500794def wait_for_comm_cycle(start_boot_seconds,
795 quiet=None):
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600796 r"""
797 Wait for communications to the BMC to stop working and then resume working.
798 This function is useful when you have initiated some kind of reboot.
799
800 Description of arguments:
801 start_boot_seconds The time that the boot test started. The format is the
802 epoch time in seconds, i.e. the number of seconds since
803 1970-01-01 00:00:00 UTC. This value should be obtained
804 from the BMC so that it is not dependent on any kind of
805 synchronization between this machine and the target BMC
806 This will allow this program to work correctly even in
807 a simulated environment. This value should be obtained
808 by the caller prior to initiating a reboot. It can be
809 obtained as follows:
810 state = st.get_state(req_states=['epoch_seconds'])
811 """
812
Michael Walsh619aa332017-04-12 15:56:51 -0500813 quiet = int(gp.get_var_value(quiet, 0))
814
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600815 # Validate parms.
816 error_message = gv.svalid_integer(start_boot_seconds,
817 var_name="start_boot_seconds")
818 if error_message != "":
819 BuiltIn().fail(gp.sprint_error(error_message))
820
821 match_state = anchor_state(DotDict([('packet_loss', '100')]))
822 # Wait for 100% packet loss trying to ping machine.
Michael Walsh619aa332017-04-12 15:56:51 -0500823 wait_state(match_state, wait_time="8 mins", interval="0 seconds")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600824
825 match_state['packet_loss'] = '^0$'
826 # Wait for 0% packet loss trying to ping machine.
Michael Walsh619aa332017-04-12 15:56:51 -0500827 wait_state(match_state, wait_time="8 mins", interval="0 seconds")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600828
829 # Get the uptime and epoch seconds for comparisons. We want to be sure
830 # that the uptime is less than the elapsed boot time. Further proof that
831 # a reboot has indeed occurred (vs random network instability giving a
Michael Walshc4c05d32018-05-29 11:39:39 -0500832 # false positive. We also use wait_state because the BMC may take a short
833 # while to be ready to process SSH requests.
Joy Onyerikwu004ad3c2018-06-11 16:29:56 -0500834 match_state = DotDict([('uptime', '^[0-9\\.]+$'),
Michael Walshc4c05d32018-05-29 11:39:39 -0500835 ('epoch_seconds', '^[0-9]+$')])
836 state = wait_state(match_state, wait_time="2 mins", interval="1 second")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600837
838 elapsed_boot_time = int(state['epoch_seconds']) - start_boot_seconds
Michael Walsh619aa332017-04-12 15:56:51 -0500839 gp.qprint_var(elapsed_boot_time)
Michael Walshe77585a2017-12-14 11:02:28 -0600840 if state['uptime'] == "":
841 error_message = "Unable to obtain uptime from the BMC. BMC is not" +\
842 " communicating."
843 BuiltIn().fail(gp.sprint_error(error_message))
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600844 if int(float(state['uptime'])) < elapsed_boot_time:
845 uptime = state['uptime']
Michael Walsh619aa332017-04-12 15:56:51 -0500846 gp.qprint_var(uptime)
Joy Onyerikwu004ad3c2018-06-11 16:29:56 -0500847 gp.qprint_timen("The uptime is less than the elapsed boot time,"
848 + " as expected.")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600849 else:
850 error_message = "The uptime is greater than the elapsed boot time," +\
851 " which is unexpected:\n" +\
852 gp.sprint_var(start_boot_seconds) +\
853 gp.sprint_var(state)
854 BuiltIn().fail(gp.sprint_error(error_message))
855
Michael Walsh619aa332017-04-12 15:56:51 -0500856 gp.qprint_timen("Verifying that REST API interface is working.")
Michael Walshb95eb542017-03-31 09:39:20 -0500857 match_state = DotDict([('rest', '^1$')])
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600858 state = wait_state(match_state, wait_time="5 mins", interval="2 seconds")