Skip to content

Commit 8e793a6

Browse files
liyingjunmriedem
authored andcommitted
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
1 parent bb95f6a commit 8e793a6

File tree

12 files changed

+159
-30
lines changed

12 files changed

+159
-30
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"priority": "INFO",
3+
"payload": {
4+
"nova_object.namespace": "nova",
5+
"nova_object.name": "ServiceStatusPayload",
6+
"nova_object.version": "1.1",
7+
"nova_object.data": {
8+
"host": "host2",
9+
"disabled": false,
10+
"last_seen_up": null,
11+
"binary": "nova-compute",
12+
"topic": "compute",
13+
"disabled_reason": null,
14+
"report_count": 0,
15+
"forced_down": false,
16+
"version": 23,
17+
"availability_zone": null,
18+
"uuid": "fa69c544-906b-4a6a-a9c6-c1f7a8078c73"
19+
}
20+
},
21+
"event_type": "service.create",
22+
"publisher_id": "nova-compute:host2"
23+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"priority": "INFO",
3+
"payload": {
4+
"nova_object.namespace": "nova",
5+
"nova_object.name": "ServiceStatusPayload",
6+
"nova_object.version": "1.1",
7+
"nova_object.data": {
8+
"host": "host2",
9+
"disabled": false,
10+
"last_seen_up": null,
11+
"binary": "nova-compute",
12+
"topic": "compute",
13+
"disabled_reason": null,
14+
"report_count": 0,
15+
"forced_down": false,
16+
"version": 23,
17+
"availability_zone": null,
18+
"uuid": "32887c0a-5063-4d39-826f-4903c241c376"
19+
}
20+
},
21+
"event_type": "service.delete",
22+
"publisher_id": "nova-compute:host2"
23+
}

nova/notifications/objects/base.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,8 @@ class NotificationPublisher(NotificationObject):
147147
# 2.1: The type of the source field changed from string to enum.
148148
# This only needs a minor bump as the enum uses the possible
149149
# values of the previous string field
150-
VERSION = '2.1'
150+
# 2.2: New enum for source fields added
151+
VERSION = '2.2'
151152

