From 4cd32645fb26d39a900433c4c1dfecaac1767522 Mon Sep 17 00:00:00 2001 From: Huan Xie Date: Sun, 22 Jan 2017 03:08:40 -0800 Subject: [PATCH] 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 --- nova/objects/migrate_data.py | 17 ++- nova/tests/unit/objects/test_migrate_data.py | 36 +++++- nova/tests/unit/objects/test_objects.py | 2 +- nova/tests/unit/virt/xenapi/test_driver.py | 20 ++++ nova/tests/unit/virt/xenapi/test_vif.py | 63 ++++++----- nova/tests/unit/virt/xenapi/test_vmops.py | 113 +++++++++++++++++-- nova/tests/unit/virt/xenapi/test_xenapi.py | 39 ++++--- nova/virt/xenapi/driver.py | 11 ++ nova/virt/xenapi/vif.py | 31 ++++- nova/virt/xenapi/vmops.py | 56 ++++++++- 10 files changed, 323 insertions(+), 65 deletions(-) diff --git a/nova/objects/migrate_data.py b/nova/objects/migrate_data.py index ba9135da65d4..2393ff54a770 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 07710b55820c..baced62af479 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 fb0da00097ea..b58bd7302d7d 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 75c9f15b2868..13586352ff43 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 238650921591..377c9a852c08 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 ad8354a93d22..241bc6850427 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 18d3f4640db8..503658c72aec 100644 --- a/nova/tests/unit/virt/xenapi/test_xenapi.py +++ b/nova/tests/unit/virt/xenapi/test_xenapi.py @@ -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): diff --git a/nova/virt/xenapi/driver.py b/nova/virt/xenapi/driver.py index 7dc7c541f3c1..037620f27e09 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 b891761fbf38..653386478dca 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 04484141a227..c10afe71d045 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 = {}