Skip to content

Commit

Permalink
Versioned notifications for service create and delete
Browse files Browse the repository at this point in the history
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
  • Loading branch information
liyingjun authored and mriedem committed Nov 27, 2017
1 parent bb95f6a commit 8e793a6
Show file tree
Hide file tree
Showing 12 changed files with 159 additions and 30 deletions.
23 changes: 23 additions & 0 deletions doc/notification_samples/service-create.json
Original file line number Diff line number Diff line change
@@ -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 changes: 23 additions & 0 deletions doc/notification_samples/service-delete.json
Original file line number Diff line number Diff line change
@@ -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"
}
10 changes: 8 additions & 2 deletions nova/notifications/objects/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -161,7 +162,12 @@ def __init__(self, host, source):

@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)
Expand Down
2 changes: 2 additions & 0 deletions nova/notifications/objects/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
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
Expand Down
10 changes: 8 additions & 2 deletions nova/objects/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -800,8 +800,14 @@ class NotificationSource(BaseNovaEnum):
API = 'nova-api'
CONDUCTOR = 'nova-conductor'
SCHEDULER = 'nova-scheduler'

ALL = (API, COMPUTE, CONDUCTOR, SCHEDULER)
NETWORK = 'nova-network'
CONSOLEAUTH = 'nova-consoleauth'
CELLS = 'nova-cells'
CONSOLE = 'nova-console'
METADATA = 'nova-metadata'

ALL = (API, COMPUTE, CONDUCTOR, SCHEDULER,
NETWORK, CONSOLEAUTH, CELLS, CONSOLE, METADATA)


class NotificationAction(BaseNovaEnum):
Expand Down
23 changes: 14 additions & 9 deletions nova/objects/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,7 @@ def create(self):

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):
Expand All @@ -373,19 +374,23 @@ def _send_status_update_notification(self, updates):
# 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):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ def setUp(self):
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__))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,29 @@
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
# service version to the current service version to avoid sample update
# 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",
Expand Down Expand Up @@ -133,3 +137,24 @@ def test_service_force_down(self):
'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])
2 changes: 1 addition & 1 deletion nova/tests/unit/notifications/objects/test_notification.py
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,7 @@ def test_payload_is_not_generated_if_notification_format_is_unversioned(
'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',
Expand Down
35 changes: 30 additions & 5 deletions nova/tests/unit/notifications/objects/test_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -30,8 +32,15 @@ def setUp(self):
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)

Expand All @@ -44,8 +53,7 @@ def _verify_notification(self, service_obj, mock_notification):
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))
Expand All @@ -60,7 +68,8 @@ def test_service_update_with_notification(self, mock_db_service_update):
'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')
Expand All @@ -75,3 +84,19 @@ def test_service_update_without_notification(self,
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)
15 changes: 10 additions & 5 deletions nova/tests/unit/objects/test_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,9 @@ def test_recreate_fails(self, mock_service_create):
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'
Expand All @@ -178,8 +179,9 @@ def test_set_id_failure(self, db_mock):
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()
Expand Down Expand Up @@ -385,17 +387,19 @@ def test_get_min_version_multiple(self, mock_gmv):
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,
Expand Down Expand Up @@ -525,8 +529,9 @@ def _create_services(self, *versions):
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']))
Original file line number Diff line number Diff line change
@@ -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.

0 comments on commit 8e793a6

Please sign in to comment.