Blame SOURCES/bz1886074-2-fence_openstack.patch

3f9bc7
--- a/agents/openstack/fence_openstack.py	2018-06-28 14:24:54.000000000 +0200
3f9bc7
+++ b/agents/openstack/fence_openstack.py	2022-04-22 10:15:28.069884360 +0200
3f9bc7
@@ -2,110 +2,303 @@
3f9bc7
 
3f9bc7
 import atexit
3f9bc7
 import logging
3f9bc7
-import os
3f9bc7
-import re
3f9bc7
 import sys
3f9bc7
-from pipes import quote
3f9bc7
-sys.path.append("/usr/share/fence")
3f9bc7
+import os
3f9bc7
+
3f9bc7
+import urllib3
3f9bc7
+
3f9bc7
+sys.path.append("@FENCEAGENTSLIBDIR@")
3f9bc7
 from fencing import *
3f9bc7
-from fencing import fail_usage, is_executable, run_command, run_delay
3f9bc7
-from keystoneclient.v3 import client as ksclient
3f9bc7
-from novaclient import client as novaclient
3f9bc7
-from keystoneclient import session as ksc_session
3f9bc7
-from keystoneclient.auth.identity import v3
3f9bc7
-
3f9bc7
-def get_name_or_uuid(options):
3f9bc7
-        return options["--uuid"] if "--uuid" in options else options["--plug"]
3f9bc7
-
3f9bc7
-def get_power_status(_, options):
3f9bc7
-        output = nova_run_command(options, "status")
3f9bc7
-        if (output == 'ACTIVE'):
3f9bc7
-                return 'on'
3f9bc7
-        else:
3f9bc7
-                return 'off'
3f9bc7
+from fencing import fail_usage, run_delay, source_env
3f9bc7
+
3f9bc7
+try:
3f9bc7
+    from novaclient import client
3f9bc7
+    from novaclient.exceptions import Conflict, NotFound
3f9bc7
+except ImportError:
3f9bc7
+    pass
3f9bc7
+
3f9bc7
+urllib3.disable_warnings(urllib3.exceptions.SecurityWarning)
3f9bc7
+
3f9bc7
+
3f9bc7
+def translate_status(instance_status):
3f9bc7
+    if instance_status == "ACTIVE":
3f9bc7
+        return "on"
3f9bc7
+    elif instance_status == "SHUTOFF":
3f9bc7
+        return "off"
3f9bc7
+    return "unknown"
3f9bc7
+
3f9bc7
+def get_cloud(options):
3f9bc7
+    import yaml
3f9bc7
+
3f9bc7
+    clouds_yaml = "~/.config/openstack/clouds.yaml"
3f9bc7
+    if not os.path.exists(os.path.expanduser(clouds_yaml)):
3f9bc7
+        clouds_yaml = "/etc/openstack/clouds.yaml"
3f9bc7
+    if not os.path.exists(os.path.expanduser(clouds_yaml)):
3f9bc7
+        fail_usage("Failed: ~/.config/openstack/clouds.yaml and /etc/openstack/clouds.yaml does not exist")
3f9bc7
+
3f9bc7
+    clouds_yaml = os.path.expanduser(clouds_yaml)
3f9bc7
+    if os.path.exists(clouds_yaml):
3f9bc7
+        with open(clouds_yaml, "r") as yaml_stream:
3f9bc7
+            try:
3f9bc7
+                clouds = yaml.safe_load(yaml_stream)
3f9bc7
+            except yaml.YAMLError as exc:
3f9bc7
+                fail_usage("Failed: Unable to read: " + clouds_yaml)
3f9bc7
+
3f9bc7
+    cloud = clouds.get("clouds").get(options["--cloud"])
3f9bc7
+    if not cloud:
3f9bc7
+        fail_usage("Cloud: {} not found.".format(options["--cloud"]))
3f9bc7
+
3f9bc7
+    return cloud
3f9bc7
+
3f9bc7
+
3f9bc7
+def get_nodes_list(conn, options):
3f9bc7
+    logging.info("Running %s action", options["--action"])
3f9bc7
+    result = {}
3f9bc7
+    response = conn.servers.list(detailed=True)
3f9bc7
+    if response is not None:
3f9bc7
+        for item in response:
3f9bc7
+            instance_id = item.id
3f9bc7
+            instance_name = item.name
3f9bc7
+            instance_status = item.status
3f9bc7
+            result[instance_id] = (instance_name, translate_status(instance_status))
3f9bc7
+    return result
3f9bc7
+
3f9bc7
+
3f9bc7
+def get_power_status(conn, options):
3f9bc7
+    logging.info("Running %s action on %s", options["--action"], options["--plug"])
3f9bc7
+    server = None
3f9bc7
+    try:
3f9bc7
+        server = conn.servers.get(options["--plug"])
3f9bc7
+    except NotFound as e:
3f9bc7
+        fail_usage("Failed: Not Found: " + str(e))
3f9bc7
+    if server is None:
3f9bc7
+        fail_usage("Server %s not found", options["--plug"])
3f9bc7
+    state = server.status
3f9bc7
+    status = translate_status(state)
3f9bc7
+    logging.info("get_power_status: %s (state: %s)" % (status, state))
3f9bc7
+    return status
3f9bc7
+
3f9bc7
+
3f9bc7
+def set_power_status(conn, options):
3f9bc7
+    logging.info("Running %s action on %s", options["--action"], options["--plug"])
3f9bc7
+    action = options["--action"]
3f9bc7
+    server = None
3f9bc7
+    try:
3f9bc7
+        server = conn.servers.get(options["--plug"])
3f9bc7
+    except NotFound as e:
3f9bc7
+        fail_usage("Failed: Not Found: " + str(e))
3f9bc7
+    if server is None:
3f9bc7
+        fail_usage("Server %s not found", options["--plug"])
3f9bc7
+    if action == "on":
3f9bc7
+        logging.info("Starting instance " + server.name)
3f9bc7
+        try:
3f9bc7
+            server.start()
3f9bc7
+        except Conflict as e:
3f9bc7
+            fail_usage(e)
3f9bc7
+        logging.info("Called start API call for " + server.id)
3f9bc7
+    if action == "off":
3f9bc7
+        logging.info("Stopping instance " + server.name)
3f9bc7
+        try:
3f9bc7
+            server.stop()
3f9bc7
+        except Conflict as e:
3f9bc7
+            fail_usage(e)
3f9bc7
+        logging.info("Called stop API call for " + server.id)
3f9bc7
+    if action == "reboot":
3f9bc7
+        logging.info("Rebooting instance " + server.name)
3f9bc7
+        try:
3f9bc7
+            server.reboot("HARD")
3f9bc7
+        except Conflict as e:
3f9bc7
+            fail_usage(e)
3f9bc7
+        logging.info("Called reboot hard API call for " + server.id)
3f9bc7
+
3f9bc7
+
3f9bc7
+def nova_login(username, password, projectname, auth_url, user_domain_name,
3f9bc7
+               project_domain_name, ssl_insecure, cacert, apitimeout):
3f9bc7
+    legacy_import = False
3f9bc7
+
3f9bc7
+    try:
3f9bc7
+        from keystoneauth1 import loading
3f9bc7
+        from keystoneauth1 import session as ksc_session
3f9bc7
+        from keystoneauth1.exceptions.discovery import DiscoveryFailure
3f9bc7
+        from keystoneauth1.exceptions.http import Unauthorized
3f9bc7
+    except ImportError:
3f9bc7
+        try:
3f9bc7
+            from keystoneclient import session as ksc_session
3f9bc7
+            from keystoneclient.auth.identity import v3
3f9bc7
+
3f9bc7
+            legacy_import = True
3f9bc7
+        except ImportError:
3f9bc7
+            fail_usage("Failed: Keystone client not found or not accessible")
3f9bc7
+
3f9bc7
+    if not legacy_import:
3f9bc7
+        loader = loading.get_plugin_loader("password")
3f9bc7
+        auth = loader.load_from_options(
3f9bc7
+            auth_url=auth_url,
3f9bc7
+            username=username,
3f9bc7
+            password=password,
3f9bc7
+            project_name=projectname,
3f9bc7
+            user_domain_name=user_domain_name,
3f9bc7
+            project_domain_name=project_domain_name,
3f9bc7
+        )
3f9bc7
+    else:
3f9bc7
+        auth = v3.Password(
3f9bc7
+            auth_url=auth_url,
3f9bc7
+            username=username,
3f9bc7
+            password=password,
3f9bc7
+            project_name=projectname,
3f9bc7
+            user_domain_name=user_domain_name,
3f9bc7
+            project_domain_name=project_domain_name,
3f9bc7
+            cacert=cacert,
3f9bc7
+        )
3f9bc7
+
3f9bc7
+    caverify=True
3f9bc7
+    if ssl_insecure:
3f9bc7
+        caverify=False
3f9bc7
+    elif cacert:
3f9bc7
+        caverify=cacert
3f9bc7
+
3f9bc7
+    session = ksc_session.Session(auth=auth, verify=caverify, timeout=apitimeout)
3f9bc7
+    nova = client.Client("2", session=session, timeout=apitimeout)
3f9bc7
+    apiversion = None
3f9bc7
+    try:
3f9bc7
+        apiversion = nova.versions.get_current()
3f9bc7
+    except DiscoveryFailure as e:
3f9bc7
+        fail_usage("Failed: Discovery Failure: " + str(e))
3f9bc7
+    except Unauthorized as e:
3f9bc7
+        fail_usage("Failed: Unauthorized: " + str(e))
3f9bc7
+    except Exception as e:
3f9bc7
+        logging.error(e)
3f9bc7
+    logging.debug("Nova version: %s", apiversion)
3f9bc7
+    return nova
3f9bc7
 
