diff --git a/agents/openstack/fence_openstack.py b/agents/openstack/fence_openstack.py index d3a4be3a..666016d7 100644 --- a/agents/openstack/fence_openstack.py +++ b/agents/openstack/fence_openstack.py @@ -1,386 +1,381 @@ #!@PYTHON@ -tt import atexit import logging import sys +import os import urllib3 sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail_usage, run_delay, source_env try: from novaclient import client from novaclient.exceptions import Conflict, NotFound except ImportError: pass urllib3.disable_warnings(urllib3.exceptions.SecurityWarning) def translate_status(instance_status): if instance_status == "ACTIVE": return "on" elif instance_status == "SHUTOFF": return "off" return "unknown" def get_cloud(options): - import os, yaml + import yaml - clouds_yaml = os.path.expanduser("~/.config/openstack/clouds.yaml") + clouds_yaml = "~/.config/openstack/clouds.yaml" + if not os.path.exists(os.path.expanduser(clouds_yaml)): + clouds_yaml = "/etc/openstack/clouds.yaml" + if not os.path.exists(os.path.expanduser(clouds_yaml)): + fail_usage("Failed: ~/.config/openstack/clouds.yaml and /etc/openstack/clouds.yaml does not exist") + + clouds_yaml = os.path.expanduser(clouds_yaml) if os.path.exists(clouds_yaml): with open(clouds_yaml, "r") as yaml_stream: try: clouds = yaml.safe_load(yaml_stream) except yaml.YAMLError as exc: fail_usage("Failed: Unable to read: " + clouds_yaml) cloud = clouds.get("clouds").get(options["--cloud"]) if not cloud: fail_usage("Cloud: {} not found.".format(options["--cloud"])) return cloud def get_nodes_list(conn, options): logging.info("Running %s action", options["--action"]) result = {} response = conn.servers.list(detailed=True) if response is not None: for item in response: instance_id = item.id instance_name = item.name instance_status = item.status result[instance_id] = (instance_name, translate_status(instance_status)) return result def get_power_status(conn, options): logging.info("Running %s action on %s", options["--action"], options["--plug"]) server = None try: server = conn.servers.get(options["--plug"]) except NotFound as e: fail_usage("Failed: Not Found: " + str(e)) if server is None: fail_usage("Server %s not found", options["--plug"]) state = server.status status = translate_status(state) logging.info("get_power_status: %s (state: %s)" % (status, state)) return status def set_power_status(conn, options): logging.info("Running %s action on %s", options["--action"], options["--plug"]) action = options["--action"] server = None try: server = conn.servers.get(options["--plug"]) except NotFound as e: fail_usage("Failed: Not Found: " + str(e)) if server is None: fail_usage("Server %s not found", options["--plug"]) if action == "on": logging.info("Starting instance " + server.name) try: server.start() except Conflict as e: fail_usage(e) logging.info("Called start API call for " + server.id) if action == "off": logging.info("Stopping instance " + server.name) try: server.stop() except Conflict as e: fail_usage(e) logging.info("Called stop API call for " + server.id) if action == "reboot": logging.info("Rebooting instance " + server.name) try: server.reboot("HARD") except Conflict as e: fail_usage(e) logging.info("Called reboot hard API call for " + server.id) def nova_login(username, password, projectname, auth_url, user_domain_name, project_domain_name, ssl_insecure, cacert, apitimeout): legacy_import = False try: from keystoneauth1 import loading from keystoneauth1 import session as ksc_session from keystoneauth1.exceptions.discovery import DiscoveryFailure from keystoneauth1.exceptions.http import Unauthorized except ImportError: try: from keystoneclient import session as ksc_session from keystoneclient.auth.identity import v3 legacy_import = True except ImportError: fail_usage("Failed: Keystone client not found or not accessible") if not legacy_import: loader = loading.get_plugin_loader("password") auth = loader.load_from_options( auth_url=auth_url, username=username, password=password, project_name=projectname, user_domain_name=user_domain_name, project_domain_name=project_domain_name, ) else: auth = v3.Password( auth_url=auth_url, username=username, password=password, project_name=projectname, user_domain_name=user_domain_name, project_domain_name=project_domain_name, cacert=cacert, ) caverify=True if ssl_insecure: caverify=False elif cacert: caverify=cacert session = ksc_session.Session(auth=auth, verify=caverify, timeout=apitimeout) nova = client.Client("2", session=session, timeout=apitimeout) apiversion = None try: apiversion = nova.versions.get_current() except DiscoveryFailure as e: fail_usage("Failed: Discovery Failure: " + str(e)) except Unauthorized as e: fail_usage("Failed: Unauthorized: " + str(e)) except Exception as e: logging.error(e) logging.debug("Nova version: %s", apiversion) return nova def define_new_opts(): all_opt["auth-url"] = { "getopt": ":", "longopt": "auth-url", "help": "--auth-url=[authurl] Keystone Auth URL", "required": "0", "shortdesc": "Keystone Auth URL", "order": 2, } all_opt["project-name"] = { "getopt": ":", "longopt": "project-name", "help": "--project-name=[project] Tenant Or Project Name", "required": "0", "shortdesc": "Keystone Project", "default": "admin", "order": 3, } all_opt["user-domain-name"] = { "getopt": ":", "longopt": "user-domain-name", "help": "--user-domain-name=[domain] Keystone User Domain Name", "required": "0", "shortdesc": "Keystone User Domain Name", "default": "Default", "order": 4, } all_opt["project-domain-name"] = { "getopt": ":", "longopt": "project-domain-name", "help": "--project-domain-name=[domain] Keystone Project Domain Name", "required": "0", "shortdesc": "Keystone Project Domain Name", "default": "Default", "order": 5, } - all_opt["clouds-yaml"] = { - "getopt": ":", - "longopt": "clouds-yaml", - "help": "--clouds-yaml=[clouds-yaml] Path to the clouds.yaml config file", - "required": "0", - "shortdesc": "clouds.yaml config file", - "default": "~/.config/openstack/clouds.yaml", - "order": 6, - } all_opt["cloud"] = { "getopt": ":", "longopt": "cloud", - "help": "--cloud=[cloud] Openstack cloud (from clouds.yaml).", + "help": "--cloud=[cloud] Openstack cloud (from ~/.config/openstack/clouds.yaml or /etc/openstack/clouds.yaml).", "required": "0", "shortdesc": "Cloud from clouds.yaml", - "order": 7, + "order": 6, } all_opt["openrc"] = { "getopt": ":", "longopt": "openrc", "help": "--openrc=[openrc] Path to the openrc config file", "required": "0", "shortdesc": "openrc config file", - "order": 8, + "order": 7, } all_opt["uuid"] = { "getopt": ":", "longopt": "uuid", "help": "--uuid=[uuid] Replaced by -n, --plug", "required": "0", "shortdesc": "Replaced by port/-n/--plug", - "order": 9, + "order": 8, } all_opt["cacert"] = { "getopt": ":", "longopt": "cacert", "help": "--cacert=[cacert] Path to the PEM file with trusted authority certificates (override global CA trust)", "required": "0", "shortdesc": "SSL X.509 certificates file", "default": "", - "order": 10, + "order": 9, } all_opt["apitimeout"] = { "getopt": ":", "type": "second", "longopt": "apitimeout", "help": "--apitimeout=[seconds] Timeout to use for API calls", "shortdesc": "Timeout in seconds to use for API calls, default is 60.", "required": "0", "default": 60, - "order": 11, + "order": 10, } def main(): conn = None device_opt = [ "login", "no_login", "passwd", "no_password", "auth-url", "project-name", "user-domain-name", "project-domain-name", - "clouds-yaml", "cloud", "openrc", "port", "no_port", "uuid", "ssl_insecure", "cacert", "apitimeout", ] atexit.register(atexit_handler) define_new_opts() all_opt["port"]["required"] = "0" all_opt["port"]["help"] = "-n, --plug=[UUID] UUID of the node to be fenced" all_opt["port"]["shortdesc"] = "UUID of the node to be fenced." all_opt["power_timeout"]["default"] = "60" options = check_input(device_opt, process_input(device_opt)) # workaround to avoid regressions if "--uuid" in options: options["--plug"] = options["--uuid"] del options["--uuid"] elif ("--help" not in options and options["--action"] in ["off", "on", "reboot", "status", "validate-all"] and "--plug" not in options): stop_after_error = False if options["--action"] == "validate-all" else True fail_usage( "Failed: You have to enter plug number or machine identification", stop_after_error, ) docs = {} docs["shortdesc"] = "Fence agent for OpenStack's Nova service" docs["longdesc"] = "fence_openstack is a Fencing agent \ which can be used with machines controlled by the Openstack's Nova service. \ This agent calls the python-novaclient and it is mandatory to be installed " docs["vendorurl"] = "https://wiki.openstack.org/wiki/Nova" show_docs(options, docs) run_delay(options) - if options.get("--clouds-yaml"): - if not os.path.exists(os.path.expanduser(options["--clouds-yaml"])): - fail_usage("Failed: {} does not exist".format(options.get("--clouds-yaml"))) - if not options.get("--cloud"): - fail_usage("Failed: \"cloud\" not specified") + if options.get("--cloud"): cloud = get_cloud(options) - username = cloud.get("username") - password = cloud.get("password") - projectname = cloud.get("project_name") + username = cloud.get("auth").get("username") + password = cloud.get("auth").get("password") + projectname = cloud.get("auth").get("project_name") auth_url = None try: - auth_url = cloud.get("auth_url") + auth_url = cloud.get("auth").get("auth_url") except KeyError: fail_usage("Failed: You have to set the Keystone service endpoint for authorization") - user_domain_name = cloud.get("user_domain_name") - project_domain_name = cloud.get("project_domain_name") + user_domain_name = cloud.get("auth").get("user_domain_name") + project_domain_name = cloud.get("auth").get("project_domain_name") caverify = cloud.get("verify") if caverify in [True, False]: options["--ssl-insecure"] = caverify else: options["--cacert"] = caverify - if options.get("--openrc") and os.path.exists(os.path.expanduser(options["--openrc"])): + elif options.get("--openrc"): + if not os.path.exists(os.path.expanduser(options["--openrc"])): + fail_usage("Failed: {} does not exist".format(options.get("--openrc"))) source_env(options["--openrc"]) env = os.environ username = env.get("OS_USERNAME") password = env.get("OS_PASSWORD") projectname = env.get("OS_PROJECT_NAME") auth_url = None try: auth_url = env["OS_AUTH_URL"] except KeyError: fail_usage("Failed: You have to set the Keystone service endpoint for authorization") user_domain_name = env.get("OS_USER_DOMAIN_NAME") project_domain_name = env.get("OS_PROJECT_DOMAIN_NAME") else: username = options["--username"] password = options["--password"] projectname = options["--project-name"] auth_url = None try: auth_url = options["--auth-url"] except KeyError: fail_usage("Failed: You have to set the Keystone service endpoint for authorization") user_domain_name = options["--user-domain-name"] project_domain_name = options["--project-domain-name"] ssl_insecure = "--ssl-insecure" in options cacert = options["--cacert"] apitimeout = options["--apitimeout"] try: conn = nova_login( username, password, projectname, auth_url, user_domain_name, project_domain_name, ssl_insecure, cacert, apitimeout, ) except Exception as e: fail_usage("Failed: Unable to connect to Nova: " + str(e)) # Operate the fencing device result = fence_action(conn, options, set_power_status, get_power_status, get_nodes_list) sys.exit(result) if __name__ == "__main__": main() diff --git a/tests/data/metadata/fence_openstack.xml b/tests/data/metadata/fence_openstack.xml index 55a57b4d..67b2191b 100644 --- a/tests/data/metadata/fence_openstack.xml +++ b/tests/data/metadata/fence_openstack.xml @@ -1,224 +1,214 @@ fence_openstack is a Fencing agent which can be used with machines controlled by the Openstack's Nova service. This agent calls the python-novaclient and it is mandatory to be installed https://wiki.openstack.org/wiki/Nova Fencing action Login name Login password or passphrase Script to run to retrieve password Login password or passphrase Script to run to retrieve password UUID of the node to be fenced. UUID of the node to be fenced. Use SSL connection without verifying certificate Login name Keystone Auth URL Keystone Auth URL Keystone Project Keystone Project Keystone User Domain Name Keystone User Domain Name Keystone Project Domain Name Keystone Project Domain Name - - - - clouds.yaml config file - - - - - clouds.yaml config file - Cloud from clouds.yaml openrc config file Replaced by port/-n/--plug SSL X.509 certificates file Timeout in seconds to use for API calls, default is 60. Disable logging to stderr. Does not affect --verbose or --debug-file or logging to syslog. Verbose mode. Multiple -v flags can be stacked on the command line (e.g., -vvv) to increase verbosity. Level of debugging detail in output. Defaults to the number of --verbose flags specified on the command line, or to 1 if verbose=1 in a stonith device configuration (i.e., on stdin). Write debug information to given file 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 Disable timeout (true/false) (default: true when run from Pacemaker 2.0+) Wait X seconds for cmd prompt after login 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 Sleep X seconds between status calls during a STONITH action Count of attempts to retry power on