summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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})