blob: 6b0d57f386579bb85119db53fb560d30c78780c0 [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 Walsh70369fd2016-11-22 11:25:57 -060035
36import commands
37from robot.libraries.BuiltIn import BuiltIn
Michael Walsh341c21e2017-01-17 16:25:20 -060038from robot.utils import DotDict
Michael Walsh70369fd2016-11-22 11:25:57 -060039
40import re
Michael Walsh341c21e2017-01-17 16:25:20 -060041import os
Michael Walsh56749222017-09-29 15:26:07 -050042import sys
43import imp
44
Michael Walsh70369fd2016-11-22 11:25:57 -060045
Michael Walsh619aa332017-04-12 15:56:51 -050046# We need utils.robot to get keywords like "Get Chassis Power State".
Michael Walsh16cbb7f2017-02-02 15:54:16 -060047gru.my_import_resource("utils.robot")
48gru.my_import_resource("state_manager.robot")
Michael Walsh70369fd2016-11-22 11:25:57 -060049
Michael Walsh56749222017-09-29 15:26:07 -050050base_path = os.path.dirname(os.path.dirname(
51 imp.find_module("gen_robot_print")[1])) + os.sep
52sys.path.append(base_path + "data/")
Michael Walsh56749222017-09-29 15:26:07 -050053
Michael Walsh940d6912017-10-27 12:32:33 -050054# Previously, I had this coded:
55# import variables as var
56# However, we ran into a problem where a robot program did this...
57# Variables ../../lib/ras/variables.py
58# Prior to doing this...
59# Library ../lib/state.py
60
61# This caused the wrong variables.py file to be selected. Attempts to fix this
62# have failed so far. For the moment, we will hard-code the value we need from
63# the file.
64
65SYSTEM_STATE_URI = "/xyz/openbmc_project/state/"
Michael Walsh56749222017-09-29 15:26:07 -050066
Michael Walsh8fae6ea2017-02-20 16:14:44 -060067# The BMC code has recently been changed as far as what states are defined and
68# what the state values can be. This module now has a means of processing both
69# the old style state (i.e. OBMC_STATES_VERSION = 0) and the new style (i.e.
Michael Walsh16cbb7f2017-02-02 15:54:16 -060070# OBMC_STATES_VERSION = 1).
Michael Walsh341c21e2017-01-17 16:25:20 -060071# The caller can set environment variable OBMC_STATES_VERSION to dictate
72# whether we're processing old or new style states. If OBMC_STATES_VERSION is
Michael Walsh8fae6ea2017-02-20 16:14:44 -060073# not set it will default to 1.
Michael Walsh341c21e2017-01-17 16:25:20 -060074
Michael Walsh619aa332017-04-12 15:56:51 -050075# As of the present moment, OBMC_STATES_VERSION of 0 is for cold that is so old
76# that it is no longer worthwhile to maintain. The OBMC_STATES_VERSION 0 code
77# is being removed but the OBMC_STATES_VERSION value will stay for now in the
78# event that it is needed in the future.
79
Michael Walsh8fae6ea2017-02-20 16:14:44 -060080OBMC_STATES_VERSION = int(os.environ.get('OBMC_STATES_VERSION', 1))
Michael Walsh341c21e2017-01-17 16:25:20 -060081
Michael Walsh619aa332017-04-12 15:56:51 -050082# When a user calls get_state w/o specifying req_states, default_req_states
83# is used as its value.
84default_req_states = ['rest',
85 'chassis',
86 'bmc',
87 'boot_progress',
Michael Walsh56749222017-09-29 15:26:07 -050088 'operating_system',
Michael Walsh619aa332017-04-12 15:56:51 -050089 'host',
90 'os_ping',
91 'os_login',
92 'os_run_cmd']
Michael Walsh8fae6ea2017-02-20 16:14:44 -060093
Michael Walsh619aa332017-04-12 15:56:51 -050094# valid_req_states is a list of sub states supported by the get_state function.
95# valid_req_states, default_req_states and master_os_up_match are used by the
96# get_state function.
97valid_req_states = ['ping',
98 'packet_loss',
99 'uptime',
100 'epoch_seconds',
101 'rest',
102 'chassis',
Michael Walsh56749222017-09-29 15:26:07 -0500103 'requested_chassis',
Michael Walsh619aa332017-04-12 15:56:51 -0500104 'bmc',
Michael Walsh56749222017-09-29 15:26:07 -0500105 'requested_bmc',
Michael Walsh619aa332017-04-12 15:56:51 -0500106 'boot_progress',
Michael Walsh56749222017-09-29 15:26:07 -0500107 'operating_system',
Michael Walsh619aa332017-04-12 15:56:51 -0500108 'host',
Michael Walsh56749222017-09-29 15:26:07 -0500109 'requested_host',
110 'attempts_left',
Michael Walsh619aa332017-04-12 15:56:51 -0500111 'os_ping',
112 'os_login',
113 'os_run_cmd']
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600114
115# valid_os_req_states and default_os_req_states are used by the os_get_state
116# function.
117# valid_os_req_states is a list of state information supported by the
118# get_os_state function.
119valid_os_req_states = ['os_ping',
120 'os_login',
121 'os_run_cmd']
122# When a user calls get_os_state w/o specifying req_states,
123# default_os_req_states is used as its value.
124default_os_req_states = ['os_ping',
125 'os_login',
126 'os_run_cmd']
127
128# Presently, some BMCs appear to not keep time very well. This environment
129# variable directs the get_state function to use either the BMC's epoch time
130# or the local epoch time.
131USE_BMC_EPOCH_TIME = int(os.environ.get('USE_BMC_EPOCH_TIME', 0))
Michael Walsh341c21e2017-01-17 16:25:20 -0600132
Michael Walsh619aa332017-04-12 15:56:51 -0500133# Useful state constant definition(s).
134# A match state for checking that the system is at "standby".
135standby_match_state = DotDict([('rest', '^1$'),
136 ('chassis', '^Off$'),
137 ('bmc', '^Ready$'),
138 ('boot_progress', ''),
Michael Walsh56749222017-09-29 15:26:07 -0500139 ('operating_system', ''),
Michael Walsh619aa332017-04-12 15:56:51 -0500140 ('host', '')])
141
142# default_state is an initial value which may be of use to callers.
143default_state = DotDict([('rest', '1'),
144 ('chassis', 'On'),
145 ('bmc', 'Ready'),
Michael Walsh01975fa2017-08-20 20:51:36 -0500146 ('boot_progress', 'OSStart'),
Michael Walsh56749222017-09-29 15:26:07 -0500147 ('operating_system', 'BootComplete'),
Michael Walsh619aa332017-04-12 15:56:51 -0500148 ('host', 'Running'),
149 ('os_ping', '1'),
150 ('os_login', '1'),
151 ('os_run_cmd', '1')])
152
153# A master dictionary to determine whether the os may be up.
154master_os_up_match = DotDict([('chassis', '^On$'),
155 ('bmc', '^Ready$'),
156 ('boot_progress',
Michael Walsh01975fa2017-08-20 20:51:36 -0500157 'FW Progress, Starting OS|OSStart'),
Michael Walsh56749222017-09-29 15:26:07 -0500158 ('operating_system', 'BootComplete'),
Michael Walsh619aa332017-04-12 15:56:51 -0500159 ('host', '^Running$')])
160
Michael Walsh45ca6e42017-09-14 17:29:12 -0500161invalid_state_match = DotDict([('rest', '^$'),
162 ('chassis', '^$'),
163 ('bmc', '^$'),
164 ('boot_progress', '^$'),
Michael Walsh56749222017-09-29 15:26:07 -0500165 ('operating_system', '^$'),
Michael Walsh45ca6e42017-09-14 17:29:12 -0500166 ('host', '^$')])
167
Michael Walsh341c21e2017-01-17 16:25:20 -0600168
169###############################################################################
170def return_default_state():
171
172 r"""
173 Return default state dictionary.
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600174
175 default_state is an initial value which may be of use to callers.
Michael Walsh341c21e2017-01-17 16:25:20 -0600176 """
177
178 return default_state
179
180###############################################################################
181
Michael Walsh70369fd2016-11-22 11:25:57 -0600182
Michael Walsh619aa332017-04-12 15:56:51 -0500183valid_state_constants = ['default', 'standby_match_state']
184
185
186###############################################################################
187def return_state_constant(state_name='default'):
188
189 r"""
190 Return default state dictionary.
191
192 default_state is an initial value which may be of use to callers.
193 """
194
195 error_message = gv.svalid_value(state_name, var_name='state_name',
196 valid_values=valid_state_constants)
197 if error_message != "":
198 BuiltIn().fail(gp.sprint_error(error_message))
199
200 if state_name == 'default':
201 return default_state
202 elif state_name == 'standby_match_state':
203 return standby_match_state
204
205###############################################################################
206
207
Michael Walsh70369fd2016-11-22 11:25:57 -0600208###############################################################################
209def anchor_state(state):
210
211 r"""
212 Add regular expression anchors ("^" and "$") to the beginning and end of
213 each item in the state dictionary passed in. Return the resulting
214 dictionary.
215
216 Description of Arguments:
217 state A dictionary such as the one returned by the get_state()
218 function.
219 """
220
Michael Walsh2ce067a2017-02-27 14:24:07 -0600221 anchored_state = state.copy()
Michael Walsh70369fd2016-11-22 11:25:57 -0600222 for key, match_state_value in anchored_state.items():
223 anchored_state[key] = "^" + str(anchored_state[key]) + "$"
224
225 return anchored_state
226
227###############################################################################
228
229
230###############################################################################
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600231def strip_anchor_state(state):
232
233 r"""
234 Strip regular expression anchors ("^" and "$") from the beginning and end
235 of each item in the state dictionary passed in. Return the resulting
236 dictionary.
237
238 Description of Arguments:
239 state A dictionary such as the one returned by the get_state()
240 function.
241 """
242
Michael Walsh2ce067a2017-02-27 14:24:07 -0600243 stripped_state = state.copy()
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600244 for key, match_state_value in stripped_state.items():
245 stripped_state[key] = stripped_state[key].strip("^$")
246
247 return stripped_state
248
249###############################################################################
250
251
252###############################################################################
Michael Walsh70369fd2016-11-22 11:25:57 -0600253def compare_states(state,
Michael Walsh45ca6e42017-09-14 17:29:12 -0500254 match_state,
255 match_type='and'):
Michael Walsh70369fd2016-11-22 11:25:57 -0600256
257 r"""
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600258 Compare 2 state dictionaries. Return True if they match and False if they
Michael Walsh70369fd2016-11-22 11:25:57 -0600259 don't. Note that the match_state dictionary does not need to have an entry
260 corresponding to each entry in the state dictionary. But for each entry
261 that it does have, the corresponding state entry will be checked for a
262 match.
263
264 Description of arguments:
265 state A state dictionary such as the one returned by the
266 get_state function.
267 match_state A dictionary whose key/value pairs are "state field"/
268 "state value". The state value is interpreted as a
269 regular expression. Every value in this dictionary is
Michael Walsh45ca6e42017-09-14 17:29:12 -0500270 considered. When match_type is 'and', if each and every
271 comparison matches, the two dictionaries are considered to
272 be matching. If match_type is 'or', if any two of the
273 elements compared match, the two dictionaries are
274 considered to be matching.
275 match_type This may be 'and' or 'or'.
Michael Walsh70369fd2016-11-22 11:25:57 -0600276 """
277
Michael Walsh45ca6e42017-09-14 17:29:12 -0500278 error_message = gv.svalid_value(match_type, var_name="match_type",
279 valid_values=['and', 'or'])
280 if error_message != "":
281 BuiltIn().fail(gp.sprint_error(error_message))
282
283 default_match = (match_type == 'and')
Michael Walsh70369fd2016-11-22 11:25:57 -0600284 for key, match_state_value in match_state.items():
Michael Walsh97df71c2017-03-27 14:33:24 -0500285 # Blank match_state_value means "don't care".
286 if match_state_value == "":
287 continue
Michael Walsh70369fd2016-11-22 11:25:57 -0600288 try:
Michael Walsh45ca6e42017-09-14 17:29:12 -0500289 match = (re.match(match_state_value, str(state[key])) is not None)
Michael Walsh70369fd2016-11-22 11:25:57 -0600290 except KeyError:
291 match = False
Michael Walsh70369fd2016-11-22 11:25:57 -0600292
Michael Walsh45ca6e42017-09-14 17:29:12 -0500293 if match != default_match:
294 return match
295
296 return default_match
Michael Walsh70369fd2016-11-22 11:25:57 -0600297
298###############################################################################
299
300
301###############################################################################
302def get_os_state(os_host="",
303 os_username="",
304 os_password="",
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600305 req_states=default_os_req_states,
306 os_up=True,
Michael Walsh70369fd2016-11-22 11:25:57 -0600307 quiet=None):
308
309 r"""
310 Get component states for the operating system such as ping, login,
311 etc, put them into a dictionary and return them to the caller.
312
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600313 Note that all substate values are strings.
314
Michael Walsh70369fd2016-11-22 11:25:57 -0600315 Description of arguments:
316 os_host The DNS name or IP address of the operating system.
317 This defaults to global ${OS_HOST}.
318 os_username The username to be used to login to the OS.
319 This defaults to global ${OS_USERNAME}.
320 os_password The password to be used to login to the OS.
321 This defaults to global ${OS_PASSWORD}.
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600322 req_states This is a list of states whose values are being requested by
323 the caller.
324 os_up If the caller knows that the os can't possibly be up, it can
325 improve performance by passing os_up=False. This function
326 will then simply return default values for all requested os
327 sub states.
Michael Walsh70369fd2016-11-22 11:25:57 -0600328 quiet Indicates whether status details (e.g. curl commands) should
329 be written to the console.
330 Defaults to either global value of ${QUIET} or to 1.
331 """
332
Michael Walsh619aa332017-04-12 15:56:51 -0500333 quiet = int(gp.get_var_value(quiet, 0))
Michael Walsh70369fd2016-11-22 11:25:57 -0600334
335 # Set parm defaults where necessary and validate all parms.
336 if os_host == "":
337 os_host = BuiltIn().get_variable_value("${OS_HOST}")
338 error_message = gv.svalid_value(os_host, var_name="os_host",
339 invalid_values=[None, ""])
340 if error_message != "":
341 BuiltIn().fail(gp.sprint_error(error_message))
342
343 if os_username == "":
344 os_username = BuiltIn().get_variable_value("${OS_USERNAME}")
345 error_message = gv.svalid_value(os_username, var_name="os_username",
346 invalid_values=[None, ""])
347 if error_message != "":
348 BuiltIn().fail(gp.sprint_error(error_message))
349
350 if os_password == "":
351 os_password = BuiltIn().get_variable_value("${OS_PASSWORD}")
352 error_message = gv.svalid_value(os_password, var_name="os_password",
353 invalid_values=[None, ""])
354 if error_message != "":
355 BuiltIn().fail(gp.sprint_error(error_message))
356
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600357 invalid_req_states = [sub_state for sub_state in req_states
358 if sub_state not in valid_os_req_states]
359 if len(invalid_req_states) > 0:
360 error_message = "The following req_states are not supported:\n" +\
361 gp.sprint_var(invalid_req_states)
362 BuiltIn().fail(gp.sprint_error(error_message))
Michael Walsh70369fd2016-11-22 11:25:57 -0600363
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600364 # Initialize all substate values supported by this function.
365 os_ping = 0
366 os_login = 0
367 os_run_cmd = 0
Michael Walsh70369fd2016-11-22 11:25:57 -0600368
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600369 if os_up:
370 if 'os_ping' in req_states:
371 # See if the OS pings.
372 cmd_buf = "ping -c 1 -w 2 " + os_host
373 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500374 gp.pissuing(cmd_buf)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600375 rc, out_buf = commands.getstatusoutput(cmd_buf)
376 if rc == 0:
377 os_ping = 1
Michael Walsh70369fd2016-11-22 11:25:57 -0600378
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600379 # Programming note: All attributes which do not require an ssh login
380 # should have been processed by this point.
381 master_req_login = ['os_login', 'os_run_cmd']
382 req_login = [sub_state for sub_state in req_states if sub_state in
383 master_req_login]
Michael Walsh97df71c2017-03-27 14:33:24 -0500384 must_login = (len(req_login) > 0)
Michael Walsh70369fd2016-11-22 11:25:57 -0600385
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600386 if must_login:
Michael Walsh3eb50022017-03-21 11:27:30 -0500387 # Open SSH connection to OS. Note that this doesn't fail even when
388 # the OS is not up.
Michael Walshac275512017-03-07 11:39:28 -0600389 cmd_buf = ["SSHLibrary.Open Connection", os_host]
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600390 if not quiet:
391 grp.rpissuing_keyword(cmd_buf)
392 ix = BuiltIn().run_keyword(*cmd_buf)
393
394 # Login to OS.
395 cmd_buf = ["Login", os_username, os_password]
396 if not quiet:
397 grp.rpissuing_keyword(cmd_buf)
Michael Walsh3eb50022017-03-21 11:27:30 -0500398 status, ret_values = \
399 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600400 if status == "PASS":
401 os_login = 1
Michael Walsh3eb50022017-03-21 11:27:30 -0500402 else:
403 gp.dprint_var(status)
404 gp.dprint_var(ret_values)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600405
406 if os_login:
407 if 'os_run_cmd' in req_states:
Michael Walsh3eb50022017-03-21 11:27:30 -0500408 # Try running a simple command (uptime) on the OS.
409 cmd_buf = ["Execute Command", "uptime",
410 "return_stderr=True", "return_rc=True"]
411 if not quiet:
412 grp.rpissuing_keyword(cmd_buf)
413 # Note that in spite of its name, there are occasions
414 # where run_keyword_and_ignore_error can fail.
415 status, ret_values = \
416 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
417 if status == "PASS":
418 stdout, stderr, rc = ret_values
419 if rc == 0 and stderr == "":
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600420 os_run_cmd = 1
Michael Walsh3eb50022017-03-21 11:27:30 -0500421 else:
422 gp.dprint_var(status)
423 gp.dprint_var(stdout)
424 gp.dprint_var(stderr)
425 gp.dprint_var(rc)
426 else:
427 gp.dprint_var(status)
428 gp.dprint_var(ret_values)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600429
430 os_state = DotDict()
431 for sub_state in req_states:
432 cmd_buf = "os_state['" + sub_state + "'] = str(" + sub_state + ")"
433 exec(cmd_buf)
Michael Walsh70369fd2016-11-22 11:25:57 -0600434
435 return os_state
436
437###############################################################################
438
439
440###############################################################################
441def get_state(openbmc_host="",
442 openbmc_username="",
443 openbmc_password="",
444 os_host="",
445 os_username="",
446 os_password="",
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600447 req_states=default_req_states,
Michael Walsh70369fd2016-11-22 11:25:57 -0600448 quiet=None):
449
450 r"""
Michael Walsh619aa332017-04-12 15:56:51 -0500451 Get component states such as chassis state, bmc state, etc, put them into a
Michael Walsh70369fd2016-11-22 11:25:57 -0600452 dictionary and return them to the caller.
453
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600454 Note that all substate values are strings.
455
Michael Walsh70369fd2016-11-22 11:25:57 -0600456 Description of arguments:
457 openbmc_host The DNS name or IP address of the BMC.
458 This defaults to global ${OPENBMC_HOST}.
459 openbmc_username The username to be used to login to the BMC.
460 This defaults to global ${OPENBMC_USERNAME}.
461 openbmc_password The password to be used to login to the BMC.
462 This defaults to global ${OPENBMC_PASSWORD}.
463 os_host The DNS name or IP address of the operating system.
464 This defaults to global ${OS_HOST}.
465 os_username The username to be used to login to the OS.
466 This defaults to global ${OS_USERNAME}.
467 os_password The password to be used to login to the OS.
468 This defaults to global ${OS_PASSWORD}.
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600469 req_states This is a list of states whose values are being requested
470 by the caller.
Michael Walsh70369fd2016-11-22 11:25:57 -0600471 quiet Indicates whether status details (e.g. curl commands)
472 should be written to the console.
473 Defaults to either global value of ${QUIET} or to 1.
474 """
475
Michael Walsh619aa332017-04-12 15:56:51 -0500476 quiet = int(gp.get_var_value(quiet, 0))
Michael Walsh70369fd2016-11-22 11:25:57 -0600477
478 # Set parm defaults where necessary and validate all parms.
479 if openbmc_host == "":
480 openbmc_host = BuiltIn().get_variable_value("${OPENBMC_HOST}")
481 error_message = gv.svalid_value(openbmc_host,
482 var_name="openbmc_host",
483 invalid_values=[None, ""])
484 if error_message != "":
485 BuiltIn().fail(gp.sprint_error(error_message))
486
487 if openbmc_username == "":
488 openbmc_username = BuiltIn().get_variable_value("${OPENBMC_USERNAME}")
489 error_message = gv.svalid_value(openbmc_username,
490 var_name="openbmc_username",
491 invalid_values=[None, ""])
492 if error_message != "":
493 BuiltIn().fail(gp.sprint_error(error_message))
494
495 if openbmc_password == "":
496 openbmc_password = BuiltIn().get_variable_value("${OPENBMC_PASSWORD}")
497 error_message = gv.svalid_value(openbmc_password,
498 var_name="openbmc_password",
499 invalid_values=[None, ""])
500 if error_message != "":
501 BuiltIn().fail(gp.sprint_error(error_message))
502
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600503 # NOTE: OS parms are optional.
Michael Walsh70369fd2016-11-22 11:25:57 -0600504 if os_host == "":
505 os_host = BuiltIn().get_variable_value("${OS_HOST}")
506 if os_host is None:
507 os_host = ""
508
509 if os_username is "":
510 os_username = BuiltIn().get_variable_value("${OS_USERNAME}")
511 if os_username is None:
512 os_username = ""
513
514 if os_password is "":
515 os_password = BuiltIn().get_variable_value("${OS_PASSWORD}")
516 if os_password is None:
517 os_password = ""
518
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600519 invalid_req_states = [sub_state for sub_state in req_states
520 if sub_state not in valid_req_states]
521 if len(invalid_req_states) > 0:
522 error_message = "The following req_states are not supported:\n" +\
523 gp.sprint_var(invalid_req_states)
524 BuiltIn().fail(gp.sprint_error(error_message))
525
526 # Initialize all substate values supported by this function.
527 ping = 0
528 packet_loss = ''
529 uptime = ''
530 epoch_seconds = ''
Michael Walsh2b269de2017-10-09 11:18:11 -0500531 rest = ''
532 chassis = ''
533 requested_chassis = ''
534 bmc = ''
535 requested_bmc = ''
536 boot_progress = ''
537 operating_system = ''
538 host = ''
539 requested_host = ''
540 attempts_left = ''
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600541
Michael Walsh70369fd2016-11-22 11:25:57 -0600542 # Get the component states.
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600543 if 'ping' in req_states:
544 # See if the OS pings.
545 cmd_buf = "ping -c 1 -w 2 " + openbmc_host
546 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500547 gp.pissuing(cmd_buf)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600548 rc, out_buf = commands.getstatusoutput(cmd_buf)
549 if rc == 0:
550 ping = 1
551
552 if 'packet_loss' in req_states:
553 # See if the OS pings.
554 cmd_buf = "ping -c 5 -w 5 " + openbmc_host +\
555 " | egrep 'packet loss' | sed -re 's/.* ([0-9]+)%.*/\\1/g'"
556 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500557 gp.pissuing(cmd_buf)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600558 rc, out_buf = commands.getstatusoutput(cmd_buf)
559 if rc == 0:
560 packet_loss = out_buf.rstrip("\n")
561
Michael Walshe53e47a2017-06-30 17:03:24 -0500562 if 'uptime' in req_states:
Michael Walshfa765932017-10-13 14:07:22 -0500563 # Sometimes reading uptime results in a blank value. Call with
564 # wait_until_keyword_succeeds to ensure a non-blank value is obtained.
565 remote_cmd_buf = "read uptime filler 2>/dev/null < /proc/uptime" +\
566 " && [ ! -z \"${uptime}\" ] && echo ${uptime}"
567 cmd_buf = ["BMC Execute Command", re.sub(r'\$', '\$', remote_cmd_buf),
568 'quiet=1']
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600569 if not quiet:
570 grp.rpissuing_keyword(cmd_buf)
Michael Walshfa765932017-10-13 14:07:22 -0500571 grp.rpissuing(remote_cmd_buf)
572 try:
573 stdout, stderr, rc =\
Michael Walshcaccd852017-11-01 17:58:41 -0500574 BuiltIn().wait_until_keyword_succeeds("10 sec", "0 sec",
Michael Walshfa765932017-10-13 14:07:22 -0500575 *cmd_buf)
Michael Walsh97df71c2017-03-27 14:33:24 -0500576 if rc == 0 and stderr == "":
577 uptime = stdout
Michael Walshfa765932017-10-13 14:07:22 -0500578 except AssertionError as my_assertion_error:
579 pass
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600580
Michael Walshe53e47a2017-06-30 17:03:24 -0500581 if 'epoch_seconds' in req_states:
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600582 date_cmd_buf = "date -u +%s"
583 if USE_BMC_EPOCH_TIME:
Michael Walshe53e47a2017-06-30 17:03:24 -0500584 cmd_buf = ["BMC Execute Command", date_cmd_buf, 'quiet=${1}']
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600585 if not quiet:
586 grp.rpissuing_keyword(cmd_buf)
Michael Walsh97df71c2017-03-27 14:33:24 -0500587 status, ret_values = \
588 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
589 if status == "PASS":
590 stdout, stderr, rc = ret_values
591 if rc == 0 and stderr == "":
592 epoch_seconds = stdout.rstrip("\n")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600593 else:
594 shell_rc, out_buf = gc.cmd_fnc_u(date_cmd_buf,
Michael Walshfa765932017-10-13 14:07:22 -0500595 quiet=quiet,
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600596 print_output=0)
597 if shell_rc == 0:
598 epoch_seconds = out_buf.rstrip("\n")
599
Michael Walsh56749222017-09-29 15:26:07 -0500600 master_req_rest = ['rest', 'host', 'requested_host', 'operating_system',
601 'attempts_left', 'boot_progress', 'chassis',
602 'requested_chassis' 'bmc' 'requested_bmc']
603
Michael Walshb95eb542017-03-31 09:39:20 -0500604 req_rest = [sub_state for sub_state in req_states if sub_state in
605 master_req_rest]
606 need_rest = (len(req_rest) > 0)
Michael Walsh56749222017-09-29 15:26:07 -0500607 state = DotDict()
608 if need_rest:
Michael Walsh940d6912017-10-27 12:32:33 -0500609 cmd_buf = ["Read Properties", SYSTEM_STATE_URI + "enumerate",
Michael Walsh56749222017-09-29 15:26:07 -0500610 "quiet=${" + str(quiet) + "}"]
Michael Walsh341c21e2017-01-17 16:25:20 -0600611 grp.rdpissuing_keyword(cmd_buf)
Michael Walsh97df71c2017-03-27 14:33:24 -0500612 status, ret_values = \
613 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
614 if status == "PASS":
Michael Walsh56749222017-09-29 15:26:07 -0500615 state['rest'] = '1'
Michael Walsh341c21e2017-01-17 16:25:20 -0600616 else:
Michael Walsh56749222017-09-29 15:26:07 -0500617 state['rest'] = '0'
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600618
Michael Walsh2b269de2017-10-09 11:18:11 -0500619 if int(state['rest']):
620 for url_path in ret_values:
621 for attr_name in ret_values[url_path]:
622 # Create a state key value based on the attr_name.
623 if type(ret_values[url_path][attr_name]) is unicode:
624 ret_values[url_path][attr_name] = \
625 re.sub(r'.*\.', "",
626 ret_values[url_path][attr_name])
627 # Do some key name manipulations.
628 new_attr_name = re.sub(r'^Current|(State|Transition)$',
629 "", attr_name)
630 new_attr_name = re.sub(r'BMC', r'Bmc', new_attr_name)
631 new_attr_name = re.sub(r'([A-Z][a-z])', r'_\1',
632 new_attr_name)
633 new_attr_name = new_attr_name.lower().lstrip("_")
634 new_attr_name = re.sub(r'power', r'chassis', new_attr_name)
635 if new_attr_name in req_states:
636 state[new_attr_name] = ret_values[url_path][attr_name]
Michael Walshb95eb542017-03-31 09:39:20 -0500637
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600638 for sub_state in req_states:
Michael Walsh56749222017-09-29 15:26:07 -0500639 if sub_state in state:
640 continue
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600641 if sub_state.startswith("os_"):
642 # We pass "os_" requests on to get_os_state.
643 continue
644 cmd_buf = "state['" + sub_state + "'] = str(" + sub_state + ")"
645 exec(cmd_buf)
646
647 if os_host == "":
648 # The caller has not specified an os_host so as far as we're concerned,
649 # it doesn't exist.
650 return state
651
652 os_req_states = [sub_state for sub_state in req_states
653 if sub_state.startswith('os_')]
654
655 if len(os_req_states) > 0:
656 # The caller has specified an os_host and they have requested
657 # information on os substates.
658
659 # Based on the information gathered on bmc, we'll try to make a
660 # determination of whether the os is even up. We'll pass the result
661 # of that assessment to get_os_state to enhance performance.
662 os_up_match = DotDict()
663 for sub_state in master_os_up_match:
664 if sub_state in req_states:
665 os_up_match[sub_state] = master_os_up_match[sub_state]
Michael Walsh70369fd2016-11-22 11:25:57 -0600666 os_up = compare_states(state, os_up_match)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600667 os_state = get_os_state(os_host=os_host,
668 os_username=os_username,
669 os_password=os_password,
670 req_states=os_req_states,
671 os_up=os_up,
672 quiet=quiet)
673 # Append os_state dictionary to ours.
674 state.update(os_state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600675
676 return state
677
678###############################################################################
679
680
681###############################################################################
682def check_state(match_state,
683 invert=0,
684 print_string="",
685 openbmc_host="",
686 openbmc_username="",
687 openbmc_password="",
688 os_host="",
689 os_username="",
690 os_password="",
691 quiet=None):
692
693 r"""
694 Check that the Open BMC machine's composite state matches the specified
695 state. On success, this keyword returns the machine's composite state as a
696 dictionary.
697
698 Description of arguments:
699 match_state A dictionary whose key/value pairs are "state field"/
700 "state value". The state value is interpreted as a
701 regular expression. Example call from robot:
Michael Walsh341c21e2017-01-17 16:25:20 -0600702 ${match_state}= Create Dictionary chassis=^On$
703 ... bmc=^Ready$
Michael Walsh01975fa2017-08-20 20:51:36 -0500704 ... boot_progress=^OSStart$
Michael Walsh70369fd2016-11-22 11:25:57 -0600705 ${state}= Check State &{match_state}
706 invert If this flag is set, this function will succeed if the
707 states do NOT match.
708 print_string This function will print this string to the console prior
709 to getting the state.
710 openbmc_host The DNS name or IP address of the BMC.
711 This defaults to global ${OPENBMC_HOST}.
712 openbmc_username The username to be used to login to the BMC.
713 This defaults to global ${OPENBMC_USERNAME}.
714 openbmc_password The password to be used to login to the BMC.
715 This defaults to global ${OPENBMC_PASSWORD}.
716 os_host The DNS name or IP address of the operating system.
717 This defaults to global ${OS_HOST}.
718 os_username The username to be used to login to the OS.
719 This defaults to global ${OS_USERNAME}.
720 os_password The password to be used to login to the OS.
721 This defaults to global ${OS_PASSWORD}.
722 quiet Indicates whether status details should be written to the
723 console. Defaults to either global value of ${QUIET} or
724 to 1.
725 """
726
Michael Walsh619aa332017-04-12 15:56:51 -0500727 quiet = int(gp.get_var_value(quiet, 0))
Michael Walsh70369fd2016-11-22 11:25:57 -0600728
729 grp.rprint(print_string)
730
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600731 req_states = match_state.keys()
Michael Walsh70369fd2016-11-22 11:25:57 -0600732 # Initialize state.
733 state = get_state(openbmc_host=openbmc_host,
734 openbmc_username=openbmc_username,
735 openbmc_password=openbmc_password,
736 os_host=os_host,
737 os_username=os_username,
738 os_password=os_password,
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600739 req_states=req_states,
Michael Walsh70369fd2016-11-22 11:25:57 -0600740 quiet=quiet)
741 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500742 gp.print_var(state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600743
744 match = compare_states(state, match_state)
745
746 if invert and match:
747 fail_msg = "The current state of the machine matches the match" +\
748 " state:\n" + gp.sprint_varx("state", state)
749 BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
750 elif not invert and not match:
751 fail_msg = "The current state of the machine does NOT match the" +\
752 " match state:\n" +\
753 gp.sprint_varx("state", state)
754 BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
755
756 return state
757
758###############################################################################
759
760
761###############################################################################
Michael Walshf893ba02017-01-10 10:28:05 -0600762def wait_state(match_state=(),
Michael Walsh70369fd2016-11-22 11:25:57 -0600763 wait_time="1 min",
764 interval="1 second",
765 invert=0,
766 openbmc_host="",
767 openbmc_username="",
768 openbmc_password="",
769 os_host="",
770 os_username="",
771 os_password="",
772 quiet=None):
773
774 r"""
775 Wait for the Open BMC machine's composite state to match the specified
776 state. On success, this keyword returns the machine's composite state as
777 a dictionary.
778
779 Description of arguments:
780 match_state A dictionary whose key/value pairs are "state field"/
781 "state value". See check_state (above) for details.
Michael Walsh619aa332017-04-12 15:56:51 -0500782 This value may also be any string accepted by
783 return_state_constant (e.g. "standby_match_state").
784 In such a case this function will call
785 return_state_constant to convert it to a proper
786 dictionary as described above.
Michael Walsh70369fd2016-11-22 11:25:57 -0600787 wait_time The total amount of time to wait for the desired state.
788 This value may be expressed in Robot Framework's time
789 format (e.g. 1 minute, 2 min 3 s, 4.5).
790 interval The amount of time between state checks.
791 This value may be expressed in Robot Framework's time
792 format (e.g. 1 minute, 2 min 3 s, 4.5).
793 invert If this flag is set, this function will for the state of
794 the machine to cease to match the match state.
795 openbmc_host The DNS name or IP address of the BMC.
796 This defaults to global ${OPENBMC_HOST}.
797 openbmc_username The username to be used to login to the BMC.
798 This defaults to global ${OPENBMC_USERNAME}.
799 openbmc_password The password to be used to login to the BMC.
800 This defaults to global ${OPENBMC_PASSWORD}.
801 os_host The DNS name or IP address of the operating system.
802 This defaults to global ${OS_HOST}.
803 os_username The username to be used to login to the OS.
804 This defaults to global ${OS_USERNAME}.
805 os_password The password to be used to login to the OS.
806 This defaults to global ${OS_PASSWORD}.
807 quiet Indicates whether status details should be written to the
808 console. Defaults to either global value of ${QUIET} or
809 to 1.
810 """
811
Michael Walsh619aa332017-04-12 15:56:51 -0500812 quiet = int(gp.get_var_value(quiet, 0))
813
814 if type(match_state) in (str, unicode):
815 match_state = return_state_constant(match_state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600816
817 if not quiet:
818 if invert:
819 alt_text = "cease to "
820 else:
821 alt_text = ""
Michael Walsh3eb50022017-03-21 11:27:30 -0500822 gp.print_timen("Checking every " + str(interval) + " for up to " +
823 str(wait_time) + " for the state of the machine to " +
824 alt_text + "match the state shown below.")
825 gp.print_var(match_state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600826
Michael Walshf893ba02017-01-10 10:28:05 -0600827 if quiet:
Michael Walsh341c21e2017-01-17 16:25:20 -0600828 print_string = ""
Michael Walshf893ba02017-01-10 10:28:05 -0600829 else:
Michael Walsh341c21e2017-01-17 16:25:20 -0600830 print_string = "#"
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600831
832 debug = int(BuiltIn().get_variable_value("${debug}", "0"))
833 if debug:
834 # In debug we print state so no need to print the "#".
835 print_string = ""
836 check_state_quiet = 1 - debug
Michael Walsh70369fd2016-11-22 11:25:57 -0600837 cmd_buf = ["Check State", match_state, "invert=${" + str(invert) + "}",
Michael Walshf893ba02017-01-10 10:28:05 -0600838 "print_string=" + print_string, "openbmc_host=" + openbmc_host,
Michael Walsh70369fd2016-11-22 11:25:57 -0600839 "openbmc_username=" + openbmc_username,
840 "openbmc_password=" + openbmc_password, "os_host=" + os_host,
841 "os_username=" + os_username, "os_password=" + os_password,
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600842 "quiet=${" + str(check_state_quiet) + "}"]
Michael Walsh70369fd2016-11-22 11:25:57 -0600843 grp.rdpissuing_keyword(cmd_buf)
Michael Walsh619aa332017-04-12 15:56:51 -0500844 try:
845 state = BuiltIn().wait_until_keyword_succeeds(wait_time, interval,
846 *cmd_buf)
847 except AssertionError as my_assertion_error:
848 gp.printn()
849 message = my_assertion_error.args[0]
850 BuiltIn().fail(message)
851
Michael Walsh70369fd2016-11-22 11:25:57 -0600852 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500853 gp.printn()
Michael Walsh70369fd2016-11-22 11:25:57 -0600854 if invert:
Michael Walsh3eb50022017-03-21 11:27:30 -0500855 gp.print_timen("The states no longer match:")
Michael Walsh70369fd2016-11-22 11:25:57 -0600856 else:
Michael Walsh3eb50022017-03-21 11:27:30 -0500857 gp.print_timen("The states match:")
858 gp.print_var(state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600859
860 return state
861
862###############################################################################
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600863
864
865###############################################################################
Michael Walsh619aa332017-04-12 15:56:51 -0500866def wait_for_comm_cycle(start_boot_seconds,
867 quiet=None):
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600868
869 r"""
870 Wait for communications to the BMC to stop working and then resume working.
871 This function is useful when you have initiated some kind of reboot.
872
873 Description of arguments:
874 start_boot_seconds The time that the boot test started. The format is the
875 epoch time in seconds, i.e. the number of seconds since
876 1970-01-01 00:00:00 UTC. This value should be obtained
877 from the BMC so that it is not dependent on any kind of
878 synchronization between this machine and the target BMC
879 This will allow this program to work correctly even in
880 a simulated environment. This value should be obtained
881 by the caller prior to initiating a reboot. It can be
882 obtained as follows:
883 state = st.get_state(req_states=['epoch_seconds'])
884 """
885
Michael Walsh619aa332017-04-12 15:56:51 -0500886 quiet = int(gp.get_var_value(quiet, 0))
887
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600888 # Validate parms.
889 error_message = gv.svalid_integer(start_boot_seconds,
890 var_name="start_boot_seconds")
891 if error_message != "":
892 BuiltIn().fail(gp.sprint_error(error_message))
893
894 match_state = anchor_state(DotDict([('packet_loss', '100')]))
895 # Wait for 100% packet loss trying to ping machine.
Michael Walsh619aa332017-04-12 15:56:51 -0500896 wait_state(match_state, wait_time="8 mins", interval="0 seconds")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600897
898 match_state['packet_loss'] = '^0$'
899 # Wait for 0% packet loss trying to ping machine.
Michael Walsh619aa332017-04-12 15:56:51 -0500900 wait_state(match_state, wait_time="8 mins", interval="0 seconds")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600901
902 # Get the uptime and epoch seconds for comparisons. We want to be sure
903 # that the uptime is less than the elapsed boot time. Further proof that
904 # a reboot has indeed occurred (vs random network instability giving a
905 # false positive.
Michael Walsh01975fa2017-08-20 20:51:36 -0500906 state = get_state(req_states=['uptime', 'epoch_seconds'], quiet=quiet)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600907
908 elapsed_boot_time = int(state['epoch_seconds']) - start_boot_seconds
Michael Walsh619aa332017-04-12 15:56:51 -0500909 gp.qprint_var(elapsed_boot_time)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600910 if int(float(state['uptime'])) < elapsed_boot_time:
911 uptime = state['uptime']
Michael Walsh619aa332017-04-12 15:56:51 -0500912 gp.qprint_var(uptime)
913 gp.qprint_timen("The uptime is less than the elapsed boot time," +
914 " as expected.")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600915 else:
916 error_message = "The uptime is greater than the elapsed boot time," +\
917 " which is unexpected:\n" +\
918 gp.sprint_var(start_boot_seconds) +\
919 gp.sprint_var(state)
920 BuiltIn().fail(gp.sprint_error(error_message))
921
Michael Walsh619aa332017-04-12 15:56:51 -0500922 gp.qprint_timen("Verifying that REST API interface is working.")
Michael Walshb95eb542017-03-31 09:39:20 -0500923 match_state = DotDict([('rest', '^1$')])
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600924 state = wait_state(match_state, wait_time="5 mins", interval="2 seconds")
925
926###############################################################################