Skip to content

Commit

Permalink
xapi: Fix live block migration
Browse files Browse the repository at this point in the history
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
  • Loading branch information
John Garbutt authored and Mate Lakat committed Sep 10, 2012
1 parent 28a5b31 commit 4c72bfc
Show file tree
Hide file tree
Showing 5 changed files with 200 additions and 43 deletions.
127 changes: 110 additions & 17 deletions nova/tests/test_xenapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@
from nova.virt.xenapi import vmops
from nova.virt.xenapi import volume_utils


LOG = logging.getLogger(__name__)

FLAGS = flags.FLAGS
Expand Down Expand Up @@ -2171,18 +2170,18 @@ def test_post_live_migration_at_destination(self):
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,
Expand All @@ -2196,30 +2195,50 @@ def test_check_can_live_migrate_source_with_block_migrate(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)
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,
{'host': 'host'},
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,
Expand Down Expand Up @@ -2310,12 +2329,19 @@ def recover_method(context, instance, destination_hostname,
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)

Expand All @@ -2324,7 +2350,8 @@ def post_method(context, instance, destination_hostname,
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")
Expand Down Expand Up @@ -2357,16 +2384,82 @@ def fake_get_vm_opaque_ref(instance):
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):
Expand Down
48 changes: 48 additions & 0 deletions nova/tests/xenapi/test_vm_utils.py
Original file line number Diff line number Diff line change
@@ -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)
10 changes: 1 addition & 9 deletions nova/virt/xenapi/fake.py
Original file line number Diff line number Diff line change
Expand Up @@ -635,15 +635,7 @@ def pool_set_name_label(self, session, pool_ref, name):
pass

def host_migrate_receive(self, session, destref, nwref, options):
# The dictionary below represents the true keys, as
# returned by a destination host, but fake values.
return {'xenops': 'http://localhost/services/xenops?'
'session_id=OpaqueRef:81d00b97-b205-b34d-924e-6f9597854cc0',
'host': 'OpaqueRef:5e4a3dd1-b71c-74ba-bbc6-58ee9ff6a889',
'master': 'http://localhost/',
'session_id': 'OpaqueRef:81d00b97-b205-b34d-924e-6f9597854cc0',
'SM': 'http://localhost/services/SM?'
'session_id=OpaqueRef:81d00b97-b205-b34d-924e-6f9597854cc0'}
return "fake_migrate_data"

def VM_assert_can_migrate(self, session, vmref, migrate_data, live,
vdi_map, vif_map, options):
Expand Down
11 changes: 11 additions & 0 deletions nova/virt/xenapi/vm_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -1590,6 +1590,17 @@ def _get_all_vdis_in_sr(session, sr_ref):
continue


def get_instance_vdis_for_sr(session, vm_ref, sr_ref):
"""Return opaqueRef for all the vdis which live on sr"""
for vbd_ref in session.call_xenapi('VM.get_VBDs', vm_ref):
try:
vdi_ref = session.call_xenapi('VBD.get_VDI', vbd_ref)
if sr_ref == session.call_xenapi('VDI.get_SR', vdi_ref):
yield vdi_ref
except session.XenAPI.Failure:
continue


def _get_vhd_parent_uuid(session, vdi_ref):
vdi_rec = session.call_xenapi("VDI.get_record", vdi_ref)

Expand Down
47 changes: 30 additions & 17 deletions nova/virt/xenapi/vmops.py
Original file line number Diff line number Diff line change
Expand Up @@ -1534,10 +1534,12 @@ def check_can_live_migrate_destination(self, ctxt, instance_ref,
"""
if block_migration:
migrate_data = self._migrate_receive(ctxt)
dest_check_data = {}
dest_check_data["block_migration"] = block_migration
dest_check_data["migrate_data"] = migrate_data
migrate_send_data = self._migrate_receive(ctxt)
destination_sr_ref = vm_utils.safe_find_sr(self._session)
dest_check_data = {
"block_migration": block_migration,
"migrate_data": {"migrate_send_data": migrate_send_data,
"destination_sr_ref": destination_sr_ref}}
return dest_check_data
else:
src = instance_ref['host']
Expand All @@ -1558,20 +1560,35 @@ def check_can_live_migrate_source(self, ctxt, instance_ref,
"""
if dest_check_data and 'migrate_data' in dest_check_data:
vmref = self._get_vm_opaque_ref(instance_ref)
vm_ref = self._get_vm_opaque_ref(instance_ref)
migrate_data = dest_check_data['migrate_data']
try:
vdi_map = {}
vif_map = {}
options = {}
self._session.call_xenapi("VM.assert_can_migrate", vmref,
migrate_data, True, vdi_map, vif_map,
options)
self._call_live_migrate_command(
"VM.assert_can_migrate", vm_ref, migrate_data)
except self._session.XenAPI.Failure as exc:
LOG.exception(exc)
raise exception.MigrationError(_('VM.assert_can_migrate'
'failed'))

def _generate_vdi_map(self, destination_sr_ref, vm_ref):
"""generate a vdi_map for _call_live_migrate_command """
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)

def _call_live_migrate_command(self, command_name, vm_ref, migrate_data):
"""unpack xapi specific parameters, and call a live migrate command"""
destination_sr_ref = migrate_data['destination_sr_ref']
migrate_send_data = migrate_data['migrate_send_data']

vdi_map = self._generate_vdi_map(destination_sr_ref, vm_ref)
vif_map = {}
options = {}
self._session.call_xenapi(command_name, vm_ref,
migrate_send_data, True,
vdi_map, vif_map, options)

def live_migrate(self, context, instance, destination_hostname,
post_method, recover_method, block_migration,
migrate_data=None):
Expand All @@ -1582,12 +1599,8 @@ def live_migrate(self, context, instance, destination_hostname,
raise exception.InvalidParameterValue('Block Migration '
'requires migrate data from destination')
try:
vdi_map = {}
vif_map = {}
options = {}
self._session.call_xenapi("VM.migrate_send", vm_ref,
migrate_data, True,
vdi_map, vif_map, options)
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'))
Expand Down

0 comments on commit 4c72bfc

Please sign in to comment.