diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 7b403e9cc257..053357dd182b 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -5287,12 +5287,16 @@ class ComputeManager(manager.Manager): self._rollback_live_migration, block_migration, migrate_data) except Exception: - # Executing live migration - # live_migration might raises exceptions, but - # nothing must be recovered in this version. LOG.exception(_LE('Live migration failed.'), instance=instance) with excutils.save_and_reraise_exception(): + # Put instance and migration into error state, + # as its almost certainly too late to rollback self._set_migration_status(migration, 'error') + # first refresh instance as it may have got updated by + # post_live_migration_at_destination + instance.refresh() + self._set_instance_obj_error_state(context, instance, + clean_task_state=True) @wrap_exception() @wrap_instance_event(prefix='compute') diff --git a/nova/tests/unit/compute/test_compute.py b/nova/tests/unit/compute/test_compute.py index 02d0598a9bda..847d5b8cbaa7 100644 --- a/nova/tests/unit/compute/test_compute.py +++ b/nova/tests/unit/compute/test_compute.py @@ -5801,6 +5801,51 @@ class ComputeTestCase(BaseTestCase): mock_post.assert_called_once_with(c, instance, False, dest) mock_clear.assert_called_once_with(mock.ANY) + @mock.patch.object(compute_rpcapi.ComputeAPI, 'pre_live_migration') + @mock.patch.object(network_api.API, 'migrate_instance_start') + @mock.patch.object(compute_rpcapi.ComputeAPI, + 'post_live_migration_at_destination') + @mock.patch.object(compute_manager.InstanceEvents, + 'clear_events_for_instance') + @mock.patch.object(compute_utils, 'EventReporter') + @mock.patch('nova.objects.Migration.save') + def test_live_migration_handles_errors_correctly(self, + mock_save, mock_event, mock_clear, + mock_post, mock_migrate, mock_pre): + # Confirm live_migration() works as expected correctly. + # creating instance testdata + c = context.get_admin_context() + instance = self._create_fake_instance_obj(context=c) + instance.host = self.compute.host + dest = 'desthost' + + migrate_data = migrate_data_obj.LibvirtLiveMigrateData( + is_shared_instance_path=False, + is_shared_block_storage=False) + mock_pre.return_value = migrate_data + + # start test + migration = objects.Migration() + with mock.patch.object(self.compute.driver, + 'cleanup') as mock_cleanup: + mock_cleanup.side_effect = test.TestingException + + self.assertRaises(test.TestingException, + self.compute.live_migration, + c, dest, instance, False, migration, migrate_data) + + # ensure we have updated the instance and migration objects + self.assertEqual(vm_states.ERROR, instance.vm_state) + self.assertIsNone(instance.task_state) + self.assertEqual("error", migration.status) + + mock_pre.assert_called_once_with(c, instance, False, None, + dest, migrate_data) + self.assertEqual(0, mock_clear.call_count) + + # cleanup + instance.destroy() + @mock.patch.object(fake.FakeDriver, 'unfilter_instance') @mock.patch.object(network_api.API, 'migrate_instance_start') @mock.patch.object(compute_rpcapi.ComputeAPI,