summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2017-02-17 02:10:25 +0000
committerGerrit Code Review <review@openstack.org>2017-02-17 02:10:25 +0000
commit0f6e5a89688d9f25412165316a0db29ba15fb01d (patch)
tree47fdbf21bd4a366a091cb4b0f3d7f6fa7c22b594
parentc09728baf92f0afcd1dcc7d2e360a5a0f3434be8 (diff)
parentef0c67934c6ccc0ef6685697b190c2c9324ee321 (diff)
downloadnova-0f6e5a89688d9f25412165316a0db29ba15fb01d.tar.gz
Merge "Fix live migrate with XenServer" into stable/ocata
-rw-r--r--nova/objects/migrate_data.py17
-rw-r--r--nova/tests/unit/objects/test_migrate_data.py36
-rw-r--r--nova/tests/unit/objects/test_objects.py2
-rw-r--r--nova/tests/unit/virt/xenapi/test_driver.py20
-rw-r--r--nova/tests/unit/virt/xenapi/test_vif.py63
-rw-r--r--nova/tests/unit/virt/xenapi/test_vmops.py113
-rw-r--r--nova/tests/unit/virt/xenapi/test_xenapi.py39
-rw-r--r--nova/virt/xenapi/driver.py11
-rw-r--r--nova/virt/xenapi/vif.py31
-rw-r--r--nova/virt/xenapi/vmops.py56
10 files changed, 323 insertions, 65 deletions
diff --git a/nova/objects/migrate_data.py b/nova/objects/migrate_data.py
index ba9135da65..2393ff54a7 100644
--- a/nova/objects/migrate_data.py
+++ b/nova/objects/migrate_data.py
@@ -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
diff --git a/nova/tests/unit/objects/test_migrate_data.py b/nova/tests/unit/objects/test_migrate_data.py
index 07710b5582..baced62af4 100644
--- a/nova/tests/unit/objects/test_migrate_data.py
+++ b/nova/tests/unit/objects/test_migrate_data.py
@@ -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,
diff --git a/nova/tests/unit/objects/test_objects.py b/nova/tests/unit/objects/test_objects.py
index fce7e0f6f1..71b919597f 100644
--- a/nova/tests/unit/objects/test_objects.py
+++ b/nova/tests/unit/objects/test_objects.py
@@ -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',
}
diff --git a/nova/tests/unit/virt/xenapi/test_driver.py b/nova/tests/unit/virt/xenapi/test_driver.py
index 75c9f15b28..13586352ff 100644
--- a/nova/tests/unit/virt/xenapi/test_driver.py
+++ b/nova/tests/unit/virt/xenapi/test_driver.py
@@ -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')
diff --git a/nova/tests/unit/virt/xenapi/test_vif.py b/nova/tests/unit/virt/xenapi/test_vif.py
index 2386509215..377c9a852c 100644
--- a/nova/tests/unit/virt/xenapi/test_vif.py
+++ b/nova/tests/unit/virt/xenapi/test_vif.py
@@ -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')
diff --git a/nova/tests/unit/virt/xenapi/test_vmops.py b/nova/tests/unit/virt/xenapi/test_vmops.py
index ad8354a93d..241bc68504 100644
--- a/nova/tests/unit/virt/xenapi/test_vmops.py
+++ b/nova/tests/unit/virt/xenapi/test_vmops.py
@@ -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')
diff --git a/nova/tests/unit/virt/xenapi/test_xenapi.py b/nova/tests/unit/virt/xenapi/test_xenapi.py
index f16cbd8641..4a027e1ea6 100644
--- a/nova/tests/unit/virt/xenapi/test_xenapi.py
+++ b/nova/tests/unit/virt/xenapi/test_xenapi.py
@@ -3445,26 +3445,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)
@@ -3800,14 +3809,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):
diff --git a/nova/virt/xenapi/driver.py b/nova/virt/xenapi/driver.py
index 7dc7c541f3..037620f27e 100644
--- a/nova/virt/xenapi/driver.py
+++ b/nova/virt/xenapi/driver.py
@@ -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,
diff --git a/nova/virt/xenapi/vif.py b/nova/virt/xenapi/vif.py
index b891761fbf..653386478d 100644
--- a/nova/virt/xenapi/vif.py
+++ b/nova/virt/xenapi/vif.py
@@ -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': {}}
diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py
index 04484141a2..c10afe71d0 100644
--- a/nova/virt/xenapi/vmops.py
+++ b/nova/virt/xenapi/vmops.py
@@ -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 = {}