3f9bc7
-def set_power_status(_, options):
3f9bc7
-    nova_run_command(options, options["--action"])
3f9bc7
-    return
3f9bc7
-
3f9bc7
-def nova_login(username,password,projectname,auth_url,user_domain_name,project_domain_name):
3f9bc7
-        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)
3f9bc7
-        session = ksc_session.Session(auth=auth)
3f9bc7
-        keystone = ksclient.Client(session=session)
3f9bc7
-        nova = novaclient.Client(session=session)
3f9bc7
-        return nova
3f9bc7
-
3f9bc7
-def nova_run_command(options,action,timeout=None):
3f9bc7
-        username=options["--username"]
3f9bc7
-        password=options["--password"]
3f9bc7
-        projectname=options["--project-name"]
3f9bc7
-        auth_url=options["--auth-url"]
3f9bc7
-        user_domain_name=options["--user-domain-name"]
3f9bc7
-        project_domain_name=options["--project-domain-name"]
3f9bc7
-        novaclient=nova_login(username,password,projectname,auth_url,user_domain_name,project_domain_name)
3f9bc7
-        server = novaclient.servers.get(options["--uuid"])
3f9bc7
-        if action == "status":
3f9bc7
-                return server.status
3f9bc7
-        if action == "on":
3f9bc7
-                server.start()
3f9bc7
-        if action == "off":
3f9bc7
-                server.stop()
3f9bc7
-        if action == "reboot":
3f9bc7
-                server.reboot('REBOOT_HARD')
3f9bc7
 
