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_valid as gv
32import gen_robot_utils as gru
33import gen_cmd as gc
34import bmc_ssh_utils as bsu
35
36from robot.libraries.BuiltIn import BuiltIn
37from robot.utils import DotDict
38
39import re
40import os
41import sys
42import imp
43
44
45# NOTE: Avoid importing utils.robot because utils.robot imports state.py
46# (indirectly) which will cause failures.
47gru.my_import_resource("rest_client.robot")
48
49base_path = os.path.dirname(os.path.dirname(
50                            imp.find_module("gen_robot_print")[1])) + os.sep
51sys.path.append(base_path + "data/")
52
53# Previously, I had this coded:
54# import variables as var
55# However, we ran into a problem where a robot program did this...
56# Variables           ../../lib/ras/variables.py
57# Prior to doing this...
58# Library            ../lib/state.py
59
60# This caused the wrong variables.py file to be selected.  Attempts to fix this
61# have failed so far.  For the moment, we will hard-code the value we need from
62# the file.
63
64SYSTEM_STATE_URI = "/xyz/openbmc_project/state/"
65
66# The BMC code has recently been changed as far as what states are defined and
67# what the state values can be.  This module now has a means of processing both
68# the old style state (i.e. OBMC_STATES_VERSION = 0) and the new style (i.e.
69# OBMC_STATES_VERSION = 1).
70# The caller can set environment variable OBMC_STATES_VERSION to dictate
71# whether we're processing old or new style states.  If OBMC_STATES_VERSION is
72# not set it will default to 1.
73
74# As of the present moment, OBMC_STATES_VERSION of 0 is for cold that is so old
75# that it is no longer worthwhile to maintain.  The OBMC_STATES_VERSION 0 code
76# is being removed but the OBMC_STATES_VERSION value will stay for now in the
77# event that it is needed in the future.
78
79OBMC_STATES_VERSION = int(os.environ.get('OBMC_STATES_VERSION', 1))
80
81redfish_support_trans_state = int(os.environ.get('REDFISH_SUPPORT_TRANS_STATE', 0)) or \
82    int(BuiltIn().get_variable_value("${REDFISH_SUPPORT_TRANS_STATE}", default=0))
83
84platform_arch_type = os.environ.get('PLATFORM_ARCH_TYPE', '') or \
85    BuiltIn().get_variable_value("${PLATFORM_ARCH_TYPE}", default="power")
86
87# valid_os_req_states and default_os_req_states are used by the os_get_state
88# function.
89# valid_os_req_states is a list of state information supported by the
90# get_os_state function.
91valid_os_req_states = ['os_ping',
92                       'os_login',
93                       'os_run_cmd']
94
95# When a user calls get_os_state w/o specifying req_states,
96# default_os_req_states is used as its value.
97default_os_req_states = ['os_ping',
98                         'os_login',
99                         'os_run_cmd']
100
101# Presently, some BMCs appear to not keep time very well.  This environment
102# variable directs the get_state function to use either the BMC's epoch time
103# or the local epoch time.
104USE_BMC_EPOCH_TIME = int(os.environ.get('USE_BMC_EPOCH_TIME', 0))
105
106# Useful state constant definition(s).
107if not redfish_support_trans_state:
108    # When a user calls get_state w/o specifying req_states, default_req_states
109    # is used as its value.
110    default_req_states = ['rest',
111                          'chassis',
112                          'bmc',
113                          'boot_progress',
114                          'operating_system',
115                          'host',
116                          'os_ping',
117                          'os_login',
118                          'os_run_cmd']
119
120    # valid_req_states is a list of sub states supported by the get_state function.
121    # valid_req_states, default_req_states and master_os_up_match are used by the
122    # get_state function.
123
124    valid_req_states = ['ping',
125                        'packet_loss',
126                        'uptime',
127                        'epoch_seconds',
128                        'elapsed_boot_time',
129                        'rest',
130                        'chassis',
131                        'requested_chassis',
132                        'bmc',
133                        'requested_bmc',
134                        'boot_progress',
135                        'operating_system',
136                        'host',
137                        'requested_host',
138                        'attempts_left',
139                        'os_ping',
140                        'os_login',
141                        'os_run_cmd']
142
143    # default_state is an initial value which may be of use to callers.
144    default_state = DotDict([('rest', '1'),
145                             ('chassis', 'On'),
146                             ('bmc', 'Ready'),
147                             ('boot_progress', 'OSStart'),
148                             ('operating_system', 'BootComplete'),
149                             ('host', 'Running'),
150                             ('os_ping', '1'),
151                             ('os_login', '1'),
152                             ('os_run_cmd', '1')])
153
154    # A match state for checking that the system is at "standby".
155    standby_match_state = DotDict([('rest', '^1$'),
156                                   ('chassis', '^Off$'),
157                                   ('bmc', '^Ready$'),
158                                   ('boot_progress', '^Off|Unspecified$'),
159                                   ('operating_system', '^Inactive$'),
160                                   ('host', '^Off$')])
161
162    # A match state for checking that the system is at "os running".
163    os_running_match_state = DotDict([('chassis', '^On$'),
164                                      ('bmc', '^Ready$'),
165                                      ('boot_progress',
166                                       'FW Progress, Starting OS|OSStart'),
167                                      ('operating_system', 'BootComplete'),
168                                      ('host', '^Running$'),
169                                      ('os_ping', '^1$'),
170                                      ('os_login', '^1$'),
171                                      ('os_run_cmd', '^1$')])
172
173    # A master dictionary to determine whether the os may be up.
174    master_os_up_match = DotDict([('chassis', '^On$'),
175                                  ('bmc', '^Ready$'),
176                                  ('boot_progress',
177                                   'FW Progress, Starting OS|OSStart'),
178                                  ('operating_system', 'BootComplete'),
179                                  ('host', '^Running|Quiesced$')])
180
181    invalid_state_match = DotDict([('rest', '^$'),
182                                   ('chassis', '^$'),
183                                   ('bmc', '^$'),
184                                   ('boot_progress', '^$'),
185                                   ('operating_system', '^$'),
186                                   ('host', '^$')])
187else:
188    # When a user calls get_state w/o specifying req_states, default_req_states
189    # is used as its value.
190    default_req_states = ['redfish',
191                          'chassis',
192                          'bmc',
193                          'boot_progress',
194                          'host',
195                          'os_ping',
196                          'os_login',
197                          'os_run_cmd']
198
199    # valid_req_states is a list of sub states supported by the get_state function.
200    # valid_req_states, default_req_states and master_os_up_match are used by the
201    # get_state function.
202
203    valid_req_states = ['ping',
204                        'packet_loss',
205                        'uptime',
206                        'epoch_seconds',
207                        'elapsed_boot_time',
208                        'redfish',
209                        'chassis',
210                        'requested_chassis',
211                        'bmc',
212                        'requested_bmc',
213                        'boot_progress',
214                        'host',
215                        'requested_host',
216                        'attempts_left',
217                        'os_ping',
218                        'os_login',
219                        'os_run_cmd']
220
221    # default_state is an initial value which may be of use to callers.
222    default_state = DotDict([('redfish', '1'),
223                             ('chassis', 'On'),
224                             ('bmc', 'Enabled'),
225                             ('boot_progress',
226                              'SystemHardwareInitializationComplete|OSBootStarted|OSRunning'),
227                             ('host', 'Enabled'),
228                             ('os_ping', '1'),
229                             ('os_login', '1'),
230                             ('os_run_cmd', '1')])
231
232    # A match state for checking that the system is at "standby".
233    standby_match_state = DotDict([('redfish', '^1$'),
234                                   ('chassis', '^Off$'),
235                                   ('bmc', '^Enabled$'),
236                                   ('boot_progress', '^None$'),
237                                   ('host', '^Disabled$')])
238
239    # A match state for checking that the system is at "os running".
240    os_running_match_state = DotDict([('chassis', '^On$'),
241                                      ('bmc', '^Enabled$'),
242                                      ('boot_progress',
243                                       'SystemHardwareInitializationComplete|OSBootStarted|OSRunning'),
244                                      ('host', '^Enabled$'),
245                                      ('os_ping', '^1$'),
246                                      ('os_login', '^1$'),
247                                      ('os_run_cmd', '^1$')])
248
249    # A master dictionary to determine whether the os may be up.
250    master_os_up_match = DotDict([('chassis', '^On$'),
251                                  ('bmc', '^Enabled$'),
252                                  ('boot_progress',
253                                   'SystemHardwareInitializationComplete|OSBootStarted|OSRunning'),
254                                  ('host', '^Enabled$')])
255
256    invalid_state_match = DotDict([('redfish', '^$'),
257                                   ('chassis', '^$'),
258                                   ('bmc', '^$'),
259                                   ('boot_progress', '^$'),
260                                   ('host', '^$')])
261
262# Filter the states based on platform type.
263if platform_arch_type == "x86":
264    default_req_states.remove("operating_system")
265    default_req_states.remove("boot_progress")
266    valid_req_states.remove("operating_system")
267    valid_req_states.remove("boot_progress")
268    del default_state["operating_system"]
269    del default_state["boot_progress"]
270    del standby_match_state["operating_system"]
271    del standby_match_state["boot_progress"]
272    del os_running_match_state["operating_system"]
273    del os_running_match_state["boot_progress"]
274    del master_os_up_match["operating_system"]
275    del master_os_up_match["boot_progress"]
276    del invalid_state_match["operating_system"]
277    del invalid_state_match["boot_progress"]
278
279
280def return_state_constant(state_name='default_state'):
281    r"""
282    Return the named state dictionary constant.
283    """
284
285    return eval(state_name)
286
287
288def anchor_state(state):
289    r"""
290    Add regular expression anchors ("^" and "$") to the beginning and end of
291    each item in the state dictionary passed in.  Return the resulting
292    dictionary.
293
294    Description of argument(s):
295    state    A dictionary such as the one returned by the get_state()
296             function.
297    """
298
299    anchored_state = state.copy()
300    for key in anchored_state.keys():
301        anchored_state[key] = "^" + str(anchored_state[key]) + "$"
302
303    return anchored_state
304
305
306def strip_anchor_state(state):
307    r"""
308    Strip regular expression anchors ("^" and "$") from the beginning and end
309    of each item in the state dictionary passed in.  Return the resulting
310    dictionary.
311
312    Description of argument(s):
313    state    A dictionary such as the one returned by the get_state()
314             function.
315    """
316
317    stripped_state = state.copy()
318    for key in stripped_state.keys():
319        stripped_state[key] = stripped_state[key].strip("^$")
320
321    return stripped_state
322
323
324def expressions_key():
325    r"""
326    Return expressions key constant.
327    """
328    return '<expressions>'
329
330
331def compare_states(state,
332                   match_state,
333                   match_type='and'):
334    r"""
335    Compare 2 state dictionaries.  Return True if they match and False if they
336    don't.  Note that the match_state dictionary does not need to have an entry
337    corresponding to each entry in the state dictionary.  But for each entry
338    that it does have, the corresponding state entry will be checked for a
339    match.
340
341    Description of argument(s):
342    state           A state dictionary such as the one returned by the
343                    get_state function.
344    match_state     A dictionary whose key/value pairs are "state field"/
345                    "state value".  The state value is interpreted as a
346                    regular expression.  Every value in this dictionary is
347                    considered.  When match_type is 'and', if each and every
348                    comparison matches, the two dictionaries are considered to
349                    be matching.  If match_type is 'or', if any two of the
350                    elements compared match, the two dictionaries are
351                    considered to be matching.
352
353                    This value may also be any string accepted by
354                    return_state_constant (e.g. "standby_match_state").  In
355                    such a case this function will call return_state_constant
356                    to convert it to a proper dictionary as described above.
357
358                    Finally, one special value is accepted for the key field:
359                    expression_key().  If such an entry exists, its value is
360                    taken to be a list of expressions to be evaluated.  These
361                    expressions may reference state dictionary entries by
362                    simply coding them in standard python syntax (e.g.
363                    state['key1']).  What follows is an example expression:
364
365                    "int(float(state['uptime'])) < int(state['elapsed_boot_time'])"
366
367                    In this example, if the state dictionary's 'uptime' entry
368                    is less than its 'elapsed_boot_time' entry, it would
369                    qualify as a match.
370    match_type      This may be 'and' or 'or'.
371    """
372
373    error_message = gv.valid_value(match_type, valid_values=['and', 'or'])
374    if error_message != "":
375        BuiltIn().fail(gp.sprint_error(error_message))
376
377    try:
378        match_state = return_state_constant(match_state)
379    except TypeError:
380        pass
381
382    default_match = (match_type == 'and')
383    for key, match_state_value in match_state.items():
384        # Blank match_state_value means "don't care".
385        if match_state_value == "":
386            continue
387        if key == expressions_key():
388            for expr in match_state_value:
389                # Use python interpreter to evaluate the expression.
390                match = eval(expr)
391                if match != default_match:
392                    return match
393        else:
394            try:
395                match = (re.match(match_state_value, str(state[key])) is not None)
396            except KeyError:
397                match = False
398            if match != default_match:
399                return match
400
401    return default_match
402
403
404def get_os_state(os_host="",
405                 os_username="",
406                 os_password="",
407                 req_states=default_os_req_states,
408                 os_up=True,
409                 quiet=None):
410    r"""
411    Get component states for the operating system such as ping, login,
412    etc, put them into a dictionary and return them to the caller.
413
414    Note that all substate values are strings.
415
416    Description of argument(s):
417    os_host      The DNS name or IP address of the operating system.
418                 This defaults to global ${OS_HOST}.
419    os_username  The username to be used to login to the OS.
420                 This defaults to global ${OS_USERNAME}.
421    os_password  The password to be used to login to the OS.
422                 This defaults to global ${OS_PASSWORD}.
423    req_states   This is a list of states whose values are being requested by
424                 the caller.
425    os_up        If the caller knows that the os can't possibly be up, it can
426                 improve performance by passing os_up=False.  This function
427                 will then simply return default values for all requested os
428                 sub states.
429    quiet        Indicates whether status details (e.g. curl commands) should
430                 be written to the console.
431                 Defaults to either global value of ${QUIET} or to 1.
432    """
433
434    quiet = int(gp.get_var_value(quiet, 0))
435
436    # Set parm defaults where necessary and validate all parms.
437    if os_host == "":
438        os_host = BuiltIn().get_variable_value("${OS_HOST}")
439    error_message = gv.valid_value(os_host, invalid_values=[None, ""])
440    if error_message != "":
441        BuiltIn().fail(gp.sprint_error(error_message))
442
443    if os_username == "":
444        os_username = BuiltIn().get_variable_value("${OS_USERNAME}")
445    error_message = gv.valid_value(os_username, invalid_values=[None, ""])
446    if error_message != "":
447        BuiltIn().fail(gp.sprint_error(error_message))
448
449    if os_password == "":
450        os_password = BuiltIn().get_variable_value("${OS_PASSWORD}")
451    error_message = gv.valid_value(os_password, invalid_values=[None, ""])
452    if error_message != "":
453        BuiltIn().fail(gp.sprint_error(error_message))
454
455    invalid_req_states = [sub_state for sub_state in req_states
456                          if sub_state not in valid_os_req_states]
457    if len(invalid_req_states) > 0:
458        error_message = "The following req_states are not supported:\n" +\
459            gp.sprint_var(invalid_req_states)
460        BuiltIn().fail(gp.sprint_error(error_message))
461
462    # Initialize all substate values supported by this function.
463    os_ping = 0
464    os_login = 0
465    os_run_cmd = 0
466
467    if os_up:
468        if 'os_ping' in req_states:
469            # See if the OS pings.
470            rc, out_buf = gc.shell_cmd("ping -c 1 -w 2 " + os_host,
471                                       print_output=0, show_err=0,
472                                       ignore_err=1)
473            if rc == 0:
474                os_ping = 1
475
476        # Programming note: All attributes which do not require an ssh login
477        # should have been processed by this point.
478        master_req_login = ['os_login', 'os_run_cmd']
479        req_login = [sub_state for sub_state in req_states if sub_state in
480                     master_req_login]
481        must_login = (len(req_login) > 0)
482
483        if must_login:
484            output, stderr, rc = bsu.os_execute_command("uptime", quiet=quiet,
485                                                        ignore_err=1,
486                                                        time_out=20,
487                                                        os_host=os_host,
488                                                        os_username=os_username,
489                                                        os_password=os_password)
490            if rc == 0:
491                os_login = 1
492                os_run_cmd = 1
493            else:
494                gp.dprint_vars(output, stderr)
495                gp.dprint_vars(rc, 1)
496
497    os_state = DotDict()
498    for sub_state in req_states:
499        cmd_buf = "os_state['" + sub_state + "'] = str(" + sub_state + ")"
500        exec(cmd_buf)
501
502    return os_state
503
504
505def get_state(openbmc_host="",
506              openbmc_username="",
507              openbmc_password="",
508              os_host="",
509              os_username="",
510              os_password="",
511              req_states=default_req_states,
512              quiet=None):
513    r"""
514    Get component states such as chassis state, bmc state, etc, put them into a
515    dictionary and return them to the caller.
516
517    Note that all substate values are strings.
518
519    Note: If elapsed_boot_time is included in req_states, it is the caller's
520    duty to call set_start_boot_seconds() in order to set global
521    start_boot_seconds.  elapsed_boot_time is the current time minus
522    start_boot_seconds.
523
524    Description of argument(s):
525    openbmc_host      The DNS name or IP address of the BMC.
526                      This defaults to global ${OPENBMC_HOST}.
527    openbmc_username  The username to be used to login to the BMC.
528                      This defaults to global ${OPENBMC_USERNAME}.
529    openbmc_password  The password to be used to login to the BMC.
530                      This defaults to global ${OPENBMC_PASSWORD}.
531    os_host           The DNS name or IP address of the operating system.
532                      This defaults to global ${OS_HOST}.
533    os_username       The username to be used to login to the OS.
534                      This defaults to global ${OS_USERNAME}.
535    os_password       The password to be used to login to the OS.
536                      This defaults to global ${OS_PASSWORD}.
537    req_states        This is a list of states whose values are being requested
538                      by the caller.
539    quiet             Indicates whether status details (e.g. curl commands)
540                      should be written to the console.
541                      Defaults to either global value of ${QUIET} or to 1.
542    """
543
544    quiet = int(gp.get_var_value(quiet, 0))
545
546    # Set parm defaults where necessary and validate all parms.
547    if openbmc_host == "":
548        openbmc_host = BuiltIn().get_variable_value("${OPENBMC_HOST}")
549    error_message = gv.valid_value(openbmc_host, invalid_values=[None, ""])
550    if error_message != "":
551        BuiltIn().fail(gp.sprint_error(error_message))
552
553    if openbmc_username == "":
554        openbmc_username = BuiltIn().get_variable_value("${OPENBMC_USERNAME}")
555    error_message = gv.valid_value(openbmc_username, invalid_values=[None, ""])
556    if error_message != "":
557        BuiltIn().fail(gp.sprint_error(error_message))
558
559    if openbmc_password == "":
560        openbmc_password = BuiltIn().get_variable_value("${OPENBMC_PASSWORD}")
561    error_message = gv.valid_value(openbmc_password, invalid_values=[None, ""])
562    if error_message != "":
563        BuiltIn().fail(gp.sprint_error(error_message))
564
565    # NOTE: OS parms are optional.
566    if os_host == "":
567        os_host = BuiltIn().get_variable_value("${OS_HOST}")
568        if os_host is None:
569            os_host = ""
570
571    if os_username is "":
572        os_username = BuiltIn().get_variable_value("${OS_USERNAME}")
573        if os_username is None:
574            os_username = ""
575
576    if os_password is "":
577        os_password = BuiltIn().get_variable_value("${OS_PASSWORD}")
578        if os_password is None:
579            os_password = ""
580
581    invalid_req_states = [sub_state for sub_state in req_states
582                          if sub_state not in valid_req_states]
583    if len(invalid_req_states) > 0:
584        error_message = "The following req_states are not supported:\n" +\
585            gp.sprint_var(invalid_req_states)
586        BuiltIn().fail(gp.sprint_error(error_message))
587
588    # Initialize all substate values supported by this function.
589    ping = 0
590    packet_loss = ''
591    uptime = ''
592    epoch_seconds = ''
593    elapsed_boot_time = ''
594    rest = ''
595    redfish = ''
596    chassis = ''
597    requested_chassis = ''
598    bmc = ''
599    requested_bmc = ''
600    # BootProgress state will get populated when state logic enumerates the
601    # state URI. This is to prevent state dictionary  boot_progress value
602    # getting empty when the BootProgress is NOT found, making it optional.
603    boot_progress = 'NA'
604    operating_system = ''
605    host = ''
606    requested_host = ''
607    attempts_left = ''
608
609    # Get the component states.
610    if 'ping' in req_states:
611        # See if the OS pings.
612        rc, out_buf = gc.shell_cmd("ping -c 1 -w 2 " + openbmc_host,
613                                   print_output=0, show_err=0,
614                                   ignore_err=1)
615        if rc == 0:
616            ping = 1
617
618    if 'packet_loss' in req_states:
619        # See if the OS pings.
620        cmd_buf = "ping -c 5 -w 5 " + openbmc_host +\
621            " | egrep 'packet loss' | sed -re 's/.* ([0-9]+)%.*/\\1/g'"
622        rc, out_buf = gc.shell_cmd(cmd_buf,
623                                   print_output=0, show_err=0,
624                                   ignore_err=1)
625        if rc == 0:
626            packet_loss = out_buf.rstrip("\n")
627
628    if 'uptime' in req_states:
629        # Sometimes reading uptime results in a blank value. Call with
630        # wait_until_keyword_succeeds to ensure a non-blank value is obtained.
631        remote_cmd_buf = "read uptime filler 2>/dev/null < /proc/uptime" +\
632            " && [ ! -z \"${uptime}\" ] && echo ${uptime}"
633        cmd_buf = ["BMC Execute Command",
634                   re.sub('\\$', '\\$', remote_cmd_buf), 'quiet=1',
635                   'test_mode=0', 'time_out=5']
636        gp.qprint_issuing(cmd_buf, 0)
637        gp.qprint_issuing(remote_cmd_buf, 0)
638        try:
639            stdout, stderr, rc =\
640                BuiltIn().wait_until_keyword_succeeds("10 sec", "5 sec",
641                                                      *cmd_buf)
642            if rc == 0 and stderr == "":
643                uptime = stdout
644        except AssertionError as my_assertion_error:
645            pass
646
647    if 'epoch_seconds' in req_states or 'elapsed_boot_time' in req_states:
648        date_cmd_buf = "date -u +%s"
649        if USE_BMC_EPOCH_TIME:
650            cmd_buf = ["BMC Execute Command", date_cmd_buf, 'quiet=${1}']
651            if not quiet:
652                gp.print_issuing(cmd_buf)
653            status, ret_values = \
654                BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
655            if status == "PASS":
656                stdout, stderr, rc = ret_values
657                if rc == 0 and stderr == "":
658                    epoch_seconds = stdout.rstrip("\n")
659        else:
660            shell_rc, out_buf = gc.cmd_fnc_u(date_cmd_buf,
661                                             quiet=quiet,
662                                             print_output=0)
663            if shell_rc == 0:
664                epoch_seconds = out_buf.rstrip("\n")
665
666    if 'elapsed_boot_time' in req_states:
667        global start_boot_seconds
668        elapsed_boot_time = int(epoch_seconds) - start_boot_seconds
669
670    if not redfish_support_trans_state:
671        master_req_rest = ['rest', 'host', 'requested_host', 'operating_system',
672                           'attempts_left', 'boot_progress', 'chassis',
673                           'requested_chassis' 'bmc' 'requested_bmc']
674
675        req_rest = [sub_state for sub_state in req_states if sub_state in
676                    master_req_rest]
677        need_rest = (len(req_rest) > 0)
678        state = DotDict()
679        if need_rest:
680            cmd_buf = ["Read Properties", SYSTEM_STATE_URI + "enumerate",
681                       "quiet=${" + str(quiet) + "}", "timeout=30"]
682            gp.dprint_issuing(cmd_buf)
683            status, ret_values = \
684                BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
685            if status == "PASS":
686                state['rest'] = '1'
687            else:
688                state['rest'] = '0'
689
690            if int(state['rest']):
691                for url_path in ret_values:
692                    # Skip conflicting "CurrentHostState" URL from the enum
693                    # /xyz/openbmc_project/state/hypervisor0
694                    if "hypervisor0" in url_path:
695                        continue
696
697                    if platform_arch_type == "x86":
698                        # Skip conflicting "CurrentPowerState" URL from the enum
699                        # /xyz/openbmc_project/state/chassis_system0
700                        if "chassis_system0" in url_path:
701                            continue
702
703                    for attr_name in ret_values[url_path]:
704                        # Create a state key value based on the attr_name.
705                        try:
706                            ret_values[url_path][attr_name] = \
707                                re.sub(r'.*\.', "",
708                                       ret_values[url_path][attr_name])
709                        except TypeError:
710                            pass
711                        # Do some key name manipulations.
712                        new_attr_name = re.sub(r'^Current|(State|Transition)$',
713                                               "", attr_name)
714                        new_attr_name = re.sub(r'BMC', r'Bmc', new_attr_name)
715                        new_attr_name = re.sub(r'([A-Z][a-z])', r'_\1',
716                                               new_attr_name)
717                        new_attr_name = new_attr_name.lower().lstrip("_")
718                        new_attr_name = re.sub(r'power', r'chassis', new_attr_name)
719                        if new_attr_name in req_states:
720                            state[new_attr_name] = ret_values[url_path][attr_name]
721    else:
722        master_req_rf = ['redfish', 'host', 'requested_host',
723                         'attempts_left', 'boot_progress', 'chassis',
724                         'requested_chassis' 'bmc' 'requested_bmc']
725
726        req_rf = [sub_state for sub_state in req_states if sub_state in
727                  master_req_rf]
728        need_rf = (len(req_rf) > 0)
729        state = DotDict()
730        if need_rf:
731            cmd_buf = ["Redfish Get States"]
732            gp.dprint_issuing(cmd_buf)
733            try:
734                status, ret_values = \
735                    BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
736            except Exception as ex:
737                # Robot raised UserKeywordExecutionFailed error exception.
738                gp.dprint_issuing("Retrying Redfish Get States")
739                status, ret_values = \
740                    BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
741
742            gp.dprint_vars(status, ret_values)
743            if status == "PASS":
744                state['redfish'] = '1'
745            else:
746                state['redfish'] = '0'
747
748            if int(state['redfish']):
749                state['chassis'] = ret_values['chassis']
750                state['boot_progress'] = ret_values['boot_progress']
751                state['host'] = ret_values['host']
752                state['bmc'] = ret_values['bmc']
753
754    for sub_state in req_states:
755        if sub_state in state:
756            continue
757        if sub_state.startswith("os_"):
758            # We pass "os_" requests on to get_os_state.
759            continue
760        cmd_buf = "state['" + sub_state + "'] = str(" + sub_state + ")"
761        exec(cmd_buf)
762
763    if os_host == "":
764        # The caller has not specified an os_host so as far as we're concerned,
765        # it doesn't exist.
766        return state
767
768    os_req_states = [sub_state for sub_state in req_states
769                     if sub_state.startswith('os_')]
770
771    if len(os_req_states) > 0:
772        # The caller has specified an os_host and they have requested
773        # information on os substates.
774
775        # Based on the information gathered on bmc, we'll try to make a
776        # determination of whether the os is even up.  We'll pass the result
777        # of that assessment to get_os_state to enhance performance.
778        os_up_match = DotDict()
779        for sub_state in master_os_up_match:
780            if sub_state in req_states:
781                os_up_match[sub_state] = master_os_up_match[sub_state]
782        os_up = compare_states(state, os_up_match)
783        os_state = get_os_state(os_host=os_host,
784                                os_username=os_username,
785                                os_password=os_password,
786                                req_states=os_req_states,
787                                os_up=os_up,
788                                quiet=quiet)
789        # Append os_state dictionary to ours.
790        state.update(os_state)
791
792    return state
793
794
795exit_wait_early_message = ""
796
797
798def set_exit_wait_early_message(value):
799    r"""
800    Set global exit_wait_early_message to the indicated value.
801
802    This is a mechanism by which the programmer can do an early exit from
803    wait_until_keyword_succeeds() based on some special condition.
804
805    Description of argument(s):
806    value                           The value to assign to the global
807                                    exit_wait_early_message.
808    """
809
810    global exit_wait_early_message
811    exit_wait_early_message = value
812
813
814def check_state(match_state,
815                invert=0,
816                print_string="",
817                openbmc_host="",
818                openbmc_username="",
819                openbmc_password="",
820                os_host="",
821                os_username="",
822                os_password="",
823                quiet=None):
824    r"""
825    Check that the Open BMC machine's composite state matches the specified
826    state.  On success, this keyword returns the machine's composite state as a
827    dictionary.
828
829    Description of argument(s):
830    match_state       A dictionary whose key/value pairs are "state field"/
831                      "state value".  The state value is interpreted as a
832                      regular expression.  Example call from robot:
833                      ${match_state}=  Create Dictionary  chassis=^On$
834                      ...  bmc=^Ready$
835                      ...  boot_progress=^OSStart$
836                      ${state}=  Check State  &{match_state}
837    invert            If this flag is set, this function will succeed if the
838                      states do NOT match.
839    print_string      This function will print this string to the console prior
840                      to getting the state.
841    openbmc_host      The DNS name or IP address of the BMC.
842                      This defaults to global ${OPENBMC_HOST}.
843    openbmc_username  The username to be used to login to the BMC.
844                      This defaults to global ${OPENBMC_USERNAME}.
845    openbmc_password  The password to be used to login to the BMC.
846                      This defaults to global ${OPENBMC_PASSWORD}.
847    os_host           The DNS name or IP address of the operating system.
848                      This defaults to global ${OS_HOST}.
849    os_username       The username to be used to login to the OS.
850                      This defaults to global ${OS_USERNAME}.
851    os_password       The password to be used to login to the OS.
852                      This defaults to global ${OS_PASSWORD}.
853    quiet             Indicates whether status details should be written to the
854                      console.  Defaults to either global value of ${QUIET} or
855                      to 1.
856    """
857
858    quiet = int(gp.get_var_value(quiet, 0))
859
860    gp.gp_print(print_string)
861
862    try:
863        match_state = return_state_constant(match_state)
864    except TypeError:
865        pass
866
867    req_states = list(match_state.keys())
868    # Remove special-case match key from req_states.
869    if expressions_key() in req_states:
870        req_states.remove(expressions_key())
871    # Initialize state.
872    state = get_state(openbmc_host=openbmc_host,
873                      openbmc_username=openbmc_username,
874                      openbmc_password=openbmc_password,
875                      os_host=os_host,
876                      os_username=os_username,
877                      os_password=os_password,
878                      req_states=req_states,
879                      quiet=quiet)
880    if not quiet:
881        gp.print_var(state)
882
883    if exit_wait_early_message != "":
884        # The exit_wait_early_message has been set by a signal handler so we
885        # will exit "successfully".  It is incumbent upon the calling function
886        # (e.g. wait_state) to check/clear this variable and to fail
887        # appropriately.
888        return state
889
890    match = compare_states(state, match_state)
891
892    if invert and match:
893        fail_msg = "The current state of the machine matches the match" +\
894                   " state:\n" + gp.sprint_varx("state", state)
895        BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
896    elif not invert and not match:
897        fail_msg = "The current state of the machine does NOT match the" +\
898                   " match state:\n" +\
899                   gp.sprint_varx("state", state)
900        BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
901
902    return state
903
904
905def wait_state(match_state=(),
906               wait_time="1 min",
907               interval="1 second",
908               invert=0,
909               openbmc_host="",
910               openbmc_username="",
911               openbmc_password="",
912               os_host="",
913               os_username="",
914               os_password="",
915               quiet=None):
916    r"""
917    Wait for the Open BMC machine's composite state to match the specified
918    state.  On success, this keyword returns the machine's composite state as
919    a dictionary.
920
921    Description of argument(s):
922    match_state       A dictionary whose key/value pairs are "state field"/
923                      "state value".  See check_state (above) for details.
924                      This value may also be any string accepted by
925                      return_state_constant (e.g. "standby_match_state").
926                      In such a case this function will call
927                      return_state_constant to convert it to a proper
928                      dictionary as described above.
929    wait_time         The total amount of time to wait for the desired state.
930                      This value may be expressed in Robot Framework's time
931                      format (e.g. 1 minute, 2 min 3 s, 4.5).
932    interval          The amount of time between state checks.
933                      This value may be expressed in Robot Framework's time
934                      format (e.g. 1 minute, 2 min 3 s, 4.5).
935    invert            If this flag is set, this function will for the state of
936                      the machine to cease to match the match state.
937    openbmc_host      The DNS name or IP address of the BMC.
938                      This defaults to global ${OPENBMC_HOST}.
939    openbmc_username  The username to be used to login to the BMC.
940                      This defaults to global ${OPENBMC_USERNAME}.
941    openbmc_password  The password to be used to login to the BMC.
942                      This defaults to global ${OPENBMC_PASSWORD}.
943    os_host           The DNS name or IP address of the operating system.
944                      This defaults to global ${OS_HOST}.
945    os_username       The username to be used to login to the OS.
946                      This defaults to global ${OS_USERNAME}.
947    os_password       The password to be used to login to the OS.
948                      This defaults to global ${OS_PASSWORD}.
949    quiet             Indicates whether status details should be written to the
950                      console.  Defaults to either global value of ${QUIET} or
951                      to 1.
952    """
953
954    quiet = int(gp.get_var_value(quiet, 0))
955
956    try:
957        match_state = return_state_constant(match_state)
958    except TypeError:
959        pass
960
961    if not quiet:
962        if invert:
963            alt_text = "cease to "
964        else:
965            alt_text = ""
966        gp.print_timen("Checking every " + str(interval) + " for up to "
967                       + str(wait_time) + " for the state of the machine to "
968                       + alt_text + "match the state shown below.")
969        gp.print_var(match_state)
970
971    if quiet:
972        print_string = ""
973    else:
974        print_string = "#"
975
976    debug = int(BuiltIn().get_variable_value("${debug}", "0"))
977    if debug:
978        # In debug we print state so no need to print the "#".
979        print_string = ""
980    check_state_quiet = 1 - debug
981    cmd_buf = ["Check State", match_state, "invert=${" + str(invert) + "}",
982               "print_string=" + print_string, "openbmc_host=" + openbmc_host,
983               "openbmc_username=" + openbmc_username,
984               "openbmc_password=" + openbmc_password, "os_host=" + os_host,
985               "os_username=" + os_username, "os_password=" + os_password,
986               "quiet=${" + str(check_state_quiet) + "}"]
987    gp.dprint_issuing(cmd_buf)
988    try:
989        state = BuiltIn().wait_until_keyword_succeeds(wait_time, interval,
990                                                      *cmd_buf)
991    except AssertionError as my_assertion_error:
992        gp.printn()
993        message = my_assertion_error.args[0]
994        BuiltIn().fail(message)
995
996    if exit_wait_early_message:
997        # The global exit_wait_early_message was set by a signal handler
998        # indicating that we should fail.
999        message = exit_wait_early_message
1000        # Clear the exit_wait_early_message variable for future use.
1001        set_exit_wait_early_message("")
1002        BuiltIn().fail(gp.sprint_error(message))
1003
1004    if not quiet:
1005        gp.printn()
1006        if invert:
1007            gp.print_timen("The states no longer match:")
1008        else:
1009            gp.print_timen("The states match:")
1010        gp.print_var(state)
1011
1012    return state
1013
1014
1015def set_start_boot_seconds(value=0):
1016    global start_boot_seconds
1017    start_boot_seconds = int(value)
1018
1019
1020set_start_boot_seconds(0)
1021
1022
1023def wait_for_comm_cycle(start_boot_seconds,
1024                        quiet=None):
1025    r"""
1026    Wait for the BMC uptime to be less than elapsed_boot_time.
1027
1028    This function will tolerate an expected loss of communication to the BMC.
1029    This function is useful when some kind of reboot has been initiated by the
1030    caller.
1031
1032    Description of argument(s):
1033    start_boot_seconds  The time that the boot test started.  The format is the
1034                        epoch time in seconds, i.e. the number of seconds since
1035                        1970-01-01 00:00:00 UTC.  This value should be obtained
1036                        from the BMC so that it is not dependent on any kind of
1037                        synchronization between this machine and the target BMC
1038                        This will allow this program to work correctly even in
1039                        a simulated environment.  This value should be obtained
1040                        by the caller prior to initiating a reboot.  It can be
1041                        obtained as follows:
1042                        state = st.get_state(req_states=['epoch_seconds'])
1043    """
1044
1045    quiet = int(gp.get_var_value(quiet, 0))
1046
1047    # Validate parms.
1048    error_message = gv.valid_integer(start_boot_seconds)
1049    if error_message:
1050        BuiltIn().fail(gp.sprint_error(error_message))
1051
1052    # Wait for uptime to be less than elapsed_boot_time.
1053    set_start_boot_seconds(start_boot_seconds)
1054    expr = 'int(float(state[\'uptime\'])) < int(state[\'elapsed_boot_time\'])'
1055    match_state = DotDict([('uptime', '^[0-9\\.]+$'),
1056                           ('elapsed_boot_time', '^[0-9]+$'),
1057                           (expressions_key(), [expr])])
1058    wait_state(match_state, wait_time="12 mins", interval="5 seconds")
1059
1060    gp.qprint_timen("Verifying that REST/Redfish API interface is working.")
1061    if not redfish_support_trans_state:
1062        match_state = DotDict([('rest', '^1$')])
1063    else:
1064        match_state = DotDict([('redfish', '^1$')])
1065    state = wait_state(match_state, wait_time="5 mins", interval="2 seconds")
1066