diff options
Diffstat (limited to 'nova/tests/unit/network/test_neutron.py')
-rw-r--r-- | nova/tests/unit/network/test_neutron.py | 1072 |
1 files changed, 906 insertions, 166 deletions
diff --git a/nova/tests/unit/network/test_neutron.py b/nova/tests/unit/network/test_neutron.py index 5056b70c4e..9aa970aca1 100644 --- a/nova/tests/unit/network/test_neutron.py +++ b/nova/tests/unit/network/test_neutron.py @@ -16,11 +16,11 @@ import collections import copy +from unittest import mock from keystoneauth1.fixture import V2Token from keystoneauth1 import loading as ks_loading from keystoneauth1 import service_token -import mock from neutronclient.common import exceptions from neutronclient.v2_0 import client from oslo_config import cfg @@ -39,9 +39,9 @@ from nova.network import constants from nova.network import model from nova.network import neutron as neutronapi from nova import objects +from nova.objects import fields as obj_fields from nova.objects import network_request as net_req_obj from nova.objects import virtual_interface as obj_vif -from nova.pci import manager as pci_manager from nova.pci import request as pci_request from nova.pci import utils as pci_utils from nova.pci import whitelist as pci_whitelist @@ -514,7 +514,11 @@ class TestAPIBase(test.TestCase): has_dns_extension = False if kwargs.get('dns_extension'): has_dns_extension = True - self.api.extensions[constants.DNS_INTEGRATION] = 1 + self.api.extensions = { + constants.DNS_INTEGRATION: { + 'alias': constants.DNS_INTEGRATION, + }, + } # Net idx is 1-based for compatibility with existing unit tests nets = self.nets[net_idx - 1] @@ -1166,35 +1170,14 @@ class TestAPI(TestAPIBase): mock_get_physnet.assert_called_once_with( mock.ANY, mock.ANY, self.port_data1[0]['network_id']) - @mock.patch.object(neutronapi, 'get_client') - def test_refresh_neutron_extensions_cache(self, mock_get_client): + def test_refresh_neutron_extensions_cache(self): mocked_client = mock.create_autospec(client.Client) - mock_get_client.return_value = mocked_client mocked_client.list_extensions.return_value = { - 'extensions': [{'name': constants.QOS_QUEUE}]} - self.api._refresh_neutron_extensions_cache(self.context) + 'extensions': [{'alias': constants.DNS_INTEGRATION}]} + self.api._refresh_neutron_extensions_cache(mocked_client) self.assertEqual( - {constants.QOS_QUEUE: {'name': constants.QOS_QUEUE}}, + {constants.DNS_INTEGRATION: {'alias': constants.DNS_INTEGRATION}}, self.api.extensions) - mock_get_client.assert_called_once_with(self.context) - mocked_client.list_extensions.assert_called_once_with() - - @mock.patch.object(neutronapi, 'get_client') - def test_populate_neutron_extension_values_rxtx_factor( - self, mock_get_client): - mocked_client = mock.create_autospec(client.Client) - mock_get_client.return_value = mocked_client - mocked_client.list_extensions.return_value = { - 'extensions': [{'name': constants.QOS_QUEUE}]} - flavor = objects.Flavor.get_by_name(self.context, 'm1.small') - flavor['rxtx_factor'] = 1 - instance = objects.Instance(system_metadata={}) - instance.flavor = flavor - port_req_body = {'port': {}} - self.api._populate_neutron_extension_values(self.context, instance, - None, port_req_body) - self.assertEqual(1, port_req_body['port']['rxtx_factor']) - mock_get_client.assert_called_once_with(self.context) mocked_client.list_extensions.assert_called_once_with() def test_allocate_for_instance_1(self): @@ -2414,9 +2397,13 @@ class TestAPI(TestAPIBase): mock_nc.show_port.side_effect = exceptions.PortNotFoundClient if fip_ext_enabled: - self.api.extensions = [constants.FIP_PORT_DETAILS] + self.api.extensions = { + constants.FIP_PORT_DETAILS: { + 'alias': constants.FIP_PORT_DETAILS, + }, + } else: - self.api.extensions = [] + self.api.extensions = {} fip = self.api.get_floating_ip(self.context, uuids.fip_id) @@ -2489,9 +2476,13 @@ class TestAPI(TestAPIBase): mock_nc.show_port.side_effect = exceptions.PortNotFoundClient if fip_ext_enabled: - self.api.extensions = [constants.FIP_PORT_DETAILS] + self.api.extensions = { + constants.FIP_PORT_DETAILS: { + 'alias': constants.FIP_PORT_DETAILS, + }, + } else: - self.api.extensions = [] + self.api.extensions = {} fip = self.api.get_floating_ip_by_address(self.context, '172.1.2.3') @@ -3391,6 +3382,155 @@ class TestAPI(TestAPIBase): mocked_client.list_ports.assert_called_once_with( tenant_id=uuids.fake, device_id=uuids.instance) + @mock.patch.object( + neutronapi.API, + '_get_physnet_tunneled_info', + new=mock.Mock(return_value=(None, False))) + @mock.patch.object( + neutronapi.API, + '_get_preexisting_port_ids', + new=mock.Mock(return_value=[])) + @mock.patch.object( + neutronapi.API, + '_get_subnets_from_port', + new=mock.Mock(return_value=[model.Subnet(cidr='1.0.0.0/8')])) + @mock.patch.object( + neutronapi.API, + '_get_floating_ips_by_fixed_and_port', + new=mock.Mock(return_value=[{'floating_ip_address': '10.0.0.1'}])) + @mock.patch.object(neutronapi, 'get_client') + def test_build_network_info_model_full_vnic_type_change( + self, mock_get_client + ): + mocked_client = mock.create_autospec(client.Client) + mock_get_client.return_value = mocked_client + fake_inst = objects.Instance() + fake_inst.project_id = uuids.fake + fake_inst.uuid = uuids.instance + fake_ports = [ + { + "id": "port1", + "network_id": "net-id", + "tenant_id": uuids.fake, + "admin_state_up": True, + "status": "ACTIVE", + "fixed_ips": [{"ip_address": "1.1.1.1"}], + "mac_address": "de:ad:be:ef:00:01", + "binding:vif_type": model.VIF_TYPE_BRIDGE, + "binding:vnic_type": model.VNIC_TYPE_DIRECT, + "binding:vif_details": {}, + }, + ] + mocked_client.list_ports.return_value = {'ports': fake_ports} + fake_inst.info_cache = objects.InstanceInfoCache.new( + self.context, uuids.instance) + fake_inst.info_cache.network_info = model.NetworkInfo.hydrate([]) + + # build the network info first + nw_infos = self.api._build_network_info_model( + self.context, + fake_inst, + force_refresh=True, + ) + + self.assertEqual(1, len(nw_infos)) + fake_inst.info_cache.network_info = nw_infos + + # change the vnic_type of the port and rebuild the network info + fake_ports[0]["binding:vnic_type"] = model.VNIC_TYPE_MACVTAP + with mock.patch( + "nova.network.neutron.API._log_error_if_vnic_type_changed" + ) as mock_log: + nw_infos = self.api._build_network_info_model( + self.context, + fake_inst, + force_refresh=True, + ) + + mock_log.assert_called_once_with( + fake_ports[0]["id"], "direct", "macvtap", fake_inst) + self.assertEqual(1, len(nw_infos)) + + @mock.patch.object( + neutronapi.API, + '_get_physnet_tunneled_info', + new=mock.Mock(return_value=(None, False))) + @mock.patch.object( + neutronapi.API, + '_get_preexisting_port_ids', + new=mock.Mock(return_value=[])) + @mock.patch.object( + neutronapi.API, + '_get_subnets_from_port', + new=mock.Mock(return_value=[model.Subnet(cidr='1.0.0.0/8')])) + @mock.patch.object( + neutronapi.API, + '_get_floating_ips_by_fixed_and_port', + new=mock.Mock(return_value=[{'floating_ip_address': '10.0.0.1'}])) + @mock.patch.object(neutronapi, 'get_client') + def test_build_network_info_model_single_vnic_type_change( + self, mock_get_client + ): + mocked_client = mock.create_autospec(client.Client) + mock_get_client.return_value = mocked_client + fake_inst = objects.Instance() + fake_inst.project_id = uuids.fake + fake_inst.uuid = uuids.instance + fake_ports = [ + { + "id": "port1", + "network_id": "net-id", + "tenant_id": uuids.fake, + "admin_state_up": True, + "status": "ACTIVE", + "fixed_ips": [{"ip_address": "1.1.1.1"}], + "mac_address": "de:ad:be:ef:00:01", + "binding:vif_type": model.VIF_TYPE_BRIDGE, + "binding:vnic_type": model.VNIC_TYPE_DIRECT, + "binding:vif_details": {}, + }, + ] + fake_nets = [ + { + "id": "net-id", + "name": "foo", + "tenant_id": uuids.fake, + } + ] + mocked_client.list_ports.return_value = {'ports': fake_ports} + fake_inst.info_cache = objects.InstanceInfoCache.new( + self.context, uuids.instance) + fake_inst.info_cache.network_info = model.NetworkInfo.hydrate([]) + + # build the network info first + nw_infos = self.api._build_network_info_model( + self.context, + fake_inst, + fake_nets, + [fake_ports[0]["id"]], + refresh_vif_id=fake_ports[0]["id"], + ) + + self.assertEqual(1, len(nw_infos)) + fake_inst.info_cache.network_info = nw_infos + + # change the vnic_type of the port and rebuild the network info + fake_ports[0]["binding:vnic_type"] = model.VNIC_TYPE_MACVTAP + with mock.patch( + "nova.network.neutron.API._log_error_if_vnic_type_changed" + ) as mock_log: + nw_infos = self.api._build_network_info_model( + self.context, + fake_inst, + fake_nets, + [fake_ports[0]["id"]], + refresh_vif_id=fake_ports[0]["id"], + ) + + mock_log.assert_called_once_with( + fake_ports[0]["id"], "direct", "macvtap", fake_inst) + self.assertEqual(1, len(nw_infos)) + @mock.patch.object(neutronapi, 'get_client') def test_get_subnets_from_port(self, mock_get_client): mocked_client = mock.create_autospec(client.Client) @@ -3473,7 +3613,7 @@ class TestAPI(TestAPIBase): 'provider:network_type': 'vxlan'}]}} test_ext_list = {'extensions': [{'name': 'Multi Provider Network', - 'alias': 'multi-segments'}]} + 'alias': 'multi-provider'}]} mock_client = mock_get_client.return_value mock_client.list_extensions.return_value = test_ext_list @@ -3494,7 +3634,7 @@ class TestAPI(TestAPIBase): 'provider:network_type': 'vlan'}} test_ext_list = {'extensions': [{'name': 'Multi Provider Network', - 'alias': 'multi-segments'}]} + 'alias': 'multi-provider'}]} mock_client = mock_get_client.return_value mock_client.list_extensions.return_value = test_ext_list @@ -3520,7 +3660,7 @@ class TestAPI(TestAPIBase): 'provider:network_type': 'vlan'}]}} test_ext_list = {'extensions': [{'name': 'Multi Provider Network', - 'alias': 'multi-segments'}]} + 'alias': 'multi-provider'}]} mock_client = mock_get_client.return_value mock_client.list_extensions.return_value = test_ext_list @@ -3565,6 +3705,23 @@ class TestAPI(TestAPIBase): self.assertFalse(tunneled) self.assertIsNone(physnet_name) + def test_is_remote_managed(self): + cases = { + (model.VNIC_TYPE_NORMAL, False), + (model.VNIC_TYPE_DIRECT, False), + (model.VNIC_TYPE_MACVTAP, False), + (model.VNIC_TYPE_DIRECT_PHYSICAL, False), + (model.VNIC_TYPE_BAREMETAL, False), + (model.VNIC_TYPE_VIRTIO_FORWARDER, False), + (model.VNIC_TYPE_VDPA, False), + (model.VNIC_TYPE_ACCELERATOR_DIRECT, False), + (model.VNIC_TYPE_ACCELERATOR_DIRECT_PHYSICAL, False), + (model.VNIC_TYPE_REMOTE_MANAGED, True), + } + + for vnic_type, expected in cases: + self.assertEqual(self.api._is_remote_managed(vnic_type), expected) + def _test_get_port_vnic_info( self, mock_get_client, binding_vnic_type, expected_vnic_type, port_resource_request=None, numa_policy=None @@ -3711,6 +3868,27 @@ class TestAPI(TestAPIBase): count = self.api.validate_networks(self.context, requested_networks, 1) self.assertEqual(1, count) + @mock.patch('nova.network.neutron.API._show_port') + def test_deferred_ip_port_none_allocation(self, mock_show): + """Test behavior when the 'none' IP allocation policy is used.""" + port = { + 'network_id': 'my_netid1', + 'device_id': None, + 'id': uuids.port, + 'fixed_ips': [], # no fixed ip + 'ip_allocation': 'none', + 'binding:vif_details': { + 'connectivity': 'l2', + }, + } + + mock_show.return_value = port + + requested_networks = objects.NetworkRequestList( + objects=[objects.NetworkRequest(port_id=port['id'])]) + count = self.api.validate_networks(self.context, requested_networks, 1) + self.assertEqual(1, count) + @mock.patch('oslo_concurrency.lockutils.lock') def test_get_instance_nw_info_locks_per_instance(self, mock_lock): instance = objects.Instance(uuid=uuids.fake) @@ -4356,7 +4534,7 @@ class TestAPI(TestAPIBase): def test_update_instance_vnic_index(self, mock_get_client, mock_refresh_extensions): api = neutronapi.API() - api.extensions = set([constants.VNIC_INDEX_EXT]) + api.extensions = set([constants.VNIC_INDEX]) mock_client = mock_get_client.return_value mock_client.update_port.return_value = 'port' @@ -4381,7 +4559,7 @@ class TestAPI(TestAPIBase): self, get_client_mock ): instance = fake_instance.fake_instance_obj(self.context) - self.api._has_port_binding_extension = mock.Mock(return_value=True) + self.api.has_port_binding_extension = mock.Mock(return_value=True) # We pass in a port profile which has a migration attribute and also # a second port profile attribute 'fake_profile' this can be @@ -4425,7 +4603,7 @@ class TestAPI(TestAPIBase): value is None. """ instance = fake_instance.fake_instance_obj(self.context) - self.api._has_port_binding_extension = mock.Mock(return_value=True) + self.api.has_port_binding_extension = mock.Mock(return_value=True) fake_ports = {'ports': [ {'id': uuids.portid, @@ -4477,16 +4655,16 @@ class TestAPI(TestAPIBase): 'device_owner': 'compute:%s' % instance.availability_zone}}) + @mock.patch.object(neutronapi.API, '_get_vf_pci_device_profile', + new=mock.Mock(return_value={})) @mock.patch( 'nova.network.neutron.API.has_extended_resource_request_extension', new=mock.Mock(return_value=False), ) @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(self, - get_client_mock, - get_pci_device_devspec_mock): - + def test_update_port_bindings_for_instance_with_pci( + self, get_client_mock, get_pci_device_devspec_mock): devspec = mock.Mock() devspec.get_tags.return_value = {'physical_network': 'physnet1'} get_pci_device_devspec_mock.return_value = devspec @@ -4494,17 +4672,21 @@ class TestAPI(TestAPIBase): 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')]) + objects=[objects.PciDevice( + vendor_id='1377', + product_id='0047', + address='0000:0a:00.1', + compute_node_id=1, + request_id='1234567890', + dev_type=obj_fields.PciDeviceType.SRIOV_VF)]) 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')]) + objects=[objects.PciDevice( + vendor_id='1377', + product_id='0047', + address='0000:0b:00.1', + compute_node_id=2, + request_id='1234567890', + dev_type=obj_fields.PciDeviceType.SRIOV_VF)]) instance.pci_devices = instance.migration_context.old_pci_devices # Validate that non-direct port aren't updated (fake-port-2). @@ -4597,7 +4779,7 @@ class TestAPI(TestAPIBase): 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) + self.api.has_port_binding_extension = mock.Mock(return_value=True) devspec = mock.Mock() devspec.get_tags.return_value = {'physical_network': 'physnet1'} @@ -4647,7 +4829,7 @@ class TestAPI(TestAPIBase): def test_update_port_bindings_for_instance_with_same_host_failed_vif_type( self, get_client_mock): instance = fake_instance.fake_instance_obj(self.context) - self.api._has_port_binding_extension = mock.Mock(return_value=True) + self.api.has_port_binding_extension = mock.Mock(return_value=True) list_ports_mock = mock.Mock() update_port_mock = mock.Mock() @@ -4692,7 +4874,7 @@ class TestAPI(TestAPIBase): def test_update_port_bindings_for_instance_with_diff_host_unbound_vif_type( self, get_client_mock): instance = fake_instance.fake_instance_obj(self.context) - self.api._has_port_binding_extension = mock.Mock(return_value=True) + self.api.has_port_binding_extension = mock.Mock(return_value=True) binding_profile = {'fake_profile': 'fake_data', constants.MIGRATING_ATTR: 'my-dest-host'} @@ -4775,6 +4957,174 @@ class TestAPI(TestAPIBase): 'nova.network.neutron.API.has_extended_resource_request_extension', new=mock.Mock(return_value=False), ) + @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_sriov_pf( + self, get_client_mock, get_pci_device_devspec_mock + ): + 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='8086', + product_id='154d', + address='0000:0a:01', + compute_node_id=1, + request_id=uuids.pci_req, + dev_type=obj_fields.PciDeviceType.SRIOV_PF, + extra_info={'mac_address': 'b4:96:91:34:f4:36'}, + ) + ] + ) + instance.pci_devices = instance.migration_context.old_pci_devices + instance.migration_context.new_pci_devices = objects.PciDeviceList( + objects=[ + objects.PciDevice( + vendor_id='8086', + product_id='154d', + address='0000:0a:02', + compute_node_id=2, + request_id=uuids.pci_req, + dev_type=obj_fields.PciDeviceType.SRIOV_PF, + extra_info={'mac_address': 'b4:96:91:34:f4:dd'}, + ) + ] + ) + instance.pci_devices = instance.migration_context.new_pci_devices + + fake_ports = { + 'ports': [ + { + 'id': uuids.port, + 'binding:vnic_type': 'direct-physical', + constants.BINDING_HOST_ID: 'fake-host-old', + constants.BINDING_PROFILE: { + 'pci_slot': '0000:0a:01', + 'physical_network': 'old_phys_net', + 'pci_vendor_info': 'old_pci_vendor_info', + }, + }, + ] + } + + migration = objects.Migration( + status='confirmed', migration_type='migration') + 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 + + self.api._update_port_binding_for_instance( + self.context, instance, instance.host, migration) + + # Assert that update_port is called with the binding:profile + # corresponding to the PCI device specified including MAC address. + update_port_mock.assert_called_once_with( + uuids.port, + { + 'port': { + constants.BINDING_HOST_ID: 'fake-host', + 'device_owner': 'compute:%s' % instance.availability_zone, + constants.BINDING_PROFILE: { + 'pci_slot': '0000:0a:02', + 'physical_network': 'physnet1', + 'pci_vendor_info': '8086:154d', + 'device_mac_address': 'b4:96:91:34:f4:dd', + }, + } + }, + ) + + @mock.patch( + 'nova.network.neutron.API.has_extended_resource_request_extension', + new=mock.Mock(return_value=False), + ) + @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_sriov_pf_no_migration( + self, get_client_mock, get_pci_device_devspec_mock + ): + 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.pci_requests = objects.InstancePCIRequests( + instance_uuid=instance.uuid, + requests=[ + objects.InstancePCIRequest( + requester_id=uuids.port, + request_id=uuids.pci_req, + ) + ], + ) + instance.pci_devices = objects.PciDeviceList( + objects=[ + objects.PciDevice( + vendor_id='8086', + product_id='154d', + address='0000:0a:02', + compute_node_id=2, + request_id=uuids.pci_req, + dev_type=obj_fields.PciDeviceType.SRIOV_PF, + extra_info={'mac_address': 'b4:96:91:34:f4:36'}, + ) + ] + ) + + fake_ports = { + 'ports': [ + { + 'id': uuids.port, + 'binding:vnic_type': 'direct-physical', + constants.BINDING_HOST_ID: 'fake-host-old', + constants.BINDING_PROFILE: { + 'pci_slot': '0000:0a:01', + 'physical_network': 'old_phys_net', + 'pci_vendor_info': 'old_pci_vendor_info', + 'device_mac_address': 'b4:96:91:34:f4:dd' + }, + }, + ] + } + + 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 + + self.api._update_port_binding_for_instance( + self.context, instance, instance.host) + + # Assert that update_port is called with the binding:profile + # corresponding to the PCI device specified including MAC address. + update_port_mock.assert_called_once_with( + uuids.port, + { + 'port': { + constants.BINDING_HOST_ID: 'fake-host', + 'device_owner': 'compute:%s' % instance.availability_zone, + constants.BINDING_PROFILE: { + 'pci_slot': '0000:0a:02', + 'physical_network': 'physnet1', + 'pci_vendor_info': '8086:154d', + 'device_mac_address': 'b4:96:91:34:f4:36', + }, + } + }, + ) + + @mock.patch( + 'nova.network.neutron.API.has_extended_resource_request_extension', + new=mock.Mock(return_value=False), + ) @mock.patch.object(neutronapi, 'get_client', return_value=mock.Mock()) def test_update_port_bindings_for_instance_with_resource_req( self, get_client_mock): @@ -4982,7 +5332,7 @@ class TestAPI(TestAPIBase): self, get_client_mock): instance = fake_instance.fake_instance_obj(self.context) - self.api._has_port_binding_extension = mock.Mock(return_value=True) + self.api.has_port_binding_extension = mock.Mock(return_value=True) # We test with an instance host and destination_host where the # port will be moving. get_ports = {'ports': [ @@ -5012,7 +5362,7 @@ class TestAPI(TestAPIBase): destination host and the binding:profile is None in the port. """ instance = fake_instance.fake_instance_obj(self.context) - self.api._has_port_binding_extension = mock.Mock(return_value=True) + self.api.has_port_binding_extension = mock.Mock(return_value=True) # We test with an instance host and destination_host where the # port will be moving but with binding:profile set to None. get_ports = { @@ -5043,7 +5393,7 @@ class TestAPI(TestAPIBase): self, get_client_mock): instance = fake_instance.fake_instance_obj(self.context) - self.api._has_port_binding_extension = mock.Mock(return_value=True) + self.api.has_port_binding_extension = mock.Mock(return_value=True) port_id = uuids.port_id get_ports = {'ports': [ {'id': port_id, @@ -5063,7 +5413,7 @@ class TestAPI(TestAPIBase): self, get_client_mock): instance = fake_instance.fake_instance_obj(self.context) - self.api._has_port_binding_extension = mock.Mock(return_value=True) + self.api.has_port_binding_extension = mock.Mock(return_value=True) get_ports = {'ports': [ {'id': uuids.port_id, constants.BINDING_HOST_ID: instance.host}]} @@ -5099,10 +5449,10 @@ class TestAPI(TestAPIBase): self, get_client_mock): instance = fake_instance.fake_instance_obj(self.context) - self.api._has_port_binding_extension = mock.Mock(return_value=True) + self.api.has_port_binding_extension = mock.Mock(return_value=True) migrate_profile = { constants.MIGRATING_ATTR: 'new-host'} - # Pass a port with an migration porfile attribute. + # Pass a port with an migration profile attribute. port_id = uuids.port_id get_ports = {'ports': [ {'id': port_id, @@ -5111,8 +5461,9 @@ class TestAPI(TestAPIBase): self.api.list_ports = mock.Mock(return_value=get_ports) mocked_client = get_client_mock.return_value - with mock.patch.object(self.api, 'supports_port_binding_extension', - return_value=True): + with mock.patch.object( + self.api, 'has_port_binding_extension', return_value=True, + ): self.api.setup_networks_on_host(self.context, instance, host='new-host', @@ -5130,10 +5481,10 @@ class TestAPI(TestAPIBase): which is raised through to the caller. """ instance = fake_instance.fake_instance_obj(self.context) - self.api._has_port_binding_extension = mock.Mock(return_value=True) + self.api.has_port_binding_extension = mock.Mock(return_value=True) migrate_profile = { constants.MIGRATING_ATTR: 'new-host'} - # Pass a port with an migration porfile attribute. + # Pass a port with an migration profile attribute. get_ports = { 'ports': [ {'id': uuids.port1, @@ -5148,8 +5499,9 @@ class TestAPI(TestAPIBase): mocked_client = get_client_mock.return_value mocked_client.delete_port_binding.side_effect = NeutronError - with mock.patch.object(self.api, 'supports_port_binding_extension', - return_value=True): + with mock.patch.object( + self.api, 'has_port_binding_extension', return_value=True, + ): ex = self.assertRaises( exception.PortBindingDeletionFailed, self.api.setup_networks_on_host, @@ -5171,15 +5523,15 @@ class TestAPI(TestAPIBase): self, get_client_mock): instance = fake_instance.fake_instance_obj(self.context) - self.api._has_port_binding_extension = mock.Mock(return_value=True) - # Pass a port without any migration porfile attribute. + self.api.has_port_binding_extension = mock.Mock(return_value=True) + # Pass a port without any migration profile attribute. get_ports = {'ports': [ {'id': uuids.port_id, constants.BINDING_HOST_ID: instance.host}]} self.api.list_ports = mock.Mock(return_value=get_ports) update_port_mock = mock.Mock() get_client_mock.return_value.update_port = update_port_mock - with mock.patch.object(self.api, 'supports_port_binding_extension', + with mock.patch.object(self.api, 'has_port_binding_extension', return_value=False): self.api.setup_networks_on_host(self.context, instance, @@ -5212,7 +5564,8 @@ class TestAPI(TestAPIBase): self.assertEqual(['2', '3'], result, "Invalid preexisting ports") @mock.patch('nova.network.neutron.API._show_port') - def _test_unbind_ports_get_client(self, mock_neutron, mock_show): + @mock.patch('nova.network.neutron.get_client') + def test_unbind_ports_get_client(self, mock_neutron, mock_show): mock_ctx = mock.Mock(is_admin=False) ports = ["1", "2", "3"] @@ -5228,23 +5581,18 @@ class TestAPI(TestAPIBase): self.assertEqual(1, mock_neutron.call_count) mock_neutron.assert_has_calls(get_client_calls, True) - @mock.patch('nova.network.neutron.get_client') - def test_unbind_ports_get_client_binding_extension(self, - mock_neutron): - self._test_unbind_ports_get_client(mock_neutron) - - @mock.patch('nova.network.neutron.get_client') - def test_unbind_ports_get_client(self, mock_neutron): - self._test_unbind_ports_get_client(mock_neutron) - + @mock.patch('nova.network.neutron.API.has_dns_extension', + new=mock.Mock(return_value=False)) @mock.patch('nova.network.neutron.API._show_port') - def _test_unbind_ports(self, mock_neutron, mock_show): + @mock.patch('nova.network.neutron.get_client') + def test_unbind_ports(self, mock_neutron, mock_show): mock_client = mock.Mock() mock_update_port = mock.Mock() mock_client.update_port = mock_update_port mock_ctx = mock.Mock(is_admin=False) ports = ["1", "2", "3"] mock_show.side_effect = [{"id": "1"}, {"id": "2"}, {"id": "3"}] + api = neutronapi.API() api._unbind_ports(mock_ctx, ports, mock_neutron, mock_client) @@ -5258,14 +5606,6 @@ class TestAPI(TestAPIBase): self.assertEqual(3, mock_update_port.call_count) mock_update_port.assert_has_calls(update_port_calls) - @mock.patch('nova.network.neutron.get_client') - def test_unbind_ports_binding_ext(self, mock_neutron): - self._test_unbind_ports(mock_neutron) - - @mock.patch('nova.network.neutron.get_client') - def test_unbind_ports(self, mock_neutron): - self._test_unbind_ports(mock_neutron) - def test_unbind_ports_no_port_ids(self): # Tests that None entries in the ports list are filtered out. mock_client = mock.Mock() @@ -5279,7 +5619,11 @@ class TestAPI(TestAPIBase): @mock.patch( 'nova.network.neutron.API.has_extended_resource_request_extension', - new=mock.Mock() + new=mock.Mock(return_value=True), + ) + @mock.patch( + 'nova.network.neutron.API.has_dns_extension', + new=mock.Mock(return_value=True), ) @mock.patch('nova.network.neutron.API.get_instance_nw_info') @mock.patch('nova.network.neutron.excutils') @@ -5822,9 +6166,13 @@ class TestAPI(TestAPIBase): mock_nc.list_ports.return_value = {'ports': []} if fip_ext_enabled: - self.api.extensions = [constants.FIP_PORT_DETAILS] + self.api.extensions = { + constants.FIP_PORT_DETAILS: { + 'alias': constants.FIP_PORT_DETAILS, + }, + } else: - self.api.extensions = [] + self.api.extensions = {} fips = self.api.get_floating_ips_by_project(self.context) @@ -5857,6 +6205,8 @@ class TestAPI(TestAPIBase): """Make sure we don't fail for floating IPs without attached ports.""" self._test_get_floating_ips_by_project(False, False) + @mock.patch('nova.network.neutron.API.has_dns_extension', + new=mock.Mock(return_value=True)) @mock.patch('nova.network.neutron.API._show_port') def test_unbind_ports_reset_dns_name_by_admin(self, mock_show): neutron = mock.Mock() @@ -5867,7 +6217,6 @@ class TestAPI(TestAPIBase): } } port_client = mock.Mock() - self.api.extensions = [constants.DNS_INTEGRATION] ports = [uuids.port_id] mock_show.return_value = {'id': uuids.port} self.api._unbind_ports(self.context, ports, neutron, port_client) @@ -5880,6 +6229,8 @@ class TestAPI(TestAPIBase): uuids.port_id, port_req_body) neutron.update_port.assert_not_called() + @mock.patch('nova.network.neutron.API.has_dns_extension', + new=mock.Mock(return_value=True)) @mock.patch('nova.network.neutron.API._show_port') def test_unbind_ports_reset_dns_name_by_non_admin(self, mock_show): neutron = mock.Mock() @@ -5890,7 +6241,6 @@ class TestAPI(TestAPIBase): } } port_client = mock.Mock() - self.api.extensions = [constants.DNS_INTEGRATION] ports = [uuids.port_id] mock_show.return_value = {'id': uuids.port} self.api._unbind_ports(self.context, ports, neutron, port_client) @@ -5904,6 +6254,8 @@ class TestAPI(TestAPIBase): neutron.update_port.assert_called_once_with( uuids.port_id, non_admin_port_req_body) + @mock.patch('nova.network.neutron.API.has_dns_extension', + new=mock.Mock(return_value=False)) @mock.patch('nova.network.neutron.API._show_port') def test_unbind_ports_reset_allocation_in_port_binding(self, mock_show): neutron = mock.Mock() @@ -5919,6 +6271,8 @@ class TestAPI(TestAPIBase): port_client.update_port.assert_called_once_with( uuids.port_id, port_req_body) + @mock.patch('nova.network.neutron.API.has_dns_extension', + new=mock.Mock(return_value=False)) @mock.patch('nova.network.neutron.API._show_port') def test_unbind_ports_reset_binding_profile(self, mock_show): neutron = mock.Mock() @@ -5928,20 +6282,22 @@ class TestAPI(TestAPIBase): 'id': uuids.port, 'binding:profile': {'pci_vendor_info': '1377:0047', 'pci_slot': '0000:0a:00.1', + 'card_serial_number': 'MT2113X00000', 'physical_network': 'physnet1', 'capabilities': ['switchdev']} } self.api._unbind_ports(self.context, ports, neutron, port_client) port_req_body = {'port': {'binding:host_id': None, 'binding:profile': - {'physical_network': 'physnet1', - 'capabilities': ['switchdev']}, + {'capabilities': ['switchdev']}, 'device_id': '', 'device_owner': ''} } port_client.update_port.assert_called_once_with( uuids.port_id, port_req_body) + @mock.patch('nova.network.neutron.API.has_dns_extension', + new=mock.Mock(return_value=False)) @mock.patch('nova.network.neutron.API._populate_neutron_extension_values') @mock.patch('nova.network.neutron.API._update_port', # called twice, fails on the 2nd call and triggers the cleanup @@ -6014,7 +6370,6 @@ class TestAPI(TestAPIBase): def test_unbind_ports_port_show_portnotfound(self, mock_log, mock_show): api = neutronapi.API() neutron_client = mock.Mock() - mock_show.return_value = {'id': uuids.port} api._unbind_ports(self.context, [uuids.port_id], neutron_client, neutron_client) mock_show.assert_called_once_with( @@ -6023,6 +6378,65 @@ class TestAPI(TestAPIBase): neutron_client=mock.ANY) mock_log.assert_not_called() + @mock.patch( + 'nova.network.neutron.API.has_dns_extension', + new=mock.Mock(return_value=False), + ) + @mock.patch('nova.network.neutron.API._show_port') + @mock.patch.object(neutronapi, 'LOG') + def test_unbind_ports_port_show_portnotfound_multiple_ports( + self, mock_log, mock_show, + ): + """Ensure we continue unbinding ports even when one isn't found.""" + mock_show.side_effect = [ + exception.PortNotFound(port_id=uuids.port_a), + {'id': uuids.port_b}, + ] + api = neutronapi.API() + neutron_client = mock.Mock() + + api._unbind_ports( + self.context, + [uuids.port_a, uuids.port_b], + neutron_client, + neutron_client, + ) + + mock_show.assert_has_calls( + [ + mock.call( + self.context, + uuids.port_a, + fields=['binding:profile', 'network_id'], + neutron_client=neutron_client, + ), + mock.call( + self.context, + uuids.port_b, + fields=['binding:profile', 'network_id'], + neutron_client=neutron_client, + ), + ] + ) + # Only the port that exists should be updated + neutron_client.update_port.assert_called_once_with( + uuids.port_b, + { + 'port': { + 'device_id': '', + 'device_owner': '', + 'binding:profile': {}, + 'binding:host_id': None, + } + } + ) + mock_log.exception.assert_not_called() + mock_log.debug.assert_called_with( + 'Unable to show port %s as it no longer exists.', uuids.port_a, + ) + + @mock.patch('nova.network.neutron.API.has_dns_extension', + new=mock.Mock(return_value=False)) @mock.patch('nova.network.neutron.API._show_port', side_effect=Exception) @mock.patch.object(neutronapi.LOG, 'exception') @@ -6040,9 +6454,11 @@ class TestAPI(TestAPIBase): 'binding:profile': {}, 'binding:host_id': None}}) self.assertTrue(mock_log.called) + @mock.patch('nova.network.neutron.API.has_dns_extension', + new=mock.Mock(return_value=False)) @mock.patch('nova.network.neutron.API._show_port') @mock.patch.object(neutronapi.LOG, 'exception') - def test_unbind_ports_portnotfound(self, mock_log, mock_show): + def test_unbind_ports_port_update_portnotfound(self, mock_log, mock_show): api = neutronapi.API() neutron_client = mock.Mock() neutron_client.update_port = mock.Mock( @@ -6056,9 +6472,13 @@ class TestAPI(TestAPIBase): 'binding:profile': {}, 'binding:host_id': None}}) mock_log.assert_not_called() + @mock.patch('nova.network.neutron.API.has_dns_extension', + new=mock.Mock(return_value=False)) @mock.patch('nova.network.neutron.API._show_port') @mock.patch.object(neutronapi.LOG, 'exception') - def test_unbind_ports_unexpected_error(self, mock_log, mock_show): + def test_unbind_ports_port_update_unexpected_error( + self, mock_log, mock_show, + ): api = neutronapi.API() neutron_client = mock.Mock() neutron_client.update_port = mock.Mock( @@ -6140,7 +6560,8 @@ class TestAPI(TestAPIBase): objects.NetworkRequest(port_id=uuids.portid_4), objects.NetworkRequest(port_id=uuids.portid_5), objects.NetworkRequest(port_id=uuids.trusted_port), - objects.NetworkRequest(port_id=uuids.portid_vdpa)]) + objects.NetworkRequest(port_id=uuids.portid_vdpa), + objects.NetworkRequest(port_id=uuids.portid_remote_managed)]) pci_requests = objects.InstancePCIRequests(requests=[]) # _get_port_vnic_info should be called for every NetworkRequest with a # port_id attribute (so six times) @@ -6154,13 +6575,14 @@ class TestAPI(TestAPIBase): (model.VNIC_TYPE_DIRECT, True, 'netN', mock.sentinel.resource_request2, None, None), (model.VNIC_TYPE_VDPA, None, 'netN', None, None, None), + (model.VNIC_TYPE_REMOTE_MANAGED, None, 'netN', None, None, None), ] # _get_physnet_tunneled_info should be called for every NetworkRequest # (so seven times) mock_get_physnet_tunneled_info.side_effect = [ ('physnet1', False), ('physnet1', False), ('', True), ('physnet1', False), ('physnet2', False), ('physnet3', False), - ('physnet4', False), ('physnet1', False) + ('physnet4', False), ('physnet1', False), ('physnet1', False), ] api = neutronapi.API() @@ -6177,13 +6599,16 @@ class TestAPI(TestAPIBase): mock.sentinel.request_group1, mock.sentinel.request_group2], port_resource_requests) - self.assertEqual(6, len(pci_requests.requests)) + self.assertEqual(7, len(pci_requests.requests)) has_pci_request_id = [net.pci_request_id is not None for net in requested_networks.objects] self.assertEqual(pci_requests.requests[3].spec[0]["dev_type"], "type-PF") self.assertEqual(pci_requests.requests[5].spec[0]["dev_type"], "vdpa") - expected_results = [True, False, False, True, True, True, True, True] + self.assertEqual(pci_requests.requests[6].spec[0]["remote_managed"], + 'True') + expected_results = [True, False, False, True, True, True, True, True, + True] self.assertEqual(expected_results, has_pci_request_id) # Make sure only the trusted VF has the 'trusted' tag set in the spec. for pci_req in pci_requests.requests: @@ -6195,11 +6620,23 @@ class TestAPI(TestAPIBase): else: self.assertNotIn(pci_request.PCI_TRUSTED_TAG, spec) + # Only remote-managed ports must have the remote_managed tag set + # to True. + for pci_req in pci_requests.requests: + spec = pci_req.spec[0] + if pci_req.requester_id == uuids.portid_remote_managed: + self.assertEqual('True', + spec[pci_request.PCI_REMOTE_MANAGED_TAG]) + else: + self.assertEqual('False', + spec[pci_request.PCI_REMOTE_MANAGED_TAG]) + # Only SRIOV ports and those with a resource_request will have # pci_req.requester_id. self.assertEqual( [uuids.portid_1, uuids.portid_3, uuids.portid_4, uuids.portid_5, - uuids.trusted_port, uuids.portid_vdpa], + uuids.trusted_port, uuids.portid_vdpa, + uuids.portid_remote_managed], [pci_req.requester_id for pci_req in pci_requests.requests]) self.assertCountEqual( @@ -6671,7 +7108,7 @@ class TestAPI(TestAPIBase): """Tests that migrate_instance_start exits early if neutron doesn't have the binding-extended API extension. """ - with mock.patch.object(self.api, 'supports_port_binding_extension', + with mock.patch.object(self.api, 'has_port_binding_extension', return_value=False): self.api.migrate_instance_start( self.context, mock.sentinel.instance, {}) @@ -6691,8 +7128,9 @@ class TestAPI(TestAPIBase): migration = objects.Migration( source_compute='source', dest_compute='dest') - with mock.patch.object(self.api, 'supports_port_binding_extension', - return_value=True): + with mock.patch.object( + self.api, 'has_port_binding_extension', return_value=True, + ): self.api.migrate_instance_start( self.context, instance, migration) @@ -6716,8 +7154,9 @@ class TestAPI(TestAPIBase): migration = objects.Migration( source_compute='source', dest_compute='dest') - with mock.patch.object(self.api, 'supports_port_binding_extension', - return_value=True): + with mock.patch.object( + self.api, 'has_port_binding_extension', return_value=True, + ): self.api.migrate_instance_start( self.context, instance, migration) @@ -6743,8 +7182,9 @@ class TestAPI(TestAPIBase): migration = objects.Migration( source_compute='source', dest_compute='dest') - with mock.patch.object(self.api, 'supports_port_binding_extension', - return_value=True): + with mock.patch.object( + self.api, 'has_port_binding_extension', return_value=True, + ): self.api.migrate_instance_start( self.context, instance, migration) @@ -6767,8 +7207,9 @@ class TestAPI(TestAPIBase): migration = objects.Migration( source_compute='source', dest_compute='dest') - with mock.patch.object(self.api, 'supports_port_binding_extension', - return_value=True): + with mock.patch.object( + self.api, 'has_port_binding_extension', return_value=True, + ): self.api.migrate_instance_start( self.context, instance, migration) @@ -6945,13 +7386,17 @@ class TestAPI(TestAPIBase): req_lvl_params.same_subtree, ) - def test_get_segment_ids_for_network_no_segment_ext(self): + @mock.patch.object(neutronapi, 'get_client') + def test_get_segment_ids_for_network_no_segment_ext(self, mock_client): + mocked_client = mock.create_autospec(client.Client) + mock_client.return_value = mocked_client with mock.patch.object( - self.api, '_has_segment_extension', return_value=False + self.api, 'has_segment_extension', return_value=False, ): self.assertEqual( [], self.api.get_segment_ids_for_network(self.context, uuids.network_id)) + mock_client.assert_called_once_with(self.context, admin=True) @mock.patch.object(neutronapi, 'get_client') def test_get_segment_ids_for_network_passes(self, mock_client): @@ -6960,26 +7405,44 @@ class TestAPI(TestAPIBase): mock_client.return_value = mocked_client mocked_client.list_subnets.return_value = subnets with mock.patch.object( - self.api, '_has_segment_extension', return_value=True + self.api, 'has_segment_extension', return_value=True, ): res = self.api.get_segment_ids_for_network( self.context, uuids.network_id) self.assertEqual([uuids.segment_id], res) + mock_client.assert_called_once_with(self.context, admin=True) mocked_client.list_subnets.assert_called_once_with( network_id=uuids.network_id, fields='segment_id') @mock.patch.object(neutronapi, 'get_client') - def test_get_segment_ids_for_network_with_no_segments(self, mock_client): + def test_get_segment_ids_for_network_with_segments_none(self, mock_client): subnets = {'subnets': [{'segment_id': None}]} mocked_client = mock.create_autospec(client.Client) mock_client.return_value = mocked_client mocked_client.list_subnets.return_value = subnets with mock.patch.object( - self.api, '_has_segment_extension', return_value=True + self.api, 'has_segment_extension', return_value=True, + ): + res = self.api.get_segment_ids_for_network( + self.context, uuids.network_id) + self.assertEqual([], res) + mock_client.assert_called_once_with(self.context, admin=True) + mocked_client.list_subnets.assert_called_once_with( + network_id=uuids.network_id, fields='segment_id') + + @mock.patch.object(neutronapi, 'get_client') + def test_get_segment_ids_for_network_with_no_segments(self, mock_client): + subnets = {'subnets': [{}]} + mocked_client = mock.create_autospec(client.Client) + mock_client.return_value = mocked_client + mocked_client.list_subnets.return_value = subnets + with mock.patch.object( + self.api, 'has_segment_extension', return_value=True, ): res = self.api.get_segment_ids_for_network( self.context, uuids.network_id) self.assertEqual([], res) + mock_client.assert_called_once_with(self.context, admin=True) mocked_client.list_subnets.assert_called_once_with( network_id=uuids.network_id, fields='segment_id') @@ -6990,19 +7453,24 @@ class TestAPI(TestAPIBase): mocked_client.list_subnets.side_effect = ( exceptions.NeutronClientException(status_code=404)) with mock.patch.object( - self.api, '_has_segment_extension', return_value=True + self.api, 'has_segment_extension', return_value=True, ): self.assertRaises(exception.InvalidRoutedNetworkConfiguration, self.api.get_segment_ids_for_network, self.context, uuids.network_id) + mock_client.assert_called_once_with(self.context, admin=True) - def test_get_segment_id_for_subnet_no_segment_ext(self): + @mock.patch.object(neutronapi, 'get_client') + def test_get_segment_id_for_subnet_no_segment_ext(self, mock_client): + mocked_client = mock.create_autospec(client.Client) + mock_client.return_value = mocked_client with mock.patch.object( - self.api, '_has_segment_extension', return_value=False + self.api, 'has_segment_extension', return_value=False, ): self.assertIsNone( self.api.get_segment_id_for_subnet(self.context, uuids.subnet_id)) + mock_client.assert_called_once_with(self.context, admin=True) @mock.patch.object(neutronapi, 'get_client') def test_get_segment_id_for_subnet_passes(self, mock_client): @@ -7011,11 +7479,12 @@ class TestAPI(TestAPIBase): mock_client.return_value = mocked_client mocked_client.show_subnet.return_value = subnet with mock.patch.object( - self.api, '_has_segment_extension', return_value=True + self.api, 'has_segment_extension', return_value=True, ): res = self.api.get_segment_id_for_subnet( self.context, uuids.subnet_id) self.assertEqual(uuids.segment_id, res) + mock_client.assert_called_once_with(self.context, admin=True) mocked_client.show_subnet.assert_called_once_with(uuids.subnet_id) @mock.patch.object(neutronapi, 'get_client') @@ -7025,11 +7494,12 @@ class TestAPI(TestAPIBase): mock_client.return_value = mocked_client mocked_client.show_subnet.return_value = subnet with mock.patch.object( - self.api, '_has_segment_extension', return_value=True + self.api, 'has_segment_extension', return_value=True, ): self.assertIsNone( self.api.get_segment_id_for_subnet(self.context, uuids.subnet_id)) + mock_client.assert_called_once_with(self.context, admin=True) @mock.patch.object(neutronapi, 'get_client') def test_get_segment_id_for_subnet_fails(self, mock_client): @@ -7038,35 +7508,35 @@ class TestAPI(TestAPIBase): mocked_client.show_subnet.side_effect = ( exceptions.NeutronClientException(status_code=404)) with mock.patch.object( - self.api, '_has_segment_extension', return_value=True + self.api, 'has_segment_extension', return_value=True, ): self.assertRaises(exception.InvalidRoutedNetworkConfiguration, self.api.get_segment_id_for_subnet, self.context, uuids.subnet_id) + mock_client.assert_called_once_with(self.context, admin=True) @mock.patch.object(neutronapi.LOG, 'debug') - def test_get_port_pci_slot(self, mock_debug): + def test_get_port_pci_dev(self, mock_debug): fake_port = {'id': uuids.fake_port_id} request = objects.InstancePCIRequest(requester_id=uuids.fake_port_id, request_id=uuids.pci_request_id) bad_request = objects.InstancePCIRequest( requester_id=uuids.wrong_port_id) - device = objects.PciDevice(request_id=uuids.pci_request_id, - address='fake-pci-address') + device = objects.PciDevice(request_id=uuids.pci_request_id) bad_device = objects.PciDevice(request_id=uuids.wrong_request_id) # Test the happy path instance = objects.Instance( pci_requests=objects.InstancePCIRequests(requests=[request]), pci_devices=objects.PciDeviceList(objects=[device])) self.assertEqual( - 'fake-pci-address', - self.api._get_port_pci_slot(self.context, instance, fake_port)) + device, + self.api._get_port_pci_dev(instance, fake_port)) # Test not finding the request instance = objects.Instance( pci_requests=objects.InstancePCIRequests( requests=[objects.InstancePCIRequest(bad_request)])) self.assertIsNone( - self.api._get_port_pci_slot(self.context, instance, fake_port)) + self.api._get_port_pci_dev(instance, fake_port)) mock_debug.assert_called_with('No PCI request found for port %s', uuids.fake_port_id, instance=instance) mock_debug.reset_mock() @@ -7075,7 +7545,7 @@ class TestAPI(TestAPIBase): pci_requests=objects.InstancePCIRequests(requests=[request]), pci_devices=objects.PciDeviceList(objects=[bad_device])) self.assertIsNone( - self.api._get_port_pci_slot(self.context, instance, fake_port)) + self.api._get_port_pci_dev(instance, fake_port)) mock_debug.assert_called_with('No PCI device found for request %s', uuids.pci_request_id, instance=instance) @@ -7246,9 +7716,9 @@ class TestInstanceHasExtendedResourceRequest(TestAPIBase): self.addCleanup(patcher.stop) self.mock_client = patcher.start().return_value self.extension = { - "extensions": [ + 'extensions': [ { - "name": constants.RESOURCE_REQUEST_GROUPS_EXTENSION, + 'alias': constants.RESOURCE_REQUEST_GROUPS, } ] } @@ -7364,6 +7834,41 @@ class TestAPIModuleMethods(test.NoDBTestCase): self.assertEqual(networks, [{'id': 1}, {'id': 2}, {'id': 3}]) + @mock.patch('nova.network.neutron.LOG.info') + @mock.patch('nova.network.neutron.LOG.exception') + @mock.patch('nova.objects.instance_info_cache.InstanceInfoCache.save') + def test_update_instance_cache_with_nw_info_not_found(self, mock_save, + mock_log_exc, + mock_log_info): + """Tests that an attempt to update (save) the instance info cache will + not log a traceback but will reraise the exception for caller handling. + """ + # Simulate the oslo.messaging created "<OriginalClass>_Remote" subclass + # type we'll be catching. + class InstanceNotFound_Remote(exception.InstanceNotFound): + + def __init__(self, message=None, **kwargs): + super().__init__(message=message, **kwargs) + + # Simulate a long exception message containing tracebacks because + # oslo.messaging appends them. + message = 'Instance was not found.\n'.ljust(255, '*') + mock_save.side_effect = InstanceNotFound_Remote(message=message, + instance_id=uuids.inst) + api = neutronapi.API() + ctxt = context.get_context() + instance = fake_instance.fake_instance_obj(ctxt, uuid=uuids.i) + + self.assertRaises( + exception.InstanceNotFound, + neutronapi.update_instance_cache_with_nw_info, api, ctxt, instance, + nw_info=model.NetworkInfo()) + + # Verify we didn't log exception at level ERROR. + mock_log_exc.assert_not_called() + # Verify exception message was truncated before logging it. + self.assertLessEqual(len(mock_log_info.call_args.args[1]), 255) + class TestAPIPortbinding(TestAPIBase): @@ -7390,25 +7895,83 @@ class TestAPIPortbinding(TestAPIBase): mock_get_client.assert_called_once_with(mock.ANY) mocked_client.list_extensions.assert_called_once_with() + @mock.patch.object( + neutronapi.API, '_get_vf_pci_device_profile', + new=mock.Mock(return_value={ + 'pf_mac_address': '52:54:00:1e:59:c6', + 'vf_num': 1, + })) @mock.patch.object(pci_whitelist.Whitelist, 'get_devspec') - @mock.patch.object(pci_manager, 'get_instance_pci_devs') - def test_populate_neutron_extension_values_binding_sriov(self, - mock_get_instance_pci_devs, - mock_get_pci_device_devspec): + @mock.patch('nova.objects.Instance.get_pci_devices') + def test_populate_neutron_extension_values_binding_sriov( + self, mock_get_instance_pci_devs, mock_get_pci_device_devspec): host_id = 'my_host_id' - instance = {'host': host_id} + instance = objects.Instance(host=host_id) port_req_body = {'port': {}} pci_req_id = 'my_req_id' pci_dev = {'vendor_id': '1377', 'product_id': '0047', 'address': '0000:0a:00.1', + 'card_serial_number': None, + 'dev_type': obj_fields.PciDeviceType.SRIOV_VF, } PciDevice = collections.namedtuple('PciDevice', - ['vendor_id', 'product_id', 'address']) + ['vendor_id', 'product_id', 'address', + 'card_serial_number', 'dev_type']) mydev = PciDevice(**pci_dev) profile = {'pci_vendor_info': '1377:0047', 'pci_slot': '0000:0a:00.1', 'physical_network': 'physnet1', + 'pf_mac_address': '52:54:00:1e:59:c6', + 'vf_num': 1, + } + + mock_get_instance_pci_devs.return_value = [mydev] + devspec = mock.Mock() + devspec.get_tags.return_value = {'physical_network': 'physnet1'} + mock_get_pci_device_devspec.return_value = devspec + + self.api._populate_neutron_binding_profile( + instance, pci_req_id, port_req_body, None) + + self.assertEqual(profile, + port_req_body['port'][ + constants.BINDING_PROFILE]) + + @mock.patch.object( + neutronapi.API, '_get_vf_pci_device_profile', + new=mock.Mock(return_value= { + 'pf_mac_address': '52:54:00:1e:59:c6', + 'vf_num': 1, + 'card_serial_number': 'MT2113X00000', + }) + ) + @mock.patch.object(pci_whitelist.Whitelist, 'get_devspec') + @mock.patch('nova.objects.Instance.get_pci_devices') + def test_populate_neutron_extension_values_binding_sriov_card_serial( + self, mock_get_instance_pci_devs, mock_get_pci_device_devspec): + host_id = 'my_host_id' + instance = objects.Instance(host=host_id) + port_req_body = {'port': {}} + pci_req_id = 'my_req_id' + pci_dev = {'vendor_id': 'a2d6', + 'product_id': '15b3', + 'address': '0000:0a:00.1', + 'card_serial_number': 'MT2113X00000', + 'dev_type': obj_fields.PciDeviceType.SRIOV_VF, + } + PciDevice = collections.namedtuple('PciDevice', + ['vendor_id', 'product_id', 'address', + 'card_serial_number', 'dev_type']) + mydev = PciDevice(**pci_dev) + profile = {'pci_vendor_info': 'a2d6:15b3', + 'pci_slot': '0000:0a:00.1', + 'physical_network': 'physnet1', + # card_serial_number is a property of the object obtained + # from extra_info. + 'card_serial_number': 'MT2113X00000', + 'pf_mac_address': '52:54:00:1e:59:c6', + 'vf_num': 1, } mock_get_instance_pci_devs.return_value = [mydev] @@ -7460,13 +8023,19 @@ class TestAPIPortbinding(TestAPIBase): profile, port_req_body['port'][constants.BINDING_PROFILE]) + @mock.patch.object( + neutronapi.API, '_get_vf_pci_device_profile', + new=mock.Mock(return_value= { + 'pf_mac_address': '52:54:00:1e:59:c6', + 'vf_num': 1, + }) + ) @mock.patch.object(pci_whitelist.Whitelist, 'get_devspec') - @mock.patch.object(pci_manager, 'get_instance_pci_devs') - def test_populate_neutron_extension_values_binding_sriov_with_cap(self, - mock_get_instance_pci_devs, - mock_get_pci_device_devspec): + @mock.patch('nova.objects.Instance.get_pci_devices') + def test_populate_neutron_extension_values_binding_sriov_with_cap( + self, mock_get_instance_pci_devs, mock_get_pci_device_devspec): host_id = 'my_host_id' - instance = {'host': host_id} + instance = objects.Instance(host=host_id) port_req_body = {'port': { constants.BINDING_PROFILE: { 'capabilities': ['switchdev']}}} @@ -7474,20 +8043,26 @@ class TestAPIPortbinding(TestAPIBase): pci_dev = {'vendor_id': '1377', 'product_id': '0047', 'address': '0000:0a:00.1', + 'card_serial_number': None, + 'dev_type': obj_fields.PciDeviceType.SRIOV_VF, } PciDevice = collections.namedtuple('PciDevice', - ['vendor_id', 'product_id', 'address']) + ['vendor_id', 'product_id', 'address', + 'card_serial_number', 'dev_type']) mydev = PciDevice(**pci_dev) profile = {'pci_vendor_info': '1377:0047', 'pci_slot': '0000:0a:00.1', 'physical_network': 'physnet1', 'capabilities': ['switchdev'], + 'pf_mac_address': '52:54:00:1e:59:c6', + 'vf_num': 1, } mock_get_instance_pci_devs.return_value = [mydev] devspec = mock.Mock() devspec.get_tags.return_value = {'physical_network': 'physnet1'} mock_get_pci_device_devspec.return_value = devspec + self.api._populate_neutron_binding_profile( instance, pci_req_id, port_req_body, None) @@ -7496,11 +8071,145 @@ class TestAPIPortbinding(TestAPIBase): constants.BINDING_PROFILE]) @mock.patch.object(pci_whitelist.Whitelist, 'get_devspec') - @mock.patch.object(pci_manager, 'get_instance_pci_devs') + @mock.patch('nova.objects.Instance.get_pci_devices') + def test_populate_neutron_extension_values_binding_sriov_pf( + self, mock_get_instance_pci_devs, mock_get_devspec + ): + host_id = 'my_host_id' + instance = objects.Instance(host=host_id) + port_req_body = {'port': {}} + + pci_dev = objects.PciDevice( + request_id=uuids.pci_req, + address='0000:01:00', + parent_addr='0000:02:00', + vendor_id='8086', + product_id='154d', + dev_type=obj_fields.PciDeviceType.SRIOV_PF, + extra_info={'mac_address': 'b4:96:91:34:f4:36'} + ) + + expected_profile = { + 'pci_vendor_info': '8086:154d', + 'pci_slot': '0000:01:00', + 'physical_network': 'physnet1', + 'device_mac_address': 'b4:96:91:34:f4:36', + } + + mock_get_instance_pci_devs.return_value = [pci_dev] + devspec = mock.Mock() + devspec.get_tags.return_value = {'physical_network': 'physnet1'} + mock_get_devspec.return_value = devspec + + self.api._populate_neutron_binding_profile( + instance, uuids.pci_req, port_req_body, None) + + self.assertEqual( + expected_profile, + port_req_body['port'][constants.BINDING_PROFILE] + ) + + @mock.patch.object( + pci_utils, 'get_vf_num_by_pci_address', + new=mock.MagicMock(side_effect=(lambda vf_a: 1 + if vf_a == '0000:0a:00.1' else None))) + @mock.patch.object( + pci_utils, 'get_mac_by_pci_address', + new=mock.MagicMock(side_effect=(lambda vf_a: { + '0000:0a:00.0': '52:54:00:1e:59:c6'}.get(vf_a))) + ) + def test__get_vf_pci_device_profile(self): + pci_dev = {'vendor_id': 'a2d6', + 'product_id': '15b3', + 'address': '0000:0a:00.1', + 'parent_addr': '0000:0a:00.0', + 'card_serial_number': 'MT2113X00000', + 'sriov_cap': { + 'pf_mac_address': '52:54:00:1e:59:c6', + 'vf_num': 1, + }, + 'dev_type': obj_fields.PciDeviceType.SRIOV_VF, + } + PciDevice = collections.namedtuple('PciDevice', + ['vendor_id', 'product_id', 'address', + 'card_serial_number', 'sriov_cap', + 'dev_type', 'parent_addr']) + mydev = PciDevice(**pci_dev) + self.assertEqual(self.api._get_vf_pci_device_profile(mydev), + {'pf_mac_address': '52:54:00:1e:59:c6', + 'vf_num': 1, + 'card_serial_number': 'MT2113X00000'}) + + @mock.patch.object( + neutronapi.API, '_get_vf_pci_device_profile', + new=mock.MagicMock(side_effect=( + lambda dev: {'0000:0a:00.1': { + 'pf_mac_address': '52:54:00:1e:59:c6', + 'vf_num': 1, + 'card_serial_number': 'MT2113X00000', + }}.get(dev.address) + ))) + @mock.patch.object(pci_whitelist.Whitelist, 'get_devspec') + def test__get_pci_device_profile_vf(self, mock_get_pci_device_devspec): + devspec = mock.Mock() + devspec.get_tags.return_value = {'physical_network': 'physnet1'} + mock_get_pci_device_devspec.return_value = devspec + + pci_dev = {'vendor_id': 'a2d6', + 'product_id': '15b3', + 'address': '0000:0a:00.1', + 'card_serial_number': 'MT2113X00000', + 'dev_type': obj_fields.PciDeviceType.SRIOV_VF, + } + PciDevice = collections.namedtuple('PciDevice', + ['vendor_id', 'product_id', 'address', + 'card_serial_number', 'dev_type']) + mydev = PciDevice(**pci_dev) + + self.assertEqual({'card_serial_number': 'MT2113X00000', + 'pci_slot': '0000:0a:00.1', + 'pci_vendor_info': 'a2d6:15b3', + 'pf_mac_address': '52:54:00:1e:59:c6', + 'physical_network': 'physnet1', + 'vf_num': 1}, + self.api._get_pci_device_profile(mydev)) + + @mock.patch.object(pci_whitelist.Whitelist, 'get_devspec') + def test__get_pci_device_profile_pf(self, mock_get_pci_device_devspec): + devspec = mock.Mock() + devspec.get_tags.return_value = {'physical_network': 'physnet1'} + mock_get_pci_device_devspec.return_value = devspec + + pci_dev = objects.PciDevice( + request_id=uuids.pci_req, + address='0000:0a:00.0', + parent_addr='0000:02:00', + vendor_id='a2d6', + product_id='15b3', + dev_type=obj_fields.PciDeviceType.SRIOV_PF, + extra_info={ + 'capabilities': jsonutils.dumps( + {'card_serial_number': 'MT2113X00000'}), + 'mac_address': 'b4:96:91:34:f4:36', + }, + + ) + self.assertEqual( + { + 'pci_slot': '0000:0a:00.0', + 'pci_vendor_info': 'a2d6:15b3', + 'physical_network': 'physnet1', + 'device_mac_address': 'b4:96:91:34:f4:36', + }, + self.api._get_pci_device_profile(pci_dev), + ) + + @mock.patch.object(pci_whitelist.Whitelist, 'get_devspec') + @mock.patch('nova.objects.Instance.get_pci_devices') def test_populate_neutron_extension_values_binding_sriov_fail( self, mock_get_instance_pci_devs, mock_get_pci_device_devspec): host_id = 'my_host_id' - instance = {'host': host_id} + instance = objects.Instance(host=host_id) port_req_body = {'port': {}} pci_req_id = 'my_req_id' pci_objs = [objects.PciDevice(vendor_id='1377', @@ -7517,7 +8226,7 @@ class TestAPIPortbinding(TestAPIBase): self.api._populate_neutron_binding_profile, instance, pci_req_id, port_req_body, None) - @mock.patch.object(pci_manager, 'get_instance_pci_devs', return_value=[]) + @mock.patch('nova.objects.Instance.get_pci_devices', return_value=[]) def test_populate_neutron_binding_profile_pci_dev_not_found( self, mock_get_instance_pci_devs): api = neutronapi.API() @@ -7528,28 +8237,52 @@ class TestAPIPortbinding(TestAPIBase): api._populate_neutron_binding_profile, instance, pci_req_id, port_req_body, None) mock_get_instance_pci_devs.assert_called_once_with( - instance, pci_req_id) + request_id=pci_req_id) - @mock.patch.object(pci_manager, 'get_instance_pci_devs') - def test_pci_parse_whitelist_called_once(self, - mock_get_instance_pci_devs): - white_list = [ - '{"address":"0000:0a:00.1","physical_network":"default"}'] - cfg.CONF.set_override('passthrough_whitelist', white_list, 'pci') + @mock.patch.object( + pci_utils, 'is_physical_function', + new=mock.Mock(return_value=False) + ) + @mock.patch.object( + pci_utils, 'get_vf_num_by_pci_address', + new=mock.MagicMock( + side_effect=(lambda vf_a: {'0000:0a:00.1': 1}.get(vf_a))) + ) + @mock.patch.object( + pci_utils, 'get_mac_by_pci_address', + new=mock.MagicMock(side_effect=(lambda vf_a: { + '0000:0a:00.0': '52:54:00:1e:59:c6'}.get(vf_a))) + ) + @mock.patch('nova.objects.Instance.get_pci_devices') + def test_pci_parse_whitelist_called_once( + self, mock_get_instance_pci_devs + ): + device_spec = [ + jsonutils.dumps( + { + "address": "0000:0a:00.1", + "physical_network": "default", + } + ) + ] + cfg.CONF.set_override( + 'device_spec', device_spec, 'pci') # NOTE(takashin): neutronapi.API must be initialized - # after the 'passthrough_whitelist' is set in this test case. + # after the 'device_spec' is set in this test case. api = neutronapi.API() host_id = 'my_host_id' - instance = {'host': host_id} + instance = objects.Instance(host=host_id) pci_req_id = 'my_req_id' port_req_body = {'port': {}} pci_dev = {'vendor_id': '1377', 'product_id': '0047', 'address': '0000:0a:00.1', + 'parent_addr': '0000:0a:00.0', + 'dev_type': obj_fields.PciDeviceType.SRIOV_VF, } - whitelist = pci_whitelist.Whitelist(CONF.pci.passthrough_whitelist) + whitelist = pci_whitelist.Whitelist(CONF.pci.device_spec) with mock.patch.object(pci_whitelist.Whitelist, '_parse_white_list_from_config', wraps=whitelist._parse_white_list_from_config @@ -7575,7 +8308,7 @@ class TestAPIPortbinding(TestAPIBase): vf.update_device(pci_dev) return instance, pf, vf - @mock.patch.object(pci_manager, 'get_instance_pci_devs') + @mock.patch('nova.objects.Instance.get_pci_devices') @mock.patch.object(pci_utils, 'get_mac_by_pci_address') def test_populate_pci_mac_address_pf(self, mock_get_mac_by_pci_address, mock_get_instance_pci_devs): @@ -7589,7 +8322,7 @@ class TestAPIPortbinding(TestAPIBase): self.api._populate_pci_mac_address(instance, 0, req) self.assertEqual(expected_port_req_body, req) - @mock.patch.object(pci_manager, 'get_instance_pci_devs') + @mock.patch('nova.objects.Instance.get_pci_devices') @mock.patch.object(pci_utils, 'get_mac_by_pci_address') def test_populate_pci_mac_address_vf(self, mock_get_mac_by_pci_address, mock_get_instance_pci_devs): @@ -7601,7 +8334,7 @@ class TestAPIPortbinding(TestAPIBase): self.api._populate_pci_mac_address(instance, 42, port_req_body) self.assertEqual(port_req_body, req) - @mock.patch.object(pci_manager, 'get_instance_pci_devs') + @mock.patch('nova.objects.Instance.get_pci_devices') @mock.patch.object(pci_utils, 'get_mac_by_pci_address') def test_populate_pci_mac_address_vf_fail(self, mock_get_mac_by_pci_address, @@ -7616,7 +8349,7 @@ class TestAPIPortbinding(TestAPIBase): self.api._populate_pci_mac_address(instance, 42, port_req_body) self.assertEqual(port_req_body, req) - @mock.patch.object(pci_manager, 'get_instance_pci_devs') + @mock.patch('nova.objects.Instance.get_pci_devices') @mock.patch('nova.network.neutron.LOG.error') def test_populate_pci_mac_address_no_device(self, mock_log_error, mock_get_instance_pci_devs): @@ -7774,7 +8507,7 @@ class TestAPIPortbinding(TestAPIBase): self.assertEqual(1, mocked_client.create_port_binding.call_count) self.assertDictEqual({uuids.port: binding['binding']}, result) - # assert that that if vnic_type and profile are set in VIF object + # assert that if vnic_type and profile are set in VIF object # the provided vnic_type and profile take precedence. nwinfo = model.NetworkInfo([model.VIF(id=uuids.port, @@ -7852,6 +8585,9 @@ class TestAPIPortbinding(TestAPIBase): self.api.delete_port_binding(self.context, port_id, 'fake-host') + @mock.patch( + 'nova.network.neutron.API.has_dns_extension', + new=mock.Mock(return_value=False)) @mock.patch('nova.accelerator.cyborg._CyborgClient.delete_arqs_by_uuid') @mock.patch('nova.network.neutron.get_binding_profile') @mock.patch('nova.network.neutron.API._show_port') @@ -8241,7 +8977,7 @@ class TestAllocateForInstance(test.NoDBTestCase): requested_ports_dict = {uuids.port1: {}, uuids.port2: {}} mock_neutron.list_extensions.return_value = {"extensions": [ - {"name": "asdf"}]} + {"alias": "asdf"}]} port1 = {"port": {"id": uuids.port1, "mac_address": "mac1r"}} port2 = {"port": {"id": uuids.port2, "mac_address": "mac2r"}} mock_admin.update_port.side_effect = [port1, port2] @@ -8324,6 +9060,10 @@ class TestAPINeutronHostnameDNSPortbinding(TestAPIBase): requested_networks=requested_networks) @mock.patch( + 'nova.network.neutron.API.has_dns_extension', + new=mock.Mock(return_value=True), + ) + @mock.patch( 'nova.network.neutron.API.has_extended_resource_request_extension', new=mock.Mock(return_value=False) ) @@ -8336,8 +9076,8 @@ class TestAPINeutronHostnameDNSPortbinding(TestAPIBase): 11, dns_extension=True, bind_host_id=self.instance.get('host')) @mock.patch( - "nova.network.neutron.API._has_dns_extension", - new=mock.Mock(return_value=True) + 'nova.network.neutron.API.has_dns_extension', + new=mock.Mock(return_value=True), ) def test_allocate_for_instance_with_requested_port_with_dns_domain(self): # The port's dns_name attribute should be set by the port update |