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 = {}