summaryrefslogtreecommitdiffstats
path: root/nova
diff options
context:
space:
mode:
authorJohn Garbutt <john.garbutt@citrix.com>2012-07-09 15:14:10 +0100
committerJohn Garbutt <john.garbutt@citrix.com>2012-07-17 09:53:01 +0100
commit8b667215e82ffb32ace5df6a12dff7ece42e2b82 (patch)
tree02fc48da39f6836691e7eb4c83768cd0062755e6 /nova
parent500ee77121512fd40f5c2afb885fdc45fbf4b57f (diff)
downloadnova-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
Diffstat (limited to 'nova')
-rw-r--r--nova/tests/test_xenapi.py109
-rw-r--r--nova/virt/xenapi/driver.py83
-rw-r--r--nova/virt/xenapi/fake.py3
-rw-r--r--nova/virt/xenapi/vmops.py57
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)