Compare commits
203 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
46d5094add | ||
|
|
783c7c0177 | ||
|
|
6d0717199c | ||
|
|
8b77e78f3d | ||
|
|
22c9c4df87 | ||
|
|
99ff6d3348 | ||
|
|
c67f83cce0 | ||
|
|
397bb3497e | ||
|
|
4c924fc505 | ||
|
|
4c5ecc808d | ||
|
|
64b5a7c3e4 | ||
|
|
40bb92f749 | ||
|
|
92bd06cf94 | ||
|
|
c9e0dfd3f5 | ||
|
|
446fe1307a | ||
|
|
2836f460e3 | ||
|
|
cb9bb7301b | ||
|
|
cb644fcef9 | ||
|
|
0a7c87eebf | ||
|
|
d7f4f42772 | ||
|
|
bdc0eb196a | ||
|
|
59427eb0d9 | ||
|
|
b6801b192a | ||
|
|
0a6c2c16a4 | ||
|
|
9a44941c66 | ||
|
|
a6508a0013 | ||
|
|
c3db66ca09 | ||
|
|
5d0fe553c4 | ||
|
|
8b8239c3d8 | ||
|
|
920bd502ec | ||
|
|
c68d33f341 | ||
|
|
8e8fdbd809 | ||
|
|
681536c8c7 | ||
|
|
083b170083 | ||
|
|
de7b0129a1 | ||
|
|
323fd01a85 | ||
|
|
c0133e6585 | ||
|
|
63fffeacd8 | ||
|
|
bc791f0e75 | ||
|
|
6ed417e6a7 | ||
|
|
4afefa3dfb | ||
|
|
9fadfbe40a | ||
|
|
f278874a93 | ||
|
|
1c5b247300 | ||
|
|
547bf5f87e | ||
|
|
96c0ac0ca8 | ||
|
|
a80fd2a51e | ||
|
|
58ea85c852 | ||
|
|
78f122f241 | ||
|
|
43eb997edb | ||
|
|
c440cdd69f | ||
|
|
b5bccba169 | ||
|
|
e058437ae0 | ||
|
|
1acacaa812 | ||
|
|
5bb1b6cbf0 | ||
|
|
de058d7ed1 | ||
|
|
98a65efb16 | ||
|
|
338539ec53 | ||
|
|
02f0f8e70a | ||
|
|
7f1bd20a09 | ||
|
|
d3d2a5ef8c | ||
|
|
3a6ae820c0 | ||
|
|
5a8860419e | ||
|
|
4aa1c7558b | ||
|
|
a8dab52376 | ||
|
|
5615d0523d | ||
|
|
10823ce133 | ||
|
|
18c098c4c1 | ||
|
|
db649d86b6 | ||
|
|
6e380b685b | ||
|
|
8dfff0e8e6 | ||
|
|
fbc7da755a | ||
|
|
b947c30910 | ||
|
|
9af96114af | ||
|
|
1ddf69a68f | ||
|
|
379ac791a8 | ||
|
|
81ea37de41 | ||
|
|
1c963fdc96 | ||
|
|
f32995228b | ||
|
|
0ec3d68994 | ||
|
|
3503e11506 | ||
|
|
c7f0ef37d0 | ||
|
|
d93b1ffe9f | ||
|
|
4e71a0c655 | ||
|
|
37dd713ed5 | ||
|
|
55aeb783e3 | ||
|
|
fe3f6e73be | ||
|
|
5baff7dc3e | ||
|
|
e3198d25a5 | ||
|
|
33ee575936 | ||
|
|
259f2562e6 | ||
|
|
1629247413 | ||
|
|
58d84aca6d | ||
|
|
236879490d | ||
|
|
79850cc89c | ||
|
|
b958214db8 | ||
|
|
a0b5f5aa1d | ||
|
|
a4a009a2c6 | ||
|
|
0ba8a35ade | ||
|
|
8bcc1b2097 | ||
|
|
b440f5c69a | ||
|
|
ad40c61ea9 | ||
|
|
858bbbf126 | ||
|
|
1d74f7e3bc | ||
|
|
b7641a9311 | ||
|
|
376d669af6 | ||
|
|
25d27f0288 | ||
|
|
3f4686ce79 | ||
|
|
86c1a9d77f | ||
|
|
9a6811ae6b | ||
|
|
e520f5f452 | ||
|
|
6a25bd983c | ||
|
|
c175ef2170 | ||
|
|
28733a5f30 | ||
|
|
7f8fec1bca | ||
|
|
278b1819d6 | ||
|
|
978bb11d4a | ||
|
|
3027b28942 | ||
|
|
2f0c1c12cf | ||
|
|
e122c61840 | ||
|
|
8f6eac819f | ||
|
|
de307e536e | ||
|
|
7406a1e713 | ||
|
|
982410dd3e | ||
|
|
83fdbf7366 | ||
|
|
2db5ae31c7 | ||
|
|
2191844ebb | ||
|
|
9c71b3df88 | ||
|
|
7beb9b4c29 | ||
|
|
8b7dce4803 | ||
|
|
94c50825e4 | ||
|
|
6ff4ba991e | ||
|
|
9e5a3708e1 | ||
|
|
ca5ae03b81 | ||
|
|
b47aefac30 | ||
|
|
70c7e1c639 | ||
|
|
ef78e68022 | ||
|
|
4dfe6a3fa8 | ||
|
|
b53da21b9b | ||
|
|
4305935312 | ||
|
|
894dfa0d7e | ||
|
|
a16351d352 | ||
|
|
75772cd3db | ||
|
|
63d9500904 | ||
|
|
c8096c6148 | ||
|
|
6d0754bb65 | ||
|
|
595b13a622 | ||
|
|
8832ad78e2 | ||
|
|
be81f8aff2 | ||
|
|
e95548b1a9 | ||
|
|
62b39fefbb | ||
|
|
f23548b92f | ||
|
|
43275537ea | ||
|
|
eaed650000 | ||
|
|
0505466ea5 | ||
|
|
0e7bfe61bd | ||
|
|
096354c255 | ||
|
|
afe06e2349 | ||
|
|
1d689ef410 | ||
|
|
58fb86a3da | ||
|
|
f675003076 | ||
|
|
e34ee792a8 | ||
|
|
037f43cd04 | ||
|
|
8f87699910 | ||
|
|
c811051351 | ||
|
|
a2750c74f9 | ||
|
|
67455c6a84 | ||
|
|
a7455a8bf7 | ||
|
|
7fcb683404 | ||
|
|
65cf19a7e4 | ||
|
|
facca13dc4 | ||
|
|
e8576e8963 | ||
|
|
dfc9a3b37d | ||
|
|
24dc92091c | ||
|
|
53d6d7d131 | ||
|
|
fd6ecc1b13 | ||
|
|
8bac4fd42a | ||
|
|
55d186be58 | ||
|
|
ed438d2eb2 | ||
|
|
4898df89ca | ||
|
|
ae949148ef | ||
|
|
e0242b49f1 | ||
|
|
5b57985686 | ||
|
|
f6ef197473 | ||
|
|
61c926a10b | ||
|
|
9c33f8aaeb | ||
|
|
08fc69cfc4 | ||
|
|
dc6b8aad7e | ||
|
|
473cee8ad3 | ||
|
|
2ffeb48010 | ||
|
|
86d3c2ff89 | ||
|
|
47759202a8 | ||
|
|
c0306ea8f4 | ||
|
|
34ccb7c23e | ||
|
|
9900273988 | ||
|
|
4662f248b3 | ||
|
|
7638103203 | ||
|
|
adc5587a2b | ||
|
|
34ab93a5f2 | ||
|
|
c286e1ec4b | ||
|
|
6c1769a15e | ||
|
|
b41a2cc940 | ||
|
|
8b357ace5a |
3
.gitignore
vendored
@@ -43,6 +43,9 @@ output/*/index.html
|
|||||||
|
|
||||||
# Sphinx
|
# Sphinx
|
||||||
doc/build
|
doc/build
|
||||||
|
doc/source/api
|
||||||
|
doc/source/samples
|
||||||
|
doc/source/watcher.conf.sample
|
||||||
|
|
||||||
# pbr generates these
|
# pbr generates these
|
||||||
AUTHORS
|
AUTHORS
|
||||||
|
|||||||
@@ -10,12 +10,12 @@ Watcher
|
|||||||
|
|
||||||
OpenStack Watcher provides a flexible and scalable resource optimization
|
OpenStack Watcher provides a flexible and scalable resource optimization
|
||||||
service for multi-tenant OpenStack-based clouds.
|
service for multi-tenant OpenStack-based clouds.
|
||||||
Watcher provides a complete optimization loop—including everything from a
|
Watcher provides a complete optimization loop-including everything from a
|
||||||
metrics receiver, complex event processor and profiler, optimization processor
|
metrics receiver, complex event processor and profiler, optimization processor
|
||||||
and an action plan applier. This provides a robust framework to realize a wide
|
and an action plan applier. This provides a robust framework to realize a wide
|
||||||
range of cloud optimization goals, including the reduction of data center
|
range of cloud optimization goals, including the reduction of data center
|
||||||
operating costs, increased system performance via intelligent virtual machine
|
operating costs, increased system performance via intelligent virtual machine
|
||||||
migration, increased energy efficiency—and more!
|
migration, increased energy efficiency-and more!
|
||||||
|
|
||||||
* Free software: Apache license
|
* Free software: Apache license
|
||||||
* Wiki: http://wiki.openstack.org/wiki/Watcher
|
* Wiki: http://wiki.openstack.org/wiki/Watcher
|
||||||
|
|||||||
246
devstack/lib/watcher
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# lib/watcher
|
||||||
|
# Functions to control the configuration and operation of the watcher services
|
||||||
|
|
||||||
|
# Dependencies:
|
||||||
|
#
|
||||||
|
# - ``functions`` file
|
||||||
|
# - ``SERVICE_{TENANT_NAME|PASSWORD}`` must be defined
|
||||||
|
# - ``DEST``, ``DATA_DIR``, ``STACK_USER`` must be defined
|
||||||
|
|
||||||
|
# ``stack.sh`` calls the entry points in this order:
|
||||||
|
#
|
||||||
|
# - is_watcher_enabled
|
||||||
|
# - install_watcher
|
||||||
|
# - configure_watcher
|
||||||
|
# - create_watcher_conf
|
||||||
|
# - init_watcher
|
||||||
|
# - start_watcher
|
||||||
|
# - stop_watcher
|
||||||
|
# - cleanup_watcher
|
||||||
|
|
||||||
|
# Save trace setting
|
||||||
|
_XTRACE_WATCHER=$(set +o | grep xtrace)
|
||||||
|
set +o xtrace
|
||||||
|
|
||||||
|
|
||||||
|
# Defaults
|
||||||
|
# --------
|
||||||
|
|
||||||
|
# Set up default directories
|
||||||
|
WATCHER_REPO=${WATCHER_REPO:-${GIT_BASE}/openstack/watcher.git}
|
||||||
|
WATCHER_BRANCH=${WATCHER_BRANCH:-master}
|
||||||
|
WATCHER_DIR=$DEST/watcher
|
||||||
|
|
||||||
|
GITREPO["python-watcherclient"]=${WATCHERCLIENT_REPO:-${GIT_BASE}/openstack/python-watcherclient.git}
|
||||||
|
GITBRANCH["python-watcherclient"]=${WATCHERCLIENT_BRANCH:-master}
|
||||||
|
GITDIR["python-watcherclient"]=$DEST/python-watcherclient
|
||||||
|
|
||||||
|
WATCHER_STATE_PATH=${WATCHER_STATE_PATH:=$DATA_DIR/watcher}
|
||||||
|
WATCHER_AUTH_CACHE_DIR=${WATCHER_AUTH_CACHE_DIR:-/var/cache/watcher}
|
||||||
|
|
||||||
|
WATCHER_CONF_DIR=/etc/watcher
|
||||||
|
WATCHER_CONF=$WATCHER_CONF_DIR/watcher.conf
|
||||||
|
WATCHER_POLICY_JSON=$WATCHER_CONF_DIR/policy.json
|
||||||
|
|
||||||
|
if is_ssl_enabled_service "watcher" || is_service_enabled tls-proxy; then
|
||||||
|
WATCHER_SERVICE_PROTOCOL="https"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Public facing bits
|
||||||
|
WATCHER_SERVICE_HOST=${WATCHER_SERVICE_HOST:-$HOST_IP}
|
||||||
|
WATCHER_SERVICE_PORT=${WATCHER_SERVICE_PORT:-9322}
|
||||||
|
WATCHER_SERVICE_PORT_INT=${WATCHER_SERVICE_PORT_INT:-19322}
|
||||||
|
WATCHER_SERVICE_PROTOCOL=${WATCHER_SERVICE_PROTOCOL:-$SERVICE_PROTOCOL}
|
||||||
|
|
||||||
|
# Support entry points installation of console scripts
|
||||||
|
if [[ -d $WATCHER_DIR/bin ]]; then
|
||||||
|
WATCHER_BIN_DIR=$WATCHER_DIR/bin
|
||||||
|
else
|
||||||
|
WATCHER_BIN_DIR=$(get_python_exec_prefix)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Entry Points
|
||||||
|
# ------------
|
||||||
|
|
||||||
|
# Test if any watcher services are enabled
|
||||||
|
# is_watcher_enabled
|
||||||
|
function is_watcher_enabled {
|
||||||
|
[[ ,${ENABLED_SERVICES} =~ ,"watcher-" ]] && return 0
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# cleanup_watcher() - Remove residual data files, anything left over from previous
|
||||||
|
# runs that a clean run would need to clean up
|
||||||
|
function cleanup_watcher {
|
||||||
|
sudo rm -rf $WATCHER_STATE_PATH $WATCHER_AUTH_CACHE_DIR
|
||||||
|
}
|
||||||
|
|
||||||
|
# configure_watcher() - Set config files, create data dirs, etc
|
||||||
|
function configure_watcher {
|
||||||
|
# Put config files in ``/etc/watcher`` for everyone to find
|
||||||
|
sudo install -d -o $STACK_USER $WATCHER_CONF_DIR
|
||||||
|
|
||||||
|
install_default_policy watcher
|
||||||
|
|
||||||
|
# Rebuild the config file from scratch
|
||||||
|
create_watcher_conf
|
||||||
|
}
|
||||||
|
|
||||||
|
# create_watcher_accounts() - Set up common required watcher accounts
|
||||||
|
#
|
||||||
|
# Project User Roles
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# SERVICE_TENANT_NAME watcher service
|
||||||
|
function create_watcher_accounts {
|
||||||
|
create_service_user "watcher" "admin"
|
||||||
|
|
||||||
|
local watcher_service=$(get_or_create_service "watcher" \
|
||||||
|
"infra-optim" "Watcher Infrastructure Optimization Service")
|
||||||
|
get_or_create_endpoint $watcher_service \
|
||||||
|
"$REGION_NAME" \
|
||||||
|
"$WATCHER_SERVICE_PROTOCOL://$WATCHER_SERVICE_HOST:$WATCHER_SERVICE_PORT" \
|
||||||
|
"$WATCHER_SERVICE_PROTOCOL://$WATCHER_SERVICE_HOST:$WATCHER_SERVICE_PORT" \
|
||||||
|
"$WATCHER_SERVICE_PROTOCOL://$WATCHER_SERVICE_HOST:$WATCHER_SERVICE_PORT"
|
||||||
|
}
|
||||||
|
|
||||||
|
# create_watcher_conf() - Create a new watcher.conf file
|
||||||
|
function create_watcher_conf {
|
||||||
|
# (Re)create ``watcher.conf``
|
||||||
|
rm -f $WATCHER_CONF
|
||||||
|
|
||||||
|
iniset $WATCHER_CONF DEFAULT debug "$ENABLE_DEBUG_LOG_LEVEL"
|
||||||
|
iniset $WATCHER_CONF DEFAULT control_exchange watcher
|
||||||
|
|
||||||
|
iniset $WATCHER_CONF database connection $(database_connection_url watcher)
|
||||||
|
iniset $WATCHER_CONF api host "$WATCHER_SERVICE_HOST"
|
||||||
|
iniset $WATCHER_CONF api port "$WATCHER_SERVICE_PORT"
|
||||||
|
|
||||||
|
iniset $WATCHER_CONF oslo_policy policy_file $WATCHER_POLICY_JSON
|
||||||
|
|
||||||
|
iniset $WATCHER_CONF oslo_messaging_rabbit rabbit_userid $RABBIT_USERID
|
||||||
|
iniset $WATCHER_CONF oslo_messaging_rabbit rabbit_password $RABBIT_PASSWORD
|
||||||
|
iniset $WATCHER_CONF oslo_messaging_rabbit rabbit_host $RABBIT_HOST
|
||||||
|
|
||||||
|
configure_auth_token_middleware $WATCHER_CONF watcher $WATCHER_AUTH_CACHE_DIR
|
||||||
|
configure_auth_token_middleware $WATCHER_CONF watcher $WATCHER_AUTH_CACHE_DIR "watcher_clients_auth"
|
||||||
|
|
||||||
|
if is_fedora || is_suse; then
|
||||||
|
# watcher defaults to /usr/local/bin, but fedora and suse pip like to
|
||||||
|
# install things in /usr/bin
|
||||||
|
iniset $WATCHER_CONF DEFAULT bindir "/usr/bin"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$WATCHER_STATE_PATH" ]; then
|
||||||
|
iniset $WATCHER_CONF DEFAULT state_path "$WATCHER_STATE_PATH"
|
||||||
|
iniset $WATCHER_CONF oslo_concurrency lock_path "$WATCHER_STATE_PATH"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$SYSLOG" != "False" ]; then
|
||||||
|
iniset $WATCHER_CONF DEFAULT use_syslog "True"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Format logging
|
||||||
|
if [ "$LOG_COLOR" == "True" ] && [ "$SYSLOG" == "False" ]; then
|
||||||
|
setup_colorized_logging $WATCHER_CONF DEFAULT
|
||||||
|
else
|
||||||
|
# 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 %(user_name)s %(project_name)s] %(instance)s%(message)s"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Register SSL certificates if provided
|
||||||
|
if is_ssl_enabled_service watcher; then
|
||||||
|
ensure_certificates WATCHER
|
||||||
|
|
||||||
|
iniset $WATCHER_CONF DEFAULT ssl_cert_file "$WATCHER_SSL_CERT"
|
||||||
|
iniset $WATCHER_CONF DEFAULT ssl_key_file "$WATCHER_SSL_KEY"
|
||||||
|
|
||||||
|
iniset $WATCHER_CONF DEFAULT enabled_ssl_apis "$WATCHER_ENABLED_APIS"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if is_service_enabled ceilometer; then
|
||||||
|
iniset $WATCHER_CONF watcher_messaging notifier_driver "messaging"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# create_watcher_cache_dir() - Part of the init_watcher() process
|
||||||
|
function create_watcher_cache_dir {
|
||||||
|
# Create cache dir
|
||||||
|
sudo install -d -o $STACK_USER $WATCHER_AUTH_CACHE_DIR
|
||||||
|
rm -rf $WATCHER_AUTH_CACHE_DIR/*
|
||||||
|
}
|
||||||
|
|
||||||
|
# init_watcher() - Initialize databases, etc.
|
||||||
|
function init_watcher {
|
||||||
|
# clean up from previous (possibly aborted) runs
|
||||||
|
# create required data files
|
||||||
|
if is_service_enabled $DATABASE_BACKENDS && is_service_enabled watcher-api; then
|
||||||
|
# (Re)create watcher database
|
||||||
|
recreate_database watcher
|
||||||
|
|
||||||
|
# Create watcher schema
|
||||||
|
$WATCHER_BIN_DIR/watcher-db-manage --config-file $WATCHER_CONF create_schema
|
||||||
|
fi
|
||||||
|
create_watcher_cache_dir
|
||||||
|
}
|
||||||
|
|
||||||
|
# install_watcherclient() - Collect source and prepare
|
||||||
|
function install_watcherclient {
|
||||||
|
if use_library_from_git "python-watcherclient"; then
|
||||||
|
git_clone_by_name "python-watcherclient"
|
||||||
|
setup_dev_lib "python-watcherclient"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# install_watcher() - Collect source and prepare
|
||||||
|
function install_watcher {
|
||||||
|
git_clone $WATCHER_REPO $WATCHER_DIR $WATCHER_BRANCH
|
||||||
|
setup_develop $WATCHER_DIR
|
||||||
|
}
|
||||||
|
|
||||||
|
# start_watcher_api() - Start the API process ahead of other things
|
||||||
|
function start_watcher_api {
|
||||||
|
# Get right service port for testing
|
||||||
|
local service_port=$WATCHER_SERVICE_PORT
|
||||||
|
local service_protocol=$WATCHER_SERVICE_PROTOCOL
|
||||||
|
if is_service_enabled tls-proxy; then
|
||||||
|
service_port=$WATCHER_SERVICE_PORT_INT
|
||||||
|
service_protocol="http"
|
||||||
|
fi
|
||||||
|
|
||||||
|
run_process watcher-api "$WATCHER_BIN_DIR/watcher-api --config-file $WATCHER_CONF"
|
||||||
|
echo "Waiting for watcher-api to start..."
|
||||||
|
if ! wait_for_service $SERVICE_TIMEOUT $service_protocol://$WATCHER_SERVICE_HOST:$service_port; then
|
||||||
|
die $LINENO "watcher-api did not start"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Start proxies if enabled
|
||||||
|
if is_service_enabled tls-proxy; then
|
||||||
|
start_tls_proxy '*' $WATCHER_SERVICE_PORT $WATCHER_SERVICE_HOST $WATCHER_SERVICE_PORT_INT &
|
||||||
|
start_tls_proxy '*' $EC2_SERVICE_PORT $WATCHER_SERVICE_HOST $WATCHER_SERVICE_PORT_INT &
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# start_watcher() - Start running processes, including screen
|
||||||
|
function start_watcher {
|
||||||
|
# ``run_process`` checks ``is_service_enabled``, it is not needed here
|
||||||
|
start_watcher_api
|
||||||
|
run_process watcher-decision-engine "$WATCHER_BIN_DIR/watcher-decision-engine --config-file $WATCHER_CONF"
|
||||||
|
run_process watcher-applier "$WATCHER_BIN_DIR/watcher-applier --config-file $WATCHER_CONF"
|
||||||
|
}
|
||||||
|
|
||||||
|
# stop_watcher() - Stop running processes (non-screen)
|
||||||
|
function stop_watcher {
|
||||||
|
for serv in watcher-api watcher-decision-engine watcher-applier; do
|
||||||
|
stop_process $serv
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Restore xtrace
|
||||||
|
$_XTRACE_WATCHER
|
||||||
|
|
||||||
|
# Tell emacs to use shell-script-mode
|
||||||
|
## Local variables:
|
||||||
|
## mode: shell-script
|
||||||
|
## End:
|
||||||
46
devstack/local.conf.compute
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
# Sample ``local.conf`` for compute node for Watcher development
|
||||||
|
# NOTE: Copy this file to the root DevStack directory for it to work properly.
|
||||||
|
|
||||||
|
[[local|localrc]]
|
||||||
|
|
||||||
|
ADMIN_PASSWORD=nomoresecrete
|
||||||
|
DATABASE_PASSWORD=stackdb
|
||||||
|
RABBIT_PASSWORD=stackqueue
|
||||||
|
SERVICE_PASSWORD=$ADMIN_PASSWORD
|
||||||
|
SERVICE_TOKEN=azertytoken
|
||||||
|
|
||||||
|
HOST_IP=192.168.42.2 # Change this to this compute node's IP address
|
||||||
|
FLAT_INTERFACE=eth0
|
||||||
|
|
||||||
|
FIXED_RANGE=10.254.1.0/24 # Change this to whatever your network is
|
||||||
|
NETWORK_GATEWAY=10.254.1.1 # Change this for your network
|
||||||
|
|
||||||
|
MULTI_HOST=1
|
||||||
|
|
||||||
|
SERVICE_HOST=192.168.42.1 # Change this to the IP of your controller node
|
||||||
|
MYSQL_HOST=$SERVICE_HOST
|
||||||
|
RABBIT_HOST=$SERVICE_HOST
|
||||||
|
GLANCE_HOSTPORT=${SERVICE_HOST}:9292
|
||||||
|
|
||||||
|
DATABASE_TYPE=mysql
|
||||||
|
|
||||||
|
# Enable services (including neutron)
|
||||||
|
ENABLED_SERVICES=n-cpu,n-api-meta,c-vol,q-agt
|
||||||
|
|
||||||
|
NOVA_VNC_ENABLED=True
|
||||||
|
NOVNCPROXY_URL="http://$SERVICE_HOST:6080/vnc_auto.html"
|
||||||
|
VNCSERVER_LISTEN=0.0.0.0
|
||||||
|
VNCSERVER_PROXYCLIENT_ADDRESS=$HOST_IP
|
||||||
|
|
||||||
|
NOVA_INSTANCES_PATH=/opt/stack/data/instances
|
||||||
|
|
||||||
|
# Enable the Ceilometer plugin for the compute agent
|
||||||
|
enable_plugin ceilometer git://git.openstack.org/openstack/ceilometer
|
||||||
|
disable_service ceilometer-acentral,ceilometer-collector,ceilometer-api
|
||||||
|
|
||||||
|
LOGFILE=$DEST/logs/stack.sh.log
|
||||||
|
LOGDAYS=2
|
||||||
|
|
||||||
|
[[post-config|$NOVA_CONF]]
|
||||||
|
[DEFAULT]
|
||||||
|
compute_monitors=cpu.virt_driver
|
||||||
48
devstack/local.conf.controller
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
# Sample ``local.conf`` for controller node for Watcher development
|
||||||
|
# NOTE: Copy this file to the root DevStack directory for it to work properly.
|
||||||
|
|
||||||
|
[[local|localrc]]
|
||||||
|
|
||||||
|
ADMIN_PASSWORD=nomoresecrete
|
||||||
|
DATABASE_PASSWORD=stackdb
|
||||||
|
RABBIT_PASSWORD=stackqueue
|
||||||
|
SERVICE_PASSWORD=$ADMIN_PASSWORD
|
||||||
|
SERVICE_TOKEN=azertytoken
|
||||||
|
|
||||||
|
HOST_IP=192.168.42.1 # Change this to your controller node IP address
|
||||||
|
FLAT_INTERFACE=eth0
|
||||||
|
|
||||||
|
FIXED_RANGE=10.254.1.0/24 # Change this to whatever your network is
|
||||||
|
NETWORK_GATEWAY=10.254.1.1 # Change this for your network
|
||||||
|
|
||||||
|
MULTI_HOST=1
|
||||||
|
|
||||||
|
# This is the controller node, so disable nova-compute
|
||||||
|
disable_service n-cpu
|
||||||
|
|
||||||
|
# Disable nova-network and use neutron instead
|
||||||
|
disable_service n-net
|
||||||
|
ENABLED_SERVICES+=,q-svc,q-dhcp,q-meta,q-agt,q-l3,neutron
|
||||||
|
|
||||||
|
# Enable remote console access
|
||||||
|
enable_service n-cauth
|
||||||
|
|
||||||
|
# Enable the Watcher plugin
|
||||||
|
enable_plugin watcher git://git.openstack.org/openstack/watcher
|
||||||
|
|
||||||
|
# Enable the Ceilometer plugin
|
||||||
|
enable_plugin ceilometer git://git.openstack.org/openstack/ceilometer
|
||||||
|
|
||||||
|
# This is the controller node, so disable the ceilometer compute agent
|
||||||
|
disable_service ceilometer-acompute
|
||||||
|
|
||||||
|
LOGFILE=$DEST/logs/stack.sh.log
|
||||||
|
LOGDAYS=2
|
||||||
|
|
||||||
|
[[post-config|$NOVA_CONF]]
|
||||||
|
[DEFAULT]
|
||||||
|
compute_monitors=cpu.virt_driver
|
||||||
|
|
||||||
|
[[post-config|$WATCHER_CONF]]
|
||||||
|
[watcher_goals]
|
||||||
|
goals=BASIC_CONSOLIDATION:basic,DUMMY:dummy
|
||||||
53
devstack/plugin.sh
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# plugin.sh - DevStack plugin script to install watcher
|
||||||
|
|
||||||
|
# Save trace setting
|
||||||
|
_XTRACE_WATCHER_PLUGIN=$(set +o | grep xtrace)
|
||||||
|
set -o xtrace
|
||||||
|
|
||||||
|
echo_summary "watcher's plugin.sh was called..."
|
||||||
|
source $DEST/watcher/devstack/lib/watcher
|
||||||
|
|
||||||
|
# Show all of defined environment variables
|
||||||
|
(set -o posix; set)
|
||||||
|
|
||||||
|
if is_service_enabled watcher-api watcher-decision-engine watcher-applier; then
|
||||||
|
if [[ "$1" == "stack" && "$2" == "pre-install" ]]; then
|
||||||
|
echo_summary "Before Installing watcher"
|
||||||
|
elif [[ "$1" == "stack" && "$2" == "install" ]]; then
|
||||||
|
echo_summary "Installing watcher"
|
||||||
|
install_watcher
|
||||||
|
|
||||||
|
LIBS_FROM_GIT="${LIBS_FROM_GIT},python-watcherclient"
|
||||||
|
|
||||||
|
install_watcherclient
|
||||||
|
cleanup_watcher
|
||||||
|
elif [[ "$1" == "stack" && "$2" == "post-config" ]]; then
|
||||||
|
echo_summary "Configuring watcher"
|
||||||
|
configure_watcher
|
||||||
|
|
||||||
|
if is_service_enabled key; then
|
||||||
|
create_watcher_accounts
|
||||||
|
fi
|
||||||
|
|
||||||
|
elif [[ "$1" == "stack" && "$2" == "extra" ]]; then
|
||||||
|
# Initialize watcher
|
||||||
|
init_watcher
|
||||||
|
|
||||||
|
# Start the watcher components
|
||||||
|
echo_summary "Starting watcher"
|
||||||
|
start_watcher
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$1" == "unstack" ]]; then
|
||||||
|
stop_watcher
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$1" == "clean" ]]; then
|
||||||
|
cleanup_watcher
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Restore xtrace
|
||||||
|
$_XTRACE_WATCHER_PLUGIN
|
||||||
9
devstack/settings
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# DevStack settings
|
||||||
|
|
||||||
|
# Make sure rabbit is enabled
|
||||||
|
enable_service rabbit
|
||||||
|
|
||||||
|
# Enable Watcher services
|
||||||
|
enable_service watcher-api
|
||||||
|
enable_service watcher-decision-engine
|
||||||
|
enable_service watcher-applier
|
||||||
@@ -34,8 +34,8 @@ Components
|
|||||||
AMQP Bus
|
AMQP Bus
|
||||||
--------
|
--------
|
||||||
|
|
||||||
The AMQP message bus handles asynchronous communications between the different
|
The AMQP message bus handles internal asynchronous communications between the
|
||||||
Watcher components.
|
different Watcher components.
|
||||||
|
|
||||||
.. _cluster_history_db_definition:
|
.. _cluster_history_db_definition:
|
||||||
|
|
||||||
@@ -51,7 +51,15 @@ MongoDB,...) but will probably be more performant when using
|
|||||||
which are optimized for handling time series data, which are arrays of numbers
|
which are optimized for handling time series data, which are arrays of numbers
|
||||||
indexed by time (a datetime or a datetime range).
|
indexed by time (a datetime or a datetime range).
|
||||||
|
|
||||||
.. _watcher_api_definition:
|
.. _cluster_model_db_definition:
|
||||||
|
|
||||||
|
Cluster Model Database
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
This component stores the data related to the
|
||||||
|
:ref:`Cluster Data Model <cluster_data_model_definition>`.
|
||||||
|
|
||||||
|
.. _archi_watcher_api_definition:
|
||||||
|
|
||||||
Watcher API
|
Watcher API
|
||||||
-----------
|
-----------
|
||||||
@@ -63,13 +71,13 @@ It enables the :ref:`Administrator <administrator_definition>` of a
|
|||||||
:ref:`Cluster <cluster_definition>` to control and monitor the Watcher system
|
:ref:`Cluster <cluster_definition>` to control and monitor the Watcher system
|
||||||
via any interaction mechanism connected to this API:
|
via any interaction mechanism connected to this API:
|
||||||
|
|
||||||
- :ref:`CLI <watcher_cli_definition>`
|
- :ref:`CLI <archi_watcher_cli_definition>`
|
||||||
- Horizon plugin
|
- Horizon plugin
|
||||||
- Python SDK
|
- Python SDK
|
||||||
|
|
||||||
You can also read the detailed description of `Watcher API`_.
|
You can also read the detailed description of `Watcher API`_.
|
||||||
|
|
||||||
.. _watcher_applier_definition:
|
.. _archi_watcher_applier_definition:
|
||||||
|
|
||||||
Watcher Applier
|
Watcher Applier
|
||||||
---------------
|
---------------
|
||||||
@@ -111,7 +119,7 @@ If the :ref:`Action <action_definition>` fails, the
|
|||||||
previous state of the :ref:`Managed resource <managed_resource_definition>`
|
previous state of the :ref:`Managed resource <managed_resource_definition>`
|
||||||
(i.e. before the command was sent to the underlying OpenStack service).
|
(i.e. before the command was sent to the underlying OpenStack service).
|
||||||
|
|
||||||
.. _watcher_cli_definition:
|
.. _archi_watcher_cli_definition:
|
||||||
|
|
||||||
Watcher CLI
|
Watcher CLI
|
||||||
-----------
|
-----------
|
||||||
@@ -119,16 +127,28 @@ Watcher CLI
|
|||||||
The watcher command-line interface (CLI) can be used to interact with the
|
The watcher command-line interface (CLI) can be used to interact with the
|
||||||
Watcher system in order to control it or to know its current status.
|
Watcher system in order to control it or to know its current status.
|
||||||
|
|
||||||
Please, read `the detailed documentation about Watcher CLI <https://factory.b-com.com/www/watcher/doc/python-watcherclient/>`_
|
Please, read `the detailed documentation about Watcher CLI
|
||||||
|
<https://factory.b-com.com/www/watcher/doc/python-watcherclient/>`_.
|
||||||
|
|
||||||
.. _watcher_database_definition:
|
.. _archi_watcher_dashboard_definition:
|
||||||
|
|
||||||
|
Watcher Dashboard
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
The Watcher Dashboard can be used to interact with the Watcher system through
|
||||||
|
Horizon in order to control it or to know its current status.
|
||||||
|
|
||||||
|
Please, read `the detailed documentation about Watcher Dashboard
|
||||||
|
<https://factory.b-com.com/www/watcher/doc/watcher-dashboard/>`_.
|
||||||
|
|
||||||
|
.. _archi_watcher_database_definition:
|
||||||
|
|
||||||
Watcher Database
|
Watcher Database
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
This database stores all the Watcher domain objects which can be requested
|
This database stores all the Watcher domain objects which can be requested
|
||||||
by the :ref:`Watcher API <watcher_api_definition>` or the
|
by the :ref:`Watcher API <archi_watcher_api_definition>` or the
|
||||||
:ref:`Watcher CLI <watcher_cli_definition>`:
|
:ref:`Watcher CLI <archi_watcher_cli_definition>`:
|
||||||
|
|
||||||
- :ref:`Audit templates <audit_template_definition>`
|
- :ref:`Audit templates <audit_template_definition>`
|
||||||
- :ref:`Audits <audit_definition>`
|
- :ref:`Audits <audit_definition>`
|
||||||
@@ -139,7 +159,7 @@ by the :ref:`Watcher API <watcher_api_definition>` or the
|
|||||||
The Watcher domain being here "*optimization of some resources provided by an
|
The Watcher domain being here "*optimization of some resources provided by an
|
||||||
OpenStack system*".
|
OpenStack system*".
|
||||||
|
|
||||||
.. _watcher_decision_engine_definition:
|
.. _archi_watcher_decision_engine_definition:
|
||||||
|
|
||||||
Watcher Decision Engine
|
Watcher Decision Engine
|
||||||
-----------------------
|
-----------------------
|
||||||
@@ -159,7 +179,7 @@ The :ref:`Strategy <strategy_definition>` is then dynamically loaded (via
|
|||||||
`stevedore <https://github.com/openstack/stevedore/>`_). The
|
`stevedore <https://github.com/openstack/stevedore/>`_). The
|
||||||
:ref:`Watcher Decision Engine <watcher_decision_engine_definition>` calls the
|
:ref:`Watcher Decision Engine <watcher_decision_engine_definition>` calls the
|
||||||
**execute()** method of the :ref:`Strategy <strategy_definition>` class which
|
**execute()** method of the :ref:`Strategy <strategy_definition>` class which
|
||||||
generates a set of :ref:`Actions <action_definition>`.
|
generates a solution composed of a set of :ref:`Actions <action_definition>`.
|
||||||
|
|
||||||
These :ref:`Actions <action_definition>` are scheduled in time by the
|
These :ref:`Actions <action_definition>` are scheduled in time by the
|
||||||
:ref:`Watcher Planner <watcher_planner_definition>` (i.e., it generates an
|
:ref:`Watcher Planner <watcher_planner_definition>` (i.e., it generates an
|
||||||
@@ -169,14 +189,244 @@ In order to compute the potential :ref:`Solution <solution_definition>` for the
|
|||||||
Audit, the :ref:`Strategy <strategy_definition>` relies on two sets of data:
|
Audit, the :ref:`Strategy <strategy_definition>` relies on two sets of data:
|
||||||
|
|
||||||
- the current state of the
|
- the current state of the
|
||||||
:ref:`Managed resources <managed_resource_definition>`
|
:ref:`Managed resources <managed_resource_definition>`
|
||||||
(e.g., the data stored in the Nova database)
|
(e.g., the data stored in the Nova database)
|
||||||
- the data stored in the
|
- the data stored in the
|
||||||
:ref:`Cluster History Database <cluster_history_db_definition>`
|
:ref:`Cluster History Database <cluster_history_db_definition>`
|
||||||
which provides information about the past of the
|
which provides information about the past of the
|
||||||
:ref:`Cluster <cluster_definition>`
|
:ref:`Cluster <cluster_definition>`
|
||||||
|
|
||||||
So far, only one :ref:`Strategy <strategy_definition>` can be associated to a
|
So far, only one :ref:`Strategy <strategy_definition>` can be associated to a
|
||||||
given :ref:`Goal <goal_definition>` via the main Watcher configuration file.
|
given :ref:`Goal <goal_definition>` via the main Watcher configuration file.
|
||||||
|
|
||||||
|
.. _data_model:
|
||||||
|
|
||||||
|
Data model
|
||||||
|
==========
|
||||||
|
|
||||||
|
The following diagram shows the data model of Watcher, especially the
|
||||||
|
functional dependency of objects from the actors (Admin, Customer) point of
|
||||||
|
view (Goals, Audits, Action Plans, ...):
|
||||||
|
|
||||||
|
.. image:: ./images/functional_data_model.svg
|
||||||
|
:width: 100%
|
||||||
|
|
||||||
|
.. _sequence_diagrams:
|
||||||
|
|
||||||
|
Sequence diagrams
|
||||||
|
=================
|
||||||
|
|
||||||
|
The following paragraph shows the messages exchanged between the different
|
||||||
|
components of Watcher for the most often used scenarios.
|
||||||
|
|
||||||
|
.. _sequence_diagrams_create_audit_template:
|
||||||
|
|
||||||
|
Create a new Audit Template
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
The :ref:`Administrator <administrator_definition>` first creates an
|
||||||
|
:ref:`Audit template <audit_template_definition>` providing at least the
|
||||||
|
following parameters:
|
||||||
|
|
||||||
|
- A name
|
||||||
|
- A goal to achieve
|
||||||
|
|
||||||
|
.. image:: ./images/sequence_create_audit_template.png
|
||||||
|
:width: 100%
|
||||||
|
|
||||||
|
The `Watcher API`_ just makes sure that the goal exists (i.e. it is declared
|
||||||
|
in the Watcher configuration file) and stores a new audit template in the
|
||||||
|
:ref:`Watcher Database <watcher_database_definition>`.
|
||||||
|
|
||||||
|
.. _sequence_diagrams_create_and_launch_audit:
|
||||||
|
|
||||||
|
Create and launch a new Audit
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
The :ref:`Administrator <administrator_definition>` can then launch a new
|
||||||
|
:ref:`Audit <audit_definition>` by providing at least the unique UUID of the
|
||||||
|
previously created :ref:`Audit template <audit_template_definition>`:
|
||||||
|
|
||||||
|
.. image:: ./images/sequence_create_and_launch_audit.png
|
||||||
|
:width: 100%
|
||||||
|
|
||||||
|
A message is sent on the :ref:`AMQP bus <amqp_bus_definition>` which triggers
|
||||||
|
the Audit in the
|
||||||
|
:ref:`Watcher Decision Engine <watcher_decision_engine_definition>`:
|
||||||
|
|
||||||
|
.. image:: ./images/sequence_trigger_audit_in_decision_engine.png
|
||||||
|
:width: 100%
|
||||||
|
|
||||||
|
The :ref:`Watcher Decision Engine <watcher_decision_engine_definition>` reads
|
||||||
|
the Audit parameters from the
|
||||||
|
:ref:`Watcher Database <watcher_database_definition>`. It instantiates the
|
||||||
|
appropriate :ref:`Strategy <strategy_definition>` (using entry points)
|
||||||
|
associated to the :ref:`Goal <goal_definition>` of the
|
||||||
|
:ref:`Audit <audit_definition>` (it uses the information of the Watcher
|
||||||
|
configuration file to find the mapping between the
|
||||||
|
:ref:`Goal <goal_definition>` and the :ref:`Strategy <strategy_definition>`
|
||||||
|
python class).
|
||||||
|
|
||||||
|
The :ref:`Watcher Decision Engine <watcher_decision_engine_definition>` also
|
||||||
|
builds the :ref:`Cluster Data Model <cluster_data_model_definition>`. This
|
||||||
|
data model is needed by the :ref:`Strategy <strategy_definition>` to know the
|
||||||
|
current state and topology of the audited
|
||||||
|
:ref:`Openstack cluster <cluster_definition>`.
|
||||||
|
|
||||||
|
The :ref:`Watcher Decision Engine <watcher_decision_engine_definition>` calls
|
||||||
|
the **execute()** method of the instantiated
|
||||||
|
:ref:`Strategy <strategy_definition>` and provides the data model as an input
|
||||||
|
parameter. This method computes a :ref:`Solution <strategy_definition>` to
|
||||||
|
achieve the goal and returns it to the
|
||||||
|
:ref:`Decision Engine <watcher_decision_engine_definition>`. At this point,
|
||||||
|
actions are not scheduled yet.
|
||||||
|
|
||||||
|
The :ref:`Watcher Decision Engine <watcher_decision_engine_definition>`
|
||||||
|
dynamically loads the :ref:`Watcher Planner <watcher_planner_definition>`
|
||||||
|
implementation which is configured in Watcher (via entry points) and calls the
|
||||||
|
**schedule()** method of this class with the solution as an input parameter.
|
||||||
|
This method finds an appropriate scheduling of
|
||||||
|
:ref:`Actions <action_definition>` taking into account some scheduling rules
|
||||||
|
(such as priorities between actions).
|
||||||
|
It generates a new :ref:`Action Plan <action_plan_definition>` with status
|
||||||
|
**RECOMMENDED** and saves it into the
|
||||||
|
:ref:`Watcher Database <watcher_database_definition>`. The saved action plan is
|
||||||
|
now a scheduled flow of actions.
|
||||||
|
|
||||||
|
If every step executed successfully, the
|
||||||
|
:ref:`Watcher Decision Engine <watcher_decision_engine_definition>` updates
|
||||||
|
the current status of the Audit to **SUCCEEDED** in the
|
||||||
|
:ref:`Watcher Database <watcher_database_definition>` and sends a notification
|
||||||
|
on the bus to inform other components that the :ref:`Audit <audit_definition>`
|
||||||
|
was successful.
|
||||||
|
|
||||||
|
|
||||||
|
.. _sequence_diagrams_launch_action_plan:
|
||||||
|
|
||||||
|
Launch Action Plan
|
||||||
|
------------------
|
||||||
|
|
||||||
|
The :ref:`Administrator <administrator_definition>` can then launch the
|
||||||
|
recommended :ref:`Action Plan <action_plan_definition>`:
|
||||||
|
|
||||||
|
.. image:: ./images/sequence_launch_action_plan.png
|
||||||
|
:width: 100%
|
||||||
|
|
||||||
|
A message is sent on the :ref:`AMQP bus <amqp_bus_definition>` which triggers
|
||||||
|
the :ref:`Action Plan <action_plan_definition>` in the
|
||||||
|
:ref:`Watcher Applier <watcher_applier_definition>`:
|
||||||
|
|
||||||
|
.. image:: ./images/sequence_launch_action_plan_in_applier.png
|
||||||
|
:width: 100%
|
||||||
|
|
||||||
|
The :ref:`Watcher Applier <watcher_applier_definition>` will get the
|
||||||
|
description of the flow of :ref:`Actions <action_definition>` from the
|
||||||
|
:ref:`Watcher Database <watcher_database_definition>` and for each
|
||||||
|
:ref:`Action <action_definition>` it will instantiate a corresponding
|
||||||
|
:ref:`Action <action_definition>` handler python class.
|
||||||
|
|
||||||
|
The :ref:`Watcher Applier <watcher_applier_definition>` will then call the
|
||||||
|
following methods of the :ref:`Action <action_definition>` handler:
|
||||||
|
|
||||||
|
- **validate_parameters()**: this method will make sure that all the
|
||||||
|
provided input parameters are valid:
|
||||||
|
|
||||||
|
- If all parameters are valid, the Watcher Applier moves on to the next
|
||||||
|
step.
|
||||||
|
- If it is not, an error is raised and the action is not executed. A
|
||||||
|
notification is sent on the bus informing other components of the
|
||||||
|
failure.
|
||||||
|
|
||||||
|
- **preconditions()**: this method will make sure that all conditions are met
|
||||||
|
before executing the action (for example, it makes sure that an instance
|
||||||
|
still exists before trying to migrate it).
|
||||||
|
- **execute()**: this method is what triggers real commands on other
|
||||||
|
OpenStack services (such as Nova, ...) in order to change target resource
|
||||||
|
state. If the action is successfully executed, a notification message is
|
||||||
|
sent on the bus indicating that the new state of the action is
|
||||||
|
**SUCCEEDED**.
|
||||||
|
|
||||||
|
If every action of the action flow has been executed successfully, a
|
||||||
|
notification is sent on the bus to indicate that the whole
|
||||||
|
:ref:`Action Plan <action_plan_definition>` has **SUCCEEDED**.
|
||||||
|
|
||||||
|
|
||||||
|
.. _state_machine_diagrams:
|
||||||
|
|
||||||
|
State Machine diagrams
|
||||||
|
======================
|
||||||
|
|
||||||
|
.. _audit_state_machine:
|
||||||
|
|
||||||
|
Audit State Machine
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
An :ref:`Audit <audit_definition>` has a life-cycle and its current state may
|
||||||
|
be one of the following:
|
||||||
|
|
||||||
|
- **PENDING** : a request for an :ref:`Audit <audit_definition>` has been
|
||||||
|
submitted (either manually by the
|
||||||
|
:ref:`Administrator <administrator_definition>` or automatically via some
|
||||||
|
event handling mechanism) and is in the queue for being processed by the
|
||||||
|
:ref:`Watcher Decision Engine <watcher_decision_engine_definition>`
|
||||||
|
- **ONGOING** : the :ref:`Audit <audit_definition>` is currently being
|
||||||
|
processed by the
|
||||||
|
:ref:`Watcher Decision Engine <watcher_decision_engine_definition>`
|
||||||
|
- **SUCCEEDED** : the :ref:`Audit <audit_definition>` has been executed
|
||||||
|
successfully and at least one solution was found
|
||||||
|
- **FAILED** : an error occured while executing the
|
||||||
|
:ref:`Audit <audit_definition>`
|
||||||
|
- **DELETED** : the :ref:`Audit <audit_definition>` is still stored in the
|
||||||
|
:ref:`Watcher database <watcher_database_definition>` but is not returned
|
||||||
|
any more through the Watcher APIs.
|
||||||
|
- **CANCELLED** : the :ref:`Audit <audit_definition>` was in **PENDING** or
|
||||||
|
**ONGOING** state and was cancelled by the
|
||||||
|
:ref:`Administrator <administrator_definition>`
|
||||||
|
|
||||||
|
The following diagram shows the different possible states of an
|
||||||
|
:ref:`Audit <audit_definition>` and what event makes the state change to a new
|
||||||
|
value:
|
||||||
|
|
||||||
|
.. image:: ./images/audit_state_machine.png
|
||||||
|
:width: 100%
|
||||||
|
|
||||||
|
.. _action_plan_state_machine:
|
||||||
|
|
||||||
|
Action Plan State Machine
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
An :ref:`Action Plan <action_plan_definition>` has a life-cycle and its current
|
||||||
|
state may be one of the following:
|
||||||
|
|
||||||
|
- **RECOMMENDED** : the :ref:`Action Plan <action_plan_definition>` is waiting
|
||||||
|
for a validation from the :ref:`Administrator <administrator_definition>`
|
||||||
|
- **PENDING** : a request for an :ref:`Action Plan <action_plan_definition>`
|
||||||
|
has been submitted (due to an
|
||||||
|
:ref:`Administrator <administrator_definition>` executing an
|
||||||
|
:ref:`Audit <audit_definition>`) and is in the queue for
|
||||||
|
being processed by the :ref:`Watcher Applier <watcher_applier_definition>`
|
||||||
|
- **ONGOING** : the :ref:`Action Plan <action_plan_definition>` is currently
|
||||||
|
being processed by the :ref:`Watcher Applier <watcher_applier_definition>`
|
||||||
|
- **SUCCEEDED** : the :ref:`Action Plan <action_plan_definition>` has been
|
||||||
|
executed successfully (i.e. all :ref:`Actions <action_definition>` that it
|
||||||
|
contains have been executed successfully)
|
||||||
|
- **FAILED** : an error occured while executing the
|
||||||
|
:ref:`Action Plan <action_plan_definition>`
|
||||||
|
- **DELETED** : the :ref:`Action Plan <action_plan_definition>` is still
|
||||||
|
stored in the :ref:`Watcher database <watcher_database_definition>` but is
|
||||||
|
not returned any more through the Watcher APIs.
|
||||||
|
- **CANCELLED** : the :ref:`Action Plan <action_plan_definition>` was in
|
||||||
|
**PENDING** or **ONGOING** state and was cancelled by the
|
||||||
|
:ref:`Administrator <administrator_definition>`
|
||||||
|
|
||||||
|
|
||||||
|
The following diagram shows the different possible states of an
|
||||||
|
:ref:`Action Plan <action_plan_definition>` and what event makes the state
|
||||||
|
change to a new value:
|
||||||
|
|
||||||
|
.. image:: ./images/action_plan_state_machine.png
|
||||||
|
:width: 100%
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. _Watcher API: webapi/v1.html
|
.. _Watcher API: webapi/v1.html
|
||||||
|
|||||||
@@ -11,19 +11,15 @@
|
|||||||
# implied.
|
# implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
from watcher import version as watcher_version
|
from watcher import version as watcher_version
|
||||||
|
|
||||||
sys.path.insert(0, os.path.abspath('../..'))
|
|
||||||
# -- General configuration ----------------------------------------------------
|
# -- General configuration ----------------------------------------------------
|
||||||
|
|
||||||
# Add any Sphinx extension module names here, as strings. They can be
|
# Add any Sphinx extension module names here, as strings. They can be
|
||||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||||
extensions = [
|
extensions = [
|
||||||
|
'oslo_config.sphinxconfiggen',
|
||||||
'sphinx.ext.autodoc',
|
'sphinx.ext.autodoc',
|
||||||
# 'sphinx.ext.intersphinx',
|
|
||||||
'sphinx.ext.viewcode',
|
'sphinx.ext.viewcode',
|
||||||
'sphinxcontrib.httpdomain',
|
'sphinxcontrib.httpdomain',
|
||||||
'sphinxcontrib.pecanwsme.rest',
|
'sphinxcontrib.pecanwsme.rest',
|
||||||
@@ -33,7 +29,8 @@ extensions = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
wsme_protocols = ['restjson']
|
wsme_protocols = ['restjson']
|
||||||
|
config_generator_config_file = '../../etc/watcher/watcher-config-generator.conf'
|
||||||
|
sample_config_basename = 'watcher'
|
||||||
|
|
||||||
# autodoc generation is a bit aggressive and a nuisance when doing heavy
|
# autodoc generation is a bit aggressive and a nuisance when doing heavy
|
||||||
# text edit cycles.
|
# text edit cycles.
|
||||||
@@ -46,8 +43,8 @@ source_suffix = '.rst'
|
|||||||
master_doc = 'index'
|
master_doc = 'index'
|
||||||
|
|
||||||
# General information about the project.
|
# General information about the project.
|
||||||
project = u'watcher'
|
project = u'Watcher'
|
||||||
copyright = u'2015, OpenStack Foundation'
|
copyright = u'OpenStack Foundation'
|
||||||
|
|
||||||
# The version info for the project you're documenting, acts as replacement for
|
# The version info for the project you're documenting, acts as replacement for
|
||||||
# |version| and |release|, also used in various other places throughout the
|
# |version| and |release|, also used in various other places throughout the
|
||||||
@@ -62,6 +59,14 @@ version = watcher_version.version_info.version_string()
|
|||||||
# A list of ignored prefixes for module index sorting.
|
# A list of ignored prefixes for module index sorting.
|
||||||
modindex_common_prefix = ['watcher.']
|
modindex_common_prefix = ['watcher.']
|
||||||
|
|
||||||
|
exclude_patterns = [
|
||||||
|
# The man directory includes some snippet files that are included
|
||||||
|
# in other documents during the build but that should not be
|
||||||
|
# included in the toctree themselves, so tell Sphinx to ignore
|
||||||
|
# them when scanning for input files.
|
||||||
|
'man/footer.rst',
|
||||||
|
'man/general-options.rst',
|
||||||
|
]
|
||||||
|
|
||||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||||
add_function_parentheses = True
|
add_function_parentheses = True
|
||||||
@@ -73,6 +78,22 @@ add_module_names = True
|
|||||||
# The name of the Pygments (syntax highlighting) style to use.
|
# The name of the Pygments (syntax highlighting) style to use.
|
||||||
pygments_style = 'sphinx'
|
pygments_style = 'sphinx'
|
||||||
|
|
||||||
|
# -- Options for man page output --------------------------------------------
|
||||||
|
|
||||||
|
# Grouping the document tree for man pages.
|
||||||
|
# List of tuples 'sourcefile', 'target', u'title', u'Authors name', 'manual'
|
||||||
|
|
||||||
|
man_pages = [
|
||||||
|
('man/watcher-api', 'watcher-api', u'Watcher API Server',
|
||||||
|
[u'OpenStack'], 1),
|
||||||
|
('man/watcher-applier', 'watcher-applier', u'Watcher Applier',
|
||||||
|
[u'OpenStack'], 1),
|
||||||
|
('man/watcher-db-manage', 'watcher-db-manage',
|
||||||
|
u'Watcher Db Management Utility', [u'OpenStack'], 1),
|
||||||
|
('man/watcher-decision-engine', 'watcher-decision-engine',
|
||||||
|
u'Watcher Decision Engine', [u'OpenStack'], 1),
|
||||||
|
]
|
||||||
|
|
||||||
# -- Options for HTML output --------------------------------------------------
|
# -- Options for HTML output --------------------------------------------------
|
||||||
|
|
||||||
# The theme to use for HTML and HTML Help pages. Major themes that come with
|
# The theme to use for HTML and HTML Help pages. Major themes that come with
|
||||||
|
|||||||
1
doc/source/config-generator.conf
Normal file
@@ -0,0 +1 @@
|
|||||||
|
../../etc/watcher/watcher-config-generator.conf
|
||||||
14
doc/source/deploy/conf-files.rst
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
.. _watcher_sample_configuration_files:
|
||||||
|
|
||||||
|
==================================
|
||||||
|
Watcher sample configuration files
|
||||||
|
==================================
|
||||||
|
|
||||||
|
watcher.conf
|
||||||
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The ``watcher.conf`` file contains most of the options to configure the
|
||||||
|
Watcher services.
|
||||||
|
|
||||||
|
.. literalinclude:: ../watcher.conf.sample
|
||||||
|
:language: ini
|
||||||
@@ -34,6 +34,8 @@ The Watcher service includes the following components:
|
|||||||
- ``watcher-applier``: applies the action plan.
|
- ``watcher-applier``: applies the action plan.
|
||||||
- `python-watcherclient`_: A command-line interface (CLI) for interacting with
|
- `python-watcherclient`_: A command-line interface (CLI) for interacting with
|
||||||
the Watcher service.
|
the Watcher service.
|
||||||
|
- `watcher-dashboard`_: An Horizon plugin for interacting with the Watcher
|
||||||
|
service.
|
||||||
|
|
||||||
Additionally, the Bare Metal service has certain external dependencies, which
|
Additionally, the Bare Metal service has certain external dependencies, which
|
||||||
are very similar to other OpenStack services:
|
are very similar to other OpenStack services:
|
||||||
@@ -52,6 +54,7 @@ additional functionality:
|
|||||||
.. _`ceilometer`: https://github.com/openstack/ceilometer
|
.. _`ceilometer`: https://github.com/openstack/ceilometer
|
||||||
.. _`nova`: https://github.com/openstack/nova
|
.. _`nova`: https://github.com/openstack/nova
|
||||||
.. _`python-watcherclient`: https://github.com/openstack/python-watcherclient
|
.. _`python-watcherclient`: https://github.com/openstack/python-watcherclient
|
||||||
|
.. _`watcher-dashboard`: https://github.com/openstack/watcher-dashboard
|
||||||
.. _`watcher metering`: https://github.com/b-com/watcher-metering
|
.. _`watcher metering`: https://github.com/b-com/watcher-metering
|
||||||
.. _`RabbitMQ`: https://www.rabbitmq.com/
|
.. _`RabbitMQ`: https://www.rabbitmq.com/
|
||||||
|
|
||||||
@@ -160,17 +163,33 @@ Configure the Watcher service
|
|||||||
The Watcher service is configured via its configuration file. This file
|
The Watcher service is configured via its configuration file. This file
|
||||||
is typically located at ``/etc/watcher/watcher.conf``.
|
is typically located at ``/etc/watcher/watcher.conf``.
|
||||||
|
|
||||||
|
You can easily generate and update a sample configuration file
|
||||||
|
named :ref:`watcher.conf.sample <watcher_sample_configuration_files>` by using
|
||||||
|
these following commands::
|
||||||
|
|
||||||
|
$ git clone git://git.openstack.org/openstack/watcher
|
||||||
|
$ cd watcher/
|
||||||
|
$ tox -econfig
|
||||||
|
$ vi etc/watcher/watcher.conf.sample
|
||||||
|
|
||||||
|
|
||||||
The configuration file is organized into the following sections:
|
The configuration file is organized into the following sections:
|
||||||
|
|
||||||
* ``[DEFAULT]`` - General configuration
|
* ``[DEFAULT]`` - General configuration
|
||||||
* ``[api]`` - API server configuration
|
* ``[api]`` - API server configuration
|
||||||
* ``[database]`` - SQL driver configuration
|
* ``[database]`` - SQL driver configuration
|
||||||
* ``[keystone_authtoken]`` - Keystone Authentication plugin configuration
|
* ``[keystone_authtoken]`` - Keystone Authentication plugin configuration
|
||||||
|
* ``[watcher_clients_auth]`` - Keystone auth configuration for clients
|
||||||
* ``[watcher_applier]`` - Watcher Applier module configuration
|
* ``[watcher_applier]`` - Watcher Applier module configuration
|
||||||
* ``[watcher_decision_engine]`` - Watcher Decision Engine module configuration
|
* ``[watcher_decision_engine]`` - Watcher Decision Engine module configuration
|
||||||
* ``[watcher_goals]`` - Goals mapping configuration
|
* ``[watcher_goals]`` - Goals mapping configuration
|
||||||
* ``[watcher_strategies]`` - Strategy configuration
|
* ``[watcher_strategies]`` - Strategy configuration
|
||||||
* ``[oslo_messaging_rabbit]`` - Oslo Messaging RabbitMQ driver configuration
|
* ``[oslo_messaging_rabbit]`` - Oslo Messaging RabbitMQ driver configuration
|
||||||
|
* ``[ceilometer_client]`` - Ceilometer client configuration
|
||||||
|
* ``[cinder_client]`` - Cinder client configuration
|
||||||
|
* ``[glance_client]`` - Glance client configuration
|
||||||
|
* ``[nova_client]`` - Nova client configuration
|
||||||
|
* ``[neutron_client]`` - Neutron client configuration
|
||||||
|
|
||||||
The Watcher configuration file is expected to be named
|
The Watcher configuration file is expected to be named
|
||||||
``watcher.conf``. When starting Watcher, you can specify a different
|
``watcher.conf``. When starting Watcher, you can specify a different
|
||||||
@@ -237,39 +256,105 @@ so that the watcher service is configured for your needs.
|
|||||||
#rabbit_port = 5672
|
#rabbit_port = 5672
|
||||||
|
|
||||||
|
|
||||||
#. Configure the Watcher Service to use these credentials with the Identity
|
#. Watcher API shall validate the token provided by every incoming request,
|
||||||
Service. Replace IDENTITY_IP with the IP of the Identity server, and
|
via keystonemiddleware, which requires the Watcher service to be configured
|
||||||
replace WATCHER_PASSWORD with the password you chose for the ``watcher``
|
with the right credentials for the Identity service.
|
||||||
user in the Identity Service::
|
|
||||||
|
In the configuration section here below:
|
||||||
|
|
||||||
|
* replace IDENTITY_IP with the IP of the Identity server
|
||||||
|
* replace WATCHER_PASSWORD with the password you chose for the ``watcher``
|
||||||
|
user
|
||||||
|
* replace KEYSTONE_SERVICE_PROJECT_NAME with the name of project created
|
||||||
|
for OpenStack services (e.g. ``service``) ::
|
||||||
|
|
||||||
[keystone_authtoken]
|
[keystone_authtoken]
|
||||||
|
|
||||||
# Complete public Identity API endpoint (string value)
|
# Authentication type to load (unknown value)
|
||||||
#auth_uri=<None>
|
# Deprecated group/name - [DEFAULT]/auth_plugin
|
||||||
auth_uri=http://IDENTITY_IP:5000/v3
|
#auth_type = <None>
|
||||||
|
auth_type = password
|
||||||
|
|
||||||
# Complete admin Identity API endpoint. This should specify the
|
# Authentication URL (unknown value)
|
||||||
# unversioned root endpoint e.g. https://localhost:35357/ (string
|
#auth_url = <None>
|
||||||
# value)
|
auth_url = http://IDENTITY_IP:35357
|
||||||
#identity_uri = <None>
|
|
||||||
identity_uri = http://IDENTITY_IP:5000
|
|
||||||
|
|
||||||
# Keystone account username (string value)
|
# Username (unknown value)
|
||||||
#admin_user=<None>
|
# Deprecated group/name - [DEFAULT]/username
|
||||||
admin_user=watcher
|
#username = <None>
|
||||||
|
username=watcher
|
||||||
|
|
||||||
# Keystone account password (string value)
|
# User's password (unknown value)
|
||||||
#admin_password=<None>
|
#password = <None>
|
||||||
admin_password=WATCHER_DBPASSWORD
|
password = WATCHER_PASSWORD
|
||||||
|
|
||||||
# Keystone service account tenant name to validate user tokens
|
# Domain ID containing project (unknown value)
|
||||||
# (string value)
|
#project_domain_id = <None>
|
||||||
#admin_tenant_name=admin
|
project_domain_id = default
|
||||||
admin_tenant_name=KEYSTONE_SERVICE_PROJECT_NAME
|
|
||||||
|
|
||||||
# Directory used to cache files related to PKI tokens (string
|
# User's domain id (unknown value)
|
||||||
# value)
|
#user_domain_id = <None>
|
||||||
#signing_dir=<None>
|
user_domain_id = default
|
||||||
|
|
||||||
|
# Project name to scope to (unknown value)
|
||||||
|
# Deprecated group/name - [DEFAULT]/tenant-name
|
||||||
|
#project_name = <None>
|
||||||
|
project_name = KEYSTONE_SERVICE_PROJECT_NAME
|
||||||
|
|
||||||
|
#. Watcher's decision engine and applier interact with other OpenStack
|
||||||
|
projects through those projects' clients. In order to instantiate these
|
||||||
|
clients, Watcher needs to request a new session from the Identity service
|
||||||
|
using the right credentials.
|
||||||
|
|
||||||
|
In the configuration section here below:
|
||||||
|
|
||||||
|
* replace IDENTITY_IP with the IP of the Identity server
|
||||||
|
* replace WATCHER_PASSWORD with the password you chose for the ``watcher``
|
||||||
|
user
|
||||||
|
* replace KEYSTONE_SERVICE_PROJECT_NAME with the name of project created
|
||||||
|
for OpenStack services (e.g. ``service``) ::
|
||||||
|
|
||||||
|
[watcher_clients_auth]
|
||||||
|
|
||||||
|
# Authentication type to load (unknown value)
|
||||||
|
# Deprecated group/name - [DEFAULT]/auth_plugin
|
||||||
|
#auth_type = <None>
|
||||||
|
auth_type = password
|
||||||
|
|
||||||
|
# Authentication URL (unknown value)
|
||||||
|
#auth_url = <None>
|
||||||
|
auth_url = http://IDENTITY_IP:35357
|
||||||
|
|
||||||
|
# Username (unknown value)
|
||||||
|
# Deprecated group/name - [DEFAULT]/username
|
||||||
|
#username = <None>
|
||||||
|
username=watcher
|
||||||
|
|
||||||
|
# User's password (unknown value)
|
||||||
|
#password = <None>
|
||||||
|
password = WATCHER_PASSWORD
|
||||||
|
|
||||||
|
# Domain ID containing project (unknown value)
|
||||||
|
#project_domain_id = <None>
|
||||||
|
project_domain_id = default
|
||||||
|
|
||||||
|
# User's domain id (unknown value)
|
||||||
|
#user_domain_id = <None>
|
||||||
|
user_domain_id = default
|
||||||
|
|
||||||
|
# Project name to scope to (unknown value)
|
||||||
|
# Deprecated group/name - [DEFAULT]/tenant-name
|
||||||
|
#project_name = <None>
|
||||||
|
project_name = KEYSTONE_SERVICE_PROJECT_NAME
|
||||||
|
|
||||||
|
#. Configure the clients to use a specific version if desired. For example, to
|
||||||
|
configure Watcher to use a Nova client with version 2.1, use::
|
||||||
|
|
||||||
|
[nova_client]
|
||||||
|
|
||||||
|
# Version of Nova API to use in novaclient. (string value)
|
||||||
|
#api_version = 2
|
||||||
|
api_version = 2.1
|
||||||
|
|
||||||
#. Create the Watcher Service database tables::
|
#. Create the Watcher Service database tables::
|
||||||
|
|
||||||
@@ -318,3 +403,33 @@ pick any database system you prefer.
|
|||||||
The original implementation has been based on MongoDB but you can create your
|
The original implementation has been based on MongoDB but you can create your
|
||||||
own storage driver using whatever technology you want.
|
own storage driver using whatever technology you want.
|
||||||
For more information : https://wiki.openstack.org/wiki/Gnocchi
|
For more information : https://wiki.openstack.org/wiki/Gnocchi
|
||||||
|
|
||||||
|
|
||||||
|
Workers
|
||||||
|
=======
|
||||||
|
|
||||||
|
You can define a number of workers for the Decision Engine and the Applier.
|
||||||
|
|
||||||
|
If you want to create and run more audits simultaneously, you have to raise
|
||||||
|
the number of workers used by the Decision Engine::
|
||||||
|
|
||||||
|
[watcher_decision_engine]
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
# The maximum number of threads that can be used to execute strategies
|
||||||
|
# (integer value)
|
||||||
|
#max_workers = 2
|
||||||
|
|
||||||
|
|
||||||
|
If you want to execute simultaneously more recommended action plans, you
|
||||||
|
have to raise the number of workers used by the Applier::
|
||||||
|
|
||||||
|
[watcher_applier]
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
# Number of workers for applier, default value is 1. (integer value)
|
||||||
|
# Minimum value: 1
|
||||||
|
#workers = 1
|
||||||
|
|
||||||
|
|||||||
@@ -6,21 +6,30 @@
|
|||||||
|
|
||||||
.. _user-guide:
|
.. _user-guide:
|
||||||
|
|
||||||
=================================
|
==================
|
||||||
Welcome to the Watcher User Guide
|
Watcher User Guide
|
||||||
=================================
|
==================
|
||||||
|
|
||||||
|
See the
|
||||||
|
`architecture page <https://factory.b-com.com/www/watcher/doc/watcher/architecture.html>`_
|
||||||
|
for an architectural overview of the different components of Watcher and how
|
||||||
|
they fit together.
|
||||||
|
|
||||||
In the `architecture <https://wiki.openstack.org/wiki/WatcherArchitecture>`_
|
|
||||||
you got information about how it works.
|
|
||||||
In this guide we're going to take you through the fundamentals of using
|
In this guide we're going to take you through the fundamentals of using
|
||||||
Watcher.
|
Watcher.
|
||||||
|
|
||||||
|
The following diagram shows the main interactions between the
|
||||||
|
:ref:`Administrator <administrator_definition>` and the Watcher system:
|
||||||
|
|
||||||
|
.. image:: ../images/sequence_overview_watcher_usage.png
|
||||||
|
:width: 100%
|
||||||
|
|
||||||
|
|
||||||
Getting started with Watcher
|
Getting started with Watcher
|
||||||
----------------------------
|
----------------------------
|
||||||
This guide assumes you have a working installation of Watcher. If you get
|
This guide assumes you have a working installation of Watcher. If you get
|
||||||
"*watcher: command not found*" you may have to verify your installation.
|
"*watcher: command not found*" you may have to verify your installation.
|
||||||
Please refer to the :doc:`installation guide <installation>`.
|
Please refer to the `installation guide`_.
|
||||||
In order to use Watcher, you have to configure your credentials suitable for
|
In order to use Watcher, you have to configure your credentials suitable for
|
||||||
watcher command-line tools.
|
watcher command-line tools.
|
||||||
If you need help on a specific command, you can use:
|
If you need help on a specific command, you can use:
|
||||||
@@ -29,6 +38,12 @@ If you need help on a specific command, you can use:
|
|||||||
|
|
||||||
$ watcher help COMMAND
|
$ watcher help COMMAND
|
||||||
|
|
||||||
|
If you want to deploy Watcher in Horizon, please refer to the `Watcher Horizon
|
||||||
|
plugin installation guide`_.
|
||||||
|
|
||||||
|
.. _`installation guide`: https://factory.b-com.com/www/watcher/doc/python-watcherclient
|
||||||
|
.. _`Watcher Horizon plugin installation guide`: https://factory.b-com.com/www/watcher/doc/watcher-dashboard/deploy/installation.html
|
||||||
|
|
||||||
Seeing what the Watcher CLI can do ?
|
Seeing what the Watcher CLI can do ?
|
||||||
------------------------------------
|
------------------------------------
|
||||||
We can see all of the commands available with Watcher CLI by running the
|
We can see all of the commands available with Watcher CLI by running the
|
||||||
@@ -49,7 +64,7 @@ This goal should be declared in the Watcher service configuration file
|
|||||||
|
|
||||||
.. code:: bash
|
.. code:: bash
|
||||||
|
|
||||||
$ watcher audit-template-create my_first_audit SERVERS_CONSOLIDATION
|
$ watcher audit-template-create my_first_audit DUMMY
|
||||||
|
|
||||||
If you get "*You must provide a username via either --os-username or via
|
If you get "*You must provide a username via either --os-username or via
|
||||||
env[OS_USERNAME]*" you may have to verify your credentials.
|
env[OS_USERNAME]*" you may have to verify your credentials.
|
||||||
@@ -90,7 +105,7 @@ configuration file.
|
|||||||
$ watcher action-plan-list --audit <the_audit_uuid>
|
$ watcher action-plan-list --audit <the_audit_uuid>
|
||||||
|
|
||||||
- Have a look on the list of optimization :ref:`actions <action_definition>`
|
- Have a look on the list of optimization :ref:`actions <action_definition>`
|
||||||
contained in this new :ref:`action plan <action_plan_definition>`:
|
contained in this new :ref:`action plan <action_plan_definition>`:
|
||||||
|
|
||||||
.. code:: bash
|
.. code:: bash
|
||||||
|
|
||||||
@@ -119,3 +134,4 @@ You can also obtain more detailed information about a specific action:
|
|||||||
.. code:: bash
|
.. code:: bash
|
||||||
|
|
||||||
$ watcher action-show <the_action_uuid>
|
$ watcher action-show <the_action_uuid>
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,9 @@
|
|||||||
|
|
||||||
.. _contributing:
|
.. _contributing:
|
||||||
|
|
||||||
======================
|
=======================
|
||||||
Contributing to Watcher
|
Contributing to Watcher
|
||||||
======================
|
=======================
|
||||||
|
|
||||||
If you're interested in contributing to the Watcher project,
|
If you're interested in contributing to the Watcher project,
|
||||||
the following will help get you started.
|
the following will help get you started.
|
||||||
@@ -43,7 +43,7 @@ notifications of important events.
|
|||||||
|
|
||||||
|
|
||||||
Project Hosting Details
|
Project Hosting Details
|
||||||
-------------------------
|
-----------------------
|
||||||
|
|
||||||
Bug tracker
|
Bug tracker
|
||||||
http://launchpad.net/watcher
|
http://launchpad.net/watcher
|
||||||
@@ -60,3 +60,12 @@ Code Hosting
|
|||||||
Code Review
|
Code Review
|
||||||
https://review.openstack.org/#/q/status:open+project:openstack/watcher,n,z
|
https://review.openstack.org/#/q/status:open+project:openstack/watcher,n,z
|
||||||
|
|
||||||
|
IRC Channel
|
||||||
|
``#openstack-watcher`` (changelog_)
|
||||||
|
|
||||||
|
Weekly Meetings
|
||||||
|
on Wednesdays at 14:00 UTC in the ``#openstack-meeting-4`` IRC
|
||||||
|
channel (`meetings logs`_)
|
||||||
|
|
||||||
|
.. _changelog: http://eavesdrop.openstack.org/irclogs/%23openstack-watcher/
|
||||||
|
.. _meetings logs: http://eavesdrop.openstack.org/meetings/watcher/
|
||||||
|
|||||||
204
doc/source/dev/devstack.rst
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
..
|
||||||
|
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/
|
||||||
|
|
||||||
|
=============================================
|
||||||
|
Set up a development environment via DevStack
|
||||||
|
=============================================
|
||||||
|
|
||||||
|
Watcher is currently able to optimize compute resources - specifically Nova
|
||||||
|
compute hosts - via operations such as live migrations. In order for you to
|
||||||
|
fully be able to exercise what Watcher can do, it is necessary to have a
|
||||||
|
multinode environment to use.
|
||||||
|
|
||||||
|
You can set up the Watcher services quickly and easily using a Watcher
|
||||||
|
DevStack plugin. See `PluginModelDocs`_ for information on DevStack's plugin
|
||||||
|
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
|
||||||
|
Watcher plugin::
|
||||||
|
|
||||||
|
enable_plugin watcher git://git.openstack.org/openstack/watcher
|
||||||
|
|
||||||
|
For more detailed instructions, see `Detailed DevStack Instructions`_. Check
|
||||||
|
out the `DevStack documentation`_ for more information regarding DevStack.
|
||||||
|
|
||||||
|
.. _PluginModelDocs: http://docs.openstack.org/developer/devstack/plugins.html
|
||||||
|
.. _DevStack documentation: http://docs.openstack.org/developer/devstack/
|
||||||
|
|
||||||
|
Detailed DevStack Instructions
|
||||||
|
==============================
|
||||||
|
|
||||||
|
#. Obtain N (where N >= 1) servers (virtual machines preferred for DevStack).
|
||||||
|
One of these servers will be the controller node while the others will be
|
||||||
|
compute nodes. N is preferably >= 3 so that you have at least 2 compute
|
||||||
|
nodes, but in order to stand up the Watcher services only 1 server is
|
||||||
|
needed (i.e., no computes are needed if you want to just experiment with
|
||||||
|
the Watcher services). These servers can be VMs running on your local
|
||||||
|
machine via VirtualBox if you prefer. DevStack currently recommends that
|
||||||
|
you use Ubuntu 14.04 LTS. The servers should also have connections to the
|
||||||
|
same network such that they are all able to communicate with one another.
|
||||||
|
|
||||||
|
#. For each server, clone the DevStack repository and create the stack user::
|
||||||
|
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install git
|
||||||
|
git clone https://git.openstack.org/openstack-dev/devstack
|
||||||
|
sudo ./devstack/tools/create-stack-user.sh
|
||||||
|
|
||||||
|
Now you have a stack user that is used to run the DevStack processes. You
|
||||||
|
may want to give your stack user a password to allow SSH via a password::
|
||||||
|
|
||||||
|
sudo passwd stack
|
||||||
|
|
||||||
|
#. Switch to the stack user and clone the DevStack repo again::
|
||||||
|
|
||||||
|
sudo su stack
|
||||||
|
cd ~
|
||||||
|
git clone https://git.openstack.org/openstack-dev/devstack
|
||||||
|
|
||||||
|
#. For each compute node, copy the provided `local.conf.compute`_ example file
|
||||||
|
to the compute node's system at ~/devstack/local.conf. Make sure the
|
||||||
|
HOST_IP and SERVICE_HOST values are changed appropriately - i.e., HOST_IP
|
||||||
|
is set to the IP address of the compute node and SERVICE_HOST is set to the
|
||||||
|
IP address of the controller node.
|
||||||
|
|
||||||
|
If you need specific metrics collected (or want to use something other
|
||||||
|
than Ceilometer), be sure to configure it. For example, in the
|
||||||
|
`local.conf.compute`_ example file, the appropriate ceilometer plugins and
|
||||||
|
services are enabled and disabled. If you were using something other than
|
||||||
|
Ceilometer, then you would likely want to configure it likewise. The
|
||||||
|
example file also sets the compute monitors nova configuration option to
|
||||||
|
use the CPU virt driver. If you needed other metrics, it may be necessary
|
||||||
|
to configure similar configuration options for the projects providing those
|
||||||
|
metrics.
|
||||||
|
|
||||||
|
#. For the controller node, copy the provided `local.conf.controller`_ example
|
||||||
|
file to the controller node's system at ~/devstack/local.conf. Make sure
|
||||||
|
the HOST_IP value is changed appropriately - i.e., HOST_IP is set to the IP
|
||||||
|
address of the controller node.
|
||||||
|
|
||||||
|
Note: if you want to use another Watcher git repository (such as a local
|
||||||
|
one), then change the enable plugin line::
|
||||||
|
|
||||||
|
enable_plugin watcher <your_local_git_repo> [optional_branch]
|
||||||
|
|
||||||
|
If you do this, then the Watcher DevStack plugin will try to pull the
|
||||||
|
python-watcherclient repo from <your_local_git_repo>/../, so either make
|
||||||
|
sure that is also available or specify WATCHERCLIENT_REPO in the local.conf
|
||||||
|
file.
|
||||||
|
|
||||||
|
Note: if you want to use a specific branch, specify WATCHER_BRANCH in the
|
||||||
|
local.conf file. By default it will use the master branch.
|
||||||
|
|
||||||
|
#. Start stacking from the controller node::
|
||||||
|
|
||||||
|
./devstack/stack.sh
|
||||||
|
|
||||||
|
#. Start stacking on each of the compute nodes using the same command.
|
||||||
|
|
||||||
|
#. Configure the environment for live migration via NFS. See the
|
||||||
|
`Multi-Node DevStack Environment`_ section for more details.
|
||||||
|
|
||||||
|
.. _local.conf.controller: https://github.com/openstack/watcher/tree/master/devstack/local.conf.controller
|
||||||
|
.. _local.conf.compute: https://github.com/openstack/watcher/tree/master/devstack/local.conf.compute
|
||||||
|
|
||||||
|
Multi-Node DevStack Environment
|
||||||
|
===============================
|
||||||
|
|
||||||
|
Since deploying Watcher with only a single compute node is not very useful, a
|
||||||
|
few tips are given here for enabling a multi-node environment with live
|
||||||
|
migration.
|
||||||
|
|
||||||
|
Configuring NFS Server
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
If you would like to use live migration for shared storage, then the controller
|
||||||
|
can serve as the NFS server if needed::
|
||||||
|
|
||||||
|
sudo apt-get install nfs-kernel-server
|
||||||
|
sudo mkdir -p /nfs/instances
|
||||||
|
sudo chown stack:stack /nfs/instances
|
||||||
|
|
||||||
|
Add an entry to `/etc/exports` with the appropriate gateway and netmask
|
||||||
|
information::
|
||||||
|
|
||||||
|
/nfs/instances <gateway>/<netmask>(rw,fsid=0,insecure,no_subtree_check,async,no_root_squash)
|
||||||
|
|
||||||
|
Export the NFS directories::
|
||||||
|
|
||||||
|
sudo exportfs -ra
|
||||||
|
|
||||||
|
Make sure the NFS server is running::
|
||||||
|
|
||||||
|
sudo service nfs-kernel-server status
|
||||||
|
|
||||||
|
If the server is not running, then start it::
|
||||||
|
|
||||||
|
sudo service nfs-kernel-server start
|
||||||
|
|
||||||
|
Configuring NFS on Compute Node
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
Each compute node needs to use the NFS server to hold the instance data::
|
||||||
|
|
||||||
|
sudo apt-get install rpcbind nfs-common
|
||||||
|
mkdir -p /opt/stack/data/instances
|
||||||
|
sudo mount <nfs-server-ip>:/nfs/instances /opt/stack/data/instances
|
||||||
|
|
||||||
|
If you would like to have the NFS directory automatically mounted on reboot,
|
||||||
|
then add the following to `/etc/fstab`::
|
||||||
|
|
||||||
|
<nfs-server-ip>:/nfs/instances /opt/stack/data/instances nfs auto 0 0
|
||||||
|
|
||||||
|
Edit `/etc/libvirt/libvirtd.conf` to make sure the following values are set::
|
||||||
|
|
||||||
|
listen_tls = 0
|
||||||
|
listen_tcp = 1
|
||||||
|
auth_tcp = "none"
|
||||||
|
|
||||||
|
Edit `/etc/default/libvirt-bin`::
|
||||||
|
|
||||||
|
libvirt_opts="-d -l"
|
||||||
|
|
||||||
|
Restart the libvirt service::
|
||||||
|
|
||||||
|
sudo service libvirt-bin restart
|
||||||
|
|
||||||
|
Setting up SSH keys between compute nodes to enable live migration
|
||||||
|
------------------------------------------------------------------
|
||||||
|
|
||||||
|
In order for live migration to work, SSH keys need to be exchanged between
|
||||||
|
each compute node:
|
||||||
|
|
||||||
|
1. The SOURCE root user's public RSA key (likely in /root/.ssh/id_rsa.pub)
|
||||||
|
needs to be in the DESTINATION stack user's authorized_keys file
|
||||||
|
(~stack/.ssh/authorized_keys). This can be accomplished by manually
|
||||||
|
copying the contents from the file on the SOURCE to the DESTINATION. If
|
||||||
|
you have a password configured for the stack user, then you can use the
|
||||||
|
following command to accomplish the same thing::
|
||||||
|
|
||||||
|
ssh-copy-id -i /root/.ssh/id_rsa.pub stack@DESTINATION
|
||||||
|
|
||||||
|
2. The DESTINATION host's public ECDSA key (/etc/ssh/ssh_host_ecdsa_key.pub)
|
||||||
|
needs to be in the SOURCE root user's known_hosts file
|
||||||
|
(/root/.ssh/known_hosts). This can be accomplished by running the
|
||||||
|
following on the SOURCE machine (hostname must be used)::
|
||||||
|
|
||||||
|
ssh-keyscan -H DEST_HOSTNAME | sudo tee -a /root/.ssh/known_hosts
|
||||||
|
|
||||||
|
In essence, this means that every compute node's root user's public RSA key
|
||||||
|
must exist in every other compute node's stack user's authorized_keys file and
|
||||||
|
every compute node's public ECDSA key needs to be in every other compute
|
||||||
|
node's root user's known_hosts file.
|
||||||
|
|
||||||
|
|
||||||
|
Environment final checkup
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
If you are willing to make sure everything is in order in your DevStack
|
||||||
|
environment, you can run the Watcher Tempest tests which will validate its API
|
||||||
|
but also that you can perform the typical Watcher workflows. To do so, have a
|
||||||
|
look at the :ref:`Tempest tests <tempest_tests>` section which will explain to
|
||||||
|
you how to run them.
|
||||||
@@ -4,9 +4,11 @@
|
|||||||
|
|
||||||
https://creativecommons.org/licenses/by/3.0/
|
https://creativecommons.org/licenses/by/3.0/
|
||||||
|
|
||||||
============================================
|
.. _watcher_developement_environment:
|
||||||
Setting up a Watcher development environment
|
|
||||||
============================================
|
=========================================
|
||||||
|
Set up a development environment manually
|
||||||
|
=========================================
|
||||||
|
|
||||||
This document describes getting the source from watcher `Git repository`_
|
This document describes getting the source from watcher `Git repository`_
|
||||||
for development purposes.
|
for development purposes.
|
||||||
@@ -75,15 +77,11 @@ extension, PyPi) cannot satisfy. These dependencies should be installed
|
|||||||
prior to using `pip`, and the installation method may vary depending on
|
prior to using `pip`, and the installation method may vary depending on
|
||||||
your platform.
|
your platform.
|
||||||
|
|
||||||
* Ubuntu 14.04:
|
* Ubuntu 14.04::
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
$ sudo apt-get install python-dev libssl-dev libmysqlclient-dev libffi-dev
|
$ sudo apt-get install python-dev libssl-dev libmysqlclient-dev libffi-dev
|
||||||
|
|
||||||
* Fedora 19+:
|
* Fedora 19+::
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
$ sudo yum install openssl-devel libffi-devel mysql-devel
|
$ sudo yum install openssl-devel libffi-devel mysql-devel
|
||||||
|
|
||||||
@@ -147,34 +145,13 @@ You should then be able to `import watcher` using Python without issue:
|
|||||||
|
|
||||||
If you can import watcher without a traceback, you should be ready to develop.
|
If you can import watcher without a traceback, you should be ready to develop.
|
||||||
|
|
||||||
Run Watcher unit tests
|
Run Watcher tests
|
||||||
======================
|
=================
|
||||||
|
|
||||||
All unit tests should be run using tox. To run the unit tests under py27 and
|
Watcher provides both :ref:`unit tests <unit_tests>` and
|
||||||
also run the pep8 tests:
|
:ref:`functional/tempest tests <tempest_tests>`. Please refer to :doc:`testing`
|
||||||
|
to understand how to run them.
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
$ workon watcher
|
|
||||||
(watcher) $ pip install tox
|
|
||||||
|
|
||||||
(watcher) $ cd watcher
|
|
||||||
(watcher) $ tox -epep8 -epy27
|
|
||||||
|
|
||||||
You may pass options to the test programs using positional arguments. To run a
|
|
||||||
specific unit test, this passes the -r option and desired test (regex string)
|
|
||||||
to os-testr:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
$ workon watcher
|
|
||||||
(watcher) $ tox -epy27 -- tests.api
|
|
||||||
|
|
||||||
When you're done, deactivate the virtualenv:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
$ deactivate
|
|
||||||
|
|
||||||
Build the Watcher documentation
|
Build the Watcher documentation
|
||||||
===============================
|
===============================
|
||||||
@@ -273,6 +250,11 @@ interface.
|
|||||||
|
|
||||||
.. _`python-watcherclient`: https://github.com/openstack/python-watcherclient
|
.. _`python-watcherclient`: https://github.com/openstack/python-watcherclient
|
||||||
|
|
||||||
|
There is also an Horizon plugin for Watcher `watcher-dashboard`_ which
|
||||||
|
allows to interact with Watcher through a web-based interface.
|
||||||
|
|
||||||
|
.. _`watcher-dashboard`: https://github.com/openstack/watcher-dashboard
|
||||||
|
|
||||||
|
|
||||||
Exercising the Watcher Services locally
|
Exercising the Watcher Services locally
|
||||||
=======================================
|
=======================================
|
||||||
@@ -287,4 +269,3 @@ template files to easily play with Watcher services within a minimal OpenStack
|
|||||||
isolated environment (Identity, Message Bus, SQL database, Horizon, ...).
|
isolated environment (Identity, Message Bus, SQL database, Horizon, ...).
|
||||||
|
|
||||||
.. _`watcher-tools`: https://github.com/b-com/watcher-tools
|
.. _`watcher-tools`: https://github.com/b-com/watcher-tools
|
||||||
|
|
||||||
|
|||||||
171
doc/source/dev/plugin/action-plugin.rst
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
..
|
||||||
|
Except where otherwise noted, this document is licensed under Creative
|
||||||
|
Commons Attribution 3.0 License. You can view the license at:
|
||||||
|
|
||||||
|
https://creativecommons.org/licenses/by/3.0/
|
||||||
|
|
||||||
|
==================
|
||||||
|
Build a new action
|
||||||
|
==================
|
||||||
|
|
||||||
|
Watcher Applier has an external :ref:`action <action_definition>` plugin
|
||||||
|
interface which gives anyone the ability to integrate an external
|
||||||
|
:ref:`action <action_definition>` in order to extend the initial set of actions
|
||||||
|
Watcher provides.
|
||||||
|
|
||||||
|
This section gives some guidelines on how to implement and integrate custom
|
||||||
|
actions with Watcher.
|
||||||
|
|
||||||
|
|
||||||
|
Creating a new plugin
|
||||||
|
=====================
|
||||||
|
|
||||||
|
First of all you have to extend the base :py:class:`BaseAction` class which
|
||||||
|
defines a set of abstract methods and/or properties that you will have to
|
||||||
|
implement:
|
||||||
|
|
||||||
|
- The :py:attr:`~.BaseAction.schema` is an abstract property that you have to
|
||||||
|
implement. This is the first function to be called by the
|
||||||
|
:ref:`applier <watcher_applier_definition>` before any further processing
|
||||||
|
and its role is to validate the input parameters that were provided to it.
|
||||||
|
- The :py:meth:`~.BaseAction.precondition` is called before the execution of
|
||||||
|
an action. This method is a hook that can be used to perform some
|
||||||
|
initializations or to make some more advanced validation on its input
|
||||||
|
parameters. If you wish to block the execution based on this factor, you
|
||||||
|
simply have to ``raise`` an exception.
|
||||||
|
- The :py:meth:`~.BaseAction.postcondition` is called after the execution of
|
||||||
|
an action. As this function is called regardless of whether an action
|
||||||
|
succeeded or not, this can prove itself useful to perform cleanup
|
||||||
|
operations.
|
||||||
|
- The :py:meth:`~.BaseAction.execute` is the main component of an action.
|
||||||
|
This is where you should implement the logic of your action.
|
||||||
|
- The :py:meth:`~.BaseAction.revert` allows you to roll back the targeted
|
||||||
|
resource to its original state following a faulty execution. Indeed, this
|
||||||
|
method is called by the workflow engine whenever an action raises an
|
||||||
|
exception.
|
||||||
|
|
||||||
|
Here is an example showing how you can write a plugin called ``DummyAction``:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
# Filepath = <PROJECT_DIR>/thirdparty/dummy.py
|
||||||
|
# Import path = thirdparty.dummy
|
||||||
|
import voluptuous
|
||||||
|
|
||||||
|
from watcher.applier.actions import base
|
||||||
|
|
||||||
|
|
||||||
|
class DummyAction(baseBaseAction):
|
||||||
|
|
||||||
|
@property
|
||||||
|
def schema(self):
|
||||||
|
return voluptuous.Schema({})
|
||||||
|
|
||||||
|
def execute(self):
|
||||||
|
# Does nothing
|
||||||
|
pass # Only returning False is considered as a failure
|
||||||
|
|
||||||
|
def revert(self):
|
||||||
|
# Does nothing
|
||||||
|
pass
|
||||||
|
|
||||||
|
def precondition(self):
|
||||||
|
# No pre-checks are done here
|
||||||
|
pass
|
||||||
|
|
||||||
|
def postcondition(self):
|
||||||
|
# Nothing done here
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
This implementation is the most basic one. So if you want to have more advanced
|
||||||
|
examples, have a look at the implementation of the actions already provided
|
||||||
|
by Watcher like.
|
||||||
|
To get a better understanding on how to implement a more advanced action,
|
||||||
|
have a look at the :py:class:`~watcher.applier.actions.migration.Migrate`
|
||||||
|
class.
|
||||||
|
|
||||||
|
Input validation
|
||||||
|
----------------
|
||||||
|
|
||||||
|
As you can see in the previous example, we are using `Voluptuous`_ to validate
|
||||||
|
the input parameters of an action. So if you want to learn more about how to
|
||||||
|
work with `Voluptuous`_, you can have a look at their `documentation`_ here:
|
||||||
|
|
||||||
|
.. _Voluptuous: https://github.com/alecthomas/voluptuous
|
||||||
|
.. _documentation: https://github.com/alecthomas/voluptuous/blob/master/README.md
|
||||||
|
|
||||||
|
Abstract Plugin Class
|
||||||
|
=====================
|
||||||
|
|
||||||
|
Here below is the abstract ``BaseAction`` class that every single action
|
||||||
|
should implement:
|
||||||
|
|
||||||
|
.. autoclass:: watcher.applier.actions.base.BaseAction
|
||||||
|
:members:
|
||||||
|
:noindex:
|
||||||
|
|
||||||
|
.. py:attribute:: schema
|
||||||
|
|
||||||
|
Defines a Schema that the input parameters shall comply to
|
||||||
|
|
||||||
|
:returns: A schema declaring the input parameters this action should be
|
||||||
|
provided along with their respective constraints
|
||||||
|
(e.g. type, value range, ...)
|
||||||
|
:rtype: :py:class:`voluptuous.Schema` instance
|
||||||
|
|
||||||
|
|
||||||
|
Register a new entry point
|
||||||
|
==========================
|
||||||
|
|
||||||
|
In order for the Watcher Applier to load your new action, the
|
||||||
|
action must be registered as a named entry point under the
|
||||||
|
``watcher_actions`` entry point of your ``setup.py`` file. If you are using
|
||||||
|
pbr_, this entry point should be placed in your ``setup.cfg`` file.
|
||||||
|
|
||||||
|
The name you give to your entry point has to be unique.
|
||||||
|
|
||||||
|
Here below is how you would proceed to register ``DummyAction`` using pbr_:
|
||||||
|
|
||||||
|
.. code-block:: ini
|
||||||
|
|
||||||
|
[entry_points]
|
||||||
|
watcher_actions =
|
||||||
|
dummy = thirdparty.dummy:DummyAction
|
||||||
|
|
||||||
|
.. _pbr: http://docs.openstack.org/developer/pbr/
|
||||||
|
|
||||||
|
|
||||||
|
Using action plugins
|
||||||
|
====================
|
||||||
|
|
||||||
|
The Watcher Applier service will automatically discover any installed plugins
|
||||||
|
when it is restarted. If a Python package containing a custom plugin is
|
||||||
|
installed within the same environment as Watcher, Watcher will automatically
|
||||||
|
make that plugin available for use.
|
||||||
|
|
||||||
|
At this point, you can use your new action plugin in your :ref:`strategy plugin
|
||||||
|
<implement_strategy_plugin>` if you reference it via the use of the
|
||||||
|
:py:meth:`~.Solution.add_action` method:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
# [...]
|
||||||
|
self.solution.add_action(
|
||||||
|
action_type="dummy", # Name of the entry point we registered earlier
|
||||||
|
applies_to="",
|
||||||
|
input_parameters={})
|
||||||
|
|
||||||
|
By doing so, your action will be saved within the Watcher Database, ready to be
|
||||||
|
processed by the planner for creating an action plan which can then be executed
|
||||||
|
by the Watcher Applier via its workflow engine.
|
||||||
|
|
||||||
|
|
||||||
|
Scheduling of an action plugin
|
||||||
|
==============================
|
||||||
|
|
||||||
|
Watcher provides a basic built-in :ref:`planner <watcher_planner_definition>`
|
||||||
|
which is only able to process the Watcher built-in actions. Therefore, you will
|
||||||
|
either have to use an existing third-party planner or :ref:`implement another
|
||||||
|
planner <implement_planner_plugin>` that will be able to take into account your
|
||||||
|
new action plugin.
|
||||||
90
doc/source/dev/plugin/base-setup.rst
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
..
|
||||||
|
Except where otherwise noted, this document is licensed under Creative
|
||||||
|
Commons Attribution 3.0 License. You can view the license at:
|
||||||
|
|
||||||
|
https://creativecommons.org/licenses/by/3.0/
|
||||||
|
|
||||||
|
.. _plugin-base_setup:
|
||||||
|
|
||||||
|
=======================================
|
||||||
|
Create a third-party plugin for Watcher
|
||||||
|
=======================================
|
||||||
|
|
||||||
|
Watcher provides a plugin architecture which allows anyone to extend the
|
||||||
|
existing functionalities by implementing third-party plugins. This process can
|
||||||
|
be cumbersome so this documentation is there to help you get going as quickly
|
||||||
|
as possible.
|
||||||
|
|
||||||
|
|
||||||
|
Pre-requisites
|
||||||
|
==============
|
||||||
|
|
||||||
|
We assume that you have set up a working Watcher development environment. So if
|
||||||
|
this not already the case, you can check out our documentation which explains
|
||||||
|
how to set up a :ref:`development environment
|
||||||
|
<watcher_developement_environment>`.
|
||||||
|
|
||||||
|
.. _development environment:
|
||||||
|
|
||||||
|
Third party project scaffolding
|
||||||
|
===============================
|
||||||
|
|
||||||
|
First off, we need to create the project structure. To do so, we can use
|
||||||
|
`cookiecutter`_ and the `OpenStack cookiecutter`_ project scaffolder to
|
||||||
|
generate the skeleton of our project::
|
||||||
|
|
||||||
|
$ virtualenv thirdparty
|
||||||
|
$ source thirdparty/bin/activate
|
||||||
|
$ pip install cookiecutter
|
||||||
|
$ cookiecutter https://github.com/openstack-dev/cookiecutter
|
||||||
|
|
||||||
|
The last command will ask you for many information, and If you set
|
||||||
|
``module_name`` and ``repo_name`` as ``thirdparty``, you should end up with a
|
||||||
|
structure that looks like this::
|
||||||
|
|
||||||
|
$ cd thirdparty
|
||||||
|
$ tree .
|
||||||
|
.
|
||||||
|
├── babel.cfg
|
||||||
|
├── CONTRIBUTING.rst
|
||||||
|
├── doc
|
||||||
|
│ └── source
|
||||||
|
│ ├── conf.py
|
||||||
|
│ ├── contributing.rst
|
||||||
|
│ ├── index.rst
|
||||||
|
│ ├── installation.rst
|
||||||
|
│ ├── readme.rst
|
||||||
|
│ └── usage.rst
|
||||||
|
├── HACKING.rst
|
||||||
|
├── LICENSE
|
||||||
|
├── MANIFEST.in
|
||||||
|
├── README.rst
|
||||||
|
├── requirements.txt
|
||||||
|
├── setup.cfg
|
||||||
|
├── setup.py
|
||||||
|
├── test-requirements.txt
|
||||||
|
├── thirdparty
|
||||||
|
│ ├── __init__.py
|
||||||
|
│ └── tests
|
||||||
|
│ ├── base.py
|
||||||
|
│ ├── __init__.py
|
||||||
|
│ └── test_thirdparty.py
|
||||||
|
└── tox.ini
|
||||||
|
|
||||||
|
.. _cookiecutter: https://github.com/audreyr/cookiecutter
|
||||||
|
.. _OpenStack cookiecutter: https://github.com/openstack-dev/cookiecutter
|
||||||
|
|
||||||
|
Implementing a plugin for Watcher
|
||||||
|
=================================
|
||||||
|
|
||||||
|
Now that the project skeleton has been created, you can start the
|
||||||
|
implementation of your plugin. As of now, you can implement the following
|
||||||
|
plugins for Watcher:
|
||||||
|
|
||||||
|
- A :ref:`strategy plugin <implement_strategy_plugin>`
|
||||||
|
- A :ref:`planner plugin <implement_planner_plugin>`
|
||||||
|
- An :ref:`action plugin <implement_strategy_plugin>`
|
||||||
|
- A :ref:`workflow engine plugin <implement_workflow_engine_plugin>`
|
||||||
|
|
||||||
|
If you want to learn more on how to implement them, you can refer to their
|
||||||
|
dedicated documentation.
|
||||||
127
doc/source/dev/plugin/planner-plugin.rst
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
..
|
||||||
|
Except where otherwise noted, this document is licensed under Creative
|
||||||
|
Commons Attribution 3.0 License. You can view the license at:
|
||||||
|
|
||||||
|
https://creativecommons.org/licenses/by/3.0/
|
||||||
|
|
||||||
|
.. _implement_planner_plugin:
|
||||||
|
|
||||||
|
===================
|
||||||
|
Build a new planner
|
||||||
|
===================
|
||||||
|
|
||||||
|
Watcher :ref:`Decision Engine <watcher_decision_engine_definition>` has an
|
||||||
|
external :ref:`planner <planner_definition>` plugin interface which gives
|
||||||
|
anyone the ability to integrate an external :ref:`planner <planner_definition>`
|
||||||
|
in order to extend the initial set of planners Watcher provides.
|
||||||
|
|
||||||
|
This section gives some guidelines on how to implement and integrate custom
|
||||||
|
planners with Watcher.
|
||||||
|
|
||||||
|
.. _Decision Engine: watcher_decision_engine_definition
|
||||||
|
|
||||||
|
Creating a new plugin
|
||||||
|
=====================
|
||||||
|
|
||||||
|
First of all you have to extend the base :py:class:`~.BasePlanner` class which
|
||||||
|
defines an abstract method that you will have to implement. The
|
||||||
|
:py:meth:`~.BasePlanner.schedule` is the method being called by the Decision
|
||||||
|
Engine to schedule a given solution (:py:class:`~.BaseSolution`) into an
|
||||||
|
:ref:`action plan <action_plan_definition>` by ordering/sequencing an unordered
|
||||||
|
set of actions contained in the proposed solution (for more details, see
|
||||||
|
:ref:`definition of a solution <solution_definition>`).
|
||||||
|
|
||||||
|
Here is an example showing how you can write a planner plugin called
|
||||||
|
``DummyPlanner``:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
# Filepath = third-party/third_party/dummy.py
|
||||||
|
# Import path = third_party.dummy
|
||||||
|
import uuid
|
||||||
|
from watcher.decision_engine.planner import base
|
||||||
|
|
||||||
|
|
||||||
|
class DummyPlanner(base.BasePlanner):
|
||||||
|
|
||||||
|
def _create_action_plan(self, context, audit_id):
|
||||||
|
action_plan_dict = {
|
||||||
|
'uuid': uuid.uuid4(),
|
||||||
|
'audit_id': audit_id,
|
||||||
|
'first_action_id': None,
|
||||||
|
'state': objects.action_plan.State.RECOMMENDED
|
||||||
|
}
|
||||||
|
|
||||||
|
new_action_plan = objects.ActionPlan(context, **action_plan_dict)
|
||||||
|
new_action_plan.create(context)
|
||||||
|
new_action_plan.save()
|
||||||
|
return new_action_plan
|
||||||
|
|
||||||
|
def schedule(self, context, audit_id, solution):
|
||||||
|
# Empty action plan
|
||||||
|
action_plan = self._create_action_plan(context, audit_id)
|
||||||
|
# todo: You need to create the workflow of actions here
|
||||||
|
# and attach it to the action plan
|
||||||
|
return action_plan
|
||||||
|
|
||||||
|
This implementation is the most basic one. So if you want to have more advanced
|
||||||
|
examples, have a look at the implementation of planners already provided by
|
||||||
|
Watcher like :py:class:`~.DefaultPlanner`. A list with all available planner
|
||||||
|
plugins can be found :ref:`here <watcher_planners>`.
|
||||||
|
|
||||||
|
Abstract Plugin Class
|
||||||
|
=====================
|
||||||
|
|
||||||
|
Here below is the abstract ``BasePlanner`` class that every single planner
|
||||||
|
should implement:
|
||||||
|
|
||||||
|
.. autoclass:: watcher.decision_engine.planner.base.BasePlanner
|
||||||
|
:members:
|
||||||
|
:noindex:
|
||||||
|
|
||||||
|
|
||||||
|
Register a new entry point
|
||||||
|
==========================
|
||||||
|
|
||||||
|
In order for the Watcher Decision Engine to load your new planner, the
|
||||||
|
latter must be registered as a new entry point under the
|
||||||
|
``watcher_planners`` entry point namespace of your ``setup.py`` file. If you
|
||||||
|
are using pbr_, this entry point should be placed in your ``setup.cfg`` file.
|
||||||
|
|
||||||
|
The name you give to your entry point has to be unique.
|
||||||
|
|
||||||
|
Here below is how you would proceed to register ``DummyPlanner`` using pbr_:
|
||||||
|
|
||||||
|
.. code-block:: ini
|
||||||
|
|
||||||
|
[entry_points]
|
||||||
|
watcher_planners =
|
||||||
|
dummy = third_party.dummy:DummyPlanner
|
||||||
|
|
||||||
|
.. _pbr: http://docs.openstack.org/developer/pbr/
|
||||||
|
|
||||||
|
|
||||||
|
Using planner plugins
|
||||||
|
=====================
|
||||||
|
|
||||||
|
The :ref:`Watcher Decision Engine <watcher_decision_engine_definition>` service
|
||||||
|
will automatically discover any installed plugins when it is started. This
|
||||||
|
means that if Watcher is already running when you install your plugin, you will
|
||||||
|
have to restart the related Watcher services. If a Python package containing a
|
||||||
|
custom plugin is installed within the same environment as Watcher, Watcher will
|
||||||
|
automatically make that plugin available for use.
|
||||||
|
|
||||||
|
At this point, Watcher will use your new planner if you referenced it in the
|
||||||
|
``planner`` option under the ``[watcher_planner]`` section of your
|
||||||
|
``watcher.conf`` configuration file when you started it. For example, if you
|
||||||
|
want to use the ``dummy`` planner you just installed, you would have to
|
||||||
|
select it as followed:
|
||||||
|
|
||||||
|
.. code-block:: ini
|
||||||
|
|
||||||
|
[watcher_planner]
|
||||||
|
planner = dummy
|
||||||
|
|
||||||
|
As you may have noticed, only a single planner implementation can be activated
|
||||||
|
at a time, so make sure it is generic enough to support all your strategies
|
||||||
|
and actions.
|
||||||
215
doc/source/dev/plugin/strategy-plugin.rst
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
..
|
||||||
|
Except where otherwise noted, this document is licensed under Creative
|
||||||
|
Commons Attribution 3.0 License. You can view the license at:
|
||||||
|
|
||||||
|
https://creativecommons.org/licenses/by/3.0/
|
||||||
|
|
||||||
|
.. _implement_strategy_plugin:
|
||||||
|
|
||||||
|
=================================
|
||||||
|
Build a new optimization strategy
|
||||||
|
=================================
|
||||||
|
|
||||||
|
Watcher Decision Engine has an external :ref:`strategy <strategy_definition>`
|
||||||
|
plugin interface which gives anyone the ability to integrate an external
|
||||||
|
strategy in order to make use of placement algorithms.
|
||||||
|
|
||||||
|
This section gives some guidelines on how to implement and integrate custom
|
||||||
|
strategies with Watcher.
|
||||||
|
|
||||||
|
|
||||||
|
Pre-requisites
|
||||||
|
==============
|
||||||
|
|
||||||
|
Before using any strategy, you should make sure you have your Telemetry service
|
||||||
|
configured so that it would provide you all the metrics you need to be able to
|
||||||
|
use your strategy.
|
||||||
|
|
||||||
|
|
||||||
|
Creating a new plugin
|
||||||
|
=====================
|
||||||
|
|
||||||
|
First of all you have to:
|
||||||
|
|
||||||
|
- Extend :py:class:`~.BaseStrategy`
|
||||||
|
- Implement its :py:meth:`~.BaseStrategy.execute` method
|
||||||
|
|
||||||
|
Here is an example showing how you can write a plugin called ``DummyStrategy``:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
class DummyStrategy(BaseStrategy):
|
||||||
|
|
||||||
|
DEFAULT_NAME = "dummy"
|
||||||
|
DEFAULT_DESCRIPTION = "Dummy Strategy"
|
||||||
|
|
||||||
|
def __init__(self, name=DEFAULT_NAME, description=DEFAULT_DESCRIPTION):
|
||||||
|
super(DummyStrategy, self).__init__(name, description)
|
||||||
|
|
||||||
|
def execute(self, model):
|
||||||
|
migration_type = 'live'
|
||||||
|
src_hypervisor = 'compute-host-1'
|
||||||
|
dst_hypervisor = 'compute-host-2'
|
||||||
|
instance_id = uuid.uuid4()
|
||||||
|
parameters = {'migration_type': migration_type,
|
||||||
|
'src_hypervisor': src_hypervisor,
|
||||||
|
'dst_hypervisor': dst_hypervisor}
|
||||||
|
self.solution.add_action(action_type="migration",
|
||||||
|
resource_id=instance_id,
|
||||||
|
input_parameters=parameters)
|
||||||
|
# Do some more stuff here ...
|
||||||
|
return self.solution
|
||||||
|
|
||||||
|
As you can see in the above example, the :py:meth:`~.BaseStrategy.execute`
|
||||||
|
method returns a :py:class:`~.BaseSolution` instance as required. This solution
|
||||||
|
is what wraps the abstract set of actions the strategy recommends to you. This
|
||||||
|
solution is then processed by a :ref:`planner <planner_definition>` to produce
|
||||||
|
an action plan which shall contain the sequenced flow of actions to be
|
||||||
|
executed by the :ref:`Watcher Applier <watcher_applier_definition>`.
|
||||||
|
|
||||||
|
Please note that your strategy class will be instantiated without any
|
||||||
|
parameter. Therefore, you should make sure not to make any of them required in
|
||||||
|
your ``__init__`` method.
|
||||||
|
|
||||||
|
|
||||||
|
Abstract Plugin Class
|
||||||
|
=====================
|
||||||
|
|
||||||
|
Here below is the abstract :py:class:`~.BaseStrategy` class that every single
|
||||||
|
strategy should implement:
|
||||||
|
|
||||||
|
.. autoclass:: watcher.decision_engine.strategy.strategies.base.BaseStrategy
|
||||||
|
:members:
|
||||||
|
:noindex:
|
||||||
|
|
||||||
|
|
||||||
|
Add a new entry point
|
||||||
|
=====================
|
||||||
|
|
||||||
|
In order for the Watcher Decision Engine to load your new strategy, the
|
||||||
|
strategy must be registered as a named entry point under the
|
||||||
|
``watcher_strategies`` entry point of your ``setup.py`` file. If you are using
|
||||||
|
pbr_, this entry point should be placed in your ``setup.cfg`` file.
|
||||||
|
|
||||||
|
The name you give to your entry point has to be unique.
|
||||||
|
|
||||||
|
Here below is how you would proceed to register ``DummyStrategy`` using pbr_:
|
||||||
|
|
||||||
|
.. code-block:: ini
|
||||||
|
|
||||||
|
[entry_points]
|
||||||
|
watcher_strategies =
|
||||||
|
dummy = thirdparty.dummy:DummyStrategy
|
||||||
|
|
||||||
|
|
||||||
|
To get a better understanding on how to implement a more advanced strategy,
|
||||||
|
have a look at the :py:class:`~.BasicConsolidation` class.
|
||||||
|
|
||||||
|
.. _pbr: http://docs.openstack.org/developer/pbr/
|
||||||
|
|
||||||
|
Using strategy plugins
|
||||||
|
======================
|
||||||
|
|
||||||
|
The Watcher Decision Engine service will automatically discover any installed
|
||||||
|
plugins when it is restarted. If a Python package containing a custom plugin is
|
||||||
|
installed within the same environment as Watcher, Watcher will automatically
|
||||||
|
make that plugin available for use.
|
||||||
|
|
||||||
|
At this point, Watcher will use your new strategy if you reference it in the
|
||||||
|
``goals`` under the ``[watcher_goals]`` section of your ``watcher.conf``
|
||||||
|
configuration file. For example, if you want to use a ``dummy`` strategy you
|
||||||
|
just installed, you would have to associate it to a goal like this:
|
||||||
|
|
||||||
|
.. code-block:: ini
|
||||||
|
|
||||||
|
[watcher_goals]
|
||||||
|
goals = BALANCE_LOAD:basic,MINIMIZE_ENERGY_CONSUMPTION:dummy
|
||||||
|
|
||||||
|
|
||||||
|
You should take care when installing strategy plugins. By their very nature,
|
||||||
|
there are no guarantees that utilizing them as is will be supported, as
|
||||||
|
they may require a set of metrics which is not yet available within the
|
||||||
|
Telemetry service. In such a case, please do make sure that you first
|
||||||
|
check/configure the latter so your new strategy can be fully functional.
|
||||||
|
|
||||||
|
Querying metrics
|
||||||
|
----------------
|
||||||
|
|
||||||
|
A large set of metrics, generated by OpenStack modules, can be used in your
|
||||||
|
strategy implementation. To collect these metrics, Watcher provides a
|
||||||
|
`Helper`_ to the Ceilometer API, which makes this API reusable and easier
|
||||||
|
to used.
|
||||||
|
|
||||||
|
If you want to use your own metrics database backend, please refer to the
|
||||||
|
`Ceilometer developer guide`_. Indeed, Ceilometer's pluggable model allows
|
||||||
|
for various types of backends. A list of the available backends is located
|
||||||
|
here_. The Ceilosca project is a good example of how to create your own
|
||||||
|
pluggable backend.
|
||||||
|
|
||||||
|
|
||||||
|
Finally, if your strategy requires new metrics not covered by Ceilometer, you
|
||||||
|
can add them through a Ceilometer `plugin`_.
|
||||||
|
|
||||||
|
.. _`Helper`: https://github.com/openstack/watcher/blob/master/watcher/metrics_engine/cluster_history/ceilometer.py#L31
|
||||||
|
.. _`Ceilometer developer guide`: http://docs.openstack.org/developer/ceilometer/architecture.html#storing-the-data
|
||||||
|
.. _`here`: http://docs.openstack.org/developer/ceilometer/install/dbreco.html#choosing-a-database-backend
|
||||||
|
.. _`plugin`: http://docs.openstack.org/developer/ceilometer/plugins.html
|
||||||
|
.. _`Ceilosca`: https://github.com/openstack/monasca-ceilometer/blob/master/ceilosca/ceilometer/storage/impl_monasca.py
|
||||||
|
|
||||||
|
|
||||||
|
Read usage metrics using the Python binding
|
||||||
|
-------------------------------------------
|
||||||
|
|
||||||
|
You can find the information about the Ceilometer Python binding on the
|
||||||
|
OpenStack `ceilometer client python API documentation
|
||||||
|
<http://docs.openstack.org/developer/python-ceilometerclient/api.html>`_
|
||||||
|
|
||||||
|
To facilitate the process, Watcher provides the ``osc`` attribute to every
|
||||||
|
strategy which includes clients to major OpenStack services, including
|
||||||
|
Ceilometer. So to access it within your strategy, you can do the following:
|
||||||
|
|
||||||
|
.. code-block:: py
|
||||||
|
|
||||||
|
# Within your strategy "execute()"
|
||||||
|
cclient = self.osc.ceilometer
|
||||||
|
# TODO: Do something here
|
||||||
|
|
||||||
|
Using that you can now query the values for that specific metric:
|
||||||
|
|
||||||
|
.. code-block:: py
|
||||||
|
|
||||||
|
query = None # e.g. [{'field': 'foo', 'op': 'le', 'value': 34},]
|
||||||
|
value_cpu = cclient.samples.list(
|
||||||
|
meter_name='cpu_util',
|
||||||
|
limit=10, q=query)
|
||||||
|
|
||||||
|
|
||||||
|
Read usage metrics using the Watcher Cluster History Helper
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
Here below is the abstract ``BaseClusterHistory`` class of the Helper.
|
||||||
|
|
||||||
|
.. autoclass:: watcher.metrics_engine.cluster_history.api.BaseClusterHistory
|
||||||
|
:members:
|
||||||
|
:noindex:
|
||||||
|
|
||||||
|
|
||||||
|
The following code snippet shows how to create a Cluster History class:
|
||||||
|
|
||||||
|
.. code-block:: py
|
||||||
|
|
||||||
|
from watcher.metrics_engine.cluster_history import ceilometer as ceil
|
||||||
|
|
||||||
|
query_history = ceil.CeilometerClusterHistory()
|
||||||
|
|
||||||
|
Using that you can now query the values for that specific metric:
|
||||||
|
|
||||||
|
.. code-block:: py
|
||||||
|
|
||||||
|
query_history.statistic_aggregation(resource_id=hypervisor.uuid,
|
||||||
|
meter_name='compute.node.cpu.percent',
|
||||||
|
period="7200",
|
||||||
|
aggregate='avg'
|
||||||
|
)
|
||||||
@@ -4,186 +4,35 @@
|
|||||||
|
|
||||||
https://creativecommons.org/licenses/by/3.0/
|
https://creativecommons.org/licenses/by/3.0/
|
||||||
|
|
||||||
===============
|
|
||||||
Watcher plugins
|
|
||||||
===============
|
|
||||||
|
|
||||||
Writing a Watcher Decision Engine plugin
|
=================
|
||||||
========================================
|
Available Plugins
|
||||||
|
=================
|
||||||
|
|
||||||
Watcher has an external :ref:`strategy <strategy_definition>` plugin interface
|
.. _watcher_strategies:
|
||||||
which gives anyone the ability to integrate an external :ref:`strategy
|
|
||||||
<strategy_definition>` in order to make use of placement algorithms.
|
|
||||||
|
|
||||||
This section gives some guidelines on how to implement and integrate custom
|
Strategies
|
||||||
Stategies with Watcher.
|
==========
|
||||||
|
|
||||||
Pre-requisites
|
.. drivers-doc:: watcher_strategies
|
||||||
--------------
|
|
||||||
|
|
||||||
Before using any strategy, you should make sure you have your Telemetry service
|
.. _watcher_actions:
|
||||||
configured so that it would provide you all the metrics you need to be able to
|
|
||||||
use your strategy.
|
|
||||||
|
|
||||||
|
Actions
|
||||||
|
=======
|
||||||
|
|
||||||
Creating a new plugin
|
.. drivers-doc:: watcher_actions
|
||||||
---------------------
|
|
||||||
|
|
||||||
First of all you have to:
|
.. _watcher_workflow_engines:
|
||||||
|
|
||||||
- Extend the base ``BaseStrategy`` class
|
Workflow Engines
|
||||||
- Implement its ``execute`` method
|
================
|
||||||
|
|
||||||
Here is an example showing how you can write a plugin called ``DummyStrategy``:
|
.. drivers-doc:: watcher_workflow_engines
|
||||||
|
|
||||||
.. code-block:: python
|
.. _watcher_planners:
|
||||||
|
|
||||||
# Filepath = third-party/third_party/dummy.py
|
Planners
|
||||||
# Import path = third_party.dummy
|
========
|
||||||
|
|
||||||
class DummyStrategy(BaseStrategy):
|
|
||||||
|
|
||||||
DEFAULT_NAME = "dummy"
|
|
||||||
DEFAULT_DESCRIPTION = "Dummy Strategy"
|
|
||||||
|
|
||||||
def __init__(self, name=DEFAULT_NAME, description=DEFAULT_DESCRIPTION):
|
|
||||||
super(DummyStrategy, self).__init__(name, description)
|
|
||||||
|
|
||||||
def execute(self, model):
|
|
||||||
self.solution.add_change_request(
|
|
||||||
Migrate(vm=my_vm, src_hypervisor=src, dest_hypervisor=dest)
|
|
||||||
)
|
|
||||||
# Do some more stuff here ...
|
|
||||||
return self.solution
|
|
||||||
|
|
||||||
As you can see in the above example, the ``execute()`` method returns a
|
|
||||||
solution as required.
|
|
||||||
|
|
||||||
Please note that your strategy class will be instantiated without any
|
|
||||||
parameter. Therefore, you should make sure not to make any of them required in
|
|
||||||
your ``__init__`` method.
|
|
||||||
|
|
||||||
|
|
||||||
Abstract Plugin Class
|
|
||||||
---------------------
|
|
||||||
|
|
||||||
Here below is the abstract ``BaseStrategy`` class that every single strategy
|
|
||||||
should implement:
|
|
||||||
|
|
||||||
.. automodule:: watcher.decision_engine.strategy.base
|
|
||||||
:noindex:
|
|
||||||
|
|
||||||
.. autoclass:: BaseStrategy
|
|
||||||
:members:
|
|
||||||
|
|
||||||
|
|
||||||
Add a new entry point
|
|
||||||
---------------------
|
|
||||||
|
|
||||||
In order for the Watcher Decision Engine to load your new strategy, the
|
|
||||||
strategy must be registered as a named entry point under the
|
|
||||||
``watcher_strategies`` entry point of your ``setup.py`` file. If you are using
|
|
||||||
pbr_, this entry point should be placed in your ``setup.cfg`` file.
|
|
||||||
|
|
||||||
The name you give to your entry point has to be unique.
|
|
||||||
|
|
||||||
Here below is how you would proceed to register ``DummyStrategy`` using pbr_:
|
|
||||||
|
|
||||||
.. code-block:: ini
|
|
||||||
|
|
||||||
[entry_points]
|
|
||||||
watcher_strategies =
|
|
||||||
dummy = third_party.dummy:DummyStrategy
|
|
||||||
|
|
||||||
|
|
||||||
To get a better understanding on how to implement a more advanced strategy,
|
|
||||||
have a look at the :py:class:`BasicConsolidation` class.
|
|
||||||
|
|
||||||
.. _pbr: http://docs.openstack.org/developer/pbr/
|
|
||||||
|
|
||||||
Using strategy plugins
|
|
||||||
----------------------
|
|
||||||
|
|
||||||
The Watcher Decision Engine service will automatically discover any installed
|
|
||||||
plugins when it is run. If a Python package containing a custom plugin is
|
|
||||||
installed within the same environment as Watcher, Watcher will automatically
|
|
||||||
make that plugin available for use.
|
|
||||||
|
|
||||||
At this point, the way Watcher will use your new strategy if you reference it
|
|
||||||
in the ``goals`` under the ``[watcher_goals]`` section of your ``watcher.conf``
|
|
||||||
configuration file. For example, if you want to use a ``dummy`` strategy you
|
|
||||||
just installed, you would have to associate it to a goal like this:
|
|
||||||
|
|
||||||
.. code-block:: ini
|
|
||||||
|
|
||||||
[watcher_goals]
|
|
||||||
goals = BALANCE_LOAD:basic,MINIMIZE_ENERGY_CONSUMPTION:dummy
|
|
||||||
|
|
||||||
|
|
||||||
You should take care when installing strategy plugins. By their very nature,
|
|
||||||
there are no guarantees that utilizing them as is will be supported, as
|
|
||||||
they may require a set of metrics which is not yet available within the
|
|
||||||
Telemetry service. In such a case, please do make sure that you first
|
|
||||||
check/configure the latter so your new strategy can be fully functional.
|
|
||||||
|
|
||||||
Querying metrics
|
|
||||||
~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
The metrics available depend on the hypervisors that OpenStack manages on
|
|
||||||
the specific implementation. You can find the metrics available per hypervisor
|
|
||||||
and OpenStack release on the OpenStack site.
|
|
||||||
|
|
||||||
There are different possible ways to obtain usage metrics in Watcher, you can
|
|
||||||
use the default Ceilometer API or our Helper.
|
|
||||||
The Helper attempted to make the Ceilometer API more reusable and easy to use.
|
|
||||||
|
|
||||||
Read usage metrics using the Python binding
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
You can find the information about the Ceilometer Python binding on the
|
|
||||||
OpenStack `ceilometer client python API documentation
|
|
||||||
<http://docs.openstack.org/developer/python-ceilometerclient/api.html>`_
|
|
||||||
|
|
||||||
The first step is to authenticate against the Ceilometer service
|
|
||||||
(assuming that you already imported the Ceilometer client for Python)
|
|
||||||
with this call:
|
|
||||||
|
|
||||||
.. code-block:: py
|
|
||||||
|
|
||||||
cclient = ceilometerclient.client.get_client(VERSION, os_username=USERNAME,
|
|
||||||
os_password=PASSWORD, os_tenant_name=PROJECT_NAME, os_auth_url=AUTH_URL)
|
|
||||||
|
|
||||||
Using that you can now query the values for that specific metric:
|
|
||||||
|
|
||||||
.. code-block:: py
|
|
||||||
|
|
||||||
value_cpu = cclient.samples.list(meter_name='cpu_util', limit=10, q=query)
|
|
||||||
|
|
||||||
Read usage metrics using the Watcher Cluster History Helper
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Here below is the abstract ``BaseClusterHistory`` class of the Helper.
|
|
||||||
|
|
||||||
.. automodule:: watcher.metrics_engine.cluster_history.api
|
|
||||||
:noindex:
|
|
||||||
|
|
||||||
.. autoclass:: BaseClusterHistory
|
|
||||||
:members:
|
|
||||||
|
|
||||||
|
|
||||||
The following snippet code shows how to create a Cluster History class:
|
|
||||||
|
|
||||||
.. code-block:: py
|
|
||||||
|
|
||||||
query_history = CeilometerClusterHistory()
|
|
||||||
|
|
||||||
Using that you can now query the values for that specific metric:
|
|
||||||
|
|
||||||
.. code-block:: py
|
|
||||||
|
|
||||||
query_history.statistic_aggregation(resource_id=hypervisor.uuid,
|
|
||||||
meter_name='compute.node.cpu.percent',
|
|
||||||
period="7200",
|
|
||||||
aggregate='avg'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
.. drivers-doc:: watcher_planners
|
||||||
|
|||||||
50
doc/source/dev/testing.rst
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
..
|
||||||
|
Except where otherwise noted, this document is licensed under Creative
|
||||||
|
Commons Attribution 3.0 License. You can view the license at:
|
||||||
|
|
||||||
|
https://creativecommons.org/licenses/by/3.0/
|
||||||
|
|
||||||
|
=======
|
||||||
|
Testing
|
||||||
|
=======
|
||||||
|
|
||||||
|
.. _unit_tests:
|
||||||
|
|
||||||
|
Unit tests
|
||||||
|
==========
|
||||||
|
|
||||||
|
All unit tests should be run using `tox`_. To run the same unit tests that are
|
||||||
|
executing onto `Gerrit`_ which includes ``py34``, ``py27`` and ``pep8``, you
|
||||||
|
can issue the following command::
|
||||||
|
|
||||||
|
$ workon watcher
|
||||||
|
(watcher) $ pip install tox
|
||||||
|
(watcher) $ cd watcher
|
||||||
|
(watcher) $ tox
|
||||||
|
|
||||||
|
If you want to only run one of the aforementioned, you can then issue one of
|
||||||
|
the following::
|
||||||
|
|
||||||
|
$ workon watcher
|
||||||
|
(watcher) $ tox -e py34
|
||||||
|
(watcher) $ tox -e py27
|
||||||
|
(watcher) $ tox -e pep8
|
||||||
|
|
||||||
|
.. _tox: https://tox.readthedocs.org/
|
||||||
|
.. _Gerrit: http://review.openstack.org/
|
||||||
|
|
||||||
|
You may pass options to the test programs using positional arguments. To run a
|
||||||
|
specific unit test, you can pass extra options to `os-testr`_ after putting
|
||||||
|
the ``--`` separator. So using the ``-r`` option followed by a regex string,
|
||||||
|
you can run the desired test::
|
||||||
|
|
||||||
|
$ workon watcher
|
||||||
|
(watcher) $ tox -e py27 -- -r watcher.tests.api
|
||||||
|
|
||||||
|
.. _os-testr: http://docs.openstack.org/developer/os-testr/
|
||||||
|
|
||||||
|
When you're done, deactivate the virtualenv::
|
||||||
|
|
||||||
|
$ deactivate
|
||||||
|
|
||||||
|
.. include:: ../../../watcher_tempest_plugin/README.rst
|
||||||
@@ -4,9 +4,9 @@
|
|||||||
|
|
||||||
https://creativecommons.org/licenses/by/3.0/
|
https://creativecommons.org/licenses/by/3.0/
|
||||||
|
|
||||||
==========
|
========
|
||||||
Glossary
|
Glossary
|
||||||
==========
|
========
|
||||||
|
|
||||||
.. glossary::
|
.. glossary::
|
||||||
:sorted:
|
:sorted:
|
||||||
@@ -20,97 +20,14 @@ They are sorted in alphabetical order.
|
|||||||
Action
|
Action
|
||||||
======
|
======
|
||||||
|
|
||||||
An :ref:`Action <action_definition>` is what enables Watcher to transform the
|
.. watcher-term:: watcher.api.controllers.v1.action
|
||||||
current state of a :ref:`Cluster <cluster_definition>` after an
|
|
||||||
:ref:`Audit <audit_definition>`.
|
|
||||||
|
|
||||||
An :ref:`Action <action_definition>` is an atomic task which changes the
|
|
||||||
current state of a target :ref:`Managed resource <managed_resource_definition>`
|
|
||||||
of the OpenStack :ref:`Cluster <cluster_definition>` such as:
|
|
||||||
|
|
||||||
- Live migration of an instance from one compute node to another compute
|
|
||||||
node with Nova
|
|
||||||
- Changing the power level of a compute node (ACPI level, ...)
|
|
||||||
- Changing the current state of an hypervisor (enable or disable) with Nova
|
|
||||||
|
|
||||||
In most cases, an :ref:`Action <action_definition>` triggers some concrete
|
|
||||||
commands on an existing OpenStack module (Nova, Neutron, Cinder, Ironic, etc.)
|
|
||||||
via a :ref:`Primitive <primitive_definition>`.
|
|
||||||
|
|
||||||
An :ref:`Action <action_definition>` has a life-cycle and its current state may
|
|
||||||
be one of the following:
|
|
||||||
|
|
||||||
- **PENDING** : the :ref:`Action <action_definition>` has not been executed
|
|
||||||
yet by the :ref:`Watcher Applier <watcher_applier_definition>`
|
|
||||||
- **ONGOING** : the :ref:`Action <action_definition>` is currently being
|
|
||||||
processed by the :ref:`Watcher Applier <watcher_applier_definition>`
|
|
||||||
- **SUCCEEDED** : the :ref:`Action <action_definition>` has been executed
|
|
||||||
successfully
|
|
||||||
- **FAILED** : an error occured while trying to execute the
|
|
||||||
:ref:`Action <action_definition>`
|
|
||||||
- **DELETED** : the :ref:`Action <action_definition>` is still stored in the
|
|
||||||
:ref:`Watcher database <watcher_database_definition>` but is not returned
|
|
||||||
any more through the Watcher APIs.
|
|
||||||
- **CANCELLED** : the :ref:`Action <action_definition>` was in **PENDING** or
|
|
||||||
**ONGOING** state and was cancelled by the
|
|
||||||
:ref:`Administrator <administrator_definition>`
|
|
||||||
|
|
||||||
.. _action_plan_definition:
|
.. _action_plan_definition:
|
||||||
|
|
||||||
Action Plan
|
Action Plan
|
||||||
===========
|
===========
|
||||||
|
|
||||||
An :ref:`Action Plan <action_plan_definition>` is a flow of
|
.. watcher-term:: watcher.api.controllers.v1.action_plan
|
||||||
:ref:`Actions <action_definition>` that should be executed in order to satisfy
|
|
||||||
a given :ref:`Goal <goal_definition>`.
|
|
||||||
|
|
||||||
An :ref:`Action Plan <action_plan_definition>` is generated by Watcher when an
|
|
||||||
:ref:`Audit <audit_definition>` is successful which implies that the
|
|
||||||
:ref:`Strategy <strategy_definition>`
|
|
||||||
which was used has found a :ref:`Solution <solution_definition>` to achieve the
|
|
||||||
:ref:`Goal <goal_definition>` of this :ref:`Audit <audit_definition>`.
|
|
||||||
|
|
||||||
In the default implementation of Watcher, an
|
|
||||||
:ref:`Action Plan <action_plan_definition>`
|
|
||||||
is only composed of successive :ref:`Actions <action_definition>`
|
|
||||||
(i.e., a Workflow of :ref:`Actions <action_definition>` belonging to a unique
|
|
||||||
branch).
|
|
||||||
|
|
||||||
However, Watcher provides abstract interfaces for many of its components,
|
|
||||||
allowing other implementations to generate and handle more complex
|
|
||||||
:ref:`Action Plan(s) <action_plan_definition>`
|
|
||||||
composed of two types of Action Item(s):
|
|
||||||
|
|
||||||
- simple :ref:`Actions <action_definition>`: atomic tasks, which means it
|
|
||||||
can not be split into smaller tasks or commands from an OpenStack point of
|
|
||||||
view.
|
|
||||||
- composite Actions: which are composed of several simple
|
|
||||||
:ref:`Actions <action_definition>`
|
|
||||||
ordered in sequential and/or parallel flows.
|
|
||||||
|
|
||||||
An :ref:`Action Plan <action_plan_definition>` may be described using
|
|
||||||
standard workflow model description formats such as
|
|
||||||
`Business Process Model and Notation 2.0 (BPMN 2.0) <http://www.omg.org/spec/BPMN/2.0/>`_
|
|
||||||
or `Unified Modeling Language (UML) <http://www.uml.org/>`_.
|
|
||||||
|
|
||||||
An :ref:`Action Plan <action_plan_definition>` has a life-cycle and its current
|
|
||||||
state may be one of the following:
|
|
||||||
|
|
||||||
- **RECOMMENDED** : the :ref:`Action Plan <action_plan_definition>` is waiting
|
|
||||||
for a validation from the :ref:`Administrator <administrator_definition>`
|
|
||||||
- **ONGOING** : the :ref:`Action Plan <action_plan_definition>` is currently
|
|
||||||
being processed by the :ref:`Watcher Applier <watcher_applier_definition>`
|
|
||||||
- **SUCCEEDED** : the :ref:`Action Plan <action_plan_definition>` has been
|
|
||||||
executed successfully (i.e. all :ref:`Actions <action_definition>` that it
|
|
||||||
contains have been executed successfully)
|
|
||||||
- **FAILED** : an error occured while executing the
|
|
||||||
:ref:`Action Plan <action_plan_definition>`
|
|
||||||
- **DELETED** : the :ref:`Action Plan <action_plan_definition>` is still
|
|
||||||
stored in the :ref:`Watcher database <watcher_database_definition>` but is
|
|
||||||
not returned any more through the Watcher APIs.
|
|
||||||
- **CANCELLED** : the :ref:`Action Plan <action_plan_definition>` was in
|
|
||||||
**PENDING** or **ONGOING** state and was cancelled by the
|
|
||||||
:ref:`Administrator <administrator_definition>`
|
|
||||||
|
|
||||||
.. _administrator_definition:
|
.. _administrator_definition:
|
||||||
|
|
||||||
@@ -144,73 +61,14 @@ any Watcher configuration files and to restart Watcher services.
|
|||||||
Audit
|
Audit
|
||||||
=====
|
=====
|
||||||
|
|
||||||
In the Watcher system, an :ref:`Audit <audit_definition>` is a request for
|
.. watcher-term:: watcher.api.controllers.v1.audit
|
||||||
optimizing a :ref:`Cluster <cluster_definition>`.
|
|
||||||
|
|
||||||
The optimization is done in order to satisfy one :ref:`Goal <goal_definition>`
|
|
||||||
on a given :ref:`Cluster <cluster_definition>`.
|
|
||||||
|
|
||||||
For each :ref:`Audit <audit_definition>`, the Watcher system generates an
|
|
||||||
:ref:`Action Plan <action_plan_definition>`.
|
|
||||||
|
|
||||||
An :ref:`Audit <audit_definition>` has a life-cycle and its current state may
|
|
||||||
be one of the following:
|
|
||||||
|
|
||||||
- **PENDING** : a request for an :ref:`Audit <audit_definition>` has been
|
|
||||||
submitted (either manually by the
|
|
||||||
:ref:`Administrator <administrator_definition>` or automatically via some
|
|
||||||
event handling mechanism) and is in the queue for being processed by the
|
|
||||||
:ref:`Watcher Decision Engine <watcher_decision_engine_definition>`
|
|
||||||
- **ONGOING** : the :ref:`Audit <audit_definition>` is currently being
|
|
||||||
processed by the
|
|
||||||
:ref:`Watcher Decision Engine <watcher_decision_engine_definition>`
|
|
||||||
- **SUCCEEDED** : the :ref:`Audit <audit_definition>` has been executed
|
|
||||||
successfully (note that it may not necessarily produce a
|
|
||||||
:ref:`Solution <solution_definition>`).
|
|
||||||
- **FAILED** : an error occured while executing the
|
|
||||||
:ref:`Audit <audit_definition>`
|
|
||||||
- **DELETED** : the :ref:`Audit <audit_definition>` is still stored in the
|
|
||||||
:ref:`Watcher database <watcher_database_definition>` but is not returned
|
|
||||||
any more through the Watcher APIs.
|
|
||||||
- **CANCELLED** : the :ref:`Audit <audit_definition>` was in **PENDING** or
|
|
||||||
**ONGOING** state and was cancelled by the
|
|
||||||
:ref:`Administrator <administrator_definition>`
|
|
||||||
|
|
||||||
.. _audit_template_definition:
|
.. _audit_template_definition:
|
||||||
|
|
||||||
Audit Template
|
Audit Template
|
||||||
==============
|
==============
|
||||||
|
|
||||||
An :ref:`Audit <audit_definition>` may be launched several times with the same
|
.. watcher-term:: watcher.api.controllers.v1.audit_template
|
||||||
settings (:ref:`Goal <goal_definition>`, thresholds, ...). Therefore it makes
|
|
||||||
sense to save those settings in some sort of Audit preset object, which is
|
|
||||||
known as an :ref:`Audit Template <audit_template_definition>`.
|
|
||||||
|
|
||||||
An :ref:`Audit Template <audit_template_definition>` contains at least the
|
|
||||||
:ref:`Goal <goal_definition>` of the :ref:`Audit <audit_definition>`.
|
|
||||||
|
|
||||||
It may also contain some error handling settings indicating whether:
|
|
||||||
|
|
||||||
- :ref:`Watcher Applier <watcher_applier_definition>` stops the
|
|
||||||
entire operation
|
|
||||||
- :ref:`Watcher Applier <watcher_applier_definition>` performs a rollback
|
|
||||||
|
|
||||||
and how many retries should be attempted before failure occurs (also the latter
|
|
||||||
can be complex: for example the scenario in which there are many first-time
|
|
||||||
failures on ultimately successful :ref:`Actions <action_definition>`).
|
|
||||||
|
|
||||||
Moreover, an :ref:`Audit Template <audit_template_definition>` may contain some
|
|
||||||
settings related to the level of automation for the
|
|
||||||
:ref:`Action Plan <action_plan_definition>` that will be generated by the
|
|
||||||
:ref:`Audit <audit_definition>`.
|
|
||||||
A flag will indicate whether the :ref:`Action Plan <action_plan_definition>`
|
|
||||||
will be launched automatically or will need a manual confirmation from the
|
|
||||||
:ref:`Administrator <administrator_definition>`.
|
|
||||||
|
|
||||||
Last but not least, an :ref:`Audit Template <audit_template_definition>` may
|
|
||||||
contain a list of extra parameters related to the
|
|
||||||
:ref:`Strategy <strategy_definition>` configuration. These parameters can be
|
|
||||||
provided as a list of key-value pairs.
|
|
||||||
|
|
||||||
.. _availability_zone_definition:
|
.. _availability_zone_definition:
|
||||||
|
|
||||||
@@ -241,136 +99,14 @@ The :ref:`Cluster <cluster_definition>` may be divided in one or several
|
|||||||
Cluster Data Model
|
Cluster Data Model
|
||||||
==================
|
==================
|
||||||
|
|
||||||
A :ref:`Cluster Data Model <cluster_data_model_definition>` is a logical
|
.. watcher-term:: watcher.metrics_engine.cluster_model_collector.api
|
||||||
representation of the current state and topology of the
|
|
||||||
:ref:`Cluster <cluster_definition>`
|
|
||||||
:ref:`Managed resources <managed_resource_definition>`.
|
|
||||||
|
|
||||||
It is represented as a set of
|
|
||||||
:ref:`Managed resources <managed_resource_definition>`
|
|
||||||
(which may be a simple tree or a flat list of key-value pairs)
|
|
||||||
which enables Watcher :ref:`Strategies <strategy_definition>` to know the
|
|
||||||
current relationships between the different
|
|
||||||
:ref:`resources <managed_resource_definition>`) of the
|
|
||||||
:ref:`Cluster <cluster_definition>` during an :ref:`Audit <audit_definition>`
|
|
||||||
and enables the :ref:`Strategy <strategy_definition>` to request information
|
|
||||||
such as:
|
|
||||||
|
|
||||||
- What compute nodes are in a given
|
|
||||||
:ref:`Availability Zone <availability_zone_definition>`
|
|
||||||
or a given :ref:`Host Aggregate <host_aggregates_definition>` ?
|
|
||||||
- What :ref:`Instances <instance_definition>` are hosted on a given compute
|
|
||||||
node ?
|
|
||||||
- What is the current load of a compute node ?
|
|
||||||
- What is the current free memory of a compute node ?
|
|
||||||
- What is the network link between two compute nodes ?
|
|
||||||
- What is the available bandwidth on a given network link ?
|
|
||||||
- What is the current space available on a given virtual disk of a given
|
|
||||||
:ref:`Instance <instance_definition>` ?
|
|
||||||
- What is the current state of a given :ref:`Instance <instance_definition>`?
|
|
||||||
- ...
|
|
||||||
|
|
||||||
In a word, this data model enables the :ref:`Strategy <strategy_definition>`
|
|
||||||
to know:
|
|
||||||
|
|
||||||
- the current topology of the :ref:`Cluster <cluster_definition>`
|
|
||||||
- the current capacity for each
|
|
||||||
:ref:`Managed resource <managed_resource_definition>`
|
|
||||||
- the current amount of used/free space for each
|
|
||||||
:ref:`Managed resource <managed_resource_definition>`
|
|
||||||
- the current state of each
|
|
||||||
:ref:`Managed resources <managed_resource_definition>`
|
|
||||||
|
|
||||||
In the Watcher project, we aim at providing a generic and very basic
|
|
||||||
:ref:`Cluster Data Model <cluster_data_model_definition>` for each
|
|
||||||
:ref:`Goal <goal_definition>`, usable in the associated
|
|
||||||
:ref:`Strategies <strategy_definition>` through some helper classes in order
|
|
||||||
to:
|
|
||||||
|
|
||||||
- simplify the development of a new
|
|
||||||
:ref:`Strategy <strategy_definition>` for a given
|
|
||||||
:ref:`Goal <goal_definition>` when there already are some existing
|
|
||||||
:ref:`Strategies <strategy_definition>` associated to the same
|
|
||||||
:ref:`Goal <goal_definition>`
|
|
||||||
- avoid duplicating the same code in several
|
|
||||||
:ref:`Strategies <strategy_definition>` associated to the same
|
|
||||||
:ref:`Goal <goal_definition>`
|
|
||||||
- have a better consistency between the different
|
|
||||||
:ref:`Strategies <strategy_definition>` for a given
|
|
||||||
:ref:`Goal <goal_definition>`
|
|
||||||
- avoid any strong coupling with any external
|
|
||||||
:ref:`Cluster Data Model <cluster_data_model_definition>`
|
|
||||||
(the proposed data model acts as a pivot data model)
|
|
||||||
|
|
||||||
There may be various
|
|
||||||
:ref:`generic and basic Cluster Data Models <cluster_data_model_definition>`
|
|
||||||
proposed in Watcher helpers, each of them being adapted to achieving a given
|
|
||||||
:ref:`Goal <goal_definition>`:
|
|
||||||
|
|
||||||
- For example, for a
|
|
||||||
:ref:`Goal <goal_definition>` which aims at optimizing the network
|
|
||||||
:ref:`resources <managed_resource_definition>` the
|
|
||||||
:ref:`Strategy <strategy_definition>` may need to know which
|
|
||||||
:ref:`resources <managed_resource_definition>` are communicating together.
|
|
||||||
- Whereas for a :ref:`Goal <goal_definition>` which aims at optimizing thermal
|
|
||||||
and power conditions, the :ref:`Strategy <strategy_definition>` may need to
|
|
||||||
know the location of each compute node in the racks and the location of each
|
|
||||||
rack in the room.
|
|
||||||
|
|
||||||
Note however that a developer can use his/her own
|
|
||||||
:ref:`Cluster Data Model <cluster_data_model_definition>` if the proposed data
|
|
||||||
model does not fit his/her needs as long as the
|
|
||||||
:ref:`Strategy <strategy_definition>` is able to produce a
|
|
||||||
:ref:`Solution <solution_definition>` for the requested
|
|
||||||
:ref:`Goal <goal_definition>`.
|
|
||||||
For example, a developer could rely on the Nova Data Model to optimize some
|
|
||||||
compute resources.
|
|
||||||
|
|
||||||
The :ref:`Cluster Data Model <cluster_data_model_definition>` may be persisted
|
|
||||||
in any appropriate storage system (SQL database, NoSQL database, JSON file,
|
|
||||||
XML File, In Memory Database, ...).
|
|
||||||
|
|
||||||
.. _cluster_history_definition:
|
.. _cluster_history_definition:
|
||||||
|
|
||||||
Cluster History
|
Cluster History
|
||||||
===============
|
===============
|
||||||
|
|
||||||
The :ref:`Cluster History <cluster_history_definition>` contains all the
|
.. watcher-term:: watcher.metrics_engine.cluster_history.api
|
||||||
previously collected timestamped data such as metrics and events associated
|
|
||||||
to any :ref:`managed resource <managed_resource_definition>` of the
|
|
||||||
:ref:`Cluster <cluster_definition>`.
|
|
||||||
|
|
||||||
Just like the :ref:`Cluster Data Model <cluster_data_model_definition>`, this
|
|
||||||
history may be used by any :ref:`Strategy <strategy_definition>` in order to
|
|
||||||
find the most optimal :ref:`Solution <solution_definition>` during an
|
|
||||||
:ref:`Audit <audit_definition>`.
|
|
||||||
|
|
||||||
In the Watcher project, a generic
|
|
||||||
:ref:`Cluster History <cluster_history_definition>`
|
|
||||||
API is proposed with some helper classes in order to :
|
|
||||||
|
|
||||||
- share a common measurement (events or metrics) naming based on what is
|
|
||||||
defined in Ceilometer.
|
|
||||||
See `the full list of available measurements <http://docs.openstack.org/admin-guide-cloud/telemetry-measurements.html>`_
|
|
||||||
- share common meter types (Cumulative, Delta, Gauge) based on what is
|
|
||||||
defined in Ceilometer.
|
|
||||||
See `the full list of meter types <http://docs.openstack.org/admin-guide-cloud/telemetry-measurements.html>`_
|
|
||||||
- simplify the development of a new :ref:`Strategy <strategy_definition>`
|
|
||||||
- avoid duplicating the same code in several
|
|
||||||
:ref:`Strategies <strategy_definition>`
|
|
||||||
- have a better consistency between the different
|
|
||||||
:ref:`Strategies <strategy_definition>`
|
|
||||||
- avoid any strong coupling with any external metrics/events storage system
|
|
||||||
(the proposed API and measurement naming system acts as a pivot format)
|
|
||||||
|
|
||||||
Note however that a developer can use his/her own history management system if
|
|
||||||
the Ceilometer system does not fit his/her needs as long as the
|
|
||||||
:ref:`Strategy <strategy_definition>` is able to produce a
|
|
||||||
:ref:`Solution <solution_definition>` for the requested
|
|
||||||
:ref:`Goal <goal_definition>`.
|
|
||||||
|
|
||||||
The :ref:`Cluster History <cluster_history_definition>` data may be persisted
|
|
||||||
in any appropriate storage system (InfluxDB, OpenTSDB, MongoDB,...).
|
|
||||||
|
|
||||||
.. _controller_node_definition:
|
.. _controller_node_definition:
|
||||||
|
|
||||||
@@ -419,20 +155,7 @@ them, or at least reported to them.
|
|||||||
Goal
|
Goal
|
||||||
====
|
====
|
||||||
|
|
||||||
A :ref:`Goal <goal_definition>` is a human readable, observable and measurable
|
.. watcher-term:: watcher.api.controllers.v1.goal
|
||||||
end result having one objective to be achieved.
|
|
||||||
|
|
||||||
Here are some examples of :ref:`Goals <goal_definition>`:
|
|
||||||
|
|
||||||
- minimize the energy consumption
|
|
||||||
- minimize the number of compute nodes (consolidation)
|
|
||||||
- balance the workload among compute nodes
|
|
||||||
- minimize the license cost (some softwares have a licensing model which is
|
|
||||||
based on the number of sockets or cores where the software is deployed)
|
|
||||||
- find the most appropriate moment for a planned maintenance on a
|
|
||||||
given group of host (which may be an entire availability zone):
|
|
||||||
power supply replacement, cooling system replacement, hardware
|
|
||||||
modification, ...
|
|
||||||
|
|
||||||
|
|
||||||
.. _host_aggregates_definition:
|
.. _host_aggregates_definition:
|
||||||
@@ -490,27 +213,27 @@ Here are some examples of
|
|||||||
|
|
||||||
It can be any of the `the official list of available resource types defined in OpenStack for HEAT <http://docs.openstack.org/developer/heat/template_guide/openstack.html>`_.
|
It can be any of the `the official list of available resource types defined in OpenStack for HEAT <http://docs.openstack.org/developer/heat/template_guide/openstack.html>`_.
|
||||||
|
|
||||||
.. _efficiency_definition:
|
.. _efficacy_definition:
|
||||||
|
|
||||||
Optimization Efficiency
|
Optimization Efficacy
|
||||||
=======================
|
=====================
|
||||||
|
|
||||||
The :ref:`Optimization Efficiency <efficiency_definition>` is the objective
|
The :ref:`Optimization Efficacy <efficacy_definition>` is the objective
|
||||||
measure of how much of the :ref:`Goal <goal_definition>` has been achieved in
|
measure of how much of the :ref:`Goal <goal_definition>` has been achieved in
|
||||||
respect with constraints and :ref:`SLAs <sla_definition>` defined by the
|
respect with constraints and :ref:`SLAs <sla_definition>` defined by the
|
||||||
:ref:`Customer <customer_definition>`.
|
:ref:`Customer <customer_definition>`.
|
||||||
|
|
||||||
The way efficiency is evaluated will depend on the
|
The way efficacy is evaluated will depend on the
|
||||||
:ref:`Goal <goal_definition>` to achieve.
|
:ref:`Goal <goal_definition>` to achieve.
|
||||||
|
|
||||||
Of course, the efficiency will be relevant only as long as the
|
Of course, the efficacy will be relevant only as long as the
|
||||||
:ref:`Action Plan <action_plan_definition>` is relevant
|
:ref:`Action Plan <action_plan_definition>` is relevant
|
||||||
(i.e., the current state of the :ref:`Cluster <cluster_definition>`
|
(i.e., the current state of the :ref:`Cluster <cluster_definition>`
|
||||||
has not changed in a way that a new :ref:`Audit <audit_definition>` would need
|
has not changed in a way that a new :ref:`Audit <audit_definition>` would need
|
||||||
to be launched).
|
to be launched).
|
||||||
|
|
||||||
For example, if the :ref:`Goal <goal_definition>` is to lower the energy
|
For example, if the :ref:`Goal <goal_definition>` is to lower the energy
|
||||||
consumption, the :ref:`Efficiency <efficiency_definition>` will be computed
|
consumption, the :ref:`Efficacy <efficacy_definition>` will be computed
|
||||||
using several indicators (KPIs):
|
using several indicators (KPIs):
|
||||||
|
|
||||||
- the percentage of energy gain (which must be the highest possible)
|
- the percentage of energy gain (which must be the highest possible)
|
||||||
@@ -521,7 +244,7 @@ using several indicators (KPIs):
|
|||||||
All those indicators (KPIs) are computed within a given timeframe, which is the
|
All those indicators (KPIs) are computed within a given timeframe, which is the
|
||||||
time taken to execute the whole :ref:`Action Plan <action_plan_definition>`.
|
time taken to execute the whole :ref:`Action Plan <action_plan_definition>`.
|
||||||
|
|
||||||
The efficiency also enables the :ref:`Administrator <administrator_definition>`
|
The efficacy also enables the :ref:`Administrator <administrator_definition>`
|
||||||
to objectively compare different :ref:`Strategies <strategy_definition>` for
|
to objectively compare different :ref:`Strategies <strategy_definition>` for
|
||||||
the same goal and same workload of the :ref:`Cluster <cluster_definition>`.
|
the same goal and same workload of the :ref:`Cluster <cluster_definition>`.
|
||||||
|
|
||||||
@@ -539,23 +262,6 @@ specific domain.
|
|||||||
Please, read `the official OpenStack definition of a Project <http://docs.openstack.org/glossary/content/glossary.html>`_.
|
Please, read `the official OpenStack definition of a Project <http://docs.openstack.org/glossary/content/glossary.html>`_.
|
||||||
|
|
||||||
|
|
||||||
.. _primitive_definition
|
|
||||||
|
|
||||||
Primitive
|
|
||||||
=========
|
|
||||||
|
|
||||||
A :ref:`Primitive <primitive_definition>` is the component that carries out a
|
|
||||||
certain type of atomic :ref:`Actions <action_definition>` on a given
|
|
||||||
:ref:`Managed resource <managed_resource_definition>` (nova, swift, neutron,
|
|
||||||
glance,..). A :ref:`Primitive <primitive_definition>` is a part of the
|
|
||||||
:ref:`Watcher Applier <watcher_applier_definition>` module.
|
|
||||||
|
|
||||||
For example, there can be a :ref:`Primitive <primitive_definition>` which is
|
|
||||||
responsible for creating a snapshot of a given instance on a Nova compute node.
|
|
||||||
This :ref:`Primitive <primitive_definition>` knows exactly how to send
|
|
||||||
the appropriate commands to Nova for this type of
|
|
||||||
:ref:`Actions <action_definition>`.
|
|
||||||
|
|
||||||
.. _sla_definition:
|
.. _sla_definition:
|
||||||
|
|
||||||
SLA
|
SLA
|
||||||
@@ -610,67 +316,21 @@ which provides a good definition.
|
|||||||
Solution
|
Solution
|
||||||
========
|
========
|
||||||
|
|
||||||
A :ref:`Solution <solution_definition>` is a set of
|
.. watcher-term:: watcher.decision_engine.solution.base
|
||||||
:ref:`Actions <action_definition>` generated by a
|
|
||||||
:ref:`Strategy <strategy_definition>` (i.e., an algorithm) in order to achieve
|
|
||||||
the :ref:`Goal <goal_definition>` of an :ref:`Audit <audit_definition>`.
|
|
||||||
|
|
||||||
A :ref:`Solution <solution_definition>` is different from an
|
|
||||||
:ref:`Action Plan <action_plan_definition>` because it contains the
|
|
||||||
non-scheduled list of :ref:`Actions <action_definition>` which is produced by a
|
|
||||||
:ref:`Strategy <strategy_definition>`. In other words, the list of Actions in
|
|
||||||
a :ref:`Solution <solution_definition>` has not yet been re-ordered by the
|
|
||||||
:ref:`Watcher Planner <watcher_planner_definition>`.
|
|
||||||
|
|
||||||
Note that some algorithms (i.e. :ref:`Strategies <strategy_definition>`) may
|
|
||||||
generate several :ref:`Solutions <solution_definition>`. This gives rise to the
|
|
||||||
problem of determining which :ref:`Solution <solution_definition>` should be
|
|
||||||
applied.
|
|
||||||
|
|
||||||
Two approaches to dealing with this can be envisaged:
|
|
||||||
|
|
||||||
- **fully automated mode**: only the :ref:`Solution <solution_definition>`
|
|
||||||
with the highest ranking (i.e., the highest
|
|
||||||
:ref:`Optimization Efficiency <efficiency_definition>`)
|
|
||||||
will be sent to the :ref:`Watcher Planner <watcher_planner_definition>` and
|
|
||||||
translated into concrete :ref:`Actions <action_definition>`.
|
|
||||||
- **manual mode**: several :ref:`Solutions <solution_definition>` are proposed
|
|
||||||
to the :ref:`Administrator <administrator_definition>` with a detailed
|
|
||||||
measurement of the estimated
|
|
||||||
:ref:`Optimization Efficiency <efficiency_definition>` and he/she decides
|
|
||||||
which one will be launched.
|
|
||||||
|
|
||||||
.. _strategy_definition:
|
.. _strategy_definition:
|
||||||
|
|
||||||
Strategy
|
Strategy
|
||||||
========
|
========
|
||||||
|
|
||||||
A :ref:`Strategy <strategy_definition>` is an algorithm implementation which is
|
.. watcher-term:: watcher.decision_engine.strategy.strategies.base
|
||||||
able to find a :ref:`Solution <solution_definition>` for a given
|
|
||||||
:ref:`Goal <goal_definition>`.
|
|
||||||
|
|
||||||
There may be several potential strategies which are able to achieve the same
|
|
||||||
:ref:`Goal <goal_definition>`. This is why it is possible to configure which
|
|
||||||
specific :ref:`Strategy <strategy_definition>` should be used for each
|
|
||||||
:ref:`Goal <goal_definition>`.
|
|
||||||
|
|
||||||
Some strategies may provide better optimization results but may take more time
|
|
||||||
to find an optimal :ref:`Solution <solution_definition>`.
|
|
||||||
|
|
||||||
When a new :ref:`Goal <goal_definition>` is added to the Watcher configuration,
|
|
||||||
at least one default associated :ref:`Strategy <strategy_definition>` should be
|
|
||||||
provided as well.
|
|
||||||
|
|
||||||
.. _watcher_applier_definition:
|
.. _watcher_applier_definition:
|
||||||
|
|
||||||
Watcher Applier
|
Watcher Applier
|
||||||
===============
|
===============
|
||||||
|
|
||||||
This component is in charge of executing the
|
.. watcher-term:: watcher.applier.base
|
||||||
:ref:`Action Plan <action_plan_definition>` built by the
|
|
||||||
:ref:`Watcher Decision Engine <watcher_decision_engine_definition>`.
|
|
||||||
|
|
||||||
See :doc:`architecture` for more details on this component.
|
|
||||||
|
|
||||||
.. _watcher_database_definition:
|
.. _watcher_database_definition:
|
||||||
|
|
||||||
@@ -696,47 +356,12 @@ See :doc:`architecture` for more details on this component.
|
|||||||
Watcher Decision Engine
|
Watcher Decision Engine
|
||||||
=======================
|
=======================
|
||||||
|
|
||||||
This component is responsible for computing a set of potential optimization
|
.. watcher-term:: watcher.decision_engine.manager
|
||||||
:ref:`Actions <action_definition>` in order to fulfill the
|
|
||||||
:ref:`Goal <goal_definition>` of an :ref:`Audit <audit_definition>`.
|
|
||||||
|
|
||||||
It first reads the parameters of the :ref:`Audit <audit_definition>` from the
|
|
||||||
associated :ref:`Audit Template <audit_template_definition>` and knows the
|
|
||||||
:ref:`Goal <goal_definition>` to achieve.
|
|
||||||
|
|
||||||
It then selects the most appropriate :ref:`Strategy <strategy_definition>`
|
|
||||||
depending on how Watcher was configured for this :ref:`Goal <goal_definition>`.
|
|
||||||
|
|
||||||
The :ref:`Strategy <strategy_definition>` is then executed and generates a set
|
|
||||||
of :ref:`Actions <action_definition>` which are scheduled in time by the
|
|
||||||
:ref:`Watcher Planner <watcher_planner_definition>` (i.e., it generates an
|
|
||||||
:ref:`Action Plan <action_plan_definition>`).
|
|
||||||
|
|
||||||
See :doc:`architecture` for more details on this component.
|
|
||||||
|
|
||||||
.. _watcher_planner_definition:
|
.. _watcher_planner_definition:
|
||||||
|
|
||||||
Watcher Planner
|
Watcher Planner
|
||||||
===============
|
===============
|
||||||
|
|
||||||
The :ref:`Watcher Planner <watcher_planner_definition>` is part of the
|
.. watcher-term:: watcher.decision_engine.planner.base
|
||||||
:ref:`Watcher Decision Engine <watcher_decision_engine_definition>`.
|
|
||||||
|
|
||||||
This module takes the set of :ref:`Actions <action_definition>` generated by a
|
|
||||||
:ref:`Strategy <strategy_definition>` and builds the design of a workflow which
|
|
||||||
defines how-to schedule in time those different
|
|
||||||
:ref:`Actions <action_definition>` and for each
|
|
||||||
:ref:`Action <action_definition>` what are the prerequisite conditions.
|
|
||||||
|
|
||||||
It is important to schedule :ref:`Actions <action_definition>` in time in order
|
|
||||||
to prevent overload of the :ref:`Cluster <cluster_definition>` while applying
|
|
||||||
the :ref:`Action Plan <action_plan_definition>`. For example, it is important
|
|
||||||
not to migrate too many instances at the same time in order to avoid a network
|
|
||||||
congestion which may decrease the :ref:`SLA <sla_definition>` for
|
|
||||||
:ref:`Customers <customer_definition>`.
|
|
||||||
|
|
||||||
It is also important to schedule :ref:`Actions <action_definition>` in order to
|
|
||||||
avoid security issues such as denial of service on core OpenStack services.
|
|
||||||
|
|
||||||
See :doc:`architecture` for more details on this component.
|
|
||||||
|
|
||||||
|
|||||||
BIN
doc/source/image_src/dia/architecture.dia
Normal file
BIN
doc/source/image_src/dia/functional_data_model.dia
Normal file
16
doc/source/image_src/plantuml/action_plan_state_machine.txt
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
@startuml
|
||||||
|
|
||||||
|
[*] --> RECOMMENDED: The Watcher Planner\ncreates the Action Plan
|
||||||
|
RECOMMENDED --> PENDING: Adminisrator launches\nthe Action Plan
|
||||||
|
PENDING --> ONGOING: The Watcher Applier receives the request\nto launch the Action Plan
|
||||||
|
ONGOING --> FAILED: Something failed while executing\nthe Action Plan in the Watcher Applier
|
||||||
|
ONGOING --> SUCCEEDED: The Watcher Applier executed\nthe Action Plan successfully
|
||||||
|
FAILED --> DELETED : Administrator removes\nAction Plan
|
||||||
|
SUCCEEDED --> DELETED : Administrator removes\nAction Plan
|
||||||
|
ONGOING --> CANCELLED : Administrator cancels\nAction Plan
|
||||||
|
RECOMMENDED --> CANCELLED : Administrator cancels\nAction Plan
|
||||||
|
PENDING --> CANCELLED : Administrator cancels\nAction Plan
|
||||||
|
CANCELLED --> DELETED
|
||||||
|
DELETED --> [*]
|
||||||
|
|
||||||
|
@enduml
|
||||||
14
doc/source/image_src/plantuml/audit_state_machine.txt
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
@startuml
|
||||||
|
|
||||||
|
[*] --> PENDING: Audit requested by Administrator
|
||||||
|
PENDING --> ONGOING: Audit request is received\nby the Watcher Decision Engine
|
||||||
|
ONGOING --> FAILED: Audit fails\n(no solution found, technical error, ...)
|
||||||
|
ONGOING --> SUCCEEDED: The Watcher Decision Engine\ncould find at least one Solution
|
||||||
|
FAILED --> DELETED : Administrator wants to\narchive/delete the Audit
|
||||||
|
SUCCEEDED --> DELETED : Administrator wants to\narchive/delete the Audit
|
||||||
|
PENDING --> CANCELLED : Administrator cancels\nthe Audit
|
||||||
|
ONGOING --> CANCELLED : Administrator cancels\nthe Audit
|
||||||
|
CANCELLED --> DELETED : Administrator wants to\narchive/delete the Audit
|
||||||
|
DELETED --> [*]
|
||||||
|
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
@startuml
|
||||||
|
|
||||||
|
|
||||||
|
actor Administrator
|
||||||
|
|
||||||
|
Administrator -> "Watcher CLI" : watcher audit-create -a <audit_template_uuid>
|
||||||
|
|
||||||
|
"Watcher CLI" -> "Watcher API" : POST audit(parameters)
|
||||||
|
"Watcher API" -> "Watcher Database" : create new audit in database (status=PENDING)
|
||||||
|
|
||||||
|
"Watcher API" <-- "Watcher Database" : new audit uuid
|
||||||
|
"Watcher CLI" <-- "Watcher API" : return new audit URL
|
||||||
|
|
||||||
|
Administrator <-- "Watcher CLI" : new audit uuid
|
||||||
|
|
||||||
|
"Watcher API" -> "AMQP Bus" : trigger_audit(new_audit.uuid)
|
||||||
|
"AMQP Bus" -> "Watcher Decision Engine" : trigger_audit(new_audit.uuid)
|
||||||
|
|
||||||
|
ref over "Watcher Decision Engine"
|
||||||
|
Trigger audit in the
|
||||||
|
Watcher Decision Engine
|
||||||
|
end ref
|
||||||
|
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
@startuml
|
||||||
|
|
||||||
|
actor Administrator
|
||||||
|
|
||||||
|
Administrator -> "Watcher CLI" : watcher audit-template-create <name> <goal>
|
||||||
|
"Watcher CLI" -> "Watcher API" : POST audit_template(parameters)
|
||||||
|
|
||||||
|
"Watcher API" -> "Watcher API" : make sure goal exist in configuration
|
||||||
|
"Watcher API" -> "Watcher Database" : create new audit_template in database
|
||||||
|
|
||||||
|
"Watcher API" <-- "Watcher Database" : new audit template uuid
|
||||||
|
"Watcher CLI" <-- "Watcher API" : return new audit template URL in HTTP Location Header
|
||||||
|
Administrator <-- "Watcher CLI" : new audit template uuid
|
||||||
|
|
||||||
|
@enduml
|
||||||
|
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
@startuml
|
||||||
|
|
||||||
|
actor Administrator
|
||||||
|
|
||||||
|
Administrator -> "Watcher CLI" : watcher action-plan-start <action_plan_uuid>
|
||||||
|
|
||||||
|
"Watcher CLI" -> "Watcher API" : PATCH action_plan(state=TRIGGERED)
|
||||||
|
"Watcher API" -> "Watcher Database" : action_plan.state=TRIGGERED
|
||||||
|
|
||||||
|
"Watcher CLI" <-- "Watcher API" : HTTP 200
|
||||||
|
|
||||||
|
Administrator <-- "Watcher CLI" : OK
|
||||||
|
|
||||||
|
"Watcher API" -> "AMQP Bus" : launch_action_plan(action_plan.uuid)
|
||||||
|
"AMQP Bus" -> "Watcher Applier" : launch_action_plan(action_plan.uuid)
|
||||||
|
|
||||||
|
ref over "Watcher Applier"
|
||||||
|
Launch Action Plan in the
|
||||||
|
Watcher Applier
|
||||||
|
end ref
|
||||||
|
|
||||||
|
@enduml
|
||||||
|
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
@startuml
|
||||||
|
|
||||||
|
"AMQP Bus" -> "Watcher Applier" : launch_action_plan(action_plan.uuid)
|
||||||
|
"Watcher Applier" -> "Watcher Database" : action_plan.state=ONGOING
|
||||||
|
"Watcher Applier" -[#blue]> "AMQP Bus" : notify action plan state = ONGOING
|
||||||
|
"Watcher Applier" -> "Watcher Database" : get_action_list(action_plan.uuid)
|
||||||
|
"Watcher Applier" <-- "Watcher Database" : actions
|
||||||
|
loop for each action of the action flow
|
||||||
|
create Action
|
||||||
|
"Watcher Applier" -> Action : instantiate Action object with target resource id\n and input parameters
|
||||||
|
"Watcher Applier" -> Action : validate_parameters()
|
||||||
|
"Watcher Applier" <-- Action : OK
|
||||||
|
"Watcher Applier" -[#blue]> "AMQP Bus" : notify action state = ONGOING
|
||||||
|
"Watcher Applier" -> Action : preconditions()
|
||||||
|
"Watcher Applier" <-- Action : OK
|
||||||
|
"Watcher Applier" -> Action : execute()
|
||||||
|
alt action is "migrate instance"
|
||||||
|
Action -> "Nova API" : migrate(instance_id, dest_host_id)
|
||||||
|
Action <-- "Nova API" : OK
|
||||||
|
else action is "disable hypervisor"
|
||||||
|
Action -> "Nova API" : host-update(host_id, maintenance=true)
|
||||||
|
Action <-- "Nova API" : OK
|
||||||
|
end
|
||||||
|
"Watcher Applier" <-- Action : OK
|
||||||
|
"Watcher Applier" -> "Watcher Database" : action.state=SUCCEEDED
|
||||||
|
"Watcher Applier" -[#blue]> "AMQP Bus" : notify action state = SUCCEEDED
|
||||||
|
end
|
||||||
|
"Watcher Applier" -> "Watcher Database" : action_plan.state=SUCCEEDED
|
||||||
|
"Watcher Applier" -[#blue]> "AMQP Bus" : notify action plan state = SUCCEEDED
|
||||||
|
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
@startuml
|
||||||
|
|
||||||
|
actor Administrator
|
||||||
|
|
||||||
|
== Create some Audit settings ==
|
||||||
|
|
||||||
|
Administrator -> Watcher : create new Audit Template (i.e. Audit settings : goal, scope, deadline,...)
|
||||||
|
Watcher -> Watcher : save Audit Template in database
|
||||||
|
Administrator <-- Watcher : Audit Template UUID
|
||||||
|
|
||||||
|
== Launch a new Audit ==
|
||||||
|
|
||||||
|
Administrator -> Watcher : launch new Audit of the Openstack infrastructure resources\nwith a previously created Audit Template
|
||||||
|
Administrator <-- Watcher : Audit UUID
|
||||||
|
Administrator -> Watcher : get the Audit state
|
||||||
|
Administrator <-- Watcher : ONGOING
|
||||||
|
Watcher -> Watcher : compute a solution to achieve optimization goal
|
||||||
|
Administrator -> Watcher : get the Audit state
|
||||||
|
Administrator <-- Watcher : SUCCEEDED
|
||||||
|
|
||||||
|
== Get the result of the Audit ==
|
||||||
|
|
||||||
|
Administrator -> Watcher : get Action Plan
|
||||||
|
Administrator <-- Watcher : recommended Action Plan and estimated efficacy
|
||||||
|
Administrator -> Administrator : verify the recommended actions\nand evaluate the estimated gain vs aggressiveness of the solution
|
||||||
|
|
||||||
|
== Launch the recommended Action Plan ==
|
||||||
|
|
||||||
|
Administrator -> Watcher : launch the Action Plan
|
||||||
|
Administrator <-- Watcher : Action Plan has been launched
|
||||||
|
Watcher -> Watcher : trigger Actions on Openstack services
|
||||||
|
Administrator -> Watcher : get the Action Plan state
|
||||||
|
Administrator <-- Watcher : ONGOING
|
||||||
|
Administrator -> Watcher : get the Action Plan state
|
||||||
|
Administrator <-- Watcher : SUCCEEDED
|
||||||
|
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
@startuml
|
||||||
|
|
||||||
|
"AMQP Bus" -> "Watcher Decision Engine" : trigger_audit(new_audit.uuid)
|
||||||
|
"Watcher Decision Engine" -> "Watcher Database" : update audit.state = ONGOING
|
||||||
|
"AMQP Bus" <[#blue]- "Watcher Decision Engine" : notify new audit state = ONGOING
|
||||||
|
"Watcher Decision Engine" -> "Watcher Database" : get audit parameters(goal, ...)
|
||||||
|
"Watcher Decision Engine" <-- "Watcher Database" : audit parameters(goal, ...)
|
||||||
|
create Strategy
|
||||||
|
"Watcher Decision Engine" -[#red]> "Strategy": select appropriate\noptimization strategy
|
||||||
|
loop while enough data to build cluster data model
|
||||||
|
"Watcher Decision Engine" -> "Nova API" : get resource state (host, instance, ...)
|
||||||
|
"Watcher Decision Engine" <-- "Nova API" : resource state
|
||||||
|
end
|
||||||
|
"Watcher Decision Engine" -[#red]> "Watcher Decision Engine": build cluster_data_model
|
||||||
|
"Watcher Decision Engine" -> "Strategy" : execute(cluster_data_model)
|
||||||
|
loop while enough history data for the strategy
|
||||||
|
"Strategy" -> "Ceilometer API": get_aggregated_metrics\n(resource_id,meter_name,period,aggregate_method)
|
||||||
|
"Strategy" <-- "Ceilometer API": aggregated metrics
|
||||||
|
end
|
||||||
|
"Strategy" -> "Strategy" : compute solution to achieve goal
|
||||||
|
"Watcher Decision Engine" <-- "Strategy" : solution = array of actions (i.e. not scheduled yet)
|
||||||
|
create "Watcher Planner"
|
||||||
|
"Watcher Decision Engine" -[#red]> "Watcher Planner": select appropriate actions scheduler (i.e. Planner implementation)
|
||||||
|
"Watcher Decision Engine" -> "Watcher Planner": schedule(audit_id, solution)
|
||||||
|
"Watcher Planner" -> "Watcher Planner": schedule actions according to\nscheduling rules/policies
|
||||||
|
"Watcher Decision Engine" <-- "Watcher Planner": new action_plan
|
||||||
|
"Watcher Decision Engine" -> "Watcher Database" : save new action_plan in database
|
||||||
|
"Watcher Decision Engine" -> "Watcher Database" : update audit.state = SUCCEEDED
|
||||||
|
"AMQP Bus" <[#blue]- "Watcher Decision Engine" : notify new audit state = SUCCEEDED
|
||||||
|
|
||||||
|
@enduml
|
||||||
BIN
doc/source/images/action_plan_state_machine.png
Normal file
|
After Width: | Height: | Size: 48 KiB |
@@ -41,36 +41,31 @@
|
|||||||
</text>
|
</text>
|
||||||
</g>
|
</g>
|
||||||
<g>
|
<g>
|
||||||
<rect style="fill: #ffb400" x="407" y="-0.832" width="162.3" height="89"/>
|
<rect style="fill: #ffb400" x="407" y="-38.25" width="325.386" height="126.418"/>
|
||||||
<path style="fill: #ffb400" d="M 407,-0.832 A 10,10 0 0 0 397,9.168 L 407,9.168 z"/>
|
<path style="fill: #ffb400" d="M 407,-38.25 A 10,10 0 0 0 397,-28.25 L 407,-28.25 z"/>
|
||||||
<path style="fill: #ffb400" d="M 579.3,9.168 A 10,10 0 0 0 569.3,-0.832 L 569.3,9.168 z"/>
|
<path style="fill: #ffb400" d="M 742.386,-28.25 A 10,10 0 0 0 732.386,-38.25 L 732.386,-28.25 z"/>
|
||||||
<rect style="fill: #ffb400" x="397" y="9.168" width="182.3" height="69"/>
|
<rect style="fill: #ffb400" x="397" y="-28.25" width="345.386" height="106.418"/>
|
||||||
<path style="fill: #ffb400" d="M 397,78.168 A 10,10 0 0 0 407,88.168 L 407,78.168 z"/>
|
<path style="fill: #ffb400" d="M 397,78.168 A 10,10 0 0 0 407,88.168 L 407,78.168 z"/>
|
||||||
<path style="fill: #ffb400" d="M 569.3,88.168 A 10,10 0 0 0 579.3,78.168 L 569.3,78.168 z"/>
|
<path style="fill: #ffb400" d="M 732.386,88.168 A 10,10 0 0 0 742.386,78.168 L 732.386,78.168 z"/>
|
||||||
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="407" y1="-0.832" x2="569.3" y2="-0.832"/>
|
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="407" y1="-38.25" x2="732.386" y2="-38.25"/>
|
||||||
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="407" y1="88.168" x2="569.3" y2="88.168"/>
|
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="407" y1="88.168" x2="732.386" y2="88.168"/>
|
||||||
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 407,-0.832 A 10,10 0 0 0 397,9.168"/>
|
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 407,-38.25 A 10,10 0 0 0 397,-28.25"/>
|
||||||
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 579.3,9.168 A 10,10 0 0 0 569.3,-0.832"/>
|
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 742.386,-28.25 A 10,10 0 0 0 732.386,-38.25"/>
|
||||||
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="397" y1="9.168" x2="397" y2="78.168"/>
|
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="397" y1="-28.25" x2="397" y2="78.168"/>
|
||||||
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="579.3" y1="9.168" x2="579.3" y2="78.168"/>
|
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="742.386" y1="-28.25" x2="742.386" y2="78.168"/>
|
||||||
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 397,78.168 A 10,10 0 0 0 407,88.168"/>
|
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 397,78.168 A 10,10 0 0 0 407,88.168"/>
|
||||||
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 569.3,88.168 A 10,10 0 0 0 579.3,78.168"/>
|
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 732.386,88.168 A 10,10 0 0 0 742.386,78.168"/>
|
||||||
<text font-size="14.1111" style="fill: #ffffff;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="488.15" y="39.1291">
|
<text font-size="14.1111" style="fill: #ffffff;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="569.693" y="20.4201">
|
||||||
<tspan x="488.15" y="39.1291">Watcher</tspan>
|
<tspan x="569.693" y="20.4201">Watcher</tspan>
|
||||||
<tspan x="488.15" y="56.768">Decision Engine</tspan>
|
<tspan x="569.693" y="38.059">Decision Engine</tspan>
|
||||||
</text>
|
</text>
|
||||||
</g>
|
</g>
|
||||||
<g>
|
<g>
|
||||||
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="341.176" y1="21" x2="387.264" y2="21.3451"/>
|
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke-dasharray: 6; stroke: #000000" x1="346.122" y1="37.0905" x2="387.264" y2="37.4729"/>
|
||||||
<polygon style="fill: #000000" points="394.764,21.4013 384.727,26.3262 387.264,21.3451 384.802,16.3265 "/>
|
<polygon style="fill: #000000" points="338.622,37.0208 348.668,32.1139 346.122,37.0905 348.575,42.1135 "/>
|
||||||
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="394.764,21.4013 384.727,26.3262 387.264,21.3451 384.802,16.3265 "/>
|
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="338.622,37.0208 348.668,32.1139 346.122,37.0905 348.575,42.1135 "/>
|
||||||
</g>
|
<polygon style="fill: #000000" points="394.764,37.5426 384.718,42.4495 387.264,37.4729 384.811,32.4499 "/>
|
||||||
<g>
|
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="394.764,37.5426 384.718,42.4495 387.264,37.4729 384.811,32.4499 "/>
|
||||||
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke-dasharray: 6; stroke: #000000" x1="349.122" y1="65.5706" x2="387.264" y2="65.8474"/>
|
|
||||||
<polygon style="fill: #000000" points="341.622,65.5162 351.658,60.5889 349.122,65.5706 351.585,70.5886 "/>
|
|
||||||
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="341.622,65.5162 351.658,60.5889 349.122,65.5706 351.585,70.5886 "/>
|
|
||||||
<polygon style="fill: #000000" points="394.764,65.9018 384.728,70.8291 387.264,65.8474 384.801,60.8294 "/>
|
|
||||||
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="394.764,65.9018 384.728,70.8291 387.264,65.8474 384.801,60.8294 "/>
|
|
||||||
</g>
|
</g>
|
||||||
<g>
|
<g>
|
||||||
<rect style="fill: #ffb400" x="407" y="-159" width="160" height="89"/>
|
<rect style="fill: #ffb400" x="407" y="-159" width="160" height="89"/>
|
||||||
@@ -160,10 +155,10 @@
|
|||||||
</text>
|
</text>
|
||||||
</g>
|
</g>
|
||||||
<g>
|
<g>
|
||||||
<rect style="fill: #ffffff" x="639.986" y="-66.4" width="117" height="56"/>
|
<rect style="fill: #ffffff" x="644.986" y="-121.4" width="117" height="56"/>
|
||||||
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="639.986" y="-66.4" width="117" height="56"/>
|
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="644.986" y="-121.4" width="117" height="56"/>
|
||||||
<text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="698.486" y="-34.5">
|
<text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="703.486" y="-89.5">
|
||||||
<tspan x="698.486" y="-34.5">Nova</tspan>
|
<tspan x="703.486" y="-89.5">Nova</tspan>
|
||||||
</text>
|
</text>
|
||||||
</g>
|
</g>
|
||||||
<g>
|
<g>
|
||||||
@@ -174,14 +169,9 @@
|
|||||||
</text>
|
</text>
|
||||||
</g>
|
</g>
|
||||||
<g>
|
<g>
|
||||||
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="149.386" y1="-56.9998" x2="216.651" y2="-56.1259"/>
|
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="337" y1="-121" x2="387.264" y2="-121"/>
|
||||||
<polygon style="fill: #000000" points="224.15,-56.0284 214.086,-51.1588 216.651,-56.1259 214.216,-61.1579 "/>
|
<polygon style="fill: #000000" points="394.764,-121 384.764,-116 387.264,-121 384.764,-126 "/>
|
||||||
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="224.15,-56.0284 214.086,-51.1588 216.651,-56.1259 214.216,-61.1579 "/>
|
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="394.764,-121 384.764,-116 387.264,-121 384.764,-126 "/>
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="341.294" y1="-128.975" x2="381.652" y2="-128.189"/>
|
|
||||||
<polygon style="fill: #000000" points="389.15,-128.043 379.055,-123.238 381.652,-128.189 379.25,-133.236 "/>
|
|
||||||
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="389.15,-128.043 379.055,-123.238 381.652,-128.189 379.25,-133.236 "/>
|
|
||||||
</g>
|
</g>
|
||||||
<g>
|
<g>
|
||||||
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke-dasharray: 6; stroke: #000000" x1="346.121" y1="-92.8795" x2="387.265" y2="-92.3705"/>
|
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke-dasharray: 6; stroke: #000000" x1="346.121" y1="-92.8795" x2="387.265" y2="-92.3705"/>
|
||||||
@@ -191,21 +181,16 @@
|
|||||||
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="394.764,-92.2777 384.703,-87.4018 387.265,-92.3705 384.827,-97.401 "/>
|
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="394.764,-92.2777 384.703,-87.4018 387.265,-92.3705 384.827,-97.401 "/>
|
||||||
</g>
|
</g>
|
||||||
<g>
|
<g>
|
||||||
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="577" y1="-114.5" x2="633.053" y2="-59.2355"/>
|
<rect style="fill: #ffffff" x="522.61" y="138.001" width="137.55" height="38"/>
|
||||||
<polygon style="fill: #000000" points="638.394,-53.9699 627.762,-57.4302 633.053,-59.2355 634.783,-64.5512 "/>
|
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="522.61" y="138.001" width="137.55" height="38"/>
|
||||||
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="638.394,-53.9699 627.762,-57.4302 633.053,-59.2355 634.783,-64.5512 "/>
|
<text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="591.385" y="160.901">
|
||||||
</g>
|
<tspan x="591.385" y="160.901">Ceilometer API</tspan>
|
||||||
<g>
|
|
||||||
<rect style="fill: #ffffff" x="654.61" y="121.001" width="137.55" height="38"/>
|
|
||||||
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="654.61" y="121.001" width="137.55" height="38"/>
|
|
||||||
<text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="723.385" y="143.901">
|
|
||||||
<tspan x="723.385" y="143.901">Ceilometer API</tspan>
|
|
||||||
</text>
|
</text>
|
||||||
</g>
|
</g>
|
||||||
<g>
|
<g>
|
||||||
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="584.386" y1="53.5" x2="833.65" y2="54.4624"/>
|
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="409.386" y1="106.75" x2="374.606" y2="138.218"/>
|
||||||
<polygon style="fill: #000000" points="841.15,54.4914 831.131,59.4527 833.65,54.4624 831.169,49.4528 "/>
|
<polygon style="fill: #000000" points="369.044,143.25 373.105,132.833 374.606,138.218 379.814,140.248 "/>
|
||||||
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="841.15,54.4914 831.131,59.4527 833.65,54.4624 831.169,49.4528 "/>
|
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="369.044,143.25 373.105,132.833 374.606,138.218 379.814,140.248 "/>
|
||||||
</g>
|
</g>
|
||||||
<g>
|
<g>
|
||||||
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="-23.614" y1="-89.95" x2="44.0985" y2="-61.7438"/>
|
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="-23.614" y1="-89.95" x2="44.0985" y2="-61.7438"/>
|
||||||
@@ -218,38 +203,62 @@
|
|||||||
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="51.4628,-17.2121 47.6424,-6.70471 46.0184,-12.0538 40.7647,-13.9639 "/>
|
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="51.4628,-17.2121 47.6424,-6.70471 46.0184,-12.0538 40.7647,-13.9639 "/>
|
||||||
</g>
|
</g>
|
||||||
<g>
|
<g>
|
||||||
<rect style="fill: #ffa200" x="506.36" y="72.1008" width="92.025" height="49.9"/>
|
<rect style="fill: #ffa200" x="422.36" y="60.1008" width="92.025" height="49.9"/>
|
||||||
<path style="fill: #ffa200" d="M 506.36,72.1008 A 10,10 0 0 0 496.36,82.1008 L 506.36,82.1008 z"/>
|
<path style="fill: #ffa200" d="M 422.36,60.1008 A 10,10 0 0 0 412.36,70.1008 L 422.36,70.1008 z"/>
|
||||||
<path style="fill: #ffa200" d="M 608.385,82.1008 A 10,10 0 0 0 598.385,72.1008 L 598.385,82.1008 z"/>
|
<path style="fill: #ffa200" d="M 524.385,70.1008 A 10,10 0 0 0 514.385,60.1008 L 514.385,70.1008 z"/>
|
||||||
<rect style="fill: #ffa200" x="496.36" y="82.1008" width="112.025" height="29.9"/>
|
<rect style="fill: #ffa200" x="412.36" y="70.1008" width="112.025" height="29.9"/>
|
||||||
<path style="fill: #ffa200" d="M 496.36,112.001 A 10,10 0 0 0 506.36,122.001 L 506.36,112.001 z"/>
|
<path style="fill: #ffa200" d="M 412.36,100.001 A 10,10 0 0 0 422.36,110.001 L 422.36,100.001 z"/>
|
||||||
<path style="fill: #ffa200" d="M 598.385,122.001 A 10,10 0 0 0 608.385,112.001 L 598.385,112.001 z"/>
|
<path style="fill: #ffa200" d="M 514.385,110.001 A 10,10 0 0 0 524.385,100.001 L 514.385,100.001 z"/>
|
||||||
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="506.36" y1="72.1008" x2="598.385" y2="72.1008"/>
|
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="422.36" y1="60.1008" x2="514.385" y2="60.1008"/>
|
||||||
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="506.36" y1="122.001" x2="598.385" y2="122.001"/>
|
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="422.36" y1="110.001" x2="514.385" y2="110.001"/>
|
||||||
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 506.36,72.1008 A 10,10 0 0 0 496.36,82.1008"/>
|
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 422.36,60.1008 A 10,10 0 0 0 412.36,70.1008"/>
|
||||||
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 608.385,82.1008 A 10,10 0 0 0 598.385,72.1008"/>
|
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 524.385,70.1008 A 10,10 0 0 0 514.385,60.1008"/>
|
||||||
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="496.36" y1="82.1008" x2="496.36" y2="112.001"/>
|
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="412.36" y1="70.1008" x2="412.36" y2="100.001"/>
|
||||||
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="608.385" y1="82.1008" x2="608.385" y2="112.001"/>
|
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="524.385" y1="70.1008" x2="524.385" y2="100.001"/>
|
||||||
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 496.36,112.001 A 10,10 0 0 0 506.36,122.001"/>
|
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 412.36,100.001 A 10,10 0 0 0 422.36,110.001"/>
|
||||||
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 598.385,122.001 A 10,10 0 0 0 608.385,112.001"/>
|
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 514.385,110.001 A 10,10 0 0 0 524.385,100.001"/>
|
||||||
<text font-size="14.1111" style="fill: #ffffff;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="552.372" y="101.331">
|
<text font-size="14.1111" style="fill: #ffffff;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="468.372" y="89.3314">
|
||||||
<tspan x="552.372" y="101.331">Strategy</tspan>
|
<tspan x="468.372" y="89.3314">Strategy</tspan>
|
||||||
</text>
|
</text>
|
||||||
</g>
|
</g>
|
||||||
<g>
|
<g>
|
||||||
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="611.386" y1="115.526" x2="646.095" y2="136.896"/>
|
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="529.386" y1="109.75" x2="550.79" y2="126.911"/>
|
||||||
<polygon style="fill: #000000" points="652.482,140.829 641.345,139.843 646.095,136.896 646.588,131.328 "/>
|
<polygon style="fill: #000000" points="556.641,131.602 545.712,129.248 550.79,126.911 551.967,121.446 "/>
|
||||||
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="652.482,140.829 641.345,139.843 646.095,136.896 646.588,131.328 "/>
|
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="556.641,131.602 545.712,129.248 550.79,126.911 551.967,121.446 "/>
|
||||||
</g>
|
</g>
|
||||||
<g>
|
<g>
|
||||||
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="577,-136.75 874.386,-136.75 874.386,3.76393 "/>
|
<rect style="fill: #ffa200" x="625.488" y="60.1008" width="80.5125" height="49.9"/>
|
||||||
<polygon style="fill: #000000" points="874.386,11.2639 869.386,1.26393 874.386,3.76393 879.386,1.26393 "/>
|
<path style="fill: #ffa200" d="M 625.488,60.1008 A 10,10 0 0 0 615.488,70.1008 L 625.488,70.1008 z"/>
|
||||||
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="874.386,11.2639 869.386,1.26393 874.386,3.76393 879.386,1.26393 "/>
|
<path style="fill: #ffa200" d="M 716,70.1008 A 10,10 0 0 0 706,60.1008 L 706,70.1008 z"/>
|
||||||
|
<rect style="fill: #ffa200" x="615.488" y="70.1008" width="100.512" height="29.9"/>
|
||||||
|
<path style="fill: #ffa200" d="M 615.488,100.001 A 10,10 0 0 0 625.488,110.001 L 625.488,100.001 z"/>
|
||||||
|
<path style="fill: #ffa200" d="M 706,110.001 A 10,10 0 0 0 716,100.001 L 706,100.001 z"/>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="625.488" y1="60.1008" x2="706" y2="60.1008"/>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="625.488" y1="110.001" x2="706" y2="110.001"/>
|
||||||
|
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 625.488,60.1008 A 10,10 0 0 0 615.488,70.1008"/>
|
||||||
|
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 716,70.1008 A 10,10 0 0 0 706,60.1008"/>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="615.488" y1="70.1008" x2="615.488" y2="100.001"/>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="716" y1="70.1008" x2="716" y2="100.001"/>
|
||||||
|
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 615.488,100.001 A 10,10 0 0 0 625.488,110.001"/>
|
||||||
|
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 706,110.001 A 10,10 0 0 0 716,100.001"/>
|
||||||
|
<text font-size="14.1111" style="fill: #ffffff;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="665.744" y="89.3314">
|
||||||
|
<tspan x="665.744" y="89.3314">Planner</tspan>
|
||||||
|
</text>
|
||||||
</g>
|
</g>
|
||||||
<g>
|
<g>
|
||||||
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="579.3" y1="21.418" x2="632.216" y2="-18.5335"/>
|
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="337" y1="4" x2="387.264" y2="4"/>
|
||||||
<polygon style="fill: #000000" points="638.201,-23.0527 633.233,-13.0367 632.216,-18.5335 627.208,-21.0175 "/>
|
<polygon style="fill: #000000" points="394.764,4 384.764,9 387.264,4 384.764,-1 "/>
|
||||||
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="638.201,-23.0527 633.233,-13.0367 632.216,-18.5335 627.208,-21.0175 "/>
|
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="394.764,4 384.764,9 387.264,4 384.764,-1 "/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="148.386" y1="-60.25" x2="218.65" y2="-60.25"/>
|
||||||
|
<polygon style="fill: #000000" points="226.15,-60.25 216.15,-55.25 218.65,-60.25 216.15,-65.25 "/>
|
||||||
|
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="226.15,-60.25 216.15,-55.25 218.65,-60.25 216.15,-65.25 "/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="581.486" y1="-96.9139" x2="633.15" y2="-96.4987"/>
|
||||||
|
<polygon style="fill: #000000" points="640.65,-96.4384 630.61,-91.519 633.15,-96.4987 630.691,-101.519 "/>
|
||||||
|
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="640.65,-96.4384 630.61,-91.519 633.15,-96.4987 630.691,-101.519 "/>
|
||||||
</g>
|
</g>
|
||||||
<g>
|
<g>
|
||||||
<path style="fill: #ffb400" d="M 844 38.3333 C 865.877,23.0833 876.816,18 898.693,18 C 920.57,18 931.509,23.0833 953.386,38.3333 L 953.386,119.667 C 931.509,134.917 920.57,140 898.693,140 C 876.816,140 865.877,134.917 844,119.667 L 844,38.3333z"/>
|
<path style="fill: #ffb400" d="M 844 38.3333 C 865.877,23.0833 876.816,18 898.693,18 C 920.57,18 931.509,23.0833 953.386,38.3333 L 953.386,119.667 C 931.509,134.917 920.57,140 898.693,140 C 876.816,140 865.877,134.917 844,119.667 L 844,38.3333z"/>
|
||||||
@@ -260,16 +269,34 @@
|
|||||||
</text>
|
</text>
|
||||||
</g>
|
</g>
|
||||||
<g>
|
<g>
|
||||||
<path style="fill: #ff5050" d="M 651.176 172.933 C 680.618,157.683 695.339,152.6 724.78,152.6 C 754.222,152.6 768.943,157.683 798.385,172.933 L 798.385,254.267 C 768.943,269.517 754.222,274.6 724.78,274.6 C 695.339,274.6 680.618,269.517 651.176,254.267 L 651.176,172.933z"/>
|
<path style="fill: #ff5050" d="M 285.054 172.933 C 312.92,157.683 326.854,152.6 354.72,152.6 C 382.586,152.6 396.52,157.683 424.386,172.933 L 424.386,254.267 C 396.52,269.517 382.586,274.6 354.72,274.6 C 326.854,274.6 312.92,269.517 285.054,254.267 L 285.054,172.933z"/>
|
||||||
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 651.176 172.933 C 680.618,157.683 695.339,152.6 724.78,152.6 C 754.222,152.6 768.943,157.683 798.385,172.933 L 798.385,254.267 C 768.943,269.517 754.222,274.6 724.78,274.6 C 695.339,274.6 680.618,269.517 651.176,254.267 L 651.176,172.933"/>
|
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 285.054 172.933 C 312.92,157.683 326.854,152.6 354.72,152.6 C 382.586,152.6 396.52,157.683 424.386,172.933 L 424.386,254.267 C 396.52,269.517 382.586,274.6 354.72,274.6 C 326.854,274.6 312.92,269.517 285.054,254.267 L 285.054,172.933"/>
|
||||||
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 651.176 172.933 C 680.618,188.183 695.339,193.267 724.78,193.267 C 754.222,193.267 768.943,188.183 798.385,172.933"/>
|
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 285.054 172.933 C 312.92,188.183 326.854,193.267 354.72,193.267 C 382.586,193.267 396.52,188.183 424.386,172.933"/>
|
||||||
<text font-size="12.8" style="fill: #ffffff;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="724.78" y="227.767">
|
<text font-size="12.8" style="fill: #ffffff;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="354.72" y="227.767">
|
||||||
<tspan x="724.78" y="227.767">Cluster History DB</tspan>
|
<tspan x="354.72" y="227.767">Cluster Model DB</tspan>
|
||||||
</text>
|
</text>
|
||||||
</g>
|
</g>
|
||||||
<g>
|
<g>
|
||||||
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="96.386,-142.999 96.386,-205.998 899.386,-205.998 899.386,5.26493 "/>
|
<path style="fill: #ff5050" d="M 522.176 185.933 C 551.618,170.683 566.339,165.6 595.78,165.6 C 625.222,165.6 639.943,170.683 669.385,185.933 L 669.385,267.267 C 639.943,282.517 625.222,287.6 595.78,287.6 C 566.339,287.6 551.618,282.517 522.176,267.267 L 522.176,185.933z"/>
|
||||||
<polygon style="fill: #000000" points="899.386,12.7649 894.386,2.76493 899.386,5.26493 904.386,2.76493 "/>
|
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 522.176 185.933 C 551.618,170.683 566.339,165.6 595.78,165.6 C 625.222,165.6 639.943,170.683 669.385,185.933 L 669.385,267.267 C 639.943,282.517 625.222,287.6 595.78,287.6 C 566.339,287.6 551.618,282.517 522.176,267.267 L 522.176,185.933"/>
|
||||||
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="899.386,12.7649 894.386,2.76493 899.386,5.26493 904.386,2.76493 "/>
|
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 522.176 185.933 C 551.618,201.183 566.339,206.267 595.78,206.267 C 625.222,206.267 639.943,201.183 669.385,185.933"/>
|
||||||
|
<text font-size="12.8" style="fill: #ffffff;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="595.78" y="240.767">
|
||||||
|
<tspan x="595.78" y="240.767">Cluster History DB</tspan>
|
||||||
|
</text>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="96.386,-142.999 96.386,-205.998 898.692,-205.998 898.692,8.26393 "/>
|
||||||
|
<polygon style="fill: #000000" points="898.692,15.7639 893.692,5.76393 898.692,8.26393 903.692,5.76393 "/>
|
||||||
|
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="898.692,15.7639 893.692,5.76393 898.692,8.26393 903.692,5.76393 "/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="583,-142.25 583,-141.25 870,-141.25 870,7.01393 "/>
|
||||||
|
<polygon style="fill: #000000" points="870,14.5139 865,4.51393 870,7.01393 875,4.51393 "/>
|
||||||
|
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="870,14.5139 865,4.51393 870,7.01393 875,4.51393 "/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="752.386" y1="60.75" x2="824.64" y2="60.9708"/>
|
||||||
|
<polygon style="fill: #000000" points="832.14,60.9938 822.125,65.9632 824.64,60.9708 822.156,55.9632 "/>
|
||||||
|
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="832.14,60.9938 822.125,65.9632 824.64,60.9708 822.156,55.9632 "/>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 27 KiB |
BIN
doc/source/images/audit_state_machine.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
139
doc/source/images/functional_data_model.svg
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/PR-SVG-20010719/DTD/svg10.dtd">
|
||||||
|
<svg width="58cm" height="28cm" viewBox="26 8 1147 549" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<g>
|
||||||
|
<rect style="fill: #ffffff" x="570" y="99" width="148.6" height="28"/>
|
||||||
|
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="570" y="99" width="148.6" height="28"/>
|
||||||
|
<text font-size="16" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="644.3" y="118">Audit Template</text>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<rect style="fill: #ffffff" x="212" y="140" width="177.5" height="28"/>
|
||||||
|
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="212" y="140" width="177.5" height="28"/>
|
||||||
|
<text font-size="16" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="300.75" y="159">OpenStack Cluster</text>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke-dasharray: 8; stroke: #000000" points="569.006,113 446,113 446,154 397.19,154 "/>
|
||||||
|
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="409.838,149 393.838,154 409.838,159 "/>
|
||||||
|
<text font-size="12.7998" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="448" y="130.5">Applies to</text>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<rect style="fill: #ffffff" x="615" y="227" width="58.4" height="28"/>
|
||||||
|
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="615" y="227" width="58.4" height="28"/>
|
||||||
|
<text font-size="16" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="644.2" y="246">Audit</text>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke-dasharray: 8; stroke: #000000" points="644.2,225.996 644.2,188.497 644.3,188.497 644.3,133.705 "/>
|
||||||
|
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="649.3,146.353 644.3,130.353 639.3,146.353 "/>
|
||||||
|
<text font-size="12.7998" style="fill: #000000;text-anchor:middle;font-family:monospace;font-style:normal;font-weight:normal" x="644.25" y="185.497">gets configuration from</text>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<rect style="fill: #ffffff" x="916" y="9" width="50.45" height="28"/>
|
||||||
|
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="916" y="9" width="50.45" height="28"/>
|
||||||
|
<text font-size="16" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="941.225" y="28">Goal</text>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke-dasharray: 8; stroke: #000000" points="644.3,97.9927 644.3,72 941.225,72 941.225,44.7125 "/>
|
||||||
|
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="946.225,57.3599 941.225,41.3599 936.225,57.3599 "/>
|
||||||
|
<text font-size="12.7998" style="fill: #000000;text-anchor:middle;font-family:monospace;font-style:normal;font-weight:normal" x="792.763" y="69">Achieves</text>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<rect style="fill: #ffffff" x="495" y="367" width="112.45" height="28"/>
|
||||||
|
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="495" y="367" width="112.45" height="28"/>
|
||||||
|
<text font-size="16" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="551.225" y="386">Action Plan</text>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke-dasharray: 8; stroke: #000000" points="644.2,256 644.2,298.5 523,298.5 523,356.295 "/>
|
||||||
|
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="518,343.647 523,359.647 528,343.647 "/>
|
||||||
|
<text font-size="12.7998" style="fill: #000000;text-anchor:middle;font-family:monospace;font-style:normal;font-weight:normal" x="583.6" y="295.5">Generates</text>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<rect style="fill: #ffffff" x="682" y="471" width="67.45" height="28"/>
|
||||||
|
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="682" y="471" width="67.45" height="28"/>
|
||||||
|
<text font-size="16" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="715.725" y="490">Action</text>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="551.225,420.945 551.225,443 715.725,443 715.725,470.029 "/>
|
||||||
|
<polygon style="fill: #000000" points="551.225,395.773 556.025,409.773 551.225,423.773 546.425,409.773 "/>
|
||||||
|
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="551.225,395.773 556.025,409.773 551.225,423.773 546.425,409.773 "/>
|
||||||
|
<text font-size="12.7998" style="fill: #000000;text-anchor:middle;font-family:monospace;font-style:normal;font-weight:normal" x="633.475" y="440">is composed of</text>
|
||||||
|
<text font-size="12.7998" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="562.225" y="407.773"></text>
|
||||||
|
<text font-size="12.7998" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="719.725" y="466.029"></text>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="749.45,499 749.45,517 862,517 862,485 749.45,485 "/>
|
||||||
|
<text font-size="12.7998" style="fill: #000000;text-anchor:middle;font-family:monospace;font-style:normal;font-weight:normal" x="805.725" y="514">Next action</text>
|
||||||
|
<polygon style="fill: #000000" points="850.075,514 850.075,506 858.075,510 "/>
|
||||||
|
<text font-size="12.7998" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="753.45" y="511"></text>
|
||||||
|
<text font-size="12.7998" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="753.45" y="482"></text>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<ellipse style="fill: #ffffff" cx="1036" cy="219" rx="6" ry="6"/>
|
||||||
|
<ellipse style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #ff0000" cx="1036" cy="219" rx="6" ry="6"/>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #ff0000" x1="1012" y1="231" x2="1060" y2="231"/>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #ff0000" x1="1036" y1="225" x2="1036" y2="255"/>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #ff0000" x1="1036" y1="255" x2="1012" y2="281"/>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #ff0000" x1="1036" y1="255" x2="1060" y2="281"/>
|
||||||
|
<text font-size="12.8" style="fill: #ff0000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="1036" y="304.9">
|
||||||
|
<tspan x="1036" y="304.9">Administrator</tspan>
|
||||||
|
</text>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke-dasharray: 8; stroke: #000000" points="985.869,255 834.138,255 834.138,241 681.113,241 "/>
|
||||||
|
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="693.76,236 677.76,241 693.76,246 "/>
|
||||||
|
<text font-size="12.7998" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="836.138" y="245">Triggers</text>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke-dasharray: 8; stroke: #000000" points="1038,192.993 882.3,192.993 882.3,113 725.305,113 "/>
|
||||||
|
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="737.953,108 721.953,113 737.953,118 "/>
|
||||||
|
<text font-size="12.7998" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="884.3" y="149.997">Defines Audit configuration in</text>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<ellipse style="fill: #ffffff" cx="103" cy="28" rx="6" ry="6"/>
|
||||||
|
<ellipse style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #ff0000" cx="103" cy="28" rx="6" ry="6"/>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #ff0000" x1="79" y1="40" x2="127" y2="40"/>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #ff0000" x1="103" y1="34" x2="103" y2="64"/>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #ff0000" x1="103" y1="64" x2="79" y2="90"/>
|
||||||
|
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #ff0000" x1="103" y1="64" x2="127" y2="90"/>
|
||||||
|
<text font-size="12.8" style="fill: #ff0000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="103" y="113.9">
|
||||||
|
<tspan x="103" y="113.9">Customer</tspan>
|
||||||
|
</text>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke-dasharray: 8; stroke: #000000" points="137.683,64 300.75,64 300.75,133.295 "/>
|
||||||
|
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="295.75,120.647 300.75,136.647 305.75,120.647 "/>
|
||||||
|
<text font-size="12.7998" style="fill: #000000;text-anchor:middle;font-family:monospace;font-style:normal;font-weight:normal" x="219.217" y="61">Consumes resources</text>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<rect style="fill: #ffffff" x="27" y="258" width="102.8" height="28"/>
|
||||||
|
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="27" y="258" width="102.8" height="28"/>
|
||||||
|
<text font-size="16" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="78.4" y="277">Resources</text>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="186.828,154 78.4,154 78.4,258 "/>
|
||||||
|
<polygon style="fill: #000000" points="212,154 198,158.8 184,154 198,149.2 "/>
|
||||||
|
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="212,154 198,158.8 184,154 198,149.2 "/>
|
||||||
|
<text font-size="12.7998" style="fill: #000000;text-anchor:middle;font-family:monospace;font-style:normal;font-weight:normal" x="145.2" y="151"></text>
|
||||||
|
<text font-size="12.7998" style="fill: #000000;text-anchor:end;font-family:monospace;font-style:normal;font-weight:normal" x="180" y="151"></text>
|
||||||
|
<text font-size="12.7998" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="82.4" y="254"></text>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke-dasharray: 8; stroke: #000000" points="715.724,499 715.724,540 78.4,540 78.4,292.705 "/>
|
||||||
|
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="83.4,305.353 78.4,289.353 73.4,305.353 "/>
|
||||||
|
<text font-size="12.7998" style="fill: #000000;text-anchor:middle;font-family:monospace;font-style:normal;font-weight:normal" x="397.062" y="537">Modifies</text>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke-dasharray: 8; stroke: #000000" points="1036,309.94 1036,381 614.155,381 "/>
|
||||||
|
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="626.803,376 610.803,381 626.803,386 "/>
|
||||||
|
<text font-size="12.7998" style="fill: #000000;text-anchor:middle;font-family:monospace;font-style:normal;font-weight:normal" x="821.725" y="378">Launches</text>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<rect style="fill: #ffffff" x="1082.9" y="43.1" width="88.25" height="28"/>
|
||||||
|
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="1082.9" y="43.1" width="88.25" height="28"/>
|
||||||
|
<text font-size="16" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="1127.02" y="62.1">Strategy</text>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke-dasharray: 8; stroke: #000000" points="966.45,23 1020.17,23 1020.17,57.1 1075.22,57.1 "/>
|
||||||
|
<polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="1062.57,62.1 1078.57,57.1 1062.57,52.1 "/>
|
||||||
|
<text font-size="12.7998" style="fill: #000000;text-anchor:start;font-family:monospace;font-style:normal;font-weight:normal" x="1022.17" y="37.05">uses</text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 12 KiB |
BIN
doc/source/images/sequence_create_and_launch_audit.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
doc/source/images/sequence_create_audit_template.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
doc/source/images/sequence_launch_action_plan.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
doc/source/images/sequence_launch_action_plan_in_applier.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
doc/source/images/sequence_overview_watcher_usage.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
doc/source/images/sequence_trigger_audit_in_decision_engine.png
Normal file
|
After Width: | Height: | Size: 55 KiB |
@@ -4,20 +4,34 @@
|
|||||||
|
|
||||||
https://creativecommons.org/licenses/by/3.0/
|
https://creativecommons.org/licenses/by/3.0/
|
||||||
|
|
||||||
============================================
|
================================
|
||||||
Welcome to Watcher's developer documentation
|
Welcome to Watcher documentation
|
||||||
============================================
|
================================
|
||||||
|
|
||||||
Mission
|
OpenStack Watcher provides a flexible and scalable resource optimization
|
||||||
=======
|
service for multi-tenant OpenStack-based clouds.
|
||||||
|
Watcher provides a complete optimization loop—including everything from a
|
||||||
|
metrics receiver, complex event processor and profiler, optimization processor
|
||||||
|
and an action plan applier. This provides a robust framework to realize a wide
|
||||||
|
range of cloud optimization goals, including the reduction of data center
|
||||||
|
operating costs, increased system performance via intelligent virtual machine
|
||||||
|
migration, increased energy efficiency—and more!
|
||||||
|
|
||||||
Provide an auditing service for OpenStack to improve workload placement
|
Watcher project consists of several source code repositories:
|
||||||
on-the-go.
|
|
||||||
|
|
||||||
The developer documentation provided here is continually kept up-to-date based
|
* `watcher`_ - is the main repository. It contains code for Watcher API server,
|
||||||
|
Watcher Decision Engine and Watcher Applier.
|
||||||
|
* `python-watcherclient`_ - Client library and CLI client for Watcher.
|
||||||
|
* `watcher-dashboard`_ - Watcher Horizon plugin.
|
||||||
|
|
||||||
|
The documentation provided here is continually kept up-to-date based
|
||||||
on the latest code, and may not represent the state of the project at any
|
on the latest code, and may not represent the state of the project at any
|
||||||
specific prior release.
|
specific prior release.
|
||||||
|
|
||||||
|
.. _watcher: https://git.openstack.org/cgit/openstack/watcher/
|
||||||
|
.. _python-watcherclient: https://git.openstack.org/cgit/openstack/python-watcherclient/
|
||||||
|
.. _watcher-dashboard: https://git.openstack.org/cgit/openstack/watcher-dashboard/
|
||||||
|
|
||||||
Developer Guide
|
Developer Guide
|
||||||
===============
|
===============
|
||||||
|
|
||||||
@@ -29,11 +43,21 @@ Introduction
|
|||||||
|
|
||||||
glossary
|
glossary
|
||||||
architecture
|
architecture
|
||||||
dev/environment
|
|
||||||
dev/contributing
|
dev/contributing
|
||||||
dev/plugins
|
|
||||||
|
|
||||||
|
|
||||||
|
Getting Started
|
||||||
|
---------------
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 1
|
||||||
|
|
||||||
|
dev/environment
|
||||||
|
dev/devstack
|
||||||
|
deploy/configuration
|
||||||
|
deploy/conf-files
|
||||||
|
dev/testing
|
||||||
|
|
||||||
API References
|
API References
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
@@ -42,25 +66,48 @@ API References
|
|||||||
|
|
||||||
webapi/v1
|
webapi/v1
|
||||||
|
|
||||||
|
Plugins
|
||||||
|
-------
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 1
|
||||||
|
|
||||||
|
dev/plugin/base-setup
|
||||||
|
dev/plugin/strategy-plugin
|
||||||
|
dev/plugin/action-plugin
|
||||||
|
dev/plugin/planner-plugin
|
||||||
|
dev/plugins
|
||||||
|
|
||||||
|
|
||||||
Admin Guide
|
Admin Guide
|
||||||
===========
|
===========
|
||||||
|
|
||||||
Overview
|
Introduction
|
||||||
--------
|
------------
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
|
||||||
deploy/user-guide
|
|
||||||
deploy/installation
|
deploy/installation
|
||||||
|
deploy/user-guide
|
||||||
|
|
||||||
Commands
|
Watcher Manual Pages
|
||||||
--------
|
====================
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
|
:glob:
|
||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
|
||||||
cmds/watcher-db-manage
|
man/*
|
||||||
|
|
||||||
|
.. # NOTE(mriedem): This is the section where we hide things that we don't
|
||||||
|
# actually want in the table of contents but sphinx build would fail if
|
||||||
|
# they aren't in the toctree somewhere. For example, we hide api/autoindex
|
||||||
|
# since that's already covered with modindex below.
|
||||||
|
.. toctree::
|
||||||
|
:hidden:
|
||||||
|
|
||||||
|
api/autoindex
|
||||||
|
|
||||||
|
|
||||||
Indices and tables
|
Indices and tables
|
||||||
|
|||||||
5
doc/source/man/footer.rst
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
BUGS
|
||||||
|
====
|
||||||
|
|
||||||
|
* Watcher bugs are tracked in Launchpad at `OpenStack Watcher
|
||||||
|
<http://bugs.launchpad.net/watcher>`__
|
||||||
66
doc/source/man/general-options.rst
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
**-h, --help**
|
||||||
|
Show the help message and exit
|
||||||
|
|
||||||
|
**--version**
|
||||||
|
Print the version number and exit
|
||||||
|
|
||||||
|
**-v, --verbose**
|
||||||
|
Print more verbose output
|
||||||
|
|
||||||
|
**--noverbose**
|
||||||
|
Disable verbose output
|
||||||
|
|
||||||
|
**-d, --debug**
|
||||||
|
Print debugging output (set logging level to DEBUG instead of
|
||||||
|
default WARNING level)
|
||||||
|
|
||||||
|
**--nodebug**
|
||||||
|
Disable debugging output
|
||||||
|
|
||||||
|
**--use-syslog**
|
||||||
|
Use syslog for logging
|
||||||
|
|
||||||
|
**--nouse-syslog**
|
||||||
|
Disable the use of syslog for logging
|
||||||
|
|
||||||
|
**--syslog-log-facility SYSLOG_LOG_FACILITY**
|
||||||
|
syslog facility to receive log lines
|
||||||
|
|
||||||
|
**--config-dir DIR**
|
||||||
|
Path to a config directory to pull \*.conf files from. This
|
||||||
|
file set is sorted, to provide a predictable parse order
|
||||||
|
if individual options are over-ridden. The set is parsed after
|
||||||
|
the file(s) specified via previous --config-file, arguments hence
|
||||||
|
over-ridden options in the directory take precedence. This means
|
||||||
|
that configuration from files in a specified config-dir will
|
||||||
|
always take precedence over configuration from files specified
|
||||||
|
by --config-file, regardless to argument order.
|
||||||
|
|
||||||
|
**--config-file PATH**
|
||||||
|
Path to a config file to use. Multiple config files can be
|
||||||
|
specified by using this flag multiple times, for example,
|
||||||
|
--config-file <file1> --config-file <file2>. Values in latter
|
||||||
|
files take precedence.
|
||||||
|
|
||||||
|
**--log-config-append PATH** **--log-config PATH**
|
||||||
|
The name of logging configuration file. It does not
|
||||||
|
disable existing loggers, but just appends specified
|
||||||
|
logging configuration to any other existing logging
|
||||||
|
options. Please see the Python logging module documentation
|
||||||
|
for details on logging configuration files. The log-config
|
||||||
|
name for this option is depcrecated.
|
||||||
|
|
||||||
|
**--log-format FORMAT**
|
||||||
|
A logging.Formatter log message format string which may use any
|
||||||
|
of the available logging.LogRecord attributes. Default: None
|
||||||
|
|
||||||
|
**--log-date-format DATE_FORMAT**
|
||||||
|
Format string for %(asctime)s in log records. Default: None
|
||||||
|
|
||||||
|
**--log-file PATH, --logfile PATH**
|
||||||
|
(Optional) Name of log file to output to. If not set, logging
|
||||||
|
will go to stdout.
|
||||||
|
|
||||||
|
**--log-dir LOG_DIR, --logdir LOG_DIR**
|
||||||
|
(Optional) The directory to keep log files in (will be prepended
|
||||||
|
to --log-file)
|
||||||
39
doc/source/man/watcher-api.rst
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
===========
|
||||||
|
watcher-api
|
||||||
|
===========
|
||||||
|
|
||||||
|
---------------------------
|
||||||
|
Service for the Watcher API
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
:Author: openstack@lists.launchpad.net
|
||||||
|
:Date:
|
||||||
|
:Copyright: OpenStack Foundation
|
||||||
|
:Version:
|
||||||
|
:Manual section: 1
|
||||||
|
:Manual group: cloud computing
|
||||||
|
|
||||||
|
SYNOPSIS
|
||||||
|
========
|
||||||
|
|
||||||
|
watcher-api [options]
|
||||||
|
|
||||||
|
DESCRIPTION
|
||||||
|
===========
|
||||||
|
|
||||||
|
watcher-api is a server daemon that serves the Watcher API
|
||||||
|
|
||||||
|
OPTIONS
|
||||||
|
=======
|
||||||
|
|
||||||
|
**General options**
|
||||||
|
|
||||||
|
.. include:: general-options.rst
|
||||||
|
|
||||||
|
FILES
|
||||||
|
=====
|
||||||
|
|
||||||
|
**/etc/watcher/watcher.conf**
|
||||||
|
Default configuration file for Watcher API
|
||||||
|
|
||||||
|
.. include:: footer.rst
|
||||||
39
doc/source/man/watcher-applier.rst
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
===============
|
||||||
|
watcher-applier
|
||||||
|
===============
|
||||||
|
|
||||||
|
-------------------------------
|
||||||
|
Service for the Watcher Applier
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
:Author: openstack@lists.launchpad.net
|
||||||
|
:Date:
|
||||||
|
:Copyright: OpenStack Foundation
|
||||||
|
:Version:
|
||||||
|
:Manual section: 1
|
||||||
|
:Manual group: cloud computing
|
||||||
|
|
||||||
|
SYNOPSIS
|
||||||
|
========
|
||||||
|
|
||||||
|
watcher-applier [options]
|
||||||
|
|
||||||
|
DESCRIPTION
|
||||||
|
===========
|
||||||
|
|
||||||
|
:ref:`Watcher Applier <watcher_applier_definition>`
|
||||||
|
|
||||||
|
OPTIONS
|
||||||
|
=======
|
||||||
|
|
||||||
|
**General options**
|
||||||
|
|
||||||
|
.. include:: general-options.rst
|
||||||
|
|
||||||
|
FILES
|
||||||
|
=====
|
||||||
|
|
||||||
|
**/etc/watcher/watcher.conf**
|
||||||
|
Default configuration file for Watcher Applier
|
||||||
|
|
||||||
|
.. include:: footer.rst
|
||||||
@@ -6,9 +6,9 @@
|
|||||||
|
|
||||||
.. _watcher-db-manage:
|
.. _watcher-db-manage:
|
||||||
|
|
||||||
=============
|
=================
|
||||||
watcher-db-manage
|
watcher-db-manage
|
||||||
=============
|
=================
|
||||||
|
|
||||||
The :command:`watcher-db-manage` utility is used to create the database schema
|
The :command:`watcher-db-manage` utility is used to create the database schema
|
||||||
tables that the watcher services will use for storage. It can also be used to
|
tables that the watcher services will use for storage. It can also be used to
|
||||||
@@ -52,7 +52,7 @@ run the following::
|
|||||||
|
|
||||||
Show the program's version number and exit.
|
Show the program's version number and exit.
|
||||||
|
|
||||||
.. option:: upgrade, downgrade, stamp, revision, version, create_schema
|
.. option:: upgrade, downgrade, stamp, revision, version, create_schema, purge
|
||||||
|
|
||||||
The :ref:`command <db-manage_cmds>` to run.
|
The :ref:`command <db-manage_cmds>` to run.
|
||||||
|
|
||||||
@@ -219,3 +219,42 @@ version
|
|||||||
Show help for version and exit.
|
Show help for version and exit.
|
||||||
|
|
||||||
This command will output the current database version.
|
This command will output the current database version.
|
||||||
|
|
||||||
|
purge
|
||||||
|
-----
|
||||||
|
|
||||||
|
.. program:: purge
|
||||||
|
|
||||||
|
.. option:: -h, --help
|
||||||
|
|
||||||
|
Show help for purge and exit.
|
||||||
|
|
||||||
|
.. option:: -d, --age-in-days
|
||||||
|
|
||||||
|
The number of days (starting from today) before which we consider soft
|
||||||
|
deleted objects as expired and should hence be erased. By default, all
|
||||||
|
objects soft deleted are considered expired. This can be useful as removing
|
||||||
|
a significant amount of objects may cause a performance issues.
|
||||||
|
|
||||||
|
.. option:: -n, --max-number
|
||||||
|
|
||||||
|
The maximum number of database objects we expect to be deleted. If exceeded,
|
||||||
|
this will prevent any deletion.
|
||||||
|
|
||||||
|
.. option:: -t, --audit-template
|
||||||
|
|
||||||
|
Either the UUID or name of the soft deleted audit template to purge. This
|
||||||
|
will also include any related objects with it.
|
||||||
|
|
||||||
|
.. option:: -e, --exclude-orphans
|
||||||
|
|
||||||
|
This is a flag to indicate when we want to exclude orphan objects from
|
||||||
|
deletion.
|
||||||
|
|
||||||
|
.. option:: --dry-run
|
||||||
|
|
||||||
|
This is a flag to indicate when we want to perform a dry run. This will show
|
||||||
|
the objects that would be deleted instead of actually deleting them.
|
||||||
|
|
||||||
|
This command will purge the current database by removing both its soft deleted
|
||||||
|
and orphan objects.
|
||||||
39
doc/source/man/watcher-decision-engine.rst
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
=======================
|
||||||
|
watcher-decision-engine
|
||||||
|
=======================
|
||||||
|
|
||||||
|
---------------------------------------
|
||||||
|
Service for the Watcher Decision Engine
|
||||||
|
---------------------------------------
|
||||||
|
|
||||||
|
:Author: openstack@lists.launchpad.net
|
||||||
|
:Date:
|
||||||
|
:Copyright: OpenStack Foundation
|
||||||
|
:Version:
|
||||||
|
:Manual section: 1
|
||||||
|
:Manual group: cloud computing
|
||||||
|
|
||||||
|
SYNOPSIS
|
||||||
|
========
|
||||||
|
|
||||||
|
watcher-decision-engine [options]
|
||||||
|
|
||||||
|
DESCRIPTION
|
||||||
|
===========
|
||||||
|
|
||||||
|
:ref:`Watcher Decision Engine <watcher_decision_engine_definition>`
|
||||||
|
|
||||||
|
OPTIONS
|
||||||
|
=======
|
||||||
|
|
||||||
|
**General options**
|
||||||
|
|
||||||
|
.. include:: general-options.rst
|
||||||
|
|
||||||
|
FILES
|
||||||
|
=====
|
||||||
|
|
||||||
|
**/etc/watcher/watcher.conf**
|
||||||
|
Default configuration file for Watcher Decision Engine
|
||||||
|
|
||||||
|
.. include:: footer.rst
|
||||||
@@ -1 +0,0 @@
|
|||||||
.. include:: ../../README.rst
|
|
||||||
@@ -1,13 +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/
|
|
||||||
|
|
||||||
========
|
|
||||||
Usage
|
|
||||||
========
|
|
||||||
|
|
||||||
To use watcher in a project::
|
|
||||||
|
|
||||||
import watcher
|
|
||||||
@@ -4,15 +4,28 @@
|
|||||||
|
|
||||||
https://creativecommons.org/licenses/by/3.0/
|
https://creativecommons.org/licenses/by/3.0/
|
||||||
|
|
||||||
=====================
|
====================
|
||||||
RESTful Web API (v1)
|
RESTful Web API (v1)
|
||||||
=====================
|
====================
|
||||||
|
|
||||||
|
Goals
|
||||||
|
=====
|
||||||
|
|
||||||
|
.. rest-controller:: watcher.api.controllers.v1.goal:GoalsController
|
||||||
|
:webprefix: /v1/goal
|
||||||
|
|
||||||
|
.. autotype:: watcher.api.controllers.v1.goal.GoalCollection
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. autotype:: watcher.api.controllers.v1.goal.Goal
|
||||||
|
:members:
|
||||||
|
|
||||||
|
|
||||||
Audit Templates
|
Audit Templates
|
||||||
===============
|
===============
|
||||||
|
|
||||||
.. rest-controller:: watcher.api.controllers.v1.audit_template:AuditTemplatesController
|
.. rest-controller:: watcher.api.controllers.v1.audit_template:AuditTemplatesController
|
||||||
:webprefix: /v1/audit_template
|
:webprefix: /v1/audit_templates
|
||||||
|
|
||||||
.. autotype:: watcher.api.controllers.v1.audit_template.AuditTemplateCollection
|
.. autotype:: watcher.api.controllers.v1.audit_template.AuditTemplateCollection
|
||||||
:members:
|
:members:
|
||||||
@@ -20,7 +33,6 @@ Audit Templates
|
|||||||
.. autotype:: watcher.api.controllers.v1.audit_template.AuditTemplate
|
.. autotype:: watcher.api.controllers.v1.audit_template.AuditTemplate
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
|
||||||
Audits
|
Audits
|
||||||
======
|
======
|
||||||
|
|
||||||
@@ -33,24 +45,22 @@ Audits
|
|||||||
.. autotype:: watcher.api.controllers.v1.audit.Audit
|
.. autotype:: watcher.api.controllers.v1.audit.Audit
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
|
||||||
Links
|
Links
|
||||||
=====
|
=====
|
||||||
|
|
||||||
.. autotype:: watcher.api.controllers.link.Link
|
.. autotype:: watcher.api.controllers.link.Link
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
Action Plans
|
||||||
ActionPlans
|
============
|
||||||
===========
|
|
||||||
|
|
||||||
.. rest-controller:: watcher.api.controllers.v1.action_plan:ActionPlansController
|
.. rest-controller:: watcher.api.controllers.v1.action_plan:ActionPlansController
|
||||||
:webprefix: /v1/action_plans
|
:webprefix: /v1/action_plans
|
||||||
|
|
||||||
.. autotype:: watcher.api.controllers.v1.action_plan.ActionPlan
|
.. autotype:: watcher.api.controllers.v1.action_plan.ActionPlanCollection
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
.. autotype:: watcher.api.controllers.v1.action_plan.ActionPlanCollection
|
.. autotype:: watcher.api.controllers.v1.action_plan.ActionPlan
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
4
etc/watcher/README-watcher.conf.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
To generate the sample watcher.conf file, run the following
|
||||||
|
command from the top level of the watcher directory:
|
||||||
|
|
||||||
|
tox -econfig
|
||||||
9
etc/watcher/watcher-config-generator.conf
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[DEFAULT]
|
||||||
|
output_file = etc/watcher/watcher.conf.sample
|
||||||
|
wrap_width = 79
|
||||||
|
|
||||||
|
namespace = watcher
|
||||||
|
namespace = keystonemiddleware.auth_token
|
||||||
|
namespace = oslo.log
|
||||||
|
namespace = oslo.db
|
||||||
|
namespace = oslo.messaging
|
||||||
@@ -1,805 +0,0 @@
|
|||||||
[DEFAULT]
|
|
||||||
|
|
||||||
#
|
|
||||||
# From oslo.log
|
|
||||||
#
|
|
||||||
|
|
||||||
# Print debugging output (set logging level to DEBUG instead of
|
|
||||||
# default INFO level). (boolean value)
|
|
||||||
#debug = false
|
|
||||||
|
|
||||||
# If set to false, will disable INFO logging level, making WARNING the
|
|
||||||
# default. (boolean value)
|
|
||||||
# This option is deprecated for removal.
|
|
||||||
# Its value may be silently ignored in the future.
|
|
||||||
#verbose = true
|
|
||||||
|
|
||||||
# The name of a logging configuration file. This file is appended to
|
|
||||||
# any existing logging configuration files. For details about logging
|
|
||||||
# configuration files, see the Python logging module documentation.
|
|
||||||
# Note that when logging configuration files are used then all logging
|
|
||||||
# configuration is set in the configuration file and other logging
|
|
||||||
# configuration options are ignored (for example, log_format). (string
|
|
||||||
# value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/log_config
|
|
||||||
#log_config_append = <None>
|
|
||||||
|
|
||||||
# DEPRECATED. A logging.Formatter log message format string which may
|
|
||||||
# use any of the available logging.LogRecord attributes. This option
|
|
||||||
# is deprecated. Please use logging_context_format_string and
|
|
||||||
# logging_default_format_string instead. This option is ignored if
|
|
||||||
# log_config_append is set. (string value)
|
|
||||||
#log_format = <None>
|
|
||||||
|
|
||||||
# Format string for %%(asctime)s in log records. Default: %(default)s
|
|
||||||
# . This option is ignored if log_config_append is set. (string value)
|
|
||||||
#log_date_format = %Y-%m-%d %H:%M:%S
|
|
||||||
|
|
||||||
# (Optional) Name of log file to output to. If no default is set,
|
|
||||||
# logging will go to stdout. This option is ignored if
|
|
||||||
# log_config_append is set. (string value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/logfile
|
|
||||||
#log_file = <None>
|
|
||||||
|
|
||||||
# (Optional) The base directory used for relative --log-file paths.
|
|
||||||
# This option is ignored if log_config_append is set. (string value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/logdir
|
|
||||||
#log_dir = <None>
|
|
||||||
|
|
||||||
# (Optional) Uses logging handler designed to watch file system. When
|
|
||||||
# log file is moved or removed this handler will open a new log file
|
|
||||||
# with specified path instantaneously. It makes sense only if log-file
|
|
||||||
# option is specified and Linux platform is used. This option is
|
|
||||||
# ignored if log_config_append is set. (boolean value)
|
|
||||||
#watch_log_file = false
|
|
||||||
|
|
||||||
# Use syslog for logging. Existing syslog format is DEPRECATED and
|
|
||||||
# will be changed later to honor RFC5424. This option is ignored if
|
|
||||||
# log_config_append is set. (boolean value)
|
|
||||||
#use_syslog = false
|
|
||||||
|
|
||||||
# (Optional) Enables or disables syslog rfc5424 format for logging. If
|
|
||||||
# enabled, prefixes the MSG part of the syslog message with APP-NAME
|
|
||||||
# (RFC5424). The format without the APP-NAME is deprecated in Kilo,
|
|
||||||
# and will be removed in Mitaka, along with this option. This option
|
|
||||||
# is ignored if log_config_append is set. (boolean value)
|
|
||||||
# This option is deprecated for removal.
|
|
||||||
# Its value may be silently ignored in the future.
|
|
||||||
#use_syslog_rfc_format = true
|
|
||||||
|
|
||||||
# Syslog facility to receive log lines. This option is ignored if
|
|
||||||
# log_config_append is set. (string value)
|
|
||||||
#syslog_log_facility = LOG_USER
|
|
||||||
|
|
||||||
# Log output to standard error. This option is ignored if
|
|
||||||
# log_config_append is set. (boolean value)
|
|
||||||
#use_stderr = true
|
|
||||||
|
|
||||||
# Format string to use for log messages with context. (string value)
|
|
||||||
#logging_context_format_string = %(asctime)s.%(msecs)03d %(process)d %(levelname)s %(name)s [%(request_id)s %(user_identity)s] %(instance)s%(message)s
|
|
||||||
|
|
||||||
# Format string to use for log messages without context. (string
|
|
||||||
# value)
|
|
||||||
#logging_default_format_string = %(asctime)s.%(msecs)03d %(process)d %(levelname)s %(name)s [-] %(instance)s%(message)s
|
|
||||||
|
|
||||||
# Data to append to log format when level is DEBUG. (string value)
|
|
||||||
#logging_debug_format_suffix = %(funcName)s %(pathname)s:%(lineno)d
|
|
||||||
|
|
||||||
# Prefix each line of exception output with this format. (string
|
|
||||||
# value)
|
|
||||||
#logging_exception_prefix = %(asctime)s.%(msecs)03d %(process)d ERROR %(name)s %(instance)s
|
|
||||||
|
|
||||||
# List of logger=LEVEL pairs. This option is ignored if
|
|
||||||
# log_config_append is set. (list value)
|
|
||||||
#default_log_levels = amqp=WARN,amqplib=WARN,boto=WARN,qpid=WARN,sqlalchemy=WARN,suds=INFO,oslo.messaging=INFO,iso8601=WARN,requests.packages.urllib3.connectionpool=WARN,urllib3.connectionpool=WARN,websocket=WARN,requests.packages.urllib3.util.retry=WARN,urllib3.util.retry=WARN,keystonemiddleware=WARN,routes.middleware=WARN,stevedore=WARN,taskflow=WARN
|
|
||||||
|
|
||||||
# Enables or disables publication of error events. (boolean value)
|
|
||||||
#publish_errors = false
|
|
||||||
|
|
||||||
# The format for an instance that is passed with the log message.
|
|
||||||
# (string value)
|
|
||||||
#instance_format = "[instance: %(uuid)s] "
|
|
||||||
|
|
||||||
# The format for an instance UUID that is passed with the log message.
|
|
||||||
# (string value)
|
|
||||||
#instance_uuid_format = "[instance: %(uuid)s] "
|
|
||||||
|
|
||||||
# Format string for user_identity field of the
|
|
||||||
# logging_context_format_string (string value)
|
|
||||||
#logging_user_identity_format = %(user)s %(tenant)s %(domain)s %(user_domain)s %(project_domain)s
|
|
||||||
|
|
||||||
# Enables or disables fatal status of deprecations. (boolean value)
|
|
||||||
#fatal_deprecations = false
|
|
||||||
|
|
||||||
#
|
|
||||||
# From oslo.messaging
|
|
||||||
#
|
|
||||||
|
|
||||||
# Size of RPC connection pool. (integer value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/rpc_conn_pool_size
|
|
||||||
#rpc_conn_pool_size = 30
|
|
||||||
|
|
||||||
# ZeroMQ bind address. Should be a wildcard (*), an ethernet
|
|
||||||
# interface, or IP. The "host" option should point or resolve to this
|
|
||||||
# address. (string value)
|
|
||||||
#rpc_zmq_bind_address = *
|
|
||||||
|
|
||||||
# MatchMaker driver. (string value)
|
|
||||||
#rpc_zmq_matchmaker = redis
|
|
||||||
|
|
||||||
# Use REQ/REP pattern for all methods CALL/CAST/FANOUT. (boolean
|
|
||||||
# value)
|
|
||||||
#rpc_zmq_all_req_rep = true
|
|
||||||
|
|
||||||
# Type of concurrency used. Either "native" or "eventlet" (string
|
|
||||||
# value)
|
|
||||||
#rpc_zmq_concurrency = eventlet
|
|
||||||
|
|
||||||
# Number of ZeroMQ contexts, defaults to 1. (integer value)
|
|
||||||
#rpc_zmq_contexts = 1
|
|
||||||
|
|
||||||
# Maximum number of ingress messages to locally buffer per topic.
|
|
||||||
# Default is unlimited. (integer value)
|
|
||||||
#rpc_zmq_topic_backlog = <None>
|
|
||||||
|
|
||||||
# Directory for holding IPC sockets. (string value)
|
|
||||||
#rpc_zmq_ipc_dir = /var/run/openstack
|
|
||||||
|
|
||||||
# Name of this node. Must be a valid hostname, FQDN, or IP address.
|
|
||||||
# Must match "host" option, if running Nova. (string value)
|
|
||||||
#rpc_zmq_host = localhost
|
|
||||||
|
|
||||||
# Seconds to wait before a cast expires (TTL). Only supported by
|
|
||||||
# impl_zmq. (integer value)
|
|
||||||
#rpc_cast_timeout = 30
|
|
||||||
|
|
||||||
# The default number of seconds that poll should wait. Poll raises
|
|
||||||
# timeout exception when timeout expired. (integer value)
|
|
||||||
#rpc_poll_timeout = 1
|
|
||||||
|
|
||||||
# Configures zmq-messaging to use broker or not. (boolean value)
|
|
||||||
#zmq_use_broker = false
|
|
||||||
|
|
||||||
# Minimal port number for random ports range. (port value)
|
|
||||||
# Minimum value: 1
|
|
||||||
# Maximum value: 65535
|
|
||||||
#rpc_zmq_min_port = 49152
|
|
||||||
|
|
||||||
# Maximal port number for random ports range. (integer value)
|
|
||||||
# Minimum value: 1
|
|
||||||
# Maximum value: 65536
|
|
||||||
#rpc_zmq_max_port = 65536
|
|
||||||
|
|
||||||
# Number of retries to find free port number before fail with
|
|
||||||
# ZMQBindError. (integer value)
|
|
||||||
#rpc_zmq_bind_port_retries = 100
|
|
||||||
|
|
||||||
# Host to locate redis. (string value)
|
|
||||||
#host = 127.0.0.1
|
|
||||||
|
|
||||||
# Use this port to connect to redis host. (port value)
|
|
||||||
# Minimum value: 1
|
|
||||||
# Maximum value: 65535
|
|
||||||
#port = 6379
|
|
||||||
|
|
||||||
# Password for Redis server (optional). (string value)
|
|
||||||
#password =
|
|
||||||
|
|
||||||
# Size of executor thread pool. (integer value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/rpc_thread_pool_size
|
|
||||||
#executor_thread_pool_size = 64
|
|
||||||
|
|
||||||
# The Drivers(s) to handle sending notifications. Possible values are
|
|
||||||
# messaging, messagingv2, routing, log, test, noop (multi valued)
|
|
||||||
# Deprecated group/name - [DEFAULT]/notification_driver
|
|
||||||
#driver =
|
|
||||||
|
|
||||||
# A URL representing the messaging driver to use for notifications. If
|
|
||||||
# not set, we fall back to the same configuration used for RPC.
|
|
||||||
# (string value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/notification_transport_url
|
|
||||||
#transport_url = <None>
|
|
||||||
|
|
||||||
# AMQP topic used for OpenStack notifications. (list value)
|
|
||||||
# Deprecated group/name - [rpc_notifier2]/topics
|
|
||||||
# Deprecated group/name - [DEFAULT]/notification_topics
|
|
||||||
#topics = notifications
|
|
||||||
|
|
||||||
# Seconds to wait for a response from a call. (integer value)
|
|
||||||
#rpc_response_timeout = 60
|
|
||||||
|
|
||||||
# A URL representing the messaging driver to use and its full
|
|
||||||
# configuration. If not set, we fall back to the rpc_backend option
|
|
||||||
# and driver specific configuration. (string value)
|
|
||||||
#transport_url = <None>
|
|
||||||
|
|
||||||
# The messaging driver to use, defaults to rabbit. Other drivers
|
|
||||||
# include amqp and zmq. (string value)
|
|
||||||
#rpc_backend = rabbit
|
|
||||||
|
|
||||||
# The default exchange under which topics are scoped. May be
|
|
||||||
# overridden by an exchange name specified in the transport_url
|
|
||||||
# option. (string value)
|
|
||||||
#control_exchange = openstack
|
|
||||||
|
|
||||||
|
|
||||||
[api]
|
|
||||||
|
|
||||||
#
|
|
||||||
# From watcher
|
|
||||||
#
|
|
||||||
|
|
||||||
# The port for the watcher API server (integer value)
|
|
||||||
#port = 9322
|
|
||||||
|
|
||||||
# The listen IP for the watcher API server (string value)
|
|
||||||
#host = 0.0.0.0
|
|
||||||
|
|
||||||
# The maximum number of items returned in a single response from a
|
|
||||||
# collection resource. (integer value)
|
|
||||||
#max_limit = 1000
|
|
||||||
|
|
||||||
|
|
||||||
[database]
|
|
||||||
|
|
||||||
#
|
|
||||||
# From oslo.db
|
|
||||||
#
|
|
||||||
|
|
||||||
# The file name to use with SQLite. (string value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/sqlite_db
|
|
||||||
#sqlite_db = oslo.sqlite
|
|
||||||
|
|
||||||
# If True, SQLite uses synchronous mode. (boolean value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/sqlite_synchronous
|
|
||||||
#sqlite_synchronous = true
|
|
||||||
|
|
||||||
# The back end to use for the database. (string value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/db_backend
|
|
||||||
#backend = sqlalchemy
|
|
||||||
|
|
||||||
# The SQLAlchemy connection string to use to connect to the database.
|
|
||||||
# (string value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/sql_connection
|
|
||||||
# Deprecated group/name - [DATABASE]/sql_connection
|
|
||||||
# Deprecated group/name - [sql]/connection
|
|
||||||
#connection = <None>
|
|
||||||
|
|
||||||
# The SQLAlchemy connection string to use to connect to the slave
|
|
||||||
# database. (string value)
|
|
||||||
#slave_connection = <None>
|
|
||||||
|
|
||||||
# The SQL mode to be used for MySQL sessions. This option, including
|
|
||||||
# the default, overrides any server-set SQL mode. To use whatever SQL
|
|
||||||
# mode is set by the server configuration, set this to no value.
|
|
||||||
# Example: mysql_sql_mode= (string value)
|
|
||||||
#mysql_sql_mode = TRADITIONAL
|
|
||||||
|
|
||||||
# Timeout before idle SQL connections are reaped. (integer value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/sql_idle_timeout
|
|
||||||
# Deprecated group/name - [DATABASE]/sql_idle_timeout
|
|
||||||
# Deprecated group/name - [sql]/idle_timeout
|
|
||||||
#idle_timeout = 3600
|
|
||||||
|
|
||||||
# Minimum number of SQL connections to keep open in a pool. (integer
|
|
||||||
# value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/sql_min_pool_size
|
|
||||||
# Deprecated group/name - [DATABASE]/sql_min_pool_size
|
|
||||||
#min_pool_size = 1
|
|
||||||
|
|
||||||
# Maximum number of SQL connections to keep open in a pool. (integer
|
|
||||||
# value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/sql_max_pool_size
|
|
||||||
# Deprecated group/name - [DATABASE]/sql_max_pool_size
|
|
||||||
#max_pool_size = <None>
|
|
||||||
|
|
||||||
# Maximum number of database connection retries during startup. Set to
|
|
||||||
# -1 to specify an infinite retry count. (integer value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/sql_max_retries
|
|
||||||
# Deprecated group/name - [DATABASE]/sql_max_retries
|
|
||||||
#max_retries = 10
|
|
||||||
|
|
||||||
# Interval between retries of opening a SQL connection. (integer
|
|
||||||
# value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/sql_retry_interval
|
|
||||||
# Deprecated group/name - [DATABASE]/reconnect_interval
|
|
||||||
#retry_interval = 10
|
|
||||||
|
|
||||||
# If set, use this value for max_overflow with SQLAlchemy. (integer
|
|
||||||
# value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/sql_max_overflow
|
|
||||||
# Deprecated group/name - [DATABASE]/sqlalchemy_max_overflow
|
|
||||||
#max_overflow = <None>
|
|
||||||
|
|
||||||
# Verbosity of SQL debugging information: 0=None, 100=Everything.
|
|
||||||
# (integer value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/sql_connection_debug
|
|
||||||
#connection_debug = 0
|
|
||||||
|
|
||||||
# Add Python stack traces to SQL as comment strings. (boolean value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/sql_connection_trace
|
|
||||||
#connection_trace = false
|
|
||||||
|
|
||||||
# If set, use this value for pool_timeout with SQLAlchemy. (integer
|
|
||||||
# value)
|
|
||||||
# Deprecated group/name - [DATABASE]/sqlalchemy_pool_timeout
|
|
||||||
#pool_timeout = <None>
|
|
||||||
|
|
||||||
# Enable the experimental use of database reconnect on connection
|
|
||||||
# lost. (boolean value)
|
|
||||||
#use_db_reconnect = false
|
|
||||||
|
|
||||||
# Seconds between retries of a database transaction. (integer value)
|
|
||||||
#db_retry_interval = 1
|
|
||||||
|
|
||||||
# If True, increases the interval between retries of a database
|
|
||||||
# operation up to db_max_retry_interval. (boolean value)
|
|
||||||
#db_inc_retry_interval = true
|
|
||||||
|
|
||||||
# If db_inc_retry_interval is set, the maximum seconds between retries
|
|
||||||
# of a database operation. (integer value)
|
|
||||||
#db_max_retry_interval = 10
|
|
||||||
|
|
||||||
# Maximum retries in case of connection error or deadlock error before
|
|
||||||
# error is raised. Set to -1 to specify an infinite retry count.
|
|
||||||
# (integer value)
|
|
||||||
#db_max_retries = 20
|
|
||||||
|
|
||||||
|
|
||||||
[keystone_authtoken]
|
|
||||||
|
|
||||||
#
|
|
||||||
# From keystonemiddleware.auth_token
|
|
||||||
#
|
|
||||||
|
|
||||||
# Complete public Identity API endpoint. (string value)
|
|
||||||
#auth_uri = <None>
|
|
||||||
|
|
||||||
# API version of the admin Identity API endpoint. (string value)
|
|
||||||
#auth_version = <None>
|
|
||||||
|
|
||||||
# Do not handle authorization requests within the middleware, but
|
|
||||||
# delegate the authorization decision to downstream WSGI components.
|
|
||||||
# (boolean value)
|
|
||||||
#delay_auth_decision = false
|
|
||||||
|
|
||||||
# Request timeout value for communicating with Identity API server.
|
|
||||||
# (integer value)
|
|
||||||
#http_connect_timeout = <None>
|
|
||||||
|
|
||||||
# How many times are we trying to reconnect when communicating with
|
|
||||||
# Identity API Server. (integer value)
|
|
||||||
#http_request_max_retries = 3
|
|
||||||
|
|
||||||
# Env key for the swift cache. (string value)
|
|
||||||
#cache = <None>
|
|
||||||
|
|
||||||
# Required if identity server requires client certificate (string
|
|
||||||
# value)
|
|
||||||
#certfile = <None>
|
|
||||||
|
|
||||||
# Required if identity server requires client certificate (string
|
|
||||||
# value)
|
|
||||||
#keyfile = <None>
|
|
||||||
|
|
||||||
# A PEM encoded Certificate Authority to use when verifying HTTPs
|
|
||||||
# connections. Defaults to system CAs. (string value)
|
|
||||||
#cafile = <None>
|
|
||||||
|
|
||||||
# Verify HTTPS connections. (boolean value)
|
|
||||||
#insecure = false
|
|
||||||
|
|
||||||
# The region in which the identity server can be found. (string value)
|
|
||||||
#region_name = <None>
|
|
||||||
|
|
||||||
# Directory used to cache files related to PKI tokens. (string value)
|
|
||||||
#signing_dir = <None>
|
|
||||||
|
|
||||||
# Optionally specify a list of memcached server(s) to use for caching.
|
|
||||||
# If left undefined, tokens will instead be cached in-process. (list
|
|
||||||
# value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/memcache_servers
|
|
||||||
#memcached_servers = <None>
|
|
||||||
|
|
||||||
# In order to prevent excessive effort spent validating tokens, the
|
|
||||||
# middleware caches previously-seen tokens for a configurable duration
|
|
||||||
# (in seconds). Set to -1 to disable caching completely. (integer
|
|
||||||
# value)
|
|
||||||
#token_cache_time = 300
|
|
||||||
|
|
||||||
# Determines the frequency at which the list of revoked tokens is
|
|
||||||
# retrieved from the Identity service (in seconds). A high number of
|
|
||||||
# revocation events combined with a low cache duration may
|
|
||||||
# significantly reduce performance. (integer value)
|
|
||||||
#revocation_cache_time = 10
|
|
||||||
|
|
||||||
# (Optional) If defined, indicate whether token data should be
|
|
||||||
# authenticated or authenticated and encrypted. Acceptable values are
|
|
||||||
# MAC or ENCRYPT. If MAC, token data is authenticated (with HMAC) in
|
|
||||||
# the cache. If ENCRYPT, token data is encrypted and authenticated in
|
|
||||||
# the cache. If the value is not one of these options or empty,
|
|
||||||
# auth_token will raise an exception on initialization. (string value)
|
|
||||||
#memcache_security_strategy = <None>
|
|
||||||
|
|
||||||
# (Optional, mandatory if memcache_security_strategy is defined) This
|
|
||||||
# string is used for key derivation. (string value)
|
|
||||||
#memcache_secret_key = <None>
|
|
||||||
|
|
||||||
# (Optional) Number of seconds memcached server is considered dead
|
|
||||||
# before it is tried again. (integer value)
|
|
||||||
#memcache_pool_dead_retry = 300
|
|
||||||
|
|
||||||
# (Optional) Maximum total number of open connections to every
|
|
||||||
# memcached server. (integer value)
|
|
||||||
#memcache_pool_maxsize = 10
|
|
||||||
|
|
||||||
# (Optional) Socket timeout in seconds for communicating with a
|
|
||||||
# memcached server. (integer value)
|
|
||||||
#memcache_pool_socket_timeout = 3
|
|
||||||
|
|
||||||
# (Optional) Number of seconds a connection to memcached is held
|
|
||||||
# unused in the pool before it is closed. (integer value)
|
|
||||||
#memcache_pool_unused_timeout = 60
|
|
||||||
|
|
||||||
# (Optional) Number of seconds that an operation will wait to get a
|
|
||||||
# memcached client connection from the pool. (integer value)
|
|
||||||
#memcache_pool_conn_get_timeout = 10
|
|
||||||
|
|
||||||
# (Optional) Use the advanced (eventlet safe) memcached client pool.
|
|
||||||
# The advanced pool will only work under python 2.x. (boolean value)
|
|
||||||
#memcache_use_advanced_pool = false
|
|
||||||
|
|
||||||
# (Optional) Indicate whether to set the X-Service-Catalog header. If
|
|
||||||
# False, middleware will not ask for service catalog on token
|
|
||||||
# validation and will not set the X-Service-Catalog header. (boolean
|
|
||||||
# value)
|
|
||||||
#include_service_catalog = true
|
|
||||||
|
|
||||||
# Used to control the use and type of token binding. Can be set to:
|
|
||||||
# "disabled" to not check token binding. "permissive" (default) to
|
|
||||||
# validate binding information if the bind type is of a form known to
|
|
||||||
# the server and ignore it if not. "strict" like "permissive" but if
|
|
||||||
# the bind type is unknown the token will be rejected. "required" any
|
|
||||||
# form of token binding is needed to be allowed. Finally the name of a
|
|
||||||
# binding method that must be present in tokens. (string value)
|
|
||||||
#enforce_token_bind = permissive
|
|
||||||
|
|
||||||
# If true, the revocation list will be checked for cached tokens. This
|
|
||||||
# requires that PKI tokens are configured on the identity server.
|
|
||||||
# (boolean value)
|
|
||||||
#check_revocations_for_cached = false
|
|
||||||
|
|
||||||
# Hash algorithms to use for hashing PKI tokens. This may be a single
|
|
||||||
# algorithm or multiple. The algorithms are those supported by Python
|
|
||||||
# standard hashlib.new(). The hashes will be tried in the order given,
|
|
||||||
# so put the preferred one first for performance. The result of the
|
|
||||||
# first hash will be stored in the cache. This will typically be set
|
|
||||||
# to multiple values only while migrating from a less secure algorithm
|
|
||||||
# to a more secure one. Once all the old tokens are expired this
|
|
||||||
# option should be set to a single value for better performance. (list
|
|
||||||
# value)
|
|
||||||
#hash_algorithms = md5
|
|
||||||
|
|
||||||
# Prefix to prepend at the beginning of the path. Deprecated, use
|
|
||||||
# identity_uri. (string value)
|
|
||||||
#auth_admin_prefix =
|
|
||||||
|
|
||||||
# Host providing the admin Identity API endpoint. Deprecated, use
|
|
||||||
# identity_uri. (string value)
|
|
||||||
#auth_host = 127.0.0.1
|
|
||||||
|
|
||||||
# Port of the admin Identity API endpoint. Deprecated, use
|
|
||||||
# identity_uri. (integer value)
|
|
||||||
#auth_port = 35357
|
|
||||||
|
|
||||||
# Protocol of the admin Identity API endpoint (http or https).
|
|
||||||
# Deprecated, use identity_uri. (string value)
|
|
||||||
#auth_protocol = https
|
|
||||||
|
|
||||||
# Complete admin Identity API endpoint. This should specify the
|
|
||||||
# unversioned root endpoint e.g. https://localhost:35357/ (string
|
|
||||||
# value)
|
|
||||||
#identity_uri = <None>
|
|
||||||
|
|
||||||
# This option is deprecated and may be removed in a future release.
|
|
||||||
# Single shared secret with the Keystone configuration used for
|
|
||||||
# bootstrapping a Keystone installation, or otherwise bypassing the
|
|
||||||
# normal authentication process. This option should not be used, use
|
|
||||||
# `admin_user` and `admin_password` instead. (string value)
|
|
||||||
#admin_token = <None>
|
|
||||||
|
|
||||||
# Service username. (string value)
|
|
||||||
#admin_user = <None>
|
|
||||||
|
|
||||||
# Service user password. (string value)
|
|
||||||
#admin_password = <None>
|
|
||||||
|
|
||||||
# Service tenant name. (string value)
|
|
||||||
#admin_tenant_name = admin
|
|
||||||
|
|
||||||
# Authentication type to load (unknown value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/auth_plugin
|
|
||||||
#auth_type = <None>
|
|
||||||
|
|
||||||
# Config Section from which to load plugin specific options (unknown
|
|
||||||
# value)
|
|
||||||
#auth_section = <None>
|
|
||||||
|
|
||||||
|
|
||||||
[matchmaker_redis]
|
|
||||||
|
|
||||||
#
|
|
||||||
# From oslo.messaging
|
|
||||||
#
|
|
||||||
|
|
||||||
# Host to locate redis. (string value)
|
|
||||||
#host = 127.0.0.1
|
|
||||||
|
|
||||||
# Use this port to connect to redis host. (port value)
|
|
||||||
# Minimum value: 1
|
|
||||||
# Maximum value: 65535
|
|
||||||
#port = 6379
|
|
||||||
|
|
||||||
# Password for Redis server (optional). (string value)
|
|
||||||
#password =
|
|
||||||
|
|
||||||
|
|
||||||
[oslo_messaging_amqp]
|
|
||||||
|
|
||||||
#
|
|
||||||
# From oslo.messaging
|
|
||||||
#
|
|
||||||
|
|
||||||
# address prefix used when sending to a specific server (string value)
|
|
||||||
# Deprecated group/name - [amqp1]/server_request_prefix
|
|
||||||
#server_request_prefix = exclusive
|
|
||||||
|
|
||||||
# address prefix used when broadcasting to all servers (string value)
|
|
||||||
# Deprecated group/name - [amqp1]/broadcast_prefix
|
|
||||||
#broadcast_prefix = broadcast
|
|
||||||
|
|
||||||
# address prefix when sending to any server in group (string value)
|
|
||||||
# Deprecated group/name - [amqp1]/group_request_prefix
|
|
||||||
#group_request_prefix = unicast
|
|
||||||
|
|
||||||
# Name for the AMQP container (string value)
|
|
||||||
# Deprecated group/name - [amqp1]/container_name
|
|
||||||
#container_name = <None>
|
|
||||||
|
|
||||||
# Timeout for inactive connections (in seconds) (integer value)
|
|
||||||
# Deprecated group/name - [amqp1]/idle_timeout
|
|
||||||
#idle_timeout = 0
|
|
||||||
|
|
||||||
# Debug: dump AMQP frames to stdout (boolean value)
|
|
||||||
# Deprecated group/name - [amqp1]/trace
|
|
||||||
#trace = false
|
|
||||||
|
|
||||||
# CA certificate PEM file to verify server certificate (string value)
|
|
||||||
# Deprecated group/name - [amqp1]/ssl_ca_file
|
|
||||||
#ssl_ca_file =
|
|
||||||
|
|
||||||
# Identifying certificate PEM file to present to clients (string
|
|
||||||
# value)
|
|
||||||
# Deprecated group/name - [amqp1]/ssl_cert_file
|
|
||||||
#ssl_cert_file =
|
|
||||||
|
|
||||||
# Private key PEM file used to sign cert_file certificate (string
|
|
||||||
# value)
|
|
||||||
# Deprecated group/name - [amqp1]/ssl_key_file
|
|
||||||
#ssl_key_file =
|
|
||||||
|
|
||||||
# Password for decrypting ssl_key_file (if encrypted) (string value)
|
|
||||||
# Deprecated group/name - [amqp1]/ssl_key_password
|
|
||||||
#ssl_key_password = <None>
|
|
||||||
|
|
||||||
# Accept clients using either SSL or plain TCP (boolean value)
|
|
||||||
# Deprecated group/name - [amqp1]/allow_insecure_clients
|
|
||||||
#allow_insecure_clients = false
|
|
||||||
|
|
||||||
# Space separated list of acceptable SASL mechanisms (string value)
|
|
||||||
# Deprecated group/name - [amqp1]/sasl_mechanisms
|
|
||||||
#sasl_mechanisms =
|
|
||||||
|
|
||||||
# Path to directory that contains the SASL configuration (string
|
|
||||||
# value)
|
|
||||||
# Deprecated group/name - [amqp1]/sasl_config_dir
|
|
||||||
#sasl_config_dir =
|
|
||||||
|
|
||||||
# Name of configuration file (without .conf suffix) (string value)
|
|
||||||
# Deprecated group/name - [amqp1]/sasl_config_name
|
|
||||||
#sasl_config_name =
|
|
||||||
|
|
||||||
# User name for message broker authentication (string value)
|
|
||||||
# Deprecated group/name - [amqp1]/username
|
|
||||||
#username =
|
|
||||||
|
|
||||||
# Password for message broker authentication (string value)
|
|
||||||
# Deprecated group/name - [amqp1]/password
|
|
||||||
#password =
|
|
||||||
|
|
||||||
|
|
||||||
[oslo_messaging_rabbit]
|
|
||||||
|
|
||||||
#
|
|
||||||
# From oslo.messaging
|
|
||||||
#
|
|
||||||
|
|
||||||
# Use durable queues in AMQP. (boolean value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/amqp_durable_queues
|
|
||||||
# Deprecated group/name - [DEFAULT]/rabbit_durable_queues
|
|
||||||
#amqp_durable_queues = false
|
|
||||||
|
|
||||||
# Auto-delete queues in AMQP. (boolean value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/amqp_auto_delete
|
|
||||||
#amqp_auto_delete = false
|
|
||||||
|
|
||||||
# Send a single AMQP reply to call message. The current behaviour
|
|
||||||
# since oslo-incubator is to send two AMQP replies - first one with
|
|
||||||
# the payload, a second one to ensure the other have finish to send
|
|
||||||
# the payload. We are going to remove it in the N release, but we must
|
|
||||||
# keep backward compatible at the same time. This option provides such
|
|
||||||
# compatibility - it defaults to False in Liberty and can be turned on
|
|
||||||
# for early adopters with a new installations or for testing. Please
|
|
||||||
# note, that this option will be removed in the Mitaka release.
|
|
||||||
# (boolean value)
|
|
||||||
#send_single_reply = false
|
|
||||||
|
|
||||||
# SSL version to use (valid only if SSL enabled). Valid values are
|
|
||||||
# TLSv1 and SSLv23. SSLv2, SSLv3, TLSv1_1, and TLSv1_2 may be
|
|
||||||
# available on some distributions. (string value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/kombu_ssl_version
|
|
||||||
#kombu_ssl_version =
|
|
||||||
|
|
||||||
# SSL key file (valid only if SSL enabled). (string value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/kombu_ssl_keyfile
|
|
||||||
#kombu_ssl_keyfile =
|
|
||||||
|
|
||||||
# SSL cert file (valid only if SSL enabled). (string value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/kombu_ssl_certfile
|
|
||||||
#kombu_ssl_certfile =
|
|
||||||
|
|
||||||
# SSL certification authority file (valid only if SSL enabled).
|
|
||||||
# (string value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/kombu_ssl_ca_certs
|
|
||||||
#kombu_ssl_ca_certs =
|
|
||||||
|
|
||||||
# How long to wait before reconnecting in response to an AMQP consumer
|
|
||||||
# cancel notification. (floating point value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/kombu_reconnect_delay
|
|
||||||
#kombu_reconnect_delay = 1.0
|
|
||||||
|
|
||||||
# How long to wait a missing client beforce abandoning to send it its
|
|
||||||
# replies. This value should not be longer than rpc_response_timeout.
|
|
||||||
# (integer value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/kombu_reconnect_timeout
|
|
||||||
#kombu_missing_consumer_retry_timeout = 5
|
|
||||||
|
|
||||||
# Determines how the next RabbitMQ node is chosen in case the one we
|
|
||||||
# are currently connected to becomes unavailable. Takes effect only if
|
|
||||||
# more than one RabbitMQ node is provided in config. (string value)
|
|
||||||
# Allowed values: round-robin, shuffle
|
|
||||||
#kombu_failover_strategy = round-robin
|
|
||||||
|
|
||||||
# The RabbitMQ broker address where a single node is used. (string
|
|
||||||
# value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/rabbit_host
|
|
||||||
#rabbit_host = localhost
|
|
||||||
|
|
||||||
# The RabbitMQ broker port where a single node is used. (port value)
|
|
||||||
# Minimum value: 1
|
|
||||||
# Maximum value: 65535
|
|
||||||
# Deprecated group/name - [DEFAULT]/rabbit_port
|
|
||||||
#rabbit_port = 5672
|
|
||||||
|
|
||||||
# RabbitMQ HA cluster host:port pairs. (list value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/rabbit_hosts
|
|
||||||
#rabbit_hosts = $rabbit_host:$rabbit_port
|
|
||||||
|
|
||||||
# Connect over SSL for RabbitMQ. (boolean value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/rabbit_use_ssl
|
|
||||||
#rabbit_use_ssl = false
|
|
||||||
|
|
||||||
# The RabbitMQ userid. (string value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/rabbit_userid
|
|
||||||
#rabbit_userid = guest
|
|
||||||
|
|
||||||
# The RabbitMQ password. (string value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/rabbit_password
|
|
||||||
#rabbit_password = guest
|
|
||||||
|
|
||||||
# The RabbitMQ login method. (string value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/rabbit_login_method
|
|
||||||
#rabbit_login_method = AMQPLAIN
|
|
||||||
|
|
||||||
# The RabbitMQ virtual host. (string value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/rabbit_virtual_host
|
|
||||||
#rabbit_virtual_host = /
|
|
||||||
|
|
||||||
# How frequently to retry connecting with RabbitMQ. (integer value)
|
|
||||||
#rabbit_retry_interval = 1
|
|
||||||
|
|
||||||
# How long to backoff for between retries when connecting to RabbitMQ.
|
|
||||||
# (integer value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/rabbit_retry_backoff
|
|
||||||
#rabbit_retry_backoff = 2
|
|
||||||
|
|
||||||
# Maximum number of RabbitMQ connection retries. Default is 0
|
|
||||||
# (infinite retry count). (integer value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/rabbit_max_retries
|
|
||||||
#rabbit_max_retries = 0
|
|
||||||
|
|
||||||
# Use HA queues in RabbitMQ (x-ha-policy: all). If you change this
|
|
||||||
# option, you must wipe the RabbitMQ database. (boolean value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/rabbit_ha_queues
|
|
||||||
#rabbit_ha_queues = false
|
|
||||||
|
|
||||||
# Number of seconds after which the Rabbit broker is considered down
|
|
||||||
# if heartbeat's keep-alive fails (0 disable the heartbeat).
|
|
||||||
# EXPERIMENTAL (integer value)
|
|
||||||
#heartbeat_timeout_threshold = 60
|
|
||||||
|
|
||||||
# How often times during the heartbeat_timeout_threshold we check the
|
|
||||||
# heartbeat. (integer value)
|
|
||||||
#heartbeat_rate = 2
|
|
||||||
|
|
||||||
# Deprecated, use rpc_backend=kombu+memory or rpc_backend=fake
|
|
||||||
# (boolean value)
|
|
||||||
# Deprecated group/name - [DEFAULT]/fake_rabbit
|
|
||||||
#fake_rabbit = false
|
|
||||||
|
|
||||||
|
|
||||||
[watcher_applier]
|
|
||||||
|
|
||||||
#
|
|
||||||
# From watcher
|
|
||||||
#
|
|
||||||
|
|
||||||
# The number of worker (integer value)
|
|
||||||
#applier_worker = 1
|
|
||||||
|
|
||||||
# The topic name used forcontrol events, this topic used for rpc call
|
|
||||||
# (string value)
|
|
||||||
#topic_control = watcher.applier.control
|
|
||||||
|
|
||||||
# The topic name used for status events, this topic is used so as to
|
|
||||||
# notifythe others components of the system (string value)
|
|
||||||
#topic_status = watcher.applier.status
|
|
||||||
|
|
||||||
# The identifier used by watcher module on the message broker (string
|
|
||||||
# value)
|
|
||||||
#publisher_id = watcher.applier.api
|
|
||||||
|
|
||||||
|
|
||||||
[watcher_decision_engine]
|
|
||||||
|
|
||||||
#
|
|
||||||
# From watcher
|
|
||||||
#
|
|
||||||
|
|
||||||
# The topic name used forcontrol events, this topic used for rpc call
|
|
||||||
# (string value)
|
|
||||||
#topic_control = watcher.decision.control
|
|
||||||
|
|
||||||
# The topic name used for status events, this topic is used so as to
|
|
||||||
# notifythe others components of the system (string value)
|
|
||||||
#topic_status = watcher.decision.status
|
|
||||||
|
|
||||||
# The identifier used by watcher module on the message broker (string
|
|
||||||
# value)
|
|
||||||
#publisher_id = watcher.decision.api
|
|
||||||
|
|
||||||
# The maximum number of threads that can be used to execute strategies
|
|
||||||
# (integer value)
|
|
||||||
#max_workers = 2
|
|
||||||
|
|
||||||
|
|
||||||
[watcher_goals]
|
|
||||||
|
|
||||||
#
|
|
||||||
# From watcher
|
|
||||||
#
|
|
||||||
|
|
||||||
# Goals used for the optimization. Maps each goal to an associated
|
|
||||||
# strategy (for example: BASIC_CONSOLIDATION:basic,
|
|
||||||
# MY_GOAL:my_strategy_1) (dict value)
|
|
||||||
#goals = DUMMY:dummy
|
|
||||||
@@ -2,28 +2,34 @@
|
|||||||
# of appearance. Changing the order has an impact on the overall integration
|
# of appearance. Changing the order has an impact on the overall integration
|
||||||
# process, which may cause wedges in the gate later.
|
# process, which may cause wedges in the gate later.
|
||||||
|
|
||||||
enum34;python_version=='2.7' or python_version=='2.6'
|
enum34;python_version=='2.7' or python_version=='2.6' or python_version=='3.3' # BSD
|
||||||
jsonpatch>=1.1
|
jsonpatch>=1.1 # BSD
|
||||||
keystonemiddleware>=2.0.0,!=2.4.0
|
keystoneauth1>=2.1.0 # Apache-2.0
|
||||||
oslo.config>=2.3.0 # Apache-2.0
|
keystonemiddleware!=4.1.0,>=4.0.0 # Apache-2.0
|
||||||
oslo.db>=2.4.1 # Apache-2.0
|
oslo.config>=3.7.0 # Apache-2.0
|
||||||
oslo.i18n>=1.5.0 # Apache-2.0
|
oslo.context>=0.2.0 # Apache-2.0
|
||||||
oslo.log>=1.8.0 # Apache-2.0
|
oslo.db>=4.1.0 # Apache-2.0
|
||||||
oslo.messaging>=1.16.0,!=1.17.0,!=1.17.1,!=2.6.0,!=2.6.1 # Apache-2.0
|
oslo.i18n>=2.1.0 # Apache-2.0
|
||||||
|
oslo.log>=1.14.0 # Apache-2.0
|
||||||
|
oslo.messaging>=4.0.0 # Apache-2.0
|
||||||
oslo.policy>=0.5.0 # Apache-2.0
|
oslo.policy>=0.5.0 # Apache-2.0
|
||||||
oslo.service>=0.7.0 # Apache-2.0
|
oslo.service>=1.0.0 # Apache-2.0
|
||||||
oslo.utils>=2.0.0,!=2.6.0 # Apache-2.0
|
oslo.utils>=3.5.0 # Apache-2.0
|
||||||
PasteDeploy>=1.5.0
|
PasteDeploy>=1.5.0 # MIT
|
||||||
pbr>=1.6
|
pbr>=1.6 # Apache-2.0
|
||||||
pecan>=1.0.0
|
pecan>=1.0.0 # BSD
|
||||||
python-ceilometerclient>=1.5.0
|
PrettyTable<0.8,>=0.7 # BSD
|
||||||
python-cinderclient>=1.3.1
|
voluptuous>=0.8.6 # BSD License
|
||||||
python-glanceclient>=0.18.0
|
python-ceilometerclient>=2.2.1 # Apache-2.0
|
||||||
python-keystoneclient>=1.6.0,!=1.8.0
|
python-cinderclient>=1.3.1 # Apache-2.0
|
||||||
python-neutronclient>=2.6.0
|
python-glanceclient>=2.0.0 # Apache-2.0
|
||||||
python-novaclient>=2.28.1,!=2.33.0
|
python-keystoneclient!=1.8.0,!=2.1.0,>=1.6.0 # Apache-2.0
|
||||||
python-openstackclient>=1.5.0
|
python-neutronclient!=4.1.0,>=2.6.0 # Apache-2.0
|
||||||
six>=1.9.0
|
python-novaclient!=2.33.0,>=2.29.0 # Apache-2.0
|
||||||
SQLAlchemy>=0.9.9,<1.1.0
|
python-openstackclient>=2.1.0 # Apache-2.0
|
||||||
|
six>=1.9.0 # MIT
|
||||||
|
SQLAlchemy<1.1.0,>=1.0.10 # MIT
|
||||||
stevedore>=1.5.0 # Apache-2.0
|
stevedore>=1.5.0 # Apache-2.0
|
||||||
WSME>=0.7
|
taskflow>=1.26.0 # Apache-2.0
|
||||||
|
WebOb>=1.2.3 # MIT
|
||||||
|
WSME>=0.8 # MIT
|
||||||
|
|||||||
30
setup.cfg
@@ -21,6 +21,7 @@ classifier =
|
|||||||
[files]
|
[files]
|
||||||
packages =
|
packages =
|
||||||
watcher
|
watcher
|
||||||
|
watcher_tempest_plugin
|
||||||
data_files =
|
data_files =
|
||||||
etc/ = etc/*
|
etc/ = etc/*
|
||||||
|
|
||||||
@@ -38,6 +39,9 @@ console_scripts =
|
|||||||
watcher-decision-engine = watcher.cmd.decisionengine:main
|
watcher-decision-engine = watcher.cmd.decisionengine:main
|
||||||
watcher-applier = watcher.cmd.applier:main
|
watcher-applier = watcher.cmd.applier:main
|
||||||
|
|
||||||
|
tempest.test_plugins =
|
||||||
|
watcher_tests = watcher_tempest_plugin.plugin:WatcherTempestPlugin
|
||||||
|
|
||||||
watcher.database.migration_backend =
|
watcher.database.migration_backend =
|
||||||
sqlalchemy = watcher.db.sqlalchemy.migration
|
sqlalchemy = watcher.db.sqlalchemy.migration
|
||||||
|
|
||||||
@@ -45,6 +49,30 @@ watcher_strategies =
|
|||||||
dummy = watcher.decision_engine.strategy.strategies.dummy_strategy:DummyStrategy
|
dummy = watcher.decision_engine.strategy.strategies.dummy_strategy:DummyStrategy
|
||||||
basic = watcher.decision_engine.strategy.strategies.basic_consolidation:BasicConsolidation
|
basic = watcher.decision_engine.strategy.strategies.basic_consolidation:BasicConsolidation
|
||||||
outlet_temp_control = watcher.decision_engine.strategy.strategies.outlet_temp_control:OutletTempControl
|
outlet_temp_control = watcher.decision_engine.strategy.strategies.outlet_temp_control:OutletTempControl
|
||||||
|
vm_workload_consolidation = watcher.decision_engine.strategy.strategies.vm_workload_consolidation:VMWorkloadConsolidation
|
||||||
|
|
||||||
|
watcher_actions =
|
||||||
|
migrate = watcher.applier.actions.migration:Migrate
|
||||||
|
nop = watcher.applier.actions.nop:Nop
|
||||||
|
sleep = watcher.applier.actions.sleep:Sleep
|
||||||
|
change_nova_service_state = watcher.applier.actions.change_nova_service_state:ChangeNovaServiceState
|
||||||
|
|
||||||
|
watcher_workflow_engines =
|
||||||
|
taskflow = watcher.applier.workflow_engine.default:DefaultWorkFlowEngine
|
||||||
|
|
||||||
|
watcher_planners =
|
||||||
|
default = watcher.decision_engine.planner.default:DefaultPlanner
|
||||||
|
|
||||||
|
[pbr]
|
||||||
|
warnerrors = true
|
||||||
|
autodoc_index_modules = true
|
||||||
|
autodoc_exclude_modules =
|
||||||
|
watcher.db.sqlalchemy.alembic.env
|
||||||
|
watcher.db.sqlalchemy.alembic.versions.*
|
||||||
|
watcher.tests.*
|
||||||
|
watcher_tempest_plugin.*
|
||||||
|
watcher.doc
|
||||||
|
|
||||||
|
|
||||||
[build_sphinx]
|
[build_sphinx]
|
||||||
source-dir = doc/source
|
source-dir = doc/source
|
||||||
@@ -66,6 +94,6 @@ output_dir = watcher/locale
|
|||||||
input_file = watcher/locale/watcher.pot
|
input_file = watcher/locale/watcher.pot
|
||||||
|
|
||||||
[extract_messages]
|
[extract_messages]
|
||||||
keywords = _ gettext ngettext l_ lazy_gettext
|
keywords = _ gettext ngettext l_ lazy_gettext _LI _LW _LE _LC
|
||||||
mapping_file = babel.cfg
|
mapping_file = babel.cfg
|
||||||
output_file = watcher/locale/watcher.pot
|
output_file = watcher/locale/watcher.pot
|
||||||
|
|||||||
3
setup.py
Executable file → Normal file
@@ -1,4 +1,3 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
|
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@@ -26,5 +25,5 @@ except ImportError:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
setuptools.setup(
|
setuptools.setup(
|
||||||
setup_requires=['pbr'],
|
setup_requires=['pbr>=1.8'],
|
||||||
pbr=True)
|
pbr=True)
|
||||||
|
|||||||
@@ -2,20 +2,20 @@
|
|||||||
# of appearance. Changing the order has an impact on the overall integration
|
# of appearance. Changing the order has an impact on the overall integration
|
||||||
# process, which may cause wedges in the gate later.
|
# process, which may cause wedges in the gate later.
|
||||||
|
|
||||||
coverage>=3.6
|
coverage>=3.6 # Apache-2.0
|
||||||
discover
|
discover # BSD
|
||||||
hacking>=0.10.2,<0.11
|
doc8 # Apache-2.0
|
||||||
mock>=1.2
|
freezegun # Apache-2.0
|
||||||
|
hacking<0.11,>=0.10.2
|
||||||
|
mock>=1.2 # BSD
|
||||||
oslotest>=1.10.0 # Apache-2.0
|
oslotest>=1.10.0 # Apache-2.0
|
||||||
python-subunit>=0.0.18
|
os-testr>=0.4.1 # Apache-2.0
|
||||||
testrepository>=0.0.18
|
python-subunit>=0.0.18 # Apache-2.0/BSD
|
||||||
testscenarios>=0.4
|
testrepository>=0.0.18 # Apache-2.0/BSD
|
||||||
testtools>=1.4.0
|
testscenarios>=0.4 # Apache-2.0/BSD
|
||||||
|
testtools>=1.4.0 # MIT
|
||||||
|
|
||||||
# Doc requirements
|
# Doc requirements
|
||||||
oslosphinx>=2.5.0 # Apache-2.0
|
oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0
|
||||||
sphinx>=1.1.2,!=1.2.0,!=1.3b1,<1.3
|
sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 # BSD
|
||||||
sphinxcontrib-pecanwsme>=0.8
|
sphinxcontrib-pecanwsme>=0.8 # Apache-2.0
|
||||||
|
|
||||||
# For PyPI distribution
|
|
||||||
twine
|
|
||||||
|
|||||||
37
tox.ini
@@ -14,48 +14,47 @@ deps = -r{toxinidir}/requirements.txt
|
|||||||
commands =
|
commands =
|
||||||
find . -type f -name "*.pyc" -delete
|
find . -type f -name "*.pyc" -delete
|
||||||
find . -type d -name "__pycache__" -delete
|
find . -type d -name "__pycache__" -delete
|
||||||
python setup.py testr --slowest --testr-args='{posargs}'
|
ostestr --concurrency=6 {posargs}
|
||||||
|
|
||||||
[testenv:pep8]
|
[testenv:pep8]
|
||||||
commands = flake8
|
commands =
|
||||||
|
doc8 doc/source/ CONTRIBUTING.rst HACKING.rst README.rst
|
||||||
|
flake8
|
||||||
|
|
||||||
[testenv:venv]
|
[testenv:venv]
|
||||||
|
setenv = PYTHONHASHSEED=0
|
||||||
commands = {posargs}
|
commands = {posargs}
|
||||||
|
|
||||||
[testenv:cover]
|
[testenv:cover]
|
||||||
commands = python setup.py testr --coverage --omit="watcher/tests/*" --testr-args='{posargs}'
|
commands = python setup.py testr --coverage --testr-args='{posargs}'
|
||||||
|
|
||||||
[testenv:docs]
|
[testenv:docs]
|
||||||
commands = python setup.py build_sphinx
|
setenv = PYTHONHASHSEED=0
|
||||||
|
commands =
|
||||||
|
doc8 doc/source/ CONTRIBUTING.rst HACKING.rst README.rst
|
||||||
|
python setup.py build_sphinx
|
||||||
|
|
||||||
[testenv:debug]
|
[testenv:debug]
|
||||||
commands = oslo_debug_helper {posargs}
|
commands = oslo_debug_helper -t watcher/tests {posargs}
|
||||||
|
|
||||||
[testenv:config]
|
[testenv:config]
|
||||||
sitepackages = False
|
sitepackages = False
|
||||||
commands =
|
commands =
|
||||||
oslo-config-generator --namespace watcher \
|
oslo-config-generator --config-file etc/watcher/watcher-config-generator.conf
|
||||||
--namespace keystonemiddleware.auth_token \
|
|
||||||
--namespace oslo.log \
|
|
||||||
--namespace oslo.db \
|
|
||||||
--namespace oslo.messaging \
|
|
||||||
--output-file etc/watcher/watcher.conf.sample
|
|
||||||
|
|
||||||
[flake8]
|
[flake8]
|
||||||
# E123, E125 skipped as they are invalid PEP-8.
|
|
||||||
|
|
||||||
show-source=True
|
show-source=True
|
||||||
ignore=E123,E125
|
ignore=
|
||||||
builtins= _
|
builtins= _
|
||||||
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build,*sqlalchemy/alembic/versions/*,demo/
|
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build,*sqlalchemy/alembic/versions/*,demo/
|
||||||
|
|
||||||
[testenv:pypi]
|
|
||||||
commands =
|
|
||||||
python setup.py sdist bdist_wheel
|
|
||||||
twine upload --config-file .pypirc {posargs} dist/*
|
|
||||||
|
|
||||||
[testenv:wheel]
|
[testenv:wheel]
|
||||||
commands = python setup.py bdist_wheel
|
commands = python setup.py bdist_wheel
|
||||||
|
|
||||||
[hacking]
|
[hacking]
|
||||||
import_exceptions = watcher._i18n
|
import_exceptions = watcher._i18n
|
||||||
|
|
||||||
|
[doc8]
|
||||||
|
extension=.rst
|
||||||
|
# todo: stop ignoring doc/source/man when https://bugs.launchpad.net/doc8/+bug/1502391 is fixed
|
||||||
|
ignore-path=doc/source/image_src,doc/source/man,doc/source/api
|
||||||
|
|||||||
@@ -15,6 +15,46 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
"""
|
||||||
|
An :ref:`Action <action_definition>` is what enables Watcher to transform the
|
||||||
|
current state of a :ref:`Cluster <cluster_definition>` after an
|
||||||
|
:ref:`Audit <audit_definition>`.
|
||||||
|
|
||||||
|
An :ref:`Action <action_definition>` is an atomic task which changes the
|
||||||
|
current state of a target :ref:`Managed resource <managed_resource_definition>`
|
||||||
|
of the OpenStack :ref:`Cluster <cluster_definition>` such as:
|
||||||
|
|
||||||
|
- Live migration of an instance from one compute node to another compute
|
||||||
|
node with Nova
|
||||||
|
- Changing the power level of a compute node (ACPI level, ...)
|
||||||
|
- Changing the current state of an hypervisor (enable or disable) with Nova
|
||||||
|
|
||||||
|
In most cases, an :ref:`Action <action_definition>` triggers some concrete
|
||||||
|
commands on an existing OpenStack module (Nova, Neutron, Cinder, Ironic, etc.).
|
||||||
|
|
||||||
|
An :ref:`Action <action_definition>` has a life-cycle and its current state may
|
||||||
|
be one of the following:
|
||||||
|
|
||||||
|
- **PENDING** : the :ref:`Action <action_definition>` has not been executed
|
||||||
|
yet by the :ref:`Watcher Applier <watcher_applier_definition>`
|
||||||
|
- **ONGOING** : the :ref:`Action <action_definition>` is currently being
|
||||||
|
processed by the :ref:`Watcher Applier <watcher_applier_definition>`
|
||||||
|
- **SUCCEEDED** : the :ref:`Action <action_definition>` has been executed
|
||||||
|
successfully
|
||||||
|
- **FAILED** : an error occured while trying to execute the
|
||||||
|
:ref:`Action <action_definition>`
|
||||||
|
- **DELETED** : the :ref:`Action <action_definition>` is still stored in the
|
||||||
|
:ref:`Watcher database <watcher_database_definition>` but is not returned
|
||||||
|
any more through the Watcher APIs.
|
||||||
|
- **CANCELLED** : the :ref:`Action <action_definition>` was in **PENDING** or
|
||||||
|
**ONGOING** state and was cancelled by the
|
||||||
|
:ref:`Administrator <administrator_definition>`
|
||||||
|
|
||||||
|
:ref:`Some default implementations are provided <watcher_planners>`, but it is
|
||||||
|
possible to :ref:`develop new implementations <implement_action_plugin>` which
|
||||||
|
are dynamically loaded by Watcher at launch time.
|
||||||
|
"""
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
import pecan
|
import pecan
|
||||||
@@ -87,29 +127,17 @@ class Action(base.APIBase):
|
|||||||
mandatory=True)
|
mandatory=True)
|
||||||
"""The action plan this action belongs to """
|
"""The action plan this action belongs to """
|
||||||
|
|
||||||
description = wtypes.text
|
|
||||||
"""Description of this action"""
|
|
||||||
|
|
||||||
state = wtypes.text
|
state = wtypes.text
|
||||||
"""This audit state"""
|
"""This audit state"""
|
||||||
|
|
||||||
alarm = types.uuid
|
alarm = types.uuid
|
||||||
"""An alarm UUID related to this action"""
|
"""An alarm UUID related to this action"""
|
||||||
|
|
||||||
applies_to = wtypes.text
|
|
||||||
"""Applies to"""
|
|
||||||
|
|
||||||
src = wtypes.text
|
|
||||||
"""Hypervisor source"""
|
|
||||||
|
|
||||||
dst = wtypes.text
|
|
||||||
"""Hypervisor source"""
|
|
||||||
|
|
||||||
action_type = wtypes.text
|
action_type = wtypes.text
|
||||||
"""Action type"""
|
"""Action type"""
|
||||||
|
|
||||||
parameter = wtypes.text
|
input_parameters = types.jsontype
|
||||||
"""Additionnal parameter"""
|
"""One or more key/value pairs """
|
||||||
|
|
||||||
next_uuid = wsme.wsproperty(types.uuid, _get_next_uuid,
|
next_uuid = wsme.wsproperty(types.uuid, _get_next_uuid,
|
||||||
_set_next_uuid,
|
_set_next_uuid,
|
||||||
@@ -230,7 +258,7 @@ class ActionsController(rest.RestController):
|
|||||||
resource_url=None,
|
resource_url=None,
|
||||||
action_plan_uuid=None, audit_uuid=None):
|
action_plan_uuid=None, audit_uuid=None):
|
||||||
limit = api_utils.validate_limit(limit)
|
limit = api_utils.validate_limit(limit)
|
||||||
sort_dir = api_utils.validate_sort_dir(sort_dir)
|
api_utils.validate_sort_dir(sort_dir)
|
||||||
|
|
||||||
marker_obj = None
|
marker_obj = None
|
||||||
if marker:
|
if marker:
|
||||||
@@ -261,10 +289,10 @@ class ActionsController(rest.RestController):
|
|||||||
sort_key=sort_key,
|
sort_key=sort_key,
|
||||||
sort_dir=sort_dir)
|
sort_dir=sort_dir)
|
||||||
|
|
||||||
@wsme_pecan.wsexpose(ActionCollection, types.uuid, types.uuid,
|
@wsme_pecan.wsexpose(ActionCollection, types.uuid, int,
|
||||||
int, wtypes.text, wtypes.text, types.uuid,
|
wtypes.text, wtypes.text, types.uuid,
|
||||||
types.uuid)
|
types.uuid)
|
||||||
def get_all(self, action_uuid=None, marker=None, limit=None,
|
def get_all(self, marker=None, limit=None,
|
||||||
sort_key='id', sort_dir='asc', action_plan_uuid=None,
|
sort_key='id', sort_dir='asc', action_plan_uuid=None,
|
||||||
audit_uuid=None):
|
audit_uuid=None):
|
||||||
"""Retrieve a list of actions.
|
"""Retrieve a list of actions.
|
||||||
@@ -285,16 +313,14 @@ class ActionsController(rest.RestController):
|
|||||||
marker, limit, sort_key, sort_dir,
|
marker, limit, sort_key, sort_dir,
|
||||||
action_plan_uuid=action_plan_uuid, audit_uuid=audit_uuid)
|
action_plan_uuid=action_plan_uuid, audit_uuid=audit_uuid)
|
||||||
|
|
||||||
@wsme_pecan.wsexpose(ActionCollection, types.uuid,
|
@wsme_pecan.wsexpose(ActionCollection, types.uuid, int,
|
||||||
types.uuid, int, wtypes.text, wtypes.text,
|
wtypes.text, wtypes.text, types.uuid,
|
||||||
types.uuid, types.uuid)
|
types.uuid)
|
||||||
def detail(self, action_uuid=None, marker=None, limit=None,
|
def detail(self, marker=None, limit=None,
|
||||||
sort_key='id', sort_dir='asc', action_plan_uuid=None,
|
sort_key='id', sort_dir='asc', action_plan_uuid=None,
|
||||||
audit_uuid=None):
|
audit_uuid=None):
|
||||||
"""Retrieve a list of actions with detail.
|
"""Retrieve a list of actions with detail.
|
||||||
|
|
||||||
:param action_uuid: UUID of a action, to get only actions for that
|
|
||||||
action.
|
|
||||||
:param marker: pagination marker for large data sets.
|
:param marker: pagination marker for large data sets.
|
||||||
:param limit: maximum number of resources to return in a single result.
|
:param limit: maximum number of resources to return in a single result.
|
||||||
:param sort_key: column to sort results by. Default: id.
|
:param sort_key: column to sort results by. Default: id.
|
||||||
@@ -337,6 +363,10 @@ class ActionsController(rest.RestController):
|
|||||||
|
|
||||||
:param action: a action within the request body.
|
:param action: a action within the request body.
|
||||||
"""
|
"""
|
||||||
|
# FIXME: blueprint edit-action-plan-flow
|
||||||
|
raise exception.OperationNotPermitted(
|
||||||
|
_("Cannot create an action directly"))
|
||||||
|
|
||||||
if self.from_actions:
|
if self.from_actions:
|
||||||
raise exception.OperationNotPermitted
|
raise exception.OperationNotPermitted
|
||||||
|
|
||||||
@@ -357,6 +387,10 @@ class ActionsController(rest.RestController):
|
|||||||
:param action_uuid: UUID of a action.
|
:param action_uuid: UUID of a action.
|
||||||
:param patch: a json PATCH document to apply to this action.
|
:param patch: a json PATCH document to apply to this action.
|
||||||
"""
|
"""
|
||||||
|
# FIXME: blueprint edit-action-plan-flow
|
||||||
|
raise exception.OperationNotPermitted(
|
||||||
|
_("Cannot modify an action directly"))
|
||||||
|
|
||||||
if self.from_actions:
|
if self.from_actions:
|
||||||
raise exception.OperationNotPermitted
|
raise exception.OperationNotPermitted
|
||||||
|
|
||||||
@@ -389,6 +423,9 @@ class ActionsController(rest.RestController):
|
|||||||
|
|
||||||
:param action_uuid: UUID of a action.
|
:param action_uuid: UUID of a action.
|
||||||
"""
|
"""
|
||||||
|
# FIXME: blueprint edit-action-plan-flow
|
||||||
|
raise exception.OperationNotPermitted(
|
||||||
|
_("Cannot delete an action directly"))
|
||||||
|
|
||||||
action_to_delete = objects.Action.get_by_uuid(
|
action_to_delete = objects.Action.get_by_uuid(
|
||||||
pecan.request.context,
|
pecan.request.context,
|
||||||
|
|||||||
@@ -15,6 +15,45 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
"""
|
||||||
|
An :ref:`Action Plan <action_plan_definition>` is a flow of
|
||||||
|
:ref:`Actions <action_definition>` that should be executed in order to satisfy
|
||||||
|
a given :ref:`Goal <goal_definition>`.
|
||||||
|
|
||||||
|
An :ref:`Action Plan <action_plan_definition>` is generated by Watcher when an
|
||||||
|
:ref:`Audit <audit_definition>` is successful which implies that the
|
||||||
|
:ref:`Strategy <strategy_definition>`
|
||||||
|
which was used has found a :ref:`Solution <solution_definition>` to achieve the
|
||||||
|
:ref:`Goal <goal_definition>` of this :ref:`Audit <audit_definition>`.
|
||||||
|
|
||||||
|
In the default implementation of Watcher, an
|
||||||
|
:ref:`Action Plan <action_plan_definition>`
|
||||||
|
is only composed of successive :ref:`Actions <action_definition>`
|
||||||
|
(i.e., a Workflow of :ref:`Actions <action_definition>` belonging to a unique
|
||||||
|
branch).
|
||||||
|
|
||||||
|
However, Watcher provides abstract interfaces for many of its components,
|
||||||
|
allowing other implementations to generate and handle more complex
|
||||||
|
:ref:`Action Plan(s) <action_plan_definition>`
|
||||||
|
composed of two types of Action Item(s):
|
||||||
|
|
||||||
|
- simple :ref:`Actions <action_definition>`: atomic tasks, which means it
|
||||||
|
can not be split into smaller tasks or commands from an OpenStack point of
|
||||||
|
view.
|
||||||
|
- composite Actions: which are composed of several simple
|
||||||
|
:ref:`Actions <action_definition>`
|
||||||
|
ordered in sequential and/or parallel flows.
|
||||||
|
|
||||||
|
An :ref:`Action Plan <action_plan_definition>` may be described using
|
||||||
|
standard workflow model description formats such as
|
||||||
|
`Business Process Model and Notation 2.0 (BPMN 2.0) <http://www.omg.org/spec/BPMN/2.0/>`_
|
||||||
|
or `Unified Modeling Language (UML) <http://www.uml.org/>`_.
|
||||||
|
|
||||||
|
To see the life-cycle and description of
|
||||||
|
:ref:`Action Plan <action_plan_definition>` states, visit :ref:`the Action Plan state
|
||||||
|
machine <action_plan_state_machine>`.
|
||||||
|
""" # noqa
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
import pecan
|
import pecan
|
||||||
@@ -23,21 +62,45 @@ import wsme
|
|||||||
from wsme import types as wtypes
|
from wsme import types as wtypes
|
||||||
import wsmeext.pecan as wsme_pecan
|
import wsmeext.pecan as wsme_pecan
|
||||||
|
|
||||||
|
from watcher._i18n import _
|
||||||
from watcher.api.controllers import base
|
from watcher.api.controllers import base
|
||||||
from watcher.api.controllers import link
|
from watcher.api.controllers import link
|
||||||
from watcher.api.controllers.v1 import collection
|
from watcher.api.controllers.v1 import collection
|
||||||
from watcher.api.controllers.v1 import types
|
from watcher.api.controllers.v1 import types
|
||||||
from watcher.api.controllers.v1 import utils as api_utils
|
from watcher.api.controllers.v1 import utils as api_utils
|
||||||
from watcher.applier.rpcapi import ApplierAPI
|
from watcher.applier import rpcapi
|
||||||
from watcher.common import exception
|
from watcher.common import exception
|
||||||
from watcher import objects
|
from watcher import objects
|
||||||
|
from watcher.objects import action_plan as ap_objects
|
||||||
|
|
||||||
|
|
||||||
class ActionPlanPatchType(types.JsonPatchType):
|
class ActionPlanPatchType(types.JsonPatchType):
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _validate_state(patch):
|
||||||
|
serialized_patch = {'path': patch.path, 'op': patch.op}
|
||||||
|
if patch.value is not wsme.Unset:
|
||||||
|
serialized_patch['value'] = patch.value
|
||||||
|
# todo: use state machines to handle state transitions
|
||||||
|
state_value = patch.value
|
||||||
|
if state_value and not hasattr(ap_objects.State, state_value):
|
||||||
|
msg = _("Invalid state: %(state)s")
|
||||||
|
raise exception.PatchError(
|
||||||
|
patch=serialized_patch, reason=msg % dict(state=state_value))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def validate(patch):
|
||||||
|
if patch.path == "/state":
|
||||||
|
ActionPlanPatchType._validate_state(patch)
|
||||||
|
return types.JsonPatchType.validate(patch)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def internal_attrs():
|
||||||
|
return types.JsonPatchType.internal_attrs()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def mandatory_attrs():
|
def mandatory_attrs():
|
||||||
return []
|
return ["audit_id", "state", "first_action_id"]
|
||||||
|
|
||||||
|
|
||||||
class ActionPlan(base.APIBase):
|
class ActionPlan(base.APIBase):
|
||||||
@@ -103,7 +166,6 @@ class ActionPlan(base.APIBase):
|
|||||||
|
|
||||||
self.fields = []
|
self.fields = []
|
||||||
fields = list(objects.ActionPlan.fields)
|
fields = list(objects.ActionPlan.fields)
|
||||||
fields.append('audit_uuid')
|
|
||||||
for field in fields:
|
for field in fields:
|
||||||
# Skip fields we do not expose.
|
# Skip fields we do not expose.
|
||||||
if not hasattr(self, field):
|
if not hasattr(self, field):
|
||||||
@@ -111,14 +173,19 @@ class ActionPlan(base.APIBase):
|
|||||||
self.fields.append(field)
|
self.fields.append(field)
|
||||||
setattr(self, field, kwargs.get(field, wtypes.Unset))
|
setattr(self, field, kwargs.get(field, wtypes.Unset))
|
||||||
|
|
||||||
self.fields.append('audit_id')
|
self.fields.append('audit_uuid')
|
||||||
|
self.fields.append('first_action_uuid')
|
||||||
|
|
||||||
setattr(self, 'audit_uuid', kwargs.get('audit_id', wtypes.Unset))
|
setattr(self, 'audit_uuid', kwargs.get('audit_id', wtypes.Unset))
|
||||||
|
setattr(self, 'first_action_uuid',
|
||||||
|
kwargs.get('first_action_id', wtypes.Unset))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _convert_with_links(action_plan, url, expand=True):
|
def _convert_with_links(action_plan, url, expand=True):
|
||||||
if not expand:
|
if not expand:
|
||||||
action_plan.unset_fields_except(['uuid', 'state', 'updated_at',
|
action_plan.unset_fields_except(
|
||||||
'audit_uuid'])
|
['uuid', 'state', 'updated_at',
|
||||||
|
'audit_uuid', 'first_action_uuid'])
|
||||||
|
|
||||||
action_plan.links = [link.Link.make_link(
|
action_plan.links = [link.Link.make_link(
|
||||||
'self', url,
|
'self', url,
|
||||||
@@ -201,7 +268,7 @@ class ActionPlansController(rest.RestController):
|
|||||||
resource_url=None, audit_uuid=None):
|
resource_url=None, audit_uuid=None):
|
||||||
|
|
||||||
limit = api_utils.validate_limit(limit)
|
limit = api_utils.validate_limit(limit)
|
||||||
sort_dir = api_utils.validate_sort_dir(sort_dir)
|
api_utils.validate_sort_dir(sort_dir)
|
||||||
|
|
||||||
marker_obj = None
|
marker_obj = None
|
||||||
if marker:
|
if marker:
|
||||||
@@ -230,9 +297,9 @@ class ActionPlansController(rest.RestController):
|
|||||||
sort_key=sort_key,
|
sort_key=sort_key,
|
||||||
sort_dir=sort_dir)
|
sort_dir=sort_dir)
|
||||||
|
|
||||||
@wsme_pecan.wsexpose(ActionPlanCollection, types.uuid, types.uuid,
|
@wsme_pecan.wsexpose(ActionPlanCollection, types.uuid, int, wtypes.text,
|
||||||
int, wtypes.text, wtypes.text, types.uuid)
|
wtypes.text, types.uuid)
|
||||||
def get_all(self, action_plan_uuid=None, marker=None, limit=None,
|
def get_all(self, marker=None, limit=None,
|
||||||
sort_key='id', sort_dir='asc', audit_uuid=None):
|
sort_key='id', sort_dir='asc', audit_uuid=None):
|
||||||
"""Retrieve a list of action plans.
|
"""Retrieve a list of action plans.
|
||||||
|
|
||||||
@@ -246,14 +313,12 @@ class ActionPlansController(rest.RestController):
|
|||||||
return self._get_action_plans_collection(
|
return self._get_action_plans_collection(
|
||||||
marker, limit, sort_key, sort_dir, audit_uuid=audit_uuid)
|
marker, limit, sort_key, sort_dir, audit_uuid=audit_uuid)
|
||||||
|
|
||||||
@wsme_pecan.wsexpose(ActionPlanCollection, types.uuid, types.uuid,
|
@wsme_pecan.wsexpose(ActionPlanCollection, types.uuid, int, wtypes.text,
|
||||||
int, wtypes.text, wtypes.text, types.uuid)
|
wtypes.text, types.uuid)
|
||||||
def detail(self, action_plan_uuid=None, marker=None, limit=None,
|
def detail(self, marker=None, limit=None,
|
||||||
sort_key='id', sort_dir='asc', audit_uuid=None):
|
sort_key='id', sort_dir='asc', audit_uuid=None):
|
||||||
"""Retrieve a list of action_plans with detail.
|
"""Retrieve a list of action_plans with detail.
|
||||||
|
|
||||||
:param action_plan_uuid: UUID of a action plan, to get only
|
|
||||||
:action_plans for that action.
|
|
||||||
:param marker: pagination marker for large data sets.
|
:param marker: pagination marker for large data sets.
|
||||||
:param limit: maximum number of resources to return in a single result.
|
:param limit: maximum number of resources to return in a single result.
|
||||||
:param sort_key: column to sort results by. Default: id.
|
:param sort_key: column to sort results by. Default: id.
|
||||||
@@ -302,10 +367,10 @@ class ActionPlansController(rest.RestController):
|
|||||||
@wsme_pecan.wsexpose(ActionPlan, types.uuid,
|
@wsme_pecan.wsexpose(ActionPlan, types.uuid,
|
||||||
body=[ActionPlanPatchType])
|
body=[ActionPlanPatchType])
|
||||||
def patch(self, action_plan_uuid, patch):
|
def patch(self, action_plan_uuid, patch):
|
||||||
"""Update an existing audit template.
|
"""Update an existing action plan.
|
||||||
|
|
||||||
:param audit template_uuid: UUID of a audit template.
|
:param action_plan_uuid: UUID of a action plan.
|
||||||
:param patch: a json PATCH document to apply to this audit template.
|
:param patch: a json PATCH document to apply to this action plan.
|
||||||
"""
|
"""
|
||||||
launch_action_plan = True
|
launch_action_plan = True
|
||||||
if self.from_actionsPlans:
|
if self.from_actionsPlans:
|
||||||
@@ -322,6 +387,34 @@ class ActionPlansController(rest.RestController):
|
|||||||
raise exception.PatchError(patch=patch, reason=e)
|
raise exception.PatchError(patch=patch, reason=e)
|
||||||
|
|
||||||
launch_action_plan = False
|
launch_action_plan = False
|
||||||
|
|
||||||
|
# transitions that are allowed via PATCH
|
||||||
|
allowed_patch_transitions = [
|
||||||
|
(ap_objects.State.RECOMMENDED,
|
||||||
|
ap_objects.State.PENDING),
|
||||||
|
(ap_objects.State.RECOMMENDED,
|
||||||
|
ap_objects.State.CANCELLED),
|
||||||
|
(ap_objects.State.ONGOING,
|
||||||
|
ap_objects.State.CANCELLED),
|
||||||
|
(ap_objects.State.PENDING,
|
||||||
|
ap_objects.State.CANCELLED),
|
||||||
|
]
|
||||||
|
|
||||||
|
# todo: improve this in blueprint watcher-api-validation
|
||||||
|
if hasattr(action_plan, 'state'):
|
||||||
|
transition = (action_plan_to_update.state, action_plan.state)
|
||||||
|
if transition not in allowed_patch_transitions:
|
||||||
|
error_message = _("State transition not allowed: "
|
||||||
|
"(%(initial_state)s -> %(new_state)s)")
|
||||||
|
raise exception.PatchError(
|
||||||
|
patch=patch,
|
||||||
|
reason=error_message % dict(
|
||||||
|
initial_state=action_plan_to_update.state,
|
||||||
|
new_state=action_plan.state))
|
||||||
|
|
||||||
|
if action_plan.state == ap_objects.State.PENDING:
|
||||||
|
launch_action_plan = True
|
||||||
|
|
||||||
# Update only the fields that have changed
|
# Update only the fields that have changed
|
||||||
for field in objects.ActionPlan.fields:
|
for field in objects.ActionPlan.fields:
|
||||||
try:
|
try:
|
||||||
@@ -334,13 +427,14 @@ class ActionPlansController(rest.RestController):
|
|||||||
if action_plan_to_update[field] != patch_val:
|
if action_plan_to_update[field] != patch_val:
|
||||||
action_plan_to_update[field] = patch_val
|
action_plan_to_update[field] = patch_val
|
||||||
|
|
||||||
if field == 'state' and patch_val == 'STARTING':
|
if (field == 'state'
|
||||||
|
and patch_val == objects.action_plan.State.PENDING):
|
||||||
launch_action_plan = True
|
launch_action_plan = True
|
||||||
|
|
||||||
action_plan_to_update.save()
|
action_plan_to_update.save()
|
||||||
|
|
||||||
if launch_action_plan:
|
if launch_action_plan:
|
||||||
applier_client = ApplierAPI()
|
applier_client = rpcapi.ApplierAPI()
|
||||||
applier_client.launch_action_plan(pecan.request.context,
|
applier_client.launch_action_plan(pecan.request.context,
|
||||||
action_plan.uuid)
|
action_plan.uuid)
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,20 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
"""
|
||||||
|
In the Watcher system, an :ref:`Audit <audit_definition>` is a request for
|
||||||
|
optimizing a :ref:`Cluster <cluster_definition>`.
|
||||||
|
|
||||||
|
The optimization is done in order to satisfy one :ref:`Goal <goal_definition>`
|
||||||
|
on a given :ref:`Cluster <cluster_definition>`.
|
||||||
|
|
||||||
|
For each :ref:`Audit <audit_definition>`, the Watcher system generates an
|
||||||
|
:ref:`Action Plan <action_plan_definition>`.
|
||||||
|
|
||||||
|
To see the life-cycle and description of an :ref:`Audit <audit_definition>`
|
||||||
|
states, visit :ref:`the Audit State machine <audit_state_machine>`.
|
||||||
|
"""
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
import pecan
|
import pecan
|
||||||
@@ -23,6 +37,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 _
|
||||||
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
|
||||||
@@ -30,7 +45,7 @@ from watcher.api.controllers.v1 import types
|
|||||||
from watcher.api.controllers.v1 import utils as api_utils
|
from watcher.api.controllers.v1 import utils as api_utils
|
||||||
from watcher.common import exception
|
from watcher.common import exception
|
||||||
from watcher.common import utils
|
from watcher.common import utils
|
||||||
from watcher.decision_engine.rpcapi import DecisionEngineAPI
|
from watcher.decision_engine import rpcapi
|
||||||
from watcher import objects
|
from watcher import objects
|
||||||
|
|
||||||
|
|
||||||
@@ -181,6 +196,7 @@ class AuditCollection(collection.Collection):
|
|||||||
"""A list containing audits objects"""
|
"""A list containing audits objects"""
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
|
super(AuditCollection, self).__init__()
|
||||||
self._type = 'audits'
|
self._type = 'audits'
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -227,7 +243,7 @@ class AuditsController(rest.RestController):
|
|||||||
sort_key, sort_dir, expand=False,
|
sort_key, sort_dir, expand=False,
|
||||||
resource_url=None, audit_template=None):
|
resource_url=None, audit_template=None):
|
||||||
limit = api_utils.validate_limit(limit)
|
limit = api_utils.validate_limit(limit)
|
||||||
sort_dir = api_utils.validate_sort_dir(sort_dir)
|
api_utils.validate_sort_dir(sort_dir)
|
||||||
|
|
||||||
marker_obj = None
|
marker_obj = None
|
||||||
if marker:
|
if marker:
|
||||||
@@ -257,10 +273,9 @@ class AuditsController(rest.RestController):
|
|||||||
sort_key=sort_key,
|
sort_key=sort_key,
|
||||||
sort_dir=sort_dir)
|
sort_dir=sort_dir)
|
||||||
|
|
||||||
@wsme_pecan.wsexpose(AuditCollection, types.uuid,
|
@wsme_pecan.wsexpose(AuditCollection, types.uuid, int, wtypes.text,
|
||||||
types.uuid, int, wtypes.text,
|
|
||||||
wtypes.text, wtypes.text)
|
wtypes.text, wtypes.text)
|
||||||
def get_all(self, audit_uuid=None, marker=None, limit=None,
|
def get_all(self, marker=None, limit=None,
|
||||||
sort_key='id', sort_dir='asc', audit_template=None):
|
sort_key='id', sort_dir='asc', audit_template=None):
|
||||||
"""Retrieve a list of audits.
|
"""Retrieve a list of audits.
|
||||||
|
|
||||||
@@ -275,13 +290,12 @@ class AuditsController(rest.RestController):
|
|||||||
sort_dir,
|
sort_dir,
|
||||||
audit_template=audit_template)
|
audit_template=audit_template)
|
||||||
|
|
||||||
@wsme_pecan.wsexpose(AuditCollection, types.uuid,
|
@wsme_pecan.wsexpose(AuditCollection, types.uuid, int, wtypes.text,
|
||||||
types.uuid, int, wtypes.text, wtypes.text)
|
wtypes.text)
|
||||||
def detail(self, audit_uuid=None, marker=None, limit=None,
|
def detail(self, marker=None, limit=None,
|
||||||
sort_key='id', sort_dir='asc'):
|
sort_key='id', sort_dir='asc'):
|
||||||
"""Retrieve a list of audits with detail.
|
"""Retrieve a list of audits with detail.
|
||||||
|
|
||||||
:param audit_uuid: UUID of a audit, to get only audits for that audit.
|
|
||||||
:param marker: pagination marker for large data sets.
|
:param marker: pagination marker for large data sets.
|
||||||
:param limit: maximum number of resources to return in a single result.
|
:param limit: maximum number of resources to return in a single result.
|
||||||
:param sort_key: column to sort results by. Default: id.
|
:param sort_key: column to sort results by. Default: id.
|
||||||
@@ -320,6 +334,11 @@ class AuditsController(rest.RestController):
|
|||||||
if self.from_audits:
|
if self.from_audits:
|
||||||
raise exception.OperationNotPermitted
|
raise exception.OperationNotPermitted
|
||||||
|
|
||||||
|
if not audit._audit_template_uuid:
|
||||||
|
raise exception.Invalid(
|
||||||
|
message=_('The audit template UUID or name specified is '
|
||||||
|
'invalid'))
|
||||||
|
|
||||||
audit_dict = audit.as_dict()
|
audit_dict = audit.as_dict()
|
||||||
context = pecan.request.context
|
context = pecan.request.context
|
||||||
new_audit = objects.Audit(context, **audit_dict)
|
new_audit = objects.Audit(context, **audit_dict)
|
||||||
@@ -330,7 +349,7 @@ class AuditsController(rest.RestController):
|
|||||||
|
|
||||||
# trigger decision-engine to run the audit
|
# trigger decision-engine to run the audit
|
||||||
|
|
||||||
dc_client = DecisionEngineAPI()
|
dc_client = rpcapi.DecisionEngineAPI()
|
||||||
dc_client.trigger_audit(context, new_audit.uuid)
|
dc_client.trigger_audit(context, new_audit.uuid)
|
||||||
|
|
||||||
return Audit.convert_with_links(new_audit)
|
return Audit.convert_with_links(new_audit)
|
||||||
|
|||||||
@@ -15,8 +15,42 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
"""
|
||||||
|
An :ref:`Audit <audit_definition>` may be launched several times with the same
|
||||||
|
settings (:ref:`Goal <goal_definition>`, thresholds, ...). Therefore it makes
|
||||||
|
sense to save those settings in some sort of Audit preset object, which is
|
||||||
|
known as an :ref:`Audit Template <audit_template_definition>`.
|
||||||
|
|
||||||
|
An :ref:`Audit Template <audit_template_definition>` contains at least the
|
||||||
|
:ref:`Goal <goal_definition>` of the :ref:`Audit <audit_definition>`.
|
||||||
|
|
||||||
|
It may also contain some error handling settings indicating whether:
|
||||||
|
|
||||||
|
- :ref:`Watcher Applier <watcher_applier_definition>` stops the
|
||||||
|
entire operation
|
||||||
|
- :ref:`Watcher Applier <watcher_applier_definition>` performs a rollback
|
||||||
|
|
||||||
|
and how many retries should be attempted before failure occurs (also the latter
|
||||||
|
can be complex: for example the scenario in which there are many first-time
|
||||||
|
failures on ultimately successful :ref:`Actions <action_definition>`).
|
||||||
|
|
||||||
|
Moreover, an :ref:`Audit Template <audit_template_definition>` may contain some
|
||||||
|
settings related to the level of automation for the
|
||||||
|
:ref:`Action Plan <action_plan_definition>` that will be generated by the
|
||||||
|
:ref:`Audit <audit_definition>`.
|
||||||
|
A flag will indicate whether the :ref:`Action Plan <action_plan_definition>`
|
||||||
|
will be launched automatically or will need a manual confirmation from the
|
||||||
|
:ref:`Administrator <administrator_definition>`.
|
||||||
|
|
||||||
|
Last but not least, an :ref:`Audit Template <audit_template_definition>` may
|
||||||
|
contain a list of extra parameters related to the
|
||||||
|
:ref:`Strategy <strategy_definition>` configuration. These parameters can be
|
||||||
|
provided as a list of key-value pairs.
|
||||||
|
"""
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
import pecan
|
import pecan
|
||||||
from pecan import rest
|
from pecan import rest
|
||||||
import wsme
|
import wsme
|
||||||
@@ -34,11 +68,25 @@ from watcher import objects
|
|||||||
|
|
||||||
|
|
||||||
class AuditTemplatePatchType(types.JsonPatchType):
|
class AuditTemplatePatchType(types.JsonPatchType):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def mandatory_attrs():
|
def mandatory_attrs():
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def validate(patch):
|
||||||
|
if patch.path == "/goal":
|
||||||
|
AuditTemplatePatchType._validate_goal(patch)
|
||||||
|
return types.JsonPatchType.validate(patch)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _validate_goal(patch):
|
||||||
|
serialized_patch = {'path': patch.path, 'op': patch.op}
|
||||||
|
if patch.value is not wsme.Unset:
|
||||||
|
serialized_patch['value'] = patch.value
|
||||||
|
new_goal = patch.value
|
||||||
|
if new_goal and new_goal not in cfg.CONF.watcher_goals.goals.keys():
|
||||||
|
raise exception.InvalidGoal(goal=new_goal)
|
||||||
|
|
||||||
|
|
||||||
class AuditTemplate(base.APIBase):
|
class AuditTemplate(base.APIBase):
|
||||||
"""API representation of a audit template.
|
"""API representation of a audit template.
|
||||||
@@ -116,13 +164,19 @@ class AuditTemplate(base.APIBase):
|
|||||||
name='My Audit Template',
|
name='My Audit Template',
|
||||||
description='Description of my audit template',
|
description='Description of my audit template',
|
||||||
host_aggregate=5,
|
host_aggregate=5,
|
||||||
goal='SERVERS_CONSOLIDATION',
|
goal='DUMMY',
|
||||||
extra={'automatic': True},
|
extra={'automatic': True},
|
||||||
created_at=datetime.datetime.utcnow(),
|
created_at=datetime.datetime.utcnow(),
|
||||||
deleted_at=None,
|
deleted_at=None,
|
||||||
updated_at=datetime.datetime.utcnow())
|
updated_at=datetime.datetime.utcnow())
|
||||||
return cls._convert_with_links(sample, 'http://localhost:9322', expand)
|
return cls._convert_with_links(sample, 'http://localhost:9322', expand)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def validate(audit_template):
|
||||||
|
if audit_template.goal not in cfg.CONF.watcher_goals.goals.keys():
|
||||||
|
raise exception.InvalidGoal(audit_template.goal)
|
||||||
|
return audit_template
|
||||||
|
|
||||||
|
|
||||||
class AuditTemplateCollection(collection.Collection):
|
class AuditTemplateCollection(collection.Collection):
|
||||||
"""API representation of a collection of audit templates."""
|
"""API representation of a collection of audit templates."""
|
||||||
@@ -131,6 +185,7 @@ class AuditTemplateCollection(collection.Collection):
|
|||||||
"""A list containing audit templates objects"""
|
"""A list containing audit templates objects"""
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
|
super(AuditTemplateCollection, self).__init__()
|
||||||
self._type = 'audit_templates'
|
self._type = 'audit_templates'
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -163,12 +218,13 @@ class AuditTemplatesController(rest.RestController):
|
|||||||
'detail': ['GET'],
|
'detail': ['GET'],
|
||||||
}
|
}
|
||||||
|
|
||||||
def _get_audit_templates_collection(self, marker, limit,
|
def _get_audit_templates_collection(self, filters, marker, limit,
|
||||||
sort_key, sort_dir, expand=False,
|
sort_key, sort_dir, expand=False,
|
||||||
resource_url=None):
|
resource_url=None):
|
||||||
|
api_utils.validate_search_filters(
|
||||||
|
filters, objects.audit_template.AuditTemplate.fields.keys())
|
||||||
limit = api_utils.validate_limit(limit)
|
limit = api_utils.validate_limit(limit)
|
||||||
sort_dir = api_utils.validate_sort_dir(sort_dir)
|
api_utils.validate_sort_dir(sort_dir)
|
||||||
|
|
||||||
marker_obj = None
|
marker_obj = None
|
||||||
if marker:
|
if marker:
|
||||||
@@ -178,6 +234,7 @@ class AuditTemplatesController(rest.RestController):
|
|||||||
|
|
||||||
audit_templates = objects.AuditTemplate.list(
|
audit_templates = objects.AuditTemplate.list(
|
||||||
pecan.request.context,
|
pecan.request.context,
|
||||||
|
filters,
|
||||||
limit,
|
limit,
|
||||||
marker_obj, sort_key=sort_key,
|
marker_obj, sort_key=sort_key,
|
||||||
sort_dir=sort_dir)
|
sort_dir=sort_dir)
|
||||||
@@ -189,26 +246,30 @@ class AuditTemplatesController(rest.RestController):
|
|||||||
sort_key=sort_key,
|
sort_key=sort_key,
|
||||||
sort_dir=sort_dir)
|
sort_dir=sort_dir)
|
||||||
|
|
||||||
@wsme_pecan.wsexpose(AuditTemplateCollection, types.uuid, int,
|
@wsme_pecan.wsexpose(AuditTemplateCollection, wtypes.text,
|
||||||
wtypes.text, wtypes.text)
|
types.uuid, int, wtypes.text, wtypes.text)
|
||||||
def get_all(self, marker=None, limit=None,
|
def get_all(self, goal=None, marker=None, limit=None,
|
||||||
sort_key='id', sort_dir='asc'):
|
sort_key='id', sort_dir='asc'):
|
||||||
"""Retrieve a list of audit templates.
|
"""Retrieve a list of audit templates.
|
||||||
|
|
||||||
|
:param goal: goal name to filter by (case sensitive)
|
||||||
:param marker: pagination marker for large data sets.
|
:param marker: pagination marker for large data sets.
|
||||||
:param limit: maximum number of resources to return in a single result.
|
:param limit: maximum number of resources to return in a single result.
|
||||||
:param sort_key: column to sort results by. Default: id.
|
:param sort_key: column to sort results by. Default: id.
|
||||||
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
|
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
|
||||||
"""
|
"""
|
||||||
return self._get_audit_templates_collection(marker, limit, sort_key,
|
filters = api_utils.as_filters_dict(goal=goal)
|
||||||
sort_dir)
|
|
||||||
|
|
||||||
@wsme_pecan.wsexpose(AuditTemplateCollection, types.uuid, int,
|
return self._get_audit_templates_collection(
|
||||||
|
filters, marker, limit, sort_key, sort_dir)
|
||||||
|
|
||||||
|
@wsme_pecan.wsexpose(AuditTemplateCollection, wtypes.text, types.uuid, int,
|
||||||
wtypes.text, wtypes.text)
|
wtypes.text, wtypes.text)
|
||||||
def detail(self, marker=None, limit=None,
|
def detail(self, goal=None, marker=None, limit=None,
|
||||||
sort_key='id', sort_dir='asc'):
|
sort_key='id', sort_dir='asc'):
|
||||||
"""Retrieve a list of audit templates with detail.
|
"""Retrieve a list of audit templates with detail.
|
||||||
|
|
||||||
|
:param goal: goal name to filter by (case sensitive)
|
||||||
:param marker: pagination marker for large data sets.
|
:param marker: pagination marker for large data sets.
|
||||||
:param limit: maximum number of resources to return in a single result.
|
:param limit: maximum number of resources to return in a single result.
|
||||||
:param sort_key: column to sort results by. Default: id.
|
:param sort_key: column to sort results by. Default: id.
|
||||||
@@ -219,9 +280,11 @@ class AuditTemplatesController(rest.RestController):
|
|||||||
if parent != "audit_templates":
|
if parent != "audit_templates":
|
||||||
raise exception.HTTPNotFound
|
raise exception.HTTPNotFound
|
||||||
|
|
||||||
|
filters = api_utils.as_filters_dict(goal=goal)
|
||||||
|
|
||||||
expand = True
|
expand = True
|
||||||
resource_url = '/'.join(['audit_templates', 'detail'])
|
resource_url = '/'.join(['audit_templates', 'detail'])
|
||||||
return self._get_audit_templates_collection(marker, limit,
|
return self._get_audit_templates_collection(filters, marker, limit,
|
||||||
sort_key, sort_dir, expand,
|
sort_key, sort_dir, expand,
|
||||||
resource_url)
|
resource_url)
|
||||||
|
|
||||||
@@ -229,7 +292,7 @@ class AuditTemplatesController(rest.RestController):
|
|||||||
def get_one(self, audit_template):
|
def get_one(self, audit_template):
|
||||||
"""Retrieve information about the given audit template.
|
"""Retrieve information about the given audit template.
|
||||||
|
|
||||||
:param audit template_uuid: UUID or name of an audit template.
|
:param audit audit_template: UUID or name of an audit template.
|
||||||
"""
|
"""
|
||||||
if self.from_audit_templates:
|
if self.from_audit_templates:
|
||||||
raise exception.OperationNotPermitted
|
raise exception.OperationNotPermitted
|
||||||
@@ -245,12 +308,14 @@ class AuditTemplatesController(rest.RestController):
|
|||||||
|
|
||||||
return AuditTemplate.convert_with_links(rpc_audit_template)
|
return AuditTemplate.convert_with_links(rpc_audit_template)
|
||||||
|
|
||||||
|
@wsme.validate(types.uuid, AuditTemplate)
|
||||||
@wsme_pecan.wsexpose(AuditTemplate, body=AuditTemplate, status_code=201)
|
@wsme_pecan.wsexpose(AuditTemplate, body=AuditTemplate, status_code=201)
|
||||||
def post(self, audit_template):
|
def post(self, audit_template):
|
||||||
"""Create a new audit template.
|
"""Create a new audit template.
|
||||||
|
|
||||||
:param audit template: a audit template within the request body.
|
:param audit template: a audit template within the request body.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.from_audit_templates:
|
if self.from_audit_templates:
|
||||||
raise exception.OperationNotPermitted
|
raise exception.OperationNotPermitted
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,23 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
"""
|
||||||
|
A :ref:`Goal <goal_definition>` is a human readable, observable and measurable
|
||||||
|
end result having one objective to be achieved.
|
||||||
|
|
||||||
|
Here are some examples of :ref:`Goals <goal_definition>`:
|
||||||
|
|
||||||
|
- minimize the energy consumption
|
||||||
|
- minimize the number of compute nodes (consolidation)
|
||||||
|
- balance the workload among compute nodes
|
||||||
|
- minimize the license cost (some softwares have a licensing model which is
|
||||||
|
based on the number of sockets or cores where the software is deployed)
|
||||||
|
- find the most appropriate moment for a planned maintenance on a
|
||||||
|
given group of host (which may be an entire availability zone):
|
||||||
|
power supply replacement, cooling system replacement, hardware
|
||||||
|
modification, ...
|
||||||
|
"""
|
||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
|
||||||
import pecan
|
import pecan
|
||||||
@@ -94,6 +111,7 @@ class GoalCollection(collection.Collection):
|
|||||||
"""A list containing goals objects"""
|
"""A list containing goals objects"""
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
|
super(GoalCollection, self).__init__()
|
||||||
self._type = 'goals'
|
self._type = 'goals'
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -141,7 +159,7 @@ class GoalsController(rest.RestController):
|
|||||||
resource_url=None, goal_name=None):
|
resource_url=None, goal_name=None):
|
||||||
|
|
||||||
limit = api_utils.validate_limit(limit)
|
limit = api_utils.validate_limit(limit)
|
||||||
sort_dir = api_utils.validate_sort_dir(sort_dir)
|
api_utils.validate_sort_dir(sort_dir)
|
||||||
|
|
||||||
goals = []
|
goals = []
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,15 @@ def validate_sort_dir(sort_dir):
|
|||||||
raise wsme.exc.ClientSideError(_("Invalid sort direction: %s. "
|
raise wsme.exc.ClientSideError(_("Invalid sort direction: %s. "
|
||||||
"Acceptable values are "
|
"Acceptable values are "
|
||||||
"'asc' or 'desc'") % sort_dir)
|
"'asc' or 'desc'") % sort_dir)
|
||||||
return sort_dir
|
|
||||||
|
|
||||||
|
def validate_search_filters(filters, allowed_fields):
|
||||||
|
# Very leightweight validation for now
|
||||||
|
# todo: improve this (e.g. https://www.parse.com/docs/rest/guide/#queries)
|
||||||
|
for filter_name in filters.keys():
|
||||||
|
if filter_name not in allowed_fields:
|
||||||
|
raise wsme.exc.ClientSideError(
|
||||||
|
_("Invalid filter: %s") % filter_name)
|
||||||
|
|
||||||
|
|
||||||
def apply_jsonpatch(doc, patch):
|
def apply_jsonpatch(doc, patch):
|
||||||
@@ -58,3 +66,12 @@ def apply_jsonpatch(doc, patch):
|
|||||||
' the resource is not allowed')
|
' the resource is not allowed')
|
||||||
raise wsme.exc.ClientSideError(msg % p['path'])
|
raise wsme.exc.ClientSideError(msg % p['path'])
|
||||||
return jsonpatch.apply_patch(doc, jsonpatch.JsonPatch(patch))
|
return jsonpatch.apply_patch(doc, jsonpatch.JsonPatch(patch))
|
||||||
|
|
||||||
|
|
||||||
|
def as_filters_dict(**filters):
|
||||||
|
filters_dict = {}
|
||||||
|
for filter_name, filter_value in filters.items():
|
||||||
|
if filter_value:
|
||||||
|
filters_dict[filter_name] = filter_value
|
||||||
|
|
||||||
|
return filters_dict
|
||||||
|
|||||||
@@ -69,8 +69,7 @@ class ParsableErrorMiddleware(object):
|
|||||||
if (state['status_code'] // 100) not in (2, 3):
|
if (state['status_code'] // 100) not in (2, 3):
|
||||||
req = webob.Request(environ)
|
req = webob.Request(environ)
|
||||||
if (req.accept.best_match(['application/json', 'application/xml']
|
if (req.accept.best_match(['application/json', 'application/xml']
|
||||||
) == 'application/xml'
|
) == 'application/xml'):
|
||||||
):
|
|
||||||
try:
|
try:
|
||||||
# simple check xml is valid
|
# simple check xml is valid
|
||||||
body = [et.ElementTree.tostring(
|
body = [et.ElementTree.tostring(
|
||||||
|
|||||||
@@ -18,51 +18,50 @@
|
|||||||
#
|
#
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
|
|
||||||
from watcher.applier.action_plan.base import BaseActionPlanHandler
|
from watcher.applier.action_plan import base
|
||||||
from watcher.applier.default import DefaultApplier
|
from watcher.applier import default
|
||||||
from watcher.applier.messaging.events import Events
|
from watcher.applier.messaging import event_types
|
||||||
from watcher.common.messaging.events.event import Event
|
from watcher.common.messaging.events import event
|
||||||
from watcher.objects.action_plan import ActionPlan
|
from watcher.objects import action_plan as ap_objects
|
||||||
from watcher.objects.action_plan import Status
|
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class DefaultActionPlanHandler(BaseActionPlanHandler):
|
class DefaultActionPlanHandler(base.BaseActionPlanHandler):
|
||||||
def __init__(self, context, manager_applier, action_plan_uuid):
|
def __init__(self, context, applier_manager, action_plan_uuid):
|
||||||
super(DefaultActionPlanHandler, self).__init__()
|
super(DefaultActionPlanHandler, self).__init__()
|
||||||
self.ctx = context
|
self.ctx = context
|
||||||
self.action_plan_uuid = action_plan_uuid
|
self.action_plan_uuid = action_plan_uuid
|
||||||
self.manager_applier = manager_applier
|
self.applier_manager = applier_manager
|
||||||
|
|
||||||
def notify(self, uuid, event_type, state):
|
def notify(self, uuid, event_type, state):
|
||||||
action_plan = ActionPlan.get_by_uuid(self.ctx, uuid)
|
action_plan = ap_objects.ActionPlan.get_by_uuid(self.ctx, uuid)
|
||||||
action_plan.state = state
|
action_plan.state = state
|
||||||
action_plan.save()
|
action_plan.save()
|
||||||
event = Event()
|
ev = event.Event()
|
||||||
event.type = event_type
|
ev.type = event_type
|
||||||
event.data = {}
|
ev.data = {}
|
||||||
payload = {'action_plan__uuid': uuid,
|
payload = {'action_plan__uuid': uuid,
|
||||||
'action_plan_state': state}
|
'action_plan_state': state}
|
||||||
self.manager_applier.topic_status.publish_event(event.type.name,
|
self.applier_manager.status_topic_handler.publish_event(
|
||||||
payload)
|
ev.type.name, payload)
|
||||||
|
|
||||||
def execute(self):
|
def execute(self):
|
||||||
try:
|
try:
|
||||||
# update state
|
# update state
|
||||||
self.notify(self.action_plan_uuid,
|
self.notify(self.action_plan_uuid,
|
||||||
Events.LAUNCH_ACTION_PLAN,
|
event_types.EventTypes.LAUNCH_ACTION_PLAN,
|
||||||
Status.ONGOING)
|
ap_objects.State.ONGOING)
|
||||||
applier = DefaultApplier(self.manager_applier, self.ctx)
|
applier = default.DefaultApplier(self.ctx, self.applier_manager)
|
||||||
result = applier.execute(self.action_plan_uuid)
|
applier.execute(self.action_plan_uuid)
|
||||||
|
state = ap_objects.State.SUCCEEDED
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
result = False
|
LOG.exception(e)
|
||||||
LOG.error("Launch Action Plan " + unicode(e))
|
state = ap_objects.State.FAILED
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
if result is True:
|
|
||||||
status = Status.SUCCEEDED
|
|
||||||
else:
|
|
||||||
status = Status.FAILED
|
|
||||||
# update state
|
# update state
|
||||||
self.notify(self.action_plan_uuid, Events.LAUNCH_ACTION_PLAN,
|
self.notify(self.action_plan_uuid,
|
||||||
status)
|
event_types.EventTypes.LAUNCH_ACTION_PLAN,
|
||||||
|
state)
|
||||||
|
|||||||
116
watcher/applier/actions/base.py
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
# Copyright (c) 2015 b<>com
|
||||||
|
#
|
||||||
|
# Authors: Jean-Emile DARTOIS <jean-emile.dartois@b-com.com>
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
# implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
import abc
|
||||||
|
|
||||||
|
import six
|
||||||
|
|
||||||
|
from watcher.common import clients
|
||||||
|
|
||||||
|
|
||||||
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
|
class BaseAction(object):
|
||||||
|
# NOTE(jed) by convention we decided
|
||||||
|
# that the attribute "resource_id" is the unique id of
|
||||||
|
# the resource to which the Action applies to allow us to use it in the
|
||||||
|
# watcher dashboard and will be nested in input_parameters
|
||||||
|
RESOURCE_ID = 'resource_id'
|
||||||
|
|
||||||
|
def __init__(self, osc=None):
|
||||||
|
""":param osc: an OpenStackClients instance"""
|
||||||
|
self._input_parameters = {}
|
||||||
|
self._osc = osc
|
||||||
|
|
||||||
|
@property
|
||||||
|
def osc(self):
|
||||||
|
if not self._osc:
|
||||||
|
self._osc = clients.OpenStackClients()
|
||||||
|
return self._osc
|
||||||
|
|
||||||
|
@property
|
||||||
|
def input_parameters(self):
|
||||||
|
return self._input_parameters
|
||||||
|
|
||||||
|
@input_parameters.setter
|
||||||
|
def input_parameters(self, p):
|
||||||
|
self._input_parameters = p
|
||||||
|
|
||||||
|
@property
|
||||||
|
def resource_id(self):
|
||||||
|
return self.input_parameters[self.RESOURCE_ID]
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def execute(self):
|
||||||
|
"""Executes the main logic of the action
|
||||||
|
|
||||||
|
This method can be used to perform an action on a given set of input
|
||||||
|
parameters to accomplish some type of operation. This operation may
|
||||||
|
return a boolean value as a result of its execution. If False, this
|
||||||
|
will be considered as an error and will then trigger the reverting of
|
||||||
|
the actions.
|
||||||
|
|
||||||
|
:returns: A flag indicating whether or not the action succeeded
|
||||||
|
:rtype: bool
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def revert(self):
|
||||||
|
"""Revert this action
|
||||||
|
|
||||||
|
This method should rollback the resource to its initial state in the
|
||||||
|
event of a faulty execution. This happens when the action raised an
|
||||||
|
exception during its :py:meth:`~.BaseAction.execute`.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def precondition(self):
|
||||||
|
"""Hook: called before the execution of an action
|
||||||
|
|
||||||
|
This method can be used to perform some initializations or to make
|
||||||
|
some more advanced validation on its input parameters. So if you wish
|
||||||
|
to block its execution based on this factor, `raise` the related
|
||||||
|
exception.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def postcondition(self):
|
||||||
|
"""Hook: called after the execution of an action
|
||||||
|
|
||||||
|
This function is called regardless of whether an action succeded or
|
||||||
|
not. So you can use it to perform cleanup operations.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
@abc.abstractproperty
|
||||||
|
def schema(self):
|
||||||
|
"""Defines a Schema that the input parameters shall comply to
|
||||||
|
|
||||||
|
:returns: A schema declaring the input parameters this action should be
|
||||||
|
provided along with their respective constraints
|
||||||
|
:rtype: :py:class:`voluptuous.Schema` instance
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def validate_parameters(self):
|
||||||
|
self.schema(self.input_parameters)
|
||||||
|
return True
|
||||||
102
watcher/applier/actions/change_nova_service_state.py
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
# Copyright (c) 2015 b<>com
|
||||||
|
#
|
||||||
|
# Authors: Jean-Emile DARTOIS <jean-emile.dartois@b-com.com>
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
# implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
import six
|
||||||
|
import voluptuous
|
||||||
|
|
||||||
|
from watcher._i18n import _
|
||||||
|
from watcher.applier.actions import base
|
||||||
|
from watcher.common import exception
|
||||||
|
from watcher.common import nova_helper
|
||||||
|
from watcher.decision_engine.model import hypervisor_state as hstate
|
||||||
|
|
||||||
|
|
||||||
|
class ChangeNovaServiceState(base.BaseAction):
|
||||||
|
"""Disables or enables the nova-compute service, deployed on a host
|
||||||
|
|
||||||
|
By using this action, you will be able to update the state of a
|
||||||
|
nova-compute service. A disabled nova-compute service can not be selected
|
||||||
|
by the nova scheduler for future deployment of server.
|
||||||
|
|
||||||
|
The action schema is::
|
||||||
|
|
||||||
|
schema = Schema({
|
||||||
|
'resource_id': str,
|
||||||
|
'state': str,
|
||||||
|
})
|
||||||
|
|
||||||
|
The `resource_id` references a nova-compute service name (list of available
|
||||||
|
nova-compute services is returned by this command: ``nova service-list
|
||||||
|
--binary nova-compute``).
|
||||||
|
The `state` value should either be `ONLINE` or `OFFLINE`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
STATE = 'state'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def schema(self):
|
||||||
|
return voluptuous.Schema({
|
||||||
|
voluptuous.Required(self.RESOURCE_ID):
|
||||||
|
voluptuous.All(
|
||||||
|
voluptuous.Any(*six.string_types),
|
||||||
|
voluptuous.Length(min=1)),
|
||||||
|
voluptuous.Required(self.STATE):
|
||||||
|
voluptuous.Any(*[state.value
|
||||||
|
for state in list(hstate.HypervisorState)]),
|
||||||
|
})
|
||||||
|
|
||||||
|
@property
|
||||||
|
def host(self):
|
||||||
|
return self.resource_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
return self.input_parameters.get(self.STATE)
|
||||||
|
|
||||||
|
def execute(self):
|
||||||
|
target_state = None
|
||||||
|
if self.state == hstate.HypervisorState.DISABLED.value:
|
||||||
|
target_state = False
|
||||||
|
elif self.state == hstate.HypervisorState.ENABLED.value:
|
||||||
|
target_state = True
|
||||||
|
return self._nova_manage_service(target_state)
|
||||||
|
|
||||||
|
def revert(self):
|
||||||
|
target_state = None
|
||||||
|
if self.state == hstate.HypervisorState.DISABLED.value:
|
||||||
|
target_state = True
|
||||||
|
elif self.state == hstate.HypervisorState.ENABLED.value:
|
||||||
|
target_state = False
|
||||||
|
return self._nova_manage_service(target_state)
|
||||||
|
|
||||||
|
def _nova_manage_service(self, state):
|
||||||
|
if state is None:
|
||||||
|
raise exception.IllegalArgumentException(
|
||||||
|
message=_("The target state is not defined"))
|
||||||
|
|
||||||
|
nova = nova_helper.NovaHelper(osc=self.osc)
|
||||||
|
if state is True:
|
||||||
|
return nova.enable_service_nova_compute(self.host)
|
||||||
|
else:
|
||||||
|
return nova.disable_service_nova_compute(self.host)
|
||||||
|
|
||||||
|
def precondition(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def postcondition(self):
|
||||||
|
pass
|
||||||
42
watcher/applier/actions/factory.py
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
# Copyright (c) 2016 b<>com
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
# implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from oslo_log import log
|
||||||
|
|
||||||
|
from watcher.applier.actions.loading import default
|
||||||
|
|
||||||
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ActionFactory(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.action_loader = default.DefaultActionLoader()
|
||||||
|
|
||||||
|
def make_action(self, object_action, osc=None):
|
||||||
|
LOG.debug("Creating instance of %s", object_action.action_type)
|
||||||
|
loaded_action = self.action_loader.load(name=object_action.action_type,
|
||||||
|
osc=osc)
|
||||||
|
loaded_action.input_parameters = object_action.input_parameters
|
||||||
|
LOG.debug("Checking the input parameters")
|
||||||
|
# NOTE(jed) if we change the schema of an action and we try to reload
|
||||||
|
# an older version of the Action, the validation can fail.
|
||||||
|
# We need to add the versioning of an Action or a migration tool.
|
||||||
|
# We can also create an new Action which extends the previous one.
|
||||||
|
loaded_action.validate_parameters()
|
||||||
|
return loaded_action
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
# -*- encoding: utf-8 -*-
|
||||||
# Copyright (c) 2015 b<>com
|
# Copyright (c) 2015 b<>com
|
||||||
#
|
#
|
||||||
# Authors: Jean-Emile DARTOIS <jean-emile.dartois@b-com.com>
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
# You may obtain a copy of the License at
|
# You may obtain a copy of the License at
|
||||||
@@ -17,10 +15,15 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from watcher.decision_engine.actions.base import BaseAction
|
from oslo_log import log
|
||||||
|
|
||||||
|
from watcher.common.loader import default
|
||||||
|
|
||||||
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Nop(BaseAction):
|
class DefaultActionLoader(default.DefaultLoader):
|
||||||
def __str__(self):
|
def __init__(self):
|
||||||
return "Nop"
|
super(DefaultActionLoader, self).__init__(namespace='watcher_actions')
|
||||||
159
watcher/applier/actions/migration.py
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
# Copyright (c) 2015 b<>com
|
||||||
|
#
|
||||||
|
# Authors: Jean-Emile DARTOIS <jean-emile.dartois@b-com.com>
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
# implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
from oslo_log import log
|
||||||
|
import six
|
||||||
|
import voluptuous
|
||||||
|
|
||||||
|
from watcher._i18n import _, _LC
|
||||||
|
from watcher.applier.actions import base
|
||||||
|
from watcher.common import exception
|
||||||
|
from watcher.common import nova_helper
|
||||||
|
from watcher.common import utils
|
||||||
|
|
||||||
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Migrate(base.BaseAction):
|
||||||
|
"""Live-Migrates a server to a destination nova-compute host
|
||||||
|
|
||||||
|
This action will allow you to migrate a server to another compute
|
||||||
|
destination host. As of now, only live migration can be performed using
|
||||||
|
this action.
|
||||||
|
.. If either host uses shared storage, you can use ``live``
|
||||||
|
.. as ``migration_type``. If both source and destination hosts provide
|
||||||
|
.. local disks, you can set the block_migration parameter to True (not
|
||||||
|
.. supported for yet).
|
||||||
|
|
||||||
|
The action schema is::
|
||||||
|
|
||||||
|
schema = Schema({
|
||||||
|
'resource_id': str, # should be a UUID
|
||||||
|
'migration_type': str, # choices -> "live" only
|
||||||
|
'dst_hypervisor': str,
|
||||||
|
'src_hypervisor': str,
|
||||||
|
})
|
||||||
|
|
||||||
|
The `resource_id` is the UUID of the server to migrate. Only live migration
|
||||||
|
is supported.
|
||||||
|
The `src_hypervisor` and `dst_hypervisor` parameters are respectively the
|
||||||
|
source and the destination compute hostname (list of available compute
|
||||||
|
hosts is returned by this command: ``nova service-list --binary
|
||||||
|
nova-compute``).
|
||||||
|
"""
|
||||||
|
|
||||||
|
# input parameters constants
|
||||||
|
MIGRATION_TYPE = 'migration_type'
|
||||||
|
LIVE_MIGRATION = 'live'
|
||||||
|
DST_HYPERVISOR = 'dst_hypervisor'
|
||||||
|
SRC_HYPERVISOR = 'src_hypervisor'
|
||||||
|
|
||||||
|
def check_resource_id(self, value):
|
||||||
|
if (value is not None and
|
||||||
|
len(value) > 0 and not
|
||||||
|
utils.is_uuid_like(value)):
|
||||||
|
raise voluptuous.Invalid(_("The parameter"
|
||||||
|
" resource_id is invalid."))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def schema(self):
|
||||||
|
return voluptuous.Schema({
|
||||||
|
voluptuous.Required(self.RESOURCE_ID): self.check_resource_id,
|
||||||
|
voluptuous.Required(self.MIGRATION_TYPE,
|
||||||
|
default=self.LIVE_MIGRATION):
|
||||||
|
voluptuous.Any(*[self.LIVE_MIGRATION]),
|
||||||
|
voluptuous.Required(self.DST_HYPERVISOR):
|
||||||
|
voluptuous.All(voluptuous.Any(*six.string_types),
|
||||||
|
voluptuous.Length(min=1)),
|
||||||
|
voluptuous.Required(self.SRC_HYPERVISOR):
|
||||||
|
voluptuous.All(voluptuous.Any(*six.string_types),
|
||||||
|
voluptuous.Length(min=1)),
|
||||||
|
})
|
||||||
|
|
||||||
|
@property
|
||||||
|
def instance_uuid(self):
|
||||||
|
return self.resource_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def migration_type(self):
|
||||||
|
return self.input_parameters.get(self.MIGRATION_TYPE)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def dst_hypervisor(self):
|
||||||
|
return self.input_parameters.get(self.DST_HYPERVISOR)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def src_hypervisor(self):
|
||||||
|
return self.input_parameters.get(self.SRC_HYPERVISOR)
|
||||||
|
|
||||||
|
def _live_migrate_instance(self, nova, destination):
|
||||||
|
result = None
|
||||||
|
try:
|
||||||
|
result = nova.live_migrate_instance(instance_id=self.instance_uuid,
|
||||||
|
dest_hostname=destination)
|
||||||
|
except nova_helper.nvexceptions.ClientException as e:
|
||||||
|
if e.code == 400:
|
||||||
|
LOG.debug("Live migration of instance %s failed. "
|
||||||
|
"Trying to live migrate using block migration."
|
||||||
|
% self.instance_uuid)
|
||||||
|
result = nova.live_migrate_instance(
|
||||||
|
instance_id=self.instance_uuid,
|
||||||
|
dest_hostname=destination,
|
||||||
|
block_migration=True)
|
||||||
|
else:
|
||||||
|
LOG.debug("Nova client exception occured while live migrating "
|
||||||
|
"instance %s.Exception: %s" %
|
||||||
|
(self.instance_uuid, e))
|
||||||
|
except Exception:
|
||||||
|
LOG.critical(_LC("Unexpected error occured. Migration failed for"
|
||||||
|
"instance %s. Leaving instance on previous "
|
||||||
|
"host."), self.instance_uuid)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def migrate(self, destination):
|
||||||
|
nova = nova_helper.NovaHelper(osc=self.osc)
|
||||||
|
LOG.debug("Migrate instance %s to %s", self.instance_uuid,
|
||||||
|
destination)
|
||||||
|
instance = nova.find_instance(self.instance_uuid)
|
||||||
|
if instance:
|
||||||
|
if self.migration_type == 'live':
|
||||||
|
return self._live_migrate_instance(nova, destination)
|
||||||
|
else:
|
||||||
|
raise exception.Invalid(
|
||||||
|
message=(_('Migration of type %(migration_type)s is not '
|
||||||
|
'supported.') %
|
||||||
|
{'migration_type': self.migration_type}))
|
||||||
|
else:
|
||||||
|
raise exception.InstanceNotFound(name=self.instance_uuid)
|
||||||
|
|
||||||
|
def execute(self):
|
||||||
|
return self.migrate(destination=self.dst_hypervisor)
|
||||||
|
|
||||||
|
def revert(self):
|
||||||
|
return self.migrate(destination=self.src_hypervisor)
|
||||||
|
|
||||||
|
def precondition(self):
|
||||||
|
# todo(jed) check if the instance exist/ check if the instance is on
|
||||||
|
# the src_hypervisor
|
||||||
|
pass
|
||||||
|
|
||||||
|
def postcondition(self):
|
||||||
|
# todo(jed) we can image to check extra parameters (nework reponse,ect)
|
||||||
|
pass
|
||||||
67
watcher/applier/actions/nop.py
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
# Copyright (c) 2015 b<>com
|
||||||
|
#
|
||||||
|
# Authors: Jean-Emile DARTOIS <jean-emile.dartois@b-com.com>
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
# implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
from oslo_log import log
|
||||||
|
import six
|
||||||
|
import voluptuous
|
||||||
|
|
||||||
|
from watcher.applier.actions import base
|
||||||
|
|
||||||
|
|
||||||
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Nop(base.BaseAction):
|
||||||
|
"""logs a message
|
||||||
|
|
||||||
|
The action schema is::
|
||||||
|
|
||||||
|
schema = Schema({
|
||||||
|
'message': str,
|
||||||
|
})
|
||||||
|
|
||||||
|
The `message` is the actual message that will be logged.
|
||||||
|
"""
|
||||||
|
|
||||||
|
MESSAGE = 'message'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def schema(self):
|
||||||
|
return voluptuous.Schema({
|
||||||
|
voluptuous.Required(self.MESSAGE): voluptuous.Any(
|
||||||
|
voluptuous.Any(*six.string_types), None)
|
||||||
|
})
|
||||||
|
|
||||||
|
@property
|
||||||
|
def message(self):
|
||||||
|
return self.input_parameters.get(self.MESSAGE)
|
||||||
|
|
||||||
|
def execute(self):
|
||||||
|
LOG.debug("executing action NOP message:%s ", self.message)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def revert(self):
|
||||||
|
LOG.debug("revert action NOP")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def precondition(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def postcondition(self):
|
||||||
|
pass
|
||||||
68
watcher/applier/actions/sleep.py
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
# Copyright (c) 2015 b<>com
|
||||||
|
#
|
||||||
|
# Authors: Jean-Emile DARTOIS <jean-emile.dartois@b-com.com>
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
# implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
from oslo_log import log
|
||||||
|
import voluptuous
|
||||||
|
|
||||||
|
from watcher.applier.actions import base
|
||||||
|
|
||||||
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Sleep(base.BaseAction):
|
||||||
|
"""Makes the executor of the action plan wait for a given duration
|
||||||
|
|
||||||
|
The action schema is::
|
||||||
|
|
||||||
|
schema = Schema({
|
||||||
|
'duration': float,
|
||||||
|
})
|
||||||
|
|
||||||
|
The `duration` is expressed in seconds.
|
||||||
|
"""
|
||||||
|
|
||||||
|
DURATION = 'duration'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def schema(self):
|
||||||
|
return voluptuous.Schema({
|
||||||
|
voluptuous.Required(self.DURATION, default=1):
|
||||||
|
voluptuous.All(float, voluptuous.Range(min=0))
|
||||||
|
})
|
||||||
|
|
||||||
|
@property
|
||||||
|
def duration(self):
|
||||||
|
return int(self.input_parameters.get(self.DURATION))
|
||||||
|
|
||||||
|
def execute(self):
|
||||||
|
LOG.debug("Starting action Sleep duration:%s ", self.duration)
|
||||||
|
time.sleep(self.duration)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def revert(self):
|
||||||
|
LOG.debug("revert action Sleep")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def precondition(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def postcondition(self):
|
||||||
|
pass
|
||||||
@@ -17,6 +17,14 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
"""
|
||||||
|
This component is in charge of executing the
|
||||||
|
:ref:`Action Plan <action_plan_definition>` built by the
|
||||||
|
:ref:`Watcher Decision Engine <watcher_decision_engine_definition>`.
|
||||||
|
|
||||||
|
See: :doc:`../architecture` for more details on this component.
|
||||||
|
"""
|
||||||
|
|
||||||
import abc
|
import abc
|
||||||
import six
|
import six
|
||||||
|
|
||||||
|
|||||||
@@ -16,25 +16,49 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
from oslo_config import cfg
|
||||||
|
from oslo_log import log
|
||||||
|
|
||||||
from watcher.applier.base import BaseApplier
|
from watcher.applier import base
|
||||||
from watcher.applier.execution.executor import ActionPlanExecutor
|
from watcher.applier.workflow_engine.loading import default
|
||||||
from watcher.objects import Action
|
from watcher import objects
|
||||||
from watcher.objects import ActionPlan
|
|
||||||
|
LOG = log.getLogger(__name__)
|
||||||
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
class DefaultApplier(BaseApplier):
|
class DefaultApplier(base.BaseApplier):
|
||||||
def __init__(self, manager_applier, context):
|
def __init__(self, context, applier_manager):
|
||||||
super(DefaultApplier, self).__init__()
|
super(DefaultApplier, self).__init__()
|
||||||
self.manager_applier = manager_applier
|
self._applier_manager = applier_manager
|
||||||
self.context = context
|
self._loader = default.DefaultWorkFlowEngineLoader()
|
||||||
self.executor = ActionPlanExecutor(manager_applier, context)
|
self._engine = None
|
||||||
|
self._context = context
|
||||||
|
|
||||||
|
@property
|
||||||
|
def context(self):
|
||||||
|
return self._context
|
||||||
|
|
||||||
|
@property
|
||||||
|
def applier_manager(self):
|
||||||
|
return self._applier_manager
|
||||||
|
|
||||||
|
@property
|
||||||
|
def engine(self):
|
||||||
|
if self._engine is None:
|
||||||
|
selected_workflow_engine = CONF.watcher_applier.workflow_engine
|
||||||
|
LOG.debug("Loading workflow engine %s ", selected_workflow_engine)
|
||||||
|
self._engine = self._loader.load(
|
||||||
|
name=selected_workflow_engine,
|
||||||
|
context=self.context,
|
||||||
|
applier_manager=self.applier_manager)
|
||||||
|
return self._engine
|
||||||
|
|
||||||
def execute(self, action_plan_uuid):
|
def execute(self, action_plan_uuid):
|
||||||
action_plan = ActionPlan.get_by_uuid(self.context, action_plan_uuid)
|
LOG.debug("Executing action plan %s ", action_plan_uuid)
|
||||||
|
action_plan = objects.ActionPlan.get_by_uuid(self.context,
|
||||||
|
action_plan_uuid)
|
||||||
# todo(jed) remove direct access to dbapi need filter in object
|
# todo(jed) remove direct access to dbapi need filter in object
|
||||||
actions = Action.dbapi.get_action_list(self.context,
|
filters = {'action_plan_id': action_plan.id}
|
||||||
filters={
|
actions = objects.Action.dbapi.get_action_list(self.context, filters)
|
||||||
'action_plan_id':
|
return self.engine.execute(actions)
|
||||||
action_plan.id})
|
|
||||||
return self.executor.execute(actions)
|
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
# Copyright (c) 2015 b<>com
|
|
||||||
#
|
|
||||||
# Authors: Jean-Emile DARTOIS <jean-emile.dartois@b-com.com>
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
# implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
#
|
|
||||||
from oslo_log import log
|
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class DeployPhase(object):
|
|
||||||
def __init__(self, executor):
|
|
||||||
# todo(jed) oslo_conf 10 secondes
|
|
||||||
self._max_timeout = 100000
|
|
||||||
self._actions = []
|
|
||||||
self._executor = executor
|
|
||||||
|
|
||||||
@property
|
|
||||||
def actions(self):
|
|
||||||
return self._actions
|
|
||||||
|
|
||||||
@property
|
|
||||||
def max_timeout(self):
|
|
||||||
return self._max_timeout
|
|
||||||
|
|
||||||
@max_timeout.setter
|
|
||||||
def max_timeout(self, m):
|
|
||||||
self._max_timeout = m
|
|
||||||
|
|
||||||
def populate(self, action):
|
|
||||||
self._actions.append(action)
|
|
||||||
|
|
||||||
def execute_primitive(self, primitive):
|
|
||||||
future = primitive.execute(primitive)
|
|
||||||
return future.result(self.max_timeout)
|
|
||||||
|
|
||||||
def rollback(self):
|
|
||||||
reverted = sorted(self.actions, reverse=True)
|
|
||||||
for primitive in reverted:
|
|
||||||
try:
|
|
||||||
self.execute_primitive(primitive)
|
|
||||||
except Exception as e:
|
|
||||||
LOG.error(e)
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
# Copyright (c) 2015 b<>com
|
|
||||||
#
|
|
||||||
# Authors: Jean-Emile DARTOIS <jean-emile.dartois@b-com.com>
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
# implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
#
|
|
||||||
from oslo_log import log
|
|
||||||
from watcher.applier.execution.deploy_phase import DeployPhase
|
|
||||||
from watcher.applier.mapping.default import DefaultActionMapper
|
|
||||||
from watcher.applier.messaging.events import Events
|
|
||||||
from watcher.common.messaging.events.event import Event
|
|
||||||
from watcher.objects import Action
|
|
||||||
from watcher.objects.action_plan import Status
|
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class ActionPlanExecutor(object):
|
|
||||||
def __init__(self, manager_applier, context):
|
|
||||||
self.manager_applier = manager_applier
|
|
||||||
self.context = context
|
|
||||||
self.deploy = DeployPhase(self)
|
|
||||||
self.mapper = DefaultActionMapper()
|
|
||||||
|
|
||||||
def get_primitive(self, action):
|
|
||||||
return self.mapper.build_primitive_from_action(action)
|
|
||||||
|
|
||||||
def notify(self, action, state):
|
|
||||||
db_action = Action.get_by_uuid(self.context, action.uuid)
|
|
||||||
db_action.state = state
|
|
||||||
db_action.save()
|
|
||||||
event = Event()
|
|
||||||
event.type = Events.LAUNCH_ACTION
|
|
||||||
event.data = {}
|
|
||||||
payload = {'action_uuid': action.uuid,
|
|
||||||
'action_state': state}
|
|
||||||
self.manager_applier.topic_status.publish_event(event.type.name,
|
|
||||||
payload)
|
|
||||||
|
|
||||||
def execute(self, actions):
|
|
||||||
for action in actions:
|
|
||||||
try:
|
|
||||||
self.notify(action, Status.ONGOING)
|
|
||||||
primitive = self.get_primitive(action)
|
|
||||||
result = self.deploy.execute_primitive(primitive)
|
|
||||||
if result is False:
|
|
||||||
self.notify(action, Status.FAILED)
|
|
||||||
self.deploy.rollback()
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
self.deploy.populate(primitive)
|
|
||||||
self.notify(action, Status.SUCCEEDED)
|
|
||||||
except Exception as e:
|
|
||||||
LOG.debug(
|
|
||||||
'The applier module failed to execute the action{0} with '
|
|
||||||
'the exception {1} '.format(
|
|
||||||
action,
|
|
||||||
unicode(e)))
|
|
||||||
|
|
||||||
LOG.error("Trigger a rollback")
|
|
||||||
self.notify(action, Status.FAILED)
|
|
||||||
self.deploy.rollback()
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
@@ -16,26 +16,30 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
|
||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
|
|
||||||
from watcher.applier.messaging.trigger import TriggerActionPlan
|
from watcher.applier.messaging import trigger
|
||||||
from watcher.common.messaging.messaging_core import MessagingCore
|
from watcher.common.messaging import messaging_core
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
# Register options
|
# Register options
|
||||||
APPLIER_MANAGER_OPTS = [
|
APPLIER_MANAGER_OPTS = [
|
||||||
cfg.IntOpt('applier_worker', default='1', help='The number of worker'),
|
cfg.IntOpt('workers',
|
||||||
cfg.StrOpt('topic_control',
|
default='1',
|
||||||
|
min=1,
|
||||||
|
required=True,
|
||||||
|
help='Number of workers for applier, default value is 1.'),
|
||||||
|
cfg.StrOpt('conductor_topic',
|
||||||
default='watcher.applier.control',
|
default='watcher.applier.control',
|
||||||
help='The topic name used for'
|
help='The topic name used for'
|
||||||
'control events, this topic '
|
'control events, this topic '
|
||||||
'used for rpc call '),
|
'used for rpc call '),
|
||||||
cfg.StrOpt('topic_status',
|
cfg.StrOpt('status_topic',
|
||||||
default='watcher.applier.status',
|
default='watcher.applier.status',
|
||||||
help='The topic name used for '
|
help='The topic name used for '
|
||||||
'status events, this topic '
|
'status events, this topic '
|
||||||
@@ -45,7 +49,11 @@ APPLIER_MANAGER_OPTS = [
|
|||||||
cfg.StrOpt('publisher_id',
|
cfg.StrOpt('publisher_id',
|
||||||
default='watcher.applier.api',
|
default='watcher.applier.api',
|
||||||
help='The identifier used by watcher '
|
help='The identifier used by watcher '
|
||||||
'module on the message broker')
|
'module on the message broker'),
|
||||||
|
cfg.StrOpt('workflow_engine',
|
||||||
|
default='taskflow',
|
||||||
|
required=True,
|
||||||
|
help='Select the engine to use to execute the workflow')
|
||||||
]
|
]
|
||||||
|
|
||||||
opt_group = cfg.OptGroup(name='watcher_applier',
|
opt_group = cfg.OptGroup(name='watcher_applier',
|
||||||
@@ -55,19 +63,17 @@ CONF.register_group(opt_group)
|
|||||||
CONF.register_opts(APPLIER_MANAGER_OPTS, opt_group)
|
CONF.register_opts(APPLIER_MANAGER_OPTS, opt_group)
|
||||||
|
|
||||||
|
|
||||||
class ApplierManager(MessagingCore):
|
class ApplierManager(messaging_core.MessagingCore):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(ApplierManager, self).__init__(
|
super(ApplierManager, self).__init__(
|
||||||
CONF.watcher_applier.publisher_id,
|
CONF.watcher_applier.publisher_id,
|
||||||
CONF.watcher_applier.topic_control,
|
CONF.watcher_applier.conductor_topic,
|
||||||
CONF.watcher_applier.topic_status,
|
CONF.watcher_applier.status_topic,
|
||||||
api_version=self.API_VERSION,
|
api_version=self.API_VERSION,
|
||||||
)
|
)
|
||||||
# shared executor of the workflow
|
self.conductor_topic_handler.add_endpoint(
|
||||||
self.executor = ThreadPoolExecutor(max_workers=1)
|
trigger.TriggerActionPlan(self))
|
||||||
# trigger action_plan
|
|
||||||
self.topic_control.add_endpoint(TriggerActionPlan(self))
|
|
||||||
|
|
||||||
def join(self):
|
def join(self):
|
||||||
self.topic_control.join()
|
self.conductor_topic_handler.join()
|
||||||
self.topic_status.join()
|
self.status_topic_handler.join()
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
# Copyright (c) 2015 b<>com
|
|
||||||
#
|
|
||||||
# Authors: Jean-Emile DARTOIS <jean-emile.dartois@b-com.com>
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
# implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
#
|
|
||||||
|
|
||||||
import abc
|
|
||||||
import six
|
|
||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(abc.ABCMeta)
|
|
||||||
class BaseActionMapper(object):
|
|
||||||
@abc.abstractmethod
|
|
||||||
def build_primitive_from_action(self, action):
|
|
||||||
"""Transform an action to a primitive
|
|
||||||
|
|
||||||
:type action: watcher.decision_engine.action.BaseAction
|
|
||||||
:return: the associated Primitive
|
|
||||||
"""
|
|
||||||
raise NotImplementedError()
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
# Copyright (c) 2015 b<>com
|
|
||||||
#
|
|
||||||
# Authors: Jean-Emile DARTOIS <jean-emile.dartois@b-com.com>
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
# implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
#
|
|
||||||
|
|
||||||
from watcher.applier.mapping.base import BaseActionMapper
|
|
||||||
from watcher.applier.primitives.change_nova_service_state import \
|
|
||||||
ChangeNovaServiceState
|
|
||||||
from watcher.applier.primitives.migration import Migrate
|
|
||||||
from watcher.applier.primitives.nop import Nop
|
|
||||||
from watcher.applier.primitives.power_state import ChangePowerState
|
|
||||||
from watcher.common.exception import ActionNotFound
|
|
||||||
from watcher.decision_engine.planner.default import Primitives
|
|
||||||
|
|
||||||
|
|
||||||
class DefaultActionMapper(BaseActionMapper):
|
|
||||||
def build_primitive_from_action(self, action):
|
|
||||||
if action.action_type == Primitives.COLD_MIGRATE.value:
|
|
||||||
return Migrate(action.applies_to, Primitives.COLD_MIGRATE,
|
|
||||||
action.src,
|
|
||||||
action.dst)
|
|
||||||
elif action.action_type == Primitives.LIVE_MIGRATE.value:
|
|
||||||
return Migrate(action.applies_to, Primitives.COLD_MIGRATE,
|
|
||||||
action.src,
|
|
||||||
action.dst)
|
|
||||||
elif action.action_type == Primitives.HYPERVISOR_STATE.value:
|
|
||||||
return ChangeNovaServiceState(action.applies_to, action.parameter)
|
|
||||||
elif action.action_type == Primitives.POWER_STATE.value:
|
|
||||||
return ChangePowerState()
|
|
||||||
elif action.action_type == Primitives.NOP.value:
|
|
||||||
return Nop()
|
|
||||||
else:
|
|
||||||
raise ActionNotFound()
|
|
||||||
@@ -17,9 +17,9 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
|
||||||
from enum import Enum
|
import enum
|
||||||
|
|
||||||
|
|
||||||
class Events(Enum):
|
class EventTypes(enum.Enum):
|
||||||
LAUNCH_ACTION_PLAN = "launch_action_plan"
|
LAUNCH_ACTION_PLAN = "launch_action_plan"
|
||||||
LAUNCH_ACTION = "launch_action"
|
LAUNCH_ACTION = "launch_action"
|
||||||
@@ -16,30 +16,35 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
from concurrent import futures
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
|
|
||||||
from watcher.applier.action_plan.default import DefaultActionPlanHandler
|
from watcher.applier.action_plan import default
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
class TriggerActionPlan(object):
|
class TriggerActionPlan(object):
|
||||||
def __init__(self, manager_applier):
|
def __init__(self, applier_manager):
|
||||||
self.manager_applier = manager_applier
|
self.applier_manager = applier_manager
|
||||||
|
workers = CONF.watcher_applier.workers
|
||||||
|
self.executor = futures.ThreadPoolExecutor(max_workers=workers)
|
||||||
|
|
||||||
def do_launch_action_plan(self, context, action_plan_uuid):
|
def do_launch_action_plan(self, context, action_plan_uuid):
|
||||||
try:
|
try:
|
||||||
cmd = DefaultActionPlanHandler(context,
|
cmd = default.DefaultActionPlanHandler(context,
|
||||||
self.manager_applier,
|
self.applier_manager,
|
||||||
action_plan_uuid)
|
action_plan_uuid)
|
||||||
cmd.execute()
|
cmd.execute()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.exception(e)
|
LOG.exception(e)
|
||||||
|
|
||||||
def launch_action_plan(self, context, action_plan_uuid):
|
def launch_action_plan(self, context, action_plan_uuid):
|
||||||
LOG.debug("Trigger ActionPlan %s" % action_plan_uuid)
|
LOG.debug("Trigger ActionPlan %s", action_plan_uuid)
|
||||||
# submit
|
# submit
|
||||||
self.manager_applier.executor.submit(self.do_launch_action_plan,
|
self.executor.submit(self.do_launch_action_plan, context,
|
||||||
context,
|
|
||||||
action_plan_uuid)
|
action_plan_uuid)
|
||||||
return action_plan_uuid
|
return action_plan_uuid
|
||||||
|
|||||||
@@ -1,83 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
# Copyright (c) 2015 b<>com
|
|
||||||
#
|
|
||||||
# Authors: Jean-Emile DARTOIS <jean-emile.dartois@b-com.com>
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
# implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
from oslo_config import cfg
|
|
||||||
|
|
||||||
|
|
||||||
from watcher._i18n import _
|
|
||||||
from watcher.applier.primitives.base import BasePrimitive
|
|
||||||
from watcher.applier.promise import Promise
|
|
||||||
from watcher.common.exception import IllegalArgumentException
|
|
||||||
from watcher.common.keystone import KeystoneClient
|
|
||||||
from watcher.common.nova import NovaClient
|
|
||||||
from watcher.decision_engine.model.hypervisor_state import HypervisorState
|
|
||||||
|
|
||||||
CONF = cfg.CONF
|
|
||||||
|
|
||||||
|
|
||||||
class ChangeNovaServiceState(BasePrimitive):
|
|
||||||
def __init__(self, host, state):
|
|
||||||
"""This class allows us to change the state of nova-compute service.
|
|
||||||
|
|
||||||
:param host: the uuid of the host
|
|
||||||
:param state: (enabled/disabled)
|
|
||||||
"""
|
|
||||||
super(BasePrimitive, self).__init__()
|
|
||||||
self._host = host
|
|
||||||
self._state = state
|
|
||||||
|
|
||||||
@property
|
|
||||||
def host(self):
|
|
||||||
return self._host
|
|
||||||
|
|
||||||
@property
|
|
||||||
def state(self):
|
|
||||||
return self._state
|
|
||||||
|
|
||||||
@Promise
|
|
||||||
def execute(self):
|
|
||||||
target_state = None
|
|
||||||
if self.state == HypervisorState.OFFLINE.value:
|
|
||||||
target_state = False
|
|
||||||
elif self.status == HypervisorState.ONLINE.value:
|
|
||||||
target_state = True
|
|
||||||
return self.nova_manage_service(target_state)
|
|
||||||
|
|
||||||
@Promise
|
|
||||||
def undo(self):
|
|
||||||
target_state = None
|
|
||||||
if self.state == HypervisorState.OFFLINE.value:
|
|
||||||
target_state = True
|
|
||||||
elif self.state == HypervisorState.ONLINE.value:
|
|
||||||
target_state = False
|
|
||||||
return self.nova_manage_service(target_state)
|
|
||||||
|
|
||||||
def nova_manage_service(self, state):
|
|
||||||
if state is None:
|
|
||||||
raise IllegalArgumentException(
|
|
||||||
_("The target state is not defined"))
|
|
||||||
|
|
||||||
keystone = KeystoneClient()
|
|
||||||
wrapper = NovaClient(keystone.get_credentials(),
|
|
||||||
session=keystone.get_session())
|
|
||||||
if state is True:
|
|
||||||
return wrapper.enable_service_nova_compute(self.host)
|
|
||||||
else:
|
|
||||||
return wrapper.disable_service_nova_compute(self.host)
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
# Copyright (c) 2015 b<>com
|
|
||||||
#
|
|
||||||
# Authors: Jean-Emile DARTOIS <jean-emile.dartois@b-com.com>
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
# implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
#
|
|
||||||
|
|
||||||
from keystoneclient.auth.identity import v3
|
|
||||||
from keystoneclient import session
|
|
||||||
from oslo_config import cfg
|
|
||||||
|
|
||||||
from watcher.applier.primitives.base import BasePrimitive
|
|
||||||
from watcher.applier.promise import Promise
|
|
||||||
from watcher.common.keystone import KeystoneClient
|
|
||||||
from watcher.common.nova import NovaClient
|
|
||||||
from watcher.decision_engine.planner.default import Primitives
|
|
||||||
|
|
||||||
CONF = cfg.CONF
|
|
||||||
|
|
||||||
|
|
||||||
class Migrate(BasePrimitive):
|
|
||||||
def __init__(self, vm_uuid=None,
|
|
||||||
migration_type=None,
|
|
||||||
source_hypervisor=None,
|
|
||||||
destination_hypervisor=None):
|
|
||||||
super(BasePrimitive, self).__init__()
|
|
||||||
self.instance_uuid = vm_uuid
|
|
||||||
self.migration_type = migration_type
|
|
||||||
self.source_hypervisor = source_hypervisor
|
|
||||||
self.destination_hypervisor = destination_hypervisor
|
|
||||||
|
|
||||||
def migrate(self, destination):
|
|
||||||
keystone = KeystoneClient()
|
|
||||||
wrapper = NovaClient(keystone.get_credentials(),
|
|
||||||
session=keystone.get_session())
|
|
||||||
instance = wrapper.find_instance(self.instance_uuid)
|
|
||||||
if instance:
|
|
||||||
project_id = getattr(instance, "tenant_id")
|
|
||||||
|
|
||||||
creds2 = \
|
|
||||||
{'auth_url': CONF.keystone_authtoken.auth_uri,
|
|
||||||
'username': CONF.keystone_authtoken.admin_user,
|
|
||||||
'password': CONF.keystone_authtoken.admin_password,
|
|
||||||
'project_id': project_id,
|
|
||||||
'user_domain_name': "default",
|
|
||||||
'project_domain_name': "default"}
|
|
||||||
auth2 = v3.Password(auth_url=creds2['auth_url'],
|
|
||||||
username=creds2['username'],
|
|
||||||
password=creds2['password'],
|
|
||||||
project_id=creds2['project_id'],
|
|
||||||
user_domain_name=creds2[
|
|
||||||
'user_domain_name'],
|
|
||||||
project_domain_name=creds2[
|
|
||||||
'project_domain_name'])
|
|
||||||
sess2 = session.Session(auth=auth2)
|
|
||||||
wrapper2 = NovaClient(creds2, session=sess2)
|
|
||||||
|
|
||||||
# todo(jed) remove Primitves
|
|
||||||
if self.migration_type is Primitives.COLD_MIGRATE:
|
|
||||||
return wrapper2.live_migrate_instance(
|
|
||||||
instance_id=self.instance_uuid,
|
|
||||||
dest_hostname=destination,
|
|
||||||
block_migration=True)
|
|
||||||
elif self.migration_type is Primitives.LIVE_MIGRATE:
|
|
||||||
return wrapper2.live_migrate_instance(
|
|
||||||
instance_id=self.instance_uuid,
|
|
||||||
dest_hostname=destination,
|
|
||||||
block_migration=False)
|
|
||||||
|
|
||||||
@Promise
|
|
||||||
def execute(self):
|
|
||||||
return self.migrate(self.destination_hypervisor)
|
|
||||||
|
|
||||||
@Promise
|
|
||||||
def undo(self):
|
|
||||||
return self.migrate(self.source_hypervisor)
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
# Copyright (c) 2015 b<>com
|
|
||||||
#
|
|
||||||
# Authors: Jean-Emile DARTOIS <jean-emile.dartois@b-com.com>
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
# implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
#
|
|
||||||
|
|
||||||
from oslo_log import log
|
|
||||||
|
|
||||||
|
|
||||||
from watcher.applier.primitives.base import BasePrimitive
|
|
||||||
from watcher.applier.promise import Promise
|
|
||||||
|
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class Nop(BasePrimitive):
|
|
||||||
|
|
||||||
@Promise
|
|
||||||
def execute(self):
|
|
||||||
LOG.debug("executing NOP command")
|
|
||||||
return True
|
|
||||||
|
|
||||||
@Promise
|
|
||||||
def undo(self):
|
|
||||||
LOG.debug("undo NOP command")
|
|
||||||
return True
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
# Copyright (c) 2015 b<>com
|
|
||||||
#
|
|
||||||
# Authors: Jean-Emile DARTOIS <jean-emile.dartois@b-com.com>
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
# implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
#
|
|
||||||
|
|
||||||
from watcher.applier.primitives.base import BasePrimitive
|
|
||||||
from watcher.applier.promise import Promise
|
|
||||||
|
|
||||||
|
|
||||||
class ChangePowerState(BasePrimitive):
|
|
||||||
|
|
||||||
@Promise
|
|
||||||
def execute(self):
|
|
||||||
raise NotImplementedError # pragma:no cover
|
|
||||||
|
|
||||||
@Promise
|
|
||||||
def undo(self):
|
|
||||||
raise NotImplementedError # pragma:no cover
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
# Copyright (c) 2015 b<>com
|
|
||||||
#
|
|
||||||
# Authors: Jean-Emile DARTOIS <jean-emile.dartois@b-com.com>
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
# implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
#
|
|
||||||
|
|
||||||
from concurrent.futures import Future
|
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
|
||||||
|
|
||||||
|
|
||||||
class Promise(object):
|
|
||||||
executor = ThreadPoolExecutor(
|
|
||||||
max_workers=10)
|
|
||||||
|
|
||||||
def __init__(self, func):
|
|
||||||
self.func = func
|
|
||||||
|
|
||||||
def resolve(self, *args, **kwargs):
|
|
||||||
resolved_args = []
|
|
||||||
resolved_kwargs = {}
|
|
||||||
|
|
||||||
for i, arg in enumerate(args):
|
|
||||||
if isinstance(arg, Future):
|
|
||||||
resolved_args.append(arg.result())
|
|
||||||
else:
|
|
||||||
resolved_args.append(arg)
|
|
||||||
|
|
||||||
for kw, arg in kwargs.items():
|
|
||||||
if isinstance(arg, Future):
|
|
||||||
resolved_kwargs[kw] = arg.result()
|
|
||||||
else:
|
|
||||||
resolved_kwargs[kw] = arg
|
|
||||||
|
|
||||||
return self.func(*resolved_args, **resolved_kwargs)
|
|
||||||
|
|
||||||
def __call__(self, *args, **kwargs):
|
|
||||||
return self.executor.submit(self.resolve, *args, **kwargs)
|
|
||||||
@@ -23,8 +23,8 @@ import oslo_messaging as om
|
|||||||
from watcher.applier.manager import APPLIER_MANAGER_OPTS
|
from watcher.applier.manager import APPLIER_MANAGER_OPTS
|
||||||
from watcher.applier.manager import opt_group
|
from watcher.applier.manager import opt_group
|
||||||
from watcher.common import exception
|
from watcher.common import exception
|
||||||
from watcher.common.messaging.messaging_core import MessagingCore
|
from watcher.common.messaging import messaging_core
|
||||||
from watcher.common.messaging.notification_handler import NotificationHandler
|
from watcher.common.messaging import notification_handler as notification
|
||||||
from watcher.common import utils
|
from watcher.common import utils
|
||||||
|
|
||||||
|
|
||||||
@@ -34,22 +34,22 @@ CONF.register_group(opt_group)
|
|||||||
CONF.register_opts(APPLIER_MANAGER_OPTS, opt_group)
|
CONF.register_opts(APPLIER_MANAGER_OPTS, opt_group)
|
||||||
|
|
||||||
|
|
||||||
class ApplierAPI(MessagingCore):
|
class ApplierAPI(messaging_core.MessagingCore):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(ApplierAPI, self).__init__(
|
super(ApplierAPI, self).__init__(
|
||||||
CONF.watcher_applier.publisher_id,
|
CONF.watcher_applier.publisher_id,
|
||||||
CONF.watcher_applier.topic_control,
|
CONF.watcher_applier.conductor_topic,
|
||||||
CONF.watcher_applier.topic_status,
|
CONF.watcher_applier.status_topic,
|
||||||
api_version=self.API_VERSION,
|
api_version=self.API_VERSION,
|
||||||
)
|
)
|
||||||
self.handler = NotificationHandler(self.publisher_id)
|
self.handler = notification.NotificationHandler(self.publisher_id)
|
||||||
self.handler.register_observer(self)
|
self.handler.register_observer(self)
|
||||||
self.topic_status.add_endpoint(self.handler)
|
self.status_topic_handler.add_endpoint(self.handler)
|
||||||
transport = om.get_transport(CONF)
|
transport = om.get_transport(CONF)
|
||||||
|
|
||||||
target = om.Target(
|
target = om.Target(
|
||||||
topic=CONF.watcher_applier.topic_control,
|
topic=CONF.watcher_applier.conductor_topic,
|
||||||
version=self.API_VERSION,
|
version=self.API_VERSION,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -63,10 +63,3 @@ class ApplierAPI(MessagingCore):
|
|||||||
return self.client.call(
|
return self.client.call(
|
||||||
context.to_dict(), 'launch_action_plan',
|
context.to_dict(), 'launch_action_plan',
|
||||||
action_plan_uuid=action_plan_uuid)
|
action_plan_uuid=action_plan_uuid)
|
||||||
|
|
||||||
def event_receive(self, event):
|
|
||||||
try:
|
|
||||||
pass
|
|
||||||
except Exception as e:
|
|
||||||
LOG.exception(e)
|
|
||||||
raise
|
|
||||||
|
|||||||
70
watcher/applier/workflow_engine/base.py
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
# Copyright (c) 2016 b<>com
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
# implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
import abc
|
||||||
|
|
||||||
|
import six
|
||||||
|
|
||||||
|
from watcher.applier.actions import factory
|
||||||
|
from watcher.applier.messaging import event_types
|
||||||
|
from watcher.common import clients
|
||||||
|
from watcher.common.messaging.events import event
|
||||||
|
from watcher import objects
|
||||||
|
|
||||||
|
|
||||||
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
|
class BaseWorkFlowEngine(object):
|
||||||
|
def __init__(self, context=None, applier_manager=None):
|
||||||
|
self._context = context
|
||||||
|
self._applier_manager = applier_manager
|
||||||
|
self._action_factory = factory.ActionFactory()
|
||||||
|
self._osc = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def context(self):
|
||||||
|
return self._context
|
||||||
|
|
||||||
|
@property
|
||||||
|
def osc(self):
|
||||||
|
if not self._osc:
|
||||||
|
self._osc = clients.OpenStackClients()
|
||||||
|
return self._osc
|
||||||
|
|
||||||
|
@property
|
||||||
|
def applier_manager(self):
|
||||||
|
return self._applier_manager
|
||||||
|
|
||||||
|
@property
|
||||||
|
def action_factory(self):
|
||||||
|
return self._action_factory
|
||||||
|
|
||||||
|
def notify(self, action, state):
|
||||||
|
db_action = objects.Action.get_by_uuid(self.context, action.uuid)
|
||||||
|
db_action.state = state
|
||||||
|
db_action.save()
|
||||||
|
ev = event.Event()
|
||||||
|
ev.type = event_types.EventTypes.LAUNCH_ACTION
|
||||||
|
ev.data = {}
|
||||||
|
payload = {'action_uuid': action.uuid,
|
||||||
|
'action_state': state}
|
||||||
|
self.applier_manager.status_topic_handler.publish_event(
|
||||||
|
ev.type.name, payload)
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def execute(self, actions):
|
||||||
|
raise NotImplementedError()
|
||||||
162
watcher/applier/workflow_engine/default.py
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
# Copyright (c) 2016 b<>com
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
# implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
from oslo_log import log
|
||||||
|
from taskflow import engines
|
||||||
|
from taskflow.patterns import graph_flow as gf
|
||||||
|
from taskflow import task
|
||||||
|
|
||||||
|
from watcher._i18n import _LE, _LW, _LC
|
||||||
|
from watcher.applier.workflow_engine import base
|
||||||
|
from watcher.common import exception
|
||||||
|
from watcher.objects import action as obj_action
|
||||||
|
|
||||||
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class DefaultWorkFlowEngine(base.BaseWorkFlowEngine):
|
||||||
|
"""Taskflow as a workflow engine for Watcher
|
||||||
|
|
||||||
|
Full documentation on taskflow at
|
||||||
|
http://docs.openstack.org/developer/taskflow/
|
||||||
|
"""
|
||||||
|
|
||||||
|
def decider(self, history):
|
||||||
|
# FIXME(jed) not possible with the current Watcher Planner
|
||||||
|
#
|
||||||
|
# decider – A callback function that will be expected to
|
||||||
|
# decide at runtime whether v should be allowed to execute
|
||||||
|
# (or whether the execution of v should be ignored,
|
||||||
|
# and therefore not executed). It is expected to take as single
|
||||||
|
# keyword argument history which will be the execution results of
|
||||||
|
# all u decideable links that have v as a target. It is expected
|
||||||
|
# to return a single boolean
|
||||||
|
# (True to allow v execution or False to not).
|
||||||
|
return True
|
||||||
|
|
||||||
|
def execute(self, actions):
|
||||||
|
try:
|
||||||
|
# NOTE(jed) We want to have a strong separation of concern
|
||||||
|
# between the Watcher planner and the Watcher Applier in order
|
||||||
|
# to us the possibility to support several workflow engine.
|
||||||
|
# We want to provide the 'taskflow' engine by
|
||||||
|
# default although we still want to leave the possibility for
|
||||||
|
# the users to change it.
|
||||||
|
# todo(jed) we need to change the way the actions are stored.
|
||||||
|
# The current implementation only use a linked list of actions.
|
||||||
|
# todo(jed) add olso conf for retry and name
|
||||||
|
flow = gf.Flow("watcher_flow")
|
||||||
|
previous = None
|
||||||
|
for a in actions:
|
||||||
|
task = TaskFlowActionContainer(a, self)
|
||||||
|
flow.add(task)
|
||||||
|
if previous is None:
|
||||||
|
previous = task
|
||||||
|
# we have only one Action in the Action Plan
|
||||||
|
if len(actions) == 1:
|
||||||
|
nop = TaskFlowNop()
|
||||||
|
flow.add(nop)
|
||||||
|
flow.link(previous, nop)
|
||||||
|
else:
|
||||||
|
# decider == guard (UML)
|
||||||
|
flow.link(previous, task, decider=self.decider)
|
||||||
|
previous = task
|
||||||
|
|
||||||
|
e = engines.load(flow)
|
||||||
|
e.run()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise exception.WorkflowExecutionException(error=e)
|
||||||
|
|
||||||
|
|
||||||
|
class TaskFlowActionContainer(task.Task):
|
||||||
|
def __init__(self, db_action, engine):
|
||||||
|
name = "action_type:{0} uuid:{1}".format(db_action.action_type,
|
||||||
|
db_action.uuid)
|
||||||
|
super(TaskFlowActionContainer, self).__init__(name=name)
|
||||||
|
self._db_action = db_action
|
||||||
|
self._engine = engine
|
||||||
|
self.loaded_action = None
|
||||||
|
|
||||||
|
@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,
|
||||||
|
obj_action.State.ONGOING)
|
||||||
|
LOG.debug("Precondition action %s", self.name)
|
||||||
|
self.action.precondition()
|
||||||
|
except Exception as e:
|
||||||
|
LOG.exception(e)
|
||||||
|
self.engine.notify(self._db_action,
|
||||||
|
obj_action.State.FAILED)
|
||||||
|
raise
|
||||||
|
|
||||||
|
def execute(self, *args, **kwargs):
|
||||||
|
try:
|
||||||
|
LOG.debug("Running action %s", self.name)
|
||||||
|
|
||||||
|
self.action.execute()
|
||||||
|
self.engine.notify(self._db_action,
|
||||||
|
obj_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)
|
||||||
|
|
||||||
|
self.engine.notify(self._db_action,
|
||||||
|
obj_action.State.FAILED)
|
||||||
|
raise
|
||||||
|
|
||||||
|
def post_execute(self):
|
||||||
|
try:
|
||||||
|
LOG.debug("postcondition action %s", self.name)
|
||||||
|
self.action.postcondition()
|
||||||
|
except Exception as e:
|
||||||
|
LOG.exception(e)
|
||||||
|
self.engine.notify(self._db_action,
|
||||||
|
obj_action.State.FAILED)
|
||||||
|
raise
|
||||||
|
|
||||||
|
def revert(self, *args, **kwargs):
|
||||||
|
LOG.warning(_LW("Revert action %s"), self.name)
|
||||||
|
try:
|
||||||
|
# todo(jed) do we need to update the states in case of failure ?
|
||||||
|
self.action.revert()
|
||||||
|
except Exception as e:
|
||||||
|
LOG.exception(e)
|
||||||
|
LOG.critical(_LC("Oops! We need disaster recover plan"))
|
||||||
|
|
||||||
|
|
||||||
|
class TaskFlowNop(task.Task):
|
||||||
|
"""This class is use in case of the workflow have only one Action.
|
||||||
|
|
||||||
|
We need at least two atoms to create a link
|
||||||
|
"""
|
||||||
|
def execute(self):
|
||||||
|
pass
|
||||||
30
watcher/applier/workflow_engine/loading/default.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
# Copyright (c) 2015 b<>com
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
# implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from oslo_log import log
|
||||||
|
|
||||||
|
from watcher.common.loader import default
|
||||||
|
|
||||||
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class DefaultWorkFlowEngineLoader(default.DefaultLoader):
|
||||||
|
def __init__(self):
|
||||||
|
super(DefaultWorkFlowEngineLoader, self).__init__(
|
||||||
|
namespace='watcher_workflow_engines')
|
||||||
@@ -25,7 +25,7 @@ from oslo_config import cfg
|
|||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
|
||||||
from watcher import _i18n
|
from watcher import _i18n
|
||||||
from watcher.applier.manager import ApplierManager
|
from watcher.applier import manager
|
||||||
from watcher.common import service
|
from watcher.common import service
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
@@ -40,6 +40,6 @@ def main():
|
|||||||
LOG.debug("Configuration:")
|
LOG.debug("Configuration:")
|
||||||
cfg.CONF.log_opt_values(LOG, std_logging.DEBUG)
|
cfg.CONF.log_opt_values(LOG, std_logging.DEBUG)
|
||||||
|
|
||||||
server = ApplierManager()
|
server = manager.ApplierManager()
|
||||||
server.connect()
|
server.connect()
|
||||||
server.join()
|
server.join()
|
||||||
|
|||||||