Support boot with extended resource request

This adds the final missing pieces to support creating servers with
ports having extended resource request. As the changes in the neutron
interface code is called from nova-compute service during the port
binding the compute service version is bumped. And a check is added to
the compute-api to reject such server create requests if there are old
computes in the cluster.

Note that some of the negative and SRIOV related interface attach
tests are also started to pass as they are not dependent on any of the
interface attach specific implementation. Still interface attach is
broken here as the failing of the positive tests show.

blueprint: qos-minimum-guaranteed-packet-rate

Change-Id: I9060cc9cb9e0d5de641ade78c5fd7e1cc77ade46
This commit is contained in:
Balazs Gibizer 2021-07-15 10:51:33 +02:00
parent 94f47471e0
commit c3886c3ca7
11 changed files with 348 additions and 156 deletions

View File

@ -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.

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -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):

View File

@ -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()

View File

@ -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

View File

@ -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,
@ -1658,14 +1652,6 @@ class MultiGroupResourceRequestBasedSchedulingTest(
self.addCleanup(patcher.stop)
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):
@ -2608,12 +2586,6 @@ class ServerMoveWithMultiGroupResourceRequestBasedSchedulingTest(
# 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()
patcher = mock.patch(
'nova.network.neutron.API.instance_has_extended_resource_request',
return_value=False,
@ -2903,6 +2875,35 @@ class CrossCellResizeWithQoSPort(PortResourceRequestBasedSchedulingTestBase):
server, qos_normal_port, qos_sriov_port)
class ExtendedResourceRequestOldCompute(
PortResourceRequestBasedSchedulingTestBase):
"""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):
self.neutron = self.useFixture(
ExtendedResourceRequestNeutronFixture(self))
ex = self.assertRaises(
client.OpenStackApiException,
self._create_server,
flavor=self.flavor,
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 '
'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
@ -2910,31 +2911,6 @@ class ExtendedResourceRequestTempNegativeTest(
are expected to be removed when support for the extension is implemented
in nova.
"""
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']}],
)
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.',
str(ex)
)
def _test_operation(self, op_name, op_callable):
# boot a server with a qos port still using the old Neutron resource
# request API extension

View File

@ -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')

View File

@ -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()

View File

@ -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
)