Two small account-quota fixes

First: even if a user has exceeded their account quota, they should be
able to make object POST requests. Updating an object's metadata isn't
going to make them any more over quota, so should be allowed.

Second: don't bother with the reseller_admin check for container or
object requests. If I send the header X-Account-Meta-Quota-Bytes: 100
on e.g. an object PUT request, the proxy will (rightly) ignore it. Now
account-quotas does too.

Change-Id: I970a76349659acdd8229a324bd33bfe7fe7261a4
This commit is contained in:
Samuel Merritt 2013-09-23 10:05:34 -07:00
parent 10bb74a872
commit d5fcc9aaf3
2 changed files with 65 additions and 13 deletions

View File

@ -67,12 +67,22 @@ class AccountQuotaMiddleware(object):
return self.app
try:
ver, acc, cont, obj = request.split_path(2, 4, rest_with_last=True)
ver, account, container, obj = request.split_path(
2, 4, rest_with_last=True)
except ValueError:
return self.app
new_quota = request.headers.get('X-Account-Meta-Quota-Bytes')
remove_quota = request.headers.get('X-Remove-Account-Meta-Quota-Bytes')
if not container:
# account request, so we pay attention to the quotas
new_quota = request.headers.get(
'X-Account-Meta-Quota-Bytes')
remove_quota = request.headers.get(
'X-Remove-Account-Meta-Quota-Bytes')
else:
# container or object request; even if the quota headers are set
# in the request, they're meaningless
new_quota = remove_quota = None
if remove_quota:
new_quota = 0 # X-Remove dominates if both are present
@ -85,11 +95,14 @@ class AccountQuotaMiddleware(object):
if new_quota is not None:
return HTTPForbidden()
if obj and request.method == "POST":
return self.app
copy_from = request.headers.get('X-Copy-From')
content_length = (request.content_length or 0)
if obj and copy_from:
path = '/' + ver + '/' + acc + '/' + copy_from.lstrip('/')
path = '/' + ver + '/' + account + '/' + copy_from.lstrip('/')
object_info = get_object_info(request.environ, self.app, path)
if not object_info or not object_info['length']:
content_length = 0

View File

@ -86,6 +86,33 @@ class TestAccountQuota(unittest.TestCase):
res = req.get_response(app)
self.assertEquals(res.status_int, 200)
def test_obj_request_ignores_attempt_to_set_quotas(self):
# If you try to set X-Account-Meta-* on an object, it's ignored, so
# the quota middleware shouldn't complain about it even if we're not a
# reseller admin.
headers = [('x-account-bytes-used', '1000')]
app = account_quotas.AccountQuotaMiddleware(FakeApp(headers))
cache = FakeCache(None)
req = Request.blank('/v1/a/c/o',
headers={'X-Account-Meta-Quota-Bytes': '99999'},
environ={'REQUEST_METHOD': 'PUT',
'swift.cache': cache})
res = req.get_response(app)
self.assertEquals(res.status_int, 200)
def test_container_request_ignores_attempt_to_set_quotas(self):
# As with an object, if you try to set X-Account-Meta-* on a
# container, it's ignored.
headers = [('x-account-bytes-used', '1000')]
app = account_quotas.AccountQuotaMiddleware(FakeApp(headers))
cache = FakeCache(None)
req = Request.blank('/v1/a/c',
headers={'X-Account-Meta-Quota-Bytes': '99999'},
environ={'REQUEST_METHOD': 'PUT',
'swift.cache': cache})
res = req.get_response(app)
self.assertEquals(res.status_int, 200)
def test_exceed_bytes_quota(self):
headers = [('x-account-bytes-used', '1000'),
('x-account-meta-quota-bytes', '0')]
@ -97,6 +124,18 @@ class TestAccountQuota(unittest.TestCase):
res = req.get_response(app)
self.assertEquals(res.status_int, 413)
def test_over_quota_obj_post_still_works(self):
headers = [('x-account-bytes-used', '1001'),
('x-account-meta-quota-bytes', '1000')]
app = account_quotas.AccountQuotaMiddleware(FakeApp(headers))
cache = FakeCache(None)
req = Request.blank('/v1/a/c/o',
environ={'REQUEST_METHOD': 'POST',
'HTTP_X_OBJECT_META_BERT': 'ernie',
'swift.cache': cache})
res = req.get_response(app)
self.assertEquals(res.status_int, 200)
def test_exceed_bytes_quota_copy_from(self):
headers = [('x-account-bytes-used', '500'),
('x-account-meta-quota-bytes', '1000'),
@ -139,7 +178,7 @@ class TestAccountQuota(unittest.TestCase):
('x-account-meta-quota-bytes', '0')]
app = account_quotas.AccountQuotaMiddleware(FakeApp(headers))
cache = FakeCache(None)
req = Request.blank('/v1/a/c/o',
req = Request.blank('/v1/a',
environ={'REQUEST_METHOD': 'PUT',
'swift.cache': cache,
'reseller_request': True})
@ -194,7 +233,7 @@ class TestAccountQuota(unittest.TestCase):
headers = [('x-account-bytes-used', '0'), ]
app = account_quotas.AccountQuotaMiddleware(FakeApp(headers))
cache = FakeCache(None)
req = Request.blank('/v1/a/c',
req = Request.blank('/v1/a',
environ={'REQUEST_METHOD': 'POST',
'swift.cache': cache,
'HTTP_X_ACCOUNT_META_QUOTA_BYTES': 'abc',
@ -206,7 +245,7 @@ class TestAccountQuota(unittest.TestCase):
headers = [('x-account-bytes-used', '0'), ]
app = account_quotas.AccountQuotaMiddleware(FakeApp(headers))
cache = FakeCache(None)
req = Request.blank('/v1/a/c',
req = Request.blank('/v1/a',
environ={'REQUEST_METHOD': 'POST',
'swift.cache': cache,
'HTTP_X_ACCOUNT_META_QUOTA_BYTES': '100'})
@ -217,7 +256,7 @@ class TestAccountQuota(unittest.TestCase):
headers = [('x-account-bytes-used', '0'), ]
app = account_quotas.AccountQuotaMiddleware(FakeApp(headers))
cache = FakeCache(None)
req = Request.blank('/v1/a/c',
req = Request.blank('/v1/a',
environ={'REQUEST_METHOD': 'POST',
'swift.cache': cache,
'HTTP_X_ACCOUNT_META_QUOTA_BYTES': '100',
@ -229,7 +268,7 @@ class TestAccountQuota(unittest.TestCase):
headers = [('x-account-bytes-used', '0'), ]
app = account_quotas.AccountQuotaMiddleware(FakeApp(headers))
cache = FakeCache(None)
req = Request.blank('/v1/a/c',
req = Request.blank('/v1/a',
environ={'REQUEST_METHOD': 'POST',
'swift.cache': cache,
'HTTP_X_ACCOUNT_META_QUOTA_BYTES': ''})
@ -240,7 +279,7 @@ class TestAccountQuota(unittest.TestCase):
headers = [('x-account-bytes-used', '0'), ]
app = account_quotas.AccountQuotaMiddleware(FakeApp(headers))
cache = FakeCache(None)
req = Request.blank('/v1/a/c', environ={
req = Request.blank('/v1/a', environ={
'REQUEST_METHOD': 'POST',
'swift.cache': cache,
'HTTP_X_REMOVE_ACCOUNT_META_QUOTA_BYTES': 'True'})
@ -250,7 +289,7 @@ class TestAccountQuota(unittest.TestCase):
def test_delete_quotas_reseller(self):
headers = [('x-account-bytes-used', '0'), ]
app = account_quotas.AccountQuotaMiddleware(FakeApp(headers))
req = Request.blank('/v1/a/c',
req = Request.blank('/v1/a',
environ={'REQUEST_METHOD': 'POST',
'HTTP_X_ACCOUNT_META_QUOTA_BYTES': '',
'reseller_request': True})
@ -261,7 +300,7 @@ class TestAccountQuota(unittest.TestCase):
headers = [('x-account-bytes-used', '0'), ]
app = account_quotas.AccountQuotaMiddleware(FakeApp(headers))
cache = FakeCache(None)
req = Request.blank('/v1/a/c', environ={
req = Request.blank('/v1/a', environ={
'REQUEST_METHOD': 'POST',
'swift.cache': cache,
'HTTP_X_REMOVE_ACCOUNT_META_QUOTA_BYTES': 'True',
@ -277,7 +316,7 @@ class TestAccountQuota(unittest.TestCase):
environ={'REQUEST_METHOD': 'PUT',
'swift.cache': cache})
res = req.get_response(app)
#Response code of 200 because authentication itself is not done here
# Response code of 200 because authentication itself is not done here
self.assertEquals(res.status_int, 200)