Blame SOURCES/bz1497072-fence_compute-fence_evacuate-Instance-HA-OSP12.patch

e4ffb1
diff -uNr a/fence/agents/compute/fence_compute.py b/fence/agents/compute/fence_compute.py
e4ffb1
--- a/fence/agents/compute/fence_compute.py	2017-09-27 15:01:34.974642469 +0200
e4ffb1
+++ b/fence/agents/compute/fence_compute.py	2017-09-27 15:24:57.482819900 +0200
e4ffb1
@@ -18,173 +18,115 @@
e4ffb1
 #END_VERSION_GENERATION
e4ffb1
 
e4ffb1
 override_status = ""
e4ffb1
-nova = None
e4ffb1
 
e4ffb1
 EVACUABLE_TAG = "evacuable"
e4ffb1
 TRUE_TAGS = ['true']
e4ffb1
 
e4ffb1
-def get_power_status(_, options):
e4ffb1
-	global override_status
e4ffb1
-
e4ffb1
-	status = "unknown"
e4ffb1
-	logging.debug("get action: " + options["--action"])
e4ffb1
+def get_power_status(connection, options):
e4ffb1
 
e4ffb1
 	if len(override_status):
e4ffb1
 		logging.debug("Pretending we're " + override_status)
e4ffb1
 		return override_status
e4ffb1
 
e4ffb1
-	if nova:
e4ffb1
+	status = "unknown"
e4ffb1
+	logging.debug("get action: " + options["--action"])
e4ffb1
+
e4ffb1
+	if connection:
e4ffb1
 		try:
e4ffb1
-			services = nova.services.list(host=options["--plug"])
e4ffb1
+			services = connection.services.list(host=options["--plug"], binary="nova-compute")
e4ffb1
 			for service in services:
e4ffb1
-				logging.debug("Status of %s is %s" % (service.binary, service.state))
e4ffb1
-				if service.binary == "nova-compute":
e4ffb1
-					if service.state == "up":
e4ffb1
-						status = "on"
e4ffb1
-					elif service.state == "down":
e4ffb1
-						status = "off"
e4ffb1
-					else:
e4ffb1
-						logging.debug("Unknown status detected from nova: " + service.state)
e4ffb1
-					break
e4ffb1
+				logging.debug("Status of %s on %s is %s, %s" % (service.binary, options["--plug"], service.state, service.status))
e4ffb1
+				if service.state == "up" and service.status == "enabled":
e4ffb1
+					# Up and operational
e4ffb1
+					status = "on"
e4ffb1
+					
e4ffb1
+				elif service.state == "down" and service.status == "disabled":
e4ffb1
+					# Down and fenced
e4ffb1
+					status = "off"
e4ffb1
+
e4ffb1
+				elif service.state == "down":
e4ffb1
+					# Down and requires fencing
e4ffb1
+					status = "failed"
e4ffb1
+
e4ffb1
+				elif service.state == "up":
e4ffb1
+					# Up and requires unfencing
e4ffb1
+					status = "running"
e4ffb1
+				else:
e4ffb1
+					logging.warning("Unknown status detected from nova for %s: %s, %s" % (options["--plug"], service.state, service.status))
e4ffb1
+					status = "%s %s" % (service.state, service.status)
e4ffb1
+				break
e4ffb1
 		except requests.exception.ConnectionError as err:
e4ffb1
 			logging.warning("Nova connection failed: " + str(err))
e4ffb1
+	logging.debug("Final status of %s is %s" % (options["--plug"], status))
e4ffb1
 	return status
e4ffb1
 
e4ffb1
-# NOTE(sbauza); We mimic the host-evacuate module since it's only a contrib
e4ffb1
-# module which is not stable
e4ffb1
-def _server_evacuate(server, on_shared_storage):
e4ffb1
-	success = False
e4ffb1
-	error_message = ""
e4ffb1
-	try:
e4ffb1
-		logging.debug("Resurrecting instance: %s" % server)
e4ffb1
-		(response, dictionary) = nova.servers.evacuate(server=server, on_shared_storage=on_shared_storage)
e4ffb1
-
e4ffb1
-		if response == None:
e4ffb1
-			error_message = "No response while evacuating instance"
e4ffb1
-		elif response.status_code == 200:
e4ffb1
-			success = True
e4ffb1
-			error_message = response.reason
e4ffb1
-		else:
e4ffb1
-			error_message = response.reason
e4ffb1
-
e4ffb1
-	except Exception as e:
e4ffb1
-		error_message = "Error while evacuating instance: %s" % e
e4ffb1
-
e4ffb1
-	return {
e4ffb1
-		"uuid": server,
e4ffb1
-		"accepted": success,
e4ffb1
-		"reason": error_message,
e4ffb1
-		}
e4ffb1
-
e4ffb1
-def _is_server_evacuable(server, evac_flavors, evac_images):
e4ffb1
-	if server.flavor.get('id') in evac_flavors:
e4ffb1
-		return True
e4ffb1
-	if server.image.get('id') in evac_images:
e4ffb1
-		return True
e4ffb1
-	logging.debug("Instance %s is not evacuable" % server.image.get('id'))
e4ffb1
-	return False
e4ffb1
-
e4ffb1
-def _get_evacuable_flavors():
e4ffb1
-	result = []
e4ffb1
-	flavors = nova.flavors.list()
e4ffb1
-	# Since the detailed view for all flavors doesn't provide the extra specs,
e4ffb1
-	# we need to call each of the flavor to get them.
e4ffb1
-	for flavor in flavors:
e4ffb1
-		tag = flavor.get_keys().get(EVACUABLE_TAG)
e4ffb1
-		if tag and tag.strip().lower() in TRUE_TAGS:
e4ffb1
-			result.append(flavor.id)
e4ffb1
-	return result
e4ffb1
-
e4ffb1
-def _get_evacuable_images():
e4ffb1
-	result = []
e4ffb1
-	images = nova.images.list(detailed=True)
e4ffb1
-	for image in images:
e4ffb1
-		if hasattr(image, 'metadata'):
e4ffb1
-			tag = image.metadata.get(EVACUABLE_TAG)
e4ffb1
-			if tag and tag.strip().lower() in TRUE_TAGS:
e4ffb1
-				result.append(image.id)
e4ffb1
-	return result
e4ffb1
-
e4ffb1
-def _host_evacuate(options):
e4ffb1
-	result = True
e4ffb1
-	images = _get_evacuable_images()
e4ffb1
-	flavors = _get_evacuable_flavors()
e4ffb1
-	servers = nova.servers.list(search_opts={'host': options["--plug"], 'all_tenants': 1 })
e4ffb1
-
e4ffb1
-	if options["--instance-filtering"] == "False":
e4ffb1
-		logging.debug("Not evacuating anything")
e4ffb1
-		evacuables = []
e4ffb1
-	elif len(flavors) or len(images):
e4ffb1
-		logging.debug("Filtering images and flavors: %s %s" % (repr(flavors), repr(images)))
e4ffb1
-		# Identify all evacuable servers
e4ffb1
-		logging.debug("Checking %s" % repr(servers))
e4ffb1
-		evacuables = [server for server in servers
e4ffb1
-				if _is_server_evacuable(server, flavors, images)]
e4ffb1
-		logging.debug("Evacuating %s" % repr(evacuables))
e4ffb1
-	else:
e4ffb1
-		logging.debug("Evacuating all images and flavors")
e4ffb1
-		evacuables = servers
e4ffb1
-
e4ffb1
-	if options["--no-shared-storage"] != "False":
e4ffb1
-		on_shared_storage = False
e4ffb1
-	else:
e4ffb1
-		on_shared_storage = True
e4ffb1
-
e4ffb1
-	for server in evacuables:
e4ffb1
-		logging.debug("Processing %s" % server)
e4ffb1
-		if hasattr(server, 'id'):
e4ffb1
-			response = _server_evacuate(server.id, on_shared_storage)
e4ffb1
-			if response["accepted"]:
e4ffb1
-				logging.debug("Evacuated %s from %s: %s" %
e4ffb1
-					      (response["uuid"], options["--plug"], response["reason"]))
e4ffb1
-			else:
e4ffb1
-				logging.error("Evacuation of %s on %s failed: %s" %
e4ffb1
-					      (response["uuid"], options["--plug"], response["reason"]))
e4ffb1
-				result = False
e4ffb1
-		else:
e4ffb1
-			logging.error("Could not evacuate instance: %s" % server.to_dict())
e4ffb1
-			# Should a malformed instance result in a failed evacuation?
e4ffb1
-			# result = False
e4ffb1
-	return result
e4ffb1
+def get_power_status_simple(connection, options):
e4ffb1
+	status = get_power_status(connection, options)
e4ffb1
+	if status in [ "off" ]:
e4ffb1
+		return status
e4ffb1
+	return "on"
e4ffb1
 
e4ffb1
 def set_attrd_status(host, status, options):
e4ffb1
 	logging.debug("Setting fencing status for %s to %s" % (host, status))
e4ffb1
 	run_command(options, "attrd_updater -p -n evacuate -Q -N %s -U %s" % (host, status))
e4ffb1
 
e4ffb1
-def set_power_status(_, options):
e4ffb1
-	global override_status
e4ffb1
-
e4ffb1
-	override_status = ""
e4ffb1
-	logging.debug("set action: " + options["--action"])
e4ffb1
+def get_attrd_status(host, options):
e4ffb1
+	(status, pipe_stdout, pipe_stderr) = run_command(options, "attrd_updater -p -n evacuate -Q -N %s" % (host))
e4ffb1
+	fields = pipe_stdout.split('"')
e4ffb1
+	if len(fields) > 6:
e4ffb1
+		return fields[5]
e4ffb1
+	logging.debug("Got %s: o:%s e:%s n:%d" % (status, pipe_stdout, pipe_stderr, len(fields)))
e4ffb1
+	return ""
e4ffb1
+
e4ffb1
+def set_power_status_on(connection, options):
e4ffb1
+	# Wait for any evacuations to complete
e4ffb1
+	while True:
e4ffb1
+		current = get_attrd_status(options["--plug"], options)
e4ffb1
+		if current in ["no", ""]:
e4ffb1
+			logging.info("Evacuation complete for: %s '%s'" % (options["--plug"], current))
e4ffb1
+			break
e4ffb1
+		else:
e4ffb1
+			logging.info("Waiting for %s to complete evacuations: %s" % (options["--plug"], current))
e4ffb1
+			time.sleep(2)
e4ffb1
 
e4ffb1
-	if not nova:
e4ffb1
-		return
e4ffb1
+	status = get_power_status(connection, options)
e4ffb1
+	# Should we do it for 'failed' too?
e4ffb1
+	if status in [ "off", "running", "failed" ]:
e4ffb1
+		try:
e4ffb1
+			# Forcing the host back up
e4ffb1
+			logging.info("Forcing nova-compute back up on "+options["--plug"])
e4ffb1
+			connection.services.force_down(options["--plug"], "nova-compute", force_down=False)
e4ffb1
+			logging.info("Forced nova-compute back up on "+options["--plug"])
e4ffb1
+		except Exception as e:
e4ffb1
+			# In theory, if force_down=False fails, that's for the exact
e4ffb1
+			# same possible reasons that below with force_down=True
e4ffb1
+			# eg. either an incompatible version or an old client.
e4ffb1
+			# Since it's about forcing back to a default value, there is
e4ffb1
+			# no real worries to just consider it's still okay even if the
e4ffb1
+			# command failed
e4ffb1
+			logging.warn("Exception from attempt to force "
e4ffb1
+				      "host back up via nova API: "
e4ffb1
+				      "%s: %s" % (e.__class__.__name__, e))
e4ffb1
+
e4ffb1
+		# Forcing the service back up in case it was disabled
e4ffb1
+		logging.info("Enabling nova-compute on "+options["--plug"])
e4ffb1
+		connection.services.enable(options["--plug"], 'nova-compute')
e4ffb1
 
e4ffb1
-	if options["--action"] == "on":
e4ffb1
-		if get_power_status(_, options) != "on":
e4ffb1
-			# Forcing the service back up in case it was disabled
e4ffb1
-			nova.services.enable(options["--plug"], 'nova-compute')
e4ffb1
-			try:
e4ffb1
-				# Forcing the host back up
e4ffb1
-				nova.services.force_down(
e4ffb1
-					options["--plug"], "nova-compute", force_down=False)
e4ffb1
-			except Exception as e:
e4ffb1
-				# In theory, if force_down=False fails, that's for the exact
e4ffb1
-				# same possible reasons that below with force_down=True
e4ffb1
-				# eg. either an incompatible version or an old client.
e4ffb1
-				# Since it's about forcing back to a default value, there is
e4ffb1
-				# no real worries to just consider it's still okay even if the
e4ffb1
-				# command failed
e4ffb1
-				logging.info("Exception from attempt to force "
e4ffb1
-					      "host back up via nova API: "
e4ffb1
-					      "%s: %s" % (e.__class__.__name__, e))
e4ffb1
-		else:
e4ffb1
-			# Pretend we're 'on' so that the fencing library doesn't loop forever waiting for the node to boot
e4ffb1
-			override_status = "on"
e4ffb1
+		# Pretend we're 'on' so that the fencing library doesn't loop forever waiting for the node to boot
e4ffb1
+		override_status = "on"
e4ffb1
+	elif status not in ["on"]:
e4ffb1
+		# Not safe to unfence, don't waste time looping to see if the status changes to "on"
e4ffb1
+		options["--power-timeout"] = "0"
e4ffb1
+
e4ffb1
+def set_power_status_off(connection, options):
e4ffb1
+	status = get_power_status(connection, options)
e4ffb1
+	if status in [ "off" ]:
e4ffb1
 		return
e4ffb1
 
e4ffb1
+	connection.services.disable(options["--plug"], 'nova-compute')
e4ffb1
 	try:
e4ffb1
-		nova.services.force_down(
e4ffb1
+		# Until 2.53
e4ffb1
+		connection.services.force_down(
e4ffb1
 			options["--plug"], "nova-compute", force_down=True)
e4ffb1
 	except Exception as e:
e4ffb1
 		# Something went wrong when we tried to force the host down.
e4ffb1
@@ -198,7 +140,7 @@
e4ffb1
 			      "%s: %s" % (e.__class__.__name__, e))
e4ffb1
 		# need to wait for nova to update its internal status or we
e4ffb1
 		# cannot call host-evacuate
e4ffb1
-		while get_power_status(_, options) != "off":
e4ffb1
+		while get_power_status(connection, options) not in ["off"]:
e4ffb1
 			# Loop forever if need be.
e4ffb1
 			#
e4ffb1
 			# Some callers (such as Pacemaker) will have a timer
e4ffb1
@@ -206,47 +148,55 @@
e4ffb1
 			logging.debug("Waiting for nova to update its internal state for %s" % options["--plug"])
e4ffb1
 			time.sleep(1)
e4ffb1
 
e4ffb1
-	if not _host_evacuate(options):
e4ffb1
-		sys.exit(1)
e4ffb1
+	set_attrd_status(options["--plug"], "yes", options)
e4ffb1
+
e4ffb1
+def set_power_status(connection, options):
e4ffb1
+	global override_status
e4ffb1
 
e4ffb1
-	return
e4ffb1
+	override_status = ""
e4ffb1
+	logging.debug("set action: " + options["--action"])
e4ffb1
+
e4ffb1
+	if not connection:
e4ffb1
+		return
e4ffb1
 
e4ffb1
+	if options["--action"] in ["off", "reboot"]:
e4ffb1
+		set_power_status_off(connection, options)
e4ffb1
+	else:
e4ffb1
+		set_power_status_on(connection, options)
e4ffb1
+	logging.debug("set action passed: " + options["--action"])
e4ffb1
+	sys.exit(0)
e4ffb1
 
e4ffb1
-def fix_domain(options):
e4ffb1
+def fix_domain(connection, options):
e4ffb1
 	domains = {}
e4ffb1
 	last_domain = None
e4ffb1
 
e4ffb1
-	if nova:
e4ffb1
+	if connection:
e4ffb1
 		# Find it in nova
e4ffb1
 
e4ffb1
-		hypervisors = nova.hypervisors.list()
e4ffb1
-		for hypervisor in hypervisors:
e4ffb1
-			shorthost = hypervisor.hypervisor_hostname.split('.')[0]
e4ffb1
+		services = connection.services.list(binary="nova-compute")
e4ffb1
+		for service in services:
e4ffb1
+			shorthost = service.host.split('.')[0]
e4ffb1
 
e4ffb1
-			if shorthost == hypervisor.hypervisor_hostname:
e4ffb1
+			if shorthost == service.host:
e4ffb1
 				# Nova is not using FQDN 
e4ffb1
 				calculated = ""
e4ffb1
 			else:
e4ffb1
 				# Compute nodes are named as FQDN, strip off the hostname
e4ffb1
-				calculated = hypervisor.hypervisor_hostname.replace(shorthost+".", "")
e4ffb1
-
e4ffb1
-			domains[calculated] = shorthost
e4ffb1
+				calculated = service.host.replace(shorthost+".", "")
e4ffb1
 
e4ffb1
 			if calculated == last_domain:
e4ffb1
 				# Avoid complaining for each compute node with the same name
e4ffb1
 				# One hopes they don't appear interleaved as A.com B.com A.com B.com
e4ffb1
-				logging.debug("Calculated the same domain from: %s" % hypervisor.hypervisor_hostname)
e4ffb1
+				logging.debug("Calculated the same domain from: %s" % service.host)
e4ffb1
+				continue
e4ffb1
 
e4ffb1
-			elif "--domain" in options and options["--domain"] == calculated:
e4ffb1
-				# Supplied domain name is valid 
e4ffb1
-				return
e4ffb1
+			domains[calculated] = service.host
e4ffb1
+			last_domain = calculated
e4ffb1
 
e4ffb1
-			elif "--domain" in options:
e4ffb1
+			if "--domain" in options and options["--domain"] != calculated:
e4ffb1
 				# Warn in case nova isn't available at some point
e4ffb1
 				logging.warning("Supplied domain '%s' does not match the one calculated from: %s"
e4ffb1
-					      % (options["--domain"], hypervisor.hypervisor_hostname))
e4ffb1
-
e4ffb1
-			last_domain = calculated
e4ffb1
+					      % (options["--domain"], service.host))
e4ffb1
 
e4ffb1
 	if len(domains) == 0 and "--domain" not in options:
e4ffb1
 		logging.error("Could not calculate the domain names used by compute nodes in nova")
e4ffb1
@@ -254,9 +204,9 @@
e4ffb1
 	elif len(domains) == 1 and "--domain" not in options:
e4ffb1
 		options["--domain"] = last_domain
e4ffb1
 
e4ffb1
-	elif len(domains) == 1:
e4ffb1
-		logging.error("Overriding supplied domain '%s' does not match the one calculated from: %s"
e4ffb1
-			      % (options["--domain"], hypervisor.hypervisor_hostname))
e4ffb1
+	elif len(domains) == 1 and options["--domain"] != last_domain:
e4ffb1
+		logging.error("Overriding supplied domain '%s' as it does not match the one calculated from: %s"
e4ffb1
+			      % (options["--domain"], domains[last_domain]))
e4ffb1
 		options["--domain"] = last_domain
e4ffb1
 
e4ffb1
 	elif len(domains) > 1:
e4ffb1
@@ -264,47 +214,49 @@
e4ffb1
 			      % (options["--domain"], repr(domains)))
e4ffb1
 		sys.exit(1)
e4ffb1
 
e4ffb1
-def fix_plug_name(options):
e4ffb1
+	return last_domain
e4ffb1
+
e4ffb1
+def fix_plug_name(connection, options):
e4ffb1
 	if options["--action"] == "list":
e4ffb1
 		return
e4ffb1
 
e4ffb1
 	if "--plug" not in options:
e4ffb1
 		return
e4ffb1
 
e4ffb1
-	fix_domain(options)
e4ffb1
-	short_plug = options["--plug"].split('.')[0]
e4ffb1
-	logging.debug("Checking target '%s' against calculated domain '%s'"% (options["--plug"], options["--domain"]))
e4ffb1
-
e4ffb1
-	if "--domain" not in options:
e4ffb1
+	calculated = fix_domain(connection, options)
e4ffb1
+	if calculated is None or "--domain" not in options:
e4ffb1
 		# Nothing supplied and nova not available... what to do... nothing
e4ffb1
 		return
e4ffb1
 
e4ffb1
-	elif options["--domain"] == "":
e4ffb1
+	short_plug = options["--plug"].split('.')[0]
e4ffb1
+	logging.debug("Checking target '%s' against calculated domain '%s'"% (options["--plug"], calculated))
e4ffb1
+
e4ffb1
+	if options["--domain"] == "":
e4ffb1
 		# Ensure any domain is stripped off since nova isn't using FQDN
e4ffb1
 		options["--plug"] = short_plug
e4ffb1
 
e4ffb1
-	elif options["--domain"] in options["--plug"]:
e4ffb1
-		# Plug already contains the domain, don't re-add 
e4ffb1
+	elif options["--plug"].endswith(options["--domain"]):
e4ffb1
+		# Plug already uses the domain, don't re-add
e4ffb1
 		return
e4ffb1
 
e4ffb1
 	else:
e4ffb1
 		# Add the domain to the plug
e4ffb1
 		options["--plug"] = short_plug + "." + options["--domain"]
e4ffb1
 
e4ffb1
-def get_plugs_list(_, options):
e4ffb1
+def get_plugs_list(connection, options):
e4ffb1
 	result = {}
e4ffb1
 
e4ffb1
-	if nova:
e4ffb1
-		hypervisors = nova.hypervisors.list()
e4ffb1
-		for hypervisor in hypervisors:
e4ffb1
-			longhost = hypervisor.hypervisor_hostname
e4ffb1
+	if connection:
e4ffb1
+		services = connection.services.list(binary="nova-compute")
e4ffb1
+		for service in services:
e4ffb1
+			longhost = service.host
e4ffb1
 			shorthost = longhost.split('.')[0]
e4ffb1
 			result[longhost] = ("", None)
e4ffb1
 			result[shorthost] = ("", None)
e4ffb1
 	return result
e4ffb1
 
e4ffb1
 def create_nova_connection(options):
e4ffb1
-	global nova
e4ffb1
+	nova = None
e4ffb1
 
e4ffb1
 	try:
e4ffb1
 		from novaclient import client
e4ffb1
@@ -330,41 +282,42 @@
e4ffb1
 		if clientargs:
e4ffb1
 			# OSP < 11
e4ffb1
 			# ArgSpec(args=['version', 'username', 'password', 'project_id', 'auth_url'],
