Use next available address for dhcp ports
Preserve sequential IP allocation for DHCP ports to avoid interferring with templating systems like heat that expect a freshly created subnet to have higher numbered IP addresses available for resources. The change to randomized allocations was breaking heat templates that tried to use a specific address in the subnet that didn't use to be near where DHCP ports were allocated (e.g. the 10th address). Change-Id: I4dbda44460adc873b2e4dc1638a34bfac9bb1fc4
This commit is contained in:
parent
746f91c7c7
commit
3f9cb90ca2
|
@ -41,7 +41,7 @@ LOG = logging.getLogger(__name__)
|
|||
class IpamNonPluggableBackend(ipam_backend_mixin.IpamBackendMixin):
|
||||
|
||||
@staticmethod
|
||||
def _generate_ip(context, subnets, filtered_ips=None):
|
||||
def _generate_ip(context, subnets, filtered_ips=None, prefer_next=False):
|
||||
"""Generate an IP address.
|
||||
|
||||
The IP address will be generated from one of the subnets defined on
|
||||
|
@ -84,7 +84,10 @@ class IpamNonPluggableBackend(ipam_backend_mixin.IpamBackendMixin):
|
|||
|
||||
# Compute a window size, select an index inside the window, then
|
||||
# select the IP address at the selected index within the window
|
||||
window = min(av_set_size, 10)
|
||||
if prefer_next:
|
||||
window = 1
|
||||
else:
|
||||
window = min(av_set_size, 10)
|
||||
ip_index = random.randint(1, window)
|
||||
candidate_ips = list(itertools.islice(av_set, ip_index))
|
||||
if candidate_ips:
|
||||
|
@ -294,7 +297,8 @@ class IpamNonPluggableBackend(ipam_backend_mixin.IpamBackendMixin):
|
|||
self._validate_max_ips_per_port(fixed_ip_set, device_owner)
|
||||
return fixed_ip_set
|
||||
|
||||
def _allocate_fixed_ips(self, context, fixed_ips, mac_address):
|
||||
def _allocate_fixed_ips(self, context, fixed_ips, mac_address,
|
||||
prefer_next=False):
|
||||
"""Allocate IP addresses according to the configured fixed_ips."""
|
||||
ips = []
|
||||
|
||||
|
@ -326,7 +330,8 @@ class IpamNonPluggableBackend(ipam_backend_mixin.IpamBackendMixin):
|
|||
else:
|
||||
subnets = [subnet]
|
||||
# IP address allocation
|
||||
result = self._generate_ip(context, subnets, allocated_ips)
|
||||
result = self._generate_ip(context, subnets, allocated_ips,
|
||||
prefer_next)
|
||||
allocated_ips.append(result['ip_address'])
|
||||
ips.append({'ip_address': result['ip_address'],
|
||||
'subnet_id': result['subnet_id']})
|
||||
|
@ -378,6 +383,8 @@ class IpamNonPluggableBackend(ipam_backend_mixin.IpamBackendMixin):
|
|||
v4, v6_stateful, v6_stateless = self._classify_subnets(
|
||||
context, subnets)
|
||||
|
||||
# preserve previous behavior of DHCP ports choosing start of pool
|
||||
prefer_next = p['device_owner'] == constants.DEVICE_OWNER_DHCP
|
||||
fixed_configured = p['fixed_ips'] is not constants.ATTR_NOT_SPECIFIED
|
||||
if fixed_configured:
|
||||
configured_ips = self._test_fixed_ips_for_port(context,
|
||||
|
@ -387,15 +394,16 @@ class IpamNonPluggableBackend(ipam_backend_mixin.IpamBackendMixin):
|
|||
subnets)
|
||||
ips = self._allocate_fixed_ips(context,
|
||||
configured_ips,
|
||||
p['mac_address'])
|
||||
p['mac_address'],
|
||||
prefer_next=prefer_next)
|
||||
|
||||
else:
|
||||
ips = []
|
||||
version_subnets = [v4, v6_stateful]
|
||||
for subnets in version_subnets:
|
||||
if subnets:
|
||||
result = IpamNonPluggableBackend._generate_ip(context,
|
||||
subnets)
|
||||
result = IpamNonPluggableBackend._generate_ip(
|
||||
context, subnets, prefer_next=prefer_next)
|
||||
ips.append({'ip_address': result['ip_address'],
|
||||
'subnet_id': result['subnet_id']})
|
||||
|
||||
|
|
|
@ -207,7 +207,7 @@ class NeutronDbSubnet(ipam_base.Subnet):
|
|||
netaddr.IPAddress(ip_range.last).format())
|
||||
session.add(av_range)
|
||||
|
||||
def _generate_ip(self, session):
|
||||
def _generate_ip(self, session, prefer_next=False):
|
||||
"""Generate an IP address from the set of available addresses."""
|
||||
ip_allocations = netaddr.IPSet()
|
||||
for ipallocation in self.subnet_manager.list_allocations(session):
|
||||
|
@ -220,8 +220,11 @@ class NeutronDbSubnet(ipam_base.Subnet):
|
|||
if av_set.size == 0:
|
||||
continue
|
||||
|
||||
# Compute a value for the selection window
|
||||
window = min(av_set.size, 10)
|
||||
if prefer_next:
|
||||
window = 1
|
||||
else:
|
||||
# Compute a value for the selection window
|
||||
window = min(av_set.size, 10)
|
||||
ip_index = random.randint(1, window)
|
||||
candidate_ips = list(itertools.islice(av_set, ip_index))
|
||||
allocated_ip = candidate_ips[-1]
|
||||
|
@ -246,7 +249,9 @@ class NeutronDbSubnet(ipam_base.Subnet):
|
|||
ip_address = str(address_request.address)
|
||||
self._verify_ip(session, ip_address)
|
||||
else:
|
||||
ip_address, all_pool_id = self._generate_ip(session)
|
||||
prefer_next = isinstance(address_request,
|
||||
ipam_req.PreferNextAddressRequest)
|
||||
ip_address, all_pool_id = self._generate_ip(session, prefer_next)
|
||||
|
||||
# Create IP allocation request object
|
||||
# The only defined status at this stage is 'ALLOCATED'.
|
||||
|
|
|
@ -206,6 +206,10 @@ class AnyAddressRequest(AddressRequest):
|
|||
"""Used to request any available address from the pool."""
|
||||
|
||||
|
||||
class PreferNextAddressRequest(AnyAddressRequest):
|
||||
"""Used to request next available IP address from the pool."""
|
||||
|
||||
|
||||
class AutomaticAddressRequest(SpecificAddressRequest):
|
||||
"""Used to create auto generated addresses, such as EUI64"""
|
||||
EUI64 = 'eui64'
|
||||
|
@ -265,6 +269,9 @@ class AddressRequestFactory(object):
|
|||
elif ip_dict.get('eui64_address'):
|
||||
return AutomaticAddressRequest(prefix=ip_dict['subnet_cidr'],
|
||||
mac=ip_dict['mac'])
|
||||
elif port['device_owner'] == constants.DEVICE_OWNER_DHCP:
|
||||
# preserve previous behavior of DHCP ports choosing start of pool
|
||||
return PreferNextAddressRequest()
|
||||
else:
|
||||
return AnyAddressRequest()
|
||||
|
||||
|
|
|
@ -1408,6 +1408,19 @@ fixed_ips=ip_address%%3D%s&fixed_ips=ip_address%%3D%s&fixed_ips=subnet_id%%3D%s
|
|||
for fixed_ip in updated_fixed_ips:
|
||||
self.assertIn(fixed_ip, result['port']['fixed_ips'])
|
||||
|
||||
def test_dhcp_port_ips_prefer_next_available_ip(self):
|
||||
# test to check that DHCP ports get the first available IP in the
|
||||
# allocation range
|
||||
with self.subnet() as subnet:
|
||||
port_ips = []
|
||||
for _ in range(10):
|
||||
with self.port(device_owner=constants.DEVICE_OWNER_DHCP,
|
||||
subnet=subnet) as port:
|
||||
port_ips.append(port['port']['fixed_ips'][0]['ip_address'])
|
||||
first_ip = netaddr.IPAddress(port_ips[0])
|
||||
expected = [str(first_ip + i) for i in range(10)]
|
||||
self.assertEqual(expected, port_ips)
|
||||
|
||||
def test_update_port_mac_ip(self):
|
||||
with self.subnet() as subnet:
|
||||
updated_fixed_ips = [{'subnet_id': subnet['subnet']['id'],
|
||||
|
|
|
@ -75,6 +75,7 @@ class TestDbBasePluginIpam(test_db_base.NeutronDbPluginV2TestCase):
|
|||
'driver': mock.Mock(),
|
||||
'subnet': mock.Mock(),
|
||||
'subnets': mock.Mock(),
|
||||
'port': {'device_owner': 'compute:None'},
|
||||
'subnet_request': ipam_req.SpecificSubnetRequest(
|
||||
self.tenant_id,
|
||||
self.subnet_id,
|
||||
|
@ -195,7 +196,7 @@ class TestDbBasePluginIpam(test_db_base.NeutronDbPluginV2TestCase):
|
|||
ips[0]['ip_address'] = ip
|
||||
|
||||
allocated_ips = mocks['ipam']._ipam_allocate_ips(
|
||||
mock.ANY, mocks['driver'], mock.ANY, ips)
|
||||
mock.ANY, mocks['driver'], mocks['port'], ips)
|
||||
|
||||
mocks['driver'].get_allocator.assert_called_once_with([subnet])
|
||||
|
||||
|
@ -262,7 +263,7 @@ class TestDbBasePluginIpam(test_db_base.NeutronDbPluginV2TestCase):
|
|||
subnet_id, auto_ip='172.23.128.94')
|
||||
|
||||
mocks['ipam']._ipam_allocate_ips(
|
||||
mock.ANY, mocks['driver'], mock.ANY, ips)
|
||||
mock.ANY, mocks['driver'], mocks['port'], ips)
|
||||
get_calls = [mock.call([data[ip][1]]) for ip in data]
|
||||
mocks['driver'].get_allocator.assert_has_calls(
|
||||
get_calls, any_order=True)
|
||||
|
@ -292,7 +293,7 @@ class TestDbBasePluginIpam(test_db_base.NeutronDbPluginV2TestCase):
|
|||
mocks['ipam']._ipam_allocate_ips,
|
||||
mock.ANY,
|
||||
mocks['driver'],
|
||||
mock.ANY,
|
||||
mocks['port'],
|
||||
ips)
|
||||
|
||||
# get_subnet should be called only for the first two networks
|
||||
|
|
|
@ -288,25 +288,36 @@ class TestAddressRequestFactory(base.BaseTestCase):
|
|||
def test_specific_address_request_is_loaded(self):
|
||||
for address in ('10.12.0.15', 'fffe::1'):
|
||||
ip = {'ip_address': address}
|
||||
port = {'device_owner': 'compute:None'}
|
||||
self.assertIsInstance(
|
||||
ipam_req.AddressRequestFactory.get_request(None, None, ip),
|
||||
ipam_req.AddressRequestFactory.get_request(None, port, ip),
|
||||
ipam_req.SpecificAddressRequest)
|
||||
|
||||
def test_any_address_request_is_loaded(self):
|
||||
for addr in [None, '']:
|
||||
ip = {'ip_address': addr}
|
||||
port = {'device_owner': 'compute:None'}
|
||||
self.assertIsInstance(
|
||||
ipam_req.AddressRequestFactory.get_request(None, None, ip),
|
||||
ipam_req.AddressRequestFactory.get_request(None, port, ip),
|
||||
ipam_req.AnyAddressRequest)
|
||||
|
||||
def test_automatic_address_request_is_loaded(self):
|
||||
ip = {'mac': '6c:62:6d:de:cf:49',
|
||||
'subnet_cidr': '2001:470:abcd::/64',
|
||||
'eui64_address': True}
|
||||
port = {'device_owner': 'compute:None'}
|
||||
self.assertIsInstance(
|
||||
ipam_req.AddressRequestFactory.get_request(None, None, ip),
|
||||
ipam_req.AddressRequestFactory.get_request(None, port, ip),
|
||||
ipam_req.AutomaticAddressRequest)
|
||||
|
||||
def test_prefernext_address_request_on_dhcp_port(self):
|
||||
ip = {}
|
||||
port = {'device_owner': 'network:dhcp'}
|
||||
self.assertIsInstance(
|
||||
ipam_req.AddressRequestFactory.get_request(None, port, ip),
|
||||
ipam_req.PreferNextAddressRequest)
|
||||
|
||||
|
||||
|
||||
class TestSubnetRequestFactory(IpamSubnetRequestTestCase):
|
||||
|
||||
|
|
Loading…
Reference in New Issue