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