refactor QuotaV2 import to match to other exts
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
This commit is contained in:
parent
2a10cd2b6c
commit
31f09ab2ec
@ -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
|
||||
|
@ -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):
|
||||
"""
|
@ -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)
|
@ -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)}
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user