Merge pull request #121 from asadoughi/allocation_pools
Allocation pools
This commit is contained in:
@@ -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},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user