From 10f7128079942b14e7627fa34b93e2e0ae05058f Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Fri, 16 Sep 2011 18:06:27 +0000 Subject: Adding migration progress --- nova/api/openstack/views/servers.py | 3 +- nova/compute/manager.py | 2 +- nova/tests/test_virt_drivers.py | 3 +- nova/tests/test_xenapi.py | 2 +- nova/virt/driver.py | 3 +- nova/virt/fake.py | 2 +- nova/virt/xenapi/vmops.py | 97 +++++++++++++++++++++++-------------- nova/virt/xenapi_conn.py | 4 +- 8 files changed, 70 insertions(+), 46 deletions(-) diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 6640be810..925668e56 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -139,7 +139,8 @@ class ViewBuilderV11(ViewBuilder): response['server']['updated'] = utils.isotime(inst['updated_at']) status = response['server'].get('status') - if status in ('ACTIVE', 'BUILD', 'REBUILD'): + if status in ('ACTIVE', 'BUILD', 'REBUILD', 'RESIZE', + 'VERIFY_RESIZE'): response['server']['progress'] = inst['progress'] or 0 response['server']['accessIPv4'] = inst.get('access_ip_v4') or "" diff --git a/nova/compute/manager.py b/nova/compute/manager.py index dc67252ca..d80fa6e70 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -977,7 +977,7 @@ class ComputeManager(manager.SchedulerDependentManager): {'status': 'migrating'}) disk_info = self.driver.migrate_disk_and_power_off( - instance_ref, migration_ref['dest_host']) + context, instance_ref, migration_ref['dest_host']) self.db.migration_update(context, migration_id, {'status': 'post-migrating'}) diff --git a/nova/tests/test_virt_drivers.py b/nova/tests/test_virt_drivers.py index 440d3401b..244e4e803 100644 --- a/nova/tests/test_virt_drivers.py +++ b/nova/tests/test_virt_drivers.py @@ -181,7 +181,8 @@ class _VirtDriverTestCase(test.TestCase): instance_ref = test_utils.get_test_instance() network_info = test_utils.get_test_network_info() self.connection.spawn(self.ctxt, instance_ref, network_info) - self.connection.migrate_disk_and_power_off(instance_ref, 'dest_host') + self.connection.migrate_disk_and_power_off( + self.ctxt, instance_ref, 'dest_host') @catch_notimplementederror def test_pause(self): diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 6b5bb240c..a8a03b56b 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -763,7 +763,7 @@ class XenAPIMigrateInstance(test.TestCase): instance = db.instance_create(self.context, self.instance_values) stubs.stubout_session(self.stubs, stubs.FakeSessionForMigrationTests) conn = xenapi_conn.get_connection(False) - conn.migrate_disk_and_power_off(instance, '127.0.0.1') + conn.migrate_disk_and_power_off(self.context, instance, '127.0.0.1') def test_revert_migrate(self): instance = db.instance_create(self.context, self.instance_values) diff --git a/nova/virt/driver.py b/nova/virt/driver.py index 8b9d3053f..d1055bf2d 100644 --- a/nova/virt/driver.py +++ b/nova/virt/driver.py @@ -225,12 +225,11 @@ class ComputeDriver(object): """ raise NotImplementedError() - def migrate_disk_and_power_off(self, instance, dest): + def migrate_disk_and_power_off(self, context, instance, dest): """ Transfers the disk of a running instance in multiple phases, turning off the instance before the end. """ - # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() def snapshot(self, context, instance, image_id): diff --git a/nova/virt/fake.py b/nova/virt/fake.py index 3596d8353..eca1bf27b 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -130,7 +130,7 @@ class FakeConnection(driver.ComputeDriver): def poll_rescued_instances(self, timeout): pass - def migrate_disk_and_power_off(self, instance, dest): + def migrate_disk_and_power_off(self, context, instance, dest): pass def pause(self, instance, callback): diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index ca5ba9392..bf746a8b4 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -55,6 +55,9 @@ flags.DEFINE_string('xenapi_vif_driver', 'nova.virt.xenapi.vif.XenAPIBridgeDriver', 'The XenAPI VIF driver using XenServer Network APIs.') +RESIZE_TOTAL_STEPS = 5 +BUILD_TOTAL_STEPS = 4 + def cmp_version(a, b): """Compare two version strings (eg 0.0.1.10 > 0.0.1.9)""" @@ -137,7 +140,13 @@ class VMOps(object): network_info) if resize_instance: self.resize_instance(instance, vdi_uuid) + + # 5. Start VM self._start(instance, vm_ref=vm_ref) + self._update_instance_progress(context, instance, + step=5, + total_steps=RESIZE_TOTAL_STEPS) + def _start(self, instance, vm_ref=None): """Power on a VM instance""" @@ -157,34 +166,7 @@ class VMOps(object): disk_image_type) return vdis - def _update_instance_progress(self, context, instance, progress): - if progress < 0: - progress = 0 - elif progress > 100: - progress = 100 - instance_id = instance['id'] - LOG.debug(_("Updating instance '%(instance_id)s' progress to" - " %(progress)d") % locals()) - db.instance_update(context, instance_id, {'progress': progress}) - def spawn(self, context, instance, network_info): - total_steps = 4 - progress = {'value': 0} - - def bump_progress(): - # FIXME(sirp): for now we're taking a KISS approach to - # instance-build-progress: - # divide the action's workflow into discrete steps and "bump" the - # instance's progress field as each step is completed. - # - # For a first cut this should be fine, however, as image size - # grows, the _create_disks step begins to dominate the equation. A - # better approximation would reflect the percentage of the image - # that has been streamed to the host machine. - progress['value'] += 100 / total_steps - self._update_instance_progress( - context, instance, progress['value']) - vdis = None try: # 1. Vanity Step @@ -194,19 +176,28 @@ class VMOps(object): # progress remaining at 0% for too long, which will appear to be # an error, we insert a "vanity" step to bump the progress up one # notch above 0. - bump_progress() + self._update_instance_progress(context, instance, + step=1, + total_steps=BUILD_TOTAL_STEPS) # 2. Fetch the Image over the Network vdis = self._create_disks(context, instance) - bump_progress() + self._update_instance_progress(context, instance, + step=2, + total_steps=BUILD_TOTAL_STEPS) # 3. Create the VM records vm_ref = self._create_vm(context, instance, vdis, network_info) - bump_progress() + self._update_instance_progress(context, instance, + step=3, + total_steps=BUILD_TOTAL_STEPS) # 4. Boot the Instance self._spawn(instance, vm_ref) - bump_progress() + self._update_instance_progress(context, instance, + step=4, + total_steps=BUILD_TOTAL_STEPS) + except (self.XenAPI.Failure, OSError, IOError) as spawn_error: LOG.exception(_("instance %s: Failed to spawn"), instance.id, exc_info=sys.exc_info()) @@ -214,8 +205,6 @@ class VMOps(object): instance.id) self._handle_spawn_error(vdis, spawn_error) raise spawn_error - else: - self._update_instance_progress(context, instance, 100) def spawn_rescue(self, context, instance, network_info): """Spawn a rescue instance.""" @@ -228,7 +217,7 @@ class VMOps(object): if vm_ref is not None: raise exception.InstanceExists(name=instance_name) - #ensure enough free memory is available + # Ensure enough free memory is available if not VMHelper.ensure_free_mem(self._session, instance): LOG.exception(_('instance %(instance_name)s: not enough free ' 'memory') % locals()) @@ -608,7 +597,25 @@ class VMOps(object): def _get_orig_vm_name_label(self, instance): return instance.name + '-orig' - def migrate_disk_and_power_off(self, instance, dest): + def _update_instance_progress(self, context, instance, step, total_steps): + """Update instance progress percent to reflect current step number + """ + # FIXME(sirp): for now we're taking a KISS approach to instance + # progress: + # Divide the action's workflow into discrete steps and "bump" the + # instance's progress field as each step is completed. + # + # For a first cut this should be fine, however, for large VM images, + # the _create_disks step begins to dominate the equation. A + # better approximation would use the percentage of the VM image that + # has been streamed to the destination host. + progress = round(float(step) / total_steps * 100) + instance_id = instance['id'] + LOG.debug(_("Updating instance '%(instance_id)s' progress to" + " %(progress)d") % locals()) + db.instance_update(context, instance_id, {'progress': progress}) + + def migrate_disk_and_power_off(self, context, instance, dest): """Copies a VHD from one host machine to another. :param instance: the instance that owns the VHD in question. @@ -625,8 +632,13 @@ class VMOps(object): base_copy_uuid = cow_uuid = None template_vdi_uuids = template_vm_ref = None try: + # 1. Create Snapshot template_vm_ref, template_vdi_uuids =\ self._create_snapshot(instance) + self._update_instance_progress(context, instance, + step=1, + total_steps=RESIZE_TOTAL_STEPS) + base_copy_uuid = template_vdi_uuids['image'] vdi_ref, vm_vdi_rec = \ VMHelper.get_vdi_for_vm_safely(self._session, vm_ref) @@ -634,12 +646,23 @@ class VMOps(object): sr_path = VMHelper.get_sr_path(self._session) - # transfer the base copy + # 2. Transfer the base copy self._migrate_vhd(instance, base_copy_uuid, dest, sr_path) + self._update_instance_progress(context, instance, + step=2, + total_steps=RESIZE_TOTAL_STEPS) - # Now power down the instance and transfer the COW VHD + # 3. Now power down the instance self._shutdown(instance, vm_ref, hard=False) + self._update_instance_progress(context, instance, + step=3, + total_steps=RESIZE_TOTAL_STEPS) + + # 4. Transfer the COW VHD self._migrate_vhd(instance, cow_uuid, dest, sr_path) + self._update_instance_progress(context, instance, + step=4, + 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 diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 306991bf3..8d9a177f8 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -234,10 +234,10 @@ class XenAPIConnection(driver.ComputeDriver): """Unpause paused VM instance""" self._vmops.unpause(instance, callback) - def migrate_disk_and_power_off(self, instance, dest): + def migrate_disk_and_power_off(self, context, instance, dest): """Transfers the VHD of a running instance to another host, then shuts off the instance copies over the COW disk""" - return self._vmops.migrate_disk_and_power_off(instance, dest) + return self._vmops.migrate_disk_and_power_off(context, instance, dest) def suspend(self, instance, callback): """suspend the specified instance""" -- cgit