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