Blame SOURCES/bz1640575-1-ocf.py-update.patch

ab36d8
--- a/heartbeat/ocf.py	2020-04-08 13:03:20.543477544 +0200
ab36d8
+++ b/heartbeat/ocf.py	2020-04-06 10:23:45.950913519 +0200
ab36d8
@@ -88,6 +88,10 @@
ab36d8
 
ab36d8
 OCF_RESOURCE_INSTANCE = env.get("OCF_RESOURCE_INSTANCE")
ab36d8
 
ab36d8
+OCF_ACTION = env.get("__OCF_ACTION")
ab36d8
+if OCF_ACTION is None and len(argv) == 2:
ab36d8
+	OCF_ACTION = argv[1]
ab36d8
+
ab36d8
 HA_DEBUG = env.get("HA_debug", 0)
ab36d8
 HA_DATEFMT = env.get("HA_DATEFMT", "%b %d %T ")
ab36d8
 HA_LOGFACILITY = env.get("HA_LOGFACILITY")
ab36d8
@@ -135,3 +139,343 @@
ab36d8
 	log.addHandler(dfh)
ab36d8
 
ab36d8
 logger = logging.LoggerAdapter(log, {'OCF_RESOURCE_INSTANCE': OCF_RESOURCE_INSTANCE})
