diff --git a/doc/source/architecture.rst b/doc/source/architecture.rst index 184fdfc7d..26e7c7a86 100644 --- a/doc/source/architecture.rst +++ b/doc/source/architecture.rst @@ -150,12 +150,14 @@ This database stores all the Watcher domain objects which can be requested by the :ref:`Watcher API ` or the :ref:`Watcher CLI `: +- :ref:`Goals ` +- :ref:`Strategies ` - :ref:`Audit templates ` - :ref:`Audits ` - :ref:`Action plans ` +- :ref:`Efficacy indicators ` via the Action + Plan API. - :ref:`Actions ` -- :ref:`Goals ` -- :ref:`Strategies ` The Watcher domain being here "*optimization of some resources provided by an OpenStack system*". @@ -210,10 +212,10 @@ view (Goals, Audits, Action Plans, ...): .. image:: ./images/functional_data_model.svg :width: 100% -Here below is a class diagram representing the main objects in Watcher from a +Here below is a diagram representing the main objects in Watcher from a database perspective: -.. image:: ./images/watcher_class_diagram.png +.. image:: ./images/watcher_db_schema_diagram.png :width: 100% @@ -297,9 +299,11 @@ This method finds an appropriate scheduling of :ref:`Actions ` taking into account some scheduling rules (such as priorities between actions). It generates a new :ref:`Action Plan ` with status -**RECOMMENDED** and saves it into the -:ref:`Watcher Database `. The saved action plan is -now a scheduled flow of actions. +**RECOMMENDED** and saves it into the:ref:`Watcher Database +`. The saved action plan is now a scheduled flow +of actions to which a global efficacy is associated alongside a number of +:ref:`Efficacy Indicators ` as specified by the +related :ref:`goal `. If every step executed successfully, the :ref:`Watcher Decision Engine ` updates @@ -308,6 +312,11 @@ the current status of the Audit to **SUCCEEDED** in the on the bus to inform other components that the :ref:`Audit ` was successful. +This internal workflow the Decision Engine follows to conduct an audit can be +seen in the sequence diagram here below: + +.. image:: ./images/sequence_from_audit_execution_to_actionplan_creation.png + :width: 100% .. _sequence_diagrams_launch_action_plan: diff --git a/doc/source/dev/plugin/action-plugin.rst b/doc/source/dev/plugin/action-plugin.rst index dfec436f9..7a29ef030 100644 --- a/doc/source/dev/plugin/action-plugin.rst +++ b/doc/source/dev/plugin/action-plugin.rst @@ -4,6 +4,8 @@ https://creativecommons.org/licenses/by/3.0/ +.. _implement_action_plugin: + ================== Build a new action ================== diff --git a/doc/source/dev/plugin/goal-plugin.rst b/doc/source/dev/plugin/goal-plugin.rst new file mode 100644 index 000000000..2f6928a88 --- /dev/null +++ b/doc/source/dev/plugin/goal-plugin.rst @@ -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 ` +plugin interface which gives anyone the ability to integrate an external +goal which can be achieved by a :ref:`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 :ref:`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 :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 + `. +- 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 ` 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 `. + + +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 + ` 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`. diff --git a/doc/source/dev/plugin/planner-plugin.rst b/doc/source/dev/plugin/planner-plugin.rst index 69e31ca4e..d85f25fc0 100644 --- a/doc/source/dev/plugin/planner-plugin.rst +++ b/doc/source/dev/plugin/planner-plugin.rst @@ -11,9 +11,10 @@ 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. +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. diff --git a/doc/source/dev/plugin/strategy-plugin.rst b/doc/source/dev/plugin/strategy-plugin.rst index 47ee1c107..94046c990 100644 --- a/doc/source/dev/plugin/strategy-plugin.rst +++ b/doc/source/dev/plugin/strategy-plugin.rst @@ -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 ` to produce -an action plan which contains the sequenced flow of actions to be -executed by the :ref:`Watcher Applier `. +solution is then processed by a :ref:`planner ` to +produce an action plan which contains the sequenced flow of actions to be +executed by the :ref:`Watcher Applier `. This +solution also contains the various :ref:`efficacy indicators +` alongside its computed :ref:`global efficacy +`. 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 +` 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, diff --git a/doc/source/dev/plugins.rst b/doc/source/dev/plugins.rst index 7a320b602..4be2c53d9 100644 --- a/doc/source/dev/plugins.rst +++ b/doc/source/dev/plugins.rst @@ -13,6 +13,13 @@ In this section we present all the plugins that are shipped along with Watcher. If you want to know which plugins your Watcher services have access to, you can use the :ref:`Guru Meditation Reports ` to display them. +.. _watcher_goals: + +Goals +===== + +.. drivers-doc:: watcher_goals + .. _watcher_strategies: Strategies diff --git a/doc/source/glossary.rst b/doc/source/glossary.rst index d3ea28738..acfa151f0 100644 --- a/doc/source/glossary.rst +++ b/doc/source/glossary.rst @@ -131,7 +131,8 @@ can potentially be hosted on a dedicated machine. Compute node ============ -Please, read `the official OpenStack definition of a Compute Node `_. +Please, read `the official OpenStack definition of a Compute Node +`_. .. _customer_definition: @@ -211,7 +212,23 @@ Here are some examples of - `Sahara Hadoop Cluster `_ - ... -It can be any of the `the official list of available resource types defined in OpenStack for HEAT `_. +It can be any of the `the official list of available resource types defined in +OpenStack for HEAT +`_. + +.. _efficacy_indicator_definition: + +Efficacy Indicator +================== + +.. watcher-term:: watcher.api.controllers.v1.efficacy_indicator + +.. _efficacy_specification_definition: + +Efficacy Specification +====================== + +.. watcher-term:: watcher.api.controllers.v1.efficacy_specification .. _efficacy_definition: @@ -234,14 +251,15 @@ to be launched). For example, if the :ref:`Goal ` is to lower the energy consumption, the :ref:`Efficacy ` will be computed -using several indicators (KPIs): +using several :ref:`efficacy indicators ` +(KPIs): - the percentage of energy gain (which must be the highest possible) - the number of :ref:`SLA violations ` (which must be the lowest possible) - the number of virtual machine migrations (which must be the lowest possible) -All those indicators (KPIs) are computed within a given timeframe, which is the +All those indicators are computed within a given timeframe, which is the time taken to execute the whole :ref:`Action Plan `. The efficacy also enables the :ref:`Administrator ` @@ -259,7 +277,8 @@ OpenStack should be owned by a specific :ref:`project `. In OpenStack Identity, a :ref:`project ` must be owned by a specific domain. -Please, read `the official OpenStack definition of a Project `_. +Please, read `the official OpenStack definition of a Project +`_. .. _sla_definition: @@ -364,4 +383,3 @@ Watcher Planner =============== .. watcher-term:: watcher.decision_engine.planner.base - diff --git a/doc/source/image_src/dia/functional_data_model.dia b/doc/source/image_src/dia/functional_data_model.dia index bfbdfa5c2..ae7654dba 100644 Binary files a/doc/source/image_src/dia/functional_data_model.dia and b/doc/source/image_src/dia/functional_data_model.dia differ diff --git a/doc/source/image_src/plantuml/sequence_from_audit_execution_to_actionplan_creation.txt b/doc/source/image_src/plantuml/sequence_from_audit_execution_to_actionplan_creation.txt new file mode 100644 index 000000000..d36274cb6 --- /dev/null +++ b/doc/source/image_src/plantuml/sequence_from_audit_execution_to_actionplan_creation.txt @@ -0,0 +1,44 @@ +@startuml + +skinparam maxMessageSize 200 + +"Decision Engine" -> "Decision Engine" : Execute audit +activate "Decision Engine" +"Decision Engine" -> "Decision Engine" : Set the audit state to ONGOING + +"Decision Engine" -> "Strategy selector" : Select strategy +activate "Strategy selector" +alt A specific strategy is provided +"Strategy selector" -> "Strategy selector" : Load strategy and inject the \ +cluster data model +else Only a goal is specified +"Strategy selector" -> "Strategy selector" : select strategy +"Strategy selector" -> "Strategy selector" : Load strategy and inject the \ +cluster data model +end +"Strategy selector" -> "Decision Engine" : Return loaded Strategy +deactivate "Strategy selector" + +"Decision Engine" -> "Strategy" : Execute the strategy +activate "Strategy" +"Strategy" -> "Strategy" : **pre_execute()**Checks if the strategy \ +pre-requisites are all set. +"Strategy" -> "Strategy" : **do_execute()**Contains the logic of the strategy +"Strategy" -> "Strategy" : **post_execute()** Set the efficacy indicators +"Strategy" -> "Strategy" : Compute the global efficacy of the solution \ +based on the provided efficacy indicators +"Strategy" -> "Decision Engine" : Return the solution +deactivate "Strategy" + +"Decision Engine" -> "Planner" : Plan the solution that was computed by the \ +strategy +activate "Planner" +"Planner" -> "Planner" : Store the planned solution as an action plan with its \ +related actions and efficacy indicators +"Planner" --> "Decision Engine" : Done +deactivate "Planner" +"Decision Engine" -> "Decision Engine" : Update the audit state to SUCCEEDED + +deactivate "Decision Engine" + +@enduml diff --git a/doc/source/image_src/plantuml/sequence_trigger_audit_in_decision_engine.txt b/doc/source/image_src/plantuml/sequence_trigger_audit_in_decision_engine.txt index 91cfa1781..69796d869 100644 --- a/doc/source/image_src/plantuml/sequence_trigger_audit_in_decision_engine.txt +++ b/doc/source/image_src/plantuml/sequence_trigger_audit_in_decision_engine.txt @@ -1,31 +1,54 @@ @startuml -"AMQP Bus" -> "Watcher Decision Engine" : trigger_audit(new_audit.uuid) -"Watcher Decision Engine" -> "Watcher Database" : update audit.state = ONGOING -"AMQP Bus" <[#blue]- "Watcher Decision Engine" : notify new audit state = ONGOING -"Watcher Decision Engine" -> "Watcher Database" : get audit parameters(goal, ...) -"Watcher Decision Engine" <-- "Watcher Database" : audit parameters(goal, ...) +skinparam maxMessageSize 100 + +"AMQP Bus" -> "Decision Engine" : trigger audit + +activate "Decision Engine" + +"Decision Engine" -> "Database" : update audit.state = ONGOING +"AMQP Bus" <[#blue]- "Decision Engine" : notify new audit state = ONGOING +"Decision Engine" -> "Database" : get audit parameters (goal, strategy, ...) +"Decision Engine" <-- "Database" : audit parameters (goal, strategy, ...) +"Decision Engine" --> "Decision Engine": select appropriate \ +optimization strategy (via the Strategy Selector) create Strategy -"Watcher Decision Engine" -[#red]> "Strategy": select appropriate\noptimization strategy -loop while enough data to build cluster data model - "Watcher Decision Engine" -> "Nova API" : get resource state (host, instance, ...) - "Watcher Decision Engine" <-- "Nova API" : resource state -end -"Watcher Decision Engine" -[#red]> "Watcher Decision Engine": build cluster_data_model -"Watcher Decision Engine" -> "Strategy" : execute(cluster_data_model) +"Decision Engine" -> "Strategy" : execute() +activate "Strategy" +create "Cluster Data Model Collector" +"Strategy" -> "Cluster Data Model Collector" : get cluster data model + +activate "Cluster Data Model Collector" + loop while enough data to build cluster data model + "Cluster Data Model Collector" -> "Nova API" : get resource state (\ +host, instance, ...) + "Cluster Data Model Collector" <-- "Nova API" : resource state + end +"Cluster Data Model Collector" -> "Strategy" : cluster data model +deactivate "Cluster Data Model Collector" + loop while enough history data for the strategy -"Strategy" -> "Ceilometer API": get_aggregated_metrics\n(resource_id,meter_name,period,aggregate_method) -"Strategy" <-- "Ceilometer API": aggregated metrics + "Strategy" -> "Ceilometer API": get necessary metrics + "Strategy" <-- "Ceilometer API": aggregated metrics end -"Strategy" -> "Strategy" : compute solution to achieve goal -"Watcher Decision Engine" <-- "Strategy" : solution = array of actions (i.e. not scheduled yet) -create "Watcher Planner" -"Watcher Decision Engine" -[#red]> "Watcher Planner": select appropriate actions scheduler (i.e. Planner implementation) -"Watcher Decision Engine" -> "Watcher Planner": schedule(audit_id, solution) -"Watcher Planner" -> "Watcher Planner": schedule actions according to\nscheduling rules/policies -"Watcher Decision Engine" <-- "Watcher Planner": new action_plan -"Watcher Decision Engine" -> "Watcher Database" : save new action_plan in database -"Watcher Decision Engine" -> "Watcher Database" : update audit.state = SUCCEEDED -"AMQP Bus" <[#blue]- "Watcher Decision Engine" : notify new audit state = SUCCEEDED +"Strategy" -> "Strategy" : compute/set needed actions for the solution \ +so it achieves its goal +"Strategy" -> "Strategy" : compute/set efficacy indicators for the solution +"Strategy" -> "Strategy" : compute/set the solution global efficacy +"Decision Engine" <-- "Strategy" : solution (contains a list of unordered \ +actions alongside its efficacy indicators as well as its global efficacy) +deactivate "Strategy" + +"Decision Engine" --> "Planner": load actions scheduler (i.e. Planner plugin) +create "Planner" +"Decision Engine" -> "Planner": schedule() +"Planner" -> "Planner": schedule actions according to \ +scheduling rules/policies +"Decision Engine" <-- "Planner": new action plan +"Decision Engine" -> "Database" : save new action plan in database +"Decision Engine" -> "Database" : update audit.state = SUCCEEDED +"AMQP Bus" <[#blue]- "Decision Engine" : notify new audit state = SUCCEEDED + +deactivate "Decision Engine" @enduml diff --git a/doc/source/image_src/plantuml/watcher_class_diagram.txt b/doc/source/image_src/plantuml/watcher_class_diagram.txt deleted file mode 100644 index 3a1e801a7..000000000 --- a/doc/source/image_src/plantuml/watcher_class_diagram.txt +++ /dev/null @@ -1,87 +0,0 @@ -@startuml - -abstract class Base { - // Timestamp mixin - DateTime created_at - DateTime updated_at - - // Soft Delete mixin - DateTime deleted_at - Integer deleted // default = 0 -} - -class Strategy { - **Integer id** // primary_key - String uuid // length = 36 - String name // length = 63, nullable = false - String display_name // length = 63, nullable = false - Integer goal_id // ForeignKey('goals.id'), nullable = false -} - - -class Goal { - **Integer id** // primary_key - String uuid // length = 36 - String name // length = 63, nullable = false - String display_name // length = 63, nullable=False -} - - -class AuditTemplate { - **Integer id** // primary_key - String uuid // length = 36 - String name // length = 63, nullable = true - String description // length = 255, nullable = true - Integer host_aggregate // nullable = true - Integer goal_id // ForeignKey('goals.id'), nullable = false - Integer strategy_id // ForeignKey('strategies.id'), nullable = true - JsonString extra - String version // length = 15, nullable = true -} - - -class Audit { - **Integer id** // primary_key - String uuid // length = 36 - String type // length = 20 - String state // length = 20, nullable = true - DateTime deadline // nullable = true - Integer audit_template_id // ForeignKey('audit_templates.id') \ -nullable = false -} - - -class Action { - **Integer id** // primary_key - String uuid // length = 36, nullable = false - Integer action_plan_id // ForeignKey('action_plans.id'), nullable = false - String action_type // length = 255, nullable = false - JsonString input_parameters // nullable = true - String state // length = 20, nullable = true - String next // length = 36, nullable = true -} - - -class ActionPlan { - **Integer id** // primary_key - String uuid // length = 36 - Integer first_action_id // - Integer audit_id // ForeignKey('audits.id'), nullable = true - String state // length = 20, nullable = true -} - -"Base" <|-- "Strategy" -"Base" <|-- "Goal" -"Base" <|-- "AuditTemplate" -"Base" <|-- "Audit" -"Base" <|-- "Action" -"Base" <|-- "ActionPlan" - - "Goal" <.. "Strategy" : Foreign Key - "Goal" <.. "AuditTemplate" : Foreign Key - "Strategy" <.. "AuditTemplate" : Foreign Key - "AuditTemplate" <.. "Audit" : Foreign Key - "ActionPlan" <.. "Action" : Foreign Key - "Audit" <.. "ActionPlan" : Foreign Key - -@enduml diff --git a/doc/source/image_src/plantuml/watcher_db_schema_diagram.txt b/doc/source/image_src/plantuml/watcher_db_schema_diagram.txt new file mode 100644 index 000000000..ab02d1c97 --- /dev/null +++ b/doc/source/image_src/plantuml/watcher_db_schema_diagram.txt @@ -0,0 +1,122 @@ +@startuml +!define table(x) class x << (T,#FFAAAA) >> +!define primary_key(x) x +!define foreign_key(x) x +hide methods +hide stereotypes + +table(goal) { + primary_key(id: Integer) + uuid : String[36] + name : String[63] + display_name : String[63] + + created_at : DateTime + updated_at : DateTime + deleted_at : DateTime + deleted : Integer +} + + +table(strategy) { + primary_key(id: Integer) + foreign_key(goal_id : Integer) + uuid : String[36] + name : String[63] + display_name : String[63] + + created_at : DateTime + updated_at : DateTime + deleted_at : DateTime + deleted : Integer +} + + +table(audit_template) { + primary_key(id: Integer) + foreign_key("goal_id : Integer") + foreign_key("strategy_id : Integer, nullable") + uuid : String[36] + name : String[63], nullable + description : String[255], nullable + host_aggregate : Integer, nullable + extra : JSONEncodedDict + version : String[15], nullable + + created_at : DateTime + updated_at : DateTime + deleted_at : DateTime + deleted : Integer +} + + +table(audit) { + primary_key(id: Integer) + foreign_key("audit_template_id : Integer") + uuid : String[36] + type : String[20] + state : String[20], nullable + deadline :DateTime, nullable + + created_at : DateTime + updated_at : DateTime + deleted_at : DateTime + deleted : Integer +} + + +table(action_plan) { + primary_key(id: Integer) + foreign_key("audit_id : Integer, nullable") + uuid : String[36] + first_action_id : Integer + state : String[20], nullable + global_efficacy : JSONEncodedDict, nullable + + created_at : DateTime + updated_at : DateTime + deleted_at : DateTime + deleted : Integer +} + + +table(action) { + primary_key(id: Integer) + foreign_key("action_plan_id : Integer") + uuid : String[36] + action_type : String[255] + input_parameters : JSONEncodedDict, nullable + state : String[20], nullable + next : String[36], nullable + + created_at : DateTime + updated_at : DateTime + deleted_at : DateTime + deleted : Integer +} + + +table(efficacy_indicator) { + primary_key(id: Integer) + foreign_key("action_plan_id : Integer") + uuid : String[36] + name : String[63] + description : String[255], nullable + unit : String[63], nullable + value : Numeric + + created_at : DateTime + updated_at : DateTime + deleted_at : DateTime + deleted : Integer +} + + "goal" <.. "strategy" : Foreign Key + "goal" <.. "audit_template" : Foreign Key + "strategy" <.. "audit_template" : Foreign Key + "audit_template" <.. "audit" : Foreign Key + "action_plan" <.. "action" : Foreign Key + "action_plan" <.. "efficacy_indicator" : Foreign Key + "audit" <.. "action_plan" : Foreign Key + +@enduml diff --git a/doc/source/images/functional_data_model.svg b/doc/source/images/functional_data_model.svg index 8cc5fb5b1..f6e71dd59 100644 --- a/doc/source/images/functional_data_model.svg +++ b/doc/source/images/functional_data_model.svg @@ -1,139 +1,600 @@ - - - - - - - Audit Template - - - - - OpenStack Cluster - - - - - Applies to - - - - - Audit - - - - - gets configuration from - - - - - Goal - - - - - Achieves - - - - - Action Plan - - - - - Generates - - - - - Action - - - - - - is composed of - - - - - - Next action - - - - - - - - - - - - - Administrator - - - - - - Triggers - - - - - Defines Audit configuration in - - - - - - - - - - Customer - - - - - - Consumes resources - - - - - Resources - - - - - - - - - - - - - Modifies - - - - - Launches - - - - - Strategy - - - - - uses - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/source/images/sequence_from_audit_execution_to_actionplan_creation.png b/doc/source/images/sequence_from_audit_execution_to_actionplan_creation.png new file mode 100644 index 000000000..f1721b326 Binary files /dev/null and b/doc/source/images/sequence_from_audit_execution_to_actionplan_creation.png differ diff --git a/doc/source/images/sequence_trigger_audit_in_decision_engine.png b/doc/source/images/sequence_trigger_audit_in_decision_engine.png index eb9e1ecaa..36848003f 100644 Binary files a/doc/source/images/sequence_trigger_audit_in_decision_engine.png and b/doc/source/images/sequence_trigger_audit_in_decision_engine.png differ diff --git a/doc/source/images/watcher_class_diagram.png b/doc/source/images/watcher_class_diagram.png deleted file mode 100644 index 95bb7feb7..000000000 Binary files a/doc/source/images/watcher_class_diagram.png and /dev/null differ diff --git a/doc/source/images/watcher_db_schema_diagram.png b/doc/source/images/watcher_db_schema_diagram.png new file mode 100644 index 000000000..a43c78faa Binary files /dev/null and b/doc/source/images/watcher_db_schema_diagram.png differ diff --git a/doc/source/index.rst b/doc/source/index.rst index b305de524..ab8ccf8f5 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -73,6 +73,7 @@ Plugins :maxdepth: 1 dev/plugin/base-setup + dev/plugin/goal-plugin dev/plugin/strategy-plugin dev/plugin/action-plugin dev/plugin/planner-plugin diff --git a/watcher/api/controllers/v1/action_plan.py b/watcher/api/controllers/v1/action_plan.py index cabbf64fb..1e3c03a01 100644 --- a/watcher/api/controllers/v1/action_plan.py +++ b/watcher/api/controllers/v1/action_plan.py @@ -16,9 +16,11 @@ # limitations under the License. """ -An :ref:`Action Plan ` is a flow of +An :ref:`Action Plan ` specifies a flow of :ref:`Actions ` that should be executed in order to satisfy -a given :ref:`Goal `. +a given :ref:`Goal `. It also contains an estimated +:ref:`global efficacy ` alongside a set of +:ref:`efficacy indicators `. An :ref:`Action Plan ` is generated by Watcher when an :ref:`Audit ` is successful which implies that the @@ -26,16 +28,13 @@ An :ref:`Action Plan ` is generated by Watcher when an which was used has found a :ref:`Solution ` to achieve the :ref:`Goal ` of this :ref:`Audit `. -In the default implementation of Watcher, an -:ref:`Action Plan ` -is only composed of successive :ref:`Actions ` -(i.e., a Workflow of :ref:`Actions ` belonging to a unique -branch). +In the default implementation of Watcher, an action plan is composed of +a list of successive :ref:`Actions ` (i.e., a Workflow of +:ref:`Actions ` belonging to a unique branch). However, Watcher provides abstract interfaces for many of its components, -allowing other implementations to generate and handle more complex -:ref:`Action Plan(s) ` -composed of two types of Action Item(s): +allowing other implementations to generate and handle more complex :ref:`Action +Plan(s) ` composed of two types of Action Item(s): - simple :ref:`Actions `: atomic tasks, which means it can not be split into smaller tasks or commands from an OpenStack point of @@ -46,13 +45,14 @@ composed of two types of Action Item(s): An :ref:`Action Plan ` may be described using standard workflow model description formats such as -`Business Process Model and Notation 2.0 (BPMN 2.0) `_ -or `Unified Modeling Language (UML) `_. +`Business Process Model and Notation 2.0 (BPMN 2.0) +`_ or `Unified Modeling Language (UML) +`_. To see the life-cycle and description of -:ref:`Action Plan ` states, visit :ref:`the Action Plan state -machine `. -""" # noqa +:ref:`Action Plan ` states, visit :ref:`the Action Plan +state machine `. +""" import datetime diff --git a/watcher/api/controllers/v1/efficacy_indicator.py b/watcher/api/controllers/v1/efficacy_indicator.py index 54fafac9c..724fc4f19 100644 --- a/watcher/api/controllers/v1/efficacy_indicator.py +++ b/watcher/api/controllers/v1/efficacy_indicator.py @@ -15,20 +15,20 @@ # limitations under the License. """ -An efficacy indicator is a single value -that gives an indication on how the :ref:`solution ` -produced by a given :ref:`strategy ` performed. These -efficacy indicators are specific to a given :ref:`goal ` and -are usually used to compute the :ref:`gobal efficacy ` of -the resulting :ref:`action plan `. +An efficacy indicator is a single value that gives an indication on how the +:ref:`solution ` produced by a given :ref:`strategy +` performed. These efficacy indicators are specific to a +given :ref:`goal ` and are usually used to compute the +:ref:`gobal efficacy ` of the resulting :ref:`action plan +`. In Watcher, these efficacy indicators are specified alongside the goal they relate to. When a strategy (which always relates to a goal) is executed, it -produces a solution containing the efficacy indicators specified by the -goal. This solution, which has been translated by the :ref:`Watcher Planner -` into an action plan, will see its -indicators and global efficacy stored and would now be accessible through the -:ref:`Watcher API `. +produces a solution containing the efficacy indicators specified by the goal. +This solution, which has been translated by the :ref:`Watcher Planner +` into an action plan, will see its indicators and +global efficacy stored and would now be accessible through the :ref:`Watcher +API `. """ import numbers diff --git a/watcher/decision_engine/audit/default.py b/watcher/decision_engine/audit/default.py index 417d5b44c..4ea64d179 100644 --- a/watcher/decision_engine/audit/default.py +++ b/watcher/decision_engine/audit/default.py @@ -13,6 +13,7 @@ # implied. # See the License for the specific language governing permissions and # limitations under the License. + from oslo_log import log from watcher.common.messaging.events import event as watcher_event diff --git a/watcher/decision_engine/goal/efficacy/base.py b/watcher/decision_engine/goal/efficacy/base.py index cff52bf85..129c094a8 100644 --- a/watcher/decision_engine/goal/efficacy/base.py +++ b/watcher/decision_engine/goal/efficacy/base.py @@ -14,6 +14,15 @@ # See the License for the specific language governing permissions and # limitations under the License. +""" +An efficacy specfication is a contract that is associated to each :ref:`Goal +` that defines the various :ref:`efficacy indicators +` a strategy achieving the associated goal +should provide within its :ref:`solution `. Indeed, each +solution proposed by a strategy will be validated against this contract before +calculating its :ref:`global efficacy `. +""" + import abc import json diff --git a/watcher/decision_engine/solution/base.py b/watcher/decision_engine/solution/base.py index 22f56e4be..3aa895ce3 100644 --- a/watcher/decision_engine/solution/base.py +++ b/watcher/decision_engine/solution/base.py @@ -18,10 +18,17 @@ # """ -A :ref:`Solution ` is a set of -:ref:`Actions ` generated by a -:ref:`Strategy ` (i.e., an algorithm) in order to achieve -the :ref:`Goal ` of an :ref:`Audit `. +A :ref:`Solution ` is the result of execution of a +:ref:`strategy ` (i.e., an algorithm). +Each solution is composed of many pieces of information: + +- A set of :ref:`actions ` generated by the strategy in + order to achieve the :ref:`goal ` of an associated + :ref:`audit `. +- A set of :ref:`efficacy indicators ` as + defined by the associated goal +- A :ref:`global efficacy ` which is computed by the + associated goal using the aforementioned efficacy indicators. A :ref:`Solution ` is different from an :ref:`Action Plan ` because it contains the diff --git a/watcher/locale/watcher.pot b/watcher/locale/watcher.pot index 7ac76e938..fcaba9613 100644 --- a/watcher/locale/watcher.pot +++ b/watcher/locale/watcher.pot @@ -7,9 +7,9 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: python-watcher 0.26.1.dev33\n" +"Project-Id-Version: python-watcher 0.26.1.dev88\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2016-05-11 15:31+0200\n" +"POT-Creation-Date: 2016-06-02 10:23+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -48,24 +48,24 @@ msgid "" "'public_endpoint' option." msgstr "" -#: watcher/api/controllers/v1/action.py:364 +#: watcher/api/controllers/v1/action.py:365 msgid "Cannot create an action directly" msgstr "" -#: watcher/api/controllers/v1/action.py:388 +#: watcher/api/controllers/v1/action.py:389 msgid "Cannot modify an action directly" msgstr "" -#: watcher/api/controllers/v1/action.py:424 +#: watcher/api/controllers/v1/action.py:425 msgid "Cannot delete an action directly" msgstr "" -#: watcher/api/controllers/v1/action_plan.py:87 +#: watcher/api/controllers/v1/action_plan.py:91 #, python-format msgid "Invalid state: %(state)s" msgstr "" -#: watcher/api/controllers/v1/action_plan.py:407 +#: watcher/api/controllers/v1/action_plan.py:451 #, python-format msgid "State transition not allowed: (%(initial_state)s -> %(new_state)s)" msgstr "" @@ -74,15 +74,15 @@ msgstr "" msgid "The audit template UUID or name specified is invalid" msgstr "" -#: watcher/api/controllers/v1/audit_template.py:138 +#: watcher/api/controllers/v1/audit_template.py:141 #, python-format msgid "" "'%(strategy)s' strategy does relate to the '%(goal)s' goal. Possible " "choices: %(choices)s" msgstr "" -#: watcher/api/controllers/v1/audit_template.py:160 -msgid "Cannot remove 'goal_uuid' attribute from an audit template" +#: watcher/api/controllers/v1/audit_template.py:169 +msgid "Cannot remove 'goal' attribute from an audit template" msgstr "" #: watcher/api/controllers/v1/types.py:123 @@ -146,18 +146,19 @@ msgstr "" msgid "The target state is not defined" msgstr "" -#: watcher/applier/actions/migration.py:71 +#: watcher/applier/actions/migration.py:69 msgid "The parameter resource_id is invalid." msgstr "" -#: watcher/applier/actions/migration.py:124 +#: watcher/applier/actions/migration.py:123 +#: watcher/applier/actions/migration.py:137 #, python-format msgid "" "Unexpected error occured. Migration failed forinstance %s. Leaving " "instance on previous host." msgstr "" -#: watcher/applier/actions/migration.py:140 +#: watcher/applier/actions/migration.py:155 #, python-format msgid "Migration of type %(migration_type)s is not supported." msgstr "" @@ -176,22 +177,22 @@ msgstr "" msgid "Oops! We need disaster recover plan" msgstr "" +#: watcher/cmd/api.py:42 +#, python-format +msgid "serving on 0.0.0.0:%(port)s, view at %(protocol)s://127.0.0.1:%(port)s" +msgstr "" + #: watcher/cmd/api.py:46 #, python-format -msgid "serving on 0.0.0.0:%(port)s, view at http://127.0.0.1:%(port)s" +msgid "serving on %(protocol)s://%(host)s:%(port)s" msgstr "" -#: watcher/cmd/api.py:50 -#, python-format -msgid "serving on http://%(host)s:%(port)s" -msgstr "" - -#: watcher/cmd/applier.py:41 +#: watcher/cmd/applier.py:38 #, python-format msgid "Starting Watcher Applier service in PID %s" msgstr "" -#: watcher/cmd/decisionengine.py:42 +#: watcher/cmd/decisionengine.py:39 #, python-format msgid "Starting Watcher Decision Engine service in PID %s" msgstr "" @@ -360,7 +361,7 @@ msgstr "" msgid "Action %(action)s could not be found" msgstr "" -#: watcher/common/exception.py:238 +#: watcher/common/exception.py:238 watcher/common/exception.py:256 #, python-format msgid "An action with UUID %(uuid)s already exists" msgstr "" @@ -374,87 +375,114 @@ msgstr "" msgid "Filtering actions on both audit and action-plan is prohibited" msgstr "" -#: watcher/common/exception.py:256 +#: watcher/common/exception.py:252 +#, python-format +msgid "Efficacy indicator %(efficacy_indicator)s could not be found" +msgstr "" + +#: watcher/common/exception.py:264 #, python-format msgid "Couldn't apply patch '%(patch)s'. Reason: %(reason)s" msgstr "" -#: watcher/common/exception.py:262 +#: watcher/common/exception.py:270 #, python-format msgid "Workflow execution error: %(error)s" msgstr "" -#: watcher/common/exception.py:266 +#: watcher/common/exception.py:274 msgid "Illegal argument" msgstr "" -#: watcher/common/exception.py:270 +#: watcher/common/exception.py:278 msgid "No such metric" msgstr "" -#: watcher/common/exception.py:274 +#: watcher/common/exception.py:282 msgid "No rows were returned" msgstr "" -#: watcher/common/exception.py:278 +#: watcher/common/exception.py:286 #, python-format msgid "%(client)s connection failed. Reason: %(reason)s" msgstr "" -#: watcher/common/exception.py:282 +#: watcher/common/exception.py:290 msgid "'Keystone API endpoint is missing''" msgstr "" -#: watcher/common/exception.py:286 +#: watcher/common/exception.py:294 msgid "The list of hypervisor(s) in the cluster is empty" msgstr "" -#: watcher/common/exception.py:290 +#: watcher/common/exception.py:298 msgid "The metrics resource collector is not defined" msgstr "" -#: watcher/common/exception.py:294 +#: watcher/common/exception.py:302 msgid "The cluster state is not defined" msgstr "" -#: watcher/common/exception.py:298 +#: watcher/common/exception.py:306 #, python-format msgid "No strategy could be found to achieve the '%(goal)s' goal." msgstr "" -#: watcher/common/exception.py:304 +#: watcher/common/exception.py:310 #, python-format -msgid "The instance '%(name)s' is not found" +msgid "The value '%(value)s' with spec type '%(spec_type)s' is invalid." msgstr "" -#: watcher/common/exception.py:308 -msgid "The hypervisor is not found" -msgstr "" - -#: watcher/common/exception.py:312 +#: watcher/common/exception.py:315 #, python-format -msgid "Error loading plugin '%(name)s'" -msgstr "" - -#: watcher/common/exception.py:316 -#, python-format -msgid "The identifier '%(name)s' is a reserved word" +msgid "" +"Could not compute the global efficacy for the '%(goal)s' goal using the " +"'%(strategy)s' strategy." msgstr "" #: watcher/common/exception.py:320 #, python-format -msgid "The %(name)s resource %(id)s is not soft deleted" +msgid "No values returned by %(resource_id)s for %(metric_name)s." msgstr "" #: watcher/common/exception.py:324 +#, python-format +msgid "No %(metric)s metric for %(host)s found." +msgstr "" + +#: watcher/common/exception.py:330 +#, python-format +msgid "The instance '%(name)s' is not found" +msgstr "" + +#: watcher/common/exception.py:334 +msgid "The hypervisor is not found" +msgstr "" + +#: watcher/common/exception.py:338 +#, python-format +msgid "Error loading plugin '%(name)s'" +msgstr "" + +#: watcher/common/exception.py:342 +#, python-format +msgid "The identifier '%(name)s' is a reserved word" +msgstr "" + +#: watcher/common/exception.py:346 +#, python-format +msgid "The %(name)s resource %(id)s is not soft deleted" +msgstr "" + +#: watcher/common/exception.py:350 msgid "Limit should be positive" msgstr "" -#: watcher/common/service.py:40 +#: watcher/common/service.py:43 msgid "Seconds between running periodic tasks." msgstr "" -#: watcher/common/service.py:43 +#: watcher/common/service.py:46 msgid "" "Name of this node. This can be an opaque identifier. It is not " "necessarily a hostname, FQDN, or IP address. However, the node name must " @@ -462,7 +490,11 @@ msgid "" " or IP address." msgstr "" -#: watcher/common/utils.py:53 +#: watcher/common/service.py:226 +msgid "Plugins" +msgstr "" + +#: watcher/common/utils.py:76 #, python-format msgid "" "Failed to remove trailing character. Returning original object.Supplied " @@ -477,231 +509,266 @@ msgstr "" msgid "Messaging configuration error" msgstr "" -#: watcher/db/purge.py:50 +#: watcher/db/purge.py:51 msgid "Goals" msgstr "" -#: watcher/db/purge.py:51 +#: watcher/db/purge.py:52 msgid "Strategies" msgstr "" -#: watcher/db/purge.py:52 +#: watcher/db/purge.py:53 msgid "Audit Templates" msgstr "" -#: watcher/db/purge.py:53 +#: watcher/db/purge.py:54 msgid "Audits" msgstr "" -#: watcher/db/purge.py:54 +#: watcher/db/purge.py:55 msgid "Action Plans" msgstr "" -#: watcher/db/purge.py:55 +#: watcher/db/purge.py:56 msgid "Actions" msgstr "" -#: watcher/db/purge.py:102 +#: watcher/db/purge.py:103 msgid "Total" msgstr "" -#: watcher/db/purge.py:160 +#: watcher/db/purge.py:166 msgid "Audit Template" msgstr "" -#: watcher/db/purge.py:227 +#: watcher/db/purge.py:233 #, python-format msgid "" "Orphans found:\n" "%s" msgstr "" -#: watcher/db/purge.py:306 +#: watcher/db/purge.py:312 #, python-format msgid "There are %(count)d objects set for deletion. Continue? [y/N]" msgstr "" -#: watcher/db/purge.py:313 +#: watcher/db/purge.py:319 #, python-format msgid "" "The number of objects (%(num)s) to delete from the database exceeds the " "maximum number of objects (%(max_number)s) specified." msgstr "" -#: watcher/db/purge.py:318 +#: watcher/db/purge.py:324 msgid "Do you want to delete objects up to the specified maximum number? [y/N]" msgstr "" -#: watcher/db/purge.py:408 +#: watcher/db/purge.py:414 msgid "Deleting..." msgstr "" -#: watcher/db/purge.py:414 +#: watcher/db/purge.py:420 msgid "Starting purge command" msgstr "" -#: watcher/db/purge.py:424 +#: watcher/db/purge.py:430 msgid " (orphans excluded)" msgstr "" -#: watcher/db/purge.py:425 +#: watcher/db/purge.py:431 msgid " (may include orphans)" msgstr "" -#: watcher/db/purge.py:428 watcher/db/purge.py:429 +#: watcher/db/purge.py:434 watcher/db/purge.py:435 #, python-format msgid "Purge results summary%s:" msgstr "" -#: watcher/db/purge.py:432 +#: watcher/db/purge.py:438 #, python-format msgid "Here below is a table containing the objects that can be purged%s:" msgstr "" -#: watcher/db/purge.py:437 +#: watcher/db/purge.py:443 msgid "Purge process completed" msgstr "" -#: watcher/db/sqlalchemy/api.py:443 +#: watcher/db/sqlalchemy/api.py:477 msgid "Cannot overwrite UUID for an existing Goal." msgstr "" -#: watcher/db/sqlalchemy/api.py:509 +#: watcher/db/sqlalchemy/api.py:543 msgid "Cannot overwrite UUID for an existing Strategy." msgstr "" -#: watcher/db/sqlalchemy/api.py:586 +#: watcher/db/sqlalchemy/api.py:620 msgid "Cannot overwrite UUID for an existing Audit Template." msgstr "" -#: watcher/db/sqlalchemy/api.py:683 +#: watcher/db/sqlalchemy/api.py:717 msgid "Cannot overwrite UUID for an existing Audit." msgstr "" -#: watcher/db/sqlalchemy/api.py:778 +#: watcher/db/sqlalchemy/api.py:812 msgid "Cannot overwrite UUID for an existing Action." msgstr "" -#: watcher/db/sqlalchemy/api.py:891 +#: watcher/db/sqlalchemy/api.py:925 msgid "Cannot overwrite UUID for an existing Action Plan." msgstr "" +#: watcher/db/sqlalchemy/api.py:1004 +msgid "Cannot overwrite UUID for an existing efficacy indicator." +msgstr "" + #: watcher/db/sqlalchemy/migration.py:73 msgid "" "Watcher database schema is already under version control; use upgrade() " "instead" msgstr "" -#: watcher/decision_engine/sync.py:94 +#: watcher/decision_engine/sync.py:106 #, python-format msgid "Goal %s already exists" msgstr "" -#: watcher/decision_engine/sync.py:103 +#: watcher/decision_engine/sync.py:115 #, python-format msgid "Strategy %s already exists" msgstr "" -#: watcher/decision_engine/sync.py:125 +#: watcher/decision_engine/sync.py:138 #, python-format msgid "Goal %s created" msgstr "" -#: watcher/decision_engine/sync.py:154 +#: watcher/decision_engine/sync.py:167 #, python-format msgid "Strategy %s created" msgstr "" -#: watcher/decision_engine/sync.py:180 +#: watcher/decision_engine/sync.py:193 #, python-format msgid "Audit Template '%s' synced" msgstr "" -#: watcher/decision_engine/sync.py:225 +#: watcher/decision_engine/sync.py:238 #, python-format msgid "Audit Template '%(audit_template)s' references a goal that does not exist" msgstr "" -#: watcher/decision_engine/sync.py:240 +#: watcher/decision_engine/sync.py:253 #, python-format msgid "" "Audit Template '%(audit_template)s' references a strategy that does not " "exist" msgstr "" -#: watcher/decision_engine/sync.py:279 +#: watcher/decision_engine/sync.py:310 #, python-format msgid "Goal %s unchanged" msgstr "" -#: watcher/decision_engine/sync.py:281 +#: watcher/decision_engine/sync.py:312 #, python-format msgid "Goal %s modified" msgstr "" -#: watcher/decision_engine/sync.py:295 +#: watcher/decision_engine/sync.py:326 #, python-format msgid "Strategy %s unchanged" msgstr "" -#: watcher/decision_engine/sync.py:297 +#: watcher/decision_engine/sync.py:328 #, python-format msgid "Strategy %s modified" msgstr "" +#: watcher/decision_engine/goal/goals.py:34 +msgid "Dummy goal" +msgstr "" + +#: watcher/decision_engine/goal/goals.py:62 +msgid "Unclassified" +msgstr "" + +#: watcher/decision_engine/goal/goals.py:82 +msgid "Server consolidation" +msgstr "" + +#: watcher/decision_engine/goal/goals.py:102 +msgid "Thermal optimization" +msgstr "" + +#: watcher/decision_engine/goal/goals.py:122 +#: watcher/decision_engine/strategy/strategies/workload_stabilization.py:130 +msgid "Workload balancing" +msgstr "" + +#: watcher/decision_engine/goal/efficacy/indicators.py:80 +msgid "Average CPU load as a percentage of the CPU time." +msgstr "" + +#: watcher/decision_engine/goal/efficacy/indicators.py:95 +msgid "" +"Represents the percentage of released nodes out of the total number of " +"migrations." +msgstr "" + +#: watcher/decision_engine/goal/efficacy/indicators.py:111 +msgid "The number of compute nodes to be released." +msgstr "" + +#: watcher/decision_engine/goal/efficacy/indicators.py:125 +msgid "The number of migrations to be performed." +msgstr "" + +#: watcher/decision_engine/goal/efficacy/specs.py:107 +msgid "Ratio of released compute nodes divided by the number of VM migrations." +msgstr "" + #: watcher/decision_engine/model/model_root.py:33 #: watcher/decision_engine/model/model_root.py:38 msgid "'obj' argument type is not valid" msgstr "" -#: watcher/decision_engine/planner/default.py:78 +#: watcher/decision_engine/planner/default.py:80 msgid "The action plan is empty" msgstr "" +#: watcher/decision_engine/solution/efficacy.py:41 +msgid "An indicator value should be a number" +msgstr "" + #: watcher/decision_engine/strategy/selection/default.py:74 #, python-format msgid "Could not load any strategy for goal %(goal)s" msgstr "" -#: watcher/decision_engine/strategy/strategies/base.py:165 -msgid "Dummy goal" -msgstr "" - -#: watcher/decision_engine/strategy/strategies/base.py:188 -msgid "Unclassified" -msgstr "" - -#: watcher/decision_engine/strategy/strategies/base.py:204 -msgid "Server consolidation" -msgstr "" - -#: watcher/decision_engine/strategy/strategies/base.py:220 -msgid "Thermal optimization" -msgstr "" - -#: watcher/decision_engine/strategy/strategies/basic_consolidation.py:119 +#: watcher/decision_engine/strategy/strategies/basic_consolidation.py:117 msgid "Basic offline consolidation" msgstr "" -#: watcher/decision_engine/strategy/strategies/basic_consolidation.py:296 -#: watcher/decision_engine/strategy/strategies/basic_consolidation.py:343 +#: watcher/decision_engine/strategy/strategies/basic_consolidation.py:263 +#: watcher/decision_engine/strategy/strategies/basic_consolidation.py:305 #, python-format msgid "No values returned by %(resource_id)s for %(metric_name)s" msgstr "" -#: watcher/decision_engine/strategy/strategies/basic_consolidation.py:456 +#: watcher/decision_engine/strategy/strategies/basic_consolidation.py:414 msgid "Initializing Sercon Consolidation" msgstr "" -#: watcher/decision_engine/strategy/strategies/basic_consolidation.py:500 +#: watcher/decision_engine/strategy/strategies/basic_consolidation.py:456 msgid "The workloads of the compute nodes of the cluster is zero" msgstr "" -#: watcher/decision_engine/strategy/strategies/dummy_strategy.py:74 +#: watcher/decision_engine/strategy/strategies/dummy_strategy.py:78 msgid "Dummy strategy" msgstr "" -#: watcher/decision_engine/strategy/strategies/outlet_temp_control.py:102 +#: watcher/decision_engine/strategy/strategies/outlet_temp_control.py:103 msgid "Outlet temperature based strategy" msgstr "" @@ -710,16 +777,20 @@ msgstr "" msgid "%s: no outlet temp data" msgstr "" -#: watcher/decision_engine/strategy/strategies/outlet_temp_control.py:181 +#: watcher/decision_engine/strategy/strategies/outlet_temp_control.py:180 #, python-format msgid "VM not active, skipped: %s" msgstr "" -#: watcher/decision_engine/strategy/strategies/outlet_temp_control.py:239 +#: watcher/decision_engine/strategy/strategies/outlet_temp_control.py:186 +msgid "VM not found" +msgstr "" + +#: watcher/decision_engine/strategy/strategies/outlet_temp_control.py:235 msgid "No hosts under outlet temp threshold found" msgstr "" -#: watcher/decision_engine/strategy/strategies/outlet_temp_control.py:262 +#: watcher/decision_engine/strategy/strategies/outlet_temp_control.py:255 msgid "No proper target host could be found" msgstr "" @@ -732,20 +803,68 @@ msgstr "" msgid "Unexpexted resource state type, state=%(state)s, state_type=%(st)s." msgstr "" -#: watcher/decision_engine/strategy/strategies/vm_workload_consolidation.py:180 +#: watcher/decision_engine/strategy/strategies/vm_workload_consolidation.py:178 #, python-format msgid "Cannot live migrate: vm_uuid=%(vm_uuid)s, state=%(vm_state)s." msgstr "" -#: watcher/decision_engine/strategy/strategies/vm_workload_consolidation.py:264 +#: watcher/decision_engine/strategy/strategies/vm_workload_consolidation.py:262 #, python-format msgid "No values returned by %(resource_id)s for memory.usage or disk.root.size" msgstr "" -#: watcher/decision_engine/strategy/strategies/vm_workload_consolidation.py:515 +#: watcher/decision_engine/strategy/strategies/vm_workload_consolidation.py:519 msgid "Executing Smart Strategy" msgstr "" +#: watcher/decision_engine/strategy/strategies/workload_balance.py:103 +msgid "workload balance migration strategy" +msgstr "" + +#: watcher/decision_engine/strategy/strategies/workload_balance.py:152 +#, python-format +msgid "VM not found Error: %s" +msgstr "" + +#: watcher/decision_engine/strategy/strategies/workload_balance.py:157 +#, python-format +msgid "VM not found from hypervisor: %s" +msgstr "" + +#: watcher/decision_engine/strategy/strategies/workload_balance.py:233 +msgid "Can not get cpu_util" +msgstr "" + +#: watcher/decision_engine/strategy/strategies/workload_balance.py:264 +msgid "Initializing Workload Balance Strategy" +msgstr "" + +#: watcher/decision_engine/strategy/strategies/workload_balance.py:282 +#, python-format +msgid "" +"No hosts current have CPU utilization under %s percent, therefore there " +"are no possible target hosts for any migration" +msgstr "" + +#: watcher/decision_engine/strategy/strategies/workload_balance.py:305 +msgid "" +"No proper target host could be found, it might be because of there's no " +"enough CPU/Memory/DISK" +msgstr "" + +#: watcher/decision_engine/strategy/strategies/workload_stabilization.py:173 +msgid "get_vm_load started" +msgstr "" + +#: watcher/decision_engine/strategy/strategies/workload_stabilization.py:251 +#, python-format +msgid "Incorrect mapping: could not find associated weight for %s in weight dict." +msgstr "" + +#: watcher/decision_engine/strategy/strategies/workload_stabilization.py:371 +msgid "Initializing Workload Stabilization" +msgstr "" + #: watcher/objects/base.py:70 #, python-format msgid "Error setting %(attr)s" @@ -783,7 +902,7 @@ msgstr "" msgid "A datetime.datetime is required here" msgstr "" -#: watcher/objects/utils.py:105 +#: watcher/objects/utils.py:114 #, python-format msgid "An object of class %s is required here" msgstr ""