3f9bc7
 def define_new_opts():
3f9bc7
     all_opt["auth-url"] = {
3f9bc7
-        "getopt" : ":",
3f9bc7
-        "longopt" : "auth-url",
3f9bc7
-        "help" : "--auth-url=[authurl]            Keystone Auth URL",
3f9bc7
-        "required" : "1",
3f9bc7
-        "shortdesc" : "Keystone Auth URL",
3f9bc7
-        "order": 1
3f9bc7
+        "getopt": ":",
3f9bc7
+        "longopt": "auth-url",
3f9bc7
+        "help": "--auth-url=[authurl]           Keystone Auth URL",
3f9bc7
+        "required": "0",
3f9bc7
+        "shortdesc": "Keystone Auth URL",
3f9bc7
+        "order": 2,
3f9bc7
     }
3f9bc7
     all_opt["project-name"] = {
3f9bc7
-        "getopt" : ":",
3f9bc7
-        "longopt" : "project-name",
3f9bc7
-        "help" : "--project-name=[project]      Tenant Or Project Name",
3f9bc7
-        "required" : "1",
3f9bc7
-        "shortdesc" : "Keystone Project",
3f9bc7
+        "getopt": ":",
3f9bc7
+        "longopt": "project-name",
3f9bc7
+        "help": "--project-name=[project]       Tenant Or Project Name",
3f9bc7
+        "required": "0",
3f9bc7
+        "shortdesc": "Keystone Project",
3f9bc7
         "default": "admin",
3f9bc7
-        "order": 1
3f9bc7
+        "order": 3,
3f9bc7
     }
