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