summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xnova/compute/manager.py7
-rw-r--r--nova/exception.py7
-rw-r--r--nova/tests/compute/test_compute.py37
-rw-r--r--nova/tests/test_xenapi.py116
-rw-r--r--nova/tests/virt/xenapi/test_vm_utils.py86
-rw-r--r--nova/tests/virt/xenapi/test_volumeops.py4
-rwxr-xr-xnova/virt/xenapi/driver.py13
-rw-r--r--nova/virt/xenapi/vm_utils.py32
-rw-r--r--nova/virt/xenapi/vmops.py153
-rw-r--r--nova/virt/xenapi/volumeops.py4
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: