blob: dd151bfae34329513753a22797075168aee216a4 [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 Walshb95eb542017-03-31 09:39:20 -050094 default_state = DotDict([('rest', '1'),
95 ('chassis', 'On'),
Michael Walsh341c21e2017-01-17 16:25:20 -060096 ('boot_progress', 'FW Progress, Starting OS'),
Michael Walsh65b12542017-02-03 15:34:38 -060097 ('host', 'Running'),
Michael Walsh341c21e2017-01-17 16:25:20 -060098 ('os_ping', '1'),
99 ('os_login', '1'),
100 ('os_run_cmd', '1')])
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600101 # valid_req_states is a list of state information supported by the
102 # get_state function.
103 # valid_req_states, default_req_states and master_os_up_match are used by
104 # the get_state function.
105 valid_req_states = ['ping',
106 'packet_loss',
107 'uptime',
108 'epoch_seconds',
Michael Walshb95eb542017-03-31 09:39:20 -0500109 'rest',
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600110 'chassis',
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600111 'boot_progress',
112 'host',
113 'os_ping',
114 'os_login',
115 'os_run_cmd']
116 # When a user calls get_state w/o specifying req_states, default_req_states
117 # is used as its value.
Michael Walshb95eb542017-03-31 09:39:20 -0500118 default_req_states = ['rest',
119 'chassis',
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600120 'boot_progress',
121 'host',
122 'os_ping',
123 'os_login',
124 'os_run_cmd']
125
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600126 # A master dictionary to determine whether the os may be up.
Michael Walsh78bb9622017-03-10 14:13:58 -0600127 master_os_up_match = DotDict([('chassis', '^On$'),
128 ('boot_progress',
129 'FW Progress, Starting OS'),
130 ('host', '^Running$')])
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600131
132# valid_os_req_states and default_os_req_states are used by the os_get_state
133# function.
134# valid_os_req_states is a list of state information supported by the
135# get_os_state function.
136valid_os_req_states = ['os_ping',
137 'os_login',
138 'os_run_cmd']
139# When a user calls get_os_state w/o specifying req_states,
140# default_os_req_states is used as its value.
141default_os_req_states = ['os_ping',
142 'os_login',
143 'os_run_cmd']
144
145# Presently, some BMCs appear to not keep time very well. This environment
146# variable directs the get_state function to use either the BMC's epoch time
147# or the local epoch time.
148USE_BMC_EPOCH_TIME = int(os.environ.get('USE_BMC_EPOCH_TIME', 0))
Michael Walsh341c21e2017-01-17 16:25:20 -0600149
150
151###############################################################################
152def return_default_state():
153
154 r"""
155 Return default state dictionary.
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600156
157 default_state is an initial value which may be of use to callers.
Michael Walsh341c21e2017-01-17 16:25:20 -0600158 """
159
160 return default_state
161
162###############################################################################
163
Michael Walsh70369fd2016-11-22 11:25:57 -0600164
165###############################################################################
166def anchor_state(state):
167
168 r"""
169 Add regular expression anchors ("^" and "$") to the beginning and end of
170 each item in the state dictionary passed in. Return the resulting
171 dictionary.
172
173 Description of Arguments:
174 state A dictionary such as the one returned by the get_state()
175 function.
176 """
177
Michael Walsh2ce067a2017-02-27 14:24:07 -0600178 anchored_state = state.copy()
Michael Walsh70369fd2016-11-22 11:25:57 -0600179 for key, match_state_value in anchored_state.items():
180 anchored_state[key] = "^" + str(anchored_state[key]) + "$"
181
182 return anchored_state
183
184###############################################################################
185
186
187###############################################################################
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600188def strip_anchor_state(state):
189
190 r"""
191 Strip regular expression anchors ("^" and "$") from the beginning and end
192 of each item in the state dictionary passed in. Return the resulting
193 dictionary.
194
195 Description of Arguments:
196 state A dictionary such as the one returned by the get_state()
197 function.
198 """
199
Michael Walsh2ce067a2017-02-27 14:24:07 -0600200 stripped_state = state.copy()
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600201 for key, match_state_value in stripped_state.items():
202 stripped_state[key] = stripped_state[key].strip("^$")
203
204 return stripped_state
205
206###############################################################################
207
208
209###############################################################################
Michael Walsh70369fd2016-11-22 11:25:57 -0600210def compare_states(state,
211 match_state):
212
213 r"""
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600214 Compare 2 state dictionaries. Return True if they match and False if they
Michael Walsh70369fd2016-11-22 11:25:57 -0600215 don't. Note that the match_state dictionary does not need to have an entry
216 corresponding to each entry in the state dictionary. But for each entry
217 that it does have, the corresponding state entry will be checked for a
218 match.
219
220 Description of arguments:
221 state A state dictionary such as the one returned by the
222 get_state function.
223 match_state A dictionary whose key/value pairs are "state field"/
224 "state value". The state value is interpreted as a
225 regular expression. Every value in this dictionary is
226 considered. If each and every one matches, the 2
227 dictionaries are considered to be matching.
228 """
229
230 match = True
231 for key, match_state_value in match_state.items():
Michael Walsh97df71c2017-03-27 14:33:24 -0500232 # Blank match_state_value means "don't care".
233 if match_state_value == "":
234 continue
Michael Walsh70369fd2016-11-22 11:25:57 -0600235 try:
236 if not re.match(match_state_value, str(state[key])):
237 match = False
238 break
239 except KeyError:
240 match = False
241 break
242
243 return match
244
245###############################################################################
246
247
248###############################################################################
249def get_os_state(os_host="",
250 os_username="",
251 os_password="",
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600252 req_states=default_os_req_states,
253 os_up=True,
Michael Walsh70369fd2016-11-22 11:25:57 -0600254 quiet=None):
255
256 r"""
257 Get component states for the operating system such as ping, login,
258 etc, put them into a dictionary and return them to the caller.
259
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600260 Note that all substate values are strings.
261
Michael Walsh70369fd2016-11-22 11:25:57 -0600262 Description of arguments:
263 os_host The DNS name or IP address of the operating system.
264 This defaults to global ${OS_HOST}.
265 os_username The username to be used to login to the OS.
266 This defaults to global ${OS_USERNAME}.
267 os_password The password to be used to login to the OS.
268 This defaults to global ${OS_PASSWORD}.
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600269 req_states This is a list of states whose values are being requested by
270 the caller.
271 os_up If the caller knows that the os can't possibly be up, it can
272 improve performance by passing os_up=False. This function
273 will then simply return default values for all requested os
274 sub states.
Michael Walsh70369fd2016-11-22 11:25:57 -0600275 quiet Indicates whether status details (e.g. curl commands) should
276 be written to the console.
277 Defaults to either global value of ${QUIET} or to 1.
278 """
279
280 quiet = grp.set_quiet_default(quiet, 1)
281
282 # Set parm defaults where necessary and validate all parms.
283 if os_host == "":
284 os_host = BuiltIn().get_variable_value("${OS_HOST}")
285 error_message = gv.svalid_value(os_host, var_name="os_host",
286 invalid_values=[None, ""])
287 if error_message != "":
288 BuiltIn().fail(gp.sprint_error(error_message))
289
290 if os_username == "":
291 os_username = BuiltIn().get_variable_value("${OS_USERNAME}")
292 error_message = gv.svalid_value(os_username, var_name="os_username",
293 invalid_values=[None, ""])
294 if error_message != "":
295 BuiltIn().fail(gp.sprint_error(error_message))
296
297 if os_password == "":
298 os_password = BuiltIn().get_variable_value("${OS_PASSWORD}")
299 error_message = gv.svalid_value(os_password, var_name="os_password",
300 invalid_values=[None, ""])
301 if error_message != "":
302 BuiltIn().fail(gp.sprint_error(error_message))
303
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600304 invalid_req_states = [sub_state for sub_state in req_states
305 if sub_state not in valid_os_req_states]
306 if len(invalid_req_states) > 0:
307 error_message = "The following req_states are not supported:\n" +\
308 gp.sprint_var(invalid_req_states)
309 BuiltIn().fail(gp.sprint_error(error_message))
Michael Walsh70369fd2016-11-22 11:25:57 -0600310
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600311 # Initialize all substate values supported by this function.
312 os_ping = 0
313 os_login = 0
314 os_run_cmd = 0
Michael Walsh70369fd2016-11-22 11:25:57 -0600315
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600316 if os_up:
317 if 'os_ping' in req_states:
318 # See if the OS pings.
319 cmd_buf = "ping -c 1 -w 2 " + os_host
320 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500321 gp.pissuing(cmd_buf)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600322 rc, out_buf = commands.getstatusoutput(cmd_buf)
323 if rc == 0:
324 os_ping = 1
Michael Walsh70369fd2016-11-22 11:25:57 -0600325
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600326 # Programming note: All attributes which do not require an ssh login
327 # should have been processed by this point.
328 master_req_login = ['os_login', 'os_run_cmd']
329 req_login = [sub_state for sub_state in req_states if sub_state in
330 master_req_login]
Michael Walsh97df71c2017-03-27 14:33:24 -0500331 must_login = (len(req_login) > 0)
Michael Walsh70369fd2016-11-22 11:25:57 -0600332
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600333 if must_login:
Michael Walsh3eb50022017-03-21 11:27:30 -0500334 # Open SSH connection to OS. Note that this doesn't fail even when
335 # the OS is not up.
Michael Walshac275512017-03-07 11:39:28 -0600336 cmd_buf = ["SSHLibrary.Open Connection", os_host]
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600337 if not quiet:
338 grp.rpissuing_keyword(cmd_buf)
339 ix = BuiltIn().run_keyword(*cmd_buf)
340
341 # Login to OS.
342 cmd_buf = ["Login", os_username, os_password]
343 if not quiet:
344 grp.rpissuing_keyword(cmd_buf)
Michael Walsh3eb50022017-03-21 11:27:30 -0500345 status, ret_values = \
346 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600347 if status == "PASS":
348 os_login = 1
Michael Walsh3eb50022017-03-21 11:27:30 -0500349 else:
350 gp.dprint_var(status)
351 gp.dprint_var(ret_values)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600352
353 if os_login:
354 if 'os_run_cmd' in req_states:
Michael Walsh3eb50022017-03-21 11:27:30 -0500355 # Try running a simple command (uptime) on the OS.
356 cmd_buf = ["Execute Command", "uptime",
357 "return_stderr=True", "return_rc=True"]
358 if not quiet:
359 grp.rpissuing_keyword(cmd_buf)
360 # Note that in spite of its name, there are occasions
361 # where run_keyword_and_ignore_error can fail.
362 status, ret_values = \
363 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
364 if status == "PASS":
365 stdout, stderr, rc = ret_values
366 if rc == 0 and stderr == "":
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600367 os_run_cmd = 1
Michael Walsh3eb50022017-03-21 11:27:30 -0500368 else:
369 gp.dprint_var(status)
370 gp.dprint_var(stdout)
371 gp.dprint_var(stderr)
372 gp.dprint_var(rc)
373 else:
374 gp.dprint_var(status)
375 gp.dprint_var(ret_values)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600376
377 os_state = DotDict()
378 for sub_state in req_states:
379 cmd_buf = "os_state['" + sub_state + "'] = str(" + sub_state + ")"
380 exec(cmd_buf)
Michael Walsh70369fd2016-11-22 11:25:57 -0600381
382 return os_state
383
384###############################################################################
385
386
387###############################################################################
388def get_state(openbmc_host="",
389 openbmc_username="",
390 openbmc_password="",
391 os_host="",
392 os_username="",
393 os_password="",
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600394 req_states=default_req_states,
Michael Walsh70369fd2016-11-22 11:25:57 -0600395 quiet=None):
396
397 r"""
398 Get component states such as power state, bmc state, etc, put them into a
399 dictionary and return them to the caller.
400
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600401 Note that all substate values are strings.
402
Michael Walsh70369fd2016-11-22 11:25:57 -0600403 Description of arguments:
404 openbmc_host The DNS name or IP address of the BMC.
405 This defaults to global ${OPENBMC_HOST}.
406 openbmc_username The username to be used to login to the BMC.
407 This defaults to global ${OPENBMC_USERNAME}.
408 openbmc_password The password to be used to login to the BMC.
409 This defaults to global ${OPENBMC_PASSWORD}.
410 os_host The DNS name or IP address of the operating system.
411 This defaults to global ${OS_HOST}.
412 os_username The username to be used to login to the OS.
413 This defaults to global ${OS_USERNAME}.
414 os_password The password to be used to login to the OS.
415 This defaults to global ${OS_PASSWORD}.
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600416 req_states This is a list of states whose values are being requested
417 by the caller.
Michael Walsh70369fd2016-11-22 11:25:57 -0600418 quiet Indicates whether status details (e.g. curl commands)
419 should be written to the console.
420 Defaults to either global value of ${QUIET} or to 1.
421 """
422
423 quiet = grp.set_quiet_default(quiet, 1)
424
425 # Set parm defaults where necessary and validate all parms.
426 if openbmc_host == "":
427 openbmc_host = BuiltIn().get_variable_value("${OPENBMC_HOST}")
428 error_message = gv.svalid_value(openbmc_host,
429 var_name="openbmc_host",
430 invalid_values=[None, ""])
431 if error_message != "":
432 BuiltIn().fail(gp.sprint_error(error_message))
433
434 if openbmc_username == "":
435 openbmc_username = BuiltIn().get_variable_value("${OPENBMC_USERNAME}")
436 error_message = gv.svalid_value(openbmc_username,
437 var_name="openbmc_username",
438 invalid_values=[None, ""])
439 if error_message != "":
440 BuiltIn().fail(gp.sprint_error(error_message))
441
442 if openbmc_password == "":
443 openbmc_password = BuiltIn().get_variable_value("${OPENBMC_PASSWORD}")
444 error_message = gv.svalid_value(openbmc_password,
445 var_name="openbmc_password",
446 invalid_values=[None, ""])
447 if error_message != "":
448 BuiltIn().fail(gp.sprint_error(error_message))
449
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600450 # NOTE: OS parms are optional.
Michael Walsh70369fd2016-11-22 11:25:57 -0600451 if os_host == "":
452 os_host = BuiltIn().get_variable_value("${OS_HOST}")
453 if os_host is None:
454 os_host = ""
455
456 if os_username is "":
457 os_username = BuiltIn().get_variable_value("${OS_USERNAME}")
458 if os_username is None:
459 os_username = ""
460
461 if os_password is "":
462 os_password = BuiltIn().get_variable_value("${OS_PASSWORD}")
463 if os_password is None:
464 os_password = ""
465
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600466 invalid_req_states = [sub_state for sub_state in req_states
467 if sub_state not in valid_req_states]
468 if len(invalid_req_states) > 0:
469 error_message = "The following req_states are not supported:\n" +\
470 gp.sprint_var(invalid_req_states)
471 BuiltIn().fail(gp.sprint_error(error_message))
472
473 # Initialize all substate values supported by this function.
474 ping = 0
475 packet_loss = ''
476 uptime = ''
477 epoch_seconds = ''
Michael Walsh97df71c2017-03-27 14:33:24 -0500478 power = ''
Michael Walshb95eb542017-03-31 09:39:20 -0500479 rest = '1'
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600480 chassis = ''
481 bmc = ''
482 boot_progress = ''
483 host = ''
484
Michael Walsh70369fd2016-11-22 11:25:57 -0600485 # Get the component states.
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600486 if 'ping' in req_states:
487 # See if the OS pings.
488 cmd_buf = "ping -c 1 -w 2 " + openbmc_host
489 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500490 gp.pissuing(cmd_buf)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600491 rc, out_buf = commands.getstatusoutput(cmd_buf)
492 if rc == 0:
493 ping = 1
494
495 if 'packet_loss' in req_states:
496 # See if the OS pings.
497 cmd_buf = "ping -c 5 -w 5 " + openbmc_host +\
498 " | egrep 'packet loss' | sed -re 's/.* ([0-9]+)%.*/\\1/g'"
499 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500500 gp.pissuing(cmd_buf)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600501 rc, out_buf = commands.getstatusoutput(cmd_buf)
502 if rc == 0:
503 packet_loss = out_buf.rstrip("\n")
504
505 master_req_login = ['uptime', 'epoch_seconds']
506 req_login = [sub_state for sub_state in req_states if sub_state in
507 master_req_login]
Michael Walsh97df71c2017-03-27 14:33:24 -0500508 must_login = (len(req_login) > 0)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600509
Michael Walsh97df71c2017-03-27 14:33:24 -0500510 bmc_login = 0
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600511 if must_login:
512 cmd_buf = ["Open Connection And Log In"]
513 if not quiet:
514 grp.rpissuing_keyword(cmd_buf)
Michael Walsh97df71c2017-03-27 14:33:24 -0500515 status, ret_values = \
516 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
517 if status == "PASS":
518 bmc_login = 1
519 else:
520 if re.match('^Authentication failed for user', ret_values):
521 # An authentication failure is worth failing on.
522 BuiltIn().fail(gp.sprint_error(ret_values))
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600523
Michael Walsh97df71c2017-03-27 14:33:24 -0500524 if 'uptime' in req_states and bmc_login:
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600525 cmd_buf = ["Execute Command", "cat /proc/uptime | cut -f 1 -d ' '",
526 "return_stderr=True", "return_rc=True"]
527 if not quiet:
528 grp.rpissuing_keyword(cmd_buf)
Michael Walsh97df71c2017-03-27 14:33:24 -0500529 status, ret_values = \
530 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
531 if status == "PASS":
532 stdout, stderr, rc = ret_values
533 if rc == 0 and stderr == "":
534 uptime = stdout
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600535
Michael Walsh97df71c2017-03-27 14:33:24 -0500536 if 'epoch_seconds' in req_states and bmc_login:
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600537 date_cmd_buf = "date -u +%s"
538 if USE_BMC_EPOCH_TIME:
539 cmd_buf = ["Execute Command", date_cmd_buf, "return_stderr=True",
540 "return_rc=True"]
541 if not quiet:
542 grp.rpissuing_keyword(cmd_buf)
Michael Walsh97df71c2017-03-27 14:33:24 -0500543 status, ret_values = \
544 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
545 if status == "PASS":
546 stdout, stderr, rc = ret_values
547 if rc == 0 and stderr == "":
548 epoch_seconds = stdout.rstrip("\n")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600549 else:
550 shell_rc, out_buf = gc.cmd_fnc_u(date_cmd_buf,
551 quiet=1,
552 print_output=0)
553 if shell_rc == 0:
554 epoch_seconds = out_buf.rstrip("\n")
555
Michael Walshb95eb542017-03-31 09:39:20 -0500556 master_req_rest = ['rest', 'power', 'chassis', 'bmc', 'boot_progress',
557 'host']
558 req_rest = [sub_state for sub_state in req_states if sub_state in
559 master_req_rest]
560 need_rest = (len(req_rest) > 0)
561
562 # Though we could try to determine 'rest' state on any of several calls,
563 # for simplicity, we'll use 'chassis' to figure it out (even if the caller
564 # hasn't explicitly asked for 'chassis').
565 if 'chassis' in req_states or need_rest:
Michael Walsh341c21e2017-01-17 16:25:20 -0600566 cmd_buf = ["Get Chassis Power State", "quiet=${" + str(quiet) + "}"]
567 grp.rdpissuing_keyword(cmd_buf)
Michael Walsh97df71c2017-03-27 14:33:24 -0500568 status, ret_values = \
569 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
570 if status == "PASS":
571 chassis = ret_values
572 chassis = re.sub(r'.*\.', "", chassis)
Michael Walshb95eb542017-03-31 09:39:20 -0500573 rest = '1'
Michael Walsh341c21e2017-01-17 16:25:20 -0600574 else:
Michael Walshb95eb542017-03-31 09:39:20 -0500575 rest = ret_values
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600576
Michael Walshb95eb542017-03-31 09:39:20 -0500577 if rest == '1':
578 if 'power' in req_states:
579 cmd_buf = ["Get Power State", "quiet=${" + str(quiet) + "}"]
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600580 grp.rdpissuing_keyword(cmd_buf)
Michael Walsh97df71c2017-03-27 14:33:24 -0500581 status, ret_values = \
582 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
583 if status == "PASS":
Michael Walshb95eb542017-03-31 09:39:20 -0500584 power = ret_values
585
586 if 'bmc' in req_states:
587 if OBMC_STATES_VERSION == 0:
588 qualifier = "utils"
589 else:
590 # This will not be supported much longer.
591 qualifier = "state_manager"
592 cmd_buf = [qualifier + ".Get BMC State",
593 "quiet=${" + str(quiet) + "}"]
594 grp.rdpissuing_keyword(cmd_buf)
595 status, ret_values = \
596 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
597 if status == "PASS":
598 bmc = ret_values
599
600 if 'boot_progress' in req_states:
601 cmd_buf = ["Get Boot Progress", "quiet=${" + str(quiet) + "}"]
602 grp.rdpissuing_keyword(cmd_buf)
603 status, ret_values = \
604 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
605 if status == "PASS":
606 boot_progress = ret_values
607
608 if 'host' in req_states:
609 if OBMC_STATES_VERSION > 0:
610 cmd_buf = ["Get Host State", "quiet=${" + str(quiet) + "}"]
611 grp.rdpissuing_keyword(cmd_buf)
612 status, ret_values = \
613 BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
614 if status == "PASS":
615 host = ret_values
616 # Strip everything up to the final period.
617 host = re.sub(r'.*\.', "", host)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600618
619 state = DotDict()
620 for sub_state in req_states:
621 if sub_state.startswith("os_"):
622 # We pass "os_" requests on to get_os_state.
623 continue
624 cmd_buf = "state['" + sub_state + "'] = str(" + sub_state + ")"
625 exec(cmd_buf)
626
627 if os_host == "":
628 # The caller has not specified an os_host so as far as we're concerned,
629 # it doesn't exist.
630 return state
631
632 os_req_states = [sub_state for sub_state in req_states
633 if sub_state.startswith('os_')]
634
635 if len(os_req_states) > 0:
636 # The caller has specified an os_host and they have requested
637 # information on os substates.
638
639 # Based on the information gathered on bmc, we'll try to make a
640 # determination of whether the os is even up. We'll pass the result
641 # of that assessment to get_os_state to enhance performance.
642 os_up_match = DotDict()
643 for sub_state in master_os_up_match:
644 if sub_state in req_states:
645 os_up_match[sub_state] = master_os_up_match[sub_state]
Michael Walsh70369fd2016-11-22 11:25:57 -0600646 os_up = compare_states(state, os_up_match)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600647 os_state = get_os_state(os_host=os_host,
648 os_username=os_username,
649 os_password=os_password,
650 req_states=os_req_states,
651 os_up=os_up,
652 quiet=quiet)
653 # Append os_state dictionary to ours.
654 state.update(os_state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600655
656 return state
657
658###############################################################################
659
660
661###############################################################################
662def check_state(match_state,
663 invert=0,
664 print_string="",
665 openbmc_host="",
666 openbmc_username="",
667 openbmc_password="",
668 os_host="",
669 os_username="",
670 os_password="",
671 quiet=None):
672
673 r"""
674 Check that the Open BMC machine's composite state matches the specified
675 state. On success, this keyword returns the machine's composite state as a
676 dictionary.
677
678 Description of arguments:
679 match_state A dictionary whose key/value pairs are "state field"/
680 "state value". The state value is interpreted as a
681 regular expression. Example call from robot:
Michael Walsh341c21e2017-01-17 16:25:20 -0600682 ${match_state}= Create Dictionary chassis=^On$
683 ... bmc=^Ready$
Michael Walsh70369fd2016-11-22 11:25:57 -0600684 ... boot_progress=^FW Progress, Starting OS$
685 ${state}= Check State &{match_state}
686 invert If this flag is set, this function will succeed if the
687 states do NOT match.
688 print_string This function will print this string to the console prior
689 to getting the state.
690 openbmc_host The DNS name or IP address of the BMC.
691 This defaults to global ${OPENBMC_HOST}.
692 openbmc_username The username to be used to login to the BMC.
693 This defaults to global ${OPENBMC_USERNAME}.
694 openbmc_password The password to be used to login to the BMC.
695 This defaults to global ${OPENBMC_PASSWORD}.
696 os_host The DNS name or IP address of the operating system.
697 This defaults to global ${OS_HOST}.
698 os_username The username to be used to login to the OS.
699 This defaults to global ${OS_USERNAME}.
700 os_password The password to be used to login to the OS.
701 This defaults to global ${OS_PASSWORD}.
702 quiet Indicates whether status details should be written to the
703 console. Defaults to either global value of ${QUIET} or
704 to 1.
705 """
706
707 quiet = grp.set_quiet_default(quiet, 1)
708
709 grp.rprint(print_string)
710
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600711 req_states = match_state.keys()
Michael Walsh70369fd2016-11-22 11:25:57 -0600712 # Initialize state.
713 state = get_state(openbmc_host=openbmc_host,
714 openbmc_username=openbmc_username,
715 openbmc_password=openbmc_password,
716 os_host=os_host,
717 os_username=os_username,
718 os_password=os_password,
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600719 req_states=req_states,
Michael Walsh70369fd2016-11-22 11:25:57 -0600720 quiet=quiet)
721 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500722 gp.print_var(state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600723
724 match = compare_states(state, match_state)
725
726 if invert and match:
727 fail_msg = "The current state of the machine matches the match" +\
728 " state:\n" + gp.sprint_varx("state", state)
729 BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
730 elif not invert and not match:
731 fail_msg = "The current state of the machine does NOT match the" +\
732 " match state:\n" +\
733 gp.sprint_varx("state", state)
734 BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
735
736 return state
737
738###############################################################################
739
740
741###############################################################################
Michael Walshf893ba02017-01-10 10:28:05 -0600742def wait_state(match_state=(),
Michael Walsh70369fd2016-11-22 11:25:57 -0600743 wait_time="1 min",
744 interval="1 second",
745 invert=0,
746 openbmc_host="",
747 openbmc_username="",
748 openbmc_password="",
749 os_host="",
750 os_username="",
751 os_password="",
752 quiet=None):
753
754 r"""
755 Wait for the Open BMC machine's composite state to match the specified
756 state. On success, this keyword returns the machine's composite state as
757 a dictionary.
758
759 Description of arguments:
760 match_state A dictionary whose key/value pairs are "state field"/
761 "state value". See check_state (above) for details.
762 wait_time The total amount of time to wait for the desired state.
763 This value may be expressed in Robot Framework's time
764 format (e.g. 1 minute, 2 min 3 s, 4.5).
765 interval The amount of time between state checks.
766 This value may be expressed in Robot Framework's time
767 format (e.g. 1 minute, 2 min 3 s, 4.5).
768 invert If this flag is set, this function will for the state of
769 the machine to cease to match the match state.
770 openbmc_host The DNS name or IP address of the BMC.
771 This defaults to global ${OPENBMC_HOST}.
772 openbmc_username The username to be used to login to the BMC.
773 This defaults to global ${OPENBMC_USERNAME}.
774 openbmc_password The password to be used to login to the BMC.
775 This defaults to global ${OPENBMC_PASSWORD}.
776 os_host The DNS name or IP address of the operating system.
777 This defaults to global ${OS_HOST}.
778 os_username The username to be used to login to the OS.
779 This defaults to global ${OS_USERNAME}.
780 os_password The password to be used to login to the OS.
781 This defaults to global ${OS_PASSWORD}.
782 quiet Indicates whether status details should be written to the
783 console. Defaults to either global value of ${QUIET} or
784 to 1.
785 """
786
787 quiet = grp.set_quiet_default(quiet, 1)
788
789 if not quiet:
790 if invert:
791 alt_text = "cease to "
792 else:
793 alt_text = ""
Michael Walsh3eb50022017-03-21 11:27:30 -0500794 gp.print_timen("Checking every " + str(interval) + " for up to " +
795 str(wait_time) + " for the state of the machine to " +
796 alt_text + "match the state shown below.")
797 gp.print_var(match_state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600798
Michael Walshf893ba02017-01-10 10:28:05 -0600799 if quiet:
Michael Walsh341c21e2017-01-17 16:25:20 -0600800 print_string = ""
Michael Walshf893ba02017-01-10 10:28:05 -0600801 else:
Michael Walsh341c21e2017-01-17 16:25:20 -0600802 print_string = "#"
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600803
804 debug = int(BuiltIn().get_variable_value("${debug}", "0"))
805 if debug:
806 # In debug we print state so no need to print the "#".
807 print_string = ""
808 check_state_quiet = 1 - debug
Michael Walsh70369fd2016-11-22 11:25:57 -0600809 cmd_buf = ["Check State", match_state, "invert=${" + str(invert) + "}",
Michael Walshf893ba02017-01-10 10:28:05 -0600810 "print_string=" + print_string, "openbmc_host=" + openbmc_host,
Michael Walsh70369fd2016-11-22 11:25:57 -0600811 "openbmc_username=" + openbmc_username,
812 "openbmc_password=" + openbmc_password, "os_host=" + os_host,
813 "os_username=" + os_username, "os_password=" + os_password,
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600814 "quiet=${" + str(check_state_quiet) + "}"]
Michael Walsh70369fd2016-11-22 11:25:57 -0600815 grp.rdpissuing_keyword(cmd_buf)
816 state = BuiltIn().wait_until_keyword_succeeds(wait_time, interval,
817 *cmd_buf)
Michael Walsh70369fd2016-11-22 11:25:57 -0600818 if not quiet:
Michael Walsh3eb50022017-03-21 11:27:30 -0500819 gp.printn()
Michael Walsh70369fd2016-11-22 11:25:57 -0600820 if invert:
Michael Walsh3eb50022017-03-21 11:27:30 -0500821 gp.print_timen("The states no longer match:")
Michael Walsh70369fd2016-11-22 11:25:57 -0600822 else:
Michael Walsh3eb50022017-03-21 11:27:30 -0500823 gp.print_timen("The states match:")
824 gp.print_var(state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600825
826 return state
827
828###############################################################################
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600829
830
831###############################################################################
832def wait_for_comm_cycle(start_boot_seconds):
833
834 r"""
835 Wait for communications to the BMC to stop working and then resume working.
836 This function is useful when you have initiated some kind of reboot.
837
838 Description of arguments:
839 start_boot_seconds The time that the boot test started. The format is the
840 epoch time in seconds, i.e. the number of seconds since
841 1970-01-01 00:00:00 UTC. This value should be obtained
842 from the BMC so that it is not dependent on any kind of
843 synchronization between this machine and the target BMC
844 This will allow this program to work correctly even in
845 a simulated environment. This value should be obtained
846 by the caller prior to initiating a reboot. It can be
847 obtained as follows:
848 state = st.get_state(req_states=['epoch_seconds'])
849 """
850
851 # Validate parms.
852 error_message = gv.svalid_integer(start_boot_seconds,
853 var_name="start_boot_seconds")
854 if error_message != "":
855 BuiltIn().fail(gp.sprint_error(error_message))
856
857 match_state = anchor_state(DotDict([('packet_loss', '100')]))
858 # Wait for 100% packet loss trying to ping machine.
859 wait_state(match_state, wait_time="3 mins", interval="0 seconds")
860
861 match_state['packet_loss'] = '^0$'
862 # Wait for 0% packet loss trying to ping machine.
863 wait_state(match_state, wait_time="4 mins", interval="0 seconds")
864
865 # Get the uptime and epoch seconds for comparisons. We want to be sure
866 # that the uptime is less than the elapsed boot time. Further proof that
867 # a reboot has indeed occurred (vs random network instability giving a
868 # false positive.
869 state = get_state(req_states=['uptime', 'epoch_seconds'])
870
871 elapsed_boot_time = int(state['epoch_seconds']) - start_boot_seconds
Michael Walsh3eb50022017-03-21 11:27:30 -0500872 gp.print_var(elapsed_boot_time)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600873 if int(float(state['uptime'])) < elapsed_boot_time:
874 uptime = state['uptime']
Michael Walsh3eb50022017-03-21 11:27:30 -0500875 gp.print_var(uptime)
876 gp.print_timen("The uptime is less than the elapsed boot time," +
877 " as expected.")
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600878 else:
879 error_message = "The uptime is greater than the elapsed boot time," +\
880 " which is unexpected:\n" +\
881 gp.sprint_var(start_boot_seconds) +\
882 gp.sprint_var(state)
883 BuiltIn().fail(gp.sprint_error(error_message))
884
Michael Walsh3eb50022017-03-21 11:27:30 -0500885 gp.print_timen("Verifying that REST API interface is working.")
Michael Walshb95eb542017-03-31 09:39:20 -0500886 match_state = DotDict([('rest', '^1$')])
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600887 state = wait_state(match_state, wait_time="5 mins", interval="2 seconds")
888
889###############################################################################