]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
refactor QuotaV2 import to match to other exts
authorMark McClain <mark.mcclain@dreamhost.com>
Sun, 6 Jan 2013 00:28:35 +0000 (19:28 -0500)
committerMark McClain <mark.mcclain@dreamhost.com>
Mon, 7 Jan 2013 04:33:29 +0000 (23:33 -0500)
fixes bug 1096486

The previous code used a special extension loading mechanism to
selectively load the Quota model is the plugin matched and object path.
This was intended to load models required by plugins, but this loading
actually occurred after the db schema was created, so the model was not
always loaded.  This fix refactors the code to make the QuotaV2 ext
behave similarly to the other extensions ensuring the models are loaded
prior to database schema creation.

Change-Id: Id7d1f7ddee69bfc4419df375366319dedc3dc439

quantum/api/extensions.py
quantum/db/quota_db.py [moved from quantum/extensions/_quotav2_driver.py with 68% similarity]
quantum/extensions/_quotav2_model.py [deleted file]
quantum/extensions/quotasv2.py
quantum/plugins/cisco/tests/unit/v2/quantumv2.conf.cisco.test
quantum/plugins/linuxbridge/lb_quantum_plugin.py
quantum/plugins/nicira/nicira_nvp_plugin/QuantumPlugin.py
quantum/plugins/openvswitch/ovs_quantum_plugin.py
quantum/quota.py
quantum/tests/unit/cisco/test_network_plugin.py
quantum/tests/unit/test_quota_per_tenant_ext.py

index 07c8cff5c26be0c6d2c4b821416d17df7656fa7e..4ae379b8f915625171d61937f3d5d4b81a03ac14 100644 (file)
@@ -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
similarity index 68%
rename from quantum/extensions/_quotav2_driver.py
rename to quantum/db/quota_db.py
index d8abada75405f6efe2ea2b200d0da43472516625..4e73fb6596706798cb9f607c38883eecea7f019f 100644 (file)
 #    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']
-
-        # 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()
+        tenant_default = dict((key, resource.default)
+                              for key, resource in resources.items())
+
+        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 (file)
index 474fa22..0000000
+++ /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)
index ddc35d361b1f67a3a6525fe86a7a19fd1c7f2c0d..6c7cd3b17e3bd00deaaaa440f6a4ad7208019009 100644 (file)
@@ -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)}
 
 
index 56511a73974bb151e2931976c5245909f45e7900..dc855a7cc06cbb8f1d92558f34bbf0c944bbc7ad 100644 (file)
@@ -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
index 7c946d000ffc95b3f0bdcbb6e6db0b8b092f6a1b..4f47a19eacd379291c3f8e2f5f6b5fb4bf61b229 100644 (file)
@@ -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"
index 0241576485006607ed404785f6159d268ab3477e..85ac3035d2ab194463200c3eeb3e30f0974683c3 100644 (file)
@@ -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
 
index 9ce325d6ad6fc9566016b606b4449d320048a9ee..6722f4676aa7d86250fd41c86ae54082d2632257 100644 (file)
@@ -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"
index 22efcc981360502e6aed5e1566904cb2e151af61..e4bc01006879c5be95fca5edb477f410403a6e7e 100644 (file)
@@ -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):
index 8e3f9fc6fd9a3c129ae7321aaa1bf2529dc76011..4ccf0f543c1292917b976df5b7dba47ce28e1626 100644 (file)
@@ -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
index 917b1bbed95212ec8ff171ba8fa625d61058873f..440cca8a284aa8fb55008bc75d9abd40d92faef6 100644 (file)
@@ -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