Compare commits

...

78 Commits

Author SHA1 Message Date
junjie huang
8ebc898924 outlet Temperature based migration strategy
It implements one of the algorithm of Intel thermal POC.
It extends the BaseStrategy class, getting the Outlet
metrics of servers via Ceilometer, and generates solutions
when the outlet Temperature is greater than threshold.
current threshold is hard-coded, will make it configurable
in the next patches.

Implements: blueprint outlet-temperature-based-strategy

Change-Id: I248147329d34eddf408652205a077895be572010
Co-Authored-By: Zhenzan Zhou <zhenzan.zhou@intel.com>
2015-12-28 21:29:08 +00:00
Jenkins
660c782626 Merge "Move Audit-template management in DefaultStrategyContext" 2015-12-23 08:29:36 +00:00
Jean-Emile DARTOIS
dfaba80252 Move Audit-template management in DefaultStrategyContext
This aim of this patchset is to move the management of the
Audit-Template into StrategyContext
in order to prepare to pass parameters to strategies
but also to prepare to add more dynamic Actions management

Partially implements: blueprint glossary-related-refactoring

Change-Id: I13ee063da947113ce349855aa331a22f40567051
2015-12-22 17:52:51 +01:00
Darren Shaw
9dccb29bf4 Move glossary.rst to root folder of doc
Move glossary.rst from /doc/dev to /doc/ as glossary is not only
related to dev. Updated index.rst.

Change-Id: I2e5251bdb0b94ef03727dea26bc43866544d2fd3
Closes-Bug: #1527130
2015-12-21 13:14:06 -06:00
Jenkins
853145f4d1 Merge "Remove string concatenation in favor of string formatting" 2015-12-21 10:13:56 +00:00
Jenkins
764f5c7681 Merge "Add Creative Commons Attribution header to documentation" 2015-12-21 10:10:36 +00:00
Jenkins
b81b767567 Merge "Rename NovaWrapper to NovaClient" 2015-12-21 10:06:53 +00:00
Jenkins
764e31a7a1 Merge "Remove useless event factory" 2015-12-21 10:06:21 +00:00
Steve Wilkerson
1c49d07912 Remove string concatenation in favor of string formatting
Some of the modules still utilized string concatenation
instead of using formatting.  In order to align with other
modules in the project, I refactored these modules to use string
formatting instead.

Change-Id: I708392e1d03b6331a134419aa0ae9dc02a05c31b
Closes-Bug: 1522738
2015-12-21 11:04:00 +01:00
Jean-Emile DARTOIS
18549dc182 Remove useless event factory
Change-Id: I8442a26ebfcfe3c17c378b283ffbbc0810b7a067
2015-12-21 10:31:15 +01:00
Jenkins
9f222221a7 Merge "Change default strategy to DummyStrategy" 2015-12-21 09:29:29 +00:00
Jean-Emile DARTOIS
1a64383a68 Rename NovaWrapper to NovaClient
This patchset aim to rename the nova client class and move it with
the other openstack clients in the folder openstack/common.

Change-Id: Ie8aab199922985f42ad85e6688f0727b24f53ffd
2015-12-21 10:26:20 +01:00
Vincent Françoise
ac07f35dc7 i18n - Make string translatable
Since internationalization should be enabled in Watcher, this
patchset refactors the Watcher codebase to wrap previously
untranslatable strings with i18n translation functions so we can
import them for translation into the .pot template file.

Partially Implements: blueprint support-translation
Change-Id: I425967a60b5a7957f753894e5d2ba0d2c5009d1d
2015-12-21 10:08:59 +01:00
Jean-Emile DARTOIS
010bc61cc9 Change default strategy to DummyStrategy
The aim of this patchset is to change the default strategy
and set invoke_on_load to False.

