diff --git a/neutron/db/ipam_backend_mixin.py b/neutron/db/ipam_backend_mixin.py index 946c7a38fd1..892ae1ffe2e 100644 --- a/neutron/db/ipam_backend_mixin.py +++ b/neutron/db/ipam_backend_mixin.py @@ -692,7 +692,8 @@ class IpamBackendMixin(db_base_plugin_common.DbBasePluginCommon): # subnets. Happens on routed networks when host isn't known. raise ipam_exceptions.DeferIpam() - raise ipam_exceptions.IpAddressGenerationFailureNoMatchingSubnet() + raise ipam_exceptions.IpAddressGenerationFailureNoMatchingSubnet( + network_id=network_id, service_type=service_type) def _find_candidate_subnets(self, context, network_id, host, service_type, fixed_configured): @@ -773,7 +774,8 @@ class IpamBackendMixin(db_base_plugin_common.DbBasePluginCommon): new_host_requested = host and host != old_host if old_ips and new_host_requested and not fixed_ips_requested: valid_subnets = self._ipam_get_subnets( - context, old_port['network_id'], host) + context, old_port['network_id'], host, + service_type=old_port.get('device_owner')) valid_subnet_ids = {s['id'] for s in valid_subnets} for fixed_ip in old_ips: if fixed_ip['subnet_id'] not in valid_subnet_ids: diff --git a/neutron/ipam/exceptions.py b/neutron/ipam/exceptions.py index 1b4ad070f6c..9df33add318 100644 --- a/neutron/ipam/exceptions.py +++ b/neutron/ipam/exceptions.py @@ -69,7 +69,8 @@ class IpAddressGenerationFailureAllSubnets(IpAddressGenerationFailure): class IpAddressGenerationFailureNoMatchingSubnet(IpAddressGenerationFailure): - message = _("No valid service subnet for the given device owner.") + message = _("No valid service subnet for the given device owner, " + "network %(network_id)s, service type %(service_type)s.") class IPAllocationFailed(exceptions.NeutronException): diff --git a/neutron/tests/unit/extensions/test_subnet_service_types.py b/neutron/tests/unit/extensions/test_subnet_service_types.py index fd86c081b05..d2edadc237f 100644 --- a/neutron/tests/unit/extensions/test_subnet_service_types.py +++ b/neutron/tests/unit/extensions/test_subnet_service_types.py @@ -12,6 +12,8 @@ import webob.exc +from neutron_lib.api.definitions import portbindings + from neutron.db import db_base_plugin_v2 from neutron.db import subnet_service_type_db_models from neutron.extensions import subnet_service_types @@ -40,7 +42,7 @@ class SubnetServiceTypesExtensionTestPlugin( """Test plugin to mixin the subnet service_types extension. """ - supported_extension_aliases = ["subnet-service-types"] + supported_extension_aliases = ["subnet-service-types", "binding"] class SubnetServiceTypesExtensionTestCase( @@ -324,6 +326,29 @@ class SubnetServiceTypesExtensionTestCase( # applied port = self._update('ports', port['id'], data) + def test_update_port_host_binding(self): + with self.network() as network: + pass + service_type = 'compute:foo' + # Create a subnet with a service_type + self._create_service_subnet([service_type], + cidr=self.CIDRS[1], + network=network) + # Create a port with a matching device owner + network = network['network'] + port = self._create_port(self.fmt, + net_id=network['id'], + tenant_id=network['tenant_id'], + device_owner=service_type, + arg_list=(portbindings.HOST_ID,), + **{portbindings.HOST_ID: 'fakehost'}) + port = self.deserialize('json', port)['port'] + # Update the port's host binding. + data = {'port': {portbindings.HOST_ID: 'fakehost2'}} + # self._update will fail with a MismatchError if the update cannot be + # applied + port = self._update('ports', port['id'], data) + class SubnetServiceTypesExtensionTestCasev6( SubnetServiceTypesExtensionTestCase):