Compare commits
203 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8635beb045 | ||
|
|
ff1af61c1b | ||
|
|
00adcb84f6 | ||
|
|
a68eb0d619 | ||
|
|
517f1d083e | ||
|
|
76a61e234a | ||
|
|
f4b631ebb3 | ||
|
|
e78610e7d1 | ||
|
|
405cde2ff9 | ||
|
|
b25ecde0db | ||
|
|
e6b4befbf4 | ||
|
|
dcf52077a4 | ||
|
|
1de00086f5 | ||
|
|
518b4c82f1 | ||
|
|
4423fdd231 | ||
|
|
b1c6ea71a4 | ||
|
|
533a4c7a34 | ||
|
|
d61c4c12d1 | ||
|
|
1d3891e1e4 | ||
|
|
f13d6dd805 | ||
|
|
81bdc51e21 | ||
|
|
e7bbf1d1b9 | ||
|
|
e34198f35c | ||
|
|
bc06a7d419 | ||
|
|
1a1d9d09f4 | ||
|
|
47ff9bb105 | ||
|
|
ca5b849f6b | ||
|
|
2272b515bf | ||
|
|
f6b1b515c4 | ||
|
|
64903ce56c | ||
|
|
86cbe4bd0c | ||
|
|
0b29a8394b | ||
|
|
ec64203bee | ||
|
|
dc01e35760 | ||
|
|
117079f81e | ||
|
|
d03e1d54bd | ||
|
|
6bdc4cac82 | ||
|
|
b4c5e2bb81 | ||
|
|
369428c313 | ||
|
|
2381a677b6 | ||
|
|
da8b24f483 | ||
|
|
d7a7b3ea54 | ||
|
|
37595e9bb7 | ||
|
|
cb693e4093 | ||
|
|
9918f59227 | ||
|
|
07c7ba9b2e | ||
|
|
73cf5a98ae | ||
|
|
80abcd6fd9 | ||
|
|
e3edc67045 | ||
|
|
b3c2d3af1f | ||
|
|
4fc3307525 | ||
|
|
77c07a466f | ||
|
|
80867703ba | ||
|
|
7b403c0d3b | ||
|
|
6f2c82316c | ||
|
|
b21193aaf2 | ||
|
|
e1a1cd79f4 | ||
|
|
019fc498ae | ||
|
|
407a505e73 | ||
|
|
488c6e3f4e | ||
|
|
28a2422ceb | ||
|
|
58b5baaeae | ||
|
|
e8fc4a1bc8 | ||
|
|
921e531adf | ||
|
|
b5cb6631cc | ||
|
|
45801cf9c5 | ||
|
|
442512cd71 | ||
|
|
2b95a4cbc4 | ||
|
|
84b12f8f1e | ||
|
|
f665d83657 | ||
|
|
5e16e032be | ||
|
|
eab47bf182 | ||
|
|
2544327979 | ||
|
|
2412df4b6c | ||
|
|
d811dd93ae | ||
|
|
cb342b45b7 | ||
|
|
1a4d5a3df3 | ||
|
|
8b8d2f01fe | ||
|
|
ca37358cac | ||
|
|
46091385d8 | ||
|
|
a457707e56 | ||
|
|
6c922b43ea | ||
|
|
5d66f66050 | ||
|
|
e7e50b3955 | ||
|
|
d9a2b8809b | ||
|
|
3d398b4d22 | ||
|
|
585fbeb9ee | ||
|
|
e9f237dc80 | ||
|
|
38f6700144 | ||
|
|
30bdf29002 | ||
|
|
e6f147d81d | ||
|
|
5aa6b16238 | ||
|
|
dcb5c1f9fc | ||
|
|
4ba01cbbcf | ||
|
|
d91d72d2c2 | ||
|
|
083bc2bed4 | ||
|
|
9d3671af37 | ||
|
|
3b88e37680 | ||
|
|
6eee64502f | ||
|
|
b9231f65cc | ||
|
|
b9b505a518 | ||
|
|
388ef9f11c | ||
|
|
4e3caaa157 | ||
|
|
8c6bf734af | ||
|
|
164a802718 | ||
|
|
277a749ca0 | ||
|
|
8401b5e479 | ||
|
|
78689fbe3b | ||
|
|
22abaa9c3a | ||
|
|
fb82131d85 | ||
|
|
f6f5079adb | ||
|
|
dbdf690cd0 | ||
|
|
f045f5d816 | ||
|
|
b77541deb2 | ||
|
|
3b9d72439c | ||
|
|
89aa2d54df | ||
|
|
86f4cee588 | ||
|
|
04ac509821 | ||
|
|
4ba9d2cb73 | ||
|
|
a71c9be860 | ||
|
|
c2cb1a1f8e | ||
|
|
79bdcf7baf | ||
|
|
de1b1a9938 | ||
|
|
031ebdecde | ||
|
|
daabe671c7 | ||
|
|
26bc3d139d | ||
|
|
4d2536b9b2 | ||
|
|
4388780e66 | ||
|
|
d03a9197b0 | ||
|
|
43f5ab18ba | ||
|
|
209176c3d7 | ||
|
|
1a21867735 | ||
|
|
5f6a97148f | ||
|
|
e6b23a0856 | ||
|
|
f9a1b9d3ce | ||
|
|
ff611544fb | ||
|
|
18e5c7d844 | ||
|
|
2966b93777 | ||
|
|
e67b532110 | ||
|
|
81765b9aa5 | ||
|
|
673642e436 | ||
|
|
1026a896e2 | ||
|
|
a3ac26870a | ||
|
|
192d8e262c | ||
|
|
3b5ef15db6 | ||
|
|
be9058f3e3 | ||
|
|
91951f3b01 | ||
|
|
57a2af2685 | ||
|
|
76e3d2e2f6 | ||
|
|
bd5a969a26 | ||
|
|
d61bf5f053 | ||
|
|
aaaf3f1c84 | ||
|
|
eb861f86ab | ||
|
|
a9e7251d0d | ||
|
|
4ff373197c | ||
|
|
87087e9add | ||
|
|
408d6d4650 | ||
|
|
e52dc4f8aa | ||
|
|
0f14b7635d | ||
|
|
bb77641aad | ||
|
|
77228a0b0a | ||
|
|
1157a8db30 | ||
|
|
18354d1b4e | ||
|
|
8387cd10de | ||
|
|
0449bae747 | ||
|
|
3e07844844 | ||
|
|
a52d92be87 | ||
|
|
96683a6133 | ||
|
|
46d5094add | ||
|
|
783c7c0177 | ||
|
|
6d0717199c | ||
|
|
8b77e78f3d | ||
|
|
22c9c4df87 | ||
|
|
99ff6d3348 | ||
|
|
c67f83cce0 | ||
|
|
397bb3497e | ||
|
|
4c924fc505 | ||
|
|
4c5ecc808d | ||
|
|
64b5a7c3e4 | ||
|
|
40bb92f749 | ||
|
|
92bd06cf94 | ||
|
|
c9e0dfd3f5 | ||
|
|
446fe1307a | ||
|
|
2836f460e3 | ||
|
|
cb9bb7301b | ||
|
|
cb644fcef9 | ||
|
|
0a7c87eebf | ||
|
|
d7f4f42772 | ||
|
|
bdc0eb196a | ||
|
|
59427eb0d9 | ||
|
|
b6801b192a | ||
|
|
0a6c2c16a4 | ||
|
|
9a44941c66 | ||
|
|
a6508a0013 | ||
|
|
c3db66ca09 | ||
|
|
5d0fe553c4 | ||
|
|
8b8239c3d8 | ||
|
|
920bd502ec | ||
|
|
c68d33f341 | ||
|
|
8e8fdbd809 | ||
|
|
681536c8c7 | ||
|
|
083b170083 | ||
|
|
c440cdd69f |
@@ -4,6 +4,7 @@ source = watcher
|
|||||||
omit = watcher/tests/*
|
omit = watcher/tests/*
|
||||||
|
|
||||||
[report]
|
[report]
|
||||||
ignore_errors = True
|
ignore_errors = True
|
||||||
exclude_lines =
|
exclude_lines =
|
||||||
@abstract
|
@abc.abstract
|
||||||
|
raise NotImplementedError
|
||||||
|
|||||||
9
.gitignore
vendored
@@ -44,6 +44,8 @@ output/*/index.html
|
|||||||
# Sphinx
|
# Sphinx
|
||||||
doc/build
|
doc/build
|
||||||
doc/source/api
|
doc/source/api
|
||||||
|
doc/source/samples
|
||||||
|
doc/source/watcher.conf.sample
|
||||||
|
|
||||||
# pbr generates these
|
# pbr generates these
|
||||||
AUTHORS
|
AUTHORS
|
||||||
@@ -62,3 +64,10 @@ sftp-config.json
|
|||||||
|
|
||||||
cover
|
cover
|
||||||
/demo/
|
/demo/
|
||||||
|
|
||||||
|
|
||||||
|
# Files created by releasenotes build
|
||||||
|
releasenotes/build
|
||||||
|
|
||||||
|
# Desktop Service Store
|
||||||
|
*.DS_Store
|
||||||
|
|||||||
@@ -10,15 +10,15 @@ Watcher
|
|||||||
|
|
||||||
OpenStack Watcher provides a flexible and scalable resource optimization
|
OpenStack Watcher provides a flexible and scalable resource optimization
|
||||||
service for multi-tenant OpenStack-based clouds.
|
service for multi-tenant OpenStack-based clouds.
|
||||||
Watcher provides a complete optimization loop—including everything from a
|
Watcher provides a complete optimization loop-including everything from a
|
||||||
metrics receiver, complex event processor and profiler, optimization processor
|
metrics receiver, complex event processor and profiler, optimization processor
|
||||||
and an action plan applier. This provides a robust framework to realize a wide
|
and an action plan applier. This provides a robust framework to realize a wide
|
||||||
range of cloud optimization goals, including the reduction of data center
|
range of cloud optimization goals, including the reduction of data center
|
||||||
operating costs, increased system performance via intelligent virtual machine
|
operating costs, increased system performance via intelligent virtual machine
|
||||||
migration, increased energy efficiency—and more!
|
migration, increased energy efficiency-and more!
|
||||||
|
|
||||||
* Free software: Apache license
|
* Free software: Apache license
|
||||||
* Wiki: http://wiki.openstack.org/wiki/Watcher
|
* Wiki: http://wiki.openstack.org/wiki/Watcher
|
||||||
* Source: https://github.com/openstack/watcher
|
* Source: https://github.com/openstack/watcher
|
||||||
* Bugs: http://bugs.launchpad.net/watcher
|
* Bugs: http://bugs.launchpad.net/watcher
|
||||||
* Documentation: https://factory.b-com.com/www/watcher/doc/watcher/index.html
|
* Documentation: http://docs.openstack.org/developer/watcher/
|
||||||
|
|||||||
@@ -27,6 +27,9 @@ ENABLED_SERVICES+=,q-svc,q-dhcp,q-meta,q-agt,q-l3,neutron
|
|||||||
# Enable remote console access
|
# Enable remote console access
|
||||||
enable_service n-cauth
|
enable_service n-cauth
|
||||||
|
|
||||||
|
# Enable the Watcher Dashboard plugin
|
||||||
|
enable_plugin watcher-dashboard git://git.openstack.org/openstack/watcher-dashboard
|
||||||
|
|
||||||
# Enable the Watcher plugin
|
# Enable the Watcher plugin
|
||||||
enable_plugin watcher git://git.openstack.org/openstack/watcher
|
enable_plugin watcher git://git.openstack.org/openstack/watcher
|
||||||
|
|
||||||
@@ -42,7 +45,3 @@ LOGDAYS=2
|
|||||||
[[post-config|$NOVA_CONF]]
|
[[post-config|$NOVA_CONF]]
|
||||||
[DEFAULT]
|
[DEFAULT]
|
||||||
compute_monitors=cpu.virt_driver
|
compute_monitors=cpu.virt_driver
|
||||||
|
|
||||||
[[post-config|$WATCHER_CONF]]
|
|
||||||
[watcher_goals]
|
|
||||||
goals=BASIC_CONSOLIDATION:basic,DUMMY:dummy
|
|
||||||
|
|||||||
@@ -150,11 +150,14 @@ This database stores all the Watcher domain objects which can be requested
|
|||||||
by the :ref:`Watcher API <archi_watcher_api_definition>` or the
|
by the :ref:`Watcher API <archi_watcher_api_definition>` or the
|
||||||
:ref:`Watcher CLI <archi_watcher_cli_definition>`:
|
:ref:`Watcher CLI <archi_watcher_cli_definition>`:
|
||||||
|
|
||||||
|
- :ref:`Goals <goal_definition>`
|
||||||
|
- :ref:`Strategies <strategy_definition>`
|
||||||
- :ref:`Audit templates <audit_template_definition>`
|
- :ref:`Audit templates <audit_template_definition>`
|
||||||
- :ref:`Audits <audit_definition>`
|
- :ref:`Audits <audit_definition>`
|
||||||
- :ref:`Action plans <action_plan_definition>`
|
- :ref:`Action plans <action_plan_definition>`
|
||||||
|
- :ref:`Efficacy indicators <efficacy_indicator_definition>` via the Action
|
||||||
|
Plan API.
|
||||||
- :ref:`Actions <action_definition>`
|
- :ref:`Actions <action_definition>`
|
||||||
- :ref:`Goals <goal_definition>`
|
|
||||||
|
|
||||||
The Watcher domain being here "*optimization of some resources provided by an
|
The Watcher domain being here "*optimization of some resources provided by an
|
||||||
OpenStack system*".
|
OpenStack system*".
|
||||||
@@ -196,8 +199,6 @@ Audit, the :ref:`Strategy <strategy_definition>` relies on two sets of data:
|
|||||||
which provides information about the past of the
|
which provides information about the past of the
|
||||||
:ref:`Cluster <cluster_definition>`
|
: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.
|
|
||||||
|
|
||||||
.. _data_model:
|
.. _data_model:
|
||||||
|
|
||||||
@@ -211,6 +212,14 @@ view (Goals, Audits, Action Plans, ...):
|
|||||||
.. image:: ./images/functional_data_model.svg
|
.. image:: ./images/functional_data_model.svg
|
||||||
:width: 100%
|
:width: 100%
|
||||||
|
|
||||||
|
Here below is a diagram representing the main objects in Watcher from a
|
||||||
|
database perspective:
|
||||||
|
|
||||||
|
.. image:: ./images/watcher_db_schema_diagram.png
|
||||||
|
:width: 100%
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. _sequence_diagrams:
|
.. _sequence_diagrams:
|
||||||
|
|
||||||
Sequence diagrams
|
Sequence diagrams
|
||||||
@@ -230,13 +239,15 @@ following parameters:
|
|||||||
|
|
||||||
- A name
|
- A name
|
||||||
- A goal to achieve
|
- A goal to achieve
|
||||||
|
- An optional strategy
|
||||||
|
|
||||||
.. image:: ./images/sequence_create_audit_template.png
|
.. image:: ./images/sequence_create_audit_template.png
|
||||||
:width: 100%
|
:width: 100%
|
||||||
|
|
||||||
The `Watcher API`_ just makes sure that the goal exists (i.e. it is declared
|
The `Watcher API`_ makes sure that both the specified goal (mandatory) and
|
||||||
in the Watcher configuration file) and stores a new audit template in the
|
its associated strategy (optional) are registered inside the :ref:`Watcher
|
||||||
:ref:`Watcher Database <watcher_database_definition>`.
|
Database <watcher_database_definition>` before storing a new audit template in
|
||||||
|
the :ref:`Watcher Database <watcher_database_definition>`.
|
||||||
|
|
||||||
.. _sequence_diagrams_create_and_launch_audit:
|
.. _sequence_diagrams_create_and_launch_audit:
|
||||||
|
|
||||||
@@ -250,6 +261,13 @@ previously created :ref:`Audit template <audit_template_definition>`:
|
|||||||
.. image:: ./images/sequence_create_and_launch_audit.png
|
.. image:: ./images/sequence_create_and_launch_audit.png
|
||||||
:width: 100%
|
:width: 100%
|
||||||
|
|
||||||
|
The :ref:`Administrator <administrator_definition>` also can specify type of
|
||||||
|
Audit and interval (in case of CONTINUOUS type). There is two types of Audit:
|
||||||
|
ONESHOT and CONTINUOUS. Oneshot Audit is launched once and if it succeeded
|
||||||
|
executed new action plan list will be provided. Continuous Audit creates
|
||||||
|
action plans with specified interval (in seconds); if action plan
|
||||||
|
has been created, all previous action plans get CANCELLED state.
|
||||||
|
|
||||||
A message is sent on the :ref:`AMQP bus <amqp_bus_definition>` which triggers
|
A message is sent on the :ref:`AMQP bus <amqp_bus_definition>` which triggers
|
||||||
the Audit in the
|
the Audit in the
|
||||||
:ref:`Watcher Decision Engine <watcher_decision_engine_definition>`:
|
:ref:`Watcher Decision Engine <watcher_decision_engine_definition>`:
|
||||||
@@ -260,12 +278,11 @@ the Audit in the
|
|||||||
The :ref:`Watcher Decision Engine <watcher_decision_engine_definition>` reads
|
The :ref:`Watcher Decision Engine <watcher_decision_engine_definition>` reads
|
||||||
the Audit parameters from the
|
the Audit parameters from the
|
||||||
:ref:`Watcher Database <watcher_database_definition>`. It instantiates the
|
:ref:`Watcher Database <watcher_database_definition>`. It instantiates the
|
||||||
appropriate :ref:`Strategy <strategy_definition>` (using entry points)
|
appropriate :ref:`strategy <strategy_definition>` (using entry points)
|
||||||
associated to the :ref:`Goal <goal_definition>` of the
|
given both the :ref:`goal <goal_definition>` and the strategy associated to the
|
||||||
:ref:`Audit <audit_definition>` (it uses the information of the Watcher
|
parent :ref:`audit template <audit_template_definition>` of the :ref:`Audit
|
||||||
configuration file to find the mapping between the
|
<audit_definition>`. If no strategy is associated to the audit template, the
|
||||||
:ref:`Goal <goal_definition>` and the :ref:`Strategy <strategy_definition>`
|
strategy is dynamically selected by the Decision Engine.
|
||||||
python class).
|
|
||||||
|
|
||||||
The :ref:`Watcher Decision Engine <watcher_decision_engine_definition>` also
|
The :ref:`Watcher Decision Engine <watcher_decision_engine_definition>` also
|
||||||
builds the :ref:`Cluster Data Model <cluster_data_model_definition>`. This
|
builds the :ref:`Cluster Data Model <cluster_data_model_definition>`. This
|
||||||
@@ -289,9 +306,11 @@ This method finds an appropriate scheduling of
|
|||||||
:ref:`Actions <action_definition>` taking into account some scheduling rules
|
:ref:`Actions <action_definition>` taking into account some scheduling rules
|
||||||
(such as priorities between actions).
|
(such as priorities between actions).
|
||||||
It generates a new :ref:`Action Plan <action_plan_definition>` with status
|
It generates a new :ref:`Action Plan <action_plan_definition>` with status
|
||||||
**RECOMMENDED** and saves it into the
|
**RECOMMENDED** and saves it into the:ref:`Watcher Database
|
||||||
:ref:`Watcher Database <watcher_database_definition>`. The saved action plan is
|
<watcher_database_definition>`. The saved action plan is now a scheduled flow
|
||||||
now a scheduled flow of actions.
|
of actions to which a global efficacy is associated alongside a number of
|
||||||
|
:ref:`Efficacy Indicators <efficacy_indicator_definition>` as specified by the
|
||||||
|
related :ref:`goal <goal_definition>`.
|
||||||
|
|
||||||
If every step executed successfully, the
|
If every step executed successfully, the
|
||||||
:ref:`Watcher Decision Engine <watcher_decision_engine_definition>` updates
|
:ref:`Watcher Decision Engine <watcher_decision_engine_definition>` updates
|
||||||
@@ -300,6 +319,11 @@ the current status of the Audit to **SUCCEEDED** in the
|
|||||||
on the bus to inform other components that the :ref:`Audit <audit_definition>`
|
on the bus to inform other components that the :ref:`Audit <audit_definition>`
|
||||||
was successful.
|
was successful.
|
||||||
|
|
||||||
|
This internal workflow the Decision Engine follows to conduct an audit can be
|
||||||
|
seen in the sequence diagram here below:
|
||||||
|
|
||||||
|
.. image:: ./images/sequence_from_audit_execution_to_actionplan_creation.png
|
||||||
|
:width: 100%
|
||||||
|
|
||||||
.. _sequence_diagrams_launch_action_plan:
|
.. _sequence_diagrams_launch_action_plan:
|
||||||
|
|
||||||
@@ -361,6 +385,28 @@ State Machine diagrams
|
|||||||
Audit State Machine
|
Audit State Machine
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
|
An :ref:`Audit <audit_definition>` has a life-cycle and its current state may
|
||||||
|
be one of the following:
|
||||||
|
|
||||||
|
- **PENDING** : a request for an :ref:`Audit <audit_definition>` has been
|
||||||
|
submitted (either manually by the
|
||||||
|
:ref:`Administrator <administrator_definition>` or automatically via some
|
||||||
|
event handling mechanism) and is in the queue for being processed by the
|
||||||
|
:ref:`Watcher Decision Engine <watcher_decision_engine_definition>`
|
||||||
|
- **ONGOING** : the :ref:`Audit <audit_definition>` is currently being
|
||||||
|
processed by the
|
||||||
|
:ref:`Watcher Decision Engine <watcher_decision_engine_definition>`
|
||||||
|
- **SUCCEEDED** : the :ref:`Audit <audit_definition>` has been executed
|
||||||
|
successfully and at least one solution was found
|
||||||
|
- **FAILED** : an error occured while executing the
|
||||||
|
:ref:`Audit <audit_definition>`
|
||||||
|
- **DELETED** : the :ref:`Audit <audit_definition>` is still stored in the
|
||||||
|
:ref:`Watcher database <watcher_database_definition>` but is not returned
|
||||||
|
any more through the Watcher APIs.
|
||||||
|
- **CANCELLED** : the :ref:`Audit <audit_definition>` was in **PENDING** or
|
||||||
|
**ONGOING** state and was cancelled by the
|
||||||
|
:ref:`Administrator <administrator_definition>`
|
||||||
|
|
||||||
The following diagram shows the different possible states of an
|
The following diagram shows the different possible states of an
|
||||||
:ref:`Audit <audit_definition>` and what event makes the state change to a new
|
:ref:`Audit <audit_definition>` and what event makes the state change to a new
|
||||||
value:
|
value:
|
||||||
@@ -373,6 +419,31 @@ value:
|
|||||||
Action Plan State Machine
|
Action Plan State Machine
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
|
An :ref:`Action Plan <action_plan_definition>` has a life-cycle and its current
|
||||||
|
state may be one of the following:
|
||||||
|
|
||||||
|
- **RECOMMENDED** : the :ref:`Action Plan <action_plan_definition>` is waiting
|
||||||
|
for a validation from the :ref:`Administrator <administrator_definition>`
|
||||||
|
- **PENDING** : a request for an :ref:`Action Plan <action_plan_definition>`
|
||||||
|
has been submitted (due to an
|
||||||
|
:ref:`Administrator <administrator_definition>` executing an
|
||||||
|
:ref:`Audit <audit_definition>`) and is in the queue for
|
||||||
|
being processed by the :ref:`Watcher Applier <watcher_applier_definition>`
|
||||||
|
- **ONGOING** : the :ref:`Action Plan <action_plan_definition>` is currently
|
||||||
|
being processed by the :ref:`Watcher Applier <watcher_applier_definition>`
|
||||||
|
- **SUCCEEDED** : the :ref:`Action Plan <action_plan_definition>` has been
|
||||||
|
executed successfully (i.e. all :ref:`Actions <action_definition>` that it
|
||||||
|
contains have been executed successfully)
|
||||||
|
- **FAILED** : an error occured while executing the
|
||||||
|
:ref:`Action Plan <action_plan_definition>`
|
||||||
|
- **DELETED** : the :ref:`Action Plan <action_plan_definition>` is still
|
||||||
|
stored in the :ref:`Watcher database <watcher_database_definition>` but is
|
||||||
|
not returned any more through the Watcher APIs.
|
||||||
|
- **CANCELLED** : the :ref:`Action Plan <action_plan_definition>` was in
|
||||||
|
**PENDING** or **ONGOING** state and was cancelled by the
|
||||||
|
:ref:`Administrator <administrator_definition>`
|
||||||
|
|
||||||
|
|
||||||
The following diagram shows the different possible states of an
|
The following diagram shows the different possible states of an
|
||||||
:ref:`Action Plan <action_plan_definition>` and what event makes the state
|
:ref:`Action Plan <action_plan_definition>` and what event makes the state
|
||||||
change to a new value:
|
change to a new value:
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ from watcher import version as watcher_version
|
|||||||
# Add any Sphinx extension module names here, as strings. They can be
|
# Add any Sphinx extension module names here, as strings. They can be
|
||||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||||
extensions = [
|
extensions = [
|
||||||
|
'oslo_config.sphinxconfiggen',
|
||||||
'sphinx.ext.autodoc',
|
'sphinx.ext.autodoc',
|
||||||
'sphinx.ext.viewcode',
|
'sphinx.ext.viewcode',
|
||||||
'sphinxcontrib.httpdomain',
|
'sphinxcontrib.httpdomain',
|
||||||
@@ -28,7 +29,8 @@ extensions = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
wsme_protocols = ['restjson']
|
wsme_protocols = ['restjson']
|
||||||
|
config_generator_config_file = '../../etc/watcher/watcher-config-generator.conf'
|
||||||
|
sample_config_basename = 'watcher'
|
||||||
|
|
||||||
# autodoc generation is a bit aggressive and a nuisance when doing heavy
|
# autodoc generation is a bit aggressive and a nuisance when doing heavy
|
||||||
# text edit cycles.
|
# text edit cycles.
|
||||||
|
|||||||
1
doc/source/config-generator.conf
Normal file
@@ -0,0 +1 @@
|
|||||||
|
../../etc/watcher/watcher-config-generator.conf
|
||||||
14
doc/source/deploy/conf-files.rst
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
.. _watcher_sample_configuration_files:
|
||||||
|
|
||||||
|
==================================
|
||||||
|
Watcher sample configuration files
|
||||||
|
==================================
|
||||||
|
|
||||||
|
watcher.conf
|
||||||
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The ``watcher.conf`` file contains most of the options to configure the
|
||||||
|
Watcher services.
|
||||||
|
|
||||||
|
.. literalinclude:: ../watcher.conf.sample
|
||||||
|
:language: ini
|
||||||
@@ -163,6 +163,16 @@ Configure the Watcher service
|
|||||||
The Watcher service is configured via its configuration file. This file
|
The Watcher service is configured via its configuration file. This file
|
||||||
is typically located at ``/etc/watcher/watcher.conf``.
|
is typically located at ``/etc/watcher/watcher.conf``.
|
||||||
|
|
||||||
|
You can easily generate and update a sample configuration file
|
||||||
|
named :ref:`watcher.conf.sample <watcher_sample_configuration_files>` by using
|
||||||
|
these following commands::
|
||||||
|
|
||||||
|
$ git clone git://git.openstack.org/openstack/watcher
|
||||||
|
$ cd watcher/
|
||||||
|
$ tox -econfig
|
||||||
|
$ vi etc/watcher/watcher.conf.sample
|
||||||
|
|
||||||
|
|
||||||
The configuration file is organized into the following sections:
|
The configuration file is organized into the following sections:
|
||||||
|
|
||||||
* ``[DEFAULT]`` - General configuration
|
* ``[DEFAULT]`` - General configuration
|
||||||
@@ -172,8 +182,6 @@ The configuration file is organized into the following sections:
|
|||||||
* ``[watcher_clients_auth]`` - Keystone auth configuration for clients
|
* ``[watcher_clients_auth]`` - Keystone auth configuration for clients
|
||||||
* ``[watcher_applier]`` - Watcher Applier module configuration
|
* ``[watcher_applier]`` - Watcher Applier module configuration
|
||||||
* ``[watcher_decision_engine]`` - Watcher Decision Engine module configuration
|
* ``[watcher_decision_engine]`` - Watcher Decision Engine module configuration
|
||||||
* ``[watcher_goals]`` - Goals mapping configuration
|
|
||||||
* ``[watcher_strategies]`` - Strategy configuration
|
|
||||||
* ``[oslo_messaging_rabbit]`` - Oslo Messaging RabbitMQ driver configuration
|
* ``[oslo_messaging_rabbit]`` - Oslo Messaging RabbitMQ driver configuration
|
||||||
* ``[ceilometer_client]`` - Ceilometer client configuration
|
* ``[ceilometer_client]`` - Ceilometer client configuration
|
||||||
* ``[cinder_client]`` - Cinder client configuration
|
* ``[cinder_client]`` - Cinder client configuration
|
||||||
|
|||||||
52
doc/source/deploy/gmr.rst
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
..
|
||||||
|
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_gmr:
|
||||||
|
|
||||||
|
=======================
|
||||||
|
Guru Meditation Reports
|
||||||
|
=======================
|
||||||
|
|
||||||
|
Watcher contains a mechanism whereby developers and system administrators can
|
||||||
|
generate a report about the state of a running Watcher service. This report
|
||||||
|
is called a *Guru Meditation Report* (*GMR* for short).
|
||||||
|
|
||||||
|
Generating a GMR
|
||||||
|
================
|
||||||
|
|
||||||
|
A *GMR* can be generated by sending the *USR2* signal to any Watcher process
|
||||||
|
with support (see below). The *GMR* will then be outputted as standard error
|
||||||
|
for that particular process.
|
||||||
|
|
||||||
|
For example, suppose that ``watcher-api`` has process id ``8675``, and was run
|
||||||
|
with ``2>/var/log/watcher/watcher-api-err.log``. Then, ``kill -USR2 8675``
|
||||||
|
will trigger the Guru Meditation report to be printed to
|
||||||
|
``/var/log/watcher/watcher-api-err.log``.
|
||||||
|
|
||||||
|
Structure of a GMR
|
||||||
|
==================
|
||||||
|
|
||||||
|
The *GMR* is designed to be extensible; any particular service may add its
|
||||||
|
own sections. However, the base *GMR* consists of several sections:
|
||||||
|
|
||||||
|
Package
|
||||||
|
Shows information about the package to which this process belongs, including
|
||||||
|
version informations.
|
||||||
|
|
||||||
|
Threads
|
||||||
|
Shows stack traces and thread ids for each of the threads within this
|
||||||
|
process.
|
||||||
|
|
||||||
|
Green Threads
|
||||||
|
Shows stack traces for each of the green threads within this process (green
|
||||||
|
threads don't have thread ids).
|
||||||
|
|
||||||
|
Configuration
|
||||||
|
Lists all the configuration options currently accessible via the CONF object
|
||||||
|
for the current process.
|
||||||
|
|
||||||
|
Plugins
|
||||||
|
Lists all the plugins currently accessible by the Watcher service.
|
||||||
@@ -90,7 +90,7 @@ should be able to run the Watcher services by issuing these commands:
|
|||||||
By default, this will show logging on the console from which it was started.
|
By default, this will show logging on the console from which it was started.
|
||||||
Once started, you can use the `Watcher Client`_ to play with Watcher service.
|
Once started, you can use the `Watcher Client`_ to play with Watcher service.
|
||||||
|
|
||||||
.. _`Watcher Client`: https://git.openstack.org/openstack/python-watcherclient.git
|
.. _`Watcher Client`: https://git.openstack.org/cgit/openstack/python-watcherclient
|
||||||
|
|
||||||
Installing from packages: PyPI
|
Installing from packages: PyPI
|
||||||
--------------------------------
|
--------------------------------
|
||||||
|
|||||||
142
doc/source/deploy/policy.rst
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
..
|
||||||
|
Copyright 2016 OpenStack Foundation
|
||||||
|
All Rights Reserved.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Policies
|
||||||
|
========
|
||||||
|
|
||||||
|
Watcher's public API calls may be restricted to certain sets of users using a
|
||||||
|
policy configuration file. This document explains exactly how policies are
|
||||||
|
configured and what they apply to.
|
||||||
|
|
||||||
|
A policy is composed of a set of rules that are used in determining if a
|
||||||
|
particular action may be performed by the authorized tenant.
|
||||||
|
|
||||||
|
Constructing a Policy Configuration File
|
||||||
|
----------------------------------------
|
||||||
|
|
||||||
|
A policy configuration file is a simply JSON object that contain sets of
|
||||||
|
rules. Each top-level key is the name of a rule. Each rule
|
||||||
|
is a string that describes an action that may be performed in the Watcher API.
|
||||||
|
|
||||||
|
The actions that may have a rule enforced on them are:
|
||||||
|
|
||||||
|
* ``strategy:get_all``, ``strategy:detail`` - List available strategies
|
||||||
|
|
||||||
|
* ``GET /v1/strategies``
|
||||||
|
* ``GET /v1/strategies/detail``
|
||||||
|
|
||||||
|
* ``strategy:get`` - Retrieve a specific strategy entity
|
||||||
|
|
||||||
|
* ``GET /v1/strategies/<STRATEGY_UUID>``
|
||||||
|
* ``GET /v1/strategies/<STRATEGY_NAME>``
|
||||||
|
|
||||||
|
|
||||||
|
* ``goal:get_all``, ``goal:detail`` - List available goals
|
||||||
|
|
||||||
|
* ``GET /v1/goals``
|
||||||
|
* ``GET /v1/goals/detail``
|
||||||
|
|
||||||
|
* ``goal:get`` - Retrieve a specific goal entity
|
||||||
|
|
||||||
|
* ``GET /v1/goals/<GOAL_UUID>``
|
||||||
|
* ``GET /v1/goals/<GOAL_NAME>``
|
||||||
|
|
||||||
|
|
||||||
|
* ``audit_template:get_all``, ``audit_template:detail`` - List available
|
||||||
|
audit_templates
|
||||||
|
|
||||||
|
* ``GET /v1/audit_templates``
|
||||||
|
* ``GET /v1/audit_templates/detail``
|
||||||
|
|
||||||
|
* ``audit_template:get`` - Retrieve a specific audit template entity
|
||||||
|
|
||||||
|
* ``GET /v1/audit_templates/<AUDIT_TEMPLATE_UUID>``
|
||||||
|
* ``GET /v1/audit_templates/<AUDIT_TEMPLATE_NAME>``
|
||||||
|
|
||||||
|
* ``audit_template:create`` - Create an audit template entity
|
||||||
|
|
||||||
|
* ``POST /v1/audit_templates``
|
||||||
|
|
||||||
|
* ``audit_template:delete`` - Delete an audit template entity
|
||||||
|
|
||||||
|
* ``DELETE /v1/audit_templates/<AUDIT_TEMPLATE_UUID>``
|
||||||
|
* ``DELETE /v1/audit_templates/<AUDIT_TEMPLATE_NAME>``
|
||||||
|
|
||||||
|
* ``audit_template:update`` - Update an audit template entity
|
||||||
|
|
||||||
|
* ``PATCH /v1/audit_templates/<AUDIT_TEMPLATE_UUID>``
|
||||||
|
* ``PATCH /v1/audit_templates/<AUDIT_TEMPLATE_NAME>``
|
||||||
|
|
||||||
|
|
||||||
|
* ``audit:get_all``, ``audit:detail`` - List available audits
|
||||||
|
|
||||||
|
* ``GET /v1/audits``
|
||||||
|
* ``GET /v1/audits/detail``
|
||||||
|
|
||||||
|
* ``audit:get`` - Retrieve a specific audit entity
|
||||||
|
|
||||||
|
* ``GET /v1/audits/<AUDIT_UUID>``
|
||||||
|
|
||||||
|
* ``audit:create`` - Create an audit entity
|
||||||
|
|
||||||
|
* ``POST /v1/audits``
|
||||||
|
|
||||||
|
* ``audit:delete`` - Delete an audit entity
|
||||||
|
|
||||||
|
* ``DELETE /v1/audits/<AUDIT_UUID>``
|
||||||
|
|
||||||
|
* ``audit:update`` - Update an audit entity
|
||||||
|
|
||||||
|
* ``PATCH /v1/audits/<AUDIT_UUID>``
|
||||||
|
|
||||||
|
|
||||||
|
* ``action_plan:get_all``, ``action_plan:detail`` - List available action plans
|
||||||
|
|
||||||
|
* ``GET /v1/action_plans``
|
||||||
|
* ``GET /v1/action_plans/detail``
|
||||||
|
|
||||||
|
* ``action_plan:get`` - Retrieve a specific action plan entity
|
||||||
|
|
||||||
|
* ``GET /v1/action_plans/<ACTION_PLAN_UUID>``
|
||||||
|
|
||||||
|
* ``action_plan:delete`` - Delete an action plan entity
|
||||||
|
|
||||||
|
* ``DELETE /v1/action_plans/<ACTION_PLAN_UUID>``
|
||||||
|
|
||||||
|
* ``action_plan:update`` - Update an action plan entity
|
||||||
|
|
||||||
|
* ``PATCH /v1/audits/<ACTION_PLAN_UUID>``
|
||||||
|
|
||||||
|
|
||||||
|
* ``action:get_all``, ``action:detail`` - List available action
|
||||||
|
|
||||||
|
* ``GET /v1/actions``
|
||||||
|
* ``GET /v1/actions/detail``
|
||||||
|
|
||||||
|
* ``action:get`` - Retrieve a specific action plan entity
|
||||||
|
|
||||||
|
* ``GET /v1/actions/<ACTION_UUID>``
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
To limit an action to a particular role or roles, you list the roles like so ::
|
||||||
|
|
||||||
|
{
|
||||||
|
"audit:create": ["role:admin", "role:superuser"]
|
||||||
|
}
|
||||||
|
|
||||||
|
The above would add a rule that only allowed users that had roles of either
|
||||||
|
"admin" or "superuser" to launch an audit.
|
||||||
@@ -11,7 +11,7 @@ Watcher User Guide
|
|||||||
==================
|
==================
|
||||||
|
|
||||||
See the
|
See the
|
||||||
`architecture page <https://factory.b-com.com/www/watcher/doc/watcher/architecture.html>`_
|
`architecture page <http://docs.openstack.org/developer/watcher/architecture.html>`_
|
||||||
for an architectural overview of the different components of Watcher and how
|
for an architectural overview of the different components of Watcher and how
|
||||||
they fit together.
|
they fit together.
|
||||||
|
|
||||||
@@ -32,17 +32,17 @@ This guide assumes you have a working installation of Watcher. If you get
|
|||||||
Please refer to the `installation guide`_.
|
Please refer to the `installation guide`_.
|
||||||
In order to use Watcher, you have to configure your credentials suitable for
|
In order to use Watcher, you have to configure your credentials suitable for
|
||||||
watcher command-line tools.
|
watcher command-line tools.
|
||||||
If you need help on a specific command, you can use:
|
|
||||||
|
|
||||||
.. code:: bash
|
You can interact with Watcher either by using our dedicated `Watcher CLI`_
|
||||||
|
named ``watcher``, or by using the `OpenStack CLI`_ ``openstack``.
|
||||||
$ watcher help COMMAND
|
|
||||||
|
|
||||||
If you want to deploy Watcher in Horizon, please refer to the `Watcher Horizon
|
If you want to deploy Watcher in Horizon, please refer to the `Watcher Horizon
|
||||||
plugin installation guide`_.
|
plugin installation guide`_.
|
||||||
|
|
||||||
.. _`installation guide`: https://factory.b-com.com/www/watcher/doc/python-watcherclient
|
.. _`installation guide`: https://factory.b-com.com/www/watcher/doc/python-watcherclient
|
||||||
.. _`Watcher Horizon plugin installation guide`: https://factory.b-com.com/www/watcher/doc/watcher-dashboard/deploy/installation.html
|
.. _`Watcher Horizon plugin installation guide`: https://factory.b-com.com/www/watcher/doc/watcher-dashboard/deploy/installation.html
|
||||||
|
.. _`OpenStack CLI`: http://docs.openstack.org/developer/python-openstackclient/man/openstack.html
|
||||||
|
.. _`Watcher CLI`: https://factory.b-com.com/www/watcher/doc/python-watcherclient/index.html
|
||||||
|
|
||||||
Seeing what the Watcher CLI can do ?
|
Seeing what the Watcher CLI can do ?
|
||||||
------------------------------------
|
------------------------------------
|
||||||
@@ -51,23 +51,76 @@ watcher binary without options.
|
|||||||
|
|
||||||
.. code:: bash
|
.. code:: bash
|
||||||
|
|
||||||
$ watcher
|
$ watcher help
|
||||||
|
|
||||||
|
or::
|
||||||
|
|
||||||
|
$ openstack help optimize
|
||||||
|
|
||||||
How do I run an audit of my cluster ?
|
How do I run an audit of my cluster ?
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
|
|
||||||
First, you need to create an :ref:`audit template <audit_template_definition>`.
|
First, you need to find the :ref:`goal <goal_definition>` you want to achieve:
|
||||||
An :ref:`audit template <audit_template_definition>` defines an optimization
|
|
||||||
:ref:`goal <goal_definition>` to achieve (i.e. the settings of your audit).
|
|
||||||
This goal should be declared in the Watcher service configuration file
|
|
||||||
**/etc/watcher/watcher.conf**.
|
|
||||||
|
|
||||||
.. code:: bash
|
.. code:: bash
|
||||||
|
|
||||||
$ watcher audit-template-create my_first_audit DUMMY
|
$ watcher goal list
|
||||||
|
|
||||||
If you get "*You must provide a username via either --os-username or via
|
or::
|
||||||
env[OS_USERNAME]*" you may have to verify your credentials.
|
|
||||||
|
$ openstack optimize goal list
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
If you get "*You must provide a username via either --os-username or via
|
||||||
|
env[OS_USERNAME]*" you may have to verify your credentials.
|
||||||
|
|
||||||
|
Then, you can create an :ref:`audit template <audit_template_definition>`.
|
||||||
|
An :ref:`audit template <audit_template_definition>` defines an optimization
|
||||||
|
:ref:`goal <goal_definition>` to achieve (i.e. the settings of your audit).
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
$ watcher audittemplate create my_first_audit_template <your_goal>
|
||||||
|
|
||||||
|
or::
|
||||||
|
|
||||||
|
$ openstack optimize audittemplate create my_first_audit_template <your_goal>
|
||||||
|
|
||||||
|
Although optional, you may want to actually set a specific strategy for your
|
||||||
|
audit template. If so, you may can search of its UUID or name using the
|
||||||
|
following command:
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
$ watcher strategy list --goal-uuid <your_goal_uuid>
|
||||||
|
|
||||||
|
or::
|
||||||
|
|
||||||
|
$ openstack optimize strategy list --goal-uuid <your_goal_uuid>
|
||||||
|
|
||||||
|
You can use the following command to check strategy details including which
|
||||||
|
parameters of which format it supports:
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
$ watcher strategy show <your_strategy>
|
||||||
|
|
||||||
|
or::
|
||||||
|
|
||||||
|
$ openstack optimize strategy show <your_strategy>
|
||||||
|
|
||||||
|
The command to create your audit template would then be:
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
$ watcher audittemplate create my_first_audit_template <your_goal> \
|
||||||
|
--strategy <your_strategy>
|
||||||
|
|
||||||
|
or::
|
||||||
|
|
||||||
|
$ openstack optimize audittemplate create my_first_audit_template <your_goal> \
|
||||||
|
--strategy <your_strategy>
|
||||||
|
|
||||||
Then, you can create an audit. An audit is a request for optimizing your
|
Then, you can create an audit. An audit is a request for optimizing your
|
||||||
cluster depending on the specified :ref:`goal <goal_definition>`.
|
cluster depending on the specified :ref:`goal <goal_definition>`.
|
||||||
@@ -76,19 +129,46 @@ You can launch an audit on your cluster by referencing the
|
|||||||
:ref:`audit template <audit_template_definition>` (i.e. the settings of your
|
:ref:`audit template <audit_template_definition>` (i.e. the settings of your
|
||||||
audit) that you want to use.
|
audit) that you want to use.
|
||||||
|
|
||||||
- Get the :ref:`audit template <audit_template_definition>` UUID:
|
- Get the :ref:`audit template <audit_template_definition>` UUID or name:
|
||||||
|
|
||||||
.. code:: bash
|
.. code:: bash
|
||||||
|
|
||||||
$ watcher audit-template-list
|
$ watcher audittemplate list
|
||||||
|
|
||||||
|
or::
|
||||||
|
|
||||||
|
$ openstack optimize audittemplate list
|
||||||
|
|
||||||
- Start an audit based on this :ref:`audit template
|
- Start an audit based on this :ref:`audit template
|
||||||
<audit_template_definition>` settings:
|
<audit_template_definition>` settings:
|
||||||
|
|
||||||
.. code:: bash
|
.. code:: bash
|
||||||
|
|
||||||
$ watcher audit-create -a <your_audit_template_uuid>
|
$ watcher audit create -a <your_audit_template>
|
||||||
|
|
||||||
|
or::
|
||||||
|
|
||||||
|
$ openstack optimize audit create -a <your_audit_template>
|
||||||
|
|
||||||
|
If your_audit_template was created by --strategy <your_strategy>, and it
|
||||||
|
defines some parameters (command `watcher strategy show` to check parameters
|
||||||
|
format), your can append `-p` to input required parameters:
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
$ watcher audit create -a <your_audit_template> \
|
||||||
|
-p <your_strategy_para1>=5.5 -p <your_strategy_para2>=hi
|
||||||
|
|
||||||
|
or::
|
||||||
|
|
||||||
|
$ openstack optimize audit create -a <your_audit_template> \
|
||||||
|
-p <your_strategy_para1>=5.5 -p <your_strategy_para2>=hi
|
||||||
|
|
||||||
|
Input parameter could cause audit creation failure, when:
|
||||||
|
|
||||||
|
- no predefined strategy for audit template
|
||||||
|
- no parameters spec in predefined strategy
|
||||||
|
- input parameters don't comply with spec
|
||||||
|
|
||||||
Watcher service will compute an :ref:`Action Plan <action_plan_definition>`
|
Watcher service will compute an :ref:`Action Plan <action_plan_definition>`
|
||||||
composed of a list of potential optimization :ref:`actions <action_definition>`
|
composed of a list of potential optimization :ref:`actions <action_definition>`
|
||||||
@@ -102,15 +182,22 @@ configuration file.
|
|||||||
|
|
||||||
.. code:: bash
|
.. code:: bash
|
||||||
|
|
||||||
$ watcher action-plan-list --audit <the_audit_uuid>
|
$ watcher actionplan list --audit <the_audit_uuid>
|
||||||
|
|
||||||
|
or::
|
||||||
|
|
||||||
|
$ openstack optimize actionplan list --audit <the_audit_uuid>
|
||||||
|
|
||||||
- Have a look on the list of optimization :ref:`actions <action_definition>`
|
- Have a look on the list of optimization :ref:`actions <action_definition>`
|
||||||
contained in this new :ref:`action plan <action_plan_definition>`:
|
contained in this new :ref:`action plan <action_plan_definition>`:
|
||||||
|
|
||||||
.. code:: bash
|
.. code:: bash
|
||||||
|
|
||||||
$ watcher action-list --action-plan <the_action_plan_uuid>
|
$ watcher action list --action-plan <the_action_plan_uuid>
|
||||||
|
|
||||||
|
or::
|
||||||
|
|
||||||
|
$ openstack optimize action list --action-plan <the_action_plan_uuid>
|
||||||
|
|
||||||
Once you have learned how to create an :ref:`Action Plan
|
Once you have learned how to create an :ref:`Action Plan
|
||||||
<action_plan_definition>`, it's time to go further by applying it to your
|
<action_plan_definition>`, it's time to go further by applying it to your
|
||||||
@@ -120,18 +207,30 @@ cluster:
|
|||||||
|
|
||||||
.. code:: bash
|
.. code:: bash
|
||||||
|
|
||||||
$ watcher action-plan-start <the_action_plan_uuid>
|
$ watcher actionplan start <the_action_plan_uuid>
|
||||||
|
|
||||||
|
or::
|
||||||
|
|
||||||
|
$ openstack optimize actionplan start <the_action_plan_uuid>
|
||||||
|
|
||||||
You can follow the states of the :ref:`actions <action_definition>` by
|
You can follow the states of the :ref:`actions <action_definition>` by
|
||||||
periodically calling:
|
periodically calling:
|
||||||
|
|
||||||
.. code:: bash
|
.. code:: bash
|
||||||
|
|
||||||
$ watcher action-list
|
$ watcher action list
|
||||||
|
|
||||||
|
or::
|
||||||
|
|
||||||
|
$ openstack optimize action list
|
||||||
|
|
||||||
You can also obtain more detailed information about a specific action:
|
You can also obtain more detailed information about a specific action:
|
||||||
|
|
||||||
.. code:: bash
|
.. code:: bash
|
||||||
|
|
||||||
$ watcher action-show <the_action_uuid>
|
$ watcher action show <the_action_uuid>
|
||||||
|
|
||||||
|
or::
|
||||||
|
|
||||||
|
$ openstack optimize action show <the_action_uuid>
|
||||||
|
|
||||||
|
|||||||
@@ -64,8 +64,8 @@ IRC Channel
|
|||||||
``#openstack-watcher`` (changelog_)
|
``#openstack-watcher`` (changelog_)
|
||||||
|
|
||||||
Weekly Meetings
|
Weekly Meetings
|
||||||
on Wednesdays at 14:00 UTC in the ``#openstack-meeting-4`` IRC
|
on Wednesdays at 14:00 UTC on even weeks, 9:00 UTC on odd weeks, in the
|
||||||
channel (`meetings logs`_)
|
``#openstack-meeting-4`` IRC channel (`meetings logs`_)
|
||||||
|
|
||||||
.. _changelog: http://eavesdrop.openstack.org/irclogs/%23openstack-watcher/
|
.. _changelog: http://eavesdrop.openstack.org/irclogs/%23openstack-watcher/
|
||||||
.. _meetings logs: http://eavesdrop.openstack.org/meetings/watcher/
|
.. _meetings logs: http://eavesdrop.openstack.org/meetings/watcher/
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ To install Watcher from packaging, refer instead to Watcher `User
|
|||||||
Documentation`_.
|
Documentation`_.
|
||||||
|
|
||||||
.. _`Git Repository`: http://git.openstack.org/cgit/openstack/watcher
|
.. _`Git Repository`: http://git.openstack.org/cgit/openstack/watcher
|
||||||
.. _`User Documentation`: https://factory.b-com.com/www/watcher/doc/watcher/
|
.. _`User Documentation`: http://docs.openstack.org/developer/watcher/
|
||||||
|
|
||||||
Prerequisites
|
Prerequisites
|
||||||
=============
|
=============
|
||||||
@@ -205,7 +205,7 @@ place:
|
|||||||
|
|
||||||
$ workon watcher
|
$ workon watcher
|
||||||
|
|
||||||
(watcher) $ watcher-db-manage --create_schema
|
(watcher) $ watcher-db-manage create_schema
|
||||||
|
|
||||||
|
|
||||||
Running Watcher services
|
Running Watcher services
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
https://creativecommons.org/licenses/by/3.0/
|
https://creativecommons.org/licenses/by/3.0/
|
||||||
|
|
||||||
|
.. _implement_action_plugin:
|
||||||
|
|
||||||
==================
|
==================
|
||||||
Build a new action
|
Build a new action
|
||||||
==================
|
==================
|
||||||
@@ -50,14 +52,16 @@ Here is an example showing how you can write a plugin called ``DummyAction``:
|
|||||||
|
|
||||||
# Filepath = <PROJECT_DIR>/thirdparty/dummy.py
|
# Filepath = <PROJECT_DIR>/thirdparty/dummy.py
|
||||||
# Import path = thirdparty.dummy
|
# Import path = thirdparty.dummy
|
||||||
|
import voluptuous
|
||||||
|
|
||||||
from watcher.applier.actions import base
|
from watcher.applier.actions import base
|
||||||
|
|
||||||
|
|
||||||
class DummyAction(baseBaseAction):
|
class DummyAction(base.BaseAction):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def schema(self):
|
def schema(self):
|
||||||
return Schema({})
|
return voluptuous.Schema({})
|
||||||
|
|
||||||
def execute(self):
|
def execute(self):
|
||||||
# Does nothing
|
# Does nothing
|
||||||
@@ -83,6 +87,58 @@ To get a better understanding on how to implement a more advanced action,
|
|||||||
have a look at the :py:class:`~watcher.applier.actions.migration.Migrate`
|
have a look at the :py:class:`~watcher.applier.actions.migration.Migrate`
|
||||||
class.
|
class.
|
||||||
|
|
||||||
|
Input validation
|
||||||
|
----------------
|
||||||
|
|
||||||
|
As you can see in the previous example, we are using `Voluptuous`_ to validate
|
||||||
|
the input parameters of an action. So if you want to learn more about how to
|
||||||
|
work with `Voluptuous`_, you can have a look at their `documentation`_:
|
||||||
|
|
||||||
|
.. _Voluptuous: https://github.com/alecthomas/voluptuous
|
||||||
|
.. _documentation: https://github.com/alecthomas/voluptuous/blob/master/README.md
|
||||||
|
|
||||||
|
|
||||||
|
Define configuration parameters
|
||||||
|
===============================
|
||||||
|
|
||||||
|
At this point, you have a fully functional action. However, in more complex
|
||||||
|
implementation, you may want to define some configuration options so one can
|
||||||
|
tune the action to its needs. To do so, you can implement the
|
||||||
|
:py:meth:`~.Loadable.get_config_opts` class method as followed:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
class DummyAction(base.BaseAction):
|
||||||
|
|
||||||
|
# [...]
|
||||||
|
|
||||||
|
def execute(self):
|
||||||
|
assert self.config.test_opt == 0
|
||||||
|
|
||||||
|
def get_config_opts(self):
|
||||||
|
return [
|
||||||
|
cfg.StrOpt('test_opt', help="Demo Option.", default=0),
|
||||||
|
# Some more options ...
|
||||||
|
]
|
||||||
|
|
||||||
|
The configuration options defined within this class method will be included
|
||||||
|
within the global ``watcher.conf`` configuration file under a section named by
|
||||||
|
convention: ``{namespace}.{plugin_name}``. In our case, the ``watcher.conf``
|
||||||
|
configuration would have to be modified as followed:
|
||||||
|
|
||||||
|
.. code-block:: ini
|
||||||
|
|
||||||
|
[watcher_actions.dummy]
|
||||||
|
# Option used for testing.
|
||||||
|
test_opt = test_value
|
||||||
|
|
||||||
|
Then, the configuration options you define within this method will then be
|
||||||
|
injected in each instantiated object via the ``config`` parameter of the
|
||||||
|
:py:meth:`~.BaseAction.__init__` method.
|
||||||
|
|
||||||
|
|
||||||
Abstract Plugin Class
|
Abstract Plugin Class
|
||||||
=====================
|
=====================
|
||||||
|
|
||||||
@@ -91,6 +147,7 @@ should implement:
|
|||||||
|
|
||||||
.. autoclass:: watcher.applier.actions.base.BaseAction
|
.. autoclass:: watcher.applier.actions.base.BaseAction
|
||||||
:members:
|
:members:
|
||||||
|
:special-members: __init__
|
||||||
:noindex:
|
:noindex:
|
||||||
|
|
||||||
.. py:attribute:: schema
|
.. py:attribute:: schema
|
||||||
@@ -148,6 +205,9 @@ By doing so, your action will be saved within the Watcher Database, ready to be
|
|||||||
processed by the planner for creating an action plan which can then be executed
|
processed by the planner for creating an action plan which can then be executed
|
||||||
by the Watcher Applier via its workflow engine.
|
by the Watcher Applier via its workflow engine.
|
||||||
|
|
||||||
|
At the last, remember to add the action into the weights in ``watcher.conf``,
|
||||||
|
otherwise you will get an error when the action be referenced in a strategy.
|
||||||
|
|
||||||
|
|
||||||
Scheduling of an action plugin
|
Scheduling of an action plugin
|
||||||
==============================
|
==============================
|
||||||
|
|||||||
@@ -71,8 +71,15 @@ structure that looks like this::
|
|||||||
│ └── test_thirdparty.py
|
│ └── test_thirdparty.py
|
||||||
└── tox.ini
|
└── tox.ini
|
||||||
|
|
||||||
|
**Note:** You should add `python-watcher`_ as a dependency in the
|
||||||
|
requirements.txt file::
|
||||||
|
|
||||||
|
# Watcher-specific requirements
|
||||||
|
python-watcher
|
||||||
|
|
||||||
.. _cookiecutter: https://github.com/audreyr/cookiecutter
|
.. _cookiecutter: https://github.com/audreyr/cookiecutter
|
||||||
.. _OpenStack cookiecutter: https://github.com/openstack-dev/cookiecutter
|
.. _OpenStack cookiecutter: https://github.com/openstack-dev/cookiecutter
|
||||||
|
.. _python-watcher: https://pypi.python.org/pypi/python-watcher
|
||||||
|
|
||||||
Implementing a plugin for Watcher
|
Implementing a plugin for Watcher
|
||||||
=================================
|
=================================
|
||||||
@@ -83,7 +90,7 @@ plugins for Watcher:
|
|||||||
|
|
||||||
- A :ref:`strategy plugin <implement_strategy_plugin>`
|
- A :ref:`strategy plugin <implement_strategy_plugin>`
|
||||||
- A :ref:`planner plugin <implement_planner_plugin>`
|
- A :ref:`planner plugin <implement_planner_plugin>`
|
||||||
- An :ref:`action plugin <implement_strategy_plugin>`
|
- An :ref:`action plugin <implement_action_plugin>`
|
||||||
- A :ref:`workflow engine plugin <implement_workflow_engine_plugin>`
|
- A :ref:`workflow engine plugin <implement_workflow_engine_plugin>`
|
||||||
|
|
||||||
If you want to learn more on how to implement them, you can refer to their
|
If you want to learn more on how to implement them, you can refer to their
|
||||||
|
|||||||
215
doc/source/dev/plugin/goal-plugin.rst
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
..
|
||||||
|
Except where otherwise noted, this document is licensed under Creative
|
||||||
|
Commons Attribution 3.0 License. You can view the license at:
|
||||||
|
|
||||||
|
https://creativecommons.org/licenses/by/3.0/
|
||||||
|
|
||||||
|
.. _implement_goal_plugin:
|
||||||
|
|
||||||
|
================
|
||||||
|
Build a new goal
|
||||||
|
================
|
||||||
|
|
||||||
|
Watcher Decision Engine has an external :ref:`goal <goal_definition>`
|
||||||
|
plugin interface which gives anyone the ability to integrate an external
|
||||||
|
goal which can be achieved by a :ref:`strategy <strategy_definition>`.
|
||||||
|
|
||||||
|
This section gives some guidelines on how to implement and integrate custom
|
||||||
|
goals with Watcher. If you wish to create a third-party package for your
|
||||||
|
plugin, you can refer to our :ref:`documentation for third-party package
|
||||||
|
creation <plugin-base_setup>`.
|
||||||
|
|
||||||
|
|
||||||
|
Pre-requisites
|
||||||
|
==============
|
||||||
|
|
||||||
|
Before using any goal, please make sure that none of the existing goals fit
|
||||||
|
your needs. Indeed, the underlying value of defining a goal is to be able to
|
||||||
|
compare the efficacy of the action plans resulting from the various strategies
|
||||||
|
satisfying the same goal. By doing so, Watcher can assist the administrator
|
||||||
|
in his choices.
|
||||||
|
|
||||||
|
|
||||||
|
Create a new plugin
|
||||||
|
===================
|
||||||
|
|
||||||
|
In order to create a new goal, you have to:
|
||||||
|
|
||||||
|
- Extend the :py:class:`~.base.Goal` class.
|
||||||
|
- Implement its :py:meth:`~.Goal.get_name` class method to return the
|
||||||
|
**unique** ID of the new goal you want to create. This unique ID should
|
||||||
|
be the same as the name of :ref:`the entry point you will declare later on
|
||||||
|
<goal_plugin_add_entrypoint>`.
|
||||||
|
- Implement its :py:meth:`~.Goal.get_display_name` class method to
|
||||||
|
return the translated display name of the goal you want to create.
|
||||||
|
Note: Do not use a variable to return the translated string so it can be
|
||||||
|
automatically collected by the translation tool.
|
||||||
|
- Implement its :py:meth:`~.Goal.get_translatable_display_name`
|
||||||
|
class method to return the translation key (actually the english display
|
||||||
|
name) of your new goal. The value return should be the same as the
|
||||||
|
string translated in :py:meth:`~.Goal.get_display_name`.
|
||||||
|
- Implement its :py:meth:`~.Goal.get_efficacy_specification` method to return
|
||||||
|
the :ref:`efficacy specification <efficacy_specification_definition>` for
|
||||||
|
your goal.
|
||||||
|
|
||||||
|
Here is an example showing how you can define a new ``NewGoal`` goal plugin:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
# filepath: thirdparty/new.py
|
||||||
|
# import path: thirdparty.new
|
||||||
|
|
||||||
|
from watcher._i18n import _
|
||||||
|
from watcher.decision_engine.goal.efficacy import specs
|
||||||
|
from watcher.decision_engine.strategy.strategies import base
|
||||||
|
|
||||||
|
class NewGoal(base.Goal):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_name(cls):
|
||||||
|
return "new_goal" # Will be the name of the entry point
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_display_name(cls):
|
||||||
|
return _("New Goal")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_translatable_display_name(cls):
|
||||||
|
return "New Goal"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_efficacy_specification(cls):
|
||||||
|
return specs.UnclassifiedStrategySpecification()
|
||||||
|
|
||||||
|
|
||||||
|
As you may have noticed, the :py:meth:`~.Goal.get_efficacy_specification`
|
||||||
|
method returns an :py:meth:`~.UnclassifiedStrategySpecification` instance which
|
||||||
|
is provided by Watcher. This efficacy specification is useful during the
|
||||||
|
development process of your goal as it corresponds to an empty specification.
|
||||||
|
If you want to learn more about what efficacy specifications are used for or to
|
||||||
|
define your own efficacy specification, please refer to the :ref:`related
|
||||||
|
section below <implement_efficacy_specification>`.
|
||||||
|
|
||||||
|
|
||||||
|
Abstract Plugin Class
|
||||||
|
=====================
|
||||||
|
|
||||||
|
Here below is the abstract :py:class:`~.base.Goal` class:
|
||||||
|
|
||||||
|
.. autoclass:: watcher.decision_engine.goal.base.Goal
|
||||||
|
:members:
|
||||||
|
:noindex:
|
||||||
|
|
||||||
|
.. _goal_plugin_add_entrypoint:
|
||||||
|
|
||||||
|
Add a new entry point
|
||||||
|
=====================
|
||||||
|
|
||||||
|
In order for the Watcher Decision Engine to load your new goal, the
|
||||||
|
goal must be registered as a named entry point under the ``watcher_goals``
|
||||||
|
entry point namespace of your ``setup.py`` file. If you are using pbr_, this
|
||||||
|
entry point should be placed in your ``setup.cfg`` file.
|
||||||
|
|
||||||
|
The name you give to your entry point has to be unique and should be the same
|
||||||
|
as the value returned by the :py:meth:`~.base.Goal.get_name` class method of
|
||||||
|
your goal.
|
||||||
|
|
||||||
|
Here below is how you would proceed to register ``NewGoal`` using pbr_:
|
||||||
|
|
||||||
|
.. code-block:: ini
|
||||||
|
|
||||||
|
[entry_points]
|
||||||
|
watcher_goals =
|
||||||
|
new_goal = thirdparty.new:NewGoal
|
||||||
|
|
||||||
|
|
||||||
|
To get a better understanding on how to implement a more advanced goal,
|
||||||
|
have a look at the :py:class:`~.ServerConsolidation` class.
|
||||||
|
|
||||||
|
.. _pbr: http://docs.openstack.org/developer/pbr/
|
||||||
|
|
||||||
|
.. _implement_efficacy_specification:
|
||||||
|
|
||||||
|
Implement a customized efficacy specification
|
||||||
|
=============================================
|
||||||
|
|
||||||
|
What is it for?
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Efficacy specifications define a set of specifications for a given goal.
|
||||||
|
These specifications actually define a list of indicators which are to be used
|
||||||
|
to compute a global efficacy that outlines how well a strategy performed when
|
||||||
|
trying to achieve the goal it is associated to.
|
||||||
|
|
||||||
|
The idea behind such specification is to give the administrator the possibility
|
||||||
|
to run an audit using different strategies satisfying the same goal and be able
|
||||||
|
to judge how they performed at a glance.
|
||||||
|
|
||||||
|
|
||||||
|
Implementation
|
||||||
|
--------------
|
||||||
|
|
||||||
|
In order to create a new efficacy specification, you have to:
|
||||||
|
|
||||||
|
- Extend the :py:class:`~.EfficacySpecification` class.
|
||||||
|
- Implement :py:meth:`~.EfficacySpecification.get_indicators_specifications`
|
||||||
|
by returning a list of :py:class:`~.IndicatorSpecification` instances.
|
||||||
|
|
||||||
|
* Each :py:class:`~.IndicatorSpecification` instance should actually extend
|
||||||
|
the latter.
|
||||||
|
* Each indicator specification should have a **unique name** which should be
|
||||||
|
a valid Python variable name.
|
||||||
|
* They should implement the :py:attr:`~.EfficacySpecification.schema`
|
||||||
|
abstract property by returning a :py:class:`~.voluptuous.Schema` instance.
|
||||||
|
This schema is the contract the strategy will have to comply with when
|
||||||
|
setting the value associated to the indicator specification within its
|
||||||
|
solution (see the :ref:`architecture of Watcher
|
||||||
|
<sequence_diagrams_create_and_launch_audit>` for more information on
|
||||||
|
the audit execution workflow).
|
||||||
|
|
||||||
|
- Implement the :py:meth:`~.EfficacySpecification.get_global_efficacy` method:
|
||||||
|
it should compute the global efficacy for the goal it achieves based on the
|
||||||
|
efficacy indicators you just defined.
|
||||||
|
|
||||||
|
Here below is an example of an efficacy specification containing one indicator
|
||||||
|
specification:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from watcher._i18n import _
|
||||||
|
from watcher.decision_engine.goal.efficacy import base as efficacy_base
|
||||||
|
from watcher.decision_engine.goal.efficacy import indicators
|
||||||
|
from watcher.decision_engine.solution import efficacy
|
||||||
|
|
||||||
|
|
||||||
|
class IndicatorExample(IndicatorSpecification):
|
||||||
|
def __init__(self):
|
||||||
|
super(IndicatorExample, self).__init__(
|
||||||
|
name="indicator_example",
|
||||||
|
description=_("Example of indicator specification."),
|
||||||
|
unit=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def schema(self):
|
||||||
|
return voluptuous.Schema(voluptuous.Range(min=0), required=True)
|
||||||
|
|
||||||
|
|
||||||
|
class UnclassifiedStrategySpecification(efficacy_base.EfficacySpecification):
|
||||||
|
|
||||||
|
def get_indicators_specifications(self):
|
||||||
|
return [IndicatorExample()]
|
||||||
|
|
||||||
|
def get_global_efficacy(self, indicators_map):
|
||||||
|
return efficacy.Indicator(
|
||||||
|
name="global_efficacy_indicator",
|
||||||
|
description="Example of global efficacy indicator",
|
||||||
|
unit="%",
|
||||||
|
value=indicators_map.indicator_example % 100)
|
||||||
|
|
||||||
|
|
||||||
|
To get a better understanding on how to implement an efficacy specification,
|
||||||
|
have a look at :py:class:`~.ServerConsolidationSpecification`.
|
||||||
|
|
||||||
|
Also, if you want to see a concrete example of an indicator specification,
|
||||||
|
have a look at :py:class:`~.ReleasedComputeNodesCount`.
|
||||||
@@ -11,9 +11,10 @@ Build a new planner
|
|||||||
===================
|
===================
|
||||||
|
|
||||||
Watcher :ref:`Decision Engine <watcher_decision_engine_definition>` has an
|
Watcher :ref:`Decision Engine <watcher_decision_engine_definition>` has an
|
||||||
external :ref:`planner <planner_definition>` plugin interface which gives
|
external :ref:`planner <watcher_planner_definition>` plugin interface which
|
||||||
anyone the ability to integrate an external :ref:`planner <planner_definition>`
|
gives anyone the ability to integrate an external :ref:`planner
|
||||||
in order to extend the initial set of planners Watcher provides.
|
<watcher_planner_definition>` in order to extend the initial set of planners
|
||||||
|
Watcher provides.
|
||||||
|
|
||||||
This section gives some guidelines on how to implement and integrate custom
|
This section gives some guidelines on how to implement and integrate custom
|
||||||
planners with Watcher.
|
planners with Watcher.
|
||||||
@@ -69,6 +70,49 @@ examples, have a look at the implementation of planners already provided by
|
|||||||
Watcher like :py:class:`~.DefaultPlanner`. A list with all available planner
|
Watcher like :py:class:`~.DefaultPlanner`. A list with all available planner
|
||||||
plugins can be found :ref:`here <watcher_planners>`.
|
plugins can be found :ref:`here <watcher_planners>`.
|
||||||
|
|
||||||
|
|
||||||
|
Define configuration parameters
|
||||||
|
===============================
|
||||||
|
|
||||||
|
At this point, you have a fully functional planner. However, in more complex
|
||||||
|
implementation, you may want to define some configuration options so one can
|
||||||
|
tune the planner to its needs. To do so, you can implement the
|
||||||
|
:py:meth:`~.Loadable.get_config_opts` class method as followed:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
class DummyPlanner(base.BasePlanner):
|
||||||
|
|
||||||
|
# [...]
|
||||||
|
|
||||||
|
def schedule(self, context, audit_uuid, solution):
|
||||||
|
assert self.config.test_opt == 0
|
||||||
|
# [...]
|
||||||
|
|
||||||
|
def get_config_opts(self):
|
||||||
|
return [
|
||||||
|
cfg.StrOpt('test_opt', help="Demo Option.", default=0),
|
||||||
|
# Some more options ...
|
||||||
|
]
|
||||||
|
|
||||||
|
The configuration options defined within this class method will be included
|
||||||
|
within the global ``watcher.conf`` configuration file under a section named by
|
||||||
|
convention: ``{namespace}.{plugin_name}``. In our case, the ``watcher.conf``
|
||||||
|
configuration would have to be modified as followed:
|
||||||
|
|
||||||
|
.. code-block:: ini
|
||||||
|
|
||||||
|
[watcher_planners.dummy]
|
||||||
|
# Option used for testing.
|
||||||
|
test_opt = test_value
|
||||||
|
|
||||||
|
Then, the configuration options you define within this method will then be
|
||||||
|
injected in each instantiated object via the ``config`` parameter of the
|
||||||
|
:py:meth:`~.BasePlanner.__init__` method.
|
||||||
|
|
||||||
|
|
||||||
Abstract Plugin Class
|
Abstract Plugin Class
|
||||||
=====================
|
=====================
|
||||||
|
|
||||||
@@ -77,6 +121,7 @@ should implement:
|
|||||||
|
|
||||||
.. autoclass:: watcher.decision_engine.planner.base.BasePlanner
|
.. autoclass:: watcher.decision_engine.planner.base.BasePlanner
|
||||||
:members:
|
:members:
|
||||||
|
:special-members: __init__
|
||||||
:noindex:
|
:noindex:
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,9 @@ plugin interface which gives anyone the ability to integrate an external
|
|||||||
strategy in order to make use of placement algorithms.
|
strategy in order to make use of placement algorithms.
|
||||||
|
|
||||||
This section gives some guidelines on how to implement and integrate custom
|
This section gives some guidelines on how to implement and integrate custom
|
||||||
strategies with Watcher.
|
strategies with Watcher. If you wish to create a third-party package for your
|
||||||
|
plugin, you can refer to our :ref:`documentation for third-party package
|
||||||
|
creation <plugin-base_setup>`.
|
||||||
|
|
||||||
|
|
||||||
Pre-requisites
|
Pre-requisites
|
||||||
@@ -26,64 +28,172 @@ configured so that it would provide you all the metrics you need to be able to
|
|||||||
use your strategy.
|
use your strategy.
|
||||||
|
|
||||||
|
|
||||||
Creating a new plugin
|
Create a new strategy plugin
|
||||||
=====================
|
============================
|
||||||
|
|
||||||
First of all you have to:
|
In order to create a new strategy, you have to:
|
||||||
|
|
||||||
- Extend :py:class:`~.BaseStrategy`
|
- Extend the :py:class:`~.UnclassifiedStrategy` class
|
||||||
- Implement its :py:meth:`~.BaseStrategy.execute` method
|
- Implement its :py:meth:`~.BaseStrategy.get_name` class method to return the
|
||||||
|
**unique** ID of the new strategy you want to create. This unique ID should
|
||||||
|
be the same as the name of :ref:`the entry point we will declare later on
|
||||||
|
<strategy_plugin_add_entrypoint>`.
|
||||||
|
- Implement its :py:meth:`~.BaseStrategy.get_display_name` class method to
|
||||||
|
return the translated display name of the strategy you want to create.
|
||||||
|
Note: Do not use a variable to return the translated string so it can be
|
||||||
|
automatically collected by the translation tool.
|
||||||
|
- Implement its :py:meth:`~.BaseStrategy.get_translatable_display_name`
|
||||||
|
class method to return the translation key (actually the english display
|
||||||
|
name) of your new strategy. The value return should be the same as the
|
||||||
|
string translated in :py:meth:`~.BaseStrategy.get_display_name`.
|
||||||
|
- Implement its :py:meth:`~.BaseStrategy.execute` method to return the
|
||||||
|
solution you computed within your strategy.
|
||||||
|
|
||||||
Here is an example showing how you can write a plugin called ``DummyStrategy``:
|
Here is an example showing how you can write a plugin called ``NewStrategy``:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
import uuid
|
# filepath: thirdparty/new.py
|
||||||
|
# import path: thirdparty.new
|
||||||
|
import abc
|
||||||
|
|
||||||
class DummyStrategy(BaseStrategy):
|
import six
|
||||||
|
|
||||||
DEFAULT_NAME = "dummy"
|
from watcher._i18n import _
|
||||||
DEFAULT_DESCRIPTION = "Dummy Strategy"
|
from watcher.decision_engine.strategy.strategies import base
|
||||||
|
|
||||||
def __init__(self, name=DEFAULT_NAME, description=DEFAULT_DESCRIPTION):
|
|
||||||
super(DummyStrategy, self).__init__(name, description)
|
|
||||||
|
|
||||||
def execute(self, model):
|
class NewStrategy(base.UnclassifiedStrategy):
|
||||||
migration_type = 'live'
|
|
||||||
src_hypervisor = 'compute-host-1'
|
def __init__(self, osc=None):
|
||||||
dst_hypervisor = 'compute-host-2'
|
super(NewStrategy, self).__init__(osc)
|
||||||
instance_id = uuid.uuid4()
|
|
||||||
parameters = {'migration_type': migration_type,
|
def execute(self, original_model):
|
||||||
'src_hypervisor': src_hypervisor,
|
self.solution.add_action(action_type="nop",
|
||||||
'dst_hypervisor': dst_hypervisor}
|
|
||||||
self.solution.add_action(action_type="migration",
|
|
||||||
resource_id=instance_id,
|
|
||||||
input_parameters=parameters)
|
input_parameters=parameters)
|
||||||
# Do some more stuff here ...
|
# Do some more stuff here ...
|
||||||
return self.solution
|
return self.solution
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_name(cls):
|
||||||
|
return "new_strategy"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_display_name(cls):
|
||||||
|
return _("New strategy")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_translatable_display_name(cls):
|
||||||
|
return "New strategy"
|
||||||
|
|
||||||
|
|
||||||
As you can see in the above example, the :py:meth:`~.BaseStrategy.execute`
|
As you can see in the above example, the :py:meth:`~.BaseStrategy.execute`
|
||||||
method returns a :py:class:`~.BaseSolution` instance as required. This solution
|
method returns a :py:class:`~.BaseSolution` instance as required. This solution
|
||||||
is what wraps the abstract set of actions the strategy recommends to you. This
|
is what wraps the abstract set of actions the strategy recommends to you. This
|
||||||
solution is then processed by a :ref:`planner <planner_definition>` to produce
|
solution is then processed by a :ref:`planner <watcher_planner_definition>` to
|
||||||
an action plan which shall contain the sequenced flow of actions to be
|
produce an action plan which contains the sequenced flow of actions to be
|
||||||
executed by the :ref:`Watcher Applier <watcher_applier_definition>`.
|
executed by the :ref:`Watcher Applier <watcher_applier_definition>`. This
|
||||||
|
solution also contains the various :ref:`efficacy indicators
|
||||||
|
<efficacy_indicator_definition>` alongside its computed :ref:`global efficacy
|
||||||
|
<efficacy_definition>`.
|
||||||
|
|
||||||
Please note that your strategy class will be instantiated without any
|
Please note that your strategy class will expect to find the same constructor
|
||||||
parameter. Therefore, you should make sure not to make any of them required in
|
signature as BaseStrategy to instantiate you strategy. Therefore, you should
|
||||||
your ``__init__`` method.
|
ensure that your ``__init__`` signature is identical to the
|
||||||
|
:py:class:`~.BaseStrategy` one.
|
||||||
|
|
||||||
|
|
||||||
|
Strategy efficacy
|
||||||
|
=================
|
||||||
|
|
||||||
|
As stated before, the ``NewStrategy`` class extends a class called
|
||||||
|
:py:class:`~.UnclassifiedStrategy`. This class actually implements a set of
|
||||||
|
abstract methods which are defined within the :py:class:`~.BaseStrategy` parent
|
||||||
|
class.
|
||||||
|
|
||||||
|
One thing this :py:class:`~.UnclassifiedStrategy` class defines is that our
|
||||||
|
``NewStrategy`` achieves the ``unclassified`` goal. This goal is a peculiar one
|
||||||
|
as it does not contain any indicator nor does it calculate a global efficacy.
|
||||||
|
This proves itself to be quite useful during the development of a new strategy
|
||||||
|
for which the goal has yet to be defined or in case a :ref:`new goal
|
||||||
|
<implement_goal_plugin>` has yet to be implemented.
|
||||||
|
|
||||||
|
|
||||||
|
Define Strategy Parameters
|
||||||
|
==========================
|
||||||
|
|
||||||
|
For each new added strategy, you can add parameters spec so that an operator
|
||||||
|
can input strategy parameters when creating an audit to control the
|
||||||
|
:py:meth:`~.BaseStrategy.execute` behavior of strategy. This is useful to
|
||||||
|
define some threshold for your strategy, and tune them at runtime.
|
||||||
|
|
||||||
|
To define parameters, just implements :py:meth:`~.BaseStrategy.get_schema` to
|
||||||
|
return parameters spec with `jsonschema
|
||||||
|
<http://json-schema.org/>`_ format.
|
||||||
|
It is strongly encouraged that provide default value for each parameter, or
|
||||||
|
else reference fails if operator specify no parameters.
|
||||||
|
|
||||||
|
Here is an example showing how you can define 2 parameters for
|
||||||
|
``DummyStrategy``:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
class DummyStrategy(base.DummyBaseStrategy):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_schema(cls):
|
||||||
|
return {
|
||||||
|
"properties": {
|
||||||
|
"para1": {
|
||||||
|
"description": "number parameter example",
|
||||||
|
"type": "number",
|
||||||
|
"default": 3.2,
|
||||||
|
"minimum": 1.0,
|
||||||
|
"maximum": 10.2,
|
||||||
|
},
|
||||||
|
"para2": {
|
||||||
|
"description": "string parameter example",
|
||||||
|
"type": "string",
|
||||||
|
"default": "hello",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
You can reference parameters in :py:meth:`~.BaseStrategy.execute`:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
class DummyStrategy(base.DummyBaseStrategy):
|
||||||
|
|
||||||
|
def execute(self):
|
||||||
|
para1 = self.input_parameters.para1
|
||||||
|
para2 = self.input_parameters.para2
|
||||||
|
|
||||||
|
if para1 > 5:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
Operator can specify parameters with following commands:
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
$ watcher audit create -a <your_audit_template> -p para1=6.0 -p para2=hi
|
||||||
|
|
||||||
|
Pls. check user-guide for details.
|
||||||
|
|
||||||
|
|
||||||
Abstract Plugin Class
|
Abstract Plugin Class
|
||||||
=====================
|
=====================
|
||||||
|
|
||||||
Here below is the abstract :py:class:`~.BaseStrategy` class that every single
|
Here below is the abstract :py:class:`~.BaseStrategy` class:
|
||||||
strategy should implement:
|
|
||||||
|
|
||||||
.. autoclass:: watcher.decision_engine.strategy.strategies.base.BaseStrategy
|
.. autoclass:: watcher.decision_engine.strategy.strategies.base.BaseStrategy
|
||||||
:members:
|
:members:
|
||||||
|
:special-members: __init__
|
||||||
:noindex:
|
:noindex:
|
||||||
|
|
||||||
|
.. _strategy_plugin_add_entrypoint:
|
||||||
|
|
||||||
Add a new entry point
|
Add a new entry point
|
||||||
=====================
|
=====================
|
||||||
@@ -93,15 +203,17 @@ strategy must be registered as a named entry point under the
|
|||||||
``watcher_strategies`` entry point of your ``setup.py`` file. If you are using
|
``watcher_strategies`` entry point of your ``setup.py`` file. If you are using
|
||||||
pbr_, this entry point should be placed in your ``setup.cfg`` file.
|
pbr_, this entry point should be placed in your ``setup.cfg`` file.
|
||||||
|
|
||||||
The name you give to your entry point has to be unique.
|
The name you give to your entry point has to be unique and should be the same
|
||||||
|
as the value returned by the :py:meth:`~.BaseStrategy.get_name` class method of
|
||||||
|
your strategy.
|
||||||
|
|
||||||
Here below is how you would proceed to register ``DummyStrategy`` using pbr_:
|
Here below is how you would proceed to register ``NewStrategy`` using pbr_:
|
||||||
|
|
||||||
.. code-block:: ini
|
.. code-block:: ini
|
||||||
|
|
||||||
[entry_points]
|
[entry_points]
|
||||||
watcher_strategies =
|
watcher_strategies =
|
||||||
dummy = thirdparty.dummy:DummyStrategy
|
new_strategy = thirdparty.new:NewStrategy
|
||||||
|
|
||||||
|
|
||||||
To get a better understanding on how to implement a more advanced strategy,
|
To get a better understanding on how to implement a more advanced strategy,
|
||||||
@@ -117,16 +229,10 @@ plugins when it is restarted. If a Python package containing a custom plugin is
|
|||||||
installed within the same environment as Watcher, Watcher will automatically
|
installed within the same environment as Watcher, Watcher will automatically
|
||||||
make that plugin available for use.
|
make that plugin available for use.
|
||||||
|
|
||||||
At this point, Watcher will use your new strategy if you reference it in the
|
At this point, Watcher will scan and register inside the :ref:`Watcher Database
|
||||||
``goals`` under the ``[watcher_goals]`` section of your ``watcher.conf``
|
<watcher_database_definition>` all the strategies (alongside the goals they
|
||||||
configuration file. For example, if you want to use a ``dummy`` strategy you
|
should satisfy) you implemented upon restarting the :ref:`Watcher Decision
|
||||||
just installed, you would have to associate it to a goal like this:
|
Engine <watcher_decision_engine_definition>`.
|
||||||
|
|
||||||
.. code-block:: ini
|
|
||||||
|
|
||||||
[watcher_goals]
|
|
||||||
goals = BALANCE_LOAD:basic,MINIMIZE_ENERGY_CONSUMPTION:dummy
|
|
||||||
|
|
||||||
|
|
||||||
You should take care when installing strategy plugins. By their very nature,
|
You should take care when installing strategy plugins. By their very nature,
|
||||||
there are no guarantees that utilizing them as is will be supported, as
|
there are no guarantees that utilizing them as is will be supported, as
|
||||||
@@ -148,7 +254,6 @@ for various types of backends. A list of the available backends is located
|
|||||||
here_. The Ceilosca project is a good example of how to create your own
|
here_. The Ceilosca project is a good example of how to create your own
|
||||||
pluggable backend.
|
pluggable backend.
|
||||||
|
|
||||||
|
|
||||||
Finally, if your strategy requires new metrics not covered by Ceilometer, you
|
Finally, if your strategy requires new metrics not covered by Ceilometer, you
|
||||||
can add them through a Ceilometer `plugin`_.
|
can add them through a Ceilometer `plugin`_.
|
||||||
|
|
||||||
@@ -191,7 +296,7 @@ Read usage metrics using the Watcher Cluster History Helper
|
|||||||
|
|
||||||
Here below is the abstract ``BaseClusterHistory`` class of the Helper.
|
Here below is the abstract ``BaseClusterHistory`` class of the Helper.
|
||||||
|
|
||||||
.. autoclass:: watcher.metrics_engine.cluster_history.api.BaseClusterHistory
|
.. autoclass:: watcher.metrics_engine.cluster_history.base.BaseClusterHistory
|
||||||
:members:
|
:members:
|
||||||
:noindex:
|
:noindex:
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,17 @@
|
|||||||
Available Plugins
|
Available Plugins
|
||||||
=================
|
=================
|
||||||
|
|
||||||
|
In this section we present all the plugins that are shipped along with Watcher.
|
||||||
|
If you want to know which plugins your Watcher services have access to, you can
|
||||||
|
use the :ref:`Guru Meditation Reports <watcher_gmr>` to display them.
|
||||||
|
|
||||||
|
.. _watcher_goals:
|
||||||
|
|
||||||
|
Goals
|
||||||
|
=====
|
||||||
|
|
||||||
|
.. drivers-doc:: watcher_goals
|
||||||
|
|
||||||
.. _watcher_strategies:
|
.. _watcher_strategies:
|
||||||
|
|
||||||
Strategies
|
Strategies
|
||||||
|
|||||||
@@ -99,14 +99,14 @@ The :ref:`Cluster <cluster_definition>` may be divided in one or several
|
|||||||
Cluster Data Model
|
Cluster Data Model
|
||||||
==================
|
==================
|
||||||
|
|
||||||
.. watcher-term:: watcher.metrics_engine.cluster_model_collector.api
|
.. watcher-term:: watcher.metrics_engine.cluster_model_collector.base
|
||||||
|
|
||||||
.. _cluster_history_definition:
|
.. _cluster_history_definition:
|
||||||
|
|
||||||
Cluster History
|
Cluster History
|
||||||
===============
|
===============
|
||||||
|
|
||||||
.. watcher-term:: watcher.metrics_engine.cluster_history.api
|
.. watcher-term:: watcher.metrics_engine.cluster_history.base
|
||||||
|
|
||||||
.. _controller_node_definition:
|
.. _controller_node_definition:
|
||||||
|
|
||||||
@@ -131,7 +131,8 @@ can potentially be hosted on a dedicated machine.
|
|||||||
Compute node
|
Compute node
|
||||||
============
|
============
|
||||||
|
|
||||||
Please, read `the official OpenStack definition of a Compute Node <http://docs.openstack.org/openstack-ops/content/compute_nodes.html>`_.
|
Please, read `the official OpenStack definition of a Compute Node
|
||||||
|
<http://docs.openstack.org/openstack-ops/content/compute_nodes.html>`_.
|
||||||
|
|
||||||
.. _customer_definition:
|
.. _customer_definition:
|
||||||
|
|
||||||
@@ -211,40 +212,57 @@ Here are some examples of
|
|||||||
- `Sahara Hadoop Cluster <http://docs.openstack.org/developer/heat/template_guide/openstack.html#OS::Sahara::Cluster>`_
|
- `Sahara Hadoop Cluster <http://docs.openstack.org/developer/heat/template_guide/openstack.html#OS::Sahara::Cluster>`_
|
||||||
- ...
|
- ...
|
||||||
|
|
||||||
It can be any of the `the official list of available resource types defined in OpenStack for HEAT <http://docs.openstack.org/developer/heat/template_guide/openstack.html>`_.
|
It can be any of the `the official list of available resource types defined in
|
||||||
|
OpenStack for HEAT
|
||||||
|
<http://docs.openstack.org/developer/heat/template_guide/openstack.html>`_.
|
||||||
|
|
||||||
.. _efficiency_definition:
|
.. _efficacy_indicator_definition:
|
||||||
|
|
||||||
Optimization Efficiency
|
Efficacy Indicator
|
||||||
=======================
|
==================
|
||||||
|
|
||||||
The :ref:`Optimization Efficiency <efficiency_definition>` is the objective
|
.. watcher-term:: watcher.api.controllers.v1.efficacy_indicator
|
||||||
|
|
||||||
|
.. _efficacy_specification_definition:
|
||||||
|
|
||||||
|
Efficacy Specification
|
||||||
|
======================
|
||||||
|
|
||||||
|
.. watcher-term:: watcher.decision_engine.goal.efficacy.base
|
||||||
|
|
||||||
|
.. _efficacy_definition:
|
||||||
|
|
||||||
|
Optimization Efficacy
|
||||||
|
=====================
|
||||||
|
|
||||||
|
The :ref:`Optimization Efficacy <efficacy_definition>` is the objective
|
||||||
measure of how much of the :ref:`Goal <goal_definition>` has been achieved in
|
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
|
The way efficacy is evaluated will depend on the :ref:`Goal <goal_definition>`
|
||||||
:ref:`Goal <goal_definition>` to achieve.
|
to achieve.
|
||||||
|
|
||||||
Of course, the efficiency will be relevant only as long as the
|
Of course, the efficacy will be relevant only as long as the
|
||||||
:ref:`Action Plan <action_plan_definition>` is relevant
|
:ref:`Action Plan <action_plan_definition>` is relevant
|
||||||
(i.e., the current state of the :ref:`Cluster <cluster_definition>`
|
(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).
|
||||||
|
|
||||||
For example, if the :ref:`Goal <goal_definition>` is to lower the energy
|
For example, if the :ref:`Goal <goal_definition>` is to lower the energy
|
||||||
consumption, the :ref:`Efficiency <efficiency_definition>` will be computed
|
consumption, the :ref:`Efficacy <efficacy_definition>` will be computed
|
||||||
using several indicators (KPIs):
|
using several :ref:`efficacy indicators <efficacy_indicator_definition>`
|
||||||
|
(KPIs):
|
||||||
|
|
||||||
- the percentage of energy gain (which must be the highest possible)
|
- the percentage of energy gain (which must be the highest possible)
|
||||||
- the number of :ref:`SLA violations <sla_violation_definition>`
|
- the number of :ref:`SLA violations <sla_violation_definition>`
|
||||||
(which must be the lowest possible)
|
(which must be the lowest possible)
|
||||||
- the number of virtual machine migrations (which must be the lowest possible)
|
- the number of virtual machine migrations (which must be the lowest possible)
|
||||||
|
|
||||||
All those indicators (KPIs) are computed within a given timeframe, which is the
|
All those indicators are computed within a given timeframe, which is the
|
||||||
time taken to execute the whole :ref:`Action Plan <action_plan_definition>`.
|
time taken to execute the whole :ref:`Action Plan <action_plan_definition>`.
|
||||||
|
|
||||||
The efficiency also enables the :ref:`Administrator <administrator_definition>`
|
The efficacy also enables the :ref:`Administrator <administrator_definition>`
|
||||||
to objectively compare different :ref:`Strategies <strategy_definition>` for
|
to objectively compare different :ref:`Strategies <strategy_definition>` for
|
||||||
the same goal and same workload of the :ref:`Cluster <cluster_definition>`.
|
the same goal and same workload of the :ref:`Cluster <cluster_definition>`.
|
||||||
|
|
||||||
@@ -259,7 +277,8 @@ OpenStack should be owned by a specific :ref:`project <project_definition>`.
|
|||||||
In OpenStack Identity, a :ref:`project <project_definition>` must be owned by a
|
In OpenStack Identity, a :ref:`project <project_definition>` must be owned by a
|
||||||
specific domain.
|
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>`_.
|
||||||
|
|
||||||
|
|
||||||
.. _sla_definition:
|
.. _sla_definition:
|
||||||
@@ -323,7 +342,7 @@ Solution
|
|||||||
Strategy
|
Strategy
|
||||||
========
|
========
|
||||||
|
|
||||||
.. watcher-term:: watcher.decision_engine.strategy.strategies.base
|
.. watcher-term:: watcher.api.controllers.v1.strategy
|
||||||
|
|
||||||
.. _watcher_applier_definition:
|
.. _watcher_applier_definition:
|
||||||
|
|
||||||
@@ -364,4 +383,3 @@ Watcher Planner
|
|||||||
===============
|
===============
|
||||||
|
|
||||||
.. watcher-term:: watcher.decision_engine.planner.base
|
.. watcher-term:: watcher.decision_engine.planner.base
|
||||||
|
|
||||||
|
|||||||
14
doc/source/image_src/plantuml/README.rst
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
plantuml
|
||||||
|
========
|
||||||
|
|
||||||
|
|
||||||
|
To build an image from a source file, you have to upload the plantuml JAR file
|
||||||
|
available on http://plantuml.com/download.html.
|
||||||
|
After, just run this command to build your image:
|
||||||
|
|
||||||
|
.. code-block:: shell
|
||||||
|
|
||||||
|
$ cd doc/source/images
|
||||||
|
$ java -jar /path/to/plantuml.jar doc/source/image_src/plantuml/my_image.txt
|
||||||
|
$ ls doc/source/images/
|
||||||
|
my_image.png
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
actor Administrator
|
actor Administrator
|
||||||
|
|
||||||
Administrator -> "Watcher CLI" : watcher audit-create -a <audit_template_uuid>
|
Administrator -> "Watcher CLI" : watcher audit create -a <audit_template>
|
||||||
|
|
||||||
"Watcher CLI" -> "Watcher API" : POST audit(parameters)
|
"Watcher CLI" -> "Watcher API" : POST audit(parameters)
|
||||||
"Watcher API" -> "Watcher Database" : create new audit in database (status=PENDING)
|
"Watcher API" -> "Watcher Database" : create new audit in database (status=PENDING)
|
||||||
@@ -14,7 +14,7 @@ Administrator -> "Watcher CLI" : watcher audit-create -a <audit_template_uuid>
|
|||||||
Administrator <-- "Watcher CLI" : new audit uuid
|
Administrator <-- "Watcher CLI" : new audit uuid
|
||||||
|
|
||||||
"Watcher API" -> "AMQP Bus" : trigger_audit(new_audit.uuid)
|
"Watcher API" -> "AMQP Bus" : trigger_audit(new_audit.uuid)
|
||||||
"AMQP Bus" -> "Watcher Decision Engine" : trigger_audit(new_audit.uuid)
|
"AMQP Bus" -> "Watcher Decision Engine" : trigger_audit(new_audit.uuid) (status=ONGOING)
|
||||||
|
|
||||||
ref over "Watcher Decision Engine"
|
ref over "Watcher Decision Engine"
|
||||||
Trigger audit in the
|
Trigger audit in the
|
||||||
|
|||||||
@@ -2,15 +2,21 @@
|
|||||||
|
|
||||||
actor Administrator
|
actor Administrator
|
||||||
|
|
||||||
Administrator -> "Watcher CLI" : watcher audit-template-create <name> <goal>
|
Administrator -> "Watcher CLI" : watcher audittemplate create <name> <goal> \
|
||||||
|
[--strategy-uuid <strategy>]
|
||||||
"Watcher CLI" -> "Watcher API" : POST audit_template(parameters)
|
"Watcher CLI" -> "Watcher API" : POST audit_template(parameters)
|
||||||
|
|
||||||
"Watcher API" -> "Watcher API" : make sure goal exist in configuration
|
"Watcher API" -> "Watcher Database" : Request if goal exists in database
|
||||||
"Watcher API" -> "Watcher Database" : create new audit_template in database
|
"Watcher API" <-- "Watcher Database" : OK
|
||||||
|
|
||||||
"Watcher API" <-- "Watcher Database" : new audit template uuid
|
"Watcher API" -> "Watcher Database" : Request if strategy exists in database (if provided)
|
||||||
"Watcher CLI" <-- "Watcher API" : return new audit template URL in HTTP Location Header
|
"Watcher API" <-- "Watcher Database" : OK
|
||||||
Administrator <-- "Watcher CLI" : new audit template uuid
|
|
||||||
|
"Watcher API" -> "Watcher Database" : Create new audit_template in database
|
||||||
|
"Watcher API" <-- "Watcher Database" : New audit template UUID
|
||||||
|
|
||||||
|
"Watcher CLI" <-- "Watcher API" : Return new audit template URL in HTTP Location Header
|
||||||
|
Administrator <-- "Watcher CLI" : New audit template UUID
|
||||||
|
|
||||||
@enduml
|
@enduml
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
@startuml
|
||||||
|
|
||||||
|
skinparam maxMessageSize 200
|
||||||
|
|
||||||
|
"Decision Engine" -> "Decision Engine" : Execute audit
|
||||||
|
activate "Decision Engine"
|
||||||
|
"Decision Engine" -> "Decision Engine" : Set the audit state to ONGOING
|
||||||
|
|
||||||
|
"Decision Engine" -> "Strategy selector" : Select strategy
|
||||||
|
activate "Strategy selector"
|
||||||
|
alt A specific strategy is provided
|
||||||
|
"Strategy selector" -> "Strategy selector" : Load strategy and inject the \
|
||||||
|
cluster data model
|
||||||
|
else Only a goal is specified
|
||||||
|
"Strategy selector" -> "Strategy selector" : select strategy
|
||||||
|
"Strategy selector" -> "Strategy selector" : Load strategy and inject the \
|
||||||
|
cluster data model
|
||||||
|
end
|
||||||
|
"Strategy selector" -> "Decision Engine" : Return loaded Strategy
|
||||||
|
deactivate "Strategy selector"
|
||||||
|
|
||||||
|
"Decision Engine" -> "Strategy" : Execute the strategy
|
||||||
|
activate "Strategy"
|
||||||
|
"Strategy" -> "Strategy" : **pre_execute()**Checks if the strategy \
|
||||||
|
pre-requisites are all set.
|
||||||
|
"Strategy" -> "Strategy" : **do_execute()**Contains the logic of the strategy
|
||||||
|
"Strategy" -> "Strategy" : **post_execute()** Set the efficacy indicators
|
||||||
|
"Strategy" -> "Strategy" : Compute the global efficacy of the solution \
|
||||||
|
based on the provided efficacy indicators
|
||||||
|
"Strategy" -> "Decision Engine" : Return the solution
|
||||||
|
deactivate "Strategy"
|
||||||
|
|
||||||
|
"Decision Engine" -> "Planner" : Plan the solution that was computed by the \
|
||||||
|
strategy
|
||||||
|
activate "Planner"
|
||||||
|
"Planner" -> "Planner" : Store the planned solution as an action plan with its \
|
||||||
|
related actions and efficacy indicators
|
||||||
|
"Planner" --> "Decision Engine" : Done
|
||||||
|
deactivate "Planner"
|
||||||
|
"Decision Engine" -> "Decision Engine" : Update the audit state to SUCCEEDED
|
||||||
|
|
||||||
|
deactivate "Decision Engine"
|
||||||
|
|
||||||
|
@enduml
|
||||||
@@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
actor Administrator
|
actor Administrator
|
||||||
|
|
||||||
Administrator -> "Watcher CLI" : watcher action-plan-start <action_plan_uuid>
|
Administrator -> "Watcher CLI" : watcher actionplan start <action_plan_uuid>
|
||||||
|
|
||||||
"Watcher CLI" -> "Watcher API" : PATCH action_plan(state=TRIGGERED)
|
"Watcher CLI" -> "Watcher API" : PATCH action_plan(state=PENDING)
|
||||||
"Watcher API" -> "Watcher Database" : action_plan.state=TRIGGERED
|
"Watcher API" -> "Watcher Database" : action_plan.state=PENDING
|
||||||
|
|
||||||
"Watcher CLI" <-- "Watcher API" : HTTP 200
|
"Watcher CLI" <-- "Watcher API" : HTTP 200
|
||||||
|
|
||||||
|
|||||||
@@ -1,31 +1,54 @@
|
|||||||
@startuml
|
@startuml
|
||||||
|
|
||||||
"AMQP Bus" -> "Watcher Decision Engine" : trigger_audit(new_audit.uuid)
|
skinparam maxMessageSize 100
|
||||||
"Watcher Decision Engine" -> "Watcher Database" : update audit.state = ONGOING
|
|
||||||
"AMQP Bus" <[#blue]- "Watcher Decision Engine" : notify new audit state = ONGOING
|
"AMQP Bus" -> "Decision Engine" : trigger audit
|
||||||
"Watcher Decision Engine" -> "Watcher Database" : get audit parameters(goal, ...)
|
|
||||||
"Watcher Decision Engine" <-- "Watcher Database" : audit parameters(goal, ...)
|
activate "Decision Engine"
|
||||||
|
|
||||||
|
"Decision Engine" -> "Database" : update audit.state = ONGOING
|
||||||
|
"AMQP Bus" <[#blue]- "Decision Engine" : notify new audit state = ONGOING
|
||||||
|
"Decision Engine" -> "Database" : get audit parameters (goal, strategy, ...)
|
||||||
|
"Decision Engine" <-- "Database" : audit parameters (goal, strategy, ...)
|
||||||
|
"Decision Engine" --> "Decision Engine": select appropriate \
|
||||||
|
optimization strategy (via the Strategy Selector)
|
||||||
create Strategy
|
create Strategy
|
||||||
"Watcher Decision Engine" -[#red]> "Strategy": select appropriate\noptimization strategy
|
"Decision Engine" -> "Strategy" : execute()
|
||||||
loop while enough data to build cluster data model
|
activate "Strategy"
|
||||||
"Watcher Decision Engine" -> "Nova API" : get resource state (host, instance, ...)
|
create "Cluster Data Model Collector"
|
||||||
"Watcher Decision Engine" <-- "Nova API" : resource state
|
"Strategy" -> "Cluster Data Model Collector" : get cluster data model
|
||||||
end
|
|
||||||
"Watcher Decision Engine" -[#red]> "Watcher Decision Engine": build cluster_data_model
|
activate "Cluster Data Model Collector"
|
||||||
"Watcher Decision Engine" -> "Strategy" : execute(cluster_data_model)
|
loop while enough data to build cluster data model
|
||||||
|
"Cluster Data Model Collector" -> "Nova API" : get resource state (\
|
||||||
|
host, instance, ...)
|
||||||
|
"Cluster Data Model Collector" <-- "Nova API" : resource state
|
||||||
|
end
|
||||||
|
"Cluster Data Model Collector" -> "Strategy" : cluster data model
|
||||||
|
deactivate "Cluster Data Model Collector"
|
||||||
|
|
||||||
loop while enough history data for the strategy
|
loop while enough history data for the strategy
|
||||||
"Strategy" -> "Ceilometer API": get_aggregated_metrics\n(resource_id,meter_name,period,aggregate_method)
|
"Strategy" -> "Ceilometer API": get necessary metrics
|
||||||
"Strategy" <-- "Ceilometer API": aggregated metrics
|
"Strategy" <-- "Ceilometer API": aggregated metrics
|
||||||
end
|
end
|
||||||
"Strategy" -> "Strategy" : compute solution to achieve goal
|
"Strategy" -> "Strategy" : compute/set needed actions for the solution \
|
||||||
"Watcher Decision Engine" <-- "Strategy" : solution = array of actions (i.e. not scheduled yet)
|
so it achieves its goal
|
||||||
create "Watcher Planner"
|
"Strategy" -> "Strategy" : compute/set efficacy indicators for the solution
|
||||||
"Watcher Decision Engine" -[#red]> "Watcher Planner": select appropriate actions scheduler (i.e. Planner implementation)
|
"Strategy" -> "Strategy" : compute/set the solution global efficacy
|
||||||
"Watcher Decision Engine" -> "Watcher Planner": schedule(audit_id, solution)
|
"Decision Engine" <-- "Strategy" : solution (contains a list of unordered \
|
||||||
"Watcher Planner" -> "Watcher Planner": schedule actions according to\nscheduling rules/policies
|
actions alongside its efficacy indicators as well as its global efficacy)
|
||||||
"Watcher Decision Engine" <-- "Watcher Planner": new action_plan
|
deactivate "Strategy"
|
||||||
"Watcher Decision Engine" -> "Watcher Database" : save new action_plan in database
|
|
||||||
"Watcher Decision Engine" -> "Watcher Database" : update audit.state = SUCCEEDED
|
"Decision Engine" --> "Planner": load actions scheduler (i.e. Planner plugin)
|
||||||
"AMQP Bus" <[#blue]- "Watcher Decision Engine" : notify new audit state = SUCCEEDED
|
create "Planner"
|
||||||
|
"Decision Engine" -> "Planner": schedule()
|
||||||
|
"Planner" -> "Planner": schedule actions according to \
|
||||||
|
scheduling rules/policies
|
||||||
|
"Decision Engine" <-- "Planner": new action plan
|
||||||
|
"Decision Engine" -> "Database" : save new action plan in database
|
||||||
|
"Decision Engine" -> "Database" : update audit.state = SUCCEEDED
|
||||||
|
"AMQP Bus" <[#blue]- "Decision Engine" : notify new audit state = SUCCEEDED
|
||||||
|
|
||||||
|
deactivate "Decision Engine"
|
||||||
|
|
||||||
@enduml
|
@enduml
|
||||||
|
|||||||
123
doc/source/image_src/plantuml/watcher_db_schema_diagram.txt
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
@startuml
|
||||||
|
!define table(x) class x << (T,#FFAAAA) >>
|
||||||
|
!define primary_key(x) <u>x</u>
|
||||||
|
!define foreign_key(x) <i><u>x</u></i>
|
||||||
|
hide methods
|
||||||
|
hide stereotypes
|
||||||
|
|
||||||
|
table(goal) {
|
||||||
|
primary_key(id: Integer)
|
||||||
|
uuid : String[36]
|
||||||
|
name : String[63]
|
||||||
|
display_name : String[63]
|
||||||
|
|
||||||
|
created_at : DateTime
|
||||||
|
updated_at : DateTime
|
||||||
|
deleted_at : DateTime
|
||||||
|
deleted : Integer
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
table(strategy) {
|
||||||
|
primary_key(id: Integer)
|
||||||
|
foreign_key(goal_id : Integer)
|
||||||
|
uuid : String[36]
|
||||||
|
name : String[63]
|
||||||
|
display_name : String[63]
|
||||||
|
|
||||||
|
created_at : DateTime
|
||||||
|
updated_at : DateTime
|
||||||
|
deleted_at : DateTime
|
||||||
|
deleted : Integer
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
table(audit_template) {
|
||||||
|
primary_key(id: Integer)
|
||||||
|
foreign_key("goal_id : Integer")
|
||||||
|
foreign_key("strategy_id : Integer, nullable")
|
||||||
|
uuid : String[36]
|
||||||
|
name : String[63], nullable
|
||||||
|
description : String[255], nullable
|
||||||
|
host_aggregate : Integer, nullable
|
||||||
|
extra : JSONEncodedDict
|
||||||
|
version : String[15], nullable
|
||||||
|
|
||||||
|
created_at : DateTime
|
||||||
|
updated_at : DateTime
|
||||||
|
deleted_at : DateTime
|
||||||
|
deleted : Integer
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
table(audit) {
|
||||||
|
primary_key(id: Integer)
|
||||||
|
foreign_key("audit_template_id : Integer")
|
||||||
|
uuid : String[36]
|
||||||
|
audit_type : String[20]
|
||||||
|
state : String[20], nullable
|
||||||
|
deadline :DateTime, nullable
|
||||||
|
interval : Integer, nullable
|
||||||
|
|
||||||
|
created_at : DateTime
|
||||||
|
updated_at : DateTime
|
||||||
|
deleted_at : DateTime
|
||||||
|
deleted : Integer
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
table(action_plan) {
|
||||||
|
primary_key(id: Integer)
|
||||||
|
foreign_key("audit_id : Integer, nullable")
|
||||||
|
uuid : String[36]
|
||||||
|
first_action_id : Integer
|
||||||
|
state : String[20], nullable
|
||||||
|
global_efficacy : JSONEncodedDict, nullable
|
||||||
|
|
||||||
|
created_at : DateTime
|
||||||
|
updated_at : DateTime
|
||||||
|
deleted_at : DateTime
|
||||||
|
deleted : Integer
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
table(action) {
|
||||||
|
primary_key(id: Integer)
|
||||||
|
foreign_key("action_plan_id : Integer")
|
||||||
|
uuid : String[36]
|
||||||
|
action_type : String[255]
|
||||||
|
input_parameters : JSONEncodedDict, nullable
|
||||||
|
state : String[20], nullable
|
||||||
|
next : String[36], nullable
|
||||||
|
|
||||||
|
created_at : DateTime
|
||||||
|
updated_at : DateTime
|
||||||
|
deleted_at : DateTime
|
||||||
|
deleted : Integer
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
table(efficacy_indicator) {
|
||||||
|
primary_key(id: Integer)
|
||||||
|
foreign_key("action_plan_id : Integer")
|
||||||
|
uuid : String[36]
|
||||||
|
name : String[63]
|
||||||
|
description : String[255], nullable
|
||||||
|
unit : String[63], nullable
|
||||||
|
value : Numeric
|
||||||
|
|
||||||
|
created_at : DateTime
|
||||||
|
updated_at : DateTime
|
||||||
|
deleted_at : DateTime
|
||||||
|
deleted : Integer
|
||||||
|
}
|
||||||
|
|
||||||
|
"goal" <.. "strategy" : Foreign Key
|
||||||
|
"goal" <.. "audit_template" : Foreign Key
|
||||||
|
"strategy" <.. "audit_template" : Foreign Key
|
||||||
|
"audit_template" <.. "audit" : Foreign Key
|
||||||
|
"action_plan" <.. "action" : Foreign Key
|
||||||
|
"action_plan" <.. "efficacy_indicator" : Foreign Key
|
||||||
|
"audit" <.. "action_plan" : Foreign Key
|
||||||
|
|
||||||
|
@enduml
|
||||||
@@ -1,139 +1,600 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/PR-SVG-20010719/DTD/svg10.dtd">
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1146pt" height="548pt" viewBox="0 0 1146 548" version="1.1">
|
||||||
<svg width="58cm" height="28cm" viewBox="26 8 1147 549" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
<defs>
|
||||||
<g>
|
<g>
|
||||||
<rect style="fill: #ffffff" x="570" y="99" width="148.6" height="28"/>
|
<symbol overflow="visible" id="glyph0-0">
|
||||||
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="570" y="99" width="148.6" height="28"/>
|
<path style="stroke:none;" d="M 0.796875 2.828125 L 0.796875 -11.28125 L 8.796875 -11.28125 L 8.796875 2.828125 Z M 1.703125 1.9375 L 7.90625 1.9375 L 7.90625 -10.390625 L 1.703125 -10.390625 Z M 1.703125 1.9375 "/>
|
||||||
<text font-size="16" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="644.3" y="118">Audit Template</text>
|
</symbol>
|
||||||
</g>
|
<symbol overflow="visible" id="glyph0-1">
|
||||||
<g>
|
<path style="stroke:none;" d="M 8.546875 -2.125 L 3.84375 -2.125 L 3.109375 0 L 0.078125 0 L 4.40625 -11.671875 L 7.984375 -11.671875 L 12.3125 0 L 9.28125 0 Z M 4.59375 -4.296875 L 7.796875 -4.296875 L 6.203125 -8.9375 Z M 4.59375 -4.296875 "/>
|
||||||
<rect style="fill: #ffffff" x="212" y="140" width="177.5" height="28"/>
|
</symbol>
|
||||||
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="212" y="140" width="177.5" height="28"/>
|
<symbol overflow="visible" id="glyph0-2">
|
||||||
<text font-size="16" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="300.75" y="159">OpenStack Cluster</text>
|
<path style="stroke:none;" d="M 1.25 -3.40625 L 1.25 -8.75 L 4.0625 -8.75 L 4.0625 -7.875 C 4.0625 -7.40625 4.054688 -6.8125 4.046875 -6.09375 C 4.046875 -5.375 4.046875 -4.894531 4.046875 -4.65625 C 4.046875 -3.957031 4.0625 -3.453125 4.09375 -3.140625 C 4.132812 -2.828125 4.203125 -2.601562 4.296875 -2.46875 C 4.410156 -2.28125 4.554688 -2.132812 4.734375 -2.03125 C 4.921875 -1.9375 5.132812 -1.890625 5.375 -1.890625 C 5.957031 -1.890625 6.414062 -2.113281 6.75 -2.5625 C 7.082031 -3.007812 7.25 -3.632812 7.25 -4.4375 L 7.25 -8.75 L 10.046875 -8.75 L 10.046875 0 L 7.25 0 L 7.25 -1.265625 C 6.832031 -0.753906 6.382812 -0.375 5.90625 -0.125 C 5.4375 0.113281 4.921875 0.234375 4.359375 0.234375 C 3.347656 0.234375 2.578125 -0.078125 2.046875 -0.703125 C 1.515625 -1.328125 1.25 -2.226562 1.25 -3.40625 Z M 1.25 -3.40625 "/>
|
||||||
</g>
|
</symbol>
|
||||||
<g>
|
<symbol overflow="visible" id="glyph0-3">
|
||||||
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke-dasharray: 8; stroke: #000000" points="569.006,113 446,113 446,154 397.19,154 "/>
|
<path style="stroke:none;" d="M 7.296875 -7.46875 L 7.296875 -12.15625 L 10.109375 -12.15625 L 10.109375 0 L 7.296875 0 L 7.296875 -1.265625 C 6.910156 -0.753906 6.484375 -0.375 6.015625 -0.125 C 5.554688 0.113281 5.023438 0.234375 4.421875 0.234375 C 3.335938 0.234375 2.445312 -0.191406 1.75 -1.046875 C 1.0625 -1.910156 0.71875 -3.019531 0.71875 -4.375 C 0.71875 -5.71875 1.0625 -6.816406 1.75 -7.671875 C 2.445312 -8.535156 3.335938 -8.96875 4.421875 -8.96875 C 5.023438 -8.96875 5.554688 -8.84375 6.015625 -8.59375 C 6.484375 -8.351562 6.910156 -7.976562 7.296875 -7.46875 Z M 5.453125 -1.8125 C 6.054688 -1.8125 6.515625 -2.03125 6.828125 -2.46875 C 7.140625 -2.90625 7.296875 -3.539062 7.296875 -4.375 C 7.296875 -5.207031 7.140625 -5.84375 6.828125 -6.28125 C 6.515625 -6.71875 6.054688 -6.9375 5.453125 -6.9375 C 4.859375 -6.9375 4.40625 -6.71875 4.09375 -6.28125 C 3.78125 -5.84375 3.625 -5.207031 3.625 -4.375 C 3.625 -3.539062 3.78125 -2.90625 4.09375 -2.46875 C 4.40625 -2.03125 4.859375 -1.8125 5.453125 -1.8125 Z M 5.453125 -1.8125 "/>
|
||||||
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="409.838,149 393.838,154 409.838,159 "/>
|
</symbol>
|
||||||
<text font-size="12.7998" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="448" y="130.5">Applies to</text>
|
<symbol overflow="visible" id="glyph0-4">
|
||||||
</g>
|
<path style="stroke:none;" d="M 1.34375 -8.75 L 4.140625 -8.75 L 4.140625 0 L 1.34375 0 Z M 1.34375 -12.15625 L 4.140625 -12.15625 L 4.140625 -9.875 L 1.34375 -9.875 Z M 1.34375 -12.15625 "/>
|
||||||
<g>
|
</symbol>
|
||||||
<rect style="fill: #ffffff" x="615" y="227" width="58.4" height="28"/>
|
<symbol overflow="visible" id="glyph0-5">
|
||||||
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="615" y="227" width="58.4" height="28"/>
|
<path style="stroke:none;" d="M 4.40625 -11.234375 L 4.40625 -8.75 L 7.28125 -8.75 L 7.28125 -6.75 L 4.40625 -6.75 L 4.40625 -3.046875 C 4.40625 -2.640625 4.484375 -2.363281 4.640625 -2.21875 C 4.804688 -2.070312 5.128906 -2 5.609375 -2 L 7.046875 -2 L 7.046875 0 L 4.640625 0 C 3.535156 0 2.753906 -0.226562 2.296875 -0.6875 C 1.835938 -1.15625 1.609375 -1.941406 1.609375 -3.046875 L 1.609375 -6.75 L 0.21875 -6.75 L 0.21875 -8.75 L 1.609375 -8.75 L 1.609375 -11.234375 Z M 4.40625 -11.234375 "/>
|
||||||
<text font-size="16" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="644.2" y="246">Audit</text>
|
</symbol>
|
||||||
</g>
|
<symbol overflow="visible" id="glyph0-6">
|
||||||
<g>
|
<path style="stroke:none;" d=""/>
|
||||||
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke-dasharray: 8; stroke: #000000" points="644.2,225.996 644.2,188.497 644.3,188.497 644.3,133.705 "/>
|
</symbol>
|
||||||
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="649.3,146.353 644.3,130.353 639.3,146.353 "/>
|
<symbol overflow="visible" id="glyph0-7">
|
||||||
<text font-size="12.7998" style="fill: #000000;text-anchor:middle;font-family:monospace;font-style:normal;font-weight:normal" x="644.25" y="185.497">gets configuration from</text>
|
<path style="stroke:none;" d="M 0.078125 -11.671875 L 10.828125 -11.671875 L 10.828125 -9.390625 L 6.96875 -9.390625 L 6.96875 0 L 3.953125 0 L 3.953125 -9.390625 L 0.078125 -9.390625 Z M 0.078125 -11.671875 "/>
|
||||||
</g>
|
</symbol>
|
||||||
<g>
|
<symbol overflow="visible" id="glyph0-8">
|
||||||
<rect style="fill: #ffffff" x="916" y="9" width="50.45" height="28"/>
|
<path style="stroke:none;" d="M 10.078125 -4.40625 L 10.078125 -3.609375 L 3.546875 -3.609375 C 3.609375 -2.953125 3.84375 -2.457031 4.25 -2.125 C 4.65625 -1.800781 5.222656 -1.640625 5.953125 -1.640625 C 6.546875 -1.640625 7.148438 -1.722656 7.765625 -1.890625 C 8.378906 -2.066406 9.015625 -2.332031 9.671875 -2.6875 L 9.671875 -0.53125 C 9.003906 -0.28125 8.335938 -0.09375 7.671875 0.03125 C 7.015625 0.164062 6.359375 0.234375 5.703125 0.234375 C 4.117188 0.234375 2.882812 -0.164062 2 -0.96875 C 1.125 -1.78125 0.6875 -2.914062 0.6875 -4.375 C 0.6875 -5.800781 1.117188 -6.921875 1.984375 -7.734375 C 2.847656 -8.554688 4.035156 -8.96875 5.546875 -8.96875 C 6.921875 -8.96875 8.019531 -8.550781 8.84375 -7.71875 C 9.664062 -6.894531 10.078125 -5.789062 10.078125 -4.40625 Z M 7.203125 -5.328125 C 7.203125 -5.859375 7.046875 -6.285156 6.734375 -6.609375 C 6.429688 -6.941406 6.03125 -7.109375 5.53125 -7.109375 C 4.988281 -7.109375 4.546875 -6.953125 4.203125 -6.640625 C 3.867188 -6.335938 3.660156 -5.898438 3.578125 -5.328125 Z M 7.203125 -5.328125 "/>
|
||||||
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="916" y="9" width="50.45" height="28"/>
|
</symbol>
|
||||||
<text font-size="16" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="941.225" y="28">Goal</text>
|
<symbol overflow="visible" id="glyph0-9">
|
||||||
</g>
|
<path style="stroke:none;" d="M 9.453125 -7.296875 C 9.804688 -7.835938 10.226562 -8.25 10.71875 -8.53125 C 11.207031 -8.820312 11.742188 -8.96875 12.328125 -8.96875 C 13.328125 -8.96875 14.085938 -8.65625 14.609375 -8.03125 C 15.140625 -7.414062 15.40625 -6.515625 15.40625 -5.328125 L 15.40625 0 L 12.59375 0 L 12.59375 -4.5625 C 12.601562 -4.632812 12.609375 -4.707031 12.609375 -4.78125 C 12.609375 -4.851562 12.609375 -4.957031 12.609375 -5.09375 C 12.609375 -5.707031 12.515625 -6.15625 12.328125 -6.4375 C 12.148438 -6.71875 11.859375 -6.859375 11.453125 -6.859375 C 10.921875 -6.859375 10.507812 -6.640625 10.21875 -6.203125 C 9.9375 -5.765625 9.789062 -5.128906 9.78125 -4.296875 L 9.78125 0 L 6.96875 0 L 6.96875 -4.5625 C 6.96875 -5.53125 6.882812 -6.15625 6.71875 -6.4375 C 6.550781 -6.71875 6.253906 -6.859375 5.828125 -6.859375 C 5.285156 -6.859375 4.867188 -6.632812 4.578125 -6.1875 C 4.285156 -5.75 4.140625 -5.125 4.140625 -4.3125 L 4.140625 0 L 1.328125 0 L 1.328125 -8.75 L 4.140625 -8.75 L 4.140625 -7.46875 C 4.484375 -7.96875 4.878906 -8.34375 5.328125 -8.59375 C 5.773438 -8.84375 6.265625 -8.96875 6.796875 -8.96875 C 7.398438 -8.96875 7.929688 -8.820312 8.390625 -8.53125 C 8.859375 -8.238281 9.210938 -7.828125 9.453125 -7.296875 Z M 9.453125 -7.296875 "/>
|
||||||
<g>
|
</symbol>
|
||||||
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke-dasharray: 8; stroke: #000000" points="644.3,97.9927 644.3,72 941.225,72 941.225,44.7125 "/>
|
<symbol overflow="visible" id="glyph0-10">
|
||||||
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="946.225,57.3599 941.225,41.3599 936.225,57.3599 "/>
|
<path style="stroke:none;" d="M 4.140625 -1.265625 L 4.140625 3.328125 L 1.34375 3.328125 L 1.34375 -8.75 L 4.140625 -8.75 L 4.140625 -7.46875 C 4.523438 -7.976562 4.953125 -8.351562 5.421875 -8.59375 C 5.890625 -8.84375 6.429688 -8.96875 7.046875 -8.96875 C 8.117188 -8.96875 9 -8.535156 9.6875 -7.671875 C 10.382812 -6.816406 10.734375 -5.71875 10.734375 -4.375 C 10.734375 -3.019531 10.382812 -1.910156 9.6875 -1.046875 C 9 -0.191406 8.117188 0.234375 7.046875 0.234375 C 6.429688 0.234375 5.890625 0.113281 5.421875 -0.125 C 4.953125 -0.375 4.523438 -0.753906 4.140625 -1.265625 Z M 6 -6.9375 C 5.40625 -6.9375 4.945312 -6.710938 4.625 -6.265625 C 4.300781 -5.828125 4.140625 -5.195312 4.140625 -4.375 C 4.140625 -3.539062 4.300781 -2.90625 4.625 -2.46875 C 4.945312 -2.03125 5.40625 -1.8125 6 -1.8125 C 6.601562 -1.8125 7.0625 -2.03125 7.375 -2.46875 C 7.6875 -2.90625 7.84375 -3.539062 7.84375 -4.375 C 7.84375 -5.207031 7.6875 -5.84375 7.375 -6.28125 C 7.0625 -6.71875 6.601562 -6.9375 6 -6.9375 Z M 6 -6.9375 "/>
|
||||||
<text font-size="12.7998" style="fill: #000000;text-anchor:middle;font-family:monospace;font-style:normal;font-weight:normal" x="792.763" y="69">Achieves</text>
|
</symbol>
|
||||||
</g>
|
<symbol overflow="visible" id="glyph0-11">
|
||||||
<g>
|
<path style="stroke:none;" d="M 1.34375 -12.15625 L 4.140625 -12.15625 L 4.140625 0 L 1.34375 0 Z M 1.34375 -12.15625 "/>
|
||||||
<rect style="fill: #ffffff" x="495" y="367" width="112.45" height="28"/>
|
</symbol>
|
||||||
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="495" y="367" width="112.45" height="28"/>
|
<symbol overflow="visible" id="glyph0-12">
|
||||||
<text font-size="16" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="551.225" y="386">Action Plan</text>
|
<path style="stroke:none;" d="M 5.265625 -3.9375 C 4.679688 -3.9375 4.242188 -3.835938 3.953125 -3.640625 C 3.660156 -3.441406 3.515625 -3.148438 3.515625 -2.765625 C 3.515625 -2.410156 3.628906 -2.132812 3.859375 -1.9375 C 4.097656 -1.738281 4.429688 -1.640625 4.859375 -1.640625 C 5.378906 -1.640625 5.816406 -1.828125 6.171875 -2.203125 C 6.535156 -2.578125 6.71875 -3.050781 6.71875 -3.625 L 6.71875 -3.9375 Z M 9.546875 -5 L 9.546875 0 L 6.71875 0 L 6.71875 -1.296875 C 6.34375 -0.765625 5.921875 -0.375 5.453125 -0.125 C 4.984375 0.113281 4.414062 0.234375 3.75 0.234375 C 2.84375 0.234375 2.101562 -0.03125 1.53125 -0.5625 C 0.96875 -1.09375 0.6875 -1.78125 0.6875 -2.625 C 0.6875 -3.65625 1.039062 -4.410156 1.75 -4.890625 C 2.457031 -5.367188 3.566406 -5.609375 5.078125 -5.609375 L 6.71875 -5.609375 L 6.71875 -5.828125 C 6.71875 -6.265625 6.539062 -6.585938 6.1875 -6.796875 C 5.84375 -7.003906 5.300781 -7.109375 4.5625 -7.109375 C 3.96875 -7.109375 3.410156 -7.046875 2.890625 -6.921875 C 2.378906 -6.804688 1.898438 -6.628906 1.453125 -6.390625 L 1.453125 -8.515625 C 2.054688 -8.660156 2.660156 -8.769531 3.265625 -8.84375 C 3.867188 -8.925781 4.472656 -8.96875 5.078125 -8.96875 C 6.648438 -8.96875 7.785156 -8.65625 8.484375 -8.03125 C 9.191406 -7.40625 9.546875 -6.394531 9.546875 -5 Z M 9.546875 -5 "/>
|
||||||
</g>
|
</symbol>
|
||||||
<g>
|
<symbol overflow="visible" id="glyph0-13">
|
||||||
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke-dasharray: 8; stroke: #000000" points="644.2,256 644.2,298.5 523,298.5 523,356.295 "/>
|
<path style="stroke:none;" d="M 6.796875 -9.703125 C 5.878906 -9.703125 5.164062 -9.363281 4.65625 -8.6875 C 4.15625 -8.007812 3.90625 -7.054688 3.90625 -5.828125 C 3.90625 -4.597656 4.15625 -3.644531 4.65625 -2.96875 C 5.164062 -2.289062 5.878906 -1.953125 6.796875 -1.953125 C 7.722656 -1.953125 8.4375 -2.289062 8.9375 -2.96875 C 9.445312 -3.644531 9.703125 -4.597656 9.703125 -5.828125 C 9.703125 -7.054688 9.445312 -8.007812 8.9375 -8.6875 C 8.4375 -9.363281 7.722656 -9.703125 6.796875 -9.703125 Z M 6.796875 -11.875 C 8.671875 -11.875 10.140625 -11.335938 11.203125 -10.265625 C 12.265625 -9.191406 12.796875 -7.710938 12.796875 -5.828125 C 12.796875 -3.941406 12.265625 -2.457031 11.203125 -1.375 C 10.140625 -0.300781 8.671875 0.234375 6.796875 0.234375 C 4.929688 0.234375 3.460938 -0.300781 2.390625 -1.375 C 1.328125 -2.457031 0.796875 -3.941406 0.796875 -5.828125 C 0.796875 -7.710938 1.328125 -9.191406 2.390625 -10.265625 C 3.460938 -11.335938 4.929688 -11.875 6.796875 -11.875 Z M 6.796875 -11.875 "/>
|
||||||
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="518,343.647 523,359.647 528,343.647 "/>
|
</symbol>
|
||||||
<text font-size="12.7998" style="fill: #000000;text-anchor:middle;font-family:monospace;font-style:normal;font-weight:normal" x="583.6" y="295.5">Generates</text>
|
<symbol overflow="visible" id="glyph0-14">
|
||||||
</g>
|
<path style="stroke:none;" d="M 10.140625 -5.328125 L 10.140625 0 L 7.328125 0 L 7.328125 -4.078125 C 7.328125 -4.835938 7.3125 -5.359375 7.28125 -5.640625 C 7.25 -5.929688 7.191406 -6.144531 7.109375 -6.28125 C 6.992188 -6.457031 6.84375 -6.597656 6.65625 -6.703125 C 6.46875 -6.804688 6.253906 -6.859375 6.015625 -6.859375 C 5.429688 -6.859375 4.972656 -6.628906 4.640625 -6.171875 C 4.304688 -5.722656 4.140625 -5.101562 4.140625 -4.3125 L 4.140625 0 L 1.34375 0 L 1.34375 -8.75 L 4.140625 -8.75 L 4.140625 -7.46875 C 4.566406 -7.976562 5.015625 -8.351562 5.484375 -8.59375 C 5.960938 -8.84375 6.488281 -8.96875 7.0625 -8.96875 C 8.070312 -8.96875 8.835938 -8.65625 9.359375 -8.03125 C 9.878906 -7.414062 10.140625 -6.515625 10.140625 -5.328125 Z M 10.140625 -5.328125 "/>
|
||||||
<g>
|
</symbol>
|
||||||
<rect style="fill: #ffffff" x="682" y="471" width="67.45" height="28"/>
|
<symbol overflow="visible" id="glyph0-15">
|
||||||
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="682" y="471" width="67.45" height="28"/>
|
<path style="stroke:none;" d="M 9.59375 -11.296875 L 9.59375 -8.828125 C 8.945312 -9.117188 8.316406 -9.335938 7.703125 -9.484375 C 7.097656 -9.628906 6.523438 -9.703125 5.984375 -9.703125 C 5.265625 -9.703125 4.734375 -9.601562 4.390625 -9.40625 C 4.046875 -9.207031 3.875 -8.898438 3.875 -8.484375 C 3.875 -8.171875 3.988281 -7.925781 4.21875 -7.75 C 4.457031 -7.570312 4.878906 -7.421875 5.484375 -7.296875 L 6.765625 -7.046875 C 8.066406 -6.785156 8.988281 -6.390625 9.53125 -5.859375 C 10.082031 -5.328125 10.359375 -4.570312 10.359375 -3.59375 C 10.359375 -2.300781 9.972656 -1.335938 9.203125 -0.703125 C 8.441406 -0.078125 7.28125 0.234375 5.71875 0.234375 C 4.976562 0.234375 4.234375 0.160156 3.484375 0.015625 C 2.742188 -0.128906 2 -0.335938 1.25 -0.609375 L 1.25 -3.15625 C 2 -2.757812 2.71875 -2.457031 3.40625 -2.25 C 4.101562 -2.050781 4.773438 -1.953125 5.421875 -1.953125 C 6.078125 -1.953125 6.578125 -2.0625 6.921875 -2.28125 C 7.273438 -2.5 7.453125 -2.8125 7.453125 -3.21875 C 7.453125 -3.582031 7.332031 -3.863281 7.09375 -4.0625 C 6.863281 -4.257812 6.394531 -4.4375 5.6875 -4.59375 L 4.515625 -4.859375 C 3.347656 -5.109375 2.492188 -5.503906 1.953125 -6.046875 C 1.421875 -6.597656 1.15625 -7.335938 1.15625 -8.265625 C 1.15625 -9.421875 1.53125 -10.3125 2.28125 -10.9375 C 3.03125 -11.5625 4.109375 -11.875 5.515625 -11.875 C 6.148438 -11.875 6.804688 -11.828125 7.484375 -11.734375 C 8.160156 -11.640625 8.863281 -11.492188 9.59375 -11.296875 Z M 9.59375 -11.296875 "/>
|
||||||
<text font-size="16" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="715.725" y="490">Action</text>
|
</symbol>
|
||||||
</g>
|
<symbol overflow="visible" id="glyph0-16">
|
||||||
<g>
|
<path style="stroke:none;" d="M 8.421875 -8.484375 L 8.421875 -6.203125 C 8.035156 -6.460938 7.648438 -6.65625 7.265625 -6.78125 C 6.890625 -6.90625 6.492188 -6.96875 6.078125 -6.96875 C 5.296875 -6.96875 4.6875 -6.738281 4.25 -6.28125 C 3.820312 -5.820312 3.609375 -5.1875 3.609375 -4.375 C 3.609375 -3.550781 3.820312 -2.910156 4.25 -2.453125 C 4.6875 -2.003906 5.296875 -1.78125 6.078125 -1.78125 C 6.515625 -1.78125 6.929688 -1.84375 7.328125 -1.96875 C 7.722656 -2.101562 8.085938 -2.296875 8.421875 -2.546875 L 8.421875 -0.265625 C 7.984375 -0.0976562 7.535156 0.0234375 7.078125 0.109375 C 6.628906 0.191406 6.179688 0.234375 5.734375 0.234375 C 4.148438 0.234375 2.910156 -0.171875 2.015625 -0.984375 C 1.128906 -1.796875 0.6875 -2.925781 0.6875 -4.375 C 0.6875 -5.8125 1.128906 -6.9375 2.015625 -7.75 C 2.910156 -8.5625 4.148438 -8.96875 5.734375 -8.96875 C 6.191406 -8.96875 6.640625 -8.925781 7.078125 -8.84375 C 7.523438 -8.757812 7.972656 -8.640625 8.421875 -8.484375 Z M 8.421875 -8.484375 "/>
|
||||||
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="551.225,420.945 551.225,443 715.725,443 715.725,470.029 "/>
|
</symbol>
|
||||||
<polygon style="fill: #000000" points="551.225,395.773 556.025,409.773 551.225,423.773 546.425,409.773 "/>
|
<symbol overflow="visible" id="glyph0-17">
|
||||||
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="551.225,395.773 556.025,409.773 551.225,423.773 546.425,409.773 "/>
|
<path style="stroke:none;" d="M 1.34375 -12.15625 L 4.140625 -12.15625 L 4.140625 -5.546875 L 7.359375 -8.75 L 10.609375 -8.75 L 6.34375 -4.734375 L 10.953125 0 L 7.5625 0 L 4.140625 -3.65625 L 4.140625 0 L 1.34375 0 Z M 1.34375 -12.15625 "/>
|
||||||
<text font-size="12.7998" style="fill: #000000;text-anchor:middle;font-family:monospace;font-style:normal;font-weight:normal" x="633.475" y="440">is composed of</text>
|
</symbol>
|
||||||
<text font-size="12.7998" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="562.225" y="407.773"></text>
|
<symbol overflow="visible" id="glyph0-18">
|
||||||
<text font-size="12.7998" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="719.725" y="466.029"></text>
|
<path style="stroke:none;" d="M 10.71875 -0.640625 C 10.164062 -0.359375 9.585938 -0.144531 8.984375 0 C 8.390625 0.15625 7.769531 0.234375 7.125 0.234375 C 5.175781 0.234375 3.632812 -0.304688 2.5 -1.390625 C 1.363281 -2.484375 0.796875 -3.960938 0.796875 -5.828125 C 0.796875 -7.691406 1.363281 -9.164062 2.5 -10.25 C 3.632812 -11.332031 5.175781 -11.875 7.125 -11.875 C 7.769531 -11.875 8.390625 -11.800781 8.984375 -11.65625 C 9.585938 -11.507812 10.164062 -11.296875 10.71875 -11.015625 L 10.71875 -8.59375 C 10.164062 -8.976562 9.617188 -9.257812 9.078125 -9.4375 C 8.535156 -9.613281 7.960938 -9.703125 7.359375 -9.703125 C 6.285156 -9.703125 5.441406 -9.359375 4.828125 -8.671875 C 4.210938 -7.984375 3.90625 -7.035156 3.90625 -5.828125 C 3.90625 -4.617188 4.210938 -3.671875 4.828125 -2.984375 C 5.441406 -2.296875 6.285156 -1.953125 7.359375 -1.953125 C 7.960938 -1.953125 8.535156 -2.039062 9.078125 -2.21875 C 9.617188 -2.394531 10.164062 -2.675781 10.71875 -3.0625 Z M 10.71875 -0.640625 "/>
|
||||||
</g>
|
</symbol>
|
||||||
<g>
|
<symbol overflow="visible" id="glyph0-19">
|
||||||
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="749.45,499 749.45,517 862,517 862,485 749.45,485 "/>
|
<path style="stroke:none;" d="M 8.1875 -8.484375 L 8.1875 -6.359375 C 7.582031 -6.609375 7 -6.796875 6.4375 -6.921875 C 5.882812 -7.046875 5.363281 -7.109375 4.875 -7.109375 C 4.34375 -7.109375 3.945312 -7.039062 3.6875 -6.90625 C 3.425781 -6.769531 3.296875 -6.566406 3.296875 -6.296875 C 3.296875 -6.066406 3.394531 -5.890625 3.59375 -5.765625 C 3.789062 -5.648438 4.140625 -5.566406 4.640625 -5.515625 L 5.140625 -5.4375 C 6.566406 -5.257812 7.523438 -4.960938 8.015625 -4.546875 C 8.515625 -4.128906 8.765625 -3.472656 8.765625 -2.578125 C 8.765625 -1.648438 8.421875 -0.945312 7.734375 -0.46875 C 7.046875 0 6.019531 0.234375 4.65625 0.234375 C 4.082031 0.234375 3.484375 0.1875 2.859375 0.09375 C 2.242188 0 1.613281 -0.140625 0.96875 -0.328125 L 0.96875 -2.453125 C 1.519531 -2.179688 2.085938 -1.976562 2.671875 -1.84375 C 3.265625 -1.707031 3.863281 -1.640625 4.46875 -1.640625 C 5.007812 -1.640625 5.414062 -1.710938 5.6875 -1.859375 C 5.96875 -2.015625 6.109375 -2.238281 6.109375 -2.53125 C 6.109375 -2.78125 6.015625 -2.96875 5.828125 -3.09375 C 5.640625 -3.21875 5.257812 -3.3125 4.6875 -3.375 L 4.203125 -3.4375 C 2.953125 -3.59375 2.078125 -3.878906 1.578125 -4.296875 C 1.078125 -4.722656 0.828125 -5.367188 0.828125 -6.234375 C 0.828125 -7.160156 1.144531 -7.847656 1.78125 -8.296875 C 2.414062 -8.742188 3.390625 -8.96875 4.703125 -8.96875 C 5.222656 -8.96875 5.765625 -8.925781 6.328125 -8.84375 C 6.898438 -8.769531 7.519531 -8.648438 8.1875 -8.484375 Z M 8.1875 -8.484375 "/>
|
||||||
<text font-size="12.7998" style="fill: #000000;text-anchor:middle;font-family:monospace;font-style:normal;font-weight:normal" x="805.725" y="514">Next action</text>
|
</symbol>
|
||||||
<polygon style="fill: #000000" points="850.075,514 850.075,506 858.075,510 "/>
|
<symbol overflow="visible" id="glyph0-20">
|
||||||
<text font-size="12.7998" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="753.45" y="511"></text>
|
<path style="stroke:none;" d="M 7.84375 -6.375 C 7.601562 -6.488281 7.359375 -6.570312 7.109375 -6.625 C 6.867188 -6.675781 6.628906 -6.703125 6.390625 -6.703125 C 5.671875 -6.703125 5.113281 -6.472656 4.71875 -6.015625 C 4.332031 -5.554688 4.140625 -4.894531 4.140625 -4.03125 L 4.140625 0 L 1.34375 0 L 1.34375 -8.75 L 4.140625 -8.75 L 4.140625 -7.3125 C 4.503906 -7.882812 4.914062 -8.300781 5.375 -8.5625 C 5.84375 -8.832031 6.40625 -8.96875 7.0625 -8.96875 C 7.15625 -8.96875 7.253906 -8.960938 7.359375 -8.953125 C 7.472656 -8.941406 7.632812 -8.925781 7.84375 -8.90625 Z M 7.84375 -6.375 "/>
|
||||||
<text font-size="12.7998" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="753.45" y="482"></text>
|
</symbol>
|
||||||
</g>
|
<symbol overflow="visible" id="glyph0-21">
|
||||||
<g>
|
<path style="stroke:none;" d="M 11.953125 -0.875 C 11.203125 -0.507812 10.421875 -0.234375 9.609375 -0.046875 C 8.804688 0.140625 7.976562 0.234375 7.125 0.234375 C 5.175781 0.234375 3.632812 -0.304688 2.5 -1.390625 C 1.363281 -2.484375 0.796875 -3.960938 0.796875 -5.828125 C 0.796875 -7.703125 1.375 -9.175781 2.53125 -10.25 C 3.6875 -11.332031 5.269531 -11.875 7.28125 -11.875 C 8.0625 -11.875 8.804688 -11.800781 9.515625 -11.65625 C 10.222656 -11.507812 10.894531 -11.296875 11.53125 -11.015625 L 11.53125 -8.59375 C 10.875 -8.96875 10.222656 -9.242188 9.578125 -9.421875 C 8.941406 -9.609375 8.300781 -9.703125 7.65625 -9.703125 C 6.457031 -9.703125 5.53125 -9.363281 4.875 -8.6875 C 4.226562 -8.019531 3.90625 -7.066406 3.90625 -5.828125 C 3.90625 -4.585938 4.21875 -3.628906 4.84375 -2.953125 C 5.46875 -2.285156 6.359375 -1.953125 7.515625 -1.953125 C 7.828125 -1.953125 8.113281 -1.972656 8.375 -2.015625 C 8.644531 -2.054688 8.890625 -2.117188 9.109375 -2.203125 L 9.109375 -4.46875 L 7.265625 -4.46875 L 7.265625 -6.484375 L 11.953125 -6.484375 Z M 11.953125 -0.875 "/>
|
||||||
<ellipse style="fill: #ffffff" cx="1036" cy="219" rx="6" ry="6"/>
|
</symbol>
|
||||||
<ellipse style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #ff0000" cx="1036" cy="219" rx="6" ry="6"/>
|
<symbol overflow="visible" id="glyph0-22">
|
||||||
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #ff0000" x1="1012" y1="231" x2="1060" y2="231"/>
|
<path style="stroke:none;" d="M 5.515625 -6.96875 C 4.890625 -6.96875 4.414062 -6.742188 4.09375 -6.296875 C 3.769531 -5.847656 3.609375 -5.207031 3.609375 -4.375 C 3.609375 -3.53125 3.769531 -2.882812 4.09375 -2.4375 C 4.414062 -2 4.890625 -1.78125 5.515625 -1.78125 C 6.117188 -1.78125 6.582031 -2 6.90625 -2.4375 C 7.226562 -2.882812 7.390625 -3.53125 7.390625 -4.375 C 7.390625 -5.207031 7.226562 -5.847656 6.90625 -6.296875 C 6.582031 -6.742188 6.117188 -6.96875 5.515625 -6.96875 Z M 5.515625 -8.96875 C 7.015625 -8.96875 8.1875 -8.5625 9.03125 -7.75 C 9.882812 -6.9375 10.3125 -5.8125 10.3125 -4.375 C 10.3125 -2.9375 9.882812 -1.804688 9.03125 -0.984375 C 8.1875 -0.171875 7.015625 0.234375 5.515625 0.234375 C 4.003906 0.234375 2.820312 -0.171875 1.96875 -0.984375 C 1.113281 -1.804688 0.6875 -2.9375 0.6875 -4.375 C 0.6875 -5.8125 1.113281 -6.9375 1.96875 -7.75 C 2.820312 -8.5625 4.003906 -8.96875 5.515625 -8.96875 Z M 5.515625 -8.96875 "/>
|
||||||
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #ff0000" x1="1036" y1="225" x2="1036" y2="255"/>
|
</symbol>
|
||||||
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #ff0000" x1="1036" y1="255" x2="1012" y2="281"/>
|
<symbol overflow="visible" id="glyph0-23">
|
||||||
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #ff0000" x1="1036" y1="255" x2="1060" y2="281"/>
|
<path style="stroke:none;" d="M 1.46875 -11.671875 L 6.46875 -11.671875 C 7.945312 -11.671875 9.082031 -11.335938 9.875 -10.671875 C 10.675781 -10.015625 11.078125 -9.078125 11.078125 -7.859375 C 11.078125 -6.640625 10.675781 -5.695312 9.875 -5.03125 C 9.082031 -4.375 7.945312 -4.046875 6.46875 -4.046875 L 4.484375 -4.046875 L 4.484375 0 L 1.46875 0 Z M 4.484375 -9.484375 L 4.484375 -6.234375 L 6.140625 -6.234375 C 6.722656 -6.234375 7.171875 -6.375 7.484375 -6.65625 C 7.804688 -6.9375 7.96875 -7.335938 7.96875 -7.859375 C 7.96875 -8.378906 7.804688 -8.78125 7.484375 -9.0625 C 7.171875 -9.34375 6.722656 -9.484375 6.140625 -9.484375 Z M 4.484375 -9.484375 "/>
|
||||||
<text font-size="12.8" style="fill: #ff0000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="1036" y="304.9">
|
</symbol>
|
||||||
<tspan x="1036" y="304.9">Administrator</tspan>
|
<symbol overflow="visible" id="glyph0-24">
|
||||||
</text>
|
<path style="stroke:none;" d="M 5.75 -6.5 C 6.375 -6.5 6.820312 -6.613281 7.09375 -6.84375 C 7.375 -7.082031 7.515625 -7.46875 7.515625 -8 C 7.515625 -8.53125 7.375 -8.910156 7.09375 -9.140625 C 6.820312 -9.367188 6.375 -9.484375 5.75 -9.484375 L 4.484375 -9.484375 L 4.484375 -6.5 Z M 4.484375 -4.421875 L 4.484375 0 L 1.46875 0 L 1.46875 -11.671875 L 6.0625 -11.671875 C 7.601562 -11.671875 8.726562 -11.410156 9.4375 -10.890625 C 10.15625 -10.378906 10.515625 -9.566406 10.515625 -8.453125 C 10.515625 -7.679688 10.328125 -7.046875 9.953125 -6.546875 C 9.585938 -6.054688 9.03125 -5.691406 8.28125 -5.453125 C 8.6875 -5.359375 9.050781 -5.144531 9.375 -4.8125 C 9.707031 -4.488281 10.039062 -3.988281 10.375 -3.3125 L 12 0 L 8.796875 0 L 7.375 -2.90625 C 7.09375 -3.488281 6.800781 -3.882812 6.5 -4.09375 C 6.207031 -4.3125 5.816406 -4.421875 5.328125 -4.421875 Z M 4.484375 -4.421875 "/>
|
||||||
</g>
|
</symbol>
|
||||||
<g>
|
<symbol overflow="visible" id="glyph0-25">
|
||||||
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke-dasharray: 8; stroke: #000000" points="985.869,255 834.138,255 834.138,241 681.113,241 "/>
|
<path style="stroke:none;" d="M 7.296875 -1.484375 C 6.910156 -0.972656 6.484375 -0.597656 6.015625 -0.359375 C 5.554688 -0.117188 5.023438 0 4.421875 0 C 3.347656 0 2.460938 -0.421875 1.765625 -1.265625 C 1.066406 -2.109375 0.71875 -3.179688 0.71875 -4.484375 C 0.71875 -5.785156 1.066406 -6.851562 1.765625 -7.6875 C 2.460938 -8.53125 3.347656 -8.953125 4.421875 -8.953125 C 5.023438 -8.953125 5.554688 -8.832031 6.015625 -8.59375 C 6.484375 -8.351562 6.910156 -7.972656 7.296875 -7.453125 L 7.296875 -8.75 L 10.109375 -8.75 L 10.109375 -0.890625 C 10.109375 0.523438 9.664062 1.601562 8.78125 2.34375 C 7.894531 3.082031 6.609375 3.453125 4.921875 3.453125 C 4.367188 3.453125 3.835938 3.410156 3.328125 3.328125 C 2.816406 3.242188 2.304688 3.117188 1.796875 2.953125 L 1.796875 0.765625 C 2.285156 1.046875 2.765625 1.253906 3.234375 1.390625 C 3.703125 1.535156 4.171875 1.609375 4.640625 1.609375 C 5.554688 1.609375 6.226562 1.40625 6.65625 1 C 7.082031 0.601562 7.296875 -0.0234375 7.296875 -0.890625 Z M 5.453125 -6.9375 C 4.878906 -6.9375 4.429688 -6.722656 4.109375 -6.296875 C 3.785156 -5.867188 3.625 -5.265625 3.625 -4.484375 C 3.625 -3.679688 3.78125 -3.070312 4.09375 -2.65625 C 4.40625 -2.238281 4.859375 -2.03125 5.453125 -2.03125 C 6.035156 -2.03125 6.488281 -2.242188 6.8125 -2.671875 C 7.132812 -3.097656 7.296875 -3.703125 7.296875 -4.484375 C 7.296875 -5.265625 7.132812 -5.867188 6.8125 -6.296875 C 6.488281 -6.722656 6.035156 -6.9375 5.453125 -6.9375 Z M 5.453125 -6.9375 "/>
|
||||||
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="693.76,236 677.76,241 693.76,246 "/>
|
</symbol>
|
||||||
<text font-size="12.7998" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="836.138" y="245">Triggers</text>
|
<symbol overflow="visible" id="glyph0-26">
|
||||||
</g>
|
<path style="stroke:none;" d="M 0.203125 -8.75 L 3 -8.75 L 5.34375 -2.8125 L 7.34375 -8.75 L 10.140625 -8.75 L 6.46875 0.828125 C 6.09375 1.804688 5.660156 2.488281 5.171875 2.875 C 4.679688 3.257812 4.03125 3.453125 3.21875 3.453125 L 1.609375 3.453125 L 1.609375 1.625 L 2.484375 1.625 C 2.953125 1.625 3.296875 1.546875 3.515625 1.390625 C 3.734375 1.242188 3.898438 0.972656 4.015625 0.578125 L 4.09375 0.34375 Z M 0.203125 -8.75 "/>
|
||||||
<g>
|
</symbol>
|
||||||
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke-dasharray: 8; stroke: #000000" points="1038,192.993 882.3,192.993 882.3,113 725.305,113 "/>
|
<symbol overflow="visible" id="glyph0-27">
|
||||||
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="737.953,108 721.953,113 737.953,118 "/>
|
<path style="stroke:none;" d="M 1.46875 -11.671875 L 9.59375 -11.671875 L 9.59375 -9.390625 L 4.484375 -9.390625 L 4.484375 -7.21875 L 9.28125 -7.21875 L 9.28125 -4.953125 L 4.484375 -4.953125 L 4.484375 -2.28125 L 9.765625 -2.28125 L 9.765625 0 L 1.46875 0 Z M 1.46875 -11.671875 "/>
|
||||||
<text font-size="12.7998" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="884.3" y="149.997">Defines Audit configuration in</text>
|
</symbol>
|
||||||
</g>
|
<symbol overflow="visible" id="glyph0-28">
|
||||||
<g>
|
<path style="stroke:none;" d="M 7.109375 -12.15625 L 7.109375 -10.328125 L 5.5625 -10.328125 C 5.164062 -10.328125 4.890625 -10.253906 4.734375 -10.109375 C 4.578125 -9.960938 4.5 -9.710938 4.5 -9.359375 L 4.5 -8.75 L 7.703125 -8.75 L 7.703125 -9.359375 C 7.703125 -10.316406 7.96875 -11.019531 8.5 -11.46875 C 9.03125 -11.925781 9.851562 -12.15625 10.96875 -12.15625 L 13.109375 -12.15625 L 13.109375 -10.328125 L 11.5625 -10.328125 C 11.164062 -10.328125 10.894531 -10.253906 10.75 -10.109375 C 10.59375 -9.960938 10.515625 -9.710938 10.515625 -9.359375 L 10.515625 -8.75 L 16.5 -8.75 L 16.5 0 L 13.6875 0 L 13.6875 -6.75 L 10.515625 -6.75 L 10.515625 0 L 7.703125 0 L 7.703125 -6.75 L 4.5 -6.75 L 4.5 0 L 1.703125 0 L 1.703125 -6.75 L 0.3125 -6.75 L 0.3125 -8.75 L 1.703125 -8.75 L 1.703125 -9.359375 C 1.703125 -10.316406 1.96875 -11.019531 2.5 -11.46875 C 3.03125 -11.925781 3.851562 -12.15625 4.96875 -12.15625 Z M 13.6875 -12.15625 L 16.5 -12.15625 L 16.5 -9.875 L 13.6875 -9.875 Z M 13.6875 -12.15625 "/>
|
||||||
<ellipse style="fill: #ffffff" cx="103" cy="28" rx="6" ry="6"/>
|
</symbol>
|
||||||
<ellipse style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #ff0000" cx="103" cy="28" rx="6" ry="6"/>
|
<symbol overflow="visible" id="glyph0-29">
|
||||||
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #ff0000" x1="79" y1="40" x2="127" y2="40"/>
|
<path style="stroke:none;" d="M 1.46875 -11.671875 L 4.484375 -11.671875 L 4.484375 0 L 1.46875 0 Z M 1.46875 -11.671875 "/>
|
||||||
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #ff0000" x1="103" y1="34" x2="103" y2="64"/>
|
</symbol>
|
||||||
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #ff0000" x1="103" y1="64" x2="79" y2="90"/>
|
<symbol overflow="visible" id="glyph1-0">
|
||||||
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #ff0000" x1="103" y1="64" x2="127" y2="90"/>
|
<path style="stroke:none;" d="M 0.65625 2.296875 L 0.65625 -9.171875 L 7.15625 -9.171875 L 7.15625 2.296875 Z M 1.390625 1.578125 L 6.4375 1.578125 L 6.4375 -8.4375 L 1.390625 -8.4375 Z M 1.390625 1.578125 "/>
|
||||||
<text font-size="12.8" style="fill: #ff0000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="103" y="113.9">
|
</symbol>
|
||||||
<tspan x="103" y="113.9">Customer</tspan>
|
<symbol overflow="visible" id="glyph1-1">
|
||||||
</text>
|
<path style="stroke:none;" d="M 3.90625 -8.34375 L 2.5625 -3.5 L 5.265625 -3.5 Z M 3.140625 -9.484375 L 4.6875 -9.484375 L 7.59375 0 L 6.265625 0 L 5.5625 -2.46875 L 2.25 -2.46875 L 1.5625 0 L 0.234375 0 Z M 3.140625 -9.484375 "/>
|
||||||
</g>
|
</symbol>
|
||||||
<g>
|
<symbol overflow="visible" id="glyph1-2">
|
||||||
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke-dasharray: 8; stroke: #000000" points="137.683,64 300.75,64 300.75,133.295 "/>
|
<path style="stroke:none;" d="M 2.375 -0.890625 L 2.375 2.703125 L 1.203125 2.703125 L 1.203125 -7.109375 L 2.375 -7.109375 L 2.375 -6.203125 C 2.570312 -6.554688 2.832031 -6.820312 3.15625 -7 C 3.476562 -7.1875 3.851562 -7.28125 4.28125 -7.28125 C 5.132812 -7.28125 5.804688 -6.945312 6.296875 -6.28125 C 6.785156 -5.613281 7.03125 -4.691406 7.03125 -3.515625 C 7.03125 -2.367188 6.785156 -1.460938 6.296875 -0.796875 C 5.804688 -0.140625 5.132812 0.1875 4.28125 0.1875 C 3.84375 0.1875 3.460938 0.09375 3.140625 -0.09375 C 2.816406 -0.28125 2.5625 -0.546875 2.375 -0.890625 Z M 5.8125 -3.546875 C 5.8125 -4.453125 5.664062 -5.132812 5.375 -5.59375 C 5.09375 -6.0625 4.671875 -6.296875 4.109375 -6.296875 C 3.535156 -6.296875 3.101562 -6.0625 2.8125 -5.59375 C 2.519531 -5.132812 2.375 -4.453125 2.375 -3.546875 C 2.375 -2.648438 2.519531 -1.96875 2.8125 -1.5 C 3.101562 -1.039062 3.535156 -0.8125 4.109375 -0.8125 C 4.671875 -0.8125 5.09375 -1.039062 5.375 -1.5 C 5.664062 -1.957031 5.8125 -2.640625 5.8125 -3.546875 Z M 5.8125 -3.546875 "/>
|
||||||
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="295.75,120.647 300.75,136.647 305.75,120.647 "/>
|
</symbol>
|
||||||
<text font-size="12.7998" style="fill: #000000;text-anchor:middle;font-family:monospace;font-style:normal;font-weight:normal" x="219.217" y="61">Consumes resources</text>
|
<symbol overflow="visible" id="glyph1-3">
|
||||||
</g>
|
<path style="stroke:none;" d="M 4.0625 -2.578125 C 4.0625 -2.054688 4.15625 -1.660156 4.34375 -1.390625 C 4.539062 -1.117188 4.828125 -0.984375 5.203125 -0.984375 L 6.5625 -0.984375 L 6.5625 0 L 5.078125 0 C 4.378906 0 3.835938 -0.222656 3.453125 -0.671875 C 3.078125 -1.117188 2.890625 -1.753906 2.890625 -2.578125 L 2.890625 -9.03125 L 1.015625 -9.03125 L 1.015625 -9.953125 L 4.0625 -9.953125 Z M 4.0625 -2.578125 "/>
|
||||||
<g>
|
</symbol>
|
||||||
<rect style="fill: #ffffff" x="27" y="258" width="102.8" height="28"/>
|
<symbol overflow="visible" id="glyph1-4">
|
||||||
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="27" y="258" width="102.8" height="28"/>
|
<path style="stroke:none;" d="M 1.625 -7.109375 L 4.609375 -7.109375 L 4.609375 -0.90625 L 6.9375 -0.90625 L 6.9375 0 L 1.125 0 L 1.125 -0.90625 L 3.453125 -0.90625 L 3.453125 -6.203125 L 1.625 -6.203125 Z M 3.453125 -9.875 L 4.609375 -9.875 L 4.609375 -8.390625 L 3.453125 -8.390625 Z M 3.453125 -9.875 "/>
|
||||||
<text font-size="16" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="78.4" y="277">Resources</text>
|
</symbol>
|
||||||
</g>
|
<symbol overflow="visible" id="glyph1-5">
|
||||||
<g>
|
<path style="stroke:none;" d="M 7.0625 -3.84375 L 7.0625 -3.28125 L 2 -3.28125 L 2 -3.234375 C 2 -2.460938 2.203125 -1.863281 2.609375 -1.4375 C 3.015625 -1.019531 3.582031 -0.8125 4.3125 -0.8125 C 4.6875 -0.8125 5.078125 -0.867188 5.484375 -0.984375 C 5.890625 -1.097656 6.320312 -1.28125 6.78125 -1.53125 L 6.78125 -0.359375 C 6.34375 -0.179688 5.914062 -0.046875 5.5 0.046875 C 5.082031 0.140625 4.679688 0.1875 4.296875 0.1875 C 3.191406 0.1875 2.328125 -0.140625 1.703125 -0.796875 C 1.085938 -1.460938 0.78125 -2.378906 0.78125 -3.546875 C 0.78125 -4.679688 1.082031 -5.585938 1.6875 -6.265625 C 2.300781 -6.941406 3.113281 -7.28125 4.125 -7.28125 C 5.03125 -7.28125 5.742188 -6.972656 6.265625 -6.359375 C 6.796875 -5.742188 7.0625 -4.90625 7.0625 -3.84375 Z M 5.890625 -4.1875 C 5.867188 -4.875 5.703125 -5.394531 5.390625 -5.75 C 5.085938 -6.113281 4.648438 -6.296875 4.078125 -6.296875 C 3.515625 -6.296875 3.050781 -6.109375 2.6875 -5.734375 C 2.320312 -5.359375 2.109375 -4.84375 2.046875 -4.1875 Z M 5.890625 -4.1875 "/>
|
||||||
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="186.828,154 78.4,154 78.4,258 "/>
|
</symbol>
|
||||||
<polygon style="fill: #000000" points="212,154 198,158.8 184,154 198,149.2 "/>
|
<symbol overflow="visible" id="glyph1-6">
|
||||||
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="212,154 198,158.8 184,154 198,149.2 "/>
|
<path style="stroke:none;" d="M 6.171875 -6.859375 L 6.171875 -5.71875 C 5.835938 -5.914062 5.5 -6.0625 5.15625 -6.15625 C 4.820312 -6.25 4.476562 -6.296875 4.125 -6.296875 C 3.601562 -6.296875 3.210938 -6.210938 2.953125 -6.046875 C 2.691406 -5.878906 2.5625 -5.617188 2.5625 -5.265625 C 2.5625 -4.941406 2.65625 -4.703125 2.84375 -4.546875 C 3.039062 -4.390625 3.523438 -4.238281 4.296875 -4.09375 L 4.78125 -4 C 5.351562 -3.894531 5.785156 -3.675781 6.078125 -3.34375 C 6.378906 -3.007812 6.53125 -2.582031 6.53125 -2.0625 C 6.53125 -1.351562 6.28125 -0.800781 5.78125 -0.40625 C 5.289062 -0.0078125 4.597656 0.1875 3.703125 0.1875 C 3.359375 0.1875 2.992188 0.148438 2.609375 0.078125 C 2.222656 0.00390625 1.804688 -0.109375 1.359375 -0.265625 L 1.359375 -1.46875 C 1.785156 -1.238281 2.195312 -1.066406 2.59375 -0.953125 C 3 -0.847656 3.378906 -0.796875 3.734375 -0.796875 C 4.242188 -0.796875 4.640625 -0.898438 4.921875 -1.109375 C 5.210938 -1.316406 5.359375 -1.609375 5.359375 -1.984375 C 5.359375 -2.523438 4.835938 -2.898438 3.796875 -3.109375 L 3.75 -3.125 L 3.3125 -3.21875 C 2.632812 -3.34375 2.140625 -3.5625 1.828125 -3.875 C 1.523438 -4.1875 1.375 -4.609375 1.375 -5.140625 C 1.375 -5.828125 1.601562 -6.351562 2.0625 -6.71875 C 2.53125 -7.09375 3.191406 -7.28125 4.046875 -7.28125 C 4.421875 -7.28125 4.785156 -7.242188 5.140625 -7.171875 C 5.492188 -7.109375 5.835938 -7.003906 6.171875 -6.859375 Z M 6.171875 -6.859375 "/>
|
||||||
<text font-size="12.7998" style="fill: #000000;text-anchor:middle;font-family:monospace;font-style:normal;font-weight:normal" x="145.2" y="151"></text>
|
</symbol>
|
||||||
<text font-size="12.7998" style="fill: #000000;text-anchor:end;font-family:monospace;font-style:normal;font-weight:normal" x="180" y="151"></text>
|
<symbol overflow="visible" id="glyph1-7">
|
||||||
<text font-size="12.7998" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="82.4" y="254"></text>
|
<path style="stroke:none;" d=""/>
|
||||||
</g>
|
</symbol>
|
||||||
<g>
|
<symbol overflow="visible" id="glyph1-8">
|
||||||
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke-dasharray: 8; stroke: #000000" points="715.724,499 715.724,540 78.4,540 78.4,292.705 "/>
|
<path style="stroke:none;" d="M 3.890625 -9.125 L 3.890625 -7.109375 L 6.546875 -7.109375 L 6.546875 -6.203125 L 3.890625 -6.203125 L 3.890625 -2.34375 C 3.890625 -1.820312 3.988281 -1.457031 4.1875 -1.25 C 4.394531 -1.039062 4.742188 -0.9375 5.234375 -0.9375 L 6.546875 -0.9375 L 6.546875 0 L 5.125 0 C 4.25 0 3.628906 -0.171875 3.265625 -0.515625 C 2.910156 -0.867188 2.734375 -1.476562 2.734375 -2.34375 L 2.734375 -6.203125 L 0.828125 -6.203125 L 0.828125 -7.109375 L 2.734375 -7.109375 L 2.734375 -9.125 Z M 3.890625 -9.125 "/>
|
||||||
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="83.4,305.353 78.4,289.353 73.4,305.353 "/>
|
</symbol>
|
||||||
<text font-size="12.7998" style="fill: #000000;text-anchor:middle;font-family:monospace;font-style:normal;font-weight:normal" x="397.062" y="537">Modifies</text>
|
<symbol overflow="visible" id="glyph1-9">
|
||||||
</g>
|
<path style="stroke:none;" d="M 3.90625 -6.296875 C 3.3125 -6.296875 2.863281 -6.0625 2.5625 -5.59375 C 2.257812 -5.132812 2.109375 -4.453125 2.109375 -3.546875 C 2.109375 -2.648438 2.257812 -1.96875 2.5625 -1.5 C 2.863281 -1.039062 3.3125 -0.8125 3.90625 -0.8125 C 4.507812 -0.8125 4.960938 -1.039062 5.265625 -1.5 C 5.566406 -1.96875 5.71875 -2.648438 5.71875 -3.546875 C 5.71875 -4.453125 5.566406 -5.132812 5.265625 -5.59375 C 4.960938 -6.0625 4.507812 -6.296875 3.90625 -6.296875 Z M 3.90625 -7.28125 C 4.894531 -7.28125 5.648438 -6.957031 6.171875 -6.3125 C 6.691406 -5.675781 6.953125 -4.753906 6.953125 -3.546875 C 6.953125 -2.335938 6.691406 -1.410156 6.171875 -0.765625 C 5.648438 -0.128906 4.894531 0.1875 3.90625 0.1875 C 2.925781 0.1875 2.175781 -0.128906 1.65625 -0.765625 C 1.132812 -1.410156 0.875 -2.335938 0.875 -3.546875 C 0.875 -4.753906 1.132812 -5.675781 1.65625 -6.3125 C 2.175781 -6.957031 2.925781 -7.28125 3.90625 -7.28125 Z M 3.90625 -7.28125 "/>
|
||||||
<g>
|
</symbol>
|
||||||
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke-dasharray: 8; stroke: #000000" points="1036,309.94 1036,381 614.155,381 "/>
|
<symbol overflow="visible" id="glyph1-10">
|
||||||
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="626.803,376 610.803,381 626.803,386 "/>
|
<path style="stroke:none;" d="M 5.453125 -3.609375 C 5.453125 -4.484375 5.304688 -5.148438 5.015625 -5.609375 C 4.734375 -6.066406 4.316406 -6.296875 3.765625 -6.296875 C 3.191406 -6.296875 2.753906 -6.066406 2.453125 -5.609375 C 2.160156 -5.148438 2.015625 -4.484375 2.015625 -3.609375 C 2.015625 -2.734375 2.164062 -2.066406 2.46875 -1.609375 C 2.769531 -1.148438 3.207031 -0.921875 3.78125 -0.921875 C 4.320312 -0.921875 4.734375 -1.148438 5.015625 -1.609375 C 5.304688 -2.066406 5.453125 -2.734375 5.453125 -3.609375 Z M 6.609375 -0.453125 C 6.609375 0.609375 6.359375 1.414062 5.859375 1.96875 C 5.359375 2.519531 4.617188 2.796875 3.640625 2.796875 C 3.316406 2.796875 2.976562 2.765625 2.625 2.703125 C 2.269531 2.640625 1.921875 2.550781 1.578125 2.4375 L 1.578125 1.28125 C 1.992188 1.476562 2.367188 1.625 2.703125 1.71875 C 3.046875 1.8125 3.359375 1.859375 3.640625 1.859375 C 4.265625 1.859375 4.722656 1.6875 5.015625 1.34375 C 5.304688 1 5.453125 0.457031 5.453125 -0.28125 L 5.453125 -1.125 C 5.265625 -0.726562 5.007812 -0.429688 4.6875 -0.234375 C 4.363281 -0.046875 3.972656 0.046875 3.515625 0.046875 C 2.679688 0.046875 2.015625 -0.28125 1.515625 -0.9375 C 1.023438 -1.601562 0.78125 -2.492188 0.78125 -3.609375 C 0.78125 -4.722656 1.023438 -5.613281 1.515625 -6.28125 C 2.015625 -6.945312 2.679688 -7.28125 3.515625 -7.28125 C 3.972656 -7.28125 4.359375 -7.1875 4.671875 -7 C 4.984375 -6.820312 5.242188 -6.539062 5.453125 -6.15625 L 5.453125 -7.078125 L 6.609375 -7.078125 Z M 6.609375 -0.453125 "/>
|
||||||
<text font-size="12.7998" style="fill: #000000;text-anchor:middle;font-family:monospace;font-style:normal;font-weight:normal" x="821.725" y="378">Launches</text>
|
</symbol>
|
||||||
</g>
|
<symbol overflow="visible" id="glyph1-11">
|
||||||
<g>
|
<path style="stroke:none;" d="M 6.734375 -0.359375 C 6.421875 -0.179688 6.097656 -0.046875 5.765625 0.046875 C 5.429688 0.140625 5.09375 0.1875 4.75 0.1875 C 3.644531 0.1875 2.78125 -0.140625 2.15625 -0.796875 C 1.539062 -1.460938 1.234375 -2.378906 1.234375 -3.546875 C 1.234375 -4.710938 1.539062 -5.625 2.15625 -6.28125 C 2.78125 -6.945312 3.644531 -7.28125 4.75 -7.28125 C 5.09375 -7.28125 5.425781 -7.234375 5.75 -7.140625 C 6.070312 -7.054688 6.398438 -6.921875 6.734375 -6.734375 L 6.734375 -5.515625 C 6.421875 -5.785156 6.109375 -5.984375 5.796875 -6.109375 C 5.492188 -6.234375 5.144531 -6.296875 4.75 -6.296875 C 4.019531 -6.296875 3.457031 -6.054688 3.0625 -5.578125 C 2.664062 -5.109375 2.46875 -4.429688 2.46875 -3.546875 C 2.46875 -2.671875 2.664062 -1.992188 3.0625 -1.515625 C 3.457031 -1.046875 4.019531 -0.8125 4.75 -0.8125 C 5.15625 -0.8125 5.519531 -0.875 5.84375 -1 C 6.164062 -1.125 6.460938 -1.316406 6.734375 -1.578125 Z M 6.734375 -0.359375 "/>
|
||||||
<rect style="fill: #ffffff" x="1082.9" y="43.1" width="88.25" height="28"/>
|
</symbol>
|
||||||
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="1082.9" y="43.1" width="88.25" height="28"/>
|
<symbol overflow="visible" id="glyph1-12">
|
||||||
<text font-size="16" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="1127.02" y="62.1">Strategy</text>
|
<path style="stroke:none;" d="M 6.671875 -4.40625 L 6.671875 0 L 5.5 0 L 5.5 -4.40625 C 5.5 -5.039062 5.382812 -5.507812 5.15625 -5.8125 C 4.9375 -6.113281 4.585938 -6.265625 4.109375 -6.265625 C 3.554688 -6.265625 3.132812 -6.070312 2.84375 -5.6875 C 2.550781 -5.300781 2.40625 -4.742188 2.40625 -4.015625 L 2.40625 0 L 1.234375 0 L 1.234375 -7.109375 L 2.40625 -7.109375 L 2.40625 -6.046875 C 2.613281 -6.453125 2.894531 -6.757812 3.25 -6.96875 C 3.601562 -7.175781 4.023438 -7.28125 4.515625 -7.28125 C 5.234375 -7.28125 5.769531 -7.039062 6.125 -6.5625 C 6.488281 -6.09375 6.671875 -5.375 6.671875 -4.40625 Z M 6.671875 -4.40625 "/>
|
||||||
</g>
|
</symbol>
|
||||||
<g>
|
<symbol overflow="visible" id="glyph1-13">
|
||||||
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke-dasharray: 8; stroke: #000000" points="966.45,23 1020.17,23 1020.17,57.1 1075.22,57.1 "/>
|
<path style="stroke:none;" d="M 6.75 -9.875 L 6.75 -8.90625 L 5.421875 -8.90625 C 5.003906 -8.90625 4.710938 -8.816406 4.546875 -8.640625 C 4.378906 -8.472656 4.296875 -8.171875 4.296875 -7.734375 L 4.296875 -7.109375 L 6.75 -7.109375 L 6.75 -6.203125 L 4.296875 -6.203125 L 4.296875 0 L 3.140625 0 L 3.140625 -6.203125 L 1.234375 -6.203125 L 1.234375 -7.109375 L 3.140625 -7.109375 L 3.140625 -7.609375 C 3.140625 -8.378906 3.316406 -8.945312 3.671875 -9.3125 C 4.023438 -9.6875 4.582031 -9.875 5.34375 -9.875 Z M 6.75 -9.875 "/>
|
||||||
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="1062.57,62.1 1078.57,57.1 1062.57,52.1 "/>
|
</symbol>
|
||||||
<text font-size="12.7998" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="1022.17" y="37.05">uses</text>
|
<symbol overflow="visible" id="glyph1-14">
|
||||||
</g>
|
<path style="stroke:none;" d="M 1.234375 -2.6875 L 1.234375 -7.09375 L 2.40625 -7.09375 L 2.40625 -2.6875 C 2.40625 -2.050781 2.515625 -1.582031 2.734375 -1.28125 C 2.960938 -0.976562 3.316406 -0.828125 3.796875 -0.828125 C 4.347656 -0.828125 4.769531 -1.019531 5.0625 -1.40625 C 5.351562 -1.800781 5.5 -2.359375 5.5 -3.078125 L 5.5 -7.09375 L 6.671875 -7.09375 L 6.671875 0 L 5.5 0 L 5.5 -1.0625 C 5.289062 -0.65625 5.003906 -0.34375 4.640625 -0.125 C 4.285156 0.0820312 3.867188 0.1875 3.390625 0.1875 C 2.660156 0.1875 2.117188 -0.0507812 1.765625 -0.53125 C 1.410156 -1.007812 1.234375 -1.726562 1.234375 -2.6875 Z M 1.234375 -2.6875 "/>
|
||||||
|
</symbol>
|
||||||
|
<symbol overflow="visible" id="glyph1-15">
|
||||||
|
<path style="stroke:none;" d="M 7.328125 -5.640625 C 7.078125 -5.835938 6.820312 -5.976562 6.5625 -6.0625 C 6.3125 -6.15625 6.03125 -6.203125 5.71875 -6.203125 C 4.988281 -6.203125 4.429688 -5.972656 4.046875 -5.515625 C 3.660156 -5.054688 3.46875 -4.394531 3.46875 -3.53125 L 3.46875 0 L 2.296875 0 L 2.296875 -7.109375 L 3.46875 -7.109375 L 3.46875 -5.71875 C 3.664062 -6.21875 3.96875 -6.601562 4.375 -6.875 C 4.78125 -7.144531 5.257812 -7.28125 5.8125 -7.28125 C 6.09375 -7.28125 6.359375 -7.242188 6.609375 -7.171875 C 6.859375 -7.097656 7.097656 -6.988281 7.328125 -6.84375 Z M 7.328125 -5.640625 "/>
|
||||||
|
</symbol>
|
||||||
|
<symbol overflow="visible" id="glyph1-16">
|
||||||
|
<path style="stroke:none;" d="M 4.453125 -3.578125 L 4.0625 -3.578125 C 3.382812 -3.578125 2.875 -3.457031 2.53125 -3.21875 C 2.1875 -2.976562 2.015625 -2.617188 2.015625 -2.140625 C 2.015625 -1.710938 2.140625 -1.378906 2.390625 -1.140625 C 2.648438 -0.910156 3.007812 -0.796875 3.46875 -0.796875 C 4.113281 -0.796875 4.617188 -1.019531 4.984375 -1.46875 C 5.359375 -1.914062 5.546875 -2.53125 5.546875 -3.3125 L 5.546875 -3.578125 Z M 6.71875 -4.0625 L 6.71875 0 L 5.546875 0 L 5.546875 -1.046875 C 5.296875 -0.628906 4.976562 -0.316406 4.59375 -0.109375 C 4.21875 0.0859375 3.757812 0.1875 3.21875 0.1875 C 2.5 0.1875 1.921875 -0.015625 1.484375 -0.421875 C 1.054688 -0.835938 0.84375 -1.382812 0.84375 -2.0625 C 0.84375 -2.851562 1.109375 -3.453125 1.640625 -3.859375 C 2.171875 -4.273438 2.953125 -4.484375 3.984375 -4.484375 L 5.546875 -4.484375 L 5.546875 -4.671875 C 5.546875 -5.234375 5.398438 -5.644531 5.109375 -5.90625 C 4.828125 -6.164062 4.378906 -6.296875 3.765625 -6.296875 C 3.359375 -6.296875 2.953125 -6.238281 2.546875 -6.125 C 2.140625 -6.007812 1.742188 -5.84375 1.359375 -5.625 L 1.359375 -6.78125 C 1.796875 -6.945312 2.210938 -7.070312 2.609375 -7.15625 C 3.003906 -7.238281 3.390625 -7.28125 3.765625 -7.28125 C 4.347656 -7.28125 4.847656 -7.191406 5.265625 -7.015625 C 5.679688 -6.847656 6.019531 -6.585938 6.28125 -6.234375 C 6.4375 -6.023438 6.546875 -5.765625 6.609375 -5.453125 C 6.679688 -5.140625 6.71875 -4.675781 6.71875 -4.0625 Z M 6.71875 -4.0625 "/>
|
||||||
|
</symbol>
|
||||||
|
<symbol overflow="visible" id="glyph1-17">
|
||||||
|
<path style="stroke:none;" d="M 4.296875 -6.390625 C 4.429688 -6.691406 4.609375 -6.914062 4.828125 -7.0625 C 5.054688 -7.207031 5.328125 -7.28125 5.640625 -7.28125 C 6.210938 -7.28125 6.613281 -7.054688 6.84375 -6.609375 C 7.082031 -6.171875 7.203125 -5.34375 7.203125 -4.125 L 7.203125 0 L 6.140625 0 L 6.140625 -4.0625 C 6.140625 -5.070312 6.082031 -5.695312 5.96875 -5.9375 C 5.851562 -6.175781 5.648438 -6.296875 5.359375 -6.296875 C 5.015625 -6.296875 4.78125 -6.164062 4.65625 -5.90625 C 4.53125 -5.644531 4.46875 -5.03125 4.46875 -4.0625 L 4.46875 0 L 3.40625 0 L 3.40625 -4.0625 C 3.40625 -5.082031 3.34375 -5.707031 3.21875 -5.9375 C 3.101562 -6.175781 2.890625 -6.296875 2.578125 -6.296875 C 2.265625 -6.296875 2.046875 -6.164062 1.921875 -5.90625 C 1.804688 -5.644531 1.75 -5.03125 1.75 -4.0625 L 1.75 0 L 0.6875 0 L 0.6875 -7.109375 L 1.75 -7.109375 L 1.75 -6.5 C 1.894531 -6.75 2.070312 -6.941406 2.28125 -7.078125 C 2.488281 -7.210938 2.722656 -7.28125 2.984375 -7.28125 C 3.304688 -7.28125 3.570312 -7.207031 3.78125 -7.0625 C 4 -6.914062 4.171875 -6.691406 4.296875 -6.390625 Z M 4.296875 -6.390625 "/>
|
||||||
|
</symbol>
|
||||||
|
<symbol overflow="visible" id="glyph1-18">
|
||||||
|
<path style="stroke:none;" d="M 6.671875 -4.40625 L 6.671875 0 L 5.5 0 L 5.5 -4.40625 C 5.5 -5.039062 5.382812 -5.507812 5.15625 -5.8125 C 4.9375 -6.113281 4.585938 -6.265625 4.109375 -6.265625 C 3.554688 -6.265625 3.132812 -6.070312 2.84375 -5.6875 C 2.550781 -5.300781 2.40625 -4.742188 2.40625 -4.015625 L 2.40625 0 L 1.234375 0 L 1.234375 -9.875 L 2.40625 -9.875 L 2.40625 -6.046875 C 2.613281 -6.453125 2.894531 -6.757812 3.25 -6.96875 C 3.601562 -7.175781 4.023438 -7.28125 4.515625 -7.28125 C 5.234375 -7.28125 5.769531 -7.039062 6.125 -6.5625 C 6.488281 -6.09375 6.671875 -5.375 6.671875 -4.40625 Z M 6.671875 -4.40625 "/>
|
||||||
|
</symbol>
|
||||||
|
<symbol overflow="visible" id="glyph1-19">
|
||||||
|
<path style="stroke:none;" d="M 0.640625 -7.109375 L 1.84375 -7.109375 L 3.90625 -1.140625 L 5.984375 -7.109375 L 7.1875 -7.109375 L 4.671875 0 L 3.15625 0 Z M 0.640625 -7.109375 "/>
|
||||||
|
</symbol>
|
||||||
|
<symbol overflow="visible" id="glyph1-20">
|
||||||
|
<path style="stroke:none;" d="M 7.015625 -0.78125 C 6.671875 -0.46875 6.28125 -0.226562 5.84375 -0.0625 C 5.414062 0.101562 4.953125 0.1875 4.453125 0.1875 C 3.253906 0.1875 2.316406 -0.242188 1.640625 -1.109375 C 0.972656 -1.972656 0.640625 -3.179688 0.640625 -4.734375 C 0.640625 -6.273438 0.976562 -7.476562 1.65625 -8.34375 C 2.332031 -9.21875 3.273438 -9.65625 4.484375 -9.65625 C 4.878906 -9.65625 5.257812 -9.597656 5.625 -9.484375 C 5.988281 -9.367188 6.34375 -9.195312 6.6875 -8.96875 L 6.6875 -7.65625 C 6.34375 -7.976562 5.988281 -8.21875 5.625 -8.375 C 5.269531 -8.53125 4.890625 -8.609375 4.484375 -8.609375 C 3.648438 -8.609375 3.023438 -8.285156 2.609375 -7.640625 C 2.191406 -6.992188 1.984375 -6.023438 1.984375 -4.734375 C 1.984375 -3.410156 2.1875 -2.429688 2.59375 -1.796875 C 3 -1.171875 3.617188 -0.859375 4.453125 -0.859375 C 4.734375 -0.859375 4.976562 -0.890625 5.1875 -0.953125 C 5.40625 -1.015625 5.601562 -1.117188 5.78125 -1.265625 L 5.78125 -3.8125 L 4.40625 -3.8125 L 4.40625 -4.859375 L 7.015625 -4.859375 Z M 7.015625 -0.78125 "/>
|
||||||
|
</symbol>
|
||||||
|
<symbol overflow="visible" id="glyph1-21">
|
||||||
|
<path style="stroke:none;" d="M 5.453125 -6.203125 L 5.453125 -9.875 L 6.609375 -9.875 L 6.609375 0 L 5.453125 0 L 5.453125 -0.890625 C 5.253906 -0.546875 4.992188 -0.28125 4.671875 -0.09375 C 4.347656 0.09375 3.972656 0.1875 3.546875 0.1875 C 2.691406 0.1875 2.015625 -0.144531 1.515625 -0.8125 C 1.023438 -1.476562 0.78125 -2.398438 0.78125 -3.578125 C 0.78125 -4.734375 1.023438 -5.640625 1.515625 -6.296875 C 2.015625 -6.953125 2.691406 -7.28125 3.546875 -7.28125 C 3.972656 -7.28125 4.347656 -7.1875 4.671875 -7 C 5.003906 -6.820312 5.265625 -6.554688 5.453125 -6.203125 Z M 2.015625 -3.546875 C 2.015625 -2.640625 2.15625 -1.957031 2.4375 -1.5 C 2.726562 -1.039062 3.15625 -0.8125 3.71875 -0.8125 C 4.28125 -0.8125 4.707031 -1.039062 5 -1.5 C 5.300781 -1.96875 5.453125 -2.648438 5.453125 -3.546875 C 5.453125 -4.453125 5.300781 -5.132812 5 -5.59375 C 4.707031 -6.0625 4.28125 -6.296875 3.71875 -6.296875 C 3.15625 -6.296875 2.726562 -6.0625 2.4375 -5.59375 C 2.15625 -5.132812 2.015625 -4.453125 2.015625 -3.546875 Z M 2.015625 -3.546875 "/>
|
||||||
|
</symbol>
|
||||||
|
<symbol overflow="visible" id="glyph1-22">
|
||||||
|
<path style="stroke:none;" d="M 0.875 -9.484375 L 2.5 -9.484375 L 5.703125 -1.671875 L 5.703125 -9.484375 L 6.9375 -9.484375 L 6.9375 0 L 5.3125 0 L 2.125 -7.796875 L 2.125 0 L 0.875 0 Z M 0.875 -9.484375 "/>
|
||||||
|
</symbol>
|
||||||
|
<symbol overflow="visible" id="glyph1-23">
|
||||||
|
<path style="stroke:none;" d="M 7.09375 -7.109375 L 4.546875 -3.703125 L 7.34375 0 L 6 0 L 3.90625 -2.84375 L 1.828125 0 L 0.484375 0 L 3.28125 -3.703125 L 0.734375 -7.109375 L 2.03125 -7.109375 L 3.90625 -4.53125 L 5.78125 -7.109375 Z M 7.09375 -7.109375 "/>
|
||||||
|
</symbol>
|
||||||
|
<symbol overflow="visible" id="glyph1-24">
|
||||||
|
<path style="stroke:none;" d="M 0.296875 -9.484375 L 7.53125 -9.484375 L 7.53125 -8.390625 L 4.5625 -8.390625 L 4.5625 0 L 3.28125 0 L 3.28125 -8.390625 L 0.296875 -8.390625 Z M 0.296875 -9.484375 "/>
|
||||||
|
</symbol>
|
||||||
|
<symbol overflow="visible" id="glyph1-25">
|
||||||
|
<path style="stroke:none;" d="M 2.765625 -1.046875 C 3.847656 -1.046875 4.601562 -1.3125 5.03125 -1.84375 C 5.457031 -2.375 5.671875 -3.335938 5.671875 -4.734375 C 5.671875 -6.128906 5.457031 -7.09375 5.03125 -7.625 C 4.601562 -8.15625 3.847656 -8.421875 2.765625 -8.421875 L 2.15625 -8.421875 L 2.15625 -1.046875 Z M 2.796875 -9.484375 C 4.242188 -9.484375 5.304688 -9.097656 5.984375 -8.328125 C 6.671875 -7.554688 7.015625 -6.359375 7.015625 -4.734375 C 7.015625 -3.109375 6.671875 -1.910156 5.984375 -1.140625 C 5.304688 -0.378906 4.242188 0 2.796875 0 L 0.875 0 L 0.875 -9.484375 Z M 2.796875 -9.484375 "/>
|
||||||
|
</symbol>
|
||||||
|
<symbol overflow="visible" id="glyph1-26">
|
||||||
|
<path style="stroke:none;" d="M 6.8125 -0.34375 C 6.488281 -0.164062 6.15625 -0.0351562 5.8125 0.046875 C 5.46875 0.140625 5.101562 0.1875 4.71875 0.1875 C 3.5 0.1875 2.550781 -0.238281 1.875 -1.09375 C 1.207031 -1.957031 0.875 -3.171875 0.875 -4.734375 C 0.875 -6.273438 1.210938 -7.476562 1.890625 -8.34375 C 2.566406 -9.21875 3.507812 -9.65625 4.71875 -9.65625 C 5.101562 -9.65625 5.46875 -9.609375 5.8125 -9.515625 C 6.15625 -9.429688 6.488281 -9.300781 6.8125 -9.125 L 6.8125 -7.8125 C 6.5 -8.070312 6.160156 -8.269531 5.796875 -8.40625 C 5.441406 -8.539062 5.082031 -8.609375 4.71875 -8.609375 C 3.882812 -8.609375 3.257812 -8.285156 2.84375 -7.640625 C 2.425781 -6.992188 2.21875 -6.023438 2.21875 -4.734375 C 2.21875 -3.429688 2.425781 -2.457031 2.84375 -1.8125 C 3.257812 -1.175781 3.882812 -0.859375 4.71875 -0.859375 C 5.09375 -0.859375 5.457031 -0.925781 5.8125 -1.0625 C 6.164062 -1.195312 6.5 -1.394531 6.8125 -1.65625 Z M 6.8125 -0.34375 "/>
|
||||||
|
</symbol>
|
||||||
|
<symbol overflow="visible" id="glyph1-27">
|
||||||
|
<path style="stroke:none;" d="M 0.546875 -9.484375 L 2.265625 -9.484375 L 3.890625 -4.65625 L 5.546875 -9.484375 L 7.265625 -9.484375 L 7.265625 0 L 6.078125 0 L 6.078125 -8.375 L 4.390625 -3.375 L 3.421875 -3.375 L 1.734375 -8.375 L 1.734375 0 L 0.546875 0 Z M 0.546875 -9.484375 "/>
|
||||||
|
</symbol>
|
||||||
|
<symbol overflow="visible" id="glyph1-28">
|
||||||
|
<path style="stroke:none;" d="M 1.359375 -9.484375 L 2.65625 -9.484375 L 2.65625 -1.078125 L 7.234375 -1.078125 L 7.234375 0 L 1.359375 0 Z M 1.359375 -9.484375 "/>
|
||||||
|
</symbol>
|
||||||
|
<symbol overflow="visible" id="glyph2-0">
|
||||||
|
<path style="stroke:none;" d="M 0.640625 2.296875 L 0.640625 -9.171875 L 7.140625 -9.171875 L 7.140625 2.296875 Z M 1.375 1.578125 L 6.421875 1.578125 L 6.421875 -8.4375 L 1.375 -8.4375 Z M 1.375 1.578125 "/>
|
||||||
|
</symbol>
|
||||||
|
<symbol overflow="visible" id="glyph2-1">
|
||||||
|
<path style="stroke:none;" d="M 6.9375 -1.734375 L 3.125 -1.734375 L 2.515625 0 L 0.0625 0 L 3.578125 -9.484375 L 6.484375 -9.484375 L 10 0 L 7.546875 0 Z M 3.734375 -3.484375 L 6.328125 -3.484375 L 5.03125 -7.25 Z M 3.734375 -3.484375 "/>
|
||||||
|
</symbol>
|
||||||
|
<symbol overflow="visible" id="glyph2-2">
|
||||||
|
<path style="stroke:none;" d="M 5.921875 -6.0625 L 5.921875 -9.875 L 8.21875 -9.875 L 8.21875 0 L 5.921875 0 L 5.921875 -1.03125 C 5.609375 -0.613281 5.265625 -0.304688 4.890625 -0.109375 C 4.515625 0.0859375 4.082031 0.1875 3.59375 0.1875 C 2.707031 0.1875 1.984375 -0.160156 1.421875 -0.859375 C 0.859375 -1.554688 0.578125 -2.453125 0.578125 -3.546875 C 0.578125 -4.640625 0.859375 -5.535156 1.421875 -6.234375 C 1.984375 -6.929688 2.707031 -7.28125 3.59375 -7.28125 C 4.082031 -7.28125 4.515625 -7.179688 4.890625 -6.984375 C 5.265625 -6.785156 5.609375 -6.476562 5.921875 -6.0625 Z M 4.4375 -1.46875 C 4.914062 -1.46875 5.28125 -1.644531 5.53125 -2 C 5.789062 -2.351562 5.921875 -2.867188 5.921875 -3.546875 C 5.921875 -4.222656 5.789062 -4.738281 5.53125 -5.09375 C 5.28125 -5.445312 4.914062 -5.625 4.4375 -5.625 C 3.945312 -5.625 3.570312 -5.445312 3.3125 -5.09375 C 3.0625 -4.738281 2.9375 -4.222656 2.9375 -3.546875 C 2.9375 -2.867188 3.0625 -2.351562 3.3125 -2 C 3.570312 -1.644531 3.945312 -1.46875 4.4375 -1.46875 Z M 4.4375 -1.46875 "/>
|
||||||
|
</symbol>
|
||||||
|
<symbol overflow="visible" id="glyph2-3">
|
||||||
|
<path style="stroke:none;" d="M 7.6875 -5.921875 C 7.96875 -6.367188 8.304688 -6.707031 8.703125 -6.9375 C 9.097656 -7.164062 9.535156 -7.28125 10.015625 -7.28125 C 10.828125 -7.28125 11.445312 -7.023438 11.875 -6.515625 C 12.300781 -6.015625 12.515625 -5.285156 12.515625 -4.328125 L 12.515625 0 L 10.234375 0 L 10.234375 -3.703125 C 10.234375 -3.765625 10.234375 -3.820312 10.234375 -3.875 C 10.242188 -3.9375 10.25 -4.019531 10.25 -4.125 C 10.25 -4.632812 10.171875 -5 10.015625 -5.21875 C 9.867188 -5.445312 9.632812 -5.5625 9.3125 -5.5625 C 8.875 -5.5625 8.535156 -5.382812 8.296875 -5.03125 C 8.066406 -4.675781 7.945312 -4.160156 7.9375 -3.484375 L 7.9375 0 L 5.65625 0 L 5.65625 -3.703125 C 5.65625 -4.492188 5.585938 -5 5.453125 -5.21875 C 5.316406 -5.445312 5.078125 -5.5625 4.734375 -5.5625 C 4.296875 -5.5625 3.957031 -5.382812 3.71875 -5.03125 C 3.476562 -4.675781 3.359375 -4.164062 3.359375 -3.5 L 3.359375 0 L 1.078125 0 L 1.078125 -7.109375 L 3.359375 -7.109375 L 3.359375 -6.0625 C 3.640625 -6.46875 3.960938 -6.769531 4.328125 -6.96875 C 4.691406 -7.175781 5.085938 -7.28125 5.515625 -7.28125 C 6.015625 -7.28125 6.453125 -7.160156 6.828125 -6.921875 C 7.203125 -6.679688 7.488281 -6.347656 7.6875 -5.921875 Z M 7.6875 -5.921875 "/>
|
||||||
|
</symbol>
|
||||||
|
<symbol overflow="visible" id="glyph2-4">
|
||||||
|
<path style="stroke:none;" d="M 1.09375 -7.109375 L 3.359375 -7.109375 L 3.359375 0 L 1.09375 0 Z M 1.09375 -9.875 L 3.359375 -9.875 L 3.359375 -8.03125 L 1.09375 -8.03125 Z M 1.09375 -9.875 "/>
|
||||||
|
</symbol>
|
||||||
|
<symbol overflow="visible" id="glyph2-5">
|
||||||
|
<path style="stroke:none;" d="M 8.234375 -4.328125 L 8.234375 0 L 5.953125 0 L 5.953125 -3.3125 C 5.953125 -3.925781 5.9375 -4.347656 5.90625 -4.578125 C 5.882812 -4.816406 5.835938 -4.988281 5.765625 -5.09375 C 5.679688 -5.238281 5.5625 -5.351562 5.40625 -5.4375 C 5.257812 -5.519531 5.085938 -5.5625 4.890625 -5.5625 C 4.410156 -5.5625 4.035156 -5.378906 3.765625 -5.015625 C 3.492188 -4.648438 3.359375 -4.144531 3.359375 -3.5 L 3.359375 0 L 1.09375 0 L 1.09375 -7.109375 L 3.359375 -7.109375 L 3.359375 -6.0625 C 3.703125 -6.476562 4.066406 -6.785156 4.453125 -6.984375 C 4.835938 -7.179688 5.265625 -7.28125 5.734375 -7.28125 C 6.554688 -7.28125 7.175781 -7.023438 7.59375 -6.515625 C 8.019531 -6.015625 8.234375 -5.285156 8.234375 -4.328125 Z M 8.234375 -4.328125 "/>
|
||||||
|
</symbol>
|
||||||
|
<symbol overflow="visible" id="glyph2-6">
|
||||||
|
<path style="stroke:none;" d="M 6.640625 -6.890625 L 6.640625 -5.15625 C 6.160156 -5.363281 5.691406 -5.515625 5.234375 -5.609375 C 4.785156 -5.710938 4.359375 -5.765625 3.953125 -5.765625 C 3.523438 -5.765625 3.203125 -5.710938 2.984375 -5.609375 C 2.773438 -5.503906 2.671875 -5.335938 2.671875 -5.109375 C 2.671875 -4.929688 2.75 -4.789062 2.90625 -4.6875 C 3.070312 -4.59375 3.359375 -4.519531 3.765625 -4.46875 L 4.171875 -4.421875 C 5.335938 -4.273438 6.117188 -4.03125 6.515625 -3.6875 C 6.921875 -3.351562 7.125 -2.820312 7.125 -2.09375 C 7.125 -1.332031 6.84375 -0.757812 6.28125 -0.375 C 5.726562 0 4.894531 0.1875 3.78125 0.1875 C 3.3125 0.1875 2.828125 0.148438 2.328125 0.078125 C 1.828125 0.00390625 1.3125 -0.109375 0.78125 -0.265625 L 0.78125 -1.984375 C 1.226562 -1.765625 1.691406 -1.597656 2.171875 -1.484375 C 2.648438 -1.378906 3.132812 -1.328125 3.625 -1.328125 C 4.070312 -1.328125 4.40625 -1.382812 4.625 -1.5 C 4.851562 -1.625 4.96875 -1.8125 4.96875 -2.0625 C 4.96875 -2.257812 4.890625 -2.40625 4.734375 -2.5 C 4.578125 -2.601562 4.269531 -2.6875 3.8125 -2.75 L 3.40625 -2.796875 C 2.394531 -2.921875 1.6875 -3.15625 1.28125 -3.5 C 0.875 -3.84375 0.671875 -4.363281 0.671875 -5.0625 C 0.671875 -5.8125 0.925781 -6.367188 1.4375 -6.734375 C 1.957031 -7.097656 2.753906 -7.28125 3.828125 -7.28125 C 4.242188 -7.28125 4.679688 -7.25 5.140625 -7.1875 C 5.597656 -7.125 6.097656 -7.023438 6.640625 -6.890625 Z M 6.640625 -6.890625 "/>
|
||||||
|
</symbol>
|
||||||
|
<symbol overflow="visible" id="glyph2-7">
|
||||||
|
<path style="stroke:none;" d="M 3.578125 -9.125 L 3.578125 -7.109375 L 5.921875 -7.109375 L 5.921875 -5.484375 L 3.578125 -5.484375 L 3.578125 -2.46875 C 3.578125 -2.132812 3.640625 -1.910156 3.765625 -1.796875 C 3.898438 -1.679688 4.160156 -1.625 4.546875 -1.625 L 5.71875 -1.625 L 5.71875 0 L 3.765625 0 C 2.867188 0 2.234375 -0.1875 1.859375 -0.5625 C 1.484375 -0.9375 1.296875 -1.570312 1.296875 -2.46875 L 1.296875 -5.484375 L 0.171875 -5.484375 L 0.171875 -7.109375 L 1.296875 -7.109375 L 1.296875 -9.125 Z M 3.578125 -9.125 "/>
|
||||||
|
</symbol>
|
||||||
|
<symbol overflow="visible" id="glyph2-8">
|
||||||
|
<path style="stroke:none;" d="M 6.375 -5.171875 C 6.175781 -5.265625 5.976562 -5.332031 5.78125 -5.375 C 5.582031 -5.425781 5.382812 -5.453125 5.1875 -5.453125 C 4.601562 -5.453125 4.148438 -5.265625 3.828125 -4.890625 C 3.515625 -4.515625 3.359375 -3.976562 3.359375 -3.28125 L 3.359375 0 L 1.09375 0 L 1.09375 -7.109375 L 3.359375 -7.109375 L 3.359375 -5.9375 C 3.648438 -6.40625 3.984375 -6.742188 4.359375 -6.953125 C 4.742188 -7.171875 5.203125 -7.28125 5.734375 -7.28125 C 5.804688 -7.28125 5.882812 -7.273438 5.96875 -7.265625 C 6.0625 -7.265625 6.191406 -7.253906 6.359375 -7.234375 Z M 6.375 -5.171875 "/>
|
||||||
|
</symbol>
|
||||||
|
<symbol overflow="visible" id="glyph2-9">
|
||||||
|
<path style="stroke:none;" d="M 4.28125 -3.203125 C 3.800781 -3.203125 3.441406 -3.117188 3.203125 -2.953125 C 2.960938 -2.796875 2.84375 -2.5625 2.84375 -2.25 C 2.84375 -1.957031 2.9375 -1.726562 3.125 -1.5625 C 3.320312 -1.40625 3.59375 -1.328125 3.9375 -1.328125 C 4.363281 -1.328125 4.722656 -1.476562 5.015625 -1.78125 C 5.304688 -2.09375 5.453125 -2.476562 5.453125 -2.9375 L 5.453125 -3.203125 Z M 7.75 -4.0625 L 7.75 0 L 5.453125 0 L 5.453125 -1.046875 C 5.148438 -0.617188 4.804688 -0.304688 4.421875 -0.109375 C 4.046875 0.0859375 3.585938 0.1875 3.046875 0.1875 C 2.304688 0.1875 1.707031 -0.0234375 1.25 -0.453125 C 0.789062 -0.890625 0.5625 -1.453125 0.5625 -2.140625 C 0.5625 -2.972656 0.847656 -3.582031 1.421875 -3.96875 C 1.992188 -4.351562 2.894531 -4.546875 4.125 -4.546875 L 5.453125 -4.546875 L 5.453125 -4.734375 C 5.453125 -5.085938 5.3125 -5.347656 5.03125 -5.515625 C 4.75 -5.679688 4.304688 -5.765625 3.703125 -5.765625 C 3.222656 -5.765625 2.769531 -5.71875 2.34375 -5.625 C 1.925781 -5.53125 1.539062 -5.382812 1.1875 -5.1875 L 1.1875 -6.921875 C 1.664062 -7.035156 2.148438 -7.125 2.640625 -7.1875 C 3.140625 -7.25 3.632812 -7.28125 4.125 -7.28125 C 5.40625 -7.28125 6.328125 -7.023438 6.890625 -6.515625 C 7.460938 -6.015625 7.75 -5.195312 7.75 -4.0625 Z M 7.75 -4.0625 "/>
|
||||||
|
</symbol>
|
||||||
|
<symbol overflow="visible" id="glyph2-10">
|
||||||
|
<path style="stroke:none;" d="M 4.46875 -5.65625 C 3.96875 -5.65625 3.582031 -5.472656 3.3125 -5.109375 C 3.050781 -4.753906 2.921875 -4.234375 2.921875 -3.546875 C 2.921875 -2.867188 3.050781 -2.347656 3.3125 -1.984375 C 3.582031 -1.617188 3.96875 -1.4375 4.46875 -1.4375 C 4.96875 -1.4375 5.347656 -1.617188 5.609375 -1.984375 C 5.867188 -2.347656 6 -2.867188 6 -3.546875 C 6 -4.234375 5.867188 -4.753906 5.609375 -5.109375 C 5.347656 -5.472656 4.96875 -5.65625 4.46875 -5.65625 Z M 4.46875 -7.28125 C 5.695312 -7.28125 6.65625 -6.945312 7.34375 -6.28125 C 8.03125 -5.625 8.375 -4.710938 8.375 -3.546875 C 8.375 -2.378906 8.03125 -1.460938 7.34375 -0.796875 C 6.65625 -0.140625 5.695312 0.1875 4.46875 0.1875 C 3.25 0.1875 2.289062 -0.140625 1.59375 -0.796875 C 0.90625 -1.460938 0.5625 -2.378906 0.5625 -3.546875 C 0.5625 -4.710938 0.90625 -5.625 1.59375 -6.28125 C 2.289062 -6.945312 3.25 -7.28125 4.46875 -7.28125 Z M 4.46875 -7.28125 "/>
|
||||||
|
</symbol>
|
||||||
|
<symbol overflow="visible" id="glyph2-11">
|
||||||
|
<path style="stroke:none;" d="M 8.703125 -0.515625 C 8.253906 -0.285156 7.785156 -0.113281 7.296875 0 C 6.816406 0.125 6.3125 0.1875 5.78125 0.1875 C 4.207031 0.1875 2.957031 -0.253906 2.03125 -1.140625 C 1.101562 -2.023438 0.640625 -3.222656 0.640625 -4.734375 C 0.640625 -6.242188 1.101562 -7.441406 2.03125 -8.328125 C 2.957031 -9.210938 4.207031 -9.65625 5.78125 -9.65625 C 6.3125 -9.65625 6.816406 -9.59375 7.296875 -9.46875 C 7.785156 -9.351562 8.253906 -9.175781 8.703125 -8.9375 L 8.703125 -6.984375 C 8.253906 -7.296875 7.804688 -7.519531 7.359375 -7.65625 C 6.921875 -7.800781 6.460938 -7.875 5.984375 -7.875 C 5.109375 -7.875 4.421875 -7.59375 3.921875 -7.03125 C 3.421875 -6.476562 3.171875 -5.710938 3.171875 -4.734375 C 3.171875 -3.753906 3.421875 -2.984375 3.921875 -2.421875 C 4.421875 -1.867188 5.109375 -1.59375 5.984375 -1.59375 C 6.460938 -1.59375 6.921875 -1.660156 7.359375 -1.796875 C 7.804688 -1.941406 8.253906 -2.171875 8.703125 -2.484375 Z M 8.703125 -0.515625 "/>
|
||||||
|
</symbol>
|
||||||
|
<symbol overflow="visible" id="glyph2-12">
|
||||||
|
<path style="stroke:none;" d="M 1.015625 -2.765625 L 1.015625 -7.109375 L 3.296875 -7.109375 L 3.296875 -6.40625 C 3.296875 -6.019531 3.289062 -5.535156 3.28125 -4.953125 C 3.28125 -4.367188 3.28125 -3.976562 3.28125 -3.78125 C 3.28125 -3.207031 3.296875 -2.796875 3.328125 -2.546875 C 3.359375 -2.296875 3.410156 -2.113281 3.484375 -2 C 3.578125 -1.851562 3.695312 -1.738281 3.84375 -1.65625 C 4 -1.570312 4.175781 -1.53125 4.375 -1.53125 C 4.84375 -1.53125 5.210938 -1.710938 5.484375 -2.078125 C 5.753906 -2.441406 5.890625 -2.945312 5.890625 -3.59375 L 5.890625 -7.109375 L 8.15625 -7.109375 L 8.15625 0 L 5.890625 0 L 5.890625 -1.03125 C 5.546875 -0.613281 5.179688 -0.304688 4.796875 -0.109375 C 4.421875 0.0859375 4 0.1875 3.53125 0.1875 C 2.707031 0.1875 2.082031 -0.0625 1.65625 -0.5625 C 1.226562 -1.070312 1.015625 -1.804688 1.015625 -2.765625 Z M 1.015625 -2.765625 "/>
|
||||||
|
</symbol>
|
||||||
|
<symbol overflow="visible" id="glyph2-13">
|
||||||
|
<path style="stroke:none;" d="M 8.1875 -3.578125 L 8.1875 -2.921875 L 2.875 -2.921875 C 2.925781 -2.390625 3.117188 -1.988281 3.453125 -1.71875 C 3.785156 -1.457031 4.25 -1.328125 4.84375 -1.328125 C 5.3125 -1.328125 5.796875 -1.394531 6.296875 -1.53125 C 6.804688 -1.675781 7.328125 -1.894531 7.859375 -2.1875 L 7.859375 -0.4375 C 7.316406 -0.226562 6.773438 -0.0703125 6.234375 0.03125 C 5.703125 0.132812 5.164062 0.1875 4.625 0.1875 C 3.34375 0.1875 2.34375 -0.140625 1.625 -0.796875 C 0.914062 -1.453125 0.5625 -2.367188 0.5625 -3.546875 C 0.5625 -4.703125 0.910156 -5.613281 1.609375 -6.28125 C 2.304688 -6.945312 3.269531 -7.28125 4.5 -7.28125 C 5.613281 -7.28125 6.503906 -6.941406 7.171875 -6.265625 C 7.847656 -5.597656 8.1875 -4.703125 8.1875 -3.578125 Z M 5.859375 -4.328125 C 5.859375 -4.765625 5.726562 -5.113281 5.46875 -5.375 C 5.21875 -5.632812 4.890625 -5.765625 4.484375 -5.765625 C 4.046875 -5.765625 3.6875 -5.640625 3.40625 -5.390625 C 3.132812 -5.148438 2.96875 -4.796875 2.90625 -4.328125 Z M 5.859375 -4.328125 "/>
|
||||||
|
</symbol>
|
||||||
|
</g>
|
||||||
|
</defs>
|
||||||
|
<g id="surface25169">
|
||||||
|
<rect x="0" y="0" width="1146" height="548" style="fill:rgb(100%,100%,100%);fill-opacity:1;stroke:none;"/>
|
||||||
|
<path style="fill-rule:evenodd;fill:rgb(100%,100%,100%);fill-opacity:1;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 28.5 4.95 L 35.825 4.95 L 35.825 6.35 L 28.5 6.35 Z M 28.5 4.95 " transform="matrix(20,0,0,20,-26,-8)"/>
|
||||||
|
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
|
||||||
|
<use xlink:href="#glyph0-1" x="537.601562" y="110"/>
|
||||||
|
<use xlink:href="#glyph0-2" x="549.984375" y="110"/>
|
||||||
|
<use xlink:href="#glyph0-3" x="561.371094" y="110"/>
|
||||||
|
<use xlink:href="#glyph0-4" x="572.816406" y="110"/>
|
||||||
|
<use xlink:href="#glyph0-5" x="578.304688" y="110"/>
|
||||||
|
<use xlink:href="#glyph0-6" x="585.960938" y="110"/>
|
||||||
|
<use xlink:href="#glyph0-7" x="623.109375" y="110"/>
|
||||||
|
<use xlink:href="#glyph0-8" x="623.109375" y="110"/>
|
||||||
|
<use xlink:href="#glyph0-9" x="633.96875" y="110"/>
|
||||||
|
<use xlink:href="#glyph0-10" x="650.648438" y="110"/>
|
||||||
|
<use xlink:href="#glyph0-11" x="662.09375" y="110"/>
|
||||||
|
<use xlink:href="#glyph0-12" x="667.582031" y="110"/>
|
||||||
|
<use xlink:href="#glyph0-5" x="678.382812" y="110"/>
|
||||||
|
<use xlink:href="#glyph0-8" x="686.039062" y="110"/>
|
||||||
|
</g>
|
||||||
|
<path style="fill-rule:evenodd;fill:rgb(100%,100%,100%);fill-opacity:1;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 10.6 7 L 19.475 7 L 19.475 8.4 L 10.6 8.4 Z M 10.6 7 " transform="matrix(20,0,0,20,-26,-8)"/>
|
||||||
|
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
|
||||||
|
<use xlink:href="#glyph0-13" x="191" y="151"/>
|
||||||
|
<use xlink:href="#glyph0-10" x="204.59375" y="151"/>
|
||||||
|
<use xlink:href="#glyph0-8" x="216.039063" y="151"/>
|
||||||
|
<use xlink:href="#glyph0-14" x="226.898438" y="151"/>
|
||||||
|
<use xlink:href="#glyph0-15" x="238.285156" y="151"/>
|
||||||
|
<use xlink:href="#glyph0-5" x="249.808594" y="151"/>
|
||||||
|
<use xlink:href="#glyph0-12" x="257.464844" y="151"/>
|
||||||
|
<use xlink:href="#glyph0-16" x="268.265625" y="151"/>
|
||||||
|
<use xlink:href="#glyph0-17" x="277.757812" y="151"/>
|
||||||
|
<use xlink:href="#glyph0-6" x="288.402344" y="151"/>
|
||||||
|
<use xlink:href="#glyph0-18" x="293.96875" y="151"/>
|
||||||
|
<use xlink:href="#glyph0-11" x="305.707031" y="151"/>
|
||||||
|
<use xlink:href="#glyph0-2" x="311.195312" y="151"/>
|
||||||
|
<use xlink:href="#glyph0-19" x="322.582031" y="151"/>
|
||||||
|
<use xlink:href="#glyph0-5" x="332.113281" y="151"/>
|
||||||
|
<use xlink:href="#glyph0-8" x="339.769531" y="151"/>
|
||||||
|
<use xlink:href="#glyph0-20" x="350.628906" y="151"/>
|
||||||
|
</g>
|
||||||
|
<path style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-dasharray:0.4,0.4;stroke-miterlimit:10;" d="M 28.451953 5.65 L 22.3 5.65 L 22.3 7.7 L 19.85957 7.7 " transform="matrix(20,0,0,20,-26,-8)"/>
|
||||||
|
<path style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 20.491797 7.45 L 19.691797 7.7 L 20.491797 7.95 " transform="matrix(20,0,0,20,-26,-8)"/>
|
||||||
|
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
|
||||||
|
<use xlink:href="#glyph1-1" x="422" y="121.5"/>
|
||||||
|
<use xlink:href="#glyph1-2" x="429.695313" y="121.5"/>
|
||||||
|
<use xlink:href="#glyph1-2" x="437.390625" y="121.5"/>
|
||||||
|
<use xlink:href="#glyph1-3" x="445.085938" y="121.5"/>
|
||||||
|
<use xlink:href="#glyph1-4" x="452.78125" y="121.5"/>
|
||||||
|
<use xlink:href="#glyph1-5" x="460.476563" y="121.5"/>
|
||||||
|
<use xlink:href="#glyph1-6" x="468.171875" y="121.5"/>
|
||||||
|
<use xlink:href="#glyph1-7" x="475.867188" y="121.5"/>
|
||||||
|
<use xlink:href="#glyph1-8" x="483.5625" y="121.5"/>
|
||||||
|
<use xlink:href="#glyph1-9" x="491.257812" y="121.5"/>
|
||||||
|
</g>
|
||||||
|
<path style="fill-rule:evenodd;fill:rgb(100%,100%,100%);fill-opacity:1;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 30.75 11.35 L 33.669922 11.35 L 33.669922 12.75 L 30.75 12.75 Z M 30.75 11.35 " transform="matrix(20,0,0,20,-26,-8)"/>
|
||||||
|
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
|
||||||
|
<use xlink:href="#glyph0-1" x="594.019531" y="238"/>
|
||||||
|
<use xlink:href="#glyph0-2" x="606.402344" y="238"/>
|
||||||
|
<use xlink:href="#glyph0-3" x="617.789062" y="238"/>
|
||||||
|
<use xlink:href="#glyph0-4" x="629.234375" y="238"/>
|
||||||
|
<use xlink:href="#glyph0-5" x="634.722656" y="238"/>
|
||||||
|
</g>
|
||||||
|
<path style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-dasharray:0.4,0.4;stroke-miterlimit:10;" d="M 32.209961 11.299805 L 32.209961 9.424805 L 32.215039 9.424805 L 32.215039 6.685352 " transform="matrix(20,0,0,20,-26,-8)"/>
|
||||||
|
<path style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 32.465039 7.317578 L 32.215039 6.517578 L 31.965039 7.317578 " transform="matrix(20,0,0,20,-26,-8)"/>
|
||||||
|
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
|
||||||
|
<use xlink:href="#glyph1-10" x="529.753906" y="176.496094"/>
|
||||||
|
<use xlink:href="#glyph1-5" x="537.449219" y="176.496094"/>
|
||||||
|
<use xlink:href="#glyph1-8" x="545.144531" y="176.496094"/>
|
||||||
|
<use xlink:href="#glyph1-6" x="552.839844" y="176.496094"/>
|
||||||
|
<use xlink:href="#glyph1-7" x="560.535156" y="176.496094"/>
|
||||||
|
<use xlink:href="#glyph1-11" x="568.230469" y="176.496094"/>
|
||||||
|
<use xlink:href="#glyph1-9" x="575.925781" y="176.496094"/>
|
||||||
|
<use xlink:href="#glyph1-12" x="583.621094" y="176.496094"/>
|
||||||
|
<use xlink:href="#glyph1-13" x="591.316406" y="176.496094"/>
|
||||||
|
<use xlink:href="#glyph1-4" x="599.011719" y="176.496094"/>
|
||||||
|
<use xlink:href="#glyph1-10" x="606.707031" y="176.496094"/>
|
||||||
|
<use xlink:href="#glyph1-14" x="614.402344" y="176.496094"/>
|
||||||
|
<use xlink:href="#glyph1-15" x="622.097656" y="176.496094"/>
|
||||||
|
<use xlink:href="#glyph1-16" x="629.792969" y="176.496094"/>
|
||||||
|
<use xlink:href="#glyph1-8" x="637.488281" y="176.496094"/>
|
||||||
|
<use xlink:href="#glyph1-4" x="645.183594" y="176.496094"/>
|
||||||
|
<use xlink:href="#glyph1-9" x="652.878906" y="176.496094"/>
|
||||||
|
<use xlink:href="#glyph1-12" x="660.574219" y="176.496094"/>
|
||||||
|
<use xlink:href="#glyph1-7" x="668.269531" y="176.496094"/>
|
||||||
|
<use xlink:href="#glyph1-13" x="675.964844" y="176.496094"/>
|
||||||
|
<use xlink:href="#glyph1-15" x="683.660156" y="176.496094"/>
|
||||||
|
<use xlink:href="#glyph1-9" x="691.355469" y="176.496094"/>
|
||||||
|
<use xlink:href="#glyph1-17" x="699.050781" y="176.496094"/>
|
||||||
|
</g>
|
||||||
|
<path style="fill-rule:evenodd;fill:rgb(100%,100%,100%);fill-opacity:1;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 45.8 0.45 L 48.322461 0.45 L 48.322461 1.85 L 45.8 1.85 Z M 45.8 0.45 " transform="matrix(20,0,0,20,-26,-8)"/>
|
||||||
|
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
|
||||||
|
<use xlink:href="#glyph0-21" x="895.03125" y="20"/>
|
||||||
|
<use xlink:href="#glyph0-22" x="908.15625" y="20"/>
|
||||||
|
<use xlink:href="#glyph0-12" x="919.152344" y="20"/>
|
||||||
|
<use xlink:href="#glyph0-11" x="929.953125" y="20"/>
|
||||||
|
</g>
|
||||||
|
<path style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-dasharray:0.4,0.4;stroke-miterlimit:10;" d="M 32.1625 4.899609 L 32.1625 3.6 L 47.061328 3.6 L 47.061328 2.235547 " transform="matrix(20,0,0,20,-26,-8)"/>
|
||||||
|
<path style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 47.311328 2.867969 L 47.061328 2.067969 L 46.811328 2.867969 " transform="matrix(20,0,0,20,-26,-8)"/>
|
||||||
|
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
|
||||||
|
<use xlink:href="#glyph1-1" x="735.457031" y="60"/>
|
||||||
|
<use xlink:href="#glyph1-11" x="743.152344" y="60"/>
|
||||||
|
<use xlink:href="#glyph1-18" x="750.847656" y="60"/>
|
||||||
|
<use xlink:href="#glyph1-4" x="758.542969" y="60"/>
|
||||||
|
<use xlink:href="#glyph1-5" x="766.238281" y="60"/>
|
||||||
|
<use xlink:href="#glyph1-19" x="773.933594" y="60"/>
|
||||||
|
<use xlink:href="#glyph1-5" x="781.628906" y="60"/>
|
||||||
|
<use xlink:href="#glyph1-6" x="789.324219" y="60"/>
|
||||||
|
</g>
|
||||||
|
<path style="fill-rule:evenodd;fill:rgb(100%,100%,100%);fill-opacity:1;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 24.75 18.35 L 30.372461 18.35 L 30.372461 19.75 L 24.75 19.75 Z M 24.75 18.35 " transform="matrix(20,0,0,20,-26,-8)"/>
|
||||||
|
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
|
||||||
|
<use xlink:href="#glyph0-1" x="474.054688" y="378"/>
|
||||||
|
<use xlink:href="#glyph0-16" x="486.4375" y="378"/>
|
||||||
|
<use xlink:href="#glyph0-5" x="495.929688" y="378"/>
|
||||||
|
<use xlink:href="#glyph0-4" x="503.585938" y="378"/>
|
||||||
|
<use xlink:href="#glyph0-22" x="509.074219" y="378"/>
|
||||||
|
<use xlink:href="#glyph0-14" x="520.070312" y="378"/>
|
||||||
|
<use xlink:href="#glyph0-6" x="531.457031" y="378"/>
|
||||||
|
<use xlink:href="#glyph0-23" x="537.023438" y="378"/>
|
||||||
|
<use xlink:href="#glyph0-11" x="548.742188" y="378"/>
|
||||||
|
<use xlink:href="#glyph0-12" x="554.230469" y="378"/>
|
||||||
|
<use xlink:href="#glyph0-14" x="565.03125" y="378"/>
|
||||||
|
</g>
|
||||||
|
<path style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-dasharray:0.4,0.4;stroke-miterlimit:10;" d="M 32.209961 12.8 L 32.209961 14.925 L 26.15 14.925 L 26.15 17.814648 " transform="matrix(20,0,0,20,-26,-8)"/>
|
||||||
|
<path style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 25.9 17.182422 L 26.15 17.982422 L 26.4 17.182422 " transform="matrix(20,0,0,20,-26,-8)"/>
|
||||||
|
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
|
||||||
|
<use xlink:href="#glyph1-20" x="522.972656" y="286.5"/>
|
||||||
|
<use xlink:href="#glyph1-5" x="530.667969" y="286.5"/>
|
||||||
|
<use xlink:href="#glyph1-12" x="538.363281" y="286.5"/>
|
||||||
|
<use xlink:href="#glyph1-5" x="546.058594" y="286.5"/>
|
||||||
|
<use xlink:href="#glyph1-15" x="553.753906" y="286.5"/>
|
||||||
|
<use xlink:href="#glyph1-16" x="561.449219" y="286.5"/>
|
||||||
|
<use xlink:href="#glyph1-8" x="569.144531" y="286.5"/>
|
||||||
|
<use xlink:href="#glyph1-5" x="576.839844" y="286.5"/>
|
||||||
|
<use xlink:href="#glyph1-6" x="584.535156" y="286.5"/>
|
||||||
|
</g>
|
||||||
|
<path style="fill-rule:evenodd;fill:rgb(100%,100%,100%);fill-opacity:1;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 34.1 23.55 L 37.472461 23.55 L 37.472461 24.95 L 34.1 24.95 Z M 34.1 23.55 " transform="matrix(20,0,0,20,-26,-8)"/>
|
||||||
|
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
|
||||||
|
<use xlink:href="#glyph0-1" x="661.035156" y="482"/>
|
||||||
|
<use xlink:href="#glyph0-16" x="673.417969" y="482"/>
|
||||||
|
<use xlink:href="#glyph0-5" x="682.910156" y="482"/>
|
||||||
|
<use xlink:href="#glyph0-4" x="690.566406" y="482"/>
|
||||||
|
<use xlink:href="#glyph0-22" x="696.054688" y="482"/>
|
||||||
|
<use xlink:href="#glyph0-14" x="707.050781" y="482"/>
|
||||||
|
</g>
|
||||||
|
<path style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 27.561328 21.047266 L 27.561328 22.15 L 35.786328 22.15 L 35.786328 23.501562 " transform="matrix(20,0,0,20,-26,-8)"/>
|
||||||
|
<path style="fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 27.561328 19.788672 L 27.801172 20.488672 L 27.561328 21.188672 L 27.321289 20.488672 Z M 27.561328 19.788672 " transform="matrix(20,0,0,20,-26,-8)"/>
|
||||||
|
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
|
||||||
|
<use xlink:href="#glyph1-4" x="553.609375" y="431"/>
|
||||||
|
<use xlink:href="#glyph1-6" x="561.304688" y="431"/>
|
||||||
|
<use xlink:href="#glyph1-7" x="569" y="431"/>
|
||||||
|
<use xlink:href="#glyph1-11" x="576.695313" y="431"/>
|
||||||
|
<use xlink:href="#glyph1-9" x="584.390625" y="431"/>
|
||||||
|
<use xlink:href="#glyph1-17" x="592.085938" y="431"/>
|
||||||
|
<use xlink:href="#glyph1-2" x="599.78125" y="431"/>
|
||||||
|
<use xlink:href="#glyph1-9" x="607.476563" y="431"/>
|
||||||
|
<use xlink:href="#glyph1-6" x="615.171875" y="431"/>
|
||||||
|
<use xlink:href="#glyph1-5" x="622.867188" y="431"/>
|
||||||
|
<use xlink:href="#glyph1-21" x="630.5625" y="431"/>
|
||||||
|
<use xlink:href="#glyph1-7" x="638.257812" y="431"/>
|
||||||
|
<use xlink:href="#glyph1-9" x="645.953125" y="431"/>
|
||||||
|
<use xlink:href="#glyph1-13" x="653.648438" y="431"/>
|
||||||
|
</g>
|
||||||
|
<path style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 37.472461 24.95 L 37.472461 25.85 L 43.1 25.85 L 43.1 24.25 L 37.472461 24.25 " transform="matrix(20,0,0,20,-26,-8)"/>
|
||||||
|
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
|
||||||
|
<use xlink:href="#glyph1-22" x="737.402344" y="505"/>
|
||||||
|
<use xlink:href="#glyph1-5" x="745.097656" y="505"/>
|
||||||
|
<use xlink:href="#glyph1-23" x="752.792969" y="505"/>
|
||||||
|
<use xlink:href="#glyph1-8" x="760.488281" y="505"/>
|
||||||
|
<use xlink:href="#glyph1-7" x="768.183594" y="505"/>
|
||||||
|
<use xlink:href="#glyph1-16" x="775.878906" y="505"/>
|
||||||
|
<use xlink:href="#glyph1-11" x="783.574219" y="505"/>
|
||||||
|
<use xlink:href="#glyph1-8" x="791.269531" y="505"/>
|
||||||
|
<use xlink:href="#glyph1-4" x="798.964844" y="505"/>
|
||||||
|
<use xlink:href="#glyph1-9" x="806.660156" y="505"/>
|
||||||
|
<use xlink:href="#glyph1-12" x="814.355469" y="505"/>
|
||||||
|
</g>
|
||||||
|
<path style=" stroke:none;fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 824.074219 505 L 824.074219 497 L 832.074219 501 Z M 824.074219 505 "/>
|
||||||
|
<path style="fill-rule:evenodd;fill:rgb(100%,100%,100%);fill-opacity:1;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(100%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 52.1 10.95 C 52.1 11.115625 51.965625 11.25 51.8 11.25 C 51.634375 11.25 51.5 11.115625 51.5 10.95 C 51.5 10.784375 51.634375 10.65 51.8 10.65 C 51.965625 10.65 52.1 10.784375 52.1 10.95 " transform="matrix(20,0,0,20,-26,-8)"/>
|
||||||
|
<path style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(100%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 50.6 11.55 L 53 11.55 " transform="matrix(20,0,0,20,-26,-8)"/>
|
||||||
|
<path style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(100%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 51.8 11.25 L 51.8 12.75 " transform="matrix(20,0,0,20,-26,-8)"/>
|
||||||
|
<path style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(100%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 51.8 12.75 L 50.6 14.05 " transform="matrix(20,0,0,20,-26,-8)"/>
|
||||||
|
<path style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(100%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 51.8 12.75 L 53 14.05 " transform="matrix(20,0,0,20,-26,-8)"/>
|
||||||
|
<g style="fill:rgb(100%,0%,0%);fill-opacity:1;">
|
||||||
|
<use xlink:href="#glyph2-1" x="959.921875" y="296.898438"/>
|
||||||
|
<use xlink:href="#glyph2-2" x="969.824219" y="296.898438"/>
|
||||||
|
<use xlink:href="#glyph2-3" x="978.984375" y="296.898438"/>
|
||||||
|
<use xlink:href="#glyph2-4" x="992.324219" y="296.898438"/>
|
||||||
|
<use xlink:href="#glyph2-5" x="996.71875" y="296.898438"/>
|
||||||
|
<use xlink:href="#glyph2-4" x="1005.820312" y="296.898438"/>
|
||||||
|
<use xlink:href="#glyph2-6" x="1010.214844" y="296.898438"/>
|
||||||
|
<use xlink:href="#glyph2-7" x="1017.832031" y="296.898438"/>
|
||||||
|
<use xlink:href="#glyph2-8" x="1023.945312" y="296.898438"/>
|
||||||
|
<use xlink:href="#glyph2-9" x="1030.253906" y="296.898438"/>
|
||||||
|
<use xlink:href="#glyph2-7" x="1038.886719" y="296.898438"/>
|
||||||
|
<use xlink:href="#glyph2-10" x="1045" y="296.898438"/>
|
||||||
|
<use xlink:href="#glyph2-8" x="1053.789062" y="296.898438"/>
|
||||||
|
</g>
|
||||||
|
<path style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-dasharray:0.4,0.4;stroke-miterlimit:10;" d="M 49.295898 12.75 L 41.708203 12.75 L 41.708203 12.05 L 34.055664 12.05 " transform="matrix(20,0,0,20,-26,-8)"/>
|
||||||
|
<path style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 34.688086 11.8 L 33.888086 12.05 L 34.688086 12.3 " transform="matrix(20,0,0,20,-26,-8)"/>
|
||||||
|
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
|
||||||
|
<use xlink:href="#glyph1-24" x="810.164062" y="236"/>
|
||||||
|
<use xlink:href="#glyph1-15" x="817.859375" y="236"/>
|
||||||
|
<use xlink:href="#glyph1-4" x="825.554688" y="236"/>
|
||||||
|
<use xlink:href="#glyph1-10" x="833.25" y="236"/>
|
||||||
|
<use xlink:href="#glyph1-10" x="840.945312" y="236"/>
|
||||||
|
<use xlink:href="#glyph1-5" x="848.640625" y="236"/>
|
||||||
|
<use xlink:href="#glyph1-15" x="856.335938" y="236"/>
|
||||||
|
<use xlink:href="#glyph1-6" x="864.03125" y="236"/>
|
||||||
|
</g>
|
||||||
|
<path style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-dasharray:0.4,0.4;stroke-miterlimit:10;" d="M 51.9 9.649609 L 44.0625 9.649609 L 44.0625 5.65 L 36.160352 5.65 " transform="matrix(20,0,0,20,-26,-8)"/>
|
||||||
|
<path style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 36.792578 5.4 L 35.992578 5.65 L 36.792578 5.9 " transform="matrix(20,0,0,20,-26,-8)"/>
|
||||||
|
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
|
||||||
|
<use xlink:href="#glyph1-25" x="857.25" y="140.996094"/>
|
||||||
|
<use xlink:href="#glyph1-5" x="864.945312" y="140.996094"/>
|
||||||
|
<use xlink:href="#glyph1-13" x="872.640625" y="140.996094"/>
|
||||||
|
<use xlink:href="#glyph1-4" x="880.335938" y="140.996094"/>
|
||||||
|
<use xlink:href="#glyph1-12" x="888.03125" y="140.996094"/>
|
||||||
|
<use xlink:href="#glyph1-5" x="895.726562" y="140.996094"/>
|
||||||
|
<use xlink:href="#glyph1-6" x="903.421875" y="140.996094"/>
|
||||||
|
<use xlink:href="#glyph1-7" x="911.117188" y="140.996094"/>
|
||||||
|
<use xlink:href="#glyph1-1" x="918.8125" y="140.996094"/>
|
||||||
|
<use xlink:href="#glyph1-14" x="926.507812" y="140.996094"/>
|
||||||
|
<use xlink:href="#glyph1-21" x="934.203125" y="140.996094"/>
|
||||||
|
<use xlink:href="#glyph1-4" x="941.898438" y="140.996094"/>
|
||||||
|
<use xlink:href="#glyph1-8" x="949.59375" y="140.996094"/>
|
||||||
|
<use xlink:href="#glyph1-7" x="957.289062" y="140.996094"/>
|
||||||
|
<use xlink:href="#glyph1-11" x="964.984375" y="140.996094"/>
|
||||||
|
<use xlink:href="#glyph1-9" x="972.679688" y="140.996094"/>
|
||||||
|
<use xlink:href="#glyph1-12" x="980.375" y="140.996094"/>
|
||||||
|
<use xlink:href="#glyph1-13" x="988.070312" y="140.996094"/>
|
||||||
|
<use xlink:href="#glyph1-4" x="995.765625" y="140.996094"/>
|
||||||
|
<use xlink:href="#glyph1-10" x="1003.460938" y="140.996094"/>
|
||||||
|
<use xlink:href="#glyph1-14" x="1011.15625" y="140.996094"/>
|
||||||
|
<use xlink:href="#glyph1-15" x="1018.851562" y="140.996094"/>
|
||||||
|
<use xlink:href="#glyph1-16" x="1026.546875" y="140.996094"/>
|
||||||
|
<use xlink:href="#glyph1-8" x="1034.242188" y="140.996094"/>
|
||||||
|
<use xlink:href="#glyph1-4" x="1041.9375" y="140.996094"/>
|
||||||
|
<use xlink:href="#glyph1-9" x="1049.632812" y="140.996094"/>
|
||||||
|
<use xlink:href="#glyph1-12" x="1057.328125" y="140.996094"/>
|
||||||
|
<use xlink:href="#glyph1-7" x="1065.023438" y="140.996094"/>
|
||||||
|
<use xlink:href="#glyph1-4" x="1072.71875" y="140.996094"/>
|
||||||
|
<use xlink:href="#glyph1-12" x="1080.414062" y="140.996094"/>
|
||||||
|
</g>
|
||||||
|
<path style="fill-rule:evenodd;fill:rgb(100%,100%,100%);fill-opacity:1;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(100%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 5.45 1.4 C 5.45 1.565625 5.315625 1.7 5.15 1.7 C 4.984375 1.7 4.85 1.565625 4.85 1.4 C 4.85 1.234375 4.984375 1.1 5.15 1.1 C 5.315625 1.1 5.45 1.234375 5.45 1.4 " transform="matrix(20,0,0,20,-26,-8)"/>
|
||||||
|
<path style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(100%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 3.95 2 L 6.35 2 " transform="matrix(20,0,0,20,-26,-8)"/>
|
||||||
|
<path style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(100%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 5.15 1.7 L 5.15 3.2 " transform="matrix(20,0,0,20,-26,-8)"/>
|
||||||
|
<path style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(100%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 5.15 3.2 L 3.95 4.5 " transform="matrix(20,0,0,20,-26,-8)"/>
|
||||||
|
<path style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(100%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 5.15 3.2 L 6.35 4.5 " transform="matrix(20,0,0,20,-26,-8)"/>
|
||||||
|
<g style="fill:rgb(100%,0%,0%);fill-opacity:1;">
|
||||||
|
<use xlink:href="#glyph2-11" x="42.332031" y="105.898438"/>
|
||||||
|
<use xlink:href="#glyph2-12" x="51.726562" y="105.898438"/>
|
||||||
|
<use xlink:href="#glyph2-6" x="60.828125" y="105.898438"/>
|
||||||
|
<use xlink:href="#glyph2-7" x="68.445312" y="105.898438"/>
|
||||||
|
<use xlink:href="#glyph2-10" x="74.558594" y="105.898438"/>
|
||||||
|
<use xlink:href="#glyph2-3" x="83.347656" y="105.898438"/>
|
||||||
|
<use xlink:href="#glyph2-13" x="96.6875" y="105.898438"/>
|
||||||
|
<use xlink:href="#glyph2-8" x="105.359375" y="105.898438"/>
|
||||||
|
</g>
|
||||||
|
<path style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-dasharray:0.4,0.4;stroke-miterlimit:10;" d="M 6.88418 3.2 L 15.0375 3.2 L 15.0375 6.664648 " transform="matrix(20,0,0,20,-26,-8)"/>
|
||||||
|
<path style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 14.7875 6.032422 L 15.0375 6.832422 L 15.2875 6.032422 " transform="matrix(20,0,0,20,-26,-8)"/>
|
||||||
|
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
|
||||||
|
<use xlink:href="#glyph1-26" x="123.957031" y="52"/>
|
||||||
|
<use xlink:href="#glyph1-9" x="131.652344" y="52"/>
|
||||||
|
<use xlink:href="#glyph1-12" x="139.347656" y="52"/>
|
||||||
|
<use xlink:href="#glyph1-6" x="147.042969" y="52"/>
|
||||||
|
<use xlink:href="#glyph1-14" x="154.738281" y="52"/>
|
||||||
|
<use xlink:href="#glyph1-17" x="162.433594" y="52"/>
|
||||||
|
<use xlink:href="#glyph1-5" x="170.128906" y="52"/>
|
||||||
|
<use xlink:href="#glyph1-6" x="177.824219" y="52"/>
|
||||||
|
<use xlink:href="#glyph1-7" x="185.519531" y="52"/>
|
||||||
|
<use xlink:href="#glyph1-15" x="193.214844" y="52"/>
|
||||||
|
<use xlink:href="#glyph1-5" x="200.910156" y="52"/>
|
||||||
|
<use xlink:href="#glyph1-6" x="208.605469" y="52"/>
|
||||||
|
<use xlink:href="#glyph1-9" x="216.300781" y="52"/>
|
||||||
|
<use xlink:href="#glyph1-14" x="223.996094" y="52"/>
|
||||||
|
<use xlink:href="#glyph1-15" x="231.691406" y="52"/>
|
||||||
|
<use xlink:href="#glyph1-11" x="239.386719" y="52"/>
|
||||||
|
<use xlink:href="#glyph1-5" x="247.082031" y="52"/>
|
||||||
|
<use xlink:href="#glyph1-6" x="254.777344" y="52"/>
|
||||||
|
</g>
|
||||||
|
<path style="fill-rule:evenodd;fill:rgb(100%,100%,100%);fill-opacity:1;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 1.35 12.9 L 6.490039 12.9 L 6.490039 14.3 L 1.35 14.3 Z M 1.35 12.9 " transform="matrix(20,0,0,20,-26,-8)"/>
|
||||||
|
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
|
||||||
|
<use xlink:href="#glyph0-24" x="5.972656" y="269"/>
|
||||||
|
<use xlink:href="#glyph0-8" x="18.296875" y="269"/>
|
||||||
|
<use xlink:href="#glyph0-19" x="29.15625" y="269"/>
|
||||||
|
<use xlink:href="#glyph0-22" x="38.6875" y="269"/>
|
||||||
|
<use xlink:href="#glyph0-2" x="49.683594" y="269"/>
|
||||||
|
<use xlink:href="#glyph0-20" x="61.070312" y="269"/>
|
||||||
|
<use xlink:href="#glyph0-16" x="68.960938" y="269"/>
|
||||||
|
<use xlink:href="#glyph0-8" x="78.453125" y="269"/>
|
||||||
|
<use xlink:href="#glyph0-19" x="89.3125" y="269"/>
|
||||||
|
</g>
|
||||||
|
<path style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 9.341406 7.7 L 3.919922 7.7 L 3.919922 12.9 " transform="matrix(20,0,0,20,-26,-8)"/>
|
||||||
|
<path style="fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 10.6 7.7 L 9.9 7.940039 L 9.2 7.7 L 9.9 7.459961 Z M 10.6 7.7 " transform="matrix(20,0,0,20,-26,-8)"/>
|
||||||
|
<path style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-dasharray:0.4,0.4;stroke-miterlimit:10;" d="M 35.786133 24.95 L 35.786133 27 L 3.919922 27 L 3.919922 14.635352 " transform="matrix(20,0,0,20,-26,-8)"/>
|
||||||
|
<path style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 4.169922 15.267578 L 3.919922 14.467578 L 3.669922 15.267578 " transform="matrix(20,0,0,20,-26,-8)"/>
|
||||||
|
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
|
||||||
|
<use xlink:href="#glyph1-27" x="340.28125" y="528"/>
|
||||||
|
<use xlink:href="#glyph1-9" x="347.976563" y="528"/>
|
||||||
|
<use xlink:href="#glyph1-21" x="355.671875" y="528"/>
|
||||||
|
<use xlink:href="#glyph1-4" x="363.367188" y="528"/>
|
||||||
|
<use xlink:href="#glyph1-13" x="371.0625" y="528"/>
|
||||||
|
<use xlink:href="#glyph1-4" x="378.757813" y="528"/>
|
||||||
|
<use xlink:href="#glyph1-5" x="386.453125" y="528"/>
|
||||||
|
<use xlink:href="#glyph1-6" x="394.148438" y="528"/>
|
||||||
|
</g>
|
||||||
|
<path style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-dasharray:0.4,0.4;stroke-miterlimit:10;" d="M 51.8 15.49707 L 51.8 19.05 L 30.707813 19.05 " transform="matrix(20,0,0,20,-26,-8)"/>
|
||||||
|
<path style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 31.340039 18.8 L 30.540039 19.05 L 31.340039 19.3 " transform="matrix(20,0,0,20,-26,-8)"/>
|
||||||
|
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
|
||||||
|
<use xlink:href="#glyph1-28" x="764.945312" y="369"/>
|
||||||
|
<use xlink:href="#glyph1-16" x="772.640625" y="369"/>
|
||||||
|
<use xlink:href="#glyph1-14" x="780.335938" y="369"/>
|
||||||
|
<use xlink:href="#glyph1-12" x="788.03125" y="369"/>
|
||||||
|
<use xlink:href="#glyph1-11" x="795.726562" y="369"/>
|
||||||
|
<use xlink:href="#glyph1-18" x="803.421875" y="369"/>
|
||||||
|
<use xlink:href="#glyph1-5" x="811.117188" y="369"/>
|
||||||
|
<use xlink:href="#glyph1-6" x="818.8125" y="369"/>
|
||||||
|
</g>
|
||||||
|
<path style="fill-rule:evenodd;fill:rgb(100%,100%,100%);fill-opacity:1;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 54.144922 2.155078 L 58.557422 2.155078 L 58.557422 3.555078 L 54.144922 3.555078 Z M 54.144922 2.155078 " transform="matrix(20,0,0,20,-26,-8)"/>
|
||||||
|
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
|
||||||
|
<use xlink:href="#glyph0-15" x="1061.902344" y="54.101562"/>
|
||||||
|
<use xlink:href="#glyph0-5" x="1073.425781" y="54.101562"/>
|
||||||
|
<use xlink:href="#glyph0-20" x="1081.082031" y="54.101562"/>
|
||||||
|
<use xlink:href="#glyph0-12" x="1088.972656" y="54.101562"/>
|
||||||
|
<use xlink:href="#glyph0-5" x="1099.773438" y="54.101562"/>
|
||||||
|
<use xlink:href="#glyph0-8" x="1107.429688" y="54.101562"/>
|
||||||
|
<use xlink:href="#glyph0-25" x="1118.289062" y="54.101562"/>
|
||||||
|
<use xlink:href="#glyph0-26" x="1129.734375" y="54.101562"/>
|
||||||
|
</g>
|
||||||
|
<path style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-dasharray:0.4,0.4;stroke-miterlimit:10;" d="M 48.322461 1.15 L 51.008594 1.15 L 51.008594 2.855078 L 53.760742 2.855078 " transform="matrix(20,0,0,20,-26,-8)"/>
|
||||||
|
<path style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 53.12832 3.105078 L 53.92832 2.855078 L 53.12832 2.605078 " transform="matrix(20,0,0,20,-26,-8)"/>
|
||||||
|
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
|
||||||
|
<use xlink:href="#glyph1-14" x="996.171875" y="28.050781"/>
|
||||||
|
<use xlink:href="#glyph1-6" x="1003.867188" y="28.050781"/>
|
||||||
|
<use xlink:href="#glyph1-5" x="1011.5625" y="28.050781"/>
|
||||||
|
<use xlink:href="#glyph1-6" x="1019.257812" y="28.050781"/>
|
||||||
|
</g>
|
||||||
|
<path style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 27.561328 21.058594 L 27.561328 22.15 L 19.345703 22.15 L 19.345703 23.652734 " transform="matrix(20,0,0,20,-26,-8)"/>
|
||||||
|
<path style="fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 27.561328 19.8 L 27.801172 20.5 L 27.561328 21.2 L 27.321289 20.5 Z M 27.561328 19.8 " transform="matrix(20,0,0,20,-26,-8)"/>
|
||||||
|
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
|
||||||
|
<use xlink:href="#glyph1-4" x="389.203125" y="431"/>
|
||||||
|
<use xlink:href="#glyph1-6" x="396.898438" y="431"/>
|
||||||
|
<use xlink:href="#glyph1-7" x="404.59375" y="431"/>
|
||||||
|
<use xlink:href="#glyph1-11" x="412.289062" y="431"/>
|
||||||
|
<use xlink:href="#glyph1-9" x="419.984375" y="431"/>
|
||||||
|
<use xlink:href="#glyph1-17" x="427.679688" y="431"/>
|
||||||
|
<use xlink:href="#glyph1-2" x="435.375" y="431"/>
|
||||||
|
<use xlink:href="#glyph1-9" x="443.070312" y="431"/>
|
||||||
|
<use xlink:href="#glyph1-6" x="450.765625" y="431"/>
|
||||||
|
<use xlink:href="#glyph1-5" x="458.460938" y="431"/>
|
||||||
|
<use xlink:href="#glyph1-21" x="466.15625" y="431"/>
|
||||||
|
<use xlink:href="#glyph1-7" x="473.851562" y="431"/>
|
||||||
|
<use xlink:href="#glyph1-9" x="481.546875" y="431"/>
|
||||||
|
<use xlink:href="#glyph1-13" x="489.242188" y="431"/>
|
||||||
|
</g>
|
||||||
|
<path style="fill-rule:evenodd;fill:rgb(100%,100%,100%);fill-opacity:1;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 15.201953 23.652734 L 23.489453 23.652734 L 23.489453 25.052734 L 15.201953 25.052734 Z M 15.201953 23.652734 " transform="matrix(20,0,0,20,-26,-8)"/>
|
||||||
|
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
|
||||||
|
<use xlink:href="#glyph0-27" x="283.082031" y="484.054688"/>
|
||||||
|
<use xlink:href="#glyph0-28" x="294.019531" y="484.054688"/>
|
||||||
|
<use xlink:href="#glyph0-16" x="311.871094" y="484.054688"/>
|
||||||
|
<use xlink:href="#glyph0-12" x="321.363281" y="484.054688"/>
|
||||||
|
<use xlink:href="#glyph0-16" x="332.164062" y="484.054688"/>
|
||||||
|
<use xlink:href="#glyph0-26" x="341.65625" y="484.054688"/>
|
||||||
|
<use xlink:href="#glyph0-6" x="352.085938" y="484.054688"/>
|
||||||
|
<use xlink:href="#glyph0-29" x="357.652344" y="484.054688"/>
|
||||||
|
<use xlink:href="#glyph0-14" x="363.609375" y="484.054688"/>
|
||||||
|
<use xlink:href="#glyph0-3" x="374.996094" y="484.054688"/>
|
||||||
|
<use xlink:href="#glyph0-4" x="386.441406" y="484.054688"/>
|
||||||
|
<use xlink:href="#glyph0-16" x="391.929688" y="484.054688"/>
|
||||||
|
<use xlink:href="#glyph0-12" x="401.421875" y="484.054688"/>
|
||||||
|
<use xlink:href="#glyph0-5" x="412.222656" y="484.054688"/>
|
||||||
|
<use xlink:href="#glyph0-22" x="419.878906" y="484.054688"/>
|
||||||
|
<use xlink:href="#glyph0-20" x="430.875" y="484.054688"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 82 KiB |
BIN
doc/source/images/watcher_db_schema_diagram.png
Normal file
|
After Width: | Height: | Size: 64 KiB |
@@ -55,9 +55,9 @@ Getting Started
|
|||||||
dev/environment
|
dev/environment
|
||||||
dev/devstack
|
dev/devstack
|
||||||
deploy/configuration
|
deploy/configuration
|
||||||
|
deploy/conf-files
|
||||||
dev/testing
|
dev/testing
|
||||||
|
|
||||||
|
|
||||||
API References
|
API References
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
@@ -73,6 +73,7 @@ Plugins
|
|||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
|
||||||
dev/plugin/base-setup
|
dev/plugin/base-setup
|
||||||
|
dev/plugin/goal-plugin
|
||||||
dev/plugin/strategy-plugin
|
dev/plugin/strategy-plugin
|
||||||
dev/plugin/action-plugin
|
dev/plugin/action-plugin
|
||||||
dev/plugin/planner-plugin
|
dev/plugin/planner-plugin
|
||||||
@@ -90,6 +91,8 @@ Introduction
|
|||||||
|
|
||||||
deploy/installation
|
deploy/installation
|
||||||
deploy/user-guide
|
deploy/user-guide
|
||||||
|
deploy/policy
|
||||||
|
deploy/gmr
|
||||||
|
|
||||||
Watcher Manual Pages
|
Watcher Manual Pages
|
||||||
====================
|
====================
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ run the following::
|
|||||||
|
|
||||||
Show the program's version number and exit.
|
Show the program's version number and exit.
|
||||||
|
|
||||||
.. option:: upgrade, downgrade, stamp, revision, version, create_schema
|
.. option:: upgrade, downgrade, stamp, revision, version, create_schema, purge
|
||||||
|
|
||||||
The :ref:`command <db-manage_cmds>` to run.
|
The :ref:`command <db-manage_cmds>` to run.
|
||||||
|
|
||||||
@@ -219,3 +219,42 @@ version
|
|||||||
Show help for version and exit.
|
Show help for version and exit.
|
||||||
|
|
||||||
This command will output the current database version.
|
This command will output the current database version.
|
||||||
|
|
||||||
|
purge
|
||||||
|
-----
|
||||||
|
|
||||||
|
.. program:: purge
|
||||||
|
|
||||||
|
.. option:: -h, --help
|
||||||
|
|
||||||
|
Show help for purge and exit.
|
||||||
|
|
||||||
|
.. option:: -d, --age-in-days
|
||||||
|
|
||||||
|
The number of days (starting from today) before which we consider soft
|
||||||
|
deleted objects as expired and should hence be erased. By default, all
|
||||||
|
objects soft deleted are considered expired. This can be useful as removing
|
||||||
|
a significant amount of objects may cause a performance issues.
|
||||||
|
|
||||||
|
.. option:: -n, --max-number
|
||||||
|
|
||||||
|
The maximum number of database objects we expect to be deleted. If exceeded,
|
||||||
|
this will prevent any deletion.
|
||||||
|
|
||||||
|
.. option:: -t, --audit-template
|
||||||
|
|
||||||
|
Either the UUID or name of the soft deleted audit template to purge. This
|
||||||
|
will also include any related objects with it.
|
||||||
|
|
||||||
|
.. option:: -e, --exclude-orphans
|
||||||
|
|
||||||
|
This is a flag to indicate when we want to exclude orphan objects from
|
||||||
|
deletion.
|
||||||
|
|
||||||
|
.. option:: --dry-run
|
||||||
|
|
||||||
|
This is a flag to indicate when we want to perform a dry run. This will show
|
||||||
|
the objects that would be deleted instead of actually deleting them.
|
||||||
|
|
||||||
|
This command will purge the current database by removing both its soft deleted
|
||||||
|
and orphan objects.
|
||||||
|
|||||||
4
etc/watcher/README-watcher.conf.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
To generate the sample watcher.conf file, run the following
|
||||||
|
command from the top level of the watcher directory:
|
||||||
|
|
||||||
|
tox -econfig
|
||||||
@@ -1,5 +1,37 @@
|
|||||||
{
|
{
|
||||||
"admin_api": "role:admin or role:administrator",
|
"admin_api": "role:admin or role:administrator",
|
||||||
"show_password": "!",
|
"show_password": "!",
|
||||||
"default": "rule:admin_api"
|
"default": "rule:admin_api",
|
||||||
|
|
||||||
|
"action:detail": "rule:default",
|
||||||
|
"action:get": "rule:default",
|
||||||
|
"action:get_all": "rule:default",
|
||||||
|
|
||||||
|
"action_plan:delete": "rule:default",
|
||||||
|
"action_plan:detail": "rule:default",
|
||||||
|
"action_plan:get": "rule:default",
|
||||||
|
"action_plan:get_all": "rule:default",
|
||||||
|
"action_plan:update": "rule:default",
|
||||||
|
|
||||||
|
"audit:create": "rule:default",
|
||||||
|
"audit:delete": "rule:default",
|
||||||
|
"audit:detail": "rule:default",
|
||||||
|
"audit:get": "rule:default",
|
||||||
|
"audit:get_all": "rule:default",
|
||||||
|
"audit:update": "rule:default",
|
||||||
|
|
||||||
|
"audit_template:create": "rule:default",
|
||||||
|
"audit_template:delete": "rule:default",
|
||||||
|
"audit_template:detail": "rule:default",
|
||||||
|
"audit_template:get": "rule:default",
|
||||||
|
"audit_template:get_all": "rule:default",
|
||||||
|
"audit_template:update": "rule:default",
|
||||||
|
|
||||||
|
"goal:detail": "rule:default",
|
||||||
|
"goal:get": "rule:default",
|
||||||
|
"goal:get_all": "rule:default",
|
||||||
|
|
||||||
|
"strategy:detail": "rule:default",
|
||||||
|
"strategy:get": "rule:default",
|
||||||
|
"strategy:get_all": "rule:default"
|
||||||
}
|
}
|
||||||
|
|||||||
16
etc/watcher/watcher-config-generator.conf
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
[DEFAULT]
|
||||||
|
output_file = etc/watcher/watcher.conf.sample
|
||||||
|
wrap_width = 79
|
||||||
|
|
||||||
|
namespace = watcher
|
||||||
|
namespace = keystonemiddleware.auth_token
|
||||||
|
namespace = oslo.cache
|
||||||
|
namespace = oslo.concurrency
|
||||||
|
namespace = oslo.db
|
||||||
|
namespace = oslo.log
|
||||||
|
namespace = oslo.messaging
|
||||||
|
namespace = oslo.policy
|
||||||
|
namespace = oslo.reports
|
||||||
|
namespace = oslo.service.periodic_task
|
||||||
|
namespace = oslo.service.service
|
||||||
|
namespace = oslo.service.wsgi
|
||||||
@@ -1,962 +0,0 @@
|
|||||||
[DEFAULT]
|
|
||||||
|
|
||||||
#
|
|
||||||
# From oslo.log
|
|
||||||
#
|
|
||||||
|
|
||||||
# Defines the format string for %(user_identity)s that is used in
|
|
||||||
# logging_context_format_string. (string value)
|
|
||||||
#logging_user_identity_format = %(user)s %(tenant)s %(domain)s %(user_domain)s %(project_domain)s
|
|
||||||
|
|
||||||
# List of package logging levels in logger=LEVEL pairs. This option is
|
|
||||||
# ignored if log_config_append is set. (list value)
|
|
||||||
#default_log_levels = amqp=WARN,amqplib=WARN,boto=WARN,qpid=WARN,sqlalchemy=WARN,suds=INFO,oslo.messaging=INFO,iso8601=WARN,requests.packages.urllib3.connectionpool=WARN,urllib3.connectionpool=WARN,websocket=WARN,requests.packages.urllib3.util.retry=WARN,urllib3.util.retry=WARN,keystonemiddleware=WARN,routes.middleware=WARN,stevedore=WARN,taskflow=WARN,keystoneauth=WARN
|
|
||||||
|
|
||||||
# Enables or disables publication of error events. (boolean value)
|
|
||||||
#publish_errors = false
|
|
||||||
|
|
||||||
# If set to true, the logging level will be set to DEBUG instead of
|
|
||||||
# the default INFO level. (boolean value)
|
|
||||||
#debug = false
|
|
||||||
|
|
||||||
# The format for an instance that is passed with the log message.
|
|
||||||
# (string value)
|
|
||||||
#instance_format = "[instance: %(uuid)s] "
|
|
||||||
|
|
||||||
# The format for an instance UUID that is passed with the log message.
|
|
||||||
# (string value)
|
|
||||||
#instance_uuid_format = "[instance: %(uuid)s] "
|
|
||||||
|
|
||||||
# If set to false, the logging level will be set to WARNING instead of
|
|
||||||
# the default INFO level. (boolean value)
|
|
||||||
# This option is deprecated for removal.
|
|
||||||
# Its value may be silently ignored in the future.
|
|
||||||
#verbose = true
|
|
||||||
|
|
||||||
# Enables or disables fatal status of deprecations. (boolean value)
|
|
||||||
#fatal_deprecations = false
|
|
||||||
|
|
||||||
# The name of a logging configuration file. This file is appended to
|
|
||||||
# any existing logging configuration files. For details about logging
|
|
||||||
# configuration files, see the Python logging module documentation.
|
|
||||||
# Note that when logging configuration files are used all logging
|
|
||||||
# configuration is defined in the configuration file and other logging
|
|
||||||
# configuration options are ignored (for example, log_format). (string
|
|
||||||
# value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/log_config
|
|
||||||
#log_config_append = <None>
|
|
||||||
|
|
||||||
# DEPRECATED. A logging.Formatter log message format string which may
|
|
||||||
# use any of the available logging.LogRecord attributes. This option
|
|
||||||
# is deprecated. Please use logging_context_format_string and
|
|
||||||
# logging_default_format_string instead. This option is ignored if
|
|
||||||
# log_config_append is set. (string value)
|
|
||||||
#log_format = <None>
|
|
||||||
|
|
||||||
# Defines the format string for %%(asctime)s in log records. Default:
|
|
||||||
# %(default)s . This option is ignored if log_config_append is set.
|
|
||||||
# (string value)
|
|
||||||
#log_date_format = %Y-%m-%d %H:%M:%S
|
|
||||||
|
|
||||||
# (Optional) Name of log file to send logging output to. If no default
|
|
||||||
# is set, logging will go to stderr as defined by use_stderr. This
|
|
||||||
# option is ignored if log_config_append is set. (string value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/logfile
|
|
||||||
#log_file = <None>
|
|
||||||
|
|
||||||
# (Optional) The base directory used for relative log_file paths.
|
|
||||||
# This option is ignored if log_config_append is set. (string value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/logdir
|
|
||||||
#log_dir = <None>
|
|
||||||
|
|
||||||
# Uses logging handler designed to watch file system. When log file is
|
|
||||||
# moved or removed this handler will open a new log file with
|
|
||||||
# specified path instantaneously. It makes sense only if log_file
|
|
||||||
# option is specified and Linux platform is used. This option is
|
|
||||||
# ignored if log_config_append is set. (boolean value)
|
|
||||||
#watch_log_file = false
|
|
||||||
|
|
||||||
# Use syslog for logging. Existing syslog format is DEPRECATED and
|
|
||||||
# will be changed later to honor RFC5424. This option is ignored if
|
|
||||||
# log_config_append is set. (boolean value)
|
|
||||||
#use_syslog = false
|
|
||||||
|
|
||||||
# Enables or disables syslog rfc5424 format for logging. If enabled,
|
|
||||||
# prefixes the MSG part of the syslog message with APP-NAME (RFC5424).
|
|
||||||
# The format without the APP-NAME is deprecated in Kilo, and will be
|
|
||||||
# removed in Mitaka, along with this option. This option is ignored if
|
|
||||||
# log_config_append is set. (boolean value)
|
|
||||||
# This option is deprecated for removal.
|
|
||||||
# Its value may be silently ignored in the future.
|
|
||||||
#use_syslog_rfc_format = true
|
|
||||||
|
|
||||||
# Syslog facility to receive log lines. This option is ignored if
|
|
||||||
# log_config_append is set. (string value)
|
|
||||||
#syslog_log_facility = LOG_USER
|
|
||||||
|
|
||||||
# Log output to standard error. This option is ignored if
|
|
||||||
# log_config_append is set. (boolean value)
|
|
||||||
#use_stderr = true
|
|
||||||
|
|
||||||
# Format string to use for log messages with context. (string value)
|
|
||||||
#logging_context_format_string = %(asctime)s.%(msecs)03d %(process)d %(levelname)s %(name)s [%(request_id)s %(user_identity)s] %(instance)s%(message)s
|
|
||||||
|
|
||||||
# Format string to use for log messages when context is undefined.
|
|
||||||
# (string value)
|
|
||||||
#logging_default_format_string = %(asctime)s.%(msecs)03d %(process)d %(levelname)s %(name)s [-] %(instance)s%(message)s
|
|
||||||
|
|
||||||
# Additional data to append to log message when logging level for the
|
|
||||||
# message is DEBUG. (string value)
|
|
||||||
#logging_debug_format_suffix = %(funcName)s %(pathname)s:%(lineno)d
|
|
||||||
|
|
||||||
# Prefix each line of exception output with this format. (string
|
|
||||||
# value)
|
|
||||||
#logging_exception_prefix = %(asctime)s.%(msecs)03d %(process)d ERROR %(name)s %(instance)s
|
|
||||||
|
|
||||||
#
|
|
||||||
# From oslo.messaging
|
|
||||||
#
|
|
||||||
|
|
||||||
# Size of executor thread pool. (integer value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/rpc_thread_pool_size
|
|
||||||
#executor_thread_pool_size = 64
|
|
||||||
|
|
||||||
# Minimal port number for random ports range. (port value)
|
|
||||||
# Minimum value: 0
|
|
||||||
# Maximum value: 65535
|
|
||||||
#rpc_zmq_min_port = 49152
|
|
||||||
|
|
||||||
# Seconds to wait for a response from a call. (integer value)
|
|
||||||
#rpc_response_timeout = 60
|
|
||||||
|
|
||||||
# Size of RPC connection pool. (integer value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/rpc_conn_pool_size
|
|
||||||
#rpc_conn_pool_size = 30
|
|
||||||
|
|
||||||
# A URL representing the messaging driver to use and its full
|
|
||||||
# configuration. If not set, we fall back to the rpc_backend option
|
|
||||||
# and driver specific configuration. (string value)
|
|
||||||
#transport_url = <None>
|
|
||||||
|
|
||||||
# Number of retries to find free port number before fail with
|
|
||||||
# ZMQBindError. (integer value)
|
|
||||||
#rpc_zmq_bind_port_retries = 100
|
|
||||||
|
|
||||||
# The messaging driver to use, defaults to rabbit. Other drivers
|
|
||||||
# include amqp and zmq. (string value)
|
|
||||||
#rpc_backend = rabbit
|
|
||||||
|
|
||||||
# Host to locate redis. (string value)
|
|
||||||
#host = 127.0.0.1
|
|
||||||
|
|
||||||
# ZeroMQ bind address. Should be a wildcard (*), an ethernet
|
|
||||||
# interface, or IP. The "host" option should point or resolve to this
|
|
||||||
# address. (string value)
|
|
||||||
#rpc_zmq_bind_address = *
|
|
||||||
|
|
||||||
# MatchMaker driver. (string value)
|
|
||||||
#rpc_zmq_matchmaker = redis
|
|
||||||
|
|
||||||
# The default exchange under which topics are scoped. May be
|
|
||||||
# overridden by an exchange name specified in the transport_url
|
|
||||||
# option. (string value)
|
|
||||||
#control_exchange = openstack
|
|
||||||
|
|
||||||
# Use this port to connect to redis host. (port value)
|
|
||||||
# Minimum value: 0
|
|
||||||
# Maximum value: 65535
|
|
||||||
#port = 6379
|
|
||||||
|
|
||||||
# Type of concurrency used. Either "native" or "eventlet" (string
|
|
||||||
# value)
|
|
||||||
#rpc_zmq_concurrency = eventlet
|
|
||||||
|
|
||||||
# Password for Redis server (optional). (string value)
|
|
||||||
#password =
|
|
||||||
|
|
||||||
# Number of ZeroMQ contexts, defaults to 1. (integer value)
|
|
||||||
#rpc_zmq_contexts = 1
|
|
||||||
|
|
||||||
# Maximum number of ingress messages to locally buffer per topic.
|
|
||||||
# Default is unlimited. (integer value)
|
|
||||||
#rpc_zmq_topic_backlog = <None>
|
|
||||||
|
|
||||||
# List of Redis Sentinel hosts (fault tolerance mode) e.g.
|
|
||||||
# [host:port, host1:port ... ] (list value)
|
|
||||||
#sentinel_hosts =
|
|
||||||
|
|
||||||
# Directory for holding IPC sockets. (string value)
|
|
||||||
#rpc_zmq_ipc_dir = /var/run/openstack
|
|
||||||
|
|
||||||
# Name of this node. Must be a valid hostname, FQDN, or IP address.
|
|
||||||
# Must match "host" option, if running Nova. (string value)
|
|
||||||
#rpc_zmq_host = localhost
|
|
||||||
|
|
||||||
# Redis replica set name. (string value)
|
|
||||||
#sentinel_group_name = oslo-messaging-zeromq
|
|
||||||
|
|
||||||
# Seconds to wait before a cast expires (TTL). Only supported by
|
|
||||||
# impl_zmq. (integer value)
|
|
||||||
#rpc_cast_timeout = 30
|
|
||||||
|
|
||||||
# Time in ms to wait between connection attempts. (integer value)
|
|
||||||
#wait_timeout = 500
|
|
||||||
|
|
||||||
# The default number of seconds that poll should wait. Poll raises
|
|
||||||
# timeout exception when timeout expired. (integer value)
|
|
||||||
#rpc_poll_timeout = 1
|
|
||||||
|
|
||||||
# Time in ms to wait before the transaction is killed. (integer value)
|
|
||||||
#check_timeout = 20000
|
|
||||||
|
|
||||||
# Expiration timeout in seconds of a name service record about
|
|
||||||
# existing target ( < 0 means no timeout). (integer value)
|
|
||||||
#zmq_target_expire = 120
|
|
||||||
|
|
||||||
# Timeout in ms on blocking socket operations (integer value)
|
|
||||||
#socket_timeout = 1000
|
|
||||||
|
|
||||||
# Maximal port number for random ports range. (integer value)
|
|
||||||
# Minimum value: 1
|
|
||||||
# Maximum value: 65536
|
|
||||||
#rpc_zmq_max_port = 65536
|
|
||||||
|
|
||||||
# Use PUB/SUB pattern for fanout methods. PUB/SUB always uses proxy.
|
|
||||||
# (boolean value)
|
|
||||||
#use_pub_sub = true
|
|
||||||
|
|
||||||
|
|
||||||
[api]
|
|
||||||
|
|
||||||
#
|
|
||||||
# From watcher
|
|
||||||
#
|
|
||||||
|
|
||||||
# The port for the watcher API server (integer value)
|
|
||||||
#port = 9322
|
|
||||||
|
|
||||||
# The maximum number of items returned in a single response from a
|
|
||||||
# collection resource. (integer value)
|
|
||||||
#max_limit = 1000
|
|
||||||
|
|
||||||
# The listen IP for the watcher API server (string value)
|
|
||||||
#host = 0.0.0.0
|
|
||||||
|
|
||||||
|
|
||||||
[ceilometer_client]
|
|
||||||
|
|
||||||
#
|
|
||||||
# From watcher
|
|
||||||
#
|
|
||||||
|
|
||||||
# Version of Ceilometer API to use in ceilometerclient. (string value)
|
|
||||||
#api_version = 2
|
|
||||||
|
|
||||||
|
|
||||||
[cinder_client]
|
|
||||||
|
|
||||||
#
|
|
||||||
# From watcher
|
|
||||||
#
|
|
||||||
|
|
||||||
# Version of Cinder API to use in cinderclient. (string value)
|
|
||||||
#api_version = 2
|
|
||||||
|
|
||||||
|
|
||||||
[database]
|
|
||||||
|
|
||||||
#
|
|
||||||
# From oslo.db
|
|
||||||
#
|
|
||||||
|
|
||||||
# If set, use this value for max_overflow with SQLAlchemy. (integer
|
|
||||||
# value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/sql_max_overflow
|
|
||||||
# Deprecated group/name - [DATABASE]/sqlalchemy_max_overflow
|
|
||||||
#max_overflow = <None>
|
|
||||||
|
|
||||||
# Add Python stack traces to SQL as comment strings. (boolean value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/sql_connection_trace
|
|
||||||
#connection_trace = false
|
|
||||||
|
|
||||||
# The SQLAlchemy connection string to use to connect to the database.
|
|
||||||
# (string value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/sql_connection
|
|
||||||
# Deprecated group/name - [DATABASE]/sql_connection
|
|
||||||
# Deprecated group/name - [sql]/connection
|
|
||||||
#connection = <None>
|
|
||||||
|
|
||||||
# If db_inc_retry_interval is set, the maximum seconds between retries
|
|
||||||
# of a database operation. (integer value)
|
|
||||||
#db_max_retry_interval = 10
|
|
||||||
|
|
||||||
# Interval between retries of opening a SQL connection. (integer
|
|
||||||
# value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/sql_retry_interval
|
|
||||||
# Deprecated group/name - [DATABASE]/reconnect_interval
|
|
||||||
#retry_interval = 10
|
|
||||||
|
|
||||||
# The file name to use with SQLite. (string value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/sqlite_db
|
|
||||||
#sqlite_db = oslo.sqlite
|
|
||||||
|
|
||||||
# Timeout before idle SQL connections are reaped. (integer value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/sql_idle_timeout
|
|
||||||
# Deprecated group/name - [DATABASE]/sql_idle_timeout
|
|
||||||
# Deprecated group/name - [sql]/idle_timeout
|
|
||||||
#idle_timeout = 3600
|
|
||||||
|
|
||||||
# If set, use this value for pool_timeout with SQLAlchemy. (integer
|
|
||||||
# value)
|
|
||||||
# Deprecated group/name - [DATABASE]/sqlalchemy_pool_timeout
|
|
||||||
#pool_timeout = <None>
|
|
||||||
|
|
||||||
# Maximum number of database connection retries during startup. Set to
|
|
||||||
# -1 to specify an infinite retry count. (integer value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/sql_max_retries
|
|
||||||
# Deprecated group/name - [DATABASE]/sql_max_retries
|
|
||||||
#max_retries = 10
|
|
||||||
|
|
||||||
# Maximum retries in case of connection error or deadlock error before
|
|
||||||
# error is raised. Set to -1 to specify an infinite retry count.
|
|
||||||
# (integer value)
|
|
||||||
#db_max_retries = 20
|
|
||||||
|
|
||||||
# Enable the experimental use of database reconnect on connection
|
|
||||||
# lost. (boolean value)
|
|
||||||
#use_db_reconnect = false
|
|
||||||
|
|
||||||
# The SQLAlchemy connection string to use to connect to the slave
|
|
||||||
# database. (string value)
|
|
||||||
#slave_connection = <None>
|
|
||||||
|
|
||||||
# Minimum number of SQL connections to keep open in a pool. (integer
|
|
||||||
# value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/sql_min_pool_size
|
|
||||||
# Deprecated group/name - [DATABASE]/sql_min_pool_size
|
|
||||||
#min_pool_size = 1
|
|
||||||
|
|
||||||
# Maximum number of SQL connections to keep open in a pool. (integer
|
|
||||||
# value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/sql_max_pool_size
|
|
||||||
# Deprecated group/name - [DATABASE]/sql_max_pool_size
|
|
||||||
#max_pool_size = <None>
|
|
||||||
|
|
||||||
# If True, SQLite uses synchronous mode. (boolean value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/sqlite_synchronous
|
|
||||||
#sqlite_synchronous = true
|
|
||||||
|
|
||||||
# The SQL mode to be used for MySQL sessions. This option, including
|
|
||||||
# the default, overrides any server-set SQL mode. To use whatever SQL
|
|
||||||
# mode is set by the server configuration, set this to no value.
|
|
||||||
# Example: mysql_sql_mode= (string value)
|
|
||||||
#mysql_sql_mode = TRADITIONAL
|
|
||||||
|
|
||||||
# Seconds between retries of a database transaction. (integer value)
|
|
||||||
#db_retry_interval = 1
|
|
||||||
|
|
||||||
# Verbosity of SQL debugging information: 0=None, 100=Everything.
|
|
||||||
# (integer value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/sql_connection_debug
|
|
||||||
#connection_debug = 0
|
|
||||||
|
|
||||||
# The back end to use for the database. (string value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/db_backend
|
|
||||||
#backend = sqlalchemy
|
|
||||||
|
|
||||||
# If True, increases the interval between retries of a database
|
|
||||||
# operation up to db_max_retry_interval. (boolean value)
|
|
||||||
#db_inc_retry_interval = true
|
|
||||||
|
|
||||||
|
|
||||||
[glance_client]
|
|
||||||
|
|
||||||
#
|
|
||||||
# From watcher
|
|
||||||
#
|
|
||||||
|
|
||||||
# Version of Glance API to use in glanceclient. (string value)
|
|
||||||
#api_version = 2
|
|
||||||
|
|
||||||
|
|
||||||
[keystone_authtoken]
|
|
||||||
|
|
||||||
#
|
|
||||||
# From keystonemiddleware.auth_token
|
|
||||||
#
|
|
||||||
|
|
||||||
# (Optional, mandatory if memcache_security_strategy is defined) This
|
|
||||||
# string is used for key derivation. (string value)
|
|
||||||
#memcache_secret_key = <None>
|
|
||||||
|
|
||||||
# In order to prevent excessive effort spent validating tokens, the
|
|
||||||
# middleware caches previously-seen tokens for a configurable duration
|
|
||||||
# (in seconds). Set to -1 to disable caching completely. (integer
|
|
||||||
# value)
|
|
||||||
#token_cache_time = 300
|
|
||||||
|
|
||||||
# Determines the frequency at which the list of revoked tokens is
|
|
||||||
# retrieved from the Identity service (in seconds). A high number of
|
|
||||||
# revocation events combined with a low cache duration may
|
|
||||||
# significantly reduce performance. (integer value)
|
|
||||||
#revocation_cache_time = 10
|
|
||||||
|
|
||||||
# (Optional) If defined, indicate whether token data should be
|
|
||||||
# authenticated or authenticated and encrypted. If MAC, token data is
|
|
||||||
# authenticated (with HMAC) in the cache. If ENCRYPT, token data is
|
|
||||||
# encrypted and authenticated in the cache. If the value is not one of
|
|
||||||
# these options or empty, auth_token will raise an exception on
|
|
||||||
# initialization. (string value)
|
|
||||||
# Allowed values: None, MAC, ENCRYPT
|
|
||||||
#memcache_security_strategy = None
|
|
||||||
|
|
||||||
# (Optional) Number of seconds memcached server is considered dead
|
|
||||||
# before it is tried again. (integer value)
|
|
||||||
#memcache_pool_dead_retry = 300
|
|
||||||
|
|
||||||
# (Optional) Maximum total number of open connections to every
|
|
||||||
# memcached server. (integer value)
|
|
||||||
#memcache_pool_maxsize = 10
|
|
||||||
|
|
||||||
# Complete public Identity API endpoint. (string value)
|
|
||||||
#auth_uri = <None>
|
|
||||||
|
|
||||||
# (Optional) Socket timeout in seconds for communicating with a
|
|
||||||
# memcached server. (integer value)
|
|
||||||
#memcache_pool_socket_timeout = 3
|
|
||||||
|
|
||||||
# (Optional) Number of seconds a connection to memcached is held
|
|
||||||
# unused in the pool before it is closed. (integer value)
|
|
||||||
#memcache_pool_unused_timeout = 60
|
|
||||||
|
|
||||||
# API version of the admin Identity API endpoint. (string value)
|
|
||||||
#auth_version = <None>
|
|
||||||
|
|
||||||
# (Optional) Number of seconds that an operation will wait to get a
|
|
||||||
# memcached client connection from the pool. (integer value)
|
|
||||||
#memcache_pool_conn_get_timeout = 10
|
|
||||||
|
|
||||||
# Do not handle authorization requests within the middleware, but
|
|
||||||
# delegate the authorization decision to downstream WSGI components.
|
|
||||||
# (boolean value)
|
|
||||||
#delay_auth_decision = false
|
|
||||||
|
|
||||||
# (Optional) Use the advanced (eventlet safe) memcached client pool.
|
|
||||||
# The advanced pool will only work under python 2.x. (boolean value)
|
|
||||||
#memcache_use_advanced_pool = false
|
|
||||||
|
|
||||||
# Request timeout value for communicating with Identity API server.
|
|
||||||
# (integer value)
|
|
||||||
#http_connect_timeout = <None>
|
|
||||||
|
|
||||||
# (Optional) Indicate whether to set the X-Service-Catalog header. If
|
|
||||||
# False, middleware will not ask for service catalog on token
|
|
||||||
# validation and will not set the X-Service-Catalog header. (boolean
|
|
||||||
# value)
|
|
||||||
#include_service_catalog = true
|
|
||||||
|
|
||||||
# How many times are we trying to reconnect when communicating with
|
|
||||||
# Identity API Server. (integer value)
|
|
||||||
#http_request_max_retries = 3
|
|
||||||
|
|
||||||
# Used to control the use and type of token binding. Can be set to:
|
|
||||||
# "disabled" to not check token binding. "permissive" (default) to
|
|
||||||
# validate binding information if the bind type is of a form known to
|
|
||||||
# the server and ignore it if not. "strict" like "permissive" but if
|
|
||||||
# the bind type is unknown the token will be rejected. "required" any
|
|
||||||
# form of token binding is needed to be allowed. Finally the name of a
|
|
||||||
# binding method that must be present in tokens. (string value)
|
|
||||||
#enforce_token_bind = permissive
|
|
||||||
|
|
||||||
# Env key for the swift cache. (string value)
|
|
||||||
#cache = <None>
|
|
||||||
|
|
||||||
# If true, the revocation list will be checked for cached tokens. This
|
|
||||||
# requires that PKI tokens are configured on the identity server.
|
|
||||||
# (boolean value)
|
|
||||||
#check_revocations_for_cached = false
|
|
||||||
|
|
||||||
# Required if identity server requires client certificate (string
|
|
||||||
# value)
|
|
||||||
#certfile = <None>
|
|
||||||
|
|
||||||
# Hash algorithms to use for hashing PKI tokens. This may be a single
|
|
||||||
# algorithm or multiple. The algorithms are those supported by Python
|
|
||||||
# standard hashlib.new(). The hashes will be tried in the order given,
|
|
||||||
# so put the preferred one first for performance. The result of the
|
|
||||||
# first hash will be stored in the cache. This will typically be set
|
|
||||||
# to multiple values only while migrating from a less secure algorithm
|
|
||||||
# to a more secure one. Once all the old tokens are expired this
|
|
||||||
# option should be set to a single value for better performance. (list
|
|
||||||
# value)
|
|
||||||
#hash_algorithms = md5
|
|
||||||
|
|
||||||
# Required if identity server requires client certificate (string
|
|
||||||
# value)
|
|
||||||
#keyfile = <None>
|
|
||||||
|
|
||||||
# A PEM encoded Certificate Authority to use when verifying HTTPs
|
|
||||||
# connections. Defaults to system CAs. (string value)
|
|
||||||
#cafile = <None>
|
|
||||||
|
|
||||||
# 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>
|
|
||||||
|
|
||||||
# Verify HTTPS connections. (boolean value)
|
|
||||||
#insecure = false
|
|
||||||
|
|
||||||
# The region in which the identity server can be found. (string value)
|
|
||||||
#region_name = <None>
|
|
||||||
|
|
||||||
# Directory used to cache files related to PKI tokens. (string value)
|
|
||||||
#signing_dir = <None>
|
|
||||||
|
|
||||||
# Optionally specify a list of memcached server(s) to use for caching.
|
|
||||||
# If left undefined, tokens will instead be cached in-process. (list
|
|
||||||
# value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/memcache_servers
|
|
||||||
#memcached_servers = <None>
|
|
||||||
|
|
||||||
|
|
||||||
[matchmaker_redis]
|
|
||||||
|
|
||||||
#
|
|
||||||
# From oslo.messaging
|
|
||||||
#
|
|
||||||
|
|
||||||
# Time in ms to wait before the transaction is killed. (integer value)
|
|
||||||
#check_timeout = 20000
|
|
||||||
|
|
||||||
# Password for Redis server (optional). (string value)
|
|
||||||
#password =
|
|
||||||
|
|
||||||
# Timeout in ms on blocking socket operations (integer value)
|
|
||||||
#socket_timeout = 1000
|
|
||||||
|
|
||||||
# List of Redis Sentinel hosts (fault tolerance mode) e.g.
|
|
||||||
# [host:port, host1:port ... ] (list value)
|
|
||||||
#sentinel_hosts =
|
|
||||||
|
|
||||||
# Redis replica set name. (string value)
|
|
||||||
#sentinel_group_name = oslo-messaging-zeromq
|
|
||||||
|
|
||||||
# Host to locate redis. (string value)
|
|
||||||
#host = 127.0.0.1
|
|
||||||
|
|
||||||
# Time in ms to wait between connection attempts. (integer value)
|
|
||||||
#wait_timeout = 500
|
|
||||||
|
|
||||||
# Use this port to connect to redis host. (port value)
|
|
||||||
# Minimum value: 0
|
|
||||||
# Maximum value: 65535
|
|
||||||
#port = 6379
|
|
||||||
|
|
||||||
|
|
||||||
[neutron_client]
|
|
||||||
|
|
||||||
#
|
|
||||||
# From watcher
|
|
||||||
#
|
|
||||||
|
|
||||||
# Version of Neutron API to use in neutronclient. (string value)
|
|
||||||
#api_version = 2
|
|
||||||
|
|
||||||
|
|
||||||
[nova_client]
|
|
||||||
|
|
||||||
#
|
|
||||||
# From watcher
|
|
||||||
#
|
|
||||||
|
|
||||||
# Version of Nova API to use in novaclient. (string value)
|
|
||||||
#api_version = 2
|
|
||||||
|
|
||||||
|
|
||||||
[oslo_messaging_amqp]
|
|
||||||
|
|
||||||
#
|
|
||||||
# From oslo.messaging
|
|
||||||
#
|
|
||||||
|
|
||||||
# CA certificate PEM file to verify server certificate (string value)
|
|
||||||
# Deprecated group/name - [amqp1]/ssl_ca_file
|
|
||||||
#ssl_ca_file =
|
|
||||||
|
|
||||||
# Private key PEM file used to sign cert_file certificate (string
|
|
||||||
# value)
|
|
||||||
# Deprecated group/name - [amqp1]/ssl_key_file
|
|
||||||
#ssl_key_file =
|
|
||||||
|
|
||||||
# User name for message broker authentication (string value)
|
|
||||||
# Deprecated group/name - [amqp1]/username
|
|
||||||
#username =
|
|
||||||
|
|
||||||
# Name for the AMQP container (string value)
|
|
||||||
# Deprecated group/name - [amqp1]/container_name
|
|
||||||
#container_name = <None>
|
|
||||||
|
|
||||||
# Space separated list of acceptable SASL mechanisms (string value)
|
|
||||||
# Deprecated group/name - [amqp1]/sasl_mechanisms
|
|
||||||
#sasl_mechanisms =
|
|
||||||
|
|
||||||
# address prefix used when sending to a specific server (string value)
|
|
||||||
# Deprecated group/name - [amqp1]/server_request_prefix
|
|
||||||
#server_request_prefix = exclusive
|
|
||||||
|
|
||||||
# Password for decrypting ssl_key_file (if encrypted) (string value)
|
|
||||||
# Deprecated group/name - [amqp1]/ssl_key_password
|
|
||||||
#ssl_key_password = <None>
|
|
||||||
|
|
||||||
# Timeout for inactive connections (in seconds) (integer value)
|
|
||||||
# Deprecated group/name - [amqp1]/idle_timeout
|
|
||||||
#idle_timeout = 0
|
|
||||||
|
|
||||||
# Identifying certificate PEM file to present to clients (string
|
|
||||||
# value)
|
|
||||||
# Deprecated group/name - [amqp1]/ssl_cert_file
|
|
||||||
#ssl_cert_file =
|
|
||||||
|
|
||||||
# address prefix used when broadcasting to all servers (string value)
|
|
||||||
# Deprecated group/name - [amqp1]/broadcast_prefix
|
|
||||||
#broadcast_prefix = broadcast
|
|
||||||
|
|
||||||
# Debug: dump AMQP frames to stdout (boolean value)
|
|
||||||
# Deprecated group/name - [amqp1]/trace
|
|
||||||
#trace = false
|
|
||||||
|
|
||||||
# Password for message broker authentication (string value)
|
|
||||||
# Deprecated group/name - [amqp1]/password
|
|
||||||
#password =
|
|
||||||
|
|
||||||
# Accept clients using either SSL or plain TCP (boolean value)
|
|
||||||
# Deprecated group/name - [amqp1]/allow_insecure_clients
|
|
||||||
#allow_insecure_clients = false
|
|
||||||
|
|
||||||
# Name of configuration file (without .conf suffix) (string value)
|
|
||||||
# Deprecated group/name - [amqp1]/sasl_config_name
|
|
||||||
#sasl_config_name =
|
|
||||||
|
|
||||||
# Path to directory that contains the SASL configuration (string
|
|
||||||
# value)
|
|
||||||
# Deprecated group/name - [amqp1]/sasl_config_dir
|
|
||||||
#sasl_config_dir =
|
|
||||||
|
|
||||||
# address prefix when sending to any server in group (string value)
|
|
||||||
# Deprecated group/name - [amqp1]/group_request_prefix
|
|
||||||
#group_request_prefix = unicast
|
|
||||||
|
|
||||||
|
|
||||||
[oslo_messaging_notifications]
|
|
||||||
|
|
||||||
#
|
|
||||||
# From oslo.messaging
|
|
||||||
#
|
|
||||||
|
|
||||||
# The Drivers(s) to handle sending notifications. Possible values are
|
|
||||||
# messaging, messagingv2, routing, log, test, noop (multi valued)
|
|
||||||
# Deprecated group/name - [DEFAULT]/notification_driver
|
|
||||||
#driver =
|
|
||||||
|
|
||||||
# A URL representing the messaging driver to use for notifications. If
|
|
||||||
# not set, we fall back to the same configuration used for RPC.
|
|
||||||
# (string value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/notification_transport_url
|
|
||||||
#transport_url = <None>
|
|
||||||
|
|
||||||
# AMQP topic used for OpenStack notifications. (list value)
|
|
||||||
# Deprecated group/name - [rpc_notifier2]/topics
|
|
||||||
# Deprecated group/name - [DEFAULT]/notification_topics
|
|
||||||
#topics = notifications
|
|
||||||
|
|
||||||
|
|
||||||
[oslo_messaging_rabbit]
|
|
||||||
|
|
||||||
#
|
|
||||||
# From oslo.messaging
|
|
||||||
#
|
|
||||||
|
|
||||||
# How often times during the heartbeat_timeout_threshold we check the
|
|
||||||
# heartbeat. (integer value)
|
|
||||||
#heartbeat_rate = 2
|
|
||||||
|
|
||||||
# Connect over SSL for RabbitMQ. (boolean value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/rabbit_use_ssl
|
|
||||||
#rabbit_use_ssl = false
|
|
||||||
|
|
||||||
# Deprecated, use rpc_backend=kombu+memory or rpc_backend=fake
|
|
||||||
# (boolean value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/fake_rabbit
|
|
||||||
#fake_rabbit = false
|
|
||||||
|
|
||||||
# The RabbitMQ userid. (string value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/rabbit_userid
|
|
||||||
#rabbit_userid = guest
|
|
||||||
|
|
||||||
# The RabbitMQ broker address where a single node is used. (string
|
|
||||||
# value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/rabbit_host
|
|
||||||
#rabbit_host = localhost
|
|
||||||
|
|
||||||
# The RabbitMQ password. (string value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/rabbit_password
|
|
||||||
#rabbit_password = guest
|
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
# The RabbitMQ login method. (string value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/rabbit_login_method
|
|
||||||
#rabbit_login_method = AMQPLAIN
|
|
||||||
|
|
||||||
# Maximum number of RabbitMQ connection retries. Default is 0
|
|
||||||
# (infinite retry count). (integer value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/rabbit_max_retries
|
|
||||||
#rabbit_max_retries = 0
|
|
||||||
|
|
||||||
# Auto-delete queues in AMQP. (boolean value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/amqp_auto_delete
|
|
||||||
#amqp_auto_delete = false
|
|
||||||
|
|
||||||
# The RabbitMQ virtual host. (string value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/rabbit_virtual_host
|
|
||||||
#rabbit_virtual_host = /
|
|
||||||
|
|
||||||
# SSL version to use (valid only if SSL enabled). Valid values are
|
|
||||||
# TLSv1 and SSLv23. SSLv2, SSLv3, TLSv1_1, and TLSv1_2 may be
|
|
||||||
# available on some distributions. (string value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/kombu_ssl_version
|
|
||||||
#kombu_ssl_version =
|
|
||||||
|
|
||||||
# How frequently to retry connecting with RabbitMQ. (integer value)
|
|
||||||
#rabbit_retry_interval = 1
|
|
||||||
|
|
||||||
# SSL key file (valid only if SSL enabled). (string value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/kombu_ssl_keyfile
|
|
||||||
#kombu_ssl_keyfile =
|
|
||||||
|
|
||||||
# Maximum interval of RabbitMQ connection retries. Default is 30
|
|
||||||
# seconds. (integer value)
|
|
||||||
#rabbit_interval_max = 30
|
|
||||||
|
|
||||||
# How long to backoff for between retries when connecting to RabbitMQ.
|
|
||||||
# (integer value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/rabbit_retry_backoff
|
|
||||||
#rabbit_retry_backoff = 2
|
|
||||||
|
|
||||||
# SSL certification authority file (valid only if SSL enabled).
|
|
||||||
# (string value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/kombu_ssl_ca_certs
|
|
||||||
#kombu_ssl_ca_certs =
|
|
||||||
|
|
||||||
# Positive integer representing duration in seconds for queue TTL
|
|
||||||
# (x-expires). Queues which are unused for the duration of the TTL are
|
|
||||||
# automatically deleted. The parameter affects only reply and fanout
|
|
||||||
# queues. (integer value)
|
|
||||||
# Minimum value: 1
|
|
||||||
#rabbit_transient_queues_ttl = 600
|
|
||||||
|
|
||||||
# How long to wait before reconnecting in response to an AMQP consumer
|
|
||||||
# cancel notification. (floating point value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/kombu_reconnect_delay
|
|
||||||
#kombu_reconnect_delay = 1.0
|
|
||||||
|
|
||||||
# Use HA queues in RabbitMQ (x-ha-policy: all). If you change this
|
|
||||||
# option, you must wipe the RabbitMQ database. (boolean value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/rabbit_ha_queues
|
|
||||||
#rabbit_ha_queues = false
|
|
||||||
|
|
||||||
# How long to wait a missing client beforce abandoning to send it its
|
|
||||||
# replies. This value should not be longer than rpc_response_timeout.
|
|
||||||
# (integer value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/kombu_reconnect_timeout
|
|
||||||
#kombu_missing_consumer_retry_timeout = 60
|
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
# Specifies the number of messages to prefetch. Setting to zero allows
|
|
||||||
# unlimited messages. (integer value)
|
|
||||||
#rabbit_qos_prefetch_count = 0
|
|
||||||
|
|
||||||
# The RabbitMQ broker port where a single node is used. (port value)
|
|
||||||
# Minimum value: 0
|
|
||||||
# Maximum value: 65535
|
|
||||||
# Deprecated group/name - [DEFAULT]/rabbit_port
|
|
||||||
#rabbit_port = 5672
|
|
||||||
|
|
||||||
# SSL cert file (valid only if SSL enabled). (string value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/kombu_ssl_certfile
|
|
||||||
#kombu_ssl_certfile =
|
|
||||||
|
|
||||||
# Number of seconds after which the Rabbit broker is considered down
|
|
||||||
# if heartbeat's keep-alive fails (0 disable the heartbeat).
|
|
||||||
# EXPERIMENTAL (integer value)
|
|
||||||
#heartbeat_timeout_threshold = 60
|
|
||||||
|
|
||||||
# RabbitMQ HA cluster host:port pairs. (list value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/rabbit_hosts
|
|
||||||
#rabbit_hosts = $rabbit_host:$rabbit_port
|
|
||||||
|
|
||||||
|
|
||||||
[watcher_applier]
|
|
||||||
|
|
||||||
#
|
|
||||||
# From watcher
|
|
||||||
#
|
|
||||||
|
|
||||||
# The topic name used for status events, this topic is used so as to
|
|
||||||
# notifythe others components of the system (string value)
|
|
||||||
#status_topic = watcher.applier.status
|
|
||||||
|
|
||||||
# Select the engine to use to execute the workflow (string value)
|
|
||||||
#workflow_engine = taskflow
|
|
||||||
|
|
||||||
# The topic name used forcontrol events, this topic used for rpc call
|
|
||||||
# (string value)
|
|
||||||
#conductor_topic = watcher.applier.control
|
|
||||||
|
|
||||||
# Number of workers for applier, default value is 1. (integer value)
|
|
||||||
# Minimum value: 1
|
|
||||||
#workers = 1
|
|
||||||
|
|
||||||
# The identifier used by watcher module on the message broker (string
|
|
||||||
# value)
|
|
||||||
#publisher_id = watcher.applier.api
|
|
||||||
|
|
||||||
|
|
||||||
[watcher_clients_auth]
|
|
||||||
|
|
||||||
#
|
|
||||||
# From watcher
|
|
||||||
#
|
|
||||||
|
|
||||||
# Optional domain name to use with v3 API and v2 parameters. It will
|
|
||||||
# be used for both the user and project domain in v3 and ignored in v2
|
|
||||||
# authentication. (unknown value)
|
|
||||||
#default_domain_name = <None>
|
|
||||||
|
|
||||||
# Authentication URL (unknown value)
|
|
||||||
#auth_url = <None>
|
|
||||||
|
|
||||||
# Domain ID to scope to (unknown value)
|
|
||||||
#domain_id = <None>
|
|
||||||
|
|
||||||
# Domain name to scope to (unknown value)
|
|
||||||
#domain_name = <None>
|
|
||||||
|
|
||||||
# Project ID to scope to (unknown value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/tenant-id
|
|
||||||
#project_id = <None>
|
|
||||||
|
|
||||||
# Project name to scope to (unknown value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/tenant-name
|
|
||||||
#project_name = <None>
|
|
||||||
|
|
||||||
# Domain ID containing project (unknown value)
|
|
||||||
#project_domain_id = <None>
|
|
||||||
|
|
||||||
# PEM encoded client certificate cert file (string value)
|
|
||||||
#certfile = <None>
|
|
||||||
|
|
||||||
# Domain name containing project (unknown value)
|
|
||||||
#project_domain_name = <None>
|
|
||||||
|
|
||||||
# Trust ID (unknown value)
|
|
||||||
#trust_id = <None>
|
|
||||||
|
|
||||||
# Optional domain ID to use with v3 and v2 parameters. It will be used
|
|
||||||
# for both the user and project domain in v3 and ignored in v2
|
|
||||||
# authentication. (unknown value)
|
|
||||||
#default_domain_id = <None>
|
|
||||||
|
|
||||||
# Verify HTTPS connections. (boolean value)
|
|
||||||
#insecure = false
|
|
||||||
|
|
||||||
# User id (unknown value)
|
|
||||||
#user_id = <None>
|
|
||||||
|
|
||||||
# PEM encoded client certificate key file (string value)
|
|
||||||
#keyfile = <None>
|
|
||||||
|
|
||||||
# Username (unknown value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/username
|
|
||||||
#username = <None>
|
|
||||||
|
|
||||||
# User's domain id (unknown value)
|
|
||||||
#user_domain_id = <None>
|
|
||||||
|
|
||||||
# User's domain name (unknown value)
|
|
||||||
#user_domain_name = <None>
|
|
||||||
|
|
||||||
# Timeout value for http requests (integer value)
|
|
||||||
#timeout = <None>
|
|
||||||
|
|
||||||
# User's password (unknown value)
|
|
||||||
#password = <None>
|
|
||||||
|
|
||||||
# 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>
|
|
||||||
|
|
||||||
# PEM encoded Certificate Authority to use when verifying HTTPs
|
|
||||||
# connections. (string value)
|
|
||||||
#cafile = <None>
|
|
||||||
|
|
||||||
|
|
||||||
[watcher_decision_engine]
|
|
||||||
|
|
||||||
#
|
|
||||||
# From watcher
|
|
||||||
#
|
|
||||||
|
|
||||||
# The maximum number of threads that can be used to execute strategies
|
|
||||||
# (integer value)
|
|
||||||
#max_workers = 2
|
|
||||||
|
|
||||||
# The topic name used for status events, this topic is used so as to
|
|
||||||
# notifythe others components of the system (string value)
|
|
||||||
#status_topic = watcher.decision.status
|
|
||||||
|
|
||||||
# The topic name used forcontrol events, this topic used for rpc call
|
|
||||||
# (string value)
|
|
||||||
#conductor_topic = watcher.decision.control
|
|
||||||
|
|
||||||
# The identifier used by watcher module on the message broker (string
|
|
||||||
# value)
|
|
||||||
#publisher_id = watcher.decision.api
|
|
||||||
|
|
||||||
|
|
||||||
[watcher_goals]
|
|
||||||
|
|
||||||
#
|
|
||||||
# From watcher
|
|
||||||
#
|
|
||||||
|
|
||||||
# Goals used for the optimization. Maps each goal to an associated
|
|
||||||
# strategy (for example: BASIC_CONSOLIDATION:basic,
|
|
||||||
# MY_GOAL:my_strategy_1) (dict value)
|
|
||||||
#goals = DUMMY:dummy
|
|
||||||
|
|
||||||
|
|
||||||
[watcher_planner]
|
|
||||||
|
|
||||||
#
|
|
||||||
# From watcher
|
|
||||||
#
|
|
||||||
|
|
||||||
# The selected planner used to schedule the actions (string value)
|
|
||||||
#planner = default
|
|
||||||
244
releasenotes/source/conf.py
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# watcher documentation build configuration file, created by
|
||||||
|
# sphinx-quickstart on Fri Jun 3 11:37:52 2016.
|
||||||
|
#
|
||||||
|
# This file is execfile()d with the current directory set to its containing dir
|
||||||
|
#
|
||||||
|
# Note that not all possible configuration values are present in this
|
||||||
|
# autogenerated file.
|
||||||
|
#
|
||||||
|
# All configuration values have a default; values that are commented out
|
||||||
|
# serve to show the default.
|
||||||
|
|
||||||
|
import sys, os
|
||||||
|
from watcher import version as watcher_version
|
||||||
|
|
||||||
|
# If extensions (or modules to document with autodoc) are in another directory,
|
||||||
|
# add these directories to sys.path here. If the directory is relative to the
|
||||||
|
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||||
|
#sys.path.insert(0, os.path.abspath('.'))
|
||||||
|
|
||||||
|
# -- General configuration ----------------------------------------------------
|
||||||
|
|
||||||
|
# If your documentation needs a minimal Sphinx version, state it here.
|
||||||
|
#needs_sphinx = '1.0'
|
||||||
|
|
||||||
|
# Add any Sphinx extension module names here, as strings. They can be
|
||||||
|
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||||
|
extensions = ['reno.sphinxext',
|
||||||
|
'oslosphinx']
|
||||||
|
|
||||||
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
|
templates_path = ['_templates']
|
||||||
|
|
||||||
|
# The suffix of source filenames.
|
||||||
|
source_suffix = '.rst'
|
||||||
|
|
||||||
|
# The encoding of source files.
|
||||||
|
#source_encoding = 'utf-8-sig'
|
||||||
|
|
||||||
|
# The master toctree document.
|
||||||
|
master_doc = 'index'
|
||||||
|
|
||||||
|
# General information about the project.
|
||||||
|
project = u'watcher'
|
||||||
|
copyright = u'2016, Watcher developers'
|
||||||
|
|
||||||
|
# The version info for the project you're documenting, acts as replacement for
|
||||||
|
# |version| and |release|, also used in various other places throughout the
|
||||||
|
# built documents.
|
||||||
|
#
|
||||||
|
# The short X.Y version.
|
||||||
|
version = watcher_version.version_info.release_string()
|
||||||
|
# The full version, including alpha/beta/rc tags.
|
||||||
|
release = watcher_version.version_info.version_string()
|
||||||
|
|
||||||
|
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||||
|
# for a list of supported languages.
|
||||||
|
#language = None
|
||||||
|
|
||||||
|
# There are two options for replacing |today|: either, you set today to some
|
||||||
|
# non-false value, then it is used:
|
||||||
|
#today = ''
|
||||||
|
# Else, today_fmt is used as the format for a strftime call.
|
||||||
|
#today_fmt = '%B %d, %Y'
|
||||||
|
|
||||||
|
# List of patterns, relative to source directory, that match files and
|
||||||
|
# directories to ignore when looking for source files.
|
||||||
|
exclude_patterns = ['_build']
|
||||||
|
|
||||||
|
# The reST default role (used for this markup: `text`) to use for all documents
|
||||||
|
#default_role = None
|
||||||
|
|
||||||
|
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||||
|
#add_function_parentheses = True
|
||||||
|
|
||||||
|
# If true, the current module name will be prepended to all description
|
||||||
|
# unit titles (such as .. function::).
|
||||||
|
#add_module_names = True
|
||||||
|
|
||||||
|
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||||
|
# output. They are ignored by default.
|
||||||
|
#show_authors = False
|
||||||
|
|
||||||
|
# The name of the Pygments (syntax highlighting) style to use.
|
||||||
|
pygments_style = 'sphinx'
|
||||||
|
|
||||||
|
# A list of ignored prefixes for module index sorting.
|
||||||
|
#modindex_common_prefix = []
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for HTML output --------------------------------------------------
|
||||||
|
|
||||||
|
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||||
|
# a list of builtin themes.
|
||||||
|
html_theme = 'default'
|
||||||
|
|
||||||
|
# Theme options are theme-specific and customize the look and feel of a theme
|
||||||
|
# further. For a list of options available for each theme, see the
|
||||||
|
# documentation.
|
||||||
|
#html_theme_options = {}
|
||||||
|
|
||||||
|
# Add any paths that contain custom themes here, relative to this directory.
|
||||||
|
#html_theme_path = []
|
||||||
|
|
||||||
|
# The name for this set of Sphinx documents. If None, it defaults to
|
||||||
|
# "<project> v<release> documentation".
|
||||||
|
#html_title = None
|
||||||
|
|
||||||
|
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||||
|
#html_short_title = None
|
||||||
|
|
||||||
|
# The name of an image file (relative to this directory) to place at the top
|
||||||
|
# of the sidebar.
|
||||||
|
#html_logo = None
|
||||||
|
|
||||||
|
# The name of an image file (within the static path) to use as favicon of the
|
||||||
|
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||||
|
# pixels large.
|
||||||
|
#html_favicon = None
|
||||||
|
|
||||||
|
# Add any paths that contain custom static files (such as style sheets) here,
|
||||||
|
# relative to this directory. They are copied after the builtin static files,
|
||||||
|
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||||
|
html_static_path = ['_static']
|
||||||
|
|
||||||
|
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||||
|
# using the given strftime format.
|
||||||
|
#html_last_updated_fmt = '%b %d, %Y'
|
||||||
|
|
||||||
|
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||||
|
# typographically correct entities.
|
||||||
|
#html_use_smartypants = True
|
||||||
|
|
||||||
|
# Custom sidebar templates, maps document names to template names.
|
||||||
|
#html_sidebars = {}
|
||||||
|
|
||||||
|
# Additional templates that should be rendered to pages, maps page names to
|
||||||
|
# template names.
|
||||||
|
#html_additional_pages = {}
|
||||||
|
|
||||||
|
# If false, no module index is generated.
|
||||||
|
#html_domain_indices = True
|
||||||
|
|
||||||
|
# If false, no index is generated.
|
||||||
|
#html_use_index = True
|
||||||
|
|
||||||
|
# If true, the index is split into individual pages for each letter.
|
||||||
|
#html_split_index = False
|
||||||
|
|
||||||
|
# If true, links to the reST sources are added to the pages.
|
||||||
|
#html_show_sourcelink = True
|
||||||
|
|
||||||
|
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||||
|
#html_show_sphinx = True
|
||||||
|
|
||||||
|
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||||
|
#html_show_copyright = True
|
||||||
|
|
||||||
|
# If true, an OpenSearch description file will be output, and all pages will
|
||||||
|
# contain a <link> tag referring to it. The value of this option must be the
|
||||||
|
# base URL from which the finished HTML is served.
|
||||||
|
#html_use_opensearch = ''
|
||||||
|
|
||||||
|
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||||
|
#html_file_suffix = None
|
||||||
|
|
||||||
|
# Output file base name for HTML help builder.
|
||||||
|
htmlhelp_basename = 'watcherdoc'
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for LaTeX output -------------------------------------------------
|
||||||
|
|
||||||
|
latex_elements = {
|
||||||
|
# The paper size ('letterpaper' or 'a4paper').
|
||||||
|
#'papersize': 'letterpaper',
|
||||||
|
|
||||||
|
# The font size ('10pt', '11pt' or '12pt').
|
||||||
|
#'pointsize': '10pt',
|
||||||
|
|
||||||
|
# Additional stuff for the LaTeX preamble.
|
||||||
|
#'preamble': '',
|
||||||
|
}
|
||||||
|
|
||||||
|
# Grouping the document tree into LaTeX files. List of tuples
|
||||||
|
# (source start file, target name, title, author, documentclass [howto/manual])
|
||||||
|
latex_documents = [
|
||||||
|
('index', 'watcher.tex', u'Watcher Documentation',
|
||||||
|
u'Watcher developers', 'manual'),
|
||||||
|
]
|
||||||
|
|
||||||
|
# The name of an image file (relative to this directory) to place at the top of
|
||||||
|
# the title page.
|
||||||
|
#latex_logo = None
|
||||||
|
|
||||||
|
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||||
|
# not chapters.
|
||||||
|
#latex_use_parts = False
|
||||||
|
|
||||||
|
# If true, show page references after internal links.
|
||||||
|
#latex_show_pagerefs = False
|
||||||
|
|
||||||
|
# If true, show URL addresses after external links.
|
||||||
|
#latex_show_urls = False
|
||||||
|
|
||||||
|
# Documents to append as an appendix to all manuals.
|
||||||
|
#latex_appendices = []
|
||||||
|
|
||||||
|
# If false, no module index is generated.
|
||||||
|
#latex_domain_indices = True
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for manual page output -------------------------------------------
|
||||||
|
|
||||||
|
# One entry per manual page. List of tuples
|
||||||
|
# (source start file, name, description, authors, manual section).
|
||||||
|
man_pages = [
|
||||||
|
('index', 'watcher', u'Watcher Documentation',
|
||||||
|
[u'Watcher developers'], 1)
|
||||||
|
]
|
||||||
|
|
||||||
|
# If true, show URL addresses after external links.
|
||||||
|
#man_show_urls = False
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for Texinfo output -----------------------------------------------
|
||||||
|
|
||||||
|
# Grouping the document tree into Texinfo files. List of tuples
|
||||||
|
# (source start file, target name, title, author,
|
||||||
|
# dir menu entry, description, category)
|
||||||
|
texinfo_documents = [
|
||||||
|
('index', 'watcher', u'Watcher Documentation',
|
||||||
|
u'Watcher developers', 'watcher', 'One line description of project.',
|
||||||
|
'Miscellaneous'),
|
||||||
|
]
|
||||||
|
|
||||||
|
# Documents to append as an appendix to all manuals.
|
||||||
|
#texinfo_appendices = []
|
||||||
|
|
||||||
|
# If false, no module index is generated.
|
||||||
|
#texinfo_domain_indices = True
|
||||||
|
|
||||||
|
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||||
|
#texinfo_show_urls = 'footnote'
|
||||||
10
releasenotes/source/index.rst
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
Welcome to watcher's Release Notes documentation!
|
||||||
|
=================================================
|
||||||
|
|
||||||
|
Contents:
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 1
|
||||||
|
|
||||||
|
unreleased.rst
|
||||||
|
|
||||||
5
releasenotes/source/unreleased.rst
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
==============================
|
||||||
|
Current Series Release Notes
|
||||||
|
==============================
|
||||||
|
|
||||||
|
.. release-notes::
|
||||||
@@ -2,32 +2,38 @@
|
|||||||
# 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.
|
||||||
|
|
||||||
|
apscheduler # MIT License
|
||||||
enum34;python_version=='2.7' or python_version=='2.6' or python_version=='3.3' # BSD
|
enum34;python_version=='2.7' or python_version=='2.6' or python_version=='3.3' # BSD
|
||||||
jsonpatch>=1.1 # BSD
|
jsonpatch>=1.1 # BSD
|
||||||
keystoneauth1>=2.1.0 # Apache-2.0
|
keystoneauth1>=2.7.0 # Apache-2.0
|
||||||
keystonemiddleware!=4.1.0,>=4.0.0 # Apache-2.0
|
keystonemiddleware!=4.1.0,!=4.5.0,>=4.0.0 # Apache-2.0
|
||||||
oslo.config>=3.7.0 # Apache-2.0
|
oslo.concurrency>=3.8.0 # Apache-2.0
|
||||||
|
oslo.cache>=1.5.0 # Apache-2.0
|
||||||
|
oslo.config>=3.10.0 # Apache-2.0
|
||||||
|
oslo.context>=2.4.0 # Apache-2.0
|
||||||
oslo.db>=4.1.0 # Apache-2.0
|
oslo.db>=4.1.0 # Apache-2.0
|
||||||
oslo.i18n>=2.1.0 # Apache-2.0
|
oslo.i18n>=2.1.0 # Apache-2.0
|
||||||
oslo.log>=1.14.0 # Apache-2.0
|
oslo.log>=1.14.0 # Apache-2.0
|
||||||
oslo.messaging>=4.0.0 # Apache-2.0
|
oslo.messaging>=5.2.0 # Apache-2.0
|
||||||
oslo.policy>=0.5.0 # Apache-2.0
|
oslo.policy>=1.9.0 # Apache-2.0
|
||||||
oslo.service>=1.0.0 # Apache-2.0
|
oslo.reports>=0.6.0 # Apache-2.0
|
||||||
oslo.utils>=3.5.0 # Apache-2.0
|
oslo.service>=1.10.0 # Apache-2.0
|
||||||
|
oslo.utils>=3.14.0 # Apache-2.0
|
||||||
PasteDeploy>=1.5.0 # MIT
|
PasteDeploy>=1.5.0 # MIT
|
||||||
pbr>=1.6 # Apache-2.0
|
pbr>=1.6 # Apache-2.0
|
||||||
pecan>=1.0.0 # BSD
|
pecan>=1.0.0 # BSD
|
||||||
voluptuous>=0.8.6 # BSD License
|
PrettyTable<0.8,>=0.7 # BSD
|
||||||
|
voluptuous>=0.8.9 # BSD License
|
||||||
python-ceilometerclient>=2.2.1 # Apache-2.0
|
python-ceilometerclient>=2.2.1 # Apache-2.0
|
||||||
python-cinderclient>=1.3.1 # Apache-2.0
|
python-cinderclient!=1.7.0,!=1.7.1,>=1.6.0 # Apache-2.0
|
||||||
python-glanceclient>=1.2.0 # Apache-2.0
|
python-glanceclient>=2.0.0 # Apache-2.0
|
||||||
python-keystoneclient!=1.8.0,!=2.1.0,>=1.6.0 # Apache-2.0
|
python-keystoneclient!=1.8.0,!=2.1.0,>=1.7.0 # Apache-2.0
|
||||||
python-neutronclient>=2.6.0 # Apache-2.0
|
python-neutronclient>=4.2.0 # Apache-2.0
|
||||||
python-novaclient!=2.33.0,>=2.29.0 # Apache-2.0
|
python-novaclient!=2.33.0,>=2.29.0 # Apache-2.0
|
||||||
python-openstackclient>=2.1.0 # Apache-2.0
|
python-openstackclient>=2.1.0 # Apache-2.0
|
||||||
six>=1.9.0 # MIT
|
six>=1.9.0 # MIT
|
||||||
SQLAlchemy<1.1.0,>=1.0.10 # MIT
|
SQLAlchemy<1.1.0,>=1.0.10 # MIT
|
||||||
stevedore>=1.5.0 # Apache-2.0
|
stevedore>=1.10.0 # Apache-2.0
|
||||||
taskflow>=1.26.0 # Apache-2.0
|
taskflow>=1.26.0 # Apache-2.0
|
||||||
WebOb>=1.2.3 # MIT
|
WebOb>=1.2.3 # MIT
|
||||||
WSME>=0.8 # MIT
|
WSME>=0.8 # MIT
|
||||||
|
|||||||
14
setup.cfg
@@ -45,10 +45,22 @@ tempest.test_plugins =
|
|||||||
watcher.database.migration_backend =
|
watcher.database.migration_backend =
|
||||||
sqlalchemy = watcher.db.sqlalchemy.migration
|
sqlalchemy = watcher.db.sqlalchemy.migration
|
||||||
|
|
||||||
|
watcher_goals =
|
||||||
|
unclassified = watcher.decision_engine.goal.goals:Unclassified
|
||||||
|
dummy = watcher.decision_engine.goal.goals:Dummy
|
||||||
|
server_consolidation = watcher.decision_engine.goal.goals:ServerConsolidation
|
||||||
|
thermal_optimization = watcher.decision_engine.goal.goals:ThermalOptimization
|
||||||
|
workload_balancing = watcher.decision_engine.goal.goals:WorkloadBalancing
|
||||||
|
airflow_optimization = watcher.decision_engine.goal.goals:AirflowOptimization
|
||||||
|
|
||||||
watcher_strategies =
|
watcher_strategies =
|
||||||
dummy = watcher.decision_engine.strategy.strategies.dummy_strategy:DummyStrategy
|
dummy = watcher.decision_engine.strategy.strategies.dummy_strategy:DummyStrategy
|
||||||
basic = watcher.decision_engine.strategy.strategies.basic_consolidation:BasicConsolidation
|
basic = watcher.decision_engine.strategy.strategies.basic_consolidation:BasicConsolidation
|
||||||
outlet_temp_control = watcher.decision_engine.strategy.strategies.outlet_temp_control:OutletTempControl
|
outlet_temperature = watcher.decision_engine.strategy.strategies.outlet_temp_control:OutletTempControl
|
||||||
|
vm_workload_consolidation = watcher.decision_engine.strategy.strategies.vm_workload_consolidation:VMWorkloadConsolidation
|
||||||
|
workload_stabilization = watcher.decision_engine.strategy.strategies.workload_stabilization:WorkloadStabilization
|
||||||
|
workload_balance = watcher.decision_engine.strategy.strategies.workload_balance:WorkloadBalance
|
||||||
|
uniform_airflow = watcher.decision_engine.strategy.strategies.uniform_airflow:UniformAirflow
|
||||||
|
|
||||||
watcher_actions =
|
watcher_actions =
|
||||||
migrate = watcher.applier.actions.migration:Migrate
|
migrate = watcher.applier.actions.migration:Migrate
|
||||||
|
|||||||
@@ -5,10 +5,11 @@
|
|||||||
coverage>=3.6 # Apache-2.0
|
coverage>=3.6 # Apache-2.0
|
||||||
discover # BSD
|
discover # BSD
|
||||||
doc8 # Apache-2.0
|
doc8 # Apache-2.0
|
||||||
|
freezegun # Apache-2.0
|
||||||
hacking<0.11,>=0.10.2
|
hacking<0.11,>=0.10.2
|
||||||
mock>=1.2 # BSD
|
mock>=2.0 # BSD
|
||||||
oslotest>=1.10.0 # Apache-2.0
|
oslotest>=1.10.0 # Apache-2.0
|
||||||
os-testr>=0.4.1 # Apache-2.0
|
os-testr>=0.7.0 # Apache-2.0
|
||||||
python-subunit>=0.0.18 # Apache-2.0/BSD
|
python-subunit>=0.0.18 # Apache-2.0/BSD
|
||||||
testrepository>=0.0.18 # Apache-2.0/BSD
|
testrepository>=0.0.18 # Apache-2.0/BSD
|
||||||
testscenarios>=0.4 # Apache-2.0/BSD
|
testscenarios>=0.4 # Apache-2.0/BSD
|
||||||
@@ -16,5 +17,11 @@ testtools>=1.4.0 # MIT
|
|||||||
|
|
||||||
# Doc requirements
|
# Doc requirements
|
||||||
oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0
|
oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0
|
||||||
sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 # BSD
|
sphinx!=1.3b1,<1.3,>=1.2.1 # BSD
|
||||||
sphinxcontrib-pecanwsme>=0.8 # Apache-2.0
|
sphinxcontrib-pecanwsme>=0.8 # Apache-2.0
|
||||||
|
|
||||||
|
# releasenotes
|
||||||
|
reno>=1.8.0 # Apache2
|
||||||
|
|
||||||
|
# bandit
|
||||||
|
bandit>=1.0.1 # Apache-2.0
|
||||||
|
|||||||
17
tox.ini
@@ -20,6 +20,7 @@ commands =
|
|||||||
commands =
|
commands =
|
||||||
doc8 doc/source/ CONTRIBUTING.rst HACKING.rst README.rst
|
doc8 doc/source/ CONTRIBUTING.rst HACKING.rst README.rst
|
||||||
flake8
|
flake8
|
||||||
|
bandit -r watcher -x tests -n5 -ll
|
||||||
|
|
||||||
[testenv:venv]
|
[testenv:venv]
|
||||||
setenv = PYTHONHASHSEED=0
|
setenv = PYTHONHASHSEED=0
|
||||||
@@ -40,18 +41,13 @@ commands = oslo_debug_helper -t watcher/tests {posargs}
|
|||||||
[testenv:config]
|
[testenv:config]
|
||||||
sitepackages = False
|
sitepackages = False
|
||||||
commands =
|
commands =
|
||||||
oslo-config-generator --namespace watcher \
|
oslo-config-generator --config-file etc/watcher/watcher-config-generator.conf
|
||||||
--namespace keystonemiddleware.auth_token \
|
|
||||||
--namespace oslo.log \
|
|
||||||
--namespace oslo.db \
|
|
||||||
--namespace oslo.messaging \
|
|
||||||
--output-file etc/watcher/watcher.conf.sample
|
|
||||||
|
|
||||||
[flake8]
|
[flake8]
|
||||||
show-source=True
|
show-source=True
|
||||||
ignore=
|
ignore=
|
||||||
builtins= _
|
builtins= _
|
||||||
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build,*sqlalchemy/alembic/versions/*,demo/
|
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build,*sqlalchemy/alembic/versions/*,demo/,releasenotes
|
||||||
|
|
||||||
[testenv:wheel]
|
[testenv:wheel]
|
||||||
commands = python setup.py bdist_wheel
|
commands = python setup.py bdist_wheel
|
||||||
@@ -63,3 +59,10 @@ import_exceptions = watcher._i18n
|
|||||||
extension=.rst
|
extension=.rst
|
||||||
# todo: stop ignoring doc/source/man when https://bugs.launchpad.net/doc8/+bug/1502391 is fixed
|
# todo: stop ignoring doc/source/man when https://bugs.launchpad.net/doc8/+bug/1502391 is fixed
|
||||||
ignore-path=doc/source/image_src,doc/source/man,doc/source/api
|
ignore-path=doc/source/image_src,doc/source/man,doc/source/api
|
||||||
|
|
||||||
|
[testenv:releasenotes]
|
||||||
|
commands = sphinx-build -a -W -E -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html
|
||||||
|
|
||||||
|
[testenv:bandit]
|
||||||
|
deps = -r{toxinidir}/test-requirements.txt
|
||||||
|
commands = bandit -r watcher -x tests -n5 -ll
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
import oslo_i18n
|
import oslo_i18n
|
||||||
|
from oslo_i18n import _lazy
|
||||||
|
|
||||||
# The domain is the name of the App which is used to generate the folder
|
# The domain is the name of the App which is used to generate the folder
|
||||||
# containing the translation files (i.e. the .pot file and the various locales)
|
# containing the translation files (i.e. the .pot file and the various locales)
|
||||||
@@ -42,5 +43,9 @@ _LE = _translators.log_error
|
|||||||
_LC = _translators.log_critical
|
_LC = _translators.log_critical
|
||||||
|
|
||||||
|
|
||||||
|
def lazy_translation_enabled():
|
||||||
|
return _lazy.USE_LAZY
|
||||||
|
|
||||||
|
|
||||||
def get_available_languages():
|
def get_available_languages():
|
||||||
return oslo_i18n.get_available_languages(DOMAIN)
|
return oslo_i18n.get_available_languages(DOMAIN)
|
||||||
|
|||||||
@@ -19,24 +19,38 @@
|
|||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
import pecan
|
import pecan
|
||||||
|
|
||||||
|
from watcher._i18n import _
|
||||||
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.selection import default \
|
|
||||||
as strategy_selector
|
|
||||||
|
|
||||||
# Register options for the service
|
# Register options for the service
|
||||||
API_SERVICE_OPTS = [
|
API_SERVICE_OPTS = [
|
||||||
cfg.IntOpt('port',
|
cfg.PortOpt('port',
|
||||||
default=9322,
|
default=9322,
|
||||||
help='The port for the watcher API server'),
|
help=_('The port for the watcher API server')),
|
||||||
cfg.StrOpt('host',
|
cfg.StrOpt('host',
|
||||||
default='0.0.0.0',
|
default='127.0.0.1',
|
||||||
help='The listen IP for the watcher API server'),
|
help=_('The listen IP for the watcher API server')),
|
||||||
cfg.IntOpt('max_limit',
|
cfg.IntOpt('max_limit',
|
||||||
default=1000,
|
default=1000,
|
||||||
help='The maximum number of items returned in a single '
|
help=_('The maximum number of items returned in a single '
|
||||||
'response from a collection resource.')
|
'response from a collection resource')),
|
||||||
|
cfg.IntOpt('workers',
|
||||||
|
min=1,
|
||||||
|
help=_('Number of workers for Watcher API service. '
|
||||||
|
'The default is equal to the number of CPUs available '
|
||||||
|
'if that can be determined, else a default worker '
|
||||||
|
'count of 1 is returned.')),
|
||||||
|
|
||||||
|
cfg.BoolOpt('enable_ssl_api',
|
||||||
|
default=False,
|
||||||
|
help=_("Enable the integrated stand-alone API to service "
|
||||||
|
"requests via HTTPS instead of HTTP. If there is a "
|
||||||
|
"front-end service performing HTTPS offloading from "
|
||||||
|
"the service, this option should be False; note, you "
|
||||||
|
"will want to change public API endpoint to represent "
|
||||||
|
"SSL termination URL with 'public_endpoint' option.")),
|
||||||
]
|
]
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
@@ -45,7 +59,6 @@ opt_group = cfg.OptGroup(name='api',
|
|||||||
|
|
||||||
CONF.register_group(opt_group)
|
CONF.register_group(opt_group)
|
||||||
CONF.register_opts(API_SERVICE_OPTS, opt_group)
|
CONF.register_opts(API_SERVICE_OPTS, opt_group)
|
||||||
CONF.register_opts(strategy_selector.WATCHER_GOALS_OPTS)
|
|
||||||
|
|
||||||
|
|
||||||
def get_pecan_config():
|
def get_pecan_config():
|
||||||
@@ -68,3 +81,12 @@ def setup_app(config=None):
|
|||||||
)
|
)
|
||||||
|
|
||||||
return acl.install(app, CONF, config.app.acl_public_routes)
|
return acl.install(app, CONF, config.app.acl_public_routes)
|
||||||
|
|
||||||
|
|
||||||
|
class VersionSelectorApplication(object):
|
||||||
|
def __init__(self):
|
||||||
|
pc = get_pecan_config()
|
||||||
|
self.v1 = setup_app(config=pc)
|
||||||
|
|
||||||
|
def __call__(self, environ, start_response):
|
||||||
|
return self.v1(environ, start_response)
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ from watcher.api import hooks
|
|||||||
# See https://pecan.readthedocs.org/en/latest/configuration.html#server-configuration # noqa
|
# See https://pecan.readthedocs.org/en/latest/configuration.html#server-configuration # noqa
|
||||||
server = {
|
server = {
|
||||||
'port': '9322',
|
'port': '9322',
|
||||||
'host': '0.0.0.0'
|
'host': '127.0.0.1'
|
||||||
}
|
}
|
||||||
|
|
||||||
# Pecan Application Configurations
|
# Pecan Application Configurations
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ from watcher.api.controllers.v1 import action_plan
|
|||||||
from watcher.api.controllers.v1 import audit
|
from watcher.api.controllers.v1 import audit
|
||||||
from watcher.api.controllers.v1 import audit_template
|
from watcher.api.controllers.v1 import audit_template
|
||||||
from watcher.api.controllers.v1 import goal
|
from watcher.api.controllers.v1 import goal
|
||||||
|
from watcher.api.controllers.v1 import strategy
|
||||||
|
|
||||||
|
|
||||||
class APIBase(wtypes.Base):
|
class APIBase(wtypes.Base):
|
||||||
@@ -157,6 +158,7 @@ class Controller(rest.RestController):
|
|||||||
actions = action.ActionsController()
|
actions = action.ActionsController()
|
||||||
action_plans = action_plan.ActionPlansController()
|
action_plans = action_plan.ActionPlansController()
|
||||||
goals = goal.GoalsController()
|
goals = goal.GoalsController()
|
||||||
|
strategies = strategy.StrategiesController()
|
||||||
|
|
||||||
@wsme_pecan.wsexpose(V1)
|
@wsme_pecan.wsexpose(V1)
|
||||||
def get(self):
|
def get(self):
|
||||||
@@ -165,4 +167,5 @@ class Controller(rest.RestController):
|
|||||||
# the request object to make the links.
|
# the request object to make the links.
|
||||||
return V1.convert()
|
return V1.convert()
|
||||||
|
|
||||||
__all__ = (Controller)
|
|
||||||
|
__all__ = ("Controller", )
|
||||||
|
|||||||
@@ -49,6 +49,10 @@ be one of the following:
|
|||||||
- **CANCELLED** : the :ref:`Action <action_definition>` was in **PENDING** or
|
- **CANCELLED** : the :ref:`Action <action_definition>` was in **PENDING** or
|
||||||
**ONGOING** state and was cancelled by the
|
**ONGOING** state and was cancelled by the
|
||||||
:ref:`Administrator <administrator_definition>`
|
:ref:`Administrator <administrator_definition>`
|
||||||
|
|
||||||
|
:ref:`Some default implementations are provided <watcher_planners>`, but it is
|
||||||
|
possible to :ref:`develop new implementations <implement_action_plugin>` which
|
||||||
|
are dynamically loaded by Watcher at launch time.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
@@ -59,12 +63,14 @@ import wsme
|
|||||||
from wsme import types as wtypes
|
from wsme import types as wtypes
|
||||||
import wsmeext.pecan as wsme_pecan
|
import wsmeext.pecan as wsme_pecan
|
||||||
|
|
||||||
|
from watcher._i18n import _
|
||||||
from watcher.api.controllers import base
|
from watcher.api.controllers import base
|
||||||
from watcher.api.controllers import link
|
from watcher.api.controllers import link
|
||||||
from watcher.api.controllers.v1 import collection
|
from watcher.api.controllers.v1 import collection
|
||||||
from watcher.api.controllers.v1 import types
|
from watcher.api.controllers.v1 import types
|
||||||
from watcher.api.controllers.v1 import utils as api_utils
|
from watcher.api.controllers.v1 import utils as api_utils
|
||||||
from watcher.common import exception
|
from watcher.common import exception
|
||||||
|
from watcher.common import policy
|
||||||
from watcher import objects
|
from watcher import objects
|
||||||
|
|
||||||
|
|
||||||
@@ -115,7 +121,7 @@ class Action(base.APIBase):
|
|||||||
self.action_next_uuid = None
|
self.action_next_uuid = None
|
||||||
# raise e
|
# raise e
|
||||||
|
|
||||||
uuid = types.uuid
|
uuid = wtypes.wsattr(types.uuid, readonly=True)
|
||||||
"""Unique UUID for this action"""
|
"""Unique UUID for this action"""
|
||||||
|
|
||||||
action_plan_uuid = wsme.wsproperty(types.uuid, _get_action_plan_uuid,
|
action_plan_uuid = wsme.wsproperty(types.uuid, _get_action_plan_uuid,
|
||||||
@@ -126,9 +132,6 @@ class Action(base.APIBase):
|
|||||||
state = wtypes.text
|
state = wtypes.text
|
||||||
"""This audit state"""
|
"""This audit state"""
|
||||||
|
|
||||||
alarm = types.uuid
|
|
||||||
"""An alarm UUID related to this action"""
|
|
||||||
|
|
||||||
action_type = wtypes.text
|
action_type = wtypes.text
|
||||||
"""Action type"""
|
"""Action type"""
|
||||||
|
|
||||||
@@ -190,7 +193,6 @@ class Action(base.APIBase):
|
|||||||
sample = cls(uuid='27e3153e-d5bf-4b7e-b517-fb518e17f34c',
|
sample = cls(uuid='27e3153e-d5bf-4b7e-b517-fb518e17f34c',
|
||||||
description='action description',
|
description='action description',
|
||||||
state='PENDING',
|
state='PENDING',
|
||||||
alarm=None,
|
|
||||||
created_at=datetime.datetime.utcnow(),
|
created_at=datetime.datetime.utcnow(),
|
||||||
deleted_at=None,
|
deleted_at=None,
|
||||||
updated_at=datetime.datetime.utcnow())
|
updated_at=datetime.datetime.utcnow())
|
||||||
@@ -302,6 +304,10 @@ class ActionsController(rest.RestController):
|
|||||||
:param audit_uuid: Optional UUID of an audit,
|
:param audit_uuid: Optional UUID of an audit,
|
||||||
to get only actions for that audit.
|
to get only actions for that audit.
|
||||||
"""
|
"""
|
||||||
|
context = pecan.request.context
|
||||||
|
policy.enforce(context, 'action:get_all',
|
||||||
|
action='action:get_all')
|
||||||
|
|
||||||
if action_plan_uuid and audit_uuid:
|
if action_plan_uuid and audit_uuid:
|
||||||
raise exception.ActionFilterCombinationProhibited
|
raise exception.ActionFilterCombinationProhibited
|
||||||
|
|
||||||
@@ -326,6 +332,10 @@ class ActionsController(rest.RestController):
|
|||||||
:param audit_uuid: Optional UUID of an audit,
|
:param audit_uuid: Optional UUID of an audit,
|
||||||
to get only actions for that audit.
|
to get only actions for that audit.
|
||||||
"""
|
"""
|
||||||
|
context = pecan.request.context
|
||||||
|
policy.enforce(context, 'action:detail',
|
||||||
|
action='action:detail')
|
||||||
|
|
||||||
# NOTE(lucasagomes): /detail should only work agaist collections
|
# NOTE(lucasagomes): /detail should only work agaist collections
|
||||||
parent = pecan.request.path.split('/')[:-1][-1]
|
parent = pecan.request.path.split('/')[:-1][-1]
|
||||||
if parent != "actions":
|
if parent != "actions":
|
||||||
@@ -349,8 +359,10 @@ class ActionsController(rest.RestController):
|
|||||||
if self.from_actions:
|
if self.from_actions:
|
||||||
raise exception.OperationNotPermitted
|
raise exception.OperationNotPermitted
|
||||||
|
|
||||||
action = objects.Action.get_by_uuid(pecan.request.context,
|
context = pecan.request.context
|
||||||
action_uuid)
|
action = api_utils.get_resource('Action', action_uuid)
|
||||||
|
policy.enforce(context, 'action:get', action, action='action:get')
|
||||||
|
|
||||||
return Action.convert_with_links(action)
|
return Action.convert_with_links(action)
|
||||||
|
|
||||||
@wsme_pecan.wsexpose(Action, body=Action, status_code=201)
|
@wsme_pecan.wsexpose(Action, body=Action, status_code=201)
|
||||||
@@ -359,6 +371,10 @@ class ActionsController(rest.RestController):
|
|||||||
|
|
||||||
:param action: a action within the request body.
|
:param action: a action within the request body.
|
||||||
"""
|
"""
|
||||||
|
# FIXME: blueprint edit-action-plan-flow
|
||||||
|
raise exception.OperationNotPermitted(
|
||||||
|
_("Cannot create an action directly"))
|
||||||
|
|
||||||
if self.from_actions:
|
if self.from_actions:
|
||||||
raise exception.OperationNotPermitted
|
raise exception.OperationNotPermitted
|
||||||
|
|
||||||
@@ -379,6 +395,10 @@ class ActionsController(rest.RestController):
|
|||||||
:param action_uuid: UUID of a action.
|
:param action_uuid: UUID of a action.
|
||||||
:param patch: a json PATCH document to apply to this action.
|
:param patch: a json PATCH document to apply to this action.
|
||||||
"""
|
"""
|
||||||
|
# FIXME: blueprint edit-action-plan-flow
|
||||||
|
raise exception.OperationNotPermitted(
|
||||||
|
_("Cannot modify an action directly"))
|
||||||
|
|
||||||
if self.from_actions:
|
if self.from_actions:
|
||||||
raise exception.OperationNotPermitted
|
raise exception.OperationNotPermitted
|
||||||
|
|
||||||
@@ -411,6 +431,9 @@ class ActionsController(rest.RestController):
|
|||||||
|
|
||||||
:param action_uuid: UUID of a action.
|
:param action_uuid: UUID of a action.
|
||||||
"""
|
"""
|
||||||
|
# FIXME: blueprint edit-action-plan-flow
|
||||||
|
raise exception.OperationNotPermitted(
|
||||||
|
_("Cannot delete an action directly"))
|
||||||
|
|
||||||
action_to_delete = objects.Action.get_by_uuid(
|
action_to_delete = objects.Action.get_by_uuid(
|
||||||
pecan.request.context,
|
pecan.request.context,
|
||||||
|
|||||||
@@ -16,9 +16,11 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
An :ref:`Action Plan <action_plan_definition>` is a flow of
|
An :ref:`Action Plan <action_plan_definition>` specifies a flow of
|
||||||
:ref:`Actions <action_definition>` that should be executed in order to satisfy
|
:ref:`Actions <action_definition>` that should be executed in order to satisfy
|
||||||
a given :ref:`Goal <goal_definition>`.
|
a given :ref:`Goal <goal_definition>`. It also contains an estimated
|
||||||
|
:ref:`global efficacy <efficacy_definition>` alongside a set of
|
||||||
|
:ref:`efficacy indicators <efficacy_indicator_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:`Audit <audit_definition>` is successful which implies that the
|
||||||
@@ -26,16 +28,13 @@ An :ref:`Action Plan <action_plan_definition>` is generated by Watcher when an
|
|||||||
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
|
In the default implementation of Watcher, an action plan is composed of
|
||||||
:ref:`Action Plan <action_plan_definition>`
|
a list of successive :ref:`Actions <action_definition>` (i.e., a Workflow of
|
||||||
is only composed of successive :ref:`Actions <action_definition>`
|
:ref:`Actions <action_definition>` belonging to a unique branch).
|
||||||
(i.e., a Workflow of :ref:`Actions <action_definition>` belonging to a unique
|
|
||||||
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
|
allowing other implementations to generate and handle more complex :ref:`Action
|
||||||
:ref:`Action Plan(s) <action_plan_definition>`
|
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
|
||||||
@@ -46,31 +45,18 @@ composed of two types of Action Item(s):
|
|||||||
|
|
||||||
An :ref:`Action Plan <action_plan_definition>` may be described using
|
An :ref:`Action Plan <action_plan_definition>` may be described using
|
||||||
standard workflow model description formats such as
|
standard workflow model description formats such as
|
||||||
`Business Process Model and Notation 2.0 (BPMN 2.0) <http://www.omg.org/spec/BPMN/2.0/>`_
|
`Business Process Model and Notation 2.0 (BPMN 2.0)
|
||||||
or `Unified Modeling Language (UML) <http://www.uml.org/>`_.
|
<http://www.omg.org/spec/BPMN/2.0/>`_ or `Unified Modeling Language (UML)
|
||||||
|
<http://www.uml.org/>`_.
|
||||||
|
|
||||||
An :ref:`Action Plan <action_plan_definition>` has a life-cycle and its current
|
To see the life-cycle and description of
|
||||||
state may be one of the following:
|
:ref:`Action Plan <action_plan_definition>` states, visit :ref:`the Action Plan
|
||||||
|
state machine <action_plan_state_machine>`.
|
||||||
- **RECOMMENDED** : the :ref:`Action Plan <action_plan_definition>` is waiting
|
"""
|
||||||
for a validation from the :ref:`Administrator <administrator_definition>`
|
|
||||||
- **ONGOING** : the :ref:`Action Plan <action_plan_definition>` is currently
|
|
||||||
being processed by the :ref:`Watcher Applier <watcher_applier_definition>`
|
|
||||||
- **SUCCEEDED** : the :ref:`Action Plan <action_plan_definition>` has been
|
|
||||||
executed successfully (i.e. all :ref:`Actions <action_definition>` that it
|
|
||||||
contains have been executed successfully)
|
|
||||||
- **FAILED** : an error occured while executing the
|
|
||||||
:ref:`Action Plan <action_plan_definition>`
|
|
||||||
- **DELETED** : the :ref:`Action Plan <action_plan_definition>` is still
|
|
||||||
stored in the :ref:`Watcher database <watcher_database_definition>` but is
|
|
||||||
not returned any more through the Watcher APIs.
|
|
||||||
- **CANCELLED** : the :ref:`Action Plan <action_plan_definition>` was in
|
|
||||||
**PENDING** or **ONGOING** state and was cancelled by the
|
|
||||||
:ref:`Administrator <administrator_definition>`
|
|
||||||
""" # noqa
|
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
|
from oslo_log import log
|
||||||
import pecan
|
import pecan
|
||||||
from pecan import rest
|
from pecan import rest
|
||||||
import wsme
|
import wsme
|
||||||
@@ -81,13 +67,17 @@ from watcher._i18n import _
|
|||||||
from watcher.api.controllers import base
|
from watcher.api.controllers import base
|
||||||
from watcher.api.controllers import link
|
from watcher.api.controllers import link
|
||||||
from watcher.api.controllers.v1 import collection
|
from watcher.api.controllers.v1 import collection
|
||||||
|
from watcher.api.controllers.v1 import efficacy_indicator as efficacyindicator
|
||||||
from watcher.api.controllers.v1 import types
|
from watcher.api.controllers.v1 import types
|
||||||
from watcher.api.controllers.v1 import utils as api_utils
|
from watcher.api.controllers.v1 import utils as api_utils
|
||||||
from watcher.applier import rpcapi
|
from watcher.applier import rpcapi
|
||||||
from watcher.common import exception
|
from watcher.common import exception
|
||||||
|
from watcher.common import policy
|
||||||
from watcher import objects
|
from watcher import objects
|
||||||
from watcher.objects import action_plan as ap_objects
|
from watcher.objects import action_plan as ap_objects
|
||||||
|
|
||||||
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class ActionPlanPatchType(types.JsonPatchType):
|
class ActionPlanPatchType(types.JsonPatchType):
|
||||||
|
|
||||||
@@ -128,6 +118,7 @@ class ActionPlan(base.APIBase):
|
|||||||
|
|
||||||
_audit_uuid = None
|
_audit_uuid = None
|
||||||
_first_action_uuid = None
|
_first_action_uuid = None
|
||||||
|
_efficacy_indicators = None
|
||||||
|
|
||||||
def _get_audit_uuid(self):
|
def _get_audit_uuid(self):
|
||||||
return self._audit_uuid
|
return self._audit_uuid
|
||||||
@@ -158,7 +149,35 @@ class ActionPlan(base.APIBase):
|
|||||||
except exception.ActionNotFound:
|
except exception.ActionNotFound:
|
||||||
self._first_action_uuid = None
|
self._first_action_uuid = None
|
||||||
|
|
||||||
uuid = types.uuid
|
def _get_efficacy_indicators(self):
|
||||||
|
if self._efficacy_indicators is None:
|
||||||
|
self._set_efficacy_indicators(wtypes.Unset)
|
||||||
|
return self._efficacy_indicators
|
||||||
|
|
||||||
|
def _set_efficacy_indicators(self, value):
|
||||||
|
efficacy_indicators = []
|
||||||
|
if value == wtypes.Unset and not self._efficacy_indicators:
|
||||||
|
try:
|
||||||
|
_efficacy_indicators = objects.EfficacyIndicator.list(
|
||||||
|
pecan.request.context,
|
||||||
|
filters={"action_plan_uuid": self.uuid})
|
||||||
|
|
||||||
|
for indicator in _efficacy_indicators:
|
||||||
|
efficacy_indicator = efficacyindicator.EfficacyIndicator(
|
||||||
|
context=pecan.request.context,
|
||||||
|
name=indicator.name,
|
||||||
|
description=indicator.description,
|
||||||
|
unit=indicator.unit,
|
||||||
|
value=indicator.value,
|
||||||
|
)
|
||||||
|
efficacy_indicators.append(efficacy_indicator.as_dict())
|
||||||
|
self._efficacy_indicators = efficacy_indicators
|
||||||
|
except exception.EfficacyIndicatorNotFound as exc:
|
||||||
|
LOG.exception(exc)
|
||||||
|
elif value and self._efficacy_indicators != value:
|
||||||
|
self._efficacy_indicators = value
|
||||||
|
|
||||||
|
uuid = wtypes.wsattr(types.uuid, readonly=True)
|
||||||
"""Unique UUID for this action plan"""
|
"""Unique UUID for this action plan"""
|
||||||
|
|
||||||
first_action_uuid = wsme.wsproperty(
|
first_action_uuid = wsme.wsproperty(
|
||||||
@@ -170,6 +189,14 @@ class ActionPlan(base.APIBase):
|
|||||||
mandatory=True)
|
mandatory=True)
|
||||||
"""The UUID of the audit this port belongs to"""
|
"""The UUID of the audit this port belongs to"""
|
||||||
|
|
||||||
|
efficacy_indicators = wsme.wsproperty(
|
||||||
|
types.jsontype, _get_efficacy_indicators, _set_efficacy_indicators,
|
||||||
|
mandatory=True)
|
||||||
|
"""The list of efficacy indicators associated to this action plan"""
|
||||||
|
|
||||||
|
global_efficacy = wtypes.wsattr(types.jsontype, readonly=True)
|
||||||
|
"""The global efficacy of this action plan"""
|
||||||
|
|
||||||
state = wtypes.text
|
state = wtypes.text
|
||||||
"""This action plan state"""
|
"""This action plan state"""
|
||||||
|
|
||||||
@@ -178,7 +205,6 @@ class ActionPlan(base.APIBase):
|
|||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
super(ActionPlan, self).__init__()
|
super(ActionPlan, self).__init__()
|
||||||
|
|
||||||
self.fields = []
|
self.fields = []
|
||||||
fields = list(objects.ActionPlan.fields)
|
fields = list(objects.ActionPlan.fields)
|
||||||
for field in fields:
|
for field in fields:
|
||||||
@@ -190,6 +216,7 @@ class ActionPlan(base.APIBase):
|
|||||||
|
|
||||||
self.fields.append('audit_uuid')
|
self.fields.append('audit_uuid')
|
||||||
self.fields.append('first_action_uuid')
|
self.fields.append('first_action_uuid')
|
||||||
|
self.fields.append('efficacy_indicators')
|
||||||
|
|
||||||
setattr(self, 'audit_uuid', kwargs.get('audit_id', wtypes.Unset))
|
setattr(self, 'audit_uuid', kwargs.get('audit_id', wtypes.Unset))
|
||||||
setattr(self, 'first_action_uuid',
|
setattr(self, 'first_action_uuid',
|
||||||
@@ -199,12 +226,13 @@ class ActionPlan(base.APIBase):
|
|||||||
def _convert_with_links(action_plan, url, expand=True):
|
def _convert_with_links(action_plan, url, expand=True):
|
||||||
if not expand:
|
if not expand:
|
||||||
action_plan.unset_fields_except(
|
action_plan.unset_fields_except(
|
||||||
['uuid', 'state', 'updated_at',
|
['uuid', 'state', 'efficacy_indicators', 'global_efficacy',
|
||||||
'audit_uuid', 'first_action_uuid'])
|
'updated_at', 'audit_uuid', 'first_action_uuid'])
|
||||||
|
|
||||||
action_plan.links = [link.Link.make_link(
|
action_plan.links = [
|
||||||
'self', url,
|
link.Link.make_link(
|
||||||
'action_plans', action_plan.uuid),
|
'self', url,
|
||||||
|
'action_plans', action_plan.uuid),
|
||||||
link.Link.make_link(
|
link.Link.make_link(
|
||||||
'bookmark', url,
|
'bookmark', url,
|
||||||
'action_plans', action_plan.uuid,
|
'action_plans', action_plan.uuid,
|
||||||
@@ -226,6 +254,12 @@ class ActionPlan(base.APIBase):
|
|||||||
updated_at=datetime.datetime.utcnow())
|
updated_at=datetime.datetime.utcnow())
|
||||||
sample._first_action_uuid = '57eaf9ab-5aaa-4f7e-bdf7-9a140ac7a720'
|
sample._first_action_uuid = '57eaf9ab-5aaa-4f7e-bdf7-9a140ac7a720'
|
||||||
sample._audit_uuid = 'abcee106-14d3-4515-b744-5a26885cf6f6'
|
sample._audit_uuid = 'abcee106-14d3-4515-b744-5a26885cf6f6'
|
||||||
|
sample._efficacy_indicators = [{'description': 'Test indicator',
|
||||||
|
'name': 'test_indicator',
|
||||||
|
'unit': '%'}]
|
||||||
|
sample._global_efficacy = {'description': 'Global efficacy',
|
||||||
|
'name': 'test_global_efficacy',
|
||||||
|
'unit': '%'}
|
||||||
return cls._convert_with_links(sample, 'http://localhost:9322', expand)
|
return cls._convert_with_links(sample, 'http://localhost:9322', expand)
|
||||||
|
|
||||||
|
|
||||||
@@ -325,6 +359,10 @@ class ActionPlansController(rest.RestController):
|
|||||||
:param audit_uuid: Optional UUID of an audit, to get only actions
|
:param audit_uuid: Optional UUID of an audit, to get only actions
|
||||||
for that audit.
|
for that audit.
|
||||||
"""
|
"""
|
||||||
|
context = pecan.request.context
|
||||||
|
policy.enforce(context, 'action_plan:get_all',
|
||||||
|
action='action_plan:get_all')
|
||||||
|
|
||||||
return self._get_action_plans_collection(
|
return self._get_action_plans_collection(
|
||||||
marker, limit, sort_key, sort_dir, audit_uuid=audit_uuid)
|
marker, limit, sort_key, sort_dir, audit_uuid=audit_uuid)
|
||||||
|
|
||||||
@@ -341,6 +379,10 @@ class ActionPlansController(rest.RestController):
|
|||||||
:param audit_uuid: Optional UUID of an audit, to get only actions
|
:param audit_uuid: Optional UUID of an audit, to get only actions
|
||||||
for that audit.
|
for that audit.
|
||||||
"""
|
"""
|
||||||
|
context = pecan.request.context
|
||||||
|
policy.enforce(context, 'action_plan:detail',
|
||||||
|
action='action_plan:detail')
|
||||||
|
|
||||||
# NOTE(lucasagomes): /detail should only work agaist collections
|
# NOTE(lucasagomes): /detail should only work agaist collections
|
||||||
parent = pecan.request.path.split('/')[:-1][-1]
|
parent = pecan.request.path.split('/')[:-1][-1]
|
||||||
if parent != "action_plans":
|
if parent != "action_plans":
|
||||||
@@ -362,8 +404,11 @@ class ActionPlansController(rest.RestController):
|
|||||||
if self.from_actionsPlans:
|
if self.from_actionsPlans:
|
||||||
raise exception.OperationNotPermitted
|
raise exception.OperationNotPermitted
|
||||||
|
|
||||||
action_plan = objects.ActionPlan.get_by_uuid(
|
context = pecan.request.context
|
||||||
pecan.request.context, action_plan_uuid)
|
action_plan = api_utils.get_resource('ActionPlan', action_plan_uuid)
|
||||||
|
policy.enforce(
|
||||||
|
context, 'action_plan:get', action_plan, action='action_plan:get')
|
||||||
|
|
||||||
return ActionPlan.convert_with_links(action_plan)
|
return ActionPlan.convert_with_links(action_plan)
|
||||||
|
|
||||||
@wsme_pecan.wsexpose(None, types.uuid, status_code=204)
|
@wsme_pecan.wsexpose(None, types.uuid, status_code=204)
|
||||||
@@ -372,11 +417,12 @@ class ActionPlansController(rest.RestController):
|
|||||||
|
|
||||||
:param action_plan_uuid: UUID of a action.
|
:param action_plan_uuid: UUID of a action.
|
||||||
"""
|
"""
|
||||||
|
context = pecan.request.context
|
||||||
|
action_plan = api_utils.get_resource('ActionPlan', action_plan_uuid)
|
||||||
|
policy.enforce(context, 'action_plan:delete', action_plan,
|
||||||
|
action='action_plan:delete')
|
||||||
|
|
||||||
action_plan_to_delete = objects.ActionPlan.get_by_uuid(
|
action_plan.soft_delete()
|
||||||
pecan.request.context,
|
|
||||||
action_plan_uuid)
|
|
||||||
action_plan_to_delete.soft_delete()
|
|
||||||
|
|
||||||
@wsme.validate(types.uuid, [ActionPlanPatchType])
|
@wsme.validate(types.uuid, [ActionPlanPatchType])
|
||||||
@wsme_pecan.wsexpose(ActionPlan, types.uuid,
|
@wsme_pecan.wsexpose(ActionPlan, types.uuid,
|
||||||
@@ -391,9 +437,12 @@ class ActionPlansController(rest.RestController):
|
|||||||
if self.from_actionsPlans:
|
if self.from_actionsPlans:
|
||||||
raise exception.OperationNotPermitted
|
raise exception.OperationNotPermitted
|
||||||
|
|
||||||
action_plan_to_update = objects.ActionPlan.get_by_uuid(
|
context = pecan.request.context
|
||||||
pecan.request.context,
|
action_plan_to_update = api_utils.get_resource('ActionPlan',
|
||||||
action_plan_uuid)
|
action_plan_uuid)
|
||||||
|
policy.enforce(context, 'action_plan:update', action_plan_to_update,
|
||||||
|
action='action_plan:update')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
action_plan_dict = action_plan_to_update.as_dict()
|
action_plan_dict = action_plan_to_update.as_dict()
|
||||||
action_plan = ActionPlan(**api_utils.apply_jsonpatch(
|
action_plan = ActionPlan(**api_utils.apply_jsonpatch(
|
||||||
|
|||||||
@@ -25,28 +25,8 @@ on a given :ref:`Cluster <cluster_definition>`.
|
|||||||
For each :ref:`Audit <audit_definition>`, the Watcher system generates an
|
For each :ref:`Audit <audit_definition>`, the Watcher system generates an
|
||||||
:ref:`Action Plan <action_plan_definition>`.
|
:ref:`Action Plan <action_plan_definition>`.
|
||||||
|
|
||||||
An :ref:`Audit <audit_definition>` has a life-cycle and its current state may
|
To see the life-cycle and description of an :ref:`Audit <audit_definition>`
|
||||||
be one of the following:
|
states, visit :ref:`the Audit State machine <audit_state_machine>`.
|
||||||
|
|
||||||
- **PENDING** : a request for an :ref:`Audit <audit_definition>` has been
|
|
||||||
submitted (either manually by the
|
|
||||||
:ref:`Administrator <administrator_definition>` or automatically via some
|
|
||||||
event handling mechanism) and is in the queue for being processed by the
|
|
||||||
:ref:`Watcher Decision Engine <watcher_decision_engine_definition>`
|
|
||||||
- **ONGOING** : the :ref:`Audit <audit_definition>` is currently being
|
|
||||||
processed by the
|
|
||||||
:ref:`Watcher Decision Engine <watcher_decision_engine_definition>`
|
|
||||||
- **SUCCEEDED** : the :ref:`Audit <audit_definition>` has been executed
|
|
||||||
successfully (note that it may not necessarily produce a
|
|
||||||
:ref:`Solution <solution_definition>`).
|
|
||||||
- **FAILED** : an error occured while executing the
|
|
||||||
:ref:`Audit <audit_definition>`
|
|
||||||
- **DELETED** : the :ref:`Audit <audit_definition>` is still stored in the
|
|
||||||
:ref:`Watcher database <watcher_database_definition>` but is not returned
|
|
||||||
any more through the Watcher APIs.
|
|
||||||
- **CANCELLED** : the :ref:`Audit <audit_definition>` was in **PENDING** or
|
|
||||||
**ONGOING** state and was cancelled by the
|
|
||||||
:ref:`Administrator <administrator_definition>`
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
@@ -64,16 +44,64 @@ from watcher.api.controllers.v1 import collection
|
|||||||
from watcher.api.controllers.v1 import types
|
from watcher.api.controllers.v1 import types
|
||||||
from watcher.api.controllers.v1 import utils as api_utils
|
from watcher.api.controllers.v1 import utils as api_utils
|
||||||
from watcher.common import exception
|
from watcher.common import exception
|
||||||
|
from watcher.common import policy
|
||||||
from watcher.common import utils
|
from watcher.common import utils
|
||||||
from watcher.decision_engine import rpcapi
|
from watcher.decision_engine import rpcapi
|
||||||
from watcher import objects
|
from watcher import objects
|
||||||
|
|
||||||
|
|
||||||
|
class AuditPostType(wtypes.Base):
|
||||||
|
|
||||||
|
audit_template_uuid = wtypes.wsattr(types.uuid, mandatory=True)
|
||||||
|
|
||||||
|
audit_type = wtypes.wsattr(wtypes.text, mandatory=True)
|
||||||
|
|
||||||
|
deadline = wtypes.wsattr(datetime.datetime, mandatory=False)
|
||||||
|
|
||||||
|
state = wsme.wsattr(wtypes.text, readonly=True,
|
||||||
|
default=objects.audit.State.PENDING)
|
||||||
|
|
||||||
|
parameters = wtypes.wsattr({wtypes.text: types.jsontype}, mandatory=False,
|
||||||
|
default={})
|
||||||
|
interval = wsme.wsattr(int, mandatory=False)
|
||||||
|
|
||||||
|
def as_audit(self):
|
||||||
|
audit_type_values = [val.value for val in objects.audit.AuditType]
|
||||||
|
if self.audit_type not in audit_type_values:
|
||||||
|
raise exception.AuditTypeNotFound(audit_type=self.audit_type)
|
||||||
|
|
||||||
|
if (self.audit_type == objects.audit.AuditType.ONESHOT.value and
|
||||||
|
self.interval != wtypes.Unset):
|
||||||
|
raise exception.AuditIntervalNotAllowed(audit_type=self.audit_type)
|
||||||
|
|
||||||
|
if (self.audit_type == objects.audit.AuditType.CONTINUOUS.value and
|
||||||
|
self.interval == wtypes.Unset):
|
||||||
|
raise exception.AuditIntervalNotSpecified(
|
||||||
|
audit_type=self.audit_type)
|
||||||
|
|
||||||
|
return Audit(
|
||||||
|
audit_template_id=self.audit_template_uuid,
|
||||||
|
audit_type=self.audit_type,
|
||||||
|
deadline=self.deadline,
|
||||||
|
parameters=self.parameters,
|
||||||
|
interval=self.interval)
|
||||||
|
|
||||||
|
|
||||||
class AuditPatchType(types.JsonPatchType):
|
class AuditPatchType(types.JsonPatchType):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def mandatory_attrs():
|
def mandatory_attrs():
|
||||||
return ['/audit_template_uuid']
|
return ['/audit_template_uuid', '/type']
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def validate(patch):
|
||||||
|
serialized_patch = {'path': patch.path, 'op': patch.op}
|
||||||
|
if patch.path in AuditPatchType.mandatory_attrs():
|
||||||
|
msg = _("%(field)s can't be updated.")
|
||||||
|
raise exception.PatchError(
|
||||||
|
patch=serialized_patch,
|
||||||
|
reason=msg % dict(field=patch.path))
|
||||||
|
return types.JsonPatchType.validate(patch)
|
||||||
|
|
||||||
|
|
||||||
class Audit(base.APIBase):
|
class Audit(base.APIBase):
|
||||||
@@ -125,7 +153,7 @@ class Audit(base.APIBase):
|
|||||||
uuid = types.uuid
|
uuid = types.uuid
|
||||||
"""Unique UUID for this audit"""
|
"""Unique UUID for this audit"""
|
||||||
|
|
||||||
type = wtypes.text
|
audit_type = wtypes.text
|
||||||
"""Type of this audit"""
|
"""Type of this audit"""
|
||||||
|
|
||||||
deadline = datetime.datetime
|
deadline = datetime.datetime
|
||||||
@@ -146,9 +174,15 @@ class Audit(base.APIBase):
|
|||||||
mandatory=False)
|
mandatory=False)
|
||||||
"""The name of the audit template this audit refers to"""
|
"""The name of the audit template this audit refers to"""
|
||||||
|
|
||||||
|
parameters = {wtypes.text: types.jsontype}
|
||||||
|
"""The strategy parameters for this audit"""
|
||||||
|
|
||||||
links = wsme.wsattr([link.Link], readonly=True)
|
links = wsme.wsattr([link.Link], readonly=True)
|
||||||
"""A list containing a self link and associated audit links"""
|
"""A list containing a self link and associated audit links"""
|
||||||
|
|
||||||
|
interval = wsme.wsattr(int, mandatory=False)
|
||||||
|
"""Launch audit periodically (in seconds)"""
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
self.fields = []
|
self.fields = []
|
||||||
fields = list(objects.Audit.fields)
|
fields = list(objects.Audit.fields)
|
||||||
@@ -174,9 +208,9 @@ class Audit(base.APIBase):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def _convert_with_links(audit, url, expand=True):
|
def _convert_with_links(audit, url, expand=True):
|
||||||
if not expand:
|
if not expand:
|
||||||
audit.unset_fields_except(['uuid', 'type', 'deadline',
|
audit.unset_fields_except(['uuid', 'audit_type', 'deadline',
|
||||||
'state', 'audit_template_uuid',
|
'state', 'audit_template_uuid',
|
||||||
'audit_template_name'])
|
'audit_template_name', 'interval'])
|
||||||
|
|
||||||
# The numeric ID should not be exposed to
|
# The numeric ID should not be exposed to
|
||||||
# the user, it's internal only.
|
# the user, it's internal only.
|
||||||
@@ -199,12 +233,13 @@ class Audit(base.APIBase):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def sample(cls, expand=True):
|
def sample(cls, expand=True):
|
||||||
sample = cls(uuid='27e3153e-d5bf-4b7e-b517-fb518e17f34c',
|
sample = cls(uuid='27e3153e-d5bf-4b7e-b517-fb518e17f34c',
|
||||||
type='ONESHOT',
|
audit_type='ONESHOT',
|
||||||
state='PENDING',
|
state='PENDING',
|
||||||
deadline=None,
|
deadline=None,
|
||||||
created_at=datetime.datetime.utcnow(),
|
created_at=datetime.datetime.utcnow(),
|
||||||
deleted_at=None,
|
deleted_at=None,
|
||||||
updated_at=datetime.datetime.utcnow())
|
updated_at=datetime.datetime.utcnow(),
|
||||||
|
interval=7200)
|
||||||
sample._audit_template_uuid = '7ae81bb3-dec3-4289-8d6c-da80bd8001ae'
|
sample._audit_template_uuid = '7ae81bb3-dec3-4289-8d6c-da80bd8001ae'
|
||||||
return cls._convert_with_links(sample, 'http://localhost:9322', expand)
|
return cls._convert_with_links(sample, 'http://localhost:9322', expand)
|
||||||
|
|
||||||
@@ -293,34 +328,41 @@ class AuditsController(rest.RestController):
|
|||||||
sort_key=sort_key,
|
sort_key=sort_key,
|
||||||
sort_dir=sort_dir)
|
sort_dir=sort_dir)
|
||||||
|
|
||||||
@wsme_pecan.wsexpose(AuditCollection, types.uuid, int, wtypes.text,
|
@wsme_pecan.wsexpose(AuditCollection, wtypes.text, types.uuid, int,
|
||||||
wtypes.text, wtypes.text)
|
wtypes.text, wtypes.text)
|
||||||
def get_all(self, marker=None, limit=None,
|
def get_all(self, audit_template=None, marker=None, limit=None,
|
||||||
sort_key='id', sort_dir='asc', audit_template=None):
|
sort_key='id', sort_dir='asc'):
|
||||||
"""Retrieve a list of audits.
|
"""Retrieve a list of audits.
|
||||||
|
|
||||||
|
:param audit_template: Optional UUID or name of an audit
|
||||||
:param marker: pagination marker for large data sets.
|
:param marker: pagination marker for large data sets.
|
||||||
:param limit: maximum number of resources to return in a single result.
|
:param limit: maximum number of resources to return in a single result.
|
||||||
:param sort_key: column to sort results by. Default: id.
|
:param sort_key: column to sort results by. Default: id.
|
||||||
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
|
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
|
||||||
:param audit_template: Optional UUID or name of an audit
|
|
||||||
template, to get only audits for that audit template.
|
template, to get only audits for that audit template.
|
||||||
"""
|
"""
|
||||||
|
context = pecan.request.context
|
||||||
|
policy.enforce(context, 'audit:get_all',
|
||||||
|
action='audit:get_all')
|
||||||
return self._get_audits_collection(marker, limit, sort_key,
|
return self._get_audits_collection(marker, limit, sort_key,
|
||||||
sort_dir,
|
sort_dir,
|
||||||
audit_template=audit_template)
|
audit_template=audit_template)
|
||||||
|
|
||||||
@wsme_pecan.wsexpose(AuditCollection, types.uuid, int, wtypes.text,
|
@wsme_pecan.wsexpose(AuditCollection, wtypes.text, types.uuid, int,
|
||||||
wtypes.text)
|
wtypes.text, wtypes.text)
|
||||||
def detail(self, marker=None, limit=None,
|
def detail(self, audit_template=None, marker=None, limit=None,
|
||||||
sort_key='id', sort_dir='asc'):
|
sort_key='id', sort_dir='asc'):
|
||||||
"""Retrieve a list of audits with detail.
|
"""Retrieve a list of audits with detail.
|
||||||
|
|
||||||
|
:param audit_template: Optional UUID or name of an audit
|
||||||
:param marker: pagination marker for large data sets.
|
:param marker: pagination marker for large data sets.
|
||||||
:param limit: maximum number of resources to return in a single result.
|
:param limit: maximum number of resources to return in a single result.
|
||||||
:param sort_key: column to sort results by. Default: id.
|
:param sort_key: column to sort results by. Default: id.
|
||||||
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
|
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
|
||||||
"""
|
"""
|
||||||
|
context = pecan.request.context
|
||||||
|
policy.enforce(context, 'audit:detail',
|
||||||
|
action='audit:detail')
|
||||||
# NOTE(lucasagomes): /detail should only work agaist collections
|
# NOTE(lucasagomes): /detail should only work agaist collections
|
||||||
parent = pecan.request.path.split('/')[:-1][-1]
|
parent = pecan.request.path.split('/')[:-1][-1]
|
||||||
if parent != "audits":
|
if parent != "audits":
|
||||||
@@ -330,7 +372,8 @@ class AuditsController(rest.RestController):
|
|||||||
resource_url = '/'.join(['audits', 'detail'])
|
resource_url = '/'.join(['audits', 'detail'])
|
||||||
return self._get_audits_collection(marker, limit,
|
return self._get_audits_collection(marker, limit,
|
||||||
sort_key, sort_dir, expand,
|
sort_key, sort_dir, expand,
|
||||||
resource_url)
|
resource_url,
|
||||||
|
audit_template=audit_template)
|
||||||
|
|
||||||
@wsme_pecan.wsexpose(Audit, types.uuid)
|
@wsme_pecan.wsexpose(Audit, types.uuid)
|
||||||
def get_one(self, audit_uuid):
|
def get_one(self, audit_uuid):
|
||||||
@@ -341,16 +384,23 @@ class AuditsController(rest.RestController):
|
|||||||
if self.from_audits:
|
if self.from_audits:
|
||||||
raise exception.OperationNotPermitted
|
raise exception.OperationNotPermitted
|
||||||
|
|
||||||
rpc_audit = objects.Audit.get_by_uuid(pecan.request.context,
|
context = pecan.request.context
|
||||||
audit_uuid)
|
rpc_audit = api_utils.get_resource('Audit', audit_uuid)
|
||||||
|
policy.enforce(context, 'audit:get', rpc_audit, action='audit:get')
|
||||||
|
|
||||||
return Audit.convert_with_links(rpc_audit)
|
return Audit.convert_with_links(rpc_audit)
|
||||||
|
|
||||||
@wsme_pecan.wsexpose(Audit, body=Audit, status_code=201)
|
@wsme_pecan.wsexpose(Audit, body=AuditPostType, status_code=201)
|
||||||
def post(self, audit):
|
def post(self, audit_p):
|
||||||
"""Create a new audit.
|
"""Create a new audit.
|
||||||
|
|
||||||
:param audit: a audit within the request body.
|
:param audit_p: a audit within the request body.
|
||||||
"""
|
"""
|
||||||
|
context = pecan.request.context
|
||||||
|
policy.enforce(context, 'audit:create',
|
||||||
|
action='audit:create')
|
||||||
|
|
||||||
|
audit = audit_p.as_audit()
|
||||||
if self.from_audits:
|
if self.from_audits:
|
||||||
raise exception.OperationNotPermitted
|
raise exception.OperationNotPermitted
|
||||||
|
|
||||||
@@ -359,6 +409,25 @@ class AuditsController(rest.RestController):
|
|||||||
message=_('The audit template UUID or name specified is '
|
message=_('The audit template UUID or name specified is '
|
||||||
'invalid'))
|
'invalid'))
|
||||||
|
|
||||||
|
audit_template = objects.AuditTemplate.get(pecan.request.context,
|
||||||
|
audit._audit_template_uuid)
|
||||||
|
strategy_id = audit_template.strategy_id
|
||||||
|
no_schema = True
|
||||||
|
if strategy_id is not None:
|
||||||
|
# validate parameter when predefined strategy in audit template
|
||||||
|
strategy = objects.Strategy.get(pecan.request.context, strategy_id)
|
||||||
|
schema = strategy.parameters_spec
|
||||||
|
if schema:
|
||||||
|
# validate input parameter with default value feedback
|
||||||
|
no_schema = False
|
||||||
|
utils.DefaultValidatingDraft4Validator(schema).validate(
|
||||||
|
audit.parameters)
|
||||||
|
|
||||||
|
if no_schema and audit.parameters:
|
||||||
|
raise exception.Invalid(_('Specify parameters but no predefined '
|
||||||
|
'strategy for audit template, or no '
|
||||||
|
'parameter spec in predefined strategy'))
|
||||||
|
|
||||||
audit_dict = audit.as_dict()
|
audit_dict = audit.as_dict()
|
||||||
context = pecan.request.context
|
context = pecan.request.context
|
||||||
new_audit = objects.Audit(context, **audit_dict)
|
new_audit = objects.Audit(context, **audit_dict)
|
||||||
@@ -369,8 +438,9 @@ class AuditsController(rest.RestController):
|
|||||||
|
|
||||||
# trigger decision-engine to run the audit
|
# trigger decision-engine to run the audit
|
||||||
|
|
||||||
dc_client = rpcapi.DecisionEngineAPI()
|
if new_audit.audit_type == objects.audit.AuditType.ONESHOT.value:
|
||||||
dc_client.trigger_audit(context, new_audit.uuid)
|
dc_client = rpcapi.DecisionEngineAPI()
|
||||||
|
dc_client.trigger_audit(context, new_audit.uuid)
|
||||||
|
|
||||||
return Audit.convert_with_links(new_audit)
|
return Audit.convert_with_links(new_audit)
|
||||||
|
|
||||||
@@ -385,6 +455,12 @@ class AuditsController(rest.RestController):
|
|||||||
if self.from_audits:
|
if self.from_audits:
|
||||||
raise exception.OperationNotPermitted
|
raise exception.OperationNotPermitted
|
||||||
|
|
||||||
|
context = pecan.request.context
|
||||||
|
audit_to_update = api_utils.get_resource('Audit',
|
||||||
|
audit_uuid)
|
||||||
|
policy.enforce(context, 'audit:update', audit_to_update,
|
||||||
|
action='audit:update')
|
||||||
|
|
||||||
audit_to_update = objects.Audit.get_by_uuid(pecan.request.context,
|
audit_to_update = objects.Audit.get_by_uuid(pecan.request.context,
|
||||||
audit_uuid)
|
audit_uuid)
|
||||||
try:
|
try:
|
||||||
@@ -414,8 +490,9 @@ class AuditsController(rest.RestController):
|
|||||||
|
|
||||||
:param audit_uuid: UUID of a audit.
|
:param audit_uuid: UUID of a audit.
|
||||||
"""
|
"""
|
||||||
|
context = pecan.request.context
|
||||||
|
audit_to_delete = api_utils.get_resource('Audit', audit_uuid)
|
||||||
|
policy.enforce(context, 'audit:update', audit_to_delete,
|
||||||
|
action='audit:update')
|
||||||
|
|
||||||
audit_to_delete = objects.Audit.get_by_uuid(
|
|
||||||
pecan.request.context,
|
|
||||||
audit_uuid)
|
|
||||||
audit_to_delete.soft_delete()
|
audit_to_delete.soft_delete()
|
||||||
|
|||||||
@@ -56,22 +56,158 @@ import wsme
|
|||||||
from wsme import types as wtypes
|
from wsme import types as wtypes
|
||||||
import wsmeext.pecan as wsme_pecan
|
import wsmeext.pecan as wsme_pecan
|
||||||
|
|
||||||
|
from watcher._i18n import _
|
||||||
from watcher.api.controllers import base
|
from watcher.api.controllers import base
|
||||||
from watcher.api.controllers import link
|
from watcher.api.controllers import link
|
||||||
from watcher.api.controllers.v1 import collection
|
from watcher.api.controllers.v1 import collection
|
||||||
from watcher.api.controllers.v1 import types
|
from watcher.api.controllers.v1 import types
|
||||||
from watcher.api.controllers.v1 import utils as api_utils
|
from watcher.api.controllers.v1 import utils as api_utils
|
||||||
|
from watcher.common import context as context_utils
|
||||||
from watcher.common import exception
|
from watcher.common import exception
|
||||||
|
from watcher.common import policy
|
||||||
from watcher.common import utils as common_utils
|
from watcher.common import utils as common_utils
|
||||||
from watcher import objects
|
from watcher import objects
|
||||||
|
|
||||||
|
|
||||||
|
class AuditTemplatePostType(wtypes.Base):
|
||||||
|
_ctx = context_utils.make_context()
|
||||||
|
|
||||||
|
name = wtypes.wsattr(wtypes.text, mandatory=True)
|
||||||
|
"""Name of this audit template"""
|
||||||
|
|
||||||
|
description = wtypes.wsattr(wtypes.text, mandatory=False)
|
||||||
|
"""Short description of this audit template"""
|
||||||
|
|
||||||
|
deadline = wsme.wsattr(datetime.datetime, mandatory=False)
|
||||||
|
"""deadline of the audit template"""
|
||||||
|
|
||||||
|
host_aggregate = wsme.wsattr(wtypes.IntegerType(minimum=1),
|
||||||
|
mandatory=False)
|
||||||
|
"""ID of the Nova host aggregate targeted by the audit template"""
|
||||||
|
|
||||||
|
extra = wtypes.wsattr({wtypes.text: types.jsontype}, mandatory=False)
|
||||||
|
"""The metadata of the audit template"""
|
||||||
|
|
||||||
|
goal = wtypes.wsattr(wtypes.text, mandatory=True)
|
||||||
|
"""Goal UUID or name of the audit template"""
|
||||||
|
|
||||||
|
strategy = wtypes.wsattr(wtypes.text, mandatory=False)
|
||||||
|
"""Strategy UUID or name of the audit template"""
|
||||||
|
|
||||||
|
version = wtypes.text
|
||||||
|
"""Internal version of the audit template"""
|
||||||
|
|
||||||
|
def as_audit_template(self):
|
||||||
|
return AuditTemplate(
|
||||||
|
name=self.name,
|
||||||
|
description=self.description,
|
||||||
|
deadline=self.deadline,
|
||||||
|
host_aggregate=self.host_aggregate,
|
||||||
|
extra=self.extra,
|
||||||
|
goal_id=self.goal, # Dirty trick ...
|
||||||
|
goal=self.goal,
|
||||||
|
strategy_id=self.strategy, # Dirty trick ...
|
||||||
|
strategy_uuid=self.strategy,
|
||||||
|
version=self.version,
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def validate(audit_template):
|
||||||
|
available_goals = objects.Goal.list(AuditTemplatePostType._ctx)
|
||||||
|
available_goal_uuids_map = {g.uuid: g for g in available_goals}
|
||||||
|
available_goal_names_map = {g.name: g for g in available_goals}
|
||||||
|
if audit_template.goal in available_goal_uuids_map:
|
||||||
|
goal = available_goal_uuids_map[audit_template.goal]
|
||||||
|
elif audit_template.goal in available_goal_names_map:
|
||||||
|
goal = available_goal_names_map[audit_template.goal]
|
||||||
|
else:
|
||||||
|
raise exception.InvalidGoal(goal=audit_template.goal)
|
||||||
|
|
||||||
|
if audit_template.strategy:
|
||||||
|
available_strategies = objects.Strategy.list(
|
||||||
|
AuditTemplatePostType._ctx)
|
||||||
|
available_strategies_map = {
|
||||||
|
s.uuid: s for s in available_strategies}
|
||||||
|
if audit_template.strategy not in available_strategies_map:
|
||||||
|
raise exception.InvalidStrategy(
|
||||||
|
strategy=audit_template.strategy)
|
||||||
|
|
||||||
|
strategy = available_strategies_map[audit_template.strategy]
|
||||||
|
# Check that the strategy we indicate is actually related to the
|
||||||
|
# specified goal
|
||||||
|
if strategy.goal_id != goal.id:
|
||||||
|
choices = ["'%s' (%s)" % (s.uuid, s.name)
|
||||||
|
for s in available_strategies]
|
||||||
|
raise exception.InvalidStrategy(
|
||||||
|
message=_(
|
||||||
|
"'%(strategy)s' strategy does relate to the "
|
||||||
|
"'%(goal)s' goal. Possible choices: %(choices)s")
|
||||||
|
% dict(strategy=strategy.name, goal=goal.name,
|
||||||
|
choices=", ".join(choices)))
|
||||||
|
audit_template.strategy = strategy.uuid
|
||||||
|
|
||||||
|
# We force the UUID so that we do not need to query the DB with the
|
||||||
|
# name afterwards
|
||||||
|
audit_template.goal = goal.uuid
|
||||||
|
|
||||||
|
return audit_template
|
||||||
|
|
||||||
|
|
||||||
class AuditTemplatePatchType(types.JsonPatchType):
|
class AuditTemplatePatchType(types.JsonPatchType):
|
||||||
|
|
||||||
|
_ctx = context_utils.make_context()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def mandatory_attrs():
|
def mandatory_attrs():
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def validate(patch):
|
||||||
|
if patch.path == "/goal" and patch.op != "remove":
|
||||||
|
AuditTemplatePatchType._validate_goal(patch)
|
||||||
|
elif patch.path == "/goal" and patch.op == "remove":
|
||||||
|
raise exception.OperationNotPermitted(
|
||||||
|
_("Cannot remove 'goal' attribute "
|
||||||
|
"from an audit template"))
|
||||||
|
if patch.path == "/strategy":
|
||||||
|
AuditTemplatePatchType._validate_strategy(patch)
|
||||||
|
return types.JsonPatchType.validate(patch)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _validate_goal(patch):
|
||||||
|
patch.path = "/goal_id"
|
||||||
|
goal = patch.value
|
||||||
|
|
||||||
|
if goal:
|
||||||
|
available_goals = objects.Goal.list(
|
||||||
|
AuditTemplatePatchType._ctx)
|
||||||
|
available_goal_uuids_map = {g.uuid: g for g in available_goals}
|
||||||
|
available_goal_names_map = {g.name: g for g in available_goals}
|
||||||
|
if goal in available_goal_uuids_map:
|
||||||
|
patch.value = available_goal_uuids_map[goal].id
|
||||||
|
elif goal in available_goal_names_map:
|
||||||
|
patch.value = available_goal_names_map[goal].id
|
||||||
|
else:
|
||||||
|
raise exception.InvalidGoal(goal=goal)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _validate_strategy(patch):
|
||||||
|
patch.path = "/strategy_id"
|
||||||
|
strategy = patch.value
|
||||||
|
if strategy:
|
||||||
|
available_strategies = objects.Strategy.list(
|
||||||
|
AuditTemplatePatchType._ctx)
|
||||||
|
available_strategy_uuids_map = {
|
||||||
|
s.uuid: s for s in available_strategies}
|
||||||
|
available_strategy_names_map = {
|
||||||
|
s.name: s for s in available_strategies}
|
||||||
|
if strategy in available_strategy_uuids_map:
|
||||||
|
patch.value = available_strategy_uuids_map[strategy].id
|
||||||
|
elif strategy in available_strategy_names_map:
|
||||||
|
patch.value = available_strategy_names_map[strategy].id
|
||||||
|
else:
|
||||||
|
raise exception.InvalidStrategy(strategy=strategy)
|
||||||
|
|
||||||
|
|
||||||
class AuditTemplate(base.APIBase):
|
class AuditTemplate(base.APIBase):
|
||||||
"""API representation of a audit template.
|
"""API representation of a audit template.
|
||||||
@@ -80,7 +216,90 @@ class AuditTemplate(base.APIBase):
|
|||||||
between the internal object model and the API representation of an
|
between the internal object model and the API representation of an
|
||||||
audit template.
|
audit template.
|
||||||
"""
|
"""
|
||||||
uuid = types.uuid
|
|
||||||
|
_goal_uuid = None
|
||||||
|
_goal_name = None
|
||||||
|
|
||||||
|
_strategy_uuid = None
|
||||||
|
_strategy_name = None
|
||||||
|
|
||||||
|
def _get_goal(self, value):
|
||||||
|
if value == wtypes.Unset:
|
||||||
|
return None
|
||||||
|
goal = None
|
||||||
|
try:
|
||||||
|
if (common_utils.is_uuid_like(value) or
|
||||||
|
common_utils.is_int_like(value)):
|
||||||
|
goal = objects.Goal.get(
|
||||||
|
pecan.request.context, value)
|
||||||
|
else:
|
||||||
|
goal = objects.Goal.get_by_name(
|
||||||
|
pecan.request.context, value)
|
||||||
|
except exception.GoalNotFound:
|
||||||
|
pass
|
||||||
|
if goal:
|
||||||
|
self.goal_id = goal.id
|
||||||
|
return goal
|
||||||
|
|
||||||
|
def _get_strategy(self, value):
|
||||||
|
if value == wtypes.Unset:
|
||||||
|
return None
|
||||||
|
strategy = None
|
||||||
|
try:
|
||||||
|
if (common_utils.is_uuid_like(value) or
|
||||||
|
common_utils.is_int_like(value)):
|
||||||
|
strategy = objects.Strategy.get(
|
||||||
|
pecan.request.context, value)
|
||||||
|
else:
|
||||||
|
strategy = objects.Strategy.get_by_name(
|
||||||
|
pecan.request.context, value)
|
||||||
|
except exception.StrategyNotFound:
|
||||||
|
pass
|
||||||
|
if strategy:
|
||||||
|
self.strategy_id = strategy.id
|
||||||
|
return strategy
|
||||||
|
|
||||||
|
def _get_goal_uuid(self):
|
||||||
|
return self._goal_uuid
|
||||||
|
|
||||||
|
def _set_goal_uuid(self, value):
|
||||||
|
if value and self._goal_uuid != value:
|
||||||
|
self._goal_uuid = None
|
||||||
|
goal = self._get_goal(value)
|
||||||
|
if goal:
|
||||||
|
self._goal_uuid = goal.uuid
|
||||||
|
|
||||||
|
def _get_strategy_uuid(self):
|
||||||
|
return self._strategy_uuid
|
||||||
|
|
||||||
|
def _set_strategy_uuid(self, value):
|
||||||
|
if value and self._strategy_uuid != value:
|
||||||
|
self._strategy_uuid = None
|
||||||
|
strategy = self._get_strategy(value)
|
||||||
|
if strategy:
|
||||||
|
self._strategy_uuid = strategy.uuid
|
||||||
|
|
||||||
|
def _get_goal_name(self):
|
||||||
|
return self._goal_name
|
||||||
|
|
||||||
|
def _set_goal_name(self, value):
|
||||||
|
if value and self._goal_name != value:
|
||||||
|
self._goal_name = None
|
||||||
|
goal = self._get_goal(value)
|
||||||
|
if goal:
|
||||||
|
self._goal_name = goal.name
|
||||||
|
|
||||||
|
def _get_strategy_name(self):
|
||||||
|
return self._strategy_name
|
||||||
|
|
||||||
|
def _set_strategy_name(self, value):
|
||||||
|
if value and self._strategy_name != value:
|
||||||
|
self._strategy_name = None
|
||||||
|
strategy = self._get_strategy(value)
|
||||||
|
if strategy:
|
||||||
|
self._strategy_name = strategy.name
|
||||||
|
|
||||||
|
uuid = wtypes.wsattr(types.uuid, readonly=True)
|
||||||
"""Unique UUID for this audit template"""
|
"""Unique UUID for this audit template"""
|
||||||
|
|
||||||
name = wtypes.text
|
name = wtypes.text
|
||||||
@@ -98,8 +317,21 @@ class AuditTemplate(base.APIBase):
|
|||||||
extra = {wtypes.text: types.jsontype}
|
extra = {wtypes.text: types.jsontype}
|
||||||
"""The metadata of the audit template"""
|
"""The metadata of the audit template"""
|
||||||
|
|
||||||
goal = wtypes.text
|
goal_uuid = wsme.wsproperty(
|
||||||
"""Goal type of the audit template"""
|
wtypes.text, _get_goal_uuid, _set_goal_uuid, mandatory=True)
|
||||||
|
"""Goal UUID the audit template refers to"""
|
||||||
|
|
||||||
|
goal_name = wsme.wsproperty(
|
||||||
|
wtypes.text, _get_goal_name, _set_goal_name, mandatory=False)
|
||||||
|
"""The name of the goal this audit template refers to"""
|
||||||
|
|
||||||
|
strategy_uuid = wsme.wsproperty(
|
||||||
|
wtypes.text, _get_strategy_uuid, _set_strategy_uuid, mandatory=False)
|
||||||
|
"""Strategy UUID the audit template refers to"""
|
||||||
|
|
||||||
|
strategy_name = wsme.wsproperty(
|
||||||
|
wtypes.text, _get_strategy_name, _set_strategy_name, mandatory=False)
|
||||||
|
"""The name of the strategy this audit template refers to"""
|
||||||
|
|
||||||
version = wtypes.text
|
version = wtypes.text
|
||||||
"""Internal version of the audit template"""
|
"""Internal version of the audit template"""
|
||||||
@@ -112,20 +344,43 @@ class AuditTemplate(base.APIBase):
|
|||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
super(AuditTemplate, self).__init__()
|
super(AuditTemplate, self).__init__()
|
||||||
|
|
||||||
self.fields = []
|
self.fields = []
|
||||||
for field in objects.AuditTemplate.fields:
|
fields = list(objects.AuditTemplate.fields)
|
||||||
|
|
||||||
|
for k in fields:
|
||||||
# Skip fields we do not expose.
|
# Skip fields we do not expose.
|
||||||
if not hasattr(self, field):
|
if not hasattr(self, k):
|
||||||
continue
|
continue
|
||||||
self.fields.append(field)
|
self.fields.append(k)
|
||||||
setattr(self, field, kwargs.get(field, wtypes.Unset))
|
setattr(self, k, kwargs.get(k, wtypes.Unset))
|
||||||
|
|
||||||
|
self.fields.append('goal_id')
|
||||||
|
self.fields.append('strategy_id')
|
||||||
|
|
||||||
|
# goal_uuid & strategy_uuid are not part of
|
||||||
|
# objects.AuditTemplate.fields because they're API-only attributes.
|
||||||
|
self.fields.append('goal_uuid')
|
||||||
|
self.fields.append('goal_name')
|
||||||
|
self.fields.append('strategy_uuid')
|
||||||
|
self.fields.append('strategy_name')
|
||||||
|
setattr(self, 'goal_uuid', kwargs.get('goal_id', wtypes.Unset))
|
||||||
|
setattr(self, 'goal_name', kwargs.get('goal_id', wtypes.Unset))
|
||||||
|
setattr(self, 'strategy_uuid',
|
||||||
|
kwargs.get('strategy_id', wtypes.Unset))
|
||||||
|
setattr(self, 'strategy_name',
|
||||||
|
kwargs.get('strategy_id', wtypes.Unset))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _convert_with_links(audit_template, url, expand=True):
|
def _convert_with_links(audit_template, url, expand=True):
|
||||||
if not expand:
|
if not expand:
|
||||||
audit_template.unset_fields_except(['uuid', 'name',
|
audit_template.unset_fields_except(
|
||||||
'host_aggregate', 'goal'])
|
['uuid', 'name', 'host_aggregate', 'goal_uuid', 'goal_name',
|
||||||
|
'strategy_uuid', 'strategy_name'])
|
||||||
|
|
||||||
|
# The numeric ID should not be exposed to
|
||||||
|
# the user, it's internal only.
|
||||||
|
audit_template.goal_id = wtypes.Unset
|
||||||
|
audit_template.strategy_id = wtypes.Unset
|
||||||
|
|
||||||
audit_template.links = [link.Link.make_link('self', url,
|
audit_template.links = [link.Link.make_link('self', url,
|
||||||
'audit_templates',
|
'audit_templates',
|
||||||
@@ -133,8 +388,7 @@ class AuditTemplate(base.APIBase):
|
|||||||
link.Link.make_link('bookmark', url,
|
link.Link.make_link('bookmark', url,
|
||||||
'audit_templates',
|
'audit_templates',
|
||||||
audit_template.uuid,
|
audit_template.uuid,
|
||||||
bookmark=True)
|
bookmark=True)]
|
||||||
]
|
|
||||||
return audit_template
|
return audit_template
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -149,7 +403,8 @@ class AuditTemplate(base.APIBase):
|
|||||||
name='My Audit Template',
|
name='My Audit Template',
|
||||||
description='Description of my audit template',
|
description='Description of my audit template',
|
||||||
host_aggregate=5,
|
host_aggregate=5,
|
||||||
goal='DUMMY',
|
goal_uuid='83e44733-b640-40e2-8d8a-7dd3be7134e6',
|
||||||
|
strategy_uuid='367d826e-b6a4-4b70-bc44-c3f6fe1c9986',
|
||||||
extra={'automatic': True},
|
extra={'automatic': True},
|
||||||
created_at=datetime.datetime.utcnow(),
|
created_at=datetime.datetime.utcnow(),
|
||||||
deleted_at=None,
|
deleted_at=None,
|
||||||
@@ -170,12 +425,12 @@ class AuditTemplateCollection(collection.Collection):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def convert_with_links(rpc_audit_templates, limit, url=None, expand=False,
|
def convert_with_links(rpc_audit_templates, limit, url=None, expand=False,
|
||||||
**kwargs):
|
**kwargs):
|
||||||
collection = AuditTemplateCollection()
|
at_collection = AuditTemplateCollection()
|
||||||
collection.audit_templates = \
|
at_collection.audit_templates = [
|
||||||
[AuditTemplate.convert_with_links(p, expand)
|
AuditTemplate.convert_with_links(p, expand)
|
||||||
for p in rpc_audit_templates]
|
for p in rpc_audit_templates]
|
||||||
collection.next = collection.get_next(limit, url=url, **kwargs)
|
at_collection.next = at_collection.get_next(limit, url=url, **kwargs)
|
||||||
return collection
|
return at_collection
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def sample(cls):
|
def sample(cls):
|
||||||
@@ -201,7 +456,8 @@ class AuditTemplatesController(rest.RestController):
|
|||||||
sort_key, sort_dir, expand=False,
|
sort_key, sort_dir, expand=False,
|
||||||
resource_url=None):
|
resource_url=None):
|
||||||
api_utils.validate_search_filters(
|
api_utils.validate_search_filters(
|
||||||
filters, objects.audit_template.AuditTemplate.fields.keys())
|
filters, list(objects.audit_template.AuditTemplate.fields.keys()) +
|
||||||
|
["goal_uuid", "goal_name", "strategy_uuid", "strategy_name"])
|
||||||
limit = api_utils.validate_limit(limit)
|
limit = api_utils.validate_limit(limit)
|
||||||
api_utils.validate_sort_dir(sort_dir)
|
api_utils.validate_sort_dir(sort_dir)
|
||||||
|
|
||||||
@@ -225,41 +481,72 @@ class AuditTemplatesController(rest.RestController):
|
|||||||
sort_key=sort_key,
|
sort_key=sort_key,
|
||||||
sort_dir=sort_dir)
|
sort_dir=sort_dir)
|
||||||
|
|
||||||
@wsme_pecan.wsexpose(AuditTemplateCollection, wtypes.text,
|
@wsme_pecan.wsexpose(AuditTemplateCollection, wtypes.text, wtypes.text,
|
||||||
types.uuid, int, wtypes.text, wtypes.text)
|
types.uuid, int, wtypes.text, wtypes.text)
|
||||||
def get_all(self, goal=None, marker=None, limit=None,
|
def get_all(self, goal=None, strategy=None, marker=None,
|
||||||
sort_key='id', sort_dir='asc'):
|
limit=None, sort_key='id', sort_dir='asc'):
|
||||||
"""Retrieve a list of audit templates.
|
"""Retrieve a list of audit templates.
|
||||||
|
|
||||||
:param goal: goal name to filter by (case sensitive)
|
:param goal: goal UUID or name to filter by
|
||||||
|
:param strategy: strategy UUID or name to filter by
|
||||||
:param marker: pagination marker for large data sets.
|
:param marker: pagination marker for large data sets.
|
||||||
:param limit: maximum number of resources to return in a single result.
|
:param limit: maximum number of resources to return in a single result.
|
||||||
:param sort_key: column to sort results by. Default: id.
|
:param sort_key: column to sort results by. Default: id.
|
||||||
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
|
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
|
||||||
"""
|
"""
|
||||||
filters = api_utils.as_filters_dict(goal=goal)
|
context = pecan.request.context
|
||||||
|
policy.enforce(context, 'audit_template:get_all',
|
||||||
|
action='audit_template:get_all')
|
||||||
|
filters = {}
|
||||||
|
if goal:
|
||||||
|
if common_utils.is_uuid_like(goal):
|
||||||
|
filters['goal_uuid'] = goal
|
||||||
|
else:
|
||||||
|
filters['goal_name'] = goal
|
||||||
|
|
||||||
|
if strategy:
|
||||||
|
if common_utils.is_uuid_like(strategy):
|
||||||
|
filters['strategy_uuid'] = strategy
|
||||||
|
else:
|
||||||
|
filters['strategy_name'] = strategy
|
||||||
|
|
||||||
return self._get_audit_templates_collection(
|
return self._get_audit_templates_collection(
|
||||||
filters, marker, limit, sort_key, sort_dir)
|
filters, marker, limit, sort_key, sort_dir)
|
||||||
|
|
||||||
@wsme_pecan.wsexpose(AuditTemplateCollection, wtypes.text, types.uuid, int,
|
@wsme_pecan.wsexpose(AuditTemplateCollection, wtypes.text, wtypes.text,
|
||||||
wtypes.text, wtypes.text)
|
types.uuid, int, wtypes.text, wtypes.text)
|
||||||
def detail(self, goal=None, marker=None, limit=None,
|
def detail(self, goal=None, strategy=None, marker=None,
|
||||||
sort_key='id', sort_dir='asc'):
|
limit=None, sort_key='id', sort_dir='asc'):
|
||||||
"""Retrieve a list of audit templates with detail.
|
"""Retrieve a list of audit templates with detail.
|
||||||
|
|
||||||
:param goal: goal name to filter by (case sensitive)
|
:param goal: goal UUID or name to filter by
|
||||||
|
:param strategy: strategy UUID or name to filter by
|
||||||
:param marker: pagination marker for large data sets.
|
:param marker: pagination marker for large data sets.
|
||||||
:param limit: maximum number of resources to return in a single result.
|
:param limit: maximum number of resources to return in a single result.
|
||||||
:param sort_key: column to sort results by. Default: id.
|
:param sort_key: column to sort results by. Default: id.
|
||||||
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
|
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
|
||||||
"""
|
"""
|
||||||
|
context = pecan.request.context
|
||||||
|
policy.enforce(context, 'audit_template:detail',
|
||||||
|
action='audit_template:detail')
|
||||||
|
|
||||||
# NOTE(lucasagomes): /detail should only work agaist collections
|
# NOTE(lucasagomes): /detail should only work agaist collections
|
||||||
parent = pecan.request.path.split('/')[:-1][-1]
|
parent = pecan.request.path.split('/')[:-1][-1]
|
||||||
if parent != "audit_templates":
|
if parent != "audit_templates":
|
||||||
raise exception.HTTPNotFound
|
raise exception.HTTPNotFound
|
||||||
|
|
||||||
filters = api_utils.as_filters_dict(goal=goal)
|
filters = {}
|
||||||
|
if goal:
|
||||||
|
if common_utils.is_uuid_like(goal):
|
||||||
|
filters['goal_uuid'] = goal
|
||||||
|
else:
|
||||||
|
filters['goal_name'] = goal
|
||||||
|
|
||||||
|
if strategy:
|
||||||
|
if common_utils.is_uuid_like(strategy):
|
||||||
|
filters['strategy_uuid'] = strategy
|
||||||
|
else:
|
||||||
|
filters['strategy_name'] = strategy
|
||||||
|
|
||||||
expand = True
|
expand = True
|
||||||
resource_url = '/'.join(['audit_templates', 'detail'])
|
resource_url = '/'.join(['audit_templates', 'detail'])
|
||||||
@@ -276,28 +563,33 @@ class AuditTemplatesController(rest.RestController):
|
|||||||
if self.from_audit_templates:
|
if self.from_audit_templates:
|
||||||
raise exception.OperationNotPermitted
|
raise exception.OperationNotPermitted
|
||||||
|
|
||||||
if common_utils.is_uuid_like(audit_template):
|
context = pecan.request.context
|
||||||
rpc_audit_template = objects.AuditTemplate.get_by_uuid(
|
rpc_audit_template = api_utils.get_resource('AuditTemplate',
|
||||||
pecan.request.context,
|
audit_template)
|
||||||
audit_template)
|
policy.enforce(context, 'audit_template:get', rpc_audit_template,
|
||||||
else:
|
action='audit_template:get')
|
||||||
rpc_audit_template = objects.AuditTemplate.get_by_name(
|
|
||||||
pecan.request.context,
|
|
||||||
audit_template)
|
|
||||||
|
|
||||||
return AuditTemplate.convert_with_links(rpc_audit_template)
|
return AuditTemplate.convert_with_links(rpc_audit_template)
|
||||||
|
|
||||||
@wsme_pecan.wsexpose(AuditTemplate, body=AuditTemplate, status_code=201)
|
@wsme.validate(types.uuid, AuditTemplatePostType)
|
||||||
def post(self, audit_template):
|
@wsme_pecan.wsexpose(AuditTemplate, body=AuditTemplatePostType,
|
||||||
|
status_code=201)
|
||||||
|
def post(self, audit_template_postdata):
|
||||||
"""Create a new audit template.
|
"""Create a new audit template.
|
||||||
|
|
||||||
:param audit template: a audit template within the request body.
|
:param audit_template_postdata: the audit template POST data
|
||||||
|
from the request body.
|
||||||
"""
|
"""
|
||||||
if self.from_audit_templates:
|
if self.from_audit_templates:
|
||||||
raise exception.OperationNotPermitted
|
raise exception.OperationNotPermitted
|
||||||
|
|
||||||
audit_template_dict = audit_template.as_dict()
|
|
||||||
context = pecan.request.context
|
context = pecan.request.context
|
||||||
|
policy.enforce(context, 'audit_template:create',
|
||||||
|
action='audit_template:create')
|
||||||
|
|
||||||
|
context = pecan.request.context
|
||||||
|
audit_template = audit_template_postdata.as_audit_template()
|
||||||
|
audit_template_dict = audit_template.as_dict()
|
||||||
new_audit_template = objects.AuditTemplate(context,
|
new_audit_template = objects.AuditTemplate(context,
|
||||||
**audit_template_dict)
|
**audit_template_dict)
|
||||||
new_audit_template.create(context)
|
new_audit_template.create(context)
|
||||||
@@ -319,6 +611,13 @@ class AuditTemplatesController(rest.RestController):
|
|||||||
if self.from_audit_templates:
|
if self.from_audit_templates:
|
||||||
raise exception.OperationNotPermitted
|
raise exception.OperationNotPermitted
|
||||||
|
|
||||||
|
context = pecan.request.context
|
||||||
|
audit_template_to_update = api_utils.get_resource('AuditTemplate',
|
||||||
|
audit_template)
|
||||||
|
policy.enforce(context, 'audit_template:update',
|
||||||
|
audit_template_to_update,
|
||||||
|
action='audit_template:update')
|
||||||
|
|
||||||
if common_utils.is_uuid_like(audit_template):
|
if common_utils.is_uuid_like(audit_template):
|
||||||
audit_template_to_update = objects.AuditTemplate.get_by_uuid(
|
audit_template_to_update = objects.AuditTemplate.get_by_uuid(
|
||||||
pecan.request.context,
|
pecan.request.context,
|
||||||
@@ -356,14 +655,11 @@ class AuditTemplatesController(rest.RestController):
|
|||||||
|
|
||||||
:param audit template_uuid: UUID or name of an audit template.
|
:param audit template_uuid: UUID or name of an audit template.
|
||||||
"""
|
"""
|
||||||
|
context = pecan.request.context
|
||||||
if common_utils.is_uuid_like(audit_template):
|
audit_template_to_delete = api_utils.get_resource('AuditTemplate',
|
||||||
audit_template_to_delete = objects.AuditTemplate.get_by_uuid(
|
audit_template)
|
||||||
pecan.request.context,
|
policy.enforce(context, 'audit_template:update',
|
||||||
audit_template)
|
audit_template_to_delete,
|
||||||
else:
|
action='audit_template:update')
|
||||||
audit_template_to_delete = objects.AuditTemplate.get_by_name(
|
|
||||||
pecan.request.context,
|
|
||||||
audit_template)
|
|
||||||
|
|
||||||
audit_template_to_delete.soft_delete()
|
audit_template_to_delete.soft_delete()
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ class Collection(base.APIBase):
|
|||||||
q_args = ''.join(['%s=%s&' % (key, kwargs[key]) for key in kwargs])
|
q_args = ''.join(['%s=%s&' % (key, kwargs[key]) for key in kwargs])
|
||||||
next_args = '?%(args)slimit=%(limit)d&marker=%(marker)s' % {
|
next_args = '?%(args)slimit=%(limit)d&marker=%(marker)s' % {
|
||||||
'args': q_args, 'limit': limit,
|
'args': q_args, 'limit': limit,
|
||||||
'marker': self.collection[-1].uuid}
|
'marker': getattr(self.collection[-1], "uuid")}
|
||||||
|
|
||||||
return link.Link.make_link('next', pecan.request.host_url,
|
return link.Link.make_link('next', pecan.request.host_url,
|
||||||
resource_url, next_args).href
|
resource_url, next_args).href
|
||||||
|
|||||||
72
watcher/api/controllers/v1/efficacy_indicator.py
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
# Copyright (c) 2016 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
An efficacy indicator is a single value that gives an indication on how the
|
||||||
|
:ref:`solution <solution_definition>` produced by a given :ref:`strategy
|
||||||
|
<strategy_definition>` performed. These efficacy indicators are specific to a
|
||||||
|
given :ref:`goal <goal_definition>` and are usually used to compute the
|
||||||
|
:ref:`gobal efficacy <efficacy_definition>` of the resulting :ref:`action plan
|
||||||
|
<action_plan_definition>`.
|
||||||
|
|
||||||
|
In Watcher, these efficacy indicators are specified alongside the goal they
|
||||||
|
relate to. When a strategy (which always relates to a goal) is executed, it
|
||||||
|
produces a solution containing the efficacy indicators specified by the goal.
|
||||||
|
This solution, which has been translated by the :ref:`Watcher Planner
|
||||||
|
<watcher_planner_definition>` into an action plan, will see its indicators and
|
||||||
|
global efficacy stored and would now be accessible through the :ref:`Watcher
|
||||||
|
API <archi_watcher_api_definition>`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import numbers
|
||||||
|
|
||||||
|
from wsme import types as wtypes
|
||||||
|
|
||||||
|
from watcher.api.controllers import base
|
||||||
|
from watcher import objects
|
||||||
|
|
||||||
|
|
||||||
|
class EfficacyIndicator(base.APIBase):
|
||||||
|
"""API representation of a efficacy indicator.
|
||||||
|
|
||||||
|
This class enforces type checking and value constraints, and converts
|
||||||
|
between the internal object model and the API representation of an
|
||||||
|
efficacy indicator.
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = wtypes.wsattr(wtypes.text, mandatory=True)
|
||||||
|
"""Name of this efficacy indicator"""
|
||||||
|
|
||||||
|
description = wtypes.wsattr(wtypes.text, mandatory=False)
|
||||||
|
"""Description of this efficacy indicator"""
|
||||||
|
|
||||||
|
unit = wtypes.wsattr(wtypes.text, mandatory=False)
|
||||||
|
"""Unit of this efficacy indicator"""
|
||||||
|
|
||||||
|
value = wtypes.wsattr(numbers.Number, mandatory=True)
|
||||||
|
"""Value of this efficacy indicator"""
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super(EfficacyIndicator, self).__init__()
|
||||||
|
|
||||||
|
self.fields = []
|
||||||
|
fields = list(objects.EfficacyIndicator.fields)
|
||||||
|
for field in fields:
|
||||||
|
# Skip fields we do not expose.
|
||||||
|
if not hasattr(self, field):
|
||||||
|
continue
|
||||||
|
self.fields.append(field)
|
||||||
|
setattr(self, field, kwargs.get(field, wtypes.Unset))
|
||||||
@@ -46,61 +46,75 @@ from watcher.api.controllers.v1 import collection
|
|||||||
from watcher.api.controllers.v1 import types
|
from watcher.api.controllers.v1 import types
|
||||||
from watcher.api.controllers.v1 import utils as api_utils
|
from watcher.api.controllers.v1 import utils as api_utils
|
||||||
from watcher.common import exception
|
from watcher.common import exception
|
||||||
|
from watcher.common import policy
|
||||||
|
from watcher import objects
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
class Goal(base.APIBase):
|
class Goal(base.APIBase):
|
||||||
"""API representation of a action.
|
"""API representation of a goal.
|
||||||
|
|
||||||
This class enforces type checking and value constraints, and converts
|
This class enforces type checking and value constraints, and converts
|
||||||
between the internal object model and the API representation of a action.
|
between the internal object model and the API representation of a goal.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
uuid = types.uuid
|
||||||
|
"""Unique UUID for this goal"""
|
||||||
|
|
||||||
name = wtypes.text
|
name = wtypes.text
|
||||||
"""Name of the goal"""
|
"""Name of the goal"""
|
||||||
|
|
||||||
strategy = wtypes.text
|
display_name = wtypes.text
|
||||||
"""The strategy associated with the goal"""
|
"""Localized name of the goal"""
|
||||||
|
|
||||||
uuid = types.uuid
|
efficacy_specification = wtypes.wsattr(types.jsontype, readonly=True)
|
||||||
"""Unused field"""
|
"""Efficacy specification for this goal"""
|
||||||
|
|
||||||
links = wsme.wsattr([link.Link], readonly=True)
|
links = wsme.wsattr([link.Link], readonly=True)
|
||||||
"""A list containing a self link and associated action links"""
|
"""A list containing a self link and associated audit template links"""
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
super(Goal, self).__init__()
|
|
||||||
|
|
||||||
self.fields = []
|
self.fields = []
|
||||||
self.fields.append('name')
|
fields = list(objects.Goal.fields)
|
||||||
self.fields.append('strategy')
|
|
||||||
setattr(self, 'name', kwargs.get('name',
|
for k in fields:
|
||||||
wtypes.Unset))
|
# Skip fields we do not expose.
|
||||||
setattr(self, 'strategy', kwargs.get('strategy',
|
if not hasattr(self, k):
|
||||||
wtypes.Unset))
|
continue
|
||||||
|
self.fields.append(k)
|
||||||
|
setattr(self, k, kwargs.get(k, wtypes.Unset))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _convert_with_links(goal, url, expand=True):
|
def _convert_with_links(goal, url, expand=True):
|
||||||
if not expand:
|
if not expand:
|
||||||
goal.unset_fields_except(['name', 'strategy'])
|
goal.unset_fields_except(['uuid', 'name', 'display_name',
|
||||||
|
'efficacy_specification'])
|
||||||
|
|
||||||
goal.links = [link.Link.make_link('self', url,
|
goal.links = [link.Link.make_link('self', url,
|
||||||
'goals', goal.name),
|
'goals', goal.uuid),
|
||||||
link.Link.make_link('bookmark', url,
|
link.Link.make_link('bookmark', url,
|
||||||
'goals', goal.name,
|
'goals', goal.uuid,
|
||||||
bookmark=True)]
|
bookmark=True)]
|
||||||
return goal
|
return goal
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def convert_with_links(cls, goal, expand=True):
|
def convert_with_links(cls, goal, expand=True):
|
||||||
goal = Goal(**goal)
|
goal = Goal(**goal.as_dict())
|
||||||
return cls._convert_with_links(goal, pecan.request.host_url, expand)
|
return cls._convert_with_links(goal, pecan.request.host_url, expand)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def sample(cls, expand=True):
|
def sample(cls, expand=True):
|
||||||
sample = cls(name='27e3153e-d5bf-4b7e-b517-fb518e17f34c',
|
sample = cls(
|
||||||
strategy='action description')
|
uuid='27e3153e-d5bf-4b7e-b517-fb518e17f34c',
|
||||||
|
name='DUMMY',
|
||||||
|
display_name='Dummy strategy',
|
||||||
|
efficacy_specification=[
|
||||||
|
{'description': 'Dummy indicator', 'name': 'dummy',
|
||||||
|
'schema': 'Range(min=0, max=100, min_included=True, '
|
||||||
|
'max_included=True, msg=None)',
|
||||||
|
'unit': '%'}
|
||||||
|
])
|
||||||
return cls._convert_with_links(sample, 'http://localhost:9322', expand)
|
return cls._convert_with_links(sample, 'http://localhost:9322', expand)
|
||||||
|
|
||||||
|
|
||||||
@@ -117,27 +131,28 @@ class GoalCollection(collection.Collection):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def convert_with_links(goals, limit, url=None, expand=False,
|
def convert_with_links(goals, limit, url=None, expand=False,
|
||||||
**kwargs):
|
**kwargs):
|
||||||
|
goal_collection = GoalCollection()
|
||||||
collection = GoalCollection()
|
goal_collection.goals = [
|
||||||
collection.goals = [Goal.convert_with_links(g, expand) for g in goals]
|
Goal.convert_with_links(g, expand) for g in goals]
|
||||||
|
|
||||||
if 'sort_key' in kwargs:
|
if 'sort_key' in kwargs:
|
||||||
reverse = False
|
reverse = False
|
||||||
if kwargs['sort_key'] == 'strategy':
|
if kwargs['sort_key'] == 'strategy':
|
||||||
if 'sort_dir' in kwargs:
|
if 'sort_dir' in kwargs:
|
||||||
reverse = True if kwargs['sort_dir'] == 'desc' else False
|
reverse = True if kwargs['sort_dir'] == 'desc' else False
|
||||||
collection.goals = sorted(
|
goal_collection.goals = sorted(
|
||||||
collection.goals,
|
goal_collection.goals,
|
||||||
key=lambda goal: goal.name,
|
key=lambda goal: goal.uuid,
|
||||||
reverse=reverse)
|
reverse=reverse)
|
||||||
|
|
||||||
collection.next = collection.get_next(limit, url=url, **kwargs)
|
goal_collection.next = goal_collection.get_next(
|
||||||
return collection
|
limit, url=url, **kwargs)
|
||||||
|
return goal_collection
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def sample(cls):
|
def sample(cls):
|
||||||
sample = cls()
|
sample = cls()
|
||||||
sample.actions = [Goal.sample(expand=False)]
|
sample.goals = [Goal.sample(expand=False)]
|
||||||
return sample
|
return sample
|
||||||
|
|
||||||
|
|
||||||
@@ -154,73 +169,76 @@ class GoalsController(rest.RestController):
|
|||||||
'detail': ['GET'],
|
'detail': ['GET'],
|
||||||
}
|
}
|
||||||
|
|
||||||
def _get_goals_collection(self, limit,
|
def _get_goals_collection(self, marker, limit, sort_key, sort_dir,
|
||||||
sort_key, sort_dir, expand=False,
|
expand=False, resource_url=None):
|
||||||
resource_url=None, goal_name=None):
|
|
||||||
|
|
||||||
limit = api_utils.validate_limit(limit)
|
limit = api_utils.validate_limit(limit)
|
||||||
api_utils.validate_sort_dir(sort_dir)
|
api_utils.validate_sort_dir(sort_dir)
|
||||||
|
|
||||||
goals = []
|
sort_db_key = (sort_key if sort_key in objects.Goal.fields.keys()
|
||||||
|
else None)
|
||||||
|
|
||||||
if not goal_name and goal_name in CONF.watcher_goals.goals.keys():
|
marker_obj = None
|
||||||
goals.append({'name': goal_name, 'strategy': goals[goal_name]})
|
if marker:
|
||||||
else:
|
marker_obj = objects.Goal.get_by_uuid(
|
||||||
for name, strategy in CONF.watcher_goals.goals.items():
|
pecan.request.context, marker)
|
||||||
goals.append({'name': name, 'strategy': strategy})
|
|
||||||
|
|
||||||
return GoalCollection.convert_with_links(goals[:limit], limit,
|
goals = objects.Goal.list(pecan.request.context, limit, marker_obj,
|
||||||
|
sort_key=sort_db_key, sort_dir=sort_dir)
|
||||||
|
|
||||||
|
return GoalCollection.convert_with_links(goals, limit,
|
||||||
url=resource_url,
|
url=resource_url,
|
||||||
expand=expand,
|
expand=expand,
|
||||||
sort_key=sort_key,
|
sort_key=sort_key,
|
||||||
sort_dir=sort_dir)
|
sort_dir=sort_dir)
|
||||||
|
|
||||||
@wsme_pecan.wsexpose(GoalCollection, int, wtypes.text, wtypes.text)
|
@wsme_pecan.wsexpose(GoalCollection, wtypes.text,
|
||||||
def get_all(self, limit=None,
|
int, wtypes.text, wtypes.text)
|
||||||
sort_key='name', sort_dir='asc'):
|
def get_all(self, marker=None, limit=None, sort_key='id', sort_dir='asc'):
|
||||||
"""Retrieve a list of goals.
|
"""Retrieve a list of goals.
|
||||||
|
|
||||||
|
:param marker: pagination marker for large data sets.
|
||||||
:param limit: maximum number of resources to return in a single result.
|
:param limit: maximum number of resources to return in a single result.
|
||||||
:param sort_key: column to sort results by. Default: id.
|
:param sort_key: column to sort results by. Default: id.
|
||||||
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
|
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
|
||||||
to get only actions for that goal.
|
|
||||||
"""
|
"""
|
||||||
return self._get_goals_collection(limit, sort_key, sort_dir)
|
context = pecan.request.context
|
||||||
|
policy.enforce(context, 'goal:get_all',
|
||||||
|
action='goal:get_all')
|
||||||
|
return self._get_goals_collection(marker, limit, sort_key, sort_dir)
|
||||||
|
|
||||||
@wsme_pecan.wsexpose(GoalCollection, wtypes.text, int,
|
@wsme_pecan.wsexpose(GoalCollection, wtypes.text, int,
|
||||||
wtypes.text, wtypes.text)
|
wtypes.text, wtypes.text)
|
||||||
def detail(self, goal_name=None, limit=None,
|
def detail(self, marker=None, limit=None, sort_key='id', sort_dir='asc'):
|
||||||
sort_key='name', sort_dir='asc'):
|
"""Retrieve a list of goals with detail.
|
||||||
"""Retrieve a list of actions with detail.
|
|
||||||
|
|
||||||
:param goal_name: name of a goal, to get only goals for that
|
:param marker: pagination marker for large data sets.
|
||||||
action.
|
|
||||||
:param limit: maximum number of resources to return in a single result.
|
:param limit: maximum number of resources to return in a single result.
|
||||||
:param sort_key: column to sort results by. Default: id.
|
:param sort_key: column to sort results by. Default: id.
|
||||||
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
|
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
|
||||||
to get only goals for that goal.
|
|
||||||
"""
|
"""
|
||||||
|
context = pecan.request.context
|
||||||
|
policy.enforce(context, 'goal:detail',
|
||||||
|
action='goal:detail')
|
||||||
# NOTE(lucasagomes): /detail should only work agaist collections
|
# NOTE(lucasagomes): /detail should only work agaist collections
|
||||||
parent = pecan.request.path.split('/')[:-1][-1]
|
parent = pecan.request.path.split('/')[:-1][-1]
|
||||||
if parent != "goals":
|
if parent != "goals":
|
||||||
raise exception.HTTPNotFound
|
raise exception.HTTPNotFound
|
||||||
expand = True
|
expand = True
|
||||||
resource_url = '/'.join(['goals', 'detail'])
|
resource_url = '/'.join(['goals', 'detail'])
|
||||||
return self._get_goals_collection(limit, sort_key, sort_dir,
|
return self._get_goals_collection(marker, limit, sort_key, sort_dir,
|
||||||
expand, resource_url, goal_name)
|
expand, resource_url)
|
||||||
|
|
||||||
@wsme_pecan.wsexpose(Goal, wtypes.text)
|
@wsme_pecan.wsexpose(Goal, wtypes.text)
|
||||||
def get_one(self, goal_name):
|
def get_one(self, goal):
|
||||||
"""Retrieve information about the given goal.
|
"""Retrieve information about the given goal.
|
||||||
|
|
||||||
:param goal_name: name of the goal.
|
:param goal: UUID or name of the goal.
|
||||||
"""
|
"""
|
||||||
if self.from_goals:
|
if self.from_goals:
|
||||||
raise exception.OperationNotPermitted
|
raise exception.OperationNotPermitted
|
||||||
|
|
||||||
goals = CONF.watcher_goals.goals
|
context = pecan.request.context
|
||||||
goal = {}
|
rpc_goal = api_utils.get_resource('Goal', goal)
|
||||||
if goal_name in goals.keys():
|
policy.enforce(context, 'goal:get', rpc_goal, action='goal:get')
|
||||||
goal = {'name': goal_name, 'strategy': goals[goal_name]}
|
|
||||||
|
|
||||||
return Goal.convert_with_links(goal)
|
return Goal.convert_with_links(rpc_goal)
|
||||||
|
|||||||
309
watcher/api/controllers/v1/strategy.py
Normal file
@@ -0,0 +1,309 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
# Copyright (c) 2016 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
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>`.
|
||||||
|
|
||||||
|
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
|
||||||
|
specific :ref:`Strategy <strategy_definition>` should be used for each goal.
|
||||||
|
|
||||||
|
Some strategies may provide better optimization results but may take more time
|
||||||
|
to find an optimal :ref:`Solution <solution_definition>`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
import pecan
|
||||||
|
from pecan import rest
|
||||||
|
import wsme
|
||||||
|
from wsme import types as wtypes
|
||||||
|
import wsmeext.pecan as wsme_pecan
|
||||||
|
|
||||||
|
from watcher.api.controllers import base
|
||||||
|
from watcher.api.controllers import link
|
||||||
|
from watcher.api.controllers.v1 import collection
|
||||||
|
from watcher.api.controllers.v1 import types
|
||||||
|
from watcher.api.controllers.v1 import utils as api_utils
|
||||||
|
from watcher.common import exception
|
||||||
|
from watcher.common import policy
|
||||||
|
from watcher.common import utils as common_utils
|
||||||
|
from watcher import objects
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
|
class Strategy(base.APIBase):
|
||||||
|
"""API representation of a strategy.
|
||||||
|
|
||||||
|
This class enforces type checking and value constraints, and converts
|
||||||
|
between the internal object model and the API representation of a strategy.
|
||||||
|
"""
|
||||||
|
_goal_uuid = None
|
||||||
|
_goal_name = None
|
||||||
|
|
||||||
|
def _get_goal(self, value):
|
||||||
|
if value == wtypes.Unset:
|
||||||
|
return None
|
||||||
|
goal = None
|
||||||
|
try:
|
||||||
|
if (common_utils.is_uuid_like(value) or
|
||||||
|
common_utils.is_int_like(value)):
|
||||||
|
goal = objects.Goal.get(pecan.request.context, value)
|
||||||
|
else:
|
||||||
|
goal = objects.Goal.get_by_name(pecan.request.context, value)
|
||||||
|
except exception.GoalNotFound:
|
||||||
|
pass
|
||||||
|
if goal:
|
||||||
|
self.goal_id = goal.id
|
||||||
|
return goal
|
||||||
|
|
||||||
|
def _get_goal_uuid(self):
|
||||||
|
return self._goal_uuid
|
||||||
|
|
||||||
|
def _set_goal_uuid(self, value):
|
||||||
|
if value and self._goal_uuid != value:
|
||||||
|
self._goal_uuid = None
|
||||||
|
goal = self._get_goal(value)
|
||||||
|
if goal:
|
||||||
|
self._goal_uuid = goal.uuid
|
||||||
|
|
||||||
|
def _get_goal_name(self):
|
||||||
|
return self._goal_name
|
||||||
|
|
||||||
|
def _set_goal_name(self, value):
|
||||||
|
if value and self._goal_name != value:
|
||||||
|
self._goal_name = None
|
||||||
|
goal = self._get_goal(value)
|
||||||
|
if goal:
|
||||||
|
self._goal_name = goal.name
|
||||||
|
|
||||||
|
uuid = types.uuid
|
||||||
|
"""Unique UUID for this strategy"""
|
||||||
|
|
||||||
|
name = wtypes.text
|
||||||
|
"""Name of the strategy"""
|
||||||
|
|
||||||
|
display_name = wtypes.text
|
||||||
|
"""Localized name of the strategy"""
|
||||||
|
|
||||||
|
links = wsme.wsattr([link.Link], readonly=True)
|
||||||
|
"""A list containing a self link and associated goal links"""
|
||||||
|
|
||||||
|
goal_uuid = wsme.wsproperty(wtypes.text, _get_goal_uuid, _set_goal_uuid,
|
||||||
|
mandatory=True)
|
||||||
|
"""The UUID of the goal this audit refers to"""
|
||||||
|
|
||||||
|
goal_name = wsme.wsproperty(wtypes.text, _get_goal_name, _set_goal_name,
|
||||||
|
mandatory=False)
|
||||||
|
"""The name of the goal this audit refers to"""
|
||||||
|
|
||||||
|
parameters_spec = {wtypes.text: types.jsontype}
|
||||||
|
""" Parameters spec dict"""
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super(Strategy, self).__init__()
|
||||||
|
|
||||||
|
self.fields = []
|
||||||
|
self.fields.append('uuid')
|
||||||
|
self.fields.append('name')
|
||||||
|
self.fields.append('display_name')
|
||||||
|
self.fields.append('goal_uuid')
|
||||||
|
self.fields.append('goal_name')
|
||||||
|
self.fields.append('parameters_spec')
|
||||||
|
setattr(self, 'uuid', kwargs.get('uuid', wtypes.Unset))
|
||||||
|
setattr(self, 'name', kwargs.get('name', wtypes.Unset))
|
||||||
|
setattr(self, 'display_name', kwargs.get('display_name', wtypes.Unset))
|
||||||
|
setattr(self, 'goal_uuid', kwargs.get('goal_id', wtypes.Unset))
|
||||||
|
setattr(self, 'goal_name', kwargs.get('goal_id', wtypes.Unset))
|
||||||
|
setattr(self, 'parameters_spec', kwargs.get('parameters_spec',
|
||||||
|
wtypes.Unset))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _convert_with_links(strategy, url, expand=True):
|
||||||
|
if not expand:
|
||||||
|
strategy.unset_fields_except(
|
||||||
|
['uuid', 'name', 'display_name', 'goal_uuid', 'goal_name'])
|
||||||
|
|
||||||
|
strategy.links = [
|
||||||
|
link.Link.make_link('self', url, 'strategies', strategy.uuid),
|
||||||
|
link.Link.make_link('bookmark', url, 'strategies', strategy.uuid,
|
||||||
|
bookmark=True)]
|
||||||
|
return strategy
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def convert_with_links(cls, strategy, expand=True):
|
||||||
|
strategy = Strategy(**strategy.as_dict())
|
||||||
|
return cls._convert_with_links(
|
||||||
|
strategy, pecan.request.host_url, expand)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def sample(cls, expand=True):
|
||||||
|
sample = cls(uuid='27e3153e-d5bf-4b7e-b517-fb518e17f34c',
|
||||||
|
name='DUMMY',
|
||||||
|
display_name='Dummy strategy')
|
||||||
|
return cls._convert_with_links(sample, 'http://localhost:9322', expand)
|
||||||
|
|
||||||
|
|
||||||
|
class StrategyCollection(collection.Collection):
|
||||||
|
"""API representation of a collection of strategies."""
|
||||||
|
|
||||||
|
strategies = [Strategy]
|
||||||
|
"""A list containing strategies objects"""
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super(StrategyCollection, self).__init__()
|
||||||
|
self._type = 'strategies'
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def convert_with_links(strategies, limit, url=None, expand=False,
|
||||||
|
**kwargs):
|
||||||
|
strategy_collection = StrategyCollection()
|
||||||
|
strategy_collection.strategies = [
|
||||||
|
Strategy.convert_with_links(g, expand) for g in strategies]
|
||||||
|
|
||||||
|
if 'sort_key' in kwargs:
|
||||||
|
reverse = False
|
||||||
|
if kwargs['sort_key'] == 'strategy':
|
||||||
|
if 'sort_dir' in kwargs:
|
||||||
|
reverse = True if kwargs['sort_dir'] == 'desc' else False
|
||||||
|
strategy_collection.strategies = sorted(
|
||||||
|
strategy_collection.strategies,
|
||||||
|
key=lambda strategy: strategy.uuid,
|
||||||
|
reverse=reverse)
|
||||||
|
|
||||||
|
strategy_collection.next = strategy_collection.get_next(
|
||||||
|
limit, url=url, **kwargs)
|
||||||
|
return strategy_collection
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def sample(cls):
|
||||||
|
sample = cls()
|
||||||
|
sample.strategies = [Strategy.sample(expand=False)]
|
||||||
|
return sample
|
||||||
|
|
||||||
|
|
||||||
|
class StrategiesController(rest.RestController):
|
||||||
|
"""REST controller for Strategies."""
|
||||||
|
def __init__(self):
|
||||||
|
super(StrategiesController, self).__init__()
|
||||||
|
|
||||||
|
from_strategies = False
|
||||||
|
"""A flag to indicate if the requests to this controller are coming
|
||||||
|
from the top-level resource Strategies."""
|
||||||
|
|
||||||
|
_custom_actions = {
|
||||||
|
'detail': ['GET'],
|
||||||
|
}
|
||||||
|
|
||||||
|
def _get_strategies_collection(self, filters, marker, limit, sort_key,
|
||||||
|
sort_dir, expand=False, resource_url=None):
|
||||||
|
api_utils.validate_search_filters(
|
||||||
|
filters, list(objects.strategy.Strategy.fields.keys()) +
|
||||||
|
["goal_uuid", "goal_name"])
|
||||||
|
limit = api_utils.validate_limit(limit)
|
||||||
|
api_utils.validate_sort_dir(sort_dir)
|
||||||
|
|
||||||
|
sort_db_key = (sort_key if sort_key in objects.Strategy.fields.keys()
|
||||||
|
else None)
|
||||||
|
|
||||||
|
marker_obj = None
|
||||||
|
if marker:
|
||||||
|
marker_obj = objects.Strategy.get_by_uuid(
|
||||||
|
pecan.request.context, marker)
|
||||||
|
|
||||||
|
strategies = objects.Strategy.list(
|
||||||
|
pecan.request.context, limit, marker_obj, filters=filters,
|
||||||
|
sort_key=sort_db_key, sort_dir=sort_dir)
|
||||||
|
|
||||||
|
return StrategyCollection.convert_with_links(
|
||||||
|
strategies, limit, url=resource_url, expand=expand,
|
||||||
|
sort_key=sort_key, sort_dir=sort_dir)
|
||||||
|
|
||||||
|
@wsme_pecan.wsexpose(StrategyCollection, wtypes.text, wtypes.text,
|
||||||
|
int, wtypes.text, wtypes.text)
|
||||||
|
def get_all(self, goal=None, marker=None, limit=None,
|
||||||
|
sort_key='id', sort_dir='asc'):
|
||||||
|
"""Retrieve a list of strategies.
|
||||||
|
|
||||||
|
:param goal: goal UUID or name to filter by.
|
||||||
|
:param marker: pagination marker for large data sets.
|
||||||
|
:param limit: maximum number of resources to return in a single result.
|
||||||
|
:param sort_key: column to sort results by. Default: id.
|
||||||
|
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
|
||||||
|
"""
|
||||||
|
context = pecan.request.context
|
||||||
|
policy.enforce(context, 'strategy:get_all',
|
||||||
|
action='strategy:get_all')
|
||||||
|
filters = {}
|
||||||
|
if goal:
|
||||||
|
if common_utils.is_uuid_like(goal):
|
||||||
|
filters['goal_uuid'] = goal
|
||||||
|
else:
|
||||||
|
filters['goal_name'] = goal
|
||||||
|
|
||||||
|
return self._get_strategies_collection(
|
||||||
|
filters, marker, limit, sort_key, sort_dir)
|
||||||
|
|
||||||
|
@wsme_pecan.wsexpose(StrategyCollection, wtypes.text, wtypes.text, int,
|
||||||
|
wtypes.text, wtypes.text)
|
||||||
|
def detail(self, goal=None, marker=None, limit=None,
|
||||||
|
sort_key='id', sort_dir='asc'):
|
||||||
|
"""Retrieve a list of strategies with detail.
|
||||||
|
|
||||||
|
:param goal: goal UUID or name to filter by.
|
||||||
|
:param marker: pagination marker for large data sets.
|
||||||
|
:param limit: maximum number of resources to return in a single result.
|
||||||
|
:param sort_key: column to sort results by. Default: id.
|
||||||
|
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
|
||||||
|
"""
|
||||||
|
context = pecan.request.context
|
||||||
|
policy.enforce(context, 'strategy:detail',
|
||||||
|
action='strategy:detail')
|
||||||
|
# NOTE(lucasagomes): /detail should only work agaist collections
|
||||||
|
parent = pecan.request.path.split('/')[:-1][-1]
|
||||||
|
if parent != "strategies":
|
||||||
|
raise exception.HTTPNotFound
|
||||||
|
expand = True
|
||||||
|
resource_url = '/'.join(['strategies', 'detail'])
|
||||||
|
|
||||||
|
filters = {}
|
||||||
|
if goal:
|
||||||
|
if common_utils.is_uuid_like(goal):
|
||||||
|
filters['goal_uuid'] = goal
|
||||||
|
else:
|
||||||
|
filters['goal_name'] = goal
|
||||||
|
|
||||||
|
return self._get_strategies_collection(
|
||||||
|
filters, marker, limit, sort_key, sort_dir, expand, resource_url)
|
||||||
|
|
||||||
|
@wsme_pecan.wsexpose(Strategy, wtypes.text)
|
||||||
|
def get_one(self, strategy):
|
||||||
|
"""Retrieve information about the given strategy.
|
||||||
|
|
||||||
|
:param strategy: UUID or name of the strategy.
|
||||||
|
"""
|
||||||
|
if self.from_strategies:
|
||||||
|
raise exception.OperationNotPermitted
|
||||||
|
|
||||||
|
context = pecan.request.context
|
||||||
|
rpc_strategy = api_utils.get_resource('Strategy', strategy)
|
||||||
|
policy.enforce(context, 'strategy:get', rpc_strategy,
|
||||||
|
action='strategy:get')
|
||||||
|
|
||||||
|
return Strategy.convert_with_links(rpc_strategy)
|
||||||
@@ -31,11 +31,6 @@ class UuidOrNameType(wtypes.UserType):
|
|||||||
|
|
||||||
basetype = wtypes.text
|
basetype = wtypes.text
|
||||||
name = 'uuid_or_name'
|
name = 'uuid_or_name'
|
||||||
# FIXME(lucasagomes): When used with wsexpose decorator WSME will try
|
|
||||||
# to get the name of the type by accessing it's __name__ attribute.
|
|
||||||
# Remove this __name__ attribute once it's fixed in WSME.
|
|
||||||
# https://bugs.launchpad.net/wsme/+bug/1265590
|
|
||||||
__name__ = name
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def validate(value):
|
def validate(value):
|
||||||
@@ -55,11 +50,6 @@ class NameType(wtypes.UserType):
|
|||||||
|
|
||||||
basetype = wtypes.text
|
basetype = wtypes.text
|
||||||
name = 'name'
|
name = 'name'
|
||||||
# FIXME(lucasagomes): When used with wsexpose decorator WSME will try
|
|
||||||
# to get the name of the type by accessing it's __name__ attribute.
|
|
||||||
# Remove this __name__ attribute once it's fixed in WSME.
|
|
||||||
# https://bugs.launchpad.net/wsme/+bug/1265590
|
|
||||||
__name__ = name
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def validate(value):
|
def validate(value):
|
||||||
@@ -79,11 +69,6 @@ class UuidType(wtypes.UserType):
|
|||||||
|
|
||||||
basetype = wtypes.text
|
basetype = wtypes.text
|
||||||
name = 'uuid'
|
name = 'uuid'
|
||||||
# FIXME(lucasagomes): When used with wsexpose decorator WSME will try
|
|
||||||
# to get the name of the type by accessing it's __name__ attribute.
|
|
||||||
# Remove this __name__ attribute once it's fixed in WSME.
|
|
||||||
# https://bugs.launchpad.net/wsme/+bug/1265590
|
|
||||||
__name__ = name
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def validate(value):
|
def validate(value):
|
||||||
@@ -103,11 +88,6 @@ class BooleanType(wtypes.UserType):
|
|||||||
|
|
||||||
basetype = wtypes.text
|
basetype = wtypes.text
|
||||||
name = 'boolean'
|
name = 'boolean'
|
||||||
# FIXME(lucasagomes): When used with wsexpose decorator WSME will try
|
|
||||||
# to get the name of the type by accessing it's __name__ attribute.
|
|
||||||
# Remove this __name__ attribute once it's fixed in WSME.
|
|
||||||
# https://bugs.launchpad.net/wsme/+bug/1265590
|
|
||||||
__name__ = name
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def validate(value):
|
def validate(value):
|
||||||
@@ -129,11 +109,6 @@ class JsonType(wtypes.UserType):
|
|||||||
|
|
||||||
basetype = wtypes.text
|
basetype = wtypes.text
|
||||||
name = 'json'
|
name = 'json'
|
||||||
# FIXME(lucasagomes): When used with wsexpose decorator WSME will try
|
|
||||||
# to get the name of the type by accessing it's __name__ attribute.
|
|
||||||
# Remove this __name__ attribute once it's fixed in WSME.
|
|
||||||
# https://bugs.launchpad.net/wsme/+bug/1265590
|
|
||||||
__name__ = name
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
# These are the json serializable native types
|
# These are the json serializable native types
|
||||||
|
|||||||
@@ -15,9 +15,12 @@
|
|||||||
|
|
||||||
import jsonpatch
|
import jsonpatch
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
from oslo_utils import uuidutils
|
||||||
|
import pecan
|
||||||
import wsme
|
import wsme
|
||||||
|
|
||||||
from watcher._i18n import _
|
from watcher._i18n import _
|
||||||
|
from watcher import objects
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
|
|
||||||
@@ -75,3 +78,19 @@ def as_filters_dict(**filters):
|
|||||||
filters_dict[filter_name] = filter_value
|
filters_dict[filter_name] = filter_value
|
||||||
|
|
||||||
return filters_dict
|
return filters_dict
|
||||||
|
|
||||||
|
|
||||||
|
def get_resource(resource, resource_ident):
|
||||||
|
"""Get the resource from the uuid or logical name.
|
||||||
|
|
||||||
|
:param resource: the resource type.
|
||||||
|
:param resource_ident: the UUID or logical name of the resource.
|
||||||
|
|
||||||
|
:returns: The resource.
|
||||||
|
"""
|
||||||
|
resource = getattr(objects, resource)
|
||||||
|
|
||||||
|
if uuidutils.is_uuid_like(resource_ident):
|
||||||
|
return resource.get_by_uuid(pecan.request.context, resource_ident)
|
||||||
|
|
||||||
|
return resource.get_by_name(pecan.request.context, resource_ident)
|
||||||
|
|||||||
@@ -56,6 +56,8 @@ class ContextHook(hooks.PecanHook):
|
|||||||
auth_token = headers.get('X-Auth-Token', auth_token)
|
auth_token = headers.get('X-Auth-Token', auth_token)
|
||||||
show_deleted = headers.get('X-Show-Deleted')
|
show_deleted = headers.get('X-Show-Deleted')
|
||||||
auth_token_info = state.request.environ.get('keystone.token_info')
|
auth_token_info = state.request.environ.get('keystone.token_info')
|
||||||
|
roles = (headers.get('X-Roles', None) and
|
||||||
|
headers.get('X-Roles').split(','))
|
||||||
|
|
||||||
auth_url = headers.get('X-Auth-Url')
|
auth_url = headers.get('X-Auth-Url')
|
||||||
if auth_url is None:
|
if auth_url is None:
|
||||||
@@ -72,7 +74,8 @@ class ContextHook(hooks.PecanHook):
|
|||||||
project_id=project_id,
|
project_id=project_id,
|
||||||
domain_id=domain_id,
|
domain_id=domain_id,
|
||||||
domain_name=domain_name,
|
domain_name=domain_name,
|
||||||
show_deleted=show_deleted)
|
show_deleted=show_deleted,
|
||||||
|
roles=roles)
|
||||||
|
|
||||||
|
|
||||||
class NoExceptionTracebackHook(hooks.PecanHook):
|
class NoExceptionTracebackHook(hooks.PecanHook):
|
||||||
|
|||||||
@@ -53,16 +53,15 @@ class DefaultActionPlanHandler(base.BaseActionPlanHandler):
|
|||||||
event_types.EventTypes.LAUNCH_ACTION_PLAN,
|
event_types.EventTypes.LAUNCH_ACTION_PLAN,
|
||||||
ap_objects.State.ONGOING)
|
ap_objects.State.ONGOING)
|
||||||
applier = default.DefaultApplier(self.ctx, self.applier_manager)
|
applier = default.DefaultApplier(self.ctx, self.applier_manager)
|
||||||
result = applier.execute(self.action_plan_uuid)
|
applier.execute(self.action_plan_uuid)
|
||||||
|
state = ap_objects.State.SUCCEEDED
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.exception(e)
|
LOG.exception(e)
|
||||||
result = False
|
state = ap_objects.State.FAILED
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
if result is True:
|
|
||||||
status = ap_objects.State.SUCCEEDED
|
|
||||||
else:
|
|
||||||
status = ap_objects.State.FAILED
|
|
||||||
# update state
|
# update state
|
||||||
self.notify(self.action_plan_uuid,
|
self.notify(self.action_plan_uuid,
|
||||||
event_types.EventTypes.LAUNCH_ACTION_PLAN,
|
event_types.EventTypes.LAUNCH_ACTION_PLAN,
|
||||||
status)
|
state)
|
||||||
|
|||||||
@@ -23,18 +23,26 @@ import abc
|
|||||||
import six
|
import six
|
||||||
|
|
||||||
from watcher.common import clients
|
from watcher.common import clients
|
||||||
|
from watcher.common.loader import loadable
|
||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(abc.ABCMeta)
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
class BaseAction(object):
|
class BaseAction(loadable.Loadable):
|
||||||
# NOTE(jed) by convention we decided
|
# NOTE(jed) by convention we decided
|
||||||
# that the attribute "resource_id" is the unique id of
|
# that the attribute "resource_id" is the unique id of
|
||||||
# the resource to which the Action applies to allow us to use it in the
|
# the resource to which the Action applies to allow us to use it in the
|
||||||
# watcher dashboard and will be nested in input_parameters
|
# watcher dashboard and will be nested in input_parameters
|
||||||
RESOURCE_ID = 'resource_id'
|
RESOURCE_ID = 'resource_id'
|
||||||
|
|
||||||
def __init__(self, osc=None):
|
def __init__(self, config, osc=None):
|
||||||
""":param osc: an OpenStackClients instance"""
|
"""Constructor
|
||||||
|
|
||||||
|
:param config: A mapping containing the configuration of this action
|
||||||
|
:type config: dict
|
||||||
|
:param osc: an OpenStackClients instance, defaults to None
|
||||||
|
:type osc: :py:class:`~.OpenStackClients` instance, optional
|
||||||
|
"""
|
||||||
|
super(BaseAction, self).__init__(config)
|
||||||
self._input_parameters = {}
|
self._input_parameters = {}
|
||||||
self._osc = osc
|
self._osc = osc
|
||||||
|
|
||||||
@@ -56,6 +64,15 @@ class BaseAction(object):
|
|||||||
def resource_id(self):
|
def resource_id(self):
|
||||||
return self.input_parameters[self.RESOURCE_ID]
|
return self.input_parameters[self.RESOURCE_ID]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_config_opts(cls):
|
||||||
|
"""Defines the configuration options to be associated to this loadable
|
||||||
|
|
||||||
|
:return: A list of configuration options relative to this Loadable
|
||||||
|
:rtype: list of :class:`oslo_config.cfg.Opt` instances
|
||||||
|
"""
|
||||||
|
return []
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def execute(self):
|
def execute(self):
|
||||||
"""Executes the main logic of the action
|
"""Executes the main logic of the action
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
|
|
||||||
from watcher.applier.actions.loading import default
|
from watcher.applier.loading import default
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
|||||||
@@ -31,27 +31,24 @@ LOG = log.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class Migrate(base.BaseAction):
|
class Migrate(base.BaseAction):
|
||||||
"""Live-Migrates a server to a destination nova-compute host
|
"""Migrates a server to a destination nova-compute host
|
||||||
|
|
||||||
This action will allow you to migrate a server to another compute
|
This action will allow you to migrate a server to another compute
|
||||||
destination host. As of now, only live migration can be performed using
|
destination host.
|
||||||
this action.
|
Migration type 'live' can only be used for migrating active VMs.
|
||||||
.. If either host uses shared storage, you can use ``live``
|
Migration type 'cold' can be used for migrating non-active VMs
|
||||||
.. as ``migration_type``. If both source and destination hosts provide
|
as well active VMs, which will be shut down while migrating.
|
||||||
.. local disks, you can set the block_migration parameter to True (not
|
|
||||||
.. supported for yet).
|
|
||||||
|
|
||||||
The action schema is::
|
The action schema is::
|
||||||
|
|
||||||
schema = Schema({
|
schema = Schema({
|
||||||
'resource_id': str, # should be a UUID
|
'resource_id': str, # should be a UUID
|
||||||
'migration_type': str, # choices -> "live" only
|
'migration_type': str, # choices -> "live", "cold"
|
||||||
'dst_hypervisor': str,
|
'dst_hypervisor': str,
|
||||||
'src_hypervisor': str,
|
'src_hypervisor': str,
|
||||||
})
|
})
|
||||||
|
|
||||||
The `resource_id` is the UUID of the server to migrate. Only live migration
|
The `resource_id` is the UUID of the server to migrate.
|
||||||
is supported.
|
|
||||||
The `src_hypervisor` and `dst_hypervisor` parameters are respectively the
|
The `src_hypervisor` and `dst_hypervisor` parameters are respectively the
|
||||||
source and the destination compute hostname (list of available compute
|
source and the destination compute hostname (list of available compute
|
||||||
hosts is returned by this command: ``nova service-list --binary
|
hosts is returned by this command: ``nova service-list --binary
|
||||||
@@ -61,6 +58,7 @@ class Migrate(base.BaseAction):
|
|||||||
# input parameters constants
|
# input parameters constants
|
||||||
MIGRATION_TYPE = 'migration_type'
|
MIGRATION_TYPE = 'migration_type'
|
||||||
LIVE_MIGRATION = 'live'
|
LIVE_MIGRATION = 'live'
|
||||||
|
COLD_MIGRATION = 'cold'
|
||||||
DST_HYPERVISOR = 'dst_hypervisor'
|
DST_HYPERVISOR = 'dst_hypervisor'
|
||||||
SRC_HYPERVISOR = 'src_hypervisor'
|
SRC_HYPERVISOR = 'src_hypervisor'
|
||||||
|
|
||||||
@@ -77,7 +75,8 @@ class Migrate(base.BaseAction):
|
|||||||
voluptuous.Required(self.RESOURCE_ID): self.check_resource_id,
|
voluptuous.Required(self.RESOURCE_ID): self.check_resource_id,
|
||||||
voluptuous.Required(self.MIGRATION_TYPE,
|
voluptuous.Required(self.MIGRATION_TYPE,
|
||||||
default=self.LIVE_MIGRATION):
|
default=self.LIVE_MIGRATION):
|
||||||
voluptuous.Any(*[self.LIVE_MIGRATION]),
|
voluptuous.Any(*[self.LIVE_MIGRATION,
|
||||||
|
self.COLD_MIGRATION]),
|
||||||
voluptuous.Required(self.DST_HYPERVISOR):
|
voluptuous.Required(self.DST_HYPERVISOR):
|
||||||
voluptuous.All(voluptuous.Any(*six.string_types),
|
voluptuous.All(voluptuous.Any(*six.string_types),
|
||||||
voluptuous.Length(min=1)),
|
voluptuous.Length(min=1)),
|
||||||
@@ -127,14 +126,30 @@ class Migrate(base.BaseAction):
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def _cold_migrate_instance(self, nova, destination):
|
||||||
|
result = None
|
||||||
|
try:
|
||||||
|
result = nova.watcher_non_live_migrate_instance(
|
||||||
|
instance_id=self.instance_uuid,
|
||||||
|
dest_hostname=destination)
|
||||||
|
except Exception as exc:
|
||||||
|
LOG.exception(exc)
|
||||||
|
LOG.critical(_LC("Unexpected error occured. Migration failed for"
|
||||||
|
"instance %s. Leaving instance on previous "
|
||||||
|
"host."), self.instance_uuid)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
def migrate(self, destination):
|
def migrate(self, destination):
|
||||||
nova = nova_helper.NovaHelper(osc=self.osc)
|
nova = nova_helper.NovaHelper(osc=self.osc)
|
||||||
LOG.debug("Migrate instance %s to %s", self.instance_uuid,
|
LOG.debug("Migrate instance %s to %s", self.instance_uuid,
|
||||||
destination)
|
destination)
|
||||||
instance = nova.find_instance(self.instance_uuid)
|
instance = nova.find_instance(self.instance_uuid)
|
||||||
if instance:
|
if instance:
|
||||||
if self.migration_type == 'live':
|
if self.migration_type == self.LIVE_MIGRATION:
|
||||||
return self._live_migrate_instance(nova, destination)
|
return self._live_migrate_instance(nova, destination)
|
||||||
|
elif self.migration_type == self.COLD_MIGRATION:
|
||||||
|
return self._cold_migrate_instance(nova, destination)
|
||||||
else:
|
else:
|
||||||
raise exception.Invalid(
|
raise exception.Invalid(
|
||||||
message=(_('Migration of type %(migration_type)s is not '
|
message=(_('Migration of type %(migration_type)s is not '
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ from oslo_config import cfg
|
|||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
|
|
||||||
from watcher.applier import base
|
from watcher.applier import base
|
||||||
from watcher.applier.workflow_engine.loading import default
|
from watcher.applier.loading import default
|
||||||
from watcher import objects
|
from watcher import objects
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
@@ -56,9 +56,7 @@ class DefaultApplier(base.BaseApplier):
|
|||||||
|
|
||||||
def execute(self, action_plan_uuid):
|
def execute(self, action_plan_uuid):
|
||||||
LOG.debug("Executing action plan %s ", action_plan_uuid)
|
LOG.debug("Executing action plan %s ", action_plan_uuid)
|
||||||
action_plan = objects.ActionPlan.get_by_uuid(self.context,
|
filters = {'action_plan_uuid': action_plan_uuid}
|
||||||
action_plan_uuid)
|
actions = objects.Action.list(self.context,
|
||||||
# todo(jed) remove direct access to dbapi need filter in object
|
filters=filters)
|
||||||
filters = {'action_plan_id': action_plan.id}
|
|
||||||
actions = objects.Action.dbapi.get_action_list(self.context, filters)
|
|
||||||
return self.engine.execute(actions)
|
return self.engine.execute(actions)
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
# Copyright (c) 2015 b<>com
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
# You may obtain a copy of the License at
|
# You may obtain a copy of the License at
|
||||||
@@ -13,18 +10,20 @@
|
|||||||
# implied.
|
# implied.
|
||||||
# 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 __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from oslo_log import log
|
|
||||||
|
|
||||||
from watcher.common.loader import default
|
from watcher.common.loader import default
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class DefaultWorkFlowEngineLoader(default.DefaultLoader):
|
class DefaultWorkFlowEngineLoader(default.DefaultLoader):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(DefaultWorkFlowEngineLoader, self).__init__(
|
super(DefaultWorkFlowEngineLoader, self).__init__(
|
||||||
namespace='watcher_workflow_engines')
|
namespace='watcher_workflow_engines')
|
||||||
|
|
||||||
|
|
||||||
|
class DefaultActionLoader(default.DefaultLoader):
|
||||||
|
def __init__(self):
|
||||||
|
super(DefaultActionLoader, self).__init__(
|
||||||
|
namespace='watcher_actions')
|
||||||
@@ -21,7 +21,6 @@ from oslo_config import cfg
|
|||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
|
|
||||||
from watcher.applier.messaging import trigger
|
from watcher.applier.messaging import trigger
|
||||||
from watcher.common.messaging import messaging_core
|
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
@@ -63,17 +62,15 @@ CONF.register_group(opt_group)
|
|||||||
CONF.register_opts(APPLIER_MANAGER_OPTS, opt_group)
|
CONF.register_opts(APPLIER_MANAGER_OPTS, opt_group)
|
||||||
|
|
||||||
|
|
||||||
class ApplierManager(messaging_core.MessagingCore):
|
class ApplierManager(object):
|
||||||
def __init__(self):
|
|
||||||
super(ApplierManager, self).__init__(
|
|
||||||
CONF.watcher_applier.publisher_id,
|
|
||||||
CONF.watcher_applier.conductor_topic,
|
|
||||||
CONF.watcher_applier.status_topic,
|
|
||||||
api_version=self.API_VERSION,
|
|
||||||
)
|
|
||||||
self.conductor_topic_handler.add_endpoint(
|
|
||||||
trigger.TriggerActionPlan(self))
|
|
||||||
|
|
||||||
def join(self):
|
API_VERSION = '1.0'
|
||||||
self.conductor_topic_handler.join()
|
|
||||||
self.status_topic_handler.join()
|
conductor_endpoints = [trigger.TriggerActionPlan]
|
||||||
|
status_endpoints = []
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.publisher_id = CONF.watcher_applier.publisher_id
|
||||||
|
self.conductor_topic = CONF.watcher_applier.conductor_topic
|
||||||
|
self.status_topic = CONF.watcher_applier.status_topic
|
||||||
|
self.api_version = self.API_VERSION
|
||||||
|
|||||||
@@ -18,48 +18,43 @@
|
|||||||
#
|
#
|
||||||
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
|
|
||||||
|
|
||||||
from watcher.applier.manager import APPLIER_MANAGER_OPTS
|
from watcher.applier import manager
|
||||||
from watcher.applier.manager import opt_group
|
|
||||||
from watcher.common import exception
|
from watcher.common import exception
|
||||||
from watcher.common.messaging import messaging_core
|
|
||||||
from watcher.common.messaging import notification_handler as notification
|
from watcher.common.messaging import notification_handler as notification
|
||||||
|
from watcher.common import service
|
||||||
from watcher.common import utils
|
from watcher.common import utils
|
||||||
|
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
CONF.register_group(opt_group)
|
CONF.register_group(manager.opt_group)
|
||||||
CONF.register_opts(APPLIER_MANAGER_OPTS, opt_group)
|
CONF.register_opts(manager.APPLIER_MANAGER_OPTS, manager.opt_group)
|
||||||
|
|
||||||
|
|
||||||
class ApplierAPI(messaging_core.MessagingCore):
|
class ApplierAPI(service.Service):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(ApplierAPI, self).__init__(
|
super(ApplierAPI, self).__init__(ApplierAPIManager)
|
||||||
CONF.watcher_applier.publisher_id,
|
|
||||||
CONF.watcher_applier.conductor_topic,
|
|
||||||
CONF.watcher_applier.status_topic,
|
|
||||||
api_version=self.API_VERSION,
|
|
||||||
)
|
|
||||||
self.handler = notification.NotificationHandler(self.publisher_id)
|
|
||||||
self.handler.register_observer(self)
|
|
||||||
self.status_topic_handler.add_endpoint(self.handler)
|
|
||||||
transport = om.get_transport(CONF)
|
|
||||||
|
|
||||||
target = om.Target(
|
|
||||||
topic=CONF.watcher_applier.conductor_topic,
|
|
||||||
version=self.API_VERSION,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.client = om.RPCClient(transport, target,
|
|
||||||
serializer=self.serializer)
|
|
||||||
|
|
||||||
def launch_action_plan(self, context, action_plan_uuid=None):
|
def launch_action_plan(self, context, action_plan_uuid=None):
|
||||||
if not utils.is_uuid_like(action_plan_uuid):
|
if not utils.is_uuid_like(action_plan_uuid):
|
||||||
raise exception.InvalidUuidOrName(name=action_plan_uuid)
|
raise exception.InvalidUuidOrName(name=action_plan_uuid)
|
||||||
|
|
||||||
return self.client.call(
|
return self.conductor_client.call(
|
||||||
context.to_dict(), 'launch_action_plan',
|
context.to_dict(), 'launch_action_plan',
|
||||||
action_plan_uuid=action_plan_uuid)
|
action_plan_uuid=action_plan_uuid)
|
||||||
|
|
||||||
|
|
||||||
|
class ApplierAPIManager(object):
|
||||||
|
|
||||||
|
API_VERSION = '1.0'
|
||||||
|
|
||||||
|
conductor_endpoints = []
|
||||||
|
status_endpoints = [notification.NotificationHandler]
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.publisher_id = CONF.watcher_applier.publisher_id
|
||||||
|
self.conductor_topic = CONF.watcher_applier.conductor_topic
|
||||||
|
self.status_topic = CONF.watcher_applier.status_topic
|
||||||
|
self.api_version = self.API_VERSION
|
||||||
|
|||||||
@@ -23,18 +23,38 @@ import six
|
|||||||
from watcher.applier.actions import factory
|
from watcher.applier.actions import factory
|
||||||
from watcher.applier.messaging import event_types
|
from watcher.applier.messaging import event_types
|
||||||
from watcher.common import clients
|
from watcher.common import clients
|
||||||
|
from watcher.common.loader import loadable
|
||||||
from watcher.common.messaging.events import event
|
from watcher.common.messaging.events import event
|
||||||
from watcher import objects
|
from watcher import objects
|
||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(abc.ABCMeta)
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
class BaseWorkFlowEngine(object):
|
class BaseWorkFlowEngine(loadable.Loadable):
|
||||||
def __init__(self, context=None, applier_manager=None):
|
|
||||||
|
def __init__(self, config, context=None, applier_manager=None):
|
||||||
|
"""Constructor
|
||||||
|
|
||||||
|
:param config: A mapping containing the configuration of this
|
||||||
|
workflow engine
|
||||||
|
:type config: dict
|
||||||
|
:param osc: an OpenStackClients object, defaults to None
|
||||||
|
:type osc: :py:class:`~.OpenStackClients` instance, optional
|
||||||
|
"""
|
||||||
|
super(BaseWorkFlowEngine, self).__init__(config)
|
||||||
self._context = context
|
self._context = context
|
||||||
self._applier_manager = applier_manager
|
self._applier_manager = applier_manager
|
||||||
self._action_factory = factory.ActionFactory()
|
self._action_factory = factory.ActionFactory()
|
||||||
self._osc = None
|
self._osc = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_config_opts(cls):
|
||||||
|
"""Defines the configuration options to be associated to this loadable
|
||||||
|
|
||||||
|
:return: A list of configuration options relative to this Loadable
|
||||||
|
:rtype: list of :class:`oslo_config.cfg.Opt` instances
|
||||||
|
"""
|
||||||
|
return []
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def context(self):
|
def context(self):
|
||||||
return self._context
|
return self._context
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ from taskflow import task
|
|||||||
|
|
||||||
from watcher._i18n import _LE, _LW, _LC
|
from watcher._i18n import _LE, _LW, _LC
|
||||||
from watcher.applier.workflow_engine import base
|
from watcher.applier.workflow_engine import base
|
||||||
|
from watcher.common import exception
|
||||||
from watcher.objects import action as obj_action
|
from watcher.objects import action as obj_action
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
@@ -77,10 +78,9 @@ class DefaultWorkFlowEngine(base.BaseWorkFlowEngine):
|
|||||||
|
|
||||||
e = engines.load(flow)
|
e = engines.load(flow)
|
||||||
e.run()
|
e.run()
|
||||||
return True
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.exception(e)
|
raise exception.WorkflowExecutionException(error=e)
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class TaskFlowActionContainer(task.Task):
|
class TaskFlowActionContainer(task.Task):
|
||||||
@@ -121,14 +121,9 @@ class TaskFlowActionContainer(task.Task):
|
|||||||
try:
|
try:
|
||||||
LOG.debug("Running action %s", self.name)
|
LOG.debug("Running action %s", self.name)
|
||||||
|
|
||||||
# todo(jed) remove return (true or false) raise an Exception
|
self.action.execute()
|
||||||
result = self.action.execute()
|
self.engine.notify(self._db_action,
|
||||||
if result is not True:
|
obj_action.State.SUCCEEDED)
|
||||||
self.engine.notify(self._db_action,
|
|
||||||
obj_action.State.FAILED)
|
|
||||||
else:
|
|
||||||
self.engine.notify(self._db_action,
|
|
||||||
obj_action.State.SUCCEEDED)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.exception(e)
|
LOG.exception(e)
|
||||||
LOG.error(_LE('The WorkFlow Engine has failed '
|
LOG.error(_LE('The WorkFlow Engine has failed '
|
||||||
|
|||||||
@@ -17,19 +17,14 @@
|
|||||||
|
|
||||||
"""Starter script for the Watcher API service."""
|
"""Starter script for the Watcher API service."""
|
||||||
|
|
||||||
import logging as std_logging
|
|
||||||
import os
|
|
||||||
import sys
|
import sys
|
||||||
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._i18n import _LI
|
||||||
from watcher.api import app as api_app
|
|
||||||
from watcher.common import service
|
from watcher.common import service
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
|
|
||||||
@@ -37,22 +32,20 @@ CONF = cfg.CONF
|
|||||||
def main():
|
def main():
|
||||||
service.prepare_service(sys.argv)
|
service.prepare_service(sys.argv)
|
||||||
|
|
||||||
app = api_app.setup_app()
|
|
||||||
|
|
||||||
# Create the WSGI server and start it
|
|
||||||
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)
|
protocol = "http" if not CONF.api.enable_ssl_api else "https"
|
||||||
|
# Build and start the WSGI app
|
||||||
|
server = service.WSGIService(
|
||||||
|
'watcher-api', CONF.api.enable_ssl_api)
|
||||||
|
|
||||||
LOG.info(_('Starting server in PID %s') % os.getpid())
|
if host == '127.0.0.1':
|
||||||
LOG.debug("Watcher configuration:")
|
LOG.info(_LI('serving on 127.0.0.1:%(port)s, '
|
||||||
cfg.CONF.log_opt_values(LOG, std_logging.DEBUG)
|
'view at %(protocol)s://127.0.0.1:%(port)s') %
|
||||||
|
dict(protocol=protocol, port=port))
|
||||||
if host == '0.0.0.0':
|
|
||||||
LOG.info(_('serving on 0.0.0.0:%(port)s, '
|
|
||||||
'view at http://127.0.0.1:%(port)s') %
|
|
||||||
dict(port=port))
|
|
||||||
else:
|
else:
|
||||||
LOG.info(_('serving on http://%(host)s:%(port)s') %
|
LOG.info(_LI('serving on %(protocol)s://%(host)s:%(port)s') %
|
||||||
dict(host=host, port=port))
|
dict(protocol=protocol, host=host, port=port))
|
||||||
|
|
||||||
srv.serve_forever()
|
launcher = service.process_launcher()
|
||||||
|
launcher.launch_service(server, workers=server.workers)
|
||||||
|
launcher.wait()
|
||||||
|
|||||||
@@ -17,29 +17,26 @@
|
|||||||
|
|
||||||
"""Starter script for the Applier service."""
|
"""Starter script for the Applier service."""
|
||||||
|
|
||||||
import logging as std_logging
|
|
||||||
import os
|
import os
|
||||||
import sys
|
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 oslo_service import service
|
||||||
|
|
||||||
from watcher import _i18n
|
from watcher._i18n import _LI
|
||||||
from watcher.applier import manager
|
from watcher.applier import manager
|
||||||
from watcher.common import service
|
from watcher.common import service as watcher_service
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
_LI = _i18n._LI
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
service.prepare_service(sys.argv)
|
watcher_service.prepare_service(sys.argv)
|
||||||
|
|
||||||
LOG.info(_LI('Starting server in PID %s') % os.getpid())
|
LOG.info(_LI('Starting Watcher Applier service in PID %s'), os.getpid())
|
||||||
LOG.debug("Configuration:")
|
|
||||||
cfg.CONF.log_opt_values(LOG, std_logging.DEBUG)
|
|
||||||
|
|
||||||
server = manager.ApplierManager()
|
applier_service = watcher_service.Service(manager.ApplierManager)
|
||||||
server.connect()
|
launcher = service.launch(CONF, applier_service)
|
||||||
server.join()
|
launcher.wait()
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ from oslo_config import cfg
|
|||||||
|
|
||||||
from watcher.common import service
|
from watcher.common import service
|
||||||
from watcher.db import migration
|
from watcher.db import migration
|
||||||
|
from watcher.db import purge
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
|
|
||||||
@@ -56,6 +56,12 @@ class DBCommand(object):
|
|||||||
def create_schema():
|
def create_schema():
|
||||||
migration.create_schema()
|
migration.create_schema()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def purge():
|
||||||
|
purge.purge(CONF.command.age_in_days, CONF.command.max_number,
|
||||||
|
CONF.command.audit_template, CONF.command.exclude_orphans,
|
||||||
|
CONF.command.dry_run)
|
||||||
|
|
||||||
|
|
||||||
def add_command_parsers(subparsers):
|
def add_command_parsers(subparsers):
|
||||||
parser = subparsers.add_parser(
|
parser = subparsers.add_parser(
|
||||||
@@ -96,6 +102,33 @@ def add_command_parsers(subparsers):
|
|||||||
help="Create the database schema.")
|
help="Create the database schema.")
|
||||||
parser.set_defaults(func=DBCommand.create_schema)
|
parser.set_defaults(func=DBCommand.create_schema)
|
||||||
|
|
||||||
|
parser = subparsers.add_parser(
|
||||||
|
'purge',
|
||||||
|
help="Purge the database.")
|
||||||
|
parser.add_argument('-d', '--age-in-days',
|
||||||
|
help="Number of days since deletion (from today) "
|
||||||
|
"to exclude from the purge. If None, everything "
|
||||||
|
"will be purged.",
|
||||||
|
type=int, default=None, nargs='?')
|
||||||
|
parser.add_argument('-n', '--max-number',
|
||||||
|
help="Max number of objects expected to be deleted. "
|
||||||
|
"Prevents the deletion if exceeded. No limit if "
|
||||||
|
"set to None.",
|
||||||
|
type=int, default=None, nargs='?')
|
||||||
|
parser.add_argument('-t', '--audit-template',
|
||||||
|
help="UUID or name of the audit template to purge.",
|
||||||
|
type=str, default=None, nargs='?')
|
||||||
|
parser.add_argument('-e', '--exclude-orphans', action='store_true',
|
||||||
|
help="Flag to indicate whether or not you want to "
|
||||||
|
"exclude orphans from deletion (default: False).",
|
||||||
|
default=False)
|
||||||
|
parser.add_argument('--dry-run', action='store_true',
|
||||||
|
help="Flag to indicate whether or not you want to "
|
||||||
|
"perform a dry run (no deletion).",
|
||||||
|
default=False)
|
||||||
|
|
||||||
|
parser.set_defaults(func=DBCommand.purge)
|
||||||
|
|
||||||
|
|
||||||
command_opt = cfg.SubCommandOpt('command',
|
command_opt = cfg.SubCommandOpt('command',
|
||||||
title='Command',
|
title='Command',
|
||||||
@@ -114,6 +147,7 @@ def main():
|
|||||||
valid_commands = set([
|
valid_commands = set([
|
||||||
'upgrade', 'downgrade', 'revision',
|
'upgrade', 'downgrade', 'revision',
|
||||||
'version', 'stamp', 'create_schema',
|
'version', 'stamp', 'create_schema',
|
||||||
|
'purge',
|
||||||
])
|
])
|
||||||
if not set(sys.argv).intersection(valid_commands):
|
if not set(sys.argv).intersection(valid_commands):
|
||||||
sys.argv.append('upgrade')
|
sys.argv.append('upgrade')
|
||||||
|
|||||||
@@ -17,30 +17,31 @@
|
|||||||
|
|
||||||
"""Starter script for the Decision Engine manager service."""
|
"""Starter script for the Decision Engine manager service."""
|
||||||
|
|
||||||
import logging as std_logging
|
|
||||||
import os
|
import os
|
||||||
import sys
|
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 oslo_service import service
|
||||||
|
|
||||||
from watcher import _i18n
|
from watcher._i18n import _LI
|
||||||
from watcher.common import service
|
from watcher.common import service as watcher_service
|
||||||
from watcher.decision_engine import manager
|
from watcher.decision_engine import manager
|
||||||
|
from watcher.decision_engine import sync
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
_LI = _i18n._LI
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
service.prepare_service(sys.argv)
|
watcher_service.prepare_service(sys.argv)
|
||||||
|
|
||||||
LOG.info(_LI('Starting server in PID %s') % os.getpid())
|
LOG.info(_LI('Starting Watcher Decision Engine service in PID %s'),
|
||||||
LOG.debug("Configuration:")
|
os.getpid())
|
||||||
cfg.CONF.log_opt_values(LOG, std_logging.DEBUG)
|
|
||||||
|
|
||||||
server = manager.DecisionEngineManager()
|
syncer = sync.Syncer()
|
||||||
server.connect()
|
syncer.sync()
|
||||||
server.join()
|
|
||||||
|
de_service = watcher_service.Service(manager.DecisionEngineManager)
|
||||||
|
launcher = service.launch(CONF, de_service)
|
||||||
|
launcher.wait()
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
|
||||||
from ceilometerclient.exc import HTTPUnauthorized
|
from ceilometerclient import exc
|
||||||
|
|
||||||
from watcher.common import clients
|
from watcher.common import clients
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ class CeilometerHelper(object):
|
|||||||
def query_retry(self, f, *args, **kargs):
|
def query_retry(self, f, *args, **kargs):
|
||||||
try:
|
try:
|
||||||
return f(*args, **kargs)
|
return f(*args, **kargs)
|
||||||
except HTTPUnauthorized:
|
except exc.HTTPUnauthorized:
|
||||||
self.osc.reset_clients()
|
self.osc.reset_clients()
|
||||||
self.ceilometer = self.osc.ceilometer()
|
self.ceilometer = self.osc.ceilometer()
|
||||||
return f(*args, **kargs)
|
return f(*args, **kargs)
|
||||||
@@ -123,7 +123,7 @@ class CeilometerHelper(object):
|
|||||||
|
|
||||||
item_value = None
|
item_value = None
|
||||||
if statistic:
|
if statistic:
|
||||||
item_value = statistic[-1]._info.get('aggregate').get('avg')
|
item_value = statistic[-1]._info.get('aggregate').get(aggregate)
|
||||||
return item_value
|
return item_value
|
||||||
|
|
||||||
def get_last_sample_values(self, resource_id, meter_name, limit=1):
|
def get_last_sample_values(self, resource_id, meter_name, limit=1):
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ from watcher import version
|
|||||||
|
|
||||||
|
|
||||||
def parse_args(argv, default_config_files=None):
|
def parse_args(argv, default_config_files=None):
|
||||||
|
default_config_files = (default_config_files or
|
||||||
|
cfg.find_config_files(project='watcher'))
|
||||||
rpc.set_defaults(control_exchange='watcher')
|
rpc.set_defaults(control_exchange='watcher')
|
||||||
cfg.CONF(argv[1:],
|
cfg.CONF(argv[1:],
|
||||||
project='python-watcher',
|
project='python-watcher',
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class RequestContext(context.RequestContext):
|
|||||||
domain_name=None, user=None, user_id=None, project=None,
|
domain_name=None, user=None, user_id=None, project=None,
|
||||||
project_id=None, is_admin=False, is_public_api=False,
|
project_id=None, is_admin=False, is_public_api=False,
|
||||||
read_only=False, show_deleted=False, request_id=None,
|
read_only=False, show_deleted=False, request_id=None,
|
||||||
trust_id=None, auth_token_info=None):
|
trust_id=None, auth_token_info=None, roles=None):
|
||||||
"""Stores several additional request parameters:
|
"""Stores several additional request parameters:
|
||||||
|
|
||||||
:param domain_id: The ID of the domain.
|
:param domain_id: The ID of the domain.
|
||||||
@@ -44,7 +44,8 @@ class RequestContext(context.RequestContext):
|
|||||||
is_admin=is_admin,
|
is_admin=is_admin,
|
||||||
read_only=read_only,
|
read_only=read_only,
|
||||||
show_deleted=show_deleted,
|
show_deleted=show_deleted,
|
||||||
request_id=request_id)
|
request_id=request_id,
|
||||||
|
roles=roles)
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
return {'auth_token': self.auth_token,
|
return {'auth_token': self.auth_token,
|
||||||
@@ -61,7 +62,8 @@ class RequestContext(context.RequestContext):
|
|||||||
'show_deleted': self.show_deleted,
|
'show_deleted': self.show_deleted,
|
||||||
'request_id': self.request_id,
|
'request_id': self.request_id,
|
||||||
'trust_id': self.trust_id,
|
'trust_id': self.trust_id,
|
||||||
'auth_token_info': self.auth_token_info}
|
'auth_token_info': self.auth_token_info,
|
||||||
|
'roles': self.roles}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, values):
|
def from_dict(cls, values):
|
||||||
|
|||||||
@@ -123,6 +123,10 @@ class NotAuthorized(WatcherException):
|
|||||||
code = 403
|
code = 403
|
||||||
|
|
||||||
|
|
||||||
|
class PolicyNotAuthorized(NotAuthorized):
|
||||||
|
msg_fmt = _("Policy doesn't allow %(action)s to be performed.")
|
||||||
|
|
||||||
|
|
||||||
class OperationNotPermitted(NotAuthorized):
|
class OperationNotPermitted(NotAuthorized):
|
||||||
msg_fmt = _("Operation not permitted")
|
msg_fmt = _("Operation not permitted")
|
||||||
|
|
||||||
@@ -147,11 +151,20 @@ class ResourceNotFound(ObjectNotFound):
|
|||||||
|
|
||||||
|
|
||||||
class InvalidIdentity(Invalid):
|
class InvalidIdentity(Invalid):
|
||||||
msg_fmt = _("Expected an uuid or int but received %(identity)s")
|
msg_fmt = _("Expected a uuid or int but received %(identity)s")
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidOperator(Invalid):
|
||||||
|
msg_fmt = _("Filter operator is not valid: %(operator)s not "
|
||||||
|
"in %(valid_operators)s")
|
||||||
|
|
||||||
|
|
||||||
class InvalidGoal(Invalid):
|
class InvalidGoal(Invalid):
|
||||||
msg_fmt = _("Goal %(goal)s is not defined in Watcher configuration file")
|
msg_fmt = _("Goal %(goal)s is invalid")
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidStrategy(Invalid):
|
||||||
|
msg_fmt = _("Strategy %(strategy)s is invalid")
|
||||||
|
|
||||||
|
|
||||||
class InvalidUUID(Invalid):
|
class InvalidUUID(Invalid):
|
||||||
@@ -166,12 +179,28 @@ class InvalidUuidOrName(Invalid):
|
|||||||
msg_fmt = _("Expected a logical name or uuid but received %(name)s")
|
msg_fmt = _("Expected a logical name or uuid but received %(name)s")
|
||||||
|
|
||||||
|
|
||||||
|
class GoalNotFound(ResourceNotFound):
|
||||||
|
msg_fmt = _("Goal %(goal)s could not be found")
|
||||||
|
|
||||||
|
|
||||||
|
class GoalAlreadyExists(Conflict):
|
||||||
|
msg_fmt = _("A goal with UUID %(uuid)s already exists")
|
||||||
|
|
||||||
|
|
||||||
|
class StrategyNotFound(ResourceNotFound):
|
||||||
|
msg_fmt = _("Strategy %(strategy)s could not be found")
|
||||||
|
|
||||||
|
|
||||||
|
class StrategyAlreadyExists(Conflict):
|
||||||
|
msg_fmt = _("A strategy with UUID %(uuid)s already exists")
|
||||||
|
|
||||||
|
|
||||||
class AuditTemplateNotFound(ResourceNotFound):
|
class AuditTemplateNotFound(ResourceNotFound):
|
||||||
msg_fmt = _("AuditTemplate %(audit_template)s could not be found")
|
msg_fmt = _("AuditTemplate %(audit_template)s could not be found")
|
||||||
|
|
||||||
|
|
||||||
class AuditTemplateAlreadyExists(Conflict):
|
class AuditTemplateAlreadyExists(Conflict):
|
||||||
msg_fmt = _("An audit_template with UUID %(uuid)s or name %(name)s "
|
msg_fmt = _("An audit_template with UUID or name %(audit_template)s "
|
||||||
"already exists")
|
"already exists")
|
||||||
|
|
||||||
|
|
||||||
@@ -180,6 +209,10 @@ class AuditTemplateReferenced(Invalid):
|
|||||||
"multiple audit")
|
"multiple audit")
|
||||||
|
|
||||||
|
|
||||||
|
class AuditTypeNotFound(Invalid):
|
||||||
|
msg_fmt = _("Audit type %(audit_type)s could not be found")
|
||||||
|
|
||||||
|
|
||||||
class AuditNotFound(ResourceNotFound):
|
class AuditNotFound(ResourceNotFound):
|
||||||
msg_fmt = _("Audit %(audit)s could not be found")
|
msg_fmt = _("Audit %(audit)s could not be found")
|
||||||
|
|
||||||
@@ -188,6 +221,14 @@ class AuditAlreadyExists(Conflict):
|
|||||||
msg_fmt = _("An audit with UUID %(uuid)s already exists")
|
msg_fmt = _("An audit with UUID %(uuid)s already exists")
|
||||||
|
|
||||||
|
|
||||||
|
class AuditIntervalNotSpecified(Invalid):
|
||||||
|
msg_fmt = _("Interval of audit must be specified for %(audit_type)s.")
|
||||||
|
|
||||||
|
|
||||||
|
class AuditIntervalNotAllowed(Invalid):
|
||||||
|
msg_fmt = _("Interval of audit must not be set for %(audit_type)s.")
|
||||||
|
|
||||||
|
|
||||||
class AuditReferenced(Invalid):
|
class AuditReferenced(Invalid):
|
||||||
msg_fmt = _("Audit %(audit)s is referenced by one or multiple action "
|
msg_fmt = _("Audit %(audit)s is referenced by one or multiple action "
|
||||||
"plans")
|
"plans")
|
||||||
@@ -224,6 +265,14 @@ class ActionFilterCombinationProhibited(Invalid):
|
|||||||
"prohibited")
|
"prohibited")
|
||||||
|
|
||||||
|
|
||||||
|
class EfficacyIndicatorNotFound(ResourceNotFound):
|
||||||
|
msg_fmt = _("Efficacy indicator %(efficacy_indicator)s could not be found")
|
||||||
|
|
||||||
|
|
||||||
|
class EfficacyIndicatorAlreadyExists(Conflict):
|
||||||
|
msg_fmt = _("An action with UUID %(uuid)s already exists")
|
||||||
|
|
||||||
|
|
||||||
class HTTPNotFound(ResourceNotFound):
|
class HTTPNotFound(ResourceNotFound):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -234,6 +283,9 @@ class PatchError(Invalid):
|
|||||||
|
|
||||||
# decision engine
|
# decision engine
|
||||||
|
|
||||||
|
class WorkflowExecutionException(WatcherException):
|
||||||
|
msg_fmt = _('Workflow execution error: %(error)s')
|
||||||
|
|
||||||
|
|
||||||
class IllegalArgumentException(WatcherException):
|
class IllegalArgumentException(WatcherException):
|
||||||
msg_fmt = _('Illegal argument')
|
msg_fmt = _('Illegal argument')
|
||||||
@@ -264,7 +316,29 @@ class MetricCollectorNotDefined(WatcherException):
|
|||||||
|
|
||||||
|
|
||||||
class ClusterStateNotDefined(WatcherException):
|
class ClusterStateNotDefined(WatcherException):
|
||||||
msg_fmt = _("the cluster state is not defined")
|
msg_fmt = _("The cluster state is not defined")
|
||||||
|
|
||||||
|
|
||||||
|
class NoAvailableStrategyForGoal(WatcherException):
|
||||||
|
msg_fmt = _("No strategy could be found to achieve the '%(goal)s' goal.")
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidIndicatorValue(WatcherException):
|
||||||
|
msg_fmt = _("The indicator '%(name)s' with value '%(value)s' "
|
||||||
|
"and spec type '%(spec_type)s' is invalid.")
|
||||||
|
|
||||||
|
|
||||||
|
class GlobalEfficacyComputationError(WatcherException):
|
||||||
|
msg_fmt = _("Could not compute the global efficacy for the '%(goal)s' "
|
||||||
|
"goal using the '%(strategy)s' strategy.")
|
||||||
|
|
||||||
|
|
||||||
|
class NoMetricValuesForVM(WatcherException):
|
||||||
|
msg_fmt = _("No values returned by %(resource_id)s for %(metric_name)s.")
|
||||||
|
|
||||||
|
|
||||||
|
class NoSuchMetricForHost(WatcherException):
|
||||||
|
msg_fmt = _("No %(metric)s metric for %(host)s found.")
|
||||||
|
|
||||||
|
|
||||||
# Model
|
# Model
|
||||||
@@ -283,3 +357,11 @@ class LoadingError(WatcherException):
|
|||||||
|
|
||||||
class ReservedWord(WatcherException):
|
class ReservedWord(WatcherException):
|
||||||
msg_fmt = _("The identifier '%(name)s' is a reserved word")
|
msg_fmt = _("The identifier '%(name)s' is a reserved word")
|
||||||
|
|
||||||
|
|
||||||
|
class NotSoftDeletedStateError(WatcherException):
|
||||||
|
msg_fmt = _("The %(name)s resource %(id)s is not soft deleted")
|
||||||
|
|
||||||
|
|
||||||
|
class NegativeLimitError(WatcherException):
|
||||||
|
msg_fmt = _("Limit should be positive")
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
# -*- encoding: utf-8 -*-
|
||||||
# Copyright (c) 2015 b<>com
|
# Copyright (c) 2016 b<>com
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@@ -16,33 +16,81 @@
|
|||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
from stevedore.driver import DriverManager
|
from stevedore import driver as drivermanager
|
||||||
from stevedore import ExtensionManager
|
from stevedore import extension as extensionmanager
|
||||||
|
|
||||||
from watcher.common import exception
|
from watcher.common import exception
|
||||||
from watcher.common.loader.base import BaseLoader
|
from watcher.common.loader import base
|
||||||
|
from watcher.common import utils
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class DefaultLoader(BaseLoader):
|
class DefaultLoader(base.BaseLoader):
|
||||||
def __init__(self, namespace):
|
|
||||||
|
def __init__(self, namespace, conf=cfg.CONF):
|
||||||
|
"""Entry point loader for Watcher using Stevedore
|
||||||
|
|
||||||
|
:param namespace: namespace of the entry point(s) to load or list
|
||||||
|
:type namespace: str
|
||||||
|
:param conf: ConfigOpts instance, defaults to cfg.CONF
|
||||||
|
"""
|
||||||
super(DefaultLoader, self).__init__()
|
super(DefaultLoader, self).__init__()
|
||||||
self.namespace = namespace
|
self.namespace = namespace
|
||||||
|
self.conf = conf
|
||||||
|
|
||||||
def load(self, name, **kwargs):
|
def load(self, name, **kwargs):
|
||||||
try:
|
try:
|
||||||
LOG.debug("Loading in namespace %s => %s ", self.namespace, name)
|
LOG.debug("Loading in namespace %s => %s ", self.namespace, name)
|
||||||
driver_manager = DriverManager(namespace=self.namespace,
|
driver_manager = drivermanager.DriverManager(
|
||||||
name=name)
|
namespace=self.namespace,
|
||||||
loaded = driver_manager.driver
|
name=name,
|
||||||
|
invoke_on_load=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
driver_cls = driver_manager.driver
|
||||||
|
config = self._load_plugin_config(name, driver_cls)
|
||||||
|
|
||||||
|
driver = driver_cls(config, **kwargs)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
LOG.exception(exc)
|
LOG.exception(exc)
|
||||||
raise exception.LoadingError(name=name)
|
raise exception.LoadingError(name=name)
|
||||||
|
|
||||||
return loaded(**kwargs)
|
return driver
|
||||||
|
|
||||||
|
def _reload_config(self):
|
||||||
|
self.conf()
|
||||||
|
|
||||||
|
def get_entry_name(self, name):
|
||||||
|
return ".".join([self.namespace, name])
|
||||||
|
|
||||||
|
def _load_plugin_config(self, name, driver_cls):
|
||||||
|
"""Load the config of the plugin"""
|
||||||
|
config = utils.Struct()
|
||||||
|
config_opts = driver_cls.get_config_opts()
|
||||||
|
if not config_opts:
|
||||||
|
return config
|
||||||
|
|
||||||
|
group_name = self.get_entry_name(name)
|
||||||
|
self.conf.register_opts(config_opts, group=group_name)
|
||||||
|
|
||||||
|
# Finalise the opt import by re-checking the configuration
|
||||||
|
# against the provided config files
|
||||||
|
self._reload_config()
|
||||||
|
|
||||||
|
config_group = self.conf.get(group_name)
|
||||||
|
if not config_group:
|
||||||
|
raise exception.LoadingError(name=name)
|
||||||
|
|
||||||
|
config.update({
|
||||||
|
name: value for name, value in config_group.items()
|
||||||
|
})
|
||||||
|
|
||||||
|
return config
|
||||||
|
|
||||||
def list_available(self):
|
def list_available(self):
|
||||||
extension_manager = ExtensionManager(namespace=self.namespace)
|
extension_manager = extensionmanager.ExtensionManager(
|
||||||
|
namespace=self.namespace)
|
||||||
return {ext.name: ext.plugin for ext in extension_manager.extensions}
|
return {ext.name: ext.plugin for ext in extension_manager.extensions}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
# -*- encoding: utf-8 -*-
|
||||||
# Copyright (c) 2015 b<>com
|
# Copyright (c) 2016 b<>com
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@@ -13,16 +13,29 @@
|
|||||||
# implied.
|
# implied.
|
||||||
# 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.
|
||||||
|
|
||||||
import abc
|
import abc
|
||||||
|
|
||||||
import six
|
import six
|
||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(abc.ABCMeta)
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
class FakeLoadable(object):
|
class Loadable(object):
|
||||||
@classmethod
|
"""Generic interface for dynamically loading a driver/entry point.
|
||||||
def namespace(cls):
|
|
||||||
return "TESTING"
|
This defines the contract in order to let the loader manager inject
|
||||||
|
the configuration parameters during the loading.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, config):
|
||||||
|
self.config = config
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_name(cls):
|
@abc.abstractmethod
|
||||||
return 'fake'
|
def get_config_opts(cls):
|
||||||
|
"""Defines the configuration options to be associated to this loadable
|
||||||
|
|
||||||
|
:return: A list of configuration options relative to this Loadable
|
||||||
|
:rtype: list of :class:`oslo_config.cfg.Opt` instances
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
@@ -1,122 +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_config import cfg
|
|
||||||
from oslo_log import log
|
|
||||||
import oslo_messaging as om
|
|
||||||
|
|
||||||
from watcher.common.messaging.events import event_dispatcher as dispatcher
|
|
||||||
from watcher.common.messaging import messaging_handler
|
|
||||||
from watcher.common import rpc
|
|
||||||
|
|
||||||
from watcher.objects import base
|
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
|
||||||
CONF = cfg.CONF
|
|
||||||
|
|
||||||
|
|
||||||
class MessagingCore(dispatcher.EventDispatcher):
|
|
||||||
|
|
||||||
API_VERSION = '1.0'
|
|
||||||
|
|
||||||
def __init__(self, publisher_id, conductor_topic, status_topic,
|
|
||||||
api_version=API_VERSION):
|
|
||||||
super(MessagingCore, self).__init__()
|
|
||||||
self.serializer = rpc.RequestContextSerializer(
|
|
||||||
base.WatcherObjectSerializer())
|
|
||||||
self.publisher_id = publisher_id
|
|
||||||
self.api_version = api_version
|
|
||||||
|
|
||||||
self.conductor_topic = conductor_topic
|
|
||||||
self.status_topic = status_topic
|
|
||||||
self.conductor_topic_handler = self.build_topic_handler(
|
|
||||||
conductor_topic)
|
|
||||||
self.status_topic_handler = self.build_topic_handler(status_topic)
|
|
||||||
|
|
||||||
self._conductor_client = None
|
|
||||||
self._status_client = None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def conductor_client(self):
|
|
||||||
if self._conductor_client is None:
|
|
||||||
transport = om.get_transport(CONF)
|
|
||||||
target = om.Target(
|
|
||||||
topic=self.conductor_topic,
|
|
||||||
version=self.API_VERSION,
|
|
||||||
)
|
|
||||||
self._conductor_client = om.RPCClient(
|
|
||||||
transport, target, serializer=self.serializer)
|
|
||||||
return self._conductor_client
|
|
||||||
|
|
||||||
@conductor_client.setter
|
|
||||||
def conductor_client(self, c):
|
|
||||||
self.conductor_client = c
|
|
||||||
|
|
||||||
@property
|
|
||||||
def status_client(self):
|
|
||||||
if self._status_client is None:
|
|
||||||
transport = om.get_transport(CONF)
|
|
||||||
target = om.Target(
|
|
||||||
topic=self.status_topic,
|
|
||||||
version=self.API_VERSION,
|
|
||||||
)
|
|
||||||
self._status_client = om.RPCClient(
|
|
||||||
transport, target, serializer=self.serializer)
|
|
||||||
return self._status_client
|
|
||||||
|
|
||||||
@status_client.setter
|
|
||||||
def status_client(self, c):
|
|
||||||
self.status_client = c
|
|
||||||
|
|
||||||
def build_topic_handler(self, topic_name):
|
|
||||||
return messaging_handler.MessagingHandler(
|
|
||||||
self.publisher_id, topic_name, self,
|
|
||||||
self.api_version, self.serializer)
|
|
||||||
|
|
||||||
def connect(self):
|
|
||||||
LOG.debug("Connecting to '%s' (%s)",
|
|
||||||
CONF.transport_url, CONF.rpc_backend)
|
|
||||||
self.conductor_topic_handler.start()
|
|
||||||
self.status_topic_handler.start()
|
|
||||||
|
|
||||||
def disconnect(self):
|
|
||||||
LOG.debug("Disconnecting from '%s' (%s)",
|
|
||||||
CONF.transport_url, CONF.rpc_backend)
|
|
||||||
self.conductor_topic_handler.stop()
|
|
||||||
self.status_topic_handler.stop()
|
|
||||||
|
|
||||||
def publish_control(self, event, payload):
|
|
||||||
return self.conductor_topic_handler.publish_event(event, payload)
|
|
||||||
|
|
||||||
def publish_status(self, event, payload, request_id=None):
|
|
||||||
return self.status_topic_handler.publish_event(
|
|
||||||
event, payload, request_id)
|
|
||||||
|
|
||||||
def get_version(self):
|
|
||||||
return self.api_version
|
|
||||||
|
|
||||||
def check_api_version(self, context):
|
|
||||||
api_manager_version = self.conductor_client.call(
|
|
||||||
context.to_dict(), 'check_api_version',
|
|
||||||
api_version=self.api_version)
|
|
||||||
return api_manager_version
|
|
||||||
|
|
||||||
def response(self, evt, ctx, message):
|
|
||||||
payload = {
|
|
||||||
'request_id': ctx['request_id'],
|
|
||||||
'msg': message
|
|
||||||
}
|
|
||||||
self.publish_status(evt, payload)
|
|
||||||
@@ -38,7 +38,7 @@ CONF = cfg.CONF
|
|||||||
|
|
||||||
class MessagingHandler(threading.Thread):
|
class MessagingHandler(threading.Thread):
|
||||||
|
|
||||||
def __init__(self, publisher_id, topic_name, endpoint, version,
|
def __init__(self, publisher_id, topic_name, endpoints, version,
|
||||||
serializer=None):
|
serializer=None):
|
||||||
super(MessagingHandler, self).__init__()
|
super(MessagingHandler, self).__init__()
|
||||||
self.publisher_id = publisher_id
|
self.publisher_id = publisher_id
|
||||||
@@ -50,10 +50,10 @@ class MessagingHandler(threading.Thread):
|
|||||||
self.__server = None
|
self.__server = None
|
||||||
self.__notifier = None
|
self.__notifier = None
|
||||||
self.__transport = None
|
self.__transport = None
|
||||||
self.add_endpoint(endpoint)
|
self.add_endpoints(endpoints)
|
||||||
|
|
||||||
def add_endpoint(self, endpoint):
|
def add_endpoints(self, endpoints):
|
||||||
self.__endpoints.append(endpoint)
|
self.__endpoints.extend(endpoints)
|
||||||
|
|
||||||
def remove_endpoint(self, endpoint):
|
def remove_endpoint(self, endpoint):
|
||||||
if endpoint in self.__endpoints:
|
if endpoint in self.__endpoints:
|
||||||
|
|||||||
@@ -15,14 +15,12 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import eventlet
|
import eventlet
|
||||||
from oslo_log import log
|
|
||||||
import oslo_messaging as messaging
|
import oslo_messaging as messaging
|
||||||
|
|
||||||
from watcher.common.messaging.utils import observable
|
from watcher.common.messaging.utils import observable
|
||||||
|
|
||||||
|
|
||||||
eventlet.monkey_patch()
|
eventlet.monkey_patch()
|
||||||
LOG = log.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class NotificationHandler(observable.Observable):
|
class NotificationHandler(observable.Observable):
|
||||||
|
|||||||
@@ -87,7 +87,6 @@ class NovaHelper(object):
|
|||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
host_name = getattr(instance, "OS-EXT-SRV-ATTR:host")
|
host_name = getattr(instance, "OS-EXT-SRV-ATTR:host")
|
||||||
# https://bugs.launchpad.net/nova/+bug/1182965
|
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"Instance %s found on host '%s'." % (instance_id, host_name))
|
"Instance %s found on host '%s'." % (instance_id, host_name))
|
||||||
|
|
||||||
@@ -532,16 +531,12 @@ class NovaHelper(object):
|
|||||||
"Trying to create new instance '%s' "
|
"Trying to create new instance '%s' "
|
||||||
"from image '%s' with flavor '%s' ..." % (
|
"from image '%s' with flavor '%s' ..." % (
|
||||||
inst_name, image_id, flavor_name))
|
inst_name, image_id, flavor_name))
|
||||||
# TODO(jed) wait feature
|
|
||||||
# Allow admin users to view any keypair
|
try:
|
||||||
# https://bugs.launchpad.net/nova/+bug/1182965
|
self.nova.keypairs.findall(name=keypair_name)
|
||||||
if not self.nova.keypairs.findall(name=keypair_name):
|
except nvexceptions.NotFound:
|
||||||
LOG.debug("Key pair '%s' not found with user '%s'" % (
|
LOG.debug("Key pair '%s' not found " % keypair_name)
|
||||||
keypair_name, self.user))
|
|
||||||
return
|
return
|
||||||
else:
|
|
||||||
LOG.debug("Key pair '%s' found with user '%s'" % (
|
|
||||||
keypair_name, self.user))
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
image = self.nova.images.get(image_id)
|
image = self.nova.images.get(image_id)
|
||||||
|
|||||||
@@ -15,55 +15,80 @@
|
|||||||
|
|
||||||
"""Policy Engine For Watcher."""
|
"""Policy Engine For Watcher."""
|
||||||
|
|
||||||
from oslo_concurrency import lockutils
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
|
||||||
from oslo_policy import policy
|
from oslo_policy import policy
|
||||||
|
|
||||||
|
from watcher.common import exception
|
||||||
|
|
||||||
|
|
||||||
_ENFORCER = None
|
_ENFORCER = None
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
@lockutils.synchronized('policy_enforcer', 'watcher-')
|
# we can get a policy enforcer by this init.
|
||||||
def init_enforcer(policy_file=None, rules=None,
|
# oslo policy support change policy rule dynamically.
|
||||||
default_rule=None, use_conf=True):
|
# at present, policy.enforce will reload the policy rules when it checks
|
||||||
"""Synchronously initializes the policy enforcer
|
# the policy files have been touched.
|
||||||
|
def init(policy_file=None, rules=None,
|
||||||
:param policy_file: Custom policy file to use, if none is specified,
|
default_rule=None, use_conf=True, overwrite=True):
|
||||||
`CONF.policy_file` will be used.
|
"""Init an Enforcer class.
|
||||||
:param rules: Default dictionary / Rules to use. It will be
|
|
||||||
considered just in the first instantiation.
|
|
||||||
:param default_rule: Default rule to use, CONF.default_rule will
|
|
||||||
be used if none is specified.
|
|
||||||
:param use_conf: Whether to load rules from config file.
|
|
||||||
|
|
||||||
|
:param policy_file: Custom policy file to use, if none is
|
||||||
|
specified, ``conf.policy_file`` will be
|
||||||
|
used.
|
||||||
|
:param rules: Default dictionary / Rules to use. It will be
|
||||||
|
considered just in the first instantiation. If
|
||||||
|
:meth:`load_rules` with ``force_reload=True``,
|
||||||
|
:meth:`clear` or :meth:`set_rules` with
|
||||||
|
``overwrite=True`` is called this will be overwritten.
|
||||||
|
:param default_rule: Default rule to use, conf.default_rule will
|
||||||
|
be used if none is specified.
|
||||||
|
:param use_conf: Whether to load rules from cache or config file.
|
||||||
|
:param overwrite: Whether to overwrite existing rules when reload rules
|
||||||
|
from config file.
|
||||||
"""
|
"""
|
||||||
global _ENFORCER
|
global _ENFORCER
|
||||||
|
|
||||||
if _ENFORCER:
|
|
||||||
return
|
|
||||||
|
|
||||||
_ENFORCER = policy.Enforcer(policy_file=policy_file,
|
|
||||||
rules=rules,
|
|
||||||
default_rule=default_rule,
|
|
||||||
use_conf=use_conf)
|
|
||||||
|
|
||||||
|
|
||||||
def get_enforcer():
|
|
||||||
"""Provides access to the single instance of Policy enforcer."""
|
|
||||||
|
|
||||||
if not _ENFORCER:
|
if not _ENFORCER:
|
||||||
init_enforcer()
|
# http://docs.openstack.org/developer/oslo.policy/usage.html
|
||||||
|
_ENFORCER = policy.Enforcer(CONF,
|
||||||
|
policy_file=policy_file,
|
||||||
|
rules=rules,
|
||||||
|
default_rule=default_rule,
|
||||||
|
use_conf=use_conf,
|
||||||
|
overwrite=overwrite)
|
||||||
return _ENFORCER
|
return _ENFORCER
|
||||||
|
|
||||||
|
|
||||||
def enforce(rule, target, creds, do_raise=False, exc=None, *args, **kwargs):
|
def enforce(context, rule=None, target=None,
|
||||||
"""A shortcut for policy.Enforcer.enforce()
|
do_raise=True, exc=None, *args, **kwargs):
|
||||||
|
|
||||||
Checks authorization of a rule against the target and credentials.
|
"""Checks authorization of a rule against the target and credentials.
|
||||||
|
|
||||||
|
:param dict context: As much information about the user performing the
|
||||||
|
action as possible.
|
||||||
|
:param rule: The rule to evaluate.
|
||||||
|
:param dict target: As much information about the object being operated
|
||||||
|
on as possible.
|
||||||
|
:param do_raise: Whether to raise an exception or not if check
|
||||||
|
fails.
|
||||||
|
:param exc: Class of the exception to raise if the check fails.
|
||||||
|
Any remaining arguments passed to :meth:`enforce` (both
|
||||||
|
positional and keyword arguments) will be passed to
|
||||||
|
the exception class. If not specified,
|
||||||
|
:class:`PolicyNotAuthorized` will be used.
|
||||||
|
|
||||||
|
:return: ``False`` if the policy does not allow the action and `exc` is
|
||||||
|
not provided; otherwise, returns a value that evaluates to
|
||||||
|
``True``. Note: for rules using the "case" expression, this
|
||||||
|
``True`` value will be the specified string from the
|
||||||
|
expression.
|
||||||
"""
|
"""
|
||||||
enforcer = get_enforcer()
|
enforcer = init()
|
||||||
return enforcer.enforce(rule, target, creds, do_raise=do_raise,
|
credentials = context.to_dict()
|
||||||
exc=exc, *args, **kwargs)
|
if not exc:
|
||||||
|
exc = exception.PolicyNotAuthorized
|
||||||
|
if target is None:
|
||||||
|
target = {'project_id': context.project_id,
|
||||||
|
'user_id': context.user_id}
|
||||||
|
return enforcer.enforce(rule, target, credentials,
|
||||||
|
do_raise=do_raise, exc=exc, *args, **kwargs)
|
||||||
|
|||||||
@@ -15,108 +15,46 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import signal
|
|
||||||
import socket
|
import socket
|
||||||
|
|
||||||
|
from oslo_concurrency import processutils
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import _options
|
from oslo_log import _options
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
import oslo_messaging as messaging
|
import oslo_messaging as om
|
||||||
|
from oslo_reports import guru_meditation_report as gmr
|
||||||
|
from oslo_reports import opts as gmr_opts
|
||||||
from oslo_service import service
|
from oslo_service import service
|
||||||
from oslo_utils import importutils
|
from oslo_service import wsgi
|
||||||
|
|
||||||
from watcher._i18n import _LE
|
from watcher._i18n import _
|
||||||
from watcher._i18n import _LI
|
from watcher.api import app
|
||||||
from watcher.common import config
|
from watcher.common import config
|
||||||
from watcher.common import context
|
from watcher.common.messaging.events import event_dispatcher as dispatcher
|
||||||
|
from watcher.common.messaging import messaging_handler
|
||||||
from watcher.common import rpc
|
from watcher.common import rpc
|
||||||
from watcher.objects import base as objects_base
|
from watcher.objects import base
|
||||||
|
from watcher import opts
|
||||||
|
from watcher import version
|
||||||
|
|
||||||
service_opts = [
|
service_opts = [
|
||||||
cfg.IntOpt('periodic_interval',
|
cfg.IntOpt('periodic_interval',
|
||||||
default=60,
|
default=60,
|
||||||
help='Seconds between running periodic tasks.'),
|
help=_('Seconds between running periodic tasks.')),
|
||||||
cfg.StrOpt('host',
|
cfg.StrOpt('host',
|
||||||
default=socket.getfqdn(),
|
default=socket.getfqdn(),
|
||||||
help='Name of this node. This can be an opaque identifier. '
|
help=_('Name of this node. This can be an opaque identifier. '
|
||||||
'It is not necessarily a hostname, FQDN, or IP address. '
|
'It is not necessarily a hostname, FQDN, or IP address. '
|
||||||
'However, the node name must be valid within '
|
'However, the node name must be valid within '
|
||||||
'an AMQP key, and if using ZeroMQ, a valid '
|
'an AMQP key, and if using ZeroMQ, a valid '
|
||||||
'hostname, FQDN, or IP address.'),
|
'hostname, FQDN, or IP address.')),
|
||||||
]
|
]
|
||||||
|
|
||||||
cfg.CONF.register_opts(service_opts)
|
cfg.CONF.register_opts(service_opts)
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class RPCService(service.Service):
|
|
||||||
|
|
||||||
def __init__(self, host, manager_module, manager_class):
|
|
||||||
super(RPCService, self).__init__()
|
|
||||||
self.host = host
|
|
||||||
manager_module = importutils.try_import(manager_module)
|
|
||||||
manager_class = getattr(manager_module, manager_class)
|
|
||||||
self.manager = manager_class(host, manager_module.MANAGER_TOPIC)
|
|
||||||
self.topic = self.manager.topic
|
|
||||||
self.rpcserver = None
|
|
||||||
self.deregister = True
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
super(RPCService, self).start()
|
|
||||||
admin_context = context.RequestContext('admin', 'admin', is_admin=True)
|
|
||||||
|
|
||||||
target = messaging.Target(topic=self.topic, server=self.host)
|
|
||||||
endpoints = [self.manager]
|
|
||||||
serializer = objects_base.IronicObjectSerializer()
|
|
||||||
self.rpcserver = rpc.get_server(target, endpoints, serializer)
|
|
||||||
self.rpcserver.start()
|
|
||||||
|
|
||||||
self.handle_signal()
|
|
||||||
self.manager.init_host()
|
|
||||||
self.tg.add_dynamic_timer(
|
|
||||||
self.manager.periodic_tasks,
|
|
||||||
periodic_interval_max=cfg.CONF.periodic_interval,
|
|
||||||
context=admin_context)
|
|
||||||
|
|
||||||
LOG.info(_LI('Created RPC server for service %(service)s on host '
|
|
||||||
'%(host)s.'),
|
|
||||||
{'service': self.topic, 'host': self.host})
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
try:
|
|
||||||
self.rpcserver.stop()
|
|
||||||
self.rpcserver.wait()
|
|
||||||
except Exception as e:
|
|
||||||
LOG.exception(_LE('Service error occurred when stopping the '
|
|
||||||
'RPC server. Error: %s'), e)
|
|
||||||
try:
|
|
||||||
self.manager.del_host(deregister=self.deregister)
|
|
||||||
except Exception as e:
|
|
||||||
LOG.exception(_LE('Service error occurred when cleaning up '
|
|
||||||
'the RPC manager. Error: %s'), e)
|
|
||||||
|
|
||||||
super(RPCService, self).stop(graceful=True)
|
|
||||||
LOG.info(_LI('Stopped RPC server for service %(service)s on host '
|
|
||||||
'%(host)s.'),
|
|
||||||
{'service': self.topic, 'host': self.host})
|
|
||||||
|
|
||||||
def _handle_signal(self):
|
|
||||||
LOG.info(_LI('Got signal SIGUSR1. Not deregistering on next shutdown '
|
|
||||||
'of service %(service)s on host %(host)s.'),
|
|
||||||
{'service': self.topic, 'host': self.host})
|
|
||||||
self.deregister = False
|
|
||||||
|
|
||||||
def handle_signal(self):
|
|
||||||
"""Add a signal handler for SIGUSR1.
|
|
||||||
|
|
||||||
The handler ensures that the manager is not deregistered when it is
|
|
||||||
shutdown.
|
|
||||||
"""
|
|
||||||
signal.signal(signal.SIGUSR1, self._handle_signal)
|
|
||||||
|
|
||||||
|
|
||||||
_DEFAULT_LOG_LEVELS = ['amqp=WARN', 'amqplib=WARN', 'qpid.messaging=INFO',
|
_DEFAULT_LOG_LEVELS = ['amqp=WARN', 'amqplib=WARN', 'qpid.messaging=INFO',
|
||||||
'oslo.messaging=INFO', 'sqlalchemy=WARN',
|
'oslo.messaging=INFO', 'sqlalchemy=WARN',
|
||||||
'keystoneclient=INFO', 'stevedore=INFO',
|
'keystoneclient=INFO', 'stevedore=INFO',
|
||||||
@@ -125,10 +63,165 @@ _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=[], conf=cfg.CONF):
|
class WSGIService(service.ServiceBase):
|
||||||
|
"""Provides ability to launch Watcher API from wsgi app."""
|
||||||
|
|
||||||
|
def __init__(self, name, use_ssl=False):
|
||||||
|
"""Initialize, but do not start the WSGI server.
|
||||||
|
|
||||||
|
:param name: The name of the WSGI server given to the loader.
|
||||||
|
:param use_ssl: Wraps the socket in an SSL context if True.
|
||||||
|
"""
|
||||||
|
self.name = name
|
||||||
|
self.app = app.VersionSelectorApplication()
|
||||||
|
self.workers = (CONF.api.workers or
|
||||||
|
processutils.get_worker_count())
|
||||||
|
self.server = wsgi.Server(CONF, name, self.app,
|
||||||
|
host=CONF.api.host,
|
||||||
|
port=CONF.api.port,
|
||||||
|
use_ssl=use_ssl,
|
||||||
|
logger_name=name)
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
"""Start serving this service using loaded configuration"""
|
||||||
|
self.server.start()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
"""Stop serving this API"""
|
||||||
|
self.server.stop()
|
||||||
|
|
||||||
|
def wait(self):
|
||||||
|
"""Wait for the service to stop serving this API"""
|
||||||
|
self.server.wait()
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
"""Reset server greenpool size to default"""
|
||||||
|
self.server.reset()
|
||||||
|
|
||||||
|
|
||||||
|
class Service(service.ServiceBase, dispatcher.EventDispatcher):
|
||||||
|
|
||||||
|
API_VERSION = '1.0'
|
||||||
|
|
||||||
|
def __init__(self, manager_class):
|
||||||
|
super(Service, self).__init__()
|
||||||
|
self.manager = manager_class()
|
||||||
|
|
||||||
|
self.publisher_id = self.manager.publisher_id
|
||||||
|
self.api_version = self.manager.API_VERSION
|
||||||
|
self.conductor_topic = self.manager.conductor_topic
|
||||||
|
self.status_topic = self.manager.status_topic
|
||||||
|
|
||||||
|
self.conductor_endpoints = [
|
||||||
|
ep(self) for ep in self.manager.conductor_endpoints
|
||||||
|
]
|
||||||
|
self.status_endpoints = [
|
||||||
|
ep(self.publisher_id) for ep in self.manager.status_endpoints
|
||||||
|
]
|
||||||
|
|
||||||
|
self.serializer = rpc.RequestContextSerializer(
|
||||||
|
base.WatcherObjectSerializer())
|
||||||
|
|
||||||
|
self.conductor_topic_handler = self.build_topic_handler(
|
||||||
|
self.conductor_topic, self.conductor_endpoints)
|
||||||
|
self.status_topic_handler = self.build_topic_handler(
|
||||||
|
self.status_topic, self.status_endpoints)
|
||||||
|
|
||||||
|
self._conductor_client = None
|
||||||
|
self._status_client = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def conductor_client(self):
|
||||||
|
if self._conductor_client is None:
|
||||||
|
transport = om.get_transport(CONF)
|
||||||
|
target = om.Target(
|
||||||
|
topic=self.conductor_topic,
|
||||||
|
version=self.API_VERSION,
|
||||||
|
)
|
||||||
|
self._conductor_client = om.RPCClient(
|
||||||
|
transport, target, serializer=self.serializer)
|
||||||
|
return self._conductor_client
|
||||||
|
|
||||||
|
@conductor_client.setter
|
||||||
|
def conductor_client(self, c):
|
||||||
|
self.conductor_client = c
|
||||||
|
|
||||||
|
@property
|
||||||
|
def status_client(self):
|
||||||
|
if self._status_client is None:
|
||||||
|
transport = om.get_transport(CONF)
|
||||||
|
target = om.Target(
|
||||||
|
topic=self.status_topic,
|
||||||
|
version=self.API_VERSION,
|
||||||
|
)
|
||||||
|
self._status_client = om.RPCClient(
|
||||||
|
transport, target, serializer=self.serializer)
|
||||||
|
return self._status_client
|
||||||
|
|
||||||
|
@status_client.setter
|
||||||
|
def status_client(self, c):
|
||||||
|
self.status_client = c
|
||||||
|
|
||||||
|
def build_topic_handler(self, topic_name, endpoints=()):
|
||||||
|
return messaging_handler.MessagingHandler(
|
||||||
|
self.publisher_id, topic_name, [self.manager] + list(endpoints),
|
||||||
|
self.api_version, self.serializer)
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
LOG.debug("Connecting to '%s' (%s)",
|
||||||
|
CONF.transport_url, CONF.rpc_backend)
|
||||||
|
self.conductor_topic_handler.start()
|
||||||
|
self.status_topic_handler.start()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
LOG.debug("Disconnecting from '%s' (%s)",
|
||||||
|
CONF.transport_url, CONF.rpc_backend)
|
||||||
|
self.conductor_topic_handler.stop()
|
||||||
|
self.status_topic_handler.stop()
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
"""Reset a service in case it received a SIGHUP."""
|
||||||
|
|
||||||
|
def wait(self):
|
||||||
|
"""Wait for service to complete."""
|
||||||
|
|
||||||
|
def publish_control(self, event, payload):
|
||||||
|
return self.conductor_topic_handler.publish_event(event, payload)
|
||||||
|
|
||||||
|
def publish_status(self, event, payload, request_id=None):
|
||||||
|
return self.status_topic_handler.publish_event(
|
||||||
|
event, payload, request_id)
|
||||||
|
|
||||||
|
def get_version(self):
|
||||||
|
return self.api_version
|
||||||
|
|
||||||
|
def check_api_version(self, context):
|
||||||
|
api_manager_version = self.conductor_client.call(
|
||||||
|
context.to_dict(), 'check_api_version',
|
||||||
|
api_version=self.api_version)
|
||||||
|
return api_manager_version
|
||||||
|
|
||||||
|
def response(self, evt, ctx, message):
|
||||||
|
payload = {
|
||||||
|
'request_id': ctx['request_id'],
|
||||||
|
'msg': message
|
||||||
|
}
|
||||||
|
self.publish_status(evt, payload)
|
||||||
|
|
||||||
|
|
||||||
|
def process_launcher(conf=cfg.CONF):
|
||||||
|
return service.ProcessLauncher(conf)
|
||||||
|
|
||||||
|
|
||||||
|
def prepare_service(argv=(), conf=cfg.CONF):
|
||||||
log.register_options(conf)
|
log.register_options(conf)
|
||||||
|
gmr_opts.set_defaults(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(conf, 'python-watcher')
|
log.setup(conf, 'python-watcher')
|
||||||
conf.log_opt_values(LOG, logging.DEBUG)
|
conf.log_opt_values(LOG, logging.DEBUG)
|
||||||
|
|
||||||
|
gmr.TextGuruMeditation.register_section(_('Plugins'), opts.show_plugins)
|
||||||
|
gmr.TextGuruMeditation.setup_autorun(version)
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
"""Utilities and helper functions."""
|
"""Utilities and helper functions."""
|
||||||
|
|
||||||
|
from jsonschema import validators
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
|
||||||
@@ -41,6 +42,29 @@ CONF.register_opts(UTILS_OPTS)
|
|||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Struct(dict):
|
||||||
|
"""Specialized dict where you access an item like an attribute
|
||||||
|
|
||||||
|
>>> struct = Struct()
|
||||||
|
>>> struct['a'] = 1
|
||||||
|
>>> struct.b = 2
|
||||||
|
>>> assert struct.a == 1
|
||||||
|
>>> assert struct['b'] == 2
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
try:
|
||||||
|
return self[name]
|
||||||
|
except KeyError:
|
||||||
|
raise AttributeError(name)
|
||||||
|
|
||||||
|
def __setattr__(self, name, value):
|
||||||
|
try:
|
||||||
|
self[name] = value
|
||||||
|
except KeyError:
|
||||||
|
raise AttributeError(name)
|
||||||
|
|
||||||
|
|
||||||
def safe_rstrip(value, chars=None):
|
def safe_rstrip(value, chars=None):
|
||||||
"""Removes trailing characters from a string if that does not make it empty
|
"""Removes trailing characters from a string if that does not make it empty
|
||||||
|
|
||||||
@@ -95,6 +119,36 @@ def is_hostname_safe(hostname):
|
|||||||
:returns: True if valid. False if not.
|
:returns: True if valid. False if not.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
m = '^[a-z0-9]([a-z0-9\-]{0,61}[a-z0-9])?$'
|
m = r'^[a-z0-9]([a-z0-9\-]{0,61}[a-z0-9])?$'
|
||||||
return (isinstance(hostname, six.string_types) and
|
return (isinstance(hostname, six.string_types) and
|
||||||
(re.match(m, hostname) is not None))
|
(re.match(m, hostname) is not None))
|
||||||
|
|
||||||
|
|
||||||
|
def get_cls_import_path(cls):
|
||||||
|
"""Return the import path of a given class"""
|
||||||
|
module = cls.__module__
|
||||||
|
if module is None or module == str.__module__:
|
||||||
|
return cls.__name__
|
||||||
|
return module + '.' + cls.__name__
|
||||||
|
|
||||||
|
|
||||||
|
# Default value feedback extension as jsonschema doesn't support it
|
||||||
|
def extend_with_default(validator_class):
|
||||||
|
validate_properties = validator_class.VALIDATORS["properties"]
|
||||||
|
|
||||||
|
def set_defaults(validator, properties, instance, schema):
|
||||||
|
for prop, subschema in properties.items():
|
||||||
|
if "default" in subschema:
|
||||||
|
instance.setdefault(prop, subschema["default"])
|
||||||
|
|
||||||
|
for error in validate_properties(
|
||||||
|
validator, properties, instance, schema,
|
||||||
|
):
|
||||||
|
yield error
|
||||||
|
|
||||||
|
return validators.extend(
|
||||||
|
validator_class, {"properties": set_defaults},
|
||||||
|
)
|
||||||
|
|
||||||
|
DefaultValidatingDraft4Validator = extend_with_default(
|
||||||
|
validators.Draft4Validator)
|
||||||
|
|||||||
@@ -35,7 +35,191 @@ class BaseConnection(object):
|
|||||||
"""Base class for storage system connections."""
|
"""Base class for storage system connections."""
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def get_audit_template_list(self, context, columns=None, filters=None,
|
def get_goal_list(self, context, filters=None, limit=None,
|
||||||
|
marker=None, sort_key=None, sort_dir=None):
|
||||||
|
"""Get specific columns for matching goals.
|
||||||
|
|
||||||
|
Return a list of the specified columns for all goals that
|
||||||
|
match the specified filters.
|
||||||
|
|
||||||
|
:param context: The security context
|
||||||
|
:param filters: Filters to apply. Defaults to None.
|
||||||
|
|
||||||
|
:param limit: Maximum number of goals to return.
|
||||||
|
:param marker: the last item of the previous page; we return the next
|
||||||
|
result set.
|
||||||
|
:param sort_key: Attribute by which results should be sorted.
|
||||||
|
:param sort_dir: direction in which results should be sorted.
|
||||||
|
(asc, desc)
|
||||||
|
:returns: A list of tuples of the specified columns.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def create_goal(self, values):
|
||||||
|
"""Create a new goal.
|
||||||
|
|
||||||
|
:param values: A dict containing several items used to identify
|
||||||
|
and track the goal. For example:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
{
|
||||||
|
'uuid': utils.generate_uuid(),
|
||||||
|
'name': 'DUMMY',
|
||||||
|
'display_name': 'Dummy',
|
||||||
|
}
|
||||||
|
:returns: A goal
|
||||||
|
:raises: :py:class:`~.GoalAlreadyExists`
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def get_goal_by_id(self, context, goal_id):
|
||||||
|
"""Return a goal given its ID.
|
||||||
|
|
||||||
|
:param context: The security context
|
||||||
|
:param goal_id: The ID of a goal
|
||||||
|
:returns: A goal
|
||||||
|
:raises: :py:class:`~.GoalNotFound`
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def get_goal_by_uuid(self, context, goal_uuid):
|
||||||
|
"""Return a goal given its UUID.
|
||||||
|
|
||||||
|
:param context: The security context
|
||||||
|
:param goal_uuid: The UUID of a goal
|
||||||
|
:returns: A goal
|
||||||
|
:raises: :py:class:`~.GoalNotFound`
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def get_goal_by_name(self, context, goal_name):
|
||||||
|
"""Return a goal given its name.
|
||||||
|
|
||||||
|
:param context: The security context
|
||||||
|
:param goal_name: The name of a goal
|
||||||
|
:returns: A goal
|
||||||
|
:raises: :py:class:`~.GoalNotFound`
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def destroy_goal(self, goal_uuid):
|
||||||
|
"""Destroy a goal.
|
||||||
|
|
||||||
|
:param goal_uuid: The UUID of a goal
|
||||||
|
:raises: :py:class:`~.GoalNotFound`
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def update_goal(self, goal_uuid, values):
|
||||||
|
"""Update properties of a goal.
|
||||||
|
|
||||||
|
:param goal_uuid: The UUID of a goal
|
||||||
|
:param values: A dict containing several items used to identify
|
||||||
|
and track the goal. For example:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
{
|
||||||
|
'uuid': utils.generate_uuid(),
|
||||||
|
'name': 'DUMMY',
|
||||||
|
'display_name': 'Dummy',
|
||||||
|
}
|
||||||
|
:returns: A goal
|
||||||
|
:raises: :py:class:`~.GoalNotFound`
|
||||||
|
:raises: :py:class:`~.Invalid`
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def get_strategy_list(self, context, filters=None, limit=None,
|
||||||
|
marker=None, sort_key=None, sort_dir=None):
|
||||||
|
"""Get specific columns for matching strategies.
|
||||||
|
|
||||||
|
Return a list of the specified columns for all strategies that
|
||||||
|
match the specified filters.
|
||||||
|
|
||||||
|
:param context: The security context
|
||||||
|
:param filters: Filters to apply. Defaults to None.
|
||||||
|
|
||||||
|
:param limit: Maximum number of strategies to return.
|
||||||
|
:param marker: The last item of the previous page; we return the next
|
||||||
|
result set.
|
||||||
|
:param sort_key: Attribute by which results should be sorted.
|
||||||
|
:param sort_dir: Direction in which results should be sorted.
|
||||||
|
(asc, desc)
|
||||||
|
:returns: A list of tuples of the specified columns.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def create_strategy(self, values):
|
||||||
|
"""Create a new strategy.
|
||||||
|
|
||||||
|
:param values: A dict containing items used to identify
|
||||||
|
and track the strategy. For example:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
{
|
||||||
|
'id': 1,
|
||||||
|
'uuid': utils.generate_uuid(),
|
||||||
|
'name': 'my_strategy',
|
||||||
|
'display_name': 'My strategy',
|
||||||
|
'goal_uuid': utils.generate_uuid(),
|
||||||
|
}
|
||||||
|
:returns: A strategy
|
||||||
|
:raises: :py:class:`~.StrategyAlreadyExists`
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def get_strategy_by_id(self, context, strategy_id):
|
||||||
|
"""Return a strategy given its ID.
|
||||||
|
|
||||||
|
:param context: The security context
|
||||||
|
:param strategy_id: The ID of a strategy
|
||||||
|
:returns: A strategy
|
||||||
|
:raises: :py:class:`~.StrategyNotFound`
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def get_strategy_by_uuid(self, context, strategy_uuid):
|
||||||
|
"""Return a strategy given its UUID.
|
||||||
|
|
||||||
|
:param context: The security context
|
||||||
|
:param strategy_uuid: The UUID of a strategy
|
||||||
|
:returns: A strategy
|
||||||
|
:raises: :py:class:`~.StrategyNotFound`
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def get_strategy_by_name(self, context, strategy_name):
|
||||||
|
"""Return a strategy given its name.
|
||||||
|
|
||||||
|
:param context: The security context
|
||||||
|
:param strategy_name: The name of a strategy
|
||||||
|
:returns: A strategy
|
||||||
|
:raises: :py:class:`~.StrategyNotFound`
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def destroy_strategy(self, strategy_uuid):
|
||||||
|
"""Destroy a strategy.
|
||||||
|
|
||||||
|
:param strategy_uuid: The UUID of a strategy
|
||||||
|
:raises: :py:class:`~.StrategyNotFound`
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def update_strategy(self, strategy_uuid, values):
|
||||||
|
"""Update properties of a strategy.
|
||||||
|
|
||||||
|
:param strategy_uuid: The UUID of a strategy
|
||||||
|
:returns: A strategy
|
||||||
|
:raises: :py:class:`~.StrategyNotFound`
|
||||||
|
:raises: :py:class:`~.Invalid`
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def get_audit_template_list(self, context, filters=None,
|
||||||
limit=None, marker=None, sort_key=None,
|
limit=None, marker=None, sort_key=None,
|
||||||
sort_dir=None):
|
sort_dir=None):
|
||||||
"""Get specific columns for matching audit templates.
|
"""Get specific columns for matching audit templates.
|
||||||
@@ -44,8 +228,6 @@ class BaseConnection(object):
|
|||||||
match the specified filters.
|
match the specified filters.
|
||||||
|
|
||||||
:param context: The security context
|
:param context: The security context
|
||||||
:param columns: List of column names to return.
|
|
||||||
Defaults to 'id' column when columns == None.
|
|
||||||
:param filters: Filters to apply. Defaults to None.
|
:param filters: Filters to apply. Defaults to None.
|
||||||
|
|
||||||
:param limit: Maximum number of audit templates to return.
|
:param limit: Maximum number of audit templates to return.
|
||||||
@@ -75,7 +257,7 @@ class BaseConnection(object):
|
|||||||
'extra': {'automatic': True}
|
'extra': {'automatic': True}
|
||||||
}
|
}
|
||||||
:returns: An audit template.
|
:returns: An audit template.
|
||||||
:raises: AuditTemplateAlreadyExists
|
:raises: :py:class:`~.AuditTemplateAlreadyExists`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
@@ -85,7 +267,7 @@ class BaseConnection(object):
|
|||||||
:param context: The security context
|
:param context: The security context
|
||||||
:param audit_template_id: The id of an audit template.
|
:param audit_template_id: The id of an audit template.
|
||||||
:returns: An audit template.
|
:returns: An audit template.
|
||||||
:raises: AuditTemplateNotFound
|
:raises: :py:class:`~.AuditTemplateNotFound`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
@@ -95,7 +277,7 @@ class BaseConnection(object):
|
|||||||
:param context: The security context
|
:param context: The security context
|
||||||
:param audit_template_uuid: The uuid of an audit template.
|
:param audit_template_uuid: The uuid of an audit template.
|
||||||
:returns: An audit template.
|
:returns: An audit template.
|
||||||
:raises: AuditTemplateNotFound
|
:raises: :py:class:`~.AuditTemplateNotFound`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def get_audit_template_by_name(self, context, audit_template_name):
|
def get_audit_template_by_name(self, context, audit_template_name):
|
||||||
@@ -104,7 +286,7 @@ class BaseConnection(object):
|
|||||||
:param context: The security context
|
:param context: The security context
|
||||||
:param audit_template_name: The name of an audit template.
|
:param audit_template_name: The name of an audit template.
|
||||||
:returns: An audit template.
|
:returns: An audit template.
|
||||||
:raises: AuditTemplateNotFound
|
:raises: :py:class:`~.AuditTemplateNotFound`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
@@ -112,7 +294,7 @@ class BaseConnection(object):
|
|||||||
"""Destroy an audit_template.
|
"""Destroy an audit_template.
|
||||||
|
|
||||||
:param audit_template_id: The id or uuid of an audit template.
|
:param audit_template_id: The id or uuid of an audit template.
|
||||||
:raises: AuditTemplateNotFound
|
:raises: :py:class:`~.AuditTemplateNotFound`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
@@ -121,8 +303,8 @@ class BaseConnection(object):
|
|||||||
|
|
||||||
:param audit_template_id: The id or uuid of an audit template.
|
:param audit_template_id: The id or uuid of an audit template.
|
||||||
:returns: An audit template.
|
:returns: An audit template.
|
||||||
:raises: AuditTemplateNotFound
|
:raises: :py:class:`~.AuditTemplateNotFound`
|
||||||
:raises: Invalid
|
:raises: :py:class:`~.Invalid`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
@@ -130,11 +312,11 @@ class BaseConnection(object):
|
|||||||
"""Soft delete an audit_template.
|
"""Soft delete an audit_template.
|
||||||
|
|
||||||
:param audit_template_id: The id or uuid of an audit template.
|
:param audit_template_id: The id or uuid of an audit template.
|
||||||
:raises: AuditTemplateNotFound
|
:raises: :py:class:`~.AuditTemplateNotFound`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def get_audit_list(self, context, columns=None, filters=None, limit=None,
|
def get_audit_list(self, context, 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 audits.
|
"""Get specific columns for matching audits.
|
||||||
|
|
||||||
@@ -142,8 +324,6 @@ class BaseConnection(object):
|
|||||||
specified filters.
|
specified filters.
|
||||||
|
|
||||||
:param context: The security context
|
:param context: The security context
|
||||||
:param columns: List of column names to return.
|
|
||||||
Defaults to 'id' column when columns == None.
|
|
||||||
:param filters: Filters to apply. Defaults to None.
|
:param filters: Filters to apply. Defaults to None.
|
||||||
|
|
||||||
:param limit: Maximum number of audits to return.
|
:param limit: Maximum number of audits to return.
|
||||||
@@ -171,7 +351,7 @@ class BaseConnection(object):
|
|||||||
'deadline': None
|
'deadline': None
|
||||||
}
|
}
|
||||||
:returns: An audit.
|
:returns: An audit.
|
||||||
:raises: AuditAlreadyExists
|
:raises: :py:class:`~.AuditAlreadyExists`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
@@ -181,7 +361,7 @@ class BaseConnection(object):
|
|||||||
:param context: The security context
|
:param context: The security context
|
||||||
:param audit_id: The id of an audit.
|
:param audit_id: The id of an audit.
|
||||||
:returns: An audit.
|
:returns: An audit.
|
||||||
:raises: AuditNotFound
|
:raises: :py:class:`~.AuditNotFound`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
@@ -191,7 +371,7 @@ class BaseConnection(object):
|
|||||||
:param context: The security context
|
:param context: The security context
|
||||||
:param audit_uuid: The uuid of an audit.
|
:param audit_uuid: The uuid of an audit.
|
||||||
:returns: An audit.
|
:returns: An audit.
|
||||||
:raises: AuditNotFound
|
:raises: :py:class:`~.AuditNotFound`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
@@ -199,7 +379,7 @@ class BaseConnection(object):
|
|||||||
"""Destroy an audit and all associated action plans.
|
"""Destroy an audit and all associated action plans.
|
||||||
|
|
||||||
:param audit_id: The id or uuid of an audit.
|
:param audit_id: The id or uuid of an audit.
|
||||||
:raises: AuditNotFound
|
:raises: :py:class:`~.AuditNotFound`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
@@ -208,8 +388,8 @@ class BaseConnection(object):
|
|||||||
|
|
||||||
:param audit_id: The id or uuid of an audit.
|
:param audit_id: The id or uuid of an audit.
|
||||||
:returns: An audit.
|
:returns: An audit.
|
||||||
:raises: AuditNotFound
|
:raises: :py:class:`~.AuditNotFound`
|
||||||
:raises: Invalid
|
:raises: :py:class:`~.Invalid`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def soft_delete_audit(self, audit_id):
|
def soft_delete_audit(self, audit_id):
|
||||||
@@ -217,11 +397,11 @@ class BaseConnection(object):
|
|||||||
|
|
||||||
:param audit_id: The id or uuid of an audit.
|
:param audit_id: The id or uuid of an audit.
|
||||||
:returns: An audit.
|
:returns: An audit.
|
||||||
:raises: AuditNotFound
|
:raises: :py:class:`~.AuditNotFound`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def get_action_list(self, context, columns=None, filters=None, limit=None,
|
def get_action_list(self, context, 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 actions.
|
"""Get specific columns for matching actions.
|
||||||
|
|
||||||
@@ -229,8 +409,6 @@ class BaseConnection(object):
|
|||||||
specified filters.
|
specified filters.
|
||||||
|
|
||||||
:param context: The security context
|
:param context: The security context
|
||||||
:param columns: List of column names to return.
|
|
||||||
Defaults to 'id' column when columns == None.
|
|
||||||
:param filters: Filters to apply. Defaults to None.
|
:param filters: Filters to apply. Defaults to None.
|
||||||
|
|
||||||
:param limit: Maximum number of actions to return.
|
:param limit: Maximum number of actions to return.
|
||||||
@@ -259,7 +437,7 @@ class BaseConnection(object):
|
|||||||
'aggregate': 'nova aggregate name or uuid'
|
'aggregate': 'nova aggregate name or uuid'
|
||||||
}
|
}
|
||||||
:returns: A action.
|
:returns: A action.
|
||||||
:raises: ActionAlreadyExists
|
:raises: :py:class:`~.ActionAlreadyExists`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
@@ -269,7 +447,7 @@ class BaseConnection(object):
|
|||||||
:param context: The security context
|
:param context: The security context
|
||||||
:param action_id: The id of a action.
|
:param action_id: The id of a action.
|
||||||
:returns: A action.
|
:returns: A action.
|
||||||
:raises: ActionNotFound
|
:raises: :py:class:`~.ActionNotFound`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
@@ -279,7 +457,7 @@ class BaseConnection(object):
|
|||||||
:param context: The security context
|
:param context: The security context
|
||||||
:param action_uuid: The uuid of a action.
|
:param action_uuid: The uuid of a action.
|
||||||
:returns: A action.
|
:returns: A action.
|
||||||
:raises: ActionNotFound
|
:raises: :py:class:`~.ActionNotFound`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
@@ -287,8 +465,8 @@ class BaseConnection(object):
|
|||||||
"""Destroy a action and all associated interfaces.
|
"""Destroy a action and all associated interfaces.
|
||||||
|
|
||||||
:param action_id: The id or uuid of a action.
|
:param action_id: The id or uuid of a action.
|
||||||
:raises: ActionNotFound
|
:raises: :py:class:`~.ActionNotFound`
|
||||||
:raises: ActionReferenced
|
:raises: :py:class:`~.ActionReferenced`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
@@ -297,14 +475,14 @@ class BaseConnection(object):
|
|||||||
|
|
||||||
:param action_id: The id or uuid of a action.
|
:param action_id: The id or uuid of a action.
|
||||||
:returns: A action.
|
:returns: A action.
|
||||||
:raises: ActionNotFound
|
:raises: :py:class:`~.ActionNotFound`
|
||||||
:raises: ActionReferenced
|
:raises: :py:class:`~.ActionReferenced`
|
||||||
:raises: Invalid
|
:raises: :py:class:`~.Invalid`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def get_action_plan_list(
|
def get_action_plan_list(
|
||||||
self, context, columns=None, filters=None, limit=None,
|
self, context, 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.
|
||||||
|
|
||||||
@@ -312,8 +490,6 @@ class BaseConnection(object):
|
|||||||
match the specified filters.
|
match the specified filters.
|
||||||
|
|
||||||
:param context: The security context
|
:param context: The security context
|
||||||
:param columns: List of column names to return.
|
|
||||||
Defaults to 'id' column when columns == None.
|
|
||||||
:param filters: Filters to apply. Defaults to None.
|
:param filters: Filters to apply. Defaults to None.
|
||||||
|
|
||||||
:param limit: Maximum number of audits to return.
|
:param limit: Maximum number of audits to return.
|
||||||
@@ -332,7 +508,7 @@ class BaseConnection(object):
|
|||||||
:param values: A dict containing several items used to identify
|
:param values: A dict containing several items used to identify
|
||||||
and track the action plan.
|
and track the action plan.
|
||||||
:returns: An action plan.
|
:returns: An action plan.
|
||||||
:raises: ActionPlanAlreadyExists
|
:raises: :py:class:`~.ActionPlanAlreadyExists`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
@@ -342,7 +518,7 @@ class BaseConnection(object):
|
|||||||
:param context: The security context
|
:param context: The security context
|
||||||
:param action_plan_id: The id of an action plan.
|
:param action_plan_id: The id of an action plan.
|
||||||
:returns: An action plan.
|
:returns: An action plan.
|
||||||
:raises: ActionPlanNotFound
|
:raises: :py:class:`~.ActionPlanNotFound`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
@@ -352,7 +528,7 @@ class BaseConnection(object):
|
|||||||
:param context: The security context
|
:param context: The security context
|
||||||
:param action_plan__uuid: The uuid of an action plan.
|
:param action_plan__uuid: The uuid of an action plan.
|
||||||
:returns: An action plan.
|
:returns: An action plan.
|
||||||
:raises: ActionPlanNotFound
|
:raises: :py:class:`~.ActionPlanNotFound`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
@@ -360,8 +536,8 @@ class BaseConnection(object):
|
|||||||
"""Destroy an action plan and all associated interfaces.
|
"""Destroy an action plan and all associated interfaces.
|
||||||
|
|
||||||
:param action_plan_id: The id or uuid of a action plan.
|
:param action_plan_id: The id or uuid of a action plan.
|
||||||
:raises: ActionPlanNotFound
|
:raises: :py:class:`~.ActionPlanNotFound`
|
||||||
:raises: ActionPlanReferenced
|
:raises: :py:class:`~.ActionPlanReferenced`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
@@ -370,7 +546,97 @@ class BaseConnection(object):
|
|||||||
|
|
||||||
:param action_plan_id: The id or uuid of an action plan.
|
:param action_plan_id: The id or uuid of an action plan.
|
||||||
:returns: An action plan.
|
:returns: An action plan.
|
||||||
:raises: ActionPlanNotFound
|
:raises: :py:class:`~.ActionPlanNotFound`
|
||||||
:raises: ActionPlanReferenced
|
:raises: :py:class:`~.ActionPlanReferenced`
|
||||||
:raises: Invalid
|
:raises: :py:class:`~.Invalid`
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def get_efficacy_indicator_list(self, context, filters=None, limit=None,
|
||||||
|
marker=None, sort_key=None, sort_dir=None):
|
||||||
|
"""Get specific columns for matching efficacy indicators.
|
||||||
|
|
||||||
|
Return a list of the specified columns for all efficacy indicators that
|
||||||
|
match the specified filters.
|
||||||
|
|
||||||
|
:param context: The security context
|
||||||
|
:param columns: List of column names to return.
|
||||||
|
Defaults to 'id' column when columns == None.
|
||||||
|
:param filters: Filters to apply. Defaults to None.
|
||||||
|
|
||||||
|
:param limit: Maximum number of efficacy indicators to return.
|
||||||
|
:param marker: The last item of the previous page; we return the next
|
||||||
|
result set.
|
||||||
|
:param sort_key: Attribute by which results should be sorted.
|
||||||
|
:param sort_dir: Direction in which results should be sorted.
|
||||||
|
(asc, desc)
|
||||||
|
:returns: A list of tuples of the specified columns.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def create_efficacy_indicator(self, values):
|
||||||
|
"""Create a new efficacy indicator.
|
||||||
|
|
||||||
|
:param values: A dict containing items used to identify
|
||||||
|
and track the efficacy indicator. For example:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
{
|
||||||
|
'id': 1,
|
||||||
|
'uuid': utils.generate_uuid(),
|
||||||
|
'name': 'my_efficacy_indicator',
|
||||||
|
'display_name': 'My efficacy indicator',
|
||||||
|
'goal_uuid': utils.generate_uuid(),
|
||||||
|
}
|
||||||
|
:returns: An efficacy_indicator
|
||||||
|
:raises: :py:class:`~.EfficacyIndicatorAlreadyExists`
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def get_efficacy_indicator_by_id(self, context, efficacy_indicator_id):
|
||||||
|
"""Return an efficacy indicator given its ID.
|
||||||
|
|
||||||
|
:param context: The security context
|
||||||
|
:param efficacy_indicator_id: The ID of an efficacy indicator
|
||||||
|
:returns: An efficacy indicator
|
||||||
|
:raises: :py:class:`~.EfficacyIndicatorNotFound`
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def get_efficacy_indicator_by_uuid(self, context, efficacy_indicator_uuid):
|
||||||
|
"""Return an efficacy indicator given its UUID.
|
||||||
|
|
||||||
|
:param context: The security context
|
||||||
|
:param efficacy_indicator_uuid: The UUID of an efficacy indicator
|
||||||
|
:returns: An efficacy indicator
|
||||||
|
:raises: :py:class:`~.EfficacyIndicatorNotFound`
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def get_efficacy_indicator_by_name(self, context, efficacy_indicator_name):
|
||||||
|
"""Return an efficacy indicator given its name.
|
||||||
|
|
||||||
|
:param context: The security context
|
||||||
|
:param efficacy_indicator_name: The name of an efficacy indicator
|
||||||
|
:returns: An efficacy indicator
|
||||||
|
:raises: :py:class:`~.EfficacyIndicatorNotFound`
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def destroy_efficacy_indicator(self, efficacy_indicator_uuid):
|
||||||
|
"""Destroy an efficacy indicator.
|
||||||
|
|
||||||
|
:param efficacy_indicator_uuid: The UUID of an efficacy indicator
|
||||||
|
:raises: :py:class:`~.EfficacyIndicatorNotFound`
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def update_efficacy_indicator(self, efficacy_indicator_uuid, values):
|
||||||
|
"""Update properties of an efficacy indicator.
|
||||||
|
|
||||||
|
:param efficacy_indicator_uuid: The UUID of an efficacy indicator
|
||||||
|
:returns: An efficacy indicator
|
||||||
|
:raises: :py:class:`~.EfficacyIndicatorNotFound`
|
||||||
|
:raises: :py:class:`~.Invalid`
|
||||||
"""
|
"""
|
||||||
|
|||||||
484
watcher/db/purge.py
Normal file
@@ -0,0 +1,484 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
# Copyright (c) 2016 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 __future__ import print_function
|
||||||
|
|
||||||
|
import collections
|
||||||
|
import datetime
|
||||||
|
import itertools
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from oslo_log import log
|
||||||
|
from oslo_utils import strutils
|
||||||
|
import prettytable as ptable
|
||||||
|
from six.moves import input
|
||||||
|
|
||||||
|
from watcher._i18n import _, _LI
|
||||||
|
from watcher._i18n import lazy_translation_enabled
|
||||||
|
from watcher.common import context
|
||||||
|
from watcher.common import exception
|
||||||
|
from watcher.common import utils
|
||||||
|
from watcher import objects
|
||||||
|
|
||||||
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class WatcherObjectsMap(object):
|
||||||
|
"""Wrapper to deal with watcher objects per type
|
||||||
|
|
||||||
|
This wrapper object contains a list of watcher objects per type.
|
||||||
|
Its main use is to simplify the merge of watcher objects by avoiding
|
||||||
|
duplicates, but also for representing the relationships between these
|
||||||
|
objects.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# This is for generating the .pot translations
|
||||||
|
keymap = collections.OrderedDict([
|
||||||
|
("goals", _("Goals")),
|
||||||
|
("strategies", _("Strategies")),
|
||||||
|
("audit_templates", _("Audit Templates")),
|
||||||
|
("audits", _("Audits")),
|
||||||
|
("action_plans", _("Action Plans")),
|
||||||
|
("actions", _("Actions")),
|
||||||
|
])
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
for attr_name in self.keys():
|
||||||
|
setattr(self, attr_name, [])
|
||||||
|
|
||||||
|
def values(self):
|
||||||
|
return (getattr(self, key) for key in self.keys())
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def keys(cls):
|
||||||
|
return cls.keymap.keys()
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return itertools.chain(*self.values())
|
||||||
|
|
||||||
|
def __add__(self, other):
|
||||||
|
new_map = self.__class__()
|
||||||
|
|
||||||
|
# Merge the 2 items dicts into a new object (and avoid dupes)
|
||||||
|
for attr_name, initials, others in zip(self.keys(), self.values(),
|
||||||
|
other.values()):
|
||||||
|
# Creates a copy
|
||||||
|
merged = initials[:]
|
||||||
|
initials_ids = [item.id for item in initials]
|
||||||
|
non_dupes = [item for item in others
|
||||||
|
if item.id not in initials_ids]
|
||||||
|
merged += non_dupes
|
||||||
|
|
||||||
|
setattr(new_map, attr_name, merged)
|
||||||
|
|
||||||
|
return new_map
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
out = ""
|
||||||
|
for key, vals in zip(self.keys(), self.values()):
|
||||||
|
ids = [val.id for val in vals]
|
||||||
|
out += "%(key)s: %(val)s" % (dict(key=key, val=ids))
|
||||||
|
out += "\n"
|
||||||
|
return out
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return sum(len(getattr(self, key)) for key in self.keys())
|
||||||
|
|
||||||
|
def get_count_table(self):
|
||||||
|
headers = list(self.keymap.values())
|
||||||
|
headers.append(_("Total")) # We also add a total count
|
||||||
|
translated_headers = [
|
||||||
|
h.translate() if lazy_translation_enabled() else h
|
||||||
|
for h in headers
|
||||||
|
]
|
||||||
|
|
||||||
|
counters = [len(cat_vals) for cat_vals in self.values()] + [len(self)]
|
||||||
|
table = ptable.PrettyTable(field_names=translated_headers)
|
||||||
|
table.add_row(counters)
|
||||||
|
return table.get_string()
|
||||||
|
|
||||||
|
|
||||||
|
class PurgeCommand(object):
|
||||||
|
"""Purges the DB by removing soft deleted entries
|
||||||
|
|
||||||
|
The workflow for this purge is the following:
|
||||||
|
|
||||||
|
# Find soft deleted objects which are expired
|
||||||
|
# Find orphan objects
|
||||||
|
# Find their related objects whether they are expired or not
|
||||||
|
# Merge them together
|
||||||
|
# If it does not exceed the limit, destroy them all
|
||||||
|
"""
|
||||||
|
|
||||||
|
ctx = context.make_context(show_deleted=True)
|
||||||
|
|
||||||
|
def __init__(self, age_in_days=None, max_number=None,
|
||||||
|
uuid=None, exclude_orphans=False, dry_run=None):
|
||||||
|
self.age_in_days = age_in_days
|
||||||
|
self.max_number = max_number
|
||||||
|
self.uuid = uuid
|
||||||
|
self.exclude_orphans = exclude_orphans
|
||||||
|
self.dry_run = dry_run
|
||||||
|
|
||||||
|
self._delete_up_to_max = None
|
||||||
|
self._objects_map = WatcherObjectsMap()
|
||||||
|
|
||||||
|
def get_expiry_date(self):
|
||||||
|
if not self.age_in_days:
|
||||||
|
return None
|
||||||
|
today = datetime.datetime.today()
|
||||||
|
expiry_date = today - datetime.timedelta(days=self.age_in_days)
|
||||||
|
return expiry_date
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_audit_template_uuid(cls, uuid_or_name):
|
||||||
|
if uuid_or_name is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
query_func = None
|
||||||
|
if not utils.is_uuid_like(uuid_or_name):
|
||||||
|
query_func = objects.AuditTemplate.get_by_name
|
||||||
|
else:
|
||||||
|
query_func = objects.AuditTemplate.get_by_uuid
|
||||||
|
|
||||||
|
try:
|
||||||
|
audit_template = query_func(cls.ctx, uuid_or_name)
|
||||||
|
except Exception as exc:
|
||||||
|
LOG.exception(exc)
|
||||||
|
raise exception.AuditTemplateNotFound(audit_template=uuid_or_name)
|
||||||
|
|
||||||
|
if not audit_template.deleted_at:
|
||||||
|
raise exception.NotSoftDeletedStateError(
|
||||||
|
name=_('Audit Template'), id=uuid_or_name)
|
||||||
|
|
||||||
|
return audit_template.uuid
|
||||||
|
|
||||||
|
def _find_goals(self, filters=None):
|
||||||
|
return objects.Goal.list(self.ctx, filters=filters)
|
||||||
|
|
||||||
|
def _find_strategies(self, filters=None):
|
||||||
|
return objects.Strategy.list(self.ctx, filters=filters)
|
||||||
|
|
||||||
|
def _find_audit_templates(self, filters=None):
|
||||||
|
return objects.AuditTemplate.list(self.ctx, filters=filters)
|
||||||
|
|
||||||
|
def _find_audits(self, filters=None):
|
||||||
|
return objects.Audit.list(self.ctx, filters=filters)
|
||||||
|
|
||||||
|
def _find_action_plans(self, filters=None):
|
||||||
|
return objects.ActionPlan.list(self.ctx, filters=filters)
|
||||||
|
|
||||||
|
def _find_actions(self, filters=None):
|
||||||
|
return objects.Action.list(self.ctx, filters=filters)
|
||||||
|
|
||||||
|
def _find_orphans(self):
|
||||||
|
orphans = WatcherObjectsMap()
|
||||||
|
|
||||||
|
filters = dict(deleted=False)
|
||||||
|
goals = objects.Goal.list(self.ctx, filters=filters)
|
||||||
|
strategies = objects.Strategy.list(self.ctx, filters=filters)
|
||||||
|
audit_templates = objects.AuditTemplate.list(self.ctx, filters=filters)
|
||||||
|
audits = objects.Audit.list(self.ctx, filters=filters)
|
||||||
|
action_plans = objects.ActionPlan.list(self.ctx, filters=filters)
|
||||||
|
actions = objects.Action.list(self.ctx, filters=filters)
|
||||||
|
|
||||||
|
goal_ids = set(g.id for g in goals)
|
||||||
|
orphans.strategies = [
|
||||||
|
strategy for strategy in strategies
|
||||||
|
if strategy.goal_id not in goal_ids]
|
||||||
|
|
||||||
|
strategy_ids = [s.id for s in (s for s in strategies
|
||||||
|
if s not in orphans.strategies)]
|
||||||
|
orphans.audit_templates = [
|
||||||
|
audit_template for audit_template in audit_templates
|
||||||
|
if audit_template.goal_id not in goal_ids or
|
||||||
|
(audit_template.strategy_id and
|
||||||
|
audit_template.strategy_id not in strategy_ids)]
|
||||||
|
|
||||||
|
audit_template_ids = [at.id for at in audit_templates
|
||||||
|
if at not in orphans.audit_templates]
|
||||||
|
orphans.audits = [
|
||||||
|
audit for audit in audits
|
||||||
|
if audit.audit_template_id not in audit_template_ids]
|
||||||
|
|
||||||
|
# Objects with orphan parents are themselves orphans
|
||||||
|
audit_ids = [audit.id for audit in audits
|
||||||
|
if audit not in orphans.audits]
|
||||||
|
orphans.action_plans = [
|
||||||
|
ap for ap in action_plans
|
||||||
|
if ap.audit_id not in audit_ids]
|
||||||
|
|
||||||
|
# Objects with orphan parents are themselves orphans
|
||||||
|
action_plan_ids = [ap.id for ap in action_plans
|
||||||
|
if ap not in orphans.action_plans]
|
||||||
|
orphans.actions = [
|
||||||
|
action for action in actions
|
||||||
|
if action.action_plan_id not in action_plan_ids]
|
||||||
|
|
||||||
|
LOG.debug("Orphans found:\n%s", orphans)
|
||||||
|
LOG.info(_LI("Orphans found:\n%s"), orphans.get_count_table())
|
||||||
|
|
||||||
|
return orphans
|
||||||
|
|
||||||
|
def _find_soft_deleted_objects(self):
|
||||||
|
to_be_deleted = WatcherObjectsMap()
|
||||||
|
expiry_date = self.get_expiry_date()
|
||||||
|
filters = dict(deleted=True)
|
||||||
|
|
||||||
|
if self.uuid:
|
||||||
|
filters["uuid"] = self.uuid
|
||||||
|
if expiry_date:
|
||||||
|
filters.update(dict(deleted_at__lt=expiry_date))
|
||||||
|
|
||||||
|
to_be_deleted.goals.extend(self._find_goals(filters))
|
||||||
|
to_be_deleted.strategies.extend(self._find_strategies(filters))
|
||||||
|
to_be_deleted.audit_templates.extend(
|
||||||
|
self._find_audit_templates(filters))
|
||||||
|
to_be_deleted.audits.extend(self._find_audits(filters))
|
||||||
|
to_be_deleted.action_plans.extend(
|
||||||
|
self._find_action_plans(filters))
|
||||||
|
to_be_deleted.actions.extend(self._find_actions(filters))
|
||||||
|
|
||||||
|
soft_deleted_objs = self._find_related_objects(
|
||||||
|
to_be_deleted, base_filters=dict(deleted=True))
|
||||||
|
|
||||||
|
LOG.debug("Soft deleted objects:\n%s", soft_deleted_objs)
|
||||||
|
|
||||||
|
return soft_deleted_objs
|
||||||
|
|
||||||
|
def _find_related_objects(self, objects_map, base_filters=None):
|
||||||
|
base_filters = base_filters or {}
|
||||||
|
|
||||||
|
for goal in objects_map.goals:
|
||||||
|
filters = {}
|
||||||
|
filters.update(base_filters)
|
||||||
|
filters.update(dict(goal_id=goal.id))
|
||||||
|
related_objs = WatcherObjectsMap()
|
||||||
|
related_objs.strategies = self._find_strategies(filters)
|
||||||
|
related_objs.audit_templates = self._find_audit_templates(filters)
|
||||||
|
objects_map += related_objs
|
||||||
|
|
||||||
|
for strategy in objects_map.strategies:
|
||||||
|
filters = {}
|
||||||
|
filters.update(base_filters)
|
||||||
|
filters.update(dict(strategy_id=strategy.id))
|
||||||
|
related_objs = WatcherObjectsMap()
|
||||||
|
related_objs.audit_templates = self._find_audit_templates(filters)
|
||||||
|
objects_map += related_objs
|
||||||
|
|
||||||
|
for audit_template in objects_map.audit_templates:
|
||||||
|
filters = {}
|
||||||
|
filters.update(base_filters)
|
||||||
|
filters.update(dict(audit_template_id=audit_template.id))
|
||||||
|
related_objs = WatcherObjectsMap()
|
||||||
|
related_objs.audits = self._find_audits(filters)
|
||||||
|
objects_map += related_objs
|
||||||
|
|
||||||
|
for audit in objects_map.audits:
|
||||||
|
filters = {}
|
||||||
|
filters.update(base_filters)
|
||||||
|
filters.update(dict(audit_id=audit.id))
|
||||||
|
related_objs = WatcherObjectsMap()
|
||||||
|
related_objs.action_plans = self._find_action_plans(filters)
|
||||||
|
objects_map += related_objs
|
||||||
|
|
||||||
|
for action_plan in objects_map.action_plans:
|
||||||
|
filters = {}
|
||||||
|
filters.update(base_filters)
|
||||||
|
filters.update(dict(action_plan_id=action_plan.id))
|
||||||
|
related_objs = WatcherObjectsMap()
|
||||||
|
related_objs.actions = self._find_actions(filters)
|
||||||
|
objects_map += related_objs
|
||||||
|
|
||||||
|
return objects_map
|
||||||
|
|
||||||
|
def confirmation_prompt(self):
|
||||||
|
print(self._objects_map.get_count_table())
|
||||||
|
raw_val = input(
|
||||||
|
_("There are %(count)d objects set for deletion. "
|
||||||
|
"Continue? [y/N]") % dict(count=len(self._objects_map)))
|
||||||
|
|
||||||
|
return strutils.bool_from_string(raw_val)
|
||||||
|
|
||||||
|
def delete_up_to_max_prompt(self, objects_map):
|
||||||
|
print(objects_map.get_count_table())
|
||||||
|
print(_("The number of objects (%(num)s) to delete from the database "
|
||||||
|
"exceeds the maximum number of objects (%(max_number)s) "
|
||||||
|
"specified.") % dict(max_number=self.max_number,
|
||||||
|
num=len(objects_map)))
|
||||||
|
raw_val = input(
|
||||||
|
_("Do you want to delete objects up to the specified maximum "
|
||||||
|
"number? [y/N]"))
|
||||||
|
|
||||||
|
self._delete_up_to_max = strutils.bool_from_string(raw_val)
|
||||||
|
|
||||||
|
return self._delete_up_to_max
|
||||||
|
|
||||||
|
def _aggregate_objects(self):
|
||||||
|
"""Objects aggregated on a 'per goal' basis"""
|
||||||
|
# todo: aggregate orphans as well
|
||||||
|
aggregate = []
|
||||||
|
for goal in self._objects_map.goals:
|
||||||
|
related_objs = WatcherObjectsMap()
|
||||||
|
|
||||||
|
# goals
|
||||||
|
related_objs.goals = [goal]
|
||||||
|
|
||||||
|
# strategies
|
||||||
|
goal_ids = [goal.id]
|
||||||
|
related_objs.strategies = [
|
||||||
|
strategy for strategy in self._objects_map.strategies
|
||||||
|
if strategy.goal_id in goal_ids
|
||||||
|
]
|
||||||
|
|
||||||
|
# audit templates
|
||||||
|
strategy_ids = [
|
||||||
|
strategy.id for strategy in related_objs.strategies]
|
||||||
|
related_objs.audit_templates = [
|
||||||
|
at for at in self._objects_map.audit_templates
|
||||||
|
if at.goal_id in goal_ids or
|
||||||
|
(at.strategy_id and at.strategy_id in strategy_ids)
|
||||||
|
]
|
||||||
|
|
||||||
|
# audits
|
||||||
|
audit_template_ids = [
|
||||||
|
audit_template.id
|
||||||
|
for audit_template in related_objs.audit_templates]
|
||||||
|
related_objs.audits = [
|
||||||
|
audit for audit in self._objects_map.audits
|
||||||
|
if audit.audit_template_id in audit_template_ids
|
||||||
|
]
|
||||||
|
|
||||||
|
# action plans
|
||||||
|
audit_ids = [audit.id for audit in related_objs.audits]
|
||||||
|
related_objs.action_plans = [
|
||||||
|
action_plan for action_plan in self._objects_map.action_plans
|
||||||
|
if action_plan.audit_id in audit_ids
|
||||||
|
]
|
||||||
|
|
||||||
|
# actions
|
||||||
|
action_plan_ids = [
|
||||||
|
action_plan.id for action_plan in related_objs.action_plans
|
||||||
|
]
|
||||||
|
related_objs.actions = [
|
||||||
|
action for action in self._objects_map.actions
|
||||||
|
if action.action_plan_id in action_plan_ids
|
||||||
|
]
|
||||||
|
aggregate.append(related_objs)
|
||||||
|
|
||||||
|
return aggregate
|
||||||
|
|
||||||
|
def _get_objects_up_to_limit(self):
|
||||||
|
aggregated_objects = self._aggregate_objects()
|
||||||
|
to_be_deleted_subset = WatcherObjectsMap()
|
||||||
|
|
||||||
|
for aggregate in aggregated_objects:
|
||||||
|
if len(aggregate) + len(to_be_deleted_subset) <= self.max_number:
|
||||||
|
to_be_deleted_subset += aggregate
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
LOG.debug(to_be_deleted_subset)
|
||||||
|
return to_be_deleted_subset
|
||||||
|
|
||||||
|
def find_objects_to_delete(self):
|
||||||
|
"""Finds all the objects to be purged
|
||||||
|
|
||||||
|
:returns: A mapping with all the Watcher objects to purged
|
||||||
|
:rtype: :py:class:`~.WatcherObjectsMap` instance
|
||||||
|
"""
|
||||||
|
to_be_deleted = self._find_soft_deleted_objects()
|
||||||
|
|
||||||
|
if not self.exclude_orphans:
|
||||||
|
to_be_deleted += self._find_orphans()
|
||||||
|
|
||||||
|
LOG.debug("Objects to be deleted:\n%s", to_be_deleted)
|
||||||
|
|
||||||
|
return to_be_deleted
|
||||||
|
|
||||||
|
def do_delete(self):
|
||||||
|
LOG.info(_LI("Deleting..."))
|
||||||
|
# Reversed to avoid errors with foreign keys
|
||||||
|
for entry in reversed(list(self._objects_map)):
|
||||||
|
entry.destroy()
|
||||||
|
|
||||||
|
def execute(self):
|
||||||
|
LOG.info(_LI("Starting purge command"))
|
||||||
|
self._objects_map = self.find_objects_to_delete()
|
||||||
|
|
||||||
|
if (self.max_number is not None and
|
||||||
|
len(self._objects_map) > self.max_number):
|
||||||
|
if self.delete_up_to_max_prompt(self._objects_map):
|
||||||
|
self._objects_map = self._get_objects_up_to_limit()
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
|
||||||
|
_orphans_note = (_(" (orphans excluded)") if self.exclude_orphans
|
||||||
|
else _(" (may include orphans)"))
|
||||||
|
if not self.dry_run and self.confirmation_prompt():
|
||||||
|
self.do_delete()
|
||||||
|
print(_("Purge results summary%s:") % _orphans_note)
|
||||||
|
LOG.info(_LI("Purge results summary%s:"), _orphans_note)
|
||||||
|
else:
|
||||||
|
LOG.debug(self._objects_map)
|
||||||
|
print(_("Here below is a table containing the objects "
|
||||||
|
"that can be purged%s:") % _orphans_note)
|
||||||
|
|
||||||
|
LOG.info("\n%s", self._objects_map.get_count_table())
|
||||||
|
print(self._objects_map.get_count_table())
|
||||||
|
LOG.info(_LI("Purge process completed"))
|
||||||
|
|
||||||
|
|
||||||
|
def purge(age_in_days, max_number, audit_template, exclude_orphans, dry_run):
|
||||||
|
"""Removes soft deleted objects from the database
|
||||||
|
|
||||||
|
:param age_in_days: Number of days since deletion (from today)
|
||||||
|
to exclude from the purge. If None, everything will be purged.
|
||||||
|
:type age_in_days: int
|
||||||
|
:param max_number: Max number of objects expected to be deleted.
|
||||||
|
Prevents the deletion if exceeded. No limit if set to None.
|
||||||
|
:type max_number: int
|
||||||
|
:param audit_template: UUID or name of the audit template to purge.
|
||||||
|
:type audit_template: str
|
||||||
|
:param exclude_orphans: Flag to indicate whether or not you want to
|
||||||
|
exclude orphans from deletion (default: False).
|
||||||
|
:type exclude_orphans: bool
|
||||||
|
:param dry_run: Flag to indicate whether or not you want to perform
|
||||||
|
a dry run (no deletion).
|
||||||
|
:type dry_run: bool
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if max_number and max_number < 0:
|
||||||
|
raise exception.NegativeLimitError
|
||||||
|
|
||||||
|
LOG.info("[options] age_in_days = %s", age_in_days)
|
||||||
|
LOG.info("[options] max_number = %s", max_number)
|
||||||
|
LOG.info("[options] audit_template = %s", audit_template)
|
||||||
|
LOG.info("[options] exclude_orphans = %s", exclude_orphans)
|
||||||
|
LOG.info("[options] dry_run = %s", dry_run)
|
||||||
|
|
||||||
|
uuid = PurgeCommand.get_audit_template_uuid(audit_template)
|
||||||
|
|
||||||
|
cmd = PurgeCommand(age_in_days, max_number, uuid,
|
||||||
|
exclude_orphans, dry_run)
|
||||||
|
|
||||||
|
cmd.execute()
|
||||||
|
|
||||||
|
except Exception as exc:
|
||||||
|
LOG.exception(exc)
|
||||||
|
print(exc)
|
||||||
|
sys.exit(1)
|
||||||
@@ -14,26 +14,29 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
|
||||||
"""SQLAlchemy storage backend."""
|
"""SQLAlchemy storage backend."""
|
||||||
|
|
||||||
|
import collections
|
||||||
|
import datetime
|
||||||
|
import operator
|
||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_db import exception as db_exc
|
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 sqlalchemy.orm import exc
|
from sqlalchemy.orm import exc
|
||||||
|
|
||||||
from watcher import _i18n
|
from watcher._i18n import _
|
||||||
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.objects import action as action_objects
|
||||||
|
from watcher.objects import action_plan as ap_objects
|
||||||
from watcher.objects import audit as audit_objects
|
from watcher.objects import audit as audit_objects
|
||||||
|
from watcher.objects import utils as objutils
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
LOG = log.getLogger(__name__)
|
|
||||||
_ = _i18n._
|
|
||||||
|
|
||||||
_FACADE = None
|
_FACADE = None
|
||||||
|
|
||||||
@@ -101,77 +104,268 @@ def _paginate_query(model, limit=None, marker=None, sort_key=None,
|
|||||||
return query.all()
|
return query.all()
|
||||||
|
|
||||||
|
|
||||||
|
class JoinMap(utils.Struct):
|
||||||
|
"""Mapping for the Join-based queries"""
|
||||||
|
|
||||||
|
|
||||||
|
NaturalJoinFilter = collections.namedtuple(
|
||||||
|
'NaturalJoinFilter', ['join_fieldname', 'join_model'])
|
||||||
|
|
||||||
|
|
||||||
class Connection(api.BaseConnection):
|
class Connection(api.BaseConnection):
|
||||||
"""SqlAlchemy connection."""
|
"""SqlAlchemy connection."""
|
||||||
|
|
||||||
|
valid_operators = {
|
||||||
|
"": operator.eq,
|
||||||
|
"eq": operator.eq,
|
||||||
|
"neq": operator.ne,
|
||||||
|
"gt": operator.gt,
|
||||||
|
"gte": operator.ge,
|
||||||
|
"lt": operator.lt,
|
||||||
|
"lte": operator.le,
|
||||||
|
"in": lambda field, choices: field.in_(choices),
|
||||||
|
"notin": lambda field, choices: field.notin_(choices),
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
pass
|
super(Connection, self).__init__()
|
||||||
|
|
||||||
|
def __add_simple_filter(self, query, model, fieldname, value, operator_):
|
||||||
|
field = getattr(model, fieldname)
|
||||||
|
|
||||||
|
if field.type.python_type is datetime.datetime:
|
||||||
|
value = objutils.datetime_or_str_or_none(value)
|
||||||
|
|
||||||
|
return query.filter(self.valid_operators[operator_](field, value))
|
||||||
|
|
||||||
|
def __add_join_filter(self, query, model, fieldname, value, operator_):
|
||||||
|
query = query.join(model)
|
||||||
|
return self.__add_simple_filter(query, model, fieldname,
|
||||||
|
value, operator_)
|
||||||
|
|
||||||
|
def __decompose_filter(self, raw_fieldname):
|
||||||
|
"""Decompose a filter name into its 2 subparts
|
||||||
|
|
||||||
|
A filter can take 2 forms:
|
||||||
|
|
||||||
|
- "<FIELDNAME>" which is a syntactic sugar for "<FIELDNAME>__eq"
|
||||||
|
- "<FIELDNAME>__<OPERATOR>" where <OPERATOR> is the comparison operator
|
||||||
|
to be used.
|
||||||
|
|
||||||
|
Available operators are:
|
||||||
|
|
||||||
|
- eq
|
||||||
|
- neq
|
||||||
|
- gt
|
||||||
|
- gte
|
||||||
|
- lt
|
||||||
|
- lte
|
||||||
|
- in
|
||||||
|
- notin
|
||||||
|
"""
|
||||||
|
separator = '__'
|
||||||
|
fieldname, separator, operator_ = raw_fieldname.partition(separator)
|
||||||
|
|
||||||
|
if operator_ and operator_ not in self.valid_operators:
|
||||||
|
raise exception.InvalidOperator(
|
||||||
|
operator=operator_, valid_operators=self.valid_operators)
|
||||||
|
|
||||||
|
return fieldname, operator_
|
||||||
|
|
||||||
|
def _add_filters(self, query, model, filters=None,
|
||||||
|
plain_fields=None, join_fieldmap=None):
|
||||||
|
"""Generic way to add filters to a Watcher model
|
||||||
|
|
||||||
|
Each filter key provided by the `filters` parameter will be decomposed
|
||||||
|
into 2 pieces: the field name and the comparison operator
|
||||||
|
|
||||||
|
- "": By default, the "eq" is applied if no operator is provided
|
||||||
|
- "eq", which stands for "equal" : e.g. {"state__eq": "PENDING"}
|
||||||
|
will result in the "WHERE state = 'PENDING'" clause.
|
||||||
|
- "neq", which stands for "not equal" : e.g. {"state__neq": "PENDING"}
|
||||||
|
will result in the "WHERE state != 'PENDING'" clause.
|
||||||
|
- "gt", which stands for "greater than" : e.g.
|
||||||
|
{"created_at__gt": "2016-06-06T10:33:22.063176"} will result in the
|
||||||
|
"WHERE created_at > '2016-06-06T10:33:22.063176'" clause.
|
||||||
|
- "gte", which stands for "greater than or equal to" : e.g.
|
||||||
|
{"created_at__gte": "2016-06-06T10:33:22.063176"} will result in the
|
||||||
|
"WHERE created_at >= '2016-06-06T10:33:22.063176'" clause.
|
||||||
|
- "lt", which stands for "less than" : e.g.
|
||||||
|
{"created_at__lt": "2016-06-06T10:33:22.063176"} will result in the
|
||||||
|
"WHERE created_at < '2016-06-06T10:33:22.063176'" clause.
|
||||||
|
- "lte", which stands for "less than or equal to" : e.g.
|
||||||
|
{"created_at__lte": "2016-06-06T10:33:22.063176"} will result in the
|
||||||
|
"WHERE created_at <= '2016-06-06T10:33:22.063176'" clause.
|
||||||
|
- "in": e.g. {"state__in": ('SUCCEEDED', 'FAILED')} will result in the
|
||||||
|
"WHERE state IN ('SUCCEEDED', 'FAILED')" clause.
|
||||||
|
|
||||||
|
:param query: a :py:class:`sqlalchemy.orm.query.Query` instance
|
||||||
|
:param model: the model class the filters should relate to
|
||||||
|
:param filters: dict with the following structure {"fieldname": value}
|
||||||
|
:param plain_fields: a :py:class:`sqlalchemy.orm.query.Query` instance
|
||||||
|
:param join_fieldmap: a :py:class:`sqlalchemy.orm.query.Query` instance
|
||||||
|
"""
|
||||||
|
soft_delete_mixin_fields = ['deleted', 'deleted_at']
|
||||||
|
timestamp_mixin_fields = ['created_at', 'updated_at']
|
||||||
|
filters = filters or {}
|
||||||
|
|
||||||
|
# Special case for 'deleted' because it is a non-boolean flag
|
||||||
|
if 'deleted' in filters:
|
||||||
|
deleted_filter = filters.pop('deleted')
|
||||||
|
op = 'eq' if not bool(deleted_filter) else 'neq'
|
||||||
|
filters['deleted__%s' % op] = 0
|
||||||
|
|
||||||
|
plain_fields = tuple(
|
||||||
|
(list(plain_fields) or []) +
|
||||||
|
soft_delete_mixin_fields +
|
||||||
|
timestamp_mixin_fields)
|
||||||
|
join_fieldmap = join_fieldmap or {}
|
||||||
|
|
||||||
|
for raw_fieldname, value in filters.items():
|
||||||
|
fieldname, operator_ = self.__decompose_filter(raw_fieldname)
|
||||||
|
if fieldname in plain_fields:
|
||||||
|
query = self.__add_simple_filter(
|
||||||
|
query, model, fieldname, value, operator_)
|
||||||
|
elif fieldname in join_fieldmap:
|
||||||
|
join_field, join_model = join_fieldmap[fieldname]
|
||||||
|
query = self.__add_join_filter(
|
||||||
|
query, join_model, join_field, value, operator_)
|
||||||
|
|
||||||
|
return query
|
||||||
|
|
||||||
|
def _get(self, context, model, fieldname, value):
|
||||||
|
query = model_query(model)
|
||||||
|
query = query.filter(getattr(model, fieldname) == value)
|
||||||
|
if not context.show_deleted:
|
||||||
|
query = query.filter(model.deleted_at.is_(None))
|
||||||
|
|
||||||
|
try:
|
||||||
|
obj = query.one()
|
||||||
|
except exc.NoResultFound:
|
||||||
|
raise exception.ResourceNotFound(name=model.__name__, id=value)
|
||||||
|
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def _update(self, model, id_, values):
|
||||||
|
session = get_session()
|
||||||
|
with session.begin():
|
||||||
|
query = model_query(model, session=session)
|
||||||
|
query = add_identity_filter(query, id_)
|
||||||
|
try:
|
||||||
|
ref = query.with_lockmode('update').one()
|
||||||
|
except exc.NoResultFound:
|
||||||
|
raise exception.ResourceNotFound(name=model.__name__, id=id_)
|
||||||
|
|
||||||
|
ref.update(values)
|
||||||
|
return ref
|
||||||
|
|
||||||
|
def _soft_delete(self, model, id_):
|
||||||
|
session = get_session()
|
||||||
|
with session.begin():
|
||||||
|
query = model_query(model, session=session)
|
||||||
|
query = add_identity_filter(query, id_)
|
||||||
|
try:
|
||||||
|
query.one()
|
||||||
|
except exc.NoResultFound:
|
||||||
|
raise exception.ResourceNotFound(name=model.__name__, id=id_)
|
||||||
|
|
||||||
|
query.soft_delete()
|
||||||
|
|
||||||
|
def _destroy(self, model, id_):
|
||||||
|
session = get_session()
|
||||||
|
with session.begin():
|
||||||
|
query = model_query(model, session=session)
|
||||||
|
query = add_identity_filter(query, id_)
|
||||||
|
|
||||||
|
try:
|
||||||
|
query.one()
|
||||||
|
except exc.NoResultFound:
|
||||||
|
raise exception.ResourceNotFound(name=model.__name__, id=id_)
|
||||||
|
|
||||||
|
query.delete()
|
||||||
|
|
||||||
|
def _add_goals_filters(self, query, filters):
|
||||||
|
if filters is None:
|
||||||
|
filters = {}
|
||||||
|
|
||||||
|
plain_fields = ['uuid', 'name', 'display_name']
|
||||||
|
|
||||||
|
return self._add_filters(
|
||||||
|
query=query, model=models.Goal, filters=filters,
|
||||||
|
plain_fields=plain_fields)
|
||||||
|
|
||||||
|
def _add_strategies_filters(self, query, filters):
|
||||||
|
plain_fields = ['uuid', 'name', 'display_name', 'goal_id']
|
||||||
|
join_fieldmap = JoinMap(
|
||||||
|
goal_uuid=NaturalJoinFilter(
|
||||||
|
join_fieldname="uuid", join_model=models.Goal),
|
||||||
|
goal_name=NaturalJoinFilter(
|
||||||
|
join_fieldname="name", join_model=models.Goal))
|
||||||
|
return self._add_filters(
|
||||||
|
query=query, model=models.Strategy, filters=filters,
|
||||||
|
plain_fields=plain_fields, join_fieldmap=join_fieldmap)
|
||||||
|
|
||||||
def _add_audit_templates_filters(self, query, filters):
|
def _add_audit_templates_filters(self, query, filters):
|
||||||
if filters is None:
|
if filters is None:
|
||||||
filters = []
|
filters = {}
|
||||||
|
|
||||||
if 'name' in filters:
|
plain_fields = ['uuid', 'name', 'host_aggregate',
|
||||||
query = query.filter_by(name=filters['name'])
|
'goal_id', 'strategy_id']
|
||||||
if 'host_aggregate' in filters:
|
join_fieldmap = JoinMap(
|
||||||
query = query.filter_by(host_aggregate=filters['host_aggregate'])
|
goal_uuid=NaturalJoinFilter(
|
||||||
if 'goal' in filters:
|
join_fieldname="uuid", join_model=models.Goal),
|
||||||
query = query.filter_by(goal=filters['goal'])
|
goal_name=NaturalJoinFilter(
|
||||||
|
join_fieldname="name", join_model=models.Goal),
|
||||||
|
strategy_uuid=NaturalJoinFilter(
|
||||||
|
join_fieldname="uuid", join_model=models.Strategy),
|
||||||
|
strategy_name=NaturalJoinFilter(
|
||||||
|
join_fieldname="name", join_model=models.Strategy),
|
||||||
|
)
|
||||||
|
|
||||||
return query
|
return self._add_filters(
|
||||||
|
query=query, model=models.AuditTemplate, filters=filters,
|
||||||
|
plain_fields=plain_fields, join_fieldmap=join_fieldmap)
|
||||||
|
|
||||||
def _add_audits_filters(self, query, filters):
|
def _add_audits_filters(self, query, filters):
|
||||||
if filters is None:
|
if filters is None:
|
||||||
filters = []
|
filters = {}
|
||||||
|
|
||||||
if 'type' in filters:
|
plain_fields = ['uuid', 'audit_type', 'state', 'audit_template_id']
|
||||||
query = query.filter_by(type=filters['type'])
|
join_fieldmap = {
|
||||||
if 'state' in filters:
|
'audit_template_uuid': ("uuid", models.AuditTemplate),
|
||||||
query = query.filter_by(state=filters['state'])
|
'audit_template_name': ("name", models.AuditTemplate),
|
||||||
if 'audit_template_id' in filters:
|
}
|
||||||
query = query.filter_by(
|
|
||||||
audit_template_id=filters['audit_template_id'])
|
return self._add_filters(
|
||||||
if 'audit_template_uuid' in filters:
|
query=query, model=models.Audit, filters=filters,
|
||||||
query = query.join(
|
plain_fields=plain_fields, join_fieldmap=join_fieldmap)
|
||||||
models.AuditTemplate,
|
|
||||||
models.Audit.audit_template_id == models.AuditTemplate.id)
|
|
||||||
query = query.filter(
|
|
||||||
models.AuditTemplate.uuid == filters['audit_template_uuid'])
|
|
||||||
if 'audit_template_name' in filters:
|
|
||||||
query = query.join(
|
|
||||||
models.AuditTemplate,
|
|
||||||
models.Audit.audit_template_id == models.AuditTemplate.id)
|
|
||||||
query = query.filter(
|
|
||||||
models.AuditTemplate.name ==
|
|
||||||
filters['audit_template_name'])
|
|
||||||
return query
|
|
||||||
|
|
||||||
def _add_action_plans_filters(self, query, filters):
|
def _add_action_plans_filters(self, query, filters):
|
||||||
if filters is None:
|
if filters is None:
|
||||||
filters = []
|
filters = {}
|
||||||
|
|
||||||
if 'state' in filters:
|
plain_fields = ['uuid', 'state', 'audit_id']
|
||||||
query = query.filter_by(state=filters['state'])
|
join_fieldmap = {
|
||||||
if 'audit_id' in filters:
|
'audit_uuid': ("uuid", models.Audit),
|
||||||
query = query.filter_by(audit_id=filters['audit_id'])
|
}
|
||||||
if 'audit_uuid' in filters:
|
|
||||||
query = query.join(models.Audit,
|
return self._add_filters(
|
||||||
models.ActionPlan.audit_id == models.Audit.id)
|
query=query, model=models.ActionPlan, filters=filters,
|
||||||
query = query.filter(models.Audit.uuid == filters['audit_uuid'])
|
plain_fields=plain_fields, join_fieldmap=join_fieldmap)
|
||||||
return query
|
|
||||||
|
|
||||||
def _add_actions_filters(self, query, filters):
|
def _add_actions_filters(self, query, filters):
|
||||||
if filters is None:
|
if filters is None:
|
||||||
filters = []
|
filters = {}
|
||||||
|
|
||||||
|
plain_fields = ['uuid', 'state', 'action_plan_id']
|
||||||
|
join_fieldmap = {
|
||||||
|
'action_plan_uuid': ("uuid", models.ActionPlan),
|
||||||
|
}
|
||||||
|
|
||||||
|
query = self._add_filters(
|
||||||
|
query=query, model=models.Action, filters=filters,
|
||||||
|
plain_fields=plain_fields, join_fieldmap=join_fieldmap)
|
||||||
|
|
||||||
if 'action_plan_id' in filters:
|
|
||||||
query = query.filter_by(action_plan_id=filters['action_plan_id'])
|
|
||||||
if 'action_plan_uuid' in filters:
|
|
||||||
query = query.join(
|
|
||||||
models.ActionPlan,
|
|
||||||
models.Action.action_plan_id == models.ActionPlan.id)
|
|
||||||
query = query.filter(
|
|
||||||
models.ActionPlan.uuid == filters['action_plan_uuid'])
|
|
||||||
if 'audit_uuid' in filters:
|
if 'audit_uuid' in filters:
|
||||||
stmt = model_query(models.ActionPlan).join(
|
stmt = model_query(models.ActionPlan).join(
|
||||||
models.Audit,
|
models.Audit,
|
||||||
@@ -179,13 +373,154 @@ class Connection(api.BaseConnection):
|
|||||||
.filter_by(uuid=filters['audit_uuid']).subquery()
|
.filter_by(uuid=filters['audit_uuid']).subquery()
|
||||||
query = query.filter_by(action_plan_id=stmt.c.id)
|
query = query.filter_by(action_plan_id=stmt.c.id)
|
||||||
|
|
||||||
if 'state' in filters:
|
|
||||||
query = query.filter_by(state=filters['state'])
|
|
||||||
if 'alarm' in filters:
|
|
||||||
query = query.filter_by(alarm=filters['alarm'])
|
|
||||||
|
|
||||||
return query
|
return query
|
||||||
|
|
||||||
|
def _add_efficacy_indicators_filters(self, query, filters):
|
||||||
|
if filters is None:
|
||||||
|
filters = {}
|
||||||
|
|
||||||
|
plain_fields = ['uuid', 'name', 'unit', 'schema', 'action_plan_id']
|
||||||
|
join_fieldmap = JoinMap(
|
||||||
|
action_plan_uuid=NaturalJoinFilter(
|
||||||
|
join_fieldname="uuid", join_model=models.ActionPlan),
|
||||||
|
)
|
||||||
|
|
||||||
|
return self._add_filters(
|
||||||
|
query=query, model=models.EfficacyIndicator, filters=filters,
|
||||||
|
plain_fields=plain_fields, join_fieldmap=join_fieldmap)
|
||||||
|
|
||||||
|
# ### GOALS ### #
|
||||||
|
|
||||||
|
def get_goal_list(self, context, filters=None, limit=None,
|
||||||
|
marker=None, sort_key=None, sort_dir=None):
|
||||||
|
|
||||||
|
query = model_query(models.Goal)
|
||||||
|
query = self._add_goals_filters(query, filters)
|
||||||
|
if not context.show_deleted:
|
||||||
|
query = query.filter_by(deleted_at=None)
|
||||||
|
return _paginate_query(models.Goal, limit, marker,
|
||||||
|
sort_key, sort_dir, query)
|
||||||
|
|
||||||
|
def create_goal(self, values):
|
||||||
|
# ensure defaults are present for new goals
|
||||||
|
if not values.get('uuid'):
|
||||||
|
values['uuid'] = utils.generate_uuid()
|
||||||
|
|
||||||
|
goal = models.Goal()
|
||||||
|
goal.update(values)
|
||||||
|
|
||||||
|
try:
|
||||||
|
goal.save()
|
||||||
|
except db_exc.DBDuplicateEntry:
|
||||||
|
raise exception.GoalAlreadyExists(uuid=values['uuid'])
|
||||||
|
return goal
|
||||||
|
|
||||||
|
def _get_goal(self, context, fieldname, value):
|
||||||
|
try:
|
||||||
|
return self._get(context, model=models.Goal,
|
||||||
|
fieldname=fieldname, value=value)
|
||||||
|
except exception.ResourceNotFound:
|
||||||
|
raise exception.GoalNotFound(goal=value)
|
||||||
|
|
||||||
|
def get_goal_by_id(self, context, goal_id):
|
||||||
|
return self._get_goal(context, fieldname="id", value=goal_id)
|
||||||
|
|
||||||
|
def get_goal_by_uuid(self, context, goal_uuid):
|
||||||
|
return self._get_goal(context, fieldname="uuid", value=goal_uuid)
|
||||||
|
|
||||||
|
def get_goal_by_name(self, context, goal_name):
|
||||||
|
return self._get_goal(context, fieldname="name", value=goal_name)
|
||||||
|
|
||||||
|
def destroy_goal(self, goal_id):
|
||||||
|
try:
|
||||||
|
return self._destroy(models.Goal, goal_id)
|
||||||
|
except exception.ResourceNotFound:
|
||||||
|
raise exception.GoalNotFound(goal=goal_id)
|
||||||
|
|
||||||
|
def update_goal(self, goal_id, values):
|
||||||
|
if 'uuid' in values:
|
||||||
|
raise exception.Invalid(
|
||||||
|
message=_("Cannot overwrite UUID for an existing Goal."))
|
||||||
|
|
||||||
|
try:
|
||||||
|
return self._update(models.Goal, goal_id, values)
|
||||||
|
except exception.ResourceNotFound:
|
||||||
|
raise exception.GoalNotFound(goal=goal_id)
|
||||||
|
|
||||||
|
def soft_delete_goal(self, goal_id):
|
||||||
|
try:
|
||||||
|
self._soft_delete(models.Goal, goal_id)
|
||||||
|
except exception.ResourceNotFound:
|
||||||
|
raise exception.GoalNotFound(goal=goal_id)
|
||||||
|
|
||||||
|
# ### STRATEGIES ### #
|
||||||
|
|
||||||
|
def get_strategy_list(self, context, filters=None, limit=None,
|
||||||
|
marker=None, sort_key=None, sort_dir=None):
|
||||||
|
|
||||||
|
query = model_query(models.Strategy)
|
||||||
|
query = self._add_strategies_filters(query, filters)
|
||||||
|
if not context.show_deleted:
|
||||||
|
query = query.filter_by(deleted_at=None)
|
||||||
|
return _paginate_query(models.Strategy, limit, marker,
|
||||||
|
sort_key, sort_dir, query)
|
||||||
|
|
||||||
|
def create_strategy(self, values):
|
||||||
|
# ensure defaults are present for new strategies
|
||||||
|
if not values.get('uuid'):
|
||||||
|
values['uuid'] = utils.generate_uuid()
|
||||||
|
|
||||||
|
strategy = models.Strategy()
|
||||||
|
strategy.update(values)
|
||||||
|
|
||||||
|
try:
|
||||||
|
strategy.save()
|
||||||
|
except db_exc.DBDuplicateEntry:
|
||||||
|
raise exception.StrategyAlreadyExists(uuid=values['uuid'])
|
||||||
|
return strategy
|
||||||
|
|
||||||
|
def _get_strategy(self, context, fieldname, value):
|
||||||
|
try:
|
||||||
|
return self._get(context, model=models.Strategy,
|
||||||
|
fieldname=fieldname, value=value)
|
||||||
|
except exception.ResourceNotFound:
|
||||||
|
raise exception.StrategyNotFound(strategy=value)
|
||||||
|
|
||||||
|
def get_strategy_by_id(self, context, strategy_id):
|
||||||
|
return self._get_strategy(context, fieldname="id", value=strategy_id)
|
||||||
|
|
||||||
|
def get_strategy_by_uuid(self, context, strategy_uuid):
|
||||||
|
return self._get_strategy(
|
||||||
|
context, fieldname="uuid", value=strategy_uuid)
|
||||||
|
|
||||||
|
def get_strategy_by_name(self, context, strategy_name):
|
||||||
|
return self._get_strategy(
|
||||||
|
context, fieldname="name", value=strategy_name)
|
||||||
|
|
||||||
|
def destroy_strategy(self, strategy_id):
|
||||||
|
try:
|
||||||
|
return self._destroy(models.Strategy, strategy_id)
|
||||||
|
except exception.ResourceNotFound:
|
||||||
|
raise exception.StrategyNotFound(strategy=strategy_id)
|
||||||
|
|
||||||
|
def update_strategy(self, strategy_id, values):
|
||||||
|
if 'uuid' in values:
|
||||||
|
raise exception.Invalid(
|
||||||
|
message=_("Cannot overwrite UUID for an existing Strategy."))
|
||||||
|
|
||||||
|
try:
|
||||||
|
return self._update(models.Strategy, strategy_id, values)
|
||||||
|
except exception.ResourceNotFound:
|
||||||
|
raise exception.StrategyNotFound(strategy=strategy_id)
|
||||||
|
|
||||||
|
def soft_delete_strategy(self, strategy_id):
|
||||||
|
try:
|
||||||
|
self._soft_delete(models.Strategy, strategy_id)
|
||||||
|
except exception.ResourceNotFound:
|
||||||
|
raise exception.StrategyNotFound(strategy=strategy_id)
|
||||||
|
|
||||||
|
# ### AUDIT TEMPLATES ### #
|
||||||
|
|
||||||
def get_audit_template_list(self, context, filters=None, limit=None,
|
def get_audit_template_list(self, context, filters=None, limit=None,
|
||||||
marker=None, sort_key=None, sort_dir=None):
|
marker=None, sort_key=None, sort_dir=None):
|
||||||
|
|
||||||
@@ -193,7 +528,6 @@ class Connection(api.BaseConnection):
|
|||||||
query = self._add_audit_templates_filters(query, filters)
|
query = self._add_audit_templates_filters(query, filters)
|
||||||
if not context.show_deleted:
|
if not context.show_deleted:
|
||||||
query = query.filter_by(deleted_at=None)
|
query = query.filter_by(deleted_at=None)
|
||||||
|
|
||||||
return _paginate_query(models.AuditTemplate, limit, marker,
|
return _paginate_query(models.AuditTemplate, limit, marker,
|
||||||
sort_key, sort_dir, query)
|
sort_key, sort_dir, query)
|
||||||
|
|
||||||
@@ -202,117 +536,78 @@ class Connection(api.BaseConnection):
|
|||||||
if not values.get('uuid'):
|
if not values.get('uuid'):
|
||||||
values['uuid'] = utils.generate_uuid()
|
values['uuid'] = utils.generate_uuid()
|
||||||
|
|
||||||
|
query = model_query(models.AuditTemplate)
|
||||||
|
query = query.filter_by(name=values.get('name'),
|
||||||
|
deleted_at=None)
|
||||||
|
|
||||||
|
if len(query.all()) > 0:
|
||||||
|
raise exception.AuditTemplateAlreadyExists(
|
||||||
|
audit_template=values['name'])
|
||||||
|
|
||||||
audit_template = models.AuditTemplate()
|
audit_template = models.AuditTemplate()
|
||||||
audit_template.update(values)
|
audit_template.update(values)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
audit_template.save()
|
audit_template.save()
|
||||||
except db_exc.DBDuplicateEntry:
|
except db_exc.DBDuplicateEntry:
|
||||||
raise exception.AuditTemplateAlreadyExists(uuid=values['uuid'],
|
raise exception.AuditTemplateAlreadyExists(
|
||||||
name=values['name'])
|
audit_template=values['name'])
|
||||||
return audit_template
|
return audit_template
|
||||||
|
|
||||||
def get_audit_template_by_id(self, context, audit_template_id):
|
def _get_audit_template(self, context, fieldname, value):
|
||||||
query = model_query(models.AuditTemplate)
|
|
||||||
query = query.filter_by(id=audit_template_id)
|
|
||||||
try:
|
try:
|
||||||
audit_template = query.one()
|
return self._get(context, model=models.AuditTemplate,
|
||||||
if not context.show_deleted:
|
fieldname=fieldname, value=value)
|
||||||
if audit_template.deleted_at is not None:
|
except exception.ResourceNotFound:
|
||||||
raise exception.AuditTemplateNotFound(
|
raise exception.AuditTemplateNotFound(audit_template=value)
|
||||||
audit_template=audit_template_id)
|
|
||||||
return audit_template
|
def get_audit_template_by_id(self, context, audit_template_id):
|
||||||
except exc.NoResultFound:
|
return self._get_audit_template(
|
||||||
raise exception.AuditTemplateNotFound(
|
context, fieldname="id", value=audit_template_id)
|
||||||
audit_template=audit_template_id)
|
|
||||||
|
|
||||||
def get_audit_template_by_uuid(self, context, audit_template_uuid):
|
def get_audit_template_by_uuid(self, context, audit_template_uuid):
|
||||||
query = model_query(models.AuditTemplate)
|
return self._get_audit_template(
|
||||||
query = query.filter_by(uuid=audit_template_uuid)
|
context, fieldname="uuid", value=audit_template_uuid)
|
||||||
|
|
||||||
try:
|
|
||||||
audit_template = query.one()
|
|
||||||
if not context.show_deleted:
|
|
||||||
if audit_template.deleted_at is not None:
|
|
||||||
raise exception.AuditTemplateNotFound(
|
|
||||||
audit_template=audit_template_uuid)
|
|
||||||
return audit_template
|
|
||||||
except exc.NoResultFound:
|
|
||||||
raise exception.AuditTemplateNotFound(
|
|
||||||
audit_template=audit_template_uuid)
|
|
||||||
|
|
||||||
def get_audit_template_by_name(self, context, audit_template_name):
|
def get_audit_template_by_name(self, context, audit_template_name):
|
||||||
query = model_query(models.AuditTemplate)
|
return self._get_audit_template(
|
||||||
query = query.filter_by(name=audit_template_name)
|
context, fieldname="name", value=audit_template_name)
|
||||||
try:
|
|
||||||
audit_template = query.one()
|
|
||||||
if not context.show_deleted:
|
|
||||||
if audit_template.deleted_at is not None:
|
|
||||||
raise exception.AuditTemplateNotFound(
|
|
||||||
audit_template=audit_template_name)
|
|
||||||
return audit_template
|
|
||||||
except exc.MultipleResultsFound:
|
|
||||||
raise exception.Conflict(
|
|
||||||
_('Multiple audit templates exist with the same name.'
|
|
||||||
' Please use the audit template uuid instead'))
|
|
||||||
except exc.NoResultFound:
|
|
||||||
raise exception.AuditTemplateNotFound(
|
|
||||||
audit_template=audit_template_name)
|
|
||||||
|
|
||||||
def destroy_audit_template(self, audit_template_id):
|
def destroy_audit_template(self, audit_template_id):
|
||||||
session = get_session()
|
try:
|
||||||
with session.begin():
|
return self._destroy(models.AuditTemplate, audit_template_id)
|
||||||
query = model_query(models.AuditTemplate, session=session)
|
except exception.ResourceNotFound:
|
||||||
query = add_identity_filter(query, audit_template_id)
|
raise exception.AuditTemplateNotFound(
|
||||||
|
audit_template=audit_template_id)
|
||||||
try:
|
|
||||||
query.one()
|
|
||||||
except exc.NoResultFound:
|
|
||||||
raise exception.AuditTemplateNotFound(node=audit_template_id)
|
|
||||||
|
|
||||||
query.delete()
|
|
||||||
|
|
||||||
def update_audit_template(self, audit_template_id, values):
|
def update_audit_template(self, audit_template_id, values):
|
||||||
if 'uuid' in values:
|
if 'uuid' in values:
|
||||||
raise exception.Invalid(
|
raise exception.Invalid(
|
||||||
message=_("Cannot overwrite UUID for an existing "
|
message=_("Cannot overwrite UUID for an existing "
|
||||||
"Audit Template."))
|
"Audit Template."))
|
||||||
|
try:
|
||||||
return self._do_update_audit_template(audit_template_id, values)
|
return self._update(
|
||||||
|
models.AuditTemplate, audit_template_id, values)
|
||||||
def _do_update_audit_template(self, audit_template_id, values):
|
except exception.ResourceNotFound:
|
||||||
session = get_session()
|
raise exception.AuditTemplateNotFound(
|
||||||
with session.begin():
|
audit_template=audit_template_id)
|
||||||
query = model_query(models.AuditTemplate, session=session)
|
|
||||||
query = add_identity_filter(query, audit_template_id)
|
|
||||||
try:
|
|
||||||
ref = query.with_lockmode('update').one()
|
|
||||||
except exc.NoResultFound:
|
|
||||||
raise exception.AuditTemplateNotFound(
|
|
||||||
audit_template=audit_template_id)
|
|
||||||
|
|
||||||
ref.update(values)
|
|
||||||
return ref
|
|
||||||
|
|
||||||
def soft_delete_audit_template(self, audit_template_id):
|
def soft_delete_audit_template(self, audit_template_id):
|
||||||
session = get_session()
|
try:
|
||||||
with session.begin():
|
self._soft_delete(models.AuditTemplate, audit_template_id)
|
||||||
query = model_query(models.AuditTemplate, session=session)
|
except exception.ResourceNotFound:
|
||||||
query = add_identity_filter(query, audit_template_id)
|
raise exception.AuditTemplateNotFound(
|
||||||
|
audit_template=audit_template_id)
|
||||||
|
|
||||||
try:
|
# ### AUDITS ### #
|
||||||
query.one()
|
|
||||||
except exc.NoResultFound:
|
|
||||||
raise exception.AuditTemplateNotFound(node=audit_template_id)
|
|
||||||
|
|
||||||
query.soft_delete()
|
|
||||||
|
|
||||||
def get_audit_list(self, context, filters=None, limit=None, marker=None,
|
def get_audit_list(self, context, filters=None, limit=None, marker=None,
|
||||||
sort_key=None, sort_dir=None):
|
sort_key=None, sort_dir=None):
|
||||||
query = model_query(models.Audit)
|
query = model_query(models.Audit)
|
||||||
query = self._add_audits_filters(query, filters)
|
query = self._add_audits_filters(query, filters)
|
||||||
if not context.show_deleted:
|
if not context.show_deleted:
|
||||||
query = query.filter(~(models.Audit.state == 'DELETED'))
|
query = query.filter(
|
||||||
|
~(models.Audit.state == audit_objects.State.DELETED))
|
||||||
|
|
||||||
return _paginate_query(models.Audit, limit, marker,
|
return _paginate_query(models.Audit, limit, marker,
|
||||||
sort_key, sort_dir, query)
|
sort_key, sort_dir, query)
|
||||||
@@ -340,7 +635,7 @@ class Connection(api.BaseConnection):
|
|||||||
try:
|
try:
|
||||||
audit = query.one()
|
audit = query.one()
|
||||||
if not context.show_deleted:
|
if not context.show_deleted:
|
||||||
if audit.state == 'DELETED':
|
if audit.state == audit_objects.State.DELETED:
|
||||||
raise exception.AuditNotFound(audit=audit_id)
|
raise exception.AuditNotFound(audit=audit_id)
|
||||||
return audit
|
return audit
|
||||||
except exc.NoResultFound:
|
except exc.NoResultFound:
|
||||||
@@ -353,7 +648,7 @@ class Connection(api.BaseConnection):
|
|||||||
try:
|
try:
|
||||||
audit = query.one()
|
audit = query.one()
|
||||||
if not context.show_deleted:
|
if not context.show_deleted:
|
||||||
if audit.state == 'DELETED':
|
if audit.state == audit_objects.State.DELETED:
|
||||||
raise exception.AuditNotFound(audit=audit_uuid)
|
raise exception.AuditNotFound(audit=audit_uuid)
|
||||||
return audit
|
return audit
|
||||||
except exc.NoResultFound:
|
except exc.NoResultFound:
|
||||||
@@ -388,40 +683,26 @@ class Connection(api.BaseConnection):
|
|||||||
message=_("Cannot overwrite UUID for an existing "
|
message=_("Cannot overwrite UUID for an existing "
|
||||||
"Audit."))
|
"Audit."))
|
||||||
|
|
||||||
return self._do_update_audit(audit_id, values)
|
try:
|
||||||
|
return self._update(models.Audit, audit_id, values)
|
||||||
def _do_update_audit(self, audit_id, values):
|
except exception.ResourceNotFound:
|
||||||
session = get_session()
|
raise exception.AuditNotFound(audit=audit_id)
|
||||||
with session.begin():
|
|
||||||
query = model_query(models.Audit, session=session)
|
|
||||||
query = add_identity_filter(query, audit_id)
|
|
||||||
try:
|
|
||||||
ref = query.with_lockmode('update').one()
|
|
||||||
except exc.NoResultFound:
|
|
||||||
raise exception.AuditNotFound(audit=audit_id)
|
|
||||||
|
|
||||||
ref.update(values)
|
|
||||||
return ref
|
|
||||||
|
|
||||||
def soft_delete_audit(self, audit_id):
|
def soft_delete_audit(self, audit_id):
|
||||||
session = get_session()
|
try:
|
||||||
with session.begin():
|
self._soft_delete(models.Audit, audit_id)
|
||||||
query = model_query(models.Audit, session=session)
|
except exception.ResourceNotFound:
|
||||||
query = add_identity_filter(query, audit_id)
|
raise exception.AuditNotFound(audit=audit_id)
|
||||||
|
|
||||||
try:
|
# ### ACTIONS ### #
|
||||||
query.one()
|
|
||||||
except exc.NoResultFound:
|
|
||||||
raise exception.AuditNotFound(node=audit_id)
|
|
||||||
|
|
||||||
query.soft_delete()
|
|
||||||
|
|
||||||
def get_action_list(self, context, filters=None, limit=None, marker=None,
|
def get_action_list(self, context, filters=None, limit=None, marker=None,
|
||||||
sort_key=None, sort_dir=None):
|
sort_key=None, sort_dir=None):
|
||||||
query = model_query(models.Action)
|
query = model_query(models.Action)
|
||||||
query = self._add_actions_filters(query, filters)
|
query = self._add_actions_filters(query, filters)
|
||||||
if not context.show_deleted:
|
if not context.show_deleted:
|
||||||
query = query.filter(~(models.Action.state == 'DELETED'))
|
query = query.filter(
|
||||||
|
~(models.Action.state == action_objects.State.DELETED))
|
||||||
return _paginate_query(models.Action, limit, marker,
|
return _paginate_query(models.Action, limit, marker,
|
||||||
sort_key, sort_dir, query)
|
sort_key, sort_dir, query)
|
||||||
|
|
||||||
@@ -444,7 +725,7 @@ class Connection(api.BaseConnection):
|
|||||||
try:
|
try:
|
||||||
action = query.one()
|
action = query.one()
|
||||||
if not context.show_deleted:
|
if not context.show_deleted:
|
||||||
if action.state == 'DELETED':
|
if action.state == action_objects.State.DELETED:
|
||||||
raise exception.ActionNotFound(
|
raise exception.ActionNotFound(
|
||||||
action=action_id)
|
action=action_id)
|
||||||
return action
|
return action
|
||||||
@@ -457,7 +738,7 @@ class Connection(api.BaseConnection):
|
|||||||
try:
|
try:
|
||||||
action = query.one()
|
action = query.one()
|
||||||
if not context.show_deleted:
|
if not context.show_deleted:
|
||||||
if action.state == 'DELETED':
|
if action.state == action_objects.State.DELETED:
|
||||||
raise exception.ActionNotFound(
|
raise exception.ActionNotFound(
|
||||||
action=action_uuid)
|
action=action_uuid)
|
||||||
return action
|
return action
|
||||||
@@ -504,17 +785,20 @@ class Connection(api.BaseConnection):
|
|||||||
try:
|
try:
|
||||||
query.one()
|
query.one()
|
||||||
except exc.NoResultFound:
|
except exc.NoResultFound:
|
||||||
raise exception.ActionNotFound(node=action_id)
|
raise exception.ActionNotFound(action=action_id)
|
||||||
|
|
||||||
query.soft_delete()
|
query.soft_delete()
|
||||||
|
|
||||||
|
# ### ACTION PLANS ### #
|
||||||
|
|
||||||
def get_action_plan_list(
|
def get_action_plan_list(
|
||||||
self, context, columns=None, filters=None, limit=None,
|
self, context, filters=None, limit=None,
|
||||||
marker=None, sort_key=None, sort_dir=None):
|
marker=None, sort_key=None, sort_dir=None):
|
||||||
query = model_query(models.ActionPlan)
|
query = model_query(models.ActionPlan)
|
||||||
query = self._add_action_plans_filters(query, filters)
|
query = self._add_action_plans_filters(query, filters)
|
||||||
if not context.show_deleted:
|
if not context.show_deleted:
|
||||||
query = query.filter(~(models.ActionPlan.state == 'DELETED'))
|
query = query.filter(
|
||||||
|
~(models.ActionPlan.state == ap_objects.State.DELETED))
|
||||||
|
|
||||||
return _paginate_query(models.ActionPlan, limit, marker,
|
return _paginate_query(models.ActionPlan, limit, marker,
|
||||||
sort_key, sort_dir, query)
|
sort_key, sort_dir, query)
|
||||||
@@ -539,7 +823,7 @@ class Connection(api.BaseConnection):
|
|||||||
try:
|
try:
|
||||||
action_plan = query.one()
|
action_plan = query.one()
|
||||||
if not context.show_deleted:
|
if not context.show_deleted:
|
||||||
if action_plan.state == 'DELETED':
|
if action_plan.state == ap_objects.State.DELETED:
|
||||||
raise exception.ActionPlanNotFound(
|
raise exception.ActionPlanNotFound(
|
||||||
action_plan=action_plan_id)
|
action_plan=action_plan_id)
|
||||||
return action_plan
|
return action_plan
|
||||||
@@ -553,7 +837,7 @@ class Connection(api.BaseConnection):
|
|||||||
try:
|
try:
|
||||||
action_plan = query.one()
|
action_plan = query.one()
|
||||||
if not context.show_deleted:
|
if not context.show_deleted:
|
||||||
if action_plan.state == 'DELETED':
|
if action_plan.state == ap_objects.State.DELETED:
|
||||||
raise exception.ActionPlanNotFound(
|
raise exception.ActionPlanNotFound(
|
||||||
action_plan=action_plan__uuid)
|
action_plan=action_plan__uuid)
|
||||||
return action_plan
|
return action_plan
|
||||||
@@ -614,6 +898,79 @@ class Connection(api.BaseConnection):
|
|||||||
try:
|
try:
|
||||||
query.one()
|
query.one()
|
||||||
except exc.NoResultFound:
|
except exc.NoResultFound:
|
||||||
raise exception.ActionPlanNotFound(node=action_plan_id)
|
raise exception.ActionPlanNotFound(action_plan=action_plan_id)
|
||||||
|
|
||||||
query.soft_delete()
|
query.soft_delete()
|
||||||
|
|
||||||
|
# ### EFFICACY INDICATORS ### #
|
||||||
|
|
||||||
|
def get_efficacy_indicator_list(self, context, filters=None, limit=None,
|
||||||
|
marker=None, sort_key=None, sort_dir=None):
|
||||||
|
|
||||||
|
query = model_query(models.EfficacyIndicator)
|
||||||
|
query = self._add_efficacy_indicators_filters(query, filters)
|
||||||
|
if not context.show_deleted:
|
||||||
|
query = query.filter_by(deleted_at=None)
|
||||||
|
return _paginate_query(models.EfficacyIndicator, limit, marker,
|
||||||
|
sort_key, sort_dir, query)
|
||||||
|
|
||||||
|
def create_efficacy_indicator(self, values):
|
||||||
|
# ensure defaults are present for new efficacy indicators
|
||||||
|
if not values.get('uuid'):
|
||||||
|
values['uuid'] = utils.generate_uuid()
|
||||||
|
|
||||||
|
efficacy_indicator = models.EfficacyIndicator()
|
||||||
|
efficacy_indicator.update(values)
|
||||||
|
|
||||||
|
try:
|
||||||
|
efficacy_indicator.save()
|
||||||
|
except db_exc.DBDuplicateEntry:
|
||||||
|
raise exception.EfficacyIndicatorAlreadyExists(uuid=values['uuid'])
|
||||||
|
return efficacy_indicator
|
||||||
|
|
||||||
|
def _get_efficacy_indicator(self, context, fieldname, value):
|
||||||
|
try:
|
||||||
|
return self._get(context, model=models.EfficacyIndicator,
|
||||||
|
fieldname=fieldname, value=value)
|
||||||
|
except exception.ResourceNotFound:
|
||||||
|
raise exception.EfficacyIndicatorNotFound(efficacy_indicator=value)
|
||||||
|
|
||||||
|
def get_efficacy_indicator_by_id(self, context, efficacy_indicator_id):
|
||||||
|
return self._get_efficacy_indicator(
|
||||||
|
context, fieldname="id", value=efficacy_indicator_id)
|
||||||
|
|
||||||
|
def get_efficacy_indicator_by_uuid(self, context, efficacy_indicator_uuid):
|
||||||
|
return self._get_efficacy_indicator(
|
||||||
|
context, fieldname="uuid", value=efficacy_indicator_uuid)
|
||||||
|
|
||||||
|
def get_efficacy_indicator_by_name(self, context, efficacy_indicator_name):
|
||||||
|
return self._get_efficacy_indicator(
|
||||||
|
context, fieldname="name", value=efficacy_indicator_name)
|
||||||
|
|
||||||
|
def update_efficacy_indicator(self, efficacy_indicator_id, values):
|
||||||
|
if 'uuid' in values:
|
||||||
|
raise exception.Invalid(
|
||||||
|
message=_("Cannot overwrite UUID for an existing "
|
||||||
|
"efficacy indicator."))
|
||||||
|
|
||||||
|
try:
|
||||||
|
return self._update(
|
||||||
|
models.EfficacyIndicator, efficacy_indicator_id, values)
|
||||||
|
except exception.ResourceNotFound:
|
||||||
|
raise exception.EfficacyIndicatorNotFound(
|
||||||
|
efficacy_indicator=efficacy_indicator_id)
|
||||||
|
|
||||||
|
def soft_delete_efficacy_indicator(self, efficacy_indicator_id):
|
||||||
|
try:
|
||||||
|
self._soft_delete(models.EfficacyIndicator, efficacy_indicator_id)
|
||||||
|
except exception.ResourceNotFound:
|
||||||
|
raise exception.EfficacyIndicatorNotFound(
|
||||||
|
efficacy_indicator=efficacy_indicator_id)
|
||||||
|
|
||||||
|
def destroy_efficacy_indicator(self, efficacy_indicator_id):
|
||||||
|
try:
|
||||||
|
return self._destroy(
|
||||||
|
models.EfficacyIndicator, efficacy_indicator_id)
|
||||||
|
except exception.ResourceNotFound:
|
||||||
|
raise exception.EfficacyIndicatorNotFound(
|
||||||
|
efficacy_indicator=efficacy_indicator_id)
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ from sqlalchemy import DateTime
|
|||||||
from sqlalchemy.ext.declarative import declarative_base
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
from sqlalchemy import ForeignKey
|
from sqlalchemy import ForeignKey
|
||||||
from sqlalchemy import Integer
|
from sqlalchemy import Integer
|
||||||
|
from sqlalchemy import Numeric
|
||||||
from sqlalchemy import schema
|
from sqlalchemy import schema
|
||||||
from sqlalchemy import String
|
from sqlalchemy import String
|
||||||
from sqlalchemy.types import TypeDecorator, TEXT
|
from sqlalchemy.types import TypeDecorator, TEXT
|
||||||
@@ -110,13 +111,43 @@ class WatcherBase(models.SoftDeleteMixin,
|
|||||||
Base = declarative_base(cls=WatcherBase)
|
Base = declarative_base(cls=WatcherBase)
|
||||||
|
|
||||||
|
|
||||||
|
class Strategy(Base):
|
||||||
|
"""Represents a strategy."""
|
||||||
|
|
||||||
|
__tablename__ = 'strategies'
|
||||||
|
__table_args__ = (
|
||||||
|
schema.UniqueConstraint('uuid', name='uniq_strategies0uuid'),
|
||||||
|
table_args()
|
||||||
|
)
|
||||||
|
id = Column(Integer, primary_key=True)
|
||||||
|
uuid = Column(String(36))
|
||||||
|
name = Column(String(63), nullable=False)
|
||||||
|
display_name = Column(String(63), nullable=False)
|
||||||
|
goal_id = Column(Integer, ForeignKey('goals.id'), nullable=False)
|
||||||
|
parameters_spec = Column(JSONEncodedDict, nullable=True)
|
||||||
|
|
||||||
|
|
||||||
|
class Goal(Base):
|
||||||
|
"""Represents a goal."""
|
||||||
|
|
||||||
|
__tablename__ = 'goals'
|
||||||
|
__table_args__ = (
|
||||||
|
schema.UniqueConstraint('uuid', name='uniq_goals0uuid'),
|
||||||
|
table_args(),
|
||||||
|
)
|
||||||
|
id = Column(Integer, primary_key=True)
|
||||||
|
uuid = Column(String(36))
|
||||||
|
name = Column(String(63), nullable=False)
|
||||||
|
display_name = Column(String(63), nullable=False)
|
||||||
|
efficacy_specification = Column(JSONEncodedList, nullable=False)
|
||||||
|
|
||||||
|
|
||||||
class AuditTemplate(Base):
|
class AuditTemplate(Base):
|
||||||
"""Represents an audit template."""
|
"""Represents an audit template."""
|
||||||
|
|
||||||
__tablename__ = 'audit_templates'
|
__tablename__ = 'audit_templates'
|
||||||
__table_args__ = (
|
__table_args__ = (
|
||||||
schema.UniqueConstraint('uuid', name='uniq_audit_templates0uuid'),
|
schema.UniqueConstraint('uuid', name='uniq_audit_templates0uuid'),
|
||||||
schema.UniqueConstraint('name', name='uniq_audit_templates0name'),
|
|
||||||
table_args()
|
table_args()
|
||||||
)
|
)
|
||||||
id = Column(Integer, primary_key=True)
|
id = Column(Integer, primary_key=True)
|
||||||
@@ -124,7 +155,8 @@ class AuditTemplate(Base):
|
|||||||
name = Column(String(63), nullable=True)
|
name = Column(String(63), nullable=True)
|
||||||
description = Column(String(255), nullable=True)
|
description = Column(String(255), nullable=True)
|
||||||
host_aggregate = Column(Integer, nullable=True)
|
host_aggregate = Column(Integer, nullable=True)
|
||||||
goal = Column(String(63), nullable=True)
|
goal_id = Column(Integer, ForeignKey('goals.id'), nullable=False)
|
||||||
|
strategy_id = Column(Integer, ForeignKey('strategies.id'), nullable=True)
|
||||||
extra = Column(JSONEncodedDict)
|
extra = Column(JSONEncodedDict)
|
||||||
version = Column(String(15), nullable=True)
|
version = Column(String(15), nullable=True)
|
||||||
|
|
||||||
@@ -139,11 +171,13 @@ class Audit(Base):
|
|||||||
)
|
)
|
||||||
id = Column(Integer, primary_key=True)
|
id = Column(Integer, primary_key=True)
|
||||||
uuid = Column(String(36))
|
uuid = Column(String(36))
|
||||||
type = Column(String(20))
|
audit_type = Column(String(20))
|
||||||
state = Column(String(20), nullable=True)
|
state = Column(String(20), nullable=True)
|
||||||
deadline = Column(DateTime, nullable=True)
|
deadline = Column(DateTime, nullable=True)
|
||||||
audit_template_id = Column(Integer, ForeignKey('audit_templates.id'),
|
audit_template_id = Column(Integer, ForeignKey('audit_templates.id'),
|
||||||
nullable=False)
|
nullable=False)
|
||||||
|
parameters = Column(JSONEncodedDict, nullable=True)
|
||||||
|
interval = Column(Integer, nullable=True)
|
||||||
|
|
||||||
|
|
||||||
class Action(Base):
|
class Action(Base):
|
||||||
@@ -162,8 +196,6 @@ class Action(Base):
|
|||||||
action_type = Column(String(255), nullable=False)
|
action_type = Column(String(255), nullable=False)
|
||||||
input_parameters = Column(JSONEncodedDict, nullable=True)
|
input_parameters = Column(JSONEncodedDict, nullable=True)
|
||||||
state = Column(String(20), nullable=True)
|
state = Column(String(20), nullable=True)
|
||||||
# todo(jed) remove parameter alarm
|
|
||||||
alarm = Column(String(36))
|
|
||||||
next = Column(String(36), nullable=True)
|
next = Column(String(36), nullable=True)
|
||||||
|
|
||||||
|
|
||||||
@@ -178,9 +210,24 @@ class ActionPlan(Base):
|
|||||||
id = Column(Integer, primary_key=True)
|
id = Column(Integer, primary_key=True)
|
||||||
uuid = Column(String(36))
|
uuid = Column(String(36))
|
||||||
first_action_id = Column(Integer)
|
first_action_id = Column(Integer)
|
||||||
# first_action_id = Column(Integer, ForeignKeyConstraint(
|
audit_id = Column(Integer, ForeignKey('audits.id'), nullable=True)
|
||||||
# ['first_action_id'], ['actions.id'], name='fk_first_action_id'),
|
|
||||||
# nullable=True)
|
|
||||||
audit_id = Column(Integer, ForeignKey('audits.id'),
|
|
||||||
nullable=True)
|
|
||||||
state = Column(String(20), nullable=True)
|
state = Column(String(20), nullable=True)
|
||||||
|
global_efficacy = Column(JSONEncodedDict, nullable=True)
|
||||||
|
|
||||||
|
|
||||||
|
class EfficacyIndicator(Base):
|
||||||
|
"""Represents an efficacy indicator."""
|
||||||
|
|
||||||
|
__tablename__ = 'efficacy_indicators'
|
||||||
|
__table_args__ = (
|
||||||
|
schema.UniqueConstraint('uuid', name='uniq_efficacy_indicators0uuid'),
|
||||||
|
table_args()
|
||||||
|
)
|
||||||
|
id = Column(Integer, primary_key=True)
|
||||||
|
uuid = Column(String(36))
|
||||||
|
name = Column(String(63))
|
||||||
|
description = Column(String(255), nullable=True)
|
||||||
|
unit = Column(String(63), nullable=True)
|
||||||
|
value = Column(Numeric())
|
||||||
|
action_plan_id = Column(Integer, ForeignKey('action_plans.id'),
|
||||||
|
nullable=False)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
# Copyright (c) 2015 b<>com
|
# Copyright (c) 2015 b<>com
|
||||||
#
|
#
|
||||||
# Authors: Jean-Emile DARTOIS <jean-emile.dartois@b-com.com>
|
# Authors: Jean-Emile DARTOIS <jean-emile.dartois@b-com.com>
|
||||||
|
# Alexander Chadin <a.chadin@servionica.ru>
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@@ -19,9 +20,92 @@
|
|||||||
import abc
|
import abc
|
||||||
import six
|
import six
|
||||||
|
|
||||||
|
from oslo_log import log
|
||||||
|
|
||||||
|
from watcher.common.messaging.events import event as watcher_event
|
||||||
|
from watcher.decision_engine.messaging import events as de_events
|
||||||
|
from watcher.decision_engine.planner import manager as planner_manager
|
||||||
|
from watcher.decision_engine.strategy.context import default as default_context
|
||||||
|
from watcher.objects import audit as audit_objects
|
||||||
|
|
||||||
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(abc.ABCMeta)
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
class BaseAuditHandler(object):
|
class BaseAuditHandler(object):
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def execute(self, audit_uuid, request_context):
|
def execute(self, audit_uuid, request_context):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def pre_execute(self, audit_uuid, request_context):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def do_execute(self, audit, request_context):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def post_execute(self, audit, solution, request_context):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
|
class AuditHandler(BaseAuditHandler):
|
||||||
|
def __init__(self, messaging):
|
||||||
|
self._messaging = messaging
|
||||||
|
self._strategy_context = default_context.DefaultStrategyContext()
|
||||||
|
self._planner_manager = planner_manager.PlannerManager()
|
||||||
|
self._planner = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def planner(self):
|
||||||
|
if self._planner is None:
|
||||||
|
self._planner = self._planner_manager.load()
|
||||||
|
return self._planner
|
||||||
|
|
||||||
|
@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 = watcher_event.Event()
|
||||||
|
event.type = event_type
|
||||||
|
event.data = {}
|
||||||
|
payload = {'audit_uuid': audit_uuid,
|
||||||
|
'audit_status': status}
|
||||||
|
self.messaging.status_topic_handler.publish_event(
|
||||||
|
event.type.name, payload)
|
||||||
|
|
||||||
|
def update_audit_state(self, request_context, audit, state):
|
||||||
|
LOG.debug("Update audit state: %s", state)
|
||||||
|
audit.state = state
|
||||||
|
audit.save()
|
||||||
|
self.notify(audit.uuid, de_events.Events.TRIGGER_AUDIT, state)
|
||||||
|
|
||||||
|
def pre_execute(self, audit, request_context):
|
||||||
|
LOG.debug("Trigger audit %s", audit.uuid)
|
||||||
|
# change state of the audit to ONGOING
|
||||||
|
self.update_audit_state(request_context, audit,
|
||||||
|
audit_objects.State.ONGOING)
|
||||||
|
|
||||||
|
def post_execute(self, audit, solution, request_context):
|
||||||
|
self.planner.schedule(request_context, audit.id, solution)
|
||||||
|
|
||||||
|
# change state of the audit to SUCCEEDED
|
||||||
|
self.update_audit_state(request_context, audit,
|
||||||
|
audit_objects.State.SUCCEEDED)
|
||||||
|
|
||||||
|
def execute(self, audit, request_context):
|
||||||
|
try:
|
||||||
|
self.pre_execute(audit, request_context)
|
||||||
|
solution = self.do_execute(audit, request_context)
|
||||||
|
self.post_execute(audit, solution, request_context)
|
||||||
|
except Exception as e:
|
||||||
|
LOG.exception(e)
|
||||||
|
self.update_audit_state(request_context, audit,
|
||||||
|
audit_objects.State.FAILED)
|
||||||
|
|||||||