152153
fields = {
153154
'host': fields.StringField(nullable=False),
@@ -161,7 +162,12 @@ def __init__(self, host, source):
161162

162163
@classmethod
163164
def from_service_obj(cls, service):
164-
return cls(host=service.host, source=service.binary)
165+
# nova-osapi_compute binary name needs to be translated to nova-api
166+
# notification source enum value.
167+
source = ("nova-api"
168+
if service.binary == "nova-osapi_compute"
169+
else service.binary)
170+
return cls(host=service.host, source=source)
165171

166172

167173
@base.NovaObjectRegistry.register_if(False)

nova/notifications/objects/service.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818
from nova.objects import fields
1919

2020

21+
@base.notification_sample('service-create.json')
2122
@base.notification_sample('service-update.json')
23+
@base.notification_sample('service-delete.json')
2224
@nova_base.NovaObjectRegistry.register_notification
2325
class ServiceStatusNotification(base.NotificationBase):
2426
# Version 1.0: Initial version

nova/objects/fields.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -800,8 +800,14 @@ class NotificationSource(BaseNovaEnum):
800800
API = 'nova-api'
801801
CONDUCTOR = 'nova-conductor'
802802
SCHEDULER = 'nova-scheduler'
803-
804-
ALL = (API, COMPUTE, CONDUCTOR, SCHEDULER)
803+
NETWORK = 'nova-network'
804+
CONSOLEAUTH = 'nova-consoleauth'
805+
CELLS = 'nova-cells'
806+
CONSOLE = 'nova-console'
807+
METADATA = 'nova-metadata'
808+
809+
ALL = (API, COMPUTE, CONDUCTOR, SCHEDULER,
810+
NETWORK, CONSOLEAUTH, CELLS, CONSOLE, METADATA)
805811

806812

807813
class NotificationAction(BaseNovaEnum):

nova/objects/service.py

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,7 @@ def create(self):
356356

357357
db_service = db.service_create(self._context, updates)
358358
self._from_db_object(self._context, self, db_service)
359+
self._send_notification(fields.NotificationAction.CREATE)
359360

360361
@base.remotable
361362
def save(self):
@@ -373,19 +374,23 @@ def _send_status_update_notification(self, updates):
373374
# every other field change. See the comment in save() too.
374375
if set(updates.keys()).intersection(
375376
{'disabled', 'disabled_reason', 'forced_down'}):
376-
payload = service_notification.ServiceStatusPayload(self)
377-
service_notification.ServiceStatusNotification(
378-
publisher=notification.NotificationPublisher.from_service_obj(
379-
self),
380-
event_type=notification.EventType(
381-
object='service',
382-
action=fields.NotificationAction.UPDATE),
383-
priority=fields.NotificationPriority.INFO,
384-
payload=payload).emit(self._context)
377+
self._send_notification(fields.NotificationAction.UPDATE)
378+
379+
def _send_notification(self, action):
380+
payload = service_notification.ServiceStatusPayload(self)
381+
service_notification.ServiceStatusNotification(
382+
publisher=notification.NotificationPublisher.from_service_obj(
383+
self),
384+
event_type=notification.EventType(
385+
object='service',
386+
action=action),
387+
priority=fields.NotificationPriority.INFO,
388+
payload=payload).emit(self._context)
385389

386390
@base.remotable
387391
def destroy(self):
388392
db.service_destroy(self._context, self.id)
393+
self._send_notification(fields.NotificationAction.DELETE)
389394

390395
@classmethod
391396
def enable_min_version_cache(cls):

nova/tests/functional/notification_sample_tests/notification_sample_base.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ def setUp(self):
8787
self.start_service('scheduler')
8888
self.start_service('network', manager=CONF.network_manager)
8989
self.compute = self.start_service('compute')
90+
# Reset the service create notifications
91+
fake_notifier.reset()
9092

9193
def _get_notification_sample(self, sample):
9294
sample_dir = os.path.dirname(os.path.abspath(__file__))

nova/tests/functional/notification_sample_tests/test_service_update.py renamed to nova/tests/functional/notification_sample_tests/test_service.py

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,25 +20,29 @@
2020
from nova.tests.functional.notification_sample_tests \
2121
import notification_sample_base
2222
from nova.tests.unit.api.openstack.compute import test_services
23+
from nova.tests.unit import fake_notifier
2324

2425

25-
class TestServiceUpdateNotificationSamplev2_52(
26+
class TestServiceNotificationBase(
2627
notification_sample_base.NotificationSampleTestBase):
2728

28-
# These tests have to be capped at 2.52 since the PUT format changes in
29-
# the 2.53 microversion.
30-
MAX_MICROVERSION = '2.52'
31-
3229
def _verify_notification(self, sample_file_name, replacements=None,
3330
actual=None):
3431
# This just extends the generic _verify_notification to default the
3532
# service version to the current service version to avoid sample update
3633
# after every service version bump.
3734
if 'version' not in replacements:
3835
replacements['version'] = service.SERVICE_VERSION
39-
base = super(TestServiceUpdateNotificationSamplev2_52, self)
36+
base = super(TestServiceNotificationBase, self)
4037
base._verify_notification(sample_file_name, replacements, actual)
4138

39+
40+
class TestServiceUpdateNotificationSamplev2_52(TestServiceNotificationBase):
41+
42+
# These tests have to be capped at 2.52 since the PUT format changes in
43+
# the 2.53 microversion.
44+
MAX_MICROVERSION = '2.52'
45+
4246
def setUp(self):
4347
super(TestServiceUpdateNotificationSamplev2_52, self).setUp()
4448
self.stub_out("nova.db.service_get_by_host_and_binary",
@@ -133,3 +137,24 @@ def test_service_force_down(self):
133137
'disabled': True,
134138
'disabled_reason': 'test2',
135139
'uuid': self.service_uuid})
140+
141+
142+
class TestServiceNotificationSample(TestServiceNotificationBase):
143+
144+
def test_service_create(self):
145+
self.compute2 = self.start_service('compute', host='host2')
146+
self._verify_notification(
147+
'service-create',
148+
replacements={
149+
'uuid':
150+
notification_sample_base.NotificationSampleTestBase.ANY})
151+
152+
def test_service_destroy(self):
153+
self.compute2 = self.start_service('compute', host='host2')
154+
compute2_service_id = self.admin_api.get_services(
155+
host=self.compute2.host, binary='nova-compute')[0]['id']
156+
self.admin_api.api_delete('os-services/%s' % compute2_service_id)
157+
self._verify_notification(
158+
'service-delete',
159+
replacements={'uuid': compute2_service_id},
160+
actual=fake_notifier.VERSIONED_NOTIFICATIONS[1])

nova/tests/unit/notifications/objects/test_notification.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -397,7 +397,7 @@ def test_payload_is_not_generated_if_notification_format_is_unversioned(
397397
'IpPayload': '1.0-8ecf567a99e516d4af094439a7632d34',
398398
'KeypairNotification': '1.0-a73147b93b520ff0061865849d3dfa56',
399399
'KeypairPayload': '1.0-6daebbbde0e1bf35c1556b1ecd9385c1',
400-
'NotificationPublisher': '2.1-9f89fe4abb80f9a7b726e59800c905de',
400+
'NotificationPublisher': '2.2-b6ad48126247e10b46b6b0240e52e614',
401401
'ServerGroupNotification': '1.0-a73147b93b520ff0061865849d3dfa56',
402402
'ServerGroupPayload': '1.0-eb4bd1738b4670cfe1b7c30344c143c3',
403403
'ServiceStatusNotification': '1.0-a73147b93b520ff0061865849d3dfa56',

nova/tests/unit/notifications/objects/test_service.py

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
# License for the specific language governing permissions and limitations
1414
# under the License.
1515

16+
import copy
17+
1618
import mock
1719
from oslo_utils import timeutils
1820

@@ -30,8 +32,15 @@ def setUp(self):
3032
super(TestServiceStatusNotification, self).setUp()
3133

3234
@mock.patch('nova.notifications.objects.service.ServiceStatusNotification')
33-
def _verify_notification(self, service_obj, mock_notification):
34-
service_obj.save()
35+
def _verify_notification(self, service_obj, action, mock_notification):
36+
if action == fields.NotificationAction.CREATE:
37+
service_obj.create()
38+
elif action == fields.NotificationAction.UPDATE:
39+
service_obj.save()
40+
elif action == fields.NotificationAction.DELETE:
41+
service_obj.destroy()
42+
else:
43+
raise Exception('Unsupported action: %s' % action)
3544

3645
self.assertTrue(mock_notification.called)
3746

@@ -44,8 +53,7 @@ def _verify_notification(self, service_obj, mock_notification):
4453
self.assertEqual(service_obj.binary, publisher.source)
4554
self.assertEqual(fields.NotificationPriority.INFO, priority)
4655
self.assertEqual('service', event_type.object)
47-
self.assertEqual(fields.NotificationAction.UPDATE,
48-
event_type.action)
56+
self.assertEqual(action, event_type.action)
4957
for field in service_notification.ServiceStatusPayload.SCHEMA:
5058
if field in fake_service:
5159
self.assertEqual(fake_service[field], getattr(payload, field))
@@ -60,7 +68,8 @@ def test_service_update_with_notification(self, mock_db_service_update):
6068
'disabled_reason': 'my reason',
6169
'forced_down': True}.items():
6270
setattr(service_obj, key, value)
63-
self._verify_notification(service_obj)
71+
self._verify_notification(service_obj,
72+
fields.NotificationAction.UPDATE)
6473

6574
@mock.patch('nova.notifications.objects.service.ServiceStatusNotification')
6675
@mock.patch('nova.db.service_update')
@@ -75,3 +84,19 @@ def test_service_update_without_notification(self,
7584
setattr(service_obj, key, value)
7685
service_obj.save()
7786
self.assertFalse(mock_notification.called)
87+
88+
@mock.patch('nova.db.service_create')
89+
def test_service_create_with_notification(self, mock_db_service_create):
90+
service_obj = objects.Service(context=self.ctxt)
91+
service_obj["uuid"] = fake_service["uuid"]
92+
mock_db_service_create.return_value = fake_service
93+
self._verify_notification(service_obj,
94+
fields.NotificationAction.CREATE)
95+
96+
@mock.patch('nova.db.service_destroy')
97+
def test_service_destroy_with_notification(self, mock_db_service_destroy):
98+
service = copy.deepcopy(fake_service)
99+
service.pop("version")
100+
service_obj = objects.Service(context=self.ctxt, **service)
101+
self._verify_notification(service_obj,
102+
fields.NotificationAction.DELETE)

0 commit comments

Comments
 (0)