From dcff384889c359e1843972af0a436ce658886752 Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Fri, 7 Sep 2012 12:17:48 +0100 Subject: [PATCH] xapi: Fix live block migration Fixes bug 1040332. Xapi VM.migrate_send and VM.assert_can_migrate calls require that vdi_map parameter is a (source_vdi_ref -> target_sr_ref) mapping, for block live migration to work, as of XenServer 6.0 RC1. On the destination side: This fix modifies the check_can_live_migrate_destination call, so that the value returned contains the "destination_sr_ref" (XenAPI specific data is stored under the "migrate_send_data key"). On the source side: check_can_live_migrate_source and live_migrate calls assemble the vdi_map by mapping all the local sr contained vdis of the VM to destination_sr_ref, and passing this parameter to the VM.migrate_send and VM.assert_can_migrate Xapi calls. Change-Id: I95f3dca651d2e72fc727646580092a25f558d6ba --- nova/tests/test_xenapi.py | 127 +++++++++++++++++++++++++---- nova/tests/xenapi/test_vm_utils.py | 48 +++++++++++ 2 files changed, 158 insertions(+), 17 deletions(-) create mode 100644 nova/tests/xenapi/test_vm_utils.py diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 2bf7429bd..dced3ddd9 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -49,7 +49,6 @@ from nova.virt.xenapi import vm_utils from nova.virt.xenapi import vmops from nova.virt.xenapi import volume_utils - LOG = logging.getLogger(__name__) FLAGS = flags.FLAGS @@ -2171,18 +2170,18 @@ class XenAPILiveMigrateTestCase(stubs.XenAPITestBase): def test_check_can_live_migrate_destination_with_block_migration(self): stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests) self.conn = xenapi_conn.XenAPIDriver(False) + + self.stubs.Set(vm_utils, "safe_find_sr", lambda _x: "asdf") + expected = {'block_migration': True, - 'migrate_data': {'xenops': '', - 'host': '', - 'master': '', - 'session_id': '', - 'SM': ''} + 'migrate_data': { + 'migrate_send_data': "fake_migrate_data", + 'destination_sr_ref': 'asdf' + } } - fake_data = self.conn.check_can_live_migrate_destination(self.context, + result = self.conn.check_can_live_migrate_destination(self.context, {'host': 'host'}, True, False) - self.assertEqual(expected.keys(), fake_data.keys()) - self.assertEqual(expected['migrate_data'].keys(), - fake_data['migrate_data'].keys()) + self.assertEqual(expected, result) def test_check_can_live_migrate_destination_block_migration_fails(self): stubs.stubout_session(self.stubs, @@ -2196,13 +2195,22 @@ class XenAPILiveMigrateTestCase(stubs.XenAPITestBase): stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests) self.conn = xenapi_conn.XenAPIDriver(False) + 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_vm_opaque_ref(instance): return "fake_vm" self.stubs.Set(self.conn._vmops, "_get_vm_opaque_ref", fake_get_vm_opaque_ref) dest_check_data = {'block_migration': True, - 'migrate_data': {}} + 'migrate_data': { + 'destination_sr_ref': None, + 'migrate_send_data': None + }} self.assertNotRaises(None, self.conn.check_can_live_migrate_source, self.context, @@ -2210,16 +2218,27 @@ class XenAPILiveMigrateTestCase(stubs.XenAPITestBase): dest_check_data) def test_check_can_live_migrate_source_with_block_migrate_fails(self): - def fake_get_vm_opaque_ref(instance): - return "fake_vm" stubs.stubout_session(self.stubs, stubs.FakeSessionForFailedMigrateTests) self.conn = xenapi_conn.XenAPIDriver(False) + + 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_vm_opaque_ref(instance): + return "fake_vm" + self.stubs.Set(self.conn._vmops, "_get_vm_opaque_ref", fake_get_vm_opaque_ref) dest_check_data = {'block_migration': True, - 'migrate_data': {}} + 'migrate_data': { + 'destination_sr_ref': None, + 'migrate_send_data': None + }} self.assertRaises(exception.MigrationError, self.conn.check_can_live_migrate_source, self.context, @@ -2310,12 +2329,19 @@ class XenAPILiveMigrateTestCase(stubs.XenAPITestBase): self.conn, None, None, None, recover_method) self.assertTrue(recover_method.called, "recover_method.called") - def test_live_migration_with_block_migration(self): + def test_live_migration_calls_post_migration(self): stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests) self.conn = xenapi_conn.XenAPIDriver(False) + 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_vm_opaque_ref(instance): return "fake_vm" + self.stubs.Set(self.conn._vmops, "_get_vm_opaque_ref", fake_get_vm_opaque_ref) @@ -2324,7 +2350,8 @@ class XenAPILiveMigrateTestCase(stubs.XenAPITestBase): post_method.called = True # pass block_migration = True and migrate data - migrate_data = {"test": "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") @@ -2357,16 +2384,82 @@ class XenAPILiveMigrateTestCase(stubs.XenAPITestBase): 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) + def recover_method(context, instance, destination_hostname, block_migration): recover_method.called = True # pass block_migration = True and migrate data - migrate_data = {"test": "data"} + migrate_data = dict(destination_sr_ref='foo', migrate_send_data='bar') self.assertRaises(exception.MigrationError, self.conn.live_migration, self.conn, None, None, None, recover_method, True, migrate_data) self.assertTrue(recover_method.called, "recover_method.called") + def test_live_migrate_block_migration_xapi_call_parameters(self): + + fake_vdi_map = object() + + class Session(xenapi_fake.SessionBase): + def VM_migrate_send(self_, session, vmref, migrate_data, islive, + vdi_map, vif_map, options): + self.assertEquals('SOMEDATA', migrate_data) + self.assertEquals(fake_vdi_map, vdi_map) + + stubs.stubout_session(self.stubs, Session) + + conn = xenapi_conn.XenAPIDriver(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) + + def fake_generate_vdi_map(destination_sr_ref, _vm_ref): + return fake_vdi_map + + self.stubs.Set(conn._vmops, "_generate_vdi_map", + fake_generate_vdi_map) + + def dummy_callback(*args, **kwargs): + pass + + conn.live_migration( + self.context, instance_ref=dict(name='ignore'), dest=None, + post_method=dummy_callback, recover_method=dummy_callback, + block_migration="SOMEDATA", + migrate_data=dict(migrate_send_data='SOMEDATA', + destination_sr_ref="TARGET_SR_OPAQUE_REF")) + + def test_generate_vdi_map(self): + stubs.stubout_session(self.stubs, xenapi_fake.SessionBase) + conn = xenapi_conn.XenAPIDriver(False) + + vm_ref = "fake_vm_ref" + + def fake_find_sr(_session): + self.assertEquals(conn._session, _session) + return "source_sr_ref" + self.stubs.Set(vm_utils, "safe_find_sr", fake_find_sr) + + def fake_get_instance_vdis_for_sr(_session, _vm_ref, _sr_ref): + self.assertEquals(conn._session, _session) + self.assertEquals(vm_ref, _vm_ref) + self.assertEquals("source_sr_ref", _sr_ref) + return ["vdi0", "vdi1"] + + self.stubs.Set(vm_utils, "get_instance_vdis_for_sr", + fake_get_instance_vdis_for_sr) + + result = conn._vmops._generate_vdi_map("dest_sr_ref", vm_ref) + + self.assertEquals({"vdi0": "dest_sr_ref", + "vdi1": "dest_sr_ref"}, result) + class XenAPIInjectMetadataTestCase(stubs.XenAPITestBase): def setUp(self): diff --git a/nova/tests/xenapi/test_vm_utils.py b/nova/tests/xenapi/test_vm_utils.py new file mode 100644 index 000000000..869a920a4 --- /dev/null +++ b/nova/tests/xenapi/test_vm_utils.py @@ -0,0 +1,48 @@ +from nova.tests.xenapi import stubs +from nova.virt.xenapi import driver as xenapi_conn +from nova.virt.xenapi import fake +from nova.virt.xenapi import vm_utils + + +class GetInstanceForVdisForSrTestCase(stubs.XenAPITestBase): + def setUp(self): + super(GetInstanceForVdisForSrTestCase, self).setUp() + self.flags(disable_process_locking=True, + instance_name_template='%d', + firewall_driver='nova.virt.xenapi.firewall.' + 'Dom0IptablesFirewallDriver', + xenapi_connection_url='test_url', + xenapi_connection_password='test_pass',) + + def tearDown(self): + super(GetInstanceForVdisForSrTestCase, self).tearDown() + + def test_get_instance_vdis_for_sr(self): + vm_ref = fake.create_vm("foo", "Running") + sr_ref = fake.create_sr() + + vdi_1 = fake.create_vdi('vdiname1', sr_ref) + vdi_2 = fake.create_vdi('vdiname2', sr_ref) + + for vdi_ref in [vdi_1, vdi_2]: + fake.create_vbd(vm_ref, vdi_ref) + + stubs.stubout_session(self.stubs, fake.SessionBase) + driver = xenapi_conn.XenAPIDriver(False) + + result = list(vm_utils.get_instance_vdis_for_sr( + driver._session, vm_ref, sr_ref)) + + self.assertEquals([vdi_1, vdi_2], result) + + def test_get_instance_vdis_for_sr_no_vbd(self): + vm_ref = fake.create_vm("foo", "Running") + sr_ref = fake.create_sr() + + stubs.stubout_session(self.stubs, fake.SessionBase) + driver = xenapi_conn.XenAPIDriver(False) + + result = list(vm_utils.get_instance_vdis_for_sr( + driver._session, vm_ref, sr_ref)) + + self.assertEquals([], result)