From 96c0ac0ca8e1ec2dc3fd55badda6a388e414b1a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vincent=20Fran=C3=A7oise?= Date: Tue, 16 Feb 2016 17:36:07 +0100 Subject: [PATCH] Doc on how to implement a custom Watcher planner This documentation describes step-by-step the process for implementing a new planner in Watcher. Change-Id: I8addba53de69be93730924a58107687020c19c74 Closes-Bug: #1533739 --- doc/source/dev/planner-plugin.rst | 127 ++++++++++++++++++++++++ doc/source/index.rst | 1 + watcher/decision_engine/planner/base.py | 10 +- 3 files changed, 134 insertions(+), 4 deletions(-) create mode 100644 doc/source/dev/planner-plugin.rst diff --git a/doc/source/dev/planner-plugin.rst b/doc/source/dev/planner-plugin.rst new file mode 100644 index 000000000..dee1cddb8 --- /dev/null +++ b/doc/source/dev/planner-plugin.rst @@ -0,0 +1,127 @@ +.. + 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/ + +.. _implement_planner_plugin: + +=================== +Build a new planner +=================== + +Watcher :ref:`Decision Engine ` has an +external :ref:`planner ` plugin interface which gives +anyone the ability to integrate an external :ref:`planner ` +in order to extend the initial set of planners Watcher provides. + +This section gives some guidelines on how to implement and integrate custom +planners with Watcher. + +.. _Decision Engine: watcher_decision_engine_definition + +Creating a new plugin +===================== + +First of all you have to extend the base :py:class:`~.BasePlanner` class which +defines an abstract method that you will have to implement. The +:py:meth:`~.BasePlanner.schedule` is the method being called by the Decision +Engine to schedule a given solution (:py:class:`~.BaseSolution`) into an +:ref:`action plan ` by ordering/sequencing an unordered +set of actions contained in the proposed solution (for more details, see +:ref:`definition of a solution `). + +Here is an example showing how you can write a planner plugin called +``DummyPlanner``: + +.. code-block:: python + + # Filepath = third-party/third_party/dummy.py + # Import path = third_party.dummy + import uuid + from watcher.decision_engine.planner import base + + + class DummyPlanner(base.BasePlanner): + + def _create_action_plan(self, context, audit_id): + action_plan_dict = { + 'uuid': uuid.uuid4(), + 'audit_id': audit_id, + 'first_action_id': None, + 'state': objects.action_plan.State.RECOMMENDED + } + + new_action_plan = objects.ActionPlan(context, **action_plan_dict) + new_action_plan.create(context) + new_action_plan.save() + return new_action_plan + + def schedule(self, context, audit_id, solution): + # Empty action plan + action_plan = self._create_action_plan(context, audit_id) + # todo: You need to create the workflow of actions here + # and attach it to the action plan + return action_plan + +This implementation is the most basic one. So if you want to have more advanced +examples, have a look at the implementation of planners already provided by +Watcher like :py:class:`~.DefaultPlanner`. A list with all available planner +plugins can be found :ref:`here `. + +Abstract Plugin Class +===================== + +Here below is the abstract ``BasePlanner`` class that every single planner +should implement: + +.. autoclass:: watcher.decision_engine.planner.base.BasePlanner + :members: + :noindex: + + +Register a new entry point +========================== + +In order for the Watcher Decision Engine to load your new planner, the +latter must be registered as a new entry point under the +``watcher_planners`` entry point namespace 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 ``DummyPlanner`` using pbr_: + +.. code-block:: ini + + [entry_points] + watcher_planners = + dummy = third_party.dummy:DummyPlanner + +.. _pbr: http://docs.openstack.org/developer/pbr/ + + +Using planner plugins +===================== + +The :ref:`Watcher Decision Engine ` service +will automatically discover any installed plugins when it is started. This +means that if Watcher is already running when you install your plugin, you will +have to restart the related Watcher services. 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, Watcher will use your new planner if you referenced it in the +``planner`` option under the ``[watcher_planner]`` section of your +``watcher.conf`` configuration file when you started it. For example, if you +want to use the ``dummy`` planner you just installed, you would have to +select it as followed: + +.. code-block:: ini + + [watcher_planner] + planner = dummy + +As you may have noticed, only a single planner implementation can be activated +at a time, so make sure it is generic enough to support all your strategies +and actions. diff --git a/doc/source/index.rst b/doc/source/index.rst index b9457a384..ac0abf7c1 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -71,6 +71,7 @@ Plugins :maxdepth: 1 dev/strategy-plugin + dev/planner-plugin dev/plugins diff --git a/watcher/decision_engine/planner/base.py b/watcher/decision_engine/planner/base.py index e628f62c2..c6358b192 100644 --- a/watcher/decision_engine/planner/base.py +++ b/watcher/decision_engine/planner/base.py @@ -50,11 +50,13 @@ class BasePlanner(object): def schedule(self, context, audit_uuid, solution): """The planner receives a solution to schedule - :param solution: the solution given by the strategy to + :param solution: A solution provided by a strategy for scheduling + :type solution: :py:class:`~.BaseSolution` subclass instance :param audit_uuid: the audit uuid - :return: ActionPlan ordered sequence of change requests - such that all security, dependency, and performance - requirements are met. + :type audit_uuid: str + :return: Action plan with an ordered sequence of actions such that all + security, dependency, and performance requirements are met. + :rtype: :py:class:`watcher.objects.action_plan.ActionPlan` instance """ # example: directed acyclic graph raise NotImplementedError()