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