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 + + + + + + + + + + + + + +