From fe2b0d37f74a322fe5d6611129f9dd5128488260 Mon Sep 17 00:00:00 2001 From: Monsyne Dragon Date: Wed, 11 Sep 2013 19:48:59 +0000 Subject: [PATCH] Add missing notifications for rescue/unrescue The rescue and unrescue operations are missing start/end notifications for the operation. Most Nova instance operations emit compute.instance..start/end notifications, which external systems can use to track changes in the state on an instance (e.g. for billing purposes). Rescues change launched_at for the instance, which can prevent an external system from having a consistant picture of the instances' running, unless they take that into account. Change-Id: I3d7d334c88953ddf693c864c3e5e0746cdd2056c Closes-bug: 1224088 --- nova/compute/manager.py | 33 ++++++++++--- nova/tests/compute/test_compute.py | 79 ++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+), 8 deletions(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index c44ef6f2b9db..8c863e968596 100755 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -2519,10 +2519,19 @@ class ComputeManager(manager.SchedulerDependentManager): admin_password = (rescue_password if rescue_password else utils.generate_password()) + self.conductor_api.notify_usage_exists(context, instance, + current_period=True) + network_info = self._get_instance_nw_info(context, instance) rescue_image_meta = self._get_rescue_image(context, instance) + extra_usage_info = {'rescue_image_name': + rescue_image_meta.get('name', '')} + self._notify_about_instance_usage(context, instance, + "rescue.start", extra_usage_info=extra_usage_info, + network_info=network_info) + try: self.driver.rescue(context, instance, network_info, @@ -2535,13 +2544,16 @@ class ComputeManager(manager.SchedulerDependentManager): reason=_("Driver Error: %s") % unicode(e)) current_power_state = self._get_power_state(context, instance) - self._instance_update(context, + instance = self._instance_update(context, instance['uuid'], vm_state=vm_states.RESCUED, task_state=None, power_state=current_power_state, launched_at=timeutils.utcnow(), expected_task_state=task_states.RESCUING) + self._notify_about_instance_usage(context, instance, + "rescue.end", extra_usage_info=extra_usage_info, + network_info=network_info) @wrap_exception() @reverts_task_state @@ -2553,18 +2565,23 @@ class ComputeManager(manager.SchedulerDependentManager): LOG.audit(_('Unrescuing'), context=context, instance=instance) network_info = self._get_instance_nw_info(context, instance) - + self._notify_about_instance_usage(context, instance, + "unrescue.start", network_info=network_info) with self._error_out_instance_on_exception(context, instance['uuid']): self.driver.unrescue(instance, network_info) current_power_state = self._get_power_state(context, instance) - self._instance_update(context, - instance['uuid'], - vm_state=vm_states.ACTIVE, - task_state=None, - expected_task_state=task_states.UNRESCUING, - power_state=current_power_state) + instance = self._instance_update(context, + instance['uuid'], + vm_state=vm_states.ACTIVE, + task_state=None, + expected_task_state=task_states.UNRESCUING, + power_state=current_power_state) + self._notify_about_instance_usage(context, + instance, + "unrescue.end", + network_info=network_info) @wrap_exception() @reverts_task_state diff --git a/nova/tests/compute/test_compute.py b/nova/tests/compute/test_compute.py index dab8e52cb020..c79dfb45e602 100644 --- a/nova/tests/compute/test_compute.py +++ b/nova/tests/compute/test_compute.py @@ -1645,6 +1645,85 @@ class ComputeTestCase(BaseTestCase): self.compute.terminate_instance(self.context, instance=instance) + def test_rescue_notifications(self): + # Ensure notifications on instance rescue. + def fake_rescue(self, context, instance_ref, network_info, image_meta, + rescue_password): + pass + self.stubs.Set(nova.virt.fake.FakeDriver, 'rescue', fake_rescue) + + instance = jsonutils.to_primitive(self._create_fake_instance()) + instance_uuid = instance['uuid'] + self.compute.run_instance(self.context, instance=instance) + + fake_notifier.NOTIFICATIONS = [] + db.instance_update(self.context, instance_uuid, + {"task_state": task_states.RESCUING}) + self.compute.rescue_instance(self.context, instance=instance) + + expected_notifications = ['compute.instance.exists', + 'compute.instance.rescue.start', + 'compute.instance.rescue.end'] + self.assertEquals([m.event_type for m in fake_notifier.NOTIFICATIONS], + expected_notifications) + for n, msg in enumerate(fake_notifier.NOTIFICATIONS): + self.assertEquals(msg.event_type, expected_notifications[n]) + self.assertEquals(msg.priority, 'INFO') + payload = msg.payload + self.assertEquals(payload['tenant_id'], self.project_id) + self.assertEquals(payload['user_id'], self.user_id) + self.assertEquals(payload['instance_id'], instance_uuid) + self.assertEquals(payload['instance_type'], 'm1.tiny') + type_id = flavors.get_flavor_by_name('m1.tiny')['id'] + self.assertEquals(str(payload['instance_type_id']), str(type_id)) + self.assertTrue('display_name' in payload) + self.assertTrue('created_at' in payload) + self.assertTrue('launched_at' in payload) + image_ref_url = glance.generate_image_url(FAKE_IMAGE_REF) + self.assertEquals(payload['image_ref_url'], image_ref_url) + msg = fake_notifier.NOTIFICATIONS[1] + self.assertTrue('rescue_image_name' in msg.payload) + + self.compute.terminate_instance(self.context, instance=instance) + + def test_unrescue_notifications(self): + # Ensure notifications on instance rescue. + def fake_unrescue(self, instance_ref, network_info): + pass + self.stubs.Set(nova.virt.fake.FakeDriver, 'unrescue', + fake_unrescue) + + instance = jsonutils.to_primitive(self._create_fake_instance()) + instance_uuid = instance['uuid'] + self.compute.run_instance(self.context, instance=instance) + + fake_notifier.NOTIFICATIONS = [] + db.instance_update(self.context, instance_uuid, + {"task_state": task_states.UNRESCUING}) + self.compute.unrescue_instance(self.context, instance=instance) + + expected_notifications = ['compute.instance.unrescue.start', + 'compute.instance.unrescue.end'] + self.assertEquals([m.event_type for m in fake_notifier.NOTIFICATIONS], + expected_notifications) + for n, msg in enumerate(fake_notifier.NOTIFICATIONS): + self.assertEquals(msg.event_type, expected_notifications[n]) + self.assertEquals(msg.priority, 'INFO') + payload = msg.payload + self.assertEquals(payload['tenant_id'], self.project_id) + self.assertEquals(payload['user_id'], self.user_id) + self.assertEquals(payload['instance_id'], instance_uuid) + self.assertEquals(payload['instance_type'], 'm1.tiny') + type_id = flavors.get_flavor_by_name('m1.tiny')['id'] + self.assertEquals(str(payload['instance_type_id']), str(type_id)) + self.assertTrue('display_name' in payload) + self.assertTrue('created_at' in payload) + self.assertTrue('launched_at' in payload) + image_ref_url = glance.generate_image_url(FAKE_IMAGE_REF) + self.assertEquals(payload['image_ref_url'], image_ref_url) + + self.compute.terminate_instance(self.context, instance=instance) + def test_rescue_handle_err(self): # If the driver fails to rescue, instance state should remain the same # and the exception should be converted to InstanceNotRescuable