461 lines
16 KiB
Diff
461 lines
16 KiB
Diff
--- a/agents/openstack/fence_openstack.py 2018-06-28 14:24:54.000000000 +0200
|
|
+++ b/agents/openstack/fence_openstack.py 2022-04-22 10:15:28.069884360 +0200
|
|
@@ -2,110 +2,303 @@
|
|
|
|
import atexit
|
|
import logging
|
|
-import os
|
|
-import re
|
|
import sys
|
|
-from pipes import quote
|
|
-sys.path.append("/usr/share/fence")
|
|
+import os
|
|
+
|
|
+import urllib3
|
|
+
|
|
+sys.path.append("@FENCEAGENTSLIBDIR@")
|
|
from fencing import *
|
|
-from fencing import fail_usage, is_executable, run_command, run_delay
|
|
-from keystoneclient.v3 import client as ksclient
|
|
-from novaclient import client as novaclient
|
|
-from keystoneclient import session as ksc_session
|
|
-from keystoneclient.auth.identity import v3
|
|
-
|
|
-def get_name_or_uuid(options):
|
|
- return options["--uuid"] if "--uuid" in options else options["--plug"]
|
|
-
|
|
-def get_power_status(_, options):
|
|
- output = nova_run_command(options, "status")
|
|
- if (output == 'ACTIVE'):
|
|
- return 'on'
|
|
- else:
|
|
- return 'off'
|
|
+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 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 set_power_status(_, options):
|
|
- nova_run_command(options, options["--action"])
|
|
- return
|
|
-
|
|
-def nova_login(username,password,projectname,auth_url,user_domain_name,project_domain_name):
|
|
- auth=v3.Password(username=username,password=password,project_name=projectname,user_domain_name=user_domain_name,project_domain_name=project_domain_name,auth_url=auth_url)
|
|
- session = ksc_session.Session(auth=auth)
|
|
- keystone = ksclient.Client(session=session)
|
|
- nova = novaclient.Client(session=session)
|
|
- return nova
|
|
-
|
|
-def nova_run_command(options,action,timeout=None):
|
|
- username=options["--username"]
|
|
- password=options["--password"]
|
|
- projectname=options["--project-name"]
|
|
- auth_url=options["--auth-url"]
|
|
- user_domain_name=options["--user-domain-name"]
|
|
- project_domain_name=options["--project-domain-name"]
|
|
- novaclient=nova_login(username,password,projectname,auth_url,user_domain_name,project_domain_name)
|
|
- server = novaclient.servers.get(options["--uuid"])
|
|
- if action == "status":
|
|
- return server.status
|
|
- if action == "on":
|
|
- server.start()
|
|
- if action == "off":
|
|
- server.stop()
|
|
- if action == "reboot":
|
|
- server.reboot('REBOOT_HARD')
|
|
|
|
def define_new_opts():
|
|
all_opt["auth-url"] = {
|
|
- "getopt" : ":",
|
|
- "longopt" : "auth-url",
|
|
- "help" : "--auth-url=[authurl] Keystone Auth URL",
|
|
- "required" : "1",
|
|
- "shortdesc" : "Keystone Auth URL",
|
|
- "order": 1
|
|
+ "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" : "1",
|
|
- "shortdesc" : "Keystone Project",
|
|
+ "getopt": ":",
|
|
+ "longopt": "project-name",
|
|
+ "help": "--project-name=[project] Tenant Or Project Name",
|
|
+ "required": "0",
|
|
+ "shortdesc": "Keystone Project",
|
|
"default": "admin",
|
|
- "order": 1
|
|
+ "order": 3,
|
|
}
|
|
all_opt["user-domain-name"] = {
|
|
- "getopt" : ":",
|
|
- "longopt" : "user-domain-name",
|
|
- "help" : "--user-domain-name=[user-domain] Keystone User Domain Name",
|
|
- "required" : "0",
|
|
- "shortdesc" : "Keystone 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": 1
|
|
+ "order": 4,
|
|
}
|
|
all_opt["project-domain-name"] = {
|
|
- "getopt" : ":",
|
|
- "longopt" : "project-domain-name",
|
|
- "help" : "--project-domain-name=[project-domain] Keystone Project Domain Name",
|
|
- "required" : "0",
|
|
- "shortdesc" : "Keystone 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": 1
|
|
+ "order": 5,
|
|
+ }
|
|
+ all_opt["cloud"] = {
|
|
+ "getopt": ":",
|
|
+ "longopt": "cloud",
|
|
+ "help": "--cloud=[cloud] Openstack cloud (from ~/.config/openstack/clouds.yaml or /etc/openstack/clouds.yaml).",
|
|
+ "required": "0",
|
|
+ "shortdesc": "Cloud from clouds.yaml",
|
|
+ "order": 6,
|
|
+ }
|
|
+ all_opt["openrc"] = {
|
|
+ "getopt": ":",
|
|
+ "longopt": "openrc",
|
|
+ "help": "--openrc=[openrc] Path to the openrc config file",
|
|
+ "required": "0",
|
|
+ "shortdesc": "openrc config file",
|
|
+ "order": 7,
|
|
}
|
|
all_opt["uuid"] = {
|
|
- "getopt" : ":",
|
|
- "longopt" : "uuid",
|
|
- "help" : "--uuid=[uuid] UUID of the nova instance",
|
|
- "required" : "1",
|
|
- "shortdesc" : "UUID of the nova instance",
|
|
- "order": 1
|
|
+ "getopt": ":",
|
|
+ "longopt": "uuid",
|
|
+ "help": "--uuid=[uuid] Replaced by -n, --plug",
|
|
+ "required": "0",
|
|
+ "shortdesc": "Replaced by port/-n/--plug",
|
|
+ "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": 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": 10,
|
|
}
|
|
|
|
+
|
|
def main():
|
|
+ conn = None
|
|
+
|
|
+ device_opt = [
|
|
+ "login",
|
|
+ "no_login",
|
|
+ "passwd",
|
|
+ "no_password",
|
|
+ "auth-url",
|
|
+ "project-name",
|
|
+ "user-domain-name",
|
|
+ "project-domain-name",
|
|
+ "cloud",
|
|
+ "openrc",
|
|
+ "port",
|
|
+ "no_port",
|
|
+ "uuid",
|
|
+ "ssl_insecure",
|
|
+ "cacert",
|
|
+ "apitimeout",
|
|
+ ]
|
|
+
|
|
atexit.register(atexit_handler)
|
|
|
|
- device_opt = ["login", "passwd", "auth-url", "project-name", "user-domain-name", "project-domain-name", "uuid"]
|
|
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 \
|
|
@@ -116,9 +309,73 @@
|
|
|
|
run_delay(options)
|
|
|
|
- result = fence_action(None, options, set_power_status, get_power_status,None)
|
|
+ if options.get("--cloud"):
|
|
+ cloud = get_cloud(options)
|
|
+ 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").get("auth_url")
|
|
+ except KeyError:
|
|
+ fail_usage("Failed: You have to set the Keystone service endpoint for authorization")
|
|
+ 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
|
|
+ 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()
|
|
-
|