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