Change-Id: I0e374993614f465b11a22e33008f7026642154ee
2015-12-21 09:55:53 +01:00
Jenkins
3dd02ee895 Merge "Code refactoring - StrategyContext and Auditendpoint" 2015-12-21 08:40:04 +00:00
Steve Wilkerson
a4bbe7f893 Add Creative Commons Attribution header to documentation
Changed the header in the documentation to reflect that the docs
are covered under the CCBY 3.0 license.

Change-Id: I29f3f1a2491c28b1a4ee12a7b97a7ab00c919867
Closes-Bug: 1526331
2015-12-20 01:51:00 -06:00
Jean-Emile DARTOIS
4c3073efb4 Code refactoring - StrategyContext and Auditendpoint
This patchset aim to remove useless code in StrategyContext
and AuditEndPoint.
This patchset also add a parameter for strategy context to define the
numbers of thread of execute the strategies.

DocImpact
Change-Id: I83e87165b03b42fe6b863921502a300bd94d2982
2015-12-18 14:25:07 +00:00
Jenkins
642226812f Merge "Remove *.pyc files before running tox tests" 2015-12-18 12:52:31 +00:00
Jenkins
8f2ca2518f Merge "'admin_user' opt (and others) imported twice" 2015-12-18 12:48:56 +00:00
Jenkins
1698eb31f3 Merge "Fix generation of watcher config file" 2015-12-17 18:34:26 +00:00
Jenkins
5fb74e677f Merge "Rename command to audit" 2015-12-17 13:21:39 +00:00
Gábor Antal
35a5ba1cd0 Remove *.pyc files before running tox tests
Modified tox.ini to remove any .pyc file before running tests.
Also, now it removes all the __pycache__ folders which was also
in the bug report.

Change-Id: I2f60751b155f8098b746339c12406b934bcdbcf9
Closes-Bug: #1525852
2015-12-17 11:59:54 +01:00
Jean-Emile DARTOIS
c0a85be7b8 Add missing parameter in prepare_service for api
Although the bug #1526888 (prepare_service() - duplicate function) got merged
https://review.openstack.org/#/c/258007/ we didn't see that the file in
(watcher/cmd/api.py) api didn't have sys.argv parameter.

Change-Id: I458b4ff169684131a20ef6ac090b1c918f08d427
Closes-Bug: 1526888
2015-12-16 17:58:18 +01:00
Jean-Emile DARTOIS
633b360652 Fix generation of watcher config file
Although the refactoring of the applier got merged we have an issue
to generate the config. This pathset fix that

Change-Id: Iafce7d0136b2ad813102c3e3caeebafa215363c8
Closes-Bug: 1526851
2015-12-16 16:09:12 +00:00
Jenkins
d3160bf007 Merge "Removed duplicated function prepare_service()" 2015-12-16 10:41:32 +00:00
Jean-Emile DARTOIS
69152f2449 Rename command to audit
This patchset is there to change the code structure.

Some Python class and packages need to be renamed
for a better compliance with the shared terminology
which provides a better understanding of Watcher
objects and components by every contributor.

This patchset is there to rename the folder command to audit

Partially implements: blueprint glossary-related-refactoring

Change-Id: I76616fb58d5e79a7dc209b80e882d216850d18a4
2015-12-16 10:23:44 +01:00
Jenkins
37cd75ffe3 Merge "Include terminology definition from docstring" 2015-12-16 09:08:25 +00:00
Gábor Antal
c7fd5e8b21 'admin_user' opt (and others) imported twice
"admin_user", "admin_tenant_name", "admin_password" and "auth_uri"
are options which are imported from the "keystone_authtoken" group,
itself being part of the "keystonemiddleware" 3rd-party library.

The problem is that these options are imported at 2 different
locations in the codebase:

- watcher/applier/manager.py
- watcher/common/keystone.py

Removed one from the applier.

Change-Id: Ie7075883018ec69f6a4e8190f50a9ea949ab9745
Closes-Bug: #1526259
2015-12-15 20:10:02 +01:00
Gábor Antal
f0b58f8c27 Removed duplicated function prepare_service()
The prepare_service() function is defined both in watcher/service.py
and in watcher/common/service.py.

