diff options
-rw-r--r-- | api-guide/source/port_with_resource_request.rst | 9 | ||||
-rw-r--r-- | doc/source/admin/ports-with-resource-requests.rst | 13 | ||||
-rw-r--r-- | nova/api/openstack/compute/servers.py | 2 | ||||
-rw-r--r-- | nova/compute/api.py | 16 | ||||
-rw-r--r-- | nova/exception.py | 6 | ||||
-rw-r--r-- | nova/network/neutron.py | 75 | ||||
-rw-r--r-- | nova/objects/service.py | 5 | ||||
-rw-r--r-- | nova/tests/functional/test_servers_resource_request.py | 68 | ||||
-rw-r--r-- | nova/tests/unit/compute/test_api.py | 2 | ||||
-rw-r--r-- | nova/tests/unit/compute/test_compute.py | 26 | ||||
-rw-r--r-- | nova/tests/unit/network/test_neutron.py | 262 |
11 files changed, 338 insertions, 146 deletions
diff --git a/api-guide/source/port_with_resource_request.rst b/api-guide/source/port_with_resource_request.rst index 868ec33a97..0109ec799b 100644 --- a/api-guide/source/port_with_resource_request.rst +++ b/api-guide/source/port_with_resource_request.rst @@ -44,10 +44,15 @@ Extended resource request Since neutron 19.0.0 (Xena), neutron implements an extended resource request format via the the ``port-resource-request-groups`` neutron API extension. As -of nova 24.0.0 (Xena), nova does not support the new extension. If the -extension is enabled in neutron, then nova will reject server create and move +of nova 24.0.0 (Xena), nova does not fully support the new extension. If the +extension is enabled in neutron, then nova will reject server move operations, as well as interface attach operation. Admins should not enable this API extension in neutron. +Please note that Nova only supports the server create operation if every +nova-compute service also upgraded to Xena version and the +``[upgrade_levels]/compute`` configuration does not prevent +the computes from using the latest RPC version. + See :nova-doc:`the admin guide <admin/port_with_resource_request.html>` for administrative details. diff --git a/doc/source/admin/ports-with-resource-requests.rst b/doc/source/admin/ports-with-resource-requests.rst index 121e52b17c..3765b09afe 100644 --- a/doc/source/admin/ports-with-resource-requests.rst +++ b/doc/source/admin/ports-with-resource-requests.rst @@ -71,10 +71,15 @@ Extended resource request Since neutron 19.0.0 (Xena), neutron implements an extended resource request format via the the ``port-resource-request-groups`` neutron API extension. As -of nova 24.0.0 (Xena), Nova does not support the new extension. If the -extension is enabled in neutron, then nova will reject server create and move -operations, as well as interface attach operation. Admins should not enable -this API extension in neutron. +of nova 24.0.0 (Xena), Nova does not fully support the new extension. If the +extension is enabled in neutron, then nova will reject server move operations, +as well as interface attach operation. Admins should not enable this API +extension in neutron. + +Please note that Nova only supports the server create operation if every +nova-compute service also upgraded to Xena version and the +:oslo.config:option:`upgrade_levels.compute` configuration does not prevent +the computes from using the latest RPC version. The extended resource request allows a single Neutron port to request resources in more than one request groups. This also means that using just one diff --git a/nova/api/openstack/compute/servers.py b/nova/api/openstack/compute/servers.py index b4d4cc3ed1..a297d11e57 100644 --- a/nova/api/openstack/compute/servers.py +++ b/nova/api/openstack/compute/servers.py @@ -844,7 +844,7 @@ class ServersController(wsgi.Controller): exception.DeviceProfileError, exception.ComputeHostNotFound, exception.ForbiddenPortsWithAccelerator, - exception.ExtendedResourceRequestNotSupported, + exception.ExtendedResourceRequestOldCompute, ) as error: raise exc.HTTPBadRequest(explanation=error.format_message()) except INVALID_FLAVOR_IMAGE_EXCEPTIONS as error: diff --git a/nova/compute/api.py b/nova/compute/api.py index 292284036f..db3e670d41 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -110,6 +110,8 @@ SUPPORT_ACCELERATOR_SERVICE_FOR_REBUILD = 53 SUPPORT_VNIC_TYPE_ACCELERATOR = 57 +MIN_COMPUTE_BOOT_WITH_EXTENDED_RESOURCE_REQUEST = 58 + # FIXME(danms): Keep a global cache of the cells we find the # first time we look. This needs to be refreshed on a timer or # trigger. @@ -1073,6 +1075,18 @@ class API: if port_resource_requests and not supports_port_resource_request: raise exception.CreateWithPortResourceRequestOldVersion() + # TODO(gibi): remove this when Nova does not need to support Wallaby + # computes any more. + if (port_resource_requests and + self.network_api.has_extended_resource_request_extension(context) + ): + # we only support the extended resource request if the computes are + # upgraded to Xena. + min_version = objects.service.get_minimum_version_all_cells( + context, ["nova-compute"]) + if min_version < MIN_COMPUTE_BOOT_WITH_EXTENDED_RESOURCE_REQUEST: + raise exception.ExtendedResourceRequestOldCompute() + base_options = { 'reservation_id': reservation_id, 'image_ref': image_href, @@ -5062,7 +5076,7 @@ class API: This function is only here temporary to help mocking this check in the functional test environment. """ - if not self.network_api._has_extended_resource_request_extension( + if not self.network_api.has_extended_resource_request_extension( context ): return True diff --git a/nova/exception.py b/nova/exception.py index 6573c8e965..f70edc253d 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -1962,10 +1962,10 @@ class CreateWithPortResourceRequestOldVersion(Invalid): "until microversion 2.72.") -class ExtendedResourceRequestNotSupported(Invalid): +class ExtendedResourceRequestOldCompute(Invalid): msg_fmt = _("The port-resource-request-groups neutron API extension is " - "not yet supported by Nova. Please turn off this extension in " - "Neutron.") + "not supported by old nova compute service. Upgrade your " + "compute services to Xena (24.0.0) or later.") class InvalidReservedMemoryPagesOption(Invalid): diff --git a/nova/network/neutron.py b/nova/network/neutron.py index 88cd4a74e1..de512e8a51 100644 --- a/nova/network/neutron.py +++ b/nova/network/neutron.py @@ -470,10 +470,11 @@ class API: 'error.', {'port_id': port_id}, instance=instance) - def _create_port_minimal(self, port_client, instance, network_id, + def _create_port_minimal(self, context, port_client, instance, network_id, fixed_ip=None, security_group_ids=None): """Attempts to create a port for the instance on the given network. + :param context: The request context. :param port_client: The client to use to create the port. :param instance: Create the port for the given instance. :param network_id: Create the port on the given network. @@ -510,7 +511,7 @@ class API: # such ports are currently not supported as they would at least # need resource allocation manipulation in placement but might also # need a new scheduling if resource on this host is not available. - if port.get(constants.RESOURCE_REQUEST, None): + if self._has_resource_request(context, port, port_client): msg = ( "The auto-created port %(port_id)s is being deleted due " "to its network having QoS policy.") @@ -979,8 +980,8 @@ class API: if not request.port_id: # create minimal port, if port not already created by user created_port = self._create_port_minimal( - neutron, instance, request.network_id, - request.address, security_group_ids) + context, neutron, instance, request.network_id, + request.address, security_group_ids) created_port_id = created_port['id'] created_port_ids.append(created_port_id) @@ -997,7 +998,7 @@ class API: def _has_resource_request(self, context, port, neutron): resource_request = port.get(constants.RESOURCE_REQUEST) or {} - if self._has_extended_resource_request_extension(context, neutron): + if self.has_extended_resource_request_extension(context, neutron): return bool(resource_request.get(constants.REQUEST_GROUPS, [])) else: return bool(resource_request) @@ -1008,7 +1009,7 @@ class API: # if we query with a non admin context. admin_context = nova_context.get_admin_context() - if not self._has_extended_resource_request_extension(admin_context): + if not self.has_extended_resource_request_extension(admin_context): # Short circuit if the extended resource request API extension is # not available return False @@ -1026,6 +1027,44 @@ class API: return True return False + def _get_binding_profile_allocation( + self, context, port, neutron, resource_provider_mapping + ): + # TODO(gibi): remove this condition and the else branch once Nova does + # not need to support old Neutron sending the legacy resource request + # extension + if self.has_extended_resource_request_extension( + context, neutron + ): + # The extended resource request format also means that a + # port has more than a one request groups + request_groups = port.get( + constants.RESOURCE_REQUEST, {}).get( + constants.REQUEST_GROUPS, []) + # Each request group id from the port needs to be mapped to + # a single provider id from the provider mappings. Each + # group from the port is mapped to a numbered request group + # in placement so we can assume that they are mapped to + # a single provider and therefore the provider mapping list + # has a single provider id. + allocation = { + group['id']: resource_provider_mapping[group['id']][0] + for group in request_groups + } + else: + # This is the legacy resource request format where a port + # is mapped to a single request group + # NOTE(gibi): In the resource provider mapping there can be + # more than one RP fulfilling a request group. But resource + # requests of a Neutron port is always mapped to a + # numbered request group that is always fulfilled by one + # resource provider. So we only pass that single RP UUID + # here. + allocation = resource_provider_mapping[ + port['id']][0] + + return allocation + def allocate_for_instance(self, context, instance, requested_networks, security_groups=None, bind_host_id=None, @@ -1095,15 +1134,12 @@ class API: for port in requested_ports_dict.values(): # only communicate the allocations if the port has resource # requests - if port.get(constants.RESOURCE_REQUEST): + if self._has_resource_request(context, port, neutron): + profile = get_binding_profile(port) - # NOTE(gibi): In the resource provider mapping there can be - # more than one RP fulfilling a request group. But resource - # requests of a Neutron port is always mapped to a - # numbered request group that is always fulfilled by one - # resource provider. So we only pass that single RP UUID here. - profile[constants.ALLOCATION] = resource_provider_mapping[ - port['id']][0] + profile[constants.ALLOCATION] = ( + self._get_binding_profile_allocation( + context, port, neutron, resource_provider_mapping)) port[constants.BINDING_PROFILE] = profile # Create ports from the list of ordered_networks. The returned @@ -1305,7 +1341,7 @@ class API: # the this extension mandatory. In Xena this extension will be optional to # support the scenario where Neutron upgraded first. So Neutron can mark # this mandatory earliest in Yoga. - def _has_extended_resource_request_extension(self, context, neutron=None): + def has_extended_resource_request_extension(self, context, neutron=None): self._refresh_neutron_extensions_cache(context, neutron=neutron) return constants.RESOURCE_REQUEST_GROUPS_EXTENSION in self.extensions @@ -2032,7 +2068,7 @@ class API: This function is only here temporarily to help mocking this check in the functional test environment. """ - return not (self._has_extended_resource_request_extension(context)) + return not (self.has_extended_resource_request_extension(context)) def create_resource_requests( self, context, requested_networks, pci_requests=None, @@ -2048,8 +2084,6 @@ class API: :type pci_requests: nova.objects.InstancePCIRequests :param affinity_policy: requested pci numa affinity policy :type affinity_policy: nova.objects.fields.PCINUMAAffinityPolicy - :raises ExtendedResourceRequestNotSupported: if the - extended-resource-request Neutron API extension is enabled. :returns: A three tuple with an instance of ``objects.NetworkMetadata`` for use by the scheduler or None, a list of RequestGroup @@ -2060,15 +2094,12 @@ class API: if not requested_networks or requested_networks.no_allocate: return None, [], None - if not self.support_create_with_resource_request(context): - raise exception.ExtendedResourceRequestNotSupported() - physnets = set() tunneled = False neutron = get_client(context, admin=True) has_extended_resource_request_extension = ( - self._has_extended_resource_request_extension(context, neutron)) + self.has_extended_resource_request_extension(context, neutron)) resource_requests = [] request_level_params = objects.RequestLevelParams() diff --git a/nova/objects/service.py b/nova/objects/service.py index 6871a99593..33e6c7d43f 100644 --- a/nova/objects/service.py +++ b/nova/objects/service.py @@ -31,7 +31,7 @@ LOG = logging.getLogger(__name__) # NOTE(danms): This is the global service version counter -SERVICE_VERSION = 57 +SERVICE_VERSION = 58 # NOTE(danms): This is our SERVICE_VERSION history. The idea is that any @@ -202,6 +202,9 @@ SERVICE_VERSION_HISTORY = ( # Version 57: Compute RPC v6.0: # Add support for vnic 'accelerator-direct'. {'compute_rpc': '6.0'}, + # Version 58: Compute RPC v6.0: + # Add support for booting with neutron extended resource request + {'compute_rpc': '6.0'}, ) # This is used to raise an error at service startup if older than N-1 computes diff --git a/nova/tests/functional/test_servers_resource_request.py b/nova/tests/functional/test_servers_resource_request.py index e77adcd275..8be374b868 100644 --- a/nova/tests/functional/test_servers_resource_request.py +++ b/nova/tests/functional/test_servers_resource_request.py @@ -1642,15 +1642,9 @@ class MultiGroupResourceRequestBasedSchedulingTest( super().setUp() self.neutron = self.useFixture( MultiGroupResourceRequestNeutronFixture(self)) - # Turn off the blanket rejections of the extended resource request. - # This test class wants to prove that the extended resource request is - # supported. - patcher = mock.patch( - 'nova.network.neutron.API.support_create_with_resource_request', - return_value=True, - ) - self.addCleanup(patcher.stop) - patcher.start() + # Turn off the blanket rejections of the extended resource request in + # port attach. This test class wants to prove that the extended + # resource request is supported. patcher = mock.patch( 'nova.compute.api.API.support_port_attach', return_value=True, @@ -1659,14 +1653,6 @@ class MultiGroupResourceRequestBasedSchedulingTest( patcher.start() @unittest.expectedFailure - def test_boot_server_with_two_ports_one_having_resource_request(self): - super().test_boot_server_with_two_ports_one_having_resource_request() - - @unittest.expectedFailure - def test_one_ovs_one_sriov_port(self): - super().test_one_ovs_one_sriov_port() - - @unittest.expectedFailure def test_interface_attach_with_resource_request(self): super().test_interface_attach_with_resource_request() @@ -1682,14 +1668,6 @@ class MultiGroupResourceRequestBasedSchedulingTest( def test_delete_bound_port_in_neutron_with_resource_request(self): super().test_delete_bound_port_in_neutron_with_resource_request() - @unittest.expectedFailure - def test_two_sriov_ports_one_with_request_two_available_pfs(self): - super().test_two_sriov_ports_one_with_request_two_available_pfs() - - @unittest.expectedFailure - def test_sriov_macvtap_port_with_resource_request(self): - super().test_sriov_macvtap_port_with_resource_request() - class ServerMoveWithPortResourceRequestTest( PortResourceRequestBasedSchedulingTestBase): @@ -2609,12 +2587,6 @@ class ServerMoveWithMultiGroupResourceRequestBasedSchedulingTest( # This test class wants to prove that the extended resource request is # supported. patcher = mock.patch( - 'nova.network.neutron.API.support_create_with_resource_request', - return_value=True, - ) - self.addCleanup(patcher.stop) - patcher.start() - patcher = mock.patch( 'nova.network.neutron.API.instance_has_extended_resource_request', return_value=False, ) @@ -2903,38 +2875,42 @@ class CrossCellResizeWithQoSPort(PortResourceRequestBasedSchedulingTestBase): server, qos_normal_port, qos_sriov_port) -class ExtendedResourceRequestTempNegativeTest( +class ExtendedResourceRequestOldCompute( PortResourceRequestBasedSchedulingTestBase): - """A set of temporary tests to show that nova currently rejects requests - that uses the extended-resource-request Neutron API extension. These test - are expected to be removed when support for the extension is implemented - in nova. + """Tests that simulate that there are compute services in the system that + hasn't been upgraded to a version that support extended resource request. + So nova rejects the operations due to the old compute. """ + @mock.patch.object( + objects.service, 'get_minimum_version_all_cells', + new=mock.Mock(return_value=57) + ) def test_boot(self): - """The neutron fixture used in this test enables the - extended-resource-request API extension. This results in any new - server to boot. This is harsh but without nova support for this - extension there is no way that this extension is helpful. So treat - this as a deployment configuration error. - """ self.neutron = self.useFixture( ExtendedResourceRequestNeutronFixture(self)) ex = self.assertRaises( client.OpenStackApiException, self._create_server, flavor=self.flavor, - networks=[{'port': self.neutron.port_1['id']}], + networks=[{'port': self.neutron.port_with_resource_request['id']}], ) - self.assertEqual(400, ex.response.status_code) self.assertIn( 'The port-resource-request-groups neutron API extension is not ' - 'yet supported by Nova. Please turn off this extension in ' - 'Neutron.', + 'supported by old nova compute service. Upgrade your compute ' + 'services to Xena (24.0.0) or later.', str(ex) ) + +class ExtendedResourceRequestTempNegativeTest( + PortResourceRequestBasedSchedulingTestBase): + """A set of temporary tests to show that nova currently rejects requests + that uses the extended-resource-request Neutron API extension. These test + are expected to be removed when support for the extension is implemented + in nova. + """ def _test_operation(self, op_name, op_callable): # boot a server with a qos port still using the old Neutron resource # request API extension diff --git a/nova/tests/unit/compute/test_api.py b/nova/tests/unit/compute/test_api.py index a8f91a0458..d0d7e5ba1d 100644 --- a/nova/tests/unit/compute/test_api.py +++ b/nova/tests/unit/compute/test_api.py @@ -7234,7 +7234,7 @@ class ComputeAPIUnitTestCase(_ComputeAPIUnitTestMixIn, test.NoDBTestCase): self.context, instance.host, 'nova-compute') @mock.patch( - 'nova.network.neutron.API._has_extended_resource_request_extension', + 'nova.network.neutron.API.has_extended_resource_request_extension', new=mock.Mock(return_value=False) ) @mock.patch('nova.compute.rpcapi.ComputeAPI.attach_interface') diff --git a/nova/tests/unit/compute/test_compute.py b/nova/tests/unit/compute/test_compute.py index a7b009d377..003114192b 100644 --- a/nova/tests/unit/compute/test_compute.py +++ b/nova/tests/unit/compute/test_compute.py @@ -9820,6 +9820,32 @@ class ComputeAPITestCase(BaseTestCase): self.assertEqual(refs[i]['display_name'], name) self.assertEqual(refs[i]['hostname'], name) + @mock.patch("nova.objects.service.get_minimum_version_all_cells") + @mock.patch( + "nova.network.neutron.API.has_extended_resource_request_extension") + @mock.patch("nova.network.neutron.API.create_resource_requests") + def test_instance_create_with_extended_res_req_old_compute( + self, mock_create_res_req, mock_has_extended_res_req, + mock_get_min_svc_version + ): + requested_networks = objects.NetworkRequestList( + objects=[ + objects.NetworkRequest(port_id=uuids.port1) + ] + ) + mock_create_res_req.return_value = ( + None, [objects.RequestGroup()], None) + mock_has_extended_res_req.return_value = True + mock_get_min_svc_version.return_value = 57 + + self.assertRaises( + exception.ExtendedResourceRequestOldCompute, + self.compute_api.create, self.context, self.default_flavor, + image_href=uuids.image_href_id, + requested_networks=requested_networks, + supports_port_resource_request=True, + ) + def test_instance_architecture(self): # Test the instance architecture. i_ref = self._create_fake_instance_obj() diff --git a/nova/tests/unit/network/test_neutron.py b/nova/tests/unit/network/test_neutron.py index 3fb6d5f2fe..e511b925a8 100644 --- a/nova/tests/unit/network/test_neutron.py +++ b/nova/tests/unit/network/test_neutron.py @@ -493,12 +493,11 @@ class TestAPIBase(test.TestCase): **kwargs) @mock.patch.object(neutronapi.API, '_populate_neutron_extension_values') - @mock.patch.object(neutronapi.API, '_refresh_neutron_extensions_cache') @mock.patch.object(neutronapi.API, 'get_instance_nw_info', return_value=None) @mock.patch.object(neutronapi, 'get_client') def _test_allocate_for_instance(self, mock_get_client, mock_get_nw, - mock_refresh, mock_populate, net_idx=1, + mock_populate, net_idx=1, requested_networks=None, exception=None, context=None, @@ -651,8 +650,6 @@ class TestAPIBase(test.TestCase): mock.call(ctxt), mock.call(ctxt, admin=True)], any_order=True) - mock_refresh.assert_not_called() - if requested_networks: mocked_client.show_port.assert_has_calls(expected_show_port_calls) self.assertEqual(len(expected_show_port_calls), @@ -4017,10 +4014,13 @@ class TestAPI(TestAPIBase): instance = fake_instance.fake_instance_obj(self.context) create_port_mock.side_effect = \ exceptions.IpAddressGenerationFailureClient() - self.assertRaises(exception.NoMoreFixedIps, - self.api._create_port_minimal, - neutronapi.get_client(self.context), - instance, uuids.my_netid1) + self.assertRaises( + exception.NoMoreFixedIps, + self.api._create_port_minimal, + self.context, + neutronapi.get_client(self.context), + instance, uuids.my_netid1 + ) self.assertTrue(create_port_mock.called) @mock.patch.object(client.Client, 'update_port', @@ -4082,10 +4082,13 @@ class TestAPI(TestAPIBase): instance = fake_instance.fake_instance_obj(self.context) fake_ip = '1.1.1.1' - self.assertRaises(exception.FixedIpAlreadyInUse, - self.api._create_port_minimal, - neutronapi.get_client(self.context), - instance, uuids.my_netid1, fixed_ip=fake_ip) + self.assertRaises( + exception.FixedIpAlreadyInUse, + self.api._create_port_minimal, + self.context, + neutronapi.get_client(self.context), + instance, uuids.my_netid1, fixed_ip=fake_ip + ) self.assertTrue(create_port_mock.called) @mock.patch.object(client.Client, 'create_port', @@ -4095,10 +4098,13 @@ class TestAPI(TestAPIBase): instance = fake_instance.fake_instance_obj(self.context) fake_ip = '1.1.1.1' - self.assertRaises(exception.FixedIpAlreadyInUse, - self.api._create_port_minimal, - neutronapi.get_client(self.context), - instance, uuids.my_netid1, fixed_ip=fake_ip) + self.assertRaises( + exception.FixedIpAlreadyInUse, + self.api._create_port_minimal, + self.context, + neutronapi.get_client(self.context), + instance, uuids.my_netid1, fixed_ip=fake_ip + ) self.assertTrue(create_port_mock.called) @mock.patch.object(client.Client, 'create_port', @@ -4107,10 +4113,13 @@ class TestAPI(TestAPIBase): instance = fake_instance.fake_instance_obj(self.context) fake_ip = '1.1.1.1' - exc = self.assertRaises(exception.InvalidInput, - self.api._create_port_minimal, - neutronapi.get_client(self.context), - instance, uuids.my_netid1, fixed_ip=fake_ip) + exc = self.assertRaises( + exception.InvalidInput, + self.api._create_port_minimal, + self.context, + neutronapi.get_client(self.context), + instance, uuids.my_netid1, fixed_ip=fake_ip + ) expected_exception_msg = ('Invalid input received: Fixed IP %(ip)s is ' 'not a valid ip address for network ' @@ -4119,6 +4128,10 @@ class TestAPI(TestAPIBase): self.assertEqual(expected_exception_msg, str(exc)) self.assertTrue(create_port_mock.called) + @mock.patch( + 'nova.network.neutron.API.has_extended_resource_request_extension', + new=mock.Mock(return_value=False), + ) def test_create_port_minimal_raise_qos_not_supported(self): instance = fake_instance.fake_instance_obj(self.context) mock_client = mock.MagicMock() @@ -4128,9 +4141,47 @@ class TestAPI(TestAPIBase): 'resources': {'CUSTOM_RESOURCE_CLASS': 42}} }} - exc = self.assertRaises(exception.NetworksWithQoSPolicyNotSupported, - self.api._create_port_minimal, - mock_client, instance, uuids.my_netid1) + exc = self.assertRaises( + exception.NetworksWithQoSPolicyNotSupported, + self.api._create_port_minimal, + self.context, + mock_client, instance, uuids.my_netid1 + ) + expected_exception_msg = ('Using networks with QoS policy is not ' + 'supported for instance %(instance)s. ' + '(Network ID is %(net_id)s)' % + {'instance': instance.uuid, + 'net_id': uuids.my_netid1}) + self.assertEqual(expected_exception_msg, str(exc)) + mock_client.delete_port.assert_called_once_with(uuids.port_id) + + @mock.patch( + 'nova.network.neutron.API.has_extended_resource_request_extension', + new=mock.Mock(return_value=True), + ) + def test_create_port_minimal_raise_extended_qos_not_supported(self): + instance = fake_instance.fake_instance_obj(self.context) + mock_client = mock.MagicMock() + mock_client.create_port.return_value = { + 'port': { + 'id': uuids.port_id, + constants.RESOURCE_REQUEST: { + 'request_groups': [ + { + "id": uuids.group1, + 'resources': {'CUSTOM_RESOURCE_CLASS': 42}, + } + ], + } + } + } + + exc = self.assertRaises( + exception.NetworksWithQoSPolicyNotSupported, + self.api._create_port_minimal, + self.context, + mock_client, instance, uuids.my_netid1 + ) expected_exception_msg = ('Using networks with QoS policy is not ' 'supported for instance %(instance)s. ' '(Network ID is %(net_id)s)' % @@ -4152,9 +4203,12 @@ class TestAPI(TestAPIBase): mock_client.delete_port.side_effect = \ exceptions.NeutronClientException() - exc = self.assertRaises(exception.NetworksWithQoSPolicyNotSupported, - self.api._create_port_minimal, - mock_client, instance, uuids.my_netid1) + exc = self.assertRaises( + exception.NetworksWithQoSPolicyNotSupported, + self.api._create_port_minimal, + self.context, + mock_client, instance, uuids.my_netid1 + ) expected_exception_msg = ('Using networks with QoS policy is not ' 'supported for instance %(instance)s. ' '(Network ID is %(net_id)s)' % @@ -5112,6 +5166,10 @@ class TestAPI(TestAPIBase): api._unbind_ports(mock_ctx, [None], mock_client, mock_client) self.assertFalse(mock_update_port.called) + @mock.patch( + 'nova.network.neutron.API.has_extended_resource_request_extension', + new=mock.Mock() + ) @mock.patch('nova.network.neutron.API.get_instance_nw_info') @mock.patch('nova.network.neutron.excutils') @mock.patch('nova.network.neutron.API._delete_ports') @@ -5343,6 +5401,10 @@ class TestAPI(TestAPIBase): self.api._delete_nic_metadata(instance, vif) instance.save.assert_not_called() + @mock.patch( + 'nova.network.neutron.API.has_extended_resource_request_extension', + new=mock.Mock(return_value=False), + ) @mock.patch('nova.network.neutron.API._check_external_network_attach') @mock.patch('nova.network.neutron.API._populate_neutron_extension_values') @mock.patch('nova.network.neutron.API._get_available_networks') @@ -5439,6 +5501,69 @@ class TestAPI(TestAPIBase): mock.sentinel.ctx, uuids.portid_1, neutron_client=mock_get_client.return_value) + @mock.patch( + "nova.network.neutron.API.has_extended_resource_request_extension") + @mock.patch('nova.objects.virtual_interface.VirtualInterface.create') + @mock.patch('nova.network.neutron.API._check_external_network_attach') + @mock.patch('nova.network.neutron.API._show_port') + @mock.patch('nova.network.neutron.API._update_port') + @mock.patch('nova.network.neutron.get_client') + def test_port_with_extended_resource_request_has_allocation_in_binding( + self, mock_get_client, mock_update_port, mock_show_port, + mock_check_external, mock_vif_create, mock_has_extended_res_req): + + nw_req = objects.NetworkRequestList( + objects=[objects.NetworkRequest(port_id=uuids.portid_1)]) + mock_inst = mock.Mock( + uuid=uuids.instance_uuid, + project_id=uuids.project_id, + availability_zone='nova', + ) + port = { + 'id': uuids.portid_1, + 'tenant_id': uuids.project_id, + 'network_id': uuids.networkid_1, + 'mac_address': 'fake-mac', + constants.RESOURCE_REQUEST: { + "request_groups": [ + {"id": uuids.group1}, + {"id": uuids.group2}, + ] + } + } + mock_show_port.return_value = port + mock_get_client.return_value.list_networks.return_value = { + "networks": [{'id': uuids.networkid_1, + 'port_security_enabled': False}]} + mock_update_port.return_value = port + mock_has_extended_res_req.return_value = True + + with mock.patch.object(self.api, 'get_instance_nw_info'): + self.api.allocate_for_instance( + mock.sentinel.ctx, mock_inst, + requested_networks=nw_req, + resource_provider_mapping={ + uuids.group1: [uuids.rp1], + uuids.group2: [uuids.rp2], + }) + + mock_update_port.assert_called_once_with( + mock_get_client.return_value, mock_inst, uuids.portid_1, + { + 'port': { + 'binding:host_id': None, + 'device_id': uuids.instance_uuid, + 'binding:profile': { + 'allocation': { + uuids.group1: uuids.rp1, + uuids.group2: uuids.rp2, + } + }, + 'device_owner': 'compute:nova'}}) + mock_show_port.assert_called_once_with( + mock.sentinel.ctx, uuids.portid_1, + neutron_client=mock_get_client.return_value) + @mock.patch('nova.network.neutron.get_client') def test_get_floating_ip_by_address_not_found_neutron_not_found(self, mock_ntrn): @@ -5796,7 +5921,7 @@ class TestAPI(TestAPIBase): self.assertEqual([], port_resource_requests) @mock.patch.object( - neutronapi.API, '_has_extended_resource_request_extension', + neutronapi.API, 'has_extended_resource_request_extension', return_value=False) @mock.patch.object(neutronapi.API, '_get_physnet_tunneled_info') @mock.patch.object(neutronapi, 'get_client', return_value=mock.Mock()) @@ -5824,7 +5949,7 @@ class TestAPI(TestAPIBase): self.assertEqual([], port_resource_requests) @mock.patch.object( - neutronapi.API, '_has_extended_resource_request_extension', + neutronapi.API, 'has_extended_resource_request_extension', return_value=False) @mock.patch('nova.objects.request_spec.RequestGroup.from_port_request') @mock.patch.object(neutronapi.API, '_get_physnet_tunneled_info') @@ -5920,31 +6045,8 @@ class TestAPI(TestAPIBase): port_uuid=uuids.trusted_port, port_resource_request=mock.sentinel.resource_request2), ]) - mock_has_extended_res_req.assert_has_calls( - [ - mock.call(self.context), - mock.call(self.context, getclient.return_value), - ] - ) - - @mock.patch.object( - neutronapi.API, '_has_extended_resource_request_extension', - return_value=True) - def test_create_resource_request_extended_not_supported( - self, mock_has_extended_extension - ): - requested_networks = objects.NetworkRequestList( - objects=[ - objects.NetworkRequest(port_id=uuids.portid_1), - ] - ) - pci_requests = objects.InstancePCIRequests(requests=[]) - self.assertRaises( - exception.ExtendedResourceRequestNotSupported, - neutronapi.API().create_resource_requests, - self.context, requested_networks, pci_requests - ) - mock_has_extended_extension.assert_called_once_with(self.context) + mock_has_extended_res_req.assert_called_once_with( + self.context, getclient.return_value) @mock.patch( 'nova.accelerator.cyborg._CyborgClient.get_device_request_groups') @@ -6044,7 +6146,7 @@ class TestAPI(TestAPIBase): 'nova.network.neutron.API.support_create_with_resource_request', new=mock.Mock(return_value=True)) @mock.patch.object( - neutronapi.API, '_has_extended_resource_request_extension', + neutronapi.API, 'has_extended_resource_request_extension', return_value=True) @mock.patch( 'nova.objects.request_spec.RequestLevelParams.extend_with' @@ -6711,7 +6813,7 @@ class TestAPI(TestAPIBase): @mock.patch( 'nova.network.neutron.API.' - '_has_extended_resource_request_extension') + 'has_extended_resource_request_extension') def test__has_resource_request(self, mock_has_extended_res_req): # Old format, resource_request in None. That is Neutron current # behavior if the port has no QoS policy associated. @@ -7623,6 +7725,10 @@ class TestAllocateForInstance(test.NoDBTestCase): exception.NetworkAmbiguous, [{'id': "net1"}, {'id': "net2"}]) + @mock.patch( + 'nova.network.neutron.API.has_extended_resource_request_extension', + new=mock.Mock(return_value=False), + ) def test_create_ports_for_instance_no_security(self): api = neutronapi.API() ordered_networks = [objects.NetworkRequest(network_id=uuids.net)] @@ -7639,6 +7745,10 @@ class TestAllocateForInstance(test.NoDBTestCase): 'network_id': uuids.net, 'tenant_id': uuids.tenant_id, 'admin_state_up': True, 'device_id': self.instance.uuid}}) + @mock.patch( + 'nova.network.neutron.API.has_extended_resource_request_extension', + new=mock.Mock(return_value=False), + ) def test_create_ports_for_instance_with_security_groups(self): api = neutronapi.API() ordered_networks = [objects.NetworkRequest(network_id=uuids.net)] @@ -7657,6 +7767,10 @@ class TestAllocateForInstance(test.NoDBTestCase): 'admin_state_up': True, 'security_groups': security_groups, 'device_id': self.instance.uuid}}) + @mock.patch( + 'nova.network.neutron.API.has_extended_resource_request_extension', + new=mock.Mock(return_value=False), + ) def test_create_ports_for_instance_with_cleanup_after_pc_failure(self): api = neutronapi.API() ordered_networks = [ @@ -7688,6 +7802,10 @@ class TestAllocateForInstance(test.NoDBTestCase): mock_client.delete_port.call_args_list) self.assertEqual(3, mock_client.create_port.call_count) + @mock.patch( + 'nova.network.neutron.API.has_extended_resource_request_extension', + new=mock.Mock(return_value=False), + ) def test_create_ports_for_instance_with_cleanup_after_sg_failure(self): api = neutronapi.API() ordered_networks = [ @@ -7843,6 +7961,10 @@ class TestAPINeutronHostnameDNSPortbinding(TestAPIBase): bind_host_id=self.instance.get('host'), requested_networks=requested_networks) + @mock.patch( + 'nova.network.neutron.API.has_extended_resource_request_extension', + new=mock.Mock(return_value=False) + ) def test_allocate_for_instance_create_port_with_dns_domain(self): # The port's dns_name attribute should be set by the port update # request in _update_port_dns_name. This should happen only when the @@ -7851,6 +7973,10 @@ class TestAPINeutronHostnameDNSPortbinding(TestAPIBase): self._test_allocate_for_instance_with_virtual_interface( 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) + ) 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 # request in _update_port_dns_name. This should happen only when the @@ -7999,14 +8125,16 @@ class TestNeutronPortSecurity(test.NoDBTestCase): api = neutronapi.API() mock_create_port.return_value = {'id': 'foo', 'mac_address': 'bar'} api.allocate_for_instance( - 'context', instance, requested_networks=onets, + mock.sentinel.context, instance, requested_networks=onets, security_groups=secgroups) mock_process_security_groups.assert_called_once_with( instance, mock.ANY, []) mock_create_port.assert_has_calls([ - mock.call(mock.ANY, instance, u'net1', None, []), - mock.call(mock.ANY, instance, u'net2', None, [])], + mock.call( + mock.sentinel.context, mock.ANY, instance, u'net1', None, []), + mock.call( + mock.sentinel.context, mock.ANY, instance, u'net2', None, [])], any_order=True) @mock.patch.object(neutronapi.API, 'get_instance_nw_info') @@ -8052,13 +8180,15 @@ class TestNeutronPortSecurity(test.NoDBTestCase): api = neutronapi.API() mock_create_port.return_value = {'id': 'foo', 'mac_address': 'bar'} api.allocate_for_instance( - 'context', instance, requested_networks=onets, + mock.sentinel.context, instance, requested_networks=onets, security_groups=secgroups) mock_create_port.assert_has_calls([ - mock.call(mock.ANY, instance, u'net1', None, + mock.call( + mock.sentinel.context, mock.ANY, instance, u'net1', None, ['default-uuid', 'secgrp-uuid1', 'secgrp-uuid2']), - mock.call(mock.ANY, instance, u'net2', None, + mock.call( + mock.sentinel.context, mock.ANY, instance, u'net2', None, ['default-uuid', 'secgrp-uuid1', 'secgrp-uuid2'])], any_order=True) @@ -8103,14 +8233,16 @@ class TestNeutronPortSecurity(test.NoDBTestCase): api = neutronapi.API() mock_create_port.return_value = {'id': 'foo', 'mac_address': 'bar'} api.allocate_for_instance( - 'context', instance, requested_networks=onets, + mock.sentinel.context, instance, requested_networks=onets, security_groups=secgroups) mock_process_security_groups.assert_called_once_with( instance, mock.ANY, []) mock_create_port.assert_has_calls([ - mock.call(mock.ANY, instance, u'net1', None, []), - mock.call(mock.ANY, instance, u'net2', None, [])], + mock.call( + mock.sentinel.context, mock.ANY, instance, u'net1', None, []), + mock.call( + mock.sentinel.context, mock.ANY, instance, u'net2', None, [])], any_order=True) @mock.patch.object(neutronapi.API, 'get_instance_nw_info') @@ -8363,7 +8495,7 @@ class TestAPIAutoAllocateNetwork(test.NoDBTestCase): # was auto-allocated port_req_body = mock.ANY create_port_mock.assert_called_once_with( - ntrn, instance, uuids.network_id, + self.context, ntrn, instance, uuids.network_id, None, # request.address (fixed IP) [], # security_group_ids - we didn't request any ) |