diff --git a/quark/api/extensions/ip_policies.py b/quark/api/extensions/ip_policies.py index 36221b9..5dbd130 100644 --- a/quark/api/extensions/ip_policies.py +++ b/quark/api/extensions/ip_policies.py @@ -42,6 +42,11 @@ class IPPoliciesController(wsgi.Controller): return {RESOURCE_NAME: self._plugin.create_ip_policy(request.context, body)} + def update(self, request, id, body=None): + body = self._deserialize(request.body, request.get_content_type()) + return {RESOURCE_NAME: + self._plugin.update_ip_policy(request.context, id, body)} + def index(self, request): context = request.context return {RESOURCE_COLLECTION: diff --git a/quark/db/api.py b/quark/db/api.py index 1843705..126217c 100644 --- a/quark/db/api.py +++ b/quark/db/api.py @@ -489,13 +489,14 @@ def security_group_rule_delete(context, rule): def ip_policy_create(context, **ip_policy_dict): new_policy = models.IPPolicy() - exclude_set = ip_policy_dict.pop("exclude") - for ip_cidr in exclude_set.iter_cidrs(): - new_policy["exclude"].append(models.IPPolicyRule( - address=int(ip_cidr.ip), - prefix=ip_cidr.prefixlen)) + ranges = ip_policy_dict.pop("exclude") + for arange in ranges: + new_policy["exclude"].append(models.IPPolicyRange( + offset=arange["offset"], + length=arange["length"])) new_policy.update(ip_policy_dict) + new_policy["tenant_id"] = context.tenant_id context.session.add(new_policy) return new_policy @@ -507,5 +508,19 @@ def ip_policy_find(context, **filters): return query.filter(*model_filters) +def ip_policy_update(context, ip_policy, **ip_policy_dict): + ranges = ip_policy_dict.pop("exclude", []) + if ranges: + ip_policy["exclude"] = [] + for arange in ranges: + ip_policy["exclude"].append(models.IPPolicyRange( + offset=arange["offset"], + length=arange["length"])) + + ip_policy.update(ip_policy_dict) + context.session.add(ip_policy) + return ip_policy + + def ip_policy_delete(context, ip_policy): context.session.delete(ip_policy) diff --git a/quark/db/models.py b/quark/db/models.py index 7c40aa0..91637d0 100644 --- a/quark/db/models.py +++ b/quark/db/models.py @@ -27,11 +27,22 @@ from neutron.db import models_v2 as models from neutron.openstack.common import log as logging from neutron.openstack.common import timeutils +from oslo.config import cfg + from quark.db import custom_types +import json + HasId = models.HasId LOG = logging.getLogger("neutron.quark.db.models") +CONF = cfg.CONF + +quark_opts = [ + cfg.StrOpt('default_ip_policy', default='{}', + help=_("Default IP allocation policy")) +] +CONF.register_opts(quark_opts, "QUARK") def _default_list_getset(collection_class, proxy): @@ -225,7 +236,8 @@ class Subnet(BASEV2, models.HasId, models.HasTenant, IsHazTags): primaryjoin="DNSNameserver.subnet_id==Subnet.id", backref='subnet', cascade='delete') - ip_policy = orm.relationship("IPPolicy", uselist=False, backref="subnet") + ip_policy_id = sa.Column(sa.String(36), + sa.ForeignKey("quark_ip_policy.id")) port_ip_association_table = sa.Table( @@ -331,25 +343,68 @@ class MacAddressRange(BASEV2, models.HasId): backref="mac_address_range") -class IPPolicy(BASEV2, models.HasId): +class IPPolicy(BASEV2, models.HasId, models.HasTenant): __tablename__ = "quark_ip_policy" - subnet_id = sa.Column(sa.String(36), sa.ForeignKey("quark_subnets.id", - ondelete="CASCADE")) - network_id = sa.Column(sa.String(36), sa.ForeignKey("quark_networks.id", - ondelete="CASCADE")) + networks = orm.relationship( + "Network", + primaryjoin="IPPolicy.id==Network.ip_policy_id", + backref="ip_policy") + subnets = orm.relationship( + "Subnet", + primaryjoin="IPPolicy.id==Subnet.ip_policy_id", + backref="ip_policy") + exclude = orm.relationship( + "IPPolicyRange", + primaryjoin="IPPolicy.id==IPPolicyRange.ip_policy_id", + backref="ip_policy") + name = sa.Column(sa.String(255), nullable=True) - join = "IPPolicy.id==IPPolicyRule.ip_policy_id" - exclude = orm.relationship("IPPolicyRule", - primaryjoin=join, - backref="ip_policy") + class JSONIPPolicy(object): + def __init__(self, policy=None): + self.policy = {} + if not policy: + self._compile_policy(CONF.QUARK.default_ip_policy) + else: + self._compile_policy(policy) + + def _compile_policy(self, policy): + self.policy = json.loads(policy) + + def __getattr__(self, name): + return getattr(self.policy, name) + + DEFAULT_POLICY = JSONIPPolicy() + + @staticmethod + def get_ip_policy_rule_set(subnet): + ip_policy = subnet["ip_policy"] or \ + subnet["network"]["ip_policy"] or \ + dict() + ip_policy_ranges = ip_policy.get("exclude", []) + \ + IPPolicy.DEFAULT_POLICY.get("exclude", []) + + ip_policy_rules = netaddr.IPSet() + subnet_net = netaddr.IPNetwork(subnet["cidr"]) + for arange in ip_policy_ranges: + end_index = arange["offset"] + arange["length"] + end_index_wraps_around = (arange["offset"] < 0 and end_index >= 0) + if end_index_wraps_around: + second_index = min(end_index, len(subnet_net)) + end_index = None + ip_policy_rules |= netaddr.IPSet(subnet_net[:second_index]) + ip_policy_rules |= netaddr.IPSet( + subnet_net[arange["offset"]:end_index]) + + return ip_policy_rules -class IPPolicyRule(BASEV2, models.HasId): +class IPPolicyRange(BASEV2, models.HasId): __tablename__ = "quark_ip_policy_rules" ip_policy_id = sa.Column(sa.String(36), sa.ForeignKey( "quark_ip_policy.id", ondelete="CASCADE")) - address = sa.Column(custom_types.INET()) - prefix = sa.Column(sa.Integer()) + + offset = sa.Column(sa.Integer()) + length = sa.Column(sa.Integer()) class Network(BASEV2, models.HasTenant, models.HasId): @@ -357,4 +412,5 @@ class Network(BASEV2, models.HasTenant, models.HasId): name = sa.Column(sa.String(255)) ports = orm.relationship(Port, backref='network') subnets = orm.relationship(Subnet, backref='network') - ip_policy = orm.relationship(IPPolicy, uselist=False, backref="network") + ip_policy_id = sa.Column(sa.String(36), + sa.ForeignKey("quark_ip_policy.id")) diff --git a/quark/exceptions.py b/quark/exceptions.py index 638f42f..8dca374 100644 --- a/quark/exceptions.py +++ b/quark/exceptions.py @@ -65,5 +65,9 @@ class IPPolicyAlreadyExists(exceptions.NeutronException): message = _("IP Policy %(id)s already exists for %(n_id)s") +class IPPolicyInUse(exceptions.InUse): + message = _("IP allocation policy %(id) in use.") + + class DriverLimitReached(exceptions.InvalidInput): message = _("Driver has reached limit on resource '%(limit)s'") diff --git a/quark/ipam.py b/quark/ipam.py index 0d3c9e5..21f482d 100644 --- a/quark/ipam.py +++ b/quark/ipam.py @@ -24,25 +24,13 @@ from neutron.openstack.common import log as logging from neutron.openstack.common import timeutils from quark.db import api as db_api +from quark.db import models LOG = logging.getLogger("neutron") class QuarkIpam(object): - @staticmethod - def get_ip_policy_rule_set(subnet): - ip_policy = subnet["ip_policy"] or \ - subnet["network"]["ip_policy"] or \ - dict() - ip_policy_rules = ip_policy.get("exclude", []) - 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"])]) - ip_policy_rules = subnet_set & ip_policy_rules - return ip_policy_rules - def _choose_available_subnet(self, context, net_id, version=None, ip_address=None): filters = {} @@ -58,7 +46,8 @@ class QuarkIpam(object): ip_policy_rules = None if not ip_address: - ip_policy_rules = self.get_ip_policy_rule_set(subnet) + ip_policy_rules = models.IPPolicy.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 @@ -118,7 +107,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 = models.IPPolicy.get_ip_policy_rule_set(subnet) # Creating this IP for the first time next_ip = None diff --git a/quark/plugin.py b/quark/plugin.py index 60b2b19..7984604 100644 --- a/quark/plugin.py +++ b/quark/plugin.py @@ -149,6 +149,9 @@ class Plugin(neutron_plugin_base_v2.NeutronPluginBaseV2, def get_ip_policies(self, context, **filters): return ip_policies.get_ip_policies(context, **filters) + def update_ip_policy(self, context, id, ip_policy): + return ip_policies.update_ip_policy(context, id, ip_policy) + def delete_ip_policy(self, context, id): return ip_policies.delete_ip_policy(context, id) diff --git a/quark/plugin_modules/ip_policies.py b/quark/plugin_modules/ip_policies.py index 29c6a8e..4a644fd 100644 --- a/quark/plugin_modules/ip_policies.py +++ b/quark/plugin_modules/ip_policies.py @@ -13,8 +13,6 @@ # License for the specific language governing permissions and limitations # under the License. -import netaddr - from neutron.common import exceptions from neutron.openstack.common import log as logging from oslo.config import cfg @@ -23,10 +21,8 @@ from quark.db import api as db_api from quark import exceptions as quark_exceptions from quark import plugin_views as v - CONF = cfg.CONF LOG = logging.getLogger("neutron.quark") -DEFAULT_SG_UUID = "00000000-0000-0000-0000-000000000000" def create_ip_policy(context, ip_policy): @@ -38,29 +34,35 @@ def create_ip_policy(context, ip_policy): raise exceptions.BadRequest(resource="ip_policy", msg="Empty ip_policy.exclude regions") - ipp["exclude"] = netaddr.IPSet(ipp["exclude"]) - network_id = ipp.get("network_id") - subnet_id = ipp.get("subnet_id") + network_ids = ipp.get("network_ids") + subnet_ids = ipp.get("subnet_ids") - model = None - if subnet_id: - model = db_api.subnet_find(context, id=subnet_id, scope=db_api.ONE) - if not model: - raise exceptions.SubnetNotFound(id=subnet_id) - elif network_id: - model = db_api.network_find(context, id=network_id, - scope=db_api.ONE) - if not model: - raise exceptions.NetworkNotFound(id=network_id) - else: + if not subnet_ids and not network_ids: raise exceptions.BadRequest( resource="ip_policy", - msg="network_id or subnet_id unspecified") + msg="network_ids or subnet_ids not specified") + + models = [] + if subnet_ids: + subnets = db_api.subnet_find( + context, id=subnet_ids, scope=db_api.ALL) + if not subnets: + raise exceptions.SubnetNotFound(id=subnet_ids) + models.extend(subnets) + + if network_ids: + nets = db_api.network_find( + context, id=network_ids, scope=db_api.ALL) + if not nets: + raise exceptions.NetworkNotFound(net_id=network_ids) + models.extend(nets) + + for model in models: + if model["ip_policy"]: + raise quark_exceptions.IPPolicyAlreadyExists( + id=model["ip_policy"]["id"], n_id=model["id"]) + model["ip_policy"] = db_api.ip_policy_create(context, **ipp) - if model["ip_policy"]: - raise quark_exceptions.IPPolicyAlreadyExists( - id=model["ip_policy"]["id"], n_id=model["id"]) - model["ip_policy"] = db_api.ip_policy_create(context, **ipp) return v._make_ip_policy_dict(model["ip_policy"]) @@ -78,9 +80,51 @@ def get_ip_policies(context, **filters): return [v._make_ip_policy_dict(ipp) for ipp in ipps] +def update_ip_policy(context, id, ip_policy): + LOG.info("update_ip_policy for tenant %s" % context.tenant_id) + + ipp = ip_policy["ip_policy"] + + ipp_db = db_api.ip_policy_find(context, id=id, scope=db_api.ONE) + if not ipp_db: + raise quark_exceptions.IPPolicyNotFound(id=id) + + network_ids = ipp.get("network_ids") + subnet_ids = ipp.get("subnet_ids") + + models = [] + if subnet_ids: + for subnet in ipp_db["subnets"]: + subnet["ip_policy"] = None + subnets = db_api.subnet_find( + context, id=subnet_ids, scope=db_api.ALL) + if len(subnets) != len(subnet_ids): + raise exceptions.SubnetNotFound(id=subnet_ids) + models.extend(subnets) + + if network_ids: + for network in ipp_db["networks"]: + network["ip_policy"] = None + nets = db_api.network_find(context, id=network_ids, scope=db_api.ALL) + if len(nets) != len(network_ids): + raise exceptions.NetworkNotFound(net_id=network_ids) + models.extend(nets) + + for model in models: + if model["ip_policy"]: + raise quark_exceptions.IPPolicyAlreadyExists( + id=model["ip_policy"]["id"], n_id=model["id"]) + model["ip_policy"] = ipp_db + + ipp_db = db_api.ip_policy_update(context, ipp_db, **ipp) + return v._make_ip_policy_dict(ipp_db) + + def delete_ip_policy(context, id): LOG.info("delete_ip_policy %s for tenant %s" % (id, context.tenant_id)) ipp = db_api.ip_policy_find(context, id=id, scope=db_api.ONE) if not ipp: raise quark_exceptions.IPPolicyNotFound(id=id) + if ipp["networks"] or ipp["subnets"]: + raise quark_exceptions.IPPolicyInUse(id=id) db_api.ip_policy_delete(context, ipp) diff --git a/quark/plugin_modules/subnets.py b/quark/plugin_modules/subnets.py index ada9f58..e3d2417 100644 --- a/quark/plugin_modules/subnets.py +++ b/quark/plugin_modules/subnets.py @@ -94,7 +94,7 @@ def create_subnet(context, subnet): gateway_ip = utils.pop_param(sub_attrs, "gateway_ip", str(cidr[1])) dns_ips = utils.pop_param(sub_attrs, "dns_nameservers", []) host_routes = utils.pop_param(sub_attrs, "host_routes", []) - allocation_pools = utils.pop_param(sub_attrs, "allocation_pools", []) + allocation_pools = utils.pop_param(sub_attrs, "allocation_pools", None) sub_attrs["network"] = net new_subnet = db_api.subnet_create(context, **sub_attrs) @@ -116,13 +116,20 @@ def create_subnet(context, subnet): new_subnet["dns_nameservers"].append(db_api.dns_create( context, ip=netaddr.IPAddress(dns_ip))) - if allocation_pools: - exclude = netaddr.IPSet([cidr]) + if isinstance(allocation_pools, list): + ranges = [] + cidrset = netaddr.IPSet([netaddr.IPNetwork(new_subnet["cidr"])]) for p in allocation_pools: - x = netaddr.IPSet(netaddr.IPRange(p["start"], p["end"])) - exclude = exclude - x + cidrset -= netaddr.IPSet(netaddr.IPRange(p["start"], p["end"])) + non_allocation_pools = v._pools_from_cidr(cidrset) + for p in non_allocation_pools: + r = netaddr.IPRange(p["start"], p["end"]) + ranges.append(dict( + length=len(r), + offset=int(r[0]) - int(cidr[0]))) new_subnet["ip_policy"] = db_api.ip_policy_create(context, - exclude=exclude) + exclude=ranges) + subnet_dict = v._make_subnet_dict(new_subnet, default_route=routes.DEFAULT_ROUTE) subnet_dict["gateway_ip"] = gateway_ip diff --git a/quark/plugin_views.py b/quark/plugin_views.py index f266013..1a81f7b 100644 --- a/quark/plugin_views.py +++ b/quark/plugin_views.py @@ -22,7 +22,7 @@ import netaddr from neutron.extensions import securitygroup as sg_ext from quark.db import api as db_api -from quark.ipam import QuarkIpam +from quark.db import models from quark import network_strategy from quark import utils @@ -44,35 +44,38 @@ def _make_network_dict(network, fields=None): return res +def _pools_from_cidr(cidr): + cidrs = cidr.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 + + 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) + ip_policy_rules = models.IPPolicy.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 + return _pools_from_cidr(allocatable) res = {"id": subnet.get("id"), "name": subnet.get("name"), @@ -200,11 +203,13 @@ def _make_ip_dict(address): def _make_ip_policy_dict(ipp): - excludes = [str(netaddr.IPNetwork((int(ippr["address"]), ippr["prefix"]))) - for ippr in ipp["exclude"]] + excludes = [dict(offset=range["offset"], length=range["length"]) + for range in ipp["exclude"]] return {"id": ipp["id"], - "subnet_id": ipp["subnet_id"], - "network_id": ipp["network_id"], + "tenant_id": ipp["tenant_id"], + "name": ipp["name"], + "subnet_ids": [s["id"] for s in ipp["subnets"]], + "network_ids": [n["id"] for n in ipp["networks"]], "exclude": excludes} diff --git a/quark/tests/plugin_modules/test_ip_policies.py b/quark/tests/plugin_modules/test_ip_policies.py index ada1728..9f6a349 100644 --- a/quark/tests/plugin_modules/test_ip_policies.py +++ b/quark/tests/plugin_modules/test_ip_policies.py @@ -37,36 +37,42 @@ class TestQuarkGetIpPolicies(test_quark_plugin.TestQuarkPlugin): self.plugin.get_ip_policy(self.context, 1) def test_get_ip_policy(self): - address = int(netaddr.IPAddress("1.1.1.1")) ip_policy = dict( id=1, - subnet_id=1, - network_id=2, - exclude=[dict(address=address, prefix=24)]) + tenant_id=1, + name="foo", + subnets=[dict(id=1)], + networks=[dict(id=2)], + exclude=[dict(offset=1, length=256)]) with self._stubs(ip_policy): resp = self.plugin.get_ip_policy(self.context, 1) - self.assertEqual(len(resp.keys()), 4) + self.assertEqual(len(resp.keys()), 6) self.assertEqual(resp["id"], 1) - self.assertEqual(resp["subnet_id"], 1) - self.assertEqual(resp["network_id"], 2) - self.assertEqual(resp["exclude"], ["1.1.1.1/24"]) + self.assertEqual(resp["name"], "foo") + self.assertEqual(resp["subnet_ids"], [1]) + self.assertEqual(resp["network_ids"], [2]) + self.assertEqual(resp["exclude"], ip_policy["exclude"]) + self.assertEqual(resp["tenant_id"], 1) def test_get_ip_policies(self): - address = int(netaddr.IPAddress("1.1.1.1")) ip_policy = dict( id=1, - subnet_id=1, - network_id=2, - exclude=[dict(address=address, prefix=24)]) + tenant_id=1, + name="foo", + subnets=[dict(id=1)], + networks=[dict(id=2)], + exclude=[dict(offset=1, length=256)]) with self._stubs([ip_policy]): resp = self.plugin.get_ip_policies(self.context) self.assertEqual(len(resp), 1) resp = resp[0] - self.assertEqual(len(resp.keys()), 4) + self.assertEqual(len(resp.keys()), 6) self.assertEqual(resp["id"], 1) - self.assertEqual(resp["subnet_id"], 1) - self.assertEqual(resp["network_id"], 2) - self.assertEqual(resp["exclude"], ["1.1.1.1/24"]) + self.assertEqual(resp["subnet_ids"], [1]) + self.assertEqual(resp["network_ids"], [2]) + self.assertEqual(resp["exclude"], ip_policy["exclude"]) + self.assertEqual(resp["name"], "foo") + self.assertEqual(resp["tenant_id"], 1) class TestQuarkCreateIpPolicies(test_quark_plugin.TestQuarkPlugin): @@ -78,8 +84,8 @@ class TestQuarkCreateIpPolicies(test_quark_plugin.TestQuarkPlugin): mock.patch("%s.network_find" % db_mod), mock.patch("%s.ip_policy_create" % db_mod), ) as (subnet_find, net_find, ip_policy_create): - subnet_find.return_value = subnet - net_find.return_value = net + subnet_find.return_value = [subnet] if subnet else None + net_find.return_value = [net] if net else None ip_policy_create.return_value = ip_policy yield ip_policy_create @@ -99,28 +105,28 @@ class TestQuarkCreateIpPolicies(test_quark_plugin.TestQuarkPlugin): with self._stubs(None): with self.assertRaises(exceptions.SubnetNotFound): self.plugin.create_ip_policy(self.context, dict( - ip_policy=dict(subnet_id=1, + ip_policy=dict(subnet_ids=[1], exclude=["1.1.1.1/24"]))) def test_create_ip_policy_invalid_network(self): with self._stubs(None): with self.assertRaises(exceptions.NetworkNotFound): self.plugin.create_ip_policy(self.context, dict( - ip_policy=dict(network_id=1, + ip_policy=dict(network_ids=[1], exclude=["1.1.1.1/24"]))) def test_create_ip_policy_network_ip_policy_already_exists(self): with self._stubs(None, net=dict(id=1, ip_policy=dict(id=2))): with self.assertRaises(quark_exceptions.IPPolicyAlreadyExists): self.plugin.create_ip_policy(self.context, dict( - ip_policy=dict(network_id=1, + ip_policy=dict(network_ids=[1], exclude=["1.1.1.1/24"]))) def test_create_ip_policy_subnet_ip_policy_already_exists(self): with self._stubs(None, subnet=dict(id=1, ip_policy=dict(id=2))): with self.assertRaises(quark_exceptions.IPPolicyAlreadyExists): self.plugin.create_ip_policy(self.context, dict( - ip_policy=dict(subnet_id=1, + ip_policy=dict(subnet_ids=[1], exclude=["1.1.1.1/24"]))) def test_create_ip_policy_network(self): @@ -130,12 +136,12 @@ class TestQuarkCreateIpPolicies(test_quark_plugin.TestQuarkPlugin): with self._stubs(ipp, net=dict(id=1, ip_policy=dict(id=2))): with self.assertRaises(quark_exceptions.IPPolicyAlreadyExists): resp = self.plugin.create_ip_policy(self.context, dict( - ip_policy=dict(network_id=1, + ip_policy=dict(network_ids=[1], exclude=["1.1.1.1/24"]))) self.assertEqual(len(resp.keys()), 3) - self.assertIsNone(resp["subnet_id"]) - self.assertEqual(resp["network_id"], 1) - self.assertEqual(resp["exclude"], ["1.1.1.1/24"]) + self.assertIsNone(resp["subnet_ids"]) + self.assertEqual(resp["network_ids"], 1) + self.assertEqual(resp["exclude"], [dict()]) def test_create_ip_policy_subnet(self): ipp = dict(subnet_id=1, network_id=None, @@ -144,7 +150,7 @@ class TestQuarkCreateIpPolicies(test_quark_plugin.TestQuarkPlugin): with self._stubs(ipp, subnet=dict(id=1, ip_policy=dict(id=2))): with self.assertRaises(quark_exceptions.IPPolicyAlreadyExists): resp = self.plugin.create_ip_policy(self.context, dict( - ip_policy=dict(subnet_id=1, + ip_policy=dict(subnet_ids=[1], exclude=["1.1.1.1/24"]))) self.assertEqual(len(resp.keys()), 3) self.assertEqual(resp["subnet_id"], 1) @@ -152,17 +158,109 @@ class TestQuarkCreateIpPolicies(test_quark_plugin.TestQuarkPlugin): self.assertEqual(resp["exclude"], ["1.1.1.1/24"]) def test_create_ip_policy(self): - ipp = dict(subnet_id=1, network_id=None, id=1, - exclude=[dict(address=int(netaddr.IPAddress("1.1.1.1")), - prefix=24)]) + ipp = dict( + subnets=[dict(id=1)], + networks=[], + id=1, + tenant_id=1, + exclude=[dict(offset=0, length=256)], + name="foo") with self._stubs(ipp, subnet=dict(id=1, ip_policy=None)): resp = self.plugin.create_ip_policy(self.context, dict( - ip_policy=dict(subnet_id=1, - exclude=["1.1.1.1/24"]))) - self.assertEqual(len(resp.keys()), 4) - self.assertEqual(resp["subnet_id"], 1) - self.assertIsNone(resp["network_id"]) - self.assertEqual(resp["exclude"], ["1.1.1.1/24"]) + ip_policy=dict(subnet_ids=[1], + exclude=[dict(offset=0, length=256)]))) + self.assertEqual(len(resp.keys()), 6) + self.assertEqual(resp["subnet_ids"], [1]) + self.assertEqual(resp["network_ids"], []) + self.assertEqual(resp["exclude"], + [dict(offset=0, length=256)]) + self.assertEqual(resp["name"], "foo") + self.assertEqual(resp["tenant_id"], 1) + + +class TestQuarkUpdateIpPolicies(test_quark_plugin.TestQuarkPlugin): + @contextlib.contextmanager + def _stubs(self, ip_policy, subnets=None, networks=None): + if not subnets: + subnets = [] + if not networks: + networks = [] + db_mod = "quark.db.api" + with contextlib.nested( + mock.patch("%s.ip_policy_find" % db_mod), + mock.patch("%s.subnet_find" % db_mod), + mock.patch("%s.network_find" % db_mod), + mock.patch("%s.ip_policy_update" % db_mod), + ) as (ip_policy_find, subnet_find, network_find, ip_policy_update): + ip_policy_find.return_value = ip_policy + subnet_find.return_value = subnets + network_find.return_value = networks + yield ip_policy_update + + def test_update_ip_policy_not_found(self): + with self._stubs(None) as (ip_policy_update): + with self.assertRaises(quark_exceptions.IPPolicyNotFound): + self.plugin.update_ip_policy(self.context, 1, + dict(ip_policy=None)) + self.assertEqual(ip_policy_update.called, 0) + + def test_update_ip_policy_subnets_not_found(self): + ipp = dict(id=1, subnets=[]) + with self._stubs(ipp) as (ip_policy_update): + with self.assertRaises(exceptions.SubnetNotFound): + self.plugin.update_ip_policy( + self.context, + 1, + dict(ip_policy=dict(subnet_ids=[100]))) + self.assertEqual(ip_policy_update.called, 0) + + def test_update_ip_policy_subnets_already_exists(self): + ipp = dict(id=1, subnets=[dict()]) + with self._stubs( + ipp, subnets=[dict(id=1, ip_policy=dict(id=1))] + ) as (ip_policy_update): + with self.assertRaises(quark_exceptions.IPPolicyAlreadyExists): + self.plugin.update_ip_policy( + self.context, + 1, + dict(ip_policy=dict(subnet_ids=[100]))) + self.assertEqual(ip_policy_update.called, 0) + + def test_update_ip_policy_subnets(self): + ipp = dict(id=1, subnets=[dict()], + exclude=[dict(offset=0, length=256)], + name="foo", tenant_id=1) + with self._stubs( + ipp, subnets=[dict(id=1, ip_policy=None)] + ) as (ip_policy_update): + self.plugin.update_ip_policy( + self.context, + 1, + dict(ip_policy=dict(subnet_ids=[100]))) + self.assertEqual(ip_policy_update.called, 1) + + def test_update_ip_policy_networks_not_found(self): + ipp = dict(id=1, networks=[]) + with self._stubs(ipp) as (ip_policy_update): + with self.assertRaises(exceptions.NetworkNotFound): + self.plugin.update_ip_policy( + self.context, + 1, + dict(ip_policy=dict(network_ids=[100]))) + self.assertEqual(ip_policy_update.called, 0) + + def test_update_ip_policy_networks(self): + ipp = dict(id=1, networks=[dict()], + exclude=[dict(offset=0, length=256)], + name="foo", tenant_id=1) + with self._stubs( + ipp, networks=[dict(id=1, ip_policy=None)] + ) as (ip_policy_update): + self.plugin.update_ip_policy( + self.context, + 1, + dict(ip_policy=dict(network_ids=[100]))) + self.assertEqual(ip_policy_update.called, 1) class TestQuarkDeleteIpPolicies(test_quark_plugin.TestQuarkPlugin): @@ -181,13 +279,16 @@ class TestQuarkDeleteIpPolicies(test_quark_plugin.TestQuarkPlugin): with self.assertRaises(quark_exceptions.IPPolicyNotFound): self.plugin.delete_ip_policy(self.context, 1) + def test_delete_ip_policy_in_use(self): + with self._stubs(dict(networks=True)): + with self.assertRaises(quark_exceptions.IPPolicyInUse): + self.plugin.delete_ip_policy(self.context, 1) + def test_delete_ip_policy(self): - address = int(netaddr.IPAddress("1.1.1.1")) ip_policy = dict( id=1, - subnet_id=1, - network_id=2, - exclude=[dict(address=address, prefix=24)]) + networks=[], + subnets=[]) with self._stubs(ip_policy) as (ip_policy_find, ip_policy_delete): self.plugin.delete_ip_policy(self.context, 1) self.assertEqual(ip_policy_find.call_count, 1) diff --git a/quark/tests/plugin_modules/test_subnets.py b/quark/tests/plugin_modules/test_subnets.py index c2b2a39..8f2adb8 100644 --- a/quark/tests/plugin_modules/test_subnets.py +++ b/quark/tests/plugin_modules/test_subnets.py @@ -179,22 +179,31 @@ class TestQuarkCreateSubnetAllocationPools(test_quark_plugin.TestQuarkPlugin): with contextlib.nested( mock.patch("quark.db.api.network_find"), mock.patch("quark.db.api.subnet_find"), - mock.patch("quark.db.api.subnet_create") + 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 setUp(self): + super(TestQuarkCreateSubnetAllocationPools, self).setUp() + models.IPPolicy.DEFAULT_POLICY = models.IPPolicy( + exclude=[models.IPPolicyRange(offset=-1, length=3)]) + + def tearDown(self): + super(TestQuarkCreateSubnetAllocationPools, self).tearDown() + models.IPPolicy.DEFAULT_POLICY = {} + 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: + 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")]) + [dict(start="192.168.1.2", end="192.168.1.254")]) def test_create_subnet_allocation_pools_one(self): pools = [dict(start="192.168.1.10", end="192.168.1.20")] @@ -202,7 +211,7 @@ class TestQuarkCreateSubnetAllocationPools(test_quark_plugin.TestQuarkPlugin): allocation_pools=pools, cidr="192.168.1.1/24", network_id=1)) - with self._stubs(s["subnet"]) as subnet_create: + 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) @@ -214,7 +223,18 @@ class TestQuarkCreateSubnetAllocationPools(test_quark_plugin.TestQuarkPlugin): allocation_pools=pools, cidr="192.168.1.1/24", network_id=1)) - with self._stubs(s["subnet"]) as subnet_create: + 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_empty_list(self): + pools = [] + 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) diff --git a/quark/tests/test_ipam.py b/quark/tests/test_ipam.py index 6a6fbfe..bd0e5cb 100644 --- a/quark/tests/test_ipam.py +++ b/quark/tests/test_ipam.py @@ -391,7 +391,7 @@ class TestQuarkIpPoliciesIpAllocation(QuarkIpamBaseTest): cidr="0.0.0.0/24", ip_version=4, next_auto_assign_ip=0, network=dict(ip_policy=None), ip_policy=dict(exclude= - [dict(address=0, prefix=24)])) + [dict(offset=0, length=256)])) with self._stubs(subnets=[(subnet, 0)], addresses=[None, None]): with self.assertRaises(exceptions.IpAddressGenerationFailure): self.ipam.allocate_ip_address(self.context, 0, 0, 0, version=4) @@ -401,8 +401,18 @@ class TestQuarkIpPoliciesIpAllocation(QuarkIpamBaseTest): cidr="0.0.0.0/24", ip_version=4, next_auto_assign_ip=0, network=dict(ip_policy=None), ip_policy=dict(exclude= - [dict(address=0, prefix=32), - dict(address=1, prefix=32)])) + [dict(offset=0, length=2)])) + with self._stubs(subnets=[(subnet, 0)], addresses=[None, None]): + address = self.ipam.allocate_ip_address(self.context, 0, 0, 0, + version=4) + self.assertEqual(address["address"], 2) + + def test_ip_policy_on_subnet_negative_offset(self): + subnet = dict(id=1, first_ip=0, last_ip=255, + cidr="0.0.0.0/24", ip_version=4, + next_auto_assign_ip=0, network=dict(ip_policy=None), + ip_policy=dict(exclude= + [dict(offset=-1, length=3)])) with self._stubs(subnets=[(subnet, 0)], addresses=[None, None]): address = self.ipam.allocate_ip_address(self.context, 0, 0, 0, version=4) @@ -410,8 +420,7 @@ class TestQuarkIpPoliciesIpAllocation(QuarkIpamBaseTest): def test_ip_policy_on_network(self): net = dict(ip_policy=dict(exclude= - [dict(address=0, prefix=32), - dict(address=1, prefix=32)])) + [dict(offset=0, length=2)])) subnet = dict(id=1, first_ip=0, last_ip=255, cidr="0.0.0.0/24", ip_version=4, next_auto_assign_ip=0, network=net, @@ -423,9 +432,8 @@ class TestQuarkIpPoliciesIpAllocation(QuarkIpamBaseTest): def test_ip_policy_on_network_exclusion_intersection(self): net = dict(ip_policy=dict(exclude= - [dict(address=0, prefix=32), - dict(address=1, prefix=32), - dict(address=254, prefix=32)])) + [dict(offset=0, length=2), + dict(offset=254, length=1)])) subnet = dict(id=1, first_ip=0, last_ip=63, cidr="0.0.0.0/30", ip_version=4, next_auto_assign_ip=0, network=net, @@ -437,14 +445,14 @@ class TestQuarkIpPoliciesIpAllocation(QuarkIpamBaseTest): def test_ip_policy_on_both_subnet_preferred(self): net = dict(ip_policy=dict(exclude= - [dict(address=0, prefix=32), - dict(address=1, prefix=32)])) + [dict(offset=0, length=1), + dict(offset=1, length=1)])) subnet = dict(id=1, first_ip=0, last_ip=255, cidr="0.0.0.0/24", ip_version=4, next_auto_assign_ip=0, network=net, ip_policy=dict(exclude= - [dict(address=254, prefix=32), - dict(address=255, prefix=32)])) + [dict(offset=254, length=1), + dict(offset=255, length=1)])) with self._stubs(subnets=[(subnet, 0)], addresses=[None, None]): address = self.ipam.allocate_ip_address(self.context, 0, 0, 0, version=4) @@ -454,7 +462,7 @@ class TestQuarkIpPoliciesIpAllocation(QuarkIpamBaseTest): subnet1 = dict(id=1, first_ip=0, last_ip=255, cidr="0.0.0.0/24", ip_version=4, network=dict(ip_policy=None), - ip_policy=dict(exclude=[dict(address=240, prefix=32)])) + ip_policy=dict(exclude=[dict(offset=240, length=1)])) subnets = [(subnet1, 1)] with self._stubs(subnets=subnets, addresses=[None, None]): address = self.ipam.allocate_ip_address(