diff --git a/neutron/db/ipam_pluggable_backend.py b/neutron/db/ipam_pluggable_backend.py index 4a82ac22dc7..4c8fb94363a 100644 --- a/neutron/db/ipam_pluggable_backend.py +++ b/neutron/db/ipam_pluggable_backend.py @@ -386,8 +386,14 @@ class IpamPluggableBackend(ipam_backend_mixin.IpamBackendMixin): original = self._make_port_dict(db_port, process_extensions=False) if original.get('mac_address') != new_mac: original_ips = original.get('fixed_ips', []) - new_ips = new_port.setdefault('fixed_ips', original_ips) - new_ips_subnets = [new_ip['subnet_id'] for new_ip in new_ips] + # NOTE(hjensas): Only set the default for 'fixed_ips' in + # new_port if the original port or new_port actually have IPs. + # Setting the default to [] breaks deferred IP allocation. + # See Bug: https://bugs.launchpad.net/neutron/+bug/1811905 + if original_ips or new_port.get('fixed_ips'): + new_ips = new_port.setdefault('fixed_ips', original_ips) + new_ips_subnets = [new_ip['subnet_id'] + for new_ip in new_ips] for orig_ip in original_ips: if ipv6_utils.is_eui64_address(orig_ip.get('ip_address')): subnet_to_delete = {} diff --git a/neutron/tests/unit/extensions/test_segment.py b/neutron/tests/unit/extensions/test_segment.py index 192dd0a0167..c4ab96daf21 100644 --- a/neutron/tests/unit/extensions/test_segment.py +++ b/neutron/tests/unit/extensions/test_segment.py @@ -18,6 +18,7 @@ import mock import netaddr from neutron_lib.api.definitions import ip_allocation as ipalloc_apidef from neutron_lib.api.definitions import l2_adjacency as l2adj_apidef +from neutron_lib.api.definitions import port as port_apidef from neutron_lib.api.definitions import portbindings from neutron_lib.callbacks import events from neutron_lib.callbacks import exceptions @@ -1578,6 +1579,27 @@ class TestSegmentAwareIpam(SegmentAwareIpamTestCase): # Since the new host is in the same segment, it succeeds. self.assertEqual(webob.exc.HTTPOk.code, response.status_int) + def test_port_update_deferred_allocation_binding_info_and_new_mac(self): + """Binding information and new mac address is provided on update""" + network, segment, subnet = self._create_test_segment_with_subnet() + + # Map the host to the segment + self._setup_host_mappings([(segment['segment']['id'], 'fakehost')]) + + port = self._create_deferred_ip_port(network) + self._validate_deferred_ip_allocation(port['port']['id']) + + # Try requesting an IP (but the only subnet is on a segment) + data = {'port': {portbindings.HOST_ID: 'fakehost', + port_apidef.PORT_MAC_ADDRESS: '00:00:00:00:00:01'}} + port_id = port['port']['id'] + port_req = self.new_update_request('ports', data, port_id) + response = port_req.get_response(self.api) + + # Port update succeeds and allocates a new IP address. + self.assertEqual(webob.exc.HTTPOk.code, response.status_int) + self._assert_one_ip_in_subnet(response, subnet['subnet']['cidr']) + class TestSegmentAwareIpamML2(TestSegmentAwareIpam): diff --git a/releasenotes/notes/fix-deferred-alloction-when-new-mac-in-same-request-as-binding-data-2a01c1ed1a8eff66.yaml b/releasenotes/notes/fix-deferred-alloction-when-new-mac-in-same-request-as-binding-data-2a01c1ed1a8eff66.yaml new file mode 100644 index 00000000000..091b6274ec3 --- /dev/null +++ b/releasenotes/notes/fix-deferred-alloction-when-new-mac-in-same-request-as-binding-data-2a01c1ed1a8eff66.yaml @@ -0,0 +1,10 @@ +--- +fixes: + - | + Fixes an issue causing IP allocation on port update to fail when the + initial IP allocation was deferred due to lack of binding info. If both the + port mac_address and binding info (binding_host_id) were updated in the + same request, the fixed_ips field was added to the request internally. The + code to complete the deferred allocation failed to execute in that case. + (For more information see bug `1811905 + `_.)