Merge "Versioned notifications for service create and delete"
This commit is contained in:
commit
b9d9de8962
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…
Reference in New Issue
Block a user