Compare commits

...

428 Commits

Author SHA1 Message Date
Vincent Françoise
1c38637dff Fixed issue on compute nodes iteration
In this changeset, I fixed the issue with the basic server
consolidation strategy to now loop over all compute nodes
as expected instead of stopping after the first one.

Change-Id: If594f0df41e39dfb0ef8f0fce41822018490c4ec
Closes-bug: #1548874
2016-10-24 20:11:19 +00:00
Vincent Françoise
1eb2b517ef Refactored Tests to load scenarios from file
In this changeset, I simplified the logic that is used to create
cluster data model scenarios.

Change-Id: Ia6e138d9897190d3207a70485dc62ccc34087686
2016-10-21 17:32:42 +02:00
licanwei
3e030618fa 'tox -e py27' failed
cfg.CONF.debug should be set False as default,
If it's True, some unittests can't pass.

Change-Id: Ib098250af3aec48aa9d9152e20c80460f3bd641e
Closes-Bug: #1625560
2016-10-20 07:31:51 +00:00
Antoine Cabot
f823345424 Update Watcher description
This change-set update Watcher description
as it is used in the ML to announce each
new release.

Change-Id: I6318107c3e3322a3ef734e90c9e3e0176967ceaf
(cherry picked from commit 8cf233ab03)
2016-09-29 09:02:51 +00:00
Vincent Françoise
19fdd1557e Doc updates
Updated inconsistent docs.

Change-Id: I4be05f662fee6ebdf721ac93dd97611b5a686273
2016-09-28 15:29:30 +00:00
Jenkins
641989b424 Merge "Add constraint target to tox.ini and remove 1 dep" into stable/newton 2016-09-26 09:41:34 +00:00
Vincent Françoise
8814c09087 Fixed GMR configuration issue
GMR was ignoring the config because the conf wasn't passed when
starting any of the Watcher services. This changeset fixes this issue.

Change-Id: If386c5f0459c4278a2a56c8c3185fcdafce673a0
2016-09-21 13:49:39 +00:00
David TARDIVEL
eb4f46b703 Add constraint target to tox.ini and remove 1 dep
This adds a pip install command to tox.ini that is only used when the
tox env is passed with the 'constraints' factor appended onto it.
As such this will not effect developer workflows or current unit tests.

The initial use of this will be in a non-voting job, to verify that the
constrained checks with tox are stable.  DevStack is already running
constrained jobs, as such problems are no expected.

To run a tox with pip using constraints on a developer system a
developer should run the desired tox environment with -constraints.
For example: $(tox -epy27-constraints)
Pip will pull the current version of the upper-constraints.txt file down
from the git.openstack.org, however this method can be overriden to use
a local file setting the environment variable "UPPER_CONSTRAINTS_FILE"
to the local path or a different URL, it is passed directly to pip.

This is currently not enabled in the default tox run, however it is
possible to enable it as a default by adding it to 'envlist' in tox.ini

This also removes requirements.txt from tox.ini deps
This is redundant, per lifeless email:
http://lists.openstack.org/pipermail/openstack-dev/2015-July/069663.html

Change-Id: I79c0ceb46fc980840a8baf5fa4a303bb450bfbec
2016-09-21 12:03:50 +02:00
OpenStack Proposal Bot
2f33dd10c0 Updated from global requirements
Change-Id: Id918c0fd8f2a567f57d3849683282aad5c1c68f8
2016-09-20 13:17:37 +00:00
Thierry Carrez
1a197ab801 Update .gitreview for stable/newton
Change-Id: I410924887299ae8d32247ff1f798ac059f8d5dbd
2016-09-16 15:02:51 +02:00
Jenkins
eeb2788355 Merge "Add rally-jobs folder to get rally support" 2016-09-15 15:15:52 +00:00
Alexander Chadin
af7871831a Add rally-jobs folder to get rally support
This patch set adds rally-jobs folder with watcher.yaml scenario
to get rally support for Watcher project

Change-Id: Icb8eace045d86a9b78b543a8a49fe747f4b00772
2016-09-15 12:54:10 +00:00
Vincent Françoise
fbd9411fd9 Log CDM structure before+after executing strategy
In this changeset, I added debug logs to dump the cluster data model
copy structure (as XML) before and after the execution of the strategy.

By doing so, we can see the cluster data model structure that is
expected after the execution of the corresponding action plan.

Change-Id: I81c23e148a78d9b176154f7620087a322a5bce28
2016-09-15 12:25:44 +00:00
Jenkins
0a0f482f2d Merge "Fixed Tempest test due to notification issues" 2016-09-15 09:55:33 +00:00
Jenkins
0873c26b17 Merge "Use memory mode for sqlite in db test" 2016-09-14 13:39:51 +00:00
Jenkins
af99b3f4eb Merge "Use parameters instead of config for workload stabilization" 2016-09-14 13:33:34 +00:00
Jenkins
4b20e991a1 Merge "Added tests on API hooks and related context" 2016-09-14 12:41:09 +00:00
Jenkins
e907cec90a Merge "When action plan is empty, its state is incorrect" 2016-09-14 12:39:39 +00:00
Jenkins
9f814e6c15 Merge "The default value of 'is_admin_project'" 2016-09-14 12:38:00 +00:00
Jenkins
5ac51efa69 Merge "Implemented GMR plugin to show CDM structures" 2016-09-14 10:09:55 +00:00
Jenkins
0baec1cfc2 Merge "Fixed indentation" 2016-09-14 09:29:51 +00:00
Vincent Françoise
72e6564549 Fixed Tempest test due to notification issues
Change-Id: I33a0764060600b8e3d6bec757669490b9003b345
2016-09-09 16:03:12 +02:00
ChangBo Guo(gcb)
23092b6f84 Use memory mode for sqlite in db test
Config option sqlite_db is deprecated in
0a1bae9859079fb21a03716be947c5f1da6db0a2, and deprecate argumentsqlite_db in method set_defaults in
https://review.openstack.org/#/c/350945/, should use config option
connection instead. For watcher, we test database with sqlite in memory
mode [1] as below:

cfg.CONF.set_override("connection", "sqlite://",
                      group="database", enforce_type=True)

and don't use config option sqlite_db, so remove unused code.

[1]http://docs.sqlalchemy.org/en/rel_1_0/core/engines.html#sqlite

Change-Id: I9b1f995e1b7004bcfe6c5a854c2f83b24e5bfb56
2016-09-09 19:51:15 +08:00
Vincent Françoise
0b276fb602 Added tests on API hooks and related context
In this changeset, I raised the overall test coverage on the Watcher
API hooks.

Change-Id: I718030fdd23e83bf50d22b00828664adf66fc59d
2016-09-08 12:18:07 +02:00
licanwei
a684e70b61 When action plan is empty, its state is incorrect
When the action plan is empty,its state is still 'RECOMMENDED'.
I think it's should be 'SUCCEED'.
Closes-Bug: #1619155

Change-Id: Icd10707c7892089999f8b37775ca62bde47db0c4
2016-09-08 13:37:39 +08:00
Jenkins
414685ab53 Merge "Add documentation for Scoring Module" 2016-09-07 14:20:32 +00:00
Alexander Chadin
2d8650f87a Use parameters instead of config for workload stabilization
This patch set allows to use audit parameters for
workload-stabilization strategy and makes some little
refactoring.

Closes-Bug: #1620604
Change-Id: I60e34611d4dd001beed31666fd11d2ab11c1723c
2016-09-07 17:15:07 +03:00
Tomasz Kaczynski
dd924dd9d5 Add documentation for Scoring Module
This change is updating the glossary with Scoring Module terms
and adding an information page about implementing scoring engine
plugin.

Partially-Implements: blueprint scoring-module
Change-Id: I411370dcc003ed837d8ce67659ecbfef3548ee11
2016-09-07 08:13:36 +00:00
Jenkins
dd5b5428db Merge "Fix incorrect strings and formatting" 2016-09-06 10:58:16 +00:00
Vincent Françoise
74989fe94e Implemented GMR plugin to show CDM structures
In this changeset, I implemented a small GMR plugin that converts
the cluster data model structure into an XML structure.

Change-Id: I75548952635a0aa3c7dbd6d068831b5765a5db1a
Closes-Bug: #1620551
2016-09-06 12:15:51 +02:00
Michelle Mandel
3b673fe9bd Fix incorrect strings and formatting
There were a few typos and some lines of code that weren't formatted
properly. Now, the typos are fixed and the misformatted code is
corrected.

Change-Id: I6009e44f65b9be1ae9edc806618342c334b2fa92
2016-09-02 14:46:05 -04:00
Bin Zhou
c1cbd9ebf4 Modify use of assertTrue(A in B)
Developers should use assertIn(A, B) instead of assertTrue(A in B ).

TrivialFix

Change-Id: Ie169e731c800b5d77fd231f09ac7f92090287ef3
2016-09-02 12:43:56 +08:00
Gábor Antal
add49f3fcb Fixed indentation
In some places, indentation was wrong.
In this patchset, they are fixed.

Change-Id: Ic66105d53b76cf53fbb4bc7bc19329b83b89687e
2016-09-01 19:00:25 +02:00
licanwei
1f4a46ea5d The default value of 'is_admin_project'
The default value of 'is_admin_project' should be 'True'
As in the file '/oslo_context/context.py':
    def __init__(self,...
                 is_admin_project=True)

Change-Id: Id5e3f7347819deff27c6423679f9964fbb35f047
2016-08-31 15:30:31 +08:00
Jenkins
8a38c4f479 Merge "Add release notes for Newton blueprints" 2016-08-30 07:36:57 +00:00
Jenkins
107cc76cdb Merge "TrivialFix: Remove cfg import unused" 2016-08-29 14:44:40 +00:00
Alexandr Stavitskiy
6e8dc5297e Merge scoring base files
Merge scoring_engine.py and scoring_container.py to base.py

Change-Id: I5cada2c9f7832827c1bccfdea1b0a2138b18bfc9
Closes-Bug: #1617376
2016-08-29 14:59:47 +03:00
Antoine Cabot
7cce4b9ed4 Add release notes for Newton blueprints
Change-Id: Id643dae85b1df86796d27fa885aa7c7303d4f4d8
2016-08-29 12:56:02 +02:00
Cao Xuan Hoang
deb5cb3fc2 TrivialFix: Remove cfg import unused
This patch removes cfg import unused in
watcher/api/controllers/v1/goal.py
watcher/api/controllers/v1/strategy.py
watcher/decision_engine/cluster/history/ceilometer.py
watcher/decision_engine/model/collector/manager.py
watcher/decision_engine/strategy/selection/default.py
watcher/tests/common/test_ceilometer_helper.py
watcher/tests/decision_engine/fake_strategies.py
watcher/tests/decision_engine/strategy/selector/test_strategy_selector.py

Change-Id: I0f30a056c3efa49faed857b6d1001a2367d384ac
2016-08-29 15:01:28 +07:00
Jenkins
3ed44383ab Merge "TrivialFix: Remove logging import unused" 2016-08-29 07:48:35 +00:00
Cao Xuan Hoang
720884cd55 TrivialFix: Remove logging import unused
This patch removes logging import unused in
watcher/applier/manager.py
watcher/applier/rpcapi.py
watcher/decision_engine/goal/base.py
watcher/decision_engine/model/notification/base.py
watcher/decision_engine/model/notification/filtering.py

Change-Id: I0b967e4931223b3b7e9459fb1483ed8185a1a7a0
2016-08-29 12:46:02 +07:00
gecong1973
249cd11533 Remove unused LOG
delete unused LOG in some files

Change-Id: I38f2a91d2e6b24b14e1d46fd8e9a5f87ea2f3171
2016-08-29 10:03:33 +08:00
Jenkins
f42106ca34 Merge "Add unit tests for continuous.py" 2016-08-26 17:44:30 +00:00
Jenkins
359debb6b0 Merge "Update configuration section for notifications" 2016-08-26 17:22:29 +00:00
Jenkins
dbcab41fae Merge "Doc on how to add notification endpoints" 2016-08-26 17:22:22 +00:00
Jenkins
28ff52f8ba Merge "Notification and CDM partial update" 2016-08-26 17:01:23 +00:00
Jenkins
781eb64c6a Merge "Added start/end date params on ceilometer queries" 2016-08-26 16:29:33 +00:00
Jenkins
45a0bda1ae Merge "Fix loading of plugin configuration parameters" 2016-08-26 16:26:17 +00:00
Jenkins
190d5ae899 Merge "Remove unreachable line" 2016-08-26 16:16:44 +00:00
David TARDIVEL
e4ba59e130 Update configuration section for notifications
Watcher consumes now notications sent by Nova services.
We have to configure Nova to publish its notifications into
the dedicated Watcher notification queue.

Change-Id: I29f2fa12dfe3a7ce0b014778109a08bbe78b4679
Partially-Implements: blueprint cluster-model-objects-wrapper
2016-08-26 15:57:54 +00:00
Vincent Françoise
f238167edc Doc on how to add notification endpoints
In this changeset, I updated the CDMC plugin documentation to explain
how to implement and register new notification endpoints.

Change-Id: Ib8c014e82051647edef5c1272f63429f76673227
Partially-Implements: blueprint cluster-model-objects-wrapper
2016-08-26 17:46:50 +02:00
Vincent Françoise
77b7fae41e Notification and CDM partial update
In this changeset, I implemented the notification handling (Rx only)
system for consuming incoming notifications, more especially the Nova
ones. The notifications handlers also contain the logic which
incrementally updates the Compute model.

Change-Id: Ia036a5a2be6caa64b7f180de38821b57c624300c
Partially-implements: blueprint cluster-model-objects-wrapper
2016-08-26 17:46:48 +02:00
Jenkins
8c23c08776 Merge "Check unspecified parameters create audit" 2016-08-26 14:53:17 +00:00
Viacheslav Samarin
103f541abd Remove unreachable line
This patch set removes unreachable line from nova_helper.py

Change-Id: I0befe271cc244b73fb9f4d79cc1d04b951b67135
Closes-Bug: #1617354
2016-08-26 17:43:59 +03:00
Jenkins
5fe6281e5c Merge "Correct watcher reraising of exception" 2016-08-26 14:16:45 +00:00
Vincent Françoise
c61793811e Added start/end date params on ceilometer queries
In this changeset, I added the start_time and end_time params
to the Ceilometer helper which can drastically reduce the execution
time of the queries.

Change-Id: I39cb3eef584acfca1b50ff6ec1b65f38750802d2
2016-08-26 14:16:19 +00:00
gengchc2
ecea228c4c Correct watcher reraising of exception
When an exception was caught and rethrown, it should call 'raise'
without any arguments because it shows the place where
an exception occured initially instead of place where
the exception re-raised

Change-Id: I662583cd3cda2424d5a510cae7e815c97a51c2fe
2016-08-26 12:52:13 +00:00
jinquanni
1fafcc5ef1 Check unspecified parameters create audit
Currently, create audit with unspecified parameters will success.
This is not reasonable, we shoud return a FAILED status to
notify the admin user.

Change-Id: Ifbcb3b8d9e736607b05b1eb408ec0f41bdf58a2f
Closes-Bug: #1599879
2016-08-26 19:18:20 +08:00
David TARDIVEL
32c13d00fe Fix loading of plugin configuration parameters
When the load a plugin, we need to reload once the watcher
configuration data, in order to include the plugin parameters in
cfg.CONF data dict. To reload the conf, we just call self.conf().
But every time we call this method, cfg.CONF is cleaned, and we
lost previously loaded parameters. This generated the exception
RequiredOptError and the plugin was not correctly loaded.

To fix it, we have just to add the watcher configuration
filename as argument of self.conf().

Change-Id: Ic2384b68f6d604640127fe06893d0f808aee34b7
Closes-Bug: #1617240
2016-08-26 12:08:45 +02:00
Tomasz Kaczynski
a1cb142009 Add Scoring Module implementation
This change is adding the main logic for the scoring module,
defines entry points for the scoring engine plugins and provides
a watcher-sync tool to enable Watcher database synchronization
without needing to restart any Watcher service.

Partially-Implements: blueprint scoring-module
Change-Id: If10daae969ec27a7008af5173359992e957dcd5e
2016-08-26 07:13:39 +00:00
Jenkins
ab10201c72 Merge "Added strategy ID + Action Plan syncing" 2016-08-25 21:08:08 +00:00
Jenkins
42b45a5949 Merge "Fixed flaky tempest test" 2016-08-25 15:50:38 +00:00
licanwei
da67b246a5 Add unit tests for continuous.py
Add unit tests about functions 'execute_audit'
and 'is_audit_inactive'

Change-Id: If9eb4d95166a486a59cc1535106944243bb7eb30
Closes-Bug: #1616759
2016-08-25 16:52:58 +08:00
OpenStack Proposal Bot
6d41c23e50 Updated from global requirements
Change-Id: I4501b494acc27f9471ea0ba1cf0151242ce17002
2016-08-25 05:06:56 +00:00
Vincent Françoise
7b228348a0 Fixed flaky tempest test
Fixed the flaky test_delete_audit test which was failing if the delete
query was sent before the audit reaches an 'idle' state.

Change-Id: I244643b1f7c9b31baa5c25753e62dd3da0a53544
2016-08-24 14:45:51 +02:00
Jenkins
eb421709d9 Merge "Updated from global requirements" 2016-08-24 11:55:02 +00:00
Andreas Jaeger
e741728eb8 Remove pot files
We do not store pot files anymore in repositories,
instead they are published at
http://tarballs.openstack.org/translation-source/watcher/master/ after
each commit and thus always accurate.

Remove the outdated and obsolete file.

Change-Id: I24f18c0a62f2c5339479d07904fb2ce0a888c696
2016-08-24 08:40:53 +02:00
OpenStack Proposal Bot
35201c8358 Updated from global requirements
Change-Id: I430ed2e1a4e2c29e35f57c6362589eb0ed36465c
2016-08-24 01:40:02 +00:00
Vincent Françoise
6be758bc5a Added strategy ID + Action Plan syncing
In this changeset, I implemented the logic which cancels
any audit or action plan whose goal has been re-synced
(upon restarting the Decision Engine).

Partially Implements: blueprint efficacy-indicator

Change-Id: I95d2739eb552d4a7a02c822b11844591008f648e
2016-08-22 10:08:05 +02:00
Jenkins
5f205d5602 Merge "Fixes to get cluster data model" 2016-08-22 07:51:46 +00:00
Prashanth Hari
9450a7079b Fixes to get cluster data model
Due to the recent code refactor in the cluster data model
compute model is failing to 'get_latest_cluster_data_model'
attribute

Change-Id: Iba848db6d03cf1b682c4000ca48cf846b0ffa79b
Closes-Bug: #1614296
2016-08-19 15:44:09 -04:00
OpenStack Proposal Bot
64f45add5f Updated from global requirements
Change-Id: I1651d322933e085773210920de257572eb142089
2016-08-19 09:12:46 +00:00
Jenkins
3a804ae045 Merge "Fix double self._goal definition" 2016-08-18 17:00:13 +00:00
Jenkins
638fd557dc Merge "Scheduler of decision_engine fix" 2016-08-18 15:44:05 +00:00
Alexandr Stavitskiy
4e3593a71d Fix double self._goal definition
This patch set removes second self._goal definition.

Change-Id: I343b82e1584ab30129e6f15cc9c15cee07250497
Closes-Bug: #1614568
2016-08-18 17:53:09 +03:00
Viacheslav Samarin
01164b0790 Scheduler of decision_engine fix
This patch set renames 'OS-EXT-STS:instance_state' to 'OS-EXT-STS:vm_state'
for correct working of decision_engine scheduler.

Change-Id: I20805a079a991d5f3b8565f52d5f7280c2389bee
Closes-Bug: #1614511
2016-08-18 16:49:20 +03:00
OpenStack Proposal Bot
9933955c7d Updated from global requirements
Change-Id: Id07d8af7e1c717d52005e73bf43baa67f60e2242
2016-08-18 05:58:34 +00:00
Jenkins
efdf6c93fc Merge "Modify libvirt_opts="-d -l" to libvirtd_opts="-d -l"" 2016-08-17 06:28:26 +00:00
Nguyen Hung Phuong
17d2d75abb Clean imports in code
In some part in the code we import objects.
In the Openstack style guidelines they recommand to import
only modules. We need to fix that.

Change-Id: I268c5045f00d25b4dfbf77c1f599c7baca8373ed
Partial-Bug:1543101
2016-08-15 13:43:28 +07:00
zte-hanrong
2e55f4ebee Modify libvirt_opts="-d -l" to libvirtd_opts="-d -l"
Change-Id: Iebb5ab722e416e83abfe48e7b66633645c63f94b
2016-08-13 16:19:54 +08:00
Jenkins
196a7a5457 Merge "Add unit tests for nova_helper.py" 2016-08-09 11:33:17 +00:00
Jenkins
1f72a35896 Merge "Updated from global requirements" 2016-08-09 11:31:04 +00:00
Joe Cropper
ea01031268 Rename (pre/post)condition to (pre/post)_condition
This patch updates the applier's abstract methods to be consistent
with other abstract methods of similar nature.

Also included are a few other miscellaneous changes.

Change-Id: Ia1527c00332011412aba2ab326ec986f1e773001
Closes-bug: 1606634
2016-08-08 08:25:41 -05:00
licanwei
6144551809 Add unit tests for nova_helper.py
The nova helper does not have a sufficient code coverage.
This commit raises its coverage from 39% to 89%.
And unit tests find two bugs.

Closes-Bug: #1607198
Change-Id: Iebb693cd09e512ce44702eddca8ead0c7310b263
Closes-Bug: #1599849
2016-08-08 19:39:58 +08:00
OpenStack Proposal Bot
1b2672a49b Updated from global requirements
Change-Id: Ia64e68758d3335e3ba4ca3c15f00ca6cbb25daa3
2016-08-08 10:49:38 +00:00
Jenkins
27b3c5254d Merge "Removed unused function in uniform airflow" 2016-08-05 01:43:19 +00:00
Jenkins
d69efcbd0f Merge "Updated from global requirements" 2016-08-04 16:02:38 +00:00
Vincent Françoise
31de0e319f Removed unused function in uniform airflow
In this changeset, I removed 2 unused methods.

Change-Id: I2e85ed63739f9bb436d110b54fe2b9ef10962205
2016-08-04 14:55:23 +02:00
zhangyanxian
8145906798 Update the home-page info with the developer documentation
Since there are so many components in openstack,
by describing the URL info in the configuration file,
it is more convenient to find the developer documentation
than from the unified portal

Change-Id: Id6bc554db8bb0cd71851773b9cea71aada4eb9e2
2016-08-04 06:11:42 +00:00
OpenStack Proposal Bot
9d2d2183f5 Updated from global requirements
Change-Id: I4ac7f94a726eb6a1dc1ff8f930f517df0731779d
2016-08-04 02:43:22 +00:00
Vincent Françoise
31c37342cd Refactored the compute model and its elements
In this changeset, I refactored the whole Watcher codebase to
adopt a naming convention about the various elements of the
Compute model so that it reflects the same naming convention
adopted by Nova.

Change-Id: I28adba5e1f27175f025330417b072686134d5f51
Partially-Implements: blueprint cluster-model-objects-wrapper
2016-08-03 12:10:43 +02:00
Jenkins
dbde1afea0 Merge "use parameters to set the threshold" 2016-08-03 08:28:16 +00:00
Jenkins
739b667cbc Merge "Merged metrics_engine package into decision_engine" 2016-08-02 16:34:48 +00:00
Jenkins
671c691189 Merge "Updated DE architecture doc + 'period' param" 2016-08-02 16:34:11 +00:00
Jenkins
32be5de2f9 Merge "Added DE Background Scheduler w/ model sync jobs" 2016-08-02 16:33:51 +00:00
Jenkins
cf01dad222 Merge "Cluster data model collector plugin documentation" 2016-08-02 16:28:56 +00:00
Jenkins
027ce5916c Merge "Loadable Cluster Data Model Collectors" 2016-08-02 16:28:51 +00:00
Jenkins
39227f6e86 Merge "Use more specific asserts" 2016-08-02 14:08:14 +00:00
Gábor Antal
cc2e805780 Use more specific asserts
Many places, there are more specific asserts which can be used.
I replaced the generic assert with more specific ones, where
it was possible.

This change enhances readibility, and on fail, more useful
message is displayed

Change-Id: I86a6baeae2cd36610a2be10ae5085555246c368a
2016-08-02 14:42:29 +02:00
Vincent Françoise
0a6841f510 Merged metrics_engine package into decision_engine
In this changeset, I merged the metrics_engine package into
the decision_engine one alongside the required changes to make
the tests pass.

Change-Id: Iac1cd266a854212bf4fa8b21c744b076c3b834a8
Partially-Implements: blueprint cluster-model-objects-wrapper
2016-08-02 12:07:35 +02:00
Vincent Françoise
4f8591cb02 Updated DE architecture doc + 'period' param
In this changeset, I updated the architecture documentation
about the Decision Engine by adding a new sequence diagram
outlining the CDM synchronization workflow.
I also explained the default 'period' parameter used in the
CDMC plugin.

Change-Id: I09790281ba9117e302ab8e66a887667929c6c261
Partially-Implements: blueprint cluster-model-objects-wrapper
2016-08-02 12:07:35 +02:00
Vincent Françoise
06c6c4691b Added DE Background Scheduler w/ model sync jobs
In this changeset, I implemented a background scheduler service
for Watcher and more particularly for the Decision Engine where
I made it create 2 types of job per cluster data model collector
plugin:

- An initial job that is asynchronously executed upon starting the
  Decision Engine
- A periodical job that gets triggered every configurable interval
  of time

Change-Id: I3f5442f81933a19565217b894bd86c186e339762
Partially-Implements: bluprint cluster-model-objects-wrapper
2016-08-02 12:07:35 +02:00
Vincent Françoise
b94677c3ef Cluster data model collector plugin documentation
In this changeset, I wrote down the documentation on how to implement
a cluster data model collector plugin for Watcher.

This documentation corresponds to part 1 of the associated
specification.

Change-Id: Iac72b933df95252163033cd559d13348075a9b16
Partially-Implements: blueprint cluster-model-objects-wrapper
2016-08-02 12:07:35 +02:00
Vincent Françoise
5a2a94fbec Loadable Cluster Data Model Collectors
In this changeset, I made BaseClusterDataModelCollector instances
pluggable. This corresponds to "part 1" of the work items detailed
in the specifications.

Change-Id: Iab1c7e264add9e2cbbbb767e3fd6e99a0c22c691
Partially-Implements: blueprint cluster-model-objects-wrapper
2016-08-02 12:07:35 +02:00
Jenkins
553c5a6c4b Merge "Add scoring engines to database and API layers" 2016-08-02 08:09:39 +00:00
Jenkins
5ffb7df20c Merge "Implement goal_id, strategy_id and host_aggregate into Audit api" 2016-08-02 07:59:46 +00:00
OpenStack Proposal Bot
61e581f45e Updated from global requirements
Change-Id: I5d8a4f1e4c3b72e248181ba4da5bd0e6f17c164d
2016-08-01 18:49:15 +00:00
Tomasz Kaczynski
26d84e353e Add scoring engines to database and API layers
A Scoring Module needs to expose a list of available
scoring engines through API and Watcher CLI. This list
is stored in database and synchronized by Decision Engine.

Partially-Implements: blueprint scoring-module
Change-Id: I32168adeaf34fd12a731204c5b58fe68434ad087
APIImpact
2016-08-01 12:40:33 +00:00
Jenkins
b2656b92c4 Merge "Add installation from Debian packages section" 2016-07-29 01:55:50 +00:00
Michael Gugino
52ffc2c8e2 Implement goal_id, strategy_id and host_aggregate into Audit api
Modifying the api controller for audit objects to allow
creation of audit objects by specifying either an
audit_template uuid/id and/or a goal_id.

strategy_id is optional.

Partially Implements: blueprint persistent-audit-parameters
Change-Id: I7b3eae4d0752a11208f5f92ee13ab1018d8521ad
2016-07-28 20:12:52 -04:00
junjie huang
5dee934565 use parameters to set the threshold
use parameters to set the threshold for strategies
of Uniform Airflow,Workload balance.

Change-Id: I5a547929eb1e2413468e9a5de16d3fd42cabadf9
Close-bug: #1600707
2016-07-28 11:28:41 +00:00
OpenStack Proposal Bot
0769e53f93 Updated from global requirements
Change-Id: I60b1bc77b3819d8fadddbd13a55b00da8e206b2f
2016-07-28 06:09:16 +00:00
Jenkins
8db3427665 Merge "Add hacking checks to watcher" 2016-07-27 09:10:06 +00:00
Jenkins
35dfa40b08 Merge "Add Python 3.5 classifier and venv" 2016-07-27 09:09:55 +00:00
Jenkins
a98636cd09 Merge "Fixed Basic optim tempest test" 2016-07-26 09:45:18 +00:00
Vincent Françoise
107bd0be54 Fixed Basic optim tempest test
In this changeset, I made some fixes in order to make the
multinode test pass on the gate.

Change-Id: I2433748a78c87b15893ea69964561955b478eebd
2016-07-26 10:43:07 +02:00
Jenkins
95917bc147 Merge "Fix 2 occurrences of typo: "occured" --> "occurred"" 2016-07-26 07:23:23 +00:00
Jenkins
cc1f66cd6a Merge "Update docs links to docs.openstack.org" 2016-07-26 06:29:41 +00:00
Ralf Rantzau
926b790747 Fix 2 occurrences of typo: "occured" --> "occurred"
Change-Id: Ie25223e8aebdc05bc0f19b9df4805dfb942f1818
2016-07-25 14:21:21 -07:00
Drew Thorstensen
7d704dbeec Add hacking checks to watcher
The hacking checks enforce during the pep8 run functional validations of
the code to ensure deeper filters and code consistency.  This change set
adds the hacking checks to the wathcer project.  These checks were
seeded from the neutron project, which had a good set of base defaults.

This change set also updates the watcher project to be compliant with
these new hacking checks.

Change-Id: I6f4566d384a7400bddf228aa127a53e6ecc82c2e
2016-07-25 07:52:45 -04:00
Tin Lam
5c08095417 Update docs links to docs.openstack.org
Changed reference from https://factory.b-com.com to
http://docs.openstack.org.

Change-Id: Ifb8b09ff9df5c67e49b594b7373f11082e00029a
Partial-Bug: #1595180
2016-07-21 13:37:16 -05:00
Jenkins
65d3a4d75e Merge "Updated from global requirements" 2016-07-21 13:29:16 +00:00
Jenkins
6727d53072 Merge "Fix dict.keys() PY3 compatible" 2016-07-21 13:02:16 +00:00
Swapnil Kulkarni (coolsvap)
b579a41f30 Remove discover from test-requirements
It's only needed for python < 2.7 which is not supported

Change-Id: Ib3f24755d50ef9466e5bb35c10ea57ac28ac7a34
2016-07-21 07:47:17 +00:00
OpenStack Proposal Bot
051810dfa2 Updated from global requirements
Change-Id: Ie246370342759d9e3935b7a6e802c94e33ad3d6a
2016-07-21 04:12:17 +00:00
Jenkins
8ab80894c3 Merge "Fix typos and messages in strategies" 2016-07-20 23:58:26 +00:00
Joe Cropper
6730202151 Fix typos and messages in strategies
This patch fixes various typos and other nits in the strategies.  It
also updates some of the log messages to be a little more operator
friendly.

Change-Id: Ic9268c6d7376dad215a1a40798485b1d836ba7ae
Closes-Bug: #1604248
2016-07-20 14:19:45 -07:00
Jenkins
a6d0eaa4c4 Merge "Update executor to eventlet" 2016-07-19 19:41:22 +00:00
Jenkins
2e04ba6b64 Merge "There are some spelling errors in the code." 2016-07-19 19:41:13 +00:00
Muzammil Mueen
fd7c41fba2 Remove unused columns parameters in watcher/db/api
In watcher/db/api.py, some abstract methods are specifying a 'columns'
parameter that is actually ignored in db/sqlalchemy/api.py. Since we
do not need this parameter, realignment was done for the signatures
of these methods, by removing the 'column' parameter (and its
docstring) from every single one of the following methods.

get_audit_template_list
get_audit_list
get_action_list
get_action_plan_list

Closes-Bug: #1597641
Change-Id: If706e24d5714f0139fd135bdc41d17d0e431e302
2016-07-19 09:51:41 -07:00
licanwei
aef1eba5df test_context_hook_before_method failed
gate-watcher-python27 FAILURE
The reason is that oslo_context.context.__init__()
added parameter 'domain_name'.
There are some mismatching in file '/watcher/common/context.py'

Change-Id: If0be05943e7c89788d6ccba3385474ccb036e6f5
Closes-Bug: #1604214
2016-07-19 10:58:14 +08:00
weiweigu
cd60336e20 Fix dict.keys() PY3 compatible
The dict.keys()[0] will raise a TypeError in PY3,
as dict.keys() doesn't return a list any more in PY3
but a view of list.

