diff options
author | Jenkins <jenkins@review.openstack.org> | 2013-05-15 18:35:49 +0000 |
---|---|---|
committer | Gerrit Code Review <review@openstack.org> | 2013-05-15 18:35:49 +0000 |
commit | 8dc14fad859e85752cb51fdf3f1790c58da7b51e (patch) | |
tree | 81d2b5cb277c4a9592cc39cda2894de7a1efec21 | |
parent | 773db796163d3ae3909c9f0973107160d36015bf (diff) | |
parent | 72e75dbcea66e6e4e2b0f5a931dcd647b2032ad6 (diff) | |
download | nova-8dc14fad859e85752cb51fdf3f1790c58da7b51e.tar.gz nova-8dc14fad859e85752cb51fdf3f1790c58da7b51e.tar.xz nova-8dc14fad859e85752cb51fdf3f1790c58da7b51e.zip |
Merge "xenapi: ensure vdi is not too big when resizing down"
-rwxr-xr-x | nova/compute/manager.py | 7 | ||||
-rw-r--r-- | nova/exception.py | 7 | ||||
-rw-r--r-- | nova/tests/compute/test_compute.py | 37 | ||||
-rw-r--r-- | nova/tests/test_xenapi.py | 116 | ||||
-rw-r--r-- | nova/tests/virt/xenapi/test_vm_utils.py | 86 | ||||
-rw-r--r-- | nova/tests/virt/xenapi/test_volumeops.py | 4 | ||||
-rwxr-xr-x | nova/virt/xenapi/driver.py | 13 | ||||
-rw-r--r-- | nova/virt/xenapi/vm_utils.py | 32 | ||||
-rw-r--r-- | nova/virt/xenapi/vmops.py | 153 | ||||
-rw-r--r-- | nova/virt/xenapi/volumeops.py | 4 |
10 files changed, 384 insertions, 75 deletions
diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 79903f854..7bc8281d4 100755 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -4117,6 +4117,13 @@ class ComputeManager(manager.SchedulerDependentManager): reservations=None): try: yield + except exception.InstanceFaultRollback, error: + self._quota_rollback(context, reservations) + msg = _("Setting instance back to ACTIVE after: %s") + LOG.info(msg % error, instance_uuid=instance_uuid) + self._instance_update(context, instance_uuid, + vm_state=vm_states.ACTIVE) + raise error.inner_exception except Exception, error: with excutils.save_and_reraise_exception(): self._quota_rollback(context, reservations) diff --git a/nova/exception.py b/nova/exception.py index a9afe37a7..439891153 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -1196,3 +1196,10 @@ class BuildAbortException(NovaException): class RescheduledException(NovaException): message = _("Build of instance %(instance_uuid)s was re-scheduled: " "%(reason)s") + + +class InstanceFaultRollback(NovaException): + def __init__(self, inner_exception=None): + message = _("Instance rollback performed due to: %s") + self.inner_exception = inner_exception + super(InstanceFaultRollback, self).__init__(message % inner_exception) diff --git a/nova/tests/compute/test_compute.py b/nova/tests/compute/test_compute.py index 9560fc78c..2adb07bdb 100644 --- a/nova/tests/compute/test_compute.py +++ b/nova/tests/compute/test_compute.py @@ -3089,6 +3089,43 @@ class ComputeTestCase(BaseTestCase): self.compute.terminate_instance(self.context, instance=jsonutils.to_primitive(instance)) + def test_resize_instance_driver_rollback(self): + # Ensure instance status set to Running after rollback. + + def throw_up(*args, **kwargs): + raise exception.InstanceFaultRollback(test.TestingException()) + + self.stubs.Set(self.compute.driver, 'migrate_disk_and_power_off', + throw_up) + + instance = jsonutils.to_primitive(self._create_fake_instance()) + instance_type = flavors.get_default_instance_type() + reservations = self._ensure_quota_reservations_rolledback() + self.compute.run_instance(self.context, instance=instance) + new_instance = db.instance_update(self.context, instance['uuid'], + {'host': 'foo'}) + new_instance = jsonutils.to_primitive(new_instance) + self.compute.prep_resize(self.context, instance=new_instance, + instance_type=instance_type, image={}, + reservations=reservations) + migration_ref = db.migration_get_by_instance_and_status( + self.context.elevated(), new_instance['uuid'], 'pre-migrating') + db.instance_update(self.context, new_instance['uuid'], + {"task_state": task_states.RESIZE_PREP}) + + self.assertRaises(test.TestingException, self.compute.resize_instance, + self.context, instance=new_instance, + migration=migration_ref, image={}, + reservations=reservations, + instance_type=jsonutils.to_primitive(instance_type)) + + instance = db.instance_get_by_uuid(self.context, new_instance['uuid']) + self.assertEqual(instance['vm_state'], vm_states.ACTIVE) + self.assertEqual(instance['task_state'], None) + + self.compute.terminate_instance(self.context, + instance=jsonutils.to_primitive(instance)) + def test_resize_instance(self): # Ensure instance can be migrated/resized. instance = jsonutils.to_primitive(self._create_fake_instance()) diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 9a366d7bf..45ebfaab3 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -1513,6 +1513,122 @@ class XenAPIMigrateInstance(stubs.XenAPITestBase): self.context, instance, '127.0.0.1', instance_type, None) + def test_migrate_rollback_when_resize_down_fs_fails(self): + conn = xenapi_conn.XenAPIDriver(fake.FakeVirtAPI(), False) + vmops = conn._vmops + virtapi = vmops._virtapi + + self.mox.StubOutWithMock(vmops, '_resize_ensure_vm_is_shutdown') + self.mox.StubOutWithMock(vmops, '_apply_orig_vm_name_label') + self.mox.StubOutWithMock(vm_utils, 'resize_disk') + self.mox.StubOutWithMock(vmops, '_migrate_vhd') + self.mox.StubOutWithMock(vm_utils, 'destroy_vdi') + self.mox.StubOutWithMock(vm_utils, 'get_vdi_for_vm_safely') + self.mox.StubOutWithMock(vmops, '_restore_orig_vm_and_cleanup_orphan') + self.mox.StubOutWithMock(virtapi, 'instance_update') + + instance = {'auto_disk_config': True, 'uuid': 'uuid'} + vm_ref = "vm_ref" + dest = "dest" + instance_type = "type" + sr_path = "sr_path" + + virtapi.instance_update(self.context, 'uuid', {'progress': 20.0}) + vmops._resize_ensure_vm_is_shutdown(instance, vm_ref) + vmops._apply_orig_vm_name_label(instance, vm_ref) + old_vdi_ref = "old_ref" + vm_utils.get_vdi_for_vm_safely(vmops._session, vm_ref).AndReturn( + (old_vdi_ref, None)) + virtapi.instance_update(self.context, 'uuid', {'progress': 40.0}) + new_vdi_ref = "new_ref" + new_vdi_uuid = "new_uuid" + vm_utils.resize_disk(vmops._session, instance, old_vdi_ref, + instance_type).AndReturn((new_vdi_ref, new_vdi_uuid)) + virtapi.instance_update(self.context, 'uuid', {'progress': 60.0}) + vmops._migrate_vhd(instance, new_vdi_uuid, dest, + sr_path, 0).AndRaise( + exception.ResizeError(reason="asdf")) + + vm_utils.destroy_vdi(vmops._session, new_vdi_ref) + vmops._restore_orig_vm_and_cleanup_orphan(instance, None) + + self.mox.ReplayAll() + + self.assertRaises(exception.InstanceFaultRollback, + vmops._migrate_disk_resizing_down, self.context, + instance, dest, instance_type, vm_ref, sr_path) + + def test_resize_ensure_vm_is_shutdown_cleanly(self): + conn = xenapi_conn.XenAPIDriver(fake.FakeVirtAPI(), False) + vmops = conn._vmops + fake_instance = {'uuid': 'uuid'} + + self.mox.StubOutWithMock(vm_utils, 'is_vm_shutdown') + self.mox.StubOutWithMock(vm_utils, 'clean_shutdown_vm') + self.mox.StubOutWithMock(vm_utils, 'hard_shutdown_vm') + + vm_utils.is_vm_shutdown(vmops._session, "ref").AndReturn(False) + vm_utils.clean_shutdown_vm(vmops._session, fake_instance, + "ref").AndReturn(True) + + self.mox.ReplayAll() + + vmops._resize_ensure_vm_is_shutdown(fake_instance, "ref") + + def test_resize_ensure_vm_is_shutdown_forced(self): + conn = xenapi_conn.XenAPIDriver(fake.FakeVirtAPI(), False) + vmops = conn._vmops + fake_instance = {'uuid': 'uuid'} + + self.mox.StubOutWithMock(vm_utils, 'is_vm_shutdown') + self.mox.StubOutWithMock(vm_utils, 'clean_shutdown_vm') + self.mox.StubOutWithMock(vm_utils, 'hard_shutdown_vm') + + vm_utils.is_vm_shutdown(vmops._session, "ref").AndReturn(False) + vm_utils.clean_shutdown_vm(vmops._session, fake_instance, + "ref").AndReturn(False) + vm_utils.hard_shutdown_vm(vmops._session, fake_instance, + "ref").AndReturn(True) + + self.mox.ReplayAll() + + vmops._resize_ensure_vm_is_shutdown(fake_instance, "ref") + + def test_resize_ensure_vm_is_shutdown_fails(self): + conn = xenapi_conn.XenAPIDriver(fake.FakeVirtAPI(), False) + vmops = conn._vmops + fake_instance = {'uuid': 'uuid'} + + self.mox.StubOutWithMock(vm_utils, 'is_vm_shutdown') + self.mox.StubOutWithMock(vm_utils, 'clean_shutdown_vm') + self.mox.StubOutWithMock(vm_utils, 'hard_shutdown_vm') + + vm_utils.is_vm_shutdown(vmops._session, "ref").AndReturn(False) + vm_utils.clean_shutdown_vm(vmops._session, fake_instance, + "ref").AndReturn(False) + vm_utils.hard_shutdown_vm(vmops._session, fake_instance, + "ref").AndReturn(False) + + self.mox.ReplayAll() + + self.assertRaises(exception.ResizeError, + vmops._resize_ensure_vm_is_shutdown, fake_instance, "ref") + + def test_resize_ensure_vm_is_shutdown_already_shutdown(self): + conn = xenapi_conn.XenAPIDriver(fake.FakeVirtAPI(), False) + vmops = conn._vmops + fake_instance = {'uuid': 'uuid'} + + self.mox.StubOutWithMock(vm_utils, 'is_vm_shutdown') + self.mox.StubOutWithMock(vm_utils, 'clean_shutdown_vm') + self.mox.StubOutWithMock(vm_utils, 'hard_shutdown_vm') + + vm_utils.is_vm_shutdown(vmops._session, "ref").AndReturn(True) + + self.mox.ReplayAll() + + vmops._resize_ensure_vm_is_shutdown(fake_instance, "ref") + class XenAPIImageTypeTestCase(test.TestCase): """Test ImageType class.""" diff --git a/nova/tests/virt/xenapi/test_vm_utils.py b/nova/tests/virt/xenapi/test_vm_utils.py index 3e532a1c7..2180f75f9 100644 --- a/nova/tests/virt/xenapi/test_vm_utils.py +++ b/nova/tests/virt/xenapi/test_vm_utils.py @@ -278,3 +278,89 @@ class FetchVhdImageTestCase(test.TestCase): self.context, self.session, self.instance, self.image_id) self.mox.VerifyAll() + + +class ResizeHelpersTestCase(test.TestCase): + def test_get_min_sectors(self): + self.mox.StubOutWithMock(utils, 'execute') + + utils.execute('resize2fs', '-P', "fakepath", + run_as_root=True).AndReturn(("size is: 42", "")) + + self.mox.ReplayAll() + + result = vm_utils._get_min_sectors("fakepath") + self.assertEquals(42 * 4096 / 512, result) + + def test_repair_filesystem(self): + self.mox.StubOutWithMock(utils, 'execute') + + utils.execute('e2fsck', '-f', "-y", "fakepath", + run_as_root=True, check_exit_code=[0, 1, 2]).AndReturn( + ("size is: 42", "")) + + self.mox.ReplayAll() + + vm_utils._repair_filesystem("fakepath") + + def _call_tune2fs_remove_journal(self, path): + utils.execute("tune2fs", "-O ^has_journal", path, run_as_root=True) + + def _call_tune2fs_add_journal(self, path): + utils.execute("tune2fs", "-j", path, run_as_root=True) + + def _call_parted(self, path, start, end): + utils.execute('parted', '--script', path, 'rm', '1', + run_as_root=True) + utils.execute('parted', '--script', path, 'mkpart', + 'primary', '%ds' % start, '%ds' % end, run_as_root=True) + + def test_resize_part_and_fs_down_succeeds(self): + self.mox.StubOutWithMock(vm_utils, "_repair_filesystem") + self.mox.StubOutWithMock(utils, 'execute') + self.mox.StubOutWithMock(vm_utils, "_get_min_sectors") + + dev_path = "/dev/fake" + partition_path = "%s1" % dev_path + vm_utils._repair_filesystem(partition_path) + self._call_tune2fs_remove_journal(partition_path) + vm_utils._get_min_sectors(partition_path).AndReturn(9) + utils.execute("resize2fs", partition_path, "10s", run_as_root=True) + self._call_parted(dev_path, 0, 9) + self._call_tune2fs_add_journal(partition_path) + + self.mox.ReplayAll() + + vm_utils._resize_part_and_fs("fake", 0, 20, 10) + + def test_resize_part_and_fs_down_fails_disk_too_big(self): + self.mox.StubOutWithMock(vm_utils, "_repair_filesystem") + self.mox.StubOutWithMock(utils, 'execute') + self.mox.StubOutWithMock(vm_utils, "_get_min_sectors") + + dev_path = "/dev/fake" + partition_path = "%s1" % dev_path + vm_utils._repair_filesystem(partition_path) + self._call_tune2fs_remove_journal(partition_path) + vm_utils._get_min_sectors(partition_path).AndReturn(10) + + self.mox.ReplayAll() + + self.assertRaises(exception.ResizeError, + vm_utils._resize_part_and_fs, "fake", 0, 20, 10) + + def test_resize_part_and_fs_up_succeeds(self): + self.mox.StubOutWithMock(vm_utils, "_repair_filesystem") + self.mox.StubOutWithMock(utils, 'execute') + + dev_path = "/dev/fake" + partition_path = "%s1" % dev_path + vm_utils._repair_filesystem(partition_path) + self._call_tune2fs_remove_journal(partition_path) + self._call_parted(dev_path, 0, 29) + utils.execute("resize2fs", partition_path, run_as_root=True) + self._call_tune2fs_add_journal(partition_path) + + self.mox.ReplayAll() + + vm_utils._resize_part_and_fs("fake", 0, 20, 30) diff --git a/nova/tests/virt/xenapi/test_volumeops.py b/nova/tests/virt/xenapi/test_volumeops.py index 7ec9eb1ea..5d4344bb0 100644 --- a/nova/tests/virt/xenapi/test_volumeops.py +++ b/nova/tests/virt/xenapi/test_volumeops.py @@ -33,7 +33,7 @@ class VolumeAttachTestCase(test.TestCase): ops = volumeops.VolumeOps('session') self.mox.StubOutWithMock(volumeops.vm_utils, 'lookup') self.mox.StubOutWithMock(volumeops.vm_utils, 'find_vbd_by_number') - self.mox.StubOutWithMock(volumeops.vm_utils, '_is_vm_shutdown') + self.mox.StubOutWithMock(volumeops.vm_utils, 'is_vm_shutdown') self.mox.StubOutWithMock(volumeops.vm_utils, 'unplug_vbd') self.mox.StubOutWithMock(volumeops.vm_utils, 'destroy_vbd') self.mox.StubOutWithMock(volumeops.volume_utils, 'get_device_number') @@ -49,7 +49,7 @@ class VolumeAttachTestCase(test.TestCase): volumeops.vm_utils.find_vbd_by_number( 'session', 'vmref', 'devnumber').AndReturn('vbdref') - volumeops.vm_utils._is_vm_shutdown('session', 'vmref').AndReturn( + volumeops.vm_utils.is_vm_shutdown('session', 'vmref').AndReturn( False) volumeops.vm_utils.unplug_vbd('session', 'vbdref') diff --git a/nova/virt/xenapi/driver.py b/nova/virt/xenapi/driver.py index 02b849a99..5bc1a3049 100755 --- a/nova/virt/xenapi/driver.py +++ b/nova/virt/xenapi/driver.py @@ -239,17 +239,8 @@ class XenAPIDriver(driver.ComputeDriver): """Transfers the VHD of a running instance to another host, then shuts off the instance copies over the COW disk""" # NOTE(vish): Xen currently does not use network info. - rv = self._vmops.migrate_disk_and_power_off(context, instance, - dest, instance_type) - block_device_mapping = driver.block_device_info_get_mapping( - block_device_info) - name_label = self._vmops._get_orig_vm_name_label(instance) - for vol in block_device_mapping: - connection_info = vol['connection_info'] - mount_device = vol['mount_device'].rpartition("/")[2] - self._volumeops.detach_volume(connection_info, - name_label, mount_device) - return rv + return self._vmops.migrate_disk_and_power_off(context, instance, + dest, instance_type, block_device_info) def suspend(self, instance): """suspend the specified instance.""" diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 4cd2b2ab5..3f2c6835f 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -286,7 +286,7 @@ def destroy_vm(session, instance, vm_ref): def clean_shutdown_vm(session, instance, vm_ref): - if _is_vm_shutdown(session, vm_ref): + if is_vm_shutdown(session, vm_ref): LOG.warn(_("VM already halted, skipping shutdown..."), instance=instance) return False @@ -301,7 +301,7 @@ def clean_shutdown_vm(session, instance, vm_ref): def hard_shutdown_vm(session, instance, vm_ref): - if _is_vm_shutdown(session, vm_ref): + if is_vm_shutdown(session, vm_ref): LOG.warn(_("VM already halted, skipping shutdown..."), instance=instance) return False @@ -315,7 +315,7 @@ def hard_shutdown_vm(session, instance, vm_ref): return True -def _is_vm_shutdown(session, vm_ref): +def is_vm_shutdown(session, vm_ref): vm_rec = session.call_xenapi("VM.get_record", vm_ref) state = compile_info(vm_rec)['state'] if state == power_state.SHUTDOWN: @@ -2078,6 +2078,21 @@ def _write_partition(virtual_size, dev): LOG.debug(_('Writing partition table %s done.'), dev_path) +def _get_min_sectors(partition_path, block_size=4096): + stdout, _err = utils.execute('resize2fs', '-P', partition_path, + run_as_root=True) + min_size_blocks = long(re.sub('[^0-9]', '', stdout)) + min_size_bytes = min_size_blocks * block_size + return min_size_bytes / SECTOR_SIZE + + +def _repair_filesystem(partition_path): + # Exit Code 1 = File system errors corrected + # 2 = File system errors corrected, system needs a reboot + utils.execute('e2fsck', '-f', '-y', partition_path, run_as_root=True, + check_exit_code=[0, 1, 2]) + + def _resize_part_and_fs(dev, start, old_sectors, new_sectors): """Resize partition and fileystem. @@ -2091,10 +2106,7 @@ def _resize_part_and_fs(dev, start, old_sectors, new_sectors): partition_path = utils.make_dev_path(dev, partition=1) # Replay journal if FS wasn't cleanly unmounted - # Exit Code 1 = File system errors corrected - # 2 = File system errors corrected, system needs a reboot - utils.execute('e2fsck', '-f', '-y', partition_path, run_as_root=True, - check_exit_code=[0, 1, 2]) + _repair_filesystem(partition_path) # Remove ext3 journal (making it ext2) utils.execute('tune2fs', '-O ^has_journal', partition_path, @@ -2102,6 +2114,12 @@ def _resize_part_and_fs(dev, start, old_sectors, new_sectors): if new_sectors < old_sectors: # Resizing down, resize filesystem before partition resize + min_sectors = _get_min_sectors(partition_path) + if min_sectors >= new_sectors: + reason = _('Resize down not allowed because minimum ' + 'filesystem sectors %(min_sectors)d is too big ' + 'for target sectors %(new_sectors)d') + raise exception.ResizeError(reason=(reason % locals())) utils.execute('resize2fs', partition_path, '%ds' % size, run_as_root=True) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 1dabe271d..034556809 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -209,6 +209,9 @@ class VMOps(object): return nova_uuids def confirm_migration(self, migration, instance, network_info): + self._destroy_orig_vm(instance, network_info) + + def _destroy_orig_vm(self, instance, network_info): name_label = self._get_orig_vm_name_label(instance) vm_ref = vm_utils.lookup(self._session, name_label) return self._destroy(instance, vm_ref, network_info=network_info) @@ -227,6 +230,9 @@ class VMOps(object): hotplug=False) def finish_revert_migration(self, instance, block_device_info=None): + self._restore_orig_vm_and_cleanup_orphan(instance, block_device_info) + + def _restore_orig_vm_and_cleanup_orphan(self, instance, block_device_info): # NOTE(sirp): the original vm was suffixed with '-orig'; find it using # the old suffix, remove the suffix, then power it back on. name_label = self._get_orig_vm_name_label(instance) @@ -797,53 +803,84 @@ class VMOps(object): self._virtapi.instance_update(context, instance['uuid'], {'progress': progress}) + def _resize_ensure_vm_is_shutdown(self, instance, vm_ref): + if vm_utils.is_vm_shutdown(self._session, vm_ref): + LOG.debug(_("VM was already shutdown."), instance=instance) + return + + if not vm_utils.clean_shutdown_vm(self._session, instance, vm_ref): + LOG.debug(_("Clean shutdown did not complete successfully, " + "trying hard shutdown."), instance=instance) + if not vm_utils.hard_shutdown_vm(self._session, instance, vm_ref): + raise exception.ResizeError( + reason=_("Unable to terminate instance.")) + def _migrate_disk_resizing_down(self, context, instance, dest, instance_type, vm_ref, sr_path): - # 1. NOOP since we're not transmitting the base-copy separately - self._update_instance_progress(context, instance, - step=1, - total_steps=RESIZE_TOTAL_STEPS) + if not instance['auto_disk_config']: + reason = _('Resize down not allowed without auto_disk_config') + raise exception.ResizeError(reason=reason) - vdi_ref, vm_vdi_rec = vm_utils.get_vdi_for_vm_safely( - self._session, vm_ref) - vdi_uuid = vm_vdi_rec['uuid'] + step = make_step_decorator(context, instance, + self._virtapi.instance_update) - old_gb = instance['root_gb'] - new_gb = instance_type['root_gb'] - LOG.debug(_("Resizing down VDI %(vdi_uuid)s from " - "%(old_gb)dGB to %(new_gb)dGB"), locals(), - instance=instance) + @step + def fake_step_to_match_resizing_up(): + pass - # 2. Power down the instance before resizing - if not vm_utils.clean_shutdown_vm(self._session, instance, vm_ref): - LOG.debug(_("Clean shutdown did not complete successfully, " - "trying hard shutdown."), instance=instance) - vm_utils.hard_shutdown_vm(self._session, instance, vm_ref) - self._update_instance_progress(context, instance, - step=2, - total_steps=RESIZE_TOTAL_STEPS) + @step + def rename_and_power_off_vm(undo_mgr): + self._resize_ensure_vm_is_shutdown(instance, vm_ref) + self._apply_orig_vm_name_label(instance, vm_ref) - # 3. Copy VDI, resize partition and filesystem, forget VDI, - # truncate VHD - new_ref, new_uuid = vm_utils.resize_disk(self._session, - instance, - vdi_ref, - instance_type) - self._update_instance_progress(context, instance, - step=3, - total_steps=RESIZE_TOTAL_STEPS) + def restore_orig_vm(): + # Do not need to restore block devices, not yet been removed + self._restore_orig_vm_and_cleanup_orphan(instance, None) - # 4. Transfer the new VHD - self._migrate_vhd(instance, new_uuid, dest, sr_path, 0) - self._update_instance_progress(context, instance, - step=4, - total_steps=RESIZE_TOTAL_STEPS) + undo_mgr.undo_with(restore_orig_vm) + + @step + def create_copy_vdi_and_resize(undo_mgr, old_vdi_ref): + new_vdi_ref, new_vdi_uuid = vm_utils.resize_disk(self._session, + instance, old_vdi_ref, instance_type) - # Clean up VDI now that it's been copied - vm_utils.destroy_vdi(self._session, new_ref) + def cleanup_vdi_copy(): + vm_utils.destroy_vdi(self._session, new_vdi_ref) + + undo_mgr.undo_with(cleanup_vdi_copy) + + return new_vdi_ref, new_vdi_uuid + + @step + def transfer_vhd_to_dest(new_vdi_ref, new_vdi_uuid): + self._migrate_vhd(instance, new_vdi_uuid, dest, sr_path, 0) + # Clean up VDI now that it's been copied + vm_utils.destroy_vdi(self._session, new_vdi_ref) + + @step + def fake_step_to_be_executed_by_finish_migration(): + pass + + undo_mgr = utils.UndoManager() + try: + fake_step_to_match_resizing_up() + rename_and_power_off_vm(undo_mgr) + old_vdi_ref, _ignore = vm_utils.get_vdi_for_vm_safely( + self._session, vm_ref) + new_vdi_ref, new_vdi_uuid = create_copy_vdi_and_resize( + undo_mgr, old_vdi_ref) + transfer_vhd_to_dest(new_vdi_ref, new_vdi_uuid) + except Exception, error: + msg = _("_migrate_disk_resizing_down failed. " + "Restoring orig vm due_to: %{exception}.") + LOG.exception(msg, instance=instance) + undo_mgr._rollback() + raise exception.InstanceFaultRollback(error) def _migrate_disk_resizing_up(self, context, instance, dest, vm_ref, sr_path): + self._apply_orig_vm_name_label(instance, vm_ref) + # 1. Create Snapshot label = "%s-snapshot" % instance['name'] with vm_utils.snapshot_attached_here( @@ -865,10 +902,7 @@ class VMOps(object): total_steps=RESIZE_TOTAL_STEPS) # 3. Now power down the instance - if not vm_utils.clean_shutdown_vm(self._session, instance, vm_ref): - LOG.debug(_("Clean shutdown did not complete successfully, " - "trying hard shutdown."), instance=instance) - vm_utils.hard_shutdown_vm(self._session, instance, vm_ref) + self._resize_ensure_vm_is_shutdown(instance, vm_ref) self._update_instance_progress(context, instance, step=3, total_steps=RESIZE_TOTAL_STEPS) @@ -882,8 +916,15 @@ class VMOps(object): step=4, total_steps=RESIZE_TOTAL_STEPS) + def _apply_orig_vm_name_label(self, instance, vm_ref): + # NOTE(sirp): in case we're resizing to the same host (for dev + # purposes), apply a suffix to name-label so the two VM records + # extant until a confirm_resize don't collide. + name_label = self._get_orig_vm_name_label(instance) + vm_utils.set_vm_name_label(self._session, vm_ref, name_label) + def migrate_disk_and_power_off(self, context, instance, dest, - instance_type): + instance_type, block_device_info): """Copies a VHD from one host machine to another, possibly resizing filesystem before hand. @@ -891,23 +932,17 @@ class VMOps(object): :param dest: the destination host machine. :param instance_type: instance_type to resize to """ - vm_ref = self._get_vm_opaque_ref(instance) - sr_path = vm_utils.get_sr_path(self._session) - resize_down = instance['root_gb'] > instance_type['root_gb'] - if resize_down and not instance['auto_disk_config']: - reason = _('Resize down not allowed without auto_disk_config') - raise exception.ResizeError(reason=reason) - # 0. Zero out the progress to begin self._update_instance_progress(context, instance, step=0, total_steps=RESIZE_TOTAL_STEPS) - # NOTE(sirp): in case we're resizing to the same host (for dev - # purposes), apply a suffix to name-label so the two VM records - # extant until a confirm_resize don't collide. - name_label = self._get_orig_vm_name_label(instance) - vm_utils.set_vm_name_label(self._session, vm_ref, name_label) + vm_ref = self._get_vm_opaque_ref(instance) + sr_path = vm_utils.get_sr_path(self._session) + + old_gb = instance['root_gb'] + new_gb = instance_type['root_gb'] + resize_down = old_gb > new_gb if resize_down: self._migrate_disk_resizing_down( @@ -916,12 +951,24 @@ class VMOps(object): self._migrate_disk_resizing_up( context, instance, dest, vm_ref, sr_path) + self._detach_block_devices_from_orig_vm(instance, block_device_info) + # NOTE(sirp): disk_info isn't used by the xenapi driver, instead it # uses a staging-area (/images/instance<uuid>) and sequence-numbered # VHDs to figure out how to reconstruct the VDI chain after syncing disk_info = {} return disk_info + def _detach_block_devices_from_orig_vm(self, instance, block_device_info): + block_device_mapping = virt_driver.block_device_info_get_mapping( + block_device_info) + name_label = self._get_orig_vm_name_label(instance) + for vol in block_device_mapping: + connection_info = vol['connection_info'] + mount_device = vol['mount_device'].rpartition("/")[2] + self._volumeops.detach_volume(connection_info, name_label, + mount_device) + def _resize_instance(self, instance, root_vdi): """Resize an instances root disk.""" diff --git a/nova/virt/xenapi/volumeops.py b/nova/virt/xenapi/volumeops.py index 5e650f55d..3560edbc1 100644 --- a/nova/virt/xenapi/volumeops.py +++ b/nova/virt/xenapi/volumeops.py @@ -141,7 +141,7 @@ class VolumeOps(object): return # Unplug VBD if we're NOT shutdown - unplug = not vm_utils._is_vm_shutdown(self._session, vm_ref) + unplug = not vm_utils.is_vm_shutdown(self._session, vm_ref) self._detach_vbd(vbd_ref, unplug=unplug) LOG.info(_('Mountpoint %(mountpoint)s detached from instance' @@ -171,7 +171,7 @@ class VolumeOps(object): # Generally speaking, detach_all will be called with VM already # shutdown; however if it's still running, we can still perform the # operation by unplugging the VBD first. - unplug = not vm_utils._is_vm_shutdown(self._session, vm_ref) + unplug = not vm_utils.is_vm_shutdown(self._session, vm_ref) vbd_refs = self._get_all_volume_vbd_refs(vm_ref) for vbd_ref in vbd_refs: |