summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api-guide/source/port_with_resource_request.rst11
-rw-r--r--doc/source/admin/ports-with-resource-requests.rst8
-rw-r--r--nova/api/openstack/compute/attach_interfaces.py4
-rw-r--r--nova/compute/api.py56
-rw-r--r--nova/compute/manager.py47
-rw-r--r--nova/network/neutron.py26
-rw-r--r--nova/objects/service.py6
-rw-r--r--nova/scheduler/utils.py13
-rw-r--r--nova/tests/functional/integrated_helpers.py5
-rw-r--r--nova/tests/functional/test_servers_resource_request.py87
-rw-r--r--nova/tests/unit/compute/test_api.py82
-rw-r--r--nova/tests/unit/network/test_neutron.py60
-rw-r--r--nova/tests/unit/scheduler/test_utils.py55
13 files changed, 295 insertions, 165 deletions
diff --git a/api-guide/source/port_with_resource_request.rst b/api-guide/source/port_with_resource_request.rst
index 1ab9e9f366..3aae3ccf9e 100644
--- a/api-guide/source/port_with_resource_request.rst
+++ b/api-guide/source/port_with_resource_request.rst
@@ -44,14 +44,9 @@ 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 fully support the new extension. If the
-extension is enabled in neutron, then nova will reject 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.
+of nova 24.0.0 (Xena), nova also supports this extension if every nova-compute
+service is 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 23fe8049bb..e48f633e88 100644
--- a/doc/source/admin/ports-with-resource-requests.rst
+++ b/doc/source/admin/ports-with-resource-requests.rst
@@ -71,12 +71,8 @@ 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 fully support the new extension. If the
-extension is enabled in neutron, then nova will reject 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
+of nova 24.0.0 (Xena), nova also supports this extension if every nova-compute
+service is upgraded to Xena version and the
:oslo.config:option:`upgrade_levels.compute` configuration does not prevent
the computes from using the latest RPC version.
diff --git a/nova/api/openstack/compute/attach_interfaces.py b/nova/api/openstack/compute/attach_interfaces.py
index a329420878..6a24a60959 100644
--- a/nova/api/openstack/compute/attach_interfaces.py
+++ b/nova/api/openstack/compute/attach_interfaces.py
@@ -177,7 +177,9 @@ class InterfaceAttachmentController(wsgi.Controller):
exception.NetworksWithQoSPolicyNotSupported,
exception.InterfaceAttachPciClaimFailed,
exception.InterfaceAttachResourceAllocationFailed,
- exception.ForbiddenPortsWithAccelerator) as e:
+ exception.ForbiddenPortsWithAccelerator,
+ exception.ExtendedResourceRequestOldCompute,
+ ) as e:
raise exc.HTTPBadRequest(explanation=e.format_message())
except (
exception.InstanceIsLocked,
diff --git a/nova/compute/api.py b/nova/compute/api.py
index d831fed376..d41d8b6109 100644
--- a/nova/compute/api.py
+++ b/nova/compute/api.py
@@ -113,6 +113,7 @@ SUPPORT_VNIC_TYPE_ACCELERATOR = 57
MIN_COMPUTE_BOOT_WITH_EXTENDED_RESOURCE_REQUEST = 58
MIN_COMPUTE_MOVE_WITH_EXTENDED_RESOURCE_REQUEST = 59
+MIN_COMPUTE_INT_ATTACH_WITH_EXTENDED_RES_REQ = 60
# 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
@@ -5097,20 +5098,37 @@ class API:
self.volume_api.attachment_delete(
context, new_attachment_id)
- def support_port_attach(self, context, port):
- """Returns false if neutron is configured with extended resource
- request and the port has resource request.
-
- 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(
+ def ensure_compute_version_for_resource_request(
+ self, context, instance, port
+ ):
+ """Checks that the compute service version is new enough for the
+ resource request of the port.
+ """
+ if self.network_api.has_extended_resource_request_extension(
context
):
- return True
+ # TODO(gibi): Remove this check in Y where we can be sure that
+ # the compute is already upgraded to X.
+ res_req = port.get(constants.RESOURCE_REQUEST) or {}
+ groups = res_req.get('request_groups', [])
+ if groups:
+ svc = objects.Service.get_by_host_and_binary(
+ context, instance.host, 'nova-compute')
+ if svc.version < MIN_COMPUTE_INT_ATTACH_WITH_EXTENDED_RES_REQ:
+ raise exception.ExtendedResourceRequestOldCompute()
- resource_request = port.get('resource_request', {})
- return not resource_request.get('request_groups', [])
+ else:
+ # NOTE(gibi): Checking if the requested port has resource request
+ # as such ports are only supported if the compute service version
+ # is >= 55.
+ # TODO(gibi): Remove this check in X as there we can be sure
+ # that all computes are new enough.
+ if port.get(constants.RESOURCE_REQUEST):
+ svc = objects.Service.get_by_host_and_binary(
+ context, instance.host, 'nova-compute')
+ if svc.version < 55:
+ raise exception.AttachInterfaceWithQoSPolicyNotSupported(
+ instance_uuid=instance.uuid)
@check_instance_lock
@check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.PAUSED,
@@ -5124,18 +5142,6 @@ class API:
if port_id:
port = self.network_api.show_port(context, port_id)['port']
- # NOTE(gibi): Checking if the requested port has resource request
- # as such ports are only supported if the compute service version
- # is >= 55.
- # TODO(gibi): Remove this check in X as there we can be sure
- # that all computes are new enough.
- if port.get(constants.RESOURCE_REQUEST):
- svc = objects.Service.get_by_host_and_binary(
- context, instance.host, 'nova-compute')
- if svc.version < 55:
- raise exception.AttachInterfaceWithQoSPolicyNotSupported(
- instance_uuid=instance.uuid)
-
if port.get('binding:vnic_type', "normal") == "vdpa":
# FIXME(sean-k-mooney): Attach works but detach results in a
# QEMU error; blocked until this is resolved
@@ -5148,8 +5154,8 @@ class API:
network_model.VNIC_TYPE_ACCELERATOR_DIRECT_PHYSICAL):
raise exception.ForbiddenPortsWithAccelerator()
- if not self.support_port_attach(context, port):
- raise exception.AttachWithExtendedQoSPolicyNotSupported()
+ self.ensure_compute_version_for_resource_request(
+ context, instance, port)
return self.compute_rpcapi.attach_interface(context,
instance=instance, network_id=network_id, port_id=port_id,
diff --git a/nova/compute/manager.py b/nova/compute/manager.py
index be17fb2db2..0673e5f4af 100644
--- a/nova/compute/manager.py
+++ b/nova/compute/manager.py
@@ -7650,27 +7650,28 @@ class ComputeManager(manager.Manager):
if not request_groups:
return None, None
- # NOTE(gibi): we assume a single RequestGroup here as:
- # 1) there can only be a single port per interface attach request
- # 2) a single port can only request resources in a single RequestGroup
- # as per the current neutron API.
- # #2) might change in the future so both
- # nova.network.neutron.API.create_resource_requests() and this function
- # takes a list of groups
- request_group = request_groups[0]
-
# restrict the resource request to the current compute node. The
# compute node uuid is the uuid of the root provider of the node in
# placement
compute_node_uuid = objects.ComputeNode.get_by_nodename(
context, instance.node).uuid
- request_group.in_tree = compute_node_uuid
-
- # NOTE(gibi): when support is added for attaching a cyborg based
- # smart NIC the ResourceRequest could be extended to handle multiple
- # request groups.
- rr = scheduler_utils.ResourceRequest.from_request_group(
- request_group, request_level_params)
+ # we can have multiple request groups, it would be enough to restrict
+ # only one of them to the compute tree but for symetry we restrict
+ # all of them
+ for request_group in request_groups:
+ request_group.in_tree = compute_node_uuid
+
+ # NOTE(gibi): group policy is mandatory in a resource request if there
+ # are multiple groups. The policy can only come from the flavor today
+ # and a new flavor is not provided with an interface attach request and
+ # the instance's current flavor might not have a policy. Still we are
+ # attaching a single port where currently the two possible groups
+ # (one for bandwidth and one for packet rate) will always be allocated
+ # from different providers. So both possible policies (none, isolated)
+ # are always fulfilled for this single port. We still has to specify
+ # one so we specify the least restrictive now.
+ rr = scheduler_utils.ResourceRequest.from_request_groups(
+ request_groups, request_level_params, group_policy='none')
res = self.reportclient.get_allocation_candidates(context, rr)
alloc_reqs, provider_sums, version = res
@@ -10293,14 +10294,20 @@ class ComputeManager(manager.Manager):
{'intf': vif['id']},
instance=instance)
profile = vif.get('profile', {}) or {} # profile can be None
- if profile.get('allocation'):
+ rps = profile.get('allocation')
+ if rps:
+ if isinstance(rps, dict):
+ # if extended resource request extension is enabled
+ # then we have a dict of providers, flatten it for the
+ # log.
+ rps = ','.join(rps.values())
LOG.error(
'The bound port %(port_id)s is deleted in Neutron but '
- 'the resource allocation on the resource provider '
- '%(rp_uuid)s is leaked until the server '
+ 'the resource allocation on the resource providers '
+ '%(rp_uuid)s are leaked until the server '
'%(server_uuid)s is deleted.',
{'port_id': vif['id'],
- 'rp_uuid': vif['profile']['allocation'],
+ 'rp_uuid': rps,
'server_uuid': instance.uuid})
del network_info[index]
diff --git a/nova/network/neutron.py b/nova/network/neutron.py
index 60ca345353..00b9b4009e 100644
--- a/nova/network/neutron.py
+++ b/nova/network/neutron.py
@@ -1754,15 +1754,27 @@ class API:
if port:
# if there is resource associated to this port then that needs to
# be deallocated so lets return info about such allocation
- resource_request = port.get(constants.RESOURCE_REQUEST)
+ resource_request = port.get(constants.RESOURCE_REQUEST) or {}
profile = get_binding_profile(port)
- allocated_rp = profile.get(constants.ALLOCATION)
- if resource_request and allocated_rp:
- port_allocation = {
- allocated_rp: {
- "resources": resource_request.get("resources", {})
+ if self.has_extended_resource_request_extension(context, neutron):
+ # new format
+ groups = resource_request.get(constants.REQUEST_GROUPS)
+ if groups:
+ allocated_rps = profile.get(constants.ALLOCATION)
+ for group in groups:
+ allocated_rp = allocated_rps[group['id']]
+ port_allocation[allocated_rp] = {
+ "resources": group.get("resources", {})
+ }
+ else:
+ # legacy format
+ allocated_rp = profile.get(constants.ALLOCATION)
+ if resource_request and allocated_rp:
+ port_allocation = {
+ allocated_rp: {
+ "resources": resource_request.get("resources", {})
+ }
}
- }
else:
# Check the info_cache. If the port is still in the info_cache and
# in that cache there is allocation in the profile then we suspect
diff --git a/nova/objects/service.py b/nova/objects/service.py
index 64a181dae3..9cdf618ea2 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 = 59
+SERVICE_VERSION = 60
# NOTE(danms): This is our SERVICE_VERSION history. The idea is that any
@@ -209,6 +209,10 @@ SERVICE_VERSION_HISTORY = (
# Add support for server move operations with neutron extended resource
# request
{'compute_rpc': '6.0'},
+ # Version 60: Compute RPC v6.0:
+ # Add support for interface attach operation 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/scheduler/utils.py b/nova/scheduler/utils.py
index 9b6f6514cb..a98aabe51b 100644
--- a/nova/scheduler/utils.py
+++ b/nova/scheduler/utils.py
@@ -199,17 +199,22 @@ class ResourceRequest(object):
return res_req
@classmethod
- def from_request_group(
+ def from_request_groups(
cls,
- request_group: 'objects.RequestGroup',
+ request_groups: ty.List['objects.RequestGroup'],
request_level_params: 'objects.RequestLevelParams',
+ group_policy: str,
) -> 'ResourceRequest':
- """Create a new instance of ResourceRequest from a RequestGroup."""
+ """Create a new instance of ResourceRequest from a list of
+ RequestGroup objects.
+ """
res_req = cls()
res_req._root_required = request_level_params.root_required
res_req._root_forbidden = request_level_params.root_forbidden
res_req._same_subtree = request_level_params.same_subtree
- res_req._add_request_group(request_group)
+ res_req.group_policy = group_policy
+ for request_group in request_groups:
+ res_req._add_request_group(request_group)
res_req.strip_zeros()
return res_req
diff --git a/nova/tests/functional/integrated_helpers.py b/nova/tests/functional/integrated_helpers.py
index 6bb0e67c63..c952f80045 100644
--- a/nova/tests/functional/integrated_helpers.py
+++ b/nova/tests/functional/integrated_helpers.py
@@ -19,6 +19,7 @@ Provides common functionality for integrated unit tests
import collections
import random
+import re
import string
import time
@@ -258,9 +259,9 @@ class InstanceHelperMixin:
server['id'], expected_statuses, actual_status,
))
- def _wait_for_log(self, log_line):
+ def _wait_for_log(self, log_line_regex):
for i in range(10):
- if log_line in self.stdlog.logger.output:
+ if re.search(log_line_regex, self.stdlog.logger.output):
return
time.sleep(0.5)
diff --git a/nova/tests/functional/test_servers_resource_request.py b/nova/tests/functional/test_servers_resource_request.py
index d308980209..c2aaf19549 100644
--- a/nova/tests/functional/test_servers_resource_request.py
+++ b/nova/tests/functional/test_servers_resource_request.py
@@ -14,7 +14,6 @@
import copy
import logging
-import unittest
from keystoneauth1 import adapter
import mock
@@ -1342,16 +1341,12 @@ class PortResourceRequestBasedSchedulingTest(
response = self.api.api_post('/os-server-external-events', events).body
self.assertEqual(200, response['events'][0]['code'])
- port_rp_uuid = self.ovs_bridge_rp_per_host[self.compute1_rp_uuid]
-
# 1) Nova logs an ERROR about the leak
self._wait_for_log(
- 'ERROR [nova.compute.manager] The bound port %(port_id)s is '
- 'deleted in Neutron but the resource allocation on the resource '
- 'provider %(rp_uuid)s is leaked until the server %(server_uuid)s '
- 'is deleted.'
+ 'The bound port %(port_id)s is deleted in Neutron but the '
+ 'resource allocation on the resource providers .* are leaked '
+ 'until the server %(server_uuid)s is deleted.'
% {'port_id': port['id'],
- 'rp_uuid': port_rp_uuid,
'server_uuid': server['id']})
allocations = self.placement.get(
@@ -1635,8 +1630,6 @@ class ExtendedPortResourceRequestBasedSchedulingTestBase(
port['id'], group_req, pps_allocations)
-# TODO(gibi): The tests are failing today as we need to fix a bunch of TODOs in
-# the code.
class MultiGroupResourceRequestBasedSchedulingTest(
ExtendedPortResourceRequestBasedSchedulingTestBase,
PortResourceRequestBasedSchedulingTest,
@@ -1650,31 +1643,6 @@ class MultiGroupResourceRequestBasedSchedulingTest(
super().setUp()
self.neutron = self.useFixture(
MultiGroupResourceRequestNeutronFixture(self))
- # 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,
- )
- self.addCleanup(patcher.stop)
- patcher.start()
-
- @unittest.expectedFailure
- def test_interface_attach_with_resource_request(self):
- super().test_interface_attach_with_resource_request()
-
- @unittest.expectedFailure
- def test_interface_attach_with_resource_request_no_candidates(self):
- super().test_interface_attach_with_resource_request_no_candidates()
-
- @unittest.expectedFailure
- def test_interface_detach_with_port_with_bandwidth_request(self):
- super().test_interface_detach_with_port_with_bandwidth_request()
-
- @unittest.expectedFailure
- def test_delete_bound_port_in_neutron_with_resource_request(self):
- super().test_delete_bound_port_in_neutron_with_resource_request()
class ServerMoveWithPortResourceRequestTest(
@@ -2959,51 +2927,26 @@ class ExtendedResourceRequestOldCompute(
lambda server: shelve_offload_then_unshelve(server),
)
-
-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
+ @mock.patch('nova.objects.service.Service.get_by_host_and_binary')
+ def test_interface_attach(self, mock_get_service):
+ # service version 59 allows booting
+ mock_get_service.return_value.version = 59
server = self._create_server(
flavor=self.flavor,
- networks=[{'port': self.neutron.port_with_resource_request['id']}],
+ networks=[{'port': self.neutron.port_1['id']}],
)
- self._wait_for_state_change(server, 'ACTIVE')
-
- # enable the new extended-resource-request Neutron API extension by
- # replacing the old neutron fixture with a new one that enables the
- # extension. Note that we are carrying over the state of the neutron
- # to the new extension to keep the port bound to the server.
- self.neutron = self.useFixture(
- ExtendedResourceRequestNeutronFixture.
- create_with_existing_neutron_state(self.neutron))
-
- # nova does not support this Neutron API extension yet so the
- # operation fails
+ self._wait_for_state_change(server, "ACTIVE")
+ # for interface attach service version 60 would be needed
ex = self.assertRaises(
client.OpenStackApiException,
- op_callable,
+ self._attach_interface,
server,
+ self.neutron.port_with_sriov_resource_request['id'],
)
self.assertEqual(400, ex.response.status_code)
self.assertIn(
- f'The {op_name} with port having extended resource request, like '
- f'a port with both QoS minimum bandwidth and packet rate '
- f'policies, is not yet supported.',
+ 'The port-resource-request-groups neutron API extension is not '
+ 'supported by old nova compute service. Upgrade your compute '
+ 'services to Xena (24.0.0) or later.',
str(ex)
)
-
- def test_interface_attach(self):
- self._test_operation(
- 'interface attach server operation',
- lambda server: self._attach_interface(
- server,
- self.neutron.port_with_sriov_resource_request['id'],
- ),
- )
diff --git a/nova/tests/unit/compute/test_api.py b/nova/tests/unit/compute/test_api.py
index d1f73a4c9d..64be53f923 100644
--- a/nova/tests/unit/compute/test_api.py
+++ b/nova/tests/unit/compute/test_api.py
@@ -7267,6 +7267,10 @@ class ComputeAPIUnitTestCase(_ComputeAPIUnitTestMixIn, test.NoDBTestCase):
mock_record.assert_called_once_with(
self.context, instance, instance_actions.ATTACH_INTERFACE)
+ @mock.patch(
+ 'nova.network.neutron.API.has_extended_resource_request_extension',
+ new=mock.Mock(return_value=False),
+ )
@mock.patch('nova.objects.service.Service.get_by_host_and_binary')
@mock.patch('nova.compute.api.API._record_action_start')
def test_attach_interface_qos_aware_port_old_compute(
@@ -7330,6 +7334,84 @@ class ComputeAPIUnitTestCase(_ComputeAPIUnitTestMixIn, test.NoDBTestCase):
port_id=mock.sentinel.port_id, requested_ip=mock.sentinel.ip,
tag=mock.sentinel.tag)
+ @mock.patch(
+ 'nova.network.neutron.API.has_extended_resource_request_extension',
+ new=mock.Mock(return_value=True),
+ )
+ @mock.patch('nova.objects.service.Service.get_by_host_and_binary')
+ @mock.patch('nova.compute.api.API._record_action_start')
+ def test_attach_interface_extended_qos_port_old_compute(
+ self, mock_record, mock_get_service
+ ):
+ instance = self._create_instance_obj()
+ service = objects.Service()
+ service.version = 59
+ mock_get_service.return_value = service
+ with mock.patch.object(
+ self.compute_api.network_api, 'show_port',
+ return_value={
+ 'port': {
+ constants.RESOURCE_REQUEST: {
+ 'request_groups': [
+ {
+ 'resources': {'CUSTOM_RESOURCE_CLASS': 42}
+ }
+ ],
+ }
+ }
+ }
+ ) as mock_show_port:
+ self.assertRaises(
+ nova.exception.ExtendedResourceRequestOldCompute,
+ self.compute_api.attach_interface,
+ self.context, instance,
+ 'foo_net_id', 'foo_port_id', None
+ )
+ mock_show_port.assert_called_once_with(self.context, 'foo_port_id')
+ mock_get_service.assert_called_once_with(
+ self.context, instance.host, 'nova-compute')
+
+ @mock.patch(
+ 'nova.network.neutron.API.has_extended_resource_request_extension',
+ new=mock.Mock(return_value=True)
+ )
+ @mock.patch('nova.compute.rpcapi.ComputeAPI.attach_interface')
+ @mock.patch('nova.objects.service.Service.get_by_host_and_binary')
+ @mock.patch('nova.compute.api.API._record_action_start')
+ def test_attach_interface_extended_qos_port(
+ self, mock_record, mock_get_service, mock_attach
+ ):
+ instance = self._create_instance_obj()
+ service = objects.Service()
+ service.version = 60
+ mock_get_service.return_value = service
+ with mock.patch.object(
+ self.compute_api.network_api, 'show_port',
+ return_value={
+ 'port': {
+ constants.RESOURCE_REQUEST: {
+ 'request_groups': [
+ {
+ 'resources': {'CUSTOM_RESOURCE_CLASS': 42}
+ }
+ ],
+ }
+ }
+ }
+ ) as mock_show_port:
+ self.compute_api.attach_interface(
+ self.context, instance, mock.sentinel.net_id,
+ mock.sentinel.port_id, mock.sentinel.ip, mock.sentinel.tag)
+
+ mock_show_port.assert_called_once_with(
+ self.context, mock.sentinel.port_id)
+ mock_get_service.assert_called_once_with(
+ self.context, instance.host, 'nova-compute')
+ mock_attach.assert_called_once_with(
+ self.context, instance=instance, network_id=mock.sentinel.net_id,
+ port_id=mock.sentinel.port_id, requested_ip=mock.sentinel.ip,
+ tag=mock.sentinel.tag)
+
@mock.patch('nova.compute.api.API._record_action_start')
@mock.patch.object(compute_rpcapi.ComputeAPI, 'detach_interface')
def test_detach_interface(self, mock_detach, mock_record):
diff --git a/nova/tests/unit/network/test_neutron.py b/nova/tests/unit/network/test_neutron.py
index 1ac357f39f..a55c091635 100644
--- a/nova/tests/unit/network/test_neutron.py
+++ b/nova/tests/unit/network/test_neutron.py
@@ -5412,6 +5412,10 @@ class TestAPI(TestAPIBase):
vif.destroy.assert_called_once_with()
self.assertEqual({}, port_allocation)
+ @mock.patch(
+ 'nova.network.neutron.API.has_extended_resource_request_extension',
+ new=mock.Mock(return_value=False),
+ )
@mock.patch('nova.network.neutron.API.get_instance_nw_info')
@mock.patch('nova.network.neutron.API._delete_nic_metadata')
@mock.patch.object(objects.VirtualInterface, 'get_by_uuid')
@@ -5458,6 +5462,62 @@ class TestAPI(TestAPIBase):
},
port_allocation)
+ @mock.patch(
+ 'nova.network.neutron.API.has_extended_resource_request_extension',
+ new=mock.Mock(return_value=True),
+ )
+ @mock.patch('nova.network.neutron.API.get_instance_nw_info')
+ @mock.patch('nova.network.neutron.API._delete_nic_metadata')
+ @mock.patch.object(objects.VirtualInterface, 'get_by_uuid')
+ @mock.patch('nova.network.neutron.get_client')
+ def test_deallocate_port_for_instance_port_with_extended_allocation(
+ self, mock_get_client, mock_get_vif_by_uuid, mock_del_nic_meta,
+ mock_netinfo):
+ mock_inst = mock.Mock(project_id="proj-1",
+ availability_zone='zone-1',
+ uuid='inst-1')
+ mock_inst.get_network_info.return_value = [
+ model.VIF(id=uuids.port_uid, preserve_on_delete=True)
+ ]
+ vif = objects.VirtualInterface()
+ vif.tag = 'foo'
+ vif.destroy = mock.MagicMock()
+ mock_get_vif_by_uuid.return_value = vif
+
+ mock_client = mock.Mock()
+ mock_client.show_port.return_value = {
+ 'port': {
+ constants.RESOURCE_REQUEST: {
+ 'request_groups': [
+ {
+ 'id': uuids.group1,
+ 'resources': {
+ 'NET_BW_EGR_KILOBIT_PER_SEC': 1000,
+ }
+ }
+ ],
+ },
+ 'binding:profile': {
+ 'allocation': {uuids.group1: uuids.rp1}
+ }
+ }
+ }
+ mock_get_client.return_value = mock_client
+
+ _, port_allocation = self.api.deallocate_port_for_instance(
+ mock.sentinel.ctx, mock_inst, uuids.port_id)
+
+ self.assertEqual(
+ {
+ uuids.rp1: {
+ "resources": {
+ 'NET_BW_EGR_KILOBIT_PER_SEC': 1000
+ }
+ }
+ },
+ port_allocation
+ )
+
@mock.patch('nova.network.neutron.API.get_instance_nw_info')
@mock.patch('nova.network.neutron.API._delete_nic_metadata')
@mock.patch.object(objects.VirtualInterface, 'get_by_uuid')
diff --git a/nova/tests/unit/scheduler/test_utils.py b/nova/tests/unit/scheduler/test_utils.py
index 1111933e22..8aff5b902e 100644
--- a/nova/tests/unit/scheduler/test_utils.py
+++ b/nova/tests/unit/scheduler/test_utils.py
@@ -1357,36 +1357,53 @@ class TestUtils(TestUtilsBase):
rr = utils.ResourceRequest.from_request_spec(rs)
self.assertResourceRequestsEqual(expected, rr)
- def test_resource_request_from_request_group(self):
- rg = objects.RequestGroup.from_port_request(
+ def test_resource_request_from_request_groups(self):
+ rgs = objects.RequestGroup.from_extended_port_request(
self.context,
- uuids.port_id,
port_resource_request={
- "resources": {
- "NET_BW_IGR_KILOBIT_PER_SEC": 1000,
- "NET_BW_EGR_KILOBIT_PER_SEC": 1000},
- "required": ["CUSTOM_PHYSNET_2",
- "CUSTOM_VNIC_TYPE_NORMAL"]
+ "request_groups": [
+ {
+ "id": "group1",
+ "resources": {
+ "NET_BW_IGR_KILOBIT_PER_SEC": 1000,
+ "NET_BW_EGR_KILOBIT_PER_SEC": 1000},
+ "required": ["CUSTOM_PHYSNET_2",
+ "CUSTOM_VNIC_TYPE_NORMAL"]
+ },
+ {
+ "id": "group2",
+ "resources": {
+ "NET_PACKET_RATE_KILOPACKET_PER_SEC": 100,
+ },
+ "required": ["CUSTOM_VNIC_TYPE_NORMAL"],
+ }
+ ],
}
)
req_lvl_params = objects.RequestLevelParams(
root_required={"CUSTOM_BLUE"},
root_forbidden={"CUSTOM_DIRTY"},
- same_subtree=[[uuids.group1]],
+ same_subtree=[["group1", "group2"]],
)
- rr = utils.ResourceRequest.from_request_group(rg, req_lvl_params)
+ rr = utils.ResourceRequest.from_request_groups(
+ rgs, req_lvl_params, 'none')
self.assertEqual(
- f'limit=1000&'
- f'required{uuids.port_id}='
- f'CUSTOM_PHYSNET_2%2C'
- f'CUSTOM_VNIC_TYPE_NORMAL&'
- f'resources{uuids.port_id}='
- f'NET_BW_EGR_KILOBIT_PER_SEC%3A1000%2C'
- f'NET_BW_IGR_KILOBIT_PER_SEC%3A1000&'
- f'root_required=CUSTOM_BLUE%2C%21CUSTOM_DIRTY&'
- f'same_subtree={uuids.group1}',
+ 'group_policy=none&'
+ 'limit=1000&'
+ 'requiredgroup1='
+ 'CUSTOM_PHYSNET_2%2C'
+ 'CUSTOM_VNIC_TYPE_NORMAL&'
+ 'requiredgroup2='
+ 'CUSTOM_VNIC_TYPE_NORMAL&'
+ 'resourcesgroup1='
+ 'NET_BW_EGR_KILOBIT_PER_SEC%3A1000%2C'
+ 'NET_BW_IGR_KILOBIT_PER_SEC%3A1000&'
+ 'resourcesgroup2='
+ 'NET_PACKET_RATE_KILOPACKET_PER_SEC%3A100&'
+ 'root_required=CUSTOM_BLUE%2C%21CUSTOM_DIRTY&'
+ 'same_subtree=group1%2Cgroup2',
rr.to_querystring())
def test_resource_request_add_group_inserts_the_group(self):