Add support for plugin actions

implements blueprint mistral-pluggable-task-actions

Change-Id: If12a0c6835edcabd33027555501dea4f473fc1f5
This commit is contained in:
Angus Salkeld 2014-05-28 22:00:11 +10:00
parent 1ceb4b1e8a
commit ebf225553d
7 changed files with 96 additions and 33 deletions

View File

@ -0,0 +1,49 @@
How to write an Action Plugin
=============================
1. Write a class based on mistral.actions.base.Actions
::
from mistral.actions import base
class RunnerAction(base.Action):
def __init__(self, param):
# store the incomming params
self.param = param
def run(self):
# return your results here
return {'status': 0}
2. Publish the class in a namespace
(in your setup.cfg)
::
[entry_points]
myproject.plugins.example =
runner = solum.mistral_plugins.somefile:RunnerAction
3. Add the namespace into /etc/mistral/mistral.conf
(don't overwrite "mistral.plugins.std")
::
action_plugins = mistral.plugins.std,myproject.plugins.example
4. Use your plugin
Note on naming the plugin.
* The namespace is "myproject.plugins.example"
* The class is named "runner"
* Now you can call the action "example.runner"
::
Workflow:
tasks:
myaction:
action: example.runner
parameters:
param: avalue_to_pass_in

View File

@ -30,6 +30,10 @@ rabbit_password = guest
# Example: server=api,engine
#server=all
# List of python module namespaces to search for plug-ins.
# See: doc/source/writing_a_plugin_action.rst on how to write a plugin.
action_plugins = mistral.actions.std
[api]
# Host and port to bind the API server to
host = 0.0.0.0

View File

@ -15,6 +15,7 @@
# limitations under the License.
import inspect
from oslo.config import cfg
from mistral.actions import base
from mistral.actions import std_actions
@ -26,36 +27,29 @@ from mistral.openstack.common import log as logging
LOG = logging.getLogger(__name__)
_ACTION_CTX_PARAM = 'action_context'
_STD_NAMESPACE = "std"
_NAMESPACES = {}
def _find_or_create_namespace(name):
def _find_or_create_namespace(full_name):
name = full_name.split('.')[-1]
ns = _NAMESPACES.get(name)
if not ns:
ns = base.Namespace(name)
ns = base.Namespace(full_name)
_NAMESPACES[name] = ns
return ns
def register_action_class(namespace_name, action_name, action_cls):
_find_or_create_namespace(namespace_name).add(action_name, action_cls)
def get_registered_namespaces():
return _NAMESPACES.copy()
def _register_standard_action_classes():
register_action_class(_STD_NAMESPACE, "echo", std_actions.EchoAction)
register_action_class(_STD_NAMESPACE, "http", std_actions.HTTPAction)
register_action_class(_STD_NAMESPACE,
"mistral_http", std_actions.MistralHTTPAction)
register_action_class(_STD_NAMESPACE, "ssh", std_actions.SSHAction)
register_action_class(_STD_NAMESPACE, "email", std_actions.SendEmailAction)
def _register_action_classes():
cfg.CONF.import_opt('action_plugins', 'mistral.config')
for py_ns in cfg.CONF.action_plugins:
ns = _find_or_create_namespace(py_ns)
ns.log()
def get_action_class(action_full_name):
@ -160,5 +154,5 @@ def create_action(db_task):
(db_task, e))
# Registering standard actions on module load.
_register_standard_action_classes()
# Registering actions on module load.
_register_action_classes()

View File

@ -15,9 +15,9 @@
# limitations under the License.
import abc
from stevedore import extension
from mistral.openstack.common import log as logging
from mistral import exceptions as exc
LOG = logging.getLogger(__name__)
@ -86,24 +86,26 @@ class Action(object):
class Namespace(object):
"""Action namespace."""
def __init__(self, name):
self.name = name
self.action_classes = {} # action name -> action class
def add(self, action_name, action_cls):
if action_name in self.action_classes:
raise exc.ActionRegistrationException(
"Action is already associated with namespace "
"[action_cls=%s, namespace=%s" %
(action_cls, self))
self.action_classes[action_name] = action_cls
def __init__(self, namespace):
self.name = namespace.split('.')[-1]
self.mgr = extension.ExtensionManager(
namespace=namespace,
invoke_on_load=False)
def contains_action_name(self, name):
return name in self.action_classes
return name in self.mgr.names()
def get_action_class(self, name):
return self.action_classes.get(name)
# ExtensionManager has no "get"
if self.contains_action_name(name):
return self.mgr[name].plugin
else:
return None
def __len__(self):
return len(self.action_classes)
# ExtensionManager has no len()
return len(self.mgr.names())
def log(self):
for ext in self.mgr:
LOG.debug('%s:%s' % (self.name, ext.name))

View File

@ -94,6 +94,11 @@ launch_opt = cfg.ListOpt(
'api, engine, and executor.'
)
action_plugins_opt = cfg.ListOpt(
'action_plugins',
default=['mistral.actions.std'],
help='List of namespaces to search for plug-ins.')
wf_trace_log_name_opt = cfg.StrOpt('workflow_trace_log_name',
default='workflow_trace',
@ -110,6 +115,7 @@ CONF.register_opts(db_opts, group='database')
CONF.register_opts(rabbit_opts, group='rabbit')
CONF.register_opts(executor_opts, group='executor')
CONF.register_opt(wf_trace_log_name_opt)
CONF.register_opt(action_plugins_opt)
CONF.register_cli_opt(use_debugger)
CONF.register_cli_opt(launch_opt)

View File

@ -15,4 +15,5 @@ python-keystoneclient>=0.7.0
networkx>=1.8
six>=1.6.0
SQLAlchemy>=0.7.8,<=0.9.99
stevedore>=0.14
yaql==0.2.1 # This is not in global requirements

View File

@ -30,3 +30,10 @@ mistral.engine.drivers =
mistral.executor.drivers =
default = mistral.engine.drivers.default.executor:DefaultExecutor
mistral.actions.std =
echo = mistral.actions.std_actions:EchoAction
http = mistral.actions.std_actions:HTTPAction
mistral_http = mistral.actions.std_actions:MistralHTTPAction
ssh = mistral.actions.std_actions:SSHAction
email = mistral.actions.std_actions:SendEmailAction