File 0021-1233.patch of Package resource-agents.11694
From 35c6faa1f43fd0751677a94573d5f5a2e76d73be Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kristoffer=20Gr=C3=B6nlund?= <krig@koru.se>
Date: Thu, 4 Oct 2018 17:01:14 +0200
Subject: [PATCH 1/6] dev: ocf.py: Add missing imports to example
---
doc/writing-python-agents.md | 3 +++
1 file changed, 3 insertions(+)
diff --git a/doc/writing-python-agents.md b/doc/writing-python-agents.md
index aeb4acbd6..584b1c997 100644
--- a/doc/writing-python-agents.md
+++ b/doc/writing-python-agents.md
@@ -60,6 +60,9 @@ logger.error("Something went terribly wrong.")
## Run loop and metadata example
``` python
+import os
+import sys
+
OCF_FUNCTIONS_DIR="%s/lib/heartbeat" % os.environ.get("OCF_ROOT")
sys.path.append(OCF_FUNCTIONS_DIR)
import ocf
From 4b5d63a4faf54f91431795fb6c705dba679b0cc7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kristoffer=20Gr=C3=B6nlund?= <krig@koru.se>
Date: Thu, 4 Oct 2018 17:01:43 +0200
Subject: [PATCH 2/6] dev: ocf.py: Catch and log exceptions in a controlled way
---
heartbeat/ocf.py | 19 +++++++++++++++----
1 file changed, 15 insertions(+), 4 deletions(-)
diff --git a/heartbeat/ocf.py b/heartbeat/ocf.py
index 451a41dec..b16b5d4dd 100644
--- a/heartbeat/ocf.py
+++ b/heartbeat/ocf.py
@@ -141,6 +141,8 @@ def emit(self, record):
logger = logging.LoggerAdapter(log, {'OCF_RESOURCE_INSTANCE': OCF_RESOURCE_INSTANCE})
+_exit_reason_set = False
+
def ocf_exit_reason(msg):
"""
Print exit error string to stderr.
@@ -148,10 +150,12 @@ def ocf_exit_reason(msg):
Allows the OCF agent to provide a string describing
why the exit code was returned.
"""
+ global _exit_reason_set
cookie = env.get("OCF_EXIT_REASON_PREFIX", "ocf-exit-reason:")
sys.stderr.write("{}{}\n".format(cookie, msg))
sys.stderr.flush()
logger.error(msg)
+ _exit_reason_set = True
def have_binary(name):
@@ -343,10 +347,17 @@ def default_for_parameter(paramname):
return meta.default
return None
arglist = [get_parameter(p, default_for_parameter(p)) for p in params]
- rc = func(*arglist)
- if rc is None:
- rc = OCF_SUCCESS
- return rc
+ try:
+ rc = func(*arglist)
+ if rc is None:
+ rc = OCF_SUCCESS
+ return rc
+ except Exception as err:
+ if not _exit_reason_set:
+ ocf_exit_reason(str(err))
+ else:
+ logger.error(str(err))
+ return OCF_ERR_GENERIC
if OCF_ACTION is None:
ocf_exit_reason("No action argument set")
From 655ffe758f3b19ec2667eb6720c77d77e07f415f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kristoffer=20Gr=C3=B6nlund?= <krig@koru.se>
Date: Thu, 4 Oct 2018 17:33:06 +0200
Subject: [PATCH 3/6] dev: ocf.py: Print usage help when passed -h/--help as
argument
---
heartbeat/ocf.py | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/heartbeat/ocf.py b/heartbeat/ocf.py
index b16b5d4dd..ff4c67c6e 100644
--- a/heartbeat/ocf.py
+++ b/heartbeat/ocf.py
@@ -359,6 +359,11 @@ def default_for_parameter(paramname):
logger.error(str(err))
return OCF_ERR_GENERIC
+ if len(sys.argv) == 2 and sys.argv[1] in ("-h", "--help"):
+ sys.stdout.write("usage: %s {%s}\n\n" % (sys.argv[0], "|".join(sorted(handlers.keys()))) +
+ "Expects to have a fully populated OCF RA compliant environment set.\n")
+ sys.exit(OCF_SUCCESS)
+
if OCF_ACTION is None:
ocf_exit_reason("No action argument set")
sys.exit(OCF_ERR_UNIMPLEMENTED)
From c95b3cdab92dc1ea2924c1ece302f6871f4f63e9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kristoffer=20Gr=C3=B6nlund?= <krig@koru.se>
Date: Thu, 4 Oct 2018 17:43:21 +0200
Subject: [PATCH 4/6] dev: ocf.py: Enable passing handler as argument to
add_action, and add Metadata.run() helper
---
heartbeat/ocf.py | 13 +++++++++++--
1 file changed, 11 insertions(+), 2 deletions(-)
diff --git a/heartbeat/ocf.py b/heartbeat/ocf.py
index ff4c67c6e..a95fa1c81 100644
--- a/heartbeat/ocf.py
+++ b/heartbeat/ocf.py
@@ -266,6 +266,7 @@ def __init__(self, name, shortdesc, longdesc):
self.longdesc = longdesc
self.parameters = []
self.actions = []
+ self._handlers = {}
def add_parameter(self, name, shortdesc="", longdesc="", content_type="string", unique=False, required=False, default=None):
for param in self.parameters:
@@ -280,11 +281,13 @@ def add_parameter(self, name, shortdesc="", longdesc="", content_type="string",
default=default))
return self
- def add_action(self, name, timeout=None, interval=None, depth=None):
+ def add_action(self, name, timeout=None, interval=None, depth=None, handler=None):
self.actions.append(Action(name=name,
timeout=timeout,
interval=interval,
depth=depth))
+ if handler is not None:
+ self._handlers[name] = handler
return self
def __str__(self):
@@ -312,8 +315,11 @@ def __str__(self):
parameters="".join(str(p) for p in self.parameters),
actions="".join(str(a) for a in self.actions))
+ def run(self):
+ run(self)
-def run(metadata, handlers):
+
+def run(metadata, handlers=None):
"""
Main loop implementation for resource agents.
Does not return.
@@ -330,6 +336,9 @@ def run(metadata, handlers):
"""
import inspect
+ metadata._handlers.update(handlers or {})
+ handlers = metadata._handlers
+
def check_required_params():
for p in metadata.parameters:
if p.required and get_parameter(p.name) is None:
From f9f067f670cea824544c61d5471865cc5cf54ecd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kristoffer=20Gr=C3=B6nlund?= <krig@koru.se>
Date: Thu, 4 Oct 2018 17:59:03 +0200
Subject: [PATCH 5/6] dev: ocf.py: Rename Metadata to Agent
---
doc/writing-python-agents.md | 29 +++++++++++-------------
heartbeat/ocf.py | 43 +++++++++++++++++++++++++-----------
2 files changed, 43 insertions(+), 29 deletions(-)
diff --git a/doc/writing-python-agents.md b/doc/writing-python-agents.md
index 584b1c997..1b87e753a 100644
--- a/doc/writing-python-agents.md
+++ b/doc/writing-python-agents.md
@@ -54,7 +54,7 @@ logger.error("Something went terribly wrong.")
* `have_binary`: Returns True if the given binary is available.
* `is_true`: Converts an OCF truth value to a Python boolean.
* `get_parameter`: Looks up the matching `OCF_RESKEY_` environment variable.
-* `Metadata`: Class which helps to generate the XML metadata.
+* `Agent`: Class which helps to generate the XML metadata.
* `run`: OCF run loop implementation.
## Run loop and metadata example
@@ -72,21 +72,18 @@ def start_action(argument):
def main():
- metadata = ocf.Metadata("example-agent",
- shortdesc="This is an example agent",
- longdesc="An example of how to " +
- "write an agent in Python using the ocf " +
- "Python library.")
- metadata.add_parameter("argument",
- shortdesc="Example argument",
- longdesc="This argument is just an example.",
- content_type="string",
- default="foobar")
- metadata.add_action("start", timeout=60)
- ocf.run(metadata,
- handlers={
- "start": start_action
- })
+ agent = ocf.Agent("example-agent",
+ shortdesc="This is an example agent",
+ longdesc="An example of how to " +
+ "write an agent in Python using the ocf " +
+ "Python library.")
+ agent.add_parameter("argument",
+ shortdesc="Example argument",
+ longdesc="This argument is just an example.",
+ content_type="string",
+ default="foobar")
+ agent.add_action("start", timeout=60, handler=start_action)
+ agent.run()
if __name__ == "__main__":
main()
diff --git a/heartbeat/ocf.py b/heartbeat/ocf.py
index a95fa1c81..e34066617 100644
--- a/heartbeat/ocf.py
+++ b/heartbeat/ocf.py
@@ -217,6 +217,9 @@ def __init__(self, name, shortdesc, longdesc, content_type, unique, required, de
self.default = default
def __str__(self):
+ return self.to_xml()
+
+ def to_xml(self):
ret = '<parameter name="' + self.name + '"'
if self.unique:
ret += ' unique="1"'
@@ -233,6 +236,7 @@ def __str__(self):
return ret
+
class Action(object):
def __init__(self, name, timeout, interval, depth):
self.name = name
@@ -241,6 +245,9 @@ def __init__(self, name, timeout, interval, depth):
self.depth = depth
def __str__(self):
+ return self.to_xml()
+
+ def to_xml(self):
def opt(s, name, var):
if var is not None:
if type(var) == int:
@@ -255,9 +262,16 @@ def opt(s, name, var):
return ret
-class Metadata(object):
+class Agent(object):
"""
- Metadata XML generator helper.
+ OCF Resource Agent metadata XML generator helper.
+
+ Use add_parameter/add_action to define parameters
+ and actions for the agent. Then call run() to
+ start the agent main loop.
+
+ See doc/writing-python-agents.md for an example
+ of how to use it.
"""
def __init__(self, name, shortdesc, longdesc):
@@ -291,6 +305,9 @@ def add_action(self, name, timeout=None, interval=None, depth=None, handler=None
return self
def __str__(self):
+ return self.to_xml()
+
+ def to_xml(self):
return """<?xml version="1.0"?>
<!DOCTYPE resource-agent SYSTEM "ra-api-1.dtd">
<resource-agent name="{name}">
@@ -312,21 +329,21 @@ def __str__(self):
""".format(name=self.name,
longdesc=self.longdesc,
shortdesc=self.shortdesc,
- parameters="".join(str(p) for p in self.parameters),
- actions="".join(str(a) for a in self.actions))
+ parameters="".join(p.to_xml() for p in self.parameters),
+ actions="".join(a.to_xml() for a in self.actions))
def run(self):
run(self)
-def run(metadata, handlers=None):
+def run(agent, handlers=None):
"""
Main loop implementation for resource agents.
Does not return.
Arguments:
- metadata: Metadata structure generated by ocf.Metadata
+ agent: Agent object.
handlers: Dict of action name to handler function.
@@ -336,11 +353,11 @@ def run(metadata, handlers=None):
"""
import inspect
- metadata._handlers.update(handlers or {})
- handlers = metadata._handlers
+ agent._handlers.update(handlers or {})
+ handlers = agent._handlers
def check_required_params():
- for p in metadata.parameters:
+ for p in agent.parameters:
if p.required and get_parameter(p.name) is None:
ocf_exit_reason("{}: Required parameter not set".format(p.name))
sys.exit(OCF_ERR_CONFIGURED)
@@ -351,7 +368,7 @@ def call_handler(func):
else:
params = inspect.getargspec(func).args
def default_for_parameter(paramname):
- for meta in metadata.parameters:
+ for meta in agent.parameters:
if meta.name == paramname:
return meta.default
return None
@@ -377,7 +394,7 @@ def default_for_parameter(paramname):
ocf_exit_reason("No action argument set")
sys.exit(OCF_ERR_UNIMPLEMENTED)
if OCF_ACTION in ('meta-data', 'usage', 'methods'):
- sys.stdout.write(str(metadata) + "\n")
+ sys.stdout.write(agent.to_xml() + "\n")
sys.exit(OCF_SUCCESS)
check_required_params()
@@ -392,7 +409,7 @@ def default_for_parameter(paramname):
class TestMetadata(unittest.TestCase):
def test_noparams_noactions(self):
- m = Metadata("foo", shortdesc="shortdesc", longdesc="longdesc")
+ m = Agent("foo", shortdesc="shortdesc", longdesc="longdesc")
self.assertEqual("""<?xml version="1.0"?>
<!DOCTYPE resource-agent SYSTEM "ra-api-1.dtd">
<resource-agent name="foo">
@@ -414,7 +431,7 @@ def test_noparams_noactions(self):
""", str(m))
def test_params_actions(self):
- m = Metadata("foo", shortdesc="shortdesc", longdesc="longdesc")
+ m = Agent("foo", shortdesc="shortdesc", longdesc="longdesc")
m.add_parameter("testparam")
m.add_action("start")
self.assertEqual(str(m.actions[0]), '<action name="start" />\n')
From ee1e4551eeb85af89815ef136c42e944bb50e0dc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kristoffer=20Gr=C3=B6nlund?= <krig@koru.se>
Date: Mon, 8 Oct 2018 14:53:24 +0200
Subject: [PATCH 6/6] dev: ocf.py: Only lookup default if parameter is not set
---
heartbeat/ocf.py | 14 ++++++++------
1 file changed, 8 insertions(+), 6 deletions(-)
diff --git a/heartbeat/ocf.py b/heartbeat/ocf.py
index e34066617..72ec90820 100644
--- a/heartbeat/ocf.py
+++ b/heartbeat/ocf.py
@@ -367,12 +367,14 @@ def call_handler(func):
params = inspect.signature(func).parameters.keys()
else:
params = inspect.getargspec(func).args
- def default_for_parameter(paramname):
- for meta in agent.parameters:
- if meta.name == paramname:
- return meta.default
- return None
- arglist = [get_parameter(p, default_for_parameter(p)) for p in params]
+ def value_for_parameter(param):
+ val = get_parameter(val)
+ if val is not None:
+ return val
+ for p in agent.parameters:
+ if p.name == param:
+ return p.default
+ arglist = [value_for_parameter(p) for p in params]
try:
rc = func(*arglist)
if rc is None: