Enforce subnets per ip version per network quota
RM#9096
This commit is contained in:
		@@ -39,6 +39,10 @@ CONF = cfg.CONF
 | 
				
			|||||||
quark_resources = [
 | 
					quark_resources = [
 | 
				
			||||||
    quota.BaseResource('ports_per_network',
 | 
					    quota.BaseResource('ports_per_network',
 | 
				
			||||||
                       'quota_ports_per_network'),
 | 
					                       'quota_ports_per_network'),
 | 
				
			||||||
 | 
					    quota.BaseResource('v6_subnets_per_network',
 | 
				
			||||||
 | 
					                       'quota_v6_subnets_per_network'),
 | 
				
			||||||
 | 
					    quota.BaseResource('v4_subnets_per_network',
 | 
				
			||||||
 | 
					                       'quota_v4_subnets_per_network'),
 | 
				
			||||||
    quota.BaseResource('routes_per_subnet',
 | 
					    quota.BaseResource('routes_per_subnet',
 | 
				
			||||||
                       'quota_routes_per_subnet'),
 | 
					                       'quota_routes_per_subnet'),
 | 
				
			||||||
    quota.BaseResource('security_rules_per_group',
 | 
					    quota.BaseResource('security_rules_per_group',
 | 
				
			||||||
@@ -51,6 +55,12 @@ quark_quota_opts = [
 | 
				
			|||||||
    cfg.IntOpt('quota_ports_per_network',
 | 
					    cfg.IntOpt('quota_ports_per_network',
 | 
				
			||||||
               default=250,
 | 
					               default=250,
 | 
				
			||||||
               help=_('Maximum ports per network')),
 | 
					               help=_('Maximum ports per network')),
 | 
				
			||||||
 | 
					    cfg.IntOpt('quota_v6_subnets_per_network',
 | 
				
			||||||
 | 
					               default=1,
 | 
				
			||||||
 | 
					               help=_('Maximum v6 subnets per network')),
 | 
				
			||||||
 | 
					    cfg.IntOpt('quota_v4_subnets_per_network',
 | 
				
			||||||
 | 
					               default=1,
 | 
				
			||||||
 | 
					               help=_('Maximum v4 subnets per network')),
 | 
				
			||||||
    cfg.IntOpt('quota_routes_per_subnet',
 | 
					    cfg.IntOpt('quota_routes_per_subnet',
 | 
				
			||||||
               default=3,
 | 
					               default=3,
 | 
				
			||||||
               help=_('Maximum routes per subnet')),
 | 
					               help=_('Maximum routes per subnet')),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,11 +13,13 @@
 | 
				
			|||||||
#    License for the specific language governing permissions and limitations
 | 
					#    License for the specific language governing permissions and limitations
 | 
				
			||||||
#    under the License.
 | 
					#    under the License.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import netaddr
 | 
				
			||||||
from neutron.common import exceptions
 | 
					from neutron.common import exceptions
 | 
				
			||||||
from neutron.extensions import providernet as pnet
 | 
					from neutron.extensions import providernet as pnet
 | 
				
			||||||
from neutron.openstack.common import importutils
 | 
					from neutron.openstack.common import importutils
 | 
				
			||||||
from neutron.openstack.common import log as logging
 | 
					from neutron.openstack.common import log as logging
 | 
				
			||||||
from neutron.openstack.common import uuidutils
 | 
					from neutron.openstack.common import uuidutils
 | 
				
			||||||
 | 
					from neutron import quota
 | 
				
			||||||
from oslo.config import cfg
 | 
					from oslo.config import cfg
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from quark.db import api as db_api
 | 
					from quark.db import api as db_api
 | 
				
			||||||
@@ -60,8 +62,25 @@ def create_network(context, network):
 | 
				
			|||||||
    LOG.info("create_network for tenant %s" % context.tenant_id)
 | 
					    LOG.info("create_network for tenant %s" % context.tenant_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with context.session.begin():
 | 
					    with context.session.begin():
 | 
				
			||||||
        # Generate a uuid that we're going to hand to the backend and db
 | 
					 | 
				
			||||||
        net_attrs = network["network"]
 | 
					        net_attrs = network["network"]
 | 
				
			||||||
 | 
					        subs = net_attrs.pop("subnets", [])
 | 
				
			||||||
 | 
					        # Enforce subnet quotas
 | 
				
			||||||
 | 
					        if len(subs) > 0:
 | 
				
			||||||
 | 
					            v4_count, v6_count = 0, 0
 | 
				
			||||||
 | 
					            for s in subs:
 | 
				
			||||||
 | 
					                version = netaddr.IPNetwork(s['subnet']['cidr']).version
 | 
				
			||||||
 | 
					                if version == 6:
 | 
				
			||||||
 | 
					                    v6_count += 1
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    v4_count += 1
 | 
				
			||||||
 | 
					            if v4_count > 0:
 | 
				
			||||||
 | 
					                quota.QUOTAS.limit_check(context, context.tenant_id,
 | 
				
			||||||
 | 
					                                         v4_subnets_per_network=v4_count)
 | 
				
			||||||
 | 
					            if v6_count > 0:
 | 
				
			||||||
 | 
					                quota.QUOTAS.limit_check(context, context.tenant_id,
 | 
				
			||||||
 | 
					                                         v6_subnets_per_network=v6_count)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Generate a uuid that we're going to hand to the backend and db
 | 
				
			||||||
        net_uuid = utils.pop_param(net_attrs, "id", None)
 | 
					        net_uuid = utils.pop_param(net_attrs, "id", None)
 | 
				
			||||||
        net_type = None
 | 
					        net_type = None
 | 
				
			||||||
        if net_uuid and context.is_admin:
 | 
					        if net_uuid and context.is_admin:
 | 
				
			||||||
@@ -96,8 +115,6 @@ def create_network(context, network):
 | 
				
			|||||||
                                  network_id=net_uuid, phys_type=pnet_type,
 | 
					                                  network_id=net_uuid, phys_type=pnet_type,
 | 
				
			||||||
                                  phys_net=phys_net, segment_id=seg_id)
 | 
					                                  phys_net=phys_net, segment_id=seg_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        subs = net_attrs.pop("subnets", [])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        net_attrs["id"] = net_uuid
 | 
					        net_attrs["id"] = net_uuid
 | 
				
			||||||
        net_attrs["tenant_id"] = context.tenant_id
 | 
					        net_attrs["tenant_id"] = context.tenant_id
 | 
				
			||||||
        net_attrs["network_plugin"] = default_net_type
 | 
					        net_attrs["network_plugin"] = default_net_type
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,6 +20,7 @@ from neutron.common import rpc as n_rpc
 | 
				
			|||||||
from neutron.openstack.common import importutils
 | 
					from neutron.openstack.common import importutils
 | 
				
			||||||
from neutron.openstack.common import log as logging
 | 
					from neutron.openstack.common import log as logging
 | 
				
			||||||
from neutron.openstack.common import timeutils
 | 
					from neutron.openstack.common import timeutils
 | 
				
			||||||
 | 
					from neutron import quota
 | 
				
			||||||
from oslo.config import cfg
 | 
					from oslo.config import cfg
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from quark import allocation_pool
 | 
					from quark import allocation_pool
 | 
				
			||||||
@@ -115,6 +116,23 @@ def create_subnet(context, subnet):
 | 
				
			|||||||
            err_vals["prefix"] = 31
 | 
					            err_vals["prefix"] = 31
 | 
				
			||||||
            err_msg = err % err_vals
 | 
					            err_msg = err % err_vals
 | 
				
			||||||
            raise exceptions.InvalidInput(error_message=err_msg)
 | 
					            raise exceptions.InvalidInput(error_message=err_msg)
 | 
				
			||||||
 | 
					        # Enforce subnet quotas
 | 
				
			||||||
 | 
					        net_subnets = get_subnets(context,
 | 
				
			||||||
 | 
					                                  filters=dict(network_id=net_id))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        v4_count, v6_count = 0, 0
 | 
				
			||||||
 | 
					        for subnet in net_subnets:
 | 
				
			||||||
 | 
					            if netaddr.IPNetwork(subnet['cidr']).version == 6:
 | 
				
			||||||
 | 
					                v6_count += 1
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                v4_count += 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if cidr.version == 6:
 | 
				
			||||||
 | 
					            quota.QUOTAS.limit_check(context, context.tenant_id,
 | 
				
			||||||
 | 
					                                     v6_subnets_per_network=v6_count + 1)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            quota.QUOTAS.limit_check(context, context.tenant_id,
 | 
				
			||||||
 | 
					                                     v4_subnets_per_network=v4_count + 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # See RM981. The default behavior of setting a gateway unless
 | 
					        # See RM981. The default behavior of setting a gateway unless
 | 
				
			||||||
        # explicitly asked to not is no longer desirable.
 | 
					        # explicitly asked to not is no longer desirable.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -240,7 +240,7 @@ class TestQuarkCreatePortFailure(test_quark_plugin.TestQuarkPlugin):
 | 
				
			|||||||
                self.plugin.create_port(self.context, port_2)
 | 
					                self.plugin.create_port(self.context, port_2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TestQuarkCreatePort(test_quark_plugin.TestQuarkPlugin):
 | 
					class TestQuarkCreatePortsSameDevBadRequest(test_quark_plugin.TestQuarkPlugin):
 | 
				
			||||||
    @contextlib.contextmanager
 | 
					    @contextlib.contextmanager
 | 
				
			||||||
    def _stubs(self, port=None, network=None, addr=None, mac=None,
 | 
					    def _stubs(self, port=None, network=None, addr=None, mac=None,
 | 
				
			||||||
               limit_checks=None):
 | 
					               limit_checks=None):
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1037,6 +1037,110 @@ class TestQuarkDeleteSubnet(test_quark_plugin.TestQuarkPlugin):
 | 
				
			|||||||
                self.plugin.delete_subnet(self.context, 1)
 | 
					                self.plugin.delete_subnet(self.context, 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TestSubnetsQuotas(test_quark_plugin.TestQuarkPlugin):
 | 
				
			||||||
 | 
					    @contextlib.contextmanager
 | 
				
			||||||
 | 
					    def _stubs(self, subnet_values, deleted_at=None):
 | 
				
			||||||
 | 
					        class FakeContext(object):
 | 
				
			||||||
 | 
					            def __enter__(*args, **kwargs):
 | 
				
			||||||
 | 
					                pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            def __exit__(*args, **kwargs):
 | 
				
			||||||
 | 
					                pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.context.session.begin = FakeContext
 | 
				
			||||||
 | 
					        subnets = list()
 | 
				
			||||||
 | 
					        for s in subnet_values:
 | 
				
			||||||
 | 
					            s["network"] = models.Network()
 | 
				
			||||||
 | 
					            s["network"]["created_at"] = s["created_at"]
 | 
				
			||||||
 | 
					            subnet = models.Subnet(**s)
 | 
				
			||||||
 | 
					            subnets.append(subnet)
 | 
				
			||||||
 | 
					        db_mod = "quark.db.api"
 | 
				
			||||||
 | 
					        rpc_mod = "neutron.common.rpc"
 | 
				
			||||||
 | 
					        time_mod = "neutron.openstack.common.timeutils"
 | 
				
			||||||
 | 
					        sub_plugin_mod = "quark.plugin_modules.subnets"
 | 
				
			||||||
 | 
					        with contextlib.nested(
 | 
				
			||||||
 | 
					            mock.patch("%s.get_subnets" % sub_plugin_mod),
 | 
				
			||||||
 | 
					            mock.patch("%s.subnet_find" % db_mod),
 | 
				
			||||||
 | 
					            mock.patch("%s.network_find" % db_mod),
 | 
				
			||||||
 | 
					            mock.patch("%s.subnet_create" % db_mod),
 | 
				
			||||||
 | 
					            mock.patch("%s.subnet_delete" % db_mod),
 | 
				
			||||||
 | 
					            mock.patch("%s.get_notifier" % rpc_mod),
 | 
				
			||||||
 | 
					            mock.patch("%s.utcnow" % time_mod),
 | 
				
			||||||
 | 
					            mock.patch("%s._validate_subnet_cidr" % sub_plugin_mod)
 | 
				
			||||||
 | 
					        ) as (get_subnets, sub_find, net_find, sub_create, sub_del, notify,
 | 
				
			||||||
 | 
					              time_func, sub_validate):
 | 
				
			||||||
 | 
					            sub_create.return_value = subnets[0]
 | 
				
			||||||
 | 
					            sub_find.return_value = subnets[0]
 | 
				
			||||||
 | 
					            retsubs = []
 | 
				
			||||||
 | 
					            if len(subnets) > 1:
 | 
				
			||||||
 | 
					                retsubs = subnets[1:]
 | 
				
			||||||
 | 
					            get_subnets.return_value = retsubs
 | 
				
			||||||
 | 
					            time_func.return_value = deleted_at
 | 
				
			||||||
 | 
					            yield notify
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_create_subnet_v4_alongside_v6_quota_pass(self):
 | 
				
			||||||
 | 
					        original_4 = cfg.CONF.QUOTAS.quota_v4_subnets_per_network
 | 
				
			||||||
 | 
					        original_6 = cfg.CONF.QUOTAS.quota_v6_subnets_per_network
 | 
				
			||||||
 | 
					        s = [dict(network_id=1, cidr="192.167.10.0/24",
 | 
				
			||||||
 | 
					                  tenant_id=1, id=1, created_at="123"),
 | 
				
			||||||
 | 
					             dict(network_id=1, cidr="::0/24",
 | 
				
			||||||
 | 
					                  tenant_id=1, id=2, created_at="123")]
 | 
				
			||||||
 | 
					        with self._stubs(s):
 | 
				
			||||||
 | 
					            cfg.CONF.set_override('quota_v4_subnets_per_network', 1, "QUOTAS")
 | 
				
			||||||
 | 
					            cfg.CONF.set_override('quota_v6_subnets_per_network', 1, "QUOTAS")
 | 
				
			||||||
 | 
					            self.plugin.create_subnet(self.context, dict(subnet=s[0]))
 | 
				
			||||||
 | 
					            cfg.CONF.set_override('quota_v4_subnets_per_network', original_4,
 | 
				
			||||||
 | 
					                                  "QUOTAS")
 | 
				
			||||||
 | 
					            cfg.CONF.set_override('quota_v6_subnets_per_network', original_6,
 | 
				
			||||||
 | 
					                                  "QUOTAS")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_create_subnet_v4_quota_pass(self):
 | 
				
			||||||
 | 
					        original_4 = cfg.CONF.QUOTAS.quota_v4_subnets_per_network
 | 
				
			||||||
 | 
					        s = [dict(network_id=1, cidr="192.167.10.0/24",
 | 
				
			||||||
 | 
					                  tenant_id=1, id=1, created_at="123")]
 | 
				
			||||||
 | 
					        with self._stubs(s):
 | 
				
			||||||
 | 
					            cfg.CONF.set_override('quota_v4_subnets_per_network', 1, "QUOTAS")
 | 
				
			||||||
 | 
					            self.plugin.create_subnet(self.context, dict(subnet=s[0]))
 | 
				
			||||||
 | 
					            cfg.CONF.set_override('quota_v4_subnets_per_network', original_4,
 | 
				
			||||||
 | 
					                                  "QUOTAS")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_create_subnet_v6_quota_pass(self):
 | 
				
			||||||
 | 
					        original_6 = cfg.CONF.QUOTAS.quota_v6_subnets_per_network
 | 
				
			||||||
 | 
					        s = [dict(network_id=1, cidr="::0/24",
 | 
				
			||||||
 | 
					                  tenant_id=1, id=1, created_at="123")]
 | 
				
			||||||
 | 
					        with self._stubs(s):
 | 
				
			||||||
 | 
					            cfg.CONF.set_override('quota_v6_subnets_per_network', 1, "QUOTAS")
 | 
				
			||||||
 | 
					            self.plugin.create_subnet(self.context, dict(subnet=s[0]))
 | 
				
			||||||
 | 
					            cfg.CONF.set_override('quota_v6_subnets_per_network', original_6,
 | 
				
			||||||
 | 
					                                  "QUOTAS")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_create_subnet_v4_quota_fail(self):
 | 
				
			||||||
 | 
					        original_4 = cfg.CONF.QUOTAS.quota_v4_subnets_per_network
 | 
				
			||||||
 | 
					        s = [dict(network_id=1, cidr="192.167.10.0/24",
 | 
				
			||||||
 | 
					                  tenant_id=1, id=1, created_at="123"),
 | 
				
			||||||
 | 
					             dict(network_id=1, cidr="192.168.10.0/24",
 | 
				
			||||||
 | 
					                  tenant_id=1, id=2, created_at="124")]
 | 
				
			||||||
 | 
					        with self._stubs(s):
 | 
				
			||||||
 | 
					            cfg.CONF.set_override('quota_v4_subnets_per_network', 1, "QUOTAS")
 | 
				
			||||||
 | 
					            with self.assertRaises(exceptions.OverQuota):
 | 
				
			||||||
 | 
					                self.plugin.create_subnet(self.context, dict(subnet=s[0]))
 | 
				
			||||||
 | 
					            cfg.CONF.set_override('quota_v4_subnets_per_network', original_4,
 | 
				
			||||||
 | 
					                                  "QUOTAS")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_create_subnet_v6_quota_fail(self):
 | 
				
			||||||
 | 
					        original_6 = cfg.CONF.QUOTAS.quota_v6_subnets_per_network
 | 
				
			||||||
 | 
					        s = [dict(network_id=1, cidr="::0/24",
 | 
				
			||||||
 | 
					                  tenant_id=1, id=1, created_at="123"),
 | 
				
			||||||
 | 
					             dict(network_id=1, cidr="::1/24",
 | 
				
			||||||
 | 
					                  tenant_id=1, id=2, created_at="124")]
 | 
				
			||||||
 | 
					        with self._stubs(s):
 | 
				
			||||||
 | 
					            cfg.CONF.set_override('quota_v6_subnets_per_network', 1, "QUOTAS")
 | 
				
			||||||
 | 
					            with self.assertRaises(exceptions.OverQuota):
 | 
				
			||||||
 | 
					                self.plugin.create_subnet(self.context, dict(subnet=s[0]))
 | 
				
			||||||
 | 
					            cfg.CONF.set_override('quota_v6_subnets_per_network', original_6,
 | 
				
			||||||
 | 
					                                  "QUOTAS")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TestSubnetsNotification(test_quark_plugin.TestQuarkPlugin):
 | 
					class TestSubnetsNotification(test_quark_plugin.TestQuarkPlugin):
 | 
				
			||||||
    @contextlib.contextmanager
 | 
					    @contextlib.contextmanager
 | 
				
			||||||
    def _stubs(self, s, deleted_at=None):
 | 
					    def _stubs(self, s, deleted_at=None):
 | 
				
			||||||
@@ -1057,6 +1161,7 @@ class TestSubnetsNotification(test_quark_plugin.TestQuarkPlugin):
 | 
				
			|||||||
        time_mod = "neutron.openstack.common.timeutils"
 | 
					        time_mod = "neutron.openstack.common.timeutils"
 | 
				
			||||||
        sub_plugin_mod = "quark.plugin_modules.subnets"
 | 
					        sub_plugin_mod = "quark.plugin_modules.subnets"
 | 
				
			||||||
        with contextlib.nested(
 | 
					        with contextlib.nested(
 | 
				
			||||||
 | 
					            mock.patch("%s.get_subnets" % sub_plugin_mod),
 | 
				
			||||||
            mock.patch("%s.subnet_find" % db_mod),
 | 
					            mock.patch("%s.subnet_find" % db_mod),
 | 
				
			||||||
            mock.patch("%s.network_find" % db_mod),
 | 
					            mock.patch("%s.network_find" % db_mod),
 | 
				
			||||||
            mock.patch("%s.subnet_create" % db_mod),
 | 
					            mock.patch("%s.subnet_create" % db_mod),
 | 
				
			||||||
@@ -1064,9 +1169,10 @@ class TestSubnetsNotification(test_quark_plugin.TestQuarkPlugin):
 | 
				
			|||||||
            mock.patch("%s.get_notifier" % rpc_mod),
 | 
					            mock.patch("%s.get_notifier" % rpc_mod),
 | 
				
			||||||
            mock.patch("%s.utcnow" % time_mod),
 | 
					            mock.patch("%s.utcnow" % time_mod),
 | 
				
			||||||
            mock.patch("%s._validate_subnet_cidr" % sub_plugin_mod)
 | 
					            mock.patch("%s._validate_subnet_cidr" % sub_plugin_mod)
 | 
				
			||||||
        ) as (sub_find, net_find, sub_create, sub_del, notify,
 | 
					        ) as (get_subnets, sub_find, net_find, sub_create, sub_del, notify,
 | 
				
			||||||
              time_func, sub_validate):
 | 
					              time_func, sub_validate):
 | 
				
			||||||
            sub_create.return_value = subnet
 | 
					            sub_create.return_value = subnet
 | 
				
			||||||
 | 
					            get_subnets.return_value = []
 | 
				
			||||||
            sub_find.return_value = subnet
 | 
					            sub_find.return_value = subnet
 | 
				
			||||||
            time_func.return_value = deleted_at
 | 
					            time_func.return_value = deleted_at
 | 
				
			||||||
            yield notify
 | 
					            yield notify
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user