These 2 needed to be merged into a single one
to avoid code duplication.

At the same time, the watcher/service.py only contains this function,
so I removed that file.

Change-Id: I0c935dfcd011bee9597315752dae8668221c53f9
Closes-Bug: #1525842
2015-12-15 19:14:52 +01:00
Jenkins
22dd6d42c3 Merge "Internationalization (i18n) - Enable French locale" 2015-12-15 17:49:50 +00:00
Vincent Françoise
bd29e2e79f Internationalization (i18n) - Enable French locale
Our project should now enable its internationalization.
This patchset add the french locale to the project but also
refactors the codebase to following the oslo_i18n recommendations.

DocImpact
Implements: blueprint support-translation

Change-Id: I0e4fbf05d16afb5e25bac78438c640f147c754b1
2015-12-15 17:57:10 +01:00
Vincent Françoise
c2eb112184 Include terminology definition from docstring
As of now, the glossary defined in our documentation reflects the
current state of the codebase. In order to avoid any discrepancy
between the codebase and each definition, the objective here is to
gather both in a single place and link it into the rst documentation
via a custom directive.
Also re-aligned the requirements with liberty for doc.

DocImpact

Change-Id: I9ca50f8d3c32b4690ee240e13dec0cb7eeedcc2c
2015-12-15 11:38:42 +01:00
Jean-Emile DARTOIS
57cecb27f5 Remove pragma no cover from code
Add exclude_lines in the report section of .coveragerc to ignore
abstract in test coverage

Change-Id: I7863a8ba7e20358fb7cdf3cc7e4d83871a5104ef
2015-12-15 10:14:40 +01:00
Jenkins
7c72d6f912 Merge "Some tests are ignored" 2015-12-15 07:57:58 +00:00
Jean-Emile DARTOIS
62570525ad Tidy up - Watcher Decision Engine package
Some Python class and packages need to be renamed
for a better compliance with the shared terminology
which provides a better understanding of Watcher
objects and components by every contributor.

This patchset is there to change the code structure by adding the folder
"strategies" and "loading".

Partially implements: blueprint glossary-related-refactoring

Change-Id: I56fb24ee6762b3186eccde5983233e17bb227cc1
2015-12-14 14:33:56 +01:00
Jenkins
92940ba9e2 Merge "Typo in ClusteStateNotDefined" 2015-12-14 10:56:51 +00:00
Jenkins
7c8ce453ed Merge "Add Apache license header to all rst documentation" 2015-12-14 10:43:37 +00:00
Gábor Antal
33ea5f96f8 Typo in ClusteStateNotDefined
ClusteStateNotDefined has a typo, it should be ClusterStateNotDefine

Change-Id: I727301786d47db847215d73722051e59d340f1c2
Closes-Bug: #1525818
2015-12-14 11:41:29 +01:00
Jenkins
916f4d0c08 Merge "Tidy up - Rename Base" 2015-12-14 10:27:47 +00:00
Jenkins
3fb5defc16 Merge "Removed H404, H405, H305 ignore in pep8" 2015-12-14 10:12:37 +00:00
Jean-Emile DARTOIS
b01f4bead4 Some tests are ignored
Testr is using the prefix _test for discover tests.

Change-Id: I963c25f1d331273dd0d28a374be894246413530b
2015-12-14 10:31:06 +01:00
Jenkins
8516d629c2 Merge "Added unit tests on nova wrapper" 2015-12-14 09:21:54 +00:00
Jean-Emile DARTOIS
35a1f0a657 Tidy up - Rename Base
Some Python class and packages need to be renamed
for a better compliance with the shared terminology
which provides a better understanding of Watcher
objects and components by every contributor.

This patchset add missing Base in class name

Partially implements: blueprint glossary-related-refactoring

Change-Id: I95a3e41fbd5fcd90a99d81c9cf278940f50c7732
2015-12-11 14:45:50 +01:00
Vincent Françoise
d934971458 Refactored Watcher codebase to add py34 support
Even though Watcher was mentioning python 3.4 as supported, it
really wasn't the case as all the unit tests were not passing in this
version of Python.

