Build a new planner¶
Watcher Decision Engine has an external planner plugin interface which gives anyone the ability to integrate an external 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.
Creating a new plugin¶
First of all you have to extend the base BasePlanner
class which
defines an abstract method that you will have to implement. The
schedule()
is the method being called by the Decision
Engine to schedule a given solution (BaseSolution
) into an
action plan by ordering/sequencing an unordered
set of actions contained in the proposed solution (for more details, see
definition of a solution).
Here is an example showing how you can write a planner plugin called
DummyPlanner
:
# Filepath = third-party/third_party/dummy.py
# Import path = third_party.dummy
from oslo_utils import uuidutils
from watcher.decision_engine.planner import base
class DummyPlanner(base.BasePlanner):
def _create_action_plan(self, context, audit_id):
action_plan_dict = {
'uuid': uuidutils.generate_uuid(),
'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 DefaultPlanner
. A list with all available planner
plugins can be found here.
Define configuration parameters¶
At this point, you have a fully functional planner. However, in more complex
implementation, you may want to define some configuration options so one can
tune the planner to its needs. To do so, you can implement the
get_config_opts()
class method as followed:
from oslo_config import cfg
class DummyPlanner(base.BasePlanner):
# [...]
def schedule(self, context, audit_uuid, solution):
assert self.config.test_opt == 0
# [...]
@classmethod
def get_config_opts(cls):
return super(
DummyPlanner, cls).get_config_opts() + [
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:
[watcher_planners.dummy]
# 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
__init__()
method.
Abstract Plugin Class¶
Here below is the abstract BasePlanner
class that every single planner
should implement:
- class watcher.decision_engine.planner.base.BasePlanner(config)[source]
- classmethod get_config_opts()[source]
Defines the configuration options to be associated to this loadable
- Returns:
A list of configuration options relative to this Loadable
- Return type:
list of
oslo_config.cfg.Opt
instances
- abstract schedule(context, audit_uuid, solution)[source]
The planner receives a solution to schedule
- Parameters:
solution (
BaseSolution
subclass instance) – A solution provided by a strategy for schedulingaudit_uuid (str) – the audit uuid
- Returns:
Action plan with an ordered sequence of actions such that all security, dependency, and performance requirements are met.
- Return type:
watcher.objects.ActionPlan
instance
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:
[entry_points]
watcher_planners =
dummy = third_party.dummy:DummyPlanner
Using planner plugins¶
The 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:
[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.