Documentation on goal and efficacy
In this changeset, I wrote a documentation detailing how one can implement a new goal plugin. I also mention to define the efficacy specification for a given goal. Partially Implements: blueprint efficacy-indicator Change-Id: Iba267ae312f248b49d4600504f11678cdc225622
This commit is contained in:
@@ -4,6 +4,8 @@
|
||||
|
||||
https://creativecommons.org/licenses/by/3.0/
|
||||
|
||||
.. _implement_action_plugin:
|
||||
|
||||
==================
|
||||
Build a new action
|
||||
==================
|
||||
|
||||
215
doc/source/dev/plugin/goal-plugin.rst
Normal file
215
doc/source/dev/plugin/goal-plugin.rst
Normal file
@@ -0,0 +1,215 @@
|
||||
..
|
||||
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_goal_plugin:
|
||||
|
||||
================
|
||||
Build a new goal
|
||||
================
|
||||
|
||||
Watcher Decision Engine has an external :ref:`goal <goal_definition>`
|
||||
plugin interface which gives anyone the ability to integrate an external
|
||||
goal which can be achieved by a :ref:`strategy <strategy_definition>`.
|
||||
|
||||
This section gives some guidelines on how to implement and integrate custom
|
||||
goals with Watcher. If you wish to create a third-party package for your
|
||||
plugin, you can refer to our :ref:`documentation for third-party package
|
||||
creation <plugin-base_setup>`.
|
||||
|
||||
|
||||
Pre-requisites
|
||||
==============
|
||||
|
||||
Before using any goal, please make sure that none of the existing goals fit
|
||||
your needs. Indeed, the underlying value of defining a goal is to be able to
|
||||
compare the efficacy of the action plans resulting from the various strategies
|
||||
satisfying the same goal. By doing so, Watcher can assist the administrator
|
||||
in his choices.
|
||||
|
||||
|
||||
Create a new plugin
|
||||
===================
|
||||
|
||||
In order to create a new goal, you have to:
|
||||
|
||||
- Extend the :py:class:`~.base.Goal` class.
|
||||
- Implement its :py:meth:`~.Goal.get_name` class method to return the
|
||||
**unique** ID of the new goal you want to create. This unique ID should
|
||||
be the same as the name of :ref:`the entry point you will declare later on
|
||||
<goal_plugin_add_entrypoint>`.
|
||||
- Implement its :py:meth:`~.Goal.get_display_name` class method to
|
||||
return the translated display name of the goal you want to create.
|
||||
Note: Do not use a variable to return the translated string so it can be
|
||||
automatically collected by the translation tool.
|
||||
- Implement its :py:meth:`~.Goal.get_translatable_display_name`
|
||||
class method to return the translation key (actually the english display
|
||||
name) of your new goal. The value return should be the same as the
|
||||
string translated in :py:meth:`~.Goal.get_display_name`.
|
||||
- Implement its :py:meth:`~.Goal.get_efficacy_specification` method to return
|
||||
the :ref:`efficacy specification <efficacy_specification_definition>` for
|
||||
your goal.
|
||||
|
||||
Here is an example showing how you can define a new ``NewGoal`` goal plugin:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# filepath: thirdparty/new.py
|
||||
# import path: thirdparty.new
|
||||
|
||||
from watcher._i18n import _
|
||||
from watcher.decision_engine.goal.efficacy import specs
|
||||
from watcher.decision_engine.strategy.strategies import base
|
||||
|
||||
class NewGoal(base.Goal):
|
||||
|
||||
@classmethod
|
||||
def get_name(cls):
|
||||
return "new_goal" # Will be the name of the entry point
|
||||
|
||||
@classmethod
|
||||
def get_display_name(cls):
|
||||
return _("New Goal")
|
||||
|
||||
@classmethod
|
||||
def get_translatable_display_name(cls):
|
||||
return "New Goal"
|
||||
|
||||
@classmethod
|
||||
def get_efficacy_specification(cls):
|
||||
return specs.UnclassifiedStrategySpecification()
|
||||
|
||||
|
||||
As you may have noticed, the :py:meth:`~.Goal.get_efficacy_specification`
|
||||
method returns an :py:meth:`~.UnclassifiedStrategySpecification` instance which
|
||||
is provided by Watcher. This efficacy specification is useful during the
|
||||
development process of your goal as it corresponds to an empty specification.
|
||||
If you want to learn more about what efficacy specifications are used for or to
|
||||
define your own efficacy specification, please refer to the :ref:`related
|
||||
section below <implement_efficacy_specification>`.
|
||||
|
||||
|
||||
Abstract Plugin Class
|
||||
=====================
|
||||
|
||||
Here below is the abstract :py:class:`~.base.Goal` class:
|
||||
|
||||
.. autoclass:: watcher.decision_engine.goal.base.Goal
|
||||
:members:
|
||||
:noindex:
|
||||
|
||||
.. _goal_plugin_add_entrypoint:
|
||||
|
||||
Add a new entry point
|
||||
=====================
|
||||
|
||||
In order for the Watcher Decision Engine to load your new goal, the
|
||||
goal must be registered as a named entry point under the ``watcher_goals``
|
||||
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 and should be the same
|
||||
as the value returned by the :py:meth:`~.base.Goal.get_name` class method of
|
||||
your goal.
|
||||
|
||||
Here below is how you would proceed to register ``NewGoal`` using pbr_:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[entry_points]
|
||||
watcher_goals =
|
||||
new_goal = thirdparty.new:NewGoal
|
||||
|
||||
|
||||
To get a better understanding on how to implement a more advanced goal,
|
||||
have a look at the :py:class:`~.ServerConsolidation` class.
|
||||
|
||||
.. _pbr: http://docs.openstack.org/developer/pbr/
|
||||
|
||||
.. _implement_efficacy_specification:
|
||||
|
||||
Implement a customized efficacy specification
|
||||
=============================================
|
||||
|
||||
What is it for?
|
||||
---------------
|
||||
|
||||
Efficacy specifications define a set of specifications for a given goal.
|
||||
These specifications actually define a list of indicators which are to be used
|
||||
to compute a global efficacy that outlines how well a strategy performed when
|
||||
trying to achieve the goal it is associated to.
|
||||
|
||||
The idea behind such specification is to give the administrator the possibility
|
||||
to run an audit using different strategies satisfying the same goal and be able
|
||||
to judge how they performed at a glance.
|
||||
|
||||
|
||||
Implementation
|
||||
--------------
|
||||
|
||||
In order to create a new efficacy specification, you have to:
|
||||
|
||||
- Extend the :py:class:`~.EfficacySpecification` class.
|
||||
- Implement :py:meth:`~.EfficacySpecification.get_indicators_specifications`
|
||||
by returning a list of :py:class:`~.IndicatorSpecification` instances.
|
||||
|
||||
* Each :py:class:`~.IndicatorSpecification` instance should actually extend
|
||||
the latter.
|
||||
* Each indicator specification should have a **unique name** which should be
|
||||
a valid Python variable name.
|
||||
* They should implement the :py:attr:`~.EfficacySpecification.schema`
|
||||
abstract property by returning a :py:class:`~.voluptuous.Schema` instance.
|
||||
This schema is the contract the strategy will have to comply with when
|
||||
setting the value associated to the indicator specification within its
|
||||
solution (see the :ref:`architecture of Watcher
|
||||
<sequence_diagrams_create_and_launch_audit>` for more information on
|
||||
the audit execution workflow).
|
||||
|
||||
- Implement the :py:meth:`~.EfficacySpecification.get_global_efficacy` method:
|
||||
it should compute the global efficacy for the goal it achieves based on the
|
||||
efficacy indicators you just defined.
|
||||
|
||||
Here below is an example of an efficacy specification containing one indicator
|
||||
specification:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from watcher._i18n import _
|
||||
from watcher.decision_engine.goal.efficacy import base as efficacy_base
|
||||
from watcher.decision_engine.goal.efficacy import indicators
|
||||
from watcher.decision_engine.solution import efficacy
|
||||
|
||||
|
||||
class IndicatorExample(IndicatorSpecification):
|
||||
def __init__(self):
|
||||
super(IndicatorExample, self).__init__(
|
||||
name="indicator_example",
|
||||
description=_("Example of indicator specification."),
|
||||
unit=None,
|
||||
)
|
||||
|
||||
@property
|
||||
def schema(self):
|
||||
return voluptuous.Schema(voluptuous.Range(min=0), required=True)
|
||||
|
||||
|
||||
class UnclassifiedStrategySpecification(efficacy_base.EfficacySpecification):
|
||||
|
||||
def get_indicators_specifications(self):
|
||||
return [IndicatorExample()]
|
||||
|
||||
def get_global_efficacy(self, indicators_map):
|
||||
return efficacy.Indicator(
|
||||
name="global_efficacy_indicator",
|
||||
description="Example of global efficacy indicator",
|
||||
unit="%",
|
||||
value=indicators_map.indicator_example % 100)
|
||||
|
||||
|
||||
To get a better understanding on how to implement an efficacy specification,
|
||||
have a look at :py:class:`~.ServerConsolidationSpecification`.
|
||||
|
||||
Also, if you want to see a concrete example of an indicator specification,
|
||||
have a look at :py:class:`~.ReleasedComputeNodesCount`.
|
||||
@@ -11,9 +11,10 @@ Build a new planner
|
||||
===================
|
||||
|
||||
Watcher :ref:`Decision Engine <watcher_decision_engine_definition>` has an
|
||||
external :ref:`planner <planner_definition>` plugin interface which gives
|
||||
anyone the ability to integrate an external :ref:`planner <planner_definition>`
|
||||
in order to extend the initial set of planners Watcher provides.
|
||||
external :ref:`planner <watcher_planner_definition>` plugin interface which
|
||||
gives anyone the ability to integrate an external :ref:`planner
|
||||
<watcher_planner_definition>` 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.
|
||||
|
||||
@@ -28,8 +28,8 @@ configured so that it would provide you all the metrics you need to be able to
|
||||
use your strategy.
|
||||
|
||||
|
||||
Create a new plugin
|
||||
===================
|
||||
Create a new strategy plugin
|
||||
============================
|
||||
|
||||
In order to create a new strategy, you have to:
|
||||
|
||||
@@ -53,6 +53,8 @@ Here is an example showing how you can write a plugin called ``NewStrategy``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# filepath: thirdparty/new.py
|
||||
# import path: thirdparty.new
|
||||
import abc
|
||||
|
||||
import six
|
||||
@@ -88,9 +90,12 @@ Here is an example showing how you can write a plugin called ``NewStrategy``:
|
||||
As you can see in the above example, the :py:meth:`~.BaseStrategy.execute`
|
||||
method returns a :py:class:`~.BaseSolution` instance as required. This solution
|
||||
is what wraps the abstract set of actions the strategy recommends to you. This
|
||||
solution is then processed by a :ref:`planner <planner_definition>` to produce
|
||||
an action plan which contains the sequenced flow of actions to be
|
||||
executed by the :ref:`Watcher Applier <watcher_applier_definition>`.
|
||||
solution is then processed by a :ref:`planner <watcher_planner_definition>` to
|
||||
produce an action plan which contains the sequenced flow of actions to be
|
||||
executed by the :ref:`Watcher Applier <watcher_applier_definition>`. This
|
||||
solution also contains the various :ref:`efficacy indicators
|
||||
<efficacy_indicator_definition>` alongside its computed :ref:`global efficacy
|
||||
<efficacy_definition>`.
|
||||
|
||||
Please note that your strategy class will expect to find the same constructor
|
||||
signature as BaseStrategy to instantiate you strategy. Therefore, you should
|
||||
@@ -98,7 +103,7 @@ ensure that your ``__init__`` signature is identical to the
|
||||
:py:class:`~.BaseStrategy` one.
|
||||
|
||||
|
||||
Create a new goal
|
||||
Strategy efficacy
|
||||
=================
|
||||
|
||||
As stated before, the ``NewStrategy`` class extends a class called
|
||||
@@ -106,126 +111,12 @@ As stated before, the ``NewStrategy`` class extends a class called
|
||||
abstract methods which are defined within the :py:class:`~.BaseStrategy` parent
|
||||
class.
|
||||
|
||||
Once you are confident in your strategy plugin, the next step is now to
|
||||
classify your goal by assigning it a proper goal. To do so, you can either
|
||||
reuse existing goals defined in Watcher. As of now, four goal-oriented abstract
|
||||
classes are defined in Watcher:
|
||||
|
||||
- :py:class:`~.UnclassifiedStrategy` which is the one I mentioned up until now.
|
||||
- :py:class:`~.DummyBaseStrategy` which is used by :py:class:`~.DummyStrategy`
|
||||
for testing purposes.
|
||||
- :py:class:`~.ServerConsolidationBaseStrategy`
|
||||
- :py:class:`~.ThermalOptimizationBaseStrategy`
|
||||
|
||||
If none of the above actually correspond to the goal your new strategy
|
||||
achieves, you can define a brand new one. To do so, you need to:
|
||||
|
||||
- Extend the :py:class:`~.BaseStrategy` class to make your new goal-oriented
|
||||
strategy abstract class :
|
||||
- Implement its :py:meth:`~.BaseStrategy.get_goal_name` class method to
|
||||
return the **unique** ID of the goal you want to achieve.
|
||||
- Implement its :py:meth:`~.BaseStrategy.get_goal_display_name` class method
|
||||
to return the translated display name of the goal you want to achieve.
|
||||
Note: Do not use a variable to return the translated string so it can be
|
||||
automatically collected by the translation tool.
|
||||
- Implement its :py:meth:`~.BaseStrategy.get_translatable_goal_display_name`
|
||||
class method to return the goal translation key (actually the english
|
||||
display name). The value return should be the same as the string translated
|
||||
in :py:meth:`~.BaseStrategy.get_goal_display_name`.
|
||||
|
||||
Here is an example showing how you can define a new ``NEW_GOAL`` goal and
|
||||
modify your ``NewStrategy`` plugin so it now achieves the latter:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import abc
|
||||
|
||||
import six
|
||||
|
||||
from watcher._i18n import _
|
||||
from watcher.decision_engine.strategy.strategies import base
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class NewGoalBaseStrategy(base.BaseStrategy):
|
||||
|
||||
@classmethod
|
||||
def get_goal_name(cls):
|
||||
return "NEW_GOAL"
|
||||
|
||||
@classmethod
|
||||
def get_goal_display_name(cls):
|
||||
return _("New goal")
|
||||
|
||||
@classmethod
|
||||
def get_translatable_goal_display_name(cls):
|
||||
return "New goal"
|
||||
|
||||
|
||||
class NewStrategy(NewGoalBaseStrategy):
|
||||
|
||||
def __init__(self, config, osc=None):
|
||||
super(NewStrategy, self).__init__(config, osc)
|
||||
|
||||
def execute(self, original_model):
|
||||
self.solution.add_action(action_type="nop",
|
||||
input_parameters=parameters)
|
||||
# Do some more stuff here ...
|
||||
return self.solution
|
||||
|
||||
@classmethod
|
||||
def get_name(cls):
|
||||
return "new_strategy"
|
||||
|
||||
@classmethod
|
||||
def get_display_name(cls):
|
||||
return _("New strategy")
|
||||
|
||||
@classmethod
|
||||
def get_translatable_display_name(cls):
|
||||
return "New strategy"
|
||||
|
||||
|
||||
Define configuration parameters
|
||||
===============================
|
||||
|
||||
At this point, you have a fully functional strategy. However, in more complex
|
||||
implementation, you may want to define some configuration options so one can
|
||||
tune the strategy to its needs. To do so, you can implement the
|
||||
:py:meth:`~.Loadable.get_config_opts` class method as followed:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
class NewStrategy(NewGoalBaseStrategy):
|
||||
|
||||
# [...]
|
||||
|
||||
def execute(self, original_model):
|
||||
assert self.config.test_opt == 0
|
||||
# [...]
|
||||
|
||||
def get_config_opts(self):
|
||||
return [
|
||||
cfg.StrOpt('test_opt', help="Demo Option.", default=0),
|
||||
# Some more options ...
|
||||
]
|
||||
|
||||
|
||||
The configuration options defined within this class method will be included
|
||||
within the global ``watcher.conf`` configuration file under a section named by
|
||||
convention: ``{namespace}.{plugin_name}``. In our case, the ``watcher.conf``
|
||||
configuration would have to be modified as followed:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[watcher_strategies.new_strategy]
|
||||
# Option used for testing.
|
||||
test_opt = test_value
|
||||
|
||||
Then, the configuration options you define within this method will then be
|
||||
injected in each instantiated object via the ``config`` parameter of the
|
||||
:py:meth:`~.BaseStrategy.__init__` method.
|
||||
One thing this :py:class:`~.UnclassifiedStrategy` class defines is that our
|
||||
``NewStrategy`` achieves the ``unclassified`` goal. This goal is a peculiar one
|
||||
as it does not contain any indicator nor does it calculate a global efficacy.
|
||||
This proves itself to be quite useful during the development of a new strategy
|
||||
for which the goal has yet to be defined or in case a :ref:`new goal
|
||||
<implement_goal_plugin>` has yet to be implemented.
|
||||
|
||||
|
||||
Abstract Plugin Class
|
||||
@@ -249,16 +140,16 @@ strategy must be registered as a named entry point under the
|
||||
pbr_, this entry point should be placed in your ``setup.cfg`` file.
|
||||
|
||||
The name you give to your entry point has to be unique and should be the same
|
||||
as the value returned by the :py:meth:`~.BaseStrategy.get_id` class method of
|
||||
as the value returned by the :py:meth:`~.BaseStrategy.get_name` class method of
|
||||
your strategy.
|
||||
|
||||
Here below is how you would proceed to register ``DummyStrategy`` using pbr_:
|
||||
Here below is how you would proceed to register ``NewStrategy`` using pbr_:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[entry_points]
|
||||
watcher_strategies =
|
||||
dummy_strategy = thirdparty.dummy:DummyStrategy
|
||||
new_strategy = thirdparty.new:NewStrategy
|
||||
|
||||
|
||||
To get a better understanding on how to implement a more advanced strategy,
|
||||
|
||||
Reference in New Issue
Block a user