blob: 78f4cf40cf34ef0101aa36808f82a4bde58ae822 [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
George Keishingc191ed72019-06-10 07:45:59 -050037try:
38 import commands
39except ImportError:
40 import subprocess
41
Michael Walsh70369fd2016-11-22 11:25:57 -060042from robot.libraries.BuiltIn import BuiltIn
Michael Walsh341c21e2017-01-17 16:25:20 -060043from robot.utils import DotDict
Michael Walsh70369fd2016-11-22 11:25:57 -060044
45import re
Michael Walsh341c21e2017-01-17 16:25:20 -060046import os
Michael Walsh56749222017-09-29 15:26:07 -050047import sys
48import imp
49
Michael Walsh70369fd2016-11-22 11:25:57 -060050
Michael Walsh5a5868a2018-10-31 15:20:04 -050051# NOTE: Avoid importing utils.robot because utils.robot imports state.py
52# (indirectly) which will cause failures.
53gru.my_import_resource("rest_client.robot")
Michael Walsh70369fd2016-11-22 11:25:57 -060054
Michael Walsh56749222017-09-29 15:26:07 -050055base_path = os.path.dirname(os.path.dirname(
56 imp.find_module("gen_robot_print")[1])) + os.sep
57sys.path.append(base_path + "data/")
Michael Walsh56749222017-09-29 15:26:07 -050058
Michael Walsh940d6912017-10-27 12:32:33 -050059# Previously, I had this coded:
60# import variables as var
61# However, we ran into a problem where a robot program did this...
62# Variables ../../lib/ras/variables.py
63# Prior to doing this...
64# Library ../lib/state.py
65
66# This caused the wrong variables.py file to be selected. Attempts to fix this
67# have failed so far. For the moment, we will hard-code the value we need from
68# the file.
69
70SYSTEM_STATE_URI = "/xyz/openbmc_project/state/"
Michael Walsh56749222017-09-29 15:26:07 -050071
Michael Walsh8fae6ea2017-02-20 16:14:44 -060072# The BMC code has recently been changed as far as what states are defined and
73# what the state values can be. This module now has a means of processing both
74# the old style state (i.e. OBMC_STATES_VERSION = 0) and the new style (i.e.
Michael Walsh16cbb7f2017-02-02 15:54:16 -060075# OBMC_STATES_VERSION = 1).
Michael Walsh341c21e2017-01-17 16:25:20 -060076# The caller can set environment variable OBMC_STATES_VERSION to dictate
77# whether we're processing old or new style states. If OBMC_STATES_VERSION is
Michael Walsh8fae6ea2017-02-20 16:14:44 -060078# not set it will default to 1.
Michael Walsh341c21e2017-01-17 16:25:20 -060079
Michael Walsh619aa332017-04-12 15:56:51 -050080# As of the present moment, OBMC_STATES_VERSION of 0 is for cold that is so old
81# that it is no longer worthwhile to maintain. The OBMC_STATES_VERSION 0 code
82# is being removed but the OBMC_STATES_VERSION value will stay for now in the
83# event that it is needed in the future.
84
Michael Walsh8fae6ea2017-02-20 16:14:44 -060085OBMC_STATES_VERSION = int(os.environ.get('OBMC_STATES_VERSION', 1))
Michael Walsh341c21e2017-01-17 16:25:20 -060086
Michael Walsh619aa332017-04-12 15:56:51 -050087# When a user calls get_state w/o specifying req_states, default_req_states
88# is used as its value.
89default_req_states = ['rest',
90 'chassis',
91 'bmc',
92 'boot_progress',
Michael Walsh56749222017-09-29 15:26:07 -050093 'operating_system',
Michael Walsh619aa332017-04-12 15:56:51 -050094 'host',
95 'os_ping',
96 'os_login',
97 'os_run_cmd']
Michael Walsh8fae6ea2017-02-20 16:14:44 -060098
Michael Walsh619aa332017-04-12 15:56:51 -050099# valid_req_states is a list of sub states supported by the get_state function.
100# valid_req_states, default_req_states and master_os_up_match are used by the
101# get_state function.
102valid_req_states = ['ping',
103 'packet_loss',
104 'uptime',
105 'epoch_seconds',
106 'rest',
107 'chassis',
Michael Walsh56749222017-09-29 15:26:07 -0500108 'requested_chassis',
Michael Walsh619aa332017-04-12 15:56:51 -0500109 'bmc',
Michael Walsh56749222017-09-29 15:26:07 -0500110 'requested_bmc',
Michael Walsh619aa332017-04-12 15:56:51 -0500111 'boot_progress',
Michael Walsh56749222017-09-29 15:26:07 -0500112 'operating_system',
Michael Walsh619aa332017-04-12 15:56:51 -0500113 'host',
Michael Walsh56749222017-09-29 15:26:07 -0500114 'requested_host',
115 'attempts_left',
Michael Walsh619aa332017-04-12 15:56:51 -0500116 'os_ping',
117 'os_login',
118 'os_run_cmd']
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600119
120# valid_os_req_states and default_os_req_states are used by the os_get_state
121# function.
122# valid_os_req_states is a list of state information supported by the
123# get_os_state function.
124valid_os_req_states = ['os_ping',
125 'os_login',
126 'os_run_cmd']
127# When a user calls get_os_state w/o specifying req_states,
128# default_os_req_states is used as its value.
129default_os_req_states = ['os_ping',
130 'os_login',
131 'os_run_cmd']
132
133# Presently, some BMCs appear to not keep time very well. This environment
134# variable directs the get_state function to use either the BMC's epoch time
135# or the local epoch time.
136USE_BMC_EPOCH_TIME = int(os.environ.get('USE_BMC_EPOCH_TIME', 0))
Michael Walsh341c21e2017-01-17 16:25:20 -0600137
Michael Walsh619aa332017-04-12 15:56:51 -0500138# Useful state constant definition(s).
Michael Walsh619aa332017-04-12 15:56:51 -0500139# default_state is an initial value which may be of use to callers.
140default_state = DotDict([('rest', '1'),
141 ('chassis', 'On'),
142 ('bmc', 'Ready'),
Michael Walsh01975fa2017-08-20 20:51:36 -0500143 ('boot_progress', 'OSStart'),
Michael Walsh56749222017-09-29 15:26:07 -0500144 ('operating_system', 'BootComplete'),
Michael Walsh619aa332017-04-12 15:56:51 -0500145 ('host', 'Running'),
146 ('os_ping', '1'),
147 ('os_login', '1'),
148 ('os_run_cmd', '1')])
149
Michael Walsh7dc885b2018-03-14 17:51:59 -0500150# A match state for checking that the system is at "standby".
151standby_match_state = DotDict([('rest', '^1$'),
152 ('chassis', '^Off$'),
153 ('bmc', '^Ready$'),
154 ('boot_progress', '^$'),
155 ('operating_system', '^$'),
156 ('host', '^$')])
157
158# A match state for checking that the system is at "os running".
159os_running_match_state = DotDict([('chassis', '^On$'),
160 ('bmc', '^Ready$'),
161 ('boot_progress',
162 'FW Progress, Starting OS|OSStart'),
163 ('operating_system', 'BootComplete'),
164 ('host', '^Running$'),
165 ('os_ping', '^1$'),
166 ('os_login', '^1$'),
167 ('os_run_cmd', '^1$')])
168
Michael Walsh619aa332017-04-12 15:56:51 -0500169# A master dictionary to determine whether the os may be up.
170master_os_up_match = DotDict([('chassis', '^On$'),
171 ('bmc', '^Ready$'),
172 ('boot_progress',
Michael Walsh01975fa2017-08-20 20:51:36 -0500173 'FW Progress, Starting OS|OSStart'),
Michael Walsh56749222017-09-29 15:26:07 -0500174 ('operating_system', 'BootComplete'),
Michael Walsh192d5e72018-11-01 14:09:11 -0500175 ('host', '^Running|Quiesced$')])
Michael Walsh619aa332017-04-12 15:56:51 -0500176
Michael Walsh45ca6e42017-09-14 17:29:12 -0500177invalid_state_match = DotDict([('rest', '^$'),
178 ('chassis', '^$'),
179 ('bmc', '^$'),
180 ('boot_progress', '^$'),
Michael Walsh56749222017-09-29 15:26:07 -0500181 ('operating_system', '^$'),
Michael Walsh45ca6e42017-09-14 17:29:12 -0500182 ('host', '^$')])
183
Michael Walsh341c21e2017-01-17 16:25:20 -0600184
Michael Walsh619aa332017-04-12 15:56:51 -0500185def return_state_constant(state_name='default'):
Michael Walsh619aa332017-04-12 15:56:51 -0500186 r"""
Michael Walsh7dc885b2018-03-14 17:51:59 -0500187 Return the named state dictionary constant.
Michael Walsh619aa332017-04-12 15:56:51 -0500188 """
189
Michael Walsh7dc885b2018-03-14 17:51:59 -0500190 cmd_buf = "state = " + state_name
191 exec(cmd_buf)
192 return state
Michael Walsh619aa332017-04-12 15:56:51 -0500193
Michael Walsh619aa332017-04-12 15:56:51 -0500194
Michael Walsh70369fd2016-11-22 11:25:57 -0600195def anchor_state(state):
Michael Walsh70369fd2016-11-22 11:25:57 -0600196 r"""
197 Add regular expression anchors ("^" and "$") to the beginning and end of
198 each item in the state dictionary passed in. Return the resulting
199 dictionary.
200
201 Description of Arguments:
202 state A dictionary such as the one returned by the get_state()
203 function.
204 """
205
Michael Walsh2ce067a2017-02-27 14:24:07 -0600206 anchored_state = state.copy()
Michael Walsh70369fd2016-11-22 11:25:57 -0600207 for key, match_state_value in anchored_state.items():
208 anchored_state[key] = "^" + str(anchored_state[key]) + "$"
209
210 return anchored_state
211
Michael Walsh70369fd2016-11-22 11:25:57 -0600212
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600213def strip_anchor_state(state):
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600214 r"""
215 Strip regular expression anchors ("^" and "$") from the beginning and end
216 of each item in the state dictionary passed in. Return the resulting
217 dictionary.
218
219 Description of Arguments:
220 state A dictionary such as the one returned by the get_state()
221 function.
222 """
223
Michael Walsh2ce067a2017-02-27 14:24:07 -0600224 stripped_state = state.copy()
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600225 for key, match_state_value in stripped_state.items():
226 stripped_state[key] = stripped_state[key].strip("^$")
227
228 return stripped_state
229
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600230
Michael Walsh70369fd2016-11-22 11:25:57 -0600231def compare_states(state,
Michael Walsh45ca6e42017-09-14 17:29:12 -0500232 match_state,
233 match_type='and'):
Michael Walsh70369fd2016-11-22 11:25:57 -0600234 r"""
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600235 Compare 2 state dictionaries. Return True if they match and False if they
Michael Walsh70369fd2016-11-22 11:25:57 -0600236 don't. Note that the match_state dictionary does not need to have an entry
237 corresponding to each entry in the state dictionary. But for each entry
238 that it does have, the corresponding state entry will be checked for a
239 match.
240
241 Description of arguments:
242 state A state dictionary such as the one returned by the
243 get_state function.
244 match_state A dictionary whose key/value pairs are "state field"/
245 "state value". The state value is interpreted as a
246 regular expression. Every value in this dictionary is
Michael Walsh45ca6e42017-09-14 17:29:12 -0500247 considered. When match_type is 'and', if each and every
248 comparison matches, the two dictionaries are considered to
249 be matching. If match_type is 'or', if any two of the
250 elements compared match, the two dictionaries are
251 considered to be matching.
Michael Walsh7dc885b2018-03-14 17:51:59 -0500252 This value may also be any string accepted by
253 return_state_constant (e.g. "standby_match_state").
254 In such a case this function will call
255 return_state_constant to convert it to a proper
256 dictionary as described above.
Michael Walsh45ca6e42017-09-14 17:29:12 -0500257 match_type This may be 'and' or 'or'.
Michael Walsh70369fd2016-11-22 11:25:57 -0600258 """
259
Michael Walsh45ca6e42017-09-14 17:29:12 -0500260 error_message = gv.svalid_value(match_type, var_name="match_type",
261 valid_values=['and', 'or'])
262 if error_message != "":
263 BuiltIn().fail(gp.sprint_error(error_message))
264
Michael Walsh7dc885b2018-03-14 17:51:59 -0500265 if type(match_state) in (str, unicode):
266 match_state = return_state_constant(match_state)
267
Michael Walsh45ca6e42017-09-14 17:29:12 -0500268 default_match = (match_type == 'and')
Michael Walsh70369fd2016-11-22 11:25:57 -0600269 for key, match_state_value in match_state.items():
Michael Walsh97df71c2017-03-27 14:33:24 -0500270 # Blank match_state_value means "don't care".
271 if match_state_value == "":
272 continue
Michael Walsh70369fd2016-11-22 11:25:57 -0600273 try:
Michael Walsh45ca6e42017-09-14 17:29:12 -0500274 match = (re.match(match_state_value, str(state[key])) is not None)
Michael Walsh70369fd2016-11-22 11:25:57 -0600275 except KeyError:
276 match = False
Michael Walsh70369fd2016-11-22 11:25:57 -0600277
Michael Walsh45ca6e42017-09-14 17:29:12 -0500278 if match != default_match:
279 return match
280
281 return default_match
Michael Walsh70369fd2016-11-22 11:25:57 -0600282
Michael Walsh70369fd2016-11-22 11:25:57 -0600283
Michael Walsh70369fd2016-11-22 11:25:57 -0600284def get_os_state(os_host="",
285 os_username="",
286 os_password="",
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600287 req_states=default_os_req_states,
288 os_up=True,
Michael Walsh70369fd2016-11-22 11:25:57 -0600289 quiet=None):
Michael Walsh70369fd2016-11-22 11:25:57 -0600290 r"""
291 Get component states for the operating system such as ping, login,
292 etc, put them into a dictionary and return them to the caller.
293
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600294 Note that all substate values are strings.
295
Michael Walsh70369fd2016-11-22 11:25:57 -0600296 Description of arguments:
297 os_host The DNS name or IP address of the operating system.
298 This defaults to global ${OS_HOST}.
299 os_username The username to be used to login to the OS.
300 This defaults to global ${OS_USERNAME}.
301 os_password The password to be used to login to the OS.
302 This defaults to global ${OS_PASSWORD}.
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600303 req_states This is a list of states whose values are being requested by
304 the caller.
305 os_up If the caller knows that the os can't possibly be up, it can
306 improve performance by passing os_up=False. This function
307 will then simply return default values for all requested os
308 sub states.
Michael Walsh70369fd2016-11-22 11:25:57 -0600309 quiet Indicates whether status details (e.g. curl commands) should
310 be written to the console.
311 Defaults to either global value of ${QUIET} or to 1.
312 """
313
Michael Walsh619aa332017-04-12 15:56:51 -0500314 quiet = int(gp.get_var_value(quiet, 0))
Michael Walsh70369fd2016-11-22 11:25:57 -0600315
316 # Set parm defaults where necessary and validate all parms.
317 if os_host == "":
318 os_host = BuiltIn().get_variable_value("${OS_HOST}")
319 error_message = gv.svalid_value(os_host, var_name="os_host",
320 invalid_values=[None, ""])
321 if error_message != "":
322 BuiltIn().fail(gp.sprint_error(error_message))
323
324 if os_username == "":
325 os_username = BuiltIn().get_variable_value("${OS_USERNAME}")
326 error_message = gv.svalid_value(os_username, var_name="os_username",
327 invalid_values=[None, ""])
328 if error_message != "":
329 BuiltIn().fail(gp.sprint_error(error_message))
330
331 if os_password == "":
332 os_password = BuiltIn().get_variable_value("${OS_PASSWORD}")
333 error_message = gv.svalid_value(os_password, var_name="os_password",
334 invalid_values=[None, ""])
335 if error_message != "":
336 BuiltIn().fail(gp.sprint_error(error_message))
337
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600338 invalid_req_states = [sub_state for sub_state in req_states
339 if sub_state not in valid_os_req_states]
340 if len(invalid_req_states) > 0:
341 error_message = "The following req_states are not supported:\n" +\
342 gp.sprint_var(invalid_req_states)
343 BuiltIn().fail(gp.sprint_error(error_message))
Michael Walsh70369fd2016-11-22 11:25:57 -0600344
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600345 # Initialize all substate values supported by this function.
346 os_ping = 0
347 os_login = 0
348 os_run_cmd = 0
Michael Walsh70369fd2016-11-22 11:25:57 -0600349
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600350 if os_up:
351 if 'os_ping' in req_states:
352 # See if the OS pings.
353 cmd_buf = "ping -c 1 -w 2 " + os_host
354 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500355 gp.pissuing(cmd_buf)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600356 rc, out_buf = commands.getstatusoutput(cmd_buf)
357 if rc == 0:
358 os_ping = 1
Michael Walsh70369fd2016-11-22 11:25:57 -0600359
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600360 # Programming note: All attributes which do not require an ssh login
361 # should have been processed by this point.
362 master_req_login = ['os_login', 'os_run_cmd']
363 req_login = [sub_state for sub_state in req_states if sub_state in
364 master_req_login]
Michael Walsh97df71c2017-03-27 14:33:24 -0500365 must_login = (len(req_login) > 0)
Michael Walsh70369fd2016-11-22 11:25:57 -0600366
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600367 if must_login:
Michael Walsh6a9bd142018-07-24 16:10:29 -0500368 output, stderr, rc = bsu.os_execute_command("uptime", quiet=quiet,
Michael Walsh7fc33972018-08-07 14:55:03 -0500369 ignore_err=1,
370 time_out=20)
Michael Walsh6a9bd142018-07-24 16:10:29 -0500371 if rc == 0:
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600372 os_login = 1
Michael Walsh6a9bd142018-07-24 16:10:29 -0500373 os_run_cmd = 1
Michael Walsh3eb50022017-03-21 11:27:30 -0500374 else:
Michael Walsh6a9bd142018-07-24 16:10:29 -0500375 gp.dprint_vars(output, stderr)
376 gp.dprint_vars(rc, 1)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600377
378 os_state = DotDict()
379 for sub_state in req_states:
380 cmd_buf = "os_state['" + sub_state + "'] = str(" + sub_state + ")"
381 exec(cmd_buf)
Michael Walsh70369fd2016-11-22 11:25:57 -0600382
383 return os_state
384
Michael Walsh70369fd2016-11-22 11:25:57 -0600385
Michael Walsh70369fd2016-11-22 11:25:57 -0600386def get_state(openbmc_host="",
387 openbmc_username="",
388 openbmc_password="",
389 os_host="",
390 os_username="",
391 os_password="",
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600392 req_states=default_req_states,
Michael Walsh70369fd2016-11-22 11:25:57 -0600393 quiet=None):
Michael Walsh70369fd2016-11-22 11:25:57 -0600394 r"""
Michael Walsh619aa332017-04-12 15:56:51 -0500395 Get component states such as chassis state, bmc state, etc, put them into a
Michael Walsh70369fd2016-11-22 11:25:57 -0600396 dictionary and return them to the caller.
397
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600398 Note that all substate values are strings.
399
Michael Walsh70369fd2016-11-22 11:25:57 -0600400 Description of arguments:
401 openbmc_host The DNS name or IP address of the BMC.
402 This defaults to global ${OPENBMC_HOST}.
403 openbmc_username The username to be used to login to the BMC.
404 This defaults to global ${OPENBMC_USERNAME}.
405 openbmc_password The password to be used to login to the BMC.
406 This defaults to global ${OPENBMC_PASSWORD}.
407 os_host The DNS name or IP address of the operating system.
408 This defaults to global ${OS_HOST}.
409 os_username The username to be used to login to the OS.
410 This defaults to global ${OS_USERNAME}.
411 os_password The password to be used to login to the OS.
412 This defaults to global ${OS_PASSWORD}.
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600413 req_states This is a list of states whose values are being requested
414 by the caller.
Michael Walsh70369fd2016-11-22 11:25:57 -0600415 quiet Indicates whether status details (e.g. curl commands)
416 should be written to the console.
417 Defaults to either global value of ${QUIET} or to 1.
418 """
419
Michael Walsh619aa332017-04-12 15:56:51 -0500420 quiet = int(gp.get_var_value(quiet, 0))
Michael Walsh70369fd2016-11-22 11:25:57 -0600421
422 # Set parm defaults where necessary and validate all parms.
423 if openbmc_host == "":
424 openbmc_host = BuiltIn().get_variable_value("${OPENBMC_HOST}")
425 error_message = gv.svalid_value(openbmc_host,
426 var_name="openbmc_host",
427 invalid_values=[None, ""])
428 if error_message != "":
429 BuiltIn().fail(gp.sprint_error(error_message))
430
431 if openbmc_username == "":
432 openbmc_username = BuiltIn().get_variable_value("${OPENBMC_USERNAME}")
433 error_message = gv.svalid_value(openbmc_username,
434 var_name="openbmc_username",
435 invalid_values=[None, ""])
436 if error_message != "":
437 BuiltIn().fail(gp.sprint_error(error_message))
438
439 if openbmc_password == "":
440 openbmc_password = BuiltIn().get_variable_value("${OPENBMC_PASSWORD}")
441 error_message = gv.svalid_value(openbmc_password,
442 var_name="openbmc_password",
443 invalid_values=[None, ""])
444 if error_message != "":
445 BuiltIn().fail(gp.sprint_error(error_message))
446
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600447 # NOTE: OS parms are optional.
Michael Walsh70369fd2016-11-22 11:25:57 -0600448 if os_host == "":
449 os_host = BuiltIn().get_variable_value("${OS_HOST}")
450 if os_host is None:
451 os_host = ""
452
453 if os_username is "":
454 os_username = BuiltIn().get_variable_value("${OS_USERNAME}")
455 if os_username is None:
456 os_username = ""
457
458 if os_password is "":
459 os_password = BuiltIn().get_variable_value("${OS_PASSWORD}")
460 if os_password is None:
461 os_password = ""
462
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600463 invalid_req_states = [sub_state for sub_state in req_states
464 if sub_state not in valid_req_states]
465 if len(invalid_req_states) > 0:
466 error_message = "The following req_states are not supported:\n" +\
467 gp.sprint_var(invalid_req_states)
468 BuiltIn().fail(gp.sprint_error(error_message))
469
470 # Initialize all substate values supported by this function.
471 ping = 0
472 packet_loss = ''
473 uptime = ''
474 epoch_seconds = ''
Michael Walsh2b269de2017-10-09 11:18:11 -0500475 rest = ''
476 chassis = ''
477 requested_chassis = ''
478 bmc = ''
479 requested_bmc = ''
480 boot_progress = ''
481 operating_system = ''
482 host = ''
483 requested_host = ''
484 attempts_left = ''
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600485
Michael Walsh70369fd2016-11-22 11:25:57 -0600486 # Get the component states.
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600487 if 'ping' in req_states:
488 # See if the OS pings.
489 cmd_buf = "ping -c 1 -w 2 " + openbmc_host
490 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500491 gp.pissuing(cmd_buf)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600492 rc, out_buf = commands.getstatusoutput(cmd_buf)
493 if rc == 0:
494 ping = 1
495
496 if 'packet_loss' in req_states:
497 # See if the OS pings.
498 cmd_buf = "ping -c 5 -w 5 " + openbmc_host +\
499 " | egrep 'packet loss' | sed -re 's/.* ([0-9]+)%.*/\\1/g'"
500 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500501 gp.pissuing(cmd_buf)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600502 rc, out_buf = commands.getstatusoutput(cmd_buf)
503 if rc == 0:
504 packet_loss = out_buf.rstrip("\n")
505
Michael Walshe53e47a2017-06-30 17:03:24 -0500506 if 'uptime' in req_states:
Michael Walshfa765932017-10-13 14:07:22 -0500507 # Sometimes reading uptime results in a blank value. Call with
508 # wait_until_keyword_succeeds to ensure a non-blank value is obtained.
509 remote_cmd_buf = "read uptime filler 2>/dev/null < /proc/uptime" +\
510 " && [ ! -z \"${uptime}\" ] && echo ${uptime}"
Joy Onyerikwu004ad3c2018-06-11 16:29:56 -0500511 cmd_buf = ["BMC Execute Command",
Michael Walsh8c34eb72018-06-14 11:26:16 -0500512 re.sub('\\$', '\\$', remote_cmd_buf), 'quiet=1']
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600513 if not quiet:
Michael Walshaf722b12018-08-17 14:55:32 -0500514 # Get loc_test_mode parm for improved output on pissuing.
515 # See sprint_issuing in gen_print.py for details.
516 loc_test_mode = int(gp.get_var_value(var_name="test_mode",
517 default=0))
518 grp.rpissuing_keyword(cmd_buf, loc_test_mode)
519 gp.pissuing(remote_cmd_buf, loc_test_mode)
Michael Walshfa765932017-10-13 14:07:22 -0500520 try:
521 stdout, stderr, rc =\
Michael Walshcaccd852017-11-01 17:58:41 -0500522 BuiltIn().wait_until_keyword_succeeds("10 sec", "0 sec",
Michael Walshfa765932017-10-13 14:07:22 -0500523 *cmd_buf)
Michael Walsh97df71c2017-03-27 14:33:24 -0500524 if rc == 0 and stderr == "":
525 uptime = stdout
Michael Walshfa765932017-10-13 14:07:22 -0500526 except AssertionError as my_assertion_error:
527 pass
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600528
Michael Walshe53e47a2017-06-30 17:03:24 -0500529 if 'epoch_seconds' in req_states:
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600530 date_cmd_buf = "date -u +%s"
531 if USE_BMC_EPOCH_TIME:
Michael Walshe53e47a2017-06-30 17:03:24 -0500532 cmd_buf = ["BMC Execute Command", date_cmd_buf, 'quiet=${1}']
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600533 if not quiet:
534 grp.rpissuing_keyword(cmd_buf)
Michael Walsh97df71c2017-03-27 14:33:24 -0500535 status, ret_values = \
536 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
537 if status == "PASS":
538 stdout, stderr, rc = ret_values
539 if rc == 0 and stderr == "":
540 epoch_seconds = stdout.rstrip("\n")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600541 else:
542 shell_rc, out_buf = gc.cmd_fnc_u(date_cmd_buf,
Michael Walshfa765932017-10-13 14:07:22 -0500543 quiet=quiet,
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600544 print_output=0)
545 if shell_rc == 0:
546 epoch_seconds = out_buf.rstrip("\n")
547
Michael Walsh56749222017-09-29 15:26:07 -0500548 master_req_rest = ['rest', 'host', 'requested_host', 'operating_system',
549 'attempts_left', 'boot_progress', 'chassis',
550 'requested_chassis' 'bmc' 'requested_bmc']
551
Michael Walshb95eb542017-03-31 09:39:20 -0500552 req_rest = [sub_state for sub_state in req_states if sub_state in
553 master_req_rest]
554 need_rest = (len(req_rest) > 0)
Michael Walsh56749222017-09-29 15:26:07 -0500555 state = DotDict()
556 if need_rest:
Michael Walsh940d6912017-10-27 12:32:33 -0500557 cmd_buf = ["Read Properties", SYSTEM_STATE_URI + "enumerate",
Michael Walsh56749222017-09-29 15:26:07 -0500558 "quiet=${" + str(quiet) + "}"]
Michael Walsh341c21e2017-01-17 16:25:20 -0600559 grp.rdpissuing_keyword(cmd_buf)
Michael Walsh97df71c2017-03-27 14:33:24 -0500560 status, ret_values = \
561 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
562 if status == "PASS":
Michael Walsh56749222017-09-29 15:26:07 -0500563 state['rest'] = '1'
Michael Walsh341c21e2017-01-17 16:25:20 -0600564 else:
Michael Walsh56749222017-09-29 15:26:07 -0500565 state['rest'] = '0'
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600566
Michael Walsh2b269de2017-10-09 11:18:11 -0500567 if int(state['rest']):
568 for url_path in ret_values:
569 for attr_name in ret_values[url_path]:
570 # Create a state key value based on the attr_name.
Joy Onyerikwu004ad3c2018-06-11 16:29:56 -0500571 if isinstance(ret_values[url_path][attr_name], unicode):
Michael Walsh2b269de2017-10-09 11:18:11 -0500572 ret_values[url_path][attr_name] = \
573 re.sub(r'.*\.', "",
574 ret_values[url_path][attr_name])
575 # Do some key name manipulations.
576 new_attr_name = re.sub(r'^Current|(State|Transition)$',
577 "", attr_name)
578 new_attr_name = re.sub(r'BMC', r'Bmc', new_attr_name)
579 new_attr_name = re.sub(r'([A-Z][a-z])', r'_\1',
580 new_attr_name)
581 new_attr_name = new_attr_name.lower().lstrip("_")
582 new_attr_name = re.sub(r'power', r'chassis', new_attr_name)
583 if new_attr_name in req_states:
584 state[new_attr_name] = ret_values[url_path][attr_name]
Michael Walshb95eb542017-03-31 09:39:20 -0500585
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600586 for sub_state in req_states:
Michael Walsh56749222017-09-29 15:26:07 -0500587 if sub_state in state:
588 continue
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600589 if sub_state.startswith("os_"):
590 # We pass "os_" requests on to get_os_state.
591 continue
592 cmd_buf = "state['" + sub_state + "'] = str(" + sub_state + ")"
593 exec(cmd_buf)
594
595 if os_host == "":
596 # The caller has not specified an os_host so as far as we're concerned,
597 # it doesn't exist.
598 return state
599
600 os_req_states = [sub_state for sub_state in req_states
601 if sub_state.startswith('os_')]
602
603 if len(os_req_states) > 0:
604 # The caller has specified an os_host and they have requested
605 # information on os substates.
606
607 # Based on the information gathered on bmc, we'll try to make a
608 # determination of whether the os is even up. We'll pass the result
609 # of that assessment to get_os_state to enhance performance.
610 os_up_match = DotDict()
611 for sub_state in master_os_up_match:
612 if sub_state in req_states:
613 os_up_match[sub_state] = master_os_up_match[sub_state]
Michael Walsh70369fd2016-11-22 11:25:57 -0600614 os_up = compare_states(state, os_up_match)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600615 os_state = get_os_state(os_host=os_host,
616 os_username=os_username,
617 os_password=os_password,
618 req_states=os_req_states,
619 os_up=os_up,
620 quiet=quiet)
621 # Append os_state dictionary to ours.
622 state.update(os_state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600623
624 return state
625
Michael Walsh70369fd2016-11-22 11:25:57 -0600626
Michael Walsh70369fd2016-11-22 11:25:57 -0600627def check_state(match_state,
628 invert=0,
629 print_string="",
630 openbmc_host="",
631 openbmc_username="",
632 openbmc_password="",
633 os_host="",
634 os_username="",
635 os_password="",
636 quiet=None):
Michael Walsh70369fd2016-11-22 11:25:57 -0600637 r"""
638 Check that the Open BMC machine's composite state matches the specified
639 state. On success, this keyword returns the machine's composite state as a
640 dictionary.
641
642 Description of arguments:
643 match_state A dictionary whose key/value pairs are "state field"/
644 "state value". The state value is interpreted as a
645 regular expression. Example call from robot:
Michael Walsh341c21e2017-01-17 16:25:20 -0600646 ${match_state}= Create Dictionary chassis=^On$
647 ... bmc=^Ready$
Michael Walsh01975fa2017-08-20 20:51:36 -0500648 ... boot_progress=^OSStart$
Michael Walsh70369fd2016-11-22 11:25:57 -0600649 ${state}= Check State &{match_state}
650 invert If this flag is set, this function will succeed if the
651 states do NOT match.
652 print_string This function will print this string to the console prior
653 to getting the state.
654 openbmc_host The DNS name or IP address of the BMC.
655 This defaults to global ${OPENBMC_HOST}.
656 openbmc_username The username to be used to login to the BMC.
657 This defaults to global ${OPENBMC_USERNAME}.
658 openbmc_password The password to be used to login to the BMC.
659 This defaults to global ${OPENBMC_PASSWORD}.
660 os_host The DNS name or IP address of the operating system.
661 This defaults to global ${OS_HOST}.
662 os_username The username to be used to login to the OS.
663 This defaults to global ${OS_USERNAME}.
664 os_password The password to be used to login to the OS.
665 This defaults to global ${OS_PASSWORD}.
666 quiet Indicates whether status details should be written to the
667 console. Defaults to either global value of ${QUIET} or
668 to 1.
669 """
670
Michael Walsh619aa332017-04-12 15:56:51 -0500671 quiet = int(gp.get_var_value(quiet, 0))
Michael Walsh70369fd2016-11-22 11:25:57 -0600672
673 grp.rprint(print_string)
674
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600675 req_states = match_state.keys()
Michael Walsh70369fd2016-11-22 11:25:57 -0600676 # Initialize state.
677 state = get_state(openbmc_host=openbmc_host,
678 openbmc_username=openbmc_username,
679 openbmc_password=openbmc_password,
680 os_host=os_host,
681 os_username=os_username,
682 os_password=os_password,
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600683 req_states=req_states,
Michael Walsh70369fd2016-11-22 11:25:57 -0600684 quiet=quiet)
685 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500686 gp.print_var(state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600687
688 match = compare_states(state, match_state)
689
690 if invert and match:
691 fail_msg = "The current state of the machine matches the match" +\
692 " state:\n" + gp.sprint_varx("state", state)
693 BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
694 elif not invert and not match:
695 fail_msg = "The current state of the machine does NOT match the" +\
696 " match state:\n" +\
697 gp.sprint_varx("state", state)
698 BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
699
700 return state
701
Michael Walsh70369fd2016-11-22 11:25:57 -0600702
Michael Walshf893ba02017-01-10 10:28:05 -0600703def wait_state(match_state=(),
Michael Walsh70369fd2016-11-22 11:25:57 -0600704 wait_time="1 min",
705 interval="1 second",
706 invert=0,
707 openbmc_host="",
708 openbmc_username="",
709 openbmc_password="",
710 os_host="",
711 os_username="",
712 os_password="",
713 quiet=None):
Michael Walsh70369fd2016-11-22 11:25:57 -0600714 r"""
715 Wait for the Open BMC machine's composite state to match the specified
716 state. On success, this keyword returns the machine's composite state as
717 a dictionary.
718
719 Description of arguments:
720 match_state A dictionary whose key/value pairs are "state field"/
721 "state value". See check_state (above) for details.
Michael Walsh619aa332017-04-12 15:56:51 -0500722 This value may also be any string accepted by
723 return_state_constant (e.g. "standby_match_state").
724 In such a case this function will call
725 return_state_constant to convert it to a proper
726 dictionary as described above.
Michael Walsh70369fd2016-11-22 11:25:57 -0600727 wait_time The total amount of time to wait for the desired state.
728 This value may be expressed in Robot Framework's time
729 format (e.g. 1 minute, 2 min 3 s, 4.5).
730 interval The amount of time between state checks.
731 This value may be expressed in Robot Framework's time
732 format (e.g. 1 minute, 2 min 3 s, 4.5).
733 invert If this flag is set, this function will for the state of
734 the machine to cease to match the match state.
735 openbmc_host The DNS name or IP address of the BMC.
736 This defaults to global ${OPENBMC_HOST}.
737 openbmc_username The username to be used to login to the BMC.
738 This defaults to global ${OPENBMC_USERNAME}.
739 openbmc_password The password to be used to login to the BMC.
740 This defaults to global ${OPENBMC_PASSWORD}.
741 os_host The DNS name or IP address of the operating system.
742 This defaults to global ${OS_HOST}.
743 os_username The username to be used to login to the OS.
744 This defaults to global ${OS_USERNAME}.
745 os_password The password to be used to login to the OS.
746 This defaults to global ${OS_PASSWORD}.
747 quiet Indicates whether status details should be written to the
748 console. Defaults to either global value of ${QUIET} or
749 to 1.
750 """
751
Michael Walsh619aa332017-04-12 15:56:51 -0500752 quiet = int(gp.get_var_value(quiet, 0))
753
754 if type(match_state) in (str, unicode):
755 match_state = return_state_constant(match_state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600756
757 if not quiet:
758 if invert:
759 alt_text = "cease to "
760 else:
761 alt_text = ""
Joy Onyerikwu004ad3c2018-06-11 16:29:56 -0500762 gp.print_timen("Checking every " + str(interval) + " for up to "
763 + str(wait_time) + " for the state of the machine to "
764 + alt_text + "match the state shown below.")
Michael Walsh3eb50022017-03-21 11:27:30 -0500765 gp.print_var(match_state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600766
Michael Walshf893ba02017-01-10 10:28:05 -0600767 if quiet:
Michael Walsh341c21e2017-01-17 16:25:20 -0600768 print_string = ""
Michael Walshf893ba02017-01-10 10:28:05 -0600769 else:
Michael Walsh341c21e2017-01-17 16:25:20 -0600770 print_string = "#"
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600771
772 debug = int(BuiltIn().get_variable_value("${debug}", "0"))
773 if debug:
774 # In debug we print state so no need to print the "#".
775 print_string = ""
776 check_state_quiet = 1 - debug
Michael Walsh70369fd2016-11-22 11:25:57 -0600777 cmd_buf = ["Check State", match_state, "invert=${" + str(invert) + "}",
Michael Walshf893ba02017-01-10 10:28:05 -0600778 "print_string=" + print_string, "openbmc_host=" + openbmc_host,
Michael Walsh70369fd2016-11-22 11:25:57 -0600779 "openbmc_username=" + openbmc_username,
780 "openbmc_password=" + openbmc_password, "os_host=" + os_host,
781 "os_username=" + os_username, "os_password=" + os_password,
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600782 "quiet=${" + str(check_state_quiet) + "}"]
Michael Walsh70369fd2016-11-22 11:25:57 -0600783 grp.rdpissuing_keyword(cmd_buf)
Michael Walsh619aa332017-04-12 15:56:51 -0500784 try:
785 state = BuiltIn().wait_until_keyword_succeeds(wait_time, interval,
786 *cmd_buf)
787 except AssertionError as my_assertion_error:
788 gp.printn()
789 message = my_assertion_error.args[0]
790 BuiltIn().fail(message)
791
Michael Walsh70369fd2016-11-22 11:25:57 -0600792 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500793 gp.printn()
Michael Walsh70369fd2016-11-22 11:25:57 -0600794 if invert:
Michael Walsh3eb50022017-03-21 11:27:30 -0500795 gp.print_timen("The states no longer match:")
Michael Walsh70369fd2016-11-22 11:25:57 -0600796 else:
Michael Walsh3eb50022017-03-21 11:27:30 -0500797 gp.print_timen("The states match:")
798 gp.print_var(state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600799
800 return state
801
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600802
Michael Walsh619aa332017-04-12 15:56:51 -0500803def wait_for_comm_cycle(start_boot_seconds,
804 quiet=None):
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600805 r"""
806 Wait for communications to the BMC to stop working and then resume working.
807 This function is useful when you have initiated some kind of reboot.
808
809 Description of arguments:
810 start_boot_seconds The time that the boot test started. The format is the
811 epoch time in seconds, i.e. the number of seconds since
812 1970-01-01 00:00:00 UTC. This value should be obtained
813 from the BMC so that it is not dependent on any kind of
814 synchronization between this machine and the target BMC
815 This will allow this program to work correctly even in
816 a simulated environment. This value should be obtained
817 by the caller prior to initiating a reboot. It can be
818 obtained as follows:
819 state = st.get_state(req_states=['epoch_seconds'])
820 """
821
Michael Walsh619aa332017-04-12 15:56:51 -0500822 quiet = int(gp.get_var_value(quiet, 0))
823
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600824 # Validate parms.
825 error_message = gv.svalid_integer(start_boot_seconds,
826 var_name="start_boot_seconds")
827 if error_message != "":
828 BuiltIn().fail(gp.sprint_error(error_message))
829
830 match_state = anchor_state(DotDict([('packet_loss', '100')]))
831 # Wait for 100% 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 match_state['packet_loss'] = '^0$'
835 # Wait for 0% packet loss trying to ping machine.
Michael Walsh619aa332017-04-12 15:56:51 -0500836 wait_state(match_state, wait_time="8 mins", interval="0 seconds")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600837
838 # Get the uptime and epoch seconds for comparisons. We want to be sure
839 # that the uptime is less than the elapsed boot time. Further proof that
840 # a reboot has indeed occurred (vs random network instability giving a
Michael Walshc4c05d32018-05-29 11:39:39 -0500841 # false positive. We also use wait_state because the BMC may take a short
842 # while to be ready to process SSH requests.
Joy Onyerikwu004ad3c2018-06-11 16:29:56 -0500843 match_state = DotDict([('uptime', '^[0-9\\.]+$'),
Michael Walshc4c05d32018-05-29 11:39:39 -0500844 ('epoch_seconds', '^[0-9]+$')])
845 state = wait_state(match_state, wait_time="2 mins", interval="1 second")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600846
847 elapsed_boot_time = int(state['epoch_seconds']) - start_boot_seconds
Michael Walsh619aa332017-04-12 15:56:51 -0500848 gp.qprint_var(elapsed_boot_time)
Michael Walshe77585a2017-12-14 11:02:28 -0600849 if state['uptime'] == "":
850 error_message = "Unable to obtain uptime from the BMC. BMC is not" +\
851 " communicating."
852 BuiltIn().fail(gp.sprint_error(error_message))
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600853 if int(float(state['uptime'])) < elapsed_boot_time:
854 uptime = state['uptime']
Michael Walsh619aa332017-04-12 15:56:51 -0500855 gp.qprint_var(uptime)
Joy Onyerikwu004ad3c2018-06-11 16:29:56 -0500856 gp.qprint_timen("The uptime is less than the elapsed boot time,"
857 + " as expected.")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600858 else:
859 error_message = "The uptime is greater than the elapsed boot time," +\
860 " which is unexpected:\n" +\
861 gp.sprint_var(start_boot_seconds) +\
862 gp.sprint_var(state)
863 BuiltIn().fail(gp.sprint_error(error_message))
864
Michael Walsh619aa332017-04-12 15:56:51 -0500865 gp.qprint_timen("Verifying that REST API interface is working.")
Michael Walshb95eb542017-03-31 09:39:20 -0500866 match_state = DotDict([('rest', '^1$')])
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600867 state = wait_state(match_state, wait_time="5 mins", interval="2 seconds")