blob: 919223cd12c9745d40ddb3a487a2aa5dd27c22be [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:
315 grp.rpissuing(cmd_buf)
316 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:
330 # Open SSH connection to OS.
Michael Walshac275512017-03-07 11:39:28 -0600331 cmd_buf = ["SSHLibrary.Open Connection", os_host]
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600332 if not quiet:
333 grp.rpissuing_keyword(cmd_buf)
334 ix = BuiltIn().run_keyword(*cmd_buf)
335
336 # Login to OS.
337 cmd_buf = ["Login", os_username, os_password]
338 if not quiet:
339 grp.rpissuing_keyword(cmd_buf)
340 status, msg = BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
341 if status == "PASS":
342 os_login = 1
343
344 if os_login:
345 if 'os_run_cmd' in req_states:
346 if os_login:
347 # Try running a simple command (uptime) on the OS.
348 cmd_buf = ["Execute Command", "uptime",
349 "return_stderr=True", "return_rc=True"]
350 if not quiet:
351 grp.rpissuing_keyword(cmd_buf)
352 output, stderr_buf, rc = \
353 BuiltIn().run_keyword(*cmd_buf)
354 if rc == 0 and stderr_buf == "":
355 os_run_cmd = 1
356
357 os_state = DotDict()
358 for sub_state in req_states:
359 cmd_buf = "os_state['" + sub_state + "'] = str(" + sub_state + ")"
360 exec(cmd_buf)
Michael Walsh70369fd2016-11-22 11:25:57 -0600361
362 return os_state
363
364###############################################################################
365
366
367###############################################################################
368def get_state(openbmc_host="",
369 openbmc_username="",
370 openbmc_password="",
371 os_host="",
372 os_username="",
373 os_password="",
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600374 req_states=default_req_states,
Michael Walsh70369fd2016-11-22 11:25:57 -0600375 quiet=None):
376
377 r"""
378 Get component states such as power state, bmc state, etc, put them into a
379 dictionary and return them to the caller.
380
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600381 Note that all substate values are strings.
382
Michael Walsh70369fd2016-11-22 11:25:57 -0600383 Description of arguments:
384 openbmc_host The DNS name or IP address of the BMC.
385 This defaults to global ${OPENBMC_HOST}.
386 openbmc_username The username to be used to login to the BMC.
387 This defaults to global ${OPENBMC_USERNAME}.
388 openbmc_password The password to be used to login to the BMC.
389 This defaults to global ${OPENBMC_PASSWORD}.
390 os_host The DNS name or IP address of the operating system.
391 This defaults to global ${OS_HOST}.
392 os_username The username to be used to login to the OS.
393 This defaults to global ${OS_USERNAME}.
394 os_password The password to be used to login to the OS.
395 This defaults to global ${OS_PASSWORD}.
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600396 req_states This is a list of states whose values are being requested
397 by the caller.
Michael Walsh70369fd2016-11-22 11:25:57 -0600398 quiet Indicates whether status details (e.g. curl commands)
399 should be written to the console.
400 Defaults to either global value of ${QUIET} or to 1.
401 """
402
403 quiet = grp.set_quiet_default(quiet, 1)
404
405 # Set parm defaults where necessary and validate all parms.
406 if openbmc_host == "":
407 openbmc_host = BuiltIn().get_variable_value("${OPENBMC_HOST}")
408 error_message = gv.svalid_value(openbmc_host,
409 var_name="openbmc_host",
410 invalid_values=[None, ""])
411 if error_message != "":
412 BuiltIn().fail(gp.sprint_error(error_message))
413
414 if openbmc_username == "":
415 openbmc_username = BuiltIn().get_variable_value("${OPENBMC_USERNAME}")
416 error_message = gv.svalid_value(openbmc_username,
417 var_name="openbmc_username",
418 invalid_values=[None, ""])
419 if error_message != "":
420 BuiltIn().fail(gp.sprint_error(error_message))
421
422 if openbmc_password == "":
423 openbmc_password = BuiltIn().get_variable_value("${OPENBMC_PASSWORD}")
424 error_message = gv.svalid_value(openbmc_password,
425 var_name="openbmc_password",
426 invalid_values=[None, ""])
427 if error_message != "":
428 BuiltIn().fail(gp.sprint_error(error_message))
429
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600430 # NOTE: OS parms are optional.
Michael Walsh70369fd2016-11-22 11:25:57 -0600431 if os_host == "":
432 os_host = BuiltIn().get_variable_value("${OS_HOST}")
433 if os_host is None:
434 os_host = ""
435
436 if os_username is "":
437 os_username = BuiltIn().get_variable_value("${OS_USERNAME}")
438 if os_username is None:
439 os_username = ""
440
441 if os_password is "":
442 os_password = BuiltIn().get_variable_value("${OS_PASSWORD}")
443 if os_password is None:
444 os_password = ""
445
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600446 invalid_req_states = [sub_state for sub_state in req_states
447 if sub_state not in valid_req_states]
448 if len(invalid_req_states) > 0:
449 error_message = "The following req_states are not supported:\n" +\
450 gp.sprint_var(invalid_req_states)
451 BuiltIn().fail(gp.sprint_error(error_message))
452
453 # Initialize all substate values supported by this function.
454 ping = 0
455 packet_loss = ''
456 uptime = ''
457 epoch_seconds = ''
458 power = '0'
459 chassis = ''
460 bmc = ''
461 boot_progress = ''
462 host = ''
463
Michael Walsh70369fd2016-11-22 11:25:57 -0600464 # Get the component states.
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600465 if 'ping' in req_states:
466 # See if the OS pings.
467 cmd_buf = "ping -c 1 -w 2 " + openbmc_host
468 if not quiet:
469 grp.rpissuing(cmd_buf)
470 rc, out_buf = commands.getstatusoutput(cmd_buf)
471 if rc == 0:
472 ping = 1
473
474 if 'packet_loss' in req_states:
475 # See if the OS pings.
476 cmd_buf = "ping -c 5 -w 5 " + openbmc_host +\
477 " | egrep 'packet loss' | sed -re 's/.* ([0-9]+)%.*/\\1/g'"
478 if not quiet:
479 grp.rpissuing(cmd_buf)
480 rc, out_buf = commands.getstatusoutput(cmd_buf)
481 if rc == 0:
482 packet_loss = out_buf.rstrip("\n")
483
484 master_req_login = ['uptime', 'epoch_seconds']
485 req_login = [sub_state for sub_state in req_states if sub_state in
486 master_req_login]
487
488 must_login = (len([sub_state for sub_state in req_states
489 if sub_state in master_req_login]) > 0)
490
491 if must_login:
492 cmd_buf = ["Open Connection And Log In"]
493 if not quiet:
494 grp.rpissuing_keyword(cmd_buf)
495 BuiltIn().run_keyword(*cmd_buf)
496
497 if 'uptime' in req_states:
498 cmd_buf = ["Execute Command", "cat /proc/uptime | cut -f 1 -d ' '",
499 "return_stderr=True", "return_rc=True"]
500 if not quiet:
501 grp.rpissuing_keyword(cmd_buf)
502 stdout_buf, stderr_buf, rc = BuiltIn().run_keyword(*cmd_buf)
503 if rc == 0 and stderr_buf == "":
504 uptime = stdout_buf
505
506 if 'epoch_seconds' in req_states:
507 date_cmd_buf = "date -u +%s"
508 if USE_BMC_EPOCH_TIME:
509 cmd_buf = ["Execute Command", date_cmd_buf, "return_stderr=True",
510 "return_rc=True"]
511 if not quiet:
512 grp.rpissuing_keyword(cmd_buf)
513 stdout_buf, stderr_buf, rc = BuiltIn().run_keyword(*cmd_buf)
514 if rc == 0 and stderr_buf == "":
515 epoch_seconds = stdout_buf.rstrip("\n")
516 else:
517 shell_rc, out_buf = gc.cmd_fnc_u(date_cmd_buf,
518 quiet=1,
519 print_output=0)
520 if shell_rc == 0:
521 epoch_seconds = out_buf.rstrip("\n")
522
523 if 'power' in req_states:
Michael Walsh341c21e2017-01-17 16:25:20 -0600524 cmd_buf = ["Get Power State", "quiet=${" + str(quiet) + "}"]
525 grp.rdpissuing_keyword(cmd_buf)
526 power = BuiltIn().run_keyword(*cmd_buf)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600527 if 'chassis' in req_states:
Michael Walsh341c21e2017-01-17 16:25:20 -0600528 cmd_buf = ["Get Chassis Power State", "quiet=${" + str(quiet) + "}"]
529 grp.rdpissuing_keyword(cmd_buf)
530 chassis = BuiltIn().run_keyword(*cmd_buf)
531 # Strip everything up to the final period.
532 chassis = re.sub(r'.*\.', "", chassis)
Michael Walsh70369fd2016-11-22 11:25:57 -0600533
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600534 if 'bmc' in req_states:
Michael Walsh341c21e2017-01-17 16:25:20 -0600535 if OBMC_STATES_VERSION == 0:
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600536 qualifier = "utils"
Michael Walsh341c21e2017-01-17 16:25:20 -0600537 else:
Michael Walsh78bb9622017-03-10 14:13:58 -0600538 # This will not be supported much longer.
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600539 qualifier = "state_manager"
540
541 cmd_buf = [qualifier + ".Get BMC State", "quiet=${" + str(quiet) + "}"]
Michael Walsh78bb9622017-03-10 14:13:58 -0600542 grp.rdpissuing_keyword(cmd_buf)
543 bmc = BuiltIn().run_keyword(*cmd_buf)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600544
545 if 'boot_progress' in req_states:
546 cmd_buf = ["Get Boot Progress", "quiet=${" + str(quiet) + "}"]
Michael Walsh70369fd2016-11-22 11:25:57 -0600547 grp.rdpissuing_keyword(cmd_buf)
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600548 boot_progress = BuiltIn().run_keyword(*cmd_buf)
549
550 if 'host' in req_states:
551 if OBMC_STATES_VERSION > 0:
552 cmd_buf = ["Get Host State", "quiet=${" + str(quiet) + "}"]
553 grp.rdpissuing_keyword(cmd_buf)
554 host = BuiltIn().run_keyword(*cmd_buf)
555 # Strip everything up to the final period.
556 host = re.sub(r'.*\.', "", host)
557
558 state = DotDict()
559 for sub_state in req_states:
560 if sub_state.startswith("os_"):
561 # We pass "os_" requests on to get_os_state.
562 continue
563 cmd_buf = "state['" + sub_state + "'] = str(" + sub_state + ")"
564 exec(cmd_buf)
565
566 if os_host == "":
567 # The caller has not specified an os_host so as far as we're concerned,
568 # it doesn't exist.
569 return state
570
571 os_req_states = [sub_state for sub_state in req_states
572 if sub_state.startswith('os_')]
573
574 if len(os_req_states) > 0:
575 # The caller has specified an os_host and they have requested
576 # information on os substates.
577
578 # Based on the information gathered on bmc, we'll try to make a
579 # determination of whether the os is even up. We'll pass the result
580 # of that assessment to get_os_state to enhance performance.
581 os_up_match = DotDict()
582 for sub_state in master_os_up_match:
583 if sub_state in req_states:
584 os_up_match[sub_state] = master_os_up_match[sub_state]
Michael Walsh70369fd2016-11-22 11:25:57 -0600585 os_up = compare_states(state, os_up_match)
586
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600587 os_state = get_os_state(os_host=os_host,
588 os_username=os_username,
589 os_password=os_password,
590 req_states=os_req_states,
591 os_up=os_up,
592 quiet=quiet)
593 # Append os_state dictionary to ours.
594 state.update(os_state)
Michael Walsh70369fd2016-11-22 11:25:57 -0600595
596 return state
597
598###############################################################################
599
600
601###############################################################################
602def check_state(match_state,
603 invert=0,
604 print_string="",
605 openbmc_host="",
606 openbmc_username="",
607 openbmc_password="",
608 os_host="",
609 os_username="",
610 os_password="",
611 quiet=None):
612
613 r"""
614 Check that the Open BMC machine's composite state matches the specified
615 state. On success, this keyword returns the machine's composite state as a
616 dictionary.
617
618 Description of arguments:
619 match_state A dictionary whose key/value pairs are "state field"/
620 "state value". The state value is interpreted as a
621 regular expression. Example call from robot:
Michael Walsh341c21e2017-01-17 16:25:20 -0600622 ${match_state}= Create Dictionary chassis=^On$
623 ... bmc=^Ready$
Michael Walsh70369fd2016-11-22 11:25:57 -0600624 ... boot_progress=^FW Progress, Starting OS$
625 ${state}= Check State &{match_state}
626 invert If this flag is set, this function will succeed if the
627 states do NOT match.
628 print_string This function will print this string to the console prior
629 to getting the state.
630 openbmc_host The DNS name or IP address of the BMC.
631 This defaults to global ${OPENBMC_HOST}.
632 openbmc_username The username to be used to login to the BMC.
633 This defaults to global ${OPENBMC_USERNAME}.
634 openbmc_password The password to be used to login to the BMC.
635 This defaults to global ${OPENBMC_PASSWORD}.
636 os_host The DNS name or IP address of the operating system.
637 This defaults to global ${OS_HOST}.
638 os_username The username to be used to login to the OS.
639 This defaults to global ${OS_USERNAME}.
640 os_password The password to be used to login to the OS.
641 This defaults to global ${OS_PASSWORD}.
642 quiet Indicates whether status details should be written to the
643 console. Defaults to either global value of ${QUIET} or
644 to 1.
645 """
646
647 quiet = grp.set_quiet_default(quiet, 1)
648
649 grp.rprint(print_string)
650
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600651 req_states = match_state.keys()
Michael Walsh70369fd2016-11-22 11:25:57 -0600652 # Initialize state.
653 state = get_state(openbmc_host=openbmc_host,
654 openbmc_username=openbmc_username,
655 openbmc_password=openbmc_password,
656 os_host=os_host,
657 os_username=os_username,
658 os_password=os_password,
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600659 req_states=req_states,
Michael Walsh70369fd2016-11-22 11:25:57 -0600660 quiet=quiet)
661 if not quiet:
662 grp.rprint_var(state)
663
664 match = compare_states(state, match_state)
665
666 if invert and match:
667 fail_msg = "The current state of the machine matches the match" +\
668 " state:\n" + gp.sprint_varx("state", state)
669 BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
670 elif not invert and not match:
671 fail_msg = "The current state of the machine does NOT match the" +\
672 " match state:\n" +\
673 gp.sprint_varx("state", state)
674 BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
675
676 return state
677
678###############################################################################
679
680
681###############################################################################
Michael Walshf893ba02017-01-10 10:28:05 -0600682def wait_state(match_state=(),
Michael Walsh70369fd2016-11-22 11:25:57 -0600683 wait_time="1 min",
684 interval="1 second",
685 invert=0,
686 openbmc_host="",
687 openbmc_username="",
688 openbmc_password="",
689 os_host="",
690 os_username="",
691 os_password="",
692 quiet=None):
693
694 r"""
695 Wait for the Open BMC machine's composite state to match the specified
696 state. On success, this keyword returns the machine's composite state as
697 a dictionary.
698
699 Description of arguments:
700 match_state A dictionary whose key/value pairs are "state field"/
701 "state value". See check_state (above) for details.
702 wait_time The total amount of time to wait for the desired state.
703 This value may be expressed in Robot Framework's time
704 format (e.g. 1 minute, 2 min 3 s, 4.5).
705 interval The amount of time between state checks.
706 This value may be expressed in Robot Framework's time
707 format (e.g. 1 minute, 2 min 3 s, 4.5).
708 invert If this flag is set, this function will for the state of
709 the machine to cease to match the match state.
710 openbmc_host The DNS name or IP address of the BMC.
711 This defaults to global ${OPENBMC_HOST}.
712 openbmc_username The username to be used to login to the BMC.
713 This defaults to global ${OPENBMC_USERNAME}.
714 openbmc_password The password to be used to login to the BMC.
715 This defaults to global ${OPENBMC_PASSWORD}.
716 os_host The DNS name or IP address of the operating system.
717 This defaults to global ${OS_HOST}.
718 os_username The username to be used to login to the OS.
719 This defaults to global ${OS_USERNAME}.
720 os_password The password to be used to login to the OS.
721 This defaults to global ${OS_PASSWORD}.
722 quiet Indicates whether status details should be written to the
723 console. Defaults to either global value of ${QUIET} or
724 to 1.
725 """
726
727 quiet = grp.set_quiet_default(quiet, 1)
728
729 if not quiet:
730 if invert:
731 alt_text = "cease to "
732 else:
733 alt_text = ""
734 grp.rprint_timen("Checking every " + str(interval) + " for up to " +
735 str(wait_time) + " for the state of the machine to " +
736 alt_text + "match the state shown below.")
737 grp.rprint_var(match_state)
738
Michael Walshf893ba02017-01-10 10:28:05 -0600739 if quiet:
Michael Walsh341c21e2017-01-17 16:25:20 -0600740 print_string = ""
Michael Walshf893ba02017-01-10 10:28:05 -0600741 else:
Michael Walsh341c21e2017-01-17 16:25:20 -0600742 print_string = "#"
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600743
744 debug = int(BuiltIn().get_variable_value("${debug}", "0"))
745 if debug:
746 # In debug we print state so no need to print the "#".
747 print_string = ""
748 check_state_quiet = 1 - debug
Michael Walsh70369fd2016-11-22 11:25:57 -0600749 cmd_buf = ["Check State", match_state, "invert=${" + str(invert) + "}",
Michael Walshf893ba02017-01-10 10:28:05 -0600750 "print_string=" + print_string, "openbmc_host=" + openbmc_host,
Michael Walsh70369fd2016-11-22 11:25:57 -0600751 "openbmc_username=" + openbmc_username,
752 "openbmc_password=" + openbmc_password, "os_host=" + os_host,
753 "os_username=" + os_username, "os_password=" + os_password,
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600754 "quiet=${" + str(check_state_quiet) + "}"]
Michael Walsh70369fd2016-11-22 11:25:57 -0600755 grp.rdpissuing_keyword(cmd_buf)
756 state = BuiltIn().wait_until_keyword_succeeds(wait_time, interval,
757 *cmd_buf)
Michael Walsh70369fd2016-11-22 11:25:57 -0600758 if not quiet:
759 grp.rprintn()
760 if invert:
761 grp.rprint_timen("The states no longer match:")
762 else:
763 grp.rprint_timen("The states match:")
764 grp.rprint_var(state)
765
766 return state
767
768###############################################################################
Michael Walsh8fae6ea2017-02-20 16:14:44 -0600769
770
771###############################################################################
772def wait_for_comm_cycle(start_boot_seconds):
773
774 r"""
775 Wait for communications to the BMC to stop working and then resume working.
776 This function is useful when you have initiated some kind of reboot.
777
778 Description of arguments:
779 start_boot_seconds The time that the boot test started. The format is the
780 epoch time in seconds, i.e. the number of seconds since
781 1970-01-01 00:00:00 UTC. This value should be obtained
782 from the BMC so that it is not dependent on any kind of
783 synchronization between this machine and the target BMC
784 This will allow this program to work correctly even in
785 a simulated environment. This value should be obtained
786 by the caller prior to initiating a reboot. It can be
787 obtained as follows:
788 state = st.get_state(req_states=['epoch_seconds'])
789 """
790
791 # Validate parms.
792 error_message = gv.svalid_integer(start_boot_seconds,
793 var_name="start_boot_seconds")
794 if error_message != "":
795 BuiltIn().fail(gp.sprint_error(error_message))
796
797 match_state = anchor_state(DotDict([('packet_loss', '100')]))
798 # Wait for 100% packet loss trying to ping machine.
799 wait_state(match_state, wait_time="3 mins", interval="0 seconds")
800
801 match_state['packet_loss'] = '^0$'
802 # Wait for 0% packet loss trying to ping machine.
803 wait_state(match_state, wait_time="4 mins", interval="0 seconds")
804
805 # Get the uptime and epoch seconds for comparisons. We want to be sure
806 # that the uptime is less than the elapsed boot time. Further proof that
807 # a reboot has indeed occurred (vs random network instability giving a
808 # false positive.
809 state = get_state(req_states=['uptime', 'epoch_seconds'])
810
811 elapsed_boot_time = int(state['epoch_seconds']) - start_boot_seconds
812 grp.rprint_var(elapsed_boot_time)
813 if int(float(state['uptime'])) < elapsed_boot_time:
814 uptime = state['uptime']
815 grp.rprint_var(uptime)
816 grp.rprint_timen("The uptime is less than the elapsed boot time," +
817 " as expected.")
818 else:
819 error_message = "The uptime is greater than the elapsed boot time," +\
820 " which is unexpected:\n" +\
821 gp.sprint_var(start_boot_seconds) +\
822 gp.sprint_var(state)
823 BuiltIn().fail(gp.sprint_error(error_message))
824
825 grp.rprint_timen("Verifying that REST API interface is working.")
826 match_state = DotDict([('chassis', '.*')])
827 state = wait_state(match_state, wait_time="5 mins", interval="2 seconds")
828
829###############################################################################