Add notification related objects

This patch adds notification related objects 'Notification'
and 'NotificationList'.

Change-Id: I98dfc70fdae4e6a41f1a82b5d8bcc363b6078922
This commit is contained in:
dineshbhor 2016-09-08 16:04:41 +05:30
parent 630b009384
commit d918efb9e0
6 changed files with 437 additions and 0 deletions

View File

@ -19,4 +19,5 @@ def register_all():
# function in order for it to be registered by services that may
# need to receive it via RPC.
__import__('masakari.objects.host')
__import__('masakari.objects.notification')
__import__('masakari.objects.segment')

View File

@ -23,6 +23,7 @@ StringField = fields.StringField
EnumField = fields.EnumField
UUIDField = fields.UUIDField
DateTimeField = fields.DateTimeField
DictOfStringsField = fields.DictOfStringsField
ObjectField = fields.ObjectField
BaseEnumField = fields.BaseEnumField
ListOfObjectsField = fields.ListOfObjectsField
@ -59,5 +60,64 @@ class FailoverSegmentRecoveryMethod(Enum):
return cls.ALL[index]
class NotificationType(Enum):
"""Represents possible notification types."""
COMPUTE_HOST = "COMPUTE_HOST"
VM = "VM"
PROCESS = "PROCESS"
ALL = (COMPUTE_HOST, VM, PROCESS)
def __init__(self):
super(NotificationType,
self).__init__(valid_values=NotificationType.ALL)
@classmethod
def index(cls, value):
"""Return an index into the Enum given a value."""
return cls.ALL.index(value)
@classmethod
def from_index(cls, index):
"""Return the Enum value at a given index."""
return cls.ALL[index]
class NotificationStatus(Enum):
"""Represents possible statuses for notifications."""
NEW = "new"
RUNNING = "running"
ERROR = "error"
FAILED = "failed"
IGNORED = "ignored"
FINISHED = "finished"
ALL = (NEW, RUNNING, ERROR, FAILED, IGNORED, FINISHED)
def __init__(self):
super(NotificationStatus,
self).__init__(valid_values=NotificationStatus.ALL)
@classmethod
def index(cls, value):
"""Return an index into the Enum given a value."""
return cls.ALL.index(value)
@classmethod
def from_index(cls, index):
"""Return the Enum value at a given index."""
return cls.ALL[index]
class FailoverSegmentRecoveryMethodField(BaseEnumField):
AUTO_TYPE = FailoverSegmentRecoveryMethod()
class NotificationTypeField(BaseEnumField):
AUTO_TYPE = NotificationType()
class NotificationStatusField(BaseEnumField):
AUTO_TYPE = NotificationStatus()

View File

@ -0,0 +1,131 @@
# Copyright 2016 NTT Data.
# 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.
from oslo_log import log as logging
from oslo_serialization import jsonutils
from oslo_utils import uuidutils
from masakari import db
from masakari import exception
from masakari import objects
from masakari.objects import base
from masakari.objects import fields
LOG = logging.getLogger(__name__)
@base.MasakariObjectRegistry.register
class Notification(base.MasakariPersistentObject, base.MasakariObject,
base.MasakariObjectDictCompat):
VERSION = '1.0'
fields = {
'id': fields.IntegerField(),
'notification_uuid': fields.UUIDField(),
'generated_time': fields.DateTimeField(),
'source_host_uuid': fields.UUIDField(),
'type': fields.NotificationTypeField(),
'payload': fields.DictOfStringsField(),
'status': fields.NotificationStatusField(),
}
@staticmethod
def _from_db_object(context, notification, db_notification):
for key in notification.fields:
if key != 'payload':
setattr(notification, key, db_notification.get(key))
else:
payload = db_notification.get("payload")
notification.payload = jsonutils.loads(payload)
notification.obj_reset_changes()
notification._context = context
return notification
@base.remotable_classmethod
def get_by_id(cls, context, id):
db_notification = db.notification_get_by_id(context, id)
return cls._from_db_object(context, cls(), db_notification)
@base.remotable_classmethod
def get_by_uuid(cls, context, uuid):
db_notification = db.notification_get_by_uuid(context, uuid)
return cls._from_db_object(context, cls(), db_notification)
@base.remotable
def create(self):
if self.obj_attr_is_set('id'):
raise exception.ObjectActionError(action='create',
reason='already created')
updates = self.masakari_obj_get_changes()
if 'notification_uuid' not in updates:
updates['notification_uuid'] = uuidutils.generate_uuid()
LOG.debug('Generated uuid %(uuid)s for notifications',
dict(uuid=updates['notification_uuid']))
if 'payload' in updates:
updates['payload'] = jsonutils.dumps(updates['payload'])
db_notification = db.notification_create(self._context, updates)
self._from_db_object(self._context, self, db_notification)
@base.remotable
def save(self):
updates = self.masakari_obj_get_changes()
updates.pop('id', None)
db_notification = db.notification_update(self._context,
self.notification_uuid,
updates)
self._from_db_object(self._context, self, db_notification)
@base.remotable
def destroy(self):
if not self.obj_attr_is_set('id'):
raise exception.ObjectActionError(action='destroy',
reason='already destroyed')
if not self.obj_attr_is_set('notification_uuid'):
raise exception.ObjectActionError(action='destroy',
reason='no uuid')
db.notification_delete(self._context, self.notification_uuid)
delattr(self, base.get_attrname('id'))
@base.MasakariObjectRegistry.register
class NotificationList(base.ObjectListBase, base.MasakariObject):
VERSION = '1.0'
fields = {
'objects': fields.ListOfObjectsField('Notification'),
}
@base.remotable_classmethod
def get_all(cls, context, filters=None, sort_keys=None,
sort_dirs=None, limit=None, marker=None):
groups = db.notifications_get_all_by_filters(context, filters=filters,
sort_keys=sort_keys,
sort_dirs=sort_dirs,
limit=limit, marker=marker
)
return base.obj_make_list(context, cls(context), objects.Notification,
groups)

View File

@ -173,6 +173,20 @@ class TestEnum(TestField):
fields.EnumField, [])
class TestDictOfStrings(TestField):
def setUp(self):
super(TestDictOfStrings, self).setUp()
self.field = fields.DictOfStringsField()
self.coerce_good_values = [({'foo': 'bar'}, {'foo': 'bar'}),
({'foo': 1}, {'foo': '1'})]
self.coerce_bad_values = [{1: 'bar'}, {'foo': None}, 'foo']
self.to_primitive_values = [({'foo': 'bar'}, {'foo': 'bar'})]
self.from_primitive_values = [({'foo': 'bar'}, {'foo': 'bar'})]
def test_stringify(self):
self.assertEqual("{key='val'}", self.field.stringify({'key': 'val'}))
class TestInteger(TestField):
def setUp(self):
super(TestInteger, self).setUp()

View File

@ -0,0 +1,229 @@
# Copyright 2016 NTT DATA
# 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 copy
import mock
from oslo_utils import timeutils
from oslo_utils import uuidutils
from masakari import db
from masakari import exception
from masakari.objects import notification
from masakari.tests.unit.objects import test_objects
from masakari.tests import uuidsentinel
NOW = timeutils.utcnow().replace(microsecond=0)
def _fake_db_notification(**kwargs):
fake_notification = {
'created_at': NOW,
'updated_at': None,
'deleted_at': None,
'deleted': False,
'id': 123,
'notification_uuid': uuidsentinel.fake_notification,
'generated_time': NOW,
'type': 'COMPUTE_HOST',
'payload': '{"fake_key": "fake_value"}',
'status': 'new',
'source_host_uuid': uuidsentinel.fake_host,
}
fake_notification.update(kwargs)
return fake_notification
def _fake_object_notification(**kwargs):
fake_notification = {
'created_at': NOW,
'updated_at': None,
'deleted_at': None,
'deleted': False,
'id': 123,
'notification_uuid': uuidsentinel.fake_notification,
'generated_time': NOW,
'type': 'COMPUTE_HOST',
'payload': {"fake_key": "fake_value"},
'status': 'new',
'source_host_uuid': uuidsentinel.fake_host,
}
fake_notification.update(kwargs)
return fake_notification
fake_object_notification = _fake_object_notification()
fake_db_notification = _fake_db_notification()
class TestNotificationObject(test_objects._LocalTest):
def _test_query(self, db_method, obj_method, *args, **kwargs):
with mock.patch.object(db, db_method) as mock_db:
db_exception = kwargs.pop('db_exception', None)
if db_exception:
mock_db.side_effect = db_exception
else:
mock_db.return_value = fake_db_notification
obj = getattr(notification.Notification, obj_method
)(self.context, *args, **kwargs)
if db_exception:
self.assertIsNone(obj)
self.compare_obj(obj, fake_object_notification)
def test_get_by_id(self):
self._test_query('notification_get_by_id', 'get_by_id', 123)
def test_get_by_uuid(self):
self._test_query('notification_get_by_uuid', 'get_by_uuid',
uuidsentinel.fake_segment)
def _notification_create_attributes(self, skip_uuid=False):
notification_obj = notification.Notification(context=self.context)
notification_obj.generated_time = NOW
notification_obj.type = "COMPUTE_HOST"
notification_obj.payload = {'fake_key': 'fake_value'}
notification_obj.status = "new"
if not skip_uuid:
notification_obj.notification_uuid = uuidsentinel.fake_notification
notification_obj.source_host_uuid = uuidsentinel.fake_host
return notification_obj
@mock.patch.object(db, 'notification_create')
def test_create(self, mock_db_create):
mock_db_create.return_value = fake_db_notification
notification_obj = self._notification_create_attributes()
notification_obj.create()
self.compare_obj(notification_obj, fake_object_notification)
mock_db_create.assert_called_once_with(self.context, {
'source_host_uuid': uuidsentinel.fake_host,
'notification_uuid': uuidsentinel.fake_notification,
'generated_time': NOW, 'status': 'new',
'type': 'COMPUTE_HOST', 'payload': '{"fake_key": "fake_value"}'})
@mock.patch.object(db, 'notification_create')
def test_recreate_fails(self, mock_notification_create):
mock_notification_create.return_value = fake_db_notification
notification_obj = self._notification_create_attributes()
notification_obj.create()
self.assertRaises(exception.ObjectActionError, notification_obj.create)
mock_notification_create.assert_called_once_with(self.context, {
'source_host_uuid': uuidsentinel.fake_host,
'notification_uuid': uuidsentinel.fake_notification,
'generated_time': NOW, 'status': 'new',
'type': 'COMPUTE_HOST', 'payload': '{"fake_key": "fake_value"}'})
@mock.patch.object(db, 'notification_create')
@mock.patch.object(uuidutils, 'generate_uuid')
def test_create_without_passing_uuid_in_updates(self, mock_generate_uuid,
mock_db_create):
mock_db_create.return_value = fake_db_notification
mock_generate_uuid.return_value = uuidsentinel.fake_notification
notification_obj = self._notification_create_attributes(skip_uuid=True)
notification_obj.create()
self.compare_obj(notification_obj, fake_object_notification)
mock_db_create.assert_called_once_with(self.context, {
'source_host_uuid': uuidsentinel.fake_host,
'notification_uuid': uuidsentinel.fake_notification,
'generated_time': NOW, 'status': 'new',
'type': 'COMPUTE_HOST', 'payload': '{"fake_key": "fake_value"}'})
self.assertTrue(mock_generate_uuid.called)
@mock.patch.object(db, 'notification_delete')
def test_destroy(self, mock_notification_delete):
notification_obj = self._notification_create_attributes()
notification_obj.id = 123
notification_obj.destroy()
(mock_notification_delete.
assert_called_once_with(self.context, uuidsentinel.fake_notification))
self.assertRaises(NotImplementedError, lambda: notification_obj.id)
@mock.patch.object(db, 'notification_delete')
def test_destroy_without_id(self, mock_destroy):
notification_obj = self._notification_create_attributes()
self.assertRaises(exception.ObjectActionError,
notification_obj.destroy)
self.assertFalse(mock_destroy.called)
@mock.patch.object(db, 'notification_delete')
def test_destroy_without_notification_uuid(self, mock_destroy):
notification_obj = self._notification_create_attributes(skip_uuid=True)
notification_obj.id = 123
self.assertRaises(exception.ObjectActionError,
notification_obj.destroy)
self.assertFalse(mock_destroy.called)
@mock.patch.object(db, 'notifications_get_all_by_filters')
def test_get_notification_by_filters(self, mock_api_get):
fake_db_notification2 = copy.deepcopy(fake_db_notification)
fake_db_notification2['type'] = 'PROCESS'
fake_db_notification2['id'] = 124
fake_db_notification2[
'notification_uuid'] = uuidsentinel.fake_db_notification2
mock_api_get.return_value = [fake_db_notification2,
fake_db_notification]
filters = {'status': 'new'}
notification_result = (notification.NotificationList.
get_all(self.context, filters=filters))
self.assertEqual(2, len(notification_result))
mock_api_get.assert_called_once_with(self.context, filters={
'status': 'new'
}, limit=None, marker=None, sort_dirs=None, sort_keys=None)
@mock.patch.object(db, 'notifications_get_all_by_filters')
def test_get_limit_and_marker_invalid_marker(self, mock_api_get):
notification_uuid = uuidsentinel.fake_notification
mock_api_get.side_effect = (exception.
MarkerNotFound(marker=notification_uuid))
self.assertRaises(exception.MarkerNotFound,
notification.NotificationList.get_all,
self.context, limit=5, marker=notification_uuid)
@mock.patch.object(db, 'notification_update')
def test_save(self, mock_notification_update):
mock_notification_update.return_value = fake_db_notification
notification_obj = self._notification_create_attributes()
notification_obj.id = 123
notification_obj.save()
self.compare_obj(notification_obj, fake_object_notification)
(mock_notification_update.
assert_called_once_with(self.context, uuidsentinel.fake_notification,
{'source_host_uuid': uuidsentinel.fake_host,
'notificati'
'on_uuid': uuidsentinel.fake_notification,
'status': 'new', 'generated_time': NOW,
'payload': {'fake_key': u'fake_value'},
'type': 'COMPUTE_HOST'}
))

View File

@ -656,6 +656,8 @@ object_data = {
'FailoverSegmentList': '1.0-dfc5c6f5704d24dcaa37b0bbb03cbe60',
'Host': '1.0-803264cd1563db37d0bf7cff48e34c1d',
'HostList': '1.0-25ebe1b17fbd9f114fae8b6a10d198c0',
'Notification': '1.0-eedfa3c203c100897021bd23f0ddf68c',
'NotificationList': '1.0-25ebe1b17fbd9f114fae8b6a10d198c0',
'MyObj': '1.6-ee7b607402fbfb3390a92ab7199e0d88',
'MyOwnedObject': '1.0-fec853730bd02d54cc32771dd67f08a0'
}