New python machine state functions.
Change-Id: Ibe051099fcac6e358433a51b890b077988ad96bf
Signed-off-by: Michael Walsh <micwalsh@us.ibm.com>
diff --git a/lib/state.py b/lib/state.py
new file mode 100755
index 0000000..029ed7d
--- /dev/null
+++ b/lib/state.py
@@ -0,0 +1,476 @@
+#!/usr/bin/env python
+
+r"""
+This module contains functions having to do with machine state: get_state,
+check_state, wait_state, etc.
+
+The 'State' is a composite of many pieces of data. Therefore, the functions
+in this module define state as an ordered dictionary. Here is an example of
+some test output showing machine state:
+
+state:
+ state[power]: 1
+ state[bmc]: HOST_BOOTED
+ state[boot_progress]: FW Progress, Starting OS
+ state[os_ping]: 1
+ state[os_login]: 1
+ state[os_run_cmd]: 1
+
+Different users may very well have different needs when inquiring about
+state. In the future, we can add code to allow a user to specify which
+pieces of info they need in the state dictionary. Examples of such data
+might include uptime, state timestamps, boot side, etc.
+
+By using the wait_state function, a caller can start a boot and then wait for
+a precisely defined state to indicate that the boot has succeeded. If
+the boot fails, they can see exactly why by looking at the current state as
+compared with the expected state.
+"""
+
+import gen_print as gp
+import gen_robot_print as grp
+import gen_valid as gv
+
+import commands
+from robot.libraries.BuiltIn import BuiltIn
+
+import re
+
+# We need utils.robot to get keywords like "Get Power State".
+BuiltIn().import_resource("utils.robot")
+
+
+###############################################################################
+def anchor_state(state):
+
+ r"""
+ Add regular expression anchors ("^" and "$") to the beginning and end of
+ each item in the state dictionary passed in. Return the resulting
+ dictionary.
+
+ Description of Arguments:
+ state A dictionary such as the one returned by the get_state()
+ function.
+ """
+
+ anchored_state = state
+ for key, match_state_value in anchored_state.items():
+ anchored_state[key] = "^" + str(anchored_state[key]) + "$"
+
+ return anchored_state
+
+###############################################################################
+
+
+###############################################################################
+def compare_states(state,
+ match_state):
+
+ r"""
+ Compare 2 state dictionaries. Return True if the match and False if they
+ don't. Note that the match_state dictionary does not need to have an entry
+ corresponding to each entry in the state dictionary. But for each entry
+ that it does have, the corresponding state entry will be checked for a
+ match.
+
+ Description of arguments:
+ state A state dictionary such as the one returned by the
+ get_state function.
+ match_state A dictionary whose key/value pairs are "state field"/
+ "state value". The state value is interpreted as a
+ regular expression. Every value in this dictionary is
+ considered. If each and every one matches, the 2
+ dictionaries are considered to be matching.
+ """
+
+ match = True
+ for key, match_state_value in match_state.items():
+ try:
+ if not re.match(match_state_value, str(state[key])):
+ match = False
+ break
+ except KeyError:
+ match = False
+ break
+
+ return match
+
+###############################################################################
+
+
+###############################################################################
+def get_os_state(os_host="",
+ os_username="",
+ os_password="",
+ quiet=None):
+
+ r"""
+ Get component states for the operating system such as ping, login,
+ etc, put them into a dictionary and return them to the caller.
+
+ Description of arguments:
+ os_host The DNS name or IP address of the operating system.
+ This defaults to global ${OS_HOST}.
+ os_username The username to be used to login to the OS.
+ This defaults to global ${OS_USERNAME}.
+ os_password The password to be used to login to the OS.
+ This defaults to global ${OS_PASSWORD}.
+ quiet Indicates whether status details (e.g. curl commands) should
+ be written to the console.
+ Defaults to either global value of ${QUIET} or to 1.
+ """
+
+ quiet = grp.set_quiet_default(quiet, 1)
+
+ # Set parm defaults where necessary and validate all parms.
+ if os_host == "":
+ os_host = BuiltIn().get_variable_value("${OS_HOST}")
+ error_message = gv.svalid_value(os_host, var_name="os_host",
+ invalid_values=[None, ""])
+ if error_message != "":
+ BuiltIn().fail(gp.sprint_error(error_message))
+
+ if os_username == "":
+ os_username = BuiltIn().get_variable_value("${OS_USERNAME}")
+ error_message = gv.svalid_value(os_username, var_name="os_username",
+ invalid_values=[None, ""])
+ if error_message != "":
+ BuiltIn().fail(gp.sprint_error(error_message))
+
+ if os_password == "":
+ os_password = BuiltIn().get_variable_value("${OS_PASSWORD}")
+ error_message = gv.svalid_value(os_password, var_name="os_password",
+ invalid_values=[None, ""])
+ if error_message != "":
+ BuiltIn().fail(gp.sprint_error(error_message))
+
+ # See if the OS pings.
+ cmd_buf = "ping -c 1 -w 2 " + os_host
+ if not quiet:
+ grp.rpissuing(cmd_buf)
+ rc, out_buf = commands.getstatusoutput(cmd_buf)
+ if rc == 0:
+ pings = 1
+ else:
+ pings = 0
+
+ # Open SSH connection to OS.
+ cmd_buf = ["Open Connection", os_host]
+ if not quiet:
+ grp.rpissuing_keyword(cmd_buf)
+ ix = BuiltIn().run_keyword(*cmd_buf)
+
+ # Login to OS.
+ cmd_buf = ["Login", os_username, os_password]
+ if not quiet:
+ grp.rpissuing_keyword(cmd_buf)
+ status, msg = BuiltIn().run_keyword_and_ignore_error(*cmd_buf)
+
+ if status == "PASS":
+ login = 1
+ else:
+ login = 0
+
+ if login:
+ # Try running a simple command (uptime) on the OS.
+ cmd_buf = ["Execute Command", "uptime", "return_stderr=True",
+ "return_rc=True"]
+ if not quiet:
+ grp.rpissuing_keyword(cmd_buf)
+ output, stderr_buf, rc = BuiltIn().run_keyword(*cmd_buf)
+ if rc == 0 and stderr_buf == "":
+ run_cmd = 1
+ else:
+ run_cmd = 0
+ else:
+ run_cmd = 0
+
+ # Create a dictionary containing the results of the prior commands.
+ cmd_buf = ["Create Dictionary", "ping=${" + str(pings) + "}",
+ "login=${" + str(login) + "}",
+ "run_cmd=${" + str(run_cmd) + "}"]
+ grp.rdpissuing_keyword(cmd_buf)
+ os_state = BuiltIn().run_keyword(*cmd_buf)
+
+ return os_state
+
+###############################################################################
+
+
+###############################################################################
+def get_state(openbmc_host="",
+ openbmc_username="",
+ openbmc_password="",
+ os_host="",
+ os_username="",
+ os_password="",
+ quiet=None):
+
+ r"""
+ Get component states such as power state, bmc state, etc, put them into a
+ dictionary and return them to the caller.
+
+ Description of arguments:
+ openbmc_host The DNS name or IP address of the BMC.
+ This defaults to global ${OPENBMC_HOST}.
+ openbmc_username The username to be used to login to the BMC.
+ This defaults to global ${OPENBMC_USERNAME}.
+ openbmc_password The password to be used to login to the BMC.
+ This defaults to global ${OPENBMC_PASSWORD}.
+ os_host The DNS name or IP address of the operating system.
+ This defaults to global ${OS_HOST}.
+ os_username The username to be used to login to the OS.
+ This defaults to global ${OS_USERNAME}.
+ os_password The password to be used to login to the OS.
+ This defaults to global ${OS_PASSWORD}.
+ quiet Indicates whether status details (e.g. curl commands)
+ should be written to the console.
+ Defaults to either global value of ${QUIET} or to 1.
+ """
+
+ quiet = grp.set_quiet_default(quiet, 1)
+
+ # Set parm defaults where necessary and validate all parms.
+ if openbmc_host == "":
+ openbmc_host = BuiltIn().get_variable_value("${OPENBMC_HOST}")
+ error_message = gv.svalid_value(openbmc_host,
+ var_name="openbmc_host",
+ invalid_values=[None, ""])
+ if error_message != "":
+ BuiltIn().fail(gp.sprint_error(error_message))
+
+ if openbmc_username == "":
+ openbmc_username = BuiltIn().get_variable_value("${OPENBMC_USERNAME}")
+ error_message = gv.svalid_value(openbmc_username,
+ var_name="openbmc_username",
+ invalid_values=[None, ""])
+ if error_message != "":
+ BuiltIn().fail(gp.sprint_error(error_message))
+
+ if openbmc_password == "":
+ openbmc_password = BuiltIn().get_variable_value("${OPENBMC_PASSWORD}")
+ error_message = gv.svalid_value(openbmc_password,
+ var_name="openbmc_password",
+ invalid_values=[None, ""])
+ if error_message != "":
+ BuiltIn().fail(gp.sprint_error(error_message))
+
+ # Set parm defaults where necessary and validate all parms. NOTE: OS parms
+ # are optional.
+ if os_host == "":
+ os_host = BuiltIn().get_variable_value("${OS_HOST}")
+ if os_host is None:
+ os_host = ""
+
+ if os_username is "":
+ os_username = BuiltIn().get_variable_value("${OS_USERNAME}")
+ if os_username is None:
+ os_username = ""
+
+ if os_password is "":
+ os_password = BuiltIn().get_variable_value("${OS_PASSWORD}")
+ if os_password is None:
+ os_password = ""
+
+ # Get the component states.
+ cmd_buf = ["Get Power State", "quiet=${" + str(quiet) + "}"]
+ grp.rdpissuing_keyword(cmd_buf)
+ power = BuiltIn().run_keyword(*cmd_buf)
+
+ cmd_buf = ["Get BMC State", "quiet=${" + str(quiet) + "}"]
+ grp.rdpissuing_keyword(cmd_buf)
+ bmc = BuiltIn().run_keyword(*cmd_buf)
+
+ cmd_buf = ["Get Boot Progress", "quiet=${" + str(quiet) + "}"]
+ grp.rdpissuing_keyword(cmd_buf)
+ boot_progress = BuiltIn().run_keyword(*cmd_buf)
+
+ # Create composite state dictionary.
+ cmd_buf = ["Create Dictionary", "power=${" + str(power) + "}",
+ "bmc=" + bmc, "boot_progress=" + boot_progress]
+ grp.rdpissuing_keyword(cmd_buf)
+ state = BuiltIn().run_keyword(*cmd_buf)
+
+ if os_host != "":
+ # Create an os_up_match dictionary to test whether we are booted enough
+ # to get operating system info.
+ cmd_buf = ["Create Dictionary", "power=^${1}$", "bmc=^HOST_BOOTED$",
+ "boot_progress=^FW Progress, Starting OS$"]
+ grp.rdpissuing_keyword(cmd_buf)
+ os_up_match = BuiltIn().run_keyword(*cmd_buf)
+ os_up = compare_states(state, os_up_match)
+
+ if os_up:
+ # Get OS information...
+ os_state = get_os_state(os_host=os_host,
+ os_username=os_username,
+ os_password=os_password,
+ quiet=quiet)
+ for key, state_value in os_state.items():
+ # Add each OS value to the state dictionary, pre-pending
+ # "os_" to each key.
+ new_key = "os_" + key
+ state[new_key] = state_value
+
+ return state
+
+###############################################################################
+
+
+###############################################################################
+def check_state(match_state,
+ invert=0,
+ print_string="",
+ openbmc_host="",
+ openbmc_username="",
+ openbmc_password="",
+ os_host="",
+ os_username="",
+ os_password="",
+ quiet=None):
+
+ r"""
+ Check that the Open BMC machine's composite state matches the specified
+ state. On success, this keyword returns the machine's composite state as a
+ dictionary.
+
+ Description of arguments:
+ match_state A dictionary whose key/value pairs are "state field"/
+ "state value". The state value is interpreted as a
+ regular expression. Example call from robot:
+ ${match_state}= Create Dictionary power=^1$
+ ... bmc=^HOST_BOOTED$
+ ... boot_progress=^FW Progress, Starting OS$
+ ${state}= Check State &{match_state}
+ invert If this flag is set, this function will succeed if the
+ states do NOT match.
+ print_string This function will print this string to the console prior
+ to getting the state.
+ openbmc_host The DNS name or IP address of the BMC.
+ This defaults to global ${OPENBMC_HOST}.
+ openbmc_username The username to be used to login to the BMC.
+ This defaults to global ${OPENBMC_USERNAME}.
+ openbmc_password The password to be used to login to the BMC.
+ This defaults to global ${OPENBMC_PASSWORD}.
+ os_host The DNS name or IP address of the operating system.
+ This defaults to global ${OS_HOST}.
+ os_username The username to be used to login to the OS.
+ This defaults to global ${OS_USERNAME}.
+ os_password The password to be used to login to the OS.
+ This defaults to global ${OS_PASSWORD}.
+ quiet Indicates whether status details should be written to the
+ console. Defaults to either global value of ${QUIET} or
+ to 1.
+ """
+
+ quiet = grp.set_quiet_default(quiet, 1)
+
+ grp.rprint(print_string)
+
+ # Initialize state.
+ state = get_state(openbmc_host=openbmc_host,
+ openbmc_username=openbmc_username,
+ openbmc_password=openbmc_password,
+ os_host=os_host,
+ os_username=os_username,
+ os_password=os_password,
+ quiet=quiet)
+ if not quiet:
+ grp.rprint_var(state)
+
+ match = compare_states(state, match_state)
+
+ if invert and match:
+ fail_msg = "The current state of the machine matches the match" +\
+ " state:\n" + gp.sprint_varx("state", state)
+ BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
+ elif not invert and not match:
+ fail_msg = "The current state of the machine does NOT match the" +\
+ " match state:\n" +\
+ gp.sprint_varx("state", state)
+ BuiltIn().fail("\n" + gp.sprint_error(fail_msg))
+
+ return state
+
+###############################################################################
+
+
+###############################################################################
+def wait_state(match_state,
+ wait_time="1 min",
+ interval="1 second",
+ invert=0,
+ openbmc_host="",
+ openbmc_username="",
+ openbmc_password="",
+ os_host="",
+ os_username="",
+ os_password="",
+ quiet=None):
+
+ r"""
+ Wait for the Open BMC machine's composite state to match the specified
+ state. On success, this keyword returns the machine's composite state as
+ a dictionary.
+
+ Description of arguments:
+ match_state A dictionary whose key/value pairs are "state field"/
+ "state value". See check_state (above) for details.
+ wait_time The total amount of time to wait for the desired state.
+ This value may be expressed in Robot Framework's time
+ format (e.g. 1 minute, 2 min 3 s, 4.5).
+ interval The amount of time between state checks.
+ This value may be expressed in Robot Framework's time
+ format (e.g. 1 minute, 2 min 3 s, 4.5).
+ invert If this flag is set, this function will for the state of
+ the machine to cease to match the match state.
+ openbmc_host The DNS name or IP address of the BMC.
+ This defaults to global ${OPENBMC_HOST}.
+ openbmc_username The username to be used to login to the BMC.
+ This defaults to global ${OPENBMC_USERNAME}.
+ openbmc_password The password to be used to login to the BMC.
+ This defaults to global ${OPENBMC_PASSWORD}.
+ os_host The DNS name or IP address of the operating system.
+ This defaults to global ${OS_HOST}.
+ os_username The username to be used to login to the OS.
+ This defaults to global ${OS_USERNAME}.
+ os_password The password to be used to login to the OS.
+ This defaults to global ${OS_PASSWORD}.
+ quiet Indicates whether status details should be written to the
+ console. Defaults to either global value of ${QUIET} or
+ to 1.
+ """
+
+ quiet = grp.set_quiet_default(quiet, 1)
+
+ if not quiet:
+ if invert:
+ alt_text = "cease to "
+ else:
+ alt_text = ""
+ grp.rprint_timen("Checking every " + str(interval) + " for up to " +
+ str(wait_time) + " for the state of the machine to " +
+ alt_text + "match the state shown below.")
+ grp.rprint_var(match_state)
+
+ cmd_buf = ["Check State", match_state, "invert=${" + str(invert) + "}",
+ "print_string=#", "openbmc_host=" + openbmc_host,
+ "openbmc_username=" + openbmc_username,
+ "openbmc_password=" + openbmc_password, "os_host=" + os_host,
+ "os_username=" + os_username, "os_password=" + os_password,
+ "quiet=${1}"]
+ grp.rdpissuing_keyword(cmd_buf)
+ state = BuiltIn().wait_until_keyword_succeeds(wait_time, interval,
+ *cmd_buf)
+
+ if not quiet:
+ grp.rprintn()
+ if invert:
+ grp.rprint_timen("The states no longer match:")
+ else:
+ grp.rprint_timen("The states match:")
+ grp.rprint_var(state)
+
+ return state
+
+###############################################################################