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:
parent
bb95f6a218
commit
8e793a6c6f
23
doc/notification_samples/service-create.json
Normal file
23
doc/notification_samples/service-create.json
Normal 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"
|
||||
}
|
23
doc/notification_samples/service-delete.json
Normal file
23
doc/notification_samples/service-delete.json
Normal 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"
|
||||
}
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
@ -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__))
|
||||
|
@ -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])
|
@ -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',
|
||||
|
@ -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)
|
||||
|
@ -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']))
|
||||
|
@ -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.
|
Loading…
x
Reference in New Issue
Block a user