Compare commits
193 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5307f5a80e | ||
|
|
b5467a2a1f | ||
|
|
83411ec89f | ||
|
|
08750536e7 | ||
|
|
9f7ccfe408 | ||
|
|
fb2619e538 | ||
|
|
6bd857fa0e | ||
|
|
e0faeea608 | ||
|
|
61aca40e6e | ||
|
|
b293389734 | ||
|
|
050e6d58f1 | ||
|
|
7223d35c47 | ||
|
|
57f1971982 | ||
|
|
c9b2b2aa39 | ||
|
|
a42c31c221 | ||
|
|
403ec94bc1 | ||
|
|
3431b77388 | ||
|
|
eb4cacc00e | ||
|
|
40a653215f | ||
|
|
1492f5d8dc | ||
|
|
76263f149a | ||
|
|
028006d15d | ||
|
|
d27ba8cc2a | ||
|
|
33750ce7a9 | ||
|
|
cb8d1a98d6 | ||
|
|
f32252d510 | ||
|
|
4849f8dde9 | ||
|
|
0cafdcdee9 | ||
|
|
3a70225164 | ||
|
|
892c766ac4 | ||
|
|
63a3fd84ae | ||
|
|
287ace1dcc | ||
|
|
4b302e415e | ||
|
|
f24744c910 | ||
|
|
d9a85eda2c | ||
|
|
82c8633e42 | ||
|
|
d3f23795f5 | ||
|
|
e7f4456a80 | ||
|
|
a36a309e2e | ||
|
|
8e3affd9ac | ||
|
|
71e979cae0 | ||
|
|
6edfd34a53 | ||
|
|
0c8c32e69e | ||
|
|
9138b7bacb | ||
|
|
072822d920 | ||
|
|
f67ce8cca5 | ||
|
|
9e6f768263 | ||
|
|
ba9c89186b | ||
|
|
16e7d9c13b | ||
|
|
c3536406bd | ||
|
|
0c66fe2e65 | ||
|
|
74933bf0ba | ||
|
|
1dae83da57 | ||
|
|
5ec8932182 | ||
|
|
701b258dc7 | ||
|
|
f7fcdf14d0 | ||
|
|
47ba6c0808 | ||
|
|
5b5fbbedb4 | ||
|
|
a1c575bfc5 | ||
|
|
27e887556d | ||
|
|
891f6bc241 | ||
|
|
5dd6817d47 | ||
|
|
7cdcb4743e | ||
|
|
6d03c4c543 | ||
|
|
bcc129cf94 | ||
|
|
40cff311c6 | ||
|
|
1a48a7fc57 | ||
|
|
652aa54586 | ||
|
|
42a3886ded | ||
|
|
3430493de1 | ||
|
|
f5bcf9d355 | ||
|
|
d809523bef | ||
|
|
bfe3c28986 | ||
|
|
3c8caa3d0a | ||
|
|
766d064dd0 | ||
|
|
ce196b68c4 | ||
|
|
42130c42a1 | ||
|
|
1a8639d256 | ||
|
|
1702fe1a83 | ||
|
|
354ebd35cc | ||
|
|
7297603f65 | ||
|
|
9626cb1356 | ||
|
|
9e027940d7 | ||
|
|
3754938d96 | ||
|
|
8a7f930a64 | ||
|
|
f7e506155b | ||
|
|
54da2a75fb | ||
|
|
5cbb9aca7e | ||
|
|
bd79882b16 | ||
|
|
960c50ba45 | ||
|
|
9411f85cd2 | ||
|
|
b4370f0461 | ||
|
|
97799521f9 | ||
|
|
96fa7f33ac | ||
|
|
1c2d0aa1f2 | ||
|
|
070aed7076 | ||
|
|
2b402d3cbf | ||
|
|
cca3e75ac1 | ||
|
|
6f27275f44 | ||
|
|
95548af426 | ||
|
|
cdc847d352 | ||
|
|
b69244f8ef | ||
|
|
cbd6d88025 | ||
|
|
028d7c939c | ||
|
|
a8fa969379 | ||
|
|
80ee4b29f5 | ||
|
|
e562c9173c | ||
|
|
ec0c359037 | ||
|
|
3b6bef180b | ||
|
|
640e4e1fea | ||
|
|
eeb817cd6e | ||
|
|
c6afa7c320 | ||
|
|
9ccd17e40b | ||
|
|
2a7e0d652c | ||
|
|
a94e35b60e | ||
|
|
72e3d5c7f9 | ||
|
|
be56441e55 | ||
|
|
aa2b213a45 | ||
|
|
668513d771 | ||
|
|
0242d33adb | ||
|
|
c38dc9828b | ||
|
|
c2e16bfa96 | ||
|
|
4ce1a9096b | ||
|
|
13644429b7 | ||
|
|
b8cc506fbe | ||
|
|
02163d64aa | ||
|
|
d91f0bff22 | ||
|
|
c91f6479f0 | ||
|
|
92572c5dec | ||
|
|
a8f08065fd | ||
|
|
e401cb7c9d | ||
|
|
0745d904fc | ||
|
|
bc4a58d2d7 | ||
|
|
f14795d29f | ||
|
|
e0104074b6 | ||
|
|
2993dea376 | ||
|
|
17b6019ea9 | ||
|
|
5969e5b52a | ||
|
|
e55f3793b6 | ||
|
|
901c598dd7 | ||
|
|
e41a90d7ad | ||
|
|
fa31341bbb | ||
|
|
051b4fcd06 | ||
|
|
cd045400ed | ||
|
|
2db668af30 | ||
|
|
39b1fcf07f | ||
|
|
94babf61da | ||
|
|
a5fba7ce28 | ||
|
|
24e01b6c98 | ||
|
|
4007f93aac | ||
|
|
77c9f88fc4 | ||
|
|
e9b7f067c5 | ||
|
|
f8aa02c4a7 | ||
|
|
3595108e49 | ||
|
|
d536ed248b | ||
|
|
71730c0eaf | ||
|
|
1b4c5dfc8b | ||
|
|
4179cfd036 | ||
|
|
49550db566 | ||
|
|
e0eba0ee7b | ||
|
|
165853ee2c | ||
|
|
0a7152fa55 | ||
|
|
6c29df11ca | ||
|
|
55bd0fd038 | ||
|
|
907cc2df16 | ||
|
|
8dcac1597b | ||
|
|
a3be1587e3 | ||
|
|
aa72f984e4 | ||
|
|
bcd2040025 | ||
|
|
f2c9dc9c32 | ||
|
|
6c94c235fc | ||
|
|
7ed45e3ef1 | ||
|
|
8722951022 | ||
|
|
568d4e831c | ||
|
|
deefc857ba | ||
|
|
d33736e7f0 | ||
|
|
1b1779cc49 | ||
|
|
d727bc3076 | ||
|
|
875b7e1ca3 | ||
|
|
c7f8755f9c | ||
|
|
0472715e0c | ||
|
|
2482e82548 | ||
|
|
e9c420467e | ||
|
|
2d5db7082b | ||
|
|
4a3a50435a | ||
|
|
05b57fee7a | ||
|
|
3729e39552 | ||
|
|
91911c8284 | ||
|
|
d7d56cbd79 | ||
|
|
d722b62b97 | ||
|
|
bf713ac7e1 | ||
|
|
51b3a15c90 | ||
|
|
74bc31e562 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -24,6 +24,7 @@ pip-log.txt
|
|||||||
.coverage*
|
.coverage*
|
||||||
.tox
|
.tox
|
||||||
nosetests.xml
|
nosetests.xml
|
||||||
|
.stestr/
|
||||||
.testrepository
|
.testrepository
|
||||||
.venv
|
.venv
|
||||||
.idea
|
.idea
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
[gerrit]
|
[gerrit]
|
||||||
host=review.openstack.org
|
host=review.opendev.org
|
||||||
port=29418
|
port=29418
|
||||||
project=openstack/watcher.git
|
project=openstack/watcher.git
|
||||||
|
defaultbranch=stable/queens
|
||||||
|
|||||||
4
.stestr.conf
Normal file
4
.stestr.conf
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
[DEFAULT]
|
||||||
|
test_path=${OS_TEST_PATH:-./watcher/tests}
|
||||||
|
top_dir=./
|
||||||
|
|
||||||
45
.zuul.yaml
Normal file
45
.zuul.yaml
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
- project:
|
||||||
|
templates:
|
||||||
|
- openstack-python-jobs
|
||||||
|
- openstack-python35-jobs
|
||||||
|
- publish-openstack-sphinx-docs
|
||||||
|
- check-requirements
|
||||||
|
- release-notes-jobs
|
||||||
|
check:
|
||||||
|
jobs:
|
||||||
|
- watcher-tempest-multinode
|
||||||
|
|
||||||
|
gate:
|
||||||
|
queue: watcher
|
||||||
|
- 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/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/devstack
|
||||||
|
- openstack/devstack-gate
|
||||||
|
- openstack/python-openstackclient
|
||||||
|
- openstack/python-watcherclient
|
||||||
|
- openstack/watcher
|
||||||
@@ -42,7 +42,7 @@ WATCHER_AUTH_CACHE_DIR=${WATCHER_AUTH_CACHE_DIR:-/var/cache/watcher}
|
|||||||
|
|
||||||
WATCHER_CONF_DIR=/etc/watcher
|
WATCHER_CONF_DIR=/etc/watcher
|
||||||
WATCHER_CONF=$WATCHER_CONF_DIR/watcher.conf
|
WATCHER_CONF=$WATCHER_CONF_DIR/watcher.conf
|
||||||
WATCHER_POLICY_JSON=$WATCHER_CONF_DIR/policy.json
|
WATCHER_POLICY_YAML=$WATCHER_CONF_DIR/policy.yaml.sample
|
||||||
|
|
||||||
WATCHER_DEVSTACK_DIR=$WATCHER_DIR/devstack
|
WATCHER_DEVSTACK_DIR=$WATCHER_DIR/devstack
|
||||||
WATCHER_DEVSTACK_FILES_DIR=$WATCHER_DEVSTACK_DIR/files
|
WATCHER_DEVSTACK_FILES_DIR=$WATCHER_DEVSTACK_DIR/files
|
||||||
@@ -106,7 +106,25 @@ function configure_watcher {
|
|||||||
# Put config files in ``/etc/watcher`` for everyone to find
|
# Put config files in ``/etc/watcher`` for everyone to find
|
||||||
sudo install -d -o $STACK_USER $WATCHER_CONF_DIR
|
sudo install -d -o $STACK_USER $WATCHER_CONF_DIR
|
||||||
|
|
||||||
install_default_policy watcher
|
local project=watcher
|
||||||
|
local project_uc
|
||||||
|
project_uc=$(echo watcher|tr a-z A-Z)
|
||||||
|
local conf_dir="${project_uc}_CONF_DIR"
|
||||||
|
# eval conf dir to get the variable
|
||||||
|
conf_dir="${!conf_dir}"
|
||||||
|
local project_dir="${project_uc}_DIR"
|
||||||
|
# eval project dir to get the variable
|
||||||
|
project_dir="${!project_dir}"
|
||||||
|
local sample_conf_dir="${project_dir}/etc/${project}"
|
||||||
|
local sample_policy_dir="${project_dir}/etc/${project}/policy.d"
|
||||||
|
local sample_policy_generator="${project_dir}/etc/${project}/oslo-policy-generator/watcher-policy-generator.conf"
|
||||||
|
|
||||||
|
# first generate policy.yaml
|
||||||
|
oslopolicy-sample-generator --config-file $sample_policy_generator
|
||||||
|
# then optionally copy over policy.d
|
||||||
|
if [[ -d $sample_policy_dir ]]; then
|
||||||
|
cp -r $sample_policy_dir $conf_dir/policy.d
|
||||||
|
fi
|
||||||
|
|
||||||
# Rebuild the config file from scratch
|
# Rebuild the config file from scratch
|
||||||
create_watcher_conf
|
create_watcher_conf
|
||||||
@@ -163,7 +181,7 @@ function create_watcher_conf {
|
|||||||
iniset $WATCHER_CONF api host "$WATCHER_SERVICE_HOST"
|
iniset $WATCHER_CONF api host "$WATCHER_SERVICE_HOST"
|
||||||
iniset $WATCHER_CONF api port "$WATCHER_SERVICE_PORT"
|
iniset $WATCHER_CONF api port "$WATCHER_SERVICE_PORT"
|
||||||
|
|
||||||
iniset $WATCHER_CONF oslo_policy policy_file $WATCHER_POLICY_JSON
|
iniset $WATCHER_CONF oslo_policy policy_file $WATCHER_POLICY_YAML
|
||||||
|
|
||||||
iniset $WATCHER_CONF oslo_messaging_rabbit rabbit_userid $RABBIT_USERID
|
iniset $WATCHER_CONF oslo_messaging_rabbit rabbit_userid $RABBIT_USERID
|
||||||
iniset $WATCHER_CONF oslo_messaging_rabbit rabbit_password $RABBIT_PASSWORD
|
iniset $WATCHER_CONF oslo_messaging_rabbit rabbit_password $RABBIT_PASSWORD
|
||||||
@@ -296,6 +314,7 @@ function start_watcher {
|
|||||||
function stop_watcher {
|
function stop_watcher {
|
||||||
if [[ "$WATCHER_USE_MOD_WSGI" == "True" ]]; then
|
if [[ "$WATCHER_USE_MOD_WSGI" == "True" ]]; then
|
||||||
disable_apache_site watcher-api
|
disable_apache_site watcher-api
|
||||||
|
restart_apache_server
|
||||||
else
|
else
|
||||||
stop_process watcher-api
|
stop_process watcher-api
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ VNCSERVER_PROXYCLIENT_ADDRESS=$HOST_IP
|
|||||||
NOVA_INSTANCES_PATH=/opt/stack/data/instances
|
NOVA_INSTANCES_PATH=/opt/stack/data/instances
|
||||||
|
|
||||||
# Enable the Ceilometer plugin for the compute agent
|
# Enable the Ceilometer plugin for the compute agent
|
||||||
enable_plugin ceilometer git://git.openstack.org/openstack/ceilometer
|
enable_plugin ceilometer https://git.openstack.org/openstack/ceilometer
|
||||||
disable_service ceilometer-acentral,ceilometer-collector,ceilometer-api
|
disable_service ceilometer-acentral,ceilometer-collector,ceilometer-api
|
||||||
|
|
||||||
LOGFILE=$DEST/logs/stack.sh.log
|
LOGFILE=$DEST/logs/stack.sh.log
|
||||||
|
|||||||
@@ -25,13 +25,13 @@ MULTI_HOST=1
|
|||||||
disable_service n-cpu
|
disable_service n-cpu
|
||||||
|
|
||||||
# Enable the Watcher Dashboard plugin
|
# Enable the Watcher Dashboard plugin
|
||||||
enable_plugin watcher-dashboard git://git.openstack.org/openstack/watcher-dashboard
|
enable_plugin watcher-dashboard https://git.openstack.org/openstack/watcher-dashboard
|
||||||
|
|
||||||
# Enable the Watcher plugin
|
# Enable the Watcher plugin
|
||||||
enable_plugin watcher git://git.openstack.org/openstack/watcher
|
enable_plugin watcher https://git.openstack.org/openstack/watcher
|
||||||
|
|
||||||
# Enable the Ceilometer plugin
|
# Enable the Ceilometer plugin
|
||||||
enable_plugin ceilometer git://git.openstack.org/openstack/ceilometer
|
enable_plugin ceilometer https://git.openstack.org/openstack/ceilometer
|
||||||
|
|
||||||
# This is the controller node, so disable the ceilometer compute agent
|
# This is the controller node, so disable the ceilometer compute agent
|
||||||
disable_service ceilometer-acompute
|
disable_service ceilometer-acompute
|
||||||
|
|||||||
@@ -3,6 +3,9 @@
|
|||||||
# Make sure rabbit is enabled
|
# Make sure rabbit is enabled
|
||||||
enable_service rabbit
|
enable_service rabbit
|
||||||
|
|
||||||
|
# Make sure mysql is enabled
|
||||||
|
enable_service mysql
|
||||||
|
|
||||||
# Enable Watcher services
|
# Enable Watcher services
|
||||||
enable_service watcher-api
|
enable_service watcher-api
|
||||||
enable_service watcher-decision-engine
|
enable_service watcher-decision-engine
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -57,7 +57,7 @@
|
|||||||
"state": "SUCCEEDED"
|
"state": "SUCCEEDED"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"global_efficacy": {},
|
"global_efficacy": [],
|
||||||
"state": "CANCELLING"
|
"state": "CANCELLING"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
==================================================
|
||||||
|
OpenStack Infrastructure Optimization Service APIs
|
||||||
|
==================================================
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ You can easily generate and update a sample configuration file
|
|||||||
named :ref:`watcher.conf.sample <watcher_sample_configuration_files>` by using
|
named :ref:`watcher.conf.sample <watcher_sample_configuration_files>` by using
|
||||||
these following commands::
|
these following commands::
|
||||||
|
|
||||||
$ git clone git://git.openstack.org/openstack/watcher
|
$ git clone https://git.openstack.org/openstack/watcher
|
||||||
$ cd watcher/
|
$ cd watcher/
|
||||||
$ tox -e genconfig
|
$ tox -e genconfig
|
||||||
$ vi etc/watcher/watcher.conf.sample
|
$ vi etc/watcher/watcher.conf.sample
|
||||||
@@ -200,8 +200,8 @@ configuration file, in order:
|
|||||||
|
|
||||||
|
|
||||||
Although some configuration options are mentioned here, it is recommended that
|
Although some configuration options are mentioned here, it is recommended that
|
||||||
you review all the `available options
|
you review all the :ref:`available options
|
||||||
<https://git.openstack.org/cgit/openstack/watcher/tree/etc/watcher/watcher.conf.sample>`_
|
<watcher_sample_configuration_files>`
|
||||||
so that the watcher service is configured for your needs.
|
so that the watcher service is configured for your needs.
|
||||||
|
|
||||||
#. The Watcher Service stores information in a database. This guide uses the
|
#. The Watcher Service stores information in a database. This guide uses the
|
||||||
@@ -391,7 +391,7 @@ Ceilometer is designed to collect measurements from OpenStack services and from
|
|||||||
other external components. If you would like to add new meters to the currently
|
other external components. If you would like to add new meters to the currently
|
||||||
existing ones, you need to follow the documentation below:
|
existing ones, you need to follow the documentation below:
|
||||||
|
|
||||||
#. https://docs.openstack.org/ceilometer/latest/contributor/new_meters.html#meters
|
#. https://docs.openstack.org/ceilometer/latest/contributor/measurements.html#new-measurements
|
||||||
|
|
||||||
The Ceilometer collector uses a pluggable storage system, meaning that you can
|
The Ceilometer collector uses a pluggable storage system, meaning that you can
|
||||||
pick any database system you prefer.
|
pick any database system you prefer.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ model. To enable the Watcher plugin with DevStack, add the following to the
|
|||||||
`[[local|localrc]]` section of your controller's `local.conf` to enable the
|
`[[local|localrc]]` section of your controller's `local.conf` to enable the
|
||||||
Watcher plugin::
|
Watcher plugin::
|
||||||
|
|
||||||
enable_plugin watcher git://git.openstack.org/openstack/watcher
|
enable_plugin watcher https://git.openstack.org/openstack/watcher
|
||||||
|
|
||||||
For more detailed instructions, see `Detailed DevStack Instructions`_. Check
|
For more detailed instructions, see `Detailed DevStack Instructions`_. Check
|
||||||
out the `DevStack documentation`_ for more information regarding DevStack.
|
out the `DevStack documentation`_ for more information regarding DevStack.
|
||||||
@@ -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::
|
||||||
|
|||||||
@@ -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/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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 []
|
||||||
|
|||||||
@@ -263,7 +263,7 @@ requires new metrics not covered by Ceilometer, you can add them through a
|
|||||||
`Ceilometer plugin`_.
|
`Ceilometer plugin`_.
|
||||||
|
|
||||||
|
|
||||||
.. _`Helper`: https://github.com/openstack/watcher/blob/master/watcher/decision_engine/cluster/history/ceilometer.py
|
.. _`Helper`: https://github.com/openstack/watcher/blob/master/watcher/datasource/ceilometer.py
|
||||||
.. _`Ceilometer developer guide`: https://docs.openstack.org/ceilometer/latest/contributor/architecture.html#storing-accessing-the-data
|
.. _`Ceilometer developer guide`: https://docs.openstack.org/ceilometer/latest/contributor/architecture.html#storing-accessing-the-data
|
||||||
.. _`Ceilometer`: https://docs.openstack.org/ceilometer/latest
|
.. _`Ceilometer`: https://docs.openstack.org/ceilometer/latest
|
||||||
.. _`Monasca`: https://github.com/openstack/monasca-api/blob/master/docs/monasca-api-spec.md
|
.. _`Monasca`: https://github.com/openstack/monasca-api/blob/master/docs/monasca-api-spec.md
|
||||||
|
|||||||
@@ -267,7 +267,7 @@ the same goal and same workload of the :ref:`Cluster <cluster_definition>`.
|
|||||||
Project
|
Project
|
||||||
=======
|
=======
|
||||||
|
|
||||||
:ref:`Projects <project_definition>` represent the base unit of “ownership”
|
:ref:`Projects <project_definition>` represent the base unit of "ownership"
|
||||||
in OpenStack, in that all :ref:`resources <managed_resource_definition>` in
|
in OpenStack, in that all :ref:`resources <managed_resource_definition>` in
|
||||||
OpenStack should be owned by a specific :ref:`project <project_definition>`.
|
OpenStack should be owned by a specific :ref:`project <project_definition>`.
|
||||||
In OpenStack Identity, a :ref:`project <project_definition>` must be owned by a
|
In OpenStack Identity, a :ref:`project <project_definition>` must be owned by a
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
|
|
||||||
[keystone_authtoken]
|
[keystone_authtoken]
|
||||||
...
|
...
|
||||||
auth_uri = http://controller:5000
|
www_authenticate_uri = http://controller:5000
|
||||||
auth_url = http://controller:35357
|
auth_url = http://controller:35357
|
||||||
memcached_servers = controller:11211
|
memcached_servers = controller:11211
|
||||||
auth_type = password
|
auth_type = password
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ Infrastructure Optimization service
|
|||||||
verify.rst
|
verify.rst
|
||||||
next-steps.rst
|
next-steps.rst
|
||||||
|
|
||||||
The Infrastructure Optimization service (watcher) provides
|
The Infrastructure Optimization service (Watcher) provides
|
||||||
flexible and scalable resource optimization service for
|
flexible and scalable resource optimization service for
|
||||||
multi-tenant OpenStack-based clouds.
|
multi-tenant OpenStack-based clouds.
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ applier. This provides a robust framework to realize a wide
|
|||||||
range of cloud optimization goals, including the reduction
|
range of cloud optimization goals, including the reduction
|
||||||
of data center operating costs, increased system performance
|
of data center operating costs, increased system performance
|
||||||
via intelligent virtual machine migration, increased energy
|
via intelligent virtual machine migration, increased energy
|
||||||
efficiency—and more!
|
efficiency and more!
|
||||||
|
|
||||||
Watcher also supports a pluggable architecture by which custom
|
Watcher also supports a pluggable architecture by which custom
|
||||||
optimization algorithms, data metrics and data profilers can be
|
optimization algorithms, data metrics and data profilers can be
|
||||||
@@ -36,4 +36,4 @@ https://docs.openstack.org/watcher/latest/glossary.html
|
|||||||
|
|
||||||
This chapter assumes a working setup of OpenStack following the
|
This chapter assumes a working setup of OpenStack following the
|
||||||
`OpenStack Installation Tutorial
|
`OpenStack Installation Tutorial
|
||||||
<https://docs.openstack.org/pike/install/>`_.
|
<https://docs.openstack.org/queens/install/>`_.
|
||||||
|
|||||||
@@ -6,4 +6,4 @@ Next steps
|
|||||||
Your OpenStack environment now includes the watcher service.
|
Your OpenStack environment now includes the watcher service.
|
||||||
|
|
||||||
To add additional services, see
|
To add additional services, see
|
||||||
https://docs.openstack.org/pike/install/.
|
https://docs.openstack.org/queens/install/.
|
||||||
|
|||||||
86
doc/source/strategies/actuation.rst
Normal file
86
doc/source/strategies/actuation.rst
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
=============
|
||||||
|
Actuator
|
||||||
|
=============
|
||||||
|
|
||||||
|
Synopsis
|
||||||
|
--------
|
||||||
|
|
||||||
|
**display name**: ``Actuator``
|
||||||
|
|
||||||
|
**goal**: ``unclassified``
|
||||||
|
|
||||||
|
.. watcher-term:: watcher.decision_engine.strategy.strategies.actuation
|
||||||
|
|
||||||
|
Requirements
|
||||||
|
------------
|
||||||
|
|
||||||
|
Metrics
|
||||||
|
*******
|
||||||
|
|
||||||
|
None
|
||||||
|
|
||||||
|
Cluster data model
|
||||||
|
******************
|
||||||
|
|
||||||
|
None
|
||||||
|
|
||||||
|
Actions
|
||||||
|
*******
|
||||||
|
|
||||||
|
Default Watcher's actions.
|
||||||
|
|
||||||
|
Planner
|
||||||
|
*******
|
||||||
|
|
||||||
|
Default Watcher's planner:
|
||||||
|
|
||||||
|
.. watcher-term:: watcher.decision_engine.planner.weight.WeightPlanner
|
||||||
|
|
||||||
|
Configuration
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Strategy parameters are:
|
||||||
|
|
||||||
|
==================== ====== ===================== =============================
|
||||||
|
parameter type default Value description
|
||||||
|
==================== ====== ===================== =============================
|
||||||
|
``actions`` array None Actions to be executed.
|
||||||
|
==================== ====== ===================== =============================
|
||||||
|
|
||||||
|
The elements of actions array are:
|
||||||
|
|
||||||
|
==================== ====== ===================== =============================
|
||||||
|
parameter type default Value description
|
||||||
|
==================== ====== ===================== =============================
|
||||||
|
``action_type`` string None Action name defined in
|
||||||
|
setup.cfg(mandatory)
|
||||||
|
``resource_id`` string None Resource_id of the action.
|
||||||
|
``input_parameters`` object None Input_parameters of the
|
||||||
|
action(mandatory).
|
||||||
|
==================== ====== ===================== =============================
|
||||||
|
|
||||||
|
Efficacy Indicator
|
||||||
|
------------------
|
||||||
|
|
||||||
|
None
|
||||||
|
|
||||||
|
Algorithm
|
||||||
|
---------
|
||||||
|
|
||||||
|
This strategy create an action plan with a predefined set of actions.
|
||||||
|
|
||||||
|
How to use it ?
|
||||||
|
---------------
|
||||||
|
|
||||||
|
.. code-block:: shell
|
||||||
|
|
||||||
|
$ openstack optimize audittemplate create \
|
||||||
|
at1 unclassified --strategy actuator
|
||||||
|
|
||||||
|
$ openstack optimize audit create -a at1 \
|
||||||
|
-p actions='[{"action_type": "migrate", "resource_id": "56a40802-6fde-4b59-957c-c84baec7eaed", "input_parameters": {"migration_type": "live", "source_node": "s01"}}]'
|
||||||
|
|
||||||
|
External Links
|
||||||
|
--------------
|
||||||
|
|
||||||
|
None
|
||||||
154
doc/source/strategies/zone_migration.rst
Normal file
154
doc/source/strategies/zone_migration.rst
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
==============
|
||||||
|
Zone migration
|
||||||
|
==============
|
||||||
|
|
||||||
|
Synopsis
|
||||||
|
--------
|
||||||
|
|
||||||
|
**display name**: ``Zone migration``
|
||||||
|
|
||||||
|
**goal**: ``hardware_maintenance``
|
||||||
|
|
||||||
|
.. watcher-term:: watcher.decision_engine.strategy.strategies.zone_migration
|
||||||
|
|
||||||
|
Requirements
|
||||||
|
------------
|
||||||
|
|
||||||
|
Metrics
|
||||||
|
*******
|
||||||
|
|
||||||
|
None
|
||||||
|
|
||||||
|
Cluster data model
|
||||||
|
******************
|
||||||
|
|
||||||
|
Default Watcher's Compute cluster data model:
|
||||||
|
|
||||||
|
.. watcher-term:: watcher.decision_engine.model.collector.nova.NovaClusterDataModelCollector
|
||||||
|
|
||||||
|
Storage cluster data model is also required:
|
||||||
|
|
||||||
|
.. watcher-term:: watcher.decision_engine.model.collector.cinder.CinderClusterDataModelCollector
|
||||||
|
|
||||||
|
Actions
|
||||||
|
*******
|
||||||
|
|
||||||
|
|
||||||
|
Default Watcher's actions:
|
||||||
|
|
||||||
|
.. list-table::
|
||||||
|
:widths: 30 30
|
||||||
|
:header-rows: 1
|
||||||
|
|
||||||
|
* - action
|
||||||
|
- description
|
||||||
|
* - ``migrate``
|
||||||
|
- .. watcher-term:: watcher.applier.actions.migration.Migrate
|
||||||
|
* - ``volume_migrate``
|
||||||
|
- .. watcher-term:: watcher.applier.actions.volume_migration.VolumeMigrate
|
||||||
|
|
||||||
|
Planner
|
||||||
|
*******
|
||||||
|
|
||||||
|
Default Watcher's planner:
|
||||||
|
|
||||||
|
.. watcher-term:: watcher.decision_engine.planner.weight.WeightPlanner
|
||||||
|
|
||||||
|
Configuration
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Strategy parameters are:
|
||||||
|
|
||||||
|
======================== ======== ============= ==============================
|
||||||
|
parameter type default Value description
|
||||||
|
======================== ======== ============= ==============================
|
||||||
|
``compute_nodes`` array None Compute nodes to migrate.
|
||||||
|
``storage_pools`` array None Storage pools to migrate.
|
||||||
|
``parallel_total`` integer 6 The number of actions to be
|
||||||
|
run in parallel in total.
|
||||||
|
``parallel_per_node`` integer 2 The number of actions to be
|
||||||
|
run in parallel per compute
|
||||||
|
node.
|
||||||
|
``parallel_per_pool`` integer 2 The number of actions to be
|
||||||
|
run in parallel per storage
|
||||||
|
pool.
|
||||||
|
``priority`` object None List prioritizes instances
|
||||||
|
and volumes.
|
||||||
|
``with_attached_volume`` boolean False False: Instances will migrate
|
||||||
|
after all volumes migrate.
|
||||||
|
True: An instance will migrate
|
||||||
|
after the attached volumes
|
||||||
|
migrate.
|
||||||
|
======================== ======== ============= ==============================
|
||||||
|
|
||||||
|
The elements of compute_nodes array are:
|
||||||
|
|
||||||
|
============= ======= =============== =============================
|
||||||
|
parameter type default Value description
|
||||||
|
============= ======= =============== =============================
|
||||||
|
``src_node`` string None Compute node from which
|
||||||
|
instances migrate(mandatory).
|
||||||
|
``dst_node`` string None Compute node to which
|
||||||
|
instances migrate.
|
||||||
|
============= ======= =============== =============================
|
||||||
|
|
||||||
|
The elements of storage_pools array are:
|
||||||
|
|
||||||
|
============= ======= =============== ==============================
|
||||||
|
parameter type default Value description
|
||||||
|
============= ======= =============== ==============================
|
||||||
|
``src_pool`` string None Storage pool from which
|
||||||
|
volumes migrate(mandatory).
|
||||||
|
``dst_pool`` string None Storage pool to which
|
||||||
|
volumes migrate.
|
||||||
|
``src_type`` string None Source volume type(mandatory).
|
||||||
|
``dst_type`` string None Destination volume type
|
||||||
|
(mandatory).
|
||||||
|
============= ======= =============== ==============================
|
||||||
|
|
||||||
|
The elements of priority object are:
|
||||||
|
|
||||||
|
================ ======= =============== ======================
|
||||||
|
parameter type default Value description
|
||||||
|
================ ======= =============== ======================
|
||||||
|
``project`` array None Project names.
|
||||||
|
``compute_node`` array None Compute node names.
|
||||||
|
``storage_pool`` array None Storage pool names.
|
||||||
|
``compute`` enum None Instance attributes.
|
||||||
|
|compute|
|
||||||
|
``storage`` enum None Volume attributes.
|
||||||
|
|storage|
|
||||||
|
================ ======= =============== ======================
|
||||||
|
|
||||||
|
.. |compute| replace:: ["vcpu_num", "mem_size", "disk_size", "created_at"]
|
||||||
|
.. |storage| replace:: ["size", "created_at"]
|
||||||
|
|
||||||
|
Efficacy Indicator
|
||||||
|
------------------
|
||||||
|
|
||||||
|
.. watcher-func::
|
||||||
|
:format: literal_block
|
||||||
|
|
||||||
|
watcher.decision_engine.goal.efficacy.specs.HardwareMaintenance.get_global_efficacy_indicator
|
||||||
|
|
||||||
|
Algorithm
|
||||||
|
---------
|
||||||
|
|
||||||
|
For more information on the zone migration strategy please refer
|
||||||
|
to: http://specs.openstack.org/openstack/watcher-specs/specs/queens/implemented/zone-migration-strategy.html
|
||||||
|
|
||||||
|
How to use it ?
|
||||||
|
---------------
|
||||||
|
|
||||||
|
.. code-block:: shell
|
||||||
|
|
||||||
|
$ openstack optimize audittemplate create \
|
||||||
|
at1 hardware_maintenance --strategy zone_migration
|
||||||
|
|
||||||
|
$ openstack optimize audit create -a at1 \
|
||||||
|
-p compute_nodes='[{"src_node": "s01", "dst_node": "d01"}]'
|
||||||
|
|
||||||
|
External Links
|
||||||
|
--------------
|
||||||
|
|
||||||
|
None
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
[DEFAULT]
|
||||||
|
output_file = /etc/watcher/policy.yaml.sample
|
||||||
|
namespace = watcher
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
{
|
|
||||||
"admin_api": "role:admin or role:administrator",
|
|
||||||
"show_password": "!",
|
|
||||||
"default": "rule:admin_api",
|
|
||||||
|
|
||||||
"action:detail": "rule:default",
|
|
||||||
"action:get": "rule:default",
|
|
||||||
"action:get_all": "rule:default",
|
|
||||||
|
|
||||||
"action_plan:delete": "rule:default",
|
|
||||||
"action_plan:detail": "rule:default",
|
|
||||||
"action_plan:get": "rule:default",
|
|
||||||
"action_plan:get_all": "rule:default",
|
|
||||||
"action_plan:update": "rule:default",
|
|
||||||
|
|
||||||
"audit:create": "rule:default",
|
|
||||||
"audit:delete": "rule:default",
|
|
||||||
"audit:detail": "rule:default",
|
|
||||||
"audit:get": "rule:default",
|
|
||||||
"audit:get_all": "rule:default",
|
|
||||||
"audit:update": "rule:default",
|
|
||||||
|
|
||||||
"audit_template:create": "rule:default",
|
|
||||||
"audit_template:delete": "rule:default",
|
|
||||||
"audit_template:detail": "rule:default",
|
|
||||||
"audit_template:get": "rule:default",
|
|
||||||
"audit_template:get_all": "rule:default",
|
|
||||||
"audit_template:update": "rule:default",
|
|
||||||
|
|
||||||
"goal:detail": "rule:default",
|
|
||||||
"goal:get": "rule:default",
|
|
||||||
"goal:get_all": "rule:default",
|
|
||||||
|
|
||||||
"scoring_engine:detail": "rule:default",
|
|
||||||
"scoring_engine:get": "rule:default",
|
|
||||||
"scoring_engine:get_all": "rule:default",
|
|
||||||
|
|
||||||
"strategy:detail": "rule:default",
|
|
||||||
"strategy:get": "rule:default",
|
|
||||||
"strategy:get_all": "rule:default",
|
|
||||||
|
|
||||||
"service:detail": "rule:default",
|
|
||||||
"service:get": "rule:default",
|
|
||||||
"service:get_all": "rule:default"
|
|
||||||
}
|
|
||||||
15
playbooks/legacy/watcher-tempest-base-multinode/post.yaml
Normal file
15
playbooks/legacy/watcher-tempest-base-multinode/post.yaml
Normal 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
|
||||||
67
playbooks/legacy/watcher-tempest-base-multinode/run.yaml
Normal file
67
playbooks/legacy/watcher-tempest-base-multinode/run.yaml
Normal 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/devstack-gate
|
||||||
|
dest: devstack-gate
|
||||||
|
EOF
|
||||||
|
/usr/zuul-env/bin/zuul-cloner -m clonemap.yaml --cache-dir /opt/git \
|
||||||
|
https://opendev.org \
|
||||||
|
openstack/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 https://opendev.org/openstack/ceilometer
|
||||||
|
# Enable watcher devstack plugin.
|
||||||
|
enable_plugin watcher https://opendev.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 }}'
|
||||||
80
playbooks/legacy/watcherclient-tempest-functional/post.yaml
Normal file
80
playbooks/legacy/watcherclient-tempest-functional/post.yaml
Normal 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
|
||||||
64
playbooks/legacy/watcherclient-tempest-functional/run.yaml
Normal file
64
playbooks/legacy/watcherclient-tempest-functional/run.yaml
Normal 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/devstack-gate
|
||||||
|
dest: devstack-gate
|
||||||
|
EOF
|
||||||
|
/usr/zuul-env/bin/zuul-cloner -m clonemap.yaml --cache-dir /opt/git \
|
||||||
|
https://opendev.org \
|
||||||
|
openstack/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 https://opendev.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 }}'
|
||||||
@@ -29,14 +29,14 @@ Useful links
|
|||||||
|
|
||||||
* How to install: https://docs.openstack.org/rally/latest/install_and_upgrade/install.html
|
* How to install: https://docs.openstack.org/rally/latest/install_and_upgrade/install.html
|
||||||
|
|
||||||
* How to set Rally up and launch your first scenario: https://rally.readthedocs.io/en/latest/tutorial/step_1_setting_up_env_and_running_benchmark_from_samples.html
|
* 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/
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Adds audit scoper for storage data model, now watcher users can specify
|
||||||
|
audit scope for storage CDM in the same manner as compute scope.
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Adds baremetal data model in Watcher
|
||||||
6
releasenotes/notes/cdm-scoping-8d9c307bad46bfa1.yaml
Normal file
6
releasenotes/notes/cdm-scoping-8d9c307bad46bfa1.yaml
Normal 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.
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- Added a way to check state of strategy before audit's execution.
|
||||||
|
Administrator can use "watcher strategy state <strategy_name>" command
|
||||||
|
to get information about metrics' availability, datasource's availability
|
||||||
|
and CDM's availability.
|
||||||
@@ -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.
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Added storage capacity balance strategy.
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Added strategy "Zone migration" and it's goal "Hardware maintenance".
|
||||||
|
The strategy migrates many instances and volumes efficiently with
|
||||||
|
minimum downtime automatically.
|
||||||
@@ -24,7 +24,6 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from watcher import version as watcher_version
|
|
||||||
|
|
||||||
# If extensions (or modules to document with autodoc) are in another directory,
|
# If extensions (or modules to document with autodoc) are in another directory,
|
||||||
# add these directories to sys.path here. If the directory is relative to the
|
# add these directories to sys.path here. If the directory is relative to the
|
||||||
@@ -57,14 +56,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.
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
# Gérald LONLAS <g.lonlas@gmail.com>, 2016. #zanata
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: watcher 1.0.1.dev51\n"
|
|
||||||
"Report-Msgid-Bugs-To: \n"
|
|
||||||
"POT-Creation-Date: 2017-03-21 11:57+0000\n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
"PO-Revision-Date: 2016-10-22 06:44+0000\n"
|
|
||||||
"Last-Translator: Gérald LONLAS <g.lonlas@gmail.com>\n"
|
|
||||||
"Language-Team: French\n"
|
|
||||||
"Language: fr\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 "Contents:"
|
|
||||||
msgstr "Contenu :"
|
|
||||||
|
|
||||||
msgid "Current Series Release Notes"
|
|
||||||
msgstr "Note de la release actuelle"
|
|
||||||
|
|
||||||
msgid "New Features"
|
|
||||||
msgstr "Nouvelles fonctionnalités"
|
|
||||||
|
|
||||||
msgid "Newton Series Release Notes"
|
|
||||||
msgstr "Note de release pour Newton"
|
|
||||||
|
|
||||||
msgid "Welcome to watcher's Release Notes documentation!"
|
|
||||||
msgstr "Bienvenue dans la documentation de la note de Release de Watcher"
|
|
||||||
@@ -4,26 +4,26 @@
|
|||||||
|
|
||||||
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.25.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.36.0 # Apache-2.0
|
||||||
oslo.messaging>=5.29.0 # Apache-2.0
|
oslo.messaging>=5.29.0 # Apache-2.0
|
||||||
oslo.policy>=1.23.0 # Apache-2.0
|
oslo.policy>=1.30.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.28.1,>=1.24.0 # Apache-2.0
|
||||||
oslo.utils>=3.28.0 # Apache-2.0
|
oslo.utils>=3.33.0 # Apache-2.0
|
||||||
oslo.versionedobjects>=1.28.0 # Apache-2.0
|
oslo.versionedobjects>=1.31.2 # 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
|
||||||
pecan!=1.0.2,!=1.0.3,!=1.0.4,!=1.2,>=1.0.0 # BSD
|
pecan!=1.0.2,!=1.0.3,!=1.0.4,!=1.2,>=1.0.0 # BSD
|
||||||
@@ -31,18 +31,18 @@ PrettyTable<0.8,>=0.7.1 # BSD
|
|||||||
voluptuous>=0.8.9 # BSD License
|
voluptuous>=0.8.9 # BSD License
|
||||||
gnocchiclient>=3.3.1 # Apache-2.0
|
gnocchiclient>=3.3.1 # Apache-2.0
|
||||||
python-ceilometerclient>=2.5.0 # Apache-2.0
|
python-ceilometerclient>=2.5.0 # Apache-2.0
|
||||||
python-cinderclient>=3.2.0 # Apache-2.0
|
python-cinderclient>=3.3.0 # Apache-2.0
|
||||||
python-glanceclient>=2.8.0 # Apache-2.0
|
python-glanceclient>=2.8.0 # Apache-2.0
|
||||||
python-keystoneclient>=3.8.0 # Apache-2.0
|
python-keystoneclient>=3.8.0 # Apache-2.0
|
||||||
python-monascaclient>=1.7.0 # Apache-2.0
|
python-monascaclient>=1.7.0 # Apache-2.0
|
||||||
python-neutronclient>=6.3.0 # Apache-2.0
|
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>=2.2.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.16.0 # Apache-2.0
|
||||||
WebOb>=1.7.1 # MIT
|
WebOb>=1.7.1 # MIT
|
||||||
WSME>=0.8.0 # MIT
|
WSME>=0.8.0 # MIT
|
||||||
networkx<2.0,>=1.10 # BSD
|
networkx<2.0,>=1.10 # BSD
|
||||||
|
|||||||
10
setup.cfg
10
setup.cfg
@@ -32,6 +32,12 @@ setup-hooks =
|
|||||||
oslo.config.opts =
|
oslo.config.opts =
|
||||||
watcher = watcher.conf.opts:list_opts
|
watcher = watcher.conf.opts:list_opts
|
||||||
|
|
||||||
|
oslo.policy.policies =
|
||||||
|
watcher = watcher.common.policies:list_rules
|
||||||
|
|
||||||
|
oslo.policy.enforcer =
|
||||||
|
watcher = watcher.common.policy:get_enforcer
|
||||||
|
|
||||||
console_scripts =
|
console_scripts =
|
||||||
watcher-api = watcher.cmd.api:main
|
watcher-api = watcher.cmd.api:main
|
||||||
watcher-db-manage = watcher.cmd.dbmanage:main
|
watcher-db-manage = watcher.cmd.dbmanage:main
|
||||||
@@ -51,6 +57,7 @@ watcher_goals =
|
|||||||
airflow_optimization = watcher.decision_engine.goal.goals:AirflowOptimization
|
airflow_optimization = watcher.decision_engine.goal.goals:AirflowOptimization
|
||||||
noisy_neighbor = watcher.decision_engine.goal.goals:NoisyNeighborOptimization
|
noisy_neighbor = watcher.decision_engine.goal.goals:NoisyNeighborOptimization
|
||||||
saving_energy = watcher.decision_engine.goal.goals:SavingEnergy
|
saving_energy = watcher.decision_engine.goal.goals:SavingEnergy
|
||||||
|
hardware_maintenance = watcher.decision_engine.goal.goals:HardwareMaintenance
|
||||||
|
|
||||||
watcher_scoring_engines =
|
watcher_scoring_engines =
|
||||||
dummy_scorer = watcher.decision_engine.scoring.dummy_scorer:DummyScorer
|
dummy_scorer = watcher.decision_engine.scoring.dummy_scorer:DummyScorer
|
||||||
@@ -71,6 +78,8 @@ watcher_strategies =
|
|||||||
workload_balance = watcher.decision_engine.strategy.strategies.workload_balance:WorkloadBalance
|
workload_balance = watcher.decision_engine.strategy.strategies.workload_balance:WorkloadBalance
|
||||||
uniform_airflow = watcher.decision_engine.strategy.strategies.uniform_airflow:UniformAirflow
|
uniform_airflow = watcher.decision_engine.strategy.strategies.uniform_airflow:UniformAirflow
|
||||||
noisy_neighbor = watcher.decision_engine.strategy.strategies.noisy_neighbor:NoisyNeighbor
|
noisy_neighbor = watcher.decision_engine.strategy.strategies.noisy_neighbor:NoisyNeighbor
|
||||||
|
storage_capacity_balance = watcher.decision_engine.strategy.strategies.storage_capacity_balance:StorageCapacityBalance
|
||||||
|
zone_migration = watcher.decision_engine.strategy.strategies.zone_migration:ZoneMigration
|
||||||
|
|
||||||
watcher_actions =
|
watcher_actions =
|
||||||
migrate = watcher.applier.actions.migration:Migrate
|
migrate = watcher.applier.actions.migration:Migrate
|
||||||
@@ -91,6 +100,7 @@ watcher_planners =
|
|||||||
watcher_cluster_data_model_collectors =
|
watcher_cluster_data_model_collectors =
|
||||||
compute = watcher.decision_engine.model.collector.nova:NovaClusterDataModelCollector
|
compute = watcher.decision_engine.model.collector.nova:NovaClusterDataModelCollector
|
||||||
storage = watcher.decision_engine.model.collector.cinder:CinderClusterDataModelCollector
|
storage = watcher.decision_engine.model.collector.cinder:CinderClusterDataModelCollector
|
||||||
|
baremetal = watcher.decision_engine.model.collector.ironic:BaremetalClusterDataModelCollector
|
||||||
|
|
||||||
|
|
||||||
[pbr]
|
[pbr]
|
||||||
|
|||||||
@@ -7,15 +7,15 @@ doc8>=0.6.0 # Apache-2.0
|
|||||||
freezegun>=0.3.6 # Apache-2.0
|
freezegun>=0.3.6 # Apache-2.0
|
||||||
hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0
|
hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0
|
||||||
mock>=2.0.0 # BSD
|
mock>=2.0.0 # BSD
|
||||||
oslotest>=1.10.0 # Apache-2.0
|
oslotest>=3.2.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.18.1 # Apache-2.0
|
||||||
sphinx>=1.6.2 # BSD
|
sphinx!=1.6.6,>=1.6.2 # BSD
|
||||||
sphinxcontrib-pecanwsme>=0.8.0 # Apache-2.0
|
sphinxcontrib-pecanwsme>=0.8.0 # Apache-2.0
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
11
tox.ini
11
tox.ini
@@ -7,14 +7,14 @@ skipsdist = True
|
|||||||
usedevelop = True
|
usedevelop = True
|
||||||
whitelist_externals = find
|
whitelist_externals = find
|
||||||
rm
|
rm
|
||||||
install_command = pip install -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages}
|
install_command = pip install -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt?h=stable/queens} {opts} {packages}
|
||||||
setenv =
|
setenv =
|
||||||
VIRTUAL_ENV={envdir}
|
VIRTUAL_ENV={envdir}
|
||||||
deps = -r{toxinidir}/test-requirements.txt
|
deps = -r{toxinidir}/test-requirements.txt
|
||||||
commands =
|
commands =
|
||||||
rm -f .testrepository/times.dbm
|
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]
|
||||||
@@ -46,11 +46,16 @@ sitepackages = False
|
|||||||
commands =
|
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
|
||||||
|
|
||||||
|
[testenv:genpolicy]
|
||||||
|
commands =
|
||||||
|
oslopolicy-sample-generator --config-file etc/watcher/oslo-policy-generator/watcher-policy-generator.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= _
|
||||||
enable-extensions = H106,H203
|
enable-extensions = H106,H203,H904
|
||||||
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build,*sqlalchemy/alembic/versions/*,demo/,releasenotes
|
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build,*sqlalchemy/alembic/versions/*,demo/,releasenotes
|
||||||
|
|
||||||
[testenv:wheel]
|
[testenv:wheel]
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|
||||||
|
|||||||
@@ -341,7 +341,7 @@ class ActionsController(rest.RestController):
|
|||||||
|
|
||||||
@wsme_pecan.wsexpose(Action, body=Action, status_code=201)
|
@wsme_pecan.wsexpose(Action, body=Action, status_code=201)
|
||||||
def post(self, action):
|
def post(self, action):
|
||||||
"""Create a new action.
|
"""Create a new action(forbidden).
|
||||||
|
|
||||||
:param action: a action within the request body.
|
:param action: a action within the request body.
|
||||||
"""
|
"""
|
||||||
@@ -364,7 +364,7 @@ class ActionsController(rest.RestController):
|
|||||||
@wsme.validate(types.uuid, [ActionPatchType])
|
@wsme.validate(types.uuid, [ActionPatchType])
|
||||||
@wsme_pecan.wsexpose(Action, types.uuid, body=[ActionPatchType])
|
@wsme_pecan.wsexpose(Action, types.uuid, body=[ActionPatchType])
|
||||||
def patch(self, action_uuid, patch):
|
def patch(self, action_uuid, patch):
|
||||||
"""Update an existing action.
|
"""Update an existing action(forbidden).
|
||||||
|
|
||||||
:param action_uuid: UUID of a action.
|
:param action_uuid: UUID of a action.
|
||||||
:param patch: a json PATCH document to apply to this action.
|
:param patch: a json PATCH document to apply to this action.
|
||||||
@@ -401,7 +401,7 @@ class ActionsController(rest.RestController):
|
|||||||
|
|
||||||
@wsme_pecan.wsexpose(None, types.uuid, status_code=204)
|
@wsme_pecan.wsexpose(None, types.uuid, status_code=204)
|
||||||
def delete(self, action_uuid):
|
def delete(self, action_uuid):
|
||||||
"""Delete a action.
|
"""Delete a action(forbidden).
|
||||||
|
|
||||||
:param action_uuid: UUID of a action.
|
:param action_uuid: UUID of a action.
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -460,6 +460,15 @@ class ActionPlansController(rest.RestController):
|
|||||||
policy.enforce(context, 'action_plan:delete', action_plan,
|
policy.enforce(context, 'action_plan:delete', action_plan,
|
||||||
action='action_plan:delete')
|
action='action_plan:delete')
|
||||||
|
|
||||||
|
allowed_states = (ap_objects.State.SUCCEEDED,
|
||||||
|
ap_objects.State.RECOMMENDED,
|
||||||
|
ap_objects.State.FAILED,
|
||||||
|
ap_objects.State.SUPERSEDED,
|
||||||
|
ap_objects.State.CANCELLED)
|
||||||
|
if action_plan.state not in allowed_states:
|
||||||
|
raise exception.DeleteError(
|
||||||
|
state=action_plan.state)
|
||||||
|
|
||||||
action_plan.soft_delete()
|
action_plan.soft_delete()
|
||||||
|
|
||||||
@wsme.validate(types.uuid, [ActionPlanPatchType])
|
@wsme.validate(types.uuid, [ActionPlanPatchType])
|
||||||
|
|||||||
@@ -37,6 +37,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
|
||||||
@@ -49,9 +51,13 @@ from watcher.common import utils
|
|||||||
from watcher.decision_engine import rpcapi
|
from watcher.decision_engine import rpcapi
|
||||||
from watcher import objects
|
from watcher import objects
|
||||||
|
|
||||||
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
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 +117,30 @@ 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())
|
||||||
|
# No more than 63 characters
|
||||||
|
if len(self.name) > 63:
|
||||||
|
LOG.warning("Audit: %s length exceeds 63 characters",
|
||||||
|
self.name)
|
||||||
|
self.name = self.name[0:63]
|
||||||
|
|
||||||
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,
|
||||||
@@ -146,10 +175,10 @@ class AuditPatchType(types.JsonPatchType):
|
|||||||
|
|
||||||
|
|
||||||
class Audit(base.APIBase):
|
class Audit(base.APIBase):
|
||||||
"""API representation of a audit.
|
"""API representation of an audit.
|
||||||
|
|
||||||
This class enforces type checking and value constraints, and converts
|
This class enforces type checking and value constraints, and converts
|
||||||
between the internal object model and the API representation of a audit.
|
between the internal object model and the API representation of an audit.
|
||||||
"""
|
"""
|
||||||
_goal_uuid = None
|
_goal_uuid = None
|
||||||
_goal_name = None
|
_goal_name = None
|
||||||
@@ -233,6 +262,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"""
|
||||||
|
|
||||||
@@ -241,19 +273,19 @@ class Audit(base.APIBase):
|
|||||||
|
|
||||||
goal_uuid = wsme.wsproperty(
|
goal_uuid = wsme.wsproperty(
|
||||||
wtypes.text, _get_goal_uuid, _set_goal_uuid, mandatory=True)
|
wtypes.text, _get_goal_uuid, _set_goal_uuid, mandatory=True)
|
||||||
"""Goal UUID the audit template refers to"""
|
"""Goal UUID the audit refers to"""
|
||||||
|
|
||||||
goal_name = wsme.wsproperty(
|
goal_name = wsme.wsproperty(
|
||||||
wtypes.text, _get_goal_name, _set_goal_name, mandatory=False)
|
wtypes.text, _get_goal_name, _set_goal_name, mandatory=False)
|
||||||
"""The name of the goal this audit template refers to"""
|
"""The name of the goal this audit refers to"""
|
||||||
|
|
||||||
strategy_uuid = wsme.wsproperty(
|
strategy_uuid = wsme.wsproperty(
|
||||||
wtypes.text, _get_strategy_uuid, _set_strategy_uuid, mandatory=False)
|
wtypes.text, _get_strategy_uuid, _set_strategy_uuid, mandatory=False)
|
||||||
"""Strategy UUID the audit template refers to"""
|
"""Strategy UUID the audit refers to"""
|
||||||
|
|
||||||
strategy_name = wsme.wsproperty(
|
strategy_name = wsme.wsproperty(
|
||||||
wtypes.text, _get_strategy_name, _set_strategy_name, mandatory=False)
|
wtypes.text, _get_strategy_name, _set_strategy_name, mandatory=False)
|
||||||
"""The name of the strategy this audit template refers to"""
|
"""The name of the strategy this audit refers to"""
|
||||||
|
|
||||||
parameters = {wtypes.text: types.jsontype}
|
parameters = {wtypes.text: types.jsontype}
|
||||||
"""The strategy parameters for this audit"""
|
"""The strategy parameters for this audit"""
|
||||||
@@ -301,7 +333,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 +356,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 +516,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 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)
|
||||||
@@ -502,7 +535,7 @@ class AuditsController(rest.RestController):
|
|||||||
def post(self, audit_p):
|
def post(self, audit_p):
|
||||||
"""Create a new audit.
|
"""Create a new audit.
|
||||||
|
|
||||||
:param audit_p: a audit within the request body.
|
:param audit_p: an audit within the request body.
|
||||||
"""
|
"""
|
||||||
context = pecan.request.context
|
context = pecan.request.context
|
||||||
policy.enforce(context, 'audit:create',
|
policy.enforce(context, 'audit:create',
|
||||||
@@ -532,7 +565,7 @@ class AuditsController(rest.RestController):
|
|||||||
|
|
||||||
if no_schema and audit.parameters:
|
if no_schema and audit.parameters:
|
||||||
raise exception.Invalid(_('Specify parameters but no predefined '
|
raise exception.Invalid(_('Specify parameters but no predefined '
|
||||||
'strategy for audit template, or no '
|
'strategy for audit, or no '
|
||||||
'parameter spec in predefined strategy'))
|
'parameter spec in predefined strategy'))
|
||||||
|
|
||||||
audit_dict = audit.as_dict()
|
audit_dict = audit.as_dict()
|
||||||
@@ -551,11 +584,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 audit: UUID or name of an 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 +596,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,16 +633,23 @@ 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')
|
||||||
|
|
||||||
|
initial_state = audit_to_delete.state
|
||||||
|
new_state = objects.audit.State.DELETED
|
||||||
|
if not objects.audit.AuditStateTransitionManager(
|
||||||
|
).check_transition(initial_state, new_state):
|
||||||
|
raise exception.DeleteError(
|
||||||
|
state=initial_state)
|
||||||
|
|
||||||
audit_to_delete.soft_delete()
|
audit_to_delete.soft_delete()
|
||||||
|
|||||||
@@ -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,23 +131,25 @@ class AuditTemplatePostType(wtypes.Base):
|
|||||||
else:
|
else:
|
||||||
raise exception.InvalidGoal(goal=audit_template.goal)
|
raise exception.InvalidGoal(goal=audit_template.goal)
|
||||||
|
|
||||||
common_utils.Draft4Validator(
|
if audit_template.scope:
|
||||||
default.DefaultScope.DEFAULT_SCHEMA).validate(audit_template.scope)
|
common_utils.Draft4Validator(
|
||||||
|
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:
|
||||||
for resource in rule['exclude']:
|
for resource in rule['exclude']:
|
||||||
if 'host_aggregates' in resource:
|
if 'host_aggregates' in resource:
|
||||||
exclude_host_aggregates = True
|
exclude_host_aggregates = True
|
||||||
if include_host_aggregates and exclude_host_aggregates:
|
if include_host_aggregates and exclude_host_aggregates:
|
||||||
raise exception.Invalid(
|
raise exception.Invalid(
|
||||||
message=_(
|
message=_(
|
||||||
"host_aggregates can't be "
|
"host_aggregates can't be "
|
||||||
"included and excluded together"))
|
"included and excluded together"))
|
||||||
|
|
||||||
if audit_template.strategy:
|
if audit_template.strategy:
|
||||||
available_strategies = objects.Strategy.list(
|
available_strategies = objects.Strategy.list(
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ from watcher.api.controllers.v1 import utils as api_utils
|
|||||||
from watcher.common import exception
|
from watcher.common import exception
|
||||||
from watcher.common import policy
|
from watcher.common import policy
|
||||||
from watcher.common import utils as common_utils
|
from watcher.common import utils as common_utils
|
||||||
|
from watcher.decision_engine import rpcapi
|
||||||
from watcher import objects
|
from watcher import objects
|
||||||
|
|
||||||
|
|
||||||
@@ -205,6 +206,7 @@ class StrategiesController(rest.RestController):
|
|||||||
|
|
||||||
_custom_actions = {
|
_custom_actions = {
|
||||||
'detail': ['GET'],
|
'detail': ['GET'],
|
||||||
|
'state': ['GET'],
|
||||||
}
|
}
|
||||||
|
|
||||||
def _get_strategies_collection(self, filters, marker, limit, sort_key,
|
def _get_strategies_collection(self, filters, marker, limit, sort_key,
|
||||||
@@ -288,6 +290,26 @@ class StrategiesController(rest.RestController):
|
|||||||
return self._get_strategies_collection(
|
return self._get_strategies_collection(
|
||||||
filters, marker, limit, sort_key, sort_dir, expand, resource_url)
|
filters, marker, limit, sort_key, sort_dir, expand, resource_url)
|
||||||
|
|
||||||
|
@wsme_pecan.wsexpose(wtypes.text, wtypes.text)
|
||||||
|
def state(self, strategy):
|
||||||
|
"""Retrieve a inforamation about strategy requirements.
|
||||||
|
|
||||||
|
:param strategy: name of the strategy.
|
||||||
|
"""
|
||||||
|
context = pecan.request.context
|
||||||
|
policy.enforce(context, 'strategy:state', action='strategy:state')
|
||||||
|
parents = pecan.request.path.split('/')[:-1]
|
||||||
|
if parents[-2] != "strategies":
|
||||||
|
raise exception.HTTPNotFound
|
||||||
|
rpc_strategy = api_utils.get_resource('Strategy', strategy)
|
||||||
|
de_client = rpcapi.DecisionEngineAPI()
|
||||||
|
strategy_state = de_client.get_strategy_info(context,
|
||||||
|
rpc_strategy.name)
|
||||||
|
strategy_state.extend([{
|
||||||
|
'type': 'Name', 'state': rpc_strategy.name,
|
||||||
|
'mandatory': '', 'comment': ''}])
|
||||||
|
return strategy_state
|
||||||
|
|
||||||
@wsme_pecan.wsexpose(Strategy, wtypes.text)
|
@wsme_pecan.wsexpose(Strategy, wtypes.text)
|
||||||
def get_one(self, strategy):
|
def get_one(self, strategy):
|
||||||
"""Retrieve information about the given strategy.
|
"""Retrieve information about the given strategy.
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ class ContextHook(hooks.PecanHook):
|
|||||||
auth_url = headers.get('X-Auth-Url')
|
auth_url = headers.get('X-Auth-Url')
|
||||||
if auth_url is None:
|
if auth_url is None:
|
||||||
importutils.import_module('keystonemiddleware.auth_token')
|
importutils.import_module('keystonemiddleware.auth_token')
|
||||||
auth_url = cfg.CONF.keystone_authtoken.auth_uri
|
auth_url = cfg.CONF.keystone_authtoken.www_authenticate_uri
|
||||||
|
|
||||||
state.request.context = context.make_context(
|
state.request.context = context.make_context(
|
||||||
auth_token=auth_token,
|
auth_token=auth_token,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -112,18 +112,11 @@ 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("Nova client exception occurred while live "
|
||||||
LOG.debug("Live migration of instance %s failed. "
|
"migrating instance "
|
||||||
"Trying to live migrate using block migration."
|
"%(instance)s.Exception: %(exception)s",
|
||||||
% self.instance_uuid)
|
{'instance': self.instance_uuid, 'exception': e})
|
||||||
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 "
|
|
||||||
"migrating instance %s.Exception: %s" %
|
|
||||||
(self.instance_uuid, e))
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.exception(e)
|
LOG.exception(e)
|
||||||
LOG.critical("Unexpected error occurred. Migration failed for "
|
LOG.critical("Unexpected error occurred. Migration failed for "
|
||||||
|
|||||||
@@ -36,16 +36,19 @@ class VolumeMigrate(base.BaseAction):
|
|||||||
|
|
||||||
By using this action, you will be able to migrate cinder volume.
|
By using this action, you will be able to migrate cinder volume.
|
||||||
Migration type 'swap' can only be used for migrating attached volume.
|
Migration type 'swap' can only be used for migrating attached volume.
|
||||||
Migration type 'cold' can only be used for migrating detached volume.
|
Migration type 'migrate' can be used for migrating detached volume to
|
||||||
|
the pool of same volume type.
|
||||||
|
Migration type 'retype' can be used for changing volume type of
|
||||||
|
detached volume.
|
||||||
|
|
||||||
The action schema is::
|
The action schema is::
|
||||||
|
|
||||||
schema = Schema({
|
schema = Schema({
|
||||||
'resource_id': str, # should be a UUID
|
'resource_id': str, # should be a UUID
|
||||||
'migration_type': str, # choices -> "swap", "cold"
|
'migration_type': str, # choices -> "swap", "migrate","retype"
|
||||||
'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.
|
||||||
@@ -60,7 +63,8 @@ class VolumeMigrate(base.BaseAction):
|
|||||||
|
|
||||||
MIGRATION_TYPE = 'migration_type'
|
MIGRATION_TYPE = 'migration_type'
|
||||||
SWAP = 'swap'
|
SWAP = 'swap'
|
||||||
COLD = 'cold'
|
RETYPE = 'retype'
|
||||||
|
MIGRATE = 'migrate'
|
||||||
DESTINATION_NODE = "destination_node"
|
DESTINATION_NODE = "destination_node"
|
||||||
DESTINATION_TYPE = "destination_type"
|
DESTINATION_TYPE = "destination_type"
|
||||||
|
|
||||||
@@ -85,7 +89,7 @@ class VolumeMigrate(base.BaseAction):
|
|||||||
},
|
},
|
||||||
'migration_type': {
|
'migration_type': {
|
||||||
'type': 'string',
|
'type': 'string',
|
||||||
"enum": ["swap", "cold"]
|
"enum": ["swap", "retype", "migrate"]
|
||||||
},
|
},
|
||||||
'destination_node': {
|
'destination_node': {
|
||||||
"anyof": [
|
"anyof": [
|
||||||
@@ -127,20 +131,6 @@ class VolumeMigrate(base.BaseAction):
|
|||||||
def destination_type(self):
|
def destination_type(self):
|
||||||
return self.input_parameters.get(self.DESTINATION_TYPE)
|
return self.input_parameters.get(self.DESTINATION_TYPE)
|
||||||
|
|
||||||
def _cold_migrate(self, volume, dest_node, dest_type):
|
|
||||||
if not self.cinder_util.can_cold(volume, dest_node):
|
|
||||||
raise exception.Invalid(
|
|
||||||
message=(_("Invalid state for cold migration")))
|
|
||||||
|
|
||||||
if dest_node:
|
|
||||||
return self.cinder_util.migrate(volume, dest_node)
|
|
||||||
elif dest_type:
|
|
||||||
return self.cinder_util.retype(volume, dest_type)
|
|
||||||
else:
|
|
||||||
raise exception.Invalid(
|
|
||||||
message=(_("destination host or destination type is "
|
|
||||||
"required when migration type is cold")))
|
|
||||||
|
|
||||||
def _can_swap(self, volume):
|
def _can_swap(self, volume):
|
||||||
"""Judge volume can be swapped"""
|
"""Judge volume can be swapped"""
|
||||||
|
|
||||||
@@ -212,12 +202,14 @@ class VolumeMigrate(base.BaseAction):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
volume = self.cinder_util.get_volume(volume_id)
|
volume = self.cinder_util.get_volume(volume_id)
|
||||||
if self.migration_type == self.COLD:
|
if self.migration_type == self.SWAP:
|
||||||
return self._cold_migrate(volume, dest_node, dest_type)
|
|
||||||
elif self.migration_type == self.SWAP:
|
|
||||||
if dest_node:
|
if dest_node:
|
||||||
LOG.warning("dest_node is ignored")
|
LOG.warning("dest_node is ignored")
|
||||||
return self._swap_volume(volume, dest_type)
|
return self._swap_volume(volume, dest_type)
|
||||||
|
elif self.migration_type == self.RETYPE:
|
||||||
|
return self.cinder_util.retype(volume, dest_type)
|
||||||
|
elif self.migration_type == self.MIGRATE:
|
||||||
|
return self.cinder_util.migrate(volume, dest_node)
|
||||||
else:
|
else:
|
||||||
raise exception.Invalid(
|
raise exception.Invalid(
|
||||||
message=(_("Migration of type '%(migration_type)s' is not "
|
message=(_("Migration of type '%(migration_type)s' is not "
|
||||||
|
|||||||
@@ -20,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
|
||||||
|
|
||||||
|
|
||||||
@@ -40,10 +40,10 @@ def main():
|
|||||||
|
|
||||||
if host == '127.0.0.1':
|
if host == '127.0.0.1':
|
||||||
LOG.info('serving on 127.0.0.1:%(port)s, '
|
LOG.info('serving on 127.0.0.1:%(port)s, '
|
||||||
'view at %(protocol)s://127.0.0.1:%(port)s' %
|
'view at %(protocol)s://127.0.0.1:%(port)s',
|
||||||
dict(protocol=protocol, port=port))
|
dict(protocol=protocol, port=port))
|
||||||
else:
|
else:
|
||||||
LOG.info('serving on %(protocol)s://%(host)s:%(port)s' %
|
LOG.info('serving on %(protocol)s://%(host)s:%(port)s',
|
||||||
dict(protocol=protocol, host=host, port=port))
|
dict(protocol=protocol, host=host, port=port))
|
||||||
|
|
||||||
api_schedule = scheduling.APISchedulingService()
|
api_schedule = scheduling.APISchedulingService()
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -70,16 +70,18 @@ class CinderHelper(object):
|
|||||||
def get_volume_type_list(self):
|
def get_volume_type_list(self):
|
||||||
return self.cinder.volume_types.list()
|
return self.cinder.volume_types.list()
|
||||||
|
|
||||||
|
def get_volume_snapshots_list(self):
|
||||||
|
return self.cinder.volume_snapshots.list(
|
||||||
|
search_opts={'all_tenants': True})
|
||||||
|
|
||||||
def get_volume_type_by_backendname(self, backendname):
|
def get_volume_type_by_backendname(self, backendname):
|
||||||
|
"""Retrun a list of volume type"""
|
||||||
volume_type_list = self.get_volume_type_list()
|
volume_type_list = self.get_volume_type_list()
|
||||||
|
|
||||||
volume_type = [volume_type for volume_type in volume_type_list
|
volume_type = [volume_type.name for volume_type in volume_type_list
|
||||||
if volume_type.extra_specs.get(
|
if volume_type.extra_specs.get(
|
||||||
'volume_backend_name') == backendname]
|
'volume_backend_name') == backendname]
|
||||||
if volume_type:
|
return volume_type
|
||||||
return volume_type[0].name
|
|
||||||
else:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def get_volume(self, volume):
|
def get_volume(self, volume):
|
||||||
|
|
||||||
@@ -111,23 +113,6 @@ class CinderHelper(object):
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def can_cold(self, volume, host=None):
|
|
||||||
"""Judge volume can be migrated"""
|
|
||||||
can_cold = False
|
|
||||||
status = self.get_volume(volume).status
|
|
||||||
snapshot = self._has_snapshot(volume)
|
|
||||||
|
|
||||||
same_host = False
|
|
||||||
if host and getattr(volume, 'os-vol-host-attr:host') == host:
|
|
||||||
same_host = True
|
|
||||||
|
|
||||||
if (status == 'available' and
|
|
||||||
snapshot is False and
|
|
||||||
same_host is False):
|
|
||||||
can_cold = True
|
|
||||||
|
|
||||||
return can_cold
|
|
||||||
|
|
||||||
def get_deleting_volume(self, volume):
|
def get_deleting_volume(self, volume):
|
||||||
volume = self.get_volume(volume)
|
volume = self.get_volume(volume)
|
||||||
all_volume = self.get_volume_list()
|
all_volume = self.get_volume_list()
|
||||||
@@ -154,18 +139,19 @@ class CinderHelper(object):
|
|||||||
volume = self.get_volume(volume.id)
|
volume = self.get_volume(volume.id)
|
||||||
time.sleep(retry_interval)
|
time.sleep(retry_interval)
|
||||||
retry -= 1
|
retry -= 1
|
||||||
LOG.debug("retry count: %s" % retry)
|
LOG.debug("retry count: %s", retry)
|
||||||
LOG.debug("Waiting to complete deletion of volume %s" % volume.id)
|
LOG.debug("Waiting to complete deletion of volume %s", volume.id)
|
||||||
if self._can_get_volume(volume.id):
|
if self._can_get_volume(volume.id):
|
||||||
LOG.error("Volume deletion error: %s" % volume.id)
|
LOG.error("Volume deletion error: %s", volume.id)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
LOG.debug("Volume %s was deleted successfully." % volume.id)
|
LOG.debug("Volume %s was deleted successfully.", volume.id)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
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)
|
||||||
@@ -193,8 +179,7 @@ class CinderHelper(object):
|
|||||||
LOG.error(error_msg)
|
LOG.error(error_msg)
|
||||||
return False
|
return False
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"Volume migration succeeded : "
|
"Volume migration succeeded : volume %s is now on host '%s'.", (
|
||||||
"volume %s is now on host '%s'." % (
|
|
||||||
volume.id, host_name))
|
volume.id, host_name))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -203,13 +188,13 @@ class CinderHelper(object):
|
|||||||
volume = self.get_volume(volume)
|
volume = self.get_volume(volume)
|
||||||
dest_backend = self.backendname_from_poolname(dest_node)
|
dest_backend = self.backendname_from_poolname(dest_node)
|
||||||
dest_type = self.get_volume_type_by_backendname(dest_backend)
|
dest_type = self.get_volume_type_by_backendname(dest_backend)
|
||||||
if volume.volume_type != dest_type:
|
if volume.volume_type not in dest_type:
|
||||||
raise exception.Invalid(
|
raise exception.Invalid(
|
||||||
message=(_("Volume type must be same for migrating")))
|
message=(_("Volume type must be same for migrating")))
|
||||||
|
|
||||||
source_node = getattr(volume, 'os-vol-host-attr:host')
|
source_node = getattr(volume, 'os-vol-host-attr:host')
|
||||||
LOG.debug("Volume %s found on host '%s'."
|
LOG.debug("Volume %s found on host '%s'.",
|
||||||
% (volume.id, source_node))
|
(volume.id, source_node))
|
||||||
|
|
||||||
self.cinder.volumes.migrate_volume(
|
self.cinder.volumes.migrate_volume(
|
||||||
volume, dest_node, False, True)
|
volume, dest_node, False, True)
|
||||||
@@ -225,8 +210,8 @@ class CinderHelper(object):
|
|||||||
|
|
||||||
source_node = getattr(volume, 'os-vol-host-attr:host')
|
source_node = getattr(volume, 'os-vol-host-attr:host')
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"Volume %s found on host '%s'." % (
|
"Volume %s found on host '%s'.",
|
||||||
volume.id, source_node))
|
(volume.id, source_node))
|
||||||
|
|
||||||
self.cinder.volumes.retype(
|
self.cinder.volumes.retype(
|
||||||
volume, dest_type, "on-demand")
|
volume, dest_type, "on-demand")
|
||||||
@@ -248,14 +233,14 @@ class CinderHelper(object):
|
|||||||
LOG.debug('Waiting volume creation of {0}'.format(new_volume))
|
LOG.debug('Waiting volume creation of {0}'.format(new_volume))
|
||||||
time.sleep(retry_interval)
|
time.sleep(retry_interval)
|
||||||
retry -= 1
|
retry -= 1
|
||||||
LOG.debug("retry count: %s" % retry)
|
LOG.debug("retry count: %s", retry)
|
||||||
|
|
||||||
if getattr(new_volume, 'status') != 'available':
|
if getattr(new_volume, 'status') != 'available':
|
||||||
error_msg = (_("Failed to create volume '%(volume)s. ") %
|
error_msg = (_("Failed to create volume '%(volume)s. ") %
|
||||||
{'volume': new_volume.id})
|
{'volume': new_volume.id})
|
||||||
raise Exception(error_msg)
|
raise Exception(error_msg)
|
||||||
|
|
||||||
LOG.debug("Volume %s was created successfully." % new_volume)
|
LOG.debug("Volume %s was created successfully.", new_volume)
|
||||||
return new_volume
|
return new_volume
|
||||||
|
|
||||||
def delete_volume(self, volume):
|
def delete_volume(self, volume):
|
||||||
|
|||||||
@@ -83,8 +83,10 @@ class OpenStackClients(object):
|
|||||||
|
|
||||||
novaclient_version = self._get_client_option('nova', 'api_version')
|
novaclient_version = self._get_client_option('nova', 'api_version')
|
||||||
nova_endpoint_type = self._get_client_option('nova', 'endpoint_type')
|
nova_endpoint_type = self._get_client_option('nova', 'endpoint_type')
|
||||||
|
nova_region_name = self._get_client_option('nova', 'region_name')
|
||||||
self._nova = nvclient.Client(novaclient_version,
|
self._nova = nvclient.Client(novaclient_version,
|
||||||
endpoint_type=nova_endpoint_type,
|
endpoint_type=nova_endpoint_type,
|
||||||
|
region_name=nova_region_name,
|
||||||
session=self.session)
|
session=self.session)
|
||||||
return self._nova
|
return self._nova
|
||||||
|
|
||||||
@@ -96,8 +98,10 @@ class OpenStackClients(object):
|
|||||||
glanceclient_version = self._get_client_option('glance', 'api_version')
|
glanceclient_version = self._get_client_option('glance', 'api_version')
|
||||||
glance_endpoint_type = self._get_client_option('glance',
|
glance_endpoint_type = self._get_client_option('glance',
|
||||||
'endpoint_type')
|
'endpoint_type')
|
||||||
|
glance_region_name = self._get_client_option('glance', 'region_name')
|
||||||
self._glance = glclient.Client(glanceclient_version,
|
self._glance = glclient.Client(glanceclient_version,
|
||||||
interface=glance_endpoint_type,
|
interface=glance_endpoint_type,
|
||||||
|
region_name=glance_region_name,
|
||||||
session=self.session)
|
session=self.session)
|
||||||
return self._glance
|
return self._glance
|
||||||
|
|
||||||
@@ -110,8 +114,11 @@ class OpenStackClients(object):
|
|||||||
'api_version')
|
'api_version')
|
||||||
gnocchiclient_interface = self._get_client_option('gnocchi',
|
gnocchiclient_interface = self._get_client_option('gnocchi',
|
||||||
'endpoint_type')
|
'endpoint_type')
|
||||||
|
gnocchiclient_region_name = self._get_client_option('gnocchi',
|
||||||
|
'region_name')
|
||||||
adapter_options = {
|
adapter_options = {
|
||||||
"interface": gnocchiclient_interface
|
"interface": gnocchiclient_interface,
|
||||||
|
"region_name": gnocchiclient_region_name
|
||||||
}
|
}
|
||||||
|
|
||||||
self._gnocchi = gnclient.Client(gnocchiclient_version,
|
self._gnocchi = gnclient.Client(gnocchiclient_version,
|
||||||
@@ -127,8 +134,10 @@ class OpenStackClients(object):
|
|||||||
cinderclient_version = self._get_client_option('cinder', 'api_version')
|
cinderclient_version = self._get_client_option('cinder', 'api_version')
|
||||||
cinder_endpoint_type = self._get_client_option('cinder',
|
cinder_endpoint_type = self._get_client_option('cinder',
|
||||||
'endpoint_type')
|
'endpoint_type')
|
||||||
|
cinder_region_name = self._get_client_option('cinder', 'region_name')
|
||||||
self._cinder = ciclient.Client(cinderclient_version,
|
self._cinder = ciclient.Client(cinderclient_version,
|
||||||
endpoint_type=cinder_endpoint_type,
|
endpoint_type=cinder_endpoint_type,
|
||||||
|
region_name=cinder_region_name,
|
||||||
session=self.session)
|
session=self.session)
|
||||||
return self._cinder
|
return self._cinder
|
||||||
|
|
||||||
@@ -141,9 +150,12 @@ class OpenStackClients(object):
|
|||||||
'api_version')
|
'api_version')
|
||||||
ceilometer_endpoint_type = self._get_client_option('ceilometer',
|
ceilometer_endpoint_type = self._get_client_option('ceilometer',
|
||||||
'endpoint_type')
|
'endpoint_type')
|
||||||
|
ceilometer_region_name = self._get_client_option('ceilometer',
|
||||||
|
'region_name')
|
||||||
self._ceilometer = ceclient.get_client(
|
self._ceilometer = ceclient.get_client(
|
||||||
ceilometerclient_version,
|
ceilometerclient_version,
|
||||||
endpoint_type=ceilometer_endpoint_type,
|
endpoint_type=ceilometer_endpoint_type,
|
||||||
|
region_name=ceilometer_region_name,
|
||||||
session=self.session)
|
session=self.session)
|
||||||
return self._ceilometer
|
return self._ceilometer
|
||||||
|
|
||||||
@@ -156,6 +168,8 @@ class OpenStackClients(object):
|
|||||||
'monasca', 'api_version')
|
'monasca', 'api_version')
|
||||||
monascaclient_interface = self._get_client_option(
|
monascaclient_interface = self._get_client_option(
|
||||||
'monasca', 'interface')
|
'monasca', 'interface')
|
||||||
|
monascaclient_region = self._get_client_option(
|
||||||
|
'monasca', 'region_name')
|
||||||
token = self.session.get_token()
|
token = self.session.get_token()
|
||||||
watcher_clients_auth_config = CONF.get(_CLIENTS_AUTH_GROUP)
|
watcher_clients_auth_config = CONF.get(_CLIENTS_AUTH_GROUP)
|
||||||
service_type = 'monitoring'
|
service_type = 'monitoring'
|
||||||
@@ -172,7 +186,8 @@ class OpenStackClients(object):
|
|||||||
'password': watcher_clients_auth_config.password,
|
'password': watcher_clients_auth_config.password,
|
||||||
}
|
}
|
||||||
endpoint = self.session.get_endpoint(service_type=service_type,
|
endpoint = self.session.get_endpoint(service_type=service_type,
|
||||||
interface=monascaclient_interface)
|
interface=monascaclient_interface,
|
||||||
|
region_name=monascaclient_region)
|
||||||
|
|
||||||
self._monasca = monclient.Client(
|
self._monasca = monclient.Client(
|
||||||
monascaclient_version, endpoint, **monasca_kwargs)
|
monascaclient_version, endpoint, **monasca_kwargs)
|
||||||
@@ -188,9 +203,11 @@ class OpenStackClients(object):
|
|||||||
'api_version')
|
'api_version')
|
||||||
neutron_endpoint_type = self._get_client_option('neutron',
|
neutron_endpoint_type = self._get_client_option('neutron',
|
||||||
'endpoint_type')
|
'endpoint_type')
|
||||||
|
neutron_region_name = self._get_client_option('neutron', 'region_name')
|
||||||
|
|
||||||
self._neutron = netclient.Client(neutronclient_version,
|
self._neutron = netclient.Client(neutronclient_version,
|
||||||
endpoint_type=neutron_endpoint_type,
|
endpoint_type=neutron_endpoint_type,
|
||||||
|
region_name=neutron_region_name,
|
||||||
session=self.session)
|
session=self.session)
|
||||||
self._neutron.format = 'json'
|
self._neutron.format = 'json'
|
||||||
return self._neutron
|
return self._neutron
|
||||||
@@ -202,7 +219,9 @@ class OpenStackClients(object):
|
|||||||
|
|
||||||
ironicclient_version = self._get_client_option('ironic', 'api_version')
|
ironicclient_version = self._get_client_option('ironic', 'api_version')
|
||||||
endpoint_type = self._get_client_option('ironic', 'endpoint_type')
|
endpoint_type = self._get_client_option('ironic', 'endpoint_type')
|
||||||
|
ironic_region_name = self._get_client_option('ironic', 'region_name')
|
||||||
self._ironic = irclient.get_client(ironicclient_version,
|
self._ironic = irclient.get_client(ironicclient_version,
|
||||||
os_endpoint_type=endpoint_type,
|
os_endpoint_type=endpoint_type,
|
||||||
|
region_name=ironic_region_name,
|
||||||
session=self.session)
|
session=self.session)
|
||||||
return self._ironic
|
return self._ironic
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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):
|
||||||
@@ -305,7 +305,7 @@ class ActionFilterCombinationProhibited(Invalid):
|
|||||||
|
|
||||||
|
|
||||||
class UnsupportedActionType(UnsupportedError):
|
class UnsupportedActionType(UnsupportedError):
|
||||||
msg_fmt = _("Provided %(action_type) is not supported yet")
|
msg_fmt = _("Provided %(action_type)s is not supported yet")
|
||||||
|
|
||||||
|
|
||||||
class EfficacyIndicatorNotFound(ResourceNotFound):
|
class EfficacyIndicatorNotFound(ResourceNotFound):
|
||||||
@@ -332,6 +332,10 @@ class PatchError(Invalid):
|
|||||||
msg_fmt = _("Couldn't apply patch '%(patch)s'. Reason: %(reason)s")
|
msg_fmt = _("Couldn't apply patch '%(patch)s'. Reason: %(reason)s")
|
||||||
|
|
||||||
|
|
||||||
|
class DeleteError(Invalid):
|
||||||
|
msg_fmt = _("Couldn't delete when state is '%(state)s'.")
|
||||||
|
|
||||||
|
|
||||||
# decision engine
|
# decision engine
|
||||||
|
|
||||||
class WorkflowExecutionException(WatcherException):
|
class WorkflowExecutionException(WatcherException):
|
||||||
@@ -362,6 +366,14 @@ class ClusterEmpty(WatcherException):
|
|||||||
msg_fmt = _("The list of compute node(s) in the cluster is empty")
|
msg_fmt = _("The list of compute node(s) in the cluster is empty")
|
||||||
|
|
||||||
|
|
||||||
|
class ComputeClusterEmpty(WatcherException):
|
||||||
|
msg_fmt = _("The list of compute node(s) in the cluster is empty")
|
||||||
|
|
||||||
|
|
||||||
|
class StorageClusterEmpty(WatcherException):
|
||||||
|
msg_fmt = _("The list of storage node(s) in the cluster is empty")
|
||||||
|
|
||||||
|
|
||||||
class MetricCollectorNotDefined(WatcherException):
|
class MetricCollectorNotDefined(WatcherException):
|
||||||
msg_fmt = _("The metrics resource collector is not defined")
|
msg_fmt = _("The metrics resource collector is not defined")
|
||||||
|
|
||||||
@@ -405,6 +417,10 @@ class UnsupportedDataSource(UnsupportedError):
|
|||||||
"by strategy %(strategy)s")
|
"by strategy %(strategy)s")
|
||||||
|
|
||||||
|
|
||||||
|
class DataSourceNotAvailable(WatcherException):
|
||||||
|
msg_fmt = _("Datasource %(datasource)s is not available.")
|
||||||
|
|
||||||
|
|
||||||
class NoSuchMetricForHost(WatcherException):
|
class NoSuchMetricForHost(WatcherException):
|
||||||
msg_fmt = _("No %(metric)s metric for %(host)s found.")
|
msg_fmt = _("No %(metric)s metric for %(host)s found.")
|
||||||
|
|
||||||
@@ -469,6 +485,14 @@ class VolumeNotFound(StorageResourceNotFound):
|
|||||||
msg_fmt = _("The volume '%(name)s' could not be found")
|
msg_fmt = _("The volume '%(name)s' could not be found")
|
||||||
|
|
||||||
|
|
||||||
|
class BaremetalResourceNotFound(WatcherException):
|
||||||
|
msg_fmt = _("The baremetal resource '%(name)s' could not be found")
|
||||||
|
|
||||||
|
|
||||||
|
class IronicNodeNotFound(BaremetalResourceNotFound):
|
||||||
|
msg_fmt = _("The ironic node %(uuid)s could not be found")
|
||||||
|
|
||||||
|
|
||||||
class LoadingError(WatcherException):
|
class LoadingError(WatcherException):
|
||||||
msg_fmt = _("Error loading plugin '%(name)s'")
|
msg_fmt = _("Error loading plugin '%(name)s'")
|
||||||
|
|
||||||
|
|||||||
49
watcher/common/ironic_helper.py
Normal file
49
watcher/common/ironic_helper.py
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
# Copyright (c) 2017 ZTE Corporation
|
||||||
|
#
|
||||||
|
# Authors:Yumeng Bao <bao.yumeng@zte.com.cn>
|
||||||
|
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
# implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
from oslo_log import log
|
||||||
|
|
||||||
|
from watcher.common import clients
|
||||||
|
from watcher.common import exception
|
||||||
|
from watcher.common import utils
|
||||||
|
|
||||||
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class IronicHelper(object):
|
||||||
|
|
||||||
|
def __init__(self, osc=None):
|
||||||
|
""":param osc: an OpenStackClients instance"""
|
||||||
|
self.osc = osc if osc else clients.OpenStackClients()
|
||||||
|
self.ironic = self.osc.ironic()
|
||||||
|
|
||||||
|
def get_ironic_node_list(self):
|
||||||
|
return self.ironic.node.list()
|
||||||
|
|
||||||
|
def get_ironic_node_by_uuid(self, node_uuid):
|
||||||
|
"""Get ironic node by node UUID"""
|
||||||
|
try:
|
||||||
|
node = self.ironic.node.get(utils.Struct(uuid=node_uuid))
|
||||||
|
if not node:
|
||||||
|
raise exception.IronicNodeNotFound(uuid=node_uuid)
|
||||||
|
except Exception as exc:
|
||||||
|
LOG.exception(exc)
|
||||||
|
raise exception.IronicNodeNotFound(uuid=node_uuid)
|
||||||
|
# We need to pass an object with an 'uuid' attribute to make it work
|
||||||
|
return node
|
||||||
@@ -52,20 +52,31 @@ class NovaHelper(object):
|
|||||||
return self.nova.hypervisors.get(utils.Struct(id=node_id))
|
return self.nova.hypervisors.get(utils.Struct(id=node_id))
|
||||||
|
|
||||||
def get_compute_node_by_hostname(self, node_hostname):
|
def get_compute_node_by_hostname(self, node_hostname):
|
||||||
"""Get compute node by ID (*not* UUID)"""
|
"""Get compute node by hostname"""
|
||||||
# We need to pass an object with an 'id' attribute to make it work
|
|
||||||
try:
|
try:
|
||||||
compute_nodes = self.nova.hypervisors.search(node_hostname)
|
hypervisors = [hv for hv in self.get_compute_node_list()
|
||||||
if len(compute_nodes) != 1:
|
if hv.service['host'] == node_hostname]
|
||||||
|
if len(hypervisors) != 1:
|
||||||
|
# TODO(hidekazu)
|
||||||
|
# this may occur if VMware vCenter driver is used
|
||||||
raise exception.ComputeNodeNotFound(name=node_hostname)
|
raise exception.ComputeNodeNotFound(name=node_hostname)
|
||||||
|
else:
|
||||||
|
compute_nodes = self.nova.hypervisors.search(
|
||||||
|
hypervisors[0].hypervisor_hostname)
|
||||||
|
if len(compute_nodes) != 1:
|
||||||
|
raise exception.ComputeNodeNotFound(name=node_hostname)
|
||||||
|
|
||||||
return self.get_compute_node_by_id(compute_nodes[0].id)
|
return self.get_compute_node_by_id(compute_nodes[0].id)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
LOG.exception(exc)
|
LOG.exception(exc)
|
||||||
raise exception.ComputeNodeNotFound(name=node_hostname)
|
raise exception.ComputeNodeNotFound(name=node_hostname)
|
||||||
|
|
||||||
def get_instance_list(self):
|
def get_instance_list(self):
|
||||||
return self.nova.servers.list(search_opts={'all_tenants': True})
|
return self.nova.servers.list(search_opts={'all_tenants': True},
|
||||||
|
limit=-1)
|
||||||
|
|
||||||
|
def get_flavor_list(self):
|
||||||
|
return self.nova.flavors.list(**{'is_public': None})
|
||||||
|
|
||||||
def get_service(self, service_id):
|
def get_service(self, service_id):
|
||||||
return self.nova.services.find(id=service_id)
|
return self.nova.services.find(id=service_id)
|
||||||
@@ -96,7 +107,7 @@ class NovaHelper(object):
|
|||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
LOG.debug("confirm resize failed for the "
|
LOG.debug("confirm resize failed for the "
|
||||||
"instance %s" % instance.id)
|
"instance %s", instance.id)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def wait_for_volume_status(self, volume, status, timeout=60,
|
def wait_for_volume_status(self, volume, status, timeout=60,
|
||||||
@@ -144,19 +155,20 @@ class NovaHelper(object):
|
|||||||
"""
|
"""
|
||||||
new_image_name = ""
|
new_image_name = ""
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"Trying a non-live migrate of instance '%s' " % instance_id)
|
"Trying a non-live migrate of instance '%s' ", instance_id)
|
||||||
|
|
||||||
# Looking for the instance to migrate
|
# Looking for the instance to migrate
|
||||||
instance = self.find_instance(instance_id)
|
instance = self.find_instance(instance_id)
|
||||||
if not instance:
|
if not instance:
|
||||||
LOG.debug("Instance %s not found !" % instance_id)
|
LOG.debug("Instance %s not found !", instance_id)
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
# NOTE: If destination node is None call Nova API to migrate
|
# NOTE: If destination node is None call Nova API to migrate
|
||||||
# instance
|
# instance
|
||||||
host_name = getattr(instance, "OS-EXT-SRV-ATTR:host")
|
host_name = getattr(instance, "OS-EXT-SRV-ATTR:host")
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"Instance %s found on host '%s'." % (instance_id, host_name))
|
"Instance %(instance)s found on host '%(host)s'.",
|
||||||
|
{'instance': instance_id, 'host': host_name})
|
||||||
|
|
||||||
if dest_hostname is None:
|
if dest_hostname is None:
|
||||||
previous_status = getattr(instance, 'status')
|
previous_status = getattr(instance, 'status')
|
||||||
@@ -176,12 +188,12 @@ class NovaHelper(object):
|
|||||||
return False
|
return False
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"cold migration succeeded : "
|
"cold migration succeeded : "
|
||||||
"instance %s is now on host '%s'." % (
|
"instance %s is now on host '%s'.", (
|
||||||
instance_id, new_hostname))
|
instance_id, new_hostname))
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"cold migration for instance %s failed" % instance_id)
|
"cold migration for instance %s failed", instance_id)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not keep_original_image_name:
|
if not keep_original_image_name:
|
||||||
@@ -200,11 +212,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")
|
||||||
@@ -214,7 +222,7 @@ class NovaHelper(object):
|
|||||||
|
|
||||||
for network_name, network_conf_obj in addresses.items():
|
for network_name, network_conf_obj in addresses.items():
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"Extracting network configuration for network '%s'" %
|
"Extracting network configuration for network '%s'",
|
||||||
network_name)
|
network_name)
|
||||||
|
|
||||||
network_names_list.append(network_name)
|
network_names_list.append(network_name)
|
||||||
@@ -235,7 +243,7 @@ class NovaHelper(object):
|
|||||||
stopped_ok = self.stop_instance(instance_id)
|
stopped_ok = self.stop_instance(instance_id)
|
||||||
|
|
||||||
if not stopped_ok:
|
if not stopped_ok:
|
||||||
LOG.debug("Could not stop instance: %s" % instance_id)
|
LOG.debug("Could not stop instance: %s", instance_id)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Building the temporary image which will be used
|
# Building the temporary image which will be used
|
||||||
@@ -245,7 +253,7 @@ class NovaHelper(object):
|
|||||||
|
|
||||||
if not image_uuid:
|
if not image_uuid:
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"Could not build temporary image of instance: %s" %
|
"Could not build temporary image of instance: %s",
|
||||||
instance_id)
|
instance_id)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -293,8 +301,10 @@ class NovaHelper(object):
|
|||||||
blocks.append(
|
blocks.append(
|
||||||
block_device_mapping_v2_item)
|
block_device_mapping_v2_item)
|
||||||
|
|
||||||
LOG.debug("Detaching volume %s from instance: %s" % (
|
LOG.debug(
|
||||||
volume_id, instance_id))
|
"Detaching volume %(volume)s from "
|
||||||
|
"instance: %(instance)s",
|
||||||
|
{'volume': volume_id, 'instance': instance_id})
|
||||||
# volume.detach()
|
# volume.detach()
|
||||||
self.nova.volumes.delete_server_volume(instance_id,
|
self.nova.volumes.delete_server_volume(instance_id,
|
||||||
volume_id)
|
volume_id)
|
||||||
@@ -302,11 +312,12 @@ class NovaHelper(object):
|
|||||||
if not self.wait_for_volume_status(volume, "available", 5,
|
if not self.wait_for_volume_status(volume, "available", 5,
|
||||||
10):
|
10):
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"Could not detach volume %s from instance: %s" % (
|
"Could not detach volume %(volume)s "
|
||||||
volume_id, instance_id))
|
"from instance: %(instance)s",
|
||||||
|
{'volume': volume_id, 'instance': instance_id})
|
||||||
return False
|
return False
|
||||||
except ciexceptions.NotFound:
|
except ciexceptions.NotFound:
|
||||||
LOG.debug("Volume '%s' not found " % image_id)
|
LOG.debug("Volume '%s' not found ", image_id)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# We create the new instance from
|
# We create the new instance from
|
||||||
@@ -325,18 +336,21 @@ class NovaHelper(object):
|
|||||||
if not new_instance:
|
if not new_instance:
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"Could not create new instance "
|
"Could not create new instance "
|
||||||
"for non-live migration of instance %s" % instance_id)
|
"for non-live migration of instance %s", instance_id)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
LOG.debug("Detaching floating ip '%s' from instance %s" % (
|
LOG.debug(
|
||||||
floating_ip, instance_id))
|
"Detaching floating ip '%(floating_ip)s' "
|
||||||
|
"from instance %(instance)s",
|
||||||
|
{'floating_ip': floating_ip, 'instance': instance_id})
|
||||||
# We detach the floating ip from the current instance
|
# We detach the floating ip from the current instance
|
||||||
instance.remove_floating_ip(floating_ip)
|
instance.remove_floating_ip(floating_ip)
|
||||||
|
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"Attaching floating ip '%s' to the new instance %s" % (
|
"Attaching floating ip '%(ip)s' to the new "
|
||||||
floating_ip, new_instance.id))
|
"instance %(id)s",
|
||||||
|
{'ip': floating_ip, 'id': new_instance.id})
|
||||||
|
|
||||||
# We attach the same floating ip to the new instance
|
# We attach the same floating ip to the new instance
|
||||||
new_instance.add_floating_ip(floating_ip)
|
new_instance.add_floating_ip(floating_ip)
|
||||||
@@ -348,12 +362,12 @@ class NovaHelper(object):
|
|||||||
# Deleting the old instance (because no more useful)
|
# Deleting the old instance (because no more useful)
|
||||||
delete_ok = self.delete_instance(instance_id)
|
delete_ok = self.delete_instance(instance_id)
|
||||||
if not delete_ok:
|
if not delete_ok:
|
||||||
LOG.debug("Could not delete instance: %s" % instance_id)
|
LOG.debug("Could not delete instance: %s", instance_id)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"Instance %s has been successfully migrated "
|
"Instance %s has been successfully migrated "
|
||||||
"to new host '%s' and its new id is %s." % (
|
"to new host '%s' and its new id is %s.", (
|
||||||
instance_id, new_host_name, new_instance.id))
|
instance_id, new_host_name, new_instance.id))
|
||||||
|
|
||||||
return True
|
return True
|
||||||
@@ -370,8 +384,10 @@ class NovaHelper(object):
|
|||||||
:param instance_id: the unique id of the instance to resize.
|
:param instance_id: the unique id of the instance to resize.
|
||||||
:param flavor: the name or ID of the flavor to resize to.
|
:param flavor: the name or ID of the flavor to resize to.
|
||||||
"""
|
"""
|
||||||
LOG.debug("Trying a resize of instance %s to flavor '%s'" % (
|
LOG.debug(
|
||||||
instance_id, flavor))
|
"Trying a resize of instance %(instance)s to "
|
||||||
|
"flavor '%(flavor)s'",
|
||||||
|
{'instance': instance_id, 'flavor': flavor})
|
||||||
|
|
||||||
# Looking for the instance to resize
|
# Looking for the instance to resize
|
||||||
instance = self.find_instance(instance_id)
|
instance = self.find_instance(instance_id)
|
||||||
@@ -388,17 +404,17 @@ class NovaHelper(object):
|
|||||||
"instance %s. Exception: %s", instance_id, e)
|
"instance %s. Exception: %s", instance_id, e)
|
||||||
|
|
||||||
if not flavor_id:
|
if not flavor_id:
|
||||||
LOG.debug("Flavor not found: %s" % flavor)
|
LOG.debug("Flavor not found: %s", flavor)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not instance:
|
if not instance:
|
||||||
LOG.debug("Instance not found: %s" % instance_id)
|
LOG.debug("Instance not found: %s", instance_id)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
instance_status = getattr(instance, 'OS-EXT-STS:vm_state')
|
instance_status = getattr(instance, 'OS-EXT-STS:vm_state')
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"Instance %s is in '%s' status." % (instance_id,
|
"Instance %(id)s is in '%(status)s' status.",
|
||||||
instance_status))
|
{'id': instance_id, 'status': instance_status})
|
||||||
|
|
||||||
instance.resize(flavor=flavor_id)
|
instance.resize(flavor=flavor_id)
|
||||||
while getattr(instance,
|
while getattr(instance,
|
||||||
@@ -422,8 +438,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,22 +451,25 @@ 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 a live migrate instance %(instance)s ",
|
||||||
|
{'instance': instance_id})
|
||||||
|
|
||||||
# Looking for the instance to migrate
|
# Looking for the instance to migrate
|
||||||
instance = self.find_instance(instance_id)
|
instance = self.find_instance(instance_id)
|
||||||
if not instance:
|
if not instance:
|
||||||
LOG.debug("Instance not found: %s" % instance_id)
|
LOG.debug("Instance not found: %s", instance_id)
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
host_name = getattr(instance, 'OS-EXT-SRV-ATTR:host')
|
host_name = getattr(instance, 'OS-EXT-SRV-ATTR:host')
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"Instance %s found on host '%s'." % (instance_id, host_name))
|
"Instance %(instance)s found on host '%(host)s'.",
|
||||||
|
{'instance': instance_id, 'host': 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)
|
||||||
|
|
||||||
@@ -469,7 +487,7 @@ class NovaHelper(object):
|
|||||||
if host_name != new_hostname and instance.status == 'ACTIVE':
|
if host_name != new_hostname and instance.status == 'ACTIVE':
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"Live migration succeeded : "
|
"Live migration succeeded : "
|
||||||
"instance %s is now on host '%s'." % (
|
"instance %s is now on host '%s'.", (
|
||||||
instance_id, new_hostname))
|
instance_id, new_hostname))
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
@@ -479,6 +497,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,
|
||||||
@@ -493,13 +514,13 @@ class NovaHelper(object):
|
|||||||
|
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"Live migration succeeded : "
|
"Live migration succeeded : "
|
||||||
"instance %s is now on host '%s'." % (
|
"instance %(instance)s is now on host '%(host)s'.",
|
||||||
instance_id, host_name))
|
{'instance': instance_id, 'host': host_name})
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def abort_live_migrate(self, instance_id, source, destination, retry=240):
|
def abort_live_migrate(self, instance_id, source, destination, retry=240):
|
||||||
LOG.debug("Aborting live migration of instance %s" % instance_id)
|
LOG.debug("Aborting live migration of instance %s", instance_id)
|
||||||
migration = self.get_running_migration(instance_id)
|
migration = self.get_running_migration(instance_id)
|
||||||
if migration:
|
if migration:
|
||||||
migration_id = getattr(migration[0], "id")
|
migration_id = getattr(migration[0], "id")
|
||||||
@@ -512,7 +533,7 @@ class NovaHelper(object):
|
|||||||
LOG.exception(e)
|
LOG.exception(e)
|
||||||
else:
|
else:
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"No running migrations found for instance %s" % instance_id)
|
"No running migrations found for instance %s", instance_id)
|
||||||
|
|
||||||
while retry:
|
while retry:
|
||||||
instance = self.nova.servers.get(instance_id)
|
instance = self.nova.servers.get(instance_id)
|
||||||
@@ -543,16 +564,17 @@ 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:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def set_host_offline(self, hostname):
|
def set_host_offline(self, hostname):
|
||||||
# See API on http://developer.openstack.org/api-ref-compute-v2.1.html
|
# See API on https://developer.openstack.org/api-ref/compute/
|
||||||
# especially the PUT request
|
# especially the PUT request
|
||||||
# regarding this resource : /v2.1/os-hosts/{host_name}
|
# regarding this resource : /v2.1/os-hosts/{host_name}
|
||||||
#
|
#
|
||||||
@@ -576,7 +598,7 @@ class NovaHelper(object):
|
|||||||
host = self.nova.hosts.get(hostname)
|
host = self.nova.hosts.get(hostname)
|
||||||
|
|
||||||
if not host:
|
if not host:
|
||||||
LOG.debug("host not found: %s" % hostname)
|
LOG.debug("host not found: %s", hostname)
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
host[0].update(
|
host[0].update(
|
||||||
@@ -598,18 +620,19 @@ class NovaHelper(object):
|
|||||||
key-value pairs to associate to the image as metadata.
|
key-value pairs to associate to the image as metadata.
|
||||||
"""
|
"""
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"Trying to create an image from instance %s ..." % instance_id)
|
"Trying to create an image from instance %s ...", instance_id)
|
||||||
|
|
||||||
# Looking for the instance
|
# Looking for the instance
|
||||||
instance = self.find_instance(instance_id)
|
instance = self.find_instance(instance_id)
|
||||||
|
|
||||||
if not instance:
|
if not instance:
|
||||||
LOG.debug("Instance not found: %s" % instance_id)
|
LOG.debug("Instance not found: %s", instance_id)
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
host_name = getattr(instance, 'OS-EXT-SRV-ATTR:host')
|
host_name = getattr(instance, 'OS-EXT-SRV-ATTR:host')
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"Instance %s found on host '%s'." % (instance_id, host_name))
|
"Instance %(instance)s found on host '%(host)s'.",
|
||||||
|
{'instance': instance_id, 'host': host_name})
|
||||||
|
|
||||||
# We need to wait for an appropriate status
|
# We need to wait for an appropriate status
|
||||||
# of the instance before we can build an image from it
|
# of the instance before we can build an image from it
|
||||||
@@ -636,14 +659,15 @@ class NovaHelper(object):
|
|||||||
if not image:
|
if not image:
|
||||||
break
|
break
|
||||||
status = image.status
|
status = image.status
|
||||||
LOG.debug("Current image status: %s" % status)
|
LOG.debug("Current image status: %s", status)
|
||||||
|
|
||||||
if not image:
|
if not image:
|
||||||
LOG.debug("Image not found: %s" % image_uuid)
|
LOG.debug("Image not found: %s", image_uuid)
|
||||||
else:
|
else:
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"Image %s successfully created for instance %s" % (
|
"Image %(image)s successfully created for "
|
||||||
image_uuid, instance_id))
|
"instance %(instance)s",
|
||||||
|
{'image': image_uuid, 'instance': instance_id})
|
||||||
return image_uuid
|
return image_uuid
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -652,16 +676,16 @@ class NovaHelper(object):
|
|||||||
|
|
||||||
:param instance_id: the unique id of the instance to delete.
|
:param instance_id: the unique id of the instance to delete.
|
||||||
"""
|
"""
|
||||||
LOG.debug("Trying to remove instance %s ..." % instance_id)
|
LOG.debug("Trying to remove instance %s ...", instance_id)
|
||||||
|
|
||||||
instance = self.find_instance(instance_id)
|
instance = self.find_instance(instance_id)
|
||||||
|
|
||||||
if not instance:
|
if not instance:
|
||||||
LOG.debug("Instance not found: %s" % instance_id)
|
LOG.debug("Instance not found: %s", instance_id)
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
self.nova.servers.delete(instance_id)
|
self.nova.servers.delete(instance_id)
|
||||||
LOG.debug("Instance %s removed." % instance_id)
|
LOG.debug("Instance %s removed.", instance_id)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def stop_instance(self, instance_id):
|
def stop_instance(self, instance_id):
|
||||||
@@ -669,21 +693,21 @@ class NovaHelper(object):
|
|||||||
|
|
||||||
:param instance_id: the unique id of the instance to stop.
|
:param instance_id: the unique id of the instance to stop.
|
||||||
"""
|
"""
|
||||||
LOG.debug("Trying to stop instance %s ..." % instance_id)
|
LOG.debug("Trying to stop instance %s ...", instance_id)
|
||||||
|
|
||||||
instance = self.find_instance(instance_id)
|
instance = self.find_instance(instance_id)
|
||||||
|
|
||||||
if not instance:
|
if not instance:
|
||||||
LOG.debug("Instance not found: %s" % instance_id)
|
LOG.debug("Instance not found: %s", instance_id)
|
||||||
return False
|
return False
|
||||||
elif getattr(instance, 'OS-EXT-STS:vm_state') == "stopped":
|
elif getattr(instance, 'OS-EXT-STS:vm_state') == "stopped":
|
||||||
LOG.debug("Instance has been stopped: %s" % instance_id)
|
LOG.debug("Instance has been stopped: %s", instance_id)
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
self.nova.servers.stop(instance_id)
|
self.nova.servers.stop(instance_id)
|
||||||
|
|
||||||
if self.wait_for_instance_state(instance, "stopped", 8, 10):
|
if self.wait_for_instance_state(instance, "stopped", 8, 10):
|
||||||
LOG.debug("Instance %s stopped." % instance_id)
|
LOG.debug("Instance %s stopped.", instance_id)
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
@@ -724,11 +748,11 @@ class NovaHelper(object):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
while instance.status not in status_list and retry:
|
while instance.status not in status_list and retry:
|
||||||
LOG.debug("Current instance status: %s" % instance.status)
|
LOG.debug("Current instance status: %s", instance.status)
|
||||||
time.sleep(sleep)
|
time.sleep(sleep)
|
||||||
instance = self.nova.servers.get(instance.id)
|
instance = self.nova.servers.get(instance.id)
|
||||||
retry -= 1
|
retry -= 1
|
||||||
LOG.debug("Current instance status: %s" % instance.status)
|
LOG.debug("Current instance status: %s", instance.status)
|
||||||
return instance.status in status_list
|
return instance.status in status_list
|
||||||
|
|
||||||
def create_instance(self, node_id, inst_name="test", image_id=None,
|
def create_instance(self, node_id, inst_name="test", image_id=None,
|
||||||
@@ -744,35 +768,34 @@ class NovaHelper(object):
|
|||||||
It returns the unique id of the created instance.
|
It returns the unique id of the created instance.
|
||||||
"""
|
"""
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"Trying to create new instance '%s' "
|
"Trying to create new instance '%(inst)s' "
|
||||||
"from image '%s' with flavor '%s' ..." % (
|
"from image '%(image)s' with flavor '%(flavor)s' ...",
|
||||||
inst_name, image_id, flavor_name))
|
{'inst': inst_name, 'image': image_id, 'flavor': flavor_name})
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.nova.keypairs.findall(name=keypair_name)
|
self.nova.keypairs.findall(name=keypair_name)
|
||||||
except nvexceptions.NotFound:
|
except nvexceptions.NotFound:
|
||||||
LOG.debug("Key pair '%s' not found " % keypair_name)
|
LOG.debug("Key pair '%s' not found ", keypair_name)
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
image = self.glance.images.get(image_id)
|
image = self.glance.images.get(image_id)
|
||||||
except glexceptions.NotFound:
|
except glexceptions.NotFound:
|
||||||
LOG.debug("Image '%s' not found " % image_id)
|
LOG.debug("Image '%s' not found ", image_id)
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
flavor = self.nova.flavors.find(name=flavor_name)
|
flavor = self.nova.flavors.find(name=flavor_name)
|
||||||
except nvexceptions.NotFound:
|
except nvexceptions.NotFound:
|
||||||
LOG.debug("Flavor '%s' not found " % flavor_name)
|
LOG.debug("Flavor '%s' not found ", flavor_name)
|
||||||
return
|
return
|
||||||
|
|
||||||
# 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
|
||||||
|
|
||||||
net_list = list()
|
net_list = list()
|
||||||
@@ -781,7 +804,7 @@ class NovaHelper(object):
|
|||||||
nic_id = self.get_network_id_from_name(network_name)
|
nic_id = self.get_network_id_from_name(network_name)
|
||||||
|
|
||||||
if not nic_id:
|
if not nic_id:
|
||||||
LOG.debug("Network '%s' not found " % network_name)
|
LOG.debug("Network '%s' not found ", network_name)
|
||||||
return
|
return
|
||||||
net_obj = {"net-id": nic_id}
|
net_obj = {"net-id": nic_id}
|
||||||
net_list.append(net_obj)
|
net_list.append(net_obj)
|
||||||
@@ -807,17 +830,27 @@ class NovaHelper(object):
|
|||||||
if create_new_floating_ip and instance.status == 'ACTIVE':
|
if create_new_floating_ip and instance.status == 'ACTIVE':
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"Creating a new floating IP"
|
"Creating a new floating IP"
|
||||||
" for instance '%s'" % instance.id)
|
" for instance '%s'", instance.id)
|
||||||
# Creating floating IP for the new instance
|
# Creating floating IP for the new instance
|
||||||
floating_ip = self.nova.floating_ips.create()
|
floating_ip = self.nova.floating_ips.create()
|
||||||
|
|
||||||
instance.add_floating_ip(floating_ip)
|
instance.add_floating_ip(floating_ip)
|
||||||
|
|
||||||
LOG.debug("Instance %s associated to Floating IP '%s'" % (
|
LOG.debug(
|
||||||
instance.id, floating_ip.ip))
|
"Instance %(instance)s associated to "
|
||||||
|
"Floating IP '%(ip)s'",
|
||||||
|
{'instance': instance.id, 'ip': floating_ip.ip})
|
||||||
|
|
||||||
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)
|
||||||
@@ -839,8 +872,9 @@ class NovaHelper(object):
|
|||||||
|
|
||||||
def get_instances_by_node(self, host):
|
def get_instances_by_node(self, host):
|
||||||
return [instance for instance in
|
return [instance for instance in
|
||||||
self.nova.servers.list(search_opts={"all_tenants": True})
|
self.nova.servers.list(search_opts={"all_tenants": True,
|
||||||
if self.get_hostname(instance) == host]
|
"host": host},
|
||||||
|
limit=-1)]
|
||||||
|
|
||||||
def get_hostname(self, instance):
|
def get_hostname(self, instance):
|
||||||
return str(getattr(instance, 'OS-EXT-SRV-ATTR:host'))
|
return str(getattr(instance, 'OS-EXT-SRV-ATTR:host'))
|
||||||
@@ -880,7 +914,7 @@ class NovaHelper(object):
|
|||||||
LOG.debug('Waiting volume update to {0}'.format(new_volume))
|
LOG.debug('Waiting volume update to {0}'.format(new_volume))
|
||||||
time.sleep(retry_interval)
|
time.sleep(retry_interval)
|
||||||
retry -= 1
|
retry -= 1
|
||||||
LOG.debug("retry count: %s" % retry)
|
LOG.debug("retry count: %s", retry)
|
||||||
if getattr(new_volume, 'status') != "in-use":
|
if getattr(new_volume, 'status') != "in-use":
|
||||||
LOG.error("Volume update retry timeout or error")
|
LOG.error("Volume update retry timeout or error")
|
||||||
return False
|
return False
|
||||||
@@ -888,5 +922,6 @@ class NovaHelper(object):
|
|||||||
host_name = getattr(new_volume, "os-vol-host-attr:host")
|
host_name = getattr(new_volume, "os-vol-host-attr:host")
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"Volume update succeeded : "
|
"Volume update succeeded : "
|
||||||
"Volume %s is now on host '%s'." % (new_volume.id, host_name))
|
"Volume %s is now on host '%s'.",
|
||||||
|
(new_volume.id, host_name))
|
||||||
return True
|
return True
|
||||||
|
|||||||
37
watcher/common/policies/__init__.py
Normal file
37
watcher/common/policies/__init__.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import itertools
|
||||||
|
|
||||||
|
from watcher.common.policies import action
|
||||||
|
from watcher.common.policies import action_plan
|
||||||
|
from watcher.common.policies import audit
|
||||||
|
from watcher.common.policies import audit_template
|
||||||
|
from watcher.common.policies import base
|
||||||
|
from watcher.common.policies import goal
|
||||||
|
from watcher.common.policies import scoring_engine
|
||||||
|
from watcher.common.policies import service
|
||||||
|
from watcher.common.policies import strategy
|
||||||
|
|
||||||
|
|
||||||
|
def list_rules():
|
||||||
|
return itertools.chain(
|
||||||
|
base.list_rules(),
|
||||||
|
action.list_rules(),
|
||||||
|
action_plan.list_rules(),
|
||||||
|
audit.list_rules(),
|
||||||
|
audit_template.list_rules(),
|
||||||
|
goal.list_rules(),
|
||||||
|
scoring_engine.list_rules(),
|
||||||
|
service.list_rules(),
|
||||||
|
strategy.list_rules(),
|
||||||
|
)
|
||||||
57
watcher/common/policies/action.py
Normal file
57
watcher/common/policies/action.py
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from oslo_policy import policy
|
||||||
|
|
||||||
|
from watcher.common.policies import base
|
||||||
|
|
||||||
|
ACTION = 'action:%s'
|
||||||
|
|
||||||
|
rules = [
|
||||||
|
policy.DocumentedRuleDefault(
|
||||||
|
name=ACTION % 'detail',
|
||||||
|
check_str=base.RULE_ADMIN_API,
|
||||||
|
description='Retrieve a list of actions with detail.',
|
||||||
|
operations=[
|
||||||
|
{
|
||||||
|
'path': '/v1/actions/detail',
|
||||||
|
'method': 'GET'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
),
|
||||||
|
policy.DocumentedRuleDefault(
|
||||||
|
name=ACTION % 'get',
|
||||||
|
check_str=base.RULE_ADMIN_API,
|
||||||
|
description='Retrieve information about a given action.',
|
||||||
|
operations=[
|
||||||
|
{
|
||||||
|
'path': '/v1/actions/{action_id}',
|
||||||
|
'method': 'GET'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
),
|
||||||
|
policy.DocumentedRuleDefault(
|
||||||
|
name=ACTION % 'get_all',
|
||||||
|
check_str=base.RULE_ADMIN_API,
|
||||||
|
description='Retrieve a list of all actions.',
|
||||||
|
operations=[
|
||||||
|
{
|
||||||
|
'path': '/v1/actions',
|
||||||
|
'method': 'GET'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def list_rules():
|
||||||
|
return rules
|
||||||
79
watcher/common/policies/action_plan.py
Normal file
79
watcher/common/policies/action_plan.py
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from oslo_policy import policy
|
||||||
|
|
||||||
|
from watcher.common.policies import base
|
||||||
|
|
||||||
|
ACTION_PLAN = 'action_plan:%s'
|
||||||
|
|
||||||
|
rules = [
|
||||||
|
policy.DocumentedRuleDefault(
|
||||||
|
name=ACTION_PLAN % 'delete',
|
||||||
|
check_str=base.RULE_ADMIN_API,
|
||||||
|
description='Delete an action plan.',
|
||||||
|
operations=[
|
||||||
|
{
|
||||||
|
'path': '/v1/action_plans/{action_plan_uuid}',
|
||||||
|
'method': 'DELETE'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
),
|
||||||
|
policy.DocumentedRuleDefault(
|
||||||
|
name=ACTION_PLAN % 'detail',
|
||||||
|
check_str=base.RULE_ADMIN_API,
|
||||||
|
description='Retrieve a list of action plans with detail.',
|
||||||
|
operations=[
|
||||||
|
{
|
||||||
|
'path': '/v1/action_plans/detail',
|
||||||
|
'method': 'GET'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
),
|
||||||
|
policy.DocumentedRuleDefault(
|
||||||
|
name=ACTION_PLAN % 'get',
|
||||||
|
check_str=base.RULE_ADMIN_API,
|
||||||
|
description='Get an action plan.',
|
||||||
|
operations=[
|
||||||
|
{
|
||||||
|
'path': '/v1/action_plans/{action_plan_id}',
|
||||||
|
'method': 'GET'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
),
|
||||||
|
policy.DocumentedRuleDefault(
|
||||||
|
name=ACTION_PLAN % 'get_all',
|
||||||
|
check_str=base.RULE_ADMIN_API,
|
||||||
|
description='Get all action plans.',
|
||||||
|
operations=[
|
||||||
|
{
|
||||||
|
'path': '/v1/action_plans',
|
||||||
|
'method': 'GET'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
),
|
||||||
|
policy.DocumentedRuleDefault(
|
||||||
|
name=ACTION_PLAN % 'update',
|
||||||
|
check_str=base.RULE_ADMIN_API,
|
||||||
|
description='Update an action plans.',
|
||||||
|
operations=[
|
||||||
|
{
|
||||||
|
'path': '/v1/action_plans/{action_plan_uuid}',
|
||||||
|
'method': 'PATCH'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def list_rules():
|
||||||
|
return rules
|
||||||
90
watcher/common/policies/audit.py
Normal file
90
watcher/common/policies/audit.py
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from oslo_policy import policy
|
||||||
|
|
||||||
|
from watcher.common.policies import base
|
||||||
|
|
||||||
|
AUDIT = 'audit:%s'
|
||||||
|
|
||||||
|
rules = [
|
||||||
|
policy.DocumentedRuleDefault(
|
||||||
|
name=AUDIT % 'create',
|
||||||
|
check_str=base.RULE_ADMIN_API,
|
||||||
|
description='Create a new audit.',
|
||||||
|
operations=[
|
||||||
|
{
|
||||||
|
'path': '/v1/audits',
|
||||||
|
'method': 'POST'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
),
|
||||||
|
policy.DocumentedRuleDefault(
|
||||||
|
name=AUDIT % 'delete',
|
||||||
|
check_str=base.RULE_ADMIN_API,
|
||||||
|
description='Delete an audit.',
|
||||||
|
operations=[
|
||||||
|
{
|
||||||
|
'path': '/v1/audits/{audit_uuid}',
|
||||||
|
'method': 'DELETE'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
),
|
||||||
|
policy.DocumentedRuleDefault(
|
||||||
|
name=AUDIT % 'detail',
|
||||||
|
check_str=base.RULE_ADMIN_API,
|
||||||
|
description='Retrieve audit list with details.',
|
||||||
|
operations=[
|
||||||
|
{
|
||||||
|
'path': '/v1/audits/detail',
|
||||||
|
'method': 'GET'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
),
|
||||||
|
policy.DocumentedRuleDefault(
|
||||||
|
name=AUDIT % 'get',
|
||||||
|
check_str=base.RULE_ADMIN_API,
|
||||||
|
description='Get an audit.',
|
||||||
|
operations=[
|
||||||
|
{
|
||||||
|
'path': '/v1/audits/{audit_uuid}',
|
||||||
|
'method': 'GET'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
),
|
||||||
|
policy.DocumentedRuleDefault(
|
||||||
|
name=AUDIT % 'get_all',
|
||||||
|
check_str=base.RULE_ADMIN_API,
|
||||||
|
description='Get all audits.',
|
||||||
|
operations=[
|
||||||
|
{
|
||||||
|
'path': '/v1/audits',
|
||||||
|
'method': 'GET'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
),
|
||||||
|
policy.DocumentedRuleDefault(
|
||||||
|
name=AUDIT % 'update',
|
||||||
|
check_str=base.RULE_ADMIN_API,
|
||||||
|
description='Update an audit.',
|
||||||
|
operations=[
|
||||||
|
{
|
||||||
|
'path': '/v1/audits/{audit_uuid}',
|
||||||
|
'method': 'PATCH'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def list_rules():
|
||||||
|
return rules
|
||||||
90
watcher/common/policies/audit_template.py
Normal file
90
watcher/common/policies/audit_template.py
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from oslo_policy import policy
|
||||||
|
|
||||||
|
from watcher.common.policies import base
|
||||||
|
|
||||||
|
AUDIT_TEMPLATE = 'audit_template:%s'
|
||||||
|
|
||||||
|
rules = [
|
||||||
|
policy.DocumentedRuleDefault(
|
||||||
|
name=AUDIT_TEMPLATE % 'create',
|
||||||
|
check_str=base.RULE_ADMIN_API,
|
||||||
|
description='Create an audit template.',
|
||||||
|
operations=[
|
||||||
|
{
|
||||||
|
'path': '/v1/audit_templates',
|
||||||
|
'method': 'POST'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
),
|
||||||
|
policy.DocumentedRuleDefault(
|
||||||
|
name=AUDIT_TEMPLATE % 'delete',
|
||||||
|
check_str=base.RULE_ADMIN_API,
|
||||||
|
description='Delete an audit template.',
|
||||||
|
operations=[
|
||||||
|
{
|
||||||
|
'path': '/v1/audit_templates/{audit_template_uuid}',
|
||||||
|
'method': 'DELETE'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
),
|
||||||
|
policy.DocumentedRuleDefault(
|
||||||
|
name=AUDIT_TEMPLATE % 'detail',
|
||||||
|
check_str=base.RULE_ADMIN_API,
|
||||||
|
description='Retrieve a list of audit templates with details.',
|
||||||
|
operations=[
|
||||||
|
{
|
||||||
|
'path': '/v1/audit_templates/detail',
|
||||||
|
'method': 'GET'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
),
|
||||||
|
policy.DocumentedRuleDefault(
|
||||||
|
name=AUDIT_TEMPLATE % 'get',
|
||||||
|
check_str=base.RULE_ADMIN_API,
|
||||||
|
description='Get an audit template.',
|
||||||
|
operations=[
|
||||||
|
{
|
||||||
|
'path': '/v1/audit_templates/{audit_template_uuid}',
|
||||||
|
'method': 'GET'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
),
|
||||||
|
policy.DocumentedRuleDefault(
|
||||||
|
name=AUDIT_TEMPLATE % 'get_all',
|
||||||
|
check_str=base.RULE_ADMIN_API,
|
||||||
|
description='Get a list of all audit templates.',
|
||||||
|
operations=[
|
||||||
|
{
|
||||||
|
'path': '/v1/audit_templates',
|
||||||
|
'method': 'GET'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
),
|
||||||
|
policy.DocumentedRuleDefault(
|
||||||
|
name=AUDIT_TEMPLATE % 'update',
|
||||||
|
check_str=base.RULE_ADMIN_API,
|
||||||
|
description='Update an audit template.',
|
||||||
|
operations=[
|
||||||
|
{
|
||||||
|
'path': '/v1/audit_templates/{audit_template_uuid}',
|
||||||
|
'method': 'PATCH'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def list_rules():
|
||||||
|
return rules
|
||||||
32
watcher/common/policies/base.py
Normal file
32
watcher/common/policies/base.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from oslo_policy import policy
|
||||||
|
|
||||||
|
RULE_ADMIN_API = 'rule:admin_api'
|
||||||
|
ROLE_ADMIN_OR_ADMINISTRATOR = 'role:admin or role:administrator'
|
||||||
|
ALWAYS_DENY = '!'
|
||||||
|
|
||||||
|
rules = [
|
||||||
|
policy.RuleDefault(
|
||||||
|
name='admin_api',
|
||||||
|
check_str=ROLE_ADMIN_OR_ADMINISTRATOR
|
||||||
|
),
|
||||||
|
policy.RuleDefault(
|
||||||
|
name='show_password',
|
||||||
|
check_str=ALWAYS_DENY
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def list_rules():
|
||||||
|
return rules
|
||||||
57
watcher/common/policies/goal.py
Normal file
57
watcher/common/policies/goal.py
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from oslo_policy import policy
|
||||||
|
|
||||||
|
from watcher.common.policies import base
|
||||||
|
|
||||||
|
GOAL = 'goal:%s'
|
||||||
|
|
||||||
|
rules = [
|
||||||
|
policy.DocumentedRuleDefault(
|
||||||
|
name=GOAL % 'detail',
|
||||||
|
check_str=base.RULE_ADMIN_API,
|
||||||
|
description='Retrieve a list of goals with detail.',
|
||||||
|
operations=[
|
||||||
|
{
|
||||||
|
'path': '/v1/goals/detail',
|
||||||
|
'method': 'DELETE'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
),
|
||||||
|
policy.DocumentedRuleDefault(
|
||||||
|
name=GOAL % 'get',
|
||||||
|
check_str=base.RULE_ADMIN_API,
|
||||||
|
description='Get a goal.',
|
||||||
|
operations=[
|
||||||
|
{
|
||||||
|
'path': '/v1/goals/{goal_uuid}',
|
||||||
|
'method': 'GET'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
),
|
||||||
|
policy.DocumentedRuleDefault(
|
||||||
|
name=GOAL % 'get_all',
|
||||||
|
check_str=base.RULE_ADMIN_API,
|
||||||
|
description='Get all goals.',
|
||||||
|
operations=[
|
||||||
|
{
|
||||||
|
'path': '/v1/goals',
|
||||||
|
'method': 'GET'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def list_rules():
|
||||||
|
return rules
|
||||||
66
watcher/common/policies/scoring_engine.py
Normal file
66
watcher/common/policies/scoring_engine.py
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from oslo_policy import policy
|
||||||
|
|
||||||
|
from watcher.common.policies import base
|
||||||
|
|
||||||
|
SCORING_ENGINE = 'scoring_engine:%s'
|
||||||
|
|
||||||
|
rules = [
|
||||||
|
# FIXME(lbragstad): Find someone from watcher to double check this
|
||||||
|
# information. This API isn't listed in watcher's API reference
|
||||||
|
# documentation.
|
||||||
|
policy.DocumentedRuleDefault(
|
||||||
|
name=SCORING_ENGINE % 'detail',
|
||||||
|
check_str=base.RULE_ADMIN_API,
|
||||||
|
description='List scoring engines with details.',
|
||||||
|
operations=[
|
||||||
|
{
|
||||||
|
'path': '/v1/scoring_engines/detail',
|
||||||
|
'method': 'GET'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
),
|
||||||
|
# FIXME(lbragstad): Find someone from watcher to double check this
|
||||||
|
# information. This API isn't listed in watcher's API reference
|
||||||
|
# documentation.
|
||||||
|
policy.DocumentedRuleDefault(
|
||||||
|
name=SCORING_ENGINE % 'get',
|
||||||
|
check_str=base.RULE_ADMIN_API,
|
||||||
|
description='Get a scoring engine.',
|
||||||
|
operations=[
|
||||||
|
{
|
||||||
|
'path': '/v1/scoring_engines/{scoring_engine_id}',
|
||||||
|
'method': 'GET'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
),
|
||||||
|
# FIXME(lbragstad): Find someone from watcher to double check this
|
||||||
|
# information. This API isn't listed in watcher's API reference
|
||||||
|
# documentation.
|
||||||
|
policy.DocumentedRuleDefault(
|
||||||
|
name=SCORING_ENGINE % 'get_all',
|
||||||
|
check_str=base.RULE_ADMIN_API,
|
||||||
|
description='Get all scoring engines.',
|
||||||
|
operations=[
|
||||||
|
{
|
||||||
|
'path': '/v1/scoring_engines',
|
||||||
|
'method': 'GET'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def list_rules():
|
||||||
|
return rules
|
||||||
57
watcher/common/policies/service.py
Normal file
57
watcher/common/policies/service.py
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from oslo_policy import policy
|
||||||
|
|
||||||
|
from watcher.common.policies import base
|
||||||
|
|
||||||
|
SERVICE = 'service:%s'
|
||||||
|
|
||||||
|
rules = [
|
||||||
|
policy.DocumentedRuleDefault(
|
||||||
|
name=SERVICE % 'detail',
|
||||||
|
check_str=base.RULE_ADMIN_API,
|
||||||
|
description='List services with detail.',
|
||||||
|
operations=[
|
||||||
|
{
|
||||||
|
'path': '/v1/services/',
|
||||||
|
'method': 'GET'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
),
|
||||||
|
policy.DocumentedRuleDefault(
|
||||||
|
name=SERVICE % 'get',
|
||||||
|
check_str=base.RULE_ADMIN_API,
|
||||||
|
description='Get a specific service.',
|
||||||
|
operations=[
|
||||||
|
{
|
||||||
|
'path': '/v1/services/{service_id}',
|
||||||
|
'method': 'GET'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
),
|
||||||
|
policy.DocumentedRuleDefault(
|
||||||
|
name=SERVICE % 'get_all',
|
||||||
|
check_str=base.RULE_ADMIN_API,
|
||||||
|
description='List all services.',
|
||||||
|
operations=[
|
||||||
|
{
|
||||||
|
'path': '/v1/services/',
|
||||||
|
'method': 'GET'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def list_rules():
|
||||||
|
return rules
|
||||||
68
watcher/common/policies/strategy.py
Normal file
68
watcher/common/policies/strategy.py
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from oslo_policy import policy
|
||||||
|
|
||||||
|
from watcher.common.policies import base
|
||||||
|
|
||||||
|
STRATEGY = 'strategy:%s'
|
||||||
|
|
||||||
|
rules = [
|
||||||
|
policy.DocumentedRuleDefault(
|
||||||
|
name=STRATEGY % 'detail',
|
||||||
|
check_str=base.RULE_ADMIN_API,
|
||||||
|
description='List strategies with detail.',
|
||||||
|
operations=[
|
||||||
|
{
|
||||||
|
'path': '/v1/strategies/detail',
|
||||||
|
'method': 'GET'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
),
|
||||||
|
policy.DocumentedRuleDefault(
|
||||||
|
name=STRATEGY % 'get',
|
||||||
|
check_str=base.RULE_ADMIN_API,
|
||||||
|
description='Get a strategy.',
|
||||||
|
operations=[
|
||||||
|
{
|
||||||
|
'path': '/v1/strategies/{strategy_uuid}',
|
||||||
|
'method': 'GET'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
),
|
||||||
|
policy.DocumentedRuleDefault(
|
||||||
|
name=STRATEGY % 'get_all',
|
||||||
|
check_str=base.RULE_ADMIN_API,
|
||||||
|
description='List all strategies.',
|
||||||
|
operations=[
|
||||||
|
{
|
||||||
|
'path': '/v1/strategies',
|
||||||
|
'method': 'GET'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
),
|
||||||
|
policy.DocumentedRuleDefault(
|
||||||
|
name=STRATEGY % 'state',
|
||||||
|
check_str=base.RULE_ADMIN_API,
|
||||||
|
description='Get state of strategy.',
|
||||||
|
operations=[
|
||||||
|
{
|
||||||
|
'path': '/v1/strategies{strategy_uuid}/state',
|
||||||
|
'method': 'GET'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def list_rules():
|
||||||
|
return rules
|
||||||
@@ -15,11 +15,13 @@
|
|||||||
|
|
||||||
"""Policy Engine For Watcher."""
|
"""Policy Engine For Watcher."""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_policy import policy
|
from oslo_policy import policy
|
||||||
|
|
||||||
from watcher.common import exception
|
from watcher.common import exception
|
||||||
|
from watcher.common import policies
|
||||||
|
|
||||||
_ENFORCER = None
|
_ENFORCER = None
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
@@ -56,6 +58,7 @@ def init(policy_file=None, rules=None,
|
|||||||
default_rule=default_rule,
|
default_rule=default_rule,
|
||||||
use_conf=use_conf,
|
use_conf=use_conf,
|
||||||
overwrite=overwrite)
|
overwrite=overwrite)
|
||||||
|
_ENFORCER.register_defaults(policies.list_rules())
|
||||||
return _ENFORCER
|
return _ENFORCER
|
||||||
|
|
||||||
|
|
||||||
@@ -92,3 +95,23 @@ def enforce(context, rule=None, target=None,
|
|||||||
'user_id': context.user_id}
|
'user_id': context.user_id}
|
||||||
return enforcer.enforce(rule, target, credentials,
|
return enforcer.enforce(rule, target, credentials,
|
||||||
do_raise=do_raise, exc=exc, *args, **kwargs)
|
do_raise=do_raise, exc=exc, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def get_enforcer():
|
||||||
|
# This method is for use by oslopolicy CLI scripts. Those scripts need the
|
||||||
|
# 'output-file' and 'namespace' options, but having those in sys.argv means
|
||||||
|
# loading the Watcher config options will fail as those are not expected
|
||||||
|
# to be present. So we pass in an arg list with those stripped out.
|
||||||
|
conf_args = []
|
||||||
|
# Start at 1 because cfg.CONF expects the equivalent of sys.argv[1:]
|
||||||
|
i = 1
|
||||||
|
while i < len(sys.argv):
|
||||||
|
if sys.argv[i].strip('-') in ['namespace', 'output-file']:
|
||||||
|
i += 2
|
||||||
|
continue
|
||||||
|
conf_args.append(sys.argv[i])
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
cfg.CONF(conf_args, project='watcher')
|
||||||
|
init()
|
||||||
|
return _ENFORCER
|
||||||
|
|||||||
@@ -69,7 +69,8 @@ _DEFAULT_LOG_LEVELS = ['amqp=WARN', 'amqplib=WARN', 'qpid.messaging=INFO',
|
|||||||
'keystoneclient=INFO', 'stevedore=INFO',
|
'keystoneclient=INFO', 'stevedore=INFO',
|
||||||
'eventlet.wsgi.server=WARN', 'iso8601=WARN',
|
'eventlet.wsgi.server=WARN', 'iso8601=WARN',
|
||||||
'paramiko=WARN', 'requests=WARN', 'neutronclient=WARN',
|
'paramiko=WARN', 'requests=WARN', 'neutronclient=WARN',
|
||||||
'glanceclient=WARN', 'watcher.openstack.common=WARN']
|
'glanceclient=WARN', 'watcher.openstack.common=WARN',
|
||||||
|
'apscheduler=WARN']
|
||||||
|
|
||||||
Singleton = service.Singleton
|
Singleton = service.Singleton
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user