Compare commits
78 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8ebc898924 | ||
|
|
660c782626 | ||
|
|
dfaba80252 | ||
|
|
9dccb29bf4 | ||
|
|
853145f4d1 | ||
|
|
764f5c7681 | ||
|
|
b81b767567 | ||
|
|
764e31a7a1 | ||
|
|
1c49d07912 | ||
|
|
18549dc182 | ||
|
|
9f222221a7 | ||
|
|
1a64383a68 | ||
|
|
ac07f35dc7 | ||
|
|
010bc61cc9 | ||
|
|
3dd02ee895 | ||
|
|
a4bbe7f893 | ||
|
|
4c3073efb4 | ||
|
|
642226812f | ||
|
|
8f2ca2518f | ||
|
|
1698eb31f3 | ||
|
|
5fb74e677f | ||
|
|
35a5ba1cd0 | ||
|
|
c0a85be7b8 | ||
|
|
633b360652 | ||
|
|
d3160bf007 | ||
|
|
69152f2449 | ||
|
|
37cd75ffe3 | ||
|
|
c7fd5e8b21 | ||
|
|
f0b58f8c27 | ||
|
|
22dd6d42c3 | ||
|
|
bd29e2e79f | ||
|
|
c2eb112184 | ||
|
|
57cecb27f5 | ||
|
|
7c72d6f912 | ||
|
|
62570525ad | ||
|
|
92940ba9e2 | ||
|
|
7c8ce453ed | ||
|
|
33ea5f96f8 | ||
|
|
916f4d0c08 | ||
|
|
3fb5defc16 | ||
|
|
b01f4bead4 | ||
|
|
8516d629c2 | ||
|
|
35a1f0a657 | ||
|
|
d934971458 | ||
|
|
d92f85574d | ||
|
|
b1fe7a5f3d | ||
|
|
8a5eb4b6a1 | ||
|
|
91c14e4eda | ||
|
|
1613bd6904 | ||
|
|
ba4f5569d1 | ||
|
|
a62553a6a5 | ||
|
|
d5ba40530f | ||
|
|
f98e96da42 | ||
|
|
64747cad1f | ||
|
|
5f87e82bac | ||
|
|
ff89e942ca | ||
|
|
7faa501fb7 | ||
|
|
ab8d242c1f | ||
|
|
bbd26cafae | ||
|
|
0042356245 | ||
|
|
e1d4026c7c | ||
|
|
df692a8215 | ||
|
|
f9323889d6 | ||
|
|
0a44b2972e | ||
|
|
c5c16ac055 | ||
|
|
3016d3da11 | ||
|
|
daa560111c | ||
|
|
16705f68da | ||
|
|
109a980c29 | ||
|
|
531373cb84 | ||
|
|
087c4d49ed | ||
|
|
022b15dc1e | ||
|
|
454f70a19f | ||
|
|
98f05a52a8 | ||
|
|
5ff9f28a83 | ||
|
|
4a88220ffe | ||
|
|
4c2d0e6345 | ||
|
|
7710b1670e |
@@ -1,7 +1,9 @@
|
|||||||
[run]
|
[run]
|
||||||
branch = True
|
branch = True
|
||||||
source = watcher
|
source = watcher
|
||||||
omit = watcher/tests/*,watcher/openstack/*
|
omit = watcher/tests/*
|
||||||
|
|
||||||
[report]
|
[report]
|
||||||
ignore_errors = True
|
ignore_errors = True
|
||||||
|
exclude_lines =
|
||||||
|
@abstract
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
..
|
||||||
|
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/
|
||||||
|
|
||||||
==========================
|
==========================
|
||||||
watcher Style Commandments
|
watcher Style Commandments
|
||||||
==========================
|
==========================
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
..
|
||||||
|
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/
|
||||||
|
|
||||||
=======
|
=======
|
||||||
Watcher
|
Watcher
|
||||||
=======
|
=======
|
||||||
|
|||||||
182
doc/source/architecture.rst
Normal file
182
doc/source/architecture.rst
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
..
|
||||||
|
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/
|
||||||
|
|
||||||
|
.. _architecture:
|
||||||
|
|
||||||
|
===================
|
||||||
|
System Architecture
|
||||||
|
===================
|
||||||
|
|
||||||
|
|
||||||
|
This page presents the current technical Architecture of the Watcher system.
|
||||||
|
|
||||||
|
.. _architecture_overview:
|
||||||
|
|
||||||
|
Overview
|
||||||
|
========
|
||||||
|
|
||||||
|
Below you will find a diagram, showing the main components of Watcher:
|
||||||
|
|
||||||
|
.. image:: ./images/architecture.svg
|
||||||
|
:width: 100%
|
||||||
|
|
||||||
|
|
||||||
|
.. _components_definition:
|
||||||
|
|
||||||
|
Components
|
||||||
|
==========
|
||||||
|
|
||||||
|
.. _amqp_bus_definition:
|
||||||
|
|
||||||
|
AMQP Bus
|
||||||
|
--------
|
||||||
|
|
||||||
|
The AMQP message bus handles asynchronous communications between the different
|
||||||
|
Watcher components.
|
||||||
|
|
||||||
|
.. _cluster_history_db_definition:
|
||||||
|
|
||||||
|
Cluster History Database
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
This component stores the data related to the
|
||||||
|
:ref:`Cluster History <cluster_history_definition>`.
|
||||||
|
|
||||||
|
It can potentially rely on any appropriate storage system (InfluxDB, OpenTSDB,
|
||||||
|
MongoDB,...) but will probably be more performant when using
|
||||||
|
`Time Series Databases <https://en.wikipedia.org/wiki/Time_series_database>`_
|
||||||
|
which are optimized for handling time series data, which are arrays of numbers
|
||||||
|
indexed by time (a datetime or a datetime range).
|
||||||
|
|
||||||
|
.. _watcher_api_definition:
|
||||||
|
|
||||||
|
Watcher API
|
||||||
|
-----------
|
||||||
|
|
||||||
|
This component implements the REST API provided by the Watcher system to the
|
||||||
|
external world.
|
||||||
|
|
||||||
|
It enables the :ref:`Administrator <administrator_definition>` of a
|
||||||
|
:ref:`Cluster <cluster_definition>` to control and monitor the Watcher system
|
||||||
|
via any interaction mechanism connected to this API:
|
||||||
|
|
||||||
|
- :ref:`CLI <watcher_cli_definition>`
|
||||||
|
- Horizon plugin
|
||||||
|
- Python SDK
|
||||||
|
|
||||||
|
You can also read the detailed description of `Watcher API`_.
|
||||||
|
|
||||||
|
.. _watcher_applier_definition:
|
||||||
|
|
||||||
|
Watcher Applier
|
||||||
|
---------------
|
||||||
|
|
||||||
|
This component is in charge of executing the
|
||||||
|
:ref:`Action Plan <action_plan_definition>` built by the
|
||||||
|
:ref:`Watcher Decision Engine <watcher_decision_engine_definition>`.
|
||||||
|
|
||||||
|
It connects to the :ref:`message bus <amqp_bus_definition>` and launches the
|
||||||
|
:ref:`Action Plan <action_plan_definition>` whenever a triggering message is
|
||||||
|
received on a dedicated AMQP queue.
|
||||||
|
|
||||||
|
The triggering message contains the Action Plan UUID.
|
||||||
|
|
||||||
|
It then gets the detailed information about the
|
||||||
|
:ref:`Action Plan <action_plan_definition>` from the
|
||||||
|
:ref:`Watcher Database <watcher_database_definition>` which contains the list
|
||||||
|
of :ref:`Actions <action_definition>` to launch.
|
||||||
|
|
||||||
|
It then loops on each :ref:`Action <action_definition>`, gets the associated
|
||||||
|
class and calls the execute() method of this class.
|
||||||
|
Most of the time, this method will first request a token to the Keystone API
|
||||||
|
and if it is allowed, sends a request to the REST API of the OpenStack service
|
||||||
|
which handles this kind of :ref:`atomic Action <action_definition>`.
|
||||||
|
|
||||||
|
Note that as soon as :ref:`Watcher Applier <watcher_applier_definition>` starts
|
||||||
|
handling a given :ref:`Action <action_definition>` from the list, a
|
||||||
|
notification message is sent on the :ref:`message bus <amqp_bus_definition>`
|
||||||
|
indicating that the state of the action has changed to **ONGOING**.
|
||||||
|
|
||||||
|
If the :ref:`Action <action_definition>` is successful,
|
||||||
|
the :ref:`Watcher Applier <watcher_applier_definition>` sends a notification
|
||||||
|
message on :ref:`the bus <amqp_bus_definition>` informing the other components
|
||||||
|
of this.
|
||||||
|
|
||||||
|
|
||||||
|
If the :ref:`Action <action_definition>` fails, the
|
||||||
|
:ref:`Watcher Applier <watcher_applier_definition>` tries to rollback to the
|
||||||
|
previous state of the :ref:`Managed resource <managed_resource_definition>`
|
||||||
|
(i.e. before the command was sent to the underlying OpenStack service).
|
||||||
|
|
||||||
|
.. _watcher_cli_definition:
|
||||||
|
|
||||||
|
Watcher CLI
|
||||||
|
-----------
|
||||||
|
|
||||||
|
The watcher command-line interface (CLI) can be used to interact with the
|
||||||
|
Watcher system in order to control it or to know its current status.
|
||||||
|
|
||||||
|
Please, read `the detailed documentation about Watcher CLI <https://factory.b-com.com/www/watcher/doc/python-watcherclient/>`_
|
||||||
|
|
||||||
|
.. _watcher_database_definition:
|
||||||
|
|
||||||
|
Watcher Database
|
||||||
|
----------------
|
||||||
|
|
||||||
|
This database stores all the Watcher domain objects which can be requested
|
||||||
|
by the :ref:`Watcher API <watcher_api_definition>` or the
|
||||||
|
:ref:`Watcher CLI <watcher_cli_definition>`:
|
||||||
|
|
||||||
|
- :ref:`Audit templates <audit_template_definition>`
|
||||||
|
- :ref:`Audits <audit_definition>`
|
||||||
|
- :ref:`Action plans <action_plan_definition>`
|
||||||
|
- :ref:`Actions <action_definition>`
|
||||||
|
- :ref:`Goals <goal_definition>`
|
||||||
|
|
||||||
|
The Watcher domain being here "*optimization of some resources provided by an
|
||||||
|
OpenStack system*".
|
||||||
|
|
||||||
|
.. _watcher_decision_engine_definition:
|
||||||
|
|
||||||
|
Watcher Decision Engine
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
This component is responsible for computing a set of potential optimization
|
||||||
|
:ref:`Actions <action_definition>` in order to fulfill
|
||||||
|
the :ref:`Goal <goal_definition>` of an :ref:`Audit <audit_definition>`.
|
||||||
|
|
||||||
|
It first reads the parameters of the :ref:`Audit <audit_definition>` from the
|
||||||
|
associated :ref:`Audit Template <audit_template_definition>` and knows the
|
||||||
|
:ref:`Goal <goal_definition>` to achieve.
|
||||||
|
|
||||||
|
It then selects the most appropriate :ref:`Strategy <strategy_definition>`
|
||||||
|
depending on how Watcher was configured for this :ref:`Goal <goal_definition>`.
|
||||||
|
|
||||||
|
The :ref:`Strategy <strategy_definition>` is then dynamically loaded (via
|
||||||
|
`stevedore <https://github.com/openstack/stevedore/>`_). The
|
||||||
|
:ref:`Watcher Decision Engine <watcher_decision_engine_definition>` calls the
|
||||||
|
**execute()** method of the :ref:`Strategy <strategy_definition>` class which
|
||||||
|
generates a set of :ref:`Actions <action_definition>`.
|
||||||
|
|
||||||
|
These :ref:`Actions <action_definition>` are scheduled in time by the
|
||||||
|
:ref:`Watcher Planner <watcher_planner_definition>` (i.e., it generates an
|
||||||
|
:ref:`Action Plan <action_plan_definition>`).
|
||||||
|
|
||||||
|
In order to compute the potential :ref:`Solution <solution_definition>` for the
|
||||||
|
Audit, the :ref:`Strategy <strategy_definition>` relies on two sets of data:
|
||||||
|
|
||||||
|
- the current state of the
|
||||||
|
:ref:`Managed resources <managed_resource_definition>`
|
||||||
|
(e.g., the data stored in the Nova database)
|
||||||
|
- the data stored in the
|
||||||
|
:ref:`Cluster History Database <cluster_history_db_definition>`
|
||||||
|
which provides information about the past of the
|
||||||
|
:ref:`Cluster <cluster_definition>`
|
||||||
|
|
||||||
|
So far, only one :ref:`Strategy <strategy_definition>` can be associated to a
|
||||||
|
given :ref:`Goal <goal_definition>` via the main Watcher configuration file.
|
||||||
|
|
||||||
|
.. _Watcher API: webapi/v1.html
|
||||||
@@ -1,3 +1,9 @@
|
|||||||
|
..
|
||||||
|
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/
|
||||||
|
|
||||||
.. _watcher-db-manage:
|
.. _watcher-db-manage:
|
||||||
|
|
||||||
=============
|
=============
|
||||||
@@ -54,7 +60,8 @@ Usage
|
|||||||
=====
|
=====
|
||||||
|
|
||||||
Options for the various :ref:`commands <db-manage_cmds>` for
|
Options for the various :ref:`commands <db-manage_cmds>` for
|
||||||
:command:`watcher-db-manage` are listed when the :option:`-h` or :option:`--help`
|
:command:`watcher-db-manage` are listed when the :option:`-h` or
|
||||||
|
:option:`--help`
|
||||||
option is used after the command.
|
option is used after the command.
|
||||||
|
|
||||||
For example::
|
For example::
|
||||||
@@ -81,8 +88,9 @@ If no configuration file is specified with the :option:`--config-file` option,
|
|||||||
Command Options
|
Command Options
|
||||||
===============
|
===============
|
||||||
|
|
||||||
:command:`watcher-db-manage` is given a command that tells the utility what actions
|
:command:`watcher-db-manage` is given a command that tells the utility
|
||||||
to perform. These commands can take arguments. Several commands are available:
|
what actions to perform.
|
||||||
|
These commands can take arguments. Several commands are available:
|
||||||
|
|
||||||
.. _create_schema:
|
.. _create_schema:
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,8 @@ extensions = [
|
|||||||
'sphinxcontrib.httpdomain',
|
'sphinxcontrib.httpdomain',
|
||||||
'sphinxcontrib.pecanwsme.rest',
|
'sphinxcontrib.pecanwsme.rest',
|
||||||
'wsmeext.sphinxext',
|
'wsmeext.sphinxext',
|
||||||
'oslosphinx'
|
'oslosphinx',
|
||||||
|
'watcher.doc',
|
||||||
]
|
]
|
||||||
|
|
||||||
wsme_protocols = ['restjson']
|
wsme_protocols = ['restjson']
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
..
|
..
|
||||||
|
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/
|
||||||
|
|
||||||
===================
|
===================
|
||||||
Configuring Watcher
|
Configuring Watcher
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
..
|
||||||
|
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/
|
||||||
|
|
||||||
==================
|
==================
|
||||||
Installing Watcher
|
Installing Watcher
|
||||||
==================
|
==================
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
.. _user-guide:
|
..
|
||||||
|
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/
|
||||||
|
|
||||||
|
.. _user-guide:
|
||||||
|
|
||||||
=================================
|
=================================
|
||||||
Welcome to the Watcher User Guide
|
Welcome to the Watcher User Guide
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
.. _architecture:
|
|
||||||
|
|
||||||
===================
|
|
||||||
System Architecture
|
|
||||||
===================
|
|
||||||
|
|
||||||
Please go to `Wiki Watcher Architecture <https://wiki.openstack.org/wiki/WatcherArchitecture>`_
|
|
||||||
|
|
||||||
.. _API service: ../webapi/v1.html
|
|
||||||
@@ -1,3 +1,9 @@
|
|||||||
|
..
|
||||||
|
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/
|
||||||
|
|
||||||
.. _contributing:
|
.. _contributing:
|
||||||
|
|
||||||
======================
|
======================
|
||||||
@@ -44,7 +50,7 @@ Bug tracker
|
|||||||
|
|
||||||
Mailing list (prefix subjects with ``[watcher]`` for faster responses)
|
Mailing list (prefix subjects with ``[watcher]`` for faster responses)
|
||||||
http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-dev
|
http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-dev
|
||||||
|
|
||||||
Wiki
|
Wiki
|
||||||
http://wiki.openstack.org/Watcher
|
http://wiki.openstack.org/Watcher
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
..
|
..
|
||||||
|
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/
|
||||||
|
|
||||||
============================================
|
============================================
|
||||||
Setting up a Watcher development environment
|
Setting up a Watcher development environment
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
..
|
||||||
|
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/
|
||||||
|
|
||||||
===============
|
===============
|
||||||
Watcher plugins
|
Watcher plugins
|
||||||
===============
|
===============
|
||||||
@@ -45,7 +51,7 @@ Here is an example showing how you can write a plugin called ``DummyStrategy``:
|
|||||||
|
|
||||||
def execute(self, model):
|
def execute(self, model):
|
||||||
self.solution.add_change_request(
|
self.solution.add_change_request(
|
||||||
Migrate(vm=my_vm, source_hypervisor=src, dest_hypervisor=dest)
|
Migrate(vm=my_vm, src_hypervisor=src, dest_hypervisor=dest)
|
||||||
)
|
)
|
||||||
# Do some more stuff here ...
|
# Do some more stuff here ...
|
||||||
return self.solution
|
return self.solution
|
||||||
|
|||||||
@@ -1,15 +1,8 @@
|
|||||||
..
|
..
|
||||||
Licensed under the Apache License, Version 2.0 (the "License"); you may
|
Except where otherwise noted, this document is licensed under Creative
|
||||||
not use this file except in compliance with the License. You may obtain
|
Commons Attribution 3.0 License. You can view the license at:
|
||||||
a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
https://creativecommons.org/licenses/by/3.0/
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
License for the specific language governing permissions and limitations
|
|
||||||
under the License.
|
|
||||||
|
|
||||||
==========
|
==========
|
||||||
Glossary
|
Glossary
|
||||||
@@ -41,7 +34,8 @@ of the OpenStack :ref:`Cluster <cluster_definition>` such as:
|
|||||||
- Changing the current state of an hypervisor (enable or disable) with Nova
|
- Changing the current state of an hypervisor (enable or disable) with Nova
|
||||||
|
|
||||||
In most cases, an :ref:`Action <action_definition>` triggers some concrete
|
In most cases, an :ref:`Action <action_definition>` triggers some concrete
|
||||||
commands on an existing OpenStack module (Nova, Neutron, Cinder, Ironic, etc.).
|
commands on an existing OpenStack module (Nova, Neutron, Cinder, Ironic, etc.)
|
||||||
|
via a :ref:`Primitive <primitive_definition>`.
|
||||||
|
|
||||||
An :ref:`Action <action_definition>` has a life-cycle and its current state may
|
An :ref:`Action <action_definition>` has a life-cycle and its current state may
|
||||||
be one of the following:
|
be one of the following:
|
||||||
@@ -71,23 +65,27 @@ An :ref:`Action Plan <action_plan_definition>` is a flow of
|
|||||||
a given :ref:`Goal <goal_definition>`.
|
a given :ref:`Goal <goal_definition>`.
|
||||||
|
|
||||||
An :ref:`Action Plan <action_plan_definition>` is generated by Watcher when an
|
An :ref:`Action Plan <action_plan_definition>` is generated by Watcher when an
|
||||||
:ref:`Audit <audit_definition>` is successful which implies that the :ref:`Strategy <strategy_definition>`
|
:ref:`Audit <audit_definition>` is successful which implies that the
|
||||||
|
:ref:`Strategy <strategy_definition>`
|
||||||
which was used has found a :ref:`Solution <solution_definition>` to achieve the
|
which was used has found a :ref:`Solution <solution_definition>` to achieve the
|
||||||
:ref:`Goal <goal_definition>` of this :ref:`Audit <audit_definition>`.
|
:ref:`Goal <goal_definition>` of this :ref:`Audit <audit_definition>`.
|
||||||
|
|
||||||
In the default implementation of Watcher, an :ref:`Action Plan <action_plan_definition>`
|
In the default implementation of Watcher, an
|
||||||
|
:ref:`Action Plan <action_plan_definition>`
|
||||||
is only composed of successive :ref:`Actions <action_definition>`
|
is only composed of successive :ref:`Actions <action_definition>`
|
||||||
(i.e., a Workflow of :ref:`Actions <action_definition>` belonging to a unique
|
(i.e., a Workflow of :ref:`Actions <action_definition>` belonging to a unique
|
||||||
branch).
|
branch).
|
||||||
|
|
||||||
However, Watcher provides abstract interfaces for many of its components,
|
However, Watcher provides abstract interfaces for many of its components,
|
||||||
allowing other implementations to generate and handle more complex :ref:`Action Plan(s) <action_plan_definition>`
|
allowing other implementations to generate and handle more complex
|
||||||
|
:ref:`Action Plan(s) <action_plan_definition>`
|
||||||
composed of two types of Action Item(s):
|
composed of two types of Action Item(s):
|
||||||
|
|
||||||
- simple :ref:`Actions <action_definition>`: atomic tasks, which means it
|
- simple :ref:`Actions <action_definition>`: atomic tasks, which means it
|
||||||
can not be split into smaller tasks or commands from an OpenStack point of
|
can not be split into smaller tasks or commands from an OpenStack point of
|
||||||
view.
|
view.
|
||||||
- composite Actions: which are composed of several simple :ref:`Actions <action_definition>`
|
- composite Actions: which are composed of several simple
|
||||||
|
:ref:`Actions <action_definition>`
|
||||||
ordered in sequential and/or parallel flows.
|
ordered in sequential and/or parallel flows.
|
||||||
|
|
||||||
An :ref:`Action Plan <action_plan_definition>` may be described using
|
An :ref:`Action Plan <action_plan_definition>` may be described using
|
||||||
@@ -134,7 +132,8 @@ is a role for users which allows them to run any Watcher commands, such as:
|
|||||||
- Launch an :ref:`Audit <audit_definition>`
|
- Launch an :ref:`Audit <audit_definition>`
|
||||||
- Get the :ref:`Action Plan <action_plan_definition>`
|
- Get the :ref:`Action Plan <action_plan_definition>`
|
||||||
- Launch a recommended :ref:`Action Plan <action_plan_definition>` manually
|
- Launch a recommended :ref:`Action Plan <action_plan_definition>` manually
|
||||||
- Archive previous :ref:`Audits <audit_definition>` and :ref:`Action Plans <action_plan_definition>`
|
- Archive previous :ref:`Audits <audit_definition>` and
|
||||||
|
:ref:`Action Plans <action_plan_definition>`
|
||||||
|
|
||||||
|
|
||||||
The :ref:`Administrator <administrator_definition>` is also allowed to modify
|
The :ref:`Administrator <administrator_definition>` is also allowed to modify
|
||||||
@@ -163,7 +162,8 @@ be one of the following:
|
|||||||
event handling mechanism) and is in the queue for being processed by the
|
event handling mechanism) and is in the queue for being processed by the
|
||||||
:ref:`Watcher Decision Engine <watcher_decision_engine_definition>`
|
:ref:`Watcher Decision Engine <watcher_decision_engine_definition>`
|
||||||
- **ONGOING** : the :ref:`Audit <audit_definition>` is currently being
|
- **ONGOING** : the :ref:`Audit <audit_definition>` is currently being
|
||||||
processed by the :ref:`Watcher Decision Engine <watcher_decision_engine_definition>`
|
processed by the
|
||||||
|
:ref:`Watcher Decision Engine <watcher_decision_engine_definition>`
|
||||||
- **SUCCEEDED** : the :ref:`Audit <audit_definition>` has been executed
|
- **SUCCEEDED** : the :ref:`Audit <audit_definition>` has been executed
|
||||||
successfully (note that it may not necessarily produce a
|
successfully (note that it may not necessarily produce a
|
||||||
:ref:`Solution <solution_definition>`).
|
:ref:`Solution <solution_definition>`).
|
||||||
@@ -191,7 +191,8 @@ An :ref:`Audit Template <audit_template_definition>` contains at least the
|
|||||||
|
|
||||||
It may also contain some error handling settings indicating whether:
|
It may also contain some error handling settings indicating whether:
|
||||||
|
|
||||||
- :ref:`Watcher Applier <watcher_applier_definition>` stops the entire operation
|
- :ref:`Watcher Applier <watcher_applier_definition>` stops the
|
||||||
|
entire operation
|
||||||
- :ref:`Watcher Applier <watcher_applier_definition>` performs a rollback
|
- :ref:`Watcher Applier <watcher_applier_definition>` performs a rollback
|
||||||
|
|
||||||
and how many retries should be attempted before failure occurs (also the latter
|
and how many retries should be attempted before failure occurs (also the latter
|
||||||
@@ -241,10 +242,12 @@ Cluster Data Model
|
|||||||
==================
|
==================
|
||||||
|
|
||||||
A :ref:`Cluster Data Model <cluster_data_model_definition>` is a logical
|
A :ref:`Cluster Data Model <cluster_data_model_definition>` is a logical
|
||||||
representation of the current state and topology of the :ref:`Cluster <cluster_definition>`
|
representation of the current state and topology of the
|
||||||
|
:ref:`Cluster <cluster_definition>`
|
||||||
:ref:`Managed resources <managed_resource_definition>`.
|
:ref:`Managed resources <managed_resource_definition>`.
|
||||||
|
|
||||||
It is represented as a set of :ref:`Managed resources <managed_resource_definition>`
|
It is represented as a set of
|
||||||
|
:ref:`Managed resources <managed_resource_definition>`
|
||||||
(which may be a simple tree or a flat list of key-value pairs)
|
(which may be a simple tree or a flat list of key-value pairs)
|
||||||
which enables Watcher :ref:`Strategies <strategy_definition>` to know the
|
which enables Watcher :ref:`Strategies <strategy_definition>` to know the
|
||||||
current relationships between the different
|
current relationships between the different
|
||||||
@@ -253,7 +256,8 @@ current relationships between the different
|
|||||||
and enables the :ref:`Strategy <strategy_definition>` to request information
|
and enables the :ref:`Strategy <strategy_definition>` to request information
|
||||||
such as:
|
such as:
|
||||||
|
|
||||||
- What compute nodes are in a given :ref:`Availability Zone <availability_zone_definition>`
|
- What compute nodes are in a given
|
||||||
|
:ref:`Availability Zone <availability_zone_definition>`
|
||||||
or a given :ref:`Host Aggregate <host_aggregates_definition>` ?
|
or a given :ref:`Host Aggregate <host_aggregates_definition>` ?
|
||||||
- What :ref:`Instances <instance_definition>` are hosted on a given compute
|
- What :ref:`Instances <instance_definition>` are hosted on a given compute
|
||||||
node ?
|
node ?
|
||||||
@@ -263,16 +267,19 @@ such as:
|
|||||||
- What is the available bandwidth on a given network link ?
|
- What is the available bandwidth on a given network link ?
|
||||||
- What is the current space available on a given virtual disk of a given
|
- What is the current space available on a given virtual disk of a given
|
||||||
:ref:`Instance <instance_definition>` ?
|
:ref:`Instance <instance_definition>` ?
|
||||||
- What is the current state of a given :ref:`Instance <instance_definition>` ?
|
- What is the current state of a given :ref:`Instance <instance_definition>`?
|
||||||
- ...
|
- ...
|
||||||
|
|
||||||
In a word, this data model enables the :ref:`Strategy <strategy_definition>`
|
In a word, this data model enables the :ref:`Strategy <strategy_definition>`
|
||||||
to know:
|
to know:
|
||||||
|
|
||||||
- the current topology of the :ref:`Cluster <cluster_definition>`
|
- the current topology of the :ref:`Cluster <cluster_definition>`
|
||||||
- the current capacity for each :ref:`Managed resource <managed_resource_definition>`
|
- the current capacity for each
|
||||||
- the current amount of used/free space for each :ref:`Managed resource <managed_resource_definition>`
|
:ref:`Managed resource <managed_resource_definition>`
|
||||||
- the current state of each :ref:`Managed resources <managed_resource_definition>`
|
- the current amount of used/free space for each
|
||||||
|
:ref:`Managed resource <managed_resource_definition>`
|
||||||
|
- the current state of each
|
||||||
|
:ref:`Managed resources <managed_resource_definition>`
|
||||||
|
|
||||||
In the Watcher project, we aim at providing a generic and very basic
|
In the Watcher project, we aim at providing a generic and very basic
|
||||||
:ref:`Cluster Data Model <cluster_data_model_definition>` for each
|
:ref:`Cluster Data Model <cluster_data_model_definition>` for each
|
||||||
@@ -295,7 +302,8 @@ to:
|
|||||||
:ref:`Cluster Data Model <cluster_data_model_definition>`
|
:ref:`Cluster Data Model <cluster_data_model_definition>`
|
||||||
(the proposed data model acts as a pivot data model)
|
(the proposed data model acts as a pivot data model)
|
||||||
|
|
||||||
There may be various :ref:`generic and basic Cluster Data Models <cluster_data_model_definition>`
|
There may be various
|
||||||
|
:ref:`generic and basic Cluster Data Models <cluster_data_model_definition>`
|
||||||
proposed in Watcher helpers, each of them being adapted to achieving a given
|
proposed in Watcher helpers, each of them being adapted to achieving a given
|
||||||
:ref:`Goal <goal_definition>`:
|
:ref:`Goal <goal_definition>`:
|
||||||
|
|
||||||
@@ -311,8 +319,10 @@ proposed in Watcher helpers, each of them being adapted to achieving a given
|
|||||||
|
|
||||||
Note however that a developer can use his/her own
|
Note however that a developer can use his/her own
|
||||||
:ref:`Cluster Data Model <cluster_data_model_definition>` if the proposed data
|
:ref:`Cluster Data Model <cluster_data_model_definition>` if the proposed data
|
||||||
model does not fit his/her needs as long as the :ref:`Strategy <strategy_definition>`
|
model does not fit his/her needs as long as the
|
||||||
is able to produce a :ref:`Solution <solution_definition>` for the requested :ref:`Goal <goal_definition>`.
|
:ref:`Strategy <strategy_definition>` is able to produce a
|
||||||
|
:ref:`Solution <solution_definition>` for the requested
|
||||||
|
:ref:`Goal <goal_definition>`.
|
||||||
For example, a developer could rely on the Nova Data Model to optimize some
|
For example, a developer could rely on the Nova Data Model to optimize some
|
||||||
compute resources.
|
compute resources.
|
||||||
|
|
||||||
@@ -335,16 +345,21 @@ history may be used by any :ref:`Strategy <strategy_definition>` in order to
|
|||||||
find the most optimal :ref:`Solution <solution_definition>` during an
|
find the most optimal :ref:`Solution <solution_definition>` during an
|
||||||
:ref:`Audit <audit_definition>`.
|
:ref:`Audit <audit_definition>`.
|
||||||
|
|
||||||
In the Watcher project, a generic :ref:`Cluster History <cluster_history_definition>`
|
In the Watcher project, a generic
|
||||||
|
:ref:`Cluster History <cluster_history_definition>`
|
||||||
API is proposed with some helper classes in order to :
|
API is proposed with some helper classes in order to :
|
||||||
|
|
||||||
- share a common measurement (events or metrics) naming based on what is
|
- share a common measurement (events or metrics) naming based on what is
|
||||||
defined in Ceilometer. See `the full list of available measurements <http://docs.openstack.org/admin-guide-cloud/telemetry-measurements.html>`_
|
defined in Ceilometer.
|
||||||
|
See `the full list of available measurements <http://docs.openstack.org/admin-guide-cloud/telemetry-measurements.html>`_
|
||||||
- share common meter types (Cumulative, Delta, Gauge) based on what is
|
- share common meter types (Cumulative, Delta, Gauge) based on what is
|
||||||
defined in Ceilometer. See `the full list of meter types <http://docs.openstack.org/admin-guide-cloud/telemetry-measurements.html>`_
|
defined in Ceilometer.
|
||||||
|
See `the full list of meter types <http://docs.openstack.org/admin-guide-cloud/telemetry-measurements.html>`_
|
||||||
- simplify the development of a new :ref:`Strategy <strategy_definition>`
|
- simplify the development of a new :ref:`Strategy <strategy_definition>`
|
||||||
- avoid duplicating the same code in several :ref:`Strategies <strategy_definition>`
|
- avoid duplicating the same code in several
|
||||||
- have a better consistency between the different :ref:`Strategies <strategy_definition>`
|
:ref:`Strategies <strategy_definition>`
|
||||||
|
- have a better consistency between the different
|
||||||
|
:ref:`Strategies <strategy_definition>`
|
||||||
- avoid any strong coupling with any external metrics/events storage system
|
- avoid any strong coupling with any external metrics/events storage system
|
||||||
(the proposed API and measurement naming system acts as a pivot format)
|
(the proposed API and measurement naming system acts as a pivot format)
|
||||||
|
|
||||||
@@ -369,8 +384,8 @@ services:
|
|||||||
- Cinder scheduler: for volumes management
|
- Cinder scheduler: for volumes management
|
||||||
- Glance controller: for image management
|
- Glance controller: for image management
|
||||||
- Neutron controller: for network management
|
- Neutron controller: for network management
|
||||||
- Nova controller: for global compute resources management with services such as
|
- Nova controller: for global compute resources management with services
|
||||||
nova-scheduler, nova-conductor and nova-network
|
such as nova-scheduler, nova-conductor and nova-network.
|
||||||
|
|
||||||
In many configurations, Watcher will reside on a controller node even if it
|
In many configurations, Watcher will reside on a controller node even if it
|
||||||
can potentially be hosted on a dedicated machine.
|
can potentially be hosted on a dedicated machine.
|
||||||
@@ -388,7 +403,8 @@ Customer
|
|||||||
========
|
========
|
||||||
|
|
||||||
A :ref:`Customer <customer_definition>` is the person or company which
|
A :ref:`Customer <customer_definition>` is the person or company which
|
||||||
subscribes to the cloud provider offering. A customer may have several :ref:`Project(s) <project_definition>`
|
subscribes to the cloud provider offering. A customer may have several
|
||||||
|
:ref:`Project(s) <project_definition>`
|
||||||
hosted on the same :ref:`Cluster <cluster_definition>` or dispatched on
|
hosted on the same :ref:`Cluster <cluster_definition>` or dispatched on
|
||||||
different clusters.
|
different clusters.
|
||||||
|
|
||||||
@@ -484,11 +500,12 @@ measure of how much of the :ref:`Goal <goal_definition>` has been achieved in
|
|||||||
respect with constraints and :ref:`SLAs <sla_definition>` defined by the
|
respect with constraints and :ref:`SLAs <sla_definition>` defined by the
|
||||||
:ref:`Customer <customer_definition>`.
|
:ref:`Customer <customer_definition>`.
|
||||||
|
|
||||||
The way efficiency is evaluated will depend on the :ref:`Goal <goal_definition>`
|
The way efficiency is evaluated will depend on the
|
||||||
to achieve.
|
:ref:`Goal <goal_definition>` to achieve.
|
||||||
|
|
||||||
Of course, the efficiency will be relevant only as long as the :ref:`Action Plan <action_plan_definition>`
|
Of course, the efficiency will be relevant only as long as the
|
||||||
is relevant (i.e., the current state of the :ref:`Cluster <cluster_definition>`
|
:ref:`Action Plan <action_plan_definition>` is relevant
|
||||||
|
(i.e., the current state of the :ref:`Cluster <cluster_definition>`
|
||||||
has not changed in a way that a new :ref:`Audit <audit_definition>` would need
|
has not changed in a way that a new :ref:`Audit <audit_definition>` would need
|
||||||
to be launched).
|
to be launched).
|
||||||
|
|
||||||
@@ -521,6 +538,24 @@ specific domain.
|
|||||||
|
|
||||||
Please, read `the official OpenStack definition of a Project <http://docs.openstack.org/glossary/content/glossary.html>`_.
|
Please, read `the official OpenStack definition of a Project <http://docs.openstack.org/glossary/content/glossary.html>`_.
|
||||||
|
|
||||||
|
|
||||||
|
.. _primitive_definition
|
||||||
|
|
||||||
|
Primitive
|
||||||
|
=========
|
||||||
|
|
||||||
|
A :ref:`Primitive <primitive_definition>` is the component that carries out a
|
||||||
|
certain type of atomic :ref:`Actions <action_definition>` on a given
|
||||||
|
:ref:`Managed resource <managed_resource_definition>` (nova, swift, neutron,
|
||||||
|
glance,..). A :ref:`Primitive <primitive_definition>` is a part of the
|
||||||
|
:ref:`Watcher Applier <watcher_applier_definition>` module.
|
||||||
|
|
||||||
|
For example, there can be a :ref:`Primitive <primitive_definition>` which is
|
||||||
|
responsible for creating a snapshot of a given instance on a Nova compute node.
|
||||||
|
This :ref:`Primitive <primitive_definition>` knows exactly how to send
|
||||||
|
the appropriate commands to Nova for this type of
|
||||||
|
:ref:`Actions <action_definition>`.
|
||||||
|
|
||||||
.. _sla_definition:
|
.. _sla_definition:
|
||||||
|
|
||||||
SLA
|
SLA
|
||||||
@@ -551,21 +586,21 @@ which provides a good definition.
|
|||||||
SLA violation
|
SLA violation
|
||||||
=============
|
=============
|
||||||
|
|
||||||
A :ref:`SLA violation <sla_violation_definition>` happens when a :ref:`SLA <sla_definition>`
|
A :ref:`SLA violation <sla_violation_definition>` happens when a
|
||||||
defined with a given :ref:`Customer <customer_definition>` could not be
|
:ref:`SLA <sla_definition>` defined with a given
|
||||||
respected by the cloud provider within the timeframe defined by the official
|
:ref:`Customer <customer_definition>` could not be respected by the
|
||||||
contract document.
|
cloud provider within the timeframe defined by the official contract document.
|
||||||
|
|
||||||
.. _slo_definition:
|
.. _slo_definition:
|
||||||
|
|
||||||
SLO
|
SLO
|
||||||
===
|
===
|
||||||
|
|
||||||
A Service Level Objective (SLO) is a key element of a :ref:`SLA <sla_definition>`
|
A Service Level Objective (SLO) is a key element of a
|
||||||
between a service provider and a :ref:`Customer <customer_definition>`. SLOs
|
:ref:`SLA <sla_definition>` between a service provider and a
|
||||||
are agreed as a means of measuring the performance of the Service Provider and
|
:ref:`Customer <customer_definition>`. SLOs are agreed as a means of measuring
|
||||||
are outlined as a way of avoiding disputes between the two parties based on
|
the performance of the Service Provider and are outlined as a way of avoiding
|
||||||
misunderstanding.
|
disputes between the two parties based on misunderstanding.
|
||||||
|
|
||||||
You can also read `the Wikipedia page for SLO <https://en.wikipedia.org/wiki/Service_level_objective>`_
|
You can also read `the Wikipedia page for SLO <https://en.wikipedia.org/wiki/Service_level_objective>`_
|
||||||
which provides a good definition.
|
which provides a good definition.
|
||||||
@@ -575,9 +610,10 @@ which provides a good definition.
|
|||||||
Solution
|
Solution
|
||||||
========
|
========
|
||||||
|
|
||||||
A :ref:`Solution <solution_definition>` is a set of :ref:`Actions <action_definition>`
|
A :ref:`Solution <solution_definition>` is a set of
|
||||||
generated by a :ref:`Strategy <strategy_definition>` (i.e., an algorithm) in
|
:ref:`Actions <action_definition>` generated by a
|
||||||
order to achieve the :ref:`Goal <goal_definition>` of an :ref:`Audit <audit_definition>`.
|
:ref:`Strategy <strategy_definition>` (i.e., an algorithm) in order to achieve
|
||||||
|
the :ref:`Goal <goal_definition>` of an :ref:`Audit <audit_definition>`.
|
||||||
|
|
||||||
A :ref:`Solution <solution_definition>` is different from an
|
A :ref:`Solution <solution_definition>` is different from an
|
||||||
:ref:`Action Plan <action_plan_definition>` because it contains the
|
:ref:`Action Plan <action_plan_definition>` because it contains the
|
||||||
@@ -593,8 +629,8 @@ applied.
|
|||||||
|
|
||||||
Two approaches to dealing with this can be envisaged:
|
Two approaches to dealing with this can be envisaged:
|
||||||
|
|
||||||
- **fully automated mode**: only the :ref:`Solution <solution_definition>` with
|
- **fully automated mode**: only the :ref:`Solution <solution_definition>`
|
||||||
the highest ranking (i.e., the highest
|
with the highest ranking (i.e., the highest
|
||||||
:ref:`Optimization Efficiency <efficiency_definition>`)
|
:ref:`Optimization Efficiency <efficiency_definition>`)
|
||||||
will be sent to the :ref:`Watcher Planner <watcher_planner_definition>` and
|
will be sent to the :ref:`Watcher Planner <watcher_planner_definition>` and
|
||||||
translated into concrete :ref:`Actions <action_definition>`.
|
translated into concrete :ref:`Actions <action_definition>`.
|
||||||
@@ -610,11 +646,13 @@ Strategy
|
|||||||
========
|
========
|
||||||
|
|
||||||
A :ref:`Strategy <strategy_definition>` is an algorithm implementation which is
|
A :ref:`Strategy <strategy_definition>` is an algorithm implementation which is
|
||||||
able to find a :ref:`Solution <solution_definition>` for a given :ref:`Goal <goal_definition>`.
|
able to find a :ref:`Solution <solution_definition>` for a given
|
||||||
|
:ref:`Goal <goal_definition>`.
|
||||||
|
|
||||||
There may be several potential strategies which are able to achieve the same
|
There may be several potential strategies which are able to achieve the same
|
||||||
:ref:`Goal <goal_definition>`. This is why it is possible to configure which
|
:ref:`Goal <goal_definition>`. This is why it is possible to configure which
|
||||||
specific :ref:`Strategy <strategy_definition>` should be used for each :ref:`Goal <goal_definition>`.
|
specific :ref:`Strategy <strategy_definition>` should be used for each
|
||||||
|
:ref:`Goal <goal_definition>`.
|
||||||
|
|
||||||
Some strategies may provide better optimization results but may take more time
|
Some strategies may provide better optimization results but may take more time
|
||||||
to find an optimal :ref:`Solution <solution_definition>`.
|
to find an optimal :ref:`Solution <solution_definition>`.
|
||||||
@@ -628,8 +666,9 @@ provided as well.
|
|||||||
Watcher Applier
|
Watcher Applier
|
||||||
===============
|
===============
|
||||||
|
|
||||||
This component is in charge of executing the :ref:`Action Plan <action_plan_definition>`
|
This component is in charge of executing the
|
||||||
built by the :ref:`Watcher Decision Engine <watcher_decision_engine_definition>`.
|
:ref:`Action Plan <action_plan_definition>` built by the
|
||||||
|
:ref:`Watcher Decision Engine <watcher_decision_engine_definition>`.
|
||||||
|
|
||||||
See :doc:`architecture` for more details on this component.
|
See :doc:`architecture` for more details on this component.
|
||||||
|
|
||||||
@@ -658,8 +697,8 @@ Watcher Decision Engine
|
|||||||
=======================
|
=======================
|
||||||
|
|
||||||
This component is responsible for computing a set of potential optimization
|
This component is responsible for computing a set of potential optimization
|
||||||
:ref:`Actions <action_definition>` in order to fulfill the :ref:`Goal <goal_definition>`
|
:ref:`Actions <action_definition>` in order to fulfill the
|
||||||
of an :ref:`Audit <audit_definition>`.
|
:ref:`Goal <goal_definition>` of an :ref:`Audit <audit_definition>`.
|
||||||
|
|
||||||
It first reads the parameters of the :ref:`Audit <audit_definition>` from the
|
It first reads the parameters of the :ref:`Audit <audit_definition>` from the
|
||||||
associated :ref:`Audit Template <audit_template_definition>` and knows the
|
associated :ref:`Audit Template <audit_template_definition>` and knows the
|
||||||
BIN
doc/source/image_src/architecture.dia
Normal file
BIN
doc/source/image_src/architecture.dia
Normal file
Binary file not shown.
275
doc/source/images/architecture.svg
Normal file
275
doc/source/images/architecture.svg
Normal file
@@ -0,0 +1,275 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/PR-SVG-20010719/DTD/svg10.dtd">
|
||||||
|
<svg width="60cm" height="27cm" viewBox="-181 -239 1196 535" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<g>
|
||||||
|
<rect style="fill: #b3d6c6" x="-169.614" y="-237" width="1173" height="532"/>
|
||||||
|
<path style="fill: #b3d6c6" d="M -169.614,-237 A 10,10 0 0 0 -179.614,-227 L -169.614,-227 z"/>
|
||||||
|
<path style="fill: #b3d6c6" d="M 1013.39,-227 A 10,10 0 0 0 1003.39,-237 L 1003.39,-227 z"/>
|
||||||
|
<rect style="fill: #b3d6c6" x="-179.614" y="-227" width="1193" height="512"/>
|
||||||
|
<path style="fill: #b3d6c6" d="M -179.614,285 A 10,10 0 0 0 -169.614,295 L -169.614,285 z"/>
|
||||||
|
<path style="fill: #b3d6c6" d="M 1003.39,295 A 10,10 0 0 0 1013.39,285 L 1003.39,285 z"/>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #ffffff" x1="-169.614" y1="-237" x2="1003.39" y2="-237"/>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #ffffff" x1="-169.614" y1="295" x2="1003.39" y2="295"/>
|
||||||
|
<path style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #ffffff" d="M -169.614,-237 A 10,10 0 0 0 -179.614,-227"/>
|
||||||
|
<path style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #ffffff" d="M 1013.39,-227 A 10,10 0 0 0 1003.39,-237"/>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #ffffff" x1="-179.614" y1="-227" x2="-179.614" y2="285"/>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #ffffff" x1="1013.39" y1="-227" x2="1013.39" y2="285"/>
|
||||||
|
<path style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #ffffff" d="M -179.614,285 A 10,10 0 0 0 -169.614,295"/>
|
||||||
|
<path style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #ffffff" d="M 1003.39,295 A 10,10 0 0 0 1013.39,285"/>
|
||||||
|
<text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="416.886" y="32.9002">
|
||||||
|
<tspan x="416.886" y="32.9002"></tspan>
|
||||||
|
</text>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<rect style="fill: #00cd78" x="244.1" y="-158" width="82.287" height="260"/>
|
||||||
|
<path style="fill: #00cd78" d="M 244.1,-158 A 10,10 0 0 0 234.1,-148 L 244.1,-148 z"/>
|
||||||
|
<path style="fill: #00cd78" d="M 336.387,-148 A 10,10 0 0 0 326.387,-158 L 326.387,-148 z"/>
|
||||||
|
<rect style="fill: #00cd78" x="234.1" y="-148" width="102.287" height="240"/>
|
||||||
|
<path style="fill: #00cd78" d="M 234.1,92 A 10,10 0 0 0 244.1,102 L 244.1,92 z"/>
|
||||||
|
<path style="fill: #00cd78" d="M 326.387,102 A 10,10 0 0 0 336.387,92 L 326.387,92 z"/>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="244.1" y1="-158" x2="326.387" y2="-158"/>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="244.1" y1="102" x2="326.387" y2="102"/>
|
||||||
|
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 244.1,-158 A 10,10 0 0 0 234.1,-148"/>
|
||||||
|
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 336.387,-148 A 10,10 0 0 0 326.387,-158"/>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="234.1" y1="-148" x2="234.1" y2="92"/>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="336.387" y1="-148" x2="336.387" y2="92"/>
|
||||||
|
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 234.1,92 A 10,10 0 0 0 244.1,102"/>
|
||||||
|
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 326.387,102 A 10,10 0 0 0 336.387,92"/>
|
||||||
|
<text font-size="12.8" style="fill: #ffffff;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="285.243" y="-32.1">
|
||||||
|
<tspan x="285.243" y="-32.1">AMQP</tspan>
|
||||||
|
<tspan x="285.243" y="-16.1">bus</tspan>
|
||||||
|
</text>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<rect style="fill: #ffb400" x="407" y="-0.832" width="162.3" height="89"/>
|
||||||
|
<path style="fill: #ffb400" d="M 407,-0.832 A 10,10 0 0 0 397,9.168 L 407,9.168 z"/>
|
||||||
|
<path style="fill: #ffb400" d="M 579.3,9.168 A 10,10 0 0 0 569.3,-0.832 L 569.3,9.168 z"/>
|
||||||
|
<rect style="fill: #ffb400" x="397" y="9.168" width="182.3" height="69"/>
|
||||||
|
<path style="fill: #ffb400" d="M 397,78.168 A 10,10 0 0 0 407,88.168 L 407,78.168 z"/>
|
||||||
|
<path style="fill: #ffb400" d="M 569.3,88.168 A 10,10 0 0 0 579.3,78.168 L 569.3,78.168 z"/>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="407" y1="-0.832" x2="569.3" y2="-0.832"/>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="407" y1="88.168" x2="569.3" y2="88.168"/>
|
||||||
|
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 407,-0.832 A 10,10 0 0 0 397,9.168"/>
|
||||||
|
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 579.3,9.168 A 10,10 0 0 0 569.3,-0.832"/>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="397" y1="9.168" x2="397" y2="78.168"/>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="579.3" y1="9.168" x2="579.3" y2="78.168"/>
|
||||||
|
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 397,78.168 A 10,10 0 0 0 407,88.168"/>
|
||||||
|
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 569.3,88.168 A 10,10 0 0 0 579.3,78.168"/>
|
||||||
|
<text font-size="14.1111" style="fill: #ffffff;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="488.15" y="39.1291">
|
||||||
|
<tspan x="488.15" y="39.1291">Watcher</tspan>
|
||||||
|
<tspan x="488.15" y="56.768">Decision Engine</tspan>
|
||||||
|
</text>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="341.176" y1="21" x2="387.264" y2="21.3451"/>
|
||||||
|
<polygon style="fill: #000000" points="394.764,21.4013 384.727,26.3262 387.264,21.3451 384.802,16.3265 "/>
|
||||||
|
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="394.764,21.4013 384.727,26.3262 387.264,21.3451 384.802,16.3265 "/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke-dasharray: 6; stroke: #000000" x1="349.122" y1="65.5706" x2="387.264" y2="65.8474"/>
|
||||||
|
<polygon style="fill: #000000" points="341.622,65.5162 351.658,60.5889 349.122,65.5706 351.585,70.5886 "/>
|
||||||
|
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="341.622,65.5162 351.658,60.5889 349.122,65.5706 351.585,70.5886 "/>
|
||||||
|
<polygon style="fill: #000000" points="394.764,65.9018 384.728,70.8291 387.264,65.8474 384.801,60.8294 "/>
|
||||||
|
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="394.764,65.9018 384.728,70.8291 387.264,65.8474 384.801,60.8294 "/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<rect style="fill: #ffb400" x="407" y="-159" width="160" height="89"/>
|
||||||
|
<path style="fill: #ffb400" d="M 407,-159 A 10,10 0 0 0 397,-149 L 407,-149 z"/>
|
||||||
|
<path style="fill: #ffb400" d="M 577,-149 A 10,10 0 0 0 567,-159 L 567,-149 z"/>
|
||||||
|
<rect style="fill: #ffb400" x="397" y="-149" width="180" height="69"/>
|
||||||
|
<path style="fill: #ffb400" d="M 397,-80 A 10,10 0 0 0 407,-70 L 407,-80 z"/>
|
||||||
|
<path style="fill: #ffb400" d="M 567,-70 A 10,10 0 0 0 577,-80 L 567,-80 z"/>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="407" y1="-159" x2="567" y2="-159"/>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="407" y1="-70" x2="567" y2="-70"/>
|
||||||
|
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 407,-159 A 10,10 0 0 0 397,-149"/>
|
||||||
|
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 577,-149 A 10,10 0 0 0 567,-159"/>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="397" y1="-149" x2="397" y2="-80"/>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="577" y1="-149" x2="577" y2="-80"/>
|
||||||
|
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 397,-80 A 10,10 0 0 0 407,-70"/>
|
||||||
|
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 567,-70 A 10,10 0 0 0 577,-80"/>
|
||||||
|
<text font-size="14.1111" style="fill: #ffffff;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="487" y="-119.039">
|
||||||
|
<tspan x="487" y="-119.039">Watcher</tspan>
|
||||||
|
<tspan x="487" y="-101.4">Actions Applier</tspan>
|
||||||
|
</text>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<rect style="fill: #ffb400" x="63.086" y="-136.5" width="70.7" height="157"/>
|
||||||
|
<path style="fill: #ffb400" d="M 63.086,-136.5 A 10,10 0 0 0 53.086,-126.5 L 63.086,-126.5 z"/>
|
||||||
|
<path style="fill: #ffb400" d="M 143.786,-126.5 A 10,10 0 0 0 133.786,-136.5 L 133.786,-126.5 z"/>
|
||||||
|
<rect style="fill: #ffb400" x="53.086" y="-126.5" width="90.7" height="137"/>
|
||||||
|
<path style="fill: #ffb400" d="M 53.086,10.5 A 10,10 0 0 0 63.086,20.5 L 63.086,10.5 z"/>
|
||||||
|
<path style="fill: #ffb400" d="M 133.786,20.5 A 10,10 0 0 0 143.786,10.5 L 133.786,10.5 z"/>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="63.086" y1="-136.5" x2="133.786" y2="-136.5"/>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="63.086" y1="20.5" x2="133.786" y2="20.5"/>
|
||||||
|
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 63.086,-136.5 A 10,10 0 0 0 53.086,-126.5"/>
|
||||||
|
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 143.786,-126.5 A 10,10 0 0 0 133.786,-136.5"/>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="53.086" y1="-126.5" x2="53.086" y2="10.5"/>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="143.786" y1="-126.5" x2="143.786" y2="10.5"/>
|
||||||
|
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 53.086,10.5 A 10,10 0 0 0 63.086,20.5"/>
|
||||||
|
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 133.786,20.5 A 10,10 0 0 0 143.786,10.5"/>
|
||||||
|
<text font-size="14.1111" style="fill: #ffffff;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="98.436" y="-62.5389">
|
||||||
|
<tspan x="98.436" y="-62.5389">Watcher</tspan>
|
||||||
|
<tspan x="98.436" y="-44.9">API</tspan>
|
||||||
|
</text>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<rect style="fill: #ffb400" x="-138.614" y="-124.5" width="105" height="69.1"/>
|
||||||
|
<path style="fill: #ffb400" d="M -138.614,-124.5 A 10,10 0 0 0 -148.614,-114.5 L -138.614,-114.5 z"/>
|
||||||
|
<path style="fill: #ffb400" d="M -23.614,-114.5 A 10,10 0 0 0 -33.614,-124.5 L -33.614,-114.5 z"/>
|
||||||
|
<rect style="fill: #ffb400" x="-148.614" y="-114.5" width="125" height="49.1"/>
|
||||||
|
<path style="fill: #ffb400" d="M -148.614,-65.4 A 10,10 0 0 0 -138.614,-55.4 L -138.614,-65.4 z"/>
|
||||||
|
<path style="fill: #ffb400" d="M -33.614,-55.4 A 10,10 0 0 0 -23.614,-65.4 L -33.614,-65.4 z"/>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="-138.614" y1="-124.5" x2="-33.614" y2="-124.5"/>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="-138.614" y1="-55.4" x2="-33.614" y2="-55.4"/>
|
||||||
|
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M -138.614,-124.5 A 10,10 0 0 0 -148.614,-114.5"/>
|
||||||
|
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M -23.614,-114.5 A 10,10 0 0 0 -33.614,-124.5"/>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="-148.614" y1="-114.5" x2="-148.614" y2="-65.4"/>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="-23.614" y1="-114.5" x2="-23.614" y2="-65.4"/>
|
||||||
|
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M -148.614,-65.4 A 10,10 0 0 0 -138.614,-55.4"/>
|
||||||
|
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M -33.614,-55.4 A 10,10 0 0 0 -23.614,-65.4"/>
|
||||||
|
<text font-size="14.1111" style="fill: #ffffff;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="-86.114" y="-94.4889">
|
||||||
|
<tspan x="-86.114" y="-94.4889">Watcher</tspan>
|
||||||
|
<tspan x="-86.114" y="-76.85">CLI</tspan>
|
||||||
|
</text>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<rect style="fill: #ffffff" x="-155.614" y="-29.5" width="117" height="56"/>
|
||||||
|
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="-155.614" y="-29.5" width="117" height="56"/>
|
||||||
|
<text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="-97.114" y="2.4">
|
||||||
|
<tspan x="-97.114" y="2.4">Horizon</tspan>
|
||||||
|
</text>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<rect style="fill: #ffb400" x="-111.614" y="12.6" width="90" height="59.9"/>
|
||||||
|
<path style="fill: #ffb400" d="M -111.614,12.6 A 10,10 0 0 0 -121.614,22.6 L -111.614,22.6 z"/>
|
||||||
|
<path style="fill: #ffb400" d="M -11.614,22.6 A 10,10 0 0 0 -21.614,12.6 L -21.614,22.6 z"/>
|
||||||
|
<rect style="fill: #ffb400" x="-121.614" y="22.6" width="110" height="39.9"/>
|
||||||
|
<path style="fill: #ffb400" d="M -121.614,62.5 A 10,10 0 0 0 -111.614,72.5 L -111.614,62.5 z"/>
|
||||||
|
<path style="fill: #ffb400" d="M -21.614,72.5 A 10,10 0 0 0 -11.614,62.5 L -21.614,62.5 z"/>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="-111.614" y1="12.6" x2="-21.614" y2="12.6"/>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="-111.614" y1="72.5" x2="-21.614" y2="72.5"/>
|
||||||
|
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M -111.614,12.6 A 10,10 0 0 0 -121.614,22.6"/>
|
||||||
|
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M -11.614,22.6 A 10,10 0 0 0 -21.614,12.6"/>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="-121.614" y1="22.6" x2="-121.614" y2="62.5"/>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="-11.614" y1="22.6" x2="-11.614" y2="62.5"/>
|
||||||
|
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M -121.614,62.5 A 10,10 0 0 0 -111.614,72.5"/>
|
||||||
|
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M -21.614,72.5 A 10,10 0 0 0 -11.614,62.5"/>
|
||||||
|
<text font-size="14.1111" style="fill: #ffffff;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="-66.614" y="38.0111">
|
||||||
|
<tspan x="-66.614" y="38.0111">Watcher</tspan>
|
||||||
|
<tspan x="-66.614" y="55.65">plugin</tspan>
|
||||||
|
</text>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<rect style="fill: #ffffff" x="639.986" y="-66.4" width="117" height="56"/>
|
||||||
|
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="639.986" y="-66.4" width="117" height="56"/>
|
||||||
|
<text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="698.486" y="-34.5">
|
||||||
|
<tspan x="698.486" y="-34.5">Nova</tspan>
|
||||||
|
</text>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<rect style="fill: #ffffff" x="822.786" y="122.5" width="167.6" height="59.501"/>
|
||||||
|
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="822.786" y="122.5" width="167.6" height="59.501"/>
|
||||||
|
<text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="906.586" y="156.151">
|
||||||
|
<tspan x="906.586" y="156.151">MariaDB Database</tspan>
|
||||||
|
</text>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="149.386" y1="-56.9998" x2="216.651" y2="-56.1259"/>
|
||||||
|
<polygon style="fill: #000000" points="224.15,-56.0284 214.086,-51.1588 216.651,-56.1259 214.216,-61.1579 "/>
|
||||||
|
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="224.15,-56.0284 214.086,-51.1588 216.651,-56.1259 214.216,-61.1579 "/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="341.294" y1="-128.975" x2="381.652" y2="-128.189"/>
|
||||||
|
<polygon style="fill: #000000" points="389.15,-128.043 379.055,-123.238 381.652,-128.189 379.25,-133.236 "/>
|
||||||
|
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="389.15,-128.043 379.055,-123.238 381.652,-128.189 379.25,-133.236 "/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke-dasharray: 6; stroke: #000000" x1="346.121" y1="-92.8795" x2="387.265" y2="-92.3705"/>
|
||||||
|
<polygon style="fill: #000000" points="338.622,-92.9723 348.683,-97.8482 346.121,-92.8795 348.559,-87.849 "/>
|
||||||
|
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="338.622,-92.9723 348.683,-97.8482 346.121,-92.8795 348.559,-87.849 "/>
|
||||||
|
<polygon style="fill: #000000" points="394.764,-92.2777 384.703,-87.4018 387.265,-92.3705 384.827,-97.401 "/>
|
||||||
|
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="394.764,-92.2777 384.703,-87.4018 387.265,-92.3705 384.827,-97.401 "/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="577" y1="-114.5" x2="633.053" y2="-59.2355"/>
|
||||||
|
<polygon style="fill: #000000" points="638.394,-53.9699 627.762,-57.4302 633.053,-59.2355 634.783,-64.5512 "/>
|
||||||
|
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="638.394,-53.9699 627.762,-57.4302 633.053,-59.2355 634.783,-64.5512 "/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<rect style="fill: #ffffff" x="654.61" y="121.001" width="137.55" height="38"/>
|
||||||
|
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="654.61" y="121.001" width="137.55" height="38"/>
|
||||||
|
<text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="723.385" y="143.901">
|
||||||
|
<tspan x="723.385" y="143.901">Ceilometer API</tspan>
|
||||||
|
</text>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="584.386" y1="53.5" x2="833.65" y2="54.4624"/>
|
||||||
|
<polygon style="fill: #000000" points="841.15,54.4914 831.131,59.4527 833.65,54.4624 831.169,49.4528 "/>
|
||||||
|
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="841.15,54.4914 831.131,59.4527 833.65,54.4624 831.169,49.4528 "/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="-23.614" y1="-89.95" x2="44.0985" y2="-61.7438"/>
|
||||||
|
<polygon style="fill: #000000" points="51.0219,-58.8598 39.8681,-58.0896 44.0985,-61.7438 43.7134,-67.3207 "/>
|
||||||
|
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="51.0219,-58.8598 39.8681,-58.0896 44.0985,-61.7438 43.7134,-67.3207 "/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="-11.614" y1="42.55" x2="46.0184" y2="-12.0538"/>
|
||||||
|
<polygon style="fill: #000000" points="51.4628,-17.2121 47.6424,-6.70471 46.0184,-12.0538 40.7647,-13.9639 "/>
|
||||||
|
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="51.4628,-17.2121 47.6424,-6.70471 46.0184,-12.0538 40.7647,-13.9639 "/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<rect style="fill: #ffa200" x="506.36" y="72.1008" width="92.025" height="49.9"/>
|
||||||
|
<path style="fill: #ffa200" d="M 506.36,72.1008 A 10,10 0 0 0 496.36,82.1008 L 506.36,82.1008 z"/>
|
||||||
|
<path style="fill: #ffa200" d="M 608.385,82.1008 A 10,10 0 0 0 598.385,72.1008 L 598.385,82.1008 z"/>
|
||||||
|
<rect style="fill: #ffa200" x="496.36" y="82.1008" width="112.025" height="29.9"/>
|
||||||
|
<path style="fill: #ffa200" d="M 496.36,112.001 A 10,10 0 0 0 506.36,122.001 L 506.36,112.001 z"/>
|
||||||
|
<path style="fill: #ffa200" d="M 598.385,122.001 A 10,10 0 0 0 608.385,112.001 L 598.385,112.001 z"/>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="506.36" y1="72.1008" x2="598.385" y2="72.1008"/>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="506.36" y1="122.001" x2="598.385" y2="122.001"/>
|
||||||
|
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 506.36,72.1008 A 10,10 0 0 0 496.36,82.1008"/>
|
||||||
|
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 608.385,82.1008 A 10,10 0 0 0 598.385,72.1008"/>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="496.36" y1="82.1008" x2="496.36" y2="112.001"/>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="608.385" y1="82.1008" x2="608.385" y2="112.001"/>
|
||||||
|
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 496.36,112.001 A 10,10 0 0 0 506.36,122.001"/>
|
||||||
|
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 598.385,122.001 A 10,10 0 0 0 608.385,112.001"/>
|
||||||
|
<text font-size="14.1111" style="fill: #ffffff;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="552.372" y="101.331">
|
||||||
|
<tspan x="552.372" y="101.331">Strategy</tspan>
|
||||||
|
</text>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="611.386" y1="115.526" x2="646.095" y2="136.896"/>
|
||||||
|
<polygon style="fill: #000000" points="652.482,140.829 641.345,139.843 646.095,136.896 646.588,131.328 "/>
|
||||||
|
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="652.482,140.829 641.345,139.843 646.095,136.896 646.588,131.328 "/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="577,-136.75 874.386,-136.75 874.386,3.76393 "/>
|
||||||
|
<polygon style="fill: #000000" points="874.386,11.2639 869.386,1.26393 874.386,3.76393 879.386,1.26393 "/>
|
||||||
|
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="874.386,11.2639 869.386,1.26393 874.386,3.76393 879.386,1.26393 "/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="579.3" y1="21.418" x2="632.216" y2="-18.5335"/>
|
||||||
|
<polygon style="fill: #000000" points="638.201,-23.0527 633.233,-13.0367 632.216,-18.5335 627.208,-21.0175 "/>
|
||||||
|
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="638.201,-23.0527 633.233,-13.0367 632.216,-18.5335 627.208,-21.0175 "/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path style="fill: #ffb400" d="M 844 38.3333 C 865.877,23.0833 876.816,18 898.693,18 C 920.57,18 931.509,23.0833 953.386,38.3333 L 953.386,119.667 C 931.509,134.917 920.57,140 898.693,140 C 876.816,140 865.877,134.917 844,119.667 L 844,38.3333z"/>
|
||||||
|
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 844 38.3333 C 865.877,23.0833 876.816,18 898.693,18 C 920.57,18 931.509,23.0833 953.386,38.3333 L 953.386,119.667 C 931.509,134.917 920.57,140 898.693,140 C 876.816,140 865.877,134.917 844,119.667 L 844,38.3333"/>
|
||||||
|
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 844 38.3333 C 865.877,53.5833 876.816,58.6667 898.693,58.6667 C 920.57,58.6667 931.509,53.5833 953.386,38.3333"/>
|
||||||
|
<text font-size="12.8" style="fill: #ffffff;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="898.693" y="93.1667">
|
||||||
|
<tspan x="898.693" y="93.1667">Watcher DB</tspan>
|
||||||
|
</text>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path style="fill: #ff5050" d="M 651.176 172.933 C 680.618,157.683 695.339,152.6 724.78,152.6 C 754.222,152.6 768.943,157.683 798.385,172.933 L 798.385,254.267 C 768.943,269.517 754.222,274.6 724.78,274.6 C 695.339,274.6 680.618,269.517 651.176,254.267 L 651.176,172.933z"/>
|
||||||
|
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 651.176 172.933 C 680.618,157.683 695.339,152.6 724.78,152.6 C 754.222,152.6 768.943,157.683 798.385,172.933 L 798.385,254.267 C 768.943,269.517 754.222,274.6 724.78,274.6 C 695.339,274.6 680.618,269.517 651.176,254.267 L 651.176,172.933"/>
|
||||||
|
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 651.176 172.933 C 680.618,188.183 695.339,193.267 724.78,193.267 C 754.222,193.267 768.943,188.183 798.385,172.933"/>
|
||||||
|
<text font-size="12.8" style="fill: #ffffff;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="724.78" y="227.767">
|
||||||
|
<tspan x="724.78" y="227.767">Cluster History DB</tspan>
|
||||||
|
</text>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="96.386,-142.999 96.386,-205.998 899.386,-205.998 899.386,5.26493 "/>
|
||||||
|
<polygon style="fill: #000000" points="899.386,12.7649 894.386,2.76493 899.386,5.26493 904.386,2.76493 "/>
|
||||||
|
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="899.386,12.7649 894.386,2.76493 899.386,5.26493 904.386,2.76493 "/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 24 KiB |
@@ -1,3 +1,9 @@
|
|||||||
|
..
|
||||||
|
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/
|
||||||
|
|
||||||
============================================
|
============================================
|
||||||
Welcome to Watcher's developer documentation
|
Welcome to Watcher's developer documentation
|
||||||
============================================
|
============================================
|
||||||
@@ -21,8 +27,8 @@ Introduction
|
|||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
|
||||||
dev/glossary
|
glossary
|
||||||
dev/architecture
|
architecture
|
||||||
dev/environment
|
dev/environment
|
||||||
dev/contributing
|
dev/contributing
|
||||||
dev/plugins
|
dev/plugins
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
..
|
||||||
|
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/
|
||||||
|
|
||||||
========
|
========
|
||||||
Usage
|
Usage
|
||||||
========
|
========
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
..
|
||||||
|
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/
|
||||||
|
|
||||||
=====================
|
=====================
|
||||||
RESTful Web API (v1)
|
RESTful Web API (v1)
|
||||||
=====================
|
=====================
|
||||||
|
|||||||
@@ -157,13 +157,17 @@
|
|||||||
# timeout exception when timeout expired. (integer value)
|
# timeout exception when timeout expired. (integer value)
|
||||||
#rpc_poll_timeout = 1
|
#rpc_poll_timeout = 1
|
||||||
|
|
||||||
# Shows whether zmq-messaging uses broker or not. (boolean value)
|
# Configures zmq-messaging to use broker or not. (boolean value)
|
||||||
#zmq_use_broker = true
|
#zmq_use_broker = false
|
||||||
|
|
||||||
# Minimal port number for random ports range. (integer value)
|
# Minimal port number for random ports range. (port value)
|
||||||
|
# Minimum value: 1
|
||||||
|
# Maximum value: 65535
|
||||||
#rpc_zmq_min_port = 49152
|
#rpc_zmq_min_port = 49152
|
||||||
|
|
||||||
# Maximal port number for random ports range. (integer value)
|
# Maximal port number for random ports range. (integer value)
|
||||||
|
# Minimum value: 1
|
||||||
|
# Maximum value: 65536
|
||||||
#rpc_zmq_max_port = 65536
|
#rpc_zmq_max_port = 65536
|
||||||
|
|
||||||
# Number of retries to find free port number before fail with
|
# Number of retries to find free port number before fail with
|
||||||
@@ -173,7 +177,9 @@
|
|||||||
# Host to locate redis. (string value)
|
# Host to locate redis. (string value)
|
||||||
#host = 127.0.0.1
|
#host = 127.0.0.1
|
||||||
|
|
||||||
# Use this port to connect to redis host. (integer value)
|
# Use this port to connect to redis host. (port value)
|
||||||
|
# Minimum value: 1
|
||||||
|
# Maximum value: 65535
|
||||||
#port = 6379
|
#port = 6379
|
||||||
|
|
||||||
# Password for Redis server (optional). (string value)
|
# Password for Redis server (optional). (string value)
|
||||||
@@ -185,16 +191,19 @@
|
|||||||
|
|
||||||
# The Drivers(s) to handle sending notifications. Possible values are
|
# The Drivers(s) to handle sending notifications. Possible values are
|
||||||
# messaging, messagingv2, routing, log, test, noop (multi valued)
|
# messaging, messagingv2, routing, log, test, noop (multi valued)
|
||||||
#notification_driver =
|
# Deprecated group/name - [DEFAULT]/notification_driver
|
||||||
|
#driver =
|
||||||
|
|
||||||
# A URL representing the messaging driver to use for notifications. If
|
# A URL representing the messaging driver to use for notifications. If
|
||||||
# not set, we fall back to the same configuration used for RPC.
|
# not set, we fall back to the same configuration used for RPC.
|
||||||
# (string value)
|
# (string value)
|
||||||
#notification_transport_url = <None>
|
# Deprecated group/name - [DEFAULT]/notification_transport_url
|
||||||
|
#transport_url = <None>
|
||||||
|
|
||||||
# AMQP topic used for OpenStack notifications. (list value)
|
# AMQP topic used for OpenStack notifications. (list value)
|
||||||
# Deprecated group/name - [rpc_notifier2]/topics
|
# Deprecated group/name - [rpc_notifier2]/topics
|
||||||
#notification_topics = notifications
|
# Deprecated group/name - [DEFAULT]/notification_topics
|
||||||
|
#topics = notifications
|
||||||
|
|
||||||
# Seconds to wait for a response from a call. (integer value)
|
# Seconds to wait for a response from a call. (integer value)
|
||||||
#rpc_response_timeout = 60
|
#rpc_response_timeout = 60
|
||||||
@@ -205,7 +214,7 @@
|
|||||||
#transport_url = <None>
|
#transport_url = <None>
|
||||||
|
|
||||||
# The messaging driver to use, defaults to rabbit. Other drivers
|
# The messaging driver to use, defaults to rabbit. Other drivers
|
||||||
# include qpid and zmq. (string value)
|
# include amqp and zmq. (string value)
|
||||||
#rpc_backend = rabbit
|
#rpc_backend = rabbit
|
||||||
|
|
||||||
# The default exchange under which topics are scoped. May be
|
# The default exchange under which topics are scoped. May be
|
||||||
@@ -508,6 +517,14 @@
|
|||||||
# Service tenant name. (string value)
|
# Service tenant name. (string value)
|
||||||
#admin_tenant_name = admin
|
#admin_tenant_name = admin
|
||||||
|
|
||||||
|
# Authentication type to load (unknown value)
|
||||||
|
# Deprecated group/name - [DEFAULT]/auth_plugin
|
||||||
|
#auth_type = <None>
|
||||||
|
|
||||||
|
# Config Section from which to load plugin specific options (unknown
|
||||||
|
# value)
|
||||||
|
#auth_section = <None>
|
||||||
|
|
||||||
|
|
||||||
[matchmaker_redis]
|
[matchmaker_redis]
|
||||||
|
|
||||||
@@ -518,7 +535,9 @@
|
|||||||
# Host to locate redis. (string value)
|
# Host to locate redis. (string value)
|
||||||
#host = 127.0.0.1
|
#host = 127.0.0.1
|
||||||
|
|
||||||
# Use this port to connect to redis host. (integer value)
|
# Use this port to connect to redis host. (port value)
|
||||||
|
# Minimum value: 1
|
||||||
|
# Maximum value: 65535
|
||||||
#port = 6379
|
#port = 6379
|
||||||
|
|
||||||
# Password for Redis server (optional). (string value)
|
# Password for Redis server (optional). (string value)
|
||||||
@@ -599,82 +618,6 @@
|
|||||||
#password =
|
#password =
|
||||||
|
|
||||||
|
|
||||||
[oslo_messaging_qpid]
|
|
||||||
|
|
||||||
#
|
|
||||||
# From oslo.messaging
|
|
||||||
#
|
|
||||||
|
|
||||||
# Use durable queues in AMQP. (boolean value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/amqp_durable_queues
|
|
||||||
# Deprecated group/name - [DEFAULT]/rabbit_durable_queues
|
|
||||||
#amqp_durable_queues = false
|
|
||||||
|
|
||||||
# Auto-delete queues in AMQP. (boolean value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/amqp_auto_delete
|
|
||||||
#amqp_auto_delete = false
|
|
||||||
|
|
||||||
# Send a single AMQP reply to call message. The current behaviour
|
|
||||||
# since oslo-incubator is to send two AMQP replies - first one with
|
|
||||||
# the payload, a second one to ensure the other have finish to send
|
|
||||||
# the payload. We are going to remove it in the N release, but we must
|
|
||||||
# keep backward compatible at the same time. This option provides such
|
|
||||||
# compatibility - it defaults to False in Liberty and can be turned on
|
|
||||||
# for early adopters with a new installations or for testing. Please
|
|
||||||
# note, that this option will be removed in the Mitaka release.
|
|
||||||
# (boolean value)
|
|
||||||
#send_single_reply = false
|
|
||||||
|
|
||||||
# Qpid broker hostname. (string value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/qpid_hostname
|
|
||||||
#qpid_hostname = localhost
|
|
||||||
|
|
||||||
# Qpid broker port. (integer value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/qpid_port
|
|
||||||
#qpid_port = 5672
|
|
||||||
|
|
||||||
# Qpid HA cluster host:port pairs. (list value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/qpid_hosts
|
|
||||||
#qpid_hosts = $qpid_hostname:$qpid_port
|
|
||||||
|
|
||||||
# Username for Qpid connection. (string value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/qpid_username
|
|
||||||
#qpid_username =
|
|
||||||
|
|
||||||
# Password for Qpid connection. (string value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/qpid_password
|
|
||||||
#qpid_password =
|
|
||||||
|
|
||||||
# Space separated list of SASL mechanisms to use for auth. (string
|
|
||||||
# value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/qpid_sasl_mechanisms
|
|
||||||
#qpid_sasl_mechanisms =
|
|
||||||
|
|
||||||
# Seconds between connection keepalive heartbeats. (integer value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/qpid_heartbeat
|
|
||||||
#qpid_heartbeat = 60
|
|
||||||
|
|
||||||
# Transport to use, either 'tcp' or 'ssl'. (string value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/qpid_protocol
|
|
||||||
#qpid_protocol = tcp
|
|
||||||
|
|
||||||
# Whether to disable the Nagle algorithm. (boolean value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/qpid_tcp_nodelay
|
|
||||||
#qpid_tcp_nodelay = true
|
|
||||||
|
|
||||||
# The number of prefetched messages held by receiver. (integer value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/qpid_receiver_capacity
|
|
||||||
#qpid_receiver_capacity = 1
|
|
||||||
|
|
||||||
# The qpid topology version to use. Version 1 is what was originally
|
|
||||||
# used by impl_qpid. Version 2 includes some backwards-incompatible
|
|
||||||
# changes that allow broker federation to work. Users should update
|
|
||||||
# to version 2 when they are able to take everything down, as it
|
|
||||||
# requires a clean break. (integer value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/qpid_topology_version
|
|
||||||
#qpid_topology_version = 1
|
|
||||||
|
|
||||||
|
|
||||||
[oslo_messaging_rabbit]
|
[oslo_messaging_rabbit]
|
||||||
|
|
||||||
#
|
#
|
||||||
@@ -725,18 +668,26 @@
|
|||||||
# Deprecated group/name - [DEFAULT]/kombu_reconnect_delay
|
# Deprecated group/name - [DEFAULT]/kombu_reconnect_delay
|
||||||
#kombu_reconnect_delay = 1.0
|
#kombu_reconnect_delay = 1.0
|
||||||
|
|
||||||
# How long to wait before considering a reconnect attempt to have
|
# How long to wait a missing client beforce abandoning to send it its
|
||||||
# failed. This value should not be longer than rpc_response_timeout.
|
# replies. This value should not be longer than rpc_response_timeout.
|
||||||
# (integer value)
|
# (integer value)
|
||||||
#kombu_reconnect_timeout = 60
|
# Deprecated group/name - [DEFAULT]/kombu_reconnect_timeout
|
||||||
|
#kombu_missing_consumer_retry_timeout = 5
|
||||||
|
|
||||||
|
# Determines how the next RabbitMQ node is chosen in case the one we
|
||||||
|
# are currently connected to becomes unavailable. Takes effect only if
|
||||||
|
# more than one RabbitMQ node is provided in config. (string value)
|
||||||
|
# Allowed values: round-robin, shuffle
|
||||||
|
#kombu_failover_strategy = round-robin
|
||||||
|
|
||||||
# The RabbitMQ broker address where a single node is used. (string
|
# The RabbitMQ broker address where a single node is used. (string
|
||||||
# value)
|
# value)
|
||||||
# Deprecated group/name - [DEFAULT]/rabbit_host
|
# Deprecated group/name - [DEFAULT]/rabbit_host
|
||||||
#rabbit_host = localhost
|
#rabbit_host = localhost
|
||||||
|
|
||||||
# The RabbitMQ broker port where a single node is used. (integer
|
# The RabbitMQ broker port where a single node is used. (port value)
|
||||||
# value)
|
# Minimum value: 1
|
||||||
|
# Maximum value: 65535
|
||||||
# Deprecated group/name - [DEFAULT]/rabbit_port
|
# Deprecated group/name - [DEFAULT]/rabbit_port
|
||||||
#rabbit_port = 5672
|
#rabbit_port = 5672
|
||||||
|
|
||||||
@@ -837,6 +788,10 @@
|
|||||||
# value)
|
# value)
|
||||||
#publisher_id = watcher.decision.api
|
#publisher_id = watcher.decision.api
|
||||||
|
|
||||||
|
# The maximum number of threads that can be used to execute strategies
|
||||||
|
# (integer value)
|
||||||
|
#max_workers = 2
|
||||||
|
|
||||||
|
|
||||||
[watcher_goals]
|
[watcher_goals]
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ classifier =
|
|||||||
Programming Language :: Python :: 2
|
Programming Language :: Python :: 2
|
||||||
Programming Language :: Python :: 2.7
|
Programming Language :: Python :: 2.7
|
||||||
Programming Language :: Python :: 3
|
Programming Language :: Python :: 3
|
||||||
Programming Language :: Python :: 3.3
|
|
||||||
Programming Language :: Python :: 3.4
|
Programming Language :: Python :: 3.4
|
||||||
|
|
||||||
[files]
|
[files]
|
||||||
@@ -43,12 +42,14 @@ watcher.database.migration_backend =
|
|||||||
sqlalchemy = watcher.db.sqlalchemy.migration
|
sqlalchemy = watcher.db.sqlalchemy.migration
|
||||||
|
|
||||||
watcher_strategies =
|
watcher_strategies =
|
||||||
dummy = watcher.decision_engine.strategy.dummy_strategy:DummyStrategy
|
dummy = watcher.decision_engine.strategy.strategies.dummy_strategy:DummyStrategy
|
||||||
basic = watcher.decision_engine.strategy.basic_consolidation:BasicConsolidation
|
basic = watcher.decision_engine.strategy.strategies.basic_consolidation:BasicConsolidation
|
||||||
|
outlet_temp_control = watcher.decision_engine.strategy.strategies.outlet_temp_control:OutletTempControl
|
||||||
|
|
||||||
[build_sphinx]
|
[build_sphinx]
|
||||||
source-dir = doc/source
|
source-dir = doc/source
|
||||||
build-dir = doc/build
|
build-dir = doc/build
|
||||||
|
fresh_env = 1
|
||||||
all_files = 1
|
all_files = 1
|
||||||
|
|
||||||
[upload_sphinx]
|
[upload_sphinx]
|
||||||
|
|||||||
@@ -2,19 +2,19 @@
|
|||||||
# of appearance. Changing the order has an impact on the overall integration
|
# of appearance. Changing the order has an impact on the overall integration
|
||||||
# process, which may cause wedges in the gate later.
|
# process, which may cause wedges in the gate later.
|
||||||
|
|
||||||
hacking<0.11,>=0.10
|
|
||||||
coverage>=3.6
|
coverage>=3.6
|
||||||
discover
|
discover
|
||||||
python-subunit>=0.0.18
|
hacking>=0.10.2,<0.11
|
||||||
|
mock>=1.2
|
||||||
oslotest>=1.10.0 # Apache-2.0
|
oslotest>=1.10.0 # Apache-2.0
|
||||||
|
python-subunit>=0.0.18
|
||||||
testrepository>=0.0.18
|
testrepository>=0.0.18
|
||||||
testscenarios>=0.4
|
testscenarios>=0.4
|
||||||
testtools>=1.4.0
|
testtools>=1.4.0
|
||||||
mock>=1.2
|
|
||||||
|
|
||||||
# Doc requirements
|
# Doc requirements
|
||||||
|
oslosphinx>=2.5.0 # Apache-2.0
|
||||||
sphinx>=1.1.2,!=1.2.0,!=1.3b1,<1.3
|
sphinx>=1.1.2,!=1.2.0,!=1.3b1,<1.3
|
||||||
oslosphinx>=2.5.0 # Apache-2.0
|
|
||||||
sphinxcontrib-pecanwsme>=0.8
|
sphinxcontrib-pecanwsme>=0.8
|
||||||
|
|
||||||
# For PyPI distribution
|
# For PyPI distribution
|
||||||
|
|||||||
17
tox.ini
17
tox.ini
@@ -1,16 +1,20 @@
|
|||||||
[tox]
|
[tox]
|
||||||
minversion = 1.6
|
minversion = 1.6
|
||||||
envlist = py33,py34,py27,pypy,pep8
|
envlist = py34,py27,pep8
|
||||||
skipsdist = True
|
skipsdist = True
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
usedevelop = True
|
usedevelop = True
|
||||||
|
whitelist_externals = find
|
||||||
install_command = pip install -U {opts} {packages}
|
install_command = pip install -U {opts} {packages}
|
||||||
setenv =
|
setenv =
|
||||||
VIRTUAL_ENV={envdir}
|
VIRTUAL_ENV={envdir}
|
||||||
deps = -r{toxinidir}/requirements.txt
|
deps = -r{toxinidir}/requirements.txt
|
||||||
-r{toxinidir}/test-requirements.txt
|
-r{toxinidir}/test-requirements.txt
|
||||||
commands = python setup.py testr --slowest --testr-args='{posargs}'
|
commands =
|
||||||
|
find . -type f -name "*.pyc" -delete
|
||||||
|
find . -type d -name "__pycache__" -delete
|
||||||
|
python setup.py testr --slowest --testr-args='{posargs}'
|
||||||
|
|
||||||
[testenv:pep8]
|
[testenv:pep8]
|
||||||
commands = flake8
|
commands = flake8
|
||||||
@@ -19,7 +23,7 @@ commands = flake8
|
|||||||
commands = {posargs}
|
commands = {posargs}
|
||||||
|
|
||||||
[testenv:cover]
|
[testenv:cover]
|
||||||
commands = python setup.py testr --coverage --omit="watcher/tests/*,watcher/openstack/*" --testr-args='{posargs}'
|
commands = python setup.py testr --coverage --omit="watcher/tests/*" --testr-args='{posargs}'
|
||||||
|
|
||||||
[testenv:docs]
|
[testenv:docs]
|
||||||
commands = python setup.py build_sphinx
|
commands = python setup.py build_sphinx
|
||||||
@@ -41,9 +45,9 @@ commands =
|
|||||||
# E123, E125 skipped as they are invalid PEP-8.
|
# E123, E125 skipped as they are invalid PEP-8.
|
||||||
|
|
||||||
show-source=True
|
show-source=True
|
||||||
ignore=E123,E125,H404,H405,H305
|
ignore=E123,E125
|
||||||
builtins= _
|
builtins= _
|
||||||
exclude=.venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build,*sqlalchemy/alembic/versions/*,demo/
|
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build,*sqlalchemy/alembic/versions/*,demo/
|
||||||
|
|
||||||
[testenv:pypi]
|
[testenv:pypi]
|
||||||
commands =
|
commands =
|
||||||
@@ -52,3 +56,6 @@ commands =
|
|||||||
|
|
||||||
[testenv:wheel]
|
[testenv:wheel]
|
||||||
commands = python setup.py bdist_wheel
|
commands = python setup.py bdist_wheel
|
||||||
|
|
||||||
|
[hacking]
|
||||||
|
import_exceptions = watcher._i18n
|
||||||
|
|||||||
@@ -16,11 +16,31 @@
|
|||||||
#
|
#
|
||||||
import oslo_i18n
|
import oslo_i18n
|
||||||
|
|
||||||
_translators = oslo_i18n.TranslatorFactory(domain='watcher')
|
# The domain is the name of the App which is used to generate the folder
|
||||||
oslo_i18n.enable_lazy()
|
# containing the translation files (i.e. the .pot file and the various locales)
|
||||||
|
DOMAIN = "watcher"
|
||||||
|
|
||||||
|
_translators = oslo_i18n.TranslatorFactory(domain=DOMAIN)
|
||||||
|
|
||||||
|
# The primary translation function using the well-known name "_"
|
||||||
_ = _translators.primary
|
_ = _translators.primary
|
||||||
|
|
||||||
|
# The contextual translation function using the name "_C"
|
||||||
|
_C = _translators.contextual_form
|
||||||
|
|
||||||
|
# The plural translation function using the name "_P"
|
||||||
|
_P = _translators.plural_form
|
||||||
|
|
||||||
|
# Translators for log levels.
|
||||||
|
#
|
||||||
|
# The abbreviated names are meant to reflect the usual use of a short
|
||||||
|
# name like '_'. The "L" is for "log" and the other letter comes from
|
||||||
|
# the level.
|
||||||
_LI = _translators.log_info
|
_LI = _translators.log_info
|
||||||
_LW = _translators.log_warning
|
_LW = _translators.log_warning
|
||||||
_LE = _translators.log_error
|
_LE = _translators.log_error
|
||||||
_LC = _translators.log_critical
|
_LC = _translators.log_critical
|
||||||
|
|
||||||
|
|
||||||
|
def get_available_languages():
|
||||||
|
return oslo_i18n.get_available_languages(DOMAIN)
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
# Watcher API
|
|
||||||
|
|
||||||
This component implements the REST API provided by the Watcher system to the external world. It enables a cluster administrator to control and monitor the Watcher system via any interaction mechanism connected to this API :
|
|
||||||
* CLI
|
|
||||||
* Horizon plugin
|
|
||||||
* Python SDK
|
|
||||||
@@ -22,7 +22,7 @@ import pecan
|
|||||||
from watcher.api import acl
|
from watcher.api import acl
|
||||||
from watcher.api import config as api_config
|
from watcher.api import config as api_config
|
||||||
from watcher.api import middleware
|
from watcher.api import middleware
|
||||||
from watcher.decision_engine.strategy.selector import default \
|
from watcher.decision_engine.strategy.selection import default \
|
||||||
as strategy_selector
|
as strategy_selector
|
||||||
|
|
||||||
# Register options for the service
|
# Register options for the service
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ class ActionCollection(collection.Collection):
|
|||||||
reverse = True if kwargs['sort_dir'] == 'desc' else False
|
reverse = True if kwargs['sort_dir'] == 'desc' else False
|
||||||
collection.actions = sorted(
|
collection.actions = sorted(
|
||||||
collection.actions,
|
collection.actions,
|
||||||
key=lambda action: action.next_uuid,
|
key=lambda action: action.next_uuid or '',
|
||||||
reverse=reverse)
|
reverse=reverse)
|
||||||
|
|
||||||
collection.next = collection.get_next(limit, url=url, **kwargs)
|
collection.next = collection.get_next(limit, url=url, **kwargs)
|
||||||
@@ -229,7 +229,6 @@ class ActionsController(rest.RestController):
|
|||||||
sort_key, sort_dir, expand=False,
|
sort_key, sort_dir, expand=False,
|
||||||
resource_url=None,
|
resource_url=None,
|
||||||
action_plan_uuid=None, audit_uuid=None):
|
action_plan_uuid=None, audit_uuid=None):
|
||||||
|
|
||||||
limit = api_utils.validate_limit(limit)
|
limit = api_utils.validate_limit(limit)
|
||||||
sort_dir = api_utils.validate_sort_dir(sort_dir)
|
sort_dir = api_utils.validate_sort_dir(sort_dir)
|
||||||
|
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ import six
|
|||||||
import wsme
|
import wsme
|
||||||
from wsme import types as wtypes
|
from wsme import types as wtypes
|
||||||
|
|
||||||
|
from watcher._i18n import _
|
||||||
from watcher.common import exception
|
from watcher.common import exception
|
||||||
from watcher.common.i18n import _
|
|
||||||
from watcher.common import utils
|
from watcher.common import utils
|
||||||
|
|
||||||
|
|
||||||
@@ -181,8 +181,9 @@ class MultiType(wtypes.UserType):
|
|||||||
return value
|
return value
|
||||||
else:
|
else:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
_("Wrong type. Expected '%(type)s', got '%(value)s'")
|
_("Wrong type. Expected '%(type)s', got '%(value)s'"),
|
||||||
% {'type': self.types, 'value': type(value)})
|
type=self.types, value=type(value)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class JsonPatchType(wtypes.Base):
|
class JsonPatchType(wtypes.Base):
|
||||||
@@ -217,7 +218,7 @@ class JsonPatchType(wtypes.Base):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def validate(patch):
|
def validate(patch):
|
||||||
_path = '/' + patch.path.split('/')[1]
|
_path = '/{0}'.format(patch.path.split('/')[1])
|
||||||
if _path in patch.internal_attrs():
|
if _path in patch.internal_attrs():
|
||||||
msg = _("'%s' is an internal attribute and can not be updated")
|
msg = _("'%s' is an internal attribute and can not be updated")
|
||||||
raise wsme.exc.ClientSideError(msg % patch.path)
|
raise wsme.exc.ClientSideError(msg % patch.path)
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import jsonpatch
|
|||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
import wsme
|
import wsme
|
||||||
|
|
||||||
from watcher.common.i18n import _
|
from watcher._i18n import _
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
|
|
||||||
@@ -28,10 +28,18 @@ JSONPATCH_EXCEPTIONS = (jsonpatch.JsonPatchException,
|
|||||||
|
|
||||||
|
|
||||||
def validate_limit(limit):
|
def validate_limit(limit):
|
||||||
if limit is not None and limit <= 0:
|
if limit is None:
|
||||||
|
return CONF.api.max_limit
|
||||||
|
|
||||||
|
if limit <= 0:
|
||||||
|
# Case where we don't a valid limit value
|
||||||
raise wsme.exc.ClientSideError(_("Limit must be positive"))
|
raise wsme.exc.ClientSideError(_("Limit must be positive"))
|
||||||
|
|
||||||
return min(CONF.api.max_limit, limit) or CONF.api.max_limit
|
if limit and not CONF.api.max_limit:
|
||||||
|
# Case where we don't have an upper limit
|
||||||
|
return limit
|
||||||
|
|
||||||
|
return min(CONF.api.max_limit, limit)
|
||||||
|
|
||||||
|
|
||||||
def validate_sort_dir(sort_dir):
|
def validate_sort_dir(sort_dir):
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ from oslo_log import log
|
|||||||
|
|
||||||
from keystonemiddleware import auth_token
|
from keystonemiddleware import auth_token
|
||||||
|
|
||||||
|
from watcher._i18n import _
|
||||||
from watcher.common import exception
|
from watcher.common import exception
|
||||||
from watcher.common.i18n import _
|
|
||||||
from watcher.common import utils
|
from watcher.common import utils
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
@@ -40,10 +40,9 @@ class AuthTokenMiddleware(auth_token.AuthProtocol):
|
|||||||
self.public_api_routes = [re.compile(route_pattern_tpl % route_tpl)
|
self.public_api_routes = [re.compile(route_pattern_tpl % route_tpl)
|
||||||
for route_tpl in public_api_routes]
|
for route_tpl in public_api_routes]
|
||||||
except re.error as e:
|
except re.error as e:
|
||||||
msg = _('Cannot compile public API routes: %s') % e
|
LOG.exception(e)
|
||||||
|
raise exception.ConfigInvalid(
|
||||||
LOG.error(msg)
|
error_msg=_('Cannot compile public API routes'))
|
||||||
raise exception.ConfigInvalid(error_msg=msg)
|
|
||||||
|
|
||||||
super(AuthTokenMiddleware, self).__init__(app, conf)
|
super(AuthTokenMiddleware, self).__init__(app, conf)
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,6 @@
|
|||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Middleware to replace the plain text message body of an error
|
Middleware to replace the plain text message body of an error
|
||||||
response with one formatted so the client can parse it.
|
response with one formatted so the client can parse it.
|
||||||
@@ -24,12 +23,12 @@ Based on pecan.middleware.errordocument
|
|||||||
import json
|
import json
|
||||||
from xml import etree as et
|
from xml import etree as et
|
||||||
|
|
||||||
|
from oslo_log import log
|
||||||
|
import six
|
||||||
import webob
|
import webob
|
||||||
|
|
||||||
from oslo_log import log
|
from watcher._i18n import _
|
||||||
|
from watcher._i18n import _LE
|
||||||
from watcher.common.i18n import _
|
|
||||||
from watcher.common.i18n import _LE
|
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
@@ -69,23 +68,28 @@ class ParsableErrorMiddleware(object):
|
|||||||
app_iter = self.app(environ, replacement_start_response)
|
app_iter = self.app(environ, replacement_start_response)
|
||||||
if (state['status_code'] // 100) not in (2, 3):
|
if (state['status_code'] // 100) not in (2, 3):
|
||||||
req = webob.Request(environ)
|
req = webob.Request(environ)
|
||||||
if (req.accept.best_match(['application/json', 'application/xml'])
|
if (req.accept.best_match(['application/json', 'application/xml']
|
||||||
== 'application/xml'):
|
) == 'application/xml'
|
||||||
|
):
|
||||||
try:
|
try:
|
||||||
# simple check xml is valid
|
# simple check xml is valid
|
||||||
body = [et.ElementTree.tostring(
|
body = [et.ElementTree.tostring(
|
||||||
et.ElementTree.fromstring('<error_message>'
|
et.ElementTree.Element('error_message',
|
||||||
+ '\n'.join(app_iter)
|
text='\n'.join(app_iter)))]
|
||||||
+ '</error_message>'))]
|
|
||||||
except et.ElementTree.ParseError as err:
|
except et.ElementTree.ParseError as err:
|
||||||
LOG.error(_LE('Error parsing HTTP response: %s'), err)
|
LOG.error(_LE('Error parsing HTTP response: %s'), err)
|
||||||
body = ['<error_message>%s' % state['status_code']
|
body = [et.ElementTree.tostring(
|
||||||
+ '</error_message>']
|
et.ElementTree.Element('error_message',
|
||||||
|
text=state['status_code']))]
|
||||||
state['headers'].append(('Content-Type', 'application/xml'))
|
state['headers'].append(('Content-Type', 'application/xml'))
|
||||||
else:
|
else:
|
||||||
|
if six.PY3:
|
||||||
|
app_iter = [i.decode('utf-8') for i in app_iter]
|
||||||
body = [json.dumps({'error_message': '\n'.join(app_iter)})]
|
body = [json.dumps({'error_message': '\n'.join(app_iter)})]
|
||||||
|
if six.PY3:
|
||||||
|
body = [item.encode('utf-8') for item in body]
|
||||||
state['headers'].append(('Content-Type', 'application/json'))
|
state['headers'].append(('Content-Type', 'application/json'))
|
||||||
state['headers'].append(('Content-Length', len(body[0])))
|
state['headers'].append(('Content-Length', str(len(body[0]))))
|
||||||
else:
|
else:
|
||||||
body = app_iter
|
body = app_iter
|
||||||
return body
|
return body
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
# Watcher Actions Applier
|
|
||||||
|
|
||||||
This component is in charge of executing the plan of actions built by the Watcher Actions Planner.
|
|
||||||
|
|
||||||
For each action of the workflow, this component may call directly the component responsible for this kind of action (Example : Nova API for an instance migration) or via some publish/subscribe pattern on the message bus.
|
|
||||||
|
|
||||||
It notifies continuously of the current progress of the Action Plan (and atomic Actions), sending status messages on the bus. Those events may be used by the CEP to trigger new actions.
|
|
||||||
|
|
||||||
This component is also connected to the Watcher MySQL database in order to:
|
|
||||||
* get the description of the action plan to execute
|
|
||||||
* persist its current state so that if it is restarted, it can restore each Action plan context and restart from the last known safe point of each ongoing workflow.
|
|
||||||
@@ -22,8 +22,7 @@ import six
|
|||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(abc.ABCMeta)
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
class ApplierCommand(object):
|
class BaseActionPlanHandler(object):
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def execute(self):
|
def execute(self):
|
||||||
raise NotImplementedError(
|
raise NotImplementedError()
|
||||||
"Should have implemented this") # pragma:no cover
|
|
||||||
@@ -18,8 +18,8 @@
|
|||||||
#
|
#
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
|
|
||||||
|
from watcher.applier.action_plan.base import BaseActionPlanHandler
|
||||||
from watcher.applier.default import DefaultApplier
|
from watcher.applier.default import DefaultApplier
|
||||||
from watcher.applier.messaging.applier_command import ApplierCommand
|
|
||||||
from watcher.applier.messaging.events import Events
|
from watcher.applier.messaging.events import Events
|
||||||
from watcher.common.messaging.events.event import Event
|
from watcher.common.messaging.events.event import Event
|
||||||
from watcher.objects.action_plan import ActionPlan
|
from watcher.objects.action_plan import ActionPlan
|
||||||
@@ -28,23 +28,23 @@ from watcher.objects.action_plan import Status
|
|||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class LaunchActionPlanCommand(ApplierCommand):
|
class DefaultActionPlanHandler(BaseActionPlanHandler):
|
||||||
def __init__(self, context, manager_applier, action_plan_uuid):
|
def __init__(self, context, manager_applier, action_plan_uuid):
|
||||||
super(LaunchActionPlanCommand, self).__init__()
|
super(DefaultActionPlanHandler, self).__init__()
|
||||||
self.ctx = context
|
self.ctx = context
|
||||||
self.action_plan_uuid = action_plan_uuid
|
self.action_plan_uuid = action_plan_uuid
|
||||||
self.manager_applier = manager_applier
|
self.manager_applier = manager_applier
|
||||||
|
|
||||||
def notify(self, uuid, event_type, status):
|
def notify(self, uuid, event_type, state):
|
||||||
action_plan = ActionPlan.get_by_uuid(self.ctx, uuid)
|
action_plan = ActionPlan.get_by_uuid(self.ctx, uuid)
|
||||||
action_plan.state = status
|
action_plan.state = state
|
||||||
action_plan.save()
|
action_plan.save()
|
||||||
event = Event()
|
event = Event()
|
||||||
event.set_type(event_type)
|
event.type = event_type
|
||||||
event.set_data({})
|
event.data = {}
|
||||||
payload = {'action_plan__uuid': uuid,
|
payload = {'action_plan__uuid': uuid,
|
||||||
'action_plan_status': status}
|
'action_plan_state': state}
|
||||||
self.manager_applier.topic_status.publish_event(event.get_type().name,
|
self.manager_applier.topic_status.publish_event(event.type.name,
|
||||||
payload)
|
payload)
|
||||||
|
|
||||||
def execute(self):
|
def execute(self):
|
||||||
@@ -22,8 +22,7 @@ import six
|
|||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(abc.ABCMeta)
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
class Applier(object):
|
class BaseApplier(object):
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def execute(self, action_plan_uuid):
|
def execute(self, action_plan_uuid):
|
||||||
raise NotImplementedError(
|
raise NotImplementedError()
|
||||||
"Should have implemented this") # pragma:no cover
|
|
||||||
|
|||||||
@@ -16,18 +16,19 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
from watcher.applier.base import Applier
|
|
||||||
from watcher.applier.execution.executor import CommandExecutor
|
from watcher.applier.base import BaseApplier
|
||||||
|
from watcher.applier.execution.executor import ActionPlanExecutor
|
||||||
from watcher.objects import Action
|
from watcher.objects import Action
|
||||||
from watcher.objects import ActionPlan
|
from watcher.objects import ActionPlan
|
||||||
|
|
||||||
|
|
||||||
class DefaultApplier(Applier):
|
class DefaultApplier(BaseApplier):
|
||||||
def __init__(self, manager_applier, context):
|
def __init__(self, manager_applier, context):
|
||||||
super(DefaultApplier, self).__init__()
|
super(DefaultApplier, self).__init__()
|
||||||
self.manager_applier = manager_applier
|
self.manager_applier = manager_applier
|
||||||
self.context = context
|
self.context = context
|
||||||
self.executor = CommandExecutor(manager_applier, context)
|
self.executor = ActionPlanExecutor(manager_applier, context)
|
||||||
|
|
||||||
def execute(self, action_plan_uuid):
|
def execute(self, action_plan_uuid):
|
||||||
action_plan = ActionPlan.get_by_uuid(self.context, action_plan_uuid)
|
action_plan = ActionPlan.get_by_uuid(self.context, action_plan_uuid)
|
||||||
|
|||||||
@@ -24,25 +24,31 @@ LOG = log.getLogger(__name__)
|
|||||||
class DeployPhase(object):
|
class DeployPhase(object):
|
||||||
def __init__(self, executor):
|
def __init__(self, executor):
|
||||||
# todo(jed) oslo_conf 10 secondes
|
# todo(jed) oslo_conf 10 secondes
|
||||||
self.maxTimeout = 100000
|
self._max_timeout = 100000
|
||||||
self.commands = []
|
self._actions = []
|
||||||
self.executor = executor
|
self._executor = executor
|
||||||
|
|
||||||
def set_max_time(self, mt):
|
@property
|
||||||
self.maxTimeout = mt
|
def actions(self):
|
||||||
|
return self._actions
|
||||||
|
|
||||||
def get_max_time(self):
|
@property
|
||||||
return self.maxTimeout
|
def max_timeout(self):
|
||||||
|
return self._max_timeout
|
||||||
|
|
||||||
|
@max_timeout.setter
|
||||||
|
def max_timeout(self, m):
|
||||||
|
self._max_timeout = m
|
||||||
|
|
||||||
def populate(self, action):
|
def populate(self, action):
|
||||||
self.commands.append(action)
|
self._actions.append(action)
|
||||||
|
|
||||||
def execute_primitive(self, primitive):
|
def execute_primitive(self, primitive):
|
||||||
futur = primitive.execute(primitive)
|
future = primitive.execute(primitive)
|
||||||
return futur.result(self.get_max_time())
|
return future.result(self.max_timeout)
|
||||||
|
|
||||||
def rollback(self):
|
def rollback(self):
|
||||||
reverted = sorted(self.commands, reverse=True)
|
reverted = sorted(self.actions, reverse=True)
|
||||||
for primitive in reverted:
|
for primitive in reverted:
|
||||||
try:
|
try:
|
||||||
self.execute_primitive(primitive)
|
self.execute_primitive(primitive)
|
||||||
|
|||||||
@@ -17,10 +17,8 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
|
|
||||||
from watcher.applier.mapper.default import DefaultCommandMapper
|
|
||||||
|
|
||||||
from watcher.applier.execution.deploy_phase import DeployPhase
|
from watcher.applier.execution.deploy_phase import DeployPhase
|
||||||
|
from watcher.applier.mapping.default import DefaultActionMapper
|
||||||
from watcher.applier.messaging.events import Events
|
from watcher.applier.messaging.events import Events
|
||||||
from watcher.common.messaging.events.event import Event
|
from watcher.common.messaging.events.event import Event
|
||||||
from watcher.objects import Action
|
from watcher.objects import Action
|
||||||
@@ -29,26 +27,26 @@ from watcher.objects.action_plan import Status
|
|||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class CommandExecutor(object):
|
class ActionPlanExecutor(object):
|
||||||
def __init__(self, manager_applier, context):
|
def __init__(self, manager_applier, context):
|
||||||
self.manager_applier = manager_applier
|
self.manager_applier = manager_applier
|
||||||
self.context = context
|
self.context = context
|
||||||
self.deploy = DeployPhase(self)
|
self.deploy = DeployPhase(self)
|
||||||
self.mapper = DefaultCommandMapper()
|
self.mapper = DefaultActionMapper()
|
||||||
|
|
||||||
def get_primitive(self, action):
|
def get_primitive(self, action):
|
||||||
return self.mapper.build_primitive_command(action)
|
return self.mapper.build_primitive_from_action(action)
|
||||||
|
|
||||||
def notify(self, action, state):
|
def notify(self, action, state):
|
||||||
db_action = Action.get_by_uuid(self.context, action.uuid)
|
db_action = Action.get_by_uuid(self.context, action.uuid)
|
||||||
db_action.state = state
|
db_action.state = state
|
||||||
db_action.save()
|
db_action.save()
|
||||||
event = Event()
|
event = Event()
|
||||||
event.set_type(Events.LAUNCH_ACTION)
|
event.type = Events.LAUNCH_ACTION
|
||||||
event.set_data({})
|
event.data = {}
|
||||||
payload = {'action_uuid': action.uuid,
|
payload = {'action_uuid': action.uuid,
|
||||||
'action_status': state}
|
'action_state': state}
|
||||||
self.manager_applier.topic_status.publish_event(event.get_type().name,
|
self.manager_applier.topic_status.publish_event(event.type.name,
|
||||||
payload)
|
payload)
|
||||||
|
|
||||||
def execute(self, actions):
|
def execute(self, actions):
|
||||||
|
|||||||
@@ -23,9 +23,6 @@ from oslo_log import log
|
|||||||
|
|
||||||
from watcher.applier.messaging.trigger import TriggerActionPlan
|
from watcher.applier.messaging.trigger import TriggerActionPlan
|
||||||
from watcher.common.messaging.messaging_core import MessagingCore
|
from watcher.common.messaging.messaging_core import MessagingCore
|
||||||
from watcher.common.messaging.notification_handler import NotificationHandler
|
|
||||||
from watcher.decision_engine.messaging.events import Events
|
|
||||||
|
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
@@ -57,19 +54,8 @@ opt_group = cfg.OptGroup(name='watcher_applier',
|
|||||||
CONF.register_group(opt_group)
|
CONF.register_group(opt_group)
|
||||||
CONF.register_opts(APPLIER_MANAGER_OPTS, opt_group)
|
CONF.register_opts(APPLIER_MANAGER_OPTS, opt_group)
|
||||||
|
|
||||||
CONF.import_opt('admin_user', 'keystonemiddleware.auth_token',
|
|
||||||
group='keystone_authtoken')
|
|
||||||
CONF.import_opt('admin_tenant_name', 'keystonemiddleware.auth_token',
|
|
||||||
group='keystone_authtoken')
|
|
||||||
CONF.import_opt('admin_password', 'keystonemiddleware.auth_token',
|
|
||||||
group='keystone_authtoken')
|
|
||||||
CONF.import_opt('auth_uri', 'keystonemiddleware.auth_token',
|
|
||||||
group='keystone_authtoken')
|
|
||||||
|
|
||||||
|
|
||||||
class ApplierManager(MessagingCore):
|
class ApplierManager(MessagingCore):
|
||||||
# todo(jed) need workflow
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(ApplierManager, self).__init__(
|
super(ApplierManager, self).__init__(
|
||||||
CONF.watcher_applier.publisher_id,
|
CONF.watcher_applier.publisher_id,
|
||||||
@@ -79,24 +65,9 @@ class ApplierManager(MessagingCore):
|
|||||||
)
|
)
|
||||||
# shared executor of the workflow
|
# shared executor of the workflow
|
||||||
self.executor = ThreadPoolExecutor(max_workers=1)
|
self.executor = ThreadPoolExecutor(max_workers=1)
|
||||||
self.handler = NotificationHandler(self.publisher_id)
|
|
||||||
self.handler.register_observer(self)
|
|
||||||
self.add_event_listener(Events.ALL, self.event_receive)
|
|
||||||
# trigger action_plan
|
# trigger action_plan
|
||||||
self.topic_control.add_endpoint(TriggerActionPlan(self))
|
self.topic_control.add_endpoint(TriggerActionPlan(self))
|
||||||
|
|
||||||
def join(self):
|
def join(self):
|
||||||
self.topic_control.join()
|
self.topic_control.join()
|
||||||
self.topic_status.join()
|
self.topic_status.join()
|
||||||
|
|
||||||
def event_receive(self, event):
|
|
||||||
try:
|
|
||||||
request_id = event.get_request_id()
|
|
||||||
event_type = event.get_type()
|
|
||||||
data = event.get_data()
|
|
||||||
LOG.debug("request id => %s" % request_id)
|
|
||||||
LOG.debug("type_event => %s" % str(event_type))
|
|
||||||
LOG.debug("data => %s" % str(data))
|
|
||||||
except Exception as e:
|
|
||||||
LOG.error("evt %s" % e.message)
|
|
||||||
raise e
|
|
||||||
|
|||||||
@@ -22,8 +22,12 @@ import six
|
|||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(abc.ABCMeta)
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
class CommandMapper(object):
|
class BaseActionMapper(object):
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def build_primitive_command(self, action):
|
def build_primitive_from_action(self, action):
|
||||||
raise NotImplementedError(
|
"""Transform an action to a primitive
|
||||||
"Should have implemented this") # pragma:no cover
|
|
||||||
|
:type action: watcher.decision_engine.action.BaseAction
|
||||||
|
:return: the associated Primitive
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
@@ -17,31 +17,31 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
from watcher.applier.mapping.base import BaseActionMapper
|
||||||
from watcher.applier.mapper.base import CommandMapper
|
from watcher.applier.primitives.change_nova_service_state import \
|
||||||
from watcher.applier.primitive.hypervisor_state import HypervisorStateCommand
|
ChangeNovaServiceState
|
||||||
from watcher.applier.primitive.migration import MigrateCommand
|
from watcher.applier.primitives.migration import Migrate
|
||||||
from watcher.applier.primitive.nop import NopCommand
|
from watcher.applier.primitives.nop import Nop
|
||||||
from watcher.applier.primitive.power_state import PowerStateCommand
|
from watcher.applier.primitives.power_state import ChangePowerState
|
||||||
from watcher.common.exception import ActionNotFound
|
from watcher.common.exception import ActionNotFound
|
||||||
from watcher.decision_engine.planner.default import Primitives
|
from watcher.decision_engine.planner.default import Primitives
|
||||||
|
|
||||||
|
|
||||||
class DefaultCommandMapper(CommandMapper):
|
class DefaultActionMapper(BaseActionMapper):
|
||||||
def build_primitive_command(self, action):
|
def build_primitive_from_action(self, action):
|
||||||
if action.action_type == Primitives.COLD_MIGRATE.value:
|
if action.action_type == Primitives.COLD_MIGRATE.value:
|
||||||
return MigrateCommand(action.applies_to, Primitives.COLD_MIGRATE,
|
return Migrate(action.applies_to, Primitives.COLD_MIGRATE,
|
||||||
action.src,
|
action.src,
|
||||||
action.dst)
|
action.dst)
|
||||||
elif action.action_type == Primitives.LIVE_MIGRATE.value:
|
elif action.action_type == Primitives.LIVE_MIGRATE.value:
|
||||||
return MigrateCommand(action.applies_to, Primitives.COLD_MIGRATE,
|
return Migrate(action.applies_to, Primitives.COLD_MIGRATE,
|
||||||
action.src,
|
action.src,
|
||||||
action.dst)
|
action.dst)
|
||||||
elif action.action_type == Primitives.HYPERVISOR_STATE.value:
|
elif action.action_type == Primitives.HYPERVISOR_STATE.value:
|
||||||
return HypervisorStateCommand(action.applies_to, action.parameter)
|
return ChangeNovaServiceState(action.applies_to, action.parameter)
|
||||||
elif action.action_type == Primitives.POWER_STATE.value:
|
elif action.action_type == Primitives.POWER_STATE.value:
|
||||||
return PowerStateCommand()
|
return ChangePowerState()
|
||||||
elif action.action_type == Primitives.NOP.value:
|
elif action.action_type == Primitives.NOP.value:
|
||||||
return NopCommand()
|
return Nop()
|
||||||
else:
|
else:
|
||||||
raise ActionNotFound()
|
raise ActionNotFound()
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
#
|
#
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
|
|
||||||
from watcher.applier.messaging.launcher import LaunchActionPlanCommand
|
from watcher.applier.action_plan.default import DefaultActionPlanHandler
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
@@ -29,12 +29,12 @@ class TriggerActionPlan(object):
|
|||||||
|
|
||||||
def do_launch_action_plan(self, context, action_plan_uuid):
|
def do_launch_action_plan(self, context, action_plan_uuid):
|
||||||
try:
|
try:
|
||||||
cmd = LaunchActionPlanCommand(context,
|
cmd = DefaultActionPlanHandler(context,
|
||||||
self.manager_applier,
|
self.manager_applier,
|
||||||
action_plan_uuid)
|
action_plan_uuid)
|
||||||
cmd.execute()
|
cmd.execute()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.error("do_launch_action_plan " + unicode(e))
|
LOG.exception(e)
|
||||||
|
|
||||||
def launch_action_plan(self, context, action_plan_uuid):
|
def launch_action_plan(self, context, action_plan_uuid):
|
||||||
LOG.debug("Trigger ActionPlan %s" % action_plan_uuid)
|
LOG.debug("Trigger ActionPlan %s" % action_plan_uuid)
|
||||||
|
|||||||
@@ -1,61 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
# Copyright (c) 2015 b<>com
|
|
||||||
#
|
|
||||||
# Authors: Jean-Emile DARTOIS <jean-emile.dartois@b-com.com>
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
# implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
from oslo_config import cfg
|
|
||||||
|
|
||||||
from watcher.applier.primitive.base import PrimitiveCommand
|
|
||||||
from watcher.applier.primitive.wrapper.nova_wrapper import NovaWrapper
|
|
||||||
from watcher.applier.promise import Promise
|
|
||||||
|
|
||||||
from watcher.common.keystone import KeystoneClient
|
|
||||||
from watcher.decision_engine.model.hypervisor_state import HypervisorState
|
|
||||||
|
|
||||||
CONF = cfg.CONF
|
|
||||||
|
|
||||||
|
|
||||||
class HypervisorStateCommand(PrimitiveCommand):
|
|
||||||
def __init__(self, host, status):
|
|
||||||
self.host = host
|
|
||||||
self.status = status
|
|
||||||
|
|
||||||
def nova_manage_service(self, status):
|
|
||||||
keystone = KeystoneClient()
|
|
||||||
wrapper = NovaWrapper(keystone.get_credentials(),
|
|
||||||
session=keystone.get_session())
|
|
||||||
if status is True:
|
|
||||||
return wrapper.enable_service_nova_compute(self.host)
|
|
||||||
else:
|
|
||||||
return wrapper.disable_service_nova_compute(self.host)
|
|
||||||
|
|
||||||
@Promise
|
|
||||||
def execute(self):
|
|
||||||
if self.status == HypervisorState.OFFLINE.value:
|
|
||||||
state = False
|
|
||||||
elif self.status == HypervisorState.ONLINE.value:
|
|
||||||
state = True
|
|
||||||
return self.nova_manage_service(state)
|
|
||||||
|
|
||||||
@Promise
|
|
||||||
def undo(self):
|
|
||||||
if self.status == HypervisorState.OFFLINE.value:
|
|
||||||
state = True
|
|
||||||
elif self.status == HypervisorState.ONLINE.value:
|
|
||||||
state = False
|
|
||||||
return self.nova_manage_service(state)
|
|
||||||
@@ -22,15 +22,13 @@ from watcher.applier.promise import Promise
|
|||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(abc.ABCMeta)
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
class PrimitiveCommand(object):
|
class BasePrimitive(object):
|
||||||
@Promise
|
@Promise
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def execute(self):
|
def execute(self):
|
||||||
raise NotImplementedError(
|
raise NotImplementedError()
|
||||||
"Should have implemented this") # pragma:no cover
|
|
||||||
|
|
||||||
@Promise
|
@Promise
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def undo(self):
|
def undo(self):
|
||||||
raise NotImplementedError(
|
raise NotImplementedError()
|
||||||
"Should have implemented this") # pragma:no cover
|
|
||||||
83
watcher/applier/primitives/change_nova_service_state.py
Normal file
83
watcher/applier/primitives/change_nova_service_state.py
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
# Copyright (c) 2015 b<>com
|
||||||
|
#
|
||||||
|
# Authors: Jean-Emile DARTOIS <jean-emile.dartois@b-com.com>
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
# implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
|
||||||
|
from watcher._i18n import _
|
||||||
|
from watcher.applier.primitives.base import BasePrimitive
|
||||||
|
from watcher.applier.promise import Promise
|
||||||
|
from watcher.common.exception import IllegalArgumentException
|
||||||
|
from watcher.common.keystone import KeystoneClient
|
||||||
|
from watcher.common.nova import NovaClient
|
||||||
|
from watcher.decision_engine.model.hypervisor_state import HypervisorState
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
|
class ChangeNovaServiceState(BasePrimitive):
|
||||||
|
def __init__(self, host, state):
|
||||||
|
"""This class allows us to change the state of nova-compute service.
|
||||||
|
|
||||||
|
:param host: the uuid of the host
|
||||||
|
:param state: (enabled/disabled)
|
||||||
|
"""
|
||||||
|
super(BasePrimitive, self).__init__()
|
||||||
|
self._host = host
|
||||||
|
self._state = state
|
||||||
|
|
||||||
|
@property
|
||||||
|
def host(self):
|
||||||
|
return self._host
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
return self._state
|
||||||
|
|
||||||
|
@Promise
|
||||||
|
def execute(self):
|
||||||
|
target_state = None
|
||||||
|
if self.state == HypervisorState.OFFLINE.value:
|
||||||
|
target_state = False
|
||||||
|
elif self.status == HypervisorState.ONLINE.value:
|
||||||
|
target_state = True
|
||||||
|
return self.nova_manage_service(target_state)
|
||||||
|
|
||||||
|
@Promise
|
||||||
|
def undo(self):
|
||||||
|
target_state = None
|
||||||
|
if self.state == HypervisorState.OFFLINE.value:
|
||||||
|
target_state = True
|
||||||
|
elif self.state == HypervisorState.ONLINE.value:
|
||||||
|
target_state = False
|
||||||
|
return self.nova_manage_service(target_state)
|
||||||
|
|
||||||
|
def nova_manage_service(self, state):
|
||||||
|
if state is None:
|
||||||
|
raise IllegalArgumentException(
|
||||||
|
_("The target state is not defined"))
|
||||||
|
|
||||||
|
keystone = KeystoneClient()
|
||||||
|
wrapper = NovaClient(keystone.get_credentials(),
|
||||||
|
session=keystone.get_session())
|
||||||
|
if state is True:
|
||||||
|
return wrapper.enable_service_nova_compute(self.host)
|
||||||
|
else:
|
||||||
|
return wrapper.disable_service_nova_compute(self.host)
|
||||||
@@ -21,20 +21,21 @@ from keystoneclient.auth.identity import v3
|
|||||||
from keystoneclient import session
|
from keystoneclient import session
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
|
||||||
from watcher.applier.primitive.base import PrimitiveCommand
|
from watcher.applier.primitives.base import BasePrimitive
|
||||||
from watcher.applier.primitive.wrapper.nova_wrapper import NovaWrapper
|
|
||||||
from watcher.applier.promise import Promise
|
from watcher.applier.promise import Promise
|
||||||
from watcher.common.keystone import KeystoneClient
|
from watcher.common.keystone import KeystoneClient
|
||||||
|
from watcher.common.nova import NovaClient
|
||||||
from watcher.decision_engine.planner.default import Primitives
|
from watcher.decision_engine.planner.default import Primitives
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
class MigrateCommand(PrimitiveCommand):
|
class Migrate(BasePrimitive):
|
||||||
def __init__(self, vm_uuid=None,
|
def __init__(self, vm_uuid=None,
|
||||||
migration_type=None,
|
migration_type=None,
|
||||||
source_hypervisor=None,
|
source_hypervisor=None,
|
||||||
destination_hypervisor=None):
|
destination_hypervisor=None):
|
||||||
|
super(BasePrimitive, self).__init__()
|
||||||
self.instance_uuid = vm_uuid
|
self.instance_uuid = vm_uuid
|
||||||
self.migration_type = migration_type
|
self.migration_type = migration_type
|
||||||
self.source_hypervisor = source_hypervisor
|
self.source_hypervisor = source_hypervisor
|
||||||
@@ -42,8 +43,8 @@ class MigrateCommand(PrimitiveCommand):
|
|||||||
|
|
||||||
def migrate(self, destination):
|
def migrate(self, destination):
|
||||||
keystone = KeystoneClient()
|
keystone = KeystoneClient()
|
||||||
wrapper = NovaWrapper(keystone.get_credentials(),
|
wrapper = NovaClient(keystone.get_credentials(),
|
||||||
session=keystone.get_session())
|
session=keystone.get_session())
|
||||||
instance = wrapper.find_instance(self.instance_uuid)
|
instance = wrapper.find_instance(self.instance_uuid)
|
||||||
if instance:
|
if instance:
|
||||||
project_id = getattr(instance, "tenant_id")
|
project_id = getattr(instance, "tenant_id")
|
||||||
@@ -64,7 +65,7 @@ class MigrateCommand(PrimitiveCommand):
|
|||||||
project_domain_name=creds2[
|
project_domain_name=creds2[
|
||||||
'project_domain_name'])
|
'project_domain_name'])
|
||||||
sess2 = session.Session(auth=auth2)
|
sess2 = session.Session(auth=auth2)
|
||||||
wrapper2 = NovaWrapper(creds2, session=sess2)
|
wrapper2 = NovaClient(creds2, session=sess2)
|
||||||
|
|
||||||
# todo(jed) remove Primitves
|
# todo(jed) remove Primitves
|
||||||
if self.migration_type is Primitives.COLD_MIGRATE:
|
if self.migration_type is Primitives.COLD_MIGRATE:
|
||||||
@@ -19,16 +19,15 @@
|
|||||||
|
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
|
|
||||||
from watcher.applier.primitive.base import PrimitiveCommand
|
|
||||||
|
from watcher.applier.primitives.base import BasePrimitive
|
||||||
from watcher.applier.promise import Promise
|
from watcher.applier.promise import Promise
|
||||||
|
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class NopCommand(PrimitiveCommand):
|
class Nop(BasePrimitive):
|
||||||
def __init__(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@Promise
|
@Promise
|
||||||
def execute(self):
|
def execute(self):
|
||||||
@@ -16,20 +16,17 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
from watcher.applier.primitive.base import PrimitiveCommand
|
|
||||||
|
from watcher.applier.primitives.base import BasePrimitive
|
||||||
from watcher.applier.promise import Promise
|
from watcher.applier.promise import Promise
|
||||||
|
|
||||||
|
|
||||||
class PowerStateCommand(PrimitiveCommand):
|
class ChangePowerState(BasePrimitive):
|
||||||
def __init__(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@Promise
|
@Promise
|
||||||
def execute(self):
|
def execute(self):
|
||||||
pass
|
raise NotImplementedError # pragma:no cover
|
||||||
|
|
||||||
@Promise
|
@Promise
|
||||||
def undo(self):
|
def undo(self):
|
||||||
# TODO(jde): migrate VM from target_hypervisor
|
raise NotImplementedError # pragma:no cover
|
||||||
# to current_hypervisor in model
|
|
||||||
return True
|
|
||||||
@@ -20,7 +20,6 @@ from oslo_config import cfg
|
|||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
import oslo_messaging as om
|
import oslo_messaging as om
|
||||||
|
|
||||||
|
|
||||||
from watcher.applier.manager import APPLIER_MANAGER_OPTS
|
from watcher.applier.manager import APPLIER_MANAGER_OPTS
|
||||||
from watcher.applier.manager import opt_group
|
from watcher.applier.manager import opt_group
|
||||||
from watcher.common import exception
|
from watcher.common import exception
|
||||||
@@ -69,5 +68,5 @@ class ApplierAPI(MessagingCore):
|
|||||||
try:
|
try:
|
||||||
pass
|
pass
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.error("evt %s" % e.message)
|
LOG.exception(e)
|
||||||
raise e
|
raise
|
||||||
|
|||||||
@@ -19,14 +19,15 @@
|
|||||||
|
|
||||||
import logging as std_logging
|
import logging as std_logging
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
from wsgiref import simple_server
|
from wsgiref import simple_server
|
||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
|
||||||
|
from watcher._i18n import _
|
||||||
from watcher.api import app as api_app
|
from watcher.api import app as api_app
|
||||||
from watcher.common.i18n import _
|
from watcher.common import service
|
||||||
from watcher import service
|
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
@@ -34,7 +35,7 @@ CONF = cfg.CONF
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
service.prepare_service()
|
service.prepare_service(sys.argv)
|
||||||
|
|
||||||
app = api_app.setup_app()
|
app = api_app.setup_app()
|
||||||
|
|
||||||
@@ -42,7 +43,6 @@ def main():
|
|||||||
host, port = cfg.CONF.api.host, cfg.CONF.api.port
|
host, port = cfg.CONF.api.host, cfg.CONF.api.port
|
||||||
srv = simple_server.make_server(host, port, app)
|
srv = simple_server.make_server(host, port, app)
|
||||||
|
|
||||||
logging.setup(CONF, 'watcher')
|
|
||||||
LOG.info(_('Starting server in PID %s') % os.getpid())
|
LOG.info(_('Starting server in PID %s') % os.getpid())
|
||||||
LOG.debug("Watcher configuration:")
|
LOG.debug("Watcher configuration:")
|
||||||
cfg.CONF.log_opt_values(LOG, std_logging.DEBUG)
|
cfg.CONF.log_opt_values(LOG, std_logging.DEBUG)
|
||||||
|
|||||||
@@ -24,13 +24,13 @@ import sys
|
|||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
|
||||||
|
from watcher import _i18n
|
||||||
from watcher.applier.manager import ApplierManager
|
from watcher.applier.manager import ApplierManager
|
||||||
from watcher.common import service
|
from watcher.common import service
|
||||||
from watcher import i18n
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
_LI = i18n._LI
|
_LI = _i18n._LI
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|||||||
@@ -23,15 +23,15 @@ import sys
|
|||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
|
||||||
|
from watcher import _i18n
|
||||||
from watcher.common import service
|
from watcher.common import service
|
||||||
from watcher.decision_engine.manager import DecisionEngineManager
|
from watcher.decision_engine.manager import DecisionEngineManager
|
||||||
|
|
||||||
from watcher import i18n
|
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
_LI = i18n._LI
|
_LI = _i18n._LI
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
from ceilometerclient import client
|
from ceilometerclient import client
|
||||||
from ceilometerclient.exc import HTTPUnauthorized
|
from ceilometerclient.exc import HTTPUnauthorized
|
||||||
|
|
||||||
from watcher.common import keystone
|
from watcher.common import keystone
|
||||||
|
|
||||||
|
|
||||||
@@ -44,6 +45,7 @@ class CeilometerClient(object):
|
|||||||
def build_query(self, user_id=None, tenant_id=None, resource_id=None,
|
def build_query(self, user_id=None, tenant_id=None, resource_id=None,
|
||||||
user_ids=None, tenant_ids=None, resource_ids=None):
|
user_ids=None, tenant_ids=None, resource_ids=None):
|
||||||
"""Returns query built from given parameters.
|
"""Returns query built from given parameters.
|
||||||
|
|
||||||
This query can be then used for querying resources, meters and
|
This query can be then used for querying resources, meters and
|
||||||
statistics.
|
statistics.
|
||||||
:Parameters:
|
:Parameters:
|
||||||
@@ -54,6 +56,7 @@ class CeilometerClient(object):
|
|||||||
- `tenant_ids`: list of tenant_ids
|
- `tenant_ids`: list of tenant_ids
|
||||||
- `resource_ids`: list of resource_ids
|
- `resource_ids`: list of resource_ids
|
||||||
"""
|
"""
|
||||||
|
|
||||||
user_ids = user_ids or []
|
user_ids = user_ids or []
|
||||||
tenant_ids = tenant_ids or []
|
tenant_ids = tenant_ids or []
|
||||||
resource_ids = resource_ids or []
|
resource_ids = resource_ids or []
|
||||||
@@ -109,7 +112,8 @@ class CeilometerClient(object):
|
|||||||
meter_name,
|
meter_name,
|
||||||
period,
|
period,
|
||||||
aggregate='avg'):
|
aggregate='avg'):
|
||||||
"""
|
"""Representing a statistic aggregate by operators
|
||||||
|
|
||||||
:param resource_id: id
|
:param resource_id: id
|
||||||
:param meter_name: meter names of which we want the statistics
|
:param meter_name: meter names of which we want the statistics
|
||||||
:param period: `period`: In seconds. If no period is given, only one
|
:param period: `period`: In seconds. If no period is given, only one
|
||||||
@@ -119,7 +123,6 @@ class CeilometerClient(object):
|
|||||||
:param aggregate:
|
:param aggregate:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
"""Representing a statistic aggregate by operators"""
|
|
||||||
|
|
||||||
query = self.build_query(resource_id=resource_id)
|
query = self.build_query(resource_id=resource_id)
|
||||||
statistic = self.query_retry(f=self.cmclient.statistics.list,
|
statistic = self.query_retry(f=self.cmclient.statistics.list,
|
||||||
|
|||||||
@@ -24,11 +24,9 @@ SHOULD include dedicated exception logging.
|
|||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from watcher.common.i18n import _
|
from watcher._i18n import _, _LE
|
||||||
from watcher.common.i18n import _LE
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -43,8 +41,8 @@ CONF.register_opts(exc_log_opts)
|
|||||||
|
|
||||||
|
|
||||||
def _cleanse_dict(original):
|
def _cleanse_dict(original):
|
||||||
"""Strip all admin_password, new_pass, rescue_pass keys from a dict."""
|
"""Strip all admin_password, new_pass, rescue_pass keys from a dict"""
|
||||||
return dict((k, v) for k, v in original.iteritems() if "_pass" not in k)
|
return dict((k, v) for k, v in six.iteritems(original) if "_pass" not in k)
|
||||||
|
|
||||||
|
|
||||||
class WatcherException(Exception):
|
class WatcherException(Exception):
|
||||||
@@ -55,7 +53,7 @@ class WatcherException(Exception):
|
|||||||
with the keyword arguments provided to the constructor.
|
with the keyword arguments provided to the constructor.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
message = _("An unknown exception occurred.")
|
message = _("An unknown exception occurred")
|
||||||
code = 500
|
code = 500
|
||||||
headers = {}
|
headers = {}
|
||||||
safe = False
|
safe = False
|
||||||
@@ -77,8 +75,8 @@ class WatcherException(Exception):
|
|||||||
# kwargs doesn't match a variable in the message
|
# kwargs doesn't match a variable in the message
|
||||||
# log the issue and the kwargs
|
# log the issue and the kwargs
|
||||||
LOG.exception(_LE('Exception in string format operation'))
|
LOG.exception(_LE('Exception in string format operation'))
|
||||||
for name, value in kwargs.iteritems():
|
for name, value in six.iteritems(kwargs):
|
||||||
LOG.error("%s: %s" % (name, value))
|
LOG.error("%s: %s", name, value)
|
||||||
|
|
||||||
if CONF.fatal_exception_format_errors:
|
if CONF.fatal_exception_format_errors:
|
||||||
raise e
|
raise e
|
||||||
@@ -89,7 +87,7 @@ class WatcherException(Exception):
|
|||||||
super(WatcherException, self).__init__(message)
|
super(WatcherException, self).__init__(message)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
"""Encode to utf-8 then wsme api can consume it as well."""
|
"""Encode to utf-8 then wsme api can consume it as well"""
|
||||||
if not six.PY3:
|
if not six.PY3:
|
||||||
return unicode(self.args[0]).encode('utf-8')
|
return unicode(self.args[0]).encode('utf-8')
|
||||||
else:
|
else:
|
||||||
@@ -106,39 +104,39 @@ class WatcherException(Exception):
|
|||||||
|
|
||||||
|
|
||||||
class NotAuthorized(WatcherException):
|
class NotAuthorized(WatcherException):
|
||||||
message = _("Not authorized.")
|
message = _("Not authorized")
|
||||||
code = 403
|
code = 403
|
||||||
|
|
||||||
|
|
||||||
class OperationNotPermitted(NotAuthorized):
|
class OperationNotPermitted(NotAuthorized):
|
||||||
message = _("Operation not permitted.")
|
message = _("Operation not permitted")
|
||||||
|
|
||||||
|
|
||||||
class Invalid(WatcherException):
|
class Invalid(WatcherException):
|
||||||
message = _("Unacceptable parameters.")
|
message = _("Unacceptable parameters")
|
||||||
code = 400
|
code = 400
|
||||||
|
|
||||||
|
|
||||||
class ObjectNotFound(WatcherException):
|
class ObjectNotFound(WatcherException):
|
||||||
message = _("The %(name)s %(id)s could not be found.")
|
message = _("The %(name)s %(id)s could not be found")
|
||||||
|
|
||||||
|
|
||||||
class Conflict(WatcherException):
|
class Conflict(WatcherException):
|
||||||
message = _('Conflict.')
|
message = _('Conflict')
|
||||||
code = 409
|
code = 409
|
||||||
|
|
||||||
|
|
||||||
class ResourceNotFound(ObjectNotFound):
|
class ResourceNotFound(ObjectNotFound):
|
||||||
message = _("The %(name)s resource %(id)s could not be found.")
|
message = _("The %(name)s resource %(id)s could not be found")
|
||||||
code = 404
|
code = 404
|
||||||
|
|
||||||
|
|
||||||
class InvalidIdentity(Invalid):
|
class InvalidIdentity(Invalid):
|
||||||
message = _("Expected an uuid or int but received %(identity)s.")
|
message = _("Expected an uuid or int but received %(identity)s")
|
||||||
|
|
||||||
|
|
||||||
class InvalidGoal(Invalid):
|
class InvalidGoal(Invalid):
|
||||||
message = _("Goal %(goal)s is not defined in Watcher configuration file.")
|
message = _("Goal %(goal)s is not defined in Watcher configuration file")
|
||||||
|
|
||||||
|
|
||||||
# Cannot be templated as the error syntax varies.
|
# Cannot be templated as the error syntax varies.
|
||||||
@@ -148,73 +146,73 @@ class InvalidParameterValue(Invalid):
|
|||||||
|
|
||||||
|
|
||||||
class InvalidUUID(Invalid):
|
class InvalidUUID(Invalid):
|
||||||
message = _("Expected a uuid but received %(uuid)s.")
|
message = _("Expected a uuid but received %(uuid)s")
|
||||||
|
|
||||||
|
|
||||||
class InvalidName(Invalid):
|
class InvalidName(Invalid):
|
||||||
message = _("Expected a logical name but received %(name)s.")
|
message = _("Expected a logical name but received %(name)s")
|
||||||
|
|
||||||
|
|
||||||
class InvalidUuidOrName(Invalid):
|
class InvalidUuidOrName(Invalid):
|
||||||
message = _("Expected a logical name or uuid but received %(name)s.")
|
message = _("Expected a logical name or uuid but received %(name)s")
|
||||||
|
|
||||||
|
|
||||||
class AuditTemplateNotFound(ResourceNotFound):
|
class AuditTemplateNotFound(ResourceNotFound):
|
||||||
message = _("AuditTemplate %(audit_template)s could not be found.")
|
message = _("AuditTemplate %(audit_template)s could not be found")
|
||||||
|
|
||||||
|
|
||||||
class AuditTemplateAlreadyExists(Conflict):
|
class AuditTemplateAlreadyExists(Conflict):
|
||||||
message = _("An audit_template with UUID %(uuid)s or name %(name)s "
|
message = _("An audit_template with UUID %(uuid)s or name %(name)s "
|
||||||
"already exists.")
|
"already exists")
|
||||||
|
|
||||||
|
|
||||||
class AuditTemplateReferenced(Invalid):
|
class AuditTemplateReferenced(Invalid):
|
||||||
message = _("AuditTemplate %(audit_template)s is referenced by one or "
|
message = _("AuditTemplate %(audit_template)s is referenced by one or "
|
||||||
"multiple audit.")
|
"multiple audit")
|
||||||
|
|
||||||
|
|
||||||
class AuditNotFound(ResourceNotFound):
|
class AuditNotFound(ResourceNotFound):
|
||||||
message = _("Audit %(audit)s could not be found.")
|
message = _("Audit %(audit)s could not be found")
|
||||||
|
|
||||||
|
|
||||||
class AuditAlreadyExists(Conflict):
|
class AuditAlreadyExists(Conflict):
|
||||||
message = _("An audit with UUID %(uuid)s already exists.")
|
message = _("An audit with UUID %(uuid)s already exists")
|
||||||
|
|
||||||
|
|
||||||
class AuditReferenced(Invalid):
|
class AuditReferenced(Invalid):
|
||||||
message = _("Audit %(audit)s is referenced by one or multiple action "
|
message = _("Audit %(audit)s is referenced by one or multiple action "
|
||||||
"plans.")
|
"plans")
|
||||||
|
|
||||||
|
|
||||||
class ActionPlanNotFound(ResourceNotFound):
|
class ActionPlanNotFound(ResourceNotFound):
|
||||||
message = _("ActionPlan %(action plan)s could not be found.")
|
message = _("ActionPlan %(action plan)s could not be found")
|
||||||
|
|
||||||
|
|
||||||
class ActionPlanAlreadyExists(Conflict):
|
class ActionPlanAlreadyExists(Conflict):
|
||||||
message = _("An action plan with UUID %(uuid)s already exists.")
|
message = _("An action plan with UUID %(uuid)s already exists")
|
||||||
|
|
||||||
|
|
||||||
class ActionPlanReferenced(Invalid):
|
class ActionPlanReferenced(Invalid):
|
||||||
message = _("Action Plan %(action_plan)s is referenced by one or "
|
message = _("Action Plan %(action_plan)s is referenced by one or "
|
||||||
"multiple actions.")
|
"multiple actions")
|
||||||
|
|
||||||
|
|
||||||
class ActionNotFound(ResourceNotFound):
|
class ActionNotFound(ResourceNotFound):
|
||||||
message = _("Action %(action)s could not be found.")
|
message = _("Action %(action)s could not be found")
|
||||||
|
|
||||||
|
|
||||||
class ActionAlreadyExists(Conflict):
|
class ActionAlreadyExists(Conflict):
|
||||||
message = _("An action with UUID %(uuid)s already exists.")
|
message = _("An action with UUID %(uuid)s already exists")
|
||||||
|
|
||||||
|
|
||||||
class ActionReferenced(Invalid):
|
class ActionReferenced(Invalid):
|
||||||
message = _("Action plan %(action_plan)s is referenced by one or "
|
message = _("Action plan %(action_plan)s is referenced by one or "
|
||||||
"multiple goals.")
|
"multiple goals")
|
||||||
|
|
||||||
|
|
||||||
class ActionFilterCombinationProhibited(Invalid):
|
class ActionFilterCombinationProhibited(Invalid):
|
||||||
message = _("Filtering actions on both audit and action-plan is "
|
message = _("Filtering actions on both audit and action-plan is "
|
||||||
"prohibited.")
|
"prohibited")
|
||||||
|
|
||||||
|
|
||||||
class HTTPNotFound(ResourceNotFound):
|
class HTTPNotFound(ResourceNotFound):
|
||||||
@@ -231,9 +229,8 @@ class PatchError(Invalid):
|
|||||||
class BaseException(Exception):
|
class BaseException(Exception):
|
||||||
|
|
||||||
def __init__(self, desc=""):
|
def __init__(self, desc=""):
|
||||||
if (not isinstance(desc, basestring)):
|
if (not isinstance(desc, six.string_types)):
|
||||||
raise IllegalArgumentException(
|
raise ValueError(_("Description must be an instance of str"))
|
||||||
"Description must be an instance of str")
|
|
||||||
|
|
||||||
desc = desc.strip()
|
desc = desc.strip()
|
||||||
|
|
||||||
@@ -243,7 +240,7 @@ class BaseException(Exception):
|
|||||||
return self._desc
|
return self._desc
|
||||||
|
|
||||||
def get_message(self):
|
def get_message(self):
|
||||||
return "An exception occurred without a description."
|
return _("An exception occurred without a description")
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.get_message()
|
return self.get_message()
|
||||||
@@ -251,10 +248,8 @@ class BaseException(Exception):
|
|||||||
|
|
||||||
class IllegalArgumentException(BaseException):
|
class IllegalArgumentException(BaseException):
|
||||||
def __init__(self, desc):
|
def __init__(self, desc):
|
||||||
BaseException.__init__(self, desc)
|
desc = desc or _("Description cannot be empty")
|
||||||
|
super(IllegalArgumentException, self).__init__(desc)
|
||||||
if self._desc == "":
|
|
||||||
raise IllegalArgumentException("Description cannot be empty")
|
|
||||||
|
|
||||||
def get_message(self):
|
def get_message(self):
|
||||||
return self._desc
|
return self._desc
|
||||||
@@ -262,10 +257,8 @@ class IllegalArgumentException(BaseException):
|
|||||||
|
|
||||||
class NoSuchMetric(BaseException):
|
class NoSuchMetric(BaseException):
|
||||||
def __init__(self, desc):
|
def __init__(self, desc):
|
||||||
BaseException.__init__(self, desc)
|
desc = desc or _("No such metric")
|
||||||
|
super(NoSuchMetric, self).__init__(desc)
|
||||||
if self._desc == "":
|
|
||||||
raise NoSuchMetric("No such metric")
|
|
||||||
|
|
||||||
def get_message(self):
|
def get_message(self):
|
||||||
return self._desc
|
return self._desc
|
||||||
@@ -273,10 +266,8 @@ class NoSuchMetric(BaseException):
|
|||||||
|
|
||||||
class NoDataFound(BaseException):
|
class NoDataFound(BaseException):
|
||||||
def __init__(self, desc):
|
def __init__(self, desc):
|
||||||
BaseException.__init__(self, desc)
|
desc = desc or _("No rows were returned")
|
||||||
|
super(NoDataFound, self).__init__(desc)
|
||||||
if self._desc == "":
|
|
||||||
raise NoSuchMetric("no rows were returned")
|
|
||||||
|
|
||||||
def get_message(self):
|
def get_message(self):
|
||||||
return self._desc
|
return self._desc
|
||||||
@@ -287,26 +278,26 @@ class KeystoneFailure(WatcherException):
|
|||||||
|
|
||||||
|
|
||||||
class ClusterEmpty(WatcherException):
|
class ClusterEmpty(WatcherException):
|
||||||
message = _("The list of hypervisor(s) in the cluster is empty.'")
|
message = _("The list of hypervisor(s) in the cluster is empty")
|
||||||
|
|
||||||
|
|
||||||
class MetricCollectorNotDefined(WatcherException):
|
class MetricCollectorNotDefined(WatcherException):
|
||||||
message = _("The metrics resource collector is not defined.'")
|
message = _("The metrics resource collector is not defined")
|
||||||
|
|
||||||
|
|
||||||
class ClusteStateNotDefined(WatcherException):
|
class ClusterStateNotDefined(WatcherException):
|
||||||
message = _("the cluster state is not defined")
|
message = _("the cluster state is not defined")
|
||||||
|
|
||||||
|
|
||||||
# Model
|
# Model
|
||||||
|
|
||||||
class VMNotFound(WatcherException):
|
class VMNotFound(WatcherException):
|
||||||
message = _("The VM could not be found.")
|
message = _("The VM could not be found")
|
||||||
|
|
||||||
|
|
||||||
class HypervisorNotFound(WatcherException):
|
class HypervisorNotFound(WatcherException):
|
||||||
message = _("The hypervisor could not be found.")
|
message = _("The hypervisor could not be found")
|
||||||
|
|
||||||
|
|
||||||
class MetaActionNotFound(WatcherException):
|
class MetaActionNotFound(WatcherException):
|
||||||
message = _("The Meta-Action could not be found.")
|
message = _("The Meta-Action could not be found")
|
||||||
|
|||||||
@@ -17,16 +17,15 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
|
||||||
from oslo_config import cfg
|
|
||||||
from oslo_log import log
|
|
||||||
|
|
||||||
from urlparse import urljoin
|
|
||||||
from urlparse import urlparse
|
|
||||||
|
|
||||||
from keystoneclient.auth.identity import generic
|
from keystoneclient.auth.identity import generic
|
||||||
from keystoneclient import session as keystone_session
|
from keystoneclient import session as keystone_session
|
||||||
|
from oslo_config import cfg
|
||||||
|
from oslo_log import log
|
||||||
|
from six.moves.urllib.parse import urljoin
|
||||||
|
from six.moves.urllib.parse import urlparse
|
||||||
|
|
||||||
from watcher.common.exception import KeystoneFailure
|
from watcher._i18n import _
|
||||||
|
from watcher.common import exception
|
||||||
|
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
@@ -54,16 +53,17 @@ class KeystoneClient(object):
|
|||||||
self._token = None
|
self._token = None
|
||||||
|
|
||||||
def get_endpoint(self, **kwargs):
|
def get_endpoint(self, **kwargs):
|
||||||
kc = self._get_ksclient()
|
kc = self.get_ksclient()
|
||||||
if not kc.has_service_catalog():
|
if not kc.has_service_catalog():
|
||||||
raise KeystoneFailure('No Keystone service catalog '
|
raise exception.KeystoneFailure(
|
||||||
'loaded')
|
_('No Keystone service catalog loaded')
|
||||||
|
)
|
||||||
attr = None
|
attr = None
|
||||||
filter_value = None
|
filter_value = None
|
||||||
if kwargs.get('region_name'):
|
if kwargs.get('region_name'):
|
||||||
attr = 'region'
|
attr = 'region'
|
||||||
filter_value = kwargs.get('region_name')
|
filter_value = kwargs.get('region_name')
|
||||||
return self._get_ksclient().service_catalog.url_for(
|
return kc.service_catalog.url_for(
|
||||||
service_type=kwargs.get('service_type') or 'metering',
|
service_type=kwargs.get('service_type') or 'metering',
|
||||||
attr=attr,
|
attr=attr,
|
||||||
filter_value=filter_value,
|
filter_value=filter_value,
|
||||||
@@ -73,23 +73,25 @@ class KeystoneClient(object):
|
|||||||
return auth_version == 'v3.0' or '/v3' in urlparse(auth_url).path
|
return auth_version == 'v3.0' or '/v3' in urlparse(auth_url).path
|
||||||
|
|
||||||
def get_keystone_url(self, auth_url, auth_version):
|
def get_keystone_url(self, auth_url, auth_version):
|
||||||
"""Gives an http/https url to contact keystone.
|
"""Gives an http/https url to contact keystone."""
|
||||||
"""
|
|
||||||
api_v3 = self._is_apiv3(auth_url, auth_version)
|
api_v3 = self._is_apiv3(auth_url, auth_version)
|
||||||
api_version = 'v3' if api_v3 else 'v2.0'
|
api_version = 'v3' if api_v3 else 'v2.0'
|
||||||
# NOTE(lucasagomes): Get rid of the trailing '/' otherwise urljoin()
|
# NOTE(lucasagomes): Get rid of the trailing '/' otherwise urljoin()
|
||||||
# fails to override the version in the URL
|
# fails to override the version in the URL
|
||||||
return urljoin(auth_url.rstrip('/'), api_version)
|
return urljoin(auth_url.rstrip('/'), api_version)
|
||||||
|
|
||||||
def _get_ksclient(self):
|
def get_ksclient(self, creds=None):
|
||||||
"""Get an endpoint and auth token from Keystone.
|
"""Get an endpoint and auth token from Keystone."""
|
||||||
"""
|
|
||||||
ks_args = self.get_credentials()
|
|
||||||
auth_version = CONF.keystone_authtoken.auth_version
|
auth_version = CONF.keystone_authtoken.auth_version
|
||||||
auth_url = CONF.keystone_authtoken.auth_uri
|
auth_url = CONF.keystone_authtoken.auth_uri
|
||||||
api_version = self._is_apiv3(auth_url, auth_version)
|
api_v3 = self._is_apiv3(auth_url, auth_version)
|
||||||
|
if creds is None:
|
||||||
|
ks_args = self._get_credentials(api_v3)
|
||||||
|
else:
|
||||||
|
ks_args = creds
|
||||||
|
|
||||||
if api_version:
|
if api_v3:
|
||||||
from keystoneclient.v3 import client
|
from keystoneclient.v3 import client
|
||||||
else:
|
else:
|
||||||
from keystoneclient.v2_0 import client
|
from keystoneclient.v2_0 import client
|
||||||
@@ -99,17 +101,29 @@ class KeystoneClient(object):
|
|||||||
|
|
||||||
return client.Client(**ks_args)
|
return client.Client(**ks_args)
|
||||||
|
|
||||||
def get_credentials(self):
|
def _get_credentials(self, api_v3):
|
||||||
creds = \
|
if api_v3:
|
||||||
{'auth_url': CONF.keystone_authtoken.auth_uri,
|
creds = \
|
||||||
'username': CONF.keystone_authtoken.admin_user,
|
{'auth_url': CONF.keystone_authtoken.auth_uri,
|
||||||
'password': CONF.keystone_authtoken.admin_password,
|
'username': CONF.keystone_authtoken.admin_user,
|
||||||
'project_name': CONF.keystone_authtoken.admin_tenant_name,
|
'password': CONF.keystone_authtoken.admin_password,
|
||||||
'user_domain_name': "default",
|
'project_name': CONF.keystone_authtoken.admin_tenant_name,
|
||||||
'project_domain_name': "default"}
|
'user_domain_name': "default",
|
||||||
|
'project_domain_name': "default"}
|
||||||
|
else:
|
||||||
|
creds = \
|
||||||
|
{'auth_url': CONF.keystone_authtoken.auth_uri,
|
||||||
|
'username': CONF.keystone_authtoken.admin_user,
|
||||||
|
'password': CONF.keystone_authtoken.admin_password,
|
||||||
|
'tenant_name': CONF.keystone_authtoken.admin_tenant_name}
|
||||||
LOG.debug(creds)
|
LOG.debug(creds)
|
||||||
return creds
|
return creds
|
||||||
|
|
||||||
|
def get_credentials(self):
|
||||||
|
api_v3 = self._is_apiv3(CONF.keystone_authtoken.auth_uri,
|
||||||
|
CONF.keystone_authtoken.auth_version)
|
||||||
|
return self._get_credentials(api_v3)
|
||||||
|
|
||||||
def get_session(self):
|
def get_session(self):
|
||||||
creds = self.get_credentials()
|
creds = self.get_credentials()
|
||||||
self._auth = generic.Password(**creds)
|
self._auth = generic.Password(**creds)
|
||||||
|
|||||||
@@ -29,20 +29,26 @@ class Event(object):
|
|||||||
self._data = data
|
self._data = data
|
||||||
self._request_id = request_id
|
self._request_id = request_id
|
||||||
|
|
||||||
def get_type(self):
|
@property
|
||||||
|
def type(self):
|
||||||
return self._type
|
return self._type
|
||||||
|
|
||||||
def set_type(self, type):
|
@type.setter
|
||||||
|
def type(self, type):
|
||||||
self._type = type
|
self._type = type
|
||||||
|
|
||||||
def get_data(self):
|
@property
|
||||||
|
def data(self):
|
||||||
return self._data
|
return self._data
|
||||||
|
|
||||||
def set_data(self, data):
|
@data.setter
|
||||||
|
def data(self, data):
|
||||||
self._data = data
|
self._data = data
|
||||||
|
|
||||||
def set_request_id(self, id):
|
@property
|
||||||
self._request_id = id
|
def request_id(self):
|
||||||
|
|
||||||
def get_request_id(self):
|
|
||||||
return self._request_id
|
return self._request_id
|
||||||
|
|
||||||
|
@request_id.setter
|
||||||
|
def request_id(self, id):
|
||||||
|
self._request_id = id
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class EventDispatcher(object):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def dispatch_event(self, event):
|
def dispatch_event(self, event):
|
||||||
LOG.debug("dispatch evt : %s" % str(event.get_type()))
|
LOG.debug("dispatch evt : %s" % str(event.type))
|
||||||
"""
|
"""
|
||||||
Dispatch an instance of Event class
|
Dispatch an instance of Event class
|
||||||
"""
|
"""
|
||||||
@@ -49,8 +49,8 @@ class EventDispatcher(object):
|
|||||||
listener(event)
|
listener(event)
|
||||||
|
|
||||||
# Dispatch the event to all the associated listeners
|
# Dispatch the event to all the associated listeners
|
||||||
if event.get_type() in self._events.keys():
|
if event.type in self._events.keys():
|
||||||
listeners = self._events[event.get_type()]
|
listeners = self._events[event.type]
|
||||||
for listener in listeners:
|
for listener in listeners:
|
||||||
listener(event)
|
listener(event)
|
||||||
|
|
||||||
|
|||||||
@@ -15,14 +15,15 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import socket
|
import socket
|
||||||
|
import threading
|
||||||
|
|
||||||
import eventlet
|
import eventlet
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
import oslo_messaging as om
|
import oslo_messaging as om
|
||||||
from threading import Thread
|
|
||||||
from watcher.common.rpc import JsonPayloadSerializer
|
from watcher.common import rpc
|
||||||
from watcher.common.rpc import RequestContextSerializer
|
from watcher._i18n import _LE, _LW
|
||||||
|
|
||||||
# NOTE:
|
# NOTE:
|
||||||
# Ubuntu 14.04 forces librabbitmq when kombu is used
|
# Ubuntu 14.04 forces librabbitmq when kombu is used
|
||||||
@@ -35,7 +36,7 @@ LOG = log.getLogger(__name__)
|
|||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
class MessagingHandler(Thread):
|
class MessagingHandler(threading.Thread):
|
||||||
|
|
||||||
def __init__(self, publisher_id, topic_watcher, endpoint, version,
|
def __init__(self, publisher_id, topic_watcher, endpoint, version,
|
||||||
serializer=None):
|
serializer=None):
|
||||||
@@ -67,7 +68,7 @@ class MessagingHandler(Thread):
|
|||||||
return self.__transport
|
return self.__transport
|
||||||
|
|
||||||
def build_notifier(self):
|
def build_notifier(self):
|
||||||
serializer = RequestContextSerializer(JsonPayloadSerializer())
|
serializer = rpc.RequestContextSerializer(rpc.JsonPayloadSerializer())
|
||||||
return om.Notifier(
|
return om.Notifier(
|
||||||
self.__transport,
|
self.__transport,
|
||||||
publisher_id=self.publisher_id,
|
publisher_id=self.publisher_id,
|
||||||
@@ -93,11 +94,11 @@ class MessagingHandler(Thread):
|
|||||||
)
|
)
|
||||||
self.__server = self.build_server(target)
|
self.__server = self.build_server(target)
|
||||||
else:
|
else:
|
||||||
LOG.warn("you have no defined endpoint, "
|
LOG.warn(
|
||||||
"so you can only publish events")
|
_LW("No endpoint defined; can only publish events"))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.exception(e)
|
LOG.exception(e)
|
||||||
LOG.error("configure : %s" % str(e.message))
|
LOG.error(_LE("Messaging configuration error"))
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
LOG.debug("configure MessagingHandler for %s" % self.topic_watcher)
|
LOG.debug("configure MessagingHandler for %s" % self.topic_watcher)
|
||||||
@@ -107,7 +108,7 @@ class MessagingHandler(Thread):
|
|||||||
self.__server.start()
|
self.__server.start()
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
LOG.debug('Stop up server')
|
LOG.debug('Stopped server')
|
||||||
self.__server.wait()
|
self.__server.wait()
|
||||||
self.__server.stop()
|
self.__server.stop()
|
||||||
|
|
||||||
|
|||||||
@@ -25,15 +25,16 @@ from oslo_log import log
|
|||||||
import cinderclient.exceptions as ciexceptions
|
import cinderclient.exceptions as ciexceptions
|
||||||
import cinderclient.v2.client as ciclient
|
import cinderclient.v2.client as ciclient
|
||||||
import glanceclient.v2.client as glclient
|
import glanceclient.v2.client as glclient
|
||||||
import keystoneclient.v3.client as ksclient
|
|
||||||
import neutronclient.neutron.client as netclient
|
import neutronclient.neutron.client as netclient
|
||||||
import novaclient.client as nvclient
|
import novaclient.client as nvclient
|
||||||
import novaclient.exceptions as nvexceptions
|
import novaclient.exceptions as nvexceptions
|
||||||
|
|
||||||
|
from watcher.common import keystone
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class NovaWrapper(object):
|
class NovaClient(object):
|
||||||
NOVA_CLIENT_API_VERSION = "2"
|
NOVA_CLIENT_API_VERSION = "2"
|
||||||
|
|
||||||
def __init__(self, creds, session):
|
def __init__(self, creds, session):
|
||||||
@@ -43,7 +44,7 @@ class NovaWrapper(object):
|
|||||||
self.cinder = None
|
self.cinder = None
|
||||||
self.nova = nvclient.Client(self.NOVA_CLIENT_API_VERSION,
|
self.nova = nvclient.Client(self.NOVA_CLIENT_API_VERSION,
|
||||||
session=session)
|
session=session)
|
||||||
self.keystone = ksclient.Client(**creds)
|
self.keystone = keystone.KeystoneClient().get_ksclient(creds)
|
||||||
self.glance = None
|
self.glance = None
|
||||||
|
|
||||||
def get_hypervisors_list(self):
|
def get_hypervisors_list(self):
|
||||||
@@ -64,21 +65,21 @@ class NovaWrapper(object):
|
|||||||
keep_original_image_name=True):
|
keep_original_image_name=True):
|
||||||
"""This method migrates a given instance
|
"""This method migrates a given instance
|
||||||
|
|
||||||
using an image of this instance and creating a new instance
|
using an image of this instance and creating a new instance
|
||||||
from this image. It saves some configuration information
|
from this image. It saves some configuration information
|
||||||
about the original instance : security group, list of networks
|
about the original instance : security group, list of networks,
|
||||||
,list of attached volumes, floating IP, ...
|
list of attached volumes, floating IP, ...
|
||||||
in order to apply the same settings to the new instance.
|
in order to apply the same settings to the new instance.
|
||||||
At the end of the process the original instance is deleted.
|
At the end of the process the original instance is deleted.
|
||||||
It returns True if the migration was successful,
|
It returns True if the migration was successful,
|
||||||
False otherwise.
|
False otherwise.
|
||||||
|
|
||||||
:param instance_id: the unique id of the instance to migrate.
|
:param instance_id: the unique id of the instance to migrate.
|
||||||
:param keep_original_image_name: flag indicating whether the
|
:param keep_original_image_name: flag indicating whether the
|
||||||
image name from which the original instance was built must be
|
image name from which the original instance was built must be
|
||||||
used as the name of the intermediate image used for migration.
|
used as the name of the intermediate image used for migration.
|
||||||
If this flag is False, a temporary image name is built
|
If this flag is False, a temporary image name is built
|
||||||
"""
|
"""
|
||||||
|
|
||||||
new_image_name = ""
|
new_image_name = ""
|
||||||
|
|
||||||
@@ -275,12 +276,14 @@ class NovaWrapper(object):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def built_in_non_live_migrate_instance(self, instance_id, hypervisor_id):
|
def built_in_non_live_migrate_instance(self, instance_id, hypervisor_id):
|
||||||
"""This method uses the Nova built-in non-live migrate()
|
"""This method does a live migration of a given instance
|
||||||
action to migrate a given instance.
|
|
||||||
It returns True if the migration was successful, False otherwise.
|
|
||||||
|
|
||||||
:param instance_id: the unique id of the instance to migrate.
|
This method uses the Nova built-in non-live migrate()
|
||||||
"""
|
action to migrate a given instance.
|
||||||
|
It returns True if the migration was successful, False otherwise.
|
||||||
|
|
||||||
|
:param instance_id: the unique id of the instance to migrate.
|
||||||
|
"""
|
||||||
|
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"Trying a Nova built-in non-live "
|
"Trying a Nova built-in non-live "
|
||||||
@@ -326,15 +329,18 @@ class NovaWrapper(object):
|
|||||||
|
|
||||||
def live_migrate_instance(self, instance_id, dest_hostname,
|
def live_migrate_instance(self, instance_id, dest_hostname,
|
||||||
block_migration=True, retry=120):
|
block_migration=True, retry=120):
|
||||||
"""This method uses the Nova built-in live_migrate()
|
"""This method does a live migration of a given instance
|
||||||
action to do a live migration of a given instance.
|
|
||||||
It returns True if the migration was successful,
|
|
||||||
False otherwise.
|
|
||||||
|
|
||||||
:param instance_id: the unique id of the instance to migrate.
|
This method uses the Nova built-in live_migrate()
|
||||||
:param dest_hostname: the name of the destination compute node.
|
action to do a live migration of a given instance.
|
||||||
:param block_migration: No shared storage is required.
|
|
||||||
"""
|
It returns True if the migration was successful,
|
||||||
|
False otherwise.
|
||||||
|
|
||||||
|
:param instance_id: the unique id of the instance to migrate.
|
||||||
|
:param dest_hostname: the name of the destination compute node.
|
||||||
|
:param block_migration: No shared storage is required.
|
||||||
|
"""
|
||||||
|
|
||||||
LOG.debug("Trying a live migrate of instance %s to host '%s'" % (
|
LOG.debug("Trying a live migrate of instance %s to host '%s'" % (
|
||||||
instance_id, dest_hostname))
|
instance_id, dest_hostname))
|
||||||
@@ -429,16 +435,17 @@ class NovaWrapper(object):
|
|||||||
def create_image_from_instance(self, instance_id, image_name,
|
def create_image_from_instance(self, instance_id, image_name,
|
||||||
metadata={"reason": "instance_migrate"}):
|
metadata={"reason": "instance_migrate"}):
|
||||||
"""This method creates a new image from a given instance.
|
"""This method creates a new image from a given instance.
|
||||||
It waits for this image to be in 'active' state before returning.
|
|
||||||
It returns the unique UUID of the created image if successful,
|
|
||||||
None otherwise
|
|
||||||
|
|
||||||
:param instance_id: the uniqueid of
|
It waits for this image to be in 'active' state before returning.
|
||||||
the instance to backup as an image.
|
It returns the unique UUID of the created image if successful,
|
||||||
:param image_name: the name of the image to create.
|
None otherwise
|
||||||
:param metadata: a dictionary containing the list of
|
|
||||||
key-value pairs to associate to the image as metadata.
|
:param instance_id: the uniqueid of
|
||||||
"""
|
the instance to backup as an image.
|
||||||
|
:param image_name: the name of the image to create.
|
||||||
|
:param metadata: a dictionary containing the list of
|
||||||
|
key-value pairs to associate to the image as metadata.
|
||||||
|
"""
|
||||||
if self.glance is None:
|
if self.glance is None:
|
||||||
glance_endpoint = self.keystone. \
|
glance_endpoint = self.keystone. \
|
||||||
service_catalog.url_for(service_type='image',
|
service_catalog.url_for(service_type='image',
|
||||||
@@ -495,8 +502,8 @@ class NovaWrapper(object):
|
|||||||
def delete_instance(self, instance_id):
|
def delete_instance(self, instance_id):
|
||||||
"""This method deletes a given instance.
|
"""This method deletes a given instance.
|
||||||
|
|
||||||
:param instance_id: the unique id of the instance to delete.
|
:param instance_id: the unique id of the instance to delete.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
LOG.debug("Trying to remove instance %s ..." % instance_id)
|
LOG.debug("Trying to remove instance %s ..." % instance_id)
|
||||||
|
|
||||||
@@ -513,8 +520,8 @@ class NovaWrapper(object):
|
|||||||
def stop_instance(self, instance_id):
|
def stop_instance(self, instance_id):
|
||||||
"""This method stops a given instance.
|
"""This method stops a given instance.
|
||||||
|
|
||||||
:param instance_id: the unique id of the instance to stop.
|
:param instance_id: the unique id of the instance to stop.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
LOG.debug("Trying to stop instance %s ..." % instance_id)
|
LOG.debug("Trying to stop instance %s ..." % instance_id)
|
||||||
|
|
||||||
@@ -533,14 +540,17 @@ class NovaWrapper(object):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def wait_for_vm_state(self, server, vm_state, retry, sleep):
|
def wait_for_vm_state(self, server, vm_state, retry, sleep):
|
||||||
"""Waits for server to be in vm_state which can be one of the following :
|
"""Waits for server to be in a specific vm_state
|
||||||
active, stopped
|
|
||||||
|
The vm_state can be one of the following :
|
||||||
|
active, stopped
|
||||||
|
|
||||||
|
:param server: server object.
|
||||||
|
:param vm_state: for which state we are waiting for
|
||||||
|
:param retry: how many times to retry
|
||||||
|
:param sleep: seconds to sleep between the retries
|
||||||
|
"""
|
||||||
|
|
||||||
:param server: server object.
|
|
||||||
:param vm_state: for which state we are waiting for
|
|
||||||
:param retry: how many times to retry
|
|
||||||
:param sleep: seconds to sleep between the retries
|
|
||||||
"""
|
|
||||||
if not server:
|
if not server:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -551,15 +561,18 @@ class NovaWrapper(object):
|
|||||||
return getattr(server, 'OS-EXT-STS:vm_state') == vm_state
|
return getattr(server, 'OS-EXT-STS:vm_state') == vm_state
|
||||||
|
|
||||||
def wait_for_instance_status(self, instance, status_list, retry, sleep):
|
def wait_for_instance_status(self, instance, status_list, retry, sleep):
|
||||||
"""Waits for instance to be in status which can be one of the following
|
"""Waits for instance to be in a specific status
|
||||||
: BUILD, ACTIVE, ERROR, VERIFY_RESIZE, SHUTOFF
|
|
||||||
|
The status can be one of the following
|
||||||
|
: BUILD, ACTIVE, ERROR, VERIFY_RESIZE, SHUTOFF
|
||||||
|
|
||||||
|
:param instance: instance object.
|
||||||
|
:param status_list: tuple containing the list of
|
||||||
|
status we are waiting for
|
||||||
|
:param retry: how many times to retry
|
||||||
|
:param sleep: seconds to sleep between the retries
|
||||||
|
"""
|
||||||
|
|
||||||
:param instance: instance object.
|
|
||||||
:param status_list: tuple containing the list of
|
|
||||||
status we are waiting for
|
|
||||||
:param retry: how many times to retry
|
|
||||||
:param sleep: seconds to sleep between the retries
|
|
||||||
"""
|
|
||||||
if not instance:
|
if not instance:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -577,11 +590,12 @@ class NovaWrapper(object):
|
|||||||
network_names_list=["demo-net"], keypair_name="mykeys",
|
network_names_list=["demo-net"], keypair_name="mykeys",
|
||||||
create_new_floating_ip=True,
|
create_new_floating_ip=True,
|
||||||
block_device_mapping_v2=None):
|
block_device_mapping_v2=None):
|
||||||
"""This method creates a new instance.
|
"""This method creates a new instance
|
||||||
It also creates, if requested, a new floating IP and associates
|
|
||||||
it with the new instance
|
It also creates, if requested, a new floating IP and associates
|
||||||
It returns the unique id of the created instance.
|
it with the new instance
|
||||||
"""
|
It returns the unique id of the created instance.
|
||||||
|
"""
|
||||||
|
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"Trying to create new instance '%s' "
|
"Trying to create new instance '%s' "
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import logging
|
||||||
import signal
|
import signal
|
||||||
import socket
|
import socket
|
||||||
|
|
||||||
@@ -24,10 +25,10 @@ import oslo_messaging as messaging
|
|||||||
from oslo_service import service
|
from oslo_service import service
|
||||||
from oslo_utils import importutils
|
from oslo_utils import importutils
|
||||||
|
|
||||||
|
from watcher._i18n import _LE
|
||||||
|
from watcher._i18n import _LI
|
||||||
from watcher.common import config
|
from watcher.common import config
|
||||||
from watcher.common import context
|
from watcher.common import context
|
||||||
from watcher.common.i18n import _LE
|
|
||||||
from watcher.common.i18n import _LI
|
|
||||||
from watcher.common import rpc
|
from watcher.common import rpc
|
||||||
from watcher.objects import base as objects_base
|
from watcher.objects import base as objects_base
|
||||||
|
|
||||||
@@ -124,9 +125,10 @@ _DEFAULT_LOG_LEVELS = ['amqp=WARN', 'amqplib=WARN', 'qpid.messaging=INFO',
|
|||||||
'glanceclient=WARN', 'watcher.openstack.common=WARN']
|
'glanceclient=WARN', 'watcher.openstack.common=WARN']
|
||||||
|
|
||||||
|
|
||||||
def prepare_service(argv=[]):
|
def prepare_service(argv=[], conf=cfg.CONF):
|
||||||
log.register_options(cfg.CONF)
|
log.register_options(conf)
|
||||||
config.parse_args(argv)
|
config.parse_args(argv)
|
||||||
cfg.set_defaults(_options.log_opts,
|
cfg.set_defaults(_options.log_opts,
|
||||||
default_log_levels=_DEFAULT_LOG_LEVELS)
|
default_log_levels=_DEFAULT_LOG_LEVELS)
|
||||||
log.setup(cfg.CONF, 'python-watcher')
|
log.setup(conf, 'python-watcher')
|
||||||
|
conf.log_opt_values(LOG, logging.DEBUG)
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import six
|
|||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
from watcher.common.i18n import _LW
|
from watcher._i18n import _LW
|
||||||
|
|
||||||
UTILS_OPTS = [
|
UTILS_OPTS = [
|
||||||
cfg.StrOpt('rootwrap_config',
|
cfg.StrOpt('rootwrap_config',
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
..
|
||||||
|
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/
|
||||||
|
|
||||||
Tempest Field Guide to Infrastructure Optimization API tests
|
Tempest Field Guide to Infrastructure Optimization API tests
|
||||||
============================================================
|
============================================================
|
||||||
|
|
||||||
|
|||||||
@@ -93,14 +93,13 @@ class BaseInfraOptimTest(test.BaseTestCase):
|
|||||||
@classmethod
|
@classmethod
|
||||||
@creates('audit_template')
|
@creates('audit_template')
|
||||||
def create_audit_template(cls, description=None, expect_errors=False):
|
def create_audit_template(cls, description=None, expect_errors=False):
|
||||||
"""
|
"""Wrapper utility for creating test audit_template.
|
||||||
Wrapper utility for creating test audit_template.
|
|
||||||
|
|
||||||
:param description: A description of the audit template.
|
:param description: A description of the audit template.
|
||||||
if not supplied, a random value will be generated.
|
if not supplied, a random value will be generated.
|
||||||
:return: Created audit template.
|
:return: Created audit template.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
description = description or data_utils.rand_name(
|
description = description or data_utils.rand_name(
|
||||||
'test-audit_template')
|
'test-audit_template')
|
||||||
resp, body = cls.client.create_audit_template(description=description)
|
resp, body = cls.client.create_audit_template(description=description)
|
||||||
@@ -108,12 +107,10 @@ class BaseInfraOptimTest(test.BaseTestCase):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def delete_audit_template(cls, audit_template_id):
|
def delete_audit_template(cls, audit_template_id):
|
||||||
"""
|
"""Deletes a audit_template having the specified UUID.
|
||||||
Deletes a audit_template having the specified UUID.
|
|
||||||
|
|
||||||
:param uuid: The unique identifier of the audit_template.
|
:param uuid: The unique identifier of the audit_template.
|
||||||
:return: Server response.
|
:return: Server response.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
resp, body = cls.client.delete_audit_template(audit_template_id)
|
resp, body = cls.client.delete_audit_template(audit_template_id)
|
||||||
|
|||||||
@@ -11,6 +11,8 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import six
|
||||||
|
|
||||||
from tempest_lib import exceptions as lib_exc
|
from tempest_lib import exceptions as lib_exc
|
||||||
|
|
||||||
from tempest.api.infra_optim.admin import base
|
from tempest.api.infra_optim.admin import base
|
||||||
@@ -27,7 +29,7 @@ class TestAuditTemplate(base.BaseInfraOptimTest):
|
|||||||
|
|
||||||
def _assertExpected(self, expected, actual):
|
def _assertExpected(self, expected, actual):
|
||||||
# Check if not expected keys/values exists in actual response body
|
# Check if not expected keys/values exists in actual response body
|
||||||
for key, value in expected.iteritems():
|
for key, value in six.iteritems(expected):
|
||||||
if key not in ('created_at', 'updated_at', 'deleted_at'):
|
if key not in ('created_at', 'updated_at', 'deleted_at'):
|
||||||
self.assertIn(key, actual)
|
self.assertIn(key, actual)
|
||||||
self.assertEqual(value, actual[key])
|
self.assertEqual(value, actual[key])
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
..
|
||||||
|
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/
|
||||||
|
|
||||||
.. _cli_field_guide:
|
.. _cli_field_guide:
|
||||||
|
|
||||||
Tempest Field Guide to CLI tests
|
Tempest Field Guide to CLI tests
|
||||||
|
|||||||
@@ -40,10 +40,7 @@ def handle_errors(f):
|
|||||||
|
|
||||||
|
|
||||||
class InfraOptimClient(service_client.ServiceClient):
|
class InfraOptimClient(service_client.ServiceClient):
|
||||||
"""
|
"""Base Tempest REST client for Watcher API."""
|
||||||
Base Tempest REST client for Watcher API.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
uri_prefix = ''
|
uri_prefix = ''
|
||||||
|
|
||||||
@@ -58,14 +55,13 @@ class InfraOptimClient(service_client.ServiceClient):
|
|||||||
return json.loads(object_str)
|
return json.loads(object_str)
|
||||||
|
|
||||||
def _get_uri(self, resource_name, uuid=None, permanent=False):
|
def _get_uri(self, resource_name, uuid=None, permanent=False):
|
||||||
"""
|
"""Get URI for a specific resource or object.
|
||||||
Get URI for a specific resource or object.
|
|
||||||
|
|
||||||
:param resource_name: The name of the REST resource, e.g., 'audits'.
|
:param resource_name: The name of the REST resource, e.g., 'audits'.
|
||||||
:param uuid: The unique identifier of an object in UUID format.
|
:param uuid: The unique identifier of an object in UUID format.
|
||||||
:return: Relative URI for the resource or object.
|
:return: Relative URI for the resource or object.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
prefix = self.uri_prefix if not permanent else ''
|
prefix = self.uri_prefix if not permanent else ''
|
||||||
|
|
||||||
return '{pref}/{res}{uuid}'.format(pref=prefix,
|
return '{pref}/{res}{uuid}'.format(pref=prefix,
|
||||||
@@ -73,16 +69,15 @@ class InfraOptimClient(service_client.ServiceClient):
|
|||||||
uuid='/%s' % uuid if uuid else '')
|
uuid='/%s' % uuid if uuid else '')
|
||||||
|
|
||||||
def _make_patch(self, allowed_attributes, **kw):
|
def _make_patch(self, allowed_attributes, **kw):
|
||||||
"""
|
"""Create a JSON patch according to RFC 6902.
|
||||||
Create a JSON patch according to RFC 6902.
|
|
||||||
|
|
||||||
:param allowed_attributes: An iterable object that contains a set of
|
:param allowed_attributes: An iterable object that contains a set of
|
||||||
allowed attributes for an object.
|
allowed attributes for an object.
|
||||||
:param **kw: Attributes and new values for them.
|
:param **kw: Attributes and new values for them.
|
||||||
:return: A JSON path that sets values of the specified attributes to
|
:return: A JSON path that sets values of the specified attributes to
|
||||||
the new ones.
|
the new ones.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def get_change(kw, path='/'):
|
def get_change(kw, path='/'):
|
||||||
for name, value in six.iteritems(kw):
|
for name, value in six.iteritems(kw):
|
||||||
if isinstance(value, dict):
|
if isinstance(value, dict):
|
||||||
@@ -103,15 +98,14 @@ class InfraOptimClient(service_client.ServiceClient):
|
|||||||
return patch
|
return patch
|
||||||
|
|
||||||
def _list_request(self, resource, permanent=False, **kwargs):
|
def _list_request(self, resource, permanent=False, **kwargs):
|
||||||
"""
|
"""Get the list of objects of the specified type.
|
||||||
Get the list of objects of the specified type.
|
|
||||||
|
|
||||||
:param resource: The name of the REST resource, e.g., 'audits'.
|
:param resource: The name of the REST resource, e.g., 'audits'.
|
||||||
"param **kw: Parameters for the request.
|
"param **kw: Parameters for the request.
|
||||||
:return: A tuple with the server response and deserialized JSON list
|
:return: A tuple with the server response and deserialized JSON list
|
||||||
of objects
|
of objects
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
uri = self._get_uri(resource, permanent=permanent)
|
uri = self._get_uri(resource, permanent=permanent)
|
||||||
if kwargs:
|
if kwargs:
|
||||||
uri += "?%s" % urllib.urlencode(kwargs)
|
uri += "?%s" % urllib.urlencode(kwargs)
|
||||||
@@ -122,13 +116,12 @@ class InfraOptimClient(service_client.ServiceClient):
|
|||||||
return resp, self.deserialize(body)
|
return resp, self.deserialize(body)
|
||||||
|
|
||||||
def _show_request(self, resource, uuid, permanent=False, **kwargs):
|
def _show_request(self, resource, uuid, permanent=False, **kwargs):
|
||||||
"""
|
"""Gets a specific object of the specified type.
|
||||||
Gets a specific object of the specified type.
|
|
||||||
|
|
||||||
:param uuid: Unique identifier of the object in UUID format.
|
:param uuid: Unique identifier of the object in UUID format.
|
||||||
:return: Serialized object as a dictionary.
|
:return: Serialized object as a dictionary.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if 'uri' in kwargs:
|
if 'uri' in kwargs:
|
||||||
uri = kwargs['uri']
|
uri = kwargs['uri']
|
||||||
else:
|
else:
|
||||||
@@ -139,16 +132,15 @@ class InfraOptimClient(service_client.ServiceClient):
|
|||||||
return resp, self.deserialize(body)
|
return resp, self.deserialize(body)
|
||||||
|
|
||||||
def _create_request(self, resource, object_dict):
|
def _create_request(self, resource, object_dict):
|
||||||
"""
|
"""Create an object of the specified type.
|
||||||
Create an object of the specified type.
|
|
||||||
|
|
||||||
:param resource: The name of the REST resource, e.g., 'audits'.
|
:param resource: The name of the REST resource, e.g., 'audits'.
|
||||||
:param object_dict: A Python dict that represents an object of the
|
:param object_dict: A Python dict that represents an object of the
|
||||||
specified type.
|
specified type.
|
||||||
:return: A tuple with the server response and the deserialized created
|
:return: A tuple with the server response and the deserialized created
|
||||||
object.
|
object.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
body = self.serialize(object_dict)
|
body = self.serialize(object_dict)
|
||||||
uri = self._get_uri(resource)
|
uri = self._get_uri(resource)
|
||||||
|
|
||||||
@@ -158,14 +150,13 @@ class InfraOptimClient(service_client.ServiceClient):
|
|||||||
return resp, self.deserialize(body)
|
return resp, self.deserialize(body)
|
||||||
|
|
||||||
def _delete_request(self, resource, uuid):
|
def _delete_request(self, resource, uuid):
|
||||||
"""
|
"""Delete specified object.
|
||||||
Delete specified object.
|
|
||||||
|
|
||||||
:param resource: The name of the REST resource, e.g., 'audits'.
|
:param resource: The name of the REST resource, e.g., 'audits'.
|
||||||
:param uuid: The unique identifier of an object in UUID format.
|
:param uuid: The unique identifier of an object in UUID format.
|
||||||
:return: A tuple with the server response and the response body.
|
:return: A tuple with the server response and the response body.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
uri = self._get_uri(resource, uuid)
|
uri = self._get_uri(resource, uuid)
|
||||||
|
|
||||||
resp, body = self.delete(uri)
|
resp, body = self.delete(uri)
|
||||||
@@ -173,15 +164,14 @@ class InfraOptimClient(service_client.ServiceClient):
|
|||||||
return resp, body
|
return resp, body
|
||||||
|
|
||||||
def _patch_request(self, resource, uuid, patch_object):
|
def _patch_request(self, resource, uuid, patch_object):
|
||||||
"""
|
"""Update specified object with JSON-patch.
|
||||||
Update specified object with JSON-patch.
|
|
||||||
|
|
||||||
:param resource: The name of the REST resource, e.g., 'audits'.
|
:param resource: The name of the REST resource, e.g., 'audits'.
|
||||||
:param uuid: The unique identifier of an object in UUID format.
|
:param uuid: The unique identifier of an object in UUID format.
|
||||||
:return: A tuple with the server response and the serialized patched
|
:return: A tuple with the server response and the serialized patched
|
||||||
object.
|
object.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
uri = self._get_uri(resource, uuid)
|
uri = self._get_uri(resource, uuid)
|
||||||
patch_body = json.dumps(patch_object)
|
patch_body = json.dumps(patch_object)
|
||||||
|
|
||||||
@@ -197,20 +187,17 @@ class InfraOptimClient(service_client.ServiceClient):
|
|||||||
|
|
||||||
@handle_errors
|
@handle_errors
|
||||||
def get_version_description(self, version='v1'):
|
def get_version_description(self, version='v1'):
|
||||||
"""
|
"""Retrieves the description of the API.
|
||||||
Retrieves the description of the API.
|
|
||||||
|
|
||||||
:param version: The version of the API. Default: 'v1'.
|
:param version: The version of the API. Default: 'v1'.
|
||||||
:return: Serialized description of API resources.
|
:return: Serialized description of API resources.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return self._list_request(version, permanent=True)
|
return self._list_request(version, permanent=True)
|
||||||
|
|
||||||
def _put_request(self, resource, put_object):
|
def _put_request(self, resource, put_object):
|
||||||
"""
|
"""Update specified object with JSON-patch."""
|
||||||
Update specified object with JSON-patch.
|
|
||||||
|
|
||||||
"""
|
|
||||||
uri = self._get_uri(resource)
|
uri = self._get_uri(resource)
|
||||||
put_body = json.dumps(put_object)
|
put_body = json.dumps(put_object)
|
||||||
|
|
||||||
|
|||||||
@@ -14,9 +14,7 @@ from tempest.services.infra_optim import base
|
|||||||
|
|
||||||
|
|
||||||
class InfraOptimClientJSON(base.InfraOptimClient):
|
class InfraOptimClientJSON(base.InfraOptimClient):
|
||||||
"""
|
"""Base Tempest REST client for Watcher API v1."""
|
||||||
Base Tempest REST client for Watcher API v1.
|
|
||||||
"""
|
|
||||||
version = '1'
|
version = '1'
|
||||||
uri_prefix = 'v1'
|
uri_prefix = 'v1'
|
||||||
|
|
||||||
@@ -40,45 +38,41 @@ class InfraOptimClientJSON(base.InfraOptimClient):
|
|||||||
|
|
||||||
@base.handle_errors
|
@base.handle_errors
|
||||||
def show_audit_template(self, uuid):
|
def show_audit_template(self, uuid):
|
||||||
"""
|
"""Gets a specific audit template.
|
||||||
Gets a specific audit template.
|
|
||||||
|
|
||||||
:param uuid: Unique identifier of the audit template in UUID format.
|
:param uuid: Unique identifier of the audit template in UUID format.
|
||||||
:return: Serialized audit template as a dictionary.
|
:return: Serialized audit template as a dictionary.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return self._show_request('audit_templates', uuid)
|
return self._show_request('audit_templates', uuid)
|
||||||
|
|
||||||
@base.handle_errors
|
@base.handle_errors
|
||||||
def show_audit_template_by_host_agregate(self, host_agregate_id):
|
def show_audit_template_by_host_agregate(self, host_agregate_id):
|
||||||
"""
|
"""Gets an audit template associated with given host agregate ID.
|
||||||
Gets an audit template associated with given host agregate ID.
|
|
||||||
|
|
||||||
:param uuid: Unique identifier of the audit_template in UUID format.
|
:param uuid: Unique identifier of the audit_template in UUID format.
|
||||||
:return: Serialized audit_template as a dictionary.
|
:return: Serialized audit_template as a dictionary.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
uri = '/audit_templates/detail?host_agregate=%s' % host_agregate_id
|
uri = '/audit_templates/detail?host_agregate=%s' % host_agregate_id
|
||||||
|
|
||||||
return self._show_request('audit_templates', uuid=None, uri=uri)
|
return self._show_request('audit_templates', uuid=None, uri=uri)
|
||||||
|
|
||||||
@base.handle_errors
|
@base.handle_errors
|
||||||
def show_audit_template_by_goal(self, goal):
|
def show_audit_template_by_goal(self, goal):
|
||||||
"""
|
"""Gets an audit template associated with given goal.
|
||||||
Gets an audit template associated with given goal.
|
|
||||||
|
|
||||||
:param uuid: Unique identifier of the audit_template in UUID format.
|
:param uuid: Unique identifier of the audit_template in UUID format.
|
||||||
:return: Serialized audit_template as a dictionary.
|
:return: Serialized audit_template as a dictionary.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
uri = '/audit_templates/detail?goal=%s' % goal
|
uri = '/audit_templates/detail?goal=%s' % goal
|
||||||
|
|
||||||
return self._show_request('audit_templates', uuid=None, uri=uri)
|
return self._show_request('audit_templates', uuid=None, uri=uri)
|
||||||
|
|
||||||
@base.handle_errors
|
@base.handle_errors
|
||||||
def create_audit_template(self, **kwargs):
|
def create_audit_template(self, **kwargs):
|
||||||
"""
|
"""Creates an audit template with the specified parameters.
|
||||||
Creates an audit template with the specified parameters.
|
|
||||||
|
|
||||||
:param name: The name of the audit template. Default: My Audit Template
|
:param name: The name of the audit template. Default: My Audit Template
|
||||||
:param description: The description of the audit template.
|
:param description: The description of the audit template.
|
||||||
@@ -91,8 +85,8 @@ class InfraOptimClientJSON(base.InfraOptimClient):
|
|||||||
Default: {}
|
Default: {}
|
||||||
:return: A tuple with the server response and the created audit
|
:return: A tuple with the server response and the created audit
|
||||||
template.
|
template.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
audit_template = {
|
audit_template = {
|
||||||
'name': kwargs.get('name', 'My Audit Template'),
|
'name': kwargs.get('name', 'My Audit Template'),
|
||||||
'description': kwargs.get('description', 'AT Description'),
|
'description': kwargs.get('description', 'AT Description'),
|
||||||
@@ -127,25 +121,22 @@ class InfraOptimClientJSON(base.InfraOptimClient):
|
|||||||
|
|
||||||
@base.handle_errors
|
@base.handle_errors
|
||||||
def delete_audit_template(self, uuid):
|
def delete_audit_template(self, uuid):
|
||||||
"""
|
"""Deletes an audit template having the specified UUID.
|
||||||
Deletes an audit template having the specified UUID.
|
|
||||||
|
|
||||||
:param uuid: The unique identifier of the audit template.
|
:param uuid: The unique identifier of the audit template.
|
||||||
:return: A tuple with the server response and the response body.
|
:return: A tuple with the server response and the response body.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return self._delete_request('audit_templates', uuid)
|
return self._delete_request('audit_templates', uuid)
|
||||||
|
|
||||||
@base.handle_errors
|
@base.handle_errors
|
||||||
def update_audit_template(self, uuid, patch):
|
def update_audit_template(self, uuid, patch):
|
||||||
"""
|
"""Update the specified audit template.
|
||||||
Update the specified audit template.
|
|
||||||
|
|
||||||
:param uuid: The unique identifier of the audit template.
|
:param uuid: The unique identifier of the audit template.
|
||||||
:param patch: List of dicts representing json patches.
|
:param patch: List of dicts representing json patches.
|
||||||
:return: A tuple with the server response and the updated audit
|
:return: A tuple with the server response and the updated audit
|
||||||
template.
|
template.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return self._patch_request('audit_templates', uuid, patch)
|
return self._patch_request('audit_templates', uuid, patch)
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
# Watcher Database
|
|
||||||
|
|
||||||
This database stores all the watcher business objects which can be requested by the Watcher API :
|
|
||||||
* Audit templates
|
|
||||||
* Audits
|
|
||||||
* Action plans
|
|
||||||
* Actions history
|
|
||||||
* Watcher settings :
|
|
||||||
* metrics/events collector endpoints for each type of metric
|
|
||||||
* manual/automatic mode
|
|
||||||
* Business Objects states
|
|
||||||
|
|
||||||
It may be any relational database or a key-value database.
|
|
||||||
|
|
||||||
Business objects are read/created/updated/deleted from/to the Watcher database using a common Python package which provides a high-level Service API.
|
|
||||||
@@ -16,12 +16,10 @@ Base classes for storage engines
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import abc
|
import abc
|
||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_db import api as db_api
|
from oslo_db import api as db_api
|
||||||
import six
|
import six
|
||||||
|
|
||||||
|
|
||||||
_BACKEND_MAPPING = {'sqlalchemy': 'watcher.db.sqlalchemy.api'}
|
_BACKEND_MAPPING = {'sqlalchemy': 'watcher.db.sqlalchemy.api'}
|
||||||
IMPL = db_api.DBAPI.from_config(cfg.CONF, backend_mapping=_BACKEND_MAPPING,
|
IMPL = db_api.DBAPI.from_config(cfg.CONF, backend_mapping=_BACKEND_MAPPING,
|
||||||
lazy=True)
|
lazy=True)
|
||||||
@@ -33,13 +31,9 @@ def get_instance():
|
|||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(abc.ABCMeta)
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
class Connection(object):
|
class BaseConnection(object):
|
||||||
"""Base class for storage system connections."""
|
"""Base class for storage system connections."""
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def __init__(self):
|
|
||||||
"""Constructor."""
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def get_audit_template_list(self, context, columns=None, filters=None,
|
def get_audit_template_list(self, context, columns=None, filters=None,
|
||||||
limit=None, marker=None, sort_key=None,
|
limit=None, marker=None, sort_key=None,
|
||||||
@@ -130,6 +124,7 @@ class Connection(object):
|
|||||||
:raises: AuditTemplateNotFound
|
:raises: AuditTemplateNotFound
|
||||||
:raises: InvalidParameterValue
|
:raises: InvalidParameterValue
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def soft_delete_audit_template(self, audit_template_id):
|
def soft_delete_audit_template(self, audit_template_id):
|
||||||
"""Soft delete an audit_template.
|
"""Soft delete an audit_template.
|
||||||
@@ -308,7 +303,7 @@ class Connection(object):
|
|||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def get_action_plan_list(
|
def get_action_plan_list(
|
||||||
self, context, columns=None, filters=None, limit=None,
|
self, context, columns=None, filters=None, limit=None,
|
||||||
marker=None, sort_key=None, sort_dir=None):
|
marker=None, sort_key=None, sort_dir=None):
|
||||||
"""Get specific columns for matching action plans.
|
"""Get specific columns for matching action plans.
|
||||||
|
|
||||||
|
|||||||
@@ -1,90 +0,0 @@
|
|||||||
"""Initial revision
|
|
||||||
|
|
||||||
Revision ID: 414bf1d36e7d
|
|
||||||
Revises: None
|
|
||||||
Create Date: 2015-04-08 15:05:50.942578
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = '414bf1d36e7d'
|
|
||||||
down_revision = None
|
|
||||||
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.create_table('audit_templates',
|
|
||||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
|
||||||
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
|
||||||
sa.Column('deleted_at', sa.DateTime(), nullable=True),
|
|
||||||
sa.Column('deleted', sa.Integer(), nullable=True),
|
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
|
||||||
sa.Column('uuid', sa.String(length=36), nullable=True),
|
|
||||||
sa.Column('name', sa.String(length=63), nullable=True),
|
|
||||||
sa.Column('description', sa.String(length=255), nullable=True),
|
|
||||||
sa.Column('host_aggregate', sa.Integer(), nullable=True),
|
|
||||||
sa.Column('goal', sa.String(length=63), nullable=True),
|
|
||||||
sa.Column('extra', watcher.db.sqlalchemy.models.JSONEncodedDict(), nullable=True),
|
|
||||||
sa.Column('version', sa.String(length=15), nullable=True),
|
|
||||||
sa.PrimaryKeyConstraint('id'),
|
|
||||||
sa.UniqueConstraint('name', name='uniq_audit_templates0name'),
|
|
||||||
sa.UniqueConstraint('uuid', name='uniq_audit_templates0uuid')
|
|
||||||
)
|
|
||||||
op.create_table('audits',
|
|
||||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
|
||||||
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
|
||||||
sa.Column('deleted_at', sa.DateTime(), nullable=True),
|
|
||||||
sa.Column('deleted', sa.Integer(), nullable=True),
|
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
|
||||||
sa.Column('uuid', sa.String(length=36), nullable=True),
|
|
||||||
sa.Column('type', sa.String(length=20), nullable=True),
|
|
||||||
sa.Column('state', sa.String(length=20), nullable=True),
|
|
||||||
sa.Column('deadline', sa.DateTime(), nullable=True),
|
|
||||||
sa.Column('audit_template_id', sa.Integer(), nullable=False),
|
|
||||||
sa.ForeignKeyConstraint(['audit_template_id'], ['audit_templates.id'], ),
|
|
||||||
sa.PrimaryKeyConstraint('id'),
|
|
||||||
sa.UniqueConstraint('uuid', name='uniq_audits0uuid')
|
|
||||||
)
|
|
||||||
op.create_table('action_plans',
|
|
||||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
|
||||||
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
|
||||||
sa.Column('deleted_at', sa.DateTime(), nullable=True),
|
|
||||||
sa.Column('deleted', sa.Integer(), nullable=True),
|
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
|
||||||
sa.Column('uuid', sa.String(length=36), nullable=True),
|
|
||||||
sa.Column('first_action_id', sa.Integer(), nullable=True),
|
|
||||||
sa.Column('audit_id', sa.Integer(), nullable=True),
|
|
||||||
sa.Column('state', sa.String(length=20), nullable=True),
|
|
||||||
sa.ForeignKeyConstraint(['audit_id'], ['audits.id'], ),
|
|
||||||
sa.PrimaryKeyConstraint('id'),
|
|
||||||
sa.UniqueConstraint('uuid', name='uniq_action_plans0uuid')
|
|
||||||
)
|
|
||||||
op.create_table('actions',
|
|
||||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
|
||||||
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
|
||||||
sa.Column('deleted_at', sa.DateTime(), nullable=True),
|
|
||||||
sa.Column('deleted', sa.Integer(), nullable=True),
|
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
|
||||||
sa.Column('uuid', sa.String(length=36), nullable=True),
|
|
||||||
sa.Column('action_plan_id', sa.Integer(), nullable=True),
|
|
||||||
sa.Column('description', sa.String(length=255), nullable=True),
|
|
||||||
sa.Column('state', sa.String(length=20), nullable=True),
|
|
||||||
sa.Column('alarm', sa.String(length=36), nullable=True),
|
|
||||||
sa.Column('next', sa.String(length=36), nullable=True),
|
|
||||||
sa.ForeignKeyConstraint(['action_plan_id'], ['action_plans.id'], ),
|
|
||||||
sa.PrimaryKeyConstraint('id'),
|
|
||||||
sa.UniqueConstraint('uuid', name='uniq_actions0uuid')
|
|
||||||
)
|
|
||||||
### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.drop_table('actions')
|
|
||||||
op.drop_table('action_plans')
|
|
||||||
op.drop_table('audits')
|
|
||||||
op.drop_table('audit_templates')
|
|
||||||
### end Alembic commands ###
|
|
||||||
@@ -22,19 +22,18 @@ from oslo_db import exception as db_exc
|
|||||||
from oslo_db.sqlalchemy import session as db_session
|
from oslo_db.sqlalchemy import session as db_session
|
||||||
from oslo_db.sqlalchemy import utils as db_utils
|
from oslo_db.sqlalchemy import utils as db_utils
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
from sqlalchemy.orm.exc import MultipleResultsFound
|
from sqlalchemy.orm import exc
|
||||||
from sqlalchemy.orm.exc import NoResultFound
|
|
||||||
|
|
||||||
|
from watcher import _i18n
|
||||||
from watcher.common import exception
|
from watcher.common import exception
|
||||||
from watcher.common import utils
|
from watcher.common import utils
|
||||||
from watcher.db import api
|
from watcher.db import api
|
||||||
from watcher.db.sqlalchemy import models
|
from watcher.db.sqlalchemy import models
|
||||||
from watcher import i18n
|
from watcher.objects import audit as audit_objects
|
||||||
from watcher.objects.audit import AuditStatus
|
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
_ = i18n._
|
_ = _i18n._
|
||||||
|
|
||||||
_FACADE = None
|
_FACADE = None
|
||||||
|
|
||||||
@@ -102,7 +101,7 @@ def _paginate_query(model, limit=None, marker=None, sort_key=None,
|
|||||||
return query.all()
|
return query.all()
|
||||||
|
|
||||||
|
|
||||||
class Connection(api.Connection):
|
class Connection(api.BaseConnection):
|
||||||
"""SqlAlchemy connection."""
|
"""SqlAlchemy connection."""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -223,7 +222,7 @@ class Connection(api.Connection):
|
|||||||
raise exception.AuditTemplateNotFound(
|
raise exception.AuditTemplateNotFound(
|
||||||
audit_template=audit_template_id)
|
audit_template=audit_template_id)
|
||||||
return audit_template
|
return audit_template
|
||||||
except NoResultFound:
|
except exc.NoResultFound:
|
||||||
raise exception.AuditTemplateNotFound(
|
raise exception.AuditTemplateNotFound(
|
||||||
audit_template=audit_template_id)
|
audit_template=audit_template_id)
|
||||||
|
|
||||||
@@ -238,7 +237,7 @@ class Connection(api.Connection):
|
|||||||
raise exception.AuditTemplateNotFound(
|
raise exception.AuditTemplateNotFound(
|
||||||
audit_template=audit_template_uuid)
|
audit_template=audit_template_uuid)
|
||||||
return audit_template
|
return audit_template
|
||||||
except NoResultFound:
|
except exc.NoResultFound:
|
||||||
raise exception.AuditTemplateNotFound(
|
raise exception.AuditTemplateNotFound(
|
||||||
audit_template=audit_template_uuid)
|
audit_template=audit_template_uuid)
|
||||||
|
|
||||||
@@ -252,11 +251,11 @@ class Connection(api.Connection):
|
|||||||
raise exception.AuditTemplateNotFound(
|
raise exception.AuditTemplateNotFound(
|
||||||
audit_template=audit_template_name)
|
audit_template=audit_template_name)
|
||||||
return audit_template
|
return audit_template
|
||||||
except MultipleResultsFound:
|
except exc.MultipleResultsFound:
|
||||||
raise exception.Conflict(
|
raise exception.Conflict(
|
||||||
'Multiple audit templates exist with same name.'
|
_('Multiple audit templates exist with the same name.'
|
||||||
' Please use the audit template uuid instead.')
|
' Please use the audit template uuid instead'))
|
||||||
except NoResultFound:
|
except exc.NoResultFound:
|
||||||
raise exception.AuditTemplateNotFound(
|
raise exception.AuditTemplateNotFound(
|
||||||
audit_template=audit_template_name)
|
audit_template=audit_template_name)
|
||||||
|
|
||||||
@@ -268,7 +267,7 @@ class Connection(api.Connection):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
query.one()
|
query.one()
|
||||||
except NoResultFound:
|
except exc.NoResultFound:
|
||||||
raise exception.AuditTemplateNotFound(node=audit_template_id)
|
raise exception.AuditTemplateNotFound(node=audit_template_id)
|
||||||
|
|
||||||
query.delete()
|
query.delete()
|
||||||
@@ -287,7 +286,7 @@ class Connection(api.Connection):
|
|||||||
query = add_identity_filter(query, audit_template_id)
|
query = add_identity_filter(query, audit_template_id)
|
||||||
try:
|
try:
|
||||||
ref = query.with_lockmode('update').one()
|
ref = query.with_lockmode('update').one()
|
||||||
except NoResultFound:
|
except exc.NoResultFound:
|
||||||
raise exception.AuditTemplateNotFound(
|
raise exception.AuditTemplateNotFound(
|
||||||
audit_template=audit_template_id)
|
audit_template=audit_template_id)
|
||||||
|
|
||||||
@@ -302,7 +301,7 @@ class Connection(api.Connection):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
query.one()
|
query.one()
|
||||||
except NoResultFound:
|
except exc.NoResultFound:
|
||||||
raise exception.AuditTemplateNotFound(node=audit_template_id)
|
raise exception.AuditTemplateNotFound(node=audit_template_id)
|
||||||
|
|
||||||
query.soft_delete()
|
query.soft_delete()
|
||||||
@@ -323,7 +322,7 @@ class Connection(api.Connection):
|
|||||||
values['uuid'] = utils.generate_uuid()
|
values['uuid'] = utils.generate_uuid()
|
||||||
|
|
||||||
if values.get('state') is None:
|
if values.get('state') is None:
|
||||||
values['state'] = AuditStatus.PENDING
|
values['state'] = audit_objects.AuditStatus.PENDING
|
||||||
|
|
||||||
audit = models.Audit()
|
audit = models.Audit()
|
||||||
audit.update(values)
|
audit.update(values)
|
||||||
@@ -343,7 +342,7 @@ class Connection(api.Connection):
|
|||||||
if audit.state == 'DELETED':
|
if audit.state == 'DELETED':
|
||||||
raise exception.AuditNotFound(audit=audit_id)
|
raise exception.AuditNotFound(audit=audit_id)
|
||||||
return audit
|
return audit
|
||||||
except NoResultFound:
|
except exc.NoResultFound:
|
||||||
raise exception.AuditNotFound(audit=audit_id)
|
raise exception.AuditNotFound(audit=audit_id)
|
||||||
|
|
||||||
def get_audit_by_uuid(self, context, audit_uuid):
|
def get_audit_by_uuid(self, context, audit_uuid):
|
||||||
@@ -356,7 +355,7 @@ class Connection(api.Connection):
|
|||||||
if audit.state == 'DELETED':
|
if audit.state == 'DELETED':
|
||||||
raise exception.AuditNotFound(audit=audit_uuid)
|
raise exception.AuditNotFound(audit=audit_uuid)
|
||||||
return audit
|
return audit
|
||||||
except NoResultFound:
|
except exc.NoResultFound:
|
||||||
raise exception.AuditNotFound(audit=audit_uuid)
|
raise exception.AuditNotFound(audit=audit_uuid)
|
||||||
|
|
||||||
def destroy_audit(self, audit_id):
|
def destroy_audit(self, audit_id):
|
||||||
@@ -374,7 +373,7 @@ class Connection(api.Connection):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
audit_ref = query.one()
|
audit_ref = query.one()
|
||||||
except NoResultFound:
|
except exc.NoResultFound:
|
||||||
raise exception.AuditNotFound(audit=audit_id)
|
raise exception.AuditNotFound(audit=audit_id)
|
||||||
|
|
||||||
if is_audit_referenced(session, audit_ref['id']):
|
if is_audit_referenced(session, audit_ref['id']):
|
||||||
@@ -396,7 +395,7 @@ class Connection(api.Connection):
|
|||||||
query = add_identity_filter(query, audit_id)
|
query = add_identity_filter(query, audit_id)
|
||||||
try:
|
try:
|
||||||
ref = query.with_lockmode('update').one()
|
ref = query.with_lockmode('update').one()
|
||||||
except NoResultFound:
|
except exc.NoResultFound:
|
||||||
raise exception.AuditNotFound(audit=audit_id)
|
raise exception.AuditNotFound(audit=audit_id)
|
||||||
|
|
||||||
ref.update(values)
|
ref.update(values)
|
||||||
@@ -410,7 +409,7 @@ class Connection(api.Connection):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
query.one()
|
query.one()
|
||||||
except NoResultFound:
|
except exc.NoResultFound:
|
||||||
raise exception.AuditNotFound(node=audit_id)
|
raise exception.AuditNotFound(node=audit_id)
|
||||||
|
|
||||||
query.soft_delete()
|
query.soft_delete()
|
||||||
@@ -447,7 +446,7 @@ class Connection(api.Connection):
|
|||||||
raise exception.ActionNotFound(
|
raise exception.ActionNotFound(
|
||||||
action=action_id)
|
action=action_id)
|
||||||
return action
|
return action
|
||||||
except NoResultFound:
|
except exc.NoResultFound:
|
||||||
raise exception.ActionNotFound(action=action_id)
|
raise exception.ActionNotFound(action=action_id)
|
||||||
|
|
||||||
def get_action_by_uuid(self, context, action_uuid):
|
def get_action_by_uuid(self, context, action_uuid):
|
||||||
@@ -460,7 +459,7 @@ class Connection(api.Connection):
|
|||||||
raise exception.ActionNotFound(
|
raise exception.ActionNotFound(
|
||||||
action=action_uuid)
|
action=action_uuid)
|
||||||
return action
|
return action
|
||||||
except NoResultFound:
|
except exc.NoResultFound:
|
||||||
raise exception.ActionNotFound(action=action_uuid)
|
raise exception.ActionNotFound(action=action_uuid)
|
||||||
|
|
||||||
def destroy_action(self, action_id):
|
def destroy_action(self, action_id):
|
||||||
@@ -487,7 +486,7 @@ class Connection(api.Connection):
|
|||||||
query = add_identity_filter(query, action_id)
|
query = add_identity_filter(query, action_id)
|
||||||
try:
|
try:
|
||||||
ref = query.with_lockmode('update').one()
|
ref = query.with_lockmode('update').one()
|
||||||
except NoResultFound:
|
except exc.NoResultFound:
|
||||||
raise exception.ActionNotFound(action=action_id)
|
raise exception.ActionNotFound(action=action_id)
|
||||||
|
|
||||||
ref.update(values)
|
ref.update(values)
|
||||||
@@ -501,7 +500,7 @@ class Connection(api.Connection):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
query.one()
|
query.one()
|
||||||
except NoResultFound:
|
except exc.NoResultFound:
|
||||||
raise exception.ActionNotFound(node=action_id)
|
raise exception.ActionNotFound(node=action_id)
|
||||||
|
|
||||||
query.soft_delete()
|
query.soft_delete()
|
||||||
@@ -541,7 +540,7 @@ class Connection(api.Connection):
|
|||||||
raise exception.ActionPlanNotFound(
|
raise exception.ActionPlanNotFound(
|
||||||
action_plan=action_plan_id)
|
action_plan=action_plan_id)
|
||||||
return action_plan
|
return action_plan
|
||||||
except NoResultFound:
|
except exc.NoResultFound:
|
||||||
raise exception.ActionPlanNotFound(action_plan=action_plan_id)
|
raise exception.ActionPlanNotFound(action_plan=action_plan_id)
|
||||||
|
|
||||||
def get_action_plan_by_uuid(self, context, action_plan__uuid):
|
def get_action_plan_by_uuid(self, context, action_plan__uuid):
|
||||||
@@ -555,7 +554,7 @@ class Connection(api.Connection):
|
|||||||
raise exception.ActionPlanNotFound(
|
raise exception.ActionPlanNotFound(
|
||||||
action_plan=action_plan__uuid)
|
action_plan=action_plan__uuid)
|
||||||
return action_plan
|
return action_plan
|
||||||
except NoResultFound:
|
except exc.NoResultFound:
|
||||||
raise exception.ActionPlanNotFound(action_plan=action_plan__uuid)
|
raise exception.ActionPlanNotFound(action_plan=action_plan__uuid)
|
||||||
|
|
||||||
def destroy_action_plan(self, action_plan_id):
|
def destroy_action_plan(self, action_plan_id):
|
||||||
@@ -573,7 +572,7 @@ class Connection(api.Connection):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
action_plan_ref = query.one()
|
action_plan_ref = query.one()
|
||||||
except NoResultFound:
|
except exc.NoResultFound:
|
||||||
raise exception.ActionPlanNotFound(action_plan=action_plan_id)
|
raise exception.ActionPlanNotFound(action_plan=action_plan_id)
|
||||||
|
|
||||||
if is_action_plan_referenced(session, action_plan_ref['id']):
|
if is_action_plan_referenced(session, action_plan_ref['id']):
|
||||||
@@ -596,7 +595,7 @@ class Connection(api.Connection):
|
|||||||
query = add_identity_filter(query, action_plan_id)
|
query = add_identity_filter(query, action_plan_id)
|
||||||
try:
|
try:
|
||||||
ref = query.with_lockmode('update').one()
|
ref = query.with_lockmode('update').one()
|
||||||
except NoResultFound:
|
except exc.NoResultFound:
|
||||||
raise exception.ActionPlanNotFound(action_plan=action_plan_id)
|
raise exception.ActionPlanNotFound(action_plan=action_plan_id)
|
||||||
|
|
||||||
ref.update(values)
|
ref.update(values)
|
||||||
@@ -610,7 +609,7 @@ class Connection(api.Connection):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
query.one()
|
query.one()
|
||||||
except NoResultFound:
|
except exc.NoResultFound:
|
||||||
raise exception.ActionPlanNotFound(node=action_plan_id)
|
raise exception.ActionPlanNotFound(node=action_plan_id)
|
||||||
|
|
||||||
query.soft_delete()
|
query.soft_delete()
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ from alembic import config as alembic_config
|
|||||||
import alembic.migration as alembic_migration
|
import alembic.migration as alembic_migration
|
||||||
from oslo_db import exception as db_exc
|
from oslo_db import exception as db_exc
|
||||||
|
|
||||||
|
from watcher._i18n import _
|
||||||
from watcher.db.sqlalchemy import api as sqla_api
|
from watcher.db.sqlalchemy import api as sqla_api
|
||||||
from watcher.db.sqlalchemy import models
|
from watcher.db.sqlalchemy import models
|
||||||
|
|
||||||
@@ -68,8 +69,9 @@ def create_schema(config=None, engine=None):
|
|||||||
# schema, it will only add the new tables, but leave
|
# schema, it will only add the new tables, but leave
|
||||||
# existing as is. So we should avoid of this situation.
|
# existing as is. So we should avoid of this situation.
|
||||||
if version(engine=engine) is not None:
|
if version(engine=engine) is not None:
|
||||||
raise db_exc.DbMigrationError("DB schema is already under version"
|
raise db_exc.DbMigrationError(
|
||||||
" control. Use upgrade() instead")
|
_("Watcher database schema is already under version control; "
|
||||||
|
"use upgrade() instead"))
|
||||||
|
|
||||||
models.Base.metadata.create_all(engine)
|
models.Base.metadata.create_all(engine)
|
||||||
stamp('head', config=config)
|
stamp('head', config=config)
|
||||||
|
|||||||
@@ -33,14 +33,14 @@ from sqlalchemy.types import TypeDecorator, TEXT
|
|||||||
|
|
||||||
from watcher.common import paths
|
from watcher.common import paths
|
||||||
|
|
||||||
|
|
||||||
sql_opts = [
|
sql_opts = [
|
||||||
cfg.StrOpt('mysql_engine',
|
cfg.StrOpt('mysql_engine',
|
||||||
default='InnoDB',
|
default='InnoDB',
|
||||||
help='MySQL engine to use.')
|
help='MySQL engine to use.')
|
||||||
]
|
]
|
||||||
|
|
||||||
_DEFAULT_SQL_CONNECTION = 'sqlite:///' + paths.state_path_def('watcher.sqlite')
|
_DEFAULT_SQL_CONNECTION = 'sqlite:///{0}'.format(
|
||||||
|
paths.state_path_def('watcher.sqlite'))
|
||||||
|
|
||||||
cfg.CONF.register_opts(sql_opts, 'database')
|
cfg.CONF.register_opts(sql_opts, 'database')
|
||||||
db_options.set_defaults(cfg.CONF, _DEFAULT_SQL_CONNECTION, 'watcher.sqlite')
|
db_options.set_defaults(cfg.CONF, _DEFAULT_SQL_CONNECTION, 'watcher.sqlite')
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
# Watcher Decision Engine
|
|
||||||
|
|
||||||
This component is responsible for computing a list of potential optimization actions in order to fulfill the goals of an audit.
|
|
||||||
|
|
||||||
It uses the following input data :
|
|
||||||
* current, previous and predicted state of the cluster (hosts, instances, network, ...)
|
|
||||||
* evolution of metrics within a time frame
|
|
||||||
|
|
||||||
It first selects the most appropriate optimization strategy depending on several factors :
|
|
||||||
* the optimization goals that must be fulfilled (servers consolidation, energy consumption, license optimization, ...)
|
|
||||||
* the deadline that was provided by the Openstack cluster admin for delivering an action plan
|
|
||||||
* the "aggressivity" level regarding potential optimization actions :
|
|
||||||
* is it allowed to do a lot of instance migrations ?
|
|
||||||
* is it allowed to consume a lot of bandwidth on the admin network ?
|
|
||||||
* is it allowed to violate initial placement constraint such as affinity/anti-affinity, region, ... ?
|
|
||||||
|
|
||||||
The strategy is then executed and generates a list of Meta-Actions in order to fulfill the goals of the Audit.
|
|
||||||
|
|
||||||
A Meta-Action is a generic optimization task which is independent from the target cluster implementation (Openstack, ...). For example, an instance migration is a Meta-Action which corresponds, in the Openstack context, to a set of technical actions on the Nova, Cinder and Neutron components.
|
|
||||||
|
|
||||||
Using Meta-Actions instead of technical actions brings two advantages in Watcher :
|
|
||||||
* a loose coupling between the Watcher Decision Engine and the Watcher Applier
|
|
||||||
* a simplification of the optimization algorithms which don't need to know the underlying technical cluster implementation
|
|
||||||
|
|
||||||
Beyond that, the Meta-Actions which are computed by the optimization strategy are not necessarily ordered in time (it depends on the selected Strategy). Therefore, the Actions Planner module of Decision Engine reorganizes the list of Meta-Actions into an ordered sequence of technical actions (migrations, ...) such that all security, dependency, and performance requirements are met. An ordered sequence of technical actions is called an "Action Plan".
|
|
||||||
|
|
||||||
The Decision Engine saves the generated Action Plan in the Watcher Database. This Action Plan is loaded later by the Watcher Actions Applier.
|
|
||||||
|
|
||||||
Like every Watcher component, the Decision Engine notifies its current status (learning phase, current status of each Audit, ...) on the message/notification bus.
|
|
||||||
|
|
||||||
## Watcher Compute Node Profiler
|
|
||||||
|
|
||||||
This module of the Decision Engine is responsible for profiling a new compute node. When a new compute node is added to the cluster, it automatically triggers test scripts in order to extract profiling information such as :
|
|
||||||
* the maximum I/O available on each disk
|
|
||||||
* the evolution of energy consumption for a given workload
|
|
||||||
|
|
||||||
It stores those information in the Watcher database. They may be used by any optimization strategy that needs to rely on real metrics about a given physical machine and not only theoretical metrics.
|
|
||||||
|
|
||||||
## Watcher Metrics Predictor
|
|
||||||
|
|
||||||
This module of the Decision Engine is able to compute some predicted metric values according to previously acquired metrics.
|
|
||||||
|
|
||||||
For instance, it may be able to predict the future CPU in the next 5 minutes for a given instance given the previous CPU load during the last 2 hours (relying on some neural network algorithm or any other machine learning system).
|
|
||||||
|
|
||||||
This component pushes the new predicted metrics to the CEP in order to trigger new actions if needed.
|
|
||||||
|
|
||||||
## Watcher Cluster State Collector
|
|
||||||
|
|
||||||
This module of the Decision Engine provides a high level API for requesting status information from the Nova API.
|
|
||||||
|
|
||||||
A DSL will be provided in order to ease the development of new optimization strategies.
|
|
||||||
|
|
||||||
Example of high level requests that may be provided :
|
|
||||||
* get the difference between current cluster state and cluster state yesterday at the same time
|
|
||||||
* get the state evolution in time of a group of instances from 9 AM to 10 AM for every day of the week
|
|
||||||
* ...
|
|
||||||
|
|
||||||
|
|
||||||
## Watcher Actions Planner
|
|
||||||
|
|
||||||
This module of the Decision Engine translates Meta-Actions into technical actions on the Openstack modules (Nova, Cinder, ...) and builds an appropriate workflow which defines how-to schedule in time those different technical actions and for each action what are the pre-requisite conditions.
|
|
||||||
|
|
||||||
Today, the Action Plan is just a simple chain of sequential actions but in later versions, we intend to rely on more complex workflow models description formats, such as [BPMN 2.0](http://www.bpmn.org/), which enable a complete definition of activity diagrams containing sequential and parallel tasks.
|
|
||||||
@@ -17,13 +17,13 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
import abc
|
import abc
|
||||||
import six
|
|
||||||
|
|
||||||
from watcher.decision_engine.strategy.level import StrategyLevel
|
import six
|
||||||
|
from watcher.decision_engine.strategy.common.level import StrategyLevel
|
||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(abc.ABCMeta)
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
class MetaAction(object):
|
class BaseAction(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._level = StrategyLevel.conservative
|
self._level = StrategyLevel.conservative
|
||||||
self._priority = 0
|
self._priority = 0
|
||||||
@@ -43,6 +43,3 @@ class MetaAction(object):
|
|||||||
@priority.setter
|
@priority.setter
|
||||||
def priority(self, p):
|
def priority(self, p):
|
||||||
self._priority = p
|
self._priority = p
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return " "
|
|
||||||
@@ -16,18 +16,18 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
from watcher.decision_engine.meta_action.base import MetaAction
|
|
||||||
|
from watcher.decision_engine.actions.base import BaseAction
|
||||||
from watcher.decision_engine.model.hypervisor_state import HypervisorState
|
from watcher.decision_engine.model.hypervisor_state import HypervisorState
|
||||||
|
|
||||||
|
|
||||||
class ChangeHypervisorState(MetaAction):
|
class ChangeHypervisorState(BaseAction):
|
||||||
def __init__(self, target):
|
def __init__(self, target):
|
||||||
MetaAction.__init__(self)
|
'''The target host to change the state
|
||||||
'''The target host to change the power
|
|
||||||
|
|
||||||
:param target:
|
:param target: the target hypervisor uuid
|
||||||
:return:
|
|
||||||
'''
|
'''
|
||||||
|
super(ChangeHypervisorState, self).__init__()
|
||||||
self._target = target
|
self._target = target
|
||||||
self._state = HypervisorState.ONLINE
|
self._state = HypervisorState.ONLINE
|
||||||
|
|
||||||
@@ -48,5 +48,5 @@ class ChangeHypervisorState(MetaAction):
|
|||||||
self._target = p
|
self._target = p
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "{0} {1} ChangeHypervisorState => {2}".format(
|
return "{} ChangeHypervisorState => {}".format(self.target,
|
||||||
MetaAction.__str__(self), self.target, self.state)
|
self.state)
|
||||||
72
watcher/decision_engine/actions/migration.py
Normal file
72
watcher/decision_engine/actions/migration.py
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
# Copyright (c) 2015 b<>com
|
||||||
|
#
|
||||||
|
# Authors: Jean-Emile DARTOIS <jean-emile.dartois@b-com.com>
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
# implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
from watcher.decision_engine.actions.base import BaseAction
|
||||||
|
|
||||||
|
|
||||||
|
class MigrationType(Enum):
|
||||||
|
# Total migration time and downtime depend on memory dirtying speed
|
||||||
|
pre_copy = 0
|
||||||
|
# Postcopy transfer a page only once reliability
|
||||||
|
post_copy = 1
|
||||||
|
|
||||||
|
|
||||||
|
class Migrate(BaseAction):
|
||||||
|
def __init__(self, vm, src_hypervisor, dest_hypervisor):
|
||||||
|
"""Request to migrate a virtual machine from a host to another
|
||||||
|
|
||||||
|
:param vm: the virtual machine uuid to migrate
|
||||||
|
:param src_hypervisor: uuid
|
||||||
|
:param dest_hypervisor: uuid
|
||||||
|
"""
|
||||||
|
super(Migrate, self).__init__()
|
||||||
|
self._reserved_disk_iops = 0
|
||||||
|
self._remaining_dirty_pages = 0
|
||||||
|
self._vm = vm
|
||||||
|
self._migration_type = MigrationType.pre_copy
|
||||||
|
self._src_hypervisor = src_hypervisor
|
||||||
|
self._dest_hypervisor = dest_hypervisor
|
||||||
|
|
||||||
|
@property
|
||||||
|
def migration_type(self):
|
||||||
|
return self._migration_type
|
||||||
|
|
||||||
|
@migration_type.setter
|
||||||
|
def migration_type(self, type):
|
||||||
|
self._migration_type = type
|
||||||
|
|
||||||
|
@property
|
||||||
|
def vm(self):
|
||||||
|
return self._vm
|
||||||
|
|
||||||
|
@property
|
||||||
|
def src_hypervisor(self):
|
||||||
|
return self._src_hypervisor
|
||||||
|
|
||||||
|
@property
|
||||||
|
def dest_hypervisor(self):
|
||||||
|
return self._dest_hypervisor
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "Migrate {} from {} to {}".format(
|
||||||
|
self.vm,
|
||||||
|
self.src_hypervisor,
|
||||||
|
self.dest_hypervisor)
|
||||||
@@ -17,12 +17,10 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
|
||||||
from enum import Enum
|
|
||||||
|
from watcher.decision_engine.actions.base import BaseAction
|
||||||
|
|
||||||
|
|
||||||
class StrategyState(Enum):
|
class Nop(BaseAction):
|
||||||
INIT = 1,
|
def __str__(self):
|
||||||
READY = 2,
|
return "Nop"
|
||||||
RUNNING = 3,
|
|
||||||
TERMINATED = 4,
|
|
||||||
ERROR = 5
|
|
||||||
@@ -16,18 +16,18 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
from watcher.decision_engine.meta_action.base import MetaAction
|
|
||||||
|
from watcher.decision_engine.actions.base import BaseAction
|
||||||
from watcher.decision_engine.model.power_state import PowerState
|
from watcher.decision_engine.model.power_state import PowerState
|
||||||
|
|
||||||
|
|
||||||
class ChangePowerState(MetaAction):
|
class ChangePowerState(BaseAction):
|
||||||
def __init__(self, target):
|
def __init__(self, target):
|
||||||
MetaAction.__init__(self)
|
|
||||||
"""The target host to change the power
|
"""The target host to change the power
|
||||||
|
|
||||||
:param target:
|
:param target:
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
|
super(ChangePowerState, self).__init__()
|
||||||
self._target = target
|
self._target = target
|
||||||
self._power_state = PowerState.g0
|
self._power_state = PowerState.g0
|
||||||
|
|
||||||
@@ -44,10 +44,9 @@ class ChangePowerState(MetaAction):
|
|||||||
return self._target
|
return self._target
|
||||||
|
|
||||||
@target.setter
|
@target.setter
|
||||||
def target(self, p):
|
def target(self, t):
|
||||||
self._target = p
|
self._target = t
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "{0} ChangePowerState {1} => {2} ".format(
|
return "ChangePowerState {} => {} ".format(self.target,
|
||||||
MetaAction.__str__(self),
|
self.powerstate)
|
||||||
self.target, self.powerstate)
|
|
||||||
@@ -21,8 +21,7 @@ import six
|
|||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(abc.ABCMeta)
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
class BaseDecisionEngineCommand(object):
|
class BaseAuditHandler(object):
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def execute(self):
|
def execute(self):
|
||||||
raise NotImplementedError(
|
raise NotImplementedError()
|
||||||
"Should have implemented this") # pragma:no cover
|
|
||||||
85
watcher/decision_engine/audit/default.py
Normal file
85
watcher/decision_engine/audit/default.py
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
# Copyright (c) 2015 b<>com
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
# 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.event import Event
|
||||||
|
from watcher.decision_engine.audit.base import \
|
||||||
|
BaseAuditHandler
|
||||||
|
from watcher.decision_engine.messaging.events import Events
|
||||||
|
from watcher.decision_engine.planner.default import DefaultPlanner
|
||||||
|
from watcher.decision_engine.strategy.context.default import \
|
||||||
|
DefaultStrategyContext
|
||||||
|
from watcher.objects.audit import Audit
|
||||||
|
from watcher.objects.audit import AuditStatus
|
||||||
|
|
||||||
|
|
||||||
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class DefaultAuditHandler(BaseAuditHandler):
|
||||||
|
def __init__(self, messaging):
|
||||||
|
super(DefaultAuditHandler, self).__init__()
|
||||||
|
self._messaging = messaging
|
||||||
|
self._strategy_context = DefaultStrategyContext()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def messaging(self):
|
||||||
|
return self._messaging
|
||||||
|
|
||||||
|
@property
|
||||||
|
def strategy_context(self):
|
||||||
|
return self._strategy_context
|
||||||
|
|
||||||
|
def notify(self, audit_uuid, event_type, status):
|
||||||
|
event = Event()
|
||||||
|
event.type = event_type
|
||||||
|
event.data = {}
|
||||||
|
payload = {'audit_uuid': audit_uuid,
|
||||||
|
'audit_status': status}
|
||||||
|
self.messaging.topic_status.publish_event(event.type.name,
|
||||||
|
payload)
|
||||||
|
|
||||||
|
def update_audit_state(self, request_context, audit_uuid, state):
|
||||||
|
LOG.debug("Update audit state:{0} ".format(state))
|
||||||
|
audit = Audit.get_by_uuid(request_context, audit_uuid)
|
||||||
|
audit.state = state
|
||||||
|
audit.save()
|
||||||
|
self.notify(audit_uuid, Events.TRIGGER_AUDIT, state)
|
||||||
|
return audit
|
||||||
|
|
||||||
|
def execute(self, audit_uuid, request_context):
|
||||||
|
try:
|
||||||
|
LOG.debug("Trigger audit %s" % audit_uuid)
|
||||||
|
|
||||||
|
# change state of the audit to ONGOING
|
||||||
|
audit = self.update_audit_state(request_context, audit_uuid,
|
||||||
|
AuditStatus.ONGOING)
|
||||||
|
|
||||||
|
# execute the strategy
|
||||||
|
solution = self.strategy_context.execute_strategy(audit_uuid,
|
||||||
|
request_context)
|
||||||
|
|
||||||
|
# schedule the actions and create in the watcher db the ActionPlan
|
||||||
|
planner = DefaultPlanner()
|
||||||
|
planner.schedule(request_context, audit.id, solution)
|
||||||
|
|
||||||
|
# change state of the audit to SUCCEEDED
|
||||||
|
self.update_audit_state(request_context, audit_uuid,
|
||||||
|
AuditStatus.SUCCEEDED)
|
||||||
|
except Exception as e:
|
||||||
|
LOG.exception(e)
|
||||||
|
self.update_audit_state(request_context, audit_uuid,
|
||||||
|
AuditStatus.FAILED)
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
# Copyright (c) 2015 b<>com
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
# 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.event import Event
|
|
||||||
from watcher.decision_engine.messaging.command.base import \
|
|
||||||
BaseDecisionEngineCommand
|
|
||||||
from watcher.decision_engine.messaging.events import Events
|
|
||||||
from watcher.decision_engine.planner.default import DefaultPlanner
|
|
||||||
from watcher.decision_engine.strategy.context.default import StrategyContext
|
|
||||||
from watcher.objects.audit import Audit
|
|
||||||
from watcher.objects.audit import AuditStatus
|
|
||||||
from watcher.objects.audit_template import AuditTemplate
|
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class TriggerAuditCommand(BaseDecisionEngineCommand):
|
|
||||||
def __init__(self, messaging, model_collector):
|
|
||||||
self.messaging = messaging
|
|
||||||
self.model_collector = model_collector
|
|
||||||
self.strategy_context = StrategyContext()
|
|
||||||
|
|
||||||
def notify(self, audit_uuid, event_type, status):
|
|
||||||
event = Event()
|
|
||||||
event.set_type(event_type)
|
|
||||||
event.set_data({})
|
|
||||||
payload = {'audit_uuid': audit_uuid,
|
|
||||||
'audit_status': status}
|
|
||||||
self.messaging.topic_status.publish_event(event.get_type().name,
|
|
||||||
payload)
|
|
||||||
|
|
||||||
def update_audit(self, request_context, audit_uuid, state):
|
|
||||||
LOG.debug("update audit {0} ".format(state))
|
|
||||||
audit = Audit.get_by_uuid(request_context, audit_uuid)
|
|
||||||
audit.state = state
|
|
||||||
audit.save()
|
|
||||||
self.notify(audit_uuid, Events.TRIGGER_AUDIT, state)
|
|
||||||
return audit
|
|
||||||
|
|
||||||
def execute(self, audit_uuid, request_context):
|
|
||||||
try:
|
|
||||||
LOG.debug("Execute TriggerAuditCommand ")
|
|
||||||
|
|
||||||
# 1 - change status to ONGOING
|
|
||||||
audit = self.update_audit(request_context, audit_uuid,
|
|
||||||
AuditStatus.ONGOING)
|
|
||||||
|
|
||||||
# 3 - Retrieve cluster-data-model
|
|
||||||
cluster = self.model_collector.get_latest_cluster_data_model()
|
|
||||||
|
|
||||||
# 4 - Select appropriate strategy
|
|
||||||
audit_template = AuditTemplate.get_by_id(request_context,
|
|
||||||
audit.audit_template_id)
|
|
||||||
|
|
||||||
self.strategy_context.set_goal(audit_template.goal)
|
|
||||||
|
|
||||||
# 5 - compute change requests
|
|
||||||
solution = self.strategy_context.execute_strategy(cluster)
|
|
||||||
|
|
||||||
# 6 - create an action plan
|
|
||||||
planner = DefaultPlanner()
|
|
||||||
planner.schedule(request_context, audit.id, solution)
|
|
||||||
|
|
||||||
# 7 - change status to SUCCEEDED and notify
|
|
||||||
self.update_audit(request_context, audit_uuid,
|
|
||||||
AuditStatus.SUCCEEDED)
|
|
||||||
except Exception as e:
|
|
||||||
self.update_audit(request_context, audit_uuid, AuditStatus.FAILED)
|
|
||||||
LOG.error("Execute audit command {0} ".format(unicode(e)))
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
# Copyright (c) 2015 b<>com
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
# implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
|
|
||||||
class EventConsumerFactory(object):
|
|
||||||
|
|
||||||
def factory(self, type):
|
|
||||||
"""Factory so as to create
|
|
||||||
|
|
||||||
:param type:
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
# return eval(type + "()")
|
|
||||||
raise AssertionError()
|
|
||||||
@@ -16,17 +16,13 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
|
||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
|
|
||||||
from watcher.common.messaging.messaging_core import MessagingCore
|
from watcher.common.messaging.messaging_core import MessagingCore
|
||||||
from watcher.common.messaging.notification_handler import NotificationHandler
|
|
||||||
from watcher.decision_engine.event.consumer_factory import EventConsumerFactory
|
|
||||||
from watcher.decision_engine.messaging.audit_endpoint import AuditEndpoint
|
from watcher.decision_engine.messaging.audit_endpoint import AuditEndpoint
|
||||||
from watcher.decision_engine.messaging.events import Events
|
|
||||||
from watcher.decision_engine.strategy.context.default import StrategyContext
|
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
@@ -47,49 +43,33 @@ WATCHER_DECISION_ENGINE_OPTS = [
|
|||||||
cfg.StrOpt('publisher_id',
|
cfg.StrOpt('publisher_id',
|
||||||
default='watcher.decision.api',
|
default='watcher.decision.api',
|
||||||
help='The identifier used by watcher '
|
help='The identifier used by watcher '
|
||||||
'module on the message broker')
|
'module on the message broker'),
|
||||||
|
cfg.IntOpt('max_workers',
|
||||||
|
default=2,
|
||||||
|
required=True,
|
||||||
|
help='The maximum number of threads that can be used to '
|
||||||
|
'execute strategies',
|
||||||
|
),
|
||||||
]
|
]
|
||||||
decision_engine_opt_group = cfg.OptGroup(
|
decision_engine_opt_group = cfg.OptGroup(name='watcher_decision_engine',
|
||||||
name='watcher_decision_engine',
|
title='Defines the parameters of '
|
||||||
title='Defines the parameters of the module decision engine')
|
'the module decision engine')
|
||||||
CONF.register_group(decision_engine_opt_group)
|
CONF.register_group(decision_engine_opt_group)
|
||||||
CONF.register_opts(WATCHER_DECISION_ENGINE_OPTS, decision_engine_opt_group)
|
CONF.register_opts(WATCHER_DECISION_ENGINE_OPTS, decision_engine_opt_group)
|
||||||
|
|
||||||
|
|
||||||
class DecisionEngineManager(MessagingCore):
|
class DecisionEngineManager(MessagingCore):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(DecisionEngineManager, self).__init__(
|
super(DecisionEngineManager, self).__init__(
|
||||||
CONF.watcher_decision_engine.publisher_id,
|
CONF.watcher_decision_engine.publisher_id,
|
||||||
CONF.watcher_decision_engine.topic_control,
|
CONF.watcher_decision_engine.topic_control,
|
||||||
CONF.watcher_decision_engine.topic_status,
|
CONF.watcher_decision_engine.topic_status,
|
||||||
api_version=self.API_VERSION,
|
api_version=self.API_VERSION)
|
||||||
)
|
endpoint = AuditEndpoint(self,
|
||||||
self.handler = NotificationHandler(self.publisher_id)
|
max_workers=CONF.watcher_decision_engine.
|
||||||
self.handler.register_observer(self)
|
max_workers)
|
||||||
self.add_event_listener(Events.ALL, self.event_receive)
|
self.topic_control.add_endpoint(endpoint)
|
||||||
# todo(jed) oslo_conf
|
|
||||||
self.executor = ThreadPoolExecutor(max_workers=2)
|
|
||||||
self.topic_control.add_endpoint(AuditEndpoint(self))
|
|
||||||
self.context = StrategyContext(self)
|
|
||||||
|
|
||||||
def join(self):
|
def join(self):
|
||||||
self.topic_control.join()
|
self.topic_control.join()
|
||||||
self.topic_status.join()
|
self.topic_status.join()
|
||||||
|
|
||||||
# TODO(ebe): Producer / consumer
|
|
||||||
def event_receive(self, event):
|
|
||||||
try:
|
|
||||||
request_id = event.get_request_id()
|
|
||||||
event_type = event.get_type()
|
|
||||||
data = event.get_data()
|
|
||||||
LOG.debug("request id => %s" % event.get_request_id())
|
|
||||||
LOG.debug("type_event => %s" % str(event.get_type()))
|
|
||||||
LOG.debug("data => %s" % str(data))
|
|
||||||
|
|
||||||
event_consumer = EventConsumerFactory().factory(event_type)
|
|
||||||
event_consumer.messaging = self
|
|
||||||
event_consumer.execute(request_id, data)
|
|
||||||
except Exception as e:
|
|
||||||
LOG.error("evt %s" % e.message)
|
|
||||||
raise e
|
|
||||||
|
|||||||
@@ -16,29 +16,35 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
|
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
|
|
||||||
from watcher.decision_engine.command.audit import TriggerAuditCommand
|
from watcher.decision_engine.audit.default import DefaultAuditHandler
|
||||||
from watcher.metrics_engine.cluster_model_collector.manager import \
|
|
||||||
CollectorManager
|
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class AuditEndpoint(object):
|
class AuditEndpoint(object):
|
||||||
def __init__(self, de):
|
def __init__(self, messaging, max_workers):
|
||||||
self.de = de
|
self._messaging = messaging
|
||||||
self.manager = CollectorManager()
|
self._executor = ThreadPoolExecutor(max_workers=max_workers)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def executor(self):
|
||||||
|
return self._executor
|
||||||
|
|
||||||
|
@property
|
||||||
|
def messaging(self):
|
||||||
|
return self._messaging
|
||||||
|
|
||||||
def do_trigger_audit(self, context, audit_uuid):
|
def do_trigger_audit(self, context, audit_uuid):
|
||||||
model_collector = self.manager.get_cluster_model_collector()
|
audit = DefaultAuditHandler(self.messaging)
|
||||||
|
|
||||||
audit = TriggerAuditCommand(self.de, model_collector)
|
|
||||||
audit.execute(audit_uuid, context)
|
audit.execute(audit_uuid, context)
|
||||||
|
|
||||||
def trigger_audit(self, context, audit_uuid):
|
def trigger_audit(self, context, audit_uuid):
|
||||||
LOG.debug("Trigger audit %s" % audit_uuid)
|
LOG.debug("Trigger audit %s" % audit_uuid)
|
||||||
self.de.executor.submit(self.do_trigger_audit,
|
self.executor.submit(self.do_trigger_audit,
|
||||||
context,
|
context,
|
||||||
audit_uuid)
|
audit_uuid)
|
||||||
return audit_uuid
|
return audit_uuid
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
# Copyright (c) 2015 b<>com
|
|
||||||
#
|
|
||||||
# Authors: Jean-Emile DARTOIS <jean-emile.dartois@b-com.com>
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
# implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
#
|
|
||||||
import abc
|
|
||||||
import six
|
|
||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(abc.ABCMeta)
|
|
||||||
class BaseEventConsumer(object):
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self._messaging = None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def messaging(self):
|
|
||||||
return self._messaging
|
|
||||||
|
|
||||||
@messaging.setter
|
|
||||||
def messaging(self, e):
|
|
||||||
self._messaging = e
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def execute(self, request_id, context, data):
|
|
||||||
raise NotImplementedError('Not implemented ...') # pragma:no cover
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
# Copyright (c) 2015 b<>com
|
|
||||||
#
|
|
||||||
# Authors: Jean-Emile DARTOIS <jean-emile.dartois@b-com.com>
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
# implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
#
|
|
||||||
|
|
||||||
from enum import Enum
|
|
||||||
|
|
||||||
from watcher.decision_engine.meta_action.base import MetaAction
|
|
||||||
|
|
||||||
|
|
||||||
class MigrationType(Enum):
|
|
||||||
# Total migration time and downtime depend on memory dirtying speed
|
|
||||||
pre_copy = 0
|
|
||||||
# Postcopy transfer a page only once reliability
|
|
||||||
post_copy = 1
|
|
||||||
|
|
||||||
|
|
||||||
class Migrate(MetaAction):
|
|
||||||
def __init__(self, vm, source_hypervisor, dest_hypervisor):
|
|
||||||
MetaAction.__init__(self)
|
|
||||||
"""Request Migrate
|
|
||||||
:param bandwidth the bandwidth reserved for the migration
|
|
||||||
:param vm: the virtual machine to migrate
|
|
||||||
:param source_hypervisor:
|
|
||||||
:param dest_hypervisor:
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
self.bandwidth = 0
|
|
||||||
self.reservedDiskIOPS = 0
|
|
||||||
self.remainingDirtyPages = 0
|
|
||||||
self.vm = vm
|
|
||||||
self.migration_type = MigrationType.pre_copy
|
|
||||||
self.source_hypervisor = source_hypervisor
|
|
||||||
self.dest_hypervisor = dest_hypervisor
|
|
||||||
|
|
||||||
def set_migration_type(self, type):
|
|
||||||
self.migration_type = type
|
|
||||||
|
|
||||||
def set_bandwidth(self, bw):
|
|
||||||
"""Set the bandwidth reserved for the migration
|
|
||||||
|
|
||||||
:param bw: bandwidth
|
|
||||||
"""
|
|
||||||
self.bandwidth = bw
|
|
||||||
|
|
||||||
def get_bandwidth(self):
|
|
||||||
return self.bandwidth
|
|
||||||
|
|
||||||
def get_vm(self):
|
|
||||||
return self.vm
|
|
||||||
|
|
||||||
def get_source_hypervisor(self):
|
|
||||||
return self.source_hypervisor
|
|
||||||
|
|
||||||
def get_dest_hypervisor(self):
|
|
||||||
return self.dest_hypervisor
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return "{0} Migrate {1} from {2} to {3}".format(
|
|
||||||
MetaAction.__str__(self), self.vm,
|
|
||||||
self.source_hypervisor,
|
|
||||||
self.dest_hypervisor)
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
# Copyright (c) 2015 b<>com
|
|
||||||
#
|
|
||||||
# Authors: Jean-Emile DARTOIS <jean-emile.dartois@b-com.com>
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
# implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
#
|
|
||||||
|
|
||||||
from watcher.decision_engine.meta_action.base import MetaAction
|
|
||||||
|
|
||||||
|
|
||||||
class Nop(MetaAction):
|
|
||||||
def __str__(self):
|
|
||||||
return "{0} Nop".format(MetaAction.__str__(self))
|
|
||||||
@@ -32,6 +32,7 @@ class Mapping(object):
|
|||||||
:param hypervisor: the hypervisor
|
:param hypervisor: the hypervisor
|
||||||
:param vm: the virtual machine or instance
|
:param vm: the virtual machine or instance
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.lock.acquire()
|
self.lock.acquire()
|
||||||
|
|
||||||
@@ -55,13 +56,15 @@ class Mapping(object):
|
|||||||
:param hypervisor: the hypervisor
|
:param hypervisor: the hypervisor
|
||||||
:param vm: the virtual machine or instance
|
:param vm: the virtual machine or instance
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.unmap_from_id(hypervisor.uuid, vm.uuid)
|
self.unmap_from_id(hypervisor.uuid, vm.uuid)
|
||||||
|
|
||||||
def unmap_from_id(self, node_uuid, vm_uuid):
|
def unmap_from_id(self, node_uuid, vm_uuid):
|
||||||
"""
|
"""Remove the instance (by id) from the hypervisor (by id)
|
||||||
|
|
||||||
:rtype : object
|
:rtype : object
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.lock.acquire()
|
self.lock.acquire()
|
||||||
if str(node_uuid) in self._mapping_hypervisors:
|
if str(node_uuid) in self._mapping_hypervisors:
|
||||||
@@ -91,6 +94,7 @@ class Mapping(object):
|
|||||||
:param vm: the uuid of the instance
|
:param vm: the uuid of the instance
|
||||||
:return: hypervisor
|
:return: hypervisor
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return self.model.get_hypervisor_from_id(
|
return self.model.get_hypervisor_from_id(
|
||||||
self.get_mapping_vm()[str(vm_uuid)])
|
self.get_mapping_vm()[str(vm_uuid)])
|
||||||
|
|
||||||
@@ -117,6 +121,7 @@ class Mapping(object):
|
|||||||
:param dest_hypervisor:
|
:param dest_hypervisor:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if src_hypervisor == dest_hypervisor:
|
if src_hypervisor == dest_hypervisor:
|
||||||
return False
|
return False
|
||||||
# unmap
|
# unmap
|
||||||
|
|||||||
@@ -15,12 +15,11 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
|
|
||||||
from watcher.common.exception import HypervisorNotFound
|
from watcher._i18n import _
|
||||||
from watcher.common.exception import IllegalArgumentException
|
from watcher.common import exception
|
||||||
from watcher.common.exception import VMNotFound
|
from watcher.decision_engine.model import hypervisor
|
||||||
from watcher.decision_engine.model.hypervisor import Hypervisor
|
from watcher.decision_engine.model import mapping
|
||||||
from watcher.decision_engine.model.mapping import Mapping
|
from watcher.decision_engine.model import vm
|
||||||
from watcher.decision_engine.model.vm import VM
|
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
@@ -29,18 +28,18 @@ class ModelRoot(object):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._hypervisors = {}
|
self._hypervisors = {}
|
||||||
self._vms = {}
|
self._vms = {}
|
||||||
self.mapping = Mapping(self)
|
self.mapping = mapping.Mapping(self)
|
||||||
self.resource = {}
|
self.resource = {}
|
||||||
|
|
||||||
def assert_hypervisor(self, hypervisor):
|
def assert_hypervisor(self, obj):
|
||||||
if not isinstance(hypervisor, Hypervisor):
|
if not isinstance(obj, hypervisor.Hypervisor):
|
||||||
raise IllegalArgumentException(
|
raise exception.IllegalArgumentException(
|
||||||
"Hypervisor must be an instance of hypervisor")
|
_("'obj' argument type is not valid"))
|
||||||
|
|
||||||
def assert_vm(self, vm):
|
def assert_vm(self, obj):
|
||||||
if not isinstance(vm, VM):
|
if not isinstance(obj, vm.VM):
|
||||||
raise IllegalArgumentException(
|
raise exception.IllegalArgumentException(
|
||||||
"VM must be an instance of VM")
|
_("'obj' argument type is not valid"))
|
||||||
|
|
||||||
def add_hypervisor(self, hypervisor):
|
def add_hypervisor(self, hypervisor):
|
||||||
self.assert_hypervisor(hypervisor)
|
self.assert_hypervisor(hypervisor)
|
||||||
@@ -49,7 +48,7 @@ class ModelRoot(object):
|
|||||||
def remove_hypervisor(self, hypervisor):
|
def remove_hypervisor(self, hypervisor):
|
||||||
self.assert_hypervisor(hypervisor)
|
self.assert_hypervisor(hypervisor)
|
||||||
if str(hypervisor.uuid) not in self._hypervisors.keys():
|
if str(hypervisor.uuid) not in self._hypervisors.keys():
|
||||||
raise HypervisorNotFound(hypervisor.uuid)
|
raise exception.HypervisorNotFound(hypervisor.uuid)
|
||||||
else:
|
else:
|
||||||
del self._hypervisors[hypervisor.uuid]
|
del self._hypervisors[hypervisor.uuid]
|
||||||
|
|
||||||
@@ -62,12 +61,12 @@ class ModelRoot(object):
|
|||||||
|
|
||||||
def get_hypervisor_from_id(self, hypervisor_uuid):
|
def get_hypervisor_from_id(self, hypervisor_uuid):
|
||||||
if str(hypervisor_uuid) not in self._hypervisors.keys():
|
if str(hypervisor_uuid) not in self._hypervisors.keys():
|
||||||
raise HypervisorNotFound(hypervisor_uuid)
|
raise exception.HypervisorNotFound(hypervisor_uuid)
|
||||||
return self._hypervisors[str(hypervisor_uuid)]
|
return self._hypervisors[str(hypervisor_uuid)]
|
||||||
|
|
||||||
def get_vm_from_id(self, uuid):
|
def get_vm_from_id(self, uuid):
|
||||||
if str(uuid) not in self._vms.keys():
|
if str(uuid) not in self._vms.keys():
|
||||||
raise VMNotFound(uuid)
|
raise exception.VMNotFound(uuid)
|
||||||
return self._vms[str(uuid)]
|
return self._vms[str(uuid)]
|
||||||
|
|
||||||
def get_all_vms(self):
|
def get_all_vms(self):
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import six
|
|||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(abc.ABCMeta)
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
class Planner(object):
|
class BasePlanner(object):
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def schedule(self, context, audit_uuid, solution):
|
def schedule(self, context, audit_uuid, solution):
|
||||||
"""The planner receives a solution to schedule
|
"""The planner receives a solution to schedule
|
||||||
@@ -33,5 +33,4 @@ class Planner(object):
|
|||||||
and performance requirements are met.
|
and performance requirements are met.
|
||||||
"""
|
"""
|
||||||
# example: directed acyclic graph
|
# example: directed acyclic graph
|
||||||
raise NotImplementedError(
|
raise NotImplementedError()
|
||||||
"Should have implemented this") # pragma:no cover
|
|
||||||
|
|||||||
@@ -19,20 +19,17 @@
|
|||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
|
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from watcher.common.exception import MetaActionNotFound
|
|
||||||
|
from watcher._i18n import _LW
|
||||||
|
from watcher.common import exception
|
||||||
from watcher.common import utils
|
from watcher.common import utils
|
||||||
from watcher.decision_engine.planner.base import Planner
|
from watcher.decision_engine.actions import hypervisor_state
|
||||||
|
from watcher.decision_engine.actions import migration
|
||||||
|
from watcher.decision_engine.actions import nop
|
||||||
|
from watcher.decision_engine.actions import power_state
|
||||||
|
from watcher.decision_engine.planner import base
|
||||||
from watcher import objects
|
from watcher import objects
|
||||||
|
|
||||||
from watcher.decision_engine.meta_action.hypervisor_state import \
|
|
||||||
ChangeHypervisorState
|
|
||||||
from watcher.decision_engine.meta_action.migrate import Migrate
|
|
||||||
from watcher.decision_engine.meta_action.nop import Nop
|
|
||||||
from watcher.decision_engine.meta_action.power_state import ChangePowerState
|
|
||||||
from watcher.objects.action import Status as AStatus
|
|
||||||
from watcher.objects.action_plan import Status as APStatus
|
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@@ -57,7 +54,7 @@ priority_primitives = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class DefaultPlanner(Planner):
|
class DefaultPlanner(base.BasePlanner):
|
||||||
def create_action(self, action_plan_id, action_type, applies_to=None,
|
def create_action(self, action_plan_id, action_type, applies_to=None,
|
||||||
src=None,
|
src=None,
|
||||||
dst=None,
|
dst=None,
|
||||||
@@ -74,7 +71,7 @@ class DefaultPlanner(Planner):
|
|||||||
'dst': dst,
|
'dst': dst,
|
||||||
'parameter': parameter,
|
'parameter': parameter,
|
||||||
'description': description,
|
'description': description,
|
||||||
'state': AStatus.PENDING,
|
'state': objects.action.Status.PENDING,
|
||||||
'alarm': None,
|
'alarm': None,
|
||||||
'next': None,
|
'next': None,
|
||||||
}
|
}
|
||||||
@@ -84,24 +81,24 @@ class DefaultPlanner(Planner):
|
|||||||
LOG.debug('Create an action plan for the audit uuid')
|
LOG.debug('Create an action plan for the audit uuid')
|
||||||
action_plan = self._create_action_plan(context, audit_id)
|
action_plan = self._create_action_plan(context, audit_id)
|
||||||
|
|
||||||
actions = list(solution.meta_actions)
|
actions = list(solution.actions)
|
||||||
to_schedule = []
|
to_schedule = []
|
||||||
|
|
||||||
for action in actions:
|
for action in actions:
|
||||||
if isinstance(action, Migrate):
|
if isinstance(action, migration.Migrate):
|
||||||
# TODO(jed) type
|
# TODO(jed) type
|
||||||
primitive = self.create_action(action_plan.id,
|
primitive = self.create_action(action_plan.id,
|
||||||
Primitives.LIVE_MIGRATE.value,
|
Primitives.LIVE_MIGRATE.value,
|
||||||
action.get_vm().uuid,
|
action.vm.uuid,
|
||||||
action.get_source_hypervisor().
|
action.src_hypervisor.
|
||||||
uuid,
|
uuid,
|
||||||
action.get_dest_hypervisor().
|
action.dest_hypervisor.
|
||||||
uuid,
|
uuid,
|
||||||
description="{0}".format(
|
description="{0}".format(
|
||||||
action)
|
action)
|
||||||
)
|
)
|
||||||
|
|
||||||
elif isinstance(action, ChangePowerState):
|
elif isinstance(action, power_state.ChangePowerState):
|
||||||
primitive = self.create_action(action_plan_id=action_plan.id,
|
primitive = self.create_action(action_plan_id=action_plan.id,
|
||||||
action_type=Primitives.
|
action_type=Primitives.
|
||||||
POWER_STATE.value,
|
POWER_STATE.value,
|
||||||
@@ -111,7 +108,7 @@ class DefaultPlanner(Planner):
|
|||||||
value,
|
value,
|
||||||
description="{0}".format(
|
description="{0}".format(
|
||||||
action))
|
action))
|
||||||
elif isinstance(action, ChangeHypervisorState):
|
elif isinstance(action, hypervisor_state.ChangeHypervisorState):
|
||||||
primitive = self.create_action(action_plan_id=action_plan.id,
|
primitive = self.create_action(action_plan_id=action_plan.id,
|
||||||
action_type=Primitives.
|
action_type=Primitives.
|
||||||
HYPERVISOR_STATE.value,
|
HYPERVISOR_STATE.value,
|
||||||
@@ -120,21 +117,21 @@ class DefaultPlanner(Planner):
|
|||||||
value,
|
value,
|
||||||
description="{0}".format(
|
description="{0}".format(
|
||||||
action))
|
action))
|
||||||
elif isinstance(action, Nop):
|
elif isinstance(action, nop.Nop):
|
||||||
primitive = self.create_action(action_plan_id=action_plan.id,
|
primitive = self.create_action(action_plan_id=action_plan.id,
|
||||||
action_type=Primitives.
|
action_type=Primitives.
|
||||||
NOP.value,
|
NOP.value,
|
||||||
description="{0}".format(
|
description="{0}".format(
|
||||||
action))
|
action))
|
||||||
else:
|
else:
|
||||||
raise MetaActionNotFound()
|
raise exception.MetaActionNotFound()
|
||||||
priority = priority_primitives[primitive['action_type']]
|
priority = priority_primitives[primitive['action_type']]
|
||||||
to_schedule.append((priority, primitive))
|
to_schedule.append((priority, primitive))
|
||||||
|
|
||||||
# scheduling
|
# scheduling
|
||||||
scheduled = sorted(to_schedule, reverse=False, key=lambda x: (x[0]))
|
scheduled = sorted(to_schedule, reverse=False, key=lambda x: (x[0]))
|
||||||
if len(scheduled) == 0:
|
if len(scheduled) == 0:
|
||||||
LOG.warning("The ActionPlan is empty")
|
LOG.warning(_LW("The action plan is empty"))
|
||||||
action_plan.first_action_id = None
|
action_plan.first_action_id = None
|
||||||
action_plan.save()
|
action_plan.save()
|
||||||
else:
|
else:
|
||||||
@@ -157,7 +154,7 @@ class DefaultPlanner(Planner):
|
|||||||
'uuid': utils.generate_uuid(),
|
'uuid': utils.generate_uuid(),
|
||||||
'audit_id': audit_id,
|
'audit_id': audit_id,
|
||||||
'first_action_id': None,
|
'first_action_id': None,
|
||||||
'state': APStatus.RECOMMENDED
|
'state': objects.action_plan.Status.RECOMMENDED
|
||||||
}
|
}
|
||||||
|
|
||||||
new_action_plan = objects.ActionPlan(context, **action_plan_dict)
|
new_action_plan = objects.ActionPlan(context, **action_plan_dict)
|
||||||
|
|||||||
@@ -23,13 +23,10 @@ import oslo_messaging as om
|
|||||||
|
|
||||||
from watcher.common import exception
|
from watcher.common import exception
|
||||||
from watcher.common.messaging.messaging_core import MessagingCore
|
from watcher.common.messaging.messaging_core import MessagingCore
|
||||||
from watcher.common.messaging.notification_handler import NotificationHandler
|
|
||||||
from watcher.common import utils
|
from watcher.common import utils
|
||||||
from watcher.decision_engine.event.consumer_factory import EventConsumerFactory
|
|
||||||
from watcher.decision_engine.manager import decision_engine_opt_group
|
from watcher.decision_engine.manager import decision_engine_opt_group
|
||||||
from watcher.decision_engine.manager import WATCHER_DECISION_ENGINE_OPTS
|
from watcher.decision_engine.manager import WATCHER_DECISION_ENGINE_OPTS
|
||||||
|
|
||||||
from watcher.decision_engine.messaging.events import Events
|
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
@@ -47,17 +44,12 @@ class DecisionEngineAPI(MessagingCore):
|
|||||||
CONF.watcher_decision_engine.topic_status,
|
CONF.watcher_decision_engine.topic_status,
|
||||||
api_version=self.API_VERSION,
|
api_version=self.API_VERSION,
|
||||||
)
|
)
|
||||||
self.handler = NotificationHandler(self.publisher_id)
|
|
||||||
self.handler.register_observer(self)
|
|
||||||
self.add_event_listener(Events.ALL, self.event_receive)
|
|
||||||
self.topic_status.add_endpoint(self.handler)
|
|
||||||
|
|
||||||
transport = om.get_transport(CONF)
|
transport = om.get_transport(CONF)
|
||||||
target = om.Target(
|
target = om.Target(
|
||||||
topic=CONF.watcher_decision_engine.topic_control,
|
topic=CONF.watcher_decision_engine.topic_control,
|
||||||
version=self.API_VERSION,
|
version=self.API_VERSION,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.client = om.RPCClient(transport, target,
|
self.client = om.RPCClient(transport, target,
|
||||||
serializer=self.serializer)
|
serializer=self.serializer)
|
||||||
|
|
||||||
@@ -67,20 +59,3 @@ class DecisionEngineAPI(MessagingCore):
|
|||||||
|
|
||||||
return self.client.call(
|
return self.client.call(
|
||||||
context.to_dict(), 'trigger_audit', audit_uuid=audit_uuid)
|
context.to_dict(), 'trigger_audit', audit_uuid=audit_uuid)
|
||||||
|
|
||||||
# TODO(ebe): Producteur / consommateur implementer
|
|
||||||
def event_receive(self, event):
|
|
||||||
|
|
||||||
try:
|
|
||||||
request_id = event.get_request_id()
|
|
||||||
event_type = event.get_type()
|
|
||||||
data = event.get_data()
|
|
||||||
LOG.debug("request id => %s" % event.get_request_id())
|
|
||||||
LOG.debug("type_event => %s" % str(event.get_type()))
|
|
||||||
LOG.debug("data => %s" % str(data))
|
|
||||||
|
|
||||||
event_consumer = EventConsumerFactory.factory(event_type)
|
|
||||||
event_consumer.execute(request_id, self.context, data)
|
|
||||||
except Exception as e:
|
|
||||||
LOG.error("evt %s" % e.message)
|
|
||||||
raise e
|
|
||||||
|
|||||||
@@ -21,19 +21,19 @@ import six
|
|||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(abc.ABCMeta)
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
class Solution(object):
|
class BaseSolution(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._origin = None
|
self._origin = None
|
||||||
self._model = None
|
self._model = None
|
||||||
self._efficiency = 0
|
self._efficacy = 0
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def efficiency(self):
|
def efficacy(self):
|
||||||
return self._efficiency
|
return self._efficacy
|
||||||
|
|
||||||
@efficiency.setter
|
@efficacy.setter
|
||||||
def efficiency(self, e):
|
def efficacy(self, e):
|
||||||
self._efficiency = e
|
self._efficacy = e
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def model(self):
|
def model(self):
|
||||||
@@ -53,10 +53,8 @@ class Solution(object):
|
|||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def add_change_request(self, r):
|
def add_change_request(self, r):
|
||||||
raise NotImplementedError(
|
raise NotImplementedError()
|
||||||
"Should have implemented this") # pragma:no cover
|
|
||||||
|
|
||||||
@abc.abstractproperty
|
@abc.abstractproperty
|
||||||
def meta_actions(self):
|
def actions(self):
|
||||||
raise NotImplementedError(
|
raise NotImplementedError()
|
||||||
"Should have implemented this") # pragma:no cover
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user