1#!/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
11default_state:
12  default_state[chassis]:                         On
13  default_state[boot_progress]:                   FW Progress, Starting OS
14  default_state[host]:                            Running
15  default_state[os_ping]:                         1
16  default_state[os_login]:                        1
17  default_state[os_run_cmd]:                      1
18
19Different users may very well have different needs when inquiring about
20state.  Support for new pieces of state information may be added to this
21module as needed.
22
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
32import gen_robot_utils as gru
33import gen_cmd as gc
34
35import commands
36from robot.libraries.BuiltIn import BuiltIn
37from robot.utils import DotDict
38
39import re
40import os
41
42# We need utils.robot to get keywords like "Get Chassis Power State".
43gru.my_import_resource("utils.robot")
44gru.my_import_resource("state_manager.robot")
45
46# 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.
49# OBMC_STATES_VERSION = 1).
50# 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
52# not set it will default to 1.
53
54# As of the present moment, OBMC_STATES_VERSION of 0 is for cold that is so old
55# that it is no longer worthwhile to maintain.  The OBMC_STATES_VERSION 0 code
56# is being removed but the OBMC_STATES_VERSION value will stay for now in the
57# event that it is needed in the future.
58
59OBMC_STATES_VERSION = int(os.environ.get('OBMC_STATES_VERSION', 1))
60
61# When a user calls get_state w/o specifying req_states, default_req_states
62# is used as its value.
63default_req_states = ['rest',
64                      'chassis',
65                      'bmc',
66                      'boot_progress',
67                      'host',
68                      'os_ping',
69                      'os_login',
70                      'os_run_cmd']
71
72# valid_req_states is a list of sub states supported by the get_state function.
73# valid_req_states, default_req_states and master_os_up_match are used by the
74# get_state function.
75valid_req_states = ['ping',
76                    'packet_loss',
77                    'uptime',
78                    'epoch_seconds',
79                    'rest',
80                    'chassis',
81                    'bmc',
82                    'boot_progress',
83                    'host',
84                    'os_ping',
85                    'os_login',
86                    'os_run_cmd']
87
88# valid_os_req_states and default_os_req_states are used by the os_get_state
89# function.
90# valid_os_req_states is a list of state information supported by the
91# get_os_state function.
92valid_os_req_states = ['os_ping',
93                       'os_login',
94                       'os_run_cmd']
95# When a user calls get_os_state w/o specifying req_states,
96# default_os_req_states is used as its value.
97default_os_req_states = ['os_ping',
98                         'os_login',
99                         'os_run_cmd']
100
101# Presently, some BMCs appear to not keep time very well.  This environment
102# variable directs the get_state function to use either the BMC's epoch time
103# or the local epoch time.
104USE_BMC_EPOCH_TIME = int(os.environ.get('USE_BMC_EPOCH_TIME', 0))
105
106# Useful state constant definition(s).
107# A match state for checking that the system is at "standby".
108standby_match_state = DotDict([('rest', '^1$'),
109                               ('chassis', '^Off$'),
110                               ('bmc', '^Ready$'),
111                               ('boot_progress', ''),
112                               ('host', '')])
113
114# default_state is an initial value which may be of use to callers.
115default_state = DotDict([('rest', '1'),
116                         ('chassis', 'On'),
117                         ('bmc', 'Ready'),
118                         ('boot_progress', 'FW Progress, Starting OS'),
119                         ('host', 'Running'),
120                         ('os_ping', '1'),
121                         ('os_login', '1'),
122                         ('os_run_cmd', '1')])
123
124# A master dictionary to determine whether the os may be up.
125master_os_up_match = DotDict([('chassis', '^On$'),
126                              ('bmc', '^Ready$'),
127                              ('boot_progress',
128                               'FW Progress, Starting OS'),
129                              ('host', '^Running$')])
130
131
132###############################################################################
133def return_default_state():
134
135    r"""
136    Return default state dictionary.
137
138    default_state is an initial value which may be of use to callers.
139    """
140
141    return default_state
142
143###############################################################################
144
145
146valid_state_constants = ['default', 'standby_match_state']
147
148
149###############################################################################
150def return_state_constant(state_name='default'):
151
152    r"""
153    Return default state dictionary.
154
155    default_state is an initial value which may be of use to callers.
156    """
157
158    error_message = gv.svalid_value(state_name, var_name='state_name',
159                                    valid_values=valid_state_constants)
160    if error_message != "":
161        BuiltIn().fail(gp.sprint_error(error_message))
162
163    if state_name == 'default':
164        return default_state
165    elif state_name == 'standby_match_state':
166        return standby_match_state
167
168###############################################################################
169
170
171###############################################################################
172def anchor_state(state):
173
174    r"""
175    Add regular expression anchors ("^" and "$") to the beginning and end of
176    each item in the state dictionary passed in.  Return the resulting
177    dictionary.
178
179    Description of Arguments:
180    state    A dictionary such as the one returned by the get_state()
181             function.
182    """
183
184    anchored_state = state.copy()
185    for key, match_state_value in anchored_state.items():
186        anchored_state[key] = "^" + str(anchored_state[key]) + "$"
187
188    return anchored_state
189
190###############################################################################
191
192
193###############################################################################
194def strip_anchor_state(state):
195
196    r"""
197    Strip regular expression anchors ("^" and "$") from the beginning and end
198    of each item in the state dictionary passed in.  Return the resulting
199    dictionary.
200
201    Description of Arguments:
202    state    A dictionary such as the one returned by the get_state()
203             function.
204    """
205
206    stripped_state = state.copy()
207    for key, match_state_value in stripped_state.items():
208        stripped_state[key] = stripped_state[key].strip("^$")
209
210    return stripped_state
211
212###############################################################################
213
214
215###############################################################################
216def compare_states(state,
217                   match_state):
218
219    r"""
220    Compare 2 state dictionaries.  Return True if they match and False if they
221    don't.  Note that the match_state dictionary does not need to have an entry
222    corresponding to each entry in the state dictionary.  But for each entry
223    that it does have, the corresponding state entry will be checked for a
224    match.
225
226    Description of arguments:
227    state           A state dictionary such as the one returned by the
228                    get_state function.
229    match_state     A dictionary whose key/value pairs are "state field"/
230                    "state value".  The state value is interpreted as a
231                    regular expression.  Every value in this dictionary is
232                    considered.  If each and every one matches, the 2
233                    dictionaries are considered to be matching.
234    """
235
236    match = True
237    for key, match_state_value in match_state.items():
238        # Blank match_state_value means "don't care".
239        if match_state_value == "":
240            continue
241        try:
242            if not re.match(match_state_value, str(state[key])):
243                match = False
244                break
245        except KeyError:
246            match = False
247            break
248
249    return match
250
251###############################################################################
252
253
254###############################################################################
255def get_os_state(os_host="",
256                 os_username="",
257                 os_password="",
258                 req_states=default_os_req_states,
259                 os_up=True,
260                 quiet=None):
261
262    r"""
263    Get component states for the operating system such as ping, login,
264    etc, put them into a dictionary and return them to the caller.
265
266    Note that all substate values are strings.
267
268    Description of arguments:
269    os_host      The DNS name or IP address of the operating system.
270                 This defaults to global ${OS_HOST}.
271    os_username  The username to be used to login to the OS.
272                 This defaults to global ${OS_USERNAME}.
273    os_password  The password to be used to login to the OS.
274                 This defaults to global ${OS_PASSWORD}.
275    req_states   This is a list of states whose values are being requested by
276                 the caller.
277    os_up        If the caller knows that the os can't possibly be up, it can
278                 improve performance by passing os_up=False.  This function
279                 will then simply return default values for all requested os
280                 sub states.
281    quiet        Indicates whether status details (e.g. curl commands) should
282                 be written to the console.
283                 Defaults to either global value of ${QUIET} or to 1.
284    """
285
286    quiet = int(gp.get_var_value(quiet, 0))
287
288    # Set parm defaults where necessary and validate all parms.
289    if os_host == "":
290        os_host = BuiltIn().get_variable_value("${OS_HOST}")
291    error_message = gv.svalid_value(os_host, var_name="os_host",
292                                    invalid_values=[None, ""])
293    if error_message != "":
294        BuiltIn().fail(gp.sprint_error(error_message))
295
296    if os_username == "":
297        os_username = BuiltIn().get_variable_value("${OS_USERNAME}")
298    error_message = gv.svalid_value(os_username, var_name="os_username",
299                                    invalid_values=[None, ""])
300    if error_message != "":
301        BuiltIn().fail(gp.sprint_error(error_message))
302
303    if os_password == "":
304        os_password = BuiltIn().get_variable_value("${OS_PASSWORD}")
305    error_message = gv.svalid_value(os_password, var_name="os_password",
306                                    invalid_values=[None, ""])
307    if error_message != "":
308        BuiltIn().fail(gp.sprint_error(error_message))
309
310    invalid_req_states = [sub_state for sub_state in req_states
311                          if sub_state not in valid_os_req_states]
312    if len(invalid_req_states) > 0:
313        error_message = "The following req_states are not supported:\n" +\
314            gp.sprint_var(invalid_req_states)
315        BuiltIn().fail(gp.sprint_error(error_message))
316
317    # Initialize all substate values supported by this function.
318    os_ping = 0
319    os_login = 0
320    os_run_cmd = 0
321
322    if os_up:
323        if 'os_ping' in req_states:
324            # See if the OS pings.
325            cmd_buf = "ping -c 1 -w 2 " + os_host
326            if not quiet:
327                gp.pissuing(cmd_buf)
328            rc, out_buf = commands.getstatusoutput(cmd_buf)
329            if rc == 0:
330                os_ping = 1
331
332        # Programming note: All attributes which do not require an ssh login
333        # should have been processed by this point.
334        master_req_login = ['os_login', 'os_run_cmd']
335        req_login = [sub_state for sub_state in req_states if sub_state in
336                     master_req_login]
337        must_login = (len(req_login) > 0)
338
339        if must_login:
340            # Open SSH connection to OS.  Note that this doesn't fail even when
341            # the OS is not up.
342            cmd_buf = ["SSHLibrary.Open Connection", os_host]
343            if not quiet:
344                grp.rpissuing_keyword(cmd_buf)
345            ix = BuiltIn().run_keyword(*cmd_buf)
346
347            # Login to OS.
348            cmd_buf = ["Login", os_username, os_password]
349            if not quiet:
350                grp.rpissuing_keyword(cmd_buf)
351            status, ret_values = \
352                BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
353            if status == "PASS":
354                os_login = 1
355            else:
356                gp.dprint_var(status)
357                gp.dprint_var(ret_values)
358
359            if os_login:
360                if 'os_run_cmd' in req_states:
361                    # Try running a simple command (uptime) on the OS.
362                    cmd_buf = ["Execute Command", "uptime",
363                               "return_stderr=True", "return_rc=True"]
364                    if not quiet:
365                        grp.rpissuing_keyword(cmd_buf)
366                    # Note that in spite of its name, there are occasions
367                    # where run_keyword_and_ignore_error can fail.
368                    status, ret_values = \
369                        BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
370                    if status == "PASS":
371                        stdout, stderr, rc = ret_values
372                        if rc == 0 and stderr == "":
373                            os_run_cmd = 1
374                        else:
375                            gp.dprint_var(status)
376                            gp.dprint_var(stdout)
377                            gp.dprint_var(stderr)
378                            gp.dprint_var(rc)
379                    else:
380                        gp.dprint_var(status)
381                        gp.dprint_var(ret_values)
382
383    os_state = DotDict()
384    for sub_state in req_states:
385        cmd_buf = "os_state['" + sub_state + "'] = str(" + sub_state + ")"
386        exec(cmd_buf)
387
388    return os_state
389
390###############################################################################
391
392
393###############################################################################
394def get_state(openbmc_host="",
395              openbmc_username="",
396              openbmc_password="",
397              os_host="",
398              os_username="",
399              os_password="",
400              req_states=default_req_states,
401              quiet=None):
402
403    r"""
404    Get component states such as chassis state, bmc state, etc, put them into a
405    dictionary and return them to the caller.
406
407    Note that all substate values are strings.
408
409    Description of arguments:
410    openbmc_host      The DNS name or IP address of the BMC.
411                      This defaults to global ${OPENBMC_HOST}.
412    openbmc_username  The username to be used to login to the BMC.
413                      This defaults to global ${OPENBMC_USERNAME}.
414    openbmc_password  The password to be used to login to the BMC.
415                      This defaults to global ${OPENBMC_PASSWORD}.
416    os_host           The DNS name or IP address of the operating system.
417                      This defaults to global ${OS_HOST}.
418    os_username       The username to be used to login to the OS.
419                      This defaults to global ${OS_USERNAME}.
420    os_password       The password to be used to login to the OS.
421                      This defaults to global ${OS_PASSWORD}.
422    req_states        This is a list of states whose values are being requested
423                      by the caller.
424    quiet             Indicates whether status details (e.g. curl commands)
425                      should be written to the console.
426                      Defaults to either global value of ${QUIET} or to 1.
427    """
428
429    quiet = int(gp.get_var_value(quiet, 0))
430
431    # Set parm defaults where necessary and validate all parms.
432    if openbmc_host == "":
433        openbmc_host = BuiltIn().get_variable_value("${OPENBMC_HOST}")
434    error_message = gv.svalid_value(openbmc_host,
435                                    var_name="openbmc_host",
436                                    invalid_values=[None, ""])
437    if error_message != "":
438        BuiltIn().fail(gp.sprint_error(error_message))
439
440    if openbmc_username == "":
441        openbmc_username = BuiltIn().get_variable_value("${OPENBMC_USERNAME}")
442    error_message = gv.svalid_value(openbmc_username,
443                                    var_name="openbmc_username",
444                                    invalid_values=[None, ""])
445    if error_message != "":
446        BuiltIn().fail(gp.sprint_error(error_message))
447
448    if openbmc_password == "":
449        openbmc_password = BuiltIn().get_variable_value("${OPENBMC_PASSWORD}")
450    error_message = gv.svalid_value(openbmc_password,
451                                    var_name="openbmc_password",
452                                    invalid_values=[None, ""])
453    if error_message != "":
454        BuiltIn().fail(gp.sprint_error(error_message))
455
456    # NOTE: OS parms are optional.
457    if os_host == "":
458        os_host = BuiltIn().get_variable_value("${OS_HOST}")
459        if os_host is None:
460            os_host = ""
461
462    if os_username is "":
463        os_username = BuiltIn().get_variable_value("${OS_USERNAME}")
464        if os_username is None:
465            os_username = ""
466
467    if os_password is "":
468        os_password = BuiltIn().get_variable_value("${OS_PASSWORD}")
469        if os_password is None:
470            os_password = ""
471
472    invalid_req_states = [sub_state for sub_state in req_states
473                          if sub_state not in valid_req_states]
474    if len(invalid_req_states) > 0:
475        error_message = "The following req_states are not supported:\n" +\
476            gp.sprint_var(invalid_req_states)
477        BuiltIn().fail(gp.sprint_error(error_message))
478
479    # Initialize all substate values supported by this function.
480    ping = 0
481    packet_loss = ''
482    uptime = ''
483    epoch_seconds = ''
484    rest = '1'
485    chassis = ''
486    bmc = ''
487    boot_progress = ''
488    host = ''
489
490    # Get the component states.
491    if 'ping' in req_states:
492        # See if the OS pings.
493        cmd_buf = "ping -c 1 -w 2 " + openbmc_host
494        if not quiet:
495            gp.pissuing(cmd_buf)
496        rc, out_buf = commands.getstatusoutput(cmd_buf)
497        if rc == 0:
498            ping = 1
499
500    if 'packet_loss' in req_states:
501        # See if the OS pings.
502        cmd_buf = "ping -c 5 -w 5 " + openbmc_host +\
503            " | egrep 'packet loss' | sed -re 's/.* ([0-9]+)%.*/\\1/g'"
504        if not quiet:
505            gp.pissuing(cmd_buf)
506        rc, out_buf = commands.getstatusoutput(cmd_buf)
507        if rc == 0:
508            packet_loss = out_buf.rstrip("\n")
509
510    master_req_login = ['uptime', 'epoch_seconds']
511    req_login = [sub_state for sub_state in req_states if sub_state in
512                 master_req_login]
513    must_login = (len(req_login) > 0)
514
515    bmc_login = 0
516    if must_login:
517        cmd_buf = ["Open Connection And Log In"]
518        if not quiet:
519            grp.rpissuing_keyword(cmd_buf)
520        status, ret_values = \
521            BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
522        if status == "PASS":
523            bmc_login = 1
524        else:
525            if re.match('^Authentication failed for user', ret_values):
526                # An authentication failure is worth failing on.
527                BuiltIn().fail(gp.sprint_error(ret_values))
528
529    if 'uptime' in req_states and bmc_login:
530        cmd_buf = ["Execute Command", "cat /proc/uptime | cut -f 1 -d ' '",
531                   "return_stderr=True", "return_rc=True"]
532        if not quiet:
533            grp.rpissuing_keyword(cmd_buf)
534        status, ret_values = \
535            BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
536        if status == "PASS":
537            stdout, stderr, rc = ret_values
538            if rc == 0 and stderr == "":
539                uptime = stdout
540
541    if 'epoch_seconds' in req_states and bmc_login:
542        date_cmd_buf = "date -u +%s"
543        if USE_BMC_EPOCH_TIME:
544            cmd_buf = ["Execute Command", date_cmd_buf, "return_stderr=True",
545                       "return_rc=True"]
546            if not quiet:
547                grp.rpissuing_keyword(cmd_buf)
548            status, ret_values = \
549                BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
550            if status == "PASS":
551                stdout, stderr, rc = ret_values
552                if rc == 0 and stderr == "":
553                    epoch_seconds = stdout.rstrip("\n")
554        else:
555            shell_rc, out_buf = gc.cmd_fnc_u(date_cmd_buf,
556                                             quiet=1,
557                                             print_output=0)
558            if shell_rc == 0:
559                epoch_seconds = out_buf.rstrip("\n")
560
561    master_req_rest = ['rest', 'chassis', 'bmc', 'boot_progress',
562                       'host']
563    req_rest = [sub_state for sub_state in req_states if sub_state in
564                master_req_rest]
565    need_rest = (len(req_rest) > 0)
566
567    # Though we could try to determine 'rest' state on any of several calls,
568    # for simplicity, we'll use 'chassis' to figure it out (even if the caller
569    # hasn't explicitly asked for 'chassis').
570    if 'chassis' in req_states or need_rest:
571        cmd_buf = ["Get Chassis Power State", "quiet=${" + str(quiet) + "}"]
572        grp.rdpissuing_keyword(cmd_buf)
573        status, ret_values = \
574            BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
575        if status == "PASS":
576            chassis = ret_values
577            chassis = re.sub(r'.*\.', "", chassis)
578            rest = '1'
579        else:
580            rest = ret_values
581
582    if rest == '1':
583        if 'bmc' in req_states:
584            if OBMC_STATES_VERSION == 0:
585                qualifier = "utils"
586            else:
587                # This will not be supported much longer.
588                qualifier = "state_manager"
589            cmd_buf = [qualifier + ".Get BMC State",
590                       "quiet=${" + str(quiet) + "}"]
591            grp.rdpissuing_keyword(cmd_buf)
592            status, ret_values = \
593                BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
594            if status == "PASS":
595                bmc = ret_values
596
597        if 'boot_progress' in req_states:
598            cmd_buf = ["Get Boot Progress", "quiet=${" + str(quiet) + "}"]
599            grp.rdpissuing_keyword(cmd_buf)
600            status, ret_values = \
601                BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
602            if status == "PASS":
603                boot_progress = ret_values
604
605        if 'host' in req_states:
606            if OBMC_STATES_VERSION > 0:
607                cmd_buf = ["Get Host State", "quiet=${" + str(quiet) + "}"]
608                grp.rdpissuing_keyword(cmd_buf)
609                status, ret_values = \
610                    BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
611                if status == "PASS":
612                    host = ret_values
613                    # Strip everything up to the final period.
614                    host = re.sub(r'.*\.', "", host)
615
616    state = DotDict()
617    for sub_state in req_states:
618        if sub_state.startswith("os_"):
619            # We pass "os_" requests on to get_os_state.
620            continue
621        cmd_buf = "state['" + sub_state + "'] = str(" + sub_state + ")"
622        exec(cmd_buf)
623
624    if os_host == "":
625        # The caller has not specified an os_host so as far as we're concerned,
626        # it doesn't exist.
627        return state
628
629    os_req_states = [sub_state for sub_state in req_states
630                     if sub_state.startswith('os_')]
631
632    if len(os_req_states) > 0:
633        # The caller has specified an os_host and they have requested
634        # information on os substates.
635
636        # Based on the information gathered on bmc, we'll try to make a
637        # determination of whether the os is even up.  We'll pass the result
638        # of that assessment to get_os_state to enhance performance.
639        os_up_match = DotDict()
640        for sub_state in master_os_up_match:
641            if sub_state in req_states:
642                os_up_match[sub_state] = master_os_up_match[sub_state]
643        os_up = compare_states(state, os_up_match)
644        os_state = get_os_state(os_host=os_host,
645                                os_username=os_username,
646                                os_password=os_password,
647                                req_states=os_req_states,
648                                os_up=os_up,
649                                quiet=quiet)
650        # Append os_state dictionary to ours.
651        state.update(os_state)
652
653    return state
654
655###############################################################################
656
657
658###############################################################################
659def check_state(match_state,
660                invert=0,
661                print_string="",
662                openbmc_host="",
663                openbmc_username="",
664                openbmc_password="",
665                os_host="",
666                os_username="",
667                os_password="",
668                quiet=None):
669
670    r"""
671    Check that the Open BMC machine's composite state matches the specified
672    state.  On success, this keyword returns the machine's composite state as a
673    dictionary.
674
675    Description of arguments:
676    match_state       A dictionary whose key/value pairs are "state field"/
677                      "state value".  The state value is interpreted as a
678                      regular expression.  Example call from robot:
679                      ${match_state}=  Create Dictionary  chassis=^On$
680                      ...  bmc=^Ready$
681                      ...  boot_progress=^FW Progress, Starting OS$
682                      ${state}=  Check State  &{match_state}
683    invert            If this flag is set, this function will succeed if the
684                      states do NOT match.
685    print_string      This function will print this string to the console prior
686                      to getting the state.
687    openbmc_host      The DNS name or IP address of the BMC.
688                      This defaults to global ${OPENBMC_HOST}.
689    openbmc_username  The username to be used to login to the BMC.
690                      This defaults to global ${OPENBMC_USERNAME}.
691    openbmc_password  The password to be used to login to the BMC.
692                      This defaults to global ${OPENBMC_PASSWORD}.
693    os_host           The DNS name or IP address of the operating system.
694                      This defaults to global ${OS_HOST}.
695    os_username       The username to be used to login to the OS.
696                      This defaults to global ${OS_USERNAME}.
697    os_password       The password to be used to login to the OS.
698                      This defaults to global ${OS_PASSWORD}.
699    quiet             Indicates whether status details should be written to the
700                      console.  Defaults to either global value of ${QUIET} or
701                      to 1.
702    """
703
704    quiet = int(gp.get_var_value(quiet, 0))
705
706    grp.rprint(print_string)
707
708    req_states = match_state.keys()
709    # Initialize state.
710    state = get_state(openbmc_host=openbmc_host,
711                      openbmc_username=openbmc_username,
712                      openbmc_password=openbmc_password,
713                      os_host=os_host,
714                      os_username=os_username,
715                      os_password=os_password,
716                      req_states=req_states,
717                      quiet=quiet)
718    if not quiet:
719        gp.print_var(state)
720
721    match = compare_states(state, match_state)
722
723    if invert and match:
724        fail_msg = "The current state of the machine matches the match" +\
725                   " state:\n" + gp.sprint_varx("state", state)
726        BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
727    elif not invert and not match:
728        fail_msg = "The current state of the machine does NOT match the" +\
729                   " match state:\n" +\
730                   gp.sprint_varx("state", state)
731        BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
732
733    return state
734
735###############################################################################
736
737
738###############################################################################
739def wait_state(match_state=(),
740               wait_time="1 min",
741               interval="1 second",
742               invert=0,
743               openbmc_host="",
744               openbmc_username="",
745               openbmc_password="",
746               os_host="",
747               os_username="",
748               os_password="",
749               quiet=None):
750
751    r"""
752    Wait for the Open BMC machine's composite state to match the specified
753    state.  On success, this keyword returns the machine's composite state as
754    a dictionary.
755
756    Description of arguments:
757    match_state       A dictionary whose key/value pairs are "state field"/
758                      "state value".  See check_state (above) for details.
759                      This value may also be any string accepted by
760                      return_state_constant (e.g. "standby_match_state").
761                      In such a case this function will call
762                      return_state_constant to convert it to a proper
763                      dictionary as described above.
764    wait_time         The total amount of time to wait for the desired state.
765                      This value may be expressed in Robot Framework's time
766                      format (e.g. 1 minute, 2 min 3 s, 4.5).
767    interval          The amount of time between state checks.
768                      This value may be expressed in Robot Framework's time
769                      format (e.g. 1 minute, 2 min 3 s, 4.5).
770    invert            If this flag is set, this function will for the state of
771                      the machine to cease to match the match state.
772    openbmc_host      The DNS name or IP address of the BMC.
773                      This defaults to global ${OPENBMC_HOST}.
774    openbmc_username  The username to be used to login to the BMC.
775                      This defaults to global ${OPENBMC_USERNAME}.
776    openbmc_password  The password to be used to login to the BMC.
777                      This defaults to global ${OPENBMC_PASSWORD}.
778    os_host           The DNS name or IP address of the operating system.
779                      This defaults to global ${OS_HOST}.
780    os_username       The username to be used to login to the OS.
781                      This defaults to global ${OS_USERNAME}.
782    os_password       The password to be used to login to the OS.
783                      This defaults to global ${OS_PASSWORD}.
784    quiet             Indicates whether status details should be written to the
785                      console.  Defaults to either global value of ${QUIET} or
786                      to 1.
787    """
788
789    quiet = int(gp.get_var_value(quiet, 0))
790
791    if type(match_state) in (str, unicode):
792        match_state = return_state_constant(match_state)
793
794    if not quiet:
795        if invert:
796            alt_text = "cease to "
797        else:
798            alt_text = ""
799        gp.print_timen("Checking every " + str(interval) + " for up to " +
800                       str(wait_time) + " for the state of the machine to " +
801                       alt_text + "match the state shown below.")
802        gp.print_var(match_state)
803
804    if quiet:
805        print_string = ""
806    else:
807        print_string = "#"
808
809    debug = int(BuiltIn().get_variable_value("${debug}", "0"))
810    if debug:
811        # In debug we print state so no need to print the "#".
812        print_string = ""
813    check_state_quiet = 1 - debug
814    cmd_buf = ["Check State", match_state, "invert=${" + str(invert) + "}",
815               "print_string=" + print_string, "openbmc_host=" + openbmc_host,
816               "openbmc_username=" + openbmc_username,
817               "openbmc_password=" + openbmc_password, "os_host=" + os_host,
818               "os_username=" + os_username, "os_password=" + os_password,
819               "quiet=${" + str(check_state_quiet) + "}"]
820    grp.rdpissuing_keyword(cmd_buf)
821    try:
822        state = BuiltIn().wait_until_keyword_succeeds(wait_time, interval,
823                                                      *cmd_buf)
824    except AssertionError as my_assertion_error:
825        gp.printn()
826        message = my_assertion_error.args[0]
827        BuiltIn().fail(message)
828
829    if not quiet:
830        gp.printn()
831        if invert:
832            gp.print_timen("The states no longer match:")
833        else:
834            gp.print_timen("The states match:")
835        gp.print_var(state)
836
837    return state
838
839###############################################################################
840
841
842###############################################################################
843def wait_for_comm_cycle(start_boot_seconds,
844                        quiet=None):
845
846    r"""
847    Wait for communications to the BMC to stop working and then resume working.
848    This function is useful when you have initiated some kind of reboot.
849
850    Description of arguments:
851    start_boot_seconds  The time that the boot test started.  The format is the
852                        epoch time in seconds, i.e. the number of seconds since
853                        1970-01-01 00:00:00 UTC.  This value should be obtained
854                        from the BMC so that it is not dependent on any kind of
855                        synchronization between this machine and the target BMC
856                        This will allow this program to work correctly even in
857                        a simulated environment.  This value should be obtained
858                        by the caller prior to initiating a reboot.  It can be
859                        obtained as follows:
860                        state = st.get_state(req_states=['epoch_seconds'])
861    """
862
863    quiet = int(gp.get_var_value(quiet, 0))
864
865    # Validate parms.
866    error_message = gv.svalid_integer(start_boot_seconds,
867                                      var_name="start_boot_seconds")
868    if error_message != "":
869        BuiltIn().fail(gp.sprint_error(error_message))
870
871    match_state = anchor_state(DotDict([('packet_loss', '100')]))
872    # Wait for 100% packet loss trying to ping machine.
873    wait_state(match_state, wait_time="8 mins", interval="0 seconds")
874
875    match_state['packet_loss'] = '^0$'
876    # Wait for 0% packet loss trying to ping machine.
877    wait_state(match_state, wait_time="8 mins", interval="0 seconds")
878
879    # Get the uptime and epoch seconds for comparisons.  We want to be sure
880    # that the uptime is less than the elapsed boot time.  Further proof that
881    # a reboot has indeed occurred (vs random network instability giving a
882    # false positive.
883    state = get_state(req_states=['uptime', 'epoch_seconds'], quiet=quiet)
884
885    elapsed_boot_time = int(state['epoch_seconds']) - start_boot_seconds
886    gp.qprint_var(elapsed_boot_time)
887    if int(float(state['uptime'])) < elapsed_boot_time:
888        uptime = state['uptime']
889        gp.qprint_var(uptime)
890        gp.qprint_timen("The uptime is less than the elapsed boot time," +
891                        " as expected.")
892    else:
893        error_message = "The uptime is greater than the elapsed boot time," +\
894                        " which is unexpected:\n" +\
895                        gp.sprint_var(start_boot_seconds) +\
896                        gp.sprint_var(state)
897        BuiltIn().fail(gp.sprint_error(error_message))
898
899    gp.qprint_timen("Verifying that REST API interface is working.")
900    match_state = DotDict([('rest', '^1$')])
901    state = wait_state(match_state, wait_time="5 mins", interval="2 seconds")
902
903###############################################################################
904