diff --git a/SOURCES/bz1497072-fence_compute-fence_evacuate-Instance-HA-OSP12.patch b/SOURCES/bz1497072-fence_compute-fence_evacuate-Instance-HA-OSP12.patch new file mode 100644 index 0000000..6ca484d --- /dev/null +++ b/SOURCES/bz1497072-fence_compute-fence_evacuate-Instance-HA-OSP12.patch @@ -0,0 +1,1119 @@ +diff -uNr a/fence/agents/compute/fence_compute.py b/fence/agents/compute/fence_compute.py +--- a/fence/agents/compute/fence_compute.py 2017-09-27 15:01:34.974642469 +0200 ++++ b/fence/agents/compute/fence_compute.py 2017-09-27 15:24:57.482819900 +0200 +@@ -18,173 +18,115 @@ + #END_VERSION_GENERATION + + override_status = "" +-nova = None + + EVACUABLE_TAG = "evacuable" + TRUE_TAGS = ['true'] + +-def get_power_status(_, options): +- global override_status +- +- status = "unknown" +- logging.debug("get action: " + options["--action"]) ++def get_power_status(connection, options): + + if len(override_status): + logging.debug("Pretending we're " + override_status) + return override_status + +- if nova: ++ status = "unknown" ++ logging.debug("get action: " + options["--action"]) ++ ++ if connection: + try: +- services = nova.services.list(host=options["--plug"]) ++ services = connection.services.list(host=options["--plug"], binary="nova-compute") + for service in services: +- logging.debug("Status of %s is %s" % (service.binary, service.state)) +- if service.binary == "nova-compute": +- if service.state == "up": +- status = "on" +- elif service.state == "down": +- status = "off" +- else: +- logging.debug("Unknown status detected from nova: " + service.state) +- break ++ logging.debug("Status of %s on %s is %s, %s" % (service.binary, options["--plug"], service.state, service.status)) ++ if service.state == "up" and service.status == "enabled": ++ # Up and operational ++ status = "on" ++ ++ elif service.state == "down" and service.status == "disabled": ++ # Down and fenced ++ status = "off" ++ ++ elif service.state == "down": ++ # Down and requires fencing ++ status = "failed" ++ ++ elif service.state == "up": ++ # Up and requires unfencing ++ status = "running" ++ else: ++ logging.warning("Unknown status detected from nova for %s: %s, %s" % (options["--plug"], service.state, service.status)) ++ status = "%s %s" % (service.state, service.status) ++ break + except requests.exception.ConnectionError as err: + logging.warning("Nova connection failed: " + str(err)) ++ logging.debug("Final status of %s is %s" % (options["--plug"], status)) + return status + +-# NOTE(sbauza); We mimic the host-evacuate module since it's only a contrib +-# module which is not stable +-def _server_evacuate(server, on_shared_storage): +- success = False +- error_message = "" +- try: +- logging.debug("Resurrecting instance: %s" % server) +- (response, dictionary) = nova.servers.evacuate(server=server, on_shared_storage=on_shared_storage) +- +- if response == None: +- error_message = "No response while evacuating instance" +- elif response.status_code == 200: +- success = True +- error_message = response.reason +- else: +- error_message = response.reason +- +- except Exception as e: +- error_message = "Error while evacuating instance: %s" % e +- +- return { +- "uuid": server, +- "accepted": success, +- "reason": error_message, +- } +- +-def _is_server_evacuable(server, evac_flavors, evac_images): +- if server.flavor.get('id') in evac_flavors: +- return True +- if server.image.get('id') in evac_images: +- return True +- logging.debug("Instance %s is not evacuable" % server.image.get('id')) +- return False +- +-def _get_evacuable_flavors(): +- result = [] +- flavors = nova.flavors.list() +- # Since the detailed view for all flavors doesn't provide the extra specs, +- # we need to call each of the flavor to get them. +- for flavor in flavors: +- tag = flavor.get_keys().get(EVACUABLE_TAG) +- if tag and tag.strip().lower() in TRUE_TAGS: +- result.append(flavor.id) +- return result +- +-def _get_evacuable_images(): +- result = [] +- images = nova.images.list(detailed=True) +- for image in images: +- if hasattr(image, 'metadata'): +- tag = image.metadata.get(EVACUABLE_TAG) +- if tag and tag.strip().lower() in TRUE_TAGS: +- result.append(image.id) +- return result +- +-def _host_evacuate(options): +- result = True +- images = _get_evacuable_images() +- flavors = _get_evacuable_flavors() +- servers = nova.servers.list(search_opts={'host': options["--plug"], 'all_tenants': 1 }) +- +- if options["--instance-filtering"] == "False": +- logging.debug("Not evacuating anything") +- evacuables = [] +- elif len(flavors) or len(images): +- logging.debug("Filtering images and flavors: %s %s" % (repr(flavors), repr(images))) +- # Identify all evacuable servers +- logging.debug("Checking %s" % repr(servers)) +- evacuables = [server for server in servers +- if _is_server_evacuable(server, flavors, images)] +- logging.debug("Evacuating %s" % repr(evacuables)) +- else: +- logging.debug("Evacuating all images and flavors") +- evacuables = servers +- +- if options["--no-shared-storage"] != "False": +- on_shared_storage = False +- else: +- on_shared_storage = True +- +- for server in evacuables: +- logging.debug("Processing %s" % server) +- if hasattr(server, 'id'): +- response = _server_evacuate(server.id, on_shared_storage) +- if response["accepted"]: +- logging.debug("Evacuated %s from %s: %s" % +- (response["uuid"], options["--plug"], response["reason"])) +- else: +- logging.error("Evacuation of %s on %s failed: %s" % +- (response["uuid"], options["--plug"], response["reason"])) +- result = False +- else: +- logging.error("Could not evacuate instance: %s" % server.to_dict()) +- # Should a malformed instance result in a failed evacuation? +- # result = False +- return result ++def get_power_status_simple(connection, options): ++ status = get_power_status(connection, options) ++ if status in [ "off" ]: ++ return status ++ return "on" + + def set_attrd_status(host, status, options): + logging.debug("Setting fencing status for %s to %s" % (host, status)) + run_command(options, "attrd_updater -p -n evacuate -Q -N %s -U %s" % (host, status)) + +-def set_power_status(_, options): +- global override_status +- +- override_status = "" +- logging.debug("set action: " + options["--action"]) ++def get_attrd_status(host, options): ++ (status, pipe_stdout, pipe_stderr) = run_command(options, "attrd_updater -p -n evacuate -Q -N %s" % (host)) ++ fields = pipe_stdout.split('"') ++ if len(fields) > 6: ++ return fields[5] ++ logging.debug("Got %s: o:%s e:%s n:%d" % (status, pipe_stdout, pipe_stderr, len(fields))) ++ return "" ++ ++def set_power_status_on(connection, options): ++ # Wait for any evacuations to complete ++ while True: ++ current = get_attrd_status(options["--plug"], options) ++ if current in ["no", ""]: ++ logging.info("Evacuation complete for: %s '%s'" % (options["--plug"], current)) ++ break ++ else: ++ logging.info("Waiting for %s to complete evacuations: %s" % (options["--plug"], current)) ++ time.sleep(2) + +- if not nova: +- return ++ status = get_power_status(connection, options) ++ # Should we do it for 'failed' too? ++ if status in [ "off", "running", "failed" ]: ++ try: ++ # Forcing the host back up ++ logging.info("Forcing nova-compute back up on "+options["--plug"]) ++ connection.services.force_down(options["--plug"], "nova-compute", force_down=False) ++ logging.info("Forced nova-compute back up on "+options["--plug"]) ++ except Exception as e: ++ # In theory, if force_down=False fails, that's for the exact ++ # same possible reasons that below with force_down=True ++ # eg. either an incompatible version or an old client. ++ # Since it's about forcing back to a default value, there is ++ # no real worries to just consider it's still okay even if the ++ # command failed ++ logging.warn("Exception from attempt to force " ++ "host back up via nova API: " ++ "%s: %s" % (e.__class__.__name__, e)) ++ ++ # Forcing the service back up in case it was disabled ++ logging.info("Enabling nova-compute on "+options["--plug"]) ++ connection.services.enable(options["--plug"], 'nova-compute') + +- if options["--action"] == "on": +- if get_power_status(_, options) != "on": +- # Forcing the service back up in case it was disabled +- nova.services.enable(options["--plug"], 'nova-compute') +- try: +- # Forcing the host back up +- nova.services.force_down( +- options["--plug"], "nova-compute", force_down=False) +- except Exception as e: +- # In theory, if force_down=False fails, that's for the exact +- # same possible reasons that below with force_down=True +- # eg. either an incompatible version or an old client. +- # Since it's about forcing back to a default value, there is +- # no real worries to just consider it's still okay even if the +- # command failed +- logging.info("Exception from attempt to force " +- "host back up via nova API: " +- "%s: %s" % (e.__class__.__name__, e)) +- else: +- # Pretend we're 'on' so that the fencing library doesn't loop forever waiting for the node to boot +- override_status = "on" ++ # Pretend we're 'on' so that the fencing library doesn't loop forever waiting for the node to boot ++ override_status = "on" ++ elif status not in ["on"]: ++ # Not safe to unfence, don't waste time looping to see if the status changes to "on" ++ options["--power-timeout"] = "0" ++ ++def set_power_status_off(connection, options): ++ status = get_power_status(connection, options) ++ if status in [ "off" ]: + return + ++ connection.services.disable(options["--plug"], 'nova-compute') + try: +- nova.services.force_down( ++ # Until 2.53 ++ connection.services.force_down( + options["--plug"], "nova-compute", force_down=True) + except Exception as e: + # Something went wrong when we tried to force the host down. +@@ -198,7 +140,7 @@ + "%s: %s" % (e.__class__.__name__, e)) + # need to wait for nova to update its internal status or we + # cannot call host-evacuate +- while get_power_status(_, options) != "off": ++ while get_power_status(connection, options) not in ["off"]: + # Loop forever if need be. + # + # Some callers (such as Pacemaker) will have a timer +@@ -206,47 +148,55 @@ + logging.debug("Waiting for nova to update its internal state for %s" % options["--plug"]) + time.sleep(1) + +- if not _host_evacuate(options): +- sys.exit(1) ++ set_attrd_status(options["--plug"], "yes", options) ++ ++def set_power_status(connection, options): ++ global override_status + +- return ++ override_status = "" ++ logging.debug("set action: " + options["--action"]) ++ ++ if not connection: ++ return + ++ if options["--action"] in ["off", "reboot"]: ++ set_power_status_off(connection, options) ++ else: ++ set_power_status_on(connection, options) ++ logging.debug("set action passed: " + options["--action"]) ++ sys.exit(0) + +-def fix_domain(options): ++def fix_domain(connection, options): + domains = {} + last_domain = None + +- if nova: ++ if connection: + # Find it in nova + +- hypervisors = nova.hypervisors.list() +- for hypervisor in hypervisors: +- shorthost = hypervisor.hypervisor_hostname.split('.')[0] ++ services = connection.services.list(binary="nova-compute") ++ for service in services: ++ shorthost = service.host.split('.')[0] + +- if shorthost == hypervisor.hypervisor_hostname: ++ if shorthost == service.host: + # Nova is not using FQDN + calculated = "" + else: + # Compute nodes are named as FQDN, strip off the hostname +- calculated = hypervisor.hypervisor_hostname.replace(shorthost+".", "") +- +- domains[calculated] = shorthost ++ calculated = service.host.replace(shorthost+".", "") + + if calculated == last_domain: + # Avoid complaining for each compute node with the same name + # One hopes they don't appear interleaved as A.com B.com A.com B.com +- logging.debug("Calculated the same domain from: %s" % hypervisor.hypervisor_hostname) ++ logging.debug("Calculated the same domain from: %s" % service.host) ++ continue + +- elif "--domain" in options and options["--domain"] == calculated: +- # Supplied domain name is valid +- return ++ domains[calculated] = service.host ++ last_domain = calculated + +- elif "--domain" in options: ++ if "--domain" in options and options["--domain"] != calculated: + # Warn in case nova isn't available at some point + logging.warning("Supplied domain '%s' does not match the one calculated from: %s" +- % (options["--domain"], hypervisor.hypervisor_hostname)) +- +- last_domain = calculated ++ % (options["--domain"], service.host)) + + if len(domains) == 0 and "--domain" not in options: + logging.error("Could not calculate the domain names used by compute nodes in nova") +@@ -254,9 +204,9 @@ + elif len(domains) == 1 and "--domain" not in options: + options["--domain"] = last_domain + +- elif len(domains) == 1: +- logging.error("Overriding supplied domain '%s' does not match the one calculated from: %s" +- % (options["--domain"], hypervisor.hypervisor_hostname)) ++ elif len(domains) == 1 and options["--domain"] != last_domain: ++ logging.error("Overriding supplied domain '%s' as it does not match the one calculated from: %s" ++ % (options["--domain"], domains[last_domain])) + options["--domain"] = last_domain + + elif len(domains) > 1: +@@ -264,47 +214,49 @@ + % (options["--domain"], repr(domains))) + sys.exit(1) + +-def fix_plug_name(options): ++ return last_domain ++ ++def fix_plug_name(connection, options): + if options["--action"] == "list": + return + + if "--plug" not in options: + return + +- fix_domain(options) +- short_plug = options["--plug"].split('.')[0] +- logging.debug("Checking target '%s' against calculated domain '%s'"% (options["--plug"], options["--domain"])) +- +- if "--domain" not in options: ++ calculated = fix_domain(connection, options) ++ if calculated is None or "--domain" not in options: + # Nothing supplied and nova not available... what to do... nothing + return + +- elif options["--domain"] == "": ++ short_plug = options["--plug"].split('.')[0] ++ logging.debug("Checking target '%s' against calculated domain '%s'"% (options["--plug"], calculated)) ++ ++ if options["--domain"] == "": + # Ensure any domain is stripped off since nova isn't using FQDN + options["--plug"] = short_plug + +- elif options["--domain"] in options["--plug"]: +- # Plug already contains the domain, don't re-add ++ elif options["--plug"].endswith(options["--domain"]): ++ # Plug already uses the domain, don't re-add + return + + else: + # Add the domain to the plug + options["--plug"] = short_plug + "." + options["--domain"] + +-def get_plugs_list(_, options): ++def get_plugs_list(connection, options): + result = {} + +- if nova: +- hypervisors = nova.hypervisors.list() +- for hypervisor in hypervisors: +- longhost = hypervisor.hypervisor_hostname ++ if connection: ++ services = connection.services.list(binary="nova-compute") ++ for service in services: ++ longhost = service.host + shorthost = longhost.split('.')[0] + result[longhost] = ("", None) + result[shorthost] = ("", None) + return result + + def create_nova_connection(options): +- global nova ++ nova = None + + try: + from novaclient import client +@@ -330,41 +282,42 @@ + if clientargs: + # OSP < 11 + # ArgSpec(args=['version', 'username', 'password', 'project_id', 'auth_url'], +- # varargs=None, +- # keywords='kwargs', defaults=(None, None, None, None)) ++ # varargs=None, ++ # keywords='kwargs', defaults=(None, None, None, None)) + nova = client.Client(version, +- options["--username"], +- options["--password"], +- options["--tenant-name"], +- options["--auth-url"], +- insecure=options["--insecure"], +- region_name=options["--region-name"], +- endpoint_type=options["--endpoint-type"], +- http_log_debug=options.has_key("--verbose")) ++ options["--username"], ++ options["--password"], ++ options["--tenant-name"], ++ options["--auth-url"], ++ insecure=options["--insecure"], ++ region_name=options["--region-name"], ++ endpoint_type=options["--endpoint-type"], ++ http_log_debug=options.has_key("--verbose")) + else: + # OSP >= 11 + # ArgSpec(args=['version'], varargs='args', keywords='kwargs', defaults=None) + nova = client.Client(version, +- username=options["--username"], +- password=options["--password"], +- tenant_name=options["--tenant-name"], +- auth_url=options["--auth-url"], +- insecure=options["--insecure"], +- region_name=options["--region-name"], +- endpoint_type=options["--endpoint-type"], +- http_log_debug=options.has_key("--verbose")) ++ username=options["--username"], ++ password=options["--password"], ++ tenant_name=options["--tenant-name"], ++ auth_url=options["--auth-url"], ++ insecure=options["--insecure"], ++ region_name=options["--region-name"], ++ endpoint_type=options["--endpoint-type"], ++ http_log_debug=options.has_key("--verbose")) + + try: + nova.hypervisors.list() +- return ++ return nova + + except NotAcceptable as e: + logging.warning(e) + + except Exception as e: + logging.warning("Nova connection failed. %s: %s" % (e.__class__.__name__, e)) +- ++ + logging.warning("Couldn't obtain a supported connection to nova, tried: %s\n" % repr(versions)) ++ return None + + def define_new_opts(): + all_opt["endpoint_type"] = { +@@ -448,11 +401,23 @@ + "order": 5, + } + ++def set_multi_power_fn(connection, options, set_power_fn, get_power_fn, retry_attempts=1): ++ for _ in range(retry_attempts): ++ set_power_fn(connection, options) ++ time.sleep(int(options["--power-wait"])) ++ ++ for _ in range(int(options["--power-timeout"])): ++ if get_power_fn(connection, options) != options["--action"]: ++ time.sleep(1) ++ else: ++ return True ++ return False ++ + def main(): + global override_status + atexit.register(atexit_handler) + +- device_opt = ["login", "passwd", "tenant_name", "auth_url", "fabric_fencing", ++ device_opt = ["login", "passwd", "tenant_name", "auth_url", "fabric_fencing", + "no_login", "no_password", "port", "domain", "no_shared_storage", "endpoint_type", + "record_only", "instance_filtering", "insecure", "region_name"] + define_new_opts() +@@ -472,30 +437,28 @@ + + run_delay(options) + +- create_nova_connection(options) ++ logging.debug("Running "+options["--action"]) ++ connection = create_nova_connection(options) + +- fix_plug_name(options) +- if options["--record-only"] in [ "1", "True", "true", "Yes", "yes"]: +- if options["--action"] == "on": +- set_attrd_status(options["--plug"], "no", options) +- sys.exit(0) +- +- elif options["--action"] in ["off", "reboot"]: +- set_attrd_status(options["--plug"], "yes", options) +- sys.exit(0) ++ if options["--action"] in ["off", "on", "reboot", "status"]: ++ fix_plug_name(connection, options) + +- elif options["--action"] in ["monitor", "status"]: +- sys.exit(0) + +- if options["--action"] in ["off", "reboot"]: +- # Pretend we're 'on' so that the fencing library will always call set_power_status(off) +- override_status = "on" +- +- if options["--action"] == "on": +- # Pretend we're 'off' so that the fencing library will always call set_power_status(on) +- override_status = "off" ++ if options["--action"] in ["reboot"]: ++ options["--action"]="off" ++ ++ if options["--action"] in ["off", "on"]: ++ # No status first, call our own version ++ result = not set_multi_power_fn(connection, options, set_power_status, get_power_status_simple, ++ 1 + int(options["--retry-on"])) ++ elif options["--action"] in ["monitor"]: ++ result = 0 ++ else: ++ result = fence_action(connection, options, set_power_status, get_power_status_simple, get_plugs_list, None) + +- result = fence_action(None, options, set_power_status, get_power_status, get_plugs_list, None) ++ logging.debug("Result for "+options["--action"]+": "+repr(result)) ++ if result == None: ++ result = 0 + sys.exit(result) + + if __name__ == "__main__": +diff -uNr a/fence/agents/compute/fence_evacuate.py b/fence/agents/compute/fence_evacuate.py +--- a/fence/agents/compute/fence_evacuate.py 1970-01-01 01:00:00.000000000 +0100 ++++ b/fence/agents/compute/fence_evacuate.py 2017-09-27 15:25:54.234304769 +0200 +@@ -0,0 +1,366 @@ ++#!/usr/bin/python -tt ++ ++import sys ++import time ++import atexit ++import logging ++import inspect ++import requests.exceptions ++ ++sys.path.append("@FENCEAGENTSLIBDIR@") ++from fencing import * ++from fencing import fail_usage, is_executable, run_command, run_delay ++ ++EVACUABLE_TAG = "evacuable" ++TRUE_TAGS = ['true'] ++ ++def get_power_status(connection, options): ++ ++ status = "unknown" ++ logging.debug("get action: " + options["--action"]) ++ ++ if connection: ++ try: ++ services = connection.services.list(host=options["--plug"], binary="nova-compute") ++ for service in services: ++ logging.debug("Status of %s is %s, %s" % (service.binary, service.state, service.status)) ++ if service.state == "up" and service.status == "enabled": ++ # Up and operational ++ status = "on" ++ ++ elif service.state == "down" and service.status == "disabled": ++ # Down and fenced ++ status = "off" ++ ++ elif service.state == "down": ++ # Down and requires fencing ++ status = "failed" ++ ++ elif service.state == "up": ++ # Up and requires unfencing ++ status = "running" ++ else: ++ logging.warning("Unknown status detected from nova for %s: %s, %s" % (options["--plug"], service.state, service.status)) ++ status = "%s %s" % (service.state, service.status) ++ break ++ except requests.exception.ConnectionError as err: ++ logging.warning("Nova connection failed: " + str(err)) ++ return status ++ ++# NOTE(sbauza); We mimic the host-evacuate module since it's only a contrib ++# module which is not stable ++def _server_evacuate(connection, server, on_shared_storage): ++ success = False ++ error_message = "" ++ try: ++ logging.debug("Resurrecting instance: %s" % server) ++ (response, dictionary) = connection.servers.evacuate(server=server, on_shared_storage=on_shared_storage) ++ ++ if response == None: ++ error_message = "No response while evacuating instance" ++ elif response.status_code == 200: ++ success = True ++ error_message = response.reason ++ else: ++ error_message = response.reason ++ ++ except Exception as e: ++ error_message = "Error while evacuating instance: %s" % e ++ ++ return { ++ "uuid": server, ++ "accepted": success, ++ "reason": error_message, ++ } ++ ++def _is_server_evacuable(server, evac_flavors, evac_images): ++ if server.flavor.get('id') in evac_flavors: ++ return True ++ if hasattr(server.image, 'get'): ++ if server.image.get('id') in evac_images: ++ return True ++ logging.debug("Instance %s is not evacuable" % server.image.get('id')) ++ return False ++ ++def _get_evacuable_flavors(connection): ++ result = [] ++ flavors = connection.flavors.list() ++ # Since the detailed view for all flavors doesn't provide the extra specs, ++ # we need to call each of the flavor to get them. ++ for flavor in flavors: ++ tag = flavor.get_keys().get(EVACUABLE_TAG) ++ if tag and tag.strip().lower() in TRUE_TAGS: ++ result.append(flavor.id) ++ return result ++ ++def _get_evacuable_images(connection): ++ result = [] ++ images = [] ++ if hasattr(connection, "images"): ++ images = connection.images.list(detailed=True) ++ elif hasattr(connection, "glance"): ++ # OSP12+ ++ images = connection.glance.list() ++ ++ for image in images: ++ if hasattr(image, 'metadata'): ++ tag = image.metadata.get(EVACUABLE_TAG) ++ if tag and tag.strip().lower() in TRUE_TAGS: ++ result.append(image.id) ++ elif hasattr(image, 'tags'): ++ # OSP12+ ++ if EVACUABLE_TAG in image.tags: ++ result.append(image.id) ++ return result ++ ++def _host_evacuate(connection, options): ++ result = True ++ images = _get_evacuable_images(connection) ++ flavors = _get_evacuable_flavors(connection) ++ servers = connection.servers.list(search_opts={'host': options["--plug"], 'all_tenants': 1 }) ++ ++ if options["--instance-filtering"] == "False": ++ logging.debug("Not evacuating anything") ++ evacuables = [] ++ elif len(flavors) or len(images): ++ logging.debug("Filtering images and flavors: %s %s" % (repr(flavors), repr(images))) ++ # Identify all evacuable servers ++ logging.debug("Checking %s" % repr(servers)) ++ evacuables = [server for server in servers ++ if _is_server_evacuable(server, flavors, images)] ++ logging.debug("Evacuating %s" % repr(evacuables)) ++ else: ++ logging.debug("Evacuating all images and flavors") ++ evacuables = servers ++ ++ if options["--no-shared-storage"] != "False": ++ on_shared_storage = False ++ else: ++ on_shared_storage = True ++ ++ for server in evacuables: ++ logging.debug("Processing %s" % server) ++ if hasattr(server, 'id'): ++ response = _server_evacuate(connection, server.id, on_shared_storage) ++ if response["accepted"]: ++ logging.debug("Evacuated %s from %s: %s" % ++ (response["uuid"], options["--plug"], response["reason"])) ++ else: ++ logging.error("Evacuation of %s on %s failed: %s" % ++ (response["uuid"], options["--plug"], response["reason"])) ++ result = False ++ else: ++ logging.error("Could not evacuate instance: %s" % server.to_dict()) ++ # Should a malformed instance result in a failed evacuation? ++ # result = False ++ return result ++ ++def set_attrd_status(host, status, options): ++ logging.debug("Setting fencing status for %s to %s" % (host, status)) ++ run_command(options, "attrd_updater -p -n evacuate -Q -N %s -U %s" % (host, status)) ++ ++def set_power_status(connection, options): ++ logging.debug("set action: " + options["--action"]) ++ ++ if not connection: ++ return ++ ++ if options["--action"] == "off" and not _host_evacuate(options): ++ sys.exit(1) ++ ++ sys.exit(0) ++ ++def get_plugs_list(connection, options): ++ result = {} ++ ++ if connection: ++ services = connection.services.list(binary="nova-compute") ++ for service in services: ++ longhost = service.host ++ shorthost = longhost.split('.')[0] ++ result[longhost] = ("", None) ++ result[shorthost] = ("", None) ++ return result ++ ++def create_nova_connection(options): ++ nova = None ++ ++ try: ++ from novaclient import client ++ from novaclient.exceptions import NotAcceptable ++ except ImportError: ++ fail_usage("Nova not found or not accessible") ++ ++ versions = [ "2.11", "2" ] ++ for version in versions: ++ clientargs = inspect.getargspec(client.Client).varargs ++ ++ # Some versions of Openstack prior to Ocata only ++ # supported positional arguments for username, ++ # password and tenant. ++ # ++ # Versions since Ocata only support named arguments. ++ # ++ # So we need to use introspection to figure out how to ++ # create a Nova client. ++ # ++ # Happy days ++ # ++ if clientargs: ++ # OSP < 11 ++ # ArgSpec(args=['version', 'username', 'password', 'project_id', 'auth_url'], ++ # varargs=None, ++ # keywords='kwargs', defaults=(None, None, None, None)) ++ nova = client.Client(version, ++ options["--username"], ++ options["--password"], ++ options["--tenant-name"], ++ options["--auth-url"], ++ insecure=options["--insecure"], ++ region_name=options["--region-name"], ++ endpoint_type=options["--endpoint-type"], ++ http_log_debug=options.has_key("--verbose")) ++ else: ++ # OSP >= 11 ++ # ArgSpec(args=['version'], varargs='args', keywords='kwargs', defaults=None) ++ nova = client.Client(version, ++ username=options["--username"], ++ password=options["--password"], ++ tenant_name=options["--tenant-name"], ++ auth_url=options["--auth-url"], ++ insecure=options["--insecure"], ++ region_name=options["--region-name"], ++ endpoint_type=options["--endpoint-type"], ++ http_log_debug=options.has_key("--verbose")) ++ ++ try: ++ nova.hypervisors.list() ++ return nova ++ ++ except NotAcceptable as e: ++ logging.warning(e) ++ ++ except Exception as e: ++ logging.warning("Nova connection failed. %s: %s" % (e.__class__.__name__, e)) ++ ++ logging.warning("Couldn't obtain a supported connection to nova, tried: %s\n" % repr(versions)) ++ return None ++ ++def define_new_opts(): ++ all_opt["endpoint_type"] = { ++ "getopt" : "e:", ++ "longopt" : "endpoint-type", ++ "help" : "-e, --endpoint-type=[endpoint] Nova Endpoint type (publicURL, internalURL, adminURL)", ++ "required" : "0", ++ "shortdesc" : "Nova Endpoint type", ++ "default" : "internalURL", ++ "order": 1, ++ } ++ all_opt["tenant_name"] = { ++ "getopt" : "t:", ++ "longopt" : "tenant-name", ++ "help" : "-t, --tenant-name=[tenant] Keystone Admin Tenant", ++ "required" : "0", ++ "shortdesc" : "Keystone Admin Tenant", ++ "default" : "", ++ "order": 1, ++ } ++ all_opt["auth_url"] = { ++ "getopt" : "k:", ++ "longopt" : "auth-url", ++ "help" : "-k, --auth-url=[url] Keystone Admin Auth URL", ++ "required" : "0", ++ "shortdesc" : "Keystone Admin Auth URL", ++ "default" : "", ++ "order": 1, ++ } ++ all_opt["region_name"] = { ++ "getopt" : "", ++ "longopt" : "region-name", ++ "help" : "--region-name=[region] Region Name", ++ "required" : "0", ++ "shortdesc" : "Region Name", ++ "default" : "", ++ "order": 1, ++ } ++ all_opt["insecure"] = { ++ "getopt" : "", ++ "longopt" : "insecure", ++ "help" : "--insecure Explicitly allow agent to perform \"insecure\" TLS (https) requests", ++ "required" : "0", ++ "shortdesc" : "Allow Insecure TLS Requests", ++ "default" : "False", ++ "order": 2, ++ } ++ all_opt["domain"] = { ++ "getopt" : "d:", ++ "longopt" : "domain", ++ "help" : "-d, --domain=[string] DNS domain in which hosts live, useful when the cluster uses short names and nova uses FQDN", ++ "required" : "0", ++ "shortdesc" : "DNS domain in which hosts live", ++ "order": 5, ++ } ++ all_opt["instance_filtering"] = { ++ "getopt" : "", ++ "longopt" : "instance-filtering", ++ "help" : "--instance-filtering Allow instances created from images and flavors with evacuable=true to be evacuated (or all if no images/flavors have been tagged)", ++ "required" : "0", ++ "shortdesc" : "Allow instances to be evacuated", ++ "default" : "True", ++ "order": 5, ++ } ++ all_opt["no_shared_storage"] = { ++ "getopt" : "", ++ "longopt" : "no-shared-storage", ++ "help" : "--no-shared-storage Disable functionality for shared storage", ++ "required" : "0", ++ "shortdesc" : "Disable functionality for dealing with shared storage", ++ "default" : "False", ++ "order": 5, ++ } ++ ++def main(): ++ atexit.register(atexit_handler) ++ ++ device_opt = ["login", "passwd", "tenant_name", "auth_url", ++ "no_login", "no_password", "port", "domain", "no_shared_storage", "endpoint_type", ++ "instance_filtering", "insecure", "region_name"] ++ define_new_opts() ++ all_opt["shell_timeout"]["default"] = "180" ++ ++ options = check_input(device_opt, process_input(device_opt)) ++ ++ docs = {} ++ docs["shortdesc"] = "Fence agent for the automatic resurrection of OpenStack compute instances" ++ docs["longdesc"] = "Used to reschedule flagged instances" ++ docs["vendorurl"] = "" ++ ++ show_docs(options, docs) ++ ++ run_delay(options) ++ ++ connection = create_nova_connection(options) ++ ++ # Un-evacuating a server doesn't make sense ++ if options["--action"] in ["on"]: ++ logging.error("Action %s is not supported by this agent" % (options["--action"])) ++ sys.exit(1) ++ ++ if options["--action"] in ["off", "reboot"]: ++ status = get_power_status(connection, options) ++ if status != "off": ++ logging.error("Cannot resurrect instances from %s in state '%s'" % (options["--plug"], status)) ++ sys.exit(1) ++ ++ elif not _host_evacuate(connection, options): ++ logging.error("Resurrection of instances from %s failed" % (options["--plug"])) ++ sys.exit(1) ++ ++ logging.info("Resurrection of instances from %s complete" % (options["--plug"])) ++ sys.exit(0) ++ ++ result = fence_action(connection, options, set_power_status, get_power_status, get_plugs_list, None) ++ sys.exit(result) ++ ++if __name__ == "__main__": ++ main() +diff -uNr a/fence/agents/compute/Makefile.am b/fence/agents/compute/Makefile.am +--- a/fence/agents/compute/Makefile.am 2017-09-27 15:01:34.844643650 +0200 ++++ b/fence/agents/compute/Makefile.am 2017-09-27 15:57:50.963839738 +0200 +@@ -1,14 +1,14 @@ + MAINTAINERCLEANFILES = Makefile.in + +-TARGET = fence_compute ++TARGET = fence_compute fence_evacuate + +-SRC = $(TARGET).py ++SRC = $(TARGET:=.py) + + EXTRA_DIST = $(SRC) + + sbin_SCRIPTS = $(TARGET) + +-man_MANS = $(TARGET).8 ++man_MANS = $(TARGET:=.8) + + FENCE_TEST_ARGS = -l test -p test -n 1 + +diff -uNr a/tests/data/metadata/fence_evacuate.xml b/tests/data/metadata/fence_evacuate.xml +--- a/tests/data/metadata/fence_evacuate.xml 1970-01-01 01:00:00.000000000 +0100 ++++ b/tests/data/metadata/fence_evacuate.xml 2017-09-27 15:28:10.978063549 +0200 +@@ -0,0 +1,163 @@ ++ ++ ++Used to reschedule flagged instances ++ ++ ++ ++ ++ ++ Keystone Admin Tenant ++ ++ ++ ++ ++ Keystone Admin Auth URL ++ ++ ++ ++ ++ Physical plug number, name of virtual machine or UUID ++ ++ ++ ++ ++ Script to retrieve password ++ ++ ++ ++ ++ Region Name ++ ++ ++ ++ ++ Login password or passphrase ++ ++ ++ ++ ++ Nova Endpoint type ++ ++ ++ ++ ++ Fencing Action ++ ++ ++ ++ ++ Login Name ++ ++ ++ ++ ++ Physical plug number, name of virtual machine or UUID ++ ++ ++ ++ ++ Login Name ++ ++ ++ ++ ++ Login password or passphrase ++ ++ ++ ++ ++ Script to retrieve password ++ ++ ++ ++ ++ Allow Insecure TLS Requests ++ ++ ++ ++ ++ DNS domain in which hosts live ++ ++ ++ ++ ++ Allow instances to be evacuated ++ ++ ++ ++ ++ Disable functionality for dealing with shared storage ++ ++ ++ ++ ++ Verbose mode ++ ++ ++ ++ ++ Write debug information to given file ++ ++ ++ ++ ++ Write debug information to given file ++ ++ ++ ++ ++ Display version information and exit ++ ++ ++ ++ ++ Display help and exit ++ ++ ++ ++ ++ Separator for CSV created by operation list ++ ++ ++ ++ ++ Wait X seconds after issuing ON/OFF ++ ++ ++ ++ ++ Wait X seconds for cmd prompt after login ++ ++ ++ ++ ++ Wait X seconds before fencing is started ++ ++ ++ ++ ++ Test X seconds for status change after ON/OFF ++ ++ ++ ++ ++ Wait X seconds for cmd prompt after issuing command ++ ++ ++ ++ ++ Count of attempts to retry power on ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ diff --git a/SOURCES/bz1497241-fence_compute-fence_scsi-fix-parameters.patch b/SOURCES/bz1497241-fence_compute-fence_scsi-fix-parameters.patch new file mode 100644 index 0000000..2d24cf4 --- /dev/null +++ b/SOURCES/bz1497241-fence_compute-fence_scsi-fix-parameters.patch @@ -0,0 +1,18 @@ +--- a/fence/agents/lib/fencing.py.py 2017-09-19 12:29:04.158438532 +0200 ++++ b/fence/agents/lib/fencing.py.py 2017-09-19 12:48:22.252509114 +0200 +@@ -705,11 +705,12 @@ + continue + + (name, value) = (line + "=").split("=", 1) +- name = name.replace("-", "_"); + value = value[:-1] + +- if name in mapping_longopt_names: +- name = mapping_longopt_names[name] ++ if name.replace("-", "_") in mapping_longopt_names: ++ name = mapping_longopt_names[name.replace("-", "_")] ++ elif name.replace("_", "-") in mapping_longopt_names: ++ name = mapping_longopt_names[name.replace("_", "-")] + + if avail_opt.count(name) == 0 and name in ["nodename"]: + continue diff --git a/SPECS/fence-agents.spec b/SPECS/fence-agents.spec index be476e8..b2b851b 100644 --- a/SPECS/fence-agents.spec +++ b/SPECS/fence-agents.spec @@ -16,7 +16,7 @@ Name: fence-agents Summary: Fence Agents for Red Hat Cluster Version: 4.0.11 -Release: 66%{?alphatag:.%{alphatag}}%{?dist}.1 +Release: 66%{?alphatag:.%{alphatag}}%{?dist}.3 License: GPLv2+ and LGPLv2+ Group: System Environment/Base URL: http://sourceware.org/cluster/wiki/ @@ -137,6 +137,8 @@ Patch112: bz1426693-1-fence_compute-project_id-to-project_name.patch Patch113: bz1426693-2-fence_compute-project_id-to-project_name.patch Patch114: bz1459199-fence_vmware_soap-fix-for-selfsigned-certificate.patch Patch115: bz1479851-fence_compute-fence_scsi-fix-parameters.patch +Patch116: bz1497072-fence_compute-fence_evacuate-Instance-HA-OSP12.patch +Patch117: bz1497241-fence_compute-fence_scsi-fix-parameters.patch %if 0%{?rhel} %global supportedagents apc apc_snmp bladecenter brocade cisco_mds cisco_ucs compute drac5 eaton_snmp emerson eps hpblade ibmblade ifmib ilo ilo_moonshot ilo_mp ilo_ssh intelmodular ipdu ipmilan mpath kdump rhevm rsa rsb sbd scsi vmware_soap wti @@ -282,6 +284,8 @@ BuildRequires: autoconf automake libtool %patch113 -p1 -b .bz1426693-2 %patch114 -p1 -b .bz1459199 %patch115 -p1 -b .bz1479851 +%patch116 -p1 -b .bz1497072 +%patch117 -p1 -b .bz1497241 %build ./autogen.sh @@ -455,7 +459,9 @@ The fence-agents-compute package contains a fence agent for Nova compute nodes. %files compute %defattr(-,root,root,-) %{_sbindir}/fence_compute +%{_sbindir}/fence_evacuate %{_mandir}/man8/fence_compute.8* +%{_mandir}/man8/fence_evacuate.8* %package drac5 License: GPLv2+ and LGPLv2+ @@ -871,6 +877,15 @@ The fence-agents-zvm package contains a fence agent for z/VM hypervisors %endif %changelog +* Fri Sep 29 2017 Oyvind Albrigtsen - 4.0.11-66.3 +- fence_compute/fence_scsi: fix issue with some parameters + (for ABI compatibility) + Resolves: rhbz#1497241 + +* Fri Sep 29 2017 Oyvind Albrigtsen - 4.0.11-66.2 +- fence_compute/fence_evacuate: changes to support Instance HA on OSP12 + Resolves: rhbz#1497072 + * Thu Aug 10 2017 Oyvind Albrigtsen - 4.0.11-66.1 - fence_compute/fence_scsi: fix issue with some parameters Resolves: rhbz#1479851