Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
19fdd1557e | ||
|
|
641989b424 | ||
|
|
8814c09087 | ||
|
|
eb4f46b703 | ||
|
|
2f33dd10c0 | ||
|
|
1a197ab801 |
@@ -2,3 +2,4 @@
|
|||||||
host=review.openstack.org
|
host=review.openstack.org
|
||||||
port=29418
|
port=29418
|
||||||
project=openstack/watcher.git
|
project=openstack/watcher.git
|
||||||
|
defaultbranch=stable/newton
|
||||||
|
|||||||
6
MANIFEST.in
Normal file
6
MANIFEST.in
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
include AUTHORS
|
||||||
|
include ChangeLog
|
||||||
|
exclude .gitignore
|
||||||
|
exclude .gitreview
|
||||||
|
|
||||||
|
global-exclude *.pyc
|
||||||
15
README.rst
15
README.rst
@@ -1,12 +1,3 @@
|
|||||||
========================
|
|
||||||
Team and repository tags
|
|
||||||
========================
|
|
||||||
|
|
||||||
.. image:: http://governance.openstack.org/badges/watcher.svg
|
|
||||||
:target: http://governance.openstack.org/reference/tags/index.html
|
|
||||||
|
|
||||||
.. Change things from this point on
|
|
||||||
|
|
||||||
..
|
..
|
||||||
Except where otherwise noted, this document is licensed under Creative
|
Except where otherwise noted, this document is licensed under Creative
|
||||||
Commons Attribution 3.0 License. You can view the license at:
|
Commons Attribution 3.0 License. You can view the license at:
|
||||||
@@ -19,8 +10,10 @@ Watcher
|
|||||||
|
|
||||||
OpenStack Watcher provides a flexible and scalable resource optimization
|
OpenStack Watcher provides a flexible and scalable resource optimization
|
||||||
service for multi-tenant OpenStack-based clouds.
|
service for multi-tenant OpenStack-based clouds.
|
||||||
Watcher provides a robust framework to realize a wide range of cloud
|
Watcher provides a complete optimization loop-including everything from a
|
||||||
optimization goals, including the reduction of data center
|
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
|
operating costs, increased system performance via intelligent virtual machine
|
||||||
migration, increased energy efficiency-and more!
|
migration, increased energy efficiency-and more!
|
||||||
|
|
||||||
|
|||||||
@@ -126,8 +126,6 @@ function create_watcher_conf {
|
|||||||
iniset $WATCHER_CONF oslo_messaging_rabbit rabbit_password $RABBIT_PASSWORD
|
iniset $WATCHER_CONF oslo_messaging_rabbit rabbit_password $RABBIT_PASSWORD
|
||||||
iniset $WATCHER_CONF oslo_messaging_rabbit rabbit_host $RABBIT_HOST
|
iniset $WATCHER_CONF oslo_messaging_rabbit rabbit_host $RABBIT_HOST
|
||||||
|
|
||||||
iniset $WATCHER_CONF oslo_messaging_notifications driver "messaging"
|
|
||||||
|
|
||||||
iniset $NOVA_CONF oslo_messaging_notifications topics "notifications,watcher_notifications"
|
iniset $NOVA_CONF oslo_messaging_notifications topics "notifications,watcher_notifications"
|
||||||
|
|
||||||
configure_auth_token_middleware $WATCHER_CONF watcher $WATCHER_AUTH_CACHE_DIR
|
configure_auth_token_middleware $WATCHER_CONF watcher $WATCHER_AUTH_CACHE_DIR
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ ENABLED_SERVICES+=,q-svc,q-dhcp,q-meta,q-agt,q-l3,neutron
|
|||||||
enable_service n-cauth
|
enable_service n-cauth
|
||||||
|
|
||||||
# Enable the Watcher Dashboard plugin
|
# Enable the Watcher Dashboard plugin
|
||||||
# enable_plugin watcher-dashboard git://git.openstack.org/openstack/watcher-dashboard
|
enable_plugin watcher-dashboard git://git.openstack.org/openstack/watcher-dashboard
|
||||||
|
|
||||||
# Enable the Watcher plugin
|
# Enable the Watcher plugin
|
||||||
enable_plugin watcher git://git.openstack.org/openstack/watcher
|
enable_plugin watcher git://git.openstack.org/openstack/watcher
|
||||||
|
|||||||
@@ -1,133 +0,0 @@
|
|||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
"""
|
|
||||||
This provides a sphinx extension able to list the implemented versioned
|
|
||||||
notifications into the developer documentation.
|
|
||||||
|
|
||||||
It is used via a single directive in the .rst file
|
|
||||||
|
|
||||||
.. versioned_notifications::
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
from sphinx.util.compat import Directive
|
|
||||||
from docutils import nodes
|
|
||||||
|
|
||||||
from watcher.notifications import base as notification
|
|
||||||
from watcher.objects import base
|
|
||||||
|
|
||||||
|
|
||||||
class VersionedNotificationDirective(Directive):
|
|
||||||
|
|
||||||
SAMPLE_ROOT = 'doc/notification_samples/'
|
|
||||||
TOGGLE_SCRIPT = """
|
|
||||||
<script>
|
|
||||||
jQuery(document).ready(function(){
|
|
||||||
jQuery('#%s-div').toggle('show');
|
|
||||||
jQuery('#%s-hideshow').on('click', function(event) {
|
|
||||||
jQuery('#%s-div').toggle('show');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
"""
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
notifications = self._collect_notifications()
|
|
||||||
return self._build_markup(notifications)
|
|
||||||
|
|
||||||
def _collect_notifications(self):
|
|
||||||
base.WatcherObjectRegistry.register_notification_objects()
|
|
||||||
notifications = []
|
|
||||||
ovos = base.WatcherObjectRegistry.obj_classes()
|
|
||||||
for name, cls in ovos.items():
|
|
||||||
cls = cls[0]
|
|
||||||
if (issubclass(cls, notification.NotificationBase) and
|
|
||||||
cls != notification.NotificationBase):
|
|
||||||
|
|
||||||
payload_name = cls.fields['payload'].objname
|
|
||||||
payload_cls = ovos[payload_name][0]
|
|
||||||
for sample in cls.samples:
|
|
||||||
notifications.append((cls.__name__,
|
|
||||||
payload_cls.__name__,
|
|
||||||
sample))
|
|
||||||
return sorted(notifications)
|
|
||||||
|
|
||||||
def _build_markup(self, notifications):
|
|
||||||
content = []
|
|
||||||
cols = ['Event type', 'Notification class', 'Payload class', 'Sample']
|
|
||||||
table = nodes.table()
|
|
||||||
content.append(table)
|
|
||||||
group = nodes.tgroup(cols=len(cols))
|
|
||||||
table.append(group)
|
|
||||||
|
|
||||||
head = nodes.thead()
|
|
||||||
group.append(head)
|
|
||||||
|
|
||||||
for _ in cols:
|
|
||||||
group.append(nodes.colspec(colwidth=1))
|
|
||||||
|
|
||||||
body = nodes.tbody()
|
|
||||||
group.append(body)
|
|
||||||
|
|
||||||
# fill the table header
|
|
||||||
row = nodes.row()
|
|
||||||
body.append(row)
|
|
||||||
for col_name in cols:
|
|
||||||
col = nodes.entry()
|
|
||||||
row.append(col)
|
|
||||||
text = nodes.strong(text=col_name)
|
|
||||||
col.append(text)
|
|
||||||
|
|
||||||
# fill the table content, one notification per row
|
|
||||||
for name, payload, sample_file in notifications:
|
|
||||||
event_type = sample_file[0: -5].replace('-', '.')
|
|
||||||
|
|
||||||
row = nodes.row()
|
|
||||||
body.append(row)
|
|
||||||
col = nodes.entry()
|
|
||||||
row.append(col)
|
|
||||||
text = nodes.literal(text=event_type)
|
|
||||||
col.append(text)
|
|
||||||
|
|
||||||
col = nodes.entry()
|
|
||||||
row.append(col)
|
|
||||||
text = nodes.literal(text=name)
|
|
||||||
col.append(text)
|
|
||||||
|
|
||||||
col = nodes.entry()
|
|
||||||
row.append(col)
|
|
||||||
text = nodes.literal(text=payload)
|
|
||||||
col.append(text)
|
|
||||||
|
|
||||||
col = nodes.entry()
|
|
||||||
row.append(col)
|
|
||||||
|
|
||||||
with open(self.SAMPLE_ROOT + sample_file, 'r') as f:
|
|
||||||
sample_content = f.read()
|
|
||||||
|
|
||||||
event_type = sample_file[0: -5]
|
|
||||||
html_str = self.TOGGLE_SCRIPT % ((event_type, ) * 3)
|
|
||||||
html_str += ("<input type='button' id='%s-hideshow' "
|
|
||||||
"value='hide/show sample'>" % event_type)
|
|
||||||
html_str += ("<div id='%s-div'><pre>%s</pre></div>"
|
|
||||||
% (event_type, sample_content))
|
|
||||||
|
|
||||||
raw = nodes.raw('', html_str, format="html")
|
|
||||||
col.append(raw)
|
|
||||||
|
|
||||||
return content
|
|
||||||
|
|
||||||
|
|
||||||
def setup(app):
|
|
||||||
app.add_directive('versioned_notifications',
|
|
||||||
VersionedNotificationDirective)
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
{
|
|
||||||
"priority": "INFO",
|
|
||||||
"payload": {
|
|
||||||
"watcher_object.data": {
|
|
||||||
"audit_type": "ONESHOT",
|
|
||||||
"parameters": {
|
|
||||||
"para2": "hello",
|
|
||||||
"para1": 3.2
|
|
||||||
},
|
|
||||||
"state": "PENDING",
|
|
||||||
"updated_at": null,
|
|
||||||
"deleted_at": null,
|
|
||||||
"goal": {
|
|
||||||
"watcher_object.data": {
|
|
||||||
"uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
|
||||||
"name": "dummy",
|
|
||||||
"updated_at": null,
|
|
||||||
"deleted_at": null,
|
|
||||||
"efficacy_specification": [],
|
|
||||||
"created_at": "2016-11-04T16:25:35Z",
|
|
||||||
"display_name": "Dummy goal"
|
|
||||||
},
|
|
||||||
"watcher_object.name": "GoalPayload",
|
|
||||||
"watcher_object.version": "1.0",
|
|
||||||
"watcher_object.namespace": "watcher"
|
|
||||||
},
|
|
||||||
"interval": null,
|
|
||||||
"scope": [],
|
|
||||||
"strategy": {
|
|
||||||
"watcher_object.data": {
|
|
||||||
"parameters_spec": {
|
|
||||||
"properties": {
|
|
||||||
"para2": {
|
|
||||||
"type": "string",
|
|
||||||
"default": "hello",
|
|
||||||
"description": "string parameter example"
|
|
||||||
},
|
|
||||||
"para1": {
|
|
||||||
"description": "number parameter example",
|
|
||||||
"maximum": 10.2,
|
|
||||||
"type": "number",
|
|
||||||
"default": 3.2,
|
|
||||||
"minimum": 1.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"name": "dummy",
|
|
||||||
"uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
|
|
||||||
"updated_at": null,
|
|
||||||
"deleted_at": null,
|
|
||||||
"created_at": "2016-11-04T16:25:35Z",
|
|
||||||
"display_name": "Dummy strategy"
|
|
||||||
},
|
|
||||||
"watcher_object.name": "StrategyPayload",
|
|
||||||
"watcher_object.version": "1.0",
|
|
||||||
"watcher_object.namespace": "watcher"
|
|
||||||
},
|
|
||||||
"created_at": "2016-11-04T16:29:20Z",
|
|
||||||
"uuid": "4a97b9dd-2023-43dc-b713-815bdd94d4d6"
|
|
||||||
},
|
|
||||||
"watcher_object.name": "AuditCreatePayload",
|
|
||||||
"watcher_object.version": "1.0",
|
|
||||||
"watcher_object.namespace": "watcher"
|
|
||||||
},
|
|
||||||
"publisher_id": "infra-optim:localhost",
|
|
||||||
"timestamp": "2016-11-04 16:31:36.264673 ",
|
|
||||||
"event_type": "audit.create",
|
|
||||||
"message_id": "cbcf9f2c-7c53-4b4d-91ec-db49cca024b6"
|
|
||||||
}
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
{
|
|
||||||
"priority": "INFO",
|
|
||||||
"payload": {
|
|
||||||
"watcher_object.data": {
|
|
||||||
"audit_type": "ONESHOT",
|
|
||||||
"parameters": {
|
|
||||||
"para2": "hello",
|
|
||||||
"para1": 3.2
|
|
||||||
},
|
|
||||||
"state": "DELETED",
|
|
||||||
"updated_at": null,
|
|
||||||
"deleted_at": null,
|
|
||||||
"goal": {
|
|
||||||
"watcher_object.data": {
|
|
||||||
"uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
|
||||||
"name": "dummy",
|
|
||||||
"updated_at": null,
|
|
||||||
"deleted_at": null,
|
|
||||||
"efficacy_specification": [],
|
|
||||||
"created_at": "2016-11-04T16:25:35Z",
|
|
||||||
"display_name": "Dummy goal"
|
|
||||||
},
|
|
||||||
"watcher_object.name": "GoalPayload",
|
|
||||||
"watcher_object.version": "1.0",
|
|
||||||
"watcher_object.namespace": "watcher"
|
|
||||||
},
|
|
||||||
"interval": null,
|
|
||||||
"scope": [],
|
|
||||||
"strategy": {
|
|
||||||
"watcher_object.data": {
|
|
||||||
"parameters_spec": {
|
|
||||||
"properties": {
|
|
||||||
"para2": {
|
|
||||||
"type": "string",
|
|
||||||
"default": "hello",
|
|
||||||
"description": "string parameter example"
|
|
||||||
},
|
|
||||||
"para1": {
|
|
||||||
"description": "number parameter example",
|
|
||||||
"maximum": 10.2,
|
|
||||||
"type": "number",
|
|
||||||
"default": 3.2,
|
|
||||||
"minimum": 1.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"name": "dummy",
|
|
||||||
"uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
|
|
||||||
"updated_at": null,
|
|
||||||
"deleted_at": null,
|
|
||||||
"created_at": "2016-11-04T16:25:35Z",
|
|
||||||
"display_name": "Dummy strategy"
|
|
||||||
},
|
|
||||||
"watcher_object.name": "StrategyPayload",
|
|
||||||
"watcher_object.version": "1.0",
|
|
||||||
"watcher_object.namespace": "watcher"
|
|
||||||
},
|
|
||||||
"created_at": "2016-11-04T16:29:20Z",
|
|
||||||
"uuid": "4a97b9dd-2023-43dc-b713-815bdd94d4d6"
|
|
||||||
},
|
|
||||||
"watcher_object.name": "AuditDeletePayload",
|
|
||||||
"watcher_object.version": "1.0",
|
|
||||||
"watcher_object.namespace": "watcher"
|
|
||||||
},
|
|
||||||
"publisher_id": "infra-optim:localhost",
|
|
||||||
"timestamp": "2016-11-04 16:31:36.264673 ",
|
|
||||||
"event_type": "audit.delete",
|
|
||||||
"message_id": "cbcf9f2c-7c53-4b4d-91ec-db49cca024b6"
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
{
|
|
||||||
"priority": "INFO",
|
|
||||||
"payload": {
|
|
||||||
"watcher_object.data": {
|
|
||||||
"audit_type": "ONESHOT",
|
|
||||||
"parameters": {
|
|
||||||
"para2": "hello",
|
|
||||||
"para1": 3.2
|
|
||||||
},
|
|
||||||
"state": "ONGOING",
|
|
||||||
"updated_at": null,
|
|
||||||
"deleted_at": null,
|
|
||||||
"fault": null,
|
|
||||||
"goal": {
|
|
||||||
"watcher_object.data": {
|
|
||||||
"uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
|
||||||
"name": "dummy",
|
|
||||||
"updated_at": null,
|
|
||||||
"deleted_at": null,
|
|
||||||
"efficacy_specification": [],
|
|
||||||
"created_at": "2016-11-04T16:25:35Z",
|
|
||||||
"display_name": "Dummy goal"
|
|
||||||
},
|
|
||||||
"watcher_object.name": "GoalPayload",
|
|
||||||
"watcher_object.version": "1.0",
|
|
||||||
"watcher_object.namespace": "watcher"
|
|
||||||
},
|
|
||||||
"interval": null,
|
|
||||||
"scope": [],
|
|
||||||
"strategy": {
|
|
||||||
"watcher_object.data": {
|
|
||||||
"parameters_spec": {
|
|
||||||
"properties": {
|
|
||||||
"para2": {
|
|
||||||
"type": "string",
|
|
||||||
"default": "hello",
|
|
||||||
"description": "string parameter example"
|
|
||||||
},
|
|
||||||
"para1": {
|
|
||||||
"description": "number parameter example",
|
|
||||||
"maximum": 10.2,
|
|
||||||
"type": "number",
|
|
||||||
"default": 3.2,
|
|
||||||
"minimum": 1.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"name": "dummy",
|
|
||||||
"uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
|
|
||||||
"updated_at": null,
|
|
||||||
"deleted_at": null,
|
|
||||||
"created_at": "2016-11-04T16:25:35Z",
|
|
||||||
"display_name": "Dummy strategy"
|
|
||||||
},
|
|
||||||
"watcher_object.name": "StrategyPayload",
|
|
||||||
"watcher_object.version": "1.0",
|
|
||||||
"watcher_object.namespace": "watcher"
|
|
||||||
},
|
|
||||||
"created_at": "2016-11-04T16:29:20Z",
|
|
||||||
"uuid": "4a97b9dd-2023-43dc-b713-815bdd94d4d6"
|
|
||||||
},
|
|
||||||
"watcher_object.name": "AuditActionPayload",
|
|
||||||
"watcher_object.version": "1.0",
|
|
||||||
"watcher_object.namespace": "watcher"
|
|
||||||
},
|
|
||||||
"publisher_id": "infra-optim:localhost",
|
|
||||||
"timestamp": "2016-11-04 16:31:36.264673 ",
|
|
||||||
"event_type": "audit.planner.end",
|
|
||||||
"message_id": "cbcf9f2c-7c53-4b4d-91ec-db49cca024b6"
|
|
||||||
}
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
{
|
|
||||||
"priority": "ERROR",
|
|
||||||
"payload": {
|
|
||||||
"watcher_object.data": {
|
|
||||||
"audit_type": "ONESHOT",
|
|
||||||
"parameters": {
|
|
||||||
"para2": "hello",
|
|
||||||
"para1": 3.2
|
|
||||||
},
|
|
||||||
"state": "ONGOING",
|
|
||||||
"updated_at": null,
|
|
||||||
"deleted_at": null,
|
|
||||||
"fault": {
|
|
||||||
"watcher_object.data": {
|
|
||||||
"exception": "WatcherException",
|
|
||||||
"exception_message": "TEST",
|
|
||||||
"function_name": "test_send_audit_action_with_error",
|
|
||||||
"module_name": "watcher.tests.notifications.test_audit_notification"
|
|
||||||
},
|
|
||||||
"watcher_object.name": "ExceptionPayload",
|
|
||||||
"watcher_object.namespace": "watcher",
|
|
||||||
"watcher_object.version": "1.0"
|
|
||||||
},
|
|
||||||
"goal": {
|
|
||||||
"watcher_object.data": {
|
|
||||||
"uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
|
||||||
"name": "dummy",
|
|
||||||
"updated_at": null,
|
|
||||||
"deleted_at": null,
|
|
||||||
"efficacy_specification": [],
|
|
||||||
"created_at": "2016-11-04T16:25:35Z",
|
|
||||||
"display_name": "Dummy goal"
|
|
||||||
},
|
|
||||||
"watcher_object.name": "GoalPayload",
|
|
||||||
"watcher_object.version": "1.0",
|
|
||||||
"watcher_object.namespace": "watcher"
|
|
||||||
},
|
|
||||||
"interval": null,
|
|
||||||
"scope": [],
|
|
||||||
"strategy": {
|
|
||||||
"watcher_object.data": {
|
|
||||||
"parameters_spec": {
|
|
||||||
"properties": {
|
|
||||||
"para2": {
|
|
||||||
"type": "string",
|
|
||||||
"default": "hello",
|
|
||||||
"description": "string parameter example"
|
|
||||||
},
|
|
||||||
"para1": {
|
|
||||||
"description": "number parameter example",
|
|
||||||
"maximum": 10.2,
|
|
||||||
"type": "number",
|
|
||||||
"default": 3.2,
|
|
||||||
"minimum": 1.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"name": "dummy",
|
|
||||||
"uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
|
|
||||||
"updated_at": null,
|
|
||||||
"deleted_at": null,
|
|
||||||
"created_at": "2016-11-04T16:25:35Z",
|
|
||||||
"display_name": "Dummy strategy"
|
|
||||||
},
|
|
||||||
"watcher_object.name": "StrategyPayload",
|
|
||||||
"watcher_object.version": "1.0",
|
|
||||||
"watcher_object.namespace": "watcher"
|
|
||||||
},
|
|
||||||
"created_at": "2016-11-04T16:29:20Z",
|
|
||||||
"uuid": "4a97b9dd-2023-43dc-b713-815bdd94d4d6"
|
|
||||||
},
|
|
||||||
"watcher_object.name": "AuditActionPayload",
|
|
||||||
"watcher_object.version": "1.0",
|
|
||||||
"watcher_object.namespace": "watcher"
|
|
||||||
},
|
|
||||||
"publisher_id": "infra-optim:localhost",
|
|
||||||
"timestamp": "2016-11-04 16:31:36.264673 ",
|
|
||||||
"event_type": "audit.planner.error",
|
|
||||||
"message_id": "cbcf9f2c-7c53-4b4d-91ec-db49cca024b6"
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
{
|
|
||||||
"priority": "INFO",
|
|
||||||
"payload": {
|
|
||||||
"watcher_object.data": {
|
|
||||||
"audit_type": "ONESHOT",
|
|
||||||
"parameters": {
|
|
||||||
"para2": "hello",
|
|
||||||
"para1": 3.2
|
|
||||||
},
|
|
||||||
"state": "ONGOING",
|
|
||||||
"updated_at": null,
|
|
||||||
"deleted_at": null,
|
|
||||||
"fault": null,
|
|
||||||
"goal": {
|
|
||||||
"watcher_object.data": {
|
|
||||||
"uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
|
||||||
"name": "dummy",
|
|
||||||
"updated_at": null,
|
|
||||||
"deleted_at": null,
|
|
||||||
"efficacy_specification": [],
|
|
||||||
"created_at": "2016-11-04T16:25:35Z",
|
|
||||||
"display_name": "Dummy goal"
|
|
||||||
},
|
|
||||||
"watcher_object.name": "GoalPayload",
|
|
||||||
"watcher_object.version": "1.0",
|
|
||||||
"watcher_object.namespace": "watcher"
|
|
||||||
},
|
|
||||||
"interval": null,
|
|
||||||
"scope": [],
|
|
||||||
"strategy": {
|
|
||||||
"watcher_object.data": {
|
|
||||||
"parameters_spec": {
|
|
||||||
"properties": {
|
|
||||||
"para2": {
|
|
||||||
"type": "string",
|
|
||||||
"default": "hello",
|
|
||||||
"description": "string parameter example"
|
|
||||||
},
|
|
||||||
"para1": {
|
|
||||||
"description": "number parameter example",
|
|
||||||
"maximum": 10.2,
|
|
||||||
"type": "number",
|
|
||||||
"default": 3.2,
|
|
||||||
"minimum": 1.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"name": "dummy",
|
|
||||||
"uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
|
|
||||||
"updated_at": null,
|
|
||||||
"deleted_at": null,
|
|
||||||
"created_at": "2016-11-04T16:25:35Z",
|
|
||||||
"display_name": "Dummy strategy"
|
|
||||||
},
|
|
||||||
"watcher_object.name": "StrategyPayload",
|
|
||||||
"watcher_object.version": "1.0",
|
|
||||||
"watcher_object.namespace": "watcher"
|
|
||||||
},
|
|
||||||
"created_at": "2016-11-04T16:29:20Z",
|
|
||||||
"uuid": "4a97b9dd-2023-43dc-b713-815bdd94d4d6"
|
|
||||||
},
|
|
||||||
"watcher_object.name": "AuditActionPayload",
|
|
||||||
"watcher_object.version": "1.0",
|
|
||||||
"watcher_object.namespace": "watcher"
|
|
||||||
},
|
|
||||||
"publisher_id": "infra-optim:localhost",
|
|
||||||
"timestamp": "2016-11-04 16:31:36.264673 ",
|
|
||||||
"event_type": "audit.planner.start",
|
|
||||||
"message_id": "cbcf9f2c-7c53-4b4d-91ec-db49cca024b6"
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
{
|
|
||||||
"priority": "INFO",
|
|
||||||
"payload": {
|
|
||||||
"watcher_object.data": {
|
|
||||||
"audit_type": "ONESHOT",
|
|
||||||
"parameters": {
|
|
||||||
"para2": "hello",
|
|
||||||
"para1": 3.2
|
|
||||||
},
|
|
||||||
"state": "ONGOING",
|
|
||||||
"updated_at": null,
|
|
||||||
"deleted_at": null,
|
|
||||||
"fault": null,
|
|
||||||
"goal": {
|
|
||||||
"watcher_object.data": {
|
|
||||||
"uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
|
||||||
"name": "dummy",
|
|
||||||
"updated_at": null,
|
|
||||||
"deleted_at": null,
|
|
||||||
"efficacy_specification": [],
|
|
||||||
"created_at": "2016-11-04T16:25:35Z",
|
|
||||||
"display_name": "Dummy goal"
|
|
||||||
},
|
|
||||||
"watcher_object.name": "GoalPayload",
|
|
||||||
"watcher_object.version": "1.0",
|
|
||||||
"watcher_object.namespace": "watcher"
|
|
||||||
},
|
|
||||||
"interval": null,
|
|
||||||
"scope": [],
|
|
||||||
"strategy": {
|
|
||||||
"watcher_object.data": {
|
|
||||||
"parameters_spec": {
|
|
||||||
"properties": {
|
|
||||||
"para2": {
|
|
||||||
"type": "string",
|
|
||||||
"default": "hello",
|
|
||||||
"description": "string parameter example"
|
|
||||||
},
|
|
||||||
"para1": {
|
|
||||||
"description": "number parameter example",
|
|
||||||
"maximum": 10.2,
|
|
||||||
"type": "number",
|
|
||||||
"default": 3.2,
|
|
||||||
"minimum": 1.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"name": "dummy",
|
|
||||||
"uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
|
|
||||||
"updated_at": null,
|
|
||||||
"deleted_at": null,
|
|
||||||
"created_at": "2016-11-04T16:25:35Z",
|
|
||||||
"display_name": "Dummy strategy"
|
|
||||||
},
|
|
||||||
"watcher_object.name": "StrategyPayload",
|
|
||||||
"watcher_object.version": "1.0",
|
|
||||||
"watcher_object.namespace": "watcher"
|
|
||||||
},
|
|
||||||
"created_at": "2016-11-04T16:29:20Z",
|
|
||||||
"uuid": "4a97b9dd-2023-43dc-b713-815bdd94d4d6"
|
|
||||||
},
|
|
||||||
"watcher_object.name": "AuditActionPayload",
|
|
||||||
"watcher_object.version": "1.0",
|
|
||||||
"watcher_object.namespace": "watcher"
|
|
||||||
},
|
|
||||||
"publisher_id": "infra-optim:localhost",
|
|
||||||
"timestamp": "2016-11-04 16:31:36.264673 ",
|
|
||||||
"event_type": "audit.strategy.end",
|
|
||||||
"message_id": "cbcf9f2c-7c53-4b4d-91ec-db49cca024b6"
|
|
||||||
}
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
{
|
|
||||||
"priority": "ERROR",
|
|
||||||
"payload": {
|
|
||||||
"watcher_object.data": {
|
|
||||||
"audit_type": "ONESHOT",
|
|
||||||
"parameters": {
|
|
||||||
"para2": "hello",
|
|
||||||
"para1": 3.2
|
|
||||||
},
|
|
||||||
"state": "ONGOING",
|
|
||||||
"updated_at": null,
|
|
||||||
"deleted_at": null,
|
|
||||||
"fault": {
|
|
||||||
"watcher_object.data": {
|
|
||||||
"exception": "WatcherException",
|
|
||||||
"exception_message": "TEST",
|
|
||||||
"function_name": "test_send_audit_action_with_error",
|
|
||||||
"module_name": "watcher.tests.notifications.test_audit_notification"
|
|
||||||
},
|
|
||||||
"watcher_object.name": "ExceptionPayload",
|
|
||||||
"watcher_object.namespace": "watcher",
|
|
||||||
"watcher_object.version": "1.0"
|
|
||||||
},
|
|
||||||
"goal": {
|
|
||||||
"watcher_object.data": {
|
|
||||||
"uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
|
||||||
"name": "dummy",
|
|
||||||
"updated_at": null,
|
|
||||||
"deleted_at": null,
|
|
||||||
"efficacy_specification": [],
|
|
||||||
"created_at": "2016-11-04T16:25:35Z",
|
|
||||||
"display_name": "Dummy goal"
|
|
||||||
},
|
|
||||||
"watcher_object.name": "GoalPayload",
|
|
||||||
"watcher_object.version": "1.0",
|
|
||||||
"watcher_object.namespace": "watcher"
|
|
||||||
},
|
|
||||||
"interval": null,
|
|
||||||
"scope": [],
|
|
||||||
"strategy": {
|
|
||||||
"watcher_object.data": {
|
|
||||||
"parameters_spec": {
|
|
||||||
"properties": {
|
|
||||||
"para2": {
|
|
||||||
"type": "string",
|
|
||||||
"default": "hello",
|
|
||||||
"description": "string parameter example"
|
|
||||||
},
|
|
||||||
"para1": {
|
|
||||||
"description": "number parameter example",
|
|
||||||
"maximum": 10.2,
|
|
||||||
"type": "number",
|
|
||||||
"default": 3.2,
|
|
||||||
"minimum": 1.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"name": "dummy",
|
|
||||||
"uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
|
|
||||||
"updated_at": null,
|
|
||||||
"deleted_at": null,
|
|
||||||
"created_at": "2016-11-04T16:25:35Z",
|
|
||||||
"display_name": "Dummy strategy"
|
|
||||||
},
|
|
||||||
"watcher_object.name": "StrategyPayload",
|
|
||||||
"watcher_object.version": "1.0",
|
|
||||||
"watcher_object.namespace": "watcher"
|
|
||||||
},
|
|
||||||
"created_at": "2016-11-04T16:29:20Z",
|
|
||||||
"uuid": "4a97b9dd-2023-43dc-b713-815bdd94d4d6"
|
|
||||||
},
|
|
||||||
"watcher_object.name": "AuditActionPayload",
|
|
||||||
"watcher_object.version": "1.0",
|
|
||||||
"watcher_object.namespace": "watcher"
|
|
||||||
},
|
|
||||||
"publisher_id": "infra-optim:localhost",
|
|
||||||
"timestamp": "2016-11-04 16:31:36.264673 ",
|
|
||||||
"event_type": "audit.strategy.error",
|
|
||||||
"message_id": "cbcf9f2c-7c53-4b4d-91ec-db49cca024b6"
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
{
|
|
||||||
"priority": "INFO",
|
|
||||||
"payload": {
|
|
||||||
"watcher_object.data": {
|
|
||||||
"audit_type": "ONESHOT",
|
|
||||||
"parameters": {
|
|
||||||
"para2": "hello",
|
|
||||||
"para1": 3.2
|
|
||||||
},
|
|
||||||
"state": "ONGOING",
|
|
||||||
"updated_at": null,
|
|
||||||
"deleted_at": null,
|
|
||||||
"fault": null,
|
|
||||||
"goal": {
|
|
||||||
"watcher_object.data": {
|
|
||||||
"uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
|
||||||
"name": "dummy",
|
|
||||||
"updated_at": null,
|
|
||||||
"deleted_at": null,
|
|
||||||
"efficacy_specification": [],
|
|
||||||
"created_at": "2016-11-04T16:25:35Z",
|
|
||||||
"display_name": "Dummy goal"
|
|
||||||
},
|
|
||||||
"watcher_object.name": "GoalPayload",
|
|
||||||
"watcher_object.version": "1.0",
|
|
||||||
"watcher_object.namespace": "watcher"
|
|
||||||
},
|
|
||||||
"interval": null,
|
|
||||||
"scope": [],
|
|
||||||
"strategy": {
|
|
||||||
"watcher_object.data": {
|
|
||||||
"parameters_spec": {
|
|
||||||
"properties": {
|
|
||||||
"para2": {
|
|
||||||
"type": "string",
|
|
||||||
"default": "hello",
|
|
||||||
"description": "string parameter example"
|
|
||||||
},
|
|
||||||
"para1": {
|
|
||||||
"description": "number parameter example",
|
|
||||||
"maximum": 10.2,
|
|
||||||
"type": "number",
|
|
||||||
"default": 3.2,
|
|
||||||
"minimum": 1.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"name": "dummy",
|
|
||||||
"uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
|
|
||||||
"updated_at": null,
|
|
||||||
"deleted_at": null,
|
|
||||||
"created_at": "2016-11-04T16:25:35Z",
|
|
||||||
"display_name": "Dummy strategy"
|
|
||||||
},
|
|
||||||
"watcher_object.name": "StrategyPayload",
|
|
||||||
"watcher_object.version": "1.0",
|
|
||||||
"watcher_object.namespace": "watcher"
|
|
||||||
},
|
|
||||||
"created_at": "2016-11-04T16:29:20Z",
|
|
||||||
"uuid": "4a97b9dd-2023-43dc-b713-815bdd94d4d6"
|
|
||||||
},
|
|
||||||
"watcher_object.name": "AuditActionPayload",
|
|
||||||
"watcher_object.version": "1.0",
|
|
||||||
"watcher_object.namespace": "watcher"
|
|
||||||
},
|
|
||||||
"publisher_id": "infra-optim:localhost",
|
|
||||||
"timestamp": "2016-11-04 16:31:36.264673 ",
|
|
||||||
"event_type": "audit.strategy.start",
|
|
||||||
"message_id": "cbcf9f2c-7c53-4b4d-91ec-db49cca024b6"
|
|
||||||
}
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
{
|
|
||||||
"publisher_id": "infra-optim:localhost",
|
|
||||||
"timestamp": "2016-11-04 16:51:38.722986 ",
|
|
||||||
"payload": {
|
|
||||||
"watcher_object.name": "AuditUpdatePayload",
|
|
||||||
"watcher_object.data": {
|
|
||||||
"strategy": {
|
|
||||||
"watcher_object.name": "StrategyPayload",
|
|
||||||
"watcher_object.data": {
|
|
||||||
"name": "dummy",
|
|
||||||
"parameters_spec": {
|
|
||||||
"properties": {
|
|
||||||
"para2": {
|
|
||||||
"default": "hello",
|
|
||||||
"type": "string",
|
|
||||||
"description": "string parameter example"
|
|
||||||
},
|
|
||||||
"para1": {
|
|
||||||
"maximum": 10.2,
|
|
||||||
"default": 3.2,
|
|
||||||
"minimum": 1.0,
|
|
||||||
"description": "number parameter example",
|
|
||||||
"type": "number"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"updated_at": null,
|
|
||||||
"display_name": "Dummy strategy",
|
|
||||||
"deleted_at": null,
|
|
||||||
"uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
|
|
||||||
"created_at": "2016-11-04T16:25:35Z"
|
|
||||||
},
|
|
||||||
"watcher_object.namespace": "watcher",
|
|
||||||
"watcher_object.version": "1.0"
|
|
||||||
},
|
|
||||||
"scope": [],
|
|
||||||
"created_at": "2016-11-04T16:51:21Z",
|
|
||||||
"uuid": "f1e0d912-afd9-4bf2-91ef-c99cd08cc1ef",
|
|
||||||
"goal": {
|
|
||||||
"watcher_object.name": "GoalPayload",
|
|
||||||
"watcher_object.data": {
|
|
||||||
"efficacy_specification": [],
|
|
||||||
"updated_at": null,
|
|
||||||
"name": "dummy",
|
|
||||||
"display_name": "Dummy goal",
|
|
||||||
"deleted_at": null,
|
|
||||||
"uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
|
||||||
"created_at": "2016-11-04T16:25:35Z"
|
|
||||||
},
|
|
||||||
"watcher_object.namespace": "watcher",
|
|
||||||
"watcher_object.version": "1.0"
|
|
||||||
},
|
|
||||||
"parameters": {
|
|
||||||
"para2": "hello",
|
|
||||||
"para1": 3.2
|
|
||||||
},
|
|
||||||
"deleted_at": null,
|
|
||||||
"state_update": {
|
|
||||||
"watcher_object.name": "AuditStateUpdatePayload",
|
|
||||||
"watcher_object.data": {
|
|
||||||
"state": "ONGOING",
|
|
||||||
"old_state": "PENDING"
|
|
||||||
},
|
|
||||||
"watcher_object.namespace": "watcher",
|
|
||||||
"watcher_object.version": "1.0"
|
|
||||||
},
|
|
||||||
"interval": null,
|
|
||||||
"updated_at": null,
|
|
||||||
"state": "ONGOING",
|
|
||||||
"audit_type": "ONESHOT"
|
|
||||||
},
|
|
||||||
"watcher_object.namespace": "watcher",
|
|
||||||
"watcher_object.version": "1.0"
|
|
||||||
},
|
|
||||||
"priority": "INFO",
|
|
||||||
"event_type": "audit.update",
|
|
||||||
"message_id": "697fdf55-7252-4b6c-a2c2-5b9e85f6342c"
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
{
|
|
||||||
"event_type": "infra-optim.exception",
|
|
||||||
"payload": {
|
|
||||||
"watcher_object.data": {
|
|
||||||
"exception": "NoAvailableStrategyForGoal",
|
|
||||||
"exception_message": "No strategy could be found to achieve the server_consolidation goal.",
|
|
||||||
"function_name": "_aggregate_create_in_db",
|
|
||||||
"module_name": "watcher.objects.aggregate"
|
|
||||||
},
|
|
||||||
"watcher_object.name": "ExceptionPayload",
|
|
||||||
"watcher_object.namespace": "watcher",
|
|
||||||
"watcher_object.version": "1.0"
|
|
||||||
},
|
|
||||||
"priority": "ERROR",
|
|
||||||
"publisher_id": "watcher-api:fake-mini"
|
|
||||||
}
|
|
||||||
@@ -298,7 +298,7 @@ The :ref:`Watcher Decision Engine <watcher_decision_engine_definition>` also
|
|||||||
builds the :ref:`Cluster Data Model <cluster_data_model_definition>`. This
|
builds the :ref:`Cluster Data Model <cluster_data_model_definition>`. This
|
||||||
data model is needed by the :ref:`Strategy <strategy_definition>` to know the
|
data model is needed by the :ref:`Strategy <strategy_definition>` to know the
|
||||||
current state and topology of the audited
|
current state and topology of the audited
|
||||||
:ref:`OpenStack cluster <cluster_definition>`.
|
:ref:`Openstack cluster <cluster_definition>`.
|
||||||
|
|
||||||
The :ref:`Watcher Decision Engine <watcher_decision_engine_definition>` calls
|
The :ref:`Watcher Decision Engine <watcher_decision_engine_definition>` calls
|
||||||
the **execute()** method of the instantiated
|
the **execute()** method of the instantiated
|
||||||
@@ -316,7 +316,7 @@ This method finds an appropriate scheduling of
|
|||||||
:ref:`Actions <action_definition>` taking into account some scheduling rules
|
:ref:`Actions <action_definition>` taking into account some scheduling rules
|
||||||
(such as priorities between actions).
|
(such as priorities between actions).
|
||||||
It generates a new :ref:`Action Plan <action_plan_definition>` with status
|
It generates a new :ref:`Action Plan <action_plan_definition>` with status
|
||||||
**RECOMMENDED** and saves it into the :ref:`Watcher Database
|
**RECOMMENDED** and saves it into the:ref:`Watcher Database
|
||||||
<watcher_database_definition>`. The saved action plan is now a scheduled flow
|
<watcher_database_definition>`. The saved action plan is now a scheduled flow
|
||||||
of actions to which a global efficacy is associated alongside a number of
|
of actions to which a global efficacy is associated alongside a number of
|
||||||
:ref:`Efficacy Indicators <efficacy_indicator_definition>` as specified by the
|
:ref:`Efficacy Indicators <efficacy_indicator_definition>` as specified by the
|
||||||
@@ -450,7 +450,7 @@ state may be one of the following:
|
|||||||
stored in the :ref:`Watcher database <watcher_database_definition>` but is
|
stored in the :ref:`Watcher database <watcher_database_definition>` but is
|
||||||
not returned any more through the Watcher APIs.
|
not returned any more through the Watcher APIs.
|
||||||
- **CANCELLED** : the :ref:`Action Plan <action_plan_definition>` was in
|
- **CANCELLED** : the :ref:`Action Plan <action_plan_definition>` was in
|
||||||
**RECOMMENDED**, **PENDING** or **ONGOING** state and was cancelled by the
|
**PENDING** or **ONGOING** state and was cancelled by the
|
||||||
:ref:`Administrator <administrator_definition>`
|
:ref:`Administrator <administrator_definition>`
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -11,21 +11,7 @@
|
|||||||
# implied.
|
# implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from watcher import version as watcher_version
|
from watcher import version as watcher_version
|
||||||
from watcher import objects
|
|
||||||
|
|
||||||
objects.register_all()
|
|
||||||
|
|
||||||
# If extensions (or modules to document with autodoc) are in another directory,
|
|
||||||
# add these directories to sys.path here. If the directory is relative to the
|
|
||||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
|
||||||
sys.path.insert(0, os.path.abspath('../../'))
|
|
||||||
sys.path.insert(0, os.path.abspath('../'))
|
|
||||||
sys.path.insert(0, os.path.abspath('./'))
|
|
||||||
|
|
||||||
# -- General configuration ----------------------------------------------------
|
# -- General configuration ----------------------------------------------------
|
||||||
|
|
||||||
@@ -40,8 +26,7 @@ extensions = [
|
|||||||
'sphinxcontrib.pecanwsme.rest',
|
'sphinxcontrib.pecanwsme.rest',
|
||||||
'stevedore.sphinxext',
|
'stevedore.sphinxext',
|
||||||
'wsmeext.sphinxext',
|
'wsmeext.sphinxext',
|
||||||
'ext.term',
|
'watcher.doc',
|
||||||
'ext.versioned_notifications',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
wsme_protocols = ['restjson']
|
wsme_protocols = ['restjson']
|
||||||
@@ -82,8 +67,6 @@ exclude_patterns = [
|
|||||||
# them when scanning for input files.
|
# them when scanning for input files.
|
||||||
'man/footer.rst',
|
'man/footer.rst',
|
||||||
'man/general-options.rst',
|
'man/general-options.rst',
|
||||||
'strategies/strategy-template.rst',
|
|
||||||
'image_src/plantuml/README.rst',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||||
|
|||||||
@@ -93,11 +93,11 @@ following command:
|
|||||||
|
|
||||||
.. code:: bash
|
.. code:: bash
|
||||||
|
|
||||||
$ watcher strategy list --goal <your_goal_uuid_or_name>
|
$ watcher strategy list --goal-uuid <your_goal_uuid>
|
||||||
|
|
||||||
or::
|
or::
|
||||||
|
|
||||||
$ openstack optimize strategy list --goal <your_goal_uuid_or_name>
|
$ openstack optimize strategy list --goal-uuid <your_goal_uuid>
|
||||||
|
|
||||||
You can use the following command to check strategy details including which
|
You can use the following command to check strategy details including which
|
||||||
parameters of which format it supports:
|
parameters of which format it supports:
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
..
|
|
||||||
Except where otherwise noted, this document is licensed under Creative
|
|
||||||
Commons Attribution 3.0 License. You can view the license at:
|
|
||||||
|
|
||||||
https://creativecommons.org/licenses/by/3.0/
|
|
||||||
|
|
||||||
.. _watcher_notifications:
|
|
||||||
|
|
||||||
========================
|
|
||||||
Notifications in Watcher
|
|
||||||
========================
|
|
||||||
|
|
||||||
.. versioned_notifications::
|
|
||||||
@@ -22,7 +22,7 @@ cluster data model collectors within Watcher.
|
|||||||
Creating a new plugin
|
Creating a new plugin
|
||||||
=====================
|
=====================
|
||||||
|
|
||||||
In order to create a new cluster data model collector, you have to:
|
In order to create a new model, you have to:
|
||||||
|
|
||||||
- Extend the :py:class:`~.base.BaseClusterDataModelCollector` class.
|
- Extend the :py:class:`~.base.BaseClusterDataModelCollector` class.
|
||||||
- Implement its :py:meth:`~.BaseClusterDataModelCollector.execute` abstract
|
- Implement its :py:meth:`~.BaseClusterDataModelCollector.execute` abstract
|
||||||
@@ -65,49 +65,6 @@ This implementation is the most basic one. So in order to get a better
|
|||||||
understanding on how to implement a more advanced cluster data model collector,
|
understanding on how to implement a more advanced cluster data model collector,
|
||||||
have a look at the :py:class:`~.NovaClusterDataModelCollector` class.
|
have a look at the :py:class:`~.NovaClusterDataModelCollector` class.
|
||||||
|
|
||||||
Define a custom model
|
|
||||||
=====================
|
|
||||||
|
|
||||||
As you may have noticed in the above example, we are reusing an existing model
|
|
||||||
provided by Watcher. However, this model can be easily customized by
|
|
||||||
implementing a new class that would implement the :py:class:`~.Model` abstract
|
|
||||||
base class. Here below is simple example on how to proceed in implementing a
|
|
||||||
custom Model:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
# Filepath = <PROJECT_DIR>/thirdparty/dummy.py
|
|
||||||
# Import path = thirdparty.dummy
|
|
||||||
|
|
||||||
from watcher.decision_engine.model import base as modelbase
|
|
||||||
from watcher.decision_engine.model.collector import base
|
|
||||||
|
|
||||||
|
|
||||||
class MyModel(modelbase.Model):
|
|
||||||
|
|
||||||
def to_string(self):
|
|
||||||
return 'MyModel'
|
|
||||||
|
|
||||||
|
|
||||||
class DummyClusterDataModelCollector(base.BaseClusterDataModelCollector):
|
|
||||||
|
|
||||||
def execute(self):
|
|
||||||
model = MyModel()
|
|
||||||
# Do something here...
|
|
||||||
return model
|
|
||||||
|
|
||||||
@property
|
|
||||||
def notification_endpoints(self):
|
|
||||||
return []
|
|
||||||
|
|
||||||
Here below is the abstract ``Model`` class that every single cluster data model
|
|
||||||
should implement:
|
|
||||||
|
|
||||||
.. autoclass:: watcher.decision_engine.model.base.Model
|
|
||||||
:members:
|
|
||||||
:special-members: __init__
|
|
||||||
:noindex:
|
|
||||||
|
|
||||||
Define configuration parameters
|
Define configuration parameters
|
||||||
===============================
|
===============================
|
||||||
|
|
||||||
|
|||||||
@@ -60,8 +60,8 @@ Here is an example showing how you can define a new ``NewGoal`` goal plugin:
|
|||||||
# import path: thirdparty.new
|
# import path: thirdparty.new
|
||||||
|
|
||||||
from watcher._i18n import _
|
from watcher._i18n import _
|
||||||
from watcher.decision_engine.goal import base
|
|
||||||
from watcher.decision_engine.goal.efficacy import specs
|
from watcher.decision_engine.goal.efficacy import specs
|
||||||
|
from watcher.decision_engine.strategy.strategies import base
|
||||||
|
|
||||||
class NewGoal(base.Goal):
|
class NewGoal(base.Goal):
|
||||||
|
|
||||||
@@ -79,11 +79,11 @@ Here is an example showing how you can define a new ``NewGoal`` goal plugin:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_efficacy_specification(cls):
|
def get_efficacy_specification(cls):
|
||||||
return specs.Unclassified()
|
return specs.UnclassifiedStrategySpecification()
|
||||||
|
|
||||||
|
|
||||||
As you may have noticed, the :py:meth:`~.Goal.get_efficacy_specification`
|
As you may have noticed, the :py:meth:`~.Goal.get_efficacy_specification`
|
||||||
method returns an :py:meth:`~.Unclassified` instance which
|
method returns an :py:meth:`~.UnclassifiedStrategySpecification` instance which
|
||||||
is provided by Watcher. This efficacy specification is useful during the
|
is provided by Watcher. This efficacy specification is useful during the
|
||||||
development process of your goal as it corresponds to an empty specification.
|
development process of your goal as it corresponds to an empty specification.
|
||||||
If you want to learn more about what efficacy specifications are used for or to
|
If you want to learn more about what efficacy specifications are used for or to
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ Here is an example showing how you can write a planner plugin called
|
|||||||
|
|
||||||
# Filepath = third-party/third_party/dummy.py
|
# Filepath = third-party/third_party/dummy.py
|
||||||
# Import path = third_party.dummy
|
# Import path = third_party.dummy
|
||||||
from oslo_utils import uuidutils
|
import uuid
|
||||||
from watcher.decision_engine.planner import base
|
from watcher.decision_engine.planner import base
|
||||||
|
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@ Here is an example showing how you can write a planner plugin called
|
|||||||
|
|
||||||
def _create_action_plan(self, context, audit_id):
|
def _create_action_plan(self, context, audit_id):
|
||||||
action_plan_dict = {
|
action_plan_dict = {
|
||||||
'uuid': uuidutils.generate_uuid(),
|
'uuid': uuid.uuid4(),
|
||||||
'audit_id': audit_id,
|
'audit_id': audit_id,
|
||||||
'first_action_id': None,
|
'first_action_id': None,
|
||||||
'state': objects.action_plan.State.RECOMMENDED
|
'state': objects.action_plan.State.RECOMMENDED
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ In order to create a new strategy, you have to:
|
|||||||
Note: Do not use a variable to return the translated string so it can be
|
Note: Do not use a variable to return the translated string so it can be
|
||||||
automatically collected by the translation tool.
|
automatically collected by the translation tool.
|
||||||
- Implement its :py:meth:`~.BaseStrategy.get_translatable_display_name`
|
- Implement its :py:meth:`~.BaseStrategy.get_translatable_display_name`
|
||||||
class method to return the translation key (actually the English display
|
class method to return the translation key (actually the english display
|
||||||
name) of your new strategy. The value return should be the same as the
|
name) of your new strategy. The value return should be the same as the
|
||||||
string translated in :py:meth:`~.BaseStrategy.get_display_name`.
|
string translated in :py:meth:`~.BaseStrategy.get_display_name`.
|
||||||
- Implement its :py:meth:`~.BaseStrategy.execute` method to return the
|
- Implement its :py:meth:`~.BaseStrategy.execute` method to return the
|
||||||
|
|||||||
@@ -96,8 +96,8 @@ The :ref:`Cluster <cluster_definition>` may be divided in one or several
|
|||||||
|
|
||||||
.. _cluster_data_model_definition:
|
.. _cluster_data_model_definition:
|
||||||
|
|
||||||
Cluster Data Model (CDM)
|
Cluster Data Model
|
||||||
========================
|
==================
|
||||||
|
|
||||||
.. watcher-term:: watcher.decision_engine.model.collector.base
|
.. watcher-term:: watcher.decision_engine.model.collector.base
|
||||||
|
|
||||||
@@ -132,7 +132,7 @@ Compute node
|
|||||||
============
|
============
|
||||||
|
|
||||||
Please, read `the official OpenStack definition of a Compute Node
|
Please, read `the official OpenStack definition of a Compute Node
|
||||||
<http://docs.openstack.org/ops-guide/arch-compute-nodes.html>`_.
|
<http://docs.openstack.org/openstack-ops/content/compute_nodes.html>`_.
|
||||||
|
|
||||||
.. _customer_definition:
|
.. _customer_definition:
|
||||||
|
|
||||||
@@ -164,8 +164,7 @@ Goal
|
|||||||
Host Aggregate
|
Host Aggregate
|
||||||
==============
|
==============
|
||||||
|
|
||||||
Please, read `the official OpenStack definition of a Host Aggregate
|
Please, read `the official OpenStack definition of a Host Aggregate <http://docs.openstack.org/developer/nova/aggregates.html>`_.
|
||||||
<http://docs.openstack.org/developer/nova/aggregates.html>`_.
|
|
||||||
|
|
||||||
.. _instance_definition:
|
.. _instance_definition:
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ actor Administrator
|
|||||||
|
|
||||||
== Create some Audit settings ==
|
== Create some Audit settings ==
|
||||||
|
|
||||||
Administrator -> Watcher : create new Audit Template (i.e. Audit settings : goal, scope, ...)
|
Administrator -> Watcher : create new Audit Template (i.e. Audit settings : goal, scope, deadline,...)
|
||||||
Watcher -> Watcher : save Audit Template in database
|
Watcher -> Watcher : save Audit Template in database
|
||||||
Administrator <-- Watcher : Audit Template UUID
|
Administrator <-- Watcher : Audit Template UUID
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,9 @@ table(audit_templates) {
|
|||||||
uuid : String[36]
|
uuid : String[36]
|
||||||
name : String[63], nullable
|
name : String[63], nullable
|
||||||
description : String[255], nullable
|
description : String[255], nullable
|
||||||
scope : JSONEncodedList
|
host_aggregate : Integer, nullable
|
||||||
|
extra : JSONEncodedDict
|
||||||
|
version : String[15], nullable
|
||||||
|
|
||||||
created_at : DateTime
|
created_at : DateTime
|
||||||
updated_at : DateTime
|
updated_at : DateTime
|
||||||
@@ -57,9 +59,10 @@ table(audits) {
|
|||||||
uuid : String[36]
|
uuid : String[36]
|
||||||
audit_type : String[20]
|
audit_type : String[20]
|
||||||
state : String[20], nullable
|
state : String[20], nullable
|
||||||
|
deadline : DateTime, nullable
|
||||||
interval : Integer, nullable
|
interval : Integer, nullable
|
||||||
parameters : JSONEncodedDict, nullable
|
parameters : JSONEncodedDict, nullable
|
||||||
scope : JSONEncodedList, nullable
|
host_aggregate : Integer, nullable
|
||||||
|
|
||||||
created_at : DateTime
|
created_at : DateTime
|
||||||
updated_at : DateTime
|
updated_at : DateTime
|
||||||
@@ -128,18 +131,6 @@ table(scoring_engines) {
|
|||||||
deleted : Integer
|
deleted : Integer
|
||||||
}
|
}
|
||||||
|
|
||||||
table(service) {
|
|
||||||
primary_key(id: Integer)
|
|
||||||
name: String[255]
|
|
||||||
host: String[255]
|
|
||||||
last_seen_up: DateTime
|
|
||||||
|
|
||||||
created_at : DateTime
|
|
||||||
updated_at : DateTime
|
|
||||||
deleted_at : DateTime
|
|
||||||
deleted : Integer
|
|
||||||
}
|
|
||||||
|
|
||||||
"goals" <.. "strategies" : Foreign Key
|
"goals" <.. "strategies" : Foreign Key
|
||||||
"goals" <.. "audit_templates" : Foreign Key
|
"goals" <.. "audit_templates" : Foreign Key
|
||||||
"strategies" <.. "audit_templates" : Foreign Key
|
"strategies" <.. "audit_templates" : Foreign Key
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 46 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 56 KiB |
@@ -56,7 +56,6 @@ Getting Started
|
|||||||
dev/devstack
|
dev/devstack
|
||||||
deploy/configuration
|
deploy/configuration
|
||||||
deploy/conf-files
|
deploy/conf-files
|
||||||
dev/notifications
|
|
||||||
dev/testing
|
dev/testing
|
||||||
dev/rally_link
|
dev/rally_link
|
||||||
|
|
||||||
@@ -97,7 +96,6 @@ Introduction
|
|||||||
deploy/user-guide
|
deploy/user-guide
|
||||||
deploy/policy
|
deploy/policy
|
||||||
deploy/gmr
|
deploy/gmr
|
||||||
strategies/strategies
|
|
||||||
|
|
||||||
Watcher Manual Pages
|
Watcher Manual Pages
|
||||||
====================
|
====================
|
||||||
|
|||||||
@@ -1,96 +0,0 @@
|
|||||||
==================================
|
|
||||||
Basic Offline Server Consolidation
|
|
||||||
==================================
|
|
||||||
|
|
||||||
Synopsis
|
|
||||||
--------
|
|
||||||
|
|
||||||
**display name**: ``basic``
|
|
||||||
|
|
||||||
**goal**: ``server_consolidation``
|
|
||||||
|
|
||||||
.. watcher-term:: watcher.decision_engine.strategy.strategies.basic_consolidation
|
|
||||||
|
|
||||||
Requirements
|
|
||||||
------------
|
|
||||||
|
|
||||||
Metrics
|
|
||||||
*******
|
|
||||||
|
|
||||||
The *basic* strategy requires the following metrics:
|
|
||||||
|
|
||||||
============================ ============ ======= =======
|
|
||||||
metric service name plugins comment
|
|
||||||
============================ ============ ======= =======
|
|
||||||
``compute.node.cpu.percent`` ceilometer_ none
|
|
||||||
``cpu_util`` ceilometer_ none
|
|
||||||
============================ ============ ======= =======
|
|
||||||
|
|
||||||
.. _ceilometer: http://docs.openstack.org/admin-guide/telemetry-measurements.html#openstack-compute
|
|
||||||
|
|
||||||
Cluster data model
|
|
||||||
******************
|
|
||||||
|
|
||||||
Default Watcher's Compute cluster data model:
|
|
||||||
|
|
||||||
.. watcher-term:: watcher.decision_engine.model.collector.nova.NovaClusterDataModelCollector
|
|
||||||
|
|
||||||
Actions
|
|
||||||
*******
|
|
||||||
|
|
||||||
Default Watcher's actions:
|
|
||||||
|
|
||||||
|
|
||||||
.. list-table::
|
|
||||||
:widths: 30 30
|
|
||||||
:header-rows: 1
|
|
||||||
|
|
||||||
* - action
|
|
||||||
- description
|
|
||||||
* - ``migration``
|
|
||||||
- .. watcher-term:: watcher.applier.actions.migration.Migrate
|
|
||||||
* - ``change_nova_service_state``
|
|
||||||
- .. watcher-term:: watcher.applier.actions.change_nova_service_state.ChangeNovaServiceState
|
|
||||||
|
|
||||||
Planner
|
|
||||||
*******
|
|
||||||
|
|
||||||
Default Watcher's planner:
|
|
||||||
|
|
||||||
.. watcher-term:: watcher.decision_engine.planner.default.DefaultPlanner
|
|
||||||
|
|
||||||
Configuration
|
|
||||||
-------------
|
|
||||||
|
|
||||||
Strategy parameter is:
|
|
||||||
|
|
||||||
====================== ====== ============= ===================================
|
|
||||||
parameter type default Value description
|
|
||||||
====================== ====== ============= ===================================
|
|
||||||
``migration_attempts`` Number 0 Maximum number of combinations to
|
|
||||||
be tried by the strategy while
|
|
||||||
searching for potential candidates.
|
|
||||||
To remove the limit, set it to 0
|
|
||||||
====================== ====== ============= ===================================
|
|
||||||
|
|
||||||
Efficacy Indicator
|
|
||||||
------------------
|
|
||||||
|
|
||||||
.. watcher-func::
|
|
||||||
:format: literal_block
|
|
||||||
|
|
||||||
watcher.decision_engine.goal.efficacy.specs.ServerConsolidation.get_global_efficacy_indicator
|
|
||||||
|
|
||||||
How to use it ?
|
|
||||||
---------------
|
|
||||||
|
|
||||||
.. code-block:: shell
|
|
||||||
|
|
||||||
$ openstack optimize audittemplate create \
|
|
||||||
at1 server_consolidation --strategy basic
|
|
||||||
|
|
||||||
$ openstack optimize audit create -a at1 -p migration_attempts=4
|
|
||||||
|
|
||||||
External Links
|
|
||||||
--------------
|
|
||||||
None.
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
=================================
|
|
||||||
Outlet Temperature Based Strategy
|
|
||||||
=================================
|
|
||||||
|
|
||||||
Synopsis
|
|
||||||
--------
|
|
||||||
|
|
||||||
**display name**: ``outlet_temperature``
|
|
||||||
|
|
||||||
**goal**: ``thermal_optimization``
|
|
||||||
|
|
||||||
Outlet (Exhaust Air) temperature is a new thermal telemetry which can be
|
|
||||||
used to measure the host's thermal/workload status. This strategy makes
|
|
||||||
decisions to migrate workloads to the hosts with good thermal condition
|
|
||||||
(lowest outlet temperature) when the outlet temperature of source hosts
|
|
||||||
reach a configurable threshold.
|
|
||||||
|
|
||||||
Requirements
|
|
||||||
------------
|
|
||||||
|
|
||||||
This strategy has a dependency on the host having Intel's Power
|
|
||||||
Node Manager 3.0 or later enabled.
|
|
||||||
|
|
||||||
|
|
||||||
Metrics
|
|
||||||
*******
|
|
||||||
|
|
||||||
The *outlet_temperature* strategy requires the following metrics:
|
|
||||||
|
|
||||||
========================================= ============ ======= =======
|
|
||||||
metric service name plugins comment
|
|
||||||
========================================= ============ ======= =======
|
|
||||||
``hardware.ipmi.node.outlet_temperature`` ceilometer_ IPMI
|
|
||||||
========================================= ============ ======= =======
|
|
||||||
|
|
||||||
.. _ceilometer: http://docs.openstack.org/admin-guide/telemetry-measurements.html#ipmi-based-meters
|
|
||||||
|
|
||||||
Cluster data model
|
|
||||||
******************
|
|
||||||
|
|
||||||
Default Watcher's Compute cluster data model:
|
|
||||||
|
|
||||||
.. watcher-term:: watcher.decision_engine.model.collector.nova.NovaClusterDataModelCollector
|
|
||||||
|
|
||||||
Actions
|
|
||||||
*******
|
|
||||||
|
|
||||||
Default Watcher's actions:
|
|
||||||
|
|
||||||
.. list-table::
|
|
||||||
:widths: 30 30
|
|
||||||
:header-rows: 1
|
|
||||||
|
|
||||||
* - action
|
|
||||||
- description
|
|
||||||
* - ``migration``
|
|
||||||
- .. watcher-term:: watcher.applier.actions.migration.Migrate
|
|
||||||
|
|
||||||
Planner
|
|
||||||
*******
|
|
||||||
|
|
||||||
Default Watcher's planner:
|
|
||||||
|
|
||||||
.. watcher-term:: watcher.decision_engine.planner.default.DefaultPlanner
|
|
||||||
|
|
||||||
Configuration
|
|
||||||
-------------
|
|
||||||
|
|
||||||
Strategy parameter is:
|
|
||||||
|
|
||||||
============== ====== ============= ====================================
|
|
||||||
parameter type default Value description
|
|
||||||
============== ====== ============= ====================================
|
|
||||||
``threshold`` Number 35.0 Temperature threshold for migration
|
|
||||||
============== ====== ============= ====================================
|
|
||||||
|
|
||||||
Efficacy Indicator
|
|
||||||
------------------
|
|
||||||
|
|
||||||
None
|
|
||||||
|
|
||||||
Algorithm
|
|
||||||
---------
|
|
||||||
|
|
||||||
For more information on the Outlet Temperature Based Strategy please refer to:
|
|
||||||
https://specs.openstack.org/openstack/watcher-specs/specs/mitaka/implemented/outlet-temperature-based-strategy.html
|
|
||||||
|
|
||||||
How to use it ?
|
|
||||||
---------------
|
|
||||||
|
|
||||||
.. code-block:: shell
|
|
||||||
|
|
||||||
$ openstack optimize audittemplate create \
|
|
||||||
at1 thermal_optimization --strategy outlet_temperature
|
|
||||||
|
|
||||||
$ openstack optimize audit create -a at1 -p threshold=31.0
|
|
||||||
|
|
||||||
External Links
|
|
||||||
--------------
|
|
||||||
|
|
||||||
- `Intel Power Node Manager 3.0 <http://www.intel.com/content/www/us/en/power-management/intelligent-power-node-manager-3-0-specification.html>`_
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
Strategies
|
|
||||||
==========
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:glob:
|
|
||||||
:maxdepth: 1
|
|
||||||
|
|
||||||
./*
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
=============
|
|
||||||
Strategy name
|
|
||||||
=============
|
|
||||||
|
|
||||||
Synopsis
|
|
||||||
--------
|
|
||||||
|
|
||||||
**display name**:
|
|
||||||
|
|
||||||
**goal**:
|
|
||||||
|
|
||||||
Add here a complete description of your strategy
|
|
||||||
|
|
||||||
Requirements
|
|
||||||
------------
|
|
||||||
|
|
||||||
Metrics
|
|
||||||
*******
|
|
||||||
|
|
||||||
Write here the list of metrics required by your strategy algorithm (in the form
|
|
||||||
of a table). If these metrics requires specific Telemetry plugin or other
|
|
||||||
additional software, please explain here how to deploy them (and add link to
|
|
||||||
dedicated installation guide).
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
======================= ============ ======= =======
|
|
||||||
metric service name plugins comment
|
|
||||||
======================= ============ ======= =======
|
|
||||||
compute.node.* ceilometer_ none one point every 60s
|
|
||||||
vm.cpu.utilization_perc monasca_ none
|
|
||||||
power ceilometer_ kwapi_ one point every 60s
|
|
||||||
======================= ============ ======= =======
|
|
||||||
|
|
||||||
|
|
||||||
.. _ceilometer: http://docs.openstack.org/admin-guide/telemetry-measurements.html#openstack-compute
|
|
||||||
.. _monasca: https://github.com/openstack/monasca-agent/blob/master/docs/Libvirt.md
|
|
||||||
.. _kwapi: https://kwapi.readthedocs.io/en/latest/index.html
|
|
||||||
|
|
||||||
|
|
||||||
Cluster data model
|
|
||||||
******************
|
|
||||||
|
|
||||||
Default Watcher's cluster data model.
|
|
||||||
|
|
||||||
or
|
|
||||||
|
|
||||||
If your strategy implementation requires a new cluster data model, please
|
|
||||||
describe it in this section, with a link to model plugin's installation guide.
|
|
||||||
|
|
||||||
Actions
|
|
||||||
*******
|
|
||||||
|
|
||||||
Default Watcher's actions.
|
|
||||||
|
|
||||||
or
|
|
||||||
|
|
||||||
If your strategy implementation requires new actions, add the list of Action
|
|
||||||
plugins here (in the form of a table) with a link to the plugin's installation
|
|
||||||
procedure.
|
|
||||||
|
|
||||||
======== =================
|
|
||||||
action description
|
|
||||||
======== =================
|
|
||||||
action1_ This action1 ...
|
|
||||||
action2_ This action2 ...
|
|
||||||
======== =================
|
|
||||||
|
|
||||||
.. _action1 : https://github.com/myrepo/watcher/plugins/action1
|
|
||||||
.. _action2 : https://github.com/myrepo/watcher/plugins/action2
|
|
||||||
|
|
||||||
Planner
|
|
||||||
*******
|
|
||||||
|
|
||||||
Default Watcher's planner.
|
|
||||||
|
|
||||||
or
|
|
||||||
|
|
||||||
If your strategy requires also a new planner to schedule built actions in time,
|
|
||||||
please describe it in this section, with a link to planner plugin's
|
|
||||||
installation guide.
|
|
||||||
|
|
||||||
Configuration
|
|
||||||
-------------
|
|
||||||
|
|
||||||
If your strategy use configurable parameters, explain here how to tune them.
|
|
||||||
|
|
||||||
|
|
||||||
Efficacy Indicator
|
|
||||||
------------------
|
|
||||||
|
|
||||||
Add here the Efficacy indicator computed by your strategy.
|
|
||||||
|
|
||||||
Algorithm
|
|
||||||
---------
|
|
||||||
|
|
||||||
Add here either the description of your algorithm or
|
|
||||||
link to the existing description.
|
|
||||||
|
|
||||||
How to use it ?
|
|
||||||
---------------
|
|
||||||
|
|
||||||
.. code-block:: shell
|
|
||||||
|
|
||||||
$ Write the command line to create an audit with your strategy.
|
|
||||||
|
|
||||||
External Links
|
|
||||||
--------------
|
|
||||||
|
|
||||||
If you have written papers, blog articles .... about your strategy into Watcher,
|
|
||||||
or if your strategy is based from external publication(s), please add HTTP
|
|
||||||
links and references in this section.
|
|
||||||
|
|
||||||
- `link1 <http://www.link1.papers.com>`_
|
|
||||||
- `link2 <http://www.link2.papers.com>`_
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
==================================
|
|
||||||
Uniform Airflow Migration Strategy
|
|
||||||
==================================
|
|
||||||
|
|
||||||
Synopsis
|
|
||||||
--------
|
|
||||||
|
|
||||||
**display name**: ``uniform_airflow``
|
|
||||||
|
|
||||||
**goal**: ``airflow_optimization``
|
|
||||||
|
|
||||||
.. watcher-term:: watcher.decision_engine.strategy.strategies.uniform_airflow
|
|
||||||
|
|
||||||
Requirements
|
|
||||||
------------
|
|
||||||
|
|
||||||
This strategy has a dependency on the server having Intel's Power
|
|
||||||
Node Manager 3.0 or later enabled.
|
|
||||||
|
|
||||||
Metrics
|
|
||||||
*******
|
|
||||||
|
|
||||||
The *uniform_airflow* strategy requires the following metrics:
|
|
||||||
|
|
||||||
================================== ============ ======= =======
|
|
||||||
metric service name plugins comment
|
|
||||||
================================== ============ ======= =======
|
|
||||||
``hardware.ipmi.node.airflow`` ceilometer_ IPMI
|
|
||||||
``hardware.ipmi.node.temperature`` ceilometer_ IPMI
|
|
||||||
``hardware.ipmi.node.power`` ceilometer_ IPMI
|
|
||||||
================================== ============ ======= =======
|
|
||||||
|
|
||||||
.. _ceilometer: http://docs.openstack.org/admin-guide/telemetry-measurements.html#ipmi-based-meters
|
|
||||||
|
|
||||||
Cluster data model
|
|
||||||
******************
|
|
||||||
|
|
||||||
Default Watcher's Compute cluster data model:
|
|
||||||
|
|
||||||
.. watcher-term:: watcher.decision_engine.model.collector.nova.NovaClusterDataModelCollector
|
|
||||||
|
|
||||||
Actions
|
|
||||||
*******
|
|
||||||
|
|
||||||
Default Watcher's actions:
|
|
||||||
|
|
||||||
|
|
||||||
.. list-table::
|
|
||||||
:widths: 30 30
|
|
||||||
:header-rows: 1
|
|
||||||
|
|
||||||
* - action
|
|
||||||
- description
|
|
||||||
* - ``migration``
|
|
||||||
- .. watcher-term:: watcher.applier.actions.migration.Migrate
|
|
||||||
|
|
||||||
Planner
|
|
||||||
*******
|
|
||||||
|
|
||||||
Default Watcher's planner:
|
|
||||||
|
|
||||||
.. watcher-term:: watcher.decision_engine.planner.default.DefaultPlanner
|
|
||||||
|
|
||||||
Configuration
|
|
||||||
-------------
|
|
||||||
|
|
||||||
Strategy parameters are:
|
|
||||||
|
|
||||||
====================== ====== ============= ===========================
|
|
||||||
parameter type default Value description
|
|
||||||
====================== ====== ============= ===========================
|
|
||||||
``threshold_airflow`` Number 400.0 Airflow threshold for
|
|
||||||
migration Unit is 0.1CFM
|
|
||||||
``threshold_inlet_t`` Number 28.0 Inlet temperature threshold
|
|
||||||
for migration decision
|
|
||||||
``threshold_power`` Number 350.0 System power threshold for
|
|
||||||
migration decision
|
|
||||||
``period`` Number 300 Aggregate time period of
|
|
||||||
ceilometer
|
|
||||||
====================== ====== ============= ===========================
|
|
||||||
|
|
||||||
Efficacy Indicator
|
|
||||||
------------------
|
|
||||||
|
|
||||||
None
|
|
||||||
|
|
||||||
Algorithm
|
|
||||||
---------
|
|
||||||
|
|
||||||
For more information on the Uniform Airflow Migration Strategy please refer to:
|
|
||||||
https://specs.openstack.org/openstack/watcher-specs/specs/newton/implemented/uniform-airflow-migration-strategy.html
|
|
||||||
|
|
||||||
How to use it ?
|
|
||||||
---------------
|
|
||||||
|
|
||||||
.. code-block:: shell
|
|
||||||
|
|
||||||
$ openstack optimize audittemplate create \
|
|
||||||
at1 airflow_optimization --strategy uniform_airflow
|
|
||||||
|
|
||||||
$ openstack optimize audit create -a at1 -p threshold_airflow=410 \
|
|
||||||
-p threshold_inlet_t=29.0 -p threshold_power=355.0 -p period=310
|
|
||||||
|
|
||||||
External Links
|
|
||||||
--------------
|
|
||||||
|
|
||||||
- `Intel Power Node Manager 3.0 <http://www.intel.com/content/www/us/en/power-management/intelligent-power-node-manager-3-0-specification.html>`_
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
==================================
|
|
||||||
VM Workload Consolidation Strategy
|
|
||||||
==================================
|
|
||||||
|
|
||||||
Synopsis
|
|
||||||
--------
|
|
||||||
|
|
||||||
**display name**: ``vm_workload_consolidation``
|
|
||||||
|
|
||||||
**goal**: ``vm_consolidation``
|
|
||||||
|
|
||||||
.. watcher-term:: watcher.decision_engine.strategy.strategies.vm_workload_consolidation
|
|
||||||
|
|
||||||
Requirements
|
|
||||||
------------
|
|
||||||
|
|
||||||
Metrics
|
|
||||||
*******
|
|
||||||
|
|
||||||
The *vm_workload_consolidation* strategy requires the following metrics:
|
|
||||||
|
|
||||||
============================ ============ ======= =======
|
|
||||||
metric service name plugins comment
|
|
||||||
============================ ============ ======= =======
|
|
||||||
``memory`` ceilometer_ none
|
|
||||||
``disk.root.size`` ceilometer_ none
|
|
||||||
============================ ============ ======= =======
|
|
||||||
|
|
||||||
The following metrics are not required but increase the accuracy of
|
|
||||||
the strategy if available:
|
|
||||||
|
|
||||||
============================ ============ ======= =======
|
|
||||||
metric service name plugins comment
|
|
||||||
============================ ============ ======= =======
|
|
||||||
``memory.usage`` ceilometer_ none
|
|
||||||
``cpu_util`` ceilometer_ none
|
|
||||||
============================ ============ ======= =======
|
|
||||||
|
|
||||||
.. _ceilometer: http://docs.openstack.org/admin-guide/telemetry-measurements.html#openstack-compute
|
|
||||||
|
|
||||||
Cluster data model
|
|
||||||
******************
|
|
||||||
|
|
||||||
Default Watcher's Compute cluster data model:
|
|
||||||
|
|
||||||
.. watcher-term:: watcher.decision_engine.model.collector.nova.NovaClusterDataModelCollector
|
|
||||||
|
|
||||||
Actions
|
|
||||||
*******
|
|
||||||
|
|
||||||
Default Watcher's actions:
|
|
||||||
|
|
||||||
|
|
||||||
.. list-table::
|
|
||||||
:widths: 30 30
|
|
||||||
:header-rows: 1
|
|
||||||
|
|
||||||
* - action
|
|
||||||
- description
|
|
||||||
* - ``migration``
|
|
||||||
- .. watcher-term:: watcher.applier.actions.migration.Migrate
|
|
||||||
* - ``change_nova_service_state``
|
|
||||||
- .. watcher-term:: watcher.applier.actions.change_nova_service_state.ChangeNovaServiceState
|
|
||||||
|
|
||||||
Planner
|
|
||||||
*******
|
|
||||||
|
|
||||||
Default Watcher's planner:
|
|
||||||
|
|
||||||
.. watcher-term:: watcher.decision_engine.planner.default.DefaultPlanner
|
|
||||||
|
|
||||||
|
|
||||||
Efficacy Indicator
|
|
||||||
------------------
|
|
||||||
|
|
||||||
.. watcher-func::
|
|
||||||
:format: literal_block
|
|
||||||
|
|
||||||
watcher.decision_engine.goal.efficacy.specs.ServerConsolidation.get_global_efficacy_indicator
|
|
||||||
|
|
||||||
Algorithm
|
|
||||||
---------
|
|
||||||
|
|
||||||
For more information on the VM Workload consolidation strategy please refer to: https://specs.openstack.org/openstack/watcher-specs/specs/mitaka/implemented/zhaw-load-consolidation.html
|
|
||||||
|
|
||||||
How to use it ?
|
|
||||||
---------------
|
|
||||||
|
|
||||||
.. code-block:: shell
|
|
||||||
|
|
||||||
$ openstack optimize audittemplate create \
|
|
||||||
at1 vm_consolidation --strategy vm_workload_consolidation
|
|
||||||
|
|
||||||
$ openstack optimize audit create -a at1
|
|
||||||
|
|
||||||
External Links
|
|
||||||
--------------
|
|
||||||
|
|
||||||
*Spec URL*
|
|
||||||
https://specs.openstack.org/openstack/watcher-specs/specs/mitaka/implemented/zhaw-load-consolidation.html
|
|
||||||
@@ -1,141 +0,0 @@
|
|||||||
=============================================
|
|
||||||
Watcher Overload standard deviation algorithm
|
|
||||||
=============================================
|
|
||||||
|
|
||||||
Synopsis
|
|
||||||
--------
|
|
||||||
|
|
||||||
**display name**: ``workload_stabilization``
|
|
||||||
|
|
||||||
**goal**: ``workload_balancing``
|
|
||||||
|
|
||||||
.. watcher-term:: watcher.decision_engine.strategy.strategies.workload_stabilization
|
|
||||||
|
|
||||||
Requirements
|
|
||||||
------------
|
|
||||||
|
|
||||||
Metrics
|
|
||||||
*******
|
|
||||||
|
|
||||||
The *workload_stabilization* strategy requires the following metrics:
|
|
||||||
|
|
||||||
============================ ============ ======= =======
|
|
||||||
metric service name plugins comment
|
|
||||||
============================ ============ ======= =======
|
|
||||||
``compute.node.cpu.percent`` ceilometer_ none
|
|
||||||
``hardware.memory.used`` ceilometer_ SNMP_
|
|
||||||
``cpu_util`` ceilometer_ none
|
|
||||||
``memory.resident`` ceilometer_ none
|
|
||||||
============================ ============ ======= =======
|
|
||||||
|
|
||||||
.. _ceilometer: http://docs.openstack.org/admin-guide/telemetry-measurements.html#openstack-compute
|
|
||||||
.. _SNMP: http://docs.openstack.org/admin-guide/telemetry-measurements.html
|
|
||||||
|
|
||||||
Cluster data model
|
|
||||||
******************
|
|
||||||
|
|
||||||
Default Watcher's Compute cluster data model:
|
|
||||||
|
|
||||||
.. watcher-term:: watcher.decision_engine.model.collector.nova.NovaClusterDataModelCollector
|
|
||||||
|
|
||||||
Actions
|
|
||||||
*******
|
|
||||||
|
|
||||||
Default Watcher's actions:
|
|
||||||
|
|
||||||
|
|
||||||
.. list-table::
|
|
||||||
:widths: 30 30
|
|
||||||
:header-rows: 1
|
|
||||||
|
|
||||||
* - action
|
|
||||||
- description
|
|
||||||
* - ``migration``
|
|
||||||
- .. watcher-term:: watcher.applier.actions.migration.Migrate
|
|
||||||
|
|
||||||
Planner
|
|
||||||
*******
|
|
||||||
|
|
||||||
Default Watcher's planner:
|
|
||||||
|
|
||||||
.. watcher-term:: watcher.decision_engine.planner.default.DefaultPlanner
|
|
||||||
|
|
||||||
Configuration
|
|
||||||
-------------
|
|
||||||
|
|
||||||
Strategy parameters are:
|
|
||||||
|
|
||||||
==================== ====== ===================== =============================
|
|
||||||
parameter type default Value description
|
|
||||||
==================== ====== ===================== =============================
|
|
||||||
``metrics`` array |metrics| Metrics used as rates of
|
|
||||||
cluster loads.
|
|
||||||
``thresholds`` object |thresholds| Dict where key is a metric
|
|
||||||
and value is a trigger value.
|
|
||||||
|
|
||||||
``weights`` object |weights| These weights used to
|
|
||||||
calculate common standard
|
|
||||||
deviation. Name of weight
|
|
||||||
contains meter name and
|
|
||||||
_weight suffix.
|
|
||||||
``instance_metrics`` object |instance_metrics| Mapping to get hardware
|
|
||||||
statistics using instance
|
|
||||||
metrics.
|
|
||||||
``host_choice`` string retry Method of host's choice.
|
|
||||||
There are cycle, retry and
|
|
||||||
fullsearch methods. Cycle
|
|
||||||
will iterate hosts in cycle.
|
|
||||||
Retry will get some hosts
|
|
||||||
random (count defined in
|
|
||||||
retry_count option).
|
|
||||||
Fullsearch will return each
|
|
||||||
host from list.
|
|
||||||
``retry_count`` number 1 Count of random returned
|
|
||||||
hosts.
|
|
||||||
``periods`` object |periods| These periods are used to get
|
|
||||||
statistic aggregation for
|
|
||||||
instance and host metrics.
|
|
||||||
The period is simply a
|
|
||||||
repeating interval of time
|
|
||||||
into which the samples are
|
|
||||||
grouped for aggregation.
|
|
||||||
Watcher uses only the last
|
|
||||||
period of all recieved ones.
|
|
||||||
==================== ====== ===================== =============================
|
|
||||||
|
|
||||||
.. |metrics| replace:: ["cpu_util", "memory.resident"]
|
|
||||||
.. |thresholds| replace:: {"cpu_util": 0.2, "memory.resident": 0.2}
|
|
||||||
.. |weights| replace:: {"cpu_util_weight": 1.0, "memory.resident_weight": 1.0}
|
|
||||||
.. |instance_metrics| replace:: {"cpu_util": "compute.node.cpu.percent", "memory.resident": "hardware.memory.used"}
|
|
||||||
.. |periods| replace:: {"instance": 720, "node": 600}
|
|
||||||
|
|
||||||
Efficacy Indicator
|
|
||||||
------------------
|
|
||||||
|
|
||||||
.. watcher-func::
|
|
||||||
:format: literal_block
|
|
||||||
|
|
||||||
watcher.decision_engine.goal.efficacy.specs.ServerConsolidation.get_global_efficacy_indicator
|
|
||||||
|
|
||||||
Algorithm
|
|
||||||
---------
|
|
||||||
|
|
||||||
You can find description of overload algorithm and role of standard deviation
|
|
||||||
here: https://specs.openstack.org/openstack/watcher-specs/specs/newton/implemented/sd-strategy.html
|
|
||||||
|
|
||||||
How to use it ?
|
|
||||||
---------------
|
|
||||||
|
|
||||||
.. code-block:: shell
|
|
||||||
|
|
||||||
$ openstack optimize audittemplate create \
|
|
||||||
at1 workload_balancing --strategy workload_stabilization
|
|
||||||
|
|
||||||
$ openstack optimize audit create -a at1 \
|
|
||||||
-p thresholds='{"memory.resident": 0.05}' \
|
|
||||||
-p metrics='["memory.resident"]'
|
|
||||||
|
|
||||||
External Links
|
|
||||||
--------------
|
|
||||||
|
|
||||||
- `Watcher Overload standard deviation algorithm spec <https://specs.openstack.org/openstack/watcher-specs/specs/newton/implemented/sd-strategy.html>`_
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
===================================
|
|
||||||
Workload Balance Migration Strategy
|
|
||||||
===================================
|
|
||||||
|
|
||||||
Synopsis
|
|
||||||
--------
|
|
||||||
|
|
||||||
**display name**: ``workload_balance``
|
|
||||||
|
|
||||||
**goal**: ``workload_balancing``
|
|
||||||
|
|
||||||
.. watcher-term:: watcher.decision_engine.strategy.strategies.workload_balance
|
|
||||||
|
|
||||||
Requirements
|
|
||||||
------------
|
|
||||||
|
|
||||||
None.
|
|
||||||
|
|
||||||
Metrics
|
|
||||||
*******
|
|
||||||
|
|
||||||
The *workload_balance* strategy requires the following metrics:
|
|
||||||
|
|
||||||
======================= ============ ======= =======
|
|
||||||
metric service name plugins comment
|
|
||||||
======================= ============ ======= =======
|
|
||||||
``cpu_util`` ceilometer_ none
|
|
||||||
======================= ============ ======= =======
|
|
||||||
|
|
||||||
.. _ceilometer: http://docs.openstack.org/admin-guide/telemetry-measurements.html#openstack-compute
|
|
||||||
|
|
||||||
|
|
||||||
Cluster data model
|
|
||||||
******************
|
|
||||||
|
|
||||||
Default Watcher's Compute cluster data model:
|
|
||||||
|
|
||||||
.. watcher-term:: watcher.decision_engine.model.collector.nova.NovaClusterDataModelCollector
|
|
||||||
|
|
||||||
Actions
|
|
||||||
*******
|
|
||||||
|
|
||||||
Default Watcher's actions:
|
|
||||||
|
|
||||||
.. list-table::
|
|
||||||
:widths: 30 30
|
|
||||||
:header-rows: 1
|
|
||||||
|
|
||||||
* - action
|
|
||||||
- description
|
|
||||||
* - ``migration``
|
|
||||||
- .. watcher-term:: watcher.applier.actions.migration.Migrate
|
|
||||||
|
|
||||||
Planner
|
|
||||||
*******
|
|
||||||
|
|
||||||
Default Watcher's planner:
|
|
||||||
|
|
||||||
.. watcher-term:: watcher.decision_engine.planner.default.DefaultPlanner
|
|
||||||
|
|
||||||
Configuration
|
|
||||||
-------------
|
|
||||||
|
|
||||||
Strategy parameters are:
|
|
||||||
|
|
||||||
============== ====== ============= ====================================
|
|
||||||
parameter type default Value description
|
|
||||||
============== ====== ============= ====================================
|
|
||||||
``threshold`` Number 25.0 Workload threshold for migration
|
|
||||||
``period`` Number 300 Aggregate time period of ceilometer
|
|
||||||
============== ====== ============= ====================================
|
|
||||||
|
|
||||||
Efficacy Indicator
|
|
||||||
------------------
|
|
||||||
|
|
||||||
None
|
|
||||||
|
|
||||||
Algorithm
|
|
||||||
---------
|
|
||||||
|
|
||||||
For more information on the Workload Balance Migration Strategy please refer
|
|
||||||
to: https://specs.openstack.org/openstack/watcher-specs/specs/mitaka/implemented/workload-balance-migration-strategy.html
|
|
||||||
|
|
||||||
How to use it ?
|
|
||||||
---------------
|
|
||||||
|
|
||||||
.. code-block:: shell
|
|
||||||
|
|
||||||
$ openstack optimize audittemplate create \
|
|
||||||
at1 workload_balancing --strategy workload_balance
|
|
||||||
|
|
||||||
$ openstack optimize audit create -a at1 -p threshold=26.0 \
|
|
||||||
-p period=310
|
|
||||||
|
|
||||||
External Links
|
|
||||||
--------------
|
|
||||||
|
|
||||||
None.
|
|
||||||
@@ -37,9 +37,5 @@
|
|||||||
|
|
||||||
"strategy:detail": "rule:default",
|
"strategy:detail": "rule:default",
|
||||||
"strategy:get": "rule:default",
|
"strategy:get": "rule:default",
|
||||||
"strategy:get_all": "rule:default",
|
"strategy:get_all": "rule:default"
|
||||||
|
|
||||||
"service:detail": "rule:default",
|
|
||||||
"service:get": "rule:default",
|
|
||||||
"service:get_all": "rule:default"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ To launch this task with configured Rally you just need to run:
|
|||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
rally task start watcher/rally-jobs/watcher-watcher.yaml
|
rally task start watcher/rally-jobs/watcher.yaml
|
||||||
|
|
||||||
Structure
|
Structure
|
||||||
---------
|
---------
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
name: "dummy"
|
name: "dummy"
|
||||||
strategy:
|
strategy:
|
||||||
name: "dummy"
|
name: "dummy"
|
||||||
|
extra: {}
|
||||||
sla:
|
sla:
|
||||||
failure_rate:
|
failure_rate:
|
||||||
max: 0
|
max: 0
|
||||||
@@ -28,6 +29,7 @@
|
|||||||
name: "dummy"
|
name: "dummy"
|
||||||
strategy:
|
strategy:
|
||||||
name: "dummy"
|
name: "dummy"
|
||||||
|
extra: {}
|
||||||
runner:
|
runner:
|
||||||
type: "constant"
|
type: "constant"
|
||||||
times: 10
|
times: 10
|
||||||
@@ -54,10 +56,12 @@
|
|||||||
name: "workload_balancing"
|
name: "workload_balancing"
|
||||||
strategy:
|
strategy:
|
||||||
name: "workload_stabilization"
|
name: "workload_stabilization"
|
||||||
|
extra: {}
|
||||||
- goal:
|
- goal:
|
||||||
name: "dummy"
|
name: "dummy"
|
||||||
strategy:
|
strategy:
|
||||||
name: "dummy"
|
name: "dummy"
|
||||||
|
extra: {}
|
||||||
sla:
|
sla:
|
||||||
failure_rate:
|
failure_rate:
|
||||||
max: 0
|
max: 0
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
features:
|
features:
|
||||||
- Added a generic scoring engine module, which
|
- Added a generic scoring engine module, which
|
||||||
will standardize interactions with scoring engines
|
will standarize interactions with scoring engines
|
||||||
through the common API. It is possible to use the
|
through the common API. It is possible to use the
|
||||||
scoring engine by different Strategies, which
|
scoring engine by different Strategies, which
|
||||||
improve the code and data model re-use.
|
improve the code and data model re-use.
|
||||||
|
|||||||
@@ -2,4 +2,4 @@
|
|||||||
features:
|
features:
|
||||||
- Added a way to return the of available goals depending
|
- Added a way to return the of available goals depending
|
||||||
on which strategies have been deployed on the node
|
on which strategies have been deployed on the node
|
||||||
where the decision engine is running.
|
where the decison engine is running.
|
||||||
|
|||||||
@@ -242,6 +242,3 @@ texinfo_documents = [
|
|||||||
|
|
||||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||||
#texinfo_show_urls = 'footnote'
|
#texinfo_show_urls = 'footnote'
|
||||||
|
|
||||||
# -- Options for Internationalization output ------------------------------
|
|
||||||
locale_dirs = ['locale/']
|
|
||||||
|
|||||||
@@ -6,6 +6,5 @@ Contents:
|
|||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
|
||||||
unreleased
|
unreleased.rst
|
||||||
newton
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
===================================
|
|
||||||
Newton Series Release Notes
|
|
||||||
===================================
|
|
||||||
|
|
||||||
.. release-notes::
|
|
||||||
:branch: origin/stable/newton
|
|
||||||
@@ -5,38 +5,37 @@
|
|||||||
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.14.0 # Apache-2.0
|
keystoneauth1>=2.10.0 # Apache-2.0
|
||||||
keystonemiddleware!=4.5.0,>=4.2.0 # Apache-2.0
|
keystonemiddleware!=4.1.0,!=4.5.0,>=4.0.0 # Apache-2.0
|
||||||
lxml>=2.3 # BSD
|
lxml>=2.3 # BSD
|
||||||
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.18.0,>=3.14.0 # Apache-2.0
|
oslo.config>=3.14.0 # Apache-2.0
|
||||||
oslo.context>=2.9.0 # Apache-2.0
|
oslo.context>=2.9.0 # Apache-2.0
|
||||||
oslo.db!=4.13.1,!=4.13.2,>=4.11.0 # Apache-2.0
|
oslo.db!=4.13.1,!=4.13.2,>=4.10.0 # Apache-2.0
|
||||||
oslo.i18n>=2.1.0 # Apache-2.0
|
oslo.i18n>=2.1.0 # Apache-2.0
|
||||||
oslo.log>=3.11.0 # Apache-2.0
|
oslo.log>=1.14.0 # Apache-2.0
|
||||||
oslo.messaging>=5.14.0 # Apache-2.0
|
oslo.messaging>=5.2.0 # Apache-2.0
|
||||||
oslo.policy>=1.17.0 # Apache-2.0
|
oslo.policy>=1.9.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>=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.18.0 # Apache-2.0
|
oslo.utils>=3.16.0 # Apache-2.0
|
||||||
oslo.versionedobjects>=1.17.0 # Apache-2.0
|
|
||||||
PasteDeploy>=1.5.0 # MIT
|
PasteDeploy>=1.5.0 # MIT
|
||||||
pbr>=1.8 # Apache-2.0
|
pbr>=1.6 # Apache-2.0
|
||||||
pecan!=1.0.2,!=1.0.3,!=1.0.4,!=1.2,>=1.0.0 # BSD
|
pecan!=1.0.2,!=1.0.3,!=1.0.4,>=1.0.0 # BSD
|
||||||
PrettyTable<0.8,>=0.7.1 # BSD
|
PrettyTable<0.8,>=0.7 # BSD
|
||||||
voluptuous>=0.8.9 # BSD License
|
voluptuous>=0.8.9 # BSD License
|
||||||
python-ceilometerclient>=2.5.0 # Apache-2.0
|
python-ceilometerclient>=2.5.0 # Apache-2.0
|
||||||
python-cinderclient!=1.7.0,!=1.7.1,>=1.6.0 # Apache-2.0
|
python-cinderclient!=1.7.0,!=1.7.1,>=1.6.0 # Apache-2.0
|
||||||
python-glanceclient>=2.5.0 # Apache-2.0
|
python-glanceclient!=2.4.0,>=2.3.0 # Apache-2.0
|
||||||
python-keystoneclient>=3.8.0 # Apache-2.0
|
python-keystoneclient!=2.1.0,>=2.0.0 # Apache-2.0
|
||||||
python-neutronclient>=5.1.0 # Apache-2.0
|
python-neutronclient>=5.1.0 # Apache-2.0
|
||||||
python-novaclient!=2.33.0,>=2.29.0 # Apache-2.0
|
python-novaclient!=2.33.0,>=2.29.0 # Apache-2.0
|
||||||
python-openstackclient>=3.3.0 # Apache-2.0
|
python-openstackclient>=2.1.0 # Apache-2.0
|
||||||
six>=1.9.0 # MIT
|
six>=1.9.0 # MIT
|
||||||
SQLAlchemy<1.1.0,>=1.0.10 # MIT
|
SQLAlchemy<1.1.0,>=1.0.10 # MIT
|
||||||
stevedore>=1.17.1 # Apache-2.0
|
stevedore>=1.16.0 # Apache-2.0
|
||||||
taskflow>=1.26.0 # Apache-2.0
|
taskflow>=1.26.0 # Apache-2.0
|
||||||
WebOb>=1.6.0 # MIT
|
WebOb>=1.2.3 # MIT
|
||||||
WSME>=0.8 # MIT
|
WSME>=0.8 # MIT
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[metadata]
|
[metadata]
|
||||||
name = python-watcher
|
name = python-watcher
|
||||||
summary = OpenStack Watcher provides a flexible and scalable resource optimization service for multi-tenant OpenStack-based clouds.
|
summary = Watcher takes advantage of CEP and ML algorithms/metaheuristics to improve physical resources usage through better VM placement. Watcher can improve your cloud optimization by reducing energy footprint and increasing profits.
|
||||||
description-file =
|
description-file =
|
||||||
README.rst
|
README.rst
|
||||||
author = OpenStack
|
author = OpenStack
|
||||||
@@ -32,7 +32,7 @@ setup-hooks =
|
|||||||
|
|
||||||
[entry_points]
|
[entry_points]
|
||||||
oslo.config.opts =
|
oslo.config.opts =
|
||||||
watcher = watcher.conf.opts:list_opts
|
watcher = watcher.opts:list_opts
|
||||||
|
|
||||||
console_scripts =
|
console_scripts =
|
||||||
watcher-api = watcher.cmd.api:main
|
watcher-api = watcher.cmd.api:main
|
||||||
|
|||||||
@@ -2,25 +2,25 @@
|
|||||||
# 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>=3.6 # Apache-2.0
|
||||||
doc8 # Apache-2.0
|
doc8 # Apache-2.0
|
||||||
freezegun>=0.3.6 # Apache-2.0
|
freezegun # Apache-2.0
|
||||||
hacking<0.11,>=0.10.2
|
hacking<0.11,>=0.10.2
|
||||||
mock>=2.0 # BSD
|
mock>=2.0 # BSD
|
||||||
oslotest>=1.10.0 # Apache-2.0
|
oslotest>=1.10.0 # Apache-2.0
|
||||||
os-testr>=0.8.0 # Apache-2.0
|
os-testr>=0.7.0 # Apache-2.0
|
||||||
python-subunit>=0.0.18 # Apache-2.0/BSD
|
python-subunit>=0.0.18 # Apache-2.0/BSD
|
||||||
testrepository>=0.0.18 # Apache-2.0/BSD
|
testrepository>=0.0.18 # Apache-2.0/BSD
|
||||||
testscenarios>=0.4 # Apache-2.0/BSD
|
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
|
oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0
|
||||||
sphinx!=1.3b1,<1.4,>=1.2.1 # BSD
|
sphinx!=1.3b1,<1.3,>=1.2.1 # 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>=1.8.0 # Apache2
|
||||||
|
|
||||||
# bandit
|
# bandit
|
||||||
bandit>=1.1.0 # Apache-2.0
|
bandit>=1.1.0 # Apache-2.0
|
||||||
|
|||||||
11
tox.ini
11
tox.ini
@@ -7,20 +7,21 @@ skipsdist = True
|
|||||||
usedevelop = True
|
usedevelop = True
|
||||||
whitelist_externals = find
|
whitelist_externals = find
|
||||||
install_command =
|
install_command =
|
||||||
pip install -U --force-reinstall -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages}
|
constraints: pip install -U --force-reinstall -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt?h=stable/newton} {opts} {packages}
|
||||||
|
pip install -U {opts} {packages}
|
||||||
setenv =
|
setenv =
|
||||||
VIRTUAL_ENV={envdir}
|
VIRTUAL_ENV={envdir}
|
||||||
deps = -r{toxinidir}/test-requirements.txt
|
deps = -r{toxinidir}/test-requirements.txt
|
||||||
commands =
|
commands =
|
||||||
find . -type f -name "*.py[c|o]" -delete
|
find . -type f -name "*.pyc" -delete
|
||||||
|
find . -type d -name "__pycache__" -delete
|
||||||
ostestr --concurrency=6 {posargs}
|
ostestr --concurrency=6 {posargs}
|
||||||
|
|
||||||
[testenv:pep8]
|
[testenv:pep8]
|
||||||
commands =
|
commands =
|
||||||
doc8 doc/source/ CONTRIBUTING.rst HACKING.rst README.rst
|
doc8 doc/source/ CONTRIBUTING.rst HACKING.rst README.rst
|
||||||
flake8
|
flake8
|
||||||
bandit -r watcher -x tests -n5 -ll -s B320
|
bandit -r watcher -x tests -n5 -ll
|
||||||
|
|
||||||
[testenv:venv]
|
[testenv:venv]
|
||||||
setenv = PYTHONHASHSEED=0
|
setenv = PYTHONHASHSEED=0
|
||||||
@@ -66,4 +67,4 @@ commands = sphinx-build -a -W -E -d releasenotes/build/doctrees -b html releasen
|
|||||||
|
|
||||||
[testenv:bandit]
|
[testenv:bandit]
|
||||||
deps = -r{toxinidir}/test-requirements.txt
|
deps = -r{toxinidir}/test-requirements.txt
|
||||||
commands = bandit -r watcher -x tests -n5 -ll -s B320
|
commands = bandit -r watcher -x tests -n5 -ll
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
# -*- encoding: utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright © 2012 New Dream Network, LLC (DreamHost)
|
# Copyright © 2012 New Dream Network, LLC (DreamHost)
|
||||||
# Copyright (c) 2016 Intel Corp
|
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
# 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
|
# not use this file except in compliance with the License. You may obtain
|
||||||
@@ -18,23 +17,32 @@
|
|||||||
|
|
||||||
"""Access Control Lists (ACL's) control access the API server."""
|
"""Access Control Lists (ACL's) control access the API server."""
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
from watcher.api.middleware import auth_token
|
from watcher.api.middleware import auth_token
|
||||||
from watcher import conf
|
|
||||||
|
|
||||||
CONF = conf.CONF
|
|
||||||
|
AUTH_OPTS = [
|
||||||
|
cfg.BoolOpt('enable_authentication',
|
||||||
|
default=True,
|
||||||
|
help='This option enables or disables user authentication '
|
||||||
|
'via keystone. Default value is True.'),
|
||||||
|
]
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
CONF.register_opts(AUTH_OPTS)
|
||||||
|
|
||||||
|
|
||||||
def install(app, conf, public_routes):
|
def install(app, conf, public_routes):
|
||||||
"""Install ACL check on application.
|
"""Install ACL check on application.
|
||||||
|
|
||||||
:param app: A WSGI application.
|
:param app: A WSGI applicatin.
|
||||||
:param conf: Settings. Dict'ified and passed to keystonemiddleware
|
:param conf: Settings. Dict'ified and passed to keystonemiddleware
|
||||||
:param public_routes: The list of the routes which will be allowed to
|
:param public_routes: The list of the routes which will be allowed to
|
||||||
access without authentication.
|
access without authentication.
|
||||||
:return: The same WSGI application with ACL installed.
|
:return: The same WSGI application with ACL installed.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if not CONF.get('enable_authentication'):
|
if not cfg.CONF.get('enable_authentication'):
|
||||||
return app
|
return app
|
||||||
return auth_token.AuthTokenMiddleware(app,
|
return auth_token.AuthTokenMiddleware(app,
|
||||||
conf=dict(conf),
|
conf=dict(conf),
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
# Copyright © 2012 New Dream Network, LLC (DreamHost)
|
# Copyright © 2012 New Dream Network, LLC (DreamHost)
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
# Copyright (c) 2016 Intel Corp
|
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
# 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
|
# not use this file except in compliance with the License. You may obtain
|
||||||
@@ -17,14 +16,49 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
import pecan
|
import pecan
|
||||||
|
|
||||||
|
from watcher._i18n import _
|
||||||
from watcher.api import acl
|
from watcher.api import acl
|
||||||
from watcher.api import config as api_config
|
from watcher.api import config as api_config
|
||||||
from watcher.api import middleware
|
from watcher.api import middleware
|
||||||
from watcher import conf
|
|
||||||
|
|
||||||
CONF = conf.CONF
|
# Register options for the service
|
||||||
|
API_SERVICE_OPTS = [
|
||||||
|
cfg.PortOpt('port',
|
||||||
|
default=9322,
|
||||||
|
help=_('The port for the watcher API server')),
|
||||||
|
cfg.StrOpt('host',
|
||||||
|
default='127.0.0.1',
|
||||||
|
help=_('The listen IP for the watcher API server')),
|
||||||
|
cfg.IntOpt('max_limit',
|
||||||
|
default=1000,
|
||||||
|
help=_('The maximum number of items returned in a single '
|
||||||
|
'response from a collection resource')),
|
||||||
|
cfg.IntOpt('workers',
|
||||||
|
min=1,
|
||||||
|
help=_('Number of workers for Watcher API service. '
|
||||||
|
'The default is equal to the number of CPUs available '
|
||||||
|
'if that can be determined, else a default worker '
|
||||||
|
'count of 1 is returned.')),
|
||||||
|
|
||||||
|
cfg.BoolOpt('enable_ssl_api',
|
||||||
|
default=False,
|
||||||
|
help=_("Enable the integrated stand-alone API to service "
|
||||||
|
"requests via HTTPS instead of HTTP. If there is a "
|
||||||
|
"front-end service performing HTTPS offloading from "
|
||||||
|
"the service, this option should be False; note, you "
|
||||||
|
"will want to change public API endpoint to represent "
|
||||||
|
"SSL termination URL with 'public_endpoint' option.")),
|
||||||
|
]
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
opt_group = cfg.OptGroup(name='api',
|
||||||
|
title='Options for the watcher-api service')
|
||||||
|
|
||||||
|
CONF.register_group(opt_group)
|
||||||
|
CONF.register_opts(API_SERVICE_OPTS, opt_group)
|
||||||
|
|
||||||
|
|
||||||
def get_pecan_config():
|
def get_pecan_config():
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ class Root(base.APIBase):
|
|||||||
root = Root()
|
root = Root()
|
||||||
root.name = "OpenStack Watcher API"
|
root.name = "OpenStack Watcher API"
|
||||||
root.description = ("Watcher is an OpenStack project which aims to "
|
root.description = ("Watcher is an OpenStack project which aims to "
|
||||||
"improve physical resources usage through "
|
"to improve physical resources usage through "
|
||||||
"better VM placement.")
|
"better VM placement.")
|
||||||
root.versions = [Version.convert('v1')]
|
root.versions = [Version.convert('v1')]
|
||||||
root.default_version = Version.convert('v1')
|
root.default_version = Version.convert('v1')
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ from watcher.api.controllers.v1 import audit
|
|||||||
from watcher.api.controllers.v1 import audit_template
|
from watcher.api.controllers.v1 import audit_template
|
||||||
from watcher.api.controllers.v1 import goal
|
from watcher.api.controllers.v1 import goal
|
||||||
from watcher.api.controllers.v1 import scoring_engine
|
from watcher.api.controllers.v1 import scoring_engine
|
||||||
from watcher.api.controllers.v1 import service
|
|
||||||
from watcher.api.controllers.v1 import strategy
|
from watcher.api.controllers.v1 import strategy
|
||||||
|
|
||||||
|
|
||||||
@@ -106,9 +105,6 @@ class V1(APIBase):
|
|||||||
scoring_engines = [link.Link]
|
scoring_engines = [link.Link]
|
||||||
"""Links to the Scoring Engines resource"""
|
"""Links to the Scoring Engines resource"""
|
||||||
|
|
||||||
services = [link.Link]
|
|
||||||
"""Links to the services resource"""
|
|
||||||
|
|
||||||
links = [link.Link]
|
links = [link.Link]
|
||||||
"""Links that point to a specific URL for this version and documentation"""
|
"""Links that point to a specific URL for this version and documentation"""
|
||||||
|
|
||||||
@@ -163,14 +159,6 @@ class V1(APIBase):
|
|||||||
'scoring_engines', '',
|
'scoring_engines', '',
|
||||||
bookmark=True)
|
bookmark=True)
|
||||||
]
|
]
|
||||||
|
|
||||||
v1.services = [link.Link.make_link(
|
|
||||||
'self', pecan.request.host_url, 'services', ''),
|
|
||||||
link.Link.make_link('bookmark',
|
|
||||||
pecan.request.host_url,
|
|
||||||
'services', '',
|
|
||||||
bookmark=True)
|
|
||||||
]
|
|
||||||
return v1
|
return v1
|
||||||
|
|
||||||
|
|
||||||
@@ -183,7 +171,6 @@ class Controller(rest.RestController):
|
|||||||
action_plans = action_plan.ActionPlansController()
|
action_plans = action_plan.ActionPlansController()
|
||||||
goals = goal.GoalsController()
|
goals = goal.GoalsController()
|
||||||
scoring_engines = scoring_engine.ScoringEngineController()
|
scoring_engines = scoring_engine.ScoringEngineController()
|
||||||
services = service.ServicesController()
|
|
||||||
strategies = strategy.StrategiesController()
|
strategies = strategy.StrategiesController()
|
||||||
|
|
||||||
@wsme_pecan.wsexpose(V1)
|
@wsme_pecan.wsexpose(V1)
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ be one of the following:
|
|||||||
processed by the :ref:`Watcher Applier <watcher_applier_definition>`
|
processed by the :ref:`Watcher Applier <watcher_applier_definition>`
|
||||||
- **SUCCEEDED** : the :ref:`Action <action_definition>` has been executed
|
- **SUCCEEDED** : the :ref:`Action <action_definition>` has been executed
|
||||||
successfully
|
successfully
|
||||||
- **FAILED** : an error occurred while trying to execute the
|
- **FAILED** : an error occured while trying to execute the
|
||||||
:ref:`Action <action_definition>`
|
:ref:`Action <action_definition>`
|
||||||
- **DELETED** : the :ref:`Action <action_definition>` is still stored in the
|
- **DELETED** : the :ref:`Action <action_definition>` is still stored in the
|
||||||
:ref:`Watcher database <watcher_database_definition>` but is not returned
|
:ref:`Watcher database <watcher_database_definition>` but is not returned
|
||||||
@@ -379,7 +379,7 @@ class ActionsController(rest.RestController):
|
|||||||
action_dict = action.as_dict()
|
action_dict = action.as_dict()
|
||||||
context = pecan.request.context
|
context = pecan.request.context
|
||||||
new_action = objects.Action(context, **action_dict)
|
new_action = objects.Action(context, **action_dict)
|
||||||
new_action.create()
|
new_action.create(context)
|
||||||
|
|
||||||
# Set the HTTP Location Header
|
# Set the HTTP Location Header
|
||||||
pecan.response.location = link.build_url('actions', new_action.uuid)
|
pecan.response.location = link.build_url('actions', new_action.uuid)
|
||||||
|
|||||||
@@ -496,6 +496,7 @@ class ActionPlansController(rest.RestController):
|
|||||||
:param action_plan_uuid: UUID of a action plan.
|
:param action_plan_uuid: UUID of a action plan.
|
||||||
:param patch: a json PATCH document to apply to this action plan.
|
:param patch: a json PATCH document to apply to this action plan.
|
||||||
"""
|
"""
|
||||||
|
launch_action_plan = True
|
||||||
if self.from_actionsPlans:
|
if self.from_actionsPlans:
|
||||||
raise exception.OperationNotPermitted
|
raise exception.OperationNotPermitted
|
||||||
|
|
||||||
|
|||||||
@@ -60,6 +60,8 @@ class AuditPostType(wtypes.Base):
|
|||||||
|
|
||||||
audit_type = wtypes.wsattr(wtypes.text, mandatory=True)
|
audit_type = wtypes.wsattr(wtypes.text, mandatory=True)
|
||||||
|
|
||||||
|
deadline = wtypes.wsattr(datetime.datetime, mandatory=False)
|
||||||
|
|
||||||
state = wsme.wsattr(wtypes.text, readonly=True,
|
state = wsme.wsattr(wtypes.text, readonly=True,
|
||||||
default=objects.audit.State.PENDING)
|
default=objects.audit.State.PENDING)
|
||||||
|
|
||||||
@@ -67,7 +69,8 @@ class AuditPostType(wtypes.Base):
|
|||||||
default={})
|
default={})
|
||||||
interval = wsme.wsattr(int, mandatory=False)
|
interval = wsme.wsattr(int, mandatory=False)
|
||||||
|
|
||||||
scope = wtypes.wsattr(types.jsontype, readonly=True)
|
host_aggregate = wsme.wsattr(wtypes.IntegerType(minimum=1),
|
||||||
|
mandatory=False)
|
||||||
|
|
||||||
def as_audit(self, context):
|
def as_audit(self, context):
|
||||||
audit_type_values = [val.value for val in objects.audit.AuditType]
|
audit_type_values = [val.value for val in objects.audit.AuditType]
|
||||||
@@ -79,7 +82,7 @@ class AuditPostType(wtypes.Base):
|
|||||||
raise exception.AuditIntervalNotAllowed(audit_type=self.audit_type)
|
raise exception.AuditIntervalNotAllowed(audit_type=self.audit_type)
|
||||||
|
|
||||||
if (self.audit_type == objects.audit.AuditType.CONTINUOUS.value and
|
if (self.audit_type == objects.audit.AuditType.CONTINUOUS.value and
|
||||||
self.interval in (wtypes.Unset, None)):
|
self.interval in (wtypes.Unset, None)):
|
||||||
raise exception.AuditIntervalNotSpecified(
|
raise exception.AuditIntervalNotSpecified(
|
||||||
audit_type=self.audit_type)
|
audit_type=self.audit_type)
|
||||||
|
|
||||||
@@ -97,7 +100,7 @@ class AuditPostType(wtypes.Base):
|
|||||||
at2a = {
|
at2a = {
|
||||||
'goal': 'goal_id',
|
'goal': 'goal_id',
|
||||||
'strategy': 'strategy_id',
|
'strategy': 'strategy_id',
|
||||||
'scope': 'scope',
|
'host_aggregate': 'host_aggregate'
|
||||||
}
|
}
|
||||||
to_string_fields = set(['goal', 'strategy'])
|
to_string_fields = set(['goal', 'strategy'])
|
||||||
for k in at2a:
|
for k in at2a:
|
||||||
@@ -111,11 +114,12 @@ class AuditPostType(wtypes.Base):
|
|||||||
pass
|
pass
|
||||||
return Audit(
|
return Audit(
|
||||||
audit_type=self.audit_type,
|
audit_type=self.audit_type,
|
||||||
|
deadline=self.deadline,
|
||||||
parameters=self.parameters,
|
parameters=self.parameters,
|
||||||
goal_id=self.goal,
|
goal_id=self.goal,
|
||||||
|
host_aggregate=self.host_aggregate,
|
||||||
strategy_id=self.strategy,
|
strategy_id=self.strategy,
|
||||||
interval=self.interval,
|
interval=self.interval)
|
||||||
scope=self.scope,)
|
|
||||||
|
|
||||||
|
|
||||||
class AuditPatchType(types.JsonPatchType):
|
class AuditPatchType(types.JsonPatchType):
|
||||||
@@ -226,6 +230,9 @@ class Audit(base.APIBase):
|
|||||||
audit_type = wtypes.text
|
audit_type = wtypes.text
|
||||||
"""Type of this audit"""
|
"""Type of this audit"""
|
||||||
|
|
||||||
|
deadline = datetime.datetime
|
||||||
|
"""deadline of the audit"""
|
||||||
|
|
||||||
state = wtypes.text
|
state = wtypes.text
|
||||||
"""This audit state"""
|
"""This audit state"""
|
||||||
|
|
||||||
@@ -254,8 +261,8 @@ class Audit(base.APIBase):
|
|||||||
interval = wsme.wsattr(int, mandatory=False)
|
interval = wsme.wsattr(int, mandatory=False)
|
||||||
"""Launch audit periodically (in seconds)"""
|
"""Launch audit periodically (in seconds)"""
|
||||||
|
|
||||||
scope = wsme.wsattr(types.jsontype, mandatory=False)
|
host_aggregate = wtypes.IntegerType(minimum=1)
|
||||||
"""Audit Scope"""
|
"""ID of the Nova host aggregate targeted by the audit template"""
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
self.fields = []
|
self.fields = []
|
||||||
@@ -285,10 +292,10 @@ class Audit(base.APIBase):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def _convert_with_links(audit, url, expand=True):
|
def _convert_with_links(audit, url, expand=True):
|
||||||
if not expand:
|
if not expand:
|
||||||
audit.unset_fields_except(['uuid', 'audit_type', 'state',
|
audit.unset_fields_except(['uuid', 'audit_type', 'deadline',
|
||||||
'goal_uuid', 'interval', 'scope',
|
'state', 'goal_uuid', 'interval',
|
||||||
'strategy_uuid', 'goal_name',
|
'strategy_uuid', 'host_aggregate',
|
||||||
'strategy_name'])
|
'goal_name', 'strategy_name'])
|
||||||
|
|
||||||
audit.links = [link.Link.make_link('self', url,
|
audit.links = [link.Link.make_link('self', url,
|
||||||
'audits', audit.uuid),
|
'audits', audit.uuid),
|
||||||
@@ -309,15 +316,15 @@ class Audit(base.APIBase):
|
|||||||
sample = cls(uuid='27e3153e-d5bf-4b7e-b517-fb518e17f34c',
|
sample = cls(uuid='27e3153e-d5bf-4b7e-b517-fb518e17f34c',
|
||||||
audit_type='ONESHOT',
|
audit_type='ONESHOT',
|
||||||
state='PENDING',
|
state='PENDING',
|
||||||
|
deadline=None,
|
||||||
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=[])
|
|
||||||
|
|
||||||
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'
|
||||||
|
sample.host_aggregate = 1
|
||||||
return cls._convert_with_links(sample, 'http://localhost:9322', expand)
|
return cls._convert_with_links(sample, 'http://localhost:9322', expand)
|
||||||
|
|
||||||
|
|
||||||
@@ -374,7 +381,7 @@ class AuditsController(rest.RestController):
|
|||||||
def _get_audits_collection(self, marker, limit,
|
def _get_audits_collection(self, marker, limit,
|
||||||
sort_key, sort_dir, expand=False,
|
sort_key, sort_dir, expand=False,
|
||||||
resource_url=None, goal=None,
|
resource_url=None, goal=None,
|
||||||
strategy=None):
|
strategy=None, host_aggregate=None):
|
||||||
limit = api_utils.validate_limit(limit)
|
limit = api_utils.validate_limit(limit)
|
||||||
api_utils.validate_sort_dir(sort_dir)
|
api_utils.validate_sort_dir(sort_dir)
|
||||||
marker_obj = None
|
marker_obj = None
|
||||||
@@ -417,16 +424,19 @@ class AuditsController(rest.RestController):
|
|||||||
|
|
||||||
@wsme_pecan.wsexpose(AuditCollection, types.uuid, int, wtypes.text,
|
@wsme_pecan.wsexpose(AuditCollection, types.uuid, int, wtypes.text,
|
||||||
wtypes.text, wtypes.text, wtypes.text, int)
|
wtypes.text, wtypes.text, wtypes.text, int)
|
||||||
def get_all(self, marker=None, limit=None, sort_key='id', sort_dir='asc',
|
def get_all(self, marker=None, limit=None,
|
||||||
goal=None, strategy=None):
|
sort_key='id', sort_dir='asc', goal=None,
|
||||||
|
strategy=None, host_aggregate=None):
|
||||||
"""Retrieve a list of audits.
|
"""Retrieve a list of audits.
|
||||||
|
|
||||||
:param marker: pagination marker for large data sets.
|
:param marker: pagination marker for large data sets.
|
||||||
:param limit: maximum number of resources to return in a single result.
|
:param limit: maximum number of resources to return in a single result.
|
||||||
:param sort_key: column to sort results by. Default: id.
|
:param sort_key: column to sort results by. Default: id.
|
||||||
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
|
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
|
||||||
|
id.
|
||||||
:param goal: goal UUID or name to filter by
|
:param goal: goal UUID or name to filter by
|
||||||
:param strategy: strategy UUID or name to filter by
|
:param strategy: strategy UUID or name to filter by
|
||||||
|
:param host_aggregate: Optional host_aggregate
|
||||||
"""
|
"""
|
||||||
|
|
||||||
context = pecan.request.context
|
context = pecan.request.context
|
||||||
@@ -435,7 +445,8 @@ class AuditsController(rest.RestController):
|
|||||||
|
|
||||||
return self._get_audits_collection(marker, limit, sort_key,
|
return self._get_audits_collection(marker, limit, sort_key,
|
||||||
sort_dir, goal=goal,
|
sort_dir, goal=goal,
|
||||||
strategy=strategy)
|
strategy=strategy,
|
||||||
|
host_aggregate=host_aggregate)
|
||||||
|
|
||||||
@wsme_pecan.wsexpose(AuditCollection, wtypes.text, types.uuid, int,
|
@wsme_pecan.wsexpose(AuditCollection, wtypes.text, types.uuid, int,
|
||||||
wtypes.text, wtypes.text)
|
wtypes.text, wtypes.text)
|
||||||
@@ -519,12 +530,13 @@ class AuditsController(rest.RestController):
|
|||||||
audit_dict = audit.as_dict()
|
audit_dict = audit.as_dict()
|
||||||
|
|
||||||
new_audit = objects.Audit(context, **audit_dict)
|
new_audit = objects.Audit(context, **audit_dict)
|
||||||
new_audit.create()
|
new_audit.create(context)
|
||||||
|
|
||||||
# Set the HTTP Location Header
|
# Set the HTTP Location Header
|
||||||
pecan.response.location = link.build_url('audits', new_audit.uuid)
|
pecan.response.location = link.build_url('audits', new_audit.uuid)
|
||||||
|
|
||||||
# trigger decision-engine to run the audit
|
# trigger decision-engine to run the audit
|
||||||
|
|
||||||
if new_audit.audit_type == objects.audit.AuditType.ONESHOT.value:
|
if new_audit.audit_type == objects.audit.AuditType.ONESHOT.value:
|
||||||
dc_client = rpcapi.DecisionEngineAPI()
|
dc_client = rpcapi.DecisionEngineAPI()
|
||||||
dc_client.trigger_audit(context, new_audit.uuid)
|
dc_client.trigger_audit(context, new_audit.uuid)
|
||||||
@@ -543,11 +555,14 @@ class AuditsController(rest.RestController):
|
|||||||
raise exception.OperationNotPermitted
|
raise exception.OperationNotPermitted
|
||||||
|
|
||||||
context = pecan.request.context
|
context = pecan.request.context
|
||||||
audit_to_update = api_utils.get_resource(
|
audit_to_update = api_utils.get_resource('Audit',
|
||||||
'Audit', audit_uuid, eager=True)
|
audit_uuid)
|
||||||
policy.enforce(context, 'audit:update', audit_to_update,
|
policy.enforce(context, 'audit:update', audit_to_update,
|
||||||
action='audit:update')
|
action='audit:update')
|
||||||
|
|
||||||
|
audit_to_update = objects.Audit.get_by_uuid(pecan.request.context,
|
||||||
|
audit_uuid)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
audit_dict = audit_to_update.as_dict()
|
audit_dict = audit_to_update.as_dict()
|
||||||
audit = Audit(**api_utils.apply_jsonpatch(audit_dict, patch))
|
audit = Audit(**api_utils.apply_jsonpatch(audit_dict, patch))
|
||||||
@@ -576,8 +591,7 @@ class AuditsController(rest.RestController):
|
|||||||
:param audit_uuid: UUID of a audit.
|
:param audit_uuid: UUID of a audit.
|
||||||
"""
|
"""
|
||||||
context = pecan.request.context
|
context = pecan.request.context
|
||||||
audit_to_delete = api_utils.get_resource(
|
audit_to_delete = api_utils.get_resource('Audit', audit_uuid)
|
||||||
'Audit', audit_uuid, eager=True)
|
|
||||||
policy.enforce(context, 'audit:update', audit_to_delete,
|
policy.enforce(context, 'audit:update', audit_to_delete,
|
||||||
action='audit:update')
|
action='audit:update')
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,11 @@ settings related to the level of automation for the
|
|||||||
A flag will indicate whether the :ref:`Action Plan <action_plan_definition>`
|
A flag will indicate whether the :ref:`Action Plan <action_plan_definition>`
|
||||||
will be launched automatically or will need a manual confirmation from the
|
will be launched automatically or will need a manual confirmation from the
|
||||||
:ref:`Administrator <administrator_definition>`.
|
:ref:`Administrator <administrator_definition>`.
|
||||||
|
|
||||||
|
Last but not least, an :ref:`Audit Template <audit_template_definition>` may
|
||||||
|
contain a list of extra parameters related to the
|
||||||
|
:ref:`Strategy <strategy_definition>` configuration. These parameters can be
|
||||||
|
provided as a list of key-value pairs.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
@@ -61,7 +66,6 @@ from watcher.common import context as context_utils
|
|||||||
from watcher.common import exception
|
from watcher.common import exception
|
||||||
from watcher.common import policy
|
from watcher.common import policy
|
||||||
from watcher.common import utils as common_utils
|
from watcher.common import utils as common_utils
|
||||||
from watcher.decision_engine.scope import default
|
|
||||||
from watcher import objects
|
from watcher import objects
|
||||||
|
|
||||||
|
|
||||||
@@ -74,24 +78,37 @@ class AuditTemplatePostType(wtypes.Base):
|
|||||||
description = wtypes.wsattr(wtypes.text, mandatory=False)
|
description = wtypes.wsattr(wtypes.text, mandatory=False)
|
||||||
"""Short description of this audit template"""
|
"""Short description of this audit template"""
|
||||||
|
|
||||||
|
deadline = wsme.wsattr(datetime.datetime, mandatory=False)
|
||||||
|
"""deadline of the audit template"""
|
||||||
|
|
||||||
|
host_aggregate = wsme.wsattr(wtypes.IntegerType(minimum=1),
|
||||||
|
mandatory=False)
|
||||||
|
"""ID of the Nova host aggregate targeted by the audit template"""
|
||||||
|
|
||||||
|
extra = wtypes.wsattr({wtypes.text: types.jsontype}, mandatory=False)
|
||||||
|
"""The metadata of the audit template"""
|
||||||
|
|
||||||
goal = wtypes.wsattr(wtypes.text, mandatory=True)
|
goal = wtypes.wsattr(wtypes.text, mandatory=True)
|
||||||
"""Goal UUID or name of the audit template"""
|
"""Goal UUID or name of the audit template"""
|
||||||
|
|
||||||
strategy = wtypes.wsattr(wtypes.text, mandatory=False)
|
strategy = wtypes.wsattr(wtypes.text, mandatory=False)
|
||||||
"""Strategy UUID or name of the audit template"""
|
"""Strategy UUID or name of the audit template"""
|
||||||
|
|
||||||
scope = wtypes.wsattr(types.jsontype, mandatory=False, default=[])
|
version = wtypes.text
|
||||||
"""Audit Scope"""
|
"""Internal version of the audit template"""
|
||||||
|
|
||||||
def as_audit_template(self):
|
def as_audit_template(self):
|
||||||
return AuditTemplate(
|
return AuditTemplate(
|
||||||
name=self.name,
|
name=self.name,
|
||||||
description=self.description,
|
description=self.description,
|
||||||
|
deadline=self.deadline,
|
||||||
|
host_aggregate=self.host_aggregate,
|
||||||
|
extra=self.extra,
|
||||||
goal_id=self.goal, # Dirty trick ...
|
goal_id=self.goal, # Dirty trick ...
|
||||||
goal=self.goal,
|
goal=self.goal,
|
||||||
strategy_id=self.strategy, # Dirty trick ...
|
strategy_id=self.strategy, # Dirty trick ...
|
||||||
strategy_uuid=self.strategy,
|
strategy_uuid=self.strategy,
|
||||||
scope=self.scope,
|
version=self.version,
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -106,9 +123,6 @@ class AuditTemplatePostType(wtypes.Base):
|
|||||||
else:
|
else:
|
||||||
raise exception.InvalidGoal(goal=audit_template.goal)
|
raise exception.InvalidGoal(goal=audit_template.goal)
|
||||||
|
|
||||||
common_utils.Draft4Validator(
|
|
||||||
default.DefaultScope.DEFAULT_SCHEMA).validate(audit_template.scope)
|
|
||||||
|
|
||||||
if audit_template.strategy:
|
if audit_template.strategy:
|
||||||
available_strategies = objects.Strategy.list(
|
available_strategies = objects.Strategy.list(
|
||||||
AuditTemplatePostType._ctx)
|
AuditTemplatePostType._ctx)
|
||||||
@@ -291,9 +305,18 @@ class AuditTemplate(base.APIBase):
|
|||||||
name = wtypes.text
|
name = wtypes.text
|
||||||
"""Name of this audit template"""
|
"""Name of this audit template"""
|
||||||
|
|
||||||
description = wtypes.wsattr(wtypes.text, mandatory=False)
|
description = wtypes.text
|
||||||
"""Short description of this audit template"""
|
"""Short description of this audit template"""
|
||||||
|
|
||||||
|
deadline = datetime.datetime
|
||||||
|
"""deadline of the audit template"""
|
||||||
|
|
||||||
|
host_aggregate = wtypes.IntegerType(minimum=1)
|
||||||
|
"""ID of the Nova host aggregate targeted by the audit template"""
|
||||||
|
|
||||||
|
extra = {wtypes.text: types.jsontype}
|
||||||
|
"""The metadata of the audit template"""
|
||||||
|
|
||||||
goal_uuid = wsme.wsproperty(
|
goal_uuid = wsme.wsproperty(
|
||||||
wtypes.text, _get_goal_uuid, _set_goal_uuid, mandatory=True)
|
wtypes.text, _get_goal_uuid, _set_goal_uuid, mandatory=True)
|
||||||
"""Goal UUID the audit template refers to"""
|
"""Goal UUID the audit template refers to"""
|
||||||
@@ -310,15 +333,15 @@ class AuditTemplate(base.APIBase):
|
|||||||
wtypes.text, _get_strategy_name, _set_strategy_name, mandatory=False)
|
wtypes.text, _get_strategy_name, _set_strategy_name, mandatory=False)
|
||||||
"""The name of the strategy this audit template refers to"""
|
"""The name of the strategy this audit template refers to"""
|
||||||
|
|
||||||
|
version = wtypes.text
|
||||||
|
"""Internal version of the audit template"""
|
||||||
|
|
||||||
audits = wsme.wsattr([link.Link], readonly=True)
|
audits = wsme.wsattr([link.Link], readonly=True)
|
||||||
"""Links to the collection of audits contained in this audit template"""
|
"""Links to the collection of audits contained in this audit template"""
|
||||||
|
|
||||||
links = wsme.wsattr([link.Link], readonly=True)
|
links = wsme.wsattr([link.Link], readonly=True)
|
||||||
"""A list containing a self link and associated audit template links"""
|
"""A list containing a self link and associated audit template links"""
|
||||||
|
|
||||||
scope = wsme.wsattr(types.jsontype, mandatory=False)
|
|
||||||
"""Audit Scope"""
|
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
super(AuditTemplate, self).__init__()
|
super(AuditTemplate, self).__init__()
|
||||||
self.fields = []
|
self.fields = []
|
||||||
@@ -351,8 +374,8 @@ class AuditTemplate(base.APIBase):
|
|||||||
def _convert_with_links(audit_template, url, expand=True):
|
def _convert_with_links(audit_template, url, expand=True):
|
||||||
if not expand:
|
if not expand:
|
||||||
audit_template.unset_fields_except(
|
audit_template.unset_fields_except(
|
||||||
['uuid', 'name', 'goal_uuid', 'goal_name',
|
['uuid', 'name', 'host_aggregate', 'goal_uuid', 'goal_name',
|
||||||
'scope', 'strategy_uuid', 'strategy_name'])
|
'strategy_uuid', 'strategy_name'])
|
||||||
|
|
||||||
# The numeric ID should not be exposed to
|
# The numeric ID should not be exposed to
|
||||||
# the user, it's internal only.
|
# the user, it's internal only.
|
||||||
@@ -379,12 +402,13 @@ class AuditTemplate(base.APIBase):
|
|||||||
sample = cls(uuid='27e3153e-d5bf-4b7e-b517-fb518e17f34c',
|
sample = cls(uuid='27e3153e-d5bf-4b7e-b517-fb518e17f34c',
|
||||||
name='My Audit Template',
|
name='My Audit Template',
|
||||||
description='Description of my audit template',
|
description='Description of my audit template',
|
||||||
|
host_aggregate=5,
|
||||||
goal_uuid='83e44733-b640-40e2-8d8a-7dd3be7134e6',
|
goal_uuid='83e44733-b640-40e2-8d8a-7dd3be7134e6',
|
||||||
strategy_uuid='367d826e-b6a4-4b70-bc44-c3f6fe1c9986',
|
strategy_uuid='367d826e-b6a4-4b70-bc44-c3f6fe1c9986',
|
||||||
|
extra={'automatic': True},
|
||||||
created_at=datetime.datetime.utcnow(),
|
created_at=datetime.datetime.utcnow(),
|
||||||
deleted_at=None,
|
deleted_at=None,
|
||||||
updated_at=datetime.datetime.utcnow(),
|
updated_at=datetime.datetime.utcnow())
|
||||||
scope=[],)
|
|
||||||
return cls._convert_with_links(sample, 'http://localhost:9322', expand)
|
return cls._convert_with_links(sample, 'http://localhost:9322', expand)
|
||||||
|
|
||||||
|
|
||||||
@@ -568,11 +592,11 @@ class AuditTemplatesController(rest.RestController):
|
|||||||
audit_template_dict = audit_template.as_dict()
|
audit_template_dict = audit_template.as_dict()
|
||||||
new_audit_template = objects.AuditTemplate(context,
|
new_audit_template = objects.AuditTemplate(context,
|
||||||
**audit_template_dict)
|
**audit_template_dict)
|
||||||
new_audit_template.create()
|
new_audit_template.create(context)
|
||||||
|
|
||||||
# Set the HTTP Location Header
|
# Set the HTTP Location Header
|
||||||
pecan.response.location = link.build_url(
|
pecan.response.location = link.build_url('audit_templates',
|
||||||
'audit_templates', new_audit_template.uuid)
|
new_audit_template.uuid)
|
||||||
return AuditTemplate.convert_with_links(new_audit_template)
|
return AuditTemplate.convert_with_links(new_audit_template)
|
||||||
|
|
||||||
@wsme.validate(types.uuid, [AuditTemplatePatchType])
|
@wsme.validate(types.uuid, [AuditTemplatePatchType])
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ An efficacy indicator is a single value that gives an indication on how the
|
|||||||
:ref:`solution <solution_definition>` produced by a given :ref:`strategy
|
:ref:`solution <solution_definition>` produced by a given :ref:`strategy
|
||||||
<strategy_definition>` performed. These efficacy indicators are specific to a
|
<strategy_definition>` performed. These efficacy indicators are specific to a
|
||||||
given :ref:`goal <goal_definition>` and are usually used to compute the
|
given :ref:`goal <goal_definition>` and are usually used to compute the
|
||||||
:ref:`global efficacy <efficacy_definition>` of the resulting :ref:`action plan
|
:ref:`gobal efficacy <efficacy_definition>` of the resulting :ref:`action plan
|
||||||
<action_plan_definition>`.
|
<action_plan_definition>`.
|
||||||
|
|
||||||
In Watcher, these efficacy indicators are specified alongside the goal they
|
In Watcher, these efficacy indicators are specified alongside the goal they
|
||||||
|
|||||||
@@ -1,263 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
# Copyright (c) 2016 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.
|
|
||||||
|
|
||||||
"""
|
|
||||||
Service mechanism provides ability to monitor Watcher services state.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import datetime
|
|
||||||
import six
|
|
||||||
|
|
||||||
from oslo_config import cfg
|
|
||||||
from oslo_log import log
|
|
||||||
from oslo_utils import timeutils
|
|
||||||
import pecan
|
|
||||||
from pecan import rest
|
|
||||||
import wsme
|
|
||||||
from wsme import types as wtypes
|
|
||||||
import wsmeext.pecan as wsme_pecan
|
|
||||||
|
|
||||||
from watcher._i18n import _LW
|
|
||||||
from watcher.api.controllers import base
|
|
||||||
from watcher.api.controllers import link
|
|
||||||
from watcher.api.controllers.v1 import collection
|
|
||||||
from watcher.api.controllers.v1 import utils as api_utils
|
|
||||||
from watcher.common import exception
|
|
||||||
from watcher.common import policy
|
|
||||||
from watcher import objects
|
|
||||||
|
|
||||||
|
|
||||||
CONF = cfg.CONF
|
|
||||||
LOG = log.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class Service(base.APIBase):
|
|
||||||
"""API representation of a service.
|
|
||||||
|
|
||||||
This class enforces type checking and value constraints, and converts
|
|
||||||
between the internal object model and the API representation of a service.
|
|
||||||
"""
|
|
||||||
|
|
||||||
_status = None
|
|
||||||
|
|
||||||
def _get_status(self):
|
|
||||||
return self._status
|
|
||||||
|
|
||||||
def _set_status(self, name):
|
|
||||||
service = objects.Service.get_by_name(pecan.request.context, name)
|
|
||||||
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(_LW('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)})
|
|
||||||
self._status = objects.service.ServiceStatus.FAILED
|
|
||||||
else:
|
|
||||||
self._status = objects.service.ServiceStatus.ACTIVE
|
|
||||||
|
|
||||||
id = wsme.wsattr(int, readonly=True)
|
|
||||||
"""ID for this service."""
|
|
||||||
|
|
||||||
name = wtypes.text
|
|
||||||
"""Name of the service."""
|
|
||||||
|
|
||||||
host = wtypes.text
|
|
||||||
"""Host where service is placed on."""
|
|
||||||
|
|
||||||
last_seen_up = wsme.wsattr(datetime.datetime, readonly=True)
|
|
||||||
"""Time when Watcher service sent latest heartbeat."""
|
|
||||||
|
|
||||||
status = wsme.wsproperty(wtypes.text, _get_status, _set_status,
|
|
||||||
mandatory=True)
|
|
||||||
|
|
||||||
links = wsme.wsattr([link.Link], readonly=True)
|
|
||||||
"""A list containing a self link."""
|
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
super(Service, self).__init__()
|
|
||||||
|
|
||||||
fields = list(objects.Service.fields.keys()) + ['status']
|
|
||||||
self.fields = []
|
|
||||||
for field in fields:
|
|
||||||
self.fields.append(field)
|
|
||||||
setattr(self, field, kwargs.get(
|
|
||||||
field if field != 'status' else 'name', wtypes.Unset))
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _convert_with_links(service, url, expand=True):
|
|
||||||
if not expand:
|
|
||||||
service.unset_fields_except(
|
|
||||||
['id', 'name', 'host', 'status'])
|
|
||||||
|
|
||||||
service.links = [
|
|
||||||
link.Link.make_link('self', url, 'services', str(service.id)),
|
|
||||||
link.Link.make_link('bookmark', url, 'services', str(service.id),
|
|
||||||
bookmark=True)]
|
|
||||||
return service
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def convert_with_links(cls, service, expand=True):
|
|
||||||
service = Service(**service.as_dict())
|
|
||||||
return cls._convert_with_links(
|
|
||||||
service, pecan.request.host_url, expand)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def sample(cls, expand=True):
|
|
||||||
sample = cls(id=1,
|
|
||||||
name='watcher-applier',
|
|
||||||
host='Controller',
|
|
||||||
last_seen_up=datetime.datetime(2016, 1, 1))
|
|
||||||
return cls._convert_with_links(sample, 'http://localhost:9322', expand)
|
|
||||||
|
|
||||||
|
|
||||||
class ServiceCollection(collection.Collection):
|
|
||||||
"""API representation of a collection of services."""
|
|
||||||
|
|
||||||
services = [Service]
|
|
||||||
"""A list containing services objects"""
|
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
super(ServiceCollection, self).__init__()
|
|
||||||
self._type = 'services'
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def convert_with_links(services, limit, url=None, expand=False,
|
|
||||||
**kwargs):
|
|
||||||
service_collection = ServiceCollection()
|
|
||||||
service_collection.services = [
|
|
||||||
Service.convert_with_links(g, expand) for g in services]
|
|
||||||
|
|
||||||
if 'sort_key' in kwargs:
|
|
||||||
reverse = False
|
|
||||||
if kwargs['sort_key'] == 'service':
|
|
||||||
if 'sort_dir' in kwargs:
|
|
||||||
reverse = True if kwargs['sort_dir'] == 'desc' else False
|
|
||||||
service_collection.services = sorted(
|
|
||||||
service_collection.services,
|
|
||||||
key=lambda service: service.id,
|
|
||||||
reverse=reverse)
|
|
||||||
|
|
||||||
service_collection.next = service_collection.get_next(
|
|
||||||
limit, url=url, marker_field='id', **kwargs)
|
|
||||||
return service_collection
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def sample(cls):
|
|
||||||
sample = cls()
|
|
||||||
sample.services = [Service.sample(expand=False)]
|
|
||||||
return sample
|
|
||||||
|
|
||||||
|
|
||||||
class ServicesController(rest.RestController):
|
|
||||||
"""REST controller for Services."""
|
|
||||||
def __init__(self):
|
|
||||||
super(ServicesController, self).__init__()
|
|
||||||
|
|
||||||
from_services = False
|
|
||||||
"""A flag to indicate if the requests to this controller are coming
|
|
||||||
from the top-level resource Services."""
|
|
||||||
|
|
||||||
_custom_actions = {
|
|
||||||
'detail': ['GET'],
|
|
||||||
}
|
|
||||||
|
|
||||||
def _get_services_collection(self, marker, limit, sort_key, sort_dir,
|
|
||||||
expand=False, resource_url=None):
|
|
||||||
limit = api_utils.validate_limit(limit)
|
|
||||||
api_utils.validate_sort_dir(sort_dir)
|
|
||||||
|
|
||||||
sort_db_key = (sort_key if sort_key in objects.Service.fields.keys()
|
|
||||||
else None)
|
|
||||||
|
|
||||||
marker_obj = None
|
|
||||||
if marker:
|
|
||||||
marker_obj = objects.Service.get(
|
|
||||||
pecan.request.context, marker)
|
|
||||||
|
|
||||||
services = objects.Service.list(
|
|
||||||
pecan.request.context, limit, marker_obj,
|
|
||||||
sort_key=sort_db_key, sort_dir=sort_dir)
|
|
||||||
|
|
||||||
return ServiceCollection.convert_with_links(
|
|
||||||
services, limit, url=resource_url, expand=expand,
|
|
||||||
sort_key=sort_key, sort_dir=sort_dir)
|
|
||||||
|
|
||||||
@wsme_pecan.wsexpose(ServiceCollection, int, int, wtypes.text, wtypes.text)
|
|
||||||
def get_all(self, marker=None, limit=None, sort_key='id', sort_dir='asc'):
|
|
||||||
"""Retrieve a list of services.
|
|
||||||
|
|
||||||
:param marker: pagination marker for large data sets.
|
|
||||||
:param limit: maximum number of resources to return in a single result.
|
|
||||||
:param sort_key: column to sort results by. Default: id.
|
|
||||||
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
|
|
||||||
"""
|
|
||||||
context = pecan.request.context
|
|
||||||
policy.enforce(context, 'service:get_all',
|
|
||||||
action='service:get_all')
|
|
||||||
|
|
||||||
return self._get_services_collection(marker, limit, sort_key, sort_dir)
|
|
||||||
|
|
||||||
@wsme_pecan.wsexpose(ServiceCollection, int, int, wtypes.text, wtypes.text)
|
|
||||||
def detail(self, marker=None, limit=None, sort_key='id', sort_dir='asc'):
|
|
||||||
"""Retrieve a list of services with detail.
|
|
||||||
|
|
||||||
:param marker: pagination marker for large data sets.
|
|
||||||
:param limit: maximum number of resources to return in a single result.
|
|
||||||
:param sort_key: column to sort results by. Default: id.
|
|
||||||
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
|
|
||||||
"""
|
|
||||||
context = pecan.request.context
|
|
||||||
policy.enforce(context, 'service:detail',
|
|
||||||
action='service:detail')
|
|
||||||
# NOTE(lucasagomes): /detail should only work agaist collections
|
|
||||||
parent = pecan.request.path.split('/')[:-1][-1]
|
|
||||||
if parent != "services":
|
|
||||||
raise exception.HTTPNotFound
|
|
||||||
expand = True
|
|
||||||
resource_url = '/'.join(['services', 'detail'])
|
|
||||||
|
|
||||||
return self._get_services_collection(
|
|
||||||
marker, limit, sort_key, sort_dir, expand, resource_url)
|
|
||||||
|
|
||||||
@wsme_pecan.wsexpose(Service, wtypes.text)
|
|
||||||
def get_one(self, service):
|
|
||||||
"""Retrieve information about the given service.
|
|
||||||
|
|
||||||
:param service: ID or name of the service.
|
|
||||||
"""
|
|
||||||
if self.from_services:
|
|
||||||
raise exception.OperationNotPermitted
|
|
||||||
|
|
||||||
context = pecan.request.context
|
|
||||||
rpc_service = api_utils.get_resource('Service', service)
|
|
||||||
policy.enforce(context, 'service:get', rpc_service,
|
|
||||||
action='service:get')
|
|
||||||
|
|
||||||
return Service.convert_with_links(rpc_service)
|
|
||||||
@@ -110,7 +110,7 @@ class Strategy(base.APIBase):
|
|||||||
"""The name of the goal this audit refers to"""
|
"""The name of the goal this audit refers to"""
|
||||||
|
|
||||||
parameters_spec = {wtypes.text: types.jsontype}
|
parameters_spec = {wtypes.text: types.jsontype}
|
||||||
"""Parameters spec dict"""
|
""" Parameters spec dict"""
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
super(Strategy, self).__init__()
|
super(Strategy, self).__init__()
|
||||||
|
|||||||
@@ -15,13 +15,11 @@
|
|||||||
|
|
||||||
import jsonpatch
|
import jsonpatch
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_utils import reflection
|
|
||||||
from oslo_utils import uuidutils
|
from oslo_utils import uuidutils
|
||||||
import pecan
|
import pecan
|
||||||
import wsme
|
import wsme
|
||||||
|
|
||||||
from watcher._i18n import _
|
from watcher._i18n import _
|
||||||
from watcher.common import utils
|
|
||||||
from watcher import objects
|
from watcher import objects
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
@@ -82,27 +80,17 @@ def as_filters_dict(**filters):
|
|||||||
return filters_dict
|
return filters_dict
|
||||||
|
|
||||||
|
|
||||||
def get_resource(resource, resource_id, eager=False):
|
def get_resource(resource, resource_ident):
|
||||||
"""Get the resource from the uuid, id or logical name.
|
"""Get the resource from the uuid or logical name.
|
||||||
|
|
||||||
:param resource: the resource type.
|
:param resource: the resource type.
|
||||||
:param resource_id: the UUID, ID or logical name of the resource.
|
:param resource_ident: the UUID or logical name of the resource.
|
||||||
|
|
||||||
:returns: The resource.
|
:returns: The resource.
|
||||||
"""
|
"""
|
||||||
resource = getattr(objects, resource)
|
resource = getattr(objects, resource)
|
||||||
|
|
||||||
_get = None
|
if uuidutils.is_uuid_like(resource_ident):
|
||||||
if utils.is_int_like(resource_id):
|
return resource.get_by_uuid(pecan.request.context, resource_ident)
|
||||||
resource_id = int(resource_id)
|
|
||||||
_get = resource.get
|
|
||||||
elif uuidutils.is_uuid_like(resource_id):
|
|
||||||
_get = resource.get_by_uuid
|
|
||||||
else:
|
|
||||||
_get = resource.get_by_name
|
|
||||||
|
|
||||||
method_signature = reflection.get_signature(_get)
|
return resource.get_by_name(pecan.request.context, resource_ident)
|
||||||
if 'eager' in method_signature.parameters:
|
|
||||||
return _get(pecan.request.context, resource_id, eager=eager)
|
|
||||||
|
|
||||||
return _get(pecan.request.context, resource_id)
|
|
||||||
|
|||||||
@@ -114,6 +114,6 @@ class NoExceptionTracebackHook(hooks.PecanHook):
|
|||||||
faultstring = faultstring.split(traceback_marker, 1)[0]
|
faultstring = faultstring.split(traceback_marker, 1)[0]
|
||||||
# Remove trailing newlines and spaces if any.
|
# Remove trailing newlines and spaces if any.
|
||||||
json_body['faultstring'] = faultstring.rstrip()
|
json_body['faultstring'] = faultstring.rstrip()
|
||||||
# Replace the whole json. Cannot change original one because it's
|
# Replace the whole json. Cannot change original one beacause it's
|
||||||
# generated on the fly.
|
# generated on the fly.
|
||||||
state.response.json = json_body
|
state.response.json = json_body
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class AuthTokenMiddleware(auth_token.AuthProtocol):
|
|||||||
for public routes in the API.
|
for public routes in the API.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def __init__(self, app, conf, public_api_routes=()):
|
def __init__(self, app, conf, public_api_routes=[]):
|
||||||
route_pattern_tpl = '%s(\.json|\.xml)?$'
|
route_pattern_tpl = '%s(\.json|\.xml)?$'
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -20,7 +20,9 @@ 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 import objects
|
from watcher.applier.messaging import event_types
|
||||||
|
from watcher.common.messaging.events import event
|
||||||
|
from watcher.objects import action_plan as ap_objects
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
@@ -32,20 +34,32 @@ class DefaultActionPlanHandler(base.BaseActionPlanHandler):
|
|||||||
self.service = service
|
self.service = service
|
||||||
self.action_plan_uuid = action_plan_uuid
|
self.action_plan_uuid = action_plan_uuid
|
||||||
|
|
||||||
def update_action_plan(self, uuid, state):
|
def notify(self, uuid, event_type, state):
|
||||||
action_plan = objects.ActionPlan.get_by_uuid(self.ctx, uuid)
|
action_plan = ap_objects.ActionPlan.get_by_uuid(self.ctx, uuid)
|
||||||
action_plan.state = state
|
action_plan.state = state
|
||||||
action_plan.save()
|
action_plan.save()
|
||||||
|
ev = event.Event()
|
||||||
|
ev.type = event_type
|
||||||
|
ev.data = {}
|
||||||
|
payload = {'action_plan__uuid': uuid,
|
||||||
|
'action_plan_state': state}
|
||||||
|
self.service.publish_status_event(ev.type.name, payload)
|
||||||
|
|
||||||
def execute(self):
|
def execute(self):
|
||||||
try:
|
try:
|
||||||
self.update_action_plan(self.action_plan_uuid,
|
# update state
|
||||||
objects.action_plan.State.ONGOING)
|
self.notify(self.action_plan_uuid,
|
||||||
|
event_types.EventTypes.LAUNCH_ACTION_PLAN,
|
||||||
|
ap_objects.State.ONGOING)
|
||||||
applier = default.DefaultApplier(self.ctx, self.service)
|
applier = default.DefaultApplier(self.ctx, self.service)
|
||||||
applier.execute(self.action_plan_uuid)
|
applier.execute(self.action_plan_uuid)
|
||||||
state = objects.action_plan.State.SUCCEEDED
|
state = ap_objects.State.SUCCEEDED
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.exception(e)
|
LOG.exception(e)
|
||||||
state = objects.action_plan.State.FAILED
|
state = ap_objects.State.FAILED
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
self.update_action_plan(self.action_plan_uuid, state)
|
# update state
|
||||||
|
self.notify(self.action_plan_uuid,
|
||||||
|
event_types.EventTypes.LAUNCH_ACTION_PLAN,
|
||||||
|
state)
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
# -*- encoding: utf-8 -*-
|
||||||
# Copyright (c) 2015 b<>com
|
# Copyright (c) 2015 b<>com
|
||||||
# Copyright (c) 2016 Intel Corp
|
|
||||||
#
|
#
|
||||||
# Authors: Jean-Emile DARTOIS <jean-emile.dartois@b-com.com>
|
# Authors: Jean-Emile DARTOIS <jean-emile.dartois@b-com.com>
|
||||||
#
|
#
|
||||||
@@ -18,40 +17,60 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
from watcher.applier.messaging import trigger
|
from watcher.applier.messaging import trigger
|
||||||
from watcher.common import service_manager
|
|
||||||
|
|
||||||
from watcher import conf
|
CONF = cfg.CONF
|
||||||
|
|
||||||
CONF = conf.CONF
|
|
||||||
|
|
||||||
|
|
||||||
class ApplierManager(service_manager.ServiceManager):
|
# Register options
|
||||||
|
APPLIER_MANAGER_OPTS = [
|
||||||
|
cfg.IntOpt('workers',
|
||||||
|
default='1',
|
||||||
|
min=1,
|
||||||
|
required=True,
|
||||||
|
help='Number of workers for applier, default value is 1.'),
|
||||||
|
cfg.StrOpt('conductor_topic',
|
||||||
|
default='watcher.applier.control',
|
||||||
|
help='The topic name used for'
|
||||||
|
'control events, this topic '
|
||||||
|
'used for rpc call '),
|
||||||
|
cfg.StrOpt('status_topic',
|
||||||
|
default='watcher.applier.status',
|
||||||
|
help='The topic name used for '
|
||||||
|
'status events, this topic '
|
||||||
|
'is used so as to notify'
|
||||||
|
'the others components '
|
||||||
|
'of the system'),
|
||||||
|
cfg.StrOpt('publisher_id',
|
||||||
|
default='watcher.applier.api',
|
||||||
|
help='The identifier used by watcher '
|
||||||
|
'module on the message broker'),
|
||||||
|
cfg.StrOpt('workflow_engine',
|
||||||
|
default='taskflow',
|
||||||
|
required=True,
|
||||||
|
help='Select the engine to use to execute the workflow')
|
||||||
|
]
|
||||||
|
|
||||||
@property
|
opt_group = cfg.OptGroup(name='watcher_applier',
|
||||||
def service_name(self):
|
title='Options for the Applier messaging'
|
||||||
return 'watcher-applier'
|
'core')
|
||||||
|
CONF.register_group(opt_group)
|
||||||
|
CONF.register_opts(APPLIER_MANAGER_OPTS, opt_group)
|
||||||
|
|
||||||
@property
|
|
||||||
def api_version(self):
|
|
||||||
return '1.0'
|
|
||||||
|
|
||||||
@property
|
class ApplierManager(object):
|
||||||
def publisher_id(self):
|
|
||||||
return CONF.watcher_applier.publisher_id
|
|
||||||
|
|
||||||
@property
|
API_VERSION = '1.0'
|
||||||
def conductor_topic(self):
|
|
||||||
return CONF.watcher_applier.conductor_topic
|
|
||||||
|
|
||||||
@property
|
conductor_endpoints = [trigger.TriggerActionPlan]
|
||||||
def notification_topics(self):
|
status_endpoints = []
|
||||||
return []
|
notification_endpoints = []
|
||||||
|
notification_topics = []
|
||||||
|
|
||||||
@property
|
def __init__(self):
|
||||||
def conductor_endpoints(self):
|
self.publisher_id = CONF.watcher_applier.publisher_id
|
||||||
return [trigger.TriggerActionPlan]
|
self.conductor_topic = CONF.watcher_applier.conductor_topic
|
||||||
|
self.status_topic = CONF.watcher_applier.status_topic
|
||||||
@property
|
self.api_version = self.API_VERSION
|
||||||
def notification_endpoints(self):
|
|
||||||
return []
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
# -*- encoding: utf-8 -*-
|
||||||
# Copyright (c) 2016 Intel Corp
|
# Copyright (c) 2015 b<>com
|
||||||
#
|
#
|
||||||
# Authors: Prudhvi Rao Shedimbi <prudhvi.rao.shedimbi@intel.com>
|
# Authors: Jean-Emile DARTOIS <jean-emile.dartois@b-com.com>
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@@ -15,19 +15,11 @@
|
|||||||
# implied.
|
# implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
from oslo_config import cfg
|
import enum
|
||||||
|
|
||||||
EXC_LOG_OPTS = [
|
|
||||||
cfg.BoolOpt('fatal_exception_format_errors',
|
|
||||||
default=False,
|
|
||||||
help='Make exception message format errors fatal.'),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def register_opts(conf):
|
class EventTypes(enum.Enum):
|
||||||
conf.register_opts(EXC_LOG_OPTS)
|
LAUNCH_ACTION_PLAN = "launch_action_plan"
|
||||||
|
LAUNCH_ACTION = "launch_action"
|
||||||
|
|
||||||
def list_opts():
|
|
||||||
return [('DEFAULT', EXC_LOG_OPTS)]
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
# -*- encoding: utf-8 -*-
|
||||||
# Copyright (c) 2015 b<>com
|
# Copyright (c) 2015 b<>com
|
||||||
# Copyright (c) 2016 Intel Corp
|
|
||||||
#
|
#
|
||||||
# Authors: Jean-Emile DARTOIS <jean-emile.dartois@b-com.com>
|
# Authors: Jean-Emile DARTOIS <jean-emile.dartois@b-com.com>
|
||||||
#
|
#
|
||||||
@@ -16,15 +15,18 @@
|
|||||||
# implied.
|
# implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
from watcher.applier import manager
|
||||||
from watcher.common import exception
|
from watcher.common import exception
|
||||||
from watcher.common import service
|
from watcher.common import service
|
||||||
from watcher.common import service_manager
|
|
||||||
from watcher.common import utils
|
from watcher.common import utils
|
||||||
|
|
||||||
from watcher import conf
|
|
||||||
|
|
||||||
CONF = conf.CONF
|
CONF = cfg.CONF
|
||||||
|
CONF.register_group(manager.opt_group)
|
||||||
|
CONF.register_opts(manager.APPLIER_MANAGER_OPTS, manager.opt_group)
|
||||||
|
|
||||||
|
|
||||||
class ApplierAPI(service.Service):
|
class ApplierAPI(service.Service):
|
||||||
@@ -37,35 +39,21 @@ class ApplierAPI(service.Service):
|
|||||||
raise exception.InvalidUuidOrName(name=action_plan_uuid)
|
raise exception.InvalidUuidOrName(name=action_plan_uuid)
|
||||||
|
|
||||||
return self.conductor_client.call(
|
return self.conductor_client.call(
|
||||||
context, 'launch_action_plan', action_plan_uuid=action_plan_uuid)
|
context.to_dict(), 'launch_action_plan',
|
||||||
|
action_plan_uuid=action_plan_uuid)
|
||||||
|
|
||||||
|
|
||||||
class ApplierAPIManager(service_manager.ServiceManager):
|
class ApplierAPIManager(object):
|
||||||
|
|
||||||
@property
|
API_VERSION = '1.0'
|
||||||
def service_name(self):
|
|
||||||
return None
|
|
||||||
|
|
||||||
@property
|
conductor_endpoints = []
|
||||||
def api_version(self):
|
status_endpoints = []
|
||||||
return '1.0'
|
notification_endpoints = []
|
||||||
|
notification_topics = []
|
||||||
|
|
||||||
@property
|
def __init__(self):
|
||||||
def publisher_id(self):
|
self.publisher_id = CONF.watcher_applier.publisher_id
|
||||||
return CONF.watcher_applier.publisher_id
|
self.conductor_topic = CONF.watcher_applier.conductor_topic
|
||||||
|
self.status_topic = CONF.watcher_applier.status_topic
|
||||||
@property
|
self.api_version = self.API_VERSION
|
||||||
def conductor_topic(self):
|
|
||||||
return CONF.watcher_applier.conductor_topic
|
|
||||||
|
|
||||||
@property
|
|
||||||
def notification_topics(self):
|
|
||||||
return []
|
|
||||||
|
|
||||||
@property
|
|
||||||
def conductor_endpoints(self):
|
|
||||||
return []
|
|
||||||
|
|
||||||
@property
|
|
||||||
def notification_endpoints(self):
|
|
||||||
return []
|
|
||||||
|
|||||||
@@ -21,8 +21,10 @@ import abc
|
|||||||
import six
|
import six
|
||||||
|
|
||||||
from watcher.applier.actions import factory
|
from watcher.applier.actions import factory
|
||||||
|
from watcher.applier.messaging import event_types
|
||||||
from watcher.common import clients
|
from watcher.common import clients
|
||||||
from watcher.common.loader import loadable
|
from watcher.common.loader import loadable
|
||||||
|
from watcher.common.messaging.events import event
|
||||||
from watcher import objects
|
from watcher import objects
|
||||||
|
|
||||||
|
|
||||||
@@ -75,7 +77,12 @@ class BaseWorkFlowEngine(loadable.Loadable):
|
|||||||
db_action = objects.Action.get_by_uuid(self.context, action.uuid)
|
db_action = objects.Action.get_by_uuid(self.context, action.uuid)
|
||||||
db_action.state = state
|
db_action.state = state
|
||||||
db_action.save()
|
db_action.save()
|
||||||
# NOTE(v-francoise): Implement notifications for action
|
ev = event.Event()
|
||||||
|
ev.type = event_types.EventTypes.LAUNCH_ACTION
|
||||||
|
ev.data = {}
|
||||||
|
payload = {'action_uuid': action.uuid,
|
||||||
|
'action_state': state}
|
||||||
|
self.applier_manager.publish_status_event(ev.type.name, payload)
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def execute(self, actions):
|
def execute(self, actions):
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ from taskflow import task
|
|||||||
from watcher._i18n import _LE, _LW, _LC
|
from watcher._i18n import _LE, _LW, _LC
|
||||||
from watcher.applier.workflow_engine import base
|
from watcher.applier.workflow_engine import base
|
||||||
from watcher.common import exception
|
from watcher.common import exception
|
||||||
from watcher import objects
|
from watcher.objects import action as obj_action
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
@@ -107,12 +107,14 @@ class TaskFlowActionContainer(task.Task):
|
|||||||
|
|
||||||
def pre_execute(self):
|
def pre_execute(self):
|
||||||
try:
|
try:
|
||||||
self.engine.notify(self._db_action, objects.action.State.ONGOING)
|
self.engine.notify(self._db_action,
|
||||||
|
obj_action.State.ONGOING)
|
||||||
LOG.debug("Pre-condition action: %s", self.name)
|
LOG.debug("Pre-condition action: %s", self.name)
|
||||||
self.action.pre_condition()
|
self.action.pre_condition()
|
||||||
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,
|
||||||
|
obj_action.State.FAILED)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def execute(self, *args, **kwargs):
|
def execute(self, *args, **kwargs):
|
||||||
@@ -120,13 +122,15 @@ class TaskFlowActionContainer(task.Task):
|
|||||||
LOG.debug("Running action: %s", self.name)
|
LOG.debug("Running action: %s", self.name)
|
||||||
|
|
||||||
self.action.execute()
|
self.action.execute()
|
||||||
self.engine.notify(self._db_action, objects.action.State.SUCCEEDED)
|
self.engine.notify(self._db_action,
|
||||||
|
obj_action.State.SUCCEEDED)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.exception(e)
|
LOG.exception(e)
|
||||||
LOG.error(_LE('The workflow engine has failed '
|
LOG.error(_LE('The workflow engine has failed '
|
||||||
'to execute the action: %s'), self.name)
|
'to execute the action: %s'), self.name)
|
||||||
|
|
||||||
self.engine.notify(self._db_action, objects.action.State.FAILED)
|
self.engine.notify(self._db_action,
|
||||||
|
obj_action.State.FAILED)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def post_execute(self):
|
def post_execute(self):
|
||||||
@@ -135,7 +139,8 @@ class TaskFlowActionContainer(task.Task):
|
|||||||
self.action.post_condition()
|
self.action.post_condition()
|
||||||
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,
|
||||||
|
obj_action.State.FAILED)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def revert(self, *args, **kwargs):
|
def revert(self, *args, **kwargs):
|
||||||
|
|||||||
@@ -24,19 +24,19 @@ from oslo_log import log as logging
|
|||||||
|
|
||||||
from watcher._i18n import _LI
|
from watcher._i18n import _LI
|
||||||
from watcher.common import service
|
from watcher.common import service
|
||||||
from watcher import conf
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
CONF = conf.CONF
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
service.prepare_service(sys.argv, CONF)
|
service.prepare_service(sys.argv)
|
||||||
|
|
||||||
host, port = cfg.CONF.api.host, cfg.CONF.api.port
|
host, port = cfg.CONF.api.host, cfg.CONF.api.port
|
||||||
protocol = "http" if not CONF.api.enable_ssl_api else "https"
|
protocol = "http" if not CONF.api.enable_ssl_api else "https"
|
||||||
# Build and start the WSGI app
|
# Build and start the WSGI app
|
||||||
server = service.WSGIService('watcher-api', CONF.api.enable_ssl_api)
|
server = service.WSGIService(
|
||||||
|
'watcher-api', CONF.api.enable_ssl_api)
|
||||||
|
|
||||||
if host == '127.0.0.1':
|
if host == '127.0.0.1':
|
||||||
LOG.info(_LI('serving on 127.0.0.1:%(port)s, '
|
LOG.info(_LI('serving on 127.0.0.1:%(port)s, '
|
||||||
|
|||||||
@@ -20,19 +20,19 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
|
||||||
from watcher._i18n import _LI
|
from watcher._i18n import _LI
|
||||||
from watcher.applier import manager
|
from watcher.applier import manager
|
||||||
from watcher.common import service as watcher_service
|
from watcher.common import service as watcher_service
|
||||||
from watcher import conf
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
CONF = conf.CONF
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
watcher_service.prepare_service(sys.argv, CONF)
|
watcher_service.prepare_service(sys.argv)
|
||||||
|
|
||||||
LOG.info(_LI('Starting Watcher Applier service in PID %s'), os.getpid())
|
LOG.info(_LI('Starting Watcher Applier service in PID %s'), os.getpid())
|
||||||
|
|
||||||
|
|||||||
@@ -24,11 +24,10 @@ import sys
|
|||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
|
||||||
from watcher.common import service
|
from watcher.common import service
|
||||||
from watcher import conf
|
|
||||||
from watcher.db import migration
|
from watcher.db import migration
|
||||||
from watcher.db import purge
|
from watcher.db import purge
|
||||||
|
|
||||||
CONF = conf.CONF
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
class DBCommand(object):
|
class DBCommand(object):
|
||||||
@@ -153,5 +152,5 @@ def main():
|
|||||||
if not set(sys.argv).intersection(valid_commands):
|
if not set(sys.argv).intersection(valid_commands):
|
||||||
sys.argv.append('upgrade')
|
sys.argv.append('upgrade')
|
||||||
|
|
||||||
service.prepare_service(sys.argv, CONF)
|
service.prepare_service(sys.argv)
|
||||||
CONF.command.func()
|
CONF.command.func()
|
||||||
|
|||||||
@@ -20,22 +20,22 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
|
||||||
from watcher._i18n import _LI
|
from watcher._i18n import _LI
|
||||||
from watcher.common import service as watcher_service
|
from watcher.common import service as watcher_service
|
||||||
from watcher import conf
|
|
||||||
from watcher.decision_engine import gmr
|
from watcher.decision_engine import gmr
|
||||||
from watcher.decision_engine import manager
|
from watcher.decision_engine import manager
|
||||||
from watcher.decision_engine import scheduling
|
from watcher.decision_engine import scheduling
|
||||||
from watcher.decision_engine import sync
|
from watcher.decision_engine import sync
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
CONF = conf.CONF
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
watcher_service.prepare_service(sys.argv, CONF)
|
watcher_service.prepare_service(sys.argv)
|
||||||
gmr.register_gmr_plugins()
|
gmr.register_gmr_plugins()
|
||||||
|
|
||||||
LOG.info(_LI('Starting Watcher Decision Engine service in PID %s'),
|
LOG.info(_LI('Starting Watcher Decision Engine service in PID %s'),
|
||||||
|
|||||||
@@ -24,17 +24,15 @@ from oslo_log import log as logging
|
|||||||
|
|
||||||
from watcher._i18n import _LI
|
from watcher._i18n import _LI
|
||||||
from watcher.common import service as service
|
from watcher.common import service as service
|
||||||
from watcher import conf
|
|
||||||
from watcher.decision_engine import sync
|
from watcher.decision_engine import sync
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
CONF = conf.CONF
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
LOG.info(_LI('Watcher sync started.'))
|
LOG.info(_LI('Watcher sync started.'))
|
||||||
|
|
||||||
service.prepare_service(sys.argv, CONF)
|
service.prepare_service(sys.argv)
|
||||||
syncer = sync.Syncer()
|
syncer = sync.Syncer()
|
||||||
syncer.sync()
|
syncer.sync()
|
||||||
|
|
||||||
|
|||||||
@@ -134,24 +134,27 @@ class CeilometerHelper(object):
|
|||||||
aggregate='avg'):
|
aggregate='avg'):
|
||||||
"""Representing a statistic aggregate by operators
|
"""Representing a statistic aggregate by operators
|
||||||
|
|
||||||
:param resource_id: id of resource to list statistics for.
|
:param resource_id: id
|
||||||
:param meter_name: Name of meter to list statistics for.
|
:param meter_name: meter names of which we want the statistics
|
||||||
:param period: Period in seconds over which to group samples.
|
:param period: `period`: In seconds. If no period is given, only one
|
||||||
:param aggregate: Available aggregates are: count, cardinality,
|
aggregate statistic is returned. If given, a faceted
|
||||||
min, max, sum, stddev, avg. Defaults to avg.
|
result will be returned, divided into given periods.
|
||||||
:return: Return the latest statistical data, None if no data.
|
Periods with no data are ignored.
|
||||||
|
:param aggregate:
|
||||||
|
:return:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
end_time = datetime.datetime.utcnow()
|
start_time = (datetime.datetime.utcnow() -
|
||||||
start_time = end_time - datetime.timedelta(seconds=int(period))
|
datetime.timedelta(seconds=int(period)))
|
||||||
query = self.build_query(
|
query = self.build_query(
|
||||||
resource_id=resource_id, start_time=start_time, end_time=end_time)
|
resource_id=resource_id, start_time=start_time)
|
||||||
statistic = self.query_retry(f=self.ceilometer.statistics.list,
|
statistic = self.query_retry(f=self.ceilometer.statistics.list,
|
||||||
meter_name=meter_name,
|
meter_name=meter_name,
|
||||||
q=query,
|
q=query,
|
||||||
period=period,
|
period=period,
|
||||||
aggregates=[
|
aggregates=[
|
||||||
{'func': aggregate}])
|
{'func': aggregate}],
|
||||||
|
groupby=['resource_id'])
|
||||||
|
|
||||||
item_value = None
|
item_value = None
|
||||||
if statistic:
|
if statistic:
|
||||||
|
|||||||
@@ -19,14 +19,47 @@ from neutronclient.neutron import client as netclient
|
|||||||
from novaclient import client as nvclient
|
from novaclient import client as nvclient
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
from watcher._i18n import _
|
||||||
from watcher.common import exception
|
from watcher.common import exception
|
||||||
|
|
||||||
from watcher import conf
|
|
||||||
|
|
||||||
CONF = conf.CONF
|
NOVA_CLIENT_OPTS = [
|
||||||
|
cfg.StrOpt('api_version',
|
||||||
|
default='2',
|
||||||
|
help=_('Version of Nova API to use in novaclient.'))]
|
||||||
|
|
||||||
|
GLANCE_CLIENT_OPTS = [
|
||||||
|
cfg.StrOpt('api_version',
|
||||||
|
default='2',
|
||||||
|
help=_('Version of Glance API to use in glanceclient.'))]
|
||||||
|
|
||||||
|
CINDER_CLIENT_OPTS = [
|
||||||
|
cfg.StrOpt('api_version',
|
||||||
|
default='2',
|
||||||
|
help=_('Version of Cinder API to use in cinderclient.'))]
|
||||||
|
|
||||||
|
CEILOMETER_CLIENT_OPTS = [
|
||||||
|
cfg.StrOpt('api_version',
|
||||||
|
default='2',
|
||||||
|
help=_('Version of Ceilometer API to use in '
|
||||||
|
'ceilometerclient.'))]
|
||||||
|
|
||||||
|
NEUTRON_CLIENT_OPTS = [
|
||||||
|
cfg.StrOpt('api_version',
|
||||||
|
default='2.0',
|
||||||
|
help=_('Version of Neutron API to use in neutronclient.'))]
|
||||||
|
|
||||||
|
cfg.CONF.register_opts(NOVA_CLIENT_OPTS, group='nova_client')
|
||||||
|
cfg.CONF.register_opts(GLANCE_CLIENT_OPTS, group='glance_client')
|
||||||
|
cfg.CONF.register_opts(CINDER_CLIENT_OPTS, group='cinder_client')
|
||||||
|
cfg.CONF.register_opts(CEILOMETER_CLIENT_OPTS, group='ceilometer_client')
|
||||||
|
cfg.CONF.register_opts(NEUTRON_CLIENT_OPTS, group='neutron_client')
|
||||||
|
|
||||||
_CLIENTS_AUTH_GROUP = 'watcher_clients_auth'
|
_CLIENTS_AUTH_GROUP = 'watcher_clients_auth'
|
||||||
|
|
||||||
|
ka_loading.register_auth_conf_options(cfg.CONF, _CLIENTS_AUTH_GROUP)
|
||||||
|
ka_loading.register_session_conf_options(cfg.CONF, _CLIENTS_AUTH_GROUP)
|
||||||
|
|
||||||
|
|
||||||
class OpenStackClients(object):
|
class OpenStackClients(object):
|
||||||
"""Convenience class to create and cache client instances."""
|
"""Convenience class to create and cache client instances."""
|
||||||
|
|||||||
@@ -26,16 +26,22 @@ import functools
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
from keystoneclient import exceptions as keystone_exceptions
|
from keystoneclient import exceptions as keystone_exceptions
|
||||||
|
from oslo_config import cfg
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from watcher._i18n import _, _LE
|
from watcher._i18n import _, _LE
|
||||||
|
|
||||||
from watcher import conf
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
CONF = conf.CONF
|
exc_log_opts = [
|
||||||
|
cfg.BoolOpt('fatal_exception_format_errors',
|
||||||
|
default=False,
|
||||||
|
help='Make exception message format errors fatal.'),
|
||||||
|
]
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
CONF.register_opts(exc_log_opts)
|
||||||
|
|
||||||
|
|
||||||
def wrap_keystone_exception(func):
|
def wrap_keystone_exception(func):
|
||||||
@@ -113,10 +119,6 @@ class WatcherException(Exception):
|
|||||||
return six.text_type(self)
|
return six.text_type(self)
|
||||||
|
|
||||||
|
|
||||||
class UnsupportedError(WatcherException):
|
|
||||||
msg_fmt = _("Not supported")
|
|
||||||
|
|
||||||
|
|
||||||
class NotAuthorized(WatcherException):
|
class NotAuthorized(WatcherException):
|
||||||
msg_fmt = _("Not authorized")
|
msg_fmt = _("Not authorized")
|
||||||
code = 403
|
code = 403
|
||||||
@@ -166,14 +168,6 @@ class InvalidStrategy(Invalid):
|
|||||||
msg_fmt = _("Strategy %(strategy)s is invalid")
|
msg_fmt = _("Strategy %(strategy)s is invalid")
|
||||||
|
|
||||||
|
|
||||||
class InvalidAudit(Invalid):
|
|
||||||
msg_fmt = _("Audit %(audit)s is invalid")
|
|
||||||
|
|
||||||
|
|
||||||
class EagerlyLoadedAuditRequired(InvalidAudit):
|
|
||||||
msg_fmt = _("Audit %(audit)s was not eagerly loaded")
|
|
||||||
|
|
||||||
|
|
||||||
class InvalidUUID(Invalid):
|
class InvalidUUID(Invalid):
|
||||||
msg_fmt = _("Expected a uuid but received %(uuid)s")
|
msg_fmt = _("Expected a uuid but received %(uuid)s")
|
||||||
|
|
||||||
@@ -334,10 +328,6 @@ class MetricCollectorNotDefined(WatcherException):
|
|||||||
msg_fmt = _("The metrics resource collector is not defined")
|
msg_fmt = _("The metrics resource collector is not defined")
|
||||||
|
|
||||||
|
|
||||||
class ClusterStateStale(WatcherException):
|
|
||||||
msg_fmt = _("The cluster state is stale")
|
|
||||||
|
|
||||||
|
|
||||||
class ClusterDataModelCollectionError(WatcherException):
|
class ClusterDataModelCollectionError(WatcherException):
|
||||||
msg_fmt = _("The cluster data model '%(cdm)s' could not be built")
|
msg_fmt = _("The cluster data model '%(cdm)s' could not be built")
|
||||||
|
|
||||||
@@ -372,19 +362,6 @@ class NoSuchMetricForHost(WatcherException):
|
|||||||
msg_fmt = _("No %(metric)s metric for %(host)s found.")
|
msg_fmt = _("No %(metric)s metric for %(host)s found.")
|
||||||
|
|
||||||
|
|
||||||
class ServiceAlreadyExists(Conflict):
|
|
||||||
msg_fmt = _("A service with name %(name)s is already working on %(host)s.")
|
|
||||||
|
|
||||||
|
|
||||||
class ServiceNotFound(ResourceNotFound):
|
|
||||||
msg_fmt = _("The service %(service)s cannot be found.")
|
|
||||||
|
|
||||||
|
|
||||||
class WildcardCharacterIsUsed(WatcherException):
|
|
||||||
msg_fmt = _("You shouldn't use any other IDs of %(resource)s if you use "
|
|
||||||
"wildcard character.")
|
|
||||||
|
|
||||||
|
|
||||||
# Model
|
# Model
|
||||||
|
|
||||||
class InstanceNotFound(WatcherException):
|
class InstanceNotFound(WatcherException):
|
||||||
@@ -409,8 +386,3 @@ class NotSoftDeletedStateError(WatcherException):
|
|||||||
|
|
||||||
class NegativeLimitError(WatcherException):
|
class NegativeLimitError(WatcherException):
|
||||||
msg_fmt = _("Limit should be positive")
|
msg_fmt = _("Limit should be positive")
|
||||||
|
|
||||||
|
|
||||||
class NotificationPayloadError(WatcherException):
|
|
||||||
_msg_fmt = _("Payload not populated when trying to send notification "
|
|
||||||
"\"%(class_name)s\"")
|
|
||||||
|
|||||||
54
watcher/common/messaging/events/event.py
Normal file
54
watcher/common/messaging/events/event.py
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
# Copyright (c) 2015 b<>com
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
# implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
|
||||||
|
class Event(object):
|
||||||
|
"""Generic event to use with EventDispatcher"""
|
||||||
|
|
||||||
|
def __init__(self, event_type=None, data=None, request_id=None):
|
||||||
|
"""Default constructor
|
||||||
|
|
||||||
|
:param event_type: the type of the event
|
||||||
|
:param data: a dictionary which contains data
|
||||||
|
:param request_id: a string which represent the uuid of the request
|
||||||
|
"""
|
||||||
|
self._type = event_type
|
||||||
|
self._data = data
|
||||||
|
self._request_id = request_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def type(self):
|
||||||
|
return self._type
|
||||||
|
|
||||||
|
@type.setter
|
||||||
|
def type(self, type):
|
||||||
|
self._type = type
|
||||||
|
|
||||||
|
@property
|
||||||
|
def data(self):
|
||||||
|
return self._data
|
||||||
|
|
||||||
|
@data.setter
|
||||||
|
def data(self, data):
|
||||||
|
self._data = data
|
||||||
|
|
||||||
|
@property
|
||||||
|
def request_id(self):
|
||||||
|
return self._request_id
|
||||||
|
|
||||||
|
@request_id.setter
|
||||||
|
def request_id(self, id):
|
||||||
|
self._request_id = id
|
||||||
78
watcher/common/messaging/events/event_dispatcher.py
Normal file
78
watcher/common/messaging/events/event_dispatcher.py
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
# Copyright (c) 2015 b<>com
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
# implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
from oslo_log import log
|
||||||
|
|
||||||
|
from watcher.decision_engine.messaging import events as messaging_events
|
||||||
|
|
||||||
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class EventDispatcher(object):
|
||||||
|
"""Generic event dispatcher which listen and dispatch events"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._events = dict()
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
self._events = None
|
||||||
|
|
||||||
|
def has_listener(self, event_type, listener):
|
||||||
|
"""Return true if listener is register to event_type """
|
||||||
|
# Check for event type and for the listener
|
||||||
|
if event_type in self._events.keys():
|
||||||
|
return listener in self._events[event_type]
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def dispatch_event(self, event):
|
||||||
|
LOG.debug("dispatch evt : %s" % str(event.type))
|
||||||
|
"""
|
||||||
|
Dispatch an instance of Event class
|
||||||
|
"""
|
||||||
|
if messaging_events.Events.ALL in self._events.keys():
|
||||||
|
listeners = self._events[messaging_events.Events.ALL]
|
||||||
|
for listener in listeners:
|
||||||
|
listener(event)
|
||||||
|
|
||||||
|
# Dispatch the event to all the associated listeners
|
||||||
|
if event.type in self._events.keys():
|
||||||
|
listeners = self._events[event.type]
|
||||||
|
for listener in listeners:
|
||||||
|
listener(event)
|
||||||
|
|
||||||
|
def add_event_listener(self, event_type, listener):
|
||||||
|
"""Add an event listener for an event type"""
|
||||||
|
# Add listener to the event type
|
||||||
|
if not self.has_listener(event_type, listener):
|
||||||
|
listeners = self._events.get(event_type, [])
|
||||||
|
listeners.append(listener)
|
||||||
|
self._events[event_type] = listeners
|
||||||
|
|
||||||
|
def remove_event_listener(self, event_type, listener):
|
||||||
|
"""Remove event listener. """
|
||||||
|
# Remove the listener from the event type
|
||||||
|
if self.has_listener(event_type, listener):
|
||||||
|
listeners = self._events[event_type]
|
||||||
|
|
||||||
|
if len(listeners) == 1:
|
||||||
|
# Only this listener remains so remove the key
|
||||||
|
del self._events[event_type]
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Update listeners chain
|
||||||
|
listeners.remove(listener)
|
||||||
|
self._events[event_type] = listeners
|
||||||
120
watcher/common/messaging/messaging_handler.py
Normal file
120
watcher/common/messaging/messaging_handler.py
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
# Copyright (c) 2015 b<>com
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
# implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
import socket
|
||||||
|
import threading
|
||||||
|
|
||||||
|
import eventlet
|
||||||
|
from oslo_config import cfg
|
||||||
|
from oslo_log import log
|
||||||
|
import oslo_messaging as om
|
||||||
|
|
||||||
|
from watcher.common import rpc
|
||||||
|
from watcher._i18n import _LE, _LW
|
||||||
|
|
||||||
|
# NOTE:
|
||||||
|
# Ubuntu 14.04 forces librabbitmq when kombu is used
|
||||||
|
# Unfortunately it forces a version that has a crash
|
||||||
|
# bug. Calling eventlet.monkey_patch() tells kombu
|
||||||
|
# to use libamqp instead.
|
||||||
|
eventlet.monkey_patch()
|
||||||
|
|
||||||
|
LOG = log.getLogger(__name__)
|
||||||
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
|
class MessagingHandler(threading.Thread):
|
||||||
|
|
||||||
|
def __init__(self, publisher_id, topic_name, endpoints, version,
|
||||||
|
serializer=None):
|
||||||
|
super(MessagingHandler, self).__init__()
|
||||||
|
self.publisher_id = publisher_id
|
||||||
|
self.topic_name = topic_name
|
||||||
|
self.__endpoints = []
|
||||||
|
self.__serializer = serializer
|
||||||
|
self.__version = version
|
||||||
|
|
||||||
|
self.__server = None
|
||||||
|
self.__notifier = None
|
||||||
|
self.__transport = None
|
||||||
|
self.add_endpoints(endpoints)
|
||||||
|
|
||||||
|
def add_endpoints(self, endpoints):
|
||||||
|
self.__endpoints.extend(endpoints)
|
||||||
|
|
||||||
|
def remove_endpoint(self, endpoint):
|
||||||
|
if endpoint in self.__endpoints:
|
||||||
|
self.__endpoints.remove(endpoint)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def endpoints(self):
|
||||||
|
return self.__endpoints
|
||||||
|
|
||||||
|
@property
|
||||||
|
def transport(self):
|
||||||
|
return self.__transport
|
||||||
|
|
||||||
|
def build_notifier(self):
|
||||||
|
serializer = rpc.RequestContextSerializer(rpc.JsonPayloadSerializer())
|
||||||
|
return om.Notifier(
|
||||||
|
self.__transport,
|
||||||
|
publisher_id=self.publisher_id,
|
||||||
|
topic=self.topic_name,
|
||||||
|
serializer=serializer
|
||||||
|
)
|
||||||
|
|
||||||
|
def build_server(self, target):
|
||||||
|
return om.get_rpc_server(self.__transport, target,
|
||||||
|
self.__endpoints,
|
||||||
|
executor='eventlet',
|
||||||
|
serializer=self.__serializer)
|
||||||
|
|
||||||
|
def _configure(self):
|
||||||
|
try:
|
||||||
|
self.__transport = om.get_transport(CONF)
|
||||||
|
self.__notifier = self.build_notifier()
|
||||||
|
if len(self.__endpoints):
|
||||||
|
target = om.Target(
|
||||||
|
topic=self.topic_name,
|
||||||
|
# For compatibility, we can override it with 'host' opt
|
||||||
|
server=CONF.host or socket.getfqdn(),
|
||||||
|
version=self.__version,
|
||||||
|
)
|
||||||
|
self.__server = self.build_server(target)
|
||||||
|
else:
|
||||||
|
LOG.warning(
|
||||||
|
_LW("No endpoint defined; can only publish events"))
|
||||||
|
except Exception as e:
|
||||||
|
LOG.exception(e)
|
||||||
|
LOG.error(_LE("Messaging configuration error"))
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
LOG.debug("configure MessagingHandler for %s" % self.topic_name)
|
||||||
|
self._configure()
|
||||||
|
if len(self.__endpoints) > 0:
|
||||||
|
LOG.debug("Starting up server")
|
||||||
|
self.__server.start()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
LOG.debug('Stopped server')
|
||||||
|
self.__server.stop()
|
||||||
|
|
||||||
|
def publish_event(self, event_type, payload, request_id=None):
|
||||||
|
self.__notifier.info(
|
||||||
|
{'version_api': self.__version,
|
||||||
|
'request_id': request_id},
|
||||||
|
{'event_id': event_type}, payload
|
||||||
|
)
|
||||||
47
watcher/common/messaging/notification_handler.py
Normal file
47
watcher/common/messaging/notification_handler.py
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
# Copyright (c) 2015 b<>com
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
# implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
import eventlet
|
||||||
|
import oslo_messaging as messaging
|
||||||
|
|
||||||
|
from watcher.common.messaging.utils import observable
|
||||||
|
|
||||||
|
|
||||||
|
eventlet.monkey_patch()
|
||||||
|
|
||||||
|
|
||||||
|
class NotificationHandler(observable.Observable):
|
||||||
|
def __init__(self, publisher_id):
|
||||||
|
super(NotificationHandler, self).__init__()
|
||||||
|
self.publisher_id = publisher_id
|
||||||
|
|
||||||
|
def info(self, ctx, publisher_id, event_type, payload, metadata):
|
||||||
|
if publisher_id == self.publisher_id:
|
||||||
|
self.set_changed()
|
||||||
|
self.notify(ctx, publisher_id, event_type, metadata, payload)
|
||||||
|
return messaging.NotificationResult.HANDLED
|
||||||
|
|
||||||
|
def warn(self, ctx, publisher_id, event_type, payload, metadata):
|
||||||
|
if publisher_id == self.publisher_id:
|
||||||
|
self.set_changed()
|
||||||
|
self.notify(ctx, publisher_id, event_type, metadata, payload)
|
||||||
|
return messaging.NotificationResult.HANDLED
|
||||||
|
|
||||||
|
def error(self, ctx, publisher_id, event_type, payload, metadata):
|
||||||
|
if publisher_id == self.publisher_id:
|
||||||
|
self.set_changed()
|
||||||
|
self.notify(ctx, publisher_id, event_type, metadata, payload)
|
||||||
|
return messaging.NotificationResult.HANDLED
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from watcher.common import synchronization
|
from watcher.common.messaging.utils import synchronization
|
||||||
|
|
||||||
|
|
||||||
class Observable(synchronization.Synchronization):
|
class Observable(synchronization.Synchronization):
|
||||||
@@ -63,17 +63,16 @@ class NovaHelper(object):
|
|||||||
LOG.exception(exc)
|
LOG.exception(exc)
|
||||||
raise exception.ComputeNodeNotFound(name=node_hostname)
|
raise exception.ComputeNodeNotFound(name=node_hostname)
|
||||||
|
|
||||||
def get_aggregate_list(self):
|
|
||||||
return self.nova.aggregates.list()
|
|
||||||
|
|
||||||
def get_aggregate_detail(self, aggregate_id):
|
|
||||||
return self.nova.aggregates.get(aggregate_id)
|
|
||||||
|
|
||||||
def get_availability_zone_list(self):
|
|
||||||
return self.nova.availability_zones.list(detailed=True)
|
|
||||||
|
|
||||||
def find_instance(self, instance_id):
|
def find_instance(self, instance_id):
|
||||||
return self.nova.servers.get(instance_id)
|
search_opts = {'all_tenants': True}
|
||||||
|
instances = self.nova.servers.list(detailed=True,
|
||||||
|
search_opts=search_opts)
|
||||||
|
instance = None
|
||||||
|
for _instance in instances:
|
||||||
|
if _instance.id == instance_id:
|
||||||
|
instance = _instance
|
||||||
|
break
|
||||||
|
return instance
|
||||||
|
|
||||||
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):
|
||||||
@@ -664,7 +663,7 @@ class NovaHelper(object):
|
|||||||
cache[fid] = flavor
|
cache[fid] = flavor
|
||||||
attr_defaults = [('name', 'unknown-id-%s' % fid),
|
attr_defaults = [('name', 'unknown-id-%s' % fid),
|
||||||
('vcpus', 0), ('ram', 0), ('disk', 0),
|
('vcpus', 0), ('ram', 0), ('disk', 0),
|
||||||
('ephemeral', 0), ('extra_specs', {})]
|
('ephemeral', 0)]
|
||||||
for attr, default in attr_defaults:
|
for attr, default in attr_defaults:
|
||||||
if not flavor:
|
if not flavor:
|
||||||
instance.flavor[attr] = default
|
instance.flavor[attr] = default
|
||||||
|
|||||||
@@ -17,9 +17,38 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from watcher import conf
|
from oslo_config import cfg
|
||||||
|
|
||||||
CONF = conf.CONF
|
PATH_OPTS = [
|
||||||
|
cfg.StrOpt('pybasedir',
|
||||||
|
default=os.path.abspath(os.path.join(os.path.dirname(__file__),
|
||||||
|
'../')),
|
||||||
|
help='Directory where the watcher python module is installed.'),
|
||||||
|
cfg.StrOpt('bindir',
|
||||||
|
default='$pybasedir/bin',
|
||||||
|
help='Directory where watcher binaries are installed.'),
|
||||||
|
cfg.StrOpt('state_path',
|
||||||
|
default='$pybasedir',
|
||||||
|
help="Top-level directory for maintaining watcher's state."),
|
||||||
|
]
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
CONF.register_opts(PATH_OPTS)
|
||||||
|
|
||||||
|
|
||||||
|
def basedir_def(*args):
|
||||||
|
"""Return an uninterpolated path relative to $pybasedir."""
|
||||||
|
return os.path.join('$pybasedir', *args)
|
||||||
|
|
||||||
|
|
||||||
|
def bindir_def(*args):
|
||||||
|
"""Return an uninterpolated path relative to $bindir."""
|
||||||
|
return os.path.join('$bindir', *args)
|
||||||
|
|
||||||
|
|
||||||
|
def state_path_def(*args):
|
||||||
|
"""Return an uninterpolated path relative to $state_path."""
|
||||||
|
return os.path.join('$state_path', *args)
|
||||||
|
|
||||||
|
|
||||||
def basedir_rel(*args):
|
def basedir_rel(*args):
|
||||||
|
|||||||
@@ -72,12 +72,8 @@ def init(conf):
|
|||||||
aliases=TRANSPORT_ALIASES)
|
aliases=TRANSPORT_ALIASES)
|
||||||
|
|
||||||
serializer = RequestContextSerializer(JsonPayloadSerializer())
|
serializer = RequestContextSerializer(JsonPayloadSerializer())
|
||||||
if not conf.notification_level:
|
NOTIFIER = messaging.Notifier(NOTIFICATION_TRANSPORT,
|
||||||
NOTIFIER = messaging.Notifier(
|
serializer=serializer)
|
||||||
NOTIFICATION_TRANSPORT, serializer=serializer, driver='noop')
|
|
||||||
else:
|
|
||||||
NOTIFIER = messaging.Notifier(NOTIFICATION_TRANSPORT,
|
|
||||||
serializer=serializer)
|
|
||||||
|
|
||||||
|
|
||||||
def initialized():
|
def initialized():
|
||||||
@@ -125,7 +121,7 @@ class RequestContextSerializer(messaging.Serializer):
|
|||||||
return self._base.deserialize_entity(context, entity)
|
return self._base.deserialize_entity(context, entity)
|
||||||
|
|
||||||
def serialize_context(self, context):
|
def serialize_context(self, context):
|
||||||
return context.to_dict()
|
return context
|
||||||
|
|
||||||
def deserialize_context(self, context):
|
def deserialize_context(self, context):
|
||||||
return watcher_context.RequestContext.from_dict(context)
|
return watcher_context.RequestContext.from_dict(context)
|
||||||
@@ -150,6 +146,8 @@ def get_server(target, endpoints, serializer=None):
|
|||||||
serializer=serializer)
|
serializer=serializer)
|
||||||
|
|
||||||
|
|
||||||
def get_notifier(publisher_id):
|
def get_notifier(service=None, host=None, publisher_id=None):
|
||||||
assert NOTIFIER is not None
|
assert NOTIFIER is not None
|
||||||
|
if not publisher_id:
|
||||||
|
publisher_id = "%s.%s" % (service, host or CONF.host)
|
||||||
return NOTIFIER.prepare(publisher_id=publisher_id)
|
return NOTIFIER.prepare(publisher_id=publisher_id)
|
||||||
|
|||||||
@@ -14,10 +14,9 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import datetime
|
import logging
|
||||||
import socket
|
import socket
|
||||||
|
|
||||||
import eventlet
|
|
||||||
from oslo_concurrency import processutils
|
from oslo_concurrency import processutils
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import _options
|
from oslo_log import _options
|
||||||
@@ -28,36 +27,30 @@ 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 watcher._i18n import _
|
from watcher._i18n import _, _LI
|
||||||
from watcher.api import app
|
from watcher.api import app
|
||||||
from watcher.common import config
|
from watcher.common import config
|
||||||
from watcher.common import context
|
from watcher.common.messaging.events import event_dispatcher as dispatcher
|
||||||
|
from watcher.common.messaging import messaging_handler
|
||||||
from watcher.common import rpc
|
from watcher.common import rpc
|
||||||
from watcher.common import scheduling
|
|
||||||
from watcher.conf import plugins as plugins_conf
|
|
||||||
from watcher import objects
|
|
||||||
from watcher.objects import base
|
from watcher.objects import base
|
||||||
from watcher.objects import fields as wfields
|
from watcher import opts
|
||||||
from watcher import version
|
from watcher import version
|
||||||
|
|
||||||
# NOTE:
|
service_opts = [
|
||||||
# Ubuntu 14.04 forces librabbitmq when kombu is used
|
cfg.IntOpt('periodic_interval',
|
||||||
# Unfortunately it forces a version that has a crash
|
default=60,
|
||||||
# bug. Calling eventlet.monkey_patch() tells kombu
|
help=_('Seconds between running periodic tasks.')),
|
||||||
# to use libamqp instead.
|
cfg.StrOpt('host',
|
||||||
eventlet.monkey_patch()
|
default=socket.getfqdn(),
|
||||||
|
help=_('Name of this node. This can be an opaque identifier. '
|
||||||
NOTIFICATION_OPTS = [
|
'It is not necessarily a hostname, FQDN, or IP address. '
|
||||||
cfg.StrOpt('notification_level',
|
'However, the node name must be valid within '
|
||||||
choices=[''] + list(wfields.NotificationPriority.ALL),
|
'an AMQP key, and if using ZeroMQ, a valid '
|
||||||
default=wfields.NotificationPriority.INFO,
|
'hostname, FQDN, or IP address.')),
|
||||||
help=_('Specifies the minimum level for which to send '
|
|
||||||
'notifications. If not set, no notifications will '
|
|
||||||
'be sent. The default is for this option to be at the '
|
|
||||||
'`INFO` level.'))
|
|
||||||
]
|
]
|
||||||
cfg.CONF.register_opts(NOTIFICATION_OPTS)
|
|
||||||
|
|
||||||
|
cfg.CONF.register_opts(service_opts)
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
@@ -75,21 +68,21 @@ Singleton = service.Singleton
|
|||||||
class WSGIService(service.ServiceBase):
|
class WSGIService(service.ServiceBase):
|
||||||
"""Provides ability to launch Watcher API from wsgi app."""
|
"""Provides ability to launch Watcher API from wsgi app."""
|
||||||
|
|
||||||
def __init__(self, service_name, use_ssl=False):
|
def __init__(self, name, use_ssl=False):
|
||||||
"""Initialize, but do not start the WSGI server.
|
"""Initialize, but do not start the WSGI server.
|
||||||
|
|
||||||
:param service_name: The service name of the WSGI server.
|
:param name: The name of the WSGI server given to the loader.
|
||||||
:param use_ssl: Wraps the socket in an SSL context if True.
|
:param use_ssl: Wraps the socket in an SSL context if True.
|
||||||
"""
|
"""
|
||||||
self.service_name = service_name
|
self.name = name
|
||||||
self.app = app.VersionSelectorApplication()
|
self.app = app.VersionSelectorApplication()
|
||||||
self.workers = (CONF.api.workers or
|
self.workers = (CONF.api.workers or
|
||||||
processutils.get_worker_count())
|
processutils.get_worker_count())
|
||||||
self.server = wsgi.Server(CONF, self.service_name, self.app,
|
self.server = wsgi.Server(CONF, name, self.app,
|
||||||
host=CONF.api.host,
|
host=CONF.api.host,
|
||||||
port=CONF.api.port,
|
port=CONF.api.port,
|
||||||
use_ssl=use_ssl,
|
use_ssl=use_ssl,
|
||||||
logger_name=self.service_name)
|
logger_name=name)
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
"""Start serving this service using loaded configuration"""
|
"""Start serving this service using loaded configuration"""
|
||||||
@@ -108,53 +101,7 @@ class WSGIService(service.ServiceBase):
|
|||||||
self.server.reset()
|
self.server.reset()
|
||||||
|
|
||||||
|
|
||||||
class ServiceHeartbeat(scheduling.BackgroundSchedulerService):
|
class Service(service.ServiceBase, dispatcher.EventDispatcher):
|
||||||
|
|
||||||
def __init__(self, gconfig=None, service_name=None, **kwargs):
|
|
||||||
gconfig = None or {}
|
|
||||||
super(ServiceHeartbeat, self).__init__(gconfig, **kwargs)
|
|
||||||
self.service_name = service_name
|
|
||||||
self.context = context.make_context()
|
|
||||||
|
|
||||||
def send_beat(self):
|
|
||||||
host = CONF.host
|
|
||||||
watcher_list = objects.Service.list(
|
|
||||||
self.context, filters={'name': self.service_name,
|
|
||||||
'host': host})
|
|
||||||
if watcher_list:
|
|
||||||
watcher_service = watcher_list[0]
|
|
||||||
watcher_service.last_seen_up = datetime.datetime.utcnow()
|
|
||||||
watcher_service.save()
|
|
||||||
else:
|
|
||||||
watcher_service = objects.Service(self.context)
|
|
||||||
watcher_service.name = self.service_name
|
|
||||||
watcher_service.host = host
|
|
||||||
watcher_service.create()
|
|
||||||
|
|
||||||
def add_heartbeat_job(self):
|
|
||||||
self.add_job(self.send_beat, 'interval', seconds=60,
|
|
||||||
next_run_time=datetime.datetime.now())
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
"""Start service."""
|
|
||||||
self.add_heartbeat_job()
|
|
||||||
super(ServiceHeartbeat, 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.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class Service(service.ServiceBase):
|
|
||||||
|
|
||||||
API_VERSION = '1.0'
|
API_VERSION = '1.0'
|
||||||
|
|
||||||
@@ -163,14 +110,18 @@ class Service(service.ServiceBase):
|
|||||||
self.manager = manager_class()
|
self.manager = manager_class()
|
||||||
|
|
||||||
self.publisher_id = self.manager.publisher_id
|
self.publisher_id = self.manager.publisher_id
|
||||||
self.api_version = self.manager.api_version
|
self.api_version = self.manager.API_VERSION
|
||||||
|
|
||||||
self.conductor_topic = self.manager.conductor_topic
|
self.conductor_topic = self.manager.conductor_topic
|
||||||
|
self.status_topic = self.manager.status_topic
|
||||||
self.notification_topics = self.manager.notification_topics
|
self.notification_topics = self.manager.notification_topics
|
||||||
|
|
||||||
self.conductor_endpoints = [
|
self.conductor_endpoints = [
|
||||||
ep(self) for ep in self.manager.conductor_endpoints
|
ep(self) for ep in self.manager.conductor_endpoints
|
||||||
]
|
]
|
||||||
|
self.status_endpoints = [
|
||||||
|
ep(self.publisher_id) for ep in self.manager.status_endpoints
|
||||||
|
]
|
||||||
self.notification_endpoints = self.manager.notification_endpoints
|
self.notification_endpoints = self.manager.notification_endpoints
|
||||||
|
|
||||||
self.serializer = rpc.RequestContextSerializer(
|
self.serializer = rpc.RequestContextSerializer(
|
||||||
@@ -179,23 +130,22 @@ class Service(service.ServiceBase):
|
|||||||
self._transport = None
|
self._transport = None
|
||||||
self._notification_transport = None
|
self._notification_transport = None
|
||||||
self._conductor_client = None
|
self._conductor_client = None
|
||||||
|
self._status_client = None
|
||||||
|
|
||||||
self.conductor_topic_handler = None
|
self.conductor_topic_handler = None
|
||||||
|
self.status_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)
|
||||||
|
if self.status_topic and self.status_endpoints:
|
||||||
|
self.status_topic_handler = self.build_topic_handler(
|
||||||
|
self.status_topic, self.status_endpoints)
|
||||||
if self.notification_topics and self.notification_endpoints:
|
if self.notification_topics and self.notification_endpoints:
|
||||||
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):
|
||||||
@@ -224,17 +174,25 @@ class Service(service.ServiceBase):
|
|||||||
def conductor_client(self, c):
|
def conductor_client(self, c):
|
||||||
self.conductor_client = c
|
self.conductor_client = c
|
||||||
|
|
||||||
|
@property
|
||||||
|
def status_client(self):
|
||||||
|
if self._status_client is None:
|
||||||
|
target = om.Target(
|
||||||
|
topic=self.status_topic,
|
||||||
|
version=self.API_VERSION,
|
||||||
|
)
|
||||||
|
self._status_client = om.RPCClient(
|
||||||
|
self.transport, target, serializer=self.serializer)
|
||||||
|
return self._status_client
|
||||||
|
|
||||||
|
@status_client.setter
|
||||||
|
def status_client(self, c):
|
||||||
|
self.status_client = c
|
||||||
|
|
||||||
def build_topic_handler(self, topic_name, endpoints=()):
|
def build_topic_handler(self, topic_name, endpoints=()):
|
||||||
serializer = rpc.RequestContextSerializer(rpc.JsonPayloadSerializer())
|
return messaging_handler.MessagingHandler(
|
||||||
target = om.Target(
|
self.publisher_id, topic_name, [self.manager] + list(endpoints),
|
||||||
topic=topic_name,
|
self.api_version, self.serializer)
|
||||||
# For compatibility, we can override it with 'host' opt
|
|
||||||
server=CONF.host or socket.gethostname(),
|
|
||||||
version=self.api_version,
|
|
||||||
)
|
|
||||||
return om.get_rpc_server(
|
|
||||||
self.transport, target, endpoints,
|
|
||||||
executor='eventlet', serializer=serializer)
|
|
||||||
|
|
||||||
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())
|
||||||
@@ -249,20 +207,20 @@ class Service(service.ServiceBase):
|
|||||||
CONF.transport_url, CONF.rpc_backend)
|
CONF.transport_url, CONF.rpc_backend)
|
||||||
if self.conductor_topic_handler:
|
if self.conductor_topic_handler:
|
||||||
self.conductor_topic_handler.start()
|
self.conductor_topic_handler.start()
|
||||||
|
if self.status_topic_handler:
|
||||||
|
self.status_topic_handler.start()
|
||||||
if self.notification_handler:
|
if self.notification_handler:
|
||||||
self.notification_handler.start()
|
self.notification_handler.start()
|
||||||
if self.heartbeat:
|
|
||||||
self.heartbeat.start()
|
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
LOG.debug("Disconnecting from '%s' (%s)",
|
LOG.debug("Disconnecting from '%s' (%s)",
|
||||||
CONF.transport_url, CONF.rpc_backend)
|
CONF.transport_url, CONF.rpc_backend)
|
||||||
if self.conductor_topic_handler:
|
if self.conductor_topic_handler:
|
||||||
self.conductor_topic_handler.stop()
|
self.conductor_topic_handler.stop()
|
||||||
|
if self.status_topic_handler:
|
||||||
|
self.status_topic_handler.stop()
|
||||||
if self.notification_handler:
|
if self.notification_handler:
|
||||||
self.notification_handler.stop()
|
self.notification_handler.stop()
|
||||||
if self.heartbeat:
|
|
||||||
self.heartbeat.stop()
|
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
"""Reset a service in case it received a SIGHUP."""
|
"""Reset a service in case it received a SIGHUP."""
|
||||||
@@ -270,11 +228,34 @@ class Service(service.ServiceBase):
|
|||||||
def wait(self):
|
def wait(self):
|
||||||
"""Wait for service to complete."""
|
"""Wait for service to complete."""
|
||||||
|
|
||||||
def check_api_version(self, ctx):
|
def publish_control(self, event, payload):
|
||||||
|
return self.conductor_topic_handler.publish_event(event, payload)
|
||||||
|
|
||||||
|
def publish_status_event(self, event, payload, request_id=None):
|
||||||
|
if self.status_topic_handler:
|
||||||
|
return self.status_topic_handler.publish_event(
|
||||||
|
event, payload, request_id)
|
||||||
|
else:
|
||||||
|
LOG.info(
|
||||||
|
_LI("No status notifier declared: notification '%s' not sent"),
|
||||||
|
event)
|
||||||
|
|
||||||
|
def get_version(self):
|
||||||
|
return self.api_version
|
||||||
|
|
||||||
|
def check_api_version(self, context):
|
||||||
api_manager_version = self.conductor_client.call(
|
api_manager_version = self.conductor_client.call(
|
||||||
ctx, 'check_api_version', api_version=self.api_version)
|
context.to_dict(), 'check_api_version',
|
||||||
|
api_version=self.api_version)
|
||||||
return api_manager_version
|
return api_manager_version
|
||||||
|
|
||||||
|
def response(self, evt, ctx, message):
|
||||||
|
payload = {
|
||||||
|
'request_id': ctx['request_id'],
|
||||||
|
'msg': message
|
||||||
|
}
|
||||||
|
self.publish_status_event(evt, payload)
|
||||||
|
|
||||||
|
|
||||||
def launch(conf, service_, workers=1, restart_method='reload'):
|
def launch(conf, service_, workers=1, restart_method='reload'):
|
||||||
return service.launch(conf, service_, workers, restart_method)
|
return service.launch(conf, service_, workers, restart_method)
|
||||||
@@ -288,9 +269,7 @@ def prepare_service(argv=(), conf=cfg.CONF):
|
|||||||
cfg.set_defaults(_options.log_opts,
|
cfg.set_defaults(_options.log_opts,
|
||||||
default_log_levels=_DEFAULT_LOG_LEVELS)
|
default_log_levels=_DEFAULT_LOG_LEVELS)
|
||||||
log.setup(conf, 'python-watcher')
|
log.setup(conf, 'python-watcher')
|
||||||
conf.log_opt_values(LOG, log.DEBUG)
|
conf.log_opt_values(LOG, logging.DEBUG)
|
||||||
objects.register_all()
|
|
||||||
|
|
||||||
gmr.TextGuruMeditation.register_section(
|
gmr.TextGuruMeditation.register_section(_('Plugins'), opts.show_plugins)
|
||||||
_('Plugins'), plugins_conf.show_plugins)
|
|
||||||
gmr.TextGuruMeditation.setup_autorun(version, conf=conf)
|
gmr.TextGuruMeditation.setup_autorun(version, conf=conf)
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# Copyright © 2016 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 abc
|
|
||||||
import six
|
|
||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(abc.ABCMeta)
|
|
||||||
class ServiceManager(object):
|
|
||||||
|
|
||||||
@abc.abstractproperty
|
|
||||||
def service_name(self):
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
@abc.abstractproperty
|
|
||||||
def api_version(self):
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
@abc.abstractproperty
|
|
||||||
def publisher_id(self):
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
@abc.abstractproperty
|
|
||||||
def conductor_topic(self):
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
@abc.abstractproperty
|
|
||||||
def notification_topics(self):
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
@abc.abstractproperty
|
|
||||||
def conductor_endpoints(self):
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
@abc.abstractproperty
|
|
||||||
def notification_endpoints(self):
|
|
||||||
raise NotImplementedError()
|
|
||||||
@@ -16,21 +16,29 @@
|
|||||||
|
|
||||||
"""Utilities and helper functions."""
|
"""Utilities and helper functions."""
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
from jsonschema import validators
|
from jsonschema import validators
|
||||||
|
from oslo_config import cfg
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from oslo_utils import strutils
|
|
||||||
from oslo_utils import timeutils
|
|
||||||
from oslo_utils import uuidutils
|
|
||||||
import six
|
|
||||||
|
|
||||||
from watcher._i18n import _LW
|
|
||||||
from watcher.common import exception
|
from watcher.common import exception
|
||||||
|
|
||||||
from watcher import conf
|
import re
|
||||||
|
import six
|
||||||
|
import uuid
|
||||||
|
|
||||||
CONF = conf.CONF
|
|
||||||
|
from watcher._i18n import _LW
|
||||||
|
|
||||||
|
UTILS_OPTS = [
|
||||||
|
cfg.StrOpt('rootwrap_config',
|
||||||
|
default="/etc/watcher/rootwrap.conf",
|
||||||
|
help='Path to the rootwrap configuration file to use for '
|
||||||
|
'running commands as root.'),
|
||||||
|
cfg.StrOpt('tempdir',
|
||||||
|
help='Explicitly specify the temporary working directory.'),
|
||||||
|
]
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
CONF.register_opts(UTILS_OPTS)
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -58,12 +66,6 @@ class Struct(dict):
|
|||||||
raise AttributeError(name)
|
raise AttributeError(name)
|
||||||
|
|
||||||
|
|
||||||
generate_uuid = uuidutils.generate_uuid
|
|
||||||
is_uuid_like = uuidutils.is_uuid_like
|
|
||||||
is_int_like = strutils.is_int_like
|
|
||||||
strtime = timeutils.strtime
|
|
||||||
|
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
@@ -81,6 +83,31 @@ def safe_rstrip(value, chars=None):
|
|||||||
return value.rstrip(chars) or value
|
return value.rstrip(chars) or value
|
||||||
|
|
||||||
|
|
||||||
|
def generate_uuid():
|
||||||
|
return str(uuid.uuid4())
|
||||||
|
|
||||||
|
|
||||||
|
def is_int_like(val):
|
||||||
|
"""Check if a value looks like an int."""
|
||||||
|
try:
|
||||||
|
return str(int(val)) == str(val)
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def is_uuid_like(val):
|
||||||
|
"""Returns validation of a value as a UUID.
|
||||||
|
|
||||||
|
For our purposes, a UUID is a canonical form string:
|
||||||
|
aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return str(uuid.UUID(val)) == val
|
||||||
|
except (TypeError, ValueError, AttributeError):
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def is_hostname_safe(hostname):
|
def is_hostname_safe(hostname):
|
||||||
"""Determine if the supplied hostname is RFC compliant.
|
"""Determine if the supplied hostname is RFC compliant.
|
||||||
|
|
||||||
@@ -106,6 +133,10 @@ def get_cls_import_path(cls):
|
|||||||
return module + '.' + cls.__name__
|
return module + '.' + cls.__name__
|
||||||
|
|
||||||
|
|
||||||
|
def strtime(at):
|
||||||
|
return at.strftime("%Y-%m-%dT%H:%M:%S.%f")
|
||||||
|
|
||||||
|
|
||||||
# Default value feedback extension as jsonschema doesn't support it
|
# Default value feedback extension as jsonschema doesn't support it
|
||||||
def extend_with_default(validator_class):
|
def extend_with_default(validator_class):
|
||||||
validate_properties = validator_class.VALIDATORS["properties"]
|
validate_properties = validator_class.VALIDATORS["properties"]
|
||||||
@@ -142,5 +173,3 @@ def extend_with_strict_schema(validator_class):
|
|||||||
|
|
||||||
StrictDefaultValidatingDraft4Validator = extend_with_default(
|
StrictDefaultValidatingDraft4Validator = extend_with_default(
|
||||||
extend_with_strict_schema(validators.Draft4Validator))
|
extend_with_strict_schema(validators.Draft4Validator))
|
||||||
|
|
||||||
Draft4Validator = validators.Draft4Validator
|
|
||||||
|
|||||||
@@ -1,54 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
# Copyright (c) 2016 b<>com
|
|
||||||
# Copyright (c) 2016 Intel Corp
|
|
||||||
#
|
|
||||||
# Authors: Vincent FRANCOISE <vincent.francoise@b-com.com>
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
# implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
from oslo_config import cfg
|
|
||||||
|
|
||||||
from watcher.conf import api
|
|
||||||
from watcher.conf import applier
|
|
||||||
from watcher.conf import ceilometer_client
|
|
||||||
from watcher.conf import cinder_client
|
|
||||||
from watcher.conf import clients_auth
|
|
||||||
from watcher.conf import db
|
|
||||||
from watcher.conf import decision_engine
|
|
||||||
from watcher.conf import exception
|
|
||||||
from watcher.conf import glance_client
|
|
||||||
from watcher.conf import neutron_client
|
|
||||||
from watcher.conf import nova_client
|
|
||||||
from watcher.conf import paths
|
|
||||||
from watcher.conf import planner
|
|
||||||
from watcher.conf import service
|
|
||||||
from watcher.conf import utils
|
|
||||||
|
|
||||||
CONF = cfg.CONF
|
|
||||||
|
|
||||||
service.register_opts(CONF)
|
|
||||||
api.register_opts(CONF)
|
|
||||||
utils.register_opts(CONF)
|
|
||||||
paths.register_opts(CONF)
|
|
||||||
exception.register_opts(CONF)
|
|
||||||
db.register_opts(CONF)
|
|
||||||
planner.register_opts(CONF)
|
|
||||||
applier.register_opts(CONF)
|
|
||||||
decision_engine.register_opts(CONF)
|
|
||||||
nova_client.register_opts(CONF)
|
|
||||||
glance_client.register_opts(CONF)
|
|
||||||
cinder_client.register_opts(CONF)
|
|
||||||
ceilometer_client.register_opts(CONF)
|
|
||||||
neutron_client.register_opts(CONF)
|
|
||||||
clients_auth.register_opts(CONF)
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
# Copyright 2014
|
|
||||||
# The Cloudscaling Group, Inc.
|
|
||||||
# Copyright (c) 2016 Intel Corp
|
|
||||||
#
|
|
||||||
# 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 keystoneauth1 import loading as ka_loading
|
|
||||||
|
|
||||||
from watcher.conf import api as conf_api
|
|
||||||
from watcher.conf import applier as conf_applier
|
|
||||||
from watcher.conf import ceilometer_client as conf_ceilometer_client
|
|
||||||
from watcher.conf import cinder_client as conf_cinder_client
|
|
||||||
from watcher.conf import db
|
|
||||||
from watcher.conf import decision_engine as conf_de
|
|
||||||
from watcher.conf import exception
|
|
||||||
from watcher.conf import glance_client as conf_glance_client
|
|
||||||
from watcher.conf import neutron_client as conf_neutron_client
|
|
||||||
from watcher.conf import nova_client as conf_nova_client
|
|
||||||
from watcher.conf import paths
|
|
||||||
from watcher.conf import planner as conf_planner
|
|
||||||
from watcher.conf import utils
|
|
||||||
|
|
||||||
|
|
||||||
def list_opts():
|
|
||||||
"""Legacy aggregation of all the watcher config options"""
|
|
||||||
return [
|
|
||||||
('DEFAULT',
|
|
||||||
(conf_api.AUTH_OPTS +
|
|
||||||
exception.EXC_LOG_OPTS +
|
|
||||||
paths.PATH_OPTS +
|
|
||||||
utils.UTILS_OPTS)),
|
|
||||||
('api', conf_api.API_SERVICE_OPTS),
|
|
||||||
('database', db.SQL_OPTS),
|
|
||||||
('watcher_planner', conf_planner.WATCHER_PLANNER_OPTS),
|
|
||||||
('watcher_applier', conf_applier.APPLIER_MANAGER_OPTS),
|
|
||||||
('watcher_decision_engine',
|
|
||||||
(conf_de.WATCHER_DECISION_ENGINE_OPTS +
|
|
||||||
conf_de.WATCHER_CONTINUOUS_OPTS)),
|
|
||||||
('nova_client', conf_nova_client.NOVA_CLIENT_OPTS),
|
|
||||||
('glance_client', conf_glance_client.GLANCE_CLIENT_OPTS),
|
|
||||||
('cinder_client', conf_cinder_client.CINDER_CLIENT_OPTS),
|
|
||||||
('ceilometer_client', conf_ceilometer_client.CEILOMETER_CLIENT_OPTS),
|
|
||||||
('neutron_client', conf_neutron_client.NEUTRON_CLIENT_OPTS),
|
|
||||||
('watcher_clients_auth',
|
|
||||||
(ka_loading.get_auth_common_conf_options() +
|
|
||||||
ka_loading.get_auth_plugin_conf_options('password') +
|
|
||||||
ka_loading.get_session_conf_options()))
|
|
||||||
]
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
# Copyright (c) 2016 Intel Corp
|
|
||||||
#
|
|
||||||
# Authors: Prudhvi Rao Shedimbi <prudhvi.rao.shedimbi@intel.com>
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
# implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
from oslo_config import cfg
|
|
||||||
|
|
||||||
api = cfg.OptGroup(name='api',
|
|
||||||
title='Options for the Watcher API service')
|
|
||||||
|
|
||||||
AUTH_OPTS = [
|
|
||||||
cfg.BoolOpt('enable_authentication',
|
|
||||||
default=True,
|
|
||||||
help='This option enables or disables user authentication '
|
|
||||||
'via keystone. Default value is True.'),
|
|
||||||
]
|
|
||||||
|
|
||||||
API_SERVICE_OPTS = [
|
|
||||||
cfg.PortOpt('port',
|
|
||||||
default=9322,
|
|
||||||
help='The port for the watcher API server'),
|
|
||||||
cfg.StrOpt('host',
|
|
||||||
default='127.0.0.1',
|
|
||||||
help='The listen IP address for the watcher API server'),
|
|
||||||
cfg.IntOpt('max_limit',
|
|
||||||
default=1000,
|
|
||||||
help='The maximum number of items returned in a single '
|
|
||||||
'response from a collection resource'),
|
|
||||||
cfg.IntOpt('workers',
|
|
||||||
min=1,
|
|
||||||
help='Number of workers for Watcher API service. '
|
|
||||||
'The default is equal to the number of CPUs available '
|
|
||||||
'if that can be determined, else a default worker '
|
|
||||||
'count of 1 is returned.'),
|
|
||||||
|
|
||||||
cfg.BoolOpt('enable_ssl_api',
|
|
||||||
default=False,
|
|
||||||
help="Enable the integrated stand-alone API to service "
|
|
||||||
"requests via HTTPS instead of HTTP. If there is a "
|
|
||||||
"front-end service performing HTTPS offloading from "
|
|
||||||
"the service, this option should be False; note, you "
|
|
||||||
"will want to change public API endpoint to represent "
|
|
||||||
"SSL termination URL with 'public_endpoint' option."),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def register_opts(conf):
|
|
||||||
conf.register_group(api)
|
|
||||||
conf.register_opts(API_SERVICE_OPTS, group=api)
|
|
||||||
conf.register_opts(AUTH_OPTS)
|
|
||||||
|
|
||||||
|
|
||||||
def list_opts():
|
|
||||||
return [('api', API_SERVICE_OPTS), ('DEFAULT', AUTH_OPTS)]
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
# Copyright (c) 2016 Intel Corp
|
|
||||||
#
|
|
||||||
# Authors: Prudhvi Rao Shedimbi <prudhvi.rao.shedimbi@intel.com>
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
# implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
from oslo_config import cfg
|
|
||||||
|
|
||||||
watcher_applier = cfg.OptGroup(name='watcher_applier',
|
|
||||||
title='Options for the Applier messaging'
|
|
||||||
'core')
|
|
||||||
|
|
||||||
APPLIER_MANAGER_OPTS = [
|
|
||||||
cfg.IntOpt('workers',
|
|
||||||
default='1',
|
|
||||||
min=1,
|
|
||||||
required=True,
|
|
||||||
help='Number of workers for applier, default value is 1.'),
|
|
||||||
cfg.StrOpt('conductor_topic',
|
|
||||||
default='watcher.applier.control',
|
|
||||||
help='The topic name used for'
|
|
||||||
'control events, this topic '
|
|
||||||
'used for rpc call '),
|
|
||||||
cfg.StrOpt('publisher_id',
|
|
||||||
default='watcher.applier.api',
|
|
||||||
help='The identifier used by watcher '
|
|
||||||
'module on the message broker'),
|
|
||||||
cfg.StrOpt('workflow_engine',
|
|
||||||
default='taskflow',
|
|
||||||
required=True,
|
|
||||||
help='Select the engine to use to execute the workflow')
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def register_opts(conf):
|
|
||||||
conf.register_group(watcher_applier)
|
|
||||||
conf.register_opts(APPLIER_MANAGER_OPTS, group=watcher_applier)
|
|
||||||
|
|
||||||
|
|
||||||
def list_opts():
|
|
||||||
return [('watcher_applier', APPLIER_MANAGER_OPTS)]
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
# Copyright (c) 2016 Intel Corp
|
|
||||||
#
|
|
||||||
# Authors: Prudhvi Rao Shedimbi <prudhvi.rao.shedimbi@intel.com>
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
# implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
from oslo_config import cfg
|
|
||||||
|
|
||||||
ceilometer_client = cfg.OptGroup(name='ceilometer_client',
|
|
||||||
title='Configuration Options for Ceilometer')
|
|
||||||
|
|
||||||
CEILOMETER_CLIENT_OPTS = [
|
|
||||||
cfg.StrOpt('api_version',
|
|
||||||
default='2',
|
|
||||||
help='Version of Ceilometer API to use in '
|
|
||||||
'ceilometerclient.')]
|
|
||||||
|
|
||||||
|
|
||||||
def register_opts(conf):
|
|
||||||
conf.register_group(ceilometer_client)
|
|
||||||
conf.register_opts(CEILOMETER_CLIENT_OPTS, group=ceilometer_client)
|
|
||||||
|
|
||||||
|
|
||||||
def list_opts():
|
|
||||||
return [('ceilometer_client', CEILOMETER_CLIENT_OPTS)]
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
# Copyright (c) 2016 Intel Corp
|
|
||||||
#
|
|
||||||
# Authors: Prudhvi Rao Shedimbi <prudhvi.rao.shedimbi@intel.com>
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
# implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
from oslo_config import cfg
|
|
||||||
|
|
||||||
cinder_client = cfg.OptGroup(name='cinder_client',
|
|
||||||
title='Configuration Options for Cinder')
|
|
||||||
|
|
||||||
CINDER_CLIENT_OPTS = [
|
|
||||||
cfg.StrOpt('api_version',
|
|
||||||
default='2',
|
|
||||||
help='Version of Cinder API to use in cinderclient.')]
|
|
||||||
|
|
||||||
|
|
||||||
def register_opts(conf):
|
|
||||||
conf.register_group(cinder_client)
|
|
||||||
conf.register_opts(CINDER_CLIENT_OPTS, group=cinder_client)
|
|
||||||
|
|
||||||
|
|
||||||
def list_opts():
|
|
||||||
return [('cinder_client', CINDER_CLIENT_OPTS)]
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
# Copyright (c) 2016 Intel Corp
|
|
||||||
#
|
|
||||||
# Authors: Prudhvi Rao Shedimbi <prudhvi.rao.shedimbi@intel.com>
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
# implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
from keystoneauth1 import loading as ka_loading
|
|
||||||
|
|
||||||
WATCHER_CLIENTS_AUTH = 'watcher_clients_auth'
|
|
||||||
|
|
||||||
|
|
||||||
def register_opts(conf):
|
|
||||||
ka_loading.register_session_conf_options(conf, WATCHER_CLIENTS_AUTH)
|
|
||||||
ka_loading.register_auth_conf_options(conf, WATCHER_CLIENTS_AUTH)
|
|
||||||
|
|
||||||
|
|
||||||
def list_opts():
|
|
||||||
return [('watcher_clients_auth', ka_loading.get_session_conf_options() +
|
|
||||||
ka_loading.get_auth_common_conf_options())]
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user