Compare commits
155 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5817f6833c | ||
|
|
67754102c8 | ||
|
|
6545f9c2ad | ||
|
|
583c23b9d9 | ||
|
|
ea9ab8d6e5 | ||
|
|
8fd57276be | ||
|
|
1d197e5e8f | ||
|
|
b33337b7bd | ||
|
|
d0b9b6ce38 | ||
|
|
3cd847e2ab | ||
|
|
ba907f4905 | ||
|
|
8e787d0a87 | ||
|
|
b45c7b678e | ||
|
|
ec64e04cc5 | ||
|
|
f206c2d425 | ||
|
|
1500bee1c6 | ||
|
|
9f813fb90d | ||
|
|
8167eec625 | ||
|
|
6731851383 | ||
|
|
0ddfa278ef | ||
|
|
aae7699bc5 | ||
|
|
8521608e19 | ||
|
|
2266e2baa3 | ||
|
|
e08a0e9af2 | ||
|
|
844577e9cc | ||
|
|
0b44492da7 | ||
|
|
b146e29c39 | ||
|
|
0cabd5bd3a | ||
|
|
728acc091b | ||
|
|
7340decf73 | ||
|
|
84e8eb4cb0 | ||
|
|
5283871af5 | ||
|
|
916fd73186 | ||
|
|
63c1aabdd2 | ||
|
|
03569db6c3 | ||
|
|
19cf05fd75 | ||
|
|
251ca83ddc | ||
|
|
5b349b4e89 | ||
|
|
b0d03ae6b8 | ||
|
|
78de029a57 | ||
|
|
489356da3a | ||
|
|
db4339c371 | ||
|
|
c7ec186576 | ||
|
|
12bdfca0d9 | ||
|
|
5f179609d0 | ||
|
|
a842bc1c91 | ||
|
|
5eb1d91335 | ||
|
|
264b0fe9a1 | ||
|
|
a487a4efc8 | ||
|
|
034f0bf68a | ||
|
|
87b95bb639 | ||
|
|
e081ac91b4 | ||
|
|
3574dba9da | ||
|
|
8fc9c6c1d8 | ||
|
|
51f17f1d7d | ||
|
|
313f156394 | ||
|
|
129de26419 | ||
|
|
d38bc4e716 | ||
|
|
6ffa5feaaf | ||
|
|
bdd3a6ab89 | ||
|
|
8c5f363844 | ||
|
|
e61140edfb | ||
|
|
abc019829f | ||
|
|
0dd5826dd3 | ||
|
|
227a9e9b63 | ||
|
|
5b6768140f | ||
|
|
1ac7fbec34 | ||
|
|
ebb8885ece | ||
|
|
3fb4cadd76 | ||
|
|
c1d3f8094b | ||
|
|
59649f6a81 | ||
|
|
045404095d | ||
|
|
606a340b5b | ||
|
|
35fdbbe16e | ||
|
|
2db279e019 | ||
|
|
986ba9872f | ||
|
|
44a5a1573c | ||
|
|
fe34b420a5 | ||
|
|
43da4a6d21 | ||
|
|
7ba1bf2237 | ||
|
|
206bb413e2 | ||
|
|
1188d4263d | ||
|
|
1451d9c134 | ||
|
|
6d739ae1d0 | ||
|
|
f2751b4818 | ||
|
|
e2bf82607e | ||
|
|
d76b5d2f7e | ||
|
|
69730d80b2 | ||
|
|
17aa1122da | ||
|
|
d0f5a9fdf5 | ||
|
|
32dacdca8b | ||
|
|
fbfb7159e3 | ||
|
|
d7a44739a6 | ||
|
|
65c63a9351 | ||
|
|
58d86de064 | ||
|
|
8d84da307b | ||
|
|
01e865edbf | ||
|
|
b4b3856f14 | ||
|
|
67d065e02a | ||
|
|
891a351a04 | ||
|
|
f47fd9ac5e | ||
|
|
7b766680b0 | ||
|
|
75a025d2d2 | ||
|
|
590bd43a1d | ||
|
|
d2e42a835b | ||
|
|
a34e55e47a | ||
|
|
a62acbf2ab | ||
|
|
35074edaf7 | ||
|
|
dd4aac4092 | ||
|
|
bd8151e581 | ||
|
|
8585e49359 | ||
|
|
5d3af47b7d | ||
|
|
1001525664 | ||
|
|
a33f40ec21 | ||
|
|
d2a8454043 | ||
|
|
27c56a19e4 | ||
|
|
9bb1e653d8 | ||
|
|
bb536ee40d | ||
|
|
a0bf1b7d70 | ||
|
|
40f6eea637 | ||
|
|
6c5a3910a7 | ||
|
|
a4fac69d85 | ||
|
|
21994297cf | ||
|
|
8a818c9071 | ||
|
|
041fcb4ca0 | ||
|
|
a8d765bb28 | ||
|
|
2b152bf17c | ||
|
|
08e585d405 | ||
|
|
38e4255ec0 | ||
|
|
a167044cde | ||
|
|
1366f79b63 | ||
|
|
f55b9b127e | ||
|
|
1b7b467151 | ||
|
|
f40fcdc573 | ||
|
|
2d98d5e743 | ||
|
|
877230569a | ||
|
|
f842c5f601 | ||
|
|
0a899a2dc2 | ||
|
|
426232e288 | ||
|
|
778d4c6fe4 | ||
|
|
f852467d6a | ||
|
|
dcf64ed1f4 | ||
|
|
03f75202c8 | ||
|
|
0173a713c1 | ||
|
|
216f3bab29 | ||
|
|
077b806bf6 | ||
|
|
d892153b58 | ||
|
|
2efe211f36 | ||
|
|
f55ea7824e | ||
|
|
e5eb4f51be | ||
|
|
f637a368d7 | ||
|
|
2e8fb5a821 | ||
|
|
527423a5fa | ||
|
|
334558f17c | ||
|
|
fd55d28d42 |
9
.gitignore
vendored
9
.gitignore
vendored
@@ -4,8 +4,7 @@
|
|||||||
*.so
|
*.so
|
||||||
|
|
||||||
# Packages
|
# Packages
|
||||||
*.egg
|
*.egg*
|
||||||
*.egg-info
|
|
||||||
dist
|
dist
|
||||||
build
|
build
|
||||||
eggs
|
eggs
|
||||||
@@ -43,9 +42,11 @@ output/*/index.html
|
|||||||
|
|
||||||
# Sphinx
|
# Sphinx
|
||||||
doc/build
|
doc/build
|
||||||
doc/source/api
|
doc/source/api/*
|
||||||
doc/source/samples
|
doc/source/samples
|
||||||
doc/source/watcher.conf.sample
|
doc/source/_static/*.sample
|
||||||
|
!doc/source/api/index.rst
|
||||||
|
!doc/source/api/v1.rst
|
||||||
|
|
||||||
# pbr generates these
|
# pbr generates these
|
||||||
AUTHORS
|
AUTHORS
|
||||||
|
|||||||
@@ -28,4 +28,4 @@ migration, increased energy efficiency-and more!
|
|||||||
* Wiki: https://wiki.openstack.org/wiki/Watcher
|
* Wiki: https://wiki.openstack.org/wiki/Watcher
|
||||||
* Source: https://github.com/openstack/watcher
|
* Source: https://github.com/openstack/watcher
|
||||||
* Bugs: https://bugs.launchpad.net/watcher
|
* Bugs: https://bugs.launchpad.net/watcher
|
||||||
* Documentation: https://docs.openstack.org/developer/watcher/
|
* Documentation: https://docs.openstack.org/watcher/latest/
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ if is_ssl_enabled_service "watcher" || is_service_enabled tls-proxy; then
|
|||||||
WATCHER_SERVICE_PROTOCOL="https"
|
WATCHER_SERVICE_PROTOCOL="https"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
WATCHER_USE_MOD_WSGI=$(trueorfalse TRUE WATCHER_USE_MOD_WSGI)
|
WATCHER_USE_MOD_WSGI=$(trueorfalse True WATCHER_USE_MOD_WSGI)
|
||||||
|
|
||||||
if is_suse; then
|
if is_suse; then
|
||||||
WATCHER_WSGI_DIR=${WATCHER_WSGI_DIR:-/srv/www/htdocs/watcher}
|
WATCHER_WSGI_DIR=${WATCHER_WSGI_DIR:-/srv/www/htdocs/watcher}
|
||||||
@@ -235,7 +235,7 @@ function init_watcher {
|
|||||||
recreate_database watcher
|
recreate_database watcher
|
||||||
|
|
||||||
# Create watcher schema
|
# Create watcher schema
|
||||||
$WATCHER_BIN_DIR/watcher-db-manage --config-file $WATCHER_CONF upgrade head
|
$WATCHER_BIN_DIR/watcher-db-manage --config-file $WATCHER_CONF upgrade
|
||||||
fi
|
fi
|
||||||
create_watcher_cache_dir
|
create_watcher_cache_dir
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ GLANCE_HOSTPORT=${SERVICE_HOST}:9292
|
|||||||
DATABASE_TYPE=mysql
|
DATABASE_TYPE=mysql
|
||||||
|
|
||||||
# Enable services (including neutron)
|
# Enable services (including neutron)
|
||||||
ENABLED_SERVICES=n-cpu,n-api-meta,c-vol,q-agt
|
ENABLED_SERVICES=n-cpu,n-api-meta,c-vol,q-agt,placement-client
|
||||||
|
|
||||||
NOVA_VNC_ENABLED=True
|
NOVA_VNC_ENABLED=True
|
||||||
NOVNCPROXY_URL="http://$SERVICE_HOST:6080/vnc_auto.html"
|
NOVNCPROXY_URL="http://$SERVICE_HOST:6080/vnc_auto.html"
|
||||||
|
|||||||
26
doc/notification_samples/service-update.json
Normal file
26
doc/notification_samples/service-update.json
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"payload": {
|
||||||
|
"watcher_object.name": "ServiceUpdatePayload",
|
||||||
|
"watcher_object.namespace": "watcher",
|
||||||
|
"watcher_object.data": {
|
||||||
|
"status_update": {
|
||||||
|
"watcher_object.name": "ServiceStatusUpdatePayload",
|
||||||
|
"watcher_object.namespace": "watcher",
|
||||||
|
"watcher_object.data": {
|
||||||
|
"old_state": "ACTIVE",
|
||||||
|
"state": "FAILED"
|
||||||
|
},
|
||||||
|
"watcher_object.version": "1.0"
|
||||||
|
},
|
||||||
|
"last_seen_up": "2016-09-22T08:32:06Z",
|
||||||
|
"name": "watcher-service",
|
||||||
|
"sevice_host": "controller"
|
||||||
|
},
|
||||||
|
"watcher_object.version": "1.0"
|
||||||
|
},
|
||||||
|
"event_type": "service.update",
|
||||||
|
"priority": "INFO",
|
||||||
|
"message_id": "3984dc2b-8aef-462b-a220-8ae04237a56e",
|
||||||
|
"timestamp": "2016-10-18 09:52:05.219414",
|
||||||
|
"publisher_id": "infra-optim:node0"
|
||||||
|
}
|
||||||
0
doc/source/_static/.placeholder
Normal file
0
doc/source/_static/.placeholder
Normal file
12
doc/source/admin/index.rst
Normal file
12
doc/source/admin/index.rst
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
===================
|
||||||
|
Administrator Guide
|
||||||
|
===================
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
apache-mod-wsgi
|
||||||
|
gmr
|
||||||
|
policy
|
||||||
|
ways-to-install
|
||||||
|
../strategies/index
|
||||||
@@ -4,12 +4,13 @@
|
|||||||
|
|
||||||
https://creativecommons.org/licenses/by/3.0/
|
https://creativecommons.org/licenses/by/3.0/
|
||||||
|
|
||||||
==================
|
=======================
|
||||||
Installing Watcher
|
Ways to install Watcher
|
||||||
==================
|
=======================
|
||||||
|
|
||||||
This document describes how to install Watcher in order to use it. If you are
|
This document describes some ways to install Watcher in order to use it.
|
||||||
intending to develop on or with Watcher, please read :doc:`../dev/environment`.
|
If you are intending to develop on or with Watcher,
|
||||||
|
please read :doc:`../contributor/environment`.
|
||||||
|
|
||||||
Prerequisites
|
Prerequisites
|
||||||
-------------
|
-------------
|
||||||
@@ -77,9 +78,10 @@ Install the Watcher modules dependencies:
|
|||||||
|
|
||||||
# pip install -r requirements.txt
|
# pip install -r requirements.txt
|
||||||
|
|
||||||
From here, refer to :doc:`configuration` to declare Watcher as a new service
|
From here, refer to :doc:`../configuration/configuring` to declare Watcher
|
||||||
into Keystone and to configure its different modules. Once configured, you
|
as a new service into Keystone and to configure its different modules.
|
||||||
should be able to run the Watcher services by issuing these commands:
|
Once configured, you should be able to run the Watcher services by issuing
|
||||||
|
these commands:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
@@ -107,7 +109,7 @@ installed on your system.
|
|||||||
|
|
||||||
Once installed, you still need to declare Watcher as a new service into
|
Once installed, you still need to declare Watcher as a new service into
|
||||||
Keystone and to configure its different modules, which you can find described
|
Keystone and to configure its different modules, which you can find described
|
||||||
in :doc:`configuration`.
|
in :doc:`../configuration/configuring`.
|
||||||
|
|
||||||
|
|
||||||
Installing from packages: Debian (experimental)
|
Installing from packages: Debian (experimental)
|
||||||
4
doc/source/api/index.rst
Normal file
4
doc/source/api/index.rst
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
.. toctree::
|
||||||
|
:maxdepth: 1
|
||||||
|
|
||||||
|
v1
|
||||||
@@ -20,6 +20,17 @@ Goals
|
|||||||
.. autotype:: watcher.api.controllers.v1.goal.Goal
|
.. autotype:: watcher.api.controllers.v1.goal.Goal
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
Strategies
|
||||||
|
==========
|
||||||
|
|
||||||
|
.. rest-controller:: watcher.api.controllers.v1.strategy:StrategiesController
|
||||||
|
:webprefix: /v1/strategies
|
||||||
|
|
||||||
|
.. autotype:: watcher.api.controllers.v1.strategy.StrategyCollection
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. autotype:: watcher.api.controllers.v1.strategy.Strategy
|
||||||
|
:members:
|
||||||
|
|
||||||
Audit Templates
|
Audit Templates
|
||||||
===============
|
===============
|
||||||
@@ -31,8 +31,7 @@ sys.path.insert(0, os.path.abspath('./'))
|
|||||||
# Add any Sphinx extension module names here, as strings. They can be
|
# Add any Sphinx extension module names here, as strings. They can be
|
||||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||||
extensions = [
|
extensions = [
|
||||||
'oslo_config.sphinxconfiggen',
|
'oslo_config.sphinxext',
|
||||||
'oslosphinx',
|
|
||||||
'sphinx.ext.autodoc',
|
'sphinx.ext.autodoc',
|
||||||
'sphinx.ext.viewcode',
|
'sphinx.ext.viewcode',
|
||||||
'sphinxcontrib.httpdomain',
|
'sphinxcontrib.httpdomain',
|
||||||
@@ -41,10 +40,14 @@ extensions = [
|
|||||||
'wsmeext.sphinxext',
|
'wsmeext.sphinxext',
|
||||||
'ext.term',
|
'ext.term',
|
||||||
'ext.versioned_notifications',
|
'ext.versioned_notifications',
|
||||||
|
'oslo_config.sphinxconfiggen',
|
||||||
|
'openstackdocstheme',
|
||||||
]
|
]
|
||||||
|
|
||||||
wsme_protocols = ['restjson']
|
wsme_protocols = ['restjson']
|
||||||
config_generator_config_file = '../../etc/watcher/watcher-config-generator.conf'
|
config_generator_config_file = [(
|
||||||
|
'../../etc/watcher/oslo-config-generator/watcher.conf',
|
||||||
|
'_static/watcher')]
|
||||||
sample_config_basename = 'watcher'
|
sample_config_basename = 'watcher'
|
||||||
|
|
||||||
# autodoc generation is a bit aggressive and a nuisance when doing heavy
|
# autodoc generation is a bit aggressive and a nuisance when doing heavy
|
||||||
@@ -92,6 +95,8 @@ add_function_parentheses = True
|
|||||||
# unit titles (such as .. function::).
|
# unit titles (such as .. function::).
|
||||||
add_module_names = True
|
add_module_names = True
|
||||||
|
|
||||||
|
suppress_warnings = ['app.add_directive']
|
||||||
|
|
||||||
# The name of the Pygments (syntax highlighting) style to use.
|
# The name of the Pygments (syntax highlighting) style to use.
|
||||||
pygments_style = 'sphinx'
|
pygments_style = 'sphinx'
|
||||||
|
|
||||||
@@ -117,12 +122,20 @@ man_pages = [
|
|||||||
# Sphinx are currently 'default' and 'sphinxdoc'.
|
# Sphinx are currently 'default' and 'sphinxdoc'.
|
||||||
# html_theme_path = ["."]
|
# html_theme_path = ["."]
|
||||||
# html_theme = '_theme'
|
# html_theme = '_theme'
|
||||||
|
html_theme = 'openstackdocs'
|
||||||
# html_static_path = ['static']
|
# html_static_path = ['static']
|
||||||
html_theme_options = {'incubating': True}
|
# html_theme_options = {}
|
||||||
|
|
||||||
# Output file base name for HTML help builder.
|
# Output file base name for HTML help builder.
|
||||||
htmlhelp_basename = '%sdoc' % project
|
htmlhelp_basename = '%sdoc' % project
|
||||||
|
|
||||||
|
html_last_updated_fmt = '%Y-%m-%d %H:%M'
|
||||||
|
|
||||||
|
#openstackdocstheme options
|
||||||
|
repository_name = 'openstack/watcher'
|
||||||
|
bug_project = 'watcher'
|
||||||
|
bug_tag = ''
|
||||||
|
|
||||||
# Grouping the document tree into LaTeX files. List of tuples
|
# Grouping the document tree into LaTeX files. List of tuples
|
||||||
# (source start file, target name, title, author, documentclass
|
# (source start file, target name, title, author, documentclass
|
||||||
# [howto/manual]).
|
# [howto/manual]).
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
../../etc/watcher/watcher-config-generator.conf
|
|
||||||
@@ -424,7 +424,7 @@ to Watcher receives Nova notifications in ``watcher_notifications`` as well.
|
|||||||
into which Nova services will publish events ::
|
into which Nova services will publish events ::
|
||||||
|
|
||||||
[oslo_messaging_notifications]
|
[oslo_messaging_notifications]
|
||||||
driver = messaging
|
driver = messagingv2
|
||||||
topics = notifications,watcher_notifications
|
topics = notifications,watcher_notifications
|
||||||
|
|
||||||
* Restart the Nova services.
|
* Restart the Nova services.
|
||||||
5
doc/source/configuration/index.rst
Normal file
5
doc/source/configuration/index.rst
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
.. toctree::
|
||||||
|
:maxdepth: 1
|
||||||
|
|
||||||
|
configuring
|
||||||
|
watcher
|
||||||
11
doc/source/configuration/watcher.rst
Normal file
11
doc/source/configuration/watcher.rst
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
.. _watcher_sample_configuration_files:
|
||||||
|
|
||||||
|
------------
|
||||||
|
watcher.conf
|
||||||
|
------------
|
||||||
|
|
||||||
|
The ``watcher.conf`` file contains most of the options to configure the
|
||||||
|
Watcher services.
|
||||||
|
|
||||||
|
.. show-options::
|
||||||
|
:config-file: etc/watcher/oslo-config-generator/watcher.conf
|
||||||
@@ -64,8 +64,9 @@ IRC Channel
|
|||||||
``#openstack-watcher`` (changelog_)
|
``#openstack-watcher`` (changelog_)
|
||||||
|
|
||||||
Weekly Meetings
|
Weekly Meetings
|
||||||
on Wednesdays at 14:00 UTC on even weeks, 9:00 UTC on odd weeks, in the
|
On Wednesdays at 14:00 UTC on even weeks in the ``#openstack-meeting-4``
|
||||||
``#openstack-meeting-4`` IRC channel (`meetings logs`_)
|
IRC channel, 13:00 UTC on odd weeks in the ``#openstack-meeting-alt``
|
||||||
|
IRC channel (`meetings logs`_)
|
||||||
|
|
||||||
.. _changelog: http://eavesdrop.openstack.org/irclogs/%23openstack-watcher/
|
.. _changelog: http://eavesdrop.openstack.org/irclogs/%23openstack-watcher/
|
||||||
.. _meetings logs: http://eavesdrop.openstack.org/meetings/watcher/
|
.. _meetings logs: http://eavesdrop.openstack.org/meetings/watcher/
|
||||||
@@ -17,7 +17,7 @@ To install Watcher from packaging, refer instead to Watcher `User
|
|||||||
Documentation`_.
|
Documentation`_.
|
||||||
|
|
||||||
.. _`Git Repository`: https://git.openstack.org/cgit/openstack/watcher
|
.. _`Git Repository`: https://git.openstack.org/cgit/openstack/watcher
|
||||||
.. _`User Documentation`: https://docs.openstack.org/developer/watcher/
|
.. _`User Documentation`: https://docs.openstack.org/watcher/latest/
|
||||||
|
|
||||||
Prerequisites
|
Prerequisites
|
||||||
=============
|
=============
|
||||||
@@ -194,7 +194,7 @@ still need to configure the following sections:
|
|||||||
message bus
|
message bus
|
||||||
|
|
||||||
So if you need some more details on how to configure one or more of these
|
So if you need some more details on how to configure one or more of these
|
||||||
sections, please do have a look at :doc:`../deploy/configuration` before
|
sections, please do have a look at :doc:`../configuration/configuring` before
|
||||||
continuing.
|
continuing.
|
||||||
|
|
||||||
|
|
||||||
@@ -240,7 +240,7 @@ To run the Watcher Applier service, use:
|
|||||||
(watcher) $ watcher-applier
|
(watcher) $ watcher-applier
|
||||||
|
|
||||||
Default configuration of these services are available into ``/etc/watcher``
|
Default configuration of these services are available into ``/etc/watcher``
|
||||||
directory. See :doc:`../deploy/configuration` for details on how Watcher is
|
directory. See :doc:`../configuration/configuring` for details on how Watcher is
|
||||||
configured. By default, Watcher is configured with SQL backends.
|
configured. By default, Watcher is configured with SQL backends.
|
||||||
|
|
||||||
|
|
||||||
8
doc/source/contributor/index.rst
Normal file
8
doc/source/contributor/index.rst
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
.. toctree::
|
||||||
|
:maxdepth: 1
|
||||||
|
|
||||||
|
environment
|
||||||
|
devstack
|
||||||
|
notifications
|
||||||
|
testing
|
||||||
|
rally_link
|
||||||
@@ -123,8 +123,9 @@ Here below is how you would proceed to register ``NewGoal`` using pbr_:
|
|||||||
new_goal = thirdparty.new:NewGoal
|
new_goal = thirdparty.new:NewGoal
|
||||||
|
|
||||||
|
|
||||||
To get a better understanding on how to implement a more advanced goal,
|
To get a better understanding on how to implement a more advanced goal, have
|
||||||
have a look at the :py:class:`~.ServerConsolidation` class.
|
a look at the
|
||||||
|
:py:class:`watcher.decision_engine.goal.goals.ServerConsolidation` class.
|
||||||
|
|
||||||
.. _pbr: http://docs.openstack.org/developer/pbr/
|
.. _pbr: http://docs.openstack.org/developer/pbr/
|
||||||
|
|
||||||
11
doc/source/contributor/plugin/index.rst
Normal file
11
doc/source/contributor/plugin/index.rst
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
.. toctree::
|
||||||
|
:maxdepth: 1
|
||||||
|
|
||||||
|
base-setup
|
||||||
|
action-plugin
|
||||||
|
cdmc-plugin
|
||||||
|
goal-plugin
|
||||||
|
planner-plugin
|
||||||
|
scoring-engine-plugin
|
||||||
|
strategy-plugin
|
||||||
|
plugins
|
||||||
@@ -37,7 +37,8 @@ Create a new scoring engine plugin
|
|||||||
|
|
||||||
In order to create a new scoring engine you have to:
|
In order to create a new scoring engine you have to:
|
||||||
|
|
||||||
- Extend the :py:class:`~.ScoringEngine` class
|
- Extend the :py:class:`watcher.decision_engine.scoring.base.ScoringEngine`
|
||||||
|
class
|
||||||
- Implement its :py:meth:`~.ScoringEngine.get_name` method to return the
|
- Implement its :py:meth:`~.ScoringEngine.get_name` method to return the
|
||||||
**unique** ID of the new scoring engine you want to create. This unique ID
|
**unique** ID of the new scoring engine you want to create. This unique ID
|
||||||
should be the same as the name of :ref:`the entry point we will declare later
|
should be the same as the name of :ref:`the entry point we will declare later
|
||||||
@@ -124,7 +125,8 @@ scoring engine deployed as a web service on external servers:
|
|||||||
Abstract Plugin Class
|
Abstract Plugin Class
|
||||||
=====================
|
=====================
|
||||||
|
|
||||||
Here below is the abstract :py:class:`~.ScoringEngine` class:
|
Here below is the abstract
|
||||||
|
:py:class:`watcher.decision_engine.scoring.base.ScoringEngine` class:
|
||||||
|
|
||||||
.. autoclass:: watcher.decision_engine.scoring.base.ScoringEngine
|
.. autoclass:: watcher.decision_engine.scoring.base.ScoringEngine
|
||||||
:members:
|
:members:
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
.. _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
|
|
||||||
@@ -65,6 +65,14 @@ Audit
|
|||||||
|
|
||||||
.. _audit_template_definition:
|
.. _audit_template_definition:
|
||||||
|
|
||||||
|
Audit Scope
|
||||||
|
===========
|
||||||
|
|
||||||
|
An Audit Scope is a set of audited resources. Audit Scope should be defined
|
||||||
|
in each Audit Template (which contains the Audit settings).
|
||||||
|
|
||||||
|
.. _audit_scope_definition:
|
||||||
|
|
||||||
Audit Template
|
Audit Template
|
||||||
==============
|
==============
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
height="210mm"
|
height="210mm"
|
||||||
id="svg4946"
|
id="svg4946"
|
||||||
version="1.1"
|
version="1.1"
|
||||||
inkscape:version="0.48.4 r9939"
|
inkscape:version="0.91 r13725"
|
||||||
sodipodi:docname="architecture.svg">
|
sodipodi:docname="architecture.svg">
|
||||||
<defs
|
<defs
|
||||||
id="defs4948">
|
id="defs4948">
|
||||||
@@ -325,6 +325,20 @@
|
|||||||
style="fill:#ffffff;fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
|
style="fill:#ffffff;fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
|
||||||
transform="matrix(-0.8,0,0,-0.8,4.8,0)" />
|
transform="matrix(-0.8,0,0,-0.8,4.8,0)" />
|
||||||
</marker>
|
</marker>
|
||||||
|
<marker
|
||||||
|
inkscape:stockid="EmptyTriangleInL"
|
||||||
|
orient="auto"
|
||||||
|
refY="0"
|
||||||
|
refX="0"
|
||||||
|
id="EmptyTriangleInL-5"
|
||||||
|
style="overflow:visible">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path7091-3"
|
||||||
|
d="m 5.77,0 -8.65,5 0,-10 8.65,5 z"
|
||||||
|
style="fill:#ffffff;fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
|
||||||
|
transform="matrix(-0.8,0,0,-0.8,4.8,0)" />
|
||||||
|
</marker>
|
||||||
</defs>
|
</defs>
|
||||||
<sodipodi:namedview
|
<sodipodi:namedview
|
||||||
inkscape:document-units="mm"
|
inkscape:document-units="mm"
|
||||||
@@ -333,17 +347,21 @@
|
|||||||
borderopacity="1.0"
|
borderopacity="1.0"
|
||||||
inkscape:pageopacity="0.0"
|
inkscape:pageopacity="0.0"
|
||||||
inkscape:pageshadow="2"
|
inkscape:pageshadow="2"
|
||||||
inkscape:zoom="4"
|
inkscape:zoom="1.4142136"
|
||||||
inkscape:cx="261.24633"
|
inkscape:cx="261.24633"
|
||||||
inkscape:cy="108.90512"
|
inkscape:cy="108.90512"
|
||||||
inkscape:current-layer="g5356"
|
inkscape:current-layer="g5356"
|
||||||
id="namedview4950"
|
id="namedview4950"
|
||||||
showgrid="false"
|
showgrid="true"
|
||||||
inkscape:window-width="1846"
|
inkscape:window-width="1215"
|
||||||
inkscape:window-height="1053"
|
inkscape:window-height="776"
|
||||||
inkscape:window-x="1911"
|
inkscape:window-x="65"
|
||||||
inkscape:window-y="37"
|
inkscape:window-y="24"
|
||||||
inkscape:window-maximized="1" />
|
inkscape:window-maximized="1">
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid4203" />
|
||||||
|
</sodipodi:namedview>
|
||||||
<metadata
|
<metadata
|
||||||
id="metadata4952">
|
id="metadata4952">
|
||||||
<rdf:RDF>
|
<rdf:RDF>
|
||||||
@@ -410,7 +428,7 @@
|
|||||||
sodipodi:cy="358.94418"
|
sodipodi:cy="358.94418"
|
||||||
sodipodi:rx="70.677063"
|
sodipodi:rx="70.677063"
|
||||||
sodipodi:ry="70.677063"
|
sodipodi:ry="70.677063"
|
||||||
d="m 394.67978,358.94418 c 0,39.03387 -31.6432,70.67707 -70.67706,70.67707 -39.03387,0 -70.67707,-31.6432 -70.67707,-70.67707 0,-39.03386 31.6432,-70.67706 70.67707,-70.67706 39.03386,0 70.67706,31.6432 70.67706,70.67706 z"
|
d="m 394.67978,358.94418 a 70.677063,70.677063 0 0 1 -70.67706,70.67707 70.677063,70.677063 0 0 1 -70.67707,-70.67707 70.677063,70.677063 0 0 1 70.67707,-70.67706 70.677063,70.677063 0 0 1 70.67706,70.67706 z"
|
||||||
transform="matrix(0.36664048,0,0,0.36664048,0.86684619,-80.697844)" />
|
transform="matrix(0.36664048,0,0,0.36664048,0.86684619,-80.697844)" />
|
||||||
<g
|
<g
|
||||||
id="g5262">
|
id="g5262">
|
||||||
@@ -536,29 +554,29 @@
|
|||||||
</g>
|
</g>
|
||||||
<rect
|
<rect
|
||||||
y="754.5235"
|
y="754.5235"
|
||||||
x="225.29219"
|
x="231.29219"
|
||||||
height="42.939091"
|
height="42.939091"
|
||||||
width="118.56741"
|
width="118.56741"
|
||||||
id="rect4267-4-7-7-3-3"
|
id="rect4267-4-7-7-3-3"
|
||||||
style="fill:#ffffff;stroke:#000000;stroke-width:1;stroke-opacity:1;display:inline" />
|
style="display:inline;fill:#ffffff;stroke:#000000;stroke-width:1;stroke-opacity:1" />
|
||||||
<text
|
<text
|
||||||
sodipodi:linespacing="125%"
|
sodipodi:linespacing="125%"
|
||||||
id="text5037-4-6-9-1-4"
|
id="text5037-4-6-9-1-4"
|
||||||
y="783.03412"
|
y="783.03412"
|
||||||
x="284.34656"
|
x="290.34656"
|
||||||
style="font-size:20.86613655px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;display:inline;font-family:Sans"
|
style="font-style:normal;font-weight:normal;font-size:20.86613655px;line-height:125%;font-family:Sans;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;display:inline;fill:#000000;fill-opacity:1;stroke:none"
|
||||||
xml:space="preserve"><tspan
|
xml:space="preserve"><tspan
|
||||||
id="tspan5184-3-5-2-1"
|
id="tspan5184-3-5-2-1"
|
||||||
style="font-size:20px;text-align:center;text-anchor:middle"
|
style="font-size:20px;text-align:center;text-anchor:middle"
|
||||||
y="783.03412"
|
y="783.03412"
|
||||||
x="284.34656"
|
x="290.34656"
|
||||||
sodipodi:role="line">ceilometer</tspan></text>
|
sodipodi:role="line">ceilometer</tspan></text>
|
||||||
<g
|
<g
|
||||||
transform="matrix(1.7775787,0,0,1.7775787,593.58186,304.14299)"
|
transform="matrix(1.7775787,0,0,1.7775787,599.58186,304.14299)"
|
||||||
id="g4982"
|
id="g4982"
|
||||||
style="display:inline">
|
style="display:inline">
|
||||||
<rect
|
<rect
|
||||||
style="fill:#ffffff;stroke:#000000;stroke-width:0.562563;stroke-opacity:1;display:inline"
|
style="display:inline;fill:#ffffff;stroke:#000000;stroke-width:0.562563;stroke-opacity:1"
|
||||||
id="rect4267-4-7-7-3-3-1"
|
id="rect4267-4-7-7-3-3-1"
|
||||||
width="66.701637"
|
width="66.701637"
|
||||||
height="24.155943"
|
height="24.155943"
|
||||||
@@ -566,7 +584,7 @@
|
|||||||
y="253.36743" />
|
y="253.36743" />
|
||||||
<text
|
<text
|
||||||
xml:space="preserve"
|
xml:space="preserve"
|
||||||
style="font-size:11.73851585px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;display:inline;font-family:Sans"
|
style="font-style:normal;font-weight:normal;font-size:11.73851585px;line-height:125%;font-family:Sans;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;display:inline;fill:#000000;fill-opacity:1;stroke:none"
|
||||||
x="-100.86156"
|
x="-100.86156"
|
||||||
y="268.36258"
|
y="268.36258"
|
||||||
id="text5037-4-6-9-1-4-3"
|
id="text5037-4-6-9-1-4-3"
|
||||||
@@ -620,12 +638,6 @@
|
|||||||
style="font-size:11.2512598px;text-align:center;text-anchor:middle"
|
style="font-size:11.2512598px;text-align:center;text-anchor:middle"
|
||||||
id="tspan5022">drivers</tspan></text>
|
id="tspan5022">drivers</tspan></text>
|
||||||
</g>
|
</g>
|
||||||
<path
|
|
||||||
sodipodi:nodetypes="cc"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
id="path5110"
|
|
||||||
d="m 376.75141,726.9703 -57.95106,0"
|
|
||||||
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;display:inline" />
|
|
||||||
<g
|
<g
|
||||||
transform="matrix(1.7775787,0,0,1.7775787,704.59677,780.35846)"
|
transform="matrix(1.7775787,0,0,1.7775787,704.59677,780.35846)"
|
||||||
id="g5267"
|
id="g5267"
|
||||||
@@ -1052,8 +1064,8 @@
|
|||||||
sodipodi:nodetypes="cc"
|
sodipodi:nodetypes="cc"
|
||||||
inkscape:connector-curvature="0"
|
inkscape:connector-curvature="0"
|
||||||
id="path5112-89"
|
id="path5112-89"
|
||||||
d="m 328.87061,655.34778 0,71.7093"
|
d="m 324.87061,655.34778 0,71.7093"
|
||||||
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;display:inline" />
|
style="display:inline;fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||||
<path
|
<path
|
||||||
sodipodi:nodetypes="cc"
|
sodipodi:nodetypes="cc"
|
||||||
inkscape:connector-curvature="0"
|
inkscape:connector-curvature="0"
|
||||||
@@ -1178,7 +1190,7 @@
|
|||||||
sodipodi:role="line">extensions</tspan></text>
|
sodipodi:role="line">extensions</tspan></text>
|
||||||
<path
|
<path
|
||||||
transform="translate(68.397849,130.15566)"
|
transform="translate(68.397849,130.15566)"
|
||||||
d="m 519.41589,367.58817 c 0,4.01434 -2.01125,7.2686 -4.49224,7.2686 -2.481,0 -4.49225,-3.25426 -4.49225,-7.2686 0,-4.01434 2.01125,-7.26861 4.49225,-7.26861 2.48099,0 4.49224,3.25427 4.49224,7.26861 z"
|
d="m 519.41589,367.58817 a 4.4922457,7.2686057 0 0 1 -4.49224,7.2686 4.4922457,7.2686057 0 0 1 -4.49225,-7.2686 4.4922457,7.2686057 0 0 1 4.49225,-7.26861 4.4922457,7.2686057 0 0 1 4.49224,7.26861 z"
|
||||||
sodipodi:ry="7.2686057"
|
sodipodi:ry="7.2686057"
|
||||||
sodipodi:rx="4.4922457"
|
sodipodi:rx="4.4922457"
|
||||||
sodipodi:cy="367.58817"
|
sodipodi:cy="367.58817"
|
||||||
@@ -1188,7 +1200,7 @@
|
|||||||
sodipodi:type="arc" />
|
sodipodi:type="arc" />
|
||||||
<path
|
<path
|
||||||
transform="matrix(0.36538461,0,0,0.36538461,397.30905,368.17877)"
|
transform="matrix(0.36538461,0,0,0.36538461,397.30905,368.17877)"
|
||||||
d="m 521.38126,368.43045 c 0,4.03162 -3.26828,7.2999 -7.2999,7.2999 -4.03162,0 -7.2999,-3.26828 -7.2999,-7.2999 0,-4.03162 3.26828,-7.2999 7.2999,-7.2999 4.03162,0 7.2999,3.26828 7.2999,7.2999 z"
|
d="m 521.38126,368.43045 a 7.2998991,7.2998991 0 0 1 -7.2999,7.2999 7.2998991,7.2998991 0 0 1 -7.2999,-7.2999 7.2998991,7.2998991 0 0 1 7.2999,-7.2999 7.2998991,7.2998991 0 0 1 7.2999,7.2999 z"
|
||||||
sodipodi:ry="7.2998991"
|
sodipodi:ry="7.2998991"
|
||||||
sodipodi:rx="7.2998991"
|
sodipodi:rx="7.2998991"
|
||||||
sodipodi:cy="368.43045"
|
sodipodi:cy="368.43045"
|
||||||
@@ -1198,7 +1210,7 @@
|
|||||||
sodipodi:type="arc" />
|
sodipodi:type="arc" />
|
||||||
<path
|
<path
|
||||||
transform="matrix(0.36538461,0,0,0.36538461,-27.122619,99.964199)"
|
transform="matrix(0.36538461,0,0,0.36538461,-27.122619,99.964199)"
|
||||||
d="m 521.38126,368.43045 c 0,4.03162 -3.26828,7.2999 -7.2999,7.2999 -4.03162,0 -7.2999,-3.26828 -7.2999,-7.2999 0,-4.03162 3.26828,-7.2999 7.2999,-7.2999 4.03162,0 7.2999,3.26828 7.2999,7.2999 z"
|
d="m 521.38126,368.43045 a 7.2998991,7.2998991 0 0 1 -7.2999,7.2999 7.2998991,7.2998991 0 0 1 -7.2999,-7.2999 7.2998991,7.2998991 0 0 1 7.2999,-7.2999 7.2998991,7.2998991 0 0 1 7.2999,7.2999 z"
|
||||||
sodipodi:ry="7.2998991"
|
sodipodi:ry="7.2998991"
|
||||||
sodipodi:rx="7.2998991"
|
sodipodi:rx="7.2998991"
|
||||||
sodipodi:cy="368.43045"
|
sodipodi:cy="368.43045"
|
||||||
@@ -1214,7 +1226,7 @@
|
|||||||
style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:3.99999997, 1.99999998, 0.99999999, 1.99999998;stroke-dashoffset:0;marker-start:none;display:inline" />
|
style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:3.99999997, 1.99999998, 0.99999999, 1.99999998;stroke-dashoffset:0;marker-start:none;display:inline" />
|
||||||
<path
|
<path
|
||||||
transform="matrix(0.36538461,0,0,0.36538461,396.74123,268.71562)"
|
transform="matrix(0.36538461,0,0,0.36538461,396.74123,268.71562)"
|
||||||
d="m 521.38126,368.43045 c 0,4.03162 -3.26828,7.2999 -7.2999,7.2999 -4.03162,0 -7.2999,-3.26828 -7.2999,-7.2999 0,-4.03162 3.26828,-7.2999 7.2999,-7.2999 4.03162,0 7.2999,3.26828 7.2999,7.2999 z"
|
d="m 521.38126,368.43045 a 7.2998991,7.2998991 0 0 1 -7.2999,7.2999 7.2998991,7.2998991 0 0 1 -7.2999,-7.2999 7.2998991,7.2998991 0 0 1 7.2999,-7.2999 7.2998991,7.2998991 0 0 1 7.2999,7.2999 z"
|
||||||
sodipodi:ry="7.2998991"
|
sodipodi:ry="7.2998991"
|
||||||
sodipodi:rx="7.2998991"
|
sodipodi:rx="7.2998991"
|
||||||
sodipodi:cy="368.43045"
|
sodipodi:cy="368.43045"
|
||||||
@@ -1224,7 +1236,7 @@
|
|||||||
sodipodi:type="arc" />
|
sodipodi:type="arc" />
|
||||||
<path
|
<path
|
||||||
transform="matrix(0.36538461,0,0,0.36538461,428.69747,559.36511)"
|
transform="matrix(0.36538461,0,0,0.36538461,428.69747,559.36511)"
|
||||||
d="m 521.38126,368.43045 c 0,4.03162 -3.26828,7.2999 -7.2999,7.2999 -4.03162,0 -7.2999,-3.26828 -7.2999,-7.2999 0,-4.03162 3.26828,-7.2999 7.2999,-7.2999 4.03162,0 7.2999,3.26828 7.2999,7.2999 z"
|
d="m 521.38126,368.43045 a 7.2998991,7.2998991 0 0 1 -7.2999,7.2999 7.2998991,7.2998991 0 0 1 -7.2999,-7.2999 7.2998991,7.2998991 0 0 1 7.2999,-7.2999 7.2998991,7.2998991 0 0 1 7.2999,7.2999 z"
|
||||||
sodipodi:ry="7.2998991"
|
sodipodi:ry="7.2998991"
|
||||||
sodipodi:rx="7.2998991"
|
sodipodi:rx="7.2998991"
|
||||||
sodipodi:cy="368.43045"
|
sodipodi:cy="368.43045"
|
||||||
@@ -1234,7 +1246,7 @@
|
|||||||
sodipodi:type="arc" />
|
sodipodi:type="arc" />
|
||||||
<path
|
<path
|
||||||
transform="matrix(0.36538461,0,0,0.36538461,531.88959,559.36511)"
|
transform="matrix(0.36538461,0,0,0.36538461,531.88959,559.36511)"
|
||||||
d="m 521.38126,368.43045 c 0,4.03162 -3.26828,7.2999 -7.2999,7.2999 -4.03162,0 -7.2999,-3.26828 -7.2999,-7.2999 0,-4.03162 3.26828,-7.2999 7.2999,-7.2999 4.03162,0 7.2999,3.26828 7.2999,7.2999 z"
|
d="m 521.38126,368.43045 a 7.2998991,7.2998991 0 0 1 -7.2999,7.2999 7.2998991,7.2998991 0 0 1 -7.2999,-7.2999 7.2998991,7.2998991 0 0 1 7.2999,-7.2999 7.2998991,7.2998991 0 0 1 7.2999,7.2999 z"
|
||||||
sodipodi:ry="7.2998991"
|
sodipodi:ry="7.2998991"
|
||||||
sodipodi:rx="7.2998991"
|
sodipodi:rx="7.2998991"
|
||||||
sodipodi:cy="368.43045"
|
sodipodi:cy="368.43045"
|
||||||
@@ -1250,7 +1262,7 @@
|
|||||||
style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:1.99999998, 1.99999998;stroke-dashoffset:0;marker-start:none;display:inline" />
|
style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:1.99999998, 1.99999998;stroke-dashoffset:0;marker-start:none;display:inline" />
|
||||||
<path
|
<path
|
||||||
transform="matrix(0.36538461,0,0,0.36538461,748.91653,525.25993)"
|
transform="matrix(0.36538461,0,0,0.36538461,748.91653,525.25993)"
|
||||||
d="m 521.38126,368.43045 c 0,4.03162 -3.26828,7.2999 -7.2999,7.2999 -4.03162,0 -7.2999,-3.26828 -7.2999,-7.2999 0,-4.03162 3.26828,-7.2999 7.2999,-7.2999 4.03162,0 7.2999,3.26828 7.2999,7.2999 z"
|
d="m 521.38126,368.43045 a 7.2998991,7.2998991 0 0 1 -7.2999,7.2999 7.2998991,7.2998991 0 0 1 -7.2999,-7.2999 7.2998991,7.2998991 0 0 1 7.2999,-7.2999 7.2998991,7.2998991 0 0 1 7.2999,7.2999 z"
|
||||||
sodipodi:ry="7.2998991"
|
sodipodi:ry="7.2998991"
|
||||||
sodipodi:rx="7.2998991"
|
sodipodi:rx="7.2998991"
|
||||||
sodipodi:cy="368.43045"
|
sodipodi:cy="368.43045"
|
||||||
@@ -1260,7 +1272,7 @@
|
|||||||
sodipodi:type="arc" />
|
sodipodi:type="arc" />
|
||||||
<path
|
<path
|
||||||
transform="matrix(0.36538461,0,0,0.36538461,748.91653,495.84628)"
|
transform="matrix(0.36538461,0,0,0.36538461,748.91653,495.84628)"
|
||||||
d="m 521.38126,368.43045 c 0,4.03162 -3.26828,7.2999 -7.2999,7.2999 -4.03162,0 -7.2999,-3.26828 -7.2999,-7.2999 0,-4.03162 3.26828,-7.2999 7.2999,-7.2999 4.03162,0 7.2999,3.26828 7.2999,7.2999 z"
|
d="m 521.38126,368.43045 a 7.2998991,7.2998991 0 0 1 -7.2999,7.2999 7.2998991,7.2998991 0 0 1 -7.2999,-7.2999 7.2998991,7.2998991 0 0 1 7.2999,-7.2999 7.2998991,7.2998991 0 0 1 7.2999,7.2999 z"
|
||||||
sodipodi:ry="7.2998991"
|
sodipodi:ry="7.2998991"
|
||||||
sodipodi:rx="7.2998991"
|
sodipodi:rx="7.2998991"
|
||||||
sodipodi:cy="368.43045"
|
sodipodi:cy="368.43045"
|
||||||
@@ -1313,7 +1325,7 @@
|
|||||||
</g>
|
</g>
|
||||||
<path
|
<path
|
||||||
transform="matrix(0.36538461,0,0,0.36538461,396.74122,268.71562)"
|
transform="matrix(0.36538461,0,0,0.36538461,396.74122,268.71562)"
|
||||||
d="m 521.38126,368.43045 c 0,4.03162 -3.26828,7.2999 -7.2999,7.2999 -4.03162,0 -7.2999,-3.26828 -7.2999,-7.2999 0,-4.03162 3.26828,-7.2999 7.2999,-7.2999 4.03162,0 7.2999,3.26828 7.2999,7.2999 z"
|
d="m 521.38126,368.43045 a 7.2998991,7.2998991 0 0 1 -7.2999,7.2999 7.2998991,7.2998991 0 0 1 -7.2999,-7.2999 7.2998991,7.2998991 0 0 1 7.2999,-7.2999 7.2998991,7.2998991 0 0 1 7.2999,7.2999 z"
|
||||||
sodipodi:ry="7.2998991"
|
sodipodi:ry="7.2998991"
|
||||||
sodipodi:rx="7.2998991"
|
sodipodi:rx="7.2998991"
|
||||||
sodipodi:cy="368.43045"
|
sodipodi:cy="368.43045"
|
||||||
@@ -1323,7 +1335,7 @@
|
|||||||
sodipodi:type="arc" />
|
sodipodi:type="arc" />
|
||||||
<path
|
<path
|
||||||
transform="matrix(0.36538461,0,0,0.36538461,-27.122619,74.87915)"
|
transform="matrix(0.36538461,0,0,0.36538461,-27.122619,74.87915)"
|
||||||
d="m 521.38126,368.43045 c 0,4.03162 -3.26828,7.2999 -7.2999,7.2999 -4.03162,0 -7.2999,-3.26828 -7.2999,-7.2999 0,-4.03162 3.26828,-7.2999 7.2999,-7.2999 4.03162,0 7.2999,3.26828 7.2999,7.2999 z"
|
d="m 521.38126,368.43045 a 7.2998991,7.2998991 0 0 1 -7.2999,7.2999 7.2998991,7.2998991 0 0 1 -7.2999,-7.2999 7.2998991,7.2998991 0 0 1 7.2999,-7.2999 7.2998991,7.2998991 0 0 1 7.2999,7.2999 z"
|
||||||
sodipodi:ry="7.2998991"
|
sodipodi:ry="7.2998991"
|
||||||
sodipodi:rx="7.2998991"
|
sodipodi:rx="7.2998991"
|
||||||
sodipodi:cy="368.43045"
|
sodipodi:cy="368.43045"
|
||||||
@@ -1339,7 +1351,7 @@
|
|||||||
style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:3.99999997, 1.99999998, 0.99999999, 1.99999998;stroke-dashoffset:0;marker-start:url(#TriangleInL);display:inline" />
|
style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:3.99999997, 1.99999998, 0.99999999, 1.99999998;stroke-dashoffset:0;marker-start:url(#TriangleInL);display:inline" />
|
||||||
<path
|
<path
|
||||||
transform="matrix(0.36538461,0,0,0.36538461,422.9095,223.59883)"
|
transform="matrix(0.36538461,0,0,0.36538461,422.9095,223.59883)"
|
||||||
d="m 521.38126,368.43045 c 0,4.03162 -3.26828,7.2999 -7.2999,7.2999 -4.03162,0 -7.2999,-3.26828 -7.2999,-7.2999 0,-4.03162 3.26828,-7.2999 7.2999,-7.2999 4.03162,0 7.2999,3.26828 7.2999,7.2999 z"
|
d="m 521.38126,368.43045 a 7.2998991,7.2998991 0 0 1 -7.2999,7.2999 7.2998991,7.2998991 0 0 1 -7.2999,-7.2999 7.2998991,7.2998991 0 0 1 7.2999,-7.2999 7.2998991,7.2998991 0 0 1 7.2999,7.2999 z"
|
||||||
sodipodi:ry="7.2998991"
|
sodipodi:ry="7.2998991"
|
||||||
sodipodi:rx="7.2998991"
|
sodipodi:rx="7.2998991"
|
||||||
sodipodi:cy="368.43045"
|
sodipodi:cy="368.43045"
|
||||||
@@ -1351,15 +1363,45 @@
|
|||||||
sodipodi:nodetypes="cc"
|
sodipodi:nodetypes="cc"
|
||||||
inkscape:connector-curvature="0"
|
inkscape:connector-curvature="0"
|
||||||
id="path3284-4-2-3-40"
|
id="path3284-4-2-3-40"
|
||||||
d="m 319.30136,753.47677 0,-27.00021"
|
d="m 325.23661,753.47677 0,-27.00021"
|
||||||
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#EmptyTriangleInL);display:inline" />
|
style="display:inline;fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#EmptyTriangleInL)" />
|
||||||
<path
|
<path
|
||||||
sodipodi:nodetypes="cc"
|
sodipodi:nodetypes="cc"
|
||||||
inkscape:connector-curvature="0"
|
inkscape:connector-curvature="0"
|
||||||
id="path3284-4-2-3-8"
|
id="path3284-4-2-3-8"
|
||||||
d="m 376.2546,753.65849 0,-27.00021"
|
d="m 414.2546,753.65849 0,-27.00021"
|
||||||
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#EmptyTriangleInL);display:inline" />
|
style="display:inline;fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#EmptyTriangleInL)" />
|
||||||
|
<rect
|
||||||
|
y="754.5235"
|
||||||
|
x="102.92204"
|
||||||
|
height="42.939091"
|
||||||
|
width="118.56741"
|
||||||
|
id="rect4267-4-7-7-3-3-3"
|
||||||
|
style="display:inline;fill:#ffffff;stroke:#000000;stroke-width:1;stroke-opacity:1" />
|
||||||
|
<text
|
||||||
|
sodipodi:linespacing="125%"
|
||||||
|
id="text5037-4-6-9-1-4-6"
|
||||||
|
y="783.03412"
|
||||||
|
x="160.34656"
|
||||||
|
style="font-style:normal;font-weight:normal;font-size:20.86613655px;line-height:125%;font-family:Sans;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;display:inline;fill:#000000;fill-opacity:1;stroke:none"
|
||||||
|
xml:space="preserve"><tspan
|
||||||
|
id="tspan5184-3-5-2-1-7"
|
||||||
|
style="font-size:20px;text-align:center;text-anchor:middle"
|
||||||
|
y="783.03412"
|
||||||
|
x="160.34656"
|
||||||
|
sodipodi:role="line">gnocchi</tspan></text>
|
||||||
|
<path
|
||||||
|
sodipodi:nodetypes="cc"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path3284-4-2-3-40-5"
|
||||||
|
d="m 191.30136,753.47677 0,-27.00021"
|
||||||
|
style="display:inline;fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#EmptyTriangleInL-5)" />
|
||||||
|
<path
|
||||||
|
sodipodi:nodetypes="cc"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path5110-9-6"
|
||||||
|
d="m 192.18905,726.66568 221.85496,0"
|
||||||
|
style="display:inline;fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 60 KiB |
@@ -43,7 +43,7 @@ Introduction
|
|||||||
|
|
||||||
glossary
|
glossary
|
||||||
architecture
|
architecture
|
||||||
dev/contributing
|
contributor/contributing
|
||||||
|
|
||||||
|
|
||||||
Getting Started
|
Getting Started
|
||||||
@@ -52,14 +52,7 @@ Getting Started
|
|||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
|
||||||
dev/environment
|
contributor/index
|
||||||
dev/devstack
|
|
||||||
deploy/configuration
|
|
||||||
deploy/conf-files
|
|
||||||
deploy/apache-mod-wsgi
|
|
||||||
dev/notifications
|
|
||||||
dev/testing
|
|
||||||
dev/rally_link
|
|
||||||
|
|
||||||
API References
|
API References
|
||||||
--------------
|
--------------
|
||||||
@@ -67,7 +60,7 @@ API References
|
|||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
|
||||||
webapi/v1
|
api/index
|
||||||
|
|
||||||
Plugins
|
Plugins
|
||||||
-------
|
-------
|
||||||
@@ -75,30 +68,38 @@ Plugins
|
|||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
|
||||||
dev/plugin/base-setup
|
contributor/plugin/index
|
||||||
dev/plugin/goal-plugin
|
|
||||||
dev/plugin/scoring-engine-plugin
|
|
||||||
dev/plugin/strategy-plugin
|
|
||||||
dev/plugin/cdmc-plugin
|
|
||||||
dev/plugin/action-plugin
|
|
||||||
dev/plugin/planner-plugin
|
|
||||||
dev/plugins
|
|
||||||
|
|
||||||
|
Installation
|
||||||
|
============
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
install/index
|
||||||
|
|
||||||
|
Watcher Configuration Options
|
||||||
|
=============================
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
configuration/index
|
||||||
|
|
||||||
Admin Guide
|
Admin Guide
|
||||||
===========
|
===========
|
||||||
|
|
||||||
Introduction
|
.. toctree::
|
||||||
------------
|
:maxdepth: 2
|
||||||
|
|
||||||
|
admin/index
|
||||||
|
|
||||||
|
User Guide
|
||||||
|
==========
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 1
|
:maxdepth: 2
|
||||||
|
|
||||||
deploy/installation
|
user/index
|
||||||
deploy/user-guide
|
|
||||||
deploy/policy
|
|
||||||
deploy/gmr
|
|
||||||
strategies/strategies
|
|
||||||
|
|
||||||
Watcher Manual Pages
|
Watcher Manual Pages
|
||||||
====================
|
====================
|
||||||
@@ -107,7 +108,7 @@ Watcher Manual Pages
|
|||||||
:glob:
|
:glob:
|
||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
|
||||||
man/*
|
man/index
|
||||||
|
|
||||||
.. # NOTE(mriedem): This is the section where we hide things that we don't
|
.. # 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
|
# actually want in the table of contents but sphinx build would fail if
|
||||||
|
|||||||
71
doc/source/install/common_configure.rst
Normal file
71
doc/source/install/common_configure.rst
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
2. Edit the ``/etc/watcher/watcher.conf`` file and complete the following
|
||||||
|
actions:
|
||||||
|
|
||||||
|
* In the ``[database]`` section, configure database access:
|
||||||
|
|
||||||
|
.. code-block:: ini
|
||||||
|
|
||||||
|
[database]
|
||||||
|
...
|
||||||
|
connection = mysql+pymysql://watcher:WATCHER_DBPASS@controller/watcher?charset=utf8
|
||||||
|
|
||||||
|
* In the `[DEFAULT]` section, configure the transport url for RabbitMQ message broker.
|
||||||
|
|
||||||
|
.. code-block:: ini
|
||||||
|
|
||||||
|
[DEFAULT]
|
||||||
|
...
|
||||||
|
control_exchange = watcher
|
||||||
|
transport_url = rabbit://openstack:RABBIT_PASS@controller
|
||||||
|
|
||||||
|
Replace the RABBIT_PASS with the password you chose for OpenStack user in RabbitMQ.
|
||||||
|
|
||||||
|
* In the `[keystone_authtoken]` section, configure Identity service access.
|
||||||
|
|
||||||
|
.. code-block:: ini
|
||||||
|
|
||||||
|
[keystone_authtoken]
|
||||||
|
...
|
||||||
|
auth_uri = http://controller:5000
|
||||||
|
auth_url = http://controller:35357
|
||||||
|
memcached_servers = controller:11211
|
||||||
|
auth_type = password
|
||||||
|
project_domain_name = default
|
||||||
|
user_domain_name = default
|
||||||
|
project_name = service
|
||||||
|
username = watcher
|
||||||
|
password = WATCHER_PASS
|
||||||
|
|
||||||
|
Replace WATCHER_PASS with the password you chose for the watcher user in the Identity service.
|
||||||
|
|
||||||
|
* Watcher interacts with other OpenStack projects via project clients, in order to instantiate these
|
||||||
|
clients, Watcher requests new session from Identity service. In the `[watcher_client_auth]` section,
|
||||||
|
configure the identity service access to interact with other OpenStack project clients.
|
||||||
|
|
||||||
|
.. code-block:: ini
|
||||||
|
|
||||||
|
[watcher_client_auth]
|
||||||
|
...
|
||||||
|
auth_type = password
|
||||||
|
auth_url = http://controller:35357
|
||||||
|
username = watcher
|
||||||
|
password = WATCHER_PASS
|
||||||
|
project_domain_name = default
|
||||||
|
user_domain_name = default
|
||||||
|
project_name = service
|
||||||
|
|
||||||
|
Replace WATCHER_PASS with the password you chose for the watcher user in the Identity service.
|
||||||
|
|
||||||
|
* In the `[oslo_messaging_notifications]` section, configure the messaging driver.
|
||||||
|
|
||||||
|
.. code-block:: ini
|
||||||
|
|
||||||
|
[oslo_messaging_notifications]
|
||||||
|
...
|
||||||
|
driver = messagingv2
|
||||||
|
|
||||||
|
3. Populate watcher database:
|
||||||
|
|
||||||
|
.. code-block:: ini
|
||||||
|
|
||||||
|
su -s /bin/sh -c "watcher-db-manage" watcher
|
||||||
139
doc/source/install/common_prerequisites.rst
Normal file
139
doc/source/install/common_prerequisites.rst
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
Prerequisites
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Before you install and configure the Infrastructure Optimization service,
|
||||||
|
you must create a database, service credentials, and API endpoints.
|
||||||
|
|
||||||
|
1. Create the database, complete these steps:
|
||||||
|
|
||||||
|
* Use the database access client to connect to the database
|
||||||
|
server as the ``root`` user:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ mysql -u root -p
|
||||||
|
|
||||||
|
* Create the ``watcher`` database:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
CREATE DATABASE watcher CHARACTER SET utf8;
|
||||||
|
|
||||||
|
* Grant proper access to the ``watcher`` database:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
GRANT ALL PRIVILEGES ON watcher.* TO 'watcher'@'localhost' \
|
||||||
|
IDENTIFIED BY 'WATCHER_DBPASS';
|
||||||
|
GRANT ALL PRIVILEGES ON watcher.* TO 'watcher'@'%' \
|
||||||
|
IDENTIFIED BY 'WATCHER_DBPASS';
|
||||||
|
|
||||||
|
Replace ``WATCHER_DBPASS`` with a suitable password.
|
||||||
|
|
||||||
|
* Exit the database access client.
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
exit;
|
||||||
|
|
||||||
|
2. Source the ``admin`` credentials to gain access to
|
||||||
|
admin-only CLI commands:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ . admin-openrc
|
||||||
|
|
||||||
|
3. To create the service credentials, complete these steps:
|
||||||
|
|
||||||
|
* Create the ``watcher`` user:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ openstack user create --domain default --password-prompt watcher
|
||||||
|
User Password:
|
||||||
|
Repeat User Password:
|
||||||
|
+---------------------+----------------------------------+
|
||||||
|
| Field | Value |
|
||||||
|
+---------------------+----------------------------------+
|
||||||
|
| domain_id | default |
|
||||||
|
| enabled | True |
|
||||||
|
| id | b18ee38e06034b748141beda8fc8bfad |
|
||||||
|
| name | watcher |
|
||||||
|
| options | {} |
|
||||||
|
| password_expires_at | None |
|
||||||
|
+---------------------+----------------------------------+
|
||||||
|
|
||||||
|
|
||||||
|
* Add the ``admin`` role to the ``watcher`` user:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ openstack role add --project service --user watcher admin
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
This command produces no output.
|
||||||
|
|
||||||
|
* Create the watcher service entities:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ openstack service create --name watcher --description "Infrastructure Optimization" infra-optim
|
||||||
|
+-------------+----------------------------------+
|
||||||
|
| Field | Value |
|
||||||
|
+-------------+----------------------------------+
|
||||||
|
| description | Infrastructure Optimization |
|
||||||
|
| enabled | True |
|
||||||
|
| id | d854f6fff0a64f77bda8003c8dedfada |
|
||||||
|
| name | watcher |
|
||||||
|
| type | infra-optim |
|
||||||
|
+-------------+----------------------------------+
|
||||||
|
|
||||||
|
|
||||||
|
4. Create the Infrastructure Optimization service API endpoints:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ openstack endpoint create --region RegionOne \
|
||||||
|
infra-optim public http://controller:9322
|
||||||
|
+-------------+----------------------------------+
|
||||||
|
| Field | Value |
|
||||||
|
+-------------+----------------------------------+
|
||||||
|
| description | Infrastructure Optimization |
|
||||||
|
| enabled | True |
|
||||||
|
| id | d854f6fff0a64f77bda8003c8dedfada |
|
||||||
|
| name | watcher |
|
||||||
|
| type | infra-optim |
|
||||||
|
+-------------+----------------------------------+
|
||||||
|
|
||||||
|
$ openstack endpoint create --region RegionOne \
|
||||||
|
infra-optim internal http://controller:9322
|
||||||
|
+--------------+----------------------------------+
|
||||||
|
| Field | Value |
|
||||||
|
+--------------+----------------------------------+
|
||||||
|
| enabled | True |
|
||||||
|
| id | 225aef8465ef4df48a341aaaf2b0a390 |
|
||||||
|
| interface | internal |
|
||||||
|
| region | RegionOne |
|
||||||
|
| region_id | RegionOne |
|
||||||
|
| service_id | d854f6fff0a64f77bda8003c8dedfada |
|
||||||
|
| service_name | watcher |
|
||||||
|
| service_type | infra-optim |
|
||||||
|
| url | http://controller:9322 |
|
||||||
|
+--------------+----------------------------------+
|
||||||
|
|
||||||
|
$ openstack endpoint create --region RegionOne \
|
||||||
|
infra-optim admin http://controller:9322
|
||||||
|
+--------------+----------------------------------+
|
||||||
|
| Field | Value |
|
||||||
|
+--------------+----------------------------------+
|
||||||
|
| enabled | True |
|
||||||
|
| id | 375eb5057fb546edbdf3ee4866179672 |
|
||||||
|
| interface | admin |
|
||||||
|
| region | RegionOne |
|
||||||
|
| region_id | RegionOne |
|
||||||
|
| service_id | d854f6fff0a64f77bda8003c8dedfada |
|
||||||
|
| service_name | watcher |
|
||||||
|
| service_type | infra-optim |
|
||||||
|
| url | http://controller:9322 |
|
||||||
|
+--------------+----------------------------------+
|
||||||
27
doc/source/install/get_started.rst
Normal file
27
doc/source/install/get_started.rst
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
============================================
|
||||||
|
Infrastructure Optimization service overview
|
||||||
|
============================================
|
||||||
|
The Infrastructure Optimization service provides flexible and scalable
|
||||||
|
optimization service for multi-tenant OpenStack based clouds.
|
||||||
|
|
||||||
|
The Infrastructure Optimization service consists of the following components:
|
||||||
|
|
||||||
|
``watcher`` command-line client
|
||||||
|
A CLI to communicate with ``watcher-api`` to optimize the cloud.
|
||||||
|
|
||||||
|
``watcher-api`` service
|
||||||
|
An OpenStack-native REST API that accepts and responds to end-user calls
|
||||||
|
by processing them and forwarding to appropriate underlying watcher
|
||||||
|
services via AMQP.
|
||||||
|
|
||||||
|
``watcher-decision-engine`` service
|
||||||
|
It runs audit and return an action plan to achieve optimization goal
|
||||||
|
specified by the end-user in audit.
|
||||||
|
|
||||||
|
``watcher-applier`` service
|
||||||
|
It executes action plan built by watcher-decision-engine. It interacts with
|
||||||
|
other OpenStack components like nova to execute the given action
|
||||||
|
plan.
|
||||||
|
|
||||||
|
``watcher-dashboard``
|
||||||
|
Watcher UI implemented as a plugin for the OpenStack Dashboard.
|
||||||
39
doc/source/install/index.rst
Normal file
39
doc/source/install/index.rst
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
===================================
|
||||||
|
Infrastructure Optimization service
|
||||||
|
===================================
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
get_started.rst
|
||||||
|
install.rst
|
||||||
|
verify.rst
|
||||||
|
next-steps.rst
|
||||||
|
|
||||||
|
The Infrastructure Optimization service (watcher) provides
|
||||||
|
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 also supports a pluggable architecture by which custom
|
||||||
|
optimization algorithms, data metrics and data profilers can be
|
||||||
|
developed and inserted into the Watcher framework.
|
||||||
|
|
||||||
|
Check the documentation for watcher optimization strategies at
|
||||||
|
https://docs.openstack.org/watcher/latest/strategies/index.html
|
||||||
|
|
||||||
|
Check watcher glossary at
|
||||||
|
https://docs.openstack.org/watcher/latest/glossary.html
|
||||||
|
|
||||||
|
|
||||||
|
This chapter assumes a working setup of OpenStack following the
|
||||||
|
`OpenStack Installation Tutorial
|
||||||
|
<https://docs.openstack.org/project-install-guide/ocata/>`_.
|
||||||
35
doc/source/install/install-obs.rst
Normal file
35
doc/source/install/install-obs.rst
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
.. _install-obs:
|
||||||
|
|
||||||
|
|
||||||
|
Install and configure for openSUSE and SUSE Linux Enterprise
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
This section describes how to install and configure the Infrastructure
|
||||||
|
Optimization service for openSUSE Leap 42.1 and
|
||||||
|
SUSE Linux Enterprise Server 12 SP1.
|
||||||
|
|
||||||
|
.. include:: common_prerequisites.rst
|
||||||
|
|
||||||
|
Install and configure components
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
#. Install the packages:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
# zypper --quiet --non-interactive install
|
||||||
|
|
||||||
|
.. include:: common_configure.rst
|
||||||
|
|
||||||
|
|
||||||
|
Finalize installation
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
Start the Infrastructure Optimization services and configure them to start when
|
||||||
|
the system boots:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
# systemctl enable openstack-watcher-api.service
|
||||||
|
|
||||||
|
# systemctl start openstack-watcher-api.service
|
||||||
38
doc/source/install/install-rdo.rst
Normal file
38
doc/source/install/install-rdo.rst
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
.. _install-rdo:
|
||||||
|
|
||||||
|
Install and configure for Red Hat Enterprise Linux and CentOS
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
||||||
|
This section describes how to install and configure the Infrastructure
|
||||||
|
Optimization service for Red Hat Enterprise Linux 7 and CentOS 7.
|
||||||
|
|
||||||
|
.. include:: common_prerequisites.rst
|
||||||
|
|
||||||
|
Install and configure components
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
1. Install the packages:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
# sudo yum install openstack-watcher-api openstack-watcher-applier \
|
||||||
|
openstack-watcher-decision-engine
|
||||||
|
|
||||||
|
.. include:: common_configure.rst
|
||||||
|
|
||||||
|
Finalize installation
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
Start the Infrastructure Optimization services and configure them to start when
|
||||||
|
the system boots:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
# systemctl enable openstack-watcher-api.service \
|
||||||
|
openstack-watcher-decision-engine.service \
|
||||||
|
openstack-watcher-applier.service
|
||||||
|
|
||||||
|
# systemctl start openstack-watcher-api.service \
|
||||||
|
openstack-watcher-decision-engine.service \
|
||||||
|
openstack-watcher-applier.service
|
||||||
34
doc/source/install/install-ubuntu.rst
Normal file
34
doc/source/install/install-ubuntu.rst
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
.. _install-ubuntu:
|
||||||
|
|
||||||
|
Install and configure for Ubuntu
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
This section describes how to install and configure the Infrastructure
|
||||||
|
Optimization service for Ubuntu 14.04 (LTS).
|
||||||
|
|
||||||
|
.. include:: common_prerequisites.rst
|
||||||
|
|
||||||
|
Install and configure components
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
1. Install the packages:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
# apt install watcher-api watcher-decision-engine \
|
||||||
|
watcher-applier
|
||||||
|
|
||||||
|
# apt install python-watcherclient
|
||||||
|
|
||||||
|
.. include:: common_configure.rst
|
||||||
|
|
||||||
|
Finalize installation
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
Restart the Infrastructure Optimization services:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
# service watcher-api restart
|
||||||
|
# service watcher-decision-engine restart
|
||||||
|
# service watcher-applier restart
|
||||||
20
doc/source/install/install.rst
Normal file
20
doc/source/install/install.rst
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
.. _install:
|
||||||
|
|
||||||
|
Install and configure
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
This section describes how to install and configure the Infrastructure
|
||||||
|
Optimization service, code-named watcher, on the controller node.
|
||||||
|
|
||||||
|
This section assumes that you already have a working OpenStack
|
||||||
|
environment with at least the following components installed:
|
||||||
|
Identity Service, Compute Service, Telemetry data collection service.
|
||||||
|
|
||||||
|
Note that installation and configuration vary by distribution.
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
install-obs.rst
|
||||||
|
install-rdo.rst
|
||||||
|
install-ubuntu.rst
|
||||||
9
doc/source/install/next-steps.rst
Normal file
9
doc/source/install/next-steps.rst
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
.. _next-steps:
|
||||||
|
|
||||||
|
Next steps
|
||||||
|
~~~~~~~~~~
|
||||||
|
|
||||||
|
Your OpenStack environment now includes the watcher service.
|
||||||
|
|
||||||
|
To add additional services, see
|
||||||
|
https://docs.openstack.org/project-install-guide/ocata/.
|
||||||
119
doc/source/install/verify.rst
Normal file
119
doc/source/install/verify.rst
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
.. _verify:
|
||||||
|
|
||||||
|
Verify operation
|
||||||
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Verify operation of the Infrastructure Optimization service.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Perform these commands on the controller node.
|
||||||
|
|
||||||
|
1. Source the ``admin`` project credentials to gain access to
|
||||||
|
admin-only CLI commands:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ . admin-openrc
|
||||||
|
|
||||||
|
2. List service components to verify successful launch and registration
|
||||||
|
of each process:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ openstack optimize service list
|
||||||
|
+----+-------------------------+------------+--------+
|
||||||
|
| ID | Name | Host | Status |
|
||||||
|
+----+-------------------------+------------+--------+
|
||||||
|
| 1 | watcher-decision-engine | controller | ACTIVE |
|
||||||
|
| 2 | watcher-applier | controller | ACTIVE |
|
||||||
|
+----+-------------------------+------------+--------+
|
||||||
|
|
||||||
|
3. List goals and strategies:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ openstack optimize goal list
|
||||||
|
+--------------------------------------+----------------------+----------------------+
|
||||||
|
| UUID | Name | Display name |
|
||||||
|
+--------------------------------------+----------------------+----------------------+
|
||||||
|
| a8cd6d1a-008b-4ff0-8dbc-b30493fcc5b9 | dummy | Dummy goal |
|
||||||
|
| 03953f2f-02d0-42b5-9a12-7ba500a54395 | workload_balancing | Workload Balancing |
|
||||||
|
| de0f8714-984b-4d6b-add1-9cad8120fbce | server_consolidation | Server Consolidation |
|
||||||
|
| f056bc80-c6d1-40dc-b002-938ccade9385 | thermal_optimization | Thermal Optimization |
|
||||||
|
| e7062856-892e-4f0f-b84d-b828464b3fd0 | airflow_optimization | Airflow Optimization |
|
||||||
|
| 1f038da9-b36c-449f-9f04-c225bf3eb478 | unclassified | Unclassified |
|
||||||
|
+--------------------------------------+----------------------+----------------------+
|
||||||
|
|
||||||
|
$ openstack optimize strategy list
|
||||||
|
+--------------------------------------+---------------------------+---------------------------------------------+----------------------+
|
||||||
|
| UUID | Name | Display name | Goal |
|
||||||
|
+--------------------------------------+---------------------------+---------------------------------------------+----------------------+
|
||||||
|
| 98ae84c8-7c9b-4cbd-8d9c-4bd7c6b106eb | dummy | Dummy strategy | dummy |
|
||||||
|
| 02a170b6-c72e-479d-95c0-8a4fdd4cc1ef | dummy_with_scorer | Dummy Strategy using sample Scoring Engines | dummy |
|
||||||
|
| 8bf591b8-57e5-4a9e-8c7d-c37bda735a45 | outlet_temperature | Outlet temperature based strategy | thermal_optimization |
|
||||||
|
| 8a0810fb-9d9a-47b9-ab25-e442878abc54 | vm_workload_consolidation | VM Workload Consolidation Strategy | server_consolidation |
|
||||||
|
| 1718859c-3eb5-45cb-9220-9cb79fe42fa5 | basic | Basic offline consolidation | server_consolidation |
|
||||||
|
| b5e7f5f1-4824-42c7-bb52-cf50724f67bf | workload_stabilization | Workload stabilization | workload_balancing |
|
||||||
|
| f853d71e-9286-4df3-9d3e-8eaf0f598e07 | workload_balance | Workload Balance Migration Strategy | workload_balancing |
|
||||||
|
| 58bdfa89-95b5-4630-adf6-fd3af5ff1f75 | uniform_airflow | Uniform airflow migration strategy | airflow_optimization |
|
||||||
|
| 66fde55d-a612-4be9-8cb0-ea63472b420b | dummy_with_resize | Dummy strategy with resize | dummy |
|
||||||
|
+--------------------------------------+---------------------------+---------------------------------------------+----------------------+
|
||||||
|
|
||||||
|
4. Run an action plan by creating an audit with dummy goal:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ openstack optimize audit create --goal dummy
|
||||||
|
+--------------+--------------------------------------+
|
||||||
|
| Field | Value |
|
||||||
|
+--------------+--------------------------------------+
|
||||||
|
| UUID | e94d4826-ad4e-44df-ad93-dff489fde457 |
|
||||||
|
| Created At | 2017-05-23T11:46:58.763394+00:00 |
|
||||||
|
| Updated At | None |
|
||||||
|
| Deleted At | None |
|
||||||
|
| State | PENDING |
|
||||||
|
| Audit Type | ONESHOT |
|
||||||
|
| Parameters | {} |
|
||||||
|
| Interval | None |
|
||||||
|
| Goal | dummy |
|
||||||
|
| Strategy | auto |
|
||||||
|
| Audit Scope | [] |
|
||||||
|
| Auto Trigger | False |
|
||||||
|
+--------------+--------------------------------------+
|
||||||
|
|
||||||
|
$ openstack optimize audit list
|
||||||
|
+--------------------------------------+------------+-----------+-------+----------+--------------+
|
||||||
|
| UUID | Audit Type | State | Goal | Strategy | Auto Trigger |
|
||||||
|
+--------------------------------------+------------+-----------+-------+----------+--------------+
|
||||||
|
| e94d4826-ad4e-44df-ad93-dff489fde457 | ONESHOT | SUCCEEDED | dummy | auto | False |
|
||||||
|
+--------------------------------------+------------+-----------+-------+----------+--------------+
|
||||||
|
|
||||||
|
$ openstack optimize actionplan list
|
||||||
|
+--------------------------------------+--------------------------------------+-------------+------------+-----------------+
|
||||||
|
| UUID | Audit | State | Updated At | Global efficacy |
|
||||||
|
+--------------------------------------+--------------------------------------+-------------+------------+-----------------+
|
||||||
|
| ba9ce6b3-969c-4b8e-bb61-ae24e8630f81 | e94d4826-ad4e-44df-ad93-dff489fde457 | RECOMMENDED | None | None |
|
||||||
|
+--------------------------------------+--------------------------------------+-------------+------------+-----------------+
|
||||||
|
|
||||||
|
$ openstack optimize actionplan start ba9ce6b3-969c-4b8e-bb61-ae24e8630f81
|
||||||
|
+---------------------+--------------------------------------+
|
||||||
|
| Field | Value |
|
||||||
|
+---------------------+--------------------------------------+
|
||||||
|
| UUID | ba9ce6b3-969c-4b8e-bb61-ae24e8630f81 |
|
||||||
|
| Created At | 2017-05-23T11:46:58+00:00 |
|
||||||
|
| Updated At | 2017-05-23T11:53:12+00:00 |
|
||||||
|
| Deleted At | None |
|
||||||
|
| Audit | e94d4826-ad4e-44df-ad93-dff489fde457 |
|
||||||
|
| Strategy | dummy |
|
||||||
|
| State | ONGOING |
|
||||||
|
| Efficacy indicators | [] |
|
||||||
|
| Global efficacy | {} |
|
||||||
|
+---------------------+--------------------------------------+
|
||||||
|
|
||||||
|
$ openstack optimize actionplan list
|
||||||
|
+--------------------------------------+--------------------------------------+-----------+---------------------------+-----------------+
|
||||||
|
| UUID | Audit | State | Updated At | Global efficacy |
|
||||||
|
+--------------------------------------+--------------------------------------+-----------+---------------------------+-----------------+
|
||||||
|
| ba9ce6b3-969c-4b8e-bb61-ae24e8630f81 | e94d4826-ad4e-44df-ad93-dff489fde457 | SUCCEEDED | 2017-05-23T11:53:16+00:00 | None |
|
||||||
|
+--------------------------------------+--------------------------------------+-----------+---------------------------+-----------------+
|
||||||
8
doc/source/man/index.rst
Normal file
8
doc/source/man/index.rst
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
.. toctree::
|
||||||
|
:glob:
|
||||||
|
:maxdepth: 1
|
||||||
|
|
||||||
|
watcher-api
|
||||||
|
watcher-applier
|
||||||
|
watcher-db-manage
|
||||||
|
watcher-decision-engine
|
||||||
@@ -57,7 +57,7 @@ Planner
|
|||||||
|
|
||||||
Default Watcher's planner:
|
Default Watcher's planner:
|
||||||
|
|
||||||
.. watcher-term:: watcher.decision_engine.planner.default.DefaultPlanner
|
.. watcher-term:: watcher.decision_engine.planner.weight.WeightPlanner
|
||||||
|
|
||||||
Configuration
|
Configuration
|
||||||
-------------
|
-------------
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ Planner
|
|||||||
|
|
||||||
Default Watcher's planner:
|
Default Watcher's planner:
|
||||||
|
|
||||||
.. watcher-term:: watcher.decision_engine.planner.default.DefaultPlanner
|
.. watcher-term:: watcher.decision_engine.planner.weight.WeightPlanner
|
||||||
|
|
||||||
Configuration
|
Configuration
|
||||||
-------------
|
-------------
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ Planner
|
|||||||
|
|
||||||
Default Watcher's planner:
|
Default Watcher's planner:
|
||||||
|
|
||||||
.. watcher-term:: watcher.decision_engine.planner.default.DefaultPlanner
|
.. watcher-term:: watcher.decision_engine.planner.weight.WeightPlanner
|
||||||
|
|
||||||
Configuration
|
Configuration
|
||||||
-------------
|
-------------
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ Planner
|
|||||||
|
|
||||||
Default Watcher's planner:
|
Default Watcher's planner:
|
||||||
|
|
||||||
.. watcher-term:: watcher.decision_engine.planner.default.DefaultPlanner
|
.. watcher-term:: watcher.decision_engine.planner.weight.WeightPlanner
|
||||||
|
|
||||||
|
|
||||||
Configuration
|
Configuration
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ Planner
|
|||||||
|
|
||||||
Default Watcher's planner:
|
Default Watcher's planner:
|
||||||
|
|
||||||
.. watcher-term:: watcher.decision_engine.planner.default.DefaultPlanner
|
.. watcher-term:: watcher.decision_engine.planner.weight.WeightPlanner
|
||||||
|
|
||||||
Configuration
|
Configuration
|
||||||
-------------
|
-------------
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ Planner
|
|||||||
|
|
||||||
Default Watcher's planner:
|
Default Watcher's planner:
|
||||||
|
|
||||||
.. watcher-term:: watcher.decision_engine.planner.default.DefaultPlanner
|
.. watcher-term:: watcher.decision_engine.planner.weight.WeightPlanner
|
||||||
|
|
||||||
Configuration
|
Configuration
|
||||||
-------------
|
-------------
|
||||||
|
|||||||
4
doc/source/user/index.rst
Normal file
4
doc/source/user/index.rst
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
user-guide
|
||||||
@@ -11,7 +11,7 @@ Watcher User Guide
|
|||||||
==================
|
==================
|
||||||
|
|
||||||
See the
|
See the
|
||||||
`architecture page <http://docs.openstack.org/developer/watcher/architecture.html>`_
|
`architecture page <https://docs.openstack.org/watcher/latest/architecture.html>`_
|
||||||
for an architectural overview of the different components of Watcher and how
|
for an architectural overview of the different components of Watcher and how
|
||||||
they fit together.
|
they fit together.
|
||||||
|
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Adds feature to cancel an action-plan.
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- Add action for compute node power on/off
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Added cinder cluster data model
|
||||||
@@ -38,7 +38,7 @@ from watcher import version as watcher_version
|
|||||||
# Add any Sphinx extension module names here, as strings. They can be
|
# Add any Sphinx extension module names here, as strings. They can be
|
||||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||||
extensions = ['reno.sphinxext',
|
extensions = ['reno.sphinxext',
|
||||||
'oslosphinx']
|
'openstackdocstheme']
|
||||||
|
|
||||||
# Add any paths that contain templates here, relative to this directory.
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
templates_path = ['_templates']
|
templates_path = ['_templates']
|
||||||
@@ -104,7 +104,7 @@ pygments_style = 'sphinx'
|
|||||||
|
|
||||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||||
# a list of builtin themes.
|
# a list of builtin themes.
|
||||||
html_theme = 'default'
|
html_theme = 'openstackdocs'
|
||||||
|
|
||||||
# Theme options are theme-specific and customize the look and feel of a theme
|
# Theme options are theme-specific and customize the look and feel of a theme
|
||||||
# further. For a list of options available for each theme, see the
|
# further. For a list of options available for each theme, see the
|
||||||
|
|||||||
@@ -5,20 +5,22 @@
|
|||||||
apscheduler # MIT License
|
apscheduler # MIT License
|
||||||
enum34;python_version=='2.7' or python_version=='2.6' or python_version=='3.3' # BSD
|
enum34;python_version=='2.7' or python_version=='2.6' or python_version=='3.3' # BSD
|
||||||
jsonpatch>=1.1 # BSD
|
jsonpatch>=1.1 # BSD
|
||||||
keystoneauth1>=2.18.0 # Apache-2.0
|
keystoneauth1>=3.0.1 # Apache-2.0
|
||||||
|
jsonschema!=2.5.0,<3.0.0,>=2.0.0 # MIT
|
||||||
keystonemiddleware>=4.12.0 # Apache-2.0
|
keystonemiddleware>=4.12.0 # Apache-2.0
|
||||||
lxml!=3.7.0,>=2.3 # BSD
|
lxml!=3.7.0,>=2.3 # BSD
|
||||||
|
croniter>=0.3.4 # MIT License
|
||||||
oslo.concurrency>=3.8.0 # Apache-2.0
|
oslo.concurrency>=3.8.0 # Apache-2.0
|
||||||
oslo.cache>=1.5.0 # Apache-2.0
|
oslo.cache>=1.5.0 # Apache-2.0
|
||||||
oslo.config>=3.22.0 # Apache-2.0
|
oslo.config!=4.3.0,!=4.4.0,>=4.0.0 # Apache-2.0
|
||||||
oslo.context>=2.12.0 # Apache-2.0
|
oslo.context>=2.14.0 # Apache-2.0
|
||||||
oslo.db>=4.19.0 # Apache-2.0
|
oslo.db>=4.24.0 # Apache-2.0
|
||||||
oslo.i18n>=2.1.0 # Apache-2.0
|
oslo.i18n!=3.15.2,>=2.1.0 # Apache-2.0
|
||||||
oslo.log>=3.22.0 # Apache-2.0
|
oslo.log>=3.22.0 # Apache-2.0
|
||||||
oslo.messaging>=5.19.0 # Apache-2.0
|
oslo.messaging!=5.25.0,>=5.24.2 # Apache-2.0
|
||||||
oslo.policy>=1.17.0 # Apache-2.0
|
oslo.policy>=1.23.0 # Apache-2.0
|
||||||
oslo.reports>=0.6.0 # Apache-2.0
|
oslo.reports>=0.6.0 # Apache-2.0
|
||||||
oslo.serialization>=1.10.0 # Apache-2.0
|
oslo.serialization!=2.19.1,>=1.10.0 # Apache-2.0
|
||||||
oslo.service>=1.10.0 # Apache-2.0
|
oslo.service>=1.10.0 # Apache-2.0
|
||||||
oslo.utils>=3.20.0 # Apache-2.0
|
oslo.utils>=3.20.0 # Apache-2.0
|
||||||
oslo.versionedobjects>=1.17.0 # Apache-2.0
|
oslo.versionedobjects>=1.17.0 # Apache-2.0
|
||||||
@@ -29,13 +31,14 @@ PrettyTable<0.8,>=0.7.1 # BSD
|
|||||||
voluptuous>=0.8.9 # BSD License
|
voluptuous>=0.8.9 # BSD License
|
||||||
gnocchiclient>=2.7.0 # Apache-2.0
|
gnocchiclient>=2.7.0 # Apache-2.0
|
||||||
python-ceilometerclient>=2.5.0 # Apache-2.0
|
python-ceilometerclient>=2.5.0 # Apache-2.0
|
||||||
python-cinderclient>=2.0.1 # Apache-2.0
|
python-cinderclient>=3.0.0 # Apache-2.0
|
||||||
python-glanceclient>=2.5.0 # Apache-2.0
|
python-glanceclient>=2.7.0 # Apache-2.0
|
||||||
python-keystoneclient>=3.8.0 # Apache-2.0
|
python-keystoneclient>=3.8.0 # Apache-2.0
|
||||||
python-monascaclient>=1.1.0 # Apache-2.0
|
python-monascaclient>=1.1.0 # Apache-2.0
|
||||||
python-neutronclient>=5.1.0 # Apache-2.0
|
python-neutronclient>=6.3.0 # Apache-2.0
|
||||||
python-novaclient>=7.1.0 # Apache-2.0
|
python-novaclient>=9.0.0 # Apache-2.0
|
||||||
python-openstackclient>=3.3.0 # Apache-2.0
|
python-openstackclient!=3.10.0,>=3.3.0 # Apache-2.0
|
||||||
|
python-ironicclient>=1.14.0 # Apache-2.0
|
||||||
six>=1.9.0 # MIT
|
six>=1.9.0 # MIT
|
||||||
SQLAlchemy!=1.1.5,!=1.1.6,!=1.1.7,!=1.1.8,>=1.0.10 # MIT
|
SQLAlchemy!=1.1.5,!=1.1.6,!=1.1.7,!=1.1.8,>=1.0.10 # MIT
|
||||||
stevedore>=1.20.0 # Apache-2.0
|
stevedore>=1.20.0 # Apache-2.0
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ description-file =
|
|||||||
README.rst
|
README.rst
|
||||||
author = OpenStack
|
author = OpenStack
|
||||||
author-email = openstack-dev@lists.openstack.org
|
author-email = openstack-dev@lists.openstack.org
|
||||||
home-page = http://docs.openstack.org/developer/watcher/
|
home-page = https://docs.openstack.org/watcher/latest/
|
||||||
classifier =
|
classifier =
|
||||||
Environment :: OpenStack
|
Environment :: OpenStack
|
||||||
Intended Audience :: Information Technology
|
Intended Audience :: Information Technology
|
||||||
@@ -53,6 +53,7 @@ watcher_goals =
|
|||||||
thermal_optimization = watcher.decision_engine.goal.goals:ThermalOptimization
|
thermal_optimization = watcher.decision_engine.goal.goals:ThermalOptimization
|
||||||
workload_balancing = watcher.decision_engine.goal.goals:WorkloadBalancing
|
workload_balancing = watcher.decision_engine.goal.goals:WorkloadBalancing
|
||||||
airflow_optimization = watcher.decision_engine.goal.goals:AirflowOptimization
|
airflow_optimization = watcher.decision_engine.goal.goals:AirflowOptimization
|
||||||
|
noisy_neighbor = watcher.decision_engine.goal.goals:NoisyNeighborOptimization
|
||||||
|
|
||||||
watcher_scoring_engines =
|
watcher_scoring_engines =
|
||||||
dummy_scorer = watcher.decision_engine.scoring.dummy_scorer:DummyScorer
|
dummy_scorer = watcher.decision_engine.scoring.dummy_scorer:DummyScorer
|
||||||
@@ -70,6 +71,7 @@ watcher_strategies =
|
|||||||
workload_stabilization = watcher.decision_engine.strategy.strategies.workload_stabilization:WorkloadStabilization
|
workload_stabilization = watcher.decision_engine.strategy.strategies.workload_stabilization:WorkloadStabilization
|
||||||
workload_balance = watcher.decision_engine.strategy.strategies.workload_balance:WorkloadBalance
|
workload_balance = watcher.decision_engine.strategy.strategies.workload_balance:WorkloadBalance
|
||||||
uniform_airflow = watcher.decision_engine.strategy.strategies.uniform_airflow:UniformAirflow
|
uniform_airflow = watcher.decision_engine.strategy.strategies.uniform_airflow:UniformAirflow
|
||||||
|
noisy_neighbor = watcher.decision_engine.strategy.strategies.noisy_neighbor:NoisyNeighbor
|
||||||
|
|
||||||
watcher_actions =
|
watcher_actions =
|
||||||
migrate = watcher.applier.actions.migration:Migrate
|
migrate = watcher.applier.actions.migration:Migrate
|
||||||
@@ -77,6 +79,7 @@ watcher_actions =
|
|||||||
sleep = watcher.applier.actions.sleep:Sleep
|
sleep = watcher.applier.actions.sleep:Sleep
|
||||||
change_nova_service_state = watcher.applier.actions.change_nova_service_state:ChangeNovaServiceState
|
change_nova_service_state = watcher.applier.actions.change_nova_service_state:ChangeNovaServiceState
|
||||||
resize = watcher.applier.actions.resize:Resize
|
resize = watcher.applier.actions.resize:Resize
|
||||||
|
change_node_power_state = watcher.applier.actions.change_node_power_state:ChangeNodePowerState
|
||||||
|
|
||||||
watcher_workflow_engines =
|
watcher_workflow_engines =
|
||||||
taskflow = watcher.applier.workflow_engine.default:DefaultWorkFlowEngine
|
taskflow = watcher.applier.workflow_engine.default:DefaultWorkFlowEngine
|
||||||
@@ -87,6 +90,8 @@ watcher_planners =
|
|||||||
|
|
||||||
watcher_cluster_data_model_collectors =
|
watcher_cluster_data_model_collectors =
|
||||||
compute = watcher.decision_engine.model.collector.nova:NovaClusterDataModelCollector
|
compute = watcher.decision_engine.model.collector.nova:NovaClusterDataModelCollector
|
||||||
|
storage = watcher.decision_engine.model.collector.cinder:CinderClusterDataModelCollector
|
||||||
|
|
||||||
|
|
||||||
[pbr]
|
[pbr]
|
||||||
warnerrors = true
|
warnerrors = true
|
||||||
@@ -104,6 +109,7 @@ source-dir = doc/source
|
|||||||
build-dir = doc/build
|
build-dir = doc/build
|
||||||
fresh_env = 1
|
fresh_env = 1
|
||||||
all_files = 1
|
all_files = 1
|
||||||
|
warning-is-error = 1
|
||||||
|
|
||||||
[upload_sphinx]
|
[upload_sphinx]
|
||||||
upload-dir = doc/build/html
|
upload-dir = doc/build/html
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# of appearance. Changing the order has an impact on the overall integration
|
# of appearance. Changing the order has an impact on the overall integration
|
||||||
# process, which may cause wedges in the gate later.
|
# process, which may cause wedges in the gate later.
|
||||||
|
|
||||||
coverage>=4.0 # Apache-2.0
|
coverage!=4.4,>=4.0 # Apache-2.0
|
||||||
doc8 # Apache-2.0
|
doc8 # Apache-2.0
|
||||||
freezegun>=0.3.6 # Apache-2.0
|
freezegun>=0.3.6 # Apache-2.0
|
||||||
hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0
|
hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0
|
||||||
@@ -15,12 +15,13 @@ testscenarios>=0.4 # Apache-2.0/BSD
|
|||||||
testtools>=1.4.0 # MIT
|
testtools>=1.4.0 # MIT
|
||||||
|
|
||||||
# Doc requirements
|
# Doc requirements
|
||||||
oslosphinx>=4.7.0 # Apache-2.0
|
openstackdocstheme>=1.11.0 # Apache-2.0
|
||||||
sphinx>=1.5.1 # BSD
|
sphinx>=1.6.2 # BSD
|
||||||
sphinxcontrib-pecanwsme>=0.8 # Apache-2.0
|
sphinxcontrib-pecanwsme>=0.8 # Apache-2.0
|
||||||
|
|
||||||
|
|
||||||
# releasenotes
|
# releasenotes
|
||||||
reno>=1.8.0 # Apache-2.0
|
reno!=2.3.1,>=1.8.0 # Apache-2.0
|
||||||
|
|
||||||
# bandit
|
# bandit
|
||||||
bandit>=1.1.0 # Apache-2.0
|
bandit>=1.1.0 # Apache-2.0
|
||||||
|
|||||||
8
tox.ini
8
tox.ini
@@ -6,13 +6,16 @@ skipsdist = True
|
|||||||
[testenv]
|
[testenv]
|
||||||
usedevelop = True
|
usedevelop = True
|
||||||
whitelist_externals = find
|
whitelist_externals = find
|
||||||
|
rm
|
||||||
install_command = pip install -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages}
|
install_command = pip install -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages}
|
||||||
setenv =
|
setenv =
|
||||||
VIRTUAL_ENV={envdir}
|
VIRTUAL_ENV={envdir}
|
||||||
deps = -r{toxinidir}/test-requirements.txt
|
deps = -r{toxinidir}/test-requirements.txt
|
||||||
commands =
|
commands =
|
||||||
|
rm -f .testrepository/times.dbm
|
||||||
find . -type f -name "*.py[c|o]" -delete
|
find . -type f -name "*.py[c|o]" -delete
|
||||||
ostestr --concurrency=6 {posargs}
|
ostestr --concurrency=6 {posargs}
|
||||||
|
passenv = http_proxy HTTP_PROXY https_proxy HTTPS_PROXY no_proxy NO_PROXY
|
||||||
|
|
||||||
[testenv:pep8]
|
[testenv:pep8]
|
||||||
commands =
|
commands =
|
||||||
@@ -41,12 +44,13 @@ commands = oslo_debug_helper -t watcher/tests {posargs}
|
|||||||
[testenv:genconfig]
|
[testenv:genconfig]
|
||||||
sitepackages = False
|
sitepackages = False
|
||||||
commands =
|
commands =
|
||||||
oslo-config-generator --config-file etc/watcher/watcher-config-generator.conf
|
oslo-config-generator --config-file etc/watcher/oslo-config-generator/watcher.conf
|
||||||
|
|
||||||
[flake8]
|
[flake8]
|
||||||
show-source=True
|
show-source=True
|
||||||
ignore= H105,E123,E226,N320
|
ignore= H105,E123,E226,N320,H202
|
||||||
builtins= _
|
builtins= _
|
||||||
|
enable-extensions = H106,H203
|
||||||
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build,*sqlalchemy/alembic/versions/*,demo/,releasenotes
|
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build,*sqlalchemy/alembic/versions/*,demo/,releasenotes
|
||||||
|
|
||||||
[testenv:wheel]
|
[testenv:wheel]
|
||||||
|
|||||||
@@ -32,16 +32,6 @@ _C = _translators.contextual_form
|
|||||||
# The plural translation function using the name "_P"
|
# The plural translation function using the name "_P"
|
||||||
_P = _translators.plural_form
|
_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():
|
def lazy_translation_enabled():
|
||||||
return _lazy.USE_LAZY
|
return _lazy.USE_LAZY
|
||||||
|
|||||||
@@ -488,6 +488,7 @@ class ActionPlansController(rest.RestController):
|
|||||||
raise exception.PatchError(patch=patch, reason=e)
|
raise exception.PatchError(patch=patch, reason=e)
|
||||||
|
|
||||||
launch_action_plan = False
|
launch_action_plan = False
|
||||||
|
cancel_action_plan = False
|
||||||
|
|
||||||
# transitions that are allowed via PATCH
|
# transitions that are allowed via PATCH
|
||||||
allowed_patch_transitions = [
|
allowed_patch_transitions = [
|
||||||
@@ -496,7 +497,7 @@ class ActionPlansController(rest.RestController):
|
|||||||
(ap_objects.State.RECOMMENDED,
|
(ap_objects.State.RECOMMENDED,
|
||||||
ap_objects.State.CANCELLED),
|
ap_objects.State.CANCELLED),
|
||||||
(ap_objects.State.ONGOING,
|
(ap_objects.State.ONGOING,
|
||||||
ap_objects.State.CANCELLED),
|
ap_objects.State.CANCELLING),
|
||||||
(ap_objects.State.PENDING,
|
(ap_objects.State.PENDING,
|
||||||
ap_objects.State.CANCELLED),
|
ap_objects.State.CANCELLED),
|
||||||
]
|
]
|
||||||
@@ -515,6 +516,8 @@ class ActionPlansController(rest.RestController):
|
|||||||
|
|
||||||
if action_plan.state == ap_objects.State.PENDING:
|
if action_plan.state == ap_objects.State.PENDING:
|
||||||
launch_action_plan = True
|
launch_action_plan = True
|
||||||
|
if action_plan.state == ap_objects.State.CANCELLED:
|
||||||
|
cancel_action_plan = True
|
||||||
|
|
||||||
# Update only the fields that have changed
|
# Update only the fields that have changed
|
||||||
for field in objects.ActionPlan.fields:
|
for field in objects.ActionPlan.fields:
|
||||||
@@ -534,6 +537,16 @@ class ActionPlansController(rest.RestController):
|
|||||||
|
|
||||||
action_plan_to_update.save()
|
action_plan_to_update.save()
|
||||||
|
|
||||||
|
# NOTE: if action plan is cancelled from pending or recommended
|
||||||
|
# state update action state here only
|
||||||
|
if cancel_action_plan:
|
||||||
|
filters = {'action_plan_uuid': action_plan.uuid}
|
||||||
|
actions = objects.Action.list(pecan.request.context,
|
||||||
|
filters=filters, eager=True)
|
||||||
|
for a in actions:
|
||||||
|
a.state = objects.action.State.CANCELLED
|
||||||
|
a.save()
|
||||||
|
|
||||||
if launch_action_plan:
|
if launch_action_plan:
|
||||||
applier_client = rpcapi.ApplierAPI()
|
applier_client = rpcapi.ApplierAPI()
|
||||||
applier_client.launch_action_plan(pecan.request.context,
|
applier_client.launch_action_plan(pecan.request.context,
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ class AuditPostType(wtypes.Base):
|
|||||||
|
|
||||||
parameters = wtypes.wsattr({wtypes.text: types.jsontype}, mandatory=False,
|
parameters = wtypes.wsattr({wtypes.text: types.jsontype}, mandatory=False,
|
||||||
default={})
|
default={})
|
||||||
interval = wsme.wsattr(int, mandatory=False)
|
interval = wsme.wsattr(types.interval_or_cron, mandatory=False)
|
||||||
|
|
||||||
scope = wtypes.wsattr(types.jsontype, readonly=True)
|
scope = wtypes.wsattr(types.jsontype, readonly=True)
|
||||||
|
|
||||||
@@ -261,7 +261,7 @@ class Audit(base.APIBase):
|
|||||||
links = wsme.wsattr([link.Link], readonly=True)
|
links = wsme.wsattr([link.Link], readonly=True)
|
||||||
"""A list containing a self link and associated audit links"""
|
"""A list containing a self link and associated audit links"""
|
||||||
|
|
||||||
interval = wsme.wsattr(int, mandatory=False)
|
interval = wsme.wsattr(wtypes.text, mandatory=False)
|
||||||
"""Launch audit periodically (in seconds)"""
|
"""Launch audit periodically (in seconds)"""
|
||||||
|
|
||||||
scope = wsme.wsattr(types.jsontype, mandatory=False)
|
scope = wsme.wsattr(types.jsontype, mandatory=False)
|
||||||
@@ -270,6 +270,9 @@ class Audit(base.APIBase):
|
|||||||
auto_trigger = wsme.wsattr(bool, mandatory=False, default=False)
|
auto_trigger = wsme.wsattr(bool, mandatory=False, default=False)
|
||||||
"""Autoexecute action plan once audit is succeeded"""
|
"""Autoexecute action plan once audit is succeeded"""
|
||||||
|
|
||||||
|
next_run_time = wsme.wsattr(datetime.datetime, mandatory=False)
|
||||||
|
"""The next time audit launch"""
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
self.fields = []
|
self.fields = []
|
||||||
fields = list(objects.Audit.fields)
|
fields = list(objects.Audit.fields)
|
||||||
@@ -301,7 +304,8 @@ class Audit(base.APIBase):
|
|||||||
audit.unset_fields_except(['uuid', 'audit_type', 'state',
|
audit.unset_fields_except(['uuid', 'audit_type', 'state',
|
||||||
'goal_uuid', 'interval', 'scope',
|
'goal_uuid', 'interval', 'scope',
|
||||||
'strategy_uuid', 'goal_name',
|
'strategy_uuid', 'goal_name',
|
||||||
'strategy_name', 'auto_trigger'])
|
'strategy_name', 'auto_trigger',
|
||||||
|
'next_run_time'])
|
||||||
|
|
||||||
audit.links = [link.Link.make_link('self', url,
|
audit.links = [link.Link.make_link('self', url,
|
||||||
'audits', audit.uuid),
|
'audits', audit.uuid),
|
||||||
@@ -325,9 +329,10 @@ class Audit(base.APIBase):
|
|||||||
created_at=datetime.datetime.utcnow(),
|
created_at=datetime.datetime.utcnow(),
|
||||||
deleted_at=None,
|
deleted_at=None,
|
||||||
updated_at=datetime.datetime.utcnow(),
|
updated_at=datetime.datetime.utcnow(),
|
||||||
interval=7200,
|
interval='7200',
|
||||||
scope=[],
|
scope=[],
|
||||||
auto_trigger=False)
|
auto_trigger=False,
|
||||||
|
next_run_time=datetime.datetime.utcnow())
|
||||||
|
|
||||||
sample.goal_id = '7ae81bb3-dec3-4289-8d6c-da80bd8001ae'
|
sample.goal_id = '7ae81bb3-dec3-4289-8d6c-da80bd8001ae'
|
||||||
sample.strategy_id = '7ae81bb3-dec3-4289-8d6c-da80bd8001ff'
|
sample.strategy_id = '7ae81bb3-dec3-4289-8d6c-da80bd8001ff'
|
||||||
|
|||||||
@@ -109,6 +109,21 @@ class AuditTemplatePostType(wtypes.Base):
|
|||||||
common_utils.Draft4Validator(
|
common_utils.Draft4Validator(
|
||||||
default.DefaultScope.DEFAULT_SCHEMA).validate(audit_template.scope)
|
default.DefaultScope.DEFAULT_SCHEMA).validate(audit_template.scope)
|
||||||
|
|
||||||
|
include_host_aggregates = False
|
||||||
|
exclude_host_aggregates = False
|
||||||
|
for rule in audit_template.scope:
|
||||||
|
if 'host_aggregates' in rule:
|
||||||
|
include_host_aggregates = True
|
||||||
|
elif 'exclude' in rule:
|
||||||
|
for resource in rule['exclude']:
|
||||||
|
if 'host_aggregates' in resource:
|
||||||
|
exclude_host_aggregates = True
|
||||||
|
if include_host_aggregates and exclude_host_aggregates:
|
||||||
|
raise exception.Invalid(
|
||||||
|
message=_(
|
||||||
|
"host_aggregates can't be "
|
||||||
|
"included and excluded together"))
|
||||||
|
|
||||||
if audit_template.strategy:
|
if audit_template.strategy:
|
||||||
available_strategies = objects.Strategy.list(
|
available_strategies = objects.Strategy.list(
|
||||||
AuditTemplatePostType._ctx)
|
AuditTemplatePostType._ctx)
|
||||||
@@ -535,7 +550,7 @@ class AuditTemplatesController(rest.RestController):
|
|||||||
def get_one(self, audit_template):
|
def get_one(self, audit_template):
|
||||||
"""Retrieve information about the given audit template.
|
"""Retrieve information about the given audit template.
|
||||||
|
|
||||||
:param audit audit_template: UUID or name of an audit template.
|
:param audit_template: UUID or name of an audit template.
|
||||||
"""
|
"""
|
||||||
if self.from_audit_templates:
|
if self.from_audit_templates:
|
||||||
raise exception.OperationNotPermitted
|
raise exception.OperationNotPermitted
|
||||||
@@ -582,7 +597,7 @@ class AuditTemplatesController(rest.RestController):
|
|||||||
def patch(self, audit_template, patch):
|
def patch(self, audit_template, patch):
|
||||||
"""Update an existing audit template.
|
"""Update an existing audit template.
|
||||||
|
|
||||||
:param audit template_uuid: UUID of a audit template.
|
:param template_uuid: UUID of a audit template.
|
||||||
:param patch: a json PATCH document to apply to this audit template.
|
:param patch: a json PATCH document to apply to this audit template.
|
||||||
"""
|
"""
|
||||||
if self.from_audit_templates:
|
if self.from_audit_templates:
|
||||||
@@ -630,7 +645,7 @@ class AuditTemplatesController(rest.RestController):
|
|||||||
def delete(self, audit_template):
|
def delete(self, audit_template):
|
||||||
"""Delete a audit template.
|
"""Delete a audit template.
|
||||||
|
|
||||||
:param audit template_uuid: UUID or name of an audit template.
|
:param template_uuid: UUID or name of an audit template.
|
||||||
"""
|
"""
|
||||||
context = pecan.request.context
|
context = pecan.request.context
|
||||||
audit_template_to_delete = api_utils.get_resource('AuditTemplate',
|
audit_template_to_delete = api_utils.get_resource('AuditTemplate',
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ from watcher.api.controllers import base
|
|||||||
from watcher.api.controllers import link
|
from watcher.api.controllers import link
|
||||||
from watcher.api.controllers.v1 import collection
|
from watcher.api.controllers.v1 import collection
|
||||||
from watcher.api.controllers.v1 import utils as api_utils
|
from watcher.api.controllers.v1 import utils as api_utils
|
||||||
|
from watcher.common import context
|
||||||
from watcher.common import exception
|
from watcher.common import exception
|
||||||
from watcher.common import policy
|
from watcher.common import policy
|
||||||
from watcher import objects
|
from watcher import objects
|
||||||
@@ -51,6 +52,7 @@ class Service(base.APIBase):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
_status = None
|
_status = None
|
||||||
|
_context = context.RequestContext(is_admin=True)
|
||||||
|
|
||||||
def _get_status(self):
|
def _get_status(self):
|
||||||
return self._status
|
return self._status
|
||||||
|
|||||||
@@ -43,6 +43,28 @@ class UuidOrNameType(wtypes.UserType):
|
|||||||
return UuidOrNameType.validate(value)
|
return UuidOrNameType.validate(value)
|
||||||
|
|
||||||
|
|
||||||
|
class IntervalOrCron(wtypes.UserType):
|
||||||
|
"""A simple int value or cron syntax type"""
|
||||||
|
|
||||||
|
basetype = wtypes.text
|
||||||
|
name = 'interval_or_cron'
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def validate(value):
|
||||||
|
if not (utils.is_int_like(value) or utils.is_cron_like(value)):
|
||||||
|
raise exception.InvalidIntervalOrCron(name=value)
|
||||||
|
return value
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def frombasetype(value):
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
return IntervalOrCron.validate(value)
|
||||||
|
|
||||||
|
|
||||||
|
interval_or_cron = IntervalOrCron()
|
||||||
|
|
||||||
|
|
||||||
class NameType(wtypes.UserType):
|
class NameType(wtypes.UserType):
|
||||||
"""A simple logical name type."""
|
"""A simple logical name type."""
|
||||||
|
|
||||||
@@ -181,7 +203,7 @@ class JsonPatchType(wtypes.Base):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def mandatory_attrs():
|
def mandatory_attrs():
|
||||||
"""Retruns a list of mandatory attributes.
|
"""Returns a list of mandatory attributes.
|
||||||
|
|
||||||
Mandatory attributes can't be removed from the document. This
|
Mandatory attributes can't be removed from the document. This
|
||||||
method should be overwritten by derived class.
|
method should be overwritten by derived class.
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ def validate_sort_dir(sort_dir):
|
|||||||
|
|
||||||
|
|
||||||
def validate_search_filters(filters, allowed_fields):
|
def validate_search_filters(filters, allowed_fields):
|
||||||
# Very leightweight validation for now
|
# Very lightweight validation for now
|
||||||
# todo: improve this (e.g. https://www.parse.com/docs/rest/guide/#queries)
|
# todo: improve this (e.g. https://www.parse.com/docs/rest/guide/#queries)
|
||||||
for filter_name in filters.keys():
|
for filter_name in filters.keys():
|
||||||
if filter_name not in allowed_fields:
|
if filter_name not in allowed_fields:
|
||||||
|
|||||||
97
watcher/api/scheduling.py
Normal file
97
watcher/api/scheduling.py
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
# Copyright (c) 2017 Servionica
|
||||||
|
#
|
||||||
|
# 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 datetime
|
||||||
|
from oslo_config import cfg
|
||||||
|
from oslo_log import log
|
||||||
|
from oslo_utils import timeutils
|
||||||
|
import six
|
||||||
|
|
||||||
|
from watcher.common import context as watcher_context
|
||||||
|
from watcher.common import scheduling
|
||||||
|
from watcher import notifications
|
||||||
|
|
||||||
|
from watcher import objects
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class APISchedulingService(scheduling.BackgroundSchedulerService):
|
||||||
|
|
||||||
|
def __init__(self, gconfig=None, **options):
|
||||||
|
self.services_status = {}
|
||||||
|
gconfig = None or {}
|
||||||
|
super(APISchedulingService, self).__init__(gconfig, **options)
|
||||||
|
|
||||||
|
def get_services_status(self, context):
|
||||||
|
services = objects.service.Service.list(context)
|
||||||
|
for service in services:
|
||||||
|
result = self.get_service_status(context, service.id)
|
||||||
|
if service.id not in self.services_status.keys():
|
||||||
|
self.services_status[service.id] = result
|
||||||
|
continue
|
||||||
|
if self.services_status[service.id] != result:
|
||||||
|
self.services_status[service.id] = result
|
||||||
|
notifications.service.send_service_update(context, service,
|
||||||
|
state=result)
|
||||||
|
|
||||||
|
def get_service_status(self, context, service_id):
|
||||||
|
service = objects.Service.get(context, service_id)
|
||||||
|
last_heartbeat = (service.last_seen_up or service.updated_at
|
||||||
|
or service.created_at)
|
||||||
|
if isinstance(last_heartbeat, six.string_types):
|
||||||
|
# NOTE(russellb) If this service came in over rpc via
|
||||||
|
# conductor, then the timestamp will be a string and needs to be
|
||||||
|
# converted back to a datetime.
|
||||||
|
last_heartbeat = timeutils.parse_strtime(last_heartbeat)
|
||||||
|
else:
|
||||||
|
# Objects have proper UTC timezones, but the timeutils comparison
|
||||||
|
# below does not (and will fail)
|
||||||
|
last_heartbeat = last_heartbeat.replace(tzinfo=None)
|
||||||
|
elapsed = timeutils.delta_seconds(last_heartbeat, timeutils.utcnow())
|
||||||
|
is_up = abs(elapsed) <= CONF.service_down_time
|
||||||
|
if not is_up:
|
||||||
|
LOG.warning('Seems service %(name)s on host %(host)s is down. '
|
||||||
|
'Last heartbeat was %(lhb)s. Elapsed time is %(el)s',
|
||||||
|
{'name': service.name,
|
||||||
|
'host': service.host,
|
||||||
|
'lhb': str(last_heartbeat), 'el': str(elapsed)})
|
||||||
|
return objects.service.ServiceStatus.FAILED
|
||||||
|
|
||||||
|
return objects.service.ServiceStatus.ACTIVE
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
"""Start service."""
|
||||||
|
context = watcher_context.make_context(is_admin=True)
|
||||||
|
self.add_job(self.get_services_status, name='service_status',
|
||||||
|
trigger='interval', jobstore='default', args=[context],
|
||||||
|
next_run_time=datetime.datetime.now(), seconds=60)
|
||||||
|
super(APISchedulingService, self).start()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
"""Stop service."""
|
||||||
|
self.shutdown()
|
||||||
|
|
||||||
|
def wait(self):
|
||||||
|
"""Wait for service to complete."""
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
"""Reset service.
|
||||||
|
|
||||||
|
Called in case service running in daemon mode receives SIGHUP.
|
||||||
|
"""
|
||||||
@@ -20,6 +20,7 @@ from oslo_log import log
|
|||||||
|
|
||||||
from watcher.applier.action_plan import base
|
from watcher.applier.action_plan import base
|
||||||
from watcher.applier import default
|
from watcher.applier import default
|
||||||
|
from watcher.common import exception
|
||||||
from watcher import notifications
|
from watcher import notifications
|
||||||
from watcher import objects
|
from watcher import objects
|
||||||
from watcher.objects import fields
|
from watcher.objects import fields
|
||||||
@@ -39,6 +40,9 @@ class DefaultActionPlanHandler(base.BaseActionPlanHandler):
|
|||||||
try:
|
try:
|
||||||
action_plan = objects.ActionPlan.get_by_uuid(
|
action_plan = objects.ActionPlan.get_by_uuid(
|
||||||
self.ctx, self.action_plan_uuid, eager=True)
|
self.ctx, self.action_plan_uuid, eager=True)
|
||||||
|
if action_plan.state == objects.action_plan.State.CANCELLED:
|
||||||
|
self._update_action_from_pending_to_cancelled()
|
||||||
|
return
|
||||||
action_plan.state = objects.action_plan.State.ONGOING
|
action_plan.state = objects.action_plan.State.ONGOING
|
||||||
action_plan.save()
|
action_plan.save()
|
||||||
notifications.action_plan.send_action_notification(
|
notifications.action_plan.send_action_notification(
|
||||||
@@ -54,6 +58,12 @@ class DefaultActionPlanHandler(base.BaseActionPlanHandler):
|
|||||||
self.ctx, action_plan,
|
self.ctx, action_plan,
|
||||||
action=fields.NotificationAction.EXECUTION,
|
action=fields.NotificationAction.EXECUTION,
|
||||||
phase=fields.NotificationPhase.END)
|
phase=fields.NotificationPhase.END)
|
||||||
|
|
||||||
|
except exception.ActionPlanCancelled as e:
|
||||||
|
LOG.exception(e)
|
||||||
|
action_plan.state = objects.action_plan.State.CANCELLED
|
||||||
|
self._update_action_from_pending_to_cancelled()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.exception(e)
|
LOG.exception(e)
|
||||||
action_plan.state = objects.action_plan.State.FAILED
|
action_plan.state = objects.action_plan.State.FAILED
|
||||||
@@ -64,3 +74,12 @@ class DefaultActionPlanHandler(base.BaseActionPlanHandler):
|
|||||||
phase=fields.NotificationPhase.ERROR)
|
phase=fields.NotificationPhase.ERROR)
|
||||||
finally:
|
finally:
|
||||||
action_plan.save()
|
action_plan.save()
|
||||||
|
|
||||||
|
def _update_action_from_pending_to_cancelled(self):
|
||||||
|
filters = {'action_plan_uuid': self.action_plan_uuid,
|
||||||
|
'state': objects.action.State.PENDING}
|
||||||
|
actions = objects.Action.list(self.ctx, filters=filters, eager=True)
|
||||||
|
if actions:
|
||||||
|
for a in actions:
|
||||||
|
a.state = objects.action.State.CANCELLED
|
||||||
|
a.save()
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
import abc
|
import abc
|
||||||
|
|
||||||
|
import jsonschema
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from watcher.common import clients
|
from watcher.common import clients
|
||||||
@@ -32,6 +33,9 @@ class BaseAction(loadable.Loadable):
|
|||||||
# watcher dashboard and will be nested in input_parameters
|
# watcher dashboard and will be nested in input_parameters
|
||||||
RESOURCE_ID = 'resource_id'
|
RESOURCE_ID = 'resource_id'
|
||||||
|
|
||||||
|
# Add action class name to the list, if implementing abort.
|
||||||
|
ABORT_TRUE = ['Sleep', 'Nop']
|
||||||
|
|
||||||
def __init__(self, config, osc=None):
|
def __init__(self, config, osc=None):
|
||||||
"""Constructor
|
"""Constructor
|
||||||
|
|
||||||
@@ -111,7 +115,7 @@ class BaseAction(loadable.Loadable):
|
|||||||
def post_condition(self):
|
def post_condition(self):
|
||||||
"""Hook: called after the execution of an action
|
"""Hook: called after the execution of an action
|
||||||
|
|
||||||
This function is called regardless of whether an action succeded or
|
This function is called regardless of whether an action succeeded or
|
||||||
not. So you can use it to perform cleanup operations.
|
not. So you can use it to perform cleanup operations.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
@@ -127,5 +131,22 @@ class BaseAction(loadable.Loadable):
|
|||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def validate_parameters(self):
|
def validate_parameters(self):
|
||||||
self.schema(self.input_parameters)
|
try:
|
||||||
return True
|
jsonschema.validate(self.input_parameters, self.schema)
|
||||||
|
return True
|
||||||
|
except jsonschema.ValidationError as e:
|
||||||
|
raise e
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def get_description(self):
|
||||||
|
"""Description of the action"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def check_abort(self):
|
||||||
|
if self.__class__.__name__ is 'Migrate':
|
||||||
|
if self.migration_type == self.LIVE_MIGRATION:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return bool(self.__class__.__name__ in self.ABORT_TRUE)
|
||||||
|
|||||||
118
watcher/applier/actions/change_node_power_state.py
Normal file
118
watcher/applier/actions/change_node_power_state.py
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
# Copyright (c) 2017 ZTE
|
||||||
|
#
|
||||||
|
# Authors: Li Canwei
|
||||||
|
#
|
||||||
|
# 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 enum
|
||||||
|
|
||||||
|
from watcher._i18n import _
|
||||||
|
from watcher.applier.actions import base
|
||||||
|
from watcher.common import exception
|
||||||
|
|
||||||
|
|
||||||
|
class NodeState(enum.Enum):
|
||||||
|
POWERON = 'on'
|
||||||
|
POWEROFF = 'off'
|
||||||
|
|
||||||
|
|
||||||
|
class ChangeNodePowerState(base.BaseAction):
|
||||||
|
"""Compute node power on/off
|
||||||
|
|
||||||
|
By using this action, you will be able to on/off the power of a
|
||||||
|
compute node.
|
||||||
|
|
||||||
|
The action schema is::
|
||||||
|
|
||||||
|
schema = Schema({
|
||||||
|
'resource_id': str,
|
||||||
|
'state': str,
|
||||||
|
})
|
||||||
|
|
||||||
|
The `resource_id` references a ironic node id (list of available
|
||||||
|
ironic node is returned by this command: ``ironic node-list``).
|
||||||
|
The `state` value should either be `on` or `off`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
STATE = 'state'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def schema(self):
|
||||||
|
return {
|
||||||
|
'type': 'object',
|
||||||
|
'properties': {
|
||||||
|
'resource_id': {
|
||||||
|
'type': 'string',
|
||||||
|
"minlength": 1
|
||||||
|
},
|
||||||
|
'state': {
|
||||||
|
'type': 'string',
|
||||||
|
'enum': [NodeState.POWERON.value,
|
||||||
|
NodeState.POWEROFF.value]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'required': ['resource_id', 'state'],
|
||||||
|
'additionalProperties': False,
|
||||||
|
}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def node_uuid(self):
|
||||||
|
return self.resource_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
return self.input_parameters.get(self.STATE)
|
||||||
|
|
||||||
|
def execute(self):
|
||||||
|
target_state = self.state
|
||||||
|
return self._node_manage_power(target_state)
|
||||||
|
|
||||||
|
def revert(self):
|
||||||
|
if self.state == NodeState.POWERON.value:
|
||||||
|
target_state = NodeState.POWEROFF.value
|
||||||
|
elif self.state == NodeState.POWEROFF.value:
|
||||||
|
target_state = NodeState.POWERON.value
|
||||||
|
return self._node_manage_power(target_state)
|
||||||
|
|
||||||
|
def _node_manage_power(self, state):
|
||||||
|
if state is None:
|
||||||
|
raise exception.IllegalArgumentException(
|
||||||
|
message=_("The target state is not defined"))
|
||||||
|
|
||||||
|
result = False
|
||||||
|
ironic_client = self.osc.ironic()
|
||||||
|
nova_client = self.osc.nova()
|
||||||
|
if state == NodeState.POWEROFF.value:
|
||||||
|
node_info = ironic_client.node.get(self.node_uuid).to_dict()
|
||||||
|
compute_node_id = node_info['extra']['compute_node_id']
|
||||||
|
compute_node = nova_client.hypervisors.get(compute_node_id)
|
||||||
|
compute_node = compute_node.to_dict()
|
||||||
|
if (compute_node['running_vms'] == 0):
|
||||||
|
result = ironic_client.node.set_power_state(
|
||||||
|
self.node_uuid, state)
|
||||||
|
else:
|
||||||
|
result = ironic_client.node.set_power_state(self.node_uuid, state)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def pre_condition(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def post_condition(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_description(self):
|
||||||
|
"""Description of the action"""
|
||||||
|
return ("Compute node power on/off through ironic.")
|
||||||
@@ -17,9 +17,6 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
|
||||||
import six
|
|
||||||
import voluptuous
|
|
||||||
|
|
||||||
from watcher._i18n import _
|
from watcher._i18n import _
|
||||||
from watcher.applier.actions import base
|
from watcher.applier.actions import base
|
||||||
from watcher.common import exception
|
from watcher.common import exception
|
||||||
@@ -51,15 +48,24 @@ class ChangeNovaServiceState(base.BaseAction):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def schema(self):
|
def schema(self):
|
||||||
return voluptuous.Schema({
|
return {
|
||||||
voluptuous.Required(self.RESOURCE_ID):
|
'type': 'object',
|
||||||
voluptuous.All(
|
'properties': {
|
||||||
voluptuous.Any(*six.string_types),
|
'resource_id': {
|
||||||
voluptuous.Length(min=1)),
|
'type': 'string',
|
||||||
voluptuous.Required(self.STATE):
|
"minlength": 1
|
||||||
voluptuous.Any(*[state.value
|
},
|
||||||
for state in list(element.ServiceState)]),
|
'state': {
|
||||||
})
|
'type': 'string',
|
||||||
|
'enum': [element.ServiceState.ONLINE.value,
|
||||||
|
element.ServiceState.OFFLINE.value,
|
||||||
|
element.ServiceState.ENABLED.value,
|
||||||
|
element.ServiceState.DISABLED.value]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'required': ['resource_id', 'state'],
|
||||||
|
'additionalProperties': False,
|
||||||
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def host(self):
|
def host(self):
|
||||||
@@ -101,3 +107,9 @@ class ChangeNovaServiceState(base.BaseAction):
|
|||||||
|
|
||||||
def post_condition(self):
|
def post_condition(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def get_description(self):
|
||||||
|
"""Description of the action"""
|
||||||
|
return ("Disables or enables the nova-compute service."
|
||||||
|
"A disabled nova-compute service can not be selected "
|
||||||
|
"by the nova for future deployment of new server.")
|
||||||
|
|||||||
@@ -17,15 +17,12 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
|
||||||
from oslo_log import log
|
|
||||||
import six
|
|
||||||
import voluptuous
|
|
||||||
|
|
||||||
|
from oslo_log import log
|
||||||
from watcher._i18n import _
|
from watcher._i18n import _
|
||||||
from watcher.applier.actions import base
|
from watcher.applier.actions import base
|
||||||
from watcher.common import exception
|
from watcher.common import exception
|
||||||
from watcher.common import nova_helper
|
from watcher.common import nova_helper
|
||||||
from watcher.common import utils
|
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
@@ -62,28 +59,36 @@ class Migrate(base.BaseAction):
|
|||||||
DESTINATION_NODE = 'destination_node'
|
DESTINATION_NODE = 'destination_node'
|
||||||
SOURCE_NODE = 'source_node'
|
SOURCE_NODE = 'source_node'
|
||||||
|
|
||||||
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
|
@property
|
||||||
def schema(self):
|
def schema(self):
|
||||||
return voluptuous.Schema({
|
return {
|
||||||
voluptuous.Required(self.RESOURCE_ID): self.check_resource_id,
|
'type': 'object',
|
||||||
voluptuous.Required(
|
'properties': {
|
||||||
self.MIGRATION_TYPE, default=self.LIVE_MIGRATION):
|
'destination_node': {
|
||||||
voluptuous.Any(
|
"anyof": [
|
||||||
*[self.LIVE_MIGRATION, self.COLD_MIGRATION]),
|
{'type': 'string', "minLength": 1},
|
||||||
voluptuous.Required(self.DESTINATION_NODE):
|
{'type': 'None'}
|
||||||
voluptuous.All(voluptuous.Any(*six.string_types),
|
]
|
||||||
voluptuous.Length(min=1)),
|
},
|
||||||
voluptuous.Required(self.SOURCE_NODE):
|
'migration_type': {
|
||||||
voluptuous.All(voluptuous.Any(*six.string_types),
|
'type': 'string',
|
||||||
voluptuous.Length(min=1)),
|
"enum": ["live", "cold"]
|
||||||
})
|
},
|
||||||
|
'resource_id': {
|
||||||
|
'type': 'string',
|
||||||
|
"minlength": 1,
|
||||||
|
"pattern": ("^([a-fA-F0-9]){8}-([a-fA-F0-9]){4}-"
|
||||||
|
"([a-fA-F0-9]){4}-([a-fA-F0-9]){4}-"
|
||||||
|
"([a-fA-F0-9]){12}$")
|
||||||
|
},
|
||||||
|
'source_node': {
|
||||||
|
'type': 'string',
|
||||||
|
"minLength": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'required': ['migration_type', 'resource_id', 'source_node'],
|
||||||
|
'additionalProperties': False,
|
||||||
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def instance_uuid(self):
|
def instance_uuid(self):
|
||||||
@@ -137,13 +142,28 @@ class Migrate(base.BaseAction):
|
|||||||
LOG.critical("Unexpected error occurred. Migration failed for "
|
LOG.critical("Unexpected error occurred. Migration failed for "
|
||||||
"instance %s. Leaving instance on previous "
|
"instance %s. Leaving instance on previous "
|
||||||
"host.", self.instance_uuid)
|
"host.", self.instance_uuid)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def migrate(self, destination):
|
def _abort_cold_migrate(self, nova):
|
||||||
|
# TODO(adisky): currently watcher uses its own version of cold migrate
|
||||||
|
# implement cold migrate using nova dependent on the blueprint
|
||||||
|
# https://blueprints.launchpad.net/nova/+spec/cold-migration-with-target
|
||||||
|
# Abort operation for cold migrate is dependent on blueprint
|
||||||
|
# https://blueprints.launchpad.net/nova/+spec/abort-cold-migration
|
||||||
|
LOG.warning("Abort operation for cold migration is not implemented")
|
||||||
|
|
||||||
|
def _abort_live_migrate(self, nova, source, destination):
|
||||||
|
return nova.abort_live_migrate(instance_id=self.instance_uuid,
|
||||||
|
source=source, destination=destination)
|
||||||
|
|
||||||
|
def migrate(self, destination=None):
|
||||||
nova = nova_helper.NovaHelper(osc=self.osc)
|
nova = nova_helper.NovaHelper(osc=self.osc)
|
||||||
LOG.debug("Migrate instance %s to %s", self.instance_uuid,
|
if destination is None:
|
||||||
destination)
|
LOG.debug("Migrating instance %s, destination node will be "
|
||||||
|
"determined by nova-scheduler", self.instance_uuid)
|
||||||
|
else:
|
||||||
|
LOG.debug("Migrate instance %s to %s", self.instance_uuid,
|
||||||
|
destination)
|
||||||
instance = nova.find_instance(self.instance_uuid)
|
instance = nova.find_instance(self.instance_uuid)
|
||||||
if instance:
|
if instance:
|
||||||
if self.migration_type == self.LIVE_MIGRATION:
|
if self.migration_type == self.LIVE_MIGRATION:
|
||||||
@@ -164,6 +184,19 @@ class Migrate(base.BaseAction):
|
|||||||
def revert(self):
|
def revert(self):
|
||||||
return self.migrate(destination=self.source_node)
|
return self.migrate(destination=self.source_node)
|
||||||
|
|
||||||
|
def abort(self):
|
||||||
|
nova = nova_helper.NovaHelper(osc=self.osc)
|
||||||
|
instance = nova.find_instance(self.instance_uuid)
|
||||||
|
if instance:
|
||||||
|
if self.migration_type == self.COLD_MIGRATION:
|
||||||
|
return self._abort_cold_migrate(nova)
|
||||||
|
elif self.migration_type == self.LIVE_MIGRATION:
|
||||||
|
return self._abort_live_migrate(
|
||||||
|
nova, source=self.source_node,
|
||||||
|
destination=self.destination_node)
|
||||||
|
else:
|
||||||
|
raise exception.InstanceNotFound(name=self.instance_uuid)
|
||||||
|
|
||||||
def pre_condition(self):
|
def pre_condition(self):
|
||||||
# TODO(jed): check if the instance exists / check if the instance is on
|
# TODO(jed): check if the instance exists / check if the instance is on
|
||||||
# the source_node
|
# the source_node
|
||||||
@@ -172,3 +205,7 @@ class Migrate(base.BaseAction):
|
|||||||
def post_condition(self):
|
def post_condition(self):
|
||||||
# TODO(jed): check extra parameters (network response, etc.)
|
# TODO(jed): check extra parameters (network response, etc.)
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def get_description(self):
|
||||||
|
"""Description of the action"""
|
||||||
|
return "Moving a VM instance from source_node to destination_node"
|
||||||
|
|||||||
@@ -18,12 +18,9 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
import six
|
|
||||||
import voluptuous
|
|
||||||
|
|
||||||
from watcher.applier.actions import base
|
from watcher.applier.actions import base
|
||||||
|
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@@ -43,10 +40,16 @@ class Nop(base.BaseAction):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def schema(self):
|
def schema(self):
|
||||||
return voluptuous.Schema({
|
return {
|
||||||
voluptuous.Required(self.MESSAGE): voluptuous.Any(
|
'type': 'object',
|
||||||
voluptuous.Any(*six.string_types), None)
|
'properties': {
|
||||||
})
|
'message': {
|
||||||
|
'type': ['string', 'null']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'required': ['message'],
|
||||||
|
'additionalProperties': False,
|
||||||
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def message(self):
|
def message(self):
|
||||||
@@ -65,3 +68,11 @@ class Nop(base.BaseAction):
|
|||||||
|
|
||||||
def post_condition(self):
|
def post_condition(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def get_description(self):
|
||||||
|
"""Description of the action"""
|
||||||
|
return "Logging a NOP message"
|
||||||
|
|
||||||
|
def abort(self):
|
||||||
|
LOG.debug("Abort action NOP")
|
||||||
|
return True
|
||||||
|
|||||||
@@ -18,13 +18,9 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
import six
|
|
||||||
import voluptuous
|
|
||||||
|
|
||||||
from watcher._i18n import _
|
|
||||||
from watcher.applier.actions import base
|
from watcher.applier.actions import base
|
||||||
from watcher.common import nova_helper
|
from watcher.common import nova_helper
|
||||||
from watcher.common import utils
|
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
@@ -49,21 +45,26 @@ class Resize(base.BaseAction):
|
|||||||
# input parameters constants
|
# input parameters constants
|
||||||
FLAVOR = 'flavor'
|
FLAVOR = 'flavor'
|
||||||
|
|
||||||
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
|
@property
|
||||||
def schema(self):
|
def schema(self):
|
||||||
return voluptuous.Schema({
|
return {
|
||||||
voluptuous.Required(self.RESOURCE_ID): self.check_resource_id,
|
'type': 'object',
|
||||||
voluptuous.Required(self.FLAVOR):
|
'properties': {
|
||||||
voluptuous.All(voluptuous.Any(*six.string_types),
|
'resource_id': {
|
||||||
voluptuous.Length(min=1)),
|
'type': 'string',
|
||||||
})
|
'minlength': 1,
|
||||||
|
'pattern': ('^([a-fA-F0-9]){8}-([a-fA-F0-9]){4}-'
|
||||||
|
'([a-fA-F0-9]){4}-([a-fA-F0-9]){4}-'
|
||||||
|
'([a-fA-F0-9]){12}$')
|
||||||
|
},
|
||||||
|
'flavor': {
|
||||||
|
'type': 'string',
|
||||||
|
'minlength': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'required': ['resource_id', 'flavor'],
|
||||||
|
'additionalProperties': False,
|
||||||
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def instance_uuid(self):
|
def instance_uuid(self):
|
||||||
@@ -104,3 +105,7 @@ class Resize(base.BaseAction):
|
|||||||
def post_condition(self):
|
def post_condition(self):
|
||||||
# TODO(jed): check extra parameters (network response, etc.)
|
# TODO(jed): check extra parameters (network response, etc.)
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def get_description(self):
|
||||||
|
"""Description of the action"""
|
||||||
|
return "Resize a server with specified flavor."
|
||||||
|
|||||||
@@ -20,8 +20,6 @@
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
import voluptuous
|
|
||||||
|
|
||||||
from watcher.applier.actions import base
|
from watcher.applier.actions import base
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
@@ -43,10 +41,17 @@ class Sleep(base.BaseAction):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def schema(self):
|
def schema(self):
|
||||||
return voluptuous.Schema({
|
return {
|
||||||
voluptuous.Required(self.DURATION, default=1):
|
'type': 'object',
|
||||||
voluptuous.All(float, voluptuous.Range(min=0))
|
'properties': {
|
||||||
})
|
'duration': {
|
||||||
|
'type': 'number',
|
||||||
|
'minimum': 0
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'required': ['duration'],
|
||||||
|
'additionalProperties': False,
|
||||||
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def duration(self):
|
def duration(self):
|
||||||
@@ -66,3 +71,11 @@ class Sleep(base.BaseAction):
|
|||||||
|
|
||||||
def post_condition(self):
|
def post_condition(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def get_description(self):
|
||||||
|
"""Description of the action"""
|
||||||
|
return "Wait for a given interval in seconds."
|
||||||
|
|
||||||
|
def abort(self):
|
||||||
|
LOG.debug("Abort action sleep")
|
||||||
|
return True
|
||||||
|
|||||||
3
watcher/applier/default.py
Normal file → Executable file
3
watcher/applier/default.py
Normal file → Executable file
@@ -58,5 +58,6 @@ class DefaultApplier(base.BaseApplier):
|
|||||||
LOG.debug("Executing action plan %s ", action_plan_uuid)
|
LOG.debug("Executing action plan %s ", action_plan_uuid)
|
||||||
|
|
||||||
filters = {'action_plan_uuid': action_plan_uuid}
|
filters = {'action_plan_uuid': action_plan_uuid}
|
||||||
actions = objects.Action.list(self.context, filters=filters)
|
actions = objects.Action.list(self.context, filters=filters,
|
||||||
|
eager=True)
|
||||||
return self.engine.execute(actions)
|
return self.engine.execute(actions)
|
||||||
|
|||||||
@@ -17,13 +17,17 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
import abc
|
import abc
|
||||||
|
import six
|
||||||
|
import time
|
||||||
|
|
||||||
|
import eventlet
|
||||||
|
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
import six
|
|
||||||
from taskflow import task as flow_task
|
from taskflow import task as flow_task
|
||||||
|
|
||||||
from watcher.applier.actions import factory
|
from watcher.applier.actions import factory
|
||||||
from watcher.common import clients
|
from watcher.common import clients
|
||||||
|
from watcher.common import exception
|
||||||
from watcher.common.loader import loadable
|
from watcher.common.loader import loadable
|
||||||
from watcher import notifications
|
from watcher import notifications
|
||||||
from watcher import objects
|
from watcher import objects
|
||||||
@@ -32,6 +36,9 @@ from watcher.objects import fields
|
|||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
CANCEL_STATE = [objects.action_plan.State.CANCELLING,
|
||||||
|
objects.action_plan.State.CANCELLED]
|
||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(abc.ABCMeta)
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
class BaseWorkFlowEngine(loadable.Loadable):
|
class BaseWorkFlowEngine(loadable.Loadable):
|
||||||
@@ -122,16 +129,34 @@ class BaseTaskFlowActionContainer(flow_task.Task):
|
|||||||
def do_post_execute(self):
|
def do_post_execute(self):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def do_revert(self):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def do_abort(self, *args, **kwargs):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
# NOTE(alexchadin): taskflow does 3 method calls (pre_execute, execute,
|
# NOTE(alexchadin): taskflow does 3 method calls (pre_execute, execute,
|
||||||
# post_execute) independently. We want to support notifications in base
|
# post_execute) independently. We want to support notifications in base
|
||||||
# class, so child's methods should be named with `do_` prefix and wrapped.
|
# class, so child's methods should be named with `do_` prefix and wrapped.
|
||||||
def pre_execute(self):
|
def pre_execute(self):
|
||||||
try:
|
try:
|
||||||
|
# NOTE(adisky): check the state of action plan before starting
|
||||||
|
# next action, if action plan is cancelled raise the exceptions
|
||||||
|
# so that taskflow does not schedule further actions.
|
||||||
|
action_plan = objects.ActionPlan.get_by_id(
|
||||||
|
self.engine.context, self._db_action.action_plan_id)
|
||||||
|
if action_plan.state in CANCEL_STATE:
|
||||||
|
raise exception.ActionPlanCancelled(uuid=action_plan.uuid)
|
||||||
self.do_pre_execute()
|
self.do_pre_execute()
|
||||||
notifications.action.send_execution_notification(
|
notifications.action.send_execution_notification(
|
||||||
self.engine.context, self._db_action,
|
self.engine.context, self._db_action,
|
||||||
fields.NotificationAction.EXECUTION,
|
fields.NotificationAction.EXECUTION,
|
||||||
fields.NotificationPhase.START)
|
fields.NotificationPhase.START)
|
||||||
|
except exception.ActionPlanCancelled as e:
|
||||||
|
LOG.exception(e)
|
||||||
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.exception(e)
|
LOG.exception(e)
|
||||||
self.engine.notify(self._db_action, objects.action.State.FAILED)
|
self.engine.notify(self._db_action, objects.action.State.FAILED)
|
||||||
@@ -142,22 +167,59 @@ class BaseTaskFlowActionContainer(flow_task.Task):
|
|||||||
priority=fields.NotificationPriority.ERROR)
|
priority=fields.NotificationPriority.ERROR)
|
||||||
|
|
||||||
def execute(self, *args, **kwargs):
|
def execute(self, *args, **kwargs):
|
||||||
|
def _do_execute_action(*args, **kwargs):
|
||||||
|
try:
|
||||||
|
self.do_execute(*args, **kwargs)
|
||||||
|
notifications.action.send_execution_notification(
|
||||||
|
self.engine.context, self._db_action,
|
||||||
|
fields.NotificationAction.EXECUTION,
|
||||||
|
fields.NotificationPhase.END)
|
||||||
|
except Exception as e:
|
||||||
|
LOG.exception(e)
|
||||||
|
LOG.error('The workflow engine has failed'
|
||||||
|
'to execute the action: %s', self.name)
|
||||||
|
self.engine.notify(self._db_action,
|
||||||
|
objects.action.State.FAILED)
|
||||||
|
notifications.action.send_execution_notification(
|
||||||
|
self.engine.context, self._db_action,
|
||||||
|
fields.NotificationAction.EXECUTION,
|
||||||
|
fields.NotificationPhase.ERROR,
|
||||||
|
priority=fields.NotificationPriority.ERROR)
|
||||||
|
raise
|
||||||
|
# NOTE: spawn a new thread for action execution, so that if action plan
|
||||||
|
# is cancelled workflow engine will not wait to finish action execution
|
||||||
|
et = eventlet.spawn(_do_execute_action, *args, **kwargs)
|
||||||
|
# NOTE: check for the state of action plan periodically,so that if
|
||||||
|
# action is finished or action plan is cancelled we can exit from here.
|
||||||
|
while True:
|
||||||
|
action_object = objects.Action.get_by_uuid(
|
||||||
|
self.engine.context, self._db_action.uuid, eager=True)
|
||||||
|
action_plan_object = objects.ActionPlan.get_by_id(
|
||||||
|
self.engine.context, action_object.action_plan_id)
|
||||||
|
if (action_object.state in [objects.action.State.SUCCEEDED,
|
||||||
|
objects.action.State.FAILED] or
|
||||||
|
action_plan_object.state in CANCEL_STATE):
|
||||||
|
break
|
||||||
|
time.sleep(1)
|
||||||
try:
|
try:
|
||||||
self.do_execute(*args, **kwargs)
|
# NOTE: kill the action execution thread, if action plan is
|
||||||
notifications.action.send_execution_notification(
|
# cancelled for all other cases wait for the result from action
|
||||||
self.engine.context, self._db_action,
|
# execution thread.
|
||||||
fields.NotificationAction.EXECUTION,
|
# Not all actions support abort operations, kill only those action
|
||||||
fields.NotificationPhase.END)
|
# which support abort operations
|
||||||
|
abort = self.action.check_abort()
|
||||||
|
if (action_plan_object.state in CANCEL_STATE and abort):
|
||||||
|
et.kill()
|
||||||
|
et.wait()
|
||||||
|
|
||||||
|
# NOTE: catch the greenlet exit exception due to thread kill,
|
||||||
|
# taskflow will call revert for the action,
|
||||||
|
# we will redirect it to abort.
|
||||||
|
except eventlet.greenlet.GreenletExit:
|
||||||
|
raise exception.ActionPlanCancelled(uuid=action_plan_object.uuid)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.exception(e)
|
LOG.exception(e)
|
||||||
LOG.error('The workflow engine has failed '
|
|
||||||
'to execute the action: %s', self.name)
|
|
||||||
self.engine.notify(self._db_action, objects.action.State.FAILED)
|
|
||||||
notifications.action.send_execution_notification(
|
|
||||||
self.engine.context, self._db_action,
|
|
||||||
fields.NotificationAction.EXECUTION,
|
|
||||||
fields.NotificationPhase.ERROR,
|
|
||||||
priority=fields.NotificationPriority.ERROR)
|
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def post_execute(self):
|
def post_execute(self):
|
||||||
@@ -171,3 +233,28 @@ class BaseTaskFlowActionContainer(flow_task.Task):
|
|||||||
fields.NotificationAction.EXECUTION,
|
fields.NotificationAction.EXECUTION,
|
||||||
fields.NotificationPhase.ERROR,
|
fields.NotificationPhase.ERROR,
|
||||||
priority=fields.NotificationPriority.ERROR)
|
priority=fields.NotificationPriority.ERROR)
|
||||||
|
|
||||||
|
def revert(self, *args, **kwargs):
|
||||||
|
action_plan = objects.ActionPlan.get_by_id(
|
||||||
|
self.engine.context, self._db_action.action_plan_id, eager=True)
|
||||||
|
# NOTE: check if revert cause by cancel action plan or
|
||||||
|
# some other exception occured during action plan execution
|
||||||
|
# if due to some other exception keep the flow intact.
|
||||||
|
if action_plan.state not in CANCEL_STATE:
|
||||||
|
self.do_revert()
|
||||||
|
return
|
||||||
|
|
||||||
|
action_object = objects.Action.get_by_uuid(
|
||||||
|
self.engine.context, self._db_action.uuid, eager=True)
|
||||||
|
if action_object.state == objects.action.State.ONGOING:
|
||||||
|
action_object.state = objects.action.State.CANCELLING
|
||||||
|
action_object.save()
|
||||||
|
self.abort()
|
||||||
|
elif action_object.state == objects.action.State.PENDING:
|
||||||
|
action_object.state = objects.action.State.CANCELLED
|
||||||
|
action_object.save()
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def abort(self, *args, **kwargs):
|
||||||
|
self.do_abort(*args, **kwargs)
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ from oslo_concurrency import processutils
|
|||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
from taskflow import engines
|
from taskflow import engines
|
||||||
|
from taskflow import exceptions as tf_exception
|
||||||
from taskflow.patterns import graph_flow as gf
|
from taskflow.patterns import graph_flow as gf
|
||||||
from taskflow import task as flow_task
|
from taskflow import task as flow_task
|
||||||
|
|
||||||
@@ -90,6 +91,15 @@ class DefaultWorkFlowEngine(base.BaseWorkFlowEngine):
|
|||||||
|
|
||||||
return flow
|
return flow
|
||||||
|
|
||||||
|
except exception.ActionPlanCancelled as e:
|
||||||
|
raise
|
||||||
|
|
||||||
|
except tf_exception.WrappedFailure as e:
|
||||||
|
if e.check("watcher.common.exception.ActionPlanCancelled"):
|
||||||
|
raise exception.ActionPlanCancelled
|
||||||
|
else:
|
||||||
|
raise exception.WorkflowExecutionException(error=e)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise exception.WorkflowExecutionException(error=e)
|
raise exception.WorkflowExecutionException(error=e)
|
||||||
|
|
||||||
@@ -108,14 +118,20 @@ class TaskFlowActionContainer(base.BaseTaskFlowActionContainer):
|
|||||||
def do_execute(self, *args, **kwargs):
|
def do_execute(self, *args, **kwargs):
|
||||||
LOG.debug("Running action: %s", self.name)
|
LOG.debug("Running action: %s", self.name)
|
||||||
|
|
||||||
self.action.execute()
|
# NOTE: For result is False, set action state fail
|
||||||
self.engine.notify(self._db_action, objects.action.State.SUCCEEDED)
|
result = self.action.execute()
|
||||||
|
if result is False:
|
||||||
|
self.engine.notify(self._db_action,
|
||||||
|
objects.action.State.FAILED)
|
||||||
|
else:
|
||||||
|
self.engine.notify(self._db_action,
|
||||||
|
objects.action.State.SUCCEEDED)
|
||||||
|
|
||||||
def do_post_execute(self):
|
def do_post_execute(self):
|
||||||
LOG.debug("Post-condition action: %s", self.name)
|
LOG.debug("Post-condition action: %s", self.name)
|
||||||
self.action.post_condition()
|
self.action.post_condition()
|
||||||
|
|
||||||
def revert(self, *args, **kwargs):
|
def do_revert(self, *args, **kwargs):
|
||||||
LOG.warning("Revert action: %s", self.name)
|
LOG.warning("Revert action: %s", self.name)
|
||||||
try:
|
try:
|
||||||
# TODO(jed): do we need to update the states in case of failure?
|
# TODO(jed): do we need to update the states in case of failure?
|
||||||
@@ -124,6 +140,21 @@ class TaskFlowActionContainer(base.BaseTaskFlowActionContainer):
|
|||||||
LOG.exception(e)
|
LOG.exception(e)
|
||||||
LOG.critical("Oops! We need a disaster recover plan.")
|
LOG.critical("Oops! We need a disaster recover plan.")
|
||||||
|
|
||||||
|
def do_abort(self, *args, **kwargs):
|
||||||
|
LOG.warning("Aborting action: %s", self.name)
|
||||||
|
try:
|
||||||
|
result = self.action.abort()
|
||||||
|
if result:
|
||||||
|
# Aborted the action.
|
||||||
|
self.engine.notify(self._db_action,
|
||||||
|
objects.action.State.CANCELLED)
|
||||||
|
else:
|
||||||
|
self.engine.notify(self._db_action,
|
||||||
|
objects.action.State.SUCCEEDED)
|
||||||
|
except Exception as e:
|
||||||
|
self.engine.notify(self._db_action, objects.action.State.FAILED)
|
||||||
|
LOG.exception(e)
|
||||||
|
|
||||||
|
|
||||||
class TaskFlowNop(flow_task.Task):
|
class TaskFlowNop(flow_task.Task):
|
||||||
"""This class is used in case of the workflow have only one Action.
|
"""This class is used in case of the workflow have only one Action.
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import sys
|
|||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
|
||||||
|
from watcher.api import scheduling
|
||||||
from watcher.common import service
|
from watcher.common import service
|
||||||
from watcher import conf
|
from watcher import conf
|
||||||
|
|
||||||
@@ -45,5 +46,8 @@ def main():
|
|||||||
LOG.info('serving on %(protocol)s://%(host)s:%(port)s' %
|
LOG.info('serving on %(protocol)s://%(host)s:%(port)s' %
|
||||||
dict(protocol=protocol, host=host, port=port))
|
dict(protocol=protocol, host=host, port=port))
|
||||||
|
|
||||||
|
api_schedule = scheduling.APISchedulingService()
|
||||||
|
api_schedule.start()
|
||||||
|
|
||||||
launcher = service.launch(CONF, server, workers=server.workers)
|
launcher = service.launch(CONF, server, workers=server.workers)
|
||||||
launcher.wait()
|
launcher.wait()
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ def add_command_parsers(subparsers):
|
|||||||
"Optionally, use --revision to specify an alembic revision "
|
"Optionally, use --revision to specify an alembic revision "
|
||||||
"string to upgrade to.")
|
"string to upgrade to.")
|
||||||
parser.set_defaults(func=DBCommand.upgrade)
|
parser.set_defaults(func=DBCommand.upgrade)
|
||||||
parser.add_argument('revision', nargs='?')
|
parser.add_argument('--revision', nargs='?')
|
||||||
|
|
||||||
parser = subparsers.add_parser(
|
parser = subparsers.add_parser(
|
||||||
'downgrade',
|
'downgrade',
|
||||||
@@ -79,7 +79,7 @@ def add_command_parsers(subparsers):
|
|||||||
"While optional, one should generally use --revision to "
|
"While optional, one should generally use --revision to "
|
||||||
"specify the alembic revision string to downgrade to.")
|
"specify the alembic revision string to downgrade to.")
|
||||||
parser.set_defaults(func=DBCommand.downgrade)
|
parser.set_defaults(func=DBCommand.downgrade)
|
||||||
parser.add_argument('revision', nargs='?')
|
parser.add_argument('--revision', nargs='?')
|
||||||
|
|
||||||
parser = subparsers.add_parser('stamp')
|
parser = subparsers.add_parser('stamp')
|
||||||
parser.add_argument('revision', nargs='?')
|
parser.add_argument('revision', nargs='?')
|
||||||
|
|||||||
@@ -44,10 +44,10 @@ def main():
|
|||||||
syncer.sync()
|
syncer.sync()
|
||||||
|
|
||||||
de_service = watcher_service.Service(manager.DecisionEngineManager)
|
de_service = watcher_service.Service(manager.DecisionEngineManager)
|
||||||
bg_schedulder_service = scheduling.DecisionEngineSchedulingService()
|
bg_scheduler_service = scheduling.DecisionEngineSchedulingService()
|
||||||
|
|
||||||
# Only 1 process
|
# Only 1 process
|
||||||
launcher = watcher_service.launch(CONF, de_service)
|
launcher = watcher_service.launch(CONF, de_service)
|
||||||
launcher.launch_service(bg_schedulder_service)
|
launcher.launch_service(bg_scheduler_service)
|
||||||
|
|
||||||
launcher.wait()
|
launcher.wait()
|
||||||
|
|||||||
79
watcher/common/cinder_helper.py
Normal file
79
watcher/common/cinder_helper.py
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
# 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.common import clients
|
||||||
|
from watcher.common import exception
|
||||||
|
|
||||||
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class CinderHelper(object):
|
||||||
|
|
||||||
|
def __init__(self, osc=None):
|
||||||
|
""":param osc: an OpenStackClients instance"""
|
||||||
|
self.osc = osc if osc else clients.OpenStackClients()
|
||||||
|
self.cinder = self.osc.cinder()
|
||||||
|
|
||||||
|
def get_storage_node_list(self):
|
||||||
|
return list(self.cinder.services.list(binary='cinder-volume'))
|
||||||
|
|
||||||
|
def get_storage_node_by_name(self, name):
|
||||||
|
"""Get storage node by name(host@backendname)"""
|
||||||
|
try:
|
||||||
|
storages = list(filter(lambda storage:
|
||||||
|
storage.host == name,
|
||||||
|
self.get_storage_node_list()))
|
||||||
|
if len(storages) != 1:
|
||||||
|
raise exception.StorageNodeNotFound(name=name)
|
||||||
|
return storages[0]
|
||||||
|
except Exception as exc:
|
||||||
|
LOG.exception(exc)
|
||||||
|
raise exception.StorageNodeNotFound(name=name)
|
||||||
|
|
||||||
|
def get_storage_pool_list(self):
|
||||||
|
return self.cinder.pools.list(detailed=True)
|
||||||
|
|
||||||
|
def get_storage_pool_by_name(self, name):
|
||||||
|
"""Get pool by name(host@backend#poolname)"""
|
||||||
|
try:
|
||||||
|
pools = list(filter(lambda pool:
|
||||||
|
pool.name == name,
|
||||||
|
self.get_storage_pool_list()))
|
||||||
|
if len(pools) != 1:
|
||||||
|
raise exception.PoolNotFound(name=name)
|
||||||
|
return pools[0]
|
||||||
|
except Exception as exc:
|
||||||
|
LOG.exception(exc)
|
||||||
|
raise exception.PoolNotFound(name=name)
|
||||||
|
|
||||||
|
def get_volume_list(self):
|
||||||
|
return self.cinder.volumes.list(search_opts={'all_tenants': True})
|
||||||
|
|
||||||
|
def get_volume_type_list(self):
|
||||||
|
return self.cinder.volume_types.list()
|
||||||
|
|
||||||
|
def get_volume_type_by_backendname(self, backendname):
|
||||||
|
volume_type_list = self.get_volume_type_list()
|
||||||
|
|
||||||
|
volume_type = list(filter(
|
||||||
|
lambda volume_type:
|
||||||
|
volume_type.extra_specs.get(
|
||||||
|
'volume_backend_name') == backendname, volume_type_list))
|
||||||
|
if volume_type:
|
||||||
|
return volume_type[0].name
|
||||||
|
else:
|
||||||
|
return ""
|
||||||
14
watcher/common/clients.py
Normal file → Executable file
14
watcher/common/clients.py
Normal file → Executable file
@@ -14,6 +14,7 @@ from ceilometerclient import client as ceclient
|
|||||||
from cinderclient import client as ciclient
|
from cinderclient import client as ciclient
|
||||||
from glanceclient import client as glclient
|
from glanceclient import client as glclient
|
||||||
from gnocchiclient import client as gnclient
|
from gnocchiclient import client as gnclient
|
||||||
|
from ironicclient import client as irclient
|
||||||
from keystoneauth1 import loading as ka_loading
|
from keystoneauth1 import loading as ka_loading
|
||||||
from keystoneclient import client as keyclient
|
from keystoneclient import client as keyclient
|
||||||
from monascaclient import client as monclient
|
from monascaclient import client as monclient
|
||||||
@@ -45,6 +46,7 @@ class OpenStackClients(object):
|
|||||||
self._ceilometer = None
|
self._ceilometer = None
|
||||||
self._monasca = None
|
self._monasca = None
|
||||||
self._neutron = None
|
self._neutron = None
|
||||||
|
self._ironic = None
|
||||||
|
|
||||||
def _get_keystone_session(self):
|
def _get_keystone_session(self):
|
||||||
auth = ka_loading.load_auth_from_conf_options(CONF,
|
auth = ka_loading.load_auth_from_conf_options(CONF,
|
||||||
@@ -188,3 +190,15 @@ class OpenStackClients(object):
|
|||||||
session=self.session)
|
session=self.session)
|
||||||
self._neutron.format = 'json'
|
self._neutron.format = 'json'
|
||||||
return self._neutron
|
return self._neutron
|
||||||
|
|
||||||
|
@exception.wrap_keystone_exception
|
||||||
|
def ironic(self):
|
||||||
|
if self._ironic:
|
||||||
|
return self._ironic
|
||||||
|
|
||||||
|
ironicclient_version = self._get_client_option('ironic', 'api_version')
|
||||||
|
endpoint_type = self._get_client_option('ironic', 'endpoint_type')
|
||||||
|
self._ironic = irclient.get_client(ironicclient_version,
|
||||||
|
ironic_url=endpoint_type,
|
||||||
|
session=self.session)
|
||||||
|
return self._ironic
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ class RequestContext(context.RequestContext):
|
|||||||
# oslo_context's RequestContext.to_dict() generates this field, we can
|
# oslo_context's RequestContext.to_dict() generates this field, we can
|
||||||
# safely ignore this as we don't use it.
|
# safely ignore this as we don't use it.
|
||||||
kwargs.pop('user_identity', None)
|
kwargs.pop('user_identity', None)
|
||||||
|
kwargs.pop('global_request_id', None)
|
||||||
if kwargs:
|
if kwargs:
|
||||||
LOG.warning('Arguments dropped when creating context: %s',
|
LOG.warning('Arguments dropped when creating context: %s',
|
||||||
str(kwargs))
|
str(kwargs))
|
||||||
|
|||||||
@@ -202,6 +202,10 @@ class InvalidUuidOrName(Invalid):
|
|||||||
msg_fmt = _("Expected a logical name or uuid but received %(name)s")
|
msg_fmt = _("Expected a logical name or uuid but received %(name)s")
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidIntervalOrCron(Invalid):
|
||||||
|
msg_fmt = _("Expected an interval or cron syntax but received %(name)s")
|
||||||
|
|
||||||
|
|
||||||
class GoalNotFound(ResourceNotFound):
|
class GoalNotFound(ResourceNotFound):
|
||||||
msg_fmt = _("Goal %(goal)s could not be found")
|
msg_fmt = _("Goal %(goal)s could not be found")
|
||||||
|
|
||||||
@@ -274,6 +278,10 @@ class ActionPlanReferenced(Invalid):
|
|||||||
"multiple actions")
|
"multiple actions")
|
||||||
|
|
||||||
|
|
||||||
|
class ActionPlanCancelled(WatcherException):
|
||||||
|
msg_fmt = _("Action Plan with UUID %(uuid)s is cancelled by user")
|
||||||
|
|
||||||
|
|
||||||
class ActionPlanIsOngoing(Conflict):
|
class ActionPlanIsOngoing(Conflict):
|
||||||
msg_fmt = _("Action Plan %(action_plan)s is currently running.")
|
msg_fmt = _("Action Plan %(action_plan)s is currently running.")
|
||||||
|
|
||||||
@@ -414,6 +422,10 @@ class WildcardCharacterIsUsed(WatcherException):
|
|||||||
"wildcard character.")
|
"wildcard character.")
|
||||||
|
|
||||||
|
|
||||||
|
class CronFormatIsInvalid(WatcherException):
|
||||||
|
msg_fmt = _("Provided cron is invalid: %(message)s")
|
||||||
|
|
||||||
|
|
||||||
# Model
|
# Model
|
||||||
|
|
||||||
class ComputeResourceNotFound(WatcherException):
|
class ComputeResourceNotFound(WatcherException):
|
||||||
@@ -428,6 +440,22 @@ class ComputeNodeNotFound(ComputeResourceNotFound):
|
|||||||
msg_fmt = _("The compute node %(name)s could not be found")
|
msg_fmt = _("The compute node %(name)s could not be found")
|
||||||
|
|
||||||
|
|
||||||
|
class StorageResourceNotFound(WatcherException):
|
||||||
|
msg_fmt = _("The storage resource '%(name)s' could not be found")
|
||||||
|
|
||||||
|
|
||||||
|
class StorageNodeNotFound(StorageResourceNotFound):
|
||||||
|
msg_fmt = _("The storage node %(name)s could not be found")
|
||||||
|
|
||||||
|
|
||||||
|
class PoolNotFound(StorageResourceNotFound):
|
||||||
|
msg_fmt = _("The pool %(name)s could not be found")
|
||||||
|
|
||||||
|
|
||||||
|
class VolumeNotFound(StorageResourceNotFound):
|
||||||
|
msg_fmt = _("The volume '%(name)s' could not be found")
|
||||||
|
|
||||||
|
|
||||||
class LoadingError(WatcherException):
|
class LoadingError(WatcherException):
|
||||||
msg_fmt = _("Error loading plugin '%(name)s'")
|
msg_fmt = _("Error loading plugin '%(name)s'")
|
||||||
|
|
||||||
|
|||||||
@@ -85,6 +85,20 @@ class NovaHelper(object):
|
|||||||
def find_instance(self, instance_id):
|
def find_instance(self, instance_id):
|
||||||
return self.nova.servers.get(instance_id)
|
return self.nova.servers.get(instance_id)
|
||||||
|
|
||||||
|
def confirm_resize(self, instance, previous_status, retry=60):
|
||||||
|
instance.confirm_resize()
|
||||||
|
instance = self.nova.servers.get(instance.id)
|
||||||
|
while instance.status != previous_status and retry:
|
||||||
|
instance = self.nova.servers.get(instance.id)
|
||||||
|
retry -= 1
|
||||||
|
time.sleep(1)
|
||||||
|
if instance.status == previous_status:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
LOG.debug("confirm resize failed for the "
|
||||||
|
"instance %s" % instance.id)
|
||||||
|
return False
|
||||||
|
|
||||||
def wait_for_volume_status(self, volume, status, timeout=60,
|
def wait_for_volume_status(self, volume, status, timeout=60,
|
||||||
poll_interval=1):
|
poll_interval=1):
|
||||||
"""Wait until volume reaches given status.
|
"""Wait until volume reaches given status.
|
||||||
@@ -105,8 +119,9 @@ class NovaHelper(object):
|
|||||||
% (volume.id, status, timeout))
|
% (volume.id, status, timeout))
|
||||||
return volume.status == status
|
return volume.status == status
|
||||||
|
|
||||||
def watcher_non_live_migrate_instance(self, instance_id, node_id,
|
def watcher_non_live_migrate_instance(self, instance_id, dest_hostname,
|
||||||
keep_original_image_name=True):
|
keep_original_image_name=True,
|
||||||
|
retry=120):
|
||||||
"""This method migrates a given instance
|
"""This method migrates a given instance
|
||||||
|
|
||||||
using an image of this instance and creating a new instance
|
using an image of this instance and creating a new instance
|
||||||
@@ -118,6 +133,9 @@ class NovaHelper(object):
|
|||||||
It returns True if the migration was successful,
|
It returns True if the migration was successful,
|
||||||
False otherwise.
|
False otherwise.
|
||||||
|
|
||||||
|
if destination hostname not given, this method calls nova api
|
||||||
|
to migrate the instance.
|
||||||
|
|
||||||
:param instance_id: the unique id of the instance to migrate.
|
:param instance_id: the unique id of the instance to migrate.
|
||||||
:param keep_original_image_name: flag indicating whether the
|
:param keep_original_image_name: flag indicating whether the
|
||||||
image name from which the original instance was built must be
|
image name from which the original instance was built must be
|
||||||
@@ -125,10 +143,8 @@ class NovaHelper(object):
|
|||||||
If this flag is False, a temporary image name is built
|
If this flag is False, a temporary image name is built
|
||||||
"""
|
"""
|
||||||
new_image_name = ""
|
new_image_name = ""
|
||||||
|
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"Trying a non-live migrate of instance '%s' "
|
"Trying a non-live migrate of instance '%s' " % instance_id)
|
||||||
"using a temporary image ..." % instance_id)
|
|
||||||
|
|
||||||
# Looking for the instance to migrate
|
# Looking for the instance to migrate
|
||||||
instance = self.find_instance(instance_id)
|
instance = self.find_instance(instance_id)
|
||||||
@@ -136,10 +152,38 @@ class NovaHelper(object):
|
|||||||
LOG.debug("Instance %s not found !" % instance_id)
|
LOG.debug("Instance %s not found !" % instance_id)
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
|
# NOTE: If destination node is None call Nova API to migrate
|
||||||
|
# instance
|
||||||
host_name = getattr(instance, "OS-EXT-SRV-ATTR:host")
|
host_name = getattr(instance, "OS-EXT-SRV-ATTR:host")
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"Instance %s found on host '%s'." % (instance_id, host_name))
|
"Instance %s found on host '%s'." % (instance_id, host_name))
|
||||||
|
|
||||||
|
if dest_hostname is None:
|
||||||
|
previous_status = getattr(instance, 'status')
|
||||||
|
|
||||||
|
instance.migrate()
|
||||||
|
instance = self.nova.servers.get(instance_id)
|
||||||
|
while (getattr(instance, 'status') not in
|
||||||
|
["VERIFY_RESIZE", "ERROR"] and retry):
|
||||||
|
instance = self.nova.servers.get(instance.id)
|
||||||
|
time.sleep(2)
|
||||||
|
retry -= 1
|
||||||
|
new_hostname = getattr(instance, 'OS-EXT-SRV-ATTR:host')
|
||||||
|
|
||||||
|
if (host_name != new_hostname and
|
||||||
|
instance.status == 'VERIFY_RESIZE'):
|
||||||
|
if not self.confirm_resize(instance, previous_status):
|
||||||
|
return False
|
||||||
|
LOG.debug(
|
||||||
|
"cold migration succeeded : "
|
||||||
|
"instance %s is now on host '%s'." % (
|
||||||
|
instance_id, new_hostname))
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
LOG.debug(
|
||||||
|
"cold migration for instance %s failed" % instance_id)
|
||||||
|
return False
|
||||||
|
|
||||||
if not keep_original_image_name:
|
if not keep_original_image_name:
|
||||||
# randrange gives you an integral value
|
# randrange gives you an integral value
|
||||||
irand = random.randint(0, 1000)
|
irand = random.randint(0, 1000)
|
||||||
@@ -268,7 +312,7 @@ class NovaHelper(object):
|
|||||||
# We create the new instance from
|
# We create the new instance from
|
||||||
# the intermediate image of the original instance
|
# the intermediate image of the original instance
|
||||||
new_instance = self. \
|
new_instance = self. \
|
||||||
create_instance(node_id,
|
create_instance(dest_hostname,
|
||||||
instance_name,
|
instance_name,
|
||||||
image_uuid,
|
image_uuid,
|
||||||
flavor_name,
|
flavor_name,
|
||||||
@@ -389,15 +433,15 @@ class NovaHelper(object):
|
|||||||
False otherwise.
|
False otherwise.
|
||||||
|
|
||||||
:param instance_id: the unique id of the instance to migrate.
|
:param instance_id: the unique id of the instance to migrate.
|
||||||
:param dest_hostname: the name of the destination compute node.
|
:param dest_hostname: the name of the destination compute node, if
|
||||||
|
destination_node is None, nova scheduler choose
|
||||||
|
the destination host
|
||||||
:param block_migration: No shared storage is required.
|
:param block_migration: No shared storage is required.
|
||||||
"""
|
"""
|
||||||
LOG.debug("Trying a live migrate of instance %s to host '%s'" % (
|
LOG.debug("Trying to live migrate instance %s " % (instance_id))
|
||||||
instance_id, dest_hostname))
|
|
||||||
|
|
||||||
# Looking for the instance to migrate
|
# Looking for the instance to migrate
|
||||||
instance = self.find_instance(instance_id)
|
instance = self.find_instance(instance_id)
|
||||||
|
|
||||||
if not instance:
|
if not instance:
|
||||||
LOG.debug("Instance not found: %s" % instance_id)
|
LOG.debug("Instance not found: %s" % instance_id)
|
||||||
return False
|
return False
|
||||||
@@ -409,6 +453,29 @@ class NovaHelper(object):
|
|||||||
instance.live_migrate(host=dest_hostname,
|
instance.live_migrate(host=dest_hostname,
|
||||||
block_migration=block_migration,
|
block_migration=block_migration,
|
||||||
disk_over_commit=True)
|
disk_over_commit=True)
|
||||||
|
|
||||||
|
instance = self.nova.servers.get(instance_id)
|
||||||
|
|
||||||
|
# NOTE: If destination host is not specified for live migration
|
||||||
|
# let nova scheduler choose the destination host.
|
||||||
|
if dest_hostname is None:
|
||||||
|
while (instance.status not in ['ACTIVE', 'ERROR'] and retry):
|
||||||
|
instance = self.nova.servers.get(instance.id)
|
||||||
|
LOG.debug(
|
||||||
|
'Waiting the migration of {0}'.format(instance.id))
|
||||||
|
time.sleep(1)
|
||||||
|
retry -= 1
|
||||||
|
new_hostname = getattr(instance, 'OS-EXT-SRV-ATTR:host')
|
||||||
|
|
||||||
|
if host_name != new_hostname and instance.status == 'ACTIVE':
|
||||||
|
LOG.debug(
|
||||||
|
"Live migration succeeded : "
|
||||||
|
"instance %s is now on host '%s'." % (
|
||||||
|
instance_id, new_hostname))
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
while getattr(instance,
|
while getattr(instance,
|
||||||
'OS-EXT-SRV-ATTR:host') != dest_hostname \
|
'OS-EXT-SRV-ATTR:host') != dest_hostname \
|
||||||
and retry:
|
and retry:
|
||||||
@@ -432,6 +499,43 @@ class NovaHelper(object):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def abort_live_migrate(self, instance_id, source, destination, retry=240):
|
||||||
|
LOG.debug("Aborting live migration of instance %s" % instance_id)
|
||||||
|
migration = self.get_running_migration(instance_id)
|
||||||
|
if migration:
|
||||||
|
migration_id = getattr(migration[0], "id")
|
||||||
|
try:
|
||||||
|
self.nova.server_migrations.live_migration_abort(
|
||||||
|
server=instance_id, migration=migration_id)
|
||||||
|
except exception as e:
|
||||||
|
# Note: Does not return from here, as abort request can't be
|
||||||
|
# accepted but migration still going on.
|
||||||
|
LOG.exception(e)
|
||||||
|
else:
|
||||||
|
LOG.debug(
|
||||||
|
"No running migrations found for instance %s" % instance_id)
|
||||||
|
|
||||||
|
while retry:
|
||||||
|
instance = self.nova.servers.get(instance_id)
|
||||||
|
if (getattr(instance, 'OS-EXT-STS:task_state') is None and
|
||||||
|
getattr(instance, 'status') in ['ACTIVE', 'ERROR']):
|
||||||
|
break
|
||||||
|
time.sleep(2)
|
||||||
|
retry -= 1
|
||||||
|
instance_host = getattr(instance, 'OS-EXT-SRV-ATTR:host')
|
||||||
|
instance_status = getattr(instance, 'status')
|
||||||
|
|
||||||
|
# Abort live migration successfull, action is cancelled
|
||||||
|
if instance_host == source and instance_status == 'ACTIVE':
|
||||||
|
return True
|
||||||
|
# Nova Unable to abort live migration, action is succeded
|
||||||
|
elif instance_host == destination and instance_status == 'ACTIVE':
|
||||||
|
return False
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise Exception("Live migration execution and abort both failed "
|
||||||
|
"for the instance %s" % instance_id)
|
||||||
|
|
||||||
def enable_service_nova_compute(self, hostname):
|
def enable_service_nova_compute(self, hostname):
|
||||||
if self.nova.services.enable(host=hostname,
|
if self.nova.services.enable(host=hostname,
|
||||||
binary='nova-compute'). \
|
binary='nova-compute'). \
|
||||||
@@ -573,6 +677,9 @@ class NovaHelper(object):
|
|||||||
if not instance:
|
if not instance:
|
||||||
LOG.debug("Instance not found: %s" % instance_id)
|
LOG.debug("Instance not found: %s" % instance_id)
|
||||||
return False
|
return False
|
||||||
|
elif getattr(instance, 'OS-EXT-STS:vm_state') == "stopped":
|
||||||
|
LOG.debug("Instance has been stopped: %s" % instance_id)
|
||||||
|
return True
|
||||||
else:
|
else:
|
||||||
self.nova.servers.stop(instance_id)
|
self.nova.servers.stop(instance_id)
|
||||||
|
|
||||||
@@ -754,3 +861,6 @@ class NovaHelper(object):
|
|||||||
instance.flavor[attr] = default
|
instance.flavor[attr] = default
|
||||||
continue
|
continue
|
||||||
instance.flavor[attr] = getattr(flavor, attr, default)
|
instance.flavor[attr] = getattr(flavor, attr, default)
|
||||||
|
|
||||||
|
def get_running_migration(self, instance_id):
|
||||||
|
return self.nova.server_migrations.list(server=instance_id)
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ from oslo_config import cfg
|
|||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
import oslo_messaging as messaging
|
import oslo_messaging as messaging
|
||||||
|
|
||||||
|
from oslo_messaging.rpc import dispatcher
|
||||||
|
|
||||||
from watcher.common import context as watcher_context
|
from watcher.common import context as watcher_context
|
||||||
from watcher.common import exception
|
from watcher.common import exception
|
||||||
|
|
||||||
@@ -51,11 +53,10 @@ JsonPayloadSerializer = messaging.JsonPayloadSerializer
|
|||||||
def init(conf):
|
def init(conf):
|
||||||
global TRANSPORT, NOTIFICATION_TRANSPORT, NOTIFIER
|
global TRANSPORT, NOTIFICATION_TRANSPORT, NOTIFIER
|
||||||
exmods = get_allowed_exmods()
|
exmods = get_allowed_exmods()
|
||||||
TRANSPORT = messaging.get_transport(conf,
|
TRANSPORT = messaging.get_rpc_transport(
|
||||||
allowed_remote_exmods=exmods)
|
conf, allowed_remote_exmods=exmods)
|
||||||
NOTIFICATION_TRANSPORT = messaging.get_notification_transport(
|
NOTIFICATION_TRANSPORT = messaging.get_notification_transport(
|
||||||
conf,
|
conf, allowed_remote_exmods=exmods)
|
||||||
allowed_remote_exmods=exmods)
|
|
||||||
|
|
||||||
serializer = RequestContextSerializer(JsonPayloadSerializer())
|
serializer = RequestContextSerializer(JsonPayloadSerializer())
|
||||||
if not conf.notification_level:
|
if not conf.notification_level:
|
||||||
@@ -128,12 +129,14 @@ def get_client(target, version_cap=None, serializer=None):
|
|||||||
|
|
||||||
def get_server(target, endpoints, serializer=None):
|
def get_server(target, endpoints, serializer=None):
|
||||||
assert TRANSPORT is not None
|
assert TRANSPORT is not None
|
||||||
|
access_policy = dispatcher.DefaultRPCAccessPolicy
|
||||||
serializer = RequestContextSerializer(serializer)
|
serializer = RequestContextSerializer(serializer)
|
||||||
return messaging.get_rpc_server(TRANSPORT,
|
return messaging.get_rpc_server(TRANSPORT,
|
||||||
target,
|
target,
|
||||||
endpoints,
|
endpoints,
|
||||||
executor='eventlet',
|
executor='eventlet',
|
||||||
serializer=serializer)
|
serializer=serializer,
|
||||||
|
access_policy=access_policy)
|
||||||
|
|
||||||
|
|
||||||
def get_notifier(publisher_id):
|
def get_notifier(publisher_id):
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ from oslo_reports import opts as gmr_opts
|
|||||||
from oslo_service import service
|
from oslo_service import service
|
||||||
from oslo_service import wsgi
|
from oslo_service import wsgi
|
||||||
|
|
||||||
|
from oslo_messaging.rpc import dispatcher
|
||||||
|
|
||||||
from watcher._i18n import _
|
from watcher._i18n import _
|
||||||
from watcher.api import app
|
from watcher.api import app
|
||||||
from watcher.common import config
|
from watcher.common import config
|
||||||
@@ -110,16 +112,19 @@ class WSGIService(service.ServiceBase):
|
|||||||
|
|
||||||
class ServiceHeartbeat(scheduling.BackgroundSchedulerService):
|
class ServiceHeartbeat(scheduling.BackgroundSchedulerService):
|
||||||
|
|
||||||
|
service_name = None
|
||||||
|
|
||||||
def __init__(self, gconfig=None, service_name=None, **kwargs):
|
def __init__(self, gconfig=None, service_name=None, **kwargs):
|
||||||
gconfig = None or {}
|
gconfig = None or {}
|
||||||
super(ServiceHeartbeat, self).__init__(gconfig, **kwargs)
|
super(ServiceHeartbeat, self).__init__(gconfig, **kwargs)
|
||||||
self.service_name = service_name
|
ServiceHeartbeat.service_name = service_name
|
||||||
self.context = context.make_context()
|
self.context = context.make_context()
|
||||||
|
self.send_beat()
|
||||||
|
|
||||||
def send_beat(self):
|
def send_beat(self):
|
||||||
host = CONF.host
|
host = CONF.host
|
||||||
watcher_list = objects.Service.list(
|
watcher_list = objects.Service.list(
|
||||||
self.context, filters={'name': self.service_name,
|
self.context, filters={'name': ServiceHeartbeat.service_name,
|
||||||
'host': host})
|
'host': host})
|
||||||
if watcher_list:
|
if watcher_list:
|
||||||
watcher_service = watcher_list[0]
|
watcher_service = watcher_list[0]
|
||||||
@@ -127,7 +132,7 @@ class ServiceHeartbeat(scheduling.BackgroundSchedulerService):
|
|||||||
watcher_service.save()
|
watcher_service.save()
|
||||||
else:
|
else:
|
||||||
watcher_service = objects.Service(self.context)
|
watcher_service = objects.Service(self.context)
|
||||||
watcher_service.name = self.service_name
|
watcher_service.name = ServiceHeartbeat.service_name
|
||||||
watcher_service.host = host
|
watcher_service.host = host
|
||||||
watcher_service.create()
|
watcher_service.create()
|
||||||
|
|
||||||
@@ -135,6 +140,10 @@ class ServiceHeartbeat(scheduling.BackgroundSchedulerService):
|
|||||||
self.add_job(self.send_beat, 'interval', seconds=60,
|
self.add_job(self.send_beat, 'interval', seconds=60,
|
||||||
next_run_time=datetime.datetime.now())
|
next_run_time=datetime.datetime.now())
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_service_name(cls):
|
||||||
|
return CONF.host, cls.service_name
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
"""Start service."""
|
"""Start service."""
|
||||||
self.add_heartbeat_job()
|
self.add_heartbeat_job()
|
||||||
@@ -168,6 +177,13 @@ class Service(service.ServiceBase):
|
|||||||
self.conductor_topic = self.manager.conductor_topic
|
self.conductor_topic = self.manager.conductor_topic
|
||||||
self.notification_topics = self.manager.notification_topics
|
self.notification_topics = self.manager.notification_topics
|
||||||
|
|
||||||
|
self.heartbeat = None
|
||||||
|
|
||||||
|
self.service_name = self.manager.service_name
|
||||||
|
if self.service_name:
|
||||||
|
self.heartbeat = ServiceHeartbeat(
|
||||||
|
service_name=self.manager.service_name)
|
||||||
|
|
||||||
self.conductor_endpoints = [
|
self.conductor_endpoints = [
|
||||||
ep(self) for ep in self.manager.conductor_endpoints
|
ep(self) for ep in self.manager.conductor_endpoints
|
||||||
]
|
]
|
||||||
@@ -183,8 +199,6 @@ class Service(service.ServiceBase):
|
|||||||
self.conductor_topic_handler = None
|
self.conductor_topic_handler = None
|
||||||
self.notification_handler = None
|
self.notification_handler = None
|
||||||
|
|
||||||
self.heartbeat = None
|
|
||||||
|
|
||||||
if self.conductor_topic and self.conductor_endpoints:
|
if self.conductor_topic and self.conductor_endpoints:
|
||||||
self.conductor_topic_handler = self.build_topic_handler(
|
self.conductor_topic_handler = self.build_topic_handler(
|
||||||
self.conductor_topic, self.conductor_endpoints)
|
self.conductor_topic, self.conductor_endpoints)
|
||||||
@@ -192,15 +206,11 @@ class Service(service.ServiceBase):
|
|||||||
self.notification_handler = self.build_notification_handler(
|
self.notification_handler = self.build_notification_handler(
|
||||||
self.notification_topics, self.notification_endpoints
|
self.notification_topics, self.notification_endpoints
|
||||||
)
|
)
|
||||||
self.service_name = self.manager.service_name
|
|
||||||
if self.service_name:
|
|
||||||
self.heartbeat = ServiceHeartbeat(
|
|
||||||
service_name=self.manager.service_name)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def transport(self):
|
def transport(self):
|
||||||
if self._transport is None:
|
if self._transport is None:
|
||||||
self._transport = om.get_transport(CONF)
|
self._transport = om.get_rpc_transport(CONF)
|
||||||
return self._transport
|
return self._transport
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -225,6 +235,7 @@ class Service(service.ServiceBase):
|
|||||||
self.conductor_client = c
|
self.conductor_client = c
|
||||||
|
|
||||||
def build_topic_handler(self, topic_name, endpoints=()):
|
def build_topic_handler(self, topic_name, endpoints=()):
|
||||||
|
access_policy = dispatcher.DefaultRPCAccessPolicy
|
||||||
serializer = rpc.RequestContextSerializer(rpc.JsonPayloadSerializer())
|
serializer = rpc.RequestContextSerializer(rpc.JsonPayloadSerializer())
|
||||||
target = om.Target(
|
target = om.Target(
|
||||||
topic=topic_name,
|
topic=topic_name,
|
||||||
@@ -234,7 +245,8 @@ class Service(service.ServiceBase):
|
|||||||
)
|
)
|
||||||
return om.get_rpc_server(
|
return om.get_rpc_server(
|
||||||
self.transport, target, endpoints,
|
self.transport, target, endpoints,
|
||||||
executor='eventlet', serializer=serializer)
|
executor='eventlet', serializer=serializer,
|
||||||
|
access_policy=access_policy)
|
||||||
|
|
||||||
def build_notification_handler(self, topic_names, endpoints=()):
|
def build_notification_handler(self, topic_names, endpoints=()):
|
||||||
serializer = rpc.RequestContextSerializer(rpc.JsonPayloadSerializer())
|
serializer = rpc.RequestContextSerializer(rpc.JsonPayloadSerializer())
|
||||||
|
|||||||
@@ -16,8 +16,11 @@
|
|||||||
|
|
||||||
"""Utilities and helper functions."""
|
"""Utilities and helper functions."""
|
||||||
|
|
||||||
|
import datetime
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
from croniter import croniter
|
||||||
|
|
||||||
from jsonschema import validators
|
from jsonschema import validators
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from oslo_utils import strutils
|
from oslo_utils import strutils
|
||||||
@@ -63,6 +66,15 @@ is_int_like = strutils.is_int_like
|
|||||||
strtime = timeutils.strtime
|
strtime = timeutils.strtime
|
||||||
|
|
||||||
|
|
||||||
|
def is_cron_like(value):
|
||||||
|
"""Return True is submitted value is like cron syntax"""
|
||||||
|
try:
|
||||||
|
croniter(value, datetime.datetime.now())
|
||||||
|
except Exception as e:
|
||||||
|
raise exception.CronFormatIsInvalid(message=str(e))
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def safe_rstrip(value, chars=None):
|
def safe_rstrip(value, chars=None):
|
||||||
"""Removes trailing characters from a string if that does not make it empty
|
"""Removes trailing characters from a string if that does not make it empty
|
||||||
|
|
||||||
@@ -111,7 +123,7 @@ def extend_with_default(validator_class):
|
|||||||
|
|
||||||
def set_defaults(validator, properties, instance, schema):
|
def set_defaults(validator, properties, instance, schema):
|
||||||
for prop, subschema in properties.items():
|
for prop, subschema in properties.items():
|
||||||
if "default" in subschema:
|
if "default" in subschema and instance is not None:
|
||||||
instance.setdefault(prop, subschema["default"])
|
instance.setdefault(prop, subschema["default"])
|
||||||
|
|
||||||
for error in validate_properties(
|
for error in validate_properties(
|
||||||
@@ -128,6 +140,9 @@ def extend_with_strict_schema(validator_class):
|
|||||||
validate_properties = validator_class.VALIDATORS["properties"]
|
validate_properties = validator_class.VALIDATORS["properties"]
|
||||||
|
|
||||||
def strict_schema(validator, properties, instance, schema):
|
def strict_schema(validator, properties, instance, schema):
|
||||||
|
if instance is None:
|
||||||
|
return
|
||||||
|
|
||||||
for para in instance.keys():
|
for para in instance.keys():
|
||||||
if para not in properties.keys():
|
if para not in properties.keys():
|
||||||
raise exception.AuditParameterNotAllowed(parameter=para)
|
raise exception.AuditParameterNotAllowed(parameter=para)
|
||||||
|
|||||||
2
watcher/conf/__init__.py
Normal file → Executable file
2
watcher/conf/__init__.py
Normal file → Executable file
@@ -29,6 +29,7 @@ from watcher.conf import decision_engine
|
|||||||
from watcher.conf import exception
|
from watcher.conf import exception
|
||||||
from watcher.conf import glance_client
|
from watcher.conf import glance_client
|
||||||
from watcher.conf import gnocchi_client
|
from watcher.conf import gnocchi_client
|
||||||
|
from watcher.conf import ironic_client
|
||||||
from watcher.conf import monasca_client
|
from watcher.conf import monasca_client
|
||||||
from watcher.conf import neutron_client
|
from watcher.conf import neutron_client
|
||||||
from watcher.conf import nova_client
|
from watcher.conf import nova_client
|
||||||
@@ -56,3 +57,4 @@ cinder_client.register_opts(CONF)
|
|||||||
ceilometer_client.register_opts(CONF)
|
ceilometer_client.register_opts(CONF)
|
||||||
neutron_client.register_opts(CONF)
|
neutron_client.register_opts(CONF)
|
||||||
clients_auth.register_opts(CONF)
|
clients_auth.register_opts(CONF)
|
||||||
|
ironic_client.register_opts(CONF)
|
||||||
|
|||||||
@@ -23,13 +23,13 @@ cinder_client = cfg.OptGroup(name='cinder_client',
|
|||||||
|
|
||||||
CINDER_CLIENT_OPTS = [
|
CINDER_CLIENT_OPTS = [
|
||||||
cfg.StrOpt('api_version',
|
cfg.StrOpt('api_version',
|
||||||
default='2',
|
default='3',
|
||||||
help='Version of Cinder API to use in cinderclient.'),
|
help='Version of Cinder API to use in cinderclient.'),
|
||||||
cfg.StrOpt('endpoint_type',
|
cfg.StrOpt('endpoint_type',
|
||||||
default='internalURL',
|
default='publicURL',
|
||||||
help='Type of endpoint to use in cinderclient.'
|
help='Type of endpoint to use in cinderclient.'
|
||||||
'Supported values: internalURL, publicURL, adminURL'
|
'Supported values: internalURL, publicURL, adminURL'
|
||||||
'The default is internalURL.')]
|
'The default is publicURL.')]
|
||||||
|
|
||||||
|
|
||||||
def register_opts(conf):
|
def register_opts(conf):
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ WATCHER_DECISION_ENGINE_OPTS = [
|
|||||||
'-whose number of hours has been offset by this value-'
|
'-whose number of hours has been offset by this value-'
|
||||||
' is older that the current time.'),
|
' is older that the current time.'),
|
||||||
cfg.IntOpt('check_periodic_interval',
|
cfg.IntOpt('check_periodic_interval',
|
||||||
default=30*60,
|
default=30 * 60,
|
||||||
help='Interval (in seconds) for checking action plan expiry.')
|
help='Interval (in seconds) for checking action plan expiry.')
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user