Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
19fdd1557e | ||
|
|
641989b424 | ||
|
|
8814c09087 | ||
|
|
eb4f46b703 | ||
|
|
2f33dd10c0 | ||
|
|
1a197ab801 |
@@ -1,5 +1,5 @@
|
||||
[gerrit]
|
||||
host=review.opendev.org
|
||||
host=review.openstack.org
|
||||
port=29418
|
||||
project=openstack/watcher.git
|
||||
defaultbranch=stable/ocata
|
||||
defaultbranch=stable/newton
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
- project:
|
||||
templates:
|
||||
- openstack-python-jobs
|
||||
- openstack-python35-jobs
|
||||
- publish-openstack-sphinx-docs
|
||||
- check-requirements
|
||||
- release-notes-jobs
|
||||
gate:
|
||||
queue: watcher
|
||||
6
MANIFEST.in
Normal file
@@ -0,0 +1,6 @@
|
||||
include AUTHORS
|
||||
include ChangeLog
|
||||
exclude .gitignore
|
||||
exclude .gitreview
|
||||
|
||||
global-exclude *.pyc
|
||||
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
|
||||
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
|
||||
service for multi-tenant OpenStack-based clouds.
|
||||
Watcher provides a robust framework to realize a wide range of cloud
|
||||
optimization goals, including the reduction of data center
|
||||
Watcher provides a complete optimization loop-including everything from a
|
||||
metrics receiver, complex event processor and profiler, optimization processor
|
||||
and an action plan applier. This provides a robust framework to realize a wide
|
||||
range of cloud optimization goals, including the reduction of data center
|
||||
operating costs, increased system performance via intelligent virtual machine
|
||||
migration, increased energy efficiency-and more!
|
||||
|
||||
|
||||
@@ -126,10 +126,7 @@ function create_watcher_conf {
|
||||
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_notifications driver "messaging"
|
||||
|
||||
iniset $NOVA_CONF oslo_messaging_notifications topics "notifications,watcher_notifications"
|
||||
iniset $NOVA_CONF notifications notify_on_state_change "vm_and_task_state"
|
||||
|
||||
configure_auth_token_middleware $WATCHER_CONF watcher $WATCHER_AUTH_CACHE_DIR
|
||||
configure_auth_token_middleware $WATCHER_CONF watcher $WATCHER_AUTH_CACHE_DIR "watcher_clients_auth"
|
||||
@@ -188,7 +185,7 @@ function init_watcher {
|
||||
recreate_database watcher
|
||||
|
||||
# Create watcher schema
|
||||
$WATCHER_BIN_DIR/watcher-db-manage --config-file $WATCHER_CONF upgrade head
|
||||
$WATCHER_BIN_DIR/watcher-db-manage --config-file $WATCHER_CONF create_schema
|
||||
fi
|
||||
create_watcher_cache_dir
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ VNCSERVER_PROXYCLIENT_ADDRESS=$HOST_IP
|
||||
NOVA_INSTANCES_PATH=/opt/stack/data/instances
|
||||
|
||||
# Enable the Ceilometer plugin for the compute agent
|
||||
enable_plugin ceilometer https://git.openstack.org/openstack/ceilometer
|
||||
enable_plugin ceilometer git://git.openstack.org/openstack/ceilometer
|
||||
disable_service ceilometer-acentral,ceilometer-collector,ceilometer-api
|
||||
|
||||
LOGFILE=$DEST/logs/stack.sh.log
|
||||
@@ -44,6 +44,3 @@ LOGDAYS=2
|
||||
[[post-config|$NOVA_CONF]]
|
||||
[DEFAULT]
|
||||
compute_monitors=cpu.virt_driver
|
||||
notify_on_state_change = vm_and_task_state
|
||||
[notifications]
|
||||
notify_on_state_change = vm_and_task_state
|
||||
|
||||
@@ -28,13 +28,13 @@ ENABLED_SERVICES+=,q-svc,q-dhcp,q-meta,q-agt,q-l3,neutron
|
||||
enable_service n-cauth
|
||||
|
||||
# Enable the Watcher Dashboard plugin
|
||||
# enable_plugin watcher-dashboard https://git.openstack.org/openstack/watcher-dashboard
|
||||
enable_plugin watcher-dashboard git://git.openstack.org/openstack/watcher-dashboard
|
||||
|
||||
# Enable the Watcher plugin
|
||||
enable_plugin watcher https://git.openstack.org/openstack/watcher
|
||||
enable_plugin watcher git://git.openstack.org/openstack/watcher
|
||||
|
||||
# Enable the Ceilometer plugin
|
||||
enable_plugin ceilometer https://git.openstack.org/openstack/ceilometer
|
||||
enable_plugin ceilometer git://git.openstack.org/openstack/ceilometer
|
||||
|
||||
# This is the controller node, so disable the ceilometer compute agent
|
||||
disable_service ceilometer-acompute
|
||||
@@ -45,6 +45,3 @@ LOGDAYS=2
|
||||
[[post-config|$NOVA_CONF]]
|
||||
[DEFAULT]
|
||||
compute_monitors=cpu.virt_driver
|
||||
notify_on_state_change = vm_and_task_state
|
||||
[notifications]
|
||||
notify_on_state_change = vm_and_task_state
|
||||
|
||||
@@ -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,54 +0,0 @@
|
||||
{
|
||||
"publisher_id": "infra-optim:node0",
|
||||
"payload": {
|
||||
"watcher_object.version": "1.0",
|
||||
"watcher_object.data": {
|
||||
"strategy_uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
|
||||
"strategy": {
|
||||
"watcher_object.version": "1.0",
|
||||
"watcher_object.data": {
|
||||
"uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
|
||||
"display_name": "test strategy",
|
||||
"name": "TEST",
|
||||
"updated_at": null,
|
||||
"parameters_spec": {},
|
||||
"created_at": "2016-10-18T09:52:05Z",
|
||||
"deleted_at": null
|
||||
},
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.name": "StrategyPayload"
|
||||
},
|
||||
"created_at": null,
|
||||
"audit_uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
||||
"audit": {
|
||||
"watcher_object.version": "1.0",
|
||||
"watcher_object.data": {
|
||||
"audit_type": "ONESHOT",
|
||||
"scope": [],
|
||||
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
||||
"goal_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
||||
"strategy_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
|
||||
"parameters": {},
|
||||
"interval": null,
|
||||
"deleted_at": null,
|
||||
"state": "PENDING",
|
||||
"created_at": "2016-10-18T09:52:05Z",
|
||||
"updated_at": null
|
||||
},
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.name": "TerseAuditPayload"
|
||||
},
|
||||
"uuid": "76be87bd-3422-43f9-93a0-e85a577e3061",
|
||||
"global_efficacy": {},
|
||||
"deleted_at": null,
|
||||
"state": "RECOMMENDED",
|
||||
"updated_at": null
|
||||
},
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.name": "ActionPlanCreatePayload"
|
||||
},
|
||||
"priority": "INFO",
|
||||
"message_id": "5148bff1-ea06-4ad6-8e4e-8c85ca5eb629",
|
||||
"event_type": "action_plan.create",
|
||||
"timestamp": "2016-10-18 09:52:05.219414"
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
{
|
||||
"publisher_id": "infra-optim:node0",
|
||||
"timestamp": "2016-10-18 09:52:05.219414",
|
||||
"payload": {
|
||||
"watcher_object.data": {
|
||||
"uuid": "76be87bd-3422-43f9-93a0-e85a577e3061",
|
||||
"created_at": "2016-10-18T09:52:05Z",
|
||||
"audit_uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
||||
"audit": {
|
||||
"watcher_object.data": {
|
||||
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
||||
"goal_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
||||
"strategy_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
|
||||
"interval": null,
|
||||
"audit_type": "ONESHOT",
|
||||
"scope": [],
|
||||
"updated_at": null,
|
||||
"deleted_at": null,
|
||||
"state": "PENDING",
|
||||
"created_at": "2016-10-18T09:52:05Z",
|
||||
"parameters": {}
|
||||
},
|
||||
"watcher_object.version": "1.0",
|
||||
"watcher_object.name": "TerseAuditPayload",
|
||||
"watcher_object.namespace": "watcher"
|
||||
},
|
||||
"global_efficacy": {},
|
||||
"updated_at": null,
|
||||
"deleted_at": null,
|
||||
"strategy_uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
|
||||
"strategy": {
|
||||
"watcher_object.data": {
|
||||
"uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
|
||||
"created_at": "2016-10-18T09:52:05Z",
|
||||
"name": "TEST",
|
||||
"display_name": "test strategy",
|
||||
"deleted_at": null,
|
||||
"updated_at": null,
|
||||
"parameters_spec": {}
|
||||
},
|
||||
"watcher_object.version": "1.0",
|
||||
"watcher_object.name": "StrategyPayload",
|
||||
"watcher_object.namespace": "watcher"
|
||||
},
|
||||
"state": "DELETED"
|
||||
},
|
||||
"watcher_object.version": "1.0",
|
||||
"watcher_object.name": "ActionPlanDeletePayload",
|
||||
"watcher_object.namespace": "watcher"
|
||||
},
|
||||
"event_type": "action_plan.delete",
|
||||
"message_id": "3d137686-a1fd-4683-ab40-c4210aac2140",
|
||||
"priority": "INFO"
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
{
|
||||
"event_type": "action_plan.execution.end",
|
||||
"payload": {
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.name": "ActionPlanActionPayload",
|
||||
"watcher_object.version": "1.0",
|
||||
"watcher_object.data": {
|
||||
"created_at": "2016-10-18T09:52:05Z",
|
||||
"deleted_at": null,
|
||||
"audit_uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
||||
"audit": {
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.name": "TerseAuditPayload",
|
||||
"watcher_object.version": "1.0",
|
||||
"watcher_object.data": {
|
||||
"created_at": "2016-10-18T09:52:05Z",
|
||||
"deleted_at": null,
|
||||
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
||||
"goal_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
||||
"strategy_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
|
||||
"scope": [],
|
||||
"audit_type": "ONESHOT",
|
||||
"state": "SUCCEEDED",
|
||||
"parameters": {},
|
||||
"interval": null,
|
||||
"updated_at": null
|
||||
}
|
||||
},
|
||||
"uuid": "76be87bd-3422-43f9-93a0-e85a577e3061",
|
||||
"fault": null,
|
||||
"state": "ONGOING",
|
||||
"global_efficacy": {},
|
||||
"strategy_uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
|
||||
"strategy": {
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.name": "StrategyPayload",
|
||||
"watcher_object.version": "1.0",
|
||||
"watcher_object.data": {
|
||||
"created_at": "2016-10-18T09:52:05Z",
|
||||
"deleted_at": null,
|
||||
"name": "TEST",
|
||||
"uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
|
||||
"parameters_spec": {},
|
||||
"display_name": "test strategy",
|
||||
"updated_at": null
|
||||
}
|
||||
},
|
||||
"updated_at": null
|
||||
}
|
||||
},
|
||||
"priority": "INFO",
|
||||
"message_id": "3984dc2b-8aef-462b-a220-8ae04237a56e",
|
||||
"timestamp": "2016-10-18 09:52:05.219414",
|
||||
"publisher_id": "infra-optim:node0"
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
{
|
||||
"event_type": "action_plan.execution.error",
|
||||
"publisher_id": "infra-optim:node0",
|
||||
"priority": "ERROR",
|
||||
"message_id": "9a45c5ae-0e21-4300-8fa0-5555d52a66d9",
|
||||
"payload": {
|
||||
"watcher_object.version": "1.0",
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.name": "ActionPlanActionPayload",
|
||||
"watcher_object.data": {
|
||||
"fault": {
|
||||
"watcher_object.version": "1.0",
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.name": "ExceptionPayload",
|
||||
"watcher_object.data": {
|
||||
"exception_message": "TEST",
|
||||
"module_name": "watcher.tests.notifications.test_action_plan_notification",
|
||||
"function_name": "test_send_action_plan_action_with_error",
|
||||
"exception": "WatcherException"
|
||||
}
|
||||
},
|
||||
"uuid": "76be87bd-3422-43f9-93a0-e85a577e3061",
|
||||
"created_at": "2016-10-18T09:52:05Z",
|
||||
"strategy_uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
|
||||
"strategy": {
|
||||
"watcher_object.version": "1.0",
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.name": "StrategyPayload",
|
||||
"watcher_object.data": {
|
||||
"uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
|
||||
"created_at": "2016-10-18T09:52:05Z",
|
||||
"name": "TEST",
|
||||
"updated_at": null,
|
||||
"display_name": "test strategy",
|
||||
"parameters_spec": {},
|
||||
"deleted_at": null
|
||||
}
|
||||
},
|
||||
"updated_at": null,
|
||||
"deleted_at": null,
|
||||
"audit_uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
||||
"audit": {
|
||||
"watcher_object.version": "1.0",
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.name": "TerseAuditPayload",
|
||||
"watcher_object.data": {
|
||||
"parameters": {},
|
||||
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
||||
"goal_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
||||
"strategy_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
|
||||
"created_at": "2016-10-18T09:52:05Z",
|
||||
"scope": [],
|
||||
"updated_at": null,
|
||||
"audit_type": "ONESHOT",
|
||||
"interval": null,
|
||||
"deleted_at": null,
|
||||
"state": "PENDING"
|
||||
}
|
||||
},
|
||||
"global_efficacy": {},
|
||||
"state": "ONGOING"
|
||||
}
|
||||
},
|
||||
"timestamp": "2016-10-18 09:52:05.219414"
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
{
|
||||
"event_type": "action_plan.execution.start",
|
||||
"payload": {
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.name": "ActionPlanActionPayload",
|
||||
"watcher_object.version": "1.0",
|
||||
"watcher_object.data": {
|
||||
"created_at": "2016-10-18T09:52:05Z",
|
||||
"deleted_at": null,
|
||||
"audit_uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
||||
"audit": {
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.name": "TerseAuditPayload",
|
||||
"watcher_object.version": "1.0",
|
||||
"watcher_object.data": {
|
||||
"created_at": "2016-10-18T09:52:05Z",
|
||||
"deleted_at": null,
|
||||
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
||||
"goal_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
||||
"strategy_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
|
||||
"scope": [],
|
||||
"audit_type": "ONESHOT",
|
||||
"state": "PENDING",
|
||||
"parameters": {},
|
||||
"interval": null,
|
||||
"updated_at": null
|
||||
}
|
||||
},
|
||||
"uuid": "76be87bd-3422-43f9-93a0-e85a577e3061",
|
||||
"fault": null,
|
||||
"state": "ONGOING",
|
||||
"global_efficacy": {},
|
||||
"strategy_uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
|
||||
"strategy": {
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.name": "StrategyPayload",
|
||||
"watcher_object.version": "1.0",
|
||||
"watcher_object.data": {
|
||||
"created_at": "2016-10-18T09:52:05Z",
|
||||
"deleted_at": null,
|
||||
"name": "TEST",
|
||||
"uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
|
||||
"parameters_spec": {},
|
||||
"display_name": "test strategy",
|
||||
"updated_at": null
|
||||
}
|
||||
},
|
||||
"updated_at": null
|
||||
}
|
||||
},
|
||||
"priority": "INFO",
|
||||
"message_id": "3984dc2b-8aef-462b-a220-8ae04237a56e",
|
||||
"timestamp": "2016-10-18 09:52:05.219414",
|
||||
"publisher_id": "infra-optim:node0"
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
{
|
||||
"payload": {
|
||||
"watcher_object.version": "1.0",
|
||||
"watcher_object.data": {
|
||||
"audit_uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
||||
"audit": {
|
||||
"watcher_object.version": "1.0",
|
||||
"watcher_object.data": {
|
||||
"audit_type": "ONESHOT",
|
||||
"scope": [],
|
||||
"created_at": "2016-10-18T09:52:05Z",
|
||||
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
|
||||
"goal_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
||||
"strategy_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
|
||||
"interval": null,
|
||||
"updated_at": null,
|
||||
"state": "PENDING",
|
||||
"deleted_at": null,
|
||||
"parameters": {}
|
||||
},
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.name": "TerseAuditPayload"
|
||||
},
|
||||
"created_at": "2016-10-18T09:52:05Z",
|
||||
"uuid": "76be87bd-3422-43f9-93a0-e85a577e3061",
|
||||
"updated_at": null,
|
||||
"state_update": {
|
||||
"watcher_object.version": "1.0",
|
||||
"watcher_object.data": {
|
||||
"old_state": "PENDING",
|
||||
"state": "ONGOING"
|
||||
},
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.name": "ActionPlanStateUpdatePayload"
|
||||
},
|
||||
"state": "ONGOING",
|
||||
"deleted_at": null,
|
||||
"strategy_uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
|
||||
"strategy": {
|
||||
"watcher_object.version": "1.0",
|
||||
"watcher_object.data": {
|
||||
"name": "TEST",
|
||||
"uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
|
||||
"display_name": "test strategy",
|
||||
"created_at": "2016-10-18T09:52:05Z",
|
||||
"updated_at": null,
|
||||
"deleted_at": null,
|
||||
"parameters_spec": {}
|
||||
},
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.name": "StrategyPayload"
|
||||
},
|
||||
"global_efficacy": {}
|
||||
},
|
||||
"watcher_object.namespace": "watcher",
|
||||
"watcher_object.name": "ActionPlanUpdatePayload"
|
||||
},
|
||||
"publisher_id": "infra-optim:node0",
|
||||
"priority": "INFO",
|
||||
"timestamp": "2016-10-18 09:52:05.219414",
|
||||
"event_type": "action_plan.update",
|
||||
"message_id": "0a8a7329-fd5a-4ec6-97d7-2b776ce51a4c"
|
||||
}
|
||||
@@ -1,71 +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_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
||||
"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_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
|
||||
"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,71 +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_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
||||
"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_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
|
||||
"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,72 +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_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
||||
"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_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
|
||||
"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,82 +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_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
||||
"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_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
|
||||
"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,72 +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_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
||||
"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_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
|
||||
"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,72 +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_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
||||
"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_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
|
||||
"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,82 +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_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
||||
"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_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
|
||||
"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,72 +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_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
||||
"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_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
|
||||
"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,80 +0,0 @@
|
||||
{
|
||||
"publisher_id": "infra-optim:localhost",
|
||||
"timestamp": "2016-11-04 16:51:38.722986 ",
|
||||
"payload": {
|
||||
"watcher_object.name": "AuditUpdatePayload",
|
||||
"watcher_object.data": {
|
||||
"strategy_uuid": "75234dfe-87e3-4f11-a0e0-3c3305d86a39",
|
||||
"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_uuid": "bc830f84-8ae3-4fc6-8bc6-e3dd15e8b49a",
|
||||
"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"
|
||||
}
|
||||
@@ -21,7 +21,7 @@ Overview
|
||||
Below you will find a diagram, showing the main components of Watcher:
|
||||
|
||||
.. image:: ./images/architecture.svg
|
||||
:width: 110%
|
||||
:width: 100%
|
||||
|
||||
|
||||
.. _components_definition:
|
||||
@@ -37,12 +37,13 @@ AMQP Bus
|
||||
The AMQP message bus handles internal asynchronous communications between the
|
||||
different Watcher components.
|
||||
|
||||
.. _cluster_datasource_definition:
|
||||
.. _cluster_history_db_definition:
|
||||
|
||||
Datasource
|
||||
----------
|
||||
Cluster History Database
|
||||
------------------------
|
||||
|
||||
This component stores the metrics related to the cluster.
|
||||
This component stores the data related to the
|
||||
:ref:`Cluster History <cluster_history_definition>`.
|
||||
|
||||
It can potentially rely on any appropriate storage system (InfluxDB, OpenTSDB,
|
||||
MongoDB,...) but will probably be more performant when using
|
||||
@@ -50,6 +51,14 @@ MongoDB,...) but will probably be more performant when using
|
||||
which are optimized for handling time series data, which are arrays of numbers
|
||||
indexed by time (a datetime or a datetime range).
|
||||
|
||||
.. _cluster_model_db_definition:
|
||||
|
||||
Cluster Model Database
|
||||
------------------------
|
||||
|
||||
This component stores the data related to the
|
||||
:ref:`Cluster Data Model <cluster_data_model_definition>`.
|
||||
|
||||
.. _archi_watcher_api_definition:
|
||||
|
||||
Watcher API
|
||||
@@ -184,8 +193,8 @@ data:
|
||||
:ref:`Managed resources <managed_resource_definition>` (e.g., the data stored
|
||||
in the Nova database). These models gives a strategy the ability to reason on
|
||||
the current state of a given :ref:`cluster <cluster_definition>`.
|
||||
- The data stored in the :ref:`Cluster Datasource
|
||||
<cluster_datasource_definition>` which provides information about the past of
|
||||
- The data stored in the :ref:`Cluster History Database
|
||||
<cluster_history_db_definition>` which provides information about the past of
|
||||
the :ref:`Cluster <cluster_definition>`.
|
||||
|
||||
Here below is a sequence diagram showing how the Decision Engine builds and
|
||||
@@ -289,7 +298,7 @@ The :ref:`Watcher Decision Engine <watcher_decision_engine_definition>` also
|
||||
builds the :ref:`Cluster Data Model <cluster_data_model_definition>`. This
|
||||
data model is needed by the :ref:`Strategy <strategy_definition>` to know the
|
||||
current state and topology of the audited
|
||||
:ref:`OpenStack cluster <cluster_definition>`.
|
||||
:ref:`Openstack cluster <cluster_definition>`.
|
||||
|
||||
The :ref:`Watcher Decision Engine <watcher_decision_engine_definition>` calls
|
||||
the **execute()** method of the instantiated
|
||||
@@ -307,7 +316,7 @@ This method finds an appropriate scheduling of
|
||||
:ref:`Actions <action_definition>` taking into account some scheduling rules
|
||||
(such as priorities between actions).
|
||||
It generates a new :ref:`Action Plan <action_plan_definition>` with status
|
||||
**RECOMMENDED** and saves it into the :ref:`Watcher Database
|
||||
**RECOMMENDED** and saves it into the:ref:`Watcher Database
|
||||
<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
|
||||
:ref:`Efficacy Indicators <efficacy_indicator_definition>` as specified by the
|
||||
@@ -441,12 +450,8 @@ state may be one of the following:
|
||||
stored in the :ref:`Watcher database <watcher_database_definition>` but is
|
||||
not returned any more through the Watcher APIs.
|
||||
- **CANCELLED** : the :ref:`Action Plan <action_plan_definition>` was in
|
||||
**RECOMMENDED**, **PENDING** or **ONGOING** state and was cancelled by the
|
||||
**PENDING** or **ONGOING** state and was cancelled by the
|
||||
:ref:`Administrator <administrator_definition>`
|
||||
- **SUPERSEDED** : the :ref:`Action Plan <action_plan_definition>` was in
|
||||
RECOMMENDED state and was automatically superseded by Watcher, due to an
|
||||
expiration delay or an update of the
|
||||
:ref:`Cluster data model <cluster_data_model_definition>`
|
||||
|
||||
|
||||
The following diagram shows the different possible states of an
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 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
|
||||
@@ -10,21 +11,7 @@
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from watcher import version as watcher_version
|
||||
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 ----------------------------------------------------
|
||||
|
||||
@@ -39,8 +26,7 @@ extensions = [
|
||||
'sphinxcontrib.pecanwsme.rest',
|
||||
'stevedore.sphinxext',
|
||||
'wsmeext.sphinxext',
|
||||
'ext.term',
|
||||
'ext.versioned_notifications',
|
||||
'watcher.doc',
|
||||
]
|
||||
|
||||
wsme_protocols = ['restjson']
|
||||
@@ -81,8 +67,6 @@ exclude_patterns = [
|
||||
# them when scanning for input files.
|
||||
'man/footer.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.
|
||||
|
||||
@@ -15,7 +15,7 @@ Service overview
|
||||
================
|
||||
|
||||
The Watcher system is a collection of services that provides support to
|
||||
optimize your IAAS platform. The Watcher service may, depending upon
|
||||
optimize your IAAS plateform. The Watcher service may, depending upon
|
||||
configuration, interact with several other OpenStack services. This includes:
|
||||
|
||||
- the OpenStack Identity service (`keystone`_) for request authentication and
|
||||
@@ -37,7 +37,7 @@ The Watcher service includes the following components:
|
||||
- `watcher-dashboard`_: An Horizon plugin for interacting with the Watcher
|
||||
service.
|
||||
|
||||
Additionally, the Watcher service has certain external dependencies, which
|
||||
Additionally, the Bare Metal service has certain external dependencies, which
|
||||
are very similar to other OpenStack services:
|
||||
|
||||
- A database to store audit and action plan information and state. You can set
|
||||
@@ -86,6 +86,7 @@ Configure the Identity service for the Watcher service
|
||||
--tenant=KEYSTONE_SERVICE_PROJECT_NAME
|
||||
$ keystone user-role-add --user=watcher \
|
||||
--tenant=KEYSTONE_SERVICE_PROJECT_NAME --role=admin
|
||||
$ keystone user-role-add --user=watcher --tenant=admin --role=admin
|
||||
|
||||
or (by using python-openstackclient 1.8.0+)
|
||||
|
||||
@@ -96,6 +97,7 @@ Configure the Identity service for the Watcher service
|
||||
--project=KEYSTONE_SERVICE_PROJECT_NAME
|
||||
$ openstack role add --project KEYSTONE_SERVICE_PROJECT_NAME \
|
||||
--user watcher admin
|
||||
$ openstack role add --user watcher --project admin admin
|
||||
|
||||
|
||||
#. You must register the Watcher Service with the Identity Service so that
|
||||
@@ -165,9 +167,9 @@ You can easily generate and update a sample configuration file
|
||||
named :ref:`watcher.conf.sample <watcher_sample_configuration_files>` by using
|
||||
these following commands::
|
||||
|
||||
$ git clone https://git.openstack.org/openstack/watcher
|
||||
$ git clone git://git.openstack.org/openstack/watcher
|
||||
$ cd watcher/
|
||||
$ tox -e genconfig
|
||||
$ tox -econfig
|
||||
$ vi etc/watcher/watcher.conf.sample
|
||||
|
||||
|
||||
@@ -366,7 +368,7 @@ Configure Nova compute
|
||||
Please check your hypervisor configuration to correctly handle
|
||||
`instance migration`_.
|
||||
|
||||
.. _`instance migration`: http://docs.openstack.org/admin-guide/compute-live-migration-usage.html
|
||||
.. _`instance migration`: http://docs.openstack.org/admin-guide-cloud/compute-configuring-migrations.html
|
||||
|
||||
Configure Measurements
|
||||
======================
|
||||
|
||||
@@ -93,11 +93,11 @@ following command:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
$ watcher strategy list --goal <your_goal_uuid_or_name>
|
||||
$ watcher strategy list --goal-uuid <your_goal_uuid>
|
||||
|
||||
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
|
||||
parameters of which format it supports:
|
||||
|
||||
@@ -19,7 +19,7 @@ model. To enable the Watcher plugin with DevStack, add the following to the
|
||||
`[[local|localrc]]` section of your controller's `local.conf` to enable the
|
||||
Watcher plugin::
|
||||
|
||||
enable_plugin watcher https://git.openstack.org/openstack/watcher
|
||||
enable_plugin watcher git://git.openstack.org/openstack/watcher
|
||||
|
||||
For more detailed instructions, see `Detailed DevStack Instructions`_. Check
|
||||
out the `DevStack documentation`_ for more information regarding DevStack.
|
||||
@@ -193,37 +193,6 @@ must exist in every other compute node's stack user's authorized_keys file and
|
||||
every compute node's public ECDSA key needs to be in every other compute
|
||||
node's root user's known_hosts file.
|
||||
|
||||
Disable serial console
|
||||
----------------------
|
||||
|
||||
Serial console needs to be disabled for live migration to work.
|
||||
|
||||
On both the controller and compute node, in /etc/nova/nova.conf
|
||||
|
||||
[serial_console]
|
||||
enabled = False
|
||||
|
||||
Alternatively, in devstack's local.conf:
|
||||
|
||||
[[post-config|$NOVA_CONF]]
|
||||
[serial_console]
|
||||
#enabled=false
|
||||
|
||||
|
||||
VNC server configuration
|
||||
------------------------
|
||||
|
||||
The VNC server listening parameter needs to be set to any address so
|
||||
that the server can accept connections from all of the compute nodes.
|
||||
|
||||
On both the controller and compute node, in /etc/nova/nova.conf
|
||||
|
||||
vncserver_listen = 0.0.0.0
|
||||
|
||||
Alternatively, in devstack's local.conf:
|
||||
|
||||
VNCSERVER_LISTEN=0.0.0.0
|
||||
|
||||
|
||||
Environment final checkup
|
||||
-------------------------
|
||||
|
||||
@@ -85,9 +85,6 @@ your platform.
|
||||
|
||||
$ sudo yum install openssl-devel libffi-devel mysql-devel
|
||||
|
||||
* CentOS 7::
|
||||
|
||||
$ sudo yum install gcc python-devel libxml2-devel libxslt-devel mariadb-devel
|
||||
|
||||
PyPi Packages and VirtualEnv
|
||||
----------------------------
|
||||
|
||||
@@ -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::
|
||||
@@ -30,12 +30,12 @@ implement:
|
||||
implement. This is the first function to be called by the
|
||||
:ref:`applier <watcher_applier_definition>` before any further processing
|
||||
and its role is to validate the input parameters that were provided to it.
|
||||
- The :py:meth:`~.BaseAction.pre_condition` is called before the execution of
|
||||
- The :py:meth:`~.BaseAction.precondition` is called before the execution of
|
||||
an action. This method is a hook that can be used to perform some
|
||||
initializations or to make some more advanced validation on its input
|
||||
parameters. If you wish to block the execution based on this factor, you
|
||||
simply have to ``raise`` an exception.
|
||||
- The :py:meth:`~.BaseAction.post_condition` is called after the execution of
|
||||
- The :py:meth:`~.BaseAction.postcondition` is called after the execution of
|
||||
an action. As this function is called regardless of whether an action
|
||||
succeeded or not, this can prove itself useful to perform cleanup
|
||||
operations.
|
||||
@@ -71,11 +71,11 @@ Here is an example showing how you can write a plugin called ``DummyAction``:
|
||||
# Does nothing
|
||||
pass
|
||||
|
||||
def pre_condition(self):
|
||||
def precondition(self):
|
||||
# No pre-checks are done here
|
||||
pass
|
||||
|
||||
def post_condition(self):
|
||||
def postcondition(self):
|
||||
# Nothing done here
|
||||
pass
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ cluster data model collectors within Watcher.
|
||||
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.
|
||||
- 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,
|
||||
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
|
||||
===============================
|
||||
|
||||
@@ -268,5 +225,5 @@ At this point, you can use your new cluster data model plugin in your
|
||||
# [...]
|
||||
dummy_collector = self.collector_manager.get_cluster_model_collector(
|
||||
"dummy") # "dummy" is the name of the entry point we declared earlier
|
||||
dummy_model = dummy_collector.get_latest_cluster_data_model()
|
||||
dummy_model = collector.get_latest_cluster_data_model()
|
||||
# Do some stuff with this model
|
||||
|
||||
@@ -60,8 +60,8 @@ Here is an example showing how you can define a new ``NewGoal`` goal plugin:
|
||||
# import path: thirdparty.new
|
||||
|
||||
from watcher._i18n import _
|
||||
from watcher.decision_engine.goal import base
|
||||
from watcher.decision_engine.goal.efficacy import specs
|
||||
from watcher.decision_engine.strategy.strategies import base
|
||||
|
||||
class NewGoal(base.Goal):
|
||||
|
||||
@@ -79,11 +79,11 @@ Here is an example showing how you can define a new ``NewGoal`` goal plugin:
|
||||
|
||||
@classmethod
|
||||
def get_efficacy_specification(cls):
|
||||
return specs.Unclassified()
|
||||
return specs.UnclassifiedStrategySpecification()
|
||||
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
@@ -39,7 +39,7 @@ Here is an example showing how you can write a planner plugin called
|
||||
|
||||
# Filepath = third-party/third_party/dummy.py
|
||||
# Import path = third_party.dummy
|
||||
from oslo_utils import uuidutils
|
||||
import uuid
|
||||
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):
|
||||
action_plan_dict = {
|
||||
'uuid': uuidutils.generate_uuid(),
|
||||
'uuid': uuid.uuid4(),
|
||||
'audit_id': audit_id,
|
||||
'first_action_id': None,
|
||||
'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
|
||||
automatically collected by the translation tool.
|
||||
- Implement its :py:meth:`~.BaseStrategy.get_translatable_display_name`
|
||||
class method to return the translation key (actually the English display
|
||||
class method to return the translation key (actually the english display
|
||||
name) of your new strategy. The value return should be the same as the
|
||||
string translated in :py:meth:`~.BaseStrategy.get_display_name`.
|
||||
- Implement its :py:meth:`~.BaseStrategy.execute` method to return the
|
||||
@@ -245,70 +245,75 @@ Querying metrics
|
||||
|
||||
A large set of metrics, generated by OpenStack modules, can be used in your
|
||||
strategy implementation. To collect these metrics, Watcher provides a
|
||||
`Helper`_ for two data sources which are `Ceilometer`_ and `Monasca`_. If you
|
||||
wish to query metrics from a different data source, you can implement your own
|
||||
and directly use it from within your new strategy. Indeed, strategies in
|
||||
Watcher have the cluster data models decoupled from the data sources which
|
||||
means that you may keep the former while changing the latter.
|
||||
The recommended way for you to support a new data source is to implement a new
|
||||
helper that would encapsulate within separate methods the queries you need to
|
||||
perform. To then use it, you would just have to instantiate it within your
|
||||
strategy.
|
||||
`Helper`_ to the Ceilometer API, which makes this API reusable and easier
|
||||
to used.
|
||||
|
||||
If you want to use Ceilometer but with your own metrics database backend,
|
||||
please refer to the `Ceilometer developer guide`_. The list of the available
|
||||
Ceilometer backends is located here_. The `Ceilosca`_ project is a good example
|
||||
of how to create your own pluggable backend. Moreover, if your strategy
|
||||
requires new metrics not covered by Ceilometer, you can add them through a
|
||||
`Ceilometer plugin`_.
|
||||
If you want to use your own metrics database backend, please refer to the
|
||||
`Ceilometer developer guide`_. Indeed, Ceilometer's pluggable model allows
|
||||
for various types of backends. A list of the available backends is located
|
||||
here_. The Ceilosca project is a good example of how to create your own
|
||||
pluggable backend.
|
||||
|
||||
Finally, if your strategy requires new metrics not covered by Ceilometer, you
|
||||
can add them through a Ceilometer `plugin`_.
|
||||
|
||||
.. _`Helper`: https://github.com/openstack/watcher/blob/master/watcher/decision_engine/cluster/history/ceilometer.py
|
||||
.. _`Ceilometer developer guide`: http://docs.openstack.org/developer/ceilometer/architecture.html#storing-the-data
|
||||
.. _`Ceilometer`: http://docs.openstack.org/developer/ceilometer/
|
||||
.. _`Monasca`: https://github.com/openstack/monasca-api/blob/master/docs/monasca-api-spec.md
|
||||
.. _`here`: http://docs.openstack.org/developer/ceilometer/install/dbreco.html#choosing-a-database-backend
|
||||
.. _`Ceilometer plugin`: http://docs.openstack.org/developer/ceilometer/plugins.html
|
||||
.. _`plugin`: http://docs.openstack.org/developer/ceilometer/plugins.html
|
||||
.. _`Ceilosca`: https://github.com/openstack/monasca-ceilometer/blob/master/ceilosca/ceilometer/storage/impl_monasca.py
|
||||
|
||||
Read usage metrics using the Watcher Datasource Helper
|
||||
------------------------------------------------------
|
||||
|
||||
The following code snippet shows how to invoke a Datasource Helper class:
|
||||
Read usage metrics using the Python binding
|
||||
-------------------------------------------
|
||||
|
||||
You can find the information about the Ceilometer Python binding on the
|
||||
OpenStack `ceilometer client python API documentation
|
||||
<http://docs.openstack.org/developer/python-ceilometerclient/api.html>`_
|
||||
|
||||
To facilitate the process, Watcher provides the ``osc`` attribute to every
|
||||
strategy which includes clients to major OpenStack services, including
|
||||
Ceilometer. So to access it within your strategy, you can do the following:
|
||||
|
||||
.. code-block:: py
|
||||
|
||||
from watcher.datasource import ceilometer as ceil
|
||||
from watcher.datasource import monasca as mon
|
||||
|
||||
@property
|
||||
def ceilometer(self):
|
||||
if self._ceilometer is None:
|
||||
self._ceilometer = ceil.CeilometerHelper(osc=self.osc)
|
||||
return self._ceilometer
|
||||
|
||||
@property
|
||||
def monasca(self):
|
||||
if self._monasca is None:
|
||||
self._monasca = mon.MonascaHelper(osc=self.osc)
|
||||
return self._monasca
|
||||
# Within your strategy "execute()"
|
||||
cclient = self.osc.ceilometer
|
||||
# TODO: Do something here
|
||||
|
||||
Using that you can now query the values for that specific metric:
|
||||
|
||||
.. code-block:: py
|
||||
|
||||
if self.config.datasource == "ceilometer":
|
||||
resource_id = "%s_%s" % (node.uuid, node.hostname)
|
||||
return self.ceilometer.statistic_aggregation(
|
||||
resource_id=resource_id,
|
||||
meter_name='compute.node.cpu.percent',
|
||||
period="7200",
|
||||
aggregate='avg',
|
||||
)
|
||||
elif self.config.datasource == "monasca":
|
||||
statistics = self.monasca.statistic_aggregation(
|
||||
meter_name='compute.node.cpu.percent',
|
||||
dimensions=dict(hostname=node.uuid),
|
||||
period=7200,
|
||||
aggregate='avg'
|
||||
)
|
||||
query = None # e.g. [{'field': 'foo', 'op': 'le', 'value': 34},]
|
||||
value_cpu = cclient.samples.list(
|
||||
meter_name='cpu_util',
|
||||
limit=10, q=query)
|
||||
|
||||
|
||||
Read usage metrics using the Watcher Cluster History Helper
|
||||
-----------------------------------------------------------
|
||||
|
||||
Here below is the abstract ``BaseClusterHistory`` class of the Helper.
|
||||
|
||||
.. autoclass:: watcher.decision_engine.cluster.history.base.BaseClusterHistory
|
||||
:members:
|
||||
:noindex:
|
||||
|
||||
The following code snippet shows how to create a Cluster History class:
|
||||
|
||||
.. code-block:: py
|
||||
|
||||
from watcher.decision_engine.cluster.history import ceilometer as ceil
|
||||
|
||||
query_history = ceil.CeilometerClusterHistory()
|
||||
|
||||
Using that you can now query the values for that specific metric:
|
||||
|
||||
.. code-block:: py
|
||||
|
||||
query_history.statistic_aggregation(resource_id=compute_node.uuid,
|
||||
meter_name='compute.node.cpu.percent',
|
||||
period="7200",
|
||||
aggregate='avg'
|
||||
)
|
||||
|
||||
@@ -96,11 +96,17 @@ The :ref:`Cluster <cluster_definition>` may be divided in one or several
|
||||
|
||||
.. _cluster_data_model_definition:
|
||||
|
||||
Cluster Data Model (CDM)
|
||||
========================
|
||||
Cluster Data Model
|
||||
==================
|
||||
|
||||
.. watcher-term:: watcher.decision_engine.model.collector.base
|
||||
|
||||
.. _cluster_history_definition:
|
||||
|
||||
Cluster History
|
||||
===============
|
||||
|
||||
.. watcher-term:: watcher.decision_engine.cluster.history.base
|
||||
|
||||
.. _controller_node_definition:
|
||||
|
||||
@@ -126,7 +132,7 @@ 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:
|
||||
|
||||
@@ -158,8 +164,7 @@ Goal
|
||||
Host Aggregate
|
||||
==============
|
||||
|
||||
Please, read `the official OpenStack definition of a Host Aggregate
|
||||
<http://docs.openstack.org/developer/nova/aggregates.html>`_.
|
||||
Please, read `the official OpenStack definition of a Host Aggregate <http://docs.openstack.org/developer/nova/aggregates.html>`_.
|
||||
|
||||
.. _instance_definition:
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 58 KiB |
@@ -9,10 +9,8 @@ FAILED --> DELETED : Administrator removes\nAction Plan
|
||||
SUCCEEDED --> DELETED : Administrator removes\nAction Plan
|
||||
ONGOING --> CANCELLED : Administrator cancels\nAction Plan
|
||||
RECOMMENDED --> CANCELLED : Administrator cancels\nAction Plan
|
||||
RECOMMENDED --> SUPERSEDED : The Watcher Decision Engine supersedes\nAction Plan
|
||||
PENDING --> CANCELLED : Administrator cancels\nAction Plan
|
||||
CANCELLED --> DELETED
|
||||
SUPERSEDED --> DELETED
|
||||
DELETED --> [*]
|
||||
|
||||
@enduml
|
||||
|
||||
@@ -4,7 +4,7 @@ actor Administrator
|
||||
|
||||
== 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
|
||||
Administrator <-- Watcher : Audit Template UUID
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 72 KiB |
@@ -41,7 +41,9 @@ table(audit_templates) {
|
||||
uuid : String[36]
|
||||
name : String[63], nullable
|
||||
description : String[255], nullable
|
||||
scope : JSONEncodedList
|
||||
host_aggregate : Integer, nullable
|
||||
extra : JSONEncodedDict
|
||||
version : String[15], nullable
|
||||
|
||||
created_at : DateTime
|
||||
updated_at : DateTime
|
||||
@@ -57,10 +59,10 @@ table(audits) {
|
||||
uuid : String[36]
|
||||
audit_type : String[20]
|
||||
state : String[20], nullable
|
||||
deadline : DateTime, nullable
|
||||
interval : Integer, nullable
|
||||
parameters : JSONEncodedDict, nullable
|
||||
scope : JSONEncodedList, nullable
|
||||
auto_trigger: Boolean
|
||||
host_aggregate : Integer, nullable
|
||||
|
||||
created_at : DateTime
|
||||
updated_at : DateTime
|
||||
@@ -74,6 +76,7 @@ table(action_plans) {
|
||||
foreign_key("audit_id : Integer, nullable")
|
||||
foreign_key("strategy_id : Integer")
|
||||
uuid : String[36]
|
||||
first_action_id : Integer
|
||||
state : String[20], nullable
|
||||
global_efficacy : JSONEncodedDict, nullable
|
||||
|
||||
@@ -91,7 +94,7 @@ table(actions) {
|
||||
action_type : String[255]
|
||||
input_parameters : JSONEncodedDict, nullable
|
||||
state : String[20], nullable
|
||||
parents : JSONEncodedList, nullable
|
||||
next : String[36], nullable
|
||||
|
||||
created_at : DateTime
|
||||
updated_at : DateTime
|
||||
@@ -128,18 +131,6 @@ table(scoring_engines) {
|
||||
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" <.. "audit_templates" : Foreign Key
|
||||
"strategies" <.. "audit_templates" : Foreign Key
|
||||
|
||||
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 56 KiB |
@@ -56,7 +56,6 @@ Getting Started
|
||||
dev/devstack
|
||||
deploy/configuration
|
||||
deploy/conf-files
|
||||
dev/notifications
|
||||
dev/testing
|
||||
dev/rally_link
|
||||
|
||||
@@ -97,7 +96,6 @@ Introduction
|
||||
deploy/user-guide
|
||||
deploy/policy
|
||||
deploy/gmr
|
||||
strategies/strategies
|
||||
|
||||
Watcher Manual Pages
|
||||
====================
|
||||
|
||||
@@ -1,99 +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
|
||||
``period`` Number 7200 The time interval in seconds
|
||||
for getting statistic aggregation
|
||||
from metric data source
|
||||
====================== ====== ============= ===================================
|
||||
|
||||
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 server_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.
|
||||
@@ -1,4 +1,4 @@
|
||||
To generate the sample watcher.conf file, run the following
|
||||
command from the top level of the watcher directory:
|
||||
|
||||
tox -e genconfig
|
||||
tox -econfig
|
||||
@@ -37,9 +37,5 @@
|
||||
|
||||
"strategy:detail": "rule:default",
|
||||
"strategy:get": "rule:default",
|
||||
"strategy:get_all": "rule:default",
|
||||
|
||||
"service:detail": "rule:default",
|
||||
"service:get": "rule:default",
|
||||
"service:get_all": "rule:default"
|
||||
"strategy: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
|
||||
---------
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
name: "dummy"
|
||||
strategy:
|
||||
name: "dummy"
|
||||
extra: {}
|
||||
sla:
|
||||
failure_rate:
|
||||
max: 0
|
||||
@@ -28,6 +29,7 @@
|
||||
name: "dummy"
|
||||
strategy:
|
||||
name: "dummy"
|
||||
extra: {}
|
||||
runner:
|
||||
type: "constant"
|
||||
times: 10
|
||||
@@ -54,10 +56,12 @@
|
||||
name: "workload_balancing"
|
||||
strategy:
|
||||
name: "workload_stabilization"
|
||||
extra: {}
|
||||
- goal:
|
||||
name: "dummy"
|
||||
strategy:
|
||||
name: "dummy"
|
||||
extra: {}
|
||||
sla:
|
||||
failure_rate:
|
||||
max: 0
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
---
|
||||
features:
|
||||
- Add notifications related to Action plan object.
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
features:
|
||||
- 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
|
||||
scoring engine by different Strategies, which
|
||||
improve the code and data model re-use.
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
---
|
||||
features:
|
||||
- Add notifications related to Audit object.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
features:
|
||||
- Watcher can continuously optimize the OpenStack cloud for a specific
|
||||
strategy or goal by triggering an audit periodically which generates
|
||||
an action plan and run it automatically.
|
||||
@@ -1,3 +0,0 @@
|
||||
---
|
||||
features:
|
||||
- Centralize all configuration options for Watcher.
|
||||
@@ -1,3 +0,0 @@
|
||||
---
|
||||
features:
|
||||
- Watcher database can now be upgraded thanks to Alembic.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
features:
|
||||
- Provides a generic way to define the scope of an audit. The set of audited
|
||||
resources will be called "Audit scope" and will be defined in each audit
|
||||
template (which contains the audit settings).
|
||||
@@ -2,4 +2,4 @@
|
||||
features:
|
||||
- Added a way to return the of available goals depending
|
||||
on which strategies have been deployed on the node
|
||||
where the decision engine is running.
|
||||
where the decison engine is running.
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
features:
|
||||
- The graph model describes how VMs are associated to compute hosts.
|
||||
This allows for seeing relationships upfront between the entities and hence
|
||||
can be used to identify hot/cold spots in the data center and influence
|
||||
a strategy decision.
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
features:
|
||||
- Watcher supports multiple metrics backend and relies on Ceilometer and
|
||||
Monasca.
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
features:
|
||||
- Watcher can now run specific actions in parallel improving the performances
|
||||
dramatically when executing an action plan.
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
features:
|
||||
- Add superseded state for an action plan if the cluster data model has
|
||||
changed after it has been created.
|
||||
@@ -1,8 +0,0 @@
|
||||
---
|
||||
features:
|
||||
- Provide a notification mechanism into Watcher that supports versioning.
|
||||
Whenever a Watcher object is created, updated or deleted, a versioned
|
||||
notification will, if it's relevant, be automatically sent to notify in order
|
||||
to allow an event-driven style of architecture within Watcher. Moreover, it
|
||||
will also give other services and/or 3rd party softwares (e.g. monitoring
|
||||
solutions or rules engines) the ability to react to such events.
|
||||
@@ -1,3 +0,0 @@
|
||||
---
|
||||
features:
|
||||
- Add a service supervisor to watch Watcher deamons.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
features:
|
||||
- all Watcher objects have been refactored to support OVO
|
||||
(oslo.versionedobjects) which was a prerequisite step in order to implement
|
||||
versioned notifications.
|
||||
@@ -1,3 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# watcher documentation build configuration file, created by
|
||||
# sphinx-quickstart on Fri Jun 3 11:37:52 2016.
|
||||
#
|
||||
@@ -240,6 +242,3 @@ texinfo_documents = [
|
||||
|
||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||
#texinfo_show_urls = 'footnote'
|
||||
|
||||
# -- Options for Internationalization output ------------------------------
|
||||
locale_dirs = ['locale/']
|
||||
|
||||
@@ -6,6 +6,5 @@ Contents:
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
unreleased
|
||||
newton
|
||||
unreleased.rst
|
||||
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
===================================
|
||||
Newton Series Release Notes
|
||||
===================================
|
||||
|
||||
.. release-notes::
|
||||
:branch: origin/stable/newton
|
||||
@@ -5,41 +5,37 @@
|
||||
apscheduler # MIT License
|
||||
enum34;python_version=='2.7' or python_version=='2.6' or python_version=='3.3' # BSD
|
||||
jsonpatch>=1.1 # BSD
|
||||
keystoneauth1>=2.18.0 # Apache-2.0
|
||||
keystonemiddleware>=4.12.0 # Apache-2.0
|
||||
lxml!=3.7.0,>=2.3 # BSD
|
||||
keystoneauth1>=2.10.0 # Apache-2.0
|
||||
keystonemiddleware!=4.1.0,!=4.5.0,>=4.0.0 # Apache-2.0
|
||||
lxml>=2.3 # BSD
|
||||
oslo.concurrency>=3.8.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.db>=4.15.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.log>=3.11.0 # Apache-2.0
|
||||
oslo.messaging>=5.14.0 # Apache-2.0
|
||||
oslo.policy>=1.17.0 # Apache-2.0
|
||||
oslo.log>=1.14.0 # Apache-2.0
|
||||
oslo.messaging>=5.2.0 # Apache-2.0
|
||||
oslo.policy>=1.9.0 # Apache-2.0
|
||||
oslo.reports>=0.6.0 # Apache-2.0
|
||||
oslo.serialization>=1.10.0 # Apache-2.0
|
||||
oslo.service>=1.10.0 # Apache-2.0
|
||||
oslo.utils>=3.18.0 # Apache-2.0
|
||||
oslo.versionedobjects>=1.17.0 # Apache-2.0
|
||||
oslo.utils>=3.16.0 # Apache-2.0
|
||||
PasteDeploy>=1.5.0 # MIT
|
||||
pbr>=1.8 # Apache-2.0
|
||||
pecan!=1.0.2,!=1.0.3,!=1.0.4,!=1.2,>=1.0.0 # BSD
|
||||
PrettyTable<0.8,>=0.7.1 # BSD
|
||||
pbr>=1.6 # Apache-2.0
|
||||
pecan!=1.0.2,!=1.0.3,!=1.0.4,>=1.0.0 # BSD
|
||||
PrettyTable<0.8,>=0.7 # BSD
|
||||
voluptuous>=0.8.9 # BSD License
|
||||
python-ceilometerclient>=2.5.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-keystoneclient>=3.8.0 # Apache-2.0
|
||||
python-monascaclient>=1.1.0 # Apache-2.0
|
||||
python-glanceclient!=2.4.0,>=2.3.0 # Apache-2.0
|
||||
python-keystoneclient!=2.1.0,>=2.0.0 # Apache-2.0
|
||||
python-neutronclient>=5.1.0 # Apache-2.0
|
||||
python-novaclient!=7.0.0,>=6.0.0 # Apache-2.0
|
||||
python-openstackclient>=3.3.0 # Apache-2.0
|
||||
python-novaclient!=2.33.0,>=2.29.0 # Apache-2.0
|
||||
python-openstackclient>=2.1.0 # Apache-2.0
|
||||
six>=1.9.0 # MIT
|
||||
SQLAlchemy<1.1.0,>=1.0.10 # MIT
|
||||
stevedore>=1.17.1 # Apache-2.0
|
||||
taskflow>=2.7.0 # Apache-2.0
|
||||
WebOb>=1.6.0 # MIT
|
||||
stevedore>=1.16.0 # Apache-2.0
|
||||
taskflow>=1.26.0 # Apache-2.0
|
||||
WebOb>=1.2.3 # MIT
|
||||
WSME>=0.8 # MIT
|
||||
networkx>=1.10 # BSD
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[metadata]
|
||||
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 =
|
||||
README.rst
|
||||
author = OpenStack
|
||||
@@ -32,7 +32,7 @@ setup-hooks =
|
||||
|
||||
[entry_points]
|
||||
oslo.config.opts =
|
||||
watcher = watcher.conf.opts:list_opts
|
||||
watcher = watcher.opts:list_opts
|
||||
|
||||
console_scripts =
|
||||
watcher-api = watcher.cmd.api:main
|
||||
@@ -64,7 +64,6 @@ watcher_scoring_engine_containers =
|
||||
watcher_strategies =
|
||||
dummy = watcher.decision_engine.strategy.strategies.dummy_strategy:DummyStrategy
|
||||
dummy_with_scorer = watcher.decision_engine.strategy.strategies.dummy_with_scorer:DummyWithScorer
|
||||
dummy_with_resize = watcher.decision_engine.strategy.strategies.dummy_with_resize:DummyWithResize
|
||||
basic = watcher.decision_engine.strategy.strategies.basic_consolidation:BasicConsolidation
|
||||
outlet_temperature = watcher.decision_engine.strategy.strategies.outlet_temp_control:OutletTempControl
|
||||
vm_workload_consolidation = watcher.decision_engine.strategy.strategies.vm_workload_consolidation:VMWorkloadConsolidation
|
||||
@@ -77,14 +76,12 @@ watcher_actions =
|
||||
nop = watcher.applier.actions.nop:Nop
|
||||
sleep = watcher.applier.actions.sleep:Sleep
|
||||
change_nova_service_state = watcher.applier.actions.change_nova_service_state:ChangeNovaServiceState
|
||||
resize = watcher.applier.actions.resize:Resize
|
||||
|
||||
watcher_workflow_engines =
|
||||
taskflow = watcher.applier.workflow_engine.default:DefaultWorkFlowEngine
|
||||
|
||||
watcher_planners =
|
||||
weight = watcher.decision_engine.planner.weight:WeightPlanner
|
||||
workload_stabilization = watcher.decision_engine.planner.workload_stabilization:WorkloadStabilizationPlanner
|
||||
default = watcher.decision_engine.planner.default:DefaultPlanner
|
||||
|
||||
watcher_cluster_data_model_collectors =
|
||||
compute = watcher.decision_engine.model.collector.nova:NovaClusterDataModelCollector
|
||||
|
||||
@@ -2,25 +2,25 @@
|
||||
# of appearance. Changing the order has an impact on the overall integration
|
||||
# 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
|
||||
freezegun>=0.3.6 # Apache-2.0
|
||||
freezegun # Apache-2.0
|
||||
hacking<0.11,>=0.10.2
|
||||
mock>=2.0 # BSD
|
||||
oslotest>=1.10.0 # Apache-2.0
|
||||
os-testr>=0.8.0 # Apache-2.0
|
||||
os-testr>=0.7.0 # Apache-2.0
|
||||
python-subunit>=0.0.18 # Apache-2.0/BSD
|
||||
testrepository>=0.0.18 # Apache-2.0/BSD
|
||||
testscenarios>=0.4 # Apache-2.0/BSD
|
||||
testtools>=1.4.0 # MIT
|
||||
|
||||
# Doc requirements
|
||||
oslosphinx>=4.7.0 # Apache-2.0
|
||||
sphinx!=1.3b1,<1.4,>=1.2.1 # BSD
|
||||
oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0
|
||||
sphinx!=1.3b1,<1.3,>=1.2.1 # BSD
|
||||
sphinxcontrib-pecanwsme>=0.8 # Apache-2.0
|
||||
|
||||
# releasenotes
|
||||
reno>=1.8.0 # Apache-2.0
|
||||
reno>=1.8.0 # Apache2
|
||||
|
||||
# bandit
|
||||
bandit>=1.1.0 # Apache-2.0
|
||||
|
||||
17
tox.ini
@@ -6,28 +6,29 @@ skipsdist = True
|
||||
[testenv]
|
||||
usedevelop = True
|
||||
whitelist_externals = find
|
||||
install_command = pip install -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt?h=stable/ocata} {opts} {packages}
|
||||
install_command =
|
||||
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 =
|
||||
VIRTUAL_ENV={envdir}
|
||||
deps = -r{toxinidir}/test-requirements.txt
|
||||
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}
|
||||
|
||||
[testenv:pep8]
|
||||
commands =
|
||||
doc8 doc/source/ CONTRIBUTING.rst HACKING.rst README.rst
|
||||
flake8
|
||||
bandit -r watcher -x tests -n5 -ll -s B320
|
||||
bandit -r watcher -x tests -n5 -ll
|
||||
|
||||
[testenv:venv]
|
||||
setenv = PYTHONHASHSEED=0
|
||||
commands = {posargs}
|
||||
|
||||
[testenv:cover]
|
||||
commands =
|
||||
python setup.py testr --coverage --testr-args='{posargs}'
|
||||
coverage report
|
||||
commands = python setup.py testr --coverage --testr-args='{posargs}'
|
||||
|
||||
[testenv:docs]
|
||||
setenv = PYTHONHASHSEED=0
|
||||
@@ -38,7 +39,7 @@ commands =
|
||||
[testenv:debug]
|
||||
commands = oslo_debug_helper -t watcher/tests {posargs}
|
||||
|
||||
[testenv:genconfig]
|
||||
[testenv:config]
|
||||
sitepackages = False
|
||||
commands =
|
||||
oslo-config-generator --config-file etc/watcher/watcher-config-generator.conf
|
||||
@@ -66,4 +67,4 @@ commands = sphinx-build -a -W -E -d releasenotes/build/doctrees -b html releasen
|
||||
|
||||
[testenv:bandit]
|
||||
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,3 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# 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
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
#
|
||||
# Copyright © 2012 New Dream Network, LLC (DreamHost)
|
||||
# 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
|
||||
@@ -18,23 +17,32 @@
|
||||
|
||||
"""Access Control Lists (ACL's) control access the API server."""
|
||||
|
||||
from oslo_config import cfg
|
||||
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):
|
||||
"""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 public_routes: The list of the routes which will be allowed to
|
||||
access without authentication.
|
||||
:return: The same WSGI application with ACL installed.
|
||||
|
||||
"""
|
||||
if not CONF.get('enable_authentication'):
|
||||
if not cfg.CONF.get('enable_authentication'):
|
||||
return app
|
||||
return auth_token.AuthTokenMiddleware(app,
|
||||
conf=dict(conf),
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
# Copyright © 2012 New Dream Network, LLC (DreamHost)
|
||||
# All Rights Reserved.
|
||||
# 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
|
||||
@@ -17,14 +16,49 @@
|
||||
# under the License.
|
||||
|
||||
|
||||
from oslo_config import cfg
|
||||
import pecan
|
||||
|
||||
from watcher._i18n import _
|
||||
from watcher.api import acl
|
||||
from watcher.api import config as api_config
|
||||
from watcher.api import middleware
|
||||
from watcher 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():
|
||||
|
||||
@@ -61,7 +61,7 @@ class Root(base.APIBase):
|
||||
root = Root()
|
||||
root.name = "OpenStack Watcher API"
|
||||
root.description = ("Watcher is an OpenStack project which aims to "
|
||||
"improve physical resources usage through "
|
||||
"to improve physical resources usage through "
|
||||
"better VM placement.")
|
||||
root.versions = [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 goal
|
||||
from watcher.api.controllers.v1 import scoring_engine
|
||||
from watcher.api.controllers.v1 import service
|
||||
from watcher.api.controllers.v1 import strategy
|
||||
|
||||
|
||||
@@ -106,9 +105,6 @@ class V1(APIBase):
|
||||
scoring_engines = [link.Link]
|
||||
"""Links to the Scoring Engines resource"""
|
||||
|
||||
services = [link.Link]
|
||||
"""Links to the services resource"""
|
||||
|
||||
links = [link.Link]
|
||||
"""Links that point to a specific URL for this version and documentation"""
|
||||
|
||||
@@ -163,14 +159,6 @@ class V1(APIBase):
|
||||
'scoring_engines', '',
|
||||
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
|
||||
|
||||
|
||||
@@ -183,7 +171,6 @@ class Controller(rest.RestController):
|
||||
action_plans = action_plan.ActionPlansController()
|
||||
goals = goal.GoalsController()
|
||||
scoring_engines = scoring_engine.ScoringEngineController()
|
||||
services = service.ServicesController()
|
||||
strategies = strategy.StrategiesController()
|
||||
|
||||
@wsme_pecan.wsexpose(V1)
|
||||
|
||||
@@ -41,7 +41,7 @@ be one of the following:
|
||||
processed by the :ref:`Watcher Applier <watcher_applier_definition>`
|
||||
- **SUCCEEDED** : the :ref:`Action <action_definition>` has been executed
|
||||
successfully
|
||||
- **FAILED** : an error occurred while trying to execute the
|
||||
- **FAILED** : an error occured while trying to execute the
|
||||
:ref:`Action <action_definition>`
|
||||
- **DELETED** : the :ref:`Action <action_definition>` is still stored in the
|
||||
:ref:`Watcher database <watcher_database_definition>` but is not returned
|
||||
@@ -88,6 +88,7 @@ class Action(base.APIBase):
|
||||
between the internal object model and the API representation of a action.
|
||||
"""
|
||||
_action_plan_uuid = None
|
||||
_next_uuid = None
|
||||
|
||||
def _get_action_plan_uuid(self):
|
||||
return self._action_plan_uuid
|
||||
@@ -104,6 +105,22 @@ class Action(base.APIBase):
|
||||
except exception.ActionPlanNotFound:
|
||||
self._action_plan_uuid = None
|
||||
|
||||
def _get_next_uuid(self):
|
||||
return self._next_uuid
|
||||
|
||||
def _set_next_uuid(self, value):
|
||||
if value == wtypes.Unset:
|
||||
self._next_uuid = wtypes.Unset
|
||||
elif value and self._next_uuid != value:
|
||||
try:
|
||||
action_next = objects.Action.get(
|
||||
pecan.request.context, value)
|
||||
self._next_uuid = action_next.uuid
|
||||
self.next = action_next.id
|
||||
except exception.ActionNotFound:
|
||||
self.action_next_uuid = None
|
||||
# raise e
|
||||
|
||||
uuid = wtypes.wsattr(types.uuid, readonly=True)
|
||||
"""Unique UUID for this action"""
|
||||
|
||||
@@ -121,8 +138,10 @@ class Action(base.APIBase):
|
||||
input_parameters = types.jsontype
|
||||
"""One or more key/value pairs """
|
||||
|
||||
parents = wtypes.wsattr(types.jsontype, readonly=True)
|
||||
"""UUIDs of parent actions"""
|
||||
next_uuid = wsme.wsproperty(types.uuid, _get_next_uuid,
|
||||
_set_next_uuid,
|
||||
mandatory=True)
|
||||
"""This next action UUID"""
|
||||
|
||||
links = wsme.wsattr([link.Link], readonly=True)
|
||||
"""A list containing a self link and associated action links"""
|
||||
@@ -133,6 +152,7 @@ class Action(base.APIBase):
|
||||
self.fields = []
|
||||
fields = list(objects.Action.fields)
|
||||
fields.append('action_plan_uuid')
|
||||
fields.append('next_uuid')
|
||||
for field in fields:
|
||||
# Skip fields we do not expose.
|
||||
if not hasattr(self, field):
|
||||
@@ -143,13 +163,15 @@ class Action(base.APIBase):
|
||||
self.fields.append('action_plan_id')
|
||||
setattr(self, 'action_plan_uuid', kwargs.get('action_plan_id',
|
||||
wtypes.Unset))
|
||||
setattr(self, 'next_uuid', kwargs.get('next',
|
||||
wtypes.Unset))
|
||||
|
||||
@staticmethod
|
||||
def _convert_with_links(action, url, expand=True):
|
||||
if not expand:
|
||||
action.unset_fields_except(['uuid', 'state', 'action_plan_uuid',
|
||||
'action_plan_id', 'action_type',
|
||||
'parents'])
|
||||
action.unset_fields_except(['uuid', 'state', 'next', 'next_uuid',
|
||||
'action_plan_uuid', 'action_plan_id',
|
||||
'action_type'])
|
||||
|
||||
action.links = [link.Link.make_link('self', url,
|
||||
'actions', action.uuid),
|
||||
@@ -171,9 +193,9 @@ class Action(base.APIBase):
|
||||
state='PENDING',
|
||||
created_at=datetime.datetime.utcnow(),
|
||||
deleted_at=None,
|
||||
updated_at=datetime.datetime.utcnow(),
|
||||
parents=[])
|
||||
updated_at=datetime.datetime.utcnow())
|
||||
sample._action_plan_uuid = '7ae81bb3-dec3-4289-8d6c-da80bd8001ae'
|
||||
sample._next_uuid = '7ae81bb3-dec3-4289-8d6c-da80bd8001ae'
|
||||
return cls._convert_with_links(sample, 'http://localhost:9322', expand)
|
||||
|
||||
|
||||
@@ -194,6 +216,17 @@ class ActionCollection(collection.Collection):
|
||||
collection.actions = [Action.convert_with_links(p, expand)
|
||||
for p in actions]
|
||||
|
||||
if 'sort_key' in kwargs:
|
||||
reverse = False
|
||||
if kwargs['sort_key'] == 'next_uuid':
|
||||
if 'sort_dir' in kwargs:
|
||||
reverse = True if kwargs['sort_dir'] == 'desc' else False
|
||||
collection.actions = sorted(
|
||||
collection.actions,
|
||||
key=lambda action: action.next_uuid or '',
|
||||
reverse=reverse)
|
||||
|
||||
collection.next = collection.get_next(limit, url=url, **kwargs)
|
||||
return collection
|
||||
|
||||
@classmethod
|
||||
@@ -235,7 +268,10 @@ class ActionsController(rest.RestController):
|
||||
if audit_uuid:
|
||||
filters['audit_uuid'] = audit_uuid
|
||||
|
||||
sort_db_key = sort_key
|
||||
if sort_key == 'next_uuid':
|
||||
sort_db_key = None
|
||||
else:
|
||||
sort_db_key = sort_key
|
||||
|
||||
actions = objects.Action.list(pecan.request.context,
|
||||
limit,
|
||||
@@ -343,7 +379,7 @@ class ActionsController(rest.RestController):
|
||||
action_dict = action.as_dict()
|
||||
context = pecan.request.context
|
||||
new_action = objects.Action(context, **action_dict)
|
||||
new_action.create()
|
||||
new_action.create(context)
|
||||
|
||||
# Set the HTTP Location Header
|
||||
pecan.response.location = link.build_url('actions', new_action.uuid)
|
||||
|
||||
@@ -106,7 +106,7 @@ class ActionPlanPatchType(types.JsonPatchType):
|
||||
|
||||
@staticmethod
|
||||
def mandatory_attrs():
|
||||
return ["audit_id", "state"]
|
||||
return ["audit_id", "state", "first_action_id"]
|
||||
|
||||
|
||||
class ActionPlan(base.APIBase):
|
||||
@@ -120,6 +120,7 @@ class ActionPlan(base.APIBase):
|
||||
_audit_uuid = None
|
||||
_strategy_uuid = None
|
||||
_strategy_name = None
|
||||
_first_action_uuid = None
|
||||
_efficacy_indicators = None
|
||||
|
||||
def _get_audit_uuid(self):
|
||||
@@ -136,6 +137,21 @@ class ActionPlan(base.APIBase):
|
||||
except exception.AuditNotFound:
|
||||
self._audit_uuid = None
|
||||
|
||||
def _get_first_action_uuid(self):
|
||||
return self._first_action_uuid
|
||||
|
||||
def _set_first_action_uuid(self, value):
|
||||
if value == wtypes.Unset:
|
||||
self._first_action_uuid = wtypes.Unset
|
||||
elif value and self._first_action_uuid != value:
|
||||
try:
|
||||
first_action = objects.Action.get(pecan.request.context,
|
||||
value)
|
||||
self._first_action_uuid = first_action.uuid
|
||||
self.first_action_id = first_action.id
|
||||
except exception.ActionNotFound:
|
||||
self._first_action_uuid = None
|
||||
|
||||
def _get_efficacy_indicators(self):
|
||||
if self._efficacy_indicators is None:
|
||||
self._set_efficacy_indicators(wtypes.Unset)
|
||||
@@ -204,6 +220,11 @@ class ActionPlan(base.APIBase):
|
||||
uuid = wtypes.wsattr(types.uuid, readonly=True)
|
||||
"""Unique UUID for this action plan"""
|
||||
|
||||
first_action_uuid = wsme.wsproperty(
|
||||
types.uuid, _get_first_action_uuid, _set_first_action_uuid,
|
||||
mandatory=True)
|
||||
"""The UUID of the first action this action plans links to"""
|
||||
|
||||
audit_uuid = wsme.wsproperty(types.uuid, _get_audit_uuid, _set_audit_uuid,
|
||||
mandatory=True)
|
||||
"""The UUID of the audit this port belongs to"""
|
||||
@@ -242,6 +263,7 @@ class ActionPlan(base.APIBase):
|
||||
setattr(self, field, kwargs.get(field, wtypes.Unset))
|
||||
|
||||
self.fields.append('audit_uuid')
|
||||
self.fields.append('first_action_uuid')
|
||||
self.fields.append('efficacy_indicators')
|
||||
|
||||
setattr(self, 'audit_uuid', kwargs.get('audit_id', wtypes.Unset))
|
||||
@@ -249,13 +271,16 @@ class ActionPlan(base.APIBase):
|
||||
setattr(self, 'strategy_uuid', kwargs.get('strategy_id', wtypes.Unset))
|
||||
fields.append('strategy_name')
|
||||
setattr(self, 'strategy_name', kwargs.get('strategy_id', wtypes.Unset))
|
||||
setattr(self, 'first_action_uuid',
|
||||
kwargs.get('first_action_id', wtypes.Unset))
|
||||
|
||||
@staticmethod
|
||||
def _convert_with_links(action_plan, url, expand=True):
|
||||
if not expand:
|
||||
action_plan.unset_fields_except(
|
||||
['uuid', 'state', 'efficacy_indicators', 'global_efficacy',
|
||||
'updated_at', 'audit_uuid', 'strategy_uuid', 'strategy_name'])
|
||||
'updated_at', 'audit_uuid', 'strategy_uuid', 'strategy_name',
|
||||
'first_action_uuid'])
|
||||
|
||||
action_plan.links = [
|
||||
link.Link.make_link(
|
||||
@@ -280,6 +305,7 @@ class ActionPlan(base.APIBase):
|
||||
created_at=datetime.datetime.utcnow(),
|
||||
deleted_at=None,
|
||||
updated_at=datetime.datetime.utcnow())
|
||||
sample._first_action_uuid = '57eaf9ab-5aaa-4f7e-bdf7-9a140ac7a720'
|
||||
sample._audit_uuid = 'abcee106-14d3-4515-b744-5a26885cf6f6'
|
||||
sample._efficacy_indicators = [{'description': 'Test indicator',
|
||||
'name': 'test_indicator',
|
||||
@@ -455,8 +481,7 @@ class ActionPlansController(rest.RestController):
|
||||
:param action_plan_uuid: UUID of a action.
|
||||
"""
|
||||
context = pecan.request.context
|
||||
action_plan = api_utils.get_resource(
|
||||
'ActionPlan', action_plan_uuid, eager=True)
|
||||
action_plan = api_utils.get_resource('ActionPlan', action_plan_uuid)
|
||||
policy.enforce(context, 'action_plan:delete', action_plan,
|
||||
action='action_plan:delete')
|
||||
|
||||
@@ -471,12 +496,13 @@ class ActionPlansController(rest.RestController):
|
||||
:param action_plan_uuid: UUID of a action plan.
|
||||
:param patch: a json PATCH document to apply to this action plan.
|
||||
"""
|
||||
launch_action_plan = True
|
||||
if self.from_actionsPlans:
|
||||
raise exception.OperationNotPermitted
|
||||
|
||||
context = pecan.request.context
|
||||
action_plan_to_update = api_utils.get_resource(
|
||||
'ActionPlan', action_plan_uuid, eager=True)
|
||||
action_plan_to_update = api_utils.get_resource('ActionPlan',
|
||||
action_plan_uuid)
|
||||
policy.enforce(context, 'action_plan:update', action_plan_to_update,
|
||||
action='action_plan:update')
|
||||
|
||||
|
||||
@@ -50,21 +50,6 @@ from watcher.decision_engine import rpcapi
|
||||
from watcher import objects
|
||||
|
||||
|
||||
ALLOWED_AUDIT_TRANSITIONS = {
|
||||
objects.audit.State.PENDING:
|
||||
[objects.audit.State.ONGOING, objects.audit.State.CANCELLED],
|
||||
objects.audit.State.ONGOING:
|
||||
[objects.audit.State.FAILED, objects.audit.State.SUCCEEDED,
|
||||
objects.audit.State.CANCELLED],
|
||||
objects.audit.State.FAILED:
|
||||
[objects.audit.State.DELETED],
|
||||
objects.audit.State.SUCCEEDED:
|
||||
[objects.audit.State.DELETED],
|
||||
objects.audit.State.CANCELLED:
|
||||
[objects.audit.State.DELETED]
|
||||
}
|
||||
|
||||
|
||||
class AuditPostType(wtypes.Base):
|
||||
|
||||
audit_template_uuid = wtypes.wsattr(types.uuid, mandatory=False)
|
||||
@@ -75,6 +60,8 @@ class AuditPostType(wtypes.Base):
|
||||
|
||||
audit_type = wtypes.wsattr(wtypes.text, mandatory=True)
|
||||
|
||||
deadline = wtypes.wsattr(datetime.datetime, mandatory=False)
|
||||
|
||||
state = wsme.wsattr(wtypes.text, readonly=True,
|
||||
default=objects.audit.State.PENDING)
|
||||
|
||||
@@ -82,9 +69,8 @@ class AuditPostType(wtypes.Base):
|
||||
default={})
|
||||
interval = wsme.wsattr(int, mandatory=False)
|
||||
|
||||
scope = wtypes.wsattr(types.jsontype, readonly=True)
|
||||
|
||||
auto_trigger = wtypes.wsattr(bool, mandatory=False)
|
||||
host_aggregate = wsme.wsattr(wtypes.IntegerType(minimum=1),
|
||||
mandatory=False)
|
||||
|
||||
def as_audit(self, context):
|
||||
audit_type_values = [val.value for val in objects.audit.AuditType]
|
||||
@@ -96,7 +82,7 @@ class AuditPostType(wtypes.Base):
|
||||
raise exception.AuditIntervalNotAllowed(audit_type=self.audit_type)
|
||||
|
||||
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(
|
||||
audit_type=self.audit_type)
|
||||
|
||||
@@ -114,7 +100,7 @@ class AuditPostType(wtypes.Base):
|
||||
at2a = {
|
||||
'goal': 'goal_id',
|
||||
'strategy': 'strategy_id',
|
||||
'scope': 'scope',
|
||||
'host_aggregate': 'host_aggregate'
|
||||
}
|
||||
to_string_fields = set(['goal', 'strategy'])
|
||||
for k in at2a:
|
||||
@@ -128,12 +114,12 @@ class AuditPostType(wtypes.Base):
|
||||
pass
|
||||
return Audit(
|
||||
audit_type=self.audit_type,
|
||||
deadline=self.deadline,
|
||||
parameters=self.parameters,
|
||||
goal_id=self.goal,
|
||||
host_aggregate=self.host_aggregate,
|
||||
strategy_id=self.strategy,
|
||||
interval=self.interval,
|
||||
scope=self.scope,
|
||||
auto_trigger=self.auto_trigger)
|
||||
interval=self.interval)
|
||||
|
||||
|
||||
class AuditPatchType(types.JsonPatchType):
|
||||
@@ -244,6 +230,9 @@ class Audit(base.APIBase):
|
||||
audit_type = wtypes.text
|
||||
"""Type of this audit"""
|
||||
|
||||
deadline = datetime.datetime
|
||||
"""deadline of the audit"""
|
||||
|
||||
state = wtypes.text
|
||||
"""This audit state"""
|
||||
|
||||
@@ -272,11 +261,8 @@ class Audit(base.APIBase):
|
||||
interval = wsme.wsattr(int, mandatory=False)
|
||||
"""Launch audit periodically (in seconds)"""
|
||||
|
||||
scope = wsme.wsattr(types.jsontype, mandatory=False)
|
||||
"""Audit Scope"""
|
||||
|
||||
auto_trigger = wsme.wsattr(bool, mandatory=False, default=False)
|
||||
"""Autoexecute action plan once audit is succeeded"""
|
||||
host_aggregate = wtypes.IntegerType(minimum=1)
|
||||
"""ID of the Nova host aggregate targeted by the audit template"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.fields = []
|
||||
@@ -306,10 +292,10 @@ class Audit(base.APIBase):
|
||||
@staticmethod
|
||||
def _convert_with_links(audit, url, expand=True):
|
||||
if not expand:
|
||||
audit.unset_fields_except(['uuid', 'audit_type', 'state',
|
||||
'goal_uuid', 'interval', 'scope',
|
||||
'strategy_uuid', 'goal_name',
|
||||
'strategy_name'])
|
||||
audit.unset_fields_except(['uuid', 'audit_type', 'deadline',
|
||||
'state', 'goal_uuid', 'interval',
|
||||
'strategy_uuid', 'host_aggregate',
|
||||
'goal_name', 'strategy_name'])
|
||||
|
||||
audit.links = [link.Link.make_link('self', url,
|
||||
'audits', audit.uuid),
|
||||
@@ -330,16 +316,15 @@ class Audit(base.APIBase):
|
||||
sample = cls(uuid='27e3153e-d5bf-4b7e-b517-fb518e17f34c',
|
||||
audit_type='ONESHOT',
|
||||
state='PENDING',
|
||||
deadline=None,
|
||||
created_at=datetime.datetime.utcnow(),
|
||||
deleted_at=None,
|
||||
updated_at=datetime.datetime.utcnow(),
|
||||
interval=7200,
|
||||
scope=[],
|
||||
auto_trigger=False)
|
||||
interval=7200)
|
||||
|
||||
sample.goal_id = '7ae81bb3-dec3-4289-8d6c-da80bd8001ae'
|
||||
sample.strategy_id = '7ae81bb3-dec3-4289-8d6c-da80bd8001ff'
|
||||
|
||||
sample.host_aggregate = 1
|
||||
return cls._convert_with_links(sample, 'http://localhost:9322', expand)
|
||||
|
||||
|
||||
@@ -396,7 +381,7 @@ class AuditsController(rest.RestController):
|
||||
def _get_audits_collection(self, marker, limit,
|
||||
sort_key, sort_dir, expand=False,
|
||||
resource_url=None, goal=None,
|
||||
strategy=None):
|
||||
strategy=None, host_aggregate=None):
|
||||
limit = api_utils.validate_limit(limit)
|
||||
api_utils.validate_sort_dir(sort_dir)
|
||||
marker_obj = None
|
||||
@@ -439,16 +424,19 @@ class AuditsController(rest.RestController):
|
||||
|
||||
@wsme_pecan.wsexpose(AuditCollection, types.uuid, int, wtypes.text,
|
||||
wtypes.text, wtypes.text, wtypes.text, int)
|
||||
def get_all(self, marker=None, limit=None, sort_key='id', sort_dir='asc',
|
||||
goal=None, strategy=None):
|
||||
def get_all(self, marker=None, limit=None,
|
||||
sort_key='id', sort_dir='asc', goal=None,
|
||||
strategy=None, host_aggregate=None):
|
||||
"""Retrieve a list of audits.
|
||||
|
||||
: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.
|
||||
id.
|
||||
:param goal: goal 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
|
||||
@@ -457,7 +445,8 @@ class AuditsController(rest.RestController):
|
||||
|
||||
return self._get_audits_collection(marker, limit, sort_key,
|
||||
sort_dir, goal=goal,
|
||||
strategy=strategy)
|
||||
strategy=strategy,
|
||||
host_aggregate=host_aggregate)
|
||||
|
||||
@wsme_pecan.wsexpose(AuditCollection, wtypes.text, types.uuid, int,
|
||||
wtypes.text, wtypes.text)
|
||||
@@ -541,12 +530,13 @@ class AuditsController(rest.RestController):
|
||||
audit_dict = audit.as_dict()
|
||||
|
||||
new_audit = objects.Audit(context, **audit_dict)
|
||||
new_audit.create()
|
||||
new_audit.create(context)
|
||||
|
||||
# Set the HTTP Location Header
|
||||
pecan.response.location = link.build_url('audits', new_audit.uuid)
|
||||
|
||||
# trigger decision-engine to run the audit
|
||||
|
||||
if new_audit.audit_type == objects.audit.AuditType.ONESHOT.value:
|
||||
dc_client = rpcapi.DecisionEngineAPI()
|
||||
dc_client.trigger_audit(context, new_audit.uuid)
|
||||
@@ -565,28 +555,20 @@ class AuditsController(rest.RestController):
|
||||
raise exception.OperationNotPermitted
|
||||
|
||||
context = pecan.request.context
|
||||
audit_to_update = api_utils.get_resource(
|
||||
'Audit', audit_uuid, eager=True)
|
||||
audit_to_update = api_utils.get_resource('Audit',
|
||||
audit_uuid)
|
||||
policy.enforce(context, 'audit:update', audit_to_update,
|
||||
action='audit:update')
|
||||
|
||||
audit_to_update = objects.Audit.get_by_uuid(pecan.request.context,
|
||||
audit_uuid)
|
||||
|
||||
try:
|
||||
audit_dict = audit_to_update.as_dict()
|
||||
audit = Audit(**api_utils.apply_jsonpatch(audit_dict, patch))
|
||||
except api_utils.JSONPATCH_EXCEPTIONS as e:
|
||||
raise exception.PatchError(patch=patch, reason=e)
|
||||
|
||||
initial_state = audit_dict['state']
|
||||
new_state = api_utils.get_patch_value(patch, 'state')
|
||||
allowed_states = ALLOWED_AUDIT_TRANSITIONS.get(initial_state, [])
|
||||
if new_state is not None and new_state not in allowed_states:
|
||||
error_message = _("State transition not allowed: "
|
||||
"(%(initial_state)s -> %(new_state)s)")
|
||||
raise exception.PatchError(
|
||||
patch=patch,
|
||||
reason=error_message % dict(
|
||||
initial_state=initial_state, new_state=new_state))
|
||||
|
||||
# Update only the fields that have changed
|
||||
for field in objects.Audit.fields:
|
||||
try:
|
||||
@@ -609,8 +591,7 @@ class AuditsController(rest.RestController):
|
||||
:param audit_uuid: UUID of a audit.
|
||||
"""
|
||||
context = pecan.request.context
|
||||
audit_to_delete = api_utils.get_resource(
|
||||
'Audit', audit_uuid, eager=True)
|
||||
audit_to_delete = api_utils.get_resource('Audit', audit_uuid)
|
||||
policy.enforce(context, 'audit:update', audit_to_delete,
|
||||
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>`
|
||||
will be launched automatically or will need a manual confirmation from the
|
||||
:ref:`Administrator <administrator_definition>`.
|
||||
|
||||
Last but not least, an :ref:`Audit Template <audit_template_definition>` may
|
||||
contain a list of extra parameters related to the
|
||||
:ref:`Strategy <strategy_definition>` configuration. These parameters can be
|
||||
provided as a list of key-value pairs.
|
||||
"""
|
||||
|
||||
import datetime
|
||||
@@ -61,7 +66,6 @@ from watcher.common import context as context_utils
|
||||
from watcher.common import exception
|
||||
from watcher.common import policy
|
||||
from watcher.common import utils as common_utils
|
||||
from watcher.decision_engine.scope import default
|
||||
from watcher import objects
|
||||
|
||||
|
||||
@@ -74,24 +78,37 @@ class AuditTemplatePostType(wtypes.Base):
|
||||
description = wtypes.wsattr(wtypes.text, mandatory=False)
|
||||
"""Short description of this audit template"""
|
||||
|
||||
deadline = wsme.wsattr(datetime.datetime, mandatory=False)
|
||||
"""deadline of the audit template"""
|
||||
|
||||
host_aggregate = wsme.wsattr(wtypes.IntegerType(minimum=1),
|
||||
mandatory=False)
|
||||
"""ID of the Nova host aggregate targeted by the audit template"""
|
||||
|
||||
extra = wtypes.wsattr({wtypes.text: types.jsontype}, mandatory=False)
|
||||
"""The metadata of the audit template"""
|
||||
|
||||
goal = wtypes.wsattr(wtypes.text, mandatory=True)
|
||||
"""Goal UUID or name of the audit template"""
|
||||
|
||||
strategy = wtypes.wsattr(wtypes.text, mandatory=False)
|
||||
"""Strategy UUID or name of the audit template"""
|
||||
|
||||
scope = wtypes.wsattr(types.jsontype, mandatory=False, default=[])
|
||||
"""Audit Scope"""
|
||||
version = wtypes.text
|
||||
"""Internal version of the audit template"""
|
||||
|
||||
def as_audit_template(self):
|
||||
return AuditTemplate(
|
||||
name=self.name,
|
||||
description=self.description,
|
||||
deadline=self.deadline,
|
||||
host_aggregate=self.host_aggregate,
|
||||
extra=self.extra,
|
||||
goal_id=self.goal, # Dirty trick ...
|
||||
goal=self.goal,
|
||||
strategy_id=self.strategy, # Dirty trick ...
|
||||
strategy_uuid=self.strategy,
|
||||
scope=self.scope,
|
||||
version=self.version,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@@ -106,9 +123,6 @@ class AuditTemplatePostType(wtypes.Base):
|
||||
else:
|
||||
raise exception.InvalidGoal(goal=audit_template.goal)
|
||||
|
||||
common_utils.Draft4Validator(
|
||||
default.DefaultScope.DEFAULT_SCHEMA).validate(audit_template.scope)
|
||||
|
||||
if audit_template.strategy:
|
||||
available_strategies = objects.Strategy.list(
|
||||
AuditTemplatePostType._ctx)
|
||||
@@ -291,9 +305,18 @@ class AuditTemplate(base.APIBase):
|
||||
name = wtypes.text
|
||||
"""Name of this audit template"""
|
||||
|
||||
description = wtypes.wsattr(wtypes.text, mandatory=False)
|
||||
description = wtypes.text
|
||||
"""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(
|
||||
wtypes.text, _get_goal_uuid, _set_goal_uuid, mandatory=True)
|
||||
"""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)
|
||||
"""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)
|
||||
"""Links to the collection of audits contained in this audit template"""
|
||||
|
||||
links = wsme.wsattr([link.Link], readonly=True)
|
||||
"""A list containing a self link and associated audit template links"""
|
||||
|
||||
scope = wsme.wsattr(types.jsontype, mandatory=False)
|
||||
"""Audit Scope"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(AuditTemplate, self).__init__()
|
||||
self.fields = []
|
||||
@@ -333,7 +356,6 @@ class AuditTemplate(base.APIBase):
|
||||
|
||||
self.fields.append('goal_id')
|
||||
self.fields.append('strategy_id')
|
||||
setattr(self, 'strategy_id', kwargs.get('strategy_id', wtypes.Unset))
|
||||
|
||||
# goal_uuid & strategy_uuid are not part of
|
||||
# objects.AuditTemplate.fields because they're API-only attributes.
|
||||
@@ -352,8 +374,8 @@ class AuditTemplate(base.APIBase):
|
||||
def _convert_with_links(audit_template, url, expand=True):
|
||||
if not expand:
|
||||
audit_template.unset_fields_except(
|
||||
['uuid', 'name', 'goal_uuid', 'goal_name',
|
||||
'scope', 'strategy_uuid', 'strategy_name'])
|
||||
['uuid', 'name', 'host_aggregate', 'goal_uuid', 'goal_name',
|
||||
'strategy_uuid', 'strategy_name'])
|
||||
|
||||
# The numeric ID should not be exposed to
|
||||
# the user, it's internal only.
|
||||
@@ -380,12 +402,13 @@ class AuditTemplate(base.APIBase):
|
||||
sample = cls(uuid='27e3153e-d5bf-4b7e-b517-fb518e17f34c',
|
||||
name='My Audit Template',
|
||||
description='Description of my audit template',
|
||||
host_aggregate=5,
|
||||
goal_uuid='83e44733-b640-40e2-8d8a-7dd3be7134e6',
|
||||
strategy_uuid='367d826e-b6a4-4b70-bc44-c3f6fe1c9986',
|
||||
extra={'automatic': True},
|
||||
created_at=datetime.datetime.utcnow(),
|
||||
deleted_at=None,
|
||||
updated_at=datetime.datetime.utcnow(),
|
||||
scope=[],)
|
||||
updated_at=datetime.datetime.utcnow())
|
||||
return cls._convert_with_links(sample, 'http://localhost:9322', expand)
|
||||
|
||||
|
||||
@@ -569,11 +592,11 @@ class AuditTemplatesController(rest.RestController):
|
||||
audit_template_dict = audit_template.as_dict()
|
||||
new_audit_template = objects.AuditTemplate(context,
|
||||
**audit_template_dict)
|
||||
new_audit_template.create()
|
||||
new_audit_template.create(context)
|
||||
|
||||
# Set the HTTP Location Header
|
||||
pecan.response.location = link.build_url(
|
||||
'audit_templates', new_audit_template.uuid)
|
||||
pecan.response.location = link.build_url('audit_templates',
|
||||
new_audit_template.uuid)
|
||||
return AuditTemplate.convert_with_links(new_audit_template)
|
||||
|
||||
@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
|
||||
<strategy_definition>` performed. These efficacy indicators are specific to a
|
||||
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>`.
|
||||
|
||||
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"""
|
||||
|
||||
parameters_spec = {wtypes.text: types.jsontype}
|
||||
"""Parameters spec dict"""
|
||||
""" Parameters spec dict"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(Strategy, self).__init__()
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# coding: utf-8
|
||||
#
|
||||
# Copyright 2013 Red Hat, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
|
||||
@@ -15,13 +15,11 @@
|
||||
|
||||
import jsonpatch
|
||||
from oslo_config import cfg
|
||||
from oslo_utils import reflection
|
||||
from oslo_utils import uuidutils
|
||||
import pecan
|
||||
import wsme
|
||||
|
||||
from watcher._i18n import _
|
||||
from watcher.common import utils
|
||||
from watcher import objects
|
||||
|
||||
CONF = cfg.CONF
|
||||
@@ -73,12 +71,6 @@ def apply_jsonpatch(doc, patch):
|
||||
return jsonpatch.apply_patch(doc, jsonpatch.JsonPatch(patch))
|
||||
|
||||
|
||||
def get_patch_value(patch, key):
|
||||
for p in patch:
|
||||
if p['op'] == 'replace' and p['path'] == '/%s' % key:
|
||||
return p['value']
|
||||
|
||||
|
||||
def as_filters_dict(**filters):
|
||||
filters_dict = {}
|
||||
for filter_name, filter_value in filters.items():
|
||||
@@ -88,27 +80,17 @@ def as_filters_dict(**filters):
|
||||
return filters_dict
|
||||
|
||||
|
||||
def get_resource(resource, resource_id, eager=False):
|
||||
"""Get the resource from the uuid, id or logical name.
|
||||
def get_resource(resource, resource_ident):
|
||||
"""Get the resource from the uuid or logical name.
|
||||
|
||||
: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.
|
||||
"""
|
||||
resource = getattr(objects, resource)
|
||||
|
||||
_get = None
|
||||
if utils.is_int_like(resource_id):
|
||||
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
|
||||
if uuidutils.is_uuid_like(resource_ident):
|
||||
return resource.get_by_uuid(pecan.request.context, resource_ident)
|
||||
|
||||
method_signature = reflection.get_signature(_get)
|
||||
if 'eager' in method_signature.parameters:
|
||||
return _get(pecan.request.context, resource_id, eager=eager)
|
||||
|
||||
return _get(pecan.request.context, resource_id)
|
||||
return resource.get_by_name(pecan.request.context, resource_ident)
|
||||
|
||||
@@ -114,6 +114,6 @@ class NoExceptionTracebackHook(hooks.PecanHook):
|
||||
faultstring = faultstring.split(traceback_marker, 1)[0]
|
||||
# Remove trailing newlines and spaces if any.
|
||||
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.
|
||||
state.response.json = json_body
|
||||
|
||||
@@ -33,7 +33,7 @@ class AuthTokenMiddleware(auth_token.AuthProtocol):
|
||||
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)?$'
|
||||
|
||||
try:
|
||||
|
||||
@@ -20,47 +20,46 @@ from oslo_log import log
|
||||
|
||||
from watcher.applier.action_plan import base
|
||||
from watcher.applier import default
|
||||
from watcher import notifications
|
||||
from watcher import objects
|
||||
from watcher.objects import fields
|
||||
from watcher.applier.messaging import event_types
|
||||
from watcher.common.messaging.events import event
|
||||
from watcher.objects import action_plan as ap_objects
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class DefaultActionPlanHandler(base.BaseActionPlanHandler):
|
||||
|
||||
def __init__(self, context, service, action_plan_uuid):
|
||||
super(DefaultActionPlanHandler, self).__init__()
|
||||
self.ctx = context
|
||||
self.service = service
|
||||
self.action_plan_uuid = action_plan_uuid
|
||||
|
||||
def notify(self, uuid, event_type, state):
|
||||
action_plan = ap_objects.ActionPlan.get_by_uuid(self.ctx, uuid)
|
||||
action_plan.state = state
|
||||
action_plan.save()
|
||||
ev = event.Event()
|
||||
ev.type = event_type
|
||||
ev.data = {}
|
||||
payload = {'action_plan__uuid': uuid,
|
||||
'action_plan_state': state}
|
||||
self.service.publish_status_event(ev.type.name, payload)
|
||||
|
||||
def execute(self):
|
||||
try:
|
||||
action_plan = objects.ActionPlan.get_by_uuid(
|
||||
self.ctx, self.action_plan_uuid, eager=True)
|
||||
action_plan.state = objects.action_plan.State.ONGOING
|
||||
action_plan.save()
|
||||
notifications.action_plan.send_action_notification(
|
||||
self.ctx, action_plan,
|
||||
action=fields.NotificationAction.EXECUTION,
|
||||
phase=fields.NotificationPhase.START)
|
||||
|
||||
# update state
|
||||
self.notify(self.action_plan_uuid,
|
||||
event_types.EventTypes.LAUNCH_ACTION_PLAN,
|
||||
ap_objects.State.ONGOING)
|
||||
applier = default.DefaultApplier(self.ctx, self.service)
|
||||
applier.execute(self.action_plan_uuid)
|
||||
|
||||
action_plan.state = objects.action_plan.State.SUCCEEDED
|
||||
notifications.action_plan.send_action_notification(
|
||||
self.ctx, action_plan,
|
||||
action=fields.NotificationAction.EXECUTION,
|
||||
phase=fields.NotificationPhase.END)
|
||||
state = ap_objects.State.SUCCEEDED
|
||||
except Exception as e:
|
||||
LOG.exception(e)
|
||||
action_plan.state = objects.action_plan.State.FAILED
|
||||
notifications.action_plan.send_action_notification(
|
||||
self.ctx, action_plan,
|
||||
action=fields.NotificationAction.EXECUTION,
|
||||
priority=fields.NotificationPriority.ERROR,
|
||||
phase=fields.NotificationPhase.ERROR)
|
||||
state = ap_objects.State.FAILED
|
||||
|
||||
finally:
|
||||
action_plan.save()
|
||||
# update state
|
||||
self.notify(self.action_plan_uuid,
|
||||
event_types.EventTypes.LAUNCH_ACTION_PLAN,
|
||||
state)
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2017 Servionica
|
||||
#
|
||||
# Authors: Alexander Chadin <a.chadin@servionica.ru>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
from oslo_log import log
|
||||
import six
|
||||
import voluptuous
|
||||
|
||||
from watcher._i18n import _, _LC
|
||||
from watcher.applier.actions import base
|
||||
from watcher.common import nova_helper
|
||||
from watcher.common import utils
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class Resize(base.BaseAction):
|
||||
"""Resizes a server with specified flavor.
|
||||
|
||||
This action will allow you to resize a server to another flavor.
|
||||
|
||||
The action schema is::
|
||||
|
||||
schema = Schema({
|
||||
'resource_id': str, # should be a UUID
|
||||
'flavor': str, # should be either ID or Name of Flavor
|
||||
})
|
||||
|
||||
The `resource_id` is the UUID of the server to resize.
|
||||
The `flavor` is the ID or Name of Flavor (Nova accepts either ID or Name
|
||||
of Flavor to resize() function).
|
||||
"""
|
||||
|
||||
# input parameters constants
|
||||
FLAVOR = 'flavor'
|
||||
|
||||
def check_resource_id(self, value):
|
||||
if (value is not None and
|
||||
len(value) > 0 and not
|
||||
utils.is_uuid_like(value)):
|
||||
raise voluptuous.Invalid(_("The parameter "
|
||||
"resource_id is invalid."))
|
||||
|
||||
@property
|
||||
def schema(self):
|
||||
return voluptuous.Schema({
|
||||
voluptuous.Required(self.RESOURCE_ID): self.check_resource_id,
|
||||
voluptuous.Required(self.FLAVOR):
|
||||
voluptuous.All(voluptuous.Any(*six.string_types),
|
||||
voluptuous.Length(min=1)),
|
||||
})
|
||||
|
||||
@property
|
||||
def instance_uuid(self):
|
||||
return self.resource_id
|
||||
|
||||
@property
|
||||
def flavor(self):
|
||||
return self.input_parameters.get(self.FLAVOR)
|
||||
|
||||
def resize(self):
|
||||
nova = nova_helper.NovaHelper(osc=self.osc)
|
||||
LOG.debug("Resize instance %s to %s flavor", self.instance_uuid,
|
||||
self.flavor)
|
||||
instance = nova.find_instance(self.instance_uuid)
|
||||
result = None
|
||||
if instance:
|
||||
try:
|
||||
result = nova.resize_instance(
|
||||
instance_id=self.instance_uuid, flavor=self.flavor)
|
||||
except Exception as exc:
|
||||
LOG.exception(exc)
|
||||
LOG.critical(
|
||||
_LC("Unexpected error occurred. Resizing failed for "
|
||||
"instance %s."), self.instance_uuid)
|
||||
return result
|
||||
|
||||
def execute(self):
|
||||
return self.resize()
|
||||
|
||||
def revert(self):
|
||||
return self.migrate(destination=self.source_node)
|
||||
|
||||
def pre_condition(self):
|
||||
# TODO(jed): check if the instance exists / check if the instance is on
|
||||
# the source_node
|
||||
pass
|
||||
|
||||
def post_condition(self):
|
||||
# TODO(jed): check extra parameters (network response, etc.)
|
||||
pass
|
||||
@@ -56,7 +56,7 @@ class DefaultApplier(base.BaseApplier):
|
||||
|
||||
def execute(self, action_plan_uuid):
|
||||
LOG.debug("Executing action plan %s ", action_plan_uuid)
|
||||
|
||||
filters = {'action_plan_uuid': action_plan_uuid}
|
||||
actions = objects.Action.list(self.context, filters=filters)
|
||||
actions = objects.Action.list(self.context,
|
||||
filters=filters)
|
||||
return self.engine.execute(actions)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2015 b<>com
|
||||
# Copyright (c) 2016 Intel Corp
|
||||
#
|
||||
# Authors: Jean-Emile DARTOIS <jean-emile.dartois@b-com.com>
|
||||
#
|
||||
@@ -18,40 +17,60 @@
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
from watcher.applier.messaging import trigger
|
||||
from watcher.common import service_manager
|
||||
|
||||
from watcher import conf
|
||||
|
||||
CONF = conf.CONF
|
||||
CONF = cfg.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
|
||||
def service_name(self):
|
||||
return 'watcher-applier'
|
||||
opt_group = cfg.OptGroup(name='watcher_applier',
|
||||
title='Options for the Applier messaging'
|
||||
'core')
|
||||
CONF.register_group(opt_group)
|
||||
CONF.register_opts(APPLIER_MANAGER_OPTS, opt_group)
|
||||
|
||||
@property
|
||||
def api_version(self):
|
||||
return '1.0'
|
||||
|
||||
@property
|
||||
def publisher_id(self):
|
||||
return CONF.watcher_applier.publisher_id
|
||||
class ApplierManager(object):
|
||||
|
||||
@property
|
||||
def conductor_topic(self):
|
||||
return CONF.watcher_applier.conductor_topic
|
||||
API_VERSION = '1.0'
|
||||
|
||||
@property
|
||||
def notification_topics(self):
|
||||
return []
|
||||
conductor_endpoints = [trigger.TriggerActionPlan]
|
||||
status_endpoints = []
|
||||
notification_endpoints = []
|
||||
notification_topics = []
|
||||
|
||||
@property
|
||||
def conductor_endpoints(self):
|
||||
return [trigger.TriggerActionPlan]
|
||||
|
||||
@property
|
||||
def notification_endpoints(self):
|
||||
return []
|
||||
def __init__(self):
|
||||
self.publisher_id = CONF.watcher_applier.publisher_id
|
||||
self.conductor_topic = CONF.watcher_applier.conductor_topic
|
||||
self.status_topic = CONF.watcher_applier.status_topic
|
||||
self.api_version = self.API_VERSION
|
||||
|
||||