e4ffb1
-			#         varargs=None,
e4ffb1
-			#         keywords='kwargs', defaults=(None, None, None, None))
e4ffb1
+			#	 varargs=None,
e4ffb1
+			#	 keywords='kwargs', defaults=(None, None, None, None))
e4ffb1
 			nova = client.Client(version,
e4ffb1
-					options["--username"],
e4ffb1
-					options["--password"],
e4ffb1
-					options["--tenant-name"],
e4ffb1
-					options["--auth-url"],
e4ffb1
-					insecure=options["--insecure"],
e4ffb1
-					region_name=options["--region-name"],
e4ffb1
-					endpoint_type=options["--endpoint-type"],
e4ffb1
-					http_log_debug=options.has_key("--verbose"))
e4ffb1
+					     options["--username"],
e4ffb1
+					     options["--password"],
e4ffb1
+					     options["--tenant-name"],
e4ffb1
+					     options["--auth-url"],
e4ffb1
+					     insecure=options["--insecure"],
e4ffb1
+					     region_name=options["--region-name"],
e4ffb1
+					     endpoint_type=options["--endpoint-type"],
e4ffb1
+					     http_log_debug=options.has_key("--verbose"))
e4ffb1
 		else:
e4ffb1
 			# OSP >= 11
e4ffb1
 			# ArgSpec(args=['version'], varargs='args', keywords='kwargs', defaults=None)
e4ffb1
 			nova = client.Client(version,
e4ffb1
-					username=options["--username"],
e4ffb1
-					password=options["--password"],
e4ffb1
-					tenant_name=options["--tenant-name"],
e4ffb1
-					auth_url=options["--auth-url"],
e4ffb1
-					insecure=options["--insecure"],
e4ffb1
-					region_name=options["--region-name"],
e4ffb1
-					endpoint_type=options["--endpoint-type"],
e4ffb1
-					http_log_debug=options.has_key("--verbose"))
e4ffb1
+					     username=options["--username"],
e4ffb1
+					     password=options["--password"],
e4ffb1
+					     tenant_name=options["--tenant-name"],
e4ffb1
+					     auth_url=options["--auth-url"],
e4ffb1
+					     insecure=options["--insecure"],
e4ffb1
+					     region_name=options["--region-name"],
e4ffb1
+					     endpoint_type=options["--endpoint-type"],
e4ffb1
+					     http_log_debug=options.has_key("--verbose"))
e4ffb1
 
e4ffb1
 		try:
e4ffb1
 			nova.hypervisors.list()
e4ffb1
-			return
e4ffb1
+			return nova
e4ffb1
 
e4ffb1
 		except NotAcceptable as e:
e4ffb1
 			logging.warning(e)
e4ffb1
 
e4ffb1
 		except Exception as e:
e4ffb1
 			logging.warning("Nova connection failed. %s: %s" % (e.__class__.__name__, e))
e4ffb1
-			
e4ffb1
+
e4ffb1
 	logging.warning("Couldn't obtain a supported connection to nova, tried: %s\n" % repr(versions))
e4ffb1
+	return None
e4ffb1
 
e4ffb1
 def define_new_opts():
e4ffb1
 	all_opt["endpoint_type"] = {
e4ffb1
@@ -448,11 +401,23 @@
e4ffb1
 		"order": 5,
e4ffb1
 	}
e4ffb1
 
e4ffb1
+def set_multi_power_fn(connection, options, set_power_fn, get_power_fn, retry_attempts=1):
e4ffb1
+	for _ in range(retry_attempts):
e4ffb1
+		set_power_fn(connection, options)
e4ffb1
+		time.sleep(int(options["--power-wait"]))
e4ffb1
+
e4ffb1
+		for _ in range(int(options["--power-timeout"])):
e4ffb1
+			if get_power_fn(connection, options) != options["--action"]:
e4ffb1
+				time.sleep(1)
e4ffb1
+			else:
e4ffb1
+				return True
e4ffb1
+	return False
e4ffb1
+
e4ffb1
 def main():
e4ffb1
 	global override_status
e4ffb1
 	atexit.register(atexit_handler)
e4ffb1
 
e4ffb1
-	device_opt = ["login", "passwd", "tenant_name", "auth_url", "fabric_fencing", 
e4ffb1
+	device_opt = ["login", "passwd", "tenant_name", "auth_url", "fabric_fencing",
e4ffb1
 		"no_login", "no_password", "port", "domain", "no_shared_storage", "endpoint_type",
e4ffb1
 		"record_only", "instance_filtering", "insecure", "region_name"]
e4ffb1
 	define_new_opts()
e4ffb1
@@ -472,30 +437,28 @@
e4ffb1
 
e4ffb1
 	run_delay(options)
e4ffb1
 
e4ffb1
-	create_nova_connection(options)
e4ffb1
+	logging.debug("Running "+options["--action"])
e4ffb1
+	connection = create_nova_connection(options)
e4ffb1
 
e4ffb1
-	fix_plug_name(options)
e4ffb1
-	if options["--record-only"] in [ "1", "True", "true", "Yes", "yes"]:
e4ffb1
-		if options["--action"] == "on":
e4ffb1
-			set_attrd_status(options["--plug"], "no", options)
e4ffb1
-			sys.exit(0)
e4ffb1
-
e4ffb1
-		elif options["--action"] in ["off", "reboot"]:
e4ffb1
-			set_attrd_status(options["--plug"], "yes", options)
e4ffb1
-			sys.exit(0)
e4ffb1
+	if options["--action"] in ["off", "on", "reboot", "status"]:
e4ffb1
+		fix_plug_name(connection, options)
e4ffb1
 
e4ffb1
-		elif options["--action"] in ["monitor", "status"]:
e4ffb1
-			sys.exit(0)
e4ffb1
 
e4ffb1
-	if options["--action"] in ["off", "reboot"]:
e4ffb1
-		# Pretend we're 'on' so that the fencing library will always call set_power_status(off)
e4ffb1
-		override_status = "on"
e4ffb1
-
e4ffb1
-	if options["--action"] == "on":
e4ffb1
-		# Pretend we're 'off' so that the fencing library will always call set_power_status(on)
e4ffb1
-		override_status = "off"
e4ffb1
+	if options["--action"] in ["reboot"]:
e4ffb1
+		options["--action"]="off"
e4ffb1
+
e4ffb1
+	if options["--action"] in ["off", "on"]:
e4ffb1
+		# No status first, call our own version
e4ffb1
+		result = not set_multi_power_fn(connection, options, set_power_status, get_power_status_simple,
e4ffb1
+						1 + int(options["--retry-on"]))
e4ffb1
+	elif options["--action"] in ["monitor"]:
e4ffb1
+		result = 0
e4ffb1
+	else:
e4ffb1
+		result = fence_action(connection, options, set_power_status, get_power_status_simple, get_plugs_list, None)
e4ffb1
 
e4ffb1
-	result = fence_action(None, options, set_power_status, get_power_status, get_plugs_list, None)
e4ffb1
+	logging.debug("Result for "+options["--action"]+": "+repr(result))
e4ffb1
+	if result == None:
e4ffb1
+		result = 0
e4ffb1
 	sys.exit(result)
e4ffb1
 
e4ffb1
 if __name__ == "__main__":
e4ffb1
diff -uNr a/fence/agents/compute/fence_evacuate.py b/fence/agents/compute/fence_evacuate.py
e4ffb1
--- a/fence/agents/compute/fence_evacuate.py	1970-01-01 01:00:00.000000000 +0100
e4ffb1
+++ b/fence/agents/compute/fence_evacuate.py	2017-09-27 15:25:54.234304769 +0200
e4ffb1
@@ -0,0 +1,366 @@
e4ffb1
+#!/usr/bin/python -tt
e4ffb1
+
e4ffb1
+import sys
e4ffb1
+import time
e4ffb1
+import atexit
e4ffb1
+import logging
e4ffb1
+import inspect
e4ffb1
+import requests.exceptions
e4ffb1
+
e4ffb1
+sys.path.append("@FENCEAGENTSLIBDIR@")
e4ffb1
+from fencing import *
e4ffb1
+from fencing import fail_usage, is_executable, run_command, run_delay
e4ffb1
+
e4ffb1
+EVACUABLE_TAG = "evacuable"
e4ffb1
+TRUE_TAGS = ['true']
e4ffb1
+
e4ffb1
+def get_power_status(connection, options):
e4ffb1
+
e4ffb1
+	status = "unknown"
e4ffb1
+	logging.debug("get action: " + options["--action"])
e4ffb1
+
e4ffb1
+	if connection:
e4ffb1
+		try:
e4ffb1
+			services = connection.services.list(host=options["--plug"], binary="nova-compute")
e4ffb1
+			for service in services:
e4ffb1
+				logging.debug("Status of %s is %s, %s" % (service.binary, service.state, service.status))
e4ffb1
+				if service.state == "up" and service.status == "enabled":
e4ffb1
+					# Up and operational
e4ffb1
+					status = "on"
e4ffb1
+					
e4ffb1
+				elif service.state == "down" and service.status == "disabled":
e4ffb1
+					# Down and fenced
e4ffb1
+					status = "off"
e4ffb1
+
e4ffb1
+				elif service.state == "down":
e4ffb1
+					# Down and requires fencing
e4ffb1
+					status = "failed"
e4ffb1
+
e4ffb1
+				elif service.state == "up":
e4ffb1
+					# Up and requires unfencing
e4ffb1
+					status = "running"
e4ffb1
+				else:
e4ffb1
+					logging.warning("Unknown status detected from nova for %s: %s, %s" % (options["--plug"], service.state, service.status))
e4ffb1
+					status = "%s %s" % (service.state, service.status)
e4ffb1
+				break
e4ffb1
+		except requests.exception.ConnectionError as err:
e4ffb1
+			logging.warning("Nova connection failed: " + str(err))
e4ffb1
+	return status
e4ffb1
+
e4ffb1
+# NOTE(sbauza); We mimic the host-evacuate module since it's only a contrib
e4ffb1
+# module which is not stable
e4ffb1
+def _server_evacuate(connection, server, on_shared_storage):
e4ffb1
+	success = False
e4ffb1
+	error_message = ""
e4ffb1
+	try:
e4ffb1
+		logging.debug("Resurrecting instance: %s" % server)
e4ffb1
+		(response, dictionary) = connection.servers.evacuate(server=server, on_shared_storage=on_shared_storage)
e4ffb1
+
e4ffb1
+		if response == None:
e4ffb1
+			error_message = "No response while evacuating instance"
e4ffb1
+		elif response.status_code == 200:
e4ffb1
+			success = True
e4ffb1
+			error_message = response.reason
e4ffb1
+		else:
e4ffb1
+			error_message = response.reason
e4ffb1
+
e4ffb1
+	except Exception as e:
e4ffb1
+		error_message = "Error while evacuating instance: %s" % e
e4ffb1
+
e4ffb1
+	return {
e4ffb1
+		"uuid": server,
e4ffb1
+		"accepted": success,
e4ffb1
+		"reason": error_message,
e4ffb1
+		}
e4ffb1
+
e4ffb1
+def _is_server_evacuable(server, evac_flavors, evac_images):
e4ffb1
+	if server.flavor.get('id') in evac_flavors:
e4ffb1
+		return True
e4ffb1
+	if hasattr(server.image, 'get'):
e4ffb1
+		if server.image.get('id') in evac_images:
e4ffb1
+			return True
e4ffb1
+	logging.debug("Instance %s is not evacuable" % server.image.get('id'))
e4ffb1
+	return False
e4ffb1
+
e4ffb1
+def _get_evacuable_flavors(connection):
e4ffb1
+	result = []
e4ffb1
+	flavors = connection.flavors.list()
e4ffb1
+	# Since the detailed view for all flavors doesn't provide the extra specs,
e4ffb1
+	# we need to call each of the flavor to get them.
e4ffb1
+	for flavor in flavors:
e4ffb1
+		tag = flavor.get_keys().get(EVACUABLE_TAG)
e4ffb1
+		if tag and tag.strip().lower() in TRUE_TAGS:
e4ffb1
+			result.append(flavor.id)
e4ffb1
+	return result
e4ffb1
+
e4ffb1
+def _get_evacuable_images(connection):
e4ffb1
+	result = []
e4ffb1
+	images = []
e4ffb1
+	if hasattr(connection, "images"):
e4ffb1
+		images = connection.images.list(detailed=True)
e4ffb1
+	elif hasattr(connection, "glance"):
e4ffb1
+		# OSP12+
e4ffb1
+		images = connection.glance.list()
e4ffb1
+
e4ffb1
+	for image in images:
e4ffb1
+		if hasattr(image, 'metadata'):
e4ffb1
+			tag = image.metadata.get(EVACUABLE_TAG)
e4ffb1
+			if tag and tag.strip().lower() in TRUE_TAGS:
e4ffb1
+				result.append(image.id)
e4ffb1
+		elif hasattr(image, 'tags'):
e4ffb1
+			# OSP12+
e4ffb1
+			if EVACUABLE_TAG in image.tags:
e4ffb1
+				result.append(image.id)
e4ffb1
+	return result
e4ffb1
+
e4ffb1
+def _host_evacuate(connection, options):
e4ffb1
+	result = True
e4ffb1
+	images = _get_evacuable_images(connection)
e4ffb1
+	flavors = _get_evacuable_flavors(connection)
e4ffb1
+	servers = connection.servers.list(search_opts={'host': options["--plug"], 'all_tenants': 1 })
e4ffb1
+
e4ffb1
+	if options["--instance-filtering"] == "False":
e4ffb1
+		logging.debug("Not evacuating anything")
e4ffb1
+		evacuables = []
e4ffb1
+	elif len(flavors) or len(images):
e4ffb1
+		logging.debug("Filtering images and flavors: %s %s" % (repr(flavors), repr(images)))
e4ffb1
+		# Identify all evacuable servers
e4ffb1
+		logging.debug("Checking %s" % repr(servers))
e4ffb1
+		evacuables = [server for server in servers
e4ffb1
+				if _is_server_evacuable(server, flavors, images)]
e4ffb1
+		logging.debug("Evacuating %s" % repr(evacuables))
e4ffb1
+	else:
e4ffb1
+		logging.debug("Evacuating all images and flavors")
e4ffb1
+		evacuables = servers
e4ffb1
+
e4ffb1
+	if options["--no-shared-storage"] != "False":
e4ffb1
+		on_shared_storage = False
e4ffb1
+	else:
e4ffb1
+		on_shared_storage = True
e4ffb1
+
e4ffb1
+	for server in evacuables:
e4ffb1
+		logging.debug("Processing %s" % server)
e4ffb1
+		if hasattr(server, 'id'):
e4ffb1
+			response = _server_evacuate(connection, server.id, on_shared_storage)
e4ffb1
+			if response["accepted"]:
e4ffb1
+				logging.debug("Evacuated %s from %s: %s" %
e4ffb1
+					      (response["uuid"], options["--plug"], response["reason"]))
e4ffb1
+			else:
e4ffb1
+				logging.error("Evacuation of %s on %s failed: %s" %
e4ffb1
+					      (response["uuid"], options["--plug"], response["reason"]))
e4ffb1
+				result = False
e4ffb1
+		else:
e4ffb1
+			logging.error("Could not evacuate instance: %s" % server.to_dict())
e4ffb1
+			# Should a malformed instance result in a failed evacuation?
e4ffb1
+			# result = False
e4ffb1
+	return result
e4ffb1
+
e4ffb1
+def set_attrd_status(host, status, options):
e4ffb1
+	logging.debug("Setting fencing status for %s to %s" % (host, status))
e4ffb1
+	run_command(options, "attrd_updater -p -n evacuate -Q -N %s -U %s" % (host, status))
e4ffb1
+
e4ffb1
+def set_power_status(connection, options):
e4ffb1
+	logging.debug("set action: " + options["--action"])
e4ffb1
+
e4ffb1
+	if not connection:
e4ffb1
+		return
e4ffb1
+
e4ffb1
+	if options["--action"] == "off" and not _host_evacuate(options):
e4ffb1
+		sys.exit(1)
e4ffb1
+
e4ffb1
+	sys.exit(0)
e4ffb1
+
e4ffb1
+def get_plugs_list(connection, options):
e4ffb1
+	result = {}
e4ffb1
+
e4ffb1
+	if connection:
e4ffb1
+		services = connection.services.list(binary="nova-compute")
e4ffb1
+		for service in services:
e4ffb1
+			longhost = service.host
e4ffb1
+			shorthost = longhost.split('.')[0]
e4ffb1
+			result[longhost] = ("", None)
e4ffb1
+			result[shorthost] = ("", None)
e4ffb1
+	return result
e4ffb1
+
e4ffb1
+def create_nova_connection(options):
e4ffb1
+	nova = None
e4ffb1
+
e4ffb1
+	try:
e4ffb1
+		from novaclient import client
e4ffb1
+		from novaclient.exceptions import NotAcceptable
e4ffb1
+	except ImportError:
e4ffb1
+		fail_usage("Nova not found or not accessible")
e4ffb1
+
e4ffb1
+	versions = [ "2.11", "2" ]
e4ffb1
+	for version in versions:
e4ffb1
+		clientargs = inspect.getargspec(client.Client).varargs
e4ffb1
+
e4ffb1
+		# Some versions of Openstack prior to Ocata only
e4ffb1
+		# supported positional arguments for username,
e4ffb1
+		# password and tenant.
e4ffb1
+		#
e4ffb1
+		# Versions since Ocata only support named arguments.
e4ffb1
+		#
e4ffb1
+		# So we need to use introspection to figure out how to
e4ffb1
+		# create a Nova client.
e4ffb1
+		#
e4ffb1
+		# Happy days
e4ffb1
+		#
e4ffb1
+		if clientargs:
e4ffb1
+			# OSP < 11
e4ffb1
+			# ArgSpec(args=['version', 'username', 'password', 'project_id', 'auth_url'],
e4ffb1
+			#	 varargs=None,
e4ffb1
+			#	 keywords='kwargs', defaults=(None, None, None, None))
e4ffb1
+			nova = client.Client(version,
e4ffb1
+					     options["--username"],
e4ffb1
+					     options["--password"],
e4ffb1
+					     options["--tenant-name"],
e4ffb1
+					     options["--auth-url"],
e4ffb1
+					     insecure=options["--insecure"],
e4ffb1
+					     region_name=options["--region-name"],
e4ffb1
+					     endpoint_type=options["--endpoint-type"],
e4ffb1
+					     http_log_debug=options.has_key("--verbose"))
e4ffb1
+		else:
e4ffb1
+			# OSP >= 11
e4ffb1
+			# ArgSpec(args=['version'], varargs='args', keywords='kwargs', defaults=None)
e4ffb1
+			nova = client.Client(version,
e4ffb1
+					     username=options["--username"],
e4ffb1
+					     password=options["--password"],
e4ffb1
+					     tenant_name=options["--tenant-name"],
e4ffb1
+					     auth_url=options["--auth-url"],
e4ffb1
+					     insecure=options["--insecure"],
e4ffb1
+					     region_name=options["--region-name"],
e4ffb1
+					     endpoint_type=options["--endpoint-type"],
e4ffb1
+					     http_log_debug=options.has_key("--verbose"))
e4ffb1
+
e4ffb1
+		try:
e4ffb1
+			nova.hypervisors.list()
e4ffb1
+			return nova
e4ffb1
+
e4ffb1
+		except NotAcceptable as e:
e4ffb1
+			logging.warning(e)
e4ffb1
+
e4ffb1
+		except Exception as e:
e4ffb1
+			logging.warning("Nova connection failed. %s: %s" % (e.__class__.__name__, e))
e4ffb1
+
e4ffb1
+	logging.warning("Couldn't obtain a supported connection to nova, tried: %s\n" % repr(versions))
e4ffb1
+	return None
e4ffb1
+
e4ffb1
+def define_new_opts():
e4ffb1
+	all_opt["endpoint_type"] = {
e4ffb1
+		"getopt" : "e:",
e4ffb1
+		"longopt" : "endpoint-type",
e4ffb1
+		"help" : "-e, --endpoint-type=[endpoint] Nova Endpoint type (publicURL, internalURL, adminURL)",
e4ffb1
+		"required" : "0",
e4ffb1
+		"shortdesc" : "Nova Endpoint type",
e4ffb1
+		"default" : "internalURL",
e4ffb1
+		"order": 1,
e4ffb1
+	}
e4ffb1
+	all_opt["tenant_name"] = {
e4ffb1
+		"getopt" : "t:",
e4ffb1
+		"longopt" : "tenant-name",
e4ffb1
+		"help" : "-t, --tenant-name=[tenant]     Keystone Admin Tenant",
e4ffb1
+		"required" : "0",
e4ffb1
+		"shortdesc" : "Keystone Admin Tenant",
e4ffb1
+		"default" : "",
e4ffb1
+		"order": 1,
e4ffb1
+	}
e4ffb1
+	all_opt["auth_url"] = {
e4ffb1
+		"getopt" : "k:",
e4ffb1
+		"longopt" : "auth-url",
e4ffb1
+		"help" : "-k, --auth-url=[url]                   Keystone Admin Auth URL",
e4ffb1
+		"required" : "0",
e4ffb1
+		"shortdesc" : "Keystone Admin Auth URL",
e4ffb1
+		"default" : "",
e4ffb1
+		"order": 1,
e4ffb1
+	}
e4ffb1
+	all_opt["region_name"] = {
e4ffb1
+		"getopt" : "",
e4ffb1
+		"longopt" : "region-name",
e4ffb1
+		"help" : "--region-name=[region]                 Region Name",
e4ffb1
+		"required" : "0",
e4ffb1
+		"shortdesc" : "Region Name",
e4ffb1
+		"default" : "",
e4ffb1
+		"order": 1,
e4ffb1
+	}
e4ffb1
+	all_opt["insecure"] = {
e4ffb1
+		"getopt" : "",
e4ffb1
+		"longopt" : "insecure",
e4ffb1
+		"help" : "--insecure                                     Explicitly allow agent to perform \"insecure\" TLS (https) requests",
e4ffb1
+		"required" : "0",
e4ffb1
+		"shortdesc" : "Allow Insecure TLS Requests",
e4ffb1
+		"default" : "False",
e4ffb1
+		"order": 2,
e4ffb1
+	}
e4ffb1
+	all_opt["domain"] = {
e4ffb1
+		"getopt" : "d:",
e4ffb1
+		"longopt" : "domain",
e4ffb1
+		"help" : "-d, --domain=[string]                  DNS domain in which hosts live, useful when the cluster uses short names and nova uses FQDN",
e4ffb1
+		"required" : "0",
e4ffb1
+		"shortdesc" : "DNS domain in which hosts live",
e4ffb1
+		"order": 5,
e4ffb1
+	}
e4ffb1
+	all_opt["instance_filtering"] = {
e4ffb1
+		"getopt" : "",
e4ffb1
+		"longopt" : "instance-filtering",
e4ffb1
+		"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)",
e4ffb1
+		"required" : "0",
e4ffb1
+		"shortdesc" : "Allow instances to be evacuated",
e4ffb1
+		"default" : "True",
e4ffb1
+		"order": 5,
e4ffb1
+	}
e4ffb1
+	all_opt["no_shared_storage"] = {
e4ffb1
+		"getopt" : "",
e4ffb1
+		"longopt" : "no-shared-storage",
e4ffb1
+		"help" : "--no-shared-storage            Disable functionality for shared storage",
e4ffb1
+		"required" : "0",
e4ffb1
+		"shortdesc" : "Disable functionality for dealing with shared storage",
e4ffb1
+		"default" : "False",
e4ffb1
+		"order": 5,
e4ffb1
+	}
e4ffb1
+
e4ffb1
+def main():
e4ffb1
+	atexit.register(atexit_handler)
e4ffb1
+
e4ffb1
+	device_opt = ["login", "passwd", "tenant_name", "auth_url",
e4ffb1
+		"no_login", "no_password", "port", "domain", "no_shared_storage", "endpoint_type",
e4ffb1
+		"instance_filtering", "insecure", "region_name"]
e4ffb1
+	define_new_opts()
e4ffb1
+	all_opt["shell_timeout"]["default"] = "180"
e4ffb1
+
e4ffb1
+	options = check_input(device_opt, process_input(device_opt))
e4ffb1
+
e4ffb1
+	docs = {}
e4ffb1
+	docs["shortdesc"] = "Fence agent for the automatic resurrection of OpenStack compute instances"
e4ffb1
+	docs["longdesc"] = "Used to reschedule flagged instances"
e4ffb1
+	docs["vendorurl"] = ""
e4ffb1
+
e4ffb1
+	show_docs(options, docs)
e4ffb1
+
e4ffb1
+	run_delay(options)
e4ffb1
+
e4ffb1
+	connection = create_nova_connection(options)
e4ffb1
+
e4ffb1
+	# Un-evacuating a server doesn't make sense
e4ffb1
+	if options["--action"] in ["on"]:
e4ffb1
+		logging.error("Action %s is not supported by this agent" % (options["--action"]))
e4ffb1
+		sys.exit(1)
e4ffb1
+
e4ffb1
+	if options["--action"] in ["off", "reboot"]:
e4ffb1
+		status = get_power_status(connection, options)
e4ffb1
+		if status != "off":
e4ffb1
+			logging.error("Cannot resurrect instances from %s in state '%s'" % (options["--plug"], status))
e4ffb1
+			sys.exit(1)
e4ffb1
+
e4ffb1
+		elif not _host_evacuate(connection, options):
e4ffb1
+			logging.error("Resurrection of instances from %s failed" % (options["--plug"]))
e4ffb1
+			sys.exit(1)
e4ffb1
+
e4ffb1
+		logging.info("Resurrection of instances from %s complete" % (options["--plug"]))
e4ffb1
+		sys.exit(0)
e4ffb1
+
e4ffb1
+	result = fence_action(connection, options, set_power_status, get_power_status, get_plugs_list, None)
e4ffb1
+	sys.exit(result)
e4ffb1
+
e4ffb1
+if __name__ == "__main__":
e4ffb1
+	main()
e4ffb1
diff -uNr a/fence/agents/compute/Makefile.am b/fence/agents/compute/Makefile.am
e4ffb1
--- a/fence/agents/compute/Makefile.am	2017-09-27 15:01:34.844643650 +0200
e4ffb1
+++ b/fence/agents/compute/Makefile.am	2017-09-27 15:57:50.963839738 +0200
e4ffb1
@@ -1,14 +1,14 @@
e4ffb1
 MAINTAINERCLEANFILES	= Makefile.in
e4ffb1
 
e4ffb1
-TARGET			= fence_compute
e4ffb1
+TARGET			= fence_compute fence_evacuate
e4ffb1
 
e4ffb1
-SRC			= $(TARGET).py
e4ffb1
+SRC			= $(TARGET:=.py)
e4ffb1
 
e4ffb1
 EXTRA_DIST		= $(SRC)
e4ffb1
 
e4ffb1
 sbin_SCRIPTS		= $(TARGET)
e4ffb1
 
e4ffb1
-man_MANS		= $(TARGET).8
e4ffb1
+man_MANS		= $(TARGET:=.8)
e4ffb1
 
e4ffb1
 FENCE_TEST_ARGS		= -l test -p test -n 1
e4ffb1
 
e4ffb1
diff -uNr a/tests/data/metadata/fence_evacuate.xml b/tests/data/metadata/fence_evacuate.xml
e4ffb1
--- a/tests/data/metadata/fence_evacuate.xml	1970-01-01 01:00:00.000000000 +0100
e4ffb1
+++ b/tests/data/metadata/fence_evacuate.xml	2017-09-27 15:28:10.978063549 +0200
e4ffb1
@@ -0,0 +1,163 @@
e4ffb1
+
e4ffb1
+<resource-agent name="fence_evacuate" shortdesc="Fence agent for the automatic resurrection of OpenStack compute instances" >
e4ffb1
+<longdesc>Used to reschedule flagged instances</longdesc>
e4ffb1
+<vendor-url></vendor-url>
e4ffb1
+<parameters>
e4ffb1
+	<parameter name="tenant_name" unique="0" required="0">
e4ffb1
+		<getopt mixed="-t, --tenant-name=[tenant]" />
e4ffb1
+		<content type="string"  />
e4ffb1
+		<shortdesc lang="en">Keystone Admin Tenant</shortdesc>
e4ffb1
+	</parameter>
e4ffb1
+	<parameter name="auth_url" unique="0" required="0">
e4ffb1
+		<getopt mixed="-k, --auth-url=[url]" />
e4ffb1
+		<content type="string"  />
e4ffb1
+		<shortdesc lang="en">Keystone Admin Auth URL</shortdesc>
e4ffb1
+	</parameter>
e4ffb1
+	<parameter name="port" unique="0" required="1" deprecated="1">
e4ffb1
+		<getopt mixed="-n, --plug=[id]" />
e4ffb1
+		<content type="string"  />
e4ffb1
+		<shortdesc lang="en">Physical plug number, name of virtual machine or UUID</shortdesc>
e4ffb1
+	</parameter>
e4ffb1
+	<parameter name="passwd_script" unique="0" required="0" deprecated="1">
e4ffb1
+		<getopt mixed="-S, --password-script=[script]" />
e4ffb1
+		<content type="string"  />
e4ffb1
+		<shortdesc lang="en">Script to retrieve password</shortdesc>
e4ffb1
+	</parameter>
e4ffb1
+	<parameter name="region_name" unique="0" required="0">
e4ffb1
+		<getopt mixed="--region-name=[region]" />
e4ffb1
+		<content type="boolean"  />
e4ffb1
+		<shortdesc lang="en">Region Name</shortdesc>
e4ffb1
+	</parameter>
e4ffb1
+	<parameter name="passwd" unique="0" required="0" deprecated="1">
e4ffb1
+		<getopt mixed="-p, --password=[password]" />
e4ffb1
+		<content type="string"  />
e4ffb1
+		<shortdesc lang="en">Login password or passphrase</shortdesc>
e4ffb1
+	</parameter>
e4ffb1
+	<parameter name="endpoint_type" unique="0" required="0">
e4ffb1
+		<getopt mixed="-e, --endpoint-type=[endpoint]" />
e4ffb1
+		<content type="string" default="internalURL"  />
e4ffb1
+		<shortdesc lang="en">Nova Endpoint type</shortdesc>
e4ffb1
+	</parameter>
e4ffb1
+	<parameter name="action" unique="0" required="1">
e4ffb1
+		<getopt mixed="-o, --action=[action]" />
e4ffb1
+		<content type="string" default="reboot"  />
e4ffb1
+		<shortdesc lang="en">Fencing Action</shortdesc>
e4ffb1
+	</parameter>
e4ffb1
+	<parameter name="login" unique="0" required="0" deprecated="1">
e4ffb1
+		<getopt mixed="-l, --username=[name]" />
e4ffb1
+		<content type="string"  />
e4ffb1
+		<shortdesc lang="en">Login Name</shortdesc>
e4ffb1
+	</parameter>
e4ffb1
+	<parameter name="plug" unique="0" required="1" obsoletes="port">
e4ffb1
+		<getopt mixed="-n, --plug=[id]" />
e4ffb1
+		<content type="string"  />
e4ffb1
+		<shortdesc lang="en">Physical plug number, name of virtual machine or UUID</shortdesc>
e4ffb1
+	</parameter>
e4ffb1
+	<parameter name="username" unique="0" required="0" obsoletes="login">
e4ffb1
+		<getopt mixed="-l, --username=[name]" />
e4ffb1
+		<content type="string"  />
e4ffb1
+		<shortdesc lang="en">Login Name</shortdesc>
e4ffb1
+	</parameter>
e4ffb1
+	<parameter name="password" unique="0" required="0" obsoletes="passwd">
e4ffb1
+		<getopt mixed="-p, --password=[password]" />
e4ffb1
+		<content type="string"  />
e4ffb1
+		<shortdesc lang="en">Login password or passphrase</shortdesc>
e4ffb1
+	</parameter>
e4ffb1
+	<parameter name="password_script" unique="0" required="0" obsoletes="passwd_script">
e4ffb1
+		<getopt mixed="-S, --password-script=[script]" />
e4ffb1
+		<content type="string"  />
e4ffb1
+		<shortdesc lang="en">Script to retrieve password</shortdesc>
e4ffb1
+	</parameter>
e4ffb1
+	<parameter name="insecure" unique="0" required="0">
e4ffb1
+		<getopt mixed="--insecure" />
e4ffb1
+		<content type="boolean" default="False"  />
e4ffb1
+		<shortdesc lang="en">Allow Insecure TLS Requests</shortdesc>
e4ffb1
+	</parameter>
e4ffb1
+	<parameter name="domain" unique="0" required="0">
e4ffb1
+		<getopt mixed="-d, --domain=[string]" />
e4ffb1
+		<content type="string"  />
e4ffb1
+		<shortdesc lang="en">DNS domain in which hosts live</shortdesc>
e4ffb1
+	</parameter>
e4ffb1
+	<parameter name="instance_filtering" unique="0" required="0">
e4ffb1
+		<getopt mixed="--instance-filtering" />
e4ffb1
+		<content type="boolean" default="True"  />
e4ffb1
+		<shortdesc lang="en">Allow instances to be evacuated</shortdesc>
e4ffb1
+	</parameter>
e4ffb1
+	<parameter name="no_shared_storage" unique="0" required="0">
e4ffb1
+		<getopt mixed="--no-shared-storage" />
e4ffb1
+		<content type="boolean" default="False"  />
e4ffb1
+		<shortdesc lang="en">Disable functionality for dealing with shared storage</shortdesc>
e4ffb1
+	</parameter>
e4ffb1
+	<parameter name="verbose" unique="0" required="0">
e4ffb1
+		<getopt mixed="-v, --verbose" />
e4ffb1
+		<content type="boolean"  />
e4ffb1
+		<shortdesc lang="en">Verbose mode</shortdesc>
e4ffb1
+	</parameter>
e4ffb1
+	<parameter name="debug" unique="0" required="0" deprecated="1">
e4ffb1
+		<getopt mixed="-D, --debug-file=[debugfile]" />
e4ffb1
+		<content type="string"  />
e4ffb1
+		<shortdesc lang="en">Write debug information to given file</shortdesc>
e4ffb1
+	</parameter>
e4ffb1
+	<parameter name="debug_file" unique="0" required="0" obsoletes="debug">
e4ffb1
+		<getopt mixed="-D, --debug-file=[debugfile]" />
e4ffb1
+		<content type="string"  />
e4ffb1
+		<shortdesc lang="en">Write debug information to given file</shortdesc>
e4ffb1
+	</parameter>
e4ffb1
+	<parameter name="version" unique="0" required="0">
e4ffb1
+		<getopt mixed="-V, --version" />
e4ffb1
+		<content type="boolean"  />
e4ffb1
+		<shortdesc lang="en">Display version information and exit</shortdesc>
e4ffb1
+	</parameter>
e4ffb1
+	<parameter name="help" unique="0" required="0">
e4ffb1
+		<getopt mixed="-h, --help" />
e4ffb1
+		<content type="boolean"  />
e4ffb1
+		<shortdesc lang="en">Display help and exit</shortdesc>
e4ffb1
+	</parameter>
e4ffb1
+	<parameter name="separator" unique="0" required="0">
e4ffb1
+		<getopt mixed="-C, --separator=[char]" />
e4ffb1
+		<content type="string" default=","  />
e4ffb1
+		<shortdesc lang="en">Separator for CSV created by operation list</shortdesc>
e4ffb1
+	</parameter>
e4ffb1
+	<parameter name="power_wait" unique="0" required="0">
e4ffb1
+		<getopt mixed="--power-wait=[seconds]" />
e4ffb1
+		<content type="second" default="0"  />
e4ffb1
+		<shortdesc lang="en">Wait X seconds after issuing ON/OFF</shortdesc>
e4ffb1
+	</parameter>
e4ffb1
+	<parameter name="login_timeout" unique="0" required="0">
e4ffb1
+		<getopt mixed="--login-timeout=[seconds]" />
e4ffb1
+		<content type="second" default="5"  />
e4ffb1
+		<shortdesc lang="en">Wait X seconds for cmd prompt after login</shortdesc>
e4ffb1
+	</parameter>
e4ffb1
+	<parameter name="delay" unique="0" required="0">
e4ffb1
+		<getopt mixed="--delay=[seconds]" />
e4ffb1
+		<content type="second" default="0"  />
e4ffb1
+		<shortdesc lang="en">Wait X seconds before fencing is started</shortdesc>
e4ffb1
+	</parameter>
e4ffb1
+	<parameter name="power_timeout" unique="0" required="0">
e4ffb1
+		<getopt mixed="--power-timeout=[seconds]" />
e4ffb1
+		<content type="second" default="20"  />
e4ffb1
+		<shortdesc lang="en">Test X seconds for status change after ON/OFF</shortdesc>
e4ffb1
+	</parameter>
e4ffb1
+	<parameter name="shell_timeout" unique="0" required="0">
e4ffb1
+		<getopt mixed="--shell-timeout=[seconds]" />
e4ffb1
+		<content type="second" default="180"  />
e4ffb1
+		<shortdesc lang="en">Wait X seconds for cmd prompt after issuing command</shortdesc>
e4ffb1
+	</parameter>
e4ffb1
+	<parameter name="retry_on" unique="0" required="0">
e4ffb1
+		<getopt mixed="--retry-on=[attempts]" />
e4ffb1
+		<content type="integer" default="1"  />
e4ffb1
+		<shortdesc lang="en">Count of attempts to retry power on</shortdesc>
e4ffb1
+	</parameter>
e4ffb1
+</parameters>
e4ffb1
+<actions>
e4ffb1
+	<action name="on" automatic="0"/>
e4ffb1
+	<action name="off" />
e4ffb1
+	<action name="reboot" />
e4ffb1
+	<action name="status" />
e4ffb1
+	<action name="list" />
e4ffb1
+	<action name="list-status" />
e4ffb1
+	<action name="monitor" />
e4ffb1
+	<action name="metadata" />
e4ffb1
+	<action name="validate-all" />
e4ffb1
+</actions>
e4ffb1
+</resource-agent>