Compare commits
246 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
de7b0129a1 | ||
|
|
323fd01a85 | ||
|
|
c0133e6585 | ||
|
|
63fffeacd8 | ||
|
|
bc791f0e75 | ||
|
|
6ed417e6a7 | ||
|
|
4afefa3dfb | ||
|
|
9fadfbe40a | ||
|
|
f278874a93 | ||
|
|
1c5b247300 | ||
|
|
547bf5f87e | ||
|
|
96c0ac0ca8 | ||
|
|
a80fd2a51e | ||
|
|
58ea85c852 | ||
|
|
78f122f241 | ||
|
|
43eb997edb | ||
|
|
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 | ||
|
|
8ebc898924 | ||
|
|
660c782626 | ||
|
|
dfaba80252 | ||
|
|
8b357ace5a | ||
|
|
9dccb29bf4 | ||
|
|
853145f4d1 | ||
|
|
764f5c7681 | ||
|
|
b81b767567 | ||
|
|
764e31a7a1 | ||
|
|
1c49d07912 | ||
|
|
18549dc182 | ||
|
|
9f222221a7 | ||
|
|
1a64383a68 | ||
|
|
ac07f35dc7 | ||
|
|
010bc61cc9 | ||
|
|
3dd02ee895 | ||
|
|
a4bbe7f893 | ||
|
|
4c3073efb4 | ||
|
|
642226812f | ||
|
|
8f2ca2518f | ||
|
|
1698eb31f3 | ||
|
|
5fb74e677f | ||
|
|
35a5ba1cd0 | ||
|
|
c0a85be7b8 | ||
|
|
633b360652 | ||
|
|
d3160bf007 | ||
|
|
69152f2449 | ||
|
|
37cd75ffe3 | ||
|
|
c7fd5e8b21 | ||
|
|
f0b58f8c27 | ||
|
|
22dd6d42c3 | ||
|
|
bd29e2e79f | ||
|
|
c2eb112184 | ||
|
|
57cecb27f5 | ||
|
|
7c72d6f912 | ||
|
|
62570525ad | ||
|
|
92940ba9e2 | ||
|
|
7c8ce453ed | ||
|
|
33ea5f96f8 | ||
|
|
916f4d0c08 | ||
|
|
3fb5defc16 | ||
|
|
b01f4bead4 | ||
|
|
8516d629c2 | ||
|
|
35a1f0a657 | ||
|
|
d934971458 | ||
|
|
d92f85574d | ||
|
|
b1fe7a5f3d | ||
|
|
8a5eb4b6a1 | ||
|
|
91c14e4eda | ||
|
|
1613bd6904 | ||
|
|
ba4f5569d1 | ||
|
|
a62553a6a5 | ||
|
|
d5ba40530f | ||
|
|
f98e96da42 | ||
|
|
64747cad1f | ||
|
|
5f87e82bac | ||
|
|
ff89e942ca | ||
|
|
7faa501fb7 | ||
|
|
ab8d242c1f | ||
|
|
bbd26cafae | ||
|
|
0042356245 | ||
|
|
e1d4026c7c | ||
|
|
df692a8215 | ||
|
|
f9323889d6 | ||
|
|
0a44b2972e | ||
|
|
c5c16ac055 | ||
|
|
3016d3da11 | ||
|
|
daa560111c | ||
|
|
16705f68da | ||
|
|
109a980c29 | ||
|
|
531373cb84 | ||
|
|
087c4d49ed | ||
|
|
022b15dc1e | ||
|
|
454f70a19f | ||
|
|
98f05a52a8 | ||
|
|
5ff9f28a83 | ||
|
|
4a88220ffe | ||
|
|
4c2d0e6345 | ||
|
|
7710b1670e |
@@ -1,7 +1,9 @@
|
||||
[run]
|
||||
branch = True
|
||||
source = watcher
|
||||
omit = watcher/tests/*,watcher/openstack/*
|
||||
omit = watcher/tests/*
|
||||
|
||||
[report]
|
||||
ignore_errors = True
|
||||
exclude_lines =
|
||||
@abstract
|
||||
|
||||
1
.gitignore
vendored
@@ -43,6 +43,7 @@ output/*/index.html
|
||||
|
||||
# Sphinx
|
||||
doc/build
|
||||
doc/source/api
|
||||
|
||||
# pbr generates these
|
||||
AUTHORS
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
..
|
||||
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/
|
||||
|
||||
==========================
|
||||
watcher Style Commandments
|
||||
==========================
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
..
|
||||
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/
|
||||
|
||||
=======
|
||||
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
|
||||
385
doc/source/architecture.rst
Normal file
@@ -0,0 +1,385 @@
|
||||
..
|
||||
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/
|
||||
|
||||
.. _architecture:
|
||||
|
||||
===================
|
||||
System Architecture
|
||||
===================
|
||||
|
||||
|
||||
This page presents the current technical Architecture of the Watcher system.
|
||||
|
||||
.. _architecture_overview:
|
||||
|
||||
Overview
|
||||
========
|
||||
|
||||
Below you will find a diagram, showing the main components of Watcher:
|
||||
|
||||
.. image:: ./images/architecture.svg
|
||||
:width: 100%
|
||||
|
||||
|
||||
.. _components_definition:
|
||||
|
||||
Components
|
||||
==========
|
||||
|
||||
.. _amqp_bus_definition:
|
||||
|
||||
AMQP Bus
|
||||
--------
|
||||
|
||||
The AMQP message bus handles internal asynchronous communications between the
|
||||
different Watcher components.
|
||||
|
||||
.. _cluster_history_db_definition:
|
||||
|
||||
Cluster History Database
|
||||
------------------------
|
||||
|
||||
This component stores the data related to the
|
||||
:ref:`Cluster History <cluster_history_definition>`.
|
||||
|
||||
It can potentially rely on any appropriate storage system (InfluxDB, OpenTSDB,
|
||||
MongoDB,...) but will probably be more performant when using
|
||||
`Time Series Databases <https://en.wikipedia.org/wiki/Time_series_database>`_
|
||||
which are optimized for handling time series data, which are arrays of numbers
|
||||
indexed by time (a datetime or a datetime range).
|
||||
|
||||
.. _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
|
||||
-----------
|
||||
|
||||
This component implements the REST API provided by the Watcher system to the
|
||||
external world.
|
||||
|
||||
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 <archi_watcher_cli_definition>`
|
||||
- Horizon plugin
|
||||
- Python SDK
|
||||
|
||||
You can also read the detailed description of `Watcher API`_.
|
||||
|
||||
.. _archi_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>`.
|
||||
|
||||
It connects to the :ref:`message bus <amqp_bus_definition>` and launches the
|
||||
:ref:`Action Plan <action_plan_definition>` whenever a triggering message is
|
||||
received on a dedicated AMQP queue.
|
||||
|
||||
The triggering message contains the Action Plan UUID.
|
||||
|
||||
It then gets the detailed information about the
|
||||
:ref:`Action Plan <action_plan_definition>` from the
|
||||
:ref:`Watcher Database <watcher_database_definition>` which contains the list
|
||||
of :ref:`Actions <action_definition>` to launch.
|
||||
|
||||
It then loops on each :ref:`Action <action_definition>`, gets the associated
|
||||
class and calls the execute() method of this class.
|
||||
Most of the time, this method will first request a token to the Keystone API
|
||||
and if it is allowed, sends a request to the REST API of the OpenStack service
|
||||
which handles this kind of :ref:`atomic Action <action_definition>`.
|
||||
|
||||
Note that as soon as :ref:`Watcher Applier <watcher_applier_definition>` starts
|
||||
handling a given :ref:`Action <action_definition>` from the list, a
|
||||
notification message is sent on the :ref:`message bus <amqp_bus_definition>`
|
||||
indicating that the state of the action has changed to **ONGOING**.
|
||||
|
||||
If the :ref:`Action <action_definition>` is successful,
|
||||
the :ref:`Watcher Applier <watcher_applier_definition>` sends a notification
|
||||
message on :ref:`the bus <amqp_bus_definition>` informing the other components
|
||||
of this.
|
||||
|
||||
|
||||
If the :ref:`Action <action_definition>` fails, the
|
||||
:ref:`Watcher Applier <watcher_applier_definition>` tries to rollback to the
|
||||
previous state of the :ref:`Managed resource <managed_resource_definition>`
|
||||
(i.e. before the command was sent to the underlying OpenStack service).
|
||||
|
||||
.. _archi_watcher_cli_definition:
|
||||
|
||||
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/>`_.
|
||||
|
||||
.. _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 <archi_watcher_api_definition>` or the
|
||||
:ref:`Watcher CLI <archi_watcher_cli_definition>`:
|
||||
|
||||
- :ref:`Audit templates <audit_template_definition>`
|
||||
- :ref:`Audits <audit_definition>`
|
||||
- :ref:`Action plans <action_plan_definition>`
|
||||
- :ref:`Actions <action_definition>`
|
||||
- :ref:`Goals <goal_definition>`
|
||||
|
||||
The Watcher domain being here "*optimization of some resources provided by an
|
||||
OpenStack system*".
|
||||
|
||||
.. _archi_watcher_decision_engine_definition:
|
||||
|
||||
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 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 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
|
||||
:ref:`Action Plan <action_plan_definition>`).
|
||||
|
||||
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)
|
||||
- the data stored in the
|
||||
: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
|
||||
-------------------
|
||||
|
||||
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
|
||||
-------------------------
|
||||
|
||||
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,24 +11,20 @@
|
||||
# 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 = [
|
||||
'sphinx.ext.autodoc',
|
||||
# 'sphinx.ext.intersphinx',
|
||||
'sphinx.ext.viewcode',
|
||||
'sphinxcontrib.httpdomain',
|
||||
'sphinxcontrib.pecanwsme.rest',
|
||||
'wsmeext.sphinxext',
|
||||
'oslosphinx'
|
||||
'oslosphinx',
|
||||
'watcher.doc',
|
||||
]
|
||||
|
||||
wsme_protocols = ['restjson']
|
||||
@@ -45,8 +41,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
|
||||
@@ -61,6 +57,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
|
||||
@@ -72,6 +76,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,4 +1,8 @@
|
||||
..
|
||||
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/
|
||||
|
||||
===================
|
||||
Configuring Watcher
|
||||
@@ -30,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:
|
||||
@@ -48,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/
|
||||
|
||||
@@ -162,11 +169,17 @@ The configuration file is organized into the following sections:
|
||||
* ``[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
|
||||
@@ -233,39 +246,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::
|
||||
|
||||
@@ -314,3 +393,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
|
||||
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
..
|
||||
Except where otherwise noted, this document is licensed under Creative
|
||||
Commons Attribution 3.0 License. You can view the license at:
|
||||
|
||||
https://creativecommons.org/licenses/by/3.0/
|
||||
|
||||
==================
|
||||
Installing Watcher
|
||||
==================
|
||||
|
||||
@@ -1,20 +1,35 @@
|
||||
.. _user-guide:
|
||||
..
|
||||
Except where otherwise noted, this document is licensed under Creative
|
||||
Commons Attribution 3.0 License. You can view the license at:
|
||||
|
||||
=================================
|
||||
Welcome to the Watcher User Guide
|
||||
=================================
|
||||
https://creativecommons.org/licenses/by/3.0/
|
||||
|
||||
.. _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:
|
||||
@@ -23,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
|
||||
@@ -43,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.
|
||||
@@ -84,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
|
||||
|
||||
@@ -113,3 +134,4 @@ You can also obtain more detailed information about a specific action:
|
||||
.. code:: bash
|
||||
|
||||
$ watcher action-show <the_action_uuid>
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
.. _architecture:
|
||||
|
||||
===================
|
||||
System Architecture
|
||||
===================
|
||||
|
||||
Please go to `Wiki Watcher Architecture <https://wiki.openstack.org/wiki/WatcherArchitecture>`_
|
||||
|
||||
.. _API service: ../webapi/v1.html
|
||||
@@ -1,8 +1,14 @@
|
||||
..
|
||||
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/
|
||||
|
||||
.. _contributing:
|
||||
|
||||
======================
|
||||
=======================
|
||||
Contributing to Watcher
|
||||
======================
|
||||
=======================
|
||||
|
||||
If you're interested in contributing to the Watcher project,
|
||||
the following will help get you started.
|
||||
@@ -37,14 +43,14 @@ notifications of important events.
|
||||
|
||||
|
||||
Project Hosting Details
|
||||
-------------------------
|
||||
-----------------------
|
||||
|
||||
Bug tracker
|
||||
http://launchpad.net/watcher
|
||||
|
||||
Mailing list (prefix subjects with ``[watcher]`` for faster responses)
|
||||
http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-dev
|
||||
|
||||
|
||||
Wiki
|
||||
http://wiki.openstack.org/Watcher
|
||||
|
||||
@@ -54,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.
|
||||
@@ -1,8 +1,14 @@
|
||||
..
|
||||
Except where otherwise noted, this document is licensed under Creative
|
||||
Commons Attribution 3.0 License. You can view the license at:
|
||||
|
||||
============================================
|
||||
Setting up a Watcher development environment
|
||||
============================================
|
||||
https://creativecommons.org/licenses/by/3.0/
|
||||
|
||||
.. _watcher_developement_environment:
|
||||
|
||||
=========================================
|
||||
Set up a development environment manually
|
||||
=========================================
|
||||
|
||||
This document describes getting the source from watcher `Git repository`_
|
||||
for development purposes.
|
||||
@@ -71,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
|
||||
@@ -143,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
|
||||
===============================
|
||||
@@ -269,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
|
||||
=======================================
|
||||
@@ -283,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
|
||||
|
||||
|
||||
@@ -1,703 +0,0 @@
|
||||
..
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
not use this file except in compliance with the License. You may obtain
|
||||
a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
License for the specific language governing permissions and limitations
|
||||
under the License.
|
||||
|
||||
==========
|
||||
Glossary
|
||||
==========
|
||||
|
||||
.. glossary::
|
||||
:sorted:
|
||||
|
||||
This page explains the different terms used in the Watcher system.
|
||||
|
||||
They are sorted in alphabetical order.
|
||||
|
||||
.. _action_definition:
|
||||
|
||||
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.).
|
||||
|
||||
An :ref:`Action <action_definition>` has a life-cycle and its current state may
|
||||
be one of the following:
|
||||
|
||||
- **PENDING** : the :ref:`Action <action_definition>` has not been executed
|
||||
yet by the :ref:`Watcher Applier <watcher_applier_definition>`
|
||||
- **ONGOING** : the :ref:`Action <action_definition>` is currently being
|
||||
processed by the :ref:`Watcher Applier <watcher_applier_definition>`
|
||||
- **SUCCEEDED** : the :ref:`Action <action_definition>` has been executed
|
||||
successfully
|
||||
- **FAILED** : an error occured while trying to execute the
|
||||
:ref:`Action <action_definition>`
|
||||
- **DELETED** : the :ref:`Action <action_definition>` is still stored in the
|
||||
:ref:`Watcher database <watcher_database_definition>` but is not returned
|
||||
any more through the Watcher APIs.
|
||||
- **CANCELLED** : the :ref:`Action <action_definition>` was in **PENDING** or
|
||||
**ONGOING** state and was cancelled by the
|
||||
:ref:`Administrator <administrator_definition>`
|
||||
|
||||
.. _action_plan_definition:
|
||||
|
||||
Action Plan
|
||||
===========
|
||||
|
||||
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>`
|
||||
|
||||
.. _administrator_definition:
|
||||
|
||||
Administrator
|
||||
=============
|
||||
|
||||
The :ref:`Administrator <administrator_definition>` is any user who has admin
|
||||
access on the OpenStack cluster. This user is allowed to create new projects
|
||||
for tenants, create new users and assign roles to each user.
|
||||
|
||||
The :ref:`Administrator <administrator_definition>` usually has remote access
|
||||
to any host of the cluster in order to change the configuration and restart any
|
||||
OpenStack service, including Watcher.
|
||||
|
||||
In the context of Watcher, the :ref:`Administrator <administrator_definition>`
|
||||
is a role for users which allows them to run any Watcher commands, such as:
|
||||
|
||||
- Create/Delete an :ref:`Audit Template <audit_template_definition>`
|
||||
- Launch an :ref:`Audit <audit_definition>`
|
||||
- Get the :ref:`Action Plan <action_plan_definition>`
|
||||
- Launch a recommended :ref:`Action Plan <action_plan_definition>` manually
|
||||
- Archive previous :ref:`Audits <audit_definition>` and :ref:`Action Plans <action_plan_definition>`
|
||||
|
||||
|
||||
The :ref:`Administrator <administrator_definition>` is also allowed to modify
|
||||
any Watcher configuration files and to restart Watcher services.
|
||||
|
||||
.. _audit_definition:
|
||||
|
||||
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>`
|
||||
|
||||
.. _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.
|
||||
|
||||
.. _availability_zone_definition:
|
||||
|
||||
Availability Zone
|
||||
=================
|
||||
|
||||
Please, read `the official OpenStack definition of an Availability Zone <http://docs.openstack.org/developer/nova/aggregates.html#availability-zones-azs>`_.
|
||||
|
||||
.. _cluster_definition:
|
||||
|
||||
Cluster
|
||||
=======
|
||||
|
||||
A :ref:`Cluster <cluster_definition>` is a set of physical machines which
|
||||
provide compute, storage and networking resources and are managed by the same
|
||||
OpenStack Controller node.
|
||||
A :ref:`Cluster <cluster_definition>` represents a set of resources that a
|
||||
cloud provider is able to offer to his/her
|
||||
:ref:`customers <customer_definition>`.
|
||||
|
||||
A data center may contain several clusters.
|
||||
|
||||
The :ref:`Cluster <cluster_definition>` may be divided in one or several
|
||||
:ref:`Availability Zone(s) <availability_zone_definition>`.
|
||||
|
||||
.. _cluster_data_model_definition:
|
||||
|
||||
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, ...).
|
||||
|
||||
.. _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,...).
|
||||
|
||||
.. _controller_node_definition:
|
||||
|
||||
Controller Node
|
||||
===============
|
||||
|
||||
A controller node is a machine that typically runs the following core OpenStack
|
||||
services:
|
||||
|
||||
- Keystone: for identity and service management
|
||||
- Cinder scheduler: for volumes management
|
||||
- Glance controller: for image management
|
||||
- Neutron controller: for network management
|
||||
- Nova controller: for global compute resources management with services such as
|
||||
nova-scheduler, nova-conductor and nova-network
|
||||
|
||||
In many configurations, Watcher will reside on a controller node even if it
|
||||
can potentially be hosted on a dedicated machine.
|
||||
|
||||
.. _compute_node_definition:
|
||||
|
||||
Compute node
|
||||
============
|
||||
|
||||
Please, read `the official OpenStack definition of a Compute Node <http://docs.openstack.org/openstack-ops/content/compute_nodes.html>`_.
|
||||
|
||||
.. _customer_definition:
|
||||
|
||||
Customer
|
||||
========
|
||||
|
||||
A :ref:`Customer <customer_definition>` is the person or company which
|
||||
subscribes to the cloud provider offering. A customer may have several :ref:`Project(s) <project_definition>`
|
||||
hosted on the same :ref:`Cluster <cluster_definition>` or dispatched on
|
||||
different clusters.
|
||||
|
||||
In the private cloud context, the :ref:`Customers <customer_definition>` are
|
||||
different groups within the same organization (different departments, project
|
||||
teams, branch offices and so on). Cloud infrastructure includes the ability to
|
||||
precisely track each customer's service usage so that it can be charged back to
|
||||
them, or at least reported to them.
|
||||
|
||||
.. _goal_definition:
|
||||
|
||||
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, ...
|
||||
|
||||
|
||||
.. _host_aggregates_definition:
|
||||
|
||||
Host Aggregate
|
||||
==============
|
||||
|
||||
Please, read `the official OpenStack definition of a Host Aggregate <http://docs.openstack.org/developer/nova/aggregates.html>`_.
|
||||
|
||||
.. _instance_definition:
|
||||
|
||||
Instance
|
||||
========
|
||||
|
||||
A running virtual machine, or a virtual machine in a known state such as
|
||||
suspended, that can be used like a hardware server.
|
||||
|
||||
.. _managed_resource_definition:
|
||||
|
||||
Managed resource
|
||||
================
|
||||
|
||||
A :ref:`Managed resource <managed_resource_definition>` is one instance of
|
||||
:ref:`Managed resource type <managed_resource_type_definition>` in a topology
|
||||
with particular properties and dependencies on other
|
||||
:ref:`Managed resources <managed_resource_definition>` (relationships).
|
||||
|
||||
For example, a :ref:`Managed resource <managed_resource_definition>` can be one
|
||||
virtual machine (i.e., an :ref:`instance <instance_definition>`) hosted on a
|
||||
:ref:`compute node <compute_node_definition>` and connected to another virtual
|
||||
machine through a network link (represented also as a
|
||||
:ref:`Managed resource <managed_resource_definition>` in the
|
||||
:ref:`Cluster Data Model <cluster_data_model_definition>`).
|
||||
|
||||
.. _managed_resource_type_definition:
|
||||
|
||||
Managed resource type
|
||||
=====================
|
||||
|
||||
A :ref:`Managed resource type <managed_resource_definition>` is a type of
|
||||
hardware or software element of the :ref:`Cluster <cluster_definition>` that
|
||||
the Watcher system can act on.
|
||||
|
||||
Here are some examples of
|
||||
:ref:`Managed resource types <managed_resource_definition>`:
|
||||
|
||||
- `Nova Host Aggregates <http://docs.openstack.org/developer/heat/template_guide/openstack.html#OS::Nova::HostAggregate>`_
|
||||
- `Nova Servers <http://docs.openstack.org/developer/heat/template_guide/openstack.html#OS::Nova::Server>`_
|
||||
- `Cinder Volumes <http://docs.openstack.org/developer/heat/template_guide/openstack.html#OS::Cinder::Volume>`_
|
||||
- `Neutron Routers <http://docs.openstack.org/developer/heat/template_guide/openstack.html#OS::Neutron::Router>`_
|
||||
- `Neutron Networks <http://docs.openstack.org/developer/heat/template_guide/openstack.html#OS::Neutron::Net>`_
|
||||
- `Neutron load-balancers <http://docs.openstack.org/developer/heat/template_guide/openstack.html#OS::Neutron::LoadBalancer>`_
|
||||
- `Sahara Hadoop Cluster <http://docs.openstack.org/developer/heat/template_guide/openstack.html#OS::Sahara::Cluster>`_
|
||||
- ...
|
||||
|
||||
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:
|
||||
|
||||
Optimization Efficiency
|
||||
=======================
|
||||
|
||||
The :ref:`Optimization Efficiency <efficiency_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 :ref:`Goal <goal_definition>`
|
||||
to achieve.
|
||||
|
||||
Of course, the efficiency 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
|
||||
using several indicators (KPIs):
|
||||
|
||||
- the percentage of energy gain (which must be the highest possible)
|
||||
- the number of :ref:`SLA violations <sla_violation_definition>`
|
||||
(which must be the lowest possible)
|
||||
- the number of virtual machine migrations (which must be the lowest possible)
|
||||
|
||||
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>`
|
||||
to objectively compare different :ref:`Strategies <strategy_definition>` for
|
||||
the same goal and same workload of the :ref:`Cluster <cluster_definition>`.
|
||||
|
||||
.. _project_definition:
|
||||
|
||||
Project
|
||||
=======
|
||||
|
||||
:ref:`Projects <project_definition>` represent the base unit of “ownership”
|
||||
in OpenStack, in that all :ref:`resources <managed_resource_definition>` in
|
||||
OpenStack should be owned by a specific :ref:`project <project_definition>`.
|
||||
In OpenStack Identity, a :ref:`project <project_definition>` must be owned by a
|
||||
specific domain.
|
||||
|
||||
Please, read `the official OpenStack definition of a Project <http://docs.openstack.org/glossary/content/glossary.html>`_.
|
||||
|
||||
.. _sla_definition:
|
||||
|
||||
SLA
|
||||
===
|
||||
|
||||
:ref:`SLA <sla_definition>` means Service Level Agreement.
|
||||
|
||||
The resources are negotiated between the :ref:`Customer <customer_definition>`
|
||||
and the Cloud Provider in a contract.
|
||||
|
||||
Most of the time, this contract is composed of two documents:
|
||||
|
||||
- :ref:`SLA <sla_definition>` : Service Level Agreement
|
||||
- :ref:`SLO <slo_definition>` : Service Level Objectives
|
||||
|
||||
Note that the :ref:`SLA <sla_definition>` is more general than the
|
||||
:ref:`SLO <slo_definition>` in the sense that the former specifies what service
|
||||
is to be provided, how it is supported, times, locations, costs, performance,
|
||||
and responsibilities of the parties involved while the
|
||||
:ref:`SLO <slo_definition>` focuses on more measurable characteristics such as
|
||||
availability, throughput, frequency, response time or quality.
|
||||
|
||||
You can also read `the Wikipedia page for SLA <https://en.wikipedia.org/wiki/Service-level_agreement>`_
|
||||
which provides a good definition.
|
||||
|
||||
.. _sla_violation_definition:
|
||||
|
||||
SLA violation
|
||||
=============
|
||||
|
||||
A :ref:`SLA violation <sla_violation_definition>` happens when a :ref:`SLA <sla_definition>`
|
||||
defined with a given :ref:`Customer <customer_definition>` could not be
|
||||
respected by the cloud provider within the timeframe defined by the official
|
||||
contract document.
|
||||
|
||||
.. _slo_definition:
|
||||
|
||||
SLO
|
||||
===
|
||||
|
||||
A Service Level Objective (SLO) is a key element of a :ref:`SLA <sla_definition>`
|
||||
between a service provider and a :ref:`Customer <customer_definition>`. SLOs
|
||||
are agreed as a means of measuring the performance of the Service Provider and
|
||||
are outlined as a way of avoiding disputes between the two parties based on
|
||||
misunderstanding.
|
||||
|
||||
You can also read `the Wikipedia page for SLO <https://en.wikipedia.org/wiki/Service_level_objective>`_
|
||||
which provides a good definition.
|
||||
|
||||
.. _solution_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.
|
||||
|
||||
.. _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_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_database_definition:
|
||||
|
||||
Watcher Database
|
||||
================
|
||||
|
||||
This database stores all the Watcher domain objects which can be requested
|
||||
by the Watcher API or the Watcher CLI:
|
||||
|
||||
- Audit templates
|
||||
- Audits
|
||||
- Action plans
|
||||
- Actions
|
||||
- Goals
|
||||
|
||||
The Watcher domain being here "*optimization of some resources provided by an
|
||||
OpenStack system*".
|
||||
|
||||
See :doc:`architecture` for more details on this component.
|
||||
|
||||
.. _watcher_decision_engine_definition:
|
||||
|
||||
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_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.
|
||||
|
||||
159
doc/source/dev/plugin/action-plugin.rst
Normal file
@@ -0,0 +1,159 @@
|
||||
..
|
||||
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
|
||||
from watcher.applier.actions import base
|
||||
|
||||
|
||||
class DummyAction(baseBaseAction):
|
||||
|
||||
@property
|
||||
def schema(self):
|
||||
return 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.
|
||||
|
||||
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'
|
||||
)
|
||||
@@ -1,183 +1,38 @@
|
||||
===============
|
||||
Watcher plugins
|
||||
===============
|
||||
..
|
||||
Except where otherwise noted, this document is licensed under Creative
|
||||
Commons Attribution 3.0 License. You can view the license at:
|
||||
|
||||
Writing a Watcher Decision Engine plugin
|
||||
========================================
|
||||
|
||||
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.
|
||||
|
||||
This section gives some guidelines on how to implement and integrate custom
|
||||
Stategies 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.
|
||||
https://creativecommons.org/licenses/by/3.0/
|
||||
|
||||
|
||||
Creating a new plugin
|
||||
---------------------
|
||||
=================
|
||||
Available Plugins
|
||||
=================
|
||||
|
||||
First of all you have to:
|
||||
.. _watcher_strategies:
|
||||
|
||||
- Extend the base ``BaseStrategy`` class
|
||||
- Implement its ``execute`` method
|
||||
Strategies
|
||||
==========
|
||||
|
||||
Here is an example showing how you can write a plugin called ``DummyStrategy``:
|
||||
.. drivers-doc:: watcher_strategies
|
||||
|
||||
.. code-block:: python
|
||||
.. _watcher_actions:
|
||||
|
||||
# Filepath = third-party/third_party/dummy.py
|
||||
# Import path = third_party.dummy
|
||||
Actions
|
||||
=======
|
||||
|
||||
class DummyStrategy(BaseStrategy):
|
||||
.. drivers-doc:: watcher_actions
|
||||
|
||||
DEFAULT_NAME = "dummy"
|
||||
DEFAULT_DESCRIPTION = "Dummy Strategy"
|
||||
.. _watcher_workflow_engines:
|
||||
|
||||
def __init__(self, name=DEFAULT_NAME, description=DEFAULT_DESCRIPTION):
|
||||
super(DummyStrategy, self).__init__(name, description)
|
||||
Workflow Engines
|
||||
================
|
||||
|
||||
def execute(self, model):
|
||||
self.solution.add_change_request(
|
||||
Migrate(vm=my_vm, source_hypervisor=src, dest_hypervisor=dest)
|
||||
)
|
||||
# Do some more stuff here ...
|
||||
return self.solution
|
||||
.. drivers-doc:: watcher_workflow_engines
|
||||
|
||||
As you can see in the above example, the ``execute()`` method returns a
|
||||
solution as required.
|
||||
.. _watcher_planners:
|
||||
|
||||
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
|
||||
367
doc/source/glossary.rst
Normal file
@@ -0,0 +1,367 @@
|
||||
..
|
||||
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/
|
||||
|
||||
========
|
||||
Glossary
|
||||
========
|
||||
|
||||
.. glossary::
|
||||
:sorted:
|
||||
|
||||
This page explains the different terms used in the Watcher system.
|
||||
|
||||
They are sorted in alphabetical order.
|
||||
|
||||
.. _action_definition:
|
||||
|
||||
Action
|
||||
======
|
||||
|
||||
.. watcher-term:: watcher.api.controllers.v1.action
|
||||
|
||||
.. _action_plan_definition:
|
||||
|
||||
Action Plan
|
||||
===========
|
||||
|
||||
.. watcher-term:: watcher.api.controllers.v1.action_plan
|
||||
|
||||
.. _administrator_definition:
|
||||
|
||||
Administrator
|
||||
=============
|
||||
|
||||
The :ref:`Administrator <administrator_definition>` is any user who has admin
|
||||
access on the OpenStack cluster. This user is allowed to create new projects
|
||||
for tenants, create new users and assign roles to each user.
|
||||
|
||||
The :ref:`Administrator <administrator_definition>` usually has remote access
|
||||
to any host of the cluster in order to change the configuration and restart any
|
||||
OpenStack service, including Watcher.
|
||||
|
||||
In the context of Watcher, the :ref:`Administrator <administrator_definition>`
|
||||
is a role for users which allows them to run any Watcher commands, such as:
|
||||
|
||||
- Create/Delete an :ref:`Audit Template <audit_template_definition>`
|
||||
- Launch an :ref:`Audit <audit_definition>`
|
||||
- Get the :ref:`Action Plan <action_plan_definition>`
|
||||
- Launch a recommended :ref:`Action Plan <action_plan_definition>` manually
|
||||
- Archive previous :ref:`Audits <audit_definition>` and
|
||||
:ref:`Action Plans <action_plan_definition>`
|
||||
|
||||
|
||||
The :ref:`Administrator <administrator_definition>` is also allowed to modify
|
||||
any Watcher configuration files and to restart Watcher services.
|
||||
|
||||
.. _audit_definition:
|
||||
|
||||
Audit
|
||||
=====
|
||||
|
||||
.. watcher-term:: watcher.api.controllers.v1.audit
|
||||
|
||||
.. _audit_template_definition:
|
||||
|
||||
Audit Template
|
||||
==============
|
||||
|
||||
.. watcher-term:: watcher.api.controllers.v1.audit_template
|
||||
|
||||
.. _availability_zone_definition:
|
||||
|
||||
Availability Zone
|
||||
=================
|
||||
|
||||
Please, read `the official OpenStack definition of an Availability Zone <http://docs.openstack.org/developer/nova/aggregates.html#availability-zones-azs>`_.
|
||||
|
||||
.. _cluster_definition:
|
||||
|
||||
Cluster
|
||||
=======
|
||||
|
||||
A :ref:`Cluster <cluster_definition>` is a set of physical machines which
|
||||
provide compute, storage and networking resources and are managed by the same
|
||||
OpenStack Controller node.
|
||||
A :ref:`Cluster <cluster_definition>` represents a set of resources that a
|
||||
cloud provider is able to offer to his/her
|
||||
:ref:`customers <customer_definition>`.
|
||||
|
||||
A data center may contain several clusters.
|
||||
|
||||
The :ref:`Cluster <cluster_definition>` may be divided in one or several
|
||||
:ref:`Availability Zone(s) <availability_zone_definition>`.
|
||||
|
||||
.. _cluster_data_model_definition:
|
||||
|
||||
Cluster Data Model
|
||||
==================
|
||||
|
||||
.. watcher-term:: watcher.metrics_engine.cluster_model_collector.api
|
||||
|
||||
.. _cluster_history_definition:
|
||||
|
||||
Cluster History
|
||||
===============
|
||||
|
||||
.. watcher-term:: watcher.metrics_engine.cluster_history.api
|
||||
|
||||
.. _controller_node_definition:
|
||||
|
||||
Controller Node
|
||||
===============
|
||||
|
||||
A controller node is a machine that typically runs the following core OpenStack
|
||||
services:
|
||||
|
||||
- Keystone: for identity and service management
|
||||
- Cinder scheduler: for volumes management
|
||||
- Glance controller: for image management
|
||||
- Neutron controller: for network management
|
||||
- Nova controller: for global compute resources management with services
|
||||
such as nova-scheduler, nova-conductor and nova-network.
|
||||
|
||||
In many configurations, Watcher will reside on a controller node even if it
|
||||
can potentially be hosted on a dedicated machine.
|
||||
|
||||
.. _compute_node_definition:
|
||||
|
||||
Compute node
|
||||
============
|
||||
|
||||
Please, read `the official OpenStack definition of a Compute Node <http://docs.openstack.org/openstack-ops/content/compute_nodes.html>`_.
|
||||
|
||||
.. _customer_definition:
|
||||
|
||||
Customer
|
||||
========
|
||||
|
||||
A :ref:`Customer <customer_definition>` is the person or company which
|
||||
subscribes to the cloud provider offering. A customer may have several
|
||||
:ref:`Project(s) <project_definition>`
|
||||
hosted on the same :ref:`Cluster <cluster_definition>` or dispatched on
|
||||
different clusters.
|
||||
|
||||
In the private cloud context, the :ref:`Customers <customer_definition>` are
|
||||
different groups within the same organization (different departments, project
|
||||
teams, branch offices and so on). Cloud infrastructure includes the ability to
|
||||
precisely track each customer's service usage so that it can be charged back to
|
||||
them, or at least reported to them.
|
||||
|
||||
.. _goal_definition:
|
||||
|
||||
Goal
|
||||
====
|
||||
|
||||
.. watcher-term:: watcher.api.controllers.v1.goal
|
||||
|
||||
|
||||
.. _host_aggregates_definition:
|
||||
|
||||
Host Aggregate
|
||||
==============
|
||||
|
||||
Please, read `the official OpenStack definition of a Host Aggregate <http://docs.openstack.org/developer/nova/aggregates.html>`_.
|
||||
|
||||
.. _instance_definition:
|
||||
|
||||
Instance
|
||||
========
|
||||
|
||||
A running virtual machine, or a virtual machine in a known state such as
|
||||
suspended, that can be used like a hardware server.
|
||||
|
||||
.. _managed_resource_definition:
|
||||
|
||||
Managed resource
|
||||
================
|
||||
|
||||
A :ref:`Managed resource <managed_resource_definition>` is one instance of
|
||||
:ref:`Managed resource type <managed_resource_type_definition>` in a topology
|
||||
with particular properties and dependencies on other
|
||||
:ref:`Managed resources <managed_resource_definition>` (relationships).
|
||||
|
||||
For example, a :ref:`Managed resource <managed_resource_definition>` can be one
|
||||
virtual machine (i.e., an :ref:`instance <instance_definition>`) hosted on a
|
||||
:ref:`compute node <compute_node_definition>` and connected to another virtual
|
||||
machine through a network link (represented also as a
|
||||
:ref:`Managed resource <managed_resource_definition>` in the
|
||||
:ref:`Cluster Data Model <cluster_data_model_definition>`).
|
||||
|
||||
.. _managed_resource_type_definition:
|
||||
|
||||
Managed resource type
|
||||
=====================
|
||||
|
||||
A :ref:`Managed resource type <managed_resource_definition>` is a type of
|
||||
hardware or software element of the :ref:`Cluster <cluster_definition>` that
|
||||
the Watcher system can act on.
|
||||
|
||||
Here are some examples of
|
||||
:ref:`Managed resource types <managed_resource_definition>`:
|
||||
|
||||
- `Nova Host Aggregates <http://docs.openstack.org/developer/heat/template_guide/openstack.html#OS::Nova::HostAggregate>`_
|
||||
- `Nova Servers <http://docs.openstack.org/developer/heat/template_guide/openstack.html#OS::Nova::Server>`_
|
||||
- `Cinder Volumes <http://docs.openstack.org/developer/heat/template_guide/openstack.html#OS::Cinder::Volume>`_
|
||||
- `Neutron Routers <http://docs.openstack.org/developer/heat/template_guide/openstack.html#OS::Neutron::Router>`_
|
||||
- `Neutron Networks <http://docs.openstack.org/developer/heat/template_guide/openstack.html#OS::Neutron::Net>`_
|
||||
- `Neutron load-balancers <http://docs.openstack.org/developer/heat/template_guide/openstack.html#OS::Neutron::LoadBalancer>`_
|
||||
- `Sahara Hadoop Cluster <http://docs.openstack.org/developer/heat/template_guide/openstack.html#OS::Sahara::Cluster>`_
|
||||
- ...
|
||||
|
||||
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:
|
||||
|
||||
Optimization Efficiency
|
||||
=======================
|
||||
|
||||
The :ref:`Optimization Efficiency <efficiency_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
|
||||
:ref:`Goal <goal_definition>` to achieve.
|
||||
|
||||
Of course, the efficiency 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
|
||||
using several indicators (KPIs):
|
||||
|
||||
- the percentage of energy gain (which must be the highest possible)
|
||||
- the number of :ref:`SLA violations <sla_violation_definition>`
|
||||
(which must be the lowest possible)
|
||||
- the number of virtual machine migrations (which must be the lowest possible)
|
||||
|
||||
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>`
|
||||
to objectively compare different :ref:`Strategies <strategy_definition>` for
|
||||
the same goal and same workload of the :ref:`Cluster <cluster_definition>`.
|
||||
|
||||
.. _project_definition:
|
||||
|
||||
Project
|
||||
=======
|
||||
|
||||
:ref:`Projects <project_definition>` represent the base unit of “ownership”
|
||||
in OpenStack, in that all :ref:`resources <managed_resource_definition>` in
|
||||
OpenStack should be owned by a specific :ref:`project <project_definition>`.
|
||||
In OpenStack Identity, a :ref:`project <project_definition>` must be owned by a
|
||||
specific domain.
|
||||
|
||||
Please, read `the official OpenStack definition of a Project <http://docs.openstack.org/glossary/content/glossary.html>`_.
|
||||
|
||||
|
||||
.. _sla_definition:
|
||||
|
||||
SLA
|
||||
===
|
||||
|
||||
:ref:`SLA <sla_definition>` means Service Level Agreement.
|
||||
|
||||
The resources are negotiated between the :ref:`Customer <customer_definition>`
|
||||
and the Cloud Provider in a contract.
|
||||
|
||||
Most of the time, this contract is composed of two documents:
|
||||
|
||||
- :ref:`SLA <sla_definition>` : Service Level Agreement
|
||||
- :ref:`SLO <slo_definition>` : Service Level Objectives
|
||||
|
||||
Note that the :ref:`SLA <sla_definition>` is more general than the
|
||||
:ref:`SLO <slo_definition>` in the sense that the former specifies what service
|
||||
is to be provided, how it is supported, times, locations, costs, performance,
|
||||
and responsibilities of the parties involved while the
|
||||
:ref:`SLO <slo_definition>` focuses on more measurable characteristics such as
|
||||
availability, throughput, frequency, response time or quality.
|
||||
|
||||
You can also read `the Wikipedia page for SLA <https://en.wikipedia.org/wiki/Service-level_agreement>`_
|
||||
which provides a good definition.
|
||||
|
||||
.. _sla_violation_definition:
|
||||
|
||||
SLA violation
|
||||
=============
|
||||
|
||||
A :ref:`SLA violation <sla_violation_definition>` happens when a
|
||||
:ref:`SLA <sla_definition>` defined with a given
|
||||
:ref:`Customer <customer_definition>` could not be respected by the
|
||||
cloud provider within the timeframe defined by the official contract document.
|
||||
|
||||
.. _slo_definition:
|
||||
|
||||
SLO
|
||||
===
|
||||
|
||||
A Service Level Objective (SLO) is a key element of a
|
||||
:ref:`SLA <sla_definition>` between a service provider and a
|
||||
:ref:`Customer <customer_definition>`. SLOs are agreed as a means of measuring
|
||||
the performance of the Service Provider and are outlined as a way of avoiding
|
||||
disputes between the two parties based on misunderstanding.
|
||||
|
||||
You can also read `the Wikipedia page for SLO <https://en.wikipedia.org/wiki/Service_level_objective>`_
|
||||
which provides a good definition.
|
||||
|
||||
.. _solution_definition:
|
||||
|
||||
Solution
|
||||
========
|
||||
|
||||
.. watcher-term:: watcher.decision_engine.solution.base
|
||||
|
||||
.. _strategy_definition:
|
||||
|
||||
Strategy
|
||||
========
|
||||
|
||||
.. watcher-term:: watcher.decision_engine.strategy.strategies.base
|
||||
|
||||
.. _watcher_applier_definition:
|
||||
|
||||
Watcher Applier
|
||||
===============
|
||||
|
||||
.. watcher-term:: watcher.applier.base
|
||||
|
||||
.. _watcher_database_definition:
|
||||
|
||||
Watcher Database
|
||||
================
|
||||
|
||||
This database stores all the Watcher domain objects which can be requested
|
||||
by the Watcher API or the Watcher CLI:
|
||||
|
||||
- Audit templates
|
||||
- Audits
|
||||
- Action plans
|
||||
- Actions
|
||||
- Goals
|
||||
|
||||
The Watcher domain being here "*optimization of some resources provided by an
|
||||
OpenStack system*".
|
||||
|
||||
See :doc:`architecture` for more details on this component.
|
||||
|
||||
.. _watcher_decision_engine_definition:
|
||||
|
||||
Watcher Decision Engine
|
||||
=======================
|
||||
|
||||
.. watcher-term:: watcher.decision_engine.manager
|
||||
|
||||
.. _watcher_planner_definition:
|
||||
|
||||
Watcher Planner
|
||||
===============
|
||||
|
||||
.. 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 |
302
doc/source/images/architecture.svg
Normal file
@@ -0,0 +1,302 @@
|
||||
<?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="60cm" height="27cm" viewBox="-181 -239 1196 535" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g>
|
||||
<rect style="fill: #b3d6c6" x="-169.614" y="-237" width="1173" height="532"/>
|
||||
<path style="fill: #b3d6c6" d="M -169.614,-237 A 10,10 0 0 0 -179.614,-227 L -169.614,-227 z"/>
|
||||
<path style="fill: #b3d6c6" d="M 1013.39,-227 A 10,10 0 0 0 1003.39,-237 L 1003.39,-227 z"/>
|
||||
<rect style="fill: #b3d6c6" x="-179.614" y="-227" width="1193" height="512"/>
|
||||
<path style="fill: #b3d6c6" d="M -179.614,285 A 10,10 0 0 0 -169.614,295 L -169.614,285 z"/>
|
||||
<path style="fill: #b3d6c6" d="M 1003.39,295 A 10,10 0 0 0 1013.39,285 L 1003.39,285 z"/>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #ffffff" x1="-169.614" y1="-237" x2="1003.39" y2="-237"/>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #ffffff" x1="-169.614" y1="295" x2="1003.39" y2="295"/>
|
||||
<path style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #ffffff" d="M -169.614,-237 A 10,10 0 0 0 -179.614,-227"/>
|
||||
<path style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #ffffff" d="M 1013.39,-227 A 10,10 0 0 0 1003.39,-237"/>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #ffffff" x1="-179.614" y1="-227" x2="-179.614" y2="285"/>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #ffffff" x1="1013.39" y1="-227" x2="1013.39" y2="285"/>
|
||||
<path style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #ffffff" d="M -179.614,285 A 10,10 0 0 0 -169.614,295"/>
|
||||
<path style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #ffffff" d="M 1003.39,295 A 10,10 0 0 0 1013.39,285"/>
|
||||
<text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="416.886" y="32.9002">
|
||||
<tspan x="416.886" y="32.9002"></tspan>
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<rect style="fill: #00cd78" x="244.1" y="-158" width="82.287" height="260"/>
|
||||
<path style="fill: #00cd78" d="M 244.1,-158 A 10,10 0 0 0 234.1,-148 L 244.1,-148 z"/>
|
||||
<path style="fill: #00cd78" d="M 336.387,-148 A 10,10 0 0 0 326.387,-158 L 326.387,-148 z"/>
|
||||
<rect style="fill: #00cd78" x="234.1" y="-148" width="102.287" height="240"/>
|
||||
<path style="fill: #00cd78" d="M 234.1,92 A 10,10 0 0 0 244.1,102 L 244.1,92 z"/>
|
||||
<path style="fill: #00cd78" d="M 326.387,102 A 10,10 0 0 0 336.387,92 L 326.387,92 z"/>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="244.1" y1="-158" x2="326.387" y2="-158"/>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="244.1" y1="102" x2="326.387" y2="102"/>
|
||||
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 244.1,-158 A 10,10 0 0 0 234.1,-148"/>
|
||||
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 336.387,-148 A 10,10 0 0 0 326.387,-158"/>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="234.1" y1="-148" x2="234.1" y2="92"/>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="336.387" y1="-148" x2="336.387" y2="92"/>
|
||||
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 234.1,92 A 10,10 0 0 0 244.1,102"/>
|
||||
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 326.387,102 A 10,10 0 0 0 336.387,92"/>
|
||||
<text font-size="12.8" style="fill: #ffffff;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="285.243" y="-32.1">
|
||||
<tspan x="285.243" y="-32.1">AMQP</tspan>
|
||||
<tspan x="285.243" y="-16.1">bus</tspan>
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<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 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 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-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"/>
|
||||
<path style="fill: #ffb400" d="M 407,-159 A 10,10 0 0 0 397,-149 L 407,-149 z"/>
|
||||
<path style="fill: #ffb400" d="M 577,-149 A 10,10 0 0 0 567,-159 L 567,-149 z"/>
|
||||
<rect style="fill: #ffb400" x="397" y="-149" width="180" height="69"/>
|
||||
<path style="fill: #ffb400" d="M 397,-80 A 10,10 0 0 0 407,-70 L 407,-80 z"/>
|
||||
<path style="fill: #ffb400" d="M 567,-70 A 10,10 0 0 0 577,-80 L 567,-80 z"/>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="407" y1="-159" x2="567" y2="-159"/>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="407" y1="-70" x2="567" y2="-70"/>
|
||||
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 407,-159 A 10,10 0 0 0 397,-149"/>
|
||||
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 577,-149 A 10,10 0 0 0 567,-159"/>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="397" y1="-149" x2="397" y2="-80"/>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="577" y1="-149" x2="577" y2="-80"/>
|
||||
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 397,-80 A 10,10 0 0 0 407,-70"/>
|
||||
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 567,-70 A 10,10 0 0 0 577,-80"/>
|
||||
<text font-size="14.1111" style="fill: #ffffff;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="487" y="-119.039">
|
||||
<tspan x="487" y="-119.039">Watcher</tspan>
|
||||
<tspan x="487" y="-101.4">Actions Applier</tspan>
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<rect style="fill: #ffb400" x="63.086" y="-136.5" width="70.7" height="157"/>
|
||||
<path style="fill: #ffb400" d="M 63.086,-136.5 A 10,10 0 0 0 53.086,-126.5 L 63.086,-126.5 z"/>
|
||||
<path style="fill: #ffb400" d="M 143.786,-126.5 A 10,10 0 0 0 133.786,-136.5 L 133.786,-126.5 z"/>
|
||||
<rect style="fill: #ffb400" x="53.086" y="-126.5" width="90.7" height="137"/>
|
||||
<path style="fill: #ffb400" d="M 53.086,10.5 A 10,10 0 0 0 63.086,20.5 L 63.086,10.5 z"/>
|
||||
<path style="fill: #ffb400" d="M 133.786,20.5 A 10,10 0 0 0 143.786,10.5 L 133.786,10.5 z"/>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="63.086" y1="-136.5" x2="133.786" y2="-136.5"/>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="63.086" y1="20.5" x2="133.786" y2="20.5"/>
|
||||
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 63.086,-136.5 A 10,10 0 0 0 53.086,-126.5"/>
|
||||
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 143.786,-126.5 A 10,10 0 0 0 133.786,-136.5"/>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="53.086" y1="-126.5" x2="53.086" y2="10.5"/>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="143.786" y1="-126.5" x2="143.786" y2="10.5"/>
|
||||
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 53.086,10.5 A 10,10 0 0 0 63.086,20.5"/>
|
||||
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 133.786,20.5 A 10,10 0 0 0 143.786,10.5"/>
|
||||
<text font-size="14.1111" style="fill: #ffffff;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="98.436" y="-62.5389">
|
||||
<tspan x="98.436" y="-62.5389">Watcher</tspan>
|
||||
<tspan x="98.436" y="-44.9">API</tspan>
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<rect style="fill: #ffb400" x="-138.614" y="-124.5" width="105" height="69.1"/>
|
||||
<path style="fill: #ffb400" d="M -138.614,-124.5 A 10,10 0 0 0 -148.614,-114.5 L -138.614,-114.5 z"/>
|
||||
<path style="fill: #ffb400" d="M -23.614,-114.5 A 10,10 0 0 0 -33.614,-124.5 L -33.614,-114.5 z"/>
|
||||
<rect style="fill: #ffb400" x="-148.614" y="-114.5" width="125" height="49.1"/>
|
||||
<path style="fill: #ffb400" d="M -148.614,-65.4 A 10,10 0 0 0 -138.614,-55.4 L -138.614,-65.4 z"/>
|
||||
<path style="fill: #ffb400" d="M -33.614,-55.4 A 10,10 0 0 0 -23.614,-65.4 L -33.614,-65.4 z"/>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="-138.614" y1="-124.5" x2="-33.614" y2="-124.5"/>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="-138.614" y1="-55.4" x2="-33.614" y2="-55.4"/>
|
||||
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M -138.614,-124.5 A 10,10 0 0 0 -148.614,-114.5"/>
|
||||
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M -23.614,-114.5 A 10,10 0 0 0 -33.614,-124.5"/>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="-148.614" y1="-114.5" x2="-148.614" y2="-65.4"/>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="-23.614" y1="-114.5" x2="-23.614" y2="-65.4"/>
|
||||
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M -148.614,-65.4 A 10,10 0 0 0 -138.614,-55.4"/>
|
||||
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M -33.614,-55.4 A 10,10 0 0 0 -23.614,-65.4"/>
|
||||
<text font-size="14.1111" style="fill: #ffffff;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="-86.114" y="-94.4889">
|
||||
<tspan x="-86.114" y="-94.4889">Watcher</tspan>
|
||||
<tspan x="-86.114" y="-76.85">CLI</tspan>
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<rect style="fill: #ffffff" x="-155.614" y="-29.5" width="117" height="56"/>
|
||||
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="-155.614" y="-29.5" 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="-97.114" y="2.4">
|
||||
<tspan x="-97.114" y="2.4">Horizon</tspan>
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<rect style="fill: #ffb400" x="-111.614" y="12.6" width="90" height="59.9"/>
|
||||
<path style="fill: #ffb400" d="M -111.614,12.6 A 10,10 0 0 0 -121.614,22.6 L -111.614,22.6 z"/>
|
||||
<path style="fill: #ffb400" d="M -11.614,22.6 A 10,10 0 0 0 -21.614,12.6 L -21.614,22.6 z"/>
|
||||
<rect style="fill: #ffb400" x="-121.614" y="22.6" width="110" height="39.9"/>
|
||||
<path style="fill: #ffb400" d="M -121.614,62.5 A 10,10 0 0 0 -111.614,72.5 L -111.614,62.5 z"/>
|
||||
<path style="fill: #ffb400" d="M -21.614,72.5 A 10,10 0 0 0 -11.614,62.5 L -21.614,62.5 z"/>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="-111.614" y1="12.6" x2="-21.614" y2="12.6"/>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="-111.614" y1="72.5" x2="-21.614" y2="72.5"/>
|
||||
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M -111.614,12.6 A 10,10 0 0 0 -121.614,22.6"/>
|
||||
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M -11.614,22.6 A 10,10 0 0 0 -21.614,12.6"/>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="-121.614" y1="22.6" x2="-121.614" y2="62.5"/>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" x1="-11.614" y1="22.6" x2="-11.614" y2="62.5"/>
|
||||
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M -121.614,62.5 A 10,10 0 0 0 -111.614,72.5"/>
|
||||
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M -21.614,72.5 A 10,10 0 0 0 -11.614,62.5"/>
|
||||
<text font-size="14.1111" style="fill: #ffffff;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="-66.614" y="38.0111">
|
||||
<tspan x="-66.614" y="38.0111">Watcher</tspan>
|
||||
<tspan x="-66.614" y="55.65">plugin</tspan>
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<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>
|
||||
<rect style="fill: #ffffff" x="822.786" y="122.5" width="167.6" height="59.501"/>
|
||||
<rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="822.786" y="122.5" width="167.6" height="59.501"/>
|
||||
<text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="906.586" y="156.151">
|
||||
<tspan x="906.586" y="156.151">MariaDB Database</tspan>
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<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"/>
|
||||
<polygon style="fill: #000000" points="338.622,-92.9723 348.683,-97.8482 346.121,-92.8795 348.559,-87.849 "/>
|
||||
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="338.622,-92.9723 348.683,-97.8482 346.121,-92.8795 348.559,-87.849 "/>
|
||||
<polygon style="fill: #000000" points="394.764,-92.2777 384.703,-87.4018 387.265,-92.3705 384.827,-97.401 "/>
|
||||
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="394.764,-92.2777 384.703,-87.4018 387.265,-92.3705 384.827,-97.401 "/>
|
||||
</g>
|
||||
<g>
|
||||
<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="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"/>
|
||||
<polygon style="fill: #000000" points="51.0219,-58.8598 39.8681,-58.0896 44.0985,-61.7438 43.7134,-67.3207 "/>
|
||||
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="51.0219,-58.8598 39.8681,-58.0896 44.0985,-61.7438 43.7134,-67.3207 "/>
|
||||
</g>
|
||||
<g>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="-11.614" y1="42.55" x2="46.0184" y2="-12.0538"/>
|
||||
<polygon style="fill: #000000" points="51.4628,-17.2121 47.6424,-6.70471 46.0184,-12.0538 40.7647,-13.9639 "/>
|
||||
<polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="51.4628,-17.2121 47.6424,-6.70471 46.0184,-12.0538 40.7647,-13.9639 "/>
|
||||
</g>
|
||||
<g>
|
||||
<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="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>
|
||||
<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="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"/>
|
||||
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" 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.3333"/>
|
||||
<path style="fill: none; fill-opacity:0; stroke-width: 4; stroke: #ffffff" d="M 844 38.3333 C 865.877,53.5833 876.816,58.6667 898.693,58.6667 C 920.57,58.6667 931.509,53.5833 953.386,38.3333"/>
|
||||
<text font-size="12.8" style="fill: #ffffff;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:700" x="898.693" y="93.1667">
|
||||
<tspan x="898.693" y="93.1667">Watcher DB</tspan>
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<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>
|
||||
<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>
|
||||
|
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 |
@@ -1,17 +1,37 @@
|
||||
============================================
|
||||
Welcome to Watcher's developer documentation
|
||||
============================================
|
||||
..
|
||||
Except where otherwise noted, this document is licensed under Creative
|
||||
Commons Attribution 3.0 License. You can view the license at:
|
||||
|
||||
Mission
|
||||
=======
|
||||
https://creativecommons.org/licenses/by/3.0/
|
||||
|
||||
Provide an auditing service for OpenStack to improve workload placement
|
||||
on-the-go.
|
||||
================================
|
||||
Welcome to Watcher documentation
|
||||
================================
|
||||
|
||||
The developer documentation provided here is continually kept up-to-date based
|
||||
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!
|
||||
|
||||
Watcher project consists of several source code repositories:
|
||||
|
||||
* `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
|
||||
===============
|
||||
|
||||
@@ -21,11 +41,21 @@ Introduction
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
dev/glossary
|
||||
dev/architecture
|
||||
dev/environment
|
||||
glossary
|
||||
architecture
|
||||
dev/contributing
|
||||
dev/plugins
|
||||
|
||||
|
||||
Getting Started
|
||||
---------------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
dev/environment
|
||||
dev/devstack
|
||||
deploy/configuration
|
||||
dev/testing
|
||||
|
||||
|
||||
API References
|
||||
@@ -36,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
|
||||
@@ -1,8 +1,14 @@
|
||||
..
|
||||
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/
|
||||
|
||||
.. _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
|
||||
@@ -54,7 +60,8 @@ Usage
|
||||
=====
|
||||
|
||||
Options for the various :ref:`commands <db-manage_cmds>` for
|
||||
:command:`watcher-db-manage` are listed when the :option:`-h` or :option:`--help`
|
||||
:command:`watcher-db-manage` are listed when the :option:`-h` or
|
||||
:option:`--help`
|
||||
option is used after the command.
|
||||
|
||||
For example::
|
||||
@@ -81,8 +88,9 @@ If no configuration file is specified with the :option:`--config-file` option,
|
||||
Command Options
|
||||
===============
|
||||
|
||||
:command:`watcher-db-manage` is given a command that tells the utility what actions
|
||||
to perform. These commands can take arguments. Several commands are available:
|
||||
:command:`watcher-db-manage` is given a command that tells the utility
|
||||
what actions to perform.
|
||||
These commands can take arguments. Several commands are available:
|
||||
|
||||
.. _create_schema:
|
||||
|
||||
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,7 +0,0 @@
|
||||
========
|
||||
Usage
|
||||
========
|
||||
|
||||
To use watcher in a project::
|
||||
|
||||
import watcher
|
||||
@@ -1,12 +1,31 @@
|
||||
=====================
|
||||
RESTful Web API (v1)
|
||||
=====================
|
||||
..
|
||||
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/
|
||||
|
||||
====================
|
||||
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:
|
||||
@@ -14,7 +33,6 @@ Audit Templates
|
||||
.. autotype:: watcher.api.controllers.v1.audit_template.AuditTemplate
|
||||
:members:
|
||||
|
||||
|
||||
Audits
|
||||
======
|
||||
|
||||
@@ -27,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:
|
||||
|
||||
|
||||
|
||||
@@ -2,28 +2,32 @@
|
||||
# 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.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
|
||||
voluptuous>=0.8.6 # BSD License
|
||||
python-ceilometerclient>=2.2.1 # Apache-2.0
|
||||
python-cinderclient>=1.3.1 # Apache-2.0
|
||||
python-glanceclient>=1.2.0 # Apache-2.0
|
||||
python-keystoneclient!=1.8.0,!=2.1.0,>=1.6.0 # Apache-2.0
|
||||
python-neutronclient>=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
|
||||
|
||||
36
setup.cfg
@@ -16,12 +16,12 @@ classifier =
|
||||
Programming Language :: Python :: 2
|
||||
Programming Language :: Python :: 2.7
|
||||
Programming Language :: Python :: 3
|
||||
Programming Language :: Python :: 3.3
|
||||
Programming Language :: Python :: 3.4
|
||||
|
||||
[files]
|
||||
packages =
|
||||
watcher
|
||||
watcher_tempest_plugin
|
||||
data_files =
|
||||
etc/ = etc/*
|
||||
|
||||
@@ -39,16 +39,44 @@ 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
|
||||
|
||||
watcher_strategies =
|
||||
dummy = watcher.decision_engine.strategy.dummy_strategy:DummyStrategy
|
||||
basic = watcher.decision_engine.strategy.basic_consolidation:BasicConsolidation
|
||||
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
|
||||
|
||||
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
|
||||
build-dir = doc/build
|
||||
fresh_env = 1
|
||||
all_files = 1
|
||||
|
||||
[upload_sphinx]
|
||||
@@ -65,6 +93,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,19 @@
|
||||
# of appearance. Changing the order has an impact on the overall integration
|
||||
# process, which may cause wedges in the gate later.
|
||||
|
||||
hacking<0.11,>=0.10
|
||||
coverage>=3.6
|
||||
discover
|
||||
python-subunit>=0.0.18
|
||||
oslotest>=1.10.0 # Apache-2.0
|
||||
testrepository>=0.0.18
|
||||
testscenarios>=0.4
|
||||
testtools>=1.4.0
|
||||
mock>=1.2
|
||||
coverage>=3.6 # Apache-2.0
|
||||
discover # BSD
|
||||
doc8 # 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
|
||||
sphinx>=1.1.2,!=1.2.0,!=1.3b1,<1.3
|
||||
oslosphinx>=2.5.0 # Apache-2.0
|
||||
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
|
||||
|
||||
41
tox.ini
@@ -1,31 +1,41 @@
|
||||
[tox]
|
||||
minversion = 1.6
|
||||
envlist = py33,py34,py27,pypy,pep8
|
||||
envlist = py34,py27,pep8
|
||||
skipsdist = True
|
||||
|
||||
[testenv]
|
||||
usedevelop = True
|
||||
whitelist_externals = find
|
||||
install_command = pip install -U {opts} {packages}
|
||||
setenv =
|
||||
VIRTUAL_ENV={envdir}
|
||||
deps = -r{toxinidir}/requirements.txt
|
||||
-r{toxinidir}/test-requirements.txt
|
||||
commands = python setup.py testr --slowest --testr-args='{posargs}'
|
||||
commands =
|
||||
find . -type f -name "*.pyc" -delete
|
||||
find . -type d -name "__pycache__" -delete
|
||||
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/*,watcher/openstack/*" --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
|
||||
@@ -38,17 +48,18 @@ commands =
|
||||
--output-file etc/watcher/watcher.conf.sample
|
||||
|
||||
[flake8]
|
||||
# E123, E125 skipped as they are invalid PEP-8.
|
||||
|
||||
show-source=True
|
||||
ignore=E123,E125,H404,H405,H305
|
||||
ignore=
|
||||
builtins= _
|
||||
exclude=.venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build,*sqlalchemy/alembic/versions/*,demo/
|
||||
|
||||
[testenv:pypi]
|
||||
commands =
|
||||
python setup.py sdist bdist_wheel
|
||||
twine upload --config-file .pypirc {posargs} dist/*
|
||||
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build,*sqlalchemy/alembic/versions/*,demo/
|
||||
|
||||
[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
|
||||
|
||||
46
watcher/_i18n.py
Normal file
@@ -0,0 +1,46 @@
|
||||
# -*- 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.
|
||||
#
|
||||
import oslo_i18n
|
||||
|
||||
# The domain is the name of the App which is used to generate the folder
|
||||
# containing the translation files (i.e. the .pot file and the various locales)
|
||||
DOMAIN = "watcher"
|
||||
|
||||
_translators = oslo_i18n.TranslatorFactory(domain=DOMAIN)
|
||||
|
||||
# The primary translation function using the well-known name "_"
|
||||
_ = _translators.primary
|
||||
|
||||
# The contextual translation function using the name "_C"
|
||||
_C = _translators.contextual_form
|
||||
|
||||
# The plural translation function using the name "_P"
|
||||
_P = _translators.plural_form
|
||||
|
||||
# Translators for log levels.
|
||||
#
|
||||
# The abbreviated names are meant to reflect the usual use of a short
|
||||
# name like '_'. The "L" is for "log" and the other letter comes from
|
||||
# the level.
|
||||
_LI = _translators.log_info
|
||||
_LW = _translators.log_warning
|
||||
_LE = _translators.log_error
|
||||
_LC = _translators.log_critical
|
||||
|
||||
|
||||
def get_available_languages():
|
||||
return oslo_i18n.get_available_languages(DOMAIN)
|
||||
@@ -1,6 +0,0 @@
|
||||
# Watcher API
|
||||
|
||||
This component implements the REST API provided by the Watcher system to the external world. It enables a cluster administrator to control and monitor the Watcher system via any interaction mechanism connected to this API :
|
||||
* CLI
|
||||
* Horizon plugin
|
||||
* Python SDK
|
||||
@@ -22,7 +22,7 @@ import pecan
|
||||
from watcher.api import acl
|
||||
from watcher.api import config as api_config
|
||||
from watcher.api import middleware
|
||||
from watcher.decision_engine.strategy.selector import default \
|
||||
from watcher.decision_engine.strategy.selection import default \
|
||||
as strategy_selector
|
||||
|
||||
# Register options for the service
|
||||
|
||||
@@ -15,6 +15,42 @@
|
||||
# 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>`
|
||||
"""
|
||||
|
||||
import datetime
|
||||
|
||||
import pecan
|
||||
@@ -87,29 +123,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,
|
||||
@@ -199,7 +223,7 @@ class ActionCollection(collection.Collection):
|
||||
reverse = True if kwargs['sort_dir'] == 'desc' else False
|
||||
collection.actions = sorted(
|
||||
collection.actions,
|
||||
key=lambda action: action.next_uuid,
|
||||
key=lambda action: action.next_uuid or '',
|
||||
reverse=reverse)
|
||||
|
||||
collection.next = collection.get_next(limit, url=url, **kwargs)
|
||||
@@ -229,9 +253,8 @@ class ActionsController(rest.RestController):
|
||||
sort_key, sort_dir, expand=False,
|
||||
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:
|
||||
@@ -262,10 +285,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.
|
||||
@@ -286,16 +309,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.
|
||||
|
||||
@@ -15,6 +15,60 @@
|
||||
# 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/>`_.
|
||||
|
||||
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>`
|
||||
""" # noqa
|
||||
|
||||
import datetime
|
||||
|
||||
import pecan
|
||||
@@ -23,21 +77,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 +181,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 +188,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 +283,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 +312,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 +323,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 +382,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 +402,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 +442,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,40 @@
|
||||
# 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>`.
|
||||
|
||||
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>`
|
||||
"""
|
||||
|
||||
import datetime
|
||||
|
||||
import pecan
|
||||
@@ -23,6 +57,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 +65,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 +216,7 @@ class AuditCollection(collection.Collection):
|
||||
"""A list containing audits objects"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(AuditCollection, self).__init__()
|
||||
self._type = 'audits'
|
||||
|
||||
@staticmethod
|
||||
@@ -227,7 +263,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 +293,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 +310,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 +354,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 +369,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,6 +15,39 @@
|
||||
# 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
|
||||
|
||||
import pecan
|
||||
@@ -116,7 +149,7 @@ 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,
|
||||
@@ -131,6 +164,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 +197,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 +213,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 +225,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 +259,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 +271,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
|
||||
|
||||
@@ -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 = []
|
||||
|
||||
|
||||
@@ -21,8 +21,8 @@ import six
|
||||
import wsme
|
||||
from wsme import types as wtypes
|
||||
|
||||
from watcher._i18n import _
|
||||
from watcher.common import exception
|
||||
from watcher.common.i18n import _
|
||||
from watcher.common import utils
|
||||
|
||||
|
||||
@@ -181,8 +181,9 @@ class MultiType(wtypes.UserType):
|
||||
return value
|
||||
else:
|
||||
raise ValueError(
|
||||
_("Wrong type. Expected '%(type)s', got '%(value)s'")
|
||||
% {'type': self.types, 'value': type(value)})
|
||||
_("Wrong type. Expected '%(type)s', got '%(value)s'"),
|
||||
type=self.types, value=type(value)
|
||||
)
|
||||
|
||||
|
||||
class JsonPatchType(wtypes.Base):
|
||||
@@ -217,7 +218,7 @@ class JsonPatchType(wtypes.Base):
|
||||
|
||||
@staticmethod
|
||||
def validate(patch):
|
||||
_path = '/' + patch.path.split('/')[1]
|
||||
_path = '/{0}'.format(patch.path.split('/')[1])
|
||||
if _path in patch.internal_attrs():
|
||||
msg = _("'%s' is an internal attribute and can not be updated")
|
||||
raise wsme.exc.ClientSideError(msg % patch.path)
|
||||
|
||||
@@ -17,7 +17,7 @@ import jsonpatch
|
||||
from oslo_config import cfg
|
||||
import wsme
|
||||
|
||||
from watcher.common.i18n import _
|
||||
from watcher._i18n import _
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
@@ -28,10 +28,18 @@ JSONPATCH_EXCEPTIONS = (jsonpatch.JsonPatchException,
|
||||
|
||||
|
||||
def validate_limit(limit):
|
||||
if limit is not None and limit <= 0:
|
||||
if limit is None:
|
||||
return CONF.api.max_limit
|
||||
|
||||
if limit <= 0:
|
||||
# Case where we don't a valid limit value
|
||||
raise wsme.exc.ClientSideError(_("Limit must be positive"))
|
||||
|
||||
return min(CONF.api.max_limit, limit) or CONF.api.max_limit
|
||||
if limit and not CONF.api.max_limit:
|
||||
# Case where we don't have an upper limit
|
||||
return limit
|
||||
|
||||
return min(CONF.api.max_limit, limit)
|
||||
|
||||
|
||||
def validate_sort_dir(sort_dir):
|
||||
@@ -39,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):
|
||||
@@ -50,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
|
||||
|
||||
@@ -19,8 +19,8 @@ from oslo_log import log
|
||||
|
||||
from keystonemiddleware import auth_token
|
||||
|
||||
from watcher._i18n import _
|
||||
from watcher.common import exception
|
||||
from watcher.common.i18n import _
|
||||
from watcher.common import utils
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
@@ -40,10 +40,9 @@ class AuthTokenMiddleware(auth_token.AuthProtocol):
|
||||
self.public_api_routes = [re.compile(route_pattern_tpl % route_tpl)
|
||||
for route_tpl in public_api_routes]
|
||||
except re.error as e:
|
||||
msg = _('Cannot compile public API routes: %s') % e
|
||||
|
||||
LOG.error(msg)
|
||||
raise exception.ConfigInvalid(error_msg=msg)
|
||||
LOG.exception(e)
|
||||
raise exception.ConfigInvalid(
|
||||
error_msg=_('Cannot compile public API routes'))
|
||||
|
||||
super(AuthTokenMiddleware, self).__init__(app, conf)
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
Middleware to replace the plain text message body of an error
|
||||
response with one formatted so the client can parse it.
|
||||
@@ -24,12 +23,12 @@ Based on pecan.middleware.errordocument
|
||||
import json
|
||||
from xml import etree as et
|
||||
|
||||
from oslo_log import log
|
||||
import six
|
||||
import webob
|
||||
|
||||
from oslo_log import log
|
||||
|
||||
from watcher.common.i18n import _
|
||||
from watcher.common.i18n import _LE
|
||||
from watcher._i18n import _
|
||||
from watcher._i18n import _LE
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
@@ -69,23 +68,27 @@ class ParsableErrorMiddleware(object):
|
||||
app_iter = self.app(environ, replacement_start_response)
|
||||
if (state['status_code'] // 100) not in (2, 3):
|
||||
req = webob.Request(environ)
|
||||
if (req.accept.best_match(['application/json', 'application/xml'])
|
||||
== 'application/xml'):
|
||||
if (req.accept.best_match(['application/json', 'application/xml']
|
||||
) == 'application/xml'):
|
||||
try:
|
||||
# simple check xml is valid
|
||||
body = [et.ElementTree.tostring(
|
||||
et.ElementTree.fromstring('<error_message>'
|
||||
+ '\n'.join(app_iter)
|
||||
+ '</error_message>'))]
|
||||
et.ElementTree.Element('error_message',
|
||||
text='\n'.join(app_iter)))]
|
||||
except et.ElementTree.ParseError as err:
|
||||
LOG.error(_LE('Error parsing HTTP response: %s'), err)
|
||||
body = ['<error_message>%s' % state['status_code']
|
||||
+ '</error_message>']
|
||||
body = [et.ElementTree.tostring(
|
||||
et.ElementTree.Element('error_message',
|
||||
text=state['status_code']))]
|
||||
state['headers'].append(('Content-Type', 'application/xml'))
|
||||
else:
|
||||
if six.PY3:
|
||||
app_iter = [i.decode('utf-8') for i in app_iter]
|
||||
body = [json.dumps({'error_message': '\n'.join(app_iter)})]
|
||||
if six.PY3:
|
||||
body = [item.encode('utf-8') for item in body]
|
||||
state['headers'].append(('Content-Type', 'application/json'))
|
||||
state['headers'].append(('Content-Length', len(body[0])))
|
||||
state['headers'].append(('Content-Length', str(len(body[0]))))
|
||||
else:
|
||||
body = app_iter
|
||||
return body
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
# Watcher Actions Applier
|
||||
|
||||
This component is in charge of executing the plan of actions built by the Watcher Actions Planner.
|
||||
|
||||
For each action of the workflow, this component may call directly the component responsible for this kind of action (Example : Nova API for an instance migration) or via some publish/subscribe pattern on the message bus.
|
||||
|
||||
It notifies continuously of the current progress of the Action Plan (and atomic Actions), sending status messages on the bus. Those events may be used by the CEP to trigger new actions.
|
||||
|
||||
This component is also connected to the Watcher MySQL database in order to:
|
||||
* get the description of the action plan to execute
|
||||
* persist its current state so that if it is restarted, it can restore each Action plan context and restart from the last known safe point of each ongoing workflow.
|
||||
@@ -22,8 +22,7 @@ import six
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class ApplierCommand(object):
|
||||
class BaseActionPlanHandler(object):
|
||||
@abc.abstractmethod
|
||||
def execute(self):
|
||||
raise NotImplementedError(
|
||||
"Should have implemented this") # pragma:no cover
|
||||
raise NotImplementedError()
|
||||
68
watcher/applier/action_plan/default.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.
|
||||
#
|
||||
from oslo_log import log
|
||||
|
||||
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(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.applier_manager = applier_manager
|
||||
|
||||
def notify(self, uuid, event_type, state):
|
||||
action_plan = ap_objects.ActionPlan.get_by_uuid(self.ctx, uuid)
|
||||
action_plan.state = state
|
||||
action_plan.save()
|
||||
ev = event.Event()
|
||||
ev.type = event_type
|
||||
ev.data = {}
|
||||
payload = {'action_plan__uuid': uuid,
|
||||
'action_plan_state': state}
|
||||
self.applier_manager.status_topic_handler.publish_event(
|
||||
ev.type.name, payload)
|
||||
|
||||
def execute(self):
|
||||
try:
|
||||
# update state
|
||||
self.notify(self.action_plan_uuid,
|
||||
event_types.EventTypes.LAUNCH_ACTION_PLAN,
|
||||
ap_objects.State.ONGOING)
|
||||
applier = default.DefaultApplier(self.ctx, self.applier_manager)
|
||||
result = applier.execute(self.action_plan_uuid)
|
||||
except Exception as e:
|
||||
LOG.exception(e)
|
||||
result = False
|
||||
finally:
|
||||
if result is True:
|
||||
status = ap_objects.State.SUCCEEDED
|
||||
else:
|
||||
status = ap_objects.State.FAILED
|
||||
# update state
|
||||
self.notify(self.action_plan_uuid,
|
||||
event_types.EventTypes.LAUNCH_ACTION_PLAN,
|
||||
status)
|
||||
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
|
||||
@@ -5,7 +5,7 @@
|
||||
# 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
|
||||
# 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,
|
||||
@@ -13,17 +13,17 @@
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
import logging
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
|
||||
from watcher.common.loader import default
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
def prepare_service(args=None, conf=cfg.CONF):
|
||||
log.register_options(conf)
|
||||
log.setup(conf, 'watcher')
|
||||
conf(args, project='python-watcher')
|
||||
conf.log_opt_values(LOG, logging.DEBUG)
|
||||
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,13 +17,20 @@
|
||||
# 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
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class Applier(object):
|
||||
class BaseApplier(object):
|
||||
@abc.abstractmethod
|
||||
def execute(self, action_plan_uuid):
|
||||
raise NotImplementedError(
|
||||
"Should have implemented this") # pragma:no cover
|
||||
raise NotImplementedError()
|
||||
|
||||
@@ -16,24 +16,49 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
from watcher.applier.base import Applier
|
||||
from watcher.applier.execution.executor import CommandExecutor
|
||||
from watcher.objects import Action
|
||||
from watcher.objects import ActionPlan
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
|
||||
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(Applier):
|
||||
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 = CommandExecutor(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,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 oslo_log import log
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class DeployPhase(object):
|
||||
def __init__(self, executor):
|
||||
# todo(jed) oslo_conf 10 secondes
|
||||
self.maxTimeout = 100000
|
||||
self.commands = []
|
||||
self.executor = executor
|
||||
|
||||
def set_max_time(self, mt):
|
||||
self.maxTimeout = mt
|
||||
|
||||
def get_max_time(self):
|
||||
return self.maxTimeout
|
||||
|
||||
def populate(self, action):
|
||||
self.commands.append(action)
|
||||
|
||||
def execute_primitive(self, primitive):
|
||||
futur = primitive.execute(primitive)
|
||||
return futur.result(self.get_max_time())
|
||||
|
||||
def rollback(self):
|
||||
reverted = sorted(self.commands, reverse=True)
|
||||
for primitive in reverted:
|
||||
try:
|
||||
self.execute_primitive(primitive)
|
||||
except Exception as e:
|
||||
LOG.error(e)
|
||||
@@ -1,78 +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.mapper.default import DefaultCommandMapper
|
||||
|
||||
from watcher.applier.execution.deploy_phase import DeployPhase
|
||||
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 CommandExecutor(object):
|
||||
def __init__(self, manager_applier, context):
|
||||
self.manager_applier = manager_applier
|
||||
self.context = context
|
||||
self.deploy = DeployPhase(self)
|
||||
self.mapper = DefaultCommandMapper()
|
||||
|
||||
def get_primitive(self, action):
|
||||
return self.mapper.build_primitive_command(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.set_type(Events.LAUNCH_ACTION)
|
||||
event.set_data({})
|
||||
payload = {'action_uuid': action.uuid,
|
||||
'action_status': state}
|
||||
self.manager_applier.topic_status.publish_event(event.get_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,29 +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.common.messaging.notification_handler import NotificationHandler
|
||||
from watcher.decision_engine.messaging.events import Events
|
||||
|
||||
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 '
|
||||
@@ -48,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',
|
||||
@@ -57,46 +62,18 @@ opt_group = cfg.OptGroup(name='watcher_applier',
|
||||
CONF.register_group(opt_group)
|
||||
CONF.register_opts(APPLIER_MANAGER_OPTS, opt_group)
|
||||
|
||||
CONF.import_opt('admin_user', 'keystonemiddleware.auth_token',
|
||||
group='keystone_authtoken')
|
||||
CONF.import_opt('admin_tenant_name', 'keystonemiddleware.auth_token',
|
||||
group='keystone_authtoken')
|
||||
CONF.import_opt('admin_password', 'keystonemiddleware.auth_token',
|
||||
group='keystone_authtoken')
|
||||
CONF.import_opt('auth_uri', 'keystonemiddleware.auth_token',
|
||||
group='keystone_authtoken')
|
||||
|
||||
|
||||
class ApplierManager(MessagingCore):
|
||||
# todo(jed) need workflow
|
||||
|
||||
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)
|
||||
self.handler = NotificationHandler(self.publisher_id)
|
||||
self.handler.register_observer(self)
|
||||
self.add_event_listener(Events.ALL, self.event_receive)
|
||||
# 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()
|
||||
|
||||
def event_receive(self, event):
|
||||
try:
|
||||
request_id = event.get_request_id()
|
||||
event_type = event.get_type()
|
||||
data = event.get_data()
|
||||
LOG.debug("request id => %s" % request_id)
|
||||
LOG.debug("type_event => %s" % str(event_type))
|
||||
LOG.debug("data => %s" % str(data))
|
||||
except Exception as e:
|
||||
LOG.error("evt %s" % e.message)
|
||||
raise e
|
||||
self.conductor_topic_handler.join()
|
||||
self.status_topic_handler.join()
|
||||
|
||||
@@ -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.mapper.base import CommandMapper
|
||||
from watcher.applier.primitive.hypervisor_state import HypervisorStateCommand
|
||||
from watcher.applier.primitive.migration import MigrateCommand
|
||||
from watcher.applier.primitive.nop import NopCommand
|
||||
from watcher.applier.primitive.power_state import PowerStateCommand
|
||||
from watcher.common.exception import ActionNotFound
|
||||
from watcher.decision_engine.planner.default import Primitives
|
||||
|
||||
|
||||
class DefaultCommandMapper(CommandMapper):
|
||||
def build_primitive_command(self, action):
|
||||
if action.action_type == Primitives.COLD_MIGRATE.value:
|
||||
return MigrateCommand(action.applies_to, Primitives.COLD_MIGRATE,
|
||||
action.src,
|
||||
action.dst)
|
||||
elif action.action_type == Primitives.LIVE_MIGRATE.value:
|
||||
return MigrateCommand(action.applies_to, Primitives.COLD_MIGRATE,
|
||||
action.src,
|
||||
action.dst)
|
||||
elif action.action_type == Primitives.HYPERVISOR_STATE.value:
|
||||
return HypervisorStateCommand(action.applies_to, action.parameter)
|
||||
elif action.action_type == Primitives.POWER_STATE.value:
|
||||
return PowerStateCommand()
|
||||
elif action.action_type == Primitives.NOP.value:
|
||||
return NopCommand()
|
||||
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"
|
||||
@@ -1,68 +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.default import DefaultApplier
|
||||
from watcher.applier.messaging.applier_command import ApplierCommand
|
||||
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
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class LaunchActionPlanCommand(ApplierCommand):
|
||||
def __init__(self, context, manager_applier, action_plan_uuid):
|
||||
super(LaunchActionPlanCommand, self).__init__()
|
||||
self.ctx = context
|
||||
self.action_plan_uuid = action_plan_uuid
|
||||
self.manager_applier = manager_applier
|
||||
|
||||
def notify(self, uuid, event_type, status):
|
||||
action_plan = ActionPlan.get_by_uuid(self.ctx, uuid)
|
||||
action_plan.state = status
|
||||
action_plan.save()
|
||||
event = Event()
|
||||
event.set_type(event_type)
|
||||
event.set_data({})
|
||||
payload = {'action_plan__uuid': uuid,
|
||||
'action_plan_status': status}
|
||||
self.manager_applier.topic_status.publish_event(event.get_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)
|
||||
except Exception as e:
|
||||
result = False
|
||||
LOG.error("Launch Action Plan " + unicode(e))
|
||||
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)
|
||||
@@ -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.messaging.launcher import LaunchActionPlanCommand
|
||||
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 = LaunchActionPlanCommand(context,
|
||||
self.manager_applier,
|
||||
action_plan_uuid)
|
||||
cmd = default.DefaultActionPlanHandler(context,
|
||||
self.applier_manager,
|
||||
action_plan_uuid)
|
||||
cmd.execute()
|
||||
except Exception as e:
|
||||
LOG.error("do_launch_action_plan " + unicode(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,36 +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
|
||||
from watcher.applier.promise import Promise
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class PrimitiveCommand(object):
|
||||
@Promise
|
||||
@abc.abstractmethod
|
||||
def execute(self):
|
||||
raise NotImplementedError(
|
||||
"Should have implemented this") # pragma:no cover
|
||||
|
||||
@Promise
|
||||
@abc.abstractmethod
|
||||
def undo(self):
|
||||
raise NotImplementedError(
|
||||
"Should have implemented this") # pragma:no cover
|
||||
@@ -1,61 +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.applier.primitive.base import PrimitiveCommand
|
||||
from watcher.applier.primitive.wrapper.nova_wrapper import NovaWrapper
|
||||
from watcher.applier.promise import Promise
|
||||
|
||||
from watcher.common.keystone import KeystoneClient
|
||||
from watcher.decision_engine.model.hypervisor_state import HypervisorState
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class HypervisorStateCommand(PrimitiveCommand):
|
||||
def __init__(self, host, status):
|
||||
self.host = host
|
||||
self.status = status
|
||||
|
||||
def nova_manage_service(self, status):
|
||||
keystone = KeystoneClient()
|
||||
wrapper = NovaWrapper(keystone.get_credentials(),
|
||||
session=keystone.get_session())
|
||||
if status is True:
|
||||
return wrapper.enable_service_nova_compute(self.host)
|
||||
else:
|
||||
return wrapper.disable_service_nova_compute(self.host)
|
||||
|
||||
@Promise
|
||||
def execute(self):
|
||||
if self.status == HypervisorState.OFFLINE.value:
|
||||
state = False
|
||||
elif self.status == HypervisorState.ONLINE.value:
|
||||
state = True
|
||||
return self.nova_manage_service(state)
|
||||
|
||||
@Promise
|
||||
def undo(self):
|
||||
if self.status == HypervisorState.OFFLINE.value:
|
||||
state = True
|
||||
elif self.status == HypervisorState.ONLINE.value:
|
||||
state = False
|
||||
return self.nova_manage_service(state)
|
||||
@@ -1,87 +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.primitive.base import PrimitiveCommand
|
||||
from watcher.applier.primitive.wrapper.nova_wrapper import NovaWrapper
|
||||
from watcher.applier.promise import Promise
|
||||
from watcher.common.keystone import KeystoneClient
|
||||
from watcher.decision_engine.planner.default import Primitives
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class MigrateCommand(PrimitiveCommand):
|
||||
def __init__(self, vm_uuid=None,
|
||||
migration_type=None,
|
||||
source_hypervisor=None,
|
||||
destination_hypervisor=None):
|
||||
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 = NovaWrapper(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 = NovaWrapper(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,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)
|
||||