Change-Id: If15a153c9db9b654e761f8ad50d5d66a427efa4e
Closes-Bug: #1583419
2016-07-14 13:47:29 +08:00
Jenkins
8635beb045 Merge "Fix unittest in test_api.py" 2016-07-13 13:21:27 +00:00
Jenkins
ff1af61c1b Merge "Update unitaty tests to verify strategy and goal name" 2016-07-13 12:54:45 +00:00
Jenkins
00adcb84f6 Merge "Optimize local.conf.controller file to enable watche-dashboard" 2016-07-13 12:45:30 +00:00
zhangyanxian
e7a1ba7e74 There are some spelling errors in the code.
Change-Id: Ifb88a13783fc12258489abb4caabca1b1038a77d
2016-07-13 02:36:47 +00:00
Yatin Kumbhare
1b0801a0c3 Add Python 3.5 classifier and venv
Now that there is a passing gate job, we can claim support for
Python 3.5 in the classifier. This patch also adds the convenience
py35 venv.

Change-Id: Idf6cd632bcb6f4f61dba65fedc9309d0184f46b7
2016-07-12 21:23:43 +05:30
David TARDIVEL
a68eb0d619 Update unitaty tests to verify strategy and goal name
strategy and goal name must be synched with entry point names.

Change-Id: I776ed98f5c13d176fdb0b8677d609fcb1ec9a235
Closes-Bug: #1602262
2016-07-12 16:54:38 +02:00
David TARDIVEL
517f1d083e Bad goal and strategy name for Airflow Optimization
The goal name 'airflow optimization' must be synched with the
entry point name.
The strategy name 'uniform airflow' must be synched with the
entry point name.

Partial-Bug: #1602262

Change-Id: Ibc6f0a7c5047e57549910818200c323b878152b0
2016-07-12 15:45:59 +02:00
licanwei
76a61e234a Fix unittest in test_api.py
tox -e py27 fails with this error:
socket.error: [Error 98] Address already in use

Change-Id: I1cf4a000fb82228bed6a7da61feed48ab679b891
Closes-Bug: #1599556
2016-07-12 17:02:43 +08:00
Jenkins
f4b631ebb3 Merge "Add new documentation section for Watcher policies rules" 2016-07-12 08:14:33 +00:00
Jenkins
e78610e7d1 Merge "Make default Planner generic to handle new action" 2016-07-12 07:35:21 +00:00
Jenkins
405cde2ff9 Merge "Uniform Airflow migration strategy implementation" 2016-07-11 07:56:26 +00:00
Jenkins
b25ecde0db Merge "Add continuously optimization" 2016-07-08 14:07:51 +00:00
Jenkins
e6b4befbf4 Merge "Fix watcher doc build error" 2016-07-08 14:07:39 +00:00
zte-hanrong
dcf52077a4 Optimize local.conf.controller file to enable watche-dashboard
Change-Id: I51cfca4d55453710685624d5cf16a92271947b6a
2016-07-08 10:06:10 +08:00
Alexander Chadin
1de00086f5 Add continuously optimization
This patch set adds implementation for CONTINUOUS type
of audit.

Change-Id: I5f4ec97b2082c8a6b3ccebe36b2a343fa4a67d19
Implements: blueprint continuously-optimization
2016-07-07 19:17:17 +03:00
Jenkins
518b4c82f1 Merge "Documentation for strategy parameters" 2016-07-07 15:10:02 +00:00
Jenkins
4423fdd231 Merge "Add Desktop Service Store to .gitignore file" 2016-07-07 15:09:53 +00:00
Jenkins
b1c6ea71a4 Merge "Fix field type to audit_type" 2016-07-07 08:57:44 +00:00
Alexander Chadin
533a4c7a34 Add Desktop Service Store to .gitignore file
This patch set adds Desktop Service Store files to .gitignore to
ignore them.

Change-Id: Iff3221eea6b0b3f51dd32097f762252b4a4ffa2d
2016-07-07 11:05:56 +03:00
Jenkins
d61c4c12d1 Merge "Fix failing Tempest tests" 2016-07-07 06:58:13 +00:00
Edwin Zhai
1d3891e1e4 Documentation for strategy parameters
Add docs of how to define and input parameters of strategy.

Change-Id: I918305b89721d141b7f37630459e6cf5999f4211
Partially-Implements: optimization-threshold
2016-07-07 00:03:43 +00:00
OpenStack Proposal Bot
f13d6dd805 Updated from global requirements
Change-Id: Ie17905661161bb34b9da611201e447117cb04544
2016-07-06 16:42:29 +00:00
Jenkins
81bdc51e21 Merge "Add policies for API access control to watcher project." 2016-07-06 16:17:43 +00:00
David TARDIVEL
ff4375337e Add installation from Debian packages section
Debian experimental packages are now available for Watcher.
I added a new section to help users to quickly deploy these
packages and test them.

Change-Id: Ib920b7dbf968c36941c0d30c9001bc150df543f8
2016-07-06 15:58:50 +00:00
David TARDIVEL
e7bbf1d1b9 Add new documentation section for Watcher policies rules
We can now use policies to allow or not users to invoke Watcher API
methods. I added, in the Admin guide section, a new documentation
introducing watcher policy rules.

Change-Id: Ie1f8a9e15561756f23242c6f6b0d53cdacf3505a
blueprint: watcher-policies
2016-07-06 15:42:12 +00:00
David TARDIVEL
21d1610071 Update executor to eventlet
Default 'blocking' executor handles only one message at once.
'eventlet' executor is recommended.

Change-Id: Id738d0462fbb3c7fd6c78ee2f0dd0f1e79131ca7
Closes-Bug: #1517843
2016-07-06 17:40:55 +02:00
Jenkins
e34198f35c Merge "Fix link error in base-setup.rst" 2016-07-06 14:51:17 +00:00
zte-hanrong
bc06a7d419 Add policies for API access control to watcher project.
Change-Id: Ibdbe494c636dfaeca9cf2ef8724d0dade1f19c7f
blueprint: watcher-policies
2016-07-06 17:38:44 +08:00
Edwin Zhai
1a1d9d09f4 Fix watcher doc build error
Updates the module name to reflect the right defination of efficacy
specification, and adds doc string to some class.

Change-Id: If54e65a5e98521d74d8e19a21dd900ecd37edfe5
Closes-Bug: #1599368
2016-07-06 06:06:00 +00:00
digambar
47ff9bb105 Fix field type to audit_type
There's a number of places where a field of an object is named "type",
which isn't good thing. Actually type is keyword in python, so wherever
I found type used in models/api/object/test, I have fixed it to
audit_type in the audit files

Change-Id: Iea6bd3acb0b2af2a833b3916701aad88f6064bba
Closes-Bug: #1533392
2016-07-06 06:26:15 +05:30
jinquanni
ca5b849f6b Remove duplicate unittest
There are two same unit test in test_default_planner.
We need to remove one.

TrivialFix

Change-Id: Ibeec9348779dafe4c2cec2411306e09e95728ca1
2016-07-05 14:57:47 +08:00
licanwei
2272b515bf Fix link error in base-setup.rst
line 93:An ref:'action plugin <implement_strategy_plugin>
here implement_strategy_plugin should be 'implement_action_plugin'

Change-Id: I3487578b91bd169335ccf336443e6174905728b0
Closes-Bug: #1599029
2016-07-05 14:44:01 +08:00
Vincent Françoise
f6b1b515c4 Fix failing Tempest tests
In this changeset, I fixed the 3 failing tests on the Tempest test
suite.

Change-Id: I3d31ea1c13b340a2a16b41c31198d7be4711400c
Closes-Bug: #1595534
2016-07-04 16:18:59 +02:00
Jenkins
64903ce56c Merge "Enable strategy parameters" 2016-07-04 08:57:54 +00:00
Jenkins
86cbe4bd0c Merge "Added filter operators" 2016-07-04 07:10:50 +00:00
Edwin Zhai
0b29a8394b Enable strategy parameters
Strategy provides parameters to customize algorithm behavior. End user
could query then specify parameters for their requirements.

Change-Id: Id097db5f6e79c94b57674c8e5d55b06098abf18c
Implements-bp: optimization-threshold
2016-07-01 03:01:41 +00:00
Jenkins
ec64203bee Merge "Update Docs links to docs.openstack.org" 2016-06-30 14:36:07 +00:00
Jenkins
dc01e35760 Merge "add dependency for 3rd-party plugins" 2016-06-30 07:18:13 +00:00
Palimariu Marius
117079f81e Update Docs links to docs.openstack.org
This patch updates the wrong documentation links with the new location.

Change-Id: I42b86a225ebb4b4610adf0f7f8c3286d8b24ed04
Closes-Bug: #1595180
2016-06-29 19:46:11 +00:00
licanwei
d03e1d54bd add dependency for 3rd-party plugins
In the base-setup.rst about 3rd-party plugins,
we should add the watcher dependency requirement.

Change-Id: I8f898b2e3e7e30c88f4f14f7bd3ae38b8aa3ebd3
Closes-Bug: #1594705
2016-06-29 17:44:46 +08:00
Jenkins
6bdc4cac82 Merge "Add goal_name field in strategy" 2016-06-28 15:31:00 +00:00
jinquanni
b4c5e2bb81 Make default Planner generic to handle new action
Currently we have to amend the code of the Planner
for each new added Watcher Action. This patch set will
modifying the DefaultPlanner class and  leverage the
plugins-parameters blueprint so that we can configure
weights via the configuration file.

Documentation and unittest also be update in this patch.

Co-Authored-By: Vincent Françoise <vincent.francoise@b-com.com>

Change-Id: Ib794488fcafd1e153a7d7b1f7253686136501872
blueprint: configurable-weights-default-planner
2016-06-28 07:15:36 +08:00
jinquanni
369428c313 Modify IRC weekly meeting time
modify IRC weekly meeting time in contributor.rst

Change-Id: I1e46bbf23e301559e2a92fce387c2d0f578eb814
2016-06-27 20:34:37 +08:00
junjie huang
2381a677b6 Uniform Airflow migration strategy implementation
This is one of the algorithm of Intel thermal POC.
It's based on the Airflow of hypervisors.

Change-Id: I48bbab75b20129d85b1fb15ede823474546fe399
blueprint: uniform-airflow-migration-strategy
2016-06-27 04:46:18 +00:00
OpenStack Proposal Bot
da8b24f483 Updated from global requirements
Change-Id: Ie49cb8d5fe7cee5592e6045a0145a6a7299c614b
2016-06-24 03:19:28 +00:00
Jenkins
d7a7b3ea54 Merge "Add importing modules instead of classes" 2016-06-23 15:39:08 +00:00
Tin Lam
37595e9bb7 Centralize plugin loaders in watcher/applier
Refactor all plugin loaders in watcher/applier into
watcher/applier/loading/default.py.

Change-Id: I0f0a87d4f72ead45d34aca1b14219fd2ede42a6f
Closes-Bug: #1591095
2016-06-23 02:50:55 -05:00
Alexandr Stavitskiy
cb693e4093 Add importing modules instead of classes
This patch removes import of classes and replaces them with import of modules.

Change-Id: Id2502cf96ac7227cf1036cd54a74f3d7acd83479
Closes-Bug: #1594434
2016-06-22 13:32:34 +03:00
Tin Lam
9918f59227 Centralize plugin loaders in decision engine
Refactor planner loader in watcher/decision_engine/planner/loading/default.py
to watcher/decision_engine/loading/default.py.

Change-Id: I3d4f3668d2269b5a77a35f4470a4d1c96c3128dd
Closes-Bug: #1591092
2016-06-21 23:37:38 -05:00
Vladimir Ostroverkhov
07c7ba9b2e Add goal_name field in strategy
This patch set adds goal_name field in strategy
Related-Bug: #1590416.

Change-Id: Ia381dd882b244487f8beec4d04dabddd1ac7dbc7
2016-06-21 22:16:27 +03:00
OpenStack Proposal Bot
73cf5a98ae Updated from global requirements
Change-Id: I9afbb5b257259f5110471972f98144ed6cb3f621
2016-06-21 18:07:32 +00:00
jinquanni
80abcd6fd9 Use disabled/enabled to change service state
The VM workload consolidation strategy sends 'down' instead
of 'disabled' to change nova-compute service state. This patch
will correct it. The same applies for enabling nova-compute service.

Change-Id: I257411ef711b215bd9b56d0bf0479c79a9ef61d8
Closes-Bug: #1591901
2016-06-21 18:25:14 +08:00
Jenkins
e3edc67045 Merge "Check if nova-service is already disabled" 2016-06-21 08:40:17 +00:00
Bruno Grazioli
b3c2d3af1f Check if nova-service is already disabled
This patch updates VM workload consolidation strategy adding a new
condition to check whether nova-compute service is disabled before
creating the action.

Change-Id: I1accbc7bbd62684dce14ca3b35c92121e923a73a
Closes-Bug: #1591927
2016-06-21 09:54:36 +02:00
Tin Lam
4fc3307525 Add bandit in tox -e pep8
Add bandit tox environment and amend pep8 env to run it.
Also, fix bandit errors with "0.0.0.0".

Change-Id: Ieb5785abd945663e07c07f0ddd3d9a074004f46a
Closes-Bug: #1594423
2016-06-20 19:03:02 -05:00
Vincent Françoise
77c07a466f Added filter operators
In this changeset, I refactored the DB filter system to support
comparison operators using a django-like syntax.
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.

Here is the list of the supported operators:

- 'eq' (==)
- 'neq' (!=)
- 'gt' (>)
- 'gte' (>=)
- 'lt' (<)
- 'lte' (<=)
- 'in' (in)
- 'notin' (not in)

Change-Id: I53a61d50a3253342a40f0ff87cb5612ed57a3bd1
2016-06-17 10:37:44 +02:00
Jenkins
80867703ba Merge "Added audit_template filter to /audits/detail" 2016-06-17 07:44:53 +00:00
Jean-Emile DARTOIS
7b403c0d3b Fix StrategyContext to use the strategy_id in the Audit Template
This patch fixes the StrategyContext to use the optional
attribute strategy_id.

Change-Id: Ib78581f564282de6cfc7f07495c846615ec1866a
Closed-bug: #1590357
2016-06-15 13:08:28 +02:00
Jenkins
6f2c82316c Merge "Use proper theme for release notes" 2016-06-15 09:05:11 +00:00
Jenkins
b21193aaf2 Merge "Documentation on goal and efficacy" 2016-06-15 09:00:09 +00:00
Jenkins
e1a1cd79f4 Merge "Added efficacy indicators to /action_plans" 2016-06-15 09:00:03 +00:00
Jenkins
019fc498ae Merge "Added pre/post execution methods to strategies" 2016-06-15 08:59:58 +00:00
Jenkins
407a505e73 Merge "Added EfficacyIndicator object" 2016-06-15 08:59:52 +00:00
Jenkins
488c6e3f4e Merge "Added efficacy specification to /goals" 2016-06-15 08:59:46 +00:00
Jenkins
28a2422ceb Merge "Added EfficacyIndicator model in DB" 2016-06-15 08:59:41 +00:00
Jenkins
58b5baaeae Merge "Decoupled Goal from Strategy" 2016-06-15 08:59:38 +00:00
Andreas Jaeger
e8fc4a1bc8 Use proper theme for release notes
Use oslosphinx as theme - watcher is an official project and thus
should use it.

Change-Id: I0ab1ad3e99a92e6b59a4bcb29f216b4af6cbac6a
2016-06-15 10:12:29 +02:00
Andreas Jaeger
921e531adf Fix releasenotes generation
Fix several problems, tox -e releasenotes did not run at all.

Fixes:
* Fix version import so that release note building works.
* There's no stable/mitaka branch for watcher, so remove the mitaka file,
  otherwise release notes build will fail.
* Add _static directory with empty placeholder for sphinx to run.

Change-Id: Ia4dd2f7a7743dfd7f7022be04552c401ade9953f
2016-06-15 09:38:48 +02:00
Jenkins
b5cb6631cc Merge "Add reno for release notes management" 2016-06-13 01:07:10 +00:00
Vincent Françoise
45801cf9c5 Documentation on goal and efficacy
In this changeset, I wrote a documentation detailing how one can
implement a new goal plugin. I also mention to define the efficacy
specification for a given goal.

Partially Implements: blueprint efficacy-indicator

Change-Id: Iba267ae312f248b49d4600504f11678cdc225622
2016-06-10 17:08:28 +02:00
Vincent Françoise
442512cd71 Added efficacy indicators to /action_plans
I this changeset, I added the efficacy indicators both at the DB
and at the API level alongside the associated logic.

Partially Implements: blueprint efficacy-indicator

Change-Id: I824553637621da67966103c1b0c01348b09bd836
2016-06-10 09:37:14 +02:00
Vincent Françoise
2b95a4cbc4 Added pre/post execution methods to strategies
In this changeset, I broke down the execute() method to sequentially
call 3 methods:

- pre_execute()
- do_execute()
- post_execute()

This changeset also removes the cluster model parameter from the
execute() method to now become a `model` property of a strategy which
is lazy loaded whenever needed.

Partially Implements: blueprint efficacy-indicator

Change-Id: I2f697938db693acfa95b2c2fbecfdc1b733c93fd
2016-06-10 09:37:11 +02:00
Vincent Françoise
84b12f8f1e Added EfficacyIndicator object
In this changeset, I created the Watcher object for the efficacy
indicators as well as the API object which will be embedded into the
/action_plans endpoint.

Partially Implements: blueprint efficacy-indicator

Change-Id: Ibbe47613317d51a3829fe9de12540c048e8d7117
2016-06-10 09:37:08 +02:00
Vincent Françoise
f665d83657 Added efficacy specification to /goals
In this changeset, I added the new Efficacy, EfficacySpecification
and IndicatorSpecification classes which are the main components for
computing an efficacy.

Partially Implements: blueprint efficacy-indicator

Change-Id: I3a1d62569de2dd6bb6f9a52f6058313fa2b886ce
2016-06-10 09:36:59 +02:00
Antoine Cabot
5e16e032be Add reno for release notes management
Change-Id: I81337ca27fa639da74cb34a874fbbc6248026c34
2016-06-09 11:01:20 +02:00
Vincent Françoise
eab47bf182 Added EfficacyIndicator model in DB
In this changeset, I created a new model named EfficacyIndicator
which is responsible for storing information regarding the efficacy
indicators that were computed by the strategy in its solution. Every
efficacy indicator should relate to a single Action Plan.

Partially Implements: blueprint efficacy-indicator

Change-Id: Ifc14ea5e16e92f032d7912c9b3fdc270af79cab6
2016-06-08 14:05:01 +02:00
Vincent Françoise
2544327979 Decoupled Goal from Strategy
In this changeset, I decoupled the notion of Goal from the Strategy
by making it a distinct object. Goals are plugins that can be loaded
just like for the strategies.

Partially Implements: blueprint efficacy-indicator

Change-Id: I4378dccd508170b305aa968843228bbc8af78895
2016-06-08 14:00:43 +02:00
Antoine Cabot
2412df4b6c Fix broken link in doc
Change-Id: I4144a9b9d4eec4445e21749a8e71597bb1ad375e
2016-06-08 09:21:52 +02:00
Jenkins
d811dd93ae Merge "Added missing config section for autogeneration" 2016-06-07 13:34:51 +00:00
Vincent Françoise
cb342b45b7 Added missing config section for autogeneration
In this changeset, I added the missing entries for the configuration
sample file generation:

- oslo.reports
- oslo.cache
- oslo.concurrency
- oslo.policy
- oslo.service.periodic_task
- oslo.service.service
- oslo.service.wsgi

Change-Id: I826f5cc1185d75f545c96242022a07089cf3042e
2016-06-06 17:47:13 +02:00
OpenStack Proposal Bot
1a4d5a3df3 Updated from global requirements
Change-Id: I65fc08c423a43aaf5b80d5728038b91c8821da42
2016-06-03 18:21:04 +00:00
Vincent Françoise
8b8d2f01fe Added audit_template filter to /audits/detail
In this changeset, I added the 'audit_template' filter to the
/audits/detail endpoint so it has the same set of parameters as
the /audits endpoint.

Change-Id: I2568be18854bd97d4b7879532cb36a7cac532719
2016-06-03 15:05:59 +02:00
Alexander Chadin
ca37358cac Add fix for hardware.cpu.util meter in sd-strategy
This patch set removes normalizing for hardware.cpu.util meter
since the values that comes from ceilometer hardware.cpu.util
are already normalized.

Closes-Bug: #1588257

Change-Id: I9494f2cc9bbaa6dfd168fb515f679eb6d7f2398a
2016-06-02 13:12:48 +03:00
Jenkins
46091385d8 Merge "Add fix for __init__() error" 2016-06-02 08:46:55 +00:00
Jenkins
a457707e56 Merge "Replace assertEqual(None, *) with assertIsNone in tests" 2016-06-02 07:59:39 +00:00
Jenkins
6c922b43ea Merge "Updated tempest test creds retrieval mechanism" 2016-06-02 07:53:11 +00:00
Alexander Chadin
5d66f66050 Add fix for __init__() error
This patch set fixes __init__() got multiple values
for keyword argument osc error.

Closes-Bug: #1587824

Change-Id: Ieaa1250774ec0fdab5450fe9c3962bb3d1f4136b
2016-06-02 10:48:28 +03:00
Jenkins
e7e50b3955 Merge "Remove direct access to dbapi" 2016-06-02 03:10:31 +00:00
Vincent Françoise
d9a2b8809b Updated tempest test creds retrieval mechanism
Following https://review.openstack.org/#/c/263376/, I update the
way Watcher tempest test retrieve the admin credentials.

Change-Id: I9633752ff69a27edf8912227ea2fd24f358dec7e
2016-05-30 11:45:10 +02:00
Jenkins
3d398b4d22 Merge "Documentation for plugins-parameters" 2016-05-30 09:41:53 +00:00
Jenkins
585fbeb9ee Merge "Watcher plugins table in Guru meditation reports" 2016-05-30 09:41:49 +00:00
Jenkins
e9f237dc80 Merge "Enabled config parameters to plugins" 2016-05-30 09:40:30 +00:00
Vincent Françoise
38f6700144 Documentation for plugins-parameters
In this changeset, I updated the documentation to explain how to
add configuration options for each type of plugin.

Partially Implements: plugins-parameters

Change-Id: Ifd373da64207110492b4a62f1cb7f13b029a45d2
2016-05-30 10:28:29 +02:00
junjie huang
30bdf29002 Workload balance migration strategy implementation
This is one of the algorithm of Intel thermal POC.
It's based on the VM workloads of hypervisors.

Change-Id: I45ab0cf0f05786e6f68025bdd315f38381900a68
blueprint: workload-balance-migration-strategy
2016-05-30 07:52:05 +00:00
Vincent Françoise
e6f147d81d Watcher plugins table in Guru meditation reports
In this changeset, I added the list of all the available plugins
for the current instance of any given Watcher service.

Partially Implements: blueprint plugins-parameters

Change-Id: I58c9724a229712b0322a578f0f89a61b38dfd80a
2016-05-30 09:48:37 +02:00
Vincent Françoise
5aa6b16238 Enabled config parameters to plugins
In this changeset, I added the possibility for all plugins to define
configuration parameters for themselves.

Partially Implements: blueprint plugins-parameters

Change-Id: I676b2583b3b4841c64c862b2b0c234b4eb5fd0fd
2016-05-30 09:48:34 +02:00
Jenkins
dcb5c1f9fc Merge "Add Overload standard deviation strategy" 2016-05-30 07:10:17 +00:00
Jenkins
4ba01cbbcf Merge "Update Watcher documentation" 2016-05-27 15:58:01 +00:00
Jenkins
d91d72d2c2 Merge "Add goal name as filter for strategy list cmd" 2016-05-27 15:51:22 +00:00
Jenkins
083bc2bed4 Merge "Add goal_name & strategy_name in /audit_templates" 2016-05-27 15:51:14 +00:00
Alexander Chadin
9d3671af37 Add Overload standard deviation strategy
The main purpose of this strategy is to choose the pair VM:dest_host that
minimizes the standard deviation in a cluster best.

Change-Id: I95a31b7bcab83411ef6b6e1e01818ca21ef96883
Implements: blueprint watcher-overload-sd
2016-05-27 16:16:36 +03:00
Jenkins
3b88e37680 Merge "Added cold VM migration support" 2016-05-27 12:33:46 +00:00
David TARDIVEL
6eee64502f Add goal name as filter for strategy list cmd
This changeset add the possibility to use the goal name as a
stragegy list filter.

Change-Id: Ibaf45e694f115308f19e9bcd3023fe2e6d1750cd
2016-05-27 11:20:55 +02:00
David TARDIVEL
b9231f65cc Update Watcher documentation
We introduced a new watcher plugin for OpenStack CLI. This patchset
updates accordingly the watcher documentation and schemas.

Partially Implements: blueprint openstackclient-plugin

Change-Id: Ib00469c8645fff21f5ba95951379827dbd359c69
2016-05-27 09:40:06 +02:00
OpenStack Proposal Bot
b9b505a518 Updated from global requirements
Change-Id: Ia90ce890fac40ddb6d38effd022ca71e9a7fc52f
2016-05-26 17:07:34 +00:00
cima
388ef9f11c Added cold VM migration support
Cold migration enables migrating some of the VMs which are not in active state (e.g. stopped). Cold migration can also be used for migrating active VM, although VM is shut down and hence unaccessible while migrating.

Change-Id: I89ad0a04d41282431c9773f6ae7feb41573368e3
Closes-Bug: #1564297
2016-05-24 13:26:45 +02:00
Jenkins
4e3caaa157 Merge "Fixed flaky tempest test" 2016-05-24 09:25:15 +00:00
Vincent Françoise
8c6bf734af Add goal_name & strategy_name in /audit_templates
In this changeset, I added both the 'goal_name' and the 'strategy_name'
field.

Change-Id: Ic164df84d4e23ec75b2b2f4b358cf827d0ad7fa5
Related-Bug: #1573582
2016-05-24 11:09:48 +02:00
sharat.sharma
164a802718 Replace assertEqual(None, *) with assertIsNone in tests
Replace assertEqual(None, *) with assertIsNone in tests to have
more clear messages in case of failure.

Change-Id: I98261ef7cca06447ea9d443a2c287c046f380f77
Closes-Bug: #1280522
2016-05-23 18:05:19 +05:30
Vincent Françoise
277a749ca0 Fix lazy translation issue with watcher-db-manage
In this changeset, I fix the issue caused by the use of lazy
translations within the 'watcher-db-manage purge' subcommand.
This is caused by the PrettyTable dependency which performs
addition operations to format its tables and the __add__ magic
method is not supported by oslo_i18n._message.Message objects.

Change-Id: Idd590e882c697957cfaf1849c3d51b52797230f6
Closes-Bug: #1584652
2016-05-23 14:35:17 +02:00
Vincent Françoise
8401b5e479 Fixed flaky tempest test
In this changeset, I fixed the test_create_audit_with_no_state
tempest test which was randomly failing because of a race condition.

Change-Id: Ibda49944c79fcd406fa81870dbbff6064b5dc4fa
2016-05-23 14:32:44 +02:00
Vincent Françoise
78689fbe3b Removed telemetry tag from tempest tests
Since telemetry was removed from tempest, this changeset removes the
telemetry tags from the watcher integration tests

Change-Id: I6229ee23740c3d92a66fc04c8de8b0ed25911022
2016-05-23 09:22:31 +00:00
OpenStack Proposal Bot
22abaa9c3a Updated from global requirements
Change-Id: I2506d35432748691fb53f8540aac43d1656a67a3
2016-05-21 15:53:55 +00:00
Alexander Chadin
fb82131d85 Fix for statistic_aggregation
This patch set fixes aggregate parameter for statistic_aggregation
function so we may use it with any aggregate function.

Change-Id: If586d656aadd3d098a1610a97a2f315e70351de5
Closes-Bug: #1583610
2016-05-19 16:41:07 +03:00
Jenkins
f6f5079adb Merge "Watcher DB class diagram" 2016-05-18 12:36:17 +00:00
Tin Lam
dbdf690cd0 Remove direct access to dbapi
In line [1], we directly use the dbapi in order to
filter the actions by action_plan_id.

[1] https://github.com/openstack/watcher/blob/master/watcher/applier/default.py#L63

Change-Id: I2c75528f619a5277d46702cc3b4ca4bc7280f990
Closes-Bug: #1534170
2016-05-17 18:27:41 -05:00
Jenkins
f045f5d816 Merge "Updated from global requirements" 2016-05-13 07:06:35 +00:00
Jenkins
b77541deb2 Merge "[nova_helper] get keypair name by every admin users" 2016-05-13 06:33:42 +00:00
OpenStack Proposal Bot
3b9d72439c Updated from global requirements
Change-Id: I0f4fe97bdfa872074964a10535db868354d926da
2016-05-11 17:29:55 +00:00
Jenkins
89aa2d54df Merge "Added .pot file" 2016-05-11 15:39:13 +00:00
Jenkins
86f4cee588 Merge "Remove [watcher_goals] config section" 2016-05-11 15:32:36 +00:00
Jenkins
04ac509821 Merge "Remove watcher_goals section from devstack plugin" 2016-05-11 15:32:35 +00:00
Jenkins
4ba9d2cb73 Merge "Documentation update for get-goal-from-strategy" 2016-05-11 15:32:28 +00:00
Jenkins
a71c9be860 Merge "Updated purge to now include goals and strategies" 2016-05-11 15:32:22 +00:00
Jenkins
c2cb1a1f8e Merge "Syncer now syncs stale audit templates" 2016-05-11 15:32:17 +00:00
Jenkins
79bdcf7baf Merge "Add strategy_id & goal_id fields in audit template" 2016-05-11 15:32:10 +00:00
Jenkins
de1b1a9938 Merge "Refactored Strategy selector to select from DB" 2016-05-11 15:32:07 +00:00
Jenkins
031ebdecde Merge "Added /strategies endpoint in Watcher API" 2016-05-11 15:32:01 +00:00
Jenkins
daabe671c7 Merge "Add Goal in BaseStrategy + Goal API reads from DB" 2016-05-11 15:31:58 +00:00
Jenkins
26bc3d139d Merge "DB sync for Strategies" 2016-05-11 15:31:39 +00:00
Jenkins
4d2536b9b2 Merge "Added Strategy model" 2016-05-11 15:30:46 +00:00
Jenkins
4388780e66 Merge "Added Goal object + goal syncing" 2016-05-11 15:29:21 +00:00
Jenkins
d03a9197b0 Merge "Added Goal model into Watcher DB" 2016-05-11 15:28:28 +00:00
Jean-Emile DARTOIS
43f5ab18ba Fix documentation watcher sql database
This changeset fixes the issue with the parameter watcher-db-manage

Change-Id: I668edd85e3ea40c2a309caacbf68cf35bfd680f7
Closes-Bug: #1580617
2016-05-11 15:58:46 +02:00
Vincent Françoise
209176c3d7 Watcher DB class diagram
In this changeset, I added a class diagram reprensenting the
database schema of Watcher.

Change-Id: I2257010d0040a3f40279ec9db2967f0e69384b62
2016-05-11 15:52:54 +02:00
Vincent Françoise
1a21867735 Added .pot file
In this changeset, I just generate the .pot file for all the new
translations that were added during the implementation of this BP

Partially Implements: blueprint get-goal-from-strategy

Change-Id: I2192508afda037510f8f91092c5cfde0115dae1d
2016-05-11 15:48:09 +02:00
Vincent Françoise
5f6a97148f Remove [watcher_goals] config section
In this changeset, I remove the now unused [watcher_goals] section.

Partially Implements: blueprint get-goal-from-strategy

Change-Id: I91e4e1ac3a58bb6f3e30b11449cf1a6eb18cd0ca
2016-05-11 15:48:09 +02:00
Vincent Françoise
e6b23a0856 Remove watcher_goals section from devstack plugin
In this changeset, I removed the now useless [watcher_goals] section
from the devstack plugin.

Partially Implements: blueprint get-goal-from-strategy

Change-Id: Iaa986f426dc47f6cbd04e74f16b67670e3563967
2016-05-11 15:48:09 +02:00
Vincent Françoise
f9a1b9d3ce Documentation update for get-goal-from-strategy
In this changeset, I updated the Watcher documentation to reflect
the changes that are introduced by this blueprint.

Partially Implements: blueprint get-goal-from-strategy

Change-Id: I40be39624097365220bf7d94cbe177bbf5bbe0ed
2016-05-11 15:48:02 +02:00
Vincent Françoise
ff611544fb Updated purge to now include goals and strategies
In this changeset, I updated the purge script to now take into
account the registered goals and strategies.

Partially Implements: blueprint get-goal-from-strategy

Change-Id: I2f1d58bb812fa45bc4bc6467760a071d8612e6a4
2016-05-11 15:31:02 +02:00
Vincent Françoise
18e5c7d844 Syncer now syncs stale audit templates
In this changeset, I introduce the syncing of audit templates.

Partially Implements: blueprint get-goal-from-strategy

Change-Id: Ie394c12fe51f73eff95465fd5140d82ebd212599
2016-05-11 15:31:02 +02:00
Vincent Françoise
2966b93777 Add strategy_id & goal_id fields in audit template
In this changeset, I updated the 'goal_id' field into the AuditTemplate
to now become a mandatory foreign key towards the Goal model. I also
added the 'strategy_id' field into the AuditTemplate model to be an
optional foreign key onto the Strategy model.

This changeset also includes an update of the /audit_template
Watcher API endpoint to reflect the previous changes.

