Merge "Support boot with extended resource request"
This commit is contained in:
commit
9abcb3825a
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -850,7 +850,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:
|
||||
|
|
|
@ -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.
|
||||
|
@ -1074,6 +1076,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,
|
||||
|
@ -5067,7 +5081,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
|
||||
|
|
|
@ -1966,10 +1966,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):
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -7244,7 +7244,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')
|
||||
|
|
|
@ -9831,6 +9831,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()
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue