Compare commits

..

67 Commits
1.5.0 ... 1.6.0

Author SHA1 Message Date
Zuul
c2e16bfa96 Merge "listen to 'compute.instance.resize.confirm.end' event" 2017-12-08 03:11:54 +00:00
Zuul
13644429b7 Merge "Fix migrate action with migration_type 'cold'" 2017-12-07 13:15:28 +00:00
Zuul
b8cc506fbe Merge "Updated from global requirements" 2017-12-07 05:06:45 +00:00
licanwei
c91f6479f0 Server with PAUSE status can also live-migrate
Closes-Bug: #1728477
Change-Id: I39a2672df842b62177fd0c4dbec7b92b398cfb96
2017-12-06 02:02:44 +00:00
Hidekazu Nakamura
92572c5dec Fix migrate action with migration_type 'cold'
Migration action with migration_type 'cold' does not work.
This patch fixes nova_helper to follow Pike release python-novaclient.

Change-Id: I0637cefad68cd4b7077a5d3e6b5db5562e7f2917
Closes-Bug: #1736624
2017-12-06 09:39:30 +09:00
OpenStack Proposal Bot
a8f08065fd Updated from global requirements
Change-Id: I2171946082a8a9daf7e5edc20bfe0c8cb084768f
2017-12-05 16:58:02 +00:00
Zuul
0745d904fc Merge "Notifications Changes Multiple Global Efficacy" 2017-12-01 09:26:17 +00:00
Zuul
bc4a58d2d7 Merge "Make gnocchi as default datasource" 2017-12-01 09:08:57 +00:00
Alexander Chadin
f14795d29f Make gnocchi as default datasource
This patch set follows deprecation steps
and changes default datasource to gnocchi

Closes-Bug: #1735180
Change-Id: I3b7ec47af7707359373a424a6469d28cbf9ce177
2017-12-01 08:21:44 +00:00
Zuul
e0104074b6 Merge "Updated from global requirements" 2017-11-30 14:04:55 +00:00
Zuul
2993dea376 Merge "extend-node-status" 2017-11-29 13:35:54 +00:00
Zuul
17b6019ea9 Merge "Fix Bug Unable to get scoped data model" 2017-11-29 10:06:17 +00:00
OpenStack Proposal Bot
5969e5b52a Updated from global requirements
Change-Id: Idc1a12df6d888880ff7858cff18cc1972920204d
2017-11-29 09:26:19 +00:00
aditi
e55f3793b6 Fix Bug Unable to get scoped data model
This patch removes 'audit_scope' from __init__ of
BaseClusterDataModelCollector class, as it is a singleton class
and cannot be instantiate more than once.

A new method is defined in BaseClusterDataModelCollector in place
of property audit_scope_handler, which takes audit_scope as
argument.

Change-Id: I0664c151d71a711c118d43c180d8b0760b1c81fa
Closes-Bug: #1732849
2017-11-28 10:24:15 +00:00
suzhengwei
901c598dd7 listen to 'compute.instance.resize.confirm.end' event
In one integrated cloud env, there would be many solutions, which would
make the compute resource strongly relocated. Watcher should listen to
all the notifications which represent the compute resource changes, to
update compute CDM. If not, the compute CDM will be stale, Watcher
couldn't work steadily and harmoniously.

Change-Id: I57173f0cce0717aa36c5ff758d972d38013e3ef8
Implements:blueprint listen-all-necessary-notifications
2017-11-27 17:20:49 +08:00
aditi
e41a90d7ad Notifications Changes Multiple Global Efficacy
This patch adds notfication changes after changing the type
of global_efficacy in ActionPlan objects.

Change-Id: I1116cf1d19e4b19e19789f943a631d0400871766
Partially-Implements: blueprint multiple-global-efficacy-indicator
2017-11-27 14:47:02 +05:30
Zuul
051b4fcd06 Merge "Remove setting of version/release from releasenotes" 2017-11-22 12:15:57 +00:00
Zuul
cd045400ed Merge "Can't cancell CONTINUOUS audit" 2017-11-22 06:57:50 +00:00
Zuul
2db668af30 Merge "Add app.wsgi to target of pep8" 2017-11-20 08:35:00 +00:00
Zuul
39b1fcf07f Merge "[Doc] Fix ubuntu version in devstack installation" 2017-11-20 01:09:46 +00:00
chao liu
94babf61da Add app.wsgi to target of pep8
The app.wsgi file should be adjusted to meet pep8 standards and
apply pep8 check to app.wsgi automatically.

Change-Id: I34ec9ce56a329ede76d3d69f6e973d66350d85d1
2017-11-18 09:41:37 +00:00
Hidekazu Nakamura
a5fba7ce28 [Doc] Fix ubuntu version in devstack installation
This patch updates ubuntu version from 14.04 to 16.04
in devstack installation.

Change-Id: Icf096dd8f3fc366567f9cde329987c4f9f02ad48
2017-11-17 13:25:34 +09:00
aditi
24e01b6c98 Remove setting of version/release from releasenotes
Release notes are version independent, so remove version/release
values.
see the discussion [1] and patch [2]
[1] http://lists.openstack.org/pipermail/openstack-dev/2017-November/124480.html
[2] https://review.openstack.org/#/c/520748/

Change-Id: I2c76dc467e1b722531ba71fb589594ae9fcc88d3
2017-11-17 11:33:20 +09:00
OpenStack Proposal Bot
4007f93aac Updated from global requirements
Change-Id: I9be5c9b8b858f5d5d555da5c02337885ecfa901d
2017-11-16 11:31:33 +00:00
OpenStack Proposal Bot
77c9f88fc4 Updated from global requirements
Change-Id: Iccd6926b31e04e4a24fbd6bf68692bd840c66f7f
2017-11-13 22:00:07 +00:00
Zuul
e9b7f067c5 Merge "Migrate to Zuul v3" 2017-11-10 08:27:28 +00:00
Zuul
f8aa02c4a7 Merge "Multiple global efficacy" 2017-11-10 08:21:26 +00:00
Alexander Chadin
3595108e49 Migrate to Zuul v3
This patch does step 1 in the docs: Move Legacy Jobs to Projects.

Partial-Implements: blueprint migrate-to-zuulv3
Change-Id: I0c0713260c2dfa2ba64a5746c342db01c1bc3d75
2017-11-09 11:51:13 +03:00
Hidekazu Nakamura
d536ed248b Fix test runner config issues with os-testr 1.0.0
The os-testr 1.0.0 release had a couple of required config changes due
to it's internal usage of stestr. This commit fixes those by adding a
.stestr.conf (to remove a warning) sets the fixture env variables in
the tox.ini instead of being hidden in .testr.conf and removing the
quotes around posargs to make passing args to ostestr actually work.
Also to keep the expected dev experience stestr is used directly for
places where ostestr was called directly.

Change-Id: I785b0dc1720328b119f0978aa573ad0b89e54c75
Closes-Bug: #1731155
2017-11-09 17:31:39 +09:00
aditi
71730c0eaf Multiple global efficacy
This patch update the global efficacy for server consolidation
strategy, test cases and general format.

Change-Id: I62af1e4be415998669f938b3d587c1ccf4293419
Implements: blueprint multiple-global-efficacy-indicator
2017-11-09 14:51:17 +09:00
Vu Cong Tuan
1b4c5dfc8b Do not use “-y” for package install
According to "code conventions" [1], do not use "-y" option.
Instead, use apt-get install package, yum install package,
or zypper install package.

[1] https://docs.openstack.org/doc-contrib-guide/writing-style/
code-conventions.html

Change-Id: I174c8a0c653e322bbf951d72445b1a43251baa7e
2017-11-08 16:13:30 +07:00
Zuul
4179cfd036 Merge "check task_state in the live_migrate_instance" 2017-11-08 01:50:38 +00:00
Zuul
49550db566 Merge "Change HTTP to HTTPS" 2017-11-08 01:43:26 +00:00
Zuul
e0eba0ee7b Merge "Update doc and add release note about cdm-scoping" 2017-11-07 02:36:38 +00:00
licanwei
165853ee2c check task_state in the live_migrate_instance
If server migration fails, the while loop will not break
until retry to 0,
we can check the task_state to avoid this situation.

Closes-Bug: #1728476
Change-Id: I07e1048eb736263a261456ee78c96fee9db13cb5
2017-11-05 20:04:45 -08:00
XieYingYun
0a7152fa55 Change HTTP to HTTPS
Use https instead of http to ensure the safety without containing our
account/password information

Change-Id: I2e4f120f1d57c6c666d65a9a279eddefd112bf23
2017-11-05 20:20:46 -07:00
OpenStack Proposal Bot
6c29df11ca Updated from global requirements
Change-Id: I691bd81b1b9933211faa336b1ac49aa5a1a7bf17
2017-11-06 02:07:12 +00:00
licanwei
55bd0fd038 Can't cancell CONTINUOUS audit
If scheduler uses db job store, the self.scheduler is None
beacuse execute_audit is class method.
We get [] from self.scheduler.get_jobs()
and can't remove the cancelled audit.

Change-Id: I1768c847465bbefbc9794281328edb07fea83b03
Closes-Bug: #1726345
2017-11-02 21:05:41 -07:00
suzhengwei
907cc2df16 add name for audit, update audit notifications
upgrade notifications

Change-Id: Ia8b7f36a8a2965bc8ec2243a10556592926297b2
Implements:blueprint add-name-for-audit
2017-11-01 10:39:43 +08:00
Hidekazu Nakamura
8dcac1597b Update doc and add release note about cdm-scoping
This patch updates document and add release note for cdm-scoping.

Change-Id: I8e9099aa5caf6bf17ea912e7b1a600af3b6d519d
Partially-Implements: blueprint cdm-scoping
2017-10-31 10:01:52 +09:00
Zuul
a3be1587e3 Merge "Update the useful links for Rally job" 2017-10-30 03:13:09 +00:00
Zuul
aa72f984e4 Merge "add name for audit, changes for watcher api/db" 2017-10-30 02:11:26 +00:00
caoyuan
bcd2040025 Update the useful links for Rally job
Change-Id: I1d6463701f9e3d99c261e8738ba0291c32632947
2017-10-30 00:32:50 +00:00
Zuul
f2c9dc9c32 Merge "update API ref doc for ScoringEngine" 2017-10-27 03:33:55 +00:00
Zuul
6c94c235fc Merge "Fix the strategy path of outlet_temp_control.py" 2017-10-26 02:30:20 +00:00
Zuul
7ed45e3ef1 Merge "Add cdm-scoping" 2017-10-25 13:52:10 +00:00
aditi
8722951022 update API ref doc for ScoringEngine
Change-Id: Icdfff4659d280a30768aebc0e8f161a699ffd793
Closes-Bug: #1726646
2017-10-24 14:16:19 +09:00
caoyuan
568d4e831c Fix the strategy path of outlet_temp_control.py
1. fix the path of outlet_temp_control.py
2. remove the unnecessary noqa

Change-Id: Ieb33776baeaa936faf940d9886ffed8ec30e91b3
2017-10-24 01:29:17 +00:00
Zuul
deefc857ba Merge "Optimize the link address" 2017-10-24 00:25:52 +00:00
Zuul
d33736e7f0 Merge "Imported Translations from Zanata" 2017-10-24 00:21:44 +00:00
Zuul
1b1779cc49 Merge "Fix a typo" 2017-10-24 00:21:43 +00:00
Zuul
d727bc3076 Merge "Unify the oslo_log import usage" 2017-10-24 00:21:43 +00:00
Zuul
875b7e1ca3 Merge "Optimise indentation for db client" 2017-10-24 00:21:42 +00:00
Zuul
c7f8755f9c Merge "Correct the schema format" 2017-10-24 00:20:55 +00:00
zhengwei6082
0472715e0c Optimize the link address
Use https instead of http to ensure the safety without containing our
account/password information

Change-Id: I16bdf6067fba4ea841071100d68edae7750946de
2017-10-23 07:03:41 +00:00
OpenStack Proposal Bot
2482e82548 Imported Translations from Zanata
For more information about this automatic import see:
https://docs.openstack.org/i18n/latest/reviewing-translation-import.html

Change-Id: I73cd252d637f890ed4ba1ea9abe06d5c47917a86
2017-10-23 06:17:23 +00:00
Zuul
e9c420467e Merge "Optimize check_migrated in cinder_helper.py" 2017-10-23 03:53:17 +00:00
Zuul
2d5db7082b Merge "Optimize live_migrate_instance" 2017-10-23 03:53:17 +00:00
caoyuan
4a3a50435a Fix a typo
Update the "peformance" to "performance"

Change-Id: Ief94893db2a3d6d83d865cfe2aa45f524309ff8d
2017-10-21 14:54:16 +08:00
caoyuan
05b57fee7a Unify the oslo_log import usage
In watcher project, some py file use "from oslo_log import log as logging"
for import, but more use "from oslo_log import log"
like https://github.com/openstack/watcher/blob/master/watcher/decision_engine/strategy/strategies/dummy_strategy.py#L19
this patch to unify it.

Change-Id: I9c667bb202bebc7942d1af5ce438375285c2e2ba
2017-10-21 06:32:59 +00:00
caoyuan
3729e39552 Optimise indentation for db client
Optimise indentation for db client, just like the others:
https://github.com/openstack/watcher/blob/master/watcher/conf/nova_client.py#L22

Change-Id: Icfbd4f26ce048fe3c3db430d95795fb0e58b7161
2017-10-21 13:55:47 +08:00
caoyuan
91911c8284 Correct the schema format
refer to https://github.com/openstack/watcher/blob/master/watcher/applier/actions/migration.py#L46

Change-Id: I2da8585ce978034fc9a79c2a0cf7bfb6eed487e2
2017-10-21 13:03:01 +08:00
suzhengwei
d7d56cbd79 add name for audit, changes for watcher api/db
Change-Id: Ibe04f5375d741d15999fde6faf767f15311c6351
Implements:blueprint add-name-for-audit
2017-10-20 14:17:20 +08:00
licanwei
d722b62b97 Optimize check_migrated in cinder_helper.py
There are more than one 'migrating' status in the volume migration.
Others include starting, completing and so on.
So we should check the final status 'success' and 'error'.

Change-Id: I8ee9330aa32ec4516f2bf9e046ea68a72bc8a53d
2017-10-16 00:55:55 -07:00
licanwei
bf713ac7e1 Optimize live_migrate_instance
https://github.com/openstack/python-novaclient/blob/
master/novaclient/v2/servers.py#L468
From nova api version 2.25(Mitaka release), the default value of
block_migration is None which is mapped to 'auto'.
So we can remove 'block_migration' from live_migrate_instance.

Change-Id: I75a581d3146824b1146e2acf5a7fbe4f8368a4e8
2017-10-15 20:22:13 -07:00
Hidekazu Nakamura
51b3a15c90 Add cdm-scoping
This patch implements cdm-scoping.
Scoping yaml file is the following:

- compute:
  - availability_zones:
    - name: nova
  - host_aggregates:
    - id: 1
    - name: agg
  - exclude:
    - compute_nodes:
      - name: w012

Change-Id: I44e00218e90c5b08b397b2a9d7fed7c195f6dc55
Partially-Implements: blueprint cdm-scoping
2017-09-27 17:39:24 +09:00
suzhengwei
74bc31e562 extend-node-status
add 'disabled_reason' filed into 'ComputeNode' resource, to distinguish
which nodes are disabled by Watcher and which are not by Watcher.

Implements:blueprint extend-node-status

Change-Id: I7175f14870834a4582e45309529d7e8d9fbb2e6f
2017-09-18 14:27:58 +08:00
119 changed files with 1391 additions and 488 deletions

1
.gitignore vendored
View File

@@ -24,6 +24,7 @@ pip-log.txt
.coverage* .coverage*
.tox .tox
nosetests.xml nosetests.xml
.stestr/
.testrepository .testrepository
.venv .venv
.idea .idea

4
.stestr.conf Normal file
View File

@@ -0,0 +1,4 @@
[DEFAULT]
test_path=${OS_TEST_PATH:-./watcher/tests}
top_dir=./

39
.zuul.yaml Normal file
View File

@@ -0,0 +1,39 @@
- project:
name: openstack/watcher
check:
jobs:
- watcher-tempest-multinode
- legacy-rally-dsvm-watcher-rally
- job:
name: watcher-tempest-base-multinode
parent: legacy-dsvm-base-multinode
run: playbooks/legacy/watcher-tempest-base-multinode/run.yaml
post-run: playbooks/legacy/watcher-tempest-base-multinode/post.yaml
timeout: 4200
required-projects:
- openstack-infra/devstack-gate
- openstack/python-openstackclient
- openstack/python-watcherclient
- openstack/watcher
- openstack/watcher-tempest-plugin
nodeset: legacy-ubuntu-xenial-2-node
- job:
name: watcher-tempest-multinode
parent: watcher-tempest-base-multinode
voting: false
- job:
# This job is used by python-watcherclient repo
name: watcherclient-tempest-functional
parent: legacy-dsvm-base
run: playbooks/legacy/watcherclient-tempest-functional/run.yaml
post-run: playbooks/legacy/watcherclient-tempest-functional/post.yaml
timeout: 4200
required-projects:
- openstack-dev/devstack
- openstack-infra/devstack-gate
- openstack/python-openstackclient
- openstack/python-watcherclient
- openstack/watcher

View File

