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
|
||||
doc/build
|
||||
doc/source/api
|
||||
doc/source/samples
|
||||
doc/source/watcher.conf.sample
|
||||
|
||||
# pbr generates these
|
||||
AUTHORS
|
||||
|
||||
@@ -10,12 +10,12 @@ Watcher
|
||||
|
||||
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
|
||||
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!
|
||||
migration, increased energy efficiency-and more!
|
||||
|
||||
* Free software: Apache license
|
||||
* 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
|
||||
--------
|
||||
|
||||
The AMQP message bus handles asynchronous communications between the different
|
||||
Watcher components.
|
||||
The AMQP message bus handles internal asynchronous communications between the
|
||||
different Watcher components.
|
||||
|
||||
.. _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
|
||||
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
|
||||
-----------
|
||||
@@ -63,13 +71,13 @@ It enables the :ref:`Administrator <administrator_definition>` of a
|
||||
:ref:`Cluster <cluster_definition>` to control and monitor the Watcher system
|
||||
via any interaction mechanism connected to this API:
|
||||
|
||||
- :ref:`CLI <watcher_cli_definition>`
|
||||
- :ref:`CLI <archi_watcher_cli_definition>`
|
||||
- Horizon plugin
|
||||
- Python SDK
|
||||
|
||||
You can also read the detailed description of `Watcher API`_.
|
||||
|
||||
.. _watcher_applier_definition:
|
||||
.. _archi_watcher_applier_definition:
|
||||
|
||||
Watcher Applier
|
||||
---------------
|
||||
@@ -111,7 +119,7 @@ If the :ref:`Action <action_definition>` fails, the
|
||||
previous state of the :ref:`Managed resource <managed_resource_definition>`
|
||||
(i.e. before the command was sent to the underlying OpenStack service).
|
||||
|
||||
.. _watcher_cli_definition:
|
||||
.. _archi_watcher_cli_definition:
|
||||
|
||||
Watcher CLI
|
||||
-----------
|
||||
@@ -119,16 +127,28 @@ Watcher CLI
|
||||
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.
|
||||
|
||||
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
|
||||
----------------
|
||||
|
||||
This database stores all the Watcher domain objects which can be requested
|
||||
by the :ref:`Watcher API <watcher_api_definition>` or the
|
||||
:ref:`Watcher CLI <watcher_cli_definition>`:
|
||||
by the :ref:`Watcher API <archi_watcher_api_definition>` or the
|
||||
:ref:`Watcher CLI <archi_watcher_cli_definition>`:
|
||||
|
||||
- :ref:`Audit templates <audit_template_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
|
||||
OpenStack system*".
|
||||
|
||||
.. _watcher_decision_engine_definition:
|
||||
.. _archi_watcher_decision_engine_definition:
|
||||
|
||||
Watcher Decision Engine
|
||||
-----------------------
|
||||
@@ -159,7 +179,7 @@ The :ref:`Strategy <strategy_definition>` is then dynamically loaded (via
|
||||
`stevedore <https://github.com/openstack/stevedore/>`_). The
|
||||
:ref:`Watcher Decision Engine <watcher_decision_engine_definition>` calls the
|
||||
**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
|
||||
: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:
|
||||
|
||||
- the current state of the
|
||||
:ref:`Managed resources <managed_resource_definition>`
|
||||
(e.g., the data stored in the Nova database)
|
||||
:ref:`Managed resources <managed_resource_definition>`
|
||||
(e.g., the data stored in the Nova database)
|
||||
- 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
|
||||
:ref:`Cluster <cluster_definition>`
|
||||
|
||||
So far, only one :ref:`Strategy <strategy_definition>` can be associated to a
|
||||
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
|
||||
|
||||
@@ -11,19 +11,15 @@
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
import sys
|
||||
from watcher import version as watcher_version
|
||||
|
||||
sys.path.insert(0, os.path.abspath('../..'))
|
||||
# -- General configuration ----------------------------------------------------
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = [
|
||||
'oslo_config.sphinxconfiggen',
|
||||
'sphinx.ext.autodoc',
|
||||
# 'sphinx.ext.intersphinx',
|
||||
'sphinx.ext.viewcode',
|
||||
'sphinxcontrib.httpdomain',
|
||||
'sphinxcontrib.pecanwsme.rest',
|
||||
@@ -33,7 +29,8 @@ extensions = [
|
||||
]
|
||||
|
||||
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
|
||||
# text edit cycles.
|
||||
@@ -46,8 +43,8 @@ source_suffix = '.rst'
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'watcher'
|
||||
copyright = u'2015, OpenStack Foundation'
|
||||
project = u'Watcher'
|
||||
copyright = u'OpenStack Foundation'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |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.
|
||||
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.
|
||||
add_function_parentheses = True
|
||||
@@ -73,6 +78,22 @@ add_module_names = True
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
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 --------------------------------------------------
|
||||
|
||||
# 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.
|
||||
- `python-watcherclient`_: A command-line interface (CLI) for interacting with
|
||||
the Watcher service.
|
||||
- `watcher-dashboard`_: An Horizon plugin for interacting with the Watcher
|
||||
service.
|
||||
|
||||
Additionally, the Bare Metal service has certain external dependencies, which
|
||||
are very similar to other OpenStack services:
|
||||
@@ -52,6 +54,7 @@ additional functionality:
|
||||
.. _`ceilometer`: https://github.com/openstack/ceilometer
|
||||
.. _`nova`: https://github.com/openstack/nova
|
||||
.. _`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
|
||||
.. _`RabbitMQ`: https://www.rabbitmq.com/
|
||||
|
||||
@@ -160,17 +163,33 @@ Configure the Watcher service
|
||||
The Watcher service is configured via its configuration file. This file
|
||||
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:
|
||||
|
||||
* ``[DEFAULT]`` - General configuration
|
||||
* ``[api]`` - API server configuration
|
||||
* ``[database]`` - SQL driver configuration
|
||||
* ``[keystone_authtoken]`` - Keystone Authentication plugin configuration
|
||||
* ``[watcher_clients_auth]`` - Keystone auth configuration for clients
|
||||
* ``[watcher_applier]`` - Watcher Applier module configuration
|
||||
* ``[watcher_decision_engine]`` - Watcher Decision Engine module configuration
|
||||
* ``[watcher_goals]`` - Goals mapping configuration
|
||||
* ``[watcher_strategies]`` - Strategy 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
|
||||
``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
|
||||
|
||||
|
||||
#. Configure the Watcher Service to use these credentials with the Identity
|
||||
Service. Replace IDENTITY_IP with the IP of the Identity server, and
|
||||
replace WATCHER_PASSWORD with the password you chose for the ``watcher``
|
||||
user in the Identity Service::
|
||||
#. Watcher API shall validate the token provided by every incoming request,
|
||||
via keystonemiddleware, which requires the Watcher service to be configured
|
||||
with the right credentials for the Identity service.
|
||||
|
||||
[keystone_authtoken]
|
||||
In the configuration section here below:
|
||||
|
||||
# Complete public Identity API endpoint (string value)
|
||||
#auth_uri=<None>
|
||||
auth_uri=http://IDENTITY_IP:5000/v3
|
||||
* 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``) ::
|
||||
|
||||
# Complete admin Identity API endpoint. This should specify the
|
||||
# unversioned root endpoint e.g. https://localhost:35357/ (string
|
||||
# value)
|
||||
#identity_uri = <None>
|
||||
identity_uri = http://IDENTITY_IP:5000
|
||||
[keystone_authtoken]
|
||||
|
||||
# Keystone account username (string value)
|
||||
#admin_user=<None>
|
||||
admin_user=watcher
|
||||
# Authentication type to load (unknown value)
|
||||
# Deprecated group/name - [DEFAULT]/auth_plugin
|
||||
#auth_type = <None>
|
||||
auth_type = password
|
||||
|
||||
# Keystone account password (string value)
|
||||
#admin_password=<None>
|
||||
admin_password=WATCHER_DBPASSWORD
|
||||
# Authentication URL (unknown value)
|
||||
#auth_url = <None>
|
||||
auth_url = http://IDENTITY_IP:35357
|
||||
|
||||
# Keystone service account tenant name to validate user tokens
|
||||
# (string value)
|
||||
#admin_tenant_name=admin
|
||||
admin_tenant_name=KEYSTONE_SERVICE_PROJECT_NAME
|
||||
# Username (unknown value)
|
||||
# Deprecated group/name - [DEFAULT]/username
|
||||
#username = <None>
|
||||
username=watcher
|
||||
|
||||
# Directory used to cache files related to PKI tokens (string
|
||||
# value)
|
||||
#signing_dir=<None>
|
||||
# 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
|
||||
|
||||
#. 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::
|
||||
|
||||
@@ -318,3 +403,33 @@ pick any database system you prefer.
|
||||
The original implementation has been based on MongoDB but you can create your
|
||||
own storage driver using whatever technology you want.
|
||||
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:
|
||||
|
||||
=================================
|
||||
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
|
||||
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
|
||||
----------------------------
|
||||
This guide assumes you have a working installation of Watcher. If you get
|
||||
"*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
|
||||
watcher command-line tools.
|
||||
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
|
||||
|
||||
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 ?
|
||||
------------------------------------
|
||||
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
|
||||
|
||||
$ 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
|
||||
env[OS_USERNAME]*" you may have to verify your credentials.
|
||||
@@ -90,7 +105,7 @@ configuration file.
|
||||
$ watcher action-plan-list --audit <the_audit_uuid>
|
||||
|
||||
- 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
|
||||
|
||||
@@ -119,3 +134,4 @@ You can also obtain more detailed information about a specific action:
|
||||
.. code:: bash
|
||||
|
||||
$ watcher action-show <the_action_uuid>
|
||||
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
|
||||
.. _contributing:
|
||||
|
||||
======================
|
||||
=======================
|
||||
Contributing to Watcher
|
||||
======================
|
||||
=======================
|
||||
|
||||
If you're interested in contributing to the Watcher project,
|
||||
the following will help get you started.
|
||||
@@ -43,7 +43,7 @@ notifications of important events.
|
||||
|
||||
|
||||
Project Hosting Details
|
||||
-------------------------
|
||||
-----------------------
|
||||
|
||||
Bug tracker
|
||||
http://launchpad.net/watcher
|
||||
@@ -60,3 +60,12 @@ Code Hosting
|
||||
Code Review
|
||||
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/
|
||||
|
||||
============================================
|
||||
Setting up a Watcher development environment
|
||||
============================================
|
||||
.. _watcher_developement_environment:
|
||||
|
||||
=========================================
|
||||
Set up a development environment manually
|
||||
=========================================
|
||||
|
||||
This document describes getting the source from watcher `Git repository`_
|
||||
for development purposes.
|
||||
@@ -75,17 +77,13 @@ extension, PyPi) cannot satisfy. These dependencies should be installed
|
||||
prior to using `pip`, and the installation method may vary depending on
|
||||
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
|
||||
|
||||
|
||||
PyPi Packages and VirtualEnv
|
||||
@@ -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.
|
||||
|
||||
Run Watcher unit tests
|
||||
======================
|
||||
Run Watcher tests
|
||||
=================
|
||||
|
||||
All unit tests should be run using tox. To run the unit tests under py27 and
|
||||
also run the pep8 tests:
|
||||
Watcher provides both :ref:`unit tests <unit_tests>` and
|
||||
: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
|
||||
===============================
|
||||
@@ -273,6 +250,11 @@ interface.
|
||||
|
||||
.. _`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
|
||||
=======================================
|
||||
@@ -287,4 +269,3 @@ template files to easily play with Watcher services within a minimal OpenStack
|
||||
isolated environment (Identity, Message Bus, SQL database, Horizon, ...).
|
||||
|
||||
.. _`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/
|
||||
|
||||
===============
|
||||
Watcher plugins
|
||||
===============
|
||||
|
||||
Writing a Watcher Decision Engine plugin
|
||||
========================================
|
||||
=================
|
||||
Available Plugins
|
||||
=================
|
||||
|
||||
Watcher has an external :ref:`strategy <strategy_definition>` plugin interface
|
||||
which gives anyone the ability to integrate an external :ref:`strategy
|
||||
<strategy_definition>` in order to make use of placement algorithms.
|
||||
.. _watcher_strategies:
|
||||
|
||||
This section gives some guidelines on how to implement and integrate custom
|
||||
Stategies with Watcher.
|
||||
Strategies
|
||||
==========
|
||||
|
||||
Pre-requisites
|
||||
--------------
|
||||
.. drivers-doc:: watcher_strategies
|
||||
|
||||
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.
|
||||
.. _watcher_actions:
|
||||
|
||||
Actions
|
||||
=======
|
||||
|
||||
Creating a new plugin
|
||||
---------------------
|
||||
.. drivers-doc:: watcher_actions
|
||||
|
||||
First of all you have to:
|
||||
.. _watcher_workflow_engines:
|
||||
|
||||
- Extend the base ``BaseStrategy`` class
|
||||
- Implement its ``execute`` method
|
||||
Workflow Engines
|
||||
================
|
||||
|
||||
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
|
||||
# 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'
|
||||
)
|
||||
Planners
|
||||
========
|
||||
|
||||
.. 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/
|
||||
|
||||
==========
|
||||
Glossary
|
||||
==========
|
||||
========
|
||||
Glossary
|
||||
========
|
||||
|
||||
.. glossary::
|
||||
:sorted:
|
||||
@@ -20,97 +20,14 @@ They are sorted in alphabetical order.
|
||||
Action
|
||||
======
|
||||
|
||||
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.)
|
||||
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>`
|
||||
.. watcher-term:: watcher.api.controllers.v1.action
|
||||
|
||||
.. _action_plan_definition:
|
||||
|
||||
Action Plan
|
||||
===========
|
||||
|
||||
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/>`_.
|
||||
|
||||
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>`
|
||||
.. watcher-term:: watcher.api.controllers.v1.action_plan
|
||||
|
||||
.. _administrator_definition:
|
||||
|
||||
@@ -144,73 +61,14 @@ any Watcher configuration files and to restart Watcher services.
|
||||
Audit
|
||||
=====
|
||||
|
||||
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>`.
|
||||
|
||||
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>`
|
||||
.. watcher-term:: watcher.api.controllers.v1.audit
|
||||
|
||||
.. _audit_template_definition:
|
||||
|
||||
Audit Template
|
||||
==============
|
||||
|
||||
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.
|
||||
.. watcher-term:: watcher.api.controllers.v1.audit_template
|
||||
|
||||
.. _availability_zone_definition:
|
||||
|
||||
@@ -241,136 +99,14 @@ The :ref:`Cluster <cluster_definition>` may be divided in one or several
|
||||
Cluster Data Model
|
||||
==================
|
||||
|
||||
A :ref:`Cluster Data Model <cluster_data_model_definition>` is a logical
|
||||
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, ...).
|
||||
.. watcher-term:: watcher.metrics_engine.cluster_model_collector.api
|
||||
|
||||
.. _cluster_history_definition:
|
||||
|
||||
Cluster History
|
||||
===============
|
||||
|
||||
The :ref:`Cluster History <cluster_history_definition>` contains all the
|
||||
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,...).
|
||||
.. watcher-term:: watcher.metrics_engine.cluster_history.api
|
||||
|
||||
.. _controller_node_definition:
|
||||
|
||||
@@ -419,20 +155,7 @@ them, or at least reported to them.
|
||||
Goal
|
||||
====
|
||||
|
||||
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, ...
|
||||
.. watcher-term:: watcher.api.controllers.v1.goal
|
||||
|
||||
|
||||
.. _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>`_.
|
||||
|
||||
.. _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
|
||||
respect with constraints and :ref:`SLAs <sla_definition>` defined by the
|
||||
: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.
|
||||
|
||||
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
|
||||
(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
|
||||
to be launched).
|
||||
|
||||
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):
|
||||
|
||||
- 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
|
||||
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
|
||||
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>`_.
|
||||
|
||||
|
||||
.. _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
|
||||
@@ -610,67 +316,21 @@ which provides a good definition.
|
||||
Solution
|
||||
========
|
||||
|
||||
A :ref:`Solution <solution_definition>` is a set of
|
||||
: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.
|
||||
.. watcher-term:: watcher.decision_engine.solution.base
|
||||
|
||||
.. _strategy_definition:
|
||||
|
||||
Strategy
|
||||
========
|
||||
|
||||
A :ref:`Strategy <strategy_definition>` is an algorithm implementation which is
|
||||
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-term:: watcher.decision_engine.strategy.strategies.base
|
||||
|
||||
.. _watcher_applier_definition:
|
||||
|
||||
Watcher Applier
|
||||
===============
|
||||
|
||||
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.
|
||||
.. watcher-term:: watcher.applier.base
|
||||
|
||||
.. _watcher_database_definition:
|
||||
|
||||
@@ -696,47 +356,12 @@ See :doc:`architecture` for more details on this component.
|
||||
Watcher Decision Engine
|
||||
=======================
|
||||
|
||||
This component is responsible for computing a set of potential optimization
|
||||
: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-term:: watcher.decision_engine.manager
|
||||
|
||||
.. _watcher_planner_definition:
|
||||
|
||||
Watcher Planner
|
||||
===============
|
||||
|
||||
The :ref:`Watcher Planner <watcher_planner_definition>` is part of the
|
||||
: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.
|
||||
.. watcher-term:: watcher.decision_engine.planner.base
|
||||
|
||||
|
||||
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>
|
||||
</g>
|
||||
<g>
|
||||
<rect style="fill: #ffb400" x="407" y="-0.832" width="162.3" height="89"/>
|
||||
<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 579.3,9.168 A 10,10 0 0 0 569.3,-0.832 L 569.3,9.168 z"/>
|
||||
<rect style="fill: #ffb400" x="397" y="9.168" width="182.3" height="69"/>
|
||||
<rect style="fill: #ffb400" x="407" y="-38.25" width="325.386" height="126.418"/>
|
||||
<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 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="-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 569.3,88.168 A 10,10 0 0 0 579.3,78.168 L 569.3,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="88.168" x2="569.3" 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 579.3,9.168 A 10,10 0 0 0 569.3,-0.832"/>
|
||||
<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="579.3" y1="9.168" x2="579.3" y2="78.168"/>
|
||||
<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="-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="732.386" y2="88.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 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="-28.25" x2="397" 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 569.3,88.168 A 10,10 0 0 0 579.3,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">
|
||||
<tspan x="488.15" y="39.1291">Watcher</tspan>
|
||||
<tspan x="488.15" y="56.768">Decision Engine</tspan>
|
||||
<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="569.693" y="20.4201">
|
||||
<tspan x="569.693" y="20.4201">Watcher</tspan>
|
||||
<tspan x="569.693" y="38.059">Decision Engine</tspan>
|
||||
</text>
|
||||
</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"/>
|
||||
<polygon style="fill: #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="394.764,21.4013 384.727,26.3262 387.264,21.3451 384.802,16.3265 "/>
|
||||
</g>
|
||||
<g>
|
||||
<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 "/>
|
||||
<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="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="338.622,37.0208 348.668,32.1139 346.122,37.0905 348.575,42.1135 "/>
|
||||
<polygon style="fill: #000000" points="394.764,37.5426 384.718,42.4495 387.264,37.4729 384.811,32.4499 "/>
|
||||
<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 "/>
|
||||
</g>
|
||||
<g>
|
||||
<rect style="fill: #ffb400" x="407" y="-159" width="160" height="89"/>
|
||||
@@ -160,10 +155,10 @@
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<rect style="fill: #ffffff" x="639.986" y="-66.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"/>
|
||||
<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">
|
||||
<tspan x="698.486" y="-34.5">Nova</tspan>
|
||||
<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="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="703.486" y="-89.5">
|
||||
<tspan x="703.486" y="-89.5">Nova</tspan>
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
@@ -174,14 +169,9 @@
|
||||
</text>
|
||||
</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"/>
|
||||
<polygon style="fill: #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="224.15,-56.0284 214.086,-51.1588 216.651,-56.1259 214.216,-61.1579 "/>
|
||||
</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 "/>
|
||||
<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="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="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-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 "/>
|
||||
</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"/>
|
||||
<polygon style="fill: #000000" points="638.394,-53.9699 627.762,-57.4302 633.053,-59.2355 634.783,-64.5512 "/>
|
||||
<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 "/>
|
||||
</g>
|
||||
<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>
|
||||
<rect style="fill: #ffffff" x="522.61" y="138.001" width="137.55" height="38"/>
|
||||
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="522.61" y="138.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="591.385" y="160.901">
|
||||
<tspan x="591.385" y="160.901">Ceilometer API</tspan>
|
||||
</text>
|
||||
</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"/>
|
||||
<polygon style="fill: #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="841.15,54.4914 831.131,59.4527 833.65,54.4624 831.169,49.4528 "/>
|
||||
<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="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="369.044,143.25 373.105,132.833 374.606,138.218 379.814,140.248 "/>
|
||||
</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"/>
|
||||
@@ -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 "/>
|
||||
</g>
|
||||
<g>
|
||||
<rect style="fill: #ffa200" x="506.36" y="72.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 608.385,82.1008 A 10,10 0 0 0 598.385,72.1008 L 598.385,82.1008 z"/>
|
||||
<rect style="fill: #ffa200" x="496.36" y="82.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 598.385,122.001 A 10,10 0 0 0 608.385,112.001 L 598.385,112.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="506.36" y1="122.001" x2="598.385" y2="122.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 608.385,82.1008 A 10,10 0 0 0 598.385,72.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="608.385" y1="82.1008" x2="608.385" y2="112.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 598.385,122.001 A 10,10 0 0 0 608.385,112.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">
|
||||
<tspan x="552.372" y="101.331">Strategy</tspan>
|
||||
<rect style="fill: #ffa200" x="422.36" y="60.1008" width="92.025" height="49.9"/>
|
||||
<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 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="412.36" y="70.1008" width="112.025" height="29.9"/>
|
||||
<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 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="422.36" y1="60.1008" x2="514.385" y2="60.1008"/>
|
||||
<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 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 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="412.36" y1="70.1008" x2="412.36" y2="100.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 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 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="468.372" y="89.3314">
|
||||
<tspan x="468.372" y="89.3314">Strategy</tspan>
|
||||
</text>
|
||||
</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"/>
|
||||
<polygon style="fill: #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="652.482,140.829 641.345,139.843 646.095,136.896 646.588,131.328 "/>
|
||||
<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="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="556.641,131.602 545.712,129.248 550.79,126.911 551.967,121.446 "/>
|
||||
</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 "/>
|
||||
<polygon style="fill: #000000" points="874.386,11.2639 869.386,1.26393 874.386,3.76393 879.386,1.26393 "/>
|
||||
<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 "/>
|
||||
<rect style="fill: #ffa200" x="625.488" y="60.1008" width="80.5125" height="49.9"/>
|
||||
<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"/>
|
||||
<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>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="579.3" y1="21.418" x2="632.216" y2="-18.5335"/>
|
||||
<polygon style="fill: #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="638.201,-23.0527 633.233,-13.0367 632.216,-18.5335 627.208,-21.0175 "/>
|
||||
<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="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="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>
|
||||
<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>
|
||||
</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: 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 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"/>
|
||||
<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">
|
||||
<tspan x="724.78" y="227.767">Cluster History DB</tspan>
|
||||
<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 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 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="354.72" y="227.767">
|
||||
<tspan x="354.72" y="227.767">Cluster Model 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 899.386,-205.998 899.386,5.26493 "/>
|
||||
<polygon style="fill: #000000" points="899.386,12.7649 894.386,2.76493 899.386,5.26493 904.386,2.76493 "/>
|
||||
<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: #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"/>
|
||||
<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"/>
|
||||
<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>
|
||||
</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/
|
||||
|
||||
============================================
|
||||
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
|
||||
on-the-go.
|
||||
Watcher project consists of several source code repositories:
|
||||
|
||||
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
|
||||
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
|
||||
===============
|
||||
|
||||
@@ -29,11 +43,21 @@ Introduction
|
||||
|
||||
glossary
|
||||
architecture
|
||||
dev/environment
|
||||
dev/contributing
|
||||
dev/plugins
|
||||
|
||||
|
||||
Getting Started
|
||||
---------------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
dev/environment
|
||||
dev/devstack
|
||||
deploy/configuration
|
||||
deploy/conf-files
|
||||
dev/testing
|
||||
|
||||
API References
|
||||
--------------
|
||||
|
||||
@@ -42,25 +66,48 @@ API References
|
||||
|
||||
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
|
||||
===========
|
||||
|
||||
Overview
|
||||
--------
|
||||
Introduction
|
||||
------------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
deploy/user-guide
|
||||
deploy/installation
|
||||
deploy/user-guide
|
||||
|
||||
Commands
|
||||
--------
|
||||
Watcher Manual Pages
|
||||
====================
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
:glob:
|
||||
: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
|
||||
|
||||
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
|
||||
=============
|
||||
=================
|
||||
|
||||
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
|
||||
@@ -52,7 +52,7 @@ run the following::
|
||||
|
||||
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.
|
||||
|
||||
@@ -219,3 +219,42 @@ version
|
||||
Show help for version and exit.
|
||||
|
||||
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/
|
||||
|
||||
=====================
|
||||
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
|
||||
===============
|
||||
|
||||
.. 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
|
||||
:members:
|
||||
@@ -20,7 +33,6 @@ Audit Templates
|
||||
.. autotype:: watcher.api.controllers.v1.audit_template.AuditTemplate
|
||||
:members:
|
||||
|
||||
|
||||
Audits
|
||||
======
|
||||
|
||||
@@ -33,24 +45,22 @@ Audits
|
||||
.. autotype:: watcher.api.controllers.v1.audit.Audit
|
||||
:members:
|
||||
|
||||
|
||||
Links
|
||||
=====
|
||||
|
||||
.. autotype:: watcher.api.controllers.link.Link
|
||||
:members:
|
||||
|
||||
|
||||
ActionPlans
|
||||
===========
|
||||
Action Plans
|
||||
============
|
||||
|
||||
.. rest-controller:: watcher.api.controllers.v1.action_plan:ActionPlansController
|
||||
:webprefix: /v1/action_plans
|
||||
|
||||
.. autotype:: watcher.api.controllers.v1.action_plan.ActionPlan
|
||||
.. autotype:: watcher.api.controllers.v1.action_plan.ActionPlanCollection
|
||||
:members:
|
||||
|
||||
.. autotype:: watcher.api.controllers.v1.action_plan.ActionPlanCollection
|
||||
.. autotype:: watcher.api.controllers.v1.action_plan.ActionPlan
|
||||
: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
|
||||
# process, which may cause wedges in the gate later.
|
||||
|
||||
enum34;python_version=='2.7' or python_version=='2.6'
|
||||
jsonpatch>=1.1
|
||||
keystonemiddleware>=2.0.0,!=2.4.0
|
||||
oslo.config>=2.3.0 # Apache-2.0
|
||||
oslo.db>=2.4.1 # Apache-2.0
|
||||
oslo.i18n>=1.5.0 # Apache-2.0
|
||||
oslo.log>=1.8.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.policy>=0.5.0 # Apache-2.0
|
||||
oslo.service>=0.7.0 # Apache-2.0
|
||||
oslo.utils>=2.0.0,!=2.6.0 # Apache-2.0
|
||||
PasteDeploy>=1.5.0
|
||||
pbr>=1.6
|
||||
pecan>=1.0.0
|
||||
python-ceilometerclient>=1.5.0
|
||||
python-cinderclient>=1.3.1
|
||||
python-glanceclient>=0.18.0
|
||||
python-keystoneclient>=1.6.0,!=1.8.0
|
||||
python-neutronclient>=2.6.0
|
||||
python-novaclient>=2.28.1,!=2.33.0
|
||||
python-openstackclient>=1.5.0
|
||||
six>=1.9.0
|
||||
SQLAlchemy>=0.9.9,<1.1.0
|
||||
stevedore>=1.5.0 # Apache-2.0
|
||||
WSME>=0.7
|
||||
enum34;python_version=='2.7' or python_version=='2.6' or python_version=='3.3' # BSD
|
||||
jsonpatch>=1.1 # BSD
|
||||
keystoneauth1>=2.1.0 # Apache-2.0
|
||||
keystonemiddleware!=4.1.0,>=4.0.0 # Apache-2.0
|
||||
oslo.config>=3.7.0 # Apache-2.0
|
||||
oslo.context>=0.2.0 # Apache-2.0
|
||||
oslo.db>=4.1.0 # 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.service>=1.0.0 # Apache-2.0
|
||||
oslo.utils>=3.5.0 # Apache-2.0
|
||||
PasteDeploy>=1.5.0 # MIT
|
||||
pbr>=1.6 # Apache-2.0
|
||||
pecan>=1.0.0 # BSD
|
||||
PrettyTable<0.8,>=0.7 # BSD
|
||||
voluptuous>=0.8.6 # BSD License
|
||||
python-ceilometerclient>=2.2.1 # Apache-2.0
|
||||
python-cinderclient>=1.3.1 # Apache-2.0
|
||||
python-glanceclient>=2.0.0 # Apache-2.0
|
||||
python-keystoneclient!=1.8.0,!=2.1.0,>=1.6.0 # Apache-2.0
|
||||
python-neutronclient!=4.1.0,>=2.6.0 # Apache-2.0
|
||||
python-novaclient!=2.33.0,>=2.29.0 # Apache-2.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
|
||||
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]
|
||||
packages =
|
||||
watcher
|
||||
watcher_tempest_plugin
|
||||
data_files =
|
||||
etc/ = etc/*
|
||||
|
||||
@@ -38,6 +39,9 @@ console_scripts =
|
||||
watcher-decision-engine = watcher.cmd.decisionengine:main
|
||||
watcher-applier = watcher.cmd.applier:main
|
||||
|
||||
tempest.test_plugins =
|
||||
watcher_tests = watcher_tempest_plugin.plugin:WatcherTempestPlugin
|
||||
|
||||
watcher.database.migration_backend =
|
||||
sqlalchemy = watcher.db.sqlalchemy.migration
|
||||
|
||||
@@ -45,6 +49,30 @@ watcher_strategies =
|
||||
dummy = watcher.decision_engine.strategy.strategies.dummy_strategy:DummyStrategy
|
||||
basic = watcher.decision_engine.strategy.strategies.basic_consolidation:BasicConsolidation
|
||||
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]
|
||||
source-dir = doc/source
|
||||
@@ -66,6 +94,6 @@ output_dir = watcher/locale
|
||||
input_file = watcher/locale/watcher.pot
|
||||
|
||||
[extract_messages]
|
||||
keywords = _ gettext ngettext l_ lazy_gettext
|
||||
keywords = _ gettext ngettext l_ lazy_gettext _LI _LW _LE _LC
|
||||
mapping_file = babel.cfg
|
||||
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.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -26,5 +25,5 @@ except ImportError:
|
||||
pass
|
||||
|
||||
setuptools.setup(
|
||||
setup_requires=['pbr'],
|
||||
setup_requires=['pbr>=1.8'],
|
||||
pbr=True)
|
||||
|
||||
@@ -2,20 +2,20 @@
|
||||
# of appearance. Changing the order has an impact on the overall integration
|
||||
# process, which may cause wedges in the gate later.
|
||||
|
||||
coverage>=3.6
|
||||
discover
|
||||
hacking>=0.10.2,<0.11
|
||||
mock>=1.2
|
||||
oslotest>=1.10.0 # Apache-2.0
|
||||
python-subunit>=0.0.18
|
||||
testrepository>=0.0.18
|
||||
testscenarios>=0.4
|
||||
testtools>=1.4.0
|
||||
coverage>=3.6 # Apache-2.0
|
||||
discover # BSD
|
||||
doc8 # Apache-2.0
|
||||
freezegun # Apache-2.0
|
||||
hacking<0.11,>=0.10.2
|
||||
mock>=1.2 # BSD
|
||||
oslotest>=1.10.0 # Apache-2.0
|
||||
os-testr>=0.4.1 # Apache-2.0
|
||||
python-subunit>=0.0.18 # Apache-2.0/BSD
|
||||
testrepository>=0.0.18 # Apache-2.0/BSD
|
||||
testscenarios>=0.4 # Apache-2.0/BSD
|
||||
testtools>=1.4.0 # MIT
|
||||
|
||||
# Doc requirements
|
||||
oslosphinx>=2.5.0 # Apache-2.0
|
||||
sphinx>=1.1.2,!=1.2.0,!=1.3b1,<1.3
|
||||
sphinxcontrib-pecanwsme>=0.8
|
||||
|
||||
# For PyPI distribution
|
||||
twine
|
||||
oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0
|
||||
sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 # BSD
|
||||
sphinxcontrib-pecanwsme>=0.8 # Apache-2.0
|
||||
|
||||
37
tox.ini
@@ -14,48 +14,47 @@ deps = -r{toxinidir}/requirements.txt
|
||||
commands =
|
||||
find . -type f -name "*.pyc" -delete
|
||||
find . -type d -name "__pycache__" -delete
|
||||
python setup.py testr --slowest --testr-args='{posargs}'
|
||||
ostestr --concurrency=6 {posargs}
|
||||
|
||||
[testenv:pep8]
|
||||
commands = flake8
|
||||
commands =
|
||||
doc8 doc/source/ CONTRIBUTING.rst HACKING.rst README.rst
|
||||
flake8
|
||||
|
||||
[testenv:venv]
|
||||
setenv = PYTHONHASHSEED=0
|
||||
commands = {posargs}
|
||||
|
||||
[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]
|
||||
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]
|
||||
commands = oslo_debug_helper {posargs}
|
||||
commands = oslo_debug_helper -t watcher/tests {posargs}
|
||||
|
||||
[testenv:config]
|
||||
sitepackages = False
|
||||
commands =
|
||||
oslo-config-generator --namespace watcher \
|
||||
--namespace keystonemiddleware.auth_token \
|
||||
--namespace oslo.log \
|
||||
--namespace oslo.db \
|
||||
--namespace oslo.messaging \
|
||||
--output-file etc/watcher/watcher.conf.sample
|
||||
oslo-config-generator --config-file etc/watcher/watcher-config-generator.conf
|
||||
|
||||
[flake8]
|
||||
# E123, E125 skipped as they are invalid PEP-8.
|
||||
|
||||
show-source=True
|
||||
ignore=E123,E125
|
||||
ignore=
|
||||
builtins= _
|
||||
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]
|
||||
commands = python setup.py bdist_wheel
|
||||
|
||||
[hacking]
|
||||
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
|
||||
# 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 pecan
|
||||
@@ -87,29 +127,17 @@ class Action(base.APIBase):
|
||||
mandatory=True)
|
||||
"""The action plan this action belongs to """
|
||||
|
||||
description = wtypes.text
|
||||
"""Description of this action"""
|
||||
|
||||
state = wtypes.text
|
||||
"""This audit state"""
|
||||
|
||||
alarm = types.uuid
|
||||
"""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"""
|
||||
|
||||
parameter = wtypes.text
|
||||
"""Additionnal parameter"""
|
||||
input_parameters = types.jsontype
|
||||
"""One or more key/value pairs """
|
||||
|
||||
next_uuid = wsme.wsproperty(types.uuid, _get_next_uuid,
|
||||
_set_next_uuid,
|
||||
@@ -230,7 +258,7 @@ class ActionsController(rest.RestController):
|
||||
resource_url=None,
|
||||
action_plan_uuid=None, audit_uuid=None):
|
||||
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
|
||||
if marker:
|
||||
@@ -261,10 +289,10 @@ class ActionsController(rest.RestController):
|
||||
sort_key=sort_key,
|
||||
sort_dir=sort_dir)
|
||||
|
||||
@wsme_pecan.wsexpose(ActionCollection, types.uuid, types.uuid,
|
||||
int, wtypes.text, wtypes.text, types.uuid,
|
||||
@wsme_pecan.wsexpose(ActionCollection, types.uuid, int,
|
||||
wtypes.text, wtypes.text, 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,
|
||||
audit_uuid=None):
|
||||
"""Retrieve a list of actions.
|
||||
@@ -285,16 +313,14 @@ class ActionsController(rest.RestController):
|
||||
marker, limit, sort_key, sort_dir,
|
||||
action_plan_uuid=action_plan_uuid, audit_uuid=audit_uuid)
|
||||
|
||||
@wsme_pecan.wsexpose(ActionCollection, types.uuid,
|
||||
types.uuid, int, wtypes.text, wtypes.text,
|
||||
types.uuid, types.uuid)
|
||||
def detail(self, action_uuid=None, marker=None, limit=None,
|
||||
@wsme_pecan.wsexpose(ActionCollection, types.uuid, int,
|
||||
wtypes.text, wtypes.text, types.uuid,
|
||||
types.uuid)
|
||||
def detail(self, marker=None, limit=None,
|
||||
sort_key='id', sort_dir='asc', action_plan_uuid=None,
|
||||
audit_uuid=None):
|
||||
"""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 limit: maximum number of resources to return in a single result.
|
||||
: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.
|
||||
"""
|
||||
# FIXME: blueprint edit-action-plan-flow
|
||||
raise exception.OperationNotPermitted(
|
||||
_("Cannot create an action directly"))
|
||||
|
||||
if self.from_actions:
|
||||
raise exception.OperationNotPermitted
|
||||
|
||||
@@ -357,6 +387,10 @@ class ActionsController(rest.RestController):
|
||||
:param action_uuid: UUID of a 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:
|
||||
raise exception.OperationNotPermitted
|
||||
|
||||
@@ -389,6 +423,9 @@ class ActionsController(rest.RestController):
|
||||
|
||||
: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(
|
||||
pecan.request.context,
|
||||
|
||||
@@ -15,6 +15,45 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# 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 pecan
|
||||
@@ -23,21 +62,45 @@ import wsme
|
||||
from wsme import types as wtypes
|
||||
import wsmeext.pecan as wsme_pecan
|
||||
|
||||
from watcher._i18n import _
|
||||
from watcher.api.controllers import base
|
||||
from watcher.api.controllers import link
|
||||
from watcher.api.controllers.v1 import collection
|
||||
from watcher.api.controllers.v1 import types
|
||||
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 import objects
|
||||
from watcher.objects import action_plan as ap_objects
|
||||
|
||||
|
||||
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
|
||||
def mandatory_attrs():
|
||||
return []
|
||||
return ["audit_id", "state", "first_action_id"]
|
||||
|
||||
|
||||
class ActionPlan(base.APIBase):
|
||||
@@ -103,7 +166,6 @@ class ActionPlan(base.APIBase):
|
||||
|
||||
self.fields = []
|
||||
fields = list(objects.ActionPlan.fields)
|
||||
fields.append('audit_uuid')
|
||||
for field in fields:
|
||||
# Skip fields we do not expose.
|
||||
if not hasattr(self, field):
|
||||
@@ -111,14 +173,19 @@ class ActionPlan(base.APIBase):
|
||||
self.fields.append(field)
|
||||
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, 'first_action_uuid',
|
||||
kwargs.get('first_action_id', wtypes.Unset))
|
||||
|
||||
@staticmethod
|
||||
def _convert_with_links(action_plan, url, expand=True):
|
||||
if not expand:
|
||||
action_plan.unset_fields_except(['uuid', 'state', 'updated_at',
|
||||
'audit_uuid'])
|
||||
action_plan.unset_fields_except(
|
||||
['uuid', 'state', 'updated_at',
|
||||
'audit_uuid', 'first_action_uuid'])
|
||||
|
||||
action_plan.links = [link.Link.make_link(
|
||||
'self', url,
|
||||
@@ -201,7 +268,7 @@ class ActionPlansController(rest.RestController):
|
||||
resource_url=None, audit_uuid=None):
|
||||
|
||||
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
|
||||
if marker:
|
||||
@@ -230,9 +297,9 @@ class ActionPlansController(rest.RestController):
|
||||
sort_key=sort_key,
|
||||
sort_dir=sort_dir)
|
||||
|
||||
@wsme_pecan.wsexpose(ActionPlanCollection, types.uuid, types.uuid,
|
||||
int, wtypes.text, wtypes.text, types.uuid)
|
||||
def get_all(self, action_plan_uuid=None, marker=None, limit=None,
|
||||
@wsme_pecan.wsexpose(ActionPlanCollection, types.uuid, int, wtypes.text,
|
||||
wtypes.text, types.uuid)
|
||||
def get_all(self, marker=None, limit=None,
|
||||
sort_key='id', sort_dir='asc', audit_uuid=None):
|
||||
"""Retrieve a list of action plans.
|
||||
|
||||
@@ -241,25 +308,23 @@ class ActionPlansController(rest.RestController):
|
||||
:param sort_key: column to sort results by. Default: id.
|
||||
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
|
||||
:param audit_uuid: Optional UUID of an audit, to get only actions
|
||||
for that audit.
|
||||
for that audit.
|
||||
"""
|
||||
return self._get_action_plans_collection(
|
||||
marker, limit, sort_key, sort_dir, audit_uuid=audit_uuid)
|
||||
|
||||
@wsme_pecan.wsexpose(ActionPlanCollection, types.uuid, types.uuid,
|
||||
int, wtypes.text, wtypes.text, types.uuid)
|
||||
def detail(self, action_plan_uuid=None, marker=None, limit=None,
|
||||
@wsme_pecan.wsexpose(ActionPlanCollection, types.uuid, int, wtypes.text,
|
||||
wtypes.text, types.uuid)
|
||||
def detail(self, marker=None, limit=None,
|
||||
sort_key='id', sort_dir='asc', audit_uuid=None):
|
||||
"""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 limit: maximum number of resources to return in a single result.
|
||||
:param sort_key: column to sort results by. Default: id.
|
||||
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
|
||||
:param audit_uuid: Optional UUID of an audit, to get only actions
|
||||
for that audit.
|
||||
for that audit.
|
||||
"""
|
||||
# NOTE(lucasagomes): /detail should only work agaist collections
|
||||
parent = pecan.request.path.split('/')[:-1][-1]
|
||||
@@ -302,10 +367,10 @@ class ActionPlansController(rest.RestController):
|
||||
@wsme_pecan.wsexpose(ActionPlan, types.uuid,
|
||||
body=[ActionPlanPatchType])
|
||||
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 patch: a json PATCH document to apply to this audit template.
|
||||
:param action_plan_uuid: UUID of a action plan.
|
||||
:param patch: a json PATCH document to apply to this action plan.
|
||||
"""
|
||||
launch_action_plan = True
|
||||
if self.from_actionsPlans:
|
||||
@@ -322,6 +387,34 @@ class ActionPlansController(rest.RestController):
|
||||
raise exception.PatchError(patch=patch, reason=e)
|
||||
|
||||
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
|
||||
for field in objects.ActionPlan.fields:
|
||||
try:
|
||||
@@ -334,13 +427,14 @@ class ActionPlansController(rest.RestController):
|
||||
if 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
|
||||
|
||||
action_plan_to_update.save()
|
||||
|
||||
if launch_action_plan:
|
||||
applier_client = ApplierAPI()
|
||||
applier_client = rpcapi.ApplierAPI()
|
||||
applier_client.launch_action_plan(pecan.request.context,
|
||||
action_plan.uuid)
|
||||
|
||||
|
||||
@@ -15,6 +15,20 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# 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 pecan
|
||||
@@ -23,6 +37,7 @@ import wsme
|
||||
from wsme import types as wtypes
|
||||
import wsmeext.pecan as wsme_pecan
|
||||
|
||||
from watcher._i18n import _
|
||||
from watcher.api.controllers import base
|
||||
from watcher.api.controllers import link
|
||||
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.common import exception
|
||||
from watcher.common import utils
|
||||
from watcher.decision_engine.rpcapi import DecisionEngineAPI
|
||||
from watcher.decision_engine import rpcapi
|
||||
from watcher import objects
|
||||
|
||||
|
||||
@@ -181,6 +196,7 @@ class AuditCollection(collection.Collection):
|
||||
"""A list containing audits objects"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(AuditCollection, self).__init__()
|
||||
self._type = 'audits'
|
||||
|
||||
@staticmethod
|
||||
@@ -227,7 +243,7 @@ class AuditsController(rest.RestController):
|
||||
sort_key, sort_dir, expand=False,
|
||||
resource_url=None, audit_template=None):
|
||||
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
|
||||
if marker:
|
||||
@@ -257,10 +273,9 @@ class AuditsController(rest.RestController):
|
||||
sort_key=sort_key,
|
||||
sort_dir=sort_dir)
|
||||
|
||||
@wsme_pecan.wsexpose(AuditCollection, types.uuid,
|
||||
types.uuid, int, wtypes.text,
|
||||
@wsme_pecan.wsexpose(AuditCollection, types.uuid, int, 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):
|
||||
"""Retrieve a list of audits.
|
||||
|
||||
@@ -275,13 +290,12 @@ class AuditsController(rest.RestController):
|
||||
sort_dir,
|
||||
audit_template=audit_template)
|
||||
|
||||
@wsme_pecan.wsexpose(AuditCollection, types.uuid,
|
||||
types.uuid, int, wtypes.text, wtypes.text)
|
||||
def detail(self, audit_uuid=None, marker=None, limit=None,
|
||||
@wsme_pecan.wsexpose(AuditCollection, types.uuid, int, wtypes.text,
|
||||
wtypes.text)
|
||||
def detail(self, marker=None, limit=None,
|
||||
sort_key='id', sort_dir='asc'):
|
||||
"""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 limit: maximum number of resources to return in a single result.
|
||||
:param sort_key: column to sort results by. Default: id.
|
||||
@@ -320,6 +334,11 @@ class AuditsController(rest.RestController):
|
||||
if self.from_audits:
|
||||
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()
|
||||
context = pecan.request.context
|
||||
new_audit = objects.Audit(context, **audit_dict)
|
||||
@@ -330,7 +349,7 @@ class AuditsController(rest.RestController):
|
||||
|
||||
# trigger decision-engine to run the audit
|
||||
|
||||
dc_client = DecisionEngineAPI()
|
||||
dc_client = rpcapi.DecisionEngineAPI()
|
||||
dc_client.trigger_audit(context, new_audit.uuid)
|
||||
|
||||
return Audit.convert_with_links(new_audit)
|
||||
|
||||
@@ -15,8 +15,42 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# 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
|
||||
|
||||
from oslo_config import cfg
|
||||
import pecan
|
||||
from pecan import rest
|
||||
import wsme
|
||||
@@ -34,11 +68,25 @@ from watcher import objects
|
||||
|
||||
|
||||
class AuditTemplatePatchType(types.JsonPatchType):
|
||||
|
||||
@staticmethod
|
||||
def mandatory_attrs():
|
||||
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):
|
||||
"""API representation of a audit template.
|
||||
@@ -116,13 +164,19 @@ class AuditTemplate(base.APIBase):
|
||||
name='My Audit Template',
|
||||
description='Description of my audit template',
|
||||
host_aggregate=5,
|
||||
goal='SERVERS_CONSOLIDATION',
|
||||
goal='DUMMY',
|
||||
extra={'automatic': True},
|
||||
created_at=datetime.datetime.utcnow(),
|
||||
deleted_at=None,
|
||||
updated_at=datetime.datetime.utcnow())
|
||||
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):
|
||||
"""API representation of a collection of audit templates."""
|
||||
@@ -131,6 +185,7 @@ class AuditTemplateCollection(collection.Collection):
|
||||
"""A list containing audit templates objects"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(AuditTemplateCollection, self).__init__()
|
||||
self._type = 'audit_templates'
|
||||
|
||||
@staticmethod
|
||||
@@ -163,12 +218,13 @@ class AuditTemplatesController(rest.RestController):
|
||||
'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,
|
||||
resource_url=None):
|
||||
|
||||
api_utils.validate_search_filters(
|
||||
filters, objects.audit_template.AuditTemplate.fields.keys())
|
||||
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
|
||||
if marker:
|
||||
@@ -178,6 +234,7 @@ class AuditTemplatesController(rest.RestController):
|
||||
|
||||
audit_templates = objects.AuditTemplate.list(
|
||||
pecan.request.context,
|
||||
filters,
|
||||
limit,
|
||||
marker_obj, sort_key=sort_key,
|
||||
sort_dir=sort_dir)
|
||||
@@ -189,26 +246,30 @@ class AuditTemplatesController(rest.RestController):
|
||||
sort_key=sort_key,
|
||||
sort_dir=sort_dir)
|
||||
|
||||
@wsme_pecan.wsexpose(AuditTemplateCollection, types.uuid, int,
|
||||
wtypes.text, wtypes.text)
|
||||
def get_all(self, marker=None, limit=None,
|
||||
@wsme_pecan.wsexpose(AuditTemplateCollection, wtypes.text,
|
||||
types.uuid, int, wtypes.text, wtypes.text)
|
||||
def get_all(self, goal=None, marker=None, limit=None,
|
||||
sort_key='id', sort_dir='asc'):
|
||||
"""Retrieve a list of audit templates.
|
||||
|
||||
:param goal: goal name to filter by (case sensitive)
|
||||
:param marker: pagination marker for large data sets.
|
||||
:param limit: maximum number of resources to return in a single result.
|
||||
:param sort_key: column to sort results by. Default: id.
|
||||
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
|
||||
"""
|
||||
return self._get_audit_templates_collection(marker, limit, sort_key,
|
||||
sort_dir)
|
||||
filters = api_utils.as_filters_dict(goal=goal)
|
||||
|
||||
@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)
|
||||
def detail(self, marker=None, limit=None,
|
||||
def detail(self, goal=None, marker=None, limit=None,
|
||||
sort_key='id', sort_dir='asc'):
|
||||
"""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 limit: maximum number of resources to return in a single result.
|
||||
:param sort_key: column to sort results by. Default: id.
|
||||
@@ -219,9 +280,11 @@ class AuditTemplatesController(rest.RestController):
|
||||
if parent != "audit_templates":
|
||||
raise exception.HTTPNotFound
|
||||
|
||||
filters = api_utils.as_filters_dict(goal=goal)
|
||||
|
||||
expand = True
|
||||
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,
|
||||
resource_url)
|
||||
|
||||
@@ -229,7 +292,7 @@ class AuditTemplatesController(rest.RestController):
|
||||
def get_one(self, 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:
|
||||
raise exception.OperationNotPermitted
|
||||
@@ -245,12 +308,14 @@ class AuditTemplatesController(rest.RestController):
|
||||
|
||||
return AuditTemplate.convert_with_links(rpc_audit_template)
|
||||
|
||||
@wsme.validate(types.uuid, AuditTemplate)
|
||||
@wsme_pecan.wsexpose(AuditTemplate, body=AuditTemplate, status_code=201)
|
||||
def post(self, audit_template):
|
||||
"""Create a new audit template.
|
||||
|
||||
:param audit template: a audit template within the request body.
|
||||
"""
|
||||
|
||||
if self.from_audit_templates:
|
||||
raise exception.OperationNotPermitted
|
||||
|
||||
|
||||
@@ -15,6 +15,23 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# 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
|
||||
|
||||
import pecan
|
||||
@@ -94,6 +111,7 @@ class GoalCollection(collection.Collection):
|
||||
"""A list containing goals objects"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(GoalCollection, self).__init__()
|
||||
self._type = 'goals'
|
||||
|
||||
@staticmethod
|
||||
@@ -141,7 +159,7 @@ class GoalsController(rest.RestController):
|
||||
resource_url=None, goal_name=None):
|
||||
|
||||
limit = api_utils.validate_limit(limit)
|
||||
sort_dir = api_utils.validate_sort_dir(sort_dir)
|
||||
api_utils.validate_sort_dir(sort_dir)
|
||||
|
||||
goals = []
|
||||
|
||||
|
||||
@@ -47,7 +47,15 @@ def validate_sort_dir(sort_dir):
|
||||
raise wsme.exc.ClientSideError(_("Invalid sort direction: %s. "
|
||||
"Acceptable values are "
|
||||
"'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):
|
||||
@@ -58,3 +66,12 @@ def apply_jsonpatch(doc, patch):
|
||||
' the resource is not allowed')
|
||||
raise wsme.exc.ClientSideError(msg % p['path'])
|
||||
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):
|
||||
req = webob.Request(environ)
|
||||
if (req.accept.best_match(['application/json', 'application/xml']
|
||||
) == 'application/xml'
|
||||
):
|
||||
) == 'application/xml'):
|
||||
try:
|
||||
# simple check xml is valid
|
||||
body = [et.ElementTree.tostring(
|
||||
|
||||
@@ -18,51 +18,50 @@
|
||||
#
|
||||
from oslo_log import log
|
||||
|
||||
from watcher.applier.action_plan.base import BaseActionPlanHandler
|
||||
from watcher.applier.default import DefaultApplier
|
||||
from watcher.applier.messaging.events import Events
|
||||
from watcher.common.messaging.events.event import Event
|
||||
from watcher.objects.action_plan import ActionPlan
|
||||
from watcher.objects.action_plan import Status
|
||||
from watcher.applier.action_plan import base
|
||||
from watcher.applier import default
|
||||
from watcher.applier.messaging import event_types
|
||||
from watcher.common.messaging.events import event
|
||||
from watcher.objects import action_plan as ap_objects
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class DefaultActionPlanHandler(BaseActionPlanHandler):
|
||||
def __init__(self, context, manager_applier, action_plan_uuid):
|
||||
class DefaultActionPlanHandler(base.BaseActionPlanHandler):
|
||||
def __init__(self, context, applier_manager, action_plan_uuid):
|
||||
super(DefaultActionPlanHandler, self).__init__()
|
||||
self.ctx = context
|
||||
self.action_plan_uuid = action_plan_uuid
|
||||
self.manager_applier = manager_applier
|
||||
self.applier_manager = applier_manager
|
||||
|
||||
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.save()
|
||||
event = Event()
|
||||
event.type = event_type
|
||||
event.data = {}
|
||||
ev = event.Event()
|
||||
ev.type = event_type
|
||||
ev.data = {}
|
||||
payload = {'action_plan__uuid': uuid,
|
||||
'action_plan_state': state}
|
||||
self.manager_applier.topic_status.publish_event(event.type.name,
|
||||
payload)
|
||||
self.applier_manager.status_topic_handler.publish_event(
|
||||
ev.type.name, payload)
|
||||
|
||||
def execute(self):
|
||||
try:
|
||||
# update state
|
||||
self.notify(self.action_plan_uuid,
|
||||
Events.LAUNCH_ACTION_PLAN,
|
||||
Status.ONGOING)
|
||||
applier = DefaultApplier(self.manager_applier, self.ctx)
|
||||
result = applier.execute(self.action_plan_uuid)
|
||||
event_types.EventTypes.LAUNCH_ACTION_PLAN,
|
||||
ap_objects.State.ONGOING)
|
||||
applier = default.DefaultApplier(self.ctx, self.applier_manager)
|
||||
applier.execute(self.action_plan_uuid)
|
||||
state = ap_objects.State.SUCCEEDED
|
||||
|
||||
except Exception as e:
|
||||
result = False
|
||||
LOG.error("Launch Action Plan " + unicode(e))
|
||||
LOG.exception(e)
|
||||
state = ap_objects.State.FAILED
|
||||
|
||||
finally:
|
||||
if result is True:
|
||||
status = Status.SUCCEEDED
|
||||
else:
|
||||
status = Status.FAILED
|
||||
# update state
|
||||
self.notify(self.action_plan_uuid, Events.LAUNCH_ACTION_PLAN,
|
||||
status)
|
||||
self.notify(self.action_plan_uuid,
|
||||
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 -*-
|
||||
# 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
|
||||
@@ -17,10 +15,15 @@
|
||||
# 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):
|
||||
def __str__(self):
|
||||
return "Nop"
|
||||
class DefaultActionLoader(default.DefaultLoader):
|
||||
def __init__(self):
|
||||
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.
|
||||
#
|
||||
|
||||
"""
|
||||
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 six
|
||||
|
||||
|
||||
@@ -16,25 +16,49 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
|
||||
from watcher.applier.base import BaseApplier
|
||||
from watcher.applier.execution.executor import ActionPlanExecutor
|
||||
from watcher.objects import Action
|
||||
from watcher.objects import ActionPlan
|
||||
from watcher.applier import base
|
||||
from watcher.applier.workflow_engine.loading import default
|
||||
from watcher import objects
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class DefaultApplier(BaseApplier):
|
||||
def __init__(self, manager_applier, context):
|
||||
class DefaultApplier(base.BaseApplier):
|
||||
def __init__(self, context, applier_manager):
|
||||
super(DefaultApplier, self).__init__()
|
||||
self.manager_applier = manager_applier
|
||||
self.context = context
|
||||
self.executor = ActionPlanExecutor(manager_applier, context)
|
||||
self._applier_manager = applier_manager
|
||||
self._loader = default.DefaultWorkFlowEngineLoader()
|
||||
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):
|
||||
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
|
||||
actions = Action.dbapi.get_action_list(self.context,
|
||||
filters={
|
||||
'action_plan_id':
|
||||
action_plan.id})
|
||||
return self.executor.execute(actions)
|
||||
filters = {'action_plan_id': action_plan.id}
|
||||
actions = objects.Action.dbapi.get_action_list(self.context, filters)
|
||||
return self.engine.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
|
||||
# limitations under the License.
|
||||
#
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
|
||||
from watcher.applier.messaging.trigger import TriggerActionPlan
|
||||
from watcher.common.messaging.messaging_core import MessagingCore
|
||||
from watcher.applier.messaging import trigger
|
||||
from watcher.common.messaging import messaging_core
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
# Register options
|
||||
APPLIER_MANAGER_OPTS = [
|
||||
cfg.IntOpt('applier_worker', default='1', help='The number of worker'),
|
||||
cfg.StrOpt('topic_control',
|
||||
cfg.IntOpt('workers',
|
||||
default='1',
|
||||
min=1,
|
||||
required=True,
|
||||
help='Number of workers for applier, default value is 1.'),
|
||||
cfg.StrOpt('conductor_topic',
|
||||
default='watcher.applier.control',
|
||||
help='The topic name used for'
|
||||
'control events, this topic '
|
||||
'used for rpc call '),
|
||||
cfg.StrOpt('topic_status',
|
||||
cfg.StrOpt('status_topic',
|
||||
default='watcher.applier.status',
|
||||
help='The topic name used for '
|
||||
'status events, this topic '
|
||||
@@ -45,7 +49,11 @@ APPLIER_MANAGER_OPTS = [
|
||||
cfg.StrOpt('publisher_id',
|
||||
default='watcher.applier.api',
|
||||
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',
|
||||
@@ -55,19 +63,17 @@ CONF.register_group(opt_group)
|
||||
CONF.register_opts(APPLIER_MANAGER_OPTS, opt_group)
|
||||
|
||||
|
||||
class ApplierManager(MessagingCore):
|
||||
class ApplierManager(messaging_core.MessagingCore):
|
||||
def __init__(self):
|
||||
super(ApplierManager, self).__init__(
|
||||
CONF.watcher_applier.publisher_id,
|
||||
CONF.watcher_applier.topic_control,
|
||||
CONF.watcher_applier.topic_status,
|
||||
CONF.watcher_applier.conductor_topic,
|
||||
CONF.watcher_applier.status_topic,
|
||||
api_version=self.API_VERSION,
|
||||
)
|
||||
# shared executor of the workflow
|
||||
self.executor = ThreadPoolExecutor(max_workers=1)
|
||||
# trigger action_plan
|
||||
self.topic_control.add_endpoint(TriggerActionPlan(self))
|
||||
self.conductor_topic_handler.add_endpoint(
|
||||
trigger.TriggerActionPlan(self))
|
||||
|
||||
def join(self):
|
||||
self.topic_control.join()
|
||||
self.topic_status.join()
|
||||
self.conductor_topic_handler.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.
|
||||
#
|
||||
|
||||
from enum import Enum
|
||||
import enum
|
||||
|
||||
|
||||
class Events(Enum):
|
||||
class EventTypes(enum.Enum):
|
||||
LAUNCH_ACTION_PLAN = "launch_action_plan"
|
||||
LAUNCH_ACTION = "launch_action"
|
||||
@@ -16,30 +16,35 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
from concurrent import futures
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
|
||||
from watcher.applier.action_plan.default import DefaultActionPlanHandler
|
||||
from watcher.applier.action_plan import default
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class TriggerActionPlan(object):
|
||||
def __init__(self, manager_applier):
|
||||
self.manager_applier = manager_applier
|
||||
def __init__(self, applier_manager):
|
||||
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):
|
||||
try:
|
||||
cmd = DefaultActionPlanHandler(context,
|
||||
self.manager_applier,
|
||||
action_plan_uuid)
|
||||
cmd = default.DefaultActionPlanHandler(context,
|
||||
self.applier_manager,
|
||||
action_plan_uuid)
|
||||
cmd.execute()
|
||||
except Exception as e:
|
||||
LOG.exception(e)
|
||||
|
||||
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
|
||||
self.manager_applier.executor.submit(self.do_launch_action_plan,
|
||||
context,
|
||||
action_plan_uuid)
|
||||
self.executor.submit(self.do_launch_action_plan, context,
|
||||
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 opt_group
|
||||
from watcher.common import exception
|
||||
from watcher.common.messaging.messaging_core import MessagingCore
|
||||
from watcher.common.messaging.notification_handler import NotificationHandler
|
||||
from watcher.common.messaging import messaging_core
|
||||
from watcher.common.messaging import notification_handler as notification
|
||||
from watcher.common import utils
|
||||
|
||||
|
||||
@@ -34,22 +34,22 @@ CONF.register_group(opt_group)
|
||||
CONF.register_opts(APPLIER_MANAGER_OPTS, opt_group)
|
||||
|
||||
|
||||
class ApplierAPI(MessagingCore):
|
||||
class ApplierAPI(messaging_core.MessagingCore):
|
||||
|
||||
def __init__(self):
|
||||
super(ApplierAPI, self).__init__(
|
||||
CONF.watcher_applier.publisher_id,
|
||||
CONF.watcher_applier.topic_control,
|
||||
CONF.watcher_applier.topic_status,
|
||||
CONF.watcher_applier.conductor_topic,
|
||||
CONF.watcher_applier.status_topic,
|
||||
api_version=self.API_VERSION,
|
||||
)
|
||||
self.handler = NotificationHandler(self.publisher_id)
|
||||
self.handler = notification.NotificationHandler(self.publisher_id)
|
||||
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)
|
||||
|
||||
target = om.Target(
|
||||
topic=CONF.watcher_applier.topic_control,
|
||||
topic=CONF.watcher_applier.conductor_topic,
|
||||
version=self.API_VERSION,
|
||||
)
|
||||
|
||||
@@ -63,10 +63,3 @@ class ApplierAPI(MessagingCore):
|
||||
return self.client.call(
|
||||
context.to_dict(), 'launch_action_plan',
|
||||
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 watcher import _i18n
|
||||
from watcher.applier.manager import ApplierManager
|
||||
from watcher.applier import manager
|
||||
from watcher.common import service
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
@@ -40,6 +40,6 @@ def main():
|
||||
LOG.debug("Configuration:")
|
||||
cfg.CONF.log_opt_values(LOG, std_logging.DEBUG)
|
||||
|
||||
server = ApplierManager()
|
||||
server = manager.ApplierManager()
|
||||
server.connect()
|
||||
server.join()
|
||||
|
||||