This patchset fixes all the failing tests in Python 3.4 while
keeping Watcher Python 2.7 compatible.

DocImpact
BugImpact
Change-Id: Ie74acc08ef0a2899349a4b419728c89e416a18cb
2015-12-11 13:24:02 +00:00
Vincent Françoise
d92f85574d Added unit tests on nova wrapper
As the coverage on the nova wrapper is actually very low, this
patchset is here to raise it a bit since it is a quite central
piece of code.

Change-Id: Ie7879c74c8d322d5031953827c339bb11d9085c1
2015-12-11 10:47:03 +01:00
Jenkins
b1fe7a5f3d Merge "Remove references to removed watcher/openstack directory" 2015-12-11 08:42:46 +00:00
Jenkins
8a5eb4b6a1 Merge "Remove unreachable code in basic_consolidation.py" 2015-12-11 08:29:57 +00:00
Jenkins
91c14e4eda Merge "Removed unnecessary code from basic_consolidation" 2015-12-11 08:07:28 +00:00
Gábor Antal
1613bd6904 Removed H404, H405, H305 ignore in pep8
In the file tox.ini we have some pep8 rules disabled.
We should remove H404,H405,H305 from the ignore list.

Removed them from the ignore list, and got some errors.
I restructured the comments, and now with H404, H405, H305 enabled,
pep8 works without any failures.

Change-Id: Ic2aeb2a8bd47e92fbd2bb0f43fd00d44b6c220ca
Closes-Bug: #1523841
2015-12-10 19:35:52 +01:00
Gábor Antal
ba4f5569d1 Removed unnecessary code from basic_consolidation
In basic_consolidation.py has a method, called calculate_score_vm().
This method used to get vm's id as a parameter, but it was replaced
and now it gets the vm as a parameter. So we don't need to get the
vm from our vm's id, as we already have the vm.

Change-Id: I96af7fbdbe85eda8d4fc44b4b162e8ba9d4967fa
2015-12-10 18:55:16 +01:00
Gábor Antal
a62553a6a5 Remove unreachable code in basic_consolidation.py
In basic_consolidation.py there is a method called calculate_score_vm()
which had two return statements following each other. The second one
(which equals the first one) is unreachable as the first one returns.

Change-Id: Ia4877c22188fae6217e07597a2dd939633414349
Closes-Bug: #1524911
2015-12-10 18:20:35 +01:00
Jean-Emile DARTOIS
d5ba40530f Rename Mapper to Mapping
Some Python class and packages need to be renamed
for a better compliance with the shared terminology
which provides a better understanding of Watcher
objects and components by every contributor.

This patchset is there to change mapper to mapping

Partially implements: blueprint glossary-related-refactoring

Change-Id: Ieaca42431322ce40d87de147ac0b46a1f446f390
2015-12-10 17:58:58 +01:00
Jean-Emile DARTOIS
f98e96da42 Tidy up - Primitive
Some Python class and packages need to be renamed
for a better compliance with the shared terminology
which provides a better understanding of Watcher
objects and components by every contributor.

This patchset is there to change
Primitive to Primitives.
Add BasePrimitive.

Partially implements: blueprint glossary-related-refactoring

Change-Id: I839bddd12b5320b338b2f207d74963afa23de522
2015-12-10 17:37:13 +01:00
Jenkins
64747cad1f Merge "Rename Command to Action" 2015-12-10 16:33:04 +00:00
Jenkins
5f87e82bac Merge "Removed py33, pypy support" 2015-12-10 16:31:43 +00:00
Jean-Emile DARTOIS
ff89e942ca Remove references to removed watcher/openstack directory
Since we removed openstack/common this is not useful.

Change-Id: Ifb8e1cdacd1879874be7496a8bc4bc8349085456
2015-12-10 16:13:43 +00:00
Gábor Antal
7faa501fb7 Removed py33, pypy support
As Openstack Liberty now targets Python 2.7 and 3.4,
we should drop Python 3.3 support from our setup.cfg

Change-Id: I8c8f9222fcf1111e99a943976aae9c5b427f7ee4
Closes-Bug: #1523983
2015-12-10 15:53:09 +01:00
Jenkins
ab8d242c1f Merge "Remove alembic revision of watcher db" 2015-12-10 08:35:04 +00:00
Jenkins
bbd26cafae Merge "Update the glossary to lay down Watcher terminology" 2015-12-10 08:35:01 +00:00
Jenkins
0042356245 Merge "Rename command to action_plan" 2015-12-10 08:31:21 +00:00
Jean-Emile DARTOIS
e1d4026c7c Remove alembic revision of watcher db
This code is not useful because we have only one version 
of watcher db currently and this alembic revision is out
of date compared to the initial schema as of mitaka-1.

Change-Id: Id21c665ff7a600a716e80d112e131a0e13687b41
2015-12-09 16:20:51 +00:00
Jenkins
df692a8215 Merge "Rename efficiency to efficacy" 2015-12-09 15:43:43 +00:00
Darren Shaw
f9323889d6 Add Apache license header to all rst documentation
This patch has the Apache license header found in
doc/source/dev/glossary.rst added to every .rst file

Change-Id: Icc20e7baf7d3cd0f116c371d54ef03c7c8401778
Closes-Bug: #1523986
2015-12-09 08:34:53 -06:00
Jenkins
0a44b2972e Merge "Rename Meta-Action to Action" 2015-12-09 10:59:14 +00:00
Jean-Emile DARTOIS
c5c16ac055 Rename Command to Action
Some Python class and packages need to be renamed
for a better compliance with the shared terminology
which provides a better understanding of Watcher
objects and components by every contributor.

This patchset is there to change Command to Action

Partially implements: blueprint glossary-related-refactoring

Change-Id: Id38e133fc8b789319c7db5712d3820ecca1bd51d
2015-12-09 09:35:30 +00:00
Jenkins
3016d3da11 Merge "Add a checker for the documentation" 2015-12-09 08:14:45 +00:00
Jean-Emile DARTOIS
daa560111c Update the glossary to lay down Watcher terminology
The Primitive used by the applier is not defined in the glossary.
This patchset start to fix this gap

Change-Id: Ie3fe153fc4c396da5c83425ccdd540910ad33e49
2015-12-09 09:10:14 +01:00
Jenkins
16705f68da Merge "Removed unused enum" 2015-12-08 23:14:03 +00:00
Jean-Emile DARTOIS
109a980c29 Rename command to action_plan
Some Python class and packages need to be renamed
for a better compliance with the shared terminology
which provides a better understanding of Watcher
objects and components by every contributor.

This patchset is there to change command to action_plan

Partially implements: blueprint glossary-related-refactoring

