diff --git a/configure.ac b/configure.ac index 432d7575d..4c3f7fcb7 100644 --- a/configure.ac +++ b/configure.ac @@ -167,6 +167,7 @@ AC_PYTHON_MODULE(pywsman, 1) ## path to 3rd-party binaries AC_PATH_PROG([IPMITOOL_PATH], [ipmitool], [/usr/bin/ipmitool]) +AC_PATH_PROG([OPENSTACK_PATH], [openstack], [/usr/bin/openstack]) AC_PATH_PROG([AMTTOOL_PATH], [amttool], [/usr/bin/amttool]) AC_PATH_PROG([GNUTLSCLI_PATH], [gnutlscli], [/usr/bin/gnutls-cli]) AC_PATH_PROG([COROSYNC_CMAPCTL_PATH], [corosync-cmapctl], [/usr/sbin/corosync-cmapctl]) @@ -210,6 +211,7 @@ AC_SUBST([AGENTS_LIST]) AM_CONDITIONAL(BUILD_XENAPILIB, test $XENAPILIB -eq 1) AC_SUBST([IPMITOOL_PATH]) +AC_SUBST([OPENSTACK_PATH]) AC_SUBST([AMTTOOL_PATH]) AC_SUBST([COROSYNC_CMAPCTL_PATH]) AC_SUBST([SG_PERSIST_PATH]) diff --git a/fence/agents/ironic/fence_ironic.py b/fence/agents/ironic/fence_ironic.py new file mode 100644 index 000000000..8e5d5b0f3 --- /dev/null +++ b/fence/agents/ironic/fence_ironic.py @@ -0,0 +1,136 @@ +#!@PYTHON@ -tt + +import atexit +import logging +import os +import re +import sys +from pipes import quote +sys.path.append("@FENCEAGENTSLIBDIR@") +from fencing import * +from fencing import fail_usage, is_executable, run_command, run_delay + +#BEGIN_VERSION_GENERATION +RELEASE_VERSION="" +REDHAT_COPYRIGHT="" +BUILD_DATE="" +#END_VERSION_GENERATION + +def get_name_or_uuid(options): + return options["--uuid"] if "--uuid" in options else options["--plug"] + +def get_power_status(_, options): + output = ironic_run_command(options, "status") + stdout = output[1] + match = re.search('power[\\s]*([a-zA-Z]{2,3})', str(stdout)) + status = match.group(1) if match else None + return status + +def set_power_status(_, options): + ironic_run_command(options, options["--action"]) + return + +def get_devices_list(_, options): + nodes = {} + output = ironic_run_command(options, "list") + stdout = output[1] + for line in stdout.splitlines(): + uuid = "UUID" + try: + (uuid, name, state) = line.split(',') + except ValueError: + pass + if "UUID" in uuid: + continue # skip line header + match = re.search('power[\\s]*([a-zA-Z]{2,3})', state) + status = match.group(1) if match else None + nodes[uuid] = (name, status) + + return nodes + +def ironic_run_command(options, action, timeout=None): + cmd = options["--openstack-path"] + " baremetal" + env = os.environ.copy() + # --username / -l + if "--username" in options and len(options["--username"]) != 0: + env["OS_USERNAME"] = options["--username"] + + # --password / -p + if "--password" in options: + env["OS_PASSWORD"] = options["--password"] + + # --tenant-name -t + if "--tenant-name" in options: + env["OS_TENANT_NAME"] = options["--tenant-name"] + + # --auth-url + if "--auth-url" in options: + env["OS_AUTH_URL"] = options["--auth-url"] + + # --action / -o + if action == "status": + cmd += " show %s -c power_state --format value" % (get_name_or_uuid(options)) + elif action in ["on", "off"]: + cmd += " power %s %s" % (action, get_name_or_uuid(options)) + elif action == "list": + cmd += " list -c 'Instance UUID' -c Name -c 'Power State' --format csv --quote minimal" + + + logging.debug("cmd -> %s" % cmd) + return run_command(options, cmd, timeout, env) + +def define_new_opts(): + all_opt["auth-url"] = { + "getopt" : ":", + "longopt" : "auth-url", + "help" : "--auth-url=[authurl] Auth URL", + "required" : "1", + "shortdesc" : "Keystone Admin Auth URL", + "order": 1 + } + all_opt["tenant-name"] = { + "getopt" : "t:", + "longopt" : "tenant-name", + "help" : "-t, --tenant-name=[tenant] Tenantname", + "required" : "0", + "shortdesc" : "Keystone Admin Tenant", + "default": "admin", + "order": 1 + } + all_opt["openstack-path"] = { + "getopt" : ":", + "longopt" : "openstack-path", + "help" : "--openstack-path=[path] Path to openstack binary", + "required" : "0", + "shortdesc" : "Path to the OpenStack binary", + "default" : "@OPENSTACK_PATH@", + "order": 200 + } + +def main(): + atexit.register(atexit_handler) + + device_opt = ["login", "passwd", "port", "auth-url", "tenant-name", "openstack-path"] + define_new_opts() + + options = check_input(device_opt, process_input(device_opt)) + + docs = {} + docs["shortdesc"] = "Fence agent for OpenStack's Ironic (Bare Metal as a service) service" + docs["longdesc"] = "fence_ironic is a Fencing agent \ +which can be used with machines controlled by the Ironic service. \ +This agent calls the openstack CLI. \ +WARNING! This fence agent is not intended for production use. Relying on a functional ironic service for fencing is not a good design choice." + docs["vendorurl"] = "https://wiki.openstack.org/wiki/Ironic" + show_docs(options, docs) + + run_delay(options) + + if not is_executable(options["--openstack-path"]): + fail_usage("openstack tool not found or not accessible") + + result = fence_action(None, options, set_power_status, get_power_status, get_devices_list) + sys.exit(result) + +if __name__ == "__main__": + main() diff --git a/make/fencebuild.mk b/make/fencebuild.mk index 87e2c574b..f80a58815 100644 --- a/make/fencebuild.mk +++ b/make/fencebuild.mk @@ -11,6 +11,7 @@ define gen_agent_from_py -e 's#@''SBINDIR@#${sbindir}#g' \ -e 's#@''LIBEXECDIR@#${libexecdir}#g' \ -e 's#@''IPMITOOL_PATH@#${IPMITOOL_PATH}#g' \ + -e 's#@''OPENSTACK_PATH@#${OPENSTACK_PATH}#g' \ -e 's#@''AMTTOOL_PATH@#${AMTTOOL_PATH}#g' \ -e 's#@''GNUTLSCLI_PATH@#${GNUTLSCLI_PATH}#g' \ -e 's#@''COROSYNC_CMAPCTL_PATH@#${COROSYNC_CMAPCTL_PATH}#g' \ diff --git a/tests/data/metadata/fence_ironic.xml b/tests/data/metadata/fence_ironic.xml new file mode 100644 index 000000000..5a1847060 --- /dev/null +++ b/tests/data/metadata/fence_ironic.xml @@ -0,0 +1,113 @@ + + +fence_ironic is a Fencing agent which can be used with machines controlled by the Ironic service. This agent calls the openstack CLI. WARNING! This fence agent is not intended for production use. Relying on a functional ironic service for fencing is not a good design choice. +https://wiki.openstack.org/wiki/Ironic + + + + + Fencing action + + + + + Keystone Admin Auth URL + + + + + Login name + + + + + Login password or passphrase + + + + + Script to run to retrieve password + + + + + Physical plug number on device, UUID or identification of machine + + + + + Keystone Admin Tenant + + + + + Verbose mode + + + + + Write debug information to given file + + + + + Display version information and exit + + + + + Display help and exit + + + + + Separator for CSV created by 'list' operation + + + + + Wait X seconds before fencing is started + + + + + Wait X seconds for cmd prompt after login + + + + + Path to the OpenStack binary + + + + + Test X seconds for status change after ON/OFF + + + + + Wait X seconds after issuing ON/OFF + + + + + Wait X seconds for cmd prompt after issuing command + + + + + Count of attempts to retry power on + + + + + + + + + + + + + +