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    if 'uptime' in req_states:
511        cmd_buf = ["BMC Execute Command", "cat /proc/uptime | cut -f 1 -d ' '",
512                   'quiet=${1}']
513        if not quiet:
514            grp.rpissuing_keyword(cmd_buf)
515        status, ret_values = \
516            BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
517        if status == "PASS":
518            stdout, stderr, rc = ret_values
519            if rc == 0 and stderr == "":
520                uptime = stdout
521
522    if 'epoch_seconds' in req_states:
523        date_cmd_buf = "date -u +%s"
524        if USE_BMC_EPOCH_TIME:
525            cmd_buf = ["BMC Execute Command", date_cmd_buf, 'quiet=${1}']
526            if not quiet:
527                grp.rpissuing_keyword(cmd_buf)
528            status, ret_values = \
529                BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
530            if status == "PASS":
531                stdout, stderr, rc = ret_values
532                if rc == 0 and stderr == "":
533                    epoch_seconds = stdout.rstrip("\n")
534        else:
535            shell_rc, out_buf = gc.cmd_fnc_u(date_cmd_buf,
536                                             quiet=1,
537                                             print_output=0)
538            if shell_rc == 0:
539                epoch_seconds = out_buf.rstrip("\n")
540
541    master_req_rest = ['rest', 'chassis', 'bmc', 'boot_progress',
542                       'host']
543    req_rest = [sub_state for sub_state in req_states if sub_state in
544                master_req_rest]
545    need_rest = (len(req_rest) > 0)
546
547    # Though we could try to determine 'rest' state on any of several calls,
548    # for simplicity, we'll use 'chassis' to figure it out (even if the caller
549    # hasn't explicitly asked for 'chassis').
550    if 'chassis' in req_states or need_rest:
551        cmd_buf = ["Get Chassis Power State", "quiet=${" + str(quiet) + "}"]
552        grp.rdpissuing_keyword(cmd_buf)
553        status, ret_values = \
554            BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
555        if status == "PASS":
556            chassis = ret_values
557            chassis = re.sub(r'.*\.', "", chassis)
558            rest = '1'
559        else:
560            rest = ret_values
561
562    if rest == '1':
563        if 'bmc' in req_states:
564            if OBMC_STATES_VERSION == 0:
565                qualifier = "utils"
566            else:
567                # This will not be supported much longer.
568                qualifier = "state_manager"
569            cmd_buf = [qualifier + ".Get BMC State",
570                       "quiet=${" + str(quiet) + "}"]
571            grp.rdpissuing_keyword(cmd_buf)
572            status, ret_values = \
573                BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
574            if status == "PASS":
575                bmc = ret_values
576
577        if 'boot_progress' in req_states:
578            cmd_buf = ["Get Boot Progress", "quiet=${" + str(quiet) + "}"]
579            grp.rdpissuing_keyword(cmd_buf)
580            status, ret_values = \
581                BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
582            if status == "PASS":
583                boot_progress = ret_values
584
585        if 'host' in req_states:
586            if OBMC_STATES_VERSION > 0:
587                cmd_buf = ["Get Host State", "quiet=${" + str(quiet) + "}"]
588                grp.rdpissuing_keyword(cmd_buf)
589                status, ret_values = \
590                    BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
591                if status == "PASS":
592                    host = ret_values
593                    # Strip everything up to the final period.
594                    host = re.sub(r'.*\.', "", host)
595
596    state = DotDict()
597    for sub_state in req_states:
598        if sub_state.startswith("os_"):
599            # We pass "os_" requests on to get_os_state.
600            continue
601        cmd_buf = "state['" + sub_state + "'] = str(" + sub_state + ")"
602        exec(cmd_buf)
603
604    if os_host == "":
605        # The caller has not specified an os_host so as far as we're concerned,
606        # it doesn't exist.
607        return state
608
609    os_req_states = [sub_state for sub_state in req_states
610                     if sub_state.startswith('os_')]
611
612    if len(os_req_states) > 0:
613        # The caller has specified an os_host and they have requested
614        # information on os substates.
615
616        # Based on the information gathered on bmc, we'll try to make a
617        # determination of whether the os is even up.  We'll pass the result
618        # of that assessment to get_os_state to enhance performance.
619        os_up_match = DotDict()
620        for sub_state in master_os_up_match:
621            if sub_state in req_states:
622                os_up_match[sub_state] = master_os_up_match[sub_state]
623        os_up = compare_states(state, os_up_match)
624        os_state = get_os_state(os_host=os_host,
625                                os_username=os_username,
626                                os_password=os_password,
627                                req_states=os_req_states,
628                                os_up=os_up,
629                                quiet=quiet)
630        # Append os_state dictionary to ours.
631        state.update(os_state)
632
633    return state
634
635###############################################################################
636
637
638###############################################################################
639def check_state(match_state,
640                invert=0,
641                print_string="",
642                openbmc_host="",
643                openbmc_username="",
644                openbmc_password="",
645                os_host="",
646                os_username="",
647                os_password="",
648                quiet=None):
649
650    r"""
651    Check that the Open BMC machine's composite state matches the specified
652    state.  On success, this keyword returns the machine's composite state as a
653    dictionary.
654
655    Description of arguments:
656    match_state       A dictionary whose key/value pairs are "state field"/
657                      "state value".  The state value is interpreted as a
658                      regular expression.  Example call from robot:
659                      ${match_state}=  Create Dictionary  chassis=^On$
660                      ...  bmc=^Ready$
661                      ...  boot_progress=^FW Progress, Starting OS$
662                      ${state}=  Check State  &{match_state}
663    invert            If this flag is set, this function will succeed if the
664                      states do NOT match.
665    print_string      This function will print this string to the console prior
666                      to getting the state.
667    openbmc_host      The DNS name or IP address of the BMC.
668                      This defaults to global ${OPENBMC_HOST}.
669    openbmc_username  The username to be used to login to the BMC.
670                      This defaults to global ${OPENBMC_USERNAME}.
671    openbmc_password  The password to be used to login to the BMC.
672                      This defaults to global ${OPENBMC_PASSWORD}.
673    os_host           The DNS name or IP address of the operating system.
674                      This defaults to global ${OS_HOST}.
675    os_username       The username to be used to login to the OS.
676                      This defaults to global ${OS_USERNAME}.
677    os_password       The password to be used to login to the OS.
678                      This defaults to global ${OS_PASSWORD}.
679    quiet             Indicates whether status details should be written to the
680                      console.  Defaults to either global value of ${QUIET} or
681                      to 1.
682    """
683
684    quiet = int(gp.get_var_value(quiet, 0))
685
686    grp.rprint(print_string)
687
688    req_states = match_state.keys()
689    # Initialize state.
690    state = get_state(openbmc_host=openbmc_host,
691                      openbmc_username=openbmc_username,
692                      openbmc_password=openbmc_password,
693                      os_host=os_host,
694                      os_username=os_username,
695                      os_password=os_password,
696                      req_states=req_states,
697                      quiet=quiet)
698    if not quiet:
699        gp.print_var(state)
700
701    match = compare_states(state, match_state)
702
703    if invert and match:
704        fail_msg = "The current state of the machine matches the match" +\
705                   " state:\n" + gp.sprint_varx("state", state)
706        BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
707    elif not invert and not match:
708        fail_msg = "The current state of the machine does NOT match the" +\
709                   " match state:\n" +\
710                   gp.sprint_varx("state", state)
711        BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
712
713    return state
714
715###############################################################################
716
717
718###############################################################################
719def wait_state(match_state=(),
720               wait_time="1 min",
721               interval="1 second",
722               invert=0,
723               openbmc_host="",
724               openbmc_username="",
725               openbmc_password="",
726               os_host="",
727               os_username="",
728               os_password="",
729               quiet=None):
730
731    r"""
732    Wait for the Open BMC machine's composite state to match the specified
733    state.  On success, this keyword returns the machine's composite state as
734    a dictionary.
735
736    Description of arguments:
737    match_state       A dictionary whose key/value pairs are "state field"/
738                      "state value".  See check_state (above) for details.
739                      This value may also be any string accepted by
740                      return_state_constant (e.g. "standby_match_state").
741                      In such a case this function will call
742                      return_state_constant to convert it to a proper
743                      dictionary as described above.
744    wait_time         The total amount of time to wait for the desired state.
745                      This value may be expressed in Robot Framework's time
746                      format (e.g. 1 minute, 2 min 3 s, 4.5).
747    interval          The amount of time between state checks.
748                      This value may be expressed in Robot Framework's time
749                      format (e.g. 1 minute, 2 min 3 s, 4.5).
750    invert            If this flag is set, this function will for the state of
751                      the machine to cease to match the match state.
752    openbmc_host      The DNS name or IP address of the BMC.
753                      This defaults to global ${OPENBMC_HOST}.
754    openbmc_username  The username to be used to login to the BMC.
755                      This defaults to global ${OPENBMC_USERNAME}.
756    openbmc_password  The password to be used to login to the BMC.
757                      This defaults to global ${OPENBMC_PASSWORD}.
758    os_host           The DNS name or IP address of the operating system.
759                      This defaults to global ${OS_HOST}.
760    os_username       The username to be used to login to the OS.
761                      This defaults to global ${OS_USERNAME}.
762    os_password       The password to be used to login to the OS.
763                      This defaults to global ${OS_PASSWORD}.
764    quiet             Indicates whether status details should be written to the
765                      console.  Defaults to either global value of ${QUIET} or
766                      to 1.
767    """
768
769    quiet = int(gp.get_var_value(quiet, 0))
770
771    if type(match_state) in (str, unicode):
772        match_state = return_state_constant(match_state)
773
774    if not quiet:
775        if invert:
776            alt_text = "cease to "
777        else:
778            alt_text = ""
779        gp.print_timen("Checking every " + str(interval) + " for up to " +
780                       str(wait_time) + " for the state of the machine to " +
781                       alt_text + "match the state shown below.")
782        gp.print_var(match_state)
783
784    if quiet:
785        print_string = ""
786    else:
787        print_string = "#"
788
789    debug = int(BuiltIn().get_variable_value("${debug}", "0"))
790    if debug:
791        # In debug we print state so no need to print the "#".
792        print_string = ""
793    check_state_quiet = 1 - debug
794    cmd_buf = ["Check State", match_state, "invert=${" + str(invert) + "}",
795               "print_string=" + print_string, "openbmc_host=" + openbmc_host,
796               "openbmc_username=" + openbmc_username,
797               "openbmc_password=" + openbmc_password, "os_host=" + os_host,
798               "os_username=" + os_username, "os_password=" + os_password,
799               "quiet=${" + str(check_state_quiet) + "}"]
800    grp.rdpissuing_keyword(cmd_buf)
801    try:
802        state = BuiltIn().wait_until_keyword_succeeds(wait_time, interval,
803                                                      *cmd_buf)
804    except AssertionError as my_assertion_error:
805        gp.printn()
806        message = my_assertion_error.args[0]
807        BuiltIn().fail(message)
808
809    if not quiet:
810        gp.printn()
811        if invert:
812            gp.print_timen("The states no longer match:")
813        else:
814            gp.print_timen("The states match:")
815        gp.print_var(state)
816
817    return state
818
819###############################################################################
820
821
822###############################################################################
823def wait_for_comm_cycle(start_boot_seconds,
824                        quiet=None):
825
826    r"""
827    Wait for communications to the BMC to stop working and then resume working.
828    This function is useful when you have initiated some kind of reboot.
829
830    Description of arguments:
831    start_boot_seconds  The time that the boot test started.  The format is the
832                        epoch time in seconds, i.e. the number of seconds since
833                        1970-01-01 00:00:00 UTC.  This value should be obtained
834                        from the BMC so that it is not dependent on any kind of
835                        synchronization between this machine and the target BMC
836                        This will allow this program to work correctly even in
837                        a simulated environment.  This value should be obtained
838                        by the caller prior to initiating a reboot.  It can be
839                        obtained as follows:
840                        state = st.get_state(req_states=['epoch_seconds'])
841    """
842
843    quiet = int(gp.get_var_value(quiet, 0))
844
845    # Validate parms.
846    error_message = gv.svalid_integer(start_boot_seconds,
847                                      var_name="start_boot_seconds")
848    if error_message != "":
849        BuiltIn().fail(gp.sprint_error(error_message))
850
851    match_state = anchor_state(DotDict([('packet_loss', '100')]))
852    # Wait for 100% packet loss trying to ping machine.
853    wait_state(match_state, wait_time="8 mins", interval="0 seconds")
854
855    match_state['packet_loss'] = '^0$'
856    # Wait for 0% packet loss trying to ping machine.
857    wait_state(match_state, wait_time="8 mins", interval="0 seconds")
858
859    # Get the uptime and epoch seconds for comparisons.  We want to be sure
860    # that the uptime is less than the elapsed boot time.  Further proof that
861    # a reboot has indeed occurred (vs random network instability giving a
862    # false positive.
863    state = get_state(req_states=['uptime', 'epoch_seconds'], quiet=quiet)
864
865    elapsed_boot_time = int(state['epoch_seconds']) - start_boot_seconds
866    gp.qprint_var(elapsed_boot_time)
867    if int(float(state['uptime'])) < elapsed_boot_time:
868        uptime = state['uptime']
869        gp.qprint_var(uptime)
870        gp.qprint_timen("The uptime is less than the elapsed boot time," +
871                        " as expected.")
872    else:
873        error_message = "The uptime is greater than the elapsed boot time," +\
874                        " which is unexpected:\n" +\
875                        gp.sprint_var(start_boot_seconds) +\
876                        gp.sprint_var(state)
877        BuiltIn().fail(gp.sprint_error(error_message))
878
879    gp.qprint_timen("Verifying that REST API interface is working.")
880    match_state = DotDict([('rest', '^1$')])
881    state = wait_state(match_state, wait_time="5 mins", interval="2 seconds")
882
883###############################################################################
884