Enforce subnets per ip version per network quota

RM#9096
This commit is contained in:
John Perkins
2014-09-26 20:17:09 -05:00
parent 7657a9f31e
commit dc0fabf5b0
5 changed files with 156 additions and 5 deletions

View File

@@ -39,6 +39,10 @@ CONF = cfg.CONF
quark_resources = [
quota.BaseResource('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_routes_per_subnet'),
quota.BaseResource('security_rules_per_group',
@@ -51,6 +55,12 @@ quark_quota_opts = [
cfg.IntOpt('quota_ports_per_network',
default=250,
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',
default=3,
help=_('Maximum routes per subnet')),

View File

@@ -13,11 +13,13 @@
# License for the specific language governing permissions and limitations
# under the License.
import netaddr
from neutron.common import exceptions
from neutron.extensions import providernet as pnet
from neutron.openstack.common import importutils
from neutron.openstack.common import log as logging
from neutron.openstack.common import uuidutils
from neutron import quota
from oslo.config import cfg
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)
with context.session.begin():
# Generate a uuid that we're going to hand to the backend and db
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_type = None
if net_uuid and context.is_admin:
@@ -96,8 +115,6 @@ def create_network(context, network):
network_id=net_uuid, phys_type=pnet_type,
phys_net=phys_net, segment_id=seg_id)
subs = net_attrs.pop("subnets", [])
net_attrs["id"] = net_uuid
net_attrs["tenant_id"] = context.tenant_id
net_attrs["network_plugin"] = default_net_type

View File

@@ -20,6 +20,7 @@ from neutron.common import rpc as n_rpc
from neutron.openstack.common import importutils
from neutron.openstack.common import log as logging
from neutron.openstack.common import timeutils
from neutron import quota
from oslo.config import cfg
from quark import allocation_pool
@@ -115,6 +116,23 @@ def create_subnet(context, subnet):
err_vals["prefix"] = 31
err_msg = err % err_vals
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
# explicitly asked to not is no longer desirable.

View File

@@ -240,7 +240,7 @@ class TestQuarkCreatePortFailure(test_quark_plugin.TestQuarkPlugin):
self.plugin.create_port(self.context, port_2)
class TestQuarkCreatePort(test_quark_plugin.TestQuarkPlugin):
class TestQuarkCreatePortsSameDevBadRequest(test_quark_plugin.TestQuarkPlugin):
@contextlib.contextmanager
def _stubs(self, port=None, network=None, addr=None, mac=None,
limit_checks=None):

View File

@@ -1037,6 +1037,110 @@ class TestQuarkDeleteSubnet(test_quark_plugin.TestQuarkPlugin):
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):
@contextlib.contextmanager
def _stubs(self, s, deleted_at=None):
@@ -1057,6 +1161,7 @@ class TestSubnetsNotification(test_quark_plugin.TestQuarkPlugin):
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),
@@ -1064,9 +1169,10 @@ class TestSubnetsNotification(test_quark_plugin.TestQuarkPlugin):
mock.patch("%s.get_notifier" % rpc_mod),
mock.patch("%s.utcnow" % time_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):
sub_create.return_value = subnet
get_subnets.return_value = []
sub_find.return_value = subnet
time_func.return_value = deleted_at
yield notify