diff --git a/neutron/ipam/drivers/neutrondb_ipam/driver.py b/neutron/ipam/drivers/neutrondb_ipam/driver.py index 996ba7ef783..1290b47fbe2 100644 --- a/neutron/ipam/drivers/neutrondb_ipam/driver.py +++ b/neutron/ipam/drivers/neutrondb_ipam/driver.py @@ -159,9 +159,16 @@ class NeutronDbSubnet(ipam_base.Subnet): def _generate_ips(self, context, prefer_next=False, num_addresses=1): """Generate a set of IPs from the set of available addresses.""" - ip_allocations = netaddr.IPSet() - for ipallocation in self.subnet_manager.list_allocations(context): - ip_allocations.add(ipallocation.ip_address) + allocated_ips = [] + requested_num_addresses = num_addresses + + allocations = self.subnet_manager.list_allocations(context) + # It is better not to use 'netaddr.IPSet.add', + # because _compact_single_network in 'IPSet.add' + # is quite time consuming. + ip_allocations = netaddr.IPSet( + [netaddr.IPAddress(allocation.ip_address) + for allocation in allocations]) for ip_pool in self.subnet_manager.list_pools(context): ip_set = netaddr.IPSet() @@ -170,34 +177,53 @@ class NeutronDbSubnet(ipam_base.Subnet): if av_set.size == 0: continue - if av_set.size < num_addresses: - # Not enough addresses in pool to perform validation - # TODO(njohnston): How to handle when there are enough IPs but - # not enough in a single pool to satisfy the request? - continue + if av_set.size < requested_num_addresses: + # All addresses of the address pool are allocated + # for the first time and the remaining addresses + # will be allocated in the next address pools. + allocated_num_addresses = av_set.size + else: + # All expected addresses can be assigned in this loop. + allocated_num_addresses = requested_num_addresses if prefer_next: allocated_ip_pool = list(itertools.islice(av_set, - num_addresses)) - return [str(allocated_ip) - for allocated_ip in allocated_ip_pool] + allocated_num_addresses)) + allocated_ips.extend([str(allocated_ip) + for allocated_ip in allocated_ip_pool]) + + requested_num_addresses -= allocated_num_addresses + if requested_num_addresses: + # More addresses need to be allocated in the next loop. + continue + return allocated_ips window = min(av_set.size, MAX_WIN) # NOTE(gryf): If there is more than one address, make the window # bigger, so that are chances to fulfill demanded amount of IPs. - if num_addresses > 1: - window = min(av_set.size, num_addresses * MULTIPLIER, + if allocated_num_addresses > 1: + window = min(av_set.size, + allocated_num_addresses * MULTIPLIER, MAX_WIN_MULTI) - if window < num_addresses: + if window < allocated_num_addresses: continue else: # Maximize randomness by using the random module's built in # sampling function av_ips = list(itertools.islice(av_set, 0, window)) - allocated_ip_pool = random.sample(av_ips, num_addresses) - return [str(allocated_ip) for allocated_ip in allocated_ip_pool] + allocated_ip_pool = random.sample(av_ips, + allocated_num_addresses) + allocated_ips.extend([str(allocated_ip) + for allocated_ip in allocated_ip_pool]) + + requested_num_addresses -= allocated_num_addresses + if requested_num_addresses: + # More addresses need to be allocated in the next loop. + continue + + return allocated_ips raise ipam_exc.IpAddressGenerationFailure( subnet_id=self.subnet_manager.neutron_id) diff --git a/neutron/tests/unit/ipam/drivers/neutrondb_ipam/test_driver.py b/neutron/tests/unit/ipam/drivers/neutrondb_ipam/test_driver.py index 39eb6cc3975..5d5515fd413 100644 --- a/neutron/tests/unit/ipam/drivers/neutrondb_ipam/test_driver.py +++ b/neutron/tests/unit/ipam/drivers/neutrondb_ipam/test_driver.py @@ -381,6 +381,31 @@ class TestNeutronDbIpamSubnet(testlib_api.SqlTestCase, ipam_subnet.bulk_allocate, ipam_req.BulkAddressRequest(target_ip_count)) + def test_bulk_allocate_multiple_address_pools(self): + target_ip_count = 10 + # 11 addresses available + allocation_pools = [{'start': '192.168.0.5', 'end': '192.168.0.9'}, + {'start': '192.168.0.15', 'end': '192.168.0.20'}] + ipam_subnet = self._create_and_allocate_ipam_subnet( + '192.168.0.0/24', allocation_pools=allocation_pools, + ip_version=constants.IP_VERSION_4)[0] + ip_addresses = ipam_subnet.bulk_allocate( + ipam_req.BulkAddressRequest(target_ip_count)) + self.assertEqual(target_ip_count, len(ip_addresses)) + self.assertRaises(ipam_exc.IpAddressGenerationFailure, + ipam_subnet.bulk_allocate, + ipam_req.BulkAddressRequest(2)) + + def test_prefernext_allocate_multiple_address_pools(self): + ipam_subnet = self._create_and_allocate_ipam_subnet( + '192.168.0.0/30', ip_version=constants.IP_VERSION_4)[0] + + ipam_subnet.allocate(ipam_req.PreferNextAddressRequest()) + # The second address generation request on a /30 for v4 net must fail + self.assertRaises(ipam_exc.IpAddressGenerationFailure, + ipam_subnet.allocate, + ipam_req.PreferNextAddressRequest) + def _test_deallocate_address(self, cidr, ip_version): ipam_subnet = self._create_and_allocate_ipam_subnet( cidr, ip_version=ip_version)[0]