Merge "Send notifications for all API changes"

This commit is contained in:
Zuul 2019-03-07 09:33:07 +00:00 committed by Gerrit Code Review
commit 0cbe4a3f7c
36 changed files with 1884 additions and 95 deletions

0
doc/ext/__init__.py Normal file
View File

View File

@ -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)

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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):

View File

@ -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,

View File

@ -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

View File

@ -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'))

View File

@ -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

View File

@ -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):

View File

@ -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

View File

@ -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):

View File

@ -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)

View File

@ -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)

View File

@ -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):

View File

@ -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)

View File

@ -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