diff --git a/doc/notification_samples/aggregate-create-end.json b/doc/notification_samples/aggregate-create-end.json new file mode 100644 index 0000000000..ded886cab8 --- /dev/null +++ b/doc/notification_samples/aggregate-create-end.json @@ -0,0 +1,19 @@ +{ + "priority": "INFO", + "payload": { + "nova_object.version": "1.0", + "nova_object.namespace": "nova", + "nova_object.name": "AggregatePayload", + "nova_object.data": { + "name": "my-aggregate", + "metadata": { + "availability_zone": "nova" + }, + "hosts": [], + "id": 1, + "uuid": "788608ec-ebdc-45c5-bc7f-e5f24ab92c80" + } + }, + "event_type": "aggregate.create.end", + "publisher_id": "nova-api:fake-mini" +} diff --git a/doc/notification_samples/aggregate-create-start.json b/doc/notification_samples/aggregate-create-start.json new file mode 100644 index 0000000000..9fc18d260f --- /dev/null +++ b/doc/notification_samples/aggregate-create-start.json @@ -0,0 +1,17 @@ +{ + "priority": "INFO", + "payload": { + "nova_object.version": "1.0", + "nova_object.namespace": "nova", + "nova_object.name": "AggregatePayload", + "nova_object.data": { + "name": "my-aggregate", + "metadata": { + "availability_zone": "nova" + }, + "uuid": "788608ec-ebdc-45c5-bc7f-e5f24ab92c80" + } + }, + "event_type": "aggregate.create.start", + "publisher_id": "nova-api:fake-mini" +} diff --git a/nova/compute/utils.py b/nova/compute/utils.py index 76744f0716..595a0a4ec2 100644 --- a/nova/compute/utils.py +++ b/nova/compute/utils.py @@ -32,6 +32,7 @@ from nova import exception from nova.i18n import _LW from nova.network import model as network_model from nova import notifications +from nova.notifications.objects import aggregate as aggregate_notification from nova.notifications.objects import base as notification_base from nova.notifications.objects import exception as notification_exception from nova.notifications.objects import instance as instance_notification @@ -451,6 +452,20 @@ def notify_about_aggregate_update(context, event_suffix, aggregate_payload): notifier.info(context, 'aggregate.%s' % event_suffix, aggregate_payload) +def notify_about_aggregate_action(context, aggregate, action, phase): + payload = aggregate_notification.AggregatePayload(aggregate) + notification = aggregate_notification.AggregateNotification( + priority=fields.NotificationPriority.INFO, + publisher=notification_base.NotificationPublisher( + context=context, host=CONF.host, binary='nova-api'), + event_type=notification_base.EventType( + object='aggregate', + action=action, + phase=phase), + payload=payload) + notification.emit(context) + + def notify_about_host_update(context, event_suffix, host_payload): """Send a notification about host update. diff --git a/nova/notifications/objects/aggregate.py b/nova/notifications/objects/aggregate.py new file mode 100644 index 0000000000..5d1160acc3 --- /dev/null +++ b/nova/notifications/objects/aggregate.py @@ -0,0 +1,51 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from nova.notifications.objects import base +from nova.objects import base as nova_base +from nova.objects import fields + + +@nova_base.NovaObjectRegistry.register_notification +class AggregatePayload(base.NotificationPayloadBase): + SCHEMA = { + 'id': ('aggregate', 'id'), + 'uuid': ('aggregate', 'uuid'), + 'name': ('aggregate', 'name'), + 'hosts': ('aggregate', 'hosts'), + 'metadata': ('aggregate', 'metadata'), + } + # Version 1.0: Initial version + VERSION = '1.0' + fields = { + 'id': fields.IntegerField(), + 'uuid': fields.UUIDField(nullable=False), + 'name': fields.StringField(), + 'hosts': fields.ListOfStringsField(nullable=True), + 'metadata': fields.DictOfStringsField(nullable=True), + } + + def __init__(self, aggregate, **kwargs): + super(AggregatePayload, self).__init__(**kwargs) + self.populate_schema(aggregate=aggregate) + + +@base.notification_sample('aggregate-create-start.json') +@base.notification_sample('aggregate-create-end.json') +@nova_base.NovaObjectRegistry.register_notification +class AggregateNotification(base.NotificationBase): + # Version 1.0: Initial version + VERSION = '1.0' + + fields = { + 'payload': fields.ObjectField('AggregatePayload') + } diff --git a/nova/objects/aggregate.py b/nova/objects/aggregate.py index a16455afce..03909bf6ef 100644 --- a/nova/objects/aggregate.py +++ b/nova/objects/aggregate.py @@ -360,11 +360,18 @@ class Aggregate(base.NovaPersistentObject, base.NovaObject): payload['meta_data'] = payload.pop('metadata') if 'uuid' not in updates: updates['uuid'] = uuidutils.generate_uuid() + self.uuid = updates['uuid'] LOG.debug('Generated uuid %(uuid)s for aggregate', dict(uuid=updates['uuid'])) compute_utils.notify_about_aggregate_update(self._context, "create.start", payload) + compute_utils.notify_about_aggregate_action( + context=self._context, + aggregate=self, + action=fields.NotificationAction.CREATE, + phase=fields.NotificationPhase.START) + metadata = updates.pop('metadata', None) db_aggregate = _aggregate_create_in_db(self._context, updates, metadata=metadata) @@ -373,6 +380,11 @@ class Aggregate(base.NovaPersistentObject, base.NovaObject): compute_utils.notify_about_aggregate_update(self._context, "create.end", payload) + compute_utils.notify_about_aggregate_action( + context=self._context, + aggregate=self, + action=fields.NotificationAction.CREATE, + phase=fields.NotificationPhase.END) @base.remotable def save(self): diff --git a/nova/tests/functional/api/client.py b/nova/tests/functional/api/client.py index b919d7f4fb..436b45643b 100644 --- a/nova/tests/functional/api/client.py +++ b/nova/tests/functional/api/client.py @@ -388,3 +388,6 @@ class TestOpenStackClient(object): def get_instance_actions(self, server_id): return self.api_get('/servers/%s/os-instance-actions' % (server_id)).body['instanceActions'] + + def post_aggregate(self, aggregate): + return self.api_post('/os-aggregates', aggregate).body['aggregate'] diff --git a/nova/tests/functional/notification_sample_tests/test_aggregate.py b/nova/tests/functional/notification_sample_tests/test_aggregate.py new file mode 100644 index 0000000000..6fc4fdbda7 --- /dev/null +++ b/nova/tests/functional/notification_sample_tests/test_aggregate.py @@ -0,0 +1,40 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +from nova.tests.functional.notification_sample_tests \ + import notification_sample_base +from nova.tests.unit import fake_notifier + + +class TestAggregateNotificationSample( + notification_sample_base.NotificationSampleTestBase): + + def test_aggregate_create_delete(self): + aggregate_req = { + "aggregate": { + "name": "my-aggregate", + "availability_zone": "nova"}} + aggregate = self.admin_api.post_aggregate(aggregate_req) + + self.assertEqual(2, len(fake_notifier.VERSIONED_NOTIFICATIONS)) + # The uuid hasn't been exposed on the REST API yet so we have no way to + # match it here, now. + self._verify_notification( + 'aggregate-create-start', + replacements={ + 'uuid': self.ANY}, + actual=fake_notifier.VERSIONED_NOTIFICATIONS[0]) + self._verify_notification( + 'aggregate-create-end', + replacements={ + 'uuid': self.ANY, + 'id': aggregate['id']}, + actual=fake_notifier.VERSIONED_NOTIFICATIONS[1]) diff --git a/nova/tests/functional/notification_sample_tests/test_exception_notification.py b/nova/tests/functional/notification_sample_tests/test_exception_notification.py index 10cf66365b..c13176855a 100644 --- a/nova/tests/functional/notification_sample_tests/test_exception_notification.py +++ b/nova/tests/functional/notification_sample_tests/test_exception_notification.py @@ -33,5 +33,7 @@ class TestExceptionNotificationSample( self.assertRaises(api_client.OpenStackApiException, self.admin_api.api_post, 'os-aggregates', post) - self.assertEqual(1, len(fake_notifier.VERSIONED_NOTIFICATIONS)) - self._verify_notification('compute-exception') + self.assertEqual(4, len(fake_notifier.VERSIONED_NOTIFICATIONS)) + self._verify_notification( + 'compute-exception', + actual=fake_notifier.VERSIONED_NOTIFICATIONS[3]) diff --git a/nova/tests/unit/compute/test_compute.py b/nova/tests/unit/compute/test_compute.py index 0e29c7cce3..9ba39ac49a 100644 --- a/nova/tests/unit/compute/test_compute.py +++ b/nova/tests/unit/compute/test_compute.py @@ -10573,10 +10573,18 @@ class ComputeAPIAggrTestCase(BaseTestCase): self.stub_out('oslo_messaging.rpc.client.call', fake_rpc_method) self.stub_out('oslo_messaging.rpc.client.cast', fake_rpc_method) - def test_aggregate_no_zone(self): + @mock.patch('nova.compute.utils.notify_about_aggregate_action') + def test_aggregate_no_zone(self, mock_notify): # Ensure we can create an aggregate without an availability zone aggr = self.api.create_aggregate(self.context, 'fake_aggregate', None) + + mock_notify.assert_has_calls([ + mock.call(context=self.context, aggregate=aggr, + action='create', phase='start'), + mock.call(context=self.context, aggregate=aggr, + action='create', phase='end')]) + self.api.delete_aggregate(self.context, aggr.id) self.assertRaises(exception.AggregateNotFound, self.api.delete_aggregate, self.context, aggr.id) diff --git a/nova/tests/unit/notifications/objects/test_notification.py b/nova/tests/unit/notifications/objects/test_notification.py index 3721c4b72d..2926f26dc7 100644 --- a/nova/tests/unit/notifications/objects/test_notification.py +++ b/nova/tests/unit/notifications/objects/test_notification.py @@ -257,6 +257,8 @@ class TestNotificationBase(test.NoDBTestCase): notification_object_data = { + 'AggregateNotification': '1.0-a73147b93b520ff0061865849d3dfa56', + 'AggregatePayload': '1.0-2550af604410af7b4ad5d46fb29ba45b', 'AuditPeriodPayload': '1.0-2b429dd307b8374636703b843fa3f9cb', 'BandwidthPayload': '1.0-ee2616a7690ab78406842a2b68e34130', 'EventType': '1.4-da0f0fbcda143ca96c2ac1b93937c22c',