3f9bc7
     all_opt["user-domain-name"] = {
3f9bc7
-        "getopt" : ":",
3f9bc7
-        "longopt" : "user-domain-name",
3f9bc7
-        "help" : "--user-domain-name=[user-domain]      Keystone User Domain Name",
3f9bc7
-        "required" : "0",
3f9bc7
-        "shortdesc" : "Keystone User Domain Name",
3f9bc7
+        "getopt": ":",
3f9bc7
+        "longopt": "user-domain-name",
3f9bc7
+        "help": "--user-domain-name=[domain]    Keystone User Domain Name",
3f9bc7
+        "required": "0",
3f9bc7
+        "shortdesc": "Keystone User Domain Name",
3f9bc7
         "default": "Default",
3f9bc7
-        "order": 1
3f9bc7
+        "order": 4,
3f9bc7
     }
3f9bc7
     all_opt["project-domain-name"] = {
3f9bc7
-        "getopt" : ":",
3f9bc7
-        "longopt" : "project-domain-name",
3f9bc7
-        "help" : "--project-domain-name=[project-domain]      Keystone Project Domain Name",
3f9bc7
-        "required" : "0",
3f9bc7
-        "shortdesc" : "Keystone Project Domain Name",
3f9bc7
+        "getopt": ":",
3f9bc7
+        "longopt": "project-domain-name",
3f9bc7
+        "help": "--project-domain-name=[domain] Keystone Project Domain Name",
3f9bc7
+        "required": "0",
3f9bc7
+        "shortdesc": "Keystone Project Domain Name",
3f9bc7
         "default": "Default",
3f9bc7
-        "order": 1
3f9bc7
+        "order": 5,
3f9bc7
+    }
3f9bc7
+    all_opt["cloud"] = {
3f9bc7
+        "getopt": ":",
3f9bc7
+        "longopt": "cloud",
3f9bc7
+        "help": "--cloud=[cloud]              Openstack cloud (from ~/.config/openstack/clouds.yaml or /etc/openstack/clouds.yaml).",
3f9bc7
+        "required": "0",
3f9bc7
+        "shortdesc": "Cloud from clouds.yaml",
3f9bc7
+        "order": 6,
3f9bc7
+    }
3f9bc7
+    all_opt["openrc"] = {
3f9bc7
+        "getopt": ":",
3f9bc7
+        "longopt": "openrc",
3f9bc7
+        "help": "--openrc=[openrc]              Path to the openrc config file",
3f9bc7
+        "required": "0",
3f9bc7
+        "shortdesc": "openrc config file",
3f9bc7
+        "order": 7,
3f9bc7
     }
