From 2d4181e483fbba4c08458892fd2dcb228700ecf5 Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Wed, 14 Sep 2011 15:52:30 +0000 Subject: Adding migration for instance progress --- .../versions/046_add_instances_progress.py | 43 ++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 nova/db/sqlalchemy/migrate_repo/versions/046_add_instances_progress.py diff --git a/nova/db/sqlalchemy/migrate_repo/versions/046_add_instances_progress.py b/nova/db/sqlalchemy/migrate_repo/versions/046_add_instances_progress.py new file mode 100644 index 000000000..d23d52e80 --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/046_add_instances_progress.py @@ -0,0 +1,43 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 OpenStack LLC. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from sqlalchemy import * +from migrate import * + +from nova import log as logging + +meta = MetaData() + +instances = Table('instances', meta, + Column("id", Integer(), primary_key=True, nullable=False)) + +# Add progress column to instances table +progress = Column('progress', Integer()) + + +def upgrade(migrate_engine): + meta.bind = migrate_engine + + try: + instances.create_column(progress) + except Exception: + logging.error(_("progress column not added to instances table")) + raise + + +def downgrade(migrate_engine): + meta.bind = migrate_engine + instances.drop_column(progress) -- cgit From e0cf82323ab19bbcbad88aa75045b3e55692f071 Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Wed, 14 Sep 2011 23:11:03 +0000 Subject: Adding progress --- nova/api/openstack/views/servers.py | 9 ++--- nova/compute/api.py | 9 +++-- nova/db/sqlalchemy/models.py | 2 + .../api/openstack/contrib/test_createserverext.py | 6 ++- nova/tests/api/openstack/test_server_actions.py | 1 + nova/tests/api/openstack/test_servers.py | 15 +++++--- nova/utils.py | 14 +++++++ nova/virt/xenapi/vmops.py | 45 ++++++++++++++++++++++ 8 files changed, 86 insertions(+), 15 deletions(-) diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 473dc9e7e..6640be810 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -137,11 +137,10 @@ class ViewBuilderV11(ViewBuilder): response = super(ViewBuilderV11, self)._build_detail(inst) response['server']['created'] = utils.isotime(inst['created_at']) response['server']['updated'] = utils.isotime(inst['updated_at']) - if 'status' in response['server']: - if response['server']['status'] == "ACTIVE": - response['server']['progress'] = 100 - elif response['server']['status'] == "BUILD": - response['server']['progress'] = 0 + + status = response['server'].get('status') + if status in ('ACTIVE', 'BUILD', 'REBUILD'): + response['server']['progress'] = inst['progress'] or 0 response['server']['accessIPv4'] = inst.get('access_ip_v4') or "" response['server']['accessIPv6'] = inst.get('access_ip_v6') or "" diff --git a/nova/compute/api.py b/nova/compute/api.py index d674224e5..8b3306409 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -762,7 +762,8 @@ class API(base.Base): self.update(context, instance_id, - task_state=task_states.DELETING) + task_state=task_states.DELETING, + progress=0) host = instance['host'] if host: @@ -785,7 +786,8 @@ class API(base.Base): instance_id, vm_state=vm_states.ACTIVE, task_state=task_states.STOPPING, - terminated_at=utils.utcnow()) + terminated_at=utils.utcnow(), + progress=0) host = instance['host'] if host: @@ -1075,7 +1077,8 @@ class API(base.Base): display_name=name, image_ref=image_href, vm_state=vm_states.ACTIVE, - task_state=task_states.REBUILDING) + task_state=task_states.REBUILDING, + progress=0) rebuild_params = { "new_pass": admin_password, diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 211049112..6ff7fbfda 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -239,6 +239,8 @@ class Instance(BASE, NovaBase): access_ip_v4 = Column(String(255)) access_ip_v6 = Column(String(255)) + progress = Column(Integer) + class VirtualStorageArray(BASE, NovaBase): """ diff --git a/nova/tests/api/openstack/contrib/test_createserverext.py b/nova/tests/api/openstack/contrib/test_createserverext.py index 078b72d67..24f756d5b 100644 --- a/nova/tests/api/openstack/contrib/test_createserverext.py +++ b/nova/tests/api/openstack/contrib/test_createserverext.py @@ -51,7 +51,8 @@ INSTANCE = { "uuid": FAKE_UUID, "created_at": datetime.datetime(2010, 10, 10, 12, 0, 0), "updated_at": datetime.datetime(2010, 11, 11, 11, 0, 0), - "security_groups": [{"id": 1, "name": "test"}] + "security_groups": [{"id": 1, "name": "test"}], + "progress": 0 } @@ -115,7 +116,8 @@ class CreateserverextTest(test.TestCase): 'user_id': 'fake', 'project_id': 'fake', 'created_at': "", - 'updated_at': ""}] + 'updated_at': "", + 'progress': 0}] def set_admin_password(self, *args, **kwargs): pass diff --git a/nova/tests/api/openstack/test_server_actions.py b/nova/tests/api/openstack/test_server_actions.py index b9ef41465..f6e45e9c7 100644 --- a/nova/tests/api/openstack/test_server_actions.py +++ b/nova/tests/api/openstack/test_server_actions.py @@ -91,6 +91,7 @@ def stub_instance(id, metadata=None, image_ref="10", flavor_id="1", "access_ip_v6": "", "uuid": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", "virtual_interfaces": [], + "progress": 0, } instance["fixed_ips"] = { diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index f0a1c5ce5..f654bf209 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -156,7 +156,7 @@ def stub_instance(id, user_id='fake', project_id='fake', private_address=None, vm_state=None, task_state=None, reservation_id="", uuid=FAKE_UUID, image_ref="10", flavor_id="1", interfaces=None, name=None, key_name='', - access_ipv4=None, access_ipv6=None): + access_ipv4=None, access_ipv6=None, progress=0): metadata = [] metadata.append(InstanceMetadata(key='seq', value=id)) @@ -216,7 +216,8 @@ def stub_instance(id, user_id='fake', project_id='fake', private_address=None, "access_ip_v4": access_ipv4, "access_ip_v6": access_ipv6, "uuid": uuid, - "virtual_interfaces": interfaces} + "virtual_interfaces": interfaces, + "progress": progress} instance["fixed_ips"] = { "address": private_address, @@ -509,7 +510,8 @@ class ServersTest(test.TestCase): }, ] new_return_server = return_server_with_attributes( - interfaces=interfaces, vm_state=vm_states.ACTIVE) + interfaces=interfaces, vm_state=vm_states.ACTIVE, + progress=100) self.stubs.Set(nova.db.api, 'instance_get', new_return_server) req = webob.Request.blank('/v1.1/fake/servers/1') @@ -606,7 +608,7 @@ class ServersTest(test.TestCase): ] new_return_server = return_server_with_attributes( interfaces=interfaces, vm_state=vm_states.ACTIVE, - image_ref=image_ref, flavor_id=flavor_id) + image_ref=image_ref, flavor_id=flavor_id, progress=100) self.stubs.Set(nova.db.api, 'instance_get', new_return_server) req = webob.Request.blank('/v1.1/fake/servers/1') @@ -1488,6 +1490,7 @@ class ServersTest(test.TestCase): "created_at": datetime.datetime(2010, 10, 10, 12, 0, 0), "updated_at": datetime.datetime(2010, 11, 11, 11, 0, 0), "config_drive": self.config_drive, + "progress": 0 } def server_update(context, id, params): @@ -3689,7 +3692,8 @@ class ServersViewBuilderV11Test(test.TestCase): "accessIPv6": "fead::1234", #"address": , #"floating_ips": [{"address":ip} for ip in public_addresses]} - "uuid": "deadbeef-feed-edee-beef-d0ea7beefedd"} + "uuid": "deadbeef-feed-edee-beef-d0ea7beefedd", + "progress": 0} return instance @@ -3812,6 +3816,7 @@ class ServersViewBuilderV11Test(test.TestCase): def test_build_server_detail_active_status(self): #set the power state of the instance to running self.instance['vm_state'] = vm_states.ACTIVE + self.instance['progress'] = 100 image_bookmark = "http://localhost/images/5" flavor_bookmark = "http://localhost/flavors/1" expected_server = { diff --git a/nova/utils.py b/nova/utils.py index 81157a4cd..a9cd04d42 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -910,3 +910,17 @@ def convert_to_list_dict(lst, label): if not isinstance(lst, list): lst = [lst] return [{label: x} for x in lst] + + +def timefunc(func): + """Decorator that logs how long a particular function took to execute""" + @functools.wraps(func) + def inner(*args, **kwargs): + start_time = time.time() + try: + return func(*args, **kwargs) + finally: + total_time = time.time() - start_time + LOG.debug(_("timefunc: '%(name)s' took %(total_time).2f secs") % + dict(name=func.__name__, total_time=total_time)) + return inner diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index fb9c602d9..e7296e32c 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -142,12 +142,55 @@ 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 + # NOTE(sirp): _create_disk will potentially take a *very* long + # time to complete since it has to fetch the image over the + # network and images can be several gigs in size. To avoid + # 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() + + # 2. Fetch the Image over the Network vdis = self._create_disks(context, instance) + bump_progress() + + # 3. Create the VM records vm_ref = self._create_vm(context, instance, vdis, network_info) + bump_progress() + + # 4. Boot the Instance self._spawn(instance, vm_ref) + bump_progress() except (self.XenAPI.Failure, OSError, IOError) as spawn_error: LOG.exception(_("instance %s: Failed to spawn"), instance.id, exc_info=sys.exc_info()) @@ -155,6 +198,8 @@ 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.""" -- cgit From af7a495d7a2b394938e9677fc7fe679c9a963f8c Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Wed, 14 Sep 2011 18:22:39 -0500 Subject: PEP8 fix --- nova/virt/xenapi/vmops.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index e7296e32c..6e768ba70 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -155,6 +155,7 @@ class VMOps(object): 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: -- cgit From 6f3ae6e1e5453330e14807348f6e3f6587877946 Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Fri, 16 Sep 2011 00:22:31 +0000 Subject: Allowing resizes to the same machine --- nova/compute/manager.py | 12 ++-- nova/flags.py | 4 ++ nova/tests/test_compute.py | 2 +- nova/tests/test_xenapi.py | 72 +++++++++++++--------- nova/tests/xenapi/stubs.py | 4 +- nova/virt/driver.py | 13 ++-- nova/virt/xenapi/vm_utils.py | 4 ++ nova/virt/xenapi/vmops.py | 71 +++++++++++++-------- nova/virt/xenapi_conn.py | 17 +++-- plugins/xenserver/xenapi/etc/xapi.d/plugins/glance | 6 +- .../xenserver/xenapi/etc/xapi.d/plugins/migration | 11 ++-- 11 files changed, 139 insertions(+), 77 deletions(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 7915830ec..6639726fa 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -844,7 +844,8 @@ class ComputeManager(manager.SchedulerDependentManager): migration_ref.instance_uuid) network_info = self._get_instance_nw_info(context, instance_ref) - self.driver.destroy(instance_ref, network_info) + self.driver.confirm_migration(migration_ref, instance_ref, network_info) + usage_info = utils.usage_from_instance(instance_ref) notifier.notify('compute.%s' % self.host, 'compute.instance.resize.confirm', @@ -899,7 +900,7 @@ class ComputeManager(manager.SchedulerDependentManager): local_gb=instance_type['local_gb'], instance_type_id=instance_type['id']) - self.driver.revert_migration(instance_ref) + self.driver.finish_revert_migration(instance_ref) self.db.migration_update(context, migration_id, {'status': 'reverted'}) usage_info = utils.usage_from_instance(instance_ref) @@ -923,7 +924,8 @@ class ComputeManager(manager.SchedulerDependentManager): # of the instance down instance_ref = self.db.instance_get_by_uuid(context, instance_id) - if instance_ref['host'] == FLAGS.host: + same_host = instance_ref['host'] == FLAGS.host + if same_host and not FLAGS.allow_resize_to_same_host: self._instance_update(context, instance_id, vm_state=vm_states.ERROR) @@ -1019,8 +1021,8 @@ class ComputeManager(manager.SchedulerDependentManager): instance_ref.uuid) network_info = self._get_instance_nw_info(context, instance_ref) - self.driver.finish_migration(context, instance_ref, disk_info, - network_info, resize_instance) + self.driver.finish_migration(context, migration_ref, instance_ref, + disk_info, network_info, resize_instance) self._instance_update(context, instance_id, diff --git a/nova/flags.py b/nova/flags.py index aa76defe5..e79b280c9 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -429,3 +429,7 @@ DEFINE_list('monkey_patch_modules', 'nova.compute.api:nova.notifier.api.notify_decorator'], 'Module list representing monkey ' 'patched module and decorator') + +DEFINE_bool('allow_resize_to_same_host', False, + 'Allow destination machine to match source for resize. Useful' + ' when testing in environments with only one host machine.') diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 4d463572b..1ecd26ce6 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -568,7 +568,7 @@ class ComputeTestCase(test.TestCase): pass self.stubs.Set(self.compute.driver, 'finish_migration', fake) - self.stubs.Set(self.compute.driver, 'revert_migration', fake) + self.stubs.Set(self.compute.driver, 'finish_revert_migration', fake) self.stubs.Set(self.compute.network_api, 'get_instance_nw_info', fake) self.compute.run_instance(self.context, instance_id) diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 4a83d139e..4541a21cb 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -76,7 +76,7 @@ class XenAPIVolumeTestCase(test.TestCase): db_fakes.stub_out_db_instance_api(self.stubs) stubs.stub_out_get_target(self.stubs) xenapi_fake.reset() - self.values = {'id': 1, + self.instance_values = {'id': 1, 'project_id': self.user_id, 'user_id': 'fake', 'image_ref': 1, @@ -132,7 +132,7 @@ class XenAPIVolumeTestCase(test.TestCase): stubs.stubout_session(self.stubs, stubs.FakeSessionForVolumeTests) conn = xenapi_conn.get_connection(False) volume = self._create_volume() - instance = db.instance_create(self.context, self.values) + instance = db.instance_create(self.context, self.instance_values) vm = xenapi_fake.create_vm(instance.name, 'Running') result = conn.attach_volume(instance.name, volume['id'], '/dev/sdc') @@ -152,7 +152,7 @@ class XenAPIVolumeTestCase(test.TestCase): stubs.FakeSessionForVolumeFailedTests) conn = xenapi_conn.get_connection(False) volume = self._create_volume() - instance = db.instance_create(self.context, self.values) + instance = db.instance_create(self.context, self.instance_values) xenapi_fake.create_vm(instance.name, 'Running') self.assertRaises(Exception, conn.attach_volume, @@ -369,7 +369,7 @@ class XenAPIVMTestCase(test.TestCase): create_record=True, empty_dns=False): stubs.stubout_loopingcall_start(self.stubs) if create_record: - values = {'id': instance_id, + instance_values = {'id': instance_id, 'project_id': self.project_id, 'user_id': self.user_id, 'image_ref': image_ref, @@ -378,7 +378,7 @@ class XenAPIVMTestCase(test.TestCase): 'instance_type_id': instance_type_id, 'os_type': os_type, 'architecture': architecture} - instance = db.instance_create(self.context, values) + instance = db.instance_create(self.context, instance_values) else: instance = db.instance_get(self.context, instance_id) network_info = [({'bridge': 'fa0', 'id': 0, 'injected': True}, @@ -622,28 +622,28 @@ class XenAPIVMTestCase(test.TestCase): # Ensure that it will not unrescue a non-rescued instance. self.assertRaises(Exception, conn.unrescue, instance, None) - def test_revert_migration(self): + def test_finish_revert_migration(self): instance = self._create_instance() class VMOpsMock(): def __init__(self): - self.revert_migration_called = False + self.finish_revert_migration_called = False - def revert_migration(self, instance): - self.revert_migration_called = True + def finish_revert_migration(self, instance): + self.finish_revert_migration_called = True stubs.stubout_session(self.stubs, stubs.FakeSessionForMigrationTests) conn = xenapi_conn.get_connection(False) conn._vmops = VMOpsMock() - conn.revert_migration(instance) - self.assertTrue(conn._vmops.revert_migration_called) + conn.finish_revert_migration(instance) + self.assertTrue(conn._vmops.finish_revert_migration_called) def _create_instance(self, instance_id=1, spawn=True): """Creates and spawns a test instance.""" stubs.stubout_loopingcall_start(self.stubs) - values = { + instance_values = { 'id': instance_id, 'project_id': self.project_id, 'user_id': self.user_id, @@ -653,7 +653,7 @@ class XenAPIVMTestCase(test.TestCase): 'instance_type_id': '3', # m1.large 'os_type': 'linux', 'architecture': 'x86-64'} - instance = db.instance_create(self.context, values) + instance = db.instance_create(self.context, instance_values) network_info = [({'bridge': 'fa0', 'id': 0, 'injected': False}, {'broadcast': '192.168.0.255', 'dns': ['192.168.0.1'], @@ -731,7 +731,7 @@ class XenAPIMigrateInstance(test.TestCase): self.user_id = 'fake' self.project_id = 'fake' self.context = context.RequestContext(self.user_id, self.project_id) - self.values = {'id': 1, + self.instance_values = {'id': 1, 'project_id': self.project_id, 'user_id': self.user_id, 'image_ref': 1, @@ -742,22 +742,34 @@ class XenAPIMigrateInstance(test.TestCase): 'os_type': 'linux', 'architecture': 'x86-64'} + migration_values = { + 'source_compute': 'nova-compute', + 'dest_compute': 'nova-compute', + 'dest_host': '10.127.5.114', + 'status': 'post-migrating', + 'instance_uuid': '15f23e6a-cc6e-4d22-b651-d9bdaac316f7', + 'old_instance_type_id': 5, + 'new_instance_type_id': 1 + } + self.migration = db.migration_create( + context.get_admin_context(), migration_values) + fake_utils.stub_out_utils_execute(self.stubs) stubs.stub_out_migration_methods(self.stubs) stubs.stubout_get_this_vm_uuid(self.stubs) glance_stubs.stubout_glance_client(self.stubs) def test_migrate_disk_and_power_off(self): - instance = db.instance_create(self.context, self.values) + 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') def test_revert_migrate(self): - instance = db.instance_create(self.context, self.values) + instance = db.instance_create(self.context, self.instance_values) self.called = False self.fake_vm_start_called = False - self.fake_revert_migration_called = False + self.fake_finish_revert_migration_called = False def fake_vm_start(*args, **kwargs): self.fake_vm_start_called = True @@ -765,13 +777,13 @@ class XenAPIMigrateInstance(test.TestCase): def fake_vdi_resize(*args, **kwargs): self.called = True - def fake_revert_migration(*args, **kwargs): - self.fake_revert_migration_called = True + def fake_finish_revert_migration(*args, **kwargs): + self.fake_finish_revert_migration_called = True self.stubs.Set(stubs.FakeSessionForMigrationTests, "VDI_resize_online", fake_vdi_resize) self.stubs.Set(vmops.VMOps, '_start', fake_vm_start) - self.stubs.Set(vmops.VMOps, 'revert_migration', fake_revert_migration) + self.stubs.Set(vmops.VMOps, 'finish_revert_migration', fake_finish_revert_migration) stubs.stubout_session(self.stubs, stubs.FakeSessionForMigrationTests) stubs.stubout_loopingcall_start(self.stubs) @@ -790,17 +802,17 @@ class XenAPIMigrateInstance(test.TestCase): 'label': 'fake', 'mac': 'DE:AD:BE:EF:00:00', 'rxtx_cap': 3})] - conn.finish_migration(self.context, instance, + conn.finish_migration(self.context, self.migration, instance, dict(base_copy='hurr', cow='durr'), network_info, resize_instance=True) self.assertEqual(self.called, True) self.assertEqual(self.fake_vm_start_called, True) - conn.revert_migration(instance) - self.assertEqual(self.fake_revert_migration_called, True) + conn.finish_revert_migration(instance) + self.assertEqual(self.fake_finish_revert_migration_called, True) def test_finish_migrate(self): - instance = db.instance_create(self.context, self.values) + instance = db.instance_create(self.context, self.instance_values) self.called = False self.fake_vm_start_called = False @@ -831,7 +843,7 @@ class XenAPIMigrateInstance(test.TestCase): 'label': 'fake', 'mac': 'DE:AD:BE:EF:00:00', 'rxtx_cap': 3})] - conn.finish_migration(self.context, instance, + conn.finish_migration(self.context, self.migration, instance, dict(base_copy='hurr', cow='durr'), network_info, resize_instance=True) self.assertEqual(self.called, True) @@ -840,8 +852,8 @@ class XenAPIMigrateInstance(test.TestCase): def test_finish_migrate_no_local_storage(self): tiny_type_id = \ instance_types.get_instance_type_by_name('m1.tiny')['id'] - self.values.update({'instance_type_id': tiny_type_id, 'local_gb': 0}) - instance = db.instance_create(self.context, self.values) + self.instance_values.update({'instance_type_id': tiny_type_id, 'local_gb': 0}) + instance = db.instance_create(self.context, self.instance_values) def fake_vdi_resize(*args, **kwargs): raise Exception("This shouldn't be called") @@ -865,12 +877,12 @@ class XenAPIMigrateInstance(test.TestCase): 'label': 'fake', 'mac': 'DE:AD:BE:EF:00:00', 'rxtx_cap': 3})] - conn.finish_migration(self.context, instance, + conn.finish_migration(self.context, self.migration, instance, dict(base_copy='hurr', cow='durr'), network_info, resize_instance=True) def test_finish_migrate_no_resize_vdi(self): - instance = db.instance_create(self.context, self.values) + instance = db.instance_create(self.context, self.instance_values) def fake_vdi_resize(*args, **kwargs): raise Exception("This shouldn't be called") @@ -896,7 +908,7 @@ class XenAPIMigrateInstance(test.TestCase): 'rxtx_cap': 3})] # Resize instance would be determined by the compute call - conn.finish_migration(self.context, instance, + conn.finish_migration(self.context, self.migration, instance, dict(base_copy='hurr', cow='durr'), network_info, resize_instance=False) diff --git a/nova/tests/xenapi/stubs.py b/nova/tests/xenapi/stubs.py index 647a4c1df..a1f6a7788 100644 --- a/nova/tests/xenapi/stubs.py +++ b/nova/tests/xenapi/stubs.py @@ -297,7 +297,7 @@ class FakeSessionForMigrationTests(fake.SessionBase): def stub_out_migration_methods(stubs): - def fake_get_snapshot(self, instance): + def fake_create_snapshot(self, instance): return 'vm_ref', dict(image='foo', snap='bar') @classmethod @@ -327,7 +327,7 @@ def stub_out_migration_methods(stubs): stubs.Set(vmops.VMOps, '_destroy', fake_destroy) stubs.Set(vm_utils.VMHelper, 'scan_default_sr', fake_sr) stubs.Set(vm_utils.VMHelper, 'scan_sr', fake_sr) - stubs.Set(vmops.VMOps, '_get_snapshot', fake_get_snapshot) + stubs.Set(vmops.VMOps, '_create_snapshot', fake_create_snapshot) stubs.Set(vm_utils.VMHelper, 'get_vdi_for_vm_safely', fake_get_vdi) stubs.Set(xenapi_conn.XenAPISession, 'wait_for_task', lambda x, y, z: None) stubs.Set(vm_utils.VMHelper, 'get_sr_path', fake_get_sr_path) diff --git a/nova/virt/driver.py b/nova/virt/driver.py index 301346c6b..8b9d3053f 100644 --- a/nova/virt/driver.py +++ b/nova/virt/driver.py @@ -244,8 +244,8 @@ class ComputeDriver(object): """ raise NotImplementedError() - def finish_migration(self, context, instance, disk_info, network_info, - resize_instance): + def finish_migration(self, context, migration, instance, disk_info, + network_info, resize_instance): """Completes a resize, turning on the migrated instance :param network_info: @@ -253,8 +253,13 @@ class ComputeDriver(object): """ raise NotImplementedError() - def revert_migration(self, instance): - """Reverts a resize, powering back on the instance""" + def confirm_migration(self, migration, instance, network_info): + """Confirms a resize, destroying the source VM""" + # TODO(Vek): Need to pass context in for access to auth_token + raise NotImplementedError() + + def finish_revert_migration(self, instance): + """Finish reverting a resize, powering back on the instance""" # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 302238c98..5778ca1c2 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -694,6 +694,10 @@ class VMHelper(HelperBase): return is_pv + @classmethod + def set_vm_name_label(cls, session, vm_ref, name_label): + session.get_xenapi().VM.set_name_label(vm_ref, name_label) + @classmethod def lookup(cls, session, name_label): """Look the instance i up, and returns it if available""" diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index e7296e32c..e6129436a 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -109,14 +109,29 @@ class VMOps(object): instance_infos.append(instance_info) return instance_infos - def revert_migration(self, instance): - vm_ref = VMHelper.lookup(self._session, instance.name) + def confirm_migration(self, migration, instance, network_info): + name_label = self._get_orig_vm_name_label(instance) + vm_ref = VMHelper.lookup(self._session, name_label) + return self._destroy(instance, vm_ref, network_info, shutdown=False) + + def finish_revert_migration(self, instance): + # 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) + vm_ref = VMHelper.lookup(self._session, name_label) + + # Remove the '-orig' suffix (which was added in case the resized VM + # ends up on the source host, common during testing) + name_label = instance.name + VMHelper.set_vm_name_label(self._session, vm_ref, name_label) + self._start(instance, vm_ref) - def finish_migration(self, context, instance, disk_info, network_info, - resize_instance): + def finish_migration(self, context, migration, instance, disk_info, + network_info, resize_instance): vdi_uuid = self.link_disks(instance, disk_info['base_copy'], disk_info['cow']) + vm_ref = self._create_vm(context, instance, [dict(vdi_type='os', vdi_uuid=vdi_uuid)], network_info) @@ -549,7 +564,8 @@ class VMOps(object): """ template_vm_ref = None try: - template_vm_ref, template_vdi_uuids = self._get_snapshot(instance) + template_vm_ref, template_vdi_uuids =\ + self._create_snapshot(instance) # call plugin to ship snapshot off to glance VMHelper.upload_image(context, self._session, instance, template_vdi_uuids, image_id) @@ -560,7 +576,7 @@ class VMOps(object): logging.debug(_("Finished snapshot and upload for VM %s"), instance) - def _get_snapshot(self, instance): + def _create_snapshot(self, instance): #TODO(sirp): Add quiesce and VSS locking support when Windows support # is added @@ -577,6 +593,20 @@ class VMOps(object): % locals()) return + def _migrate_vhd(self, instance, vdi_uuid, dest, sr_path): + instance_id = instance.id + params = {'host': dest, + 'vdi_uuid': vdi_uuid, + 'instance_id': instance_id, + 'sr_path': sr_path} + + task = self._session.async_call_plugin('migration', 'transfer_vhd', + {'params': pickle.dumps(params)}) + self._session.wait_for_task(task, instance_id) + + def _get_orig_vm_name_label(self, instance): + return instance.name + '-orig' + def migrate_disk_and_power_off(self, instance, dest): """Copies a VHD from one host machine to another. @@ -594,34 +624,27 @@ class VMOps(object): base_copy_uuid = cow_uuid = None template_vdi_uuids = template_vm_ref = None try: - # transfer the base copy - template_vm_ref, template_vdi_uuids = self._get_snapshot(instance) + template_vm_ref, template_vdi_uuids =\ + self._create_snapshot(instance) base_copy_uuid = template_vdi_uuids['image'] vdi_ref, vm_vdi_rec = \ VMHelper.get_vdi_for_vm_safely(self._session, vm_ref) cow_uuid = vm_vdi_rec['uuid'] - params = {'host': dest, - 'vdi_uuid': base_copy_uuid, - 'instance_id': instance.id, - 'sr_path': VMHelper.get_sr_path(self._session)} + sr_path = VMHelper.get_sr_path(self._session) - task = self._session.async_call_plugin('migration', 'transfer_vhd', - {'params': pickle.dumps(params)}) - self._session.wait_for_task(task, instance.id) + # transfer the base copy + self._migrate_vhd(instance, base_copy_uuid, dest, sr_path) # Now power down the instance and transfer the COW VHD self._shutdown(instance, vm_ref, hard=False) + self._migrate_vhd(instance, cow_uuid, dest, sr_path) - params = {'host': dest, - 'vdi_uuid': cow_uuid, - 'instance_id': instance.id, - 'sr_path': VMHelper.get_sr_path(self._session), } - - task = self._session.async_call_plugin('migration', 'transfer_vhd', - {'params': pickle.dumps(params)}) - self._session.wait_for_task(task, instance.id) - + # 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) + VMHelper.set_vm_name_label(self._session, vm_ref, name_label) finally: if template_vm_ref: self._destroy(instance, template_vm_ref, diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index f6dbc19f8..306991bf3 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -189,14 +189,19 @@ class XenAPIConnection(driver.ComputeDriver): """Create VM instance""" self._vmops.spawn(context, instance, network_info) - def revert_migration(self, instance): - """Reverts a resize, powering back on the instance""" - self._vmops.revert_migration(instance) + def confirm_migration(self, migration, instance, network_info): + """Confirms a resize, destroying the source VM""" + # TODO(Vek): Need to pass context in for access to auth_token + self._vmops.confirm_migration(migration, instance, network_info) - def finish_migration(self, context, instance, disk_info, network_info, - resize_instance=False): + def finish_revert_migration(self, instance): + """Finish reverting a resize, powering back on the instance""" + self._vmops.finish_revert_migration(instance) + + def finish_migration(self, context, migration, instance, disk_info, + network_info, resize_instance=False): """Completes a resize, turning on the migrated instance""" - self._vmops.finish_migration(context, instance, disk_info, + self._vmops.finish_migration(context, migration, instance, disk_info, network_info, resize_instance) def snapshot(self, context, instance, image_id): diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/glance b/plugins/xenserver/xenapi/etc/xapi.d/plugins/glance index 1a9ac37e9..49601b1cd 100755 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/glance +++ b/plugins/xenserver/xenapi/etc/xapi.d/plugins/glance @@ -211,7 +211,11 @@ def _import_vhds(sr_path, staging_path, uuid_stack): snap_info = prepare_if_exists(staging_path, 'snap.vhd', image_info[0]) if snap_info: - paths_to_move.append(snap_info[0]) + # NOTE(sirp): this is an insert rather than an append since the + # 'snapshot' vhd needs to be copied into the SR before the base copy. + # If it doesn't, then there is a possibliity that snapwatchd will + # delete the base_copy since it is an unreferenced parent. + paths_to_move.insert(snap_info[0]) # We return this snap as the VDI instead of image.vhd vdi_return_list.append(dict(vdi_type="os", vdi_uuid=snap_info[1])) else: diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/migration b/plugins/xenserver/xenapi/etc/xapi.d/plugins/migration index ac1c50ad9..4ec3dc7af 100755 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/migration +++ b/plugins/xenserver/xenapi/etc/xapi.d/plugins/migration @@ -48,7 +48,7 @@ def move_vhds_into_sr(session, args): # Discover the copied VHDs locally, and then set up paths to copy # them to under the SR - source_image_path = "%s/instance%d" % ('/images/', instance_id) + source_image_path = "/images/instance%d" % instance_id source_base_copy_path = "%s/%s.vhd" % (source_image_path, old_base_copy_uuid) source_cow_path = "%s/%s.vhd" % (source_image_path, old_cow_uuid) @@ -74,9 +74,12 @@ def move_vhds_into_sr(session, args): (new_cow_path, new_base_copy_path)) subprocess.call(shlex.split('/usr/sbin/vhd-util modify -n %s -p %s' % (new_cow_path, new_base_copy_path))) + logging.debug('Moving VHDs into SR %s' % sr_path) - shutil.move("%s/%s.vhd" % (temp_vhd_path, new_base_copy_uuid), sr_path) - shutil.move("%s/%s.vhd" % (temp_vhd_path, new_cow_uuid), sr_path) + # NOTE(sirp): COW should be copied before base_copy to avoid snapwatchd + # GC'ing an unreferenced base copy VDI + shutil.move(new_cow_path, sr_path) + shutil.move(new_base_copy_path, sr_path) logging.debug('Cleaning up temporary SR path %s' % temp_vhd_path) os.rmdir(temp_vhd_path) @@ -93,7 +96,7 @@ def transfer_vhd(session, args): vhd_path = "%s.vhd" % vdi_uuid source_path = "%s/%s" % (sr_path, vhd_path) - dest_path = '%s:%sinstance%d/' % (host, '/images/', instance_id) + dest_path = '%s:/images/instance%d/' % (host, instance_id) logging.debug("Preparing to transmit %s to %s" % (source_path, dest_path)) -- cgit From 80462f3e446964560198e77af6903d84a05cab87 Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Fri, 16 Sep 2011 11:17:44 -0500 Subject: Fixing tests, PEP8 failures --- nova/compute/manager.py | 3 ++- nova/tests/test_xenapi.py | 6 ++++-- nova/tests/xenapi/stubs.py | 3 +++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 6639726fa..dc67252ca 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -844,7 +844,8 @@ class ComputeManager(manager.SchedulerDependentManager): migration_ref.instance_uuid) network_info = self._get_instance_nw_info(context, instance_ref) - self.driver.confirm_migration(migration_ref, instance_ref, network_info) + self.driver.confirm_migration( + migration_ref, instance_ref, network_info) usage_info = utils.usage_from_instance(instance_ref) notifier.notify('compute.%s' % self.host, diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 4541a21cb..6b5bb240c 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -783,7 +783,8 @@ class XenAPIMigrateInstance(test.TestCase): self.stubs.Set(stubs.FakeSessionForMigrationTests, "VDI_resize_online", fake_vdi_resize) self.stubs.Set(vmops.VMOps, '_start', fake_vm_start) - self.stubs.Set(vmops.VMOps, 'finish_revert_migration', fake_finish_revert_migration) + self.stubs.Set(vmops.VMOps, 'finish_revert_migration', + fake_finish_revert_migration) stubs.stubout_session(self.stubs, stubs.FakeSessionForMigrationTests) stubs.stubout_loopingcall_start(self.stubs) @@ -852,7 +853,8 @@ class XenAPIMigrateInstance(test.TestCase): def test_finish_migrate_no_local_storage(self): tiny_type_id = \ instance_types.get_instance_type_by_name('m1.tiny')['id'] - self.instance_values.update({'instance_type_id': tiny_type_id, 'local_gb': 0}) + self.instance_values.update({'instance_type_id': tiny_type_id, + 'local_gb': 0}) instance = db.instance_create(self.context, self.instance_values) def fake_vdi_resize(*args, **kwargs): diff --git a/nova/tests/xenapi/stubs.py b/nova/tests/xenapi/stubs.py index a1f6a7788..aee279920 100644 --- a/nova/tests/xenapi/stubs.py +++ b/nova/tests/xenapi/stubs.py @@ -295,6 +295,9 @@ class FakeSessionForMigrationTests(fake.SessionBase): vm['is_control_domain'] = False vm['domid'] = random.randrange(1, 1 << 16) + def VM_set_name_label(self, *args): + pass + def stub_out_migration_methods(stubs): def fake_create_snapshot(self, instance): -- cgit 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 From d6afa7c9d9897f5cdd54a54361c41d2665afc56a Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Fri, 16 Sep 2011 18:34:21 +0000 Subject: Zero out the progress when beginning a resize --- nova/virt/xenapi/vmops.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index bf746a8b4..c3f2ee896 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -623,6 +623,11 @@ class VMOps(object): :param disk_type: values are 'primary' or 'cow'. """ + # 0. Zero out the progress to begin + self._update_instance_progress(context, instance, + step=0, + total_steps=RESIZE_TOTAL_STEPS) + vm_ref = VMHelper.lookup(self._session, instance.name) # The primary VDI becomes the COW after the snapshot, and we can -- cgit From f31b37c80a9ef0c4ba07940897388094e5ed052c Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Fri, 16 Sep 2011 18:37:53 +0000 Subject: Pep8 Fix --- nova/virt/xenapi/vmops.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index c3f2ee896..74c1197ae 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -147,7 +147,6 @@ class VMOps(object): step=5, total_steps=RESIZE_TOTAL_STEPS) - def _start(self, instance, vm_ref=None): """Power on a VM instance""" if not vm_ref: @@ -657,7 +656,7 @@ class VMOps(object): step=2, total_steps=RESIZE_TOTAL_STEPS) - # 3. Now power down the instance + # 3. Now power down the instance self._shutdown(instance, vm_ref, hard=False) self._update_instance_progress(context, instance, step=3, -- cgit From 5652459c41534bed1d19a794659146ef99941f0f Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Fri, 16 Sep 2011 20:07:38 +0000 Subject: Fixing list prepend --- plugins/xenserver/xenapi/etc/xapi.d/plugins/glance | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/glance b/plugins/xenserver/xenapi/etc/xapi.d/plugins/glance index 49601b1cd..950b78707 100755 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/glance +++ b/plugins/xenserver/xenapi/etc/xapi.d/plugins/glance @@ -215,7 +215,7 @@ def _import_vhds(sr_path, staging_path, uuid_stack): # 'snapshot' vhd needs to be copied into the SR before the base copy. # If it doesn't, then there is a possibliity that snapwatchd will # delete the base_copy since it is an unreferenced parent. - paths_to_move.insert(snap_info[0]) + paths_to_move.insert(0, snap_info[0]) # We return this snap as the VDI instead of image.vhd vdi_return_list.append(dict(vdi_type="os", vdi_uuid=snap_info[1])) else: -- cgit From 768b219eb21c03681924f5dca36fd59e31291ea7 Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Mon, 19 Sep 2011 19:32:09 +0000 Subject: Renaming progress migration to 47 --- .../versions/046_add_instances_progress.py | 43 ---------------------- .../versions/047_add_instances_progress.py | 43 ++++++++++++++++++++++ 2 files changed, 43 insertions(+), 43 deletions(-) delete mode 100644 nova/db/sqlalchemy/migrate_repo/versions/046_add_instances_progress.py create mode 100644 nova/db/sqlalchemy/migrate_repo/versions/047_add_instances_progress.py diff --git a/nova/db/sqlalchemy/migrate_repo/versions/046_add_instances_progress.py b/nova/db/sqlalchemy/migrate_repo/versions/046_add_instances_progress.py deleted file mode 100644 index d23d52e80..000000000 --- a/nova/db/sqlalchemy/migrate_repo/versions/046_add_instances_progress.py +++ /dev/null @@ -1,43 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2010 OpenStack LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from sqlalchemy import * -from migrate import * - -from nova import log as logging - -meta = MetaData() - -instances = Table('instances', meta, - Column("id", Integer(), primary_key=True, nullable=False)) - -# Add progress column to instances table -progress = Column('progress', Integer()) - - -def upgrade(migrate_engine): - meta.bind = migrate_engine - - try: - instances.create_column(progress) - except Exception: - logging.error(_("progress column not added to instances table")) - raise - - -def downgrade(migrate_engine): - meta.bind = migrate_engine - instances.drop_column(progress) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/047_add_instances_progress.py b/nova/db/sqlalchemy/migrate_repo/versions/047_add_instances_progress.py new file mode 100644 index 000000000..d23d52e80 --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/047_add_instances_progress.py @@ -0,0 +1,43 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 OpenStack LLC. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from sqlalchemy import * +from migrate import * + +from nova import log as logging + +meta = MetaData() + +instances = Table('instances', meta, + Column("id", Integer(), primary_key=True, nullable=False)) + +# Add progress column to instances table +progress = Column('progress', Integer()) + + +def upgrade(migrate_engine): + meta.bind = migrate_engine + + try: + instances.create_column(progress) + except Exception: + logging.error(_("progress column not added to instances table")) + raise + + +def downgrade(migrate_engine): + meta.bind = migrate_engine + instances.drop_column(progress) -- cgit