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 import sys
sys.path.insert(0, os.path.abspath('../..')) sys.path.insert(0, os.path.abspath('../..'))
sys.path.insert(0, os.path.abspath('../'))
# -- General configuration ---------------------------------------------------- # -- General configuration ----------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be # Add any Sphinx extension module names here, as strings. They can be
@ -23,7 +24,8 @@ sys.path.insert(0, os.path.abspath('../..'))
extensions = [ extensions = [
'sphinx.ext.autodoc', 'sphinx.ext.autodoc',
#'sphinx.ext.intersphinx', #'sphinx.ext.intersphinx',
'oslosphinx' 'oslosphinx',
'ext.versioned_notifications'
] ]
# autodoc generation is a bit aggressive and a nuisance when doing heavy # autodoc generation is a bit aggressive and a nuisance when doing heavy

View File

@ -76,3 +76,13 @@ Indices and tables
================== ==================
* :ref:`search` * :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. workflows.
""" """
import traceback
from oslo_log import log as logging from oslo_log import log as logging
import oslo_messaging as messaging import oslo_messaging as messaging
@ -30,6 +31,7 @@ from oslo_utils import timeutils
import masakari.conf import masakari.conf
from masakari.engine import driver from masakari.engine import driver
from masakari.engine import instance_events as virt_events from masakari.engine import instance_events as virt_events
from masakari.engine import utils as engine_utils
from masakari import exception from masakari import exception
from masakari import manager from masakari import manager
from masakari import objects from masakari import objects
@ -58,6 +60,7 @@ class MasakariManager(manager.Manager):
notification_status = fields.NotificationStatus.FINISHED notification_status = fields.NotificationStatus.FINISHED
notification_event = notification.payload.get('event') notification_event = notification.payload.get('event')
process_name = notification.payload.get('process_name') process_name = notification.payload.get('process_name')
exception_info = None
if notification_event.upper() == 'STARTED': if notification_event.upper() == 'STARTED':
LOG.info("Notification type '%(type)s' received for host " LOG.info("Notification type '%(type)s' received for host "
@ -83,11 +86,12 @@ class MasakariManager(manager.Manager):
self.driver.execute_process_failure( self.driver.execute_process_failure(
context, process_name, host_name, context, process_name, host_name,
notification.notification_uuid) notification.notification_uuid)
except exception.SkipProcessRecoveryException: except exception.SkipProcessRecoveryException as e:
notification_status = fields.NotificationStatus.FINISHED notification_status = fields.NotificationStatus.FINISHED
except (exception.MasakariException, except (exception.MasakariException,
exception.ProcessRecoveryFailureException): exception.ProcessRecoveryFailureException) as e:
notification_status = fields.NotificationStatus.ERROR notification_status = fields.NotificationStatus.ERROR
exception_info = e
else: else:
LOG.warning("Invalid event: %(event)s received for " LOG.warning("Invalid event: %(event)s received for "
"notification type: %(notification_type)s", "notification type: %(notification_type)s",
@ -95,6 +99,20 @@ class MasakariManager(manager.Manager):
'notification_type': notification.type}) 'notification_type': notification.type})
notification_status = fields.NotificationStatus.IGNORED 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 return notification_status
def _handle_notification_type_instance(self, context, notification): def _handle_notification_type_instance(self, context, notification):
@ -106,23 +124,41 @@ class MasakariManager(manager.Manager):
return fields.NotificationStatus.IGNORED return fields.NotificationStatus.IGNORED
notification_status = fields.NotificationStatus.FINISHED notification_status = fields.NotificationStatus.FINISHED
exception_info = None
try: try:
self.driver.execute_instance_failure( self.driver.execute_instance_failure(
context, notification.payload.get('instance_uuid'), context, notification.payload.get('instance_uuid'),
notification.notification_uuid) notification.notification_uuid)
except exception.IgnoreInstanceRecoveryException: except exception.IgnoreInstanceRecoveryException as e:
notification_status = fields.NotificationStatus.IGNORED notification_status = fields.NotificationStatus.IGNORED
except exception.SkipInstanceRecoveryException: exception_info = e
except exception.SkipInstanceRecoveryException as e:
notification_status = fields.NotificationStatus.FINISHED notification_status = fields.NotificationStatus.FINISHED
except (exception.MasakariException, except (exception.MasakariException,
exception.InstanceRecoveryFailureException): exception.InstanceRecoveryFailureException) as e:
notification_status = fields.NotificationStatus.ERROR 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 return notification_status
def _handle_notification_type_host(self, context, notification): def _handle_notification_type_host(self, context, notification):
notification_status = fields.NotificationStatus.FINISHED notification_status = fields.NotificationStatus.FINISHED
notification_event = notification.payload.get('event') notification_event = notification.payload.get('event')
exception_info = None
if notification_event.upper() == 'STARTED': if notification_event.upper() == 'STARTED':
LOG.info("Notification type '%(type)s' received for host " LOG.info("Notification type '%(type)s' received for host "
@ -163,12 +199,13 @@ class MasakariManager(manager.Manager):
context, host_name, recovery_method, context, host_name, recovery_method,
notification.notification_uuid, notification.notification_uuid,
reserved_host_list=reserved_host_list) reserved_host_list=reserved_host_list)
except exception.SkipHostRecoveryException: except exception.SkipHostRecoveryException as e:
notification_status = fields.NotificationStatus.FINISHED notification_status = fields.NotificationStatus.FINISHED
except (exception.HostRecoveryFailureException, except (exception.HostRecoveryFailureException,
exception.ReservedHostsUnavailable, exception.ReservedHostsUnavailable,
exception.MasakariException): exception.MasakariException) as e:
notification_status = fields.NotificationStatus.ERROR notification_status = fields.NotificationStatus.ERROR
exception_info = e
else: else:
LOG.warning("Invalid event: %(event)s received for " LOG.warning("Invalid event: %(event)s received for "
"notification type: %(type)s", "notification type: %(type)s",
@ -176,6 +213,20 @@ class MasakariManager(manager.Manager):
'type': notification.type}) 'type': notification.type})
notification_status = fields.NotificationStatus.IGNORED 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 return notification_status
def _process_notification(self, context, notification): def _process_notification(self, context, notification):
@ -233,6 +284,11 @@ class MasakariManager(manager.Manager):
notification.update(update_data) notification.update(update_data)
notification.save() notification.save()
engine_utils.notify_about_notification_update(context,
notification,
action=fields.EventNotificationAction.NOTIFICATION_PROCESS,
phase=fields.EventNotificationPhase.START)
do_process_notification(notification) do_process_notification(notification)
def process_notification(self, context, notification=None): def process_notification(self, context, notification=None):

View File

