From 14fa25702654eaf4b9845c7c1933b9a6021df836 Mon Sep 17 00:00:00 2001 From: Oyvind Albrigtsen Date: Fri, 29 Apr 2022 10:31:31 +0200 Subject: [PATCH] - all agents: unify ssl parameters to avoid having to use --ssl when using --ssl-secure/--ssl-insecure for some agents Resolves: rhbz#2072420 - fence_gce: update fence agent Resolves: rhbz#2079889 --- ...2420-all-agents-unify-ssl-parameters.patch | 291 +++++++++++++ bz2079889-fence_gce-update.patch | 410 ++++++++++++++++++ fence-agents.spec | 13 +- 3 files changed, 713 insertions(+), 1 deletion(-) create mode 100644 bz2072420-all-agents-unify-ssl-parameters.patch create mode 100644 bz2079889-fence_gce-update.patch diff --git a/bz2072420-all-agents-unify-ssl-parameters.patch b/bz2072420-all-agents-unify-ssl-parameters.patch new file mode 100644 index 0000000..6f8f0af --- /dev/null +++ b/bz2072420-all-agents-unify-ssl-parameters.patch @@ -0,0 +1,291 @@ +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/ibmz/fence_ibmz.py | 4 ++-- + agents/rhevm/fence_rhevm.py | 8 ++++---- + agents/skalar/fence_skalar.py | 2 +- + 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/ibmz/fence_ibmz.py b/agents/ibmz/fence_ibmz.py +index 47408ccf4..d477adeb9 100644 +--- a/agents/ibmz/fence_ibmz.py ++++ b/agents/ibmz/fence_ibmz.py +@@ -534,7 +534,7 @@ def main(): + requests_log.propagate = True + if "--verbose" in options: + requests_log.setLevel(logging.DEBUG) +- if "--ssl-secure" not in options: ++ if "--ssl-insecure" in options: + urllib3.disable_warnings( + category=urllib3.exceptions.InsecureRequestWarning) + +@@ -548,7 +548,7 @@ def main(): + 'connect_timeout': int(options['--connect-timeout']), + 'read_timeout': int(options['--read-timeout']), + 'port': int(options['--ipport']), +- 'ssl_verify': bool('--ssl-secure' in options), ++ 'ssl_verify': bool('--ssl-insecure' not in options), + } + try: + conn = APIClient(hmc_address, hmc_userid, hmc_password, config) +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/skalar/fence_skalar.py b/agents/skalar/fence_skalar.py +index 959527411..0e11d83f9 100644 +--- a/agents/skalar/fence_skalar.py ++++ b/agents/skalar/fence_skalar.py +@@ -200,7 +200,7 @@ def main(): + run_delay(options) + + proto = "https://" +- if "--ssl" in options or "--ssl-secure" in options: ++ if "--ssl-secure" in options: + ssl_verify = True + elif "--ssl-insecure" in options: + ssl_verify = False +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 = "" + +@@ -182,7 +182,9 @@ def main(): + options = check_input(device_opt, process_input(device_opt), other_conditions=True) + + if "--disable-ssl" in options or options["--ssl"] == "0": +- del options["--ssl"] ++ for k in ["--ssl", "--ssl-secure", "--ssl-insecure"]: ++ if k in options: ++ del options[k] + + if len(options.get("--plug", "")) > 8: + fail_usage("Failed: Name of image can not be longer than 8 characters") +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/bz2079889-fence_gce-update.patch b/bz2079889-fence_gce-update.patch new file mode 100644 index 0000000..97e5193 --- /dev/null +++ b/bz2079889-fence_gce-update.patch @@ -0,0 +1,410 @@ +--- fence-agents-4.10.0/agents/gce/fence_gce.py 2022-04-29 10:13:50.317888041 +0200 ++++ fence-agents-4.10.0/agents/gce/fence_gce.py2 2022-04-29 10:17:54.813248566 +0200 +@@ -2,10 +2,10 @@ + + # + # 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 +-# RHEL 8.x: nothing additional needed +-# SLES 12.x: python-google-api-python-client python-oauth2client python-oauth2client-gce +-# SLES 15.x: python3-google-api-python-client python3-oauth2client python3-oauth2client-gce ++# 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 +@@ -27,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/support/google') + import httplib2 +@@ -42,6 +42,19 @@ + + 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 +@@ -66,7 +79,7 @@ + { + "matchlength": 4, + "match": "https://compute.googleapis.com/compute/v1/projects/(.*)/zones/(.*)/instances/(.*)/reset(.*)", +- "replace": "https://baremetalsolution.googleapis.com/v1alpha1/projects/\\1/locations/\\2/instances/\\3:resetInstance\\4" ++ "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 +@@ -121,14 +134,17 @@ + + 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 = retry_api_execute(options, conn.instances().list( +- project=options["--project"], +- zone=options["--zone"])) +- 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 + +@@ -142,23 +158,54 @@ + 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 = retry_api_execute(options, conn.instances().get( +- project=options["--project"], +- zone=options["--zone"], +- instance=options["--plug"])) ++ 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, options, 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 ++ return False ++ ++ wait_time = 0 + project = options["--project"] +- zone = options["--zone"] + while True: + result = retry_api_execute(options, conn.zoneOperations().get( + project=project, +@@ -166,56 +213,93 @@ + 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"); ++ 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 = retry_api_execute(options, conn.instances().stop( +- project=options["--project"], +- zone=options["--zone"], +- instance=options["--plug"])) ++ 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") +- wait_for_operation(conn, options, 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 = retry_api_execute(options, conn.instances().start( +- project=options["--project"], +- zone=options["--zone"], +- instance=options["--plug"])) +- wait_for_operation(conn, options, operation) +- logging.info("Poweron of %s in zone %s complete" % (options["--plug"], options["--zone"])) ++ 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"); ++ 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 = retry_api_execute(options, conn.instances().reset( +- project=options["--project"], +- zone=options["--zone"], +- instance=options["--plug"])) +- logging.info("Reset command completed, waiting for the operation to complete") +- wait_for_operation(conn, options, 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 ++ logging.exception("Failed: power_cycle") ++ raise err + + +-def get_zone(conn, options): ++def get_zone(conn, options, instance): + logging.debug("get_zone"); + project = options['--project'] +- instance = options['--plug'] + fl = 'name="%s"' % instance + request = replace_api_uri(options, conn.instances().aggregatedList(project=project, filter=fl)) + while request is not None: +@@ -227,7 +311,7 @@ + return inst['zone'].split("/")[-1] + request = replace_api_uri(options, conn.instances().aggregatedList_next( + previous_request=request, previous_response=response)) +- raise Exception("Unable to find instance %s" % (instance)) ++ raise_fence_agent(options, "Unable to find instance %s" % (instance)) + + + def get_metadata(metadata_key, params=None, timeout=None): +@@ -326,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" : ":", +@@ -341,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 + } + + +@@ -350,7 +484,8 @@ + + device_opt = ["port", "no_password", "zone", "project", "stackdriver-logging", + "method", "baremetalsolution", "apitimeout", "retries", "retrysleep", +- "serviceaccount", "proxyhost", "proxyport"] ++ "serviceaccount", "plugzonemap", "proxyhost", "proxyport", "earlyexit", ++ "warntimeout", "errortimeout", "runonwarn", "runonfail"] + + atexit.register(atexit_handler) + +@@ -431,22 +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 "--baremetalsolution" in options: + options["--zone"] = "none" +- if not options.get("--zone"): +- try: +- options["--zone"] = get_zone(conn, options) +- except Exception as err: +- fail_usage("Failed retrieving GCE zone. Please provide --zone option: {}".format(str(err))) ++ ++ # 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/fence-agents.spec b/fence-agents.spec index 4b9f63e..7cf8ff1 100644 --- a/fence-agents.spec +++ b/fence-agents.spec @@ -59,7 +59,7 @@ Name: fence-agents Summary: Set of unified programs capable of host isolation ("fencing") Version: 4.10.0 -Release: 21%{?alphatag:.%{alphatag}}%{?dist} +Release: 22%{?alphatag:.%{alphatag}}%{?dist} License: GPLv2+ and LGPLv2+ URL: https://github.com/ClusterLabs/fence-agents Source0: https://fedorahosted.org/releases/f/e/fence-agents/%{name}-%{version}.tar.gz @@ -234,6 +234,8 @@ Patch17: bz2041933-bz2041935-2-fence_openstack-clouds-openrc.patch Patch18: bz2042496-fence_ibm_vpc-fence_ibm_powervs.patch Patch19: bz2022334-fence_zvmip-add-disable-ssl.patch Patch20: bz2065114-fence_lpar-refactor.patch +Patch21: bz2072420-all-agents-unify-ssl-parameters.patch +Patch22: bz2079889-fence_gce-update.patch %global supportedagents amt_ws apc apc_snmp bladecenter brocade cisco_mds cisco_ucs compute drac5 eaton_snmp emerson eps evacuate 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 @@ -365,6 +367,8 @@ BuildRequires: %{systemd_units} %patch18 -p1 %patch19 -p1 %patch20 -p1 +%patch21 -p1 +%patch22 -p1 # prevent compilation of something that won't get used anyway sed -i.orig 's|FENCE_ZVM=1|FENCE_ZVM=0|' configure.ac @@ -1456,6 +1460,13 @@ are located on corosync cluster nodes. %endif %changelog +* Fri Apr 29 2022 Oyvind Albrigtsen - 4.10.0-22 +- all agents: unify ssl parameters to avoid having to use --ssl when + using --ssl-secure/--ssl-insecure for some agents + Resolves: rhbz#2072420 +- fence_gce: update fence agent + Resolves: rhbz#2079889 + * Wed Apr 6 2022 Oyvind Albrigtsen - 4.10.0-21 - fence_lpar: refactor to avoid duplicate code Resolves: rhbz#2065114