Add notification related objects
This patch adds notification related objects 'Notification' and 'NotificationList'. Change-Id: I98dfc70fdae4e6a41f1a82b5d8bcc363b6078922
This commit is contained in:
parent
630b009384
commit
d918efb9e0
|
@ -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')
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
|
@ -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()
|
||||
|
|
|
@ -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'}
|
||||
))
|
|
@ -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'
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue