Merge pull request #121 from asadoughi/allocation_pools

Allocation pools
This commit is contained in:
Matt Dietz
2013-07-18 11:02:37 -07:00
5 changed files with 104 additions and 22 deletions

View File

@@ -16,13 +16,9 @@
# under the License.
from neutron.api import extensions
from neutron.api.v2 import attributes
EXTENDED_ATTRIBUTES_2_0 = {
'subnets': {
"allocation_pools": {'allow_post': True, 'allow_put': True,
'default': attributes.ATTR_NOT_SPECIFIED,
'is_visible': False},
"enable_dhcp": {'allow_post': False, 'allow_put': False,
'default': False,
'is_visible': True},

View File

@@ -30,7 +30,8 @@ LOG = logging.getLogger("neutron")
class QuarkIpam(object):
def _get_ip_policy_rule_set(self, subnet):
@staticmethod
def get_ip_policy_rule_set(subnet):
ip_policy = subnet["ip_policy"] or \
subnet["network"]["ip_policy"] or \
dict()
@@ -38,7 +39,7 @@ class QuarkIpam(object):
ip_policy_rules = netaddr.IPSet(
[netaddr.IPNetwork((int(ippr["address"]), ippr["prefix"]))
for ippr in ip_policy_rules])
subnet_set = netaddr.IPSet(netaddr.IPNetwork(subnet["cidr"]))
subnet_set = netaddr.IPSet([netaddr.IPNetwork(subnet["cidr"])])
ip_policy_rules = subnet_set & ip_policy_rules
return ip_policy_rules
@@ -57,7 +58,7 @@ class QuarkIpam(object):
ip_policy_rules = None
if not ip_address:
ip_policy_rules = self._get_ip_policy_rule_set(subnet)
ip_policy_rules = self.get_ip_policy_rule_set(subnet)
policy_size = ip_policy_rules.size if ip_policy_rules else 0
if ipnet.size > (ips_in_subnet + policy_size):
return subnet
@@ -117,7 +118,7 @@ class QuarkIpam(object):
subnet = self._choose_available_subnet(
elevated, net_id, ip_address=ip_address, version=version)
ip_policy_rules = self._get_ip_policy_rule_set(subnet)
ip_policy_rules = self.get_ip_policy_rule_set(subnet)
# Creating this IP for the first time
next_ip = None

View File

@@ -251,6 +251,7 @@ class Plugin(neutron_plugin_base_v2.NeutronPluginBaseV2,
gateway_ip = _pop_param(sub_attrs, "gateway_ip", str(cidr[1]))
dns_ips = _pop_param(sub_attrs, "dns_nameservers", [])
routes = _pop_param(sub_attrs, "host_routes", [])
allocation_pools = _pop_param(sub_attrs, "allocation_pools", [])
new_subnet = db_api.subnet_create(context, **sub_attrs)
@@ -270,6 +271,16 @@ class Plugin(neutron_plugin_base_v2.NeutronPluginBaseV2,
new_subnet["dns_nameservers"].append(db_api.dns_create(
context, ip=netaddr.IPAddress(dns_ip)))
if allocation_pools:
exclude = netaddr.IPSet([cidr])
for p in allocation_pools:
x = netaddr.IPSet(netaddr.IPRange(p["start"], p["end"]))
exclude = exclude - x
new_subnet["ip_policy"] = db_api.ip_policy_create(context,
exclude=exclude)
# HACK(amir): force backref for ip_policy
if not new_subnet["network"]:
new_subnet["network"] = net
subnet_dict = v._make_subnet_dict(new_subnet,
default_route=DEFAULT_ROUTE)
subnet_dict["gateway_ip"] = gateway_ip

View File

@@ -19,6 +19,7 @@ View Helpers for Quark Plugin
import netaddr
from quark.ipam import QuarkIpam
from quark import network_strategy
STRATEGY = network_strategy.STRATEGY
@@ -42,12 +43,38 @@ def _make_subnet_dict(subnet, default_route=None, fields=None):
dns_nameservers = [str(netaddr.IPAddress(dns["ip"]))
for dns in subnet.get("dns_nameservers")]
net_id = STRATEGY.get_parent_network(subnet["network_id"])
def _allocation_pools(subnet):
ip_policy_rules = QuarkIpam.get_ip_policy_rule_set(subnet)
cidr = netaddr.IPSet([netaddr.IPNetwork(subnet["cidr"])])
allocatable = cidr - ip_policy_rules
cidrs = allocatable.iter_cidrs()
if len(cidrs) == 0:
return []
if len(cidrs) == 1:
return [dict(start=str(cidrs[0][0]),
end=str(cidrs[0][-1]))]
pool_start = cidrs[0][0]
prev_cidr_end = cidrs[0][-1]
pools = []
for cidr in cidrs[1:]:
cidr_start = cidr[0]
if prev_cidr_end + 1 != cidr_start:
pools.append(dict(start=str(pool_start),
end=str(prev_cidr_end)))
pool_start = cidr_start
prev_cidr_end = cidr[-1]
pools.append(dict(start=str(pool_start), end=str(prev_cidr_end)))
return pools
res = {"id": subnet.get('id'),
"name": subnet.get('name'),
"tenant_id": subnet.get('tenant_id'),
"network_id": net_id,
"ip_version": subnet.get('ip_version'),
"allocation_pools": [],
"allocation_pools": _allocation_pools(subnet),
"dns_nameservers": dns_nameservers or [],
"cidr": subnet.get('cidr'),
"enable_dhcp": None}

View File

@@ -89,11 +89,11 @@ class TestQuarkGetSubnets(TestQuarkPlugin):
for subnet in subnets:
s_dict = subnet.copy()
s_dict["routes"] = route_models
s = models.Subnet()
s = models.Subnet(network=models.Network())
s.update(s_dict)
subnet_models.append(s)
elif subnets:
mod = models.Subnet()
mod = models.Subnet(network=models.Network())
mod.update(subnets)
mod["routes"] = route_models
subnet_models = mod
@@ -111,7 +111,7 @@ class TestQuarkGetSubnets(TestQuarkPlugin):
subnet = dict(id=subnet_id, network_id=1, name=subnet_id,
tenant_id=self.context.tenant_id, ip_version=4,
cidr="192.168.0.0/24", gateway_ip="192.168.0.1",
allocation_pools=[], dns_nameservers=[],
dns_nameservers=[],
enable_dhcp=None)
expected_route = dict(destination=route["cidr"],
nexthop=route["gateway"])
@@ -141,7 +141,7 @@ class TestQuarkGetSubnets(TestQuarkPlugin):
subnet = dict(id=subnet_id, network_id=1, name=subnet_id,
tenant_id=self.context.tenant_id, ip_version=4,
cidr="192.168.0.0/24", gateway_ip="192.168.0.1",
allocation_pools=[], dns_nameservers=[],
dns_nameservers=[],
enable_dhcp=None)
with self._stubs(subnets=subnet, routes=[route]):
@@ -174,6 +174,9 @@ class TestQuarkCreateSubnetOverlapping(TestQuarkPlugin):
) as (net_find, subnet_find, subnet_create):
net_find.return_value = network
subnet_find.return_value = subnet_models
subnet_create.return_value = models.Subnet(
network=models.Network(),
cidr="192.168.1.1/24")
yield subnet_create
def test_create_subnet_overlapping_true(self):
@@ -207,13 +210,63 @@ class TestQuarkCreateSubnetOverlapping(TestQuarkPlugin):
self.plugin.create_subnet(self.context, s)
class TestQuarkCreateSubnetAllocationPools(TestQuarkPlugin):
@contextlib.contextmanager
def _stubs(self, subnet):
s = models.Subnet(network=models.Network(id=1, subnets=[]))
s.update(subnet)
with contextlib.nested(
mock.patch("quark.db.api.network_find"),
mock.patch("quark.db.api.subnet_find"),
mock.patch("quark.db.api.subnet_create")
) as (net_find, subnet_find, subnet_create):
net_find.return_value = s["network"]
subnet_find.return_value = []
subnet_create.return_value = s
yield subnet_create
def test_create_subnet_allocation_pools_zero(self):
s = dict(subnet=dict(
cidr="192.168.1.1/24",
network_id=1))
with self._stubs(s["subnet"]) as subnet_create:
resp = self.plugin.create_subnet(self.context, s)
self.assertEqual(subnet_create.call_count, 1)
self.assertEqual(resp["allocation_pools"],
[dict(start="192.168.1.0", end="192.168.1.255")])
def test_create_subnet_allocation_pools_one(self):
pools = [dict(start="192.168.1.10", end="192.168.1.20")]
s = dict(subnet=dict(
allocation_pools=pools,
cidr="192.168.1.1/24",
network_id=1))
with self._stubs(s["subnet"]) as subnet_create:
resp = self.plugin.create_subnet(self.context, s)
self.assertEqual(subnet_create.call_count, 1)
self.assertEqual(resp["allocation_pools"], pools)
def test_create_subnet_allocation_pools_two(self):
pools = [dict(start="192.168.1.10", end="192.168.1.20"),
dict(start="192.168.1.40", end="192.168.1.50")]
s = dict(subnet=dict(
allocation_pools=pools,
cidr="192.168.1.1/24",
network_id=1))
with self._stubs(s["subnet"]) as subnet_create:
resp = self.plugin.create_subnet(self.context, s)
self.assertEqual(subnet_create.call_count, 1)
self.assertEqual(resp["allocation_pools"], pools)
# TODO(amir): Refactor the tests to test individual subnet attributes.
# * copy.deepcopy was necessary to maintain tests on keys, which is a bit ugly.
# * workaround is also in place for lame ATTR_NOT_SPECIFIED object()
class TestQuarkCreateSubnet(TestQuarkPlugin):
@contextlib.contextmanager
def _stubs(self, subnet=None, network=None, routes=None, dns=None):
subnet_mod = models.Subnet()
subnet_mod = models.Subnet(network=models.Network())
dns_ips = subnet.pop("dns_nameservers", [])
host_routes = subnet.pop("host_routes", [])
subnet_mod.update(subnet)
@@ -242,7 +295,6 @@ class TestQuarkCreateSubnet(TestQuarkPlugin):
subnet=dict(network_id=1,
tenant_id=self.context.tenant_id, ip_version=4,
cidr="172.16.0.0/24", gateway_ip="0.0.0.0",
allocation_pools=[],
dns_nameservers=neutron_attrs.ATTR_NOT_SPECIFIED,
host_routes=neutron_attrs.ATTR_NOT_SPECIFIED,
enable_dhcp=None))
@@ -281,7 +333,6 @@ class TestQuarkCreateSubnet(TestQuarkPlugin):
subnet=dict(network_id=1,
tenant_id=self.context.tenant_id, ip_version=4,
cidr="172.16.0.0/24",
allocation_pools=[],
gateway_ip=neutron_attrs.ATTR_NOT_SPECIFIED,
dns_nameservers=neutron_attrs.ATTR_NOT_SPECIFIED,
enable_dhcp=None))
@@ -316,7 +367,6 @@ class TestQuarkCreateSubnet(TestQuarkPlugin):
subnet=dict(network_id=1,
tenant_id=self.context.tenant_id, ip_version=4,
cidr="172.16.0.0/24", gateway_ip="0.0.0.0",
allocation_pools=[],
dns_nameservers=["4.2.2.1", "4.2.2.2"],
enable_dhcp=None))
network = dict(network_id=1)
@@ -345,7 +395,6 @@ class TestQuarkCreateSubnet(TestQuarkPlugin):
subnet=dict(network_id=1,
tenant_id=self.context.tenant_id, ip_version=4,
cidr="172.16.0.0/24", gateway_ip="0.0.0.0",
allocation_pools=[],
dns_nameservers=neutron_attrs.ATTR_NOT_SPECIFIED,
host_routes=[{"destination": "1.1.1.1/8",
"nexthop": "172.16.0.4"}],
@@ -379,7 +428,6 @@ class TestQuarkCreateSubnet(TestQuarkPlugin):
subnet=dict(network_id=1,
tenant_id=self.context.tenant_id, ip_version=4,
cidr="172.16.0.0/24",
allocation_pools=[],
gateway_ip=neutron_attrs.ATTR_NOT_SPECIFIED,
dns_nameservers=neutron_attrs.ATTR_NOT_SPECIFIED,
host_routes=[{"destination": "0.0.0.0/0",
@@ -420,7 +468,6 @@ class TestQuarkCreateSubnet(TestQuarkPlugin):
tenant_id=self.context.tenant_id, ip_version=4,
cidr="172.16.0.0/24",
gateway_ip="172.16.0.3",
allocation_pools=[],
dns_nameservers=neutron_attrs.ATTR_NOT_SPECIFIED,
host_routes=[{"destination": "0.0.0.0/0",
"nexthop": "172.16.0.4"}],
@@ -473,7 +520,6 @@ class TestQuarkUpdateSubnet(TestQuarkPlugin):
network_id=1,
tenant_id=self.context.tenant_id, ip_version=4,
cidr="172.16.0.0/24",
allocation_pools=[],
host_routes=host_routes,
dns_nameservers=["4.2.2.1", "4.2.2.2"],
enable_dhcp=None)
@@ -503,7 +549,8 @@ class TestQuarkUpdateSubnet(TestQuarkPlugin):
subnet_find.return_value = subnet_mod
route_find.return_value = subnet_mod["routes"][0] \
if subnet_mod["routes"] and find_routes else None
new_subnet_mod = copy.deepcopy(subnet_mod)
new_subnet_mod = models.Subnet(network=models.Network())
new_subnet_mod.update(subnet_mod)
if new_routes:
new_subnet_mod["routes"] = new_routes
if new_dns_servers: