Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d5e816eaa | ||
|
|
9c3736cb1b | ||
|
|
520cdb3c51 | ||
|
|
4d8d1cd356 | ||
|
|
6d4fb3c3eb | ||
|
|
1caf89686c | ||
|
|
ed3224835a | ||
|
|
8a7e316f73 | ||
|
|
112ac3bbdf | ||
|
|
d1ab697612 | ||
|
|
095ca0ffb2 | ||
|
|
e1131a65d8 | ||
|
|
5e507e56f4 |
@@ -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/ocata
|
||||||
|
|||||||
9
.zuul.yaml
Normal file
9
.zuul.yaml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
- project:
|
||||||
|
templates:
|
||||||
|
- openstack-python-jobs
|
||||||
|
- openstack-python35-jobs
|
||||||
|
- publish-openstack-sphinx-docs
|
||||||
|
- check-requirements
|
||||||
|
- release-notes-jobs
|
||||||
|
gate:
|
||||||
|
queue: watcher
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
If you would like to contribute to the development of OpenStack,
|
If you would like to contribute to the development of OpenStack,
|
||||||
you must follow the steps in this page:
|
you must follow the steps in this page:
|
||||||
|
|
||||||
https://docs.openstack.org/infra/manual/developers.html
|
http://docs.openstack.org/infra/manual/developers.html
|
||||||
|
|
||||||
Once those steps have been completed, changes to OpenStack
|
Once those steps have been completed, changes to OpenStack
|
||||||
should be submitted for review via the Gerrit tool, following
|
should be submitted for review via the Gerrit tool, following
|
||||||
the workflow documented at:
|
the workflow documented at:
|
||||||
|
|
||||||
https://docs.openstack.org/infra/manual/developers.html#development-workflow
|
http://docs.openstack.org/infra/manual/developers.html#development-workflow
|
||||||
|
|
||||||
Pull requests submitted through GitHub will be ignored.
|
Pull requests submitted through GitHub will be ignored.
|
||||||
|
|
||||||
|
|||||||
@@ -8,4 +8,4 @@
|
|||||||
watcher Style Commandments
|
watcher Style Commandments
|
||||||
==========================
|
==========================
|
||||||
|
|
||||||
Read the OpenStack Style Commandments https://docs.openstack.org/developer/hacking/
|
Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/
|
||||||
|
|||||||
10
README.rst
10
README.rst
@@ -2,8 +2,8 @@
|
|||||||
Team and repository tags
|
Team and repository tags
|
||||||
========================
|
========================
|
||||||
|
|
||||||
.. image:: https://governance.openstack.org/badges/watcher.svg
|
.. image:: http://governance.openstack.org/badges/watcher.svg
|
||||||
:target: https://governance.openstack.org/reference/tags/index.html
|
:target: http://governance.openstack.org/reference/tags/index.html
|
||||||
|
|
||||||
.. Change things from this point on
|
.. Change things from this point on
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ operating costs, increased system performance via intelligent virtual machine
|
|||||||
migration, increased energy efficiency-and more!
|
migration, increased energy efficiency-and more!
|
||||||
|
|
||||||
* Free software: Apache license
|
* Free software: Apache license
|
||||||
* Wiki: https://wiki.openstack.org/wiki/Watcher
|
* Wiki: http://wiki.openstack.org/wiki/Watcher
|
||||||
* Source: https://github.com/openstack/watcher
|
* Source: https://github.com/openstack/watcher
|
||||||
* Bugs: https://bugs.launchpad.net/watcher
|
* Bugs: http://bugs.launchpad.net/watcher
|
||||||
* Documentation: https://docs.openstack.org/developer/watcher/
|
* Documentation: http://docs.openstack.org/developer/watcher/
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
# 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.
|
|
||||||
|
|
||||||
# This is an example Apache2 configuration file for using the
|
|
||||||
# Watcher API through mod_wsgi. This version assumes you are
|
|
||||||
# running devstack to configure the software.
|
|
||||||
|
|
||||||
Listen %WATCHER_SERVICE_PORT%
|
|
||||||
|
|
||||||
<VirtualHost *:%WATCHER_SERVICE_PORT%>
|
|
||||||
WSGIDaemonProcess watcher-api user=%USER% processes=%APIWORKERS% threads=1 display-name=%{GROUP}
|
|
||||||
WSGIScriptAlias / %WATCHER_WSGI_DIR%/app.wsgi
|
|
||||||
WSGIApplicationGroup %{GLOBAL}
|
|
||||||
WSGIProcessGroup watcher-api
|
|
||||||
WSGIPassAuthorization On
|
|
||||||
|
|
||||||
ErrorLogFormat "%M"
|
|
||||||
ErrorLog /var/log/%APACHE_NAME%/watcher-api.log
|
|
||||||
CustomLog /var/log/%APACHE_NAME%/watcher-api-access.log combined
|
|
||||||
|
|
||||||
|
|
||||||
<Directory %WATCHER_WSGI_DIR%>
|
|
||||||
WSGIProcessGroup watcher-api
|
|
||||||
WSGIApplicationGroup %{GLOBAL}
|
|
||||||
<IfVersion >= 2.4>
|
|
||||||
Require all granted
|
|
||||||
</IfVersion>
|
|
||||||
<IfVersion < 2.4>
|
|
||||||
Order allow,deny
|
|
||||||
Allow from all
|
|
||||||
</IfVersion>
|
|
||||||
</Directory>
|
|
||||||
</VirtualHost>
|
|
||||||
@@ -44,9 +44,6 @@ WATCHER_CONF_DIR=/etc/watcher
|
|||||||
WATCHER_CONF=$WATCHER_CONF_DIR/watcher.conf
|
WATCHER_CONF=$WATCHER_CONF_DIR/watcher.conf
|
||||||
WATCHER_POLICY_JSON=$WATCHER_CONF_DIR/policy.json
|
WATCHER_POLICY_JSON=$WATCHER_CONF_DIR/policy.json
|
||||||
|
|
||||||
WATCHER_DEVSTACK_DIR=$WATCHER_DIR/devstack
|
|
||||||
WATCHER_DEVSTACK_FILES_DIR=$WATCHER_DEVSTACK_DIR/files
|
|
||||||
|
|
||||||
NOVA_CONF_DIR=/etc/nova
|
NOVA_CONF_DIR=/etc/nova
|
||||||
NOVA_CONF=$NOVA_CONF_DIR/nova.conf
|
NOVA_CONF=$NOVA_CONF_DIR/nova.conf
|
||||||
|
|
||||||
@@ -54,13 +51,6 @@ if is_ssl_enabled_service "watcher" || is_service_enabled tls-proxy; then
|
|||||||
WATCHER_SERVICE_PROTOCOL="https"
|
WATCHER_SERVICE_PROTOCOL="https"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
WATCHER_USE_MOD_WSGI=$(trueorfalse TRUE WATCHER_USE_MOD_WSGI)
|
|
||||||
|
|
||||||
if is_suse; then
|
|
||||||
WATCHER_WSGI_DIR=${WATCHER_WSGI_DIR:-/srv/www/htdocs/watcher}
|
|
||||||
else
|
|
||||||
WATCHER_WSGI_DIR=${WATCHER_WSGI_DIR:-/var/www/watcher}
|
|
||||||
fi
|
|
||||||
# Public facing bits
|
# Public facing bits
|
||||||
WATCHER_SERVICE_HOST=${WATCHER_SERVICE_HOST:-$HOST_IP}
|
WATCHER_SERVICE_HOST=${WATCHER_SERVICE_HOST:-$HOST_IP}
|
||||||
WATCHER_SERVICE_PORT=${WATCHER_SERVICE_PORT:-9322}
|
WATCHER_SERVICE_PORT=${WATCHER_SERVICE_PORT:-9322}
|
||||||
@@ -84,21 +74,10 @@ function is_watcher_enabled {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
#_cleanup_watcher_apache_wsgi - Remove wsgi files,
|
|
||||||
#disable and remove apache vhost file
|
|
||||||
function _cleanup_watcher_apache_wsgi {
|
|
||||||
sudo rm -rf $WATCHER_WSGI_DIR
|
|
||||||
sudo rm -f $(apache_site_config_for watcher-api)
|
|
||||||
restart_apache_server
|
|
||||||
}
|
|
||||||
|
|
||||||
# cleanup_watcher() - Remove residual data files, anything left over from previous
|
# cleanup_watcher() - Remove residual data files, anything left over from previous
|
||||||
# runs that a clean run would need to clean up
|
# runs that a clean run would need to clean up
|
||||||
function cleanup_watcher {
|
function cleanup_watcher {
|
||||||
sudo rm -rf $WATCHER_STATE_PATH $WATCHER_AUTH_CACHE_DIR
|
sudo rm -rf $WATCHER_STATE_PATH $WATCHER_AUTH_CACHE_DIR
|
||||||
if [[ "$WATCHER_USE_MOD_WSGI" == "True" ]]; then
|
|
||||||
_cleanup_watcher_apache_wsgi
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# configure_watcher() - Set config files, create data dirs, etc
|
# configure_watcher() - Set config files, create data dirs, etc
|
||||||
@@ -129,28 +108,6 @@ function create_watcher_accounts {
|
|||||||
"$WATCHER_SERVICE_PROTOCOL://$WATCHER_SERVICE_HOST:$WATCHER_SERVICE_PORT"
|
"$WATCHER_SERVICE_PROTOCOL://$WATCHER_SERVICE_HOST:$WATCHER_SERVICE_PORT"
|
||||||
}
|
}
|
||||||
|
|
||||||
# _config_watcher_apache_wsgi() - Set WSGI config files of watcher
|
|
||||||
function _config_watcher_apache_wsgi {
|
|
||||||
local watcher_apache_conf
|
|
||||||
if [[ "$WATCHER_USE_MOD_WSGI" == "True" ]]; then
|
|
||||||
sudo mkdir -p $WATCHER_WSGI_DIR
|
|
||||||
sudo cp $WATCHER_DIR/watcher/api/app.wsgi $WATCHER_WSGI_DIR/app.wsgi
|
|
||||||
watcher_apache_conf=$(apache_site_config_for watcher-api)
|
|
||||||
sudo cp $WATCHER_DEVSTACK_FILES_DIR/apache-watcher-api.template $watcher_apache_conf
|
|
||||||
sudo sed -e "
|
|
||||||
s|%WATCHER_SERVICE_PORT%|$WATCHER_SERVICE_PORT|g;
|
|
||||||
s|%WATCHER_WSGI_DIR%|$WATCHER_WSGI_DIR|g;
|
|
||||||
s|%USER%|$STACK_USER|g;
|
|
||||||
s|%APIWORKERS%|$API_WORKERS|g;
|
|
||||||
s|%APACHE_NAME%|$APACHE_NAME|g;
|
|
||||||
" -i $watcher_apache_conf
|
|
||||||
enable_apache_site watcher-api
|
|
||||||
tail_log watcher-access /var/log/$APACHE_NAME/watcher-api-access.log
|
|
||||||
tail_log watcher-api /var/log/$APACHE_NAME/watcher-api.log
|
|
||||||
fi
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
# create_watcher_conf() - Create a new watcher.conf file
|
# create_watcher_conf() - Create a new watcher.conf file
|
||||||
function create_watcher_conf {
|
function create_watcher_conf {
|
||||||
# (Re)create ``watcher.conf``
|
# (Re)create ``watcher.conf``
|
||||||
@@ -169,7 +126,7 @@ function create_watcher_conf {
|
|||||||
iniset $WATCHER_CONF oslo_messaging_rabbit rabbit_password $RABBIT_PASSWORD
|
iniset $WATCHER_CONF oslo_messaging_rabbit rabbit_password $RABBIT_PASSWORD
|
||||||
iniset $WATCHER_CONF oslo_messaging_rabbit rabbit_host $RABBIT_HOST
|
iniset $WATCHER_CONF oslo_messaging_rabbit rabbit_host $RABBIT_HOST
|
||||||
|
|
||||||
iniset $WATCHER_CONF oslo_messaging_notifications driver "messagingv2"
|
iniset $WATCHER_CONF oslo_messaging_notifications driver "messaging"
|
||||||
|
|
||||||
iniset $NOVA_CONF oslo_messaging_notifications topics "notifications,watcher_notifications"
|
iniset $NOVA_CONF oslo_messaging_notifications topics "notifications,watcher_notifications"
|
||||||
iniset $NOVA_CONF notifications notify_on_state_change "vm_and_task_state"
|
iniset $NOVA_CONF notifications notify_on_state_change "vm_and_task_state"
|
||||||
@@ -197,13 +154,9 @@ function create_watcher_conf {
|
|||||||
setup_colorized_logging $WATCHER_CONF DEFAULT
|
setup_colorized_logging $WATCHER_CONF DEFAULT
|
||||||
else
|
else
|
||||||
# Show user_name and project_name instead of user_id and project_id
|
# Show user_name and project_name instead of user_id and project_id
|
||||||
iniset $WATCHER_CONF DEFAULT logging_context_format_string "%(asctime)s.%(msecs)03d %(levelname)s %(name)s [%(request_id)s %(project_domain)s %(user_name)s %(project_name)s] %(instance)s%(message)s"
|
iniset $WATCHER_CONF DEFAULT logging_context_format_string "%(asctime)s.%(msecs)03d %(levelname)s %(name)s [%(request_id)s %(user_name)s %(project_name)s] %(instance)s%(message)s"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
#config apache files
|
|
||||||
if [[ "$WATCHER_USE_MOD_WSGI" == "True" ]]; then
|
|
||||||
_config_watcher_apache_wsgi
|
|
||||||
fi
|
|
||||||
# Register SSL certificates if provided
|
# Register SSL certificates if provided
|
||||||
if is_ssl_enabled_service watcher; then
|
if is_ssl_enabled_service watcher; then
|
||||||
ensure_certificates WATCHER
|
ensure_certificates WATCHER
|
||||||
@@ -252,26 +205,19 @@ function install_watcherclient {
|
|||||||
function install_watcher {
|
function install_watcher {
|
||||||
git_clone $WATCHER_REPO $WATCHER_DIR $WATCHER_BRANCH
|
git_clone $WATCHER_REPO $WATCHER_DIR $WATCHER_BRANCH
|
||||||
setup_develop $WATCHER_DIR
|
setup_develop $WATCHER_DIR
|
||||||
if [[ "$WATCHER_USE_MOD_WSGI" == "True" ]]; then
|
|
||||||
install_apache_wsgi
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# start_watcher_api() - Start the API process ahead of other things
|
# start_watcher_api() - Start the API process ahead of other things
|
||||||
function start_watcher_api {
|
function start_watcher_api {
|
||||||
# Get right service port for testing
|
# Get right service port for testing
|
||||||
|
|
||||||
local service_port=$WATCHER_SERVICE_PORT
|
local service_port=$WATCHER_SERVICE_PORT
|
||||||
local service_protocol=$WATCHER_SERVICE_PROTOCOL
|
local service_protocol=$WATCHER_SERVICE_PROTOCOL
|
||||||
if is_service_enabled tls-proxy; then
|
if is_service_enabled tls-proxy; then
|
||||||
service_port=$WATCHER_SERVICE_PORT_INT
|
service_port=$WATCHER_SERVICE_PORT_INT
|
||||||
service_protocol="http"
|
service_protocol="http"
|
||||||
fi
|
fi
|
||||||
if [[ "$WATCHER_USE_MOD_WSGI" == "True" ]]; then
|
|
||||||
restart_apache_server
|
|
||||||
else
|
|
||||||
run_process watcher-api "$WATCHER_BIN_DIR/watcher-api --config-file $WATCHER_CONF"
|
run_process watcher-api "$WATCHER_BIN_DIR/watcher-api --config-file $WATCHER_CONF"
|
||||||
fi
|
|
||||||
echo "Waiting for watcher-api to start..."
|
echo "Waiting for watcher-api to start..."
|
||||||
if ! wait_for_service $SERVICE_TIMEOUT $service_protocol://$WATCHER_SERVICE_HOST:$service_port; then
|
if ! wait_for_service $SERVICE_TIMEOUT $service_protocol://$WATCHER_SERVICE_HOST:$service_port; then
|
||||||
die $LINENO "watcher-api did not start"
|
die $LINENO "watcher-api did not start"
|
||||||
@@ -294,12 +240,7 @@ function start_watcher {
|
|||||||
|
|
||||||
# stop_watcher() - Stop running processes (non-screen)
|
# stop_watcher() - Stop running processes (non-screen)
|
||||||
function stop_watcher {
|
function stop_watcher {
|
||||||
if [[ "$WATCHER_USE_MOD_WSGI" == "True" ]]; then
|
for serv in watcher-api watcher-decision-engine watcher-applier; do
|
||||||
disable_apache_site watcher-api
|
|
||||||
else
|
|
||||||
stop_process watcher-api
|
|
||||||
fi
|
|
||||||
for serv in watcher-decision-engine watcher-applier; do
|
|
||||||
stop_process $serv
|
stop_process $serv
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -17,10 +17,6 @@ NETWORK_GATEWAY=10.254.1.1 # Change this for your network
|
|||||||
|
|
||||||
MULTI_HOST=1
|
MULTI_HOST=1
|
||||||
|
|
||||||
|
|
||||||
#Set this to FALSE if do not want to run watcher-api behind mod-wsgi
|
|
||||||
#WATCHER_USE_MOD_WSGI=TRUE
|
|
||||||
|
|
||||||
# This is the controller node, so disable nova-compute
|
# This is the controller node, so disable nova-compute
|
||||||
disable_service n-cpu
|
disable_service n-cpu
|
||||||
|
|
||||||
@@ -32,21 +28,16 @@ ENABLED_SERVICES+=,q-svc,q-dhcp,q-meta,q-agt,q-l3,neutron
|
|||||||
enable_service n-cauth
|
enable_service n-cauth
|
||||||
|
|
||||||
# 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
|
||||||
# Enable the ceilometer api explicitly(bug:1667678)
|
|
||||||
enable_service ceilometer-api
|
|
||||||
|
|
||||||
# Enable the Gnocchi plugin
|
|
||||||
enable_plugin gnocchi https://git.openstack.org/openstack/gnocchi
|
|
||||||
|
|
||||||
LOGFILE=$DEST/logs/stack.sh.log
|
LOGFILE=$DEST/logs/stack.sh.log
|
||||||
LOGDAYS=2
|
LOGDAYS=2
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
{
|
|
||||||
"priority": "INFO",
|
|
||||||
"payload": {
|
|
||||||
"watcher_object.namespace": "watcher",
|
|
||||||
"watcher_object.version": "1.0",
|
|
||||||
"watcher_object.name": "ActionCreatePayload",
|
|
||||||
"watcher_object.data": {
|
|
||||||
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
|
||||||
"input_parameters": {
|
|
||||||
"param2": 2,
|
|
||||||
"param1": 1
|
|
||||||
},
|
|
||||||
"created_at": "2016-10-18T09:52:05Z",
|
|
||||||
"updated_at": null,
|
|
||||||
"state": "PENDING",
|
|
||||||
"action_plan": {
|
|
||||||
"watcher_object.namespace": "watcher",
|
|
||||||
"watcher_object.version": "1.0",
|
|
||||||
"watcher_object.name": "TerseActionPlanPayload",
|
|
||||||
"watcher_object.data": {
|
|
||||||
"uuid": "76be87bd-3422-43f9-93a0-e85a577e3061",
|
|
||||||
"global_efficacy": {},
|
|
||||||
"created_at": "2016-10-18T09:52:05Z",
|
|
||||||
"updated_at": null,
|
|
||||||
"state": "ONGOING",
|
|
||||||
"audit_uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
|
||||||
"strategy_uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
|
|
||||||
"deleted_at": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"parents": [],
|
|
||||||
"action_type": "nop",
|
|
||||||
"deleted_at": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"publisher_id": "infra-optim:node0",
|
|
||||||
"timestamp": "2017-01-01 00:00:00.000000",
|
|
||||||
"event_type": "action.create",
|
|
||||||
"message_id": "530b409c-9b6b-459b-8f08-f93dbfeb4d41"
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
{
|
|
||||||
"priority": "INFO",
|
|
||||||
"payload": {
|
|
||||||
"watcher_object.namespace": "watcher",
|
|
||||||
"watcher_object.version": "1.0",
|
|
||||||
"watcher_object.name": "ActionDeletePayload",
|
|
||||||
"watcher_object.data": {
|
|
||||||
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
|
||||||
"input_parameters": {
|
|
||||||
"param2": 2,
|
|
||||||
"param1": 1
|
|
||||||
},
|
|
||||||
"created_at": "2016-10-18T09:52:05Z",
|
|
||||||
"updated_at": null,
|
|
||||||
"state": "DELETED",
|
|
||||||
"action_plan": {
|
|
||||||
"watcher_object.namespace": "watcher",
|
|
||||||
"watcher_object.version": "1.0",
|
|
||||||
"watcher_object.name": "TerseActionPlanPayload",
|
|
||||||
"watcher_object.data": {
|
|
||||||
"uuid": "76be87bd-3422-43f9-93a0-e85a577e3061",
|
|
||||||
"global_efficacy": {},
|
|
||||||
"created_at": "2016-10-18T09:52:05Z",
|
|
||||||
"updated_at": null,
|
|
||||||
"state": "ONGOING",
|
|
||||||
"audit_uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
|
||||||
"strategy_uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
|
|
||||||
"deleted_at": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"parents": [],
|
|
||||||
"action_type": "nop",
|
|
||||||
"deleted_at": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"publisher_id": "infra-optim:node0",
|
|
||||||
"timestamp": "2017-01-01 00:00:00.000000",
|
|
||||||
"event_type": "action.delete",
|
|
||||||
"message_id": "530b409c-9b6b-459b-8f08-f93dbfeb4d41"
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
{
|
|
||||||
"priority": "INFO",
|
|
||||||
"payload": {
|
|
||||||
"watcher_object.namespace": "watcher",
|
|
||||||
"watcher_object.version": "1.0",
|
|
||||||
"watcher_object.name": "ActionExecutionPayload",
|
|
||||||
"watcher_object.data": {
|
|
||||||
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
|
||||||
"input_parameters": {
|
|
||||||
"param2": 2,
|
|
||||||
"param1": 1
|
|
||||||
},
|
|
||||||
"fault": null,
|
|
||||||
"created_at": "2016-10-18T09:52:05Z",
|
|
||||||
"updated_at": null,
|
|
||||||
"state": "SUCCEEDED",
|
|
||||||
"action_plan": {
|
|
||||||
"watcher_object.namespace": "watcher",
|
|
||||||
"watcher_object.version": "1.0",
|
|
||||||
"watcher_object.name": "TerseActionPlanPayload",
|
|
||||||
"watcher_object.data": {
|
|
||||||
"uuid": "76be87bd-3422-43f9-93a0-e85a577e3061",
|
|
||||||
"global_efficacy": {},
|
|
||||||
"created_at": "2016-10-18T09:52:05Z",
|
|
||||||
"updated_at": null,
|
|
||||||
"state": "ONGOING",
|
|
||||||
"audit_uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
|
||||||
"strategy_uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
|
|
||||||
"deleted_at": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"parents": [],
|
|
||||||
"action_type": "nop",
|
|
||||||
"deleted_at": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"event_type": "action.execution.end",
|
|
||||||
"publisher_id": "infra-optim:node0",
|
|
||||||
"timestamp": "2017-01-01 00:00:00.000000",
|
|
||||||
"message_id": "530b409c-9b6b-459b-8f08-f93dbfeb4d41"
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
{
|
|
||||||
"priority": "ERROR",
|
|
||||||
"payload": {
|
|
||||||
"watcher_object.namespace": "watcher",
|
|
||||||
"watcher_object.version": "1.0",
|
|
||||||
"watcher_object.name": "ActionExecutionPayload",
|
|
||||||
"watcher_object.data": {
|
|
||||||
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
|
||||||
"input_parameters": {
|
|
||||||
"param2": 2,
|
|
||||||
"param1": 1
|
|
||||||
},
|
|
||||||
"fault": {
|
|
||||||
"watcher_object.namespace": "watcher",
|
|
||||||
"watcher_object.version": "1.0",
|
|
||||||
"watcher_object.name": "ExceptionPayload",
|
|
||||||
"watcher_object.data": {
|
|
||||||
"module_name": "watcher.tests.notifications.test_action_notification",
|
|
||||||
"exception": "WatcherException",
|
|
||||||
"exception_message": "TEST",
|
|
||||||
"function_name": "test_send_action_execution_with_error"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"created_at": "2016-10-18T09:52:05Z",
|
|
||||||
"updated_at": null,
|
|
||||||
"state": "FAILED",
|
|
||||||
"action_plan": {
|
|
||||||
"watcher_object.namespace": "watcher",
|
|
||||||
"watcher_object.version": "1.0",
|
|
||||||
"watcher_object.name": "TerseActionPlanPayload",
|
|
||||||
"watcher_object.data": {
|
|
||||||
"uuid": "76be87bd-3422-43f9-93a0-e85a577e3061",
|
|
||||||
"global_efficacy": {},
|
|
||||||
"created_at": "2016-10-18T09:52:05Z",
|
|
||||||
"updated_at": null,
|
|
||||||
"state": "ONGOING",
|
|
||||||
"audit_uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
|
||||||
"strategy_uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
|
|
||||||
"deleted_at": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"parents": [],
|
|
||||||
"action_type": "nop",
|
|
||||||
"deleted_at": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"event_type": "action.execution.error",
|
|
||||||
"publisher_id": "infra-optim:node0",
|
|
||||||
"timestamp": "2017-01-01 00:00:00.000000",
|
|
||||||
"message_id": "530b409c-9b6b-459b-8f08-f93dbfeb4d41"
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
{
|
|
||||||
"priority": "INFO",
|
|
||||||
"payload": {
|
|
||||||
"watcher_object.namespace": "watcher",
|
|
||||||
"watcher_object.version": "1.0",
|
|
||||||
"watcher_object.name": "ActionExecutionPayload",
|
|
||||||
"watcher_object.data": {
|
|
||||||
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
|
||||||
"input_parameters": {
|
|
||||||
"param2": 2,
|
|
||||||
"param1": 1
|
|
||||||
},
|
|
||||||
"fault": null,
|
|
||||||
"created_at": "2016-10-18T09:52:05Z",
|
|
||||||
"updated_at": null,
|
|
||||||
"state": "ONGOING",
|
|
||||||
"action_plan": {
|
|
||||||
"watcher_object.namespace": "watcher",
|
|
||||||
"watcher_object.version": "1.0",
|
|
||||||
"watcher_object.name": "TerseActionPlanPayload",
|
|
||||||
"watcher_object.data": {
|
|
||||||
"uuid": "76be87bd-3422-43f9-93a0-e85a577e3061",
|
|
||||||
"global_efficacy": {},
|
|
||||||
"created_at": "2016-10-18T09:52:05Z",
|
|
||||||
"updated_at": null,
|
|
||||||
"state": "ONGOING",
|
|
||||||
"audit_uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
|
||||||
"strategy_uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
|
|
||||||
"deleted_at": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"parents": [],
|
|
||||||
"action_type": "nop",
|
|
||||||
"deleted_at": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"event_type": "action.execution.start",
|
|
||||||
"publisher_id": "infra-optim:node0",
|
|
||||||
"timestamp": "2017-01-01 00:00:00.000000",
|
|
||||||
"message_id": "530b409c-9b6b-459b-8f08-f93dbfeb4d41"
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
{
|
|
||||||
"priority": "INFO",
|
|
||||||
"payload": {
|
|
||||||
"watcher_object.namespace": "watcher",
|
|
||||||
"watcher_object.version": "1.0",
|
|
||||||
"watcher_object.name": "ActionUpdatePayload",
|
|
||||||
"watcher_object.data": {
|
|
||||||
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
|
||||||
"input_parameters": {
|
|
||||||
"param2": 2,
|
|
||||||
"param1": 1
|
|
||||||
},
|
|
||||||
"created_at": "2016-10-18T09:52:05Z",
|
|
||||||
"updated_at": null,
|
|
||||||
"state_update": {
|
|
||||||
"watcher_object.namespace": "watcher",
|
|
||||||
"watcher_object.version": "1.0",
|
|
||||||
"watcher_object.name": "ActionStateUpdatePayload",
|
|
||||||
"watcher_object.data": {
|
|
||||||
"old_state": "PENDING",
|
|
||||||
"state": "ONGOING"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"state": "ONGOING",
|
|
||||||
"action_plan": {
|
|
||||||
"watcher_object.namespace": "watcher",
|
|
||||||
"watcher_object.version": "1.0",
|
|
||||||
"watcher_object.name": "TerseActionPlanPayload",
|
|
||||||
"watcher_object.data": {
|
|
||||||
"uuid": "76be87bd-3422-43f9-93a0-e85a577e3061",
|
|
||||||
"global_efficacy": {},
|
|
||||||
"created_at": "2016-10-18T09:52:05Z",
|
|
||||||
"updated_at": null,
|
|
||||||
"state": "ONGOING",
|
|
||||||
"audit_uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
|
||||||
"strategy_uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
|
|
||||||
"deleted_at": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"parents": [],
|
|
||||||
"action_type": "nop",
|
|
||||||
"deleted_at": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"event_type": "action.update",
|
|
||||||
"publisher_id": "infra-optim:node0",
|
|
||||||
"timestamp": "2017-01-01 00:00:00.000000",
|
|
||||||
"message_id": "530b409c-9b6b-459b-8f08-f93dbfeb4d41"
|
|
||||||
}
|
|
||||||
@@ -407,9 +407,6 @@ be one of the following:
|
|||||||
- **CANCELLED** : the :ref:`Audit <audit_definition>` was in **PENDING** or
|
- **CANCELLED** : the :ref:`Audit <audit_definition>` was in **PENDING** or
|
||||||
**ONGOING** state and was cancelled by the
|
**ONGOING** state and was cancelled by the
|
||||||
:ref:`Administrator <administrator_definition>`
|
:ref:`Administrator <administrator_definition>`
|
||||||
- **SUSPENDED** : the :ref:`Audit <audit_definition>` was in **ONGOING**
|
|
||||||
state and was suspended by the
|
|
||||||
:ref:`Administrator <administrator_definition>`
|
|
||||||
|
|
||||||
The following diagram shows the different possible states of an
|
The following diagram shows the different possible states of an
|
||||||
:ref:`Audit <audit_definition>` and what event makes the state change to a new
|
:ref:`Audit <audit_definition>` and what event makes the state change to a new
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
..
|
|
||||||
Except where otherwise noted, this document is licensed under Creative
|
|
||||||
Commons Attribution 3.0 License. You can view the license at:
|
|
||||||
|
|
||||||
https://creativecommons.org/licenses/by/3.0/
|
|
||||||
|
|
||||||
|
|
||||||
Installing API behind mod_wsgi
|
|
||||||
==============================
|
|
||||||
|
|
||||||
#. Install the Apache Service::
|
|
||||||
|
|
||||||
Fedora 21/RHEL7/CentOS7:
|
|
||||||
sudo yum install httpd
|
|
||||||
|
|
||||||
Fedora 22 (or higher):
|
|
||||||
sudo dnf install httpd
|
|
||||||
|
|
||||||
Debian/Ubuntu:
|
|
||||||
apt-get install apache2
|
|
||||||
|
|
||||||
#. Copy ``etc/apache2/watcher.conf`` under the apache sites::
|
|
||||||
|
|
||||||
Fedora/RHEL7/CentOS7:
|
|
||||||
sudo cp etc/apache2/watcher /etc/httpd/conf.d/watcher.conf
|
|
||||||
|
|
||||||
Debian/Ubuntu:
|
|
||||||
sudo cp etc/apache2/watcher /etc/apache2/sites-available/watcher.conf
|
|
||||||
|
|
||||||
#. Edit ``<apache-configuration-dir>/watcher.conf`` according to installation
|
|
||||||
and environment.
|
|
||||||
|
|
||||||
* Modify the ``WSGIDaemonProcess`` directive to set the ``user`` and
|
|
||||||
``group`` values to appropriate user on your server.
|
|
||||||
* Modify the ``WSGIScriptAlias`` directive to point to the
|
|
||||||
watcher/api/app.wsgi script.
|
|
||||||
* Modify the ``Directory`` directive to set the path to the Watcher API
|
|
||||||
code.
|
|
||||||
* Modify the ``ErrorLog and CustomLog`` to redirect the logs to the right
|
|
||||||
directory.
|
|
||||||
|
|
||||||
#. Enable the apache watcher site and reload::
|
|
||||||
|
|
||||||
Fedora/RHEL7/CentOS7:
|
|
||||||
sudo systemctl reload httpd
|
|
||||||
|
|
||||||
Debian/Ubuntu:
|
|
||||||
sudo a2ensite watcher
|
|
||||||
sudo service apache2 reload
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -18,14 +18,14 @@ The source install instructions specifically avoid using platform specific
|
|||||||
packages, instead using the source for the code and the Python Package Index
|
packages, instead using the source for the code and the Python Package Index
|
||||||
(PyPi_).
|
(PyPi_).
|
||||||
|
|
||||||
.. _PyPi: https://pypi.python.org/pypi
|
.. _PyPi: http://pypi.python.org/pypi
|
||||||
|
|
||||||
It's expected that your system already has python2.7_, latest version of pip_,
|
It's expected that your system already has python2.7_, latest version of pip_,
|
||||||
and git_ available.
|
and git_ available.
|
||||||
|
|
||||||
.. _python2.7: https://www.python.org
|
.. _python2.7: http://www.python.org
|
||||||
.. _pip: https://pip.pypa.io/en/latest/installing/
|
.. _pip: http://www.pip-installer.org/en/latest/installing.html
|
||||||
.. _git: https://git-scm.com/
|
.. _git: http://git-scm.com/
|
||||||
|
|
||||||
Your system shall also have some additional system libraries:
|
Your system shall also have some additional system libraries:
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -92,12 +92,6 @@ Detailed DevStack Instructions
|
|||||||
Note: if you want to use a specific branch, specify WATCHER_BRANCH in the
|
Note: if you want to use a specific branch, specify WATCHER_BRANCH in the
|
||||||
local.conf file. By default it will use the master branch.
|
local.conf file. By default it will use the master branch.
|
||||||
|
|
||||||
Note: watcher-api will default run under apache/httpd, set the variable
|
|
||||||
WATCHER_USE_MOD_WSGI=FALSE if you do not wish to run under apache/httpd.
|
|
||||||
For development environment it is suggested to set WATHCER_USE_MOD_WSGI
|
|
||||||
to FALSE. For Production environment it is suggested to keep it at the
|
|
||||||
default TRUE value.
|
|
||||||
|
|
||||||
#. Start stacking from the controller node::
|
#. Start stacking from the controller node::
|
||||||
|
|
||||||
./devstack/stack.sh
|
./devstack/stack.sh
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ for development purposes.
|
|||||||
To install Watcher from packaging, refer instead to Watcher `User
|
To install Watcher from packaging, refer instead to Watcher `User
|
||||||
Documentation`_.
|
Documentation`_.
|
||||||
|
|
||||||
.. _`Git Repository`: https://git.openstack.org/cgit/openstack/watcher
|
.. _`Git Repository`: http://git.openstack.org/cgit/openstack/watcher
|
||||||
.. _`User Documentation`: https://docs.openstack.org/developer/watcher/
|
.. _`User Documentation`: http://docs.openstack.org/developer/watcher/
|
||||||
|
|
||||||
Prerequisites
|
Prerequisites
|
||||||
=============
|
=============
|
||||||
@@ -35,10 +35,10 @@ following tools available on your system:
|
|||||||
**Reminder**: If you're successfully using a different platform, or a
|
**Reminder**: If you're successfully using a different platform, or a
|
||||||
different version of the above, please document your configuration here!
|
different version of the above, please document your configuration here!
|
||||||
|
|
||||||
.. _Python: https://www.python.org/
|
.. _Python: http://www.python.org/
|
||||||
.. _git: https://git-scm.com/
|
.. _git: http://git-scm.com/
|
||||||
.. _setuptools: https://pypi.python.org/pypi/setuptools
|
.. _setuptools: http://pypi.python.org/pypi/setuptools
|
||||||
.. _virtualenvwrapper: https://virtualenvwrapper.readthedocs.io/en/latest/install.html
|
.. _virtualenvwrapper: https://virtualenvwrapper.readthedocs.org/en/latest/install.html
|
||||||
|
|
||||||
Getting the latest code
|
Getting the latest code
|
||||||
=======================
|
=======================
|
||||||
@@ -175,12 +175,11 @@ The HTML files are available into ``doc/build`` directory.
|
|||||||
Configure the Watcher services
|
Configure the Watcher services
|
||||||
==============================
|
==============================
|
||||||
|
|
||||||
Watcher services require a configuration file. Use tox to generate
|
Watcher services require a configuration file. There is a sample configuration
|
||||||
a sample configuration file that can be used to get started:
|
file that can be used to get started:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
$ tox -e genconfig
|
|
||||||
$ cp etc/watcher.conf.sample etc/watcher.conf
|
$ cp etc/watcher.conf.sample etc/watcher.conf
|
||||||
|
|
||||||
Most of the default configuration should be enough to get you going, but you
|
Most of the default configuration should be enough to get you going, but you
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ Unit tests
|
|||||||
==========
|
==========
|
||||||
|
|
||||||
All unit tests should be run using `tox`_. To run the same unit tests that are
|
All unit tests should be run using `tox`_. To run the same unit tests that are
|
||||||
executing onto `Gerrit`_ which includes ``py35``, ``py27`` and ``pep8``, you
|
executing onto `Gerrit`_ which includes ``py34``, ``py27`` and ``pep8``, you
|
||||||
can issue the following command::
|
can issue the following command::
|
||||||
|
|
||||||
$ workon watcher
|
$ workon watcher
|
||||||
@@ -26,7 +26,7 @@ If you want to only run one of the aforementioned, you can then issue one of
|
|||||||
the following::
|
the following::
|
||||||
|
|
||||||
$ workon watcher
|
$ workon watcher
|
||||||
(watcher) $ tox -e py35
|
(watcher) $ tox -e py34
|
||||||
(watcher) $ tox -e py27
|
(watcher) $ tox -e py27
|
||||||
(watcher) $ tox -e pep8
|
(watcher) $ tox -e pep8
|
||||||
|
|
||||||
|
|||||||
BIN
doc/source/image_src/plantuml/action_plan_state_machine.png
Normal file
BIN
doc/source/image_src/plantuml/action_plan_state_machine.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 58 KiB |
@@ -4,14 +4,11 @@
|
|||||||
PENDING --> ONGOING: Audit request is received\nby the Watcher Decision Engine
|
PENDING --> ONGOING: Audit request is received\nby the Watcher Decision Engine
|
||||||
ONGOING --> FAILED: Audit fails\n(no solution found, technical error, ...)
|
ONGOING --> FAILED: Audit fails\n(no solution found, technical error, ...)
|
||||||
ONGOING --> SUCCEEDED: The Watcher Decision Engine\ncould find at least one Solution
|
ONGOING --> SUCCEEDED: The Watcher Decision Engine\ncould find at least one Solution
|
||||||
ONGOING --> SUSPENDED: Administrator wants to\nsuspend the Audit
|
|
||||||
SUSPENDED --> ONGOING: Administrator wants to\nresume the Audit
|
|
||||||
FAILED --> DELETED : Administrator wants to\narchive/delete the Audit
|
FAILED --> DELETED : Administrator wants to\narchive/delete the Audit
|
||||||
SUCCEEDED --> DELETED : Administrator wants to\narchive/delete the Audit
|
SUCCEEDED --> DELETED : Administrator wants to\narchive/delete the Audit
|
||||||
PENDING --> CANCELLED : Administrator cancels\nthe Audit
|
PENDING --> CANCELLED : Administrator cancels\nthe Audit
|
||||||
ONGOING --> CANCELLED : Administrator cancels\nthe Audit
|
ONGOING --> CANCELLED : Administrator cancels\nthe Audit
|
||||||
CANCELLED --> DELETED : Administrator wants to\narchive/delete the Audit
|
CANCELLED --> DELETED : Administrator wants to\narchive/delete the Audit
|
||||||
SUSPENDED --> DELETED: Administrator wants to\narchive/delete the Audit
|
|
||||||
DELETED --> [*]
|
DELETED --> [*]
|
||||||
|
|
||||||
@enduml
|
@enduml
|
||||||
|
|||||||
BIN
doc/source/image_src/plantuml/watcher_db_schema_diagram.png
Normal file
BIN
doc/source/image_src/plantuml/watcher_db_schema_diagram.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 72 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 36 KiB |
@@ -56,7 +56,6 @@ Getting Started
|
|||||||
dev/devstack
|
dev/devstack
|
||||||
deploy/configuration
|
deploy/configuration
|
||||||
deploy/conf-files
|
deploy/conf-files
|
||||||
deploy/apache-mod-wsgi
|
|
||||||
dev/notifications
|
dev/notifications
|
||||||
dev/testing
|
dev/testing
|
||||||
dev/rally_link
|
dev/rally_link
|
||||||
|
|||||||
@@ -72,9 +72,6 @@ Strategy parameter is:
|
|||||||
parameter type default Value description
|
parameter type default Value description
|
||||||
============== ====== ============= ====================================
|
============== ====== ============= ====================================
|
||||||
``threshold`` Number 35.0 Temperature threshold for migration
|
``threshold`` Number 35.0 Temperature threshold for migration
|
||||||
``period`` Number 30 The time interval in seconds for
|
|
||||||
getting statistic aggregation from
|
|
||||||
metric data source
|
|
||||||
============== ====== ============= ====================================
|
============== ====== ============= ====================================
|
||||||
|
|
||||||
Efficacy Indicator
|
Efficacy Indicator
|
||||||
|
|||||||
@@ -70,20 +70,6 @@ Default Watcher's planner:
|
|||||||
.. watcher-term:: watcher.decision_engine.planner.default.DefaultPlanner
|
.. watcher-term:: watcher.decision_engine.planner.default.DefaultPlanner
|
||||||
|
|
||||||
|
|
||||||
Configuration
|
|
||||||
-------------
|
|
||||||
|
|
||||||
Strategy parameter is:
|
|
||||||
|
|
||||||
====================== ====== ============= ===================================
|
|
||||||
parameter type default Value description
|
|
||||||
====================== ====== ============= ===================================
|
|
||||||
``period`` Number 3600 The time interval in seconds
|
|
||||||
for getting statistic aggregation
|
|
||||||
from metric data source
|
|
||||||
====================== ====== ============= ===================================
|
|
||||||
|
|
||||||
|
|
||||||
Efficacy Indicator
|
Efficacy Indicator
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
# 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.
|
|
||||||
|
|
||||||
# This is an example Apache2 configuration file for using
|
|
||||||
# Watcher API through mod_wsgi
|
|
||||||
Listen 9322
|
|
||||||
|
|
||||||
<VirtualHost *:9322>
|
|
||||||
WSGIDaemonProcess watcher-api user=stack group=stack processes=2 threads=2 display-name=%{GROUP}
|
|
||||||
WSGIScriptAlias / /opt/stack/watcher/watcher/api/app.wsgi
|
|
||||||
WSGIProcessGroup watcher-api
|
|
||||||
|
|
||||||
ErrorLog /var/log/httpd/watcher_error.log
|
|
||||||
LogLevel info
|
|
||||||
CustomLog /var/log/httpd/watcher_access.log combined
|
|
||||||
|
|
||||||
<Directory /opt/stack/watcher/watcher/api>
|
|
||||||
WSGIProcessGroup watcher-api
|
|
||||||
WSGIApplicationGroup %{GLOBAL}
|
|
||||||
AllowOverride All
|
|
||||||
Require all granted
|
|
||||||
</Directory>
|
|
||||||
</VirtualHost>
|
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
---
|
---
|
||||||
features:
|
features:
|
||||||
- Check the creation time of the action plan,
|
- Add superseded state for an action plan if the cluster data model has
|
||||||
and set its state to SUPERSEDED if it has expired.
|
changed after it has been created.
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
---
|
|
||||||
features:
|
|
||||||
- |
|
|
||||||
Added SUSPENDED audit state
|
|
||||||
@@ -1,16 +1,3 @@
|
|||||||
# 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.
|
|
||||||
|
|
||||||
# watcher documentation build configuration file, created by
|
# watcher documentation build configuration file, created by
|
||||||
# sphinx-quickstart on Fri Jun 3 11:37:52 2016.
|
# sphinx-quickstart on Fri Jun 3 11:37:52 2016.
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -1,17 +1,3 @@
|
|||||||
..
|
|
||||||
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.
|
|
||||||
|
|
||||||
=================================================
|
|
||||||
Welcome to watcher's Release Notes documentation!
|
Welcome to watcher's Release Notes documentation!
|
||||||
=================================================
|
=================================================
|
||||||
|
|
||||||
@@ -21,6 +7,5 @@ Contents:
|
|||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
|
||||||
unreleased
|
unreleased
|
||||||
ocata
|
|
||||||
newton
|
newton
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
===================================
|
|
||||||
Ocata Series Release Notes
|
|
||||||
===================================
|
|
||||||
|
|
||||||
.. release-notes::
|
|
||||||
:branch: origin/stable/ocata
|
|
||||||
@@ -10,37 +10,36 @@ keystonemiddleware>=4.12.0 # Apache-2.0
|
|||||||
lxml!=3.7.0,>=2.3 # BSD
|
lxml!=3.7.0,>=2.3 # BSD
|
||||||
oslo.concurrency>=3.8.0 # Apache-2.0
|
oslo.concurrency>=3.8.0 # Apache-2.0
|
||||||
oslo.cache>=1.5.0 # Apache-2.0
|
oslo.cache>=1.5.0 # Apache-2.0
|
||||||
oslo.config>=3.22.0 # Apache-2.0
|
oslo.config!=3.18.0,>=3.14.0 # Apache-2.0
|
||||||
oslo.context>=2.12.0 # Apache-2.0
|
oslo.context>=2.9.0 # Apache-2.0
|
||||||
oslo.db>=4.19.0 # Apache-2.0
|
oslo.db>=4.15.0 # Apache-2.0
|
||||||
oslo.i18n>=2.1.0 # Apache-2.0
|
oslo.i18n>=2.1.0 # Apache-2.0
|
||||||
oslo.log>=3.22.0 # Apache-2.0
|
oslo.log>=3.11.0 # Apache-2.0
|
||||||
oslo.messaging>=5.19.0 # Apache-2.0
|
oslo.messaging>=5.14.0 # Apache-2.0
|
||||||
oslo.policy>=1.17.0 # Apache-2.0
|
oslo.policy>=1.17.0 # Apache-2.0
|
||||||
oslo.reports>=0.6.0 # Apache-2.0
|
oslo.reports>=0.6.0 # Apache-2.0
|
||||||
oslo.serialization>=1.10.0 # Apache-2.0
|
oslo.serialization>=1.10.0 # Apache-2.0
|
||||||
oslo.service>=1.10.0 # Apache-2.0
|
oslo.service>=1.10.0 # Apache-2.0
|
||||||
oslo.utils>=3.20.0 # Apache-2.0
|
oslo.utils>=3.18.0 # Apache-2.0
|
||||||
oslo.versionedobjects>=1.17.0 # Apache-2.0
|
oslo.versionedobjects>=1.17.0 # Apache-2.0
|
||||||
PasteDeploy>=1.5.0 # MIT
|
PasteDeploy>=1.5.0 # MIT
|
||||||
pbr!=2.1.0,>=2.0.0 # Apache-2.0
|
pbr>=1.8 # 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
|
||||||
PrettyTable<0.8,>=0.7.1 # BSD
|
PrettyTable<0.8,>=0.7.1 # BSD
|
||||||
voluptuous>=0.8.9 # BSD License
|
voluptuous>=0.8.9 # BSD License
|
||||||
gnocchiclient>=2.7.0 # Apache-2.0
|
|
||||||
python-ceilometerclient>=2.5.0 # Apache-2.0
|
python-ceilometerclient>=2.5.0 # Apache-2.0
|
||||||
python-cinderclient>=2.0.1 # Apache-2.0
|
python-cinderclient!=1.7.0,!=1.7.1,>=1.6.0 # Apache-2.0
|
||||||
python-glanceclient>=2.5.0 # Apache-2.0
|
python-glanceclient>=2.5.0 # Apache-2.0
|
||||||
python-keystoneclient>=3.8.0 # Apache-2.0
|
python-keystoneclient>=3.8.0 # Apache-2.0
|
||||||
python-monascaclient>=1.1.0 # Apache-2.0
|
python-monascaclient>=1.1.0 # Apache-2.0
|
||||||
python-neutronclient>=5.1.0 # Apache-2.0
|
python-neutronclient>=5.1.0 # Apache-2.0
|
||||||
python-novaclient>=7.1.0 # Apache-2.0
|
python-novaclient!=7.0.0,>=6.0.0 # Apache-2.0
|
||||||
python-openstackclient>=3.3.0 # Apache-2.0
|
python-openstackclient>=3.3.0 # Apache-2.0
|
||||||
six>=1.9.0 # MIT
|
six>=1.9.0 # MIT
|
||||||
SQLAlchemy!=1.1.5,!=1.1.6,!=1.1.7,!=1.1.8,>=1.0.10 # MIT
|
SQLAlchemy<1.1.0,>=1.0.10 # MIT
|
||||||
stevedore>=1.20.0 # Apache-2.0
|
stevedore>=1.17.1 # Apache-2.0
|
||||||
taskflow>=2.7.0 # Apache-2.0
|
taskflow>=2.7.0 # Apache-2.0
|
||||||
WebOb>=1.7.1 # MIT
|
WebOb>=1.6.0 # MIT
|
||||||
WSME>=0.8 # MIT
|
WSME>=0.8 # MIT
|
||||||
networkx>=1.10 # BSD
|
networkx>=1.10 # BSD
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ classifier =
|
|||||||
Programming Language :: Python :: 2
|
Programming Language :: Python :: 2
|
||||||
Programming Language :: Python :: 2.7
|
Programming Language :: Python :: 2.7
|
||||||
Programming Language :: Python :: 3
|
Programming Language :: Python :: 3
|
||||||
|
Programming Language :: Python :: 3.4
|
||||||
Programming Language :: Python :: 3.5
|
Programming Language :: Python :: 3.5
|
||||||
|
|
||||||
[files]
|
[files]
|
||||||
|
|||||||
2
setup.py
2
setup.py
@@ -25,5 +25,5 @@ except ImportError:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
setuptools.setup(
|
setuptools.setup(
|
||||||
setup_requires=['pbr>=2.0.0'],
|
setup_requires=['pbr>=1.8'],
|
||||||
pbr=True)
|
pbr=True)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
coverage>=4.0 # Apache-2.0
|
coverage>=4.0 # Apache-2.0
|
||||||
doc8 # Apache-2.0
|
doc8 # Apache-2.0
|
||||||
freezegun>=0.3.6 # Apache-2.0
|
freezegun>=0.3.6 # Apache-2.0
|
||||||
hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0
|
hacking<0.11,>=0.10.2
|
||||||
mock>=2.0 # BSD
|
mock>=2.0 # BSD
|
||||||
oslotest>=1.10.0 # Apache-2.0
|
oslotest>=1.10.0 # Apache-2.0
|
||||||
os-testr>=0.8.0 # Apache-2.0
|
os-testr>=0.8.0 # Apache-2.0
|
||||||
@@ -16,7 +16,7 @@ testtools>=1.4.0 # MIT
|
|||||||
|
|
||||||
# Doc requirements
|
# Doc requirements
|
||||||
oslosphinx>=4.7.0 # Apache-2.0
|
oslosphinx>=4.7.0 # Apache-2.0
|
||||||
sphinx>=1.5.1 # BSD
|
sphinx!=1.3b1,<1.4,>=1.2.1 # BSD
|
||||||
sphinxcontrib-pecanwsme>=0.8 # Apache-2.0
|
sphinxcontrib-pecanwsme>=0.8 # Apache-2.0
|
||||||
|
|
||||||
# releasenotes
|
# releasenotes
|
||||||
|
|||||||
6
tox.ini
6
tox.ini
@@ -1,12 +1,12 @@
|
|||||||
[tox]
|
[tox]
|
||||||
minversion = 1.8
|
minversion = 1.8
|
||||||
envlist = py35,py27,pep8
|
envlist = py35,py34,py27,pep8
|
||||||
skipsdist = True
|
skipsdist = True
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
usedevelop = True
|
usedevelop = True
|
||||||
whitelist_externals = find
|
whitelist_externals = find
|
||||||
install_command = pip install -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/ocata} {opts} {packages}
|
||||||
setenv =
|
setenv =
|
||||||
VIRTUAL_ENV={envdir}
|
VIRTUAL_ENV={envdir}
|
||||||
deps = -r{toxinidir}/test-requirements.txt
|
deps = -r{toxinidir}/test-requirements.txt
|
||||||
@@ -45,7 +45,7 @@ commands =
|
|||||||
|
|
||||||
[flake8]
|
[flake8]
|
||||||
show-source=True
|
show-source=True
|
||||||
ignore= H105,E123,E226,N320
|
ignore=
|
||||||
builtins= _
|
builtins= _
|
||||||
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
|
||||||
|
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
# -*- mode: python -*-
|
|
||||||
# -*- encoding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
"""
|
|
||||||
Use this file for deploying the API service under Apache2 mod_wsgi.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from oslo_config import cfg
|
|
||||||
import oslo_i18n as i18n
|
|
||||||
from oslo_log import log
|
|
||||||
|
|
||||||
from watcher.api import app
|
|
||||||
from watcher.common import service
|
|
||||||
|
|
||||||
|
|
||||||
CONF = cfg.CONF
|
|
||||||
|
|
||||||
i18n.install('watcher')
|
|
||||||
|
|
||||||
service.prepare_service(sys.argv)
|
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
|
||||||
LOG.debug("Configuration:")
|
|
||||||
CONF.log_opt_values(LOG, log.DEBUG)
|
|
||||||
|
|
||||||
application = app.VersionSelectorApplication()
|
|
||||||
|
|
||||||
@@ -50,6 +50,21 @@ from watcher.decision_engine import rpcapi
|
|||||||
from watcher import objects
|
from watcher import objects
|
||||||
|
|
||||||
|
|
||||||
|
ALLOWED_AUDIT_TRANSITIONS = {
|
||||||
|
objects.audit.State.PENDING:
|
||||||
|
[objects.audit.State.ONGOING, objects.audit.State.CANCELLED],
|
||||||
|
objects.audit.State.ONGOING:
|
||||||
|
[objects.audit.State.FAILED, objects.audit.State.SUCCEEDED,
|
||||||
|
objects.audit.State.CANCELLED],
|
||||||
|
objects.audit.State.FAILED:
|
||||||
|
[objects.audit.State.DELETED],
|
||||||
|
objects.audit.State.SUCCEEDED:
|
||||||
|
[objects.audit.State.DELETED],
|
||||||
|
objects.audit.State.CANCELLED:
|
||||||
|
[objects.audit.State.DELETED]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class AuditPostType(wtypes.Base):
|
class AuditPostType(wtypes.Base):
|
||||||
|
|
||||||
audit_template_uuid = wtypes.wsattr(types.uuid, mandatory=False)
|
audit_template_uuid = wtypes.wsattr(types.uuid, mandatory=False)
|
||||||
@@ -129,15 +144,8 @@ class AuditPatchType(types.JsonPatchType):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def validate(patch):
|
def validate(patch):
|
||||||
|
serialized_patch = {'path': patch.path, 'op': patch.op}
|
||||||
def is_new_state_none(p):
|
if patch.path in AuditPatchType.mandatory_attrs():
|
||||||
return p.path == '/state' and p.op == 'replace' and p.value is None
|
|
||||||
|
|
||||||
serialized_patch = {'path': patch.path,
|
|
||||||
'op': patch.op,
|
|
||||||
'value': patch.value}
|
|
||||||
if (patch.path in AuditPatchType.mandatory_attrs() or
|
|
||||||
is_new_state_none(patch)):
|
|
||||||
msg = _("%(field)s can't be updated.")
|
msg = _("%(field)s can't be updated.")
|
||||||
raise exception.PatchError(
|
raise exception.PatchError(
|
||||||
patch=serialized_patch,
|
patch=serialized_patch,
|
||||||
@@ -301,7 +309,7 @@ class Audit(base.APIBase):
|
|||||||
audit.unset_fields_except(['uuid', 'audit_type', 'state',
|
audit.unset_fields_except(['uuid', 'audit_type', 'state',
|
||||||
'goal_uuid', 'interval', 'scope',
|
'goal_uuid', 'interval', 'scope',
|
||||||
'strategy_uuid', 'goal_name',
|
'strategy_uuid', 'goal_name',
|
||||||
'strategy_name', 'auto_trigger'])
|
'strategy_name'])
|
||||||
|
|
||||||
audit.links = [link.Link.make_link('self', url,
|
audit.links = [link.Link.make_link('self', url,
|
||||||
'audits', audit.uuid),
|
'audits', audit.uuid),
|
||||||
@@ -564,11 +572,14 @@ class AuditsController(rest.RestController):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
audit_dict = audit_to_update.as_dict()
|
audit_dict = audit_to_update.as_dict()
|
||||||
|
audit = Audit(**api_utils.apply_jsonpatch(audit_dict, patch))
|
||||||
|
except api_utils.JSONPATCH_EXCEPTIONS as e:
|
||||||
|
raise exception.PatchError(patch=patch, reason=e)
|
||||||
|
|
||||||
initial_state = audit_dict['state']
|
initial_state = audit_dict['state']
|
||||||
new_state = api_utils.get_patch_value(patch, 'state')
|
new_state = api_utils.get_patch_value(patch, 'state')
|
||||||
if not api_utils.check_audit_state_transition(
|
allowed_states = ALLOWED_AUDIT_TRANSITIONS.get(initial_state, [])
|
||||||
patch, initial_state):
|
if new_state is not None and new_state not in allowed_states:
|
||||||
error_message = _("State transition not allowed: "
|
error_message = _("State transition not allowed: "
|
||||||
"(%(initial_state)s -> %(new_state)s)")
|
"(%(initial_state)s -> %(new_state)s)")
|
||||||
raise exception.PatchError(
|
raise exception.PatchError(
|
||||||
@@ -576,10 +587,6 @@ class AuditsController(rest.RestController):
|
|||||||
reason=error_message % dict(
|
reason=error_message % dict(
|
||||||
initial_state=initial_state, new_state=new_state))
|
initial_state=initial_state, new_state=new_state))
|
||||||
|
|
||||||
audit = Audit(**api_utils.apply_jsonpatch(audit_dict, patch))
|
|
||||||
except api_utils.JSONPATCH_EXCEPTIONS as e:
|
|
||||||
raise exception.PatchError(patch=patch, reason=e)
|
|
||||||
|
|
||||||
# Update only the fields that have changed
|
# Update only the fields that have changed
|
||||||
for field in objects.Audit.fields:
|
for field in objects.Audit.fields:
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import wsme
|
|||||||
from wsme import types as wtypes
|
from wsme import types as wtypes
|
||||||
import wsmeext.pecan as wsme_pecan
|
import wsmeext.pecan as wsme_pecan
|
||||||
|
|
||||||
|
from watcher._i18n import _LW
|
||||||
from watcher.api.controllers import base
|
from watcher.api.controllers import base
|
||||||
from watcher.api.controllers import link
|
from watcher.api.controllers import link
|
||||||
from watcher.api.controllers.v1 import collection
|
from watcher.api.controllers.v1 import collection
|
||||||
@@ -55,8 +56,8 @@ class Service(base.APIBase):
|
|||||||
def _get_status(self):
|
def _get_status(self):
|
||||||
return self._status
|
return self._status
|
||||||
|
|
||||||
def _set_status(self, id):
|
def _set_status(self, name):
|
||||||
service = objects.Service.get(pecan.request.context, id)
|
service = objects.Service.get_by_name(pecan.request.context, name)
|
||||||
last_heartbeat = (service.last_seen_up or service.updated_at
|
last_heartbeat = (service.last_seen_up or service.updated_at
|
||||||
or service.created_at)
|
or service.created_at)
|
||||||
if isinstance(last_heartbeat, six.string_types):
|
if isinstance(last_heartbeat, six.string_types):
|
||||||
@@ -71,9 +72,9 @@ class Service(base.APIBase):
|
|||||||
elapsed = timeutils.delta_seconds(last_heartbeat, timeutils.utcnow())
|
elapsed = timeutils.delta_seconds(last_heartbeat, timeutils.utcnow())
|
||||||
is_up = abs(elapsed) <= CONF.service_down_time
|
is_up = abs(elapsed) <= CONF.service_down_time
|
||||||
if not is_up:
|
if not is_up:
|
||||||
LOG.warning('Seems service %(name)s on host %(host)s is down. '
|
LOG.warning(_LW('Seems service %(name)s on host %(host)s is down. '
|
||||||
'Last heartbeat was %(lhb)s.'
|
'Last heartbeat was %(lhb)s.'
|
||||||
'Elapsed time is %(el)s',
|
'Elapsed time is %(el)s'),
|
||||||
{'name': service.name,
|
{'name': service.name,
|
||||||
'host': service.host,
|
'host': service.host,
|
||||||
'lhb': str(last_heartbeat), 'el': str(elapsed)})
|
'lhb': str(last_heartbeat), 'el': str(elapsed)})
|
||||||
@@ -107,7 +108,7 @@ class Service(base.APIBase):
|
|||||||
for field in fields:
|
for field in fields:
|
||||||
self.fields.append(field)
|
self.fields.append(field)
|
||||||
setattr(self, field, kwargs.get(
|
setattr(self, field, kwargs.get(
|
||||||
field if field != 'status' else 'id', wtypes.Unset))
|
field if field != 'status' else 'name', wtypes.Unset))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _convert_with_links(service, url, expand=True):
|
def _convert_with_links(service, url, expand=True):
|
||||||
|
|||||||
@@ -79,15 +79,6 @@ def get_patch_value(patch, key):
|
|||||||
return p['value']
|
return p['value']
|
||||||
|
|
||||||
|
|
||||||
def check_audit_state_transition(patch, initial):
|
|
||||||
is_transition_valid = True
|
|
||||||
state_value = get_patch_value(patch, "state")
|
|
||||||
if state_value is not None:
|
|
||||||
is_transition_valid = objects.audit.AuditStateTransitionManager(
|
|
||||||
).check_transition(initial, state_value)
|
|
||||||
return is_transition_valid
|
|
||||||
|
|
||||||
|
|
||||||
def as_filters_dict(**filters):
|
def as_filters_dict(**filters):
|
||||||
filters_dict = {}
|
filters_dict = {}
|
||||||
for filter_name, filter_value in filters.items():
|
for filter_name, filter_value in filters.items():
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ from oslo_serialization import jsonutils
|
|||||||
import six
|
import six
|
||||||
import webob
|
import webob
|
||||||
|
|
||||||
from watcher._i18n import _
|
from watcher._i18n import _, _LE
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
@@ -79,7 +79,7 @@ class ParsableErrorMiddleware(object):
|
|||||||
et.ElementTree.Element(
|
et.ElementTree.Element(
|
||||||
'error_message', text='\n'.join(app_iter)))]
|
'error_message', text='\n'.join(app_iter)))]
|
||||||
except et.ElementTree.ParseError as err:
|
except et.ElementTree.ParseError as err:
|
||||||
LOG.error('Error parsing HTTP response: %s', err)
|
LOG.error(_LE('Error parsing HTTP response: %s'), err)
|
||||||
body = ['<error_message>%s'
|
body = ['<error_message>%s'
|
||||||
'</error_message>' % state['status_code']]
|
'</error_message>' % state['status_code']]
|
||||||
state['headers'].append(('Content-Type', 'application/xml'))
|
state['headers'].append(('Content-Type', 'application/xml'))
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ from oslo_log import log
|
|||||||
import six
|
import six
|
||||||
import voluptuous
|
import voluptuous
|
||||||
|
|
||||||
from watcher._i18n import _
|
from watcher._i18n import _, _LC
|
||||||
from watcher.applier.actions import base
|
from watcher.applier.actions import base
|
||||||
from watcher.common import exception
|
from watcher.common import exception
|
||||||
from watcher.common import nova_helper
|
from watcher.common import nova_helper
|
||||||
@@ -120,9 +120,9 @@ class Migrate(base.BaseAction):
|
|||||||
"migrating instance %s.Exception: %s" %
|
"migrating instance %s.Exception: %s" %
|
||||||
(self.instance_uuid, e))
|
(self.instance_uuid, e))
|
||||||
except Exception:
|
except Exception:
|
||||||
LOG.critical("Unexpected error occurred. Migration failed for "
|
LOG.critical(_LC("Unexpected error occurred. Migration failed for "
|
||||||
"instance %s. Leaving instance on previous "
|
"instance %s. Leaving instance on previous "
|
||||||
"host.", self.instance_uuid)
|
"host."), self.instance_uuid)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@@ -134,9 +134,9 @@ class Migrate(base.BaseAction):
|
|||||||
dest_hostname=destination)
|
dest_hostname=destination)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
LOG.exception(exc)
|
LOG.exception(exc)
|
||||||
LOG.critical("Unexpected error occurred. Migration failed for "
|
LOG.critical(_LC("Unexpected error occurred. Migration failed for "
|
||||||
"instance %s. Leaving instance on previous "
|
"instance %s. Leaving instance on previous "
|
||||||
"host.", self.instance_uuid)
|
"host."), self.instance_uuid)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ from oslo_log import log
|
|||||||
import six
|
import six
|
||||||
import voluptuous
|
import voluptuous
|
||||||
|
|
||||||
from watcher._i18n import _
|
from watcher._i18n import _, _LC
|
||||||
from watcher.applier.actions import base
|
from watcher.applier.actions import base
|
||||||
from watcher.common import nova_helper
|
from watcher.common import nova_helper
|
||||||
from watcher.common import utils
|
from watcher.common import utils
|
||||||
@@ -86,8 +86,8 @@ class Resize(base.BaseAction):
|
|||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
LOG.exception(exc)
|
LOG.exception(exc)
|
||||||
LOG.critical(
|
LOG.critical(
|
||||||
"Unexpected error occurred. Resizing failed for "
|
_LC("Unexpected error occurred. Resizing failed for "
|
||||||
"instance %s.", self.instance_uuid)
|
"instance %s."), self.instance_uuid)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def execute(self):
|
def execute(self):
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ class ApplierAPI(service.Service):
|
|||||||
if not utils.is_uuid_like(action_plan_uuid):
|
if not utils.is_uuid_like(action_plan_uuid):
|
||||||
raise exception.InvalidUuidOrName(name=action_plan_uuid)
|
raise exception.InvalidUuidOrName(name=action_plan_uuid)
|
||||||
|
|
||||||
self.conductor_client.cast(
|
return self.conductor_client.call(
|
||||||
context, 'launch_action_plan', action_plan_uuid=action_plan_uuid)
|
context, 'launch_action_plan', action_plan_uuid=action_plan_uuid)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -18,19 +18,12 @@
|
|||||||
|
|
||||||
import abc
|
import abc
|
||||||
|
|
||||||
from oslo_log import log
|
|
||||||
import six
|
import six
|
||||||
from taskflow import task as flow_task
|
|
||||||
|
|
||||||
from watcher.applier.actions import factory
|
from watcher.applier.actions import factory
|
||||||
from watcher.common import clients
|
from watcher.common import clients
|
||||||
from watcher.common.loader import loadable
|
from watcher.common.loader import loadable
|
||||||
from watcher import notifications
|
|
||||||
from watcher import objects
|
from watcher import objects
|
||||||
from watcher.objects import fields
|
|
||||||
|
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(abc.ABCMeta)
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
@@ -79,95 +72,11 @@ class BaseWorkFlowEngine(loadable.Loadable):
|
|||||||
return self._action_factory
|
return self._action_factory
|
||||||
|
|
||||||
def notify(self, action, state):
|
def notify(self, action, state):
|
||||||
db_action = objects.Action.get_by_uuid(self.context, action.uuid,
|
db_action = objects.Action.get_by_uuid(self.context, action.uuid)
|
||||||
eager=True)
|
|
||||||
db_action.state = state
|
db_action.state = state
|
||||||
db_action.save()
|
db_action.save()
|
||||||
|
# NOTE(v-francoise): Implement notifications for action
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def execute(self, actions):
|
def execute(self, actions):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
class BaseTaskFlowActionContainer(flow_task.Task):
|
|
||||||
|
|
||||||
def __init__(self, name, db_action, engine, **kwargs):
|
|
||||||
super(BaseTaskFlowActionContainer, self).__init__(name=name)
|
|
||||||
self._db_action = db_action
|
|
||||||
self._engine = engine
|
|
||||||
self.loaded_action = None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def engine(self):
|
|
||||||
return self._engine
|
|
||||||
|
|
||||||
@property
|
|
||||||
def action(self):
|
|
||||||
if self.loaded_action is None:
|
|
||||||
action = self.engine.action_factory.make_action(
|
|
||||||
self._db_action,
|
|
||||||
osc=self._engine.osc)
|
|
||||||
self.loaded_action = action
|
|
||||||
return self.loaded_action
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def do_pre_execute(self):
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def do_execute(self, *args, **kwargs):
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def do_post_execute(self):
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
# NOTE(alexchadin): taskflow does 3 method calls (pre_execute, execute,
|
|
||||||
# post_execute) independently. We want to support notifications in base
|
|
||||||
# class, so child's methods should be named with `do_` prefix and wrapped.
|
|
||||||
def pre_execute(self):
|
|
||||||
try:
|
|
||||||
self.do_pre_execute()
|
|
||||||
notifications.action.send_execution_notification(
|
|
||||||
self.engine.context, self._db_action,
|
|
||||||
fields.NotificationAction.EXECUTION,
|
|
||||||
fields.NotificationPhase.START)
|
|
||||||
except Exception as e:
|
|
||||||
LOG.exception(e)
|
|
||||||
self.engine.notify(self._db_action, objects.action.State.FAILED)
|
|
||||||
notifications.action.send_execution_notification(
|
|
||||||
self.engine.context, self._db_action,
|
|
||||||
fields.NotificationAction.EXECUTION,
|
|
||||||
fields.NotificationPhase.ERROR,
|
|
||||||
priority=fields.NotificationPriority.ERROR)
|
|
||||||
|
|
||||||
def execute(self, *args, **kwargs):
|
|
||||||
try:
|
|
||||||
self.do_execute(*args, **kwargs)
|
|
||||||
notifications.action.send_execution_notification(
|
|
||||||
self.engine.context, self._db_action,
|
|
||||||
fields.NotificationAction.EXECUTION,
|
|
||||||
fields.NotificationPhase.END)
|
|
||||||
except Exception as e:
|
|
||||||
LOG.exception(e)
|
|
||||||
LOG.error('The workflow engine has failed '
|
|
||||||
'to execute the action: %s', self.name)
|
|
||||||
self.engine.notify(self._db_action, objects.action.State.FAILED)
|
|
||||||
notifications.action.send_execution_notification(
|
|
||||||
self.engine.context, self._db_action,
|
|
||||||
fields.NotificationAction.EXECUTION,
|
|
||||||
fields.NotificationPhase.ERROR,
|
|
||||||
priority=fields.NotificationPriority.ERROR)
|
|
||||||
raise
|
|
||||||
|
|
||||||
def post_execute(self):
|
|
||||||
try:
|
|
||||||
self.do_post_execute()
|
|
||||||
except Exception as e:
|
|
||||||
LOG.exception(e)
|
|
||||||
self.engine.notify(self._db_action, objects.action.State.FAILED)
|
|
||||||
notifications.action.send_execution_notification(
|
|
||||||
self.engine.context, self._db_action,
|
|
||||||
fields.NotificationAction.EXECUTION,
|
|
||||||
fields.NotificationPhase.ERROR,
|
|
||||||
priority=fields.NotificationPriority.ERROR)
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ from taskflow import engines
|
|||||||
from taskflow.patterns import graph_flow as gf
|
from taskflow.patterns import graph_flow as gf
|
||||||
from taskflow import task as flow_task
|
from taskflow import task as flow_task
|
||||||
|
|
||||||
|
from watcher._i18n import _LE, _LW, _LC
|
||||||
from watcher.applier.workflow_engine import base
|
from watcher.applier.workflow_engine import base
|
||||||
from watcher.common import exception
|
from watcher.common import exception
|
||||||
from watcher import objects
|
from watcher import objects
|
||||||
@@ -94,35 +95,69 @@ class DefaultWorkFlowEngine(base.BaseWorkFlowEngine):
|
|||||||
raise exception.WorkflowExecutionException(error=e)
|
raise exception.WorkflowExecutionException(error=e)
|
||||||
|
|
||||||
|
|
||||||
class TaskFlowActionContainer(base.BaseTaskFlowActionContainer):
|
class TaskFlowActionContainer(flow_task.Task):
|
||||||
def __init__(self, db_action, engine):
|
def __init__(self, db_action, engine):
|
||||||
name = "action_type:{0} uuid:{1}".format(db_action.action_type,
|
name = "action_type:{0} uuid:{1}".format(db_action.action_type,
|
||||||
db_action.uuid)
|
db_action.uuid)
|
||||||
super(TaskFlowActionContainer, self).__init__(name, db_action, engine)
|
super(TaskFlowActionContainer, self).__init__(name=name)
|
||||||
|
self._db_action = db_action
|
||||||
|
self._engine = engine
|
||||||
|
self.loaded_action = None
|
||||||
|
|
||||||
def do_pre_execute(self):
|
@property
|
||||||
|
def action(self):
|
||||||
|
if self.loaded_action is None:
|
||||||
|
action = self.engine.action_factory.make_action(
|
||||||
|
self._db_action,
|
||||||
|
osc=self._engine.osc)
|
||||||
|
self.loaded_action = action
|
||||||
|
return self.loaded_action
|
||||||
|
|
||||||
|
@property
|
||||||
|
def engine(self):
|
||||||
|
return self._engine
|
||||||
|
|
||||||
|
def pre_execute(self):
|
||||||
|
try:
|
||||||
self.engine.notify(self._db_action, objects.action.State.ONGOING)
|
self.engine.notify(self._db_action, objects.action.State.ONGOING)
|
||||||
LOG.debug("Pre-condition action: %s", self.name)
|
LOG.debug("Pre-condition action: %s", self.name)
|
||||||
self.action.pre_condition()
|
self.action.pre_condition()
|
||||||
|
except Exception as e:
|
||||||
|
LOG.exception(e)
|
||||||
|
self.engine.notify(self._db_action, objects.action.State.FAILED)
|
||||||
|
raise
|
||||||
|
|
||||||
def do_execute(self, *args, **kwargs):
|
def execute(self, *args, **kwargs):
|
||||||
|
try:
|
||||||
LOG.debug("Running action: %s", self.name)
|
LOG.debug("Running action: %s", self.name)
|
||||||
|
|
||||||
self.action.execute()
|
self.action.execute()
|
||||||
self.engine.notify(self._db_action, objects.action.State.SUCCEEDED)
|
self.engine.notify(self._db_action, objects.action.State.SUCCEEDED)
|
||||||
|
except Exception as e:
|
||||||
|
LOG.exception(e)
|
||||||
|
LOG.error(_LE('The workflow engine has failed '
|
||||||
|
'to execute the action: %s'), self.name)
|
||||||
|
|
||||||
def do_post_execute(self):
|
self.engine.notify(self._db_action, objects.action.State.FAILED)
|
||||||
|
raise
|
||||||
|
|
||||||
|
def post_execute(self):
|
||||||
|
try:
|
||||||
LOG.debug("Post-condition action: %s", self.name)
|
LOG.debug("Post-condition action: %s", self.name)
|
||||||
self.action.post_condition()
|
self.action.post_condition()
|
||||||
|
except Exception as e:
|
||||||
|
LOG.exception(e)
|
||||||
|
self.engine.notify(self._db_action, objects.action.State.FAILED)
|
||||||
|
raise
|
||||||
|
|
||||||
def revert(self, *args, **kwargs):
|
def revert(self, *args, **kwargs):
|
||||||
LOG.warning("Revert action: %s", self.name)
|
LOG.warning(_LW("Revert action: %s"), self.name)
|
||||||
try:
|
try:
|
||||||
# TODO(jed): do we need to update the states in case of failure?
|
# TODO(jed): do we need to update the states in case of failure?
|
||||||
self.action.revert()
|
self.action.revert()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.exception(e)
|
LOG.exception(e)
|
||||||
LOG.critical("Oops! We need a disaster recover plan.")
|
LOG.critical(_LC("Oops! We need a disaster recover plan."))
|
||||||
|
|
||||||
|
|
||||||
class TaskFlowNop(flow_task.Task):
|
class TaskFlowNop(flow_task.Task):
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import sys
|
|||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
|
||||||
|
from watcher._i18n import _LI
|
||||||
from watcher.common import service
|
from watcher.common import service
|
||||||
from watcher import conf
|
from watcher import conf
|
||||||
|
|
||||||
@@ -38,11 +39,11 @@ def main():
|
|||||||
server = service.WSGIService('watcher-api', CONF.api.enable_ssl_api)
|
server = service.WSGIService('watcher-api', CONF.api.enable_ssl_api)
|
||||||
|
|
||||||
if host == '127.0.0.1':
|
if host == '127.0.0.1':
|
||||||
LOG.info('serving on 127.0.0.1:%(port)s, '
|
LOG.info(_LI('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(_LI('serving on %(protocol)s://%(host)s:%(port)s') %
|
||||||
dict(protocol=protocol, host=host, port=port))
|
dict(protocol=protocol, host=host, port=port))
|
||||||
|
|
||||||
launcher = service.launch(CONF, server, workers=server.workers)
|
launcher = service.launch(CONF, server, workers=server.workers)
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import sys
|
|||||||
|
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
|
||||||
|
from watcher._i18n import _LI
|
||||||
from watcher.applier import manager
|
from watcher.applier import manager
|
||||||
from watcher.common import service as watcher_service
|
from watcher.common import service as watcher_service
|
||||||
from watcher import conf
|
from watcher import conf
|
||||||
@@ -33,7 +34,7 @@ CONF = conf.CONF
|
|||||||
def main():
|
def main():
|
||||||
watcher_service.prepare_service(sys.argv, CONF)
|
watcher_service.prepare_service(sys.argv, CONF)
|
||||||
|
|
||||||
LOG.info('Starting Watcher Applier service in PID %s', os.getpid())
|
LOG.info(_LI('Starting Watcher Applier service in PID %s'), os.getpid())
|
||||||
|
|
||||||
applier_service = watcher_service.Service(manager.ApplierManager)
|
applier_service = watcher_service.Service(manager.ApplierManager)
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import sys
|
|||||||
|
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
|
||||||
|
from watcher._i18n import _LI
|
||||||
from watcher.common import service as watcher_service
|
from watcher.common import service as watcher_service
|
||||||
from watcher import conf
|
from watcher import conf
|
||||||
from watcher.decision_engine import gmr
|
from watcher.decision_engine import gmr
|
||||||
@@ -37,7 +38,7 @@ def main():
|
|||||||
watcher_service.prepare_service(sys.argv, CONF)
|
watcher_service.prepare_service(sys.argv, CONF)
|
||||||
gmr.register_gmr_plugins()
|
gmr.register_gmr_plugins()
|
||||||
|
|
||||||
LOG.info('Starting Watcher Decision Engine service in PID %s',
|
LOG.info(_LI('Starting Watcher Decision Engine service in PID %s'),
|
||||||
os.getpid())
|
os.getpid())
|
||||||
|
|
||||||
syncer = sync.Syncer()
|
syncer = sync.Syncer()
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import sys
|
|||||||
|
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
|
||||||
|
from watcher._i18n import _LI
|
||||||
from watcher.common import service as service
|
from watcher.common import service as service
|
||||||
from watcher import conf
|
from watcher import conf
|
||||||
from watcher.decision_engine import sync
|
from watcher.decision_engine import sync
|
||||||
@@ -31,10 +32,10 @@ CONF = conf.CONF
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
LOG.info('Watcher sync started.')
|
LOG.info(_LI('Watcher sync started.'))
|
||||||
|
|
||||||
service.prepare_service(sys.argv, CONF)
|
service.prepare_service(sys.argv, CONF)
|
||||||
syncer = sync.Syncer()
|
syncer = sync.Syncer()
|
||||||
syncer.sync()
|
syncer.sync()
|
||||||
|
|
||||||
LOG.info('Watcher sync finished.')
|
LOG.info(_LI('Watcher sync finished.'))
|
||||||
|
|||||||
@@ -13,7 +13,6 @@
|
|||||||
from ceilometerclient import client as ceclient
|
from ceilometerclient import client as ceclient
|
||||||
from cinderclient import client as ciclient
|
from cinderclient import client as ciclient
|
||||||
from glanceclient import client as glclient
|
from glanceclient import client as glclient
|
||||||
from gnocchiclient import client as gnclient
|
|
||||||
from keystoneauth1 import loading as ka_loading
|
from keystoneauth1 import loading as ka_loading
|
||||||
from keystoneclient import client as keyclient
|
from keystoneclient import client as keyclient
|
||||||
from monascaclient import client as monclient
|
from monascaclient import client as monclient
|
||||||
@@ -40,7 +39,6 @@ class OpenStackClients(object):
|
|||||||
self._keystone = None
|
self._keystone = None
|
||||||
self._nova = None
|
self._nova = None
|
||||||
self._glance = None
|
self._glance = None
|
||||||
self._gnocchi = None
|
|
||||||
self._cinder = None
|
self._cinder = None
|
||||||
self._ceilometer = None
|
self._ceilometer = None
|
||||||
self._monasca = None
|
self._monasca = None
|
||||||
@@ -80,9 +78,7 @@ class OpenStackClients(object):
|
|||||||
return self._nova
|
return self._nova
|
||||||
|
|
||||||
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')
|
|
||||||
self._nova = nvclient.Client(novaclient_version,
|
self._nova = nvclient.Client(novaclient_version,
|
||||||
endpoint_type=nova_endpoint_type,
|
|
||||||
session=self.session)
|
session=self.session)
|
||||||
return self._nova
|
return self._nova
|
||||||
|
|
||||||
@@ -92,37 +88,17 @@ class OpenStackClients(object):
|
|||||||
return self._glance
|
return self._glance
|
||||||
|
|
||||||
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',
|
|
||||||
'endpoint_type')
|
|
||||||
self._glance = glclient.Client(glanceclient_version,
|
self._glance = glclient.Client(glanceclient_version,
|
||||||
interface=glance_endpoint_type,
|
|
||||||
session=self.session)
|
session=self.session)
|
||||||
return self._glance
|
return self._glance
|
||||||
|
|
||||||
@exception.wrap_keystone_exception
|
|
||||||
def gnocchi(self):
|
|
||||||
if self._gnocchi:
|
|
||||||
return self._gnocchi
|
|
||||||
|
|
||||||
gnocchiclient_version = self._get_client_option('gnocchi',
|
|
||||||
'api_version')
|
|
||||||
gnocchiclient_interface = self._get_client_option('gnocchi',
|
|
||||||
'endpoint_type')
|
|
||||||
self._gnocchi = gnclient.Client(gnocchiclient_version,
|
|
||||||
interface=gnocchiclient_interface,
|
|
||||||
session=self.session)
|
|
||||||
return self._gnocchi
|
|
||||||
|
|
||||||
@exception.wrap_keystone_exception
|
@exception.wrap_keystone_exception
|
||||||
def cinder(self):
|
def cinder(self):
|
||||||
if self._cinder:
|
if self._cinder:
|
||||||
return self._cinder
|
return self._cinder
|
||||||
|
|
||||||
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',
|
|
||||||
'endpoint_type')
|
|
||||||
self._cinder = ciclient.Client(cinderclient_version,
|
self._cinder = ciclient.Client(cinderclient_version,
|
||||||
endpoint_type=cinder_endpoint_type,
|
|
||||||
session=self.session)
|
session=self.session)
|
||||||
return self._cinder
|
return self._cinder
|
||||||
|
|
||||||
@@ -133,11 +109,7 @@ class OpenStackClients(object):
|
|||||||
|
|
||||||
ceilometerclient_version = self._get_client_option('ceilometer',
|
ceilometerclient_version = self._get_client_option('ceilometer',
|
||||||
'api_version')
|
'api_version')
|
||||||
ceilometer_endpoint_type = self._get_client_option('ceilometer',
|
self._ceilometer = ceclient.get_client(ceilometerclient_version,
|
||||||
'endpoint_type')
|
|
||||||
self._ceilometer = ceclient.get_client(
|
|
||||||
ceilometerclient_version,
|
|
||||||
endpoint_type=ceilometer_endpoint_type,
|
|
||||||
session=self.session)
|
session=self.session)
|
||||||
return self._ceilometer
|
return self._ceilometer
|
||||||
|
|
||||||
@@ -148,8 +120,6 @@ class OpenStackClients(object):
|
|||||||
|
|
||||||
monascaclient_version = self._get_client_option(
|
monascaclient_version = self._get_client_option(
|
||||||
'monasca', 'api_version')
|
'monasca', 'api_version')
|
||||||
monascaclient_interface = self._get_client_option(
|
|
||||||
'monasca', 'interface')
|
|
||||||
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'
|
||||||
@@ -165,8 +135,7 @@ class OpenStackClients(object):
|
|||||||
'username': watcher_clients_auth_config.username,
|
'username': watcher_clients_auth_config.username,
|
||||||
'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)
|
|
||||||
|
|
||||||
self._monasca = monclient.Client(
|
self._monasca = monclient.Client(
|
||||||
monascaclient_version, endpoint, **monasca_kwargs)
|
monascaclient_version, endpoint, **monasca_kwargs)
|
||||||
@@ -180,11 +149,7 @@ class OpenStackClients(object):
|
|||||||
|
|
||||||
neutronclient_version = self._get_client_option('neutron',
|
neutronclient_version = self._get_client_option('neutron',
|
||||||
'api_version')
|
'api_version')
|
||||||
neutron_endpoint_type = self._get_client_option('neutron',
|
|
||||||
'endpoint_type')
|
|
||||||
|
|
||||||
self._neutron = netclient.Client(neutronclient_version,
|
self._neutron = netclient.Client(neutronclient_version,
|
||||||
endpoint_type=neutron_endpoint_type,
|
|
||||||
session=self.session)
|
session=self.session)
|
||||||
self._neutron.format = 'json'
|
self._neutron.format = 'json'
|
||||||
return self._neutron
|
return self._neutron
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ from oslo_log import log as logging
|
|||||||
from oslo_utils import timeutils
|
from oslo_utils import timeutils
|
||||||
import six
|
import six
|
||||||
|
|
||||||
|
from watcher._i18n import _LW
|
||||||
from watcher.common import utils
|
from watcher.common import utils
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
@@ -64,7 +65,7 @@ class RequestContext(context.RequestContext):
|
|||||||
# safely ignore this as we don't use it.
|
# safely ignore this as we don't use it.
|
||||||
kwargs.pop('user_identity', None)
|
kwargs.pop('user_identity', None)
|
||||||
if kwargs:
|
if kwargs:
|
||||||
LOG.warning('Arguments dropped when creating context: %s',
|
LOG.warning(_LW('Arguments dropped when creating context: %s'),
|
||||||
str(kwargs))
|
str(kwargs))
|
||||||
|
|
||||||
# FIXME(dims): user_id and project_id duplicate information that is
|
# FIXME(dims): user_id and project_id duplicate information that is
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ from keystoneclient import exceptions as keystone_exceptions
|
|||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from watcher._i18n import _
|
from watcher._i18n import _, _LE
|
||||||
|
|
||||||
from watcher import conf
|
from watcher import conf
|
||||||
|
|
||||||
@@ -83,9 +83,9 @@ class WatcherException(Exception):
|
|||||||
except Exception:
|
except Exception:
|
||||||
# kwargs doesn't match a variable in msg_fmt
|
# kwargs doesn't match a variable in msg_fmt
|
||||||
# log the issue and the kwargs
|
# log the issue and the kwargs
|
||||||
LOG.exception('Exception in string format operation')
|
LOG.exception(_LE('Exception in string format operation'))
|
||||||
for name, value in kwargs.items():
|
for name, value in kwargs.items():
|
||||||
LOG.error("%(name)s: %(value)s",
|
LOG.error(_LE("%(name)s: %(value)s"),
|
||||||
{'name': name, 'value': value})
|
{'name': name, 'value': value})
|
||||||
|
|
||||||
if CONF.fatal_exception_format_errors:
|
if CONF.fatal_exception_format_errors:
|
||||||
@@ -130,7 +130,7 @@ class OperationNotPermitted(NotAuthorized):
|
|||||||
msg_fmt = _("Operation not permitted")
|
msg_fmt = _("Operation not permitted")
|
||||||
|
|
||||||
|
|
||||||
class Invalid(WatcherException, ValueError):
|
class Invalid(WatcherException):
|
||||||
msg_fmt = _("Unacceptable parameters")
|
msg_fmt = _("Unacceptable parameters")
|
||||||
code = 400
|
code = 400
|
||||||
|
|
||||||
@@ -149,10 +149,6 @@ class ResourceNotFound(ObjectNotFound):
|
|||||||
code = 404
|
code = 404
|
||||||
|
|
||||||
|
|
||||||
class InvalidParameter(Invalid):
|
|
||||||
msg_fmt = _("%(parameter)s has to be of type %(parameter_type)s")
|
|
||||||
|
|
||||||
|
|
||||||
class InvalidIdentity(Invalid):
|
class InvalidIdentity(Invalid):
|
||||||
msg_fmt = _("Expected a uuid or int but received %(identity)s")
|
msg_fmt = _("Expected a uuid or int but received %(identity)s")
|
||||||
|
|
||||||
@@ -186,10 +182,6 @@ class EagerlyLoadedActionPlanRequired(InvalidActionPlan):
|
|||||||
msg_fmt = _("Action plan %(action_plan)s was not eagerly loaded")
|
msg_fmt = _("Action plan %(action_plan)s was not eagerly loaded")
|
||||||
|
|
||||||
|
|
||||||
class EagerlyLoadedActionRequired(InvalidActionPlan):
|
|
||||||
msg_fmt = _("Action %(action)s was not eagerly loaded")
|
|
||||||
|
|
||||||
|
|
||||||
class InvalidUUID(Invalid):
|
class InvalidUUID(Invalid):
|
||||||
msg_fmt = _("Expected a uuid but received %(uuid)s")
|
msg_fmt = _("Expected a uuid but received %(uuid)s")
|
||||||
|
|
||||||
@@ -275,7 +267,9 @@ class ActionPlanReferenced(Invalid):
|
|||||||
|
|
||||||
|
|
||||||
class ActionPlanIsOngoing(Conflict):
|
class ActionPlanIsOngoing(Conflict):
|
||||||
msg_fmt = _("Action Plan %(action_plan)s is currently running.")
|
msg_fmt = _("Action Plan %(action_plan)s is currently running. "
|
||||||
|
"New Action Plan %(new_action_plan)s will be set as "
|
||||||
|
"SUPERSEDED")
|
||||||
|
|
||||||
|
|
||||||
class ActionNotFound(ResourceNotFound):
|
class ActionNotFound(ResourceNotFound):
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ from oslo_config import cfg
|
|||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
import oslo_messaging as messaging
|
import oslo_messaging as messaging
|
||||||
|
|
||||||
|
from watcher._i18n import _LE
|
||||||
from watcher.common import context as watcher_context
|
from watcher.common import context as watcher_context
|
||||||
from watcher.common import exception
|
from watcher.common import exception
|
||||||
|
|
||||||
@@ -31,6 +32,7 @@ __all__ = [
|
|||||||
'get_client',
|
'get_client',
|
||||||
'get_server',
|
'get_server',
|
||||||
'get_notifier',
|
'get_notifier',
|
||||||
|
'TRANSPORT_ALIASES',
|
||||||
]
|
]
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
@@ -44,6 +46,16 @@ ALLOWED_EXMODS = [
|
|||||||
]
|
]
|
||||||
EXTRA_EXMODS = []
|
EXTRA_EXMODS = []
|
||||||
|
|
||||||
|
# NOTE(lucasagomes): The watcher.openstack.common.rpc entries are for
|
||||||
|
# backwards compat with IceHouse rpc_backend configuration values.
|
||||||
|
TRANSPORT_ALIASES = {
|
||||||
|
'watcher.openstack.common.rpc.impl_kombu': 'rabbit',
|
||||||
|
'watcher.openstack.common.rpc.impl_qpid': 'qpid',
|
||||||
|
'watcher.openstack.common.rpc.impl_zmq': 'zmq',
|
||||||
|
'watcher.rpc.impl_kombu': 'rabbit',
|
||||||
|
'watcher.rpc.impl_qpid': 'qpid',
|
||||||
|
'watcher.rpc.impl_zmq': 'zmq',
|
||||||
|
}
|
||||||
|
|
||||||
JsonPayloadSerializer = messaging.JsonPayloadSerializer
|
JsonPayloadSerializer = messaging.JsonPayloadSerializer
|
||||||
|
|
||||||
@@ -52,10 +64,12 @@ def init(conf):
|
|||||||
global TRANSPORT, NOTIFICATION_TRANSPORT, NOTIFIER
|
global TRANSPORT, NOTIFICATION_TRANSPORT, NOTIFIER
|
||||||
exmods = get_allowed_exmods()
|
exmods = get_allowed_exmods()
|
||||||
TRANSPORT = messaging.get_transport(conf,
|
TRANSPORT = messaging.get_transport(conf,
|
||||||
allowed_remote_exmods=exmods)
|
allowed_remote_exmods=exmods,
|
||||||
|
aliases=TRANSPORT_ALIASES)
|
||||||
NOTIFICATION_TRANSPORT = messaging.get_notification_transport(
|
NOTIFICATION_TRANSPORT = messaging.get_notification_transport(
|
||||||
conf,
|
conf,
|
||||||
allowed_remote_exmods=exmods)
|
allowed_remote_exmods=exmods,
|
||||||
|
aliases=TRANSPORT_ALIASES)
|
||||||
|
|
||||||
serializer = RequestContextSerializer(JsonPayloadSerializer())
|
serializer = RequestContextSerializer(JsonPayloadSerializer())
|
||||||
if not conf.notification_level:
|
if not conf.notification_level:
|
||||||
@@ -73,7 +87,7 @@ def initialized():
|
|||||||
def cleanup():
|
def cleanup():
|
||||||
global TRANSPORT, NOTIFICATION_TRANSPORT, NOTIFIER
|
global TRANSPORT, NOTIFICATION_TRANSPORT, NOTIFIER
|
||||||
if NOTIFIER is None:
|
if NOTIFIER is None:
|
||||||
LOG.exception("RPC cleanup: NOTIFIER is None")
|
LOG.exception(_LE("RPC cleanup: NOTIFIER is None"))
|
||||||
TRANSPORT.cleanup()
|
TRANSPORT.cleanup()
|
||||||
NOTIFICATION_TRANSPORT.cleanup()
|
NOTIFICATION_TRANSPORT.cleanup()
|
||||||
TRANSPORT = NOTIFICATION_TRANSPORT = NOTIFIER = None
|
TRANSPORT = NOTIFICATION_TRANSPORT = NOTIFIER = None
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ from oslo_utils import timeutils
|
|||||||
from oslo_utils import uuidutils
|
from oslo_utils import uuidutils
|
||||||
import six
|
import six
|
||||||
|
|
||||||
|
from watcher._i18n import _LW
|
||||||
from watcher.common import exception
|
from watcher.common import exception
|
||||||
|
|
||||||
from watcher import conf
|
from watcher import conf
|
||||||
@@ -72,9 +73,9 @@ def safe_rstrip(value, chars=None):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
if not isinstance(value, six.string_types):
|
if not isinstance(value, six.string_types):
|
||||||
LOG.warning(
|
LOG.warning(_LW(
|
||||||
"Failed to remove trailing character. Returning original object."
|
"Failed to remove trailing character. Returning original object."
|
||||||
"Supplied object is not a string: %s,", value)
|
"Supplied object is not a string: %s,"), value)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
return value.rstrip(chars) or value
|
return value.rstrip(chars) or value
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ from watcher.conf import db
|
|||||||
from watcher.conf import decision_engine
|
from watcher.conf import decision_engine
|
||||||
from watcher.conf import exception
|
from watcher.conf import exception
|
||||||
from watcher.conf import glance_client
|
from watcher.conf import glance_client
|
||||||
from watcher.conf import gnocchi_client
|
|
||||||
from watcher.conf import monasca_client
|
from watcher.conf import monasca_client
|
||||||
from watcher.conf import neutron_client
|
from watcher.conf import neutron_client
|
||||||
from watcher.conf import nova_client
|
from watcher.conf import nova_client
|
||||||
@@ -51,7 +50,6 @@ decision_engine.register_opts(CONF)
|
|||||||
monasca_client.register_opts(CONF)
|
monasca_client.register_opts(CONF)
|
||||||
nova_client.register_opts(CONF)
|
nova_client.register_opts(CONF)
|
||||||
glance_client.register_opts(CONF)
|
glance_client.register_opts(CONF)
|
||||||
gnocchi_client.register_opts(CONF)
|
|
||||||
cinder_client.register_opts(CONF)
|
cinder_client.register_opts(CONF)
|
||||||
ceilometer_client.register_opts(CONF)
|
ceilometer_client.register_opts(CONF)
|
||||||
neutron_client.register_opts(CONF)
|
neutron_client.register_opts(CONF)
|
||||||
|
|||||||
@@ -32,10 +32,9 @@ API_SERVICE_OPTS = [
|
|||||||
cfg.PortOpt('port',
|
cfg.PortOpt('port',
|
||||||
default=9322,
|
default=9322,
|
||||||
help='The port for the watcher API server'),
|
help='The port for the watcher API server'),
|
||||||
cfg.HostAddressOpt('host',
|
cfg.StrOpt('host',
|
||||||
default='127.0.0.1',
|
default='127.0.0.1',
|
||||||
help='The listen IP address for the watcher API server'
|
help='The listen IP address for the watcher API server'),
|
||||||
),
|
|
||||||
cfg.IntOpt('max_limit',
|
cfg.IntOpt('max_limit',
|
||||||
default=1000,
|
default=1000,
|
||||||
help='The maximum number of items returned in a single '
|
help='The maximum number of items returned in a single '
|
||||||
|
|||||||
@@ -25,12 +25,7 @@ CEILOMETER_CLIENT_OPTS = [
|
|||||||
cfg.StrOpt('api_version',
|
cfg.StrOpt('api_version',
|
||||||
default='2',
|
default='2',
|
||||||
help='Version of Ceilometer API to use in '
|
help='Version of Ceilometer API to use in '
|
||||||
'ceilometerclient.'),
|
'ceilometerclient.')]
|
||||||
cfg.StrOpt('endpoint_type',
|
|
||||||
default='internalURL',
|
|
||||||
help='Type of endpoint to use in ceilometerclient.'
|
|
||||||
'Supported values: internalURL, publicURL, adminURL'
|
|
||||||
'The default is internalURL.')]
|
|
||||||
|
|
||||||
|
|
||||||
def register_opts(conf):
|
def register_opts(conf):
|
||||||
|
|||||||
@@ -24,12 +24,7 @@ cinder_client = cfg.OptGroup(name='cinder_client',
|
|||||||
CINDER_CLIENT_OPTS = [
|
CINDER_CLIENT_OPTS = [
|
||||||
cfg.StrOpt('api_version',
|
cfg.StrOpt('api_version',
|
||||||
default='2',
|
default='2',
|
||||||
help='Version of Cinder API to use in cinderclient.'),
|
help='Version of Cinder API to use in cinderclient.')]
|
||||||
cfg.StrOpt('endpoint_type',
|
|
||||||
default='internalURL',
|
|
||||||
help='Type of endpoint to use in cinderclient.'
|
|
||||||
'Supported values: internalURL, publicURL, adminURL'
|
|
||||||
'The default is internalURL.')]
|
|
||||||
|
|
||||||
|
|
||||||
def register_opts(conf):
|
def register_opts(conf):
|
||||||
|
|||||||
@@ -42,15 +42,6 @@ WATCHER_DECISION_ENGINE_OPTS = [
|
|||||||
required=True,
|
required=True,
|
||||||
help='The maximum number of threads that can be used to '
|
help='The maximum number of threads that can be used to '
|
||||||
'execute strategies'),
|
'execute strategies'),
|
||||||
cfg.IntOpt('action_plan_expiry',
|
|
||||||
default=24,
|
|
||||||
help='An expiry timespan(hours). Watcher invalidates any '
|
|
||||||
'action plan for which its creation time '
|
|
||||||
'-whose number of hours has been offset by this value-'
|
|
||||||
' is older that the current time.'),
|
|
||||||
cfg.IntOpt('check_periodic_interval',
|
|
||||||
default=30*60,
|
|
||||||
help='Interval (in seconds) for checking action plan expiry.')
|
|
||||||
]
|
]
|
||||||
|
|
||||||
WATCHER_CONTINUOUS_OPTS = [
|
WATCHER_CONTINUOUS_OPTS = [
|
||||||
|
|||||||
@@ -24,12 +24,7 @@ glance_client = cfg.OptGroup(name='glance_client',
|
|||||||
GLANCE_CLIENT_OPTS = [
|
GLANCE_CLIENT_OPTS = [
|
||||||
cfg.StrOpt('api_version',
|
cfg.StrOpt('api_version',
|
||||||
default='2',
|
default='2',
|
||||||
help='Version of Glance API to use in glanceclient.'),
|
help='Version of Glance API to use in glanceclient.')]
|
||||||
cfg.StrOpt('endpoint_type',
|
|
||||||
default='internalURL',
|
|
||||||
help='Type of endpoint to use in glanceclient.'
|
|
||||||
'Supported values: internalURL, publicURL, adminURL'
|
|
||||||
'The default is internalURL.')]
|
|
||||||
|
|
||||||
|
|
||||||
def register_opts(conf):
|
def register_opts(conf):
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
# Copyright (c) 2017 Servionica
|
|
||||||
#
|
|
||||||
# Authors: Alexander Chadin <a.chadin@servionica.ru>
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
# implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
from oslo_config import cfg
|
|
||||||
|
|
||||||
gnocchi_client = cfg.OptGroup(name='gnocchi_client',
|
|
||||||
title='Configuration Options for Gnocchi')
|
|
||||||
|
|
||||||
GNOCCHI_CLIENT_OPTS = [
|
|
||||||
cfg.StrOpt('api_version',
|
|
||||||
default='1',
|
|
||||||
help='Version of Gnocchi API to use in gnocchiclient.'),
|
|
||||||
cfg.StrOpt('endpoint_type',
|
|
||||||
default='internalURL',
|
|
||||||
help='Type of endpoint to use in gnocchi client.'
|
|
||||||
'Supported values: internalURL, publicURL, adminURL'
|
|
||||||
'The default is internalURL.'),
|
|
||||||
cfg.IntOpt('query_max_retries',
|
|
||||||
default=10,
|
|
||||||
help='How many times Watcher is trying to query again'),
|
|
||||||
cfg.IntOpt('query_timeout',
|
|
||||||
default=1,
|
|
||||||
help='How many seconds Watcher should wait to do query again')]
|
|
||||||
|
|
||||||
|
|
||||||
def register_opts(conf):
|
|
||||||
conf.register_group(gnocchi_client)
|
|
||||||
conf.register_opts(GNOCCHI_CLIENT_OPTS, group=gnocchi_client)
|
|
||||||
|
|
||||||
|
|
||||||
def list_opts():
|
|
||||||
return [('gnocchi_client', GNOCCHI_CLIENT_OPTS)]
|
|
||||||
@@ -24,12 +24,7 @@ monasca_client = cfg.OptGroup(name='monasca_client',
|
|||||||
MONASCA_CLIENT_OPTS = [
|
MONASCA_CLIENT_OPTS = [
|
||||||
cfg.StrOpt('api_version',
|
cfg.StrOpt('api_version',
|
||||||
default='2_0',
|
default='2_0',
|
||||||
help='Version of Monasca API to use in monascaclient.'),
|
help='Version of Monasca API to use in monascaclient.')]
|
||||||
cfg.StrOpt('interface',
|
|
||||||
default='internal',
|
|
||||||
help='Type of interface used for monasca endpoint.'
|
|
||||||
'Supported values: internal, public, admin'
|
|
||||||
'The default is internal.')]
|
|
||||||
|
|
||||||
|
|
||||||
def register_opts(conf):
|
def register_opts(conf):
|
||||||
|
|||||||
@@ -24,12 +24,7 @@ neutron_client = cfg.OptGroup(name='neutron_client',
|
|||||||
NEUTRON_CLIENT_OPTS = [
|
NEUTRON_CLIENT_OPTS = [
|
||||||
cfg.StrOpt('api_version',
|
cfg.StrOpt('api_version',
|
||||||
default='2.0',
|
default='2.0',
|
||||||
help='Version of Neutron API to use in neutronclient.'),
|
help='Version of Neutron API to use in neutronclient.')]
|
||||||
cfg.StrOpt('endpoint_type',
|
|
||||||
default='internalURL',
|
|
||||||
help='Type of endpoint to use in neutronclient.'
|
|
||||||
'Supported values: internalURL, publicURL, adminURL'
|
|
||||||
'The default is internalURL.')]
|
|
||||||
|
|
||||||
|
|
||||||
def register_opts(conf):
|
def register_opts(conf):
|
||||||
|
|||||||
@@ -24,12 +24,7 @@ nova_client = cfg.OptGroup(name='nova_client',
|
|||||||
NOVA_CLIENT_OPTS = [
|
NOVA_CLIENT_OPTS = [
|
||||||
cfg.StrOpt('api_version',
|
cfg.StrOpt('api_version',
|
||||||
default='2',
|
default='2',
|
||||||
help='Version of Nova API to use in novaclient.'),
|
help='Version of Nova API to use in novaclient.')]
|
||||||
cfg.StrOpt('endpoint_type',
|
|
||||||
default='internalURL',
|
|
||||||
help='Type of endpoint to use in novaclient.'
|
|
||||||
'Supported values: internalURL, publicURL, adminURL'
|
|
||||||
'The default is internalURL.')]
|
|
||||||
|
|
||||||
|
|
||||||
def register_opts(conf):
|
def register_opts(conf):
|
||||||
|
|||||||
@@ -26,14 +26,13 @@ SERVICE_OPTS = [
|
|||||||
cfg.IntOpt('periodic_interval',
|
cfg.IntOpt('periodic_interval',
|
||||||
default=60,
|
default=60,
|
||||||
help=_('Seconds between running periodic tasks.')),
|
help=_('Seconds between running periodic tasks.')),
|
||||||
cfg.HostAddressOpt('host',
|
cfg.StrOpt('host',
|
||||||
default=socket.gethostname(),
|
default=socket.gethostname(),
|
||||||
help=_('Name of this node. This can be an opaque '
|
help=_('Name of this node. This can be an opaque identifier. '
|
||||||
'identifier. It is not necessarily a hostname, '
|
'It is not necessarily a hostname, FQDN, or IP address. '
|
||||||
'FQDN, or IP address. However, the node name '
|
'However, the node name must be valid within '
|
||||||
'must be valid within an AMQP key, and if using '
|
'an AMQP key, and if using ZeroMQ, a valid '
|
||||||
'ZeroMQ, a valid hostname, FQDN, or IP address.')
|
'hostname, FQDN, or IP address.')),
|
||||||
),
|
|
||||||
cfg.IntOpt('service_down_time',
|
cfg.IntOpt('service_down_time',
|
||||||
default=90,
|
default=90,
|
||||||
help=_('Maximum time since last check-in for up service.'))
|
help=_('Maximum time since last check-in for up service.'))
|
||||||
|
|||||||
@@ -1,92 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
# Copyright (c) 2017 Servionica
|
|
||||||
#
|
|
||||||
# Authors: Alexander Chadin <a.chadin@servionica.ru>
|
|
||||||
#
|
|
||||||
# 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 datetime import datetime
|
|
||||||
import time
|
|
||||||
|
|
||||||
from oslo_config import cfg
|
|
||||||
from oslo_log import log
|
|
||||||
|
|
||||||
from watcher.common import clients
|
|
||||||
from watcher.common import exception
|
|
||||||
|
|
||||||
CONF = cfg.CONF
|
|
||||||
LOG = log.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class GnocchiHelper(object):
|
|
||||||
|
|
||||||
def __init__(self, osc=None):
|
|
||||||
""":param osc: an OpenStackClients instance"""
|
|
||||||
self.osc = osc if osc else clients.OpenStackClients()
|
|
||||||
self.gnocchi = self.osc.gnocchi()
|
|
||||||
|
|
||||||
def query_retry(self, f, *args, **kwargs):
|
|
||||||
for i in range(CONF.gnocchi_client.query_max_retries):
|
|
||||||
try:
|
|
||||||
return f(*args, **kwargs)
|
|
||||||
except Exception as e:
|
|
||||||
LOG.exception(e)
|
|
||||||
time.sleep(CONF.gnocchi_client.query_timeout)
|
|
||||||
raise
|
|
||||||
|
|
||||||
def statistic_aggregation(self,
|
|
||||||
resource_id,
|
|
||||||
metric,
|
|
||||||
granularity,
|
|
||||||
start_time=None,
|
|
||||||
stop_time=None,
|
|
||||||
aggregation='mean'):
|
|
||||||
"""Representing a statistic aggregate by operators
|
|
||||||
|
|
||||||
:param metric: metric name of which we want the statistics
|
|
||||||
:param resource_id: id of resource to list statistics for
|
|
||||||
:param start_time: Start datetime from which metrics will be used
|
|
||||||
:param stop_time: End datetime from which metrics will be used
|
|
||||||
:param granularity: frequency of marking metric point, in seconds
|
|
||||||
:param aggregation: Should be chosen in accrodance with policy
|
|
||||||
aggregations
|
|
||||||
:return: value of aggregated metric
|
|
||||||
"""
|
|
||||||
|
|
||||||
if start_time is not None and not isinstance(start_time, datetime):
|
|
||||||
raise exception.InvalidParameter(parameter='start_time',
|
|
||||||
parameter_type=datetime)
|
|
||||||
|
|
||||||
if stop_time is not None and not isinstance(stop_time, datetime):
|
|
||||||
raise exception.InvalidParameter(parameter='stop_time',
|
|
||||||
parameter_type=datetime)
|
|
||||||
|
|
||||||
raw_kwargs = dict(
|
|
||||||
metric=metric,
|
|
||||||
start=start_time,
|
|
||||||
stop=stop_time,
|
|
||||||
resource_id=resource_id,
|
|
||||||
granularity=granularity,
|
|
||||||
aggregation=aggregation,
|
|
||||||
)
|
|
||||||
|
|
||||||
kwargs = {k: v for k, v in raw_kwargs.items() if k and v}
|
|
||||||
|
|
||||||
statistics = self.query_retry(
|
|
||||||
f=self.gnocchi.metric.get_measures, **kwargs)
|
|
||||||
|
|
||||||
if statistics:
|
|
||||||
# return value of latest measure
|
|
||||||
# measure has structure [time, granularity, value]
|
|
||||||
return statistics[-1][2]
|
|
||||||
@@ -27,7 +27,7 @@ from oslo_utils import strutils
|
|||||||
import prettytable as ptable
|
import prettytable as ptable
|
||||||
from six.moves import input
|
from six.moves import input
|
||||||
|
|
||||||
from watcher._i18n import _
|
from watcher._i18n import _, _LI
|
||||||
from watcher._i18n import lazy_translation_enabled
|
from watcher._i18n import lazy_translation_enabled
|
||||||
from watcher.common import context
|
from watcher.common import context
|
||||||
from watcher.common import exception
|
from watcher.common import exception
|
||||||
@@ -231,7 +231,7 @@ class PurgeCommand(object):
|
|||||||
if action.action_plan_id not in action_plan_ids]
|
if action.action_plan_id not in action_plan_ids]
|
||||||
|
|
||||||
LOG.debug("Orphans found:\n%s", orphans)
|
LOG.debug("Orphans found:\n%s", orphans)
|
||||||
LOG.info("Orphans found:\n%s", orphans.get_count_table())
|
LOG.info(_LI("Orphans found:\n%s"), orphans.get_count_table())
|
||||||
|
|
||||||
return orphans
|
return orphans
|
||||||
|
|
||||||
@@ -403,13 +403,13 @@ class PurgeCommand(object):
|
|||||||
return to_be_deleted
|
return to_be_deleted
|
||||||
|
|
||||||
def do_delete(self):
|
def do_delete(self):
|
||||||
LOG.info("Deleting...")
|
LOG.info(_LI("Deleting..."))
|
||||||
# Reversed to avoid errors with foreign keys
|
# Reversed to avoid errors with foreign keys
|
||||||
for entry in reversed(list(self._objects_map)):
|
for entry in reversed(list(self._objects_map)):
|
||||||
entry.destroy()
|
entry.destroy()
|
||||||
|
|
||||||
def execute(self):
|
def execute(self):
|
||||||
LOG.info("Starting purge command")
|
LOG.info(_LI("Starting purge command"))
|
||||||
self._objects_map = self.find_objects_to_delete()
|
self._objects_map = self.find_objects_to_delete()
|
||||||
|
|
||||||
if (self.max_number is not None and
|
if (self.max_number is not None and
|
||||||
@@ -424,15 +424,15 @@ class PurgeCommand(object):
|
|||||||
if not self.dry_run and self.confirmation_prompt():
|
if not self.dry_run and self.confirmation_prompt():
|
||||||
self.do_delete()
|
self.do_delete()
|
||||||
print(_("Purge results summary%s:") % _orphans_note)
|
print(_("Purge results summary%s:") % _orphans_note)
|
||||||
LOG.info("Purge results summary%s:", _orphans_note)
|
LOG.info(_LI("Purge results summary%s:"), _orphans_note)
|
||||||
else:
|
else:
|
||||||
LOG.debug(self._objects_map)
|
LOG.debug(self._objects_map)
|
||||||
print(_("Here below is a table containing the objects "
|
print(_("Here below is a table containing the objects "
|
||||||
"that can be purged%s:") % _orphans_note)
|
"that can be purged%s:") % _orphans_note)
|
||||||
|
|
||||||
LOG.info("\n%s", self._objects_map.get_count_table())
|
LOG.info(_LI("\n%s"), self._objects_map.get_count_table())
|
||||||
print(self._objects_map.get_count_table())
|
print(self._objects_map.get_count_table())
|
||||||
LOG.info("Purge process completed")
|
LOG.info(_LI("Purge process completed"))
|
||||||
|
|
||||||
|
|
||||||
def purge(age_in_days, max_number, goal, exclude_orphans, dry_run):
|
def purge(age_in_days, max_number, goal, exclude_orphans, dry_run):
|
||||||
@@ -457,11 +457,11 @@ def purge(age_in_days, max_number, goal, exclude_orphans, dry_run):
|
|||||||
if max_number and max_number < 0:
|
if max_number and max_number < 0:
|
||||||
raise exception.NegativeLimitError
|
raise exception.NegativeLimitError
|
||||||
|
|
||||||
LOG.info("[options] age_in_days = %s", age_in_days)
|
LOG.info(_LI("[options] age_in_days = %s"), age_in_days)
|
||||||
LOG.info("[options] max_number = %s", max_number)
|
LOG.info(_LI("[options] max_number = %s"), max_number)
|
||||||
LOG.info("[options] goal = %s", goal)
|
LOG.info(_LI("[options] goal = %s"), goal)
|
||||||
LOG.info("[options] exclude_orphans = %s", exclude_orphans)
|
LOG.info(_LI("[options] exclude_orphans = %s"), exclude_orphans)
|
||||||
LOG.info("[options] dry_run = %s", dry_run)
|
LOG.info(_LI("[options] dry_run = %s"), dry_run)
|
||||||
|
|
||||||
uuid = PurgeCommand.get_goal_uuid(goal)
|
uuid = PurgeCommand.get_goal_uuid(goal)
|
||||||
|
|
||||||
|
|||||||
@@ -102,24 +102,23 @@ class AuditHandler(BaseAuditHandler):
|
|||||||
audit.state = state
|
audit.state = state
|
||||||
audit.save()
|
audit.save()
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def check_ongoing_action_plans(request_context):
|
|
||||||
a_plan_filters = {'state': objects.action_plan.State.ONGOING}
|
|
||||||
ongoing_action_plans = objects.ActionPlan.list(
|
|
||||||
request_context, filters=a_plan_filters)
|
|
||||||
if ongoing_action_plans:
|
|
||||||
raise exception.ActionPlanIsOngoing(
|
|
||||||
action_plan=ongoing_action_plans[0].uuid)
|
|
||||||
|
|
||||||
def pre_execute(self, audit, request_context):
|
def pre_execute(self, audit, request_context):
|
||||||
LOG.debug("Trigger audit %s", audit.uuid)
|
LOG.debug("Trigger audit %s", audit.uuid)
|
||||||
self.check_ongoing_action_plans(request_context)
|
|
||||||
# change state of the audit to ONGOING
|
# change state of the audit to ONGOING
|
||||||
self.update_audit_state(audit, objects.audit.State.ONGOING)
|
self.update_audit_state(audit, objects.audit.State.ONGOING)
|
||||||
|
|
||||||
def post_execute(self, audit, solution, request_context):
|
def post_execute(self, audit, solution, request_context):
|
||||||
action_plan = self.do_schedule(request_context, audit, solution)
|
action_plan = self.do_schedule(request_context, audit, solution)
|
||||||
if audit.auto_trigger:
|
a_plan_filters = {'state': objects.action_plan.State.ONGOING}
|
||||||
|
ongoing_action_plans = objects.ActionPlan.list(
|
||||||
|
request_context, filters=a_plan_filters)
|
||||||
|
if ongoing_action_plans:
|
||||||
|
action_plan.state = objects.action_plan.State.SUPERSEDED
|
||||||
|
action_plan.save()
|
||||||
|
raise exception.ActionPlanIsOngoing(
|
||||||
|
action_plan=ongoing_action_plans[0].uuid,
|
||||||
|
new_action_plan=action_plan.uuid)
|
||||||
|
elif audit.auto_trigger:
|
||||||
applier_client = rpcapi.ApplierAPI()
|
applier_client = rpcapi.ApplierAPI()
|
||||||
applier_client.launch_action_plan(request_context,
|
applier_client.launch_action_plan(request_context,
|
||||||
action_plan.uuid)
|
action_plan.uuid)
|
||||||
|
|||||||
@@ -49,7 +49,9 @@ class ContinuousAuditHandler(base.AuditHandler):
|
|||||||
def _is_audit_inactive(self, audit):
|
def _is_audit_inactive(self, audit):
|
||||||
audit = objects.Audit.get_by_uuid(
|
audit = objects.Audit.get_by_uuid(
|
||||||
self.context_show_deleted, audit.uuid)
|
self.context_show_deleted, audit.uuid)
|
||||||
if objects.audit.AuditStateTransitionManager().is_inactive(audit):
|
if audit.state in (objects.audit.State.CANCELLED,
|
||||||
|
objects.audit.State.DELETED,
|
||||||
|
objects.audit.State.FAILED):
|
||||||
# if audit isn't in active states, audit's job must be removed to
|
# if audit isn't in active states, audit's job must be removed to
|
||||||
# prevent using of inactive audit in future.
|
# prevent using of inactive audit in future.
|
||||||
job_to_delete = [job for job in self.jobs
|
job_to_delete = [job for job in self.jobs
|
||||||
@@ -70,7 +72,7 @@ class ContinuousAuditHandler(base.AuditHandler):
|
|||||||
a_plan_filters = {'audit_uuid': audit.uuid,
|
a_plan_filters = {'audit_uuid': audit.uuid,
|
||||||
'state': objects.action_plan.State.RECOMMENDED}
|
'state': objects.action_plan.State.RECOMMENDED}
|
||||||
action_plans = objects.ActionPlan.list(
|
action_plans = objects.ActionPlan.list(
|
||||||
request_context, filters=a_plan_filters, eager=True)
|
request_context, filters=a_plan_filters)
|
||||||
for plan in action_plans:
|
for plan in action_plans:
|
||||||
plan.state = objects.action_plan.State.CANCELLED
|
plan.state = objects.action_plan.State.CANCELLED
|
||||||
plan.save()
|
plan.save()
|
||||||
|
|||||||
@@ -235,8 +235,7 @@ class ModelBuilder(object):
|
|||||||
"disk": flavor.disk,
|
"disk": flavor.disk,
|
||||||
"disk_capacity": flavor.disk,
|
"disk_capacity": flavor.disk,
|
||||||
"vcpus": flavor.vcpus,
|
"vcpus": flavor.vcpus,
|
||||||
"state": getattr(instance, "OS-EXT-STS:vm_state"),
|
"state": getattr(instance, "OS-EXT-STS:vm_state")}
|
||||||
"metadata": instance.metadata}
|
|
||||||
|
|
||||||
# node_attributes = dict()
|
# node_attributes = dict()
|
||||||
# node_attributes["layer"] = "virtual"
|
# node_attributes["layer"] = "virtual"
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ class Instance(compute_resource.ComputeResource):
|
|||||||
"disk": wfields.IntegerField(),
|
"disk": wfields.IntegerField(),
|
||||||
"disk_capacity": wfields.NonNegativeIntegerField(),
|
"disk_capacity": wfields.NonNegativeIntegerField(),
|
||||||
"vcpus": wfields.NonNegativeIntegerField(),
|
"vcpus": wfields.NonNegativeIntegerField(),
|
||||||
"metadata": wfields.JsonField(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def accept(self, visitor):
|
def accept(self, visitor):
|
||||||
|
|||||||
@@ -235,10 +235,3 @@ class ModelRoot(nx.DiGraph, base.Model):
|
|||||||
model.add_instance(instance)
|
model.add_instance(instance)
|
||||||
|
|
||||||
return model
|
return model
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def is_isomorphic(cls, G1, G2):
|
|
||||||
def node_match(node1, node2):
|
|
||||||
return node1.as_dict() == node2.as_dict()
|
|
||||||
return nx.algorithms.isomorphism.isomorph.is_isomorphic(
|
|
||||||
G1, G2, node_match=node_match)
|
|
||||||
|
|||||||
@@ -17,6 +17,8 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
|
|
||||||
|
from watcher._i18n import _LI, _LW
|
||||||
from watcher.common import exception
|
from watcher.common import exception
|
||||||
from watcher.common import nova_helper
|
from watcher.common import nova_helper
|
||||||
from watcher.decision_engine.model import element
|
from watcher.decision_engine.model import element
|
||||||
@@ -43,8 +45,8 @@ class NovaNotification(base.NotificationEndpoint):
|
|||||||
if node_uuid:
|
if node_uuid:
|
||||||
self.get_or_create_node(node_uuid)
|
self.get_or_create_node(node_uuid)
|
||||||
except exception.ComputeNodeNotFound:
|
except exception.ComputeNodeNotFound:
|
||||||
LOG.warning("Could not find compute node %(node)s for "
|
LOG.warning(_LW("Could not find compute node %(node)s for "
|
||||||
"instance %(instance)s",
|
"instance %(instance)s"),
|
||||||
dict(node=node_uuid, instance=instance_uuid))
|
dict(node=node_uuid, instance=instance_uuid))
|
||||||
try:
|
try:
|
||||||
instance = self.cluster_data_model.get_instance_by_uuid(
|
instance = self.cluster_data_model.get_instance_by_uuid(
|
||||||
@@ -65,7 +67,6 @@ class NovaNotification(base.NotificationEndpoint):
|
|||||||
memory_mb = instance_flavor_data['memory_mb']
|
memory_mb = instance_flavor_data['memory_mb']
|
||||||
num_cores = instance_flavor_data['vcpus']
|
num_cores = instance_flavor_data['vcpus']
|
||||||
disk_gb = instance_flavor_data['root_gb']
|
disk_gb = instance_flavor_data['root_gb']
|
||||||
instance_metadata = data['nova_object.data']['metadata']
|
|
||||||
|
|
||||||
instance.update({
|
instance.update({
|
||||||
'state': instance_data['state'],
|
'state': instance_data['state'],
|
||||||
@@ -75,7 +76,6 @@ class NovaNotification(base.NotificationEndpoint):
|
|||||||
'vcpus': num_cores,
|
'vcpus': num_cores,
|
||||||
'disk': disk_gb,
|
'disk': disk_gb,
|
||||||
'disk_capacity': disk_gb,
|
'disk_capacity': disk_gb,
|
||||||
'metadata': instance_metadata,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -91,7 +91,6 @@ class NovaNotification(base.NotificationEndpoint):
|
|||||||
memory_mb = data['memory_mb']
|
memory_mb = data['memory_mb']
|
||||||
num_cores = data['vcpus']
|
num_cores = data['vcpus']
|
||||||
disk_gb = data['root_gb']
|
disk_gb = data['root_gb']
|
||||||
instance_metadata = data['metadata']
|
|
||||||
|
|
||||||
instance.update({
|
instance.update({
|
||||||
'state': data['state'],
|
'state': data['state'],
|
||||||
@@ -101,7 +100,6 @@ class NovaNotification(base.NotificationEndpoint):
|
|||||||
'vcpus': num_cores,
|
'vcpus': num_cores,
|
||||||
'disk': disk_gb,
|
'disk': disk_gb,
|
||||||
'disk_capacity': disk_gb,
|
'disk_capacity': disk_gb,
|
||||||
'metadata': instance_metadata,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -200,18 +198,18 @@ class NovaNotification(base.NotificationEndpoint):
|
|||||||
try:
|
try:
|
||||||
self.cluster_data_model.delete_instance(instance, node)
|
self.cluster_data_model.delete_instance(instance, node)
|
||||||
except Exception:
|
except Exception:
|
||||||
LOG.info("Instance %s already deleted", instance.uuid)
|
LOG.info(_LI("Instance %s already deleted"), instance.uuid)
|
||||||
|
|
||||||
|
|
||||||
class VersionedNotificationEndpoint(NovaNotification):
|
class VersionnedNotificationEndpoint(NovaNotification):
|
||||||
publisher_id_regex = r'^nova-compute.*'
|
publisher_id_regex = r'^nova-compute.*'
|
||||||
|
|
||||||
|
|
||||||
class UnversionedNotificationEndpoint(NovaNotification):
|
class UnversionnedNotificationEndpoint(NovaNotification):
|
||||||
publisher_id_regex = r'^compute.*'
|
publisher_id_regex = r'^compute.*'
|
||||||
|
|
||||||
|
|
||||||
class ServiceUpdated(VersionedNotificationEndpoint):
|
class ServiceUpdated(VersionnedNotificationEndpoint):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def filter_rule(self):
|
def filter_rule(self):
|
||||||
@@ -222,10 +220,8 @@ class ServiceUpdated(VersionedNotificationEndpoint):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def info(self, ctxt, publisher_id, event_type, payload, metadata):
|
def info(self, ctxt, publisher_id, event_type, payload, metadata):
|
||||||
ctxt.request_id = metadata['message_id']
|
LOG.info(_LI("Event '%(event)s' received from %(publisher)s "
|
||||||
ctxt.project_domain = event_type
|
"with metadata %(metadata)s") %
|
||||||
LOG.info("Event '%(event)s' received from %(publisher)s "
|
|
||||||
"with metadata %(metadata)s" %
|
|
||||||
dict(event=event_type,
|
dict(event=event_type,
|
||||||
publisher=publisher_id,
|
publisher=publisher_id,
|
||||||
metadata=metadata))
|
metadata=metadata))
|
||||||
@@ -239,7 +235,7 @@ class ServiceUpdated(VersionedNotificationEndpoint):
|
|||||||
LOG.exception(exc)
|
LOG.exception(exc)
|
||||||
|
|
||||||
|
|
||||||
class InstanceCreated(VersionedNotificationEndpoint):
|
class InstanceCreated(VersionnedNotificationEndpoint):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def filter_rule(self):
|
def filter_rule(self):
|
||||||
@@ -266,15 +262,14 @@ class InstanceCreated(VersionedNotificationEndpoint):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def info(self, ctxt, publisher_id, event_type, payload, metadata):
|
def info(self, ctxt, publisher_id, event_type, payload, metadata):
|
||||||
ctxt.request_id = metadata['message_id']
|
LOG.info(_LI("Event '%(event)s' received from %(publisher)s "
|
||||||
ctxt.project_domain = event_type
|
"with metadata %(metadata)s") %
|
||||||
LOG.info("Event '%(event)s' received from %(publisher)s "
|
|
||||||
"with metadata %(metadata)s" %
|
|
||||||
dict(event=event_type,
|
dict(event=event_type,
|
||||||
publisher=publisher_id,
|
publisher=publisher_id,
|
||||||
metadata=metadata))
|
metadata=metadata))
|
||||||
LOG.debug(payload)
|
LOG.debug(payload)
|
||||||
instance_data = payload['nova_object.data']
|
instance_data = payload['nova_object.data']
|
||||||
|
|
||||||
instance_uuid = instance_data['uuid']
|
instance_uuid = instance_data['uuid']
|
||||||
node_uuid = instance_data.get('host')
|
node_uuid = instance_data.get('host')
|
||||||
instance = self.get_or_create_instance(instance_uuid, node_uuid)
|
instance = self.get_or_create_instance(instance_uuid, node_uuid)
|
||||||
@@ -282,7 +277,7 @@ class InstanceCreated(VersionedNotificationEndpoint):
|
|||||||
self.update_instance(instance, payload)
|
self.update_instance(instance, payload)
|
||||||
|
|
||||||
|
|
||||||
class InstanceUpdated(VersionedNotificationEndpoint):
|
class InstanceUpdated(VersionnedNotificationEndpoint):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _match_not_new_instance_state(data):
|
def _match_not_new_instance_state(data):
|
||||||
@@ -301,10 +296,8 @@ class InstanceUpdated(VersionedNotificationEndpoint):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def info(self, ctxt, publisher_id, event_type, payload, metadata):
|
def info(self, ctxt, publisher_id, event_type, payload, metadata):
|
||||||
ctxt.request_id = metadata['message_id']
|
LOG.info(_LI("Event '%(event)s' received from %(publisher)s "
|
||||||
ctxt.project_domain = event_type
|
"with metadata %(metadata)s") %
|
||||||
LOG.info("Event '%(event)s' received from %(publisher)s "
|
|
||||||
"with metadata %(metadata)s" %
|
|
||||||
dict(event=event_type,
|
dict(event=event_type,
|
||||||
publisher=publisher_id,
|
publisher=publisher_id,
|
||||||
metadata=metadata))
|
metadata=metadata))
|
||||||
@@ -317,7 +310,7 @@ class InstanceUpdated(VersionedNotificationEndpoint):
|
|||||||
self.update_instance(instance, payload)
|
self.update_instance(instance, payload)
|
||||||
|
|
||||||
|
|
||||||
class InstanceDeletedEnd(VersionedNotificationEndpoint):
|
class InstanceDeletedEnd(VersionnedNotificationEndpoint):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def filter_rule(self):
|
def filter_rule(self):
|
||||||
@@ -328,10 +321,8 @@ class InstanceDeletedEnd(VersionedNotificationEndpoint):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def info(self, ctxt, publisher_id, event_type, payload, metadata):
|
def info(self, ctxt, publisher_id, event_type, payload, metadata):
|
||||||
ctxt.request_id = metadata['message_id']
|
LOG.info(_LI("Event '%(event)s' received from %(publisher)s "
|
||||||
ctxt.project_domain = event_type
|
"with metadata %(metadata)s") %
|
||||||
LOG.info("Event '%(event)s' received from %(publisher)s "
|
|
||||||
"with metadata %(metadata)s" %
|
|
||||||
dict(event=event_type,
|
dict(event=event_type,
|
||||||
publisher=publisher_id,
|
publisher=publisher_id,
|
||||||
metadata=metadata))
|
metadata=metadata))
|
||||||
@@ -352,7 +343,7 @@ class InstanceDeletedEnd(VersionedNotificationEndpoint):
|
|||||||
self.delete_instance(instance, node)
|
self.delete_instance(instance, node)
|
||||||
|
|
||||||
|
|
||||||
class LegacyInstanceUpdated(UnversionedNotificationEndpoint):
|
class LegacyInstanceUpdated(UnversionnedNotificationEndpoint):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def filter_rule(self):
|
def filter_rule(self):
|
||||||
@@ -363,10 +354,8 @@ class LegacyInstanceUpdated(UnversionedNotificationEndpoint):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def info(self, ctxt, publisher_id, event_type, payload, metadata):
|
def info(self, ctxt, publisher_id, event_type, payload, metadata):
|
||||||
ctxt.request_id = metadata['message_id']
|
LOG.info(_LI("Event '%(event)s' received from %(publisher)s "
|
||||||
ctxt.project_domain = event_type
|
"with metadata %(metadata)s") %
|
||||||
LOG.info("Event '%(event)s' received from %(publisher)s "
|
|
||||||
"with metadata %(metadata)s" %
|
|
||||||
dict(event=event_type,
|
dict(event=event_type,
|
||||||
publisher=publisher_id,
|
publisher=publisher_id,
|
||||||
metadata=metadata))
|
metadata=metadata))
|
||||||
@@ -379,7 +368,7 @@ class LegacyInstanceUpdated(UnversionedNotificationEndpoint):
|
|||||||
self.legacy_update_instance(instance, payload)
|
self.legacy_update_instance(instance, payload)
|
||||||
|
|
||||||
|
|
||||||
class LegacyInstanceCreatedEnd(UnversionedNotificationEndpoint):
|
class LegacyInstanceCreatedEnd(UnversionnedNotificationEndpoint):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def filter_rule(self):
|
def filter_rule(self):
|
||||||
@@ -390,10 +379,8 @@ class LegacyInstanceCreatedEnd(UnversionedNotificationEndpoint):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def info(self, ctxt, publisher_id, event_type, payload, metadata):
|
def info(self, ctxt, publisher_id, event_type, payload, metadata):
|
||||||
ctxt.request_id = metadata['message_id']
|
LOG.info(_LI("Event '%(event)s' received from %(publisher)s "
|
||||||
ctxt.project_domain = event_type
|
"with metadata %(metadata)s") %
|
||||||
LOG.info("Event '%(event)s' received from %(publisher)s "
|
|
||||||
"with metadata %(metadata)s" %
|
|
||||||
dict(event=event_type,
|
dict(event=event_type,
|
||||||
publisher=publisher_id,
|
publisher=publisher_id,
|
||||||
metadata=metadata))
|
metadata=metadata))
|
||||||
@@ -406,7 +393,7 @@ class LegacyInstanceCreatedEnd(UnversionedNotificationEndpoint):
|
|||||||
self.legacy_update_instance(instance, payload)
|
self.legacy_update_instance(instance, payload)
|
||||||
|
|
||||||
|
|
||||||
class LegacyInstanceDeletedEnd(UnversionedNotificationEndpoint):
|
class LegacyInstanceDeletedEnd(UnversionnedNotificationEndpoint):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def filter_rule(self):
|
def filter_rule(self):
|
||||||
@@ -417,10 +404,8 @@ class LegacyInstanceDeletedEnd(UnversionedNotificationEndpoint):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def info(self, ctxt, publisher_id, event_type, payload, metadata):
|
def info(self, ctxt, publisher_id, event_type, payload, metadata):
|
||||||
ctxt.request_id = metadata['message_id']
|
LOG.info(_LI("Event '%(event)s' received from %(publisher)s "
|
||||||
ctxt.project_domain = event_type
|
"with metadata %(metadata)s") %
|
||||||
LOG.info("Event '%(event)s' received from %(publisher)s "
|
|
||||||
"with metadata %(metadata)s" %
|
|
||||||
dict(event=event_type,
|
dict(event=event_type,
|
||||||
publisher=publisher_id,
|
publisher=publisher_id,
|
||||||
metadata=metadata))
|
metadata=metadata))
|
||||||
@@ -439,7 +424,7 @@ class LegacyInstanceDeletedEnd(UnversionedNotificationEndpoint):
|
|||||||
self.delete_instance(instance, node)
|
self.delete_instance(instance, node)
|
||||||
|
|
||||||
|
|
||||||
class LegacyLiveMigratedEnd(UnversionedNotificationEndpoint):
|
class LegacyLiveMigratedEnd(UnversionnedNotificationEndpoint):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def filter_rule(self):
|
def filter_rule(self):
|
||||||
@@ -450,10 +435,8 @@ class LegacyLiveMigratedEnd(UnversionedNotificationEndpoint):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def info(self, ctxt, publisher_id, event_type, payload, metadata):
|
def info(self, ctxt, publisher_id, event_type, payload, metadata):
|
||||||
ctxt.request_id = metadata['message_id']
|
LOG.info(_LI("Event '%(event)s' received from %(publisher)s "
|
||||||
ctxt.project_domain = event_type
|
"with metadata %(metadata)s") %
|
||||||
LOG.info("Event '%(event)s' received from %(publisher)s "
|
|
||||||
"with metadata %(metadata)s" %
|
|
||||||
dict(event=event_type,
|
dict(event=event_type,
|
||||||
publisher=publisher_id,
|
publisher=publisher_id,
|
||||||
metadata=metadata))
|
metadata=metadata))
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ from oslo_config import cfg
|
|||||||
from oslo_config import types
|
from oslo_config import types
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
|
|
||||||
|
from watcher._i18n import _LW
|
||||||
from watcher.common import utils
|
from watcher.common import utils
|
||||||
from watcher.decision_engine.planner import base
|
from watcher.decision_engine.planner import base
|
||||||
from watcher import objects
|
from watcher import objects
|
||||||
@@ -83,6 +84,18 @@ class WeightPlanner(base.BasePlanner):
|
|||||||
default=cls.parallelization),
|
default=cls.parallelization),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def format_action(action_plan_id, action_type,
|
||||||
|
input_parameters=None, parents=()):
|
||||||
|
return {
|
||||||
|
'uuid': utils.generate_uuid(),
|
||||||
|
'action_plan_id': int(action_plan_id),
|
||||||
|
'action_type': action_type,
|
||||||
|
'input_parameters': input_parameters,
|
||||||
|
'state': objects.action.State.PENDING,
|
||||||
|
'parents': parents or None,
|
||||||
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def chunkify(lst, n):
|
def chunkify(lst, n):
|
||||||
"""Yield successive n-sized chunks from lst."""
|
"""Yield successive n-sized chunks from lst."""
|
||||||
@@ -151,11 +164,11 @@ class WeightPlanner(base.BasePlanner):
|
|||||||
context, action_plan.id, solution.efficacy_indicators)
|
context, action_plan.id, solution.efficacy_indicators)
|
||||||
|
|
||||||
if len(action_graph.nodes()) == 0:
|
if len(action_graph.nodes()) == 0:
|
||||||
LOG.warning("The action plan is empty")
|
LOG.warning(_LW("The action plan is empty"))
|
||||||
action_plan.state = objects.action_plan.State.SUCCEEDED
|
action_plan.state = objects.action_plan.State.SUCCEEDED
|
||||||
action_plan.save()
|
action_plan.save()
|
||||||
|
|
||||||
self.create_scheduled_actions(action_graph)
|
self.create_scheduled_actions(action_plan, action_graph)
|
||||||
return action_plan
|
return action_plan
|
||||||
|
|
||||||
def get_sorted_actions_by_weight(self, context, action_plan, solution):
|
def get_sorted_actions_by_weight(self, context, action_plan, solution):
|
||||||
@@ -174,7 +187,7 @@ class WeightPlanner(base.BasePlanner):
|
|||||||
|
|
||||||
return reversed(sorted(weighted_actions.items(), key=lambda x: x[0]))
|
return reversed(sorted(weighted_actions.items(), key=lambda x: x[0]))
|
||||||
|
|
||||||
def create_scheduled_actions(self, graph):
|
def create_scheduled_actions(self, action_plan, graph):
|
||||||
for action in graph.nodes():
|
for action in graph.nodes():
|
||||||
LOG.debug("Creating the %s in the Watcher database",
|
LOG.debug("Creating the %s in the Watcher database",
|
||||||
action.action_type)
|
action.action_type)
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ from oslo_config import cfg
|
|||||||
from oslo_config import types
|
from oslo_config import types
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
|
|
||||||
|
from watcher._i18n import _LW
|
||||||
from watcher.common import clients
|
from watcher.common import clients
|
||||||
from watcher.common import exception
|
from watcher.common import exception
|
||||||
from watcher.common import nova_helper
|
from watcher.common import nova_helper
|
||||||
@@ -116,7 +117,7 @@ class WorkloadStabilizationPlanner(base.BasePlanner):
|
|||||||
scheduled = sorted(to_schedule, key=lambda weight: (weight[0]),
|
scheduled = sorted(to_schedule, key=lambda weight: (weight[0]),
|
||||||
reverse=True)
|
reverse=True)
|
||||||
if len(scheduled) == 0:
|
if len(scheduled) == 0:
|
||||||
LOG.warning("The action plan is empty")
|
LOG.warning(_LW("The action plan is empty"))
|
||||||
action_plan.state = objects.action_plan.State.SUCCEEDED
|
action_plan.state = objects.action_plan.State.SUCCEEDED
|
||||||
action_plan.save()
|
action_plan.save()
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ class DecisionEngineAPI(service.Service):
|
|||||||
if not utils.is_uuid_like(audit_uuid):
|
if not utils.is_uuid_like(audit_uuid):
|
||||||
raise exception.InvalidUuidOrName(name=audit_uuid)
|
raise exception.InvalidUuidOrName(name=audit_uuid)
|
||||||
|
|
||||||
self.conductor_client.cast(
|
return self.conductor_client.call(
|
||||||
context, 'trigger_audit', audit_uuid=audit_uuid)
|
context, 'trigger_audit', audit_uuid=audit_uuid)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -19,17 +19,12 @@ import datetime
|
|||||||
import eventlet
|
import eventlet
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
|
|
||||||
from watcher.common import context
|
|
||||||
from watcher.common import exception
|
from watcher.common import exception
|
||||||
from watcher.common import scheduling
|
from watcher.common import scheduling
|
||||||
|
|
||||||
from watcher.decision_engine.model.collector import manager
|
from watcher.decision_engine.model.collector import manager
|
||||||
from watcher import objects
|
|
||||||
|
|
||||||
from watcher import conf
|
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
CONF = conf.CONF
|
|
||||||
|
|
||||||
|
|
||||||
class DecisionEngineSchedulingService(scheduling.BackgroundSchedulerService):
|
class DecisionEngineSchedulingService(scheduling.BackgroundSchedulerService):
|
||||||
@@ -78,20 +73,9 @@ class DecisionEngineSchedulingService(scheduling.BackgroundSchedulerService):
|
|||||||
|
|
||||||
return _sync
|
return _sync
|
||||||
|
|
||||||
def add_checkstate_job(self):
|
|
||||||
# 30 minutes interval
|
|
||||||
interval = CONF.watcher_decision_engine.check_periodic_interval
|
|
||||||
ap_manager = objects.action_plan.StateManager()
|
|
||||||
if CONF.watcher_decision_engine.action_plan_expiry != 0:
|
|
||||||
self.add_job(ap_manager.check_expired, 'interval',
|
|
||||||
args=[context.make_context()],
|
|
||||||
seconds=interval,
|
|
||||||
next_run_time=datetime.datetime.now())
|
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
"""Start service."""
|
"""Start service."""
|
||||||
self.add_sync_jobs()
|
self.add_sync_jobs()
|
||||||
self.add_checkstate_job()
|
|
||||||
super(DecisionEngineSchedulingService, self).start()
|
super(DecisionEngineSchedulingService, self).start()
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
|
|
||||||
|
from watcher._i18n import _LW
|
||||||
from watcher.common import exception
|
from watcher.common import exception
|
||||||
from watcher.common import nova_helper
|
from watcher.common import nova_helper
|
||||||
from watcher.decision_engine.scope import base
|
from watcher.decision_engine.scope import base
|
||||||
@@ -169,9 +170,9 @@ class DefaultScope(base.BaseScope):
|
|||||||
node_name = cluster_model.get_node_by_instance_uuid(
|
node_name = cluster_model.get_node_by_instance_uuid(
|
||||||
instance_uuid).uuid
|
instance_uuid).uuid
|
||||||
except exception.ComputeResourceNotFound:
|
except exception.ComputeResourceNotFound:
|
||||||
LOG.warning("The following instance %s cannot be found. "
|
LOG.warning(_LW("The following instance %s cannot be found. "
|
||||||
"It might be deleted from CDM along with node"
|
"It might be deleted from CDM along with node"
|
||||||
" instance was hosted on.",
|
" instance was hosted on."),
|
||||||
instance_uuid)
|
instance_uuid)
|
||||||
continue
|
continue
|
||||||
self.remove_instance(
|
self.remove_instance(
|
||||||
|
|||||||
@@ -39,8 +39,6 @@ which are dynamically loaded by Watcher at launch time.
|
|||||||
import abc
|
import abc
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from oslo_utils import strutils
|
|
||||||
|
|
||||||
from watcher.common import clients
|
from watcher.common import clients
|
||||||
from watcher.common import context
|
from watcher.common import context
|
||||||
from watcher.common import exception
|
from watcher.common import exception
|
||||||
@@ -266,22 +264,6 @@ class BaseStrategy(loadable.Loadable):
|
|||||||
def state_collector(self, s):
|
def state_collector(self, s):
|
||||||
self._cluster_state_collector = s
|
self._cluster_state_collector = s
|
||||||
|
|
||||||
def filter_instances_by_audit_tag(self, instances):
|
|
||||||
if not self.config.check_optimize_metadata:
|
|
||||||
return instances
|
|
||||||
instances_to_migrate = []
|
|
||||||
for instance in instances:
|
|
||||||
optimize = True
|
|
||||||
if instance.metadata:
|
|
||||||
try:
|
|
||||||
optimize = strutils.bool_from_string(
|
|
||||||
instance.metadata.get('optimize'))
|
|
||||||
except ValueError:
|
|
||||||
optimize = False
|
|
||||||
if optimize:
|
|
||||||
instances_to_migrate.append(instance)
|
|
||||||
return instances_to_migrate
|
|
||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(abc.ABCMeta)
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
class DummyBaseStrategy(BaseStrategy):
|
class DummyBaseStrategy(BaseStrategy):
|
||||||
|
|||||||
@@ -35,15 +35,12 @@ migration is possible on your OpenStack cluster.
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import datetime
|
|
||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
|
|
||||||
from watcher._i18n import _
|
from watcher._i18n import _, _LE, _LI, _LW
|
||||||
from watcher.common import exception
|
from watcher.common import exception
|
||||||
from watcher.datasource import ceilometer as ceil
|
from watcher.datasource import ceilometer as ceil
|
||||||
from watcher.datasource import gnocchi as gnoc
|
|
||||||
from watcher.datasource import monasca as mon
|
from watcher.datasource import monasca as mon
|
||||||
from watcher.decision_engine.model import element
|
from watcher.decision_engine.model import element
|
||||||
from watcher.decision_engine.strategy.strategies import base
|
from watcher.decision_engine.strategy.strategies import base
|
||||||
@@ -64,9 +61,6 @@ class BasicConsolidation(base.ServerConsolidationBaseStrategy):
|
|||||||
monasca=dict(
|
monasca=dict(
|
||||||
host_cpu_usage='cpu.percent',
|
host_cpu_usage='cpu.percent',
|
||||||
instance_cpu_usage='vm.cpu.utilization_perc'),
|
instance_cpu_usage='vm.cpu.utilization_perc'),
|
||||||
gnocchi=dict(
|
|
||||||
host_cpu_usage='compute.node.cpu.percent',
|
|
||||||
instance_cpu_usage='cpu_util'),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
MIGRATION = "migrate"
|
MIGRATION = "migrate"
|
||||||
@@ -93,7 +87,6 @@ class BasicConsolidation(base.ServerConsolidationBaseStrategy):
|
|||||||
|
|
||||||
self._ceilometer = None
|
self._ceilometer = None
|
||||||
self._monasca = None
|
self._monasca = None
|
||||||
self._gnocchi = None
|
|
||||||
|
|
||||||
# TODO(jed): improve threshold overbooking?
|
# TODO(jed): improve threshold overbooking?
|
||||||
self.threshold_mem = 1
|
self.threshold_mem = 1
|
||||||
@@ -112,10 +105,6 @@ class BasicConsolidation(base.ServerConsolidationBaseStrategy):
|
|||||||
def period(self):
|
def period(self):
|
||||||
return self.input_parameters.get('period', 7200)
|
return self.input_parameters.get('period', 7200)
|
||||||
|
|
||||||
@property
|
|
||||||
def granularity(self):
|
|
||||||
return self.input_parameters.get('granularity', 300)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_display_name(cls):
|
def get_display_name(cls):
|
||||||
return _("Basic offline consolidation")
|
return _("Basic offline consolidation")
|
||||||
@@ -143,12 +132,6 @@ class BasicConsolidation(base.ServerConsolidationBaseStrategy):
|
|||||||
"type": "number",
|
"type": "number",
|
||||||
"default": 7200
|
"default": 7200
|
||||||
},
|
},
|
||||||
"granularity": {
|
|
||||||
"description": "The time between two measures in an "
|
|
||||||
"aggregated timeseries of a metric.",
|
|
||||||
"type": "number",
|
|
||||||
"default": 300
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,12 +142,7 @@ class BasicConsolidation(base.ServerConsolidationBaseStrategy):
|
|||||||
"datasource",
|
"datasource",
|
||||||
help="Data source to use in order to query the needed metrics",
|
help="Data source to use in order to query the needed metrics",
|
||||||
default="ceilometer",
|
default="ceilometer",
|
||||||
choices=["ceilometer", "monasca", "gnocchi"]),
|
choices=["ceilometer", "monasca"]),
|
||||||
cfg.BoolOpt(
|
|
||||||
"check_optimize_metadata",
|
|
||||||
help="Check optimize metadata field in instance before "
|
|
||||||
"migration",
|
|
||||||
default=False),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -187,16 +165,6 @@ class BasicConsolidation(base.ServerConsolidationBaseStrategy):
|
|||||||
def monasca(self, monasca):
|
def monasca(self, monasca):
|
||||||
self._monasca = monasca
|
self._monasca = monasca
|
||||||
|
|
||||||
@property
|
|
||||||
def gnocchi(self):
|
|
||||||
if self._gnocchi is None:
|
|
||||||
self._gnocchi = gnoc.GnocchiHelper(osc=self.osc)
|
|
||||||
return self._gnocchi
|
|
||||||
|
|
||||||
@gnocchi.setter
|
|
||||||
def gnocchi(self, gnocchi):
|
|
||||||
self._gnocchi = gnocchi
|
|
||||||
|
|
||||||
def check_migration(self, source_node, destination_node,
|
def check_migration(self, source_node, destination_node,
|
||||||
instance_to_migrate):
|
instance_to_migrate):
|
||||||
"""Check if the migration is possible
|
"""Check if the migration is possible
|
||||||
@@ -292,19 +260,6 @@ class BasicConsolidation(base.ServerConsolidationBaseStrategy):
|
|||||||
period=self.period,
|
period=self.period,
|
||||||
aggregate='avg',
|
aggregate='avg',
|
||||||
)
|
)
|
||||||
elif self.config.datasource == "gnocchi":
|
|
||||||
resource_id = "%s_%s" % (node.uuid, node.hostname)
|
|
||||||
stop_time = datetime.datetime.utcnow()
|
|
||||||
start_time = stop_time - datetime.timedelta(
|
|
||||||
seconds=int(self.period))
|
|
||||||
return self.gnocchi.statistic_aggregation(
|
|
||||||
resource_id=resource_id,
|
|
||||||
metric=metric_name,
|
|
||||||
granularity=self.granularity,
|
|
||||||
start_time=start_time,
|
|
||||||
stop_time=stop_time,
|
|
||||||
aggregation='mean'
|
|
||||||
)
|
|
||||||
elif self.config.datasource == "monasca":
|
elif self.config.datasource == "monasca":
|
||||||
statistics = self.monasca.statistic_aggregation(
|
statistics = self.monasca.statistic_aggregation(
|
||||||
meter_name=metric_name,
|
meter_name=metric_name,
|
||||||
@@ -334,18 +289,6 @@ class BasicConsolidation(base.ServerConsolidationBaseStrategy):
|
|||||||
period=self.period,
|
period=self.period,
|
||||||
aggregate='avg'
|
aggregate='avg'
|
||||||
)
|
)
|
||||||
elif self.config.datasource == "gnocchi":
|
|
||||||
stop_time = datetime.datetime.utcnow()
|
|
||||||
start_time = stop_time - datetime.timedelta(
|
|
||||||
seconds=int(self.period))
|
|
||||||
return self.gnocchi.statistic_aggregation(
|
|
||||||
resource_id=instance.uuid,
|
|
||||||
metric=metric_name,
|
|
||||||
granularity=self.granularity,
|
|
||||||
start_time=start_time,
|
|
||||||
stop_time=stop_time,
|
|
||||||
aggregation='mean',
|
|
||||||
)
|
|
||||||
elif self.config.datasource == "monasca":
|
elif self.config.datasource == "monasca":
|
||||||
statistics = self.monasca.statistic_aggregation(
|
statistics = self.monasca.statistic_aggregation(
|
||||||
meter_name=metric_name,
|
meter_name=metric_name,
|
||||||
@@ -376,8 +319,8 @@ class BasicConsolidation(base.ServerConsolidationBaseStrategy):
|
|||||||
if host_avg_cpu_util is None:
|
if host_avg_cpu_util is None:
|
||||||
resource_id = "%s_%s" % (node.uuid, node.hostname)
|
resource_id = "%s_%s" % (node.uuid, node.hostname)
|
||||||
LOG.error(
|
LOG.error(
|
||||||
"No values returned by %(resource_id)s "
|
_LE("No values returned by %(resource_id)s "
|
||||||
"for %(metric_name)s" % dict(
|
"for %(metric_name)s") % dict(
|
||||||
resource_id=resource_id,
|
resource_id=resource_id,
|
||||||
metric_name=self.METRIC_NAMES[
|
metric_name=self.METRIC_NAMES[
|
||||||
self.config.datasource]['host_cpu_usage']))
|
self.config.datasource]['host_cpu_usage']))
|
||||||
@@ -396,8 +339,8 @@ class BasicConsolidation(base.ServerConsolidationBaseStrategy):
|
|||||||
instance_cpu_utilization = self.get_instance_cpu_usage(instance)
|
instance_cpu_utilization = self.get_instance_cpu_usage(instance)
|
||||||
if instance_cpu_utilization is None:
|
if instance_cpu_utilization is None:
|
||||||
LOG.error(
|
LOG.error(
|
||||||
"No values returned by %(resource_id)s "
|
_LE("No values returned by %(resource_id)s "
|
||||||
"for %(metric_name)s" % dict(
|
"for %(metric_name)s") % dict(
|
||||||
resource_id=instance.uuid,
|
resource_id=instance.uuid,
|
||||||
metric_name=self.METRIC_NAMES[
|
metric_name=self.METRIC_NAMES[
|
||||||
self.config.datasource]['instance_cpu_usage']))
|
self.config.datasource]['instance_cpu_usage']))
|
||||||
@@ -442,10 +385,9 @@ class BasicConsolidation(base.ServerConsolidationBaseStrategy):
|
|||||||
def node_and_instance_score(self, sorted_scores):
|
def node_and_instance_score(self, sorted_scores):
|
||||||
"""Get List of VMs from node"""
|
"""Get List of VMs from node"""
|
||||||
node_to_release = sorted_scores[len(sorted_scores) - 1][0]
|
node_to_release = sorted_scores[len(sorted_scores) - 1][0]
|
||||||
instances = self.compute_model.get_node_instances(
|
instances_to_migrate = self.compute_model.get_node_instances(
|
||||||
self.compute_model.get_node_by_uuid(node_to_release))
|
self.compute_model.get_node_by_uuid(node_to_release))
|
||||||
|
|
||||||
instances_to_migrate = self.filter_instances_by_audit_tag(instances)
|
|
||||||
instance_score = []
|
instance_score = []
|
||||||
for instance in instances_to_migrate:
|
for instance in instances_to_migrate:
|
||||||
if instance.state == element.InstanceState.ACTIVE.value:
|
if instance.state == element.InstanceState.ACTIVE.value:
|
||||||
@@ -497,7 +439,7 @@ class BasicConsolidation(base.ServerConsolidationBaseStrategy):
|
|||||||
return unsuccessful_migration + 1
|
return unsuccessful_migration + 1
|
||||||
|
|
||||||
def pre_execute(self):
|
def pre_execute(self):
|
||||||
LOG.info("Initializing Server Consolidation")
|
LOG.info(_LI("Initializing Server Consolidation"))
|
||||||
|
|
||||||
if not self.compute_model:
|
if not self.compute_model:
|
||||||
raise exception.ClusterStateNotDefined()
|
raise exception.ClusterStateNotDefined()
|
||||||
@@ -519,9 +461,9 @@ class BasicConsolidation(base.ServerConsolidationBaseStrategy):
|
|||||||
LOG.debug("Compute node(s) BFD %s", sorted_scores)
|
LOG.debug("Compute node(s) BFD %s", sorted_scores)
|
||||||
# Get Node to be released
|
# Get Node to be released
|
||||||
if len(scores) == 0:
|
if len(scores) == 0:
|
||||||
LOG.warning(
|
LOG.warning(_LW(
|
||||||
"The workloads of the compute nodes"
|
"The workloads of the compute nodes"
|
||||||
" of the cluster is zero")
|
" of the cluster is zero"))
|
||||||
return
|
return
|
||||||
|
|
||||||
while sorted_scores and (
|
while sorted_scores and (
|
||||||
|
|||||||
@@ -28,14 +28,11 @@ Outlet (Exhaust Air) Temperature is one of the important thermal
|
|||||||
telemetries to measure thermal/workload status of server.
|
telemetries to measure thermal/workload status of server.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import datetime
|
|
||||||
|
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
|
|
||||||
from watcher._i18n import _
|
from watcher._i18n import _, _LW, _LI
|
||||||
from watcher.common import exception as wexc
|
from watcher.common import exception as wexc
|
||||||
from watcher.datasource import ceilometer as ceil
|
from watcher.datasource import ceilometer as ceil
|
||||||
from watcher.datasource import gnocchi as gnoc
|
|
||||||
from watcher.decision_engine.model import element
|
from watcher.decision_engine.model import element
|
||||||
from watcher.decision_engine.strategy.strategies import base
|
from watcher.decision_engine.strategy.strategies import base
|
||||||
|
|
||||||
@@ -74,15 +71,9 @@ class OutletTempControl(base.ThermalOptimizationBaseStrategy):
|
|||||||
""" # noqa
|
""" # noqa
|
||||||
|
|
||||||
# The meter to report outlet temperature in ceilometer
|
# The meter to report outlet temperature in ceilometer
|
||||||
|
METER_NAME = "hardware.ipmi.node.outlet_temperature"
|
||||||
MIGRATION = "migrate"
|
MIGRATION = "migrate"
|
||||||
|
|
||||||
METRIC_NAMES = dict(
|
|
||||||
ceilometer=dict(
|
|
||||||
host_outlet_temp='hardware.ipmi.node.outlet_temperature'),
|
|
||||||
gnocchi=dict(
|
|
||||||
host_outlet_temp='hardware.ipmi.node.outlet_temperature'),
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(self, config, osc=None):
|
def __init__(self, config, osc=None):
|
||||||
"""Outlet temperature control using live migration
|
"""Outlet temperature control using live migration
|
||||||
|
|
||||||
@@ -92,8 +83,8 @@ class OutletTempControl(base.ThermalOptimizationBaseStrategy):
|
|||||||
:type osc: :py:class:`~.OpenStackClients` instance, optional
|
:type osc: :py:class:`~.OpenStackClients` instance, optional
|
||||||
"""
|
"""
|
||||||
super(OutletTempControl, self).__init__(config, osc)
|
super(OutletTempControl, self).__init__(config, osc)
|
||||||
|
self._meter = self.METER_NAME
|
||||||
self._ceilometer = None
|
self._ceilometer = None
|
||||||
self._gnocchi = None
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_name(cls):
|
def get_name(cls):
|
||||||
@@ -107,10 +98,6 @@ class OutletTempControl(base.ThermalOptimizationBaseStrategy):
|
|||||||
def get_translatable_display_name(cls):
|
def get_translatable_display_name(cls):
|
||||||
return "Outlet temperature based strategy"
|
return "Outlet temperature based strategy"
|
||||||
|
|
||||||
@property
|
|
||||||
def period(self):
|
|
||||||
return self.input_parameters.get('period', 30)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_schema(cls):
|
def get_schema(cls):
|
||||||
# Mandatory default setting for each element
|
# Mandatory default setting for each element
|
||||||
@@ -121,18 +108,6 @@ class OutletTempControl(base.ThermalOptimizationBaseStrategy):
|
|||||||
"type": "number",
|
"type": "number",
|
||||||
"default": 35.0
|
"default": 35.0
|
||||||
},
|
},
|
||||||
"period": {
|
|
||||||
"description": "The time interval in seconds for "
|
|
||||||
"getting statistic aggregation",
|
|
||||||
"type": "number",
|
|
||||||
"default": 30
|
|
||||||
},
|
|
||||||
"granularity": {
|
|
||||||
"description": "The time between two measures in an "
|
|
||||||
"aggregated timeseries of a metric.",
|
|
||||||
"type": "number",
|
|
||||||
"default": 300
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,20 +121,6 @@ class OutletTempControl(base.ThermalOptimizationBaseStrategy):
|
|||||||
def ceilometer(self, c):
|
def ceilometer(self, c):
|
||||||
self._ceilometer = c
|
self._ceilometer = c
|
||||||
|
|
||||||
@property
|
|
||||||
def gnocchi(self):
|
|
||||||
if self._gnocchi is None:
|
|
||||||
self._gnocchi = gnoc.GnocchiHelper(osc=self.osc)
|
|
||||||
return self._gnocchi
|
|
||||||
|
|
||||||
@gnocchi.setter
|
|
||||||
def gnocchi(self, g):
|
|
||||||
self._gnocchi = g
|
|
||||||
|
|
||||||
@property
|
|
||||||
def granularity(self):
|
|
||||||
return self.input_parameters.get('granularity', 300)
|
|
||||||
|
|
||||||
def calc_used_resource(self, node):
|
def calc_used_resource(self, node):
|
||||||
"""Calculate the used vcpus, memory and disk based on VM flavors"""
|
"""Calculate the used vcpus, memory and disk based on VM flavors"""
|
||||||
instances = self.compute_model.get_node_instances(node)
|
instances = self.compute_model.get_node_instances(node)
|
||||||
@@ -182,34 +143,17 @@ class OutletTempControl(base.ThermalOptimizationBaseStrategy):
|
|||||||
|
|
||||||
hosts_need_release = []
|
hosts_need_release = []
|
||||||
hosts_target = []
|
hosts_target = []
|
||||||
metric_name = self.METRIC_NAMES[
|
|
||||||
self.config.datasource]['host_outlet_temp']
|
|
||||||
for node in nodes.values():
|
for node in nodes.values():
|
||||||
resource_id = node.uuid
|
resource_id = node.uuid
|
||||||
outlet_temp = None
|
|
||||||
|
|
||||||
if self.config.datasource == "ceilometer":
|
|
||||||
outlet_temp = self.ceilometer.statistic_aggregation(
|
outlet_temp = self.ceilometer.statistic_aggregation(
|
||||||
resource_id=resource_id,
|
resource_id=resource_id,
|
||||||
meter_name=metric_name,
|
meter_name=self._meter,
|
||||||
period=self.period,
|
period="30",
|
||||||
aggregate='avg'
|
aggregate='avg')
|
||||||
)
|
|
||||||
elif self.config.datasource == "gnocchi":
|
|
||||||
stop_time = datetime.datetime.utcnow()
|
|
||||||
start_time = stop_time - datetime.timedelta(
|
|
||||||
seconds=int(self.period))
|
|
||||||
outlet_temp = self.gnocchi.statistic_aggregation(
|
|
||||||
resource_id=resource_id,
|
|
||||||
metric=metric_name,
|
|
||||||
granularity=self.granularity,
|
|
||||||
start_time=start_time,
|
|
||||||
stop_time=stop_time,
|
|
||||||
aggregation='mean'
|
|
||||||
)
|
|
||||||
# some hosts may not have outlet temp meters, remove from target
|
# some hosts may not have outlet temp meters, remove from target
|
||||||
if outlet_temp is None:
|
if outlet_temp is None:
|
||||||
LOG.warning("%s: no outlet temp data", resource_id)
|
LOG.warning(_LW("%s: no outlet temp data"), resource_id)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
LOG.debug("%s: outlet temperature %f" % (resource_id, outlet_temp))
|
LOG.debug("%s: outlet temperature %f" % (resource_id, outlet_temp))
|
||||||
@@ -232,13 +176,13 @@ class OutletTempControl(base.ThermalOptimizationBaseStrategy):
|
|||||||
# select the first active instance to migrate
|
# select the first active instance to migrate
|
||||||
if (instance.state !=
|
if (instance.state !=
|
||||||
element.InstanceState.ACTIVE.value):
|
element.InstanceState.ACTIVE.value):
|
||||||
LOG.info("Instance not active, skipped: %s",
|
LOG.info(_LI("Instance not active, skipped: %s"),
|
||||||
instance.uuid)
|
instance.uuid)
|
||||||
continue
|
continue
|
||||||
return mig_source_node, instance
|
return mig_source_node, instance
|
||||||
except wexc.InstanceNotFound as e:
|
except wexc.InstanceNotFound as e:
|
||||||
LOG.exception(e)
|
LOG.exception(e)
|
||||||
LOG.info("Instance not found")
|
LOG.info(_LI("Instance not found"))
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -289,7 +233,7 @@ class OutletTempControl(base.ThermalOptimizationBaseStrategy):
|
|||||||
return self.solution
|
return self.solution
|
||||||
|
|
||||||
if len(hosts_target) == 0:
|
if len(hosts_target) == 0:
|
||||||
LOG.warning("No hosts under outlet temp threshold found")
|
LOG.warning(_LW("No hosts under outlet temp threshold found"))
|
||||||
return self.solution
|
return self.solution
|
||||||
|
|
||||||
# choose the server with highest outlet t
|
# choose the server with highest outlet t
|
||||||
@@ -310,7 +254,7 @@ class OutletTempControl(base.ThermalOptimizationBaseStrategy):
|
|||||||
if len(dest_servers) == 0:
|
if len(dest_servers) == 0:
|
||||||
# TODO(zhenzanz): maybe to warn that there's no resource
|
# TODO(zhenzanz): maybe to warn that there's no resource
|
||||||
# for instance.
|
# for instance.
|
||||||
LOG.info("No proper target host could be found")
|
LOG.info(_LI("No proper target host could be found"))
|
||||||
return self.solution
|
return self.solution
|
||||||
|
|
||||||
dest_servers = sorted(dest_servers, key=lambda x: (x["outlet_temp"]))
|
dest_servers = sorted(dest_servers, key=lambda x: (x["outlet_temp"]))
|
||||||
|
|||||||
@@ -42,15 +42,12 @@ airflow is higher than the specified threshold.
|
|||||||
- It assumes that live migrations are possible.
|
- It assumes that live migrations are possible.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import datetime
|
|
||||||
|
|
||||||
from oslo_config import cfg
|
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
|
|
||||||
from watcher._i18n import _
|
from watcher._i18n import _, _LI, _LW
|
||||||
from watcher.common import exception as wexc
|
from watcher.common import exception as wexc
|
||||||
from watcher.datasource import ceilometer as ceil
|
from watcher.datasource import ceilometer as ceil
|
||||||
from watcher.datasource import gnocchi as gnoc
|
|
||||||
from watcher.decision_engine.model import element
|
from watcher.decision_engine.model import element
|
||||||
from watcher.decision_engine.strategy.strategies import base
|
from watcher.decision_engine.strategy.strategies import base
|
||||||
|
|
||||||
@@ -83,28 +80,15 @@ class UniformAirflow(base.BaseStrategy):
|
|||||||
- It assumes that live migrations are possible.
|
- It assumes that live migrations are possible.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# The meter to report Airflow of physical server in ceilometer
|
||||||
|
METER_NAME_AIRFLOW = "hardware.ipmi.node.airflow"
|
||||||
|
# The meter to report inlet temperature of physical server in ceilometer
|
||||||
|
METER_NAME_INLET_T = "hardware.ipmi.node.temperature"
|
||||||
|
# The meter to report system power of physical server in ceilometer
|
||||||
|
METER_NAME_POWER = "hardware.ipmi.node.power"
|
||||||
# choose 300 seconds as the default duration of meter aggregation
|
# choose 300 seconds as the default duration of meter aggregation
|
||||||
PERIOD = 300
|
PERIOD = 300
|
||||||
|
|
||||||
METRIC_NAMES = dict(
|
|
||||||
ceilometer=dict(
|
|
||||||
# The meter to report Airflow of physical server in ceilometer
|
|
||||||
host_airflow='hardware.ipmi.node.airflow',
|
|
||||||
# The meter to report inlet temperature of physical server
|
|
||||||
# in ceilometer
|
|
||||||
host_inlet_temp='hardware.ipmi.node.temperature',
|
|
||||||
# The meter to report system power of physical server in ceilometer
|
|
||||||
host_power='hardware.ipmi.node.power'),
|
|
||||||
gnocchi=dict(
|
|
||||||
# The meter to report Airflow of physical server in gnocchi
|
|
||||||
host_airflow='hardware.ipmi.node.airflow',
|
|
||||||
# The meter to report inlet temperature of physical server
|
|
||||||
# in gnocchi
|
|
||||||
host_inlet_temp='hardware.ipmi.node.temperature',
|
|
||||||
# The meter to report system power of physical server in gnocchi
|
|
||||||
host_power='hardware.ipmi.node.power'),
|
|
||||||
)
|
|
||||||
|
|
||||||
MIGRATION = "migrate"
|
MIGRATION = "migrate"
|
||||||
|
|
||||||
def __init__(self, config, osc=None):
|
def __init__(self, config, osc=None):
|
||||||
@@ -117,14 +101,10 @@ class UniformAirflow(base.BaseStrategy):
|
|||||||
super(UniformAirflow, self).__init__(config, osc)
|
super(UniformAirflow, self).__init__(config, osc)
|
||||||
# The migration plan will be triggered when the airflow reaches
|
# The migration plan will be triggered when the airflow reaches
|
||||||
# threshold
|
# threshold
|
||||||
self.meter_name_airflow = self.METRIC_NAMES[
|
self.meter_name_airflow = self.METER_NAME_AIRFLOW
|
||||||
self.config.datasource]['host_airflow']
|
self.meter_name_inlet_t = self.METER_NAME_INLET_T
|
||||||
self.meter_name_inlet_t = self.METRIC_NAMES[
|
self.meter_name_power = self.METER_NAME_POWER
|
||||||
self.config.datasource]['host_inlet_temp']
|
|
||||||
self.meter_name_power = self.METRIC_NAMES[
|
|
||||||
self.config.datasource]['host_power']
|
|
||||||
self._ceilometer = None
|
self._ceilometer = None
|
||||||
self._gnocchi = None
|
|
||||||
self._period = self.PERIOD
|
self._period = self.PERIOD
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -137,16 +117,6 @@ class UniformAirflow(base.BaseStrategy):
|
|||||||
def ceilometer(self, c):
|
def ceilometer(self, c):
|
||||||
self._ceilometer = c
|
self._ceilometer = c
|
||||||
|
|
||||||
@property
|
|
||||||
def gnocchi(self):
|
|
||||||
if self._gnocchi is None:
|
|
||||||
self._gnocchi = gnoc.GnocchiHelper(osc=self.osc)
|
|
||||||
return self._gnocchi
|
|
||||||
|
|
||||||
@gnocchi.setter
|
|
||||||
def gnocchi(self, g):
|
|
||||||
self._gnocchi = g
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_name(cls):
|
def get_name(cls):
|
||||||
return "uniform_airflow"
|
return "uniform_airflow"
|
||||||
@@ -163,10 +133,6 @@ class UniformAirflow(base.BaseStrategy):
|
|||||||
def get_goal_name(cls):
|
def get_goal_name(cls):
|
||||||
return "airflow_optimization"
|
return "airflow_optimization"
|
||||||
|
|
||||||
@property
|
|
||||||
def granularity(self):
|
|
||||||
return self.input_parameters.get('granularity', 300)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_schema(cls):
|
def get_schema(cls):
|
||||||
# Mandatory default setting for each element
|
# Mandatory default setting for each element
|
||||||
@@ -195,25 +161,9 @@ class UniformAirflow(base.BaseStrategy):
|
|||||||
"type": "number",
|
"type": "number",
|
||||||
"default": 300
|
"default": 300
|
||||||
},
|
},
|
||||||
"granularity": {
|
|
||||||
"description": "The time between two measures in an "
|
|
||||||
"aggregated timeseries of a metric.",
|
|
||||||
"type": "number",
|
|
||||||
"default": 300
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_config_opts(cls):
|
|
||||||
return [
|
|
||||||
cfg.StrOpt(
|
|
||||||
"datasource",
|
|
||||||
help="Data source to use in order to query the needed metrics",
|
|
||||||
default="ceilometer",
|
|
||||||
choices=["ceilometer", "gnocchi"])
|
|
||||||
]
|
|
||||||
|
|
||||||
def calculate_used_resource(self, node):
|
def calculate_used_resource(self, node):
|
||||||
"""Compute the used vcpus, memory and disk based on instance flavors"""
|
"""Compute the used vcpus, memory and disk based on instance flavors"""
|
||||||
instances = self.compute_model.get_node_instances(node)
|
instances = self.compute_model.get_node_instances(node)
|
||||||
@@ -238,7 +188,6 @@ class UniformAirflow(base.BaseStrategy):
|
|||||||
source_instances = self.compute_model.get_node_instances(
|
source_instances = self.compute_model.get_node_instances(
|
||||||
source_node)
|
source_node)
|
||||||
if source_instances:
|
if source_instances:
|
||||||
if self.config.datasource == "ceilometer":
|
|
||||||
inlet_t = self.ceilometer.statistic_aggregation(
|
inlet_t = self.ceilometer.statistic_aggregation(
|
||||||
resource_id=source_node.uuid,
|
resource_id=source_node.uuid,
|
||||||
meter_name=self.meter_name_inlet_t,
|
meter_name=self.meter_name_inlet_t,
|
||||||
@@ -249,24 +198,6 @@ class UniformAirflow(base.BaseStrategy):
|
|||||||
meter_name=self.meter_name_power,
|
meter_name=self.meter_name_power,
|
||||||
period=self._period,
|
period=self._period,
|
||||||
aggregate='avg')
|
aggregate='avg')
|
||||||
elif self.config.datasource == "gnocchi":
|
|
||||||
stop_time = datetime.datetime.utcnow()
|
|
||||||
start_time = stop_time - datetime.timedelta(
|
|
||||||
seconds=int(self._period))
|
|
||||||
inlet_t = self.gnocchi.statistic_aggregation(
|
|
||||||
resource_id=source_node.uuid,
|
|
||||||
metric=self.meter_name_inlet_t,
|
|
||||||
granularity=self.granularity,
|
|
||||||
start_time=start_time,
|
|
||||||
stop_time=stop_time,
|
|
||||||
aggregation='mean')
|
|
||||||
power = self.gnocchi.statistic_aggregation(
|
|
||||||
resource_id=source_node.uuid,
|
|
||||||
metric=self.meter_name_power,
|
|
||||||
granularity=self.granularity,
|
|
||||||
start_time=start_time,
|
|
||||||
stop_time=stop_time,
|
|
||||||
aggregation='mean')
|
|
||||||
if (power < self.threshold_power and
|
if (power < self.threshold_power and
|
||||||
inlet_t < self.threshold_inlet_t):
|
inlet_t < self.threshold_inlet_t):
|
||||||
# hardware issue, migrate all instances from this node
|
# hardware issue, migrate all instances from this node
|
||||||
@@ -279,13 +210,13 @@ class UniformAirflow(base.BaseStrategy):
|
|||||||
if (instance.state !=
|
if (instance.state !=
|
||||||
element.InstanceState.ACTIVE.value):
|
element.InstanceState.ACTIVE.value):
|
||||||
LOG.info(
|
LOG.info(
|
||||||
"Instance not active, skipped: %s",
|
_LI("Instance not active, skipped: %s"),
|
||||||
instance.uuid)
|
instance.uuid)
|
||||||
continue
|
continue
|
||||||
instances_tobe_migrate.append(instance)
|
instances_tobe_migrate.append(instance)
|
||||||
return source_node, instances_tobe_migrate
|
return source_node, instances_tobe_migrate
|
||||||
else:
|
else:
|
||||||
LOG.info("Instance not found on node: %s",
|
LOG.info(_LI("Instance not found on node: %s"),
|
||||||
source_node.uuid)
|
source_node.uuid)
|
||||||
|
|
||||||
def filter_destination_hosts(self, hosts, instances_to_migrate):
|
def filter_destination_hosts(self, hosts, instances_to_migrate):
|
||||||
@@ -326,8 +257,8 @@ class UniformAirflow(base.BaseStrategy):
|
|||||||
break
|
break
|
||||||
# check if all instances have target hosts
|
# check if all instances have target hosts
|
||||||
if len(destination_hosts) != len(instances_to_migrate):
|
if len(destination_hosts) != len(instances_to_migrate):
|
||||||
LOG.warning("Not all target hosts could be found; it might "
|
LOG.warning(_LW("Not all target hosts could be found; it might "
|
||||||
"be because there is not enough resource")
|
"be because there is not enough resource"))
|
||||||
return None
|
return None
|
||||||
return destination_hosts
|
return destination_hosts
|
||||||
|
|
||||||
@@ -340,30 +271,17 @@ class UniformAirflow(base.BaseStrategy):
|
|||||||
overload_hosts = []
|
overload_hosts = []
|
||||||
nonoverload_hosts = []
|
nonoverload_hosts = []
|
||||||
for node_id in nodes:
|
for node_id in nodes:
|
||||||
airflow = None
|
|
||||||
node = self.compute_model.get_node_by_uuid(
|
node = self.compute_model.get_node_by_uuid(
|
||||||
node_id)
|
node_id)
|
||||||
resource_id = node.uuid
|
resource_id = node.uuid
|
||||||
if self.config.datasource == "ceilometer":
|
|
||||||
airflow = self.ceilometer.statistic_aggregation(
|
airflow = self.ceilometer.statistic_aggregation(
|
||||||
resource_id=resource_id,
|
resource_id=resource_id,
|
||||||
meter_name=self.meter_name_airflow,
|
meter_name=self.meter_name_airflow,
|
||||||
period=self._period,
|
period=self._period,
|
||||||
aggregate='avg')
|
aggregate='avg')
|
||||||
elif self.config.datasource == "gnocchi":
|
|
||||||
stop_time = datetime.datetime.utcnow()
|
|
||||||
start_time = stop_time - datetime.timedelta(
|
|
||||||
seconds=int(self._period))
|
|
||||||
airflow = self.gnocchi.statistic_aggregation(
|
|
||||||
resource_id=resource_id,
|
|
||||||
metric=self.meter_name_airflow,
|
|
||||||
granularity=self.granularity,
|
|
||||||
start_time=start_time,
|
|
||||||
stop_time=stop_time,
|
|
||||||
aggregation='mean')
|
|
||||||
# some hosts may not have airflow meter, remove from target
|
# some hosts may not have airflow meter, remove from target
|
||||||
if airflow is None:
|
if airflow is None:
|
||||||
LOG.warning("%s: no airflow data", resource_id)
|
LOG.warning(_LW("%s: no airflow data"), resource_id)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
LOG.debug("%s: airflow %f" % (resource_id, airflow))
|
LOG.debug("%s: airflow %f" % (resource_id, airflow))
|
||||||
@@ -398,9 +316,9 @@ class UniformAirflow(base.BaseStrategy):
|
|||||||
return self.solution
|
return self.solution
|
||||||
|
|
||||||
if not target_nodes:
|
if not target_nodes:
|
||||||
LOG.warning("No hosts currently have airflow under %s, "
|
LOG.warning(_LW("No hosts currently have airflow under %s, "
|
||||||
"therefore there are no possible target "
|
"therefore there are no possible target "
|
||||||
"hosts for any migration",
|
"hosts for any migration"),
|
||||||
self.threshold_airflow)
|
self.threshold_airflow)
|
||||||
return self.solution
|
return self.solution
|
||||||
|
|
||||||
@@ -419,8 +337,8 @@ class UniformAirflow(base.BaseStrategy):
|
|||||||
destination_hosts = self.filter_destination_hosts(
|
destination_hosts = self.filter_destination_hosts(
|
||||||
target_nodes, instances_src)
|
target_nodes, instances_src)
|
||||||
if not destination_hosts:
|
if not destination_hosts:
|
||||||
LOG.warning("No target host could be found; it might "
|
LOG.warning(_LW("No target host could be found; it might "
|
||||||
"be because there is not enough resources")
|
"be because there is not enough resources"))
|
||||||
return self.solution
|
return self.solution
|
||||||
# generate solution to migrate the instance to the dest server,
|
# generate solution to migrate the instance to the dest server,
|
||||||
for info in destination_hosts:
|
for info in destination_hosts:
|
||||||
|
|||||||
@@ -52,16 +52,13 @@ correctly on all compute nodes within the cluster.
|
|||||||
This strategy assumes it is possible to live migrate any VM from
|
This strategy assumes it is possible to live migrate any VM from
|
||||||
an active compute node to any other active compute node.
|
an active compute node to any other active compute node.
|
||||||
"""
|
"""
|
||||||
import datetime
|
|
||||||
|
|
||||||
from oslo_config import cfg
|
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from watcher._i18n import _
|
from watcher._i18n import _, _LE, _LI
|
||||||
from watcher.common import exception
|
from watcher.common import exception
|
||||||
from watcher.datasource import ceilometer as ceil
|
from watcher.datasource import ceilometer as ceil
|
||||||
from watcher.datasource import gnocchi as gnoc
|
|
||||||
from watcher.decision_engine.model import element
|
from watcher.decision_engine.model import element
|
||||||
from watcher.decision_engine.strategy.strategies import base
|
from watcher.decision_engine.strategy.strategies import base
|
||||||
|
|
||||||
@@ -71,33 +68,12 @@ LOG = log.getLogger(__name__)
|
|||||||
class VMWorkloadConsolidation(base.ServerConsolidationBaseStrategy):
|
class VMWorkloadConsolidation(base.ServerConsolidationBaseStrategy):
|
||||||
"""VM Workload Consolidation Strategy"""
|
"""VM Workload Consolidation Strategy"""
|
||||||
|
|
||||||
HOST_CPU_USAGE_METRIC_NAME = 'compute.node.cpu.percent'
|
|
||||||
INSTANCE_CPU_USAGE_METRIC_NAME = 'cpu_util'
|
|
||||||
|
|
||||||
METRIC_NAMES = dict(
|
|
||||||
ceilometer=dict(
|
|
||||||
cpu_util_metric='cpu_util',
|
|
||||||
ram_util_metric='memory.usage',
|
|
||||||
ram_alloc_metric='memory',
|
|
||||||
disk_alloc_metric='disk.root.size'),
|
|
||||||
gnocchi=dict(
|
|
||||||
cpu_util_metric='cpu_util',
|
|
||||||
ram_util_metric='memory.usage',
|
|
||||||
ram_alloc_metric='memory',
|
|
||||||
disk_alloc_metric='disk.root.size'),
|
|
||||||
)
|
|
||||||
|
|
||||||
MIGRATION = "migrate"
|
|
||||||
CHANGE_NOVA_SERVICE_STATE = "change_nova_service_state"
|
|
||||||
|
|
||||||
def __init__(self, config, osc=None):
|
def __init__(self, config, osc=None):
|
||||||
super(VMWorkloadConsolidation, self).__init__(config, osc)
|
super(VMWorkloadConsolidation, self).__init__(config, osc)
|
||||||
self._ceilometer = None
|
self._ceilometer = None
|
||||||
self._gnocchi = None
|
|
||||||
self.number_of_migrations = 0
|
self.number_of_migrations = 0
|
||||||
self.number_of_released_nodes = 0
|
self.number_of_released_nodes = 0
|
||||||
# self.ceilometer_instance_data_cache = dict()
|
self.ceilometer_instance_data_cache = dict()
|
||||||
self.datasource_instance_data_cache = dict()
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_name(cls):
|
def get_name(cls):
|
||||||
@@ -111,10 +87,6 @@ class VMWorkloadConsolidation(base.ServerConsolidationBaseStrategy):
|
|||||||
def get_translatable_display_name(cls):
|
def get_translatable_display_name(cls):
|
||||||
return "VM Workload Consolidation Strategy"
|
return "VM Workload Consolidation Strategy"
|
||||||
|
|
||||||
@property
|
|
||||||
def period(self):
|
|
||||||
return self.input_parameters.get('period', 3600)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ceilometer(self):
|
def ceilometer(self):
|
||||||
if self._ceilometer is None:
|
if self._ceilometer is None:
|
||||||
@@ -125,50 +97,6 @@ class VMWorkloadConsolidation(base.ServerConsolidationBaseStrategy):
|
|||||||
def ceilometer(self, ceilometer):
|
def ceilometer(self, ceilometer):
|
||||||
self._ceilometer = ceilometer
|
self._ceilometer = ceilometer
|
||||||
|
|
||||||
@property
|
|
||||||
def gnocchi(self):
|
|
||||||
if self._gnocchi is None:
|
|
||||||
self._gnocchi = gnoc.GnocchiHelper(osc=self.osc)
|
|
||||||
return self._gnocchi
|
|
||||||
|
|
||||||
@gnocchi.setter
|
|
||||||
def gnocchi(self, gnocchi):
|
|
||||||
self._gnocchi = gnocchi
|
|
||||||
|
|
||||||
@property
|
|
||||||
def granularity(self):
|
|
||||||
return self.input_parameters.get('granularity', 300)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_schema(cls):
|
|
||||||
# Mandatory default setting for each element
|
|
||||||
return {
|
|
||||||
"properties": {
|
|
||||||
"period": {
|
|
||||||
"description": "The time interval in seconds for "
|
|
||||||
"getting statistic aggregation",
|
|
||||||
"type": "number",
|
|
||||||
"default": 3600
|
|
||||||
},
|
|
||||||
"granularity": {
|
|
||||||
"description": "The time between two measures in an "
|
|
||||||
"aggregated timeseries of a metric.",
|
|
||||||
"type": "number",
|
|
||||||
"default": 300
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_config_opts(cls):
|
|
||||||
return [
|
|
||||||
cfg.StrOpt(
|
|
||||||
"datasource",
|
|
||||||
help="Data source to use in order to query the needed metrics",
|
|
||||||
default="ceilometer",
|
|
||||||
choices=["ceilometer", "gnocchi"])
|
|
||||||
]
|
|
||||||
|
|
||||||
def get_state_str(self, state):
|
def get_state_str(self, state):
|
||||||
"""Get resource state in string format.
|
"""Get resource state in string format.
|
||||||
|
|
||||||
@@ -179,9 +107,9 @@ class VMWorkloadConsolidation(base.ServerConsolidationBaseStrategy):
|
|||||||
elif isinstance(state, (element.InstanceState, element.ServiceState)):
|
elif isinstance(state, (element.InstanceState, element.ServiceState)):
|
||||||
return state.value
|
return state.value
|
||||||
else:
|
else:
|
||||||
LOG.error('Unexpected resource state type, '
|
LOG.error(_LE('Unexpexted resource state type, '
|
||||||
'state=%(state)s, state_type=%(st)s.' %
|
'state=%(state)s, state_type=%(st)s.') % dict(
|
||||||
dict(state=state,
|
state=state,
|
||||||
st=type(state)))
|
st=type(state)))
|
||||||
raise exception.WatcherException
|
raise exception.WatcherException
|
||||||
|
|
||||||
@@ -193,7 +121,7 @@ class VMWorkloadConsolidation(base.ServerConsolidationBaseStrategy):
|
|||||||
"""
|
"""
|
||||||
params = {'state': element.ServiceState.ENABLED.value}
|
params = {'state': element.ServiceState.ENABLED.value}
|
||||||
self.solution.add_action(
|
self.solution.add_action(
|
||||||
action_type=self.CHANGE_NOVA_SERVICE_STATE,
|
action_type='change_nova_service_state',
|
||||||
resource_id=node.uuid,
|
resource_id=node.uuid,
|
||||||
input_parameters=params)
|
input_parameters=params)
|
||||||
self.number_of_released_nodes -= 1
|
self.number_of_released_nodes -= 1
|
||||||
@@ -206,7 +134,7 @@ class VMWorkloadConsolidation(base.ServerConsolidationBaseStrategy):
|
|||||||
"""
|
"""
|
||||||
params = {'state': element.ServiceState.DISABLED.value}
|
params = {'state': element.ServiceState.DISABLED.value}
|
||||||
self.solution.add_action(
|
self.solution.add_action(
|
||||||
action_type=self.CHANGE_NOVA_SERVICE_STATE,
|
action_type='change_nova_service_state',
|
||||||
resource_id=node.uuid,
|
resource_id=node.uuid,
|
||||||
input_parameters=params)
|
input_parameters=params)
|
||||||
self.number_of_released_nodes += 1
|
self.number_of_released_nodes += 1
|
||||||
@@ -221,13 +149,13 @@ class VMWorkloadConsolidation(base.ServerConsolidationBaseStrategy):
|
|||||||
"""
|
"""
|
||||||
instance_state_str = self.get_state_str(instance.state)
|
instance_state_str = self.get_state_str(instance.state)
|
||||||
if instance_state_str != element.InstanceState.ACTIVE.value:
|
if instance_state_str != element.InstanceState.ACTIVE.value:
|
||||||
# Watcher currently only supports live VM migration and block live
|
# Watcher curently only supports live VM migration and block live
|
||||||
# VM migration which both requires migrated VM to be active.
|
# VM migration which both requires migrated VM to be active.
|
||||||
# When supported, the cold migration may be used as a fallback
|
# When supported, the cold migration may be used as a fallback
|
||||||
# migration mechanism to move non active VMs.
|
# migration mechanism to move non active VMs.
|
||||||
LOG.error(
|
LOG.error(
|
||||||
'Cannot live migrate: instance_uuid=%(instance_uuid)s, '
|
_LE('Cannot live migrate: instance_uuid=%(instance_uuid)s, '
|
||||||
'state=%(instance_state)s.' % dict(
|
'state=%(instance_state)s.') % dict(
|
||||||
instance_uuid=instance.uuid,
|
instance_uuid=instance.uuid,
|
||||||
instance_state=instance_state_str))
|
instance_state=instance_state_str))
|
||||||
return
|
return
|
||||||
@@ -243,13 +171,13 @@ class VMWorkloadConsolidation(base.ServerConsolidationBaseStrategy):
|
|||||||
params = {'migration_type': migration_type,
|
params = {'migration_type': migration_type,
|
||||||
'source_node': source_node.uuid,
|
'source_node': source_node.uuid,
|
||||||
'destination_node': destination_node.uuid}
|
'destination_node': destination_node.uuid}
|
||||||
self.solution.add_action(action_type=self.MIGRATION,
|
self.solution.add_action(action_type='migrate',
|
||||||
resource_id=instance.uuid,
|
resource_id=instance.uuid,
|
||||||
input_parameters=params)
|
input_parameters=params)
|
||||||
self.number_of_migrations += 1
|
self.number_of_migrations += 1
|
||||||
|
|
||||||
def disable_unused_nodes(self):
|
def disable_unused_nodes(self):
|
||||||
"""Generate actions for disabling unused nodes.
|
"""Generate actions for disablity of unused nodes.
|
||||||
|
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
@@ -259,101 +187,62 @@ class VMWorkloadConsolidation(base.ServerConsolidationBaseStrategy):
|
|||||||
element.ServiceState.DISABLED.value):
|
element.ServiceState.DISABLED.value):
|
||||||
self.add_action_disable_node(node)
|
self.add_action_disable_node(node)
|
||||||
|
|
||||||
def get_instance_utilization(self, instance):
|
def get_instance_utilization(self, instance,
|
||||||
|
period=3600, aggr='avg'):
|
||||||
"""Collect cpu, ram and disk utilization statistics of a VM.
|
"""Collect cpu, ram and disk utilization statistics of a VM.
|
||||||
|
|
||||||
:param instance: instance object
|
:param instance: instance object
|
||||||
|
:param period: seconds
|
||||||
:param aggr: string
|
:param aggr: string
|
||||||
:return: dict(cpu(number of vcpus used), ram(MB used), disk(B used))
|
:return: dict(cpu(number of vcpus used), ram(MB used), disk(B used))
|
||||||
"""
|
"""
|
||||||
instance_cpu_util = None
|
if instance.uuid in self.ceilometer_instance_data_cache.keys():
|
||||||
instance_ram_util = None
|
return self.ceilometer_instance_data_cache.get(instance.uuid)
|
||||||
instance_disk_util = None
|
|
||||||
|
|
||||||
if instance.uuid in self.datasource_instance_data_cache.keys():
|
cpu_util_metric = 'cpu_util'
|
||||||
return self.datasource_instance_data_cache.get(instance.uuid)
|
ram_util_metric = 'memory.usage'
|
||||||
|
|
||||||
cpu_util_metric = self.METRIC_NAMES[
|
ram_alloc_metric = 'memory'
|
||||||
self.config.datasource]['cpu_util_metric']
|
disk_alloc_metric = 'disk.root.size'
|
||||||
ram_util_metric = self.METRIC_NAMES[
|
|
||||||
self.config.datasource]['ram_util_metric']
|
|
||||||
ram_alloc_metric = self.METRIC_NAMES[
|
|
||||||
self.config.datasource]['ram_alloc_metric']
|
|
||||||
disk_alloc_metric = self.METRIC_NAMES[
|
|
||||||
self.config.datasource]['disk_alloc_metric']
|
|
||||||
|
|
||||||
if self.config.datasource == "ceilometer":
|
|
||||||
instance_cpu_util = self.ceilometer.statistic_aggregation(
|
instance_cpu_util = self.ceilometer.statistic_aggregation(
|
||||||
resource_id=instance.uuid, meter_name=cpu_util_metric,
|
resource_id=instance.uuid, meter_name=cpu_util_metric,
|
||||||
period=self.period, aggregate='avg')
|
period=period, aggregate=aggr)
|
||||||
instance_ram_util = self.ceilometer.statistic_aggregation(
|
|
||||||
resource_id=instance.uuid, meter_name=ram_util_metric,
|
|
||||||
period=self.period, aggregate='avg')
|
|
||||||
if not instance_ram_util:
|
|
||||||
instance_ram_util = self.ceilometer.statistic_aggregation(
|
|
||||||
resource_id=instance.uuid, meter_name=ram_alloc_metric,
|
|
||||||
period=self.period, aggregate='avg')
|
|
||||||
instance_disk_util = self.ceilometer.statistic_aggregation(
|
|
||||||
resource_id=instance.uuid, meter_name=disk_alloc_metric,
|
|
||||||
period=self.period, aggregate='avg')
|
|
||||||
elif self.config.datasource == "gnocchi":
|
|
||||||
stop_time = datetime.datetime.utcnow()
|
|
||||||
start_time = stop_time - datetime.timedelta(
|
|
||||||
seconds=int(self.period))
|
|
||||||
instance_cpu_util = self.gnocchi.statistic_aggregation(
|
|
||||||
resource_id=instance.uuid,
|
|
||||||
metric=cpu_util_metric,
|
|
||||||
granularity=self.granularity,
|
|
||||||
start_time=start_time,
|
|
||||||
stop_time=stop_time,
|
|
||||||
aggregation='mean'
|
|
||||||
)
|
|
||||||
instance_ram_util = self.gnocchi.statistic_aggregation(
|
|
||||||
resource_id=instance.uuid,
|
|
||||||
metric=ram_util_metric,
|
|
||||||
granularity=self.granularity,
|
|
||||||
start_time=start_time,
|
|
||||||
stop_time=stop_time,
|
|
||||||
aggregation='mean'
|
|
||||||
)
|
|
||||||
if not instance_ram_util:
|
|
||||||
instance_ram_util = self.gnocchi.statistic_aggregation(
|
|
||||||
resource_id=instance.uuid,
|
|
||||||
metric=ram_alloc_metric,
|
|
||||||
granularity=self.granularity,
|
|
||||||
start_time=start_time,
|
|
||||||
stop_time=stop_time,
|
|
||||||
aggregation='mean'
|
|
||||||
)
|
|
||||||
instance_disk_util = self.gnocchi.statistic_aggregation(
|
|
||||||
resource_id=instance.uuid,
|
|
||||||
metric=disk_alloc_metric,
|
|
||||||
granularity=self.granularity,
|
|
||||||
start_time=start_time,
|
|
||||||
stop_time=stop_time,
|
|
||||||
aggregation='mean'
|
|
||||||
)
|
|
||||||
if instance_cpu_util:
|
if instance_cpu_util:
|
||||||
total_cpu_utilization = (
|
total_cpu_utilization = (
|
||||||
instance.vcpus * (instance_cpu_util / 100.0))
|
instance.vcpus * (instance_cpu_util / 100.0))
|
||||||
else:
|
else:
|
||||||
total_cpu_utilization = instance.vcpus
|
total_cpu_utilization = instance.vcpus
|
||||||
|
|
||||||
|
instance_ram_util = self.ceilometer.statistic_aggregation(
|
||||||
|
resource_id=instance.uuid, meter_name=ram_util_metric,
|
||||||
|
period=period, aggregate=aggr)
|
||||||
|
|
||||||
|
if not instance_ram_util:
|
||||||
|
instance_ram_util = self.ceilometer.statistic_aggregation(
|
||||||
|
resource_id=instance.uuid, meter_name=ram_alloc_metric,
|
||||||
|
period=period, aggregate=aggr)
|
||||||
|
|
||||||
|
instance_disk_util = self.ceilometer.statistic_aggregation(
|
||||||
|
resource_id=instance.uuid, meter_name=disk_alloc_metric,
|
||||||
|
period=period, aggregate=aggr)
|
||||||
|
|
||||||
if not instance_ram_util or not instance_disk_util:
|
if not instance_ram_util or not instance_disk_util:
|
||||||
LOG.error(
|
LOG.error(
|
||||||
'No values returned by %s for memory.usage '
|
_LE('No values returned by %s for memory.usage '
|
||||||
'or disk.root.size', instance.uuid)
|
'or disk.root.size'), instance.uuid)
|
||||||
raise exception.NoDataFound
|
raise exception.NoDataFound
|
||||||
|
|
||||||
self.datasource_instance_data_cache[instance.uuid] = dict(
|
self.ceilometer_instance_data_cache[instance.uuid] = dict(
|
||||||
cpu=total_cpu_utilization, ram=instance_ram_util,
|
cpu=total_cpu_utilization, ram=instance_ram_util,
|
||||||
disk=instance_disk_util)
|
disk=instance_disk_util)
|
||||||
return self.datasource_instance_data_cache.get(instance.uuid)
|
return self.ceilometer_instance_data_cache.get(instance.uuid)
|
||||||
|
|
||||||
def get_node_utilization(self, node):
|
def get_node_utilization(self, node, period=3600, aggr='avg'):
|
||||||
"""Collect cpu, ram and disk utilization statistics of a node.
|
"""Collect cpu, ram and disk utilization statistics of a node.
|
||||||
|
|
||||||
:param node: node object
|
:param node: node object
|
||||||
|
:param period: seconds
|
||||||
:param aggr: string
|
:param aggr: string
|
||||||
:return: dict(cpu(number of cores used), ram(MB used), disk(B used))
|
:return: dict(cpu(number of cores used), ram(MB used), disk(B used))
|
||||||
"""
|
"""
|
||||||
@@ -363,7 +252,7 @@ class VMWorkloadConsolidation(base.ServerConsolidationBaseStrategy):
|
|||||||
node_cpu_util = 0
|
node_cpu_util = 0
|
||||||
for instance in node_instances:
|
for instance in node_instances:
|
||||||
instance_util = self.get_instance_utilization(
|
instance_util = self.get_instance_utilization(
|
||||||
instance)
|
instance, period, aggr)
|
||||||
node_cpu_util += instance_util['cpu']
|
node_cpu_util += instance_util['cpu']
|
||||||
node_ram_util += instance_util['ram']
|
node_ram_util += instance_util['ram']
|
||||||
node_disk_util += instance_util['disk']
|
node_disk_util += instance_util['disk']
|
||||||
@@ -468,7 +357,7 @@ class VMWorkloadConsolidation(base.ServerConsolidationBaseStrategy):
|
|||||||
"""
|
"""
|
||||||
migrate_actions = (
|
migrate_actions = (
|
||||||
a for a in self.solution.actions if a[
|
a for a in self.solution.actions if a[
|
||||||
'action_type'] == self.MIGRATION)
|
'action_type'] == 'migrate')
|
||||||
instance_to_be_migrated = (
|
instance_to_be_migrated = (
|
||||||
a['input_parameters']['resource_id'] for a in migrate_actions)
|
a['input_parameters']['resource_id'] for a in migrate_actions)
|
||||||
instance_uuids = list(set(instance_to_be_migrated))
|
instance_uuids = list(set(instance_to_be_migrated))
|
||||||
@@ -498,11 +387,11 @@ class VMWorkloadConsolidation(base.ServerConsolidationBaseStrategy):
|
|||||||
Offload phase performing first-fit based bin packing to offload
|
Offload phase performing first-fit based bin packing to offload
|
||||||
overloaded nodes. This is done in a fashion of moving
|
overloaded nodes. This is done in a fashion of moving
|
||||||
the least CPU utilized VM first as live migration these
|
the least CPU utilized VM first as live migration these
|
||||||
generally causes less troubles. This phase results in a cluster
|
generaly causes less troubles. This phase results in a cluster
|
||||||
with no overloaded nodes.
|
with no overloaded nodes.
|
||||||
* This phase is be able to enable disabled nodes (if needed
|
* This phase is be able to enable disabled nodes (if needed
|
||||||
and any available) in the case of the resource capacity provided by
|
and any available) in the case of the resource capacity provided by
|
||||||
active nodes is not able to accommodate all the load.
|
active nodes is not able to accomodate all the load.
|
||||||
As the offload phase is later followed by the consolidation phase,
|
As the offload phase is later followed by the consolidation phase,
|
||||||
the node enabler in this phase doesn't necessarily results
|
the node enabler in this phase doesn't necessarily results
|
||||||
in more enabled nodes in the final solution.
|
in more enabled nodes in the final solution.
|
||||||
@@ -535,9 +424,9 @@ class VMWorkloadConsolidation(base.ServerConsolidationBaseStrategy):
|
|||||||
Consolidation phase performing first-fit based bin packing.
|
Consolidation phase performing first-fit based bin packing.
|
||||||
First, nodes with the lowest cpu utilization are consolidated
|
First, nodes with the lowest cpu utilization are consolidated
|
||||||
by moving their load to nodes with the highest cpu utilization
|
by moving their load to nodes with the highest cpu utilization
|
||||||
which can accommodate the load. In this phase the most cpu utilized
|
which can accomodate the load. In this phase the most cpu utilizied
|
||||||
VMs are prioritized as their load is more difficult to accommodate
|
VMs are prioritizied as their load is more difficult to accomodate
|
||||||
in the system than less cpu utilized VMs which can be later used
|
in the system than less cpu utilizied VMs which can be later used
|
||||||
to fill smaller CPU capacity gaps.
|
to fill smaller CPU capacity gaps.
|
||||||
|
|
||||||
:param cc: dictionary containing resource capacity coefficients
|
:param cc: dictionary containing resource capacity coefficients
|
||||||
@@ -586,7 +475,7 @@ class VMWorkloadConsolidation(base.ServerConsolidationBaseStrategy):
|
|||||||
|
|
||||||
:param original_model: root_model object
|
:param original_model: root_model object
|
||||||
"""
|
"""
|
||||||
LOG.info('Executing Smart Strategy')
|
LOG.info(_LI('Executing Smart Strategy'))
|
||||||
rcu = self.get_relative_cluster_utilization()
|
rcu = self.get_relative_cluster_utilization()
|
||||||
|
|
||||||
cc = {'cpu': 1.0, 'ram': 1.0, 'disk': 1.0}
|
cc = {'cpu': 1.0, 'ram': 1.0, 'disk': 1.0}
|
||||||
|
|||||||
@@ -46,15 +46,12 @@ hosts nodes.
|
|||||||
algorithm with `CONTINUOUS` audits.
|
algorithm with `CONTINUOUS` audits.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import datetime
|
|
||||||
|
|
||||||
from oslo_config import cfg
|
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
|
|
||||||
from watcher._i18n import _
|
from watcher._i18n import _, _LE, _LI, _LW
|
||||||
from watcher.common import exception as wexc
|
from watcher.common import exception as wexc
|
||||||
from watcher.datasource import ceilometer as ceil
|
from watcher.datasource import ceilometer as ceil
|
||||||
from watcher.datasource import gnocchi as gnoc
|
|
||||||
from watcher.decision_engine.model import element
|
from watcher.decision_engine.model import element
|
||||||
from watcher.decision_engine.strategy.strategies import base
|
from watcher.decision_engine.strategy.strategies import base
|
||||||
|
|
||||||
@@ -107,7 +104,6 @@ class WorkloadBalance(base.WorkloadStabilizationBaseStrategy):
|
|||||||
# reaches threshold
|
# reaches threshold
|
||||||
self._meter = self.METER_NAME
|
self._meter = self.METER_NAME
|
||||||
self._ceilometer = None
|
self._ceilometer = None
|
||||||
self._gnocchi = None
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ceilometer(self):
|
def ceilometer(self):
|
||||||
@@ -119,16 +115,6 @@ class WorkloadBalance(base.WorkloadStabilizationBaseStrategy):
|
|||||||
def ceilometer(self, c):
|
def ceilometer(self, c):
|
||||||
self._ceilometer = c
|
self._ceilometer = c
|
||||||
|
|
||||||
@property
|
|
||||||
def gnocchi(self):
|
|
||||||
if self._gnocchi is None:
|
|
||||||
self._gnocchi = gnoc.GnocchiHelper(osc=self.osc)
|
|
||||||
return self._gnocchi
|
|
||||||
|
|
||||||
@gnocchi.setter
|
|
||||||
def gnocchi(self, gnocchi):
|
|
||||||
self._gnocchi = gnocchi
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_name(cls):
|
def get_name(cls):
|
||||||
return "workload_balance"
|
return "workload_balance"
|
||||||
@@ -141,10 +127,6 @@ class WorkloadBalance(base.WorkloadStabilizationBaseStrategy):
|
|||||||
def get_translatable_display_name(cls):
|
def get_translatable_display_name(cls):
|
||||||
return "Workload Balance Migration Strategy"
|
return "Workload Balance Migration Strategy"
|
||||||
|
|
||||||
@property
|
|
||||||
def granularity(self):
|
|
||||||
return self.input_parameters.get('granularity', 300)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_schema(cls):
|
def get_schema(cls):
|
||||||
# Mandatory default setting for each element
|
# Mandatory default setting for each element
|
||||||
@@ -160,25 +142,9 @@ class WorkloadBalance(base.WorkloadStabilizationBaseStrategy):
|
|||||||
"type": "number",
|
"type": "number",
|
||||||
"default": 300
|
"default": 300
|
||||||
},
|
},
|
||||||
"granularity": {
|
|
||||||
"description": "The time between two measures in an "
|
|
||||||
"aggregated timeseries of a metric.",
|
|
||||||
"type": "number",
|
|
||||||
"default": 300
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_config_opts(cls):
|
|
||||||
return [
|
|
||||||
cfg.StrOpt(
|
|
||||||
"datasource",
|
|
||||||
help="Data source to use in order to query the needed metrics",
|
|
||||||
default="ceilometer",
|
|
||||||
choices=["ceilometer", "gnocchi"])
|
|
||||||
]
|
|
||||||
|
|
||||||
def calculate_used_resource(self, node):
|
def calculate_used_resource(self, node):
|
||||||
"""Calculate the used vcpus, memory and disk based on VM flavors"""
|
"""Calculate the used vcpus, memory and disk based on VM flavors"""
|
||||||
instances = self.compute_model.get_node_instances(node)
|
instances = self.compute_model.get_node_instances(node)
|
||||||
@@ -221,14 +187,14 @@ class WorkloadBalance(base.WorkloadStabilizationBaseStrategy):
|
|||||||
min_delta = current_delta
|
min_delta = current_delta
|
||||||
instance_id = instance.uuid
|
instance_id = instance.uuid
|
||||||
except wexc.InstanceNotFound:
|
except wexc.InstanceNotFound:
|
||||||
LOG.error("Instance not found; error: %s",
|
LOG.error(_LE("Instance not found; error: %s"),
|
||||||
instance_id)
|
instance_id)
|
||||||
if instance_id:
|
if instance_id:
|
||||||
return (source_node,
|
return (source_node,
|
||||||
self.compute_model.get_instance_by_uuid(
|
self.compute_model.get_instance_by_uuid(
|
||||||
instance_id))
|
instance_id))
|
||||||
else:
|
else:
|
||||||
LOG.info("VM not found from node: %s",
|
LOG.info(_LI("VM not found from node: %s"),
|
||||||
source_node.uuid)
|
source_node.uuid)
|
||||||
|
|
||||||
def filter_destination_hosts(self, hosts, instance_to_migrate,
|
def filter_destination_hosts(self, hosts, instance_to_migrate,
|
||||||
@@ -285,30 +251,15 @@ class WorkloadBalance(base.WorkloadStabilizationBaseStrategy):
|
|||||||
instances = self.compute_model.get_node_instances(node)
|
instances = self.compute_model.get_node_instances(node)
|
||||||
node_workload = 0.0
|
node_workload = 0.0
|
||||||
for instance in instances:
|
for instance in instances:
|
||||||
cpu_util = None
|
|
||||||
try:
|
try:
|
||||||
if self.config.datasource == "ceilometer":
|
|
||||||
cpu_util = self.ceilometer.statistic_aggregation(
|
cpu_util = self.ceilometer.statistic_aggregation(
|
||||||
resource_id=instance.uuid,
|
resource_id=instance.uuid,
|
||||||
meter_name=self._meter,
|
meter_name=self._meter,
|
||||||
period=self._period,
|
period=self._period,
|
||||||
aggregate='avg')
|
aggregate='avg')
|
||||||
elif self.config.datasource == "gnocchi":
|
|
||||||
stop_time = datetime.datetime.utcnow()
|
|
||||||
start_time = stop_time - datetime.timedelta(
|
|
||||||
seconds=int(self._period))
|
|
||||||
cpu_util = self.gnocchi.statistic_aggregation(
|
|
||||||
resource_id=instance.uuid,
|
|
||||||
metric=self._meter,
|
|
||||||
granularity=self.granularity,
|
|
||||||
start_time=start_time,
|
|
||||||
stop_time=stop_time,
|
|
||||||
aggregation='mean'
|
|
||||||
)
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
LOG.exception(exc)
|
LOG.exception(exc)
|
||||||
LOG.error("Can not get cpu_util from %s",
|
LOG.error(_LE("Can not get cpu_util from Ceilometer"))
|
||||||
self.config.datasource)
|
|
||||||
continue
|
continue
|
||||||
if cpu_util is None:
|
if cpu_util is None:
|
||||||
LOG.debug("Instance (%s): cpu_util is None", instance.uuid)
|
LOG.debug("Instance (%s): cpu_util is None", instance.uuid)
|
||||||
@@ -338,7 +289,7 @@ class WorkloadBalance(base.WorkloadStabilizationBaseStrategy):
|
|||||||
|
|
||||||
This can be used to fetch some pre-requisites or data.
|
This can be used to fetch some pre-requisites or data.
|
||||||
"""
|
"""
|
||||||
LOG.info("Initializing Workload Balance Strategy")
|
LOG.info(_LI("Initializing Workload Balance Strategy"))
|
||||||
|
|
||||||
if not self.compute_model:
|
if not self.compute_model:
|
||||||
raise wexc.ClusterStateNotDefined()
|
raise wexc.ClusterStateNotDefined()
|
||||||
@@ -363,9 +314,9 @@ class WorkloadBalance(base.WorkloadStabilizationBaseStrategy):
|
|||||||
return self.solution
|
return self.solution
|
||||||
|
|
||||||
if not target_nodes:
|
if not target_nodes:
|
||||||
LOG.warning("No hosts current have CPU utilization under %s "
|
LOG.warning(_LW("No hosts current have CPU utilization under %s "
|
||||||
"percent, therefore there are no possible target "
|
"percent, therefore there are no possible target "
|
||||||
"hosts for any migration",
|
"hosts for any migration"),
|
||||||
self.threshold)
|
self.threshold)
|
||||||
return self.solution
|
return self.solution
|
||||||
|
|
||||||
@@ -386,8 +337,8 @@ class WorkloadBalance(base.WorkloadStabilizationBaseStrategy):
|
|||||||
# pick up the lowest one as dest server
|
# pick up the lowest one as dest server
|
||||||
if not destination_hosts:
|
if not destination_hosts:
|
||||||
# for instance.
|
# for instance.
|
||||||
LOG.warning("No proper target host could be found, it might "
|
LOG.warning(_LW("No proper target host could be found, it might "
|
||||||
"be because of there's no enough CPU/Memory/DISK")
|
"be because of there's no enough CPU/Memory/DISK"))
|
||||||
return self.solution
|
return self.solution
|
||||||
destination_hosts = sorted(destination_hosts,
|
destination_hosts = sorted(destination_hosts,
|
||||||
key=lambda x: (x["cpu_util"]))
|
key=lambda x: (x["cpu_util"]))
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ It assumes that live migrations are possible in your cluster.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
import datetime
|
|
||||||
import itertools
|
import itertools
|
||||||
import math
|
import math
|
||||||
import random
|
import random
|
||||||
@@ -39,10 +38,9 @@ from oslo_config import cfg
|
|||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
import oslo_utils
|
import oslo_utils
|
||||||
|
|
||||||
from watcher._i18n import _
|
from watcher._i18n import _LI, _LW, _
|
||||||
from watcher.common import exception
|
from watcher.common import exception
|
||||||
from watcher.datasource import ceilometer as ceil
|
from watcher.datasource import ceilometer as ceil
|
||||||
from watcher.datasource import gnocchi as gnoc
|
|
||||||
from watcher.decision_engine.model import element
|
from watcher.decision_engine.model import element
|
||||||
from watcher.decision_engine.strategy.strategies import base
|
from watcher.decision_engine.strategy.strategies import base
|
||||||
|
|
||||||
@@ -74,7 +72,6 @@ class WorkloadStabilization(base.WorkloadStabilizationBaseStrategy):
|
|||||||
"""
|
"""
|
||||||
super(WorkloadStabilization, self).__init__(config, osc)
|
super(WorkloadStabilization, self).__init__(config, osc)
|
||||||
self._ceilometer = None
|
self._ceilometer = None
|
||||||
self._gnocchi = None
|
|
||||||
self._nova = None
|
self._nova = None
|
||||||
self.weights = None
|
self.weights = None
|
||||||
self.metrics = None
|
self.metrics = None
|
||||||
@@ -96,10 +93,6 @@ class WorkloadStabilization(base.WorkloadStabilizationBaseStrategy):
|
|||||||
def get_translatable_display_name(cls):
|
def get_translatable_display_name(cls):
|
||||||
return "Workload stabilization"
|
return "Workload stabilization"
|
||||||
|
|
||||||
@property
|
|
||||||
def granularity(self):
|
|
||||||
return self.input_parameters.get('granularity', 300)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_schema(cls):
|
def get_schema(cls):
|
||||||
return {
|
return {
|
||||||
@@ -156,25 +149,9 @@ class WorkloadStabilization(base.WorkloadStabilizationBaseStrategy):
|
|||||||
" ones.",
|
" ones.",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"default": {"instance": 720, "node": 600}
|
"default": {"instance": 720, "node": 600}
|
||||||
},
|
|
||||||
"granularity": {
|
|
||||||
"description": "The time between two measures in an "
|
|
||||||
"aggregated timeseries of a metric.",
|
|
||||||
"type": "number",
|
|
||||||
"default": 300
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@classmethod
|
|
||||||
def get_config_opts(cls):
|
|
||||||
return [
|
|
||||||
cfg.StrOpt(
|
|
||||||
"datasource",
|
|
||||||
help="Data source to use in order to query the needed metrics",
|
|
||||||
default="ceilometer",
|
|
||||||
choices=["ceilometer", "gnocchi"])
|
|
||||||
]
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ceilometer(self):
|
def ceilometer(self):
|
||||||
@@ -196,16 +173,6 @@ class WorkloadStabilization(base.WorkloadStabilizationBaseStrategy):
|
|||||||
def ceilometer(self, c):
|
def ceilometer(self, c):
|
||||||
self._ceilometer = c
|
self._ceilometer = c
|
||||||
|
|
||||||
@property
|
|
||||||
def gnocchi(self):
|
|
||||||
if self._gnocchi is None:
|
|
||||||
self._gnocchi = gnoc.GnocchiHelper(osc=self.osc)
|
|
||||||
return self._gnocchi
|
|
||||||
|
|
||||||
@gnocchi.setter
|
|
||||||
def gnocchi(self, gnocchi):
|
|
||||||
self._gnocchi = gnocchi
|
|
||||||
|
|
||||||
def transform_instance_cpu(self, instance_load, host_vcpus):
|
def transform_instance_cpu(self, instance_load, host_vcpus):
|
||||||
"""Transform instance cpu utilization to overall host cpu utilization.
|
"""Transform instance cpu utilization to overall host cpu utilization.
|
||||||
|
|
||||||
@@ -219,7 +186,7 @@ class WorkloadStabilization(base.WorkloadStabilizationBaseStrategy):
|
|||||||
|
|
||||||
@MEMOIZE
|
@MEMOIZE
|
||||||
def get_instance_load(self, instance):
|
def get_instance_load(self, instance):
|
||||||
"""Gathering instance load through ceilometer/gnocchi statistic.
|
"""Gathering instance load through ceilometer statistic.
|
||||||
|
|
||||||
:param instance: instance for which statistic is gathered.
|
:param instance: instance for which statistic is gathered.
|
||||||
:return: dict
|
:return: dict
|
||||||
@@ -227,31 +194,18 @@ class WorkloadStabilization(base.WorkloadStabilizationBaseStrategy):
|
|||||||
LOG.debug('get_instance_load started')
|
LOG.debug('get_instance_load started')
|
||||||
instance_load = {'uuid': instance.uuid, 'vcpus': instance.vcpus}
|
instance_load = {'uuid': instance.uuid, 'vcpus': instance.vcpus}
|
||||||
for meter in self.metrics:
|
for meter in self.metrics:
|
||||||
avg_meter = None
|
|
||||||
if self.config.datasource == "ceilometer":
|
|
||||||
avg_meter = self.ceilometer.statistic_aggregation(
|
avg_meter = self.ceilometer.statistic_aggregation(
|
||||||
resource_id=instance.uuid,
|
resource_id=instance.uuid,
|
||||||
meter_name=meter,
|
meter_name=meter,
|
||||||
period=self.periods['instance'],
|
period=self.periods['instance'],
|
||||||
aggregate='min'
|
aggregate='min'
|
||||||
)
|
)
|
||||||
elif self.config.datasource == "gnocchi":
|
|
||||||
stop_time = datetime.datetime.utcnow()
|
|
||||||
start_time = stop_time - datetime.timedelta(
|
|
||||||
seconds=int(self.periods['instance']))
|
|
||||||
avg_meter = self.gnocchi.statistic_aggregation(
|
|
||||||
resource_id=instance.uuid,
|
|
||||||
metric=meter,
|
|
||||||
granularity=self.granularity,
|
|
||||||
start_time=start_time,
|
|
||||||
stop_time=stop_time,
|
|
||||||
aggregation='mean'
|
|
||||||
)
|
|
||||||
if avg_meter is None:
|
if avg_meter is None:
|
||||||
LOG.warning(
|
LOG.warning(
|
||||||
"No values returned by %(resource_id)s "
|
_LW("No values returned by %(resource_id)s "
|
||||||
"for %(metric_name)s" % dict(
|
"for %(metric_name)s") % dict(
|
||||||
resource_id=instance.uuid, metric_name=meter))
|
resource_id=instance.uuid,
|
||||||
|
metric_name=meter))
|
||||||
avg_meter = 0
|
avg_meter = 0
|
||||||
if meter == 'cpu_util':
|
if meter == 'cpu_util':
|
||||||
avg_meter /= float(100)
|
avg_meter /= float(100)
|
||||||
@@ -279,34 +233,21 @@ class WorkloadStabilization(base.WorkloadStabilizationBaseStrategy):
|
|||||||
for node_id, node in self.get_available_nodes().items():
|
for node_id, node in self.get_available_nodes().items():
|
||||||
hosts_load[node_id] = {}
|
hosts_load[node_id] = {}
|
||||||
hosts_load[node_id]['vcpus'] = node.vcpus
|
hosts_load[node_id]['vcpus'] = node.vcpus
|
||||||
|
|
||||||
for metric in self.metrics:
|
for metric in self.metrics:
|
||||||
resource_id = ''
|
resource_id = ''
|
||||||
avg_meter = None
|
|
||||||
meter_name = self.instance_metrics[metric]
|
meter_name = self.instance_metrics[metric]
|
||||||
if re.match('^compute.node', meter_name) is not None:
|
if re.match('^compute.node', meter_name) is not None:
|
||||||
resource_id = "%s_%s" % (node.uuid, node.hostname)
|
resource_id = "%s_%s" % (node.uuid, node.hostname)
|
||||||
else:
|
else:
|
||||||
resource_id = node_id
|
resource_id = node_id
|
||||||
if self.config.datasource == "ceilometer":
|
|
||||||
avg_meter = self.ceilometer.statistic_aggregation(
|
avg_meter = self.ceilometer.statistic_aggregation(
|
||||||
resource_id=resource_id,
|
resource_id=resource_id,
|
||||||
meter_name=self.instance_metrics[metric],
|
meter_name=self.instance_metrics[metric],
|
||||||
period=self.periods['node'],
|
period=self.periods['node'],
|
||||||
aggregate='avg'
|
aggregate='avg'
|
||||||
)
|
)
|
||||||
elif self.config.datasource == "gnocchi":
|
|
||||||
stop_time = datetime.datetime.utcnow()
|
|
||||||
start_time = stop_time - datetime.timedelta(
|
|
||||||
seconds=int(self.periods['node']))
|
|
||||||
avg_meter = self.gnocchi.statistic_aggregation(
|
|
||||||
resource_id=resource_id,
|
|
||||||
metric=self.instance_metrics[metric],
|
|
||||||
granularity=self.granularity,
|
|
||||||
start_time=start_time,
|
|
||||||
stop_time=stop_time,
|
|
||||||
aggregation='mean'
|
|
||||||
)
|
|
||||||
|
|
||||||
if avg_meter is None:
|
if avg_meter is None:
|
||||||
raise exception.NoSuchMetricForHost(
|
raise exception.NoSuchMetricForHost(
|
||||||
metric=meter_name,
|
metric=meter_name,
|
||||||
@@ -458,7 +399,7 @@ class WorkloadStabilization(base.WorkloadStabilizationBaseStrategy):
|
|||||||
return self.solution
|
return self.solution
|
||||||
|
|
||||||
def pre_execute(self):
|
def pre_execute(self):
|
||||||
LOG.info("Initializing Workload Stabilization")
|
LOG.info(_LI("Initializing Workload Stabilization"))
|
||||||
|
|
||||||
if not self.compute_model:
|
if not self.compute_model:
|
||||||
raise exception.ClusterStateNotDefined()
|
raise exception.ClusterStateNotDefined()
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import collections
|
|||||||
|
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
|
|
||||||
|
from watcher._i18n import _LI, _LW
|
||||||
from watcher.common import context
|
from watcher.common import context
|
||||||
from watcher.decision_engine.loading import default
|
from watcher.decision_engine.loading import default
|
||||||
from watcher.decision_engine.scoring import scoring_factory
|
from watcher.decision_engine.scoring import scoring_factory
|
||||||
@@ -135,7 +136,7 @@ class Syncer(object):
|
|||||||
|
|
||||||
for goal_name, goal_map in goals_map.items():
|
for goal_name, goal_map in goals_map.items():
|
||||||
if goal_map in self.available_goals_map:
|
if goal_map in self.available_goals_map:
|
||||||
LOG.info("Goal %s already exists", goal_name)
|
LOG.info(_LI("Goal %s already exists"), goal_name)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
self.goal_mapping.update(self._sync_goal(goal_map))
|
self.goal_mapping.update(self._sync_goal(goal_map))
|
||||||
@@ -144,14 +145,14 @@ class Syncer(object):
|
|||||||
if (strategy_map in self.available_strategies_map and
|
if (strategy_map in self.available_strategies_map and
|
||||||
strategy_map.goal_name not in
|
strategy_map.goal_name not in
|
||||||
[g.name for g in self.goal_mapping.values()]):
|
[g.name for g in self.goal_mapping.values()]):
|
||||||
LOG.info("Strategy %s already exists", strategy_name)
|
LOG.info(_LI("Strategy %s already exists"), strategy_name)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
self.strategy_mapping.update(self._sync_strategy(strategy_map))
|
self.strategy_mapping.update(self._sync_strategy(strategy_map))
|
||||||
|
|
||||||
for se_name, se_map in scoringengines_map.items():
|
for se_name, se_map in scoringengines_map.items():
|
||||||
if se_map in self.available_scoringengines_map:
|
if se_map in self.available_scoringengines_map:
|
||||||
LOG.info("Scoring Engine %s already exists",
|
LOG.info(_LI("Scoring Engine %s already exists"),
|
||||||
se_name)
|
se_name)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -176,7 +177,7 @@ class Syncer(object):
|
|||||||
indicator._asdict()
|
indicator._asdict()
|
||||||
for indicator in goal_map.efficacy_specification]
|
for indicator in goal_map.efficacy_specification]
|
||||||
goal.create()
|
goal.create()
|
||||||
LOG.info("Goal %s created", goal_name)
|
LOG.info(_LI("Goal %s created"), goal_name)
|
||||||
|
|
||||||
# Updating the internal states
|
# Updating the internal states
|
||||||
self.available_goals_map[goal] = goal_map
|
self.available_goals_map[goal] = goal_map
|
||||||
@@ -207,7 +208,7 @@ class Syncer(object):
|
|||||||
strategy.goal_id = objects.Goal.get_by_name(self.ctx, goal_name).id
|
strategy.goal_id = objects.Goal.get_by_name(self.ctx, goal_name).id
|
||||||
strategy.parameters_spec = parameters_spec
|
strategy.parameters_spec = parameters_spec
|
||||||
strategy.create()
|
strategy.create()
|
||||||
LOG.info("Strategy %s created", strategy_name)
|
LOG.info(_LI("Strategy %s created"), strategy_name)
|
||||||
|
|
||||||
# Updating the internal states
|
# Updating the internal states
|
||||||
self.available_strategies_map[strategy] = strategy_map
|
self.available_strategies_map[strategy] = strategy_map
|
||||||
@@ -232,7 +233,7 @@ class Syncer(object):
|
|||||||
scoringengine.description = scoringengine_map.description
|
scoringengine.description = scoringengine_map.description
|
||||||
scoringengine.metainfo = scoringengine_map.metainfo
|
scoringengine.metainfo = scoringengine_map.metainfo
|
||||||
scoringengine.create()
|
scoringengine.create()
|
||||||
LOG.info("Scoring Engine %s created", scoringengine_name)
|
LOG.info(_LI("Scoring Engine %s created"), scoringengine_name)
|
||||||
|
|
||||||
# Updating the internal states
|
# Updating the internal states
|
||||||
self.available_scoringengines_map[scoringengine] = \
|
self.available_scoringengines_map[scoringengine] = \
|
||||||
@@ -269,17 +270,17 @@ class Syncer(object):
|
|||||||
# and soft delete stale audits and action plans
|
# and soft delete stale audits and action plans
|
||||||
for stale_audit_template in self.stale_audit_templates_map.values():
|
for stale_audit_template in self.stale_audit_templates_map.values():
|
||||||
stale_audit_template.save()
|
stale_audit_template.save()
|
||||||
LOG.info("Audit Template '%s' synced",
|
LOG.info(_LI("Audit Template '%s' synced"),
|
||||||
stale_audit_template.name)
|
stale_audit_template.name)
|
||||||
|
|
||||||
for stale_audit in self.stale_audits_map.values():
|
for stale_audit in self.stale_audits_map.values():
|
||||||
stale_audit.save()
|
stale_audit.save()
|
||||||
LOG.info("Stale audit '%s' synced and cancelled",
|
LOG.info(_LI("Stale audit '%s' synced and cancelled"),
|
||||||
stale_audit.uuid)
|
stale_audit.uuid)
|
||||||
|
|
||||||
for stale_action_plan in self.stale_action_plans_map.values():
|
for stale_action_plan in self.stale_action_plans_map.values():
|
||||||
stale_action_plan.save()
|
stale_action_plan.save()
|
||||||
LOG.info("Stale action plan '%s' synced and cancelled",
|
LOG.info(_LI("Stale action plan '%s' synced and cancelled"),
|
||||||
stale_action_plan.uuid)
|
stale_action_plan.uuid)
|
||||||
|
|
||||||
def _find_stale_audit_templates_due_to_goal(self):
|
def _find_stale_audit_templates_due_to_goal(self):
|
||||||
@@ -394,15 +395,15 @@ class Syncer(object):
|
|||||||
invalid_ats = objects.AuditTemplate.list(self.ctx, filters=filters)
|
invalid_ats = objects.AuditTemplate.list(self.ctx, filters=filters)
|
||||||
for at in invalid_ats:
|
for at in invalid_ats:
|
||||||
LOG.warning(
|
LOG.warning(
|
||||||
"Audit Template '%(audit_template)s' references a "
|
_LW("Audit Template '%(audit_template)s' references a "
|
||||||
"goal that does not exist", audit_template=at.uuid)
|
"goal that does not exist"), audit_template=at.uuid)
|
||||||
|
|
||||||
stale_audits = objects.Audit.list(
|
stale_audits = objects.Audit.list(
|
||||||
self.ctx, filters=filters, eager=True)
|
self.ctx, filters=filters, eager=True)
|
||||||
for audit in stale_audits:
|
for audit in stale_audits:
|
||||||
LOG.warning(
|
LOG.warning(
|
||||||
"Audit '%(audit)s' references a "
|
_LW("Audit '%(audit)s' references a "
|
||||||
"goal that does not exist", audit=audit.uuid)
|
"goal that does not exist"), audit=audit.uuid)
|
||||||
if audit.id not in self.stale_audits_map:
|
if audit.id not in self.stale_audits_map:
|
||||||
audit.state = objects.audit.State.CANCELLED
|
audit.state = objects.audit.State.CANCELLED
|
||||||
self.stale_audits_map[audit.id] = audit
|
self.stale_audits_map[audit.id] = audit
|
||||||
@@ -421,8 +422,8 @@ class Syncer(object):
|
|||||||
invalid_ats = objects.AuditTemplate.list(self.ctx, filters=filters)
|
invalid_ats = objects.AuditTemplate.list(self.ctx, filters=filters)
|
||||||
for at in invalid_ats:
|
for at in invalid_ats:
|
||||||
LOG.info(
|
LOG.info(
|
||||||
"Audit Template '%(audit_template)s' references a "
|
_LI("Audit Template '%(audit_template)s' references a "
|
||||||
"strategy that does not exist",
|
"strategy that does not exist"),
|
||||||
audit_template=at.uuid)
|
audit_template=at.uuid)
|
||||||
# In this case we can reset the strategy ID to None
|
# In this case we can reset the strategy ID to None
|
||||||
# so the audit template can still achieve the same goal
|
# so the audit template can still achieve the same goal
|
||||||
@@ -437,8 +438,8 @@ class Syncer(object):
|
|||||||
self.ctx, filters=filters, eager=True)
|
self.ctx, filters=filters, eager=True)
|
||||||
for audit in stale_audits:
|
for audit in stale_audits:
|
||||||
LOG.warning(
|
LOG.warning(
|
||||||
"Audit '%(audit)s' references a "
|
_LW("Audit '%(audit)s' references a "
|
||||||
"strategy that does not exist", audit=audit.uuid)
|
"strategy that does not exist"), audit=audit.uuid)
|
||||||
if audit.id not in self.stale_audits_map:
|
if audit.id not in self.stale_audits_map:
|
||||||
audit.state = objects.audit.State.CANCELLED
|
audit.state = objects.audit.State.CANCELLED
|
||||||
self.stale_audits_map[audit.id] = audit
|
self.stale_audits_map[audit.id] = audit
|
||||||
@@ -450,8 +451,8 @@ class Syncer(object):
|
|||||||
self.ctx, filters=filters, eager=True)
|
self.ctx, filters=filters, eager=True)
|
||||||
for action_plan in stale_action_plans:
|
for action_plan in stale_action_plans:
|
||||||
LOG.warning(
|
LOG.warning(
|
||||||
"Action Plan '%(action_plan)s' references a "
|
_LW("Action Plan '%(action_plan)s' references a "
|
||||||
"strategy that does not exist",
|
"strategy that does not exist"),
|
||||||
action_plan=action_plan.uuid)
|
action_plan=action_plan.uuid)
|
||||||
if action_plan.id not in self.stale_action_plans_map:
|
if action_plan.id not in self.stale_action_plans_map:
|
||||||
action_plan.state = objects.action_plan.State.CANCELLED
|
action_plan.state = objects.action_plan.State.CANCELLED
|
||||||
@@ -466,7 +467,7 @@ class Syncer(object):
|
|||||||
se for se in self.available_scoringengines
|
se for se in self.available_scoringengines
|
||||||
if se.name not in self.discovered_map['scoringengines']]
|
if se.name not in self.discovered_map['scoringengines']]
|
||||||
for se in removed_se:
|
for se in removed_se:
|
||||||
LOG.info("Scoring Engine %s removed", se.name)
|
LOG.info(_LI("Scoring Engine %s removed"), se.name)
|
||||||
se.soft_delete()
|
se.soft_delete()
|
||||||
|
|
||||||
def _discover(self):
|
def _discover(self):
|
||||||
@@ -525,9 +526,9 @@ class Syncer(object):
|
|||||||
for matching_goal in matching_goals:
|
for matching_goal in matching_goals:
|
||||||
if (matching_goal.efficacy_specification == goal_efficacy_spec and
|
if (matching_goal.efficacy_specification == goal_efficacy_spec and
|
||||||
matching_goal.display_name == goal_display_name):
|
matching_goal.display_name == goal_display_name):
|
||||||
LOG.info("Goal %s unchanged", goal_name)
|
LOG.info(_LI("Goal %s unchanged"), goal_name)
|
||||||
else:
|
else:
|
||||||
LOG.info("Goal %s modified", goal_name)
|
LOG.info(_LI("Goal %s modified"), goal_name)
|
||||||
matching_goal.soft_delete()
|
matching_goal.soft_delete()
|
||||||
stale_goals.append(matching_goal)
|
stale_goals.append(matching_goal)
|
||||||
|
|
||||||
@@ -544,9 +545,9 @@ class Syncer(object):
|
|||||||
matching_strategy.goal_id not in self.goal_mapping and
|
matching_strategy.goal_id not in self.goal_mapping and
|
||||||
matching_strategy.parameters_spec ==
|
matching_strategy.parameters_spec ==
|
||||||
ast.literal_eval(parameters_spec)):
|
ast.literal_eval(parameters_spec)):
|
||||||
LOG.info("Strategy %s unchanged", strategy_name)
|
LOG.info(_LI("Strategy %s unchanged"), strategy_name)
|
||||||
else:
|
else:
|
||||||
LOG.info("Strategy %s modified", strategy_name)
|
LOG.info(_LI("Strategy %s modified"), strategy_name)
|
||||||
matching_strategy.soft_delete()
|
matching_strategy.soft_delete()
|
||||||
stale_strategies.append(matching_strategy)
|
stale_strategies.append(matching_strategy)
|
||||||
|
|
||||||
@@ -562,9 +563,9 @@ class Syncer(object):
|
|||||||
for matching_scoringengine in matching_scoringengines:
|
for matching_scoringengine in matching_scoringengines:
|
||||||
if (matching_scoringengine.description == se_description and
|
if (matching_scoringengine.description == se_description and
|
||||||
matching_scoringengine.metainfo == se_metainfo):
|
matching_scoringengine.metainfo == se_metainfo):
|
||||||
LOG.info("Scoring Engine %s unchanged", se_name)
|
LOG.info(_LI("Scoring Engine %s unchanged"), se_name)
|
||||||
else:
|
else:
|
||||||
LOG.info("Scoring Engine %s modified", se_name)
|
LOG.info(_LI("Scoring Engine %s modified"), se_name)
|
||||||
matching_scoringengine.soft_delete()
|
matching_scoringengine.soft_delete()
|
||||||
stale_scoringengines.append(matching_scoringengine)
|
stale_scoringengines.append(matching_scoringengine)
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import os
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
import pep8
|
import pep8
|
||||||
|
import six
|
||||||
|
|
||||||
|
|
||||||
def flake8ext(f):
|
def flake8ext(f):
|
||||||
@@ -60,7 +61,7 @@ def _regex_for_level(level, hint):
|
|||||||
|
|
||||||
log_translation_hint = re.compile(
|
log_translation_hint = re.compile(
|
||||||
'|'.join('(?:%s)' % _regex_for_level(level, hint)
|
'|'.join('(?:%s)' % _regex_for_level(level, hint)
|
||||||
for level, hint in _all_log_levels.items()))
|
for level, hint in six.iteritems(_all_log_levels)))
|
||||||
|
|
||||||
log_warn = re.compile(
|
log_warn = re.compile(
|
||||||
r"(.)*LOG\.(warn)\(\s*('|\"|_)")
|
r"(.)*LOG\.(warn)\(\s*('|\"|_)")
|
||||||
|
|||||||
640
watcher/locale/fr/LC_MESSAGES/watcher.po
Normal file
640
watcher/locale/fr/LC_MESSAGES/watcher.po
Normal file
@@ -0,0 +1,640 @@
|
|||||||
|
# French translations for python-watcher.
|
||||||
|
# Copyright (C) 2015 ORGANIZATION
|
||||||
|
# This file is distributed under the same license as the python-watcher
|
||||||
|
# project.
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, 2015.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: python-watcher 0.21.1.dev32\n"
|
||||||
|
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||||
|
"POT-Creation-Date: 2016-02-09 09:07+0100\n"
|
||||||
|
"PO-Revision-Date: 2015-12-11 15:42+0100\n"
|
||||||
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
|
"Language: fr\n"
|
||||||
|
"Language-Team: fr <LL@li.org>\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n > 1)\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=utf-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Generated-By: Babel 2.1.1\n"
|
||||||
|
|
||||||
|
#: watcher/api/controllers/v1/action_plan.py:102
|
||||||
|
#, python-format
|
||||||
|
msgid "Invalid state: %(state)s"
|
||||||
|
msgstr "État invalide : %(state)s"
|
||||||
|
|
||||||
|
#: watcher/api/controllers/v1/action_plan.py:422
|
||||||
|
#, python-format
|
||||||
|
msgid "State transition not allowed: (%(initial_state)s -> %(new_state)s)"
|
||||||
|
msgstr "Transition d'état non autorisée : (%(initial_state)s -> %(new_state)s)"
|
||||||
|
|
||||||
|
#: watcher/api/controllers/v1/audit.py:359
|
||||||
|
msgid "The audit template UUID or name specified is invalid"
|
||||||
|
msgstr "Le nom ou UUID de l'audit template est invalide"
|
||||||
|
|
||||||
|
#: watcher/api/controllers/v1/types.py:148
|
||||||
|
#, python-format
|
||||||
|
msgid "%s is not JSON serializable"
|
||||||
|
msgstr "%s n'est pas sérialisable en JSON"
|
||||||
|
|
||||||
|
#: watcher/api/controllers/v1/types.py:184
|
||||||
|
#, python-format
|
||||||
|
msgid "Wrong type. Expected '%(type)s', got '%(value)s'"
|
||||||
|
msgstr "Type incorrect. '%(type)s' attendu, '%(value)s' obtenu"
|
||||||
|
|
||||||
|
#: watcher/api/controllers/v1/types.py:223
|
||||||
|
#, python-format
|
||||||
|
msgid "'%s' is an internal attribute and can not be updated"
|
||||||
|
msgstr "'%s' wat un attribut interne et ne peut pas être modifié"
|
||||||
|
|
||||||
|
#: watcher/api/controllers/v1/types.py:227
|
||||||
|
#, python-format
|
||||||
|
msgid "'%s' is a mandatory attribute and can not be removed"
|
||||||
|
msgstr "'%s' est un attribut obligatoire et ne peut pas être enlevé"
|
||||||
|
|
||||||
|
#: watcher/api/controllers/v1/types.py:232
|
||||||
|
msgid "'add' and 'replace' operations needs value"
|
||||||
|
msgstr "Les opérations 'add' et 'replace' recquièrent une valeur"
|
||||||
|
|
||||||
|
#: watcher/api/controllers/v1/utils.py:36
|
||||||
|
msgid "Limit must be positive"
|
||||||
|
msgstr "Limit doit être positif"
|
||||||
|
|
||||||
|
#: watcher/api/controllers/v1/utils.py:47
|
||||||
|
#, python-format
|
||||||
|
msgid "Invalid sort direction: %s. Acceptable values are 'asc' or 'desc'"
|
||||||
|
msgstr "Ordre de tri invalide : %s. Les valeurs acceptées sont 'asc' or 'desc'"
|
||||||
|
|
||||||
|
#: watcher/api/controllers/v1/utils.py:57
|
||||||
|
#, python-format
|
||||||
|
msgid "Adding a new attribute (%s) to the root of the resource is not allowed"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/api/middleware/auth_token.py:45
|
||||||
|
msgid "Cannot compile public API routes"
|
||||||
|
msgstr "Ne peut pas compiler les chemins d'API publique"
|
||||||
|
|
||||||
|
#: watcher/api/middleware/parsable_error.py:52
|
||||||
|
#, python-format
|
||||||
|
msgid "ErrorDocumentMiddleware received an invalid status %s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/api/middleware/parsable_error.py:79
|
||||||
|
#, python-format
|
||||||
|
msgid "Error parsing HTTP response: %s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/applier/actions/change_nova_service_state.py:69
|
||||||
|
msgid "The target state is not defined"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/applier/actions/migration.py:43
|
||||||
|
msgid "The parameter resource_id is invalid."
|
||||||
|
msgstr "Le paramètre resource_id est invalide"
|
||||||
|
|
||||||
|
#: watcher/applier/actions/migration.py:86
|
||||||
|
#, python-format
|
||||||
|
msgid "Migration of type %(migration_type)s is not supported."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/applier/workflow_engine/default.py:128
|
||||||
|
#, python-format
|
||||||
|
msgid "The WorkFlow Engine has failed to execute the action %s"
|
||||||
|
msgstr "Le moteur de workflow a echoué lors de l'éxécution de l'action %s"
|
||||||
|
|
||||||
|
#: watcher/applier/workflow_engine/default.py:146
|
||||||
|
#, python-format
|
||||||
|
msgid "Revert action %s"
|
||||||
|
msgstr "Annulation de l'action %s"
|
||||||
|
|
||||||
|
#: watcher/applier/workflow_engine/default.py:152
|
||||||
|
msgid "Oops! We need disaster recover plan"
|
||||||
|
msgstr "Oops! Nous avons besoin d'un plan de reprise d'activité"
|
||||||
|
|
||||||
|
#: watcher/cmd/api.py:46 watcher/cmd/applier.py:39
|
||||||
|
#: watcher/cmd/decisionengine.py:40
|
||||||
|
#, python-format
|
||||||
|
msgid "Starting server in PID %s"
|
||||||
|
msgstr "Démarre le serveur avec pour PID %s"
|
||||||
|
|
||||||
|
#: watcher/cmd/api.py:51
|
||||||
|
#, python-format
|
||||||
|
msgid "serving on 0.0.0.0:%(port)s, view at http://127.0.0.1:%(port)s"
|
||||||
|
msgstr "Sert sur 0.0.0.0:%(port)s, accessible à http://127.0.0.1:%(port)s"
|
||||||
|
|
||||||
|
#: watcher/cmd/api.py:55
|
||||||
|
#, python-format
|
||||||
|
msgid "serving on http://%(host)s:%(port)s"
|
||||||
|
msgstr "Sert sur http://%(host)s:%(port)s"
|
||||||
|
|
||||||
|
#: watcher/common/clients.py:29
|
||||||
|
msgid "Version of Nova API to use in novaclient."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/common/clients.py:34
|
||||||
|
msgid "Version of Glance API to use in glanceclient."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/common/clients.py:39
|
||||||
|
msgid "Version of Cinder API to use in cinderclient."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/common/clients.py:44
|
||||||
|
msgid "Version of Ceilometer API to use in ceilometerclient."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/common/clients.py:50
|
||||||
|
msgid "Version of Neutron API to use in neutronclient."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/common/exception.py:59
|
||||||
|
#, python-format
|
||||||
|
msgid "Unexpected keystone client error occurred: %s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/common/exception.py:72
|
||||||
|
msgid "An unknown exception occurred"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/common/exception.py:92
|
||||||
|
msgid "Exception in string format operation"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/common/exception.py:122
|
||||||
|
msgid "Not authorized"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/common/exception.py:127
|
||||||
|
msgid "Operation not permitted"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/common/exception.py:131
|
||||||
|
msgid "Unacceptable parameters"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/common/exception.py:136
|
||||||
|
#, python-format
|
||||||
|
msgid "The %(name)s %(id)s could not be found"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/common/exception.py:140
|
||||||
|
#, fuzzy
|
||||||
|
msgid "Conflict"
|
||||||
|
msgstr "Conflit"
|
||||||
|
|
||||||
|
#: watcher/common/exception.py:145
|
||||||
|
#, python-format
|
||||||
|
msgid "The %(name)s resource %(id)s could not be found"
|
||||||
|
msgstr "La ressource %(name)s / %(id)s est introuvable"
|
||||||
|
|
||||||
|
#: watcher/common/exception.py:150
|
||||||
|
#, python-format
|
||||||
|
msgid "Expected an uuid or int but received %(identity)s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/common/exception.py:154
|
||||||
|
#, python-format
|
||||||
|
msgid "Goal %(goal)s is not defined in Watcher configuration file"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/common/exception.py:158
|
||||||
|
#, python-format
|
||||||
|
msgid "Expected a uuid but received %(uuid)s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/common/exception.py:162
|
||||||
|
#, python-format
|
||||||
|
msgid "Expected a logical name but received %(name)s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/common/exception.py:166
|
||||||
|
#, python-format
|
||||||
|
msgid "Expected a logical name or uuid but received %(name)s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/common/exception.py:170
|
||||||
|
#, python-format
|
||||||
|
msgid "AuditTemplate %(audit_template)s could not be found"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/common/exception.py:174
|
||||||
|
#, python-format
|
||||||
|
msgid "An audit_template with UUID %(uuid)s or name %(name)s already exists"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/common/exception.py:179
|
||||||
|
#, python-format
|
||||||
|
msgid "AuditTemplate %(audit_template)s is referenced by one or multiple audit"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/common/exception.py:184
|
||||||
|
#, python-format
|
||||||
|
msgid "Audit %(audit)s could not be found"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/common/exception.py:188
|
||||||
|
#, python-format
|
||||||
|
msgid "An audit with UUID %(uuid)s already exists"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/common/exception.py:192
|
||||||
|
#, python-format
|
||||||
|
msgid "Audit %(audit)s is referenced by one or multiple action plans"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/common/exception.py:197
|
||||||
|
#, python-format
|
||||||
|
msgid "ActionPlan %(action_plan)s could not be found"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/common/exception.py:201
|
||||||
|
#, python-format
|
||||||
|
msgid "An action plan with UUID %(uuid)s already exists"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/common/exception.py:205
|
||||||
|
#, python-format
|
||||||
|
msgid "Action Plan %(action_plan)s is referenced by one or multiple actions"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/common/exception.py:210
|
||||||
|
#, python-format
|
||||||
|
msgid "Action %(action)s could not be found"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/common/exception.py:214
|
||||||
|
#, python-format
|
||||||
|
msgid "An action with UUID %(uuid)s already exists"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/common/exception.py:218
|
||||||
|
#, python-format
|
||||||
|
msgid "Action plan %(action_plan)s is referenced by one or multiple goals"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/common/exception.py:223
|
||||||
|
msgid "Filtering actions on both audit and action-plan is prohibited"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/common/exception.py:232
|
||||||
|
#, python-format
|
||||||
|
msgid "Couldn't apply patch '%(patch)s'. Reason: %(reason)s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/common/exception.py:239
|
||||||
|
msgid "Illegal argument"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/common/exception.py:243
|
||||||
|
msgid "No such metric"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/common/exception.py:247
|
||||||
|
msgid "No rows were returned"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/common/exception.py:251
|
||||||
|
#, python-format
|
||||||
|
msgid "%(client)s connection failed. Reason: %(reason)s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/common/exception.py:255
|
||||||
|
msgid "'Keystone API endpoint is missing''"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/common/exception.py:259
|
||||||
|
msgid "The list of hypervisor(s) in the cluster is empty"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/common/exception.py:263
|
||||||
|
msgid "The metrics resource collector is not defined"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/common/exception.py:267
|
||||||
|
msgid "the cluster state is not defined"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/common/exception.py:273
|
||||||
|
#, python-format
|
||||||
|
msgid "The instance '%(name)s' is not found"
|
||||||
|
msgstr "L'instance '%(name)s' n'a pas été trouvée"
|
||||||
|
|
||||||
|
#: watcher/common/exception.py:277
|
||||||
|
msgid "The hypervisor is not found"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/common/exception.py:281
|
||||||
|
#, fuzzy, python-format
|
||||||
|
msgid "Error loading plugin '%(name)s'"
|
||||||
|
msgstr "Erreur lors du chargement du module '%(name)s'"
|
||||||
|
|
||||||
|
#: watcher/common/exception.py:285
|
||||||
|
#, fuzzy, python-format
|
||||||
|
msgid "The identifier '%(name)s' is a reserved word"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/common/service.py:83
|
||||||
|
#, python-format
|
||||||
|
msgid "Created RPC server for service %(service)s on host %(host)s."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/common/service.py:92
|
||||||
|
#, python-format
|
||||||
|
msgid "Service error occurred when stopping the RPC server. Error: %s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/common/service.py:97
|
||||||
|
#, python-format
|
||||||
|
msgid "Service error occurred when cleaning up the RPC manager. Error: %s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/common/service.py:101
|
||||||
|
#, python-format
|
||||||
|
msgid "Stopped RPC server for service %(service)s on host %(host)s."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/common/service.py:106
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"Got signal SIGUSR1. Not deregistering on next shutdown of service "
|
||||||
|
"%(service)s on host %(host)s."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/common/utils.py:53
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"Failed to remove trailing character. Returning original object.Supplied "
|
||||||
|
"object is not a string: %s,"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/common/messaging/messaging_handler.py:98
|
||||||
|
msgid "No endpoint defined; can only publish events"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/common/messaging/messaging_handler.py:101
|
||||||
|
msgid "Messaging configuration error"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/db/sqlalchemy/api.py:256
|
||||||
|
msgid ""
|
||||||
|
"Multiple audit templates exist with the same name. Please use the audit "
|
||||||
|
"template uuid instead"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/db/sqlalchemy/api.py:278
|
||||||
|
msgid "Cannot overwrite UUID for an existing Audit Template."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/db/sqlalchemy/api.py:388
|
||||||
|
msgid "Cannot overwrite UUID for an existing Audit."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/db/sqlalchemy/api.py:480
|
||||||
|
msgid "Cannot overwrite UUID for an existing Action."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/db/sqlalchemy/api.py:590
|
||||||
|
msgid "Cannot overwrite UUID for an existing Action Plan."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/db/sqlalchemy/migration.py:73
|
||||||
|
msgid ""
|
||||||
|
"Watcher database schema is already under version control; use upgrade() "
|
||||||
|
"instead"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/decision_engine/model/model_root.py:37
|
||||||
|
#: watcher/decision_engine/model/model_root.py:42
|
||||||
|
msgid "'obj' argument type is not valid"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/decision_engine/planner/default.py:72
|
||||||
|
msgid "The action plan is empty"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/decision_engine/strategy/selection/default.py:60
|
||||||
|
#, python-format
|
||||||
|
msgid "Incorrect mapping: could not find associated strategy for '%s'"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/decision_engine/strategy/strategies/basic_consolidation.py:269
|
||||||
|
#: watcher/decision_engine/strategy/strategies/basic_consolidation.py:316
|
||||||
|
#, python-format
|
||||||
|
msgid "No values returned by %(resource_id)s for %(metric_name)s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/decision_engine/strategy/strategies/basic_consolidation.py:426
|
||||||
|
msgid "Initializing Server Consolidation"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/decision_engine/strategy/strategies/basic_consolidation.py:470
|
||||||
|
msgid "The workloads of the compute nodes of the cluster is zero"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/decision_engine/strategy/strategies/outlet_temp_control.py:127
|
||||||
|
#, python-format
|
||||||
|
msgid "%s: no outlet temp data"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/decision_engine/strategy/strategies/outlet_temp_control.py:151
|
||||||
|
#, python-format
|
||||||
|
msgid "VM not active, skipped: %s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/decision_engine/strategy/strategies/outlet_temp_control.py:208
|
||||||
|
msgid "No hosts under outlet temp threshold found"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/decision_engine/strategy/strategies/outlet_temp_control.py:231
|
||||||
|
msgid "No proper target host could be found"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/objects/base.py:70
|
||||||
|
#, python-format
|
||||||
|
msgid "Error setting %(attr)s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/objects/base.py:108
|
||||||
|
msgid "Invalid version string"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/objects/base.py:172
|
||||||
|
#, python-format
|
||||||
|
msgid "Unable to instantiate unregistered object type %(objtype)s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/objects/base.py:299
|
||||||
|
#, python-format
|
||||||
|
msgid "Cannot load '%(attrname)s' in the base class"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/objects/base.py:308
|
||||||
|
msgid "Cannot save anything in the base class"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/objects/base.py:340
|
||||||
|
#, python-format
|
||||||
|
msgid "%(objname)s object has no attribute '%(attrname)s'"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/objects/base.py:390
|
||||||
|
#, python-format
|
||||||
|
msgid "'%(objclass)s' object has no attribute '%(attrname)s'"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/objects/utils.py:40
|
||||||
|
msgid "A datetime.datetime is required here"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: watcher/objects/utils.py:105
|
||||||
|
#, python-format
|
||||||
|
msgid "An object of class %s is required here"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#~ msgid "Cannot compile public API routes: %s"
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ msgid "An exception occurred without a description."
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ msgid "no rows were returned"
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ msgid ""
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ msgid "An unknown exception occurred."
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ msgid "Not authorized."
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ msgid "Operation not permitted."
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ msgid "Unacceptable parameters."
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ msgid "The %(name)s %(id)s could not be found."
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ msgid "The %(name)s resource %(id)s could not be found."
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ msgid "Expected an uuid or int but received %(identity)s."
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ msgid "Goal %(goal)s is not defined in Watcher configuration file."
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ msgid "Expected a uuid but received %(uuid)s."
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ msgid "Expected a logical name but received %(name)s."
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ msgid "Expected a logical name or uuid but received %(name)s."
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ msgid "AuditTemplate %(audit_template)s could not be found."
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ msgid "An audit_template with UUID %(uuid)s or name %(name)s already exists."
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ msgid "Audit %(audit)s could not be found."
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ msgid "An audit with UUID %(uuid)s already exists."
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ msgid "Audit %(audit)s is referenced by one or multiple action plans."
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ msgid "ActionPlan %(action plan)s could not be found."
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ msgid "An action plan with UUID %(uuid)s already exists."
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ msgid "Action Plan %(action_plan)s is referenced by one or multiple actions."
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ msgid "Action %(action)s could not be found."
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ msgid "An action with UUID %(uuid)s already exists."
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ msgid "Action plan %(action_plan)s is referenced by one or multiple goals."
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ msgid "Filtering actions on both audit and action-plan is prohibited."
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ msgid "The list of hypervisor(s) in the cluster is empty.'"
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ msgid "The metrics resource collector is not defined.'"
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ msgid "The VM could not be found."
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ msgid "The hypervisor could not be found."
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ msgid "The Meta-Action could not be found."
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ msgid "'hypervisor' argument type is not valid"
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ msgid "'vm' argument type is not valid"
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ msgid "The Meta-Action could not be found"
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ msgid "The VM could not be found"
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ msgid "The hypervisor could not be found"
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ msgid "Trigger a rollback"
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ msgid "The WorkFlow Engine has failedto execute the action %s"
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ msgid "ActionPlan %(action plan)s could not be found"
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ msgid "Description must be an instance of str"
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ msgid "An exception occurred without a description"
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ msgid "Description cannot be empty"
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ msgid "The hypervisor state is invalid."
|
||||||
|
#~ msgstr "L'état de l'hyperviseur est invalide"
|
||||||
|
|
||||||
|
#~ msgid "%(err)s"
|
||||||
|
#~ msgstr "%(err)s"
|
||||||
|
|
||||||
|
#~ msgid "No Keystone service catalog loaded"
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ msgid "Cannot overwrite UUID for an existing AuditTemplate."
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
|
#~ msgid ""
|
||||||
|
#~ "This identifier is reserved word and "
|
||||||
|
#~ "cannot be used as variables '%(name)s'"
|
||||||
|
#~ msgstr ""
|
||||||
|
|
||||||
@@ -20,7 +20,6 @@
|
|||||||
# need to be changed after we moved these function inside the package
|
# need to be changed after we moved these function inside the package
|
||||||
# Todo(gibi): remove these imports after legacy notifications using these are
|
# Todo(gibi): remove these imports after legacy notifications using these are
|
||||||
# transformed to versioned notifications
|
# transformed to versioned notifications
|
||||||
from watcher.notifications import action # noqa
|
|
||||||
from watcher.notifications import action_plan # noqa
|
from watcher.notifications import action_plan # noqa
|
||||||
from watcher.notifications import audit # noqa
|
from watcher.notifications import audit # noqa
|
||||||
from watcher.notifications import exception # noqa
|
from watcher.notifications import exception # noqa
|
||||||
|
|||||||
@@ -1,302 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
# Copyright (c) 2017 Servionica
|
|
||||||
#
|
|
||||||
# Authors: Alexander Chadin <a.chadin@servionica.ru>
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
# implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
from oslo_config import cfg
|
|
||||||
|
|
||||||
from watcher.common import context as wcontext
|
|
||||||
from watcher.common import exception
|
|
||||||
from watcher.notifications import action_plan as ap_notifications
|
|
||||||
from watcher.notifications import base as notificationbase
|
|
||||||
from watcher.notifications import exception as exception_notifications
|
|
||||||
from watcher import objects
|
|
||||||
from watcher.objects import base
|
|
||||||
from watcher.objects import fields as wfields
|
|
||||||
|
|
||||||
CONF = cfg.CONF
|
|
||||||
|
|
||||||
|
|
||||||
@base.WatcherObjectRegistry.register_notification
|
|
||||||
class ActionPayload(notificationbase.NotificationPayloadBase):
|
|
||||||
SCHEMA = {
|
|
||||||
'uuid': ('action', 'uuid'),
|
|
||||||
|
|
||||||
'action_type': ('action', 'action_type'),
|
|
||||||
'input_parameters': ('action', 'input_parameters'),
|
|
||||||
'state': ('action', 'state'),
|
|
||||||
'parents': ('action', 'parents'),
|
|
||||||
|
|
||||||
'created_at': ('action', 'created_at'),
|
|
||||||
'updated_at': ('action', 'updated_at'),
|
|
||||||
'deleted_at': ('action', 'deleted_at'),
|
|
||||||
}
|
|
||||||
|
|
||||||
# Version 1.0: Initial version
|
|
||||||
VERSION = '1.0'
|
|
||||||
|
|
||||||
fields = {
|
|
||||||
'uuid': wfields.UUIDField(),
|
|
||||||
'action_type': wfields.StringField(nullable=False),
|
|
||||||
'input_parameters': wfields.DictField(nullable=False, default={}),
|
|
||||||
'state': wfields.StringField(nullable=False),
|
|
||||||
'parents': wfields.ListOfUUIDsField(nullable=False, default=[]),
|
|
||||||
'action_plan_uuid': wfields.UUIDField(),
|
|
||||||
'action_plan': wfields.ObjectField('TerseActionPlanPayload'),
|
|
||||||
|
|
||||||
'created_at': wfields.DateTimeField(nullable=True),
|
|
||||||
'updated_at': wfields.DateTimeField(nullable=True),
|
|
||||||
'deleted_at': wfields.DateTimeField(nullable=True),
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, action, **kwargs):
|
|
||||||
super(ActionPayload, self).__init__(**kwargs)
|
|
||||||
self.populate_schema(action=action)
|
|
||||||
|
|
||||||
|
|
||||||
@base.WatcherObjectRegistry.register_notification
|
|
||||||
class ActionStateUpdatePayload(notificationbase.NotificationPayloadBase):
|
|
||||||
# Version 1.0: Initial version
|
|
||||||
VERSION = '1.0'
|
|
||||||
|
|
||||||
fields = {
|
|
||||||
'old_state': wfields.StringField(nullable=True),
|
|
||||||
'state': wfields.StringField(nullable=True),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@base.WatcherObjectRegistry.register_notification
|
|
||||||
class ActionCreatePayload(ActionPayload):
|
|
||||||
# Version 1.0: Initial version
|
|
||||||
VERSION = '1.0'
|
|
||||||
fields = {}
|
|
||||||
|
|
||||||
def __init__(self, action, action_plan):
|
|
||||||
super(ActionCreatePayload, self).__init__(
|
|
||||||
action=action,
|
|
||||||
action_plan=action_plan)
|
|
||||||
|
|
||||||
|
|
||||||
@base.WatcherObjectRegistry.register_notification
|
|
||||||
class ActionUpdatePayload(ActionPayload):
|
|
||||||
# Version 1.0: Initial version
|
|
||||||
VERSION = '1.0'
|
|
||||||
fields = {
|
|
||||||
'state_update': wfields.ObjectField('ActionStateUpdatePayload'),
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, action, state_update, action_plan):
|
|
||||||
super(ActionUpdatePayload, self).__init__(
|
|
||||||
action=action,
|
|
||||||
state_update=state_update,
|
|
||||||
action_plan=action_plan)
|
|
||||||
|
|
||||||
|
|
||||||
@base.WatcherObjectRegistry.register_notification
|
|
||||||
class ActionExecutionPayload(ActionPayload):
|
|
||||||
# Version 1.0: Initial version
|
|
||||||
VERSION = '1.0'
|
|
||||||
fields = {
|
|
||||||
'fault': wfields.ObjectField('ExceptionPayload', nullable=True),
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, action, action_plan, **kwargs):
|
|
||||||
super(ActionExecutionPayload, self).__init__(
|
|
||||||
action=action,
|
|
||||||
action_plan=action_plan,
|
|
||||||
**kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
@base.WatcherObjectRegistry.register_notification
|
|
||||||
class ActionDeletePayload(ActionPayload):
|
|
||||||
# Version 1.0: Initial version
|
|
||||||
VERSION = '1.0'
|
|
||||||
fields = {}
|
|
||||||
|
|
||||||
def __init__(self, action, action_plan):
|
|
||||||
super(ActionDeletePayload, self).__init__(
|
|
||||||
action=action,
|
|
||||||
action_plan=action_plan)
|
|
||||||
|
|
||||||
|
|
||||||
@notificationbase.notification_sample('action-execution-error.json')
|
|
||||||
@notificationbase.notification_sample('action-execution-end.json')
|
|
||||||
@notificationbase.notification_sample('action-execution-start.json')
|
|
||||||
@base.WatcherObjectRegistry.register_notification
|
|
||||||
class ActionExecutionNotification(notificationbase.NotificationBase):
|
|
||||||
# Version 1.0: Initial version
|
|
||||||
VERSION = '1.0'
|
|
||||||
|
|
||||||
fields = {
|
|
||||||
'payload': wfields.ObjectField('ActionExecutionPayload')
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@notificationbase.notification_sample('action-create.json')
|
|
||||||
@base.WatcherObjectRegistry.register_notification
|
|
||||||
class ActionCreateNotification(notificationbase.NotificationBase):
|
|
||||||
# Version 1.0: Initial version
|
|
||||||
VERSION = '1.0'
|
|
||||||
|
|
||||||
fields = {
|
|
||||||
'payload': wfields.ObjectField('ActionCreatePayload')
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@notificationbase.notification_sample('action-update.json')
|
|
||||||
@base.WatcherObjectRegistry.register_notification
|
|
||||||
class ActionUpdateNotification(notificationbase.NotificationBase):
|
|
||||||
# Version 1.0: Initial version
|
|
||||||
VERSION = '1.0'
|
|
||||||
|
|
||||||
fields = {
|
|
||||||
'payload': wfields.ObjectField('ActionUpdatePayload')
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@notificationbase.notification_sample('action-delete.json')
|
|
||||||
@base.WatcherObjectRegistry.register_notification
|
|
||||||
class ActionDeleteNotification(notificationbase.NotificationBase):
|
|
||||||
# Version 1.0: Initial version
|
|
||||||
VERSION = '1.0'
|
|
||||||
|
|
||||||
fields = {
|
|
||||||
'payload': wfields.ObjectField('ActionDeletePayload')
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def _get_action_plan_payload(action):
|
|
||||||
action_plan = None
|
|
||||||
strategy_uuid = None
|
|
||||||
audit = None
|
|
||||||
try:
|
|
||||||
action_plan = action.action_plan
|
|
||||||
audit = objects.Audit.get(wcontext.make_context(show_deleted=True),
|
|
||||||
action_plan.audit_id)
|
|
||||||
if audit.strategy_id:
|
|
||||||
strategy_uuid = objects.Strategy.get(
|
|
||||||
wcontext.make_context(show_deleted=True),
|
|
||||||
audit.strategy_id).uuid
|
|
||||||
except NotImplementedError:
|
|
||||||
raise exception.EagerlyLoadedActionRequired(action=action.uuid)
|
|
||||||
|
|
||||||
action_plan_payload = ap_notifications.TerseActionPlanPayload(
|
|
||||||
action_plan=action_plan,
|
|
||||||
audit_uuid=audit.uuid, strategy_uuid=strategy_uuid)
|
|
||||||
|
|
||||||
return action_plan_payload
|
|
||||||
|
|
||||||
|
|
||||||
def send_create(context, action, service='infra-optim', host=None):
|
|
||||||
"""Emit an action.create notification."""
|
|
||||||
action_plan_payload = _get_action_plan_payload(action)
|
|
||||||
|
|
||||||
versioned_payload = ActionCreatePayload(
|
|
||||||
action=action,
|
|
||||||
action_plan=action_plan_payload,
|
|
||||||
)
|
|
||||||
|
|
||||||
notification = ActionCreateNotification(
|
|
||||||
priority=wfields.NotificationPriority.INFO,
|
|
||||||
event_type=notificationbase.EventType(
|
|
||||||
object='action',
|
|
||||||
action=wfields.NotificationAction.CREATE),
|
|
||||||
publisher=notificationbase.NotificationPublisher(
|
|
||||||
host=host or CONF.host,
|
|
||||||
binary=service),
|
|
||||||
payload=versioned_payload)
|
|
||||||
|
|
||||||
notification.emit(context)
|
|
||||||
|
|
||||||
|
|
||||||
def send_update(context, action, service='infra-optim',
|
|
||||||
host=None, old_state=None):
|
|
||||||
"""Emit an action.update notification."""
|
|
||||||
action_plan_payload = _get_action_plan_payload(action)
|
|
||||||
|
|
||||||
state_update = ActionStateUpdatePayload(
|
|
||||||
old_state=old_state,
|
|
||||||
state=action.state if old_state else None)
|
|
||||||
|
|
||||||
versioned_payload = ActionUpdatePayload(
|
|
||||||
action=action,
|
|
||||||
state_update=state_update,
|
|
||||||
action_plan=action_plan_payload,
|
|
||||||
)
|
|
||||||
|
|
||||||
notification = ActionUpdateNotification(
|
|
||||||
priority=wfields.NotificationPriority.INFO,
|
|
||||||
event_type=notificationbase.EventType(
|
|
||||||
object='action',
|
|
||||||
action=wfields.NotificationAction.UPDATE),
|
|
||||||
publisher=notificationbase.NotificationPublisher(
|
|
||||||
host=host or CONF.host,
|
|
||||||
binary=service),
|
|
||||||
payload=versioned_payload)
|
|
||||||
|
|
||||||
notification.emit(context)
|
|
||||||
|
|
||||||
|
|
||||||
def send_delete(context, action, service='infra-optim', host=None):
|
|
||||||
"""Emit an action.delete notification."""
|
|
||||||
action_plan_payload = _get_action_plan_payload(action)
|
|
||||||
|
|
||||||
versioned_payload = ActionDeletePayload(
|
|
||||||
action=action,
|
|
||||||
action_plan=action_plan_payload,
|
|
||||||
)
|
|
||||||
|
|
||||||
notification = ActionDeleteNotification(
|
|
||||||
priority=wfields.NotificationPriority.INFO,
|
|
||||||
event_type=notificationbase.EventType(
|
|
||||||
object='action',
|
|
||||||
action=wfields.NotificationAction.DELETE),
|
|
||||||
publisher=notificationbase.NotificationPublisher(
|
|
||||||
host=host or CONF.host,
|
|
||||||
binary=service),
|
|
||||||
payload=versioned_payload)
|
|
||||||
|
|
||||||
notification.emit(context)
|
|
||||||
|
|
||||||
|
|
||||||
def send_execution_notification(context, action, notification_action, phase,
|
|
||||||
priority=wfields.NotificationPriority.INFO,
|
|
||||||
service='infra-optim', host=None):
|
|
||||||
"""Emit an action execution notification."""
|
|
||||||
action_plan_payload = _get_action_plan_payload(action)
|
|
||||||
|
|
||||||
fault = None
|
|
||||||
if phase == wfields.NotificationPhase.ERROR:
|
|
||||||
fault = exception_notifications.ExceptionPayload.from_exception()
|
|
||||||
|
|
||||||
versioned_payload = ActionExecutionPayload(
|
|
||||||
action=action,
|
|
||||||
action_plan=action_plan_payload,
|
|
||||||
fault=fault,
|
|
||||||
)
|
|
||||||
|
|
||||||
notification = ActionExecutionNotification(
|
|
||||||
priority=priority,
|
|
||||||
event_type=notificationbase.EventType(
|
|
||||||
object='action',
|
|
||||||
action=notification_action,
|
|
||||||
phase=phase),
|
|
||||||
publisher=notificationbase.NotificationPublisher(
|
|
||||||
host=host or CONF.host,
|
|
||||||
binary=service),
|
|
||||||
payload=versioned_payload)
|
|
||||||
|
|
||||||
notification.emit(context)
|
|
||||||
@@ -32,12 +32,14 @@ CONF = cfg.CONF
|
|||||||
|
|
||||||
|
|
||||||
@base.WatcherObjectRegistry.register_notification
|
@base.WatcherObjectRegistry.register_notification
|
||||||
class TerseActionPlanPayload(notificationbase.NotificationPayloadBase):
|
class ActionPlanPayload(notificationbase.NotificationPayloadBase):
|
||||||
SCHEMA = {
|
SCHEMA = {
|
||||||
'uuid': ('action_plan', 'uuid'),
|
'uuid': ('action_plan', 'uuid'),
|
||||||
|
|
||||||
'state': ('action_plan', 'state'),
|
'state': ('action_plan', 'state'),
|
||||||
'global_efficacy': ('action_plan', 'global_efficacy'),
|
'global_efficacy': ('action_plan', 'global_efficacy'),
|
||||||
|
'audit_uuid': ('audit', 'uuid'),
|
||||||
|
'strategy_uuid': ('strategy', 'uuid'),
|
||||||
|
|
||||||
'created_at': ('action_plan', 'created_at'),
|
'created_at': ('action_plan', 'created_at'),
|
||||||
'updated_at': ('action_plan', 'updated_at'),
|
'updated_at': ('action_plan', 'updated_at'),
|
||||||
@@ -52,50 +54,20 @@ class TerseActionPlanPayload(notificationbase.NotificationPayloadBase):
|
|||||||
'state': wfields.StringField(),
|
'state': wfields.StringField(),
|
||||||
'global_efficacy': wfields.FlexibleDictField(nullable=True),
|
'global_efficacy': wfields.FlexibleDictField(nullable=True),
|
||||||
'audit_uuid': wfields.UUIDField(),
|
'audit_uuid': wfields.UUIDField(),
|
||||||
'strategy_uuid': wfields.UUIDField(nullable=True),
|
'strategy_uuid': wfields.UUIDField(),
|
||||||
|
'audit': wfields.ObjectField('TerseAuditPayload'),
|
||||||
|
'strategy': wfields.ObjectField('StrategyPayload'),
|
||||||
|
|
||||||
'created_at': wfields.DateTimeField(nullable=True),
|
'created_at': wfields.DateTimeField(nullable=True),
|
||||||
'updated_at': wfields.DateTimeField(nullable=True),
|
'updated_at': wfields.DateTimeField(nullable=True),
|
||||||
'deleted_at': wfields.DateTimeField(nullable=True),
|
'deleted_at': wfields.DateTimeField(nullable=True),
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, action_plan, audit=None, strategy=None, **kwargs):
|
|
||||||
super(TerseActionPlanPayload, self).__init__(audit=audit,
|
|
||||||
strategy=strategy,
|
|
||||||
**kwargs)
|
|
||||||
self.populate_schema(action_plan=action_plan)
|
|
||||||
|
|
||||||
|
|
||||||
@base.WatcherObjectRegistry.register_notification
|
|
||||||
class ActionPlanPayload(TerseActionPlanPayload):
|
|
||||||
SCHEMA = {
|
|
||||||
'uuid': ('action_plan', 'uuid'),
|
|
||||||
|
|
||||||
'state': ('action_plan', 'state'),
|
|
||||||
'global_efficacy': ('action_plan', 'global_efficacy'),
|
|
||||||
|
|
||||||
'created_at': ('action_plan', 'created_at'),
|
|
||||||
'updated_at': ('action_plan', 'updated_at'),
|
|
||||||
'deleted_at': ('action_plan', 'deleted_at'),
|
|
||||||
}
|
|
||||||
|
|
||||||
# Version 1.0: Initial version
|
|
||||||
VERSION = '1.0'
|
|
||||||
|
|
||||||
fields = {
|
|
||||||
'audit': wfields.ObjectField('TerseAuditPayload'),
|
|
||||||
'strategy': wfields.ObjectField('StrategyPayload'),
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, action_plan, audit, strategy, **kwargs):
|
def __init__(self, action_plan, audit, strategy, **kwargs):
|
||||||
if not kwargs.get('audit_uuid'):
|
|
||||||
kwargs['audit_uuid'] = audit.uuid
|
|
||||||
|
|
||||||
if strategy and not kwargs.get('strategy_uuid'):
|
|
||||||
kwargs['strategy_uuid'] = strategy.uuid
|
|
||||||
|
|
||||||
super(ActionPlanPayload, self).__init__(
|
super(ActionPlanPayload, self).__init__(
|
||||||
action_plan, audit=audit, strategy=strategy, **kwargs)
|
audit=audit, strategy=strategy, **kwargs)
|
||||||
|
self.populate_schema(
|
||||||
|
action_plan=action_plan, audit=audit, strategy=strategy)
|
||||||
|
|
||||||
|
|
||||||
@base.WatcherObjectRegistry.register_notification
|
@base.WatcherObjectRegistry.register_notification
|
||||||
|
|||||||
@@ -198,7 +198,7 @@ class NotificationBase(NotificationObject):
|
|||||||
|
|
||||||
|
|
||||||
def notification_sample(sample):
|
def notification_sample(sample):
|
||||||
"""Provide a notification sample of the decorated notification.
|
"""Provide a notification sample of the decatorated notification.
|
||||||
|
|
||||||
Class decorator to attach the notification sample information
|
Class decorator to attach the notification sample information
|
||||||
to the notification object for documentation generation purposes.
|
to the notification object for documentation generation purposes.
|
||||||
|
|||||||
@@ -17,7 +17,6 @@
|
|||||||
from watcher.common import exception
|
from watcher.common import exception
|
||||||
from watcher.common import utils
|
from watcher.common import utils
|
||||||
from watcher.db import api as db_api
|
from watcher.db import api as db_api
|
||||||
from watcher import notifications
|
|
||||||
from watcher import objects
|
from watcher import objects
|
||||||
from watcher.objects import base
|
from watcher.objects import base
|
||||||
from watcher.objects import fields as wfields
|
from watcher.objects import fields as wfields
|
||||||
@@ -135,8 +134,6 @@ class Action(base.WatcherPersistentObject, base.WatcherObject,
|
|||||||
# notifications containing information about the related relationships
|
# notifications containing information about the related relationships
|
||||||
self._from_db_object(self, db_action, eager=True)
|
self._from_db_object(self, db_action, eager=True)
|
||||||
|
|
||||||
notifications.action.send_create(self.obj_context, self)
|
|
||||||
|
|
||||||
def destroy(self):
|
def destroy(self):
|
||||||
"""Delete the Action from the DB"""
|
"""Delete the Action from the DB"""
|
||||||
self.dbapi.destroy_action(self.uuid)
|
self.dbapi.destroy_action(self.uuid)
|
||||||
@@ -153,7 +150,6 @@ class Action(base.WatcherPersistentObject, base.WatcherObject,
|
|||||||
db_obj = self.dbapi.update_action(self.uuid, updates)
|
db_obj = self.dbapi.update_action(self.uuid, updates)
|
||||||
obj = self._from_db_object(self, db_obj, eager=False)
|
obj = self._from_db_object(self, db_obj, eager=False)
|
||||||
self.obj_refresh(obj)
|
self.obj_refresh(obj)
|
||||||
notifications.action.send_update(self.obj_context, self)
|
|
||||||
self.obj_reset_changes()
|
self.obj_reset_changes()
|
||||||
|
|
||||||
@base.remotable
|
@base.remotable
|
||||||
@@ -177,5 +173,3 @@ class Action(base.WatcherPersistentObject, base.WatcherObject,
|
|||||||
obj = self._from_db_object(
|
obj = self._from_db_object(
|
||||||
self.__class__(self._context), db_obj, eager=False)
|
self.__class__(self._context), db_obj, eager=False)
|
||||||
self.obj_refresh(obj)
|
self.obj_refresh(obj)
|
||||||
|
|
||||||
notifications.action.send_delete(self.obj_context, self)
|
|
||||||
|
|||||||
@@ -67,23 +67,16 @@ state may be one of the following:
|
|||||||
- **CANCELLED** : the :ref:`Action Plan <action_plan_definition>` was in
|
- **CANCELLED** : the :ref:`Action Plan <action_plan_definition>` was in
|
||||||
**PENDING** or **ONGOING** state and was cancelled by the
|
**PENDING** or **ONGOING** state and was cancelled by the
|
||||||
:ref:`Administrator <administrator_definition>`
|
:ref:`Administrator <administrator_definition>`
|
||||||
- **SUPERSEDED** : the :ref:`Action Plan <action_plan_definition>` was in
|
|
||||||
**RECOMMENDED** state and was superseded by the
|
|
||||||
:ref:`Administrator <administrator_definition>`
|
|
||||||
"""
|
"""
|
||||||
import datetime
|
|
||||||
|
|
||||||
from watcher.common import exception
|
from watcher.common import exception
|
||||||
from watcher.common import utils
|
from watcher.common import utils
|
||||||
from watcher import conf
|
|
||||||
from watcher.db import api as db_api
|
from watcher.db import api as db_api
|
||||||
from watcher import notifications
|
from watcher import notifications
|
||||||
from watcher import objects
|
from watcher import objects
|
||||||
from watcher.objects import base
|
from watcher.objects import base
|
||||||
from watcher.objects import fields as wfields
|
from watcher.objects import fields as wfields
|
||||||
|
|
||||||
CONF = conf.CONF
|
|
||||||
|
|
||||||
|
|
||||||
class State(object):
|
class State(object):
|
||||||
RECOMMENDED = 'RECOMMENDED'
|
RECOMMENDED = 'RECOMMENDED'
|
||||||
@@ -296,8 +289,7 @@ class ActionPlan(base.WatcherPersistentObject, base.WatcherObject,
|
|||||||
"""Soft Delete the Action plan from the DB"""
|
"""Soft Delete the Action plan from the DB"""
|
||||||
related_actions = objects.Action.list(
|
related_actions = objects.Action.list(
|
||||||
context=self._context,
|
context=self._context,
|
||||||
filters={"action_plan_uuid": self.uuid},
|
filters={"action_plan_uuid": self.uuid})
|
||||||
eager=True)
|
|
||||||
|
|
||||||
# Cascade soft_delete of related actions
|
# Cascade soft_delete of related actions
|
||||||
for related_action in related_actions:
|
for related_action in related_actions:
|
||||||
@@ -322,18 +314,3 @@ class ActionPlan(base.WatcherPersistentObject, base.WatcherObject,
|
|||||||
notifications.action_plan.send_delete(self._context, self)
|
notifications.action_plan.send_delete(self._context, self)
|
||||||
|
|
||||||
_notify()
|
_notify()
|
||||||
|
|
||||||
|
|
||||||
class StateManager(object):
|
|
||||||
def check_expired(self, context):
|
|
||||||
action_plan_expiry = (
|
|
||||||
CONF.watcher_decision_engine.action_plan_expiry)
|
|
||||||
date_created = datetime.datetime.utcnow() - datetime.timedelta(
|
|
||||||
hours=action_plan_expiry)
|
|
||||||
filters = {'state__eq': State.RECOMMENDED,
|
|
||||||
'created_at__lt': date_created}
|
|
||||||
action_plans = objects.ActionPlan.list(
|
|
||||||
context, filters=filters, eager=True)
|
|
||||||
for action_plan in action_plans:
|
|
||||||
action_plan.state = State.SUPERSEDED
|
|
||||||
action_plan.save()
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user