blob: 73a35a2ddc5d04e99aa3598cd58e7ea732e6ec63 [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 Walsh65b12542017-02-03 15:34:38 -060014 default_state[host]: Running
Michael Walsh341c21e2017-01-17 16:25:20 -060015 default_state[os_ping]: 1
16 default_state[os_login]: 1
17 default_state[os_run_cmd]: 1
Michael Walsh70369fd2016-11-22 11:25:57 -060018
19Different users may very well have different needs when inquiring about
Michael Walsh8fae6ea2017-02-20 16:14:44 -060020state. Support for new pieces of state information may be added to this
21module as needed.
Michael Walsh70369fd2016-11-22 11:25:57 -060022
23By using the wait_state function, a caller can start a boot and then wait for
24a precisely defined state to indicate that the boot has succeeded. If
25the boot fails, they can see exactly why by looking at the current state as
26compared with the expected state.
27"""
28
29import gen_print as gp
30import gen_robot_print as grp
31import gen_valid as gv
Michael Walsh16cbb7f2017-02-02 15:54:16 -060032import gen_robot_utils as gru
Michael Walsh8fae6ea2017-02-20 16:14:44 -060033import gen_cmd as gc
Michael Walsh70369fd2016-11-22 11:25:57 -060034
35import commands
36from robot.libraries.BuiltIn import BuiltIn
Michael Walsh341c21e2017-01-17 16:25:20 -060037from robot.utils import DotDict
Michael Walsh70369fd2016-11-22 11:25:57 -060038
39import re
Michael Walsh341c21e2017-01-17 16:25:20 -060040import os
Michael Walsh70369fd2016-11-22 11:25:57 -060041
Michael Walsh619aa332017-04-12 15:56:51 -050042# We need utils.robot to get keywords like "Get Chassis Power State".
Michael Walsh16cbb7f2017-02-02 15:54:16 -060043gru.my_import_resource("utils.robot")
44gru.my_import_resource("state_manager.robot")
Michael Walsh70369fd2016-11-22 11:25:57 -060045
Michael Walsh8fae6ea2017-02-20 16:14:44 -060046# The BMC code has recently been changed as far as what states are defined and
47# what the state values can be. This module now has a means of processing both
48# the old style state (i.e. OBMC_STATES_VERSION = 0) and the new style (i.e.
Michael Walsh16cbb7f2017-02-02 15:54:16 -060049# OBMC_STATES_VERSION = 1).
Michael Walsh341c21e2017-01-17 16:25:20 -060050# The caller can set environment variable OBMC_STATES_VERSION to dictate
51# whether we're processing old or new style states. If OBMC_STATES_VERSION is
Michael Walsh8fae6ea2017-02-20 16:14:44 -060052# not set it will default to 1.
Michael Walsh341c21e2017-01-17 16:25:20 -060053
Michael Walsh619aa332017-04-12 15:56:51 -050054# As of the present moment, OBMC_STATES_VERSION of 0 is for cold that is so old
55# that it is no longer worthwhile to maintain. The OBMC_STATES_VERSION 0 code
56# is being removed but the OBMC_STATES_VERSION value will stay for now in the
57# event that it is needed in the future.
58
Michael Walsh8fae6ea2017-02-20 16:14:44 -060059OBMC_STATES_VERSION = int(os.environ.get('OBMC_STATES_VERSION', 1))
Michael Walsh341c21e2017-01-17 16:25:20 -060060
Michael Walsh619aa332017-04-12 15:56:51 -050061# When a user calls get_state w/o specifying req_states, default_req_states
62# is used as its value.
63default_req_states = ['rest',
64 'chassis',
65 'bmc',
66 'boot_progress',
67 'host',
68 'os_ping',
69 'os_login',
70 'os_run_cmd']
Michael Walsh8fae6ea2017-02-20 16:14:44 -060071
Michael Walsh619aa332017-04-12 15:56:51 -050072# valid_req_states is a list of sub states supported by the get_state function.
73# valid_req_states, default_req_states and master_os_up_match are used by the
74# get_state function.
75valid_req_states = ['ping',
76 'packet_loss',
77 'uptime',
78 'epoch_seconds',
79 'rest',
80 'chassis',
81 'bmc',
82 'boot_progress',
83 'host',
84 'os_ping',
85 'os_login',
86 'os_run_cmd']
Michael Walsh8fae6ea2017-02-20 16:14:44 -060087
88# valid_os_req_states and default_os_req_states are used by the os_get_state
89# function.
90# valid_os_req_states is a list of state information supported by the
91# get_os_state function.
92valid_os_req_states = ['os_ping',
93 'os_login',
94 'os_run_cmd']
95# When a user calls get_os_state w/o specifying req_states,
96# default_os_req_states is used as its value.
97default_os_req_states = ['os_ping',
98 'os_login',
99 'os_run_cmd']
100
101# Presently, some BMCs appear to not keep time very well. This environment
102# variable directs the get_state function to use either the BMC's epoch time
103# or the local epoch time.
104USE_BMC_EPOCH_TIME = int(os.environ.get('USE_BMC_EPOCH_TIME', 0))
Michael Walsh341c21e2017-01-17 16:25:20 -0600105
Michael Walsh619aa332017-04-12 15:56:51 -0500106# Useful state constant definition(s).
107# A match state for checking that the system is at "standby".
108standby_match_state = DotDict([('rest', '^1$'),
109 ('chassis', '^Off$'),
110 ('bmc', '^Ready$'),
111 ('boot_progress', ''),
112 ('host', '')])
113
114# default_state is an initial value which may be of use to callers.
115default_state = DotDict([('rest', '1'),
116 ('chassis', 'On'),
117 ('bmc', 'Ready'),
Michael Walsh01975fa2017-08-20 20:51:36 -0500118 ('boot_progress', 'OSStart'),
Michael Walsh619aa332017-04-12 15:56:51 -0500119 ('host', 'Running'),
120 ('os_ping', '1'),
121 ('os_login', '1'),
122 ('os_run_cmd', '1')])
123
124# A master dictionary to determine whether the os may be up.
125master_os_up_match = DotDict([('chassis', '^On$'),
126 ('bmc', '^Ready$'),
127 ('boot_progress',
Michael Walsh01975fa2017-08-20 20:51:36 -0500128 'FW Progress, Starting OS|OSStart'),
Michael Walsh619aa332017-04-12 15:56:51 -0500129 ('host', '^Running$')])
130
Michael Walsh45ca6e42017-09-14 17:29:12 -0500131invalid_state_match = DotDict([('rest', '^$'),
132 ('chassis', '^$'),
133 ('bmc', '^$'),
134 ('boot_progress', '^$'),
135 ('host', '^$')])
136
Michael Walsh341c21e2017-01-17 16:25:20 -0600137
138###############################################################################
139def return_default_state():
140
141 r"""
142 Return default state dictionary.
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600143
144 default_state is an initial value which may be of use to callers.
Michael Walsh341c21e2017-01-17 16:25:20 -0600145 """
146
147 return default_state
148
149###############################################################################
150
Michael Walsh70369fd2016-11-22 11:25:57 -0600151
Michael Walsh619aa332017-04-12 15:56:51 -0500152valid_state_constants = ['default', 'standby_match_state']
153
154
155###############################################################################
156def return_state_constant(state_name='default'):
157
158 r"""
159 Return default state dictionary.
160
161 default_state is an initial value which may be of use to callers.
162 """
163
164 error_message = gv.svalid_value(state_name, var_name='state_name',
165 valid_values=valid_state_constants)
166 if error_message != "":
167 BuiltIn().fail(gp.sprint_error(error_message))
168
169 if state_name == 'default':
170 return default_state
171 elif state_name == 'standby_match_state':
172 return standby_match_state
173
174###############################################################################
175
176
Michael Walsh70369fd2016-11-22 11:25:57 -0600177###############################################################################
178def anchor_state(state):
179
180 r"""
181 Add regular expression anchors ("^" and "$") to the beginning and end of
182 each item in the state dictionary passed in. Return the resulting
183 dictionary.
184
185 Description of Arguments:
186 state A dictionary such as the one returned by the get_state()
187 function.
188 """
189
Michael Walsh2ce067a2017-02-27 14:24:07 -0600190 anchored_state = state.copy()
Michael Walsh70369fd2016-11-22 11:25:57 -0600191 for key, match_state_value in anchored_state.items():
192 anchored_state[key] = "^" + str(anchored_state[key]) + "$"
193
194 return anchored_state
195
196###############################################################################
197
198
199###############################################################################
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600200def strip_anchor_state(state):
201
202 r"""
203 Strip regular expression anchors ("^" and "$") from the beginning and end
204 of each item in the state dictionary passed in. Return the resulting
205 dictionary.
206
207 Description of Arguments:
208 state A dictionary such as the one returned by the get_state()
209 function.
210 """
211
Michael Walsh2ce067a2017-02-27 14:24:07 -0600212 stripped_state = state.copy()
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600213 for key, match_state_value in stripped_state.items():
214 stripped_state[key] = stripped_state[key].strip("^$")
215
216 return stripped_state
217
218###############################################################################
219
220
221###############################################################################
Michael Walsh70369fd2016-11-22 11:25:57 -0600222def compare_states(state,
Michael Walsh45ca6e42017-09-14 17:29:12 -0500223 match_state,
224 match_type='and'):
Michael Walsh70369fd2016-11-22 11:25:57 -0600225
226 r"""
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600227 Compare 2 state dictionaries. Return True if they match and False if they
Michael Walsh70369fd2016-11-22 11:25:57 -0600228 don't. Note that the match_state dictionary does not need to have an entry
229 corresponding to each entry in the state dictionary. But for each entry
230 that it does have, the corresponding state entry will be checked for a
231 match.
232
233 Description of arguments:
234 state A state dictionary such as the one returned by the
235 get_state function.
236 match_state A dictionary whose key/value pairs are "state field"/
237 "state value". The state value is interpreted as a
238 regular expression. Every value in this dictionary is
Michael Walsh45ca6e42017-09-14 17:29:12 -0500239 considered. When match_type is 'and', if each and every
240 comparison matches, the two dictionaries are considered to
241 be matching. If match_type is 'or', if any two of the
242 elements compared match, the two dictionaries are
243 considered to be matching.
244 match_type This may be 'and' or 'or'.
Michael Walsh70369fd2016-11-22 11:25:57 -0600245 """
246
Michael Walsh45ca6e42017-09-14 17:29:12 -0500247 error_message = gv.svalid_value(match_type, var_name="match_type",
248 valid_values=['and', 'or'])
249 if error_message != "":
250 BuiltIn().fail(gp.sprint_error(error_message))
251
252 default_match = (match_type == 'and')
Michael Walsh70369fd2016-11-22 11:25:57 -0600253 for key, match_state_value in match_state.items():
Michael Walsh97df71c2017-03-27 14:33:24 -0500254 # Blank match_state_value means "don't care".
255 if match_state_value == "":
256 continue
Michael Walsh70369fd2016-11-22 11:25:57 -0600257 try:
Michael Walsh45ca6e42017-09-14 17:29:12 -0500258 match = (re.match(match_state_value, str(state[key])) is not None)
Michael Walsh70369fd2016-11-22 11:25:57 -0600259 except KeyError:
260 match = False
Michael Walsh70369fd2016-11-22 11:25:57 -0600261
Michael Walsh45ca6e42017-09-14 17:29:12 -0500262 if match != default_match:
263 return match
264
265 return default_match
Michael Walsh70369fd2016-11-22 11:25:57 -0600266
267###############################################################################
268
269
270###############################################################################
271def get_os_state(os_host="",
272 os_username="",
273 os_password="",
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600274 req_states=default_os_req_states,
275 os_up=True,
Michael Walsh70369fd2016-11-22 11:25:57 -0600276 quiet=None):
277
278 r"""
279 Get component states for the operating system such as ping, login,
280 etc, put them into a dictionary and return them to the caller.
281
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600282 Note that all substate values are strings.
283
Michael Walsh70369fd2016-11-22 11:25:57 -0600284 Description of arguments:
285 os_host The DNS name or IP address of the operating system.
286 This defaults to global ${OS_HOST}.
287 os_username The username to be used to login to the OS.
288 This defaults to global ${OS_USERNAME}.
289 os_password The password to be used to login to the OS.
290 This defaults to global ${OS_PASSWORD}.
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600291 req_states This is a list of states whose values are being requested by
292 the caller.
293 os_up If the caller knows that the os can't possibly be up, it can
294 improve performance by passing os_up=False. This function
295 will then simply return default values for all requested os
296 sub states.
Michael Walsh70369fd2016-11-22 11:25:57 -0600297 quiet Indicates whether status details (e.g. curl commands) should
298 be written to the console.
299 Defaults to either global value of ${QUIET} or to 1.
300 """
301
Michael Walsh619aa332017-04-12 15:56:51 -0500302 quiet = int(gp.get_var_value(quiet, 0))
Michael Walsh70369fd2016-11-22 11:25:57 -0600303
304 # Set parm defaults where necessary and validate all parms.
305 if os_host == "":
306 os_host = BuiltIn().get_variable_value("${OS_HOST}")
307 error_message = gv.svalid_value(os_host, var_name="os_host",
308 invalid_values=[None, ""])
309 if error_message != "":
310 BuiltIn().fail(gp.sprint_error(error_message))
311
312 if os_username == "":
313 os_username = BuiltIn().get_variable_value("${OS_USERNAME}")
314 error_message = gv.svalid_value(os_username, var_name="os_username",
315 invalid_values=[None, ""])
316 if error_message != "":
317 BuiltIn().fail(gp.sprint_error(error_message))
318
319 if os_password == "":
320 os_password = BuiltIn().get_variable_value("${OS_PASSWORD}")
321 error_message = gv.svalid_value(os_password, var_name="os_password",
322 invalid_values=[None, ""])
323 if error_message != "":
324 BuiltIn().fail(gp.sprint_error(error_message))
325
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600326 invalid_req_states = [sub_state for sub_state in req_states
327 if sub_state not in valid_os_req_states]
328 if len(invalid_req_states) > 0:
329 error_message = "The following req_states are not supported:\n" +\
330 gp.sprint_var(invalid_req_states)
331 BuiltIn().fail(gp.sprint_error(error_message))
Michael Walsh70369fd2016-11-22 11:25:57 -0600332
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600333 # Initialize all substate values supported by this function.
334 os_ping = 0
335 os_login = 0
336 os_run_cmd = 0
Michael Walsh70369fd2016-11-22 11:25:57 -0600337
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600338 if os_up:
339 if 'os_ping' in req_states:
340 # See if the OS pings.
341 cmd_buf = "ping -c 1 -w 2 " + os_host
342 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500343 gp.pissuing(cmd_buf)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600344 rc, out_buf = commands.getstatusoutput(cmd_buf)
345 if rc == 0:
346 os_ping = 1
Michael Walsh70369fd2016-11-22 11:25:57 -0600347
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600348 # Programming note: All attributes which do not require an ssh login
349 # should have been processed by this point.
350 master_req_login = ['os_login', 'os_run_cmd']
351 req_login = [sub_state for sub_state in req_states if sub_state in
352 master_req_login]
Michael Walsh97df71c2017-03-27 14:33:24 -0500353 must_login = (len(req_login) > 0)
Michael Walsh70369fd2016-11-22 11:25:57 -0600354
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600355 if must_login:
Michael Walsh3eb50022017-03-21 11:27:30 -0500356 # Open SSH connection to OS. Note that this doesn't fail even when
357 # the OS is not up.
Michael Walshac275512017-03-07 11:39:28 -0600358 cmd_buf = ["SSHLibrary.Open Connection", os_host]
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600359 if not quiet:
360 grp.rpissuing_keyword(cmd_buf)
361 ix = BuiltIn().run_keyword(*cmd_buf)
362
363 # Login to OS.
364 cmd_buf = ["Login", os_username, os_password]
365 if not quiet:
366 grp.rpissuing_keyword(cmd_buf)
Michael Walsh3eb50022017-03-21 11:27:30 -0500367 status, ret_values = \
368 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600369 if status == "PASS":
370 os_login = 1
Michael Walsh3eb50022017-03-21 11:27:30 -0500371 else:
372 gp.dprint_var(status)
373 gp.dprint_var(ret_values)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600374
375 if os_login:
376 if 'os_run_cmd' in req_states:
Michael Walsh3eb50022017-03-21 11:27:30 -0500377 # Try running a simple command (uptime) on the OS.
378 cmd_buf = ["Execute Command", "uptime",
379 "return_stderr=True", "return_rc=True"]
380 if not quiet:
381 grp.rpissuing_keyword(cmd_buf)
382 # Note that in spite of its name, there are occasions
383 # where run_keyword_and_ignore_error can fail.
384 status, ret_values = \
385 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
386 if status == "PASS":
387 stdout, stderr, rc = ret_values
388 if rc == 0 and stderr == "":
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600389 os_run_cmd = 1
Michael Walsh3eb50022017-03-21 11:27:30 -0500390 else:
391 gp.dprint_var(status)
392 gp.dprint_var(stdout)
393 gp.dprint_var(stderr)
394 gp.dprint_var(rc)
395 else:
396 gp.dprint_var(status)
397 gp.dprint_var(ret_values)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600398
399 os_state = DotDict()
400 for sub_state in req_states:
401 cmd_buf = "os_state['" + sub_state + "'] = str(" + sub_state + ")"
402 exec(cmd_buf)
Michael Walsh70369fd2016-11-22 11:25:57 -0600403
404 return os_state
405
406###############################################################################
407
408
409###############################################################################
410def get_state(openbmc_host="",
411 openbmc_username="",
412 openbmc_password="",
413 os_host="",
414 os_username="",
415 os_password="",
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600416 req_states=default_req_states,
Michael Walsh70369fd2016-11-22 11:25:57 -0600417 quiet=None):
418
419 r"""
Michael Walsh619aa332017-04-12 15:56:51 -0500420 Get component states such as chassis state, bmc state, etc, put them into a
Michael Walsh70369fd2016-11-22 11:25:57 -0600421 dictionary and return them to the caller.
422
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600423 Note that all substate values are strings.
424
Michael Walsh70369fd2016-11-22 11:25:57 -0600425 Description of arguments:
426 openbmc_host The DNS name or IP address of the BMC.
427 This defaults to global ${OPENBMC_HOST}.
428 openbmc_username The username to be used to login to the BMC.
429 This defaults to global ${OPENBMC_USERNAME}.
430 openbmc_password The password to be used to login to the BMC.
431 This defaults to global ${OPENBMC_PASSWORD}.
432 os_host The DNS name or IP address of the operating system.
433 This defaults to global ${OS_HOST}.
434 os_username The username to be used to login to the OS.
435 This defaults to global ${OS_USERNAME}.
436 os_password The password to be used to login to the OS.
437 This defaults to global ${OS_PASSWORD}.
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600438 req_states This is a list of states whose values are being requested
439 by the caller.
Michael Walsh70369fd2016-11-22 11:25:57 -0600440 quiet Indicates whether status details (e.g. curl commands)
441 should be written to the console.
442 Defaults to either global value of ${QUIET} or to 1.
443 """
444
Michael Walsh619aa332017-04-12 15:56:51 -0500445 quiet = int(gp.get_var_value(quiet, 0))
Michael Walsh70369fd2016-11-22 11:25:57 -0600446
447 # Set parm defaults where necessary and validate all parms.
448 if openbmc_host == "":
449 openbmc_host = BuiltIn().get_variable_value("${OPENBMC_HOST}")
450 error_message = gv.svalid_value(openbmc_host,
451 var_name="openbmc_host",
452 invalid_values=[None, ""])
453 if error_message != "":
454 BuiltIn().fail(gp.sprint_error(error_message))
455
456 if openbmc_username == "":
457 openbmc_username = BuiltIn().get_variable_value("${OPENBMC_USERNAME}")
458 error_message = gv.svalid_value(openbmc_username,
459 var_name="openbmc_username",
460 invalid_values=[None, ""])
461 if error_message != "":
462 BuiltIn().fail(gp.sprint_error(error_message))
463
464 if openbmc_password == "":
465 openbmc_password = BuiltIn().get_variable_value("${OPENBMC_PASSWORD}")
466 error_message = gv.svalid_value(openbmc_password,
467 var_name="openbmc_password",
468 invalid_values=[None, ""])
469 if error_message != "":
470 BuiltIn().fail(gp.sprint_error(error_message))
471
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600472 # NOTE: OS parms are optional.
Michael Walsh70369fd2016-11-22 11:25:57 -0600473 if os_host == "":
474 os_host = BuiltIn().get_variable_value("${OS_HOST}")
475 if os_host is None:
476 os_host = ""
477
478 if os_username is "":
479 os_username = BuiltIn().get_variable_value("${OS_USERNAME}")
480 if os_username is None:
481 os_username = ""
482
483 if os_password is "":
484 os_password = BuiltIn().get_variable_value("${OS_PASSWORD}")
485 if os_password is None:
486 os_password = ""
487
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600488 invalid_req_states = [sub_state for sub_state in req_states
489 if sub_state not in valid_req_states]
490 if len(invalid_req_states) > 0:
491 error_message = "The following req_states are not supported:\n" +\
492 gp.sprint_var(invalid_req_states)
493 BuiltIn().fail(gp.sprint_error(error_message))
494
495 # Initialize all substate values supported by this function.
496 ping = 0
497 packet_loss = ''
498 uptime = ''
499 epoch_seconds = ''
Michael Walshb95eb542017-03-31 09:39:20 -0500500 rest = '1'
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600501 chassis = ''
502 bmc = ''
503 boot_progress = ''
504 host = ''
505
Michael Walsh70369fd2016-11-22 11:25:57 -0600506 # Get the component states.
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600507 if 'ping' in req_states:
508 # See if the OS pings.
509 cmd_buf = "ping -c 1 -w 2 " + openbmc_host
510 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500511 gp.pissuing(cmd_buf)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600512 rc, out_buf = commands.getstatusoutput(cmd_buf)
513 if rc == 0:
514 ping = 1
515
516 if 'packet_loss' in req_states:
517 # See if the OS pings.
518 cmd_buf = "ping -c 5 -w 5 " + openbmc_host +\
519 " | egrep 'packet loss' | sed -re 's/.* ([0-9]+)%.*/\\1/g'"
520 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500521 gp.pissuing(cmd_buf)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600522 rc, out_buf = commands.getstatusoutput(cmd_buf)
523 if rc == 0:
524 packet_loss = out_buf.rstrip("\n")
525
Michael Walshe53e47a2017-06-30 17:03:24 -0500526 if 'uptime' in req_states:
527 cmd_buf = ["BMC Execute Command", "cat /proc/uptime | cut -f 1 -d ' '",
528 'quiet=${1}']
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600529 if not quiet:
530 grp.rpissuing_keyword(cmd_buf)
Michael Walsh97df71c2017-03-27 14:33:24 -0500531 status, ret_values = \
532 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
533 if status == "PASS":
534 stdout, stderr, rc = ret_values
535 if rc == 0 and stderr == "":
536 uptime = stdout
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600537
Michael Walshe53e47a2017-06-30 17:03:24 -0500538 if 'epoch_seconds' in req_states:
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600539 date_cmd_buf = "date -u +%s"
540 if USE_BMC_EPOCH_TIME:
Michael Walshe53e47a2017-06-30 17:03:24 -0500541 cmd_buf = ["BMC Execute Command", date_cmd_buf, 'quiet=${1}']
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600542 if not quiet:
543 grp.rpissuing_keyword(cmd_buf)
Michael Walsh97df71c2017-03-27 14:33:24 -0500544 status, ret_values = \
545 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
546 if status == "PASS":
547 stdout, stderr, rc = ret_values
548 if rc == 0 and stderr == "":
549 epoch_seconds = stdout.rstrip("\n")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600550 else:
551 shell_rc, out_buf = gc.cmd_fnc_u(date_cmd_buf,
552 quiet=1,
553 print_output=0)
554 if shell_rc == 0:
555 epoch_seconds = out_buf.rstrip("\n")
556
Michael Walsh619aa332017-04-12 15:56:51 -0500557 master_req_rest = ['rest', 'chassis', 'bmc', 'boot_progress',
Michael Walshb95eb542017-03-31 09:39:20 -0500558 'host']
559 req_rest = [sub_state for sub_state in req_states if sub_state in
560 master_req_rest]
561 need_rest = (len(req_rest) > 0)
562
563 # Though we could try to determine 'rest' state on any of several calls,
564 # for simplicity, we'll use 'chassis' to figure it out (even if the caller
565 # hasn't explicitly asked for 'chassis').
566 if 'chassis' in req_states or need_rest:
Michael Walsh341c21e2017-01-17 16:25:20 -0600567 cmd_buf = ["Get Chassis Power State", "quiet=${" + str(quiet) + "}"]
568 grp.rdpissuing_keyword(cmd_buf)
Michael Walsh97df71c2017-03-27 14:33:24 -0500569 status, ret_values = \
570 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
571 if status == "PASS":
572 chassis = ret_values
573 chassis = re.sub(r'.*\.', "", chassis)
Michael Walshb95eb542017-03-31 09:39:20 -0500574 rest = '1'
Michael Walsh341c21e2017-01-17 16:25:20 -0600575 else:
Michael Walshb95eb542017-03-31 09:39:20 -0500576 rest = ret_values
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600577
Michael Walshb95eb542017-03-31 09:39:20 -0500578 if rest == '1':
Michael Walshb95eb542017-03-31 09:39:20 -0500579 if 'bmc' in req_states:
580 if OBMC_STATES_VERSION == 0:
581 qualifier = "utils"
582 else:
583 # This will not be supported much longer.
584 qualifier = "state_manager"
585 cmd_buf = [qualifier + ".Get BMC State",
586 "quiet=${" + str(quiet) + "}"]
587 grp.rdpissuing_keyword(cmd_buf)
588 status, ret_values = \
589 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
590 if status == "PASS":
591 bmc = ret_values
592
593 if 'boot_progress' in req_states:
594 cmd_buf = ["Get Boot Progress", "quiet=${" + str(quiet) + "}"]
595 grp.rdpissuing_keyword(cmd_buf)
596 status, ret_values = \
597 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
598 if status == "PASS":
599 boot_progress = ret_values
600
601 if 'host' in req_states:
602 if OBMC_STATES_VERSION > 0:
603 cmd_buf = ["Get Host State", "quiet=${" + str(quiet) + "}"]
604 grp.rdpissuing_keyword(cmd_buf)
605 status, ret_values = \
606 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
607 if status == "PASS":
608 host = ret_values
609 # Strip everything up to the final period.
610 host = re.sub(r'.*\.', "", host)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600611
612 state = DotDict()
613 for sub_state in req_states:
614 if sub_state.startswith("os_"):
615 # We pass "os_" requests on to get_os_state.
616 continue
617 cmd_buf = "state['" + sub_state + "'] = str(" + sub_state + ")"
618 exec(cmd_buf)
619
620 if os_host == "":
621 # The caller has not specified an os_host so as far as we're concerned,
622 # it doesn't exist.
623 return state
624
625 os_req_states = [sub_state for sub_state in req_states
626 if sub_state.startswith('os_')]
627
628 if len(os_req_states) > 0:
629 # The caller has specified an os_host and they have requested
630 # information on os substates.
631
632 # Based on the information gathered on bmc, we'll try to make a
633 # determination of whether the os is even up. We'll pass the result
634 # of that assessment to get_os_state to enhance performance.
635 os_up_match = DotDict()
636 for sub_state in master_os_up_match:
637 if sub_state in req_states:
638 os_up_match[sub_state] = master_os_up_match[sub_state]
Michael Walsh70369fd2016-11-22 11:25:57 -0600639 os_up = compare_states(state, os_up_match)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600640 os_state = get_os_state(os_host=os_host,
641 os_username=os_username,
642 os_password=os_password,
643 req_states=os_req_states,
644 os_up=os_up,
645 quiet=quiet)
646 # Append os_state dictionary to ours.
647 state.update(os_state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600648
649 return state
650
651###############################################################################
652
653
654###############################################################################
655def check_state(match_state,
656 invert=0,
657 print_string="",
658 openbmc_host="",
659 openbmc_username="",
660 openbmc_password="",
661 os_host="",
662 os_username="",
663 os_password="",
664 quiet=None):
665
666 r"""
667 Check that the Open BMC machine's composite state matches the specified
668 state. On success, this keyword returns the machine's composite state as a
669 dictionary.
670
671 Description of arguments:
672 match_state A dictionary whose key/value pairs are "state field"/
673 "state value". The state value is interpreted as a
674 regular expression. Example call from robot:
Michael Walsh341c21e2017-01-17 16:25:20 -0600675 ${match_state}= Create Dictionary chassis=^On$
676 ... bmc=^Ready$
Michael Walsh01975fa2017-08-20 20:51:36 -0500677 ... boot_progress=^OSStart$
Michael Walsh70369fd2016-11-22 11:25:57 -0600678 ${state}= Check State &{match_state}
679 invert If this flag is set, this function will succeed if the
680 states do NOT match.
681 print_string This function will print this string to the console prior
682 to getting the state.
683 openbmc_host The DNS name or IP address of the BMC.
684 This defaults to global ${OPENBMC_HOST}.
685 openbmc_username The username to be used to login to the BMC.
686 This defaults to global ${OPENBMC_USERNAME}.
687 openbmc_password The password to be used to login to the BMC.
688 This defaults to global ${OPENBMC_PASSWORD}.
689 os_host The DNS name or IP address of the operating system.
690 This defaults to global ${OS_HOST}.
691 os_username The username to be used to login to the OS.
692 This defaults to global ${OS_USERNAME}.
693 os_password The password to be used to login to the OS.
694 This defaults to global ${OS_PASSWORD}.
695 quiet Indicates whether status details should be written to the
696 console. Defaults to either global value of ${QUIET} or
697 to 1.
698 """
699
Michael Walsh619aa332017-04-12 15:56:51 -0500700 quiet = int(gp.get_var_value(quiet, 0))
Michael Walsh70369fd2016-11-22 11:25:57 -0600701
702 grp.rprint(print_string)
703
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600704 req_states = match_state.keys()
Michael Walsh70369fd2016-11-22 11:25:57 -0600705 # Initialize state.
706 state = get_state(openbmc_host=openbmc_host,
707 openbmc_username=openbmc_username,
708 openbmc_password=openbmc_password,
709 os_host=os_host,
710 os_username=os_username,
711 os_password=os_password,
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600712 req_states=req_states,
Michael Walsh70369fd2016-11-22 11:25:57 -0600713 quiet=quiet)
714 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500715 gp.print_var(state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600716
717 match = compare_states(state, match_state)
718
719 if invert and match:
720 fail_msg = "The current state of the machine matches the match" +\
721 " state:\n" + gp.sprint_varx("state", state)
722 BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
723 elif not invert and not match:
724 fail_msg = "The current state of the machine does NOT match the" +\
725 " match state:\n" +\
726 gp.sprint_varx("state", state)
727 BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
728
729 return state
730
731###############################################################################
732
733
734###############################################################################
Michael Walshf893ba02017-01-10 10:28:05 -0600735def wait_state(match_state=(),
Michael Walsh70369fd2016-11-22 11:25:57 -0600736 wait_time="1 min",
737 interval="1 second",
738 invert=0,
739 openbmc_host="",
740 openbmc_username="",
741 openbmc_password="",
742 os_host="",
743 os_username="",
744 os_password="",
745 quiet=None):
746
747 r"""
748 Wait for the Open BMC machine's composite state to match the specified
749 state. On success, this keyword returns the machine's composite state as
750 a dictionary.
751
752 Description of arguments:
753 match_state A dictionary whose key/value pairs are "state field"/
754 "state value". See check_state (above) for details.
Michael Walsh619aa332017-04-12 15:56:51 -0500755 This value may also be any string accepted by
756 return_state_constant (e.g. "standby_match_state").
757 In such a case this function will call
758 return_state_constant to convert it to a proper
759 dictionary as described above.
Michael Walsh70369fd2016-11-22 11:25:57 -0600760 wait_time The total amount of time to wait for the desired state.
761 This value may be expressed in Robot Framework's time
762 format (e.g. 1 minute, 2 min 3 s, 4.5).
763 interval The amount of time between state checks.
764 This value may be expressed in Robot Framework's time
765 format (e.g. 1 minute, 2 min 3 s, 4.5).
766 invert If this flag is set, this function will for the state of
767 the machine to cease to match the match state.
768 openbmc_host The DNS name or IP address of the BMC.
769 This defaults to global ${OPENBMC_HOST}.
770 openbmc_username The username to be used to login to the BMC.
771 This defaults to global ${OPENBMC_USERNAME}.
772 openbmc_password The password to be used to login to the BMC.
773 This defaults to global ${OPENBMC_PASSWORD}.
774 os_host The DNS name or IP address of the operating system.
775 This defaults to global ${OS_HOST}.
776 os_username The username to be used to login to the OS.
777 This defaults to global ${OS_USERNAME}.
778 os_password The password to be used to login to the OS.
779 This defaults to global ${OS_PASSWORD}.
780 quiet Indicates whether status details should be written to the
781 console. Defaults to either global value of ${QUIET} or
782 to 1.
783 """
784
Michael Walsh619aa332017-04-12 15:56:51 -0500785 quiet = int(gp.get_var_value(quiet, 0))
786
787 if type(match_state) in (str, unicode):
788 match_state = return_state_constant(match_state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600789
790 if not quiet:
791 if invert:
792 alt_text = "cease to "
793 else:
794 alt_text = ""
Michael Walsh3eb50022017-03-21 11:27:30 -0500795 gp.print_timen("Checking every " + str(interval) + " for up to " +
796 str(wait_time) + " for the state of the machine to " +
797 alt_text + "match the state shown below.")
798 gp.print_var(match_state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600799
Michael Walshf893ba02017-01-10 10:28:05 -0600800 if quiet:
Michael Walsh341c21e2017-01-17 16:25:20 -0600801 print_string = ""
Michael Walshf893ba02017-01-10 10:28:05 -0600802 else:
Michael Walsh341c21e2017-01-17 16:25:20 -0600803 print_string = "#"
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600804
805 debug = int(BuiltIn().get_variable_value("${debug}", "0"))
806 if debug:
807 # In debug we print state so no need to print the "#".
808 print_string = ""
809 check_state_quiet = 1 - debug
Michael Walsh70369fd2016-11-22 11:25:57 -0600810 cmd_buf = ["Check State", match_state, "invert=${" + str(invert) + "}",
Michael Walshf893ba02017-01-10 10:28:05 -0600811 "print_string=" + print_string, "openbmc_host=" + openbmc_host,
Michael Walsh70369fd2016-11-22 11:25:57 -0600812 "openbmc_username=" + openbmc_username,
813 "openbmc_password=" + openbmc_password, "os_host=" + os_host,
814 "os_username=" + os_username, "os_password=" + os_password,
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600815 "quiet=${" + str(check_state_quiet) + "}"]
Michael Walsh70369fd2016-11-22 11:25:57 -0600816 grp.rdpissuing_keyword(cmd_buf)
Michael Walsh619aa332017-04-12 15:56:51 -0500817 try:
818 state = BuiltIn().wait_until_keyword_succeeds(wait_time, interval,
819 *cmd_buf)
820 except AssertionError as my_assertion_error:
821 gp.printn()
822 message = my_assertion_error.args[0]
823 BuiltIn().fail(message)
824
Michael Walsh70369fd2016-11-22 11:25:57 -0600825 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500826 gp.printn()
Michael Walsh70369fd2016-11-22 11:25:57 -0600827 if invert:
Michael Walsh3eb50022017-03-21 11:27:30 -0500828 gp.print_timen("The states no longer match:")
Michael Walsh70369fd2016-11-22 11:25:57 -0600829 else:
Michael Walsh3eb50022017-03-21 11:27:30 -0500830 gp.print_timen("The states match:")
831 gp.print_var(state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600832
833 return state
834
835###############################################################################
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600836
837
838###############################################################################
Michael Walsh619aa332017-04-12 15:56:51 -0500839def wait_for_comm_cycle(start_boot_seconds,
840 quiet=None):
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600841
842 r"""
843 Wait for communications to the BMC to stop working and then resume working.
844 This function is useful when you have initiated some kind of reboot.
845
846 Description of arguments:
847 start_boot_seconds The time that the boot test started. The format is the
848 epoch time in seconds, i.e. the number of seconds since
849 1970-01-01 00:00:00 UTC. This value should be obtained
850 from the BMC so that it is not dependent on any kind of
851 synchronization between this machine and the target BMC
852 This will allow this program to work correctly even in
853 a simulated environment. This value should be obtained
854 by the caller prior to initiating a reboot. It can be
855 obtained as follows:
856 state = st.get_state(req_states=['epoch_seconds'])
857 """
858
Michael Walsh619aa332017-04-12 15:56:51 -0500859 quiet = int(gp.get_var_value(quiet, 0))
860
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600861 # Validate parms.
862 error_message = gv.svalid_integer(start_boot_seconds,
863 var_name="start_boot_seconds")
864 if error_message != "":
865 BuiltIn().fail(gp.sprint_error(error_message))
866
867 match_state = anchor_state(DotDict([('packet_loss', '100')]))
868 # Wait for 100% packet loss trying to ping machine.
Michael Walsh619aa332017-04-12 15:56:51 -0500869 wait_state(match_state, wait_time="8 mins", interval="0 seconds")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600870
871 match_state['packet_loss'] = '^0$'
872 # Wait for 0% packet loss trying to ping machine.
Michael Walsh619aa332017-04-12 15:56:51 -0500873 wait_state(match_state, wait_time="8 mins", interval="0 seconds")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600874
875 # Get the uptime and epoch seconds for comparisons. We want to be sure
876 # that the uptime is less than the elapsed boot time. Further proof that
877 # a reboot has indeed occurred (vs random network instability giving a
878 # false positive.
Michael Walsh01975fa2017-08-20 20:51:36 -0500879 state = get_state(req_states=['uptime', 'epoch_seconds'], quiet=quiet)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600880
881 elapsed_boot_time = int(state['epoch_seconds']) - start_boot_seconds
Michael Walsh619aa332017-04-12 15:56:51 -0500882 gp.qprint_var(elapsed_boot_time)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600883 if int(float(state['uptime'])) < elapsed_boot_time:
884 uptime = state['uptime']
Michael Walsh619aa332017-04-12 15:56:51 -0500885 gp.qprint_var(uptime)
886 gp.qprint_timen("The uptime is less than the elapsed boot time," +
887 " as expected.")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600888 else:
889 error_message = "The uptime is greater than the elapsed boot time," +\
890 " which is unexpected:\n" +\
891 gp.sprint_var(start_boot_seconds) +\
892 gp.sprint_var(state)
893 BuiltIn().fail(gp.sprint_error(error_message))
894
Michael Walsh619aa332017-04-12 15:56:51 -0500895 gp.qprint_timen("Verifying that REST API interface is working.")
Michael Walshb95eb542017-03-31 09:39:20 -0500896 match_state = DotDict([('rest', '^1$')])
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600897 state = wait_state(match_state, wait_time="5 mins", interval="2 seconds")
898
899###############################################################################