Build a new goal

Watcher Decision Engine has an external goal plugin interface which gives anyone the ability to integrate an external goal which can be achieved by a strategy.

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 documentation for third-party package creation.

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 Goal class.

  • Implement its 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 the entry point you will declare later on.

  • Implement its 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 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 get_display_name().

  • Implement its get_efficacy_specification() method to return the efficacy specification for your goal.

Here is an example showing how you can define a new NewGoal goal plugin:

# filepath: thirdparty/new.py
# import path: thirdparty.new

from watcher._i18n import _
from watcher.decision_engine.goal import base
from watcher.decision_engine.goal.efficacy import specs

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.Unclassified()

As you may have noticed, the get_efficacy_specification() method returns an Unclassified() 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 related section below.

Abstract Plugin Class

Here below is the abstract Goal class:

class watcher.decision_engine.goal.base.Goal(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 classmethod get_display_name()[source]

The goal display name for the goal

abstract get_efficacy_specification()[source]

The efficacy spec for the current goal

abstract classmethod get_name()[source]

Name of the goal: should be identical to the related entry point

abstract classmethod get_translatable_display_name()[source]

The translatable msgid of the goal

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 get_name() class method of your goal.

Here below is how you would proceed to register NewGoal using pbr:

[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 watcher.decision_engine.goal.goals.ServerConsolidation class.

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 EfficacySpecification class.

  • Implement get_indicators_specifications() by returning a list of IndicatorSpecification instances.

    • Each 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 schema abstract property by returning a 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 architecture of Watcher for more information on the audit execution workflow).

  • Implement the 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:

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 ServerConsolidationSpecification.

Also, if you want to see a concrete example of an indicator specification, have a look at ReleasedComputeNodesCount.