Skip to content

Commit

Permalink
Wrap quota controller with resource.Resource
Browse files Browse the repository at this point in the history
Fixes bug 1116137

Currently the format of error message returned by quota extension was
different with quantum other resource. Other resource will return as
json(eg, '{"QuantumError": "error message"}'). But quota extension only
return messages without any format.
'quantum.api.v2.resource.Resource' provider error messages processing.
So wrap quota controller with it.

By the way, fix some small stuff:
* Use specific exception 'QuotaTenantNotFound' instead of generic exception.
* Correct error message.
* Use attribute mapping checking the request body.

Change-Id: I71261198aa79e9ed8e0ae672de32552abdbf45c1
  • Loading branch information
soulxu committed Mar 8, 2013
1 parent e0f567f commit 987b1a3
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 113 deletions.
4 changes: 4 additions & 0 deletions quantum/common/exceptions.py
Expand Up @@ -228,6 +228,10 @@ class OverQuota(Conflict):
message = _("Quota exceeded for resources: %(overs)s")


class QuotaMissingTenant(BadRequest):
message = _("Tenant-id was missing from Quota request")


class InvalidQuotaValue(Conflict):
message = _("Change would make usage less than 0 for the following "
"resources: %(unders)s")
Expand Down
79 changes: 31 additions & 48 deletions quantum/extensions/quotasv2.py
Expand Up @@ -19,8 +19,10 @@
import webob

from quantum.api import extensions
from quantum.api.v2.attributes import convert_to_int
from quantum.api.v2 import base
from quantum.common import exceptions
from quantum.api.v2 import resource
from quantum.common import exceptions as q_exc
from quantum.manager import QuantumManager
from quantum.openstack.common import importutils
from quantum import quota
Expand Down Expand Up @@ -49,21 +51,10 @@ def _update_attributes(self):
attr_dict = EXTENDED_ATTRIBUTES_2_0[RESOURCE_COLLECTION]
attr_dict[quota_resource] = {'allow_post': False,
'allow_put': True,
'convert_to': int,
'convert_to': convert_to_int,
'is_visible': True}
self._update_extended_attributes = False

def _get_body(self, request):
body = self._deserialize(request.body,
request.best_match_content_type())
if self._update_extended_attributes:
self._update_attributes()

attr_info = EXTENDED_ATTRIBUTES_2_0[RESOURCE_COLLECTION]
req_body = base.Controller.prepare_request_body(
request.context, body, False, self._resource_name, attr_info)
return req_body

def _get_quotas(self, request, tenant_id):
return self._driver.get_tenant_quotas(
request.context, QUOTAS.resources, tenant_id)
Expand All @@ -73,53 +64,43 @@ def create(self, request, body=None):

def index(self, request):
context = request.context
if not context.is_admin:
raise webob.exc.HTTPForbidden()
self._check_admin(context)
return {self._resource_name + "s":
self._driver.get_all_quotas(context, QUOTAS.resources)}

def tenant(self, request):
"""Retrieve the tenant info in context."""
context = request.context
if not context.tenant_id:
raise webob.exc.HTTPBadRequest('invalid tenant')
raise q_exc.QuotaMissingTenant()
return {'tenant': {'tenant_id': context.tenant_id}}

def show(self, request, id):
context = request.context
tenant_id = id
if not tenant_id:
raise webob.exc.HTTPBadRequest('invalid tenant')
if (tenant_id != context.tenant_id and
not context.is_admin):
raise webob.exc.HTTPForbidden()
return {self._resource_name:
self._get_quotas(request, tenant_id)}

def _check_modification_delete_privilege(self, context, tenant_id):
if not tenant_id:
raise webob.exc.HTTPBadRequest('invalid tenant')
if id != request.context.tenant_id:
self._check_admin(request.context,
reason=_("Non-admin is not authorised "
"to access quotas for another tenant"))
return {self._resource_name: self._get_quotas(request, id)}

def _check_admin(self, context,
reason=_("Only admin can view or configure quota")):
if not context.is_admin:
raise webob.exc.HTTPForbidden()
return tenant_id
raise q_exc.AdminRequired(reason=reason)

def delete(self, request, id):
tenant_id = self._check_modification_delete_privilege(request.context,
id)
self._driver.delete_tenant_quota(request.context, tenant_id)

