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