From e507094eef9a7b4a54a04faade8aa95a36fa6d93 Mon Sep 17 00:00:00 2001 From: Bob Ball Date: Fri, 22 Mar 2013 10:23:22 +0000 Subject: 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 --- nova/tests/compute/test_compute.py | 2 +- nova/tests/test_xenapi.py | 139 ++++++++++++++++++++++--------- nova/tests/virt/xenapi/test_vmops.py | 15 ++++ nova/tests/virt/xenapi/test_volumeops.py | 68 +++++++++++++-- 4 files changed, 175 insertions(+), 49 deletions(-) (limited to 'nova/tests') 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']) -- cgit