3f9bc7
     all_opt["uuid"] = {
3f9bc7
-        "getopt" : ":",
3f9bc7
-        "longopt" : "uuid",
3f9bc7
-        "help" : "--uuid=[uuid]      UUID of the nova instance",
3f9bc7
-        "required" : "1",
3f9bc7
-        "shortdesc" : "UUID of the nova instance",
3f9bc7
-        "order": 1
3f9bc7
+        "getopt": ":",
3f9bc7
+        "longopt": "uuid",
3f9bc7
+        "help": "--uuid=[uuid]                  Replaced by -n, --plug",
3f9bc7
+        "required": "0",
3f9bc7
+        "shortdesc": "Replaced by port/-n/--plug",
3f9bc7
+        "order": 8,
3f9bc7
+    }
3f9bc7
+    all_opt["cacert"] = {
3f9bc7
+        "getopt": ":",
3f9bc7
+        "longopt": "cacert",
3f9bc7
+        "help": "--cacert=[cacert]              Path to the PEM file with trusted authority certificates (override global CA trust)",
3f9bc7
+        "required": "0",
3f9bc7
+        "shortdesc": "SSL X.509 certificates file",
3f9bc7
+        "default": "",
3f9bc7
+        "order": 9,
3f9bc7
+    }
3f9bc7
+    all_opt["apitimeout"] = {
3f9bc7
+        "getopt": ":",
3f9bc7
+        "type": "second",
3f9bc7
+        "longopt": "apitimeout",
3f9bc7
+        "help": "--apitimeout=[seconds]         Timeout to use for API calls",
3f9bc7
+        "shortdesc": "Timeout in seconds to use for API calls, default is 60.",
3f9bc7
+        "required": "0",
3f9bc7
+        "default": 60,
3f9bc7
+        "order": 10,
3f9bc7
     }
3f9bc7
 
3f9bc7
+
3f9bc7
 def main():
3f9bc7
+    conn = None
3f9bc7
+
3f9bc7
+    device_opt = [
3f9bc7
+        "login",
3f9bc7
+        "no_login",
3f9bc7
+        "passwd",
3f9bc7
+        "no_password",
3f9bc7
+        "auth-url",
3f9bc7
+        "project-name",
3f9bc7
+        "user-domain-name",
3f9bc7
+        "project-domain-name",
3f9bc7
+        "cloud",
3f9bc7
+        "openrc",
3f9bc7
+        "port",
3f9bc7
+        "no_port",
3f9bc7
+        "uuid",
3f9bc7
+        "ssl_insecure",
3f9bc7
+        "cacert",
3f9bc7
+        "apitimeout",
3f9bc7
+    ]
3f9bc7
+
3f9bc7
     atexit.register(atexit_handler)
3f9bc7
 
3f9bc7
-    device_opt = ["login", "passwd", "auth-url", "project-name", "user-domain-name", "project-domain-name", "uuid"]
3f9bc7
     define_new_opts()
3f9bc7
 
3f9bc7
+    all_opt["port"]["required"] = "0"
3f9bc7
+    all_opt["port"]["help"] = "-n, --plug=[UUID]              UUID of the node to be fenced"
3f9bc7
+    all_opt["port"]["shortdesc"] = "UUID of the node to be fenced."
3f9bc7
+    all_opt["power_timeout"]["default"] = "60"
3f9bc7
+
3f9bc7
     options = check_input(device_opt, process_input(device_opt))
3f9bc7
 
3f9bc7
+    # workaround to avoid regressions
3f9bc7
+    if "--uuid" in options:
3f9bc7
+        options["--plug"] = options["--uuid"]
3f9bc7
+        del options["--uuid"]
3f9bc7
+    elif ("--help" not in options
3f9bc7
+          and options["--action"] in ["off", "on", "reboot", "status", "validate-all"]
3f9bc7
+          and "--plug" not in options):
3f9bc7
+        stop_after_error = False if options["--action"] == "validate-all" else True
3f9bc7
+        fail_usage(
3f9bc7
+            "Failed: You have to enter plug number or machine identification",
3f9bc7
+            stop_after_error,
3f9bc7
+        )
3f9bc7
+
3f9bc7
     docs = {}
3f9bc7
     docs["shortdesc"] = "Fence agent for OpenStack's Nova service"
3f9bc7
     docs["longdesc"] = "fence_openstack is a Fencing agent \
3f9bc7
@@ -116,9 +309,73 @@
3f9bc7
 
3f9bc7
     run_delay(options)
3f9bc7
 
3f9bc7
-    result = fence_action(None, options, set_power_status, get_power_status,None)
3f9bc7
+    if options.get("--cloud"):
3f9bc7
+        cloud = get_cloud(options)
3f9bc7
+        username = cloud.get("auth").get("username")
3f9bc7
+        password = cloud.get("auth").get("password")
3f9bc7
+        projectname = cloud.get("auth").get("project_name")
3f9bc7
+        auth_url = None
3f9bc7
+        try:
3f9bc7
+            auth_url = cloud.get("auth").get("auth_url")
3f9bc7
+        except KeyError:
3f9bc7
+            fail_usage("Failed: You have to set the Keystone service endpoint for authorization")
3f9bc7
+        user_domain_name = cloud.get("auth").get("user_domain_name")
3f9bc7
+        project_domain_name = cloud.get("auth").get("project_domain_name")
3f9bc7
+        caverify = cloud.get("verify")
3f9bc7
+        if caverify in [True, False]:
3f9bc7
+                options["--ssl-insecure"] = caverify
3f9bc7
+        else:
3f9bc7
+                options["--cacert"] = caverify
3f9bc7
+    elif options.get("--openrc"):
3f9bc7
+        if not os.path.exists(os.path.expanduser(options["--openrc"])):
3f9bc7
+            fail_usage("Failed: {} does not exist".format(options.get("--openrc")))
3f9bc7
+        source_env(options["--openrc"])
3f9bc7
+        env = os.environ
3f9bc7
+        username = env.get("OS_USERNAME")
3f9bc7
+        password = env.get("OS_PASSWORD")
3f9bc7
+        projectname = env.get("OS_PROJECT_NAME")
3f9bc7
+        auth_url = None
3f9bc7
+        try:
3f9bc7
+            auth_url = env["OS_AUTH_URL"]
3f9bc7
+        except KeyError:
3f9bc7
+            fail_usage("Failed: You have to set the Keystone service endpoint for authorization")
3f9bc7
+        user_domain_name = env.get("OS_USER_DOMAIN_NAME")
3f9bc7
+        project_domain_name = env.get("OS_PROJECT_DOMAIN_NAME")
3f9bc7
+    else:
3f9bc7
+        username = options["--username"]
3f9bc7
+        password = options["--password"]
3f9bc7
+        projectname = options["--project-name"]
3f9bc7
+        auth_url = None
3f9bc7
+        try:
3f9bc7
+            auth_url = options["--auth-url"]
3f9bc7
+        except KeyError:
3f9bc7
+            fail_usage("Failed: You have to set the Keystone service endpoint for authorization")
3f9bc7
+        user_domain_name = options["--user-domain-name"]
3f9bc7
+        project_domain_name = options["--project-domain-name"]
3f9bc7
+
3f9bc7
+    ssl_insecure = "--ssl-insecure" in options
3f9bc7
+    cacert = options["--cacert"]
3f9bc7
+    apitimeout = options["--apitimeout"]
3f9bc7
+
3f9bc7
+    try:
3f9bc7
+        conn = nova_login(
3f9bc7
+            username,
3f9bc7
+            password,
3f9bc7
+            projectname,
3f9bc7
+            auth_url,
3f9bc7
+            user_domain_name,
3f9bc7
+            project_domain_name,
3f9bc7
+            ssl_insecure,
3f9bc7
+            cacert,
3f9bc7
+            apitimeout,
3f9bc7
+        )
3f9bc7
+    except Exception as e:
3f9bc7
+        fail_usage("Failed: Unable to connect to Nova: " + str(e))
3f9bc7
+
3f9bc7
+    # Operate the fencing device
3f9bc7
+    result = fence_action(conn, options, set_power_status, get_power_status, get_nodes_list)
3f9bc7
     sys.exit(result)
3f9bc7
 
3f9bc7
+
3f9bc7
 if __name__ == "__main__":
3f9bc7
     main()
3f9bc7
-