From 21a6f752ae0f26ab8f943657bb32579a2dd7089a Mon Sep 17 00:00:00 2001 From: Kevin George Date: Tue, 2 Jul 2013 05:18:39 -0500 Subject: [PATCH] Quotas API --- quark/plugin.py | 51 ++++++++++++++++++++++---------- quark/quota_driver.py | 49 ++++++++++++++++++++++++++++++ quark/tests/test_quark_plugin.py | 20 +++++++++++++ 3 files changed, 105 insertions(+), 15 deletions(-) create mode 100644 quark/quota_driver.py diff --git a/quark/plugin.py b/quark/plugin.py index 168905e..008918c 100644 --- a/quark/plugin.py +++ b/quark/plugin.py @@ -35,6 +35,7 @@ from neutron.openstack.common.db.sqlalchemy import session as neutron_session 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 neutron import neutron_plugin_base_v2 @@ -66,8 +67,18 @@ quark_opts = [ help=_("Path to the config for the net driver")) ] +quark_quota_opts = [ + cfg.IntOpt('quota_ports_per_network', + default=64, + help=_('Maximum ports per network per tenant')), + cfg.IntOpt('quota_security_rules_per_group', + default=20, + help=_('Maximum security group rules in a group')), +] + STRATEGY = network_strategy.STRATEGY CONF.register_opts(quark_opts, "QUARK") +CONF.register_opts(quark_quota_opts, "QUOTAS") def _pop_param(attrs, param, default=None): @@ -93,7 +104,8 @@ class Plugin(neutron_plugin_base_v2.NeutronPluginBaseV2, supported_extension_aliases = ["mac_address_ranges", "routes", "ip_addresses", "ports_quark", "security-group", - "subnets_quark", "provider", "ip_policies"] + "subnets_quark", "provider", + "ip_policies", "quotas"] def _initDBMaker(self): # This needs to be called after _ENGINE is configured @@ -125,6 +137,14 @@ class Plugin(neutron_plugin_base_v2.NeutronPluginBaseV2, self.ipam_reuse_after = CONF.QUARK.ipam_reuse_after neutron_db_api.register_models(base=models.BASEV2) + quark_resources = [ + quota.BaseResource('ports_per_network', + 'quota_ports_per_network'), + quota.BaseResource('security_rules_per_group', + 'quota_security_rules_per_group'), + ] + quota.QUOTAS.register_resources(quark_resources) + def _make_security_group_list(self, context, group_ids): if not group_ids or group_ids is attributes.ATTR_NOT_SPECIFIED: return ([], []) @@ -590,6 +610,10 @@ class Plugin(neutron_plugin_base_v2.NeutronPluginBaseV2, if not net: raise exceptions.NetworkNotFound(net_id=net_id) + quota.QUOTAS.limit_check( + context, context.tenant_id, + ports_per_network=len(net.get('ports', []))+1) + if fixed_ips: for fixed_ip in fixed_ips: subnet_id = fixed_ip.get("subnet_id") @@ -1080,9 +1104,6 @@ class Plugin(neutron_plugin_base_v2.NeutronPluginBaseV2, def create_security_group(self, context, security_group): LOG.info("create_security_group for tenant %s" % (context.tenant_id)) - if (db_api.security_group_find(context).count() >= - CONF.QUOTAS.quota_security_group): - raise exceptions.OverQuota(overs="security groups") group = security_group["security_group"] group_name = group.get('name', '') if group_name == "default": @@ -1107,14 +1128,13 @@ class Plugin(neutron_plugin_base_v2.NeutronPluginBaseV2, 'group_id': '00000000-0000-0000-0000-000000000000', 'port_egress_rules': [], 'port_ingress_rules': [ - {'ethertype': 'IPv4', 'protocol': 6, - 'port_range_min': 0, 'port_range_max': 65535}, - {'ethertype': 'IPv4', 'protocol': 17, - 'port_range_min': 0, 'port_range_max': 65535}, - {'ethertype': 'IPv6', 'protocol': 6, - 'port_range_min': 0, 'port_range_max': 65535}, - {'ethertype': 'IPv6', 'protocol': 17, - 'port_range_min': 0, 'port_range_max': 65535}]} + {'ethertype': 'IPv4', 'protocol': 1}, + {'ethertype': 'IPv4', 'protocol': 6}, + {'ethertype': 'IPv4', 'protocol': 17}, + {'ethertype': 'IPv6', 'protocol': 1}, + {'ethertype': 'IPv6', 'protocol': 6}, + {'ethertype': 'IPv6', 'protocol': 17}, + ]} self.net_driver.create_security_group( context, @@ -1134,9 +1154,6 @@ class Plugin(neutron_plugin_base_v2.NeutronPluginBaseV2, def create_security_group_rule(self, context, security_group_rule): LOG.info("create_security_group for tenant %s" % (context.tenant_id)) - if (db_api.security_group_rule_find(context).count() >= - CONF.QUOTAS.quota_security_group_rule): - raise exceptions.OverQuota(overs="security group rules") rule = self._validate_security_group_rule( context, security_group_rule["security_group_rule"]) rule['id'] = uuidutils.generate_uuid() @@ -1147,6 +1164,10 @@ class Plugin(neutron_plugin_base_v2.NeutronPluginBaseV2, if not group: raise sg_ext.SecurityGroupNotFound(group_id=group_id) + quota.QUOTAS.limit_check( + context, context.tenant_id, + security_rules_per_group=len(group.get('rules', []))+1) + self.net_driver.create_security_group_rule( context, group_id, diff --git a/quark/quota_driver.py b/quark/quota_driver.py new file mode 100644 index 0000000..9476c6f --- /dev/null +++ b/quark/quota_driver.py @@ -0,0 +1,49 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack Foundation. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from neutron.db import quota_db + + +class QuarkQuotaDriver(quota_db.DbQuotaDriver): + """Driver to perform necessary checks to enforce quotas and obtain quota + information. + + The default driver utilizes the local database. + """ + + @staticmethod + def delete_tenant_quota(context, tenant_id): + """Delete the quota entries for a given tenant_id. + + Atfer deletion, this tenant will use default quota values in conf. + """ + tenant_quotas = context.session.query(quota_db.Quota) + tenant_quotas = tenant_quotas.filter_by(tenant_id=tenant_id) + tenant_quotas.delete() + + @staticmethod + def update_quota_limit(context, tenant_id, resource, limit): + tenant_quota = context.session.query(quota_db.Quota).filter_by( + tenant_id=tenant_id, resource=resource).first() + + if tenant_quota: + tenant_quota.update({'limit': limit}) + else: + tenant_quota = quota_db.Quota(tenant_id=tenant_id, + resource=resource, + limit=limit) + context.session.add(tenant_quota) diff --git a/quark/tests/test_quark_plugin.py b/quark/tests/test_quark_plugin.py index 5e72f01..4ad0966 100644 --- a/quark/tests/test_quark_plugin.py +++ b/quark/tests/test_quark_plugin.py @@ -37,6 +37,7 @@ class TestQuarkPlugin(test_base.TestBase): def setUp(self): super(TestQuarkPlugin, self).setUp() + cfg.CONF.set_override('quota_ports_per_network', 1, 'QUOTAS') cfg.CONF.set_override('connection', 'sqlite://', 'database') db_api.configure_db() self.plugin = quark.plugin.Plugin() @@ -1219,6 +1220,18 @@ class TestQuarkCreatePort(TestQuarkPlugin): with self.assertRaises(exceptions.NetworkNotFound): self.plugin.create_port(self.context, port) + def test_create_port_net_at_max(self): + network = dict(id=1, ports=[models.Port()]) + mac = dict(address="aa:bb:cc:dd:ee:ff") + port_name = "foobar" + ip = dict() + port = dict(port=dict(mac_address=mac["address"], network_id=1, + tenant_id=self.context.tenant_id, device_id=2, + name=port_name)) + with self._stubs(port=port["port"], network=network, addr=ip, mac=mac): + with self.assertRaises(exceptions.OverQuota): + self.plugin.create_port(self.context, port) + def test_create_port_security_groups(self, groups=[1]): network = dict(id=1) mac = dict(address="aa:bb:cc:dd:ee:ff") @@ -1888,6 +1901,7 @@ class TestQuarkCreateSecurityGroupRule(TestQuarkPlugin): def setUp(self, *args, **kwargs): super(TestQuarkCreateSecurityGroupRule, self).setUp(*args, **kwargs) cfg.CONF.set_override('quota_security_group_rule', 1, 'QUOTAS') + cfg.CONF.set_override('quota_security_rules_per_group', 1, 'QUOTAS') self.rule = {'id': 1, 'ethertype': 'IPv4', 'security_group_id': 1, 'group': {'id': 1}} self.expected = { @@ -1928,6 +1942,7 @@ class TestQuarkCreateSecurityGroupRule(TestQuarkPlugin): rule = dict(self.rule, **ruleset) group = rule.pop('group') expected = dict(self.expected, **ruleset) + expected.pop('group', None) with self._stubs(rule, group) as rule_create: result = self.plugin.create_security_group_rule( self.context, {'security_group_rule': rule}) @@ -1973,6 +1988,11 @@ class TestQuarkCreateSecurityGroupRule(TestQuarkPlugin): with self.assertRaises(sg_ext.SecurityGroupNotFound): self._test_create_security_rule(group=None) + def test_create_security_rule_group_at_max(self): + with self.assertRaises(exceptions.OverQuota): + self._test_create_security_rule( + group={'id': 1, 'rules': [models.SecurityGroupRule()]}) + class TestQuarkDeleteSecurityGroupRule(TestQuarkPlugin): @contextlib.contextmanager