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            try:
733                status, ret_values = \
734                    BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
735            except Exception as ex:
736                # Robot raised UserKeywordExecutionFailed error exception.
737                gp.dprint_issuing("Retrying Redfish Get States")
738                status, ret_values = \
739                    BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
740
741            gp.dprint_vars(status, ret_values)
742            if status == "PASS":
743                state['redfish'] = '1'
744            else:
745                state['redfish'] = '0'
746
747            if int(state['redfish']):
748                state['chassis'] = ret_values['chassis']
749                state['boot_progress'] = ret_values['boot_progress']
750                state['host'] = ret_values['host']
751                state['bmc'] = ret_values['bmc']
752
753    for sub_state in req_states:
754        if sub_state in state:
755            continue
756        if sub_state.startswith("os_"):
757            # We pass "os_" requests on to get_os_state.
758            continue
759        cmd_buf = "state['" + sub_state + "'] = str(" + sub_state + ")"
760        exec(cmd_buf)
761
762    if os_host == "":
763        # The caller has not specified an os_host so as far as we're concerned,
764        # it doesn't exist.
765        return state
766
767    os_req_states = [sub_state for sub_state in req_states
768                     if sub_state.startswith('os_')]
769
770    if len(os_req_states) > 0:
771        # The caller has specified an os_host and they have requested
772        # information on os substates.
773
774        # Based on the information gathered on bmc, we'll try to make a
775        # determination of whether the os is even up.  We'll pass the result
776        # of that assessment to get_os_state to enhance performance.
777        os_up_match = DotDict()
778        for sub_state in master_os_up_match:
779            if sub_state in req_states:
780                os_up_match[sub_state] = master_os_up_match[sub_state]
781        os_up = compare_states(state, os_up_match)
782        os_state = get_os_state(os_host=os_host,
783                                os_username=os_username,
784                                os_password=os_password,
785                                req_states=os_req_states,
786                                os_up=os_up,
787                                quiet=quiet)
788        # Append os_state dictionary to ours.
789        state.update(os_state)
790
791    return state
792
793
794exit_wait_early_message = ""
795
796
797def set_exit_wait_early_message(value):
798    r"""
799    Set global exit_wait_early_message to the indicated value.
800
801    This is a mechanism by which the programmer can do an early exit from
802    wait_until_keyword_succeeds() based on some special condition.
803
804    Description of argument(s):
805    value                           The value to assign to the global
806                                    exit_wait_early_message.
807    """
808
809    global exit_wait_early_message
810    exit_wait_early_message = value
811
812
813def check_state(match_state,
814                invert=0,
815                print_string="",
816                openbmc_host="",
817                openbmc_username="",
818                openbmc_password="",
819                os_host="",
820                os_username="",
821                os_password="",
822                quiet=None):
823    r"""
824    Check that the Open BMC machine's composite state matches the specified
825    state.  On success, this keyword returns the machine's composite state as a
826    dictionary.
827
828    Description of argument(s):
829    match_state       A dictionary whose key/value pairs are "state field"/
830                      "state value".  The state value is interpreted as a
831                      regular expression.  Example call from robot:
832                      ${match_state}=  Create Dictionary  chassis=^On$
833                      ...  bmc=^Ready$
834                      ...  boot_progress=^OSStart$
835                      ${state}=  Check State  &{match_state}
836    invert            If this flag is set, this function will succeed if the
837                      states do NOT match.
838    print_string      This function will print this string to the console prior
839                      to getting the state.
840    openbmc_host      The DNS name or IP address of the BMC.
841                      This defaults to global ${OPENBMC_HOST}.
842    openbmc_username  The username to be used to login to the BMC.
843                      This defaults to global ${OPENBMC_USERNAME}.
844    openbmc_password  The password to be used to login to the BMC.
845                      This defaults to global ${OPENBMC_PASSWORD}.
846    os_host           The DNS name or IP address of the operating system.
847                      This defaults to global ${OS_HOST}.
848    os_username       The username to be used to login to the OS.
849                      This defaults to global ${OS_USERNAME}.
850    os_password       The password to be used to login to the OS.
851                      This defaults to global ${OS_PASSWORD}.
852    quiet             Indicates whether status details should be written to the
853                      console.  Defaults to either global value of ${QUIET} or
854                      to 1.
855    """
856
857    quiet = int(gp.get_var_value(quiet, 0))
858
859    gp.gp_print(print_string)
860
861    try:
862        match_state = return_state_constant(match_state)
863    except TypeError:
864        pass
865
866    req_states = list(match_state.keys())
867    # Remove special-case match key from req_states.
868    if expressions_key() in req_states:
869        req_states.remove(expressions_key())
870    # Initialize state.
871    state = get_state(openbmc_host=openbmc_host,
872                      openbmc_username=openbmc_username,
873                      openbmc_password=openbmc_password,
874                      os_host=os_host,
875                      os_username=os_username,
876                      os_password=os_password,
877                      req_states=req_states,
878                      quiet=quiet)
879    if not quiet:
880        gp.print_var(state)
881
882    if exit_wait_early_message != "":
883        # The exit_wait_early_message has been set by a signal handler so we
884        # will exit "successfully".  It is incumbent upon the calling function
885        # (e.g. wait_state) to check/clear this variable and to fail
886        # appropriately.
887        return state
888
889    match = compare_states(state, match_state)
890
891    if invert and match:
892        fail_msg = "The current state of the machine matches the match" +\
893                   " state:\n" + gp.sprint_varx("state", state)
894        BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
895    elif not invert and not match:
896        fail_msg = "The current state of the machine does NOT match the" +\
897                   " match state:\n" +\
898                   gp.sprint_varx("state", state)
899        BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
900
901    return state
902
903
904def wait_state(match_state=(),
905               wait_time="1 min",
906               interval="1 second",
907               invert=0,
908               openbmc_host="",
909               openbmc_username="",
910               openbmc_password="",
911               os_host="",
912               os_username="",
913               os_password="",
914               quiet=None):
915    r"""
916    Wait for the Open BMC machine's composite state to match the specified
917    state.  On success, this keyword returns the machine's composite state as
918    a dictionary.
919
920    Description of argument(s):
921    match_state       A dictionary whose key/value pairs are "state field"/
922                      "state value".  See check_state (above) for details.
923                      This value may also be any string accepted by
924                      return_state_constant (e.g. "standby_match_state").
925                      In such a case this function will call
926                      return_state_constant to convert it to a proper
927                      dictionary as described above.
928    wait_time         The total amount of time to wait for the desired state.
929                      This value may be expressed in Robot Framework's time
930                      format (e.g. 1 minute, 2 min 3 s, 4.5).
931    interval          The amount of time between state checks.
932                      This value may be expressed in Robot Framework's time
933                      format (e.g. 1 minute, 2 min 3 s, 4.5).
934    invert            If this flag is set, this function will for the state of
935                      the machine to cease to match the match state.
936    openbmc_host      The DNS name or IP address of the BMC.
937                      This defaults to global ${OPENBMC_HOST}.
938    openbmc_username  The username to be used to login to the BMC.
939                      This defaults to global ${OPENBMC_USERNAME}.
940    openbmc_password  The password to be used to login to the BMC.
941                      This defaults to global ${OPENBMC_PASSWORD}.
942    os_host           The DNS name or IP address of the operating system.
943                      This defaults to global ${OS_HOST}.
944    os_username       The username to be used to login to the OS.
945                      This defaults to global ${OS_USERNAME}.
946    os_password       The password to be used to login to the OS.
947                      This defaults to global ${OS_PASSWORD}.
948    quiet             Indicates whether status details should be written to the
949                      console.  Defaults to either global value of ${QUIET} or
950                      to 1.
951    """
952
953    quiet = int(gp.get_var_value(quiet, 0))
954
955    try:
956        match_state = return_state_constant(match_state)
957    except TypeError:
958        pass
959
960    if not quiet:
961        if invert:
962            alt_text = "cease to "
963        else:
964            alt_text = ""
965        gp.print_timen("Checking every " + str(interval) + " for up to "
966                       + str(wait_time) + " for the state of the machine to "
967                       + alt_text + "match the state shown below.")
968        gp.print_var(match_state)
969
970    if quiet:
971        print_string = ""
972    else:
973        print_string = "#"
974
975    debug = int(BuiltIn().get_variable_value("${debug}", "0"))
976    if debug:
977        # In debug we print state so no need to print the "#".
978        print_string = ""
979    check_state_quiet = 1 - debug
980    cmd_buf = ["Check State", match_state, "invert=${" + str(invert) + "}",
981               "print_string=" + print_string, "openbmc_host=" + openbmc_host,
982               "openbmc_username=" + openbmc_username,
983               "openbmc_password=" + openbmc_password, "os_host=" + os_host,
984               "os_username=" + os_username, "os_password=" + os_password,
985               "quiet=${" + str(check_state_quiet) + "}"]
986    gp.dprint_issuing(cmd_buf)
987    try:
988        state = BuiltIn().wait_until_keyword_succeeds(wait_time, interval,
989                                                      *cmd_buf)
990    except AssertionError as my_assertion_error:
991        gp.printn()
992        message = my_assertion_error.args[0]
993        BuiltIn().fail(message)
994
995    if exit_wait_early_message:
996        # The global exit_wait_early_message was set by a signal handler
997        # indicating that we should fail.
998        message = exit_wait_early_message
999        # Clear the exit_wait_early_message variable for future use.
1000        set_exit_wait_early_message("")
1001        BuiltIn().fail(gp.sprint_error(message))
1002
1003    if not quiet:
1004        gp.printn()
1005        if invert:
1006            gp.print_timen("The states no longer match:")
1007        else:
1008            gp.print_timen("The states match:")
1009        gp.print_var(state)
1010
1011    return state
1012
1013
1014def set_start_boot_seconds(value=0):
1015    global start_boot_seconds
1016    start_boot_seconds = int(value)
1017
1018
1019set_start_boot_seconds(0)
1020
1021
1022def wait_for_comm_cycle(start_boot_seconds,
1023                        quiet=None):
1024    r"""
1025    Wait for the BMC uptime to be less than elapsed_boot_time.
1026
1027    This function will tolerate an expected loss of communication to the BMC.
1028    This function is useful when some kind of reboot has been initiated by the
1029    caller.
1030
1031    Description of argument(s):
1032    start_boot_seconds  The time that the boot test started.  The format is the
1033                        epoch time in seconds, i.e. the number of seconds since
1034                        1970-01-01 00:00:00 UTC.  This value should be obtained
1035                        from the BMC so that it is not dependent on any kind of
1036                        synchronization between this machine and the target BMC
1037                        This will allow this program to work correctly even in
1038                        a simulated environment.  This value should be obtained
1039                        by the caller prior to initiating a reboot.  It can be
1040                        obtained as follows:
1041                        state = st.get_state(req_states=['epoch_seconds'])
1042    """
1043
1044    quiet = int(gp.get_var_value(quiet, 0))
1045
1046    # Validate parms.
1047    error_message = gv.valid_integer(start_boot_seconds)
1048    if error_message:
1049        BuiltIn().fail(gp.sprint_error(error_message))
1050
1051    # Wait for uptime to be less than elapsed_boot_time.
1052    set_start_boot_seconds(start_boot_seconds)
1053    expr = 'int(float(state[\'uptime\'])) < int(state[\'elapsed_boot_time\'])'
1054    match_state = DotDict([('uptime', '^[0-9\\.]+$'),
1055                           ('elapsed_boot_time', '^[0-9]+$'),
1056                           (expressions_key(), [expr])])
1057    wait_state(match_state, wait_time="12 mins", interval="5 seconds")
1058
1059    gp.qprint_timen("Verifying that REST/Redfish API interface is working.")
1060    if not redfish_support_trans_state:
1061        match_state = DotDict([('rest', '^1$')])
1062    else:
1063        match_state = DotDict([('redfish', '^1$')])
1064    state = wait_state(match_state, wait_time="5 mins", interval="2 seconds")
1065