diff --git a/quantum/extensions/quotasv2.py b/quantum/extensions/quotasv2.py index e68b791e6..41a95018e 100644 --- a/quantum/extensions/quotasv2.py +++ b/quantum/extensions/quotasv2.py @@ -41,7 +41,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) + self._driver = importutils.import_class(cfg.CONF.QUOTAS.quota_driver) self._update_extended_attributes = True def _update_attributes(self): @@ -56,7 +56,7 @@ class QuotaSetsController(wsgi.Controller): def _get_body(self, request): body = self._deserialize(request.body, request.best_match_content_type()) - if self._update_extended_attributes is True: + if self._update_extended_attributes: self._update_attributes() attr_info = EXTENDED_ATTRIBUTES_2_0[RESOURCE_COLLECTION] @@ -69,7 +69,7 @@ class QuotaSetsController(wsgi.Controller): request.context, QUOTAS.resources, tenant_id) def create(self, request, body=None): - raise NotImplementedError() + raise webob.exc.HTTPNotImplemented() def index(self, request): context = request.context @@ -127,7 +127,7 @@ class Quotasv2(extensions.ExtensionDescriptor): @classmethod def get_name(cls): - return "Quotas for each tenant" + return "Quota management support" @classmethod def get_alias(cls): @@ -135,8 +135,10 @@ class Quotasv2(extensions.ExtensionDescriptor): @classmethod def get_description(cls): - return ("Expose functions for cloud admin to update quotas" - "for each tenant") + description = 'Expose functions for quotas management' + if cfg.CONF.QUOTAS.quota_driver == DB_QUOTA_DRIVER: + description += ' per tenant' + return description @classmethod def get_namespace(cls): @@ -160,8 +162,3 @@ class Quotasv2(extensions.ExtensionDescriptor): return EXTENDED_ATTRIBUTES_2_0 else: return {} - - def check_env(self): - if cfg.CONF.QUOTAS.quota_driver != DB_QUOTA_DRIVER: - msg = _('Quota driver %s is needed.') % DB_QUOTA_DRIVER - raise exceptions.InvalidExtenstionEnv(reason=msg) diff --git a/quantum/quota.py b/quantum/quota.py index 217fdeec5..2a9d4f011 100644 --- a/quantum/quota.py +++ b/quantum/quota.py @@ -17,6 +17,7 @@ """Quotas for instances, volumes, and floating ips.""" from oslo.config import cfg +import webob from quantum.common import exceptions from quantum.openstack.common import importutils @@ -124,6 +125,26 @@ class ConfDriver(object): raise exceptions.OverQuota(overs=sorted(overs), quotas=quotas, usages={}) + @staticmethod + def get_tenant_quotas(context, resources, tenant_id): + quotas = {} + sub_resources = dict((k, v) for k, v in resources.items()) + for resource in sub_resources.values(): + quotas[resource.name] = resource.default + return quotas + + @staticmethod + def get_all_quotas(context, resources): + return [] + + @staticmethod + def delete_tenant_quota(context, tenant_id): + raise webob.exc.HTTPForbidden() + + @staticmethod + def update_quota_limit(context, tenant_id, resource, limit): + raise webob.exc.HTTPForbidden() + class BaseResource(object): """Describe a single resource for quota checking.""" diff --git a/quantum/tests/unit/test_quota_per_tenant_ext.py b/quantum/tests/unit/test_quota_per_tenant_ext.py index d86b0e6b1..0006562e2 100644 --- a/quantum/tests/unit/test_quota_per_tenant_ext.py +++ b/quantum/tests/unit/test_quota_per_tenant_ext.py @@ -213,3 +213,106 @@ class QuotaExtensionTestCase(testlib_api.WebTestCase): class QuotaExtensionTestCaseXML(QuotaExtensionTestCase): fmt = 'xml' + + +class QuotaExtensionCfgTestCase(testlib_api.WebTestCase): + fmt = 'json' + + def setUp(self): + super(QuotaExtensionCfgTestCase, self).setUp() + 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) + cfg.CONF.set_override( + 'quota_items', + ['network', 'subnet', 'port', 'extra1'], + group='QUOTAS') + quota.QUOTAS = quota.QuotaEngine() + quota.register_resources_from_config() + 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') + 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) + super(QuotaExtensionCfgTestCase, self).setUp() + + def tearDown(self): + self._plugin_patcher.stop() + self.api = None + self.plugin = None + db._ENGINE = None + db._MAKER = None + cfg.CONF.reset() + + # Restore the global RESOURCE_ATTRIBUTE_MAP + attributes.RESOURCE_ATTRIBUTE_MAP = self.saved_attr_map + super(QuotaExtensionCfgTestCase, self).tearDown() + + 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, fmt=self.fmt), + extra_environ=env) + quota = self.deserialize(res) + self.assertEqual(10, quota['quota']['network']) + self.assertEqual(10, quota['quota']['subnet']) + self.assertEqual(50, quota['quota']['port']) + self.assertEqual(-1, quota['quota']['extra1']) + + def test_show_quotas_with_admin(self): + tenant_id = 'tenant_id1' + env = {'quantum.context': context.Context('', tenant_id + '2', + is_admin=True)} + res = self.api.get(_get_path('quotas', id=tenant_id, fmt=self.fmt), + extra_environ=env) + self.assertEqual(200, res.status_int) + + def test_show_quotas_without_admin_forbidden(self): + tenant_id = 'tenant_id1' + env = {'quantum.context': context.Context('', tenant_id + '2', + is_admin=False)} + res = self.api.get(_get_path('quotas', id=tenant_id, fmt=self.fmt), + extra_environ=env, expect_errors=True) + self.assertEqual(403, res.status_int) + + def test_update_quotas_forbidden(self): + tenant_id = 'tenant_id1' + quotas = {'quota': {'network': 100}} + res = self.api.put(_get_path('quotas', id=tenant_id, fmt=self.fmt), + self.serialize(quotas), + expect_errors=True) + self.assertEqual(403, res.status_int) + + def test_delete_quotas_forbidden(self): + tenant_id = 'tenant_id1' + env = {'quantum.context': context.Context('', tenant_id, + is_admin=False)} + res = self.api.delete(_get_path('quotas', id=tenant_id, fmt=self.fmt), + extra_environ=env, expect_errors=True) + self.assertEqual(403, res.status_int) + + +class QuotaExtensionCfgTestCaseXML(QuotaExtensionCfgTestCase): + fmt = 'xml'