summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBob Ball <bob.ball@citrix.com>2013-03-22 10:23:22 +0000
committerGerrit Code Review <review@openstack.org>2013-05-07 14:39:59 +0000
commite507094eef9a7b4a54a04faade8aa95a36fa6d93 (patch)
treecbe4b482c4885b7154960e654b999e1c66e0ae71
parentd9be77ddd487c71729387a2b31f470be56738bd8 (diff)
downloadnova-e507094eef9a7b4a54a04faade8aa95a36fa6d93.tar.gz
nova-e507094eef9a7b4a54a04faade8aa95a36fa6d93.tar.xz
nova-e507094eef9a7b4a54a04faade8aa95a36fa6d93.zip
Enable live block migration when using iSCSI volumes
Fix for bug 1160323. DocImpact Depends on a version of XAPI supporting relax-xsm-sr-check. Currently no release versions support this, so the change at https://github.com/xen-org/xen-api/pull/1116 would need to be applied to the source to enable this. XenServer 6.2 is expected to support this mode, and possibly some future hotfixes for XenServer 6.1. It also depends on a configuration change which must be documented and added to devstack: * Set "relax-xsm-sr-check = true" in /etc/xapi.conf This commit makes the following changes: * Attach the SR on the destination host prior to migrate * Returns the SR-ref from the pre_migrate call (API change) * Populates the XS VDI map with the sr-ref on the destination host * Removes the connection to the SR from the source host post-migrate * Added plugin to determine if iSCSI block migration is enabled Associated tempest test at https://review.openstack.org/#/c/26478/ Change-Id: I917d9cf094190d636f4b9e14f6c8e728ff85af0e Fixes: bug 1160323
-rwxr-xr-xnova/compute/manager.py13
-rw-r--r--nova/tests/compute/test_compute.py2
-rw-r--r--nova/tests/test_xenapi.py139
-rw-r--r--nova/tests/virt/xenapi/test_vmops.py15
-rw-r--r--nova/tests/virt/xenapi/test_volumeops.py68
-rwxr-xr-xnova/virt/xenapi/driver.py7
-rw-r--r--nova/virt/xenapi/vmops.py93
-rw-r--r--nova/virt/xenapi/volumeops.py59
-rw-r--r--plugins/xenserver/xenapi/contrib/rpmbuild/SPECS/openstack-xen-plugins.spec1
-rwxr-xr-xplugins/xenserver/xenapi/etc/xapi.d/plugins/config_file19
10 files changed, 338 insertions, 78 deletions
diff --git a/nova/compute/manager.py b/nova/compute/manager.py
index 70d88117d..edf9da667 100755
--- a/nova/compute/manager.py
+++ b/nova/compute/manager.py
@@ -3224,7 +3224,8 @@ class ComputeManager(manager.SchedulerDependentManager):
raise exception.FixedIpNotFoundForInstance(
instance_uuid=instance['uuid'])
- self.driver.pre_live_migration(context, instance,
+ pre_live_migration_data = self.driver.pre_live_migration(context,
+ instance,
block_device_info,
self._legacy_nw_info(network_info),
migrate_data)
@@ -3246,6 +3247,8 @@ class ComputeManager(manager.SchedulerDependentManager):
if block_migration:
self.driver.pre_block_migration(context, instance, disk)
+ return pre_live_migration_data
+
def live_migration(self, context, dest, instance,
block_migration=False, migrate_data=None):
"""Executing live migration.
@@ -3257,14 +3260,18 @@ class ComputeManager(manager.SchedulerDependentManager):
:param migrate_data: implementation specific params
"""
+ # Create a local copy since we'll be modifying the dictionary
+ migrate_data = dict(migrate_data or {})
try:
if block_migration:
disk = self.driver.get_instance_disk_info(instance['name'])
else:
disk = None
- self.compute_rpcapi.pre_live_migration(context, instance,
- block_migration, disk, dest, migrate_data)
+ pre_migration_data = self.compute_rpcapi.pre_live_migration(
+ context, instance,
+ block_migration, disk, dest, migrate_data)
+ migrate_data['pre_live_migration_result'] = pre_migration_data
except Exception:
with excutils.save_and_reraise_exception():
diff --git a/nova/tests/compute/test_compute.py b/nova/tests/compute/test_compute.py
index f59b876e2..28a0e436a 100644
--- a/nova/tests/compute/test_compute.py
+++ b/nova/tests/compute/test_compute.py
@@ -3497,7 +3497,7 @@ class ComputeTestCase(BaseTestCase):
instance['name']).AndReturn('fake_disk')
self.compute.compute_rpcapi.pre_live_migration(c,
instance, True, 'fake_disk', dest_host,
- None).AndRaise(test.TestingException())
+ {}).AndRaise(test.TestingException())
self.compute._instance_update(c, instance['uuid'],
host=src_host, vm_state=vm_states.ACTIVE,
diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py
index 3490a5dbf..6beb58991 100644
--- a/nova/tests/test_xenapi.py
+++ b/nova/tests/test_xenapi.py
@@ -2825,21 +2825,29 @@ class XenAPILiveMigrateTestCase(stubs.XenAPITestBase):
{}, {},
True, False)
- def test_check_can_live_migrate_source_with_block_migrate(self):
- stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests)
- self.conn = xenapi_conn.XenAPIDriver(fake.FakeVirtAPI(), False)
-
+ def _add_default_live_migrate_stubs(self, conn):
def fake_generate_vdi_map(destination_sr_ref, _vm_ref):
pass
- self.stubs.Set(self.conn._vmops, "_generate_vdi_map",
- fake_generate_vdi_map)
+ def fake_get_iscsi_srs(destination_sr_ref, _vm_ref):
+ return []
def fake_get_vm_opaque_ref(instance):
return "fake_vm"
- self.stubs.Set(self.conn._vmops, "_get_vm_opaque_ref",
+ self.stubs.Set(conn._vmops, "_generate_vdi_map",
+ fake_generate_vdi_map)
+ self.stubs.Set(conn._vmops, "_get_iscsi_srs",
+ fake_get_iscsi_srs)
+ self.stubs.Set(conn._vmops, "_get_vm_opaque_ref",
fake_get_vm_opaque_ref)
+
+ def test_check_can_live_migrate_source_with_block_migrate(self):
+ stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests)
+ self.conn = xenapi_conn.XenAPIDriver(fake.FakeVirtAPI(), False)
+
+ self._add_default_live_migrate_stubs(self.conn)
+
dest_check_data = {'block_migration': True,
'migrate_data': {
'destination_sr_ref': None,
@@ -2850,22 +2858,65 @@ class XenAPILiveMigrateTestCase(stubs.XenAPITestBase):
dest_check_data)
self.assertEqual(dest_check_data, result)
- def test_check_can_live_migrate_source_with_block_migrate_fails(self):
- stubs.stubout_session(self.stubs,
- stubs.FakeSessionForFailedMigrateTests)
+ def test_check_can_live_migrate_source_with_block_migrate_iscsi(self):
+ stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests)
self.conn = xenapi_conn.XenAPIDriver(fake.FakeVirtAPI(), False)
- def fake_generate_vdi_map(destination_sr_ref, _vm_ref):
- pass
+ self._add_default_live_migrate_stubs(self.conn)
- self.stubs.Set(self.conn._vmops, "_generate_vdi_map",
- fake_generate_vdi_map)
+ def fake_get_iscsi_srs(destination_sr_ref, _vm_ref):
+ return ['sr_ref']
+ self.stubs.Set(self.conn._vmops, "_get_iscsi_srs",
+ fake_get_iscsi_srs)
- def fake_get_vm_opaque_ref(instance):
- return "fake_vm"
+ def fake_make_plugin_call(plugin, method, **args):
+ return "true"
+ self.stubs.Set(self.conn._vmops, "_make_plugin_call",
+ fake_make_plugin_call)
- self.stubs.Set(self.conn._vmops, "_get_vm_opaque_ref",
- fake_get_vm_opaque_ref)
+ dest_check_data = {'block_migration': True,
+ 'migrate_data': {
+ 'destination_sr_ref': None,
+ 'migrate_send_data': None
+ }}
+ result = self.conn.check_can_live_migrate_source(self.context,
+ {'host': 'host'},
+ dest_check_data)
+ self.assertEqual(dest_check_data, result)
+
+ def test_check_can_live_migrate_source_with_block_iscsi_fails(self):
+ stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests)
+ self.conn = xenapi_conn.XenAPIDriver(fake.FakeVirtAPI(), False)
+
+ self._add_default_live_migrate_stubs(self.conn)
+
+ def fake_get_iscsi_srs(destination_sr_ref, _vm_ref):
+ return ['sr_ref']
+ self.stubs.Set(self.conn._vmops, "_get_iscsi_srs",
+ fake_get_iscsi_srs)
+
+ def fake_make_plugin_call(plugin, method, **args):
+ return {'returncode': 'error', 'message': 'Plugin not found'}
+ self.stubs.Set(self.conn._vmops, "_make_plugin_call",
+ fake_make_plugin_call)
+
+ dest_check_data = {'block_migration': True,
+ 'migrate_data': {
+ 'destination_sr_ref': None,
+ 'migrate_send_data': None
+ }}
+
+ self.assertRaises(exception.MigrationError,
+ self.conn.check_can_live_migrate_source,
+ self.context, {'host': 'host'},
+ {})
+
+ def test_check_can_live_migrate_source_with_block_migrate_fails(self):
+ stubs.stubout_session(self.stubs,
+ stubs.FakeSessionForFailedMigrateTests)
+ self.conn = xenapi_conn.XenAPIDriver(fake.FakeVirtAPI(), False)
+
+ self._add_default_live_migrate_stubs(self.conn)
dest_check_data = {'block_migration': True,
'migrate_data': {
@@ -2966,28 +3017,46 @@ class XenAPILiveMigrateTestCase(stubs.XenAPITestBase):
stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests)
self.conn = xenapi_conn.XenAPIDriver(fake.FakeVirtAPI(), False)
- def fake_generate_vdi_map(destination_sr_ref, _vm_ref):
- pass
+ self._add_default_live_migrate_stubs(self.conn)
- self.stubs.Set(self.conn._vmops, "_generate_vdi_map",
- fake_generate_vdi_map)
+ def post_method(context, instance, destination_hostname,
+ block_migration):
+ post_method.called = True
- def fake_get_vm_opaque_ref(instance):
- return "fake_vm"
+ # pass block_migration = True and migrate data
+ migrate_data = {"destination_sr_ref": "foo",
+ "migrate_send_data": "bar"}
+ self.conn.live_migration(self.conn, None, None, post_method, None,
+ True, migrate_data)
+ self.assertTrue(post_method.called, "post_method.called")
- self.stubs.Set(self.conn._vmops, "_get_vm_opaque_ref",
- fake_get_vm_opaque_ref)
+ def test_live_migration_block_cleans_srs(self):
+ stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests)
+ self.conn = xenapi_conn.XenAPIDriver(fake.FakeVirtAPI(), False)
+
+ self._add_default_live_migrate_stubs(self.conn)
+
+ def fake_get_iscsi_srs(context, instance):
+ return ['sr_ref']
+ self.stubs.Set(self.conn._vmops, "_get_iscsi_srs",
+ fake_get_iscsi_srs)
+
+ def fake_forget_sr(context, instance):
+ fake_forget_sr.called = True
+ self.stubs.Set(volume_utils, "forget_sr",
+ fake_forget_sr)
def post_method(context, instance, destination_hostname,
block_migration):
post_method.called = True
- # pass block_migration = True and migrate data
migrate_data = {"destination_sr_ref": "foo",
"migrate_send_data": "bar"}
self.conn.live_migration(self.conn, None, None, post_method, None,
True, migrate_data)
+
self.assertTrue(post_method.called, "post_method.called")
+ self.assertTrue(fake_forget_sr.called, "forget_sr.called")
def test_live_migration_with_block_migration_raises_invalid_param(self):
stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests)
@@ -3012,15 +3081,7 @@ class XenAPILiveMigrateTestCase(stubs.XenAPITestBase):
stubs.FakeSessionForFailedMigrateTests)
self.conn = xenapi_conn.XenAPIDriver(fake.FakeVirtAPI(), False)
- 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_generate_vdi_map(destination_sr_ref, _vm_ref):
- pass
- self.stubs.Set(self.conn._vmops, "_generate_vdi_map",
- fake_generate_vdi_map)
+ self._add_default_live_migrate_stubs(self.conn)
def recover_method(context, instance, destination_hostname,
block_migration):
@@ -3046,11 +3107,7 @@ class XenAPILiveMigrateTestCase(stubs.XenAPITestBase):
conn = xenapi_conn.XenAPIDriver(fake.FakeVirtAPI(), False)
- def fake_get_vm_opaque_ref(instance):
- return "fake_vm"
-
- self.stubs.Set(conn._vmops, "_get_vm_opaque_ref",
- fake_get_vm_opaque_ref)
+ self._add_default_live_migrate_stubs(conn)
def fake_generate_vdi_map(destination_sr_ref, _vm_ref):
return fake_vdi_map
diff --git a/nova/tests/virt/xenapi/test_vmops.py b/nova/tests/virt/xenapi/test_vmops.py
index 1acc4d515..18a444f41 100644
--- a/nova/tests/virt/xenapi/test_vmops.py
+++ b/nova/tests/virt/xenapi/test_vmops.py
@@ -151,3 +151,18 @@ class VMOpsTestCase(test.TestCase):
self._vmops._determine_vm_mode(fake_instance, fake_vdis,
fake_disk_type))
self.mox.VerifyAll()
+
+ def test_xsm_sr_check_relaxed_cached(self):
+ self.make_plugin_call_count = 0
+
+ def fake_make_plugin_call(plugin, method, **args):
+ self.make_plugin_call_count = self.make_plugin_call_count + 1
+ return "true"
+
+ self.stubs.Set(self._vmops, "_make_plugin_call",
+ fake_make_plugin_call)
+
+ self.assertTrue(self._vmops._is_xsm_sr_check_relaxed())
+ self.assertTrue(self._vmops._is_xsm_sr_check_relaxed())
+
+ self.assertEqual(self.make_plugin_call_count, 1)
diff --git a/nova/tests/virt/xenapi/test_volumeops.py b/nova/tests/virt/xenapi/test_volumeops.py
index 3497babf2..7ec9eb1ea 100644
--- a/nova/tests/virt/xenapi/test_volumeops.py
+++ b/nova/tests/virt/xenapi/test_volumeops.py
@@ -31,7 +31,7 @@ class VolumeAttachTestCase(test.TestCase):
return side_effect
ops = volumeops.VolumeOps('session')
- self.mox.StubOutWithMock(volumeops.vm_utils, 'vm_ref_or_raise')
+ 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, 'unplug_vbd')
@@ -40,7 +40,7 @@ class VolumeAttachTestCase(test.TestCase):
self.mox.StubOutWithMock(volumeops.volume_utils, 'find_sr_from_vbd')
self.mox.StubOutWithMock(volumeops.volume_utils, 'purge_sr')
- volumeops.vm_utils.vm_ref_or_raise('session', 'instance_1').AndReturn(
+ volumeops.vm_utils.lookup('session', 'instance_1').AndReturn(
'vmref')
volumeops.volume_utils.get_device_number('mountpoint').AndReturn(
@@ -78,6 +78,8 @@ class VolumeAttachTestCase(test.TestCase):
self.mox.StubOutWithMock(volumeops.vm_utils, 'vm_ref_or_raise')
self.mox.StubOutWithMock(volumeops.volume_utils, 'get_device_number')
+ connection_info = dict(driver_volume_type='iscsi', data='conn_data')
+
volumeops.vm_utils.vm_ref_or_raise('session', 'instance_1').AndReturn(
'vmref')
@@ -85,11 +87,12 @@ class VolumeAttachTestCase(test.TestCase):
'devnumber')
ops._connect_volume(
- 'conn_data', 'devnumber', 'instance_1', 'vmref', hotplug=True)
+ connection_info, 'devnumber', 'instance_1', 'vmref',
+ hotplug=True).AndReturn(('sruuid', 'vdiuuid'))
self.mox.ReplayAll()
ops.attach_volume(
- dict(driver_volume_type='iscsi', data='conn_data'),
+ connection_info,
'instance_1', 'mountpoint')
def test_attach_volume_no_hotplug(self):
@@ -98,6 +101,8 @@ class VolumeAttachTestCase(test.TestCase):
self.mox.StubOutWithMock(volumeops.vm_utils, 'vm_ref_or_raise')
self.mox.StubOutWithMock(volumeops.volume_utils, 'get_device_number')
+ connection_info = dict(driver_volume_type='iscsi', data='conn_data')
+
volumeops.vm_utils.vm_ref_or_raise('session', 'instance_1').AndReturn(
'vmref')
@@ -105,11 +110,12 @@ class VolumeAttachTestCase(test.TestCase):
'devnumber')
ops._connect_volume(
- 'conn_data', 'devnumber', 'instance_1', 'vmref', hotplug=False)
+ connection_info, 'devnumber', 'instance_1', 'vmref',
+ hotplug=False).AndReturn(('sruuid', 'vdiuuid'))
self.mox.ReplayAll()
ops.attach_volume(
- dict(driver_volume_type='iscsi', data='conn_data'),
+ connection_info,
'instance_1', 'mountpoint', hotplug=False)
def test_connect_volume_no_hotplug(self):
@@ -124,6 +130,8 @@ class VolumeAttachTestCase(test.TestCase):
vdi_ref = 'vdi_ref'
vbd_ref = 'vbd_ref'
connection_data = {'vdi_uuid': vdi_uuid}
+ connection_info = {'data': connection_data,
+ 'driver_volume_type': 'iscsi'}
vm_ref = 'vm_ref'
dev_number = 1
@@ -160,7 +168,53 @@ class VolumeAttachTestCase(test.TestCase):
self.mox.ReplayAll()
- ops._connect_volume(connection_data, dev_number, instance_name,
+ ops._connect_volume(connection_info, dev_number, instance_name,
vm_ref, hotplug=False)
self.assertEquals(False, called['VBD.plug'])
+
+ def test_connect_volume(self):
+ session = stubs.FakeSessionForVolumeTests('fake_uri')
+ ops = volumeops.VolumeOps(session)
+ sr_uuid = '1'
+ sr_label = 'Disk-for:None'
+ sr_params = ''
+ sr_ref = 'sr_ref'
+ vdi_uuid = '2'
+ vdi_ref = 'vdi_ref'
+ vbd_ref = 'vbd_ref'
+ connection_data = {'vdi_uuid': vdi_uuid}
+ connection_info = {'data': connection_data,
+ 'driver_volume_type': 'iscsi'}
+
+ called = collections.defaultdict(bool)
+
+ def fake_call_xenapi(self, method, *args, **kwargs):
+ called[method] = True
+
+ self.stubs.Set(ops._session, 'call_xenapi', fake_call_xenapi)
+
+ self.mox.StubOutWithMock(volumeops.volume_utils, 'parse_sr_info')
+ volumeops.volume_utils.parse_sr_info(
+ connection_data, sr_label).AndReturn(
+ tuple([sr_uuid, sr_label, sr_params]))
+
+ self.mox.StubOutWithMock(
+ volumeops.volume_utils, 'find_sr_by_uuid')
+ volumeops.volume_utils.find_sr_by_uuid(session, sr_uuid).AndReturn(
+ None)
+
+ self.mox.StubOutWithMock(
+ volumeops.volume_utils, 'introduce_sr')
+ volumeops.volume_utils.introduce_sr(
+ session, sr_uuid, sr_label, sr_params).AndReturn(sr_ref)
+
+ self.mox.StubOutWithMock(volumeops.volume_utils, 'introduce_vdi')
+ volumeops.volume_utils.introduce_vdi(
+ session, sr_ref, vdi_uuid=vdi_uuid).AndReturn(vdi_ref)
+
+ self.mox.ReplayAll()
+
+ ops.connect_volume(connection_info)
+
+ self.assertEquals(False, called['VBD.plug'])
diff --git a/nova/virt/xenapi/driver.py b/nova/virt/xenapi/driver.py
index 074b43f22..02b849a99 100755
--- a/nova/virt/xenapi/driver.py
+++ b/nova/virt/xenapi/driver.py
@@ -372,7 +372,7 @@ class XenAPIDriver(driver.ComputeDriver):
mountpoint)
def detach_volume(self, connection_info, instance, mountpoint):
- """Detach volume storage to VM instance."""
+ """Detach volume storage from VM instance."""
return self._volumeops.detach_volume(connection_info,
instance['name'],
mountpoint)
@@ -504,7 +504,10 @@ class XenAPIDriver(driver.ComputeDriver):
at compute manager.
"""
# TODO(JohnGarbutt) look again when boot-from-volume hits trunk
- pass
+ pre_live_migration_result = {}
+ pre_live_migration_result['sr_uuid_map'] = \
+ self._vmops.attach_block_device_volumes(block_device_info)
+ return pre_live_migration_result
def post_live_migration_at_destination(self, ctxt, instance_ref,
network_info, block_migration,
diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py
index e7dd3cff0..29879c1d7 100644
--- a/nova/virt/xenapi/vmops.py
+++ b/nova/virt/xenapi/vmops.py
@@ -1549,15 +1549,17 @@ class VMOps(object):
return self._make_plugin_call('xenstore.py', 'delete_record', instance,
vm_ref=vm_ref, path=path)
- def _make_plugin_call(self, plugin, method, instance, vm_ref=None,
+ def _make_plugin_call(self, plugin, method, instance=None, vm_ref=None,
**addl_args):
"""
Abstracts out the process of calling a method of a xenapi plugin.
Any errors raised by the plugin will in turn raise a RuntimeError here.
"""
- vm_ref = vm_ref or self._get_vm_opaque_ref(instance)
- vm_rec = self._session.call_xenapi("VM.get_record", vm_ref)
- args = {'dom_id': vm_rec['domid']}
+ args = {}
+ if instance or vm_ref:
+ vm_ref = vm_ref or self._get_vm_opaque_ref(instance)
+ vm_rec = self._session.call_xenapi("VM.get_record", vm_ref)
+ args['dom_id'] = vm_rec['domid']
args.update(addl_args)
try:
return self._session.call_plugin(plugin, method, args)
@@ -1660,6 +1662,21 @@ class VMOps(object):
raise exception.MigrationPreCheckError(reason=msg)
return migrate_data
+ def _get_iscsi_srs(self, ctxt, instance_ref):
+ vm_ref = self._get_vm_opaque_ref(instance_ref)
+ vbd_refs = self._session.call_xenapi("VM.get_VBDs", vm_ref)
+
+ iscsi_srs = []
+
+ for vbd_ref in vbd_refs:
+ vdi_ref = self._session.call_xenapi("VBD.get_VDI", vbd_ref)
+ # Check if it's on an iSCSI SR
+ sr_ref = self._session.call_xenapi("VDI.get_SR", vdi_ref)
+ if self._session.call_xenapi("SR.get_type", sr_ref) == 'iscsi':
+ iscsi_srs.append(sr_ref)
+
+ return iscsi_srs
+
def check_can_live_migrate_destination(self, ctxt, instance_ref,
block_migration=False,
disk_over_commit=False):
@@ -1687,6 +1704,20 @@ class VMOps(object):
# block migration work will be able to resolve this
return dest_check_data
+ def _is_xsm_sr_check_relaxed(self):
+ try:
+ return self.cached_xsm_sr_relaxed
+ except AttributeError:
+ config_value = None
+ try:
+ config_value = self._make_plugin_call('config_file',
+ 'get_val',
+ key='relax-xsm-sr-check')
+ except Exception as exc:
+ LOG.exception(exc)
+ self.cached_xsm_sr_relaxed = config_value == "true"
+ return self.cached_xsm_sr_relaxed
+
def check_can_live_migrate_source(self, ctxt, instance_ref,
dest_check_data):
"""Check if it's possible to execute live migration on the source side.
@@ -1697,6 +1728,13 @@ class VMOps(object):
destination, includes block_migration flag
"""
+ if len(self._get_iscsi_srs(ctxt, instance_ref)) > 0:
+ # XAPI must support the relaxed SR check for live migrating with
+ # iSCSI VBDs
+ if not self._is_xsm_sr_check_relaxed():
+ raise exception.MigrationError(_('XAPI supporting '
+ 'relax-xsm-sr-check=true requried'))
+
if 'migrate_data' in dest_check_data:
vm_ref = self._get_vm_opaque_ref(instance_ref)
migrate_data = dest_check_data['migrate_data']
@@ -1709,9 +1747,10 @@ class VMOps(object):
raise exception.MigrationPreCheckError(reason=msg)
return dest_check_data
- def _generate_vdi_map(self, destination_sr_ref, vm_ref):
+ def _generate_vdi_map(self, destination_sr_ref, vm_ref, sr_ref=None):
"""generate a vdi_map for _call_live_migrate_command."""
- sr_ref = vm_utils.safe_find_sr(self._session)
+ if sr_ref is None:
+ sr_ref = vm_utils.safe_find_sr(self._session)
vm_vdis = vm_utils.get_instance_vdis_for_sr(self._session,
vm_ref, sr_ref)
return dict((vdi, destination_sr_ref) for vdi in vm_vdis)
@@ -1722,6 +1761,19 @@ class VMOps(object):
migrate_send_data = migrate_data['migrate_send_data']
vdi_map = self._generate_vdi_map(destination_sr_ref, vm_ref)
+
+ # Add destination SR refs for all of the VDIs that we created
+ # as part of the pre migration callback
+ if 'pre_live_migration_result' in migrate_data:
+ pre_migrate_data = migrate_data['pre_live_migration_result']
+ sr_uuid_map = pre_migrate_data.get('sr_uuid_map', [])
+ for sr_uuid in sr_uuid_map:
+ # Source and destination SRs have the same UUID, so get the
+ # reference for the local SR
+ sr_ref = self._session.call_xenapi("SR.get_by_uuid", sr_uuid)
+ vdi_map.update(
+ self._generate_vdi_map(
+ sr_uuid_map[sr_uuid], vm_ref, sr_ref))
vif_map = {}
options = {}
self._session.call_xenapi(command_name, vm_ref,
@@ -1737,12 +1789,18 @@ class VMOps(object):
if not migrate_data:
raise exception.InvalidParameterValue('Block Migration '
'requires migrate data from destination')
+
+ iscsi_srs = self._get_iscsi_srs(context, instance)
try:
self._call_live_migrate_command(
"VM.migrate_send", vm_ref, migrate_data)
except self._session.XenAPI.Failure as exc:
LOG.exception(exc)
raise exception.MigrationError(_('Migrate Send failed'))
+
+ # Tidy up the iSCSI SRs
+ for sr_ref in iscsi_srs:
+ volume_utils.forget_sr(self._session, sr_ref)
else:
host_ref = self._get_host_opaque_ref(context,
destination_hostname)
@@ -1775,3 +1833,26 @@ class VMOps(object):
usage[uuid] = {'memory_mb': memory_mb, 'uuid': uuid}
return usage
+
+ def attach_block_device_volumes(self, block_device_info):
+ sr_uuid_map = {}
+ try:
+ if block_device_info is not None:
+ for block_device_map in block_device_info[
+ 'block_device_mapping']:
+ sr_uuid, _ = self._volumeops.attach_volume(
+ block_device_map['connection_info'],
+ None,
+ block_device_map['mount_device'],
+ hotplug=False)
+
+ sr_ref = self._session.call_xenapi('SR.get_by_uuid',
+ sr_uuid)
+ sr_uuid_map[sr_uuid] = sr_ref
+ except Exception:
+ with excutils.save_and_reraise_exception():
+ # Disconnect the volumes we just connected
+ for sr in sr_uuid_map:
+ volume_utils.forget_sr(self._session, sr_uuid_map[sr_ref])
+
+ return sr_uuid_map
diff --git a/nova/virt/xenapi/volumeops.py b/nova/virt/xenapi/volumeops.py
index d59d96fe7..5e650f55d 100644
--- a/nova/virt/xenapi/volumeops.py
+++ b/nova/virt/xenapi/volumeops.py
@@ -39,29 +39,49 @@ class VolumeOps(object):
def attach_volume(self, connection_info, instance_name, mountpoint,
hotplug=True):
- """Attach volume storage to VM instance."""
-
- vm_ref = vm_utils.vm_ref_or_raise(self._session, instance_name)
+ """
+ Attach volume storage to VM instance.
+ """
# NOTE: No Resource Pool concept so far
LOG.debug(_("Attach_volume: %(connection_info)s, %(instance_name)s,"
" %(mountpoint)s") % locals())
- driver_type = connection_info['driver_volume_type']
- if driver_type not in ['iscsi', 'xensm']:
- raise exception.VolumeDriverNotFound(driver_type=driver_type)
-
- connection_data = connection_info['data']
dev_number = volume_utils.get_device_number(mountpoint)
+ vm_ref = vm_utils.vm_ref_or_raise(self._session, instance_name)
- self._connect_volume(connection_data, dev_number, instance_name,
- vm_ref, hotplug=hotplug)
+ sr_uuid, vdi_uuid = self._connect_volume(connection_info, dev_number,
+ instance_name, vm_ref,
+ hotplug=hotplug)
LOG.info(_('Mountpoint %(mountpoint)s attached to'
' instance %(instance_name)s') % locals())
- def _connect_volume(self, connection_data, dev_number, instance_name,
- vm_ref, hotplug=True):
+ return (sr_uuid, vdi_uuid)
+
+ def connect_volume(self, connection_info):
+ """
+ Attach volume storage to the hypervisor without attaching to a VM
+
+ Used to attach the just the SR - e.g. for during live migration
+ """
+
+ # NOTE: No Resource Pool concept so far
+ LOG.debug(_("Connect_volume: %(connection_info)s") % locals())
+
+ sr_uuid, vdi_uuid = self._connect_volume(connection_info,
+ None, None, None, False)
+
+ return (sr_uuid, vdi_uuid)
+
+ def _connect_volume(self, connection_info, dev_number=None,
+ instance_name=None, vm_ref=None, hotplug=True):
+ driver_type = connection_info['driver_volume_type']
+ if driver_type not in ['iscsi', 'xensm']:
+ raise exception.VolumeDriverNotFound(driver_type=driver_type)
+
+ connection_data = connection_info['data']
+
sr_uuid, sr_label, sr_params = volume_utils.parse_sr_info(
connection_data, 'Disk-for:%s' % instance_name)
@@ -86,12 +106,16 @@ class VolumeOps(object):
vdi_ref = volume_utils.introduce_vdi(self._session, sr_ref)
# Attach
- vbd_ref = vm_utils.create_vbd(self._session, vm_ref, vdi_ref,
- dev_number, bootable=False,
- osvol=True)
+ if vm_ref:
+ vbd_ref = vm_utils.create_vbd(self._session, vm_ref, vdi_ref,
+ dev_number, bootable=False,
+ osvol=True)
+
+ if hotplug:
+ self._session.call_xenapi("VBD.plug", vbd_ref)
- if hotplug:
- self._session.call_xenapi("VBD.plug", vbd_ref)
+ vdi_uuid = self._session.call_xenapi("VDI.get_uuid", vdi_ref)
+ return (sr_uuid, vdi_uuid)
except Exception:
with excutils.save_and_reraise_exception():
# NOTE(sirp): Forgetting the SR will have the effect of
@@ -106,7 +130,6 @@ class VolumeOps(object):
device_number = volume_utils.get_device_number(mountpoint)
vm_ref = vm_utils.vm_ref_or_raise(self._session, instance_name)
-
try:
vbd_ref = vm_utils.find_vbd_by_number(
self._session, vm_ref, device_number)
diff --git a/plugins/xenserver/xenapi/contrib/rpmbuild/SPECS/openstack-xen-plugins.spec b/plugins/xenserver/xenapi/contrib/rpmbuild/SPECS/openstack-xen-plugins.spec
index 66c13967e..b93c7b071 100644
--- a/plugins/xenserver/xenapi/contrib/rpmbuild/SPECS/openstack-xen-plugins.spec
+++ b/plugins/xenserver/xenapi/contrib/rpmbuild/SPECS/openstack-xen-plugins.spec
@@ -31,6 +31,7 @@ rm -rf $RPM_BUILD_ROOT
/etc/xapi.d/plugins/agent
/etc/xapi.d/plugins/bandwidth
/etc/xapi.d/plugins/bittorrent
+/etc/xapi.d/plugins/config_file
/etc/xapi.d/plugins/glance
/etc/xapi.d/plugins/kernel
/etc/xapi.d/plugins/migration
diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/config_file b/plugins/xenserver/xenapi/etc/xapi.d/plugins/config_file
new file mode 100755
index 000000000..417c477b4
--- /dev/null
+++ b/plugins/xenserver/xenapi/etc/xapi.d/plugins/config_file
@@ -0,0 +1,19 @@
+#!/usr/bin/env python
+
+import XenAPIPlugin
+
+
+def get_val(session, args):
+ config_key = args['key']
+ config_file = open('/etc/xapi.conf')
+ try:
+ for line in config_file:
+ split = line.split('=')
+ if (len(split) == 2) and (split[0].strip() == config_key):
+ return split[1].strip()
+ return ""
+ finally:
+ config_file.close()
+
+if __name__ == '__main__':
+ XenAPIPlugin.dispatch({"get_val": get_val})