Skip to content

Commit 66c7f00

Browse files
committed
Fix unplugging VIF when migrate/resize VM
When migrating/resizing VM to destination host that has VIF type difference from source VIF type, it fails due to exception in unplugging VIF on source host after user perform a confirmation action. This change unplugs the vifs in resize_instance and wraps the call to unplug in confirm with an try except block. the call to unplug_vifs in confirm is not removed to support rolling upgrades but a todo is added to remove it after the Wallaby release. Change-Id: I2c195df5fcf844c0587933b5b5995bdca1a3ebed Closes-Bug: #1895220
1 parent d6689e3 commit 66c7f00

File tree

3 files changed

+71
-5
lines changed

3 files changed

+71
-5
lines changed

nova/tests/unit/virt/libvirt/test_driver.py

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21765,6 +21765,7 @@ def test_migrate_disk_and_power_off_exception(
2176521765
context.get_admin_context(), ins_ref, '10.0.0.2',
2176621766
flavor_obj, None)
2176721767

21768+
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver.unplug_vifs')
2176821769
@mock.patch('nova.virt.libvirt.utils.save_and_migrate_vtpm_dir')
2176921770
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver.'
2177021771
'_get_instance_disk_info')
@@ -21779,8 +21780,8 @@ def test_migrate_disk_and_power_off_exception(
2177921780
def _test_migrate_disk_and_power_off(
2178021781
self, ctxt, flavor_obj, mock_execute, mock_exists, mock_rename,
2178121782
mock_is_shared, mock_get_host_ip, mock_destroy,
21782-
mock_get_disk_info, mock_vtpm, block_device_info=None,
21783-
params_for_instance=None):
21783+
mock_get_disk_info, mock_vtpm, mock_unplug_vifs,
21784+
block_device_info=None, params_for_instance=None):
2178421785
"""Test for nova.virt.libvirt.driver.LivirtConnection
2178521786
.migrate_disk_and_power_off.
2178621787
"""
@@ -21798,7 +21799,8 @@ def _test_migrate_disk_and_power_off(
2179821799
self.assertEqual(out, disk_info_text)
2179921800
mock_vtpm.assert_called_with(
2180021801
instance.uuid, mock.ANY, mock.ANY, '10.0.0.2', mock.ANY, mock.ANY)
21801-
21802+
mock_unplug_vifs.assert_called_once()
21803+
mock_unplug_vifs.reset_mock()
2180221804
# dest is same host case
2180321805
out = self.drvr.migrate_disk_and_power_off(
2180421806
ctxt, instance, '10.0.0.1', flavor_obj, None,
@@ -21807,6 +21809,7 @@ def _test_migrate_disk_and_power_off(
2180721809
self.assertEqual(out, disk_info_text)
2180821810
mock_vtpm.assert_called_with(
2180921811
instance.uuid, mock.ANY, mock.ANY, '10.0.0.1', mock.ANY, mock.ANY)
21812+
mock_unplug_vifs.assert_called_once()
2181021813

2181121814
def test_migrate_disk_and_power_off(self):
2181221815
flavor = {'root_gb': 10, 'ephemeral_gb': 20}
@@ -21863,6 +21866,8 @@ def test_migrate_disk_and_power_off_boot_from_volume_backed_snapshot(
2186321866
disconnect_volume.assert_called_with(self.context,
2186421867
mock.sentinel.conn_info_vda, mock.ANY)
2186521868

21869+
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver.unplug_vifs',
21870+
new=mock.Mock())
2186621871
@mock.patch('os.rename')
2186721872
@mock.patch('nova.virt.libvirt.utils.copy_image')
2186821873
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver._destroy')
@@ -21911,6 +21916,8 @@ def test_migrate_disk_and_power_off_swap(self, mock_get_disk_info,
2191121916
for call in mock_copy_image.mock_calls:
2191221917
self.assertFalse(call[0].endswith('.swap'))
2191321918

21919+
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver.unplug_vifs',
21920+
new=mock.Mock())
2191421921
def _test_migrate_disk_and_power_off_resize_check(self, expected_exc):
2191521922
"""Test for nova.virt.libvirt.libvirt_driver.LibvirtConnection
2191621923
.migrate_disk_and_power_off.
@@ -21924,6 +21931,8 @@ def _test_migrate_disk_and_power_off_resize_check(self, expected_exc):
2192421931
self.drvr.migrate_disk_and_power_off,
2192521932
None, instance, '10.0.0.1', flavor_obj, None)
2192621933

21934+
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver.unplug_vifs',
21935+
new=mock.Mock())
2192721936
@mock.patch('nova.virt.libvirt.utils.save_and_migrate_vtpm_dir')
2192821937
@mock.patch('oslo_concurrency.processutils.execute')
2192921938
@mock.patch('os.rename')
@@ -22105,6 +22114,8 @@ def test_migrate_disk_and_power_off_resize_error_eph(self, mock_get,
2210522114
flavor_obj = objects.Flavor(**flavor)
2210622115
self._test_migrate_disk_and_power_off(self.context, flavor_obj)
2210722116

22117+
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver.unplug_vifs',
22118+
new=mock.Mock())
2210822119
@mock.patch('os.rename')
2210922120
@mock.patch('oslo_concurrency.processutils.execute')
2211022121
@mock.patch('nova.virt.libvirt.utils.copy_image')
@@ -22794,6 +22805,44 @@ def test_cleanup_resize_not_same_host(self):
2279422805
mock_undef.assert_called_once_with(instance)
2279522806
mock_unplug.assert_called_once_with(instance, fake_net)
2279622807

22808+
@mock.patch('time.sleep', new=mock.Mock())
22809+
def test_cleanup_resize_not_same_host_works_when_unplug_fails(self):
22810+
CONF.set_override('policy_dirs', [], group='oslo_policy')
22811+
host = 'not' + CONF.host
22812+
instance = self._create_instance({'host': host})
22813+
instance.old_flavor = instance.flavor
22814+
instance.new_flavor = instance.flavor
22815+
fake_net = _fake_network_info(self)
22816+
22817+
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
22818+
22819+
with test.nested(
22820+
mock.patch('nova.compute.utils.is_volume_backed_instance',
22821+
return_value=False),
22822+
mock.patch.object(os.path, 'exists'),
22823+
mock.patch.object(libvirt_utils, 'get_instance_path'),
22824+
mock.patch.object(shutil, 'rmtree'),
22825+
mock.patch.object(drvr.image_backend, 'by_name',
22826+
new_callable=mock.NonCallableMock),
22827+
mock.patch.object(drvr, '_undefine_domain'),
22828+
mock.patch.object(drvr, 'unplug_vifs'),
22829+
mock.patch('nova.virt.libvirt.driver.LOG.debug')
22830+
) as (mock_volume_backed, mock_exists, mock_get_path,
22831+
mock_rmtree, mock_image_by_name, mock_undef, mock_unplug,
22832+
mock_log):
22833+
mock_exists.return_value = True
22834+
mock_get_path.return_value = '/fake/inst'
22835+
error = exception.InternalError("fake error")
22836+
mock_unplug.side_effect = error
22837+
22838+
drvr._cleanup_resize(self.context, instance, fake_net)
22839+
22840+
mock_get_path.assert_called_once_with(instance)
22841+
self.assertEqual(5, mock_rmtree.call_count)
22842+
mock_undef.assert_called_once_with(instance)
22843+
mock_unplug.assert_called_once_with(instance, fake_net)
22844+
mock_log.assert_called_once_with(error, instance=instance)
22845+
2279722846
@mock.patch('time.sleep', new=mock.Mock())
2279822847
def test_cleanup_resize_not_same_host_volume_backed(self):
2279922848
"""Tests cleaning up after a resize is confirmed with a volume-backed

nova/virt/libvirt/driver.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1574,7 +1574,12 @@ def _cleanup_resize(self, context, instance, network_info):
15741574

15751575
if instance.host != CONF.host:
15761576
self._undefine_domain(instance)
1577-
self.unplug_vifs(instance, network_info)
1577+
# TODO(sean-k-mooney): remove this call to unplug_vifs after
1578+
# Wallaby is released. VIFs are now unplugged in resize_instance.
1579+
try:
1580+
self.unplug_vifs(instance, network_info)
1581+
except exception.InternalError as e:
1582+
LOG.debug(e, instance=instance)
15781583

15791584
def _get_volume_driver(self, connection_info):
15801585
driver_type = connection_info.get('driver_volume_type')
@@ -10257,7 +10262,7 @@ def migrate_disk_and_power_off(self, context, instance, dest,
1025710262
exception.ResizeError(reason=reason))
1025810263

1025910264
self.power_off(instance, timeout, retry_interval)
10260-
10265+
self.unplug_vifs(instance, network_info)
1026110266
block_device_mapping = driver.block_device_info_get_mapping(
1026210267
block_device_info)
1026310268
for vol in block_device_mapping:
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
fixes:
3+
- |
4+
Support for cold migration and resize between hosts with different network backends
5+
was previously incomplete. If the os-vif plugin for all network backends available
6+
in the cloud are not installed on all nodes unplugging will fail during confirming
7+
the resize. The issue is caused by the VIF unplug that happened during the resize
8+
confirm action on the source host when the original backend information of the VIF
9+
was not available. The fix moved the unplug to happen during the resize action
10+
when such information is still available. See `bug #1895220`_ for more details.
11+
12+
.. _`bug #1895220`: https://bugs.launchpad.net/nova/+bug/1895220

0 commit comments

Comments
 (0)