Compare commits

..

155 Commits
1.1.0 ... 1.3.0

Author SHA1 Message Date
licanwei
5817f6833c Fix gate-watcher-python27-ubuntu-xenial FAILURE
There is no exc.HTTPUnauthorized in monascaclient now.

Change-Id: I1942b32d4581d1770819aa083a640f394c17d25a
Closes-Bug: #1706858
2017-07-27 15:20:51 +08:00
Jenkins
67754102c8 Merge "Remove all sphinx warnings" 2017-07-26 01:07:44 +00:00
Jenkins
6545f9c2ad Merge "Update the documention for doc migration" 2017-07-25 13:25:42 +00:00
Jenkins
583c23b9d9 Merge "Update URLs in documents according to document migration" 2017-07-25 13:25:36 +00:00
Jenkins
ea9ab8d6e5 Merge "Update default ironic endpoint type" 2017-07-25 13:20:59 +00:00
Alexander Chadin
8fd57276be Remove all sphinx warnings
This patch set fixes all sphinx warnings and makes
https://docs.openstack.org/watcher/latest/configuration/index.html
available.

Change-Id: I76a715c10cb1d582419ff37869093fa9b8678310
2017-07-25 07:31:53 +00:00
zhengwei6082
1d197e5e8f Update the documention for doc migration
Change-Id: I003f4b0abde102b07646ef180e2506bca19162ff
2017-07-25 06:22:03 +00:00
Hidekazu Nakamura
b33337b7bd Replace voluptuous with JSONSchema in BaseAction
Now that we replaced voluptuous with JSONSchema in all actions,
this patch replaces voluptuous with JSONSchema in BaseAction
and removes validate_parameters method in each action.

Partially Implements: blueprint jsonschema-validation

Change-Id: I07c907ddfa4a568d7fff42776df02218330d56a0
2017-07-25 13:19:13 +09:00
melissaml
d0b9b6ce38 Update URLs in documents according to document migration
Change-Id: I52396f2bed4b0fde889b37cabb6a15225a63cc0d
2017-07-25 10:07:44 +08:00
OpenStack Proposal Bot
3cd847e2ab Updated from global requirements
Change-Id: I5631ff4426442335051cc55c1af2f855b08e19f7
2017-07-23 19:11:17 +00:00
Jenkins
ba907f4905 Merge "Remove testenv for install-guide" 2017-07-22 01:20:07 +00:00
Alexander Chadin
8e787d0a87 Remove testenv for install-guide
Since install-guide is placed in doc/source/
we don't need additional env to build it.

Change-Id: Idcc711a4d5f4a88e960f22795b7e101ae388551e
2017-07-21 12:54:06 +03:00
Jenkins
b45c7b678e Merge "Add title to administrator guide" 2017-07-21 09:37:52 +00:00
Jenkins
ec64e04cc5 Merge "[Doc] Add gnocchi to system architecture diagram" 2017-07-21 07:41:31 +00:00
Jenkins
f206c2d425 Merge "Enable migration to rely on nova-scheduler" 2017-07-21 02:44:31 +00:00
Jenkins
1500bee1c6 Merge "Fix devstack plugin" 2017-07-20 09:27:49 +00:00
Jenkins
9f813fb90d Merge "Update weekly meetings time in docs" 2017-07-20 09:23:31 +00:00
Alexander Chadin
8167eec625 Fix devstack plugin
This patch set fixes devstack plugin by removing head
keyword from watcher-db-manage.

It is related to https://review.openstack.org/#/c/483825/

Change-Id: I3dcea6ae799c94a882e68d66920c5cd87d83d85e
2017-07-20 08:48:57 +00:00
aditi
6731851383 Enable migration to rely on nova-scheduler
This patch removes the requirement to specify destination node,
for migrate action. There can be some strategies that wants to rely
on nova-scheduler for destination node. It will not impact any current
strategy behavior.

Change-Id: Ia12b2f0b8369e632a7959b28e485d86b6cff83e9
Closes-Bug: #1691056
2017-07-20 05:25:08 +00:00
Jenkins
0ddfa278ef Merge "remove useless logging" 2017-07-19 12:42:52 +00:00
Jenkins
aae7699bc5 Merge "Fix dbmanage upgrade and downgrade" 2017-07-19 12:37:59 +00:00
licanwei
8521608e19 Update default ironic endpoint type
The default ironic endpoint type is publicURL in ironicclient.

Change-Id: Ief78c085adafb08abb09c77af7429fbe6c3d7405
2017-07-19 11:04:12 +08:00
Jenkins
2266e2baa3 Merge "New cron type for audit interval" 2017-07-19 02:46:25 +00:00
OpenStack Proposal Bot
e08a0e9af2 Updated from global requirements
Change-Id: Ib1f8d483a2286fde752a5051f8d3a14d1fee63a8
2017-07-18 01:59:22 +00:00
licanwei
844577e9cc remove useless logging
There are many uselees logging in the decision log file:
Arguments dropped when creating context: {u'global_request_id': None}

Change-Id: I7583b2ff6ea1cc15173536ca1cf08cc9f0ecb20d
Closes-Bug: #1704736
2017-07-17 16:15:23 +08:00
Alexander Chadin
0b44492da7 New cron type for audit interval
This patch set adds cron supporting for audit.

Implements: blueprint cron-based-continuous-audits
Change-Id: I8570bebb13332dfba80185e912aeda45b6b4cd70
2017-07-14 11:50:46 +03:00
licanwei
b146e29c39 Fix dbmanage upgrade and downgrade
The argument is '--revision'

Change-Id: Icf3a37ed42dd1dfe4fc9c52525dd4f0fb851417c
2017-07-14 14:12:41 +08:00
Alexander Chadin
0cabd5bd3a Update weekly meetings time in docs
Change-Id: Ic6ebbda653d5b90d6e1e3d866f4180e6d6e07452
2017-07-13 15:52:28 +03:00
Jenkins
728acc091b Merge "Abort operation for live migration" 2017-07-13 09:05:56 +00:00
Hidekazu Nakamura
7340decf73 Add title to administrator guide
As of now, Watcher administrator guide has no title[1]
This patch fix it.

[1] https://docs.openstack.org/watcher/latest/admin/

Change-Id: I995faf02f9217187b824974ee391918dfd6ed920
2017-07-13 16:01:26 +09:00
Jenkins
84e8eb4cb0 Merge "Cinder model integration" 2017-07-12 14:55:10 +00:00
aditi
5283871af5 Abort operation for live migration
This patch adds abort operation for live migration
to support abort in cancel action plan.

Change-Id: I458e93d9bd09dc4cf80cc941104129fc7600a6b1
Partially-Implements: blueprint cancel-action-plan
2017-07-12 06:09:23 +00:00
Hidekazu Nakamura
916fd73186 [Doc] Add gnocchi to system architecture diagram
This patch set adds gnocchi to system architecture diagram.

Change-Id: I8e3a53910ad9ea861f174a6f178199626ed2247f
2017-07-11 00:16:55 +09:00
xiaoxue
63c1aabdd2 bug fix: Can't get sample through CeilometerHelper
wrong parameter passing of function "query_sample"
the default parameter will be recognized as "user_id"

Change-Id: I293b130c3f709dc93cd4b1b7382ae8895a54765d
Closes-Bug: #1703297
2017-07-10 14:37:33 +08:00
Jenkins
03569db6c3 Merge "move doc/source/webapi content to doc/source/api" 2017-07-07 08:14:07 +00:00
Jenkins
19cf05fd75 Merge "Replace voluptuous with JSONSchema to validate change_node_power_state" 2017-07-07 05:49:51 +00:00
Jenkins
251ca83ddc Merge "Update Documentation link in README" 2017-07-07 05:47:06 +00:00
akihito-inoh
5b349b4e89 Replace voluptuous with JSONSchema to validate change_node_power_state
Now in watcher,both JSONSchema and voluptuous are used to validate
JSON payloads. We want to remove voluptuous and Use
JSONSchema as our only JSON validation tool to keep consistence
and also to make it easier to expose the validation schema
through our API in future work.

In this patch, we replace voluptuous with JSONSchema to validate
the change_node_power_state action in watcher applier.

Partially Implements: blueprint jsonschema-validation

Change-Id: If9ffe5e0b107e0da5673247e4d5ec9917790827f
2017-07-06 15:58:49 +00:00
Alexander Chadin
b0d03ae6b8 move doc/source/webapi content to doc/source/api
Change-Id: I5b1023f2d7ac2760895bba8f7d0cb10260061754
2017-07-06 15:55:12 +03:00
Jenkins
78de029a57 Merge "Adapt watcher documentation for new standards" 2017-07-06 10:23:29 +00:00
Hidekazu Nakamura
489356da3a Cinder model integration
This patch adds Cinder model integration.

Change-Id: I31d5bc5e2bbed885d074d66bf7999d42cec15f10
Implements: blueprint cinder-model-integration
2017-07-05 08:55:10 +09:00
Alexander Chadin
db4339c371 Update Documentation link in README
This patch set updates documentation link in README
because of doc migrations.

Depends-On: Iecb4f60efb015a56b9b37331859848b287112842
Change-Id: I5b8a5ec8a328ae275fe6271965ea0077ae301814
2017-07-04 16:01:07 +03:00
Alexander Chadin
c7ec186576 Adapt watcher documentation for new standards
This patch set makes the following changes:

 * Add index file to each subdirectory of doc/source
 * Update doc/source/index.rst with new links
 * Move content of install-guide to the doc/source/install
 * Minor changes

Depends-On: Ifc5512c0e2373cf3387e0e0498268eab092e52bb
Change-Id: Iecb4f60efb015a56b9b37331859848b287112842
2017-07-04 15:49:24 +03:00
Hidekazu Nakamura
12bdfca0d9 Replace default neutron endpoint type
The default neutron endpoint type is publicURL in neutronclient.
This patch replaces default neutron endpoint type from
internalURL to publicURL.

Change-Id: I8162b6178051df5f086488ecfb5d0bdc569ef9cd
Related-Bug: #1686298
2017-07-01 19:51:06 +09:00
Jenkins
5f179609d0 Merge "Replace default glance endpoint type" 2017-06-30 17:21:28 +00:00
aditi
a842bc1c91 switch to openstackdocs theme.
Change-Id: I95a1f6813225134fafdc22384fb8a822ab9e013d
2017-06-29 08:50:03 +00:00
Hidekazu Nakamura
5eb1d91335 Replace default glance endpoint type
The default glance endpoint type is publicURL in glanceclient.
This patch replaces default glance endpoint type from
internalURL to publicURL.

Change-Id: I39451ba89f191693475a694bd5c9045bf3bf539a
Related-Bug: #1686298
2017-06-29 16:42:39 +09:00
Jenkins
264b0fe9a1 Merge "Noisy Neighbor Strategy" 2017-06-28 12:06:15 +00:00
Jenkins
a487a4efc8 Merge "Fix test_list_with_limit failed" 2017-06-28 11:39:51 +00:00
Jenkins
034f0bf68a Merge "Replace the usage of 'manager' with 'os_primary'" 2017-06-28 08:51:02 +00:00
licanwei
87b95bb639 Fix test_list_with_limit failed
There is no action plan if creating audit was cancelled.
In this case, we just return none.

Change-Id: Ia7a93dab5978d181557d7dd7499e83655aec6f40
Closes-Bug: #1700901
2017-06-28 14:40:46 +08:00
Jenkins
e081ac91b4 Merge "Updated from global requirements" 2017-06-28 06:09:56 +00:00
rajat29
3574dba9da Replace the usage of 'manager' with 'os_primary'
In tempest, alias 'manager' has been moved to 'os_primary'
in version Pike, and it will be removed in version Queens.
This patch is to replace the usage of 'manager' with 'os_primary'.

For other details, please check [1] and [2]
[1] https://review.openstack.org/#/c/468036/
[2] https://review.openstack.org/#/c/463484/

Change-Id: I582758c42f61a85a2fd4aac5f7a97cd6021ecf68
2017-06-28 10:36:43 +05:30
Jenkins
8fc9c6c1d8 Merge "avoid repeated actions in the solution" 2017-06-27 23:48:16 +00:00
OpenStack Proposal Bot
51f17f1d7d Updated from global requirements
Change-Id: I5298796b492396d0ecfccbcf601c11069c20b624
2017-06-27 12:25:18 +00:00
Jenkins
313f156394 Merge "Update .gitignore" 2017-06-27 11:36:03 +00:00
suzhengwei
129de26419 avoid repeated actions in the solution
Change-Id: If163aee969b51764d69d42655c05e0651e4f150c
2017-06-27 09:02:27 +00:00
Jenkins
d38bc4e716 Merge "node.status for vm_workload_consolidation" 2017-06-27 08:34:33 +00:00
Jenkins
6ffa5feaaf Merge "Fix get_action_plan_list filter error" 2017-06-27 08:33:50 +00:00
Jenkins
bdd3a6ab89 Merge "Pass environment variables of proxy to tox" 2017-06-27 07:37:31 +00:00
iswarya_vakati
8c5f363844 Update .gitignore
Because egg* already ignores egg-info.

Change-Id: I0afedfa82bc1c91d40e42f21fdcd90eccc0faacf
2017-06-26 17:30:19 +05:30
Jenkins
e61140edfb Merge "Enable some off-by-default checks" 2017-06-25 12:07:09 +00:00
Kien Nguyen
abc019829f Pass environment variables of proxy to tox
When a development environment is under a proxy, tox is failed even if
environment variables of the proxy are set.

This patch fix this problem. Refer patch set [1]

[1] https://review.openstack.org/#/c/189569

Change-Id: I6c0b896a6de1b7193dd4b77b6bc4433d0c75732d
2017-06-25 18:36:45 +07:00
Jenkins
0dd5826dd3 Merge "fix Keyerror in test_nova_cdmc" 2017-06-24 03:41:26 +00:00
zhengwei6082
227a9e9b63 Enable some off-by-default checks
Some of the available checks are disabled by default, like:
[H106] Don’t put vim configuration in source files
[H203] Use assertIs(Not)None to check for None

Change-Id: I369cff1c0f7f3cd3f5bcf3785b6904c9326c6759
2017-06-23 17:35:08 +08:00
Jenkins
5b6768140f Merge "Replace voluptuous with JSONSchema to validate sleep action" 2017-06-21 14:39:14 +00:00
Jenkins
1ac7fbec34 Merge "Updated from global requirements" 2017-06-21 10:01:02 +00:00
Jenkins
ebb8885ece Merge "Add action for compute node power on/off" 2017-06-21 09:59:13 +00:00
Jenkins
3fb4cadd76 Merge "Replace voluptuous with JSONSchema to validate resize action" 2017-06-21 09:45:39 +00:00
OpenStack Proposal Bot
c1d3f8094b Updated from global requirements
Change-Id: I1a5694edcdeefad2ee93b0c893f79c1ddf792692
2017-06-19 21:15:54 +00:00
licanwei
59649f6a81 Fix get_action_plan_list filter error
When using 'query.filter_by(deleted_at=None)'
Will generate the incorrect SQL 'audits.deleted_at', as below:
SELECT * FROM action_plans INNER JOIN audits ON audits.id =
action_plans.audit_id WHERE audits.uuid = '' AND
audits.deleted_at IS NULL ORDER BY action_plans.id ASC

The correct filter field is 'action_plans.deleted_at'

Change-Id: Ie05f35233f78e82bc7af2c26c9effd62ea5f86ab
Closes-Bug: #1698720
2017-06-19 16:23:05 +08:00
suzhengwei
045404095d node.status for vm_workload_consolidation
The primary usage of "node.state" is wrong, it should be 'node.status'.
So correct it and refactor the method 'get_state_str'.

Change-Id: I9004c85bfb09f9b41fc56ecb5a56122d523a661f
2017-06-16 07:04:08 +00:00
Prudhvi Rao Shedimbi
606a340b5b Noisy Neighbor Strategy
Added strategy to identify and migrate a Noisy Neighbor - a low priority VM
that negatively affects peformance of a high priority VM by over utilizing
Last Level Cache.

Partially Implements: blueprint noisy-neighbor-strategy

Change-Id: Ia13f7e91a695024410b8f3b3d3d1646a0dd687d4
2017-06-15 14:51:10 -07:00
OpenStack Proposal Bot
35fdbbe16e Updated from global requirements
Change-Id: I1fea346ba9a7f592d456101e09d00c45a91ff311
2017-06-15 16:37:35 +00:00
Yumeng_Bao
2db279e019 fix Keyerror in test_nova_cdmc
fix keyerror in watcher.tests.decision_engine.cluster.test_nova_cdmc.
TestNovaClusterDataModelCollector.test_nova_cdmc_execute

Change-Id: I44810ae539d8783173dfbb9dd462101e21025802
Closes-Bug: #1698123
2017-06-15 19:08:59 +08:00
Jenkins
986ba9872f Merge "Replace voluptuous with JSONSchema to validate migration action" 2017-06-15 06:21:05 +00:00
Jenkins
44a5a1573c Merge "Replace voluptuous with JSONSchema to validate nop action" 2017-06-14 14:59:22 +00:00
Jenkins
fe34b420a5 Merge "Remove deprecated oslo_messaging.get_transport" 2017-06-14 13:13:34 +00:00
Jenkins
43da4a6d21 Merge "Replace voluptuous with JSONSchema to validate change_nova_service_state" 2017-06-14 13:12:34 +00:00
Jenkins
7ba1bf2237 Merge "Remove log translations and hacking" 2017-06-14 07:41:50 +00:00
Jenkins
206bb413e2 Merge "fix multinode tempest test failure" 2017-06-14 02:27:25 +00:00
licanwei
1188d4263d Add action for compute node power on/off
Add action for compute node power on/off

Change-Id: I7b0c0a7500f72f49af8201547640b2322c64baff
Implements: blueprint add-power-on-off
2017-06-13 15:07:33 +08:00
Yumeng_Bao
1451d9c134 Replace voluptuous with JSONSchema to validate migration action
Now in watcher,both JSONSchema and voluptuous are used to validate
JSON payloads. We want to remove voluptuous and Use
JSONSchema as our only JSON validation tool to keep consistence
and also to make it easier to expose the validation schema
through our API in future work.

In this patch, we replace voluptuous with JSONSchema to validate
the migration action in watcher applier.

Partially Implements: blueprint jsonschema-validation

Change-Id: I02bff5db9bd06567bcc33b61a316c42c805bb20e
2017-06-12 17:40:07 +08:00
OpenStack Proposal Bot
6d739ae1d0 Updated from global requirements
Change-Id: I8efcebd4dbbaa7fd35085d6378907e5ffa0dd27b
2017-06-10 11:50:38 +00:00
Yumeng_Bao
f2751b4818 Replace voluptuous with JSONSchema to validate change_nova_service_state
Now in watcher,both JSONSchema and voluptuous are used to validate
JSON payloads. We want to remove voluptuous and Use
JSONSchema as our only JSON validation tool to keep consistence
and also to make it easier to expose the validation schema
through our API in future work.

In this patch, we replace voluptuous with JSONSchema to validate
the change_nova_service_state action in watcher applier.

Partially Implements: blueprint jsonschema-validation

Change-Id: I09a03fff96d9555024a74ba255c6951affc39de8
2017-06-10 13:51:25 +08:00
Yumeng_Bao
e2bf82607e Replace voluptuous with JSONSchema to validate resize action
Now in watcher,both JSONSchema and voluptuous are used to validate
JSON payloads. We want to remove voluptuous and Use JSONSchema
as our only JSON validation tool to keep consistence and also
to make it easier to expose the validation schema through
our API in future work.

In this patch, we replace voluptuous with JSONSchema to validate
the resize action in watcher applier.

Partially Implements: blueprint jsonschema-validation

Change-Id: I0ee4ba010a9f437658af81d5c971449aefc7f9c4
2017-06-10 13:40:29 +08:00
Yumeng_Bao
d76b5d2f7e Replace voluptuous with JSONSchema to validate sleep action
Now in watcher,both JSONSchema and voluptuous are used to validate
JSON payloads. We want to remove voluptuous and Use
JSONSchema as our only JSON validation tool to keep consistence
and also to make it easier to expose the validation schema
through our API in future work.

In this patch, we replace voluptuous with JSONSchema to validate
the sleep action in watcher applier.

Partially Implements: blueprint jsonschema-validation

Change-Id: I3032490236536a11e7045a56ad0bd40ef979407e
2017-06-10 13:22:36 +08:00
Yumeng_Bao
69730d80b2 Replace voluptuous with JSONSchema to validate nop action
Now in watcher,both JSONSchema and voluptuous are used to validate
JSON payloads. We want to remove voluptuous and Use
JSONSchema as our only JSON validation tool to keep consistence
and also to make it easier to expose the validation schema
through our API in future work.

In this patch, we replace voluptuous with JSONSchema to validate
the nop action in watcher applier.

Partially Implements: blueprint jsonschema-validation

Change-Id: Idf42b3359c36ac9480bd1f1bdd31e756214628ef
2017-06-09 13:14:29 +08:00
Ngo Quoc Cuong
17aa1122da Remove log translations and hacking
Log translation is no longer used, remove translate functions

Change-Id: Ibe205266debd6d39dab0a652a327ab19081ff27a
2017-06-08 09:48:26 +07:00
Ngo Quoc Cuong
d0f5a9fdf5 Remove deprecated oslo_messaging.get_transport
DeprecationWarning:
Using function/method 'oslo_messaging.transport.get_transport()'
is deprecated: use get_rpc_transport or get_notification_transport.

Replace get_transport with get_rpc_transport.

Change-Id: I13455842235a16463e61e5500c9e250a5cc9f86e
2017-06-08 08:34:39 +07:00
Jenkins
32dacdca8b Merge "Cancel Action Plan" 2017-06-07 14:09:32 +00:00
Jenkins
fbfb7159e3 Merge "fix clod_migrate problem" 2017-06-07 10:26:13 +00:00
aditi
d7a44739a6 Cancel Action Plan
This patch adds feature to cancel action plan in watcher.
A General flow from watcher-api to watcher-applier is implemented.

action plan cancel can cancel any [ongoing, pending, recommended]
action plan, it will update the action states also to "cancelled".
For ongoing actions in action plan, actions needs to be aborted.
Seperate patches will be added to support abort operation
in each action.

Notification part is addressed by a seperate blueprint.
https://blueprints.launchpad.net/watcher/+spec/notifications-actionplan-cancel

Change-Id: I895a5eaca5239d5657702c8d1875b9ece21682dc
Partially-Implements: blueprint cancel-action-plan
2017-06-07 05:36:18 +00:00
licanwei
65c63a9351 fix multinode tempest test failure
fix all five tempest tests:
watcher_tempest_plugin.tests.api.admin.test_service.
TestShowListService.test_show_service

watcher_tempest_plugin.tests.api.admin.test_service.
TestShowListService.test_show_service_with_links

tearDownClass (watcher_tempest_plugin.tests.api.admin.
test_action_plan.TestCreateDeleteExecuteActionPlan)

watcher_tempest_plugin.tests.scenario.test_execute_basic_optim
.TestExecuteBasicStrategy.test_execute_basic_action_plan

watcher_tempest_plugin.tests.scenario.test_execute_workload_balancing
.TestExecuteWorkloadBalancingStrategy
.test_execute_workload_stabilization

Change-Id: I4d8945cf2dedea3fa32029d6c07d24a411c1f2e4
Closes-Bug: #1695225
2017-06-07 11:56:23 +08:00
OpenStack Proposal Bot
58d86de064 Updated from global requirements
Change-Id: I65ae1ac5170cf675d8c396ef3b7166bafc19190e
2017-06-06 06:14:58 +00:00
Yumeng Bao
8d84da307b Add rm to whitelist_externals in tox.ini
Fix the following WARNING:

WARNING:test command found but not installed in testenv
  cmd: /bin/rm
  env: /home/jenkins/workspace/gate-watcher-python27-ubuntu-xenial/.tox/py27
Maybe you forgot to specify a dependency? See also the whitelist_externals envconfig setting.

Change-Id: Ie091bd64b6a87c30535ada34daf9d594aa3fdd41
2017-06-05 19:10:28 +08:00
Jenkins
01e865edbf Merge "Replace default cinder endpoint type" 2017-06-05 08:37:45 +00:00
Jenkins
b4b3856f14 Merge "Add action description" 2017-06-02 08:31:21 +00:00
Jenkins
67d065e02a Merge "Remove usage of parameter enforce_type" 2017-06-02 01:26:10 +00:00
Jenkins
891a351a04 Merge "Watcher official install-guide" 2017-06-01 04:38:16 +00:00
Feng Shengqin
f47fd9ac5e Remove usage of parameter enforce_type
Oslo.config deprecated parameter enforce_type and change
its default value to True. Remove the usage of it to avoid
DeprecationWarning: "Using the 'enforce_type' argument is
deprecated in version '4.0' and will be removed in version
'5.0': The argument enforce_type has changed its default
value to True and then will be removed completely."

Change-Id: I59621664773ee5ad264e6da9b15231f30dbb9c40
Closes-Bug: #1694616
2017-06-01 10:13:20 +08:00
Hidekazu Nakamura
7b766680b0 Replace default cinder endpoint type
The default cinder endpoint type is publicURL in cinderclient.
This patch replaces default cinder endpoint type from
internalURL to publicURL.

Change-Id: Ie6951086e4656bd83195dab151dbaaf948113a7c
Related-Bug: #1686298
2017-06-01 01:20:00 +00:00
licanwei
75a025d2d2 Add action description
Add action get_description in class BaseAction.
This information will be sent to the API side via notification.

Partially Implements: blueprint dynamic-action-description

Change-Id: I9ce1b18ad8c5eb7db62ec926d1859d0f508074b0
2017-05-31 18:03:19 +08:00
Jenkins
590bd43a1d Merge "Trivial fix typos" 2017-05-31 08:54:56 +00:00
Jenkins
d2e42a835b Merge "Deleted audit record still get by 'audit list'cmd" 2017-05-31 08:54:15 +00:00
Jenkins
a34e55e47a Merge "Replace oslo_utils.timeutils.isotime" 2017-05-31 08:49:41 +00:00
aditi
a62acbf2ab Watcher official install-guide
Currently watcher project does not have an official
install guide at [1]

This patch adds watcher install guide for rdo and debian.

install-guide is written following the document [2].

[1] https://docs.openstack.org/project-install-guide/ocata/
[2] https://docs.openstack.org/contributor-guide/project-install-guide.html

Change-Id: Idfae7286003f81222dadf91ddcaf95a42c7eb07f
2017-05-31 08:40:27 +00:00
Vu Cong Tuan
35074edaf7 Trivial fix typos
Change-Id: I4c7d3a0d815a616d1ba2c0d26135db5f2aea0c2f
2017-05-30 15:55:33 +07:00
Luong Anh Tuan
dd4aac4092 Replace oslo_utils.timeutils.isotime
Function 'oslo_utils.timeutils.isotime()' is deprecated in version '1.6'
and will be removed in a future version. We use
datetime.datetime.isoformat() instead. For more informations:
http://docs.openstack.org/developer/oslo.utils/api/timeutils.html#oslo_utils.timeutils.isotime

Change-Id: I17384c369fdc7f86b37fd62370d800ed2463adbe
Closes-Bug: #1514331
2017-05-29 23:34:03 +07:00
Jenkins
bd8151e581 Merge "Reduced the code complexity" 2017-05-29 09:15:26 +00:00
Jenkins
8585e49359 Merge "Updated from global requirements" 2017-05-27 12:23:06 +00:00
Jenkins
5d3af47b7d Merge "Versioned Notifications for service object" 2017-05-27 12:20:45 +00:00
OpenStack Proposal Bot
1001525664 Updated from global requirements
Change-Id: I1baa46eba0e7dbae816dbbeb0914f4e1efc8fa0a
2017-05-26 17:31:36 +00:00
licanwei
a33f40ec21 Deleted audit record still get by 'audit list'cmd
Audit record was deleted but the field 'state' maybe not set DELETED.
get_audit_list's filter used field 'state' will get
wrong result.
filter rule should use field 'deleted_at' instead of field 'state'.
get_action_list and get_action_plan_list have the same filter rule.

Change-Id: I08b2a005ca5fb7c2741ac5ed97c6e6b4279758ed
Closes-Bug: #1693666
2017-05-26 14:53:32 +08:00
Vladimir Ostroverkhov
d2a8454043 Versioned Notifications for service object
Implements: blueprint service-versioned-notifications-api

Change-Id: I9d601edb265ee230104f6c63a5f044869aeb3a02
2017-05-25 12:52:01 +03:00
suzhengwei
27c56a19e4 fix clod_migrate problem
1.TypeError: watcher_non_live_migrate_instance() got an unexpected keyword
  argument 'dest_hostname'
2.Conflict: Cannot 'stop' instance ****** while it is in vm_state stopped
  (HTTP 409)

Closes-Bug: #1693434

Change-Id: I7293dd5d08e33c2e534d072da8592172bc438c9e
2017-05-25 07:27:26 +00:00
Jenkins
9bb1e653d8 Merge "Change cinder api_version to '3' in default" 2017-05-25 06:51:58 +00:00
Akihito INOH
bb536ee40d Change cinder api_version to '3' in default
Block Storage API v2 is deprecated now. Instead of it, we should use v3
in default (api_version).

This patch replace cinder default api_version from '2' to '3' in
conf/cinder_client.py

Change-Id: I53ffa74cdac7ac31c74937bf18da8ed2fec92223
Closes-Bug: #1691104
2017-05-25 09:26:33 +09:00
OpenStack Proposal Bot
a0bf1b7d70 Updated from global requirements
Change-Id: Id71f841aa19c8cc07205a59450ea376d4a7ffce4
2017-05-24 03:47:55 +00:00
Jenkins
40f6eea637 Merge "Remove the deprecated tempest.test.attr" 2017-05-22 09:38:12 +00:00
suzhengwei
6c5a3910a7 doc error for WeightPlanner
Change-Id: I6c51a919baa32e9b46bfadfcf8e6afac27890b17
2017-05-22 16:08:53 +08:00
Ngo Quoc Cuong
a4fac69d85 Remove the deprecated tempest.test.attr
[1] moves the attr decorator from test.py to tempest/lib. So, all the
references to tempest.test has to be moved to tempest.lib.decorator.

[2] https://review.openstack.org/#/c/456236/

Change-Id: If977e559d9f3b982baf2974efef3c5b375f263b9
2017-05-22 10:11:18 +07:00
Luong Anh Tuan
21994297cf Replace assertRaisesRegexp with assertRaisesRegex
This replaces the deprecated (in python 3.2) unittest.TestCase
method assertRaisesRegexp() with assertRaisesRegex().

Change-Id: I38c3055288034aba51c11bb1bccd3655f760cecc
Closes-Bug: #1436957
2017-05-19 18:06:00 +07:00
OpenStack Proposal Bot
8a818c9071 Updated from global requirements
Change-Id: Id3b644be5dccdf4abf60c6a49400f61582f0d244
2017-05-17 04:00:56 +00:00
Jenkins
041fcb4ca0 Merge "Added tempest test for workload_stabilization" 2017-05-17 01:32:13 +00:00
Jenkins
a8d765bb28 Merge "Fix a typo" 2017-05-10 18:22:08 +00:00
zhangjianfeng
2b152bf17c [bugfix]for division use accurate division
for now / just get int value ,this will lead method
filter_destination_hosts return 0 host
because self.threshold / 100 * host.vcpus is 0
so we need use accurate division to change this

close-bug: 1689269

Change-Id: I5663951ce750d6c4580a507ccfc0268baea0685f
2017-05-08 18:27:24 +08:00
chenaidong1
08e585d405 Fix a typo
'specfication' should be 'specification'

Change-Id: I845e6199c4e2152295fb02ac44a1b090a3d7561d
2017-05-08 15:43:57 +08:00
OpenStack Proposal Bot
38e4255ec0 Updated from global requirements
Change-Id: I5ab36d982f5ef09548303f1233116a7ad74e00f2
2017-05-04 13:33:41 +00:00
Jenkins
a167044cde Merge "[bugfix]retry is reached but action still success" 2017-05-04 09:17:36 +00:00
Jenkins
1366f79b63 Merge "Add Watcher JobStore for background jobs" 2017-05-03 14:50:35 +00:00
Jenkins
f55b9b127e Merge "Add 'rm -f .testrepository/times.dbm' command in testenv" 2017-05-03 12:01:49 +00:00
Jenkins
1b7b467151 Merge "replace nova endpoint" 2017-05-03 12:01:32 +00:00
Alexander Chadin
f40fcdc573 Add Watcher JobStore for background jobs
This patch set adds WatcherJobStore class that allows to link
jobs and services.

Partially-Implements: blueprint background-jobs-ha
Change-Id: I575887ca6dae60b3b7709e6d2e2b256e09a3d824
2017-05-03 12:07:01 +03:00
Jenkins
2d98d5e743 Merge "Fix devstack plugin" 2017-05-02 18:36:27 +00:00
OpenStack Proposal Bot
877230569a Updated from global requirements
Change-Id: I685b9a86764c24fb638c6a684a7b82818d8a6aac
2017-04-27 11:50:22 +00:00
Jenkins
f842c5f601 Merge "Add host_aggregates in exclude rule of audit scope" 2017-04-27 11:01:22 +00:00
Pradeep Kumar Singh
0a899a2dc2 Add host_aggregates in exclude rule of audit scope
Currently if user wants to skip some host_aggregates from audit,
it is not possible. This patch adds host_aggregates into the exclude
rule of audit scope. This patch also implements audit-tag-vm-metadata
using scopes.

TODOs:
1. Add tests
2. Remove old implementation of audit-tag-vm-metadata

Change-Id: Ie86378cb02145a660bbf446eedb29dc311fa29d7
Implements: BP audit-tag-vm-metadata
2017-04-27 08:37:16 +00:00
licanwei
426232e288 replace nova endpoint
The default is publicURL in novaclient.
This also caused the failure of
gate-watcher-dsvm-multinode-ubuntu-xenial-nv.

Change-Id: I485dd62fb7199ffeca29a9b573a624bf144484d1
Closes-Bug: #1686298
Closes-Bug: #1686281
2017-04-27 02:08:04 +00:00
Jenkins
778d4c6fe4 Merge "Set access_policy for messaging's dispatcher" 2017-04-25 14:11:17 +00:00
Jenkins
f852467d6a Merge "Add ironicclient" 2017-04-25 13:43:14 +00:00
M V P Nitesh
dcf64ed1f4 Add 'rm -f .testrepository/times.dbm' command in testenv
Running py2* post py3* tests results in error. Add
'rm -f .testrepository/times.dbm' command in testenv to
resolve this.

Change-Id: Ia43f8d10f157d988c4d2c89f16cac0ea729cabe6
2017-04-25 12:52:13 +05:30
Jenkins
03f75202c8 Merge "[Doc] fix local.conf.compute" 2017-04-25 07:01:57 +00:00
Jenkins
0173a713c1 Merge "use instance data replace exception.NoDataFound" 2017-04-25 00:14:07 +00:00
Hidekazu Nakamura
216f3bab29 [Doc] fix local.conf.compute
Compute node needs placement-client service from Ocata.

Change-Id: Ibd02a126bb4808625cede8fe04255ac014268adb
2017-04-24 20:58:37 +09:00
zhangjianfeng
077b806bf6 [bugfix]retry is reached but action still success
now when action like live-migration retry is reached
result is False but action still success
we can add check in do execute method can fix this problem.
close-bug: 1685757

Change-Id: I8390566ec8dcfa3a71b931d5be1b305802ac0b2a
2017-04-24 18:17:22 +08:00
licanwei
d892153b58 use instance data replace exception.NoDataFound
If instance_ram_util and instance_disk_util can not get data
from datasource. we can use instance data,
just like total_cpu_utilization

Change-Id: I4170b96946b07435411ada5ff4a14c978c0435b4
2017-04-24 15:35:04 +08:00
shubhendu
2efe211f36 Set access_policy for messaging's dispatcher
oslo.messaging allow dispatcher to restrict endpoint
methods with DefaultRPCAccessPolicy to fix FutureWarning

Closes-Bug:1663543

Change-Id: I0288320193b0153ee223664696abca21cbdb0349
2017-04-24 06:32:33 +00:00
Hidekazu Nakamura
f55ea7824e Fix devstack plugin
Stack.sh after unstack.sh results in error.
This patch fix it.

Change-Id: I35d74896611e56d916a9846b2f854bd060a606e4
2017-04-21 16:49:04 +09:00
Hidekazu Nakamura
e5eb4f51be [Doc] messaging -> messagingv2
Nova sends notifications using 2.0 messging format.

Change-Id: Ib0202ba5291f1666bdbf9b6830521b1a2aa20a80
2017-04-18 11:54:16 +09:00
licanwei
f637a368d7 Add ironicclient
This patch set adds ironicclient.

Change-Id: I122a26465d339ee6e36c0f234d3fd6c57cee2afa
Partially-Implements: blueprint build-baremetal-data-model-in-watcher
2017-04-17 06:48:05 +00:00
Jenkins
2e8fb5a821 Merge "exception when running 'watcher actionplan start XXX'" 2017-04-13 11:59:45 +00:00
licanwei
527423a5fa exception when running 'watcher actionplan start XXX'
In DefaultApplier.execute(), Action.list should set eager=True.
Otherwise it will trigger an exception when the notification is sent.

This also causes the FAILURE of
gate-watcher-dsvm-multinode-ubuntu-xenial-nv.

Change-Id: I27db9691727671abb582d4f22283ebda5bd51b07
Closes-Bug: #1676308
2017-04-07 16:19:21 +08:00
Vincent Françoise
334558f17c Added tempest test for workload_stabilization
In this changeset, I added a tempest test that is in charge of
executing the workload_stabilization strategy.

Change-Id: I61bad268fc5895ddb22312baeb21da5ae3c71de9
2017-03-30 07:20:13 +00:00
Béla Vancsics
fd55d28d42 Reduced the code complexity
I extracted some of the functionalities into helper functions
to reduce the length and complexity of build_query (in
watcher/datasource/ceilometer.py).
Additionally it became more readable as well, without
changing its functionality.

Change-Id: I9e5c524754cf0f9d718a216465ba1b7536add80e
2017-03-16 07:45:44 +01:00
229 changed files with 6860 additions and 826 deletions

9
.gitignore vendored
View File

