blob: 4020b9c65a563270d5786fd5cb0059340b5cfbb2 [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$'),
George Keishing569796c2019-06-10 13:25:53 -0500154 ('boot_progress', '^Off|Unspecified$'),
155 ('operating_system', '^Inactive$'),
156 ('host', '^Off$')])
Michael Walsh7dc885b2018-03-14 17:51:59 -0500157
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
George Keishing569796c2019-06-10 13:25:53 -0500185def return_state_constant(state_name='default_state'):
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
George Keishing569796c2019-06-10 13:25:53 -0500190 return eval(state_name)
Michael Walsh619aa332017-04-12 15:56:51 -0500191
Michael Walsh619aa332017-04-12 15:56:51 -0500192
Michael Walsh70369fd2016-11-22 11:25:57 -0600193def anchor_state(state):
Michael Walsh70369fd2016-11-22 11:25:57 -0600194 r"""
195 Add regular expression anchors ("^" and "$") to the beginning and end of
196 each item in the state dictionary passed in. Return the resulting
197 dictionary.
198
199 Description of Arguments:
200 state A dictionary such as the one returned by the get_state()
201 function.
202 """
203
Michael Walsh2ce067a2017-02-27 14:24:07 -0600204 anchored_state = state.copy()
Michael Walsh70369fd2016-11-22 11:25:57 -0600205 for key, match_state_value in anchored_state.items():
206 anchored_state[key] = "^" + str(anchored_state[key]) + "$"
207
208 return anchored_state
209
Michael Walsh70369fd2016-11-22 11:25:57 -0600210
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600211def strip_anchor_state(state):
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600212 r"""
213 Strip regular expression anchors ("^" and "$") from the beginning and end
214 of each item in the state dictionary passed in. Return the resulting
215 dictionary.
216
217 Description of Arguments:
218 state A dictionary such as the one returned by the get_state()
219 function.
220 """
221
Michael Walsh2ce067a2017-02-27 14:24:07 -0600222 stripped_state = state.copy()
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600223 for key, match_state_value in stripped_state.items():
224 stripped_state[key] = stripped_state[key].strip("^$")
225
226 return stripped_state
227
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600228
Michael Walsh70369fd2016-11-22 11:25:57 -0600229def compare_states(state,
Michael Walsh45ca6e42017-09-14 17:29:12 -0500230 match_state,
231 match_type='and'):
Michael Walsh70369fd2016-11-22 11:25:57 -0600232 r"""
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600233 Compare 2 state dictionaries. Return True if they match and False if they
Michael Walsh70369fd2016-11-22 11:25:57 -0600234 don't. Note that the match_state dictionary does not need to have an entry
235 corresponding to each entry in the state dictionary. But for each entry
236 that it does have, the corresponding state entry will be checked for a
237 match.
238
239 Description of arguments:
240 state A state dictionary such as the one returned by the
241 get_state function.
242 match_state A dictionary whose key/value pairs are "state field"/
243 "state value". The state value is interpreted as a
244 regular expression. Every value in this dictionary is
Michael Walsh45ca6e42017-09-14 17:29:12 -0500245 considered. When match_type is 'and', if each and every
246 comparison matches, the two dictionaries are considered to
247 be matching. If match_type is 'or', if any two of the
248 elements compared match, the two dictionaries are
249 considered to be matching.
Michael Walsh7dc885b2018-03-14 17:51:59 -0500250 This value may also be any string accepted by
251 return_state_constant (e.g. "standby_match_state").
252 In such a case this function will call
253 return_state_constant to convert it to a proper
254 dictionary as described above.
Michael Walsh45ca6e42017-09-14 17:29:12 -0500255 match_type This may be 'and' or 'or'.
Michael Walsh70369fd2016-11-22 11:25:57 -0600256 """
257
Michael Walsh45ca6e42017-09-14 17:29:12 -0500258 error_message = gv.svalid_value(match_type, var_name="match_type",
259 valid_values=['and', 'or'])
260 if error_message != "":
261 BuiltIn().fail(gp.sprint_error(error_message))
262
George Keishing569796c2019-06-10 13:25:53 -0500263 try:
Michael Walsh7dc885b2018-03-14 17:51:59 -0500264 match_state = return_state_constant(match_state)
George Keishing569796c2019-06-10 13:25:53 -0500265 except TypeError:
266 pass
Michael Walsh7dc885b2018-03-14 17:51:59 -0500267
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.
George Keishing569796c2019-06-10 13:25:53 -0500353 rc, out_buf = gc.shell_cmd("ping -c 1 -w 2 " + os_host,
354 print_output=0, show_err=0,
355 ignore_err=1)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600356 if rc == 0:
357 os_ping = 1
Michael Walsh70369fd2016-11-22 11:25:57 -0600358
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600359 # Programming note: All attributes which do not require an ssh login
360 # should have been processed by this point.
361 master_req_login = ['os_login', 'os_run_cmd']
362 req_login = [sub_state for sub_state in req_states if sub_state in
363 master_req_login]
Michael Walsh97df71c2017-03-27 14:33:24 -0500364 must_login = (len(req_login) > 0)
Michael Walsh70369fd2016-11-22 11:25:57 -0600365
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600366 if must_login:
Michael Walsh6a9bd142018-07-24 16:10:29 -0500367 output, stderr, rc = bsu.os_execute_command("uptime", quiet=quiet,
Michael Walsh7fc33972018-08-07 14:55:03 -0500368 ignore_err=1,
369 time_out=20)
Michael Walsh6a9bd142018-07-24 16:10:29 -0500370 if rc == 0:
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600371 os_login = 1
Michael Walsh6a9bd142018-07-24 16:10:29 -0500372 os_run_cmd = 1
Michael Walsh3eb50022017-03-21 11:27:30 -0500373 else:
Michael Walsh6a9bd142018-07-24 16:10:29 -0500374 gp.dprint_vars(output, stderr)
375 gp.dprint_vars(rc, 1)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600376
377 os_state = DotDict()
378 for sub_state in req_states:
379 cmd_buf = "os_state['" + sub_state + "'] = str(" + sub_state + ")"
380 exec(cmd_buf)
Michael Walsh70369fd2016-11-22 11:25:57 -0600381
382 return os_state
383
Michael Walsh70369fd2016-11-22 11:25:57 -0600384
Michael Walsh70369fd2016-11-22 11:25:57 -0600385def get_state(openbmc_host="",
386 openbmc_username="",
387 openbmc_password="",
388 os_host="",
389 os_username="",
390 os_password="",
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600391 req_states=default_req_states,
Michael Walsh70369fd2016-11-22 11:25:57 -0600392 quiet=None):
Michael Walsh70369fd2016-11-22 11:25:57 -0600393 r"""
Michael Walsh619aa332017-04-12 15:56:51 -0500394 Get component states such as chassis state, bmc state, etc, put them into a
Michael Walsh70369fd2016-11-22 11:25:57 -0600395 dictionary and return them to the caller.
396
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600397 Note that all substate values are strings.
398
Michael Walsh70369fd2016-11-22 11:25:57 -0600399 Description of arguments:
400 openbmc_host The DNS name or IP address of the BMC.
401 This defaults to global ${OPENBMC_HOST}.
402 openbmc_username The username to be used to login to the BMC.
403 This defaults to global ${OPENBMC_USERNAME}.
404 openbmc_password The password to be used to login to the BMC.
405 This defaults to global ${OPENBMC_PASSWORD}.
406 os_host The DNS name or IP address of the operating system.
407 This defaults to global ${OS_HOST}.
408 os_username The username to be used to login to the OS.
409 This defaults to global ${OS_USERNAME}.
410 os_password The password to be used to login to the OS.
411 This defaults to global ${OS_PASSWORD}.
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600412 req_states This is a list of states whose values are being requested
413 by the caller.
Michael Walsh70369fd2016-11-22 11:25:57 -0600414 quiet Indicates whether status details (e.g. curl commands)
415 should be written to the console.
416 Defaults to either global value of ${QUIET} or to 1.
417 """
418
Michael Walsh619aa332017-04-12 15:56:51 -0500419 quiet = int(gp.get_var_value(quiet, 0))
Michael Walsh70369fd2016-11-22 11:25:57 -0600420
421 # Set parm defaults where necessary and validate all parms.
422 if openbmc_host == "":
423 openbmc_host = BuiltIn().get_variable_value("${OPENBMC_HOST}")
424 error_message = gv.svalid_value(openbmc_host,
425 var_name="openbmc_host",
426 invalid_values=[None, ""])
427 if error_message != "":
428 BuiltIn().fail(gp.sprint_error(error_message))
429
430 if openbmc_username == "":
431 openbmc_username = BuiltIn().get_variable_value("${OPENBMC_USERNAME}")
432 error_message = gv.svalid_value(openbmc_username,
433 var_name="openbmc_username",
434 invalid_values=[None, ""])
435 if error_message != "":
436 BuiltIn().fail(gp.sprint_error(error_message))
437
438 if openbmc_password == "":
439 openbmc_password = BuiltIn().get_variable_value("${OPENBMC_PASSWORD}")
440 error_message = gv.svalid_value(openbmc_password,
441 var_name="openbmc_password",
442 invalid_values=[None, ""])
443 if error_message != "":
444 BuiltIn().fail(gp.sprint_error(error_message))
445
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600446 # NOTE: OS parms are optional.
Michael Walsh70369fd2016-11-22 11:25:57 -0600447 if os_host == "":
448 os_host = BuiltIn().get_variable_value("${OS_HOST}")
449 if os_host is None:
450 os_host = ""
451
452 if os_username is "":
453 os_username = BuiltIn().get_variable_value("${OS_USERNAME}")
454 if os_username is None:
455 os_username = ""
456
457 if os_password is "":
458 os_password = BuiltIn().get_variable_value("${OS_PASSWORD}")
459 if os_password is None:
460 os_password = ""
461
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600462 invalid_req_states = [sub_state for sub_state in req_states
463 if sub_state not in valid_req_states]
464 if len(invalid_req_states) > 0:
465 error_message = "The following req_states are not supported:\n" +\
466 gp.sprint_var(invalid_req_states)
467 BuiltIn().fail(gp.sprint_error(error_message))
468
469 # Initialize all substate values supported by this function.
470 ping = 0
471 packet_loss = ''
472 uptime = ''
473 epoch_seconds = ''
Michael Walsh2b269de2017-10-09 11:18:11 -0500474 rest = ''
475 chassis = ''
476 requested_chassis = ''
477 bmc = ''
478 requested_bmc = ''
479 boot_progress = ''
480 operating_system = ''
481 host = ''
482 requested_host = ''
483 attempts_left = ''
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600484
Michael Walsh70369fd2016-11-22 11:25:57 -0600485 # Get the component states.
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600486 if 'ping' in req_states:
487 # See if the OS pings.
George Keishing569796c2019-06-10 13:25:53 -0500488 rc, out_buf = gc.shell_cmd("ping -c 1 -w 2 " + openbmc_host,
489 print_output=0, show_err=0,
490 ignore_err=1)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600491 if rc == 0:
492 ping = 1
493
494 if 'packet_loss' in req_states:
495 # See if the OS pings.
496 cmd_buf = "ping -c 5 -w 5 " + openbmc_host +\
497 " | egrep 'packet loss' | sed -re 's/.* ([0-9]+)%.*/\\1/g'"
George Keishing569796c2019-06-10 13:25:53 -0500498 rc, out_buf = gc.shell_cmd(cmd_buf,
499 print_output=0, show_err=0,
500 ignore_err=1)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600501 if rc == 0:
502 packet_loss = out_buf.rstrip("\n")
503
Michael Walshe53e47a2017-06-30 17:03:24 -0500504 if 'uptime' in req_states:
Michael Walshfa765932017-10-13 14:07:22 -0500505 # Sometimes reading uptime results in a blank value. Call with
506 # wait_until_keyword_succeeds to ensure a non-blank value is obtained.
507 remote_cmd_buf = "read uptime filler 2>/dev/null < /proc/uptime" +\
508 " && [ ! -z \"${uptime}\" ] && echo ${uptime}"
Joy Onyerikwu004ad3c2018-06-11 16:29:56 -0500509 cmd_buf = ["BMC Execute Command",
George Keishing569796c2019-06-10 13:25:53 -0500510 re.sub('\\$', '\\$', remote_cmd_buf), 'quiet=1',
511 'test_mode=0']
512 gp.print_issuing(cmd_buf, 0)
513 gp.print_issuing(remote_cmd_buf, 0)
Michael Walshfa765932017-10-13 14:07:22 -0500514 try:
515 stdout, stderr, rc =\
Michael Walshcaccd852017-11-01 17:58:41 -0500516 BuiltIn().wait_until_keyword_succeeds("10 sec", "0 sec",
Michael Walshfa765932017-10-13 14:07:22 -0500517 *cmd_buf)
Michael Walsh97df71c2017-03-27 14:33:24 -0500518 if rc == 0 and stderr == "":
519 uptime = stdout
Michael Walshfa765932017-10-13 14:07:22 -0500520 except AssertionError as my_assertion_error:
521 pass
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600522
Michael Walshe53e47a2017-06-30 17:03:24 -0500523 if 'epoch_seconds' in req_states:
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600524 date_cmd_buf = "date -u +%s"
525 if USE_BMC_EPOCH_TIME:
Michael Walshe53e47a2017-06-30 17:03:24 -0500526 cmd_buf = ["BMC Execute Command", date_cmd_buf, 'quiet=${1}']
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600527 if not quiet:
528 grp.rpissuing_keyword(cmd_buf)
Michael Walsh97df71c2017-03-27 14:33:24 -0500529 status, ret_values = \
530 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
531 if status == "PASS":
532 stdout, stderr, rc = ret_values
533 if rc == 0 and stderr == "":
534 epoch_seconds = stdout.rstrip("\n")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600535 else:
536 shell_rc, out_buf = gc.cmd_fnc_u(date_cmd_buf,
Michael Walshfa765932017-10-13 14:07:22 -0500537 quiet=quiet,
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600538 print_output=0)
539 if shell_rc == 0:
540 epoch_seconds = out_buf.rstrip("\n")
541
Michael Walsh56749222017-09-29 15:26:07 -0500542 master_req_rest = ['rest', 'host', 'requested_host', 'operating_system',
543 'attempts_left', 'boot_progress', 'chassis',
544 'requested_chassis' 'bmc' 'requested_bmc']
545
Michael Walshb95eb542017-03-31 09:39:20 -0500546 req_rest = [sub_state for sub_state in req_states if sub_state in
547 master_req_rest]
548 need_rest = (len(req_rest) > 0)
Michael Walsh56749222017-09-29 15:26:07 -0500549 state = DotDict()
550 if need_rest:
Michael Walsh940d6912017-10-27 12:32:33 -0500551 cmd_buf = ["Read Properties", SYSTEM_STATE_URI + "enumerate",
Michael Walsh56749222017-09-29 15:26:07 -0500552 "quiet=${" + str(quiet) + "}"]
Michael Walsh341c21e2017-01-17 16:25:20 -0600553 grp.rdpissuing_keyword(cmd_buf)
Michael Walsh97df71c2017-03-27 14:33:24 -0500554 status, ret_values = \
555 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
556 if status == "PASS":
Michael Walsh56749222017-09-29 15:26:07 -0500557 state['rest'] = '1'
Michael Walsh341c21e2017-01-17 16:25:20 -0600558 else:
Michael Walsh56749222017-09-29 15:26:07 -0500559 state['rest'] = '0'
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600560
Michael Walsh2b269de2017-10-09 11:18:11 -0500561 if int(state['rest']):
562 for url_path in ret_values:
563 for attr_name in ret_values[url_path]:
564 # Create a state key value based on the attr_name.
George Keishing569796c2019-06-10 13:25:53 -0500565 try:
Michael Walsh2b269de2017-10-09 11:18:11 -0500566 ret_values[url_path][attr_name] = \
567 re.sub(r'.*\.', "",
568 ret_values[url_path][attr_name])
George Keishing569796c2019-06-10 13:25:53 -0500569 except TypeError:
570 pass
Michael Walsh2b269de2017-10-09 11:18:11 -0500571 # Do some key name manipulations.
572 new_attr_name = re.sub(r'^Current|(State|Transition)$',
573 "", attr_name)
574 new_attr_name = re.sub(r'BMC', r'Bmc', new_attr_name)
575 new_attr_name = re.sub(r'([A-Z][a-z])', r'_\1',
576 new_attr_name)
577 new_attr_name = new_attr_name.lower().lstrip("_")
578 new_attr_name = re.sub(r'power', r'chassis', new_attr_name)
579 if new_attr_name in req_states:
580 state[new_attr_name] = ret_values[url_path][attr_name]
Michael Walshb95eb542017-03-31 09:39:20 -0500581
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600582 for sub_state in req_states:
Michael Walsh56749222017-09-29 15:26:07 -0500583 if sub_state in state:
584 continue
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600585 if sub_state.startswith("os_"):
586 # We pass "os_" requests on to get_os_state.
587 continue
588 cmd_buf = "state['" + sub_state + "'] = str(" + sub_state + ")"
589 exec(cmd_buf)
590
591 if os_host == "":
592 # The caller has not specified an os_host so as far as we're concerned,
593 # it doesn't exist.
594 return state
595
596 os_req_states = [sub_state for sub_state in req_states
597 if sub_state.startswith('os_')]
598
599 if len(os_req_states) > 0:
600 # The caller has specified an os_host and they have requested
601 # information on os substates.
602
603 # Based on the information gathered on bmc, we'll try to make a
604 # determination of whether the os is even up. We'll pass the result
605 # of that assessment to get_os_state to enhance performance.
606 os_up_match = DotDict()
607 for sub_state in master_os_up_match:
608 if sub_state in req_states:
609 os_up_match[sub_state] = master_os_up_match[sub_state]
Michael Walsh70369fd2016-11-22 11:25:57 -0600610 os_up = compare_states(state, os_up_match)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600611 os_state = get_os_state(os_host=os_host,
612 os_username=os_username,
613 os_password=os_password,
614 req_states=os_req_states,
615 os_up=os_up,
616 quiet=quiet)
617 # Append os_state dictionary to ours.
618 state.update(os_state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600619
620 return state
621
Michael Walsh70369fd2016-11-22 11:25:57 -0600622
Michael Walsh70369fd2016-11-22 11:25:57 -0600623def check_state(match_state,
624 invert=0,
625 print_string="",
626 openbmc_host="",
627 openbmc_username="",
628 openbmc_password="",
629 os_host="",
630 os_username="",
631 os_password="",
632 quiet=None):
Michael Walsh70369fd2016-11-22 11:25:57 -0600633 r"""
634 Check that the Open BMC machine's composite state matches the specified
635 state. On success, this keyword returns the machine's composite state as a
636 dictionary.
637
638 Description of arguments:
639 match_state A dictionary whose key/value pairs are "state field"/
640 "state value". The state value is interpreted as a
641 regular expression. Example call from robot:
Michael Walsh341c21e2017-01-17 16:25:20 -0600642 ${match_state}= Create Dictionary chassis=^On$
643 ... bmc=^Ready$
Michael Walsh01975fa2017-08-20 20:51:36 -0500644 ... boot_progress=^OSStart$
Michael Walsh70369fd2016-11-22 11:25:57 -0600645 ${state}= Check State &{match_state}
646 invert If this flag is set, this function will succeed if the
647 states do NOT match.
648 print_string This function will print this string to the console prior
649 to getting the state.
650 openbmc_host The DNS name or IP address of the BMC.
651 This defaults to global ${OPENBMC_HOST}.
652 openbmc_username The username to be used to login to the BMC.
653 This defaults to global ${OPENBMC_USERNAME}.
654 openbmc_password The password to be used to login to the BMC.
655 This defaults to global ${OPENBMC_PASSWORD}.
656 os_host The DNS name or IP address of the operating system.
657 This defaults to global ${OS_HOST}.
658 os_username The username to be used to login to the OS.
659 This defaults to global ${OS_USERNAME}.
660 os_password The password to be used to login to the OS.
661 This defaults to global ${OS_PASSWORD}.
662 quiet Indicates whether status details should be written to the
663 console. Defaults to either global value of ${QUIET} or
664 to 1.
665 """
666
Michael Walsh619aa332017-04-12 15:56:51 -0500667 quiet = int(gp.get_var_value(quiet, 0))
Michael Walsh70369fd2016-11-22 11:25:57 -0600668
669 grp.rprint(print_string)
670
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600671 req_states = match_state.keys()
Michael Walsh70369fd2016-11-22 11:25:57 -0600672 # Initialize state.
673 state = get_state(openbmc_host=openbmc_host,
674 openbmc_username=openbmc_username,
675 openbmc_password=openbmc_password,
676 os_host=os_host,
677 os_username=os_username,
678 os_password=os_password,
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600679 req_states=req_states,
Michael Walsh70369fd2016-11-22 11:25:57 -0600680 quiet=quiet)
681 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500682 gp.print_var(state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600683
684 match = compare_states(state, match_state)
685
686 if invert and match:
687 fail_msg = "The current state of the machine matches the match" +\
688 " state:\n" + gp.sprint_varx("state", state)
689 BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
690 elif not invert and not match:
691 fail_msg = "The current state of the machine does NOT match the" +\
692 " match state:\n" +\
693 gp.sprint_varx("state", state)
694 BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
695
696 return state
697
Michael Walsh70369fd2016-11-22 11:25:57 -0600698
Michael Walshf893ba02017-01-10 10:28:05 -0600699def wait_state(match_state=(),
Michael Walsh70369fd2016-11-22 11:25:57 -0600700 wait_time="1 min",
701 interval="1 second",
702 invert=0,
703 openbmc_host="",
704 openbmc_username="",
705 openbmc_password="",
706 os_host="",
707 os_username="",
708 os_password="",
709 quiet=None):
Michael Walsh70369fd2016-11-22 11:25:57 -0600710 r"""
711 Wait for the Open BMC machine's composite state to match the specified
712 state. On success, this keyword returns the machine's composite state as
713 a dictionary.
714
715 Description of arguments:
716 match_state A dictionary whose key/value pairs are "state field"/
717 "state value". See check_state (above) for details.
Michael Walsh619aa332017-04-12 15:56:51 -0500718 This value may also be any string accepted by
719 return_state_constant (e.g. "standby_match_state").
720 In such a case this function will call
721 return_state_constant to convert it to a proper
722 dictionary as described above.
Michael Walsh70369fd2016-11-22 11:25:57 -0600723 wait_time The total amount of time to wait for the desired state.
724 This value may be expressed in Robot Framework's time
725 format (e.g. 1 minute, 2 min 3 s, 4.5).
726 interval The amount of time between state checks.
727 This value may be expressed in Robot Framework's time
728 format (e.g. 1 minute, 2 min 3 s, 4.5).
729 invert If this flag is set, this function will for the state of
730 the machine to cease to match the match state.
731 openbmc_host The DNS name or IP address of the BMC.
732 This defaults to global ${OPENBMC_HOST}.
733 openbmc_username The username to be used to login to the BMC.
734 This defaults to global ${OPENBMC_USERNAME}.
735 openbmc_password The password to be used to login to the BMC.
736 This defaults to global ${OPENBMC_PASSWORD}.
737 os_host The DNS name or IP address of the operating system.
738 This defaults to global ${OS_HOST}.
739 os_username The username to be used to login to the OS.
740 This defaults to global ${OS_USERNAME}.
741 os_password The password to be used to login to the OS.
742 This defaults to global ${OS_PASSWORD}.
743 quiet Indicates whether status details should be written to the
744 console. Defaults to either global value of ${QUIET} or
745 to 1.
746 """
747
Michael Walsh619aa332017-04-12 15:56:51 -0500748 quiet = int(gp.get_var_value(quiet, 0))
749
George Keishing569796c2019-06-10 13:25:53 -0500750 try:
Michael Walsh619aa332017-04-12 15:56:51 -0500751 match_state = return_state_constant(match_state)
George Keishing569796c2019-06-10 13:25:53 -0500752 except TypeError:
753 pass
Michael Walsh70369fd2016-11-22 11:25:57 -0600754
755 if not quiet:
756 if invert:
757 alt_text = "cease to "
758 else:
759 alt_text = ""
Joy Onyerikwu004ad3c2018-06-11 16:29:56 -0500760 gp.print_timen("Checking every " + str(interval) + " for up to "
761 + str(wait_time) + " for the state of the machine to "
762 + alt_text + "match the state shown below.")
Michael Walsh3eb50022017-03-21 11:27:30 -0500763 gp.print_var(match_state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600764
Michael Walshf893ba02017-01-10 10:28:05 -0600765 if quiet:
Michael Walsh341c21e2017-01-17 16:25:20 -0600766 print_string = ""
Michael Walshf893ba02017-01-10 10:28:05 -0600767 else:
Michael Walsh341c21e2017-01-17 16:25:20 -0600768 print_string = "#"
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600769
770 debug = int(BuiltIn().get_variable_value("${debug}", "0"))
771 if debug:
772 # In debug we print state so no need to print the "#".
773 print_string = ""
774 check_state_quiet = 1 - debug
Michael Walsh70369fd2016-11-22 11:25:57 -0600775 cmd_buf = ["Check State", match_state, "invert=${" + str(invert) + "}",
Michael Walshf893ba02017-01-10 10:28:05 -0600776 "print_string=" + print_string, "openbmc_host=" + openbmc_host,
Michael Walsh70369fd2016-11-22 11:25:57 -0600777 "openbmc_username=" + openbmc_username,
778 "openbmc_password=" + openbmc_password, "os_host=" + os_host,
779 "os_username=" + os_username, "os_password=" + os_password,
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600780 "quiet=${" + str(check_state_quiet) + "}"]
Michael Walsh70369fd2016-11-22 11:25:57 -0600781 grp.rdpissuing_keyword(cmd_buf)
Michael Walsh619aa332017-04-12 15:56:51 -0500782 try:
783 state = BuiltIn().wait_until_keyword_succeeds(wait_time, interval,
784 *cmd_buf)
785 except AssertionError as my_assertion_error:
786 gp.printn()
787 message = my_assertion_error.args[0]
788 BuiltIn().fail(message)
789
Michael Walsh70369fd2016-11-22 11:25:57 -0600790 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500791 gp.printn()
Michael Walsh70369fd2016-11-22 11:25:57 -0600792 if invert:
Michael Walsh3eb50022017-03-21 11:27:30 -0500793 gp.print_timen("The states no longer match:")
Michael Walsh70369fd2016-11-22 11:25:57 -0600794 else:
Michael Walsh3eb50022017-03-21 11:27:30 -0500795 gp.print_timen("The states match:")
796 gp.print_var(state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600797
798 return state
799
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600800
Michael Walsh619aa332017-04-12 15:56:51 -0500801def wait_for_comm_cycle(start_boot_seconds,
802 quiet=None):
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600803 r"""
804 Wait for communications to the BMC to stop working and then resume working.
805 This function is useful when you have initiated some kind of reboot.
806
807 Description of arguments:
808 start_boot_seconds The time that the boot test started. The format is the
809 epoch time in seconds, i.e. the number of seconds since
810 1970-01-01 00:00:00 UTC. This value should be obtained
811 from the BMC so that it is not dependent on any kind of
812 synchronization between this machine and the target BMC
813 This will allow this program to work correctly even in
814 a simulated environment. This value should be obtained
815 by the caller prior to initiating a reboot. It can be
816 obtained as follows:
817 state = st.get_state(req_states=['epoch_seconds'])
818 """
819
Michael Walsh619aa332017-04-12 15:56:51 -0500820 quiet = int(gp.get_var_value(quiet, 0))
821
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600822 # Validate parms.
823 error_message = gv.svalid_integer(start_boot_seconds,
824 var_name="start_boot_seconds")
825 if error_message != "":
826 BuiltIn().fail(gp.sprint_error(error_message))
827
828 match_state = anchor_state(DotDict([('packet_loss', '100')]))
829 # Wait for 100% packet loss trying to ping machine.
Michael Walsh619aa332017-04-12 15:56:51 -0500830 wait_state(match_state, wait_time="8 mins", interval="0 seconds")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600831
832 match_state['packet_loss'] = '^0$'
833 # Wait for 0% packet loss trying to ping machine.
Michael Walsh619aa332017-04-12 15:56:51 -0500834 wait_state(match_state, wait_time="8 mins", interval="0 seconds")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600835
836 # Get the uptime and epoch seconds for comparisons. We want to be sure
837 # that the uptime is less than the elapsed boot time. Further proof that
838 # a reboot has indeed occurred (vs random network instability giving a
Michael Walshc4c05d32018-05-29 11:39:39 -0500839 # false positive. We also use wait_state because the BMC may take a short
840 # while to be ready to process SSH requests.
Joy Onyerikwu004ad3c2018-06-11 16:29:56 -0500841 match_state = DotDict([('uptime', '^[0-9\\.]+$'),
Michael Walshc4c05d32018-05-29 11:39:39 -0500842 ('epoch_seconds', '^[0-9]+$')])
843 state = wait_state(match_state, wait_time="2 mins", interval="1 second")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600844
845 elapsed_boot_time = int(state['epoch_seconds']) - start_boot_seconds
Michael Walsh619aa332017-04-12 15:56:51 -0500846 gp.qprint_var(elapsed_boot_time)
Michael Walshe77585a2017-12-14 11:02:28 -0600847 if state['uptime'] == "":
848 error_message = "Unable to obtain uptime from the BMC. BMC is not" +\
849 " communicating."
850 BuiltIn().fail(gp.sprint_error(error_message))
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600851 if int(float(state['uptime'])) < elapsed_boot_time:
852 uptime = state['uptime']
Michael Walsh619aa332017-04-12 15:56:51 -0500853 gp.qprint_var(uptime)
Joy Onyerikwu004ad3c2018-06-11 16:29:56 -0500854 gp.qprint_timen("The uptime is less than the elapsed boot time,"
855 + " as expected.")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600856 else:
857 error_message = "The uptime is greater than the elapsed boot time," +\
858 " which is unexpected:\n" +\
859 gp.sprint_var(start_boot_seconds) +\
860 gp.sprint_var(state)
861 BuiltIn().fail(gp.sprint_error(error_message))
862
Michael Walsh619aa332017-04-12 15:56:51 -0500863 gp.qprint_timen("Verifying that REST API interface is working.")
Michael Walshb95eb542017-03-31 09:39:20 -0500864 match_state = DotDict([('rest', '^1$')])
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600865 state = wait_state(match_state, wait_time="5 mins", interval="2 seconds")