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 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
54OBMC_STATES_VERSION = int(os.environ.get('OBMC_STATES_VERSION', 1))
55
56if OBMC_STATES_VERSION == 0:
57    # default_state is an initial value which may be of use to callers.
58    default_state = DotDict([('power', '1'),
59                             ('bmc', 'HOST_BOOTED'),
60                             ('boot_progress', 'FW Progress, Starting OS'),
61                             ('os_ping', '1'),
62                             ('os_login', '1'),
63                             ('os_run_cmd', '1')])
64    # 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',
73                        'bmc',
74                        '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',
81                          'bmc',
82                          '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'),
88                                  ('bmc', '^HOST_BOOTED$'),
89                                  ('boot_progress',
90                                   'FW Progress, Starting OS')])
91
92else:
93    # default_state is an initial value which may be of use to callers.
94    default_state = DotDict([('chassis', 'On'),
95                             ('boot_progress', 'FW Progress, Starting OS'),
96                             ('host', 'Running'),
97                             ('os_ping', '1'),
98                             ('os_login', '1'),
99                             ('os_run_cmd', '1')])
100    # 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',
109                        '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',
117                          'boot_progress',
118                          'host',
119                          'os_ping',
120                          'os_login',
121                          'os_run_cmd']
122
123    # A master dictionary to determine whether the os may be up.
124    master_os_up_match = DotDict([('chassis', '^On$'),
125                                  ('boot_progress',
126                                   'FW Progress, Starting OS'),
127                                  ('host', '^Running$')])
128
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))
146
147
148###############################################################################
149def return_default_state():
150
151    r"""
152    Return default state dictionary.
153
154    default_state is an initial value which may be of use to callers.
155    """
156
157    return default_state
158
159###############################################################################
160
161
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
175    anchored_state = state.copy()
176    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###############################################################################
185def 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
197    stripped_state = state.copy()
198    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###############################################################################
207def compare_states(state,
208                   match_state):
209
210    r"""
211    Compare 2 state dictionaries.  Return True if they match and False if they
212    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        # Blank match_state_value means "don't care".
230        if match_state_value == "":
231            continue
232        try:
233            if not re.match(match_state_value, str(state[key])):
234                match = False
235                break
236        except KeyError:
237            match = False
238            break
239
240    return match
241
242###############################################################################
243
244
245###############################################################################
246def get_os_state(os_host="",
247                 os_username="",
248                 os_password="",
249                 req_states=default_os_req_states,
250                 os_up=True,
251                 quiet=None):
252
253    r"""
254    Get component states for the operating system such as ping, login,
255    etc, put them into a dictionary and return them to the caller.
256
257    Note that all substate values are strings.
258
259    Description of arguments:
260    os_host      The DNS name or IP address of the operating system.
261                 This defaults to global ${OS_HOST}.
262    os_username  The username to be used to login to the OS.
263                 This defaults to global ${OS_USERNAME}.
264    os_password  The password to be used to login to the OS.
265                 This defaults to global ${OS_PASSWORD}.
266    req_states   This is a list of states whose values are being requested by
267                 the caller.
268    os_up        If the caller knows that the os can't possibly be up, it can
269                 improve performance by passing os_up=False.  This function
270                 will then simply return default values for all requested os
271                 sub states.
272    quiet        Indicates whether status details (e.g. curl commands) should
273                 be written to the console.
274                 Defaults to either global value of ${QUIET} or to 1.
275    """
276
277    quiet = grp.set_quiet_default(quiet, 1)
278
279    # Set parm defaults where necessary and validate all parms.
280    if os_host == "":
281        os_host = BuiltIn().get_variable_value("${OS_HOST}")
282    error_message = gv.svalid_value(os_host, var_name="os_host",
283                                    invalid_values=[None, ""])
284    if error_message != "":
285        BuiltIn().fail(gp.sprint_error(error_message))
286
287    if os_username == "":
288        os_username = BuiltIn().get_variable_value("${OS_USERNAME}")
289    error_message = gv.svalid_value(os_username, var_name="os_username",
290                                    invalid_values=[None, ""])
291    if error_message != "":
292        BuiltIn().fail(gp.sprint_error(error_message))
293
294    if os_password == "":
295        os_password = BuiltIn().get_variable_value("${OS_PASSWORD}")
296    error_message = gv.svalid_value(os_password, var_name="os_password",
297                                    invalid_values=[None, ""])
298    if error_message != "":
299        BuiltIn().fail(gp.sprint_error(error_message))
300
301    invalid_req_states = [sub_state for sub_state in req_states
302                          if sub_state not in valid_os_req_states]
303    if len(invalid_req_states) > 0:
304        error_message = "The following req_states are not supported:\n" +\
305            gp.sprint_var(invalid_req_states)
306        BuiltIn().fail(gp.sprint_error(error_message))
307
308    # Initialize all substate values supported by this function.
309    os_ping = 0
310    os_login = 0
311    os_run_cmd = 0
312
313    if os_up:
314        if 'os_ping' in req_states:
315            # See if the OS pings.
316            cmd_buf = "ping -c 1 -w 2 " + os_host
317            if not quiet:
318                gp.pissuing(cmd_buf)
319            rc, out_buf = commands.getstatusoutput(cmd_buf)
320            if rc == 0:
321                os_ping = 1
322
323        # Programming note: All attributes which do not require an ssh login
324        # should have been processed by this point.
325        master_req_login = ['os_login', 'os_run_cmd']
326        req_login = [sub_state for sub_state in req_states if sub_state in
327                     master_req_login]
328        must_login = (len(req_login) > 0)
329
330        if must_login:
331            # Open SSH connection to OS.  Note that this doesn't fail even when
332            # the OS is not up.
333            cmd_buf = ["SSHLibrary.Open Connection", os_host]
334            if not quiet:
335                grp.rpissuing_keyword(cmd_buf)
336            ix = BuiltIn().run_keyword(*cmd_buf)
337
338            # Login to OS.
339            cmd_buf = ["Login", os_username, os_password]
340            if not quiet:
341                grp.rpissuing_keyword(cmd_buf)
342            status, ret_values = \
343                BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
344            if status == "PASS":
345                os_login = 1
346            else:
347                gp.dprint_var(status)
348                gp.dprint_var(ret_values)
349
350            if os_login:
351                if 'os_run_cmd' in req_states:
352                    # Try running a simple command (uptime) on the OS.
353                    cmd_buf = ["Execute Command", "uptime",
354                               "return_stderr=True", "return_rc=True"]
355                    if not quiet:
356                        grp.rpissuing_keyword(cmd_buf)
357                    # Note that in spite of its name, there are occasions
358                    # where run_keyword_and_ignore_error can fail.
359                    status, ret_values = \
360                        BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
361                    if status == "PASS":
362                        stdout, stderr, rc = ret_values
363                        if rc == 0 and stderr == "":
364                            os_run_cmd = 1
365                        else:
366                            gp.dprint_var(status)
367                            gp.dprint_var(stdout)
368                            gp.dprint_var(stderr)
369                            gp.dprint_var(rc)
370                    else:
371                        gp.dprint_var(status)
372                        gp.dprint_var(ret_values)
373
374    os_state = DotDict()
375    for sub_state in req_states:
376        cmd_buf = "os_state['" + sub_state + "'] = str(" + sub_state + ")"
377        exec(cmd_buf)
378
379    return os_state
380
381###############################################################################
382
383
384###############################################################################
385def get_state(openbmc_host="",
386              openbmc_username="",
387              openbmc_password="",
388              os_host="",
389              os_username="",
390              os_password="",
391              req_states=default_req_states,
392              quiet=None):
393
394    r"""
395    Get component states such as power state, bmc state, etc, put them into a
396    dictionary and return them to the caller.
397
398    Note that all substate values are strings.
399
400    Description of arguments:
401    openbmc_host      The DNS name or IP address of the BMC.
402                      This defaults to global ${OPENBMC_HOST}.
403    openbmc_username  The username to be used to login to the BMC.
404                      This defaults to global ${OPENBMC_USERNAME}.
405    openbmc_password  The password to be used to login to the BMC.
406                      This defaults to global ${OPENBMC_PASSWORD}.
407    os_host           The DNS name or IP address of the operating system.
408                      This defaults to global ${OS_HOST}.
409    os_username       The username to be used to login to the OS.
410                      This defaults to global ${OS_USERNAME}.
411    os_password       The password to be used to login to the OS.
412                      This defaults to global ${OS_PASSWORD}.
413    req_states        This is a list of states whose values are being requested
414                      by the caller.
415    quiet             Indicates whether status details (e.g. curl commands)
416                      should be written to the console.
417                      Defaults to either global value of ${QUIET} or to 1.
418    """
419
420    quiet = grp.set_quiet_default(quiet, 1)
421
422    # Set parm defaults where necessary and validate all parms.
423    if openbmc_host == "":
424        openbmc_host = BuiltIn().get_variable_value("${OPENBMC_HOST}")
425    error_message = gv.svalid_value(openbmc_host,
426                                    var_name="openbmc_host",
427                                    invalid_values=[None, ""])
428    if error_message != "":
429        BuiltIn().fail(gp.sprint_error(error_message))
430
431    if openbmc_username == "":
432        openbmc_username = BuiltIn().get_variable_value("${OPENBMC_USERNAME}")
433    error_message = gv.svalid_value(openbmc_username,
434                                    var_name="openbmc_username",
435                                    invalid_values=[None, ""])
436    if error_message != "":
437        BuiltIn().fail(gp.sprint_error(error_message))
438
439    if openbmc_password == "":
440        openbmc_password = BuiltIn().get_variable_value("${OPENBMC_PASSWORD}")
441    error_message = gv.svalid_value(openbmc_password,
442                                    var_name="openbmc_password",
443                                    invalid_values=[None, ""])
444    if error_message != "":
445        BuiltIn().fail(gp.sprint_error(error_message))
446
447    # NOTE: OS parms are optional.
448    if os_host == "":
449        os_host = BuiltIn().get_variable_value("${OS_HOST}")
450        if os_host is None:
451            os_host = ""
452
453    if os_username is "":
454        os_username = BuiltIn().get_variable_value("${OS_USERNAME}")
455        if os_username is None:
456            os_username = ""
457
458    if os_password is "":
459        os_password = BuiltIn().get_variable_value("${OS_PASSWORD}")
460        if os_password is None:
461            os_password = ""
462
463    invalid_req_states = [sub_state for sub_state in req_states
464                          if sub_state not in valid_req_states]
465    if len(invalid_req_states) > 0:
466        error_message = "The following req_states are not supported:\n" +\
467            gp.sprint_var(invalid_req_states)
468        BuiltIn().fail(gp.sprint_error(error_message))
469
470    # Initialize all substate values supported by this function.
471    ping = 0
472    packet_loss = ''
473    uptime = ''
474    epoch_seconds = ''
475    power = ''
476    chassis = ''
477    bmc = ''
478    boot_progress = ''
479    host = ''
480
481    # Get the component states.
482    if 'ping' in req_states:
483        # See if the OS pings.
484        cmd_buf = "ping -c 1 -w 2 " + openbmc_host
485        if not quiet:
486            gp.pissuing(cmd_buf)
487        rc, out_buf = commands.getstatusoutput(cmd_buf)
488        if rc == 0:
489            ping = 1
490
491    if 'packet_loss' in req_states:
492        # See if the OS pings.
493        cmd_buf = "ping -c 5 -w 5 " + openbmc_host +\
494            " | egrep 'packet loss' | sed -re 's/.* ([0-9]+)%.*/\\1/g'"
495        if not quiet:
496            gp.pissuing(cmd_buf)
497        rc, out_buf = commands.getstatusoutput(cmd_buf)
498        if rc == 0:
499            packet_loss = out_buf.rstrip("\n")
500
501    master_req_login = ['uptime', 'epoch_seconds']
502    req_login = [sub_state for sub_state in req_states if sub_state in
503                 master_req_login]
504    must_login = (len(req_login) > 0)
505
506    bmc_login = 0
507    if must_login:
508        cmd_buf = ["Open Connection And Log In"]
509        if not quiet:
510            grp.rpissuing_keyword(cmd_buf)
511        status, ret_values = \
512            BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
513        if status == "PASS":
514            bmc_login = 1
515        else:
516            if re.match('^Authentication failed for user', ret_values):
517                # An authentication failure is worth failing on.
518                BuiltIn().fail(gp.sprint_error(ret_values))
519
520    if 'uptime' in req_states and bmc_login:
521        cmd_buf = ["Execute Command", "cat /proc/uptime | cut -f 1 -d ' '",
522                   "return_stderr=True", "return_rc=True"]
523        if not quiet:
524            grp.rpissuing_keyword(cmd_buf)
525        status, ret_values = \
526            BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
527        if status == "PASS":
528            stdout, stderr, rc = ret_values
529            if rc == 0 and stderr == "":
530                uptime = stdout
531
532    if 'epoch_seconds' in req_states and bmc_login:
533        date_cmd_buf = "date -u +%s"
534        if USE_BMC_EPOCH_TIME:
535            cmd_buf = ["Execute Command", date_cmd_buf, "return_stderr=True",
536                       "return_rc=True"]
537            if not quiet:
538                grp.rpissuing_keyword(cmd_buf)
539            status, ret_values = \
540                BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
541            if status == "PASS":
542                stdout, stderr, rc = ret_values
543                if rc == 0 and stderr == "":
544                    epoch_seconds = stdout.rstrip("\n")
545        else:
546            shell_rc, out_buf = gc.cmd_fnc_u(date_cmd_buf,
547                                             quiet=1,
548                                             print_output=0)
549            if shell_rc == 0:
550                epoch_seconds = out_buf.rstrip("\n")
551
552    if 'power' in req_states:
553        cmd_buf = ["Get Power State", "quiet=${" + str(quiet) + "}"]
554        grp.rdpissuing_keyword(cmd_buf)
555        status, ret_values = \
556            BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
557        if status == "PASS":
558            power = ret_values
559    if 'chassis' in req_states:
560        cmd_buf = ["Get Chassis Power State", "quiet=${" + str(quiet) + "}"]
561        grp.rdpissuing_keyword(cmd_buf)
562        status, ret_values = \
563            BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
564        if status == "PASS":
565            chassis = ret_values
566            chassis = re.sub(r'.*\.', "", chassis)
567
568    if 'bmc' in req_states:
569        if OBMC_STATES_VERSION == 0:
570            qualifier = "utils"
571        else:
572            # This will not be supported much longer.
573            qualifier = "state_manager"
574
575        cmd_buf = [qualifier + ".Get BMC State", "quiet=${" + str(quiet) + "}"]
576        grp.rdpissuing_keyword(cmd_buf)
577        status, ret_values = \
578            BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
579        if status == "PASS":
580            bmc = ret_values
581
582    if 'boot_progress' in req_states:
583        cmd_buf = ["Get Boot Progress", "quiet=${" + str(quiet) + "}"]
584        grp.rdpissuing_keyword(cmd_buf)
585        status, ret_values = \
586            BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
587        if status == "PASS":
588            boot_progress = ret_values
589
590    if 'host' in req_states:
591        if OBMC_STATES_VERSION > 0:
592            cmd_buf = ["Get Host State", "quiet=${" + str(quiet) + "}"]
593            grp.rdpissuing_keyword(cmd_buf)
594            status, ret_values = \
595                BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
596            if status == "PASS":
597                host = ret_values
598                # Strip everything up to the final period.
599                host = re.sub(r'.*\.', "", host)
600
601    state = DotDict()
602    for sub_state in req_states:
603        if sub_state.startswith("os_"):
604            # We pass "os_" requests on to get_os_state.
605            continue
606        cmd_buf = "state['" + sub_state + "'] = str(" + sub_state + ")"
607        exec(cmd_buf)
608
609    if os_host == "":
610        # The caller has not specified an os_host so as far as we're concerned,
611        # it doesn't exist.
612        return state
613
614    os_req_states = [sub_state for sub_state in req_states
615                     if sub_state.startswith('os_')]
616
617    if len(os_req_states) > 0:
618        # The caller has specified an os_host and they have requested
619        # information on os substates.
620
621        # Based on the information gathered on bmc, we'll try to make a
622        # determination of whether the os is even up.  We'll pass the result
623        # of that assessment to get_os_state to enhance performance.
624        os_up_match = DotDict()
625        for sub_state in master_os_up_match:
626            if sub_state in req_states:
627                os_up_match[sub_state] = master_os_up_match[sub_state]
628        os_up = compare_states(state, os_up_match)
629        os_state = get_os_state(os_host=os_host,
630                                os_username=os_username,
631                                os_password=os_password,
632                                req_states=os_req_states,
633                                os_up=os_up,
634                                quiet=quiet)
635        # Append os_state dictionary to ours.
636        state.update(os_state)
637
638    return state
639
640###############################################################################
641
642
643###############################################################################
644def check_state(match_state,
645                invert=0,
646                print_string="",
647                openbmc_host="",
648                openbmc_username="",
649                openbmc_password="",
650                os_host="",
651                os_username="",
652                os_password="",
653                quiet=None):
654
655    r"""
656    Check that the Open BMC machine's composite state matches the specified
657    state.  On success, this keyword returns the machine's composite state as a
658    dictionary.
659
660    Description of arguments:
661    match_state       A dictionary whose key/value pairs are "state field"/
662                      "state value".  The state value is interpreted as a
663                      regular expression.  Example call from robot:
664                      ${match_state}=  Create Dictionary  chassis=^On$
665                      ...  bmc=^Ready$
666                      ...  boot_progress=^FW Progress, Starting OS$
667                      ${state}=  Check State  &{match_state}
668    invert            If this flag is set, this function will succeed if the
669                      states do NOT match.
670    print_string      This function will print this string to the console prior
671                      to getting the state.
672    openbmc_host      The DNS name or IP address of the BMC.
673                      This defaults to global ${OPENBMC_HOST}.
674    openbmc_username  The username to be used to login to the BMC.
675                      This defaults to global ${OPENBMC_USERNAME}.
676    openbmc_password  The password to be used to login to the BMC.
677                      This defaults to global ${OPENBMC_PASSWORD}.
678    os_host           The DNS name or IP address of the operating system.
679                      This defaults to global ${OS_HOST}.
680    os_username       The username to be used to login to the OS.
681                      This defaults to global ${OS_USERNAME}.
682    os_password       The password to be used to login to the OS.
683                      This defaults to global ${OS_PASSWORD}.
684    quiet             Indicates whether status details should be written to the
685                      console.  Defaults to either global value of ${QUIET} or
686                      to 1.
687    """
688
689    quiet = grp.set_quiet_default(quiet, 1)
690
691    grp.rprint(print_string)
692
693    req_states = match_state.keys()
694    # Initialize state.
695    state = get_state(openbmc_host=openbmc_host,
696                      openbmc_username=openbmc_username,
697                      openbmc_password=openbmc_password,
698                      os_host=os_host,
699                      os_username=os_username,
700                      os_password=os_password,
701                      req_states=req_states,
702                      quiet=quiet)
703    if not quiet:
704        gp.print_var(state)
705
706    match = compare_states(state, match_state)
707
708    if invert and match:
709        fail_msg = "The current state of the machine matches the match" +\
710                   " state:\n" + gp.sprint_varx("state", state)
711        BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
712    elif not invert and not match:
713        fail_msg = "The current state of the machine does NOT match the" +\
714                   " match state:\n" +\
715                   gp.sprint_varx("state", state)
716        BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
717
718    return state
719
720###############################################################################
721
722
723###############################################################################
724def wait_state(match_state=(),
725               wait_time="1 min",
726               interval="1 second",
727               invert=0,
728               openbmc_host="",
729               openbmc_username="",
730               openbmc_password="",
731               os_host="",
732               os_username="",
733               os_password="",
734               quiet=None):
735
736    r"""
737    Wait for the Open BMC machine's composite state to match the specified
738    state.  On success, this keyword returns the machine's composite state as
739    a dictionary.
740
741    Description of arguments:
742    match_state       A dictionary whose key/value pairs are "state field"/
743                      "state value".  See check_state (above) for details.
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 = grp.set_quiet_default(quiet, 1)
770
771    if not quiet:
772        if invert:
773            alt_text = "cease to "
774        else:
775            alt_text = ""
776        gp.print_timen("Checking every " + str(interval) + " for up to " +
777                       str(wait_time) + " for the state of the machine to " +
778                       alt_text + "match the state shown below.")
779        gp.print_var(match_state)
780
781    if quiet:
782        print_string = ""
783    else:
784        print_string = "#"
785
786    debug = int(BuiltIn().get_variable_value("${debug}", "0"))
787    if debug:
788        # In debug we print state so no need to print the "#".
789        print_string = ""
790    check_state_quiet = 1 - debug
791    cmd_buf = ["Check State", match_state, "invert=${" + str(invert) + "}",
792               "print_string=" + print_string, "openbmc_host=" + openbmc_host,
793               "openbmc_username=" + openbmc_username,
794               "openbmc_password=" + openbmc_password, "os_host=" + os_host,
795               "os_username=" + os_username, "os_password=" + os_password,
796               "quiet=${" + str(check_state_quiet) + "}"]
797    grp.rdpissuing_keyword(cmd_buf)
798    state = BuiltIn().wait_until_keyword_succeeds(wait_time, interval,
799                                                  *cmd_buf)
800    if not quiet:
801        gp.printn()
802        if invert:
803            gp.print_timen("The states no longer match:")
804        else:
805            gp.print_timen("The states match:")
806        gp.print_var(state)
807
808    return state
809
810###############################################################################
811
812
813###############################################################################
814def wait_for_comm_cycle(start_boot_seconds):
815
816    r"""
817    Wait for communications to the BMC to stop working and then resume working.
818    This function is useful when you have initiated some kind of reboot.
819
820    Description of arguments:
821    start_boot_seconds  The time that the boot test started.  The format is the
822                        epoch time in seconds, i.e. the number of seconds since
823                        1970-01-01 00:00:00 UTC.  This value should be obtained
824                        from the BMC so that it is not dependent on any kind of
825                        synchronization between this machine and the target BMC
826                        This will allow this program to work correctly even in
827                        a simulated environment.  This value should be obtained
828                        by the caller prior to initiating a reboot.  It can be
829                        obtained as follows:
830                        state = st.get_state(req_states=['epoch_seconds'])
831    """
832
833    # Validate parms.
834    error_message = gv.svalid_integer(start_boot_seconds,
835                                      var_name="start_boot_seconds")
836    if error_message != "":
837        BuiltIn().fail(gp.sprint_error(error_message))
838
839    match_state = anchor_state(DotDict([('packet_loss', '100')]))
840    # Wait for 100% packet loss trying to ping machine.
841    wait_state(match_state, wait_time="3 mins", interval="0 seconds")
842
843    match_state['packet_loss'] = '^0$'
844    # Wait for 0% packet loss trying to ping machine.
845    wait_state(match_state, wait_time="4 mins", interval="0 seconds")
846
847    # Get the uptime and epoch seconds for comparisons.  We want to be sure
848    # that the uptime is less than the elapsed boot time.  Further proof that
849    # a reboot has indeed occurred (vs random network instability giving a
850    # false positive.
851    state = get_state(req_states=['uptime', 'epoch_seconds'])
852
853    elapsed_boot_time = int(state['epoch_seconds']) - start_boot_seconds
854    gp.print_var(elapsed_boot_time)
855    if int(float(state['uptime'])) < elapsed_boot_time:
856        uptime = state['uptime']
857        gp.print_var(uptime)
858        gp.print_timen("The uptime is less than the elapsed boot time," +
859                       " as expected.")
860    else:
861        error_message = "The uptime is greater than the elapsed boot time," +\
862                        " which is unexpected:\n" +\
863                        gp.sprint_var(start_boot_seconds) +\
864                        gp.sprint_var(state)
865        BuiltIn().fail(gp.sprint_error(error_message))
866
867    gp.print_timen("Verifying that REST API interface is working.")
868    match_state = DotDict([('chassis', '.*')])
869    state = wait_state(match_state, wait_time="5 mins", interval="2 seconds")
870
871###############################################################################
872