1#!/usr/bin/env python3
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
265    if not redfish_support_trans_state:
266        default_req_states.remove("operating_system")
267        valid_req_states.remove("operating_system")
268        del default_state["operating_system"]
269        del standby_match_state["operating_system"]
270        del os_running_match_state["operating_system"]
271        del master_os_up_match["operating_system"]
272        del invalid_state_match["operating_system"]
273
274    default_req_states.remove("boot_progress")
275    valid_req_states.remove("boot_progress")
276    del default_state["boot_progress"]
277    del standby_match_state["boot_progress"]
278    del os_running_match_state["boot_progress"]
279    del master_os_up_match["boot_progress"]
280    del invalid_state_match["boot_progress"]
281
282
283def return_state_constant(state_name='default_state'):
284    r"""
285    Return the named state dictionary constant.
286    """
287
288    return eval(state_name)
289
290
291def anchor_state(state):
292    r"""
293    Add regular expression anchors ("^" and "$") to the beginning and end of
294    each item in the state dictionary passed in.  Return the resulting
295    dictionary.
296
297    Description of argument(s):
298    state    A dictionary such as the one returned by the get_state()
299             function.
300    """
301
302    anchored_state = state.copy()
303    for key in anchored_state.keys():
304        anchored_state[key] = "^" + str(anchored_state[key]) + "$"
305
306    return anchored_state
307
308
309def strip_anchor_state(state):
310    r"""
311    Strip regular expression anchors ("^" and "$") from the beginning and end
312    of each item in the state dictionary passed in.  Return the resulting
313    dictionary.
314
315    Description of argument(s):
316    state    A dictionary such as the one returned by the get_state()
317             function.
318    """
319
320    stripped_state = state.copy()
321    for key in stripped_state.keys():
322        stripped_state[key] = stripped_state[key].strip("^$")
323
324    return stripped_state
325
326
327def expressions_key():
328    r"""
329    Return expressions key constant.
330    """
331    return '<expressions>'
332
333
334def compare_states(state,
335                   match_state,
336                   match_type='and'):
337    r"""
338    Compare 2 state dictionaries.  Return True if they match and False if they
339    don't.  Note that the match_state dictionary does not need to have an entry
340    corresponding to each entry in the state dictionary.  But for each entry
341    that it does have, the corresponding state entry will be checked for a
342    match.
343
344    Description of argument(s):
345    state           A state dictionary such as the one returned by the
346                    get_state function.
347    match_state     A dictionary whose key/value pairs are "state field"/
348                    "state value".  The state value is interpreted as a
349                    regular expression.  Every value in this dictionary is
350                    considered.  When match_type is 'and', if each and every
351                    comparison matches, the two dictionaries are considered to
352                    be matching.  If match_type is 'or', if any two of the
353                    elements compared match, the two dictionaries are
354                    considered to be matching.
355
356                    This value may also be any string accepted by
357                    return_state_constant (e.g. "standby_match_state").  In
358                    such a case this function will call return_state_constant
359                    to convert it to a proper dictionary as described above.
360
361                    Finally, one special value is accepted for the key field:
362                    expression_key().  If such an entry exists, its value is
363                    taken to be a list of expressions to be evaluated.  These
364                    expressions may reference state dictionary entries by
365                    simply coding them in standard python syntax (e.g.
366                    state['key1']).  What follows is an example expression:
367
368                    "int(float(state['uptime'])) < int(state['elapsed_boot_time'])"
369
370                    In this example, if the state dictionary's 'uptime' entry
371                    is less than its 'elapsed_boot_time' entry, it would
372                    qualify as a match.
373    match_type      This may be 'and' or 'or'.
374    """
375
376    error_message = gv.valid_value(match_type, valid_values=['and', 'or'])
377    if error_message != "":
378        BuiltIn().fail(gp.sprint_error(error_message))
379
380    try:
381        match_state = return_state_constant(match_state)
382    except TypeError:
383        pass
384
385    default_match = (match_type == 'and')
386    for key, match_state_value in match_state.items():
387        # Blank match_state_value means "don't care".
388        if match_state_value == "":
389            continue
390        if key == expressions_key():
391            for expr in match_state_value:
392                # Use python interpreter to evaluate the expression.
393                match = eval(expr)
394                if match != default_match:
395                    return match
396        else:
397            try:
398                match = (re.match(match_state_value, str(state[key])) is not None)
399            except KeyError:
400                match = False
401            if match != default_match:
402                return match
403
404    return default_match
405
406
407def get_os_state(os_host="",
408                 os_username="",
409                 os_password="",
410                 req_states=default_os_req_states,
411                 os_up=True,
412                 quiet=None):
413    r"""
414    Get component states for the operating system such as ping, login,
415    etc, put them into a dictionary and return them to the caller.
416
417    Note that all substate values are strings.
418
419    Description of argument(s):
420    os_host      The DNS name or IP address of the operating system.
421                 This defaults to global ${OS_HOST}.
422    os_username  The username to be used to login to the OS.
423                 This defaults to global ${OS_USERNAME}.
424    os_password  The password to be used to login to the OS.
425                 This defaults to global ${OS_PASSWORD}.
426    req_states   This is a list of states whose values are being requested by
427                 the caller.
428    os_up        If the caller knows that the os can't possibly be up, it can
429                 improve performance by passing os_up=False.  This function
430                 will then simply return default values for all requested os
431                 sub states.
432    quiet        Indicates whether status details (e.g. curl commands) should
433                 be written to the console.
434                 Defaults to either global value of ${QUIET} or to 1.
435    """
436
437    quiet = int(gp.get_var_value(quiet, 0))
438
439    # Set parm defaults where necessary and validate all parms.
440    if os_host == "":
441        os_host = BuiltIn().get_variable_value("${OS_HOST}")
442    error_message = gv.valid_value(os_host, invalid_values=[None, ""])
443    if error_message != "":
444        BuiltIn().fail(gp.sprint_error(error_message))
445
446    if os_username == "":
447        os_username = BuiltIn().get_variable_value("${OS_USERNAME}")
448    error_message = gv.valid_value(os_username, invalid_values=[None, ""])
449    if error_message != "":
450        BuiltIn().fail(gp.sprint_error(error_message))
451
452    if os_password == "":
453        os_password = BuiltIn().get_variable_value("${OS_PASSWORD}")
454    error_message = gv.valid_value(os_password, invalid_values=[None, ""])
455    if error_message != "":
456        BuiltIn().fail(gp.sprint_error(error_message))
457
458    invalid_req_states = [sub_state for sub_state in req_states
459                          if sub_state not in valid_os_req_states]
460    if len(invalid_req_states) > 0:
461        error_message = "The following req_states are not supported:\n" +\
462            gp.sprint_var(invalid_req_states)
463        BuiltIn().fail(gp.sprint_error(error_message))
464
465    # Initialize all substate values supported by this function.
466    os_ping = 0
467    os_login = 0
468    os_run_cmd = 0
469
470    if os_up:
471        if 'os_ping' in req_states:
472            # See if the OS pings.
473            rc, out_buf = gc.shell_cmd("ping -c 1 -w 2 " + os_host,
474                                       print_output=0, show_err=0,
475                                       ignore_err=1)
476            if rc == 0:
477                os_ping = 1
478
479        # Programming note: All attributes which do not require an ssh login
480        # should have been processed by this point.
481        master_req_login = ['os_login', 'os_run_cmd']
482        req_login = [sub_state for sub_state in req_states if sub_state in
483                     master_req_login]
484        must_login = (len(req_login) > 0)
485
486        if must_login:
487            output, stderr, rc = bsu.os_execute_command("uptime", quiet=quiet,
488                                                        ignore_err=1,
489                                                        time_out=20,
490                                                        os_host=os_host,
491                                                        os_username=os_username,
492                                                        os_password=os_password)
493            if rc == 0:
494                os_login = 1
495                os_run_cmd = 1
496            else:
497                gp.dprint_vars(output, stderr)
498                gp.dprint_vars(rc, 1)
499
500    os_state = DotDict()
501    for sub_state in req_states:
502        cmd_buf = "os_state['" + sub_state + "'] = str(" + sub_state + ")"
503        exec(cmd_buf)
504
505    return os_state
506
507
508def get_state(openbmc_host="",
509              openbmc_username="",
510              openbmc_password="",
511              os_host="",
512              os_username="",
513              os_password="",
514              req_states=default_req_states,
515              quiet=None):
516    r"""
517    Get component states such as chassis state, bmc state, etc, put them into a
518    dictionary and return them to the caller.
519
520    Note that all substate values are strings.
521
522    Note: If elapsed_boot_time is included in req_states, it is the caller's
523    duty to call set_start_boot_seconds() in order to set global
524    start_boot_seconds.  elapsed_boot_time is the current time minus
525    start_boot_seconds.
526
527    Description of argument(s):
528    openbmc_host      The DNS name or IP address of the BMC.
529                      This defaults to global ${OPENBMC_HOST}.
530    openbmc_username  The username to be used to login to the BMC.
531                      This defaults to global ${OPENBMC_USERNAME}.
532    openbmc_password  The password to be used to login to the BMC.
533                      This defaults to global ${OPENBMC_PASSWORD}.
534    os_host           The DNS name or IP address of the operating system.
535                      This defaults to global ${OS_HOST}.
536    os_username       The username to be used to login to the OS.
537                      This defaults to global ${OS_USERNAME}.
538    os_password       The password to be used to login to the OS.
539                      This defaults to global ${OS_PASSWORD}.
540    req_states        This is a list of states whose values are being requested
541                      by the caller.
542    quiet             Indicates whether status details (e.g. curl commands)
543                      should be written to the console.
544                      Defaults to either global value of ${QUIET} or to 1.
545    """
546
547    quiet = int(gp.get_var_value(quiet, 0))
548
549    # Set parm defaults where necessary and validate all parms.
550    if openbmc_host == "":
551        openbmc_host = BuiltIn().get_variable_value("${OPENBMC_HOST}")
552    error_message = gv.valid_value(openbmc_host, invalid_values=[None, ""])
553    if error_message != "":
554        BuiltIn().fail(gp.sprint_error(error_message))
555
556    if openbmc_username == "":
557        openbmc_username = BuiltIn().get_variable_value("${OPENBMC_USERNAME}")
558    error_message = gv.valid_value(openbmc_username, invalid_values=[None, ""])
559    if error_message != "":
560        BuiltIn().fail(gp.sprint_error(error_message))
561
562    if openbmc_password == "":
563        openbmc_password = BuiltIn().get_variable_value("${OPENBMC_PASSWORD}")
564    error_message = gv.valid_value(openbmc_password, invalid_values=[None, ""])
565    if error_message != "":
566        BuiltIn().fail(gp.sprint_error(error_message))
567
568    # NOTE: OS parms are optional.
569    if os_host == "":
570        os_host = BuiltIn().get_variable_value("${OS_HOST}")
571        if os_host is None:
572            os_host = ""
573
574    if os_username == "":
575        os_username = BuiltIn().get_variable_value("${OS_USERNAME}")
576        if os_username is None:
577            os_username = ""
578
579    if os_password == "":
580        os_password = BuiltIn().get_variable_value("${OS_PASSWORD}")
581        if os_password is None:
582            os_password = ""
583
584    invalid_req_states = [sub_state for sub_state in req_states
585                          if sub_state not in valid_req_states]
586    if len(invalid_req_states) > 0:
587        error_message = "The following req_states are not supported:\n" +\
588            gp.sprint_var(invalid_req_states)
589        BuiltIn().fail(gp.sprint_error(error_message))
590
591    # Initialize all substate values supported by this function.
592    ping = 0
593    packet_loss = ''
594    uptime = ''
595    epoch_seconds = ''
596    elapsed_boot_time = ''
597    rest = ''
598    redfish = ''
599    chassis = ''
600    requested_chassis = ''
601    bmc = ''
602    requested_bmc = ''
603    # BootProgress state will get populated when state logic enumerates the
604    # state URI. This is to prevent state dictionary  boot_progress value
605    # getting empty when the BootProgress is NOT found, making it optional.
606    boot_progress = 'NA'
607    operating_system = ''
608    host = ''
609    requested_host = ''
610    attempts_left = ''
611
612    # Get the component states.
613    if 'ping' in req_states:
614        # See if the OS pings.
615        rc, out_buf = gc.shell_cmd("ping -c 1 -w 2 " + openbmc_host,
616                                   print_output=0, show_err=0,
617                                   ignore_err=1)
618        if rc == 0:
619            ping = 1
620
621    if 'packet_loss' in req_states:
622        # See if the OS pings.
623        cmd_buf = "ping -c 5 -w 5 " + openbmc_host +\
624            " | egrep 'packet loss' | sed -re 's/.* ([0-9]+)%.*/\\1/g'"
625        rc, out_buf = gc.shell_cmd(cmd_buf,
626                                   print_output=0, show_err=0,
627                                   ignore_err=1)
628        if rc == 0:
629            packet_loss = out_buf.rstrip("\n")
630
631    if 'uptime' in req_states:
632        # Sometimes reading uptime results in a blank value. Call with
633        # wait_until_keyword_succeeds to ensure a non-blank value is obtained.
634        remote_cmd_buf = "read uptime filler 2>/dev/null < /proc/uptime" +\
635            " && [ ! -z \"${uptime}\" ] && echo ${uptime}"
636        cmd_buf = ["BMC Execute Command",
637                   re.sub('\\$', '\\$', remote_cmd_buf), 'quiet=1',
638                   'test_mode=0', 'time_out=5']
639        gp.qprint_issuing(cmd_buf, 0)
640        gp.qprint_issuing(remote_cmd_buf, 0)
641        try:
642            stdout, stderr, rc =\
643                BuiltIn().wait_until_keyword_succeeds("10 sec", "5 sec",
644                                                      *cmd_buf)
645            if rc == 0 and stderr == "":
646                uptime = stdout
647        except AssertionError as my_assertion_error:
648            pass
649
650    if 'epoch_seconds' in req_states or 'elapsed_boot_time' in req_states:
651        date_cmd_buf = "date -u +%s"
652        if USE_BMC_EPOCH_TIME:
653            cmd_buf = ["BMC Execute Command", date_cmd_buf, 'quiet=${1}']
654            if not quiet:
655                gp.print_issuing(cmd_buf)
656            status, ret_values = \
657                BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
658            if status == "PASS":
659                stdout, stderr, rc = ret_values
660                if rc == 0 and stderr == "":
661                    epoch_seconds = stdout.rstrip("\n")
662        else:
663            shell_rc, out_buf = gc.cmd_fnc_u(date_cmd_buf,
664                                             quiet=quiet,
665                                             print_output=0)
666            if shell_rc == 0:
667                epoch_seconds = out_buf.rstrip("\n")
668
669    if 'elapsed_boot_time' in req_states:
670        global start_boot_seconds
671        elapsed_boot_time = int(epoch_seconds) - start_boot_seconds
672
673    if not redfish_support_trans_state:
674        master_req_rest = ['rest', 'host', 'requested_host', 'operating_system',
675                           'attempts_left', 'boot_progress', 'chassis',
676                           'requested_chassis' 'bmc' 'requested_bmc']
677
678        req_rest = [sub_state for sub_state in req_states if sub_state in
679                    master_req_rest]
680        need_rest = (len(req_rest) > 0)
681        state = DotDict()
682        if need_rest:
683            cmd_buf = ["Read Properties", SYSTEM_STATE_URI + "enumerate",
684                       "quiet=${" + str(quiet) + "}", "timeout=30"]
685            gp.dprint_issuing(cmd_buf)
686            status, ret_values = \
687                BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
688            if status == "PASS":
689                state['rest'] = '1'
690            else:
691                state['rest'] = '0'
692
693            if int(state['rest']):
694                for url_path in ret_values:
695                    # Skip conflicting "CurrentHostState" URL from the enum
696                    # /xyz/openbmc_project/state/hypervisor0
697                    if "hypervisor0" in url_path:
698                        continue
699
700                    if platform_arch_type == "x86":
701                        # Skip conflicting "CurrentPowerState" URL from the enum
702                        # /xyz/openbmc_project/state/chassis_system0
703                        if "chassis_system0" in url_path:
704                            continue
705
706                    for attr_name in ret_values[url_path]:
707                        # Create a state key value based on the attr_name.
708                        try:
709                            ret_values[url_path][attr_name] = \
710                                re.sub(r'.*\.', "",
711                                       ret_values[url_path][attr_name])
712                        except TypeError:
713                            pass
714                        # Do some key name manipulations.
715                        new_attr_name = re.sub(r'^Current|(State|Transition)$',
716                                               "", attr_name)
717                        new_attr_name = re.sub(r'BMC', r'Bmc', new_attr_name)
718                        new_attr_name = re.sub(r'([A-Z][a-z])', r'_\1',
719                                               new_attr_name)
720                        new_attr_name = new_attr_name.lower().lstrip("_")
721                        new_attr_name = re.sub(r'power', r'chassis', new_attr_name)
722                        if new_attr_name in req_states:
723                            state[new_attr_name] = ret_values[url_path][attr_name]
724    else:
725        master_req_rf = ['redfish', 'host', 'requested_host',
726                         'attempts_left', 'boot_progress', 'chassis',
727                         'requested_chassis' 'bmc' 'requested_bmc']
728
729        req_rf = [sub_state for sub_state in req_states if sub_state in
730                  master_req_rf]
731        need_rf = (len(req_rf) > 0)
732        state = DotDict()
733        if need_rf:
734            cmd_buf = ["Redfish Get States"]
735            gp.dprint_issuing(cmd_buf)
736            try:
737                status, ret_values = \
738                    BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
739            except Exception as ex:
740                # Robot raised UserKeywordExecutionFailed error exception.
741                gp.dprint_issuing("Retrying Redfish Get States")
742                status, ret_values = \
743                    BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
744
745            gp.dprint_vars(status, ret_values)
746            if status == "PASS":
747                state['redfish'] = '1'
748            else:
749                state['redfish'] = '0'
750
751            if int(state['redfish']):
752                state['chassis'] = ret_values['chassis']
753                state['host'] = ret_values['host']
754                state['bmc'] = ret_values['bmc']
755                if platform_arch_type != "x86":
756                    state['boot_progress'] = ret_values['boot_progress']
757
758    for sub_state in req_states:
759        if sub_state in state:
760            continue
761        if sub_state.startswith("os_"):
762            # We pass "os_" requests on to get_os_state.
763            continue
764        cmd_buf = "state['" + sub_state + "'] = str(" + sub_state + ")"
765        exec(cmd_buf)
766
767    if os_host == "":
768        # The caller has not specified an os_host so as far as we're concerned,
769        # it doesn't exist.
770        return state
771
772    os_req_states = [sub_state for sub_state in req_states
773                     if sub_state.startswith('os_')]
774
775    if len(os_req_states) > 0:
776        # The caller has specified an os_host and they have requested
777        # information on os substates.
778
779        # Based on the information gathered on bmc, we'll try to make a
780        # determination of whether the os is even up.  We'll pass the result
781        # of that assessment to get_os_state to enhance performance.
782        os_up_match = DotDict()
783        for sub_state in master_os_up_match:
784            if sub_state in req_states:
785                os_up_match[sub_state] = master_os_up_match[sub_state]
786        os_up = compare_states(state, os_up_match)
787        os_state = get_os_state(os_host=os_host,
788                                os_username=os_username,
789                                os_password=os_password,
790                                req_states=os_req_states,
791                                os_up=os_up,
792                                quiet=quiet)
793        # Append os_state dictionary to ours.
794        state.update(os_state)
795
796    return state
797
798
799exit_wait_early_message = ""
800
801
802def set_exit_wait_early_message(value):
803    r"""
804    Set global exit_wait_early_message to the indicated value.
805
806    This is a mechanism by which the programmer can do an early exit from
807    wait_until_keyword_succeeds() based on some special condition.
808
809    Description of argument(s):
810    value                           The value to assign to the global
811                                    exit_wait_early_message.
812    """
813
814    global exit_wait_early_message
815    exit_wait_early_message = value
816
817
818def check_state(match_state,
819                invert=0,
820                print_string="",
821                openbmc_host="",
822                openbmc_username="",
823                openbmc_password="",
824                os_host="",
825                os_username="",
826                os_password="",
827                quiet=None):
828    r"""
829    Check that the Open BMC machine's composite state matches the specified
830    state.  On success, this keyword returns the machine's composite state as a
831    dictionary.
832
833    Description of argument(s):
834    match_state       A dictionary whose key/value pairs are "state field"/
835                      "state value".  The state value is interpreted as a
836                      regular expression.  Example call from robot:
837                      ${match_state}=  Create Dictionary  chassis=^On$
838                      ...  bmc=^Ready$
839                      ...  boot_progress=^OSStart$
840                      ${state}=  Check State  &{match_state}
841    invert            If this flag is set, this function will succeed if the
842                      states do NOT match.
843    print_string      This function will print this string to the console prior
844                      to getting the state.
845    openbmc_host      The DNS name or IP address of the BMC.
846                      This defaults to global ${OPENBMC_HOST}.
847    openbmc_username  The username to be used to login to the BMC.
848                      This defaults to global ${OPENBMC_USERNAME}.
849    openbmc_password  The password to be used to login to the BMC.
850                      This defaults to global ${OPENBMC_PASSWORD}.
851    os_host           The DNS name or IP address of the operating system.
852                      This defaults to global ${OS_HOST}.
853    os_username       The username to be used to login to the OS.
854                      This defaults to global ${OS_USERNAME}.
855    os_password       The password to be used to login to the OS.
856                      This defaults to global ${OS_PASSWORD}.
857    quiet             Indicates whether status details should be written to the
858                      console.  Defaults to either global value of ${QUIET} or
859                      to 1.
860    """
861
862    quiet = int(gp.get_var_value(quiet, 0))
863
864    gp.gp_print(print_string)
865
866    try:
867        match_state = return_state_constant(match_state)
868    except TypeError:
869        pass
870
871    req_states = list(match_state.keys())
872    # Remove special-case match key from req_states.
873    if expressions_key() in req_states:
874        req_states.remove(expressions_key())
875    # Initialize state.
876    state = get_state(openbmc_host=openbmc_host,
877                      openbmc_username=openbmc_username,
878                      openbmc_password=openbmc_password,
879                      os_host=os_host,
880                      os_username=os_username,
881                      os_password=os_password,
882                      req_states=req_states,
883                      quiet=quiet)
884    if not quiet:
885        gp.print_var(state)
886
887    if exit_wait_early_message != "":
888        # The exit_wait_early_message has been set by a signal handler so we
889        # will exit "successfully".  It is incumbent upon the calling function
890        # (e.g. wait_state) to check/clear this variable and to fail
891        # appropriately.
892        return state
893
894    match = compare_states(state, match_state)
895
896    if invert and match:
897        fail_msg = "The current state of the machine matches the match" +\
898                   " state:\n" + gp.sprint_varx("state", state)
899        BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
900    elif not invert and not match:
901        fail_msg = "The current state of the machine does NOT match the" +\
902                   " match state:\n" +\
903                   gp.sprint_varx("state", state)
904        BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
905
906    return state
907
908
909def wait_state(match_state=(),
910               wait_time="1 min",
911               interval="1 second",
912               invert=0,
913               openbmc_host="",
914               openbmc_username="",
915               openbmc_password="",
916               os_host="",
917               os_username="",
918               os_password="",
919               quiet=None):
920    r"""
921    Wait for the Open BMC machine's composite state to match the specified
922    state.  On success, this keyword returns the machine's composite state as
923    a dictionary.
924
925    Description of argument(s):
926    match_state       A dictionary whose key/value pairs are "state field"/
927                      "state value".  See check_state (above) for details.
928                      This value may also be any string accepted by
929                      return_state_constant (e.g. "standby_match_state").
930                      In such a case this function will call
931                      return_state_constant to convert it to a proper
932                      dictionary as described above.
933    wait_time         The total amount of time to wait for the desired state.
934                      This value may be expressed in Robot Framework's time
935                      format (e.g. 1 minute, 2 min 3 s, 4.5).
936    interval          The amount of time between state checks.
937                      This value may be expressed in Robot Framework's time
938                      format (e.g. 1 minute, 2 min 3 s, 4.5).
939    invert            If this flag is set, this function will for the state of
940                      the machine to cease to match the match state.
941    openbmc_host      The DNS name or IP address of the BMC.
942                      This defaults to global ${OPENBMC_HOST}.
943    openbmc_username  The username to be used to login to the BMC.
944                      This defaults to global ${OPENBMC_USERNAME}.
945    openbmc_password  The password to be used to login to the BMC.
946                      This defaults to global ${OPENBMC_PASSWORD}.
947    os_host           The DNS name or IP address of the operating system.
948                      This defaults to global ${OS_HOST}.
949    os_username       The username to be used to login to the OS.
950                      This defaults to global ${OS_USERNAME}.
951    os_password       The password to be used to login to the OS.
952                      This defaults to global ${OS_PASSWORD}.
953    quiet             Indicates whether status details should be written to the
954                      console.  Defaults to either global value of ${QUIET} or
955                      to 1.
956    """
957
958    quiet = int(gp.get_var_value(quiet, 0))
959
960    try:
961        match_state = return_state_constant(match_state)
962    except TypeError:
963        pass
964
965    if not quiet:
966        if invert:
967            alt_text = "cease to "
968        else:
969            alt_text = ""
970        gp.print_timen("Checking every " + str(interval) + " for up to "
971                       + str(wait_time) + " for the state of the machine to "
972                       + alt_text + "match the state shown below.")
973        gp.print_var(match_state)
974
975    if quiet:
976        print_string = ""
977    else:
978        print_string = "#"
979
980    debug = int(BuiltIn().get_variable_value("${debug}", "0"))
981    if debug:
982        # In debug we print state so no need to print the "#".
983        print_string = ""
984    check_state_quiet = 1 - debug
985    cmd_buf = ["Check State", match_state, "invert=${" + str(invert) + "}",
986               "print_string=" + print_string, "openbmc_host=" + openbmc_host,
987               "openbmc_username=" + openbmc_username,
988               "openbmc_password=" + openbmc_password, "os_host=" + os_host,
989               "os_username=" + os_username, "os_password=" + os_password,
990               "quiet=${" + str(check_state_quiet) + "}"]
991    gp.dprint_issuing(cmd_buf)
992    try:
993        state = BuiltIn().wait_until_keyword_succeeds(wait_time, interval,
994                                                      *cmd_buf)
995    except AssertionError as my_assertion_error:
996        gp.printn()
997        message = my_assertion_error.args[0]
998        BuiltIn().fail(message)
999
1000    if exit_wait_early_message:
1001        # The global exit_wait_early_message was set by a signal handler
1002        # indicating that we should fail.
1003        message = exit_wait_early_message
1004        # Clear the exit_wait_early_message variable for future use.
1005        set_exit_wait_early_message("")
1006        BuiltIn().fail(gp.sprint_error(message))
1007
1008    if not quiet:
1009        gp.printn()
1010        if invert:
1011            gp.print_timen("The states no longer match:")
1012        else:
1013            gp.print_timen("The states match:")
1014        gp.print_var(state)
1015
1016    return state
1017
1018
1019def set_start_boot_seconds(value=0):
1020    global start_boot_seconds
1021    start_boot_seconds = int(value)
1022
1023
1024set_start_boot_seconds(0)
1025
1026
1027def wait_for_comm_cycle(start_boot_seconds,
1028                        quiet=None):
1029    r"""
1030    Wait for the BMC uptime to be less than elapsed_boot_time.
1031
1032    This function will tolerate an expected loss of communication to the BMC.
1033    This function is useful when some kind of reboot has been initiated by the
1034    caller.
1035
1036    Description of argument(s):
1037    start_boot_seconds  The time that the boot test started.  The format is the
1038                        epoch time in seconds, i.e. the number of seconds since
1039                        1970-01-01 00:00:00 UTC.  This value should be obtained
1040                        from the BMC so that it is not dependent on any kind of
1041                        synchronization between this machine and the target BMC
1042                        This will allow this program to work correctly even in
1043                        a simulated environment.  This value should be obtained
1044                        by the caller prior to initiating a reboot.  It can be
1045                        obtained as follows:
1046                        state = st.get_state(req_states=['epoch_seconds'])
1047    """
1048
1049    quiet = int(gp.get_var_value(quiet, 0))
1050
1051    # Validate parms.
1052    error_message = gv.valid_integer(start_boot_seconds)
1053    if error_message:
1054        BuiltIn().fail(gp.sprint_error(error_message))
1055
1056    # Wait for uptime to be less than elapsed_boot_time.
1057    set_start_boot_seconds(start_boot_seconds)
1058    expr = 'int(float(state[\'uptime\'])) < int(state[\'elapsed_boot_time\'])'
1059    match_state = DotDict([('uptime', '^[0-9\\.]+$'),
1060                           ('elapsed_boot_time', '^[0-9]+$'),
1061                           (expressions_key(), [expr])])
1062    wait_state(match_state, wait_time="12 mins", interval="5 seconds")
1063
1064    gp.qprint_timen("Verifying that REST/Redfish API interface is working.")
1065    if not redfish_support_trans_state:
1066        match_state = DotDict([('rest', '^1$')])
1067    else:
1068        match_state = DotDict([('redfish', '^1$')])
1069    state = wait_state(match_state, wait_time="5 mins", interval="2 seconds")
1070