Fix port update exception when unshelving an instance with PCI devices

It is possible that _update_port_binding_for_instance() is called
without a migration object, such as when a user unshelves an instance.

If the instance has a port(s) with a PCI device binding, the current
logic extracts a pci mapping from old to new devices from the migration
object and migration context.  If a 'new' device is not found in the
PCI mapping, an exception is thrown.

In the case of an unshelve, there is no migration object (or migration
context), and as such we have an empty pci mapping.

This fix will only check for a new device if we have a migration object.

Conflicts:
      nova/tests/unit/network/test_neutronv2.py

NOTE(mriedem): The conflict is due to not having change
I818d2232f3398489be6303414585840c151e4db7 in Newton.

Closes-Bug: 1677621
Change-Id: I578153ca862753ef5b8041ee3853d3c7b2e2be30
(cherry picked from commit c2ff276c84)
(cherry picked from commit f281c18ba9)
This commit is contained in:
Steven Webster 2017-03-27 12:18:23 -04:00 committed by Matt Riedemann
parent 52a75c7826
commit 70c1eb689a
2 changed files with 54 additions and 3 deletions

View File

@ -2393,7 +2393,8 @@ class API(base_api.NetworkAPI):
# resize is happening on the same host, a new PCI device can be # resize is happening on the same host, a new PCI device can be
# allocated. # allocated.
vnic_type = p.get('binding:vnic_type') vnic_type = p.get('binding:vnic_type')
if vnic_type in network_model.VNIC_TYPES_SRIOV: if (vnic_type in network_model.VNIC_TYPES_SRIOV
and migration is not None):
if not pci_mapping: if not pci_mapping:
pci_mapping = self._get_pci_mapping_for_migration(context, pci_mapping = self._get_pci_mapping_for_migration(context,
instance, migration) instance, migration)

View File

@ -3811,6 +3811,7 @@ class TestNeutronv2WithMock(test.TestCase):
'pci_vendor_info': 'old_pci_vendor_info'}}, 'pci_vendor_info': 'old_pci_vendor_info'}},
{'id': 'fake-port-2', {'id': 'fake-port-2',
'binding:host_id': instance.host}]} 'binding:host_id': instance.host}]}
migration = {'status': 'confirmed'}
list_ports_mock = mock.Mock(return_value=fake_ports) list_ports_mock = mock.Mock(return_value=fake_ports)
get_client_mock.return_value.list_ports = list_ports_mock get_client_mock.return_value.list_ports = list_ports_mock
@ -3818,7 +3819,7 @@ class TestNeutronv2WithMock(test.TestCase):
get_client_mock.return_value.update_port = update_port_mock get_client_mock.return_value.update_port = update_port_mock
self.api._update_port_binding_for_instance(self.context, instance, self.api._update_port_binding_for_instance(self.context, instance,
instance.host) instance.host, migration)
# Assert that update_port is called with the binding:profile # Assert that update_port is called with the binding:profile
# corresponding to the PCI device specified. # corresponding to the PCI device specified.
update_port_mock.assert_called_once_with( update_port_mock.assert_called_once_with(
@ -3864,6 +3865,7 @@ class TestNeutronv2WithMock(test.TestCase):
{'pci_slot': '0000:0a:00.1', {'pci_slot': '0000:0a:00.1',
'physical_network': 'old_phys_net', 'physical_network': 'old_phys_net',
'pci_vendor_info': 'old_pci_vendor_info'}}]} 'pci_vendor_info': 'old_pci_vendor_info'}}]}
migration = {'status': 'confirmed'}
list_ports_mock = mock.Mock(return_value=fake_ports) list_ports_mock = mock.Mock(return_value=fake_ports)
get_client_mock.return_value.list_ports = list_ports_mock get_client_mock.return_value.list_ports = list_ports_mock
@ -3872,7 +3874,55 @@ class TestNeutronv2WithMock(test.TestCase):
self.api._update_port_binding_for_instance, self.api._update_port_binding_for_instance,
self.context, self.context,
instance, instance,
instance.host,
migration)
@mock.patch.object(pci_whitelist.Whitelist, 'get_devspec')
@mock.patch.object(neutronapi, 'get_client', return_value=mock.Mock())
def test_update_port_bindings_for_instance_with_pci_no_migration(self,
get_client_mock,
get_pci_device_devspec_mock):
self.api._has_port_binding_extension = mock.Mock(return_value=True)
devspec = mock.Mock()
devspec.get_tags.return_value = {'physical_network': 'physnet1'}
get_pci_device_devspec_mock.return_value = devspec
instance = fake_instance.fake_instance_obj(self.context)
instance.migration_context = objects.MigrationContext()
instance.migration_context.old_pci_devices = objects.PciDeviceList(
objects=[objects.PciDevice(vendor_id='1377',
product_id='0047',
address='0000:0a:00.1',
compute_node_id=1,
request_id='1234567890')])
instance.migration_context.new_pci_devices = objects.PciDeviceList(
objects=[objects.PciDevice(vendor_id='1377',
product_id='0047',
address='0000:0b:00.1',
compute_node_id=2,
request_id='1234567890')])
instance.pci_devices = instance.migration_context.old_pci_devices
fake_ports = {'ports': [
{'id': 'fake-port-1',
'binding:vnic_type': 'direct',
neutronapi.BINDING_HOST_ID: instance.host,
neutronapi.BINDING_PROFILE:
{'pci_slot': '0000:0a:00.1',
'physical_network': 'phys_net',
'pci_vendor_info': 'pci_vendor_info'}}]}
list_ports_mock = mock.Mock(return_value=fake_ports)
get_client_mock.return_value.list_ports = list_ports_mock
update_port_mock = mock.Mock()
get_client_mock.return_value.update_port = update_port_mock
# Try to update the port binding with no migration object.
self.api._update_port_binding_for_instance(self.context, instance,
instance.host) instance.host)
# No ports should be updated if the port's pci binding did not change.
update_port_mock.assert_not_called()
def test_get_pci_mapping_for_migration(self): def test_get_pci_mapping_for_migration(self):
instance = fake_instance.fake_instance_obj(self.context) instance = fake_instance.fake_instance_obj(self.context)