Skip to content

Commit

Permalink
Merge "Versioned notifications for service create and delete"
Browse files Browse the repository at this point in the history
  • Loading branch information
Zuul authored and openstack-gerrit committed Nov 27, 2017
2 parents c6b352d + 8e793a6 commit b9d9de8
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 b9d9de8

Please sign in to comment.