diff --git a/.fence-agents.metadata b/.fence-agents.metadata index a89b55a..00c9f88 100644 --- a/.fence-agents.metadata +++ b/.fence-agents.metadata @@ -22,7 +22,6 @@ e0fa19f8fda46a1fa2253477499b116b33f67175 SOURCES/pyasn1-0.4.8.tar.gz 43b89feb6864fe359aae89120627165219de313b SOURCES/pyasn1-modules-0.2.8.tar.gz 326a73f58a62ebee00c11a12cfdd838b196e0e8e SOURCES/pycryptodome-3.6.4.tar.gz c8307f47e3b75a2d02af72982a2dfefa3f56e407 SOURCES/pyparsing-2.4.7-py2.py3-none-any.whl -6082312a090f5be5e796e0854294da0738ec0379 SOURCES/pyparsing-3.0.1.tar.gz c2ba10c775b7a52a4b57cac4d4110a0c0f812a82 SOURCES/python-dateutil-2.8.2.tar.gz 1dc2fa004aa6517f1620e55d8a7b8e68a9cf2a47 SOURCES/python-string-utils-1.0.0.tar.gz 8c7a89d183d3e9b70bf91ba5b75eccf7111b9d8d SOURCES/requests-2.26.0.tar.gz diff --git a/.gitignore b/.gitignore index 21e220e..050b1cd 100644 --- a/.gitignore +++ b/.gitignore @@ -22,7 +22,6 @@ SOURCES/pyasn1-0.4.8.tar.gz SOURCES/pyasn1-modules-0.2.8.tar.gz SOURCES/pycryptodome-3.6.4.tar.gz SOURCES/pyparsing-2.4.7-py2.py3-none-any.whl -SOURCES/pyparsing-3.0.1.tar.gz SOURCES/python-dateutil-2.8.2.tar.gz SOURCES/python-string-utils-1.0.0.tar.gz SOURCES/requests-2.26.0.tar.gz diff --git a/SOURCES/bz1886074-1-fencing-source_env.patch b/SOURCES/bz1886074-1-fencing-source_env.patch new file mode 100644 index 0000000..31ad189 --- /dev/null +++ b/SOURCES/bz1886074-1-fencing-source_env.patch @@ -0,0 +1,17 @@ +--- a/lib/fencing.py.py 2022-04-22 10:25:59.347139685 +0200 ++++ b/lib/fencing.py.py 2022-04-22 10:26:05.127169492 +0200 +@@ -1115,6 +1115,14 @@ + except pexpect.ExceptionPexpect: + pass + ++def source_env(env_file): ++ # POSIX: name shall not contain '=', value doesn't contain '\0' ++ output = subprocess.check_output("source {} && env -0".format(env_file), shell=True, ++ executable="/bin/sh") ++ # replace env ++ os.environ.clear() ++ os.environ.update(line.partition('=')[::2] for line in output.decode("utf-8").split('\0')) ++ + # Convert array of format [[key1, value1], [key2, value2], ... [keyN, valueN]] to dict, where key is + # in format a.b.c.d...z and returned dict has key only z + def array_to_dict(array): diff --git a/SOURCES/bz1886074-2-fence_openstack.patch b/SOURCES/bz1886074-2-fence_openstack.patch new file mode 100644 index 0000000..51796c9 --- /dev/null +++ b/SOURCES/bz1886074-2-fence_openstack.patch @@ -0,0 +1,460 @@ +--- 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() +- diff --git a/SOURCES/bz1886074-4-fencing-source_env-dont-process-empty-lines.patch b/SOURCES/bz1886074-4-fencing-source_env-dont-process-empty-lines.patch new file mode 100644 index 0000000..8803895 --- /dev/null +++ b/SOURCES/bz1886074-4-fencing-source_env-dont-process-empty-lines.patch @@ -0,0 +1,22 @@ +From fbca33a536413565108374dd4ed3237b6f7896bd Mon Sep 17 00:00:00 2001 +From: Oyvind Albrigtsen +Date: Mon, 15 Aug 2022 10:40:19 +0200 +Subject: [PATCH] fencing: source_env(): dont process empty lines + +--- + lib/fencing.py.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/lib/fencing.py.py b/lib/fencing.py.py +index fc3679e33..940bd01d1 100644 +--- a/lib/fencing.py.py ++++ b/lib/fencing.py.py +@@ -1264,7 +1264,7 @@ def source_env(env_file): + executable="/bin/sh") + # replace env + os.environ.clear() +- os.environ.update(line.partition('=')[::2] for line in output.decode("utf-8").split('\0')) ++ os.environ.update(line.partition('=')[::2] for line in output.decode("utf-8").split('\0') if not re.match("^\s*$", line)) + + # Convert array of format [[key1, value1], [key2, value2], ... [keyN, valueN]] to dict, where key is + # in format a.b.c.d...z and returned dict has key only z diff --git a/SOURCES/bz2072421-1-all-agents-unify-ssl-parameters.patch b/SOURCES/bz2072421-1-all-agents-unify-ssl-parameters.patch new file mode 100644 index 0000000..d783a47 --- /dev/null +++ b/SOURCES/bz2072421-1-all-agents-unify-ssl-parameters.patch @@ -0,0 +1,243 @@ +From 999f2f8b4dc7d258679daf8c3f13d9b317ff4435 Mon Sep 17 00:00:00 2001 +From: Oyvind Albrigtsen +Date: Thu, 7 Apr 2022 13:11:12 +0200 +Subject: [PATCH] all agents: unify ssl parameters to avoid having to use --ssl + when using --ssl-secure/--ssl-insecure for some agents + +THIS MIGHT BREAK SETUPS USING fence_docker or fence_pve! +--- + agents/cisco_ucs/fence_cisco_ucs.py | 9 +++++---- + agents/docker/fence_docker.py | 9 ++++++--- + agents/rhevm/fence_rhevm.py | 8 ++++---- + agents/vmware_rest/fence_vmware_rest.py | 8 ++++---- + agents/vmware_soap/fence_vmware_soap.py | 2 +- + agents/vmware_vcloud/fence_vmware_vcloud.py | 4 ++-- + agents/zvm/fence_zvmip.py | 8 +++++--- + lib/fencing.py.py | 6 ++++++ + tests/data/metadata/fence_docker.xml | 4 ++-- + 13 files changed, 64 insertions(+), 31 deletions(-) + +diff --git a/agents/cisco_ucs/fence_cisco_ucs.py b/agents/cisco_ucs/fence_cisco_ucs.py +index 2280dbbc7..b85379a73 100644 +--- a/agents/cisco_ucs/fence_cisco_ucs.py ++++ b/agents/cisco_ucs/fence_cisco_ucs.py +@@ -99,7 +99,7 @@ def get_list(conn, options): + + def send_command(opt, command, timeout): + ## setup correct URL +- if "--ssl" in opt or "--ssl-secure" in opt or "--ssl-insecure" in opt: ++ if "--ssl-secure" in opt or "--ssl-insecure" in opt: + url = "https:" + else: + url = "http:" +@@ -114,13 +114,14 @@ def send_command(opt, command, timeout): + conn.setopt(pycurl.POSTFIELDS, command.encode("ascii")) + conn.setopt(pycurl.WRITEFUNCTION, web_buffer.write) + conn.setopt(pycurl.TIMEOUT, timeout) +- if "--ssl" in opt or "--ssl-secure" in opt: ++ ++ if "--ssl-secure" in opt: + conn.setopt(pycurl.SSL_VERIFYPEER, 1) + conn.setopt(pycurl.SSL_VERIFYHOST, 2) +- +- if "--ssl-insecure" in opt: ++ elif "--ssl-insecure" in opt: + conn.setopt(pycurl.SSL_VERIFYPEER, 0) + conn.setopt(pycurl.SSL_VERIFYHOST, 0) ++ + conn.perform() + result = web_buffer.getvalue().decode() + +diff --git a/agents/docker/fence_docker.py b/agents/docker/fence_docker.py +index fef87da86..004402518 100644 +--- a/agents/docker/fence_docker.py ++++ b/agents/docker/fence_docker.py +@@ -43,7 +43,7 @@ def get_list(conn, options): + + + def send_cmd(options, cmd, post = False): +- url = "http%s://%s:%s/v%s/%s" % ("s" if "--ssl" in options else "", options["--ip"], options["--ipport"], options["--api-version"], cmd) ++ url = "http%s://%s:%s/v%s/%s" % ("s" if "--ssl-secure" in options or "--ssl-insecure" in options else "", options["--ip"], options["--ipport"], options["--api-version"], cmd) + conn = pycurl.Curl() + output_buffer = io.BytesIO() + if logging.getLogger().getEffectiveLevel() < logging.WARNING: +@@ -55,7 +55,8 @@ def send_cmd(options, cmd, post = False): + conn.setopt(pycurl.POSTFIELDSIZE, 0) + conn.setopt(pycurl.WRITEFUNCTION, output_buffer.write) + conn.setopt(pycurl.TIMEOUT, int(options["--shell-timeout"])) +- if "--ssl" in options: ++ ++ if "--ssl-secure" in options: + if not (set(("--tlscert", "--tlskey", "--tlscacert")) <= set(options)): + fail_usage("Failed. If --ssl option is used, You have to also \ + specify: --tlscert, --tlskey and --tlscacert") +@@ -63,7 +64,7 @@ def send_cmd(options, cmd, post = False): + conn.setopt(pycurl.SSLCERT, options["--tlscert"]) + conn.setopt(pycurl.SSLKEY, options["--tlskey"]) + conn.setopt(pycurl.CAINFO, options["--tlscacert"]) +- else: ++ elif "--ssl-insecure" in options: + conn.setopt(pycurl.SSL_VERIFYPEER, 0) + conn.setopt(pycurl.SSL_VERIFYHOST, 0) + +@@ -136,6 +137,8 @@ def main(): + + device_opt = ["ipaddr", "no_password", "no_login", "port", "method", "web", "tlscert", "tlskey", "tlscacert", "ssl", "api_version"] + ++ all_opt["ssl"]["default"] = "1" ++ + options = check_input(device_opt, process_input(device_opt)) + + docs = { } +diff --git a/agents/rhevm/fence_rhevm.py b/agents/rhevm/fence_rhevm.py +index 25aecbe58..5f74d06f6 100644 +--- a/agents/rhevm/fence_rhevm.py ++++ b/agents/rhevm/fence_rhevm.py +@@ -85,7 +85,7 @@ def send_command(opt, command, method="GET"): + logging.debug("auto-detected API version: " + opt["--api-version"]) + + ## setup correct URL +- if "--ssl" in opt or "--ssl-secure" in opt or "--ssl-insecure" in opt: ++ if "--ssl-secure" in opt or "--ssl-insecure" in opt: + url = "https:" + else: + url = "http:" +@@ -126,11 +126,11 @@ def send_command(opt, command, method="GET"): + conn.setopt(pycurl.COOKIEJAR, cookie_file) + + conn.setopt(pycurl.TIMEOUT, int(opt["--shell-timeout"])) +- if "--ssl" in opt or "--ssl-secure" in opt: ++ ++ if "--ssl-secure" in opt: + conn.setopt(pycurl.SSL_VERIFYPEER, 1) + conn.setopt(pycurl.SSL_VERIFYHOST, 2) +- +- if "--ssl-insecure" in opt: ++ elif "--ssl-insecure" in opt: + conn.setopt(pycurl.SSL_VERIFYPEER, 0) + conn.setopt(pycurl.SSL_VERIFYHOST, 0) + +diff --git a/agents/vmware_rest/fence_vmware_rest.py b/agents/vmware_rest/fence_vmware_rest.py +index e49fd5663..4b884fc62 100644 +--- a/agents/vmware_rest/fence_vmware_rest.py ++++ b/agents/vmware_rest/fence_vmware_rest.py +@@ -69,7 +69,7 @@ def connect(opt): + conn = pycurl.Curl() + + ## setup correct URL +- if "--ssl" in opt or "--ssl-secure" in opt or "--ssl-insecure" in opt: ++ if "--ssl-secure" in opt or "--ssl-insecure" in opt: + conn.base_url = "https:" + else: + conn.base_url = "http:" +@@ -89,11 +89,11 @@ def connect(opt): + conn.setopt(pycurl.USERPWD, opt["--username"] + ":" + opt["--password"]) + + conn.setopt(pycurl.TIMEOUT, int(opt["--shell-timeout"])) +- if "--ssl" in opt or "--ssl-secure" in opt: ++ ++ if "--ssl-secure" in opt: + conn.setopt(pycurl.SSL_VERIFYPEER, 1) + conn.setopt(pycurl.SSL_VERIFYHOST, 2) +- +- if "--ssl-insecure" in opt: ++ elif "--ssl-insecure" in opt: + conn.setopt(pycurl.SSL_VERIFYPEER, 0) + conn.setopt(pycurl.SSL_VERIFYHOST, 0) + +diff --git a/agents/vmware_soap/fence_vmware_soap.py b/agents/vmware_soap/fence_vmware_soap.py +index 2cd45e0b3..51fb0f147 100644 +--- a/agents/vmware_soap/fence_vmware_soap.py ++++ b/agents/vmware_soap/fence_vmware_soap.py +@@ -34,7 +34,7 @@ def send(self, request): + def soap_login(options): + run_delay(options) + +- if "--ssl" in options or "--ssl-secure" in options or "--ssl-insecure" in options: ++ if "--ssl-secure" in options or "--ssl-insecure" in options: + if "--ssl-insecure" in options: + import ssl + import urllib3 +diff --git a/agents/vmware_vcloud/fence_vmware_vcloud.py b/agents/vmware_vcloud/fence_vmware_vcloud.py +index 42372a83d..7626b82bb 100644 +--- a/agents/vmware_vcloud/fence_vmware_vcloud.py ++++ b/agents/vmware_vcloud/fence_vmware_vcloud.py +@@ -60,7 +60,7 @@ def connect(opt): + conn = pycurl.Curl() + + ## setup correct URL +- if "--ssl" in opt or "--ssl-secure" in opt or "--ssl-insecure" in opt: ++ if "--ssl-secure" in opt or "--ssl-insecure" in opt: + conn.base_url = "https:" + else: + conn.base_url = "http:" +@@ -76,7 +76,7 @@ def connect(opt): + conn.setopt(pycurl.USERPWD, opt["--username"] + ":" + opt["--password"]) + + conn.setopt(pycurl.TIMEOUT, int(opt["--shell-timeout"])) +- if "--ssl" in opt or "--ssl-secure" in opt: ++ if "--ssl-secure" in opt: + conn.setopt(pycurl.SSL_VERIFYPEER, 1) + conn.setopt(pycurl.SSL_VERIFYHOST, 2) + elif "--ssl-insecure" in opt: +diff --git a/agents/zvm/fence_zvmip.py b/agents/zvm/fence_zvmip.py +index e8f849eda..90ca95d45 100644 +--- a/agents/zvm/fence_zvmip.py ++++ b/agents/zvm/fence_zvmip.py +@@ -26,7 +26,7 @@ def open_socket(options): + except socket.gaierror: + fail(EC_LOGIN_DENIED) + +- if "--ssl" in options: ++ if "--ssl-secure" in options or "--ssl-insecure" in options: + import ssl + sock = socket.socket() + sslcx = ssl.create_default_context() +@@ -132,7 +132,7 @@ def get_list_of_images(options, command, data_as_plug): + images = set() + + if output_len > 3*INT4: +- recvflag = socket.MSG_WAITALL if "--ssl" not in options else 0 ++ recvflag = socket.MSG_WAITALL if "--ssl-secure" not in options and "--ssl-insecure" not in options else 0 + array_len = struct.unpack("!i", conn.recv(INT4))[0] + data = "" + +diff --git a/lib/fencing.py.py b/lib/fencing.py.py +index 696388d55..b746ede8b 100644 +--- a/lib/fencing.py.py ++++ b/lib/fencing.py.py +@@ -789,6 +789,12 @@ def check_input(device_opt, opt, other_conditions = False): + if "--password-script" in options: + options["--password"] = os.popen(options["--password-script"]).read().rstrip() + ++ if "--ssl-secure" in options or "--ssl-insecure" in options: ++ options["--ssl"] = "" ++ ++ if "--ssl" in options and "--ssl-insecure" not in options: ++ options["--ssl-secure"] = "" ++ + if os.environ.get("PCMK_service") == "pacemaker-fenced" and "--disable-timeout" not in options: + options["--disable-timeout"] = "1" + +diff --git a/tests/data/metadata/fence_docker.xml b/tests/data/metadata/fence_docker.xml +index 723e72280..51c7c470a 100644 +--- a/tests/data/metadata/fence_docker.xml ++++ b/tests/data/metadata/fence_docker.xml +@@ -20,7 +20,7 @@ + + + +- ++ + TCP/UDP port to use for connection with device + + +@@ -43,7 +43,7 @@ + + + +- ++ + Use SSL connection with verifying certificate + + diff --git a/SOURCES/bz2072421-2-fence_zvmip-connect-error.patch b/SOURCES/bz2072421-2-fence_zvmip-connect-error.patch new file mode 100644 index 0000000..1a1bd97 --- /dev/null +++ b/SOURCES/bz2072421-2-fence_zvmip-connect-error.patch @@ -0,0 +1,30 @@ +From 6430104318b4bf349425b08636183bf839812e04 Mon Sep 17 00:00:00 2001 +From: Oyvind Albrigtsen +Date: Tue, 31 May 2022 08:55:25 +0200 +Subject: [PATCH] fence_zvmip: show unable to connect error instead of full + stacktrace, e.g. when not using --ssl for SSL devices + +--- + agents/zvm/fence_zvmip.py | 9 +++++++-- + 1 file changed, 7 insertions(+), 2 deletions(-) + +diff --git a/agents/zvm/fence_zvmip.py b/agents/zvm/fence_zvmip.py +index 90ca95d45..4f538e10d 100644 +--- a/agents/zvm/fence_zvmip.py ++++ b/agents/zvm/fence_zvmip.py +@@ -127,8 +127,13 @@ def get_list_of_images(options, command, data_as_plug): + + conn.send(packet) + +- request_id = struct.unpack("!i", conn.recv(INT4))[0] +- (output_len, request_id, return_code, reason_code) = struct.unpack("!iiii", conn.recv(INT4 * 4)) ++ try: ++ request_id = struct.unpack("!i", conn.recv(INT4))[0] ++ (output_len, request_id, return_code, reason_code) = struct.unpack("!iiii", conn.recv(INT4 * 4)) ++ except struct.error: ++ logging.debug(sys.exc_info()) ++ fail_usage("Failed: Unable to connect to {} port: {} SSL: {} \n".format(options["--ip"], options["--ipport"], bool("--ssl" in options))) ++ + images = set() + + if output_len > 3*INT4: diff --git a/SOURCES/bz2078244-fence_gce-update.patch b/SOURCES/bz2078244-fence_gce-update.patch new file mode 100644 index 0000000..444daf3 --- /dev/null +++ b/SOURCES/bz2078244-fence_gce-update.patch @@ -0,0 +1,563 @@ +--- fence-agents-4.2.1/agents/gce/fence_gce.py 2022-04-28 15:58:42.949723547 +0200 ++++ fence-agents-4.2.1/agents/gce/fence_gce.py2 2022-04-28 15:59:21.054915265 +0200 +@@ -1,10 +1,22 @@ + #!@PYTHON@ -tt + ++# ++# Requires the googleapiclient and oauth2client ++# RHEL 7.x: google-api-python-client==1.6.7 python-gflags==2.0 pyasn1==0.4.8 rsa==3.4.2 pysocks==1.7.1 httplib2==0.19.0 ++# RHEL 8.x: pysocks==1.7.1 httplib2==0.19.0 ++# SLES 12.x: python-google-api-python-client python-oauth2client python-oauth2client-gce pysocks==1.7.1 httplib2==0.19.0 ++# SLES 15.x: python3-google-api-python-client python3-oauth2client pysocks==1.7.1 httplib2==0.19.0 ++# ++ + import atexit + import logging ++import json ++import re + import os ++import socket + import sys + import time ++ + if sys.version_info >= (3, 0): + # Python 3 imports. + import urllib.parse as urlparse +@@ -15,7 +27,7 @@ + import urllib2 as urlrequest + sys.path.append("@FENCEAGENTSLIBDIR@") + +-from fencing import fail_usage, run_delay, all_opt, atexit_handler, check_input, process_input, show_docs, fence_action ++from fencing import fail_usage, run_delay, all_opt, atexit_handler, check_input, process_input, show_docs, fence_action, run_command + try: + sys.path.insert(0, '/usr/lib/fence-agents/bundled/google') + import httplib2 +@@ -30,6 +42,85 @@ + + METADATA_SERVER = 'http://metadata.google.internal/computeMetadata/v1/' + METADATA_HEADERS = {'Metadata-Flavor': 'Google'} ++INSTANCE_LINK = 'https://www.googleapis.com/compute/v1/projects/{}/zones/{}/instances/{}' ++ ++def run_on_fail(options): ++ if "--runonfail" in options: ++ run_command(options, options["--runonfail"]) ++ ++def fail_fence_agent(options, message): ++ run_on_fail(options) ++ fail_usage(message) ++ ++def raise_fence_agent(options, message): ++ run_on_fail(options) ++ raise Exception(message) ++ ++# ++# Will use baremetalsolution setting or the environment variable ++# FENCE_GCE_URI_REPLACEMENTS to replace the uri for calls to *.googleapis.com. ++# ++def replace_api_uri(options, http_request): ++ uri_replacements = [] ++ # put any env var replacements first, then baremetalsolution if in options ++ if "FENCE_GCE_URI_REPLACEMENTS" in os.environ: ++ logging.debug("FENCE_GCE_URI_REPLACEMENTS environment variable exists") ++ env_uri_replacements = os.environ["FENCE_GCE_URI_REPLACEMENTS"] ++ try: ++ uri_replacements_json = json.loads(env_uri_replacements) ++ if isinstance(uri_replacements_json, list): ++ uri_replacements = uri_replacements_json ++ else: ++ logging.warning("FENCE_GCE_URI_REPLACEMENTS exists, but is not a JSON List") ++ except ValueError as e: ++ logging.warning("FENCE_GCE_URI_REPLACEMENTS exists but is not valid JSON") ++ if "--baremetalsolution" in options: ++ uri_replacements.append( ++ { ++ "matchlength": 4, ++ "match": "https://compute.googleapis.com/compute/v1/projects/(.*)/zones/(.*)/instances/(.*)/reset(.*)", ++ "replace": "https://baremetalsolution.googleapis.com/v1/projects/\\1/locations/\\2/instances/\\3:resetInstance\\4" ++ }) ++ for uri_replacement in uri_replacements: ++ # each uri_replacement should have matchlength, match, and replace ++ if "matchlength" not in uri_replacement or "match" not in uri_replacement or "replace" not in uri_replacement: ++ logging.warning("FENCE_GCE_URI_REPLACEMENTS missing matchlength, match, or replace in %s" % uri_replacement) ++ continue ++ match = re.match(uri_replacement["match"], http_request.uri) ++ if match is None or len(match.groups()) != uri_replacement["matchlength"]: ++ continue ++ replaced_uri = re.sub(uri_replacement["match"], uri_replacement["replace"], http_request.uri) ++ match = re.match("https:\/\/.*.googleapis.com", replaced_uri) ++ if match is None or match.start() != 0: ++ logging.warning("FENCE_GCE_URI_REPLACEMENTS replace is not " ++ "targeting googleapis.com, ignoring it: %s" % replaced_uri) ++ continue ++ logging.debug("Replacing googleapis uri %s with %s" % (http_request.uri, replaced_uri)) ++ http_request.uri = replaced_uri ++ break ++ return http_request ++ ++def retry_api_execute(options, http_request): ++ replaced_http_request = replace_api_uri(options, http_request) ++ retries = 3 ++ if options.get("--retries"): ++ retries = int(options.get("--retries")) ++ retry_sleep = 5 ++ if options.get("--retrysleep"): ++ retry_sleep = int(options.get("--retrysleep")) ++ retry = 0 ++ current_err = None ++ while retry <= retries: ++ if retry > 0: ++ time.sleep(retry_sleep) ++ try: ++ return replaced_http_request.execute() ++ except Exception as err: ++ current_err = err ++ logging.warning("Could not execute api call to: %s, retry: %s, " ++ "err: %s" % (replaced_http_request.uri, retry, str(err))) ++ retry += 1 ++ raise current_err + + + def translate_status(instance_status): +@@ -43,86 +134,174 @@ + + def get_nodes_list(conn, options): + result = {} ++ if "--zone" not in options: ++ fail_fence_agent(options, "Failed: get_nodes_list: Please specify the --zone in the command") + try: +- instanceList = conn.instances().list(project=options["--project"], zone=options["--zone"]).execute() +- for instance in instanceList["items"]: +- result[instance["id"]] = (instance["name"], translate_status(instance["status"])) ++ for zone in options["--zone"].split(","): ++ instanceList = retry_api_execute(options, conn.instances().list( ++ project=options["--project"], ++ zone=zone)) ++ for instance in instanceList["items"]: ++ result[instance["id"]] = (instance["name"], translate_status(instance["status"])) + except Exception as err: +- fail_usage("Failed: get_nodes_list: {}".format(str(err))) ++ fail_fence_agent(options, "Failed: get_nodes_list: {}".format(str(err))) + + return result + + + def get_power_status(conn, options): ++ logging.debug("get_power_status") ++ # if this is bare metal we need to just send back the opposite of the ++ # requested action: if on send off, if off send on ++ if "--baremetalsolution" in options: ++ if options.get("--action") == "on": ++ return "off" ++ else: ++ return "on" ++ # If zone is not listed for an entry we attempt to get it automatically ++ instance = options["--plug"] ++ zone = get_zone(conn, options, instance) if "--plugzonemap" not in options else options["--plugzonemap"][instance] ++ instance_status = get_instance_power_status(conn, options, instance, zone) ++ # If any of the instances do not match the intended status we return the ++ # the opposite status so that the fence agent can change it. ++ if instance_status != options.get("--action"): ++ return instance_status ++ ++ return options.get("--action") ++ ++ ++def get_instance_power_status(conn, options, instance, zone): + try: +- instance = conn.instances().get( +- project=options["--project"], +- zone=options["--zone"], +- instance=options["--plug"]).execute() ++ instance = retry_api_execute( ++ options, ++ conn.instances().get(project=options["--project"], zone=zone, instance=instance)) + return translate_status(instance["status"]) + except Exception as err: +- fail_usage("Failed: get_power_status: {}".format(str(err))) ++ fail_fence_agent(options, "Failed: get_instance_power_status: {}".format(str(err))) + + +-def wait_for_operation(conn, project, zone, operation): ++def check_for_existing_operation(conn, options, instance, zone, operation_type): ++ logging.debug("check_for_existing_operation") ++ if "--baremetalsolution" in options: ++ # There is no API for checking in progress operations ++ return False ++ ++ project = options["--project"] ++ target_link = INSTANCE_LINK.format(project, zone, instance) ++ query_filter = '(targetLink = "{}") AND (operationType = "{}") AND (status = "RUNNING")'.format(target_link, operation_type) ++ result = retry_api_execute( ++ options, ++ conn.zoneOperations().list(project=project, zone=zone, filter=query_filter, maxResults=1)) ++ ++ if "items" in result and result["items"]: ++ logging.info("Existing %s operation found", operation_type) ++ return result["items"][0] ++ ++ ++def wait_for_operation(conn, options, zone, operation): ++ if 'name' not in operation: ++ logging.warning('Cannot wait for operation to complete, the' ++ ' requested operation will continue asynchronously') ++ return False ++ ++ wait_time = 0 ++ project = options["--project"] + while True: +- result = conn.zoneOperations().get( ++ result = retry_api_execute(options, conn.zoneOperations().get( + project=project, + zone=zone, +- operation=operation['name']).execute() ++ operation=operation['name'])) + if result['status'] == 'DONE': + if 'error' in result: +- raise Exception(result['error']) +- return ++ raise_fence_agent(options, result['error']) ++ return True ++ ++ if "--errortimeout" in options and wait_time > int(options["--errortimeout"]): ++ raise_fence_agent(options, "Operation did not complete before the timeout.") ++ ++ if "--warntimeout" in options and wait_time > int(options["--warntimeout"]): ++ logging.warning("Operation did not complete before the timeout.") ++ if "--runonwarn" in options: ++ run_command(options, options["--runonwarn"]) ++ return False ++ ++ wait_time = wait_time + 1 + time.sleep(1) + + + def set_power_status(conn, options): ++ logging.debug("set_power_status") ++ instance = options["--plug"] ++ # If zone is not listed for an entry we attempt to get it automatically ++ zone = get_zone(conn, options, instance) if "--plugzonemap" not in options else options["--plugzonemap"][instance] ++ set_instance_power_status(conn, options, instance, zone, options["--action"]) ++ ++ ++def set_instance_power_status(conn, options, instance, zone, action): ++ logging.info("Setting power status of %s in zone %s", instance, zone) ++ project = options["--project"] ++ + try: +- if options["--action"] == "off": +- logging.info("Issuing poweroff of %s in zone %s" % (options["--plug"], options["--zone"])) +- operation = conn.instances().stop( +- project=options["--project"], +- zone=options["--zone"], +- instance=options["--plug"]).execute() +- wait_for_operation(conn, options["--project"], options["--zone"], operation) +- logging.info("Poweroff of %s in zone %s complete" % (options["--plug"], options["--zone"])) +- elif options["--action"] == "on": +- logging.info("Issuing poweron of %s in zone %s" % (options["--plug"], options["--zone"])) +- operation = conn.instances().start( +- project=options["--project"], +- zone=options["--zone"], +- instance=options["--plug"]).execute() +- wait_for_operation(conn, options["--project"], options["--zone"], operation) +- logging.info("Poweron of %s in zone %s complete" % (options["--plug"], options["--zone"])) ++ if action == "off": ++ logging.info("Issuing poweroff of %s in zone %s", instance, zone) ++ operation = check_for_existing_operation(conn, options, instance, zone, "stop") ++ if operation and "--earlyexit" in options: ++ return ++ if not operation: ++ operation = retry_api_execute( ++ options, ++ conn.instances().stop(project=project, zone=zone, instance=instance)) ++ logging.info("Poweroff command completed, waiting for the operation to complete") ++ if wait_for_operation(conn, options, zone, operation): ++ logging.info("Poweroff of %s in zone %s complete", instance, zone) ++ elif action == "on": ++ logging.info("Issuing poweron of %s in zone %s", instance, zone) ++ operation = check_for_existing_operation(conn, options, instance, zone, "start") ++ if operation and "--earlyexit" in options: ++ return ++ if not operation: ++ operation = retry_api_execute( ++ options, ++ conn.instances().start(project=project, zone=zone, instance=instance)) ++ if wait_for_operation(conn, options, zone, operation): ++ logging.info("Poweron of %s in zone %s complete", instance, zone) + except Exception as err: +- fail_usage("Failed: set_power_status: {}".format(str(err))) +- ++ fail_fence_agent(options, "Failed: set_instance_power_status: {}".format(str(err))) + + def power_cycle(conn, options): ++ logging.debug("power_cycle") ++ instance = options["--plug"] ++ # If zone is not listed for an entry we attempt to get it automatically ++ zone = get_zone(conn, options, instance) if "--plugzonemap" not in options else options["--plugzonemap"][instance] ++ return power_cycle_instance(conn, options, instance, zone) ++ ++ ++def power_cycle_instance(conn, options, instance, zone): ++ logging.info("Issuing reset of %s in zone %s", instance, zone) ++ project = options["--project"] ++ + try: +- logging.info('Issuing reset of %s in zone %s' % (options["--plug"], options["--zone"])) +- operation = conn.instances().reset( +- project=options["--project"], +- zone=options["--zone"], +- instance=options["--plug"]).execute() +- wait_for_operation(conn, options["--project"], options["--zone"], operation) +- logging.info('Reset of %s in zone %s complete' % (options["--plug"], options["--zone"])) ++ operation = check_for_existing_operation(conn, options, instance, zone, "reset") ++ if operation and "--earlyexit" in options: ++ return True ++ if not operation: ++ operation = retry_api_execute( ++ options, ++ conn.instances().reset(project=project, zone=zone, instance=instance)) ++ logging.info("Reset command sent, waiting for the operation to complete") ++ if wait_for_operation(conn, options, zone, operation): ++ logging.info("Reset of %s in zone %s complete", instance, zone) + return True + except Exception as err: +- logging.error("Failed: power_cycle: {}".format(str(err))) +- return False +- +- +-def get_instance(conn, project, zone, instance): +- request = conn.instances().get( +- project=project, zone=zone, instance=instance) +- return request.execute() ++ logging.exception("Failed: power_cycle") ++ raise err + + +-def get_zone(conn, project, instance): ++def get_zone(conn, options, instance): ++ logging.debug("get_zone"); ++ project = options['--project'] + fl = 'name="%s"' % instance +- request = conn.instances().aggregatedList(project=project, filter=fl) ++ request = replace_api_uri(options, conn.instances().aggregatedList(project=project, filter=fl)) + while request is not None: + response = request.execute() + zones = response.get('items', {}) +@@ -130,9 +309,9 @@ + for inst in zone.get('instances', []): + if inst['name'] == instance: + return inst['zone'].split("/")[-1] +- request = conn.instances().aggregatedList_next( +- previous_request=request, previous_response=response) +- raise Exception("Unable to find instance %s" % (instance)) ++ request = replace_api_uri(options, conn.instances().aggregatedList_next( ++ previous_request=request, previous_response=response)) ++ raise_fence_agent(options, "Unable to find instance %s" % (instance)) + + + def get_metadata(metadata_key, params=None, timeout=None): +@@ -149,6 +328,7 @@ + Raises: + urlerror.HTTPError: raises when the GET request fails. + """ ++ logging.debug("get_metadata"); + timeout = timeout or 60 + metadata_url = os.path.join(METADATA_SERVER, metadata_key) + params = urlparse.urlencode(params or {}) +@@ -178,12 +358,50 @@ + all_opt["stackdriver-logging"] = { + "getopt" : "", + "longopt" : "stackdriver-logging", +- "help" : "--stackdriver-logging Enable Logging to Stackdriver. Using stackdriver logging requires additional libraries (google-cloud-logging).", +- "shortdesc" : "Stackdriver-logging support. Requires additional libraries (google-cloud-logging).", +- "longdesc" : "If enabled IP failover logs will be posted to stackdriver logging. Using stackdriver logging requires additional libraries (google-cloud-logging).", ++ "help" : "--stackdriver-logging Enable Logging to Stackdriver", ++ "shortdesc" : "Stackdriver-logging support.", ++ "longdesc" : "If enabled IP failover logs will be posted to stackdriver logging.", + "required" : "0", + "order" : 4 + } ++ all_opt["baremetalsolution"] = { ++ "getopt" : "", ++ "longopt" : "baremetalsolution", ++ "help" : "--baremetalsolution Enable on bare metal", ++ "shortdesc" : "If enabled this is a bare metal offering from google.", ++ "required" : "0", ++ "order" : 5 ++ } ++ 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" : 6 ++ } ++ all_opt["retries"] = { ++ "getopt" : ":", ++ "type" : "integer", ++ "longopt" : "retries", ++ "help" : "--retries=[retries] Number of retries on failure for API calls", ++ "shortdesc" : "Number of retries on failure for API calls, default is 3.", ++ "required" : "0", ++ "default" : 3, ++ "order" : 7 ++ } ++ all_opt["retrysleep"] = { ++ "getopt" : ":", ++ "type" : "second", ++ "longopt" : "retrysleep", ++ "help" : "--retrysleep=[seconds] Time to sleep between API retries", ++ "shortdesc" : "Time to sleep in seconds between API retries, default is 5.", ++ "required" : "0", ++ "default" : 5, ++ "order" : 8 ++ } + all_opt["serviceaccount"] = { + "getopt" : ":", + "longopt" : "serviceaccount", +@@ -192,13 +410,21 @@ + "required" : "0", + "order" : 9 + } ++ all_opt["plugzonemap"] = { ++ "getopt" : ":", ++ "longopt" : "plugzonemap", ++ "help" : "--plugzonemap=[plugzonemap] Comma separated zone map when fencing multiple plugs", ++ "shortdesc" : "Comma separated zone map when fencing multiple plugs.", ++ "required" : "0", ++ "order" : 10 ++ } + all_opt["proxyhost"] = { + "getopt" : ":", + "longopt" : "proxyhost", + "help" : "--proxyhost=[proxy_host] The proxy host to use, if one is needed to access the internet (Example: 10.122.0.33)", + "shortdesc" : "If a proxy is used for internet access, the proxy host should be specified.", + "required" : "0", +- "order" : 10 ++ "order" : 11 + } + all_opt["proxyport"] = { + "getopt" : ":", +@@ -207,7 +433,49 @@ + "help" : "--proxyport=[proxy_port] The proxy port to use, if one is needed to access the internet (Example: 3127)", + "shortdesc" : "If a proxy is used for internet access, the proxy port should be specified.", + "required" : "0", +- "order" : 11 ++ "order" : 12 ++ } ++ all_opt["earlyexit"] = { ++ "getopt" : "", ++ "longopt" : "earlyexit", ++ "help" : "--earlyexit Return early if reset is already in progress", ++ "shortdesc" : "If an existing reset operation is detected, the fence agent will return before the operation completes with a 0 return code.", ++ "required" : "0", ++ "order" : 13 ++ } ++ all_opt["warntimeout"] = { ++ "getopt" : ":", ++ "type" : "second", ++ "longopt" : "warntimeout", ++ "help" : "--warntimeout=[warn_timeout] Timeout seconds before logging a warning and returning a 0 status code", ++ "shortdesc" : "If the operation is not completed within the timeout, the cluster operations are allowed to continue.", ++ "required" : "0", ++ "order" : 14 ++ } ++ all_opt["errortimeout"] = { ++ "getopt" : ":", ++ "type" : "second", ++ "longopt" : "errortimeout", ++ "help" : "--errortimeout=[error_timeout] Timeout seconds before failing and returning a non-zero status code", ++ "shortdesc" : "If the operation is not completed within the timeout, cluster is notified of the operation failure.", ++ "required" : "0", ++ "order" : 15 ++ } ++ all_opt["runonwarn"] = { ++ "getopt" : ":", ++ "longopt" : "runonwarn", ++ "help" : "--runonwarn=[run_on_warn] If a timeout occurs and warning is generated, run the supplied command", ++ "shortdesc" : "If a timeout would occur while running the agent, then the supplied command is run.", ++ "required" : "0", ++ "order" : 16 ++ } ++ all_opt["runonfail"] = { ++ "getopt" : ":", ++ "longopt" : "runonfail", ++ "help" : "--runonfail=[run_on_fail] If a failure occurs, run the supplied command", ++ "shortdesc" : "If a failure would occur while running the agent, then the supplied command is run.", ++ "required" : "0", ++ "order" : 17 + } + + +@@ -215,7 +483,9 @@ + conn = None + + device_opt = ["port", "no_password", "zone", "project", "stackdriver-logging", +- "method", "serviceaccount", "proxyhost", "proxyport"] ++ "method", "baremetalsolution", "apitimeout", "retries", "retrysleep", ++ "serviceaccount", "plugzonemap", "proxyhost", "proxyport", "earlyexit", ++ "warntimeout", "errortimeout", "runonwarn", "runonfail"] + + atexit.register(atexit_handler) + +@@ -259,6 +529,11 @@ + logging.error('Couldn\'t import google.cloud.logging, ' + 'disabling Stackdriver-logging support') + ++ # if apitimeout is defined we set the socket timeout, if not we keep the ++ # socket default which is 60s ++ if options.get("--apitimeout"): ++ socket.setdefaulttimeout(options["--apitimeout"]) ++ + # Prepare cli + try: + serviceaccount = options.get("--serviceaccount") +@@ -291,20 +566,39 @@ + conn = googleapiclient.discovery.build( + 'compute', 'v1', credentials=credentials, cache_discovery=False) + except Exception as err: +- fail_usage("Failed: Create GCE compute v1 connection: {}".format(str(err))) ++ fail_fence_agent(options, "Failed: Create GCE compute v1 connection: {}".format(str(err))) + + # Get project and zone + if not options.get("--project"): + try: + options["--project"] = get_metadata('project/project-id') + except Exception as err: +- fail_usage("Failed retrieving GCE project. Please provide --project option: {}".format(str(err))) ++ fail_fence_agent(options, "Failed retrieving GCE project. Please provide --project option: {}".format(str(err))) + +- if not options.get("--zone"): +- try: +- options["--zone"] = get_zone(conn, options['--project'], options['--plug']) +- except Exception as err: +- fail_usage("Failed retrieving GCE zone. Please provide --zone option: {}".format(str(err))) ++ if "--baremetalsolution" in options: ++ options["--zone"] = "none" ++ ++ # Populates zone automatically if missing from the command ++ zones = [] if not "--zone" in options else options["--zone"].split(",") ++ options["--plugzonemap"] = {} ++ if "--plug" in options: ++ for i, instance in enumerate(options["--plug"].split(",")): ++ if len(zones) == 1: ++ # If only one zone is specified, use it across all plugs ++ options["--plugzonemap"][instance] = zones[0] ++ continue ++ ++ if len(zones) - 1 >= i: ++ # If we have enough zones specified with the --zone flag use the zone at ++ # the same index as the plug ++ options["--plugzonemap"][instance] = zones[i] ++ continue ++ ++ try: ++ # In this case we do not have a zone specified so we attempt to detect it ++ options["--plugzonemap"][instance] = get_zone(conn, options, instance) ++ except Exception as err: ++ fail_fence_agent(options, "Failed retrieving GCE zone. Please provide --zone option: {}".format(str(err))) + + # Operate the fencing device + result = fence_action(conn, options, set_power_status, get_power_status, get_nodes_list, power_cycle) diff --git a/SOURCES/bz2080729-1-fence_apc-fence_ilo_moonshot-import-logging.patch b/SOURCES/bz2080729-1-fence_apc-fence_ilo_moonshot-import-logging.patch new file mode 100644 index 0000000..dbbde08 --- /dev/null +++ b/SOURCES/bz2080729-1-fence_apc-fence_ilo_moonshot-import-logging.patch @@ -0,0 +1,34 @@ +From 6ea2c6b5d1cc51e93fa7084d76d9272512461e58 Mon Sep 17 00:00:00 2001 +From: Oyvind Albrigtsen +Date: Mon, 16 May 2022 11:01:21 +0200 +Subject: [PATCH] fence_apc/fence_ilo_moonshot: add missing "import logging" + +--- + agents/apc/fence_apc.py | 1 + + agents/ilo_moonshot/fence_ilo_moonshot.py | 1 + + 2 files changed, 2 insertions(+) + +diff --git a/agents/apc/fence_apc.py b/agents/apc/fence_apc.py +index 901aad25e..3ea0f37d6 100644 +--- a/agents/apc/fence_apc.py ++++ b/agents/apc/fence_apc.py +@@ -15,6 +15,7 @@ + ##### + + import sys, re, time ++import logging + import atexit + sys.path.append("@FENCEAGENTSLIBDIR@") + from fencing import * +diff --git a/agents/ilo_moonshot/fence_ilo_moonshot.py b/agents/ilo_moonshot/fence_ilo_moonshot.py +index 6f5cca320..1923eeb1c 100644 +--- a/agents/ilo_moonshot/fence_ilo_moonshot.py ++++ b/agents/ilo_moonshot/fence_ilo_moonshot.py +@@ -1,6 +1,7 @@ + #!@PYTHON@ -tt + + import sys ++import logging + import atexit + sys.path.append("@FENCEAGENTSLIBDIR@") + from fencing import * diff --git a/SOURCES/bz2080729-2-fence_lpar-fix-import-fail_usage.patch b/SOURCES/bz2080729-2-fence_lpar-fix-import-fail_usage.patch new file mode 100644 index 0000000..599a22c --- /dev/null +++ b/SOURCES/bz2080729-2-fence_lpar-fix-import-fail_usage.patch @@ -0,0 +1,65 @@ +From e3dff8570b70f0c19eca84cf02f0aadd68e16599 Mon Sep 17 00:00:00 2001 +From: Thomas Renninger +Date: Fri, 25 Feb 2022 14:05:42 +0100 +Subject: [PATCH] fence_lpar: fix missing import logging, use fail_usage + +and slightly re-factor code to avoid duplicate code lines. +Should be cleanup only, no functional change. +--- + agents/lpar/fence_lpar.py | 39 ++++++++++++++++++--------------------- + 1 file changed, 18 insertions(+), 21 deletions(-) + +diff --git a/agents/lpar/fence_lpar.py b/agents/lpar/fence_lpar.py +index ad18c6191..2046b0e4e 100644 +--- a/agents/lpar/fence_lpar.py ++++ b/agents/lpar/fence_lpar.py +@@ -28,31 +28,28 @@ def _normalize_status(status): + + def get_power_status(conn, options): + if options["--hmc-version"] == "3": +- conn.send("lssyscfg -r lpar -m " + options["--managed"] + " -n " + options["--plug"] + " -F name,state\n") +- +- # First line (command) may cause parsing issues if long +- conn.readline() +- conn.log_expect(options["--command-prompt"], int(options["--power-timeout"])) +- +- try: +- status = re.compile("^" + options["--plug"] + ",(.*?),.*$", +- re.IGNORECASE | re.MULTILINE).search(conn.before).group(1) +- except AttributeError as e: +- logging.error("Failed: {}".format(str(e))) +- fail(EC_STATUS_HMC) ++ command = "lssyscfg -r lpar -m " + options["--managed"] + " -n " + options["--plug"] + " -F name,state\n" + elif options["--hmc-version"] in ["4", "IVM"]: +- conn.send("lssyscfg -r lpar -m "+ options["--managed"] + +- " --filter 'lpar_names=" + options["--plug"] + "'\n") ++ command = "lssyscfg -r lpar -m "+ options["--managed"] + \ ++ " --filter 'lpar_names=" + options["--plug"] + "'\n" ++ else: ++ # Bad HMC Version cannot be reached ++ fail(EC_STATUS_HMC) + +- # First line (command) may cause parsing issues if long +- conn.readline() +- conn.log_expect(options["--command-prompt"], int(options["--power-timeout"])) ++ conn.send(command) ++ # First line (command) may cause parsing issues if long ++ conn.readline() ++ conn.log_expect(options["--command-prompt"], int(options["--power-timeout"])) + +- try: ++ try: ++ if options["--hmc-version"] == "3": ++ status = re.compile("^" + options["--plug"] + ",(.*?),.*$", ++ re.IGNORECASE | re.MULTILINE).search(conn.before).group(1) ++ elif options["--hmc-version"] in ["4", "IVM"]: + status = re.compile(",state=(.*?),", re.IGNORECASE).search(conn.before).group(1) +- except AttributeError as e: +- logging.error("Failed: {}".format(str(e))) +- fail(EC_STATUS_HMC) ++ except AttributeError as e: ++ fail_usage("Command on HMC failed: {}\n{}".format(command, str(e)), False) ++ fail(EC_STATUS_HMC) + + return _normalize_status(status) + diff --git a/SOURCES/bz2080994-fence_ibm_vpc-fix-parameters.patch b/SOURCES/bz2080994-fence_ibm_vpc-fix-parameters.patch new file mode 100644 index 0000000..5e031ad --- /dev/null +++ b/SOURCES/bz2080994-fence_ibm_vpc-fix-parameters.patch @@ -0,0 +1,65 @@ +From d0254345472c9415af1e06e9e8df2fe0ce464db0 Mon Sep 17 00:00:00 2001 +From: Oyvind Albrigtsen +Date: Mon, 2 May 2022 11:14:42 +0200 +Subject: [PATCH] fence_ibm_vpc: remove unused instance parameter and make + limit optional + +--- + agents/ibm_vpc/fence_ibm_vpc.py | 11 +---------- + tests/data/metadata/fence_ibm_vpc.xml | 7 +------ + 2 files changed, 2 insertions(+), 16 deletions(-) + +diff --git a/agents/ibm_vpc/fence_ibm_vpc.py b/agents/ibm_vpc/fence_ibm_vpc.py +index 9f84f7b2d..9e38e8301 100755 +--- a/agents/ibm_vpc/fence_ibm_vpc.py ++++ b/agents/ibm_vpc/fence_ibm_vpc.py +@@ -161,14 +161,6 @@ def define_new_opts(): + "shortdesc" : "API Key", + "order" : 0 + } +- all_opt["instance"] = { +- "getopt" : ":", +- "longopt" : "instance", +- "help" : "--instance=[instance] Cloud Instance", +- "required" : "1", +- "shortdesc" : "Cloud Instance", +- "order" : 0 +- } + all_opt["region"] = { + "getopt" : ":", + "longopt" : "region", +@@ -181,7 +173,7 @@ def define_new_opts(): + "getopt" : ":", + "longopt" : "limit", + "help" : "--limit=[number] Limit number of nodes returned by API", +- "required" : "1", ++ "required" : "0", + "default": 50, + "shortdesc" : "Number of nodes returned by API", + "order" : 0 +@@ -191,7 +183,6 @@ def define_new_opts(): + def main(): + device_opt = [ + "apikey", +- "instance", + "region", + "limit", + "port", +diff --git a/tests/data/metadata/fence_ibm_vpc.xml b/tests/data/metadata/fence_ibm_vpc.xml +index 926efcaa0..ee7151673 100644 +--- a/tests/data/metadata/fence_ibm_vpc.xml ++++ b/tests/data/metadata/fence_ibm_vpc.xml +@@ -8,12 +8,7 @@ + + API Key + +- +- +- +- Cloud Instance +- +- ++ + + + Number of nodes returned by API diff --git a/SOURCES/bz2083254-fence_ibm_vpc-fix-parameters.patch b/SOURCES/bz2083254-fence_ibm_vpc-fix-parameters.patch deleted file mode 100644 index 5e031ad..0000000 --- a/SOURCES/bz2083254-fence_ibm_vpc-fix-parameters.patch +++ /dev/null @@ -1,65 +0,0 @@ -From d0254345472c9415af1e06e9e8df2fe0ce464db0 Mon Sep 17 00:00:00 2001 -From: Oyvind Albrigtsen -Date: Mon, 2 May 2022 11:14:42 +0200 -Subject: [PATCH] fence_ibm_vpc: remove unused instance parameter and make - limit optional - ---- - agents/ibm_vpc/fence_ibm_vpc.py | 11 +---------- - tests/data/metadata/fence_ibm_vpc.xml | 7 +------ - 2 files changed, 2 insertions(+), 16 deletions(-) - -diff --git a/agents/ibm_vpc/fence_ibm_vpc.py b/agents/ibm_vpc/fence_ibm_vpc.py -index 9f84f7b2d..9e38e8301 100755 ---- a/agents/ibm_vpc/fence_ibm_vpc.py -+++ b/agents/ibm_vpc/fence_ibm_vpc.py -@@ -161,14 +161,6 @@ def define_new_opts(): - "shortdesc" : "API Key", - "order" : 0 - } -- all_opt["instance"] = { -- "getopt" : ":", -- "longopt" : "instance", -- "help" : "--instance=[instance] Cloud Instance", -- "required" : "1", -- "shortdesc" : "Cloud Instance", -- "order" : 0 -- } - all_opt["region"] = { - "getopt" : ":", - "longopt" : "region", -@@ -181,7 +173,7 @@ def define_new_opts(): - "getopt" : ":", - "longopt" : "limit", - "help" : "--limit=[number] Limit number of nodes returned by API", -- "required" : "1", -+ "required" : "0", - "default": 50, - "shortdesc" : "Number of nodes returned by API", - "order" : 0 -@@ -191,7 +183,6 @@ def define_new_opts(): - def main(): - device_opt = [ - "apikey", -- "instance", - "region", - "limit", - "port", -diff --git a/tests/data/metadata/fence_ibm_vpc.xml b/tests/data/metadata/fence_ibm_vpc.xml -index 926efcaa0..ee7151673 100644 ---- a/tests/data/metadata/fence_ibm_vpc.xml -+++ b/tests/data/metadata/fence_ibm_vpc.xml -@@ -8,12 +8,7 @@ - - API Key - -- -- -- -- Cloud Instance -- -- -+ - - - Number of nodes returned by API diff --git a/SOURCES/bz2086838-1-fence_apc-fence_ilo_moonshot-import-logging.patch b/SOURCES/bz2086838-1-fence_apc-fence_ilo_moonshot-import-logging.patch deleted file mode 100644 index dbbde08..0000000 --- a/SOURCES/bz2086838-1-fence_apc-fence_ilo_moonshot-import-logging.patch +++ /dev/null @@ -1,34 +0,0 @@ -From 6ea2c6b5d1cc51e93fa7084d76d9272512461e58 Mon Sep 17 00:00:00 2001 -From: Oyvind Albrigtsen -Date: Mon, 16 May 2022 11:01:21 +0200 -Subject: [PATCH] fence_apc/fence_ilo_moonshot: add missing "import logging" - ---- - agents/apc/fence_apc.py | 1 + - agents/ilo_moonshot/fence_ilo_moonshot.py | 1 + - 2 files changed, 2 insertions(+) - -diff --git a/agents/apc/fence_apc.py b/agents/apc/fence_apc.py -index 901aad25e..3ea0f37d6 100644 ---- a/agents/apc/fence_apc.py -+++ b/agents/apc/fence_apc.py -@@ -15,6 +15,7 @@ - ##### - - import sys, re, time -+import logging - import atexit - sys.path.append("@FENCEAGENTSLIBDIR@") - from fencing import * -diff --git a/agents/ilo_moonshot/fence_ilo_moonshot.py b/agents/ilo_moonshot/fence_ilo_moonshot.py -index 6f5cca320..1923eeb1c 100644 ---- a/agents/ilo_moonshot/fence_ilo_moonshot.py -+++ b/agents/ilo_moonshot/fence_ilo_moonshot.py -@@ -1,6 +1,7 @@ - #!@PYTHON@ -tt - - import sys -+import logging - import atexit - sys.path.append("@FENCEAGENTSLIBDIR@") - from fencing import * diff --git a/SOURCES/bz2086838-2-fence_lpar-fix-import-fail_usage.patch b/SOURCES/bz2086838-2-fence_lpar-fix-import-fail_usage.patch deleted file mode 100644 index 599a22c..0000000 --- a/SOURCES/bz2086838-2-fence_lpar-fix-import-fail_usage.patch +++ /dev/null @@ -1,65 +0,0 @@ -From e3dff8570b70f0c19eca84cf02f0aadd68e16599 Mon Sep 17 00:00:00 2001 -From: Thomas Renninger -Date: Fri, 25 Feb 2022 14:05:42 +0100 -Subject: [PATCH] fence_lpar: fix missing import logging, use fail_usage - -and slightly re-factor code to avoid duplicate code lines. -Should be cleanup only, no functional change. ---- - agents/lpar/fence_lpar.py | 39 ++++++++++++++++++--------------------- - 1 file changed, 18 insertions(+), 21 deletions(-) - -diff --git a/agents/lpar/fence_lpar.py b/agents/lpar/fence_lpar.py -index ad18c6191..2046b0e4e 100644 ---- a/agents/lpar/fence_lpar.py -+++ b/agents/lpar/fence_lpar.py -@@ -28,31 +28,28 @@ def _normalize_status(status): - - def get_power_status(conn, options): - if options["--hmc-version"] == "3": -- conn.send("lssyscfg -r lpar -m " + options["--managed"] + " -n " + options["--plug"] + " -F name,state\n") -- -- # First line (command) may cause parsing issues if long -- conn.readline() -- conn.log_expect(options["--command-prompt"], int(options["--power-timeout"])) -- -- try: -- status = re.compile("^" + options["--plug"] + ",(.*?),.*$", -- re.IGNORECASE | re.MULTILINE).search(conn.before).group(1) -- except AttributeError as e: -- logging.error("Failed: {}".format(str(e))) -- fail(EC_STATUS_HMC) -+ command = "lssyscfg -r lpar -m " + options["--managed"] + " -n " + options["--plug"] + " -F name,state\n" - elif options["--hmc-version"] in ["4", "IVM"]: -- conn.send("lssyscfg -r lpar -m "+ options["--managed"] + -- " --filter 'lpar_names=" + options["--plug"] + "'\n") -+ command = "lssyscfg -r lpar -m "+ options["--managed"] + \ -+ " --filter 'lpar_names=" + options["--plug"] + "'\n" -+ else: -+ # Bad HMC Version cannot be reached -+ fail(EC_STATUS_HMC) - -- # First line (command) may cause parsing issues if long -- conn.readline() -- conn.log_expect(options["--command-prompt"], int(options["--power-timeout"])) -+ conn.send(command) -+ # First line (command) may cause parsing issues if long -+ conn.readline() -+ conn.log_expect(options["--command-prompt"], int(options["--power-timeout"])) - -- try: -+ try: -+ if options["--hmc-version"] == "3": -+ status = re.compile("^" + options["--plug"] + ",(.*?),.*$", -+ re.IGNORECASE | re.MULTILINE).search(conn.before).group(1) -+ elif options["--hmc-version"] in ["4", "IVM"]: - status = re.compile(",state=(.*?),", re.IGNORECASE).search(conn.before).group(1) -- except AttributeError as e: -- logging.error("Failed: {}".format(str(e))) -- fail(EC_STATUS_HMC) -+ except AttributeError as e: -+ fail_usage("Command on HMC failed: {}\n{}".format(command, str(e)), False) -+ fail(EC_STATUS_HMC) - - return _normalize_status(status) - diff --git a/SOURCES/bz2091826-fence_ibm_vpc-add-proxy-support.patch b/SOURCES/bz2091826-fence_ibm_vpc-add-proxy-support.patch new file mode 100644 index 0000000..39a2e2e --- /dev/null +++ b/SOURCES/bz2091826-fence_ibm_vpc-add-proxy-support.patch @@ -0,0 +1,62 @@ +From be409554bbc99df2bba22cb01e8a6df634af896d Mon Sep 17 00:00:00 2001 +From: Oyvind Albrigtsen +Date: Tue, 31 May 2022 15:46:40 +0200 +Subject: [PATCH] fence_ibm_vpc: add proxy support + +--- + agents/ibm_vpc/fence_ibm_vpc.py | 11 +++++++++++ + tests/data/metadata/fence_ibm_vpc.xml | 5 +++++ + 2 files changed, 16 insertions(+) + +diff --git a/agents/ibm_vpc/fence_ibm_vpc.py b/agents/ibm_vpc/fence_ibm_vpc.py +index 9e38e8301..3da3ce056 100755 +--- a/agents/ibm_vpc/fence_ibm_vpc.py ++++ b/agents/ibm_vpc/fence_ibm_vpc.py +@@ -88,6 +88,7 @@ def connect(opt): + conn.setopt(pycurl.TIMEOUT, int(opt["--shell-timeout"])) + conn.setopt(pycurl.SSL_VERIFYPEER, 1) + conn.setopt(pycurl.SSL_VERIFYHOST, 2) ++ conn.setopt(pycurl.PROXY, "{}".format(opt["--proxy"])) + + # get bearer token + bearer_token = get_bearer_token(conn, opt) +@@ -169,6 +170,15 @@ def define_new_opts(): + "shortdesc" : "Region", + "order" : 0 + } ++ all_opt["proxy"] = { ++ "getopt" : ":", ++ "longopt" : "proxy", ++ "help" : "--proxy=[http://:] Proxy: 'http://:'", ++ "required" : "0", ++ "default": "", ++ "shortdesc" : "Network proxy", ++ "order" : 0 ++ } + all_opt["limit"] = { + "getopt" : ":", + "longopt" : "limit", +@@ -185,6 +195,7 @@ def main(): + "apikey", + "region", + "limit", ++ "proxy", + "port", + "no_password", + ] +diff --git a/tests/data/metadata/fence_ibm_vpc.xml b/tests/data/metadata/fence_ibm_vpc.xml +index ee7151673..09da0e303 100644 +--- a/tests/data/metadata/fence_ibm_vpc.xml ++++ b/tests/data/metadata/fence_ibm_vpc.xml +@@ -13,6 +13,11 @@ + + Number of nodes returned by API + ++ ++ ++ ++ Network proxy ++ + + + diff --git a/SOURCES/bz2092460-fence_ibm_vpc-add-proxy-support.patch b/SOURCES/bz2092460-fence_ibm_vpc-add-proxy-support.patch deleted file mode 100644 index 39a2e2e..0000000 --- a/SOURCES/bz2092460-fence_ibm_vpc-add-proxy-support.patch +++ /dev/null @@ -1,62 +0,0 @@ -From be409554bbc99df2bba22cb01e8a6df634af896d Mon Sep 17 00:00:00 2001 -From: Oyvind Albrigtsen -Date: Tue, 31 May 2022 15:46:40 +0200 -Subject: [PATCH] fence_ibm_vpc: add proxy support - ---- - agents/ibm_vpc/fence_ibm_vpc.py | 11 +++++++++++ - tests/data/metadata/fence_ibm_vpc.xml | 5 +++++ - 2 files changed, 16 insertions(+) - -diff --git a/agents/ibm_vpc/fence_ibm_vpc.py b/agents/ibm_vpc/fence_ibm_vpc.py -index 9e38e8301..3da3ce056 100755 ---- a/agents/ibm_vpc/fence_ibm_vpc.py -+++ b/agents/ibm_vpc/fence_ibm_vpc.py -@@ -88,6 +88,7 @@ def connect(opt): - conn.setopt(pycurl.TIMEOUT, int(opt["--shell-timeout"])) - conn.setopt(pycurl.SSL_VERIFYPEER, 1) - conn.setopt(pycurl.SSL_VERIFYHOST, 2) -+ conn.setopt(pycurl.PROXY, "{}".format(opt["--proxy"])) - - # get bearer token - bearer_token = get_bearer_token(conn, opt) -@@ -169,6 +170,15 @@ def define_new_opts(): - "shortdesc" : "Region", - "order" : 0 - } -+ all_opt["proxy"] = { -+ "getopt" : ":", -+ "longopt" : "proxy", -+ "help" : "--proxy=[http://:] Proxy: 'http://:'", -+ "required" : "0", -+ "default": "", -+ "shortdesc" : "Network proxy", -+ "order" : 0 -+ } - all_opt["limit"] = { - "getopt" : ":", - "longopt" : "limit", -@@ -185,6 +195,7 @@ def main(): - "apikey", - "region", - "limit", -+ "proxy", - "port", - "no_password", - ] -diff --git a/tests/data/metadata/fence_ibm_vpc.xml b/tests/data/metadata/fence_ibm_vpc.xml -index ee7151673..09da0e303 100644 ---- a/tests/data/metadata/fence_ibm_vpc.xml -+++ b/tests/data/metadata/fence_ibm_vpc.xml -@@ -13,6 +13,11 @@ - - Number of nodes returned by API - -+ -+ -+ -+ Network proxy -+ - - - diff --git a/SOURCES/bz2092921-fence_ibm_powervs-proxy-private-api-servers.patch b/SOURCES/bz2092921-fence_ibm_powervs-proxy-private-api-servers.patch new file mode 100644 index 0000000..36f07fc --- /dev/null +++ b/SOURCES/bz2092921-fence_ibm_powervs-proxy-private-api-servers.patch @@ -0,0 +1,455 @@ +From 98fec5c6d55369ad681abc0cde0d8677835957ab Mon Sep 17 00:00:00 2001 +From: Arnold Beilmann +Date: Thu, 5 May 2022 15:26:22 +0200 +Subject: [PATCH 1/2] modified for PowerVS + +--- + agents/ibm_powervs/fence_ibm_powervs.py | 108 +++++++++++++++++++----- + 1 file changed, 89 insertions(+), 19 deletions(-) + +diff --git a/agents/ibm_powervs/fence_ibm_powervs.py b/agents/ibm_powervs/fence_ibm_powervs.py +index 6649771ea..727009220 100755 +--- a/agents/ibm_powervs/fence_ibm_powervs.py ++++ b/agents/ibm_powervs/fence_ibm_powervs.py +@@ -1,10 +1,11 @@ +-#!@PYTHON@ -tt ++#!/usr/libexec/platform-python -tt + + import sys + import pycurl, io, json + import logging + import atexit +-sys.path.append("@FENCEAGENTSLIBDIR@") ++import time ++sys.path.append("/usr/share/fence") + from fencing import * + from fencing import fail, run_delay, EC_LOGIN_DENIED, EC_STATUS + +@@ -14,16 +15,30 @@ + "ERROR": "unknown" + } + ++def get_token(conn, options): ++ ++ try: ++ command = "identity/token" ++ action = "grant_type=urn%3Aibm%3Aparams%3Aoauth%3Agrant-type%3Aapikey&apikey={}".format(options["--token"]) ++ res = send_command(conn, command, "POST", action, printResult=False) ++ except Exception as e: ++ logging.debug("Failed: {}".format(e)) ++ return "TOKEN_IS_MISSING_OR_WRONG" ++ ++ #if "--verbose" in options: ++ # logging.debug(json.dumps(res, indent=2)) ++ ++ return res["access_token"] ++ + def get_list(conn, options): + outlets = {} +- ++ + try: + command = "cloud-instances/{}/pvm-instances".format(options["--instance"]) + res = send_command(conn, command) + except Exception as e: + logging.debug("Failed: {}".format(e)) + return outlets +- + for r in res["pvmInstances"]: + if "--verbose" in options: + logging.debug(json.dumps(r, indent=2)) +@@ -32,6 +47,7 @@ def get_list(conn, options): + return outlets + + def get_power_status(conn, options): ++ + try: + command = "cloud-instances/{}/pvm-instances/{}".format( + options["--instance"], options["--plug"]) +@@ -40,10 +56,11 @@ def get_power_status(conn, options): + except KeyError as e: + logging.debug("Failed: Unable to get status for {}".format(e)) + fail(EC_STATUS) +- ++ + return result + + def set_power_status(conn, options): ++ + action = { + "on" : '{"action" : "start"}', + "off" : '{"action" : "immediate-shutdown"}', +@@ -56,35 +73,63 @@ def set_power_status(conn, options): + logging.debug("Failed: Unable to set power to {} for {}".format(options["--action"], e)) + fail(EC_STATUS) + +-def connect(opt): ++def connect(opt, token): + conn = pycurl.Curl() + + ## setup correct URL +- conn.base_url = "https://" + opt["--region"] + ".power-iaas.cloud.ibm.com/pcloud/v1/" ++ conn.base_url = "https://private." + opt["--region"] + ".power-iaas.cloud.ibm.com/pcloud/v1/" ++ if opt["--api-type"] == "public": ++ conn.base_url = "https://" + opt["--region"] + ".power-iaas.cloud.ibm.com/pcloud/v1/" + + if opt["--verbose-level"] > 1: +- conn.setopt(pycurl.VERBOSE, 1) ++ conn.setopt(pycurl.VERBOSE, 0) + ++ conn.setopt(pycurl.CONNECTTIMEOUT,int(opt["--shell-timeout"])) + conn.setopt(pycurl.TIMEOUT, int(opt["--shell-timeout"])) + conn.setopt(pycurl.SSL_VERIFYPEER, 1) + conn.setopt(pycurl.SSL_VERIFYHOST, 2) ++ conn.setopt(pycurl.PROXY, "{}".format(opt["--proxy"])) + + # set auth token for later requests + conn.setopt(pycurl.HTTPHEADER, [ + "Content-Type: application/json", +- "Authorization: Bearer {}".format(opt["--token"]), ++ "Authorization: Bearer {}".format(token), + "CRN: {}".format(opt["--crn"]), + "User-Agent: curl", + ]) ++ ++ return conn ++ ++def auth_connect(opt): ++ conn = pycurl.Curl() ++ ++ # setup correct URL ++ conn.base_url = "https://iam.cloud.ibm.com/" ++ ++ if opt["--verbose-level"] > 1: ++ conn.setopt(pycurl.VERBOSE, 1) ++ ++ conn.setopt(pycurl.CONNECTTIMEOUT,int(opt["--shell-timeout"])) ++ conn.setopt(pycurl.TIMEOUT, int(opt["--shell-timeout"])) ++ conn.setopt(pycurl.SSL_VERIFYPEER, 1) ++ conn.setopt(pycurl.SSL_VERIFYHOST, 2) ++ conn.setopt(pycurl.PROXY, "{}".format(opt["--proxy"])) ++ ++ # set auth token for later requests ++ conn.setopt(pycurl.HTTPHEADER, [ ++ "Content-type: application/x-www-form-urlencoded", ++ "Accept: application/json", ++ "User-Agent: curl", ++ ]) + + return conn + + def disconnect(conn): + conn.close() + +-def send_command(conn, command, method="GET", action=None): ++def send_command(conn, command, method="GET", action=None, printResult=True): + url = conn.base_url + command +- ++ + conn.setopt(pycurl.URL, url.encode("ascii")) + + web_buffer = io.BytesIO() +@@ -99,8 +144,10 @@ def send_command(conn, command, method="GET", action=None): + conn.setopt(pycurl.WRITEFUNCTION, web_buffer.write) + + try: ++ time.sleep(3) + conn.perform() + except Exception as e: ++ logging.error("ADD_DEBUG: {}".format(e)) + raise(e) + + rc = conn.getinfo(pycurl.HTTP_CODE) +@@ -110,8 +157,7 @@ def send_command(conn, command, method="GET", action=None): + + if rc != 200: + if len(result) > 0: +- raise Exception("{}: {}".format(rc, +- result["value"]["messages"][0]["default_message"])) ++ raise Exception("{}: {}".format(rc,result)) + else: + raise Exception("Remote returned {} for request to {}".format(rc, url)) + +@@ -121,7 +167,8 @@ def send_command(conn, command, method="GET", action=None): + logging.debug("url: {}".format(url)) + logging.debug("method: {}".format(method)) + logging.debug("response code: {}".format(rc)) +- logging.debug("result: {}\n".format(result)) ++ if printResult: ++ logging.debug("result: {}\n".format(result)) + + return result + +@@ -129,9 +176,9 @@ def define_new_opts(): + all_opt["token"] = { + "getopt" : ":", + "longopt" : "token", +- "help" : "--token=[token] Bearer Token", ++ "help" : "--token=[token] API Token", + "required" : "1", +- "shortdesc" : "Bearer Token", ++ "shortdesc" : "API Token", + "order" : 0 + } + all_opt["crn"] = { +@@ -158,6 +205,22 @@ def define_new_opts(): + "shortdesc" : "Region", + "order" : 0 + } ++ all_opt["api-type"] = { ++ "getopt" : ":", ++ "longopt" : "api-type", ++ "help" : "--api-type=[private|public] API-type: 'private' (default) or 'public'", ++ "required" : "0", ++ "shortdesc" : "API-type (private|public)", ++ "order" : 0 ++ } ++ all_opt["proxy"] = { ++ "getopt" : ":", ++ "longopt" : "proxy", ++ "help" : "--proxy=[http://:] Proxy: 'http://:'", ++ "required" : "0", ++ "shortdesc" : "Network proxy", ++ "order" : 0 ++ } + + + def main(): +@@ -166,6 +229,8 @@ def main(): + "crn", + "instance", + "region", ++ "api-type", ++ "proxy", + "port", + "no_password", + ] +@@ -173,9 +238,11 @@ def main(): + atexit.register(atexit_handler) + define_new_opts() + +- all_opt["shell_timeout"]["default"] = "15" ++ all_opt["shell_timeout"]["default"] = "500" + all_opt["power_timeout"]["default"] = "30" + all_opt["power_wait"]["default"] = "1" ++ all_opt["api-type"]["default"] = "private" ++ all_opt["proxy"]["default"] = "" + + options = check_input(device_opt, process_input(device_opt)) + +@@ -190,8 +257,11 @@ def main(): + ## Fence operations + #### + run_delay(options) +- +- conn = connect(options) ++ ++ auth_conn = auth_connect(options) ++ token = get_token(auth_conn, options) ++ disconnect(auth_conn) ++ conn = connect(options, token) + atexit.register(disconnect, conn) + + result = fence_action(conn, options, set_power_status, get_power_status, get_list) + +From fbe9a539ac8f40686a8027b7e768d9f7b799e485 Mon Sep 17 00:00:00 2001 +From: Oyvind Albrigtsen +Date: Fri, 6 May 2022 11:22:47 +0200 +Subject: [PATCH 2/2] fence_ibm_powervs: cleanup and fixes + +--- + agents/ibm_powervs/fence_ibm_powervs.py | 37 ++++++++++------------- + tests/data/metadata/fence_ibm_powervs.xml | 19 ++++++++++-- + 2 files changed, 33 insertions(+), 23 deletions(-) + +diff --git a/agents/ibm_powervs/fence_ibm_powervs.py b/agents/ibm_powervs/fence_ibm_powervs.py +index 727009220..819ab8896 100755 +--- a/agents/ibm_powervs/fence_ibm_powervs.py ++++ b/agents/ibm_powervs/fence_ibm_powervs.py +@@ -1,11 +1,11 @@ +-#!/usr/libexec/platform-python -tt ++#!@PYTHON@ -tt + + import sys + import pycurl, io, json + import logging + import atexit + import time +-sys.path.append("/usr/share/fence") ++sys.path.append("@FENCEAGENTSLIBDIR@") + from fencing import * + from fencing import fail, run_delay, EC_LOGIN_DENIED, EC_STATUS + +@@ -16,7 +16,6 @@ + } + + def get_token(conn, options): +- + try: + command = "identity/token" + action = "grant_type=urn%3Aibm%3Aparams%3Aoauth%3Agrant-type%3Aapikey&apikey={}".format(options["--token"]) +@@ -25,20 +24,18 @@ def get_token(conn, options): + logging.debug("Failed: {}".format(e)) + return "TOKEN_IS_MISSING_OR_WRONG" + +- #if "--verbose" in options: +- # logging.debug(json.dumps(res, indent=2)) +- + return res["access_token"] + + def get_list(conn, options): + outlets = {} +- ++ + try: + command = "cloud-instances/{}/pvm-instances".format(options["--instance"]) + res = send_command(conn, command) + except Exception as e: + logging.debug("Failed: {}".format(e)) + return outlets ++ + for r in res["pvmInstances"]: + if "--verbose" in options: + logging.debug(json.dumps(r, indent=2)) +@@ -47,7 +44,6 @@ def get_list(conn, options): + return outlets + + def get_power_status(conn, options): +- + try: + command = "cloud-instances/{}/pvm-instances/{}".format( + options["--instance"], options["--plug"]) +@@ -56,11 +52,10 @@ def get_power_status(conn, options): + except KeyError as e: + logging.debug("Failed: Unable to get status for {}".format(e)) + fail(EC_STATUS) +- ++ + return result + + def set_power_status(conn, options): +- + action = { + "on" : '{"action" : "start"}', + "off" : '{"action" : "immediate-shutdown"}', +@@ -77,11 +72,11 @@ def connect(opt, token): + conn = pycurl.Curl() + + ## setup correct URL +- conn.base_url = "https://private." + opt["--region"] + ".power-iaas.cloud.ibm.com/pcloud/v1/" +- if opt["--api-type"] == "public": +- conn.base_url = "https://" + opt["--region"] + ".power-iaas.cloud.ibm.com/pcloud/v1/" ++ conn.base_url = "https://" + opt["--region"] + ".power-iaas.cloud.ibm.com/pcloud/v1/" ++ if opt["--api-type"] == "private": ++ conn.base_url = "https://private." + opt["--region"] + ".power-iaas.cloud.ibm.com/pcloud/v1/" + +- if opt["--verbose-level"] > 1: ++ if opt["--verbose-level"] < 3: + conn.setopt(pycurl.VERBOSE, 0) + + conn.setopt(pycurl.CONNECTTIMEOUT,int(opt["--shell-timeout"])) +@@ -129,7 +124,7 @@ def disconnect(conn): + + def send_command(conn, command, method="GET", action=None, printResult=True): + url = conn.base_url + command +- ++ + conn.setopt(pycurl.URL, url.encode("ascii")) + + web_buffer = io.BytesIO() +@@ -144,10 +139,9 @@ def send_command(conn, command, method="GET", action=None, printResult=True): + conn.setopt(pycurl.WRITEFUNCTION, web_buffer.write) + + try: +- time.sleep(3) + conn.perform() + except Exception as e: +- logging.error("ADD_DEBUG: {}".format(e)) ++ logging.error("send_command(): {}".format(e)) + raise(e) + + rc = conn.getinfo(pycurl.HTTP_CODE) +@@ -208,9 +202,9 @@ def define_new_opts(): + all_opt["api-type"] = { + "getopt" : ":", + "longopt" : "api-type", +- "help" : "--api-type=[private|public] API-type: 'private' (default) or 'public'", ++ "help" : "--api-type=[public|private] API-type: 'public' (default) or 'private'", + "required" : "0", +- "shortdesc" : "API-type (private|public)", ++ "shortdesc" : "API-type (public|private)", + "order" : 0 + } + all_opt["proxy"] = { +@@ -238,9 +232,10 @@ def main(): + atexit.register(atexit_handler) + define_new_opts() + +- all_opt["shell_timeout"]["default"] = "500" ++ all_opt["shell_timeout"]["default"] = "15" + all_opt["power_timeout"]["default"] = "30" + all_opt["power_wait"]["default"] = "1" ++ all_opt["stonith_status_sleep"]["default"] = "3" + all_opt["api-type"]["default"] = "private" + all_opt["proxy"]["default"] = "" + +@@ -257,7 +252,7 @@ def main(): + ## Fence operations + #### + run_delay(options) +- ++ + auth_conn = auth_connect(options) + token = get_token(auth_conn, options) + disconnect(auth_conn) +diff --git a/tests/data/metadata/fence_ibm_powervs.xml b/tests/data/metadata/fence_ibm_powervs.xml +index fe86331bd..81cea4379 100644 +--- a/tests/data/metadata/fence_ibm_powervs.xml ++++ b/tests/data/metadata/fence_ibm_powervs.xml +@@ -3,6 +3,16 @@ + fence_ibm_powervs is an I/O Fencing agent which can be used with IBM PowerVS to fence virtual machines. + https://www.ibm.com + ++ ++ ++ ++ API-type (public|private) ++ ++ ++ ++ ++ API-type (public|private) ++ + + + +@@ -13,6 +23,11 @@ + + PowerVS Instance + ++ ++ ++ ++ Network proxy ++ + + + +@@ -21,7 +36,7 @@ + + + +- Bearer Token ++ API Token + + + +@@ -110,7 +125,7 @@ + + + +- ++ + Sleep X seconds between status calls during a STONITH action + + diff --git a/SOURCES/bz2100152-fence_ibm_powervs-proxy-private-api-servers.patch b/SOURCES/bz2100152-fence_ibm_powervs-proxy-private-api-servers.patch deleted file mode 100644 index 36f07fc..0000000 --- a/SOURCES/bz2100152-fence_ibm_powervs-proxy-private-api-servers.patch +++ /dev/null @@ -1,455 +0,0 @@ -From 98fec5c6d55369ad681abc0cde0d8677835957ab Mon Sep 17 00:00:00 2001 -From: Arnold Beilmann -Date: Thu, 5 May 2022 15:26:22 +0200 -Subject: [PATCH 1/2] modified for PowerVS - ---- - agents/ibm_powervs/fence_ibm_powervs.py | 108 +++++++++++++++++++----- - 1 file changed, 89 insertions(+), 19 deletions(-) - -diff --git a/agents/ibm_powervs/fence_ibm_powervs.py b/agents/ibm_powervs/fence_ibm_powervs.py -index 6649771ea..727009220 100755 ---- a/agents/ibm_powervs/fence_ibm_powervs.py -+++ b/agents/ibm_powervs/fence_ibm_powervs.py -@@ -1,10 +1,11 @@ --#!@PYTHON@ -tt -+#!/usr/libexec/platform-python -tt - - import sys - import pycurl, io, json - import logging - import atexit --sys.path.append("@FENCEAGENTSLIBDIR@") -+import time -+sys.path.append("/usr/share/fence") - from fencing import * - from fencing import fail, run_delay, EC_LOGIN_DENIED, EC_STATUS - -@@ -14,16 +15,30 @@ - "ERROR": "unknown" - } - -+def get_token(conn, options): -+ -+ try: -+ command = "identity/token" -+ action = "grant_type=urn%3Aibm%3Aparams%3Aoauth%3Agrant-type%3Aapikey&apikey={}".format(options["--token"]) -+ res = send_command(conn, command, "POST", action, printResult=False) -+ except Exception as e: -+ logging.debug("Failed: {}".format(e)) -+ return "TOKEN_IS_MISSING_OR_WRONG" -+ -+ #if "--verbose" in options: -+ # logging.debug(json.dumps(res, indent=2)) -+ -+ return res["access_token"] -+ - def get_list(conn, options): - outlets = {} -- -+ - try: - command = "cloud-instances/{}/pvm-instances".format(options["--instance"]) - res = send_command(conn, command) - except Exception as e: - logging.debug("Failed: {}".format(e)) - return outlets -- - for r in res["pvmInstances"]: - if "--verbose" in options: - logging.debug(json.dumps(r, indent=2)) -@@ -32,6 +47,7 @@ def get_list(conn, options): - return outlets - - def get_power_status(conn, options): -+ - try: - command = "cloud-instances/{}/pvm-instances/{}".format( - options["--instance"], options["--plug"]) -@@ -40,10 +56,11 @@ def get_power_status(conn, options): - except KeyError as e: - logging.debug("Failed: Unable to get status for {}".format(e)) - fail(EC_STATUS) -- -+ - return result - - def set_power_status(conn, options): -+ - action = { - "on" : '{"action" : "start"}', - "off" : '{"action" : "immediate-shutdown"}', -@@ -56,35 +73,63 @@ def set_power_status(conn, options): - logging.debug("Failed: Unable to set power to {} for {}".format(options["--action"], e)) - fail(EC_STATUS) - --def connect(opt): -+def connect(opt, token): - conn = pycurl.Curl() - - ## setup correct URL -- conn.base_url = "https://" + opt["--region"] + ".power-iaas.cloud.ibm.com/pcloud/v1/" -+ conn.base_url = "https://private." + opt["--region"] + ".power-iaas.cloud.ibm.com/pcloud/v1/" -+ if opt["--api-type"] == "public": -+ conn.base_url = "https://" + opt["--region"] + ".power-iaas.cloud.ibm.com/pcloud/v1/" - - if opt["--verbose-level"] > 1: -- conn.setopt(pycurl.VERBOSE, 1) -+ conn.setopt(pycurl.VERBOSE, 0) - -+ conn.setopt(pycurl.CONNECTTIMEOUT,int(opt["--shell-timeout"])) - conn.setopt(pycurl.TIMEOUT, int(opt["--shell-timeout"])) - conn.setopt(pycurl.SSL_VERIFYPEER, 1) - conn.setopt(pycurl.SSL_VERIFYHOST, 2) -+ conn.setopt(pycurl.PROXY, "{}".format(opt["--proxy"])) - - # set auth token for later requests - conn.setopt(pycurl.HTTPHEADER, [ - "Content-Type: application/json", -- "Authorization: Bearer {}".format(opt["--token"]), -+ "Authorization: Bearer {}".format(token), - "CRN: {}".format(opt["--crn"]), - "User-Agent: curl", - ]) -+ -+ return conn -+ -+def auth_connect(opt): -+ conn = pycurl.Curl() -+ -+ # setup correct URL -+ conn.base_url = "https://iam.cloud.ibm.com/" -+ -+ if opt["--verbose-level"] > 1: -+ conn.setopt(pycurl.VERBOSE, 1) -+ -+ conn.setopt(pycurl.CONNECTTIMEOUT,int(opt["--shell-timeout"])) -+ conn.setopt(pycurl.TIMEOUT, int(opt["--shell-timeout"])) -+ conn.setopt(pycurl.SSL_VERIFYPEER, 1) -+ conn.setopt(pycurl.SSL_VERIFYHOST, 2) -+ conn.setopt(pycurl.PROXY, "{}".format(opt["--proxy"])) -+ -+ # set auth token for later requests -+ conn.setopt(pycurl.HTTPHEADER, [ -+ "Content-type: application/x-www-form-urlencoded", -+ "Accept: application/json", -+ "User-Agent: curl", -+ ]) - - return conn - - def disconnect(conn): - conn.close() - --def send_command(conn, command, method="GET", action=None): -+def send_command(conn, command, method="GET", action=None, printResult=True): - url = conn.base_url + command -- -+ - conn.setopt(pycurl.URL, url.encode("ascii")) - - web_buffer = io.BytesIO() -@@ -99,8 +144,10 @@ def send_command(conn, command, method="GET", action=None): - conn.setopt(pycurl.WRITEFUNCTION, web_buffer.write) - - try: -+ time.sleep(3) - conn.perform() - except Exception as e: -+ logging.error("ADD_DEBUG: {}".format(e)) - raise(e) - - rc = conn.getinfo(pycurl.HTTP_CODE) -@@ -110,8 +157,7 @@ def send_command(conn, command, method="GET", action=None): - - if rc != 200: - if len(result) > 0: -- raise Exception("{}: {}".format(rc, -- result["value"]["messages"][0]["default_message"])) -+ raise Exception("{}: {}".format(rc,result)) - else: - raise Exception("Remote returned {} for request to {}".format(rc, url)) - -@@ -121,7 +167,8 @@ def send_command(conn, command, method="GET", action=None): - logging.debug("url: {}".format(url)) - logging.debug("method: {}".format(method)) - logging.debug("response code: {}".format(rc)) -- logging.debug("result: {}\n".format(result)) -+ if printResult: -+ logging.debug("result: {}\n".format(result)) - - return result - -@@ -129,9 +176,9 @@ def define_new_opts(): - all_opt["token"] = { - "getopt" : ":", - "longopt" : "token", -- "help" : "--token=[token] Bearer Token", -+ "help" : "--token=[token] API Token", - "required" : "1", -- "shortdesc" : "Bearer Token", -+ "shortdesc" : "API Token", - "order" : 0 - } - all_opt["crn"] = { -@@ -158,6 +205,22 @@ def define_new_opts(): - "shortdesc" : "Region", - "order" : 0 - } -+ all_opt["api-type"] = { -+ "getopt" : ":", -+ "longopt" : "api-type", -+ "help" : "--api-type=[private|public] API-type: 'private' (default) or 'public'", -+ "required" : "0", -+ "shortdesc" : "API-type (private|public)", -+ "order" : 0 -+ } -+ all_opt["proxy"] = { -+ "getopt" : ":", -+ "longopt" : "proxy", -+ "help" : "--proxy=[http://:] Proxy: 'http://:'", -+ "required" : "0", -+ "shortdesc" : "Network proxy", -+ "order" : 0 -+ } - - - def main(): -@@ -166,6 +229,8 @@ def main(): - "crn", - "instance", - "region", -+ "api-type", -+ "proxy", - "port", - "no_password", - ] -@@ -173,9 +238,11 @@ def main(): - atexit.register(atexit_handler) - define_new_opts() - -- all_opt["shell_timeout"]["default"] = "15" -+ all_opt["shell_timeout"]["default"] = "500" - all_opt["power_timeout"]["default"] = "30" - all_opt["power_wait"]["default"] = "1" -+ all_opt["api-type"]["default"] = "private" -+ all_opt["proxy"]["default"] = "" - - options = check_input(device_opt, process_input(device_opt)) - -@@ -190,8 +257,11 @@ def main(): - ## Fence operations - #### - run_delay(options) -- -- conn = connect(options) -+ -+ auth_conn = auth_connect(options) -+ token = get_token(auth_conn, options) -+ disconnect(auth_conn) -+ conn = connect(options, token) - atexit.register(disconnect, conn) - - result = fence_action(conn, options, set_power_status, get_power_status, get_list) - -From fbe9a539ac8f40686a8027b7e768d9f7b799e485 Mon Sep 17 00:00:00 2001 -From: Oyvind Albrigtsen -Date: Fri, 6 May 2022 11:22:47 +0200 -Subject: [PATCH 2/2] fence_ibm_powervs: cleanup and fixes - ---- - agents/ibm_powervs/fence_ibm_powervs.py | 37 ++++++++++------------- - tests/data/metadata/fence_ibm_powervs.xml | 19 ++++++++++-- - 2 files changed, 33 insertions(+), 23 deletions(-) - -diff --git a/agents/ibm_powervs/fence_ibm_powervs.py b/agents/ibm_powervs/fence_ibm_powervs.py -index 727009220..819ab8896 100755 ---- a/agents/ibm_powervs/fence_ibm_powervs.py -+++ b/agents/ibm_powervs/fence_ibm_powervs.py -@@ -1,11 +1,11 @@ --#!/usr/libexec/platform-python -tt -+#!@PYTHON@ -tt - - import sys - import pycurl, io, json - import logging - import atexit - import time --sys.path.append("/usr/share/fence") -+sys.path.append("@FENCEAGENTSLIBDIR@") - from fencing import * - from fencing import fail, run_delay, EC_LOGIN_DENIED, EC_STATUS - -@@ -16,7 +16,6 @@ - } - - def get_token(conn, options): -- - try: - command = "identity/token" - action = "grant_type=urn%3Aibm%3Aparams%3Aoauth%3Agrant-type%3Aapikey&apikey={}".format(options["--token"]) -@@ -25,20 +24,18 @@ def get_token(conn, options): - logging.debug("Failed: {}".format(e)) - return "TOKEN_IS_MISSING_OR_WRONG" - -- #if "--verbose" in options: -- # logging.debug(json.dumps(res, indent=2)) -- - return res["access_token"] - - def get_list(conn, options): - outlets = {} -- -+ - try: - command = "cloud-instances/{}/pvm-instances".format(options["--instance"]) - res = send_command(conn, command) - except Exception as e: - logging.debug("Failed: {}".format(e)) - return outlets -+ - for r in res["pvmInstances"]: - if "--verbose" in options: - logging.debug(json.dumps(r, indent=2)) -@@ -47,7 +44,6 @@ def get_list(conn, options): - return outlets - - def get_power_status(conn, options): -- - try: - command = "cloud-instances/{}/pvm-instances/{}".format( - options["--instance"], options["--plug"]) -@@ -56,11 +52,10 @@ def get_power_status(conn, options): - except KeyError as e: - logging.debug("Failed: Unable to get status for {}".format(e)) - fail(EC_STATUS) -- -+ - return result - - def set_power_status(conn, options): -- - action = { - "on" : '{"action" : "start"}', - "off" : '{"action" : "immediate-shutdown"}', -@@ -77,11 +72,11 @@ def connect(opt, token): - conn = pycurl.Curl() - - ## setup correct URL -- conn.base_url = "https://private." + opt["--region"] + ".power-iaas.cloud.ibm.com/pcloud/v1/" -- if opt["--api-type"] == "public": -- conn.base_url = "https://" + opt["--region"] + ".power-iaas.cloud.ibm.com/pcloud/v1/" -+ conn.base_url = "https://" + opt["--region"] + ".power-iaas.cloud.ibm.com/pcloud/v1/" -+ if opt["--api-type"] == "private": -+ conn.base_url = "https://private." + opt["--region"] + ".power-iaas.cloud.ibm.com/pcloud/v1/" - -- if opt["--verbose-level"] > 1: -+ if opt["--verbose-level"] < 3: - conn.setopt(pycurl.VERBOSE, 0) - - conn.setopt(pycurl.CONNECTTIMEOUT,int(opt["--shell-timeout"])) -@@ -129,7 +124,7 @@ def disconnect(conn): - - def send_command(conn, command, method="GET", action=None, printResult=True): - url = conn.base_url + command -- -+ - conn.setopt(pycurl.URL, url.encode("ascii")) - - web_buffer = io.BytesIO() -@@ -144,10 +139,9 @@ def send_command(conn, command, method="GET", action=None, printResult=True): - conn.setopt(pycurl.WRITEFUNCTION, web_buffer.write) - - try: -- time.sleep(3) - conn.perform() - except Exception as e: -- logging.error("ADD_DEBUG: {}".format(e)) -+ logging.error("send_command(): {}".format(e)) - raise(e) - - rc = conn.getinfo(pycurl.HTTP_CODE) -@@ -208,9 +202,9 @@ def define_new_opts(): - all_opt["api-type"] = { - "getopt" : ":", - "longopt" : "api-type", -- "help" : "--api-type=[private|public] API-type: 'private' (default) or 'public'", -+ "help" : "--api-type=[public|private] API-type: 'public' (default) or 'private'", - "required" : "0", -- "shortdesc" : "API-type (private|public)", -+ "shortdesc" : "API-type (public|private)", - "order" : 0 - } - all_opt["proxy"] = { -@@ -238,9 +232,10 @@ def main(): - atexit.register(atexit_handler) - define_new_opts() - -- all_opt["shell_timeout"]["default"] = "500" -+ all_opt["shell_timeout"]["default"] = "15" - all_opt["power_timeout"]["default"] = "30" - all_opt["power_wait"]["default"] = "1" -+ all_opt["stonith_status_sleep"]["default"] = "3" - all_opt["api-type"]["default"] = "private" - all_opt["proxy"]["default"] = "" - -@@ -257,7 +252,7 @@ def main(): - ## Fence operations - #### - run_delay(options) -- -+ - auth_conn = auth_connect(options) - token = get_token(auth_conn, options) - disconnect(auth_conn) -diff --git a/tests/data/metadata/fence_ibm_powervs.xml b/tests/data/metadata/fence_ibm_powervs.xml -index fe86331bd..81cea4379 100644 ---- a/tests/data/metadata/fence_ibm_powervs.xml -+++ b/tests/data/metadata/fence_ibm_powervs.xml -@@ -3,6 +3,16 @@ - fence_ibm_powervs is an I/O Fencing agent which can be used with IBM PowerVS to fence virtual machines. - https://www.ibm.com - -+ -+ -+ -+ API-type (public|private) -+ -+ -+ -+ -+ API-type (public|private) -+ - - - -@@ -13,6 +23,11 @@ - - PowerVS Instance - -+ -+ -+ -+ Network proxy -+ - - - -@@ -21,7 +36,7 @@ - - - -- Bearer Token -+ API Token - - - -@@ -110,7 +125,7 @@ - - - -- -+ - Sleep X seconds between status calls during a STONITH action - - diff --git a/SPECS/fence-agents.spec b/SPECS/fence-agents.spec index 993b3dc..091209f 100644 --- a/SPECS/fence-agents.spec +++ b/SPECS/fence-agents.spec @@ -87,7 +87,7 @@ Name: fence-agents Summary: Set of unified programs capable of host isolation ("fencing") Version: 4.2.1 -Release: 89%{?alphatag:.%{alphatag}}%{?dist}.3 +Release: 103%{?alphatag:.%{alphatag}}%{?dist} License: GPLv2+ and LGPLv2+ Group: System Environment/Base URL: https://github.com/ClusterLabs/fence-agents @@ -136,8 +136,7 @@ Source33: %{setuptools}-%{setuptools_version}.tar.gz ## required for installation Source34: setuptools_scm-6.3.2.tar.gz Source35: packaging-21.2-py3-none-any.whl -Source36: pyparsing-3.0.1.tar.gz -Source37: tomli-1.0.1.tar.gz +Source36: tomli-1.0.1.tar.gz ### END Patch0: fence_impilan-fence_ilo_ssh-add-ilo5-support.patch @@ -249,19 +248,25 @@ Patch105: bz1977588-4-fence_kubevirt-power-timeout-40s.patch Patch106: bz1963163-fence_zvmip-add-ssl-tls-support.patch Patch107: bz1977588-5-fence_kubevirt-get-namespace-from-context.patch Patch108: bz2048857-fence_aws-botocore-bundled.patch -Patch109: bz2083254-fence_ibm_vpc-fix-parameters.patch -Patch110: bz2086838-1-fence_apc-fence_ilo_moonshot-import-logging.patch -Patch111: bz2086838-2-fence_lpar-fix-import-fail_usage.patch -Patch112: bz2092460-fence_ibm_vpc-add-proxy-support.patch -Patch113: bz2100152-fence_ibm_powervs-proxy-private-api-servers.patch +Patch109: bz1886074-1-fencing-source_env.patch +Patch110: bz1886074-2-fence_openstack.patch +Patch111: bz2072421-1-all-agents-unify-ssl-parameters.patch +Patch112: bz2078244-fence_gce-update.patch +Patch113: bz2080994-fence_ibm_vpc-fix-parameters.patch +Patch114: bz2080729-1-fence_apc-fence_ilo_moonshot-import-logging.patch +Patch115: bz2080729-2-fence_lpar-fix-import-fail_usage.patch +Patch116: bz2072421-2-fence_zvmip-connect-error.patch +Patch117: bz2091826-fence_ibm_vpc-add-proxy-support.patch +Patch118: bz2092921-fence_ibm_powervs-proxy-private-api-servers.patch +Patch119: bz1886074-4-fencing-source_env-dont-process-empty-lines.patch %if 0%{?fedora} || 0%{?rhel} > 7 %global supportedagents amt_ws apc apc_snmp bladecenter brocade cisco_mds cisco_ucs compute drac5 eaton_snmp emerson eps evacuate hds_cb hpblade ibmblade ibm_powervs ibm_vpc ifmib ilo ilo_moonshot ilo_mp ilo_ssh intelmodular ipdu ipmilan kdump kubevirt lpar mpath redfish rhevm rsa rsb sbd scsi vmware_rest vmware_soap wti %ifarch x86_64 -%global testagents virsh heuristics_ping aliyun aws azure_arm gce +%global testagents virsh heuristics_ping aliyun aws azure_arm gce openstack %endif %ifarch ppc64le -%global testagents virsh heuristics_ping +%global testagents virsh heuristics_ping openstack %endif %ifarch s390x %global testagents virsh zvm heuristics_ping @@ -445,6 +450,12 @@ BuildRequires: python3-google-api-client python3-pip python3-wheel python3-jinja %patch111 -p1 %patch112 -p1 %patch113 -p1 +%patch114 -p1 +%patch115 -p1 +%patch116 -p1 +%patch117 -p1 +%patch118 -p1 +%patch119 -p1 # prevent compilation of something that won't get used anyway sed -i.orig 's|FENCE_ZVM=1|FENCE_ZVM=0|' configure.ac @@ -1227,6 +1238,23 @@ Device Mapper Multipath. %{_datadir}/cluster/fence_mpath_check* %{_mandir}/man8/fence_mpath.8* +%ifarch x86_64 ppc64le +%package openstack +License: GPLv2+ and LGPLv2+ and ASL 2.0 and MIT and Python +Summary: Fence agent for OpenStack's Nova service +%if 0%{?fedora} || 0%{?centos} > 7 || 0%{?rhel} > 7 || 0%{?suse_version} +Requires: python3-requests +%else +Requires: python-requests +%endif +Requires: fence-agents-common = %{version}-%{release} +%description openstack +Fence agent for OpenStack's Nova service. +%files openstack +%{_sbindir}/fence_openstack +%{_mandir}/man8/fence_openstack.8* +%endif + %package redfish License: GPLv2+ and LGPLv2+ Group: System Environment/Base @@ -1411,23 +1439,38 @@ Fence agent for IBM z/VM over IP. %endif %changelog -* Thu Jun 23 2022 Oyvind Albrigtsen - 4.2.1-89.3 -- fence_ibm_vpc: add proxy support - Resolves: rhbz#2092460 +* Tue Aug 16 2022 Oyvind Albrigtsen - 4.2.1-103 +- fence_openstack: new fence agent + Resolves: rhbz#1886074 + +* Wed Jun 22 2022 Oyvind Albrigtsen - 4.2.1-99 - fence_ibm_powervs: add support for proxy, private API servers and get token via API key - Resolves: rhbz#2100152 + Resolves: rhbz#2092921 + +* Tue Jun 7 2022 Oyvind Albrigtsen - 4.2.1-98 +- fence_ibm_vpc: add proxy support + Resolves: rhbz#2091826 + +* Tue May 31 2022 Oyvind Albrigtsen - 4.2.1-96 +- all agents: unify ssl parameters to avoid having to use --ssl when + using --ssl-secure/--ssl-insecure for some agents + Resolves: rhbz#2072421 -* Tue May 17 2022 Oyvind Albrigtsen - 4.2.1-89.2 +* Tue May 17 2022 Oyvind Albrigtsen - 4.2.1-95 - fence_apc/fence_ilo_moonshot/fence_lpar: add missing "import logging" - Resolves: rhbz#2086838 + Resolves: rhbz#2080729 -* Tue May 10 2022 Oyvind Albrigtsen - 4.2.1-89.1 +* Thu May 5 2022 Oyvind Albrigtsen - 4.2.1-94 - fence_ibm_vpc: remove unused instance parameter and make limit optional - Resolves: rhbz#2083254 + Resolves: rhbz#2080994 + +* Thu Apr 28 2022 Oyvind Albrigtsen - 4.2.1-93 +- fence_gce: update fence agent + Resolves: rhbz#2078244 -* Fri Mar 4 2022 Oyvind Albrigtsen - 4.2.1-89 +* Wed Apr 27 2022 Oyvind Albrigtsen - 4.2.1-92 - fence_ibm_vpc: new fence agent Resolves: rhbz#1728203