Spread allocations of fixed ips

The default fixed_ip allocation always does .first() out of the fixed
ip pool. This leads to a pathological situation that in a rapidly
changing environment we're constantly recycling the same small number
of ips out of the pool. Given other racey behavior in dnsmasq around
processing DHCP Release requests, we can start to get DHCP fails when
a DHCP release is missed, we immediately recycle the IP and give it to
a new guest.

We can mitigate this by sorting the results by updated_at. This
means we'll favor least recently used fixed ips.

Depends-On: I27f73c1edf12218818c4d279efbd9fef5cdef672

Related-Bug: #1532809

Change-Id: I3c83bd68a0e2bbbcdd6d955722dbc9f9fc528113
(cherry picked from commit 32b4ce0e92)
This commit is contained in:
Sean Dague 2016-01-27 13:02:35 +00:00
parent 686e342f16
commit 65d0ef8b69
2 changed files with 29 additions and 0 deletions

View File

@ -1139,6 +1139,13 @@ def fixed_ip_associate(context, address, instance_uuid, network_id=None,
retry_on_request=True)
def fixed_ip_associate_pool(context, network_id, instance_uuid=None,
host=None, virtual_interface_id=None):
"""allocate a fixed ip out of a fixed ip network pool.
This allocates an unallocated fixed ip out of a specified
network. We sort by updated_at to hand out the oldest address in
the list.
"""
if instance_uuid and not uuidutils.is_uuid_like(instance_uuid):
raise exception.InvalidUUID(uuid=instance_uuid)
@ -1152,6 +1159,7 @@ def fixed_ip_associate_pool(context, network_id, instance_uuid=None,
filter_by(reserved=False).\
filter_by(instance_uuid=None).\
filter_by(host=None).\
order_by(asc(models.FixedIp.updated_at)).\
first()
if not fixed_ip_ref:

View File

@ -4588,6 +4588,27 @@ class FixedIPTestCase(BaseInstanceTypeTestCase):
fixed_ip = db.fixed_ip_get_by_address(self.ctxt, address)
self.assertEqual(fixed_ip['instance_uuid'], instance_uuid)
def test_fixed_ip_associate_pool_order(self):
"""Test that fixed_ip always uses oldest fixed_ip.
We should always be using the fixed ip with the oldest
updated_at.
"""
instance_uuid = self._create_instance()
network = db.network_create_safe(self.ctxt, {})
self.addCleanup(timeutils.clear_time_override)
start = timeutils.utcnow()
for i in range(1, 4):
now = start - datetime.timedelta(hours=i)
timeutils.set_time_override(now)
address = self.create_fixed_ip(
updated_at=now,
address='10.1.0.%d' % i,
network_id=network['id'])
db.fixed_ip_associate_pool(self.ctxt, network['id'], instance_uuid)
fixed_ip = db.fixed_ip_get_by_address(self.ctxt, address)
self.assertEqual(fixed_ip['instance_uuid'], instance_uuid)
def test_fixed_ip_associate_pool_succeeds_fip_ref_network_id_is_none(self):
instance_uuid = self._create_instance()
network = db.network_create_safe(self.ctxt, {})