diff --git a/neutron/db/ipam_backend_mixin.py b/neutron/db/ipam_backend_mixin.py index 7b448e4be48..e9caee98018 100644 --- a/neutron/db/ipam_backend_mixin.py +++ b/neutron/db/ipam_backend_mixin.py @@ -695,7 +695,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): @@ -775,7 +776,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 feb93a7f803..250ca7aad26 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 59e421ff33f..fe78a018677 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.extensions import subnet_service_types from neutron.tests.unit.db import test_db_base_plugin_v2 @@ -38,7 +40,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( @@ -322,6 +324,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):