As this changeset changes the API, this should be merged alongside the
related changeset from python-watcherclient.

Partially Implements: blueprint get-goal-from-strategy

Change-Id: Ic0573d036d1bbd7820f8eb963e47912d6b3ed1a9
2016-05-11 15:31:02 +02:00
Vincent Françoise
e67b532110 Refactored Strategy selector to select from DB
In this changeset, I refactored the strategy selector to now
look into the Watcher DB instead of looking into the configuration
file.

Partially Implements: blueprint get-goal-from-strategy

Change-Id: I2bcb63542f6237f26796a3e5a781c8b62820cf6f
2016-05-11 15:31:01 +02:00
Vincent Françoise
81765b9aa5 Added /strategies endpoint in Watcher API
In this changeset, I added the /strategies endpoint to the Watcher
API service.
This also includes the related Tempest tests.

Partially Implements: blueprint get-goal-from-strategy

Change-Id: I1b70836e0df2082ab0016ecc207e89fdcb0fc8b9
2016-05-11 15:31:01 +02:00
Vincent Françoise
673642e436 Add Goal in BaseStrategy + Goal API reads from DB
In this changeset, I changed the Strategy base class to add new
abstract class methods. I also added an abstract strategy class
per Goal type (dummy, server consolidation, thermal optimization).

This changeset also includes an update of the /goals Watcher API
endpoint to now use the new Goal model (DB entries) instead of
reading from the configuration file.

Partially Implements: blueprint get-goal-from-strategy
Change-Id: Iecfed58c72f3f9df4e9d27e50a3a274a1fc0a75f
2016-05-11 15:31:00 +02:00
Jenkins
1026a896e2 Merge "Log "https" if using SSL" 2016-05-11 13:24:43 +00:00
Vincent Françoise
a3ac26870a DB sync for Strategies
In this changeset, I added the ability to synchronize the strategies
into the Wather DB so that it can later be served through the Watcher
API.

Partially Implements: blueprint get-goal-from-strategy

Change-Id: Ifeaa1f6e1f4ff7d7efc1b221cf57797a49dc5bc5
2016-05-11 15:19:40 +02:00
Vincent Françoise
192d8e262c Added Strategy model
In this changeset, I add the Strategy model as well as the DB
functionalities we need to manipulate strategies.

This changeset implies a DB schema update.

Partially Implements: blueprint get-goal-from-strategy

Change-Id: I438a8788844fbc514edfe1e9e3136f46ba5a82f2
2016-05-11 15:19:40 +02:00
Vincent Françoise
3b5ef15db6 Added Goal object + goal syncing
In this changeset, I added the Goal object into Watcher along with
a sync module that is responsible for syncing the goals with the
Watcher DB.

Partially Implements: blueprint get-goal-from-strategy

Change-Id: Ia3a2032dd9023d668c6f32ebbce44f8c1d77b0a3
2016-05-11 15:19:40 +02:00
Vincent Françoise
be9058f3e3 Added Goal model into Watcher DB
In this changeset, I added the Goal model into Watcher.
This implies a change into the Watcher DB schema

Partially Implements: blueprint get-goal-from-strategy

Change-Id: I5b5b0ffc7cff8affb59f17743e1af0e1277c2878
2016-05-11 15:19:40 +02:00
Jenkins
91951f3b01 Merge "Refactored DE and Applier to use oslo.service" 2016-05-11 13:14:52 +00:00
Jenkins
57a2af2685 Merge "Refactored Watcher API service" 2016-05-11 13:14:29 +00:00
Yosef Hoffman
76e3d2e2f6 Log "https" if using SSL
When starting the Watcher API service, the URI it served to is shown in
a log message. In this log message (in watcher/cmd/api.py) take into
account the case where SSL has been enabled with CONF.api.enable_ssl_api
set to True and format this log message accordingly.

Change-Id: I98541810139d9d4319ac89f21a5e0bc25454ee62
Closes-Bug: #1580044
2016-05-10 11:44:56 -04:00
Jenkins
bd5a969a26 Merge "Remove using of UUID field in POST methods of Watcher API" 2016-04-29 02:57:34 +00:00
zhangguoqing
d61bf5f053 [nova_helper] get keypair name by every admin users
Since the bug #1182965 has been fixed, allow admin users
to view any keypair.

Change-Id: I9cf948701515afd45e6720cfd15cfac6b5866aa5
2016-04-25 21:20:16 +08:00
Alexander Chadin
aaaf3f1c84 Remove using of UUID field in POST methods of Watcher API
This patch set removes the possibility of using UUID field
in POST methods of Watcher API.

Closes-Bug: #1572625

Change-Id: I88a8aa5346e937e3e9409b55da3316cbe1ed832a
2016-04-25 16:05:59 +03:00
Vincent Françoise
eb861f86ab Refactored DE and Applier to use oslo.service
In this PS, I have refactored the Decision Engine and the Applier
to use the oslo service utility.

Change-Id: If29158cc9b5e5e50f6c69d67c232cceeb07084f2
Closes-Bug: #1541850
2016-04-22 10:33:21 +02:00
Vincent Françoise
a9e7251d0d Refactored Watcher API service
This patchset introduces the use of oslo.service to run the
Watcher API service.

Change-Id: I6c38a3c1a2b4dc47388876e4c0ba61b7447690bd
Related-Bug: #1541850
2016-04-22 10:33:21 +02:00
OpenStack Proposal Bot
4ff373197c Updated from global requirements
Change-Id: I04865b9e63d6fc805802b6057ba9750116849c98
2016-04-19 12:30:29 +00:00
Jenkins
87087e9add Merge "Removed unused 'alarm' field" 2016-04-19 08:02:21 +00:00
Larry Rensing
408d6d4650 Removed unused 'alarm' field
The 'alarm' field is currently unused, so it has been removed.

Change-Id: I02fa15b06ed49dbc5dd63de54a9cde601413983c
Closes-Bug: #1550261
2016-04-18 14:12:12 +00:00
Alexander Chadin
e52dc4f8aa Add parameters verification when Audit is being created
We have to check Audit Type and Audit State to make sure
these parameters are in valid status.

Also, we provide default states for the next attributes:

- 'audit_template' is required and should be either UUID or text field
- 'state' is readonly so it raises an error if submitted in POST
  and is set by default to PENDING
- 'deadline' is optional and should be a datetime
- 'type' is a required text field

Change-Id: I2a7e0deec0ee2040e86400b500bb0efd8eade564
Closes-Bug: #1532843
Closes-Bug: #1533210
2016-04-14 15:43:26 +03:00
Jenkins
0f14b7635d Merge "correct the available disk, memory calculating Source data are misused in outlet temperature strategy. This patch fixes it." 2016-04-12 07:08:15 +00:00
junjie huang
bb77641aad correct the available disk, memory calculating
Source data are misused in outlet temperature strategy. This patch
fixes it.

Change-Id: I8ad5c974d7674ddfe6c4c9e3a6e3029d34a400db
Closes-bug: #1569114
2016-04-11 17:53:54 +00:00
Vincent Françoise
77228a0b0a Upgrade Watcher Tempest tests for multinode
Change-Id: I4b84ba9814227776232c8ab883cdaaf411930ee6
2016-04-11 16:49:10 +02:00
Jenkins
1157a8db30 Merge "Fix for deleting audit template" 2016-04-08 18:58:07 +00:00
Jenkins
18354d1b4e Merge "Update .coveragerc to ignore abstract methods" 2016-04-08 18:57:11 +00:00
Larry Rensing
8387cd10de Update .coveragerc to ignore abstract methods
Due to importing modules rather than functions and decorators directly,
@abc.abstract and 'raise NotImplementedError' were added to the
.coveragerc file.  Since abstract methods are not testable, this will
give us a more accurate representation of our coverage.

Change-Id: Id5ed5e1f5e142d10f41ad18d20228399226ec20d
Co-Authored-By: Jin Li <jl7351@att.com>
Closes-Bug: #1563717
2016-04-08 16:59:57 +00:00
OpenStack Proposal Bot
0449bae747 Updated from global requirements
Change-Id: Ieead2a4c784c248bd6af821f5e1e84c5e6cd3b5a
2016-04-07 17:25:39 +00:00
Alexander Chadin
3e07844844 Fix for deleting audit template
We need to update sqlalchemy/api and sqlalchemy/models (and appropriate tests)
to support deleting audit templates and recreating them with the same names.

Change-Id: Icf54cf1ed989a3f2ad689e25be4474b16a3a3eb2
Related-Bug: #1510179
2016-04-07 11:27:53 +03:00
zhangguoqing
a52d92be87 Remove unused logging import and LOG global var
In some modules the global LOG is not used any more. And the import
of logging is not used. This patch removes the unused logging import
and LOG vars.

Change-Id: I794ee719d76f04e70154cf67f726152fbb1ba15a
2016-04-06 10:34:39 +08:00
OpenStack Proposal Bot
96683a6133 Updated from global requirements
Change-Id: Ib98e484bb216eb31b64931db735ced8d1de738a4
2016-04-05 13:44:07 +00:00
Jenkins
46d5094add Merge "Added information on plugin mechanism to glossary" 2016-04-05 07:52:55 +00:00
Jenkins
783c7c0177 Merge "Disabled PATCH, POST and DELETE for /actions" 2016-04-05 07:30:23 +00:00
Jenkins
6d0717199c Merge "Invalid states for Action Plan in the glossary" 2016-04-05 07:27:12 +00:00
cima
8b77e78f3d Added missing support for resource states in unicode format in VM workload consolidation strategy
Unicode type resource state is now handled in the same fashion as resource state specified by general string.

Change-Id: I35ffa09015283b51c935515436735aecbe83a9d6
Closes-Bug: #1565764
2016-04-04 15:17:35 +02:00
Vincent Françoise
22c9c4df87 Disabled PATCH, POST and DELETE for /actions
I removed the POST, PATCH and DELETE verbs from the actions
controller as they should only be modified internally.

Change-Id: Ia72484249240f829423056f66c5c0f9632d02106
Closes-Bug: #1533281
2016-03-30 10:10:28 +02:00
Jenkins
99ff6d3348 Merge "Integrated consolidation strategy with watcher" 2016-03-29 15:36:28 +00:00
Tin Lam
c67f83cce0 Added information on plugin mechanism to glossary
Added extra information regarding the plugin mechanism for:
action, strategy, and Watcher planner.

Change-Id: I9a7523282e229b83c16b06e3806ff795a0699c78
Closes-Bug: #1558470
2016-03-24 18:42:17 -05:00
Larry Rensing
397bb3497e Invalid states for Action Plan in the glossary
The list of possible states for Action Plan objects was outdated, and
was updated to match the state machine diagram.  A reference to the
state machines for Audits and Action Plans were added to the glossary,
and the descriptions of each state were moved to the sections containing
the state machines within the Architecture page.

Change-Id: I27043ad864c02fff50fb31868b27dc4b4897dbd4
Closes-Bug: #1558464
2016-03-24 15:14:42 +00:00
Bruno Grazioli
4c924fc505 Integrated consolidation strategy with watcher
This patch adds a new load consolidation strategy based on a heuristic
algorithm which focuses on measured CPU utilization and tries to
minimize hosts which have too much or too little load.
A new goal "vm_workload_consolidation" was added which executes
the strategy "VM_WORKLOAD_CONSOLIDATION".
This work depends on the implemetation of the bug:
https://bugs.launchpad.net/watcher/+bug/1553124

Change-Id: Ide05bddb5c85a3df05b94658ee5bd98f32e554b0
Implements: blueprint basic-cloud-consolidation-integration
2016-03-24 12:00:01 +01:00
jaugustine
4c5ecc808d Added oslo.context to requirements.txt
Added missing dependency oslo.context to requirements.txt

Change-Id: I88c42fd2381bad55ff499e096a93dcc2cc1d44e5
Closes-Bug: #1560976
2016-03-23 10:45:23 -05:00
Jenkins
64b5a7c3e4 Merge "Updated action-plugin doc to refer to Voluptuous" 2016-03-22 15:17:37 +00:00
Jenkins
40bb92f749 Merge "Remove true/false return from action.execute()" 2016-03-22 01:13:43 +00:00
Jenkins
92bd06cf94 Merge "Remove the watcher sample configuration file" 2016-03-21 13:10:28 +00:00
David TARDIVEL
c9e0dfd3f5 Remove the watcher sample configuration file
Watcher sample configuration file groups parameters from
various projects and the watcher project ones. This makes it
tricky to review updates on configuration parameters.

It is inconvenient for developer if the add/remove/change some
configuration options cause they need to take care about the
config.sample file.

The sample configuration file should be available into HTML doc.

This patchset:
. removes the file /etc/watcher/watcher.conf.sample
. adds an admin script tool to be able to built it, by using tox
. includes a new section 'Watcher sample configuration files' into
  the doc source files
. uses sphinx extension oslo_config.sphinxgenconfig

Change-Id: If2180de3614663f9cbc5396961a8d2175e28e315
Closes-Bug: #1541734
2016-03-21 11:47:29 +01:00
Vincent Françoise
446fe1307a Updated action-plugin doc to refer to Voluptuous
In this patchset, I added a small subsection which highlights the fact
that actions are using Voluptuous Schemas to validate their input
parameters.

Change-Id: I96a6060cf167468e4a3f7c8d8cd78330a20572e3
Closes-Bug: #1545643
2016-03-21 11:33:31 +01:00
Larry Rensing
2836f460e3 Rename variable vm_avg_cpu_util
The variable vm_avg_cpu_util was renamed to host_avg_cpu_util for
clarity, as it was really referring to the host average cpu util.

Change-Id: I7aaef9eb2c8421d01715c86afa36ab67f2fd5f30
Closes-Bug: #1559113
2016-03-18 10:45:08 -05:00
Jenkins
cb9bb7301b Merge "renamed "efficiency" with "efficacy" Closes-Bug:#1558468" 2016-03-18 11:09:12 +00:00
Jenkins
cb644fcef9 Merge "Renamed api.py to base.py in metrics engine" 2016-03-18 10:59:59 +00:00
sai
0a7c87eebf renamed "efficiency" with "efficacy"
Closes-Bug:#1558468

Change-Id: Iaf5f113b0aeb02904e76e7d1e729a93df3554275
2016-03-18 00:06:04 -05:00
Tin Lam
d7f4f42772 Remove true/false return from action.execute()
In watcher/applier/workflow_engine/default.py, we are checking the
return value of action.execute(). As the "TODO" above indicates it
(line 118), we should get rid of this and only flag an action as
failed if an exception was raised during its execute(). We will
need to update the related unit tests.

Change-Id: Ia8ff7abd9994c3504e733ccd1d629cafe9d4b839
Closes-Bug: #1548383
2016-03-16 18:18:55 -05:00
OpenStack Proposal Bot
bdc0eb196a Updated from global requirements
Change-Id: I568d88a71c47e16daa2f34b7dec97137eb7519b8
2016-03-16 13:34:18 +00:00
Jenkins
59427eb0d9 Merge "Refactored check for invalid goal" 2016-03-16 00:25:39 +00:00
Jenkins
b6801b192a Merge "Updated from global requirements" 2016-03-15 21:10:26 +00:00
Jenkins
0a6c2c16a4 Merge "Added Disk Capacity in cluster-data-model" 2016-03-15 16:28:53 +00:00
Vincent Françoise
9a44941c66 Documentation on purge command
This patchset add a new entry for the purge into the Watcher
documentation.

Change-Id: Ifb74b379bccd59ff736bf186bdaaf74de77098f1
Implements: blueprint db-purge-engine
2016-03-14 15:49:45 +01:00
Vincent Françoise
a6508a0013 Added purge script for soft deleted objects
This patchset implements the purge script as specified in its
related blueprint:

- The '--age-in-days' option allows to specify the number of
  days before expiry
- The '--max-number' option allows us to specify a limit on the number
  of objects to delete
- The '--audit-template' option allows you to only delete objects
  related to the specified audit template UUID or name
- The '--dry-run' option to go through the purge procedure without
  actually deleting anything
- The '--exclude-orphans' option which allows you to exclude from the
  purge any object that does not have a parent (i.e. and audit without
  a related audit template)

A prompt has been added to also propose to narrow down the number of
deletions to be below the specified limit.

Change-Id: I3ce83ab95277c109df67a6b5b920a878f6e59d3f
Implements: blueprint db-purge-engine
2016-03-14 15:49:45 +01:00
Vincent Françoise
c3db66ca09 Added Mixin-related filters on DB queries
As a pre-requisite for being able to query the database for objects
that are expired, I need a way to express date comparison on the
'deleted_at' field which is common for every Watcher object. As they
are coming from mixins, I decided to implement these filters with a
syntax borrowed from the Django ORM where the field is suffixed by the
comparison operator you want to apply:

- The '__lt' suffix stands for 'less than'
- The '__lte' suffix stands for 'less than or equal to'
- The '__gt' suffix stands for 'greater than'
- The '__gte' suffix stands for 'greater than or equal to'
- The '__eq' suffix stands for 'equal to'

I also added a 'uuid' filter to later on be able to filter by uuid.

Partially Implements: blueprint db-purge-engine

Change-Id: I763f330c1b8ea8395990d2276b71e87f5b3f3ddc
2016-03-14 15:46:58 +01:00
Jenkins
5d0fe553c4 Merge "Fixed wrongly used assertEqual method" 2016-03-14 14:43:34 +00:00
OpenStack Proposal Bot
8b8239c3d8 Updated from global requirements
Change-Id: Ib9bec0311e8ad6680487631e6707bafe4a259be6
2016-03-09 16:53:45 +00:00
Larry Rensing
920bd502ec Refactored check for invalid goal
When creating a new audit template, the verification of its goal
existence was previously done in watcher/objects/audit_template.py.
This check was moved to api/controllers/v1/audit_template.py, rather
than in the DAO class.

Change-Id: I6efb0657f64c46a56914a946ec78013b9e47331b
Closes-Bug: #1536191
2016-03-09 10:48:16 -06:00
Gábor Antal
c68d33f341 Renamed api.py to base.py in metrics engine
In watcher/metrics_engine/cluster_history/api.py,
we can find the BaseClusterHistory abstract base class.

To follow the same naming convention observed throughout the rest
of the project, I renamed watcher/metrics_engine/cluster_history/api.py
to watcher/metrics_engine/cluster_history/base.py

Change-Id: If18f8db7f0982e47c1998a469c54952670c262f5
Closes-Bug: #1548398
2016-03-09 12:02:05 +01:00
Vincent Françoise
8e8fdbd809 Re-generated the watcher.pot
There are translations that are missing from watcher.pot.
This patchset includes them.

Change-Id: Ia418066b5653b4c81885d3eb150613ba357f9b7b
Related-Bug: #1510189
2016-03-09 11:20:16 +01:00
Bruno Grazioli
681536c8c7 Added Disk Capacity in cluster-data-model
Fetched information of total disk capacity from nova and added a new
resource 'disk_capacity' to NovaClusterModelCollector cluster. Also a
new resource type 'disk_capacity' was added to ResourceType.
https://bugs.launchpad.net/watcher/+bug/1553124

Change-Id: I85750f25c6d2693432da8e5e3a3d0861320f4787
Closes-Bug: #1553124
2016-03-09 08:33:31 +00:00
Tin Lam
083b170083 Removing unicode from README.rst
Removing unicode from README.rst to unblock
the python34 gate failure.

Change-Id: I60da31e22b6a09540c9d6fca659a1b21d931a0b7
Closes-Bug: #1554347
2016-03-08 17:07:53 -06:00
Vincent Françoise
de7b0129a1 Doc on how to set up a thirdparty project
This documentation is a pre-requisite to all plugin documentation
as it guides you through the creation of a project from scratch
instead of simply forcusing on the implementation of the plugin
itself.

Change-Id: Id2e09b3667390ee6c4be42454c41f9d266fdfac2
Related-Bug: #1534639
Related-Bug: #1533739
Related-Bug: #1533740
2016-03-04 12:07:41 +01:00
Jenkins
323fd01a85 Merge "Updated Strategy plugin doc" 2016-03-04 08:27:36 +00:00
Jenkins
c0133e6585 Merge "Remove tests omission from coverage target in tox.ini" 2016-03-04 08:25:30 +00:00
Gábor Antal
63fffeacd8 Remove tests omission from coverage target in tox.ini
In coverage target in tox.ini, there is the following argument:
  --omit="watcher/tests/*"

However, in .coveragerc, the tests are also omitted,
according to line 4 in .coveragerc:
  omit = watcher/tests/*

So the watcher/tests/* directory is omitted twice. As it can be
seen in other modules (e.g.: swift, nova) omitting only in
.coveragerc should be enough.

Change-Id: I72951196a346fb73a90c998138fc91dd171432cd
Closes-Bug: #1552617
2016-03-04 07:36:26 +00:00
Jenkins
bc791f0e75 Merge "add Goal into RESTful Web API (v1) documentation" 2016-03-03 16:45:20 +00:00
David TARDIVEL
6ed417e6a7 add Goal into RESTful Web API (v1) documentation
Change-Id: I94ba5debf6f0ff9562dc95cbc5c646f33881d9e2
Closes-Bug: #1546630
2016-03-03 16:02:56 +00:00
Vincent Françoise
4afefa3dfb Updated Strategy plugin doc
As we modified the way a strategy gets implemented in
blueprint watcher-add-actions-via-conf, this patchset updates the
documentation regarding the implementation of a strategy plugin.

Change-Id: I517455bc34623feff704956ce30ed545a0e1014b
Closes-Bug: #1533740
2016-03-03 16:18:33 +01:00
Jenkins
9fadfbe40a Merge "Doc on how to implement a custom Watcher action" 2016-03-03 15:12:21 +00:00
Jenkins
f278874a93 Merge "Add Watcher dashboard to the list of projects" 2016-03-03 14:34:20 +00:00
Jenkins
1c5b247300 Merge "Doc on how to implement a custom Watcher planner" 2016-03-03 14:19:58 +00:00
Jenkins
547bf5f87e Merge "Improve DevStack documentation for beginners" 2016-03-03 14:19:21 +00:00
Vincent Françoise
96c0ac0ca8 Doc on how to implement a custom Watcher planner
This documentation describes step-by-step the process for implementing
a new planner in Watcher.

Change-Id: I8addba53de69be93730924a58107687020c19c74
Closes-Bug: #1533739
2016-03-03 14:09:41 +00:00
Antoine Cabot
a80fd2a51e Add Watcher dashboard to the list of projects
We provide a list of Watcher related projects
on the generated doc. This commit add links to
the Watcher dashboard project in various locations.

Change-Id: I1993cd5a11d3fcc8ca2c40b1779700359adab4ea
2016-03-03 13:47:24 +00:00
Vincent Françoise
58ea85c852 Doc on how to implement a custom Watcher action
This documentation describes step-by-step the process for implementing
a new action in Watcher.

Change-Id: I978b81cdf9ac6dcf43eb3ecbb79ab64ae4fd6f72
Closes-Bug: #1534639
2016-03-02 14:46:37 +01:00
Jenkins
78f122f241 Merge "RST directive to discover and generate drivers doc" 2016-03-01 23:09:18 +00:00
Jenkins
43eb997edb Merge "Updated Watcher doc to mention Tempest tests" 2016-03-01 22:58:24 +00:00
Gábor Antal
c440cdd69f Fixed wrongly used assertEqual method
In several places, assertEqual is used the following way:
  assertEqual(observed, expected)
However, the correct way to use assertEqual is:
  assertEqual(expected, observed)

Change-Id: I5a7442f4adf98bf7bc73cef1d17d20da39d9a7f8
Closes-Bug: #1551861
2016-03-01 18:20:37 +01:00
Taylor Peoples
b5bccba169 Improve DevStack documentation for beginners
The DevStack documentation should provide basic steps for setting up
Watcher with DevStack assuming the reader has no previous knowledge of
DevStack.

Change-Id: I830b1d9accb0e65bba73944697cba9c53ac3263e
Closes-Bug: #1538291
2016-03-01 17:25:24 +01:00
Jenkins
e058437ae0 Merge "Replace "Triggered" state by "Pending" state" 2016-03-01 15:48:33 +00:00
Jenkins
1acacaa812 Merge "Added support for live migration on non-shared storage" 2016-03-01 09:31:50 +00:00
Daniel Pawlik
5bb1b6cbf0 Added support for live migration on non-shared storage
Watcher applier should be able to live migrate instances on any storage
type. To do this watcher will catch error 400 returned from nova if we
try to live migrate instance which is not on shared storage and live
migrate instance using block_migrate.

Added unit tests, changed action in watcher applier.

Closes-bug: #1549307

Change-Id: I97e583c9b4a0bb9daa1d39e6d652d6474a5aaeb1
2016-03-01 08:35:14 +00:00
Vincent Françoise
de058d7ed1 Updated Watcher doc to mention Tempest tests
The Watcher Tempest tests are only mentioned inside a README.rst.
They are now part of the main documentation.

Change-Id: Ieca85dc7f7307b45e4b99af4a4600a8c2d2b59d7
Closes-Bug: #1536993
2016-02-29 17:42:21 +01:00
Vincent Françoise
98a65efb16 RST directive to discover and generate drivers doc
This patchset introduces a new custom directive called 'drivers-doc'
which loads all available drivers under a given namespace and import
their respective docstring into the .rst document.

This patchset also contains some modification/addition to the
docstring of these drivers to make the final document complete.

Change-Id: Ib3df59fa45cea9d11d20fb73a5f0f1d564135bca
Closes-Bug: #1536218
Closes-Bug: #1536735
2016-02-29 16:34:44 +01:00
Tin Lam
338539ec53 Rename 'TRIGGERED' state as 'PENDING'
"TRIGGERED" is not the correct word to use to describe the action
state, we should use "PENDING" instead.

Change-Id: If24979cdb916523861324f7bcc024e2f1fc28b05
Closes-Bug: #1548377
2016-02-26 23:07:59 -06:00
Jenkins
02f0f8e70a Merge "Add missing requirements" 2016-02-26 17:06:55 +00:00
Jenkins
7f1bd20a09 Merge "Add start directory for oslo_debug_helper" 2016-02-26 17:04:03 +00:00
Jenkins
d3d2a5ef8c Merge "Updated from global requirements" 2016-02-26 17:03:57 +00:00
Jenkins
3a6ae820c0 Merge "Fixed type in get_audit_template_by_name method" 2016-02-26 17:03:51 +00:00
Jenkins
5a8860419e Merge "Cleanup in tests/__init__.py" 2016-02-26 16:59:08 +00:00
Steve Wilkerson
4aa1c7558b Fixed type in get_audit_template_by_name method
Removed an extra underscore in the
get_audit_template_by__name method name in
watcher/db/api.py

Change-Id: I2687858ff4510c626c4dd2e2e9a5701405b5da55
Closes-Bug: #1548765
2016-02-26 08:25:42 -06:00
OpenStack Proposal Bot
a8dab52376 Updated from global requirements
Change-Id: Id68a055e6514ea30e28ef6adcfb22bb2ff3eeafb
2016-02-26 01:54:52 +00:00
Gábor Antal
5615d0523d Cleanup in tests/__init__.py
In watcher/tests/__init__.py has a totally unused,
misleading class so I removed it, as it is never used.

Change-Id: Ib878252453489eb3e1b1ff06f4d6b5e2b0726be5
Closes-Bug: #1549920
2016-02-25 19:10:54 +01:00
Jenkins
10823ce133 Merge "Cleanup in test_objects.py" 2016-02-25 16:06:02 +00:00
Jenkins
18c098c4c1 Merge "Update nova service state" 2016-02-25 08:30:40 +00:00
Jenkins
db649d86b6 Merge "Useless return statement in validate_sort_dir" 2016-02-24 16:49:01 +00:00
Tin Lam
6e380b685b Update nova service state
The primitive ChangeNovaServiceState allows us to change the state of
the nova-compute by calling nova api. The state of a nova-compute can
be ENABLED or DISABLED, however in the current implementation we use
OFFLINE and ONLINE.

Update the code to use ENABLED or DISABLED.

Change-Id: If3d9726bc5ae980b66c7fd4c5b7986f89d8bc690
Closes-Bug: #1523891
2016-02-23 00:46:30 -06:00
Antoine Cabot
8dfff0e8e6 Replace "Triggered" state by "Pending" state
This commit only applies to documentation, state name
must be updated in code accordingly.

Change-Id: I027fd55d968c12992d800de3657543be417c71b0
Related-Bug: #1548377
2016-02-22 17:33:41 +01:00
Chaozhe.Chen
fbc7da755a Add start directory for oslo_debug_helper
When I used `tox -e debug <test-name>` to test a case, the case did not
run properly and there is an error like this:

ImportError: Start directory is not importable: './python-watcher/tests'

If we use '-t wartcher/tests' to point out the test path, the case will
run properly under debug.
So we'd better add this start directory for oslo_debug_helper.

Change-Id: I04d9937f72a95f8f045129af08df0cd0d0870d39
2016-02-23 00:01:18 +08:00
Chaozhe.Chen
b947c30910 Add missing requirements
WebOb is needed in our code[1].

[1]https://github.com/openstack/watcher/blob/master/watcher/api/
middleware/parsable_error.py#L70

Change-Id: I6dd3940057368ff70656dd40c75ec59284f378bf
2016-02-22 21:14:25 +08:00
OpenStack Proposal Bot
9af96114af Updated from global requirements
Change-Id: I28d515eef452dd715a28e3fb4c177fc93f307c79
2016-02-20 22:02:12 +00:00
Vincent Françoise
1ddf69a68f Re-enable related Tempest test
Following the previous 2 patchset
https://review.openstack.org/#/c/279517/ and
https://review.openstack.org/#/c/279555/, this patchset re-enables
the related Tempest test which filters goals while removing the one
that was filtering by host aggregate.

Change-Id: I384e62320de34761e29d5cbac37ddc8ae253a70c
Closes-Bug: #1510189
2016-02-19 14:32:42 +00:00
Jenkins
379ac791a8 Merge "Pass parameter to the query in get_last_sample_values" 2016-02-19 10:24:36 +00:00
Jenkins
81ea37de41 Merge "Remove unused function and argument" 2016-02-19 01:01:10 +00:00
Gábor Antal
1c963fdc96 Useless return statement in validate_sort_dir
In watcher/api/controllers/v1/utils.py, in
the validate_sort_dir method has a return statement,
however the return value is exactly the parameter's value.

This is misleading, so I removed it.

Change-Id: I18c5c7853a5afedac88431347712a4348c9fd5dd
Closes-Bug: #1546917
2016-02-18 17:56:16 +01:00
Gábor Antal
f32995228b Pass parameter to the query in get_last_sample_values
In watcher/common/ceilometer_help.py:129,
there is an unused parameter, called limit:
  def get_last_sample_values(self, resource_id, meter_name, limit=1):

In the next line, there is a method call which can take this
currently unused 'limit' parameter. Probably, passing this 'limit'
parameter to the query was originally intended, however it wasn't
added to the query call.

Closes-Bug: #1541415
Change-Id: I025070c6004243d6b8a6ea7a1d83081480c4148b
2016-02-18 13:55:02 +01:00
Jenkins
0ec3d68994 Merge "Added goal filter in Watcher API" 2016-02-18 10:26:38 +00:00
Jenkins
3503e11506 Merge "Improve variable names in strategy implementations" 2016-02-18 10:22:50 +00:00
Jenkins
c7f0ef37d0 Merge "Added unit tests on actions" 2016-02-18 08:42:46 +00:00
Béla Vancsics
d93b1ffe9f Remove unused function and argument
I removed the unused function and (function)argument in code

Change-Id: Ib7afa5d868c3c7769f53e45c270850e4c3370f86
2016-02-18 09:17:43 +01:00
Vincent Françoise
4e71a0c655 Added goal filter in Watcher API
Although it was proposed via python-watcherclient, the feature was
not implemented on the Watcher API.
As the notion of host aggregate is currently unused in Watcher,
decision was made to only implement the filtering of goal within
the Watcher API whilst removing the host_aggregate filter from the
Watcher client.
Thus, this patchset adds this missing functionality by adding the
'goal' parameter to the API.

Change-Id: I54d248f7e470249c6412650ddf50a3e3631d2a09
Related-Bug: #1510189
2016-02-17 18:22:51 +01:00
Steve Wilkerson
37dd713ed5 Improve variable names in strategy implementations
Renamed many of the variables and method parameters
in the strategy implementations to make the names
more meaningful.  Also changed the abstract method
signature in base.py to reflect these changes.

Closes-Bug: #1541615

Change-Id: Ibeba6c6ef6d5b70482930f387b05d5d650812355
2016-02-16 09:15:14 -06:00
Vincent Françoise
55aeb783e3 Added unit tests on actions
As we had a low test coverage on actions, I added some more tests
with this patchset. This actually revealed a small bug (typo) in
"change_nova_service_state" which has been fixed in here.

Note that Tempest test also cover these action via the
basic_consolidation strategy.

Change-Id: I2d7116a6fdefee82ca254512a9cf50fc61e3c80e
Closes-Bug: #1523513
2016-02-16 11:42:37 +01:00
Jenkins
fe3f6e73be Merge "Remove KEYSTONE_CATALOG_BACKEND from DevStack plugin" 2016-02-16 08:24:54 +00:00
Jean-Emile DARTOIS
5baff7dc3e Clean imports in code
In some part in the code we import objects.
In the Openstack style guidelines they recommand
to import only modules.
We need to fix that.

Change-Id: I4bfee2b94d101940d615f78f9bebb83310ed90ba
Partial-Bug:1543101
2016-02-15 18:04:24 +01:00
Jean-Emile DARTOIS
e3198d25a5 Add Voluptuous to validate the action parameters
We want a simplest way to validate the input parameters of an
Action through a schema.

APIImpact
DocImpact
Partially implements: blueprint watcher-add-actions-via-conf

Change-Id: I139775f467fe7778c7354b0cfacf796fc27ffcb2
2016-02-12 17:47:52 +01:00
Jenkins
33ee575936 Merge "Better cleanup for Tempest tests" 2016-02-12 16:04:26 +00:00
Darren Shaw
259f2562e6 Remove KEYSTONE_CATALOG_BACKEND from DevStack plugin
Via Sean Dague in the mailing list:
In Newton, the KEYSTONE_CATALOG_BACKEND variable will be removed.
This commit removes the if check and variable from watcher

Change-Id: I73c5dd25feff9ba9824c267a8817a49e4ad3a06a
Closes-Bug: #1544433
2016-02-12 09:51:51 -06:00
Jenkins
1629247413 Merge "Update the default version of Neutron API" 2016-02-12 15:41:34 +00:00
Jenkins
58d84aca6d Merge "Ceilometer client instantiation fixup" 2016-02-12 10:00:33 +00:00
Gábor Antal
236879490d Cleanup in test_objects.py
In watcher/tests/objects/test_objects.py, there is a class
called "_TestObject(object)" which is probably an older test class.

This test class runs never (as the name starts with "_"
and inherits from object), and has some really old test,
like test_orphaned_object method, which is testing an exception
that doesn't exist at the current codebase.

Change-Id: I7559a004e8c136a206fc1cf7ac330c7d4157f94f
Closes-Bug: #1544685
2016-02-11 19:29:16 +01:00
Vincent Françoise
79850cc89c Better cleanup for Tempest tests
When running our Tempest tests, we are now cleaning up all the
objects we have created within the WatcherDB via a soft_delete.

Partially Implements: blueprint deletion-of-actions-plan

Change-Id: Ibdcfd2be37094377d09ad77d5c20298ee2baa4d0
2016-02-11 18:36:16 +01:00
Vincent Françoise
b958214db8 Ceilometer client instantiation fixup
A problem was found during manual integration tests which were failing
because we couldn't instantiate the ceilometer client when trying to
execute an action plan using the 'basic_consolidation' strategy.

This patchset fixes the problem with an update of the related tests

Change-Id: I2b1f1dcc16fd8dfbf508c4d5661c1fce194254e4
Closes-Bug: #1544652
2016-02-11 18:29:58 +01:00
Jenkins
a0b5f5aa1d Merge "Delete linked actions when deleting an action plan" 2016-02-11 09:39:33 +00:00
David TARDIVEL
a4a009a2c6 Update the default version of Neutron API
Default value of Neutron API should be 2.0 in neutronclient.
Closes-Bug: #1544134

Change-Id: I241c067c33c992da53f30e974ffca3edb466811d
2016-02-10 17:04:34 +01:00
Taylor Peoples
0ba8a35ade Sync with openstack/requirements master branch
In order to accept the requirements contract defined in
openstack/requirements, we need to sync with the master branch of that
project's global-requirements.txt and test-requirements.txt files.

Most of the changes are just version changes and nothing major.  The
only major change is:

1) The twine dependency is removed, and therefore the pypi tox
environment was also removed.

Change-Id: Idbe9e73ddc5a34ac49aa6f6eff0779d46a75f583
Closes-Bug: #1533282
2016-02-09 16:31:07 +00:00
Vincent Françoise
8bcc1b2097 Delete linked actions when deleting an action plan
When a user deletes an action plan, we now delete all related actions.

Partially Implements: blueprint deletion-of-actions-plan

Change-Id: I5d519c38458741be78591cbec04dbd410a6dc14b
2016-02-08 17:04:37 +01:00
Jenkins
b440f5c69a Merge "Add IRC information into contributing page" 2016-02-05 17:19:48 +00:00
Jenkins
ad40c61ea9 Merge "Update docs for password auth configuration options" 2016-02-05 15:58:31 +00:00
David TARDIVEL
858bbbf126 Add IRC information into contributing page
Add Watcher IRC channels : #openstack-watcher and
 #openstack-meeeting-4

Change-Id: I4c128544a32548065fddde54b28f645fc63ebfd5
Closes-Bug: #1536200
2016-02-05 15:53:21 +00:00
Jenkins
1d74f7e3bc Merge "Remove references to SERVERS_CONSOLIDATION" 2016-02-05 14:28:34 +00:00
Jenkins
b7641a9311 Merge "Added Tempest scenario for BASIC_CONSOLIDATION" 2016-02-04 08:18:30 +00:00
David TARDIVEL
376d669af6 Update docs for password auth configuration options
Watcher uses now auth_type 'password' plugin for authentication.
Configuration related to credentials used to validate and apply
for a token has been updated.

Change-Id: If71bb908741130cb01d5d1525a12cf9a68b58a58
Closes-Bug: #1541296
2016-02-03 19:24:00 +01:00
Jenkins
25d27f0288 Merge "Create OpenStackClients convenience class" 2016-02-03 10:21:32 +00:00
Jenkins
3f4686ce79 Merge "Use install instead of mkdir for DevStack dirs" 2016-02-03 06:43:17 +00:00
Taylor Peoples
86c1a9d77f Remove references to SERVERS_CONSOLIDATION
Change I6c43eba941022a88851a199b56a6c20f017b9e71 seemed to have remove
most references to the SERVERS_CONSOLIDATION goal.  Since this goal does
not currently exist in the actual code and all usages of it are for
samples or for tests, it is replaced with the DUMMY goal to avoid
confusion.

Change-Id: I4d2240d3b22c42ebf4e6120e2cd7677ec49d8e98
Closes-Bug: #1538388
2016-02-03 07:22:44 +01:00
Taylor Peoples
9a6811ae6b Create OpenStackClients convenience class
The OpenStackClients class provides a convenient way to create and
cache client instances.  The idea behind this code comes from Magnum
[0].

The OpenStackClients class will act as the manager of other project's
clients, providing an easy way to fetch instances of said clients. This
will allow the clients to be cached.

An instance of OpenStackClients is created for every call that comes
into the decision engine and the applier, using the request context to
pass needed (domain id) parameters to get a Keystone session.  This
instance should be shared as much as possible to avoid additional
unneccessary connections to the other services.

This class will also allow for the version of each client to be
configurable via the watcher.conf file.

The method by which a Keystone session is also changed to use the
keystoneauth1.loading library.  In order to avoid DuplicateOptErrors
with the keystone_authtoken group used for the keystonemiddleware in the
API code, a new conf group named "watcher_clients_auth" is created.  A
typical configuration using a password authentication scheme will look
like:
  [watcher_clients_auth]
  auth_type = password
  auth_url = http://<server-ip>:<port>
  username = <username>
  password = <password>
  project_domain_id = default
  user_domain_id = default

[0]: https://github.com/openstack/magnum/blob/master/magnum/common/clients.py

DocImpact
Change-Id: Iab9d0b304099686da2e9e2b19e8b1de4332ff378
Implements: blueprint external-api-versioning
Closes-Bug: #1530790
Closes-Bug: #1539670
Closes-Bug: #1522774
2016-02-03 02:27:26 +01:00
Jenkins
e520f5f452 Merge "Removed unused parameter in dt_deserializer()" 2016-02-03 00:13:38 +00:00
Jenkins
6a25bd983c Merge "Remove InvalidParameterValue exception" 2016-02-02 23:29:55 +00:00
Jenkins
c175ef2170 Merge "Define self.client in MessagingCore" 2016-02-02 16:08:12 +00:00
Jenkins
28733a5f30 Merge "Remove unused parameter in Actions API controller" 2016-02-02 14:45:47 +00:00
Vincent Françoise
7f8fec1bca Added Tempest scenario for BASIC_CONSOLIDATION
As of now we only have a single scenario which creates and
successfully executes the DUMMY goal.

This patchset adds a new scenario which creates and executes the
BASIC_CONSOLIDATION goal mapped to the 'basic' (sercon) strategy.

The documentation has also been updated to take into account the
multinode configuration.

Change-Id: Ie246aed288ade56a8fe9c0d9b08365d72e60ada1
Closes-Bug: #1538606
2016-02-02 13:35:17 +00:00
Edwin Zhai
278b1819d6 Use install instead of mkdir for DevStack dirs
The current code will not work if WATCHER_CONF_DIR or
WATCHER_AUTH_CACHE_DIR already exist but are owned by a different user
such as root. Use install instead of mkdir to handle this scenario.

Change-Id: Ie582a4b393e898e007d73f31de490c4b77e40be3
Closes-Bug: #1539422
2016-02-02 09:53:55 +00:00
Gábor Antal
978bb11d4a Removed unused parameter in dt_deserializer()
In the file watcher/objects/utils.py, on line 120,
there is an unused parameter:
  def dt_deserializer(instance, val):

I removed that parameter, and modified the test.

Change-Id: Ibc7ab703d37d7f9248a84e41508820453c8954b7
Closes-Bug: #1540521
2016-02-01 19:29:04 +01:00
Steve Wilkerson
3027b28942 Remove unused parameter in Actions API controller
Removed the action_uuid parameter in get_all() and
detail()

Change-Id: If99a4a50bb72383bd96ad284d35946911cb68d1d
Closes-Bug: #1538171
2016-01-29 12:41:10 -06:00
Darren Shaw
2f0c1c12cf Define self.client in MessagingCore
Currently self.client is referenced within MessagingCore,
but no definition is made in its constructor. Additionally
self.client is defined in children classes of MessagingCore.
This patchset defines self.client in the constructor of
MessagingCore and removes the redefinition in its children.

-self.client lazily loaded

Co-Authored-By: v-francoise <Vincent.FRANCOISE@b-com.com>
Change-Id: I14525a175bf1ebde3d2636024ad2f2219c79d6e1
Closes-Bug: #1521636
2016-01-27 16:24:45 +01:00
Taylor Peoples
e122c61840 Remove InvalidParameterValue exception
The InvalidParameterValue exception does not define a meaningful
msg_fmt.  It is currently _("%(err)s"), which is the equivalent of
nothing and does not help with translation.

Replace InvalidParameterValue with Invalid exceptions.

Change-Id: If8b064e446cbc97e380127f360f262be9e8877a1
Closes-Bug: #1538398
2016-01-27 16:13:52 +01:00
Vincent Françoise
8f6eac819f Tempest API tests on /actions
Following the blueprint tempest-basic-set-up which implemented a first
batch of tests, this one adds a new set of API tests on actions.

I also added extra check on actions within the dummy strategy
scenario.

Change-Id: Ib9bf093d0ed457ecba32e8251c019d2cf5c98128
Closes-Bug: #1538074
2016-01-27 10:02:59 +01:00
Vincent Françoise
de307e536e GET on an action_plan provides first_action_uuid
Whenever trying to get the first action related to a given action
plan, we were getting back a 'null' value from the API even though
we knew there were actions to be linked to it in the DB.
So I fixed this issue and added a related unit test.

Change-Id: I1fa755f24fbf37ecd6ce2cc2396658fca8743a1c
Closes-Bug: #1538130
2016-01-27 09:38:15 +01:00
Vincent Françoise
7406a1e713 Fixed ActionPlanNotFound typo in msg_fmt
The msg_fmt of ActionPlanNotFound was missing an "_" which caused
errors upon trying to format it, so I fixed it.

Change-Id: I515c2097a563f809e319d2e57480fd340b878cef
Closes-Bug: #1538065
2016-01-26 11:32:09 +01:00
Vincent Françoise
982410dd3e Fixed tempest test bug
has_audit_succeeded was not implemented so I added it back.

Change-Id: Ic567ff56ea6d513c32fbe7ad08cca96b5dfb15e8
Closes-Bug: #1537144
2016-01-26 09:17:39 +01:00
390 changed files with 30292 additions and 8147 deletions

View File

@@ -1,9 +1,12 @@
[run] [run]
branch = True branch = True
source = watcher source = watcher
omit = watcher/tests/* omit =
watcher/tests/*
watcher/hacking/*
[report] [report]
ignore_errors = True ignore_errors = True
exclude_lines = exclude_lines =
@abstract @abc.abstract
raise NotImplementedError

9
.gitignore vendored
View File

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

View File

@@ -2,3 +2,4 @@
host=review.openstack.org host=review.openstack.org
port=29418 port=29418
project=openstack/watcher.git project=openstack/watcher.git
defaultbranch=stable/newton

View File

@@ -10,15 +10,13 @@ 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 robust framework to realize a wide range of cloud
metrics receiver, complex event processor and profiler, optimization processor optimization goals, including the reduction of data center
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
operating costs, increased system performance via intelligent virtual machine operating costs, increased system performance via intelligent virtual machine
migration, increased energy efficiencyand 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/

View File

@@ -44,6 +44,9 @@ WATCHER_CONF_DIR=/etc/watcher
WATCHER_CONF=$WATCHER_CONF_DIR/watcher.conf WATCHER_CONF=$WATCHER_CONF_DIR/watcher.conf
WATCHER_POLICY_JSON=$WATCHER_CONF_DIR/policy.json WATCHER_POLICY_JSON=$WATCHER_CONF_DIR/policy.json
NOVA_CONF_DIR=/etc/nova
NOVA_CONF=$NOVA_CONF_DIR/nova.conf
if is_ssl_enabled_service "watcher" || is_service_enabled tls-proxy; then if is_ssl_enabled_service "watcher" || is_service_enabled tls-proxy; then
WATCHER_SERVICE_PROTOCOL="https" WATCHER_SERVICE_PROTOCOL="https"
fi fi
@@ -80,10 +83,7 @@ function cleanup_watcher {
# configure_watcher() - Set config files, create data dirs, etc # configure_watcher() - Set config files, create data dirs, etc
function configure_watcher { function configure_watcher {
# Put config files in ``/etc/watcher`` for everyone to find # Put config files in ``/etc/watcher`` for everyone to find
if [[ ! -d $WATCHER_CONF_DIR ]]; then sudo install -d -o $STACK_USER $WATCHER_CONF_DIR
sudo mkdir -p $WATCHER_CONF_DIR
sudo chown $STACK_USER $WATCHER_CONF_DIR
fi
install_default_policy watcher install_default_policy watcher
@@ -99,15 +99,13 @@ function configure_watcher {
function create_watcher_accounts { function create_watcher_accounts {
create_service_user "watcher" "admin" create_service_user "watcher" "admin"
if [[ "$KEYSTONE_CATALOG_BACKEND" = 'sql' ]]; then local watcher_service=$(get_or_create_service "watcher" \
local watcher_service=$(get_or_create_service "watcher" \ "infra-optim" "Watcher Infrastructure Optimization Service")
"infra-optim" "Watcher Infrastructure Optimization Service") get_or_create_endpoint $watcher_service \
get_or_create_endpoint $watcher_service \ "$REGION_NAME" \
"$REGION_NAME" \ "$WATCHER_SERVICE_PROTOCOL://$WATCHER_SERVICE_HOST:$WATCHER_SERVICE_PORT" \
"$WATCHER_SERVICE_PROTOCOL://$WATCHER_SERVICE_HOST:$WATCHER_SERVICE_PORT" \ "$WATCHER_SERVICE_PROTOCOL://$WATCHER_SERVICE_HOST:$WATCHER_SERVICE_PORT" \
"$WATCHER_SERVICE_PROTOCOL://$WATCHER_SERVICE_HOST:$WATCHER_SERVICE_PORT" \ "$WATCHER_SERVICE_PROTOCOL://$WATCHER_SERVICE_HOST:$WATCHER_SERVICE_PORT"
"$WATCHER_SERVICE_PROTOCOL://$WATCHER_SERVICE_HOST:$WATCHER_SERVICE_PORT"
fi
} }
# create_watcher_conf() - Create a new watcher.conf file # create_watcher_conf() - Create a new watcher.conf file
@@ -128,14 +126,10 @@ function create_watcher_conf {
iniset $WATCHER_CONF oslo_messaging_rabbit rabbit_password $RABBIT_PASSWORD iniset $WATCHER_CONF oslo_messaging_rabbit rabbit_password $RABBIT_PASSWORD
iniset $WATCHER_CONF oslo_messaging_rabbit rabbit_host $RABBIT_HOST iniset $WATCHER_CONF oslo_messaging_rabbit rabbit_host $RABBIT_HOST
iniset $WATCHER_CONF keystone_authtoken admin_user watcher iniset $NOVA_CONF oslo_messaging_notifications topics "notifications,watcher_notifications"
iniset $WATCHER_CONF keystone_authtoken admin_password $SERVICE_PASSWORD
iniset $WATCHER_CONF keystone_authtoken admin_tenant_name $SERVICE_TENANT_NAME
configure_auth_token_middleware $WATCHER_CONF watcher $WATCHER_AUTH_CACHE_DIR configure_auth_token_middleware $WATCHER_CONF watcher $WATCHER_AUTH_CACHE_DIR
configure_auth_token_middleware $WATCHER_CONF watcher $WATCHER_AUTH_CACHE_DIR "watcher_clients_auth"
iniset $WATCHER_CONF keystone_authtoken auth_uri $KEYSTONE_SERVICE_URI/v3
iniset $WATCHER_CONF keystone_authtoken auth_version v3
if is_fedora || is_suse; then if is_fedora || is_suse; then
# watcher defaults to /usr/local/bin, but fedora and suse pip like to # watcher defaults to /usr/local/bin, but fedora and suse pip like to
@@ -178,9 +172,8 @@ function create_watcher_conf {
# create_watcher_cache_dir() - Part of the init_watcher() process # create_watcher_cache_dir() - Part of the init_watcher() process
function create_watcher_cache_dir { function create_watcher_cache_dir {
# Create cache dir # Create cache dir
sudo mkdir -p $WATCHER_AUTH_CACHE_DIR sudo install -d -o $STACK_USER $WATCHER_AUTH_CACHE_DIR
sudo chown $STACK_USER $WATCHER_AUTH_CACHE_DIR rm -rf $WATCHER_AUTH_CACHE_DIR/*
rm -f $WATCHER_AUTH_CACHE_DIR/*
} }
# init_watcher() - Initialize databases, etc. # init_watcher() - Initialize databases, etc.

View File

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

View File

@@ -127,7 +127,19 @@ Watcher CLI
The watcher command-line interface (CLI) can be used to interact with the The watcher command-line interface (CLI) can be used to interact with the
Watcher system in order to control it or to know its current status. Watcher system in order to control it or to know its current status.
Please, read `the detailed documentation about Watcher CLI <https://factory.b-com.com/www/watcher/doc/python-watcherclient/>`_ Please, read `the detailed documentation about Watcher CLI
<https://factory.b-com.com/www/watcher/doc/python-watcherclient/>`_.
.. _archi_watcher_dashboard_definition:
Watcher Dashboard
-----------------
The Watcher Dashboard can be used to interact with the Watcher system through
Horizon in order to control it or to know its current status.
Please, read `the detailed documentation about Watcher Dashboard
<http://docs.openstack.org/developer/watcher-dashboard/>`_.
.. _archi_watcher_database_definition: .. _archi_watcher_database_definition:
@@ -138,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*".
@@ -156,37 +171,47 @@ This component is responsible for computing a set of potential optimization
:ref:`Actions <action_definition>` in order to fulfill :ref:`Actions <action_definition>` in order to fulfill
the :ref:`Goal <goal_definition>` of an :ref:`Audit <audit_definition>`. the :ref:`Goal <goal_definition>` of an :ref:`Audit <audit_definition>`.
It first reads the parameters of the :ref:`Audit <audit_definition>` from the It first reads the parameters of the :ref:`Audit <audit_definition>` to know
associated :ref:`Audit Template <audit_template_definition>` and knows the the :ref:`Goal <goal_definition>` to achieve.
:ref:`Goal <goal_definition>` to achieve.
It then selects the most appropriate :ref:`Strategy <strategy_definition>` Unless specified, it then selects the most appropriate :ref:`strategy
depending on how Watcher was configured for this :ref:`Goal <goal_definition>`. <strategy_definition>` from the list of available strategies achieving this
goal.
The :ref:`Strategy <strategy_definition>` is then dynamically loaded (via The :ref:`Strategy <strategy_definition>` is then dynamically loaded (via
`stevedore <https://github.com/openstack/stevedore/>`_). The `stevedore <http://docs.openstack.org/developer/stevedore/>`_). The
:ref:`Watcher Decision Engine <watcher_decision_engine_definition>` calls the :ref:`Watcher Decision Engine <watcher_decision_engine_definition>` executes
**execute()** method of the :ref:`Strategy <strategy_definition>` class which the strategy.
generates a solution composed of a set of :ref:`Actions <action_definition>`.
In order to compute the potential :ref:`Solution <solution_definition>` for the
Audit, the :ref:`Strategy <strategy_definition>` relies on different sets of
data:
- :ref:`Cluster data models <cluster_data_model_definition>` that are
periodically synchronized through pluggable cluster data model collectors.
These models contain the current state of various
:ref:`Managed resources <managed_resource_definition>` (e.g., the data stored
in the Nova database). These models gives a strategy the ability to reason on
the current state of a given :ref:`cluster <cluster_definition>`.
- The data stored in the :ref:`Cluster History Database
<cluster_history_db_definition>` which provides information about the past of
the :ref:`Cluster <cluster_definition>`.
Here below is a sequence diagram showing how the Decision Engine builds and
maintains the :ref:`cluster data models <cluster_data_model_definition>` that
are used by the strategies.
.. image:: ./images/sequence_architecture_cdmc_sync.png
:width: 100%
The execution of a strategy then yields a solution composed of a set of
:ref:`Actions <action_definition>` as well as a set of :ref:`efficacy
indicators <efficacy_indicator_definition>`.
These :ref:`Actions <action_definition>` are scheduled in time by the These :ref:`Actions <action_definition>` are scheduled in time by the
:ref:`Watcher Planner <watcher_planner_definition>` (i.e., it generates an :ref:`Watcher Planner <watcher_planner_definition>` (i.e., it generates an
:ref:`Action Plan <action_plan_definition>`). :ref:`Action Plan <action_plan_definition>`).
In order to compute the potential :ref:`Solution <solution_definition>` for the
Audit, the :ref:`Strategy <strategy_definition>` relies on two sets of data:
- the current state of the
:ref:`Managed resources <managed_resource_definition>`
(e.g., the data stored in the Nova database)
- the data stored in the
:ref:`Cluster History Database <cluster_history_db_definition>`
which provides information about the past of the
:ref:`Cluster <cluster_definition>`
So far, only one :ref:`Strategy <strategy_definition>` can be associated to a
given :ref:`Goal <goal_definition>` via the main Watcher configuration file.
.. _data_model: .. _data_model:
Data model Data model
@@ -199,6 +224,12 @@ 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
.. _sequence_diagrams: .. _sequence_diagrams:
Sequence diagrams Sequence diagrams
@@ -218,13 +249,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:
@@ -238,6 +271,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>`:
@@ -248,12 +288,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
@@ -277,9 +316,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
@@ -288,6 +329,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:
@@ -349,6 +395,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 occurred 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:
@@ -361,6 +429,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 occurred 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:

View File

@@ -18,17 +18,20 @@ 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',
'oslosphinx',
'sphinx.ext.autodoc', 'sphinx.ext.autodoc',
'sphinx.ext.viewcode', 'sphinx.ext.viewcode',
'sphinxcontrib.httpdomain', 'sphinxcontrib.httpdomain',
'sphinxcontrib.pecanwsme.rest', 'sphinxcontrib.pecanwsme.rest',
'stevedore.sphinxext',
'wsmeext.sphinxext', 'wsmeext.sphinxext',
'oslosphinx',
'watcher.doc', 'watcher.doc',
] ]
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.

View File

@@ -0,0 +1 @@
../../etc/watcher/watcher-config-generator.conf

View 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

View File

@@ -34,6 +34,8 @@ The Watcher service includes the following components:
- ``watcher-applier``: applies the action plan. - ``watcher-applier``: applies the action plan.
- `python-watcherclient`_: A command-line interface (CLI) for interacting with - `python-watcherclient`_: A command-line interface (CLI) for interacting with
the Watcher service. the Watcher service.
- `watcher-dashboard`_: An Horizon plugin for interacting with the Watcher
service.
Additionally, the Bare Metal service has certain external dependencies, which Additionally, the Bare Metal service has certain external dependencies, which
are very similar to other OpenStack services: are very similar to other OpenStack services:
@@ -52,6 +54,7 @@ additional functionality:
.. _`ceilometer`: https://github.com/openstack/ceilometer .. _`ceilometer`: https://github.com/openstack/ceilometer
.. _`nova`: https://github.com/openstack/nova .. _`nova`: https://github.com/openstack/nova
.. _`python-watcherclient`: https://github.com/openstack/python-watcherclient .. _`python-watcherclient`: https://github.com/openstack/python-watcherclient
.. _`watcher-dashboard`: https://github.com/openstack/watcher-dashboard
.. _`watcher metering`: https://github.com/b-com/watcher-metering .. _`watcher metering`: https://github.com/b-com/watcher-metering
.. _`RabbitMQ`: https://www.rabbitmq.com/ .. _`RabbitMQ`: https://www.rabbitmq.com/
@@ -160,17 +163,31 @@ 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
* ``[api]`` - API server configuration * ``[api]`` - API server configuration
* ``[database]`` - SQL driver configuration * ``[database]`` - SQL driver configuration
* ``[keystone_authtoken]`` - Keystone Authentication plugin configuration * ``[keystone_authtoken]`` - Keystone Authentication plugin configuration
* ``[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
* ``[cinder_client]`` - Cinder client configuration
* ``[glance_client]`` - Glance client configuration
* ``[nova_client]`` - Nova client configuration
* ``[neutron_client]`` - Neutron client configuration
The Watcher configuration file is expected to be named The Watcher configuration file is expected to be named
``watcher.conf``. When starting Watcher, you can specify a different ``watcher.conf``. When starting Watcher, you can specify a different
@@ -237,39 +254,105 @@ so that the watcher service is configured for your needs.
#rabbit_port = 5672 #rabbit_port = 5672
#. Configure the Watcher Service to use these credentials with the Identity #. Watcher API shall validate the token provided by every incoming request,
Service. Replace IDENTITY_IP with the IP of the Identity server, and via keystonemiddleware, which requires the Watcher service to be configured
replace WATCHER_PASSWORD with the password you chose for the ``watcher`` with the right credentials for the Identity service.
user in the Identity Service::
[keystone_authtoken] In the configuration section here below:
# Complete public Identity API endpoint (string value) * replace IDENTITY_IP with the IP of the Identity server
#auth_uri=<None> * replace WATCHER_PASSWORD with the password you chose for the ``watcher``
auth_uri=http://IDENTITY_IP:5000/v3 user
* replace KEYSTONE_SERVICE_PROJECT_NAME with the name of project created
for OpenStack services (e.g. ``service``) ::
# Complete admin Identity API endpoint. This should specify the [keystone_authtoken]
# unversioned root endpoint e.g. https://localhost:35357/ (string
# value)
#identity_uri = <None>
identity_uri = http://IDENTITY_IP:5000
# Keystone account username (string value) # Authentication type to load (unknown value)
#admin_user=<None> # Deprecated group/name - [DEFAULT]/auth_plugin
admin_user=watcher #auth_type = <None>
auth_type = password
# Keystone account password (string value) # Authentication URL (unknown value)
#admin_password=<None> #auth_url = <None>
admin_password=WATCHER_DBPASSWORD auth_url = http://IDENTITY_IP:35357
# Keystone service account tenant name to validate user tokens # Username (unknown value)
# (string value) # Deprecated group/name - [DEFAULT]/username
#admin_tenant_name=admin #username = <None>
admin_tenant_name=KEYSTONE_SERVICE_PROJECT_NAME username=watcher
# Directory used to cache files related to PKI tokens (string # User's password (unknown value)
# value) #password = <None>
#signing_dir=<None> password = WATCHER_PASSWORD
# Domain ID containing project (unknown value)
#project_domain_id = <None>
project_domain_id = default
# User's domain id (unknown value)
#user_domain_id = <None>
user_domain_id = default
# Project name to scope to (unknown value)
# Deprecated group/name - [DEFAULT]/tenant-name
#project_name = <None>
project_name = KEYSTONE_SERVICE_PROJECT_NAME
#. Watcher's decision engine and applier interact with other OpenStack
projects through those projects' clients. In order to instantiate these
clients, Watcher needs to request a new session from the Identity service
using the right credentials.
In the configuration section here below:
* replace IDENTITY_IP with the IP of the Identity server
* replace WATCHER_PASSWORD with the password you chose for the ``watcher``
user
* replace KEYSTONE_SERVICE_PROJECT_NAME with the name of project created
for OpenStack services (e.g. ``service``) ::
[watcher_clients_auth]
# Authentication type to load (unknown value)
# Deprecated group/name - [DEFAULT]/auth_plugin
#auth_type = <None>
auth_type = password
# Authentication URL (unknown value)
#auth_url = <None>
auth_url = http://IDENTITY_IP:35357
# Username (unknown value)
# Deprecated group/name - [DEFAULT]/username
#username = <None>
username=watcher
# User's password (unknown value)
#password = <None>
password = WATCHER_PASSWORD
# Domain ID containing project (unknown value)
#project_domain_id = <None>
project_domain_id = default
# User's domain id (unknown value)
#user_domain_id = <None>
user_domain_id = default
# Project name to scope to (unknown value)
# Deprecated group/name - [DEFAULT]/tenant-name
#project_name = <None>
project_name = KEYSTONE_SERVICE_PROJECT_NAME
#. Configure the clients to use a specific version if desired. For example, to
configure Watcher to use a Nova client with version 2.1, use::
[nova_client]
# Version of Nova API to use in novaclient. (string value)
#api_version = 2
api_version = 2.1
#. Create the Watcher Service database tables:: #. Create the Watcher Service database tables::
@@ -320,6 +403,35 @@ own storage driver using whatever technology you want.
For more information : https://wiki.openstack.org/wiki/Gnocchi For more information : https://wiki.openstack.org/wiki/Gnocchi
Configure Nova Notifications
============================
Watcher can consume notifications generated by the Nova services, in order to
build or update, in real time, its cluster data model related to computing
resources.
Nova publishes, by default, notifications on ``notifications`` AMQP queue
(configurable) and ``versioned_notifications`` AMQP queue (not
configurable). ``notifications`` queue is mainly used by ceilometer, so we can
not use it. And some events, related to nova-compute service state, are only
sent into the ``versioned_notifications`` queue.
By default, Watcher listens to AMQP queues named ``watcher_notifications``
and ``versioned_notifications``. So you have to update the Nova
configuration file on controller and compute nodes, in order
to Watcher receives Nova notifications in ``watcher_notifications`` as well.
* In the file ``/etc/nova/nova.conf``, update the section
``[oslo_messaging_notifications]``, by redefining the list of topics
into which Nova services will publish events ::
[oslo_messaging_notifications]
driver = messaging
topics = notifications,watcher_notifications
* Restart the Nova services.
Workers Workers
======= =======

52
doc/source/deploy/gmr.rst Normal file
View 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.

View File

@@ -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
-------------------------------- --------------------------------
@@ -108,3 +108,54 @@ installed on your system.
Once installed, you still need to declare Watcher as a new service into Once installed, you still need to declare Watcher as a new service into
Keystone and to configure its different modules, which you can find described Keystone and to configure its different modules, which you can find described
in :doc:`configuration`. in :doc:`configuration`.
Installing from packages: Debian (experimental)
-----------------------------------------------
Experimental Debian packages are available on `Debian repositories`_. The best
way to use them is to install them into a Docker_ container.
Here is single Dockerfile snippet you can use to run your Docker container:
.. code-block:: bash
FROM debian:experimental
MAINTAINER David TARDIVEL <david.tardivel@b-com.com>
RUN apt-get update
RUN apt-get dist-upgrade -y
RUN apt-get install -y vim net-tools
RUN apt-get install -yt experimental watcher-api
CMD ["/usr/bin/watcher-api"]
Build your container from this Dockerfile:
.. code-block:: bash
$ docker build -t watcher/api .
To run your container, execute this command:
.. code-block:: bash
$ docker run -d -p 9322:9322 watcher/api
Check in your logs Watcher API is started
.. code-block:: bash
$ docker logs <container ID>
You can run similar container with Watcher Decision Engine (package
``watcher-decision-engine``) and with the Watcher Applier (package
``watcher-applier``).
.. _Docker: https://www.docker.com/
.. _`Debian repositories`: https://packages.debian.org/experimental/allpackages

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

View File

@@ -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,13 +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
plugin installation guide`_.
.. _`installation guide`: https://factory.b-com.com/www/watcher/doc/python-watcherclient .. _`installation guide`: http://docs.openstack.org/developer/python-watcherclient
.. _`Watcher Horizon plugin installation guide`: http://docs.openstack.org/developer/watcher-dashboard/deploy/installation.html
.. _`OpenStack CLI`: http://docs.openstack.org/developer/python-openstackclient/man/openstack.html
.. _`Watcher CLI`: http://docs.openstack.org/developer/python-watcherclient/index.html
Seeing what the Watcher CLI can do ? Seeing what the Watcher CLI can do ?
------------------------------------ ------------------------------------
@@ -47,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 SERVERS_CONSOLIDATION $ 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>`.
@@ -72,23 +129,50 @@ 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>`
(instance migration, disabling of an hypervisor, ...) according to the (instance migration, disabling of a compute node, ...) according to the
:ref:`goal <goal_definition>` to achieve. You can see all of the goals :ref:`goal <goal_definition>` to achieve. You can see all of the goals
available in section ``[watcher_strategies]`` of the Watcher service available in section ``[watcher_strategies]`` of the Watcher service
configuration file. configuration file.
@@ -98,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
@@ -116,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>

View File

@@ -60,3 +60,12 @@ Code Hosting
Code Review Code Review
https://review.openstack.org/#/q/status:open+project:openstack/watcher,n,z https://review.openstack.org/#/q/status:open+project:openstack/watcher,n,z
IRC Channel
``#openstack-watcher`` (changelog_)
Weekly Meetings
on Wednesdays at 14:00 UTC on even weeks, 9:00 UTC on odd weeks, in the
``#openstack-meeting-4`` IRC channel (`meetings logs`_)
.. _changelog: http://eavesdrop.openstack.org/irclogs/%23openstack-watcher/
.. _meetings logs: http://eavesdrop.openstack.org/meetings/watcher/

View File

@@ -9,35 +9,97 @@ Set up a development environment via DevStack
============================================= =============================================
Watcher is currently able to optimize compute resources - specifically Nova Watcher is currently able to optimize compute resources - specifically Nova
compute hosts - via operations such as live migrations. In order for you to compute hosts - via operations such as live migrations. In order for you to
fully be able to exercise what Watcher can do, it is necessary to have a fully be able to exercise what Watcher can do, it is necessary to have a
multinode environment to use. If you have no experience with DevStack, you multinode environment to use.
should check out the `DevStack documentation`_ and be comfortable with the
basics of DevStack before attempting to get a multinode DevStack setup with
the Watcher plugin.
You can set up the Watcher services quickly and easily using a Watcher You can set up the Watcher services quickly and easily using a Watcher
DevStack plugin. See `PluginModelDocs`_ for information on DevStack's plugin DevStack plugin. See `PluginModelDocs`_ for information on DevStack's plugin
model. model. To enable the Watcher plugin with DevStack, add the following to the
.. _DevStack documentation: http://docs.openstack.org/developer/devstack/
.. _PluginModelDocs: http://docs.openstack.org/developer/devstack/plugins.html
It is recommended that you build off of the provided example local.conf files
(`local.conf.controller`_, `local.conf.compute`_). You'll likely want to
configure something to obtain metrics, such as Ceilometer. Ceilometer is used
in the example local.conf files.
To configure the Watcher services with DevStack, add the following to the
`[[local|localrc]]` section of your controller's `local.conf` to enable the `[[local|localrc]]` section of your controller's `local.conf` to enable the
Watcher plugin:: Watcher plugin::
enable_plugin watcher git://git.openstack.org/openstack/watcher enable_plugin watcher git://git.openstack.org/openstack/watcher
Then run devstack normally:: For more detailed instructions, see `Detailed DevStack Instructions`_. Check
out the `DevStack documentation`_ for more information regarding DevStack.
cd /opt/stack/devstack .. _PluginModelDocs: http://docs.openstack.org/developer/devstack/plugins.html
./stack.sh .. _DevStack documentation: http://docs.openstack.org/developer/devstack/
Detailed DevStack Instructions
==============================
#. Obtain N (where N >= 1) servers (virtual machines preferred for DevStack).
One of these servers will be the controller node while the others will be
compute nodes. N is preferably >= 3 so that you have at least 2 compute
nodes, but in order to stand up the Watcher services only 1 server is
needed (i.e., no computes are needed if you want to just experiment with
the Watcher services). These servers can be VMs running on your local
machine via VirtualBox if you prefer. DevStack currently recommends that
you use Ubuntu 14.04 LTS. The servers should also have connections to the
same network such that they are all able to communicate with one another.
#. For each server, clone the DevStack repository and create the stack user::
sudo apt-get update
sudo apt-get install git
git clone https://git.openstack.org/openstack-dev/devstack
sudo ./devstack/tools/create-stack-user.sh
Now you have a stack user that is used to run the DevStack processes. You
may want to give your stack user a password to allow SSH via a password::
sudo passwd stack
#. Switch to the stack user and clone the DevStack repo again::
sudo su stack
cd ~
git clone https://git.openstack.org/openstack-dev/devstack
#. For each compute node, copy the provided `local.conf.compute`_ example file
to the compute node's system at ~/devstack/local.conf. Make sure the
HOST_IP and SERVICE_HOST values are changed appropriately - i.e., HOST_IP
is set to the IP address of the compute node and SERVICE_HOST is set to the
IP address of the controller node.
If you need specific metrics collected (or want to use something other
than Ceilometer), be sure to configure it. For example, in the
`local.conf.compute`_ example file, the appropriate ceilometer plugins and
services are enabled and disabled. If you were using something other than
Ceilometer, then you would likely want to configure it likewise. The
example file also sets the compute monitors nova configuration option to
use the CPU virt driver. If you needed other metrics, it may be necessary
to configure similar configuration options for the projects providing those
metrics.
#. For the controller node, copy the provided `local.conf.controller`_ example
file to the controller node's system at ~/devstack/local.conf. Make sure
the HOST_IP value is changed appropriately - i.e., HOST_IP is set to the IP
address of the controller node.
Note: if you want to use another Watcher git repository (such as a local
one), then change the enable plugin line::
enable_plugin watcher <your_local_git_repo> [optional_branch]
If you do this, then the Watcher DevStack plugin will try to pull the
python-watcherclient repo from <your_local_git_repo>/../, so either make
sure that is also available or specify WATCHERCLIENT_REPO in the local.conf
file.
Note: if you want to use a specific branch, specify WATCHER_BRANCH in the
local.conf file. By default it will use the master branch.
#. Start stacking from the controller node::
./devstack/stack.sh
#. Start stacking on each of the compute nodes using the same command.
#. Configure the environment for live migration via NFS. See the
`Multi-Node DevStack Environment`_ section for more details.
.. _local.conf.controller: https://github.com/openstack/watcher/tree/master/devstack/local.conf.controller .. _local.conf.controller: https://github.com/openstack/watcher/tree/master/devstack/local.conf.controller
.. _local.conf.compute: https://github.com/openstack/watcher/tree/master/devstack/local.conf.compute .. _local.conf.compute: https://github.com/openstack/watcher/tree/master/devstack/local.conf.compute
@@ -98,13 +160,12 @@ Edit `/etc/libvirt/libvirtd.conf` to make sure the following values are set::
Edit `/etc/default/libvirt-bin`:: Edit `/etc/default/libvirt-bin`::
libvirt_opts="-d -l" libvirtd_opts="-d -l"
Restart the libvirt service:: Restart the libvirt service::
sudo service libvirt-bin restart sudo service libvirt-bin restart
Setting up SSH keys between compute nodes to enable live migration Setting up SSH keys between compute nodes to enable live migration
------------------------------------------------------------------ ------------------------------------------------------------------
@@ -113,8 +174,8 @@ each compute node:
1. The SOURCE root user's public RSA key (likely in /root/.ssh/id_rsa.pub) 1. The SOURCE root user's public RSA key (likely in /root/.ssh/id_rsa.pub)
needs to be in the DESTINATION stack user's authorized_keys file needs to be in the DESTINATION stack user's authorized_keys file
(~stack/.ssh/authorized_keys). This can be accomplished by manually (~stack/.ssh/authorized_keys). This can be accomplished by manually
copying the contents from the file on the SOURCE to the DESTINATION. If copying the contents from the file on the SOURCE to the DESTINATION. If
you have a password configured for the stack user, then you can use the you have a password configured for the stack user, then you can use the
following command to accomplish the same thing:: following command to accomplish the same thing::
@@ -122,7 +183,7 @@ each compute node:
2. The DESTINATION host's public ECDSA key (/etc/ssh/ssh_host_ecdsa_key.pub) 2. The DESTINATION host's public ECDSA key (/etc/ssh/ssh_host_ecdsa_key.pub)
needs to be in the SOURCE root user's known_hosts file needs to be in the SOURCE root user's known_hosts file
(/root/.ssh/known_hosts). This can be accomplished by running the (/root/.ssh/known_hosts). This can be accomplished by running the
following on the SOURCE machine (hostname must be used):: following on the SOURCE machine (hostname must be used)::
ssh-keyscan -H DEST_HOSTNAME | sudo tee -a /root/.ssh/known_hosts ssh-keyscan -H DEST_HOSTNAME | sudo tee -a /root/.ssh/known_hosts
@@ -131,3 +192,13 @@ In essence, this means that every compute node's root user's public RSA key
must exist in every other compute node's stack user's authorized_keys file and must exist in every other compute node's stack user's authorized_keys file and
every compute node's public ECDSA key needs to be in every other compute every compute node's public ECDSA key needs to be in every other compute
node's root user's known_hosts file. node's root user's known_hosts file.
Environment final checkup
-------------------------
If you are willing to make sure everything is in order in your DevStack
environment, you can run the Watcher Tempest tests which will validate its API
but also that you can perform the typical Watcher workflows. To do so, have a
look at the :ref:`Tempest tests <tempest_tests>` section which will explain to
you how to run them.

View File

@@ -4,6 +4,8 @@
https://creativecommons.org/licenses/by/3.0/ https://creativecommons.org/licenses/by/3.0/
.. _watcher_developement_environment:
========================================= =========================================
Set up a development environment manually Set up a development environment manually
========================================= =========================================
@@ -15,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
============= =============
@@ -143,34 +145,13 @@ You should then be able to `import watcher` using Python without issue:
If you can import watcher without a traceback, you should be ready to develop. If you can import watcher without a traceback, you should be ready to develop.
Run Watcher unit tests Run Watcher tests
====================== =================
All unit tests should be run using tox. To run the unit tests under py27 and Watcher provides both :ref:`unit tests <unit_tests>` and
also run the pep8 tests: :ref:`functional/tempest tests <tempest_tests>`. Please refer to :doc:`testing`
to understand how to run them.
.. code-block:: bash
$ workon watcher
(watcher) $ pip install tox
(watcher) $ cd watcher
(watcher) $ tox -epep8 -epy27
You may pass options to the test programs using positional arguments. To run a
specific unit test, this passes the -r option and desired test (regex string)
to os-testr:
.. code-block:: bash
$ workon watcher
(watcher) $ tox -epy27 -- tests.api
When you're done, deactivate the virtualenv:
.. code-block:: bash
$ deactivate
Build the Watcher documentation Build the Watcher documentation
=============================== ===============================
@@ -224,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
@@ -269,6 +250,11 @@ interface.
.. _`python-watcherclient`: https://github.com/openstack/python-watcherclient .. _`python-watcherclient`: https://github.com/openstack/python-watcherclient
There is also an Horizon plugin for Watcher `watcher-dashboard`_ which
allows to interact with Watcher through a web-based interface.
.. _`watcher-dashboard`: https://github.com/openstack/watcher-dashboard
Exercising the Watcher Services locally Exercising the Watcher Services locally
======================================= =======================================

View File

@@ -0,0 +1,219 @@
..
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_action_plugin:
==================
Build a new action
==================
Watcher Applier has an external :ref:`action <action_definition>` plugin
interface which gives anyone the ability to integrate an external
:ref:`action <action_definition>` in order to extend the initial set of actions
Watcher provides.
This section gives some guidelines on how to implement and integrate custom
actions with Watcher.
Creating a new plugin
=====================
First of all you have to extend the base :py:class:`BaseAction` class which
defines a set of abstract methods and/or properties that you will have to
implement:
- The :py:attr:`~.BaseAction.schema` is an abstract property that you have to
implement. This is the first function to be called by the
:ref:`applier <watcher_applier_definition>` before any further processing
and its role is to validate the input parameters that were provided to it.
- The :py:meth:`~.BaseAction.precondition` is called before the execution of
an action. This method is a hook that can be used to perform some
initializations or to make some more advanced validation on its input
parameters. If you wish to block the execution based on this factor, you
simply have to ``raise`` an exception.
- The :py:meth:`~.BaseAction.postcondition` is called after the execution of
an action. As this function is called regardless of whether an action
succeeded or not, this can prove itself useful to perform cleanup
operations.
- The :py:meth:`~.BaseAction.execute` is the main component of an action.
This is where you should implement the logic of your action.
- The :py:meth:`~.BaseAction.revert` allows you to roll back the targeted
resource to its original state following a faulty execution. Indeed, this
method is called by the workflow engine whenever an action raises an
exception.
Here is an example showing how you can write a plugin called ``DummyAction``:
.. code-block:: python
# Filepath = <PROJECT_DIR>/thirdparty/dummy.py
# Import path = thirdparty.dummy
import voluptuous
from watcher.applier.actions import base
class DummyAction(base.BaseAction):
@property
def schema(self):
return voluptuous.Schema({})
def execute(self):
# Does nothing
pass # Only returning False is considered as a failure
def revert(self):
# Does nothing
pass
def precondition(self):
# No pre-checks are done here
pass
def postcondition(self):
# Nothing done here
pass
This implementation is the most basic one. So in order 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` 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
@classmethod
def get_config_opts(cls):
return super(
DummyAction, cls).get_config_opts() + [
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
=====================
Here below is the abstract ``BaseAction`` class that every single action
should implement:
.. autoclass:: watcher.applier.actions.base.BaseAction
:members:
:special-members: __init__
:noindex:
.. py:attribute:: schema
Defines a Schema that the input parameters shall comply to
:returns: A schema declaring the input parameters this action should be
provided along with their respective constraints
(e.g. type, value range, ...)
:rtype: :py:class:`voluptuous.Schema` instance
Register a new entry point
==========================
In order for the Watcher Applier to load your new action, the
action must be registered as a named entry point under the
``watcher_actions`` entry point 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.
Here below is how you would proceed to register ``DummyAction`` using pbr_:
.. code-block:: ini
[entry_points]
watcher_actions =
dummy = thirdparty.dummy:DummyAction
.. _pbr: http://docs.openstack.org/developer/pbr/
Using action plugins
====================
The Watcher Applier service will automatically discover any installed plugins
when it is restarted. If a Python package containing a custom plugin is
installed within the same environment as Watcher, Watcher will automatically
make that plugin available for use.
At this point, you can use your new action plugin in your :ref:`strategy plugin
<implement_strategy_plugin>` if you reference it via the use of the
:py:meth:`~.Solution.add_action` method:
.. code-block:: python
# [...]
self.solution.add_action(
action_type="dummy", # Name of the entry point we registered earlier
applies_to="",
input_parameters={})
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
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
==============================
Watcher provides a basic built-in :ref:`planner <watcher_planner_definition>`
which is only able to process the Watcher built-in actions. Therefore, you will
either have to use an existing third-party planner or :ref:`implement another
planner <implement_planner_plugin>` that will be able to take into account your
new action plugin.

View File

@@ -0,0 +1,100 @@
..
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/
.. _plugin-base_setup:
=======================================
Create a third-party plugin for Watcher
=======================================
Watcher provides a plugin architecture which allows anyone to extend the
existing functionalities by implementing third-party plugins. This process can
be cumbersome so this documentation is there to help you get going as quickly
as possible.
Pre-requisites
==============
We assume that you have set up a working Watcher development environment. So if
this not already the case, you can check out our documentation which explains
how to set up a :ref:`development environment
<watcher_developement_environment>`.
.. _development environment:
Third party project scaffolding
===============================
First off, we need to create the project structure. To do so, we can use
`cookiecutter`_ and the `OpenStack cookiecutter`_ project scaffolder to
generate the skeleton of our project::
$ virtualenv thirdparty
$ source thirdparty/bin/activate
$ pip install cookiecutter
$ cookiecutter https://github.com/openstack-dev/cookiecutter
The last command will ask you for many information, and If you set
``module_name`` and ``repo_name`` as ``thirdparty``, you should end up with a
structure that looks like this::
$ cd thirdparty
$ tree .
.
├── babel.cfg
├── CONTRIBUTING.rst
├── doc
│   └── source
│   ├── conf.py
│   ├── contributing.rst
│   ├── index.rst
│   ├── installation.rst
│   ├── readme.rst
│   └── usage.rst
├── HACKING.rst
├── LICENSE
├── MANIFEST.in
├── README.rst
├── requirements.txt
├── setup.cfg
├── setup.py
├── test-requirements.txt
├── thirdparty
│   ├── __init__.py
│   └── tests
│   ├── base.py
│   ├── __init__.py
│   └── test_thirdparty.py
└── 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
.. _OpenStack cookiecutter: https://github.com/openstack-dev/cookiecutter
.. _python-watcher: https://pypi.python.org/pypi/python-watcher
Implementing a plugin for Watcher
=================================
Now that the project skeleton has been created, you can start the
implementation of your plugin. As of now, you can implement the following
plugins for Watcher:
- A :ref:`goal plugin <implement_goal_plugin>`
- A :ref:`strategy plugin <implement_strategy_plugin>`
- An :ref:`action plugin <implement_action_plugin>`
- A :ref:`planner plugin <implement_planner_plugin>`
- A workflow engine plugin
- A :ref:`cluster data model collector plugin
<implement_cluster_data_model_collector_plugin>`
If you want to learn more on how to implement them, you can refer to their
dedicated documentation.

View File

@@ -0,0 +1,229 @@
..
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_cluster_data_model_collector_plugin:
========================================
Build a new cluster data model collector
========================================
Watcher Decision Engine has an external cluster data model (CDM) plugin
interface which gives anyone the ability to integrate an external cluster data
model collector (CDMC) in order to extend the initial set of cluster data model
collectors Watcher provides.
This section gives some guidelines on how to implement and integrate custom
cluster data model collectors within Watcher.
Creating a new plugin
=====================
In order to create a new model, you have to:
- Extend the :py:class:`~.base.BaseClusterDataModelCollector` class.
- Implement its :py:meth:`~.BaseClusterDataModelCollector.execute` abstract
method to return your entire cluster data model that this method should
build.
- Implement its :py:meth:`~.Goal.notification_endpoints` abstract property to
return the list of all the :py:class:`~.base.NotificationEndpoint` instances
that will be responsible for handling incoming notifications in order to
incrementally update your cluster data model.
First of all, you have to extend the :class:`~.BaseClusterDataModelCollector`
base class which defines the :py:meth:`~.BaseClusterDataModelCollector.execute`
abstract method you will have to implement. This method is responsible for
building an entire cluster data model.
Here is an example showing how you can write a plugin called
``DummyClusterDataModelCollector``:
.. code-block:: python
# Filepath = <PROJECT_DIR>/thirdparty/dummy.py
# Import path = thirdparty.dummy
from watcher.decision_engine.model import model_root
from watcher.decision_engine.model.collector import base
class DummyClusterDataModelCollector(base.BaseClusterDataModelCollector):
def execute(self):
model = model_root.ModelRoot()
# Do something here...
return model
@property
def notification_endpoints(self):
return []
This implementation is the most basic one. So in order to get a better
understanding on how to implement a more advanced cluster data model collector,
have a look at the :py:class:`~.NovaClusterDataModelCollector` class.
Define configuration parameters
===============================
At this point, you have a fully functional cluster data model collector.
By default, cluster data model collectors define a ``period`` option (see
:py:meth:`~.BaseClusterDataModelCollector.get_config_opts`) that corresponds
to the interval of time between each synchronization of the in-memory model.
However, in more complex implementation, you may want to define some
configuration options so one can tune the cluster data model collector to your
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
from watcher.decision_engine.model import model_root
from watcher.decision_engine.model.collector import base
class DummyClusterDataModelCollector(base.BaseClusterDataModelCollector):
def execute(self):
model = model_root.ModelRoot()
# Do something here...
return model
@property
def notification_endpoints(self):
return []
@classmethod
def get_config_opts(cls):
return super(
DummyClusterDataModelCollector, cls).get_config_opts() + [
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}`` (see section :ref:`Register a new
entry point <register_new_cdmc_entrypoint>`). The namespace for CDMC plugins is
``watcher_cluster_data_model_collectors``, so in our case, the ``watcher.conf``
configuration would have to be modified as followed:
.. code-block:: ini
[watcher_cluster_data_model_collectors.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:`~.BaseClusterDataModelCollector.__init__` method.
Abstract Plugin Class
=====================
Here below is the abstract ``BaseClusterDataModelCollector`` class that every
single cluster data model collector should implement:
.. autoclass:: watcher.decision_engine.model.collector.base.BaseClusterDataModelCollector
:members:
:special-members: __init__
:noindex:
.. _register_new_cdmc_entrypoint:
Register a new entry point
==========================
In order for the Watcher Decision Engine to load your new cluster data model
collector, the latter must be registered as a named entry point under the
``watcher_cluster_data_model_collectors`` 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.
Here below is how to register ``DummyClusterDataModelCollector`` using pbr_:
.. code-block:: ini
[entry_points]
watcher_cluster_data_model_collectors =
dummy = thirdparty.dummy:DummyClusterDataModelCollector
.. _pbr: http://docs.openstack.org/developer/pbr/
Add new notification endpoints
==============================
At this point, you have a fully functional cluster data model collector.
However, this CDMC is only refreshed periodically via a background scheduler.
As you may sometimes execute a strategy with a stale CDM due to a high activity
on your infrastructure, you can define some notification endpoints that will be
responsible for incrementally updating the CDM based on notifications emitted
by other services such as Nova. To do so, you can implement and register a new
``DummyEndpoint`` notification endpoint regarding a ``dummy`` event as shown
below:
.. code-block:: python
from watcher.decision_engine.model import model_root
from watcher.decision_engine.model.collector import base
class DummyNotification(base.NotificationEndpoint):
@property
def filter_rule(self):
return filtering.NotificationFilter(
publisher_id=r'.*',
event_type=r'^dummy$',
)
def info(self, ctxt, publisher_id, event_type, payload, metadata):
# Do some CDM modifications here...
pass
class DummyClusterDataModelCollector(base.BaseClusterDataModelCollector):
def execute(self):
model = model_root.ModelRoot()
# Do something here...
return model
@property
def notification_endpoints(self):
return [DummyNotification(self)]
Note that if the event you are trying to listen to is published by a new
service, you may have to also add a new topic Watcher will have to subscribe to
in the ``notification_topics`` option of the ``[watcher_decision_engine]``
section.
Using cluster data model collector plugins
==========================================
The Watcher Decision Engine service will automatically discover any installed
plugins when it is restarted. If a Python package containing a custom plugin is
installed within the same environment as Watcher, Watcher will automatically
make that plugin available for use.
At this point, you can use your new cluster data model plugin in your
:ref:`strategy plugin <implement_strategy_plugin>` by using the
:py:attr:`~.BaseStrategy.collector_manager` property as followed:
.. code-block:: python
# [...]
dummy_collector = self.collector_manager.get_cluster_model_collector(
"dummy") # "dummy" is the name of the entry point we declared earlier
dummy_model = collector.get_latest_cluster_data_model()
# Do some stuff with this model

View 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`.

View File

@@ -0,0 +1,174 @@
..
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_planner_plugin:
===================
Build a new planner
===================
Watcher :ref:`Decision Engine <watcher_decision_engine_definition>` has an
external :ref:`planner <watcher_planner_definition>` plugin interface which
gives anyone the ability to integrate an external :ref:`planner
<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
planners with Watcher.
.. _Decision Engine: watcher_decision_engine_definition
Creating a new plugin
=====================
First of all you have to extend the base :py:class:`~.BasePlanner` class which
defines an abstract method that you will have to implement. The
:py:meth:`~.BasePlanner.schedule` is the method being called by the Decision
Engine to schedule a given solution (:py:class:`~.BaseSolution`) into an
:ref:`action plan <action_plan_definition>` by ordering/sequencing an unordered
set of actions contained in the proposed solution (for more details, see
:ref:`definition of a solution <solution_definition>`).
Here is an example showing how you can write a planner plugin called
``DummyPlanner``:
.. code-block:: python
# Filepath = third-party/third_party/dummy.py
# Import path = third_party.dummy
import uuid
from watcher.decision_engine.planner import base
class DummyPlanner(base.BasePlanner):
def _create_action_plan(self, context, audit_id):
action_plan_dict = {
'uuid': uuid.uuid4(),
'audit_id': audit_id,
'first_action_id': None,
'state': objects.action_plan.State.RECOMMENDED
}
new_action_plan = objects.ActionPlan(context, **action_plan_dict)
new_action_plan.create(context)
new_action_plan.save()
return new_action_plan
def schedule(self, context, audit_id, solution):
# Empty action plan
action_plan = self._create_action_plan(context, audit_id)
# todo: You need to create the workflow of actions here
# and attach it to the action plan
return action_plan
This implementation is the most basic one. So if you want to have more advanced
examples, have a look at the implementation of planners already provided by
Watcher like :py:class:`~.DefaultPlanner`. A list with all available planner
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
# [...]
@classmethod
def get_config_opts(cls):
return super(
DummyPlanner, cls).get_config_opts() + [
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
=====================
Here below is the abstract ``BasePlanner`` class that every single planner
should implement:
.. autoclass:: watcher.decision_engine.planner.base.BasePlanner
:members:
:special-members: __init__
:noindex:
Register a new entry point
==========================
In order for the Watcher Decision Engine to load your new planner, the
latter must be registered as a new entry point under the
``watcher_planners`` 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.
Here below is how you would proceed to register ``DummyPlanner`` using pbr_:
.. code-block:: ini
[entry_points]
watcher_planners =
dummy = third_party.dummy:DummyPlanner
.. _pbr: http://docs.openstack.org/developer/pbr/
Using planner plugins
=====================
The :ref:`Watcher Decision Engine <watcher_decision_engine_definition>` service
will automatically discover any installed plugins when it is started. This
means that if Watcher is already running when you install your plugin, you will
have to restart the related Watcher services. If a Python package containing a
custom plugin is installed within the same environment as Watcher, Watcher will
automatically make that plugin available for use.
At this point, Watcher will use your new planner if you referenced it in the
``planner`` option under the ``[watcher_planner]`` section of your
``watcher.conf`` configuration file when you started it. For example, if you
want to use the ``dummy`` planner you just installed, you would have to
select it as followed:
.. code-block:: ini
[watcher_planner]
planner = dummy
As you may have noticed, only a single planner implementation can be activated
at a time, so make sure it is generic enough to support all your strategies
and actions.

View File

@@ -0,0 +1,210 @@
..
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_scoring_engine_plugin:
==========================
Build a new scoring engine
==========================
Watcher Decision Engine has an external :ref:`scoring engine
<scoring_engine_definition>` plugin interface which gives anyone the ability
to integrate an external scoring engine in order to make use of it in a
:ref:`strategy <strategy_definition>`.
This section gives some guidelines on how to implement and integrate custom
scoring engines 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
==============
Because scoring engines execute a purely mathematical tasks, they typically do
not have any additional dependencies. Additional requirements might be defined
by specific scoring engine implementations. For example, some scoring engines
might require to prepare learning data, which has to be loaded during the
scoring engine startup. Some other might require some external services to be
available (e.g. if the scoring infrastructure is running in the cloud).
Create a new scoring engine plugin
==================================
In order to create a new scoring engine you have to:
- Extend the :py:class:`~.ScoringEngine` class
- Implement its :py:meth:`~.ScoringEngine.get_name` method to return the
**unique** ID of the new scoring engine you want to create. This unique ID
should be the same as the name of :ref:`the entry point we will declare later
on <scoring_engine_plugin_add_entrypoint>`.
- Implement its :py:meth:`~.ScoringEngine.get_description` method to return the
user-friendly description of the implemented scoring engine. It might contain
information about algorithm used, learning data etc.
- Implement its :py:meth:`~.ScoringEngine.get_metainfo` method to return the
machine-friendly metadata about this scoring engine. For example, it could be
a JSON formatted text with information about the data model used, its input
and output data format, column names, etc.
- Implement its :py:meth:`~.ScoringEngine.calculate_score` method to return the
result calculated by this scoring engine.
Here is an example showing how you can write a plugin called ``NewScorer``:
.. code-block:: python
# filepath: thirdparty/new.py
# import path: thirdparty.new
from watcher.decision_engine.scoring import base
class NewScorer(base.ScoringEngine):
def get_name(self):
return 'new_scorer'
def get_description(self):
return ''
def get_metainfo(self):
return """{
"feature_columns": [
"column1",
"column2",
"column3"],
"result_columns": [
"value",
"probability"]
}"""
def calculate_score(self, features):
return '[12, 0.83]'
As you can see in the above example, the
:py:meth:`~.ScoringEngine.calculate_score` method returns a string. Both this
class and the client (caller) should perform all the necessary serialization
or deserialization.
(Optional) Create a new scoring engine container plugin
=======================================================
Optionally, it's possible to implement a container plugin, which can return a
list of scoring engines. This list can be re-evaluated multiple times during
the lifecycle of :ref:`Watcher Decision Engine
<watcher_decision_engine_definition>` and synchronized with :ref:`Watcher
Database <watcher_database_definition>` using the ``watcher-sync`` command line
tool.
Below is an example of a container using some scoring engine implementation
that is simply made of a client responsible for communicating with a real
scoring engine deployed as a web service on external servers:
.. code-block:: python
class NewScoringContainer(base.ScoringEngineContainer):
@classmethod
def get_scoring_engine_list(self):
return [
RemoteScoringEngine(
name='scoring_engine1',
description='Some remote Scoring Engine 1',
remote_url='http://engine1.example.com/score'),
RemoteScoringEngine(
name='scoring_engine2',
description='Some remote Scoring Engine 2',
remote_url='http://engine2.example.com/score'),
]
Abstract Plugin Class
=====================
Here below is the abstract :py:class:`~.ScoringEngine` class:
.. autoclass:: watcher.decision_engine.scoring.base.ScoringEngine
:members:
:special-members: __init__
:noindex:
Abstract Plugin Container Class
===============================
Here below is the abstract :py:class:`~.ScoringContainer` class:
.. autoclass:: watcher.decision_engine.scoring.base.ScoringEngineContainer
:members:
:special-members: __init__
:noindex:
.. _scoring_engine_plugin_add_entrypoint:
Add a new entry point
=====================
In order for the Watcher Decision Engine to load your new scoring engine, it
must be registered as a named entry point under the ``watcher_scoring_engines``
entry point 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:`~.ScoringEngine.get_name` method of your
strategy.
Here below is how you would proceed to register ``NewScorer`` using pbr_:
.. code-block:: ini
[entry_points]
watcher_scoring_engines =
new_scorer = thirdparty.new:NewScorer
To get a better understanding on how to implement a more advanced scoring
engine, have a look at the :py:class:`~.DummyScorer` class. This implementation
is not really using machine learning, but other than that it contains all the
pieces which the "real" implementation would have.
In addition, for some use cases there is a need to register a list (possibly
dynamic, depending on the implementation and configuration) of scoring engines
in a single plugin, so there is no need to restart :ref:`Watcher Decision
Engine <watcher_decision_engine_definition>` every time such list changes. For
these cases, an additional ``watcher_scoring_engine_containers`` entry point
can be used.
For the example how to use scoring engine containers, please have a look at
the :py:class:`~.DummyScoringContainer` and the way it is configured in
``setup.cfg``. For new containers it could be done like this:
.. code-block:: ini
[entry_points]
watcher_scoring_engine_containers =
new_scoring_container = thirdparty.new:NewContainer
.. _pbr: http://docs.openstack.org/developer/pbr/
Using scoring engine plugins
============================
The Watcher Decision Engine service will automatically discover any installed
plugins when it is restarted. If a Python package containing a custom plugin is
installed within the same environment as Watcher, Watcher will automatically
make that plugin available for use.
At this point, Watcher will scan and register inside the :ref:`Watcher Database
<watcher_database_definition>` all the scoring engines you implemented upon
restarting the :ref:`Watcher Decision Engine
<watcher_decision_engine_definition>`.
In addition, ``watcher-sync`` tool can be used to trigger :ref:`Watcher
Database <watcher_database_definition>` synchronization. This might be used for
"dynamic" scoring containers, which can return different scoring engines based
on some external configuration (if they support that).

View File

@@ -0,0 +1,319 @@
..
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_strategy_plugin:
=================================
Build a new optimization strategy
=================================
Watcher Decision Engine has an external :ref:`strategy <strategy_definition>`
plugin interface which gives anyone the ability to integrate an external
strategy in order to make use of placement algorithms.
This section gives some guidelines on how to implement and integrate custom
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
==============
Before using any strategy, you should make sure you have your Telemetry service
configured so that it would provide you all the metrics you need to be able to
use your strategy.
Create a new strategy plugin
============================
In order to create a new strategy, you have to:
- Extend the :py:class:`~.UnclassifiedStrategy` class
- 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 ``NewStrategy``:
.. code-block:: python
# filepath: thirdparty/new.py
# import path: thirdparty.new
import abc
import six
from watcher._i18n import _
from watcher.decision_engine.strategy.strategies import base
class NewStrategy(base.UnclassifiedStrategy):
def __init__(self, osc=None):
super(NewStrategy, self).__init__(osc)
def execute(self, original_model):
self.solution.add_action(action_type="nop",
input_parameters=parameters)
# Do some more stuff here ...
return self.solution
@classmethod
def get_name(cls):
return "new_strategy"
@classmethod
def get_display_name(cls):
return _("New strategy")
@classmethod
def get_translatable_display_name(cls):
return "New strategy"
As you can see in the above example, the :py:meth:`~.BaseStrategy.execute`
method returns a :py:class:`~.BaseSolution` instance as required. This solution
is what wraps the abstract set of actions the strategy recommends to you. This
solution is then processed by a :ref:`planner <watcher_planner_definition>` to
produce an action plan which contains the sequenced flow of actions to be
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 expect to find the same constructor
signature as BaseStrategy to instantiate you strategy. Therefore, you should
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
=====================
Here below is the abstract :py:class:`~.BaseStrategy` class:
.. autoclass:: watcher.decision_engine.strategy.strategies.base.BaseStrategy
:members:
:special-members: __init__
:noindex:
.. _strategy_plugin_add_entrypoint:
Add a new entry point
=====================
In order for the Watcher Decision Engine to load your new strategy, the
strategy must be registered as a named entry point under the
``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.
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 ``NewStrategy`` using pbr_:
.. code-block:: ini
[entry_points]
watcher_strategies =
new_strategy = thirdparty.new:NewStrategy
To get a better understanding on how to implement a more advanced strategy,
have a look at the :py:class:`~.BasicConsolidation` class.
.. _pbr: http://docs.openstack.org/developer/pbr/
Using strategy plugins
======================
The Watcher Decision Engine service will automatically discover any installed
plugins when it is restarted. If a Python package containing a custom plugin is
installed within the same environment as Watcher, Watcher will automatically
make that plugin available for use.
At this point, Watcher will scan and register inside the :ref:`Watcher Database
<watcher_database_definition>` all the strategies (alongside the goals they
should satisfy) you implemented upon restarting the :ref:`Watcher Decision
Engine <watcher_decision_engine_definition>`.
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
they may require a set of metrics which is not yet available within the
Telemetry service. In such a case, please do make sure that you first
check/configure the latter so your new strategy can be fully functional.
Querying metrics
----------------
A large set of metrics, generated by OpenStack modules, can be used in your
strategy implementation. To collect these metrics, Watcher provides a
`Helper`_ to the Ceilometer API, which makes this API reusable and easier
to used.
If you want to use your own metrics database backend, please refer to the
`Ceilometer developer guide`_. Indeed, Ceilometer's pluggable model allows
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
pluggable backend.
Finally, if your strategy requires new metrics not covered by Ceilometer, you
can add them through a Ceilometer `plugin`_.
.. _`Helper`: https://github.com/openstack/watcher/blob/master/watcher/decision_engine/cluster/history/ceilometer.py
.. _`Ceilometer developer guide`: http://docs.openstack.org/developer/ceilometer/architecture.html#storing-the-data
.. _`here`: http://docs.openstack.org/developer/ceilometer/install/dbreco.html#choosing-a-database-backend
.. _`plugin`: http://docs.openstack.org/developer/ceilometer/plugins.html
.. _`Ceilosca`: https://github.com/openstack/monasca-ceilometer/blob/master/ceilosca/ceilometer/storage/impl_monasca.py
Read usage metrics using the Python binding
-------------------------------------------
You can find the information about the Ceilometer Python binding on the
OpenStack `ceilometer client python API documentation
<http://docs.openstack.org/developer/python-ceilometerclient/api.html>`_
To facilitate the process, Watcher provides the ``osc`` attribute to every
strategy which includes clients to major OpenStack services, including
Ceilometer. So to access it within your strategy, you can do the following:
.. code-block:: py
# Within your strategy "execute()"
cclient = self.osc.ceilometer
# TODO: Do something here
Using that you can now query the values for that specific metric:
.. code-block:: py
query = None # e.g. [{'field': 'foo', 'op': 'le', 'value': 34},]
value_cpu = cclient.samples.list(
meter_name='cpu_util',
limit=10, q=query)
Read usage metrics using the Watcher Cluster History Helper
-----------------------------------------------------------
Here below is the abstract ``BaseClusterHistory`` class of the Helper.
.. autoclass:: watcher.decision_engine.cluster.history.base.BaseClusterHistory
:members:
:noindex:
The following code snippet shows how to create a Cluster History class:
.. code-block:: py
from watcher.decision_engine.cluster.history import ceilometer as ceil
query_history = ceil.CeilometerClusterHistory()
Using that you can now query the values for that specific metric:
.. code-block:: py
query_history.statistic_aggregation(resource_id=compute_node.uuid,
meter_name='compute.node.cpu.percent',
period="7200",
aggregate='avg'
)

View File

@@ -0,0 +1,76 @@
..
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/
=================
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
=====
.. list-plugins:: watcher_goals
:detailed:
.. _watcher_scoring_engines:
Scoring Engines
===============
.. list-plugins:: watcher_scoring_engines
:detailed:
.. _watcher_scoring_engine_containers:
Scoring Engine Containers
=========================
.. list-plugins:: watcher_scoring_engine_containers
:detailed:
.. _watcher_strategies:
Strategies
==========
.. list-plugins:: watcher_strategies
:detailed:
.. _watcher_actions:
Actions
=======
.. list-plugins:: watcher_actions
:detailed:
.. _watcher_workflow_engines:
Workflow Engines
================
.. list-plugins:: watcher_workflow_engines
:detailed:
.. _watcher_planners:
Planners
========
.. list-plugins:: watcher_planners
:detailed:
Cluster Data Model Collectors
=============================
.. list-plugins:: watcher_cluster_data_model_collectors
:detailed:

View File

@@ -0,0 +1 @@
.. include:: ../../../rally-jobs/README.rst

View File

@@ -1,203 +0,0 @@
..
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/
=================================
Build a new optimization strategy
=================================
Watcher Decision Engine has an external :ref:`strategy <strategy_definition>`
plugin interface which gives anyone the ability to integrate an external
:ref:`strategy <strategy_definition>` in order to make use of placement
algorithms.
This section gives some guidelines on how to implement and integrate custom
Stategies with Watcher.
Pre-requisites
==============
Before using any strategy, you should make sure you have your Telemetry service
configured so that it would provide you all the metrics you need to be able to
use your strategy.
Creating a new plugin
=====================
First of all you have to:
- Extend the base ``BaseStrategy`` class
- Implement its ``execute`` method
Here is an example showing how you can write a plugin called ``DummyStrategy``:
.. code-block:: python
# Filepath = third-party/third_party/dummy.py
# Import path = third_party.dummy
class DummyStrategy(BaseStrategy):
DEFAULT_NAME = "dummy"
DEFAULT_DESCRIPTION = "Dummy Strategy"
def __init__(self, name=DEFAULT_NAME, description=DEFAULT_DESCRIPTION):
super(DummyStrategy, self).__init__(name, description)
def execute(self, model):
self.solution.add_change_request(
Migrate(vm=my_vm, src_hypervisor=src, dest_hypervisor=dest)
)
# Do some more stuff here ...
return self.solution
As you can see in the above example, the ``execute()`` method returns a
solution as required.
Please note that your strategy class will be instantiated without any
parameter. Therefore, you should make sure not to make any of them required in
your ``__init__`` method.
Abstract Plugin Class
=====================
Here below is the abstract ``BaseStrategy`` class that every single strategy
should implement:
.. automodule:: watcher.decision_engine.strategy.strategies.base
:noindex:
.. autoclass:: BaseStrategy
:members:
:noindex:
Add a new entry point
=====================
In order for the Watcher Decision Engine to load your new strategy, the
strategy must be registered as a named entry point under the
``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.
The name you give to your entry point has to be unique.
Here below is how you would proceed to register ``DummyStrategy`` using pbr_:
.. code-block:: ini
[entry_points]
watcher_strategies =
dummy = third_party.dummy:DummyStrategy
To get a better understanding on how to implement a more advanced strategy,
have a look at the :py:class:`BasicConsolidation` class.
.. _pbr: http://docs.openstack.org/developer/pbr/
Using strategy plugins
======================
The Watcher Decision Engine service will automatically discover any installed
plugins when it is run. If a Python package containing a custom plugin is
installed within the same environment as Watcher, Watcher will automatically
make that plugin available for use.
At this point, the way Watcher will use your new strategy if you reference it
in the ``goals`` under the ``[watcher_goals]`` section of your ``watcher.conf``
configuration file. For example, if you want to use a ``dummy`` strategy you
just installed, you would have to associate it to a goal like this:
.. 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,
there are no guarantees that utilizing them as is will be supported, as
they may require a set of metrics which is not yet available within the
Telemetry service. In such a case, please do make sure that you first
check/configure the latter so your new strategy can be fully functional.
Querying metrics
----------------
A large set of metrics, generated by OpenStack modules, can be used in your
strategy implementation. To collect these metrics, Watcher provides a
`Helper`_ to the Ceilometer API, which makes this API reusable and easier
to used.
If you want to use your own metrics database backend, please refer to the
`Ceilometer developer guide`_. Indeed, Ceilometer's pluggable model allows
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
pluggable backend.
Finally, if your strategy requires new metrics not covered by Ceilometer, you
can add them through a Ceilometer `plugin`_.
.. _`Helper`: https://github.com/openstack/watcher/blob/master/watcher/metrics_engine/cluster_history/ceilometer.py#L31
.. _`Ceilometer developer guide`: http://docs.openstack.org/developer/ceilometer/architecture.html#storing-the-data
.. _`here`: http://docs.openstack.org/developer/ceilometer/install/dbreco.html#choosing-a-database-backend
.. _`plugin`: http://docs.openstack.org/developer/ceilometer/plugins.html
.. _`Ceilosca`: https://github.com/openstack/monasca-ceilometer/blob/master/ceilosca/ceilometer/storage/impl_monasca.py
Read usage metrics using the Python binding
-------------------------------------------
You can find the information about the Ceilometer Python binding on the
OpenStack `ceilometer client python API documentation
<http://docs.openstack.org/developer/python-ceilometerclient/api.html>`_
The first step is to authenticate against the Ceilometer service
(assuming that you already imported the Ceilometer client for Python)
with this call:
.. code-block:: py
cclient = ceilometerclient.client.get_client(VERSION, os_username=USERNAME,
os_password=PASSWORD, os_tenant_name=PROJECT_NAME, os_auth_url=AUTH_URL)
Using that you can now query the values for that specific metric:
.. code-block:: py
value_cpu = cclient.samples.list(meter_name='cpu_util', limit=10, q=query)
Read usage metrics using the Watcher Cluster History Helper
-----------------------------------------------------------
Here below is the abstract ``BaseClusterHistory`` class of the Helper.
.. automodule:: watcher.metrics_engine.cluster_history.api
:noindex:
.. autoclass:: BaseClusterHistory
:members:
:noindex:
The following snippet code shows how to create a Cluster History class:
.. code-block:: py
query_history = CeilometerClusterHistory()
Using that you can now query the values for that specific metric:
.. code-block:: py
query_history.statistic_aggregation(resource_id=hypervisor.uuid,
meter_name='compute.node.cpu.percent',
period="7200",
aggregate='avg'
)

View File

@@ -0,0 +1,50 @@
..
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/
=======
Testing
=======
.. _unit_tests:
Unit tests
==========
All unit tests should be run using `tox`_. To run the same unit tests that are
executing onto `Gerrit`_ which includes ``py34``, ``py27`` and ``pep8``, you
can issue the following command::
$ workon watcher
(watcher) $ pip install tox
(watcher) $ cd watcher
(watcher) $ tox
If you want to only run one of the aforementioned, you can then issue one of
the following::
$ workon watcher
(watcher) $ tox -e py34
(watcher) $ tox -e py27
(watcher) $ tox -e pep8
.. _tox: https://tox.readthedocs.org/
.. _Gerrit: http://review.openstack.org/
You may pass options to the test programs using positional arguments. To run a
specific unit test, you can pass extra options to `os-testr`_ after putting
the ``--`` separator. So using the ``-r`` option followed by a regex string,
you can run the desired test::
$ workon watcher
(watcher) $ tox -e py27 -- -r watcher.tests.api
.. _os-testr: http://docs.openstack.org/developer/os-testr/
When you're done, deactivate the virtualenv::
$ deactivate
.. include:: ../../../watcher_tempest_plugin/README.rst

View File

@@ -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.decision_engine.model.collector.base
.. _cluster_history_definition: .. _cluster_history_definition:
Cluster History Cluster History
=============== ===============
.. watcher-term:: watcher.metrics_engine.cluster_history.api .. watcher-term:: watcher.decision_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,8 +277,15 @@ 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>`_.
.. _scoring_engine_definition:
Scoring Engine
==============
.. watcher-term:: watcher.api.controllers.v1.scoring_engine
.. _sla_definition: .. _sla_definition:
@@ -323,7 +348,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 +389,3 @@ Watcher Planner
=============== ===============
.. watcher-term:: watcher.decision_engine.planner.base .. watcher-term:: watcher.decision_engine.planner.base

View 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

View File

@@ -1,15 +1,15 @@
@startuml @startuml
[*] --> RECOMMENDED: The Watcher Planner\ncreates the Action Plan [*] --> RECOMMENDED: The Watcher Planner\ncreates the Action Plan
RECOMMENDED --> TRIGGERED: Administrator launches\nthe Action Plan RECOMMENDED --> PENDING: Adminisrator launches\nthe Action Plan
TRIGGERED --> ONGOING: The Watcher Applier receives the request\nto launch the Action Plan PENDING --> ONGOING: The Watcher Applier receives the request\nto launch the Action Plan
ONGOING --> FAILED: Something failed while executing\nthe Action Plan in the Watcher Applier ONGOING --> FAILED: Something failed while executing\nthe Action Plan in the Watcher Applier
ONGOING --> SUCCEEDED: The Watcher Applier executed\nthe Action Plan successfully ONGOING --> SUCCEEDED: The Watcher Applier executed\nthe Action Plan successfully
FAILED --> DELETED : Administrator removes\nAction Plan FAILED --> DELETED : Administrator removes\nAction Plan
SUCCEEDED --> DELETED : Administrator removes\nAction Plan SUCCEEDED --> DELETED : Administrator removes\nAction Plan
ONGOING --> CANCELLED : Administrator cancels\nAction Plan ONGOING --> CANCELLED : Administrator cancels\nAction Plan
RECOMMENDED --> CANCELLED : Administrator cancels\nAction Plan RECOMMENDED --> CANCELLED : Administrator cancels\nAction Plan
TRIGGERED --> CANCELLED : Administrator cancels\nAction Plan PENDING --> CANCELLED : Administrator cancels\nAction Plan
CANCELLED --> DELETED CANCELLED --> DELETED
DELETED --> [*] DELETED --> [*]

View File

@@ -0,0 +1,41 @@
@startuml
skinparam maxMessageSize 100
actor "Administrator"
== Initialization ==
"Administrator" -> "Decision Engine" : Start all services
"Decision Engine" -> "Background Task Scheduler" : Start
activate "Background Task Scheduler"
"Background Task Scheduler" -> "Cluster Model Collector Loader"\
: List available cluster data models
"Cluster Model Collector Loader" --> "Background Task Scheduler"\
: list of BaseClusterModelCollector instances
loop for every available cluster data model collector
"Background Task Scheduler" -> "Background Task Scheduler"\
: add periodic synchronization job
create "Jobs Pool"
"Background Task Scheduler" -> "Jobs Pool" : Create sync job
end
deactivate "Background Task Scheduler"
hnote over "Background Task Scheduler" : Idle
== Job workflow ==
"Background Task Scheduler" -> "Jobs Pool" : Trigger synchronization job
"Jobs Pool" -> "Nova Cluster Data Model Collector" : synchronize
activate "Nova Cluster Data Model Collector"
"Nova Cluster Data Model Collector" -> "Nova API"\
: Fetch needed data to build the cluster data model
"Nova API" --> "Nova Cluster Data Model Collector" : Needed data
"Nova Cluster Data Model Collector" -> "Nova Cluster Data Model Collector"\
: Build an in-memory cluster data model
]o<-- "Nova Cluster Data Model Collector" : Done
deactivate "Nova Cluster Data Model Collector"
@enduml

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,31 +1,50 @@
@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 strategy
loop while enough data to build cluster data model activate "Strategy"
"Watcher Decision Engine" -> "Nova API" : get resource state (host, instance, ...) "Strategy" -> "Cluster Data Model Collector" : get cluster data model
"Watcher Decision Engine" <-- "Nova API" : resource state "Cluster Data Model Collector" --> "Strategy"\
end : copy of the in-memory cluster data model
"Watcher Decision Engine" -[#red]> "Watcher Decision Engine": build cluster_data_model loop while enough history data for the strategy
"Watcher Decision Engine" -> "Strategy" : execute(cluster_data_model) "Strategy" -> "Ceilometer API" : get necessary metrics
loop while enough history data for the strategy "Strategy" <-- "Ceilometer API" : aggregated metrics
"Strategy" -> "Ceilometer API": get_aggregated_metrics\n(resource_id,meter_name,period,aggregate_method) end
"Strategy" <-- "Ceilometer API": aggregated metrics "Strategy" -> "Strategy"\
end : compute/set needed actions for the solution so it achieves its goal
"Strategy" -> "Strategy" : compute solution to achieve goal "Strategy" -> "Strategy" : compute/set efficacy indicators for the solution
"Watcher Decision Engine" <-- "Strategy" : solution = array of actions (i.e. not scheduled yet) "Strategy" -> "Strategy" : compute/set the solution global efficacy
create "Watcher Planner" "Decision Engine" <-- "Strategy"\
"Watcher Decision Engine" -[#red]> "Watcher Planner": select appropriate actions scheduler (i.e. Planner implementation) : solution (unordered actions, efficacy indicators and global efficacy)
"Watcher Decision Engine" -> "Watcher Planner": schedule(audit_id, solution) deactivate "Strategy"
"Watcher Planner" -> "Watcher Planner": schedule actions according to\nscheduling rules/policies
"Watcher Decision Engine" <-- "Watcher Planner": new action_plan create "Planner"
"Watcher Decision Engine" -> "Watcher Database" : save new action_plan in database "Decision Engine" -> "Planner" : load actions scheduler
"Watcher Decision Engine" -> "Watcher Database" : update audit.state = SUCCEEDED "Planner" --> "Decision Engine" : planner plugin
"AMQP Bus" <[#blue]- "Watcher Decision Engine" : notify new audit state = SUCCEEDED "Decision Engine" -> "Planner" : schedule actions
activate "Planner"
"Planner" -> "Planner"\
: schedule actions according to scheduling rules/policies
"Decision Engine" <-- "Planner" : new action plan
deactivate "Planner"
"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"
hnote over "Decision Engine" : Idle
@enduml @enduml

View File

@@ -0,0 +1,144 @@
@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(goals) {
primary_key(id: Integer)
uuid : String[36]
name : String[63]
display_name : String[63]
efficacy_specification : JSONEncodedList, nullable
created_at : DateTime
updated_at : DateTime
deleted_at : DateTime
deleted : Integer
}
table(strategies) {
primary_key(id: Integer)
foreign_key(goal_id : Integer)
uuid : String[36]
name : String[63]
display_name : String[63]
parameters_spec : JSONEncodedDict, nullable
created_at : DateTime
updated_at : DateTime
deleted_at : DateTime
deleted : Integer
}
table(audit_templates) {
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(audits) {
primary_key(id: Integer)
foreign_key("goal_id : Integer")
foreign_key("strategy_id : Integer, nullable")
uuid : String[36]
audit_type : String[20]
state : String[20], nullable
deadline : DateTime, nullable
interval : Integer, nullable
parameters : JSONEncodedDict, nullable
host_aggregate : Integer, nullable
created_at : DateTime
updated_at : DateTime
deleted_at : DateTime
deleted : Integer
}
table(action_plans) {
primary_key(id: Integer)
foreign_key("audit_id : Integer, nullable")
foreign_key("strategy_id : Integer")
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(actions) {
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_indicators) {
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
}
table(scoring_engines) {
primary_key(id: Integer)
uuid : String[36]
name : String[63]
description : String[255], nullable
metainfo : Text, nullable
created_at : DateTime
updated_at : DateTime
deleted_at : DateTime
deleted : Integer
}
"goals" <.. "strategies" : Foreign Key
"goals" <.. "audit_templates" : Foreign Key
"strategies" <.. "audit_templates" : Foreign Key
"goals" <.. "audits" : Foreign Key
"strategies" <.. "audits" : Foreign Key
"action_plans" <.. "actions" : Foreign Key
"action_plans" <.. "efficacy_indicators" : Foreign Key
"strategies" <.. "action_plans" : Foreign Key
"audits" <.. "action_plans" : Foreign Key
@enduml

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 48 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

View File

@@ -22,6 +22,7 @@ Watcher project consists of several source code repositories:
* `watcher`_ - is the main repository. It contains code for Watcher API server, * `watcher`_ - is the main repository. It contains code for Watcher API server,
Watcher Decision Engine and Watcher Applier. Watcher Decision Engine and Watcher Applier.
* `python-watcherclient`_ - Client library and CLI client for Watcher. * `python-watcherclient`_ - Client library and CLI client for Watcher.
* `watcher-dashboard`_ - Watcher Horizon plugin.
The documentation provided here is continually kept up-to-date based The documentation provided here is continually kept up-to-date based
on the latest code, and may not represent the state of the project at any on the latest code, and may not represent the state of the project at any
@@ -29,6 +30,7 @@ specific prior release.
.. _watcher: https://git.openstack.org/cgit/openstack/watcher/ .. _watcher: https://git.openstack.org/cgit/openstack/watcher/
.. _python-watcherclient: https://git.openstack.org/cgit/openstack/python-watcherclient/ .. _python-watcherclient: https://git.openstack.org/cgit/openstack/python-watcherclient/
.. _watcher-dashboard: https://git.openstack.org/cgit/openstack/watcher-dashboard/
Developer Guide Developer Guide
=============== ===============
@@ -53,7 +55,9 @@ Getting Started
dev/environment dev/environment
dev/devstack dev/devstack
deploy/configuration deploy/configuration
deploy/conf-files
dev/testing
dev/rally_link
API References API References
-------------- --------------
@@ -69,7 +73,14 @@ Plugins
.. toctree:: .. toctree::
:maxdepth: 1 :maxdepth: 1
dev/strategy-plugin dev/plugin/base-setup
dev/plugin/goal-plugin
dev/plugin/scoring-engine-plugin
dev/plugin/strategy-plugin
dev/plugin/cdmc-plugin
dev/plugin/action-plugin
dev/plugin/planner-plugin
dev/plugins
Admin Guide Admin Guide
@@ -83,6 +94,8 @@ Introduction
deploy/installation deploy/installation
deploy/user-guide deploy/user-guide
deploy/policy
deploy/gmr
Watcher Manual Pages Watcher Manual Pages
==================== ====================

View File

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

View File

@@ -8,6 +8,19 @@
RESTful Web API (v1) RESTful Web API (v1)
==================== ====================
Goals
=====
.. rest-controller:: watcher.api.controllers.v1.goal:GoalsController
:webprefix: /v1/goal
.. autotype:: watcher.api.controllers.v1.goal.GoalCollection
:members:
.. autotype:: watcher.api.controllers.v1.goal.Goal
:members:
Audit Templates Audit Templates
=============== ===============

View 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

View File

@@ -1,5 +1,41 @@
{ {
"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",
"scoring_engine:detail": "rule:default",
"scoring_engine:get": "rule:default",
"scoring_engine:get_all": "rule:default",
"strategy:detail": "rule:default",
"strategy:get": "rule:default",
"strategy:get_all": "rule:default"
} }

View 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

View File

@@ -1,775 +0,0 @@
[DEFAULT]
#
# From oslo.log
#
# 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
# (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>
# 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
# If set to true, the logging level will be set to DEBUG instead of
# the default INFO level. (boolean value)
#debug = false
# 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
# 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
# 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
# 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>
# 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
# 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>
# Enables or disables publication of error events. (boolean value)
#publish_errors = false
# 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
# The format for an instance that is passed with the log message.
# (string value)
#instance_format = "[instance: %(uuid)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>
# The format for an instance UUID that is passed with the log message.
# (string value)
#instance_uuid_format = "[instance: %(uuid)s] "
# Enables or disables fatal status of deprecations. (boolean value)
#fatal_deprecations = false
# 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
#
# From oslo.messaging
#
# Directory for holding IPC sockets. (string value)
#rpc_zmq_ipc_dir = /var/run/openstack
# Number of retries to find free port number before fail with
# ZMQBindError. (integer value)
#rpc_zmq_bind_port_retries = 100
# AMQP topic used for OpenStack notifications. (list value)
# Deprecated group/name - [rpc_notifier2]/topics
# Deprecated group/name - [DEFAULT]/notification_topics
#topics = notifications
# 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
# 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
# Seconds to wait before a cast expires (TTL). Only supported by
# impl_zmq. (integer value)
#rpc_cast_timeout = 30
# Seconds to wait for a response from a call. (integer value)
#rpc_response_timeout = 60
# Use this port to connect to redis host. (port value)
# Minimum value: 1
# Maximum value: 65535
#port = 6379
# The default number of seconds that poll should wait. Poll raises
# timeout exception when timeout expired. (integer value)
#rpc_poll_timeout = 1
# 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>
# Password for Redis server (optional). (string value)
#password =
# Configures zmq-messaging to use proxy with non PUB/SUB patterns.
# (boolean value)
#direct_over_proxy = true
# 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 =
# Size of executor thread pool. (integer value)
# Deprecated group/name - [DEFAULT]/rpc_thread_pool_size
#executor_thread_pool_size = 64
# Size of RPC connection pool. (integer value)
# Deprecated group/name - [DEFAULT]/rpc_conn_pool_size
#rpc_conn_pool_size = 30
# Use PUB/SUB pattern for fanout methods. PUB/SUB always uses proxy.
# (boolean value)
#use_pub_sub = true
# 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>
# 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
# Minimal port number for random ports range. (port value)
# Minimum value: 1
# Maximum value: 65535
#rpc_zmq_min_port = 49152
# Type of concurrency used. Either "native" or "eventlet" (string
# value)
#rpc_zmq_concurrency = eventlet
# Number of ZeroMQ contexts, defaults to 1. (integer value)
#rpc_zmq_contexts = 1
# Maximal port number for random ports range. (integer value)
# Minimum value: 1
# Maximum value: 65536
#rpc_zmq_max_port = 65536
# 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
# Maximum number of ingress messages to locally buffer per topic.
# Default is unlimited. (integer value)
#rpc_zmq_topic_backlog = <None>
[api]
#
# From watcher
#
# The port for the watcher API server (integer value)
#port = 9322
# The listen IP for the watcher API server (string value)
#host = 0.0.0.0
# The maximum number of items returned in a single response from a
# collection resource. (integer value)
#max_limit = 1000
[database]
#
# From oslo.db
#
# 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>
# Add Python stack traces to SQL as comment strings. (boolean value)
# Deprecated group/name - [DEFAULT]/sql_connection_trace
#connection_trace = false
# Seconds between retries of a database transaction. (integer value)
#db_retry_interval = 1
# 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>
# If True, SQLite uses synchronous mode. (boolean value)
# Deprecated group/name - [DEFAULT]/sqlite_synchronous
#sqlite_synchronous = true
# If db_inc_retry_interval is set, the maximum seconds between retries
# of a database operation. (integer value)
#db_max_retry_interval = 10
# Enable the experimental use of database reconnect on connection
# lost. (boolean value)
#use_db_reconnect = false
# 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
# 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
# If True, increases the interval between retries of a database
# operation up to db_max_retry_interval. (boolean value)
#db_inc_retry_interval = true
# The SQLAlchemy connection string to use to connect to the slave
# database. (string value)
#slave_connection = <None>
# 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>
# 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
# 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
# Verbosity of SQL debugging information: 0=None, 100=Everything.
# (integer value)
# Deprecated group/name - [DEFAULT]/sql_connection_debug
#connection_debug = 0
# 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>
# 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
# The back end to use for the database. (string value)
# Deprecated group/name - [DEFAULT]/db_backend
#backend = sqlalchemy
[keystone_authtoken]
#
# From keystonemiddleware.auth_token
#
# A PEM encoded Certificate Authority to use when verifying HTTPs
# connections. Defaults to system CAs. (string value)
#cafile = <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
# (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
# Verify HTTPS connections. (boolean value)
#insecure = false
# 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
# The region in which the identity server can be found. (string value)
#region_name = <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
# Request timeout value for communicating with Identity API server.
# (integer value)
#http_connect_timeout = <None>
# Directory used to cache files related to PKI tokens. (string value)
#signing_dir = <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
# 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>
# Required if identity server requires client certificate (string
# value)
#certfile = <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>
# 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
# API version of the admin Identity API endpoint. (string value)
#auth_version = <None>
# 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
# 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, mandatory if memcache_security_strategy is defined) This
# string is used for key derivation. (string value)
#memcache_secret_key = <None>
# 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
# (Optional) Maximum total number of open connections to every
# memcached server. (integer value)
#memcache_pool_maxsize = 10
# How many times are we trying to reconnect when communicating with
# Identity API Server. (integer value)
#http_request_max_retries = 3
# (Optional) Number of seconds memcached server is considered dead
# before it is tried again. (integer value)
#memcache_pool_dead_retry = 300
# (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
# (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
# Env key for the swift cache. (string value)
#cache = <None>
# Required if identity server requires client certificate (string
# value)
#keyfile = <None>
[matchmaker_redis]
#
# From oslo.messaging
#
# Password for Redis server (optional). (string value)
#password =
# Host to locate redis. (string value)
#host = 127.0.0.1
# Use this port to connect to redis host. (port value)
# Minimum value: 1
# Maximum value: 65535
#port = 6379
[oslo_messaging_amqp]
#
# From oslo.messaging
#
# Timeout for inactive connections (in seconds) (integer value)
# Deprecated group/name - [amqp1]/idle_timeout
#idle_timeout = 0
# Password for message broker authentication (string value)
# Deprecated group/name - [amqp1]/password
#password =
# Identifying certificate PEM file to present to clients (string
# value)
# Deprecated group/name - [amqp1]/ssl_cert_file
#ssl_cert_file =
# Private key PEM file used to sign cert_file certificate (string
# value)
# Deprecated group/name - [amqp1]/ssl_key_file
#ssl_key_file =
# address prefix when sending to any server in group (string value)
# Deprecated group/name - [amqp1]/group_request_prefix
#group_request_prefix = unicast
# Path to directory that contains the SASL configuration (string
# value)
# Deprecated group/name - [amqp1]/sasl_config_dir
#sasl_config_dir =
# Debug: dump AMQP frames to stdout (boolean value)
# Deprecated group/name - [amqp1]/trace
#trace = false
# Password for decrypting ssl_key_file (if encrypted) (string value)
# Deprecated group/name - [amqp1]/ssl_key_password
#ssl_key_password = <None>
# address prefix used when sending to a specific server (string value)
# Deprecated group/name - [amqp1]/server_request_prefix
#server_request_prefix = exclusive
# Name of configuration file (without .conf suffix) (string value)
# Deprecated group/name - [amqp1]/sasl_config_name
#sasl_config_name =
# Name for the AMQP container (string value)
# Deprecated group/name - [amqp1]/container_name
#container_name = <None>
# Accept clients using either SSL or plain TCP (boolean value)
# Deprecated group/name - [amqp1]/allow_insecure_clients
#allow_insecure_clients = false
# CA certificate PEM file to verify server certificate (string value)
# Deprecated group/name - [amqp1]/ssl_ca_file
#ssl_ca_file =
# User name for message broker authentication (string value)
# Deprecated group/name - [amqp1]/username
#username =
# address prefix used when broadcasting to all servers (string value)
# Deprecated group/name - [amqp1]/broadcast_prefix
#broadcast_prefix = broadcast
# Space separated list of acceptable SASL mechanisms (string value)
# Deprecated group/name - [amqp1]/sasl_mechanisms
#sasl_mechanisms =
[oslo_messaging_rabbit]
#
# From oslo.messaging
#
# The RabbitMQ password. (string value)
# Deprecated group/name - [DEFAULT]/rabbit_password
#rabbit_password = guest
# SSL cert file (valid only if SSL enabled). (string value)
# Deprecated group/name - [DEFAULT]/kombu_ssl_certfile
#kombu_ssl_certfile =
# The RabbitMQ login method. (string value)
# Deprecated group/name - [DEFAULT]/rabbit_login_method
#rabbit_login_method = AMQPLAIN
# 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 =
# The RabbitMQ virtual host. (string value)
# Deprecated group/name - [DEFAULT]/rabbit_virtual_host
#rabbit_virtual_host = /
# 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
# How frequently to retry connecting with RabbitMQ. (integer value)
#rabbit_retry_interval = 1
# 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
# Deprecated, use rpc_backend=kombu+memory or rpc_backend=fake
# (boolean value)
# Deprecated group/name - [DEFAULT]/fake_rabbit
#fake_rabbit = false
# 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
# How often times during the heartbeat_timeout_threshold we check the
# heartbeat. (integer value)
#heartbeat_rate = 2
# The RabbitMQ broker address where a single node is used. (string
# value)
# Deprecated group/name - [DEFAULT]/rabbit_host
#rabbit_host = localhost
# The RabbitMQ broker port where a single node is used. (port value)
# Minimum value: 1
# Maximum value: 65535
# Deprecated group/name - [DEFAULT]/rabbit_port
#rabbit_port = 5672
# 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
# 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
# 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
# RabbitMQ HA cluster host:port pairs. (list value)
# Deprecated group/name - [DEFAULT]/rabbit_hosts
#rabbit_hosts = $rabbit_host:$rabbit_port
# Auto-delete queues in AMQP. (boolean value)
# Deprecated group/name - [DEFAULT]/amqp_auto_delete
#amqp_auto_delete = false
# 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
# Connect over SSL for RabbitMQ. (boolean value)
# Deprecated group/name - [DEFAULT]/rabbit_use_ssl
#rabbit_use_ssl = false
# 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 =
# The RabbitMQ userid. (string value)
# Deprecated group/name - [DEFAULT]/rabbit_userid
#rabbit_userid = guest
# SSL key file (valid only if SSL enabled). (string value)
# Deprecated group/name - [DEFAULT]/kombu_ssl_keyfile
#kombu_ssl_keyfile =
[watcher_applier]
#
# From watcher
#
# Number of workers for applier, default value is 1. (integer value)
# Minimum value: 1
#workers = 1
# 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)
#topic_control = watcher.applier.control
# The identifier used by watcher module on the message broker (string
# value)
#publisher_id = watcher.applier.api
# The topic name used for status events, this topic is used so as to
# notifythe others components of the system (string value)
#topic_status = watcher.applier.status
[watcher_decision_engine]
#
# From watcher
#
# The identifier used by watcher module on the message broker (string
# value)
#publisher_id = watcher.decision.api
# The topic name used forcontrol events, this topic used for rpc call
# (string value)
#topic_control = watcher.decision.control
# 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)
#topic_status = watcher.decision.status
[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

42
rally-jobs/README.rst Normal file
View File

@@ -0,0 +1,42 @@
Rally job
=========
We provide, with Watcher, a Rally plugin you can use to benchmark the optimization service.
To launch this task with configured Rally you just need to run:
::
rally task start watcher/rally-jobs/watcher.yaml
Structure
---------
* plugins - directory where you can add rally plugins. Almost everything in
Rally is a plugin. Benchmark context, Benchmark scenario, SLA checks, Generic
cleanup resources, ....
* extra - all files from this directory will be copy pasted to gates, so you
are able to use absolute paths in rally tasks.
Files will be located in ~/.rally/extra/*
* watcher.yaml is a task that is run in gates against OpenStack
deployed by DevStack
Useful links
------------
* How to install: http://docs.openstack.org/developer/rally/install.html
* How to set Rally up and launch your first scenario: https://rally.readthedocs.io/en/latest/tutorial/step_1_setting_up_env_and_running_benchmark_from_samples.html
* More about Rally: https://rally.readthedocs.org/en/latest/
* Rally release notes: https://rally.readthedocs.org/en/latest/release_notes.html
* How to add rally-gates: https://rally.readthedocs.org/en/latest/gates.html
* About plugins: https://rally.readthedocs.org/en/latest/plugins.html
* Plugin samples: https://github.com/openstack/rally/tree/master/samples/plugins

View File

@@ -0,0 +1,67 @@
---
Watcher.create_audit_and_delete:
-
runner:
type: "constant"
times: 10
concurrency: 2
context:
users:
tenants: 2
users_per_tenant: 2
audit_templates:
audit_templates_per_admin: 5
fill_strategy: "round_robin"
params:
- goal:
name: "dummy"
strategy:
name: "dummy"
extra: {}
sla:
failure_rate:
max: 0
Watcher.create_audit_template_and_delete:
-
args:
goal:
name: "dummy"
strategy:
name: "dummy"
extra: {}
runner:
type: "constant"
times: 10
concurrency: 2
sla:
failure_rate:
max: 0
Watcher.list_audit_templates:
-
runner:
type: "constant"
times: 10
concurrency: 2
context:
users:
tenants: 2
users_per_tenant: 2
audit_templates:
audit_templates_per_admin: 5
fill_strategy: "random"
params:
- goal:
name: "workload_balancing"
strategy:
name: "workload_stabilization"
extra: {}
- goal:
name: "dummy"
strategy:
name: "dummy"
extra: {}
sla:
failure_rate:
max: 0

View File

@@ -0,0 +1,8 @@
---
features:
- Added a standard way to both declare and fetch
configuration options so that whenever the
administrator generates the Watcher
configuration sample file, it contains the
configuration options of the plugins that are
currently available.

View File

@@ -0,0 +1,7 @@
---
features:
- Added a generic scoring engine module, which
will standarize interactions with scoring engines
through the common API. It is possible to use the
scoring engine by different Strategies, which
improve the code and data model re-use.

View File

@@ -0,0 +1,6 @@
---
features:
- Added an in-memory cache of the cluster model
built up and kept fresh via notifications from
services of interest in addition to periodic
syncing logic.

View File

@@ -0,0 +1,4 @@
---
features:
- Added a way to add a new action without having to
amend the source code of the default planner.

View File

@@ -0,0 +1,4 @@
---
features:
- Added a way to create periodic audit to be able to
optimize continuously the cloud infrastructure.

View File

@@ -0,0 +1,4 @@
---
features:
- Added a way to compare the efficacy of different
strategies for a give optimization goal.

View File

@@ -0,0 +1,5 @@
---
features:
- Added a way to return the of available goals depending
on which strategies have been deployed on the node
where the decison engine is running.

View File

@@ -0,0 +1,5 @@
---
features:
- Allow decision engine to pass strategy parameters,
like optimization threshold, to selected strategy,
also strategy to provide parameters info to end user.

View File

@@ -0,0 +1,6 @@
---
features:
- Copy all audit templates parameters into
audit instead of having a reference to the
audit template.

View File

@@ -0,0 +1,7 @@
---
features:
- Added a strategy that monitors if there is a higher
load on some hosts compared to other hosts in the
cluster and re-balances the work across hosts to
minimize the standard deviation of the loads in
the cluster.

View File

@@ -0,0 +1,5 @@
---
features:
- Added a new strategy based on the airflow
of servers. This strategy makes decisions
to migrate VMs to make the airflow uniform.

View File

@@ -0,0 +1,4 @@
---
features:
- Added policies to handle user rights
to access Watcher API.

View File

@@ -0,0 +1,7 @@
---
features:
- Added a strategy based on the VM workloads of
hypervisors. This strategy makes decisions to
migrate workloads to make the total VM workloads
of each hypervisor balanced, when the total VM
workloads of hypervisor reaches threshold.

244
releasenotes/source/conf.py Normal file
View 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'

View File

@@ -0,0 +1,10 @@
Welcome to watcher's Release Notes documentation!
=================================================
Contents:
.. toctree::
:maxdepth: 1
unreleased.rst

View File

@@ -0,0 +1,5 @@
==============================
Current Series Release Notes
==============================
.. release-notes::

View File

@@ -2,29 +2,40 @@
# 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.
enum34;python_version=='2.7' or python_version=='2.6' apscheduler # MIT License
jsonpatch>=1.1 enum34;python_version=='2.7' or python_version=='2.6' or python_version=='3.3' # BSD
keystonemiddleware>=2.0.0,!=2.4.0 jsonpatch>=1.1 # BSD
oslo.config>=2.3.0 # Apache-2.0 keystoneauth1>=2.10.0 # Apache-2.0
oslo.db>=2.4.1 # Apache-2.0 keystonemiddleware!=4.1.0,!=4.5.0,>=4.0.0 # Apache-2.0
oslo.i18n>=1.5.0 # Apache-2.0 lxml>=2.3 # BSD
oslo.log>=1.8.0 # Apache-2.0 oslo.concurrency>=3.8.0 # Apache-2.0
oslo.messaging>=1.16.0,!=1.17.0,!=1.17.1,!=2.6.0,!=2.6.1 # Apache-2.0 oslo.cache>=1.5.0 # Apache-2.0
oslo.policy>=0.5.0 # Apache-2.0 oslo.config>=3.14.0 # Apache-2.0
oslo.service>=0.7.0 # Apache-2.0 oslo.context>=2.9.0 # Apache-2.0
oslo.utils>=2.0.0,!=2.6.0 # Apache-2.0 oslo.db!=4.13.1,!=4.13.2,>=4.10.0 # Apache-2.0
PasteDeploy>=1.5.0 oslo.i18n>=2.1.0 # Apache-2.0
pbr>=1.6 oslo.log>=1.14.0 # Apache-2.0
pecan>=1.0.0 oslo.messaging>=5.2.0 # Apache-2.0
python-ceilometerclient>=1.5.0 oslo.policy>=1.9.0 # Apache-2.0
python-cinderclient>=1.3.1 oslo.reports>=0.6.0 # Apache-2.0
python-glanceclient>=0.18.0 oslo.serialization>=1.10.0 # Apache-2.0
python-keystoneclient>=1.6.0,!=1.8.0 oslo.service>=1.10.0 # Apache-2.0
python-neutronclient>=2.6.0 oslo.utils>=3.16.0 # Apache-2.0
python-novaclient>=2.28.1,!=2.33.0 PasteDeploy>=1.5.0 # MIT
python-openstackclient>=1.5.0 pbr>=1.6 # Apache-2.0
six>=1.9.0 pecan!=1.0.2,!=1.0.3,!=1.0.4,>=1.0.0 # BSD
SQLAlchemy>=0.9.9,<1.1.0 PrettyTable<0.8,>=0.7 # BSD
stevedore>=1.5.0 # Apache-2.0 voluptuous>=0.8.9 # BSD License
taskflow>=1.25.0 # Apache-2.0 python-ceilometerclient>=2.5.0 # Apache-2.0
WSME>=0.7 python-cinderclient!=1.7.0,!=1.7.1,>=1.6.0 # Apache-2.0
python-glanceclient!=2.4.0,>=2.3.0 # Apache-2.0
python-keystoneclient!=2.1.0,>=2.0.0 # Apache-2.0
python-neutronclient>=5.1.0 # Apache-2.0
python-novaclient!=2.33.0,>=2.29.0 # Apache-2.0
python-openstackclient>=2.1.0 # Apache-2.0
six>=1.9.0 # MIT
SQLAlchemy<1.1.0,>=1.0.10 # MIT
stevedore>=1.16.0 # Apache-2.0
taskflow>=1.26.0 # Apache-2.0
WebOb>=1.2.3 # MIT
WSME>=0.8 # MIT

View File

@@ -1,11 +1,11 @@
[metadata] [metadata]
name = python-watcher name = python-watcher
summary = Watcher takes advantage of CEP and ML algorithms/metaheuristics to improve physical resources usage through better VM placement. Watcher can improve your cloud optimization by reducing energy footprint and increasing profits. summary = OpenStack Watcher provides a flexible and scalable resource optimization service for multi-tenant OpenStack-based clouds.
description-file = description-file =
README.rst README.rst
author = OpenStack author = OpenStack
author-email = openstack-dev@lists.openstack.org author-email = openstack-dev@lists.openstack.org
home-page = http://www.openstack.org/ home-page = http://docs.openstack.org/developer/watcher/
classifier = classifier =
Environment :: OpenStack Environment :: OpenStack
Intended Audience :: Information Technology Intended Audience :: Information Technology
@@ -17,6 +17,7 @@ classifier =
Programming Language :: Python :: 2.7 Programming Language :: Python :: 2.7
Programming Language :: Python :: 3 Programming Language :: Python :: 3
Programming Language :: Python :: 3.4 Programming Language :: Python :: 3.4
Programming Language :: Python :: 3.5
[files] [files]
packages = packages =
@@ -38,6 +39,7 @@ console_scripts =
watcher-db-manage = watcher.cmd.dbmanage:main watcher-db-manage = watcher.cmd.dbmanage:main
watcher-decision-engine = watcher.cmd.decisionengine:main watcher-decision-engine = watcher.cmd.decisionengine:main
watcher-applier = watcher.cmd.applier:main watcher-applier = watcher.cmd.applier:main
watcher-sync = watcher.cmd.sync:main
tempest.test_plugins = tempest.test_plugins =
watcher_tests = watcher_tempest_plugin.plugin:WatcherTempestPlugin watcher_tests = watcher_tempest_plugin.plugin:WatcherTempestPlugin
@@ -45,10 +47,29 @@ 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_scoring_engines =
dummy_scorer = watcher.decision_engine.scoring.dummy_scorer:DummyScorer
watcher_scoring_engine_containers =
dummy_scoring_container = watcher.decision_engine.scoring.dummy_scoring_container:DummyScoringContainer
watcher_strategies = watcher_strategies =
dummy = watcher.decision_engine.strategy.strategies.dummy_strategy:DummyStrategy dummy = watcher.decision_engine.strategy.strategies.dummy_strategy:DummyStrategy
dummy_with_scorer = watcher.decision_engine.strategy.strategies.dummy_with_scorer:DummyWithScorer
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
@@ -62,8 +83,12 @@ watcher_workflow_engines =
watcher_planners = watcher_planners =
default = watcher.decision_engine.planner.default:DefaultPlanner default = watcher.decision_engine.planner.default:DefaultPlanner
watcher_cluster_data_model_collectors =
compute = watcher.decision_engine.model.collector.nova:NovaClusterDataModelCollector
[pbr] [pbr]
autodoc_index_modules = True warnerrors = true
autodoc_index_modules = true
autodoc_exclude_modules = autodoc_exclude_modules =
watcher.db.sqlalchemy.alembic.env watcher.db.sqlalchemy.alembic.env
watcher.db.sqlalchemy.alembic.versions.* watcher.db.sqlalchemy.alembic.versions.*

3
setup.py Executable file → Normal file
View File

@@ -1,4 +1,3 @@
#!/usr/bin/env python
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
@@ -26,5 +25,5 @@ except ImportError:
pass pass
setuptools.setup( setuptools.setup(
setup_requires=['pbr'], setup_requires=['pbr>=1.8'],
pbr=True) pbr=True)

View File

@@ -2,23 +2,25 @@
# 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.
coverage>=3.6 coverage>=3.6 # Apache-2.0
discover
doc8 # Apache-2.0 doc8 # Apache-2.0
hacking>=0.10.2,<0.11 freezegun # Apache-2.0
mock>=1.2 hacking<0.11,>=0.10.2
oslotest>=1.10.0 # Apache-2.0 mock>=2.0 # BSD
os-testr>=0.1.0 oslotest>=1.10.0 # Apache-2.0
python-subunit>=0.0.18 os-testr>=0.7.0 # Apache-2.0
testrepository>=0.0.18 python-subunit>=0.0.18 # Apache-2.0/BSD
testscenarios>=0.4 testrepository>=0.0.18 # Apache-2.0/BSD
testtools>=1.4.0 testscenarios>=0.4 # Apache-2.0/BSD
testtools>=1.4.0 # MIT
# Doc requirements # Doc requirements
oslosphinx>=2.5.0 # Apache-2.0 oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0
sphinx>=1.1.2,!=1.2.0,!=1.3b1,<1.3 sphinx!=1.3b1,<1.3,>=1.2.1 # BSD
sphinxcontrib-pecanwsme>=0.8 sphinxcontrib-pecanwsme>=0.8 # Apache-2.0
# For PyPI distribution # releasenotes
twine reno>=1.8.0 # Apache2
# bandit
bandit>=1.1.0 # Apache-2.0

40
tox.ini
View File

@@ -1,16 +1,17 @@
[tox] [tox]
minversion = 1.6 minversion = 1.8
envlist = py34,py27,pep8 envlist = py35,py34,py27,pep8
skipsdist = True skipsdist = True
[testenv] [testenv]
usedevelop = True usedevelop = True
whitelist_externals = find whitelist_externals = find
install_command = pip install -U {opts} {packages} install_command =
constraints: pip install -U --force-reinstall -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt?h=stable/newton} {opts} {packages}
pip install -U {opts} {packages}
setenv = setenv =
VIRTUAL_ENV={envdir} VIRTUAL_ENV={envdir}
deps = -r{toxinidir}/requirements.txt deps = -r{toxinidir}/test-requirements.txt
-r{toxinidir}/test-requirements.txt
commands = commands =
find . -type f -name "*.pyc" -delete find . -type f -name "*.pyc" -delete
find . -type d -name "__pycache__" -delete find . -type d -name "__pycache__" -delete
@@ -20,13 +21,14 @@ 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 -s B320
[testenv:venv] [testenv:venv]
setenv = PYTHONHASHSEED=0 setenv = PYTHONHASHSEED=0
commands = {posargs} commands = {posargs}
[testenv:cover] [testenv:cover]
commands = python setup.py testr --coverage --omit="watcher/tests/*" --testr-args='{posargs}' commands = python setup.py testr --coverage --testr-args='{posargs}'
[testenv:docs] [testenv:docs]
setenv = PYTHONHASHSEED=0 setenv = PYTHONHASHSEED=0
@@ -35,36 +37,34 @@ commands =
python setup.py build_sphinx python setup.py build_sphinx
[testenv:debug] [testenv:debug]
commands = oslo_debug_helper {posargs} 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:pypi]
commands =
python setup.py sdist bdist_wheel
twine upload --config-file .pypirc {posargs} dist/*
[testenv:wheel] [testenv:wheel]
commands = python setup.py bdist_wheel commands = python setup.py bdist_wheel
[hacking] [hacking]
import_exceptions = watcher._i18n import_exceptions = watcher._i18n
local-check-factory = watcher.hacking.checks.factory
[doc8] [doc8]
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 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 -s B320

View File

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

View File

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

View File

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

View File

@@ -34,6 +34,8 @@ 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 scoring_engine
from watcher.api.controllers.v1 import strategy
class APIBase(wtypes.Base): class APIBase(wtypes.Base):
@@ -100,6 +102,9 @@ class V1(APIBase):
action_plans = [link.Link] action_plans = [link.Link]
"""Links to the action plans resource""" """Links to the action plans resource"""
scoring_engines = [link.Link]
"""Links to the Scoring Engines resource"""
links = [link.Link] links = [link.Link]
"""Links that point to a specific URL for this version and documentation""" """Links that point to a specific URL for this version and documentation"""
@@ -146,6 +151,14 @@ class V1(APIBase):
'action_plans', '', 'action_plans', '',
bookmark=True) bookmark=True)
] ]
v1.scoring_engines = [link.Link.make_link(
'self', pecan.request.host_url, 'scoring_engines', ''),
link.Link.make_link('bookmark',
pecan.request.host_url,
'scoring_engines', '',
bookmark=True)
]
return v1 return v1
@@ -157,6 +170,8 @@ 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()
scoring_engines = scoring_engine.ScoringEngineController()
strategies = strategy.StrategiesController()
@wsme_pecan.wsexpose(V1) @wsme_pecan.wsexpose(V1)
def get(self): def get(self):
@@ -165,4 +180,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", )

View File

@@ -27,7 +27,7 @@ of the OpenStack :ref:`Cluster <cluster_definition>` such as:
- Live migration of an instance from one compute node to another compute - Live migration of an instance from one compute node to another compute
node with Nova node with Nova
- Changing the power level of a compute node (ACPI level, ...) - Changing the power level of a compute node (ACPI level, ...)
- Changing the current state of an hypervisor (enable or disable) with Nova - Changing the current state of a compute node (enable or disable) with Nova
In most cases, an :ref:`Action <action_definition>` triggers some concrete In most cases, an :ref:`Action <action_definition>` triggers some concrete
commands on an existing OpenStack module (Nova, Neutron, Cinder, Ironic, etc.). commands on an existing OpenStack module (Nova, Neutron, Cinder, Ironic, etc.).
@@ -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,16 +132,10 @@ 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"""
applies_to = wtypes.text
"""Applies to"""
action_type = wtypes.text action_type = wtypes.text
"""Action type""" """Action type"""
input_parameters = wtypes.DictType(wtypes.text, wtypes.text) input_parameters = types.jsontype
"""One or more key/value pairs """ """One or more key/value pairs """
next_uuid = wsme.wsproperty(types.uuid, _get_next_uuid, next_uuid = wsme.wsproperty(types.uuid, _get_next_uuid,
@@ -151,8 +151,6 @@ class Action(base.APIBase):
self.fields = [] self.fields = []
fields = list(objects.Action.fields) fields = list(objects.Action.fields)
# audit_template_uuid is not part of objects.Audit.fields
# because it's an API-only attribute.
fields.append('action_plan_uuid') fields.append('action_plan_uuid')
fields.append('next_uuid') fields.append('next_uuid')
for field in fields: for field in fields:
@@ -193,7 +191,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())
@@ -257,7 +254,7 @@ class ActionsController(rest.RestController):
resource_url=None, resource_url=None,
action_plan_uuid=None, audit_uuid=None): action_plan_uuid=None, audit_uuid=None):
limit = api_utils.validate_limit(limit) limit = api_utils.validate_limit(limit)
sort_dir = api_utils.validate_sort_dir(sort_dir) api_utils.validate_sort_dir(sort_dir)
marker_obj = None marker_obj = None
if marker: if marker:
@@ -288,10 +285,10 @@ class ActionsController(rest.RestController):
sort_key=sort_key, sort_key=sort_key,
sort_dir=sort_dir) sort_dir=sort_dir)
@wsme_pecan.wsexpose(ActionCollection, types.uuid, types.uuid, @wsme_pecan.wsexpose(ActionCollection, types.uuid, int,
int, wtypes.text, wtypes.text, types.uuid, wtypes.text, wtypes.text, types.uuid,
types.uuid) types.uuid)
def get_all(self, action_uuid=None, marker=None, limit=None, def get_all(self, marker=None, limit=None,
sort_key='id', sort_dir='asc', action_plan_uuid=None, sort_key='id', sort_dir='asc', action_plan_uuid=None,
audit_uuid=None): audit_uuid=None):
"""Retrieve a list of actions. """Retrieve a list of actions.
@@ -305,6 +302,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
@@ -312,16 +313,14 @@ class ActionsController(rest.RestController):
marker, limit, sort_key, sort_dir, marker, limit, sort_key, sort_dir,
action_plan_uuid=action_plan_uuid, audit_uuid=audit_uuid) action_plan_uuid=action_plan_uuid, audit_uuid=audit_uuid)
@wsme_pecan.wsexpose(ActionCollection, types.uuid, @wsme_pecan.wsexpose(ActionCollection, types.uuid, int,
types.uuid, int, wtypes.text, wtypes.text, wtypes.text, wtypes.text, types.uuid,
types.uuid, types.uuid) types.uuid)
def detail(self, action_uuid=None, marker=None, limit=None, def detail(self, marker=None, limit=None,
sort_key='id', sort_dir='asc', action_plan_uuid=None, sort_key='id', sort_dir='asc', action_plan_uuid=None,
audit_uuid=None): audit_uuid=None):
"""Retrieve a list of actions with detail. """Retrieve a list of actions with detail.
:param action_uuid: UUID of a action, to get only actions for that
action.
: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.
@@ -331,6 +330,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":
@@ -354,8 +357,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)
@@ -364,6 +369,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
@@ -384,6 +393,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
@@ -416,6 +429,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,

View File

@@ -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,18 @@ 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.common import utils
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):
@@ -127,7 +118,10 @@ class ActionPlan(base.APIBase):
""" """
_audit_uuid = None _audit_uuid = None
_strategy_uuid = None
_strategy_name = 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 +152,72 @@ 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
def _get_strategy(self, value):
if value == wtypes.Unset:
return None
strategy = None
try:
if utils.is_uuid_like(value) or 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_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_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 action plan""" """Unique UUID for this action plan"""
first_action_uuid = wsme.wsproperty( first_action_uuid = wsme.wsproperty(
@@ -170,6 +229,22 @@ 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"""
strategy_uuid = wsme.wsproperty(
wtypes.text, _get_strategy_uuid, _set_strategy_uuid, mandatory=False)
"""Strategy UUID the action plan refers to"""
strategy_name = wsme.wsproperty(
wtypes.text, _get_strategy_name, _set_strategy_name, mandatory=False)
"""The name of the strategy this action plan refers 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,10 +253,8 @@ 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)
fields.append('audit_uuid')
for field in fields: for field in fields:
# Skip fields we do not expose. # Skip fields we do not expose.
if not hasattr(self, field): if not hasattr(self, field):
@@ -189,18 +262,30 @@ class ActionPlan(base.APIBase):
self.fields.append(field) self.fields.append(field)
setattr(self, field, kwargs.get(field, wtypes.Unset)) setattr(self, field, kwargs.get(field, wtypes.Unset))
self.fields.append('audit_id') self.fields.append('audit_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))
fields.append('strategy_uuid')
setattr(self, 'strategy_uuid', kwargs.get('strategy_id', wtypes.Unset))
fields.append('strategy_name')
setattr(self, 'strategy_name', kwargs.get('strategy_id', wtypes.Unset))
setattr(self, 'first_action_uuid',
kwargs.get('first_action_id', wtypes.Unset))
@staticmethod @staticmethod
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(['uuid', 'state', 'updated_at', action_plan.unset_fields_except(
'audit_uuid']) ['uuid', 'state', 'efficacy_indicators', 'global_efficacy',
'updated_at', 'audit_uuid', 'strategy_uuid', 'strategy_name',
'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,
@@ -222,6 +307,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)
@@ -237,8 +328,8 @@ class ActionPlanCollection(collection.Collection):
@staticmethod @staticmethod
def convert_with_links(rpc_action_plans, limit, url=None, expand=False, def convert_with_links(rpc_action_plans, limit, url=None, expand=False,
**kwargs): **kwargs):
collection = ActionPlanCollection() ap_collection = ActionPlanCollection()
collection.action_plans = [ActionPlan.convert_with_links( ap_collection.action_plans = [ActionPlan.convert_with_links(
p, expand) for p in rpc_action_plans] p, expand) for p in rpc_action_plans]
if 'sort_key' in kwargs: if 'sort_key' in kwargs:
@@ -246,13 +337,13 @@ class ActionPlanCollection(collection.Collection):
if kwargs['sort_key'] == 'audit_uuid': if kwargs['sort_key'] == 'audit_uuid':
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.action_plans = sorted( ap_collection.action_plans = sorted(
collection.action_plans, ap_collection.action_plans,
key=lambda action_plan: action_plan.audit_uuid, key=lambda action_plan: action_plan.audit_uuid,
reverse=reverse) reverse=reverse)
collection.next = collection.get_next(limit, url=url, **kwargs) ap_collection.next = ap_collection.get_next(limit, url=url, **kwargs)
return collection return ap_collection
@classmethod @classmethod
def sample(cls): def sample(cls):
@@ -263,6 +354,7 @@ class ActionPlanCollection(collection.Collection):
class ActionPlansController(rest.RestController): class ActionPlansController(rest.RestController):
"""REST controller for Actions.""" """REST controller for Actions."""
def __init__(self): def __init__(self):
super(ActionPlansController, self).__init__() super(ActionPlansController, self).__init__()
@@ -276,10 +368,11 @@ class ActionPlansController(rest.RestController):
def _get_action_plans_collection(self, marker, limit, def _get_action_plans_collection(self, marker, limit,
sort_key, sort_dir, expand=False, sort_key, sort_dir, expand=False,
resource_url=None, audit_uuid=None): resource_url=None, audit_uuid=None,
strategy=None):
limit = api_utils.validate_limit(limit) limit = api_utils.validate_limit(limit)
sort_dir = api_utils.validate_sort_dir(sort_dir) api_utils.validate_sort_dir(sort_dir)
marker_obj = None marker_obj = None
if marker: if marker:
@@ -290,6 +383,12 @@ class ActionPlansController(rest.RestController):
if audit_uuid: if audit_uuid:
filters['audit_uuid'] = audit_uuid filters['audit_uuid'] = audit_uuid
if strategy:
if utils.is_uuid_like(strategy):
filters['strategy_uuid'] = strategy
else:
filters['strategy_name'] = strategy
if sort_key == 'audit_uuid': if sort_key == 'audit_uuid':
sort_db_key = None sort_db_key = None
else: else:
@@ -309,9 +408,9 @@ class ActionPlansController(rest.RestController):
sort_dir=sort_dir) sort_dir=sort_dir)
@wsme_pecan.wsexpose(ActionPlanCollection, types.uuid, int, wtypes.text, @wsme_pecan.wsexpose(ActionPlanCollection, types.uuid, int, wtypes.text,
wtypes.text, types.uuid) wtypes.text, types.uuid, wtypes.text)
def get_all(self, marker=None, limit=None, def get_all(self, marker=None, limit=None,
sort_key='id', sort_dir='asc', audit_uuid=None): sort_key='id', sort_dir='asc', audit_uuid=None, strategy=None):
"""Retrieve a list of action plans. """Retrieve a list of action plans.
:param marker: pagination marker for large data sets. :param marker: pagination marker for large data sets.
@@ -320,14 +419,20 @@ class ActionPlansController(rest.RestController):
:param sort_dir: direction to sort. "asc" or "desc". Default: asc. :param sort_dir: direction to sort. "asc" or "desc". Default: asc.
: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.
:param strategy: strategy UUID or name to filter by
""" """
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, strategy=strategy)
@wsme_pecan.wsexpose(ActionPlanCollection, types.uuid, int, wtypes.text, @wsme_pecan.wsexpose(ActionPlanCollection, types.uuid, int, wtypes.text,
wtypes.text, types.uuid) wtypes.text, types.uuid, wtypes.text)
def detail(self, marker=None, limit=None, def detail(self, marker=None, limit=None,
sort_key='id', sort_dir='asc', audit_uuid=None): sort_key='id', sort_dir='asc', audit_uuid=None, strategy=None):
"""Retrieve a list of action_plans with detail. """Retrieve a list of action_plans with detail.
:param marker: pagination marker for large data sets. :param marker: pagination marker for large data sets.
@@ -336,7 +441,12 @@ class ActionPlansController(rest.RestController):
:param sort_dir: direction to sort. "asc" or "desc". Default: asc. :param sort_dir: direction to sort. "asc" or "desc". Default: asc.
: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.
:param strategy: strategy UUID or name to filter by
""" """
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":
@@ -345,9 +455,8 @@ class ActionPlansController(rest.RestController):
expand = True expand = True
resource_url = '/'.join(['action_plans', 'detail']) resource_url = '/'.join(['action_plans', 'detail'])
return self._get_action_plans_collection( return self._get_action_plans_collection(
marker, limit, marker, limit, sort_key, sort_dir, expand,
sort_key, sort_dir, expand, resource_url, audit_uuid=audit_uuid, strategy=strategy)
resource_url, audit_uuid=audit_uuid)
@wsme_pecan.wsexpose(ActionPlan, types.uuid) @wsme_pecan.wsexpose(ActionPlan, types.uuid)
def get_one(self, action_plan_uuid): def get_one(self, action_plan_uuid):
@@ -358,8 +467,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)
@@ -368,11 +480,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,
@@ -387,9 +500,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(
@@ -402,12 +518,12 @@ class ActionPlansController(rest.RestController):
# transitions that are allowed via PATCH # transitions that are allowed via PATCH
allowed_patch_transitions = [ allowed_patch_transitions = [
(ap_objects.State.RECOMMENDED, (ap_objects.State.RECOMMENDED,
ap_objects.State.TRIGGERED), ap_objects.State.PENDING),
(ap_objects.State.RECOMMENDED, (ap_objects.State.RECOMMENDED,
ap_objects.State.CANCELLED), ap_objects.State.CANCELLED),
(ap_objects.State.ONGOING, (ap_objects.State.ONGOING,
ap_objects.State.CANCELLED), ap_objects.State.CANCELLED),
(ap_objects.State.TRIGGERED, (ap_objects.State.PENDING,
ap_objects.State.CANCELLED), ap_objects.State.CANCELLED),
] ]
@@ -423,7 +539,7 @@ class ActionPlansController(rest.RestController):
initial_state=action_plan_to_update.state, initial_state=action_plan_to_update.state,
new_state=action_plan.state)) new_state=action_plan.state))
if action_plan.state == ap_objects.State.TRIGGERED: if action_plan.state == ap_objects.State.PENDING:
launch_action_plan = True launch_action_plan = True
# Update only the fields that have changed # Update only the fields that have changed
@@ -438,8 +554,8 @@ class ActionPlansController(rest.RestController):
if action_plan_to_update[field] != patch_val: if action_plan_to_update[field] != patch_val:
action_plan_to_update[field] = patch_val action_plan_to_update[field] = patch_val
if (field == 'state' if (field == 'state'and
and patch_val == objects.action_plan.State.TRIGGERED): patch_val == objects.action_plan.State.PENDING):
launch_action_plan = True launch_action_plan = True
action_plan_to_update.save() action_plan_to_update.save()

View File

@@ -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,99 @@ 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.rpcapi import DecisionEngineAPI 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=False)
goal = wtypes.wsattr(wtypes.text, mandatory=False)
strategy = wtypes.wsattr(wtypes.text, mandatory=False)
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)
host_aggregate = wsme.wsattr(wtypes.IntegerType(minimum=1),
mandatory=False)
def as_audit(self, context):
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 not in (wtypes.Unset, None)):
raise exception.AuditIntervalNotAllowed(audit_type=self.audit_type)
if (self.audit_type == objects.audit.AuditType.CONTINUOUS.value and
self.interval in (wtypes.Unset, None)):
raise exception.AuditIntervalNotSpecified(
audit_type=self.audit_type)
# If audit_template_uuid was provided, we will provide any
# variables not included in the request, but not override
# those variables that were included.
if self.audit_template_uuid:
try:
audit_template = objects.AuditTemplate.get(
context, self.audit_template_uuid)
except exception.AuditTemplateNotFound:
raise exception.Invalid(
message=_('The audit template UUID or name specified is '
'invalid'))
at2a = {
'goal': 'goal_id',
'strategy': 'strategy_id',
'host_aggregate': 'host_aggregate'
}
to_string_fields = set(['goal', 'strategy'])
for k in at2a:
if not getattr(self, k):
try:
at_attr = getattr(audit_template, at2a[k])
if at_attr and (k in to_string_fields):
at_attr = str(at_attr)
setattr(self, k, at_attr)
except AttributeError:
pass
return Audit(
audit_type=self.audit_type,
deadline=self.deadline,
parameters=self.parameters,
goal_id=self.goal,
host_aggregate=self.host_aggregate,
strategy_id=self.strategy,
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):
@@ -82,50 +145,89 @@ class Audit(base.APIBase):
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 audit. between the internal object model and the API representation of a audit.
""" """
_audit_template_uuid = None _goal_uuid = None
_audit_template_name = None _goal_name = None
_strategy_uuid = None
_strategy_name = None
def _get_audit_template(self, value): def _get_goal(self, value):
if value == wtypes.Unset: if value == wtypes.Unset:
return None return None
audit_template = None goal = None
try: try:
if utils.is_uuid_like(value) or utils.is_int_like(value): if utils.is_uuid_like(value) or utils.is_int_like(value):
audit_template = objects.AuditTemplate.get( goal = objects.Goal.get(
pecan.request.context, value) pecan.request.context, value)
else: else:
audit_template = objects.AuditTemplate.get_by_name( goal = objects.Goal.get_by_name(
pecan.request.context, value) pecan.request.context, value)
except exception.AuditTemplateNotFound: except exception.GoalNotFound:
pass pass
if audit_template: if goal:
self.audit_template_id = audit_template.id self.goal_id = goal.id
return audit_template return goal
def _get_audit_template_uuid(self): def _get_goal_uuid(self):
return self._audit_template_uuid return self._goal_uuid
def _set_audit_template_uuid(self, value): def _set_goal_uuid(self, value):
if value and self._audit_template_uuid != value: if value and self._goal_uuid != value:
self._audit_template_uuid = None self._goal_uuid = None
audit_template = self._get_audit_template(value) goal = self._get_goal(value)
if audit_template: if goal:
self._audit_template_uuid = audit_template.uuid self._goal_uuid = goal.uuid
def _get_audit_template_name(self): def _get_goal_name(self):
return self._audit_template_name return self._goal_name
def _set_audit_template_name(self, value): def _set_goal_name(self, value):
if value and self._audit_template_name != value: if value and self._goal_name != value:
self._audit_template_name = None self._goal_name = None
audit_template = self._get_audit_template(value) goal = self._get_goal(value)
if audit_template: if goal:
self._audit_template_name = audit_template.name self._goal_name = goal.name
def _get_strategy(self, value):
if value == wtypes.Unset:
return None
strategy = None
try:
if utils.is_uuid_like(value) or 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_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_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 = 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
@@ -134,25 +236,37 @@ class Audit(base.APIBase):
state = wtypes.text state = wtypes.text
"""This audit state""" """This audit state"""
audit_template_uuid = wsme.wsproperty(wtypes.text, goal_uuid = wsme.wsproperty(
_get_audit_template_uuid, wtypes.text, _get_goal_uuid, _set_goal_uuid, mandatory=True)
_set_audit_template_uuid, """Goal UUID the audit template refers to"""
mandatory=True)
"""The UUID of the audit template this audit refers to"""
audit_template_name = wsme.wsproperty(wtypes.text, goal_name = wsme.wsproperty(
_get_audit_template_name, wtypes.text, _get_goal_name, _set_goal_name, mandatory=False)
_set_audit_template_name, """The name of the goal this audit template refers to"""
mandatory=False)
"""The name of the audit template this audit 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"""
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)"""
host_aggregate = wtypes.IntegerType(minimum=1)
"""ID of the Nova host aggregate targeted by the audit template"""
def __init__(self, **kwargs): def __init__(self, **kwargs):
self.fields = [] self.fields = []
fields = list(objects.Audit.fields) fields = list(objects.Audit.fields)
for k in fields: for k in fields:
# Skip fields we do not expose. # Skip fields we do not expose.
if not hasattr(self, k): if not hasattr(self, k):
@@ -160,27 +274,28 @@ class Audit(base.APIBase):
self.fields.append(k) self.fields.append(k)
setattr(self, k, kwargs.get(k, wtypes.Unset)) setattr(self, k, kwargs.get(k, wtypes.Unset))
self.fields.append('audit_template_id') self.fields.append('goal_id')
self.fields.append('strategy_id')
# audit_template_uuid & audit_template_name are not part of fields.append('goal_uuid')
# objects.Audit.fields because they're API-only attributes. setattr(self, 'goal_uuid', kwargs.get('goal_id',
fields.append('audit_template_uuid')
setattr(self, 'audit_template_uuid', kwargs.get('audit_template_id',
wtypes.Unset)) wtypes.Unset))
fields.append('audit_template_name') fields.append('goal_name')
setattr(self, 'audit_template_name', kwargs.get('audit_template_id', setattr(self, 'goal_name', kwargs.get('goal_id',
wtypes.Unset))
fields.append('strategy_uuid')
setattr(self, 'strategy_uuid', kwargs.get('strategy_id',
wtypes.Unset))
fields.append('strategy_name')
setattr(self, 'strategy_name', kwargs.get('strategy_id',
wtypes.Unset)) wtypes.Unset))
@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', 'goal_uuid', 'interval',
'audit_template_name']) 'strategy_uuid', 'host_aggregate',
'goal_name', 'strategy_name'])
# The numeric ID should not be exposed to
# the user, it's internal only.
audit.audit_template_id = wtypes.Unset
audit.links = [link.Link.make_link('self', url, audit.links = [link.Link.make_link('self', url,
'audits', audit.uuid), 'audits', audit.uuid),
@@ -199,13 +314,17 @@ 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(),
sample._audit_template_uuid = '7ae81bb3-dec3-4289-8d6c-da80bd8001ae' interval=7200)
sample.goal_id = '7ae81bb3-dec3-4289-8d6c-da80bd8001ae'
sample.strategy_id = '7ae81bb3-dec3-4289-8d6c-da80bd8001ff'
sample.host_aggregate = 1
return cls._convert_with_links(sample, 'http://localhost:9322', expand) return cls._convert_with_links(sample, 'http://localhost:9322', expand)
@@ -228,12 +347,12 @@ class AuditCollection(collection.Collection):
if 'sort_key' in kwargs: if 'sort_key' in kwargs:
reverse = False reverse = False
if kwargs['sort_key'] == 'audit_template_uuid': if kwargs['sort_key'] == 'goal_uuid':
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.audits = sorted( collection.audits = sorted(
collection.audits, collection.audits,
key=lambda audit: audit.audit_template_uuid, key=lambda audit: audit.goal_uuid,
reverse=reverse) reverse=reverse)
collection.next = collection.get_next(limit, url=url, **kwargs) collection.next = collection.get_next(limit, url=url, **kwargs)
@@ -261,24 +380,34 @@ class AuditsController(rest.RestController):
def _get_audits_collection(self, marker, limit, def _get_audits_collection(self, marker, limit,
sort_key, sort_dir, expand=False, sort_key, sort_dir, expand=False,
resource_url=None, audit_template=None): resource_url=None, goal=None,
strategy=None, host_aggregate=None):
limit = api_utils.validate_limit(limit) limit = api_utils.validate_limit(limit)
sort_dir = api_utils.validate_sort_dir(sort_dir) api_utils.validate_sort_dir(sort_dir)
marker_obj = None marker_obj = None
if marker: if marker:
marker_obj = objects.Audit.get_by_uuid(pecan.request.context, marker_obj = objects.Audit.get_by_uuid(pecan.request.context,
marker) marker)
filters = {} filters = {}
if audit_template: if goal:
if utils.is_uuid_like(audit_template): if utils.is_uuid_like(goal):
filters['audit_template_uuid'] = audit_template filters['goal_uuid'] = goal
else: else:
filters['audit_template_name'] = audit_template # TODO(michaelgugino): add method to get goal by name.
filters['goal_name'] = goal
if sort_key == 'audit_template_uuid': if strategy:
sort_db_key = None if utils.is_uuid_like(strategy):
filters['strategy_uuid'] = strategy
else:
# TODO(michaelgugino): add method to get goal by name.
filters['strategy_name'] = strategy
if sort_key == 'goal_uuid':
sort_db_key = 'goal_id'
elif sort_key == 'strategy_uuid':
sort_db_key = 'strategy_id'
else: else:
sort_db_key = sort_key sort_db_key = sort_key
@@ -294,33 +423,46 @@ class AuditsController(rest.RestController):
sort_dir=sort_dir) sort_dir=sort_dir)
@wsme_pecan.wsexpose(AuditCollection, types.uuid, int, wtypes.text, @wsme_pecan.wsexpose(AuditCollection, types.uuid, int, wtypes.text,
wtypes.text, wtypes.text) wtypes.text, wtypes.text, wtypes.text, int)
def get_all(self, marker=None, limit=None, def get_all(self, marker=None, limit=None,
sort_key='id', sort_dir='asc', audit_template=None): sort_key='id', sort_dir='asc', goal=None,
strategy=None, host_aggregate=None):
"""Retrieve a list of audits. """Retrieve a list of audits.
: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 id.
template, to get only audits for that audit template. :param goal: goal UUID or name to filter by
:param strategy: strategy UUID or name to filter by
:param host_aggregate: Optional host_aggregate
""" """
return self._get_audits_collection(marker, limit, sort_key,
sort_dir,
audit_template=audit_template)
@wsme_pecan.wsexpose(AuditCollection, types.uuid, int, wtypes.text, context = pecan.request.context
wtypes.text) policy.enforce(context, 'audit:get_all',
def detail(self, marker=None, limit=None, action='audit:get_all')
return self._get_audits_collection(marker, limit, sort_key,
sort_dir, goal=goal,
strategy=strategy,
host_aggregate=host_aggregate)
@wsme_pecan.wsexpose(AuditCollection, wtypes.text, types.uuid, int,
wtypes.text, wtypes.text)
def detail(self, goal=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 goal: goal 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: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 +472,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,
goal=goal)
@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,26 +484,51 @@ 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(context)
if self.from_audits: if self.from_audits:
raise exception.OperationNotPermitted raise exception.OperationNotPermitted
if not audit._audit_template_uuid: if not audit._goal_uuid:
raise exception.Invalid( raise exception.Invalid(
message=_('The audit template UUID or name specified is ' message=_('A valid goal_id or audit_template_id '
'invalid')) 'must be provided'))
strategy_uuid = audit.strategy_uuid
no_schema = True
if strategy_uuid is not None:
# validate parameter when predefined strategy in audit template
strategy = objects.Strategy.get(pecan.request.context,
strategy_uuid)
schema = strategy.parameters_spec
if schema:
# validate input parameter with default value feedback
no_schema = False
utils.StrictDefaultValidatingDraft4Validator(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
new_audit = objects.Audit(context, **audit_dict) new_audit = objects.Audit(context, **audit_dict)
new_audit.create(context) new_audit.create(context)
@@ -369,8 +537,9 @@ class AuditsController(rest.RestController):
# trigger decision-engine to run the audit # trigger decision-engine to run the audit
dc_client = 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,8 +554,15 @@ 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:
audit_dict = audit_to_update.as_dict() audit_dict = audit_to_update.as_dict()
audit = Audit(**api_utils.apply_jsonpatch(audit_dict, patch)) audit = Audit(**api_utils.apply_jsonpatch(audit_dict, patch))
@@ -414,8 +590,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()

View File

@@ -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='SERVERS_CONSOLIDATION', 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):
@@ -197,12 +452,14 @@ class AuditTemplatesController(rest.RestController):
'detail': ['GET'], 'detail': ['GET'],
} }
def _get_audit_templates_collection(self, marker, limit, def _get_audit_templates_collection(self, filters, marker, limit,
sort_key, sort_dir, expand=False, sort_key, sort_dir, expand=False,
resource_url=None): resource_url=None):
api_utils.validate_search_filters(
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)
sort_dir = api_utils.validate_sort_dir(sort_dir) api_utils.validate_sort_dir(sort_dir)
marker_obj = None marker_obj = None
if marker: if marker:
@@ -212,6 +469,7 @@ class AuditTemplatesController(rest.RestController):
audit_templates = objects.AuditTemplate.list( audit_templates = objects.AuditTemplate.list(
pecan.request.context, pecan.request.context,
filters,
limit, limit,
marker_obj, sort_key=sort_key, marker_obj, sort_key=sort_key,
sort_dir=sort_dir) sort_dir=sort_dir)
@@ -223,39 +481,76 @@ class AuditTemplatesController(rest.RestController):
sort_key=sort_key, sort_key=sort_key,
sort_dir=sort_dir) sort_dir=sort_dir)
@wsme_pecan.wsexpose(AuditTemplateCollection, types.uuid, int, @wsme_pecan.wsexpose(AuditTemplateCollection, wtypes.text, wtypes.text,
wtypes.text, wtypes.text) types.uuid, int, wtypes.text, wtypes.text)
def get_all(self, 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 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.
""" """
return self._get_audit_templates_collection(marker, limit, sort_key, context = pecan.request.context
sort_dir) 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
@wsme_pecan.wsexpose(AuditTemplateCollection, types.uuid, int, if strategy:
wtypes.text, wtypes.text) if common_utils.is_uuid_like(strategy):
def detail(self, marker=None, limit=None, filters['strategy_uuid'] = strategy
sort_key='id', sort_dir='asc'): else:
filters['strategy_name'] = strategy
return self._get_audit_templates_collection(
filters, marker, limit, sort_key, sort_dir)
@wsme_pecan.wsexpose(AuditTemplateCollection, wtypes.text, wtypes.text,
types.uuid, int, wtypes.text, wtypes.text)
def detail(self, goal=None, strategy=None, marker=None,
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 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 = {}
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'])
return self._get_audit_templates_collection(marker, limit, return self._get_audit_templates_collection(filters, marker, limit,
sort_key, sort_dir, expand, sort_key, sort_dir, expand,
resource_url) resource_url)
@@ -263,33 +558,38 @@ class AuditTemplatesController(rest.RestController):
def get_one(self, audit_template): def get_one(self, audit_template):
"""Retrieve information about the given audit template. """Retrieve information about the given audit template.
:param audit template_uuid: UUID or name of an audit template. :param audit audit_template: UUID or name of an audit template.
""" """
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)
@@ -311,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,
@@ -348,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()

View File

@@ -35,7 +35,7 @@ class Collection(base.APIBase):
"""Return whether collection has more items.""" """Return whether collection has more items."""
return len(self.collection) and len(self.collection) == limit return len(self.collection) and len(self.collection) == limit
def get_next(self, limit, url=None, **kwargs): def get_next(self, limit, url=None, marker_field="uuid", **kwargs):
"""Return a link to the next subset of the collection.""" """Return a link to the next subset of the collection."""
if not self.has_next(limit): if not self.has_next(limit):
return wtypes.Unset return wtypes.Unset
@@ -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], marker_field)}
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

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

View File

@@ -32,8 +32,6 @@ Here are some examples of :ref:`Goals <goal_definition>`:
modification, ... modification, ...
""" """
from oslo_config import cfg
import pecan import pecan
from pecan import rest from pecan import rest
import wsme import wsme
@@ -46,61 +44,73 @@ 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
CONF = cfg.CONF from watcher import objects
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 +127,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 +165,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)
sort_dir = 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)

View File

@@ -0,0 +1,248 @@
# -*- encoding: utf-8 -*-
# Copyright 2016 Intel
# 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.
"""
A :ref:`Scoring Engine <scoring_engine_definition>` is an executable that has
a well-defined input, a well-defined output, and performs a purely mathematical
task. That is, the calculation does not depend on the environment in which it
is running - it would produce the same result anywhere.
Because there might be multiple algorithms used to build a particular data
model (and therefore a scoring engine), the usage of scoring engine might
vary. A metainfo field is supposed to contain any information which might
be needed by the user of a given scoring engine.
"""
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 import objects
class ScoringEngine(base.APIBase):
"""API representation of a scoring engine.
This class enforces type checking and value constraints, and converts
between the internal object model and the API representation of a scoring
engine.
"""
uuid = types.uuid
"""Unique UUID of the scoring engine"""
name = wtypes.text
"""The name of the scoring engine"""
description = wtypes.text
"""A human readable description of the Scoring Engine"""
metainfo = wtypes.text
"""A metadata associated with the scoring engine"""
links = wsme.wsattr([link.Link], readonly=True)
"""A list containing a self link and associated action links"""
def __init__(self, **kwargs):
super(ScoringEngine, self).__init__()
self.fields = []
self.fields.append('uuid')
self.fields.append('name')
self.fields.append('description')
self.fields.append('metainfo')
setattr(self, 'uuid', kwargs.get('uuid', wtypes.Unset))
setattr(self, 'name', kwargs.get('name', wtypes.Unset))
setattr(self, 'description', kwargs.get('description', wtypes.Unset))
setattr(self, 'metainfo', kwargs.get('metainfo', wtypes.Unset))
@staticmethod
def _convert_with_links(se, url, expand=True):
if not expand:
se.unset_fields_except(
['uuid', 'name', 'description', 'metainfo'])
se.links = [link.Link.make_link('self', url,
'scoring_engines', se.uuid),
link.Link.make_link('bookmark', url,
'scoring_engines', se.uuid,
bookmark=True)]
return se
@classmethod
def convert_with_links(cls, scoring_engine, expand=True):
scoring_engine = ScoringEngine(**scoring_engine.as_dict())
return cls._convert_with_links(
scoring_engine, pecan.request.host_url, expand)
@classmethod
def sample(cls, expand=True):
sample = cls(uuid='81bbd3c7-3b08-4d12-a268-99354dbf7b71',
name='sample-se-123',
description='Sample Scoring Engine 123 just for testing')
return cls._convert_with_links(sample, 'http://localhost:9322', expand)
class ScoringEngineCollection(collection.Collection):
"""API representation of a collection of scoring engines."""
scoring_engines = [ScoringEngine]
"""A list containing scoring engine objects"""
def __init__(self, **kwargs):
super(ScoringEngineCollection, self).__init__()
self._type = 'scoring_engines'
@staticmethod
def convert_with_links(scoring_engines, limit, url=None, expand=False,
**kwargs):
collection = ScoringEngineCollection()
collection.scoring_engines = [ScoringEngine.convert_with_links(
se, expand) for se in scoring_engines]
if 'sort_key' in kwargs:
reverse = False
if kwargs['sort_key'] == 'name':
if 'sort_dir' in kwargs:
reverse = True if kwargs['sort_dir'] == 'desc' else False
collection.goals = sorted(
collection.scoring_engines,
key=lambda se: se.name,
reverse=reverse)
collection.next = collection.get_next(limit, url=url, **kwargs)
return collection
@classmethod
def sample(cls):
sample = cls()
sample.scoring_engines = [ScoringEngine.sample(expand=False)]
return sample
class ScoringEngineController(rest.RestController):
"""REST controller for Scoring Engines."""
def __init__(self):
super(ScoringEngineController, self).__init__()
from_scoring_engines = False
"""A flag to indicate if the requests to this controller are coming
from the top-level resource Scoring Engines."""
_custom_actions = {
'detail': ['GET'],
}
def _get_scoring_engines_collection(self, marker, limit,
sort_key, sort_dir, expand=False,
resource_url=None):
limit = api_utils.validate_limit(limit)
api_utils.validate_sort_dir(sort_dir)
marker_obj = None
if marker:
marker_obj = objects.ScoringEngine.get_by_uuid(
pecan.request.context, marker)
filters = {}
sort_db_key = sort_key
scoring_engines = objects.ScoringEngine.list(
context=pecan.request.context,
limit=limit,
marker=marker_obj,
sort_key=sort_db_key,
sort_dir=sort_dir,
filters=filters)
return ScoringEngineCollection.convert_with_links(
scoring_engines,
limit,
url=resource_url,
expand=expand,
sort_key=sort_key,
sort_dir=sort_dir)
@wsme_pecan.wsexpose(ScoringEngineCollection, wtypes.text,
int, wtypes.text, wtypes.text)
def get_all(self, marker=None, limit=None, sort_key='id',
sort_dir='asc'):
"""Retrieve a list of Scoring Engines.
: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: name.
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
"""
context = pecan.request.context
policy.enforce(context, 'scoring_engine:get_all',
action='scoring_engine:get_all')
return self._get_scoring_engines_collection(
marker, limit, sort_key, sort_dir)
@wsme_pecan.wsexpose(ScoringEngineCollection, wtypes.text,
int, wtypes.text, wtypes.text)
def detail(self, marker=None, limit=None, sort_key='id', sort_dir='asc'):
"""Retrieve a list of Scoring Engines with detail.
: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: name.
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
"""
context = pecan.request.context
policy.enforce(context, 'scoring_engine:detail',
action='scoring_engine:detail')
parent = pecan.request.path.split('/')[:-1][-1]
if parent != "scoring_engines":
raise exception.HTTPNotFound
expand = True
resource_url = '/'.join(['scoring_engines', 'detail'])
return self._get_scoring_engines_collection(
marker, limit, sort_key, sort_dir, expand, resource_url)
@wsme_pecan.wsexpose(ScoringEngine, wtypes.text)
def get_one(self, scoring_engine):
"""Retrieve information about the given Scoring Engine.
:param scoring_engine_name: The name of the Scoring Engine.
"""
context = pecan.request.context
policy.enforce(context, 'scoring_engine:get',
action='scoring_engine:get')
if self.from_scoring_engines:
raise exception.OperationNotPermitted
rpc_scoring_engine = api_utils.get_resource(
'ScoringEngine', scoring_engine)
return ScoringEngine.convert_with_links(rpc_scoring_engine)

View File

@@ -0,0 +1,305 @@
# -*- 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>`.
"""
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
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)

View File

@@ -15,7 +15,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import json from oslo_serialization import jsonutils
from oslo_utils import strutils from oslo_utils import strutils
import six import six
import wsme import wsme
@@ -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
@@ -143,7 +118,7 @@ class JsonType(wtypes.UserType):
@staticmethod @staticmethod
def validate(value): def validate(value):
try: try:
json.dumps(value) jsonutils.dumps(value, default=None)
except TypeError: except TypeError:
raise exception.Invalid(_('%s is not JSON serializable') % value) raise exception.Invalid(_('%s is not JSON serializable') % value)
else: else:

View File

@@ -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
@@ -47,7 +50,15 @@ def validate_sort_dir(sort_dir):
raise wsme.exc.ClientSideError(_("Invalid sort direction: %s. " raise wsme.exc.ClientSideError(_("Invalid sort direction: %s. "
"Acceptable values are " "Acceptable values are "
"'asc' or 'desc'") % sort_dir) "'asc' or 'desc'") % sort_dir)
return sort_dir
def validate_search_filters(filters, allowed_fields):
# Very leightweight validation for now
# todo: improve this (e.g. https://www.parse.com/docs/rest/guide/#queries)
for filter_name in filters.keys():
if filter_name not in allowed_fields:
raise wsme.exc.ClientSideError(
_("Invalid filter: %s") % filter_name)
def apply_jsonpatch(doc, patch): def apply_jsonpatch(doc, patch):
@@ -58,3 +69,28 @@ def apply_jsonpatch(doc, patch):
' the resource is not allowed') ' the resource is not allowed')
raise wsme.exc.ClientSideError(msg % p['path']) raise wsme.exc.ClientSideError(msg % p['path'])
return jsonpatch.apply_patch(doc, jsonpatch.JsonPatch(patch)) return jsonpatch.apply_patch(doc, jsonpatch.JsonPatch(patch))
def as_filters_dict(**filters):
filters_dict = {}
for filter_name, filter_value in filters.items():
if filter_value:
filters_dict[filter_name] = filter_value
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)

View File

@@ -18,6 +18,7 @@
from oslo_config import cfg from oslo_config import cfg
from oslo_utils import importutils from oslo_utils import importutils
from pecan import hooks from pecan import hooks
from six.moves import http_client
from watcher.common import context from watcher.common import context
@@ -56,6 +57,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 +75,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):
@@ -92,18 +96,20 @@ class NoExceptionTracebackHook(hooks.PecanHook):
return return
# Do nothing if there is no error. # Do nothing if there is no error.
if 200 <= state.response.status_int < 400: # Status codes in the range 200 (OK) to 399 (400 = BAD_REQUEST) are not
# an error.
if (http_client.OK <= state.response.status_int <
http_client.BAD_REQUEST):
return return
json_body = state.response.json json_body = state.response.json
# Do not remove traceback when server in debug mode (except 'Server' # Do not remove traceback when traceback config is set
# errors when 'debuginfo' will be used for traces). if cfg.CONF.debug:
if cfg.CONF.debug and json_body.get('faultcode') != 'Server':
return return
faultstring = json_body.get('faultstring') faultstring = json_body.get('faultstring')
traceback_marker = 'Traceback (most recent call last):' traceback_marker = 'Traceback (most recent call last):'
if faultstring and (traceback_marker in faultstring): if faultstring and traceback_marker in faultstring:
# Cut-off traceback. # Cut-off traceback.
faultstring = faultstring.split(traceback_marker, 1)[0] faultstring = faultstring.split(traceback_marker, 1)[0]
# Remove trailing newlines and spaces if any. # Remove trailing newlines and spaces if any.

View File

@@ -20,21 +20,21 @@ response with one formatted so the client can parse it.
Based on pecan.middleware.errordocument Based on pecan.middleware.errordocument
""" """
import json
from xml import etree as et from xml import etree as et
from oslo_log import log from oslo_log import log
from oslo_serialization import jsonutils
import six import six
import webob import webob
from watcher._i18n import _ from watcher._i18n import _, _LE
from watcher._i18n import _LE
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
class ParsableErrorMiddleware(object): class ParsableErrorMiddleware(object):
"""Replace error body with something the client can parse.""" """Replace error body with something the client can parse."""
def __init__(self, app): def __init__(self, app):
self.app = app self.app = app
@@ -59,8 +59,7 @@ class ParsableErrorMiddleware(object):
# compute the length. # compute the length.
headers = [(h, v) headers = [(h, v)
for (h, v) in headers for (h, v) in headers
if h not in ('Content-Length', 'Content-Type') if h not in ('Content-Length', 'Content-Type')]
]
# Save the headers in case we need to modify them. # Save the headers in case we need to modify them.
state['headers'] = headers state['headers'] = headers
return start_response(status, headers, exc_info) return start_response(status, headers, exc_info)
@@ -68,23 +67,27 @@ class ParsableErrorMiddleware(object):
app_iter = self.app(environ, replacement_start_response) app_iter = self.app(environ, replacement_start_response)
if (state['status_code'] // 100) not in (2, 3): if (state['status_code'] // 100) not in (2, 3):
req = webob.Request(environ) req = webob.Request(environ)
if (req.accept.best_match(['application/json', 'application/xml'] if (
) == 'application/xml'): req.accept.best_match(
['application/json',
'application/xml']) == 'application/xml'
):
try: try:
# simple check xml is valid # simple check xml is valid
body = [et.ElementTree.tostring( body = [
et.ElementTree.Element('error_message', et.ElementTree.tostring(
text='\n'.join(app_iter)))] et.ElementTree.Element(
'error_message', text='\n'.join(app_iter)))]
except et.ElementTree.ParseError as err: except et.ElementTree.ParseError as err:
LOG.error(_LE('Error parsing HTTP response: %s'), err) LOG.error(_LE('Error parsing HTTP response: %s'), err)
body = [et.ElementTree.tostring( body = ['<error_message>%s'
et.ElementTree.Element('error_message', '</error_message>' % state['status_code']]
text=state['status_code']))]
state['headers'].append(('Content-Type', 'application/xml')) state['headers'].append(('Content-Type', 'application/xml'))
else: else:
if six.PY3: if six.PY3:
app_iter = [i.decode('utf-8') for i in app_iter] app_iter = [i.decode('utf-8') for i in app_iter]
body = [json.dumps({'error_message': '\n'.join(app_iter)})] body = [jsonutils.dumps(
{'error_message': '\n'.join(app_iter)})]
if six.PY3: if six.PY3:
body = [item.encode('utf-8') for item in body] body = [item.encode('utf-8') for item in body]
state['headers'].append(('Content-Type', 'application/json')) state['headers'].append(('Content-Type', 'application/json'))

View File

@@ -28,11 +28,11 @@ LOG = log.getLogger(__name__)
class DefaultActionPlanHandler(base.BaseActionPlanHandler): class DefaultActionPlanHandler(base.BaseActionPlanHandler):
def __init__(self, context, applier_manager, action_plan_uuid): def __init__(self, context, service, action_plan_uuid):
super(DefaultActionPlanHandler, self).__init__() super(DefaultActionPlanHandler, self).__init__()
self.ctx = context self.ctx = context
self.service = service
self.action_plan_uuid = action_plan_uuid self.action_plan_uuid = action_plan_uuid
self.applier_manager = applier_manager
def notify(self, uuid, event_type, state): def notify(self, uuid, event_type, state):
action_plan = ap_objects.ActionPlan.get_by_uuid(self.ctx, uuid) action_plan = ap_objects.ActionPlan.get_by_uuid(self.ctx, uuid)
@@ -43,8 +43,7 @@ class DefaultActionPlanHandler(base.BaseActionPlanHandler):
ev.data = {} ev.data = {}
payload = {'action_plan__uuid': uuid, payload = {'action_plan__uuid': uuid,
'action_plan_state': state} 'action_plan_state': state}
self.applier_manager.topic_status.publish_event(ev.type.name, self.service.publish_status_event(ev.type.name, payload)
payload)
def execute(self): def execute(self):
try: try:
@@ -52,17 +51,15 @@ class DefaultActionPlanHandler(base.BaseActionPlanHandler):
self.notify(self.action_plan_uuid, self.notify(self.action_plan_uuid,
event_types.EventTypes.LAUNCH_ACTION_PLAN, event_types.EventTypes.LAUNCH_ACTION_PLAN,
ap_objects.State.ONGOING) ap_objects.State.ONGOING)
applier = default.DefaultApplier(self.applier_manager, self.ctx) applier = default.DefaultApplier(self.ctx, self.service)
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)

View File

@@ -15,19 +15,40 @@
# 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
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):
def __init__(self): # NOTE(jed): by convention we decided
# 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
# watcher dashboard and will be nested in input_parameters
RESOURCE_ID = 'resource_id'
def __init__(self, config, osc=None):
"""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._applies_to = "" self._osc = osc
@property
def osc(self):
if not self._osc:
self._osc = clients.OpenStackClients()
return self._osc
@property @property
def input_parameters(self): def input_parameters(self):
@@ -38,25 +59,73 @@ class BaseAction(object):
self._input_parameters = p self._input_parameters = p
@property @property
def applies_to(self): def resource_id(self):
return self._applies_to return self.input_parameters[self.RESOURCE_ID]
@applies_to.setter @classmethod
def applies_to(self, a): def get_config_opts(cls):
self._applies_to = a """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
This method can be used to perform an action on a given set of input
parameters to accomplish some type of operation. This operation may
return a boolean value as a result of its execution. If False, this
will be considered as an error and will then trigger the reverting of
the actions.
:returns: A flag indicating whether or not the action succeeded
:rtype: bool
"""
raise NotImplementedError() raise NotImplementedError()
@abc.abstractmethod @abc.abstractmethod
def revert(self): def revert(self):
"""Revert this action
This method should rollback the resource to its initial state in the
event of a faulty execution. This happens when the action raised an
exception during its :py:meth:`~.BaseAction.execute`.
"""
raise NotImplementedError() raise NotImplementedError()
@abc.abstractmethod @abc.abstractmethod
def precondition(self): def pre_condition(self):
"""Hook: called before the execution of an action
This method can be used to perform some initializations or to make
some more advanced validation on its input parameters. So if you wish
to block its execution based on this factor, `raise` the related
exception.
"""
raise NotImplementedError() raise NotImplementedError()
@abc.abstractmethod @abc.abstractmethod
def postcondition(self): def post_condition(self):
"""Hook: called after the execution of an action
This function is called regardless of whether an action succeded or
not. So you can use it to perform cleanup operations.
"""
raise NotImplementedError() raise NotImplementedError()
@abc.abstractproperty
def schema(self):
"""Defines a Schema that the input parameters shall comply to
:returns: A schema declaring the input parameters this action should be
provided along with their respective constraints
:rtype: :py:class:`voluptuous.Schema` instance
"""
raise NotImplementedError()
def validate_parameters(self):
self.schema(self.input_parameters)
return True

Some files were not shown because too many files have changed in this diff Show More