diff options
| author | John Garbutt <john.garbutt@citrix.com> | 2012-07-09 15:14:10 +0100 |
|---|---|---|
| committer | John Garbutt <john.garbutt@citrix.com> | 2012-07-17 09:53:01 +0100 |
| commit | 8b667215e82ffb32ace5df6a12dff7ece42e2b82 (patch) | |
| tree | 02fc48da39f6836691e7eb4c83768cd0062755e6 | |
| parent | 500ee77121512fd40f5c2afb885fdc45fbf4b57f (diff) | |
| download | nova-8b667215e82ffb32ace5df6a12dff7ece42e2b82.tar.gz nova-8b667215e82ffb32ace5df6a12dff7ece42e2b82.tar.xz nova-8b667215e82ffb32ace5df6a12dff7ece42e2b82.zip | |
Partially implements blueprint xenapi-live-migration
This is dependent on refactoring of libvirt live migration.
Enables live migration using xenapi:
* works when both hosts are part of the same aggregate
* assumes shared storage is configured as the default SR
Limitations in this version:
* only works for images that don't have an external
ramdisk/kernel
Change-Id: I74fc6aae9a615be7cefb1cf07755764df1af957a
| -rw-r--r-- | nova/tests/test_xenapi.py | 109 | ||||
| -rw-r--r-- | nova/virt/xenapi/driver.py | 83 | ||||
| -rw-r--r-- | nova/virt/xenapi/fake.py | 3 | ||||
| -rw-r--r-- | nova/virt/xenapi/vmops.py | 57 |
4 files changed, 247 insertions, 5 deletions
diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 0b3010f3e..9486f4144 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -2050,3 +2050,112 @@ class VmUtilsTestCase(test.TestCase): auto_disk_config='auto disk config', os_type='os type') self.assertEquals(expected, actual) + + +class XenAPILiveMigrateTestCase(stubs.XenAPITestBase): + """Unit tests for live_migration.""" + def setUp(self): + super(XenAPILiveMigrateTestCase, self).setUp() + self.flags(xenapi_connection_url='test_url', + xenapi_connection_password='test_pass', + firewall_driver='nova.virt.xenapi.firewall.' + 'Dom0IptablesFirewallDriver', + host='host') + db_fakes.stub_out_db_instance_api(self.stubs) + stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests) + self.context = context.get_admin_context() + self.conn = xenapi_conn.XenAPIDriver(False) + + def test_live_migration_calls_vmops(self): + def fake_live_migrate(context, instance_ref, dest, post_method, + recover_method, block_migration): + fake_live_migrate.called = True + self.stubs.Set(self.conn._vmops, "live_migrate", fake_live_migrate) + + self.conn.live_migration(None, None, None, None, None) + self.assertTrue(fake_live_migrate.called) + + def test_pre_live_migration(self): + # ensure method is present + self.conn.pre_live_migration(None, None, None, None) + + def test_post_live_migration_at_destination(self): + # ensure method is present + self.conn.post_live_migration_at_destination(None, None, None, None) + + def test_check_can_live_migrate_raises_on_block_migrate(self): + self.assertRaises(NotImplementedError, + self.conn.check_can_live_migrate_destination, + None, None, True, None) + + def test_check_can_live_migrate_works(self): + class fake_aggregate: + def __init__(self): + self.metadetails = {"host": "test_host_uuid"} + + def fake_aggregate_get_by_host(context, host): + self.assertEqual(FLAGS.host, host) + return fake_aggregate() + + self.stubs.Set(db, "aggregate_get_by_host", + fake_aggregate_get_by_host) + self.conn.check_can_live_migrate_destination(self.context, + {'host': 'host'}, False, False) + + def test_check_can_live_migrate_fails(self): + class fake_aggregate: + def __init__(self): + self.metadetails = {"dest_other": "test_host_uuid"} + + def fake_aggregate_get_by_host(context, host): + self.assertEqual(FLAGS.host, host) + return fake_aggregate() + + self.stubs.Set(db, "aggregate_get_by_host", + fake_aggregate_get_by_host) + self.assertRaises(exception.MigrationError, + self.conn.check_can_live_migrate_destination, + self.context, {'host': 'host'}, None, None) + + def test_live_migration(self): + def fake_get_vm_opaque_ref(instance): + return "fake_vm" + self.stubs.Set(self.conn._vmops, "_get_vm_opaque_ref", + fake_get_vm_opaque_ref) + + def fake_get_host_opaque_ref(context, destination_hostname): + return "fake_host" + self.stubs.Set(self.conn._vmops, "_get_host_opaque_ref", + fake_get_host_opaque_ref) + + def post_method(context, instance, destination_hostname, + block_migration): + post_method.called = True + + self.conn.live_migration(self.conn, None, None, post_method, None) + + self.assertTrue(post_method.called, "post_method.called") + + def test_live_migration_on_failure(self): + def fake_get_vm_opaque_ref(instance): + return "fake_vm" + self.stubs.Set(self.conn._vmops, "_get_vm_opaque_ref", + fake_get_vm_opaque_ref) + + def fake_get_host_opaque_ref(context, destination_hostname): + return "fake_host" + self.stubs.Set(self.conn._vmops, "_get_host_opaque_ref", + fake_get_host_opaque_ref) + + def fake_call_xenapi(*args): + raise NotImplementedError() + self.stubs.Set(self.conn._vmops._session, "call_xenapi", + fake_call_xenapi) + + def recover_method(context, instance, destination_hostname, + block_migration): + recover_method.called = True + + self.assertRaises(NotImplementedError, self.conn.live_migration, + self.conn, None, None, None, recover_method) + self.assertTrue(recover_method.called, "recover_method.called") diff --git a/nova/virt/xenapi/driver.py b/nova/virt/xenapi/driver.py index c7fad6387..2f4059bd3 100644 --- a/nova/virt/xenapi/driver.py +++ b/nova/virt/xenapi/driver.py @@ -411,17 +411,90 @@ class XenAPIDriver(driver.ComputeDriver): db.compute_node_update(ctxt, compute_node_ref[0]['id'], dic) def ensure_filtering_rules_for_instance(self, instance_ref, network_info): - """This method is supported only libvirt.""" # NOTE(salvatore-orlando): it enforces security groups on # host initialization and live migration. - # Live migration is not supported by XenAPI (as of 2011-11-09) # In XenAPI we do not assume instances running upon host initialization return - def live_migration(self, context, instance_ref, dest, + def check_can_live_migrate_destination(self, ctxt, instance_ref, + block_migration=False, disk_over_commit=False): + """Check if it is possible to execute live migration. + + :param context: security context + :param instance_ref: nova.db.sqlalchemy.models.Instance object + :param block_migration: if true, prepare for block migration + :param disk_over_commit: if true, allow disk over commit + + """ + self._vmops.check_can_live_migrate_destination(ctxt, instance_ref, + block_migration, disk_over_commit) + + def check_can_live_migrate_destination_cleanup(self, ctxt, + dest_check_data): + """Do required cleanup on dest host after check_can_live_migrate calls + + :param ctxt: security context + :param disk_over_commit: if true, allow disk over commit + """ + pass + + def check_can_live_migrate_source(self, ctxt, instance_ref, + dest_check_data): + """Check if it is possible to execute live migration. + + This checks if the live migration can succeed, based on the + results from check_can_live_migrate_destination. + + :param context: security context + :param instance_ref: nova.db.sqlalchemy.models.Instance + :param dest_check_data: result of check_can_live_migrate_destination + """ + pass + + def live_migration(self, ctxt, instance_ref, dest, post_method, recover_method, block_migration=False): - """This method is supported only by libvirt.""" - return + """Performs the live migration of the specified instance. + + :params ctxt: security context + :params instance_ref: + nova.db.sqlalchemy.models.Instance object + instance object that is migrated. + :params dest: destination host + :params post_method: + post operation method. + expected nova.compute.manager.post_live_migration. + :params recover_method: + recovery method when any exception occurs. + expected nova.compute.manager.recover_live_migration. + :params block_migration: if true, migrate VM disk. + """ + self._vmops.live_migrate(ctxt, instance_ref, dest, post_method, + recover_method, block_migration) + + def pre_live_migration(self, context, instance_ref, block_device_info, + network_info): + """Preparation live migration. + + :params block_device_info: + It must be the result of _get_instance_volume_bdms() + at compute manager. + """ + # TODO(JohnGarbutt) look again when boot-from-volume hits trunk + pass + + def post_live_migration_at_destination(self, ctxt, instance_ref, + network_info, block_migration): + """Post operation of live migration at destination host. + + :params ctxt: security context + :params instance_ref: + nova.db.sqlalchemy.models.Instance object + instance object that is migrated. + :params network_info: instance network infomation + :params : block_migration: if true, post operation of block_migraiton. + """ + # TODO(JohnGarbutt) look at moving/downloading ramdisk and kernel + pass def unfilter_instance(self, instance_ref, network_info): """Removes security groups configured for an instance.""" diff --git a/nova/virt/xenapi/fake.py b/nova/virt/xenapi/fake.py index 416bf8b8e..cbfa72413 100644 --- a/nova/virt/xenapi/fake.py +++ b/nova/virt/xenapi/fake.py @@ -488,6 +488,9 @@ class SessionBase(object): db_ref['xenstore_data'] = {} db_ref['xenstore_data'][key] = value + def VM_pool_migrate(self, _1, vm_ref, host_ref, options): + pass + def VDI_remove_from_other_config(self, _1, vdi_ref, key): db_ref = _db_content['VDI'][vdi_ref] if not 'other_config' in db_ref: diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 4805ccaf7..95999584c 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -36,6 +36,7 @@ from nova import db from nova import exception from nova import flags from nova.openstack.common import cfg +from nova.openstack.common import excutils from nova.openstack.common import importutils from nova.openstack.common import jsonutils from nova.openstack.common import log as logging @@ -1377,3 +1378,59 @@ class VMOps(object): """Removes filters for each VIF of the specified instance.""" self.firewall_driver.unfilter_instance(instance_ref, network_info=network_info) + + def _get_host_uuid_from_aggregate(self, context, hostname): + current_aggregate = db.aggregate_get_by_host(context, FLAGS.host) + try: + return current_aggregate.metadetails[hostname] + except KeyError: + reason = _('Destination host:%(hostname)s must be in the same ' + 'aggregate as the source server') + raise exception.MigrationError(reason=reason % locals()) + + def _ensure_host_in_aggregate(self, context, hostname): + self._get_host_uuid_from_aggregate(context, hostname) + + def _get_host_opaque_ref(self, context, hostname): + host_uuid = self._get_host_uuid_from_aggregate(context, hostname) + return self._session.call_xenapi("host.get_by_uuid", host_uuid) + + def check_can_live_migrate_destination(self, ctxt, instance_ref, + block_migration=False, + disk_over_commit=False): + """Check if it is possible to execute live migration. + + :param context: security context + :param instance_ref: nova.db.sqlalchemy.models.Instance object + :param block_migration: if true, prepare for block migration + :param disk_over_commit: if true, allow disk over commit + + """ + if block_migration: + #TODO(johngarbutt): XenServer feature coming soon fixes this + raise NotImplementedError() + else: + src = instance_ref['host'] + self._ensure_host_in_aggregate(ctxt, src) + # TODO(johngarbutt) we currently assume + # instance is on a SR shared with other destination + # block migration work will be able to resolve this + + def live_migrate(self, context, instance, destination_hostname, + post_method, recover_method, block_migration): + if block_migration: + #TODO(johngarbutt): see above + raise NotImplementedError() + else: + try: + vm_ref = self._get_vm_opaque_ref(instance) + host_ref = self._get_host_opaque_ref(context, + destination_hostname) + self._session.call_xenapi("VM.pool_migrate", vm_ref, + host_ref, {}) + post_method(context, instance, destination_hostname, + block_migration) + except Exception: + with excutils.save_and_reraise_exception(): + recover_method(context, instance, destination_hostname, + block_migration) |