ab36d8
+
ab36d8
+
ab36d8
+_exit_reason_set = False
ab36d8
+
ab36d8
+def ocf_exit_reason(msg):
ab36d8
+	"""
ab36d8
+	Print exit error string to stderr.
ab36d8
+
ab36d8
+	Allows the OCF agent to provide a string describing
ab36d8
+	why the exit code was returned.
ab36d8
+	"""
ab36d8
+	global _exit_reason_set
ab36d8
+	cookie = env.get("OCF_EXIT_REASON_PREFIX", "ocf-exit-reason:")
ab36d8
+	sys.stderr.write("{}{}\n".format(cookie, msg))
ab36d8
+	sys.stderr.flush()
ab36d8
+	logger.error(msg)
ab36d8
+	_exit_reason_set = True
ab36d8
+
ab36d8
+
ab36d8
+def have_binary(name):
ab36d8
+	"""
ab36d8
+	True if binary exists, False otherwise.
ab36d8
+	"""
ab36d8
+	def _access_check(fn):
ab36d8
+		return (os.path.exists(fn) and
ab36d8
+				os.access(fn, os.F_OK | os.X_OK) and
ab36d8
+				not os.path.isdir(fn))
ab36d8
+	if _access_check(name):
ab36d8
+		return True
ab36d8
+	path = env.get("PATH", os.defpath).split(os.pathsep)
ab36d8
+	seen = set()
ab36d8
+	for dir in path:
ab36d8
+		dir = os.path.normcase(dir)
ab36d8
+		if dir not in seen:
ab36d8
+			seen.add(dir)
ab36d8
+			name2 = os.path.join(dir, name)
ab36d8
+			if _access_check(name2):
ab36d8
+				return True
ab36d8
+	return False
ab36d8
+
ab36d8
+
ab36d8
+def is_true(val):
ab36d8
+	"""
ab36d8
+	Convert an OCF truth value to a
ab36d8
+	Python boolean.
ab36d8
+	"""
ab36d8
+	return val in ("yes", "true", "1", 1, "YES", "TRUE", "ja", "on", "ON", True)
ab36d8
+
ab36d8
+
ab36d8
+def is_probe():
ab36d8
+	"""
ab36d8
+	A probe is defined as a monitor operation
ab36d8
+	with an interval of zero. This is called
ab36d8
+	by Pacemaker to check the status of a possibly
ab36d8
+	not running resource.
ab36d8
+	"""
ab36d8
+	return (OCF_ACTION == "monitor" and
ab36d8
+			env.get("OCF_RESKEY_CRM_meta_interval", "") == "0")
ab36d8
+
ab36d8
+
ab36d8
+def get_parameter(name, default=None):
ab36d8
+	"""
ab36d8
+	Extract the parameter value from the environment
ab36d8
+	"""
ab36d8
+	return env.get("OCF_RESKEY_{}".format(name), default)
ab36d8
+
ab36d8
+
ab36d8
+def distro():
ab36d8
+	"""
ab36d8
+	Return name of distribution/platform.
ab36d8
+
ab36d8
+	If possible, returns "name/version", else
ab36d8
+	just "name".
ab36d8
+	"""
ab36d8
+	import subprocess
ab36d8
+	import platform
ab36d8
+	try:
ab36d8
+		ret = subprocess.check_output(["lsb_release", "-si"])
ab36d8
+		if type(ret) != str:
ab36d8
+			ret = ret.decode()
ab36d8
+		distro = ret.strip()
ab36d8
+		ret = subprocess.check_output(["lsb_release", "-sr"])
ab36d8
+		if type(ret) != str:
ab36d8
+			ret = ret.decode()
ab36d8
+		version = ret.strip()
ab36d8
+		return "{}/{}".format(distro, version)
ab36d8
+	except Exception:
ab36d8
+		if os.path.exists("/etc/debian_version"):
ab36d8
+			return "Debian"
ab36d8
+		if os.path.exists("/etc/SuSE-release"):
ab36d8
+			return "SUSE"
ab36d8
+		if os.path.exists("/etc/redhat-release"):
ab36d8
+			return "Redhat"
ab36d8
+	return platform.system()
ab36d8
+
ab36d8
+
ab36d8
+class Parameter(object):
ab36d8
+	def __init__(self, name, shortdesc, longdesc, content_type, unique, required, default):
ab36d8
+		self.name = name
ab36d8
+		self.shortdesc = shortdesc
ab36d8
+		self.longdesc = longdesc
ab36d8
+		self.content_type = content_type
ab36d8
+		self.unique = unique
ab36d8
+		self.required = required
ab36d8
+		self.default = default
ab36d8
+
ab36d8
+	def __str__(self):
ab36d8
+		return self.to_xml()
ab36d8
+
ab36d8
+	def to_xml(self):
ab36d8
+		ret = '
ab36d8
+		if self.unique:
ab36d8
+			ret += ' unique="1"'
ab36d8
+		if self.required:
ab36d8
+			ret += ' required="1"'
ab36d8
+		ret += ">\n"
ab36d8
+		ret += '<longdesc lang="en">' + self.longdesc + '</longdesc>' + "\n"
ab36d8
+		ret += '<shortdesc lang="en">' + self.shortdesc + '</shortdesc>' + "\n"
ab36d8
+		ret += '
ab36d8
+		if self.default is not None:
ab36d8
+			ret += ' default="{}"'.format(self.default)
ab36d8
+		ret += " />\n"
ab36d8
+		ret += "</parameter>\n"
ab36d8
+		return ret
ab36d8
+
ab36d8
+
ab36d8
+
ab36d8
+class Action(object):
ab36d8
+	def __init__(self, name, timeout, interval, depth, role):
ab36d8
+		self.name = name
ab36d8
+		self.timeout = timeout
ab36d8
+		self.interval = interval
ab36d8
+		self.depth = depth
ab36d8
+		self.role = role
ab36d8
+
ab36d8
+	def __str__(self):
ab36d8
+		return self.to_xml()
ab36d8
+
ab36d8
+	def to_xml(self):
ab36d8
+		def opt(s, name, var):
ab36d8
+			if var is not None:
ab36d8
+				if type(var) == int and name in ("timeout", "interval"):
ab36d8
+					var = "{}s".format(var)
ab36d8
+				return s + ' {}="{}"'.format(name, var)
ab36d8
+			return s
ab36d8
+		ret = '
ab36d8
+		ret = opt(ret, "timeout", self.timeout)
ab36d8
+		ret = opt(ret, "interval", self.interval)
ab36d8
+		ret = opt(ret, "depth", self.depth)
ab36d8
+		ret = opt(ret, "role", self.role)
ab36d8
+		ret += " />\n"
ab36d8
+		return ret
ab36d8
+
ab36d8
+
ab36d8
+class Agent(object):
ab36d8
+	"""
ab36d8
+	OCF Resource Agent metadata XML generator helper.
ab36d8
+
ab36d8
+	Use add_parameter/add_action to define parameters
ab36d8
+	and actions for the agent. Then call run() to
ab36d8
+	start the agent main loop.
ab36d8
+
ab36d8
+	See doc/dev-guides/writing-python-agents.md for an example
ab36d8
+	of how to use it.
ab36d8
+	"""
ab36d8
+
ab36d8
+	def __init__(self, name, shortdesc, longdesc):
ab36d8
+		self.name = name
ab36d8
+		self.shortdesc = shortdesc
ab36d8
+		self.longdesc = longdesc
ab36d8
+		self.parameters = []
ab36d8
+		self.actions = []
ab36d8
+		self._handlers = {}
ab36d8
+
ab36d8
+	def add_parameter(self, name, shortdesc="", longdesc="", content_type="string", unique=False, required=False, default=None):
ab36d8
+		for param in self.parameters:
ab36d8
+			if param.name == name:
ab36d8
+				raise ValueError("Parameter {} defined twice in metadata".format(name))
ab36d8
+		self.parameters.append(Parameter(name=name,
ab36d8
+										 shortdesc=shortdesc,
ab36d8
+										 longdesc=longdesc,
ab36d8
+										 content_type=content_type,
ab36d8
+										 unique=unique,
ab36d8
+										 required=required,
ab36d8
+										 default=default))
ab36d8
+		return self
ab36d8
+
ab36d8
+	def add_action(self, name, timeout=None, interval=None, depth=None, role=None, handler=None):
ab36d8
+		self.actions.append(Action(name=name,
ab36d8
+								   timeout=timeout,
ab36d8
+								   interval=interval,
ab36d8
+								   depth=depth,
ab36d8
+								   role=role))
ab36d8
+		if handler is not None:
ab36d8
+			self._handlers[name] = handler
ab36d8
+		return self
ab36d8
+
ab36d8
+	def __str__(self):
ab36d8
+		return self.to_xml()
ab36d8
+
ab36d8
+	def to_xml(self):
ab36d8
+		return """
ab36d8
+
ab36d8
+<resource-agent name="{name}">
ab36d8
+<version>1.0</version>
ab36d8
+<longdesc lang="en">
ab36d8
+{longdesc}
ab36d8
+</longdesc>
ab36d8
+<shortdesc lang="en">{shortdesc}</shortdesc>
ab36d8
+
ab36d8
+<parameters>
ab36d8
+{parameters}
ab36d8
+</parameters>
ab36d8
+
ab36d8
+<actions>
ab36d8
+{actions}
ab36d8
+</actions>
ab36d8
+
ab36d8
+</resource-agent>
ab36d8
+""".format(name=self.name,
ab36d8
+		   longdesc=self.longdesc,
ab36d8
+		   shortdesc=self.shortdesc,
ab36d8
+		   parameters="".join(p.to_xml() for p in self.parameters),
ab36d8
+		   actions="".join(a.to_xml() for a in self.actions))
ab36d8
+
ab36d8
+	def run(self):
ab36d8
+		run(self)
ab36d8
+
ab36d8
+
ab36d8
+def run(agent, handlers=None):
ab36d8
+	"""
ab36d8
+	Main loop implementation for resource agents.
ab36d8
+	Does not return.
ab36d8
+
ab36d8
+	Arguments:
ab36d8
+
ab36d8
+	agent: Agent object.
ab36d8
+
ab36d8
+	handlers: Dict of action name to handler function.
ab36d8
+
ab36d8
+	Handler functions can take parameters as arguments,
ab36d8
+	the run loop will read parameter values from the
ab36d8
+	environment and pass to the handler.
ab36d8
+	"""
ab36d8
+	import inspect
ab36d8
+
ab36d8
+	agent._handlers.update(handlers or {})
ab36d8
+	handlers = agent._handlers
ab36d8
+
ab36d8
+	def check_required_params():
ab36d8
+		for p in agent.parameters:
ab36d8
+			if p.required and get_parameter(p.name) is None:
ab36d8
+				ocf_exit_reason("{}: Required parameter not set".format(p.name))
ab36d8
+				sys.exit(OCF_ERR_CONFIGURED)
ab36d8
+
ab36d8
+	def call_handler(func):
ab36d8
+		if hasattr(inspect, 'signature'):
ab36d8
+			params = inspect.signature(func).parameters.keys()
ab36d8
+		else:
ab36d8
+			params = inspect.getargspec(func).args
ab36d8
+		def value_for_parameter(param):
ab36d8
+			val = get_parameter(param)
ab36d8
+			if val is not None:
ab36d8
+				return val
ab36d8
+			for p in agent.parameters:
ab36d8
+				if p.name == param:
ab36d8
+					return p.default
ab36d8
+		arglist = [value_for_parameter(p) for p in params]
ab36d8
+		try:
ab36d8
+			rc = func(*arglist)
ab36d8
+			if rc is None:
ab36d8
+				rc = OCF_SUCCESS
ab36d8
+			return rc
ab36d8
+		except Exception as err:
ab36d8
+			if not _exit_reason_set:
ab36d8
+				ocf_exit_reason(str(err))
ab36d8
+			else:
ab36d8
+				logger.error(str(err))
ab36d8
+			return OCF_ERR_GENERIC
ab36d8
+
ab36d8
+	meta_data_action = False
ab36d8
+	for action in agent.actions:
ab36d8
+		if action.name == "meta-data":
ab36d8
+			meta_data_action = True
ab36d8
+			break
ab36d8
+	if not meta_data_action:
ab36d8
+		agent.add_action("meta-data", timeout=10)
ab36d8
+
ab36d8
+	if len(sys.argv) == 2 and sys.argv[1] in ("-h", "--help"):
ab36d8
+		sys.stdout.write("usage: %s {%s}\n\n" % (sys.argv[0], "|".join(sorted(handlers.keys()))) +
ab36d8
+		                 "Expects to have a fully populated OCF RA compliant environment set.\n")
ab36d8
+		sys.exit(OCF_SUCCESS)
ab36d8
+
ab36d8
+	if OCF_ACTION is None:
ab36d8
+		ocf_exit_reason("No action argument set")
ab36d8
+		sys.exit(OCF_ERR_UNIMPLEMENTED)
ab36d8
+	if OCF_ACTION in ('meta-data', 'usage', 'methods'):
ab36d8
+		sys.stdout.write(agent.to_xml() + "\n")
ab36d8
+		sys.exit(OCF_SUCCESS)
ab36d8
+
ab36d8
+	check_required_params()
ab36d8
+	if OCF_ACTION in handlers:
ab36d8
+		rc = call_handler(handlers[OCF_ACTION])
ab36d8
+		sys.exit(rc)
ab36d8
+	sys.exit(OCF_ERR_UNIMPLEMENTED)
ab36d8
+
ab36d8
+
ab36d8
+if __name__ == "__main__":
ab36d8
+	import unittest
ab36d8
+
ab36d8
+	class TestMetadata(unittest.TestCase):
ab36d8
+		def test_noparams_noactions(self):
ab36d8
+			m = Agent("foo", shortdesc="shortdesc", longdesc="longdesc")
ab36d8
+			self.assertEqual("""
ab36d8
+
ab36d8
+<resource-agent name="foo">
ab36d8
+<version>1.0</version>
ab36d8
+<longdesc lang="en">
ab36d8
+longdesc
ab36d8
+</longdesc>
ab36d8
+<shortdesc lang="en">shortdesc</shortdesc>
ab36d8
+
ab36d8
+<parameters>
ab36d8
+
ab36d8
+</parameters>
ab36d8
+
ab36d8
+<actions>
ab36d8
+
ab36d8
+</actions>
ab36d8
+
ab36d8
+</resource-agent>
ab36d8
+""", str(m))
ab36d8
+
ab36d8
+		def test_params_actions(self):
ab36d8
+			m = Agent("foo", shortdesc="shortdesc", longdesc="longdesc")
ab36d8
+			m.add_parameter("testparam")
ab36d8
+			m.add_action("start")
ab36d8
+			self.assertEqual(str(m.actions[0]), '<action name="start" />\n')
ab36d8
+
ab36d8
+	unittest.main()