@@ -4,8 +4,7 @@
*.so *.so
# Packages # Packages
*.egg *.egg*
*.egg-info
dist dist
build build
eggs eggs
@@ -43,9 +42,11 @@ output/*/index.html
# Sphinx # Sphinx
doc/build doc/build
doc/source/api doc/source/api/*
doc/source/samples doc/source/samples
doc/source/watcher.conf.sample doc/source/_static/*.sample
!doc/source/api/index.rst
!doc/source/api/v1.rst
# pbr generates these # pbr generates these
AUTHORS AUTHORS

View File

@@ -28,4 +28,4 @@ migration, increased energy efficiency-and more!
* Wiki: https://wiki.openstack.org/wiki/Watcher * Wiki: https://wiki.openstack.org/wiki/Watcher
* Source: https://github.com/openstack/watcher * Source: https://github.com/openstack/watcher
* Bugs: https://bugs.launchpad.net/watcher * Bugs: https://bugs.launchpad.net/watcher
* Documentation: https://docs.openstack.org/developer/watcher/ * Documentation: https://docs.openstack.org/watcher/latest/

View File

@@ -54,7 +54,7 @@ if is_ssl_enabled_service "watcher" || is_service_enabled tls-proxy; then
WATCHER_SERVICE_PROTOCOL="https" WATCHER_SERVICE_PROTOCOL="https"
fi fi
WATCHER_USE_MOD_WSGI=$(trueorfalse TRUE WATCHER_USE_MOD_WSGI) WATCHER_USE_MOD_WSGI=$(trueorfalse True WATCHER_USE_MOD_WSGI)
if is_suse; then if is_suse; then
WATCHER_WSGI_DIR=${WATCHER_WSGI_DIR:-/srv/www/htdocs/watcher} WATCHER_WSGI_DIR=${WATCHER_WSGI_DIR:-/srv/www/htdocs/watcher}
@@ -235,7 +235,7 @@ function init_watcher {
recreate_database watcher recreate_database watcher
# Create watcher schema # Create watcher schema
$WATCHER_BIN_DIR/watcher-db-manage --config-file $WATCHER_CONF upgrade head $WATCHER_BIN_DIR/watcher-db-manage --config-file $WATCHER_CONF upgrade
fi fi
create_watcher_cache_dir create_watcher_cache_dir
} }

View File

@@ -25,7 +25,7 @@ GLANCE_HOSTPORT=${SERVICE_HOST}:9292
DATABASE_TYPE=mysql DATABASE_TYPE=mysql
# Enable services (including neutron) # Enable services (including neutron)
ENABLED_SERVICES=n-cpu,n-api-meta,c-vol,q-agt ENABLED_SERVICES=n-cpu,n-api-meta,c-vol,q-agt,placement-client
NOVA_VNC_ENABLED=True NOVA_VNC_ENABLED=True
NOVNCPROXY_URL="http://$SERVICE_HOST:6080/vnc_auto.html" NOVNCPROXY_URL="http://$SERVICE_HOST:6080/vnc_auto.html"

View File

@@ -0,0 +1,26 @@
{
"payload": {
"watcher_object.name": "ServiceUpdatePayload",
"watcher_object.namespace": "watcher",
"watcher_object.data": {
"status_update": {
"watcher_object.name": "ServiceStatusUpdatePayload",
"watcher_object.namespace": "watcher",
"watcher_object.data": {
"old_state": "ACTIVE",
"state": "FAILED"
},
"watcher_object.version": "1.0"
},
"last_seen_up": "2016-09-22T08:32:06Z",
"name": "watcher-service",
"sevice_host": "controller"
},
"watcher_object.version": "1.0"
},
"event_type": "service.update",
"priority": "INFO",
"message_id": "3984dc2b-8aef-462b-a220-8ae04237a56e",
"timestamp": "2016-10-18 09:52:05.219414",
"publisher_id": "infra-optim:node0"
}

View File

View File

@@ -0,0 +1,12 @@
===================
Administrator Guide
===================
.. toctree::
:maxdepth: 2
apache-mod-wsgi
gmr
policy
ways-to-install
../strategies/index

View File

@@ -4,12 +4,13 @@
https://creativecommons.org/licenses/by/3.0/ https://creativecommons.org/licenses/by/3.0/
================== =======================
Installing Watcher Ways to install Watcher
================== =======================
This document describes how to install Watcher in order to use it. If you are This document describes some ways to install Watcher in order to use it.
intending to develop on or with Watcher, please read :doc:`../dev/environment`. If you are intending to develop on or with Watcher,
please read :doc:`../contributor/environment`.
Prerequisites Prerequisites
------------- -------------
@@ -77,9 +78,10 @@ Install the Watcher modules dependencies:
# pip install -r requirements.txt # pip install -r requirements.txt
From here, refer to :doc:`configuration` to declare Watcher as a new service From here, refer to :doc:`../configuration/configuring` to declare Watcher
into Keystone and to configure its different modules. Once configured, you as a new service into Keystone and to configure its different modules.
should be able to run the Watcher services by issuing these commands: Once configured, you should be able to run the Watcher services by issuing
these commands:
.. code-block:: bash .. code-block:: bash
@@ -107,7 +109,7 @@ 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/configuring`.
Installing from packages: Debian (experimental) Installing from packages: Debian (experimental)

4
doc/source/api/index.rst Normal file
View File

@@ -0,0 +1,4 @@
.. toctree::
:maxdepth: 1
v1

View File

@@ -20,6 +20,17 @@ Goals
.. autotype:: watcher.api.controllers.v1.goal.Goal .. autotype:: watcher.api.controllers.v1.goal.Goal
:members: :members:
Strategies
==========
.. rest-controller:: watcher.api.controllers.v1.strategy:StrategiesController
:webprefix: /v1/strategies
.. autotype:: watcher.api.controllers.v1.strategy.StrategyCollection
:members:
.. autotype:: watcher.api.controllers.v1.strategy.Strategy
:members:
Audit Templates Audit Templates
=============== ===============

View File

@@ -31,8 +31,7 @@ sys.path.insert(0, os.path.abspath('./'))
# 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', 'oslo_config.sphinxext',
'oslosphinx',
'sphinx.ext.autodoc', 'sphinx.ext.autodoc',
'sphinx.ext.viewcode', 'sphinx.ext.viewcode',
'sphinxcontrib.httpdomain', 'sphinxcontrib.httpdomain',
@@ -41,10 +40,14 @@ extensions = [
'wsmeext.sphinxext', 'wsmeext.sphinxext',
'ext.term', 'ext.term',
'ext.versioned_notifications', 'ext.versioned_notifications',
'oslo_config.sphinxconfiggen',
'openstackdocstheme',
] ]
wsme_protocols = ['restjson'] wsme_protocols = ['restjson']
config_generator_config_file = '../../etc/watcher/watcher-config-generator.conf' config_generator_config_file = [(
'../../etc/watcher/oslo-config-generator/watcher.conf',
'_static/watcher')]
sample_config_basename = 'watcher' 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
@@ -92,6 +95,8 @@ add_function_parentheses = True
# unit titles (such as .. function::). # unit titles (such as .. function::).
add_module_names = True add_module_names = True
suppress_warnings = ['app.add_directive']
# The name of the Pygments (syntax highlighting) style to use. # The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx' pygments_style = 'sphinx'
@@ -117,12 +122,20 @@ man_pages = [
# Sphinx are currently 'default' and 'sphinxdoc'. # Sphinx are currently 'default' and 'sphinxdoc'.
# html_theme_path = ["."] # html_theme_path = ["."]
# html_theme = '_theme' # html_theme = '_theme'
html_theme = 'openstackdocs'
# html_static_path = ['static'] # html_static_path = ['static']
html_theme_options = {'incubating': True} # html_theme_options = {}
# Output file base name for HTML help builder. # Output file base name for HTML help builder.
htmlhelp_basename = '%sdoc' % project htmlhelp_basename = '%sdoc' % project
html_last_updated_fmt = '%Y-%m-%d %H:%M'
#openstackdocstheme options
repository_name = 'openstack/watcher'
bug_project = 'watcher'
bug_tag = ''
# Grouping the document tree into LaTeX files. List of tuples # Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass # (source start file, target name, title, author, documentclass
# [howto/manual]). # [howto/manual]).

View File

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

View File

@@ -424,7 +424,7 @@ to Watcher receives Nova notifications in ``watcher_notifications`` as well.
into which Nova services will publish events :: into which Nova services will publish events ::
[oslo_messaging_notifications] [oslo_messaging_notifications]
driver = messaging driver = messagingv2
topics = notifications,watcher_notifications topics = notifications,watcher_notifications
* Restart the Nova services. * Restart the Nova services.

View File

@@ -0,0 +1,5 @@
.. toctree::
:maxdepth: 1
configuring
watcher

View File

@@ -0,0 +1,11 @@
.. _watcher_sample_configuration_files:
------------
watcher.conf
------------
The ``watcher.conf`` file contains most of the options to configure the
Watcher services.
.. show-options::
:config-file: etc/watcher/oslo-config-generator/watcher.conf

View File

@@ -64,8 +64,9 @@ IRC Channel
``#openstack-watcher`` (changelog_) ``#openstack-watcher`` (changelog_)
Weekly Meetings Weekly Meetings
on Wednesdays at 14:00 UTC on even weeks, 9:00 UTC on odd weeks, in the On Wednesdays at 14:00 UTC on even weeks in the ``#openstack-meeting-4``
``#openstack-meeting-4`` IRC channel (`meetings logs`_) IRC channel, 13:00 UTC on odd weeks in the ``#openstack-meeting-alt``
IRC channel (`meetings logs`_)
.. _changelog: http://eavesdrop.openstack.org/irclogs/%23openstack-watcher/ .. _changelog: http://eavesdrop.openstack.org/irclogs/%23openstack-watcher/
.. _meetings logs: http://eavesdrop.openstack.org/meetings/watcher/ .. _meetings logs: http://eavesdrop.openstack.org/meetings/watcher/

View File

@@ -17,7 +17,7 @@ To install Watcher from packaging, refer instead to Watcher `User
Documentation`_. Documentation`_.
.. _`Git Repository`: https://git.openstack.org/cgit/openstack/watcher .. _`Git Repository`: https://git.openstack.org/cgit/openstack/watcher
.. _`User Documentation`: https://docs.openstack.org/developer/watcher/ .. _`User Documentation`: https://docs.openstack.org/watcher/latest/
Prerequisites Prerequisites
============= =============
@@ -194,7 +194,7 @@ still need to configure the following sections:
message bus message bus
So if you need some more details on how to configure one or more of these So if you need some more details on how to configure one or more of these
sections, please do have a look at :doc:`../deploy/configuration` before sections, please do have a look at :doc:`../configuration/configuring` before
continuing. continuing.
@@ -240,7 +240,7 @@ To run the Watcher Applier service, use:
(watcher) $ watcher-applier (watcher) $ watcher-applier
Default configuration of these services are available into ``/etc/watcher`` Default configuration of these services are available into ``/etc/watcher``
directory. See :doc:`../deploy/configuration` for details on how Watcher is directory. See :doc:`../configuration/configuring` for details on how Watcher is
configured. By default, Watcher is configured with SQL backends. configured. By default, Watcher is configured with SQL backends.

View File

@@ -0,0 +1,8 @@
.. toctree::
:maxdepth: 1
environment
devstack
notifications
testing
rally_link

View File

@@ -123,8 +123,9 @@ Here below is how you would proceed to register ``NewGoal`` using pbr_:
new_goal = thirdparty.new:NewGoal new_goal = thirdparty.new:NewGoal
To get a better understanding on how to implement a more advanced goal, To get a better understanding on how to implement a more advanced goal, have
have a look at the :py:class:`~.ServerConsolidation` class. a look at the
:py:class:`watcher.decision_engine.goal.goals.ServerConsolidation` class.
.. _pbr: http://docs.openstack.org/developer/pbr/ .. _pbr: http://docs.openstack.org/developer/pbr/

View File

@@ -0,0 +1,11 @@
.. toctree::
:maxdepth: 1
base-setup
action-plugin
cdmc-plugin
goal-plugin
planner-plugin
scoring-engine-plugin
strategy-plugin
plugins

View File

@@ -37,7 +37,8 @@ Create a new scoring engine plugin
In order to create a new scoring engine you have to: In order to create a new scoring engine you have to:
- Extend the :py:class:`~.ScoringEngine` class - Extend the :py:class:`watcher.decision_engine.scoring.base.ScoringEngine`
class
- Implement its :py:meth:`~.ScoringEngine.get_name` method to return the - 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 **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 should be the same as the name of :ref:`the entry point we will declare later
@@ -124,7 +125,8 @@ scoring engine deployed as a web service on external servers:
Abstract Plugin Class Abstract Plugin Class
===================== =====================
Here below is the abstract :py:class:`~.ScoringEngine` class: Here below is the abstract
:py:class:`watcher.decision_engine.scoring.base.ScoringEngine` class:
.. autoclass:: watcher.decision_engine.scoring.base.ScoringEngine .. autoclass:: watcher.decision_engine.scoring.base.ScoringEngine
:members: :members:

View File

@@ -1,14 +0,0 @@
.. _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

@@ -65,6 +65,14 @@ Audit
.. _audit_template_definition: .. _audit_template_definition:
Audit Scope
===========
An Audit Scope is a set of audited resources. Audit Scope should be defined
in each Audit Template (which contains the Audit settings).
.. _audit_scope_definition:
Audit Template Audit Template
============== ==============

View File

@@ -13,7 +13,7 @@
height="210mm" height="210mm"
id="svg4946" id="svg4946"
version="1.1" version="1.1"
inkscape:version="0.48.4 r9939" inkscape:version="0.91 r13725"
sodipodi:docname="architecture.svg"> sodipodi:docname="architecture.svg">
<defs <defs
id="defs4948"> id="defs4948">
@@ -325,6 +325,20 @@
style="fill:#ffffff;fill-rule:evenodd;stroke:#000000;stroke-width:1pt" style="fill:#ffffff;fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
transform="matrix(-0.8,0,0,-0.8,4.8,0)" /> transform="matrix(-0.8,0,0,-0.8,4.8,0)" />
</marker> </marker>
<marker
inkscape:stockid="EmptyTriangleInL"
orient="auto"
refY="0"
refX="0"
id="EmptyTriangleInL-5"
style="overflow:visible">
<path
inkscape:connector-curvature="0"
id="path7091-3"
d="m 5.77,0 -8.65,5 0,-10 8.65,5 z"
style="fill:#ffffff;fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
transform="matrix(-0.8,0,0,-0.8,4.8,0)" />
</marker>
</defs> </defs>
<sodipodi:namedview <sodipodi:namedview
inkscape:document-units="mm" inkscape:document-units="mm"
@@ -333,17 +347,21 @@
borderopacity="1.0" borderopacity="1.0"
inkscape:pageopacity="0.0" inkscape:pageopacity="0.0"
inkscape:pageshadow="2" inkscape:pageshadow="2"
inkscape:zoom="4" inkscape:zoom="1.4142136"
inkscape:cx="261.24633" inkscape:cx="261.24633"
inkscape:cy="108.90512" inkscape:cy="108.90512"
inkscape:current-layer="g5356" inkscape:current-layer="g5356"
id="namedview4950" id="namedview4950"
showgrid="false" showgrid="true"
inkscape:window-width="1846" inkscape:window-width="1215"
inkscape:window-height="1053" inkscape:window-height="776"
inkscape:window-x="1911" inkscape:window-x="65"
inkscape:window-y="37" inkscape:window-y="24"
inkscape:window-maximized="1" /> inkscape:window-maximized="1">
<inkscape:grid
type="xygrid"
id="grid4203" />
</sodipodi:namedview>
<metadata <metadata
id="metadata4952"> id="metadata4952">
<rdf:RDF> <rdf:RDF>
@@ -410,7 +428,7 @@
sodipodi:cy="358.94418" sodipodi:cy="358.94418"
sodipodi:rx="70.677063" sodipodi:rx="70.677063"
sodipodi:ry="70.677063" sodipodi:ry="70.677063"
d="m 394.67978,358.94418 c 0,39.03387 -31.6432,70.67707 -70.67706,70.67707 -39.03387,0 -70.67707,-31.6432 -70.67707,-70.67707 0,-39.03386 31.6432,-70.67706 70.67707,-70.67706 39.03386,0 70.67706,31.6432 70.67706,70.67706 z" d="m 394.67978,358.94418 a 70.677063,70.677063 0 0 1 -70.67706,70.67707 70.677063,70.677063 0 0 1 -70.67707,-70.67707 70.677063,70.677063 0 0 1 70.67707,-70.67706 70.677063,70.677063 0 0 1 70.67706,70.67706 z"
transform="matrix(0.36664048,0,0,0.36664048,0.86684619,-80.697844)" /> transform="matrix(0.36664048,0,0,0.36664048,0.86684619,-80.697844)" />
<g <g
id="g5262"> id="g5262">
@@ -536,29 +554,29 @@
</g> </g>
<rect <rect
y="754.5235" y="754.5235"
x="225.29219" x="231.29219"
height="42.939091" height="42.939091"
width="118.56741" width="118.56741"
id="rect4267-4-7-7-3-3" id="rect4267-4-7-7-3-3"
style="fill:#ffffff;stroke:#000000;stroke-width:1;stroke-opacity:1;display:inline" /> style="display:inline;fill:#ffffff;stroke:#000000;stroke-width:1;stroke-opacity:1" />
<text <text
sodipodi:linespacing="125%" sodipodi:linespacing="125%"
id="text5037-4-6-9-1-4" id="text5037-4-6-9-1-4"
y="783.03412" y="783.03412"
x="284.34656" x="290.34656"
style="font-size:20.86613655px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;display:inline;font-family:Sans" style="font-style:normal;font-weight:normal;font-size:20.86613655px;line-height:125%;font-family:Sans;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;display:inline;fill:#000000;fill-opacity:1;stroke:none"
xml:space="preserve"><tspan xml:space="preserve"><tspan
id="tspan5184-3-5-2-1" id="tspan5184-3-5-2-1"
style="font-size:20px;text-align:center;text-anchor:middle" style="font-size:20px;text-align:center;text-anchor:middle"
y="783.03412" y="783.03412"
x="284.34656" x="290.34656"
sodipodi:role="line">ceilometer</tspan></text> sodipodi:role="line">ceilometer</tspan></text>
<g <g
transform="matrix(1.7775787,0,0,1.7775787,593.58186,304.14299)" transform="matrix(1.7775787,0,0,1.7775787,599.58186,304.14299)"
id="g4982" id="g4982"
style="display:inline"> style="display:inline">
<rect <rect
style="fill:#ffffff;stroke:#000000;stroke-width:0.562563;stroke-opacity:1;display:inline" style="display:inline;fill:#ffffff;stroke:#000000;stroke-width:0.562563;stroke-opacity:1"
id="rect4267-4-7-7-3-3-1" id="rect4267-4-7-7-3-3-1"
width="66.701637" width="66.701637"
height="24.155943" height="24.155943"
@@ -566,7 +584,7 @@
y="253.36743" /> y="253.36743" />
<text <text
xml:space="preserve" xml:space="preserve"
style="font-size:11.73851585px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;display:inline;font-family:Sans" style="font-style:normal;font-weight:normal;font-size:11.73851585px;line-height:125%;font-family:Sans;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;display:inline;fill:#000000;fill-opacity:1;stroke:none"
x="-100.86156" x="-100.86156"
y="268.36258" y="268.36258"
id="text5037-4-6-9-1-4-3" id="text5037-4-6-9-1-4-3"
@@ -620,12 +638,6 @@
style="font-size:11.2512598px;text-align:center;text-anchor:middle" style="font-size:11.2512598px;text-align:center;text-anchor:middle"
id="tspan5022">drivers</tspan></text> id="tspan5022">drivers</tspan></text>
</g> </g>
<path
sodipodi:nodetypes="cc"
inkscape:connector-curvature="0"
id="path5110"
d="m 376.75141,726.9703 -57.95106,0"
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;display:inline" />
<g <g
transform="matrix(1.7775787,0,0,1.7775787,704.59677,780.35846)" transform="matrix(1.7775787,0,0,1.7775787,704.59677,780.35846)"
id="g5267" id="g5267"
@@ -1052,8 +1064,8 @@
sodipodi:nodetypes="cc" sodipodi:nodetypes="cc"
inkscape:connector-curvature="0" inkscape:connector-curvature="0"
id="path5112-89" id="path5112-89"
d="m 328.87061,655.34778 0,71.7093" d="m 324.87061,655.34778 0,71.7093"
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;display:inline" /> style="display:inline;fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path <path
sodipodi:nodetypes="cc" sodipodi:nodetypes="cc"
inkscape:connector-curvature="0" inkscape:connector-curvature="0"
@@ -1178,7 +1190,7 @@
sodipodi:role="line">extensions</tspan></text> sodipodi:role="line">extensions</tspan></text>
<path <path
transform="translate(68.397849,130.15566)" transform="translate(68.397849,130.15566)"
d="m 519.41589,367.58817 c 0,4.01434 -2.01125,7.2686 -4.49224,7.2686 -2.481,0 -4.49225,-3.25426 -4.49225,-7.2686 0,-4.01434 2.01125,-7.26861 4.49225,-7.26861 2.48099,0 4.49224,3.25427 4.49224,7.26861 z" d="m 519.41589,367.58817 a 4.4922457,7.2686057 0 0 1 -4.49224,7.2686 4.4922457,7.2686057 0 0 1 -4.49225,-7.2686 4.4922457,7.2686057 0 0 1 4.49225,-7.26861 4.4922457,7.2686057 0 0 1 4.49224,7.26861 z"
sodipodi:ry="7.2686057" sodipodi:ry="7.2686057"
sodipodi:rx="4.4922457" sodipodi:rx="4.4922457"
sodipodi:cy="367.58817" sodipodi:cy="367.58817"
@@ -1188,7 +1200,7 @@
sodipodi:type="arc" /> sodipodi:type="arc" />
<path <path
transform="matrix(0.36538461,0,0,0.36538461,397.30905,368.17877)" transform="matrix(0.36538461,0,0,0.36538461,397.30905,368.17877)"
d="m 521.38126,368.43045 c 0,4.03162 -3.26828,7.2999 -7.2999,7.2999 -4.03162,0 -7.2999,-3.26828 -7.2999,-7.2999 0,-4.03162 3.26828,-7.2999 7.2999,-7.2999 4.03162,0 7.2999,3.26828 7.2999,7.2999 z" d="m 521.38126,368.43045 a 7.2998991,7.2998991 0 0 1 -7.2999,7.2999 7.2998991,7.2998991 0 0 1 -7.2999,-7.2999 7.2998991,7.2998991 0 0 1 7.2999,-7.2999 7.2998991,7.2998991 0 0 1 7.2999,7.2999 z"
sodipodi:ry="7.2998991" sodipodi:ry="7.2998991"
sodipodi:rx="7.2998991" sodipodi:rx="7.2998991"
sodipodi:cy="368.43045" sodipodi:cy="368.43045"
@@ -1198,7 +1210,7 @@
sodipodi:type="arc" /> sodipodi:type="arc" />
<path <path
transform="matrix(0.36538461,0,0,0.36538461,-27.122619,99.964199)" transform="matrix(0.36538461,0,0,0.36538461,-27.122619,99.964199)"
d="m 521.38126,368.43045 c 0,4.03162 -3.26828,7.2999 -7.2999,7.2999 -4.03162,0 -7.2999,-3.26828 -7.2999,-7.2999 0,-4.03162 3.26828,-7.2999 7.2999,-7.2999 4.03162,0 7.2999,3.26828 7.2999,7.2999 z" d="m 521.38126,368.43045 a 7.2998991,7.2998991 0 0 1 -7.2999,7.2999 7.2998991,7.2998991 0 0 1 -7.2999,-7.2999 7.2998991,7.2998991 0 0 1 7.2999,-7.2999 7.2998991,7.2998991 0 0 1 7.2999,7.2999 z"
sodipodi:ry="7.2998991" sodipodi:ry="7.2998991"
sodipodi:rx="7.2998991" sodipodi:rx="7.2998991"
sodipodi:cy="368.43045" sodipodi:cy="368.43045"
@@ -1214,7 +1226,7 @@
style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:3.99999997, 1.99999998, 0.99999999, 1.99999998;stroke-dashoffset:0;marker-start:none;display:inline" /> style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:3.99999997, 1.99999998, 0.99999999, 1.99999998;stroke-dashoffset:0;marker-start:none;display:inline" />
<path <path
transform="matrix(0.36538461,0,0,0.36538461,396.74123,268.71562)" transform="matrix(0.36538461,0,0,0.36538461,396.74123,268.71562)"
d="m 521.38126,368.43045 c 0,4.03162 -3.26828,7.2999 -7.2999,7.2999 -4.03162,0 -7.2999,-3.26828 -7.2999,-7.2999 0,-4.03162 3.26828,-7.2999 7.2999,-7.2999 4.03162,0 7.2999,3.26828 7.2999,7.2999 z" d="m 521.38126,368.43045 a 7.2998991,7.2998991 0 0 1 -7.2999,7.2999 7.2998991,7.2998991 0 0 1 -7.2999,-7.2999 7.2998991,7.2998991 0 0 1 7.2999,-7.2999 7.2998991,7.2998991 0 0 1 7.2999,7.2999 z"
sodipodi:ry="7.2998991" sodipodi:ry="7.2998991"
sodipodi:rx="7.2998991" sodipodi:rx="7.2998991"
sodipodi:cy="368.43045" sodipodi:cy="368.43045"
@@ -1224,7 +1236,7 @@
sodipodi:type="arc" /> sodipodi:type="arc" />
<path <path
transform="matrix(0.36538461,0,0,0.36538461,428.69747,559.36511)" transform="matrix(0.36538461,0,0,0.36538461,428.69747,559.36511)"
d="m 521.38126,368.43045 c 0,4.03162 -3.26828,7.2999 -7.2999,7.2999 -4.03162,0 -7.2999,-3.26828 -7.2999,-7.2999 0,-4.03162 3.26828,-7.2999 7.2999,-7.2999 4.03162,0 7.2999,3.26828 7.2999,7.2999 z" d="m 521.38126,368.43045 a 7.2998991,7.2998991 0 0 1 -7.2999,7.2999 7.2998991,7.2998991 0 0 1 -7.2999,-7.2999 7.2998991,7.2998991 0 0 1 7.2999,-7.2999 7.2998991,7.2998991 0 0 1 7.2999,7.2999 z"
sodipodi:ry="7.2998991" sodipodi:ry="7.2998991"
sodipodi:rx="7.2998991" sodipodi:rx="7.2998991"
sodipodi:cy="368.43045" sodipodi:cy="368.43045"
@@ -1234,7 +1246,7 @@
sodipodi:type="arc" /> sodipodi:type="arc" />
<path <path
transform="matrix(0.36538461,0,0,0.36538461,531.88959,559.36511)" transform="matrix(0.36538461,0,0,0.36538461,531.88959,559.36511)"
d="m 521.38126,368.43045 c 0,4.03162 -3.26828,7.2999 -7.2999,7.2999 -4.03162,0 -7.2999,-3.26828 -7.2999,-7.2999 0,-4.03162 3.26828,-7.2999 7.2999,-7.2999 4.03162,0 7.2999,3.26828 7.2999,7.2999 z" d="m 521.38126,368.43045 a 7.2998991,7.2998991 0 0 1 -7.2999,7.2999 7.2998991,7.2998991 0 0 1 -7.2999,-7.2999 7.2998991,7.2998991 0 0 1 7.2999,-7.2999 7.2998991,7.2998991 0 0 1 7.2999,7.2999 z"
sodipodi:ry="7.2998991" sodipodi:ry="7.2998991"
sodipodi:rx="7.2998991" sodipodi:rx="7.2998991"
sodipodi:cy="368.43045" sodipodi:cy="368.43045"
@@ -1250,7 +1262,7 @@
style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:1.99999998, 1.99999998;stroke-dashoffset:0;marker-start:none;display:inline" /> style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:1.99999998, 1.99999998;stroke-dashoffset:0;marker-start:none;display:inline" />
<path <path
transform="matrix(0.36538461,0,0,0.36538461,748.91653,525.25993)" transform="matrix(0.36538461,0,0,0.36538461,748.91653,525.25993)"
d="m 521.38126,368.43045 c 0,4.03162 -3.26828,7.2999 -7.2999,7.2999 -4.03162,0 -7.2999,-3.26828 -7.2999,-7.2999 0,-4.03162 3.26828,-7.2999 7.2999,-7.2999 4.03162,0 7.2999,3.26828 7.2999,7.2999 z" d="m 521.38126,368.43045 a 7.2998991,7.2998991 0 0 1 -7.2999,7.2999 7.2998991,7.2998991 0 0 1 -7.2999,-7.2999 7.2998991,7.2998991 0 0 1 7.2999,-7.2999 7.2998991,7.2998991 0 0 1 7.2999,7.2999 z"
sodipodi:ry="7.2998991" sodipodi:ry="7.2998991"
sodipodi:rx="7.2998991" sodipodi:rx="7.2998991"
sodipodi:cy="368.43045" sodipodi:cy="368.43045"
@@ -1260,7 +1272,7 @@
sodipodi:type="arc" /> sodipodi:type="arc" />
<path <path
transform="matrix(0.36538461,0,0,0.36538461,748.91653,495.84628)" transform="matrix(0.36538461,0,0,0.36538461,748.91653,495.84628)"
d="m 521.38126,368.43045 c 0,4.03162 -3.26828,7.2999 -7.2999,7.2999 -4.03162,0 -7.2999,-3.26828 -7.2999,-7.2999 0,-4.03162 3.26828,-7.2999 7.2999,-7.2999 4.03162,0 7.2999,3.26828 7.2999,7.2999 z" d="m 521.38126,368.43045 a 7.2998991,7.2998991 0 0 1 -7.2999,7.2999 7.2998991,7.2998991 0 0 1 -7.2999,-7.2999 7.2998991,7.2998991 0 0 1 7.2999,-7.2999 7.2998991,7.2998991 0 0 1 7.2999,7.2999 z"
sodipodi:ry="7.2998991" sodipodi:ry="7.2998991"
sodipodi:rx="7.2998991" sodipodi:rx="7.2998991"
sodipodi:cy="368.43045" sodipodi:cy="368.43045"
@@ -1313,7 +1325,7 @@
</g> </g>
<path <path
transform="matrix(0.36538461,0,0,0.36538461,396.74122,268.71562)" transform="matrix(0.36538461,0,0,0.36538461,396.74122,268.71562)"
d="m 521.38126,368.43045 c 0,4.03162 -3.26828,7.2999 -7.2999,7.2999 -4.03162,0 -7.2999,-3.26828 -7.2999,-7.2999 0,-4.03162 3.26828,-7.2999 7.2999,-7.2999 4.03162,0 7.2999,3.26828 7.2999,7.2999 z" d="m 521.38126,368.43045 a 7.2998991,7.2998991 0 0 1 -7.2999,7.2999 7.2998991,7.2998991 0 0 1 -7.2999,-7.2999 7.2998991,7.2998991 0 0 1 7.2999,-7.2999 7.2998991,7.2998991 0 0 1 7.2999,7.2999 z"
sodipodi:ry="7.2998991" sodipodi:ry="7.2998991"
sodipodi:rx="7.2998991" sodipodi:rx="7.2998991"
sodipodi:cy="368.43045" sodipodi:cy="368.43045"
@@ -1323,7 +1335,7 @@
sodipodi:type="arc" /> sodipodi:type="arc" />
<path <path
transform="matrix(0.36538461,0,0,0.36538461,-27.122619,74.87915)" transform="matrix(0.36538461,0,0,0.36538461,-27.122619,74.87915)"
d="m 521.38126,368.43045 c 0,4.03162 -3.26828,7.2999 -7.2999,7.2999 -4.03162,0 -7.2999,-3.26828 -7.2999,-7.2999 0,-4.03162 3.26828,-7.2999 7.2999,-7.2999 4.03162,0 7.2999,3.26828 7.2999,7.2999 z" d="m 521.38126,368.43045 a 7.2998991,7.2998991 0 0 1 -7.2999,7.2999 7.2998991,7.2998991 0 0 1 -7.2999,-7.2999 7.2998991,7.2998991 0 0 1 7.2999,-7.2999 7.2998991,7.2998991 0 0 1 7.2999,7.2999 z"
sodipodi:ry="7.2998991" sodipodi:ry="7.2998991"
sodipodi:rx="7.2998991" sodipodi:rx="7.2998991"
sodipodi:cy="368.43045" sodipodi:cy="368.43045"
@@ -1339,7 +1351,7 @@
style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:3.99999997, 1.99999998, 0.99999999, 1.99999998;stroke-dashoffset:0;marker-start:url(#TriangleInL);display:inline" /> style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:3.99999997, 1.99999998, 0.99999999, 1.99999998;stroke-dashoffset:0;marker-start:url(#TriangleInL);display:inline" />
<path <path
transform="matrix(0.36538461,0,0,0.36538461,422.9095,223.59883)" transform="matrix(0.36538461,0,0,0.36538461,422.9095,223.59883)"
d="m 521.38126,368.43045 c 0,4.03162 -3.26828,7.2999 -7.2999,7.2999 -4.03162,0 -7.2999,-3.26828 -7.2999,-7.2999 0,-4.03162 3.26828,-7.2999 7.2999,-7.2999 4.03162,0 7.2999,3.26828 7.2999,7.2999 z" d="m 521.38126,368.43045 a 7.2998991,7.2998991 0 0 1 -7.2999,7.2999 7.2998991,7.2998991 0 0 1 -7.2999,-7.2999 7.2998991,7.2998991 0 0 1 7.2999,-7.2999 7.2998991,7.2998991 0 0 1 7.2999,7.2999 z"
sodipodi:ry="7.2998991" sodipodi:ry="7.2998991"
sodipodi:rx="7.2998991" sodipodi:rx="7.2998991"
sodipodi:cy="368.43045" sodipodi:cy="368.43045"
@@ -1351,15 +1363,45 @@
sodipodi:nodetypes="cc" sodipodi:nodetypes="cc"
inkscape:connector-curvature="0" inkscape:connector-curvature="0"
id="path3284-4-2-3-40" id="path3284-4-2-3-40"
d="m 319.30136,753.47677 0,-27.00021" d="m 325.23661,753.47677 0,-27.00021"
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#EmptyTriangleInL);display:inline" /> style="display:inline;fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#EmptyTriangleInL)" />
<path <path
sodipodi:nodetypes="cc" sodipodi:nodetypes="cc"
inkscape:connector-curvature="0" inkscape:connector-curvature="0"
id="path3284-4-2-3-8" id="path3284-4-2-3-8"
d="m 376.2546,753.65849 0,-27.00021" d="m 414.2546,753.65849 0,-27.00021"
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#EmptyTriangleInL);display:inline" /> style="display:inline;fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#EmptyTriangleInL)" />
<rect
y="754.5235"
x="102.92204"
height="42.939091"
width="118.56741"
id="rect4267-4-7-7-3-3-3"
style="display:inline;fill:#ffffff;stroke:#000000;stroke-width:1;stroke-opacity:1" />
<text
sodipodi:linespacing="125%"
id="text5037-4-6-9-1-4-6"
y="783.03412"
x="160.34656"
style="font-style:normal;font-weight:normal;font-size:20.86613655px;line-height:125%;font-family:Sans;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;display:inline;fill:#000000;fill-opacity:1;stroke:none"
xml:space="preserve"><tspan
id="tspan5184-3-5-2-1-7"
style="font-size:20px;text-align:center;text-anchor:middle"
y="783.03412"
x="160.34656"
sodipodi:role="line">gnocchi</tspan></text>
<path
sodipodi:nodetypes="cc"
inkscape:connector-curvature="0"
id="path3284-4-2-3-40-5"
d="m 191.30136,753.47677 0,-27.00021"
style="display:inline;fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#EmptyTriangleInL-5)" />
<path
sodipodi:nodetypes="cc"
inkscape:connector-curvature="0"
id="path5110-9-6"
d="m 192.18905,726.66568 221.85496,0"
style="display:inline;fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
</g> </g>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 60 KiB

View File

@@ -43,7 +43,7 @@ Introduction
glossary glossary
architecture architecture
dev/contributing contributor/contributing
Getting Started Getting Started
@@ -52,14 +52,7 @@ Getting Started
.. toctree:: .. toctree::
:maxdepth: 1 :maxdepth: 1
dev/environment contributor/index
dev/devstack
deploy/configuration
deploy/conf-files
deploy/apache-mod-wsgi
dev/notifications
dev/testing
dev/rally_link
API References API References
-------------- --------------
@@ -67,7 +60,7 @@ API References
.. toctree:: .. toctree::
:maxdepth: 1 :maxdepth: 1
webapi/v1 api/index
Plugins Plugins
------- -------
@@ -75,30 +68,38 @@ Plugins
.. toctree:: .. toctree::
:maxdepth: 1 :maxdepth: 1
dev/plugin/base-setup contributor/plugin/index
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
Installation
============
.. toctree::
:maxdepth: 2
install/index
Watcher Configuration Options
=============================
.. toctree::
:maxdepth: 2
configuration/index
Admin Guide Admin Guide
=========== ===========
Introduction .. toctree::
------------ :maxdepth: 2
admin/index
User Guide
==========
.. toctree:: .. toctree::
:maxdepth: 1 :maxdepth: 2
deploy/installation user/index
deploy/user-guide
deploy/policy
deploy/gmr
strategies/strategies
Watcher Manual Pages Watcher Manual Pages
==================== ====================
@@ -107,7 +108,7 @@ Watcher Manual Pages
:glob: :glob:
:maxdepth: 1 :maxdepth: 1
man/* man/index
.. # NOTE(mriedem): This is the section where we hide things that we don't .. # NOTE(mriedem): This is the section where we hide things that we don't
# actually want in the table of contents but sphinx build would fail if # actually want in the table of contents but sphinx build would fail if

View File

@@ -0,0 +1,71 @@
2. Edit the ``/etc/watcher/watcher.conf`` file and complete the following
actions:
* In the ``[database]`` section, configure database access:
.. code-block:: ini
[database]
...
connection = mysql+pymysql://watcher:WATCHER_DBPASS@controller/watcher?charset=utf8
* In the `[DEFAULT]` section, configure the transport url for RabbitMQ message broker.
.. code-block:: ini
[DEFAULT]
...
control_exchange = watcher
transport_url = rabbit://openstack:RABBIT_PASS@controller
Replace the RABBIT_PASS with the password you chose for OpenStack user in RabbitMQ.
* In the `[keystone_authtoken]` section, configure Identity service access.
.. code-block:: ini
[keystone_authtoken]
...
auth_uri = http://controller:5000
auth_url = http://controller:35357
memcached_servers = controller:11211
auth_type = password
project_domain_name = default
user_domain_name = default
project_name = service
username = watcher
password = WATCHER_PASS
Replace WATCHER_PASS with the password you chose for the watcher user in the Identity service.
* Watcher interacts with other OpenStack projects via project clients, in order to instantiate these
clients, Watcher requests new session from Identity service. In the `[watcher_client_auth]` section,
configure the identity service access to interact with other OpenStack project clients.
.. code-block:: ini
[watcher_client_auth]
...
auth_type = password
auth_url = http://controller:35357
username = watcher
password = WATCHER_PASS
project_domain_name = default
user_domain_name = default
project_name = service
Replace WATCHER_PASS with the password you chose for the watcher user in the Identity service.
* In the `[oslo_messaging_notifications]` section, configure the messaging driver.
.. code-block:: ini
[oslo_messaging_notifications]
...
driver = messagingv2
3. Populate watcher database:
.. code-block:: ini
su -s /bin/sh -c "watcher-db-manage" watcher

View File

@@ -0,0 +1,139 @@
Prerequisites
-------------
Before you install and configure the Infrastructure Optimization service,
you must create a database, service credentials, and API endpoints.
1. Create the database, complete these steps:
* Use the database access client to connect to the database
server as the ``root`` user:
.. code-block:: console
$ mysql -u root -p
* Create the ``watcher`` database:
.. code-block:: console
CREATE DATABASE watcher CHARACTER SET utf8;
* Grant proper access to the ``watcher`` database:
.. code-block:: console
GRANT ALL PRIVILEGES ON watcher.* TO 'watcher'@'localhost' \
IDENTIFIED BY 'WATCHER_DBPASS';
GRANT ALL PRIVILEGES ON watcher.* TO 'watcher'@'%' \
IDENTIFIED BY 'WATCHER_DBPASS';
Replace ``WATCHER_DBPASS`` with a suitable password.
* Exit the database access client.
.. code-block:: console
exit;
2. Source the ``admin`` credentials to gain access to
admin-only CLI commands:
.. code-block:: console
$ . admin-openrc
3. To create the service credentials, complete these steps:
* Create the ``watcher`` user:
.. code-block:: console
$ openstack user create --domain default --password-prompt watcher
User Password:
Repeat User Password:
+---------------------+----------------------------------+
| Field | Value |
+---------------------+----------------------------------+
| domain_id | default |
| enabled | True |
| id | b18ee38e06034b748141beda8fc8bfad |
| name | watcher |
| options | {} |
| password_expires_at | None |
+---------------------+----------------------------------+
* Add the ``admin`` role to the ``watcher`` user:
.. code-block:: console
$ openstack role add --project service --user watcher admin
.. note::
This command produces no output.
* Create the watcher service entities:
.. code-block:: console
$ openstack service create --name watcher --description "Infrastructure Optimization" infra-optim
+-------------+----------------------------------+
| Field | Value |
+-------------+----------------------------------+
| description | Infrastructure Optimization |
| enabled | True |
| id | d854f6fff0a64f77bda8003c8dedfada |
| name | watcher |
| type | infra-optim |
+-------------+----------------------------------+
4. Create the Infrastructure Optimization service API endpoints:
.. code-block:: console
$ openstack endpoint create --region RegionOne \
infra-optim public http://controller:9322
+-------------+----------------------------------+
| Field | Value |
+-------------+----------------------------------+
| description | Infrastructure Optimization |
| enabled | True |
| id | d854f6fff0a64f77bda8003c8dedfada |
| name | watcher |
| type | infra-optim |
+-------------+----------------------------------+
$ openstack endpoint create --region RegionOne \
infra-optim internal http://controller:9322
+--------------+----------------------------------+
| Field | Value |
+--------------+----------------------------------+
| enabled | True |
| id | 225aef8465ef4df48a341aaaf2b0a390 |
| interface | internal |
| region | RegionOne |
| region_id | RegionOne |
| service_id | d854f6fff0a64f77bda8003c8dedfada |
| service_name | watcher |
| service_type | infra-optim |
| url | http://controller:9322 |
+--------------+----------------------------------+
$ openstack endpoint create --region RegionOne \
infra-optim admin http://controller:9322
+--------------+----------------------------------+
| Field | Value |
+--------------+----------------------------------+
| enabled | True |
| id | 375eb5057fb546edbdf3ee4866179672 |
| interface | admin |
| region | RegionOne |
| region_id | RegionOne |
| service_id | d854f6fff0a64f77bda8003c8dedfada |
| service_name | watcher |
| service_type | infra-optim |
| url | http://controller:9322 |
+--------------+----------------------------------+

View File

@@ -0,0 +1,27 @@
============================================
Infrastructure Optimization service overview
============================================
The Infrastructure Optimization service provides flexible and scalable
optimization service for multi-tenant OpenStack based clouds.
The Infrastructure Optimization service consists of the following components:
``watcher`` command-line client
A CLI to communicate with ``watcher-api`` to optimize the cloud.
``watcher-api`` service
An OpenStack-native REST API that accepts and responds to end-user calls
by processing them and forwarding to appropriate underlying watcher
services via AMQP.
``watcher-decision-engine`` service
It runs audit and return an action plan to achieve optimization goal
specified by the end-user in audit.
``watcher-applier`` service
It executes action plan built by watcher-decision-engine. It interacts with
other OpenStack components like nova to execute the given action
plan.
``watcher-dashboard``
Watcher UI implemented as a plugin for the OpenStack Dashboard.

View File

@@ -0,0 +1,39 @@
===================================
Infrastructure Optimization service
===================================
.. toctree::
:maxdepth: 2
get_started.rst
install.rst
verify.rst
next-steps.rst
The Infrastructure Optimization service (watcher) provides
flexible and scalable resource optimization service for
multi-tenant OpenStack-based clouds.
Watcher provides a complete optimization loop including
everything from a metrics receiver, complex event processor
and profiler, optimization processor 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 migration, increased energy
efficiency—and more!
Watcher also supports a pluggable architecture by which custom
optimization algorithms, data metrics and data profilers can be
developed and inserted into the Watcher framework.
Check the documentation for watcher optimization strategies at
https://docs.openstack.org/watcher/latest/strategies/index.html
Check watcher glossary at
https://docs.openstack.org/watcher/latest/glossary.html
This chapter assumes a working setup of OpenStack following the
`OpenStack Installation Tutorial
<https://docs.openstack.org/project-install-guide/ocata/>`_.

View File

@@ -0,0 +1,35 @@
.. _install-obs:
Install and configure for openSUSE and SUSE Linux Enterprise
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This section describes how to install and configure the Infrastructure
Optimization service for openSUSE Leap 42.1 and
SUSE Linux Enterprise Server 12 SP1.
.. include:: common_prerequisites.rst
Install and configure components
--------------------------------
#. Install the packages:
.. code-block:: console
# zypper --quiet --non-interactive install
.. include:: common_configure.rst
Finalize installation
---------------------
Start the Infrastructure Optimization services and configure them to start when
the system boots:
.. code-block:: console
# systemctl enable openstack-watcher-api.service
# systemctl start openstack-watcher-api.service

View File

@@ -0,0 +1,38 @@
.. _install-rdo:
Install and configure for Red Hat Enterprise Linux and CentOS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This section describes how to install and configure the Infrastructure
Optimization service for Red Hat Enterprise Linux 7 and CentOS 7.
.. include:: common_prerequisites.rst
Install and configure components
--------------------------------
1. Install the packages:
.. code-block:: console
# sudo yum install openstack-watcher-api openstack-watcher-applier \
openstack-watcher-decision-engine
.. include:: common_configure.rst
Finalize installation
---------------------
Start the Infrastructure Optimization services and configure them to start when
the system boots:
.. code-block:: console
# systemctl enable openstack-watcher-api.service \
openstack-watcher-decision-engine.service \
openstack-watcher-applier.service
# systemctl start openstack-watcher-api.service \
openstack-watcher-decision-engine.service \
openstack-watcher-applier.service

View File

@@ -0,0 +1,34 @@
.. _install-ubuntu:
Install and configure for Ubuntu
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This section describes how to install and configure the Infrastructure
Optimization service for Ubuntu 14.04 (LTS).
.. include:: common_prerequisites.rst
Install and configure components
--------------------------------
1. Install the packages:
.. code-block:: console
# apt install watcher-api watcher-decision-engine \
watcher-applier
# apt install python-watcherclient
.. include:: common_configure.rst
Finalize installation
---------------------
Restart the Infrastructure Optimization services:
.. code-block:: console
# service watcher-api restart
# service watcher-decision-engine restart
# service watcher-applier restart

View File

@@ -0,0 +1,20 @@
.. _install:
Install and configure
~~~~~~~~~~~~~~~~~~~~~
This section describes how to install and configure the Infrastructure
Optimization service, code-named watcher, on the controller node.
This section assumes that you already have a working OpenStack
environment with at least the following components installed:
Identity Service, Compute Service, Telemetry data collection service.
Note that installation and configuration vary by distribution.
.. toctree::
:maxdepth: 2
install-obs.rst
install-rdo.rst
install-ubuntu.rst

View File

@@ -0,0 +1,9 @@
.. _next-steps:
Next steps
~~~~~~~~~~
Your OpenStack environment now includes the watcher service.
To add additional services, see
https://docs.openstack.org/project-install-guide/ocata/.

View File

@@ -0,0 +1,119 @@
.. _verify:
Verify operation
~~~~~~~~~~~~~~~~
Verify operation of the Infrastructure Optimization service.
.. note::
Perform these commands on the controller node.
1. Source the ``admin`` project credentials to gain access to
admin-only CLI commands:
.. code-block:: console
$ . admin-openrc
2. List service components to verify successful launch and registration
of each process:
.. code-block:: console
$ openstack optimize service list
+----+-------------------------+------------+--------+
| ID | Name | Host | Status |
+----+-------------------------+------------+--------+
| 1 | watcher-decision-engine | controller | ACTIVE |
| 2 | watcher-applier | controller | ACTIVE |
+----+-------------------------+------------+--------+
3. List goals and strategies:
.. code-block:: console
$ openstack optimize goal list
+--------------------------------------+----------------------+----------------------+
| UUID | Name | Display name |
+--------------------------------------+----------------------+----------------------+
| a8cd6d1a-008b-4ff0-8dbc-b30493fcc5b9 | dummy | Dummy goal |
| 03953f2f-02d0-42b5-9a12-7ba500a54395 | workload_balancing | Workload Balancing |
| de0f8714-984b-4d6b-add1-9cad8120fbce | server_consolidation | Server Consolidation |
| f056bc80-c6d1-40dc-b002-938ccade9385 | thermal_optimization | Thermal Optimization |
| e7062856-892e-4f0f-b84d-b828464b3fd0 | airflow_optimization | Airflow Optimization |
| 1f038da9-b36c-449f-9f04-c225bf3eb478 | unclassified | Unclassified |
+--------------------------------------+----------------------+----------------------+
$ openstack optimize strategy list
+--------------------------------------+---------------------------+---------------------------------------------+----------------------+
| UUID | Name | Display name | Goal |
+--------------------------------------+---------------------------+---------------------------------------------+----------------------+
| 98ae84c8-7c9b-4cbd-8d9c-4bd7c6b106eb | dummy | Dummy strategy | dummy |
| 02a170b6-c72e-479d-95c0-8a4fdd4cc1ef | dummy_with_scorer | Dummy Strategy using sample Scoring Engines | dummy |
| 8bf591b8-57e5-4a9e-8c7d-c37bda735a45 | outlet_temperature | Outlet temperature based strategy | thermal_optimization |
| 8a0810fb-9d9a-47b9-ab25-e442878abc54 | vm_workload_consolidation | VM Workload Consolidation Strategy | server_consolidation |
| 1718859c-3eb5-45cb-9220-9cb79fe42fa5 | basic | Basic offline consolidation | server_consolidation |
| b5e7f5f1-4824-42c7-bb52-cf50724f67bf | workload_stabilization | Workload stabilization | workload_balancing |
| f853d71e-9286-4df3-9d3e-8eaf0f598e07 | workload_balance | Workload Balance Migration Strategy | workload_balancing |
| 58bdfa89-95b5-4630-adf6-fd3af5ff1f75 | uniform_airflow | Uniform airflow migration strategy | airflow_optimization |
| 66fde55d-a612-4be9-8cb0-ea63472b420b | dummy_with_resize | Dummy strategy with resize | dummy |
+--------------------------------------+---------------------------+---------------------------------------------+----------------------+
4. Run an action plan by creating an audit with dummy goal:
.. code-block:: console
$ openstack optimize audit create --goal dummy
+--------------+--------------------------------------+
| Field | Value |
+--------------+--------------------------------------+
| UUID | e94d4826-ad4e-44df-ad93-dff489fde457 |
| Created At | 2017-05-23T11:46:58.763394+00:00 |
| Updated At | None |
| Deleted At | None |
| State | PENDING |
| Audit Type | ONESHOT |
| Parameters | {} |
| Interval | None |
| Goal | dummy |
| Strategy | auto |
| Audit Scope | [] |
| Auto Trigger | False |
+--------------+--------------------------------------+
$ openstack optimize audit list
+--------------------------------------+------------+-----------+-------+----------+--------------+
| UUID | Audit Type | State | Goal | Strategy | Auto Trigger |
+--------------------------------------+------------+-----------+-------+----------+--------------+
| e94d4826-ad4e-44df-ad93-dff489fde457 | ONESHOT | SUCCEEDED | dummy | auto | False |
+--------------------------------------+------------+-----------+-------+----------+--------------+
$ openstack optimize actionplan list
+--------------------------------------+--------------------------------------+-------------+------------+-----------------+
| UUID | Audit | State | Updated At | Global efficacy |
+--------------------------------------+--------------------------------------+-------------+------------+-----------------+
| ba9ce6b3-969c-4b8e-bb61-ae24e8630f81 | e94d4826-ad4e-44df-ad93-dff489fde457 | RECOMMENDED | None | None |
+--------------------------------------+--------------------------------------+-------------+------------+-----------------+
$ openstack optimize actionplan start ba9ce6b3-969c-4b8e-bb61-ae24e8630f81
+---------------------+--------------------------------------+
| Field | Value |
+---------------------+--------------------------------------+
| UUID | ba9ce6b3-969c-4b8e-bb61-ae24e8630f81 |
| Created At | 2017-05-23T11:46:58+00:00 |
| Updated At | 2017-05-23T11:53:12+00:00 |
| Deleted At | None |
| Audit | e94d4826-ad4e-44df-ad93-dff489fde457 |
| Strategy | dummy |
| State | ONGOING |
| Efficacy indicators | [] |
| Global efficacy | {} |
+---------------------+--------------------------------------+
$ openstack optimize actionplan list
+--------------------------------------+--------------------------------------+-----------+---------------------------+-----------------+
| UUID | Audit | State | Updated At | Global efficacy |
+--------------------------------------+--------------------------------------+-----------+---------------------------+-----------------+
| ba9ce6b3-969c-4b8e-bb61-ae24e8630f81 | e94d4826-ad4e-44df-ad93-dff489fde457 | SUCCEEDED | 2017-05-23T11:53:16+00:00 | None |
+--------------------------------------+--------------------------------------+-----------+---------------------------+-----------------+

8
doc/source/man/index.rst Normal file
View File

@@ -0,0 +1,8 @@
.. toctree::
:glob:
:maxdepth: 1
watcher-api
watcher-applier
watcher-db-manage
watcher-decision-engine

View File

@@ -57,7 +57,7 @@ Planner
Default Watcher's planner: Default Watcher's planner:
.. watcher-term:: watcher.decision_engine.planner.default.DefaultPlanner .. watcher-term:: watcher.decision_engine.planner.weight.WeightPlanner
Configuration Configuration
------------- -------------

View File

@@ -61,7 +61,7 @@ Planner
Default Watcher's planner: Default Watcher's planner:
.. watcher-term:: watcher.decision_engine.planner.default.DefaultPlanner .. watcher-term:: watcher.decision_engine.planner.weight.WeightPlanner
Configuration Configuration
------------- -------------

View File

@@ -59,7 +59,7 @@ Planner
Default Watcher's planner: Default Watcher's planner:
.. watcher-term:: watcher.decision_engine.planner.default.DefaultPlanner .. watcher-term:: watcher.decision_engine.planner.weight.WeightPlanner
Configuration Configuration
------------- -------------

View File

@@ -67,7 +67,7 @@ Planner
Default Watcher's planner: Default Watcher's planner:
.. watcher-term:: watcher.decision_engine.planner.default.DefaultPlanner .. watcher-term:: watcher.decision_engine.planner.weight.WeightPlanner
Configuration Configuration

View File

@@ -58,7 +58,7 @@ Planner
Default Watcher's planner: Default Watcher's planner:
.. watcher-term:: watcher.decision_engine.planner.default.DefaultPlanner .. watcher-term:: watcher.decision_engine.planner.weight.WeightPlanner
Configuration Configuration
------------- -------------

View File

@@ -56,7 +56,7 @@ Planner
Default Watcher's planner: Default Watcher's planner:
.. watcher-term:: watcher.decision_engine.planner.default.DefaultPlanner .. watcher-term:: watcher.decision_engine.planner.weight.WeightPlanner
Configuration Configuration
------------- -------------

View File

@@ -0,0 +1,4 @@
.. toctree::
:maxdepth: 2
user-guide

View File

@@ -11,7 +11,7 @@ Watcher User Guide
================== ==================
See the See the
`architecture page <http://docs.openstack.org/developer/watcher/architecture.html>`_ `architecture page <https://docs.openstack.org/watcher/latest/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.

View File

@@ -0,0 +1,4 @@
---
features:
- |
Adds feature to cancel an action-plan.

View File

@@ -0,0 +1,3 @@
---
features:
- Add action for compute node power on/off

View File

@@ -0,0 +1,4 @@
---
features:
- |
Added cinder cluster data model

View File

@@ -38,7 +38,7 @@ from watcher import version as watcher_version
# Add any Sphinx extension module names here, as strings. They can be # Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['reno.sphinxext', extensions = ['reno.sphinxext',
'oslosphinx'] 'openstackdocstheme']
# Add any paths that contain templates here, relative to this directory. # Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates'] templates_path = ['_templates']
@@ -104,7 +104,7 @@ pygments_style = 'sphinx'
# The theme to use for HTML and HTML Help pages. See the documentation for # The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes. # a list of builtin themes.
html_theme = 'default' html_theme = 'openstackdocs'
# Theme options are theme-specific and customize the look and feel of a theme # 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 # further. For a list of options available for each theme, see the

View File

@@ -5,20 +5,22 @@
apscheduler # MIT License apscheduler # MIT License
enum34;python_version=='2.7' or python_version=='2.6' or python_version=='3.3' # BSD enum34;python_version=='2.7' or python_version=='2.6' or python_version=='3.3' # BSD
jsonpatch>=1.1 # BSD jsonpatch>=1.1 # BSD
keystoneauth1>=2.18.0 # Apache-2.0 keystoneauth1>=3.0.1 # Apache-2.0
jsonschema!=2.5.0,<3.0.0,>=2.0.0 # MIT
keystonemiddleware>=4.12.0 # Apache-2.0 keystonemiddleware>=4.12.0 # Apache-2.0
lxml!=3.7.0,>=2.3 # BSD lxml!=3.7.0,>=2.3 # BSD
croniter>=0.3.4 # MIT License
oslo.concurrency>=3.8.0 # Apache-2.0 oslo.concurrency>=3.8.0 # Apache-2.0
oslo.cache>=1.5.0 # Apache-2.0 oslo.cache>=1.5.0 # Apache-2.0
oslo.config>=3.22.0 # Apache-2.0 oslo.config!=4.3.0,!=4.4.0,>=4.0.0 # Apache-2.0
oslo.context>=2.12.0 # Apache-2.0 oslo.context>=2.14.0 # Apache-2.0
oslo.db>=4.19.0 # Apache-2.0 oslo.db>=4.24.0 # Apache-2.0
oslo.i18n>=2.1.0 # Apache-2.0 oslo.i18n!=3.15.2,>=2.1.0 # Apache-2.0
oslo.log>=3.22.0 # Apache-2.0 oslo.log>=3.22.0 # Apache-2.0
oslo.messaging>=5.19.0 # Apache-2.0 oslo.messaging!=5.25.0,>=5.24.2 # Apache-2.0
oslo.policy>=1.17.0 # Apache-2.0 oslo.policy>=1.23.0 # Apache-2.0
oslo.reports>=0.6.0 # Apache-2.0 oslo.reports>=0.6.0 # Apache-2.0
oslo.serialization>=1.10.0 # Apache-2.0 oslo.serialization!=2.19.1,>=1.10.0 # Apache-2.0
oslo.service>=1.10.0 # Apache-2.0 oslo.service>=1.10.0 # Apache-2.0
oslo.utils>=3.20.0 # Apache-2.0 oslo.utils>=3.20.0 # Apache-2.0
oslo.versionedobjects>=1.17.0 # Apache-2.0 oslo.versionedobjects>=1.17.0 # Apache-2.0
@@ -29,13 +31,14 @@ PrettyTable<0.8,>=0.7.1 # BSD
voluptuous>=0.8.9 # BSD License voluptuous>=0.8.9 # BSD License
gnocchiclient>=2.7.0 # Apache-2.0 gnocchiclient>=2.7.0 # Apache-2.0
python-ceilometerclient>=2.5.0 # Apache-2.0 python-ceilometerclient>=2.5.0 # Apache-2.0
python-cinderclient>=2.0.1 # Apache-2.0 python-cinderclient>=3.0.0 # Apache-2.0
python-glanceclient>=2.5.0 # Apache-2.0 python-glanceclient>=2.7.0 # Apache-2.0
python-keystoneclient>=3.8.0 # Apache-2.0 python-keystoneclient>=3.8.0 # Apache-2.0
python-monascaclient>=1.1.0 # Apache-2.0 python-monascaclient>=1.1.0 # Apache-2.0
python-neutronclient>=5.1.0 # Apache-2.0 python-neutronclient>=6.3.0 # Apache-2.0
python-novaclient>=7.1.0 # Apache-2.0 python-novaclient>=9.0.0 # Apache-2.0
python-openstackclient>=3.3.0 # Apache-2.0 python-openstackclient!=3.10.0,>=3.3.0 # Apache-2.0
python-ironicclient>=1.14.0 # Apache-2.0
six>=1.9.0 # MIT six>=1.9.0 # MIT
SQLAlchemy!=1.1.5,!=1.1.6,!=1.1.7,!=1.1.8,>=1.0.10 # MIT SQLAlchemy!=1.1.5,!=1.1.6,!=1.1.7,!=1.1.8,>=1.0.10 # MIT
stevedore>=1.20.0 # Apache-2.0 stevedore>=1.20.0 # Apache-2.0

View File

@@ -5,7 +5,7 @@ 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://docs.openstack.org/developer/watcher/ home-page = https://docs.openstack.org/watcher/latest/
classifier = classifier =
Environment :: OpenStack Environment :: OpenStack
Intended Audience :: Information Technology Intended Audience :: Information Technology
@@ -53,6 +53,7 @@ watcher_goals =
thermal_optimization = watcher.decision_engine.goal.goals:ThermalOptimization thermal_optimization = watcher.decision_engine.goal.goals:ThermalOptimization
workload_balancing = watcher.decision_engine.goal.goals:WorkloadBalancing workload_balancing = watcher.decision_engine.goal.goals:WorkloadBalancing
airflow_optimization = watcher.decision_engine.goal.goals:AirflowOptimization airflow_optimization = watcher.decision_engine.goal.goals:AirflowOptimization
noisy_neighbor = watcher.decision_engine.goal.goals:NoisyNeighborOptimization
watcher_scoring_engines = watcher_scoring_engines =
dummy_scorer = watcher.decision_engine.scoring.dummy_scorer:DummyScorer dummy_scorer = watcher.decision_engine.scoring.dummy_scorer:DummyScorer
@@ -70,6 +71,7 @@ watcher_strategies =
workload_stabilization = watcher.decision_engine.strategy.strategies.workload_stabilization:WorkloadStabilization workload_stabilization = watcher.decision_engine.strategy.strategies.workload_stabilization:WorkloadStabilization
workload_balance = watcher.decision_engine.strategy.strategies.workload_balance:WorkloadBalance workload_balance = watcher.decision_engine.strategy.strategies.workload_balance:WorkloadBalance
uniform_airflow = watcher.decision_engine.strategy.strategies.uniform_airflow:UniformAirflow uniform_airflow = watcher.decision_engine.strategy.strategies.uniform_airflow:UniformAirflow
noisy_neighbor = watcher.decision_engine.strategy.strategies.noisy_neighbor:NoisyNeighbor
watcher_actions = watcher_actions =
migrate = watcher.applier.actions.migration:Migrate migrate = watcher.applier.actions.migration:Migrate
@@ -77,6 +79,7 @@ watcher_actions =
sleep = watcher.applier.actions.sleep:Sleep sleep = watcher.applier.actions.sleep:Sleep
change_nova_service_state = watcher.applier.actions.change_nova_service_state:ChangeNovaServiceState change_nova_service_state = watcher.applier.actions.change_nova_service_state:ChangeNovaServiceState
resize = watcher.applier.actions.resize:Resize resize = watcher.applier.actions.resize:Resize
change_node_power_state = watcher.applier.actions.change_node_power_state:ChangeNodePowerState
watcher_workflow_engines = watcher_workflow_engines =
taskflow = watcher.applier.workflow_engine.default:DefaultWorkFlowEngine taskflow = watcher.applier.workflow_engine.default:DefaultWorkFlowEngine
@@ -87,6 +90,8 @@ watcher_planners =
watcher_cluster_data_model_collectors = watcher_cluster_data_model_collectors =
compute = watcher.decision_engine.model.collector.nova:NovaClusterDataModelCollector compute = watcher.decision_engine.model.collector.nova:NovaClusterDataModelCollector
storage = watcher.decision_engine.model.collector.cinder:CinderClusterDataModelCollector
[pbr] [pbr]
warnerrors = true warnerrors = true
@@ -104,6 +109,7 @@ source-dir = doc/source
build-dir = doc/build build-dir = doc/build
fresh_env = 1 fresh_env = 1
all_files = 1 all_files = 1
warning-is-error = 1
[upload_sphinx] [upload_sphinx]
upload-dir = doc/build/html upload-dir = doc/build/html

View File

@@ -2,7 +2,7 @@
# 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>=4.0 # Apache-2.0 coverage!=4.4,>=4.0 # Apache-2.0
doc8 # Apache-2.0 doc8 # Apache-2.0
freezegun>=0.3.6 # Apache-2.0 freezegun>=0.3.6 # Apache-2.0
hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0 hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0
@@ -15,12 +15,13 @@ testscenarios>=0.4 # Apache-2.0/BSD
testtools>=1.4.0 # MIT testtools>=1.4.0 # MIT
# Doc requirements # Doc requirements
oslosphinx>=4.7.0 # Apache-2.0 openstackdocstheme>=1.11.0 # Apache-2.0
sphinx>=1.5.1 # BSD sphinx>=1.6.2 # BSD
sphinxcontrib-pecanwsme>=0.8 # Apache-2.0 sphinxcontrib-pecanwsme>=0.8 # Apache-2.0
# releasenotes # releasenotes
reno>=1.8.0 # Apache-2.0 reno!=2.3.1,>=1.8.0 # Apache-2.0
# bandit # bandit
bandit>=1.1.0 # Apache-2.0 bandit>=1.1.0 # Apache-2.0

View File

@@ -6,13 +6,16 @@ skipsdist = True
[testenv] [testenv]
usedevelop = True usedevelop = True
whitelist_externals = find whitelist_externals = find
rm
install_command = pip install -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages} install_command = pip install -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages}
setenv = setenv =
VIRTUAL_ENV={envdir} VIRTUAL_ENV={envdir}
deps = -r{toxinidir}/test-requirements.txt deps = -r{toxinidir}/test-requirements.txt
commands = commands =
rm -f .testrepository/times.dbm
find . -type f -name "*.py[c|o]" -delete find . -type f -name "*.py[c|o]" -delete
ostestr --concurrency=6 {posargs} ostestr --concurrency=6 {posargs}
passenv = http_proxy HTTP_PROXY https_proxy HTTPS_PROXY no_proxy NO_PROXY
[testenv:pep8] [testenv:pep8]
commands = commands =
@@ -41,12 +44,13 @@ commands = oslo_debug_helper -t watcher/tests {posargs}
[testenv:genconfig] [testenv:genconfig]
sitepackages = False sitepackages = False
commands = commands =
oslo-config-generator --config-file etc/watcher/watcher-config-generator.conf oslo-config-generator --config-file etc/watcher/oslo-config-generator/watcher.conf
[flake8] [flake8]
show-source=True show-source=True
ignore= H105,E123,E226,N320 ignore= H105,E123,E226,N320,H202
builtins= _ builtins= _
enable-extensions = H106,H203
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build,*sqlalchemy/alembic/versions/*,demo/,releasenotes exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build,*sqlalchemy/alembic/versions/*,demo/,releasenotes
[testenv:wheel] [testenv:wheel]

View File

@@ -32,16 +32,6 @@ _C = _translators.contextual_form
# The plural translation function using the name "_P" # The plural translation function using the name "_P"
_P = _translators.plural_form _P = _translators.plural_form
# Translators for log levels.
#
# The abbreviated names are meant to reflect the usual use of a short
# name like '_'. The "L" is for "log" and the other letter comes from
# the level.
_LI = _translators.log_info
_LW = _translators.log_warning
_LE = _translators.log_error
_LC = _translators.log_critical
def lazy_translation_enabled(): def lazy_translation_enabled():
return _lazy.USE_LAZY return _lazy.USE_LAZY

View File

@@ -488,6 +488,7 @@ class ActionPlansController(rest.RestController):
raise exception.PatchError(patch=patch, reason=e) raise exception.PatchError(patch=patch, reason=e)
launch_action_plan = False launch_action_plan = False
cancel_action_plan = False
# transitions that are allowed via PATCH # transitions that are allowed via PATCH
allowed_patch_transitions = [ allowed_patch_transitions = [
@@ -496,7 +497,7 @@ class ActionPlansController(rest.RestController):
(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.CANCELLING),
(ap_objects.State.PENDING, (ap_objects.State.PENDING,
ap_objects.State.CANCELLED), ap_objects.State.CANCELLED),
] ]
@@ -515,6 +516,8 @@ class ActionPlansController(rest.RestController):
if action_plan.state == ap_objects.State.PENDING: if action_plan.state == ap_objects.State.PENDING:
launch_action_plan = True launch_action_plan = True
if action_plan.state == ap_objects.State.CANCELLED:
cancel_action_plan = True
# Update only the fields that have changed # Update only the fields that have changed
for field in objects.ActionPlan.fields: for field in objects.ActionPlan.fields:
@@ -534,6 +537,16 @@ class ActionPlansController(rest.RestController):
action_plan_to_update.save() action_plan_to_update.save()
# NOTE: if action plan is cancelled from pending or recommended
# state update action state here only
if cancel_action_plan:
filters = {'action_plan_uuid': action_plan.uuid}
actions = objects.Action.list(pecan.request.context,
filters=filters, eager=True)
for a in actions:
a.state = objects.action.State.CANCELLED
a.save()
if launch_action_plan: if launch_action_plan:
applier_client = rpcapi.ApplierAPI() applier_client = rpcapi.ApplierAPI()
applier_client.launch_action_plan(pecan.request.context, applier_client.launch_action_plan(pecan.request.context,

View File

@@ -65,7 +65,7 @@ class AuditPostType(wtypes.Base):
parameters = wtypes.wsattr({wtypes.text: types.jsontype}, mandatory=False, parameters = wtypes.wsattr({wtypes.text: types.jsontype}, mandatory=False,
default={}) default={})
interval = wsme.wsattr(int, mandatory=False) interval = wsme.wsattr(types.interval_or_cron, mandatory=False)
scope = wtypes.wsattr(types.jsontype, readonly=True) scope = wtypes.wsattr(types.jsontype, readonly=True)
@@ -261,7 +261,7 @@ class Audit(base.APIBase):
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) interval = wsme.wsattr(wtypes.text, mandatory=False)
"""Launch audit periodically (in seconds)""" """Launch audit periodically (in seconds)"""
scope = wsme.wsattr(types.jsontype, mandatory=False) scope = wsme.wsattr(types.jsontype, mandatory=False)
@@ -270,6 +270,9 @@ class Audit(base.APIBase):
auto_trigger = wsme.wsattr(bool, mandatory=False, default=False) auto_trigger = wsme.wsattr(bool, mandatory=False, default=False)
"""Autoexecute action plan once audit is succeeded""" """Autoexecute action plan once audit is succeeded"""
next_run_time = wsme.wsattr(datetime.datetime, mandatory=False)
"""The next time audit launch"""
def __init__(self, **kwargs): def __init__(self, **kwargs):
self.fields = [] self.fields = []
fields = list(objects.Audit.fields) fields = list(objects.Audit.fields)
@@ -301,7 +304,8 @@ class Audit(base.APIBase):
audit.unset_fields_except(['uuid', 'audit_type', 'state', audit.unset_fields_except(['uuid', 'audit_type', 'state',
'goal_uuid', 'interval', 'scope', 'goal_uuid', 'interval', 'scope',
'strategy_uuid', 'goal_name', 'strategy_uuid', 'goal_name',
'strategy_name', 'auto_trigger']) 'strategy_name', 'auto_trigger',
'next_run_time'])
audit.links = [link.Link.make_link('self', url, audit.links = [link.Link.make_link('self', url,
'audits', audit.uuid), 'audits', audit.uuid),
@@ -325,9 +329,10 @@ class Audit(base.APIBase):
created_at=datetime.datetime.utcnow(), created_at=datetime.datetime.utcnow(),
deleted_at=None, deleted_at=None,
updated_at=datetime.datetime.utcnow(), updated_at=datetime.datetime.utcnow(),
interval=7200, interval='7200',
scope=[], scope=[],
auto_trigger=False) auto_trigger=False,
next_run_time=datetime.datetime.utcnow())
sample.goal_id = '7ae81bb3-dec3-4289-8d6c-da80bd8001ae' sample.goal_id = '7ae81bb3-dec3-4289-8d6c-da80bd8001ae'
sample.strategy_id = '7ae81bb3-dec3-4289-8d6c-da80bd8001ff' sample.strategy_id = '7ae81bb3-dec3-4289-8d6c-da80bd8001ff'

View File

@@ -109,6 +109,21 @@ class AuditTemplatePostType(wtypes.Base):
common_utils.Draft4Validator( common_utils.Draft4Validator(
default.DefaultScope.DEFAULT_SCHEMA).validate(audit_template.scope) default.DefaultScope.DEFAULT_SCHEMA).validate(audit_template.scope)
include_host_aggregates = False
exclude_host_aggregates = False
for rule in audit_template.scope:
if 'host_aggregates' in rule:
include_host_aggregates = True
elif 'exclude' in rule:
for resource in rule['exclude']:
if 'host_aggregates' in resource:
exclude_host_aggregates = True
if include_host_aggregates and exclude_host_aggregates:
raise exception.Invalid(
message=_(
"host_aggregates can't be "
"included and excluded together"))
if audit_template.strategy: if audit_template.strategy:
available_strategies = objects.Strategy.list( available_strategies = objects.Strategy.list(
AuditTemplatePostType._ctx) AuditTemplatePostType._ctx)
@@ -535,7 +550,7 @@ 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 audit_template: UUID or name of an audit template. :param 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
@@ -582,7 +597,7 @@ class AuditTemplatesController(rest.RestController):
def patch(self, audit_template, patch): def patch(self, audit_template, patch):
"""Update an existing audit template. """Update an existing audit template.
:param audit template_uuid: UUID of a audit template. :param template_uuid: UUID of a audit template.
:param patch: a json PATCH document to apply to this audit template. :param patch: a json PATCH document to apply to this audit template.
""" """
if self.from_audit_templates: if self.from_audit_templates:
@@ -630,7 +645,7 @@ class AuditTemplatesController(rest.RestController):
def delete(self, audit_template): def delete(self, audit_template):
"""Delete a audit template. """Delete a audit template.
:param audit template_uuid: UUID or name of an audit template. :param template_uuid: UUID or name of an audit template.
""" """
context = pecan.request.context context = pecan.request.context
audit_template_to_delete = api_utils.get_resource('AuditTemplate', audit_template_to_delete = api_utils.get_resource('AuditTemplate',

View File

@@ -34,6 +34,7 @@ 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 utils as api_utils from watcher.api.controllers.v1 import utils as api_utils
from watcher.common import context
from watcher.common import exception from watcher.common import exception
from watcher.common import policy from watcher.common import policy
from watcher import objects from watcher import objects
@@ -51,6 +52,7 @@ class Service(base.APIBase):
""" """
_status = None _status = None
_context = context.RequestContext(is_admin=True)
def _get_status(self): def _get_status(self):
return self._status return self._status

View File

@@ -43,6 +43,28 @@ class UuidOrNameType(wtypes.UserType):
return UuidOrNameType.validate(value) return UuidOrNameType.validate(value)
class IntervalOrCron(wtypes.UserType):
"""A simple int value or cron syntax type"""
basetype = wtypes.text
name = 'interval_or_cron'
@staticmethod
def validate(value):
if not (utils.is_int_like(value) or utils.is_cron_like(value)):
raise exception.InvalidIntervalOrCron(name=value)
return value
@staticmethod
def frombasetype(value):
if value is None:
return None
return IntervalOrCron.validate(value)
interval_or_cron = IntervalOrCron()
class NameType(wtypes.UserType): class NameType(wtypes.UserType):
"""A simple logical name type.""" """A simple logical name type."""
@@ -181,7 +203,7 @@ class JsonPatchType(wtypes.Base):
@staticmethod @staticmethod
def mandatory_attrs(): def mandatory_attrs():
"""Retruns a list of mandatory attributes. """Returns a list of mandatory attributes.
Mandatory attributes can't be removed from the document. This Mandatory attributes can't be removed from the document. This
method should be overwritten by derived class. method should be overwritten by derived class.

View File

@@ -55,7 +55,7 @@ def validate_sort_dir(sort_dir):
def validate_search_filters(filters, allowed_fields): def validate_search_filters(filters, allowed_fields):
# Very leightweight validation for now # Very lightweight validation for now
# todo: improve this (e.g. https://www.parse.com/docs/rest/guide/#queries) # todo: improve this (e.g. https://www.parse.com/docs/rest/guide/#queries)
for filter_name in filters.keys(): for filter_name in filters.keys():
if filter_name not in allowed_fields: if filter_name not in allowed_fields:

97
watcher/api/scheduling.py Normal file
View File

@@ -0,0 +1,97 @@
# -*- encoding: utf-8 -*-
# Copyright (c) 2017 Servionica
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import datetime
from oslo_config import cfg
from oslo_log import log
from oslo_utils import timeutils
import six
from watcher.common import context as watcher_context
from watcher.common import scheduling
from watcher import notifications
from watcher import objects
CONF = cfg.CONF
LOG = log.getLogger(__name__)
class APISchedulingService(scheduling.BackgroundSchedulerService):
def __init__(self, gconfig=None, **options):
self.services_status = {}
gconfig = None or {}
super(APISchedulingService, self).__init__(gconfig, **options)
def get_services_status(self, context):
services = objects.service.Service.list(context)
for service in services:
result = self.get_service_status(context, service.id)
if service.id not in self.services_status.keys():
self.services_status[service.id] = result
continue
if self.services_status[service.id] != result:
self.services_status[service.id] = result
notifications.service.send_service_update(context, service,
state=result)
def get_service_status(self, context, service_id):
service = objects.Service.get(context, service_id)
last_heartbeat = (service.last_seen_up or service.updated_at
or service.created_at)
if isinstance(last_heartbeat, six.string_types):
# NOTE(russellb) If this service came in over rpc via
# conductor, then the timestamp will be a string and needs to be
# converted back to a datetime.
last_heartbeat = timeutils.parse_strtime(last_heartbeat)
else:
# Objects have proper UTC timezones, but the timeutils comparison
# below does not (and will fail)
last_heartbeat = last_heartbeat.replace(tzinfo=None)
elapsed = timeutils.delta_seconds(last_heartbeat, timeutils.utcnow())
is_up = abs(elapsed) <= CONF.service_down_time
if not is_up:
LOG.warning('Seems service %(name)s on host %(host)s is down. '
'Last heartbeat was %(lhb)s. Elapsed time is %(el)s',
{'name': service.name,
'host': service.host,
'lhb': str(last_heartbeat), 'el': str(elapsed)})
return objects.service.ServiceStatus.FAILED
return objects.service.ServiceStatus.ACTIVE
def start(self):
"""Start service."""
context = watcher_context.make_context(is_admin=True)
self.add_job(self.get_services_status, name='service_status',
trigger='interval', jobstore='default', args=[context],
next_run_time=datetime.datetime.now(), seconds=60)
super(APISchedulingService, self).start()
def stop(self):
"""Stop service."""
self.shutdown()
def wait(self):
"""Wait for service to complete."""
def reset(self):
"""Reset service.
Called in case service running in daemon mode receives SIGHUP.
"""

View File

@@ -20,6 +20,7 @@ from oslo_log import log
from watcher.applier.action_plan import base from watcher.applier.action_plan import base
from watcher.applier import default from watcher.applier import default
from watcher.common import exception
from watcher import notifications from watcher import notifications
from watcher import objects from watcher import objects
from watcher.objects import fields from watcher.objects import fields
@@ -39,6 +40,9 @@ class DefaultActionPlanHandler(base.BaseActionPlanHandler):
try: try:
action_plan = objects.ActionPlan.get_by_uuid( action_plan = objects.ActionPlan.get_by_uuid(
self.ctx, self.action_plan_uuid, eager=True) self.ctx, self.action_plan_uuid, eager=True)
if action_plan.state == objects.action_plan.State.CANCELLED:
self._update_action_from_pending_to_cancelled()
return
action_plan.state = objects.action_plan.State.ONGOING action_plan.state = objects.action_plan.State.ONGOING
action_plan.save() action_plan.save()
notifications.action_plan.send_action_notification( notifications.action_plan.send_action_notification(
@@ -54,6 +58,12 @@ class DefaultActionPlanHandler(base.BaseActionPlanHandler):
self.ctx, action_plan, self.ctx, action_plan,
action=fields.NotificationAction.EXECUTION, action=fields.NotificationAction.EXECUTION,
phase=fields.NotificationPhase.END) phase=fields.NotificationPhase.END)
except exception.ActionPlanCancelled as e:
LOG.exception(e)
action_plan.state = objects.action_plan.State.CANCELLED
self._update_action_from_pending_to_cancelled()
except Exception as e: except Exception as e:
LOG.exception(e) LOG.exception(e)
action_plan.state = objects.action_plan.State.FAILED action_plan.state = objects.action_plan.State.FAILED
@@ -64,3 +74,12 @@ class DefaultActionPlanHandler(base.BaseActionPlanHandler):
phase=fields.NotificationPhase.ERROR) phase=fields.NotificationPhase.ERROR)
finally: finally:
action_plan.save() action_plan.save()
def _update_action_from_pending_to_cancelled(self):
filters = {'action_plan_uuid': self.action_plan_uuid,
'state': objects.action.State.PENDING}
actions = objects.Action.list(self.ctx, filters=filters, eager=True)
if actions:
for a in actions:
a.state = objects.action.State.CANCELLED
a.save()

View File

@@ -18,6 +18,7 @@
import abc import abc
import jsonschema
import six import six
from watcher.common import clients from watcher.common import clients
@@ -32,6 +33,9 @@ class BaseAction(loadable.Loadable):
# watcher dashboard and will be nested in input_parameters # watcher dashboard and will be nested in input_parameters
RESOURCE_ID = 'resource_id' RESOURCE_ID = 'resource_id'
# Add action class name to the list, if implementing abort.
ABORT_TRUE = ['Sleep', 'Nop']
def __init__(self, config, osc=None): def __init__(self, config, osc=None):
"""Constructor """Constructor
@@ -111,7 +115,7 @@ class BaseAction(loadable.Loadable):
def post_condition(self): def post_condition(self):
"""Hook: called after the execution of an action """Hook: called after the execution of an action
This function is called regardless of whether an action succeded or This function is called regardless of whether an action succeeded or
not. So you can use it to perform cleanup operations. not. So you can use it to perform cleanup operations.
""" """
raise NotImplementedError() raise NotImplementedError()
@@ -127,5 +131,22 @@ class BaseAction(loadable.Loadable):
raise NotImplementedError() raise NotImplementedError()
def validate_parameters(self): def validate_parameters(self):
self.schema(self.input_parameters) try:
return True jsonschema.validate(self.input_parameters, self.schema)
return True
except jsonschema.ValidationError as e:
raise e
@abc.abstractmethod
def get_description(self):
"""Description of the action"""
raise NotImplementedError()
def check_abort(self):
if self.__class__.__name__ is 'Migrate':
if self.migration_type == self.LIVE_MIGRATION:
return True
else:
return False
else:
return bool(self.__class__.__name__ in self.ABORT_TRUE)

View File

@@ -0,0 +1,118 @@
# -*- encoding: utf-8 -*-
# Copyright (c) 2017 ZTE
#
# Authors: Li Canwei
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import enum
from watcher._i18n import _
from watcher.applier.actions import base
from watcher.common import exception
class NodeState(enum.Enum):
POWERON = 'on'
POWEROFF = 'off'
class ChangeNodePowerState(base.BaseAction):
"""Compute node power on/off
By using this action, you will be able to on/off the power of a
compute node.
The action schema is::
schema = Schema({
'resource_id': str,
'state': str,
})
The `resource_id` references a ironic node id (list of available
ironic node is returned by this command: ``ironic node-list``).
The `state` value should either be `on` or `off`.
"""
STATE = 'state'
@property
def schema(self):
return {
'type': 'object',
'properties': {
'resource_id': {
'type': 'string',
"minlength": 1
},
'state': {
'type': 'string',
'enum': [NodeState.POWERON.value,
NodeState.POWEROFF.value]
}
},
'required': ['resource_id', 'state'],
'additionalProperties': False,
}
@property
def node_uuid(self):
return self.resource_id
@property
def state(self):
return self.input_parameters.get(self.STATE)
def execute(self):
target_state = self.state
return self._node_manage_power(target_state)
def revert(self):
if self.state == NodeState.POWERON.value:
target_state = NodeState.POWEROFF.value
elif self.state == NodeState.POWEROFF.value:
target_state = NodeState.POWERON.value
return self._node_manage_power(target_state)
def _node_manage_power(self, state):
if state is None:
raise exception.IllegalArgumentException(
message=_("The target state is not defined"))
result = False
ironic_client = self.osc.ironic()
nova_client = self.osc.nova()
if state == NodeState.POWEROFF.value:
node_info = ironic_client.node.get(self.node_uuid).to_dict()
compute_node_id = node_info['extra']['compute_node_id']
compute_node = nova_client.hypervisors.get(compute_node_id)
compute_node = compute_node.to_dict()
if (compute_node['running_vms'] == 0):
result = ironic_client.node.set_power_state(
self.node_uuid, state)
else:
result = ironic_client.node.set_power_state(self.node_uuid, state)
return result
def pre_condition(self):
pass
def post_condition(self):
pass
def get_description(self):
"""Description of the action"""
return ("Compute node power on/off through ironic.")

View File

@@ -17,9 +17,6 @@
# limitations under the License. # limitations under the License.
# #
import six
import voluptuous
from watcher._i18n import _ from watcher._i18n import _
from watcher.applier.actions import base from watcher.applier.actions import base
from watcher.common import exception from watcher.common import exception
@@ -51,15 +48,24 @@ class ChangeNovaServiceState(base.BaseAction):
@property @property
def schema(self): def schema(self):
return voluptuous.Schema({ return {
voluptuous.Required(self.RESOURCE_ID): 'type': 'object',
voluptuous.All( 'properties': {
voluptuous.Any(*six.string_types), 'resource_id': {
voluptuous.Length(min=1)), 'type': 'string',
voluptuous.Required(self.STATE): "minlength": 1
voluptuous.Any(*[state.value },
for state in list(element.ServiceState)]), 'state': {
}) 'type': 'string',
'enum': [element.ServiceState.ONLINE.value,
element.ServiceState.OFFLINE.value,
element.ServiceState.ENABLED.value,
element.ServiceState.DISABLED.value]
}
},
'required': ['resource_id', 'state'],
'additionalProperties': False,
}
@property @property
def host(self): def host(self):
@@ -101,3 +107,9 @@ class ChangeNovaServiceState(base.BaseAction):
def post_condition(self): def post_condition(self):
pass pass
def get_description(self):
"""Description of the action"""
return ("Disables or enables the nova-compute service."
"A disabled nova-compute service can not be selected "
"by the nova for future deployment of new server.")

View File

@@ -17,15 +17,12 @@
# limitations under the License. # limitations under the License.
# #
from oslo_log import log
import six
import voluptuous
from oslo_log import log
from watcher._i18n import _ from watcher._i18n import _
from watcher.applier.actions import base from watcher.applier.actions import base
from watcher.common import exception from watcher.common import exception
from watcher.common import nova_helper from watcher.common import nova_helper
from watcher.common import utils
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
@@ -62,28 +59,36 @@ class Migrate(base.BaseAction):
DESTINATION_NODE = 'destination_node' DESTINATION_NODE = 'destination_node'
SOURCE_NODE = 'source_node' SOURCE_NODE = 'source_node'
def check_resource_id(self, value):
if (value is not None and
len(value) > 0 and not
utils.is_uuid_like(value)):
raise voluptuous.Invalid(_("The parameter "
"resource_id is invalid."))
@property @property
def schema(self): def schema(self):
return voluptuous.Schema({ return {
voluptuous.Required(self.RESOURCE_ID): self.check_resource_id, 'type': 'object',
voluptuous.Required( 'properties': {
self.MIGRATION_TYPE, default=self.LIVE_MIGRATION): 'destination_node': {
voluptuous.Any( "anyof": [
*[self.LIVE_MIGRATION, self.COLD_MIGRATION]), {'type': 'string', "minLength": 1},
voluptuous.Required(self.DESTINATION_NODE): {'type': 'None'}
voluptuous.All(voluptuous.Any(*six.string_types), ]
voluptuous.Length(min=1)), },
voluptuous.Required(self.SOURCE_NODE): 'migration_type': {
voluptuous.All(voluptuous.Any(*six.string_types), 'type': 'string',
voluptuous.Length(min=1)), "enum": ["live", "cold"]
}) },
'resource_id': {
'type': 'string',
"minlength": 1,
"pattern": ("^([a-fA-F0-9]){8}-([a-fA-F0-9]){4}-"
"([a-fA-F0-9]){4}-([a-fA-F0-9]){4}-"
"([a-fA-F0-9]){12}$")
},
'source_node': {
'type': 'string',
"minLength": 1
}
},
'required': ['migration_type', 'resource_id', 'source_node'],
'additionalProperties': False,
}
@property @property
def instance_uuid(self): def instance_uuid(self):
@@ -137,13 +142,28 @@ class Migrate(base.BaseAction):
LOG.critical("Unexpected error occurred. Migration failed for " LOG.critical("Unexpected error occurred. Migration failed for "
"instance %s. Leaving instance on previous " "instance %s. Leaving instance on previous "
"host.", self.instance_uuid) "host.", self.instance_uuid)
return result return result
def migrate(self, destination): def _abort_cold_migrate(self, nova):
# TODO(adisky): currently watcher uses its own version of cold migrate
# implement cold migrate using nova dependent on the blueprint
# https://blueprints.launchpad.net/nova/+spec/cold-migration-with-target
# Abort operation for cold migrate is dependent on blueprint
# https://blueprints.launchpad.net/nova/+spec/abort-cold-migration
LOG.warning("Abort operation for cold migration is not implemented")
def _abort_live_migrate(self, nova, source, destination):
return nova.abort_live_migrate(instance_id=self.instance_uuid,
source=source, destination=destination)
def migrate(self, destination=None):
nova = nova_helper.NovaHelper(osc=self.osc) nova = nova_helper.NovaHelper(osc=self.osc)
LOG.debug("Migrate instance %s to %s", self.instance_uuid, if destination is None:
destination) LOG.debug("Migrating instance %s, destination node will be "
"determined by nova-scheduler", self.instance_uuid)
else:
LOG.debug("Migrate instance %s to %s", self.instance_uuid,
destination)
instance = nova.find_instance(self.instance_uuid) instance = nova.find_instance(self.instance_uuid)
if instance: if instance:
if self.migration_type == self.LIVE_MIGRATION: if self.migration_type == self.LIVE_MIGRATION:
@@ -164,6 +184,19 @@ class Migrate(base.BaseAction):
def revert(self): def revert(self):
return self.migrate(destination=self.source_node) return self.migrate(destination=self.source_node)
def abort(self):
nova = nova_helper.NovaHelper(osc=self.osc)
instance = nova.find_instance(self.instance_uuid)
if instance:
if self.migration_type == self.COLD_MIGRATION:
return self._abort_cold_migrate(nova)
elif self.migration_type == self.LIVE_MIGRATION:
return self._abort_live_migrate(
nova, source=self.source_node,
destination=self.destination_node)
else:
raise exception.InstanceNotFound(name=self.instance_uuid)
def pre_condition(self): def pre_condition(self):
# TODO(jed): check if the instance exists / check if the instance is on # TODO(jed): check if the instance exists / check if the instance is on
# the source_node # the source_node
@@ -172,3 +205,7 @@ class Migrate(base.BaseAction):
def post_condition(self): def post_condition(self):
# TODO(jed): check extra parameters (network response, etc.) # TODO(jed): check extra parameters (network response, etc.)
pass pass
def get_description(self):
"""Description of the action"""
return "Moving a VM instance from source_node to destination_node"

View File

@@ -18,12 +18,9 @@
# #
from oslo_log import log from oslo_log import log
import six
import voluptuous
from watcher.applier.actions import base from watcher.applier.actions import base
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
@@ -43,10 +40,16 @@ class Nop(base.BaseAction):
@property @property
def schema(self): def schema(self):
return voluptuous.Schema({ return {
voluptuous.Required(self.MESSAGE): voluptuous.Any( 'type': 'object',
voluptuous.Any(*six.string_types), None) 'properties': {
}) 'message': {
'type': ['string', 'null']
}
},
'required': ['message'],
'additionalProperties': False,
}
@property @property
def message(self): def message(self):
@@ -65,3 +68,11 @@ class Nop(base.BaseAction):
def post_condition(self): def post_condition(self):
pass pass
def get_description(self):
"""Description of the action"""
return "Logging a NOP message"
def abort(self):
LOG.debug("Abort action NOP")
return True

View File

@@ -18,13 +18,9 @@
# #
from oslo_log import log from oslo_log import log
import six
import voluptuous
from watcher._i18n import _
from watcher.applier.actions import base from watcher.applier.actions import base
from watcher.common import nova_helper from watcher.common import nova_helper
from watcher.common import utils
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
@@ -49,21 +45,26 @@ class Resize(base.BaseAction):
# input parameters constants # input parameters constants
FLAVOR = 'flavor' FLAVOR = 'flavor'
def check_resource_id(self, value):
if (value is not None and
len(value) > 0 and not
utils.is_uuid_like(value)):
raise voluptuous.Invalid(_("The parameter "
"resource_id is invalid."))
@property @property
def schema(self): def schema(self):
return voluptuous.Schema({ return {
voluptuous.Required(self.RESOURCE_ID): self.check_resource_id, 'type': 'object',
voluptuous.Required(self.FLAVOR): 'properties': {
voluptuous.All(voluptuous.Any(*six.string_types), 'resource_id': {
voluptuous.Length(min=1)), 'type': 'string',
}) 'minlength': 1,
'pattern': ('^([a-fA-F0-9]){8}-([a-fA-F0-9]){4}-'
'([a-fA-F0-9]){4}-([a-fA-F0-9]){4}-'
'([a-fA-F0-9]){12}$')
},
'flavor': {
'type': 'string',
'minlength': 1,
},
},
'required': ['resource_id', 'flavor'],
'additionalProperties': False,
}
@property @property
def instance_uuid(self): def instance_uuid(self):
@@ -104,3 +105,7 @@ class Resize(base.BaseAction):
def post_condition(self): def post_condition(self):
# TODO(jed): check extra parameters (network response, etc.) # TODO(jed): check extra parameters (network response, etc.)
pass pass
def get_description(self):
"""Description of the action"""
return "Resize a server with specified flavor."

View File

@@ -20,8 +20,6 @@
import time import time
from oslo_log import log from oslo_log import log
import voluptuous
from watcher.applier.actions import base from watcher.applier.actions import base
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
@@ -43,10 +41,17 @@ class Sleep(base.BaseAction):
@property @property
def schema(self): def schema(self):
return voluptuous.Schema({ return {
voluptuous.Required(self.DURATION, default=1): 'type': 'object',
voluptuous.All(float, voluptuous.Range(min=0)) 'properties': {
}) 'duration': {
'type': 'number',
'minimum': 0
},
},
'required': ['duration'],
'additionalProperties': False,
}
@property @property
def duration(self): def duration(self):
@@ -66,3 +71,11 @@ class Sleep(base.BaseAction):
def post_condition(self): def post_condition(self):
pass pass
def get_description(self):
"""Description of the action"""
return "Wait for a given interval in seconds."
def abort(self):
LOG.debug("Abort action sleep")
return True

3
watcher/applier/default.py Normal file → Executable file
View File

@@ -58,5 +58,6 @@ class DefaultApplier(base.BaseApplier):
LOG.debug("Executing action plan %s ", action_plan_uuid) LOG.debug("Executing action plan %s ", action_plan_uuid)
filters = {'action_plan_uuid': action_plan_uuid} filters = {'action_plan_uuid': action_plan_uuid}
actions = objects.Action.list(self.context, filters=filters) actions = objects.Action.list(self.context, filters=filters,
eager=True)
return self.engine.execute(actions) return self.engine.execute(actions)

View File

@@ -17,13 +17,17 @@
# #
import abc import abc
import six
import time
import eventlet
from oslo_log import log from oslo_log import log
import six
from taskflow import task as flow_task from taskflow import task as flow_task
from watcher.applier.actions import factory from watcher.applier.actions import factory
from watcher.common import clients from watcher.common import clients
from watcher.common import exception
from watcher.common.loader import loadable from watcher.common.loader import loadable
from watcher import notifications from watcher import notifications
from watcher import objects from watcher import objects
@@ -32,6 +36,9 @@ from watcher.objects import fields
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
CANCEL_STATE = [objects.action_plan.State.CANCELLING,
objects.action_plan.State.CANCELLED]
@six.add_metaclass(abc.ABCMeta) @six.add_metaclass(abc.ABCMeta)
class BaseWorkFlowEngine(loadable.Loadable): class BaseWorkFlowEngine(loadable.Loadable):
@@ -122,16 +129,34 @@ class BaseTaskFlowActionContainer(flow_task.Task):
def do_post_execute(self): def do_post_execute(self):
raise NotImplementedError() raise NotImplementedError()
@abc.abstractmethod
def do_revert(self):
raise NotImplementedError()
@abc.abstractmethod
def do_abort(self, *args, **kwargs):
raise NotImplementedError()
# NOTE(alexchadin): taskflow does 3 method calls (pre_execute, execute, # NOTE(alexchadin): taskflow does 3 method calls (pre_execute, execute,
# post_execute) independently. We want to support notifications in base # post_execute) independently. We want to support notifications in base
# class, so child's methods should be named with `do_` prefix and wrapped. # class, so child's methods should be named with `do_` prefix and wrapped.
def pre_execute(self): def pre_execute(self):
try: try:
# NOTE(adisky): check the state of action plan before starting
# next action, if action plan is cancelled raise the exceptions
# so that taskflow does not schedule further actions.
action_plan = objects.ActionPlan.get_by_id(
self.engine.context, self._db_action.action_plan_id)
if action_plan.state in CANCEL_STATE:
raise exception.ActionPlanCancelled(uuid=action_plan.uuid)
self.do_pre_execute() self.do_pre_execute()
notifications.action.send_execution_notification( notifications.action.send_execution_notification(
self.engine.context, self._db_action, self.engine.context, self._db_action,
fields.NotificationAction.EXECUTION, fields.NotificationAction.EXECUTION,
fields.NotificationPhase.START) fields.NotificationPhase.START)
except exception.ActionPlanCancelled as e:
LOG.exception(e)
raise
except Exception as e: except Exception as e:
LOG.exception(e) LOG.exception(e)
self.engine.notify(self._db_action, objects.action.State.FAILED) self.engine.notify(self._db_action, objects.action.State.FAILED)
@@ -142,22 +167,59 @@ class BaseTaskFlowActionContainer(flow_task.Task):
priority=fields.NotificationPriority.ERROR) priority=fields.NotificationPriority.ERROR)
def execute(self, *args, **kwargs): def execute(self, *args, **kwargs):
def _do_execute_action(*args, **kwargs):
try:
self.do_execute(*args, **kwargs)
notifications.action.send_execution_notification(
self.engine.context, self._db_action,
fields.NotificationAction.EXECUTION,
fields.NotificationPhase.END)
except Exception as e:
LOG.exception(e)
LOG.error('The workflow engine has failed'
'to execute the action: %s', self.name)
self.engine.notify(self._db_action,
objects.action.State.FAILED)
notifications.action.send_execution_notification(
self.engine.context, self._db_action,
fields.NotificationAction.EXECUTION,
fields.NotificationPhase.ERROR,
priority=fields.NotificationPriority.ERROR)
raise
# NOTE: spawn a new thread for action execution, so that if action plan
# is cancelled workflow engine will not wait to finish action execution
et = eventlet.spawn(_do_execute_action, *args, **kwargs)
# NOTE: check for the state of action plan periodically,so that if
# action is finished or action plan is cancelled we can exit from here.
while True:
action_object = objects.Action.get_by_uuid(
self.engine.context, self._db_action.uuid, eager=True)
action_plan_object = objects.ActionPlan.get_by_id(
self.engine.context, action_object.action_plan_id)
if (action_object.state in [objects.action.State.SUCCEEDED,
objects.action.State.FAILED] or
action_plan_object.state in CANCEL_STATE):
break
time.sleep(1)
try: try:
self.do_execute(*args, **kwargs) # NOTE: kill the action execution thread, if action plan is
notifications.action.send_execution_notification( # cancelled for all other cases wait for the result from action
self.engine.context, self._db_action, # execution thread.
fields.NotificationAction.EXECUTION, # Not all actions support abort operations, kill only those action
fields.NotificationPhase.END) # which support abort operations
abort = self.action.check_abort()
if (action_plan_object.state in CANCEL_STATE and abort):
et.kill()
et.wait()
# NOTE: catch the greenlet exit exception due to thread kill,
# taskflow will call revert for the action,
# we will redirect it to abort.
except eventlet.greenlet.GreenletExit:
raise exception.ActionPlanCancelled(uuid=action_plan_object.uuid)
except Exception as e: except Exception as e:
LOG.exception(e) LOG.exception(e)
LOG.error('The workflow engine has failed '
'to execute the action: %s', self.name)
self.engine.notify(self._db_action, objects.action.State.FAILED)
notifications.action.send_execution_notification(
self.engine.context, self._db_action,
fields.NotificationAction.EXECUTION,
fields.NotificationPhase.ERROR,
priority=fields.NotificationPriority.ERROR)
raise raise
def post_execute(self): def post_execute(self):
@@ -171,3 +233,28 @@ class BaseTaskFlowActionContainer(flow_task.Task):
fields.NotificationAction.EXECUTION, fields.NotificationAction.EXECUTION,
fields.NotificationPhase.ERROR, fields.NotificationPhase.ERROR,
priority=fields.NotificationPriority.ERROR) priority=fields.NotificationPriority.ERROR)
def revert(self, *args, **kwargs):
action_plan = objects.ActionPlan.get_by_id(
self.engine.context, self._db_action.action_plan_id, eager=True)
# NOTE: check if revert cause by cancel action plan or
# some other exception occured during action plan execution
# if due to some other exception keep the flow intact.
if action_plan.state not in CANCEL_STATE:
self.do_revert()
return
action_object = objects.Action.get_by_uuid(
self.engine.context, self._db_action.uuid, eager=True)
if action_object.state == objects.action.State.ONGOING:
action_object.state = objects.action.State.CANCELLING
action_object.save()
self.abort()
elif action_object.state == objects.action.State.PENDING:
action_object.state = objects.action.State.CANCELLED
action_object.save()
else:
pass
def abort(self, *args, **kwargs):
self.do_abort(*args, **kwargs)

View File

@@ -19,6 +19,7 @@ from oslo_concurrency import processutils
from oslo_config import cfg from oslo_config import cfg
from oslo_log import log from oslo_log import log
from taskflow import engines from taskflow import engines
from taskflow import exceptions as tf_exception
from taskflow.patterns import graph_flow as gf from taskflow.patterns import graph_flow as gf
from taskflow import task as flow_task from taskflow import task as flow_task
@@ -90,6 +91,15 @@ class DefaultWorkFlowEngine(base.BaseWorkFlowEngine):
return flow return flow
except exception.ActionPlanCancelled as e:
raise
except tf_exception.WrappedFailure as e:
if e.check("watcher.common.exception.ActionPlanCancelled"):
raise exception.ActionPlanCancelled
else:
raise exception.WorkflowExecutionException(error=e)
except Exception as e: except Exception as e:
raise exception.WorkflowExecutionException(error=e) raise exception.WorkflowExecutionException(error=e)
@@ -108,14 +118,20 @@ class TaskFlowActionContainer(base.BaseTaskFlowActionContainer):
def do_execute(self, *args, **kwargs): def do_execute(self, *args, **kwargs):
LOG.debug("Running action: %s", self.name) LOG.debug("Running action: %s", self.name)
self.action.execute() # NOTE: For result is False, set action state fail
self.engine.notify(self._db_action, objects.action.State.SUCCEEDED) result = self.action.execute()
if result is False:
self.engine.notify(self._db_action,
objects.action.State.FAILED)
else:
self.engine.notify(self._db_action,
objects.action.State.SUCCEEDED)
def do_post_execute(self): def do_post_execute(self):
LOG.debug("Post-condition action: %s", self.name) LOG.debug("Post-condition action: %s", self.name)
self.action.post_condition() self.action.post_condition()
def revert(self, *args, **kwargs): def do_revert(self, *args, **kwargs):
LOG.warning("Revert action: %s", self.name) LOG.warning("Revert action: %s", self.name)
try: try:
# TODO(jed): do we need to update the states in case of failure? # TODO(jed): do we need to update the states in case of failure?
@@ -124,6 +140,21 @@ class TaskFlowActionContainer(base.BaseTaskFlowActionContainer):
LOG.exception(e) LOG.exception(e)
LOG.critical("Oops! We need a disaster recover plan.") LOG.critical("Oops! We need a disaster recover plan.")
def do_abort(self, *args, **kwargs):
LOG.warning("Aborting action: %s", self.name)
try:
result = self.action.abort()
if result:
# Aborted the action.
self.engine.notify(self._db_action,
objects.action.State.CANCELLED)
else:
self.engine.notify(self._db_action,
objects.action.State.SUCCEEDED)
except Exception as e:
self.engine.notify(self._db_action, objects.action.State.FAILED)
LOG.exception(e)
class TaskFlowNop(flow_task.Task): class TaskFlowNop(flow_task.Task):
"""This class is used in case of the workflow have only one Action. """This class is used in case of the workflow have only one Action.

View File

@@ -22,6 +22,7 @@ import sys
from oslo_config import cfg from oslo_config import cfg
from oslo_log import log as logging from oslo_log import log as logging
from watcher.api import scheduling
from watcher.common import service from watcher.common import service
from watcher import conf from watcher import conf
@@ -45,5 +46,8 @@ def main():
LOG.info('serving on %(protocol)s://%(host)s:%(port)s' % LOG.info('serving on %(protocol)s://%(host)s:%(port)s' %
dict(protocol=protocol, host=host, port=port)) dict(protocol=protocol, host=host, port=port))
api_schedule = scheduling.APISchedulingService()
api_schedule.start()
launcher = service.launch(CONF, server, workers=server.workers) launcher = service.launch(CONF, server, workers=server.workers)
launcher.wait() launcher.wait()

View File

@@ -71,7 +71,7 @@ def add_command_parsers(subparsers):
"Optionally, use --revision to specify an alembic revision " "Optionally, use --revision to specify an alembic revision "
"string to upgrade to.") "string to upgrade to.")
parser.set_defaults(func=DBCommand.upgrade) parser.set_defaults(func=DBCommand.upgrade)
parser.add_argument('revision', nargs='?') parser.add_argument('--revision', nargs='?')
parser = subparsers.add_parser( parser = subparsers.add_parser(
'downgrade', 'downgrade',
@@ -79,7 +79,7 @@ def add_command_parsers(subparsers):
"While optional, one should generally use --revision to " "While optional, one should generally use --revision to "
"specify the alembic revision string to downgrade to.") "specify the alembic revision string to downgrade to.")
parser.set_defaults(func=DBCommand.downgrade) parser.set_defaults(func=DBCommand.downgrade)
parser.add_argument('revision', nargs='?') parser.add_argument('--revision', nargs='?')
parser = subparsers.add_parser('stamp') parser = subparsers.add_parser('stamp')
parser.add_argument('revision', nargs='?') parser.add_argument('revision', nargs='?')

View File

@@ -44,10 +44,10 @@ def main():
syncer.sync() syncer.sync()
de_service = watcher_service.Service(manager.DecisionEngineManager) de_service = watcher_service.Service(manager.DecisionEngineManager)
bg_schedulder_service = scheduling.DecisionEngineSchedulingService() bg_scheduler_service = scheduling.DecisionEngineSchedulingService()
# Only 1 process # Only 1 process
launcher = watcher_service.launch(CONF, de_service) launcher = watcher_service.launch(CONF, de_service)
launcher.launch_service(bg_schedulder_service) launcher.launch_service(bg_scheduler_service)
launcher.wait() launcher.wait()

View File

@@ -0,0 +1,79 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
from oslo_log import log
from watcher.common import clients
from watcher.common import exception
LOG = log.getLogger(__name__)
class CinderHelper(object):
def __init__(self, osc=None):
""":param osc: an OpenStackClients instance"""
self.osc = osc if osc else clients.OpenStackClients()
self.cinder = self.osc.cinder()
def get_storage_node_list(self):
return list(self.cinder.services.list(binary='cinder-volume'))
def get_storage_node_by_name(self, name):
"""Get storage node by name(host@backendname)"""
try:
storages = list(filter(lambda storage:
storage.host == name,
self.get_storage_node_list()))
if len(storages) != 1:
raise exception.StorageNodeNotFound(name=name)
return storages[0]
except Exception as exc:
LOG.exception(exc)
raise exception.StorageNodeNotFound(name=name)
def get_storage_pool_list(self):
return self.cinder.pools.list(detailed=True)
def get_storage_pool_by_name(self, name):
"""Get pool by name(host@backend#poolname)"""
try:
pools = list(filter(lambda pool:
pool.name == name,
self.get_storage_pool_list()))
if len(pools) != 1:
raise exception.PoolNotFound(name=name)
return pools[0]
except Exception as exc:
LOG.exception(exc)
raise exception.PoolNotFound(name=name)
def get_volume_list(self):
return self.cinder.volumes.list(search_opts={'all_tenants': True})
def get_volume_type_list(self):
return self.cinder.volume_types.list()
def get_volume_type_by_backendname(self, backendname):
volume_type_list = self.get_volume_type_list()
volume_type = list(filter(
lambda volume_type:
volume_type.extra_specs.get(
'volume_backend_name') == backendname, volume_type_list))
if volume_type:
return volume_type[0].name
else:
return ""

14
watcher/common/clients.py Normal file → Executable file
View File

@@ -14,6 +14,7 @@ from ceilometerclient import client as ceclient
from cinderclient import client as ciclient from cinderclient import client as ciclient
from glanceclient import client as glclient from glanceclient import client as glclient
from gnocchiclient import client as gnclient from gnocchiclient import client as gnclient
from ironicclient import client as irclient
from keystoneauth1 import loading as ka_loading from keystoneauth1 import loading as ka_loading
from keystoneclient import client as keyclient from keystoneclient import client as keyclient
from monascaclient import client as monclient from monascaclient import client as monclient
@@ -45,6 +46,7 @@ class OpenStackClients(object):
self._ceilometer = None self._ceilometer = None
self._monasca = None self._monasca = None
self._neutron = None self._neutron = None
self._ironic = None
def _get_keystone_session(self): def _get_keystone_session(self):
auth = ka_loading.load_auth_from_conf_options(CONF, auth = ka_loading.load_auth_from_conf_options(CONF,
@@ -188,3 +190,15 @@ class OpenStackClients(object):
session=self.session) session=self.session)
self._neutron.format = 'json' self._neutron.format = 'json'
return self._neutron return self._neutron
@exception.wrap_keystone_exception
def ironic(self):
if self._ironic:
return self._ironic
ironicclient_version = self._get_client_option('ironic', 'api_version')
endpoint_type = self._get_client_option('ironic', 'endpoint_type')
self._ironic = irclient.get_client(ironicclient_version,
ironic_url=endpoint_type,
session=self.session)
return self._ironic

View File

@@ -63,6 +63,7 @@ class RequestContext(context.RequestContext):
# oslo_context's RequestContext.to_dict() generates this field, we can # oslo_context's RequestContext.to_dict() generates this field, we can
# safely ignore this as we don't use it. # safely ignore this as we don't use it.
kwargs.pop('user_identity', None) kwargs.pop('user_identity', None)
kwargs.pop('global_request_id', None)
if kwargs: if kwargs:
LOG.warning('Arguments dropped when creating context: %s', LOG.warning('Arguments dropped when creating context: %s',
str(kwargs)) str(kwargs))

View File

@@ -202,6 +202,10 @@ class InvalidUuidOrName(Invalid):
msg_fmt = _("Expected a logical name or uuid but received %(name)s") msg_fmt = _("Expected a logical name or uuid but received %(name)s")
class InvalidIntervalOrCron(Invalid):
msg_fmt = _("Expected an interval or cron syntax but received %(name)s")
class GoalNotFound(ResourceNotFound): class GoalNotFound(ResourceNotFound):
msg_fmt = _("Goal %(goal)s could not be found") msg_fmt = _("Goal %(goal)s could not be found")
@@ -274,6 +278,10 @@ class ActionPlanReferenced(Invalid):
"multiple actions") "multiple actions")
class ActionPlanCancelled(WatcherException):
msg_fmt = _("Action Plan with UUID %(uuid)s is cancelled by user")
class ActionPlanIsOngoing(Conflict): class ActionPlanIsOngoing(Conflict):
msg_fmt = _("Action Plan %(action_plan)s is currently running.") msg_fmt = _("Action Plan %(action_plan)s is currently running.")
@@ -414,6 +422,10 @@ class WildcardCharacterIsUsed(WatcherException):
"wildcard character.") "wildcard character.")
class CronFormatIsInvalid(WatcherException):
msg_fmt = _("Provided cron is invalid: %(message)s")
# Model # Model
class ComputeResourceNotFound(WatcherException): class ComputeResourceNotFound(WatcherException):
@@ -428,6 +440,22 @@ class ComputeNodeNotFound(ComputeResourceNotFound):
msg_fmt = _("The compute node %(name)s could not be found") msg_fmt = _("The compute node %(name)s could not be found")
class StorageResourceNotFound(WatcherException):
msg_fmt = _("The storage resource '%(name)s' could not be found")
class StorageNodeNotFound(StorageResourceNotFound):
msg_fmt = _("The storage node %(name)s could not be found")
class PoolNotFound(StorageResourceNotFound):
msg_fmt = _("The pool %(name)s could not be found")
class VolumeNotFound(StorageResourceNotFound):
msg_fmt = _("The volume '%(name)s' could not be found")
class LoadingError(WatcherException): class LoadingError(WatcherException):
msg_fmt = _("Error loading plugin '%(name)s'") msg_fmt = _("Error loading plugin '%(name)s'")

View File

@@ -85,6 +85,20 @@ class NovaHelper(object):
def find_instance(self, instance_id): def find_instance(self, instance_id):
return self.nova.servers.get(instance_id) return self.nova.servers.get(instance_id)
def confirm_resize(self, instance, previous_status, retry=60):
instance.confirm_resize()
instance = self.nova.servers.get(instance.id)
while instance.status != previous_status and retry:
instance = self.nova.servers.get(instance.id)
retry -= 1
time.sleep(1)
if instance.status == previous_status:
return True
else:
LOG.debug("confirm resize failed for the "
"instance %s" % instance.id)
return False
def wait_for_volume_status(self, volume, status, timeout=60, def wait_for_volume_status(self, volume, status, timeout=60,
poll_interval=1): poll_interval=1):
"""Wait until volume reaches given status. """Wait until volume reaches given status.
@@ -105,8 +119,9 @@ class NovaHelper(object):
% (volume.id, status, timeout)) % (volume.id, status, timeout))
return volume.status == status return volume.status == status
def watcher_non_live_migrate_instance(self, instance_id, node_id, def watcher_non_live_migrate_instance(self, instance_id, dest_hostname,
keep_original_image_name=True): keep_original_image_name=True,
retry=120):
"""This method migrates a given instance """This method migrates a given instance
using an image of this instance and creating a new instance using an image of this instance and creating a new instance
@@ -118,6 +133,9 @@ class NovaHelper(object):
It returns True if the migration was successful, It returns True if the migration was successful,
False otherwise. False otherwise.
if destination hostname not given, this method calls nova api
to migrate the instance.
:param instance_id: the unique id of the instance to migrate. :param instance_id: the unique id of the instance to migrate.
:param keep_original_image_name: flag indicating whether the :param keep_original_image_name: flag indicating whether the
image name from which the original instance was built must be image name from which the original instance was built must be
@@ -125,10 +143,8 @@ class NovaHelper(object):
If this flag is False, a temporary image name is built If this flag is False, a temporary image name is built
""" """
new_image_name = "" new_image_name = ""
LOG.debug( LOG.debug(
"Trying a non-live migrate of instance '%s' " "Trying a non-live migrate of instance '%s' " % instance_id)
"using a temporary image ..." % instance_id)
# Looking for the instance to migrate # Looking for the instance to migrate
instance = self.find_instance(instance_id) instance = self.find_instance(instance_id)
@@ -136,10 +152,38 @@ class NovaHelper(object):
LOG.debug("Instance %s not found !" % instance_id) LOG.debug("Instance %s not found !" % instance_id)
return False return False
else: else:
# NOTE: If destination node is None call Nova API to migrate
# instance
host_name = getattr(instance, "OS-EXT-SRV-ATTR:host") host_name = getattr(instance, "OS-EXT-SRV-ATTR:host")
LOG.debug( LOG.debug(
"Instance %s found on host '%s'." % (instance_id, host_name)) "Instance %s found on host '%s'." % (instance_id, host_name))
if dest_hostname is None:
previous_status = getattr(instance, 'status')
instance.migrate()
instance = self.nova.servers.get(instance_id)
while (getattr(instance, 'status') not in
["VERIFY_RESIZE", "ERROR"] and retry):
instance = self.nova.servers.get(instance.id)
time.sleep(2)
retry -= 1
new_hostname = getattr(instance, 'OS-EXT-SRV-ATTR:host')
if (host_name != new_hostname and
instance.status == 'VERIFY_RESIZE'):
if not self.confirm_resize(instance, previous_status):
return False
LOG.debug(
"cold migration succeeded : "
"instance %s is now on host '%s'." % (
instance_id, new_hostname))
return True
else:
LOG.debug(
"cold migration for instance %s failed" % instance_id)
return False
if not keep_original_image_name: if not keep_original_image_name:
# randrange gives you an integral value # randrange gives you an integral value
irand = random.randint(0, 1000) irand = random.randint(0, 1000)
@@ -268,7 +312,7 @@ class NovaHelper(object):
# We create the new instance from # We create the new instance from
# the intermediate image of the original instance # the intermediate image of the original instance
new_instance = self. \ new_instance = self. \
create_instance(node_id, create_instance(dest_hostname,
instance_name, instance_name,
image_uuid, image_uuid,
flavor_name, flavor_name,
@@ -389,15 +433,15 @@ class NovaHelper(object):
False otherwise. False otherwise.
:param instance_id: the unique id of the instance to migrate. :param instance_id: the unique id of the instance to migrate.
:param dest_hostname: the name of the destination compute node. :param dest_hostname: the name of the destination compute node, if
destination_node is None, nova scheduler choose
the destination host
:param block_migration: No shared storage is required. :param block_migration: No shared storage is required.
""" """
LOG.debug("Trying a live migrate of instance %s to host '%s'" % ( LOG.debug("Trying to live migrate instance %s " % (instance_id))
instance_id, dest_hostname))
# Looking for the instance to migrate # Looking for the instance to migrate
instance = self.find_instance(instance_id) instance = self.find_instance(instance_id)
if not instance: if not instance:
LOG.debug("Instance not found: %s" % instance_id) LOG.debug("Instance not found: %s" % instance_id)
return False return False
@@ -409,6 +453,29 @@ class NovaHelper(object):
instance.live_migrate(host=dest_hostname, instance.live_migrate(host=dest_hostname,
block_migration=block_migration, block_migration=block_migration,
disk_over_commit=True) disk_over_commit=True)
instance = self.nova.servers.get(instance_id)
# NOTE: If destination host is not specified for live migration
# let nova scheduler choose the destination host.
if dest_hostname is None:
while (instance.status not in ['ACTIVE', 'ERROR'] and retry):
instance = self.nova.servers.get(instance.id)
LOG.debug(
'Waiting the migration of {0}'.format(instance.id))
time.sleep(1)
retry -= 1
new_hostname = getattr(instance, 'OS-EXT-SRV-ATTR:host')
if host_name != new_hostname and instance.status == 'ACTIVE':
LOG.debug(
"Live migration succeeded : "
"instance %s is now on host '%s'." % (
instance_id, new_hostname))
return True
else:
return False
while getattr(instance, while getattr(instance,
'OS-EXT-SRV-ATTR:host') != dest_hostname \ 'OS-EXT-SRV-ATTR:host') != dest_hostname \
and retry: and retry:
@@ -432,6 +499,43 @@ class NovaHelper(object):
return True return True
def abort_live_migrate(self, instance_id, source, destination, retry=240):
LOG.debug("Aborting live migration of instance %s" % instance_id)
migration = self.get_running_migration(instance_id)
if migration:
migration_id = getattr(migration[0], "id")
try:
self.nova.server_migrations.live_migration_abort(
server=instance_id, migration=migration_id)
except exception as e:
# Note: Does not return from here, as abort request can't be
# accepted but migration still going on.
LOG.exception(e)
else:
LOG.debug(
"No running migrations found for instance %s" % instance_id)
while retry:
instance = self.nova.servers.get(instance_id)
if (getattr(instance, 'OS-EXT-STS:task_state') is None and
getattr(instance, 'status') in ['ACTIVE', 'ERROR']):
break
time.sleep(2)
retry -= 1
instance_host = getattr(instance, 'OS-EXT-SRV-ATTR:host')
instance_status = getattr(instance, 'status')
# Abort live migration successfull, action is cancelled
if instance_host == source and instance_status == 'ACTIVE':
return True
# Nova Unable to abort live migration, action is succeded
elif instance_host == destination and instance_status == 'ACTIVE':
return False
else:
raise Exception("Live migration execution and abort both failed "
"for the instance %s" % instance_id)
def enable_service_nova_compute(self, hostname): def enable_service_nova_compute(self, hostname):
if self.nova.services.enable(host=hostname, if self.nova.services.enable(host=hostname,
binary='nova-compute'). \ binary='nova-compute'). \
@@ -573,6 +677,9 @@ class NovaHelper(object):
if not instance: if not instance:
LOG.debug("Instance not found: %s" % instance_id) LOG.debug("Instance not found: %s" % instance_id)
return False return False
elif getattr(instance, 'OS-EXT-STS:vm_state') == "stopped":
LOG.debug("Instance has been stopped: %s" % instance_id)
return True
else: else:
self.nova.servers.stop(instance_id) self.nova.servers.stop(instance_id)
@@ -754,3 +861,6 @@ class NovaHelper(object):
instance.flavor[attr] = default instance.flavor[attr] = default
continue continue
instance.flavor[attr] = getattr(flavor, attr, default) instance.flavor[attr] = getattr(flavor, attr, default)
def get_running_migration(self, instance_id):
return self.nova.server_migrations.list(server=instance_id)

View File

@@ -17,6 +17,8 @@ from oslo_config import cfg
from oslo_log import log from oslo_log import log
import oslo_messaging as messaging import oslo_messaging as messaging
from oslo_messaging.rpc import dispatcher
from watcher.common import context as watcher_context from watcher.common import context as watcher_context
from watcher.common import exception from watcher.common import exception
@@ -51,11 +53,10 @@ JsonPayloadSerializer = messaging.JsonPayloadSerializer
def init(conf): def init(conf):
global TRANSPORT, NOTIFICATION_TRANSPORT, NOTIFIER global TRANSPORT, NOTIFICATION_TRANSPORT, NOTIFIER
exmods = get_allowed_exmods() exmods = get_allowed_exmods()
TRANSPORT = messaging.get_transport(conf, TRANSPORT = messaging.get_rpc_transport(
allowed_remote_exmods=exmods) conf, allowed_remote_exmods=exmods)
NOTIFICATION_TRANSPORT = messaging.get_notification_transport( NOTIFICATION_TRANSPORT = messaging.get_notification_transport(
conf, conf, allowed_remote_exmods=exmods)
allowed_remote_exmods=exmods)
serializer = RequestContextSerializer(JsonPayloadSerializer()) serializer = RequestContextSerializer(JsonPayloadSerializer())
if not conf.notification_level: if not conf.notification_level:
@@ -128,12 +129,14 @@ def get_client(target, version_cap=None, serializer=None):
def get_server(target, endpoints, serializer=None): def get_server(target, endpoints, serializer=None):
assert TRANSPORT is not None assert TRANSPORT is not None
access_policy = dispatcher.DefaultRPCAccessPolicy
serializer = RequestContextSerializer(serializer) serializer = RequestContextSerializer(serializer)
return messaging.get_rpc_server(TRANSPORT, return messaging.get_rpc_server(TRANSPORT,
target, target,
endpoints, endpoints,
executor='eventlet', executor='eventlet',
serializer=serializer) serializer=serializer,
access_policy=access_policy)
def get_notifier(publisher_id): def get_notifier(publisher_id):

View File

@@ -28,6 +28,8 @@ from oslo_reports import opts as gmr_opts
from oslo_service import service from oslo_service import service
from oslo_service import wsgi from oslo_service import wsgi
from oslo_messaging.rpc import dispatcher
from watcher._i18n import _ from watcher._i18n import _
from watcher.api import app from watcher.api import app
from watcher.common import config from watcher.common import config
@@ -110,16 +112,19 @@ class WSGIService(service.ServiceBase):
class ServiceHeartbeat(scheduling.BackgroundSchedulerService): class ServiceHeartbeat(scheduling.BackgroundSchedulerService):
service_name = None
def __init__(self, gconfig=None, service_name=None, **kwargs): def __init__(self, gconfig=None, service_name=None, **kwargs):
gconfig = None or {} gconfig = None or {}
super(ServiceHeartbeat, self).__init__(gconfig, **kwargs) super(ServiceHeartbeat, self).__init__(gconfig, **kwargs)
self.service_name = service_name ServiceHeartbeat.service_name = service_name
self.context = context.make_context() self.context = context.make_context()
self.send_beat()
def send_beat(self): def send_beat(self):
host = CONF.host host = CONF.host
watcher_list = objects.Service.list( watcher_list = objects.Service.list(
self.context, filters={'name': self.service_name, self.context, filters={'name': ServiceHeartbeat.service_name,
'host': host}) 'host': host})
if watcher_list: if watcher_list:
watcher_service = watcher_list[0] watcher_service = watcher_list[0]
@@ -127,7 +132,7 @@ class ServiceHeartbeat(scheduling.BackgroundSchedulerService):
watcher_service.save() watcher_service.save()
else: else:
watcher_service = objects.Service(self.context) watcher_service = objects.Service(self.context)
watcher_service.name = self.service_name watcher_service.name = ServiceHeartbeat.service_name
watcher_service.host = host watcher_service.host = host
watcher_service.create() watcher_service.create()
@@ -135,6 +140,10 @@ class ServiceHeartbeat(scheduling.BackgroundSchedulerService):
self.add_job(self.send_beat, 'interval', seconds=60, self.add_job(self.send_beat, 'interval', seconds=60,
next_run_time=datetime.datetime.now()) next_run_time=datetime.datetime.now())
@classmethod
def get_service_name(cls):
return CONF.host, cls.service_name
def start(self): def start(self):
"""Start service.""" """Start service."""
self.add_heartbeat_job() self.add_heartbeat_job()
@@ -168,6 +177,13 @@ class Service(service.ServiceBase):
self.conductor_topic = self.manager.conductor_topic self.conductor_topic = self.manager.conductor_topic
self.notification_topics = self.manager.notification_topics self.notification_topics = self.manager.notification_topics
self.heartbeat = None
self.service_name = self.manager.service_name
if self.service_name:
self.heartbeat = ServiceHeartbeat(
service_name=self.manager.service_name)
self.conductor_endpoints = [ self.conductor_endpoints = [
ep(self) for ep in self.manager.conductor_endpoints ep(self) for ep in self.manager.conductor_endpoints
] ]
@@ -183,8 +199,6 @@ class Service(service.ServiceBase):
self.conductor_topic_handler = None self.conductor_topic_handler = None
self.notification_handler = None self.notification_handler = None
self.heartbeat = None
if self.conductor_topic and self.conductor_endpoints: if self.conductor_topic and self.conductor_endpoints:
self.conductor_topic_handler = self.build_topic_handler( self.conductor_topic_handler = self.build_topic_handler(
self.conductor_topic, self.conductor_endpoints) self.conductor_topic, self.conductor_endpoints)
@@ -192,15 +206,11 @@ class Service(service.ServiceBase):
self.notification_handler = self.build_notification_handler( self.notification_handler = self.build_notification_handler(
self.notification_topics, self.notification_endpoints self.notification_topics, self.notification_endpoints
) )
self.service_name = self.manager.service_name
if self.service_name:
self.heartbeat = ServiceHeartbeat(
service_name=self.manager.service_name)
@property @property
def transport(self): def transport(self):
if self._transport is None: if self._transport is None:
self._transport = om.get_transport(CONF) self._transport = om.get_rpc_transport(CONF)
return self._transport return self._transport
@property @property
@@ -225,6 +235,7 @@ class Service(service.ServiceBase):
self.conductor_client = c self.conductor_client = c
def build_topic_handler(self, topic_name, endpoints=()): def build_topic_handler(self, topic_name, endpoints=()):
access_policy = dispatcher.DefaultRPCAccessPolicy
serializer = rpc.RequestContextSerializer(rpc.JsonPayloadSerializer()) serializer = rpc.RequestContextSerializer(rpc.JsonPayloadSerializer())
target = om.Target( target = om.Target(
topic=topic_name, topic=topic_name,
@@ -234,7 +245,8 @@ class Service(service.ServiceBase):
) )
return om.get_rpc_server( return om.get_rpc_server(
self.transport, target, endpoints, self.transport, target, endpoints,
executor='eventlet', serializer=serializer) executor='eventlet', serializer=serializer,
access_policy=access_policy)
def build_notification_handler(self, topic_names, endpoints=()): def build_notification_handler(self, topic_names, endpoints=()):
serializer = rpc.RequestContextSerializer(rpc.JsonPayloadSerializer()) serializer = rpc.RequestContextSerializer(rpc.JsonPayloadSerializer())

View File

@@ -16,8 +16,11 @@
"""Utilities and helper functions.""" """Utilities and helper functions."""
import datetime
import re import re
from croniter import croniter
from jsonschema import validators from jsonschema import validators
from oslo_log import log as logging from oslo_log import log as logging
from oslo_utils import strutils from oslo_utils import strutils
@@ -63,6 +66,15 @@ is_int_like = strutils.is_int_like
strtime = timeutils.strtime strtime = timeutils.strtime
def is_cron_like(value):
"""Return True is submitted value is like cron syntax"""
try:
croniter(value, datetime.datetime.now())
except Exception as e:
raise exception.CronFormatIsInvalid(message=str(e))
return True
def safe_rstrip(value, chars=None): def safe_rstrip(value, chars=None):
"""Removes trailing characters from a string if that does not make it empty """Removes trailing characters from a string if that does not make it empty
@@ -111,7 +123,7 @@ def extend_with_default(validator_class):
def set_defaults(validator, properties, instance, schema): def set_defaults(validator, properties, instance, schema):
for prop, subschema in properties.items(): for prop, subschema in properties.items():
if "default" in subschema: if "default" in subschema and instance is not None:
instance.setdefault(prop, subschema["default"]) instance.setdefault(prop, subschema["default"])
for error in validate_properties( for error in validate_properties(
@@ -128,6 +140,9 @@ def extend_with_strict_schema(validator_class):
validate_properties = validator_class.VALIDATORS["properties"] validate_properties = validator_class.VALIDATORS["properties"]
def strict_schema(validator, properties, instance, schema): def strict_schema(validator, properties, instance, schema):
if instance is None:
return
for para in instance.keys(): for para in instance.keys():
if para not in properties.keys(): if para not in properties.keys():
raise exception.AuditParameterNotAllowed(parameter=para) raise exception.AuditParameterNotAllowed(parameter=para)

2
watcher/conf/__init__.py Normal file → Executable file
View File

@@ -29,6 +29,7 @@ from watcher.conf import decision_engine
from watcher.conf import exception from watcher.conf import exception
from watcher.conf import glance_client from watcher.conf import glance_client
from watcher.conf import gnocchi_client from watcher.conf import gnocchi_client
from watcher.conf import ironic_client
from watcher.conf import monasca_client from watcher.conf import monasca_client
from watcher.conf import neutron_client from watcher.conf import neutron_client
from watcher.conf import nova_client from watcher.conf import nova_client
@@ -56,3 +57,4 @@ cinder_client.register_opts(CONF)
ceilometer_client.register_opts(CONF) ceilometer_client.register_opts(CONF)
neutron_client.register_opts(CONF) neutron_client.register_opts(CONF)
clients_auth.register_opts(CONF) clients_auth.register_opts(CONF)
ironic_client.register_opts(CONF)

View File

@@ -23,13 +23,13 @@ cinder_client = cfg.OptGroup(name='cinder_client',
CINDER_CLIENT_OPTS = [ CINDER_CLIENT_OPTS = [
cfg.StrOpt('api_version', cfg.StrOpt('api_version',
default='2', default='3',
help='Version of Cinder API to use in cinderclient.'), help='Version of Cinder API to use in cinderclient.'),
cfg.StrOpt('endpoint_type', cfg.StrOpt('endpoint_type',
default='internalURL', default='publicURL',
help='Type of endpoint to use in cinderclient.' help='Type of endpoint to use in cinderclient.'
'Supported values: internalURL, publicURL, adminURL' 'Supported values: internalURL, publicURL, adminURL'
'The default is internalURL.')] 'The default is publicURL.')]
def register_opts(conf): def register_opts(conf):

View File

@@ -49,7 +49,7 @@ WATCHER_DECISION_ENGINE_OPTS = [
'-whose number of hours has been offset by this value-' '-whose number of hours has been offset by this value-'
' is older that the current time.'), ' is older that the current time.'),
cfg.IntOpt('check_periodic_interval', cfg.IntOpt('check_periodic_interval',
default=30*60, default=30 * 60,
help='Interval (in seconds) for checking action plan expiry.') help='Interval (in seconds) for checking action plan expiry.')
] ]

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