diff --git a/doc/notification_samples/aggregate-remove_host-end.json b/doc/notification_samples/aggregate-remove_host-end.json new file mode 100644 index 000000000000..9708bf86e8e8 --- /dev/null +++ b/doc/notification_samples/aggregate-remove_host-end.json @@ -0,0 +1,19 @@ +{ + "priority": "INFO", + "payload": { + "nova_object.version": "1.1", + "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.remove_host.end", + "publisher_id": "nova-api:fake-mini" +} diff --git a/doc/notification_samples/aggregate-remove_host-start.json b/doc/notification_samples/aggregate-remove_host-start.json new file mode 100644 index 000000000000..e8852921b437 --- /dev/null +++ b/doc/notification_samples/aggregate-remove_host-start.json @@ -0,0 +1,19 @@ +{ + "priority": "INFO", + "payload": { + "nova_object.version": "1.1", + "nova_object.namespace": "nova", + "nova_object.name": "AggregatePayload", + "nova_object.data": { + "name": "my-aggregate", + "metadata": { + "availability_zone": "nova" + }, + "hosts": ["compute"], + "id": 1, + "uuid": "788608ec-ebdc-45c5-bc7f-e5f24ab92c80" + } + }, + "event_type": "aggregate.remove_host.start", + "publisher_id": "nova-api:fake-mini" +} diff --git a/nova/compute/api.py b/nova/compute/api.py index ed5f64492f27..ee78e83e2fd8 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -4813,6 +4813,13 @@ class AggregateAPI(base.Base): nova_context.set_target_cell(context, mapping.cell_mapping) objects.Service.get_by_compute_host(context, host_name) aggregate = objects.Aggregate.get_by_id(context, aggregate_id) + + compute_utils.notify_about_aggregate_action( + context=context, + aggregate=aggregate, + action=fields_obj.NotificationAction.REMOVE_HOST, + phase=fields_obj.NotificationPhase.START) + aggregate.delete_host(host_name) self.scheduler_client.update_aggregates(context, [aggregate]) self._update_az_cache_for_host(context, host_name, aggregate.metadata) @@ -4821,6 +4828,11 @@ class AggregateAPI(base.Base): compute_utils.notify_about_aggregate_update(context, "removehost.end", aggregate_payload) + compute_utils.notify_about_aggregate_action( + context=context, + aggregate=aggregate, + action=fields_obj.NotificationAction.REMOVE_HOST, + phase=fields_obj.NotificationPhase.END) return aggregate diff --git a/nova/notifications/objects/aggregate.py b/nova/notifications/objects/aggregate.py index a71a63503d54..3efa292ec91c 100644 --- a/nova/notifications/objects/aggregate.py +++ b/nova/notifications/objects/aggregate.py @@ -48,6 +48,8 @@ class AggregatePayload(base.NotificationPayloadBase): @base.notification_sample('aggregate-delete-end.json') @base.notification_sample('aggregate-add_host-start.json') @base.notification_sample('aggregate-add_host-end.json') +@base.notification_sample('aggregate-remove_host-start.json') +@base.notification_sample('aggregate-remove_host-end.json') @nova_base.NovaObjectRegistry.register_notification class AggregateNotification(base.NotificationBase): # Version 1.0: Initial version diff --git a/nova/tests/functional/notification_sample_tests/test_aggregate.py b/nova/tests/functional/notification_sample_tests/test_aggregate.py index 5f045642a882..61a4a20565b9 100644 --- a/nova/tests/functional/notification_sample_tests/test_aggregate.py +++ b/nova/tests/functional/notification_sample_tests/test_aggregate.py @@ -53,7 +53,7 @@ class TestAggregateNotificationSample( 'id': aggregate['id']}, actual=fake_notifier.VERSIONED_NOTIFICATIONS[3]) - def test_aggregate_add_host(self): + def test_aggregate_add_remove_host(self): aggregate_req = { "aggregate": { "name": "my-aggregate", @@ -82,3 +82,26 @@ class TestAggregateNotificationSample( 'uuid': aggregate['uuid'], 'id': aggregate['id']}, actual=fake_notifier.VERSIONED_NOTIFICATIONS[1]) + + remove_host_req = { + "remove_host": { + "host": "compute" + } + } + self.admin_api.post_aggregate_action(aggregate['id'], remove_host_req) + + self.assertEqual(4, len(fake_notifier.VERSIONED_NOTIFICATIONS)) + self._verify_notification( + 'aggregate-remove_host-start', + replacements={ + 'uuid': aggregate['uuid'], + 'id': aggregate['id']}, + actual=fake_notifier.VERSIONED_NOTIFICATIONS[2]) + self._verify_notification( + 'aggregate-remove_host-end', + replacements={ + 'uuid': aggregate['uuid'], + 'id': aggregate['id']}, + actual=fake_notifier.VERSIONED_NOTIFICATIONS[3]) + + self.admin_api.delete_aggregate(aggregate['id']) diff --git a/nova/tests/unit/compute/test_compute.py b/nova/tests/unit/compute/test_compute.py index 8bb343477ec7..ae3df5c917c5 100644 --- a/nova/tests/unit/compute/test_compute.py +++ b/nova/tests/unit/compute/test_compute.py @@ -11407,9 +11407,10 @@ class ComputeAPIAggrTestCase(BaseTestCase): self.api.add_host_to_aggregate, self.context, aggr.id, 'invalid_host') + @mock.patch('nova.compute.utils.notify_about_aggregate_action') @mock.patch.object(availability_zones, 'update_host_availability_zone_cache') - def test_remove_host_from_aggregate_active(self, mock_az): + def test_remove_host_from_aggregate_active(self, mock_az, mock_notify): # Ensure we can remove a host from an aggregate. values = _create_service_entries(self.context) fake_zone = values[0][0] @@ -11428,6 +11429,7 @@ class ComputeAPIAggrTestCase(BaseTestCase): fake_remove_aggregate_host) fake_notifier.NOTIFICATIONS = [] + mock_notify.reset_mock() expected = self.api.remove_host_from_aggregate(self.context, aggr.id, host_to_remove) @@ -11441,6 +11443,12 @@ class ComputeAPIAggrTestCase(BaseTestCase): self.assertEqual(len(aggr.hosts) - 1, len(expected.hosts)) mock_az.assert_called_with(self.context, host_to_remove) + mock_notify.assert_has_calls([ + mock.call(context=self.context, aggregate=expected, + action='remove_host', phase='start'), + mock.call(context=self.context, aggregate=expected, + action='remove_host', phase='end')]) + def test_remove_host_from_aggregate_raise_not_found(self): # Ensure HostMappingNotFound is raised when removing invalid host. _create_service_entries(self.context, [['fake_zone', ['fake_host']]]) @@ -11573,10 +11581,11 @@ class ComputeAPIAggrCallsSchedulerTestCase(test.NoDBTestCase): host_param='fakehost', host='fakehost') + @mock.patch('nova.compute.utils.notify_about_aggregate_action') @mock.patch('nova.compute.rpcapi.ComputeAPI.remove_aggregate_host') @mock.patch.object(scheduler_client.SchedulerClient, 'update_aggregates') def test_remove_host_from_aggregate(self, update_aggregates, - mock_remove_agg): + mock_remove_agg, mock_notify): self.api._update_az_cache_for_host = mock.Mock() agg = objects.Aggregate(name='fake', metadata={}) agg.delete_host = mock.Mock() @@ -11589,6 +11598,11 @@ class ComputeAPIAggrCallsSchedulerTestCase(test.NoDBTestCase): mock_remove_agg.assert_called_once_with(self.context, aggregate=agg, host_param='fakehost', host='fakehost') + mock_notify.assert_has_calls([ + mock.call(context=self.context, aggregate=agg, + action='remove_host', phase='start'), + mock.call(context=self.context, aggregate=agg, + action='remove_host', phase='end')]) class ComputeAggrTestCase(BaseTestCase):