Merge "Add bulk IP address assignment to ipam driver" into stable/rocky

This commit is contained in:
Zuul 2020-03-31 10:35:23 +00:00 committed by Gerrit Code Review
commit 9ab3d21789
3 changed files with 91 additions and 11 deletions

View File

@ -33,6 +33,9 @@ from neutron.ipam import utils as ipam_utils
LOG = log.getLogger(__name__)
MAX_WIN = 1000
MULTIPLIER = 100
MAX_WIN_MULTI = MAX_WIN * MULTIPLIER
class NeutronDbSubnet(ipam_base.Subnet):
@ -152,6 +155,10 @@ class NeutronDbSubnet(ipam_base.Subnet):
def _generate_ip(self, context, prefer_next=False):
"""Generate an IP address from the set of available addresses."""
return self._generate_ips(context, prefer_next)[0]
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)
@ -163,16 +170,34 @@ 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 prefer_next:
window = 1
allocated_ip_pool = list(itertools.islice(av_set,
num_addresses))
return [str(allocated_ip)
for allocated_ip in allocated_ip_pool]
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,
MAX_WIN_MULTI)
if window < num_addresses:
continue
else:
# Compute a value for the selection window
window = min(av_set.size, 30)
ip_index = random.randint(1, window)
candidate_ips = list(itertools.islice(av_set, ip_index))
allocated_ip = candidate_ips[
random.randint(0, len(candidate_ips) - 1)]
return str(allocated_ip), ip_pool.id
# 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]
raise ipam_exc.IpAddressGenerationFailure(
subnet_id=self.subnet_manager.neutron_id)
@ -182,7 +207,6 @@ class NeutronDbSubnet(ipam_base.Subnet):
# running transaction, which is started on create_port or upper level.
# To be able to do rollback/retry actions correctly ipam driver
# should not create new nested transaction blocks.
all_pool_id = None
# NOTE(salv-orlando): It would probably better to have a simpler
# model for address requests and just check whether there is a
# specific IP address specified in address_request
@ -194,8 +218,7 @@ class NeutronDbSubnet(ipam_base.Subnet):
else:
prefer_next = isinstance(address_request,
ipam_req.PreferNextAddressRequest)
ip_address, all_pool_id = self._generate_ip(self._context,
prefer_next)
ip_address = self._generate_ip(self._context, prefer_next)
# Create IP allocation request object
# The only defined status at this stage is 'ALLOCATED'.
@ -215,6 +238,26 @@ class NeutronDbSubnet(ipam_base.Subnet):
subnet_id=self.subnet_manager.neutron_id)
return ip_address
def bulk_allocate(self, address_request):
# The signature of this function differs from allocate only in that it
# returns a list of addresses, as opposed to a single address.
if not isinstance(address_request, ipam_req.BulkAddressRequest):
return [self.allocate(address_request)]
num_addrs = address_request.num_addresses
allocated_ip_pool = self._generate_ips(self._context,
False,
num_addrs)
# Create IP allocation request objects
try:
with self._context.session.begin(subtransactions=True):
for ip_address in allocated_ip_pool:
self.subnet_manager.create_allocation(self._context,
ip_address)
except db_exc.DBReferenceError:
raise n_exc.SubnetNotFound(
subnet_id=self.subnet_manager.neutron_id)
return allocated_ip_pool
def deallocate(self, address):
# This is almost a no-op because the Neutron DB IPAM driver does not
# delete IPAllocation objects at every deallocation. The only

View File

@ -202,6 +202,21 @@ class SpecificAddressRequest(AddressRequest):
return self._address
class BulkAddressRequest(AddressRequest):
"""For requesting a batch of available addresses from IPAM"""
def __init__(self, num_addresses):
"""Initialize BulkAddressRequest
:param num_addresses: The quantity of IP addresses being requested
:type num_addresses: int
"""
super(BulkAddressRequest, self).__init__()
self._num_addresses = num_addresses
@property
def num_addresses(self):
return self._num_addresses
class AnyAddressRequest(AddressRequest):
"""Used to request any available address from the pool."""

View File

@ -359,6 +359,28 @@ class TestNeutronDbIpamSubnet(testlib_api.SqlTestCase,
ipam_subnet.allocate,
ipam_req.AnyAddressRequest)
def test_bulk_allocate_v4_address(self):
target_ip_count = 10
ipam_subnet = self._create_and_allocate_ipam_subnet(
'192.168.0.0/28', 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(target_ip_count))
def test_bulk_allocate_v6_address(self):
target_ip_count = 10
ipam_subnet = self._create_and_allocate_ipam_subnet(
'fd00::/124', ip_version=constants.IP_VERSION_6)[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(target_ip_count))
def _test_deallocate_address(self, cidr, ip_version):
ipam_subnet = self._create_and_allocate_ipam_subnet(
cidr, ip_version=ip_version)[0]