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 @obj_base.NovaObjectRegistry.register
class XenapiLiveMigrateData(LiveMigrateData): class XenapiLiveMigrateData(LiveMigrateData):
VERSION = '1.0' # Version 1.0: Initial version
# Version 1.1: Added vif_uuid_map
VERSION = '1.1'
fields = { fields = {
'block_migration': fields.BooleanField(nullable=True), 'block_migration': fields.BooleanField(nullable=True),
@ -230,6 +232,7 @@ class XenapiLiveMigrateData(LiveMigrateData):
'sr_uuid_map': fields.DictOfStringsField(), 'sr_uuid_map': fields.DictOfStringsField(),
'kernel_file': fields.StringField(), 'kernel_file': fields.StringField(),
'ramdisk_file': fields.StringField(), 'ramdisk_file': fields.StringField(),
'vif_uuid_map': fields.DictOfStringsField(),
} }
def to_legacy_dict(self, pre_migration_result=False): def to_legacy_dict(self, pre_migration_result=False):
@ -244,6 +247,8 @@ class XenapiLiveMigrateData(LiveMigrateData):
live_result = { live_result = {
'sr_uuid_map': ('sr_uuid_map' in self and self.sr_uuid_map 'sr_uuid_map': ('sr_uuid_map' in self and self.sr_uuid_map
or {}), or {}),
'vif_uuid_map': ('vif_uuid_map' in self and self.vif_uuid_map
or {}),
} }
if pre_migration_result: if pre_migration_result:
legacy['pre_live_migration_result'] = live_result legacy['pre_live_migration_result'] = live_result
@ -263,6 +268,16 @@ class XenapiLiveMigrateData(LiveMigrateData):
if 'pre_live_migration_result' in legacy: if 'pre_live_migration_result' in legacy:
self.sr_uuid_map = \ self.sr_uuid_map = \
legacy['pre_live_migration_result']['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 @obj_base.NovaObjectRegistry.register

View File

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

View File

@ -1172,7 +1172,7 @@ object_data = {
'VirtualInterface': '1.3-efd3ca8ebcc5ce65fff5a25f31754c54', 'VirtualInterface': '1.3-efd3ca8ebcc5ce65fff5a25f31754c54',
'VirtualInterfaceList': '1.0-9750e2074437b3077e46359102779fc6', 'VirtualInterfaceList': '1.0-9750e2074437b3077e46359102779fc6',
'VolumeUsage': '1.0-6c8190c46ce1469bb3286a1f21c2e475', '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') driver.detach_interface('fake_context', 'fake_instance', 'fake_vif')
mock_detach_interface.assert_called_once_with('fake_instance', mock_detach_interface.assert_called_once_with('fake_instance',
'fake_vif') '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, mock_hot_plug.assert_called_once_with(fake_vif, instance,
'fake_vm_ref', 'fake_vif_ref') 'fake_vm_ref', 'fake_vif_ref')
@mock.patch.object(vif.XenAPIOpenVswitchDriver, '_delete_linux_bridge') @mock.patch.object(vif.XenAPIOpenVswitchDriver,
@mock.patch.object(vif.XenAPIOpenVswitchDriver, '_delete_linux_port') 'delete_network_and_bridge')
@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(network_utils, 'find_network_with_name_label', @mock.patch.object(network_utils, 'find_network_with_name_label',
return_value='fake_network') return_value='fake_network')
@mock.patch.object(vif.XenVIFDriver, 'unplug') @mock.patch.object(vif.XenVIFDriver, 'unplug')
def test_unplug(self, mock_super_unplug, def test_unplug(self, mock_super_unplug,
mock_find_network_with_name_label, mock_find_network_with_name_label,
mock_ovs_del_port, mock_delete_network_bridge):
mock_ovs_del_br,
mock_device_exists,
mock_delete_linux_port,
mock_delete_linux_bridge):
instance = {'name': "fake_instance"} instance = {'name': "fake_instance"}
vm_ref = "fake_vm_ref" vm_ref = "fake_vm_ref"
mock_network_get_VIFs = self.mock_patch_object( mock_network_get_VIFs = self.mock_patch_object(
self._session.network, 'get_VIFs', return_val=None) 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.ovs_driver.unplug(instance, fake_vif, vm_ref)
self.assertTrue(mock_super_unplug.called) self.assertTrue(mock_super_unplug.called)
self.assertTrue(mock_find_network_with_name_label.called) self.assertTrue(mock_find_network_with_name_label.called)
self.assertTrue(mock_network_get_VIFs.called) self.assertTrue(mock_network_get_VIFs.called)
self.assertTrue(mock_network_get_bridge.called) self.assertTrue(mock_delete_network_bridge.called)
self.assertEqual(mock_ovs_del_port.call_count, 2)
self.assertTrue(mock_network_destroy.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_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(vif.XenAPIOpenVswitchDriver, '_ovs_del_port')
@mock.patch.object(network_utils, 'find_network_with_name_label', @mock.patch.object(network_utils, 'find_network_with_name_label',
return_value='fake_network') return_value='fake_network')
@mock.patch.object(vif.XenVIFDriver, 'unplug') def test_delete_network_and_bridge_destroy_exception(self,
def test_unplug_exception(self, mock_super_unplug, mock_find_network,
mock_find_network_with_name_label, mock_ovs_del_port):
mock_ovs_del_port,
mock_ovs_del_br):
instance = {'name': "fake_instance"} instance = {'name': "fake_instance"}
vm_ref = "fake_vm_ref"
self.mock_patch_object( self.mock_patch_object(
self._session.network, 'get_VIFs', return_val=None) self._session.network, 'get_VIFs', return_val=None)
self.mock_patch_object( self.mock_patch_object(
@ -254,8 +261,10 @@ class XenAPIOpenVswitchDriverTestCase(XenVIFDriverTestBase):
side_effect=test.TestingException) side_effect=test.TestingException)
self.assertRaises(exception.VirtualInterfaceUnplugException, self.assertRaises(exception.VirtualInterfaceUnplugException,
self.ovs_driver.unplug, instance, fake_vif, self.ovs_driver.delete_network_and_bridge, instance,
vm_ref) 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, '_device_exists')
@mock.patch.object(vif.XenAPIOpenVswitchDriver, '_brctl_add_if') @mock.patch.object(vif.XenAPIOpenVswitchDriver, '_brctl_add_if')

View File

@ -1411,19 +1411,31 @@ class LiveMigrateTestCase(VMOpsTestBase):
self.vmops._get_host_uuid_from_aggregate, self.vmops._get_host_uuid_from_aggregate,
context, hostname) context, hostname)
@mock.patch.object(vmops.VMOps, 'create_interim_networks')
@mock.patch.object(vmops.VMOps, 'connect_block_device_volumes') @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 = objects.XenapiLiveMigrateData()
migrate_data.block_migration = True migrate_data.block_migration = True
sr_uuid_map = {"sr_uuid": "sr_ref"} 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_connect.return_value = {"sr_uuid": "sr_ref"}
mock_create.return_value = {"neutron_vif_uuid": "dest_network_ref"}
result = self.vmops.pre_live_migration( 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.assertTrue(result.block_migration)
self.assertEqual(result.sr_uuid_map, sr_uuid_map) 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_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): class LiveMigrateFakeVersionTestCase(VMOpsTestBase):
@ -1552,8 +1564,7 @@ class LiveMigrateHelperTestCase(VMOpsTestBase):
mock_forget.assert_called_once_with(self.vmops._session, mock_forget.assert_called_once_with(self.vmops._session,
'sr_ref_1') 'sr_ref_1')
def _call_live_migrate_command_with_migrate_send_data(self, def _call_live_migrate_command_with_migrate_send_data(self, migrate_data):
migrate_data):
command_name = 'test_command' command_name = 'test_command'
vm_ref = "vm_ref" vm_ref = "vm_ref"
@ -1565,21 +1576,27 @@ class LiveMigrateHelperTestCase(VMOpsTestBase):
with mock.patch.object(self.vmops, with mock.patch.object(self.vmops,
"_generate_vdi_map") as mock_gen_vdi_map, \ "_generate_vdi_map") as mock_gen_vdi_map, \
mock.patch.object(self.vmops._session, 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_call_xenapi.side_effect = side_effect
mock_gen_vdi_map.side_effect = [ mock_gen_vdi_map.side_effect = [
{"vdi": "sr_ref"}, {"vdi": "sr_ref_2"}] {"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, self.vmops._call_live_migrate_command(command_name,
vm_ref, migrate_data) 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'} expected_vdi_map = {'vdi': 'sr_ref'}
if 'sr_uuid_map' in migrate_data: if 'sr_uuid_map' in migrate_data:
expected_vdi_map = {'vdi': 'sr_ref_2'} expected_vdi_map = {'vdi': 'sr_ref_2'}
self.assertEqual(mock_call_xenapi.call_args_list[-1], 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, 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], self.assertEqual(mock_gen_vdi_map.call_args_list[0],
mock.call(migrate_data.destination_sr_ref, vm_ref)) 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.migrate_send_data = {"foo": "bar"}
migrate_data.destination_sr_ref = "sr_ref" migrate_data.destination_sr_ref = "sr_ref"
migrate_data.sr_uuid_map = {"sr_uuid2": "sr_ref_3"} 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) self._call_live_migrate_command_with_migrate_send_data(migrate_data)
def test_call_live_migrate_command_with_no_sr_uuid_map(self): 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, self._call_live_migrate_command_with_migrate_send_data,
migrate_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): 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, 'find_sr_by_uuid', return_value='sr_ref')
@mock.patch.object(volume_utils, 'forget_sr') @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': block_device_info = {'block_device_mapping': [{'connection_info':
{'data': {'volume_id': 'fake-uuid', {'data': {'volume_id': 'fake-uuid',
'target_iqn': 'fake-iqn', 'target_iqn': 'fake-iqn',
'target_portal': 'fake-portal'}}}]} 'target_portal': 'fake-portal'}}}]}
network_info = [{'id': 'vif1'}]
self.vmops.rollback_live_migration_at_destination('instance', self.vmops.rollback_live_migration_at_destination('instance',
network_info,
block_device_info) block_device_info)
forget_sr.assert_called_once_with(self.vmops._session, 'sr_ref') 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, 'forget_sr')
@mock.patch.object(volume_utils, 'find_sr_by_uuid', @mock.patch.object(volume_utils, 'find_sr_by_uuid',
side_effect=test.TestingException) 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': block_device_info = {'block_device_mapping': [{'connection_info':
{'data': {'volume_id': 'fake-uuid', {'data': {'volume_id': 'fake-uuid',
'target_iqn': 'fake-iqn', 'target_iqn': 'fake-iqn',
'target_portal': 'fake-portal'}}}]} 'target_portal': 'fake-portal'}}}]}
network_info = [{'id': 'vif1'}]
self.vmops.rollback_live_migration_at_destination('instance', self.vmops.rollback_live_migration_at_destination('instance',
network_info,
block_device_info) block_device_info)
self.assertFalse(forget_sr.called) 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') @mock.patch.object(vmops.VMOps, '_resize_ensure_vm_is_shutdown')

View File

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

View File

@ -542,6 +542,7 @@ class XenAPIDriver(driver.ComputeDriver):
# any volume that was attached to the destination during # any volume that was attached to the destination during
# live migration. XAPI should take care of all other cleanup. # live migration. XAPI should take care of all other cleanup.
self._vmops.rollback_live_migration_at_destination(instance, self._vmops.rollback_live_migration_at_destination(instance,
network_info,
block_device_info) block_device_info)
def pre_live_migration(self, context, instance, 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) 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, def post_live_migration_at_destination(self, context, instance,
network_info, network_info,
block_migration=False, block_migration=False,

View File

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

View File

@ -2355,15 +2355,50 @@ class VMOps(object):
self._generate_vdi_map( self._generate_vdi_map(
sr_uuid_map[sr_uuid], vm_ref, sr_ref)) sr_uuid_map[sr_uuid], vm_ref, sr_ref))
vif_map = {} 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 = {} options = {}
self._session.call_xenapi(command_name, vm_ref, self._session.call_xenapi(command_name, vm_ref,
migrate_send_data, True, migrate_send_data, True,
vdi_map, vif_map, options) 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, def pre_live_migration(self, context, instance, block_device_info,
network_info, disk_info, migrate_data): network_info, disk_info, migrate_data):
migrate_data.sr_uuid_map = self.connect_block_device_volumes( migrate_data.sr_uuid_map = self.connect_block_device_volumes(
block_device_info) 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 return migrate_data
def live_migrate(self, context, instance, destination_hostname, def live_migrate(self, context, instance, destination_hostname,
@ -2419,6 +2454,11 @@ class VMOps(object):
migrate_data.kernel_file, migrate_data.kernel_file,
migrate_data.ramdisk_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, def post_live_migration_at_destination(self, context, instance,
network_info, block_migration, network_info, block_migration,
block_device_info): block_device_info):
@ -2433,7 +2473,7 @@ class VMOps(object):
vm_ref = self._get_vm_opaque_ref(instance) vm_ref = self._get_vm_opaque_ref(instance)
vm_utils.strip_base_mirror_from_vdis(self._session, vm_ref) 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): block_device_info):
bdms = block_device_info['block_device_mapping'] or [] 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'), LOG.exception(_LE('Failed to forget the SR for volume %s'),
params['id'], instance=instance) 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): def get_per_instance_usage(self):
"""Get usage info about each active instance.""" """Get usage info about each active instance."""
usage = {} usage = {}