diff --git a/neutron/plugins/ml2/plugin.py b/neutron/plugins/ml2/plugin.py index 2bd0c599eb3..43952f7d46b 100644 --- a/neutron/plugins/ml2/plugin.py +++ b/neutron/plugins/ml2/plugin.py @@ -1619,10 +1619,9 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2, for port in port_list: self._before_create_port(context, port) - port_list, net_cache = self.allocate_macs_and_ips_for_ports( - context, port_list) - try: + port_list, net_cache = self.allocate_macs_and_ips_for_ports( + context, port_list) return self._create_port_bulk(context, port_list, net_cache) except Exception: with excutils.save_and_reraise_exception(): @@ -1630,7 +1629,7 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2, # deallocated now for port in port_list: self.ipam.deallocate_ips_from_port( - context, port, port['ipams']) + context, port, port.get('ipams')) @db_api.retry_if_session_inactive() def _create_port_bulk(self, context, port_list, network_cache): diff --git a/neutron/tests/unit/plugins/ml2/test_plugin.py b/neutron/tests/unit/plugins/ml2/test_plugin.py index 624fda289b0..c0ac5a1f243 100644 --- a/neutron/tests/unit/plugins/ml2/test_plugin.py +++ b/neutron/tests/unit/plugins/ml2/test_plugin.py @@ -55,6 +55,7 @@ from neutron.db import ipam_pluggable_backend from neutron.db import provisioning_blocks from neutron.db import securitygroups_db as sg_db from neutron.db import segments_db +from neutron.ipam import driver from neutron.objects import base as base_obj from neutron.objects import ports as port_obj from neutron.objects import router as l3_obj @@ -1737,6 +1738,39 @@ class TestMl2PortsV2(test_plugin.TestPortsV2, Ml2PluginV2TestCase): ports_out = self.plugin.create_port_bulk(ctx, ports_in) self.assertEqual(edo, ports_out[0]['extra_dhcp_opts']) + def test_create_ports_bulk_with_wrong_fixed_ips(self): + cidr = '10.0.10.0/24' + with self.network() as net: + with self.subnet(net, cidr=cidr) as snet: + net_id = net['network']['id'] + data = [{'network_id': net_id, + 'fixed_ips': [{'subnet_id': snet['subnet']['id'], + 'ip_address': '10.0.10.100'}], + 'tenant_id': snet['subnet']['tenant_id'] + }, + {'network_id': net_id, + 'fixed_ips': [{'subnet_id': snet['subnet']['id'], + 'ip_address': '10.0.20.101'}], + 'tenant_id': snet['subnet']['tenant_id'] + }] + res = self._create_bulk_from_list(self.fmt, 'port', + data, as_admin=True) + self.assertEqual(webob.exc.HTTPBadRequest.code, res.status_int) + self.assertIn('IP address 10.0.20.101 is not a valid IP for ' + 'the specified subnet.', + res.json['NeutronError']['message']) + + ipam_driver = driver.Pool.get_instance(None, self.context) + ipam_allocator = ipam_driver.get_allocator([cidr]) + with db_api.CONTEXT_READER.using(self.context): + ipam_subnet = ipam_allocator._driver.get_subnet( + snet['subnet']['id']) + allocations = ipam_subnet.subnet_manager.list_allocations( + self.context) + # There are no leftovers (e.g.: 10.0.10.100) in the + # "IpamAllocation" registers + self.assertEqual([], allocations) + def test_delete_port_no_notify_in_disassociate_floatingips(self): ctx = context.get_admin_context() plugin = directory.get_plugin() diff --git a/releasenotes/notes/port_bulk_creation_no_ipamallocation_leftovers-9d72cc5f616f51e4.yaml b/releasenotes/notes/port_bulk_creation_no_ipamallocation_leftovers-9d72cc5f616f51e4.yaml new file mode 100644 index 00000000000..806f2d416ce --- /dev/null +++ b/releasenotes/notes/port_bulk_creation_no_ipamallocation_leftovers-9d72cc5f616f51e4.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + During the port bulk creation, if an IPAM allocation fails (for example, if + the IP address is outside of the subnet CIDR), the other IPAM allocations + already created are deleted before raising the exception. Fixes bug + `2039550 `_.