Merge "Send notifications for all API changes"
This commit is contained in:
commit
0cbe4a3f7c
|
@ -0,0 +1,161 @@
|
|||
# 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::
|
||||
|
||||
"""
|
||||
import os
|
||||
|
||||
from docutils import nodes
|
||||
from docutils.parsers import rst
|
||||
import importlib
|
||||
from oslo_serialization import jsonutils
|
||||
import pkgutil
|
||||
|
||||
from masakari.notifications.objects import base as notification
|
||||
from masakari.objects import base
|
||||
from masakari.tests import json_ref
|
||||
import masakari.utils
|
||||
|
||||
|
||||
class VersionedNotificationDirective(rst.Directive):
|
||||
|
||||
SAMPLE_ROOT = 'doc/notification_samples/'
|
||||
TOGGLE_SCRIPT = """
|
||||
<!-- jQuery -->
|
||||
<script type="text/javascript" src="../_static/js/jquery-3.2.1.min.js">
|
||||
</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 _import_all_notification_packages(self):
|
||||
list(map(lambda module: importlib.import_module(module),
|
||||
('masakari.notifications.objects.' + name for _, name, _ in
|
||||
pkgutil.iter_modules(masakari.notifications.objects.__path__))))
|
||||
|
||||
def _collect_notifications(self):
|
||||
self._import_all_notification_packages()
|
||||
base.MasakariObjectRegistry.register_notification_objects()
|
||||
notifications = {}
|
||||
ovos = base.MasakariObjectRegistry.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:
|
||||
if sample in notifications:
|
||||
raise ValueError('Duplicated usage of %s '
|
||||
'sample file detected' % sample)
|
||||
|
||||
notifications[sample] = ((cls.__name__,
|
||||
payload_cls.__name__,
|
||||
sample))
|
||||
return sorted(notifications.values())
|
||||
|
||||
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(os.path.join(self.SAMPLE_ROOT, sample_file), 'r') as f:
|
||||
sample_content = f.read()
|
||||
|
||||
sample_obj = jsonutils.loads(sample_content)
|
||||
sample_obj = json_ref.resolve_refs(
|
||||
sample_obj,
|
||||
base_path=os.path.abspath(self.SAMPLE_ROOT))
|
||||
sample_content = jsonutils.dumps(sample_obj,
|
||||
sort_keys=True, indent=4,
|
||||
separators=(',', ': '))
|
||||
|
||||
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)
|
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
"event_type": "host.create.end",
|
||||
"timestamp": "2018-11-27 13:09:30.737034",
|
||||
"payload": {
|
||||
"masakari_object.name": "HostApiPayload",
|
||||
"masakari_object.data": {
|
||||
"reserved": false,
|
||||
"uuid": "d6a2d900-1977-48fd-aa52-ad7a41fc068b",
|
||||
"on_maintenance": false,
|
||||
"control_attributes": "TEST",
|
||||
"name": "fake-mini",
|
||||
"failover_segment": {
|
||||
"masakari_object.name": "FailoverSegment",
|
||||
"masakari_object.data": {
|
||||
"uuid": "89597691-bebd-4860-a93e-1b6e9de34b9e",
|
||||
"deleted": false,
|
||||
"created_at": "2018-11-27T09:26:30Z",
|
||||
"recovery_method": "auto",
|
||||
"updated_at": "2018-11-27T09:54:50Z",
|
||||
"name": "test",
|
||||
"service_type": "compute",
|
||||
"deleted_at": null,
|
||||
"id": 877, "description": null
|
||||
},
|
||||
"masakari_object.version": "1.0",
|
||||
"masakari_object.namespace": "masakari"
|
||||
},
|
||||
"fault": null,
|
||||
"type": "COMPUTE",
|
||||
"id": 70,
|
||||
"failover_segment_id": "89597691-bebd-4860-a93e-1b6e9de34b9e"
|
||||
},
|
||||
"masakari_object.version": "1.0",
|
||||
"masakari_object.namespace": "masakari"
|
||||
},
|
||||
"priority": "INFO",
|
||||
"publisher_id": "masakari-api:fake-mini",
|
||||
"message_id": "e437834a-73e1-4c47-939a-83f6aca2e7ac"
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"event_type": "host.create.start",
|
||||
"timestamp": "2018-11-27 13:09:30.716747",
|
||||
"payload": {
|
||||
"masakari_object.name": "HostApiPayload",
|
||||
"masakari_object.data": {
|
||||
"reserved": false,
|
||||
"name": "fake-mini",
|
||||
"on_maintenance": false,
|
||||
"control_attributes": "TEST",
|
||||
"fault": null,
|
||||
"type": "COMPUTE",
|
||||
"failover_segment_id": "89597691-bebd-4860-a93e-1b6e9de34b9e"
|
||||
},
|
||||
"masakari_object.version": "1.0",
|
||||
"masakari_object.namespace": "masakari"
|
||||
},
|
||||
"priority": "INFO",
|
||||
"publisher_id": "masakari-api:fake-mini",
|
||||
"message_id": "0ed836cc-353a-40bc-b86b-d89e6632d838"
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"event_type": "notification.create.end",
|
||||
"timestamp": "2018-11-27 13:46:25.496514",
|
||||
"payload": {
|
||||
"masakari_object.name": "NotificationApiPayload",
|
||||
"masakari_object.data": {
|
||||
"notification_uuid": "e6b1996f-7792-4a65-83c3-23f2d4721eb0",
|
||||
"status": "new",
|
||||
"source_host_uuid": "d4ffe3a4-b2a8-41f3-a2b0-bae3b06fc1a3",
|
||||
"fault": null,
|
||||
"id": 1,
|
||||
"generated_time": "2017-06-13T15:34:55Z",
|
||||
"type": "VM",
|
||||
"payload": {"process_name": "nova-compute"}
|
||||
},
|
||||
"masakari_object.version": "1.0",
|
||||
"masakari_object.namespace": "masakari"
|
||||
},
|
||||
"priority": "INFO",
|
||||
"publisher_id": "masakari-api:fake-mini",
|
||||
"message_id": "500447b9-4797-4090-9189-b56bc3521b75"
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"event_type": "notification.create.start",
|
||||
"timestamp": "2018-11-27 13:46:23.060352",
|
||||
"payload": {
|
||||
"masakari_object.name": "NotificationApiPayload",
|
||||
"masakari_object.data": {
|
||||
"status": "new",
|
||||
"source_host_uuid": "d4ffe3a4-b2a8-41f3-a2b0-bae3b06fc1a3",
|
||||
"fault": null,
|
||||
"generated_time": "2017-06-13T15:34:55Z",
|
||||
"type": "VM",
|
||||
"payload": {"process_name": "nova-compute"}
|
||||
},
|
||||
"masakari_object.version": "1.0",
|
||||
"masakari_object.namespace": "masakari"
|
||||
},
|
||||
"priority": "INFO",
|
||||
"publisher_id": "masakari-api:fake-mini",
|
||||
"message_id": "5e2e4699-0bbd-4583-b1e2-a87c458f84eb"
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"event_type": "segment.create.end",
|
||||
"timestamp": "2018-11-22 09:25:12.813483",
|
||||
"payload": {
|
||||
"masakari_object.name": "SegmentApiPayload",
|
||||
"masakari_object.data": {
|
||||
"description": null,
|
||||
"fault": null,
|
||||
"recovery_method": "auto",
|
||||
"name": "test",
|
||||
"service_type": "compute",
|
||||
"id": 850,
|
||||
"uuid": "5cce639c-da08-4e78-b615-66c88aa49d50"
|
||||
},
|
||||
"masakari_object.version": "1.0",
|
||||
"masakari_object.namespace": "masakari"
|
||||
},
|
||||
"priority": "INFO",
|
||||
"publisher_id": "masakari-api:fake-mini",
|
||||
"message_id": "b8478b31-5943-4495-8867-e8291655f660"
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"event_type": "segment.create.start",
|
||||
"timestamp": "2018-11-22 09:25:12.393979",
|
||||
"payload": {
|
||||
"masakari_object.name": "SegmentApiPayload",
|
||||
"masakari_object.data": {
|
||||
"service_type": "compute",
|
||||
"fault": null,
|
||||
"recovery_method": "auto",
|
||||
"description": null,
|
||||
"name": "test"
|
||||
},
|
||||
"masakari_object.version": "1.0",
|
||||
"masakari_object.namespace": "masakari"
|
||||
},
|
||||
|
||||
"publisher_id": "masakari-api:fake-mini",
|
||||
"message_id": "e44cb15b-dcba-409e-b0e1-9ee103b9a168"
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
"event_type": "host.delete.end",
|
||||
"timestamp": "2018-11-27 13:35:09.882636",
|
||||
"payload": {
|
||||
"masakari_object.name": "HostApiPayload",
|
||||
"masakari_object.data": {
|
||||
"reserved": false,
|
||||
"uuid": "3d8d1751-9cab-4a48-8801-96f102200077",
|
||||
"on_maintenance": false,
|
||||
"control_attributes": "TEST",
|
||||
"name": "fake-mini",
|
||||
"failover_segment": {
|
||||
"masakari_object.name": "FailoverSegment",
|
||||
"masakari_object.data": {
|
||||
"uuid": "89597691-bebd-4860-a93e-1b6e9de34b9e",
|
||||
"deleted": false,
|
||||
"created_at": "2018-11-27T09:26:30Z",
|
||||
"recovery_method": "auto",
|
||||
"updated_at": "2018-11-27T09:54:50Z",
|
||||
"name": "test",
|
||||
"service_type": "compute",
|
||||
"deleted_at": null,
|
||||
"id": 877,
|
||||
"description": null
|
||||
},
|
||||
"masakari_object.version": "1.0",
|
||||
"masakari_object.namespace": "masakari"
|
||||
},
|
||||
"fault": null,
|
||||
"type": "COMPUTE",
|
||||
"failover_segment_id": "89597691-bebd-4860-a93e-1b6e9de34b9e"
|
||||
},
|
||||
"masakari_object.version": "1.0",
|
||||
"masakari_object.namespace": "masakari"
|
||||
},
|
||||
"priority": "INFO",
|
||||
"publisher_id": "masakari-api:fake-mini",
|
||||
"message_id": "64d61bcf-c875-41c3-b795-19a076f6de96"
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"event_type": "host.delete.start",
|
||||
"timestamp": "2018-11-27 13:31:47.451466",
|
||||
"payload": {
|
||||
"masakari_object.name": "HostApiPayload",
|
||||
"masakari_object.data": {
|
||||
"reserved": false,
|
||||
"uuid": "3d8d1751-9cab-4a48-8801-96f102200077",
|
||||
"on_maintenance": false,
|
||||
"control_attributes": "TEST",
|
||||
"name": "fake-mini",
|
||||
"failover_segment": {
|
||||
"masakari_object.name": "FailoverSegment",
|
||||
"masakari_object.data": {
|
||||
"uuid": "89597691-bebd-4860-a93e-1b6e9de34b9e",
|
||||
"deleted": false,
|
||||
"created_at": "2018-11-27T09:26:30Z",
|
||||
"recovery_method": "auto",
|
||||
"updated_at": "2018-11-27T09:54:50Z",
|
||||
"name": "test",
|
||||
"service_type": "compute",
|
||||
"deleted_at": null,
|
||||
"id": 877,
|
||||
"description": null
|
||||
},
|
||||
"masakari_object.version": "1.0",
|
||||
"masakari_object.namespace": "masakari"
|
||||
},
|
||||
"fault": null,
|
||||
"type": "COMPUTE",
|
||||
"id": 71,
|
||||
"failover_segment_id": "89597691-bebd-4860-a93e-1b6e9de34b9e"
|
||||
},
|
||||
"masakari_object.version": "1.0",
|
||||
"masakari_object.namespace": "masakari"
|
||||
},
|
||||
"priority": "INFO",
|
||||
"publisher_id": "masakari-api:fake-mini",
|
||||
"message_id": "b5914f94-99dd-42fa-aaf3-3cedacda6b67"
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"event_type": "segment.delete.end",
|
||||
"timestamp": "2018-11-27 14:36:07.457369",
|
||||
"payload": {
|
||||
"masakari_object.name": "SegmentApiPayload",
|
||||
"masakari_object.data": {
|
||||
"description": null,
|
||||
"fault": null,
|
||||
"recovery_method": "auto",
|
||||
"uuid": "89597691-bebd-4860-a93e-1b6e9de34b9e",
|
||||
"service_type": "compute",
|
||||
"name": "test2"
|
||||
},
|
||||
"masakari_object.version": "1.0",
|
||||
"masakari_object.namespace": "masakari"
|
||||
},
|
||||
"priority": "INFO",
|
||||
"publisher_id": "masakari-api:fake-mini",
|
||||
"message_id": "00184d05-7a96-4021-b44e-03912a6c0b0d"
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"event_type": "segment.delete.start",
|
||||
"timestamp": "2018-11-27 14:36:07.442538",
|
||||
"payload": {
|
||||
"masakari_object.name": "SegmentApiPayload",
|
||||
"masakari_object.data": {
|
||||
"description": null,
|
||||
"fault": null,
|
||||
"recovery_method": "auto",
|
||||
"name": "test2",
|
||||
"service_type": "compute",
|
||||
"id": 877,
|
||||
"uuid": "89597691-bebd-4860-a93e-1b6e9de34b9e"
|
||||
},
|
||||
"masakari_object.version": "1.0",
|
||||
"masakari_object.namespace": "masakari"
|
||||
},
|
||||
"priority": "INFO",
|
||||
"publisher_id": "masakari-api:fake-mini",
|
||||
"message_id": "e6c32ecb-eacc-433d-ba8c-6390ea3da6d2"
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"event_type": "segment.create.error",
|
||||
"timestamp": "2018-11-28 14:24:27.902437",
|
||||
"payload": {
|
||||
"masakari_object.name": "SegmentApiPayload",
|
||||
"masakari_object.data": {
|
||||
"service_type": "compute",
|
||||
"fault": {
|
||||
"masakari_object.name": "ExceptionPayload",
|
||||
"masakari_object.data": {
|
||||
"module_name": "pymysql.err",
|
||||
"exception": "DBError",
|
||||
"traceback": "Traceback (most recent call last):\n File \"/opt/stack/masakari/masakari/ha/api.py\", line ...",
|
||||
"exception_message": "(pymysql.err.Internal Error) (1054, u\"Unknown column 'name' in 'field list'\" ...",
|
||||
"function_name": "raise_mysql_exception"
|
||||
},
|
||||
"masakari_object.version": "1.0",
|
||||
"masakari_object.namespace": "masakari"
|
||||
},
|
||||
"recovery_method": "auto",
|
||||
"description": null,
|
||||
"name": "testT6"
|
||||
},
|
||||
"masakari_object.version": "1.0",
|
||||
"masakari_object.namespace": "masakari"
|
||||
},
|
||||
"priority": "ERROR",
|
||||
"publisher_id": "masakari-api:fake-mini",
|
||||
"message_id": "e5405591-1d19-4a8c-aa92-4d551165d863"
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"event_type": "notification.process.end",
|
||||
"timestamp": "2018-12-20 05:26:05.075917",
|
||||
"payload": {
|
||||
"masakari_object.name": "NotificationApiPayload",
|
||||
"masakari_object.data": {
|
||||
"notification_uuid": "15505a8c-8856-4f3d-9747-55b6e899c0f5",
|
||||
"status": "ignored",
|
||||
"source_host_uuid": "6bfaf80d-7592-4ea8-ad12-60d45476d056",
|
||||
"fault": null,
|
||||
"id": 47,
|
||||
"generated_time": "2017-06-13T15:34:55Z",
|
||||
"type": "VM",
|
||||
"payload": {"process_name": "nova-compute"}
|
||||
},
|
||||
"masakari_object.version": "1.0",
|
||||
"masakari_object.namespace": "masakari"
|
||||
},
|
||||
"priority": "INFO",
|
||||
"publisher_id": "masakari-engine:fake-mini",
|
||||
"message_id": "c081eb25-7450-4fa2-bb19-ae6d4466e14e"
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"event_type": "notification.process.error",
|
||||
"timestamp": "2018-12-20 06:21:19.315761",
|
||||
"payload": {
|
||||
"masakari_object.name": "NotificationApiPayload",
|
||||
"masakari_object.data": {
|
||||
"notification_uuid": "0adb94e0-8283-4702-9793-186d4ed914e8",
|
||||
"status": "running",
|
||||
"source_host_uuid": "6bfaf80d-7592-4ea8-ad12-60d45476d056",
|
||||
"fault": {
|
||||
"masakari_object.name": "ExceptionPayload",
|
||||
"masakari_object.data": {
|
||||
"module_name": "masakari.engine.manager",
|
||||
"exception": "str",
|
||||
"traceback": "Traceback (most recent call last):\n File \"/opt/stack/masakari/masakari/engine/manager.py\", line ...",
|
||||
"exception_message": "Failed to execute process recovery workflow.",
|
||||
"function_name": "_handle_notification_type_process"
|
||||
},
|
||||
"masakari_object.version": "1.0",
|
||||
"masakari_object.namespace": "masakari"
|
||||
},
|
||||
"id": 51,
|
||||
"generated_time": "2017-06-13T15:34:55Z",
|
||||
"type": "PROCESS",
|
||||
"payload": {"process_name": "nova-compute"}
|
||||
},
|
||||
"masakari_object.version": "1.0",
|
||||
"masakari_object.namespace": "masakari"
|
||||
},
|
||||
"priority": "ERROR",
|
||||
"publisher_id": "masakari-engine:fake-mini",
|
||||
"message_id": "5f3c9705-b3fb-41f9-a4e0-4868db93178c"
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"event_type": "notification.process.start",
|
||||
"timestamp": "2018-12-20 05:26:05.002421",
|
||||
"payload": {
|
||||
"masakari_object.name": "NotificationApiPayload",
|
||||
"masakari_object.data": {
|
||||
"notification_uuid": "15505a8c-8856-4f3d-9747-55b6e899c0f5",
|
||||
"status": "new",
|
||||
"source_host_uuid": "6bfaf80d-7592-4ea8-ad12-60d45476d056",
|
||||
"fault": null,
|
||||
"id": 47,
|
||||
"generated_time": "2017-06-13T15:34:55Z",
|
||||
"type": "VM",
|
||||
"payload": {"process_name": "nova-compute"}
|
||||
},
|
||||
"masakari_object.version": "1.0",
|
||||
"masakari_object.namespace": "masakari"
|
||||
},
|
||||
"priority": "INFO",
|
||||
"publisher_id": "masakari-engine:fake-mini",
|
||||
"message_id": "285be756-ac29-4b78-9e2b-9756f5077012"
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"event_type": "host.update.end",
|
||||
"timestamp": "2018-11-27 13:13:25.361394",
|
||||
"payload": {
|
||||
"masakari_object.name": "HostApiPayload",
|
||||
"masakari_object.data": {
|
||||
"reserved": false,
|
||||
"uuid": "d6a2d900-1977-48fd-aa52-ad7a41fc068b",
|
||||
"on_maintenance": false,
|
||||
"control_attributes": "TEST",
|
||||
"name": "fake-mini",
|
||||
"failover_segment": {
|
||||
"masakari_object.name": "FailoverSegment",
|
||||
"masakari_object.data": {
|
||||
"uuid": "89597691-bebd-4860-a93e-1b6e9de34b9e",
|
||||
"deleted": false,
|
||||
"created_at": "2018-11-27T09:26:30Z",
|
||||
"recovery_method": "auto",
|
||||
"updated_at": "2018-11-27T09:54:50Z",
|
||||
"name": "test",
|
||||
"service_type": "compute",
|
||||
"deleted_at": null,
|
||||
"id": 877,
|
||||
"description": null
|
||||
},
|
||||
"masakari_object.version": "1.0",
|
||||
"masakari_object.namespace": "masakari"
|
||||
},
|
||||
"fault": null,
|
||||
"type": "COMPUTE",
|
||||
"id": 70,
|
||||
"failover_segment_id": "89597691-bebd-4860-a93e-1b6e9de34b9e"
|
||||
},
|
||||
"masakari_object.version": "1.0",
|
||||
"masakari_object.namespace": "masakari"
|
||||
},
|
||||
"priority": "INFO",
|
||||
"publisher_id": "masakari-api:fake-mini",
|
||||
"message_id": "e7f85d49-7d02-4713-b90b-433f8e447558"
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
"event_type": "host.update.start",
|
||||
"timestamp": "2018-11-27 13:13:25.298007",
|
||||
"payload": {
|
||||
"masakari_object.name": "HostApiPayload",
|
||||
"masakari_object.data": {
|
||||
"reserved": false,
|
||||
"uuid": "d6a2d900-1977-48fd-aa52-ad7a41fc068b",
|
||||
"on_maintenance": false,
|
||||
"control_attributes": "TEST",
|
||||
"name": "fake-mini",
|
||||
"failover_segment": {
|
||||
"masakari_object.name": "FailoverSegment",
|
||||
"masakari_object.data": {
|
||||
"uuid": "89597691-bebd-4860-a93e-1b6e9de34b9e",
|
||||
"deleted": false,
|
||||
"created_at": "2018-11-27T09:26:30Z",
|
||||
"recovery_method": "auto",
|
||||
"updated_at": "2018-11-27T09:54:50Z",
|
||||
"name": "test",
|
||||
"service_type": "compute",
|
||||
"deleted_at": null,
|
||||
"id": 877, "description": null
|
||||
},
|
||||
"masakari_object.version": "1.0",
|
||||
"masakari_object.namespace": "masakari"
|
||||
},
|
||||
"fault": null,
|
||||
"type": "COMPUTE",
|
||||
"id": 70,
|
||||
"failover_segment_id": "89597691-bebd-4860-a93e-1b6e9de34b9e"
|
||||
},
|
||||
"masakari_object.version": "1.0",
|
||||
"masakari_object.namespace": "masakari"
|
||||
},
|
||||
"priority": "INFO",
|
||||
"publisher_id": "masakari-api:fake-mini",
|
||||
"message_id": "d1a3ae84-7f41-4884-bc3f-fa34c7cd1424"
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"event_type": "segment.update.end",
|
||||
"timestamp": "2018-11-27 14:32:20.417745",
|
||||
"payload": {
|
||||
"masakari_object.name": "SegmentApiPayload",
|
||||
"masakari_object.data": {
|
||||
"description": null,
|
||||
"fault": null,
|
||||
"recovery_method": "auto",
|
||||
"name": "test2",
|
||||
"service_type": "compute",
|
||||
"id": 877,
|
||||
"uuid": "89597691-bebd-4860-a93e-1b6e9de34b9e"
|
||||
},
|
||||
"masakari_object.version": "1.0",
|
||||
"masakari_object.namespace": "masakari"
|
||||
},
|
||||
"priority": "INFO",
|
||||
"publisher_id": "masakari-api:fake-mini",
|
||||
"message_id": "3fbe50a5-9175-4161-85f0-e502f9024657"
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"event_type": "segment.update.start",
|
||||
"timestamp": "2018-11-27 14:32:20.396940",
|
||||
"payload": {
|
||||
"masakari_object.name": "SegmentApiPayload",
|
||||
"masakari_object.data": {
|
||||
"description": null,
|
||||
"fault": null,
|
||||
"recovery_method": "auto",
|
||||
"name": "test",
|
||||
"service_type": "compute",
|
||||
"id": 877,
|
||||
"uuid": "89597691-bebd-4860-a93e-1b6e9de34b9e"
|
||||
},
|
||||
"masakari_object.version": "1.0",
|
||||
"masakari_object.namespace": "masakari"
|
||||
},
|
||||
"priority": "INFO",
|
||||
"publisher_id": "masakari-api:fake-mini",
|
||||
"message_id": "e6322900-025d-4dd6-a3a1-3e0e1e9badeb"
|
||||
}
|
|
@ -16,6 +16,7 @@ import os
|
|||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.abspath('../..'))
|
||||
sys.path.insert(0, os.path.abspath('../'))
|
||||
# -- General configuration ----------------------------------------------------
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
|
@ -23,7 +24,8 @@ sys.path.insert(0, os.path.abspath('../..'))
|
|||
extensions = [
|
||||
'sphinx.ext.autodoc',
|
||||
#'sphinx.ext.intersphinx',
|
||||
'oslosphinx'
|
||||
'oslosphinx',
|
||||
'ext.versioned_notifications'
|
||||
]
|
||||
|
||||
# autodoc generation is a bit aggressive and a nuisance when doing heavy
|
||||
|
|
|
@ -76,3 +76,13 @@ Indices and tables
|
|||
==================
|
||||
|
||||
* :ref:`search`
|
||||
|
||||
Versioned Notifications
|
||||
=======================
|
||||
|
||||
This provides the list of existing versioned notifications with sample payloads.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
notifications
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
..
|
||||
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.
|
||||
|
||||
Notifications in Masakari
|
||||
==========================
|
||||
|
||||
Similarly to other OpenStack services Masakari emits notifications to the message
|
||||
bus with the Notifier class provided by `oslo.messaging-doc`_. From the notification
|
||||
consumer point of view a notification consists of two parts: an envelope with a fixed
|
||||
structure defined by oslo.messaging and a payload defined by the service emitting the
|
||||
notification. The envelope format is the following::
|
||||
|
||||
{
|
||||
"priority": <string, selected from a predefined list by the sender>,
|
||||
"event_type": <string, defined by the sender>,
|
||||
"timestamp": <string, the isotime of when the notification emitted>,
|
||||
"publisher_id": <string, defined by the sender>,
|
||||
"message_id": <uuid, generated by oslo>,
|
||||
"payload": <json serialized dict, defined by the sender>
|
||||
}
|
||||
|
||||
oslo.messaging provides below choices of notification drivers:
|
||||
=============== ==========================================================================
|
||||
Driver Description
|
||||
=============== ==========================================================================
|
||||
messaging Send notifications using the 1.0 message format
|
||||
messagingv2 Send notifications using the 2.0 message format (with a message envelope)
|
||||
routing Configurable routing notifier (by priority or event_type)
|
||||
log Publish notifications via Python logging infrastructure
|
||||
test Store notifications in memory for test verification
|
||||
noop Disable sending notifications entirely
|
||||
=============== ==========================================================================
|
||||
|
||||
So notifications can be completely disabled by setting the following in
|
||||
Masakari configuration file:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[oslo_messaging_notifications]
|
||||
driver = noop
|
||||
|
||||
Masakari supports only Versioned notifications.
|
||||
|
||||
Versioned notifications
|
||||
-----------------------
|
||||
Masakari code uses the masakari.rpc.get_notifier call to get a configured
|
||||
oslo.messaging Notifier object and it uses the oslo provided functions on the
|
||||
Notifier object to emit notifications. The configuration of the returned
|
||||
Notifier object depends on the parameters of the get_notifier call and the
|
||||
value of the oslo.messaging configuration options ``driver`` and ``topics``.
|
||||
The versioned notification the the payload is not a free form dictionary but a
|
||||
serialized `oslo.versionedobjects-doc`_.
|
||||
|
||||
.. _service.update:
|
||||
|
||||
For example the wire format of the ``segment.update`` notification looks like
|
||||
the following::
|
||||
|
||||
{
|
||||
"event_type": "api.update.segments.start",
|
||||
"timestamp": "2018-11-27 14:32:20.396940",
|
||||
"payload": {
|
||||
"masakari_object.name": "SegmentApiPayload",
|
||||
"masakari_object.data": {
|
||||
"description": null,
|
||||
"fault": null,
|
||||
"recovery_method": "auto",
|
||||
"name": "test",
|
||||
"service_type": "compute",
|
||||
"id": 877,
|
||||
"uuid": "89597691-bebd-4860-a93e-1b6e9de34b9e"
|
||||
}, "
|
||||
"masakari_object.version": "1.0",
|
||||
"masakari_object.namespace": "masakari"
|
||||
},
|
||||
"priority": "INFO",
|
||||
"publisher_id": "masakari-api:test-virtualbox",
|
||||
"message_id": "e6322900-025d-4dd6-a3a1-3e0e1e9badeb"
|
||||
}
|
||||
|
||||
The serialized oslo versionedobject as a payload provides a version number to
|
||||
the consumer so the consumer can detect if the structure of the payload is
|
||||
changed. Masakari provides the following contract regarding the versioned
|
||||
notification payload:
|
||||
|
||||
* the payload version defined by the ``masakari_object.version`` field of the
|
||||
payload will be increased if and only if the syntax or the semantics of the
|
||||
``masakari_object.data`` field of the payload is changed.
|
||||
* a minor version bump indicates a backward compatible change which means that
|
||||
only new fields are added to the payload so a well written consumer can still
|
||||
consume the new payload without any change.
|
||||
* a major version bump indicates a backward incompatible change of the payload
|
||||
which can mean removed fields, type change, etc in the payload.
|
||||
* there is an additional field 'masakari_object.name' for every payload besides
|
||||
'masakari_object.data' and 'masakari_object.version'. This field contains the name of
|
||||
the Masakari internal representation of the payload type. Client code should not
|
||||
depend on this name.
|
||||
|
||||
Existing versioned notifications
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
* This provides the list of existing versioned notifications with sample payloads.
|
||||
|
||||
.. versioned_notifications::
|
||||
.. _`oslo.messaging-doc`: http://docs.openstack.org/developer/oslo.messaging/notifier.html
|
||||
.. _`oslo.versionedobjects-doc`: http://docs.openstack.org/developer/oslo.messaging/notifier.html
|
|
@ -21,6 +21,7 @@ notifications. It is responsible for processing notifications and executing
|
|||
workflows.
|
||||
|
||||
"""
|
||||
import traceback
|
||||
|
||||
from oslo_log import log as logging
|
||||
import oslo_messaging as messaging
|
||||
|
@ -30,6 +31,7 @@ from oslo_utils import timeutils
|
|||
import masakari.conf
|
||||
from masakari.engine import driver
|
||||
from masakari.engine import instance_events as virt_events
|
||||
from masakari.engine import utils as engine_utils
|
||||
from masakari import exception
|
||||
from masakari import manager
|
||||
from masakari import objects
|
||||
|
@ -58,6 +60,7 @@ class MasakariManager(manager.Manager):
|
|||
notification_status = fields.NotificationStatus.FINISHED
|
||||
notification_event = notification.payload.get('event')
|
||||
process_name = notification.payload.get('process_name')
|
||||
exception_info = None
|
||||
|
||||
if notification_event.upper() == 'STARTED':
|
||||
LOG.info("Notification type '%(type)s' received for host "
|
||||
|
@ -83,11 +86,12 @@ class MasakariManager(manager.Manager):
|
|||
self.driver.execute_process_failure(
|
||||
context, process_name, host_name,
|
||||
notification.notification_uuid)
|
||||
except exception.SkipProcessRecoveryException:
|
||||
except exception.SkipProcessRecoveryException as e:
|
||||
notification_status = fields.NotificationStatus.FINISHED
|
||||
except (exception.MasakariException,
|
||||
exception.ProcessRecoveryFailureException):
|
||||
exception.ProcessRecoveryFailureException) as e:
|
||||
notification_status = fields.NotificationStatus.ERROR
|
||||
exception_info = e
|
||||
else:
|
||||
LOG.warning("Invalid event: %(event)s received for "
|
||||
"notification type: %(notification_type)s",
|
||||
|
@ -95,6 +99,20 @@ class MasakariManager(manager.Manager):
|
|||
'notification_type': notification.type})
|
||||
notification_status = fields.NotificationStatus.IGNORED
|
||||
|
||||
if exception_info:
|
||||
tb = traceback.format_exc()
|
||||
engine_utils.notify_about_notification_update(context,
|
||||
notification,
|
||||
action=fields.EventNotificationAction.NOTIFICATION_PROCESS,
|
||||
phase=fields.EventNotificationPhase.ERROR,
|
||||
exception=str(exception_info),
|
||||
tb=tb)
|
||||
else:
|
||||
engine_utils.notify_about_notification_update(context,
|
||||
notification,
|
||||
action=fields.EventNotificationAction.NOTIFICATION_PROCESS,
|
||||
phase=fields.EventNotificationPhase.END)
|
||||
|
||||
return notification_status
|
||||
|
||||
def _handle_notification_type_instance(self, context, notification):
|
||||
|
@ -106,23 +124,41 @@ class MasakariManager(manager.Manager):
|
|||
return fields.NotificationStatus.IGNORED
|
||||
|
||||
notification_status = fields.NotificationStatus.FINISHED
|
||||
exception_info = None
|
||||
try:
|
||||
self.driver.execute_instance_failure(
|
||||
context, notification.payload.get('instance_uuid'),
|
||||
notification.notification_uuid)
|
||||
except exception.IgnoreInstanceRecoveryException:
|
||||
except exception.IgnoreInstanceRecoveryException as e:
|
||||
notification_status = fields.NotificationStatus.IGNORED
|
||||
except exception.SkipInstanceRecoveryException:
|
||||
exception_info = e
|
||||
except exception.SkipInstanceRecoveryException as e:
|
||||
notification_status = fields.NotificationStatus.FINISHED
|
||||
except (exception.MasakariException,
|
||||
exception.InstanceRecoveryFailureException):
|
||||
exception.InstanceRecoveryFailureException) as e:
|
||||
notification_status = fields.NotificationStatus.ERROR
|
||||
exception_info = e
|
||||
|
||||
if exception_info:
|
||||
tb = traceback.format_exc()
|
||||
engine_utils.notify_about_notification_update(context,
|
||||
notification,
|
||||
action=fields.EventNotificationAction.NOTIFICATION_PROCESS,
|
||||
phase=fields.EventNotificationPhase.ERROR,
|
||||
exception=str(exception_info),
|
||||
tb=tb)
|
||||
else:
|
||||
engine_utils.notify_about_notification_update(context,
|
||||
notification,
|
||||
action=fields.EventNotificationAction.NOTIFICATION_PROCESS,
|
||||
phase=fields.EventNotificationPhase.END)
|
||||
|
||||
return notification_status
|
||||
|
||||
def _handle_notification_type_host(self, context, notification):
|
||||
notification_status = fields.NotificationStatus.FINISHED
|
||||
notification_event = notification.payload.get('event')
|
||||
exception_info = None
|
||||
|
||||
if notification_event.upper() == 'STARTED':
|
||||
LOG.info("Notification type '%(type)s' received for host "
|
||||
|
@ -163,12 +199,13 @@ class MasakariManager(manager.Manager):
|
|||
context, host_name, recovery_method,
|
||||
notification.notification_uuid,
|
||||
reserved_host_list=reserved_host_list)
|
||||
except exception.SkipHostRecoveryException:
|
||||
except exception.SkipHostRecoveryException as e:
|
||||
notification_status = fields.NotificationStatus.FINISHED
|
||||
except (exception.HostRecoveryFailureException,
|
||||
exception.ReservedHostsUnavailable,
|
||||
exception.MasakariException):
|
||||
exception.MasakariException) as e:
|
||||
notification_status = fields.NotificationStatus.ERROR
|
||||
exception_info = e
|
||||
else:
|
||||
LOG.warning("Invalid event: %(event)s received for "
|
||||
"notification type: %(type)s",
|
||||
|
@ -176,6 +213,20 @@ class MasakariManager(manager.Manager):
|
|||
'type': notification.type})
|
||||
notification_status = fields.NotificationStatus.IGNORED
|
||||
|
||||
if exception_info:
|
||||
tb = traceback.format_exc()
|
||||
engine_utils.notify_about_notification_update(context,
|
||||
notification,
|
||||
action=fields.EventNotificationAction.NOTIFICATION_PROCESS,
|
||||
phase=fields.EventNotificationPhase.ERROR,
|
||||
exception=str(exception_info),
|
||||
tb=tb)
|
||||
else:
|
||||
engine_utils.notify_about_notification_update(context,
|
||||
notification,
|
||||
action=fields.EventNotificationAction.NOTIFICATION_PROCESS,
|
||||
phase=fields.EventNotificationPhase.END)
|
||||
|
||||
return notification_status
|
||||
|
||||
def _process_notification(self, context, notification):
|
||||
|
@ -233,6 +284,11 @@ class MasakariManager(manager.Manager):
|
|||
notification.update(update_data)
|
||||
notification.save()
|
||||
|
||||
engine_utils.notify_about_notification_update(context,
|
||||
notification,
|
||||
action=fields.EventNotificationAction.NOTIFICATION_PROCESS,
|
||||
phase=fields.EventNotificationPhase.START)
|
||||
|
||||
do_process_notification(notification)
|
||||
|
||||
def process_notification(self, context, notification=None):
|
||||
|
|
|
@ -13,11 +13,14 @@
|
|||
# under the License.
|
||||
|
||||
import datetime
|
||||
import traceback
|
||||
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import excutils
|
||||
from oslo_utils import strutils
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from masakari.api import utils as api_utils
|
||||
from masakari.compute import nova
|
||||
import masakari.conf
|
||||
from masakari.engine import rpcapi as engine_rpcapi
|
||||
|
@ -91,8 +94,15 @@ class FailoverSegmentAPI(object):
|
|||
segment.recovery_method = segment_data.get('recovery_method')
|
||||
segment.service_type = segment_data.get('service_type')
|
||||
|
||||
segment.create()
|
||||
|
||||
try:
|
||||
segment.create()
|
||||
except Exception as e:
|
||||
with excutils.save_and_reraise_exception():
|
||||
tb = traceback.format_exc()
|
||||
api_utils.notify_about_segment_api(context, segment,
|
||||
action=fields.EventNotificationAction.SEGMENT_CREATE,
|
||||
phase=fields.EventNotificationPhase.ERROR, exception=e,
|
||||
tb=tb)
|
||||
return segment
|
||||
|
||||
def update_segment(self, context, uuid, segment_data):
|
||||
|
@ -104,8 +114,16 @@ class FailoverSegmentAPI(object):
|
|||
LOG.error(msg)
|
||||
raise exception.FailoverSegmentInUse(msg)
|
||||
|
||||
segment.update(segment_data)
|
||||
segment.save()
|
||||
try:
|
||||
segment.update(segment_data)
|
||||
segment.save()
|
||||
except Exception as e:
|
||||
with excutils.save_and_reraise_exception():
|
||||
tb = traceback.format_exc()
|
||||
api_utils.notify_about_segment_api(context, segment,
|
||||
action=fields.EventNotificationAction.SEGMENT_UPDATE,
|
||||
phase=fields.EventNotificationPhase.ERROR, exception=e,
|
||||
tb=tb)
|
||||
return segment
|
||||
|
||||
def delete_segment(self, context, uuid):
|
||||
|
@ -117,7 +135,15 @@ class FailoverSegmentAPI(object):
|
|||
LOG.error(msg)
|
||||
raise exception.FailoverSegmentInUse(msg)
|
||||
|
||||
segment.destroy()
|
||||
try:
|
||||
segment.destroy()
|
||||
except Exception as e:
|
||||
with excutils.save_and_reraise_exception():
|
||||
tb = traceback.format_exc()
|
||||
api_utils.notify_about_segment_api(context, segment,
|
||||
action=fields.EventNotificationAction.SEGMENT_DELETE,
|
||||
phase=fields.EventNotificationPhase.ERROR, exception=e,
|
||||
tb=tb)
|
||||
|
||||
|
||||
class HostAPI(object):
|
||||
|
@ -173,7 +199,16 @@ class HostAPI(object):
|
|||
|
||||
self._is_valid_host_name(context, host.name)
|
||||
|
||||
host.create()
|
||||
try:
|
||||
host.create()
|
||||
except Exception as e:
|
||||
with excutils.save_and_reraise_exception():
|
||||
tb = traceback.format_exc()
|
||||
api_utils.notify_about_host_api(context, host,
|
||||
action=fields.EventNotificationAction.HOST_CREATE,
|
||||
phase=fields.EventNotificationPhase.ERROR, exception=e,
|
||||
tb=tb)
|
||||
|
||||
return host
|
||||
|
||||
def update_host(self, context, segment_uuid, id, host_data):
|
||||
|
@ -198,16 +233,22 @@ class HostAPI(object):
|
|||
host_data['reserved'] = strutils.bool_from_string(
|
||||
host_data['reserved'], strict=True)
|
||||
|
||||
host.update(host_data)
|
||||
|
||||
host.save()
|
||||
|
||||
try:
|
||||
host.update(host_data)
|
||||
host.save()
|
||||
except Exception as e:
|
||||
with excutils.save_and_reraise_exception():
|
||||
tb = traceback.format_exc()
|
||||
api_utils.notify_about_host_api(context, host,
|
||||
action=fields.EventNotificationAction.HOST_UPDATE,
|
||||
phase=fields.EventNotificationPhase.ERROR, exception=e,
|
||||
tb=tb)
|
||||
return host
|
||||
|
||||
def delete_host(self, context, segment_uuid, id):
|
||||
"""Delete the host"""
|
||||
segment = objects.FailoverSegment.get_by_uuid(context, segment_uuid)
|
||||
|
||||
segment = objects.FailoverSegment.get_by_uuid(context, segment_uuid)
|
||||
host = objects.Host.get_by_uuid(context, id, segment_uuid=segment_uuid)
|
||||
if is_failover_segment_under_recovery(segment):
|
||||
msg = _("Host %s can't be deleted as "
|
||||
|
@ -215,7 +256,15 @@ class HostAPI(object):
|
|||
LOG.error(msg)
|
||||
raise exception.HostInUse(msg)
|
||||
|
||||
host.destroy()
|
||||
try:
|
||||
host.destroy()
|
||||
except Exception as e:
|
||||
with excutils.save_and_reraise_exception():
|
||||
tb = traceback.format_exc()
|
||||
api_utils.notify_about_host_api(context, host,
|
||||
action=fields.EventNotificationAction.HOST_DELETE,
|
||||
phase=fields.EventNotificationPhase.ERROR, exception=e,
|
||||
tb=tb)
|
||||
|
||||
|
||||
class NotificationAPI(object):
|
||||
|
@ -279,9 +328,16 @@ class NotificationAPI(object):
|
|||
{'host': host_name, 'type': notification.type})
|
||||
raise exception.DuplicateNotification(message=message)
|
||||
|
||||
notification.create()
|
||||
self.engine_rpcapi.process_notification(context, notification)
|
||||
|
||||
try:
|
||||
notification.create()
|
||||
self.engine_rpcapi.process_notification(context, notification)
|
||||
except Exception as e:
|
||||
with excutils.save_and_reraise_exception():
|
||||
tb = traceback.format_exc()
|
||||
api_utils.notify_about_notification_api(context, notification,
|
||||
action=fields.EventNotificationAction.NOTIFICATION_CREATE,
|
||||
phase=fields.EventNotificationPhase.ERROR, exception=e,
|
||||
tb=tb)
|
||||
return notification
|
||||
|
||||
def get_all(self, context, filters=None, sort_keys=None,
|
||||
|
|
|
@ -154,13 +154,10 @@ class NotificationApiPayload(NotificationApiPayloadBase):
|
|||
|
||||
@base.notification_sample('create-segment-start.json')
|
||||
@base.notification_sample('create-segment-end.json')
|
||||
@base.notification_sample('create-segment-error.json')
|
||||
@base.notification_sample('update-segment-start.json')
|
||||
@base.notification_sample('update-segment-end.json')
|
||||
@base.notification_sample('update-segment-error.json')
|
||||
@base.notification_sample('delete-segment-start.json')
|
||||
@base.notification_sample('delete-segment-end.json')
|
||||
@base.notification_sample('delete-segment-error.json')
|
||||
@masakari_base.MasakariObjectRegistry.register_notification
|
||||
class SegmentApiNotification(base.NotificationBase):
|
||||
# Version 1.0: Initial version
|
||||
|
@ -173,13 +170,10 @@ class SegmentApiNotification(base.NotificationBase):
|
|||
|
||||
@base.notification_sample('create-host-start.json')
|
||||
@base.notification_sample('create-host-end.json')
|
||||
@base.notification_sample('create-host-error.json')
|
||||
@base.notification_sample('update-host-start.json')
|
||||
@base.notification_sample('update-host-end.json')
|
||||
@base.notification_sample('update-host-error.json')
|
||||
@base.notification_sample('delete-host-start.json')
|
||||
@base.notification_sample('delete-host-end.json')
|
||||
@base.notification_sample('delete-host-error.json')
|
||||
@masakari_base.MasakariObjectRegistry.register_notification
|
||||
class HostApiNotification(base.NotificationBase):
|
||||
# Version 1.0: Initial version
|
||||
|
@ -192,7 +186,9 @@ class HostApiNotification(base.NotificationBase):
|
|||
|
||||
@base.notification_sample('create-notification-start.json')
|
||||
@base.notification_sample('create-notification-end.json')
|
||||
@base.notification_sample('create-notification-error.json')
|
||||
@base.notification_sample('process-notification-start.json')
|
||||
@base.notification_sample('process-notification-end.json')
|
||||
@base.notification_sample('process-notification-error.json')
|
||||
@masakari_base.MasakariObjectRegistry.register_notification
|
||||
class NotificationApiNotification(base.NotificationBase):
|
||||
# Version 1.0: Initial version
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
from oslo_log import log as logging
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from masakari.api import utils as api_utils
|
||||
from masakari import db
|
||||
from masakari import exception
|
||||
from masakari import objects
|
||||
|
@ -91,7 +92,17 @@ class Host(base.MasakariPersistentObject, base.MasakariObject,
|
|||
raise exception.ObjectActionError(action='create',
|
||||
reason='failover segment '
|
||||
'assigned')
|
||||
|
||||
api_utils.notify_about_host_api(self._context, self,
|
||||
action=fields.EventNotificationAction.HOST_CREATE,
|
||||
phase=fields.EventNotificationPhase.START)
|
||||
|
||||
db_host = db.host_create(self._context, updates)
|
||||
|
||||
api_utils.notify_about_host_api(self._context, self,
|
||||
action=fields.EventNotificationAction.HOST_CREATE,
|
||||
phase=fields.EventNotificationPhase.END)
|
||||
|
||||
self._from_db_object(self._context, self, db_host)
|
||||
|
||||
@base.remotable
|
||||
|
@ -102,7 +113,17 @@ class Host(base.MasakariPersistentObject, base.MasakariObject,
|
|||
reason='failover segment '
|
||||
'changed')
|
||||
updates.pop('id', None)
|
||||
|
||||
api_utils.notify_about_host_api(self._context, self,
|
||||
action=fields.EventNotificationAction.HOST_UPDATE,
|
||||
phase=fields.EventNotificationPhase.START)
|
||||
|
||||
db_host = db.host_update(self._context, self.uuid, updates)
|
||||
|
||||
api_utils.notify_about_host_api(self._context, self,
|
||||
action=fields.EventNotificationAction.HOST_UPDATE,
|
||||
phase=fields.EventNotificationPhase.END)
|
||||
|
||||
self._from_db_object(self._context, self, db_host)
|
||||
|
||||
@base.remotable
|
||||
|
@ -114,7 +135,16 @@ class Host(base.MasakariPersistentObject, base.MasakariObject,
|
|||
raise exception.ObjectActionError(action='destroy',
|
||||
reason='no uuid')
|
||||
|
||||
api_utils.notify_about_host_api(self._context, self,
|
||||
action=fields.EventNotificationAction.HOST_DELETE,
|
||||
phase=fields.EventNotificationPhase.START)
|
||||
|
||||
db.host_delete(self._context, self.uuid)
|
||||
|
||||
api_utils.notify_about_host_api(self._context, self,
|
||||
action=fields.EventNotificationAction.HOST_DELETE,
|
||||
phase=fields.EventNotificationPhase.END)
|
||||
|
||||
delattr(self, base.get_attrname('id'))
|
||||
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ from oslo_log import log as logging
|
|||
from oslo_serialization import jsonutils
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from masakari.api import utils as api_utils
|
||||
from masakari import db
|
||||
from masakari import exception
|
||||
from masakari import objects
|
||||
|
@ -81,7 +82,16 @@ class Notification(base.MasakariPersistentObject, base.MasakariObject,
|
|||
if 'payload' in updates:
|
||||
updates['payload'] = jsonutils.dumps(updates['payload'])
|
||||
|
||||
api_utils.notify_about_notification_api(self._context, self,
|
||||
action=fields.EventNotificationAction.NOTIFICATION_CREATE,
|
||||
phase=fields.EventNotificationPhase.START)
|
||||
|
||||
db_notification = db.notification_create(self._context, updates)
|
||||
|
||||
api_utils.notify_about_notification_api(self._context, self,
|
||||
action=fields.EventNotificationAction.NOTIFICATION_CREATE,
|
||||
phase=fields.EventNotificationPhase.END)
|
||||
|
||||
self._from_db_object(self._context, self, db_notification)
|
||||
|
||||
@base.remotable
|
||||
|
@ -129,3 +139,16 @@ class NotificationList(base.ObjectListBase, base.MasakariObject):
|
|||
|
||||
return base.obj_make_list(context, cls(context), objects.Notification,
|
||||
groups)
|
||||
|
||||
|
||||
def notification_sample(sample):
|
||||
"""Class decorator to attach the notification sample information
|
||||
to the notification object for documentation generation purposes.
|
||||
:param sample: the path of the sample json file relative to the
|
||||
doc/notification_samples/ directory in the nova repository
|
||||
root.
|
||||
"""
|
||||
def wrap(cls):
|
||||
cls.sample = sample
|
||||
return cls
|
||||
return wrap
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
from oslo_log import log as logging
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from masakari.api import utils as api_utils
|
||||
from masakari import db
|
||||
from masakari import exception
|
||||
from masakari import objects
|
||||
|
@ -74,15 +75,34 @@ class FailoverSegment(base.MasakariPersistentObject, base.MasakariObject,
|
|||
LOG.debug('Generated uuid %(uuid)s for failover segment',
|
||||
dict(uuid=updates['uuid']))
|
||||
|
||||
api_utils.notify_about_segment_api(self._context, self,
|
||||
action=fields.EventNotificationAction.SEGMENT_CREATE,
|
||||
phase=fields.EventNotificationPhase.START)
|
||||
|
||||
db_segment = db.failover_segment_create(self._context, updates)
|
||||
|
||||
api_utils.notify_about_segment_api(self._context, self,
|
||||
action=fields.EventNotificationAction.SEGMENT_CREATE,
|
||||
phase=fields.EventNotificationPhase.END)
|
||||
|
||||
self._from_db_object(self._context, self, db_segment)
|
||||
|
||||
@base.remotable
|
||||
def save(self):
|
||||
updates = self.masakari_obj_get_changes()
|
||||
updates.pop('id', None)
|
||||
|
||||
api_utils.notify_about_segment_api(self._context, self,
|
||||
action=fields.EventNotificationAction.SEGMENT_UPDATE,
|
||||
phase=fields.EventNotificationPhase.START)
|
||||
|
||||
db_segment = db.failover_segment_update(self._context,
|
||||
self.uuid, updates)
|
||||
|
||||
api_utils.notify_about_segment_api(self._context, self,
|
||||
action=fields.EventNotificationAction.SEGMENT_UPDATE,
|
||||
phase=fields.EventNotificationPhase.END)
|
||||
|
||||
self._from_db_object(self._context, self, db_segment)
|
||||
|
||||
@base.remotable
|
||||
|
@ -94,7 +114,16 @@ class FailoverSegment(base.MasakariPersistentObject, base.MasakariObject,
|
|||
raise exception.ObjectActionError(action='destroy',
|
||||
reason='no uuid')
|
||||
|
||||
api_utils.notify_about_segment_api(self._context, self,
|
||||
action=fields.EventNotificationAction.SEGMENT_DELETE,
|
||||
phase=fields.EventNotificationPhase.START)
|
||||
|
||||
db.failover_segment_delete(self._context, self.uuid)
|
||||
|
||||
api_utils.notify_about_segment_api(self._context, self,
|
||||
action=fields.EventNotificationAction.SEGMENT_DELETE,
|
||||
phase=fields.EventNotificationPhase.END)
|
||||
|
||||
delattr(self, base.get_attrname('id'))
|
||||
|
||||
def is_under_recovery(self, filters=None):
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import os
|
||||
|
||||
from oslo_serialization import jsonutils
|
||||
|
||||
|
||||
def _resolve_ref(ref, base_path):
|
||||
file_path, _, json_path = ref.partition('#')
|
||||
if json_path:
|
||||
raise NotImplementedError('JSON refs with JSON path after the "#" is '
|
||||
'not yet supported')
|
||||
|
||||
path = os.path.join(base_path, file_path)
|
||||
# binary mode is needed due to bug/1515231
|
||||
with open(path, 'r+b') as f:
|
||||
ref_value = jsonutils.load(f)
|
||||
base_path = os.path.dirname(path)
|
||||
res = resolve_refs(ref_value, base_path)
|
||||
return res
|
||||
|
||||
|
||||
def resolve_refs(obj_with_refs, base_path):
|
||||
if isinstance(obj_with_refs, list):
|
||||
for i, item in enumerate(obj_with_refs):
|
||||
obj_with_refs[i] = resolve_refs(item, base_path)
|
||||
elif isinstance(obj_with_refs, dict):
|
||||
if '$ref' in obj_with_refs.keys():
|
||||
ref = obj_with_refs.pop('$ref')
|
||||
resolved_ref = _resolve_ref(ref, base_path)
|
||||
# the rest of the ref dict contains overrides for the ref. Resolve
|
||||
# refs in the overrides then apply those overrides recursively
|
||||
# here.
|
||||
resolved_overrides = resolve_refs(obj_with_refs, base_path)
|
||||
_update_dict_recursively(resolved_ref, resolved_overrides)
|
||||
return resolved_ref
|
||||
else:
|
||||
for key, value in obj_with_refs.items():
|
||||
obj_with_refs[key] = resolve_refs(value, base_path)
|
||||
else:
|
||||
# scalar, nothing to do
|
||||
pass
|
||||
|
||||
return obj_with_refs
|
||||
|
||||
|
||||
def _update_dict_recursively(d, update):
|
||||
"""Update dict d recursively with data from dict update"""
|
||||
for k, v in update.items():
|
||||
if k in d and isinstance(d[k], dict) and isinstance(v, dict):
|
||||
_update_dict_recursively(d[k], v)
|
||||
else:
|
||||
d[k] = v
|
|
@ -12,17 +12,18 @@
|
|||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import mock
|
||||
from oslo_utils import importutils
|
||||
from oslo_utils import timeutils
|
||||
|
||||
import masakari.conf
|
||||
from masakari import context
|
||||
from masakari.engine import utils as engine_utils
|
||||
from masakari import exception
|
||||
from masakari.objects import fields
|
||||
from masakari.objects import host as host_obj
|
||||
from masakari.objects import notification as notification_obj
|
||||
from masakari import rpc
|
||||
from masakari import test
|
||||
from masakari.tests.unit import fakes
|
||||
from masakari.tests import uuidsentinel
|
||||
|
@ -47,6 +48,7 @@ def _get_vm_type_notification(status="new"):
|
|||
class EngineManagerUnitTestCase(test.NoDBTestCase):
|
||||
def setUp(self):
|
||||
super(EngineManagerUnitTestCase, self).setUp()
|
||||
rpc.init(CONF)
|
||||
self.engine = importutils.import_object(CONF.engine_manager)
|
||||
self.context = context.RequestContext()
|
||||
|
||||
|
@ -77,8 +79,10 @@ class EngineManagerUnitTestCase(test.NoDBTestCase):
|
|||
@mock.patch("masakari.engine.drivers.taskflow."
|
||||
"TaskFlowDriver.execute_instance_failure")
|
||||
@mock.patch.object(notification_obj.Notification, "save")
|
||||
def test_process_notification_type_vm_success(self, mock_save,
|
||||
mock_instance_failure, mock_notification_get):
|
||||
@mock.patch.object(engine_utils, 'notify_about_notification_update')
|
||||
def test_process_notification_type_vm_success(self,
|
||||
mock_notify_about_notification_update, mock_save,
|
||||
mock_instance_failure, mock_notification_get):
|
||||
mock_instance_failure.side_effect = self._fake_notification_workflow()
|
||||
notification = _get_vm_type_notification()
|
||||
mock_notification_get.return_value = notification
|
||||
|
@ -88,12 +92,25 @@ class EngineManagerUnitTestCase(test.NoDBTestCase):
|
|||
mock_instance_failure.assert_called_once_with(
|
||||
self.context, notification.payload.get('instance_uuid'),
|
||||
notification.notification_uuid)
|
||||
action = fields.EventNotificationAction.NOTIFICATION_PROCESS
|
||||
phase_start = fields.EventNotificationPhase.START
|
||||
phase_end = fields.EventNotificationPhase.END
|
||||
notify_calls = [
|
||||
mock.call(self.context, notification, action=action,
|
||||
phase=phase_start),
|
||||
mock.call(self.context, notification, action=action,
|
||||
phase=phase_end)]
|
||||
mock_notify_about_notification_update.assert_has_calls(notify_calls)
|
||||
|
||||
@mock.patch("masakari.engine.drivers.taskflow."
|
||||
"TaskFlowDriver.execute_instance_failure")
|
||||
@mock.patch.object(notification_obj.Notification, "save")
|
||||
def test_process_notification_type_vm_error(self, mock_save,
|
||||
mock_instance_failure, mock_notification_get):
|
||||
@mock.patch.object(engine_utils, 'notify_about_notification_update')
|
||||
@mock.patch('traceback.format_exc')
|
||||
def test_process_notification_type_vm_error(self, mock_format,
|
||||
mock_notify_about_notification_update, mock_save,
|
||||
mock_instance_failure, mock_notification_get):
|
||||
mock_format.return_value = mock.ANY
|
||||
mock_instance_failure.side_effect = self._fake_notification_workflow(
|
||||
exc=exception.InstanceRecoveryFailureException)
|
||||
notification = _get_vm_type_notification()
|
||||
|
@ -104,6 +121,19 @@ class EngineManagerUnitTestCase(test.NoDBTestCase):
|
|||
mock_instance_failure.assert_called_once_with(
|
||||
self.context, notification.payload.get('instance_uuid'),
|
||||
notification.notification_uuid)
|
||||
e = exception.InstanceRecoveryFailureException('Failed to execute '
|
||||
'instance recovery workflow.')
|
||||
action = fields.EventNotificationAction.NOTIFICATION_PROCESS
|
||||
phase_start = fields.EventNotificationPhase.START
|
||||
phase_error = fields.EventNotificationPhase.ERROR
|
||||
notify_calls = [
|
||||
mock.call(self.context, notification, action=action,
|
||||
phase=phase_start),
|
||||
mock.call(self.context, notification, action=action,
|
||||
phase=phase_error,
|
||||
exception=str(e),
|
||||
tb=mock.ANY)]
|
||||
mock_notify_about_notification_update.assert_has_calls(notify_calls)
|
||||
|
||||
@mock.patch.object(notification_obj.Notification, "save")
|
||||
def test_process_notification_type_vm_error_event_unmatched(
|
||||
|
@ -125,8 +155,12 @@ class EngineManagerUnitTestCase(test.NoDBTestCase):
|
|||
@mock.patch("masakari.engine.drivers.taskflow."
|
||||
"TaskFlowDriver.execute_instance_failure")
|
||||
@mock.patch.object(notification_obj.Notification, "save")
|
||||
@mock.patch.object(engine_utils, 'notify_about_notification_update')
|
||||
@mock.patch('traceback.format_exc')
|
||||
def test_process_notification_type_vm_skip_recovery(
|
||||
self, mock_save, mock_instance_failure, mock_notification_get):
|
||||
self, mock_format, mock_notify_about_notification_update,
|
||||
mock_save, mock_instance_failure, mock_notification_get):
|
||||
mock_format.return_value = mock.ANY
|
||||
notification = _get_vm_type_notification()
|
||||
mock_notification_get.return_value = notification
|
||||
mock_instance_failure.side_effect = self._fake_notification_workflow(
|
||||
|
@ -137,14 +171,25 @@ class EngineManagerUnitTestCase(test.NoDBTestCase):
|
|||
mock_instance_failure.assert_called_once_with(
|
||||
self.context, notification.payload.get('instance_uuid'),
|
||||
notification.notification_uuid)
|
||||
action = fields.EventNotificationAction.NOTIFICATION_PROCESS
|
||||
phase_start = fields.EventNotificationPhase.START
|
||||
phase_end = fields.EventNotificationPhase.END
|
||||
notify_calls = [
|
||||
mock.call(self.context, notification, action=action,
|
||||
phase=phase_start),
|
||||
mock.call(self.context, notification, action=action,
|
||||
phase=phase_end)]
|
||||
mock_notify_about_notification_update.assert_has_calls(notify_calls)
|
||||
|
||||
@mock.patch.object(host_obj.Host, "get_by_uuid")
|
||||
@mock.patch.object(host_obj.Host, "save")
|
||||
@mock.patch("masakari.engine.drivers.taskflow."
|
||||
"TaskFlowDriver.execute_process_failure")
|
||||
@mock.patch.object(notification_obj.Notification, "save")
|
||||
@mock.patch.object(engine_utils, 'notify_about_notification_update')
|
||||
def test_process_notification_type_process_event_stopped(
|
||||
self, mock_notification_save, mock_process_failure,
|
||||
self, mock_notify_about_notification_update,
|
||||
mock_notification_save, mock_process_failure,
|
||||
mock_host_save, mock_host_obj, mock_notification_get):
|
||||
notification = self._get_process_type_notification()
|
||||
mock_notification_get.return_value = notification
|
||||
|
@ -158,15 +203,28 @@ class EngineManagerUnitTestCase(test.NoDBTestCase):
|
|||
self.context, notification.payload.get('process_name'),
|
||||
fake_host.name,
|
||||
notification.notification_uuid)
|
||||
action = fields.EventNotificationAction.NOTIFICATION_PROCESS
|
||||
phase_start = fields.EventNotificationPhase.START
|
||||
phase_end = fields.EventNotificationPhase.END
|
||||
notify_calls = [
|
||||
mock.call(self.context, notification, action=action,
|
||||
phase=phase_start),
|
||||
mock.call(self.context, notification, action=action,
|
||||
phase=phase_end)]
|
||||
mock_notify_about_notification_update.assert_has_calls(notify_calls)
|
||||
|
||||
@mock.patch.object(host_obj.Host, "get_by_uuid")
|
||||
@mock.patch.object(host_obj.Host, "save")
|
||||
@mock.patch("masakari.engine.drivers.taskflow."
|
||||
"TaskFlowDriver.execute_process_failure")
|
||||
@mock.patch.object(notification_obj.Notification, "save")
|
||||
@mock.patch.object(engine_utils, 'notify_about_notification_update')
|
||||
@mock.patch('traceback.format_exc')
|
||||
def test_process_notification_type_process_skip_recovery(
|
||||
self, mock_notification_save, mock_process_failure,
|
||||
self, mock_format, mock_notify_about_notification_update,
|
||||
mock_notification_save, mock_process_failure,
|
||||
mock_host_save, mock_host_obj, mock_notification_get):
|
||||
mock_format.return_value = mock.ANY
|
||||
notification = self._get_process_type_notification()
|
||||
mock_notification_get.return_value = notification
|
||||
fake_host = fakes.create_fake_host()
|
||||
|
@ -176,15 +234,28 @@ class EngineManagerUnitTestCase(test.NoDBTestCase):
|
|||
self.engine.process_notification(self.context,
|
||||
notification=notification)
|
||||
self.assertEqual("finished", notification.status)
|
||||
action = fields.EventNotificationAction.NOTIFICATION_PROCESS
|
||||
phase_start = fields.EventNotificationPhase.START
|
||||
phase_end = fields.EventNotificationPhase.END
|
||||
notify_calls = [
|
||||
mock.call(self.context, notification, action=action,
|
||||
phase=phase_start),
|
||||
mock.call(self.context, notification, action=action,
|
||||
phase=phase_end)]
|
||||
mock_notify_about_notification_update.assert_has_calls(notify_calls)
|
||||
|
||||
@mock.patch.object(host_obj.Host, "get_by_uuid")
|
||||
@mock.patch.object(host_obj.Host, "save")
|
||||
@mock.patch("masakari.engine.drivers.taskflow."
|
||||
"TaskFlowDriver.execute_process_failure")
|
||||
@mock.patch.object(notification_obj.Notification, "save")
|
||||
@mock.patch.object(engine_utils, 'notify_about_notification_update')
|
||||
@mock.patch('traceback.format_exc')
|
||||
def test_process_notification_type_process_recovery_failure(
|
||||
self, mock_notification_save, mock_process_failure,
|
||||
self, mock_format, mock_notify_about_notification_update,
|
||||
mock_notification_save, mock_process_failure,
|
||||
mock_host_save, mock_host_obj, mock_notification_get):
|
||||
mock_format.return_value = mock.ANY
|
||||
notification = self._get_process_type_notification()
|
||||
mock_notification_get.return_value = notification
|
||||
fake_host = fakes.create_fake_host()
|
||||
|
@ -194,15 +265,30 @@ class EngineManagerUnitTestCase(test.NoDBTestCase):
|
|||
self.engine.process_notification(self.context,
|
||||
notification=notification)
|
||||
self.assertEqual("error", notification.status)
|
||||
e = exception.ProcessRecoveryFailureException('Failed to execute '
|
||||
'process recovery workflow.')
|
||||
action = fields.EventNotificationAction.NOTIFICATION_PROCESS
|
||||
phase_start = fields.EventNotificationPhase.START
|
||||
phase_error = fields.EventNotificationPhase.ERROR
|
||||
notify_calls = [
|
||||
mock.call(self.context, notification, action=action,
|
||||
phase=phase_start),
|
||||
mock.call(self.context, notification, action=action,
|
||||
phase=phase_error,
|
||||
exception=str(e),
|
||||
tb=mock.ANY)]
|
||||
mock_notify_about_notification_update.assert_has_calls(notify_calls)
|
||||
|
||||
@mock.patch.object(host_obj.Host, "get_by_uuid")
|
||||
@mock.patch.object(host_obj.Host, "save")
|
||||
@mock.patch.object(notification_obj.Notification, "save")
|
||||
@mock.patch.object(engine_utils, 'notify_about_notification_update')
|
||||
@mock.patch("masakari.engine.drivers.taskflow."
|
||||
"TaskFlowDriver.execute_process_failure")
|
||||
def test_process_notification_type_process_event_started(
|
||||
self, mock_process_failure, mock_notification_save,
|
||||
mock_host_save, mock_host_obj, mock_notification_get):
|
||||
self, mock_process_failure, mock_notify_about_notification_update,
|
||||
mock_notification_save, mock_host_save, mock_host_obj,
|
||||
mock_notification_get):
|
||||
notification = self._get_process_type_notification()
|
||||
mock_notification_get.return_value = notification
|
||||
notification.payload['event'] = 'started'
|
||||
|
@ -212,13 +298,23 @@ class EngineManagerUnitTestCase(test.NoDBTestCase):
|
|||
notification=notification)
|
||||
self.assertEqual("finished", notification.status)
|
||||
self.assertFalse(mock_process_failure.called)
|
||||
action = fields.EventNotificationAction.NOTIFICATION_PROCESS
|
||||
phase_start = fields.EventNotificationPhase.START
|
||||
phase_end = fields.EventNotificationPhase.END
|
||||
notify_calls = [
|
||||
mock.call(self.context, notification, action=action,
|
||||
phase=phase_start),
|
||||
mock.call(self.context, notification, action=action,
|
||||
phase=phase_end)]
|
||||
mock_notify_about_notification_update.assert_has_calls(notify_calls)
|
||||
|
||||
@mock.patch.object(notification_obj.Notification, "save")
|
||||
@mock.patch.object(engine_utils, 'notify_about_notification_update')
|
||||
@mock.patch("masakari.engine.drivers.taskflow."
|
||||
"TaskFlowDriver.execute_process_failure")
|
||||
def test_process_notification_type_process_event_other(
|
||||
self, mock_process_failure, mock_notification_save,
|
||||
mock_notification_get):
|
||||
self, mock_process_failure, mock_notify_about_notification_update,
|
||||
mock_notification_save, mock_notification_get):
|
||||
notification = self._get_process_type_notification()
|
||||
mock_notification_get.return_value = notification
|
||||
notification.payload['event'] = 'other'
|
||||
|
@ -226,6 +322,15 @@ class EngineManagerUnitTestCase(test.NoDBTestCase):
|
|||
notification=notification)
|
||||
self.assertEqual("ignored", notification.status)
|
||||
self.assertFalse(mock_process_failure.called)
|
||||
action = fields.EventNotificationAction.NOTIFICATION_PROCESS
|
||||
phase_start = fields.EventNotificationPhase.START
|
||||
phase_end = fields.EventNotificationPhase.END
|
||||
notify_calls = [
|
||||
mock.call(self.context, notification, action=action,
|
||||
phase=phase_start),
|
||||
mock.call(self.context, notification, action=action,
|
||||
phase=phase_end)]
|
||||
mock_notify_about_notification_update.assert_has_calls(notify_calls)
|
||||
|
||||
@mock.patch.object(host_obj.Host, "get_by_uuid")
|
||||
@mock.patch.object(host_obj.Host, "save")
|
||||
|
@ -234,8 +339,10 @@ class EngineManagerUnitTestCase(test.NoDBTestCase):
|
|||
@mock.patch("masakari.engine.drivers.taskflow."
|
||||
"TaskFlowDriver.execute_host_failure")
|
||||
@mock.patch.object(notification_obj.Notification, "save")
|
||||
@mock.patch.object(engine_utils, 'notify_about_notification_update')
|
||||
def test_process_notification_type_compute_host_event_stopped(
|
||||
self, mock_notification_save, mock_host_failure, mock_get_all,
|
||||
self, mock_notify_about_notification_update,
|
||||
mock_notification_save, mock_host_failure, mock_get_all,
|
||||
mock_host_update, mock_host_save, mock_host_obj,
|
||||
mock_notification_get):
|
||||
notification = self._get_compute_host_type_notification()
|
||||
|
@ -257,16 +364,29 @@ class EngineManagerUnitTestCase(test.NoDBTestCase):
|
|||
self.context,
|
||||
fake_host.name, fake_host.failover_segment.recovery_method,
|
||||
notification.notification_uuid, reserved_host_list=None)
|
||||
action = fields.EventNotificationAction.NOTIFICATION_PROCESS
|
||||
phase_start = fields.EventNotificationPhase.START
|
||||
phase_end = fields.EventNotificationPhase.END
|
||||
notify_calls = [
|
||||
mock.call(self.context, notification, action=action,
|
||||
phase=phase_start),
|
||||
mock.call(self.context, notification, action=action,
|
||||
phase=phase_end)]
|
||||
mock_notify_about_notification_update.assert_has_calls(notify_calls)
|
||||
|
||||
@mock.patch.object(host_obj.Host, "get_by_uuid")
|
||||
@mock.patch.object(host_obj.Host, "save")
|
||||
@mock.patch.object(host_obj.Host, "update")
|
||||
@mock.patch.object(host_obj.HostList, "get_all")
|
||||
@mock.patch.object(notification_obj.Notification, "save")
|
||||
@mock.patch.object(engine_utils, 'notify_about_notification_update')
|
||||
@mock.patch('traceback.format_exc')
|
||||
def test_process_notification_host_failure_without_reserved_hosts(
|
||||
self, mock_notification_save, mock_get_all,
|
||||
self, mock_format, mock_notify_about_notification_update,
|
||||
mock_notification_save, mock_get_all,
|
||||
mock_host_update, mock_host_save, mock_host_obj,
|
||||
mock_notification_get):
|
||||
mock_format.return_value = mock.ANY
|
||||
reserved_host_list = []
|
||||
mock_get_all.return_value = reserved_host_list
|
||||
|
||||
|
@ -286,6 +406,19 @@ class EngineManagerUnitTestCase(test.NoDBTestCase):
|
|||
}
|
||||
mock_host_update.assert_called_once_with(update_data_by_host_failure)
|
||||
self.assertEqual("error", notification.status)
|
||||
action = fields.EventNotificationAction.NOTIFICATION_PROCESS
|
||||
phase_start = fields.EventNotificationPhase.START
|
||||
phase_error = fields.EventNotificationPhase.ERROR
|
||||
e = exception.ReservedHostsUnavailable(
|
||||
'No reserved_hosts available for evacuation.')
|
||||
notify_calls = [
|
||||
mock.call(self.context, notification, action=action,
|
||||
phase=phase_start),
|
||||
mock.call(self.context, notification, action=action,
|
||||
phase=phase_error,
|
||||
exception=str(e),
|
||||
tb=mock.ANY)]
|
||||
mock_notify_about_notification_update.assert_has_calls(notify_calls)
|
||||
|
||||
@mock.patch.object(host_obj.Host, "get_by_uuid")
|
||||
@mock.patch.object(host_obj.Host, "save")
|
||||
|
@ -294,8 +427,10 @@ class EngineManagerUnitTestCase(test.NoDBTestCase):
|
|||
@mock.patch("masakari.engine.drivers.taskflow."
|
||||
"TaskFlowDriver.execute_host_failure")
|
||||
@mock.patch.object(notification_obj.Notification, "save")
|
||||
@mock.patch.object(engine_utils, 'notify_about_notification_update')
|
||||
def test_process_notification_host_failure_with_reserved_hosts(
|
||||
self, mock_notification_save, mock_host_failure, mock_get_all,
|
||||
self, mock_notify_about_notification_update,
|
||||
mock_notification_save, mock_host_failure, mock_get_all,
|
||||
mock_host_update, mock_host_save, mock_host_obj,
|
||||
mock_notification_get):
|
||||
fake_host = fakes.create_fake_host()
|
||||
|
@ -325,6 +460,15 @@ class EngineManagerUnitTestCase(test.NoDBTestCase):
|
|||
mock_get_all.assert_called_once_with(self.context, filters={
|
||||
'failover_segment_id': fake_host.failover_segment.uuid,
|
||||
'reserved': True, 'on_maintenance': False})
|
||||
action = fields.EventNotificationAction.NOTIFICATION_PROCESS
|
||||
phase_start = fields.EventNotificationPhase.START
|
||||
phase_end = fields.EventNotificationPhase.END
|
||||
notify_calls = [
|
||||
mock.call(self.context, notification, action=action,
|
||||
phase=phase_start),
|
||||
mock.call(self.context, notification, action=action,
|
||||
phase=phase_end)]
|
||||
mock_notify_about_notification_update.assert_has_calls(notify_calls)
|
||||
|
||||
@mock.patch.object(host_obj.Host, "get_by_uuid")
|
||||
@mock.patch.object(host_obj.Host, "save")
|
||||
|
@ -333,8 +477,10 @@ class EngineManagerUnitTestCase(test.NoDBTestCase):
|
|||
@mock.patch("masakari.engine.drivers.taskflow."
|
||||
"TaskFlowDriver.execute_host_failure")
|
||||
@mock.patch.object(notification_obj.Notification, "save")
|
||||
@mock.patch.object(engine_utils, 'notify_about_notification_update')
|
||||
def test_process_notification_reserved_host_failure(
|
||||
self, mock_notification_save, mock_host_failure, mock_get_all,
|
||||
self, mock_notify_about_notification_update,
|
||||
mock_notification_save, mock_host_failure, mock_get_all,
|
||||
mock_host_update, mock_host_save, mock_host_obj,
|
||||
mock_notification_get):
|
||||
fake_host = fakes.create_fake_host(reserved=True)
|
||||
|
@ -362,6 +508,15 @@ class EngineManagerUnitTestCase(test.NoDBTestCase):
|
|||
fake_host.name, fake_host.failover_segment.recovery_method,
|
||||
notification.notification_uuid,
|
||||
reserved_host_list=reserved_host_list)
|
||||
action = fields.EventNotificationAction.NOTIFICATION_PROCESS
|
||||
phase_start = fields.EventNotificationPhase.START
|
||||
phase_end = fields.EventNotificationPhase.END
|
||||
notify_calls = [
|
||||
mock.call(self.context, notification, action=action,
|
||||
phase=phase_start),
|
||||
mock.call(self.context, notification, action=action,
|
||||
phase=phase_end)]
|
||||
mock_notify_about_notification_update.assert_has_calls(notify_calls)
|
||||
|
||||
@mock.patch.object(host_obj.Host, "get_by_uuid")
|
||||
@mock.patch.object(host_obj.Host, "save")
|
||||
|
@ -370,10 +525,14 @@ class EngineManagerUnitTestCase(test.NoDBTestCase):
|
|||
@mock.patch("masakari.engine.drivers.taskflow."
|
||||
"TaskFlowDriver.execute_host_failure")
|
||||
@mock.patch.object(notification_obj.Notification, "save")
|
||||
@mock.patch.object(engine_utils, 'notify_about_notification_update')
|
||||
@mock.patch('traceback.format_exc')
|
||||
def test_process_notification_type_compute_host_recovery_exception(
|
||||
self, mock_notification_save, mock_host_failure, mock_get_all,
|
||||
self, mock_format, mock_notify_about_notification_update,
|
||||
mock_notification_save, mock_host_failure, mock_get_all,
|
||||
mock_host_update, mock_host_save, mock_host_obj,
|
||||
mock_notification_get):
|
||||
mock_format.return_value = mock.ANY
|
||||
notification = self._get_compute_host_type_notification()
|
||||
mock_notification_get.return_value = notification
|
||||
fake_host = fakes.create_fake_host()
|
||||
|
@ -390,13 +549,69 @@ class EngineManagerUnitTestCase(test.NoDBTestCase):
|
|||
}
|
||||
mock_host_update.assert_called_once_with(update_data_by_host_failure)
|
||||
self.assertEqual("error", notification.status)
|
||||
e = exception.HostRecoveryFailureException('Failed to execute host '
|
||||
'recovery.')
|
||||
action = fields.EventNotificationAction.NOTIFICATION_PROCESS
|
||||
phase_start = fields.EventNotificationPhase.START
|
||||
phase_error = fields.EventNotificationPhase.ERROR
|
||||
notify_calls = [
|
||||
mock.call(self.context, notification, action=action,
|
||||
phase=phase_start),
|
||||
mock.call(self.context, notification, action=action,
|
||||
phase=phase_error,
|
||||
exception=str(e),
|
||||
tb=mock.ANY)]
|
||||
mock_notify_about_notification_update.assert_has_calls(notify_calls)
|
||||
|
||||
@mock.patch.object(host_obj.Host, "get_by_uuid")
|
||||
@mock.patch.object(host_obj.Host, "save")
|
||||
@mock.patch.object(host_obj.Host, "update")
|
||||
@mock.patch.object(host_obj.HostList, "get_all")
|
||||
@mock.patch("masakari.engine.drivers.taskflow."
|
||||
"TaskFlowDriver.execute_host_failure")
|
||||
@mock.patch.object(notification_obj.Notification, "save")
|
||||
@mock.patch.object(engine_utils, 'notify_about_notification_update')
|
||||
@mock.patch('traceback.format_exc')
|
||||
def test_process_notification_type_compute_host_skip_host_recovery(
|
||||
self, mock_format, mock_notify_about_notification_update,
|
||||
mock_notification_save, mock_host_failure, mock_get_all,
|
||||
mock_host_update, mock_host_save, mock_host_obj,
|
||||
mock_notification_get):
|
||||
mock_format.return_value = mock.ANY
|
||||
notification = self._get_compute_host_type_notification()
|
||||
mock_notification_get.return_value = notification
|
||||
fake_host = fakes.create_fake_host()
|
||||
mock_get_all.return_value = None
|
||||
fake_host.failover_segment = fakes.create_fake_failover_segment()
|
||||
mock_host_obj.return_value = fake_host
|
||||
# mock_host_failure.side_effect = str(e)
|
||||
mock_host_failure.side_effect = self._fake_notification_workflow(
|
||||
exc=exception.SkipHostRecoveryException)
|
||||
self.engine.process_notification(self.context,
|
||||
notification=notification)
|
||||
|
||||
update_data_by_host_failure = {
|
||||
'on_maintenance': True,
|
||||
}
|
||||
mock_host_update.assert_called_once_with(update_data_by_host_failure)
|
||||
self.assertEqual("finished", notification.status)
|
||||
action = fields.EventNotificationAction.NOTIFICATION_PROCESS
|
||||
phase_start = fields.EventNotificationPhase.START
|
||||
phase_end = fields.EventNotificationPhase.END
|
||||
notify_calls = [
|
||||
mock.call(self.context, notification, action=action,
|
||||
phase=phase_start),
|
||||
mock.call(self.context, notification, action=action,
|
||||
phase=phase_end)]
|
||||
mock_notify_about_notification_update.assert_has_calls(notify_calls)
|
||||
|
||||
@mock.patch.object(notification_obj.Notification, "save")
|
||||
@mock.patch.object(engine_utils, 'notify_about_notification_update')
|
||||
@mock.patch("masakari.engine.drivers.taskflow."
|
||||
"TaskFlowDriver.execute_host_failure")
|
||||
def test_process_notification_type_compute_host_event_started(
|
||||
self, mock_host_failure, mock_notification_save,
|
||||
mock_notification_get):
|
||||
self, mock_host_failure, mock_notify_about_notification_update,
|
||||
mock_notification_save, mock_notification_get):
|
||||
notification = self._get_compute_host_type_notification()
|
||||
mock_notification_get.return_value = notification
|
||||
notification.payload['event'] = 'started'
|
||||
|
@ -404,13 +619,23 @@ class EngineManagerUnitTestCase(test.NoDBTestCase):
|
|||
notification=notification)
|
||||
self.assertEqual("finished", notification.status)
|
||||
self.assertFalse(mock_host_failure.called)
|
||||
action = fields.EventNotificationAction.NOTIFICATION_PROCESS
|
||||
phase_start = fields.EventNotificationPhase.START
|
||||
phase_end = fields.EventNotificationPhase.END
|
||||
notify_calls = [
|
||||
mock.call(self.context, notification, action=action,
|
||||
phase=phase_start),
|
||||
mock.call(self.context, notification, action=action,
|
||||
phase=phase_end)]
|
||||
mock_notify_about_notification_update.assert_has_calls(notify_calls)
|
||||
|
||||
@mock.patch.object(notification_obj.Notification, "save")
|
||||
@mock.patch.object(engine_utils, 'notify_about_notification_update')
|
||||
@mock.patch("masakari.engine.drivers.taskflow."
|
||||
"TaskFlowDriver.execute_host_failure")
|
||||
def test_process_notification_type_compute_host_event_other(
|
||||
self, mock_host_failure, mock_notification_save,
|
||||
mock_notification_get):
|
||||
self, mock_host_failure, mock_notify_about_notification_update,
|
||||
mock_notification_save, mock_notification_get):
|
||||
notification = self._get_compute_host_type_notification()
|
||||
mock_notification_get.return_value = notification
|
||||
notification.payload['event'] = 'other'
|
||||
|
@ -418,13 +643,26 @@ class EngineManagerUnitTestCase(test.NoDBTestCase):
|
|||
notification=notification)
|
||||
self.assertEqual("ignored", notification.status)
|
||||
self.assertFalse(mock_host_failure.called)
|
||||
action = fields.EventNotificationAction.NOTIFICATION_PROCESS
|
||||
phase_start = fields.EventNotificationPhase.START
|
||||
phase_end = fields.EventNotificationPhase.END
|
||||
notify_calls = [
|
||||
mock.call(self.context, notification, action=action,
|
||||
phase=phase_start),
|
||||
mock.call(self.context, notification, action=action,
|
||||
phase=phase_end)]
|
||||
mock_notify_about_notification_update.assert_has_calls(notify_calls)
|
||||
|
||||
@mock.patch("masakari.compute.nova.API.stop_server")
|
||||
@mock.patch.object(notification_obj.Notification, "save")
|
||||
@mock.patch.object(engine_utils, 'notify_about_notification_update')
|
||||
@mock.patch('traceback.format_exc')
|
||||
@mock.patch("masakari.compute.nova.API.get_server")
|
||||
def test_process_notification_type_vm_ignore_instance_in_paused(
|
||||
self, mock_get_server, mock_notification_save, mock_stop_server,
|
||||
mock_notification_get):
|
||||
self, mock_get_server, mock_format,
|
||||
mock_notify_about_notification_update, mock_notification_save,
|
||||
mock_stop_server, mock_notification_get):
|
||||
mock_format.return_value = mock.ANY
|
||||
notification = _get_vm_type_notification()
|
||||
mock_notification_get.return_value = notification
|
||||
mock_get_server.return_value = fakes.FakeNovaClient.Server(
|
||||
|
@ -435,13 +673,32 @@ class EngineManagerUnitTestCase(test.NoDBTestCase):
|
|||
notification=notification)
|
||||
self.assertEqual("ignored", notification.status)
|
||||
self.assertFalse(mock_stop_server.called)
|
||||
msg = ("Recovery of instance '%(instance_uuid)s' is ignored as it is "
|
||||
"in '%(vm_state)s' state.") % {'instance_uuid':
|
||||
uuidsentinel.fake_ins, 'vm_state': 'paused'}
|
||||
e = exception.IgnoreInstanceRecoveryException(msg)
|
||||
action = fields.EventNotificationAction.NOTIFICATION_PROCESS
|
||||
phase_start = fields.EventNotificationPhase.START
|
||||
phase_error = fields.EventNotificationPhase.ERROR
|
||||
notify_calls = [
|
||||
mock.call(self.context, notification, action=action,
|
||||
phase=phase_start),
|
||||
mock.call(self.context, notification, action=action,
|
||||
phase=phase_error,
|
||||
exception=str(e),
|
||||
tb=mock.ANY)]
|
||||
mock_notify_about_notification_update.assert_has_calls(notify_calls)
|
||||
|
||||
@mock.patch("masakari.compute.nova.API.stop_server")
|
||||
@mock.patch.object(notification_obj.Notification, "save")
|
||||
@mock.patch.object(engine_utils, 'notify_about_notification_update')
|
||||
@mock.patch('traceback.format_exc')
|
||||
@mock.patch("masakari.compute.nova.API.get_server")
|
||||
def test_process_notification_type_vm_ignore_instance_in_rescued(
|
||||
self, mock_get_server, mock_notification_save, mock_stop_server,
|
||||
mock_notification_get):
|
||||
self, mock_get_server, mock_format,
|
||||
mock_notify_about_notification_update, mock_notification_save,
|
||||
mock_stop_server, mock_notification_get):
|
||||
mock_format.return_value = mock.ANY
|
||||
notification = _get_vm_type_notification()
|
||||
mock_notification_get.return_value = notification
|
||||
mock_get_server.return_value = fakes.FakeNovaClient.Server(
|
||||
|
@ -452,6 +709,21 @@ class EngineManagerUnitTestCase(test.NoDBTestCase):
|
|||
notification=notification)
|
||||
self.assertEqual("ignored", notification.status)
|
||||
self.assertFalse(mock_stop_server.called)
|
||||
msg = ("Recovery of instance '%(instance_uuid)s' is ignored as it is "
|
||||
"in '%(vm_state)s' state.") % {'instance_uuid':
|
||||
uuidsentinel.fake_ins, 'vm_state': 'rescued'}
|
||||
e = exception.IgnoreInstanceRecoveryException(msg)
|
||||
action = fields.EventNotificationAction.NOTIFICATION_PROCESS
|
||||
phase_start = fields.EventNotificationPhase.START
|
||||
phase_error = fields.EventNotificationPhase.ERROR
|
||||
notify_calls = [
|
||||
mock.call(self.context, notification, action=action,
|
||||
phase=phase_start),
|
||||
mock.call(self.context, notification, action=action,
|
||||
phase=phase_error,
|
||||
exception=str(e),
|
||||
tb=mock.ANY)]
|
||||
mock_notify_about_notification_update.assert_has_calls(notify_calls)
|
||||
|
||||
def test_process_notification_stop_from_recovery_failure(self,
|
||||
mock_get_noti):
|
||||
|
|
|
@ -19,11 +19,14 @@ import copy
|
|||
import mock
|
||||
from oslo_utils import timeutils
|
||||
|
||||
from masakari.api import utils as api_utils
|
||||
from masakari.compute import nova as nova_obj
|
||||
from masakari.engine import rpcapi as engine_rpcapi
|
||||
from masakari import exception
|
||||
from masakari.ha import api as ha_api
|
||||
from masakari import objects
|
||||
from masakari.objects import base as obj_base
|
||||
from masakari.objects import fields
|
||||
from masakari.objects import host as host_obj
|
||||
from masakari.objects import notification as notification_obj
|
||||
from masakari.objects import segment as segment_obj
|
||||
|
@ -61,6 +64,12 @@ class FailoverSegmentAPITestCase(test.NoDBTestCase):
|
|||
service_type="COMPUTE", recovery_method="auto",
|
||||
uuid=uuidsentinel.fake_segment
|
||||
)
|
||||
self.exception_in_use = exception.FailoverSegmentInUse(
|
||||
uuid=self.failover_segment.uuid)
|
||||
|
||||
def _fake_notification_workflow(self, exc=None):
|
||||
if exc:
|
||||
return exc
|
||||
|
||||
def _assert_segment_data(self, expected, actual):
|
||||
self.assertTrue(obj_base.obj_equal_prims(expected, actual),
|
||||
|
@ -125,6 +134,30 @@ class FailoverSegmentAPITestCase(test.NoDBTestCase):
|
|||
self._assert_segment_data(
|
||||
self.failover_segment, _make_segment_obj(result))
|
||||
|
||||
@mock.patch.object(segment_obj.FailoverSegment, 'create')
|
||||
@mock.patch.object(api_utils, 'notify_about_segment_api')
|
||||
def test_segment_create_exception(self, mock_notify_about_segment_api,
|
||||
mock_segment_create):
|
||||
segment_data = {"name": "segment1",
|
||||
"service_type": "COMPUTE",
|
||||
"recovery_method": "auto",
|
||||
"description": "something"}
|
||||
e = exception.InvalidInput(reason="TEST")
|
||||
mock_segment_create.side_effect = e
|
||||
mock_notify_about_segment_api.return_value = mock.Mock()
|
||||
|
||||
self.assertRaises(exception.InvalidInput,
|
||||
self.segment_api.create_segment, self.context,
|
||||
segment_data)
|
||||
action = fields.EventNotificationAction.SEGMENT_CREATE
|
||||
phase_error = fields.EventNotificationPhase.ERROR
|
||||
notify_calls = [
|
||||
mock.call(self.context, mock.ANY, action=action,
|
||||
phase=phase_error,
|
||||
exception=e,
|
||||
tb=mock.ANY)]
|
||||
mock_notify_about_segment_api.assert_has_calls(notify_calls)
|
||||
|
||||
@mock.patch.object(segment_obj.FailoverSegment, 'get_by_uuid')
|
||||
def test_get_segment(self, mock_get_segment):
|
||||
|
||||
|
@ -157,21 +190,94 @@ class FailoverSegmentAPITestCase(test.NoDBTestCase):
|
|||
segment_data)
|
||||
self._assert_segment_data(self.failover_segment, result)
|
||||
|
||||
@mock.patch.object(segment_obj.FailoverSegment,
|
||||
'is_under_recovery')
|
||||
@mock.patch.object(segment_obj.FailoverSegment, 'update')
|
||||
@mock.patch.object(api_utils, 'notify_about_segment_api')
|
||||
@mock.patch.object(segment_obj.FailoverSegment, 'get_by_uuid')
|
||||
def test_segment_update_exception(self, mock_get,
|
||||
mock_notify_about_segment_api,
|
||||
mock_segment_update,
|
||||
mock_is_under_recovery):
|
||||
mock_get.return_value = self.failover_segment
|
||||
segment_data = {"name": "segment1",
|
||||
"service_type": "COMPUTE",
|
||||
"recovery_method": "auto",
|
||||
"description": "something"}
|
||||
e = exception.InvalidInput(reason="TEST")
|
||||
mock_segment_update.side_effect = e
|
||||
mock_is_under_recovery.return_value = False
|
||||
mock_notify_about_segment_api.return_value = mock.Mock()
|
||||
|
||||
self.assertRaises(exception.InvalidInput,
|
||||
self.segment_api.update_segment, self.context,
|
||||
uuidsentinel.fake_segment, segment_data)
|
||||
action = fields.EventNotificationAction.SEGMENT_UPDATE
|
||||
phase_error = fields.EventNotificationPhase.ERROR
|
||||
notify_calls = [
|
||||
mock.call(self.context, mock.ANY, action=action,
|
||||
phase=phase_error,
|
||||
exception=e,
|
||||
tb=mock.ANY)]
|
||||
mock_notify_about_segment_api.assert_has_calls(notify_calls)
|
||||
|
||||
@mock.patch.object(exception, 'FailoverSegmentInUse')
|
||||
@mock.patch.object(segment_obj.FailoverSegment,
|
||||
'is_under_recovery')
|
||||
@mock.patch.object(segment_obj, 'FailoverSegment')
|
||||
@mock.patch.object(segment_obj.FailoverSegment, 'get_by_uuid')
|
||||
def test_update_segment_under_recovery(self, mock_get, mock_segment_obj,
|
||||
mock_is_under_recovery):
|
||||
mock_is_under_recovery, mock_FailoverSegmentInUse):
|
||||
segment_data = {"name": "segment1"}
|
||||
mock_get.return_value = self.failover_segment
|
||||
mock_segment_obj.return_value = self.failover_segment
|
||||
mock_is_under_recovery.return_value = True
|
||||
self.assertRaises(exception.FailoverSegmentInUse,
|
||||
mock_FailoverSegmentInUse.return_value = self.exception_in_use
|
||||
self.assertRaises(type(self.exception_in_use),
|
||||
self.segment_api.update_segment,
|
||||
self.context, uuidsentinel.fake_segment,
|
||||
segment_data)
|
||||
|
||||
@mock.patch.object(segment_obj.FailoverSegment, 'destroy')
|
||||
@mock.patch.object(segment_obj.FailoverSegment,
|
||||
'is_under_recovery')
|
||||
@mock.patch.object(segment_obj.FailoverSegment, 'get_by_uuid')
|
||||
def test_segment_delete(self, mock_get, mock_is_under_recovery,
|
||||
mock_segment_destroy):
|
||||
mock_get.return_value = self.failover_segment
|
||||
mock_is_under_recovery.return_value = False
|
||||
self.segment_api.delete_segment(self.context,
|
||||
uuidsentinel.fake_segment)
|
||||
mock_segment_destroy.assert_called_once()
|
||||
|
||||
@mock.patch.object(segment_obj.FailoverSegment,
|
||||
'is_under_recovery')
|
||||
@mock.patch.object(segment_obj.FailoverSegment, 'destroy')
|
||||
@mock.patch.object(api_utils, 'notify_about_segment_api')
|
||||
@mock.patch.object(segment_obj.FailoverSegment, 'get_by_uuid')
|
||||
def test_segment_delete_exception(self, mock_get,
|
||||
mock_notify_about_segment_api,
|
||||
mock_segment_destroy,
|
||||
mock_is_under_recovery):
|
||||
mock_get.return_value = self.failover_segment
|
||||
mock_is_under_recovery.return_value = False
|
||||
e = exception.InvalidInput(reason="TEST")
|
||||
mock_segment_destroy.side_effect = e
|
||||
mock_notify_about_segment_api.return_value = mock.Mock()
|
||||
|
||||
self.assertRaises(exception.InvalidInput,
|
||||
self.segment_api.delete_segment, self.context,
|
||||
uuidsentinel.fake_segment)
|
||||
|
||||
action = fields.EventNotificationAction.SEGMENT_DELETE
|
||||
phase_error = fields.EventNotificationPhase.ERROR
|
||||
notify_calls = [
|
||||
mock.call(self.context, mock.ANY, action=action,
|
||||
phase=phase_error,
|
||||
exception=e,
|
||||
tb=mock.ANY)]
|
||||
mock_notify_about_segment_api.assert_has_calls(notify_calls)
|
||||
|
||||
|
||||
class HostAPITestCase(test.NoDBTestCase):
|
||||
"""Test Case for host api."""
|
||||
|
@ -194,6 +300,8 @@ class HostAPITestCase(test.NoDBTestCase):
|
|||
uuid=uuidsentinel.fake_host_1,
|
||||
failover_segment_id=uuidsentinel.fake_segment1
|
||||
)
|
||||
self.exception_in_use = exception.HostInUse(
|
||||
uuid=self.host.uuid)
|
||||
|
||||
def _assert_host_data(self, expected, actual):
|
||||
self.assertTrue(obj_base.obj_equal_prims(expected, actual),
|
||||
|
@ -294,6 +402,41 @@ class HostAPITestCase(test.NoDBTestCase):
|
|||
self.host_api.create_host,
|
||||
self.context, uuidsentinel.fake_segment1, host_data)
|
||||
|
||||
@mock.patch.object(host_obj, 'Host')
|
||||
@mock.patch.object(host_obj.Host, 'create')
|
||||
@mock.patch.object(nova_obj.API, 'hypervisor_search')
|
||||
@mock.patch.object(api_utils, 'notify_about_host_api')
|
||||
@mock.patch.object(segment_obj.FailoverSegment, 'get_by_uuid')
|
||||
def test_host_create_exception(self, mock_get,
|
||||
mock_notify_about_host_api,
|
||||
mock_hypervisor_search, mock_host_obj,
|
||||
mock_host_create):
|
||||
mock_get.return_value = self.failover_segment
|
||||
host_data = {
|
||||
"name": "host-1", "type": "fake-type",
|
||||
"reserved": False,
|
||||
"on_maintenance": False,
|
||||
"control_attributes": "fake-control_attributes"
|
||||
}
|
||||
|
||||
e = exception.InvalidInput(reason="TEST")
|
||||
mock_host_obj.side_effect = e
|
||||
mock_notify_about_host_api.return_value = mock.Mock()
|
||||
|
||||
self.assertRaises(exception.InvalidInput,
|
||||
self.host_api.create_host, self.context,
|
||||
uuidsentinel.fake_segment1, host_data)
|
||||
action = fields.EventNotificationAction.HOST_CREATE
|
||||
phase_error = fields.EventNotificationPhase.ERROR
|
||||
notify_calls = [
|
||||
mock.call(self.context, mock.ANY, action=action,
|
||||
phase=phase_error,
|
||||
exception=e,
|
||||
tb=mock.ANY)]
|
||||
mock_notify_about_host_api.assert_has_calls(notify_calls)
|
||||
mock_hypervisor_search.assert_called_once()
|
||||
|
||||
@mock.patch.object(api_utils, 'notify_about_host_api')
|
||||
@mock.patch('oslo_utils.uuidutils.generate_uuid')
|
||||
@mock.patch('masakari.db.host_create')
|
||||
@mock.patch.object(host_obj.Host, '_from_db_object')
|
||||
|
@ -303,7 +446,8 @@ class HostAPITestCase(test.NoDBTestCase):
|
|||
mock_hypervisor_search,
|
||||
mock__from_db_object,
|
||||
mock_host_create,
|
||||
mock_generate_uuid):
|
||||
mock_generate_uuid,
|
||||
mock_notify_about_host_api):
|
||||
host_data = {
|
||||
"name": "host-1", "type": "fake-type",
|
||||
"reserved": 'On',
|
||||
|
@ -322,10 +466,19 @@ class HostAPITestCase(test.NoDBTestCase):
|
|||
mock_host_create.create = mock.Mock()
|
||||
mock_get_segment.return_value = self.failover_segment
|
||||
mock_generate_uuid.return_value = uuidsentinel.fake_uuid
|
||||
self.host_api.create_host(self.context,
|
||||
uuidsentinel.fake_segment1,
|
||||
host_data)
|
||||
result = self.host_api.create_host(self.context,
|
||||
uuidsentinel.fake_segment1,
|
||||
host_data)
|
||||
mock_host_create.assert_called_with(self.context, expected_data)
|
||||
action = fields.EventNotificationAction.HOST_CREATE
|
||||
phase_start = fields.EventNotificationPhase.START
|
||||
phase_end = fields.EventNotificationPhase.END
|
||||
notify_calls = [
|
||||
mock.call(self.context, result, action=action,
|
||||
phase=phase_start),
|
||||
mock.call(self.context, result, action=action,
|
||||
phase=phase_end)]
|
||||
mock_notify_about_host_api.assert_has_calls(notify_calls)
|
||||
|
||||
@mock.patch.object(host_obj.Host, 'get_by_uuid')
|
||||
@mock.patch.object(segment_obj.FailoverSegment, 'get_by_uuid')
|
||||
|
@ -373,8 +526,8 @@ class HostAPITestCase(test.NoDBTestCase):
|
|||
@mock.patch.object(host_obj.Host, 'get_by_uuid')
|
||||
@mock.patch.object(segment_obj.FailoverSegment, 'get_by_uuid')
|
||||
def test_update_with_non_existing_host(self, mock_segment_get, mock_get,
|
||||
mock_hypervisor_search,
|
||||
mock_is_under_recovery):
|
||||
mock_hypervisor_search,
|
||||
mock_is_under_recovery):
|
||||
mock_segment_get.return_value = self.failover_segment
|
||||
host_data = {"name": "host-2"}
|
||||
mock_get.return_value = self.host
|
||||
|
@ -387,23 +540,59 @@ class HostAPITestCase(test.NoDBTestCase):
|
|||
uuidsentinel.fake_host_1,
|
||||
host_data)
|
||||
|
||||
@mock.patch.object(segment_obj.FailoverSegment,
|
||||
'is_under_recovery')
|
||||
@mock.patch.object(nova_obj.API, 'hypervisor_search')
|
||||
@mock.patch.object(host_obj.Host, 'save')
|
||||
@mock.patch.object(api_utils, 'notify_about_host_api')
|
||||
@mock.patch.object(host_obj.Host, 'get_by_uuid')
|
||||
@mock.patch.object(segment_obj.FailoverSegment, 'get_by_uuid')
|
||||
def test_host_update_exception(self, mock_segment_get, mock_get,
|
||||
mock_notify_about_host_api,
|
||||
mock_host_obj, mock_hypervisor_search,
|
||||
mock_is_under_recovery):
|
||||
mock_segment_get.return_value = self.failover_segment
|
||||
host_data = {"name": "host_1"}
|
||||
mock_get.return_value = self.host
|
||||
e = exception.InvalidInput(reason="TEST")
|
||||
mock_host_obj.side_effect = e
|
||||
mock_is_under_recovery.return_value = False
|
||||
mock_notify_about_host_api.return_value = mock.Mock()
|
||||
|
||||
self.assertRaises(exception.InvalidInput,
|
||||
self.host_api.update_host, self.context,
|
||||
uuidsentinel.fake_segment,
|
||||
uuidsentinel.fake_host_1,
|
||||
host_data)
|
||||
action = fields.EventNotificationAction.HOST_UPDATE
|
||||
phase_error = fields.EventNotificationPhase.ERROR
|
||||
notify_calls = [
|
||||
mock.call(self.context, mock.ANY, action=action,
|
||||
phase=phase_error,
|
||||
exception=e,
|
||||
tb=mock.ANY)]
|
||||
mock_notify_about_host_api.assert_has_calls(notify_calls)
|
||||
|
||||
@mock.patch.object(exception, 'HostInUse')
|
||||
@mock.patch.object(segment_obj.FailoverSegment,
|
||||
'is_under_recovery')
|
||||
@mock.patch.object(host_obj, 'Host')
|
||||
@mock.patch.object(host_obj.Host, 'get_by_uuid')
|
||||
@mock.patch.object(segment_obj.FailoverSegment, 'get_by_uuid')
|
||||
def test_update_host_under_recovery(self, mock_segment_get, mock_get,
|
||||
mock_host_obj,
|
||||
mock_is_under_recovery):
|
||||
mock_host_obj, mock_is_under_recovery, mock_HostInUse):
|
||||
mock_segment_get.return_value = self.failover_segment
|
||||
host_data = {"name": "host_1"}
|
||||
mock_get.return_value = self.host
|
||||
mock_host_obj.return_value = self.host
|
||||
mock_is_under_recovery.return_value = True
|
||||
self.assertRaises(exception.HostInUse, self.host_api.update_host,
|
||||
mock_HostInUse.return_value = self.exception_in_use
|
||||
self.assertRaises(type(self.exception_in_use),
|
||||
self.host_api.update_host,
|
||||
self.context, uuidsentinel.fake_segment,
|
||||
uuidsentinel.fake_host_1, host_data)
|
||||
|
||||
@mock.patch.object(api_utils, 'notify_about_host_api')
|
||||
@mock.patch.object(segment_obj.FailoverSegment,
|
||||
'is_under_recovery')
|
||||
@mock.patch.object(host_obj.Host, '_from_db_object')
|
||||
|
@ -412,7 +601,8 @@ class HostAPITestCase(test.NoDBTestCase):
|
|||
@mock.patch.object(segment_obj.FailoverSegment, 'get_by_uuid')
|
||||
def test_update_convert_boolean_attributes(
|
||||
self, mock_segment, mock_host_update, mock_host_object,
|
||||
mock__from_db_object, mock_is_under_recovery):
|
||||
mock__from_db_object, mock_is_under_recovery,
|
||||
mock_notify_about_host_api):
|
||||
host_data = {
|
||||
"reserved": 'Off',
|
||||
"on_maintenance": 'True',
|
||||
|
@ -430,26 +620,83 @@ class HostAPITestCase(test.NoDBTestCase):
|
|||
mock_host_object.return_value = self.host
|
||||
self.host._context = self.context
|
||||
mock_is_under_recovery.return_value = False
|
||||
self.host_api.update_host(self.context,
|
||||
uuidsentinel.fake_segment1,
|
||||
uuidsentinel.fake_host_1,
|
||||
host_data)
|
||||
result = self.host_api.update_host(self.context,
|
||||
uuidsentinel.fake_segment1,
|
||||
uuidsentinel.fake_host_1,
|
||||
host_data)
|
||||
mock_host_update.assert_called_with(self.context,
|
||||
uuidsentinel.fake_host_1,
|
||||
expected_data)
|
||||
action = fields.EventNotificationAction.HOST_UPDATE
|
||||
phase_start = fields.EventNotificationPhase.START
|
||||
phase_end = fields.EventNotificationPhase.END
|
||||
notify_calls = [
|
||||
mock.call(self.context, result, action=action,
|
||||
phase=phase_start),
|
||||
mock.call(self.context, result, action=action,
|
||||
phase=phase_end)]
|
||||
mock_notify_about_host_api.assert_has_calls(notify_calls)
|
||||
|
||||
@mock.patch.object(segment_obj.FailoverSegment,
|
||||
'is_under_recovery')
|
||||
@mock.patch.object(host_obj.Host, 'destroy')
|
||||
@mock.patch.object(host_obj.Host, 'get_by_uuid')
|
||||
@mock.patch.object(segment_obj.FailoverSegment, 'get_by_uuid')
|
||||
def test_delete_host(self, mock_segment_get, mock_get,
|
||||
mock_segment_destroy, mock_is_under_recovery):
|
||||
mock_segment_get.return_value = self.failover_segment
|
||||
mock_get.return_value = self.host
|
||||
mock_is_under_recovery.return_value = False
|
||||
|
||||
self.host_api.delete_host(self.context,
|
||||
uuidsentinel.fake_segment,
|
||||
uuidsentinel.fake_host_1)
|
||||
mock_segment_destroy.assert_called_once()
|
||||
|
||||
@mock.patch.object(segment_obj.FailoverSegment,
|
||||
'is_under_recovery')
|
||||
@mock.patch.object(host_obj.Host, 'destroy')
|
||||
@mock.patch.object(api_utils, 'notify_about_host_api')
|
||||
@mock.patch.object(host_obj.Host, 'get_by_uuid')
|
||||
@mock.patch.object(segment_obj.FailoverSegment, 'get_by_uuid')
|
||||
def test_host_delete_exception(self, mock_segment_get, mock_get,
|
||||
mock_notify_about_host_api,
|
||||
mock_host_destroy, mock_is_under_recovery):
|
||||
mock_segment_get.return_value = self.failover_segment
|
||||
mock_get.return_value = self.host
|
||||
mock_is_under_recovery.return_value = False
|
||||
e = exception.InvalidInput(reason="TEST")
|
||||
mock_host_destroy.side_effect = e
|
||||
mock_notify_about_host_api.return_value = mock.Mock()
|
||||
|
||||
self.assertRaises(exception.InvalidInput,
|
||||
self.host_api.delete_host, self.context,
|
||||
uuidsentinel.fake_segment,
|
||||
uuidsentinel.fake_host_1)
|
||||
|
||||
action = fields.EventNotificationAction.HOST_DELETE
|
||||
phase_error = fields.EventNotificationPhase.ERROR
|
||||
notify_calls = [
|
||||
mock.call(self.context, mock.ANY, action=action,
|
||||
phase=phase_error,
|
||||
exception=e,
|
||||
tb=mock.ANY)]
|
||||
mock_notify_about_host_api.assert_has_calls(notify_calls)
|
||||
|
||||
@mock.patch.object(exception, 'HostInUse')
|
||||
@mock.patch.object(segment_obj.FailoverSegment,
|
||||
'is_under_recovery')
|
||||
@mock.patch.object(host_obj, 'Host')
|
||||
@mock.patch.object(host_obj.Host, 'get_by_uuid')
|
||||
@mock.patch.object(segment_obj.FailoverSegment, 'get_by_uuid')
|
||||
def test_delete_host_under_recovery(self, mock_segment_get, mock_get,
|
||||
mock_host_obj,
|
||||
mock_is_under_recovery):
|
||||
mock_host_obj, mock_is_under_recovery, mock_HostInUse):
|
||||
mock_segment_get.return_value = self.failover_segment
|
||||
mock_get.return_value = self.host
|
||||
mock_is_under_recovery.return_value = True
|
||||
self.assertRaises(exception.HostInUse, self.host_api.delete_host,
|
||||
mock_HostInUse.return_value = self.exception_in_use
|
||||
self.assertRaises(type(self.exception_in_use),
|
||||
self.host_api.delete_host,
|
||||
self.context, uuidsentinel.fake_segment,
|
||||
uuidsentinel.fake_host_1)
|
||||
|
||||
|
@ -479,6 +726,8 @@ class NotificationAPITestCase(test.NoDBTestCase):
|
|||
status="running",
|
||||
notification_uuid=uuidsentinel.fake_notification
|
||||
)
|
||||
self.exception_duplicate = exception.DuplicateNotification(
|
||||
host='host_1', type='COMPUTE_HOST')
|
||||
|
||||
def _assert_notification_data(self, expected, actual):
|
||||
self.assertTrue(obj_base.obj_equal_prims(expected, actual),
|
||||
|
@ -516,6 +765,48 @@ class NotificationAPITestCase(test.NoDBTestCase):
|
|||
self._assert_notification_data(
|
||||
self.notification, _make_notification_obj(result))
|
||||
|
||||
@mock.patch.object(api_utils, 'notify_about_notification_api')
|
||||
@mock.patch.object(notification_obj.NotificationList, 'get_all')
|
||||
@mock.patch.object(notification_obj.Notification, 'create')
|
||||
@mock.patch.object(host_obj.Host, 'get_by_name')
|
||||
def test_create_notification_exception(self, mock_host_obj,
|
||||
mock_notification_obj, mock_get_all,
|
||||
mock_notify_about_notification_api):
|
||||
fake_notification = fakes_data.create_fake_notification(
|
||||
type="COMPUTE_HOST", id=2, payload={
|
||||
'event': 'STARTED', 'host_status': 'NORMAL',
|
||||
'cluster_status': 'ONLINE'
|
||||
},
|
||||
source_host_uuid=uuidsentinel.fake_host, generated_time=NOW,
|
||||
status="running",
|
||||
notification_uuid=uuidsentinel.fake_notification_2
|
||||
)
|
||||
fake_notification_list = [self.notification, fake_notification]
|
||||
mock_get_all.return_value = fake_notification_list
|
||||
notification_data = {"hostname": "fake_host",
|
||||
"payload": {"event": "STARTED",
|
||||
"host_status": "NORMAL",
|
||||
"cluster_status": "OFFLINE"},
|
||||
"type": "VM",
|
||||
"generated_time": "2016-10-13T09:11:21.656788"}
|
||||
mock_host_obj.return_value = self.host
|
||||
e = exception.InvalidInput(reason="TEST")
|
||||
mock_notification_obj.side_effect = e
|
||||
mock_notify_about_notification_api.return_value = mock.Mock()
|
||||
|
||||
self.assertRaises(exception.InvalidInput,
|
||||
self.notification_api.create_notification,
|
||||
self.context, notification_data)
|
||||
|
||||
action = fields.EventNotificationAction.NOTIFICATION_CREATE
|
||||
phase_error = fields.EventNotificationPhase.ERROR
|
||||
notify_calls = [
|
||||
mock.call(self.context, mock.ANY, action=action,
|
||||
phase=phase_error,
|
||||
exception=e,
|
||||
tb=mock.ANY)]
|
||||
mock_notify_about_notification_api.assert_has_calls(notify_calls)
|
||||
|
||||
@mock.patch.object(host_obj.Host, 'get_by_name')
|
||||
def test_create_host_on_maintenance(self, mock_host):
|
||||
self.host.on_maintenance = True
|
||||
|
@ -531,8 +822,11 @@ class NotificationAPITestCase(test.NoDBTestCase):
|
|||
self.notification_api.create_notification,
|
||||
self.context, notification_data)
|
||||
|
||||
@mock.patch.object(exception, 'DuplicateNotification')
|
||||
@mock.patch.object(objects, 'Notification')
|
||||
@mock.patch.object(host_obj.Host, 'get_by_name')
|
||||
def test_create_duplicate_notification(self, mock_host):
|
||||
def test_create_duplicate_notification(self, mock_host,
|
||||
mock_notification_obj, mock_DuplicateNotification):
|
||||
mock_host.return_value = self.host
|
||||
self.notification_api._is_duplicate_notification = mock.Mock(
|
||||
return_value=True)
|
||||
|
@ -542,8 +836,16 @@ class NotificationAPITestCase(test.NoDBTestCase):
|
|||
'cluster_status': 'ONLINE'},
|
||||
"type": "COMPUTE_HOST",
|
||||
"generated_time": str(NOW)}
|
||||
self.notification.type = notification_data.get('type')
|
||||
self.notification.generated_time = notification_data.get(
|
||||
'generated_time')
|
||||
self.notification.source_host_uuid = self.host.uuid
|
||||
self.notification.payload = notification_data.get('payload')
|
||||
self.notification.status = fields.NotificationStatus.NEW
|
||||
mock_notification_obj.return_value = self.notification
|
||||
mock_DuplicateNotification.return_value = self.exception_duplicate
|
||||
|
||||
self.assertRaises(exception.DuplicateNotification,
|
||||
self.assertRaises(type(self.exception_duplicate),
|
||||
self.notification_api.create_notification,
|
||||
self.context, notification_data)
|
||||
|
||||
|
|
|
@ -18,8 +18,10 @@ import copy
|
|||
import mock
|
||||
from oslo_utils import timeutils
|
||||
|
||||
from masakari.api import utils as api_utils
|
||||
from masakari import db
|
||||
from masakari import exception
|
||||
from masakari.objects import fields
|
||||
from masakari.objects import host
|
||||
from masakari.objects import segment
|
||||
from masakari.tests.unit.objects import test_objects
|
||||
|
@ -115,8 +117,9 @@ class TestHostObject(test_objects._LocalTest):
|
|||
|
||||
return host_obj
|
||||
|
||||
@mock.patch.object(api_utils, 'notify_about_host_api')
|
||||
@mock.patch.object(db, 'host_create')
|
||||
def test_create(self, mock_db_create):
|
||||
def test_create(self, mock_db_create, mock_notify_about_host_api):
|
||||
|
||||
mock_db_create.return_value = fake_host
|
||||
host_obj = self._host_create_attributes()
|
||||
|
@ -128,14 +131,34 @@ class TestHostObject(test_objects._LocalTest):
|
|||
'reserved': False, 'name': u'foo-host',
|
||||
'control_attributes': u'fake_attributes',
|
||||
'type': u'fake-type'})
|
||||
action = fields.EventNotificationAction.HOST_CREATE
|
||||
phase_start = fields.EventNotificationPhase.START
|
||||
phase_end = fields.EventNotificationPhase.END
|
||||
notify_calls = [
|
||||
mock.call(self.context, host_obj, action=action,
|
||||
phase=phase_start),
|
||||
mock.call(self.context, host_obj, action=action,
|
||||
phase=phase_end)]
|
||||
mock_notify_about_host_api.assert_has_calls(notify_calls)
|
||||
|
||||
@mock.patch.object(api_utils, 'notify_about_host_api')
|
||||
@mock.patch.object(db, 'host_create')
|
||||
def test_recreate_fails(self, mock_host_create):
|
||||
def test_recreate_fails(self, mock_host_create,
|
||||
mock_notify_about_host_api):
|
||||
mock_host_create.return_value = fake_host
|
||||
host_obj = self._host_create_attributes()
|
||||
host_obj.create()
|
||||
|
||||
self.assertRaises(exception.ObjectActionError, host_obj.create)
|
||||
action = fields.EventNotificationAction.HOST_CREATE
|
||||
phase_start = fields.EventNotificationPhase.START
|
||||
phase_end = fields.EventNotificationPhase.END
|
||||
notify_calls = [
|
||||
mock.call(self.context, host_obj, action=action,
|
||||
phase=phase_start),
|
||||
mock.call(self.context, host_obj, action=action,
|
||||
phase=phase_end)]
|
||||
mock_notify_about_host_api.assert_has_calls(notify_calls)
|
||||
|
||||
mock_host_create.assert_called_once_with(self.context, {
|
||||
'uuid': uuidsentinel.fake_host, 'name': 'foo-host',
|
||||
|
@ -143,21 +166,39 @@ class TestHostObject(test_objects._LocalTest):
|
|||
'type': 'fake-type', 'reserved': False, 'on_maintenance': False,
|
||||
'control_attributes': 'fake_attributes'})
|
||||
|
||||
@mock.patch.object(api_utils, 'notify_about_host_api')
|
||||
@mock.patch.object(db, 'host_delete')
|
||||
def test_destroy(self, mock_host_destroy):
|
||||
def test_destroy(self, mock_host_destroy, mock_notify_about_host_api):
|
||||
host_obj = self._host_create_attributes()
|
||||
host_obj.id = 123
|
||||
host_obj.destroy()
|
||||
|
||||
mock_host_destroy.assert_called_once_with(
|
||||
self.context, uuidsentinel.fake_host)
|
||||
action = fields.EventNotificationAction.HOST_DELETE
|
||||
phase_start = fields.EventNotificationPhase.START
|
||||
phase_end = fields.EventNotificationPhase.END
|
||||
notify_calls = [
|
||||
mock.call(self.context, host_obj, action=action,
|
||||
phase=phase_start),
|
||||
mock.call(self.context, host_obj, action=action,
|
||||
phase=phase_end)]
|
||||
mock_notify_about_host_api.assert_has_calls(notify_calls)
|
||||
|
||||
@mock.patch.object(api_utils, 'notify_about_host_api')
|
||||
@mock.patch.object(db, 'host_delete')
|
||||
def test_destroy_host_not_found(self, mock_host_destroy):
|
||||
def test_destroy_host_not_found(self, mock_host_destroy,
|
||||
mock_notify_about_host_api):
|
||||
mock_host_destroy.side_effect = exception.HostNotFound(id=123)
|
||||
host_obj = self._host_create_attributes()
|
||||
host_obj.id = 123
|
||||
self.assertRaises(exception.HostNotFound, host_obj.destroy)
|
||||
action = fields.EventNotificationAction.HOST_DELETE
|
||||
phase_start = fields.EventNotificationPhase.START
|
||||
notify_calls = [
|
||||
mock.call(self.context, host_obj, action=action,
|
||||
phase=phase_start)]
|
||||
mock_notify_about_host_api.assert_has_calls(notify_calls)
|
||||
|
||||
@mock.patch.object(db, 'host_get_all_by_filters')
|
||||
def test_get_host_by_filters(self, mock_api_get):
|
||||
|
@ -182,8 +223,9 @@ class TestHostObject(test_objects._LocalTest):
|
|||
host.HostList.get_all,
|
||||
self.context, limit=5, marker=host_name)
|
||||
|
||||
@mock.patch.object(api_utils, 'notify_about_host_api')
|
||||
@mock.patch.object(db, 'host_update')
|
||||
def test_save(self, mock_host_update):
|
||||
def test_save(self, mock_host_update, mock_notify_about_host_api):
|
||||
|
||||
mock_host_update.return_value = fake_host
|
||||
|
||||
|
@ -201,6 +243,15 @@ class TestHostObject(test_objects._LocalTest):
|
|||
'uuid': uuidsentinel.fake_host,
|
||||
'reserved': False, 'on_maintenance': False
|
||||
}))
|
||||
action = fields.EventNotificationAction.HOST_UPDATE
|
||||
phase_start = fields.EventNotificationPhase.START
|
||||
phase_end = fields.EventNotificationPhase.END
|
||||
notify_calls = [
|
||||
mock.call(self.context, host_obj, action=action,
|
||||
phase=phase_start),
|
||||
mock.call(self.context, host_obj, action=action,
|
||||
phase=phase_end)]
|
||||
mock_notify_about_host_api.assert_has_calls(notify_calls)
|
||||
|
||||
@mock.patch.object(db, 'host_update')
|
||||
def test_save_lazy_attribute_changed(self, mock_host_update):
|
||||
|
@ -213,8 +264,10 @@ class TestHostObject(test_objects._LocalTest):
|
|||
host_obj.id = 123
|
||||
self.assertRaises(exception.ObjectActionError, host_obj.save)
|
||||
|
||||
@mock.patch.object(api_utils, 'notify_about_host_api')
|
||||
@mock.patch.object(db, 'host_update')
|
||||
def test_save_host_already_exists(self, mock_host_update):
|
||||
def test_save_host_already_exists(self, mock_host_update,
|
||||
mock_notify_about_host_api):
|
||||
|
||||
mock_host_update.side_effect = exception.HostExists(name="foo-host")
|
||||
|
||||
|
@ -224,9 +277,17 @@ class TestHostObject(test_objects._LocalTest):
|
|||
host_object.uuid = uuidsentinel.fake_host
|
||||
|
||||
self.assertRaises(exception.HostExists, host_object.save)
|
||||
action = fields.EventNotificationAction.HOST_UPDATE
|
||||
phase_start = fields.EventNotificationPhase.START
|
||||
notify_calls = [
|
||||
mock.call(self.context, host_object, action=action,
|
||||
phase=phase_start)]
|
||||
mock_notify_about_host_api.assert_has_calls(notify_calls)
|
||||
|
||||
@mock.patch.object(api_utils, 'notify_about_host_api')
|
||||
@mock.patch.object(db, 'host_update')
|
||||
def test_save_host_not_found(self, mock_host_update):
|
||||
def test_save_host_not_found(self, mock_host_update,
|
||||
mock_notify_about_host_api):
|
||||
|
||||
mock_host_update.side_effect = exception.HostNotFound(name="foo-host")
|
||||
|
||||
|
@ -236,3 +297,9 @@ class TestHostObject(test_objects._LocalTest):
|
|||
host_object.uuid = uuidsentinel.fake_host
|
||||
|
||||
self.assertRaises(exception.HostNotFound, host_object.save)
|
||||
action = fields.EventNotificationAction.HOST_UPDATE
|
||||
phase_start = fields.EventNotificationPhase.START
|
||||
notify_calls = [
|
||||
mock.call(self.context, host_object, action=action,
|
||||
phase=phase_start)]
|
||||
mock_notify_about_host_api.assert_has_calls(notify_calls)
|
||||
|
|
|
@ -19,8 +19,10 @@ import mock
|
|||
from oslo_utils import timeutils
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from masakari.api import utils as api_utils
|
||||
from masakari import db
|
||||
from masakari import exception
|
||||
from masakari.objects import fields
|
||||
from masakari.objects import notification
|
||||
from masakari.tests.unit.objects import test_objects
|
||||
from masakari.tests import uuidsentinel
|
||||
|
@ -106,8 +108,9 @@ class TestNotificationObject(test_objects._LocalTest):
|
|||
|
||||
return notification_obj
|
||||
|
||||
@mock.patch.object(api_utils, 'notify_about_notification_api')
|
||||
@mock.patch.object(db, 'notification_create')
|
||||
def test_create(self, mock_db_create):
|
||||
def test_create(self, mock_db_create, mock_notify_about_notification_api):
|
||||
|
||||
mock_db_create.return_value = fake_db_notification
|
||||
notification_obj = self._notification_create_attributes()
|
||||
|
@ -119,9 +122,20 @@ class TestNotificationObject(test_objects._LocalTest):
|
|||
'notification_uuid': uuidsentinel.fake_notification,
|
||||
'generated_time': NOW, 'status': 'new',
|
||||
'type': 'COMPUTE_HOST', 'payload': '{"fake_key": "fake_value"}'})
|
||||
action = fields.EventNotificationAction.NOTIFICATION_CREATE
|
||||
phase_start = fields.EventNotificationPhase.START
|
||||
phase_end = fields.EventNotificationPhase.END
|
||||
notify_calls = [
|
||||
mock.call(self.context, notification_obj, action=action,
|
||||
phase=phase_start),
|
||||
mock.call(self.context, notification_obj, action=action,
|
||||
phase=phase_end)]
|
||||
mock_notify_about_notification_api.assert_has_calls(notify_calls)
|
||||
|
||||
@mock.patch.object(api_utils, 'notify_about_notification_api')
|
||||
@mock.patch.object(db, 'notification_create')
|
||||
def test_recreate_fails(self, mock_notification_create):
|
||||
def test_recreate_fails(self, mock_notification_create,
|
||||
mock_notify_about_notification_api):
|
||||
mock_notification_create.return_value = fake_db_notification
|
||||
notification_obj = self._notification_create_attributes()
|
||||
notification_obj.create()
|
||||
|
@ -133,11 +147,21 @@ class TestNotificationObject(test_objects._LocalTest):
|
|||
'notification_uuid': uuidsentinel.fake_notification,
|
||||
'generated_time': NOW, 'status': 'new',
|
||||
'type': 'COMPUTE_HOST', 'payload': '{"fake_key": "fake_value"}'})
|
||||
action = fields.EventNotificationAction.NOTIFICATION_CREATE
|
||||
phase_start = fields.EventNotificationPhase.START
|
||||
phase_end = fields.EventNotificationPhase.END
|
||||
notify_calls = [
|
||||
mock.call(self.context, notification_obj, action=action,
|
||||
phase=phase_start),
|
||||
mock.call(self.context, notification_obj, action=action,
|
||||
phase=phase_end)]
|
||||
mock_notify_about_notification_api.assert_has_calls(notify_calls)
|
||||
|
||||
@mock.patch.object(api_utils, 'notify_about_notification_api')
|
||||
@mock.patch.object(db, 'notification_create')
|
||||
@mock.patch.object(uuidutils, 'generate_uuid')
|
||||
def test_create_without_passing_uuid_in_updates(self, mock_generate_uuid,
|
||||
mock_db_create):
|
||||
mock_db_create, mock_notify_about_notification_api):
|
||||
|
||||
mock_db_create.return_value = fake_db_notification
|
||||
mock_generate_uuid.return_value = uuidsentinel.fake_notification
|
||||
|
@ -152,6 +176,12 @@ class TestNotificationObject(test_objects._LocalTest):
|
|||
'generated_time': NOW, 'status': 'new',
|
||||
'type': 'COMPUTE_HOST', 'payload': '{"fake_key": "fake_value"}'})
|
||||
self.assertTrue(mock_generate_uuid.called)
|
||||
action = fields.EventNotificationAction.NOTIFICATION_CREATE
|
||||
phase_start = fields.EventNotificationPhase.START
|
||||
notify_calls = [
|
||||
mock.call(self.context, notification_obj, action=action,
|
||||
phase=phase_start)]
|
||||
mock_notify_about_notification_api.assert_has_calls(notify_calls)
|
||||
|
||||
@mock.patch.object(db, 'notification_delete')
|
||||
def test_destroy(self, mock_notification_delete):
|
||||
|
|
|
@ -18,7 +18,9 @@ import copy
|
|||
import mock
|
||||
from oslo_utils import timeutils
|
||||
|
||||
from masakari.api import utils as api_utils
|
||||
from masakari import exception
|
||||
from masakari.objects import fields
|
||||
from masakari.objects import segment
|
||||
from masakari.tests.unit.objects import test_objects
|
||||
from masakari.tests import uuidsentinel
|
||||
|
@ -86,8 +88,9 @@ class TestFailoverSegmentObject(test_objects._LocalTest):
|
|||
|
||||
return segment_obj
|
||||
|
||||
@mock.patch.object(api_utils, 'notify_about_segment_api')
|
||||
@mock.patch('masakari.db.failover_segment_create')
|
||||
def test_create(self, mock_segment_create):
|
||||
def test_create(self, mock_segment_create, mock_notify_about_segment_api):
|
||||
mock_segment_create.return_value = fake_segment
|
||||
|
||||
segment_obj = self._segment_create_attribute()
|
||||
|
@ -98,9 +101,20 @@ class TestFailoverSegmentObject(test_objects._LocalTest):
|
|||
'uuid': uuidsentinel.fake_segment, 'name': 'foo-segment',
|
||||
'description': 'keydata', 'service_type': 'fake-user',
|
||||
'recovery_method': 'auto'})
|
||||
action = fields.EventNotificationAction.SEGMENT_CREATE
|
||||
phase_start = fields.EventNotificationPhase.START
|
||||
phase_end = fields.EventNotificationPhase.END
|
||||
notify_calls = [
|
||||
mock.call(self.context, segment_obj, action=action,
|
||||
phase=phase_start),
|
||||
mock.call(self.context, segment_obj, action=action,
|
||||
phase=phase_end)]
|
||||
mock_notify_about_segment_api.assert_has_calls(notify_calls)
|
||||
|
||||
@mock.patch.object(api_utils, 'notify_about_segment_api')
|
||||
@mock.patch('masakari.db.failover_segment_create')
|
||||
def test_recreate_fails(self, mock_segment_create):
|
||||
def test_recreate_fails(self, mock_segment_create,
|
||||
mock_notify_about_segment_api):
|
||||
mock_segment_create.return_value = fake_segment
|
||||
|
||||
segment_obj = self._segment_create_attribute()
|
||||
|
@ -111,25 +125,52 @@ class TestFailoverSegmentObject(test_objects._LocalTest):
|
|||
'uuid': uuidsentinel.fake_segment, 'name': 'foo-segment',
|
||||
'description': 'keydata', 'service_type': 'fake-user',
|
||||
'recovery_method': 'auto'})
|
||||
action = fields.EventNotificationAction.SEGMENT_CREATE
|
||||
phase_start = fields.EventNotificationPhase.START
|
||||
phase_end = fields.EventNotificationPhase.END
|
||||
notify_calls = [
|
||||
mock.call(self.context, segment_obj, action=action,
|
||||
phase=phase_start),
|
||||
mock.call(self.context, segment_obj, action=action,
|
||||
phase=phase_end)]
|
||||
mock_notify_about_segment_api.assert_has_calls(notify_calls)
|
||||
|
||||
@mock.patch.object(api_utils, 'notify_about_segment_api')
|
||||
@mock.patch('masakari.db.failover_segment_delete')
|
||||
def test_destroy(self, mock_segment_destroy):
|
||||
|
||||
def test_destroy(self, mock_segment_destroy,
|
||||
mock_notify_about_segment_api):
|
||||
segment_obj = self._segment_create_attribute()
|
||||
segment_obj.id = 123
|
||||
segment_obj.destroy()
|
||||
|
||||
mock_segment_destroy.assert_called_once_with(
|
||||
self.context, uuidsentinel.fake_segment)
|
||||
action = fields.EventNotificationAction.SEGMENT_DELETE
|
||||
phase_start = fields.EventNotificationPhase.START
|
||||
phase_end = fields.EventNotificationPhase.END
|
||||
notify_calls = [
|
||||
mock.call(self.context, segment_obj, action=action,
|
||||
phase=phase_start),
|
||||
mock.call(self.context, segment_obj, action=action,
|
||||
phase=phase_end)]
|
||||
mock_notify_about_segment_api.assert_has_calls(notify_calls)
|
||||
|
||||
@mock.patch.object(api_utils, 'notify_about_segment_api')
|
||||
@mock.patch('masakari.db.failover_segment_delete')
|
||||
def test_destroy_failover_segment_found(self, mock_segment_destroy):
|
||||
def test_destroy_failover_segment_found(self, mock_segment_destroy,
|
||||
mock_notify_about_segment_api):
|
||||
mock_segment_destroy.side_effect = exception.FailoverSegmentNotFound(
|
||||
id=123)
|
||||
segment_obj = self._segment_create_attribute()
|
||||
segment_obj.id = 123
|
||||
self.assertRaises(exception.FailoverSegmentNotFound,
|
||||
segment_obj.destroy)
|
||||
action = fields.EventNotificationAction.SEGMENT_DELETE
|
||||
phase_start = fields.EventNotificationPhase.START
|
||||
notify_calls = [
|
||||
mock.call(self.context, segment_obj, action=action,
|
||||
phase=phase_start)]
|
||||
mock_notify_about_segment_api.assert_has_calls(notify_calls)
|
||||
|
||||
@mock.patch('masakari.db.failover_segment_get_all_by_filters')
|
||||
def test_get_segment_by_recovery_method(self, mock_api_get):
|
||||
|
@ -175,8 +216,9 @@ class TestFailoverSegmentObject(test_objects._LocalTest):
|
|||
segment.FailoverSegmentList.get_all,
|
||||
self.context, limit=5, marker=segment_name)
|
||||
|
||||
@mock.patch.object(api_utils, 'notify_about_segment_api')
|
||||
@mock.patch('masakari.db.failover_segment_update')
|
||||
def test_save(self, mock_segment_update):
|
||||
def test_save(self, mock_segment_update, mock_notify_about_segment_api):
|
||||
|
||||
mock_segment_update.return_value = fake_segment
|
||||
|
||||
|
@ -188,9 +230,20 @@ class TestFailoverSegmentObject(test_objects._LocalTest):
|
|||
|
||||
self.compare_obj(segment_object, fake_segment)
|
||||
self.assertTrue(mock_segment_update.called)
|
||||
action = fields.EventNotificationAction.SEGMENT_UPDATE
|
||||
phase_start = fields.EventNotificationPhase.START
|
||||
phase_end = fields.EventNotificationPhase.END
|
||||
notify_calls = [
|
||||
mock.call(self.context, segment_object, action=action,
|
||||
phase=phase_start),
|
||||
mock.call(self.context, segment_object, action=action,
|
||||
phase=phase_end)]
|
||||
mock_notify_about_segment_api.assert_has_calls(notify_calls)
|
||||
|
||||
@mock.patch.object(api_utils, 'notify_about_segment_api')
|
||||
@mock.patch('masakari.db.failover_segment_update')
|
||||
def test_save_failover_segment_not_found(self, mock_segment_update):
|
||||
def test_save_failover_segment_not_found(self, mock_segment_update,
|
||||
mock_notify_about_segment_api):
|
||||
|
||||
mock_segment_update.side_effect = (
|
||||
exception.FailoverSegmentNotFound(id=uuidsentinel.fake_segment))
|
||||
|
@ -202,9 +255,17 @@ class TestFailoverSegmentObject(test_objects._LocalTest):
|
|||
|
||||
self.assertRaises(exception.FailoverSegmentNotFound,
|
||||
segment_object.save)
|
||||
action = fields.EventNotificationAction.SEGMENT_UPDATE
|
||||
phase_start = fields.EventNotificationPhase.START
|
||||
notify_calls = [
|
||||
mock.call(self.context, segment_object, action=action,
|
||||
phase=phase_start)]
|
||||
mock_notify_about_segment_api.assert_has_calls(notify_calls)
|
||||
|
||||
@mock.patch.object(api_utils, 'notify_about_segment_api')
|
||||
@mock.patch('masakari.db.failover_segment_update')
|
||||
def test_save_failover_segment_already_exists(self, mock_segment_update):
|
||||
def test_save_failover_segment_already_exists(self, mock_segment_update,
|
||||
mock_notify_about_segment_api):
|
||||
|
||||
mock_segment_update.side_effect = (
|
||||
exception.FailoverSegmentExists(name="foo-segment"))
|
||||
|
@ -215,3 +276,9 @@ class TestFailoverSegmentObject(test_objects._LocalTest):
|
|||
segment_object.uuid = uuidsentinel.fake_segment
|
||||
|
||||
self.assertRaises(exception.FailoverSegmentExists, segment_object.save)
|
||||
action = fields.EventNotificationAction.SEGMENT_UPDATE
|
||||
phase_start = fields.EventNotificationPhase.START
|
||||
notify_calls = [
|
||||
mock.call(self.context, segment_object, action=action,
|
||||
phase=phase_start)]
|
||||
mock_notify_about_segment_api.assert_has_calls(notify_calls)
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
---
|
||||
features:
|
||||
- |
|
||||
Added support to emit event notifications whenever user interacts with
|
||||
Masakari restFul APIs. The emitted notifications are documented at
|
||||
`sample_payloads`_.
|
||||
|
||||
To enable this feature one should set `driver` config option under the
|
||||
`oslo_messaging_notifications` section as shown below::
|
||||
|
||||
[oslo_messaging_notifications]
|
||||
driver = log
|
||||
|
||||
Note: Possible values are `messaging`, `messagingv2`, `routing`, `log`,
|
||||
`test`, `noop`.
|
||||
Notifications can be completely disabled by setting `driver` value as `noop`
|
||||
|
||||
.. _`sample_payloads`: https://docs.openstack.org/masakari/latest/#versioned-notifications
|
Loading…
Reference in New Issue