diff options
-rwxr-xr-x | nova/compute/manager.py | 13 | ||||
-rw-r--r-- | nova/tests/compute/test_compute.py | 2 | ||||
-rw-r--r-- | nova/tests/test_xenapi.py | 139 | ||||
-rw-r--r-- | nova/tests/virt/xenapi/test_vmops.py | 15 | ||||
-rw-r--r-- | nova/tests/virt/xenapi/test_volumeops.py | 68 | ||||
-rwxr-xr-x | nova/virt/xenapi/driver.py | 7 | ||||
-rw-r--r-- | nova/virt/xenapi/vmops.py | 93 | ||||
-rw-r--r-- | nova/virt/xenapi/volumeops.py | 59 | ||||
-rw-r--r-- | plugins/xenserver/xenapi/contrib/rpmbuild/SPECS/openstack-xen-plugins.spec | 1 | ||||
-rwxr-xr-x | plugins/xenserver/xenapi/etc/xapi.d/plugins/config_file | 19 |
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}) |