From 58ea85c852b8691b21ec722bee8b99c87493d432 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vincent=20Fran=C3=A7oise?= Date: Mon, 15 Feb 2016 11:44:15 +0100 Subject: [PATCH] Doc on how to implement a custom Watcher action This documentation describes step-by-step the process for implementing a new action in Watcher. Change-Id: I978b81cdf9ac6dcf43eb3ecbb79ab64ae4fd6f72 Closes-Bug: #1534639 --- doc/source/dev/plugin/action-plugin.rst | 159 ++++++++++++++++++++++++ doc/source/dev/strategy-plugin.rst | 2 + doc/source/index.rst | 1 + watcher/applier/actions/base.py | 35 ++++++ 4 files changed, 197 insertions(+) create mode 100644 doc/source/dev/plugin/action-plugin.rst diff --git a/doc/source/dev/plugin/action-plugin.rst b/doc/source/dev/plugin/action-plugin.rst new file mode 100644 index 000000000..c1816994c --- /dev/null +++ b/doc/source/dev/plugin/action-plugin.rst @@ -0,0 +1,159 @@ +.. + Except where otherwise noted, this document is licensed under Creative + Commons Attribution 3.0 License. You can view the license at: + + https://creativecommons.org/licenses/by/3.0/ + +================== +Build a new action +================== + +Watcher Applier has an external :ref:`action ` plugin +interface which gives anyone the ability to integrate an external +:ref:`action ` in order to extend the initial set of actions +Watcher provides. + +This section gives some guidelines on how to implement and integrate custom +actions with Watcher. + + +Creating a new plugin +===================== + +First of all you have to extend the base :py:class:`BaseAction` class which +defines a set of abstract methods and/or properties that you will have to +implement: + + - The :py:attr:`~.BaseAction.schema` is an abstract property that you have to + implement. This is the first function to be called by the + :ref:`applier ` before any further processing + and its role is to validate the input parameters that were provided to it. + - The :py:meth:`~.BaseAction.precondition` is called before the execution of + an action. This method is a hook that can be used to perform some + initializations or to make some more advanced validation on its input + parameters. If you wish to block the execution based on this factor, you + simply have to ``raise`` an exception. + - The :py:meth:`~.BaseAction.postcondition` is called after the execution of + an action. As this function is called regardless of whether an action + succeeded or not, this can prove itself useful to perform cleanup + operations. + - The :py:meth:`~.BaseAction.execute` is the main component of an action. + This is where you should implement the logic of your action. + - The :py:meth:`~.BaseAction.revert` allows you to roll back the targeted + resource to its original state following a faulty execution. Indeed, this + method is called by the workflow engine whenever an action raises an + exception. + +Here is an example showing how you can write a plugin called ``DummyAction``: + +.. code-block:: python + + # Filepath = /thirdparty/dummy.py + # Import path = thirdparty.dummy + from watcher.applier.actions import base + + + class DummyAction(baseBaseAction): + + @property + def schema(self): + return Schema({}) + + def execute(self): + # Does nothing + pass # Only returning False is considered as a failure + + def revert(self): + # Does nothing + pass + + def precondition(self): + # No pre-checks are done here + pass + + def postcondition(self): + # Nothing done here + pass + + +This implementation is the most basic one. So if you want to have more advanced +examples, have a look at the implementation of the actions already provided +by Watcher like. +To get a better understanding on how to implement a more advanced action, +have a look at the :py:class:`~watcher.applier.actions.migration.Migrate` +class. + +Abstract Plugin Class +===================== + +Here below is the abstract ``BaseAction`` class that every single action +should implement: + +.. autoclass:: watcher.applier.actions.base.BaseAction + :members: + :noindex: + + .. py:attribute:: schema + + Defines a Schema that the input parameters shall comply to + + :returns: A schema declaring the input parameters this action should be + provided along with their respective constraints + (e.g. type, value range, ...) + :rtype: :py:class:`voluptuous.Schema` instance + + +Register a new entry point +========================== + +In order for the Watcher Applier to load your new action, the +action must be registered as a named entry point under the +``watcher_actions`` entry point of your ``setup.py`` file. If you are using +pbr_, this entry point should be placed in your ``setup.cfg`` file. + +The name you give to your entry point has to be unique. + +Here below is how you would proceed to register ``DummyAction`` using pbr_: + +.. code-block:: ini + + [entry_points] + watcher_actions = + dummy = thirdparty.dummy:DummyAction + +.. _pbr: http://docs.openstack.org/developer/pbr/ + + +Using action plugins +==================== + +The Watcher Applier service will automatically discover any installed plugins +when it is restarted. If a Python package containing a custom plugin is +installed within the same environment as Watcher, Watcher will automatically +make that plugin available for use. + +At this point, you can use your new action plugin in your :ref:`strategy plugin +` if you reference it via the use of the +:py:meth:`~.Solution.add_action` method: + +.. code-block:: python + + # [...] + self.solution.add_action( + action_type="dummy", # Name of the entry point we registered earlier + applies_to="", + input_parameters={}) + +By doing so, your action will be saved within the Watcher Database, ready to be +processed by the planner for creating an action plan which can then be executed +by the Watcher Applier via its workflow engine. + + +Scheduling of an action plugin +============================== + +Watcher provides a basic built-in :ref:`planner ` +which is only able to process the Watcher built-in actions. Therefore, you will +either have to use an existing third-party planner or :ref:`implement another +planner ` that will be able to take into account your +new action plugin. diff --git a/doc/source/dev/strategy-plugin.rst b/doc/source/dev/strategy-plugin.rst index 61df43ec3..4f0406690 100644 --- a/doc/source/dev/strategy-plugin.rst +++ b/doc/source/dev/strategy-plugin.rst @@ -4,6 +4,8 @@ https://creativecommons.org/licenses/by/3.0/ +.. _implement_strategy_plugin: + ================================= Build a new optimization strategy ================================= diff --git a/doc/source/index.rst b/doc/source/index.rst index b9457a384..a63abd797 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -72,6 +72,7 @@ Plugins dev/strategy-plugin dev/plugins + dev/plugin/action-plugin Admin Guide diff --git a/watcher/applier/actions/base.py b/watcher/applier/actions/base.py index 533ddeae5..ef31a4334 100644 --- a/watcher/applier/actions/base.py +++ b/watcher/applier/actions/base.py @@ -58,22 +58,57 @@ class BaseAction(object): @abc.abstractmethod def execute(self): + """Executes the main logic of the action + + This method can be used to perform an action on a given set of input + parameters to accomplish some type of operation. This operation may + return a boolean value as a result of its execution. If False, this + will be considered as an error and will then trigger the reverting of + the actions. + + :returns: A flag indicating whether or not the action succeeded + :rtype: bool + """ raise NotImplementedError() @abc.abstractmethod def revert(self): + """Revert this action + + This method should rollback the resource to its initial state in the + event of a faulty execution. This happens when the action raised an + exception during its :py:meth:`~.BaseAction.execute`. + """ raise NotImplementedError() @abc.abstractmethod def precondition(self): + """Hook: called before the execution of an action + + This method can be used to perform some initializations or to make + some more advanced validation on its input parameters. So if you wish + to block its execution based on this factor, `raise` the related + exception. + """ raise NotImplementedError() @abc.abstractmethod def postcondition(self): + """Hook: called after the execution of an action + + This function is called regardless of whether an action succeded or + not. So you can use it to perform cleanup operations. + """ raise NotImplementedError() @abc.abstractproperty def schema(self): + """Defines a Schema that the input parameters shall comply to + + :returns: A schema declaring the input parameters this action should be + provided along with their respective constraints + :rtype: :py:class:`voluptuous.Schema` instance + """ raise NotImplementedError() def validate_parameters(self):