def update(self, request, id):
tenant_id = self._check_modification_delete_privilege(request.context,
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])
self._driver.update_quota_limit(request.context,
tenant_id,
key,
value)
return {self._resource_name: self._get_quotas(request, tenant_id)}
self._check_admin(request.context)
self._driver.delete_tenant_quota(request.context, id)

def update(self, request, id, body=None):
self._check_admin(request.context)
if self._update_extended_attributes:
self._update_attributes()
body = base.Controller.prepare_request_body(
request.context, body, False, self._resource_name,
EXTENDED_ATTRIBUTES_2_0[RESOURCE_COLLECTION])
for key, value in body[self._resource_name].iteritems():
self._driver.update_quota_limit(request.context, id, key, value)
return {self._resource_name: self._get_quotas(request, id)}


class Quotasv2(extensions.ExtensionDescriptor):
Expand Down Expand Up @@ -151,7 +132,9 @@ def get_updated(cls):
@classmethod
def get_resources(cls):
""" Returns Ext Resources """
controller = QuotaSetsController(QuantumManager.get_plugin())
controller = resource.Resource(
QuotaSetsController(QuantumManager.get_plugin()),
faults=base.FAULT_MAP)
return [extensions.ResourceExtension(
Quotasv2.get_alias(),
controller,
Expand Down
Expand Up @@ -12,7 +12,6 @@
from quantum import manager
from quantum.plugins.linuxbridge.db import l2network_db_v2
from quantum import quota
from quantum.tests import base
from quantum.tests.unit import test_api_v2
from quantum.tests.unit import test_extensions
from quantum.tests.unit import testlib_api
Expand All @@ -24,7 +23,6 @@


class QuotaExtensionTestCase(testlib_api.WebTestCase):
fmt = 'json'

def setUp(self):
super(QuotaExtensionTestCase, self).setUp()
Expand All @@ -47,10 +45,6 @@ def setUp(self):

# Update the plugin and extensions path
cfg.CONF.set_override('core_plugin', TARGET_PLUGIN)
cfg.CONF.set_override(
'quota_driver',
'quantum.db.quota_db.DbQuotaDriver',
group='QUOTAS')
cfg.CONF.set_override(
'quota_items',
['network', 'subnet', 'port', 'extra1'],
Expand All @@ -68,7 +62,6 @@ def setUp(self):
app = config.load_paste_app('extensions_test_app')
ext_middleware = extensions.ExtensionMiddleware(app, ext_mgr=ext_mgr)
self.api = webtest.TestApp(ext_middleware)
super(QuotaExtensionTestCase, self).setUp()

def tearDown(self):
self._plugin_patcher.stop()
Expand All @@ -82,6 +75,17 @@ def tearDown(self):
attributes.RESOURCE_ATTRIBUTE_MAP = self.saved_attr_map
super(QuotaExtensionTestCase, self).tearDown()


class QuotaExtensionDbTestCase(QuotaExtensionTestCase):
fmt = 'json'

def setUp(self):
cfg.CONF.set_override(
'quota_driver',
'quantum.db.quota_db.DbQuotaDriver',
group='QUOTAS')
super(QuotaExtensionDbTestCase, self).setUp()

def test_quotas_loaded_right(self):
res = self.api.get(_get_path('quotas', fmt=self.fmt))
quota = self.deserialize(res)
Expand All @@ -106,16 +110,50 @@ def test_show_quotas_with_admin(self):
res = self.api.get(_get_path('quotas', id=tenant_id, fmt=self.fmt),
extra_environ=env)
self.assertEqual(200, res.status_int)
quota = self.deserialize(res)
self.assertEqual(10, quota['quota']['network'])
self.assertEqual(10, quota['quota']['subnet'])
self.assertEqual(50, quota['quota']['port'])

def test_show_quotas_without_admin_forbidden(self):
def test_show_quotas_without_admin_forbidden_returns_403(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_without_admin_forbidden(self):
def test_show_quotas_with_owner_tenant(self):
tenant_id = 'tenant_id1'
env = {'quantum.context': context.Context('', tenant_id,
is_admin=False)}
res = self.api.get(_get_path('quotas', id=tenant_id, fmt=self.fmt),
extra_environ=env)
self.assertEqual(200, res.status_int)
quota = self.deserialize(res)
self.assertEqual(10, quota['quota']['network'])
self.assertEqual(10, quota['quota']['subnet'])
self.assertEqual(50, quota['quota']['port'])

def test_list_quotas_with_admin(self):
tenant_id = 'tenant_id1'
env = {'quantum.context': context.Context('', tenant_id,
is_admin=True)}
res = self.api.get(_get_path('quotas', fmt=self.fmt),
extra_environ=env)
self.assertEqual(200, res.status_int)
quota = self.deserialize(res)
self.assertEqual([], quota['quotas'])

def test_list_quotas_without_admin_forbidden_returns_403(self):
tenant_id = 'tenant_id1'
env = {'quantum.context': context.Context('', tenant_id,
is_admin=False)}
res = self.api.get(_get_path('quotas', fmt=self.fmt),
extra_environ=env, expect_errors=True)
self.assertEqual(403, res.status_int)

def test_update_quotas_without_admin_forbidden_returns_403(self):
tenant_id = 'tenant_id1'
env = {'quantum.context': context.Context('', tenant_id,
is_admin=False)}
Expand All @@ -125,6 +163,26 @@ def test_update_quotas_without_admin_forbidden(self):
expect_errors=True)
self.assertEqual(403, res.status_int)

def test_update_quotas_with_non_integer_returns_400(self):
tenant_id = 'tenant_id1'
env = {'quantum.context': context.Context('', tenant_id,
is_admin=True)}
quotas = {'quota': {'network': 'abc'}}
res = self.api.put(_get_path('quotas', id=tenant_id, fmt=self.fmt),
self.serialize(quotas), extra_environ=env,
expect_errors=True)
self.assertEqual(400, res.status_int)

def test_update_quotas_with_non_support_resource_returns_400(self):
tenant_id = 'tenant_id1'
env = {'quantum.context': context.Context('', tenant_id,
is_admin=True)}
quotas = {'quota': {'abc': 100}}
res = self.api.put(_get_path('quotas', id=tenant_id, fmt=self.fmt),
self.serialize(quotas), extra_environ=env,
expect_errors=True)
self.assertEqual(400, res.status_int)

def test_update_quotas_with_admin(self):
tenant_id = 'tenant_id1'
env = {'quantum.context': context.Context('', tenant_id + '2',
Expand All @@ -138,6 +196,8 @@ def test_update_quotas_with_admin(self):
extra_environ=env2)
quota = self.deserialize(res)
self.assertEqual(100, quota['quota']['network'])
self.assertEqual(10, quota['quota']['subnet'])
self.assertEqual(50, quota['quota']['port'])

def test_update_attributes(self):
tenant_id = 'tenant_id1'
Expand All @@ -161,15 +221,15 @@ def test_delete_quotas_with_admin(self):
extra_environ=env)
self.assertEqual(204, res.status_int)

def test_delete_quotas_without_admin_forbidden(self):
def test_delete_quotas_without_admin_forbidden_returns_403(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)

def test_quotas_loaded_bad(self):
def test_quotas_loaded_bad_returns_404(self):
try:
res = self.api.get(_get_path('quotas'), expect_errors=True)
self.assertEqual(404, res.status_int)
Expand Down Expand Up @@ -210,65 +270,30 @@ def test_quotas_limit_check_with_invalid_quota_value(self):
tenant_id,
network=-1)

def test_quotas_get_tenant_from_request_context(self):
tenant_id = 'tenant_id1'
env = {'quantum.context': context.Context('', tenant_id,
is_admin=True)}
res = self.api.get(_get_path('quotas/tenant', fmt=self.fmt),
extra_environ=env)
self.assertEqual(200, res.status_int)
quota = self.deserialize(res)
self.assertEqual(quota['tenant']['tenant_id'], tenant_id)

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()
def test_quotas_get_tenant_from_empty_request_context_returns_400(self):
env = {'quantum.context': context.Context('', '',
is_admin=True)}
res = self.api.get(_get_path('quotas/tenant', fmt=self.fmt),
extra_environ=env, expect_errors=True)
self.assertEqual(400, res.status_int)

# 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()
class QuotaExtensionDbTestCaseXML(QuotaExtensionDbTestCase):
fmt = 'xml'

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()
class QuotaExtensionCfgTestCase(QuotaExtensionTestCase):
fmt = 'json'

def test_quotas_default_values(self):
tenant_id = 'tenant_id1'
Expand Down

0 comments on commit 987b1a3

Please sign in to comment.