Change-Id: I19a70adeca347ce747a2221b5fc31658139c95a2
2015-12-08 15:26:44 +01:00
Vincent Françoise
531373cb84 Removed unused enum
This StrategyState Enum is not used anywhere in the codebase (and
shouldn't be), so I removed it.

Change-Id: I0b5d3102b4d08856dccd751313fdd097937d8ccf
2015-12-08 14:22:22 +01:00
Jean-Emile DARTOIS
087c4d49ed Rename Meta-Action to Action
Some Python class and packages need to be renamed
for a better compliance with the shared terminology
which provides a better understanding of Watcher
objects and components by every contributor.

This patchset is there to change Meta-Action to Action

Partially implements: blueprint glossary-related-refactoring

Change-Id: Ie67b800332179be93718030ddd7a36ddc76a544c
2015-12-08 11:53:29 +01:00
Jean-Emile DARTOIS
022b15dc1e Add a checker for the documentation
This patchset add some unit tests on the documentation:
Checking wrapping
Checking trailing spaces
Checking no_cr

Change-Id: I3fa56d3e7dd3218dcd398e6750bdd2fb3a8e75b4
2015-12-08 11:28:45 +01:00
Jean-Emile DARTOIS
454f70a19f Rename efficiency to efficacy
Some Python class and packages need to be renamed
for a better compliance with the shared Terminology
which provides a better understanding of Watcher
objects and components by every contributor.

This patchset is there to change efficiency to efficacy

Partially implements: blueprint glossary-related-refactoring

Change-Id: I4c84192d49a147e0fd406da35e2805143b902331
2015-12-08 09:18:48 +00:00
Steve Wilkerson
98f05a52a8 Fix Watcher Applier variables in CamelCase
PEP8 standards call for instance variable names to be written
in the same manner as methods, with underscores separating words.
This fix addresses an instance variable  in the DeployPhase class.

As the instance variable seems public, the Java-ish getter and
setter mentioned in the bug report have been removed as well.

Change-Id: I8835315c8cae64665d3ccb321c4066e37a22c467
Closes-Bug: 1522489
2015-12-07 10:40:57 -06:00
Jenkins
5ff9f28a83 Merge "Remove duplicate setup in Watcher API main()" 2015-12-07 10:35:55 +00:00
Darren Shaw
4a88220ffe Remove duplicate setup in Watcher API main()
main() duplicates code for oslo_log setup, which is first
called within prepare_services(). This patches removes
the duplication call in main()

Change-Id: I712d3733218c18b2eb02d4bf26e54b29ef72a988
Closes-Bug: #1522781
2015-12-04 20:39:42 -06:00
Jean-Emile DARTOIS
4c2d0e6345 Cleanup deprecated documentation
Since vincent mahe created a great
documentation available in doc/source
this one is deprecated

Change-Id: I81ff633771570f28a38e3d277718f0a97a9d5090
2015-12-04 18:21:47 +01:00
vmahe
7710b1670e Provide detailed information on architecture
Added new global architecture diagram using Dia tool
and a more detailed description of each component.
Exported this diagram in SVG format.
The source code of the diagram is stored in
/doc/images_src/ folder.
Moved also the architecture.rst file from the /dev
folder to the root folder of the Watcher doc.

Change-Id: I74379390178673dcb6a043967b31e38ca9560bb3
2015-12-04 17:33:05 +01:00
181 changed files with 3657 additions and 2079 deletions

View File

@@ -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

View File

@@ -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
========================== ==========================

View File

@@ -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
View 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

View File

@@ -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:

View File

@@ -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']

View File

@@ -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

View File

@@ -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
================== ==================

View File

@@ -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

View File

@@ -1,9 +0,0 @@
.. _architecture:
===================
System Architecture
===================
Please go to `Wiki Watcher Architecture <https://wiki.openstack.org/wiki/WatcherArchitecture>`_
.. _API service: ../webapi/v1.html

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

Binary file not shown.

View 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

View File

@@ -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

View File

@@ -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
======== ========

View File

@@ -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)
===================== =====================

View File

@@ -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]

View File

@@ -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]

View File

@@ -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
View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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):

View File

@@ -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)

View File

@@ -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

View File

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

View File

@@ -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

View File

@@ -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):

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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):

View File

@@ -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

View File

@@ -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()

View File

@@ -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()

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View 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)

View File

@@ -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:

View File

@@ -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):

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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():

View File

@@ -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():

View File

@@ -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,

View File

@@ -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")

View File

@@ -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)

View File

@@ -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

View File

@@ -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)

View File

@@ -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()

View File

@@ -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' "

View File

@@ -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)

View File

@@ -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',

View File

@@ -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
============================================================ ============================================================

View File

@@ -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)

View File

@@ -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])

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

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

View File

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

View File

@@ -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 ###

View File

@@ -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()

View File

@@ -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)

View File

@@ -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')

View File

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

View File

@@ -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 " "

View File

@@ -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)

View 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)

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View 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)

View File

@@ -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)))

View File

@@ -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()

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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))

View File

@@ -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

View File

@@ -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):

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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