Fix live migrate with XenServer

Live migration with XenServer as hypervisor failed with xapi
errors "VIF_NOT_IN_MAP". There are two reasons for this
problem:

(1) Before XS7.0, it supports VM live migration without
setting vif_ref and network_ref explicitly if the destination
host has same network, but since XS7.0, it doesn't support
this way, we must give vif_ref and network_ref mapping.

(2) In nova, XenServer has introduced interim network for
fixing ovs updating wrong port in neutron, see bug 1268955
and also interim network can assist support neutron security
group (linux bridge) as we cannot make VIF connected to
linux bridge directly via XAPI

To achieve this, we will add {src_vif_ref: dest_network_ref}
mapping information, in pre_live_migration, we first create
interim network in destination host and store
{neutron_vif_uuid: dest_network_ref} in migrate_data, then in
source host, before live_migration, we will calculate the
{src_vif_ref: dest_network_ref} and set it as parameters to
xapi when calling VM.migrate_send. Also, we will handle the
case where the destination host is running older code that
doesn't have this new src_vif_ref mapping, like live migrating
from an Ocata compute node to a Newton compute node.

Closes-bug: 1658877

Change-Id: If0fb5d764011521916fbbe15224f524a220052f3
This commit is contained in:
Huan Xie 2017-01-22 03:08:40 -08:00 committed by Bob Ball
parent 93bf6ba518
commit 4cd32645fb
10 changed files with 323 additions and 65 deletions

View File

@ -221,7 +221,9 @@ class LibvirtLiveMigrateData(LiveMigrateData):
@obj_base.NovaObjectRegistry.register
class XenapiLiveMigrateData(LiveMigrateData):
VERSION = '1.0'
# Version 1.0: Initial version
# Version 1.1: Added vif_uuid_map
VERSION = '1.1'
fields = {
'block_migration': fields.BooleanField(nullable=True),
@ -230,6 +232,7 @@ class XenapiLiveMigrateData(LiveMigrateData):
'sr_uuid_map': fields.DictOfStringsField(),
'kernel_file': fields.StringField(),
'ramdisk_file': fields.StringField(),
'vif_uuid_map': fields.DictOfStringsField(),
}
def to_legacy_dict(self, pre_migration_result=False):
@ -244,6 +247,8 @@ class XenapiLiveMigrateData(LiveMigrateData):
live_result = {
'sr_uuid_map': ('sr_uuid_map' in self and self.sr_uuid_map
or {}),
'vif_uuid_map': ('vif_uuid_map' in self and self.vif_uuid_map
or {}),
}
if pre_migration_result:
legacy['pre_live_migration_result'] = live_result
@ -263,6 +268,16 @@ class XenapiLiveMigrateData(LiveMigrateData):
if 'pre_live_migration_result' in legacy:
self.sr_uuid_map = \
legacy['pre_live_migration_result']['sr_uuid_map']
self.vif_uuid_map = \
legacy['pre_live_migration_result'].get('vif_uuid_map', {})
def obj_make_compatible(self, primitive, target_version):
super(XenapiLiveMigrateData, self).obj_make_compatible(
primitive, target_version)
target_version = versionutils.convert_version_to_tuple(target_version)
if target_version < (1, 1):
if 'vif_uuid_map' in primitive:
del primitive['vif_uuid_map']
@obj_base.NovaObjectRegistry.register

View File

@ -240,7 +240,8 @@ class _TestXenapiLiveMigrateData(object):
block_migration=False,
destination_sr_ref='foo',
migrate_send_data={'key': 'val'},
sr_uuid_map={'apple': 'banana'})
sr_uuid_map={'apple': 'banana'},
vif_uuid_map={'orange': 'lemon'})
expected = {
'is_volume_backed': False,
'block_migration': False,
@ -257,7 +258,8 @@ class _TestXenapiLiveMigrateData(object):
block_migration=False,
destination_sr_ref='foo',
migrate_send_data={'key': 'val'},
sr_uuid_map={'apple': 'banana'})
sr_uuid_map={'apple': 'banana'},
vif_uuid_map={'orange': 'lemon'})
legacy = obj.to_legacy_dict()
legacy['ignore_this_thing'] = True
obj2 = migrate_data.XenapiLiveMigrateData()
@ -268,7 +270,8 @@ class _TestXenapiLiveMigrateData(object):
obj = migrate_data.XenapiLiveMigrateData(
is_volume_backed=False,
destination_sr_ref='foo',
sr_uuid_map={'apple': 'banana'})
sr_uuid_map={'apple': 'banana'},
vif_uuid_map={'orange': 'lemon'})
expected = {
'is_volume_backed': False,
}
@ -280,6 +283,7 @@ class _TestXenapiLiveMigrateData(object):
'is_volume_backed': False,
'pre_live_migration_result': {
'sr_uuid_map': {},
'vif_uuid_map': {},
},
}
self.assertEqual(expected, obj.to_legacy_dict(True))
@ -288,25 +292,47 @@ class _TestXenapiLiveMigrateData(object):
obj = migrate_data.XenapiLiveMigrateData(
is_volume_backed=False,
destination_sr_ref='foo',
sr_uuid_map={'apple': 'banana'})
sr_uuid_map={'apple': 'banana'},
vif_uuid_map={'orange': 'lemon'})
legacy = obj.to_legacy_dict()
obj2 = migrate_data.XenapiLiveMigrateData()
obj2.from_legacy_dict(legacy)
self.assertFalse(obj2.block_migration)
self.assertNotIn('migrate_send_data', obj2)
self.assertNotIn('sr_uuid_map', obj2)
self.assertNotIn('vif_uuid_map', obj2)
def test_to_legacy_with_pre_result(self):
obj = migrate_data.XenapiLiveMigrateData(
sr_uuid_map={'a': 'b'})
sr_uuid_map={'a': 'b'},
vif_uuid_map={'c': 'd'})
self.assertNotIn('sr_uuid_map', obj.to_legacy_dict())
self.assertNotIn('vi_uuid_map', obj.to_legacy_dict())
legacy = obj.to_legacy_dict(True)
self.assertEqual(
{'a': 'b'},
legacy['pre_live_migration_result']['sr_uuid_map'])
self.assertEqual(
{'c': 'd'},
legacy['pre_live_migration_result']['vif_uuid_map']
)
obj2 = migrate_data.XenapiLiveMigrateData()
obj2.from_legacy_dict(legacy)
self.assertEqual({'a': 'b'}, obj2.sr_uuid_map)
self.assertEqual({'c': 'd'}, obj2.vif_uuid_map)
def test_obj_make_compatible(self):
obj = migrate_data.XenapiLiveMigrateData(
is_volume_backed=False,
block_migration=False,
destination_sr_ref='foo',
migrate_send_data={'key': 'val'},
sr_uuid_map={'apple': 'banana'},
vif_uuid_map={'orange': 'lemon'})
primitive = obj.obj_to_primitive('1.0')
self.assertNotIn('vif_uuid_map', primitive['nova_object.data'])
primitive2 = obj.obj_to_primitive('1.1')
self.assertIn('vif_uuid_map', primitive2['nova_object.data'])
class TestXenapiLiveMigrateData(test_objects._LocalTest,

View File

@ -1172,7 +1172,7 @@ object_data = {
'VirtualInterface': '1.3-efd3ca8ebcc5ce65fff5a25f31754c54',
'VirtualInterfaceList': '1.0-9750e2074437b3077e46359102779fc6',
'VolumeUsage': '1.0-6c8190c46ce1469bb3286a1f21c2e475',
'XenapiLiveMigrateData': '1.0-5f982bec68f066e194cd9ce53a24ac4c',
'XenapiLiveMigrateData': '1.1-79e69f5ac9abfbcfcbaec18e8280bec6',
}

View File

@ -215,3 +215,23 @@ class XenAPIDriverTestCase(stubs.XenAPITestBaseNoDB):
driver.detach_interface('fake_context', 'fake_instance', 'fake_vif')
mock_detach_interface.assert_called_once_with('fake_instance',
'fake_vif')
@mock.patch.object(xenapi_driver.vmops.VMOps,
'post_live_migration_at_source')
def test_post_live_migration_at_source(self, mock_post_live_migration):
driver = self._get_driver()
driver.post_live_migration_at_source('fake_context', 'fake_instance',
'fake_network_info')
mock_post_live_migration.assert_called_once_with(
'fake_context', 'fake_instance', 'fake_network_info')
@mock.patch.object(xenapi_driver.vmops.VMOps,
'rollback_live_migration_at_destination')
def test_rollback_live_migration_at_destination(self, mock_rollback):
driver = self._get_driver()
driver.rollback_live_migration_at_destination(
'fake_context', 'fake_instance', 'fake_network_info',
'fake_block_device')
mock_rollback.assert_called_once_with('fake_instance',
'fake_network_info',
'fake_block_device')

View File

@ -199,52 +199,59 @@ class XenAPIOpenVswitchDriverTestCase(XenVIFDriverTestBase):
mock_hot_plug.assert_called_once_with(fake_vif, instance,
'fake_vm_ref', 'fake_vif_ref')
@mock.patch.object(vif.XenAPIOpenVswitchDriver, '_delete_linux_bridge')
@mock.patch.object(vif.XenAPIOpenVswitchDriver, '_delete_linux_port')
@mock.patch.object(vif.XenAPIOpenVswitchDriver, '_device_exists',
return_value=True)
@mock.patch.object(vif.XenAPIOpenVswitchDriver, '_ovs_del_br')
@mock.patch.object(vif.XenAPIOpenVswitchDriver, '_ovs_del_port')
@mock.patch.object(vif.XenAPIOpenVswitchDriver,
'delete_network_and_bridge')
@mock.patch.object(network_utils, 'find_network_with_name_label',
return_value='fake_network')
@mock.patch.object(vif.XenVIFDriver, 'unplug')
def test_unplug(self, mock_super_unplug,
mock_find_network_with_name_label,
mock_ovs_del_port,
mock_ovs_del_br,
mock_device_exists,
mock_delete_linux_port,
mock_delete_linux_bridge):
mock_delete_network_bridge):
instance = {'name': "fake_instance"}
vm_ref = "fake_vm_ref"
mock_network_get_VIFs = self.mock_patch_object(
self._session.network, 'get_VIFs', return_val=None)
mock_network_get_bridge = self.mock_patch_object(
self._session.network, 'get_bridge', return_val='fake_bridge')
mock_network_destroy = self.mock_patch_object(
self._session.network, 'destroy')
self.ovs_driver.unplug(instance, fake_vif, vm_ref)
self.assertTrue(mock_super_unplug.called)
self.assertTrue(mock_find_network_with_name_label.called)
self.assertTrue(mock_network_get_VIFs.called)
self.assertTrue(mock_network_get_bridge.called)
self.assertEqual(mock_ovs_del_port.call_count, 2)
self.assertTrue(mock_network_destroy.called)
self.assertTrue(mock_delete_network_bridge.called)
@mock.patch.object(vif.XenAPIOpenVswitchDriver, '_delete_linux_bridge')
@mock.patch.object(vif.XenAPIOpenVswitchDriver, '_delete_linux_port')
@mock.patch.object(vif.XenAPIOpenVswitchDriver, '_device_exists')
@mock.patch.object(vif.XenAPIOpenVswitchDriver, '_ovs_del_br')
@mock.patch.object(vif.XenAPIOpenVswitchDriver, '_ovs_del_port')
@mock.patch.object(network_utils, 'find_network_with_name_label')
def test_delete_network_and_bridge(self, mock_find_network,
mock_ovs_del_port, mock_ovs_del_br,
mock_device_exists,
mock_delete_linux_port,
mock_delete_linux_bridge):
mock_find_network.return_value = 'fake_network'
mock_device_exists.return_value = True
instance = {'name': 'fake_instance'}
vif = {'id': 'fake_vif'}
self._session.network = mock.Mock()
self.ovs_driver.delete_network_and_bridge(instance, vif)
self._session.network.get_bridge.assert_called_once_with(
'fake_network')
self._session.network.destroy.assert_called_once_with('fake_network')
self.assertTrue(mock_find_network.called)
self.assertEqual(mock_ovs_del_port.call_count, 2)
self.assertEqual(mock_delete_linux_port.call_count, 2)
self.assertTrue(mock_delete_linux_bridge.called)
self.assertTrue(mock_ovs_del_br.called)
@mock.patch.object(vif.XenAPIOpenVswitchDriver, '_ovs_del_port')
@mock.patch.object(network_utils, 'find_network_with_name_label',
return_value='fake_network')
@mock.patch.object(vif.XenVIFDriver, 'unplug')
def test_unplug_exception(self, mock_super_unplug,
mock_find_network_with_name_label,
mock_ovs_del_port,
mock_ovs_del_br):
def test_delete_network_and_bridge_destroy_exception(self,
mock_find_network,
mock_ovs_del_port):
instance = {'name': "fake_instance"}
vm_ref = "fake_vm_ref"
self.mock_patch_object(
self._session.network, 'get_VIFs', return_val=None)
self.mock_patch_object(
@ -254,8 +261,10 @@ class XenAPIOpenVswitchDriverTestCase(XenVIFDriverTestBase):
side_effect=test.TestingException)
self.assertRaises(exception.VirtualInterfaceUnplugException,
self.ovs_driver.unplug, instance, fake_vif,
vm_ref)
self.ovs_driver.delete_network_and_bridge, instance,
fake_vif)
self.assertTrue(mock_find_network.called)
self.assertTrue(mock_ovs_del_port.called)
@mock.patch.object(vif.XenAPIOpenVswitchDriver, '_device_exists')
@mock.patch.object(vif.XenAPIOpenVswitchDriver, '_brctl_add_if')

