Allow driver to properly unplug VIFs on destination on confirm resize

Update PCI related VIFs with the original PCI address on the source
host to allow the virt driver to properly unplug VIFs from
hypervisor, e.g allow the proper VF representor to be unplugged
from the integration bridge in case of a hardware offloaded OVS.

While other approaches are possible for solving the issue,
The approach proposed in the series allows the fix to be safely
backported.

Closes-Bug: #1809095

Conflicts:
    nova/tests/unit/compute/test_compute_mgr.py

Change-Id: Id3c4d839fb1a6da47cfb366b65c0904d281a218f
(cherry picked from commit 000e93df09)
This commit is contained in:
Adrian Chiris 2019-03-12 14:34:35 +02:00
parent 84bb00a86d
commit 77a339c4e8
3 changed files with 128 additions and 8 deletions

View File

@ -28,6 +28,7 @@ terminating it.
import base64
import binascii
import contextlib
import copy
import functools
import inspect
import sys
@ -4005,6 +4006,33 @@ class ComputeManager(manager.Manager):
do_confirm_resize(context, instance, migration.id)
def _get_updated_nw_info_with_pci_mapping(self, nw_info, pci_mapping):
# NOTE(adrianc): This method returns a copy of nw_info if modifications
# are made else it returns the original nw_info.
updated_nw_info = nw_info
if nw_info and pci_mapping:
updated_nw_info = copy.deepcopy(nw_info)
for vif in updated_nw_info:
if vif['vnic_type'] in network_model.VNIC_TYPES_SRIOV:
try:
vif_pci_addr = vif['profile']['pci_slot']
new_addr = pci_mapping[vif_pci_addr].address
vif['profile']['pci_slot'] = new_addr
LOG.debug("Updating VIF's PCI address for VIF %(id)s. "
"Original value %(orig_val)s, "
"new value %(new_val)s",
{'id': vif['id'],
'orig_val': vif_pci_addr,
'new_val': new_addr})
except (KeyError, AttributeError):
with excutils.save_and_reraise_exception():
# NOTE(adrianc): This should never happen. If we
# get here it means there is some inconsistency
# with either 'nw_info' or 'pci_mapping'.
LOG.error("Unexpected error when updating network "
"information with PCI mapping.")
return updated_nw_info
def _confirm_resize(self, context, instance, migration=None):
"""Destroys the source instance."""
self._notify_about_instance_usage(context, instance,
@ -4023,9 +4051,16 @@ class ComputeManager(manager.Manager):
# NOTE(tr3buchet): tear down networks on source host
self.network_api.setup_networks_on_host(context, instance,
migration.source_compute, teardown=True)
network_info = self.network_api.get_instance_nw_info(context,
instance)
# NOTE(adrianc): Populate old PCI device in VIF profile
# to allow virt driver to properly unplug it from Hypervisor.
pci_mapping = (instance.migration_context.
get_pci_mapping_for_migration(True))
network_info = self._get_updated_nw_info_with_pci_mapping(
network_info, pci_mapping)
# TODO(mriedem): Get BDMs here and pass them to the driver.
self.driver.confirm_migration(context, migration, instance,
network_info)

View File

@ -5814,6 +5814,8 @@ class ComputeTestCase(BaseTestCase,
migration_context.migration_id = migration.id
migration_context.old_numa_topology = old_inst_topology
migration_context.new_numa_topology = new_inst_topology
migration_context.old_pci_devices = None
migration_context.new_pci_devices = None
instance.migration_context = migration_context
instance.vm_state = vm_states.RESIZED
@ -5850,12 +5852,14 @@ class ComputeTestCase(BaseTestCase,
old_pci_devices = objects.PciDeviceList(
objects=[objects.PciDevice(vendor_id='1377',
product_id='0047',
address='0000:0a:00.1')])
address='0000:0a:00.1',
request_id=uuids.req1)])
new_pci_devices = objects.PciDeviceList(
objects=[objects.PciDevice(vendor_id='1377',
product_id='0047',
address='0000:0b:00.1')])
address='0000:0b:00.1',
request_id=uuids.req2)])
if expected_pci_addr == old_pci_devices[0].address:
expected_pci_device = old_pci_devices[0]
@ -8270,7 +8274,10 @@ class ComputeTestCase(BaseTestCase,
self.compute.confirm_resize(self.context, instance=instance,
migration=migration)
def test_allow_confirm_resize_on_instance_in_deleting_task_state(self):
@mock.patch.object(objects.MigrationContext,
'get_pci_mapping_for_migration')
def test_allow_confirm_resize_on_instance_in_deleting_task_state(
self, mock_pci_mapping):
instance = self._create_fake_instance_obj()
old_type = instance.flavor
new_type = flavors.get_flavor_by_flavor_id('4')
@ -8278,6 +8285,7 @@ class ComputeTestCase(BaseTestCase,
instance.flavor = new_type
instance.old_flavor = old_type
instance.new_flavor = new_type
instance.migration_context = objects.MigrationContext()
def fake_drop_move_claim(*args, **kwargs):
pass

View File

@ -7208,7 +7208,9 @@ class ComputeManagerMigrationTestCase(test.NoDBTestCase,
mock_confirm, mock_nwapi, mock_notify,
mock_mig_save, mock_mig_get, mock_inst_get):
self._mock_rt()
self.instance.migration_context = objects.MigrationContext()
self.instance.migration_context = objects.MigrationContext(
new_pci_devices=None,
old_pci_devices=None)
self.migration.source_compute = self.instance['host']
self.migration.source_node = self.instance['node']
self.migration.status = 'confirming'
@ -7224,6 +7226,7 @@ class ComputeManagerMigrationTestCase(test.NoDBTestCase,
do_confirm_resize()
@mock.patch('nova.objects.MigrationContext.get_pci_mapping_for_migration')
@mock.patch('nova.compute.utils.add_instance_fault_from_exc')
@mock.patch('nova.objects.Migration.get_by_id')
@mock.patch('nova.objects.Instance.get_by_uuid')
@ -7232,7 +7235,7 @@ class ComputeManagerMigrationTestCase(test.NoDBTestCase,
@mock.patch('nova.objects.Instance.save')
def test_confirm_resize_driver_confirm_migration_fails(
self, instance_save, notify_action, notify_usage,
instance_get_by_uuid, migration_get_by_id, add_fault):
instance_get_by_uuid, migration_get_by_id, add_fault, get_mapping):
"""Tests the scenario that driver.confirm_migration raises some error
to make sure the error is properly handled, like the instance and
migration status is set to 'error'.
@ -7240,6 +7243,7 @@ class ComputeManagerMigrationTestCase(test.NoDBTestCase,
self.migration.status = 'confirming'
migration_get_by_id.return_value = self.migration
instance_get_by_uuid.return_value = self.instance
self.instance.migration_context = objects.MigrationContext()
error = exception.HypervisorUnavailable(
host=self.migration.source_compute)
@ -7247,9 +7251,11 @@ class ComputeManagerMigrationTestCase(test.NoDBTestCase,
mock.patch.object(self.compute, 'network_api'),
mock.patch.object(self.compute.driver, 'confirm_migration',
side_effect=error),
mock.patch.object(self.compute, '_delete_allocation_after_move')
mock.patch.object(self.compute, '_delete_allocation_after_move'),
mock.patch.object(self.compute,
'_get_updated_nw_info_with_pci_mapping')
) as (
network_api, confirm_migration, delete_allocation
network_api, confirm_migration, delete_allocation, pci_mapping
):
self.assertRaises(exception.HypervisorUnavailable,
self.compute.confirm_resize,
@ -7276,6 +7282,59 @@ class ComputeManagerMigrationTestCase(test.NoDBTestCase,
instance_get_by_uuid.assert_called_once()
migration_get_by_id.assert_called_once()
def test_confirm_resize_calls_virt_driver_with_old_pci(self):
@mock.patch.object(self.migration, 'save')
@mock.patch.object(self.compute, '_notify_about_instance_usage')
@mock.patch.object(self.compute, 'network_api')
@mock.patch.object(self.compute.driver, 'confirm_migration')
@mock.patch.object(self.compute, '_delete_allocation_after_move')
@mock.patch.object(self.instance, 'drop_migration_context')
@mock.patch.object(self.instance, 'save')
def do_confirm_resize(mock_save, mock_drop, mock_delete,
mock_confirm, mock_nwapi, mock_notify,
mock_mig_save):
# Mock virt driver confirm_resize() to save the provided
# network_info, we will check it later.
updated_nw_info = []
def driver_confirm_resize(*args, **kwargs):
if 'network_info' in kwargs:
nw_info = kwargs['network_info']
else:
nw_info = args[3]
updated_nw_info.extend(nw_info)
mock_confirm.side_effect = driver_confirm_resize
self._mock_rt()
old_devs = objects.PciDeviceList(
objects=[objects.PciDevice(
address='0000:04:00.2',
request_id=uuids.pcidev1)])
new_devs = objects.PciDeviceList(
objects=[objects.PciDevice(
address='0000:05:00.3',
request_id=uuids.pcidev1)])
self.instance.migration_context = objects.MigrationContext(
new_pci_devices=new_devs,
old_pci_devices=old_devs)
# Create VIF with new_devs[0] PCI address.
nw_info = network_model.NetworkInfo([
network_model.VIF(
id=uuids.port1,
vnic_type=network_model.VNIC_TYPE_DIRECT,
profile={'pci_slot': new_devs[0].address})])
mock_nwapi.get_instance_nw_info.return_value = nw_info
self.migration.source_compute = self.instance['host']
self.migration.source_node = self.instance['node']
self.compute._confirm_resize(self.context, self.instance,
self.migration)
# Assert virt driver confirm_migration() was called
# with the updated nw_info object.
self.assertEqual(old_devs[0].address,
updated_nw_info[0]['profile']['pci_slot'])
do_confirm_resize()
def test_delete_allocation_after_move_confirm_by_migration(self):
with mock.patch.object(self.compute, 'reportclient') as mock_report:
mock_report.delete_allocation_for_instance.return_value = True
@ -8671,6 +8730,24 @@ class ComputeManagerMigrationTestCase(test.NoDBTestCase,
doit()
def test_get_updated_nw_info_with_pci_mapping(self):
old_dev = objects.PciDevice(address='0000:04:00.2')
new_dev = objects.PciDevice(address='0000:05:00.3')
pci_mapping = {old_dev.address: new_dev}
nw_info = network_model.NetworkInfo([
network_model.VIF(
id=uuids.port1,
vnic_type=network_model.VNIC_TYPE_NORMAL),
network_model.VIF(
id=uuids.port2,
vnic_type=network_model.VNIC_TYPE_DIRECT,
profile={'pci_slot': old_dev.address})])
updated_nw_info = self.compute._get_updated_nw_info_with_pci_mapping(
nw_info, pci_mapping)
self.assertDictEqual(nw_info[0], updated_nw_info[0])
self.assertEqual(new_dev.address,
updated_nw_info[1]['profile']['pci_slot'])
class ComputeManagerInstanceUsageAuditTestCase(test.TestCase):
def setUp(self):