diff --git a/doc/notification_samples/instance-rebuild-end.json b/doc/notification_samples/instance-rebuild-end.json new file mode 100755 index 000000000000..787a301944c8 --- /dev/null +++ b/doc/notification_samples/instance-rebuild-end.json @@ -0,0 +1,74 @@ +{ + "event_type": "instance.rebuild.end", + "publisher_id": "nova-compute:compute", + "payload": { + "nova_object.namespace": "nova", + "nova_object.version": "1.1", + "nova_object.data": { + "node": "fake-mini", + "fault": null, + "host_name": "some-server", + "power_state": "running", + "image_uuid": "a2459075-d96c-40d5-893e-577ff92e721c", + "display_name": "some-server", + "display_description": "some-server", + "tenant_id": "6f70656e737461636b20342065766572", + "kernel_id": "", + "created_at": "2012-10-29T13:42:11Z", + "host": "compute", + "reservation_id": "r-wczvhcla", + "flavor": { + "nova_object.namespace": "nova", + "nova_object.version": "1.3", + "nova_object.data": { + "disabled": false, + "ephemeral_gb": 0, + "extra_specs": {"hw:watchdog_action": "disabled"}, + "flavorid": "a22d5517-147c-4147-a0d1-e698df5cd4e3", + "is_public": true, + "memory_mb": 512, + "name": "test_flavor", + "projects": null, + "root_gb": 1, + "rxtx_factor": 1.0, + "swap": 0, + "vcpu_weight": 0, + "vcpus": 1 + }, + "nova_object.name": "FlavorPayload" + }, + "user_id": "fake", + "terminated_at": null, + "ip_addresses": [ + { + "nova_object.namespace": "nova", + "nova_object.version": "1.0", + "nova_object.data": { + "port_uuid": "ce531f90-199f-48c0-816c-13e38010b442", + "version": 4, + "label": "private-network", + "address": "192.168.1.3", + "meta": {}, + "mac": "fa:16:3e:4c:2c:30", + "device_name": "tapce531f90-19" + }, + "nova_object.name": "IpPayload" + } + ], + "state": "active", + "launched_at": "2012-10-29T13:42:11Z", + "metadata": {}, + "deleted_at": null, + "os_type": null, + "uuid": "b271fcb9-75c3-4c76-84eb-6ccad1150ece", + "locked": false, + "availability_zone": null, + "ramdisk_id": "", + "architecture": null, + "progress": 0, + "task_state": null + }, + "nova_object.name": "InstanceActionPayload" + }, + "priority": "INFO" +} diff --git a/doc/notification_samples/instance-rebuild-start.json b/doc/notification_samples/instance-rebuild-start.json new file mode 100755 index 000000000000..4120a39c015b --- /dev/null +++ b/doc/notification_samples/instance-rebuild-start.json @@ -0,0 +1,74 @@ +{ + "priority": "INFO", + "event_type": "instance.rebuild.start", + "publisher_id": "nova-compute:compute", + "payload": { + "nova_object.name": "InstanceActionPayload", + "nova_object.namespace": "nova", + "nova_object.version": "1.1", + "nova_object.data": { + "display_description": "some-server", + "reservation_id": "r-rqe0mlje", + "progress": 0, + "user_id": "fake", + "task_state": "rebuilding", + "node": "fake-mini", + "created_at": "2012-10-29T13:42:11Z", + "kernel_id": "", + "metadata": {}, + "deleted_at": null, + "host_name": "some-server", + "uuid": "ff0ee97f-3f14-4259-a79c-83949f8493bd", + "os_type": null, + "display_name": "some-server", + "ramdisk_id": "", + "power_state": "running", + "locked": false, + "flavor": { + "nova_object.name": "FlavorPayload", + "nova_object.namespace": "nova", + "nova_object.version": "1.3", + "nova_object.data": { + "disabled": false, + "ephemeral_gb": 0, + "extra_specs": {"hw:watchdog_action": "disabled"}, + "flavorid": "a22d5517-147c-4147-a0d1-e698df5cd4e3", + "is_public": true, + "memory_mb": 512, + "name": "test_flavor", + "projects": null, + "root_gb": 1, + "rxtx_factor": 1.0, + "swap": 0, + "vcpu_weight": 0, + "vcpus": 1 + } + }, + "terminated_at": null, + "architecture": null, + "launched_at": "2012-10-29T13:42:11Z", + "ip_addresses": [ + { + "nova_object.name": "IpPayload", + "nova_object.namespace": "nova", + "nova_object.version": "1.0", + "nova_object.data": { + "meta": {}, + "mac": "fa:16:3e:4c:2c:30", + "label": "private-network", + "port_uuid": "ce531f90-199f-48c0-816c-13e38010b442", + "version": 4, + "device_name": "tapce531f90-19", + "address": "192.168.1.3" + } + } + ], + "tenant_id": "6f70656e737461636b20342065766572", + "availability_zone": null, + "host": "compute", + "image_uuid": "a2459075-d96c-40d5-893e-577ff92e721c", + "state": "active", + "fault": null + } + } +} \ No newline at end of file diff --git a/nova/compute/manager.py b/nova/compute/manager.py index b8a18faea465..615988187228 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -2852,6 +2852,13 @@ class ComputeManager(manager.Manager): extra_usage_info = {'image_name': self._get_image_name(image_meta)} self._notify_about_instance_usage(context, instance, "rebuild.start", extra_usage_info=extra_usage_info) + # NOTE: image_name is not included in the versioned notification + # because we already provide the image_uuid in the notification + # payload and the image details can be looked up via the uuid. + compute_utils.notify_about_instance_action( + context, instance, self.host, + action=fields.NotificationAction.REBUILD, + phase=fields.NotificationPhase.START) instance.power_state = self._get_power_state(context, instance) instance.task_state = task_states.REBUILDING @@ -2920,6 +2927,10 @@ class ComputeManager(manager.Manager): context, instance, "rebuild.end", network_info=network_info, extra_usage_info=extra_usage_info) + compute_utils.notify_about_instance_action( + context, instance, self.host, + action=fields.NotificationAction.REBUILD, + phase=fields.NotificationPhase.END) def _handle_bad_volumes_detached(self, context, instance, bad_devices, block_device_info): diff --git a/nova/notifications/objects/instance.py b/nova/notifications/objects/instance.py index d81afa93a2df..20b42731f755 100644 --- a/nova/notifications/objects/instance.py +++ b/nova/notifications/objects/instance.py @@ -268,6 +268,8 @@ class InstanceStateUpdatePayload(base.NotificationPayloadBase): # @base.notification_sample('instance-live_migration_rollback-end.json') # @base.notification_sample('instance-live_migration_rollback_dest-start.json') # @base.notification_sample('instance-live_migration_rollback_dest-end.json') +@base.notification_sample('instance-rebuild-start.json') +@base.notification_sample('instance-rebuild-end.json') # @base.notification_sample('instance-rebuild-error.json') # @base.notification_sample('instance-remove_fixed_ip-start.json') # @base.notification_sample('instance-remove_fixed_ip-end.json') diff --git a/nova/tests/functional/notification_sample_tests/test_instance.py b/nova/tests/functional/notification_sample_tests/test_instance.py index c8bf32b1b7d3..a7115ef05fe3 100644 --- a/nova/tests/functional/notification_sample_tests/test_instance.py +++ b/nova/tests/functional/notification_sample_tests/test_instance.py @@ -75,6 +75,7 @@ class TestInstanceNotificationSample( self._test_unshelve_server, self._test_resize_server, self._test_snapshot_server, + self._test_rebuild_server, ] for action in actions: @@ -544,6 +545,35 @@ class TestInstanceNotificationSample( 'uuid': server['id']}, actual=fake_notifier.VERSIONED_NOTIFICATIONS[1]) + def _test_rebuild_server(self, server): + post = { + 'rebuild': { + 'imageRef': 'a2459075-d96c-40d5-893e-577ff92e721c', + 'metadata': {} + } + } + self.api.post_server_action(server['id'], post) + # Before going back to ACTIVE state + # server state need to be changed to REBUILD state + self._wait_for_state_change(self.api, server, + expected_status='REBUILD') + self._wait_for_state_change(self.api, server, + expected_status='ACTIVE') + + self.assertEqual(2, len(fake_notifier.VERSIONED_NOTIFICATIONS)) + self._verify_notification( + 'instance-rebuild-start', + replacements={ + 'reservation_id': server['reservation_id'], + 'uuid': server['id']}, + actual=fake_notifier.VERSIONED_NOTIFICATIONS[0]) + self._verify_notification( + 'instance-rebuild-end', + replacements={ + 'reservation_id': server['reservation_id'], + 'uuid': server['id']}, + actual=fake_notifier.VERSIONED_NOTIFICATIONS[1]) + def _test_restore_server(self, server): self.flags(reclaim_instance_interval=30) self.api.delete_server(server['id']) diff --git a/nova/tests/unit/compute/test_compute.py b/nova/tests/unit/compute/test_compute.py index d83e736492f0..cf58fa3f427a 100644 --- a/nova/tests/unit/compute/test_compute.py +++ b/nova/tests/unit/compute/test_compute.py @@ -11697,7 +11697,7 @@ class EvacuateHostTestCase(BaseTestCase): super(EvacuateHostTestCase, self).tearDown() def _rebuild(self, on_shared_storage=True, migration=None, - send_node=False): + send_node=False, vm_states_is_stopped=False): network_api = self.compute.network_api ctxt = context.get_admin_context() @@ -11706,11 +11706,13 @@ class EvacuateHostTestCase(BaseTestCase): node = NODENAME limits = {} + @mock.patch('nova.compute.utils.notify_about_instance_action') @mock.patch.object(network_api, 'setup_networks_on_host') @mock.patch.object(network_api, 'setup_instance_network_on_host') @mock.patch('nova.context.RequestContext.elevated', return_value=ctxt) def _test_rebuild(mock_context, mock_setup_instance_network_on_host, - mock_setup_networks_on_host): + mock_setup_networks_on_host, mock_notify, + vm_is_stopped=False): orig_image_ref = None image_ref = None injected_files = None @@ -11721,12 +11723,29 @@ class EvacuateHostTestCase(BaseTestCase): image_ref, injected_files, 'newpass', {}, bdms, recreate=True, on_shared_storage=on_shared_storage, migration=migration, scheduled_node=node, limits=limits) + if vm_states_is_stopped: + mock_notify.assert_has_calls([ + mock.call(ctxt, self.inst, self.inst.host, + action='rebuild', phase='start'), + mock.call(ctxt, self.inst, self.inst.host, + action='power_off', phase='start'), + mock.call(ctxt, self.inst, self.inst.host, + action='power_off', phase='end'), + mock.call(ctxt, self.inst, self.inst.host, + action='rebuild', phase='end')]) + else: + mock_notify.assert_has_calls([ + mock.call(ctxt, self.inst, self.inst.host, + action='rebuild', phase='start'), + mock.call(ctxt, self.inst, self.inst.host, + action='rebuild', phase='end')]) + mock_setup_networks_on_host.assert_called_once_with( ctxt, self.inst, self.inst.host) mock_setup_instance_network_on_host.assert_called_once_with( ctxt, self.inst, self.inst.host) - _test_rebuild() + _test_rebuild(vm_is_stopped=vm_states_is_stopped) def test_rebuild_on_host_updated_target(self): """Confirm evacuate scenario updates host and node.""" @@ -11795,7 +11814,7 @@ class EvacuateHostTestCase(BaseTestCase): self.stub_out('nova.virt.fake.FakeDriver.instance_on_disk', lambda *a, **ka: True) - self._rebuild() + self._rebuild(vm_states_is_stopped=True) # Check the vm state is reset to stopped instance = db.instance_get(self.context, self.inst.id)