Enforce subnets per ip version per network quota
RM#9096
This commit is contained in:
@@ -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')),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user