diff --git a/quantum/api/extensions.py b/quantum/api/extensions.py index 07c8cff5c2..4ae379b8f9 100644 --- a/quantum/api/extensions.py +++ b/quantum/api/extensions.py @@ -36,27 +36,6 @@ from quantum import wsgi LOG = logging.getLogger('quantum.api.extensions') -# Besides the supported_extension_aliases in plugin class, -# we also support register enabled extensions here so that we -# can load some mandatory files (such as db models) before initialize plugin -ENABLED_EXTS = { - 'quantum.plugins.linuxbridge.lb_quantum_plugin.LinuxBridgePluginV2': - { - 'ext_alias': ["quotas"], - 'ext_db_models': ['quantum.extensions._quotav2_model.Quota'], - }, - 'quantum.plugins.openvswitch.ovs_quantum_plugin.OVSQuantumPluginV2': - { - 'ext_alias': ["quotas"], - 'ext_db_models': ['quantum.extensions._quotav2_model.Quota'], - }, - 'quantum.plugins.nicira.nicira_nvp_plugin.QuantumPlugin.NvpPluginV2': - { - 'ext_alias': ["quotas"], - 'ext_db_models': ['quantum.extensions._quotav2_model.Quota'], - }, -} - class PluginInterface(object): __metaclass__ = ABCMeta @@ -559,9 +538,6 @@ class PluginAwareExtensionManager(ExtensionManager): alias in plugin.supported_extension_aliases) for plugin in self.plugins.values()) plugin_provider = cfg.CONF.core_plugin - if not supports_extension and plugin_provider in ENABLED_EXTS: - supports_extension = (alias in - ENABLED_EXTS[plugin_provider]['ext_alias']) if not supports_extension: LOG.warn(_("extension %s not supported by any of loaded plugins"), alias) @@ -581,11 +557,6 @@ class PluginAwareExtensionManager(ExtensionManager): @classmethod def get_instance(cls): if cls._instance is None: - plugin_provider = cfg.CONF.core_plugin - if plugin_provider in ENABLED_EXTS: - for model in ENABLED_EXTS[plugin_provider]['ext_db_models']: - LOG.debug('loading model %s', model) - model_class = importutils.import_class(model) cls._instance = cls(get_extensions_path(), QuantumManager.get_service_plugins()) return cls._instance diff --git a/quantum/extensions/_quotav2_driver.py b/quantum/db/quota_db.py similarity index 68% rename from quantum/extensions/_quotav2_driver.py rename to quantum/db/quota_db.py index d8abada754..4e73fb6596 100644 --- a/quantum/extensions/_quotav2_driver.py +++ b/quantum/db/quota_db.py @@ -15,8 +15,22 @@ # License for the specific language governing permissions and limitations # under the License. +import sqlalchemy as sa + from quantum.common import exceptions -from quantum.extensions import _quotav2_model as quotav2_model +from quantum.db import model_base +from quantum.db import models_v2 + + +class Quota(model_base.BASEV2, models_v2.HasId): + """Represent a single quota override for a tenant. + + If there is no row for a given tenant id and resource, then the + default for the quota class is used. + """ + tenant_id = sa.Column(sa.String(255), index=True) + resource = sa.Column(sa.String(255)) + limit = sa.Column(sa.Integer) class DbQuotaDriver(object): @@ -38,17 +52,15 @@ class DbQuotaDriver(object): :return dict: from resource name to dict of name and limit """ - quotas = {} - tenant_quotas = context.session.query( - quotav2_model.Quota).filter_by(tenant_id=tenant_id).all() - tenant_quotas_dict = {} - for _quota in tenant_quotas: - tenant_quotas_dict[_quota['resource']] = _quota['limit'] - for key, resource in resources.items(): - quotas[key] = dict( - name=key, - limit=tenant_quotas_dict.get(key, resource.default)) - return quotas + # init with defaults + tenant_quota = dict((key, resource.default) + for key, resource in resources.items()) + + # update with tenant specific limits + q_qry = context.session.query(Quota).filter_by(tenant_id=tenant_id) + tenant_quota.update((q['resource'], q['limit']) for q in q_qry.all()) + + return tenant_quota @staticmethod def delete_tenant_quota(context, tenant_id): @@ -57,8 +69,8 @@ class DbQuotaDriver(object): Atfer deletion, this tenant will use default quota values in conf. """ with context.session.begin(): - tenant_quotas = context.session.query( - quotav2_model.Quota).filter_by(tenant_id=tenant_id).all() + tenant_quotas = context.session.query(Quota).filter_by( + tenant_id=tenant_id).all() for quota in tenant_quotas: context.session.delete(quota) @@ -74,22 +86,38 @@ class DbQuotaDriver(object): resourcekey2: ... """ - _quotas = context.session.query(quotav2_model.Quota).all() - quotas = {} - tenant_quotas_dict = {} - for _quota in _quotas: - tenant_id = _quota['tenant_id'] - if tenant_id not in quotas: - quotas[tenant_id] = {'tenant_id': tenant_id} - tenant_quotas_dict = quotas[tenant_id] - tenant_quotas_dict[_quota['resource']] = _quota['limit'] + tenant_default = dict((key, resource.default) + for key, resource in resources.items()) - # we complete the quotas according to input resources - for tenant_quotas_dict in quotas.itervalues(): - for key, resource in resources.items(): - tenant_quotas_dict[key] = tenant_quotas_dict.get( - key, resource.default) - return quotas.itervalues() + all_tenant_quotas = {} + + for quota in context.session.query(Quota).all(): + tenant_id = quota['tenant_id'] + + # avoid setdefault() because only want to copy when actually req'd + tenant_quota = all_tenant_quotas.get(tenant_id) + if tenant_quota is None: + tenant_quota = tenant_default.copy() + tenant_quota['tenant_id'] = tenant_id + all_tenant_quotas[tenant_id] = tenant_quota + + tenant_quota[quota['resource']] = quota['limit'] + + return all_tenant_quotas.itervalues() + + @staticmethod + def update_quota_limit(context, tenant_id, resource, limit): + with context.session.begin(): + tenant_quota = context.session.query(Quota).filter_by( + tenant_id=tenant_id, resource=resource).first() + + if tenant_quota: + tenant_quota.update({'limit': limit}) + else: + tenant_quota = Quota(tenant_id=tenant_id, + resource=resource, + limit=limit) + context.session.add(tenant_quota) def _get_quotas(self, context, tenant_id, resources, keys): """ diff --git a/quantum/extensions/_quotav2_model.py b/quantum/extensions/_quotav2_model.py deleted file mode 100644 index 474fa228b9..0000000000 --- a/quantum/extensions/_quotav2_model.py +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright (c) 2012 OpenStack, LLC. -# -# 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. - -import sqlalchemy as sa - -from quantum.db import model_base -from quantum.db import models_v2 - - -class Quota(model_base.BASEV2, models_v2.HasId): - """Represent a single quota override for a tenant. - - If there is no row for a given tenant id and resource, then the - default for the quota class is used. - """ - tenant_id = sa.Column(sa.String(255), index=True) - resource = sa.Column(sa.String(255)) - limit = sa.Column(sa.Integer) diff --git a/quantum/extensions/quotasv2.py b/quantum/extensions/quotasv2.py index ddc35d361b..6c7cd3b17e 100644 --- a/quantum/extensions/quotasv2.py +++ b/quantum/extensions/quotasv2.py @@ -20,17 +20,16 @@ import webob from quantum.api import extensions from quantum.api.v2 import base from quantum.common import exceptions -from quantum.extensions import _quotav2_driver as quotav2_driver -from quantum.extensions import _quotav2_model as quotav2_model from quantum.manager import QuantumManager from quantum.openstack.common import cfg +from quantum.openstack.common import importutils from quantum import quota from quantum import wsgi RESOURCE_NAME = 'quota' RESOURCE_COLLECTION = RESOURCE_NAME + "s" QUOTAS = quota.QUOTAS -DB_QUOTA_DRIVER = 'quantum.extensions._quotav2_driver.DbQuotaDriver' +DB_QUOTA_DRIVER = 'quantum.db.quota_db.DbQuotaDriver' EXTENDED_ATTRIBUTES_2_0 = { RESOURCE_COLLECTION: {} } @@ -48,6 +47,7 @@ class QuotaSetsController(wsgi.Controller): def __init__(self, plugin): self._resource_name = RESOURCE_NAME self._plugin = plugin + self._driver = importutils.import_class(DB_QUOTA_DRIVER) def _get_body(self, request): body = self._deserialize(request.body, request.get_content_type()) @@ -57,9 +57,8 @@ class QuotaSetsController(wsgi.Controller): return req_body def _get_quotas(self, request, tenant_id): - values = quotav2_driver.DbQuotaDriver.get_tenant_quotas( + return self._driver.get_tenant_quotas( request.context, QUOTAS.resources, tenant_id) - return dict((k, v['limit']) for k, v in values.items()) def create(self, request, body=None): raise NotImplementedError() @@ -69,8 +68,7 @@ class QuotaSetsController(wsgi.Controller): if not context.is_admin: raise webob.exc.HTTPForbidden() return {self._resource_name + "s": - quotav2_driver.DbQuotaDriver.get_all_quotas( - context, QUOTAS.resources)} + self._driver.get_all_quotas(context, QUOTAS.resources)} def tenant(self, request): """Retrieve the tenant info in context.""" @@ -93,37 +91,26 @@ class QuotaSetsController(wsgi.Controller): def _check_modification_delete_privilege(self, context, tenant_id): if not tenant_id: raise webob.exc.HTTPBadRequest('invalid tenant') - if (not context.is_admin): + if not context.is_admin: raise webob.exc.HTTPForbidden() return tenant_id def delete(self, request, id): - tenant_id = id tenant_id = self._check_modification_delete_privilege(request.context, - tenant_id) - quotav2_driver.DbQuotaDriver.delete_tenant_quota(request.context, - tenant_id) + id) + self._driver.delete_tenant_quota(request.context, tenant_id) def update(self, request, id): - tenant_id = id tenant_id = self._check_modification_delete_privilege(request.context, - tenant_id) + id) req_body = self._get_body(request) for key in req_body[self._resource_name].keys(): if key in QUOTAS.resources: value = int(req_body[self._resource_name][key]) - with request.context.session.begin(): - tenant_quotas = request.context.session.query( - quotav2_model.Quota).filter_by(tenant_id=tenant_id, - resource=key).all() - if not tenant_quotas: - quota = quotav2_model.Quota(tenant_id=tenant_id, - resource=key, - limit=value) - request.context.session.add(quota) - else: - quota = tenant_quotas[0] - quota.update({'limit': value}) + self._driver.update_quota_limit(request.context, + tenant_id, + key, + value) return {self._resource_name: self._get_quotas(request, tenant_id)} diff --git a/quantum/plugins/cisco/tests/unit/v2/quantumv2.conf.cisco.test b/quantum/plugins/cisco/tests/unit/v2/quantumv2.conf.cisco.test index 56511a7397..dc855a7cc0 100644 --- a/quantum/plugins/cisco/tests/unit/v2/quantumv2.conf.cisco.test +++ b/quantum/plugins/cisco/tests/unit/v2/quantumv2.conf.cisco.test @@ -40,4 +40,4 @@ default_quota = -1 # default driver to use for quota checks # quota_driver = quantum.quota.ConfDriver -quota_driver = quantum.extensions._quotav2_driver.DbQuotaDriver +quota_driver = quantum.db.quota_db.DbQuotaDriver diff --git a/quantum/plugins/linuxbridge/lb_quantum_plugin.py b/quantum/plugins/linuxbridge/lb_quantum_plugin.py index 7c946d000f..4f47a19eac 100644 --- a/quantum/plugins/linuxbridge/lb_quantum_plugin.py +++ b/quantum/plugins/linuxbridge/lb_quantum_plugin.py @@ -25,6 +25,7 @@ from quantum.db import db_base_plugin_v2 from quantum.db import dhcp_rpc_base from quantum.db import l3_db from quantum.db import l3_rpc_base +from quantum.db import quota_db from quantum.extensions import portbindings from quantum.extensions import providernet as provider from quantum.openstack.common import cfg @@ -156,7 +157,7 @@ class LinuxBridgePluginV2(db_base_plugin_v2.QuantumDbPluginV2, # is qualified by class __native_bulk_support = True - supported_extension_aliases = ["provider", "router", "binding"] + supported_extension_aliases = ["provider", "router", "binding", "quotas"] network_view = "extension:provider_network:view" network_set = "extension:provider_network:set" diff --git a/quantum/plugins/nicira/nicira_nvp_plugin/QuantumPlugin.py b/quantum/plugins/nicira/nicira_nvp_plugin/QuantumPlugin.py index 0241576485..85ac3035d2 100644 --- a/quantum/plugins/nicira/nicira_nvp_plugin/QuantumPlugin.py +++ b/quantum/plugins/nicira/nicira_nvp_plugin/QuantumPlugin.py @@ -43,6 +43,7 @@ from quantum.db import api as db from quantum.db import db_base_plugin_v2 from quantum.db import dhcp_rpc_base from quantum.db import models_v2 +from quantum.db import quota_db from quantum.extensions import providernet as pnet from quantum.openstack.common import cfg from quantum.openstack.common import rpc @@ -127,7 +128,7 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2): functionality using NVP. """ - supported_extension_aliases = ["provider"] + supported_extension_aliases = ["provider", "quotas"] # Default controller cluster default_cluster = None diff --git a/quantum/plugins/openvswitch/ovs_quantum_plugin.py b/quantum/plugins/openvswitch/ovs_quantum_plugin.py index 9ce325d6ad..6722f4676a 100644 --- a/quantum/plugins/openvswitch/ovs_quantum_plugin.py +++ b/quantum/plugins/openvswitch/ovs_quantum_plugin.py @@ -31,6 +31,7 @@ from quantum.db import db_base_plugin_v2 from quantum.db import dhcp_rpc_base from quantum.db import l3_db from quantum.db import l3_rpc_base +from quantum.db import quota_db from quantum.extensions import portbindings from quantum.extensions import providernet as provider from quantum.openstack.common import cfg @@ -194,7 +195,7 @@ class OVSQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2, # bulk operations. Name mangling is used in order to ensure it # is qualified by class __native_bulk_support = True - supported_extension_aliases = ["provider", "router", "binding"] + supported_extension_aliases = ["provider", "router", "binding", "quotas"] network_view = "extension:provider_network:view" network_set = "extension:provider_network:set" diff --git a/quantum/quota.py b/quantum/quota.py index 22efcc9813..e4bc010068 100644 --- a/quantum/quota.py +++ b/quantum/quota.py @@ -139,10 +139,9 @@ class BaseResource(object): @property def default(self): """Return the default value of the quota.""" - if hasattr(cfg.CONF.QUOTAS, self.flag): - return cfg.CONF.QUOTAS[self.flag] - else: - return cfg.CONF.QUOTAS.default_quota + return getattr(cfg.CONF.QUOTAS, + self.flag, + cfg.CONF.QUOTAS.default_quota) class CountableResource(BaseResource): diff --git a/quantum/tests/unit/cisco/test_network_plugin.py b/quantum/tests/unit/cisco/test_network_plugin.py index 8e3f9fc6fd..4ccf0f543c 100644 --- a/quantum/tests/unit/cisco/test_network_plugin.py +++ b/quantum/tests/unit/cisco/test_network_plugin.py @@ -23,7 +23,7 @@ from quantum.common.test_lib import test_config from quantum import context from quantum.db import api as db from quantum.db import l3_db -from quantum.extensions import _quotav2_model as quotav2_model +from quantum.db import quota_db from quantum.manager import QuantumManager from quantum.openstack.common import cfg from quantum.plugins.cisco.common import cisco_constants as const diff --git a/quantum/tests/unit/test_quota_per_tenant_ext.py b/quantum/tests/unit/test_quota_per_tenant_ext.py index 917b1bbed9..440cca8a28 100644 --- a/quantum/tests/unit/test_quota_per_tenant_ext.py +++ b/quantum/tests/unit/test_quota_per_tenant_ext.py @@ -26,12 +26,6 @@ _get_path = test_api_v2._get_path class QuotaExtensionTestCase(unittest.TestCase): def setUp(self): - if getattr(self, 'testflag', 1) == 1: - self._setUp1() - else: - self._setUp2() - - def _setUp1(self): db._ENGINE = None db._MAKER = None # Ensure 'stale' patched copies of the plugin are never returned @@ -53,7 +47,7 @@ class QuotaExtensionTestCase(unittest.TestCase): cfg.CONF.set_override('core_plugin', TARGET_PLUGIN) cfg.CONF.set_override( 'quota_driver', - 'quantum.extensions._quotav2_driver.DbQuotaDriver', + 'quantum.db.quota_db.DbQuotaDriver', group='QUOTAS') cfg.CONF.set_override( 'quota_items', @@ -62,6 +56,7 @@ class QuotaExtensionTestCase(unittest.TestCase): self._plugin_patcher = mock.patch(TARGET_PLUGIN, autospec=True) self.plugin = self._plugin_patcher.start() + self.plugin.return_value.supported_extension_aliases = ['quotas'] # QUOTAS will regester the items in conf when starting # extra1 here is added later, so have to do it manually quota.QUOTAS.register_resource_by_name('extra1') @@ -71,34 +66,6 @@ class QuotaExtensionTestCase(unittest.TestCase): ext_middleware = extensions.ExtensionMiddleware(app, ext_mgr=ext_mgr) self.api = webtest.TestApp(ext_middleware) - def _setUp2(self): - db._ENGINE = None - db._MAKER = None - # Ensure 'stale' patched copies of the plugin are never returned - manager.QuantumManager._instance = None - - # Ensure existing ExtensionManager is not used - extensions.PluginAwareExtensionManager._instance = None - - # Save the global RESOURCE_ATTRIBUTE_MAP - self.saved_attr_map = {} - for resource, attrs in attributes.RESOURCE_ATTRIBUTE_MAP.iteritems(): - self.saved_attr_map[resource] = attrs.copy() - - # Create the default configurations - args = ['--config-file', test_extensions.etcdir('quantum.conf.test')] - config.parse(args=args) - - # Update the plugin and extensions path - cfg.CONF.set_override('core_plugin', TARGET_PLUGIN) - self._plugin_patcher = mock.patch(TARGET_PLUGIN, autospec=True) - self.plugin = self._plugin_patcher.start() - ext_mgr = extensions.PluginAwareExtensionManager.get_instance() - l2network_db_v2.initialize() - app = config.load_paste_app('extensions_test_app') - ext_middleware = extensions.ExtensionMiddleware(app, ext_mgr=ext_mgr) - self.api = webtest.TestApp(ext_middleware) - def tearDown(self): self._plugin_patcher.stop() self.api = None @@ -114,7 +81,7 @@ class QuotaExtensionTestCase(unittest.TestCase): res = self.api.get(_get_path('quotas')) self.assertEqual(200, res.status_int) - def test_quotas_defaul_values(self): + def test_quotas_default_values(self): tenant_id = 'tenant_id1' env = {'quantum.context': context.Context('', tenant_id)} res = self.api.get(_get_path('quotas', id=tenant_id), @@ -181,10 +148,8 @@ class QuotaExtensionTestCase(unittest.TestCase): self.assertEqual(403, res.status_int) def test_quotas_loaded_bad(self): - self.testflag = 2 try: res = self.api.get(_get_path('quotas'), expect_errors=True) self.assertEqual(404, res.status_int) except Exception: pass - self.testflag = 1