Versioned notifications for service create and delete

New notifications service.create and service.delete are introduced
with INFO priority and the payload of the notification is the serialized
form of the already existing Service versioned object. Service.create
notification will be emitted after the service is created (so the uuid
is available) and also send the service.delete notification after the
service is deleted.

Implement blueprint: service-create-destroy-notification

Change-Id: I955d98f9fd4b121f98e172e5ab30eb668a24006d
This commit is contained in:
liyingjun 2017-11-14 16:58:23 +08:00 committed by Matt Riedemann
parent bb95f6a218
commit 8e793a6c6f
12 changed files with 158 additions and 29 deletions

View File

@ -0,0 +1,23 @@
{
"priority": "INFO",
"payload": {
"nova_object.namespace": "nova",
"nova_object.name": "ServiceStatusPayload",
"nova_object.version": "1.1",
"nova_object.data": {
"host": "host2",
"disabled": false,
"last_seen_up": null,
"binary": "nova-compute",
"topic": "compute",
"disabled_reason": null,
"report_count": 0,
"forced_down": false,
"version": 23,
"availability_zone": null,
"uuid": "fa69c544-906b-4a6a-a9c6-c1f7a8078c73"
}
},
"event_type": "service.create",
"publisher_id": "nova-compute:host2"
}

View File

@ -0,0 +1,23 @@
{
"priority": "INFO",
"payload": {
"nova_object.namespace": "nova",
"nova_object.name": "ServiceStatusPayload",
"nova_object.version": "1.1",
"nova_object.data": {
"host": "host2",
"disabled": false,
"last_seen_up": null,
"binary": "nova-compute",
"topic": "compute",
"disabled_reason": null,
"report_count": 0,
"forced_down": false,
"version": 23,
"availability_zone": null,
"uuid": "32887c0a-5063-4d39-826f-4903c241c376"
}
},
"event_type": "service.delete",
"publisher_id": "nova-compute:host2"
}

View File

@ -147,7 +147,8 @@ class NotificationPublisher(NotificationObject):
# 2.1: The type of the source field changed from string to enum.
# This only needs a minor bump as the enum uses the possible
# values of the previous string field
VERSION = '2.1'
# 2.2: New enum for source fields added
VERSION = '2.2'
fields = {
'host': fields.StringField(nullable=False),
@ -161,7 +162,12 @@ class NotificationPublisher(NotificationObject):
@classmethod
def from_service_obj(cls, service):
return cls(host=service.host, source=service.binary)
# nova-osapi_compute binary name needs to be translated to nova-api
# notification source enum value.
source = ("nova-api"
if service.binary == "nova-osapi_compute"
else service.binary)
return cls(host=service.host, source=source)
@base.NovaObjectRegistry.register_if(False)

View File

@ -18,7 +18,9 @@ from nova.objects import base as nova_base
from nova.objects import fields
@base.notification_sample('service-create.json')
@base.notification_sample('service-update.json')
@base.notification_sample('service-delete.json')
@nova_base.NovaObjectRegistry.register_notification
class ServiceStatusNotification(base.NotificationBase):
# Version 1.0: Initial version

View File

@ -800,8 +800,14 @@ class NotificationSource(BaseNovaEnum):
API = 'nova-api'
CONDUCTOR = 'nova-conductor'
SCHEDULER = 'nova-scheduler'
NETWORK = 'nova-network'
CONSOLEAUTH = 'nova-consoleauth'
CELLS = 'nova-cells'
CONSOLE = 'nova-console'
METADATA = 'nova-metadata'
ALL = (API, COMPUTE, CONDUCTOR, SCHEDULER)
ALL = (API, COMPUTE, CONDUCTOR, SCHEDULER,
NETWORK, CONSOLEAUTH, CELLS, CONSOLE, METADATA)
class NotificationAction(BaseNovaEnum):

View File

@ -356,6 +356,7 @@ class Service(base.NovaPersistentObject, base.NovaObject,
db_service = db.service_create(self._context, updates)
self._from_db_object(self._context, self, db_service)
self._send_notification(fields.NotificationAction.CREATE)
@base.remotable
def save(self):
@ -373,19 +374,23 @@ class Service(base.NovaPersistentObject, base.NovaObject,
# every other field change. See the comment in save() too.
if set(updates.keys()).intersection(
{'disabled', 'disabled_reason', 'forced_down'}):
payload = service_notification.ServiceStatusPayload(self)
service_notification.ServiceStatusNotification(
publisher=notification.NotificationPublisher.from_service_obj(
self),
event_type=notification.EventType(
object='service',
action=fields.NotificationAction.UPDATE),
priority=fields.NotificationPriority.INFO,
payload=payload).emit(self._context)
self._send_notification(fields.NotificationAction.UPDATE)
def _send_notification(self, action):
payload = service_notification.ServiceStatusPayload(self)
service_notification.ServiceStatusNotification(
publisher=notification.NotificationPublisher.from_service_obj(
self),
event_type=notification.EventType(
object='service',
action=action),
priority=fields.NotificationPriority.INFO,
payload=payload).emit(self._context)
@base.remotable
def destroy(self):
db.service_destroy(self._context, self.id)
self._send_notification(fields.NotificationAction.DELETE)
@classmethod
def enable_min_version_cache(cls):

View File

@ -87,6 +87,8 @@ class NotificationSampleTestBase(test.TestCase,
self.start_service('scheduler')
self.start_service('network', manager=CONF.network_manager)
self.compute = self.start_service('compute')
# Reset the service create notifications
fake_notifier.reset()
def _get_notification_sample(self, sample):
sample_dir = os.path.dirname(os.path.abspath(__file__))

View File

@ -20,15 +20,12 @@ from nova.tests import fixtures
from nova.tests.functional.notification_sample_tests \
import notification_sample_base
from nova.tests.unit.api.openstack.compute import test_services
from nova.tests.unit import fake_notifier
class TestServiceUpdateNotificationSamplev2_52(
class TestServiceNotificationBase(
notification_sample_base.NotificationSampleTestBase):
# These tests have to be capped at 2.52 since the PUT format changes in
# the 2.53 microversion.
MAX_MICROVERSION = '2.52'
def _verify_notification(self, sample_file_name, replacements=None,
actual=None):
# This just extends the generic _verify_notification to default the
@ -36,9 +33,16 @@ class TestServiceUpdateNotificationSamplev2_52(
# after every service version bump.
if 'version' not in replacements:
replacements['version'] = service.SERVICE_VERSION
base = super(TestServiceUpdateNotificationSamplev2_52, self)
base = super(TestServiceNotificationBase, self)
base._verify_notification(sample_file_name, replacements, actual)
class TestServiceUpdateNotificationSamplev2_52(TestServiceNotificationBase):
# These tests have to be capped at 2.52 since the PUT format changes in
# the 2.53 microversion.
MAX_MICROVERSION = '2.52'
def setUp(self):
super(TestServiceUpdateNotificationSamplev2_52, self).setUp()
self.stub_out("nova.db.service_get_by_host_and_binary",
@ -133,3 +137,24 @@ class TestServiceUpdateNotificationSampleLatest(
'disabled': True,
'disabled_reason': 'test2',
'uuid': self.service_uuid})
class TestServiceNotificationSample(TestServiceNotificationBase):
def test_service_create(self):
self.compute2 = self.start_service('compute', host='host2')
self._verify_notification(
'service-create',
replacements={
'uuid':
notification_sample_base.NotificationSampleTestBase.ANY})
def test_service_destroy(self):
self.compute2 = self.start_service('compute', host='host2')
compute2_service_id = self.admin_api.get_services(
host=self.compute2.host, binary='nova-compute')[0]['id']
self.admin_api.api_delete('os-services/%s' % compute2_service_id)
self._verify_notification(
'service-delete',
replacements={'uuid': compute2_service_id},
actual=fake_notifier.VERSIONED_NOTIFICATIONS[1])

View File

@ -397,7 +397,7 @@ notification_object_data = {
'IpPayload': '1.0-8ecf567a99e516d4af094439a7632d34',
'KeypairNotification': '1.0-a73147b93b520ff0061865849d3dfa56',
'KeypairPayload': '1.0-6daebbbde0e1bf35c1556b1ecd9385c1',
'NotificationPublisher': '2.1-9f89fe4abb80f9a7b726e59800c905de',
'NotificationPublisher': '2.2-b6ad48126247e10b46b6b0240e52e614',
'ServerGroupNotification': '1.0-a73147b93b520ff0061865849d3dfa56',
'ServerGroupPayload': '1.0-eb4bd1738b4670cfe1b7c30344c143c3',
'ServiceStatusNotification': '1.0-a73147b93b520ff0061865849d3dfa56',

View File

@ -13,6 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
import copy
import mock
from oslo_utils import timeutils
@ -30,8 +32,15 @@ class TestServiceStatusNotification(test.TestCase):
super(TestServiceStatusNotification, self).setUp()
@mock.patch('nova.notifications.objects.service.ServiceStatusNotification')
def _verify_notification(self, service_obj, mock_notification):
service_obj.save()
def _verify_notification(self, service_obj, action, mock_notification):
if action == fields.NotificationAction.CREATE:
service_obj.create()
elif action == fields.NotificationAction.UPDATE:
service_obj.save()
elif action == fields.NotificationAction.DELETE:
service_obj.destroy()
else:
raise Exception('Unsupported action: %s' % action)
self.assertTrue(mock_notification.called)
@ -44,8 +53,7 @@ class TestServiceStatusNotification(test.TestCase):
self.assertEqual(service_obj.binary, publisher.source)
self.assertEqual(fields.NotificationPriority.INFO, priority)
self.assertEqual('service', event_type.object)
self.assertEqual(fields.NotificationAction.UPDATE,
event_type.action)
self.assertEqual(action, event_type.action)
for field in service_notification.ServiceStatusPayload.SCHEMA:
if field in fake_service:
self.assertEqual(fake_service[field], getattr(payload, field))
@ -60,7 +68,8 @@ class TestServiceStatusNotification(test.TestCase):
'disabled_reason': 'my reason',
'forced_down': True}.items():
setattr(service_obj, key, value)
self._verify_notification(service_obj)
self._verify_notification(service_obj,
fields.NotificationAction.UPDATE)
@mock.patch('nova.notifications.objects.service.ServiceStatusNotification')
@mock.patch('nova.db.service_update')
@ -75,3 +84,19 @@ class TestServiceStatusNotification(test.TestCase):
setattr(service_obj, key, value)
service_obj.save()
self.assertFalse(mock_notification.called)
@mock.patch('nova.db.service_create')
def test_service_create_with_notification(self, mock_db_service_create):
service_obj = objects.Service(context=self.ctxt)
service_obj["uuid"] = fake_service["uuid"]
mock_db_service_create.return_value = fake_service
self._verify_notification(service_obj,
fields.NotificationAction.CREATE)
@mock.patch('nova.db.service_destroy')
def test_service_destroy_with_notification(self, mock_db_service_destroy):
service = copy.deepcopy(fake_service)
service.pop("version")
service_obj = objects.Service(context=self.ctxt, **service)
self._verify_notification(service_obj,
fields.NotificationAction.DELETE)

View File

@ -158,8 +158,9 @@ class _TestServiceObject(object):
mock_service_create(self.context, {'host': 'fake-host',
'version': fake_service['version']})
@mock.patch('nova.objects.Service._send_notification')
@mock.patch.object(db, 'service_update', return_value=fake_service)
def test_save(self, mock_service_update):
def test_save(self, mock_service_update, mock_notify):
service_obj = service.Service(context=self.context)
service_obj.id = 123
service_obj.host = 'fake-host'
@ -178,8 +179,9 @@ class _TestServiceObject(object):
self.assertRaises(ovo_exc.ReadOnlyFieldError, setattr,
service_obj, 'id', 124)
@mock.patch('nova.objects.Service._send_notification')
@mock.patch.object(db, 'service_destroy')
def _test_destroy(self, mock_service_destroy):
def _test_destroy(self, mock_service_destroy, mock_notify):
service_obj = service.Service(context=self.context)
service_obj.id = 123
service_obj.destroy()
@ -385,17 +387,19 @@ class _TestServiceObject(object):
binaries)
self.assertEqual(1, minimum)
@mock.patch('nova.objects.Service._send_notification')
@mock.patch('nova.db.service_get_minimum_version',
return_value={'nova-compute': 2})
def test_create_above_minimum(self, mock_get):
def test_create_above_minimum(self, mock_get, mock_notify):
with mock.patch('nova.objects.service.SERVICE_VERSION',
new=3):
objects.Service(context=self.context,
binary='nova-compute').create()
@mock.patch('nova.objects.Service._send_notification')
@mock.patch('nova.db.service_get_minimum_version',
return_value={'nova-compute': 2})
def test_create_equal_to_minimum(self, mock_get):
def test_create_equal_to_minimum(self, mock_get, mock_notify):
with mock.patch('nova.objects.service.SERVICE_VERSION',
new=2):
objects.Service(context=self.context,
@ -525,8 +529,9 @@ class TestServiceVersionCells(test.TestCase):
service.create()
index += 1
@mock.patch('nova.objects.Service._send_notification')
@mock.patch('nova.objects.Service._check_minimum_version')
def test_version_all_cells(self, mock_check):
def test_version_all_cells(self, mock_check, mock_notify):
self._create_services(16, 16, 13, 16)
self.assertEqual(13, service.get_minimum_version_all_cells(
self.context, ['nova-compute']))

View File

@ -0,0 +1,7 @@
---
features:
- |
Added support for service create and destroy versioned notifications.
The ``service.create`` notification will be emitted after the service is
created (so the uuid is available) and also send the ``service.delete``
notification after the service is deleted.