@ -13,11 +13,14 @@
# under the License. # under the License.
import datetime import datetime
import traceback
from oslo_log import log as logging from oslo_log import log as logging
from oslo_utils import excutils
from oslo_utils import strutils from oslo_utils import strutils
from oslo_utils import uuidutils from oslo_utils import uuidutils
from masakari.api import utils as api_utils
from masakari.compute import nova from masakari.compute import nova
import masakari.conf import masakari.conf
from masakari.engine import rpcapi as engine_rpcapi from masakari.engine import rpcapi as engine_rpcapi
@ -91,8 +94,15 @@ class FailoverSegmentAPI(object):
segment.recovery_method = segment_data.get('recovery_method') segment.recovery_method = segment_data.get('recovery_method')
segment.service_type = segment_data.get('service_type') 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 return segment
def update_segment(self, context, uuid, segment_data): def update_segment(self, context, uuid, segment_data):
@ -104,8 +114,16 @@ class FailoverSegmentAPI(object):
LOG.error(msg) LOG.error(msg)
raise exception.FailoverSegmentInUse(msg) raise exception.FailoverSegmentInUse(msg)
segment.update(segment_data) try:
segment.save() 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 return segment
def delete_segment(self, context, uuid): def delete_segment(self, context, uuid):
@ -117,7 +135,15 @@ class FailoverSegmentAPI(object):
LOG.error(msg) LOG.error(msg)
raise exception.FailoverSegmentInUse(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): class HostAPI(object):
@ -173,7 +199,16 @@ class HostAPI(object):
self._is_valid_host_name(context, host.name) 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 return host
def update_host(self, context, segment_uuid, id, host_data): 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'] = strutils.bool_from_string(
host_data['reserved'], strict=True) host_data['reserved'], strict=True)
host.update(host_data) try:
host.update(host_data)
host.save() 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 return host
def delete_host(self, context, segment_uuid, id): def delete_host(self, context, segment_uuid, id):
"""Delete the host""" """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) host = objects.Host.get_by_uuid(context, id, segment_uuid=segment_uuid)
if is_failover_segment_under_recovery(segment): if is_failover_segment_under_recovery(segment):
msg = _("Host %s can't be deleted as " msg = _("Host %s can't be deleted as "
@ -215,7 +256,15 @@ class HostAPI(object):
LOG.error(msg) LOG.error(msg)
raise exception.HostInUse(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): class NotificationAPI(object):
@ -279,9 +328,16 @@ class NotificationAPI(object):
{'host': host_name, 'type': notification.type}) {'host': host_name, 'type': notification.type})
raise exception.DuplicateNotification(message=message) raise exception.DuplicateNotification(message=message)
notification.create() try:
self.engine_rpcapi.process_notification(context, notification) 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 return notification
def get_all(self, context, filters=None, sort_keys=None, 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-start.json')
@base.notification_sample('create-segment-end.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-start.json')
@base.notification_sample('update-segment-end.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-start.json')
@base.notification_sample('delete-segment-end.json') @base.notification_sample('delete-segment-end.json')
@base.notification_sample('delete-segment-error.json')
@masakari_base.MasakariObjectRegistry.register_notification @masakari_base.MasakariObjectRegistry.register_notification
class SegmentApiNotification(base.NotificationBase): class SegmentApiNotification(base.NotificationBase):
# Version 1.0: Initial version # 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-start.json')
@base.notification_sample('create-host-end.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-start.json')
@base.notification_sample('update-host-end.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-start.json')
@base.notification_sample('delete-host-end.json') @base.notification_sample('delete-host-end.json')
@base.notification_sample('delete-host-error.json')
@masakari_base.MasakariObjectRegistry.register_notification @masakari_base.MasakariObjectRegistry.register_notification
class HostApiNotification(base.NotificationBase): class HostApiNotification(base.NotificationBase):
# Version 1.0: Initial version # 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-start.json')
@base.notification_sample('create-notification-end.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 @masakari_base.MasakariObjectRegistry.register_notification
class NotificationApiNotification(base.NotificationBase): class NotificationApiNotification(base.NotificationBase):
# Version 1.0: Initial version # Version 1.0: Initial version

View File

@ -16,6 +16,7 @@
from oslo_log import log as logging from oslo_log import log as logging
from oslo_utils import uuidutils from oslo_utils import uuidutils
from masakari.api import utils as api_utils
from masakari import db from masakari import db
from masakari import exception from masakari import exception
from masakari import objects from masakari import objects
@ -91,7 +92,17 @@ class Host(base.MasakariPersistentObject, base.MasakariObject,
raise exception.ObjectActionError(action='create', raise exception.ObjectActionError(action='create',
reason='failover segment ' reason='failover segment '
'assigned') '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) 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) self._from_db_object(self._context, self, db_host)
@base.remotable @base.remotable
@ -102,7 +113,17 @@ class Host(base.MasakariPersistentObject, base.MasakariObject,
reason='failover segment ' reason='failover segment '
'changed') 'changed')
updates.pop('id', None) 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) 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) self._from_db_object(self._context, self, db_host)
@base.remotable @base.remotable
@ -114,7 +135,16 @@ class Host(base.MasakariPersistentObject, base.MasakariObject,
raise exception.ObjectActionError(action='destroy', raise exception.ObjectActionError(action='destroy',
reason='no uuid') 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) 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')) 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_serialization import jsonutils
from oslo_utils import uuidutils from oslo_utils import uuidutils
from masakari.api import utils as api_utils
from masakari import db from masakari import db
from masakari import exception from masakari import exception
from masakari import objects from masakari import objects
@ -81,7 +82,16 @@ class Notification(base.MasakariPersistentObject, base.MasakariObject,
if 'payload' in updates: if 'payload' in updates:
updates['payload'] = jsonutils.dumps(updates['payload']) 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) 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) self._from_db_object(self._context, self, db_notification)
@base.remotable @base.remotable
@ -129,3 +139,16 @@ class NotificationList(base.ObjectListBase, base.MasakariObject):
return base.obj_make_list(context, cls(context), objects.Notification, return base.obj_make_list(context, cls(context), objects.Notification,
groups) 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_log import log as logging
from oslo_utils import uuidutils from oslo_utils import uuidutils
from masakari.api import utils as api_utils
from masakari import db from masakari import db
from masakari import exception from masakari import exception
from masakari import objects from masakari import objects
@ -74,15 +75,34 @@ class FailoverSegment(base.MasakariPersistentObject, base.MasakariObject,
LOG.debug('Generated uuid %(uuid)s for failover segment', LOG.debug('Generated uuid %(uuid)s for failover segment',
dict(uuid=updates['uuid'])) 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) 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) self._from_db_object(self._context, self, db_segment)
@base.remotable @base.remotable
def save(self): def save(self):
updates = self.masakari_obj_get_changes() updates = self.masakari_obj_get_changes()
updates.pop('id', None) 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, db_segment = db.failover_segment_update(self._context,
self.uuid, updates) 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) self._from_db_object(self._context, self, db_segment)
@base.remotable @base.remotable
@ -94,7 +114,16 @@ class FailoverSegment(base.MasakariPersistentObject, base.MasakariObject,
raise exception.ObjectActionError(action='destroy', raise exception.ObjectActionError(action='destroy',
reason='no uuid') 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) 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')) delattr(self, base.get_attrname('id'))
def is_under_recovery(self, filters=None): 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 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import mock import mock
from oslo_utils import importutils from oslo_utils import importutils
from oslo_utils import timeutils from oslo_utils import timeutils
import masakari.conf import masakari.conf
from masakari import context from masakari import context
from masakari.engine import utils as engine_utils
from masakari import exception from masakari import exception
from masakari.objects import fields from masakari.objects import fields
from masakari.objects import host as host_obj from masakari.objects import host as host_obj
from masakari.objects import notification as notification_obj from masakari.objects import notification as notification_obj
from masakari import rpc
from masakari import test from masakari import test
from masakari.tests.unit import fakes from masakari.tests.unit import fakes
from masakari.tests import uuidsentinel from masakari.tests import uuidsentinel
@ -47,6 +48,7 @@ def _get_vm_type_notification(status="new"):
class EngineManagerUnitTestCase(test.NoDBTestCase): class EngineManagerUnitTestCase(test.NoDBTestCase):
def setUp(self): def setUp(self):
super(EngineManagerUnitTestCase, self).setUp() super(EngineManagerUnitTestCase, self).setUp()
rpc.init(CONF)
self.engine = importutils.import_object(CONF.engine_manager) self.engine = importutils.import_object(CONF.engine_manager)
self.context = context.RequestContext() self.context = context.RequestContext()
@ -77,8 +79,10 @@ class EngineManagerUnitTestCase(test.NoDBTestCase):
@mock.patch("masakari.engine.drivers.taskflow." @mock.patch("masakari.engine.drivers.taskflow."
"TaskFlowDriver.execute_instance_failure") "TaskFlowDriver.execute_instance_failure")
@mock.patch.object(notification_obj.Notification, "save") @mock.patch.object(notification_obj.Notification, "save")
def test_process_notification_type_vm_success(self, mock_save, @mock.patch.object(engine_utils, 'notify_about_notification_update')
mock_instance_failure, mock_notification_get): 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() mock_instance_failure.side_effect = self._fake_notification_workflow()
notification = _get_vm_type_notification() notification = _get_vm_type_notification()
mock_notification_get.return_value = notification mock_notification_get.return_value = notification
@ -88,12 +92,25 @@ class EngineManagerUnitTestCase(test.NoDBTestCase):
mock_instance_failure.assert_called_once_with( mock_instance_failure.assert_called_once_with(
self.context, notification.payload.get('instance_uuid'), self.context, notification.payload.get('instance_uuid'),
notification.notification_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." @mock.patch("masakari.engine.drivers.taskflow."
"TaskFlowDriver.execute_instance_failure") "TaskFlowDriver.execute_instance_failure")
@mock.patch.object(notification_obj.Notification, "save") @mock.patch.object(notification_obj.Notification, "save")
def test_process_notification_type_vm_error(self, mock_save, @mock.patch.object(engine_utils, 'notify_about_notification_update')
mock_instance_failure, mock_notification_get): @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( mock_instance_failure.side_effect = self._fake_notification_workflow(
exc=exception.InstanceRecoveryFailureException) exc=exception.InstanceRecoveryFailureException)
notification = _get_vm_type_notification() notification = _get_vm_type_notification()
@ -104,6 +121,19 @@ class EngineManagerUnitTestCase(test.NoDBTestCase):
mock_instance_failure.assert_called_once_with( mock_instance_failure.assert_called_once_with(
self.context, notification.payload.get('instance_uuid'), self.context, notification.payload.get('instance_uuid'),
notification.notification_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") @mock.patch.object(notification_obj.Notification, "save")
def test_process_notification_type_vm_error_event_unmatched( def test_process_notification_type_vm_error_event_unmatched(
@ -125,8 +155,12 @@ class EngineManagerUnitTestCase(test.NoDBTestCase):
@mock.patch("masakari.engine.drivers.taskflow." @mock.patch("masakari.engine.drivers.taskflow."
"TaskFlowDriver.execute_instance_failure") "TaskFlowDriver.execute_instance_failure")
@mock.patch.object(notification_obj.Notification, "save") @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( 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() notification = _get_vm_type_notification()
mock_notification_get.return_value = notification mock_notification_get.return_value = notification
mock_instance_failure.side_effect = self._fake_notification_workflow( mock_instance_failure.side_effect = self._fake_notification_workflow(
@ -137,14 +171,25 @@ class EngineManagerUnitTestCase(test.NoDBTestCase):
mock_instance_failure.assert_called_once_with( mock_instance_failure.assert_called_once_with(
self.context, notification.payload.get('instance_uuid'), self.context, notification.payload.get('instance_uuid'),
notification.notification_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, "get_by_uuid")
@mock.patch.object(host_obj.Host, "save") @mock.patch.object(host_obj.Host, "save")
@mock.patch("masakari.engine.drivers.taskflow." @mock.patch("masakari.engine.drivers.taskflow."
"TaskFlowDriver.execute_process_failure") "TaskFlowDriver.execute_process_failure")
@mock.patch.object(notification_obj.Notification, "save") @mock.patch.object(notification_obj.Notification, "save")
@mock.patch.object(engine_utils, 'notify_about_notification_update')
def test_process_notification_type_process_event_stopped( 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): mock_host_save, mock_host_obj, mock_notification_get):
notification = self._get_process_type_notification() notification = self._get_process_type_notification()
mock_notification_get.return_value = notification mock_notification_get.return_value = notification
@ -158,15 +203,28 @@ class EngineManagerUnitTestCase(test.NoDBTestCase):
self.context, notification.payload.get('process_name'), self.context, notification.payload.get('process_name'),
fake_host.name, fake_host.name,
notification.notification_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, "get_by_uuid")
@mock.patch.object(host_obj.Host, "save") @mock.patch.object(host_obj.Host, "save")
@mock.patch("masakari.engine.drivers.taskflow." @mock.patch("masakari.engine.drivers.taskflow."
"TaskFlowDriver.execute_process_failure") "TaskFlowDriver.execute_process_failure")
@mock.patch.object(notification_obj.Notification, "save") @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( 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_host_save, mock_host_obj, mock_notification_get):
mock_format.return_value = mock.ANY
notification = self._get_process_type_notification() notification = self._get_process_type_notification()
mock_notification_get.return_value = notification mock_notification_get.return_value = notification
fake_host = fakes.create_fake_host() fake_host = fakes.create_fake_host()
@ -176,15 +234,28 @@ class EngineManagerUnitTestCase(test.NoDBTestCase):
self.engine.process_notification(self.context, self.engine.process_notification(self.context,
notification=notification) notification=notification)
self.assertEqual("finished", notification.status) 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, "get_by_uuid")
@mock.patch.object(host_obj.Host, "save") @mock.patch.object(host_obj.Host, "save")
@mock.patch("masakari.engine.drivers.taskflow." @mock.patch("masakari.engine.drivers.taskflow."
"TaskFlowDriver.execute_process_failure") "TaskFlowDriver.execute_process_failure")
@mock.patch.object(notification_obj.Notification, "save") @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( 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_host_save, mock_host_obj, mock_notification_get):
mock_format.return_value = mock.ANY
notification = self._get_process_type_notification() notification = self._get_process_type_notification()
mock_notification_get.return_value = notification mock_notification_get.return_value = notification
fake_host = fakes.create_fake_host() fake_host = fakes.create_fake_host()
@ -194,15 +265,30 @@ class EngineManagerUnitTestCase(test.NoDBTestCase):
self.engine.process_notification(self.context, self.engine.process_notification(self.context,
notification=notification) notification=notification)
self.assertEqual("error", notification.status) 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, "get_by_uuid")
@mock.patch.object(host_obj.Host, "save") @mock.patch.object(host_obj.Host, "save")
@mock.patch.object(notification_obj.Notification, "save") @mock.patch.object(notification_obj.Notification, "save")
@mock.patch.object(engine_utils, 'notify_about_notification_update')
@mock.patch("masakari.engine.drivers.taskflow." @mock.patch("masakari.engine.drivers.taskflow."
"TaskFlowDriver.execute_process_failure") "TaskFlowDriver.execute_process_failure")
def test_process_notification_type_process_event_started( def test_process_notification_type_process_event_started(
self, mock_process_failure, mock_notification_save, self, mock_process_failure, mock_notify_about_notification_update,
mock_host_save, mock_host_obj, mock_notification_get): mock_notification_save, mock_host_save, mock_host_obj,
mock_notification_get):
notification = self._get_process_type_notification() notification = self._get_process_type_notification()
mock_notification_get.return_value = notification mock_notification_get.return_value = notification
notification.payload['event'] = 'started' notification.payload['event'] = 'started'
@ -212,13 +298,23 @@ class EngineManagerUnitTestCase(test.NoDBTestCase):
notification=notification) notification=notification)
self.assertEqual("finished", notification.status) self.assertEqual("finished", notification.status)
self.assertFalse(mock_process_failure.called) 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(notification_obj.Notification, "save")
@mock.patch.object(engine_utils, 'notify_about_notification_update')
@mock.patch("masakari.engine.drivers.taskflow." @mock.patch("masakari.engine.drivers.taskflow."
"TaskFlowDriver.execute_process_failure") "TaskFlowDriver.execute_process_failure")
def test_process_notification_type_process_event_other( def test_process_notification_type_process_event_other(
self, mock_process_failure, mock_notification_save, self, mock_process_failure, mock_notify_about_notification_update,
mock_notification_get): mock_notification_save, mock_notification_get):
notification = self._get_process_type_notification() notification = self._get_process_type_notification()
mock_notification_get.return_value = notification mock_notification_get.return_value = notification
notification.payload['event'] = 'other' notification.payload['event'] = 'other'
@ -226,6 +322,15 @@ class EngineManagerUnitTestCase(test.NoDBTestCase):
notification=notification) notification=notification)
self.assertEqual("ignored", notification.status) self.assertEqual("ignored", notification.status)
self.assertFalse(mock_process_failure.called) 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, "get_by_uuid")
@mock.patch.object(host_obj.Host, "save") @mock.patch.object(host_obj.Host, "save")
@ -234,8 +339,10 @@ class EngineManagerUnitTestCase(test.NoDBTestCase):
@mock.patch("masakari.engine.drivers.taskflow." @mock.patch("masakari.engine.drivers.taskflow."
"TaskFlowDriver.execute_host_failure") "TaskFlowDriver.execute_host_failure")
@mock.patch.object(notification_obj.Notification, "save") @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( 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_host_update, mock_host_save, mock_host_obj,
mock_notification_get): mock_notification_get):
notification = self._get_compute_host_type_notification() notification = self._get_compute_host_type_notification()
@ -257,16 +364,29 @@ class EngineManagerUnitTestCase(test.NoDBTestCase):
self.context, self.context,
fake_host.name, fake_host.failover_segment.recovery_method, fake_host.name, fake_host.failover_segment.recovery_method,
notification.notification_uuid, reserved_host_list=None) 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, "get_by_uuid")
@mock.patch.object(host_obj.Host, "save") @mock.patch.object(host_obj.Host, "save")
@mock.patch.object(host_obj.Host, "update") @mock.patch.object(host_obj.Host, "update")
@mock.patch.object(host_obj.HostList, "get_all") @mock.patch.object(host_obj.HostList, "get_all")
@mock.patch.object(notification_obj.Notification, "save") @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( 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_host_update, mock_host_save, mock_host_obj,
mock_notification_get): mock_notification_get):
mock_format.return_value = mock.ANY
reserved_host_list = [] reserved_host_list = []
mock_get_all.return_value = 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) mock_host_update.assert_called_once_with(update_data_by_host_failure)
self.assertEqual("error", notification.status) 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, "get_by_uuid")
@mock.patch.object(host_obj.Host, "save") @mock.patch.object(host_obj.Host, "save")
@ -294,8 +427,10 @@ class EngineManagerUnitTestCase(test.NoDBTestCase):
@mock.patch("masakari.engine.drivers.taskflow." @mock.patch("masakari.engine.drivers.taskflow."
"TaskFlowDriver.execute_host_failure") "TaskFlowDriver.execute_host_failure")
@mock.patch.object(notification_obj.Notification, "save") @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( 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_host_update, mock_host_save, mock_host_obj,
mock_notification_get): mock_notification_get):
fake_host = fakes.create_fake_host() fake_host = fakes.create_fake_host()
@ -325,6 +460,15 @@ class EngineManagerUnitTestCase(test.NoDBTestCase):
mock_get_all.assert_called_once_with(self.context, filters={ mock_get_all.assert_called_once_with(self.context, filters={
'failover_segment_id': fake_host.failover_segment.uuid, 'failover_segment_id': fake_host.failover_segment.uuid,
'reserved': True, 'on_maintenance': False}) '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, "get_by_uuid")
@mock.patch.object(host_obj.Host, "save") @mock.patch.object(host_obj.Host, "save")
@ -333,8 +477,10 @@ class EngineManagerUnitTestCase(test.NoDBTestCase):
@mock.patch("masakari.engine.drivers.taskflow." @mock.patch("masakari.engine.drivers.taskflow."
"TaskFlowDriver.execute_host_failure") "TaskFlowDriver.execute_host_failure")
@mock.patch.object(notification_obj.Notification, "save") @mock.patch.object(notification_obj.Notification, "save")
@mock.patch.object(engine_utils, 'notify_about_notification_update')
def test_process_notification_reserved_host_failure( 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_host_update, mock_host_save, mock_host_obj,
mock_notification_get): mock_notification_get):
fake_host = fakes.create_fake_host(reserved=True) 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, fake_host.name, fake_host.failover_segment.recovery_method,
notification.notification_uuid, notification.notification_uuid,
reserved_host_list=reserved_host_list) 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, "get_by_uuid")
@mock.patch.object(host_obj.Host, "save") @mock.patch.object(host_obj.Host, "save")
@ -370,10 +525,14 @@ class EngineManagerUnitTestCase(test.NoDBTestCase):
@mock.patch("masakari.engine.drivers.taskflow." @mock.patch("masakari.engine.drivers.taskflow."
"TaskFlowDriver.execute_host_failure") "TaskFlowDriver.execute_host_failure")
@mock.patch.object(notification_obj.Notification, "save") @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( 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_host_update, mock_host_save, mock_host_obj,
mock_notification_get): mock_notification_get):
mock_format.return_value = mock.ANY
notification = self._get_compute_host_type_notification() notification = self._get_compute_host_type_notification()
mock_notification_get.return_value = notification mock_notification_get.return_value = notification
fake_host = fakes.create_fake_host() 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) mock_host_update.assert_called_once_with(update_data_by_host_failure)
self.assertEqual("error", notification.status) 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(notification_obj.Notification, "save")
@mock.patch.object(engine_utils, 'notify_about_notification_update')
@mock.patch("masakari.engine.drivers.taskflow." @mock.patch("masakari.engine.drivers.taskflow."
"TaskFlowDriver.execute_host_failure") "TaskFlowDriver.execute_host_failure")
def test_process_notification_type_compute_host_event_started( def test_process_notification_type_compute_host_event_started(
self, mock_host_failure, mock_notification_save, self, mock_host_failure, mock_notify_about_notification_update,
mock_notification_get): mock_notification_save, mock_notification_get):
notification = self._get_compute_host_type_notification() notification = self._get_compute_host_type_notification()
mock_notification_get.return_value = notification mock_notification_get.return_value = notification
notification.payload['event'] = 'started' notification.payload['event'] = 'started'
@ -404,13 +619,23 @@ class EngineManagerUnitTestCase(test.NoDBTestCase):
notification=notification) notification=notification)
self.assertEqual("finished", notification.status) self.assertEqual("finished", notification.status)
self.assertFalse(mock_host_failure.called) 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(notification_obj.Notification, "save")
@mock.patch.object(engine_utils, 'notify_about_notification_update')
@mock.patch("masakari.engine.drivers.taskflow." @mock.patch("masakari.engine.drivers.taskflow."
"TaskFlowDriver.execute_host_failure") "TaskFlowDriver.execute_host_failure")
def test_process_notification_type_compute_host_event_other( def test_process_notification_type_compute_host_event_other(
self, mock_host_failure, mock_notification_save, self, mock_host_failure, mock_notify_about_notification_update,
mock_notification_get): mock_notification_save, mock_notification_get):
notification = self._get_compute_host_type_notification() notification = self._get_compute_host_type_notification()
mock_notification_get.return_value = notification mock_notification_get.return_value = notification
notification.payload['event'] = 'other' notification.payload['event'] = 'other'
@ -418,13 +643,26 @@ class EngineManagerUnitTestCase(test.NoDBTestCase):
notification=notification) notification=notification)
self.assertEqual("ignored", notification.status) self.assertEqual("ignored", notification.status)
self.assertFalse(mock_host_failure.called) 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("masakari.compute.nova.API.stop_server")
@mock.patch.object(notification_obj.Notification, "save") @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") @mock.patch("masakari.compute.nova.API.get_server")
def test_process_notification_type_vm_ignore_instance_in_paused( def test_process_notification_type_vm_ignore_instance_in_paused(
self, mock_get_server, mock_notification_save, mock_stop_server, self, mock_get_server, mock_format,
mock_notification_get): 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() notification = _get_vm_type_notification()
mock_notification_get.return_value = notification mock_notification_get.return_value = notification
mock_get_server.return_value = fakes.FakeNovaClient.Server( mock_get_server.return_value = fakes.FakeNovaClient.Server(
@ -435,13 +673,32 @@ class EngineManagerUnitTestCase(test.NoDBTestCase):
notification=notification) notification=notification)
self.assertEqual("ignored", notification.status) self.assertEqual("ignored", notification.status)
self.assertFalse(mock_stop_server.called) 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("masakari.compute.nova.API.stop_server")
@mock.patch.object(notification_obj.Notification, "save") @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") @mock.patch("masakari.compute.nova.API.get_server")
def test_process_notification_type_vm_ignore_instance_in_rescued( def test_process_notification_type_vm_ignore_instance_in_rescued(
self, mock_get_server, mock_notification_save, mock_stop_server, self, mock_get_server, mock_format,
mock_notification_get): 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() notification = _get_vm_type_notification()
mock_notification_get.return_value = notification mock_notification_get.return_value = notification
mock_get_server.return_value = fakes.FakeNovaClient.Server( mock_get_server.return_value = fakes.FakeNovaClient.Server(
@ -452,6 +709,21 @@ class EngineManagerUnitTestCase(test.NoDBTestCase):
notification=notification) notification=notification)
self.assertEqual("ignored", notification.status) self.assertEqual("ignored", notification.status)
self.assertFalse(mock_stop_server.called) 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, def test_process_notification_stop_from_recovery_failure(self,
mock_get_noti): mock_get_noti):

View File

@ -19,11 +19,14 @@ import copy
import mock import mock
from oslo_utils import timeutils from oslo_utils import timeutils
from masakari.api import utils as api_utils
from masakari.compute import nova as nova_obj from masakari.compute import nova as nova_obj
from masakari.engine import rpcapi as engine_rpcapi from masakari.engine import rpcapi as engine_rpcapi
from masakari import exception from masakari import exception
from masakari.ha import api as ha_api from masakari.ha import api as ha_api
from masakari import objects
from masakari.objects import base as obj_base 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 host as host_obj
from masakari.objects import notification as notification_obj from masakari.objects import notification as notification_obj
from masakari.objects import segment as segment_obj from masakari.objects import segment as segment_obj
@ -61,6 +64,12 @@ class FailoverSegmentAPITestCase(test.NoDBTestCase):
service_type="COMPUTE", recovery_method="auto", service_type="COMPUTE", recovery_method="auto",
uuid=uuidsentinel.fake_segment 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): def _assert_segment_data(self, expected, actual):
self.assertTrue(obj_base.obj_equal_prims(expected, actual), self.assertTrue(obj_base.obj_equal_prims(expected, actual),
@ -125,6 +134,30 @@ class FailoverSegmentAPITestCase(test.NoDBTestCase):
self._assert_segment_data( self._assert_segment_data(
self.failover_segment, _make_segment_obj(result)) 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') @mock.patch.object(segment_obj.FailoverSegment, 'get_by_uuid')
def test_get_segment(self, mock_get_segment): def test_get_segment(self, mock_get_segment):
@ -157,21 +190,94 @@ class FailoverSegmentAPITestCase(test.NoDBTestCase):
segment_data) segment_data)
self._assert_segment_data(self.failover_segment, result) 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, @mock.patch.object(segment_obj.FailoverSegment,
'is_under_recovery') 'is_under_recovery')
@mock.patch.object(segment_obj, 'FailoverSegment') @mock.patch.object(segment_obj, 'FailoverSegment')
@mock.patch.object(segment_obj.FailoverSegment, 'get_by_uuid') @mock.patch.object(segment_obj.FailoverSegment, 'get_by_uuid')
def test_update_segment_under_recovery(self, mock_get, mock_segment_obj, 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"} segment_data = {"name": "segment1"}
mock_get.return_value = self.failover_segment mock_get.return_value = self.failover_segment
mock_segment_obj.return_value = self.failover_segment mock_segment_obj.return_value = self.failover_segment
mock_is_under_recovery.return_value = True 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.segment_api.update_segment,
self.context, uuidsentinel.fake_segment, self.context, uuidsentinel.fake_segment,
segment_data) 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): class HostAPITestCase(test.NoDBTestCase):
"""Test Case for host api.""" """Test Case for host api."""
@ -194,6 +300,8 @@ class HostAPITestCase(test.NoDBTestCase):
uuid=uuidsentinel.fake_host_1, uuid=uuidsentinel.fake_host_1,
failover_segment_id=uuidsentinel.fake_segment1 failover_segment_id=uuidsentinel.fake_segment1
) )
self.exception_in_use = exception.HostInUse(
uuid=self.host.uuid)
def _assert_host_data(self, expected, actual): def _assert_host_data(self, expected, actual):
self.assertTrue(obj_base.obj_equal_prims(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.host_api.create_host,
self.context, uuidsentinel.fake_segment1, host_data) 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('oslo_utils.uuidutils.generate_uuid')
@mock.patch('masakari.db.host_create') @mock.patch('masakari.db.host_create')
@mock.patch.object(host_obj.Host, '_from_db_object') @mock.patch.object(host_obj.Host, '_from_db_object')
@ -303,7 +446,8 @@ class HostAPITestCase(test.NoDBTestCase):
mock_hypervisor_search, mock_hypervisor_search,
mock__from_db_object, mock__from_db_object,
mock_host_create, mock_host_create,
mock_generate_uuid): mock_generate_uuid,
mock_notify_about_host_api):
host_data = { host_data = {
"name": "host-1", "type": "fake-type", "name": "host-1", "type": "fake-type",
"reserved": 'On', "reserved": 'On',
@ -322,10 +466,19 @@ class HostAPITestCase(test.NoDBTestCase):
mock_host_create.create = mock.Mock() mock_host_create.create = mock.Mock()
mock_get_segment.return_value = self.failover_segment mock_get_segment.return_value = self.failover_segment
mock_generate_uuid.return_value = uuidsentinel.fake_uuid mock_generate_uuid.return_value = uuidsentinel.fake_uuid
self.host_api.create_host(self.context, result = self.host_api.create_host(self.context,
uuidsentinel.fake_segment1, uuidsentinel.fake_segment1,
host_data) host_data)
mock_host_create.assert_called_with(self.context, expected_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(host_obj.Host, 'get_by_uuid')
@mock.patch.object(segment_obj.FailoverSegment, '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(host_obj.Host, 'get_by_uuid')
@mock.patch.object(segment_obj.FailoverSegment, '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, def test_update_with_non_existing_host(self, mock_segment_get, mock_get,
mock_hypervisor_search, mock_hypervisor_search,
mock_is_under_recovery): mock_is_under_recovery):
mock_segment_get.return_value = self.failover_segment mock_segment_get.return_value = self.failover_segment
host_data = {"name": "host-2"} host_data = {"name": "host-2"}
mock_get.return_value = self.host mock_get.return_value = self.host
@ -387,23 +540,59 @@ class HostAPITestCase(test.NoDBTestCase):
uuidsentinel.fake_host_1, uuidsentinel.fake_host_1,
host_data) 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, @mock.patch.object(segment_obj.FailoverSegment,
'is_under_recovery') 'is_under_recovery')
@mock.patch.object(host_obj, 'Host') @mock.patch.object(host_obj, 'Host')
@mock.patch.object(host_obj.Host, 'get_by_uuid') @mock.patch.object(host_obj.Host, 'get_by_uuid')
@mock.patch.object(segment_obj.FailoverSegment, 'get_by_uuid') @mock.patch.object(segment_obj.FailoverSegment, 'get_by_uuid')
def test_update_host_under_recovery(self, mock_segment_get, mock_get, def test_update_host_under_recovery(self, mock_segment_get, mock_get,
mock_host_obj, mock_host_obj, mock_is_under_recovery, mock_HostInUse):
mock_is_under_recovery):
mock_segment_get.return_value = self.failover_segment mock_segment_get.return_value = self.failover_segment
host_data = {"name": "host_1"} host_data = {"name": "host_1"}
mock_get.return_value = self.host mock_get.return_value = self.host
mock_host_obj.return_value = self.host mock_host_obj.return_value = self.host
mock_is_under_recovery.return_value = True 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, self.context, uuidsentinel.fake_segment,
uuidsentinel.fake_host_1, host_data) uuidsentinel.fake_host_1, host_data)
@mock.patch.object(api_utils, 'notify_about_host_api')
@mock.patch.object(segment_obj.FailoverSegment, @mock.patch.object(segment_obj.FailoverSegment,
'is_under_recovery') 'is_under_recovery')
@mock.patch.object(host_obj.Host, '_from_db_object') @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') @mock.patch.object(segment_obj.FailoverSegment, 'get_by_uuid')
def test_update_convert_boolean_attributes( def test_update_convert_boolean_attributes(
self, mock_segment, mock_host_update, mock_host_object, 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 = { host_data = {
"reserved": 'Off', "reserved": 'Off',
"on_maintenance": 'True', "on_maintenance": 'True',
@ -430,26 +620,83 @@ class HostAPITestCase(test.NoDBTestCase):
mock_host_object.return_value = self.host mock_host_object.return_value = self.host
self.host._context = self.context self.host._context = self.context
mock_is_under_recovery.return_value = False mock_is_under_recovery.return_value = False
self.host_api.update_host(self.context, result = self.host_api.update_host(self.context,
uuidsentinel.fake_segment1, uuidsentinel.fake_segment1,
uuidsentinel.fake_host_1, uuidsentinel.fake_host_1,
host_data) host_data)
mock_host_update.assert_called_with(self.context, mock_host_update.assert_called_with(self.context,
uuidsentinel.fake_host_1, uuidsentinel.fake_host_1,
expected_data) 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, @mock.patch.object(segment_obj.FailoverSegment,
'is_under_recovery') 'is_under_recovery')
@mock.patch.object(host_obj, 'Host') @mock.patch.object(host_obj, 'Host')
@mock.patch.object(host_obj.Host, 'get_by_uuid') @mock.patch.object(host_obj.Host, 'get_by_uuid')
@mock.patch.object(segment_obj.FailoverSegment, 'get_by_uuid') @mock.patch.object(segment_obj.FailoverSegment, 'get_by_uuid')
def test_delete_host_under_recovery(self, mock_segment_get, mock_get, def test_delete_host_under_recovery(self, mock_segment_get, mock_get,
mock_host_obj, mock_host_obj, mock_is_under_recovery, mock_HostInUse):
mock_is_under_recovery):
mock_segment_get.return_value = self.failover_segment mock_segment_get.return_value = self.failover_segment
mock_get.return_value = self.host mock_get.return_value = self.host
mock_is_under_recovery.return_value = True 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, self.context, uuidsentinel.fake_segment,
uuidsentinel.fake_host_1) uuidsentinel.fake_host_1)
@ -479,6 +726,8 @@ class NotificationAPITestCase(test.NoDBTestCase):
status="running", status="running",
notification_uuid=uuidsentinel.fake_notification notification_uuid=uuidsentinel.fake_notification
) )
self.exception_duplicate = exception.DuplicateNotification(
host='host_1', type='COMPUTE_HOST')
def _assert_notification_data(self, expected, actual): def _assert_notification_data(self, expected, actual):
self.assertTrue(obj_base.obj_equal_prims(expected, actual), self.assertTrue(obj_base.obj_equal_prims(expected, actual),
@ -516,6 +765,48 @@ class NotificationAPITestCase(test.NoDBTestCase):
self._assert_notification_data( self._assert_notification_data(
self.notification, _make_notification_obj(result)) 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') @mock.patch.object(host_obj.Host, 'get_by_name')
def test_create_host_on_maintenance(self, mock_host): def test_create_host_on_maintenance(self, mock_host):
self.host.on_maintenance = True self.host.on_maintenance = True
@ -531,8 +822,11 @@ class NotificationAPITestCase(test.NoDBTestCase):
self.notification_api.create_notification, self.notification_api.create_notification,
self.context, notification_data) self.context, notification_data)
@mock.patch.object(exception, 'DuplicateNotification')
@mock.patch.object(objects, 'Notification')
@mock.patch.object(host_obj.Host, 'get_by_name') @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 mock_host.return_value = self.host
self.notification_api._is_duplicate_notification = mock.Mock( self.notification_api._is_duplicate_notification = mock.Mock(
return_value=True) return_value=True)
@ -542,8 +836,16 @@ class NotificationAPITestCase(test.NoDBTestCase):
'cluster_status': 'ONLINE'}, 'cluster_status': 'ONLINE'},
"type": "COMPUTE_HOST", "type": "COMPUTE_HOST",
"generated_time": str(NOW)} "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.notification_api.create_notification,
self.context, notification_data) self.context, notification_data)

View File

@ -18,8 +18,10 @@ import copy
import mock import mock
from oslo_utils import timeutils from oslo_utils import timeutils
from masakari.api import utils as api_utils
from masakari import db from masakari import db
from masakari import exception from masakari import exception
from masakari.objects import fields
from masakari.objects import host from masakari.objects import host
from masakari.objects import segment from masakari.objects import segment
from masakari.tests.unit.objects import test_objects from masakari.tests.unit.objects import test_objects
@ -115,8 +117,9 @@ class TestHostObject(test_objects._LocalTest):
return host_obj return host_obj
@mock.patch.object(api_utils, 'notify_about_host_api')
@mock.patch.object(db, 'host_create') @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 mock_db_create.return_value = fake_host
host_obj = self._host_create_attributes() host_obj = self._host_create_attributes()
@ -128,14 +131,34 @@ class TestHostObject(test_objects._LocalTest):
'reserved': False, 'name': u'foo-host', 'reserved': False, 'name': u'foo-host',
'control_attributes': u'fake_attributes', 'control_attributes': u'fake_attributes',
'type': u'fake-type'}) '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') @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 mock_host_create.return_value = fake_host
host_obj = self._host_create_attributes() host_obj = self._host_create_attributes()
host_obj.create() host_obj.create()
self.assertRaises(exception.ObjectActionError, 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, { mock_host_create.assert_called_once_with(self.context, {
'uuid': uuidsentinel.fake_host, 'name': 'foo-host', 'uuid': uuidsentinel.fake_host, 'name': 'foo-host',
@ -143,21 +166,39 @@ class TestHostObject(test_objects._LocalTest):
'type': 'fake-type', 'reserved': False, 'on_maintenance': False, 'type': 'fake-type', 'reserved': False, 'on_maintenance': False,
'control_attributes': 'fake_attributes'}) 'control_attributes': 'fake_attributes'})
@mock.patch.object(api_utils, 'notify_about_host_api')
@mock.patch.object(db, 'host_delete') @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 = self._host_create_attributes()
host_obj.id = 123 host_obj.id = 123
host_obj.destroy() host_obj.destroy()
mock_host_destroy.assert_called_once_with( mock_host_destroy.assert_called_once_with(
self.context, uuidsentinel.fake_host) 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') @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) mock_host_destroy.side_effect = exception.HostNotFound(id=123)
host_obj = self._host_create_attributes() host_obj = self._host_create_attributes()
host_obj.id = 123 host_obj.id = 123
self.assertRaises(exception.HostNotFound, host_obj.destroy) 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') @mock.patch.object(db, 'host_get_all_by_filters')
def test_get_host_by_filters(self, mock_api_get): def test_get_host_by_filters(self, mock_api_get):
@ -182,8 +223,9 @@ class TestHostObject(test_objects._LocalTest):
host.HostList.get_all, host.HostList.get_all,
self.context, limit=5, marker=host_name) self.context, limit=5, marker=host_name)
@mock.patch.object(api_utils, 'notify_about_host_api')
@mock.patch.object(db, 'host_update') @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 mock_host_update.return_value = fake_host
@ -201,6 +243,15 @@ class TestHostObject(test_objects._LocalTest):
'uuid': uuidsentinel.fake_host, 'uuid': uuidsentinel.fake_host,
'reserved': False, 'on_maintenance': False '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') @mock.patch.object(db, 'host_update')
def test_save_lazy_attribute_changed(self, mock_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 host_obj.id = 123
self.assertRaises(exception.ObjectActionError, host_obj.save) self.assertRaises(exception.ObjectActionError, host_obj.save)
@mock.patch.object(api_utils, 'notify_about_host_api')
@mock.patch.object(db, 'host_update') @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") 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 host_object.uuid = uuidsentinel.fake_host
self.assertRaises(exception.HostExists, host_object.save) 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') @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") 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 host_object.uuid = uuidsentinel.fake_host
self.assertRaises(exception.HostNotFound, host_object.save) 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 timeutils
from oslo_utils import uuidutils from oslo_utils import uuidutils
from masakari.api import utils as api_utils
from masakari import db from masakari import db
from masakari import exception from masakari import exception
from masakari.objects import fields
from masakari.objects import notification from masakari.objects import notification
from masakari.tests.unit.objects import test_objects from masakari.tests.unit.objects import test_objects
from masakari.tests import uuidsentinel from masakari.tests import uuidsentinel
@ -106,8 +108,9 @@ class TestNotificationObject(test_objects._LocalTest):
return notification_obj return notification_obj
@mock.patch.object(api_utils, 'notify_about_notification_api')
@mock.patch.object(db, 'notification_create') @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 mock_db_create.return_value = fake_db_notification
notification_obj = self._notification_create_attributes() notification_obj = self._notification_create_attributes()
@ -119,9 +122,20 @@ class TestNotificationObject(test_objects._LocalTest):
'notification_uuid': uuidsentinel.fake_notification, 'notification_uuid': uuidsentinel.fake_notification,
'generated_time': NOW, 'status': 'new', 'generated_time': NOW, 'status': 'new',
'type': 'COMPUTE_HOST', 'payload': '{"fake_key": "fake_value"}'}) '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(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 mock_notification_create.return_value = fake_db_notification
notification_obj = self._notification_create_attributes() notification_obj = self._notification_create_attributes()
notification_obj.create() notification_obj.create()
@ -133,11 +147,21 @@ class TestNotificationObject(test_objects._LocalTest):
'notification_uuid': uuidsentinel.fake_notification, 'notification_uuid': uuidsentinel.fake_notification,
'generated_time': NOW, 'status': 'new', 'generated_time': NOW, 'status': 'new',
'type': 'COMPUTE_HOST', 'payload': '{"fake_key": "fake_value"}'}) '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(db, 'notification_create')
@mock.patch.object(uuidutils, 'generate_uuid') @mock.patch.object(uuidutils, 'generate_uuid')
def test_create_without_passing_uuid_in_updates(self, mock_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_db_create.return_value = fake_db_notification
mock_generate_uuid.return_value = uuidsentinel.fake_notification mock_generate_uuid.return_value = uuidsentinel.fake_notification
@ -152,6 +176,12 @@ class TestNotificationObject(test_objects._LocalTest):
'generated_time': NOW, 'status': 'new', 'generated_time': NOW, 'status': 'new',
'type': 'COMPUTE_HOST', 'payload': '{"fake_key": "fake_value"}'}) 'type': 'COMPUTE_HOST', 'payload': '{"fake_key": "fake_value"}'})
self.assertTrue(mock_generate_uuid.called) 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') @mock.patch.object(db, 'notification_delete')
def test_destroy(self, mock_notification_delete): def test_destroy(self, mock_notification_delete):

View File

@ -18,7 +18,9 @@ import copy
import mock import mock
from oslo_utils import timeutils from oslo_utils import timeutils
from masakari.api import utils as api_utils
from masakari import exception from masakari import exception
from masakari.objects import fields
from masakari.objects import segment from masakari.objects import segment
from masakari.tests.unit.objects import test_objects from masakari.tests.unit.objects import test_objects
from masakari.tests import uuidsentinel from masakari.tests import uuidsentinel
@ -86,8 +88,9 @@ class TestFailoverSegmentObject(test_objects._LocalTest):
return segment_obj return segment_obj
@mock.patch.object(api_utils, 'notify_about_segment_api')
@mock.patch('masakari.db.failover_segment_create') @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 mock_segment_create.return_value = fake_segment
segment_obj = self._segment_create_attribute() segment_obj = self._segment_create_attribute()
@ -98,9 +101,20 @@ class TestFailoverSegmentObject(test_objects._LocalTest):
'uuid': uuidsentinel.fake_segment, 'name': 'foo-segment', 'uuid': uuidsentinel.fake_segment, 'name': 'foo-segment',
'description': 'keydata', 'service_type': 'fake-user', 'description': 'keydata', 'service_type': 'fake-user',
'recovery_method': 'auto'}) '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') @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 mock_segment_create.return_value = fake_segment
segment_obj = self._segment_create_attribute() segment_obj = self._segment_create_attribute()
@ -111,25 +125,52 @@ class TestFailoverSegmentObject(test_objects._LocalTest):
'uuid': uuidsentinel.fake_segment, 'name': 'foo-segment', 'uuid': uuidsentinel.fake_segment, 'name': 'foo-segment',
'description': 'keydata', 'service_type': 'fake-user', 'description': 'keydata', 'service_type': 'fake-user',
'recovery_method': 'auto'}) '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') @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 = self._segment_create_attribute()
segment_obj.id = 123 segment_obj.id = 123
segment_obj.destroy() segment_obj.destroy()
mock_segment_destroy.assert_called_once_with( mock_segment_destroy.assert_called_once_with(
self.context, uuidsentinel.fake_segment) 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') @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( mock_segment_destroy.side_effect = exception.FailoverSegmentNotFound(
id=123) id=123)
segment_obj = self._segment_create_attribute() segment_obj = self._segment_create_attribute()
segment_obj.id = 123 segment_obj.id = 123
self.assertRaises(exception.FailoverSegmentNotFound, self.assertRaises(exception.FailoverSegmentNotFound,
segment_obj.destroy) 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') @mock.patch('masakari.db.failover_segment_get_all_by_filters')
def test_get_segment_by_recovery_method(self, mock_api_get): def test_get_segment_by_recovery_method(self, mock_api_get):
@ -175,8 +216,9 @@ class TestFailoverSegmentObject(test_objects._LocalTest):
segment.FailoverSegmentList.get_all, segment.FailoverSegmentList.get_all,
self.context, limit=5, marker=segment_name) self.context, limit=5, marker=segment_name)
@mock.patch.object(api_utils, 'notify_about_segment_api')
@mock.patch('masakari.db.failover_segment_update') @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 mock_segment_update.return_value = fake_segment
@ -188,9 +230,20 @@ class TestFailoverSegmentObject(test_objects._LocalTest):
self.compare_obj(segment_object, fake_segment) self.compare_obj(segment_object, fake_segment)
self.assertTrue(mock_segment_update.called) 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') @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 = ( mock_segment_update.side_effect = (
exception.FailoverSegmentNotFound(id=uuidsentinel.fake_segment)) exception.FailoverSegmentNotFound(id=uuidsentinel.fake_segment))
@ -202,9 +255,17 @@ class TestFailoverSegmentObject(test_objects._LocalTest):
self.assertRaises(exception.FailoverSegmentNotFound, self.assertRaises(exception.FailoverSegmentNotFound,
segment_object.save) 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') @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 = ( mock_segment_update.side_effect = (
exception.FailoverSegmentExists(name="foo-segment")) exception.FailoverSegmentExists(name="foo-segment"))
@ -215,3 +276,9 @@ class TestFailoverSegmentObject(test_objects._LocalTest):
segment_object.uuid = uuidsentinel.fake_segment segment_object.uuid = uuidsentinel.fake_segment
self.assertRaises(exception.FailoverSegmentExists, segment_object.save) 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