Fix recursion error in account_quota middleware
There is an infinite loop if multiple quota limits are set and exceeded,
eventually resulting in a 500 response due to a RecursionError ("maximum
recursion depth exceeded").
The issue is the delayed rejection, required to support container_acls.
If any quota is exceeded the middleware needs to return directly,
without proceeding to check other quota settings.
The fix is basically to add a "return self.app". However, there is quite
some redundant code, thus moving this into its own method.
Another test with multiple exceeded quotas has been added, which is
failing without the bugfix.
Closes-Bug: #2118758
Change-Id: I49ec4c5f6c83f36ce1d38f2f1687081c71488286
Signed-off-by: Christian Schwede <cschwede@redhat.com>
This commit is contained in:
@@ -102,6 +102,24 @@ class AccountQuotaMiddleware(object):
|
||||
def __init__(self, app, *args, **kwargs):
|
||||
self.app = app
|
||||
|
||||
def quota_exceeded(self, request, body):
|
||||
# request.environ['swift.authorize'](req) is delayed and not called
|
||||
# immediately to support container acls. However, the middleware should
|
||||
# still return immediately if any quota is exceeded.
|
||||
resp = HTTPRequestEntityTooLarge(body=body)
|
||||
if 'swift.authorize' in request.environ:
|
||||
orig_authorize = request.environ['swift.authorize']
|
||||
|
||||
def reject_authorize(*args, **kwargs):
|
||||
aresp = orig_authorize(*args, **kwargs)
|
||||
if aresp:
|
||||
return aresp
|
||||
return resp
|
||||
request.environ['swift.authorize'] = reject_authorize
|
||||
return self.app
|
||||
else:
|
||||
return resp
|
||||
|
||||
def validate_and_translate_quotas(self, request, quota_type):
|
||||
new_quotas = {}
|
||||
new_quotas[None] = request.headers.get(
|
||||
@@ -209,18 +227,7 @@ class AccountQuotaMiddleware(object):
|
||||
if quota >= 0:
|
||||
new_size = int(account_info['bytes']) + content_length
|
||||
if quota < new_size:
|
||||
resp = HTTPRequestEntityTooLarge(body='Upload exceeds quota.')
|
||||
if 'swift.authorize' in request.environ:
|
||||
orig_authorize = request.environ['swift.authorize']
|
||||
|
||||
def reject_authorize(*args, **kwargs):
|
||||
aresp = orig_authorize(*args, **kwargs)
|
||||
if aresp:
|
||||
return aresp
|
||||
return resp
|
||||
request.environ['swift.authorize'] = reject_authorize
|
||||
else:
|
||||
return resp
|
||||
return self.quota_exceeded(request, "Upload exceeds quota.")
|
||||
|
||||
# Check for quota count violation
|
||||
try:
|
||||
@@ -230,18 +237,7 @@ class AccountQuotaMiddleware(object):
|
||||
if quota >= 0:
|
||||
new_count = int(account_info['total_object_count']) + 1
|
||||
if quota < new_count:
|
||||
resp = HTTPRequestEntityTooLarge(body='Upload exceeds quota.')
|
||||
if 'swift.authorize' in request.environ:
|
||||
orig_authorize = request.environ['swift.authorize']
|
||||
|
||||
def reject_authorize(*args, **kwargs):
|
||||
aresp = orig_authorize(*args, **kwargs)
|
||||
if aresp:
|
||||
return aresp
|
||||
return resp
|
||||
request.environ['swift.authorize'] = reject_authorize
|
||||
else:
|
||||
return resp
|
||||
return self.quota_exceeded(request, "Upload exceeds quota.")
|
||||
|
||||
container_info = get_container_info(request.environ, self.app,
|
||||
swift_source='AQ')
|
||||
@@ -259,19 +255,8 @@ class AccountQuotaMiddleware(object):
|
||||
policy_stats = account_info['storage_policies'].get(policy_idx, {})
|
||||
new_size = int(policy_stats.get('bytes', 0)) + content_length
|
||||
if policy_quota < new_size:
|
||||
resp = HTTPRequestEntityTooLarge(
|
||||
body='Upload exceeds policy quota.')
|
||||
if 'swift.authorize' in request.environ:
|
||||
orig_authorize = request.environ['swift.authorize']
|
||||
|
||||
def reject_authorize(*args, **kwargs):
|
||||
aresp = orig_authorize(*args, **kwargs)
|
||||
if aresp:
|
||||
return aresp
|
||||
return resp
|
||||
request.environ['swift.authorize'] = reject_authorize
|
||||
else:
|
||||
return resp
|
||||
return self.quota_exceeded(
|
||||
request, "Upload exceeds policy quota.")
|
||||
|
||||
# Check quota-count per policy
|
||||
sysmeta_key = 'quota-count-policy-%s' % policy_idx
|
||||
@@ -283,19 +268,8 @@ class AccountQuotaMiddleware(object):
|
||||
policy_stats = account_info['storage_policies'].get(policy_idx, {})
|
||||
new_size = int(policy_stats.get('object_count', 0)) + 1
|
||||
if policy_quota < new_size:
|
||||
resp = HTTPRequestEntityTooLarge(
|
||||
body='Upload exceeds policy quota.')
|
||||
if 'swift.authorize' in request.environ:
|
||||
orig_authorize = request.environ['swift.authorize']
|
||||
|
||||
def reject_authorize(*args, **kwargs):
|
||||
aresp = orig_authorize(*args, **kwargs)
|
||||
if aresp:
|
||||
return aresp
|
||||
return resp
|
||||
request.environ['swift.authorize'] = reject_authorize
|
||||
else:
|
||||
return resp
|
||||
return self.quota_exceeded(
|
||||
request, "Upload exceeds policy quota.")
|
||||
|
||||
return self.app
|
||||
|
||||
|
||||
@@ -318,6 +318,22 @@ class TestAccountQuota(unittest.TestCase):
|
||||
self.assertEqual(res.status_int, 413)
|
||||
self.assertEqual(res.body, b'Upload exceeds quota.')
|
||||
|
||||
def test_exceed_multiple_quota_authorized(self):
|
||||
self.app.register('HEAD', '/v1/a', HTTPOk, {
|
||||
'x-account-bytes-used': '100',
|
||||
'x-account-object-count': '10',
|
||||
'x-account-sysmeta-quota-count': '10',
|
||||
'x-account-sysmeta-quota-bytes': '10'})
|
||||
app = FakeAuthFilter(account_quotas.AccountQuotaMiddleware(self.app))
|
||||
cache = FakeCache(None)
|
||||
req = Request.blank('/v1/a/c/o', method='PUT',
|
||||
headers={'x-auth-token': 'secret',
|
||||
'content-length': '901'},
|
||||
environ={'swift.cache': cache})
|
||||
res = req.get_response(app)
|
||||
self.assertEqual(res.status_int, 413)
|
||||
self.assertEqual(res.body, b'Upload exceeds quota.')
|
||||
|
||||
def test_over_quota_container_create_still_works(self):
|
||||
self.app.register('HEAD', '/v1/a', HTTPOk, {
|
||||
'x-account-bytes-used': '1001',
|
||||
|
||||
Reference in New Issue
Block a user