From ad63b7d0afe3a42aeef6edd728849c03ee3b32f5 Mon Sep 17 00:00:00 2001 From: vmahe Date: Mon, 14 Dec 2015 18:08:41 +0100 Subject: [PATCH] Provide a more dynamic Actions management solution Today, the set of possible `Actions`_ is fixed for a given version of Watcher and enables optimization algorithms to include actions such as instance migration, changing hypervisor state, changing power state (ACPI level, ...). It should be possible to deploy and configure (in watcher.conf) new potential `Actions`_ and associate each `Action`_ to a Python class, without the need to redeploy a new version of Watcher. Change-Id: If4b68079967854f9040f11206e2b6202a66d8551 blueprint: watcher-add-actions-via-conf --- ...her_Actions_Management_Functional_Need.dia | Bin 0 -> 3023 bytes ...her_Actions_Management_Functional_Need.png | Bin 0 -> 26885 bytes ...her_Actions_Management_Functional_Need.svg | 171 ++++++ .../approved/watcher-add-actions-via-conf.rst | 523 ++++++++++++++++++ tests/test_titles.py | 2 + 5 files changed, 696 insertions(+) create mode 100644 doc/source/image_src/Watcher_Actions_Management_Functional_Need.dia create mode 100644 doc/source/images/Watcher_Actions_Management_Functional_Need.png create mode 100644 doc/source/images/Watcher_Actions_Management_Functional_Need.svg create mode 100644 specs/mitaka/approved/watcher-add-actions-via-conf.rst diff --git a/doc/source/image_src/Watcher_Actions_Management_Functional_Need.dia b/doc/source/image_src/Watcher_Actions_Management_Functional_Need.dia new file mode 100644 index 0000000000000000000000000000000000000000..fd7cdc14fd25702a68514732ca36db25c682ebdf GIT binary patch literal 3023 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 R)7jgz{{#PSk0Erv007;l^aua| literal 0 HcmV?d00001 diff --git a/doc/source/images/Watcher_Actions_Management_Functional_Need.png b/doc/source/images/Watcher_Actions_Management_Functional_Need.png new file mode 100644 index 0000000000000000000000000000000000000000..97aabbd0453ec3326b2fb1f6834e0529603607fe GIT binary patch literal 26885 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 l;s1XOa5KyQd5s8MV+p + + + + + + + + + + + Functional Need + + + + + + + Watcher Strategy + + + + + + + + Watcher Planner + + + + + + + + Watcher Applier + + + + + + + Define and instanciate Actions + for Optimization + + + + + + + Schedule Actions + in Time + + + + + + + Simulate + and + Execute Actions + + + + + + + + + + + + + + + List of Actions + + + input parameters + + + target resource + + + + + + + + + + + + Actions flow description + + + + + + + + History + + + Output results + + + Events + + + + + + + + + + + + + + + + + + + + + + Scheduling Policies/Rules + + + Rules solver + + + + + + + + + + Configuration of Workflow Engine + (implementation, persistence, ...) + + + Mapping between Action types and Action Handlers + + + + + + + + + + + + + + + + + + + + + + + Action types description + + + + + + + + diff --git a/specs/mitaka/approved/watcher-add-actions-via-conf.rst b/specs/mitaka/approved/watcher-add-actions-via-conf.rst new file mode 100644 index 0000000..a7a2d54 --- /dev/null +++ b/specs/mitaka/approved/watcher-add-actions-via-conf.rst @@ -0,0 +1,523 @@ +.. + This work is licensed under a Creative Commons Attribution 3.0 Unported + License. + + http://creativecommons.org/licenses/by/3.0/legalcode + +================================================== +Provide a more dynamic Actions management solution +================================================== + +Include the URL of your launchpad blueprint: + +https://blueprints.launchpad.net/watcher/+spec/watcher-add-actions-via-conf + +Watcher aims at providing an open solution for auditing any pool of resources +and optimizing it through recommended `Actions`_. + +This blueprint aims at providing a generic and dynamic system for adding new +Optimization actions that can be used in one or several Strategies to reach +the Goal_(s) of an `Audit`_. + +.. _problem_description: + +Problem description +=================== + +Today, the set of possible `Actions`_ is fixed for a given version of Watcher +and enables optimization algorithms to include actions such as instance +migration, changing hypervisor state, changing power state (ACPI level, ...). + +It should be possible to deploy and configure (in watcher.conf) new potential +Actions and associate each Action to a Python class, without the need to +redeploy a new version of Watcher. + +Note that even if so far `Watcher Decision Engine`_ and `Watcher Applier`_ are +packaged in the same Python package, those services may be deployed on separate +machines. Beyond that, there will probably be a blueprint aiming at having the +`Watcher Planner`_ as a separate component (today it is included in the +Watcher Decision Engine). + +This is the reason why, there should be a strong separation of concern +regarding: + +* Action types definition (input parameters, ...) +* Actions instantiation of a given Action type +* Actions scheduling (through Action types scheduling policies, ...) +* Actions simulation and execution (through some Action handler class) + +Each of these concerns are related to a specific `Bounded Context`_ and the +objective of this blueprint is to better address each Bounded Context +independently so that we can update the model and source code of a given +context without necessarily impacting the other contexts (Micro-Services +architecture). + +Below you will find a diagram, showing the functional need regarding Actions +management in Watcher: + +.. image:: ../../../doc/source/images/Watcher_Actions_Management_Functional_Need.png + :width: 140% + +You can see that there is a need in Watcher for three main phases: + +* The first phase named "**Define and instantiate Actions for Optimization**" + takes place in the Strategies of the Watcher Decision Engine: each + `Strategy`_ needs to be able to use and instantiate a set of pre-defined + optimization Action types in order to achieve the goal of the `Audit`_. + Those Actions are not scheduled within a timeframe at that point. + + During this phase, developers need to be able to do four things in their + Strategy: + + * describe and register new types of Actions, with a dedicated Domain + Specific Language (DSL). Each type of Action should have its own input + parameters, name, description, version and target `resource type`_; + * create one or several instances of these Action types (i.e. Watcher + needs some kind of factory for Actions); + * add these Actions to the recommended `Solution`_ as a simple unordered + list of Actions; + * persist this list of recommended Actions in the `Watcher Database`_. + +* The second phase named "**Schedule Actions in Time**" takes place in the + Watcher Planner: the Actions instantiated in the Strategy need to + be scheduled so that they do not disturb the `SLA`_ and also because there is + some logical order when executing the Actions. For example, you will + disable an hypervisor before you start evacuating all instances from it (to + make sure that the hypervisor does not receive new initial placement request + from Nova). Therefore, the Watcher Planner must be able to take into + account some scheduling policies/rules such as: + + * do not migrate too many virtual machines at the same time in order to avoid + network congestion; + * make sure there's no more virtual machines on a compute node before + powering it off (for energy saving); + * live migration is preferred to inactive migration; + * ... + + As a result, during this phase of the Watcher processing, there should be an + efficient and extensible way to define those scheduling policies/rules and + there should be some implementation which can take them into account to + produce the design of a flow of Actions describing the dependency chain + between each Action (after Action A trigger Action B, ...). + + Ideally, the developer should be able to integrate a new "*rules solver*" + which could be dynamically loaded in the Watcher Planner according to what + is set in Watcher configuration files. + + Note that this scheduled flow of recommended Actions is what is named an + `Action Plan`_ in the Watcher vocabulary. + + It should also be possible to define error recovery rules which define what + must be done whenever an error occurs in the worklow (how many attempts must + be done, how often, ...). It should be possible to define error recovery + policies for a specific Action or for a wider scope (the whole flow or + embedded flow). + + Both scheduling policies/rules and the design of the flow of Actions could + rely on dedicated Domain Specific Languages (DSL) and on a specific "solver" + which would be able to take into account those rules. + + Again, the developers need an easy way to persist scheduling policies/rules + and the design of the flow of Actions in the `Watcher Database`_. + +* The third phase named "**Simulate and Execute Actions**" takes place in the + Watcher Applier: it consists in executing the flow of Actions built in + phase two. + This means that there should be some workflow engine system which is able to + read the flow description (described with a DSL and stored in the + Watcher Database), simulate the execution of this flow and if the + simulation is successful, really execute all the Actions it contains + in respect with the schedule (i.e. the dependency chain between Actions). + + This workflow engine should be able to load the implementation (named + "**ActionHandler**") of each Action type and do the following: + + * first launch some simulation method which makes sure all pre-conditions are + met before executing the real command and avoid useless rollbacks on the + real OpenStack services. The simulation phase could also make sure that all + **ActionHandler** implementations are present and that all + `target resource`_ exist. + * if pre-conditions are met, trigger some concrete command on technical + components (most of the time, it will be OpenStack components such as Nova, + Neutron,...). + * handle error recovery rules + + In order to do the mapping between Action types used in phase 1 and 2 and + **ActionHandler** classes, it must be possible for developer to implement + a mapping system which would be loaded dynamically and configurable. + + The workflow engine should be able to create a current context of execution + of the workflow which enables to take some output results from a previously + executed Action and inject it as input parameters of the next upcoming + Action. This context must be persisted in some database and the workflow + engine should be able to resume an interrupted workflow (in the case the + Watcher Applier service was stopped for example) from where it was + stoppped and not restart it from the zero. + + Ideally, it should be possible for an operator with an admin role to browse + the history of activity of the workflow engine (especially events and alarms + regarding executed Actions). + + The workflow engine should be able to send some notifications (on the Watcher + AMQP message bus) whenever the current state of an Action changes. + +Use Cases +---------- + +As a developer. +I want to be able to create and use new optimization Actions_ in the +optimization Strategies_ (loaded in the `Watcher Decision Engine`_). +So that I can easily develop new Strategies (for a given optimization Goal_) +which rely on these actions to change the state of a managed resource and +without needing an upgrade of Watcher. + +As a developer. +I want to be able to create workflows in the `Watcher Planner`_ containing +atomic actions and embedded workflows (made of several actions). Workflows may +schedule those actions in sequence or in parallel. +So that I can schedule optimization actions according to some scheduling +policies/rules. + +As a developer. +I want to be able to develop scheduling policies/rules and dynamically add +those rules without needing an upgrade of Watcher. +So that I can control how actions are scheduled in a given timeframe. + +As a developer. +I want to be able to provide new actions handlers as Python class which can be +loaded in the `Watcher Applier`_. +So that the Watcher Applier can launch concrete commands to the service +responsible for managing a resource (most of the time, it will be OpenStack +services such as Nova, Cinder, Neutron, ... but it can also be any resource +management service). + +As an operator with an admin role. +I want to be able to easily install and configure new optimization actions +without needing to deploy a new version of the Watcher software. +So that they can be used for a new optimization Strategy that I need to +install + +Project Priority +----------------- + +Not relevant because Watcher is not in the big tent so far. + +Proposed change +=============== + +Estimated changes are going to be in the following places: + +* in the `Watcher Decision Engine`_: + + * in `Strategies`_ + * in the `Watcher Planner`_ + +* in the `Watcher Applier`_ + +As described in the :ref:`Problem description `, Watcher +should integrate a new Task/Flow management system which provides a clear +separation between the three main phases: + +* Define new optimization `Action`_ types and instantiate them in Strategies +* Schedule Actions in time +* Simulate and execute Actions + +Alternatives +------------ + +We could deploy a new version of Watcher each time we need to add new Action +types but this would impact the availability of Watcher and would lead to a +much less evolutive and system. + +Data model impact +----------------- + +The following data object will probably be impacted: + +* **Action**: + + * there may be some changes in the way we store input parameters + of an `Action`_ (probably as an array of key-value pairs) and the unique id + of the `target resource`_. + * We may also need to store in the database the list of output values + returned after the Action was executed by the Watcher Applier. + +It may also be necessary to add some new data objects such as: + +* **ActionType** which would contain all the information related to a certain + type of Action: + + * **action_type**: the unique id of the Action type. + * **action_type_version**: the version of the Action type. It would be + provided with `SemVer`_ format. + * **parameters**: an array of input parameters provided as tuples with the + following fields: **(parameter_name, parameter_type)**. The + **parameter_type** can be any simple type such as string, integer, boolean, + float, ... + * **target_resource_type**: the unique `resource type`_ that this Action + type can change. It can be any `HEAT resource type`_. + * **display_name**: a short human readable name for the Action type. + * **display_description**: a long human readable description for the + Action type. + +REST API impact +--------------- + +There will be an impact on every REST resource URLs that starts with +**/v1/actions/** and that uses the type **Action**: + +* GET /v1/actions/(action_uuid) +* POST /v1/actions +* PATCH /v1/actions +* GET /v1/actions/detail + +The type **Action** will have new attributes mentionned in the previous +paragraph. + +Please look at the `Actions handling in Watcher API`_. + +If Watcher stores new data objects such as **ActionType**, it would be +necessary to provide new REST resource URLs for managing those objects with +CRUD operations. + +Note that for creating a new **ActionType** with the API, the user may provide +the description of this **ActionType** in a file compliant with the DSL that +will be used for it (i.e. probably a JSON or YAML file). + +Security impact +--------------- + +In the case a new Action needs to access an OpenStack service (for example, +Neutron), the **watcher** user under which Watcher Applier is running will +need to be declared as having a role with enough rights on this service to +trigger the concrete Action. + +Notifications impact +-------------------- + +None + +Other end user impact +--------------------- + +Aside from the API, here are there other ways a user will interact with this +feature: + +* impact on **python-watcherclient**: + + * will need to find a new way to display the list of Actions and the + detailed information about an Action. + * will need to be able to handle new data objects such as **ActionType** as + well + +Performance Impact +------------------ + +None + +Other deployer impact +--------------------- + +When delivering a new `Strategy`_, the operator will deploy the following +softwares: + +* the main Python class implementing the `Strategy`_; +* the files containing the description of the needed `Actions`_ types (written + in whatever DSL is appropriate for this); +* the files containing the description of the scheduling rules (written in + whatever DSL is appropriate for this); +* the Python Planner class (Rules solver) that will be able to read the + scheduling rules and generate a schedule of the Actions. Note that this is + optional to deliver a new class here because the new Strategy may rely on + a previously deployed Rules solver; +* the Python classes of each Actions handler needed in the Actions flow + to simulate/execute in the Watcher Applier. + +The operator will also need to change the Watcher configuration in order to +indicate: + +* What `Watcher Planner`_ implementation will be used for scheduling Actions +* What **Watcher Actions Mapper** implementation will be used to do the mapping + between Action types and Action handlers (i.e. Python class loaded by the + `Watcher Applier`_). +* What implementation of the Workflow engine must be used to simulate and + execute the Actions flow. +* What storage backend will be used by the Workflow engine used in the + Watcher Applier to persist the current state of the Actions flow. + +Developer impact +---------------- + +None + + +Implementation +============== + +Assignee(s) +----------- + +Primary assignee: + jed56 + +Work Items +---------- + +The Watcher team should first study whether solutions like `TaskFlow`_ or +`Mistral`_ would fit the need. It would certainly avoid long rewriting of +source code and would even help us anticipate the future needs regarding +the management/scheduling of `Actions`_ in Watcher. + +Here is the list of foreseen work items: + +* Find an appropriate Domain Specific Language (DSL) for describing a new + Action type in Watcher. +* Implement the factory which is able to instantiate Actions in the + `Strategy`_ according to the Action type description. +* Put the `Watcher Planner`_ classes in a dedicated Python package (not in + the `Watcher Decision Engine`_) +* Add a dynamic loading of the Watcher Planner implementation (via + stevedore) +* Find a generic format to persist the description of the flow of Actions that + will be generated by the `Watcher Planner`_ and loaded by the + `Watcher Applier`_ +* Provide a default implementation of the `Watcher Planner`_. This default + implementation should be very simple and based on priority associated to each + Action. Later, more complex implementations can be provided which would be + able to read scheduling rules DSL. We may have to benchmark several existing + Rules solver implementations. +* Add a dynamic loading of Actions handlers in the Watcher Applier (via + stevedore). +* Integrate an existing Workflow engine which would be able to load and execute + Actions handlers according to the design of the Actions flow produced + by the Watcher Planner (i.e. what is called the `Action Plan`_) +* Add a simulation phase in the Watcher Applier in order to verify that + the Actions flow can be executed without errors. +* Make sure errors are handled correctly whenever an Action fails during the + simulation phase or during the real execution of the Actions flow in the + Watcher Applier. + + +Dependencies +============ + +There are some dependencies with the following blueprints: + +* In blueprint named "`Code refactoring using terms defined in our glossary`_", + some classes related to this specification (meta-action, primitive, ...) will + be renamed or moved to another package. +* We have to consider the existence of existing workflow management frameworks + such as `TaskFlow`_: see blueprint + https://blueprints.launchpad.net/watcher/+spec/use-taskflow + +We should also have a look to other existing workflow management frameworks: + + * `Mistral`_: this OpenStack project proposes a Workflow as a service system + * `PyUtilib`_: it's a self contained workflow engine, intended to be embedded + and developed to automate the processing of scientific workflows. + * `Spiff Workflow`_: a workflow engine implemented in pure Python + * `hurry.workflow`_: a simple workflow system. It can be used to implement + stateful multi-version workflows for Zope Toolkit applications. + +Testing +======= + +Unit tests will be needed for: + +* the loading of the files containing the description of the needed `Actions`_ + types (written in whatever DSL is appropriate for this); +* the loading of files containing the description of the scheduling rules + (written in whatever DSL is appropriate for this); +* the Python Planner class (Rules solver) that will be able to read the + scheduling rules and generate a schedule of the Actions; +* the dynamic loading of an Action handler class inside the + `Watcher Applier`_; +* the Python classes of each Actions handler needed in the Actions flow + to simulate/execute in the Watcher Applier; +* the dynamic loading of a `Watcher Planner`_ implementation; +* the dynamic loading of a **Watcher Actions Mapper** implementation; +* the execution of the default provided Watcher Planner implementation +* the execution of the default provided **Watcher Actions Mapper** + implementation; +* the configuration of different storage backends for the Workflow engine used + in the Watcher Applier to persist the current state of the Actions + flow. +* the execution of a simulation phase and the error management during this + simulation phase + +It will also be necessary to validate the whole Action management system in +the existing integration tests. + + +Documentation Impact +==================== + +The documentation will have to be updated, especially the glossary, in order to +explain the new concepts regarding `Actions`_ definition, scheduling and +execution. + +The architecture description will also need to be updated because: + +* the `Watcher Planner`_ will become independent from the + `Watcher Decision Engine`_ +* a new component will be introduced: the **Watcher Actions Mapper** +* many component implementations will be provided as plugins (Action types, + Watcher Planner, **Watcher Actions Mapper**, **ActionHandler**) + +The documentation regarding Watcher installation and configuration will also +need to be updated in order to explain: + +* howto describe new Action types with the proposed DSL; +* howto deploy new Action types into Watcher; +* howto use new Action types in your optimization strategy (i.e. howto + instantiate an Action from a given Action type); +* howto add new scheduling policies/rules in the `Watcher Planner`_; +* howto build flows of Actions using the proposed DSL; +* howto add a new Watcher Planner implementation; +* howto add a new **Watcher Actions Mapper** implementation; +* howto configure the `Watcher Applier`_: engine implementation, persistence + backend, ... + +References +========== + +IRC discussions: + +* Action point related to `TaskFlow`_: http://eavesdrop.openstack.org/meetings/watcher/2015/watcher.2015-12-09-13.59.html +* A lot of exchanges regarding Actions and Workflow management in the Git + reviews related to the Watcher glossary: https://review.openstack.org/#/c/246370/ + + +History +======= + +None + + +.. _Actions handling in Watcher API: https://factory.b-com.com/www/watcher/doc/watcher/webapi/v1.html#actions +.. _SemVer: http://semver.org/ +.. _stevedore: https://pypi.python.org/pypi/stevedore +.. _TaskFlow: https://wiki.openstack.org/wiki/TaskFlow +.. _Mistral: https://wiki.openstack.org/wiki/Mistral +.. _PyUtilib: https://software.sandia.gov/trac/pyutilib +.. _Spiff Workflow: https://github.com/knipknap/SpiffWorkflow/wiki +.. _hurry.workflow: https://pypi.python.org/pypi/hurry.workflow +.. _potential Action states: https://factory.b-com.com/www/watcher/doc/watcher/glossary.html#action +.. _Action: https://factory.b-com.com/www/watcher/doc/watcher/glossary.html#action +.. _Actions: https://factory.b-com.com/www/watcher/doc/watcher/glossary.html#action +.. _Action Plan: https://factory.b-com.com/www/watcher/doc/watcher/glossary.html#action-plan +.. _Audit: https://factory.b-com.com/www/watcher/doc/watcher/glossary.html#audit +.. _Solution: https://factory.b-com.com/www/watcher/doc/watcher/glossary.html#solution +.. _SLA: https://factory.b-com.com/www/watcher/doc/watcher/glossary.html#sla +.. _Strategy: https://factory.b-com.com/www/watcher/doc/watcher/glossary.html#strategy +.. _Strategies: https://factory.b-com.com/www/watcher/doc/watcher/glossary.html#strategy +.. _Watcher Applier: https://factory.b-com.com/www/watcher/doc/watcher/architecture.html#watcher-applier +.. _Watcher Decision Engine: https://factory.b-com.com/www/watcher/doc/watcher/architecture.html#watcher-decision-engine +.. _Watcher Planner: https://factory.b-com.com/www/watcher/doc/watcher/glossary.html#watcher-planner +.. _Watcher Database: https://factory.b-com.com/www/watcher/doc/watcher/architecture.html#watcher-database +.. _resource type: https://factory.b-com.com/www/watcher/doc/watcher/glossary.html#managed-resource-type +.. _target resource: https://factory.b-com.com/www/watcher/doc/watcher/glossary.html#managed-resource +.. _Code refactoring using terms defined in our glossary: https://blueprints.launchpad.net/watcher/+spec/glossary-related-refactoring +.. _Bounded Context: http://martinfowler.com/bliki/BoundedContext.html +.. _HEAT resource type: http://docs.openstack.org/developer/heat/template_guide/openstack.html +.. _goal: https://factory.b-com.com/www/watcher/doc/watcher/glossary.html#goal diff --git a/tests/test_titles.py b/tests/test_titles.py index 6905003..afe7f7a 100644 --- a/tests/test_titles.py +++ b/tests/test_titles.py @@ -81,6 +81,8 @@ class TestTitles(testtools.TestCase): code_block = True if "http://" in line or "https://" in line: continue + if line.startswith("..") and "image::" in line: + continue # Allow lines which do not contain any whitespace if re.match("\s*[^\s]+$", line): continue