@@ -20,7 +20,7 @@
"watcher_object.name": "TerseActionPlanPayload", "watcher_object.name": "TerseActionPlanPayload",
"watcher_object.data": { "watcher_object.data": {
"uuid": "76be87bd-3422-43f9-93a0-e85a577e3061", "uuid": "76be87bd-3422-43f9-93a0-e85a577e3061",
"global_efficacy": {}, "global_efficacy": [],
"created_at": "2016-10-18T09:52:05Z", "created_at": "2016-10-18T09:52:05Z",
"updated_at": null, "updated_at": null,
"state": "CANCELLING", "state": "CANCELLING",

View File

@@ -30,7 +30,7 @@
"watcher_object.name": "TerseActionPlanPayload", "watcher_object.name": "TerseActionPlanPayload",
"watcher_object.data": { "watcher_object.data": {
"uuid": "76be87bd-3422-43f9-93a0-e85a577e3061", "uuid": "76be87bd-3422-43f9-93a0-e85a577e3061",
"global_efficacy": {}, "global_efficacy": [],
"created_at": "2016-10-18T09:52:05Z", "created_at": "2016-10-18T09:52:05Z",
"updated_at": null, "updated_at": null,
"state": "CANCELLING", "state": "CANCELLING",

View File

@@ -20,7 +20,7 @@
"watcher_object.name": "TerseActionPlanPayload", "watcher_object.name": "TerseActionPlanPayload",
"watcher_object.data": { "watcher_object.data": {
"uuid": "76be87bd-3422-43f9-93a0-e85a577e3061", "uuid": "76be87bd-3422-43f9-93a0-e85a577e3061",
"global_efficacy": {}, "global_efficacy": [],
"created_at": "2016-10-18T09:52:05Z", "created_at": "2016-10-18T09:52:05Z",
"updated_at": null, "updated_at": null,
"state": "CANCELLING", "state": "CANCELLING",

View File

@@ -20,7 +20,7 @@
"watcher_object.name": "TerseActionPlanPayload", "watcher_object.name": "TerseActionPlanPayload",
"watcher_object.data": { "watcher_object.data": {
"uuid": "76be87bd-3422-43f9-93a0-e85a577e3061", "uuid": "76be87bd-3422-43f9-93a0-e85a577e3061",
"global_efficacy": {}, "global_efficacy": [],
"created_at": "2016-10-18T09:52:05Z", "created_at": "2016-10-18T09:52:05Z",
"updated_at": null, "updated_at": null,
"state": "ONGOING", "state": "ONGOING",

View File

@@ -30,7 +30,7 @@
"watcher_object.name": "TerseActionPlanPayload", "watcher_object.name": "TerseActionPlanPayload",
"watcher_object.data": { "watcher_object.data": {
"uuid": "76be87bd-3422-43f9-93a0-e85a577e3061", "uuid": "76be87bd-3422-43f9-93a0-e85a577e3061",
"global_efficacy": {}, "global_efficacy":[],
"created_at": "2016-10-18T09:52:05Z", "created_at": "2016-10-18T09:52:05Z",
"updated_at": null, "updated_at": null,
"state": "ONGOING", "state": "ONGOING",

View File

@@ -20,7 +20,7 @@
"watcher_object.name": "TerseActionPlanPayload", "watcher_object.name": "TerseActionPlanPayload",
"watcher_object.data": { "watcher_object.data": {
"uuid": "76be87bd-3422-43f9-93a0-e85a577e3061", "uuid": "76be87bd-3422-43f9-93a0-e85a577e3061",
"global_efficacy": {}, "global_efficacy": [],
"created_at": "2016-10-18T09:52:05Z", "created_at": "2016-10-18T09:52:05Z",
"updated_at": null, "updated_at": null,
"state": "ONGOING", "state": "ONGOING",

View File

@@ -29,7 +29,7 @@
"uuid": "76be87bd-3422-43f9-93a0-e85a577e3061", "uuid": "76be87bd-3422-43f9-93a0-e85a577e3061",
"fault": null, "fault": null,
"state": "CANCELLED", "state": "CANCELLED",
"global_efficacy": {}, "global_efficacy": [],
"strategy_uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3", "strategy_uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
"strategy": { "strategy": {
"watcher_object.namespace": "watcher", "watcher_object.namespace": "watcher",

View File

@@ -57,7 +57,7 @@
"state": "SUCCEEDED" "state": "SUCCEEDED"
} }
}, },
"global_efficacy": {}, "global_efficacy": [],
"state": "CANCELLING" "state": "CANCELLING"
} }
}, },

View File

@@ -29,7 +29,7 @@
"uuid": "76be87bd-3422-43f9-93a0-e85a577e3061", "uuid": "76be87bd-3422-43f9-93a0-e85a577e3061",
"fault": null, "fault": null,
"state": "CANCELLING", "state": "CANCELLING",
"global_efficacy": {}, "global_efficacy": [],
"strategy_uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3", "strategy_uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
"strategy": { "strategy": {
"watcher_object.namespace": "watcher", "watcher_object.namespace": "watcher",

View File

@@ -23,6 +23,7 @@
"audit": { "audit": {
"watcher_object.version": "1.0", "watcher_object.version": "1.0",
"watcher_object.data": { "watcher_object.data": {
"name": "my_audit",
"audit_type": "ONESHOT", "audit_type": "ONESHOT",
"scope": [], "scope": [],
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d", "uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",

View File

@@ -8,6 +8,7 @@
"audit_uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d", "audit_uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
"audit": { "audit": {
"watcher_object.data": { "watcher_object.data": {
"name": "my_audit",
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d", "uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
"goal_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a", "goal_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
"strategy_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39", "strategy_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",

View File

@@ -15,6 +15,7 @@
"watcher_object.data": { "watcher_object.data": {
"created_at": "2016-10-18T09:52:05Z", "created_at": "2016-10-18T09:52:05Z",
"deleted_at": null, "deleted_at": null,
"name": "my_audit",
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d", "uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
"goal_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a", "goal_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
"strategy_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39", "strategy_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
@@ -29,7 +30,7 @@
"uuid": "76be87bd-3422-43f9-93a0-e85a577e3061", "uuid": "76be87bd-3422-43f9-93a0-e85a577e3061",
"fault": null, "fault": null,
"state": "ONGOING", "state": "ONGOING",
"global_efficacy": {}, "global_efficacy": [],
"strategy_uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3", "strategy_uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
"strategy": { "strategy": {
"watcher_object.namespace": "watcher", "watcher_object.namespace": "watcher",

View File

@@ -45,6 +45,7 @@
"watcher_object.name": "TerseAuditPayload", "watcher_object.name": "TerseAuditPayload",
"watcher_object.data": { "watcher_object.data": {
"parameters": {}, "parameters": {},
"name": "my_audit",
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d", "uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
"goal_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a", "goal_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
"strategy_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39", "strategy_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
@@ -57,7 +58,7 @@
"state": "PENDING" "state": "PENDING"
} }
}, },
"global_efficacy": {}, "global_efficacy": [],
"state": "ONGOING" "state": "ONGOING"
} }
}, },

View File

@@ -15,6 +15,7 @@
"watcher_object.data": { "watcher_object.data": {
"created_at": "2016-10-18T09:52:05Z", "created_at": "2016-10-18T09:52:05Z",
"deleted_at": null, "deleted_at": null,
"name": "my_audit",
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d", "uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
"goal_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a", "goal_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
"strategy_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39", "strategy_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
@@ -29,7 +30,7 @@
"uuid": "76be87bd-3422-43f9-93a0-e85a577e3061", "uuid": "76be87bd-3422-43f9-93a0-e85a577e3061",
"fault": null, "fault": null,
"state": "ONGOING", "state": "ONGOING",
"global_efficacy": {}, "global_efficacy": [],
"strategy_uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3", "strategy_uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
"strategy": { "strategy": {
"watcher_object.namespace": "watcher", "watcher_object.namespace": "watcher",

View File

@@ -6,6 +6,7 @@
"audit": { "audit": {
"watcher_object.version": "1.0", "watcher_object.version": "1.0",
"watcher_object.data": { "watcher_object.data": {
"name": "my_audit",
"audit_type": "ONESHOT", "audit_type": "ONESHOT",
"scope": [], "scope": [],
"created_at": "2016-10-18T09:52:05Z", "created_at": "2016-10-18T09:52:05Z",

View File

@@ -2,6 +2,7 @@
"priority": "INFO", "priority": "INFO",
"payload": { "payload": {
"watcher_object.data": { "watcher_object.data": {
"name": "my_audit",
"audit_type": "ONESHOT", "audit_type": "ONESHOT",
"parameters": { "parameters": {
"para2": "hello", "para2": "hello",

View File

@@ -2,6 +2,7 @@
"priority": "INFO", "priority": "INFO",
"payload": { "payload": {
"watcher_object.data": { "watcher_object.data": {
"name": "my_audit",
"audit_type": "ONESHOT", "audit_type": "ONESHOT",
"parameters": { "parameters": {
"para2": "hello", "para2": "hello",

View File

@@ -2,6 +2,7 @@
"priority": "INFO", "priority": "INFO",
"payload": { "payload": {
"watcher_object.data": { "watcher_object.data": {
"name": "my_audit",
"audit_type": "ONESHOT", "audit_type": "ONESHOT",
"parameters": { "parameters": {
"para2": "hello", "para2": "hello",

View File

@@ -2,6 +2,7 @@
"priority": "ERROR", "priority": "ERROR",
"payload": { "payload": {
"watcher_object.data": { "watcher_object.data": {
"name": "my_audit",
"audit_type": "ONESHOT", "audit_type": "ONESHOT",
"parameters": { "parameters": {
"para2": "hello", "para2": "hello",

View File

@@ -2,6 +2,7 @@
"priority": "INFO", "priority": "INFO",
"payload": { "payload": {
"watcher_object.data": { "watcher_object.data": {
"name": "my_audit",
"audit_type": "ONESHOT", "audit_type": "ONESHOT",
"parameters": { "parameters": {
"para2": "hello", "para2": "hello",

View File

@@ -2,6 +2,7 @@
"priority": "INFO", "priority": "INFO",
"payload": { "payload": {
"watcher_object.data": { "watcher_object.data": {
"name": "my_audit",
"audit_type": "ONESHOT", "audit_type": "ONESHOT",
"parameters": { "parameters": {
"para2": "hello", "para2": "hello",

View File

@@ -2,6 +2,7 @@
"priority": "ERROR", "priority": "ERROR",
"payload": { "payload": {
"watcher_object.data": { "watcher_object.data": {
"name": "my_audit",
"audit_type": "ONESHOT", "audit_type": "ONESHOT",
"parameters": { "parameters": {
"para2": "hello", "para2": "hello",

View File

@@ -2,6 +2,7 @@
"priority": "INFO", "priority": "INFO",
"payload": { "payload": {
"watcher_object.data": { "watcher_object.data": {
"name": "my_audit",
"audit_type": "ONESHOT", "audit_type": "ONESHOT",
"parameters": { "parameters": {
"para2": "hello", "para2": "hello",

View File

@@ -4,6 +4,7 @@
"payload": { "payload": {
"watcher_object.name": "AuditUpdatePayload", "watcher_object.name": "AuditUpdatePayload",
"watcher_object.data": { "watcher_object.data": {
"name": "my_audit",
"strategy_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39", "strategy_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
"strategy": { "strategy": {
"watcher_object.name": "StrategyPayload", "watcher_object.name": "StrategyPayload",

View File

@@ -126,7 +126,7 @@ Here is single Dockerfile snippet you can use to run your Docker container:
MAINTAINER David TARDIVEL <david.tardivel@b-com.com> MAINTAINER David TARDIVEL <david.tardivel@b-com.com>
RUN apt-get update RUN apt-get update
RUN apt-get dist-upgrade -y RUN apt-get dist-upgrade
RUN apt-get install vim net-tools RUN apt-get install vim net-tools
RUN apt-get install experimental watcher-api RUN apt-get install experimental watcher-api

View File

@@ -86,3 +86,15 @@ Actions
.. autotype:: watcher.api.controllers.v1.action.Action .. autotype:: watcher.api.controllers.v1.action.Action
:members: :members:
Scoring Engine
==============
.. rest-controller:: watcher.api.controllers.v1.scoring_engine:ScoringEngineController
:webprefix: /v1/scoring_engine
.. autotype:: watcher.api.controllers.v1.scoring_engine.ScoringEngineCollection
:members:
.. autotype:: watcher.api.controllers.v1.scoring_engine.ScoringEngine
:members:

View File

@@ -24,8 +24,8 @@ signed OpenStack's contributor's agreement.
.. seealso:: .. seealso::
* http://docs.openstack.org/infra/manual/developers.html * https://docs.openstack.org/infra/manual/developers.html
* http://wiki.openstack.org/CLA * https://wiki.openstack.org/CLA
LaunchPad Project LaunchPad Project
----------------- -----------------
@@ -37,22 +37,22 @@ notifications of important events.
.. seealso:: .. seealso::
* http://launchpad.net * https://launchpad.net
* http://launchpad.net/watcher * https://launchpad.net/watcher
* http://launchpad.net/~openstack * https://launchpad.net/~openstack
Project Hosting Details Project Hosting Details
----------------------- -----------------------
Bug tracker Bug tracker
http://launchpad.net/watcher https://launchpad.net/watcher
Mailing list (prefix subjects with ``[watcher]`` for faster responses) Mailing list (prefix subjects with ``[watcher]`` for faster responses)
http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-dev https://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-dev
Wiki Wiki
http://wiki.openstack.org/Watcher https://wiki.openstack.org/Watcher
Code Hosting Code Hosting
https://git.openstack.org/cgit/openstack/watcher https://git.openstack.org/cgit/openstack/watcher

View File

@@ -37,7 +37,7 @@ Detailed DevStack Instructions
needed (i.e., no computes are needed if you want to just experiment with needed (i.e., no computes are needed if you want to just experiment with
the Watcher services). These servers can be VMs running on your local the Watcher services). These servers can be VMs running on your local
machine via VirtualBox if you prefer. DevStack currently recommends that machine via VirtualBox if you prefer. DevStack currently recommends that
you use Ubuntu 14.04 LTS. The servers should also have connections to the you use Ubuntu 16.04 LTS. The servers should also have connections to the
same network such that they are all able to communicate with one another. same network such that they are all able to communicate with one another.
#. For each server, clone the DevStack repository and create the stack user:: #. For each server, clone the DevStack repository and create the stack user::

View File

@@ -69,8 +69,8 @@ itself.
These dependencies can be installed from PyPi_ using the Python tool pip_. These dependencies can be installed from PyPi_ using the Python tool pip_.
.. _PyPi: http://pypi.python.org/ .. _PyPi: https://pypi.python.org/
.. _pip: http://pypi.python.org/pypi/pip .. _pip: https://pypi.python.org/pypi/pip
However, your system *may* need additional dependencies that `pip` (and by However, your system *may* need additional dependencies that `pip` (and by
extension, PyPi) cannot satisfy. These dependencies should be installed extension, PyPi) cannot satisfy. These dependencies should be installed
@@ -125,7 +125,7 @@ You can re-activate this virtualenv for your current shell using:
For more information on virtual environments, see virtualenv_. For more information on virtual environments, see virtualenv_.
.. _virtualenv: http://www.virtualenv.org/ .. _virtualenv: https://www.virtualenv.org/

View File

@@ -28,6 +28,8 @@ In order to create a new cluster data model collector, you have to:
- Implement its :py:meth:`~.BaseClusterDataModelCollector.execute` abstract - Implement its :py:meth:`~.BaseClusterDataModelCollector.execute` abstract
method to return your entire cluster data model that this method should method to return your entire cluster data model that this method should
build. build.
- Implement its :py:meth:`~.BaseClusterDataModelCollector.audit_scope_handler`
abstract property to return your audit scope handler.
- Implement its :py:meth:`~.Goal.notification_endpoints` abstract property to - Implement its :py:meth:`~.Goal.notification_endpoints` abstract property to
return the list of all the :py:class:`~.base.NotificationEndpoint` instances return the list of all the :py:class:`~.base.NotificationEndpoint` instances
that will be responsible for handling incoming notifications in order to that will be responsible for handling incoming notifications in order to
@@ -57,6 +59,10 @@ Here is an example showing how you can write a plugin called
# Do something here... # Do something here...
return model return model
@property
def audit_scope_handler(self):
return None
@property @property
def notification_endpoints(self): def notification_endpoints(self):
return [] return []
@@ -135,6 +141,10 @@ class method as followed:
# Do something here... # Do something here...
return model return model
@property
def audit_scope_handler(self):
return None
@property @property
def notification_endpoints(self): def notification_endpoints(self):
return [] return []

View File

@@ -75,7 +75,7 @@ table(action_plans) {
foreign_key("strategy_id : Integer") foreign_key("strategy_id : Integer")
uuid : String[36] uuid : String[36]
state : String[20], nullable state : String[20], nullable
global_efficacy : JSONEncodedDict, nullable global_efficacy : JSONEncodedList, nullable
created_at : DateTime created_at : DateTime
updated_at : DateTime updated_at : DateTime

View File

@@ -0,0 +1,15 @@
- hosts: primary
tasks:
- name: Copy files from {{ ansible_user_dir }}/workspace/ on node
synchronize:
src: '{{ ansible_user_dir }}/workspace/'
dest: '{{ zuul.executor.log_root }}'
mode: pull
copy_links: true
verify_host: true
rsync_opts:
- --include=/logs/**
- --include=*/
- --exclude=*
- --prune-empty-dirs

View File

@@ -0,0 +1,67 @@
- hosts: primary
name: Legacy Watcher tempest base multinode
tasks:
- name: Ensure legacy workspace directory
file:
path: '{{ ansible_user_dir }}/workspace'
state: directory
- shell:
cmd: |
set -e
set -x
cat > clonemap.yaml << EOF
clonemap:
- name: openstack-infra/devstack-gate
dest: devstack-gate
EOF
/usr/zuul-env/bin/zuul-cloner -m clonemap.yaml --cache-dir /opt/git \
git://git.openstack.org \
openstack-infra/devstack-gate
executable: /bin/bash
chdir: '{{ ansible_user_dir }}/workspace'
environment: '{{ zuul | zuul_legacy_vars }}'
- shell:
cmd: |
set -e
set -x
cat << 'EOF' >>"/tmp/dg-local.conf"
[[local|localrc]]
TEMPEST_PLUGINS='/opt/stack/new/watcher-tempest-plugin'
enable_plugin ceilometer git://git.openstack.org/openstack/ceilometer
# Enable watcher devstack plugin.
enable_plugin watcher git://git.openstack.org/openstack/watcher
EOF
executable: /bin/bash
chdir: '{{ ansible_user_dir }}/workspace'
environment: '{{ zuul | zuul_legacy_vars }}'
- shell:
cmd: |
set -e
set -x
export DEVSTACK_SUBNODE_CONFIG=" "
export PYTHONUNBUFFERED=true
export DEVSTACK_GATE_TEMPEST=1
export DEVSTACK_GATE_NEUTRON=1
export DEVSTACK_GATE_TOPOLOGY="multinode"
export PROJECTS="openstack/watcher $PROJECTS"
export PROJECTS="openstack/python-watcherclient $PROJECTS"
export PROJECTS="openstack/watcher-tempest-plugin $PROJECTS"
export DEVSTACK_GATE_TEMPEST_REGEX="watcher_tempest_plugin"
export BRANCH_OVERRIDE=default
if [ "$BRANCH_OVERRIDE" != "default" ] ; then
export OVERRIDE_ZUUL_BRANCH=$BRANCH_OVERRIDE
fi
cp devstack-gate/devstack-vm-gate-wrap.sh ./safe-devstack-vm-gate-wrap.sh
./safe-devstack-vm-gate-wrap.sh
executable: /bin/bash
chdir: '{{ ansible_user_dir }}/workspace'
environment: '{{ zuul | zuul_legacy_vars }}'

View File

@@ -0,0 +1,80 @@
- hosts: primary
tasks:
- name: Copy files from {{ ansible_user_dir }}/workspace/ on node
synchronize:
src: '{{ ansible_user_dir }}/workspace/'
dest: '{{ zuul.executor.log_root }}'
mode: pull
copy_links: true
verify_host: true
rsync_opts:
- --include=**/*nose_results.html
- --include=*/
- --exclude=*
- --prune-empty-dirs
- name: Copy files from {{ ansible_user_dir }}/workspace/ on node
synchronize:
src: '{{ ansible_user_dir }}/workspace/'
dest: '{{ zuul.executor.log_root }}'
mode: pull
copy_links: true
verify_host: true
rsync_opts:
- --include=**/*testr_results.html.gz
- --include=*/
- --exclude=*
- --prune-empty-dirs
- name: Copy files from {{ ansible_user_dir }}/workspace/ on node
synchronize:
src: '{{ ansible_user_dir }}/workspace/'
dest: '{{ zuul.executor.log_root }}'
mode: pull
copy_links: true
verify_host: true
rsync_opts:
- --include=/.testrepository/tmp*
- --include=*/
- --exclude=*
- --prune-empty-dirs
- name: Copy files from {{ ansible_user_dir }}/workspace/ on node
synchronize:
src: '{{ ansible_user_dir }}/workspace/'
dest: '{{ zuul.executor.log_root }}'
mode: pull
copy_links: true
verify_host: true
rsync_opts:
- --include=**/*testrepository.subunit.gz
- --include=*/
- --exclude=*
- --prune-empty-dirs
- name: Copy files from {{ ansible_user_dir }}/workspace/ on node
synchronize:
src: '{{ ansible_user_dir }}/workspace/'
dest: '{{ zuul.executor.log_root }}/tox'
mode: pull
copy_links: true
verify_host: true
rsync_opts:
- --include=/.tox/*/log/*
- --include=*/
- --exclude=*
- --prune-empty-dirs
- name: Copy files from {{ ansible_user_dir }}/workspace/ on node
synchronize:
src: '{{ ansible_user_dir }}/workspace/'
dest: '{{ zuul.executor.log_root }}'
mode: pull
copy_links: true
verify_host: true
rsync_opts:
- --include=/logs/**
- --include=*/
- --exclude=*
- --prune-empty-dirs

View File

@@ -0,0 +1,64 @@
- hosts: all
name: Legacy watcherclient-dsvm-functional
tasks:
- name: Ensure legacy workspace directory
file:
path: '{{ ansible_user_dir }}/workspace'
state: directory
- shell:
cmd: |
set -e
set -x
cat > clonemap.yaml << EOF
clonemap:
- name: openstack-infra/devstack-gate
dest: devstack-gate
EOF
/usr/zuul-env/bin/zuul-cloner -m clonemap.yaml --cache-dir /opt/git \
git://git.openstack.org \
openstack-infra/devstack-gate
executable: /bin/bash
chdir: '{{ ansible_user_dir }}/workspace'
environment: '{{ zuul | zuul_legacy_vars }}'
- shell:
cmd: |
set -e
set -x
cat << 'EOF' >>"/tmp/dg-local.conf"
[[local|localrc]]
enable_plugin watcher git://git.openstack.org/openstack/watcher
EOF
executable: /bin/bash
chdir: '{{ ansible_user_dir }}/workspace'
environment: '{{ zuul | zuul_legacy_vars }}'
- shell:
cmd: |
set -e
set -x
ENABLED_SERVICES=tempest
ENABLED_SERVICES+=,watcher-api,watcher-decision-engine,watcher-applier
export ENABLED_SERVICES
export PYTHONUNBUFFERED=true
export BRANCH_OVERRIDE=default
export PROJECTS="openstack/watcher $PROJECTS"
export DEVSTACK_PROJECT_FROM_GIT=python-watcherclient
if [ "$BRANCH_OVERRIDE" != "default" ] ; then
export OVERRIDE_ZUUL_BRANCH=$BRANCH_OVERRIDE
fi
function post_test_hook {
# Configure and run functional tests
$BASE/new/python-watcherclient/watcherclient/tests/functional/hooks/post_test_hook.sh
}
export -f post_test_hook
cp devstack-gate/devstack-vm-gate-wrap.sh ./safe-devstack-vm-gate-wrap.sh
./safe-devstack-vm-gate-wrap.sh
executable: /bin/bash
chdir: '{{ ansible_user_dir }}/workspace'
environment: '{{ zuul | zuul_legacy_vars }}'

View File

@@ -31,12 +31,12 @@ Useful links
* How to set Rally up and launch your first scenario: https://rally.readthedocs.io/en/latest/tutorial/step_1_setting_up_env_and_running_benchmark_from_samples.html * How to set Rally up and launch your first scenario: https://rally.readthedocs.io/en/latest/tutorial/step_1_setting_up_env_and_running_benchmark_from_samples.html
* More about Rally: https://rally.readthedocs.org/en/latest/ * More about Rally: https://docs.openstack.org/rally/latest/
* Rally release notes: https://rally.readthedocs.org/en/latest/release_notes.html * Rally project info and release notes: https://docs.openstack.org/rally/latest/project_info/index.html
* How to add rally-gates: https://rally.readthedocs.org/en/latest/gates.html * How to add rally-gates: https://docs.openstack.org/rally/latest/quick_start/gates.html#gate-jobs
* About plugins: https://rally.readthedocs.org/en/latest/plugins.html * About plugins: https://docs.openstack.org/rally/latest/plugins/index.html
* Plugin samples: https://github.com/openstack/rally/tree/master/samples/plugins * Plugin samples: https://github.com/openstack/rally/tree/master/samples/

View File

@@ -0,0 +1,6 @@
---
features:
- |
Each CDM collector can have its own CDM scoper now. This changed Scope
JSON schema definition for the audit template POST data. Please see audit
template create help message in python-watcherclient.

View File

@@ -1,5 +1,5 @@
--- ---
features: features:
- Added strategy to identify and migrate a Noisy Neighbor - a low priority VM - 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 that negatively affects performance of a high priority VM by over utilizing
Last Level Cache. Last Level Cache.

View File

@@ -57,14 +57,11 @@ master_doc = 'index'
project = u'watcher' project = u'watcher'
copyright = u'2016, Watcher developers' copyright = u'2016, Watcher developers'
# The version info for the project you're documenting, acts as replacement for # Release notes are version independent
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version. # The short X.Y version.
version = watcher_version.version_info.release_string() version = ''
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
release = watcher_version.version_string release = ''
# The language for content autogenerated by Sphinx. Refer to documentation # The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages. # for a list of supported languages.

View File

@@ -0,0 +1,207 @@
# Andi Chandler <andi@gowling.com>, 2016. #zanata
# Andi Chandler <andi@gowling.com>, 2017. #zanata
msgid ""
msgstr ""
"Project-Id-Version: watcher 1.4.1.dev113\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-10-23 04:03+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2017-10-21 06:22+0000\n"
"Last-Translator: Andi Chandler <andi@gowling.com>\n"
"Language-Team: English (United Kingdom)\n"
"Language: en-GB\n"
"X-Generator: Zanata 3.9.6\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
msgid "0.29.0"
msgstr "0.29.0"
msgid "0.33.0"
msgstr "0.33.0"
msgid "0.34.0"
msgstr "0.34.0"
msgid "1.0.0"
msgstr "1.0.0"
msgid "1.1.0"
msgstr "1.1.0"
msgid "1.3.0"
msgstr "1.3.0"
msgid "1.4.0"
msgstr "1.4.0"
msgid "1.4.1"
msgstr "1.4.1"
msgid "Add a service supervisor to watch Watcher deamons."
msgstr "Add a service supervisor to watch Watcher daemons."
msgid "Add action for compute node power on/off"
msgstr "Add action for compute node power on/off"
msgid ""
"Add description property for dynamic action. Admin can see detail "
"information of any specify action."
msgstr ""
"Add description property for dynamic action. Admin can see detail "
"information of any specify action."
msgid "Add notifications related to Action object."
msgstr "Add notifications related to Action object."
msgid "Add notifications related to Action plan object."
msgstr "Add notifications related to Action plan object."
msgid "Add notifications related to Audit object."
msgstr "Add notifications related to Audit object."
msgid "Add notifications related to Service object."
msgstr "Add notifications related to Service object."
msgid ""
"Add superseded state for an action plan if the cluster data model has "
"changed after it has been created."
msgstr ""
"Add superseded state for an action plan if the cluster data model has "
"changed after it has been created."
msgid "Added SUSPENDED audit state"
msgstr "Added SUSPENDED audit state"
msgid ""
"Added a generic scoring engine module, which will standardize interactions "
"with scoring engines through the common API. It is possible to use the "
"scoring engine by different Strategies, which improve the code and data "
"model re-use."
msgstr ""
"Added a generic scoring engine module, which will standardize interactions "
"with scoring engines through the common API. It is possible to use the "
"scoring engine by different Strategies, which improve the code and data "
"model re-use."
msgid ""
"Added a generic scoring engine module, which will standarize interactions "
"with scoring engines through the common API. It is possible to use the "
"scoring engine by different Strategies, which improve the code and data "
"model re-use."
msgstr ""
"Added a generic scoring engine module, which will standardise interactions "
"with scoring engines through the common API. It is possible to use the "
"scoring engine by different Strategies, which improve the code and data "
"model re-use."
msgid ""
"Added a new strategy based on the airflow of servers. This strategy makes "
"decisions to migrate VMs to make the airflow uniform."
msgstr ""
"Added a new strategy based on the airflow of servers. This strategy makes "
"decisions to migrate VMs to make the airflow uniform."
msgid ""
"Added a standard way to both declare and fetch configuration options so that "
"whenever the administrator generates the Watcher configuration sample file, "
"it contains the configuration options of the plugins that are currently "
"available."
msgstr ""
"Added a standard way to both declare and fetch configuration options so that "
"whenever the administrator generates the Watcher configuration sample file, "
"it contains the configuration options of the plugins that are currently "
"available."
msgid ""
"Added a strategy based on the VM workloads of hypervisors. This strategy "
"makes decisions to migrate workloads to make the total VM workloads of each "
"hypervisor balanced, when the total VM workloads of hypervisor reaches "
"threshold."
msgstr ""
"Added a strategy based on the VM workloads of hypervisors. This strategy "
"makes decisions to migrate workloads to make the total VM workloads of each "
"hypervisor balanced, when the total VM workloads of hypervisor reaches "
"threshold."
msgid ""
"Added a strategy that monitors if there is a higher load on some hosts "
"compared to other hosts in the cluster and re-balances the work across hosts "
"to minimize the standard deviation of the loads in the cluster."
msgstr ""
"Added a strategy that monitors if there is a higher load on some hosts "
"compared to other hosts in the cluster and re-balances the work across hosts "
"to minimise the standard deviation of the loads in the cluster."
msgid ""
"Added a way to add a new action without having to amend the source code of "
"the default planner."
msgstr ""
"Added a way to add a new action without having to amend the source code of "
"the default planner."
msgid ""
"Added a way to compare the efficacy of different strategies for a give "
"optimization goal."
msgstr ""
"Added a way to compare the efficacy of different strategies for a give "
"optimisation goal."
msgid ""
"Added a way to create periodic audit to be able to optimize continuously the "
"cloud infrastructure."
msgstr ""
"Added a way to create periodic audit to be able to continuously optimise the "
"cloud infrastructure."
msgid ""
"Added a way to return the of available goals depending on which strategies "
"have been deployed on the node where the decision engine is running."
msgstr ""
"Added a way to return the of available goals depending on which strategies "
"have been deployed on the node where the decision engine is running."
msgid ""
"Added a way to return the of available goals depending on which strategies "
"have been deployed on the node where the decison engine is running."
msgstr ""
"Added a way to return the of available goals depending on which strategies "
"have been deployed on the node where the decision engine is running."
msgid ""
"Added an in-memory cache of the cluster model built up and kept fresh via "
"notifications from services of interest in addition to periodic syncing "
"logic."
msgstr ""
"Added an in-memory cache of the cluster model built up and kept fresh via "
"notifications from services of interest in addition to periodic syncing "
"logic."
msgid ""
"Added binding between apscheduler job and Watcher decision engine service. "
"It will allow to provide HA support in the future."
msgstr ""
"Added binding between apscheduler job and Watcher decision engine service. "
"It will allow to provide HA support in the future."
msgid "Added cinder cluster data model"
msgstr "Added cinder cluster data model"
msgid ""
"Added gnocchi support as data source for metrics. Administrator can change "
"data source for each strategy using config file."
msgstr ""
"Added Gnocchi support as data source for metrics. Administrator can change "
"data source for each strategy using config file."
msgid "Added policies to handle user rights to access Watcher API."
msgstr "Added policies to handle user rights to access Watcher API."
#, fuzzy
msgid "Contents:"
msgstr "Contents:"
#, fuzzy
msgid "Current Series Release Notes"
msgstr "Current Series Release Notes"

View File

@@ -4,16 +4,16 @@
apscheduler>=3.0.5 # MIT License apscheduler>=3.0.5 # MIT License
enum34>=1.0.4;python_version=='2.7' or python_version=='2.6' or python_version=='3.3' # BSD enum34>=1.0.4;python_version=='2.7' or python_version=='2.6' or python_version=='3.3' # BSD
jsonpatch>=1.16 # BSD jsonpatch!=1.20,>=1.16 # BSD
keystoneauth1>=3.2.0 # Apache-2.0 keystoneauth1>=3.3.0 # Apache-2.0
jsonschema<3.0.0,>=2.6.0 # MIT jsonschema<3.0.0,>=2.6.0 # MIT
keystonemiddleware>=4.17.0 # Apache-2.0 keystonemiddleware>=4.17.0 # Apache-2.0
lxml!=3.7.0,>=3.4.1 # BSD lxml!=3.7.0,>=3.4.1 # BSD
croniter>=0.3.4 # MIT License croniter>=0.3.4 # MIT License
oslo.concurrency>=3.20.0 # Apache-2.0 oslo.concurrency>=3.20.0 # Apache-2.0
oslo.cache>=1.26.0 # Apache-2.0 oslo.cache>=1.26.0 # Apache-2.0
oslo.config>=4.6.0 # Apache-2.0 oslo.config>=5.1.0 # Apache-2.0
oslo.context!=2.19.1,>=2.14.0 # Apache-2.0 oslo.context>=2.19.2 # Apache-2.0
oslo.db>=4.27.0 # Apache-2.0 oslo.db>=4.27.0 # Apache-2.0
oslo.i18n>=3.15.3 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0
oslo.log>=3.30.0 # Apache-2.0 oslo.log>=3.30.0 # Apache-2.0
@@ -22,7 +22,7 @@ oslo.policy>=1.23.0 # Apache-2.0
oslo.reports>=1.18.0 # Apache-2.0 oslo.reports>=1.18.0 # Apache-2.0
oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0 oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0
oslo.service>=1.24.0 # Apache-2.0 oslo.service>=1.24.0 # Apache-2.0
oslo.utils>=3.28.0 # Apache-2.0 oslo.utils>=3.31.0 # Apache-2.0
oslo.versionedobjects>=1.28.0 # Apache-2.0 oslo.versionedobjects>=1.28.0 # Apache-2.0
PasteDeploy>=1.5.0 # MIT PasteDeploy>=1.5.0 # MIT
pbr!=2.1.0,>=2.0.0 # Apache-2.0 pbr!=2.1.0,>=2.0.0 # Apache-2.0
@@ -39,7 +39,7 @@ python-neutronclient>=6.3.0 # Apache-2.0
python-novaclient>=9.1.0 # Apache-2.0 python-novaclient>=9.1.0 # Apache-2.0
python-openstackclient>=3.12.0 # Apache-2.0 python-openstackclient>=3.12.0 # Apache-2.0
python-ironicclient>=1.14.0 # Apache-2.0 python-ironicclient>=1.14.0 # Apache-2.0
six>=1.9.0 # MIT six>=1.10.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
taskflow>=2.7.0 # Apache-2.0 taskflow>=2.7.0 # Apache-2.0

View File

@@ -11,7 +11,7 @@ oslotest>=1.10.0 # Apache-2.0
os-testr>=1.0.0 # Apache-2.0 os-testr>=1.0.0 # Apache-2.0
testrepository>=0.0.18 # Apache-2.0/BSD testrepository>=0.0.18 # Apache-2.0/BSD
testscenarios>=0.4 # Apache-2.0/BSD testscenarios>=0.4 # Apache-2.0/BSD
testtools>=1.4.0 # MIT testtools>=2.2.0 # MIT
# Doc requirements # Doc requirements
openstackdocstheme>=1.17.0 # Apache-2.0 openstackdocstheme>=1.17.0 # Apache-2.0

View File

@@ -14,7 +14,7 @@ deps = -r{toxinidir}/test-requirements.txt
commands = commands =
rm -f .testrepository/times.dbm 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} stestr run {posargs}
passenv = http_proxy HTTP_PROXY https_proxy HTTPS_PROXY no_proxy NO_PROXY passenv = http_proxy HTTP_PROXY https_proxy HTTPS_PROXY no_proxy NO_PROXY
[testenv:pep8] [testenv:pep8]
@@ -47,6 +47,7 @@ commands =
oslo-config-generator --config-file etc/watcher/oslo-config-generator/watcher.conf oslo-config-generator --config-file etc/watcher/oslo-config-generator/watcher.conf
[flake8] [flake8]
filename = *.py,app.wsgi
show-source=True show-source=True
ignore= H105,E123,E226,N320,H202 ignore= H105,E123,E226,N320,H202
builtins= _ builtins= _

View File

@@ -37,4 +37,3 @@ LOG.debug("Configuration:")
CONF.log_opt_values(LOG, log.DEBUG) CONF.log_opt_values(LOG, log.DEBUG)
application = app.VersionSelectorApplication() application = app.VersionSelectorApplication()

View File

@@ -52,6 +52,8 @@ from watcher import objects
class AuditPostType(wtypes.Base): class AuditPostType(wtypes.Base):
name = wtypes.wsattr(wtypes.text, mandatory=False)
audit_template_uuid = wtypes.wsattr(types.uuid, mandatory=False) audit_template_uuid = wtypes.wsattr(types.uuid, mandatory=False)
goal = wtypes.wsattr(wtypes.text, mandatory=False) goal = wtypes.wsattr(wtypes.text, mandatory=False)
@@ -111,7 +113,25 @@ class AuditPostType(wtypes.Base):
setattr(self, k, at_attr) setattr(self, k, at_attr)
except AttributeError: except AttributeError:
pass pass
# Note: If audit name was not provided, used a default name
if not self.name:
if self.strategy:
strategy = objects.Strategy.get(context, self.strategy)
self.name = "%s-%s" % (strategy.name,
datetime.datetime.utcnow().isoformat())
elif self.audit_template_uuid:
audit_template = objects.AuditTemplate.get(
context, self.audit_template_uuid)
self.name = "%s-%s" % (audit_template.name,
datetime.datetime.utcnow().isoformat())
else:
goal = objects.Goal.get(context, self.goal)
self.name = "%s-%s" % (goal.name,
datetime.datetime.utcnow().isoformat())
return Audit( return Audit(
name=self.name,
audit_type=self.audit_type, audit_type=self.audit_type,
parameters=self.parameters, parameters=self.parameters,
goal_id=self.goal, goal_id=self.goal,
@@ -233,6 +253,9 @@ class Audit(base.APIBase):
uuid = types.uuid uuid = types.uuid
"""Unique UUID for this audit""" """Unique UUID for this audit"""
name = wtypes.text
"""Name of this audit"""
audit_type = wtypes.text audit_type = wtypes.text
"""Type of this audit""" """Type of this audit"""
@@ -301,7 +324,7 @@ class Audit(base.APIBase):
@staticmethod @staticmethod
def _convert_with_links(audit, url, expand=True): def _convert_with_links(audit, url, expand=True):
if not expand: if not expand:
audit.unset_fields_except(['uuid', 'audit_type', 'state', audit.unset_fields_except(['uuid', 'name', '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',
@@ -324,6 +347,7 @@ class Audit(base.APIBase):
@classmethod @classmethod
def sample(cls, expand=True): def sample(cls, expand=True):
sample = cls(uuid='27e3153e-d5bf-4b7e-b517-fb518e17f34c', sample = cls(uuid='27e3153e-d5bf-4b7e-b517-fb518e17f34c',
name='My Audit',
audit_type='ONESHOT', audit_type='ONESHOT',
state='PENDING', state='PENDING',
created_at=datetime.datetime.utcnow(), created_at=datetime.datetime.utcnow(),
@@ -483,17 +507,17 @@ class AuditsController(rest.RestController):
resource_url, resource_url,
goal=goal) goal=goal)
@wsme_pecan.wsexpose(Audit, types.uuid) @wsme_pecan.wsexpose(Audit, wtypes.text)
def get_one(self, audit_uuid): def get_one(self, audit):
"""Retrieve information about the given audit. """Retrieve information about the given audit.
:param audit_uuid: UUID of a audit. :param audit_uuid: UUID or name of an audit.
""" """
if self.from_audits: if self.from_audits:
raise exception.OperationNotPermitted raise exception.OperationNotPermitted
context = pecan.request.context context = pecan.request.context
rpc_audit = api_utils.get_resource('Audit', audit_uuid) rpc_audit = api_utils.get_resource('Audit', audit)
policy.enforce(context, 'audit:get', rpc_audit, action='audit:get') policy.enforce(context, 'audit:get', rpc_audit, action='audit:get')
return Audit.convert_with_links(rpc_audit) return Audit.convert_with_links(rpc_audit)
@@ -551,11 +575,11 @@ class AuditsController(rest.RestController):
return Audit.convert_with_links(new_audit) return Audit.convert_with_links(new_audit)
@wsme.validate(types.uuid, [AuditPatchType]) @wsme.validate(types.uuid, [AuditPatchType])
@wsme_pecan.wsexpose(Audit, types.uuid, body=[AuditPatchType]) @wsme_pecan.wsexpose(Audit, wtypes.text, body=[AuditPatchType])
def patch(self, audit_uuid, patch): def patch(self, audit, patch):
"""Update an existing audit. """Update an existing audit.
:param audit_uuid: UUID of a audit. :param auditd: UUID or name of a audit.
:param patch: a json PATCH document to apply to this audit. :param patch: a json PATCH document to apply to this audit.
""" """
if self.from_audits: if self.from_audits:
@@ -563,7 +587,7 @@ class AuditsController(rest.RestController):
context = pecan.request.context context = pecan.request.context
audit_to_update = api_utils.get_resource( audit_to_update = api_utils.get_resource(
'Audit', audit_uuid, eager=True) 'Audit', audit, eager=True)
policy.enforce(context, 'audit:update', audit_to_update, policy.enforce(context, 'audit:update', audit_to_update,
action='audit:update') action='audit:update')
@@ -600,15 +624,15 @@ class AuditsController(rest.RestController):
audit_to_update.save() audit_to_update.save()
return Audit.convert_with_links(audit_to_update) return Audit.convert_with_links(audit_to_update)
@wsme_pecan.wsexpose(None, types.uuid, status_code=204) @wsme_pecan.wsexpose(None, wtypes.text, status_code=204)
def delete(self, audit_uuid): def delete(self, audit):
"""Delete a audit. """Delete an audit.
:param audit_uuid: UUID of a audit. :param audit: UUID or name of an audit.
""" """
context = pecan.request.context context = pecan.request.context
audit_to_delete = api_utils.get_resource( audit_to_delete = api_utils.get_resource(
'Audit', audit_uuid, eager=True) 'Audit', audit, eager=True)
policy.enforce(context, 'audit:update', audit_to_delete, policy.enforce(context, 'audit:update', audit_to_delete,
action='audit:update') action='audit:update')

View File

@@ -51,6 +51,8 @@ import wsme
from wsme import types as wtypes from wsme import types as wtypes
import wsmeext.pecan as wsme_pecan import wsmeext.pecan as wsme_pecan
from oslo_log import log
from watcher._i18n import _ from watcher._i18n import _
from watcher.api.controllers import base from watcher.api.controllers import base
from watcher.api.controllers import link from watcher.api.controllers import link
@@ -61,9 +63,11 @@ from watcher.common import context as context_utils
from watcher.common import exception from watcher.common import exception
from watcher.common import policy from watcher.common import policy
from watcher.common import utils as common_utils from watcher.common import utils as common_utils
from watcher.decision_engine.scope import default from watcher.decision_engine.loading import default as default_loading
from watcher import objects from watcher import objects
LOG = log.getLogger(__name__)
class AuditTemplatePostType(wtypes.Base): class AuditTemplatePostType(wtypes.Base):
_ctx = context_utils.make_context() _ctx = context_utils.make_context()
@@ -94,6 +98,27 @@ class AuditTemplatePostType(wtypes.Base):
scope=self.scope, scope=self.scope,
) )
@staticmethod
def _build_schema():
SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "array",
"items": {
"type": "object",
"properties": AuditTemplatePostType._get_schemas(),
"additionalProperties": False
}
}
return SCHEMA
@staticmethod
def _get_schemas():
collectors = default_loading.ClusterDataModelCollectorLoader(
).list_available()
schemas = {k: c.SCHEMA for k, c
in collectors.items() if hasattr(c, "SCHEMA")}
return schemas
@staticmethod @staticmethod
def validate(audit_template): def validate(audit_template):
available_goals = objects.Goal.list(AuditTemplatePostType._ctx) available_goals = objects.Goal.list(AuditTemplatePostType._ctx)
@@ -106,12 +131,14 @@ class AuditTemplatePostType(wtypes.Base):
else: else:
raise exception.InvalidGoal(goal=audit_template.goal) raise exception.InvalidGoal(goal=audit_template.goal)
if audit_template.scope:
common_utils.Draft4Validator( common_utils.Draft4Validator(
default.DefaultScope.DEFAULT_SCHEMA).validate(audit_template.scope) AuditTemplatePostType._build_schema()
).validate(audit_template.scope)
include_host_aggregates = False include_host_aggregates = False
exclude_host_aggregates = False exclude_host_aggregates = False
for rule in audit_template.scope: for rule in audit_template.scope[0]['compute']:
if 'host_aggregates' in rule: if 'host_aggregates' in rule:
include_host_aggregates = True include_host_aggregates = True
elif 'exclude' in rule: elif 'exclude' in rule:

View File

@@ -36,15 +36,20 @@ class ChangeNovaServiceState(base.BaseAction):
schema = Schema({ schema = Schema({
'resource_id': str, 'resource_id': str,
'state': str, 'state': str,
'disabled_reason': str,
}) })
The `resource_id` references a nova-compute service name (list of available The `resource_id` references a nova-compute service name (list of available
nova-compute services is returned by this command: ``nova service-list nova-compute services is returned by this command: ``nova service-list
--binary nova-compute``). --binary nova-compute``).
The `state` value should either be `ONLINE` or `OFFLINE`. The `state` value should either be `ONLINE` or `OFFLINE`.
The `disabled_reason` references the reason why Watcher disables this
nova-compute service. The value should be with `watcher_` prefix, such as
`watcher_disabled`, `watcher_maintaining`.
""" """
STATE = 'state' STATE = 'state'
REASON = 'disabled_reason'
@property @property
def schema(self): def schema(self):
@@ -61,6 +66,10 @@ class ChangeNovaServiceState(base.BaseAction):
element.ServiceState.OFFLINE.value, element.ServiceState.OFFLINE.value,
element.ServiceState.ENABLED.value, element.ServiceState.ENABLED.value,
element.ServiceState.DISABLED.value] element.ServiceState.DISABLED.value]
},
'disabled_reason': {
'type': 'string',
"minlength": 1
} }
}, },
'required': ['resource_id', 'state'], 'required': ['resource_id', 'state'],
@@ -75,6 +84,10 @@ class ChangeNovaServiceState(base.BaseAction):
def state(self): def state(self):
return self.input_parameters.get(self.STATE) return self.input_parameters.get(self.STATE)
@property
def reason(self):
return self.input_parameters.get(self.REASON)
def execute(self): def execute(self):
target_state = None target_state = None
if self.state == element.ServiceState.DISABLED.value: if self.state == element.ServiceState.DISABLED.value:
@@ -100,7 +113,7 @@ class ChangeNovaServiceState(base.BaseAction):
if state is True: if state is True:
return nova.enable_service_nova_compute(self.host) return nova.enable_service_nova_compute(self.host)
else: else:
return nova.disable_service_nova_compute(self.host) return nova.disable_service_nova_compute(self.host, self.reason)
def pre_condition(self): def pre_condition(self):
pass pass

View File

@@ -112,15 +112,6 @@ class Migrate(base.BaseAction):
result = nova.live_migrate_instance(instance_id=self.instance_uuid, result = nova.live_migrate_instance(instance_id=self.instance_uuid,
dest_hostname=destination) dest_hostname=destination)
except nova_helper.nvexceptions.ClientException as e: except nova_helper.nvexceptions.ClientException as e:
if e.code == 400:
LOG.debug("Live migration of instance %s failed. "
"Trying to live migrate using block migration."
% self.instance_uuid)
result = nova.live_migrate_instance(
instance_id=self.instance_uuid,
dest_hostname=destination,
block_migration=True)
else:
LOG.debug("Nova client exception occurred while live " LOG.debug("Nova client exception occurred while live "
"migrating instance %s.Exception: %s" % "migrating instance %s.Exception: %s" %
(self.instance_uuid, e)) (self.instance_uuid, e))

View File

@@ -45,7 +45,7 @@ class VolumeMigrate(base.BaseAction):
'migration_type': str, # choices -> "swap", "cold" 'migration_type': str, # choices -> "swap", "cold"
'destination_node': str, 'destination_node': str,
'destination_type': str, 'destination_type': str,
)} })
The `resource_id` is the UUID of cinder volume to migrate. The `resource_id` is the UUID of cinder volume to migrate.
The `destination_node` is the destination block storage pool name. The `destination_node` is the destination block storage pool name.

View File

@@ -20,13 +20,13 @@
import sys import sys
from oslo_config import cfg from oslo_config import cfg
from oslo_log import log as logging from oslo_log import log
from watcher.api import scheduling from watcher.api import scheduling
from watcher.common import service from watcher.common import service
from watcher import conf from watcher import conf
LOG = logging.getLogger(__name__) LOG = log.getLogger(__name__)
CONF = conf.CONF CONF = conf.CONF

View File

@@ -20,14 +20,14 @@
import os import os
import sys import sys
from oslo_log import log as logging from oslo_log import log
from watcher.applier import manager from watcher.applier import manager
from watcher.applier import sync from watcher.applier import sync
from watcher.common import service as watcher_service from watcher.common import service as watcher_service
from watcher import conf from watcher import conf
LOG = logging.getLogger(__name__) LOG = log.getLogger(__name__)
CONF = conf.CONF CONF = conf.CONF

View File

@@ -20,7 +20,7 @@
import os import os
import sys import sys
from oslo_log import log as logging from oslo_log import log
from watcher.common import service as watcher_service from watcher.common import service as watcher_service
from watcher import conf from watcher import conf
@@ -29,7 +29,7 @@ from watcher.decision_engine import manager
from watcher.decision_engine import scheduling from watcher.decision_engine import scheduling
from watcher.decision_engine import sync from watcher.decision_engine import sync
LOG = logging.getLogger(__name__) LOG = log.getLogger(__name__)
CONF = conf.CONF CONF = conf.CONF

View File

@@ -20,13 +20,13 @@
import sys import sys
from oslo_log import log as logging from oslo_log import log
from watcher.common import service as service from watcher.common import service as service
from watcher import conf from watcher import conf
from watcher.decision_engine import sync from watcher.decision_engine import sync
LOG = logging.getLogger(__name__) LOG = log.getLogger(__name__)
CONF = conf.CONF CONF = conf.CONF

View File

@@ -165,7 +165,8 @@ class CinderHelper(object):
def check_migrated(self, volume, retry_interval=10): def check_migrated(self, volume, retry_interval=10):
volume = self.get_volume(volume) volume = self.get_volume(volume)
while getattr(volume, 'migration_status') == 'migrating': final_status = ('success', 'error')
while getattr(volume, 'migration_status') not in final_status:
volume = self.get_volume(volume.id) volume = self.get_volume(volume.id)
LOG.debug('Waiting the migration of {0}'.format(volume)) LOG.debug('Waiting the migration of {0}'.format(volume))
time.sleep(retry_interval) time.sleep(retry_interval)

View File

@@ -11,11 +11,11 @@
# under the License. # under the License.
from oslo_context import context from oslo_context import context
from oslo_log import log as logging from oslo_log import log
from oslo_utils import timeutils from oslo_utils import timeutils
import six import six
LOG = logging.getLogger(__name__) LOG = log.getLogger(__name__)
class RequestContext(context.RequestContext): class RequestContext(context.RequestContext):

View File

@@ -26,14 +26,14 @@ import functools
import sys import sys
from keystoneclient import exceptions as keystone_exceptions from keystoneclient import exceptions as keystone_exceptions
from oslo_log import log as logging from oslo_log import log
import six import six
from watcher._i18n import _ from watcher._i18n import _
from watcher import conf from watcher import conf
LOG = logging.getLogger(__name__) LOG = log.getLogger(__name__)
CONF = conf.CONF CONF = conf.CONF
@@ -249,7 +249,7 @@ class AuditNotFound(ResourceNotFound):
class AuditAlreadyExists(Conflict): class AuditAlreadyExists(Conflict):
msg_fmt = _("An audit with UUID %(uuid)s already exists") msg_fmt = _("An audit with UUID or name %(audit)s already exists")
class AuditIntervalNotSpecified(Invalid): class AuditIntervalNotSpecified(Invalid):

View File

@@ -200,11 +200,7 @@ class NovaHelper(object):
new_image_name = getattr(image, "name") new_image_name = getattr(image, "name")
instance_name = getattr(instance, "name") instance_name = getattr(instance, "name")
flavordict = getattr(instance, "flavor") flavor_name = instance.flavor.get('original_name')
# a_dict = dict([flavorstr.strip('{}').split(":"),])
flavor_id = flavordict["id"]
flavor = self.nova.flavors.get(flavor_id)
flavor_name = getattr(flavor, "name")
keypair_name = getattr(instance, "key_name") keypair_name = getattr(instance, "key_name")
addresses = getattr(instance, "addresses") addresses = getattr(instance, "addresses")
@@ -422,8 +418,7 @@ class NovaHelper(object):
return True return True
def live_migrate_instance(self, instance_id, dest_hostname, def live_migrate_instance(self, instance_id, dest_hostname, retry=120):
block_migration=False, retry=120):
"""This method does a live migration of a given instance """This method does a live migration of a given instance
This method uses the Nova built-in live_migrate() This method uses the Nova built-in live_migrate()
@@ -436,7 +431,6 @@ class NovaHelper(object):
:param dest_hostname: the name of the destination compute node, if :param dest_hostname: the name of the destination compute node, if
destination_node is None, nova scheduler choose destination_node is None, nova scheduler choose
the destination host the destination host
:param block_migration: No shared storage is required.
""" """
LOG.debug("Trying to live migrate instance %s " % (instance_id)) LOG.debug("Trying to live migrate instance %s " % (instance_id))
@@ -450,8 +444,9 @@ class NovaHelper(object):
LOG.debug( LOG.debug(
"Instance %s found on host '%s'." % (instance_id, host_name)) "Instance %s found on host '%s'." % (instance_id, host_name))
instance.live_migrate(host=dest_hostname, # From nova api version 2.25(Mitaka release), the default value of
block_migration=block_migration) # block_migration is None which is mapped to 'auto'.
instance.live_migrate(host=dest_hostname)
instance = self.nova.servers.get(instance_id) instance = self.nova.servers.get(instance_id)
@@ -479,6 +474,9 @@ class NovaHelper(object):
'OS-EXT-SRV-ATTR:host') != dest_hostname \ 'OS-EXT-SRV-ATTR:host') != dest_hostname \
and retry: and retry:
instance = self.nova.servers.get(instance.id) instance = self.nova.servers.get(instance.id)
if not getattr(instance, 'OS-EXT-STS:task_state'):
LOG.debug("Instance task state: %s is null" % instance_id)
break
LOG.debug( LOG.debug(
'Waiting the migration of {0} to {1}'.format( 'Waiting the migration of {0} to {1}'.format(
instance, instance,
@@ -543,9 +541,10 @@ class NovaHelper(object):
else: else:
return False return False
def disable_service_nova_compute(self, hostname): def disable_service_nova_compute(self, hostname, reason=None):
if self.nova.services.disable(host=hostname, if self.nova.services.disable_log_reason(host=hostname,
binary='nova-compute'). \ binary='nova-compute',
reason=reason). \
status == 'disabled': status == 'disabled':
return True return True
else: else:
@@ -768,10 +767,9 @@ class NovaHelper(object):
# Make sure all security groups exist # Make sure all security groups exist
for sec_group_name in sec_group_list: for sec_group_name in sec_group_list:
try: group_id = self.get_security_group_id_from_name(sec_group_name)
self.nova.security_groups.find(name=sec_group_name)
except nvexceptions.NotFound: if not group_id:
LOG.debug("Security group '%s' not found " % sec_group_name) LOG.debug("Security group '%s' not found " % sec_group_name)
return return
@@ -818,6 +816,14 @@ class NovaHelper(object):
return instance return instance
def get_security_group_id_from_name(self, group_name="default"):
"""This method returns the security group of the provided group name"""
security_groups = self.neutron.list_security_groups(name=group_name)
security_group_id = security_groups['security_groups'][0]['id']
return security_group_id
def get_network_id_from_name(self, net_name="private"): def get_network_id_from_name(self, net_name="private"):
"""This method returns the unique id of the provided network name""" """This method returns the unique id of the provided network name"""
networks = self.neutron.list_networks(name=net_name) networks = self.neutron.list_networks(name=net_name)

View File

@@ -24,7 +24,7 @@ import string
from croniter import croniter from croniter import croniter
from jsonschema import validators from jsonschema import validators
from oslo_log import log as logging from oslo_log import log
from oslo_utils import strutils from oslo_utils import strutils
from oslo_utils import uuidutils from oslo_utils import uuidutils
import six import six
@@ -35,7 +35,7 @@ from watcher import conf
CONF = conf.CONF CONF = conf.CONF
LOG = logging.getLogger(__name__) LOG = log.getLogger(__name__)
class Struct(dict): class Struct(dict):

View File

@@ -401,6 +401,16 @@ class BaseConnection(object):
:raises: :py:class:`~.AuditNotFound` :raises: :py:class:`~.AuditNotFound`
""" """
def get_audit_by_name(self, context, audit_name, eager=False):
"""Return an audit.
:param context: The security context
:param audit_name: The name of an audit.
:param eager: If True, also loads One-to-X data (Default: False)
:returns: An audit.
:raises: :py:class:`~.AuditNotFound`
"""
@abc.abstractmethod @abc.abstractmethod
def destroy_audit(self, audit_id): def destroy_audit(self, audit_id):
"""Destroy an audit and all associated action plans. """Destroy an audit and all associated action plans.

View File

@@ -0,0 +1,22 @@
"""add name for audit
Revision ID: 3cfc94cecf4e
Revises: d098df6021e2
Create Date: 2017-07-19 15:44:57.661099
"""
# revision identifiers, used by Alembic.
revision = '3cfc94cecf4e'
down_revision = 'd09a5945e4a0'
from alembic import op
import sqlalchemy as sa
def upgrade():
op.add_column('audits', sa.Column('name', sa.String(length=63), nullable=True))
def downgrade():
op.drop_column('audits', 'name')

View File

@@ -659,6 +659,14 @@ class Connection(api.BaseConnection):
if not values.get('uuid'): if not values.get('uuid'):
values['uuid'] = utils.generate_uuid() values['uuid'] = utils.generate_uuid()
query = model_query(models.Audit)
query = query.filter_by(name=values.get('name'),
deleted_at=None)
if len(query.all()) > 0:
raise exception.AuditAlreadyExists(
audit=values['name'])
if values.get('state') is None: if values.get('state') is None:
values['state'] = objects.audit.State.PENDING values['state'] = objects.audit.State.PENDING
@@ -668,7 +676,7 @@ class Connection(api.BaseConnection):
try: try:
audit = self._create(models.Audit, values) audit = self._create(models.Audit, values)
except db_exc.DBDuplicateEntry: except db_exc.DBDuplicateEntry:
raise exception.AuditAlreadyExists(uuid=values['uuid']) raise exception.AuditAlreadyExists(audit=values['uuid'])
return audit return audit
def _get_audit(self, context, fieldname, value, eager): def _get_audit(self, context, fieldname, value, eager):
@@ -686,6 +694,10 @@ class Connection(api.BaseConnection):
return self._get_audit( return self._get_audit(
context, fieldname="uuid", value=audit_uuid, eager=eager) context, fieldname="uuid", value=audit_uuid, eager=eager)
def get_audit_by_name(self, context, audit_name, eager=False):
return self._get_audit(
context, fieldname="name", value=audit_name, eager=eager)
def destroy_audit(self, audit_id): def destroy_audit(self, audit_id):
def is_audit_referenced(session, audit_id): def is_audit_referenced(session, audit_id):
"""Checks whether the audit is referenced by action_plan(s).""" """Checks whether the audit is referenced by action_plan(s)."""

View File

@@ -166,10 +166,12 @@ class Audit(Base):
__tablename__ = 'audits' __tablename__ = 'audits'
__table_args__ = ( __table_args__ = (
UniqueConstraint('uuid', name='uniq_audits0uuid'), UniqueConstraint('uuid', name='uniq_audits0uuid'),
UniqueConstraint('name', 'deleted', name='uniq_audits0name'),
table_args() table_args()
) )
id = Column(Integer, primary_key=True, autoincrement=True) id = Column(Integer, primary_key=True, autoincrement=True)
uuid = Column(String(36)) uuid = Column(String(36))
name = Column(String(63), nullable=True)
audit_type = Column(String(20)) audit_type = Column(String(20))
state = Column(String(20), nullable=True) state = Column(String(20), nullable=True)
parameters = Column(JSONEncodedDict, nullable=True) parameters = Column(JSONEncodedDict, nullable=True)
@@ -197,7 +199,7 @@ class ActionPlan(Base):
audit_id = Column(Integer, ForeignKey('audits.id'), nullable=False) audit_id = Column(Integer, ForeignKey('audits.id'), nullable=False)
strategy_id = Column(Integer, ForeignKey('strategies.id'), nullable=False) strategy_id = Column(Integer, ForeignKey('strategies.id'), nullable=False)
state = Column(String(20), nullable=True) state = Column(String(20), nullable=True)
global_efficacy = Column(JSONEncodedDict, nullable=True) global_efficacy = Column(JSONEncodedList, nullable=True)
audit = orm.relationship(Audit, foreign_keys=audit_id, lazy=None) audit = orm.relationship(Audit, foreign_keys=audit_id, lazy=None)
strategy = orm.relationship(Strategy, foreign_keys=strategy_id, lazy=None) strategy = orm.relationship(Strategy, foreign_keys=strategy_id, lazy=None)

View File

@@ -131,6 +131,9 @@ class ContinuousAuditHandler(base.AuditHandler):
scheduler_job_args = [ scheduler_job_args = [
job.args for job in self.scheduler.get_jobs() job.args for job in self.scheduler.get_jobs()
if job.name == 'execute_audit'] if job.name == 'execute_audit']
for args in scheduler_job_args:
if self._is_audit_inactive(args[0]):
scheduler_job_args.remove(args)
for audit in audits: for audit in audits:
# if audit is not presented in scheduled audits yet. # if audit is not presented in scheduled audits yet.
if audit.uuid not in [arg[0].uuid for arg in scheduler_job_args]: if audit.uuid not in [arg[0].uuid for arg in scheduler_job_args]:

View File

@@ -57,7 +57,8 @@ class EfficacySpecification(object):
efficacy indicators related to this spec efficacy indicators related to this spec
:type indicators_map: :py:class:`~.IndicatorsMap` instance :type indicators_map: :py:class:`~.IndicatorsMap` instance
:raises: NotImplementedError :raises: NotImplementedError
:returns: :py:class:`~.Indicator` instance :returns: :py:class:`~.Indicator` instance list, each instance specify
global efficacy for different openstack resource.
""" """
raise NotImplementedError() raise NotImplementedError()

View File

@@ -40,14 +40,16 @@ class ServerConsolidation(base.EfficacySpecification):
def get_global_efficacy_indicator(self, indicators_map=None): def get_global_efficacy_indicator(self, indicators_map=None):
value = 0 value = 0
global_efficacy = []
if indicators_map and indicators_map.compute_nodes_count > 0: if indicators_map and indicators_map.compute_nodes_count > 0:
value = (float(indicators_map.released_compute_nodes_count) / value = (float(indicators_map.released_compute_nodes_count) /
float(indicators_map.compute_nodes_count)) * 100 float(indicators_map.compute_nodes_count)) * 100
global_efficacy.append(efficacy.Indicator(
return efficacy.Indicator(
name="released_nodes_ratio", name="released_nodes_ratio",
description=_("Ratio of released compute nodes divided by the " description=_("Ratio of released compute nodes divided by the "
"total number of enabled compute nodes."), "total number of enabled compute nodes."),
unit='%', unit='%',
value=value, value=value,
) ))
return global_efficacy

View File

@@ -172,7 +172,7 @@ class NoisyNeighborOptimization(base.Goal):
"""NoisyNeighborOptimization """NoisyNeighborOptimization
This goal is used to identify and migrate a Noisy Neighbor - This goal is used to identify and migrate a Noisy Neighbor -
a low priority VM that negatively affects peformance of a high priority VM a low priority VM that negatively affects performance of a high priority VM
in terms of IPC by over utilizing Last Level Cache. in terms of IPC by over utilizing Last Level Cache.
""" """

View File

@@ -128,6 +128,7 @@ class BaseClusterDataModelCollector(loadable.LoadableSingleton):
self.osc = osc if osc else clients.OpenStackClients() self.osc = osc if osc else clients.OpenStackClients()
self._cluster_data_model = None self._cluster_data_model = None
self.lock = threading.RLock() self.lock = threading.RLock()
self._audit_scope_handler = None
@property @property
def cluster_data_model(self): def cluster_data_model(self):
@@ -156,6 +157,11 @@ class BaseClusterDataModelCollector(loadable.LoadableSingleton):
def set_cluster_data_model_as_stale(self): def set_cluster_data_model_as_stale(self):
self.cluster_data_model = self.STALE_MODEL self.cluster_data_model = self.STALE_MODEL
@abc.abstractmethod
def get_audit_scope_handler(self, audit_scope):
"""Get audit scope handler"""
raise NotImplementedError()
@abc.abstractmethod @abc.abstractmethod
def execute(self): def execute(self):
"""Build a cluster data model""" """Build a cluster data model"""

View File

@@ -54,6 +54,9 @@ class CinderClusterDataModelCollector(base.BaseClusterDataModelCollector):
cinder.VolumeResizeEnd(self) cinder.VolumeResizeEnd(self)
] ]
def get_audit_scope_handler(self, audit_scope):
return None
def execute(self): def execute(self):
"""Build the storage cluster data model""" """Build the storage cluster data model"""
LOG.debug("Building latest Cinder cluster data model") LOG.debug("Building latest Cinder cluster data model")

View File

@@ -60,4 +60,5 @@ class CollectorManager(object):
:returns: cluster data model collector plugin :returns: cluster data model collector plugin
:rtype: :py:class:`~.BaseClusterDataModelCollector` :rtype: :py:class:`~.BaseClusterDataModelCollector`
""" """
return self.collector_loader.load(name, osc=osc) return self.collector_loader.load(
name, osc=osc)

View File

@@ -21,6 +21,7 @@ from watcher.decision_engine.model.collector import base
from watcher.decision_engine.model import element from watcher.decision_engine.model import element
from watcher.decision_engine.model import model_root from watcher.decision_engine.model import model_root
from watcher.decision_engine.model.notification import nova from watcher.decision_engine.model.notification import nova
from watcher.decision_engine.scope import compute as compute_scope
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
@@ -32,6 +33,109 @@ class NovaClusterDataModelCollector(base.BaseClusterDataModelCollector):
representation of the resources exposed by the compute service. representation of the resources exposed by the compute service.
""" """
HOST_AGGREGATES = "#/items/properties/compute/host_aggregates/"
SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "array",
"items": {
"type": "object",
"properties": {
"host_aggregates": {
"type": "array",
"items": {
"anyOf": [
{"$ref": HOST_AGGREGATES + "id"},
{"$ref": HOST_AGGREGATES + "name"},
]
}
},
"availability_zones": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
}
},
"additionalProperties": False
}
},
"exclude": {
"type": "array",
"items": {
"type": "object",
"properties": {
"instances": {
"type": "array",
"items": {
"type": "object",
"properties": {
"uuid": {
"type": "string"
}
},
"additionalProperties": False
}
},
"compute_nodes": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
}
},
"additionalProperties": False
}
},
"host_aggregates": {
"type": "array",
"items": {
"anyOf": [
{"$ref": HOST_AGGREGATES + "id"},
{"$ref": HOST_AGGREGATES + "name"},
]
}
},
"instance_metadata": {
"type": "array",
"items": {
"type": "object"
}
}
},
"additionalProperties": False
}
}
},
"additionalProperties": False
},
"host_aggregates": {
"id": {
"properties": {
"id": {
"oneOf": [
{"type": "integer"},
{"enum": ["*"]}
]
}
},
"additionalProperties": False
},
"name": {
"properties": {
"name": {
"type": "string"
}
},
"additionalProperties": False
}
},
"additionalProperties": False
}
def __init__(self, config, osc=None): def __init__(self, config, osc=None):
super(NovaClusterDataModelCollector, self).__init__(config, osc) super(NovaClusterDataModelCollector, self).__init__(config, osc)
@@ -53,8 +157,14 @@ class NovaClusterDataModelCollector(base.BaseClusterDataModelCollector):
nova.LegacyInstanceUpdated(self), nova.LegacyInstanceUpdated(self),
nova.LegacyInstanceDeletedEnd(self), nova.LegacyInstanceDeletedEnd(self),
nova.LegacyLiveMigratedEnd(self), nova.LegacyLiveMigratedEnd(self),
nova.LegacyInstanceResizeConfirmEnd(self),
] ]
def get_audit_scope_handler(self, audit_scope):
self._audit_scope_handler = compute_scope.ComputeScope(
audit_scope, self.config)
return self._audit_scope_handler
def execute(self): def execute(self):
"""Build the compute cluster data model""" """Build the compute cluster data model"""
LOG.debug("Building latest Nova cluster data model") LOG.debug("Building latest Nova cluster data model")
@@ -139,7 +249,8 @@ class ModelBuilder(object):
"disk_capacity": node.local_gb, "disk_capacity": node.local_gb,
"vcpus": node.vcpus, "vcpus": node.vcpus,
"state": node.state, "state": node.state,
"status": node.status} "status": node.status,
"disabled_reason": compute_service.disabled_reason}
compute_node = element.ComputeNode(**node_attributes) compute_node = element.ComputeNode(**node_attributes)
# compute_node = self._build_node("physical", "compute", "hypervisor", # compute_node = self._build_node("physical", "compute", "hypervisor",

View File

@@ -36,8 +36,8 @@ class ComputeNode(compute_resource.ComputeResource):
"id": wfields.StringField(), "id": wfields.StringField(),
"hostname": wfields.StringField(), "hostname": wfields.StringField(),
"status": wfields.StringField(default=ServiceState.ENABLED.value), "status": wfields.StringField(default=ServiceState.ENABLED.value),
"disabled_reason": wfields.StringField(nullable=True),
"state": wfields.StringField(default=ServiceState.ONLINE.value), "state": wfields.StringField(default=ServiceState.ONLINE.value),
"memory": wfields.NonNegativeIntegerField(), "memory": wfields.NonNegativeIntegerField(),
"disk": wfields.IntegerField(), "disk": wfields.IntegerField(),
"disk_capacity": wfields.NonNegativeIntegerField(), "disk_capacity": wfields.NonNegativeIntegerField(),

View File

@@ -122,11 +122,15 @@ class NovaNotification(base.NotificationEndpoint):
node_status = ( node_status = (
element.ServiceState.DISABLED.value element.ServiceState.DISABLED.value
if node_data['disabled'] else element.ServiceState.ENABLED.value) if node_data['disabled'] else element.ServiceState.ENABLED.value)
disabled_reason = (
node_data['disabled_reason']
if node_data['disabled'] else None)
node.update({ node.update({
'hostname': node_data['host'], 'hostname': node_data['host'],
'state': node_state, 'state': node_state,
'status': node_status, 'status': node_status,
'disabled_reason': disabled_reason,
}) })
def create_compute_node(self, node_hostname): def create_compute_node(self, node_hostname):
@@ -466,3 +470,30 @@ class LegacyLiveMigratedEnd(UnversionedNotificationEndpoint):
instance = self.get_or_create_instance(instance_uuid, node_uuid) instance = self.get_or_create_instance(instance_uuid, node_uuid)
self.legacy_update_instance(instance, payload) self.legacy_update_instance(instance, payload)
class LegacyInstanceResizeConfirmEnd(UnversionedNotificationEndpoint):
@property
def filter_rule(self):
"""Nova compute.instance.resize.confirm.end filter"""
return filtering.NotificationFilter(
publisher_id=self.publisher_id_regex,
event_type='compute.instance.resize.confirm.end',
)
def info(self, ctxt, publisher_id, event_type, payload, metadata):
ctxt.request_id = metadata['message_id']
ctxt.project_domain = event_type
LOG.info("Event '%(event)s' received from %(publisher)s "
"with metadata %(metadata)s" %
dict(event=event_type,
publisher=publisher_id,
metadata=metadata))
LOG.debug(payload)
instance_uuid = payload['instance_id']
node_uuid = payload.get('node')
instance = self.get_or_create_instance(instance_uuid, node_uuid)
self.legacy_update_instance(instance, payload)

View File

@@ -24,113 +24,11 @@ from watcher.decision_engine.scope import base
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
class DefaultScope(base.BaseScope): class ComputeScope(base.BaseScope):
"""Default Audit Scope Handler""" """Compute Audit Scope Handler"""
DEFAULT_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "array",
"items": {
"type": "object",
"properties": {
"host_aggregates": {
"type": "array",
"items": {
"anyOf": [
{"$ref": "#/host_aggregates/id"},
{"$ref": "#/host_aggregates/name"},
]
}
},
"availability_zones": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
}
},
"additionalProperties": False
}
},
"exclude": {
"type": "array",
"items": {
"type": "object",
"properties": {
"instances": {
"type": "array",
"items": {
"type": "object",
"properties": {
"uuid": {
"type": "string"
}
},
"additionalProperties": False
}
},
"compute_nodes": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
}
},
"additionalProperties": False
}
},
"host_aggregates": {
"type": "array",
"items": {
"anyOf": [
{"$ref": "#/host_aggregates/id"},
{"$ref": "#/host_aggregates/name"},
]
}
},
"instance_metadata": {
"type": "array",
"items": {
"type": "object"
}
}
},
"additionalProperties": False
}
}
},
"additionalProperties": False
},
"host_aggregates": {
"id": {
"properties": {
"id": {
"oneOf": [
{"type": "integer"},
{"enum": ["*"]}
]
}
},
"additionalProperties": False
},
"name": {
"properties": {
"name": {
"type": "string"
}
},
"additionalProperties": False
}
},
"additionalProperties": False
}
def __init__(self, scope, config, osc=None): def __init__(self, scope, config, osc=None):
super(DefaultScope, self).__init__(scope, config) super(ComputeScope, self).__init__(scope, config)
self._osc = osc self._osc = osc
self.wrapper = nova_helper.NovaHelper(osc=self._osc) self.wrapper = nova_helper.NovaHelper(osc=self._osc)

View File

@@ -16,13 +16,13 @@
import numbers import numbers
from oslo_log import log as logging from oslo_log import log
from watcher._i18n import _ from watcher._i18n import _
from watcher.common import exception from watcher.common import exception
from watcher.common import utils from watcher.common import utils
LOG = logging.getLogger(__name__) LOG = log.getLogger(__name__)
class IndicatorsMap(utils.Struct): class IndicatorsMap(utils.Struct):
@@ -62,7 +62,7 @@ class Efficacy(object):
self.indicators = [] self.indicators = []
# Used to compute the global efficacy # Used to compute the global efficacy
self._indicators_mapping = IndicatorsMap() self._indicators_mapping = IndicatorsMap()
self.global_efficacy = None self.global_efficacy = []
def set_efficacy_indicators(self, **indicators_map): def set_efficacy_indicators(self, **indicators_map):
"""Set the efficacy indicators """Set the efficacy indicators

View File

@@ -48,7 +48,6 @@ from watcher.common.loader import loadable
from watcher.common import utils from watcher.common import utils
from watcher.decision_engine.loading import default as loading from watcher.decision_engine.loading import default as loading
from watcher.decision_engine.model.collector import manager from watcher.decision_engine.model.collector import manager
from watcher.decision_engine.scope import default as default_scope
from watcher.decision_engine.solution import default from watcher.decision_engine.solution import default
from watcher.decision_engine.strategy.common import level from watcher.decision_engine.strategy.common import level
@@ -85,7 +84,6 @@ class BaseStrategy(loadable.Loadable):
self._storage_model = None self._storage_model = None
self._input_parameters = utils.Struct() self._input_parameters = utils.Struct()
self._audit_scope = None self._audit_scope = None
self._audit_scope_handler = None
@classmethod @classmethod
@abc.abstractmethod @abc.abstractmethod
@@ -182,7 +180,9 @@ class BaseStrategy(loadable.Loadable):
if self._compute_model is None: if self._compute_model is None:
collector = self.collector_manager.get_cluster_model_collector( collector = self.collector_manager.get_cluster_model_collector(
'compute', osc=self.osc) 'compute', osc=self.osc)
self._compute_model = self.audit_scope_handler.get_scoped_model( audit_scope_handler = collector.get_audit_scope_handler(
audit_scope=self.audit_scope)
self._compute_model = audit_scope_handler.get_scoped_model(
collector.get_latest_cluster_data_model()) collector.get_latest_cluster_data_model())
if not self._compute_model: if not self._compute_model:
@@ -253,13 +253,6 @@ class BaseStrategy(loadable.Loadable):
def audit_scope(self, s): def audit_scope(self, s):
self._audit_scope = s self._audit_scope = s
@property
def audit_scope_handler(self):
if not self._audit_scope_handler:
self._audit_scope_handler = default_scope.DefaultScope(
self.audit_scope, self.config)
return self._audit_scope_handler
@property @property
def name(self): def name(self):
return self._name return self._name
@@ -331,6 +324,8 @@ class UnclassifiedStrategy(BaseStrategy):
@six.add_metaclass(abc.ABCMeta) @six.add_metaclass(abc.ABCMeta)
class ServerConsolidationBaseStrategy(BaseStrategy): class ServerConsolidationBaseStrategy(BaseStrategy):
REASON_FOR_DISABLE = 'watcher_disabled'
@classmethod @classmethod
def get_goal_name(cls): def get_goal_name(cls):
return "server_consolidation" return "server_consolidation"

View File

@@ -158,7 +158,7 @@ class BasicConsolidation(base.ServerConsolidationBaseStrategy):
cfg.StrOpt( cfg.StrOpt(
"datasource", "datasource",
help="Data source to use in order to query the needed metrics", help="Data source to use in order to query the needed metrics",
default="ceilometer", default="gnocchi",
choices=["ceilometer", "monasca", "gnocchi"]), choices=["ceilometer", "monasca", "gnocchi"]),
cfg.BoolOpt( cfg.BoolOpt(
"check_optimize_metadata", "check_optimize_metadata",
@@ -415,8 +415,9 @@ class BasicConsolidation(base.ServerConsolidationBaseStrategy):
return self.calculate_weight(instance, total_cores_used, 0, 0) return self.calculate_weight(instance, total_cores_used, 0, 0)
def add_change_service_state(self, resource_id, state): def add_action_disable_node(self, resource_id):
parameters = {'state': state} parameters = {'state': element.ServiceState.DISABLED.value,
'disabled_reason': self.REASON_FOR_DISABLE}
self.solution.add_action(action_type=self.CHANGE_NOVA_SERVICE_STATE, self.solution.add_action(action_type=self.CHANGE_NOVA_SERVICE_STATE,
resource_id=resource_id, resource_id=resource_id,
input_parameters=parameters) input_parameters=parameters)
@@ -472,9 +473,7 @@ class BasicConsolidation(base.ServerConsolidationBaseStrategy):
mig_destination_node.uuid) mig_destination_node.uuid)
if len(self.compute_model.get_node_instances(mig_source_node)) == 0: if len(self.compute_model.get_node_instances(mig_source_node)) == 0:
self.add_change_service_state(mig_source_node. self.add_action_disable_node(mig_source_node.uuid)
uuid,
element.ServiceState.DISABLED.value)
self.number_of_released_nodes += 1 self.number_of_released_nodes += 1
def calculate_num_migrations(self, sorted_instances, node_to_release, def calculate_num_migrations(self, sorted_instances, node_to_release,

View File

@@ -71,8 +71,8 @@ class OutletTempControl(base.ThermalOptimizationBaseStrategy):
*Spec URL* *Spec URL*
https://github.com/openstack/watcher-specs/blob/master/specs/mitaka/approved/outlet-temperature-based-strategy.rst https://github.com/openstack/watcher-specs/blob/master/specs/mitaka/implemented/outlet-temperature-based-strategy.rst
""" # noqa """
# The meter to report outlet temperature in ceilometer # The meter to report outlet temperature in ceilometer
MIGRATION = "migrate" MIGRATION = "migrate"
@@ -167,7 +167,7 @@ class OutletTempControl(base.ThermalOptimizationBaseStrategy):
cfg.StrOpt( cfg.StrOpt(
"datasource", "datasource",
help="Data source to use in order to query the needed metrics", help="Data source to use in order to query the needed metrics",
default="ceilometer", default="gnocchi",
choices=["ceilometer", "gnocchi"]) choices=["ceilometer", "gnocchi"])
] ]

View File

@@ -210,7 +210,7 @@ class UniformAirflow(base.BaseStrategy):
cfg.StrOpt( cfg.StrOpt(
"datasource", "datasource",
help="Data source to use in order to query the needed metrics", help="Data source to use in order to query the needed metrics",
default="ceilometer", default="gnocchi",
choices=["ceilometer", "gnocchi"]) choices=["ceilometer", "gnocchi"])
] ]

View File

@@ -165,7 +165,7 @@ class VMWorkloadConsolidation(base.ServerConsolidationBaseStrategy):
cfg.StrOpt( cfg.StrOpt(
"datasource", "datasource",
help="Data source to use in order to query the needed metrics", help="Data source to use in order to query the needed metrics",
default="ceilometer", default="gnocchi",
choices=["ceilometer", "gnocchi"]) choices=["ceilometer", "gnocchi"])
] ]
@@ -228,7 +228,8 @@ class VMWorkloadConsolidation(base.ServerConsolidationBaseStrategy):
:param node: node object :param node: node object
:return: None :return: None
""" """
params = {'state': element.ServiceState.DISABLED.value} params = {'state': element.ServiceState.DISABLED.value,
'disabled_reason': self.REASON_FOR_DISABLE}
self.solution.add_action( self.solution.add_action(
action_type=self.CHANGE_NOVA_SERVICE_STATE, action_type=self.CHANGE_NOVA_SERVICE_STATE,
resource_id=node.uuid, resource_id=node.uuid,
@@ -244,7 +245,8 @@ class VMWorkloadConsolidation(base.ServerConsolidationBaseStrategy):
:return: None :return: None
""" """
instance_state_str = self.get_instance_state_str(instance) instance_state_str = self.get_instance_state_str(instance)
if instance_state_str != element.InstanceState.ACTIVE.value: if instance_state_str not in (element.InstanceState.ACTIVE.value,
element.InstanceState.PAUSED.value):
# Watcher currently only supports live VM migration and block live # Watcher currently only supports live VM migration and block live
# VM migration which both requires migrated VM to be active. # VM migration which both requires migrated VM to be active.
# When supported, the cold migration may be used as a fallback # When supported, the cold migration may be used as a fallback

View File

@@ -187,7 +187,7 @@ class WorkloadBalance(base.WorkloadStabilizationBaseStrategy):
cfg.StrOpt( cfg.StrOpt(
"datasource", "datasource",
help="Data source to use in order to query the needed metrics", help="Data source to use in order to query the needed metrics",
default="ceilometer", default="gnocchi",
choices=["ceilometer", "gnocchi"]) choices=["ceilometer", "gnocchi"])
] ]

View File

@@ -172,7 +172,7 @@ class WorkloadStabilization(base.WorkloadStabilizationBaseStrategy):
cfg.StrOpt( cfg.StrOpt(
"datasource", "datasource",
help="Data source to use in order to query the needed metrics", help="Data source to use in order to query the needed metrics",
default="ceilometer", default="gnocchi",
choices=["ceilometer", "gnocchi"]) choices=["ceilometer", "gnocchi"])
] ]

View File

@@ -45,12 +45,13 @@ class TerseActionPlanPayload(notificationbase.NotificationPayloadBase):
} }
# Version 1.0: Initial version # Version 1.0: Initial version
VERSION = '1.0' # Version 1.1: Changed 'global_efficacy' type Dictionary to List
VERSION = '1.1'
fields = { fields = {
'uuid': wfields.UUIDField(), 'uuid': wfields.UUIDField(),
'state': wfields.StringField(), 'state': wfields.StringField(),
'global_efficacy': wfields.FlexibleDictField(nullable=True), 'global_efficacy': wfields.FlexibleListOfDictField(nullable=True),
'audit_uuid': wfields.UUIDField(), 'audit_uuid': wfields.UUIDField(),
'strategy_uuid': wfields.UUIDField(nullable=True), 'strategy_uuid': wfields.UUIDField(nullable=True),
@@ -80,7 +81,8 @@ class ActionPlanPayload(TerseActionPlanPayload):
} }
# Version 1.0: Initial version # Version 1.0: Initial version
VERSION = '1.0' # Vesrsion 1.1: changed global_efficacy type
VERSION = '1.1'
fields = { fields = {
'audit': wfields.ObjectField('TerseAuditPayload'), 'audit': wfields.ObjectField('TerseAuditPayload'),
@@ -112,7 +114,8 @@ class ActionPlanStateUpdatePayload(notificationbase.NotificationPayloadBase):
@base.WatcherObjectRegistry.register_notification @base.WatcherObjectRegistry.register_notification
class ActionPlanCreatePayload(ActionPlanPayload): class ActionPlanCreatePayload(ActionPlanPayload):
# Version 1.0: Initial version # Version 1.0: Initial version
VERSION = '1.0' # Version 1.1: Changed global_efficacy_type
VERSION = '1.1'
fields = {} fields = {}
def __init__(self, action_plan, audit, strategy): def __init__(self, action_plan, audit, strategy):
@@ -125,7 +128,8 @@ class ActionPlanCreatePayload(ActionPlanPayload):
@base.WatcherObjectRegistry.register_notification @base.WatcherObjectRegistry.register_notification
class ActionPlanUpdatePayload(ActionPlanPayload): class ActionPlanUpdatePayload(ActionPlanPayload):
# Version 1.0: Initial version # Version 1.0: Initial version
VERSION = '1.0' # Version 1.1: Changed global_efficacy_type
VERSION = '1.1'
fields = { fields = {
'state_update': wfields.ObjectField('ActionPlanStateUpdatePayload'), 'state_update': wfields.ObjectField('ActionPlanStateUpdatePayload'),
} }
@@ -141,7 +145,8 @@ class ActionPlanUpdatePayload(ActionPlanPayload):
@base.WatcherObjectRegistry.register_notification @base.WatcherObjectRegistry.register_notification
class ActionPlanActionPayload(ActionPlanPayload): class ActionPlanActionPayload(ActionPlanPayload):
# Version 1.0: Initial version # Version 1.0: Initial version
VERSION = '1.0' # Version 1.1: Changed global_efficacy_type
VERSION = '1.1'
fields = { fields = {
'fault': wfields.ObjectField('ExceptionPayload', nullable=True), 'fault': wfields.ObjectField('ExceptionPayload', nullable=True),
} }
@@ -157,7 +162,8 @@ class ActionPlanActionPayload(ActionPlanPayload):
@base.WatcherObjectRegistry.register_notification @base.WatcherObjectRegistry.register_notification
class ActionPlanDeletePayload(ActionPlanPayload): class ActionPlanDeletePayload(ActionPlanPayload):
# Version 1.0: Initial version # Version 1.0: Initial version
VERSION = '1.0' # Version 1.1: Changed global_efficacy_type
VERSION = '1.1'
fields = {} fields = {}
def __init__(self, action_plan, audit, strategy): def __init__(self, action_plan, audit, strategy):
@@ -170,7 +176,8 @@ class ActionPlanDeletePayload(ActionPlanPayload):
@base.WatcherObjectRegistry.register_notification @base.WatcherObjectRegistry.register_notification
class ActionPlanCancelPayload(ActionPlanPayload): class ActionPlanCancelPayload(ActionPlanPayload):
# Version 1.0: Initial version # Version 1.0: Initial version
VERSION = '1.0' # Version 1.1: Changed global_efficacy_type
VERSION = '1.1'
fields = { fields = {
'fault': wfields.ObjectField('ExceptionPayload', nullable=True), 'fault': wfields.ObjectField('ExceptionPayload', nullable=True),
} }

View File

@@ -33,7 +33,7 @@ CONF = cfg.CONF
class TerseAuditPayload(notificationbase.NotificationPayloadBase): class TerseAuditPayload(notificationbase.NotificationPayloadBase):
SCHEMA = { SCHEMA = {
'uuid': ('audit', 'uuid'), 'uuid': ('audit', 'uuid'),
'name': ('audit', 'name'),
'audit_type': ('audit', 'audit_type'), 'audit_type': ('audit', 'audit_type'),
'state': ('audit', 'state'), 'state': ('audit', 'state'),
'parameters': ('audit', 'parameters'), 'parameters': ('audit', 'parameters'),
@@ -51,10 +51,12 @@ class TerseAuditPayload(notificationbase.NotificationPayloadBase):
# Version 1.1: Added 'auto_trigger' boolean field, # Version 1.1: Added 'auto_trigger' boolean field,
# Added 'next_run_time' DateTime field, # Added 'next_run_time' DateTime field,
# 'interval' type has been changed from Integer to String # 'interval' type has been changed from Integer to String
VERSION = '1.1' # Version 1.2: Added 'name' string field
VERSION = '1.2'
fields = { fields = {
'uuid': wfields.UUIDField(), 'uuid': wfields.UUIDField(),
'name': wfields.StringField(),
'audit_type': wfields.StringField(), 'audit_type': wfields.StringField(),
'state': wfields.StringField(), 'state': wfields.StringField(),
'parameters': wfields.FlexibleDictField(nullable=True), 'parameters': wfields.FlexibleDictField(nullable=True),
@@ -80,7 +82,7 @@ class TerseAuditPayload(notificationbase.NotificationPayloadBase):
class AuditPayload(TerseAuditPayload): class AuditPayload(TerseAuditPayload):
SCHEMA = { SCHEMA = {
'uuid': ('audit', 'uuid'), 'uuid': ('audit', 'uuid'),
'name': ('audit', 'name'),
'audit_type': ('audit', 'audit_type'), 'audit_type': ('audit', 'audit_type'),
'state': ('audit', 'state'), 'state': ('audit', 'state'),
'parameters': ('audit', 'parameters'), 'parameters': ('audit', 'parameters'),
@@ -97,7 +99,8 @@ class AuditPayload(TerseAuditPayload):
# Version 1.0: Initial version # Version 1.0: Initial version
# Version 1.1: Added 'auto_trigger' field, # Version 1.1: Added 'auto_trigger' field,
# Added 'next_run_time' field # Added 'next_run_time' field
VERSION = '1.1' # Version 1.2: Added 'name' string field
VERSION = '1.2'
fields = { fields = {
'goal': wfields.ObjectField('GoalPayload'), 'goal': wfields.ObjectField('GoalPayload'),

View File

@@ -105,7 +105,8 @@ class ActionPlan(base.WatcherPersistentObject, base.WatcherObject,
# Version 1.1: Added 'audit' and 'strategy' object field # Version 1.1: Added 'audit' and 'strategy' object field
# Version 1.2: audit_id is not nullable anymore # Version 1.2: audit_id is not nullable anymore
# Version 2.0: Removed 'first_action_id' object field # Version 2.0: Removed 'first_action_id' object field
VERSION = '2.0' # Version 2.1: Changed global_efficacy type
VERSION = '2.1'
dbapi = db_api.get_instance() dbapi = db_api.get_instance()
@@ -115,7 +116,7 @@ class ActionPlan(base.WatcherPersistentObject, base.WatcherObject,
'audit_id': wfields.IntegerField(), 'audit_id': wfields.IntegerField(),
'strategy_id': wfields.IntegerField(), 'strategy_id': wfields.IntegerField(),
'state': wfields.StringField(nullable=True), 'state': wfields.StringField(nullable=True),
'global_efficacy': wfields.FlexibleDictField(nullable=True), 'global_efficacy': wfields.FlexibleListOfDictField(nullable=True),
'audit': wfields.ObjectField('Audit', nullable=True), 'audit': wfields.ObjectField('Audit', nullable=True),
'strategy': wfields.ObjectField('Strategy', nullable=True), 'strategy': wfields.ObjectField('Strategy', nullable=True),

View File

@@ -86,13 +86,15 @@ class Audit(base.WatcherPersistentObject, base.WatcherObject,
# Version 1.2: Added 'auto_trigger' boolean field # Version 1.2: Added 'auto_trigger' boolean field
# Version 1.3: Added 'next_run_time' DateTime field, # Version 1.3: Added 'next_run_time' DateTime field,
# 'interval' type has been changed from Integer to String # 'interval' type has been changed from Integer to String
VERSION = '1.3' # Version 1.4: Added 'name' string field
VERSION = '1.4'
dbapi = db_api.get_instance() dbapi = db_api.get_instance()
fields = { fields = {
'id': wfields.IntegerField(), 'id': wfields.IntegerField(),
'uuid': wfields.UUIDField(), 'uuid': wfields.UUIDField(),
'name': wfields.StringField(),
'audit_type': wfields.StringField(), 'audit_type': wfields.StringField(),
'state': wfields.StringField(), 'state': wfields.StringField(),
'parameters': wfields.FlexibleDictField(nullable=True), 'parameters': wfields.FlexibleDictField(nullable=True),
@@ -204,6 +206,25 @@ class Audit(base.WatcherPersistentObject, base.WatcherObject,
audit = cls._from_db_object(cls(context), db_audit, eager=eager) audit = cls._from_db_object(cls(context), db_audit, eager=eager)
return audit return audit
@base.remotable_classmethod
def get_by_name(cls, context, name, eager=False):
"""Find an audit based on name and return a :class:`Audit` object.
:param context: Security context. NOTE: This should only
be used internally by the indirection_api.
Unfortunately, RPC requires context as the first
argument, even though we don't use it.
A context should be set when instantiating the
object, e.g.: Audit(context)
:param name: the name of an audit.
:param eager: Load object fields if True (Default: False)
:returns: a :class:`Audit` object.
"""
db_audit = cls.dbapi.get_audit_by_name(context, name, eager=eager)
audit = cls._from_db_object(cls(context), db_audit, eager=eager)
return audit
@base.remotable_classmethod @base.remotable_classmethod
def list(cls, context, limit=None, marker=None, filters=None, def list(cls, context, limit=None, marker=None, filters=None,
sort_key=None, sort_dir=None, eager=False): sort_key=None, sort_dir=None, eager=False):

View File

@@ -169,7 +169,8 @@ class TestListAction(api_base.FunctionalTest):
action_list.append(action.uuid) action_list.append(action.uuid)
audit2 = obj_utils.create_test_audit( audit2 = obj_utils.create_test_audit(
self.context, id=2, uuid=utils.generate_uuid()) self.context, id=2, uuid=utils.generate_uuid(),
name='My Audit {0}'.format(2))
action_plan_2 = obj_utils.create_test_action_plan( action_plan_2 = obj_utils.create_test_action_plan(
self.context, self.context,
uuid=utils.generate_uuid(), uuid=utils.generate_uuid(),

View File

@@ -128,12 +128,12 @@ class TestListActionPlan(api_base.FunctionalTest):
def test_many_with_soft_deleted_audit_uuid(self): def test_many_with_soft_deleted_audit_uuid(self):
action_plan_list = [] action_plan_list = []
audit1 = obj_utils.create_test_audit(self.context, audit1 = obj_utils.create_test_audit(
id=2, self.context, id=2,
uuid=utils.generate_uuid()) uuid=utils.generate_uuid(), name='My Audit {0}'.format(2))
audit2 = obj_utils.create_test_audit(self.context, audit2 = obj_utils.create_test_audit(
id=3, self.context, id=3,
uuid=utils.generate_uuid()) uuid=utils.generate_uuid(), name='My Audit {0}'.format(3))
for id_ in range(0, 2): for id_ in range(0, 2):
action_plan = obj_utils.create_test_action_plan( action_plan = obj_utils.create_test_action_plan(
@@ -163,9 +163,9 @@ class TestListActionPlan(api_base.FunctionalTest):
def test_many_with_audit_uuid(self): def test_many_with_audit_uuid(self):
action_plan_list = [] action_plan_list = []
audit = obj_utils.create_test_audit(self.context, audit = obj_utils.create_test_audit(
id=2, self.context, id=2,
uuid=utils.generate_uuid()) uuid=utils.generate_uuid(), name='My Audit {0}'.format(2))
for id_ in range(2, 5): for id_ in range(2, 5):
action_plan = obj_utils.create_test_action_plan( action_plan = obj_utils.create_test_action_plan(
self.context, id=id_, uuid=utils.generate_uuid(), self.context, id=id_, uuid=utils.generate_uuid(),
@@ -178,18 +178,18 @@ class TestListActionPlan(api_base.FunctionalTest):
def test_many_with_audit_uuid_filter(self): def test_many_with_audit_uuid_filter(self):
action_plan_list1 = [] action_plan_list1 = []
audit1 = obj_utils.create_test_audit(self.context, audit1 = obj_utils.create_test_audit(
id=2, self.context, id=2,
uuid=utils.generate_uuid()) uuid=utils.generate_uuid(), name='My Audit {0}'.format(2))
for id_ in range(2, 5): for id_ in range(2, 5):
action_plan = obj_utils.create_test_action_plan( action_plan = obj_utils.create_test_action_plan(
self.context, id=id_, uuid=utils.generate_uuid(), self.context, id=id_, uuid=utils.generate_uuid(),
audit_id=audit1.id) audit_id=audit1.id)
action_plan_list1.append(action_plan.uuid) action_plan_list1.append(action_plan.uuid)
audit2 = obj_utils.create_test_audit(self.context, audit2 = obj_utils.create_test_audit(
id=3, self.context, id=3,
uuid=utils.generate_uuid()) uuid=utils.generate_uuid(), name='My Audit {0}'.format(3))
action_plan_list2 = [] action_plan_list2 = []
for id_ in [5, 6, 7]: for id_ in [5, 6, 7]:
action_plan = obj_utils.create_test_action_plan( action_plan = obj_utils.create_test_action_plan(
@@ -237,9 +237,9 @@ class TestListActionPlan(api_base.FunctionalTest):
def test_many_with_sort_key_audit_uuid(self): def test_many_with_sort_key_audit_uuid(self):
audit_list = [] audit_list = []
for id_ in range(2, 5): for id_ in range(2, 5):
audit = obj_utils.create_test_audit(self.context, audit = obj_utils.create_test_audit(
id=id_, self.context, id=id_,
uuid=utils.generate_uuid()) uuid=utils.generate_uuid(), name='My Audit {0}'.format(id_))
obj_utils.create_test_action_plan( obj_utils.create_test_action_plan(
self.context, id=id_, uuid=utils.generate_uuid(), self.context, id=id_, uuid=utils.generate_uuid(),
audit_id=audit.id) audit_id=audit.id)

View File

@@ -512,7 +512,7 @@ class TestPost(FunctionalTestWithSetup):
self.assertEqual(test_time, return_created_at) self.assertEqual(test_time, return_created_at)
def test_create_audit_template_validation_with_aggregates(self): def test_create_audit_template_validation_with_aggregates(self):
scope = [{'host_aggregates': [{'id': '*'}]}, scope = [{'compute': [{'host_aggregates': [{'id': '*'}]},
{'availability_zones': [{'name': 'AZ1'}, {'availability_zones': [{'name': 'AZ1'},
{'name': 'AZ2'}]}, {'name': 'AZ2'}]},
{'exclude': [ {'exclude': [
@@ -525,6 +525,8 @@ class TestPost(FunctionalTestWithSetup):
{'host_aggregates': [{'id': '*'}]} {'host_aggregates': [{'id': '*'}]}
]} ]}
] ]
}
]
audit_template_dict = post_get_test_audit_template( audit_template_dict = post_get_test_audit_template(
goal=self.fake_goal1.uuid, goal=self.fake_goal1.uuid,
strategy=self.fake_strategy1.uuid, scope=scope) strategy=self.fake_strategy1.uuid, scope=scope)

View File

@@ -154,8 +154,9 @@ class TestListAudit(api_base.FunctionalTest):
def test_many(self): def test_many(self):
audit_list = [] audit_list = []
for id_ in range(5): for id_ in range(5):
audit = obj_utils.create_test_audit(self.context, id=id_, audit = obj_utils.create_test_audit(
uuid=utils.generate_uuid()) self.context, id=id_,
uuid=utils.generate_uuid(), name='My Audit {0}'.format(id_))
audit_list.append(audit.uuid) audit_list.append(audit.uuid)
response = self.get_json('/audits') response = self.get_json('/audits')
self.assertEqual(len(audit_list), len(response['audits'])) self.assertEqual(len(audit_list), len(response['audits']))
@@ -165,12 +166,14 @@ class TestListAudit(api_base.FunctionalTest):
def test_many_without_soft_deleted(self): def test_many_without_soft_deleted(self):
audit_list = [] audit_list = []
for id_ in [1, 2, 3]: for id_ in [1, 2, 3]:
audit = obj_utils.create_test_audit(self.context, id=id_, audit = obj_utils.create_test_audit(
uuid=utils.generate_uuid()) self.context, id=id_,
uuid=utils.generate_uuid(), name='My Audit {0}'.format(id_))
audit_list.append(audit.uuid) audit_list.append(audit.uuid)
for id_ in [4, 5]: for id_ in [4, 5]:
audit = obj_utils.create_test_audit(self.context, id=id_, audit = obj_utils.create_test_audit(
uuid=utils.generate_uuid()) self.context, id=id_,
uuid=utils.generate_uuid(), name='My Audit {0}'.format(id_))
audit.soft_delete() audit.soft_delete()
response = self.get_json('/audits') response = self.get_json('/audits')
self.assertEqual(3, len(response['audits'])) self.assertEqual(3, len(response['audits']))
@@ -180,12 +183,14 @@ class TestListAudit(api_base.FunctionalTest):
def test_many_with_soft_deleted(self): def test_many_with_soft_deleted(self):
audit_list = [] audit_list = []
for id_ in [1, 2, 3]: for id_ in [1, 2, 3]:
audit = obj_utils.create_test_audit(self.context, id=id_, audit = obj_utils.create_test_audit(
uuid=utils.generate_uuid()) self.context, id=id_,
uuid=utils.generate_uuid(), name='My Audit {0}'.format(id_))
audit_list.append(audit.uuid) audit_list.append(audit.uuid)
for id_ in [4, 5]: for id_ in [4, 5]:
audit = obj_utils.create_test_audit(self.context, id=id_, audit = obj_utils.create_test_audit(
uuid=utils.generate_uuid()) self.context, id=id_,
uuid=utils.generate_uuid(), name='My Audit {0}'.format(id_))
audit.soft_delete() audit.soft_delete()
audit_list.append(audit.uuid) audit_list.append(audit.uuid)
response = self.get_json('/audits', response = self.get_json('/audits',
@@ -203,7 +208,7 @@ class TestListAudit(api_base.FunctionalTest):
uuid=utils.generate_uuid()) uuid=utils.generate_uuid())
obj_utils.create_test_audit( obj_utils.create_test_audit(
self.context, id=id_, uuid=utils.generate_uuid(), self.context, id=id_, uuid=utils.generate_uuid(),
goal_id=goal.id) goal_id=goal.id, name='My Audit {0}'.format(id_))
goal_list.append(goal.uuid) goal_list.append(goal.uuid)
response = self.get_json('/audits/?sort_key=goal_uuid') response = self.get_json('/audits/?sort_key=goal_uuid')
@@ -214,7 +219,9 @@ class TestListAudit(api_base.FunctionalTest):
def test_links(self): def test_links(self):
uuid = utils.generate_uuid() uuid = utils.generate_uuid()
obj_utils.create_test_audit(self.context, id=1, uuid=uuid) obj_utils.create_test_audit(
self.context, id=1, uuid=uuid,
name='My Audit {0}'.format(1))
response = self.get_json('/audits/%s' % uuid) response = self.get_json('/audits/%s' % uuid)
self.assertIn('links', response.keys()) self.assertIn('links', response.keys())
self.assertEqual(2, len(response['links'])) self.assertEqual(2, len(response['links']))
@@ -225,8 +232,9 @@ class TestListAudit(api_base.FunctionalTest):
def test_collection_links(self): def test_collection_links(self):
for id_ in range(5): for id_ in range(5):
obj_utils.create_test_audit(self.context, id=id_, obj_utils.create_test_audit(
uuid=utils.generate_uuid()) self.context, id=id_,
uuid=utils.generate_uuid(), name='My Audit {0}'.format(id_))
response = self.get_json('/audits/?limit=3') response = self.get_json('/audits/?limit=3')
self.assertEqual(3, len(response['audits'])) self.assertEqual(3, len(response['audits']))
@@ -236,8 +244,9 @@ class TestListAudit(api_base.FunctionalTest):
def test_collection_links_default_limit(self): def test_collection_links_default_limit(self):
cfg.CONF.set_override('max_limit', 3, 'api') cfg.CONF.set_override('max_limit', 3, 'api')
for id_ in range(5): for id_ in range(5):
obj_utils.create_test_audit(self.context, id=id_, obj_utils.create_test_audit(
uuid=utils.generate_uuid()) self.context, id=id_,
uuid=utils.generate_uuid(), name='My Audit {0}'.format(id_))
response = self.get_json('/audits') response = self.get_json('/audits')
self.assertEqual(3, len(response['audits'])) self.assertEqual(3, len(response['audits']))

View File

@@ -110,18 +110,22 @@ class TestChangeNovaServiceState(base.TestCase):
def test_execute_change_service_state_with_disable_target(self): def test_execute_change_service_state_with_disable_target(self):
self.action.input_parameters["state"] = ( self.action.input_parameters["state"] = (
element.ServiceState.DISABLED.value) element.ServiceState.DISABLED.value)
self.action.input_parameters["disabled_reason"] = (
"watcher_disabled")
self.action.execute() self.action.execute()
self.m_helper_cls.assert_called_once_with(osc=self.m_osc) self.m_helper_cls.assert_called_once_with(osc=self.m_osc)
self.m_helper.disable_service_nova_compute.assert_called_once_with( self.m_helper.disable_service_nova_compute.assert_called_once_with(
"compute-1") "compute-1", "watcher_disabled")
def test_revert_change_service_state_with_enable_target(self): def test_revert_change_service_state_with_enable_target(self):
self.action.input_parameters["disabled_reason"] = (
"watcher_disabled")
self.action.revert() self.action.revert()
self.m_helper_cls.assert_called_once_with(osc=self.m_osc) self.m_helper_cls.assert_called_once_with(osc=self.m_osc)
self.m_helper.disable_service_nova_compute.assert_called_once_with( self.m_helper.disable_service_nova_compute.assert_called_once_with(
"compute-1") "compute-1", "watcher_disabled")
def test_revert_change_service_state_with_disable_target(self): def test_revert_change_service_state_with_disable_target(self):
self.action.input_parameters["state"] = ( self.action.input_parameters["state"] = (

View File

@@ -210,34 +210,6 @@ class TestMigration(base.TestCase):
dest_hostname="compute1-hostname" dest_hostname="compute1-hostname"
) )
def test_live_migrate_non_shared_storage_instance(self):
self.m_helper.find_instance.return_value = self.INSTANCE_UUID
self.m_helper.live_migrate_instance.side_effect = [
nova_helper.nvexceptions.ClientException(400, "BadRequest"), True]
try:
self.action.execute()
except Exception as exc:
self.fail(exc)
self.m_helper.live_migrate_instance.assert_has_calls([
mock.call(instance_id=self.INSTANCE_UUID,
dest_hostname="compute2-hostname"),
mock.call(instance_id=self.INSTANCE_UUID,
dest_hostname="compute2-hostname",
block_migration=True)
])
expected = [mock.call.first(instance_id=self.INSTANCE_UUID,
dest_hostname="compute2-hostname"),
mock.call.second(instance_id=self.INSTANCE_UUID,
dest_hostname="compute2-hostname",
block_migration=True)
]
self.m_helper.live_migrate_instance.mock_calls == expected
self.assertEqual(2, self.m_helper.live_migrate_instance.call_count)
def test_abort_live_migrate(self): def test_abort_live_migrate(self):
migration = mock.MagicMock() migration = mock.MagicMock()
migration.id = "2" migration.id = "2"

View File

@@ -165,6 +165,27 @@ class TestNovaHelper(base.TestCase):
) )
self.assertFalse(is_success) self.assertFalse(is_success)
@mock.patch.object(time, 'sleep', mock.Mock())
def test_live_migrate_instance_with_task_state(
self, mock_glance, mock_cinder, mock_neutron, mock_nova):
nova_util = nova_helper.NovaHelper()
server = self.fake_server(self.instance_uuid)
setattr(server, 'OS-EXT-SRV-ATTR:host',
self.source_node)
setattr(server, 'OS-EXT-STS:task_state', '')
self.fake_nova_find_list(nova_util, find=server, list=None)
nova_util.live_migrate_instance(
self.instance_uuid, self.destination_node
)
time.sleep.assert_not_called()
setattr(server, 'OS-EXT-STS:task_state', 'migrating')
self.fake_nova_find_list(nova_util, find=server, list=server)
nova_util.live_migrate_instance(
self.instance_uuid, self.destination_node
)
time.sleep.assert_called_with(1)
@mock.patch.object(time, 'sleep', mock.Mock()) @mock.patch.object(time, 'sleep', mock.Mock())
def test_live_migrate_instance_no_destination_node( def test_live_migrate_instance_no_destination_node(
self, mock_glance, mock_cinder, mock_neutron, mock_nova): self, mock_glance, mock_cinder, mock_neutron, mock_nova):
@@ -328,13 +349,13 @@ class TestNovaHelper(base.TestCase):
mock_neutron, mock_nova): mock_neutron, mock_nova):
nova_util = nova_helper.NovaHelper() nova_util = nova_helper.NovaHelper()
nova_services = nova_util.nova.services nova_services = nova_util.nova.services
nova_services.disable.return_value = mock.MagicMock( nova_services.disable_log_reason.return_value = mock.MagicMock(
status='enabled') status='enabled')
result = nova_util.disable_service_nova_compute('nanjing') result = nova_util.disable_service_nova_compute('nanjing')
self.assertFalse(result) self.assertFalse(result)
nova_services.disable.return_value = mock.MagicMock( nova_services.disable_log_reason.return_value = mock.MagicMock(
status='disabled') status='disabled')
result = nova_util.disable_service_nova_compute('nanjing') result = nova_util.disable_service_nova_compute('nanjing')

View File

@@ -38,25 +38,31 @@ class TestDbAuditFilters(base.DbTestCase):
def _data_setup(self): def _data_setup(self):
self.audit_template_name = "Audit Template" self.audit_template_name = "Audit Template"
gen_name = lambda: "Audit %s" % w_utils.generate_uuid()
self.audit1_name = gen_name()
self.audit2_name = gen_name()
self.audit3_name = gen_name()
self.audit4_name = gen_name()
self.audit_template = utils.create_test_audit_template( self.audit_template = utils.create_test_audit_template(
name=self.audit_template_name, id=1, uuid=None) name=self.audit_template_name, id=1, uuid=None)
with freezegun.freeze_time(self.FAKE_TODAY): with freezegun.freeze_time(self.FAKE_TODAY):
self.audit1 = utils.create_test_audit( self.audit1 = utils.create_test_audit(
audit_template_id=self.audit_template.id, id=1, uuid=None) audit_template_id=self.audit_template.id, id=1, uuid=None,
name=self.audit1_name)
with freezegun.freeze_time(self.FAKE_OLD_DATE): with freezegun.freeze_time(self.FAKE_OLD_DATE):
self.audit2 = utils.create_test_audit( self.audit2 = utils.create_test_audit(
audit_template_id=self.audit_template.id, id=2, uuid=None, audit_template_id=self.audit_template.id, id=2, uuid=None,
state=objects.audit.State.FAILED) name=self.audit2_name, state=objects.audit.State.FAILED)
with freezegun.freeze_time(self.FAKE_OLDER_DATE): with freezegun.freeze_time(self.FAKE_OLDER_DATE):
self.audit3 = utils.create_test_audit( self.audit3 = utils.create_test_audit(
audit_template_id=self.audit_template.id, id=3, uuid=None, audit_template_id=self.audit_template.id, id=3, uuid=None,
state=objects.audit.State.CANCELLED) name=self.audit3_name, state=objects.audit.State.CANCELLED)
with freezegun.freeze_time(self.FAKE_OLDER_DATE): with freezegun.freeze_time(self.FAKE_OLDER_DATE):
self.audit4 = utils.create_test_audit( self.audit4 = utils.create_test_audit(
audit_template_id=self.audit_template.id, id=4, uuid=None, audit_template_id=self.audit_template.id, id=4, uuid=None,
state=objects.audit.State.SUSPENDED) name=self.audit4_name, state=objects.audit.State.SUSPENDED)
def _soft_delete_audits(self): def _soft_delete_audits(self):
with freezegun.freeze_time(self.FAKE_TODAY): with freezegun.freeze_time(self.FAKE_TODAY):
@@ -266,8 +272,9 @@ class DbAuditTestCase(base.DbTestCase):
def test_get_audit_list(self): def test_get_audit_list(self):
uuids = [] uuids = []
for _ in range(1, 4): for id_ in range(1, 4):
audit = utils.create_test_audit(uuid=w_utils.generate_uuid()) audit = utils.create_test_audit(uuid=w_utils.generate_uuid(),
name='My Audit {0}'.format(id_))
uuids.append(six.text_type(audit['uuid'])) uuids.append(six.text_type(audit['uuid']))
audits = self.dbapi.get_audit_list(self.context) audits = self.dbapi.get_audit_list(self.context)
audit_uuids = [a.uuid for a in audits] audit_uuids = [a.uuid for a in audits]
@@ -286,6 +293,7 @@ class DbAuditTestCase(base.DbTestCase):
for i in range(1, 4): for i in range(1, 4):
audit = utils.create_test_audit( audit = utils.create_test_audit(
id=i, uuid=w_utils.generate_uuid(), id=i, uuid=w_utils.generate_uuid(),
name='My Audit {0}'.format(i),
goal_id=goal.id, strategy_id=strategy.id) goal_id=goal.id, strategy_id=strategy.id)
uuids.append(six.text_type(audit['uuid'])) uuids.append(six.text_type(audit['uuid']))
audits = self.dbapi.get_audit_list(self.context, eager=True) audits = self.dbapi.get_audit_list(self.context, eager=True)
@@ -300,6 +308,7 @@ class DbAuditTestCase(base.DbTestCase):
id=1, id=1,
audit_type=objects.audit.AuditType.ONESHOT.value, audit_type=objects.audit.AuditType.ONESHOT.value,
uuid=w_utils.generate_uuid(), uuid=w_utils.generate_uuid(),
name='My Audit {0}'.format(1),
state=objects.audit.State.ONGOING) state=objects.audit.State.ONGOING)
audit2 = self._create_test_audit( audit2 = self._create_test_audit(
id=2, id=2,
@@ -389,3 +398,21 @@ class DbAuditTestCase(base.DbTestCase):
self.assertEqual(audit['id'], action_plan.audit_id) self.assertEqual(audit['id'], action_plan.audit_id)
self.assertRaises(exception.AuditReferenced, self.assertRaises(exception.AuditReferenced,
self.dbapi.destroy_audit, audit['id']) self.dbapi.destroy_audit, audit['id'])
def test_create_audit_already_exists(self):
uuid = w_utils.generate_uuid()
self._create_test_audit(id=1, uuid=uuid)
self.assertRaises(exception.AuditAlreadyExists,
self._create_test_audit,
id=2, uuid=uuid)
def test_create_same_name_audit(self):
audit = utils.create_test_audit(
uuid=w_utils.generate_uuid(),
name='my_audit')
self.assertEqual(audit['uuid'], audit.uuid)
self.assertRaises(
exception.AuditAlreadyExists,
utils.create_test_audit,
uuid=w_utils.generate_uuid(),
name='my_audit')

View File

@@ -100,6 +100,13 @@ class TestPurgeCommand(base.DbTestCase):
self.audit_template3_name = self.generate_unique_name( self.audit_template3_name = self.generate_unique_name(
prefix="Audit Template 3 ") prefix="Audit Template 3 ")
self.audit1_name = self.generate_unique_name(
prefix="Audit 1 ")
self.audit2_name = self.generate_unique_name(
prefix="Audit 2 ")
self.audit3_name = self.generate_unique_name(
prefix="Audit 3 ")
with freezegun.freeze_time(self.expired_date): with freezegun.freeze_time(self.expired_date):
self.goal1 = obj_utils.create_test_goal( self.goal1 = obj_utils.create_test_goal(
self.context, id=self._generate_id(), self.context, id=self._generate_id(),
@@ -154,15 +161,15 @@ class TestPurgeCommand(base.DbTestCase):
with freezegun.freeze_time(self.expired_date): with freezegun.freeze_time(self.expired_date):
self.audit1 = obj_utils.create_test_audit( self.audit1 = obj_utils.create_test_audit(
self.context, id=self._generate_id(), self.context, id=self._generate_id(),
uuid=utils.generate_uuid(), uuid=utils.generate_uuid(), name=self.audit1_name,
goal_id=self.goal1.id, strategy_id=self.strategy1.id) goal_id=self.goal1.id, strategy_id=self.strategy1.id)
self.audit2 = obj_utils.create_test_audit( self.audit2 = obj_utils.create_test_audit(
self.context, id=self._generate_id(), self.context, id=self._generate_id(),
uuid=utils.generate_uuid(), uuid=utils.generate_uuid(), name=self.audit2_name,
goal_id=self.goal2.id, strategy_id=self.strategy2.id) goal_id=self.goal2.id, strategy_id=self.strategy2.id)
self.audit3 = obj_utils.create_test_audit( self.audit3 = obj_utils.create_test_audit(
self.context, id=self._generate_id(), self.context, id=self._generate_id(),
uuid=utils.generate_uuid(), uuid=utils.generate_uuid(), name=self.audit3_name,
goal_id=self.goal3.id, strategy_id=self.strategy3.id) goal_id=self.goal3.id, strategy_id=self.strategy3.id)
self.audit1.soft_delete() self.audit1.soft_delete()
@@ -272,7 +279,8 @@ class TestPurgeCommand(base.DbTestCase):
audit4 = obj_utils.create_test_audit( audit4 = obj_utils.create_test_audit(
self.context, audit_template_id=audit_template4.id, self.context, audit_template_id=audit_template4.id,
strategy_id=self.strategy1.id, id=self._generate_id(), strategy_id=self.strategy1.id, id=self._generate_id(),
uuid=utils.generate_uuid()) uuid=utils.generate_uuid(),
name=self.generate_unique_name(prefix="Audit 4 "))
action_plan4 = obj_utils.create_test_action_plan( action_plan4 = obj_utils.create_test_action_plan(
self.context, self.context,
id=self._generate_id(), uuid=utils.generate_uuid(), id=self._generate_id(), uuid=utils.generate_uuid(),
@@ -290,7 +298,8 @@ class TestPurgeCommand(base.DbTestCase):
audit5 = obj_utils.create_test_audit( audit5 = obj_utils.create_test_audit(
self.context, audit_template_id=audit_template5.id, self.context, audit_template_id=audit_template5.id,
strategy_id=self.strategy1.id, id=self._generate_id(), strategy_id=self.strategy1.id, id=self._generate_id(),
uuid=utils.generate_uuid()) uuid=utils.generate_uuid(),
name=self.generate_unique_name(prefix="Audit 5 "))
action_plan5 = obj_utils.create_test_action_plan( action_plan5 = obj_utils.create_test_action_plan(
self.context, self.context,
id=self._generate_id(), uuid=utils.generate_uuid(), id=self._generate_id(), uuid=utils.generate_uuid(),
@@ -369,7 +378,8 @@ class TestPurgeCommand(base.DbTestCase):
audit4 = obj_utils.create_test_audit( audit4 = obj_utils.create_test_audit(
self.context, self.context,
id=self._generate_id(), uuid=utils.generate_uuid(), id=self._generate_id(), uuid=utils.generate_uuid(),
audit_template_id=audit_template4.id) audit_template_id=audit_template4.id,
name=self.generate_unique_name(prefix="Audit 4 "))
action_plan4 = obj_utils.create_test_action_plan( action_plan4 = obj_utils.create_test_action_plan(
self.context, self.context,
id=self._generate_id(), uuid=utils.generate_uuid(), id=self._generate_id(), uuid=utils.generate_uuid(),
@@ -387,7 +397,8 @@ class TestPurgeCommand(base.DbTestCase):
audit5 = obj_utils.create_test_audit( audit5 = obj_utils.create_test_audit(
self.context, audit_template_id=audit_template5.id, self.context, audit_template_id=audit_template5.id,
strategy_id=self.strategy1.id, id=self._generate_id(), strategy_id=self.strategy1.id, id=self._generate_id(),
uuid=utils.generate_uuid()) uuid=utils.generate_uuid(),
name=self.generate_unique_name(prefix="Audit 5 "))
action_plan5 = obj_utils.create_test_action_plan( action_plan5 = obj_utils.create_test_action_plan(
self.context, self.context,
id=self._generate_id(), uuid=utils.generate_uuid(), id=self._generate_id(), uuid=utils.generate_uuid(),

View File

@@ -83,6 +83,7 @@ def get_test_audit(**kwargs):
audit_data = { audit_data = {
'id': kwargs.get('id', 1), 'id': kwargs.get('id', 1),
'uuid': kwargs.get('uuid', '10a47dd1-4874-4298-91cf-eff046dbdb8d'), 'uuid': kwargs.get('uuid', '10a47dd1-4874-4298-91cf-eff046dbdb8d'),
'name': kwargs.get('name', 'My Audit'),
'audit_type': kwargs.get('audit_type', 'ONESHOT'), 'audit_type': kwargs.get('audit_type', 'ONESHOT'),
'state': kwargs.get('state', objects.audit.State.PENDING), 'state': kwargs.get('state', objects.audit.State.PENDING),
'created_at': kwargs.get('created_at'), 'created_at': kwargs.get('created_at'),
@@ -166,7 +167,7 @@ def get_test_action_plan(**kwargs):
'state': kwargs.get('state', objects.action_plan.State.ONGOING), 'state': kwargs.get('state', objects.action_plan.State.ONGOING),
'audit_id': kwargs.get('audit_id', 1), 'audit_id': kwargs.get('audit_id', 1),
'strategy_id': kwargs.get('strategy_id', 1), 'strategy_id': kwargs.get('strategy_id', 1),
'global_efficacy': kwargs.get('global_efficacy', {}), 'global_efficacy': kwargs.get('global_efficacy', []),
'created_at': kwargs.get('created_at'), 'created_at': kwargs.get('created_at'),
'updated_at': kwargs.get('updated_at'), 'updated_at': kwargs.get('updated_at'),
'deleted_at': kwargs.get('deleted_at'), 'deleted_at': kwargs.get('deleted_at'),

View File

@@ -232,6 +232,7 @@ class TestContinuousAuditHandler(base.DbTestCase):
obj_utils.create_test_audit( obj_utils.create_test_audit(
self.context, self.context,
id=id_, id=id_,
name='My Audit {0}'.format(id_),
uuid=uuidutils.generate_uuid(), uuid=uuidutils.generate_uuid(),
audit_template_id=audit_template.id, audit_template_id=audit_template.id,
goal_id=self.goal.id, goal_id=self.goal.id,
@@ -376,7 +377,7 @@ class TestContinuousAuditHandler(base.DbTestCase):
audit_handler = continuous.ContinuousAuditHandler() audit_handler = continuous.ContinuousAuditHandler()
self.audits[0].next_run_time = (datetime.datetime.now() - self.audits[0].next_run_time = (datetime.datetime.now() -
datetime.timedelta(seconds=1800)) datetime.timedelta(seconds=1800))
m_is_inactive.return_value = True m_is_inactive.return_value = False
m_get_jobs.return_value = None m_get_jobs.return_value = None
audit_handler.execute_audit(self.audits[0], self.context) audit_handler.execute_audit(self.audits[0], self.context)

View File

@@ -27,6 +27,9 @@ class DummyClusterDataModelCollector(base.BaseClusterDataModelCollector):
def notification_endpoints(self): def notification_endpoints(self):
return [] return []
def get_audit_scope_handler(self, audit_scope):
return None
def execute(self): def execute(self):
model = model_root.ModelRoot() model = model_root.ModelRoot()
# Do something here... # Do something here...

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