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:
parent
93bf6ba518
commit
4cd32645fb
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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',
|
||||
}
|
||||
|
||||
|
||||
|
@ -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')
|
||||
|
@ -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')
|
||||
|
@ -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')
|
||||
|
@ -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):
|
||||
|
@ -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,
|
||||
|
@ -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': {}}
|
||||
|
@ -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 = {}
|
||||
|
Loading…
Reference in New Issue
Block a user