View File

@ -1411,19 +1411,31 @@ class LiveMigrateTestCase(VMOpsTestBase):
self.vmops._get_host_uuid_from_aggregate,
context, hostname)
@mock.patch.object(vmops.VMOps, 'create_interim_networks')
@mock.patch.object(vmops.VMOps, 'connect_block_device_volumes')
def test_pre_live_migration(self, mock_connect):
def test_pre_live_migration(self, mock_connect, mock_create):
migrate_data = objects.XenapiLiveMigrateData()
migrate_data.block_migration = True
sr_uuid_map = {"sr_uuid": "sr_ref"}
vif_uuid_map = {"neutron_vif_uuid": "dest_network_ref"}
mock_connect.return_value = {"sr_uuid": "sr_ref"}
mock_create.return_value = {"neutron_vif_uuid": "dest_network_ref"}
result = self.vmops.pre_live_migration(
None, None, "bdi", None, None, migrate_data)
None, None, "bdi", "fake_network_info", None, migrate_data)
self.assertTrue(result.block_migration)
self.assertEqual(result.sr_uuid_map, sr_uuid_map)
self.assertEqual(result.vif_uuid_map, vif_uuid_map)
mock_connect.assert_called_once_with("bdi")
mock_create.assert_called_once_with("fake_network_info")
@mock.patch.object(vmops.VMOps, '_delete_networks_and_bridges')
def test_post_live_migration_at_source(self, mock_delete):
self.vmops.post_live_migration_at_source('fake_context',
'fake_instance',
'fake_network_info')
mock_delete.assert_called_once_with('fake_instance',
'fake_network_info')
class LiveMigrateFakeVersionTestCase(VMOpsTestBase):
@ -1552,8 +1564,7 @@ class LiveMigrateHelperTestCase(VMOpsTestBase):
mock_forget.assert_called_once_with(self.vmops._session,
'sr_ref_1')
def _call_live_migrate_command_with_migrate_send_data(self,
migrate_data):
def _call_live_migrate_command_with_migrate_send_data(self, migrate_data):
command_name = 'test_command'
vm_ref = "vm_ref"
@ -1565,21 +1576,27 @@ class LiveMigrateHelperTestCase(VMOpsTestBase):
with mock.patch.object(self.vmops,
"_generate_vdi_map") as mock_gen_vdi_map, \
mock.patch.object(self.vmops._session,
'call_xenapi') as mock_call_xenapi:
'call_xenapi') as mock_call_xenapi, \
mock.patch.object(self.vmops,
"_generate_vif_network_map") as mock_vif_map:
mock_call_xenapi.side_effect = side_effect
mock_gen_vdi_map.side_effect = [
{"vdi": "sr_ref"}, {"vdi": "sr_ref_2"}]
mock_vif_map.return_value = {"vif_ref1": "dest_net_ref"}
self.vmops._call_live_migrate_command(command_name,
vm_ref, migrate_data)
expect_vif_map = {}
if 'vif_uuid_map' in migrate_data:
expect_vif_map.update({"vif_ref1": "dest_net_ref"})
expected_vdi_map = {'vdi': 'sr_ref'}
if 'sr_uuid_map' in migrate_data:
expected_vdi_map = {'vdi': 'sr_ref_2'}
self.assertEqual(mock_call_xenapi.call_args_list[-1],
mock.call('test_command', vm_ref,
mock.call(command_name, vm_ref,
migrate_data.migrate_send_data, True,
expected_vdi_map, {}, {}))
expected_vdi_map, expect_vif_map, {}))
self.assertEqual(mock_gen_vdi_map.call_args_list[0],
mock.call(migrate_data.destination_sr_ref, vm_ref))
@ -1593,6 +1610,7 @@ class LiveMigrateHelperTestCase(VMOpsTestBase):
migrate_data.migrate_send_data = {"foo": "bar"}
migrate_data.destination_sr_ref = "sr_ref"
migrate_data.sr_uuid_map = {"sr_uuid2": "sr_ref_3"}
migrate_data.vif_uuid_map = {"vif_id": "dest_net_ref"}
self._call_live_migrate_command_with_migrate_send_data(migrate_data)
def test_call_live_migrate_command_with_no_sr_uuid_map(self):
@ -1607,30 +1625,105 @@ class LiveMigrateHelperTestCase(VMOpsTestBase):
self._call_live_migrate_command_with_migrate_send_data,
migrate_data)
def test_generate_vif_network_map(self):
with mock.patch.object(self._session.VIF,
'get_other_config') as mock_other_config, \
mock.patch.object(self._session.VM,
'get_VIFs') as mock_get_vif:
mock_other_config.side_effect = [{'nicira-iface-id': 'vif_id_a'},
{'nicira-iface-id': 'vif_id_b'}]
mock_get_vif.return_value = ['vif_ref1', 'vif_ref2']
vif_uuid_map = {'vif_id_b': 'dest_net_ref2',
'vif_id_a': 'dest_net_ref1'}
vif_map = self.vmops._generate_vif_network_map('vm_ref',
vif_uuid_map)
expected = {'vif_ref1': 'dest_net_ref1',
'vif_ref2': 'dest_net_ref2'}
self.assertEqual(vif_map, expected)
def test_generate_vif_network_map_exception(self):
with mock.patch.object(self._session.VIF,
'get_other_config') as mock_other_config, \
mock.patch.object(self._session.VM,
'get_VIFs') as mock_get_vif:
mock_other_config.side_effect = [{'nicira-iface-id': 'vif_id_a'},
{'nicira-iface-id': 'vif_id_b'}]
mock_get_vif.return_value = ['vif_ref1', 'vif_ref2']
vif_uuid_map = {'vif_id_c': 'dest_net_ref2',
'vif_id_d': 'dest_net_ref1'}
self.assertRaises(exception.MigrationError,
self.vmops._generate_vif_network_map,
'vm_ref', vif_uuid_map)
def test_generate_vif_network_map_exception_no_iface(self):
with mock.patch.object(self._session.VIF,
'get_other_config') as mock_other_config, \
mock.patch.object(self._session.VM,
'get_VIFs') as mock_get_vif:
mock_other_config.return_value = {}
mock_get_vif.return_value = ['vif_ref1']
vif_uuid_map = {}
self.assertRaises(exception.MigrationError,
self.vmops._generate_vif_network_map,
'vm_ref', vif_uuid_map)
def test_delete_networks_and_bridges(self):
self.vmops.vif_driver = mock.Mock()
network_info = ['fake_vif']
self.vmops._delete_networks_and_bridges('fake_instance', network_info)
self.vmops.vif_driver.delete_network_and_bridge.\
assert_called_once_with('fake_instance', 'fake_vif')
def test_create_interim_networks(self):
class FakeVifDriver(object):
def create_vif_interim_network(self, vif):
if vif['id'] == "vif_1":
return "network_ref_1"
if vif['id'] == "vif_2":
return "network_ref_2"
network_info = [{'id': "vif_1"}, {'id': 'vif_2'}]
self.vmops.vif_driver = FakeVifDriver()
vif_map = self.vmops.create_interim_networks(network_info)
self.assertEqual(vif_map, {'vif_1': 'network_ref_1',
'vif_2': 'network_ref_2'})
class RollbackLiveMigrateDestinationTestCase(VMOpsTestBase):
@mock.patch.object(vmops.VMOps, '_delete_networks_and_bridges')
@mock.patch.object(volume_utils, 'find_sr_by_uuid', return_value='sr_ref')
@mock.patch.object(volume_utils, 'forget_sr')
def test_rollback_dest_calls_sr_forget(self, forget_sr, sr_ref):
def test_rollback_dest_calls_sr_forget(self, forget_sr, sr_ref,
delete_networks_bridges):
block_device_info = {'block_device_mapping': [{'connection_info':
{'data': {'volume_id': 'fake-uuid',
'target_iqn': 'fake-iqn',
'target_portal': 'fake-portal'}}}]}
network_info = [{'id': 'vif1'}]
self.vmops.rollback_live_migration_at_destination('instance',
network_info,
block_device_info)
forget_sr.assert_called_once_with(self.vmops._session, 'sr_ref')
delete_networks_bridges.assert_called_once_with(
'instance', [{'id': 'vif1'}])
@mock.patch.object(vmops.VMOps, '_delete_networks_and_bridges')
@mock.patch.object(volume_utils, 'forget_sr')
@mock.patch.object(volume_utils, 'find_sr_by_uuid',
side_effect=test.TestingException)
def test_rollback_dest_handles_exception(self, find_sr_ref, forget_sr):
def test_rollback_dest_handles_exception(self, find_sr_ref, forget_sr,
delete_networks_bridges):
block_device_info = {'block_device_mapping': [{'connection_info':
{'data': {'volume_id': 'fake-uuid',
'target_iqn': 'fake-iqn',
'target_portal': 'fake-portal'}}}]}
network_info = [{'id': 'vif1'}]
self.vmops.rollback_live_migration_at_destination('instance',
network_info,
block_device_info)
self.assertFalse(forget_sr.called)
delete_networks_bridges.assert_called_once_with(
'instance', [{'id': 'vif1'}])
@mock.patch.object(vmops.VMOps, '_resize_ensure_vm_is_shutdown')

View File

@ -3444,26 +3444,35 @@ class XenAPILiveMigrateTestCase(stubs.XenAPITestBaseNoDB):
True, False)
def _add_default_live_migrate_stubs(self, conn):
def fake_generate_vdi_map(destination_sr_ref, _vm_ref):
@classmethod
def fake_generate_vdi_map(cls, destination_sr_ref, _vm_ref):
pass
def fake_get_iscsi_srs(destination_sr_ref, _vm_ref):
@classmethod
def fake_get_iscsi_srs(cls, destination_sr_ref, _vm_ref):
return []
def fake_get_vm_opaque_ref(instance):
@classmethod
def fake_get_vm_opaque_ref(cls, instance):
return "fake_vm"
def fake_lookup_kernel_ramdisk(session, vm):
return ("fake_PV_kernel", "fake_PV_ramdisk")
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)
self.stubs.Set(vm_utils, "lookup_kernel_ramdisk",
fake_lookup_kernel_ramdisk)
@classmethod
def fake_generate_vif_map(cls, vif_uuid_map):
return {'vif_ref1': 'dest_net_ref'}
self.stub_out('nova.virt.xenapi.vmops.VMOps._generate_vdi_map',
fake_generate_vdi_map)
self.stub_out('nova.virt.xenapi.vmops.VMOps._get_iscsi_srs',
fake_get_iscsi_srs)
self.stub_out('nova.virt.xenapi.vmops.VMOps._get_vm_opaque_ref',
fake_get_vm_opaque_ref)
self.stub_out('nova.virt.xenapi.vm_utils.lookup_kernel_ramdisk',
fake_lookup_kernel_ramdisk)
self.stub_out('nova.virt.xenapi.vmops.VMOps._generate_vif_network_map',
fake_generate_vif_map)
def test_check_can_live_migrate_source_with_block_migrate(self):
stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests)
@ -3799,14 +3808,16 @@ class XenAPILiveMigrateTestCase(stubs.XenAPITestBaseNoDB):
self.assertEqual({"vdi0": "dest_sr_ref",
"vdi1": "dest_sr_ref"}, result)
def test_rollback_live_migration_at_destination(self):
@mock.patch.object(vmops.VMOps, "_delete_networks_and_bridges")
def test_rollback_live_migration_at_destination(self, mock_delete_network):
stubs.stubout_session(self.stubs, xenapi_fake.SessionBase)
conn = xenapi_conn.XenAPIDriver(fake.FakeVirtAPI(), False)
network_info = ["fake_vif1"]
with mock.patch.object(conn, "destroy") as mock_destroy:
conn.rollback_live_migration_at_destination("context",
"instance", [], {'block_device_mapping': []})
"instance", network_info, {'block_device_mapping': []})
self.assertFalse(mock_destroy.called)
self.assertTrue(mock_delete_network.called)
class XenAPIInjectMetadataTestCase(stubs.XenAPITestBaseNoDB):

