Introduce ItemAllocator class

The ItemAllocator class is used as the base class for
LinkLocalAllocator in preparation for adding
FipRulePriorityAllocator as a child class.

Change-Id: I2c77e5a895f750845b46d3e8a2326e01ea87ee78
Partial-Bug: #1414779
Signed-off-by: Ryan Moats <rmoats@us.ibm.com>
This commit is contained in:
Ryan Moats 2015-08-06 16:24:55 -05:00
parent e0999554a4
commit 2093d87270
3 changed files with 144 additions and 70 deletions

View File

@ -0,0 +1,104 @@
# Copyright 2015 IBM Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
class ItemAllocator(object):
"""Manages allocation of items from a pool
Some of the allocations such as link local addresses used for routing
inside the fip namespaces need to persist across agent restarts to maintain
consistency. Persisting such allocations in the neutron database is
unnecessary and would degrade performance. ItemAllocator utilizes local
file system to track allocations made for objects of a given class.
The persistent datastore is a file. The records are one per line of
the format: key<delimiter>value. For example if the delimiter is a ','
(the default value) then the records will be: key,value (one per line)
"""
def __init__(self, state_file, ItemClass, item_pool, delimiter=','):
"""Read the file with previous allocations recorded.
See the note in the allocate method for more detail.
"""
self.ItemClass = ItemClass
self.state_file = state_file
self.allocations = {}
self.remembered = {}
self.pool = item_pool
for line in self._read():
key, saved_value = line.strip().split(delimiter)
self.remembered[key] = self.ItemClass(saved_value)
self.pool.difference_update(self.remembered.values())
def allocate(self, key):
"""Try to allocate an item of ItemClass type.
I expect this to work in all cases because I expect the pool size to be
large enough for any situation. Nonetheless, there is some defensive
programming in here.
Since the allocations are persisted, there is the chance to leak
allocations which should have been released but were not. This leak
could eventually exhaust the pool.
So, if a new allocation is needed, the code first checks to see if
there are any remembered allocations for the key. If not, it checks
the free pool. If the free pool is empty then it dumps the remembered
allocations to free the pool. This final desperate step will not
happen often in practice.
"""
if key in self.remembered:
self.allocations[key] = self.remembered.pop(key)
return self.allocations[key]
if not self.pool:
# Desperate times. Try to get more in the pool.
self.pool.update(self.remembered.values())
self.remembered.clear()
if not self.pool:
# More than 256 routers on a compute node!
raise RuntimeError("Cannot allocate item of type:"
" %s from pool using file %s"
% (self.ItemClass, self.state_file))
self.allocations[key] = self.pool.pop()
self._write_allocations()
return self.allocations[key]
def release(self, key):
self.pool.add(self.allocations.pop(key))
self._write_allocations()
def _write_allocations(self):
current = ["%s,%s\n" % (k, v) for k, v in self.allocations.items()]
remembered = ["%s,%s\n" % (k, v) for k, v in self.remembered.items()]
current.extend(remembered)
self._write(current)
def _write(self, lines):
with open(self.state_file, "w") as f:
f.writelines(lines)
def _read(self):
if not os.path.exists(self.state_file):
return []
with open(self.state_file) as f:
return f.readlines()

View File

@ -13,7 +13,8 @@
# under the License.
import netaddr
import os
from neutron.agent.l3.item_allocator import ItemAllocator
class LinkLocalAddressPair(netaddr.IPNetwork):
@ -26,7 +27,7 @@ class LinkLocalAddressPair(netaddr.IPNetwork):
netaddr.IPNetwork("%s/%s" % (self.broadcast, self.prefixlen)))
class LinkLocalAllocator(object):
class LinkLocalAllocator(ItemAllocator):
"""Manages allocation of link local IP addresses.
These link local addresses are used for routing inside the fip namespaces.
@ -37,73 +38,13 @@ class LinkLocalAllocator(object):
Persisting these in the database is unnecessary and would degrade
performance.
"""
def __init__(self, state_file, subnet):
"""Read the file with previous allocations recorded.
See the note in the allocate method for more detail.
def __init__(self, data_store_path, subnet):
"""Create the necessary pool and item allocator
using ',' as the delimiter and LinkLocalAllocator as the
class type
"""
self.state_file = state_file
subnet = netaddr.IPNetwork(subnet)
self.allocations = {}
self.remembered = {}
for line in self._read():
key, cidr = line.strip().split(',')
self.remembered[key] = LinkLocalAddressPair(cidr)
self.pool = set(LinkLocalAddressPair(s) for s in subnet.subnet(31))
self.pool.difference_update(self.remembered.values())
def allocate(self, key):
"""Try to allocate a link local address pair.
I expect this to work in all cases because I expect the pool size to be
large enough for any situation. Nonetheless, there is some defensive
programming in here.
Since the allocations are persisted, there is the chance to leak
allocations which should have been released but were not. This leak
could eventually exhaust the pool.
So, if a new allocation is needed, the code first checks to see if
there are any remembered allocations for the key. If not, it checks
the free pool. If the free pool is empty then it dumps the remembered
allocations to free the pool. This final desperate step will not
happen often in practice.
"""
if key in self.remembered:
self.allocations[key] = self.remembered.pop(key)
return self.allocations[key]
if not self.pool:
# Desperate times. Try to get more in the pool.
self.pool.update(self.remembered.values())
self.remembered.clear()
if not self.pool:
# More than 256 routers on a compute node!
raise RuntimeError(_("Cannot allocate link local address"))
self.allocations[key] = self.pool.pop()
self._write_allocations()
return self.allocations[key]
def release(self, key):
self.pool.add(self.allocations.pop(key))
self._write_allocations()
def _write_allocations(self):
current = ["%s,%s\n" % (k, v) for k, v in self.allocations.items()]
remembered = ["%s,%s\n" % (k, v) for k, v in self.remembered.items()]
current.extend(remembered)
self._write(current)
def _write(self, lines):
with open(self.state_file, "w") as f:
f.writelines(lines)
def _read(self):
if not os.path.exists(self.state_file):
return []
with open(self.state_file) as f:
return f.readlines()
pool = set(LinkLocalAddressPair(s) for s in subnet.subnet(31))
super(LinkLocalAllocator, self).__init__(data_store_path,
LinkLocalAddressPair,
pool)

View File

@ -0,0 +1,29 @@
# Copyright 2014 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from neutron.agent.l3 import item_allocator as ia
from neutron.tests import base
class TestItemAllocator(base.BaseTestCase):
def setUp(self):
super(TestItemAllocator, self).setUp()
def test__init__(self):
test_pool = set(s for s in range(32768, 40000))
a = ia.ItemAllocator('/file', object, test_pool)
self.assertEqual('/file', a.state_file)
self.assertEqual({}, a.allocations)
self.assertEqual(object, a.ItemClass)
self.assertEqual(test_pool, a.pool)