Merge "Versioned notifications for service create and delete"

This commit is contained in:
Zuul 2017-11-27 19:41:04 +00:00 committed by Gerrit Code Review
commit b9d9de8962
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.