Compare commits
362 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d398b4d22 | ||
|
|
585fbeb9ee | ||
|
|
e9f237dc80 | ||
|
|
38f6700144 | ||
|
|
30bdf29002 | ||
|
|
e6f147d81d | ||
|
|
5aa6b16238 | ||
|
|
dcb5c1f9fc | ||
|
|
4ba01cbbcf | ||
|
|
d91d72d2c2 | ||
|
|
083bc2bed4 | ||
|
|
9d3671af37 | ||
|
|
3b88e37680 | ||
|
|
6eee64502f | ||
|
|
b9231f65cc | ||
|
|
b9b505a518 | ||
|
|
388ef9f11c | ||
|
|
4e3caaa157 | ||
|
|
8c6bf734af | ||
|
|
277a749ca0 | ||
|
|
8401b5e479 | ||
|
|
78689fbe3b | ||
|
|
22abaa9c3a | ||
|
|
fb82131d85 | ||
|
|
f6f5079adb | ||
|
|
f045f5d816 | ||
|
|
b77541deb2 | ||
|
|
3b9d72439c | ||
|
|
89aa2d54df | ||
|
|
86f4cee588 | ||
|
|
04ac509821 | ||
|
|
4ba9d2cb73 | ||
|
|
a71c9be860 | ||
|
|
c2cb1a1f8e | ||
|
|
79bdcf7baf | ||
|
|
de1b1a9938 | ||
|
|
031ebdecde | ||
|
|
daabe671c7 | ||
|
|
26bc3d139d | ||
|
|
4d2536b9b2 | ||
|
|
4388780e66 | ||
|
|
d03a9197b0 | ||
|
|
43f5ab18ba | ||
|
|
209176c3d7 | ||
|
|
1a21867735 | ||
|
|
5f6a97148f | ||
|
|
e6b23a0856 | ||
|
|
f9a1b9d3ce | ||
|
|
ff611544fb | ||
|
|
18e5c7d844 | ||
|
|
2966b93777 | ||
|
|
e67b532110 | ||
|
|
81765b9aa5 | ||
|
|
673642e436 | ||
|
|
1026a896e2 | ||
|
|
a3ac26870a | ||
|
|
192d8e262c | ||
|
|
3b5ef15db6 | ||
|
|
be9058f3e3 | ||
|
|
91951f3b01 | ||
|
|
57a2af2685 | ||
|
|
76e3d2e2f6 | ||
|
|
bd5a969a26 | ||
|
|
d61bf5f053 | ||
|
|
aaaf3f1c84 | ||
|
|
eb861f86ab | ||
|
|
a9e7251d0d | ||
|
|
4ff373197c | ||
|
|
87087e9add | ||
|
|
408d6d4650 | ||
|
|
e52dc4f8aa | ||
|
|
0f14b7635d | ||
|
|
bb77641aad | ||
|
|
77228a0b0a | ||
|
|
1157a8db30 | ||
|
|
18354d1b4e | ||
|
|
8387cd10de | ||
|
|
0449bae747 | ||
|
|
3e07844844 | ||
|
|
a52d92be87 | ||
|
|
96683a6133 | ||
|
|
46d5094add | ||
|
|
783c7c0177 | ||
|
|
6d0717199c | ||
|
|
8b77e78f3d | ||
|
|
22c9c4df87 | ||
|
|
99ff6d3348 | ||
|
|
c67f83cce0 | ||
|
|
397bb3497e | ||
|
|
4c924fc505 | ||
|
|
4c5ecc808d | ||
|
|
64b5a7c3e4 | ||
|
|
40bb92f749 | ||
|
|
92bd06cf94 | ||
|
|
c9e0dfd3f5 | ||
|
|
446fe1307a | ||
|
|
2836f460e3 | ||
|
|
cb9bb7301b | ||
|
|
cb644fcef9 | ||
|
|
0a7c87eebf | ||
|
|
d7f4f42772 | ||
|
|
bdc0eb196a | ||
|
|
59427eb0d9 | ||
|
|
b6801b192a | ||
|
|
0a6c2c16a4 | ||
|
|
9a44941c66 | ||
|
|
a6508a0013 | ||
|
|
c3db66ca09 | ||
|
|
5d0fe553c4 | ||
|
|
8b8239c3d8 | ||
|
|
920bd502ec | ||
|
|
c68d33f341 | ||
|
|
8e8fdbd809 | ||
|
|
681536c8c7 | ||
|
|
083b170083 | ||
|
|
de7b0129a1 | ||
|
|
323fd01a85 | ||
|
|
c0133e6585 | ||
|
|
63fffeacd8 | ||
|
|
bc791f0e75 | ||
|
|
6ed417e6a7 | ||
|
|
4afefa3dfb | ||
|
|
9fadfbe40a | ||
|
|
f278874a93 | ||
|
|
1c5b247300 | ||
|
|
547bf5f87e | ||
|
|
96c0ac0ca8 | ||
|
|
a80fd2a51e | ||
|
|
58ea85c852 | ||
|
|
78f122f241 | ||
|
|
43eb997edb | ||
|
|
c440cdd69f | ||
|
|
b5bccba169 | ||
|
|
e058437ae0 | ||
|
|
1acacaa812 | ||
|
|
5bb1b6cbf0 | ||
|
|
de058d7ed1 | ||
|
|
98a65efb16 | ||
|
|
338539ec53 | ||
|
|
02f0f8e70a | ||
|
|
7f1bd20a09 | ||
|
|
d3d2a5ef8c | ||
|
|
3a6ae820c0 | ||
|
|
5a8860419e | ||
|
|
4aa1c7558b | ||
|
|
a8dab52376 | ||
|
|
5615d0523d | ||
|
|
10823ce133 | ||
|
|
18c098c4c1 | ||
|
|
db649d86b6 | ||
|
|
6e380b685b | ||
|
|
8dfff0e8e6 | ||
|
|
fbc7da755a | ||
|
|
b947c30910 | ||
|
|
9af96114af | ||
|
|
1ddf69a68f | ||
|
|
379ac791a8 | ||
|
|
81ea37de41 | ||
|
|
1c963fdc96 | ||
|
|
f32995228b | ||
|
|
0ec3d68994 | ||
|
|
3503e11506 | ||
|
|
c7f0ef37d0 | ||
|
|
d93b1ffe9f | ||
|
|
4e71a0c655 | ||
|
|
37dd713ed5 | ||
|
|
55aeb783e3 | ||
|
|
fe3f6e73be | ||
|
|
5baff7dc3e | ||
|
|
e3198d25a5 | ||
|
|
33ee575936 | ||
|
|
259f2562e6 | ||
|
|
1629247413 | ||
|
|
58d84aca6d | ||
|
|
236879490d | ||
|
|
79850cc89c | ||
|
|
b958214db8 | ||
|
|
a0b5f5aa1d | ||
|
|
a4a009a2c6 | ||
|
|
0ba8a35ade | ||
|
|
8bcc1b2097 | ||
|
|
b440f5c69a | ||
|
|
ad40c61ea9 | ||
|
|
858bbbf126 | ||
|
|
1d74f7e3bc | ||
|
|
b7641a9311 | ||
|
|
376d669af6 | ||
|
|
25d27f0288 | ||
|
|
3f4686ce79 | ||
|
|
86c1a9d77f | ||
|
|
9a6811ae6b | ||
|
|
e520f5f452 | ||
|
|
6a25bd983c | ||
|
|
c175ef2170 | ||
|
|
28733a5f30 | ||
|
|
7f8fec1bca | ||
|
|
278b1819d6 | ||
|
|
978bb11d4a | ||
|
|
3027b28942 | ||
|
|
2f0c1c12cf | ||
|
|
e122c61840 | ||
|
|
8f6eac819f | ||
|
|
de307e536e | ||
|
|
7406a1e713 | ||
|
|
982410dd3e | ||
|
|
83fdbf7366 | ||
|
|
2db5ae31c7 | ||
|
|
2191844ebb | ||
|
|
9c71b3df88 | ||
|
|
7beb9b4c29 | ||
|
|
8b7dce4803 | ||
|
|
94c50825e4 | ||
|
|
6ff4ba991e | ||
|
|
9e5a3708e1 | ||
|
|
ca5ae03b81 | ||
|
|
b47aefac30 | ||
|
|
70c7e1c639 | ||
|
|
ef78e68022 | ||
|
|
4dfe6a3fa8 | ||
|
|
b53da21b9b | ||
|
|
4305935312 | ||
|
|
894dfa0d7e | ||
|
|
a16351d352 | ||
|
|
75772cd3db | ||
|
|
63d9500904 | ||
|
|
c8096c6148 | ||
|
|
6d0754bb65 | ||
|
|
595b13a622 | ||
|
|
8832ad78e2 | ||
|
|
be81f8aff2 | ||
|
|
e95548b1a9 | ||
|
|
62b39fefbb | ||
|
|
f23548b92f | ||
|
|
43275537ea | ||
|
|
eaed650000 | ||
|
|
0505466ea5 | ||
|
|
0e7bfe61bd | ||
|
|
096354c255 | ||
|
|
afe06e2349 | ||
|
|
1d689ef410 | ||
|
|
58fb86a3da | ||
|
|
f675003076 | ||
|
|
e34ee792a8 | ||
|
|
037f43cd04 | ||
|
|
8f87699910 | ||
|
|
c811051351 | ||
|
|
a2750c74f9 | ||
|
|
67455c6a84 | ||
|
|
a7455a8bf7 | ||
|
|
7fcb683404 | ||
|
|
65cf19a7e4 | ||
|
|
facca13dc4 | ||
|
|
e8576e8963 | ||
|
|
dfc9a3b37d | ||
|
|
24dc92091c | ||
|
|
53d6d7d131 | ||
|
|
fd6ecc1b13 | ||
|
|
8bac4fd42a | ||
|
|
55d186be58 | ||
|
|
ed438d2eb2 | ||
|
|
4898df89ca | ||
|
|
ae949148ef | ||
|
|
e0242b49f1 | ||
|
|
5b57985686 | ||
|
|
f6ef197473 | ||
|
|
61c926a10b | ||
|
|
9c33f8aaeb | ||
|
|
08fc69cfc4 | ||
|
|
dc6b8aad7e | ||
|
|
473cee8ad3 | ||
|
|
2ffeb48010 | ||
|
|
86d3c2ff89 | ||
|
|
47759202a8 | ||
|
|
c0306ea8f4 | ||
|
|
34ccb7c23e | ||
|
|
9900273988 | ||
|
|
4662f248b3 | ||
|
|
7638103203 | ||
|
|
adc5587a2b | ||
|
|
34ab93a5f2 | ||
|
|
c286e1ec4b | ||
|
|
6c1769a15e | ||
|
|
b41a2cc940 | ||
|
|
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,10 @@
|
||||
[run]
|
||||
branch = True
|
||||
source = watcher
|
||||
omit = watcher/tests/*,watcher/openstack/*
|
||||
omit = watcher/tests/*
|
||||
|
||||
[report]
|
||||
ignore_errors = True
|
||||
ignore_errors = True
|
||||
exclude_lines =
|
||||
@abc.abstract
|
||||
raise NotImplementedError
|
||||
|
||||
3
.gitignore
vendored
@@ -43,6 +43,9 @@ output/*/index.html
|
||||
|
||||
# Sphinx
|
||||
doc/build
|
||||
doc/source/api
|
||||
doc/source/samples
|
||||
doc/source/watcher.conf.sample
|
||||
|
||||
# pbr generates these
|
||||
AUTHORS
|
||||
|
||||
@@ -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
|
||||
==========================
|
||||
|
||||
10
README.rst
@@ -1,15 +1,21 @@
|
||||
..
|
||||
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
|
||||
=======
|
||||
|
||||
OpenStack Watcher provides a flexible and scalable resource optimization
|
||||
service for multi-tenant OpenStack-based clouds.
|
||||
Watcher provides a complete optimization loop—including everything from a
|
||||
Watcher provides a complete optimization loop-including everything from a
|
||||
metrics receiver, complex event processor and profiler, optimization processor
|
||||
and an action plan applier. This provides a robust framework to realize a wide
|
||||
range of cloud optimization goals, including the reduction of data center
|
||||
operating costs, increased system performance via intelligent virtual machine
|
||||
migration, increased energy efficiency—and more!
|
||||
migration, increased energy efficiency-and more!
|
||||
|
||||
* Free software: Apache license
|
||||
* Wiki: http://wiki.openstack.org/wiki/Watcher
|
||||
|
||||
246
devstack/lib/watcher
Normal file
@@ -0,0 +1,246 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# lib/watcher
|
||||
# Functions to control the configuration and operation of the watcher services
|
||||
|
||||
# Dependencies:
|
||||
#
|
||||
# - ``functions`` file
|
||||
# - ``SERVICE_{TENANT_NAME|PASSWORD}`` must be defined
|
||||
# - ``DEST``, ``DATA_DIR``, ``STACK_USER`` must be defined
|
||||
|
||||
# ``stack.sh`` calls the entry points in this order:
|
||||
#
|
||||
# - is_watcher_enabled
|
||||
# - install_watcher
|
||||
# - configure_watcher
|
||||
# - create_watcher_conf
|
||||
# - init_watcher
|
||||
# - start_watcher
|
||||
# - stop_watcher
|
||||
# - cleanup_watcher
|
||||
|
||||
# Save trace setting
|
||||
_XTRACE_WATCHER=$(set +o | grep xtrace)
|
||||
set +o xtrace
|
||||
|
||||
|
||||
# Defaults
|
||||
# --------
|
||||
|
||||
# Set up default directories
|
||||
WATCHER_REPO=${WATCHER_REPO:-${GIT_BASE}/openstack/watcher.git}
|
||||
WATCHER_BRANCH=${WATCHER_BRANCH:-master}
|
||||
WATCHER_DIR=$DEST/watcher
|
||||
|
||||
GITREPO["python-watcherclient"]=${WATCHERCLIENT_REPO:-${GIT_BASE}/openstack/python-watcherclient.git}
|
||||
GITBRANCH["python-watcherclient"]=${WATCHERCLIENT_BRANCH:-master}
|
||||
GITDIR["python-watcherclient"]=$DEST/python-watcherclient
|
||||
|
||||
WATCHER_STATE_PATH=${WATCHER_STATE_PATH:=$DATA_DIR/watcher}
|
||||
WATCHER_AUTH_CACHE_DIR=${WATCHER_AUTH_CACHE_DIR:-/var/cache/watcher}
|
||||
|
||||
WATCHER_CONF_DIR=/etc/watcher
|
||||
WATCHER_CONF=$WATCHER_CONF_DIR/watcher.conf
|
||||
WATCHER_POLICY_JSON=$WATCHER_CONF_DIR/policy.json
|
||||
|
||||
if is_ssl_enabled_service "watcher" || is_service_enabled tls-proxy; then
|
||||
WATCHER_SERVICE_PROTOCOL="https"
|
||||
fi
|
||||
|
||||
# Public facing bits
|
||||
WATCHER_SERVICE_HOST=${WATCHER_SERVICE_HOST:-$HOST_IP}
|
||||
WATCHER_SERVICE_PORT=${WATCHER_SERVICE_PORT:-9322}
|
||||
WATCHER_SERVICE_PORT_INT=${WATCHER_SERVICE_PORT_INT:-19322}
|
||||
WATCHER_SERVICE_PROTOCOL=${WATCHER_SERVICE_PROTOCOL:-$SERVICE_PROTOCOL}
|
||||
|
||||
# Support entry points installation of console scripts
|
||||
if [[ -d $WATCHER_DIR/bin ]]; then
|
||||
WATCHER_BIN_DIR=$WATCHER_DIR/bin
|
||||
else
|
||||
WATCHER_BIN_DIR=$(get_python_exec_prefix)
|
||||
fi
|
||||
|
||||
# Entry Points
|
||||
# ------------
|
||||
|
||||
# Test if any watcher services are enabled
|
||||
# is_watcher_enabled
|
||||
function is_watcher_enabled {
|
||||
[[ ,${ENABLED_SERVICES} =~ ,"watcher-" ]] && return 0
|
||||
return 1
|
||||
}
|
||||
|
||||
# cleanup_watcher() - Remove residual data files, anything left over from previous
|
||||
# runs that a clean run would need to clean up
|
||||
function cleanup_watcher {
|
||||
sudo rm -rf $WATCHER_STATE_PATH $WATCHER_AUTH_CACHE_DIR
|
||||
}
|
||||
|
||||
# configure_watcher() - Set config files, create data dirs, etc
|
||||
function configure_watcher {
|
||||
# Put config files in ``/etc/watcher`` for everyone to find
|
||||
sudo install -d -o $STACK_USER $WATCHER_CONF_DIR
|
||||
|
||||
install_default_policy watcher
|
||||
|
||||
# Rebuild the config file from scratch
|
||||
create_watcher_conf
|
||||
}
|
||||
|
||||
# create_watcher_accounts() - Set up common required watcher accounts
|
||||
#
|
||||
# Project User Roles
|
||||
# ------------------------------------------------------------------
|
||||
# SERVICE_TENANT_NAME watcher service
|
||||
function create_watcher_accounts {
|
||||
create_service_user "watcher" "admin"
|
||||
|
||||
local watcher_service=$(get_or_create_service "watcher" \
|
||||
"infra-optim" "Watcher Infrastructure Optimization Service")
|
||||
get_or_create_endpoint $watcher_service \
|
||||
"$REGION_NAME" \
|
||||
"$WATCHER_SERVICE_PROTOCOL://$WATCHER_SERVICE_HOST:$WATCHER_SERVICE_PORT" \
|
||||
"$WATCHER_SERVICE_PROTOCOL://$WATCHER_SERVICE_HOST:$WATCHER_SERVICE_PORT" \
|
||||
"$WATCHER_SERVICE_PROTOCOL://$WATCHER_SERVICE_HOST:$WATCHER_SERVICE_PORT"
|
||||
}
|
||||
|
||||
# create_watcher_conf() - Create a new watcher.conf file
|
||||
function create_watcher_conf {
|
||||
# (Re)create ``watcher.conf``
|
||||
rm -f $WATCHER_CONF
|
||||
|
||||
iniset $WATCHER_CONF DEFAULT debug "$ENABLE_DEBUG_LOG_LEVEL"
|
||||
iniset $WATCHER_CONF DEFAULT control_exchange watcher
|
||||
|
||||
iniset $WATCHER_CONF database connection $(database_connection_url watcher)
|
||||
iniset $WATCHER_CONF api host "$WATCHER_SERVICE_HOST"
|
||||
iniset $WATCHER_CONF api port "$WATCHER_SERVICE_PORT"
|
||||
|
||||
iniset $WATCHER_CONF oslo_policy policy_file $WATCHER_POLICY_JSON
|
||||
|
||||
iniset $WATCHER_CONF oslo_messaging_rabbit rabbit_userid $RABBIT_USERID
|
||||
iniset $WATCHER_CONF oslo_messaging_rabbit rabbit_password $RABBIT_PASSWORD
|
||||
iniset $WATCHER_CONF oslo_messaging_rabbit rabbit_host $RABBIT_HOST
|
||||
|
||||
configure_auth_token_middleware $WATCHER_CONF watcher $WATCHER_AUTH_CACHE_DIR
|
||||
configure_auth_token_middleware $WATCHER_CONF watcher $WATCHER_AUTH_CACHE_DIR "watcher_clients_auth"
|
||||
|
||||
if is_fedora || is_suse; then
|
||||
# watcher defaults to /usr/local/bin, but fedora and suse pip like to
|
||||
# install things in /usr/bin
|
||||
iniset $WATCHER_CONF DEFAULT bindir "/usr/bin"
|
||||
fi
|
||||
|
||||
if [ -n "$WATCHER_STATE_PATH" ]; then
|
||||
iniset $WATCHER_CONF DEFAULT state_path "$WATCHER_STATE_PATH"
|
||||
iniset $WATCHER_CONF oslo_concurrency lock_path "$WATCHER_STATE_PATH"
|
||||
fi
|
||||
|
||||
if [ "$SYSLOG" != "False" ]; then
|
||||
iniset $WATCHER_CONF DEFAULT use_syslog "True"
|
||||
fi
|
||||
|
||||
# Format logging
|
||||
if [ "$LOG_COLOR" == "True" ] && [ "$SYSLOG" == "False" ]; then
|
||||
setup_colorized_logging $WATCHER_CONF DEFAULT
|
||||
else
|
||||
# Show user_name and project_name instead of user_id and project_id
|
||||
iniset $WATCHER_CONF DEFAULT logging_context_format_string "%(asctime)s.%(msecs)03d %(levelname)s %(name)s [%(request_id)s %(user_name)s %(project_name)s] %(instance)s%(message)s"
|
||||
fi
|
||||
|
||||
# Register SSL certificates if provided
|
||||
if is_ssl_enabled_service watcher; then
|
||||
ensure_certificates WATCHER
|
||||
|
||||
iniset $WATCHER_CONF DEFAULT ssl_cert_file "$WATCHER_SSL_CERT"
|
||||
iniset $WATCHER_CONF DEFAULT ssl_key_file "$WATCHER_SSL_KEY"
|
||||
|
||||
iniset $WATCHER_CONF DEFAULT enabled_ssl_apis "$WATCHER_ENABLED_APIS"
|
||||
fi
|
||||
|
||||
if is_service_enabled ceilometer; then
|
||||
iniset $WATCHER_CONF watcher_messaging notifier_driver "messaging"
|
||||
fi
|
||||
}
|
||||
|
||||
# create_watcher_cache_dir() - Part of the init_watcher() process
|
||||
function create_watcher_cache_dir {
|
||||
# Create cache dir
|
||||
sudo install -d -o $STACK_USER $WATCHER_AUTH_CACHE_DIR
|
||||
rm -rf $WATCHER_AUTH_CACHE_DIR/*
|
||||
}
|
||||
|
||||
# init_watcher() - Initialize databases, etc.
|
||||
function init_watcher {
|
||||
# clean up from previous (possibly aborted) runs
|
||||
# create required data files
|
||||
if is_service_enabled $DATABASE_BACKENDS && is_service_enabled watcher-api; then
|
||||
# (Re)create watcher database
|
||||
recreate_database watcher
|
||||
|
||||
# Create watcher schema
|
||||
$WATCHER_BIN_DIR/watcher-db-manage --config-file $WATCHER_CONF create_schema
|
||||
fi
|
||||
create_watcher_cache_dir
|
||||
}
|
||||
|
||||
# install_watcherclient() - Collect source and prepare
|
||||
function install_watcherclient {
|
||||
if use_library_from_git "python-watcherclient"; then
|
||||
git_clone_by_name "python-watcherclient"
|
||||
setup_dev_lib "python-watcherclient"
|
||||
fi
|
||||
}
|
||||
|
||||
# install_watcher() - Collect source and prepare
|
||||
function install_watcher {
|
||||
git_clone $WATCHER_REPO $WATCHER_DIR $WATCHER_BRANCH
|
||||
setup_develop $WATCHER_DIR
|
||||
}
|
||||
|
||||
# start_watcher_api() - Start the API process ahead of other things
|
||||
function start_watcher_api {
|
||||
# Get right service port for testing
|
||||
local service_port=$WATCHER_SERVICE_PORT
|
||||
local service_protocol=$WATCHER_SERVICE_PROTOCOL
|
||||
if is_service_enabled tls-proxy; then
|
||||
service_port=$WATCHER_SERVICE_PORT_INT
|
||||
service_protocol="http"
|
||||
fi
|
||||
|
||||
run_process watcher-api "$WATCHER_BIN_DIR/watcher-api --config-file $WATCHER_CONF"
|
||||
echo "Waiting for watcher-api to start..."
|
||||
if ! wait_for_service $SERVICE_TIMEOUT $service_protocol://$WATCHER_SERVICE_HOST:$service_port; then
|
||||
die $LINENO "watcher-api did not start"
|
||||
fi
|
||||
|
||||
# Start proxies if enabled
|
||||
if is_service_enabled tls-proxy; then
|
||||
start_tls_proxy '*' $WATCHER_SERVICE_PORT $WATCHER_SERVICE_HOST $WATCHER_SERVICE_PORT_INT &
|
||||
start_tls_proxy '*' $EC2_SERVICE_PORT $WATCHER_SERVICE_HOST $WATCHER_SERVICE_PORT_INT &
|
||||
fi
|
||||
}
|
||||
|
||||
# start_watcher() - Start running processes, including screen
|
||||
function start_watcher {
|
||||
# ``run_process`` checks ``is_service_enabled``, it is not needed here
|
||||
start_watcher_api
|
||||
run_process watcher-decision-engine "$WATCHER_BIN_DIR/watcher-decision-engine --config-file $WATCHER_CONF"
|
||||
run_process watcher-applier "$WATCHER_BIN_DIR/watcher-applier --config-file $WATCHER_CONF"
|
||||
}
|
||||
|
||||
# stop_watcher() - Stop running processes (non-screen)
|
||||
function stop_watcher {
|
||||
for serv in watcher-api watcher-decision-engine watcher-applier; do
|
||||
stop_process $serv
|
||||
done
|
||||
}
|
||||
|
||||
# Restore xtrace
|
||||
$_XTRACE_WATCHER
|
||||
|
||||
# Tell emacs to use shell-script-mode
|
||||
## Local variables:
|
||||
## mode: shell-script
|
||||
## End:
|
||||
46
devstack/local.conf.compute
Normal file
@@ -0,0 +1,46 @@
|
||||
# Sample ``local.conf`` for compute node for Watcher development
|
||||
# NOTE: Copy this file to the root DevStack directory for it to work properly.
|
||||
|
||||
[[local|localrc]]
|
||||
|
||||
ADMIN_PASSWORD=nomoresecrete
|
||||
DATABASE_PASSWORD=stackdb
|
||||
RABBIT_PASSWORD=stackqueue
|
||||
SERVICE_PASSWORD=$ADMIN_PASSWORD
|
||||
SERVICE_TOKEN=azertytoken
|
||||
|
||||
HOST_IP=192.168.42.2 # Change this to this compute node's IP address
|
||||
FLAT_INTERFACE=eth0
|
||||
|
||||
FIXED_RANGE=10.254.1.0/24 # Change this to whatever your network is
|
||||
NETWORK_GATEWAY=10.254.1.1 # Change this for your network
|
||||
|
||||
MULTI_HOST=1
|
||||
|
||||
SERVICE_HOST=192.168.42.1 # Change this to the IP of your controller node
|
||||
MYSQL_HOST=$SERVICE_HOST
|
||||
RABBIT_HOST=$SERVICE_HOST
|
||||
GLANCE_HOSTPORT=${SERVICE_HOST}:9292
|
||||
|
||||
DATABASE_TYPE=mysql
|
||||
|
||||
# Enable services (including neutron)
|
||||
ENABLED_SERVICES=n-cpu,n-api-meta,c-vol,q-agt
|
||||
|
||||
NOVA_VNC_ENABLED=True
|
||||
NOVNCPROXY_URL="http://$SERVICE_HOST:6080/vnc_auto.html"
|
||||
VNCSERVER_LISTEN=0.0.0.0
|
||||
VNCSERVER_PROXYCLIENT_ADDRESS=$HOST_IP
|
||||
|
||||
NOVA_INSTANCES_PATH=/opt/stack/data/instances
|
||||
|
||||
# Enable the Ceilometer plugin for the compute agent
|
||||
enable_plugin ceilometer git://git.openstack.org/openstack/ceilometer
|
||||
disable_service ceilometer-acentral,ceilometer-collector,ceilometer-api
|
||||
|
||||
LOGFILE=$DEST/logs/stack.sh.log
|
||||
LOGDAYS=2
|
||||
|
||||
[[post-config|$NOVA_CONF]]
|
||||
[DEFAULT]
|
||||
compute_monitors=cpu.virt_driver
|
||||
44
devstack/local.conf.controller
Normal file
@@ -0,0 +1,44 @@
|
||||
# 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
|
||||
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
|
||||
440
doc/source/architecture.rst
Normal file
@@ -0,0 +1,440 @@
|
||||
..
|
||||
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>`
|
||||
- :ref:`Strategies <strategy_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>`
|
||||
|
||||
|
||||
.. _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%
|
||||
|
||||
Here below is a class diagram representing the main objects in Watcher from a
|
||||
database perspective:
|
||||
|
||||
.. image:: ./images/watcher_class_diagram.png
|
||||
: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
|
||||
- An optional strategy
|
||||
|
||||
.. image:: ./images/sequence_create_audit_template.png
|
||||
:width: 100%
|
||||
|
||||
The `Watcher API`_ makes sure that both the specified goal (mandatory) and
|
||||
its associated strategy (optional) are registered inside the :ref:`Watcher
|
||||
Database <watcher_database_definition>` before storing 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)
|
||||
given both the :ref:`goal <goal_definition>` and the strategy associated to the
|
||||
parent :ref:`audit template <audit_template_definition>` of the :ref:`Audit
|
||||
<audit_definition>`. If no strategy is associated to the audit template, the
|
||||
strategy is dynamically selected by the Decision Engine.
|
||||
|
||||
The :ref:`Watcher Decision Engine <watcher_decision_engine_definition>` also
|
||||
builds the :ref:`Cluster Data Model <cluster_data_model_definition>`. This
|
||||
data model is needed by the :ref:`Strategy <strategy_definition>` to know the
|
||||
current state and topology of the audited
|
||||
:ref:`Openstack cluster <cluster_definition>`.
|
||||
|
||||
The :ref:`Watcher Decision Engine <watcher_decision_engine_definition>` calls
|
||||
the **execute()** method of the instantiated
|
||||
:ref:`Strategy <strategy_definition>` and provides the data model as an input
|
||||
parameter. This method computes a :ref:`Solution <strategy_definition>` to
|
||||
achieve the goal and returns it to the
|
||||
:ref:`Decision Engine <watcher_decision_engine_definition>`. At this point,
|
||||
actions are not scheduled yet.
|
||||
|
||||
The :ref:`Watcher Decision Engine <watcher_decision_engine_definition>`
|
||||
dynamically loads the :ref:`Watcher Planner <watcher_planner_definition>`
|
||||
implementation which is configured in Watcher (via entry points) and calls the
|
||||
**schedule()** method of this class with the solution as an input parameter.
|
||||
This method finds an appropriate scheduling of
|
||||
:ref:`Actions <action_definition>` taking into account some scheduling rules
|
||||
(such as priorities between actions).
|
||||
It generates a new :ref:`Action Plan <action_plan_definition>` with status
|
||||
**RECOMMENDED** and saves it into the
|
||||
:ref:`Watcher Database <watcher_database_definition>`. The saved action plan is
|
||||
now a scheduled flow of actions.
|
||||
|
||||
If every step executed successfully, the
|
||||
:ref:`Watcher Decision Engine <watcher_decision_engine_definition>` updates
|
||||
the current status of the Audit to **SUCCEEDED** in the
|
||||
:ref:`Watcher Database <watcher_database_definition>` and sends a notification
|
||||
on the bus to inform other components that the :ref:`Audit <audit_definition>`
|
||||
was successful.
|
||||
|
||||
|
||||
.. _sequence_diagrams_launch_action_plan:
|
||||
|
||||
Launch Action Plan
|
||||
------------------
|
||||
|
||||
The :ref:`Administrator <administrator_definition>` can then launch the
|
||||
recommended :ref:`Action Plan <action_plan_definition>`:
|
||||
|
||||
.. image:: ./images/sequence_launch_action_plan.png
|
||||
:width: 100%
|
||||
|
||||
A message is sent on the :ref:`AMQP bus <amqp_bus_definition>` which triggers
|
||||
the :ref:`Action Plan <action_plan_definition>` in the
|
||||
:ref:`Watcher Applier <watcher_applier_definition>`:
|
||||
|
||||
.. image:: ./images/sequence_launch_action_plan_in_applier.png
|
||||
:width: 100%
|
||||
|
||||
The :ref:`Watcher Applier <watcher_applier_definition>` will get the
|
||||
description of the flow of :ref:`Actions <action_definition>` from the
|
||||
:ref:`Watcher Database <watcher_database_definition>` and for each
|
||||
:ref:`Action <action_definition>` it will instantiate a corresponding
|
||||
:ref:`Action <action_definition>` handler python class.
|
||||
|
||||
The :ref:`Watcher Applier <watcher_applier_definition>` will then call the
|
||||
following methods of the :ref:`Action <action_definition>` handler:
|
||||
|
||||
- **validate_parameters()**: this method will make sure that all the
|
||||
provided input parameters are valid:
|
||||
|
||||
- If all parameters are valid, the Watcher Applier moves on to the next
|
||||
step.
|
||||
- If it is not, an error is raised and the action is not executed. A
|
||||
notification is sent on the bus informing other components of the
|
||||
failure.
|
||||
|
||||
- **preconditions()**: this method will make sure that all conditions are met
|
||||
before executing the action (for example, it makes sure that an instance
|
||||
still exists before trying to migrate it).
|
||||
- **execute()**: this method is what triggers real commands on other
|
||||
OpenStack services (such as Nova, ...) in order to change target resource
|
||||
state. If the action is successfully executed, a notification message is
|
||||
sent on the bus indicating that the new state of the action is
|
||||
**SUCCEEDED**.
|
||||
|
||||
If every action of the action flow has been executed successfully, a
|
||||
notification is sent on the bus to indicate that the whole
|
||||
:ref:`Action Plan <action_plan_definition>` has **SUCCEEDED**.
|
||||
|
||||
|
||||
.. _state_machine_diagrams:
|
||||
|
||||
State Machine diagrams
|
||||
======================
|
||||
|
||||
.. _audit_state_machine:
|
||||
|
||||
Audit State Machine
|
||||
-------------------
|
||||
|
||||
An :ref:`Audit <audit_definition>` has a life-cycle and its current state may
|
||||
be one of the following:
|
||||
|
||||
- **PENDING** : a request for an :ref:`Audit <audit_definition>` has been
|
||||
submitted (either manually by the
|
||||
:ref:`Administrator <administrator_definition>` or automatically via some
|
||||
event handling mechanism) and is in the queue for being processed by the
|
||||
:ref:`Watcher Decision Engine <watcher_decision_engine_definition>`
|
||||
- **ONGOING** : the :ref:`Audit <audit_definition>` is currently being
|
||||
processed by the
|
||||
:ref:`Watcher Decision Engine <watcher_decision_engine_definition>`
|
||||
- **SUCCEEDED** : the :ref:`Audit <audit_definition>` has been executed
|
||||
successfully and at least one solution was found
|
||||
- **FAILED** : an error occured while executing the
|
||||
:ref:`Audit <audit_definition>`
|
||||
- **DELETED** : the :ref:`Audit <audit_definition>` is still stored in the
|
||||
:ref:`Watcher database <watcher_database_definition>` but is not returned
|
||||
any more through the Watcher APIs.
|
||||
- **CANCELLED** : the :ref:`Audit <audit_definition>` was in **PENDING** or
|
||||
**ONGOING** state and was cancelled by the
|
||||
:ref:`Administrator <administrator_definition>`
|
||||
|
||||
The following diagram shows the different possible states of an
|
||||
:ref:`Audit <audit_definition>` and what event makes the state change to a new
|
||||
value:
|
||||
|
||||
.. image:: ./images/audit_state_machine.png
|
||||
:width: 100%
|
||||
|
||||
.. _action_plan_state_machine:
|
||||
|
||||
Action Plan State Machine
|
||||
-------------------------
|
||||
|
||||
An :ref:`Action Plan <action_plan_definition>` has a life-cycle and its current
|
||||
state may be one of the following:
|
||||
|
||||
- **RECOMMENDED** : the :ref:`Action Plan <action_plan_definition>` is waiting
|
||||
for a validation from the :ref:`Administrator <administrator_definition>`
|
||||
- **PENDING** : a request for an :ref:`Action Plan <action_plan_definition>`
|
||||
has been submitted (due to an
|
||||
:ref:`Administrator <administrator_definition>` executing an
|
||||
:ref:`Audit <audit_definition>`) and is in the queue for
|
||||
being processed by the :ref:`Watcher Applier <watcher_applier_definition>`
|
||||
- **ONGOING** : the :ref:`Action Plan <action_plan_definition>` is currently
|
||||
being processed by the :ref:`Watcher Applier <watcher_applier_definition>`
|
||||
- **SUCCEEDED** : the :ref:`Action Plan <action_plan_definition>` has been
|
||||
executed successfully (i.e. all :ref:`Actions <action_definition>` that it
|
||||
contains have been executed successfully)
|
||||
- **FAILED** : an error occured while executing the
|
||||
:ref:`Action Plan <action_plan_definition>`
|
||||
- **DELETED** : the :ref:`Action Plan <action_plan_definition>` is still
|
||||
stored in the :ref:`Watcher database <watcher_database_definition>` but is
|
||||
not returned any more through the Watcher APIs.
|
||||
- **CANCELLED** : the :ref:`Action Plan <action_plan_definition>` was in
|
||||
**PENDING** or **ONGOING** state and was cancelled by the
|
||||
:ref:`Administrator <administrator_definition>`
|
||||
|
||||
|
||||
The following diagram shows the different possible states of an
|
||||
:ref:`Action Plan <action_plan_definition>` and what event makes the state
|
||||
change to a new value:
|
||||
|
||||
.. image:: ./images/action_plan_state_machine.png
|
||||
:width: 100%
|
||||
|
||||
|
||||
|
||||
.. _Watcher API: webapi/v1.html
|
||||
@@ -11,28 +11,26 @@
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
import sys
|
||||
from watcher import version as watcher_version
|
||||
|
||||
sys.path.insert(0, os.path.abspath('../..'))
|
||||
# -- General configuration ----------------------------------------------------
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = [
|
||||
'oslo_config.sphinxconfiggen',
|
||||
'sphinx.ext.autodoc',
|
||||
# 'sphinx.ext.intersphinx',
|
||||
'sphinx.ext.viewcode',
|
||||
'sphinxcontrib.httpdomain',
|
||||
'sphinxcontrib.pecanwsme.rest',
|
||||
'wsmeext.sphinxext',
|
||||
'oslosphinx'
|
||||
'oslosphinx',
|
||||
'watcher.doc',
|
||||
]
|
||||
|
||||
wsme_protocols = ['restjson']
|
||||
|
||||
config_generator_config_file = '../../etc/watcher/watcher-config-generator.conf'
|
||||
sample_config_basename = 'watcher'
|
||||
|
||||
# autodoc generation is a bit aggressive and a nuisance when doing heavy
|
||||
# text edit cycles.
|
||||
@@ -45,8 +43,8 @@ source_suffix = '.rst'
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'watcher'
|
||||
copyright = u'2015, OpenStack Foundation'
|
||||
project = u'Watcher'
|
||||
copyright = u'OpenStack Foundation'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
@@ -61,6 +59,14 @@ version = watcher_version.version_info.version_string()
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
modindex_common_prefix = ['watcher.']
|
||||
|
||||
exclude_patterns = [
|
||||
# The man directory includes some snippet files that are included
|
||||
# in other documents during the build but that should not be
|
||||
# included in the toctree themselves, so tell Sphinx to ignore
|
||||
# them when scanning for input files.
|
||||
'man/footer.rst',
|
||||
'man/general-options.rst',
|
||||
]
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
add_function_parentheses = True
|
||||
@@ -72,6 +78,22 @@ add_module_names = True
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# -- Options for man page output --------------------------------------------
|
||||
|
||||
# Grouping the document tree for man pages.
|
||||
# List of tuples 'sourcefile', 'target', u'title', u'Authors name', 'manual'
|
||||
|
||||
man_pages = [
|
||||
('man/watcher-api', 'watcher-api', u'Watcher API Server',
|
||||
[u'OpenStack'], 1),
|
||||
('man/watcher-applier', 'watcher-applier', u'Watcher Applier',
|
||||
[u'OpenStack'], 1),
|
||||
('man/watcher-db-manage', 'watcher-db-manage',
|
||||
u'Watcher Db Management Utility', [u'OpenStack'], 1),
|
||||
('man/watcher-decision-engine', 'watcher-decision-engine',
|
||||
u'Watcher Decision Engine', [u'OpenStack'], 1),
|
||||
]
|
||||
|
||||
# -- Options for HTML output --------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. Major themes that come with
|
||||
|
||||
1
doc/source/config-generator.conf
Normal file
@@ -0,0 +1 @@
|
||||
../../etc/watcher/watcher-config-generator.conf
|
||||
14
doc/source/deploy/conf-files.rst
Normal file
@@ -0,0 +1,14 @@
|
||||
.. _watcher_sample_configuration_files:
|
||||
|
||||
==================================
|
||||
Watcher sample configuration files
|
||||
==================================
|
||||
|
||||
watcher.conf
|
||||
~~~~~~~~~~~~
|
||||
|
||||
The ``watcher.conf`` file contains most of the options to configure the
|
||||
Watcher services.
|
||||
|
||||
.. literalinclude:: ../watcher.conf.sample
|
||||
:language: ini
|
||||
@@ -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/
|
||||
|
||||
@@ -156,17 +163,31 @@ Configure the Watcher service
|
||||
The Watcher service is configured via its configuration file. This file
|
||||
is typically located at ``/etc/watcher/watcher.conf``.
|
||||
|
||||
You can easily generate and update a sample configuration file
|
||||
named :ref:`watcher.conf.sample <watcher_sample_configuration_files>` by using
|
||||
these following commands::
|
||||
|
||||
$ git clone git://git.openstack.org/openstack/watcher
|
||||
$ cd watcher/
|
||||
$ tox -econfig
|
||||
$ vi etc/watcher/watcher.conf.sample
|
||||
|
||||
|
||||
The configuration file is organized into the following sections:
|
||||
|
||||
* ``[DEFAULT]`` - General configuration
|
||||
* ``[api]`` - API server configuration
|
||||
* ``[database]`` - SQL driver configuration
|
||||
* ``[keystone_authtoken]`` - Keystone Authentication plugin configuration
|
||||
* ``[watcher_clients_auth]`` - Keystone auth configuration for clients
|
||||
* ``[watcher_applier]`` - Watcher Applier module configuration
|
||||
* ``[watcher_decision_engine]`` - Watcher Decision Engine module configuration
|
||||
* ``[watcher_goals]`` - Goals mapping configuration
|
||||
* ``[watcher_strategies]`` - Strategy configuration
|
||||
* ``[oslo_messaging_rabbit]`` - Oslo Messaging RabbitMQ driver configuration
|
||||
* ``[ceilometer_client]`` - Ceilometer client configuration
|
||||
* ``[cinder_client]`` - Cinder client configuration
|
||||
* ``[glance_client]`` - Glance client configuration
|
||||
* ``[nova_client]`` - Nova client configuration
|
||||
* ``[neutron_client]`` - Neutron client configuration
|
||||
|
||||
The Watcher configuration file is expected to be named
|
||||
``watcher.conf``. When starting Watcher, you can specify a different
|
||||
@@ -233,39 +254,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 +401,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
|
||||
|
||||
|
||||
52
doc/source/deploy/gmr.rst
Normal file
@@ -0,0 +1,52 @@
|
||||
..
|
||||
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_gmr:
|
||||
|
||||
=======================
|
||||
Guru Meditation Reports
|
||||
=======================
|
||||
|
||||
Watcher contains a mechanism whereby developers and system administrators can
|
||||
generate a report about the state of a running Watcher service. This report
|
||||
is called a *Guru Meditation Report* (*GMR* for short).
|
||||
|
||||
Generating a GMR
|
||||
================
|
||||
|
||||
A *GMR* can be generated by sending the *USR2* signal to any Watcher process
|
||||
with support (see below). The *GMR* will then be outputted as standard error
|
||||
for that particular process.
|
||||
|
||||
For example, suppose that ``watcher-api`` has process id ``8675``, and was run
|
||||
with ``2>/var/log/watcher/watcher-api-err.log``. Then, ``kill -USR2 8675``
|
||||
will trigger the Guru Meditation report to be printed to
|
||||
``/var/log/watcher/watcher-api-err.log``.
|
||||
|
||||
Structure of a GMR
|
||||
==================
|
||||
|
||||
The *GMR* is designed to be extensible; any particular service may add its
|
||||
own sections. However, the base *GMR* consists of several sections:
|
||||
|
||||
Package
|
||||
Shows information about the package to which this process belongs, including
|
||||
version informations.
|
||||
|
||||
Threads
|
||||
Shows stack traces and thread ids for each of the threads within this
|
||||
process.
|
||||
|
||||
Green Threads
|
||||
Shows stack traces for each of the green threads within this process (green
|
||||
threads don't have thread ids).
|
||||
|
||||
Configuration
|
||||
Lists all the configuration options currently accessible via the CONF object
|
||||
for the current process.
|
||||
|
||||
Plugins
|
||||
Lists all the plugins currently accessible by the Watcher service.
|
||||
@@ -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,27 +1,48 @@
|
||||
.. _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:
|
||||
|
||||
.. code:: bash
|
||||
You can interact with Watcher either by using our dedicated `Watcher CLI`_
|
||||
named ``watcher``, or by using the `OpenStack CLI`_ ``openstack``.
|
||||
|
||||
$ 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
|
||||
.. _`OpenStack CLI`: http://docs.openstack.org/developer/python-openstackclient/man/openstack.html
|
||||
.. _`Watcher CLI`: https://factory.b-com.com/www/watcher/doc/python-watcherclient/index.html
|
||||
|
||||
Seeing what the Watcher CLI can do ?
|
||||
------------------------------------
|
||||
@@ -30,23 +51,66 @@ watcher binary without options.
|
||||
|
||||
.. code:: bash
|
||||
|
||||
$ watcher
|
||||
$ watcher help
|
||||
|
||||
or::
|
||||
|
||||
$ openstack help optimize
|
||||
|
||||
How do I run an audit of my cluster ?
|
||||
-------------------------------------
|
||||
|
||||
First, you need to create an :ref:`audit template <audit_template_definition>`.
|
||||
An :ref:`audit template <audit_template_definition>` defines an optimization
|
||||
:ref:`goal <goal_definition>` to achieve (i.e. the settings of your audit).
|
||||
This goal should be declared in the Watcher service configuration file
|
||||
**/etc/watcher/watcher.conf**.
|
||||
First, you need to find the :ref:`goal <goal_definition>` you want to achieve:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
$ watcher audit-template-create my_first_audit SERVERS_CONSOLIDATION
|
||||
$ watcher goal list
|
||||
|
||||
If you get "*You must provide a username via either --os-username or via
|
||||
env[OS_USERNAME]*" you may have to verify your credentials.
|
||||
or::
|
||||
|
||||
$ openstack optimize goal list
|
||||
|
||||
.. note::
|
||||
|
||||
If you get "*You must provide a username via either --os-username or via
|
||||
env[OS_USERNAME]*" you may have to verify your credentials.
|
||||
|
||||
Then, you can create an :ref:`audit template <audit_template_definition>`.
|
||||
An :ref:`audit template <audit_template_definition>` defines an optimization
|
||||
:ref:`goal <goal_definition>` to achieve (i.e. the settings of your audit).
|
||||
|
||||
.. code:: bash
|
||||
|
||||
$ watcher audittemplate create my_first_audit_template <your_goal>
|
||||
|
||||
or::
|
||||
|
||||
$ openstack optimize audittemplate create my_first_audit_template <your_goal>
|
||||
|
||||
Although optional, you may want to actually set a specific strategy for your
|
||||
audit template. If so, you may can search of its UUID or name using the
|
||||
following command:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
$ watcher strategy list --goal-uuid <your_goal_uuid>
|
||||
|
||||
or::
|
||||
|
||||
$ openstack optimize strategy list --goal-uuid <your_goal_uuid>
|
||||
|
||||
|
||||
The command to create your audit template would then be:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
$ watcher audittemplate create my_first_audit_template <your_goal> \
|
||||
--strategy <your_strategy>
|
||||
|
||||
or::
|
||||
|
||||
$ openstack optimize audittemplate create my_first_audit_template <your_goal> \
|
||||
--strategy <your_strategy>
|
||||
|
||||
Then, you can create an audit. An audit is a request for optimizing your
|
||||
cluster depending on the specified :ref:`goal <goal_definition>`.
|
||||
@@ -55,19 +119,26 @@ You can launch an audit on your cluster by referencing the
|
||||
:ref:`audit template <audit_template_definition>` (i.e. the settings of your
|
||||
audit) that you want to use.
|
||||
|
||||
- Get the :ref:`audit template <audit_template_definition>` UUID:
|
||||
- Get the :ref:`audit template <audit_template_definition>` UUID or name:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
$ watcher audit-template-list
|
||||
$ watcher audittemplate list
|
||||
|
||||
or::
|
||||
|
||||
$ openstack optimize audittemplate list
|
||||
|
||||
- Start an audit based on this :ref:`audit template
|
||||
<audit_template_definition>` settings:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
$ watcher audit-create -a <your_audit_template_uuid>
|
||||
$ watcher audit create -a <your_audit_template>
|
||||
|
||||
or::
|
||||
|
||||
$ openstack optimize audit create -a <your_audit_template>
|
||||
|
||||
Watcher service will compute an :ref:`Action Plan <action_plan_definition>`
|
||||
composed of a list of potential optimization :ref:`actions <action_definition>`
|
||||
@@ -81,15 +152,22 @@ configuration file.
|
||||
|
||||
.. code:: bash
|
||||
|
||||
$ watcher action-plan-list --audit <the_audit_uuid>
|
||||
$ watcher actionplan list --audit <the_audit_uuid>
|
||||
|
||||
or::
|
||||
|
||||
$ openstack optimize actionplan 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
|
||||
|
||||
$ watcher action-list --action-plan <the_action_plan_uuid>
|
||||
$ watcher action list --action-plan <the_action_plan_uuid>
|
||||
|
||||
or::
|
||||
|
||||
$ openstack optimize action list --action-plan <the_action_plan_uuid>
|
||||
|
||||
Once you have learned how to create an :ref:`Action Plan
|
||||
<action_plan_definition>`, it's time to go further by applying it to your
|
||||
@@ -99,17 +177,30 @@ cluster:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
$ watcher action-plan-start <the_action_plan_uuid>
|
||||
$ watcher actionplan start <the_action_plan_uuid>
|
||||
|
||||
or::
|
||||
|
||||
$ openstack optimize actionplan start <the_action_plan_uuid>
|
||||
|
||||
You can follow the states of the :ref:`actions <action_definition>` by
|
||||
periodically calling:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
$ watcher action-list
|
||||
$ watcher action list
|
||||
|
||||
or::
|
||||
|
||||
$ openstack optimize action list
|
||||
|
||||
You can also obtain more detailed information about a specific action:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
$ watcher action-show <the_action_uuid>
|
||||
$ watcher action show <the_action_uuid>
|
||||
|
||||
or::
|
||||
|
||||
$ openstack optimize 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
|
||||
===============================
|
||||
@@ -224,7 +205,7 @@ place:
|
||||
|
||||
$ workon watcher
|
||||
|
||||
(watcher) $ watcher-db-manage --create_schema
|
||||
(watcher) $ watcher-db-manage create_schema
|
||||
|
||||
|
||||
Running Watcher services
|
||||
@@ -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.
|
||||
|
||||
214
doc/source/dev/plugin/action-plugin.rst
Normal file
@@ -0,0 +1,214 @@
|
||||
..
|
||||
Except where otherwise noted, this document is licensed under Creative
|
||||
Commons Attribution 3.0 License. You can view the license at:
|
||||
|
||||
https://creativecommons.org/licenses/by/3.0/
|
||||
|
||||
==================
|
||||
Build a new action
|
||||
==================
|
||||
|
||||
Watcher Applier has an external :ref:`action <action_definition>` plugin
|
||||
interface which gives anyone the ability to integrate an external
|
||||
:ref:`action <action_definition>` in order to extend the initial set of actions
|
||||
Watcher provides.
|
||||
|
||||
This section gives some guidelines on how to implement and integrate custom
|
||||
actions with Watcher.
|
||||
|
||||
|
||||
Creating a new plugin
|
||||
=====================
|
||||
|
||||
First of all you have to extend the base :py:class:`BaseAction` class which
|
||||
defines a set of abstract methods and/or properties that you will have to
|
||||
implement:
|
||||
|
||||
- The :py:attr:`~.BaseAction.schema` is an abstract property that you have to
|
||||
implement. This is the first function to be called by the
|
||||
:ref:`applier <watcher_applier_definition>` before any further processing
|
||||
and its role is to validate the input parameters that were provided to it.
|
||||
- The :py:meth:`~.BaseAction.precondition` is called before the execution of
|
||||
an action. This method is a hook that can be used to perform some
|
||||
initializations or to make some more advanced validation on its input
|
||||
parameters. If you wish to block the execution based on this factor, you
|
||||
simply have to ``raise`` an exception.
|
||||
- The :py:meth:`~.BaseAction.postcondition` is called after the execution of
|
||||
an action. As this function is called regardless of whether an action
|
||||
succeeded or not, this can prove itself useful to perform cleanup
|
||||
operations.
|
||||
- The :py:meth:`~.BaseAction.execute` is the main component of an action.
|
||||
This is where you should implement the logic of your action.
|
||||
- The :py:meth:`~.BaseAction.revert` allows you to roll back the targeted
|
||||
resource to its original state following a faulty execution. Indeed, this
|
||||
method is called by the workflow engine whenever an action raises an
|
||||
exception.
|
||||
|
||||
Here is an example showing how you can write a plugin called ``DummyAction``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# Filepath = <PROJECT_DIR>/thirdparty/dummy.py
|
||||
# Import path = thirdparty.dummy
|
||||
import voluptuous
|
||||
|
||||
from watcher.applier.actions import base
|
||||
|
||||
|
||||
class DummyAction(base.BaseAction):
|
||||
|
||||
@property
|
||||
def schema(self):
|
||||
return voluptuous.Schema({})
|
||||
|
||||
def execute(self):
|
||||
# Does nothing
|
||||
pass # Only returning False is considered as a failure
|
||||
|
||||
def revert(self):
|
||||
# Does nothing
|
||||
pass
|
||||
|
||||
def precondition(self):
|
||||
# No pre-checks are done here
|
||||
pass
|
||||
|
||||
def postcondition(self):
|
||||
# Nothing done here
|
||||
pass
|
||||
|
||||
|
||||
This implementation is the most basic one. So if you want to have more advanced
|
||||
examples, have a look at the implementation of the actions already provided
|
||||
by Watcher like.
|
||||
To get a better understanding on how to implement a more advanced action,
|
||||
have a look at the :py:class:`~watcher.applier.actions.migration.Migrate`
|
||||
class.
|
||||
|
||||
Input validation
|
||||
----------------
|
||||
|
||||
As you can see in the previous example, we are using `Voluptuous`_ to validate
|
||||
the input parameters of an action. So if you want to learn more about how to
|
||||
work with `Voluptuous`_, you can have a look at their `documentation`_:
|
||||
|
||||
.. _Voluptuous: https://github.com/alecthomas/voluptuous
|
||||
.. _documentation: https://github.com/alecthomas/voluptuous/blob/master/README.md
|
||||
|
||||
|
||||
Define configuration parameters
|
||||
===============================
|
||||
|
||||
At this point, you have a fully functional action. However, in more complex
|
||||
implementation, you may want to define some configuration options so one can
|
||||
tune the action to its needs. To do so, you can implement the
|
||||
:py:meth:`~.Loadable.get_config_opts` class method as followed:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
class DummyAction(base.BaseAction):
|
||||
|
||||
# [...]
|
||||
|
||||
def execute(self):
|
||||
assert self.config.test_opt == 0
|
||||
|
||||
def get_config_opts(self):
|
||||
return [
|
||||
cfg.StrOpt('test_opt', help="Demo Option.", default=0),
|
||||
# Some more options ...
|
||||
]
|
||||
|
||||
The configuration options defined within this class method will be included
|
||||
within the global ``watcher.conf`` configuration file under a section named by
|
||||
convention: ``{namespace}.{plugin_name}``. In our case, the ``watcher.conf``
|
||||
configuration would have to be modified as followed:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[watcher_actions.dummy]
|
||||
# Option used for testing.
|
||||
test_opt = test_value
|
||||
|
||||
Then, the configuration options you define within this method will then be
|
||||
injected in each instantiated object via the ``config`` parameter of the
|
||||
:py:meth:`~.BaseAction.__init__` method.
|
||||
|
||||
|
||||
Abstract Plugin Class
|
||||
=====================
|
||||
|
||||
Here below is the abstract ``BaseAction`` class that every single action
|
||||
should implement:
|
||||
|
||||
.. autoclass:: watcher.applier.actions.base.BaseAction
|
||||
:members:
|
||||
:special-members: __init__
|
||||
: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.
|
||||
171
doc/source/dev/plugin/planner-plugin.rst
Normal file
@@ -0,0 +1,171 @@
|
||||
..
|
||||
Except where otherwise noted, this document is licensed under Creative
|
||||
Commons Attribution 3.0 License. You can view the license at:
|
||||
|
||||
https://creativecommons.org/licenses/by/3.0/
|
||||
|
||||
.. _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>`.
|
||||
|
||||
|
||||
Define configuration parameters
|
||||
===============================
|
||||
|
||||
At this point, you have a fully functional planner. However, in more complex
|
||||
implementation, you may want to define some configuration options so one can
|
||||
tune the planner to its needs. To do so, you can implement the
|
||||
:py:meth:`~.Loadable.get_config_opts` class method as followed:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
class DummyPlanner(base.BasePlanner):
|
||||
|
||||
# [...]
|
||||
|
||||
def schedule(self, context, audit_uuid, solution):
|
||||
assert self.config.test_opt == 0
|
||||
# [...]
|
||||
|
||||
def get_config_opts(self):
|
||||
return [
|
||||
cfg.StrOpt('test_opt', help="Demo Option.", default=0),
|
||||
# Some more options ...
|
||||
]
|
||||
|
||||
The configuration options defined within this class method will be included
|
||||
within the global ``watcher.conf`` configuration file under a section named by
|
||||
convention: ``{namespace}.{plugin_name}``. In our case, the ``watcher.conf``
|
||||
configuration would have to be modified as followed:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[watcher_planners.dummy]
|
||||
# Option used for testing.
|
||||
test_opt = test_value
|
||||
|
||||
Then, the configuration options you define within this method will then be
|
||||
injected in each instantiated object via the ``config`` parameter of the
|
||||
:py:meth:`~.BasePlanner.__init__` method.
|
||||
|
||||
|
||||
Abstract Plugin Class
|
||||
=====================
|
||||
|
||||
Here below is the abstract ``BasePlanner`` class that every single planner
|
||||
should implement:
|
||||
|
||||
.. autoclass:: watcher.decision_engine.planner.base.BasePlanner
|
||||
:members:
|
||||
:special-members: __init__
|
||||
: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.
|
||||
365
doc/source/dev/plugin/strategy-plugin.rst
Normal file
@@ -0,0 +1,365 @@
|
||||
..
|
||||
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. If you wish to create a third-party package for your
|
||||
plugin, you can refer to our :ref:`documentation for third-party package
|
||||
creation <plugin-base_setup>`.
|
||||
|
||||
|
||||
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.
|
||||
|
||||
|
||||
Create a new plugin
|
||||
===================
|
||||
|
||||
In order to create a new strategy, you have to:
|
||||
|
||||
- Extend the :py:class:`~.UnclassifiedStrategy` class
|
||||
- Implement its :py:meth:`~.BaseStrategy.get_name` class method to return the
|
||||
**unique** ID of the new strategy you want to create. This unique ID should
|
||||
be the same as the name of :ref:`the entry point we will declare later on
|
||||
<strategy_plugin_add_entrypoint>`.
|
||||
- Implement its :py:meth:`~.BaseStrategy.get_display_name` class method to
|
||||
return the translated display name of the strategy you want to create.
|
||||
Note: Do not use a variable to return the translated string so it can be
|
||||
automatically collected by the translation tool.
|
||||
- Implement its :py:meth:`~.BaseStrategy.get_translatable_display_name`
|
||||
class method to return the translation key (actually the english display
|
||||
name) of your new strategy. The value return should be the same as the
|
||||
string translated in :py:meth:`~.BaseStrategy.get_display_name`.
|
||||
- Implement its :py:meth:`~.BaseStrategy.execute` method to return the
|
||||
solution you computed within your strategy.
|
||||
|
||||
Here is an example showing how you can write a plugin called ``NewStrategy``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import abc
|
||||
|
||||
import six
|
||||
|
||||
from watcher._i18n import _
|
||||
from watcher.decision_engine.strategy.strategies import base
|
||||
|
||||
|
||||
class NewStrategy(base.UnclassifiedStrategy):
|
||||
|
||||
def __init__(self, osc=None):
|
||||
super(NewStrategy, self).__init__(osc)
|
||||
|
||||
def execute(self, original_model):
|
||||
self.solution.add_action(action_type="nop",
|
||||
input_parameters=parameters)
|
||||
# Do some more stuff here ...
|
||||
return self.solution
|
||||
|
||||
@classmethod
|
||||
def get_name(cls):
|
||||
return "new_strategy"
|
||||
|
||||
@classmethod
|
||||
def get_display_name(cls):
|
||||
return _("New strategy")
|
||||
|
||||
@classmethod
|
||||
def get_translatable_display_name(cls):
|
||||
return "New strategy"
|
||||
|
||||
|
||||
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 contains the sequenced flow of actions to be
|
||||
executed by the :ref:`Watcher Applier <watcher_applier_definition>`.
|
||||
|
||||
Please note that your strategy class will expect to find the same constructor
|
||||
signature as BaseStrategy to instantiate you strategy. Therefore, you should
|
||||
ensure that your ``__init__`` signature is identical to the
|
||||
:py:class:`~.BaseStrategy` one.
|
||||
|
||||
|
||||
Create a new goal
|
||||
=================
|
||||
|
||||
As stated before, the ``NewStrategy`` class extends a class called
|
||||
:py:class:`~.UnclassifiedStrategy`. This class actually implements a set of
|
||||
abstract methods which are defined within the :py:class:`~.BaseStrategy` parent
|
||||
class.
|
||||
|
||||
Once you are confident in your strategy plugin, the next step is now to
|
||||
classify your goal by assigning it a proper goal. To do so, you can either
|
||||
reuse existing goals defined in Watcher. As of now, four goal-oriented abstract
|
||||
classes are defined in Watcher:
|
||||
|
||||
- :py:class:`~.UnclassifiedStrategy` which is the one I mentioned up until now.
|
||||
- :py:class:`~.DummyBaseStrategy` which is used by :py:class:`~.DummyStrategy`
|
||||
for testing purposes.
|
||||
- :py:class:`~.ServerConsolidationBaseStrategy`
|
||||
- :py:class:`~.ThermalOptimizationBaseStrategy`
|
||||
|
||||
If none of the above actually correspond to the goal your new strategy
|
||||
achieves, you can define a brand new one. To do so, you need to:
|
||||
|
||||
- Extend the :py:class:`~.BaseStrategy` class to make your new goal-oriented
|
||||
strategy abstract class :
|
||||
- Implement its :py:meth:`~.BaseStrategy.get_goal_name` class method to
|
||||
return the **unique** ID of the goal you want to achieve.
|
||||
- Implement its :py:meth:`~.BaseStrategy.get_goal_display_name` class method
|
||||
to return the translated display name of the goal you want to achieve.
|
||||
Note: Do not use a variable to return the translated string so it can be
|
||||
automatically collected by the translation tool.
|
||||
- Implement its :py:meth:`~.BaseStrategy.get_translatable_goal_display_name`
|
||||
class method to return the goal translation key (actually the english
|
||||
display name). The value return should be the same as the string translated
|
||||
in :py:meth:`~.BaseStrategy.get_goal_display_name`.
|
||||
|
||||
Here is an example showing how you can define a new ``NEW_GOAL`` goal and
|
||||
modify your ``NewStrategy`` plugin so it now achieves the latter:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import abc
|
||||
|
||||
import six
|
||||
|
||||
from watcher._i18n import _
|
||||
from watcher.decision_engine.strategy.strategies import base
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class NewGoalBaseStrategy(base.BaseStrategy):
|
||||
|
||||
@classmethod
|
||||
def get_goal_name(cls):
|
||||
return "NEW_GOAL"
|
||||
|
||||
@classmethod
|
||||
def get_goal_display_name(cls):
|
||||
return _("New goal")
|
||||
|
||||
@classmethod
|
||||
def get_translatable_goal_display_name(cls):
|
||||
return "New goal"
|
||||
|
||||
|
||||
class NewStrategy(NewGoalBaseStrategy):
|
||||
|
||||
def __init__(self, config, osc=None):
|
||||
super(NewStrategy, self).__init__(config, osc)
|
||||
|
||||
def execute(self, original_model):
|
||||
self.solution.add_action(action_type="nop",
|
||||
input_parameters=parameters)
|
||||
# Do some more stuff here ...
|
||||
return self.solution
|
||||
|
||||
@classmethod
|
||||
def get_name(cls):
|
||||
return "new_strategy"
|
||||
|
||||
@classmethod
|
||||
def get_display_name(cls):
|
||||
return _("New strategy")
|
||||
|
||||
@classmethod
|
||||
def get_translatable_display_name(cls):
|
||||
return "New strategy"
|
||||
|
||||
|
||||
Define configuration parameters
|
||||
===============================
|
||||
|
||||
At this point, you have a fully functional strategy. However, in more complex
|
||||
implementation, you may want to define some configuration options so one can
|
||||
tune the strategy to its needs. To do so, you can implement the
|
||||
:py:meth:`~.Loadable.get_config_opts` class method as followed:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
class NewStrategy(NewGoalBaseStrategy):
|
||||
|
||||
# [...]
|
||||
|
||||
def execute(self, original_model):
|
||||
assert self.config.test_opt == 0
|
||||
# [...]
|
||||
|
||||
def get_config_opts(self):
|
||||
return [
|
||||
cfg.StrOpt('test_opt', help="Demo Option.", default=0),
|
||||
# Some more options ...
|
||||
]
|
||||
|
||||
|
||||
The configuration options defined within this class method will be included
|
||||
within the global ``watcher.conf`` configuration file under a section named by
|
||||
convention: ``{namespace}.{plugin_name}``. In our case, the ``watcher.conf``
|
||||
configuration would have to be modified as followed:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[watcher_strategies.new_strategy]
|
||||
# Option used for testing.
|
||||
test_opt = test_value
|
||||
|
||||
Then, the configuration options you define within this method will then be
|
||||
injected in each instantiated object via the ``config`` parameter of the
|
||||
:py:meth:`~.BaseStrategy.__init__` method.
|
||||
|
||||
|
||||
Abstract Plugin Class
|
||||
=====================
|
||||
|
||||
Here below is the abstract :py:class:`~.BaseStrategy` class:
|
||||
|
||||
.. autoclass:: watcher.decision_engine.strategy.strategies.base.BaseStrategy
|
||||
:members:
|
||||
:special-members: __init__
|
||||
:noindex:
|
||||
|
||||
.. _strategy_plugin_add_entrypoint:
|
||||
|
||||
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 and should be the same
|
||||
as the value returned by the :py:meth:`~.BaseStrategy.get_id` class method of
|
||||
your strategy.
|
||||
|
||||
Here below is how you would proceed to register ``DummyStrategy`` using pbr_:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[entry_points]
|
||||
watcher_strategies =
|
||||
dummy_strategy = 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 scan and register inside the :ref:`Watcher Database
|
||||
<watcher_database_definition>` all the strategies (alongside the goals they
|
||||
should satisfy) you implemented upon restarting the :ref:`Watcher Decision
|
||||
Engine <watcher_decision_engine_definition>`.
|
||||
|
||||
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.base.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,42 @@
|
||||
===============
|
||||
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:
|
||||
In this section we present all the plugins that are shipped along with Watcher.
|
||||
If you want to know which plugins your Watcher services have access to, you can
|
||||
use the :ref:`Guru Meditation Reports <watcher_gmr>` to display them.
|
||||
|
||||
- Extend the base ``BaseStrategy`` class
|
||||
- Implement its ``execute`` method
|
||||
.. _watcher_strategies:
|
||||
|
||||
Here is an example showing how you can write a plugin called ``DummyStrategy``:
|
||||
Strategies
|
||||
==========
|
||||
|
||||
.. code-block:: python
|
||||
.. drivers-doc:: watcher_strategies
|
||||
|
||||
# Filepath = third-party/third_party/dummy.py
|
||||
# Import path = third_party.dummy
|
||||
.. _watcher_actions:
|
||||
|
||||
class DummyStrategy(BaseStrategy):
|
||||
Actions
|
||||
=======
|
||||
|
||||
DEFAULT_NAME = "dummy"
|
||||
DEFAULT_DESCRIPTION = "Dummy Strategy"
|
||||
.. drivers-doc:: watcher_actions
|
||||
|
||||
def __init__(self, name=DEFAULT_NAME, description=DEFAULT_DESCRIPTION):
|
||||
super(DummyStrategy, self).__init__(name, description)
|
||||
.. _watcher_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
|
||||
Workflow Engines
|
||||
================
|
||||
|
||||
As you can see in the above example, the ``execute()`` method returns a
|
||||
solution as required.
|
||||
.. drivers-doc:: watcher_workflow_engines
|
||||
|
||||
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.
|
||||
.. _watcher_planners:
|
||||
|
||||
Planners
|
||||
========
|
||||
|
||||
Abstract Plugin Class
|
||||
---------------------
|
||||
|
||||
Here below is the abstract ``BaseStrategy`` class that every single strategy
|
||||
should implement:
|
||||
|
||||
.. automodule:: watcher.decision_engine.strategy.base
|
||||
:noindex:
|
||||
|
||||
.. autoclass:: BaseStrategy
|
||||
:members:
|
||||
|
||||
|
||||
Add a new entry point
|
||||
---------------------
|
||||
|
||||
In order for the Watcher Decision Engine to load your new strategy, the
|
||||
strategy must be registered as a named entry point under the
|
||||
``watcher_strategies`` entry point of your ``setup.py`` file. If you are using
|
||||
pbr_, this entry point should be placed in your ``setup.cfg`` file.
|
||||
|
||||
The name you give to your entry point has to be unique.
|
||||
|
||||
Here below is how you would proceed to register ``DummyStrategy`` using pbr_:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[entry_points]
|
||||
watcher_strategies =
|
||||
dummy = third_party.dummy:DummyStrategy
|
||||
|
||||
|
||||
To get a better understanding on how to implement a more advanced strategy,
|
||||
have a look at the :py:class:`BasicConsolidation` class.
|
||||
|
||||
.. _pbr: http://docs.openstack.org/developer/pbr/
|
||||
|
||||
Using strategy plugins
|
||||
----------------------
|
||||
|
||||
The Watcher Decision Engine service will automatically discover any installed
|
||||
plugins when it is run. If a Python package containing a custom plugin is
|
||||
installed within the same environment as Watcher, Watcher will automatically
|
||||
make that plugin available for use.
|
||||
|
||||
At this point, the way Watcher will use your new strategy if you reference it
|
||||
in the ``goals`` under the ``[watcher_goals]`` section of your ``watcher.conf``
|
||||
configuration file. For example, if you want to use a ``dummy`` strategy you
|
||||
just installed, you would have to associate it to a goal like this:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[watcher_goals]
|
||||
goals = BALANCE_LOAD:basic,MINIMIZE_ENERGY_CONSUMPTION:dummy
|
||||
|
||||
|
||||
You should take care when installing strategy plugins. By their very nature,
|
||||
there are no guarantees that utilizing them as is will be supported, as
|
||||
they may require a set of metrics which is not yet available within the
|
||||
Telemetry service. In such a case, please do make sure that you first
|
||||
check/configure the latter so your new strategy can be fully functional.
|
||||
|
||||
Querying metrics
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
The metrics available depend on the hypervisors that OpenStack manages on
|
||||
the specific implementation. You can find the metrics available per hypervisor
|
||||
and OpenStack release on the OpenStack site.
|
||||
|
||||
There are different possible ways to obtain usage metrics in Watcher, you can
|
||||
use the default Ceilometer API or our Helper.
|
||||
The Helper attempted to make the Ceilometer API more reusable and easy to use.
|
||||
|
||||
Read usage metrics using the Python binding
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You can find the information about the Ceilometer Python binding on the
|
||||
OpenStack `ceilometer client python API documentation
|
||||
<http://docs.openstack.org/developer/python-ceilometerclient/api.html>`_
|
||||
|
||||
The first step is to authenticate against the Ceilometer service
|
||||
(assuming that you already imported the Ceilometer client for Python)
|
||||
with this call:
|
||||
|
||||
.. code-block:: py
|
||||
|
||||
cclient = ceilometerclient.client.get_client(VERSION, os_username=USERNAME,
|
||||
os_password=PASSWORD, os_tenant_name=PROJECT_NAME, os_auth_url=AUTH_URL)
|
||||
|
||||
Using that you can now query the values for that specific metric:
|
||||
|
||||
.. code-block:: py
|
||||
|
||||
value_cpu = cclient.samples.list(meter_name='cpu_util', limit=10, q=query)
|
||||
|
||||
Read usage metrics using the Watcher Cluster History Helper
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Here below is the abstract ``BaseClusterHistory`` class of the Helper.
|
||||
|
||||
.. automodule:: watcher.metrics_engine.cluster_history.api
|
||||
:noindex:
|
||||
|
||||
.. autoclass:: BaseClusterHistory
|
||||
:members:
|
||||
|
||||
|
||||
The following snippet code shows how to create a Cluster History class:
|
||||
|
||||
.. code-block:: py
|
||||
|
||||
query_history = CeilometerClusterHistory()
|
||||
|
||||
Using that you can now query the values for that specific metric:
|
||||
|
||||
.. code-block:: py
|
||||
|
||||
query_history.statistic_aggregation(resource_id=hypervisor.uuid,
|
||||
meter_name='compute.node.cpu.percent',
|
||||
period="7200",
|
||||
aggregate='avg'
|
||||
)
|
||||
|
||||
.. drivers-doc:: watcher_planners
|
||||
|
||||
50
doc/source/dev/testing.rst
Normal file
@@ -0,0 +1,50 @@
|
||||
..
|
||||
Except where otherwise noted, this document is licensed under Creative
|
||||
Commons Attribution 3.0 License. You can view the license at:
|
||||
|
||||
https://creativecommons.org/licenses/by/3.0/
|
||||
|
||||
=======
|
||||
Testing
|
||||
=======
|
||||
|
||||
.. _unit_tests:
|
||||
|
||||
Unit tests
|
||||
==========
|
||||
|
||||
All unit tests should be run using `tox`_. To run the same unit tests that are
|
||||
executing onto `Gerrit`_ which includes ``py34``, ``py27`` and ``pep8``, you
|
||||
can issue the following command::
|
||||
|
||||
$ workon watcher
|
||||
(watcher) $ pip install tox
|
||||
(watcher) $ cd watcher
|
||||
(watcher) $ tox
|
||||
|
||||
If you want to only run one of the aforementioned, you can then issue one of
|
||||
the following::
|
||||
|
||||
$ workon watcher
|
||||
(watcher) $ tox -e py34
|
||||
(watcher) $ tox -e py27
|
||||
(watcher) $ tox -e pep8
|
||||
|
||||
.. _tox: https://tox.readthedocs.org/
|
||||
.. _Gerrit: http://review.openstack.org/
|
||||
|
||||
You may pass options to the test programs using positional arguments. To run a
|
||||
specific unit test, you can pass extra options to `os-testr`_ after putting
|
||||
the ``--`` separator. So using the ``-r`` option followed by a regex string,
|
||||
you can run the desired test::
|
||||
|
||||
$ workon watcher
|
||||
(watcher) $ tox -e py27 -- -r watcher.tests.api
|
||||
|
||||
.. _os-testr: http://docs.openstack.org/developer/os-testr/
|
||||
|
||||
When you're done, deactivate the virtualenv::
|
||||
|
||||
$ deactivate
|
||||
|
||||
.. include:: ../../../watcher_tempest_plugin/README.rst
|
||||
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.base
|
||||
|
||||
.. _cluster_history_definition:
|
||||
|
||||
Cluster History
|
||||
===============
|
||||
|
||||
.. watcher-term:: watcher.metrics_engine.cluster_history.base
|
||||
|
||||
.. _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>`_.
|
||||
|
||||
.. _efficacy_definition:
|
||||
|
||||
Optimization Efficacy
|
||||
=====================
|
||||
|
||||
The :ref:`Optimization Efficacy <efficacy_definition>` is the objective
|
||||
measure of how much of the :ref:`Goal <goal_definition>` has been achieved in
|
||||
respect with constraints and :ref:`SLAs <sla_definition>` defined by the
|
||||
:ref:`Customer <customer_definition>`.
|
||||
|
||||
The way efficacy is evaluated will depend on the :ref:`Goal <goal_definition>`
|
||||
to achieve.
|
||||
|
||||
Of course, the efficacy will be relevant only as long as the
|
||||
:ref:`Action Plan <action_plan_definition>` is relevant
|
||||
(i.e., the current state of the :ref:`Cluster <cluster_definition>`
|
||||
has not changed in a way that a new :ref:`Audit <audit_definition>` would need
|
||||
to be launched).
|
||||
|
||||
For example, if the :ref:`Goal <goal_definition>` is to lower the energy
|
||||
consumption, the :ref:`Efficacy <efficacy_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 efficacy also enables the :ref:`Administrator <administrator_definition>`
|
||||
to objectively compare different :ref:`Strategies <strategy_definition>` for
|
||||
the same goal and same workload of the :ref:`Cluster <cluster_definition>`.
|
||||
|
||||
.. _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.api.controllers.v1.strategy
|
||||
|
||||
.. _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
14
doc/source/image_src/plantuml/README.rst
Normal file
@@ -0,0 +1,14 @@
|
||||
plantuml
|
||||
========
|
||||
|
||||
|
||||
To build an image from a source file, you have to upload the plantuml JAR file
|
||||
available on http://plantuml.com/download.html.
|
||||
After, just run this command to build your image:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
$ cd doc/source/images
|
||||
$ java -jar /path/to/plantuml.jar doc/source/image_src/plantuml/my_image.txt
|
||||
$ ls doc/source/images/
|
||||
my_image.png
|
||||
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>
|
||||
|
||||
"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) (status=ONGOING)
|
||||
|
||||
ref over "Watcher Decision Engine"
|
||||
Trigger audit in the
|
||||
Watcher Decision Engine
|
||||
end ref
|
||||
|
||||
@enduml
|
||||
@@ -0,0 +1,22 @@
|
||||
@startuml
|
||||
|
||||
actor Administrator
|
||||
|
||||
Administrator -> "Watcher CLI" : watcher audittemplate create <name> <goal> \
|
||||
[--strategy-uuid <strategy>]
|
||||
"Watcher CLI" -> "Watcher API" : POST audit_template(parameters)
|
||||
|
||||
"Watcher API" -> "Watcher Database" : Request if goal exists in database
|
||||
"Watcher API" <-- "Watcher Database" : OK
|
||||
|
||||
"Watcher API" -> "Watcher Database" : Request if strategy exists in database (if provided)
|
||||
"Watcher API" <-- "Watcher Database" : OK
|
||||
|
||||
"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 actionplan start <action_plan_uuid>
|
||||
|
||||
"Watcher CLI" -> "Watcher API" : PATCH action_plan(state=PENDING)
|
||||
"Watcher API" -> "Watcher Database" : action_plan.state=PENDING
|
||||
|
||||
"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
|
||||
87
doc/source/image_src/plantuml/watcher_class_diagram.txt
Normal file
@@ -0,0 +1,87 @@
|
||||
@startuml
|
||||
|
||||
abstract class Base {
|
||||
// Timestamp mixin
|
||||
DateTime created_at
|
||||
DateTime updated_at
|
||||
|
||||
// Soft Delete mixin
|
||||
DateTime deleted_at
|
||||
Integer deleted // default = 0
|
||||
}
|
||||
|
||||
class Strategy {
|
||||
**Integer id** // primary_key
|
||||
String uuid // length = 36
|
||||
String name // length = 63, nullable = false
|
||||
String display_name // length = 63, nullable = false
|
||||
<i>Integer goal_id</i> // ForeignKey('goals.id'), nullable = false
|
||||
}
|
||||
|
||||
|
||||
class Goal {
|
||||
**Integer id** // primary_key
|
||||
String uuid // length = 36
|
||||
String name // length = 63, nullable = false
|
||||
String display_name // length = 63, nullable=False
|
||||
}
|
||||
|
||||
|
||||
class AuditTemplate {
|
||||
**Integer id** // primary_key
|
||||
String uuid // length = 36
|
||||
String name // length = 63, nullable = true
|
||||
String description // length = 255, nullable = true
|
||||
Integer host_aggregate // nullable = true
|
||||
<i>Integer goal_id</i> // ForeignKey('goals.id'), nullable = false
|
||||
<i>Integer strategy_id</i> // ForeignKey('strategies.id'), nullable = true
|
||||
JsonString extra
|
||||
String version // length = 15, nullable = true
|
||||
}
|
||||
|
||||
|
||||
class Audit {
|
||||
**Integer id** // primary_key
|
||||
String uuid // length = 36
|
||||
String type // length = 20
|
||||
String state // length = 20, nullable = true
|
||||
DateTime deadline // nullable = true
|
||||
<i>Integer audit_template_id</i> // ForeignKey('audit_templates.id') \
|
||||
nullable = false
|
||||
}
|
||||
|
||||
|
||||
class Action {
|
||||
**Integer id** // primary_key
|
||||
String uuid // length = 36, nullable = false
|
||||
<i>Integer action_plan_id</i> // ForeignKey('action_plans.id'), nullable = false
|
||||
String action_type // length = 255, nullable = false
|
||||
JsonString input_parameters // nullable = true
|
||||
String state // length = 20, nullable = true
|
||||
String next // length = 36, nullable = true
|
||||
}
|
||||
|
||||
|
||||
class ActionPlan {
|
||||
**Integer id** // primary_key
|
||||
String uuid // length = 36
|
||||
Integer first_action_id //
|
||||
<i>Integer audit_id</i> // ForeignKey('audits.id'), nullable = true
|
||||
String state // length = 20, nullable = true
|
||||
}
|
||||
|
||||
"Base" <|-- "Strategy"
|
||||
"Base" <|-- "Goal"
|
||||
"Base" <|-- "AuditTemplate"
|
||||
"Base" <|-- "Audit"
|
||||
"Base" <|-- "Action"
|
||||
"Base" <|-- "ActionPlan"
|
||||
|
||||
"Goal" <.. "Strategy" : Foreign Key
|
||||
"Goal" <.. "AuditTemplate" : Foreign Key
|
||||
"Strategy" <.. "AuditTemplate" : Foreign Key
|
||||
"AuditTemplate" <.. "Audit" : Foreign Key
|
||||
"ActionPlan" <.. "Action" : Foreign Key
|
||||
"Audit" <.. "ActionPlan" : Foreign Key
|
||||
|
||||
@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: 33 KiB |
BIN
doc/source/images/sequence_create_audit_template.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
doc/source/images/sequence_launch_action_plan.png
Normal file
|
After Width: | Height: | Size: 27 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 |
BIN
doc/source/images/watcher_class_diagram.png
Normal file
|
After Width: | Height: | Size: 89 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,13 +41,23 @@ 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
|
||||
deploy/conf-files
|
||||
dev/testing
|
||||
|
||||
API References
|
||||
--------------
|
||||
|
||||
@@ -36,25 +66,49 @@ 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
|
||||
deploy/gmr
|
||||
|
||||
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
|
||||
@@ -46,7 +52,7 @@ run the following::
|
||||
|
||||
Show the program's version number and exit.
|
||||
|
||||
.. option:: upgrade, downgrade, stamp, revision, version, create_schema
|
||||
.. option:: upgrade, downgrade, stamp, revision, version, create_schema, purge
|
||||
|
||||
The :ref:`command <db-manage_cmds>` to run.
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -211,3 +219,42 @@ version
|
||||
Show help for version and exit.
|
||||
|
||||
This command will output the current database version.
|
||||
|
||||
purge
|
||||
-----
|
||||
|
||||
.. program:: purge
|
||||
|
||||
.. option:: -h, --help
|
||||
|
||||
Show help for purge and exit.
|
||||
|
||||
.. option:: -d, --age-in-days
|
||||
|
||||
The number of days (starting from today) before which we consider soft
|
||||
deleted objects as expired and should hence be erased. By default, all
|
||||
objects soft deleted are considered expired. This can be useful as removing
|
||||
a significant amount of objects may cause a performance issues.
|
||||
|
||||
.. option:: -n, --max-number
|
||||
|
||||
The maximum number of database objects we expect to be deleted. If exceeded,
|
||||
this will prevent any deletion.
|
||||
|
||||
.. option:: -t, --audit-template
|
||||
|
||||
Either the UUID or name of the soft deleted audit template to purge. This
|
||||
will also include any related objects with it.
|
||||
|
||||
.. option:: -e, --exclude-orphans
|
||||
|
||||
This is a flag to indicate when we want to exclude orphan objects from
|
||||
deletion.
|
||||
|
||||
.. option:: --dry-run
|
||||
|
||||
This is a flag to indicate when we want to perform a dry run. This will show
|
||||
the objects that would be deleted instead of actually deleting them.
|
||||
|
||||
This command will purge the current database by removing both its soft deleted
|
||||
and orphan objects.
|
||||
39
doc/source/man/watcher-decision-engine.rst
Normal file
@@ -0,0 +1,39 @@
|
||||
=======================
|
||||
watcher-decision-engine
|
||||
=======================
|
||||
|
||||
---------------------------------------
|
||||
Service for the Watcher Decision Engine
|
||||
---------------------------------------
|
||||
|
||||
:Author: openstack@lists.launchpad.net
|
||||
:Date:
|
||||
:Copyright: OpenStack Foundation
|
||||
:Version:
|
||||
:Manual section: 1
|
||||
:Manual group: cloud computing
|
||||
|
||||
SYNOPSIS
|
||||
========
|
||||
|
||||
watcher-decision-engine [options]
|
||||
|
||||
DESCRIPTION
|
||||
===========
|
||||
|
||||
:ref:`Watcher Decision Engine <watcher_decision_engine_definition>`
|
||||
|
||||
OPTIONS
|
||||
=======
|
||||
|
||||
**General options**
|
||||
|
||||
.. include:: general-options.rst
|
||||
|
||||
FILES
|
||||
=====
|
||||
|
||||
**/etc/watcher/watcher.conf**
|
||||
Default configuration file for Watcher Decision Engine
|
||||
|
||||
.. include:: footer.rst
|
||||
@@ -1 +0,0 @@
|
||||
.. include:: ../../README.rst
|
||||
@@ -1,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:
|
||||
|
||||
|
||||
|
||||
4
etc/watcher/README-watcher.conf.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
To generate the sample watcher.conf file, run the following
|
||||
command from the top level of the watcher directory:
|
||||
|
||||
tox -econfig
|
||||
9
etc/watcher/watcher-config-generator.conf
Normal file
@@ -0,0 +1,9 @@
|
||||
[DEFAULT]
|
||||
output_file = etc/watcher/watcher.conf.sample
|
||||
wrap_width = 79
|
||||
|
||||
namespace = watcher
|
||||
namespace = keystonemiddleware.auth_token
|
||||
namespace = oslo.log
|
||||
namespace = oslo.db
|
||||
namespace = oslo.messaging
|
||||
@@ -1,850 +0,0 @@
|
||||
[DEFAULT]
|
||||
|
||||
#
|
||||
# From oslo.log
|
||||
#
|
||||
|
||||
# Print debugging output (set logging level to DEBUG instead of
|
||||
# default INFO level). (boolean value)
|
||||
#debug = false
|
||||
|
||||
# If set to false, will disable INFO logging level, making WARNING the
|
||||
# default. (boolean value)
|
||||
# This option is deprecated for removal.
|
||||
# Its value may be silently ignored in the future.
|
||||
#verbose = true
|
||||
|
||||
# The name of a logging configuration file. This file is appended to
|
||||
# any existing logging configuration files. For details about logging
|
||||
# configuration files, see the Python logging module documentation.
|
||||
# Note that when logging configuration files are used then all logging
|
||||
# configuration is set in the configuration file and other logging
|
||||
# configuration options are ignored (for example, log_format). (string
|
||||
# value)
|
||||
# Deprecated group/name - [DEFAULT]/log_config
|
||||
#log_config_append = <None>
|
||||
|
||||
# DEPRECATED. A logging.Formatter log message format string which may
|
||||
# use any of the available logging.LogRecord attributes. This option
|
||||
# is deprecated. Please use logging_context_format_string and
|
||||
# logging_default_format_string instead. This option is ignored if
|
||||
# log_config_append is set. (string value)
|
||||
#log_format = <None>
|
||||
|
||||
# Format string for %%(asctime)s in log records. Default: %(default)s
|
||||
# . This option is ignored if log_config_append is set. (string value)
|
||||
#log_date_format = %Y-%m-%d %H:%M:%S
|
||||
|
||||
# (Optional) Name of log file to output to. If no default is set,
|
||||
# logging will go to stdout. This option is ignored if
|
||||
# log_config_append is set. (string value)
|
||||
# Deprecated group/name - [DEFAULT]/logfile
|
||||
#log_file = <None>
|
||||
|
||||
# (Optional) The base directory used for relative --log-file paths.
|
||||
# This option is ignored if log_config_append is set. (string value)
|
||||
# Deprecated group/name - [DEFAULT]/logdir
|
||||
#log_dir = <None>
|
||||
|
||||
# (Optional) Uses logging handler designed to watch file system. When
|
||||
# log file is moved or removed this handler will open a new log file
|
||||
# with specified path instantaneously. It makes sense only if log-file
|
||||
# option is specified and Linux platform is used. This option is
|
||||
# ignored if log_config_append is set. (boolean value)
|
||||
#watch_log_file = false
|
||||
|
||||
# Use syslog for logging. Existing syslog format is DEPRECATED and
|
||||
# will be changed later to honor RFC5424. This option is ignored if
|
||||
# log_config_append is set. (boolean value)
|
||||
#use_syslog = false
|
||||
|
||||
# (Optional) Enables or disables syslog rfc5424 format for logging. If
|
||||
# enabled, prefixes the MSG part of the syslog message with APP-NAME
|
||||
# (RFC5424). The format without the APP-NAME is deprecated in Kilo,
|
||||
# and will be removed in Mitaka, along with this option. This option
|
||||
# is ignored if log_config_append is set. (boolean value)
|
||||
# This option is deprecated for removal.
|
||||
# Its value may be silently ignored in the future.
|
||||
#use_syslog_rfc_format = true
|
||||
|
||||
# Syslog facility to receive log lines. This option is ignored if
|
||||
# log_config_append is set. (string value)
|
||||
#syslog_log_facility = LOG_USER
|
||||
|
||||
# Log output to standard error. This option is ignored if
|
||||
# log_config_append is set. (boolean value)
|
||||
#use_stderr = true
|
||||
|
||||
# Format string to use for log messages with context. (string value)
|
||||
#logging_context_format_string = %(asctime)s.%(msecs)03d %(process)d %(levelname)s %(name)s [%(request_id)s %(user_identity)s] %(instance)s%(message)s
|
||||
|
||||
# Format string to use for log messages without context. (string
|
||||
# value)
|
||||
#logging_default_format_string = %(asctime)s.%(msecs)03d %(process)d %(levelname)s %(name)s [-] %(instance)s%(message)s
|
||||
|
||||
# Data to append to log format when level is DEBUG. (string value)
|
||||
#logging_debug_format_suffix = %(funcName)s %(pathname)s:%(lineno)d
|
||||
|
||||
# Prefix each line of exception output with this format. (string
|
||||
# value)
|
||||
#logging_exception_prefix = %(asctime)s.%(msecs)03d %(process)d ERROR %(name)s %(instance)s
|
||||
|
||||
# List of logger=LEVEL pairs. This option is ignored if
|
||||
# log_config_append is set. (list value)
|
||||
#default_log_levels = amqp=WARN,amqplib=WARN,boto=WARN,qpid=WARN,sqlalchemy=WARN,suds=INFO,oslo.messaging=INFO,iso8601=WARN,requests.packages.urllib3.connectionpool=WARN,urllib3.connectionpool=WARN,websocket=WARN,requests.packages.urllib3.util.retry=WARN,urllib3.util.retry=WARN,keystonemiddleware=WARN,routes.middleware=WARN,stevedore=WARN,taskflow=WARN
|
||||
|
||||
# Enables or disables publication of error events. (boolean value)
|
||||
#publish_errors = false
|
||||
|
||||
# The format for an instance that is passed with the log message.
|
||||
# (string value)
|
||||
#instance_format = "[instance: %(uuid)s] "
|
||||
|
||||
# The format for an instance UUID that is passed with the log message.
|
||||
# (string value)
|
||||
#instance_uuid_format = "[instance: %(uuid)s] "
|
||||
|
||||
# Format string for user_identity field of the
|
||||
# logging_context_format_string (string value)
|
||||
#logging_user_identity_format = %(user)s %(tenant)s %(domain)s %(user_domain)s %(project_domain)s
|
||||
|
||||
# Enables or disables fatal status of deprecations. (boolean value)
|
||||
#fatal_deprecations = false
|
||||
|
||||
#
|
||||
# From oslo.messaging
|
||||
#
|
||||
|
||||
# Size of RPC connection pool. (integer value)
|
||||
# Deprecated group/name - [DEFAULT]/rpc_conn_pool_size
|
||||
#rpc_conn_pool_size = 30
|
||||
|
||||
# ZeroMQ bind address. Should be a wildcard (*), an ethernet
|
||||
# interface, or IP. The "host" option should point or resolve to this
|
||||
# address. (string value)
|
||||
#rpc_zmq_bind_address = *
|
||||
|
||||
# MatchMaker driver. (string value)
|
||||
#rpc_zmq_matchmaker = redis
|
||||
|
||||
# Use REQ/REP pattern for all methods CALL/CAST/FANOUT. (boolean
|
||||
# value)
|
||||
#rpc_zmq_all_req_rep = true
|
||||
|
||||
# Type of concurrency used. Either "native" or "eventlet" (string
|
||||
# value)
|
||||
#rpc_zmq_concurrency = eventlet
|
||||
|
||||
# Number of ZeroMQ contexts, defaults to 1. (integer value)
|
||||
#rpc_zmq_contexts = 1
|
||||
|
||||
# Maximum number of ingress messages to locally buffer per topic.
|
||||
# Default is unlimited. (integer value)
|
||||
#rpc_zmq_topic_backlog = <None>
|
||||
|
||||
# Directory for holding IPC sockets. (string value)
|
||||
#rpc_zmq_ipc_dir = /var/run/openstack
|
||||
|
||||
# Name of this node. Must be a valid hostname, FQDN, or IP address.
|
||||
# Must match "host" option, if running Nova. (string value)
|
||||
#rpc_zmq_host = localhost
|
||||
|
||||
# Seconds to wait before a cast expires (TTL). Only supported by
|
||||
# impl_zmq. (integer value)
|
||||
#rpc_cast_timeout = 30
|
||||
|
||||
# The default number of seconds that poll should wait. Poll raises
|
||||
# timeout exception when timeout expired. (integer value)
|
||||
#rpc_poll_timeout = 1
|
||||
|
||||
# Shows whether zmq-messaging uses broker or not. (boolean value)
|
||||
#zmq_use_broker = true
|
||||
|
||||
# Minimal port number for random ports range. (integer value)
|
||||
#rpc_zmq_min_port = 49152
|
||||
|
||||
# Maximal port number for random ports range. (integer value)
|
||||
#rpc_zmq_max_port = 65536
|
||||
|
||||
# Number of retries to find free port number before fail with
|
||||
# ZMQBindError. (integer value)
|
||||
#rpc_zmq_bind_port_retries = 100
|
||||
|
||||
# Host to locate redis. (string value)
|
||||
#host = 127.0.0.1
|
||||
|
||||
# Use this port to connect to redis host. (integer value)
|
||||
#port = 6379
|
||||
|
||||
# Password for Redis server (optional). (string value)
|
||||
#password =
|
||||
|
||||
# Size of executor thread pool. (integer value)
|
||||
# Deprecated group/name - [DEFAULT]/rpc_thread_pool_size
|
||||
#executor_thread_pool_size = 64
|
||||
|
||||
# The Drivers(s) to handle sending notifications. Possible values are
|
||||
# messaging, messagingv2, routing, log, test, noop (multi valued)
|
||||
#notification_driver =
|
||||
|
||||
# A URL representing the messaging driver to use for notifications. If
|
||||
# not set, we fall back to the same configuration used for RPC.
|
||||
# (string value)
|
||||
#notification_transport_url = <None>
|
||||
|
||||
# AMQP topic used for OpenStack notifications. (list value)
|
||||
# Deprecated group/name - [rpc_notifier2]/topics
|
||||
#notification_topics = notifications
|
||||
|
||||
# Seconds to wait for a response from a call. (integer value)
|
||||
#rpc_response_timeout = 60
|
||||
|
||||
# A URL representing the messaging driver to use and its full
|
||||
# configuration. If not set, we fall back to the rpc_backend option
|
||||
# and driver specific configuration. (string value)
|
||||
#transport_url = <None>
|
||||
|
||||
# The messaging driver to use, defaults to rabbit. Other drivers
|
||||
# include qpid and zmq. (string value)
|
||||
#rpc_backend = rabbit
|
||||
|
||||
# The default exchange under which topics are scoped. May be
|
||||
# overridden by an exchange name specified in the transport_url
|
||||
# option. (string value)
|
||||
#control_exchange = openstack
|
||||
|
||||
|
||||
[api]
|
||||
|
||||
#
|
||||
# From watcher
|
||||
#
|
||||
|
||||
# The port for the watcher API server (integer value)
|
||||
#port = 9322
|
||||
|
||||
# The listen IP for the watcher API server (string value)
|
||||
#host = 0.0.0.0
|
||||
|
||||
# The maximum number of items returned in a single response from a
|
||||
# collection resource. (integer value)
|
||||
#max_limit = 1000
|
||||
|
||||
|
||||
[database]
|
||||
|
||||
#
|
||||
# From oslo.db
|
||||
#
|
||||
|
||||
# The file name to use with SQLite. (string value)
|
||||
# Deprecated group/name - [DEFAULT]/sqlite_db
|
||||
#sqlite_db = oslo.sqlite
|
||||
|
||||
# If True, SQLite uses synchronous mode. (boolean value)
|
||||
# Deprecated group/name - [DEFAULT]/sqlite_synchronous
|
||||
#sqlite_synchronous = true
|
||||
|
||||
# The back end to use for the database. (string value)
|
||||
# Deprecated group/name - [DEFAULT]/db_backend
|
||||
#backend = sqlalchemy
|
||||
|
||||
# The SQLAlchemy connection string to use to connect to the database.
|
||||
# (string value)
|
||||
# Deprecated group/name - [DEFAULT]/sql_connection
|
||||
# Deprecated group/name - [DATABASE]/sql_connection
|
||||
# Deprecated group/name - [sql]/connection
|
||||
#connection = <None>
|
||||
|
||||
# The SQLAlchemy connection string to use to connect to the slave
|
||||
# database. (string value)
|
||||
#slave_connection = <None>
|
||||
|
||||
# The SQL mode to be used for MySQL sessions. This option, including
|
||||
# the default, overrides any server-set SQL mode. To use whatever SQL
|
||||
# mode is set by the server configuration, set this to no value.
|
||||
# Example: mysql_sql_mode= (string value)
|
||||
#mysql_sql_mode = TRADITIONAL
|
||||
|
||||
# Timeout before idle SQL connections are reaped. (integer value)
|
||||
# Deprecated group/name - [DEFAULT]/sql_idle_timeout
|
||||
# Deprecated group/name - [DATABASE]/sql_idle_timeout
|
||||
# Deprecated group/name - [sql]/idle_timeout
|
||||
#idle_timeout = 3600
|
||||
|
||||
# Minimum number of SQL connections to keep open in a pool. (integer
|
||||
# value)
|
||||
# Deprecated group/name - [DEFAULT]/sql_min_pool_size
|
||||
# Deprecated group/name - [DATABASE]/sql_min_pool_size
|
||||
#min_pool_size = 1
|
||||
|
||||
# Maximum number of SQL connections to keep open in a pool. (integer
|
||||
# value)
|
||||
# Deprecated group/name - [DEFAULT]/sql_max_pool_size
|
||||
# Deprecated group/name - [DATABASE]/sql_max_pool_size
|
||||
#max_pool_size = <None>
|
||||
|
||||
# Maximum number of database connection retries during startup. Set to
|
||||
# -1 to specify an infinite retry count. (integer value)
|
||||
# Deprecated group/name - [DEFAULT]/sql_max_retries
|
||||
# Deprecated group/name - [DATABASE]/sql_max_retries
|
||||
#max_retries = 10
|
||||
|
||||
# Interval between retries of opening a SQL connection. (integer
|
||||
# value)
|
||||
# Deprecated group/name - [DEFAULT]/sql_retry_interval
|
||||
# Deprecated group/name - [DATABASE]/reconnect_interval
|
||||
#retry_interval = 10
|
||||
|
||||
# If set, use this value for max_overflow with SQLAlchemy. (integer
|
||||
# value)
|
||||
# Deprecated group/name - [DEFAULT]/sql_max_overflow
|
||||
# Deprecated group/name - [DATABASE]/sqlalchemy_max_overflow
|
||||
#max_overflow = <None>
|
||||
|
||||
# Verbosity of SQL debugging information: 0=None, 100=Everything.
|
||||
# (integer value)
|
||||
# Deprecated group/name - [DEFAULT]/sql_connection_debug
|
||||
#connection_debug = 0
|
||||
|
||||
# Add Python stack traces to SQL as comment strings. (boolean value)
|
||||
# Deprecated group/name - [DEFAULT]/sql_connection_trace
|
||||
#connection_trace = false
|
||||
|
||||
# If set, use this value for pool_timeout with SQLAlchemy. (integer
|
||||
# value)
|
||||
# Deprecated group/name - [DATABASE]/sqlalchemy_pool_timeout
|
||||
#pool_timeout = <None>
|
||||
|
||||
# Enable the experimental use of database reconnect on connection
|
||||
# lost. (boolean value)
|
||||
#use_db_reconnect = false
|
||||
|
||||
# Seconds between retries of a database transaction. (integer value)
|
||||
#db_retry_interval = 1
|
||||
|
||||
# If True, increases the interval between retries of a database
|
||||
# operation up to db_max_retry_interval. (boolean value)
|
||||
#db_inc_retry_interval = true
|
||||
|
||||
# If db_inc_retry_interval is set, the maximum seconds between retries
|
||||
# of a database operation. (integer value)
|
||||
#db_max_retry_interval = 10
|
||||
|
||||
# Maximum retries in case of connection error or deadlock error before
|
||||
# error is raised. Set to -1 to specify an infinite retry count.
|
||||
# (integer value)
|
||||
#db_max_retries = 20
|
||||
|
||||
|
||||
[keystone_authtoken]
|
||||
|
||||
#
|
||||
# From keystonemiddleware.auth_token
|
||||
#
|
||||
|
||||
# Complete public Identity API endpoint. (string value)
|
||||
#auth_uri = <None>
|
||||
|
||||
# API version of the admin Identity API endpoint. (string value)
|
||||
#auth_version = <None>
|
||||
|
||||
# Do not handle authorization requests within the middleware, but
|
||||
# delegate the authorization decision to downstream WSGI components.
|
||||
# (boolean value)
|
||||
#delay_auth_decision = false
|
||||
|
||||
# Request timeout value for communicating with Identity API server.
|
||||
# (integer value)
|
||||
#http_connect_timeout = <None>
|
||||
|
||||
# How many times are we trying to reconnect when communicating with
|
||||
# Identity API Server. (integer value)
|
||||
#http_request_max_retries = 3
|
||||
|
||||
# Env key for the swift cache. (string value)
|
||||
#cache = <None>
|
||||
|
||||
# Required if identity server requires client certificate (string
|
||||
# value)
|
||||
#certfile = <None>
|
||||
|
||||
# Required if identity server requires client certificate (string
|
||||
# value)
|
||||
#keyfile = <None>
|
||||
|
||||
# A PEM encoded Certificate Authority to use when verifying HTTPs
|
||||
# connections. Defaults to system CAs. (string value)
|
||||
#cafile = <None>
|
||||
|
||||
# Verify HTTPS connections. (boolean value)
|
||||
#insecure = false
|
||||
|
||||
# The region in which the identity server can be found. (string value)
|
||||
#region_name = <None>
|
||||
|
||||
# Directory used to cache files related to PKI tokens. (string value)
|
||||
#signing_dir = <None>
|
||||
|
||||
# Optionally specify a list of memcached server(s) to use for caching.
|
||||
# If left undefined, tokens will instead be cached in-process. (list
|
||||
# value)
|
||||
# Deprecated group/name - [DEFAULT]/memcache_servers
|
||||
#memcached_servers = <None>
|
||||
|
||||
# In order to prevent excessive effort spent validating tokens, the
|
||||
# middleware caches previously-seen tokens for a configurable duration
|
||||
# (in seconds). Set to -1 to disable caching completely. (integer
|
||||
# value)
|
||||
#token_cache_time = 300
|
||||
|
||||
# Determines the frequency at which the list of revoked tokens is
|
||||
# retrieved from the Identity service (in seconds). A high number of
|
||||
# revocation events combined with a low cache duration may
|
||||
# significantly reduce performance. (integer value)
|
||||
#revocation_cache_time = 10
|
||||
|
||||
# (Optional) If defined, indicate whether token data should be
|
||||
# authenticated or authenticated and encrypted. Acceptable values are
|
||||
# MAC or ENCRYPT. If MAC, token data is authenticated (with HMAC) in
|
||||
# the cache. If ENCRYPT, token data is encrypted and authenticated in
|
||||
# the cache. If the value is not one of these options or empty,
|
||||
# auth_token will raise an exception on initialization. (string value)
|
||||
#memcache_security_strategy = <None>
|
||||
|
||||
# (Optional, mandatory if memcache_security_strategy is defined) This
|
||||
# string is used for key derivation. (string value)
|
||||
#memcache_secret_key = <None>
|
||||
|
||||
# (Optional) Number of seconds memcached server is considered dead
|
||||
# before it is tried again. (integer value)
|
||||
#memcache_pool_dead_retry = 300
|
||||
|
||||
# (Optional) Maximum total number of open connections to every
|
||||
# memcached server. (integer value)
|
||||
#memcache_pool_maxsize = 10
|
||||
|
||||
# (Optional) Socket timeout in seconds for communicating with a
|
||||
# memcached server. (integer value)
|
||||
#memcache_pool_socket_timeout = 3
|
||||
|
||||
# (Optional) Number of seconds a connection to memcached is held
|
||||
# unused in the pool before it is closed. (integer value)
|
||||
#memcache_pool_unused_timeout = 60
|
||||
|
||||
# (Optional) Number of seconds that an operation will wait to get a
|
||||
# memcached client connection from the pool. (integer value)
|
||||
#memcache_pool_conn_get_timeout = 10
|
||||
|
||||
# (Optional) Use the advanced (eventlet safe) memcached client pool.
|
||||
# The advanced pool will only work under python 2.x. (boolean value)
|
||||
#memcache_use_advanced_pool = false
|
||||
|
||||
# (Optional) Indicate whether to set the X-Service-Catalog header. If
|
||||
# False, middleware will not ask for service catalog on token
|
||||
# validation and will not set the X-Service-Catalog header. (boolean
|
||||
# value)
|
||||
#include_service_catalog = true
|
||||
|
||||
# Used to control the use and type of token binding. Can be set to:
|
||||
# "disabled" to not check token binding. "permissive" (default) to
|
||||
# validate binding information if the bind type is of a form known to
|
||||
# the server and ignore it if not. "strict" like "permissive" but if
|
||||
# the bind type is unknown the token will be rejected. "required" any
|
||||
# form of token binding is needed to be allowed. Finally the name of a
|
||||
# binding method that must be present in tokens. (string value)
|
||||
#enforce_token_bind = permissive
|
||||
|
||||
# If true, the revocation list will be checked for cached tokens. This
|
||||
# requires that PKI tokens are configured on the identity server.
|
||||
# (boolean value)
|
||||
#check_revocations_for_cached = false
|
||||
|
||||
# Hash algorithms to use for hashing PKI tokens. This may be a single
|
||||
# algorithm or multiple. The algorithms are those supported by Python
|
||||
# standard hashlib.new(). The hashes will be tried in the order given,
|
||||
# so put the preferred one first for performance. The result of the
|
||||
# first hash will be stored in the cache. This will typically be set
|
||||
# to multiple values only while migrating from a less secure algorithm
|
||||
# to a more secure one. Once all the old tokens are expired this
|
||||
# option should be set to a single value for better performance. (list
|
||||
# value)
|
||||
#hash_algorithms = md5
|
||||
|
||||
# Prefix to prepend at the beginning of the path. Deprecated, use
|
||||
# identity_uri. (string value)
|
||||
#auth_admin_prefix =
|
||||
|
||||
# Host providing the admin Identity API endpoint. Deprecated, use
|
||||
# identity_uri. (string value)
|
||||
#auth_host = 127.0.0.1
|
||||
|
||||
# Port of the admin Identity API endpoint. Deprecated, use
|
||||
# identity_uri. (integer value)
|
||||
#auth_port = 35357
|
||||
|
||||
# Protocol of the admin Identity API endpoint (http or https).
|
||||
# Deprecated, use identity_uri. (string value)
|
||||
#auth_protocol = https
|
||||
|
||||
# Complete admin Identity API endpoint. This should specify the
|
||||
# unversioned root endpoint e.g. https://localhost:35357/ (string
|
||||
# value)
|
||||
#identity_uri = <None>
|
||||
|
||||
# This option is deprecated and may be removed in a future release.
|
||||
# Single shared secret with the Keystone configuration used for
|
||||
# bootstrapping a Keystone installation, or otherwise bypassing the
|
||||
# normal authentication process. This option should not be used, use
|
||||
# `admin_user` and `admin_password` instead. (string value)
|
||||
#admin_token = <None>
|
||||
|
||||
# Service username. (string value)
|
||||
#admin_user = <None>
|
||||
|
||||
# Service user password. (string value)
|
||||
#admin_password = <None>
|
||||
|
||||
# Service tenant name. (string value)
|
||||
#admin_tenant_name = admin
|
||||
|
||||
|
||||
[matchmaker_redis]
|
||||
|
||||
#
|
||||
# From oslo.messaging
|
||||
#
|
||||
|
||||
# Host to locate redis. (string value)
|
||||
#host = 127.0.0.1
|
||||
|
||||
# Use this port to connect to redis host. (integer value)
|
||||
#port = 6379
|
||||
|
||||
# Password for Redis server (optional). (string value)
|
||||
#password =
|
||||
|
||||
|
||||
[oslo_messaging_amqp]
|
||||
|
||||
#
|
||||
# From oslo.messaging
|
||||
#
|
||||
|
||||
# address prefix used when sending to a specific server (string value)
|
||||
# Deprecated group/name - [amqp1]/server_request_prefix
|
||||
#server_request_prefix = exclusive
|
||||
|
||||
# address prefix used when broadcasting to all servers (string value)
|
||||
# Deprecated group/name - [amqp1]/broadcast_prefix
|
||||
#broadcast_prefix = broadcast
|
||||
|
||||
# address prefix when sending to any server in group (string value)
|
||||
# Deprecated group/name - [amqp1]/group_request_prefix
|
||||
#group_request_prefix = unicast
|
||||
|
||||
# Name for the AMQP container (string value)
|
||||
# Deprecated group/name - [amqp1]/container_name
|
||||
#container_name = <None>
|
||||
|
||||
# Timeout for inactive connections (in seconds) (integer value)
|
||||
# Deprecated group/name - [amqp1]/idle_timeout
|
||||
#idle_timeout = 0
|
||||
|
||||
# Debug: dump AMQP frames to stdout (boolean value)
|
||||
# Deprecated group/name - [amqp1]/trace
|
||||
#trace = false
|
||||
|
||||
# CA certificate PEM file to verify server certificate (string value)
|
||||
# Deprecated group/name - [amqp1]/ssl_ca_file
|
||||
#ssl_ca_file =
|
||||
|
||||
# Identifying certificate PEM file to present to clients (string
|
||||
# value)
|
||||
# Deprecated group/name - [amqp1]/ssl_cert_file
|
||||
#ssl_cert_file =
|
||||
|
||||
# Private key PEM file used to sign cert_file certificate (string
|
||||
# value)
|
||||
# Deprecated group/name - [amqp1]/ssl_key_file
|
||||
#ssl_key_file =
|
||||
|
||||
# Password for decrypting ssl_key_file (if encrypted) (string value)
|
||||
# Deprecated group/name - [amqp1]/ssl_key_password
|
||||
#ssl_key_password = <None>
|
||||
|
||||
# Accept clients using either SSL or plain TCP (boolean value)
|
||||
# Deprecated group/name - [amqp1]/allow_insecure_clients
|
||||
#allow_insecure_clients = false
|
||||
|
||||
# Space separated list of acceptable SASL mechanisms (string value)
|
||||
# Deprecated group/name - [amqp1]/sasl_mechanisms
|
||||
#sasl_mechanisms =
|
||||
|
||||
# Path to directory that contains the SASL configuration (string
|
||||
# value)
|
||||
# Deprecated group/name - [amqp1]/sasl_config_dir
|
||||
#sasl_config_dir =
|
||||
|
||||
# Name of configuration file (without .conf suffix) (string value)
|
||||
# Deprecated group/name - [amqp1]/sasl_config_name
|
||||
#sasl_config_name =
|
||||
|
||||
# User name for message broker authentication (string value)
|
||||
# Deprecated group/name - [amqp1]/username
|
||||
#username =
|
||||
|
||||
# Password for message broker authentication (string value)
|
||||
# Deprecated group/name - [amqp1]/password
|
||||
#password =
|
||||
|
||||
|
||||
[oslo_messaging_qpid]
|
||||
|
||||
#
|
||||
# From oslo.messaging
|
||||
#
|
||||
|
||||
# Use durable queues in AMQP. (boolean value)
|
||||
# Deprecated group/name - [DEFAULT]/amqp_durable_queues
|
||||
# Deprecated group/name - [DEFAULT]/rabbit_durable_queues
|
||||
#amqp_durable_queues = false
|
||||
|
||||
# Auto-delete queues in AMQP. (boolean value)
|
||||
# Deprecated group/name - [DEFAULT]/amqp_auto_delete
|
||||
#amqp_auto_delete = false
|
||||
|
||||
# Send a single AMQP reply to call message. The current behaviour
|
||||
# since oslo-incubator is to send two AMQP replies - first one with
|
||||
# the payload, a second one to ensure the other have finish to send
|
||||
# the payload. We are going to remove it in the N release, but we must
|
||||
# keep backward compatible at the same time. This option provides such
|
||||
# compatibility - it defaults to False in Liberty and can be turned on
|
||||
# for early adopters with a new installations or for testing. Please
|
||||
# note, that this option will be removed in the Mitaka release.
|
||||
# (boolean value)
|
||||
#send_single_reply = false
|
||||
|
||||
# Qpid broker hostname. (string value)
|
||||
# Deprecated group/name - [DEFAULT]/qpid_hostname
|
||||
#qpid_hostname = localhost
|
||||
|
||||
# Qpid broker port. (integer value)
|
||||
# Deprecated group/name - [DEFAULT]/qpid_port
|
||||
#qpid_port = 5672
|
||||
|
||||
# Qpid HA cluster host:port pairs. (list value)
|
||||
# Deprecated group/name - [DEFAULT]/qpid_hosts
|
||||
#qpid_hosts = $qpid_hostname:$qpid_port
|
||||
|
||||
# Username for Qpid connection. (string value)
|
||||
# Deprecated group/name - [DEFAULT]/qpid_username
|
||||
#qpid_username =
|
||||
|
||||
# Password for Qpid connection. (string value)
|
||||
# Deprecated group/name - [DEFAULT]/qpid_password
|
||||
#qpid_password =
|
||||
|
||||
# Space separated list of SASL mechanisms to use for auth. (string
|
||||
# value)
|
||||
# Deprecated group/name - [DEFAULT]/qpid_sasl_mechanisms
|
||||
#qpid_sasl_mechanisms =
|
||||
|
||||
# Seconds between connection keepalive heartbeats. (integer value)
|
||||
# Deprecated group/name - [DEFAULT]/qpid_heartbeat
|
||||
#qpid_heartbeat = 60
|
||||
|
||||
# Transport to use, either 'tcp' or 'ssl'. (string value)
|
||||
# Deprecated group/name - [DEFAULT]/qpid_protocol
|
||||
#qpid_protocol = tcp
|
||||
|
||||
# Whether to disable the Nagle algorithm. (boolean value)
|
||||
# Deprecated group/name - [DEFAULT]/qpid_tcp_nodelay
|
||||
#qpid_tcp_nodelay = true
|
||||
|
||||
# The number of prefetched messages held by receiver. (integer value)
|
||||
# Deprecated group/name - [DEFAULT]/qpid_receiver_capacity
|
||||
#qpid_receiver_capacity = 1
|
||||
|
||||
# The qpid topology version to use. Version 1 is what was originally
|
||||
# used by impl_qpid. Version 2 includes some backwards-incompatible
|
||||
# changes that allow broker federation to work. Users should update
|
||||
# to version 2 when they are able to take everything down, as it
|
||||
# requires a clean break. (integer value)
|
||||
# Deprecated group/name - [DEFAULT]/qpid_topology_version
|
||||
#qpid_topology_version = 1
|
||||
|
||||
|
||||
[oslo_messaging_rabbit]
|
||||
|
||||
#
|
||||
# From oslo.messaging
|
||||
#
|
||||
|
||||
# Use durable queues in AMQP. (boolean value)
|
||||
# Deprecated group/name - [DEFAULT]/amqp_durable_queues
|
||||
# Deprecated group/name - [DEFAULT]/rabbit_durable_queues
|
||||
#amqp_durable_queues = false
|
||||
|
||||
# Auto-delete queues in AMQP. (boolean value)
|
||||
# Deprecated group/name - [DEFAULT]/amqp_auto_delete
|
||||
#amqp_auto_delete = false
|
||||
|
||||
# Send a single AMQP reply to call message. The current behaviour
|
||||
# since oslo-incubator is to send two AMQP replies - first one with
|
||||
# the payload, a second one to ensure the other have finish to send
|
||||
# the payload. We are going to remove it in the N release, but we must
|
||||
# keep backward compatible at the same time. This option provides such
|
||||
# compatibility - it defaults to False in Liberty and can be turned on
|
||||
# for early adopters with a new installations or for testing. Please
|
||||
# note, that this option will be removed in the Mitaka release.
|
||||
# (boolean value)
|
||||
#send_single_reply = false
|
||||
|
||||
# SSL version to use (valid only if SSL enabled). Valid values are
|
||||
# TLSv1 and SSLv23. SSLv2, SSLv3, TLSv1_1, and TLSv1_2 may be
|
||||
# available on some distributions. (string value)
|
||||
# Deprecated group/name - [DEFAULT]/kombu_ssl_version
|
||||
#kombu_ssl_version =
|
||||
|
||||
# SSL key file (valid only if SSL enabled). (string value)
|
||||
# Deprecated group/name - [DEFAULT]/kombu_ssl_keyfile
|
||||
#kombu_ssl_keyfile =
|
||||
|
||||
# SSL cert file (valid only if SSL enabled). (string value)
|
||||
# Deprecated group/name - [DEFAULT]/kombu_ssl_certfile
|
||||
#kombu_ssl_certfile =
|
||||
|
||||
# SSL certification authority file (valid only if SSL enabled).
|
||||
# (string value)
|
||||
# Deprecated group/name - [DEFAULT]/kombu_ssl_ca_certs
|
||||
#kombu_ssl_ca_certs =
|
||||
|
||||
# How long to wait before reconnecting in response to an AMQP consumer
|
||||
# cancel notification. (floating point value)
|
||||
# Deprecated group/name - [DEFAULT]/kombu_reconnect_delay
|
||||
#kombu_reconnect_delay = 1.0
|
||||
|
||||
# How long to wait before considering a reconnect attempt to have
|
||||
# failed. This value should not be longer than rpc_response_timeout.
|
||||
# (integer value)
|
||||
#kombu_reconnect_timeout = 60
|
||||
|
||||
# The RabbitMQ broker address where a single node is used. (string
|
||||
# value)
|
||||
# Deprecated group/name - [DEFAULT]/rabbit_host
|
||||
#rabbit_host = localhost
|
||||
|
||||
# The RabbitMQ broker port where a single node is used. (integer
|
||||
# value)
|
||||
# Deprecated group/name - [DEFAULT]/rabbit_port
|
||||
#rabbit_port = 5672
|
||||
|
||||
# RabbitMQ HA cluster host:port pairs. (list value)
|
||||
# Deprecated group/name - [DEFAULT]/rabbit_hosts
|
||||
#rabbit_hosts = $rabbit_host:$rabbit_port
|
||||
|
||||
# Connect over SSL for RabbitMQ. (boolean value)
|
||||
# Deprecated group/name - [DEFAULT]/rabbit_use_ssl
|
||||
#rabbit_use_ssl = false
|
||||
|
||||
# The RabbitMQ userid. (string value)
|
||||
# Deprecated group/name - [DEFAULT]/rabbit_userid
|
||||
#rabbit_userid = guest
|
||||
|
||||
# The RabbitMQ password. (string value)
|
||||
# Deprecated group/name - [DEFAULT]/rabbit_password
|
||||
#rabbit_password = guest
|
||||
|
||||
# The RabbitMQ login method. (string value)
|
||||
# Deprecated group/name - [DEFAULT]/rabbit_login_method
|
||||
#rabbit_login_method = AMQPLAIN
|
||||
|
||||
# The RabbitMQ virtual host. (string value)
|
||||
# Deprecated group/name - [DEFAULT]/rabbit_virtual_host
|
||||
#rabbit_virtual_host = /
|
||||
|
||||
# How frequently to retry connecting with RabbitMQ. (integer value)
|
||||
#rabbit_retry_interval = 1
|
||||
|
||||
# How long to backoff for between retries when connecting to RabbitMQ.
|
||||
# (integer value)
|
||||
# Deprecated group/name - [DEFAULT]/rabbit_retry_backoff
|
||||
#rabbit_retry_backoff = 2
|
||||
|
||||
# Maximum number of RabbitMQ connection retries. Default is 0
|
||||
# (infinite retry count). (integer value)
|
||||
# Deprecated group/name - [DEFAULT]/rabbit_max_retries
|
||||
#rabbit_max_retries = 0
|
||||
|
||||
# Use HA queues in RabbitMQ (x-ha-policy: all). If you change this
|
||||
# option, you must wipe the RabbitMQ database. (boolean value)
|
||||
# Deprecated group/name - [DEFAULT]/rabbit_ha_queues
|
||||
#rabbit_ha_queues = false
|
||||
|
||||
# Number of seconds after which the Rabbit broker is considered down
|
||||
# if heartbeat's keep-alive fails (0 disable the heartbeat).
|
||||
# EXPERIMENTAL (integer value)
|
||||
#heartbeat_timeout_threshold = 60
|
||||
|
||||
# How often times during the heartbeat_timeout_threshold we check the
|
||||
# heartbeat. (integer value)
|
||||
#heartbeat_rate = 2
|
||||
|
||||
# Deprecated, use rpc_backend=kombu+memory or rpc_backend=fake
|
||||
# (boolean value)
|
||||
# Deprecated group/name - [DEFAULT]/fake_rabbit
|
||||
#fake_rabbit = false
|
||||
|
||||
|
||||
[watcher_applier]
|
||||
|
||||
#
|
||||
# From watcher
|
||||
#
|
||||
|
||||
# The number of worker (integer value)
|
||||
#applier_worker = 1
|
||||
|
||||
# The topic name used forcontrol events, this topic used for rpc call
|
||||
# (string value)
|
||||
#topic_control = watcher.applier.control
|
||||
|
||||
# The topic name used for status events, this topic is used so as to
|
||||
# notifythe others components of the system (string value)
|
||||
#topic_status = watcher.applier.status
|
||||
|
||||
# The identifier used by watcher module on the message broker (string
|
||||
# value)
|
||||
#publisher_id = watcher.applier.api
|
||||
|
||||
|
||||
[watcher_decision_engine]
|
||||
|
||||
#
|
||||
# From watcher
|
||||
#
|
||||
|
||||
# The topic name used forcontrol events, this topic used for rpc call
|
||||
# (string value)
|
||||
#topic_control = watcher.decision.control
|
||||
|
||||
# The topic name used for status events, this topic is used so as to
|
||||
# notifythe others components of the system (string value)
|
||||
#topic_status = watcher.decision.status
|
||||
|
||||
# The identifier used by watcher module on the message broker (string
|
||||
# value)
|
||||
#publisher_id = watcher.decision.api
|
||||
|
||||
|
||||
[watcher_goals]
|
||||
|
||||
#
|
||||
# From watcher
|
||||
#
|
||||
|
||||
# Goals used for the optimization. Maps each goal to an associated
|
||||
# strategy (for example: BASIC_CONSOLIDATION:basic,
|
||||
# MY_GOAL:my_strategy_1) (dict value)
|
||||
#goals = DUMMY:dummy
|
||||
@@ -2,28 +2,37 @@
|
||||
# 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.5.0,>=4.0.0 # Apache-2.0
|
||||
oslo.concurrency>=3.8.0 # Apache-2.0
|
||||
oslo.cache>=1.5.0 # Apache-2.0
|
||||
oslo.config>=3.9.0 # Apache-2.0
|
||||
oslo.context>=2.2.0 # Apache-2.0
|
||||
oslo.db>=4.1.0 # Apache-2.0
|
||||
oslo.i18n>=2.1.0 # Apache-2.0
|
||||
oslo.log>=1.14.0 # Apache-2.0
|
||||
oslo.messaging>=4.5.0 # Apache-2.0
|
||||
oslo.policy>=0.5.0 # Apache-2.0
|
||||
oslo.reports>=0.6.0 # Apache-2.0
|
||||
oslo.service>=1.10.0 # Apache-2.0
|
||||
oslo.utils>=3.5.0 # Apache-2.0
|
||||
PasteDeploy>=1.5.0 # MIT
|
||||
pbr>=1.6 # Apache-2.0
|
||||
pecan>=1.0.0 # BSD
|
||||
PrettyTable<0.8,>=0.7 # BSD
|
||||
voluptuous>=0.8.9 # BSD License
|
||||
python-ceilometerclient>=2.2.1 # Apache-2.0
|
||||
python-cinderclient!=1.7.0,>=1.6.0 # Apache-2.0
|
||||
python-glanceclient>=2.0.0 # Apache-2.0
|
||||
python-keystoneclient!=1.8.0,!=2.1.0,>=1.7.0 # Apache-2.0
|
||||
python-neutronclient>=4.2.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.10.0 # Apache-2.0
|
||||
taskflow>=1.26.0 # Apache-2.0
|
||||
WebOb>=1.2.3 # MIT
|
||||
WSME>=0.8 # MIT
|
||||
|
||||
39
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,47 @@ 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
|
||||
vm_workload_consolidation = watcher.decision_engine.strategy.strategies.vm_workload_consolidation:VMWorkloadConsolidation
|
||||
workload_stabilization = watcher.decision_engine.strategy.strategies.workload_stabilization:WorkloadStabilization
|
||||
workload_balance = watcher.decision_engine.strategy.strategies.workload_balance:WorkloadBalance
|
||||
|
||||
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 +96,6 @@ output_dir = watcher/locale
|
||||
input_file = watcher/locale/watcher.pot
|
||||
|
||||
[extract_messages]
|
||||
keywords = _ gettext ngettext l_ lazy_gettext
|
||||
keywords = _ gettext ngettext l_ lazy_gettext _LI _LW _LE _LC
|
||||
mapping_file = babel.cfg
|
||||
output_file = watcher/locale/watcher.pot
|
||||
|
||||
3
setup.py
Executable file → Normal file
@@ -1,4 +1,3 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -26,5 +25,5 @@ except ImportError:
|
||||
pass
|
||||
|
||||
setuptools.setup(
|
||||
setup_requires=['pbr'],
|
||||
setup_requires=['pbr>=1.8'],
|
||||
pbr=True)
|
||||
|
||||
@@ -2,20 +2,20 @@
|
||||
# of appearance. Changing the order has an impact on the overall integration
|
||||
# process, which may cause wedges in the gate later.
|
||||
|
||||
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
|
||||
freezegun # Apache-2.0
|
||||
hacking<0.11,>=0.10.2
|
||||
mock>=2.0 # 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
|
||||
|
||||
48
tox.ini
@@ -1,54 +1,60 @@
|
||||
[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
|
||||
commands =
|
||||
oslo-config-generator --namespace watcher \
|
||||
--namespace keystonemiddleware.auth_token \
|
||||
--namespace oslo.log \
|
||||
--namespace oslo.db \
|
||||
--namespace oslo.messaging \
|
||||
--output-file etc/watcher/watcher.conf.sample
|
||||
oslo-config-generator --config-file etc/watcher/watcher-config-generator.conf
|
||||
|
||||
[flake8]
|
||||
# E123, E125 skipped as they are invalid PEP-8.
|
||||
|
||||
show-source=True
|
||||
ignore=E123,E125,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
|
||||
|
||||
51
watcher/_i18n.py
Normal file
@@ -0,0 +1,51 @@
|
||||
# -*- 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
|
||||
from oslo_i18n import _lazy
|
||||
|
||||
# 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 lazy_translation_enabled():
|
||||
return _lazy.USE_LAZY
|
||||
|
||||
|
||||
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
|
||||
@@ -19,24 +19,38 @@
|
||||
from oslo_config import cfg
|
||||
import pecan
|
||||
|
||||
from watcher._i18n import _
|
||||
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 \
|
||||
as strategy_selector
|
||||
|
||||
# Register options for the service
|
||||
API_SERVICE_OPTS = [
|
||||
cfg.IntOpt('port',
|
||||
default=9322,
|
||||
help='The port for the watcher API server'),
|
||||
cfg.PortOpt('port',
|
||||
default=9322,
|
||||
help=_('The port for the watcher API server')),
|
||||
cfg.StrOpt('host',
|
||||
default='0.0.0.0',
|
||||
help='The listen IP for the watcher API server'),
|
||||
help=_('The listen IP for the watcher API server')),
|
||||
cfg.IntOpt('max_limit',
|
||||
default=1000,
|
||||
help='The maximum number of items returned in a single '
|
||||
'response from a collection resource.')
|
||||
help=_('The maximum number of items returned in a single '
|
||||
'response from a collection resource')),
|
||||
cfg.IntOpt('workers',
|
||||
min=1,
|
||||
help=_('Number of workers for Watcher API service. '
|
||||
'The default is equal to the number of CPUs available '
|
||||
'if that can be determined, else a default worker '
|
||||
'count of 1 is returned.')),
|
||||
|
||||
cfg.BoolOpt('enable_ssl_api',
|
||||
default=False,
|
||||
help=_("Enable the integrated stand-alone API to service "
|
||||
"requests via HTTPS instead of HTTP. If there is a "
|
||||
"front-end service performing HTTPS offloading from "
|
||||
"the service, this option should be False; note, you "
|
||||
"will want to change public API endpoint to represent "
|
||||
"SSL termination URL with 'public_endpoint' option.")),
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
@@ -45,7 +59,6 @@ opt_group = cfg.OptGroup(name='api',
|
||||
|
||||
CONF.register_group(opt_group)
|
||||
CONF.register_opts(API_SERVICE_OPTS, opt_group)
|
||||
CONF.register_opts(strategy_selector.WATCHER_GOALS_OPTS)
|
||||
|
||||
|
||||
def get_pecan_config():
|
||||
@@ -68,3 +81,12 @@ def setup_app(config=None):
|
||||
)
|
||||
|
||||
return acl.install(app, CONF, config.app.acl_public_routes)
|
||||
|
||||
|
||||
class VersionSelectorApplication(object):
|
||||
def __init__(self):
|
||||
pc = get_pecan_config()
|
||||
self.v1 = setup_app(config=pc)
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
return self.v1(environ, start_response)
|
||||
|
||||
@@ -34,6 +34,7 @@ from watcher.api.controllers.v1 import action_plan
|
||||
from watcher.api.controllers.v1 import audit
|
||||
from watcher.api.controllers.v1 import audit_template
|
||||
from watcher.api.controllers.v1 import goal
|
||||
from watcher.api.controllers.v1 import strategy
|
||||
|
||||
|
||||
class APIBase(wtypes.Base):
|
||||
@@ -157,6 +158,7 @@ class Controller(rest.RestController):
|
||||
actions = action.ActionsController()
|
||||
action_plans = action_plan.ActionPlansController()
|
||||
goals = goal.GoalsController()
|
||||
strategies = strategy.StrategiesController()
|
||||
|
||||
@wsme_pecan.wsexpose(V1)
|
||||
def get(self):
|
||||
|
||||
@@ -15,6 +15,46 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""
|
||||
An :ref:`Action <action_definition>` is what enables Watcher to transform the
|
||||
current state of a :ref:`Cluster <cluster_definition>` after an
|
||||
:ref:`Audit <audit_definition>`.
|
||||
|
||||
An :ref:`Action <action_definition>` is an atomic task which changes the
|
||||
current state of a target :ref:`Managed resource <managed_resource_definition>`
|
||||
of the OpenStack :ref:`Cluster <cluster_definition>` such as:
|
||||
|
||||
- Live migration of an instance from one compute node to another compute
|
||||
node with Nova
|
||||
- Changing the power level of a compute node (ACPI level, ...)
|
||||
- Changing the current state of an hypervisor (enable or disable) with Nova
|
||||
|
||||
In most cases, an :ref:`Action <action_definition>` triggers some concrete
|
||||
commands on an existing OpenStack module (Nova, Neutron, Cinder, Ironic, etc.).
|
||||
|
||||
An :ref:`Action <action_definition>` has a life-cycle and its current state may
|
||||
be one of the following:
|
||||
|
||||
- **PENDING** : the :ref:`Action <action_definition>` has not been executed
|
||||
yet by the :ref:`Watcher Applier <watcher_applier_definition>`
|
||||
- **ONGOING** : the :ref:`Action <action_definition>` is currently being
|
||||
processed by the :ref:`Watcher Applier <watcher_applier_definition>`
|
||||
- **SUCCEEDED** : the :ref:`Action <action_definition>` has been executed
|
||||
successfully
|
||||
- **FAILED** : an error occured while trying to execute the
|
||||
:ref:`Action <action_definition>`
|
||||
- **DELETED** : the :ref:`Action <action_definition>` is still stored in the
|
||||
:ref:`Watcher database <watcher_database_definition>` but is not returned
|
||||
any more through the Watcher APIs.
|
||||
- **CANCELLED** : the :ref:`Action <action_definition>` was in **PENDING** or
|
||||
**ONGOING** state and was cancelled by the
|
||||
:ref:`Administrator <administrator_definition>`
|
||||
|
||||
:ref:`Some default implementations are provided <watcher_planners>`, but it is
|
||||
possible to :ref:`develop new implementations <implement_action_plugin>` which
|
||||
are dynamically loaded by Watcher at launch time.
|
||||
"""
|
||||
|
||||
import datetime
|
||||
|
||||
import pecan
|
||||
@@ -79,7 +119,7 @@ class Action(base.APIBase):
|
||||
self.action_next_uuid = None
|
||||
# raise e
|
||||
|
||||
uuid = types.uuid
|
||||
uuid = wtypes.wsattr(types.uuid, readonly=True)
|
||||
"""Unique UUID for this action"""
|
||||
|
||||
action_plan_uuid = wsme.wsproperty(types.uuid, _get_action_plan_uuid,
|
||||
@@ -87,29 +127,14 @@ 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,
|
||||
@@ -166,7 +191,6 @@ class Action(base.APIBase):
|
||||
sample = cls(uuid='27e3153e-d5bf-4b7e-b517-fb518e17f34c',
|
||||
description='action description',
|
||||
state='PENDING',
|
||||
alarm=None,
|
||||
created_at=datetime.datetime.utcnow(),
|
||||
deleted_at=None,
|
||||
updated_at=datetime.datetime.utcnow())
|
||||
@@ -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.
|
||||
@@ -338,6 +359,10 @@ class ActionsController(rest.RestController):
|
||||
|
||||
:param action: a action within the request body.
|
||||
"""
|
||||
# FIXME: blueprint edit-action-plan-flow
|
||||
raise exception.OperationNotPermitted(
|
||||
_("Cannot create an action directly"))
|
||||
|
||||
if self.from_actions:
|
||||
raise exception.OperationNotPermitted
|
||||
|
||||
@@ -358,6 +383,10 @@ class ActionsController(rest.RestController):
|
||||
:param action_uuid: UUID of a action.
|
||||
:param patch: a json PATCH document to apply to this action.
|
||||
"""
|
||||
# FIXME: blueprint edit-action-plan-flow
|
||||
raise exception.OperationNotPermitted(
|
||||
_("Cannot modify an action directly"))
|
||||
|
||||
if self.from_actions:
|
||||
raise exception.OperationNotPermitted
|
||||
|
||||
@@ -390,6 +419,9 @@ class ActionsController(rest.RestController):
|
||||
|
||||
:param action_uuid: UUID of a action.
|
||||
"""
|
||||
# FIXME: blueprint edit-action-plan-flow
|
||||
raise exception.OperationNotPermitted(
|
||||
_("Cannot delete an action directly"))
|
||||
|
||||
action_to_delete = objects.Action.get_by_uuid(
|
||||
pecan.request.context,
|
||||
|
||||
@@ -15,6 +15,45 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""
|
||||
An :ref:`Action Plan <action_plan_definition>` is a flow of
|
||||
:ref:`Actions <action_definition>` that should be executed in order to satisfy
|
||||
a given :ref:`Goal <goal_definition>`.
|
||||
|
||||
An :ref:`Action Plan <action_plan_definition>` is generated by Watcher when an
|
||||
:ref:`Audit <audit_definition>` is successful which implies that the
|
||||
:ref:`Strategy <strategy_definition>`
|
||||
which was used has found a :ref:`Solution <solution_definition>` to achieve the
|
||||
:ref:`Goal <goal_definition>` of this :ref:`Audit <audit_definition>`.
|
||||
|
||||
In the default implementation of Watcher, an
|
||||
:ref:`Action Plan <action_plan_definition>`
|
||||
is only composed of successive :ref:`Actions <action_definition>`
|
||||
(i.e., a Workflow of :ref:`Actions <action_definition>` belonging to a unique
|
||||
branch).
|
||||
|
||||
However, Watcher provides abstract interfaces for many of its components,
|
||||
allowing other implementations to generate and handle more complex
|
||||
:ref:`Action Plan(s) <action_plan_definition>`
|
||||
composed of two types of Action Item(s):
|
||||
|
||||
- simple :ref:`Actions <action_definition>`: atomic tasks, which means it
|
||||
can not be split into smaller tasks or commands from an OpenStack point of
|
||||
view.
|
||||
- composite Actions: which are composed of several simple
|
||||
:ref:`Actions <action_definition>`
|
||||
ordered in sequential and/or parallel flows.
|
||||
|
||||
An :ref:`Action Plan <action_plan_definition>` may be described using
|
||||
standard workflow model description formats such as
|
||||
`Business Process Model and Notation 2.0 (BPMN 2.0) <http://www.omg.org/spec/BPMN/2.0/>`_
|
||||
or `Unified Modeling Language (UML) <http://www.uml.org/>`_.
|
||||
|
||||
To see the life-cycle and description of
|
||||
:ref:`Action Plan <action_plan_definition>` states, visit :ref:`the Action Plan state
|
||||
machine <action_plan_state_machine>`.
|
||||
""" # noqa
|
||||
|
||||
import datetime
|
||||
|
||||
import pecan
|
||||
@@ -23,21 +62,45 @@ import wsme
|
||||
from wsme import types as wtypes
|
||||
import wsmeext.pecan as wsme_pecan
|
||||
|
||||
from watcher._i18n import _
|
||||
from watcher.api.controllers import base
|
||||
from watcher.api.controllers import link
|
||||
from watcher.api.controllers.v1 import collection
|
||||
from watcher.api.controllers.v1 import types
|
||||
from watcher.api.controllers.v1 import utils as api_utils
|
||||
from watcher.applier.rpcapi import ApplierAPI
|
||||
from watcher.applier import rpcapi
|
||||
from watcher.common import exception
|
||||
from watcher import objects
|
||||
from watcher.objects import action_plan as ap_objects
|
||||
|
||||
|
||||
class ActionPlanPatchType(types.JsonPatchType):
|
||||
|
||||
@staticmethod
|
||||
def _validate_state(patch):
|
||||
serialized_patch = {'path': patch.path, 'op': patch.op}
|
||||
if patch.value is not wsme.Unset:
|
||||
serialized_patch['value'] = patch.value
|
||||
# todo: use state machines to handle state transitions
|
||||
state_value = patch.value
|
||||
if state_value and not hasattr(ap_objects.State, state_value):
|
||||
msg = _("Invalid state: %(state)s")
|
||||
raise exception.PatchError(
|
||||
patch=serialized_patch, reason=msg % dict(state=state_value))
|
||||
|
||||
@staticmethod
|
||||
def validate(patch):
|
||||
if patch.path == "/state":
|
||||
ActionPlanPatchType._validate_state(patch)
|
||||
return types.JsonPatchType.validate(patch)
|
||||
|
||||
@staticmethod
|
||||
def internal_attrs():
|
||||
return types.JsonPatchType.internal_attrs()
|
||||
|
||||
@staticmethod
|
||||
def mandatory_attrs():
|
||||
return []
|
||||
return ["audit_id", "state", "first_action_id"]
|
||||
|
||||
|
||||
class ActionPlan(base.APIBase):
|
||||
@@ -80,7 +143,7 @@ class ActionPlan(base.APIBase):
|
||||
except exception.ActionNotFound:
|
||||
self._first_action_uuid = None
|
||||
|
||||
uuid = types.uuid
|
||||
uuid = wtypes.wsattr(types.uuid, readonly=True)
|
||||
"""Unique UUID for this action plan"""
|
||||
|
||||
first_action_uuid = wsme.wsproperty(
|
||||
@@ -103,7 +166,6 @@ class ActionPlan(base.APIBase):
|
||||
|
||||
self.fields = []
|
||||
fields = list(objects.ActionPlan.fields)
|
||||
fields.append('audit_uuid')
|
||||
for field in fields:
|
||||
# Skip fields we do not expose.
|
||||
if not hasattr(self, field):
|
||||
@@ -111,14 +173,19 @@ class ActionPlan(base.APIBase):
|
||||
self.fields.append(field)
|
||||
setattr(self, field, kwargs.get(field, wtypes.Unset))
|
||||
|
||||
self.fields.append('audit_id')
|
||||
self.fields.append('audit_uuid')
|
||||
self.fields.append('first_action_uuid')
|
||||
|
||||
setattr(self, 'audit_uuid', kwargs.get('audit_id', wtypes.Unset))
|
||||
setattr(self, 'first_action_uuid',
|
||||
kwargs.get('first_action_id', wtypes.Unset))
|
||||
|
||||
@staticmethod
|
||||
def _convert_with_links(action_plan, url, expand=True):
|
||||
if not expand:
|
||||
action_plan.unset_fields_except(['uuid', 'state', 'updated_at',
|
||||
'audit_uuid'])
|
||||
action_plan.unset_fields_except(
|
||||
['uuid', 'state', 'updated_at',
|
||||
'audit_uuid', 'first_action_uuid'])
|
||||
|
||||
action_plan.links = [link.Link.make_link(
|
||||
'self', url,
|
||||
@@ -201,7 +268,7 @@ class ActionPlansController(rest.RestController):
|
||||
resource_url=None, audit_uuid=None):
|
||||
|
||||
limit = api_utils.validate_limit(limit)
|
||||
sort_dir = api_utils.validate_sort_dir(sort_dir)
|
||||
api_utils.validate_sort_dir(sort_dir)
|
||||
|
||||
marker_obj = None
|
||||
if marker:
|
||||
@@ -230,9 +297,9 @@ class ActionPlansController(rest.RestController):
|
||||
sort_key=sort_key,
|
||||
sort_dir=sort_dir)
|
||||
|
||||
@wsme_pecan.wsexpose(ActionPlanCollection, types.uuid, types.uuid,
|
||||
int, wtypes.text, wtypes.text, types.uuid)
|
||||
def get_all(self, action_plan_uuid=None, marker=None, limit=None,
|
||||
@wsme_pecan.wsexpose(ActionPlanCollection, types.uuid, int, wtypes.text,
|
||||
wtypes.text, types.uuid)
|
||||
def get_all(self, marker=None, limit=None,
|
||||
sort_key='id', sort_dir='asc', audit_uuid=None):
|
||||
"""Retrieve a list of action plans.
|
||||
|
||||
@@ -241,25 +308,23 @@ class ActionPlansController(rest.RestController):
|
||||
:param sort_key: column to sort results by. Default: id.
|
||||
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
|
||||
:param audit_uuid: Optional UUID of an audit, to get only actions
|
||||
for that audit.
|
||||
for that audit.
|
||||
"""
|
||||
return self._get_action_plans_collection(
|
||||
marker, limit, sort_key, sort_dir, audit_uuid=audit_uuid)
|
||||
|
||||
@wsme_pecan.wsexpose(ActionPlanCollection, types.uuid, types.uuid,
|
||||
int, wtypes.text, wtypes.text, types.uuid)
|
||||
def detail(self, action_plan_uuid=None, marker=None, limit=None,
|
||||
@wsme_pecan.wsexpose(ActionPlanCollection, types.uuid, int, wtypes.text,
|
||||
wtypes.text, types.uuid)
|
||||
def detail(self, marker=None, limit=None,
|
||||
sort_key='id', sort_dir='asc', audit_uuid=None):
|
||||
"""Retrieve a list of action_plans with detail.
|
||||
|
||||
:param action_plan_uuid: UUID of a action plan, to get only
|
||||
:action_plans for that action.
|
||||
:param marker: pagination marker for large data sets.
|
||||
:param limit: maximum number of resources to return in a single result.
|
||||
:param sort_key: column to sort results by. Default: id.
|
||||
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
|
||||
:param audit_uuid: Optional UUID of an audit, to get only actions
|
||||
for that audit.
|
||||
for that audit.
|
||||
"""
|
||||
# NOTE(lucasagomes): /detail should only work agaist collections
|
||||
parent = pecan.request.path.split('/')[:-1][-1]
|
||||
@@ -302,10 +367,10 @@ class ActionPlansController(rest.RestController):
|
||||
@wsme_pecan.wsexpose(ActionPlan, types.uuid,
|
||||
body=[ActionPlanPatchType])
|
||||
def patch(self, action_plan_uuid, patch):
|
||||
"""Update an existing audit template.
|
||||
"""Update an existing action plan.
|
||||
|
||||
:param audit template_uuid: UUID of a audit template.
|
||||
:param patch: a json PATCH document to apply to this audit template.
|
||||
:param action_plan_uuid: UUID of a action plan.
|
||||
:param patch: a json PATCH document to apply to this action plan.
|
||||
"""
|
||||
launch_action_plan = True
|
||||
if self.from_actionsPlans:
|
||||
@@ -322,6 +387,34 @@ class ActionPlansController(rest.RestController):
|
||||
raise exception.PatchError(patch=patch, reason=e)
|
||||
|
||||
launch_action_plan = False
|
||||
|
||||
# transitions that are allowed via PATCH
|
||||
allowed_patch_transitions = [
|
||||
(ap_objects.State.RECOMMENDED,
|
||||
ap_objects.State.PENDING),
|
||||
(ap_objects.State.RECOMMENDED,
|
||||
ap_objects.State.CANCELLED),
|
||||
(ap_objects.State.ONGOING,
|
||||
ap_objects.State.CANCELLED),
|
||||
(ap_objects.State.PENDING,
|
||||
ap_objects.State.CANCELLED),
|
||||
]
|
||||
|
||||
# todo: improve this in blueprint watcher-api-validation
|
||||
if hasattr(action_plan, 'state'):
|
||||
transition = (action_plan_to_update.state, action_plan.state)
|
||||
if transition not in allowed_patch_transitions:
|
||||
error_message = _("State transition not allowed: "
|
||||
"(%(initial_state)s -> %(new_state)s)")
|
||||
raise exception.PatchError(
|
||||
patch=patch,
|
||||
reason=error_message % dict(
|
||||
initial_state=action_plan_to_update.state,
|
||||
new_state=action_plan.state))
|
||||
|
||||
if action_plan.state == ap_objects.State.PENDING:
|
||||
launch_action_plan = True
|
||||
|
||||
# Update only the fields that have changed
|
||||
for field in objects.ActionPlan.fields:
|
||||
try:
|
||||
@@ -334,13 +427,14 @@ class ActionPlansController(rest.RestController):
|
||||
if action_plan_to_update[field] != patch_val:
|
||||
action_plan_to_update[field] = patch_val
|
||||
|
||||
if field == 'state' and patch_val == 'STARTING':
|
||||
if (field == 'state'
|
||||
and patch_val == objects.action_plan.State.PENDING):
|
||||
launch_action_plan = True
|
||||
|
||||
action_plan_to_update.save()
|
||||
|
||||
if launch_action_plan:
|
||||
applier_client = ApplierAPI()
|
||||
applier_client = rpcapi.ApplierAPI()
|
||||
applier_client.launch_action_plan(pecan.request.context,
|
||||
action_plan.uuid)
|
||||
|
||||
|
||||
@@ -15,6 +15,20 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""
|
||||
In the Watcher system, an :ref:`Audit <audit_definition>` is a request for
|
||||
optimizing a :ref:`Cluster <cluster_definition>`.
|
||||
|
||||
The optimization is done in order to satisfy one :ref:`Goal <goal_definition>`
|
||||
on a given :ref:`Cluster <cluster_definition>`.
|
||||
|
||||
For each :ref:`Audit <audit_definition>`, the Watcher system generates an
|
||||
:ref:`Action Plan <action_plan_definition>`.
|
||||
|
||||
To see the life-cycle and description of an :ref:`Audit <audit_definition>`
|
||||
states, visit :ref:`the Audit State machine <audit_state_machine>`.
|
||||
"""
|
||||
|
||||
import datetime
|
||||
|
||||
import pecan
|
||||
@@ -23,6 +37,7 @@ import wsme
|
||||
from wsme import types as wtypes
|
||||
import wsmeext.pecan as wsme_pecan
|
||||
|
||||
from watcher._i18n import _
|
||||
from watcher.api.controllers import base
|
||||
from watcher.api.controllers import link
|
||||
from watcher.api.controllers.v1 import collection
|
||||
@@ -30,10 +45,32 @@ 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
|
||||
|
||||
|
||||
class AuditPostType(wtypes.Base):
|
||||
|
||||
audit_template_uuid = wtypes.wsattr(types.uuid, mandatory=True)
|
||||
|
||||
type = wtypes.wsattr(wtypes.text, mandatory=True)
|
||||
|
||||
deadline = wtypes.wsattr(datetime.datetime, mandatory=False)
|
||||
|
||||
state = wsme.wsattr(wtypes.text, readonly=True,
|
||||
default=objects.audit.State.PENDING)
|
||||
|
||||
def as_audit(self):
|
||||
audit_type_values = [val.value for val in objects.audit.AuditType]
|
||||
if self.type not in audit_type_values:
|
||||
raise exception.AuditTypeNotFound(audit_type=self.type)
|
||||
|
||||
return Audit(
|
||||
audit_template_id=self.audit_template_uuid,
|
||||
type=self.type,
|
||||
deadline=self.deadline)
|
||||
|
||||
|
||||
class AuditPatchType(types.JsonPatchType):
|
||||
|
||||
@staticmethod
|
||||
@@ -181,6 +218,7 @@ class AuditCollection(collection.Collection):
|
||||
"""A list containing audits objects"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(AuditCollection, self).__init__()
|
||||
self._type = 'audits'
|
||||
|
||||
@staticmethod
|
||||
@@ -227,7 +265,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 +295,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 +312,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.
|
||||
@@ -311,15 +347,21 @@ class AuditsController(rest.RestController):
|
||||
audit_uuid)
|
||||
return Audit.convert_with_links(rpc_audit)
|
||||
|
||||
@wsme_pecan.wsexpose(Audit, body=Audit, status_code=201)
|
||||
def post(self, audit):
|
||||
@wsme_pecan.wsexpose(Audit, body=AuditPostType, status_code=201)
|
||||
def post(self, audit_p):
|
||||
"""Create a new audit.
|
||||
|
||||
:param audit: a audit within the request body.
|
||||
:param audit_p: a audit within the request body.
|
||||
"""
|
||||
audit = audit_p.as_audit()
|
||||
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 +372,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
|
||||
@@ -23,22 +56,157 @@ 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.common import context as context_utils
|
||||
from watcher.common import exception
|
||||
from watcher.common import utils as common_utils
|
||||
from watcher import objects
|
||||
|
||||
|
||||
class AuditTemplatePostType(wtypes.Base):
|
||||
_ctx = context_utils.make_context()
|
||||
|
||||
name = wtypes.wsattr(wtypes.text, mandatory=True)
|
||||
"""Name of this audit template"""
|
||||
|
||||
description = wtypes.wsattr(wtypes.text, mandatory=False)
|
||||
"""Short description of this audit template"""
|
||||
|
||||
deadline = wsme.wsattr(datetime.datetime, mandatory=False)
|
||||
"""deadline of the audit template"""
|
||||
|
||||
host_aggregate = wsme.wsattr(wtypes.IntegerType(minimum=1),
|
||||
mandatory=False)
|
||||
"""ID of the Nova host aggregate targeted by the audit template"""
|
||||
|
||||
extra = wtypes.wsattr({wtypes.text: types.jsontype}, mandatory=False)
|
||||
"""The metadata of the audit template"""
|
||||
|
||||
goal = wtypes.wsattr(wtypes.text, mandatory=True)
|
||||
"""Goal UUID or name of the audit template"""
|
||||
|
||||
strategy = wtypes.wsattr(wtypes.text, mandatory=False)
|
||||
"""Strategy UUID or name of the audit template"""
|
||||
|
||||
version = wtypes.text
|
||||
"""Internal version of the audit template"""
|
||||
|
||||
def as_audit_template(self):
|
||||
return AuditTemplate(
|
||||
name=self.name,
|
||||
description=self.description,
|
||||
deadline=self.deadline,
|
||||
host_aggregate=self.host_aggregate,
|
||||
extra=self.extra,
|
||||
goal_id=self.goal, # Dirty trick ...
|
||||
goal=self.goal,
|
||||
strategy_id=self.strategy, # Dirty trick ...
|
||||
strategy_uuid=self.strategy,
|
||||
version=self.version,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def validate(audit_template):
|
||||
available_goals = objects.Goal.list(AuditTemplatePostType._ctx)
|
||||
available_goal_uuids_map = {g.uuid: g for g in available_goals}
|
||||
available_goal_names_map = {g.name: g for g in available_goals}
|
||||
if audit_template.goal in available_goal_uuids_map:
|
||||
goal = available_goal_uuids_map[audit_template.goal]
|
||||
elif audit_template.goal in available_goal_names_map:
|
||||
goal = available_goal_names_map[audit_template.goal]
|
||||
else:
|
||||
raise exception.InvalidGoal(goal=audit_template.goal)
|
||||
|
||||
if audit_template.strategy:
|
||||
available_strategies = objects.Strategy.list(
|
||||
AuditTemplatePostType._ctx)
|
||||
available_strategies_map = {
|
||||
s.uuid: s for s in available_strategies}
|
||||
if audit_template.strategy not in available_strategies_map:
|
||||
raise exception.InvalidStrategy(
|
||||
strategy=audit_template.strategy)
|
||||
|
||||
strategy = available_strategies_map[audit_template.strategy]
|
||||
# Check that the strategy we indicate is actually related to the
|
||||
# specified goal
|
||||
if strategy.goal_id != goal.id:
|
||||
choices = ["'%s' (%s)" % (s.uuid, s.name)
|
||||
for s in available_strategies]
|
||||
raise exception.InvalidStrategy(
|
||||
message=_(
|
||||
"'%(strategy)s' strategy does relate to the "
|
||||
"'%(goal)s' goal. Possible choices: %(choices)s")
|
||||
% dict(strategy=strategy.name, goal=goal.name,
|
||||
choices=", ".join(choices)))
|
||||
audit_template.strategy = strategy.uuid
|
||||
|
||||
# We force the UUID so that we do not need to query the DB with the
|
||||
# name afterwards
|
||||
audit_template.goal = goal.uuid
|
||||
|
||||
return audit_template
|
||||
|
||||
|
||||
class AuditTemplatePatchType(types.JsonPatchType):
|
||||
|
||||
_ctx = context_utils.make_context()
|
||||
|
||||
@staticmethod
|
||||
def mandatory_attrs():
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
def validate(patch):
|
||||
if patch.path == "/goal" and patch.op != "remove":
|
||||
AuditTemplatePatchType._validate_goal(patch)
|
||||
elif patch.path == "/goal" and patch.op == "remove":
|
||||
raise exception.OperationNotPermitted(
|
||||
_("Cannot remove 'goal' attribute "
|
||||
"from an audit template"))
|
||||
if patch.path == "/strategy":
|
||||
AuditTemplatePatchType._validate_strategy(patch)
|
||||
return types.JsonPatchType.validate(patch)
|
||||
|
||||
@staticmethod
|
||||
def _validate_goal(patch):
|
||||
patch.path = "/goal_id"
|
||||
goal = patch.value
|
||||
|
||||
if goal:
|
||||
available_goals = objects.Goal.list(
|
||||
AuditTemplatePatchType._ctx)
|
||||
available_goal_uuids_map = {g.uuid: g for g in available_goals}
|
||||
available_goal_names_map = {g.name: g for g in available_goals}
|
||||
if goal in available_goal_uuids_map:
|
||||
patch.value = available_goal_uuids_map[goal].id
|
||||
elif goal in available_goal_names_map:
|
||||
patch.value = available_goal_names_map[goal].id
|
||||
else:
|
||||
raise exception.InvalidGoal(goal=goal)
|
||||
|
||||
@staticmethod
|
||||
def _validate_strategy(patch):
|
||||
patch.path = "/strategy_id"
|
||||
strategy = patch.value
|
||||
if strategy:
|
||||
available_strategies = objects.Strategy.list(
|
||||
AuditTemplatePatchType._ctx)
|
||||
available_strategy_uuids_map = {
|
||||
s.uuid: s for s in available_strategies}
|
||||
available_strategy_names_map = {
|
||||
s.name: s for s in available_strategies}
|
||||
if strategy in available_strategy_uuids_map:
|
||||
patch.value = available_strategy_uuids_map[strategy].id
|
||||
elif strategy in available_strategy_names_map:
|
||||
patch.value = available_strategy_names_map[strategy].id
|
||||
else:
|
||||
raise exception.InvalidStrategy(strategy=strategy)
|
||||
|
||||
|
||||
class AuditTemplate(base.APIBase):
|
||||
"""API representation of a audit template.
|
||||
@@ -47,7 +215,90 @@ class AuditTemplate(base.APIBase):
|
||||
between the internal object model and the API representation of an
|
||||
audit template.
|
||||
"""
|
||||
uuid = types.uuid
|
||||
|
||||
_goal_uuid = None
|
||||
_goal_name = None
|
||||
|
||||
_strategy_uuid = None
|
||||
_strategy_name = None
|
||||
|
||||
def _get_goal(self, value):
|
||||
if value == wtypes.Unset:
|
||||
return None
|
||||
goal = None
|
||||
try:
|
||||
if (common_utils.is_uuid_like(value) or
|
||||
common_utils.is_int_like(value)):
|
||||
goal = objects.Goal.get(
|
||||
pecan.request.context, value)
|
||||
else:
|
||||
goal = objects.Goal.get_by_name(
|
||||
pecan.request.context, value)
|
||||
except exception.GoalNotFound:
|
||||
pass
|
||||
if goal:
|
||||
self.goal_id = goal.id
|
||||
return goal
|
||||
|
||||
def _get_strategy(self, value):
|
||||
if value == wtypes.Unset:
|
||||
return None
|
||||
strategy = None
|
||||
try:
|
||||
if (common_utils.is_uuid_like(value) or
|
||||
common_utils.is_int_like(value)):
|
||||
strategy = objects.Strategy.get(
|
||||
pecan.request.context, value)
|
||||
else:
|
||||
strategy = objects.Strategy.get_by_name(
|
||||
pecan.request.context, value)
|
||||
except exception.StrategyNotFound:
|
||||
pass
|
||||
if strategy:
|
||||
self.strategy_id = strategy.id
|
||||
return strategy
|
||||
|
||||
def _get_goal_uuid(self):
|
||||
return self._goal_uuid
|
||||
|
||||
def _set_goal_uuid(self, value):
|
||||
if value and self._goal_uuid != value:
|
||||
self._goal_uuid = None
|
||||
goal = self._get_goal(value)
|
||||
if goal:
|
||||
self._goal_uuid = goal.uuid
|
||||
|
||||
def _get_strategy_uuid(self):
|
||||
return self._strategy_uuid
|
||||
|
||||
def _set_strategy_uuid(self, value):
|
||||
if value and self._strategy_uuid != value:
|
||||
self._strategy_uuid = None
|
||||
strategy = self._get_strategy(value)
|
||||
if strategy:
|
||||
self._strategy_uuid = strategy.uuid
|
||||
|
||||
def _get_goal_name(self):
|
||||
return self._goal_name
|
||||
|
||||
def _set_goal_name(self, value):
|
||||
if value and self._goal_name != value:
|
||||
self._goal_name = None
|
||||
goal = self._get_goal(value)
|
||||
if goal:
|
||||
self._goal_name = goal.name
|
||||
|
||||
def _get_strategy_name(self):
|
||||
return self._strategy_name
|
||||
|
||||
def _set_strategy_name(self, value):
|
||||
if value and self._strategy_name != value:
|
||||
self._strategy_name = None
|
||||
strategy = self._get_strategy(value)
|
||||
if strategy:
|
||||
self._strategy_name = strategy.name
|
||||
|
||||
uuid = wtypes.wsattr(types.uuid, readonly=True)
|
||||
"""Unique UUID for this audit template"""
|
||||
|
||||
name = wtypes.text
|
||||
@@ -65,8 +316,21 @@ class AuditTemplate(base.APIBase):
|
||||
extra = {wtypes.text: types.jsontype}
|
||||
"""The metadata of the audit template"""
|
||||
|
||||
goal = wtypes.text
|
||||
"""Goal type of the audit template"""
|
||||
goal_uuid = wsme.wsproperty(
|
||||
wtypes.text, _get_goal_uuid, _set_goal_uuid, mandatory=True)
|
||||
"""Goal UUID the audit template refers to"""
|
||||
|
||||
goal_name = wsme.wsproperty(
|
||||
wtypes.text, _get_goal_name, _set_goal_name, mandatory=False)
|
||||
"""The name of the goal this audit template refers to"""
|
||||
|
||||
strategy_uuid = wsme.wsproperty(
|
||||
wtypes.text, _get_strategy_uuid, _set_strategy_uuid, mandatory=False)
|
||||
"""Strategy UUID the audit template refers to"""
|
||||
|
||||
strategy_name = wsme.wsproperty(
|
||||
wtypes.text, _get_strategy_name, _set_strategy_name, mandatory=False)
|
||||
"""The name of the strategy this audit template refers to"""
|
||||
|
||||
version = wtypes.text
|
||||
"""Internal version of the audit template"""
|
||||
@@ -79,20 +343,43 @@ class AuditTemplate(base.APIBase):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(AuditTemplate, self).__init__()
|
||||
|
||||
self.fields = []
|
||||
for field in objects.AuditTemplate.fields:
|
||||
fields = list(objects.AuditTemplate.fields)
|
||||
|
||||
for k in fields:
|
||||
# Skip fields we do not expose.
|
||||
if not hasattr(self, field):
|
||||
if not hasattr(self, k):
|
||||
continue
|
||||
self.fields.append(field)
|
||||
setattr(self, field, kwargs.get(field, wtypes.Unset))
|
||||
self.fields.append(k)
|
||||
setattr(self, k, kwargs.get(k, wtypes.Unset))
|
||||
|
||||
self.fields.append('goal_id')
|
||||
self.fields.append('strategy_id')
|
||||
|
||||
# goal_uuid & strategy_uuid are not part of
|
||||
# objects.AuditTemplate.fields because they're API-only attributes.
|
||||
self.fields.append('goal_uuid')
|
||||
self.fields.append('goal_name')
|
||||
self.fields.append('strategy_uuid')
|
||||
self.fields.append('strategy_name')
|
||||
setattr(self, 'goal_uuid', kwargs.get('goal_id', wtypes.Unset))
|
||||
setattr(self, 'goal_name', kwargs.get('goal_id', wtypes.Unset))
|
||||
setattr(self, 'strategy_uuid',
|
||||
kwargs.get('strategy_id', wtypes.Unset))
|
||||
setattr(self, 'strategy_name',
|
||||
kwargs.get('strategy_id', wtypes.Unset))
|
||||
|
||||
@staticmethod
|
||||
def _convert_with_links(audit_template, url, expand=True):
|
||||
if not expand:
|
||||
audit_template.unset_fields_except(['uuid', 'name',
|
||||
'host_aggregate', 'goal'])
|
||||
audit_template.unset_fields_except(
|
||||
['uuid', 'name', 'host_aggregate', 'goal_uuid', 'goal_name',
|
||||
'strategy_uuid', 'strategy_name'])
|
||||
|
||||
# The numeric ID should not be exposed to
|
||||
# the user, it's internal only.
|
||||
audit_template.goal_id = wtypes.Unset
|
||||
audit_template.strategy_id = wtypes.Unset
|
||||
|
||||
audit_template.links = [link.Link.make_link('self', url,
|
||||
'audit_templates',
|
||||
@@ -100,8 +387,7 @@ class AuditTemplate(base.APIBase):
|
||||
link.Link.make_link('bookmark', url,
|
||||
'audit_templates',
|
||||
audit_template.uuid,
|
||||
bookmark=True)
|
||||
]
|
||||
bookmark=True)]
|
||||
return audit_template
|
||||
|
||||
@classmethod
|
||||
@@ -116,7 +402,8 @@ class AuditTemplate(base.APIBase):
|
||||
name='My Audit Template',
|
||||
description='Description of my audit template',
|
||||
host_aggregate=5,
|
||||
goal='SERVERS_CONSOLIDATION',
|
||||
goal_uuid='83e44733-b640-40e2-8d8a-7dd3be7134e6',
|
||||
strategy_uuid='367d826e-b6a4-4b70-bc44-c3f6fe1c9986',
|
||||
extra={'automatic': True},
|
||||
created_at=datetime.datetime.utcnow(),
|
||||
deleted_at=None,
|
||||
@@ -131,17 +418,18 @@ class AuditTemplateCollection(collection.Collection):
|
||||
"""A list containing audit templates objects"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(AuditTemplateCollection, self).__init__()
|
||||
self._type = 'audit_templates'
|
||||
|
||||
@staticmethod
|
||||
def convert_with_links(rpc_audit_templates, limit, url=None, expand=False,
|
||||
**kwargs):
|
||||
collection = AuditTemplateCollection()
|
||||
collection.audit_templates = \
|
||||
[AuditTemplate.convert_with_links(p, expand)
|
||||
for p in rpc_audit_templates]
|
||||
collection.next = collection.get_next(limit, url=url, **kwargs)
|
||||
return collection
|
||||
at_collection = AuditTemplateCollection()
|
||||
at_collection.audit_templates = [
|
||||
AuditTemplate.convert_with_links(p, expand)
|
||||
for p in rpc_audit_templates]
|
||||
at_collection.next = at_collection.get_next(limit, url=url, **kwargs)
|
||||
return at_collection
|
||||
|
||||
@classmethod
|
||||
def sample(cls):
|
||||
@@ -163,12 +451,14 @@ 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, list(objects.audit_template.AuditTemplate.fields.keys()) +
|
||||
["goal_uuid", "goal_name", "strategy_uuid", "strategy_name"])
|
||||
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 +468,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 +480,43 @@ 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,
|
||||
sort_key='id', sort_dir='asc'):
|
||||
@wsme_pecan.wsexpose(AuditTemplateCollection, wtypes.text, wtypes.text,
|
||||
types.uuid, int, wtypes.text, wtypes.text)
|
||||
def get_all(self, goal=None, strategy=None, marker=None,
|
||||
limit=None, sort_key='id', sort_dir='asc'):
|
||||
"""Retrieve a list of audit templates.
|
||||
|
||||
:param goal: goal UUID or name to filter by
|
||||
:param strategy: strategy UUID or name to filter by
|
||||
: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 = {}
|
||||
if goal:
|
||||
if common_utils.is_uuid_like(goal):
|
||||
filters['goal_uuid'] = goal
|
||||
else:
|
||||
filters['goal_name'] = goal
|
||||
|
||||
@wsme_pecan.wsexpose(AuditTemplateCollection, types.uuid, int,
|
||||
wtypes.text, wtypes.text)
|
||||
def detail(self, marker=None, limit=None,
|
||||
sort_key='id', sort_dir='asc'):
|
||||
if strategy:
|
||||
if common_utils.is_uuid_like(strategy):
|
||||
filters['strategy_uuid'] = strategy
|
||||
else:
|
||||
filters['strategy_name'] = strategy
|
||||
|
||||
return self._get_audit_templates_collection(
|
||||
filters, marker, limit, sort_key, sort_dir)
|
||||
|
||||
@wsme_pecan.wsexpose(AuditTemplateCollection, wtypes.text, wtypes.text,
|
||||
types.uuid, int, wtypes.text, wtypes.text)
|
||||
def detail(self, goal=None, strategy=None, marker=None,
|
||||
limit=None, sort_key='id', sort_dir='asc'):
|
||||
"""Retrieve a list of audit templates with detail.
|
||||
|
||||
:param goal: goal UUID or name to filter by
|
||||
:param strategy: strategy UUID or name to filter by
|
||||
: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 +527,22 @@ class AuditTemplatesController(rest.RestController):
|
||||
if parent != "audit_templates":
|
||||
raise exception.HTTPNotFound
|
||||
|
||||
filters = {}
|
||||
if goal:
|
||||
if common_utils.is_uuid_like(goal):
|
||||
filters['goal_uuid'] = goal
|
||||
else:
|
||||
filters['goal_name'] = goal
|
||||
|
||||
if strategy:
|
||||
if common_utils.is_uuid_like(strategy):
|
||||
filters['strategy_uuid'] = strategy
|
||||
else:
|
||||
filters['strategy_name'] = strategy
|
||||
|
||||
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 +550,7 @@ class AuditTemplatesController(rest.RestController):
|
||||
def get_one(self, audit_template):
|
||||
"""Retrieve information about the given audit template.
|
||||
|
||||
:param audit template_uuid: UUID or name of an audit template.
|
||||
:param audit audit_template: UUID or name of an audit template.
|
||||
"""
|
||||
if self.from_audit_templates:
|
||||
raise exception.OperationNotPermitted
|
||||
@@ -245,17 +566,21 @@ class AuditTemplatesController(rest.RestController):
|
||||
|
||||
return AuditTemplate.convert_with_links(rpc_audit_template)
|
||||
|
||||
@wsme_pecan.wsexpose(AuditTemplate, body=AuditTemplate, status_code=201)
|
||||
def post(self, audit_template):
|
||||
@wsme.validate(types.uuid, AuditTemplatePostType)
|
||||
@wsme_pecan.wsexpose(AuditTemplate, body=AuditTemplatePostType,
|
||||
status_code=201)
|
||||
def post(self, audit_template_postdata):
|
||||
"""Create a new audit template.
|
||||
|
||||
:param audit template: a audit template within the request body.
|
||||
:param audit_template_postdata: the audit template POST data
|
||||
from the request body.
|
||||
"""
|
||||
if self.from_audit_templates:
|
||||
raise exception.OperationNotPermitted
|
||||
|
||||
audit_template_dict = audit_template.as_dict()
|
||||
context = pecan.request.context
|
||||
audit_template = audit_template_postdata.as_audit_template()
|
||||
audit_template_dict = audit_template.as_dict()
|
||||
new_audit_template = objects.AuditTemplate(context,
|
||||
**audit_template_dict)
|
||||
new_audit_template.create(context)
|
||||
|
||||
@@ -44,7 +44,7 @@ class Collection(base.APIBase):
|
||||
q_args = ''.join(['%s=%s&' % (key, kwargs[key]) for key in kwargs])
|
||||
next_args = '?%(args)slimit=%(limit)d&marker=%(marker)s' % {
|
||||
'args': q_args, 'limit': limit,
|
||||
'marker': self.collection[-1].uuid}
|
||||
'marker': getattr(self.collection[-1], "uuid")}
|
||||
|
||||
return link.Link.make_link('next', pecan.request.host_url,
|
||||
resource_url, next_args).href
|
||||
|
||||
@@ -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
|
||||
@@ -29,61 +46,64 @@ 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.common import exception
|
||||
from watcher.common import utils as common_utils
|
||||
from watcher import objects
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class Goal(base.APIBase):
|
||||
"""API representation of a action.
|
||||
"""API representation of a goal.
|
||||
|
||||
This class enforces type checking and value constraints, and converts
|
||||
between the internal object model and the API representation of a action.
|
||||
between the internal object model and the API representation of a goal.
|
||||
"""
|
||||
|
||||
uuid = types.uuid
|
||||
"""Unique UUID for this goal"""
|
||||
|
||||
name = wtypes.text
|
||||
"""Name of the goal"""
|
||||
|
||||
strategy = wtypes.text
|
||||
"""The strategy associated with the goal"""
|
||||
|
||||
uuid = types.uuid
|
||||
"""Unused field"""
|
||||
display_name = wtypes.text
|
||||
"""Localized name of the goal"""
|
||||
|
||||
links = wsme.wsattr([link.Link], readonly=True)
|
||||
"""A list containing a self link and associated action links"""
|
||||
"""A list containing a self link and associated audit template links"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(Goal, self).__init__()
|
||||
|
||||
self.fields = []
|
||||
self.fields.append('uuid')
|
||||
self.fields.append('name')
|
||||
self.fields.append('strategy')
|
||||
setattr(self, 'name', kwargs.get('name',
|
||||
wtypes.Unset))
|
||||
setattr(self, 'strategy', kwargs.get('strategy',
|
||||
wtypes.Unset))
|
||||
self.fields.append('display_name')
|
||||
setattr(self, 'uuid', kwargs.get('uuid', wtypes.Unset))
|
||||
setattr(self, 'name', kwargs.get('name', wtypes.Unset))
|
||||
setattr(self, 'display_name', kwargs.get('display_name', wtypes.Unset))
|
||||
|
||||
@staticmethod
|
||||
def _convert_with_links(goal, url, expand=True):
|
||||
if not expand:
|
||||
goal.unset_fields_except(['name', 'strategy'])
|
||||
goal.unset_fields_except(['uuid', 'name', 'display_name'])
|
||||
|
||||
goal.links = [link.Link.make_link('self', url,
|
||||
'goals', goal.name),
|
||||
'goals', goal.uuid),
|
||||
link.Link.make_link('bookmark', url,
|
||||
'goals', goal.name,
|
||||
'goals', goal.uuid,
|
||||
bookmark=True)]
|
||||
return goal
|
||||
|
||||
@classmethod
|
||||
def convert_with_links(cls, goal, expand=True):
|
||||
goal = Goal(**goal)
|
||||
goal = Goal(**goal.as_dict())
|
||||
return cls._convert_with_links(goal, pecan.request.host_url, expand)
|
||||
|
||||
@classmethod
|
||||
def sample(cls, expand=True):
|
||||
sample = cls(name='27e3153e-d5bf-4b7e-b517-fb518e17f34c',
|
||||
strategy='action description')
|
||||
sample = cls(uuid='27e3153e-d5bf-4b7e-b517-fb518e17f34c',
|
||||
name='DUMMY',
|
||||
display_name='Dummy strategy')
|
||||
return cls._convert_with_links(sample, 'http://localhost:9322', expand)
|
||||
|
||||
|
||||
@@ -94,32 +114,34 @@ class GoalCollection(collection.Collection):
|
||||
"""A list containing goals objects"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(GoalCollection, self).__init__()
|
||||
self._type = 'goals'
|
||||
|
||||
@staticmethod
|
||||
def convert_with_links(goals, limit, url=None, expand=False,
|
||||
**kwargs):
|
||||
|
||||
collection = GoalCollection()
|
||||
collection.goals = [Goal.convert_with_links(g, expand) for g in goals]
|
||||
goal_collection = GoalCollection()
|
||||
goal_collection.goals = [
|
||||
Goal.convert_with_links(g, expand) for g in goals]
|
||||
|
||||
if 'sort_key' in kwargs:
|
||||
reverse = False
|
||||
if kwargs['sort_key'] == 'strategy':
|
||||
if 'sort_dir' in kwargs:
|
||||
reverse = True if kwargs['sort_dir'] == 'desc' else False
|
||||
collection.goals = sorted(
|
||||
collection.goals,
|
||||
key=lambda goal: goal.name,
|
||||
goal_collection.goals = sorted(
|
||||
goal_collection.goals,
|
||||
key=lambda goal: goal.uuid,
|
||||
reverse=reverse)
|
||||
|
||||
collection.next = collection.get_next(limit, url=url, **kwargs)
|
||||
return collection
|
||||
goal_collection.next = goal_collection.get_next(
|
||||
limit, url=url, **kwargs)
|
||||
return goal_collection
|
||||
|
||||
@classmethod
|
||||
def sample(cls):
|
||||
sample = cls()
|
||||
sample.actions = [Goal.sample(expand=False)]
|
||||
sample.goals = [Goal.sample(expand=False)]
|
||||
return sample
|
||||
|
||||
|
||||
@@ -136,51 +158,49 @@ class GoalsController(rest.RestController):
|
||||
'detail': ['GET'],
|
||||
}
|
||||
|
||||
def _get_goals_collection(self, limit,
|
||||
sort_key, sort_dir, expand=False,
|
||||
resource_url=None, goal_name=None):
|
||||
|
||||
def _get_goals_collection(self, marker, limit, sort_key, sort_dir,
|
||||
expand=False, resource_url=None):
|
||||
limit = api_utils.validate_limit(limit)
|
||||
sort_dir = api_utils.validate_sort_dir(sort_dir)
|
||||
api_utils.validate_sort_dir(sort_dir)
|
||||
|
||||
goals = []
|
||||
sort_db_key = (sort_key if sort_key in objects.Goal.fields.keys()
|
||||
else None)
|
||||
|
||||
if not goal_name and goal_name in CONF.watcher_goals.goals.keys():
|
||||
goals.append({'name': goal_name, 'strategy': goals[goal_name]})
|
||||
else:
|
||||
for name, strategy in CONF.watcher_goals.goals.items():
|
||||
goals.append({'name': name, 'strategy': strategy})
|
||||
marker_obj = None
|
||||
if marker:
|
||||
marker_obj = objects.Goal.get_by_uuid(
|
||||
pecan.request.context, marker)
|
||||
|
||||
return GoalCollection.convert_with_links(goals[:limit], limit,
|
||||
goals = objects.Goal.list(pecan.request.context, limit, marker_obj,
|
||||
sort_key=sort_db_key, sort_dir=sort_dir)
|
||||
|
||||
return GoalCollection.convert_with_links(goals, limit,
|
||||
url=resource_url,
|
||||
expand=expand,
|
||||
sort_key=sort_key,
|
||||
sort_dir=sort_dir)
|
||||
|
||||
@wsme_pecan.wsexpose(GoalCollection, int, wtypes.text, wtypes.text)
|
||||
def get_all(self, limit=None,
|
||||
sort_key='name', sort_dir='asc'):
|
||||
@wsme_pecan.wsexpose(GoalCollection, wtypes.text,
|
||||
int, wtypes.text, wtypes.text)
|
||||
def get_all(self, marker=None, limit=None, sort_key='id', sort_dir='asc'):
|
||||
"""Retrieve a list of goals.
|
||||
|
||||
: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.
|
||||
to get only actions for that goal.
|
||||
"""
|
||||
return self._get_goals_collection(limit, sort_key, sort_dir)
|
||||
return self._get_goals_collection(marker, limit, sort_key, sort_dir)
|
||||
|
||||
@wsme_pecan.wsexpose(GoalCollection, wtypes.text, int,
|
||||
wtypes.text, wtypes.text)
|
||||
def detail(self, goal_name=None, limit=None,
|
||||
sort_key='name', sort_dir='asc'):
|
||||
"""Retrieve a list of actions with detail.
|
||||
def detail(self, marker=None, limit=None, sort_key='id', sort_dir='asc'):
|
||||
"""Retrieve a list of goals with detail.
|
||||
|
||||
:param goal_name: name of a goal, to get only goals 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.
|
||||
to get only goals for that goal.
|
||||
"""
|
||||
# NOTE(lucasagomes): /detail should only work agaist collections
|
||||
parent = pecan.request.path.split('/')[:-1][-1]
|
||||
@@ -188,21 +208,23 @@ class GoalsController(rest.RestController):
|
||||
raise exception.HTTPNotFound
|
||||
expand = True
|
||||
resource_url = '/'.join(['goals', 'detail'])
|
||||
return self._get_goals_collection(limit, sort_key, sort_dir,
|
||||
expand, resource_url, goal_name)
|
||||
return self._get_goals_collection(marker, limit, sort_key, sort_dir,
|
||||
expand, resource_url)
|
||||
|
||||
@wsme_pecan.wsexpose(Goal, wtypes.text)
|
||||
def get_one(self, goal_name):
|
||||
def get_one(self, goal):
|
||||
"""Retrieve information about the given goal.
|
||||
|
||||
:param goal_name: name of the goal.
|
||||
:param goal: UUID or name of the goal.
|
||||
"""
|
||||
if self.from_goals:
|
||||
raise exception.OperationNotPermitted
|
||||
|
||||
goals = CONF.watcher_goals.goals
|
||||
goal = {}
|
||||
if goal_name in goals.keys():
|
||||
goal = {'name': goal_name, 'strategy': goals[goal_name]}
|
||||
if common_utils.is_uuid_like(goal):
|
||||
get_goal_func = objects.Goal.get_by_uuid
|
||||
else:
|
||||
get_goal_func = objects.Goal.get_by_name
|
||||
|
||||
return Goal.convert_with_links(goal)
|
||||
rpc_goal = get_goal_func(pecan.request.context, goal)
|
||||
|
||||
return Goal.convert_with_links(rpc_goal)
|
||||
|
||||
281
watcher/api/controllers/v1/strategy.py
Normal file
@@ -0,0 +1,281 @@
|
||||
# -*- 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.
|
||||
|
||||
"""
|
||||
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 goal.
|
||||
|
||||
Some strategies may provide better optimization results but may take more time
|
||||
to find an optimal :ref:`Solution <solution_definition>`.
|
||||
"""
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
import pecan
|
||||
from pecan import rest
|
||||
import wsme
|
||||
from wsme import types as wtypes
|
||||
import wsmeext.pecan as wsme_pecan
|
||||
|
||||
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.common import exception
|
||||
from watcher.common import utils as common_utils
|
||||
from watcher import objects
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class Strategy(base.APIBase):
|
||||
"""API representation of a strategy.
|
||||
|
||||
This class enforces type checking and value constraints, and converts
|
||||
between the internal object model and the API representation of a strategy.
|
||||
"""
|
||||
_goal_uuid = None
|
||||
|
||||
def _get_goal(self, value):
|
||||
if value == wtypes.Unset:
|
||||
return None
|
||||
goal = None
|
||||
try:
|
||||
if (common_utils.is_uuid_like(value) or
|
||||
common_utils.is_int_like(value)):
|
||||
goal = objects.Goal.get(pecan.request.context, value)
|
||||
else:
|
||||
goal = objects.Goal.get_by_name(pecan.request.context, value)
|
||||
except exception.GoalNotFound:
|
||||
pass
|
||||
if goal:
|
||||
self.goal_id = goal.id
|
||||
return goal
|
||||
|
||||
def _get_goal_uuid(self):
|
||||
return self._goal_uuid
|
||||
|
||||
def _set_goal_uuid(self, value):
|
||||
if value and self._goal_uuid != value:
|
||||
self._goal_uuid = None
|
||||
goal = self._get_goal(value)
|
||||
if goal:
|
||||
self._goal_uuid = goal.uuid
|
||||
|
||||
uuid = types.uuid
|
||||
"""Unique UUID for this strategy"""
|
||||
|
||||
name = wtypes.text
|
||||
"""Name of the strategy"""
|
||||
|
||||
display_name = wtypes.text
|
||||
"""Localized name of the strategy"""
|
||||
|
||||
links = wsme.wsattr([link.Link], readonly=True)
|
||||
"""A list containing a self link and associated goal links"""
|
||||
|
||||
goal_uuid = wsme.wsproperty(wtypes.text, _get_goal_uuid, _set_goal_uuid,
|
||||
mandatory=True)
|
||||
"""The UUID of the goal this audit refers to"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(Strategy, self).__init__()
|
||||
|
||||
self.fields = []
|
||||
self.fields.append('uuid')
|
||||
self.fields.append('name')
|
||||
self.fields.append('display_name')
|
||||
self.fields.append('goal_uuid')
|
||||
setattr(self, 'uuid', kwargs.get('uuid', wtypes.Unset))
|
||||
setattr(self, 'name', kwargs.get('name', wtypes.Unset))
|
||||
setattr(self, 'display_name', kwargs.get('display_name', wtypes.Unset))
|
||||
setattr(self, 'goal_uuid', kwargs.get('goal_id', wtypes.Unset))
|
||||
|
||||
@staticmethod
|
||||
def _convert_with_links(strategy, url, expand=True):
|
||||
if not expand:
|
||||
strategy.unset_fields_except(
|
||||
['uuid', 'name', 'display_name', 'goal_uuid'])
|
||||
|
||||
strategy.links = [
|
||||
link.Link.make_link('self', url, 'strategies', strategy.uuid),
|
||||
link.Link.make_link('bookmark', url, 'strategies', strategy.uuid,
|
||||
bookmark=True)]
|
||||
return strategy
|
||||
|
||||
@classmethod
|
||||
def convert_with_links(cls, strategy, expand=True):
|
||||
strategy = Strategy(**strategy.as_dict())
|
||||
return cls._convert_with_links(
|
||||
strategy, pecan.request.host_url, expand)
|
||||
|
||||
@classmethod
|
||||
def sample(cls, expand=True):
|
||||
sample = cls(uuid='27e3153e-d5bf-4b7e-b517-fb518e17f34c',
|
||||
name='DUMMY',
|
||||
display_name='Dummy strategy')
|
||||
return cls._convert_with_links(sample, 'http://localhost:9322', expand)
|
||||
|
||||
|
||||
class StrategyCollection(collection.Collection):
|
||||
"""API representation of a collection of strategies."""
|
||||
|
||||
strategies = [Strategy]
|
||||
"""A list containing strategies objects"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(StrategyCollection, self).__init__()
|
||||
self._type = 'strategies'
|
||||
|
||||
@staticmethod
|
||||
def convert_with_links(strategies, limit, url=None, expand=False,
|
||||
**kwargs):
|
||||
strategy_collection = StrategyCollection()
|
||||
strategy_collection.strategies = [
|
||||
Strategy.convert_with_links(g, expand) for g in strategies]
|
||||
|
||||
if 'sort_key' in kwargs:
|
||||
reverse = False
|
||||
if kwargs['sort_key'] == 'strategy':
|
||||
if 'sort_dir' in kwargs:
|
||||
reverse = True if kwargs['sort_dir'] == 'desc' else False
|
||||
strategy_collection.strategies = sorted(
|
||||
strategy_collection.strategies,
|
||||
key=lambda strategy: strategy.uuid,
|
||||
reverse=reverse)
|
||||
|
||||
strategy_collection.next = strategy_collection.get_next(
|
||||
limit, url=url, **kwargs)
|
||||
return strategy_collection
|
||||
|
||||
@classmethod
|
||||
def sample(cls):
|
||||
sample = cls()
|
||||
sample.strategies = [Strategy.sample(expand=False)]
|
||||
return sample
|
||||
|
||||
|
||||
class StrategiesController(rest.RestController):
|
||||
"""REST controller for Strategies."""
|
||||
def __init__(self):
|
||||
super(StrategiesController, self).__init__()
|
||||
|
||||
from_strategies = False
|
||||
"""A flag to indicate if the requests to this controller are coming
|
||||
from the top-level resource Strategies."""
|
||||
|
||||
_custom_actions = {
|
||||
'detail': ['GET'],
|
||||
}
|
||||
|
||||
def _get_strategies_collection(self, filters, marker, limit, sort_key,
|
||||
sort_dir, expand=False, resource_url=None):
|
||||
api_utils.validate_search_filters(
|
||||
filters, list(objects.strategy.Strategy.fields.keys()) +
|
||||
["goal_uuid", "goal_name"])
|
||||
limit = api_utils.validate_limit(limit)
|
||||
api_utils.validate_sort_dir(sort_dir)
|
||||
|
||||
sort_db_key = (sort_key if sort_key in objects.Strategy.fields.keys()
|
||||
else None)
|
||||
|
||||
marker_obj = None
|
||||
if marker:
|
||||
marker_obj = objects.Strategy.get_by_uuid(
|
||||
pecan.request.context, marker)
|
||||
|
||||
strategies = objects.Strategy.list(
|
||||
pecan.request.context, limit, marker_obj, filters=filters,
|
||||
sort_key=sort_db_key, sort_dir=sort_dir)
|
||||
|
||||
return StrategyCollection.convert_with_links(
|
||||
strategies, limit, url=resource_url, expand=expand,
|
||||
sort_key=sort_key, sort_dir=sort_dir)
|
||||
|
||||
@wsme_pecan.wsexpose(StrategyCollection, wtypes.text, wtypes.text,
|
||||
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 strategies.
|
||||
|
||||
:param goal: goal UUID or name to filter by.
|
||||
: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.
|
||||
"""
|
||||
filters = {}
|
||||
if goal:
|
||||
if common_utils.is_uuid_like(goal):
|
||||
filters['goal_uuid'] = goal
|
||||
else:
|
||||
filters['goal_name'] = goal
|
||||
|
||||
return self._get_strategies_collection(
|
||||
filters, marker, limit, sort_key, sort_dir)
|
||||
|
||||
@wsme_pecan.wsexpose(StrategyCollection, wtypes.text, wtypes.text, int,
|
||||
wtypes.text, wtypes.text)
|
||||
def detail(self, goal=None, marker=None, limit=None,
|
||||
sort_key='id', sort_dir='asc'):
|
||||
"""Retrieve a list of strategies with detail.
|
||||
|
||||
:param goal: goal UUID or name to filter by.
|
||||
: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.
|
||||
"""
|
||||
# NOTE(lucasagomes): /detail should only work agaist collections
|
||||
parent = pecan.request.path.split('/')[:-1][-1]
|
||||
if parent != "strategies":
|
||||
raise exception.HTTPNotFound
|
||||
expand = True
|
||||
resource_url = '/'.join(['strategies', 'detail'])
|
||||
|
||||
filters = {}
|
||||
if goal:
|
||||
if common_utils.is_uuid_like(goal):
|
||||
filters['goal_uuid'] = goal
|
||||
else:
|
||||
filters['goal_name'] = goal
|
||||
|
||||
return self._get_strategies_collection(
|
||||
filters, marker, limit, sort_key, sort_dir, expand, resource_url)
|
||||
|
||||
@wsme_pecan.wsexpose(Strategy, wtypes.text)
|
||||
def get_one(self, strategy):
|
||||
"""Retrieve information about the given strategy.
|
||||
|
||||
:param strategy: UUID or name of the strategy.
|
||||
"""
|
||||
if self.from_strategies:
|
||||
raise exception.OperationNotPermitted
|
||||
|
||||
if common_utils.is_uuid_like(strategy):
|
||||
get_strategy_func = objects.Strategy.get_by_uuid
|
||||
else:
|
||||
get_strategy_func = objects.Strategy.get_by_name
|
||||
|
||||
rpc_strategy = get_strategy_func(pecan.request.context, strategy)
|
||||
|
||||
return Strategy.convert_with_links(rpc_strategy)
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -31,11 +31,6 @@ class UuidOrNameType(wtypes.UserType):
|
||||
|
||||
basetype = wtypes.text
|
||||
name = 'uuid_or_name'
|
||||
# FIXME(lucasagomes): When used with wsexpose decorator WSME will try
|
||||
# to get the name of the type by accessing it's __name__ attribute.
|
||||
# Remove this __name__ attribute once it's fixed in WSME.
|
||||
# https://bugs.launchpad.net/wsme/+bug/1265590
|
||||
__name__ = name
|
||||
|
||||
@staticmethod
|
||||
def validate(value):
|
||||
@@ -55,11 +50,6 @@ class NameType(wtypes.UserType):
|
||||
|
||||
basetype = wtypes.text
|
||||
name = 'name'
|
||||
# FIXME(lucasagomes): When used with wsexpose decorator WSME will try
|
||||
# to get the name of the type by accessing it's __name__ attribute.
|
||||
# Remove this __name__ attribute once it's fixed in WSME.
|
||||
# https://bugs.launchpad.net/wsme/+bug/1265590
|
||||
__name__ = name
|
||||
|
||||
@staticmethod
|
||||
def validate(value):
|
||||
@@ -79,11 +69,6 @@ class UuidType(wtypes.UserType):
|
||||
|
||||
basetype = wtypes.text
|
||||
name = 'uuid'
|
||||
# FIXME(lucasagomes): When used with wsexpose decorator WSME will try
|
||||
# to get the name of the type by accessing it's __name__ attribute.
|
||||
# Remove this __name__ attribute once it's fixed in WSME.
|
||||
# https://bugs.launchpad.net/wsme/+bug/1265590
|
||||
__name__ = name
|
||||
|
||||
@staticmethod
|
||||
def validate(value):
|
||||
@@ -103,11 +88,6 @@ class BooleanType(wtypes.UserType):
|
||||
|
||||
basetype = wtypes.text
|
||||
name = 'boolean'
|
||||
# FIXME(lucasagomes): When used with wsexpose decorator WSME will try
|
||||
# to get the name of the type by accessing it's __name__ attribute.
|
||||
# Remove this __name__ attribute once it's fixed in WSME.
|
||||
# https://bugs.launchpad.net/wsme/+bug/1265590
|
||||
__name__ = name
|
||||
|
||||
@staticmethod
|
||||
def validate(value):
|
||||
@@ -129,11 +109,6 @@ class JsonType(wtypes.UserType):
|
||||
|
||||
basetype = wtypes.text
|
||||
name = 'json'
|
||||
# FIXME(lucasagomes): When used with wsexpose decorator WSME will try
|
||||
# to get the name of the type by accessing it's __name__ attribute.
|
||||
# Remove this __name__ attribute once it's fixed in WSME.
|
||||
# https://bugs.launchpad.net/wsme/+bug/1265590
|
||||
__name__ = name
|
||||
|
||||
def __str__(self):
|
||||
# These are the json serializable native types
|
||||
@@ -181,8 +156,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 +193,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()
|
||||
67
watcher/applier/action_plan/default.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
|
||||
|
||||
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)
|
||||
applier.execute(self.action_plan_uuid)
|
||||
state = ap_objects.State.SUCCEEDED
|
||||
|
||||
except Exception as e:
|
||||
LOG.exception(e)
|
||||
state = ap_objects.State.FAILED
|
||||
|
||||
finally:
|
||||
# update state
|
||||
self.notify(self.action_plan_uuid,
|
||||
event_types.EventTypes.LAUNCH_ACTION_PLAN,
|
||||
state)
|
||||
133
watcher/applier/actions/base.py
Normal file
@@ -0,0 +1,133 @@
|
||||
# -*- 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
|
||||
from watcher.common.loader import loadable
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class BaseAction(loadable.Loadable):
|
||||
# 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, config, osc=None):
|
||||
"""Constructor
|
||||
|
||||
:param config: A mapping containing the configuration of this action
|
||||
:type config: dict
|
||||
:param osc: an OpenStackClients instance, defaults to None
|
||||
:type osc: :py:class:`~.OpenStackClients` instance, optional
|
||||
"""
|
||||
super(BaseAction, self).__init__(config)
|
||||
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]
|
||||
|
||||
@classmethod
|
||||
def get_config_opts(cls):
|
||||
"""Defines the configuration options to be associated to this loadable
|
||||
|
||||
:return: A list of configuration options relative to this Loadable
|
||||
:rtype: list of :class:`oslo_config.cfg.Opt` instances
|
||||
"""
|
||||
return []
|
||||
|
||||
@abc.abstractmethod
|
||||
def execute(self):
|
||||
"""Executes the main logic of the action
|
||||
|
||||
This method can be used to perform an action on a given set of input
|
||||
parameters to accomplish some type of operation. This operation may
|
||||
return a boolean value as a result of its execution. If False, this
|
||||
will be considered as an error and will then trigger the reverting of
|
||||
the actions.
|
||||
|
||||
:returns: A flag indicating whether or not the action succeeded
|
||||
:rtype: bool
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractmethod
|
||||
def revert(self):
|
||||
"""Revert this action
|
||||
|
||||
This method should rollback the resource to its initial state in the
|
||||
event of a faulty execution. This happens when the action raised an
|
||||
exception during its :py:meth:`~.BaseAction.execute`.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractmethod
|
||||
def precondition(self):
|
||||
"""Hook: called before the execution of an action
|
||||
|
||||
This method can be used to perform some initializations or to make
|
||||
some more advanced validation on its input parameters. So if you wish
|
||||
to block its execution based on this factor, `raise` the related
|
||||
exception.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractmethod
|
||||
def postcondition(self):
|
||||
"""Hook: called after the execution of an action
|
||||
|
||||
This function is called regardless of whether an action succeded or
|
||||
not. So you can use it to perform cleanup operations.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractproperty
|
||||
def schema(self):
|
||||
"""Defines a Schema that the input parameters shall comply to
|
||||
|
||||
:returns: A schema declaring the input parameters this action should be
|
||||
provided along with their respective constraints
|
||||
:rtype: :py:class:`voluptuous.Schema` instance
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def validate_parameters(self):
|
||||
self.schema(self.input_parameters)
|
||||
return True
|
||||
102
watcher/applier/actions/change_nova_service_state.py
Normal file
@@ -0,0 +1,102 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2015 b<>com
|
||||
#
|
||||
# Authors: Jean-Emile DARTOIS <jean-emile.dartois@b-com.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
import six
|
||||
import voluptuous
|
||||
|
||||
from watcher._i18n import _
|
||||
from watcher.applier.actions import base
|
||||
from watcher.common import exception
|
||||
from watcher.common import nova_helper
|
||||
from watcher.decision_engine.model import hypervisor_state as hstate
|
||||
|
||||
|
||||
class ChangeNovaServiceState(base.BaseAction):
|
||||
"""Disables or enables the nova-compute service, deployed on a host
|
||||
|
||||
By using this action, you will be able to update the state of a
|
||||
nova-compute service. A disabled nova-compute service can not be selected
|
||||
by the nova scheduler for future deployment of server.
|
||||
|
||||
The action schema is::
|
||||
|
||||
schema = Schema({
|
||||
'resource_id': str,
|
||||
'state': str,
|
||||
})
|
||||
|
||||
The `resource_id` references a nova-compute service name (list of available
|
||||
nova-compute services is returned by this command: ``nova service-list
|
||||
--binary nova-compute``).
|
||||
The `state` value should either be `ONLINE` or `OFFLINE`.
|
||||
"""
|
||||
|
||||
STATE = 'state'
|
||||
|
||||
@property
|
||||
def schema(self):
|
||||
return voluptuous.Schema({
|
||||
voluptuous.Required(self.RESOURCE_ID):
|
||||
voluptuous.All(
|
||||
voluptuous.Any(*six.string_types),
|
||||
voluptuous.Length(min=1)),
|
||||
voluptuous.Required(self.STATE):
|
||||
voluptuous.Any(*[state.value
|
||||
for state in list(hstate.HypervisorState)]),
|
||||
})
|
||||
|
||||
@property
|
||||
def host(self):
|
||||
return self.resource_id
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
return self.input_parameters.get(self.STATE)
|
||||
|
||||
def execute(self):
|
||||
target_state = None
|
||||
if self.state == hstate.HypervisorState.DISABLED.value:
|
||||
target_state = False
|
||||
elif self.state == hstate.HypervisorState.ENABLED.value:
|
||||
target_state = True
|
||||
return self._nova_manage_service(target_state)
|
||||
|
||||
def revert(self):
|
||||
target_state = None
|
||||
if self.state == hstate.HypervisorState.DISABLED.value:
|
||||
target_state = True
|
||||
elif self.state == hstate.HypervisorState.ENABLED.value:
|
||||
target_state = False
|
||||
return self._nova_manage_service(target_state)
|
||||
|
||||
def _nova_manage_service(self, state):
|
||||
if state is None:
|
||||
raise exception.IllegalArgumentException(
|
||||
message=_("The target state is not defined"))
|
||||
|
||||
nova = nova_helper.NovaHelper(osc=self.osc)
|
||||
if state is True:
|
||||
return nova.enable_service_nova_compute(self.host)
|
||||
else:
|
||||
return nova.disable_service_nova_compute(self.host)
|
||||
|
||||
def precondition(self):
|
||||
pass
|
||||
|
||||
def postcondition(self):
|
||||
pass
|
||||
42
watcher/applier/actions/factory.py
Normal file
@@ -0,0 +1,42 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2016 b<>com
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from oslo_log import log
|
||||
|
||||
from watcher.applier.actions.loading import default
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class ActionFactory(object):
|
||||
def __init__(self):
|
||||
self.action_loader = default.DefaultActionLoader()
|
||||
|
||||
def make_action(self, object_action, osc=None):
|
||||
LOG.debug("Creating instance of %s", object_action.action_type)
|
||||
loaded_action = self.action_loader.load(name=object_action.action_type,
|
||||
osc=osc)
|
||||
loaded_action.input_parameters = object_action.input_parameters
|
||||
LOG.debug("Checking the input parameters")
|
||||
# NOTE(jed) if we change the schema of an action and we try to reload
|
||||
# an older version of the Action, the validation can fail.
|
||||
# We need to add the versioning of an Action or a migration tool.
|
||||
# We can also create an new Action which extends the previous one.
|
||||
loaded_action.validate_parameters()
|
||||
return loaded_action
|
||||
@@ -1,8 +1,6 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2015 b<>com
|
||||
#
|
||||
# Authors: Jean-Emile DARTOIS <jean-emile.dartois@b-com.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
@@ -17,12 +15,11 @@
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
from enum import Enum
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from watcher.common.loader import default
|
||||
|
||||
|
||||
class StrategyState(Enum):
|
||||
INIT = 1,
|
||||
READY = 2,
|
||||
RUNNING = 3,
|
||||
TERMINATED = 4,
|
||||
ERROR = 5
|
||||
class DefaultActionLoader(default.DefaultLoader):
|
||||
def __init__(self):
|
||||
super(DefaultActionLoader, self).__init__(namespace='watcher_actions')
|
||||
174
watcher/applier/actions/migration.py
Normal file
@@ -0,0 +1,174 @@
|
||||
# -*- 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):
|
||||
"""Migrates a server to a destination nova-compute host
|
||||
|
||||
This action will allow you to migrate a server to another compute
|
||||
destination host.
|
||||
Migration type 'live' can only be used for migrating active VMs.
|
||||
Migration type 'cold' can be used for migrating non-active VMs
|
||||
as well active VMs, which will be shut down while migrating.
|
||||
|
||||
The action schema is::
|
||||
|
||||
schema = Schema({
|
||||
'resource_id': str, # should be a UUID
|
||||
'migration_type': str, # choices -> "live", "cold"
|
||||
'dst_hypervisor': str,
|
||||
'src_hypervisor': str,
|
||||
})
|
||||
|
||||
The `resource_id` is the UUID of the server to migrate.
|
||||
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'
|
||||
COLD_MIGRATION = 'cold'
|
||||
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,
|
||||
self.COLD_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 _cold_migrate_instance(self, nova, destination):
|
||||
result = None
|
||||
try:
|
||||
result = nova.watcher_non_live_migrate_instance(
|
||||
instance_id=self.instance_uuid,
|
||||
dest_hostname=destination)
|
||||
except Exception as exc:
|
||||
LOG.exception(exc)
|
||||
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 == self.LIVE_MIGRATION:
|
||||
return self._live_migrate_instance(nova, destination)
|
||||
elif self.migration_type == self.COLD_MIGRATION:
|
||||
return self._cold_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)
|
||||
|
||||