blob: fcb7a7ad447e9a209ac50893799b32f007cf47be [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 Walsh341c21e2017-01-17 16:25:20 -060013 default_state[boot_progress]: FW Progress, Starting OS
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
42# We need utils.robot to get keywords like "Get 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 Walsh8fae6ea2017-02-20 16:14:44 -060054OBMC_STATES_VERSION = int(os.environ.get('OBMC_STATES_VERSION', 1))
Michael Walsh341c21e2017-01-17 16:25:20 -060055
56if OBMC_STATES_VERSION == 0:
Michael Walsh8fae6ea2017-02-20 16:14:44 -060057 # default_state is an initial value which may be of use to callers.
Michael Walsh341c21e2017-01-17 16:25:20 -060058 default_state = DotDict([('power', '1'),
Michael Walsh78bb9622017-03-10 14:13:58 -060059 ('bmc', 'HOST_BOOTED'),
Michael Walsh341c21e2017-01-17 16:25:20 -060060 ('boot_progress', 'FW Progress, Starting OS'),
61 ('os_ping', '1'),
62 ('os_login', '1'),
63 ('os_run_cmd', '1')])
Michael Walsh8fae6ea2017-02-20 16:14:44 -060064 # valid_req_states, default_req_states and master_os_up_match are used by
65 # the get_state function.
66 # valid_req_states is a list of state information supported by the
67 # get_state function.
68 valid_req_states = ['ping',
69 'packet_loss',
70 'uptime',
71 'epoch_seconds',
72 'power',
Michael Walsh78bb9622017-03-10 14:13:58 -060073 'bmc',
Michael Walsh8fae6ea2017-02-20 16:14:44 -060074 'boot_progress',
75 'os_ping',
76 'os_login',
77 'os_run_cmd']
78 # When a user calls get_state w/o specifying req_states, default_req_states
79 # is used as its value.
80 default_req_states = ['power',
Michael Walsh78bb9622017-03-10 14:13:58 -060081 'bmc',
Michael Walsh8fae6ea2017-02-20 16:14:44 -060082 'boot_progress',
83 'os_ping',
84 'os_login',
85 'os_run_cmd']
86 # A master dictionary to determine whether the os may be up.
87 master_os_up_match = DotDict([('power', 'On'),
Michael Walsh78bb9622017-03-10 14:13:58 -060088 ('bmc', '^HOST_BOOTED$'),
Michael Walsh8fae6ea2017-02-20 16:14:44 -060089 ('boot_progress',
90 'FW Progress, Starting OS')])
91
Michael Walsh341c21e2017-01-17 16:25:20 -060092else:
Michael Walsh8fae6ea2017-02-20 16:14:44 -060093 # default_state is an initial value which may be of use to callers.
Michael Walsh341c21e2017-01-17 16:25:20 -060094 default_state = DotDict([('chassis', 'On'),
Michael Walsh341c21e2017-01-17 16:25:20 -060095 ('boot_progress', 'FW Progress, Starting OS'),
Michael Walsh65b12542017-02-03 15:34:38 -060096 ('host', 'Running'),
Michael Walsh341c21e2017-01-17 16:25:20 -060097 ('os_ping', '1'),
98 ('os_login', '1'),
99 ('os_run_cmd', '1')])
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600100 # valid_req_states is a list of state information supported by the
101 # get_state function.
102 # valid_req_states, default_req_states and master_os_up_match are used by
103 # the get_state function.
104 valid_req_states = ['ping',
105 'packet_loss',
106 'uptime',
107 'epoch_seconds',
108 'chassis',
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600109 'boot_progress',
110 'host',
111 'os_ping',
112 'os_login',
113 'os_run_cmd']
114 # When a user calls get_state w/o specifying req_states, default_req_states
115 # is used as its value.
116 default_req_states = ['chassis',
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600117 'boot_progress',
118 'host',
119 'os_ping',
120 'os_login',
121 'os_run_cmd']
122
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600123 # A master dictionary to determine whether the os may be up.
Michael Walsh78bb9622017-03-10 14:13:58 -0600124 master_os_up_match = DotDict([('chassis', '^On$'),
125 ('boot_progress',
126 'FW Progress, Starting OS'),
127 ('host', '^Running$')])
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600128
129# valid_os_req_states and default_os_req_states are used by the os_get_state
130# function.
131# valid_os_req_states is a list of state information supported by the
132# get_os_state function.
133valid_os_req_states = ['os_ping',
134 'os_login',
135 'os_run_cmd']
136# When a user calls get_os_state w/o specifying req_states,
137# default_os_req_states is used as its value.
138default_os_req_states = ['os_ping',
139 'os_login',
140 'os_run_cmd']
141
142# Presently, some BMCs appear to not keep time very well. This environment
143# variable directs the get_state function to use either the BMC's epoch time
144# or the local epoch time.
145USE_BMC_EPOCH_TIME = int(os.environ.get('USE_BMC_EPOCH_TIME', 0))
Michael Walsh341c21e2017-01-17 16:25:20 -0600146
147
148###############################################################################
149def return_default_state():
150
151 r"""
152 Return default state dictionary.
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600153
154 default_state is an initial value which may be of use to callers.
Michael Walsh341c21e2017-01-17 16:25:20 -0600155 """
156
157 return default_state
158
159###############################################################################
160
Michael Walsh70369fd2016-11-22 11:25:57 -0600161
162###############################################################################
163def anchor_state(state):
164
165 r"""
166 Add regular expression anchors ("^" and "$") to the beginning and end of
167 each item in the state dictionary passed in. Return the resulting
168 dictionary.
169
170 Description of Arguments:
171 state A dictionary such as the one returned by the get_state()
172 function.
173 """
174
Michael Walsh2ce067a2017-02-27 14:24:07 -0600175 anchored_state = state.copy()
Michael Walsh70369fd2016-11-22 11:25:57 -0600176 for key, match_state_value in anchored_state.items():
177 anchored_state[key] = "^" + str(anchored_state[key]) + "$"
178
179 return anchored_state
180
181###############################################################################
182
183
184###############################################################################
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600185def strip_anchor_state(state):
186
187 r"""
188 Strip regular expression anchors ("^" and "$") from the beginning and end
189 of each item in the state dictionary passed in. Return the resulting
190 dictionary.
191
192 Description of Arguments:
193 state A dictionary such as the one returned by the get_state()
194 function.
195 """
196
Michael Walsh2ce067a2017-02-27 14:24:07 -0600197 stripped_state = state.copy()
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600198 for key, match_state_value in stripped_state.items():
199 stripped_state[key] = stripped_state[key].strip("^$")
200
201 return stripped_state
202
203###############################################################################
204
205
206###############################################################################
Michael Walsh70369fd2016-11-22 11:25:57 -0600207def compare_states(state,
208 match_state):
209
210 r"""
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600211 Compare 2 state dictionaries. Return True if they match and False if they
Michael Walsh70369fd2016-11-22 11:25:57 -0600212 don't. Note that the match_state dictionary does not need to have an entry
213 corresponding to each entry in the state dictionary. But for each entry
214 that it does have, the corresponding state entry will be checked for a
215 match.
216
217 Description of arguments:
218 state A state dictionary such as the one returned by the
219 get_state function.
220 match_state A dictionary whose key/value pairs are "state field"/
221 "state value". The state value is interpreted as a
222 regular expression. Every value in this dictionary is
223 considered. If each and every one matches, the 2
224 dictionaries are considered to be matching.
225 """
226
227 match = True
228 for key, match_state_value in match_state.items():
229 try:
230 if not re.match(match_state_value, str(state[key])):
231 match = False
232 break
233 except KeyError:
234 match = False
235 break
236
237 return match
238
239###############################################################################
240
241
242###############################################################################
243def get_os_state(os_host="",
244 os_username="",
245 os_password="",
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600246 req_states=default_os_req_states,
247 os_up=True,
Michael Walsh70369fd2016-11-22 11:25:57 -0600248 quiet=None):
249
250 r"""
251 Get component states for the operating system such as ping, login,
252 etc, put them into a dictionary and return them to the caller.
253
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600254 Note that all substate values are strings.
255
Michael Walsh70369fd2016-11-22 11:25:57 -0600256 Description of arguments:
257 os_host The DNS name or IP address of the operating system.
258 This defaults to global ${OS_HOST}.
259 os_username The username to be used to login to the OS.
260 This defaults to global ${OS_USERNAME}.
261 os_password The password to be used to login to the OS.
262 This defaults to global ${OS_PASSWORD}.
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600263 req_states This is a list of states whose values are being requested by
264 the caller.
265 os_up If the caller knows that the os can't possibly be up, it can
266 improve performance by passing os_up=False. This function
267 will then simply return default values for all requested os
268 sub states.
Michael Walsh70369fd2016-11-22 11:25:57 -0600269 quiet Indicates whether status details (e.g. curl commands) should
270 be written to the console.
271 Defaults to either global value of ${QUIET} or to 1.
272 """
273
274 quiet = grp.set_quiet_default(quiet, 1)
275
276 # Set parm defaults where necessary and validate all parms.
277 if os_host == "":
278 os_host = BuiltIn().get_variable_value("${OS_HOST}")
279 error_message = gv.svalid_value(os_host, var_name="os_host",
280 invalid_values=[None, ""])
281 if error_message != "":
282 BuiltIn().fail(gp.sprint_error(error_message))
283
284 if os_username == "":
285 os_username = BuiltIn().get_variable_value("${OS_USERNAME}")
286 error_message = gv.svalid_value(os_username, var_name="os_username",
287 invalid_values=[None, ""])
288 if error_message != "":
289 BuiltIn().fail(gp.sprint_error(error_message))
290
291 if os_password == "":
292 os_password = BuiltIn().get_variable_value("${OS_PASSWORD}")
293 error_message = gv.svalid_value(os_password, var_name="os_password",
294 invalid_values=[None, ""])
295 if error_message != "":
296 BuiltIn().fail(gp.sprint_error(error_message))
297
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600298 invalid_req_states = [sub_state for sub_state in req_states
299 if sub_state not in valid_os_req_states]
300 if len(invalid_req_states) > 0:
301 error_message = "The following req_states are not supported:\n" +\
302 gp.sprint_var(invalid_req_states)
303 BuiltIn().fail(gp.sprint_error(error_message))
Michael Walsh70369fd2016-11-22 11:25:57 -0600304
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600305 # Initialize all substate values supported by this function.
306 os_ping = 0
307 os_login = 0
308 os_run_cmd = 0
Michael Walsh70369fd2016-11-22 11:25:57 -0600309
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600310 if os_up:
311 if 'os_ping' in req_states:
312 # See if the OS pings.
313 cmd_buf = "ping -c 1 -w 2 " + os_host
314 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500315 gp.pissuing(cmd_buf)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600316 rc, out_buf = commands.getstatusoutput(cmd_buf)
317 if rc == 0:
318 os_ping = 1
Michael Walsh70369fd2016-11-22 11:25:57 -0600319
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600320 # Programming note: All attributes which do not require an ssh login
321 # should have been processed by this point.
322 master_req_login = ['os_login', 'os_run_cmd']
323 req_login = [sub_state for sub_state in req_states if sub_state in
324 master_req_login]
Michael Walsh70369fd2016-11-22 11:25:57 -0600325
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600326 must_login = (len([sub_state for sub_state in req_states
327 if sub_state in master_req_login]) > 0)
Michael Walsh70369fd2016-11-22 11:25:57 -0600328
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600329 if must_login:
Michael Walsh3eb50022017-03-21 11:27:30 -0500330 # Open SSH connection to OS. Note that this doesn't fail even when
331 # the OS is not up.
Michael Walshac275512017-03-07 11:39:28 -0600332 cmd_buf = ["SSHLibrary.Open Connection", os_host]
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600333 if not quiet:
334 grp.rpissuing_keyword(cmd_buf)
335 ix = BuiltIn().run_keyword(*cmd_buf)
336
337 # Login to OS.
338 cmd_buf = ["Login", os_username, os_password]
339 if not quiet:
340 grp.rpissuing_keyword(cmd_buf)
Michael Walsh3eb50022017-03-21 11:27:30 -0500341 status, ret_values = \
342 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600343 if status == "PASS":
344 os_login = 1
Michael Walsh3eb50022017-03-21 11:27:30 -0500345 else:
346 gp.dprint_var(status)
347 gp.dprint_var(ret_values)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600348
349 if os_login:
350 if 'os_run_cmd' in req_states:
Michael Walsh3eb50022017-03-21 11:27:30 -0500351 # Try running a simple command (uptime) on the OS.
352 cmd_buf = ["Execute Command", "uptime",
353 "return_stderr=True", "return_rc=True"]
354 if not quiet:
355 grp.rpissuing_keyword(cmd_buf)
356 # Note that in spite of its name, there are occasions
357 # where run_keyword_and_ignore_error can fail.
358 status, ret_values = \
359 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
360 if status == "PASS":
361 stdout, stderr, rc = ret_values
362 if rc == 0 and stderr == "":
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600363 os_run_cmd = 1
Michael Walsh3eb50022017-03-21 11:27:30 -0500364 else:
365 gp.dprint_var(status)
366 gp.dprint_var(stdout)
367 gp.dprint_var(stderr)
368 gp.dprint_var(rc)
369 else:
370 gp.dprint_var(status)
371 gp.dprint_var(ret_values)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600372
373 os_state = DotDict()
374 for sub_state in req_states:
375 cmd_buf = "os_state['" + sub_state + "'] = str(" + sub_state + ")"
376 exec(cmd_buf)
Michael Walsh70369fd2016-11-22 11:25:57 -0600377
378 return os_state
379
380###############################################################################
381
382
383###############################################################################
384def get_state(openbmc_host="",
385 openbmc_username="",
386 openbmc_password="",
387 os_host="",
388 os_username="",
389 os_password="",
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600390 req_states=default_req_states,
Michael Walsh70369fd2016-11-22 11:25:57 -0600391 quiet=None):
392
393 r"""
394 Get component states such as power state, bmc state, etc, put them into a
395 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
419 quiet = grp.set_quiet_default(quiet, 1)
420
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 = ''
474 power = '0'
475 chassis = ''
476 bmc = ''
477 boot_progress = ''
478 host = ''
479
Michael Walsh70369fd2016-11-22 11:25:57 -0600480 # Get the component states.
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600481 if 'ping' in req_states:
482 # See if the OS pings.
483 cmd_buf = "ping -c 1 -w 2 " + openbmc_host
484 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500485 gp.pissuing(cmd_buf)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600486 rc, out_buf = commands.getstatusoutput(cmd_buf)
487 if rc == 0:
488 ping = 1
489
490 if 'packet_loss' in req_states:
491 # See if the OS pings.
492 cmd_buf = "ping -c 5 -w 5 " + openbmc_host +\
493 " | egrep 'packet loss' | sed -re 's/.* ([0-9]+)%.*/\\1/g'"
494 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500495 gp.pissuing(cmd_buf)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600496 rc, out_buf = commands.getstatusoutput(cmd_buf)
497 if rc == 0:
498 packet_loss = out_buf.rstrip("\n")
499
500 master_req_login = ['uptime', 'epoch_seconds']
501 req_login = [sub_state for sub_state in req_states if sub_state in
502 master_req_login]
503
504 must_login = (len([sub_state for sub_state in req_states
505 if sub_state in master_req_login]) > 0)
506
507 if must_login:
508 cmd_buf = ["Open Connection And Log In"]
509 if not quiet:
510 grp.rpissuing_keyword(cmd_buf)
511 BuiltIn().run_keyword(*cmd_buf)
512
513 if 'uptime' in req_states:
514 cmd_buf = ["Execute Command", "cat /proc/uptime | cut -f 1 -d ' '",
515 "return_stderr=True", "return_rc=True"]
516 if not quiet:
517 grp.rpissuing_keyword(cmd_buf)
Michael Walsh3eb50022017-03-21 11:27:30 -0500518 stdout, stderr, rc = BuiltIn().run_keyword(*cmd_buf)
519 if rc == 0 and stderr == "":
520 uptime = stdout
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600521
522 if 'epoch_seconds' in req_states:
523 date_cmd_buf = "date -u +%s"
524 if USE_BMC_EPOCH_TIME:
525 cmd_buf = ["Execute Command", date_cmd_buf, "return_stderr=True",
526 "return_rc=True"]
527 if not quiet:
528 grp.rpissuing_keyword(cmd_buf)
Michael Walsh3eb50022017-03-21 11:27:30 -0500529 stdout, stderr, rc = BuiltIn().run_keyword(*cmd_buf)
530 if rc == 0 and stderr == "":
531 epoch_seconds = stdout.rstrip("\n")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600532 else:
533 shell_rc, out_buf = gc.cmd_fnc_u(date_cmd_buf,
534 quiet=1,
535 print_output=0)
536 if shell_rc == 0:
537 epoch_seconds = out_buf.rstrip("\n")
538
539 if 'power' in req_states:
Michael Walsh341c21e2017-01-17 16:25:20 -0600540 cmd_buf = ["Get Power State", "quiet=${" + str(quiet) + "}"]
541 grp.rdpissuing_keyword(cmd_buf)
542 power = BuiltIn().run_keyword(*cmd_buf)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600543 if 'chassis' in req_states:
Michael Walsh341c21e2017-01-17 16:25:20 -0600544 cmd_buf = ["Get Chassis Power State", "quiet=${" + str(quiet) + "}"]
545 grp.rdpissuing_keyword(cmd_buf)
546 chassis = BuiltIn().run_keyword(*cmd_buf)
547 # Strip everything up to the final period.
548 chassis = re.sub(r'.*\.', "", chassis)
Michael Walsh70369fd2016-11-22 11:25:57 -0600549
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600550 if 'bmc' in req_states:
Michael Walsh341c21e2017-01-17 16:25:20 -0600551 if OBMC_STATES_VERSION == 0:
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600552 qualifier = "utils"
Michael Walsh341c21e2017-01-17 16:25:20 -0600553 else:
Michael Walsh78bb9622017-03-10 14:13:58 -0600554 # This will not be supported much longer.
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600555 qualifier = "state_manager"
556
557 cmd_buf = [qualifier + ".Get BMC State", "quiet=${" + str(quiet) + "}"]
Michael Walsh78bb9622017-03-10 14:13:58 -0600558 grp.rdpissuing_keyword(cmd_buf)
559 bmc = BuiltIn().run_keyword(*cmd_buf)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600560
561 if 'boot_progress' in req_states:
562 cmd_buf = ["Get Boot Progress", "quiet=${" + str(quiet) + "}"]
Michael Walsh70369fd2016-11-22 11:25:57 -0600563 grp.rdpissuing_keyword(cmd_buf)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600564 boot_progress = BuiltIn().run_keyword(*cmd_buf)
565
566 if 'host' in req_states:
567 if OBMC_STATES_VERSION > 0:
568 cmd_buf = ["Get Host State", "quiet=${" + str(quiet) + "}"]
569 grp.rdpissuing_keyword(cmd_buf)
570 host = BuiltIn().run_keyword(*cmd_buf)
571 # Strip everything up to the final period.
572 host = re.sub(r'.*\.', "", host)
573
574 state = DotDict()
575 for sub_state in req_states:
576 if sub_state.startswith("os_"):
577 # We pass "os_" requests on to get_os_state.
578 continue
579 cmd_buf = "state['" + sub_state + "'] = str(" + sub_state + ")"
580 exec(cmd_buf)
581
582 if os_host == "":
583 # The caller has not specified an os_host so as far as we're concerned,
584 # it doesn't exist.
585 return state
586
587 os_req_states = [sub_state for sub_state in req_states
588 if sub_state.startswith('os_')]
589
590 if len(os_req_states) > 0:
591 # The caller has specified an os_host and they have requested
592 # information on os substates.
593
594 # Based on the information gathered on bmc, we'll try to make a
595 # determination of whether the os is even up. We'll pass the result
596 # of that assessment to get_os_state to enhance performance.
597 os_up_match = DotDict()
598 for sub_state in master_os_up_match:
599 if sub_state in req_states:
600 os_up_match[sub_state] = master_os_up_match[sub_state]
Michael Walsh70369fd2016-11-22 11:25:57 -0600601 os_up = compare_states(state, os_up_match)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600602 os_state = get_os_state(os_host=os_host,
603 os_username=os_username,
604 os_password=os_password,
605 req_states=os_req_states,
606 os_up=os_up,
607 quiet=quiet)
608 # Append os_state dictionary to ours.
609 state.update(os_state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600610
611 return state
612
613###############################################################################
614
615
616###############################################################################
617def check_state(match_state,
618 invert=0,
619 print_string="",
620 openbmc_host="",
621 openbmc_username="",
622 openbmc_password="",
623 os_host="",
624 os_username="",
625 os_password="",
626 quiet=None):
627
628 r"""
629 Check that the Open BMC machine's composite state matches the specified
630 state. On success, this keyword returns the machine's composite state as a
631 dictionary.
632
633 Description of arguments:
634 match_state A dictionary whose key/value pairs are "state field"/
635 "state value". The state value is interpreted as a
636 regular expression. Example call from robot:
Michael Walsh341c21e2017-01-17 16:25:20 -0600637 ${match_state}= Create Dictionary chassis=^On$
638 ... bmc=^Ready$
Michael Walsh70369fd2016-11-22 11:25:57 -0600639 ... boot_progress=^FW Progress, Starting OS$
640 ${state}= Check State &{match_state}
641 invert If this flag is set, this function will succeed if the
642 states do NOT match.
643 print_string This function will print this string to the console prior
644 to getting the state.
645 openbmc_host The DNS name or IP address of the BMC.
646 This defaults to global ${OPENBMC_HOST}.
647 openbmc_username The username to be used to login to the BMC.
648 This defaults to global ${OPENBMC_USERNAME}.
649 openbmc_password The password to be used to login to the BMC.
650 This defaults to global ${OPENBMC_PASSWORD}.
651 os_host The DNS name or IP address of the operating system.
652 This defaults to global ${OS_HOST}.
653 os_username The username to be used to login to the OS.
654 This defaults to global ${OS_USERNAME}.
655 os_password The password to be used to login to the OS.
656 This defaults to global ${OS_PASSWORD}.
657 quiet Indicates whether status details should be written to the
658 console. Defaults to either global value of ${QUIET} or
659 to 1.
660 """
661
662 quiet = grp.set_quiet_default(quiet, 1)
663
664 grp.rprint(print_string)
665
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600666 req_states = match_state.keys()
Michael Walsh70369fd2016-11-22 11:25:57 -0600667 # Initialize state.
668 state = get_state(openbmc_host=openbmc_host,
669 openbmc_username=openbmc_username,
670 openbmc_password=openbmc_password,
671 os_host=os_host,
672 os_username=os_username,
673 os_password=os_password,
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600674 req_states=req_states,
Michael Walsh70369fd2016-11-22 11:25:57 -0600675 quiet=quiet)
676 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500677 gp.print_var(state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600678
679 match = compare_states(state, match_state)
680
681 if invert and match:
682 fail_msg = "The current state of the machine matches the match" +\
683 " state:\n" + gp.sprint_varx("state", state)
684 BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
685 elif not invert and not match:
686 fail_msg = "The current state of the machine does NOT match the" +\
687 " match state:\n" +\
688 gp.sprint_varx("state", state)
689 BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
690
691 return state
692
693###############################################################################
694
695
696###############################################################################
Michael Walshf893ba02017-01-10 10:28:05 -0600697def wait_state(match_state=(),
Michael Walsh70369fd2016-11-22 11:25:57 -0600698 wait_time="1 min",
699 interval="1 second",
700 invert=0,
701 openbmc_host="",
702 openbmc_username="",
703 openbmc_password="",
704 os_host="",
705 os_username="",
706 os_password="",
707 quiet=None):
708
709 r"""
710 Wait for the Open BMC machine's composite state to match the specified
711 state. On success, this keyword returns the machine's composite state as
712 a dictionary.
713
714 Description of arguments:
715 match_state A dictionary whose key/value pairs are "state field"/
716 "state value". See check_state (above) for details.
717 wait_time The total amount of time to wait for the desired state.
718 This value may be expressed in Robot Framework's time
719 format (e.g. 1 minute, 2 min 3 s, 4.5).
720 interval The amount of time between state checks.
721 This value may be expressed in Robot Framework's time
722 format (e.g. 1 minute, 2 min 3 s, 4.5).
723 invert If this flag is set, this function will for the state of
724 the machine to cease to match the match state.
725 openbmc_host The DNS name or IP address of the BMC.
726 This defaults to global ${OPENBMC_HOST}.
727 openbmc_username The username to be used to login to the BMC.
728 This defaults to global ${OPENBMC_USERNAME}.
729 openbmc_password The password to be used to login to the BMC.
730 This defaults to global ${OPENBMC_PASSWORD}.
731 os_host The DNS name or IP address of the operating system.
732 This defaults to global ${OS_HOST}.
733 os_username The username to be used to login to the OS.
734 This defaults to global ${OS_USERNAME}.
735 os_password The password to be used to login to the OS.
736 This defaults to global ${OS_PASSWORD}.
737 quiet Indicates whether status details should be written to the
738 console. Defaults to either global value of ${QUIET} or
739 to 1.
740 """
741
742 quiet = grp.set_quiet_default(quiet, 1)
743
744 if not quiet:
745 if invert:
746 alt_text = "cease to "
747 else:
748 alt_text = ""
Michael Walsh3eb50022017-03-21 11:27:30 -0500749 gp.print_timen("Checking every " + str(interval) + " for up to " +
750 str(wait_time) + " for the state of the machine to " +
751 alt_text + "match the state shown below.")
752 gp.print_var(match_state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600753
Michael Walshf893ba02017-01-10 10:28:05 -0600754 if quiet:
Michael Walsh341c21e2017-01-17 16:25:20 -0600755 print_string = ""
Michael Walshf893ba02017-01-10 10:28:05 -0600756 else:
Michael Walsh341c21e2017-01-17 16:25:20 -0600757 print_string = "#"
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600758
759 debug = int(BuiltIn().get_variable_value("${debug}", "0"))
760 if debug:
761 # In debug we print state so no need to print the "#".
762 print_string = ""
763 check_state_quiet = 1 - debug
Michael Walsh70369fd2016-11-22 11:25:57 -0600764 cmd_buf = ["Check State", match_state, "invert=${" + str(invert) + "}",
Michael Walshf893ba02017-01-10 10:28:05 -0600765 "print_string=" + print_string, "openbmc_host=" + openbmc_host,
Michael Walsh70369fd2016-11-22 11:25:57 -0600766 "openbmc_username=" + openbmc_username,
767 "openbmc_password=" + openbmc_password, "os_host=" + os_host,
768 "os_username=" + os_username, "os_password=" + os_password,
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600769 "quiet=${" + str(check_state_quiet) + "}"]
Michael Walsh70369fd2016-11-22 11:25:57 -0600770 grp.rdpissuing_keyword(cmd_buf)
771 state = BuiltIn().wait_until_keyword_succeeds(wait_time, interval,
772 *cmd_buf)
Michael Walsh70369fd2016-11-22 11:25:57 -0600773 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500774 gp.printn()
Michael Walsh70369fd2016-11-22 11:25:57 -0600775 if invert:
Michael Walsh3eb50022017-03-21 11:27:30 -0500776 gp.print_timen("The states no longer match:")
Michael Walsh70369fd2016-11-22 11:25:57 -0600777 else:
Michael Walsh3eb50022017-03-21 11:27:30 -0500778 gp.print_timen("The states match:")
779 gp.print_var(state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600780
781 return state
782
783###############################################################################
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600784
785
786###############################################################################
787def wait_for_comm_cycle(start_boot_seconds):
788
789 r"""
790 Wait for communications to the BMC to stop working and then resume working.
791 This function is useful when you have initiated some kind of reboot.
792
793 Description of arguments:
794 start_boot_seconds The time that the boot test started. The format is the
795 epoch time in seconds, i.e. the number of seconds since
796 1970-01-01 00:00:00 UTC. This value should be obtained
797 from the BMC so that it is not dependent on any kind of
798 synchronization between this machine and the target BMC
799 This will allow this program to work correctly even in
800 a simulated environment. This value should be obtained
801 by the caller prior to initiating a reboot. It can be
802 obtained as follows:
803 state = st.get_state(req_states=['epoch_seconds'])
804 """
805
806 # Validate parms.
807 error_message = gv.svalid_integer(start_boot_seconds,
808 var_name="start_boot_seconds")
809 if error_message != "":
810 BuiltIn().fail(gp.sprint_error(error_message))
811
812 match_state = anchor_state(DotDict([('packet_loss', '100')]))
813 # Wait for 100% packet loss trying to ping machine.
814 wait_state(match_state, wait_time="3 mins", interval="0 seconds")
815
816 match_state['packet_loss'] = '^0$'
817 # Wait for 0% packet loss trying to ping machine.
818 wait_state(match_state, wait_time="4 mins", interval="0 seconds")
819
820 # Get the uptime and epoch seconds for comparisons. We want to be sure
821 # that the uptime is less than the elapsed boot time. Further proof that
822 # a reboot has indeed occurred (vs random network instability giving a
823 # false positive.
824 state = get_state(req_states=['uptime', 'epoch_seconds'])
825
826 elapsed_boot_time = int(state['epoch_seconds']) - start_boot_seconds
Michael Walsh3eb50022017-03-21 11:27:30 -0500827 gp.print_var(elapsed_boot_time)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600828 if int(float(state['uptime'])) < elapsed_boot_time:
829 uptime = state['uptime']
Michael Walsh3eb50022017-03-21 11:27:30 -0500830 gp.print_var(uptime)
831 gp.print_timen("The uptime is less than the elapsed boot time," +
832 " as expected.")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600833 else:
834 error_message = "The uptime is greater than the elapsed boot time," +\
835 " which is unexpected:\n" +\
836 gp.sprint_var(start_boot_seconds) +\
837 gp.sprint_var(state)
838 BuiltIn().fail(gp.sprint_error(error_message))
839
Michael Walsh3eb50022017-03-21 11:27:30 -0500840 gp.print_timen("Verifying that REST API interface is working.")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600841 match_state = DotDict([('chassis', '.*')])
842 state = wait_state(match_state, wait_time="5 mins", interval="2 seconds")
843
844###############################################################################