View File

@ -542,6 +542,7 @@ class XenAPIDriver(driver.ComputeDriver):
# any volume that was attached to the destination during
# live migration. XAPI should take care of all other cleanup.
self._vmops.rollback_live_migration_at_destination(instance,
network_info,
block_device_info)
def pre_live_migration(self, context, instance, block_device_info,
@ -567,6 +568,16 @@ class XenAPIDriver(driver.ComputeDriver):
"""
self._vmops.post_live_migration(context, instance, migrate_data)
def post_live_migration_at_source(self, context, instance, network_info):
"""Unplug VIFs from networks at source.
:param context: security context
:param instance: instance object reference
:param network_info: instance network information
"""
self._vmops.post_live_migration_at_source(context, instance,
network_info)
def post_live_migration_at_destination(self, context, instance,
network_info,
block_migration=False,

View File

@ -83,6 +83,9 @@ class XenVIFDriver(object):
raise exception.NovaException(
reason=_("Failed to unplug vif %s") % vif)
def get_vif_interim_net_name(self, vif_id):
return ("net-" + vif_id)[:network_model.NIC_NAME_LEN]
def hot_plug(self, vif, instance, vm_ref, vif_ref):
"""hotplug virtual interface to running instance.
:param nova.network.model.VIF vif:
@ -121,10 +124,20 @@ class XenVIFDriver(object):
"""
pass
def create_vif_interim_network(self, vif):
pass
def delete_network_and_bridge(self, instance, vif):
pass
class XenAPIBridgeDriver(XenVIFDriver):
"""VIF Driver for XenAPI that uses XenAPI to create Networks."""
# NOTE(huanxie): This driver uses linux bridge as backend for XenServer,
# it only supports nova network, for using neutron, you should use
# XenAPIOpenVswitchDriver
def plug(self, instance, vif, vm_ref=None, device=None):
if not vm_ref:
vm_ref = vm_utils.lookup(self._session, instance['name'])
@ -274,8 +287,7 @@ class XenAPIOpenVswitchDriver(XenVIFDriver):
4. delete linux bridge qbr and related ports if exist
"""
super(XenAPIOpenVswitchDriver, self).unplug(instance, vif, vm_ref)
net_name = self.get_vif_interim_net_name(vif)
net_name = self.get_vif_interim_net_name(vif['id'])
network = network_utils.find_network_with_name_label(
self._session, net_name)
if network is None:
@ -287,6 +299,16 @@ class XenAPIOpenVswitchDriver(XenVIFDriver):
# source and target VM will be connected to the same
# interim network.
return
self.delete_network_and_bridge(instance, vif)
def delete_network_and_bridge(self, instance, vif):
net_name = self.get_vif_interim_net_name(vif['id'])
network = network_utils.find_network_with_name_label(
self._session, net_name)
if network is None:
LOG.debug("Didn't find network by name %s", net_name,
instance=instance)
return
LOG.debug('destroying patch port pair for vif: vif_id=%(vif_id)s',
{'vif_id': vif['id']})
bridge_name = self._session.network.get_bridge(network)
@ -468,11 +490,8 @@ class XenAPIOpenVswitchDriver(XenVIFDriver):
# Add port to interim bridge
self._ovs_add_port(bridge_name, patch_port1)
def get_vif_interim_net_name(self, vif):
return ("net-" + vif['id'])[:network_model.NIC_NAME_LEN]
def create_vif_interim_network(self, vif):
net_name = self.get_vif_interim_net_name(vif)
net_name = self.get_vif_interim_net_name(vif['id'])
network_rec = {'name_label': net_name,
'name_description': "interim network for vif",
'other_config': {}}

View File

@ -2355,15 +2355,50 @@ class VMOps(object):
self._generate_vdi_map(
sr_uuid_map[sr_uuid], vm_ref, sr_ref))
vif_map = {}
vif_uuid_map = None
if 'vif_uuid_map' in migrate_data:
vif_uuid_map = migrate_data.vif_uuid_map
if vif_uuid_map:
vif_map = self._generate_vif_network_map(vm_ref, vif_uuid_map)
LOG.debug("Generated vif_map for live migration: %s", vif_map)
options = {}
self._session.call_xenapi(command_name, vm_ref,
migrate_send_data, True,
vdi_map, vif_map, options)
def _generate_vif_network_map(self, vm_ref, vif_uuid_map):
# Generate a mapping dictionary of src_vif_ref: dest_network_ref
vif_map = {}
# vif_uuid_map is dictionary of neutron_vif_uuid: dest_network_ref
vifs = self._session.VM.get_VIFs(vm_ref)
for vif in vifs:
other_config = self._session.VIF.get_other_config(vif)
neutron_id = other_config.get('nicira-iface-id')
if neutron_id is None or neutron_id not in vif_uuid_map.keys():
raise exception.MigrationError(
reason=_('No mapping for source network %s') % (
neutron_id))
network_ref = vif_uuid_map[neutron_id]
vif_map[vif] = network_ref
return vif_map
def create_interim_networks(self, network_info):
# Creating an interim bridge in destination host before live_migration
vif_map = {}
for vif in network_info:
network_ref = self.vif_driver.create_vif_interim_network(vif)
vif_map.update({vif['id']: network_ref})
return vif_map
def pre_live_migration(self, context, instance, block_device_info,
network_info, disk_info, migrate_data):
migrate_data.sr_uuid_map = self.connect_block_device_volumes(
block_device_info)
migrate_data.vif_uuid_map = self.create_interim_networks(network_info)
LOG.debug("pre_live_migration, vif_uuid_map: %(vif_map)s, "
"sr_uuid_map: %(sr_map)s",
{'vif_map': migrate_data.vif_uuid_map,
'sr_map': migrate_data.sr_uuid_map}, instance=instance)
return migrate_data
def live_migrate(self, context, instance, destination_hostname,
@ -2419,6 +2454,11 @@ class VMOps(object):
migrate_data.kernel_file,
migrate_data.ramdisk_file)
def post_live_migration_at_source(self, context, instance, network_info):
LOG.debug('post_live_migration_at_source, delete networks and bridges',
instance=instance)
self._delete_networks_and_bridges(instance, network_info)
def post_live_migration_at_destination(self, context, instance,
network_info, block_migration,
block_device_info):
@ -2433,7 +2473,7 @@ class VMOps(object):
vm_ref = self._get_vm_opaque_ref(instance)
vm_utils.strip_base_mirror_from_vdis(self._session, vm_ref)
def rollback_live_migration_at_destination(self, instance,
def rollback_live_migration_at_destination(self, instance, network_info,
block_device_info):
bdms = block_device_info['block_device_mapping'] or []
@ -2450,6 +2490,20 @@ class VMOps(object):
LOG.exception(_LE('Failed to forget the SR for volume %s'),
params['id'], instance=instance)
# delete VIF and network in destination host
LOG.debug('rollback_live_migration_at_destination, delete networks '
'and bridges', instance=instance)
self._delete_networks_and_bridges(instance, network_info)
def _delete_networks_and_bridges(self, instance, network_info):
# Unplug VIFs and delete networks
for vif in network_info:
try:
self.vif_driver.delete_network_and_bridge(instance, vif)
except Exception:
LOG.exception(_LE('Failed to delete networks and bridges with '
'VIF %s'), vif['id'], instance=instance)
def get_per_instance_usage(self):
"""Get usage info about each active instance."""
usage = {}