account_quota: migrate quota_bytes and quota_count to the sysmeta namespace

Account quota metadata such as quota_bytes and quota_count are stored in
the `meta` namespace which users have access to. However, this should be
only available to reseller admins.

This patch adds support for writing the quota metadata to `sysmeta`
namespace, so that it is not accessible by users. The account policy
quota is already using `sysmeta` and has the namespace
`X-Account-Quota-*`, so we are following this pattern.

If present, `X-Account-Quota-Bytes` is always preferred. However, in
order to maintain backwards compatibility, `X-Account-Meta-Quota-Bytes`
will still be honoured if it exists and `X-Account-Quota-Bytes` is not
present.

This also adds some new "legacy" tests to validate backwards
compatibility.

Co-authored-by: Azmain Adib <adib1905@gmail.com>
Co-authored-by: Daanish Khan <daanish1337@gmail.com>
Co-authored-by: Mohammed Al-Jawaheri <mjawaheri02@gmail.com>
Co-authored-by: Nada El-Mestkawy <nadamaged05@gmail.com>
Co-authored-by: Tra Bui <trabui.0517@gmail.com>
Co-authored-by: Chris Smart <distroguy@gmail.com>
Change-Id: Icf7b26023ab5b84136ceaa103fa2797534320f1a
This commit is contained in:
Daanish Khan 2024-09-06 15:41:25 +10:00 committed by Tim Burke
parent cd0fe25da1
commit 4eefae2482
3 changed files with 222 additions and 75 deletions

View File

@ -24,12 +24,22 @@ quota
+---------------------------------------------+-------------------------------+
|Metadata | Use |
+=============================================+===============================+
| X-Account-Meta-Quota-Bytes | Maximum overall bytes stored |
| X-Account-Meta-Quota-Bytes (obsoleted) | Maximum overall bytes stored |
| | in account across containers. |
+---------------------------------------------+-------------------------------+
| X-Account-Meta-Quota-Count | Maximum object count under |
| X-Account-Quota-Bytes | Maximum overall bytes stored |
| | in account across containers. |
+---------------------------------------------+-------------------------------+
| X-Account-Quota-Bytes-Policy-<policyname> | Maximum overall bytes stored |
| | in account across containers, |
| | for the given policy. |
+---------------------------------------------+-------------------------------+
| X-Account-Quota-Count | Maximum object count under |
| | account. |
+---------------------------------------------+-------------------------------+
| X-Account-Quota-Count-Policy-<policyname> | Maximum object count under |
| | account, for the given policy.|
+---------------------------------------------+-------------------------------+
Write requests to those metadata entries are only permitted for resellers.
@ -95,9 +105,9 @@ class AccountQuotaMiddleware(object):
def validate_and_translate_quotas(self, request, quota_type):
new_quotas = {}
new_quotas[None] = request.headers.get(
'X-Account-Meta-%s' % quota_type)
'X-Account-%s' % quota_type)
if request.headers.get(
'X-Remove-Account-Meta-%s' % quota_type):
'X-Remove-Account-%s' % quota_type):
new_quotas[None] = '' # X-Remove dominates if both are present
for policy in POLICIES:
@ -114,8 +124,9 @@ class AccountQuotaMiddleware(object):
raise HTTPBadRequest()
for idx, quota in new_quotas.items():
if idx is None:
continue # For legacy reasons, it's in user meta
hdr = 'X-Account-Sysmeta-%s-Policy-%d' % (quota_type, idx)
hdr = 'X-Account-Sysmeta-%s' % quota_type
else:
hdr = 'X-Account-Sysmeta-%s-Policy-%d' % (quota_type, idx)
request.headers[hdr] = quota
elif any(quota is not None for quota in new_quotas.values()):
# deny quota set for non-reseller
@ -123,11 +134,29 @@ class AccountQuotaMiddleware(object):
def handle_account(self, request):
if request.method in ("POST", "PUT"):
# Support old meta format
for legacy_header in [
'X-Account-Meta-Quota-Bytes',
'X-Remove-Account-Meta-Quota-Bytes',
]:
new_header = legacy_header.replace('-Meta-', '-')
legacy_value = request.headers.get(legacy_header)
if legacy_value is not None and not \
request.headers.get(new_header):
request.headers[new_header] = legacy_value
# account request, so we pay attention to the quotas
self.validate_and_translate_quotas(request, "Quota-Bytes")
self.validate_and_translate_quotas(request, "Quota-Count")
resp = request.get_response(self.app)
# Non-resellers can't update quotas, but they *can* see them
# Global quotas
postfixes = ('Quota-Bytes', 'Quota-Count')
for postfix in postfixes:
value = resp.headers.get('X-Account-Sysmeta-%s' % postfix)
if value:
resp.headers['X-Account-%s' % postfix] = value
# Per policy quotas
for policy in POLICIES:
infixes = ('Quota-Bytes-Policy', 'Quota-Count-Policy')
for infix in infixes:
@ -170,7 +199,11 @@ class AccountQuotaMiddleware(object):
# Check for quota byte violation
try:
quota = int(account_info['meta'].get('quota-bytes', -1))
quota = int(
account_info["sysmeta"].get(
"quota-bytes", account_info["meta"].get("quota-bytes", -1)
)
)
except ValueError:
quota = -1
if quota >= 0:
@ -191,7 +224,7 @@ class AccountQuotaMiddleware(object):
# Check for quota count violation
try:
quota = int(account_info['meta'].get('quota-count', -1))
quota = int(account_info['sysmeta'].get('quota-count', -1))
except ValueError:
quota = -1
if quota >= 0:

View File

@ -968,10 +968,9 @@ class TestAccountQuotas(unittest.TestCase):
resp.read()
self.assertEqual(resp.status, 204)
def test_admin_can_set_and_remove_user_quota(self):
def _test_admin_can_set_and_remove_user_quota(self, quota_header):
if tf.skip_if_no_reseller_admin:
raise SkipTest('No admin user configured')
quota_header = 'X-Account-Meta-Quota-Bytes'
def get_current_quota():
def head(url, token, parsed, conn):
@ -984,7 +983,9 @@ class TestAccountQuotas(unittest.TestCase):
resp = retry(head)
resp.read()
self.assertEqual(resp.status, 204)
return resp.headers.get(quota_header)
# Non-user-meta is authoritative now
return resp.headers.get(quota_header.replace('-Meta-', '-'),
resp.headers.get(quota_header))
original_quota = get_current_quota()
@ -1008,6 +1009,14 @@ class TestAccountQuotas(unittest.TestCase):
finally:
self._check_admin_can_post({quota_header: original_quota or ''})
def test_admin_can_set_and_remove_user_quota_legacy(self):
self._test_admin_can_set_and_remove_user_quota(
'X-Account-Meta-Quota-Bytes')
def test_admin_can_set_and_remove_user_quota(self):
self._test_admin_can_set_and_remove_user_quota(
'X-Account-Quota-Bytes')
def test_admin_can_set_and_remove_user_policy_quota(self):
if tf.skip_if_no_reseller_admin:
raise SkipTest('No admin user configured')

View File

@ -109,7 +109,7 @@ class TestAccountQuota(unittest.TestCase):
# activation of the account-quota middleware
self.app.register('HEAD', '/v1/a', HTTPOk, {
'x-account-bytes-used': '1000',
'x-account-meta-quota-bytes': 'pasty-plastogene'})
'x-account-sysmeta-quota-bytes': 'pasty-plastogene'})
app = account_quotas.AccountQuotaMiddleware(self.app)
cache = FakeCache(None)
req = Request.blank('/v1/a/c/o',
@ -118,7 +118,7 @@ class TestAccountQuota(unittest.TestCase):
res = req.get_response(app)
self.assertEqual(res.status_int, 200)
def test_exceed_bytes_quota(self):
def test_exceed_bytes_quota_legacy(self):
self.app.register('HEAD', '/v1/a', HTTPOk, {
'x-account-bytes-used': '1000',
'x-account-meta-quota-bytes': '0'})
@ -131,13 +131,26 @@ class TestAccountQuota(unittest.TestCase):
self.assertEqual(res.status_int, 413)
self.assertEqual(res.body, b'Upload exceeds quota.')
def test_exceed_bytes_quota(self):
self.app.register('HEAD', '/v1/a', HTTPOk, {
'x-account-bytes-used': '1000',
'x-account-sysmeta-quota-bytes': '0'})
app = account_quotas.AccountQuotaMiddleware(self.app)
cache = FakeCache(None)
req = Request.blank('/v1/a/c/o',
environ={'REQUEST_METHOD': 'PUT',
'swift.cache': cache})
res = req.get_response(app)
self.assertEqual(res.status_int, 413)
self.assertEqual(res.body, b'Upload exceeds quota.')
@patch_policies
def test_exceed_per_policy_quota(self):
self.app.register('HEAD', '/v1/a', HTTPOk, {
'x-account-bytes-used': '100',
'x-account-storage-policy-unu-bytes-used': '100',
'x-account-sysmeta-quota-bytes-policy-1': '10',
'x-account-meta-quota-bytes': '1000'})
'x-account-sysmeta-quota-bytes': '1000'})
app = account_quotas.AccountQuotaMiddleware(self.app)
cache = FakeCache(None)
req = Request.blank('/v1/a/c/o',
@ -148,13 +161,15 @@ class TestAccountQuota(unittest.TestCase):
self.assertEqual(res.body, b'Upload exceeds policy quota.')
@patch_policies
def test_policy_quota_translation(self):
def test_policy_quota_translation_legacy_loses(self):
# if we have both meta (legacy) and sysmeta, ensure sysmeta wins
def do_test(method):
self.app.register(method, '/v1/a', HTTPOk, {
'x-account-bytes-used': '100',
'x-account-storage-policy-unu-bytes-used': '100',
'x-account-sysmeta-quota-bytes-policy-1': '10',
'x-account-meta-quota-bytes': '1000'})
'x-account-sysmeta-quota-bytes': '1000',
'x-account-meta-quota-bytes': '2000'})
app = account_quotas.AccountQuotaMiddleware(self.app)
cache = FakeCache(None)
req = Request.blank('/v1/a', method=method, environ={
@ -162,7 +177,37 @@ class TestAccountQuota(unittest.TestCase):
res = req.get_response(app)
self.assertEqual(res.status_int, 200)
self.assertEqual(res.headers.get(
'X-Account-Meta-Quota-Bytes'), '1000')
'X-Account-Quota-Bytes'), '1000')
self.assertEqual(res.headers.get(
'X-Account-Meta-Quota-Bytes'), '2000')
self.assertEqual(res.headers.get(
'X-Account-Sysmeta-Quota-Bytes-Policy-1'), '10')
self.assertEqual(res.headers.get(
'X-Account-Quota-Bytes-Policy-Unu'), '10')
self.assertEqual(res.headers.get(
'X-Account-Storage-Policy-Unu-Bytes-Used'), '100')
do_test('GET')
do_test('HEAD')
@patch_policies
def test_policy_quota_translation(self):
def do_test(method):
self.app.register(method, '/v1/a', HTTPOk, {
'x-account-bytes-used': '100',
'x-account-storage-policy-unu-bytes-used': '100',
'x-account-sysmeta-quota-bytes-policy-1': '10',
'x-account-sysmeta-quota-bytes': '1000'})
app = account_quotas.AccountQuotaMiddleware(self.app)
cache = FakeCache(None)
req = Request.blank('/v1/a', method=method, environ={
'swift.cache': cache})
res = req.get_response(app)
self.assertEqual(res.status_int, 200)
self.assertEqual(res.headers.get(
'X-Account-Sysmeta-Quota-Bytes'), '1000')
self.assertEqual(res.headers.get(
'X-Account-Quota-Bytes'), '1000')
self.assertEqual(res.headers.get(
'X-Account-Sysmeta-Quota-Bytes-Policy-1'), '10')
self.assertEqual(res.headers.get(
@ -176,7 +221,7 @@ class TestAccountQuota(unittest.TestCase):
def test_exceed_quota_not_authorized(self):
self.app.register('HEAD', '/v1/a', HTTPOk, {
'x-account-bytes-used': '1000',
'x-account-meta-quota-bytes': '0'})
'x-account-sysmeta-quota-bytes': '0'})
app = FakeAuthFilter(account_quotas.AccountQuotaMiddleware(self.app))
cache = FakeCache(None)
req = Request.blank('/v1/a/c/o', method='PUT',
@ -185,7 +230,7 @@ class TestAccountQuota(unittest.TestCase):
res = req.get_response(app)
self.assertEqual(res.status_int, 403)
def test_exceed_quota_authorized(self):
def test_exceed_quota_authorized_legacy(self):
self.app.register('HEAD', '/v1/a', HTTPOk, {
'x-account-bytes-used': '1000',
'x-account-meta-quota-bytes': '0'})
@ -197,10 +242,22 @@ class TestAccountQuota(unittest.TestCase):
res = req.get_response(app)
self.assertEqual(res.status_int, 413)
def test_exceed_quota_authorized(self):
self.app.register('HEAD', '/v1/a', HTTPOk, {
'x-account-bytes-used': '1000',
'x-account-sysmeta-quota-bytes': '0'})
app = FakeAuthFilter(account_quotas.AccountQuotaMiddleware(self.app))
cache = FakeCache(None)
req = Request.blank('/v1/a/c/o', method='PUT',
headers={'x-auth-token': 'secret'},
environ={'swift.cache': cache})
res = req.get_response(app)
self.assertEqual(res.status_int, 413)
def test_under_quota_not_authorized(self):
self.app.register('HEAD', '/v1/a', HTTPOk, {
'x-account-bytes-used': '0',
'x-account-meta-quota-bytes': '1000'})
'x-account-sysmeta-quota-bytes': '1000'})
app = FakeAuthFilter(account_quotas.AccountQuotaMiddleware(self.app))
cache = FakeCache(None)
req = Request.blank('/v1/a/c/o', method='PUT',
@ -209,7 +266,7 @@ class TestAccountQuota(unittest.TestCase):
res = req.get_response(app)
self.assertEqual(res.status_int, 403)
def test_under_quota_authorized(self):
def test_under_quota_authorized_legacy(self):
self.app.register('HEAD', '/v1/a', HTTPOk, {
'x-account-bytes-used': '0',
'x-account-meta-quota-bytes': '1000'})
@ -221,10 +278,22 @@ class TestAccountQuota(unittest.TestCase):
res = req.get_response(app)
self.assertEqual(res.status_int, 200)
def test_exceed_quota_bytes_on_empty_account_not_authorized(self):
def test_under_quota_authorized(self):
self.app.register('HEAD', '/v1/a', HTTPOk, {
'x-account-bytes-used': '0',
'x-account-meta-quota-bytes': '10'})
'x-account-sysmeta-quota-bytes': '1000'})
app = FakeAuthFilter(account_quotas.AccountQuotaMiddleware(self.app))
cache = FakeCache(None)
req = Request.blank('/v1/a/c/o', method='PUT',
headers={'x-auth-token': 'secret'},
environ={'swift.cache': cache})
res = req.get_response(app)
self.assertEqual(res.status_int, 200)
def test_exceed_quota_bytes_on_empty_account_authorized(self):
self.app.register('HEAD', '/v1/a', HTTPOk, {
'x-account-bytes-used': '0',
'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',
@ -235,10 +304,10 @@ class TestAccountQuota(unittest.TestCase):
self.assertEqual(res.status_int, 413)
self.assertEqual(res.body, b'Upload exceeds quota.')
def test_exceed_quota_bytes_not_authorized(self):
def test_exceed_quota_bytes_authorized(self):
self.app.register('HEAD', '/v1/a', HTTPOk, {
'x-account-bytes-used': '100',
'x-account-meta-quota-bytes': '1000'})
'x-account-sysmeta-quota-bytes': '1000'})
app = FakeAuthFilter(account_quotas.AccountQuotaMiddleware(self.app))
cache = FakeCache(None)
req = Request.blank('/v1/a/c/o', method='PUT',
@ -252,7 +321,7 @@ class TestAccountQuota(unittest.TestCase):
def test_over_quota_container_create_still_works(self):
self.app.register('HEAD', '/v1/a', HTTPOk, {
'x-account-bytes-used': '1001',
'x-account-meta-quota-bytes': '1000'})
'x-account-sysmeta-quota-bytes': '1000'})
self.app.register('PUT', '/v1/a/new_container', HTTPOk, {})
app = account_quotas.AccountQuotaMiddleware(self.app)
cache = FakeCache(None)
@ -266,7 +335,7 @@ class TestAccountQuota(unittest.TestCase):
def test_over_quota_container_post_still_works(self):
self.app.register('HEAD', '/v1/a', HTTPOk, {
'x-account-bytes-used': '1001',
'x-account-meta-quota-bytes': '1000'})
'x-account-sysmeta-quota-bytes': '1000'})
self.app.register('POST', '/v1/a/new_container', HTTPOk, {})
app = account_quotas.AccountQuotaMiddleware(self.app)
cache = FakeCache(None)
@ -280,7 +349,7 @@ class TestAccountQuota(unittest.TestCase):
def test_over_quota_obj_post_still_works(self):
self.app.register('HEAD', '/v1/a', HTTPOk, {
'x-account-bytes-used': '1001',
'x-account-meta-quota-bytes': '1000'})
'x-account-sysmeta-quota-bytes': '1000'})
self.app.register('POST', '/v1/a/c/o', HTTPOk, {})
app = account_quotas.AccountQuotaMiddleware(self.app)
cache = FakeCache(None)
@ -294,7 +363,7 @@ class TestAccountQuota(unittest.TestCase):
def test_exceed_bytes_quota_reseller(self):
self.app.register('HEAD', '/v1/a', HTTPOk, {
'x-account-bytes-used': '1000',
'x-account-meta-quota-bytes': '0'})
'x-account-sysmeta-quota-bytes': '0'})
self.app.register('PUT', '/v1/a', HTTPOk, {})
app = account_quotas.AccountQuotaMiddleware(self.app)
cache = FakeCache(None)
@ -308,7 +377,7 @@ class TestAccountQuota(unittest.TestCase):
def test_exceed_bytes_quota_reseller_copy_from(self):
self.app.register('HEAD', '/v1/a', HTTPOk, {
'x-account-bytes-used': '500',
'x-account-meta-quota-bytes': '1000'})
'x-account-sysmeta-quota-bytes': '1000'})
self.app.register('GET', '/v1/a/c2/o2', HTTPOk, {
'content-length': '1000'}, b'a' * 1000)
app = copy.filter_factory({})(
@ -325,7 +394,7 @@ class TestAccountQuota(unittest.TestCase):
def test_exceed_bytes_quota_reseller_copy_verb(self):
self.app.register('HEAD', '/v1/a', HTTPOk, {
'x-account-bytes-used': '500',
'x-account-meta-quota-bytes': '1000'})
'x-account-sysmeta-quota-bytes': '1000'})
self.app.register('GET', '/v1/a/c2/o2', HTTPOk, {
'content-length': '1000'}, b'a' * 1000)
app = copy.filter_factory({})(
@ -361,7 +430,7 @@ class TestAccountQuota(unittest.TestCase):
def test_not_exceed_bytes_quota(self):
self.app.register('HEAD', '/v1/a', HTTPOk, {
'x-account-bytes-used': '1000',
'x-account-meta-quota-bytes': '2000'})
'x-account-sysmeta-quota-bytes': '2000'})
app = account_quotas.AccountQuotaMiddleware(self.app)
cache = FakeCache(None)
req = Request.blank('/v1/a/c/o',
@ -376,7 +445,7 @@ class TestAccountQuota(unittest.TestCase):
req = Request.blank('/v1/a',
environ={'REQUEST_METHOD': 'POST',
'swift.cache': cache,
'HTTP_X_ACCOUNT_META_QUOTA_BYTES': 'abc',
'HTTP_X_ACCOUNT_QUOTA_BYTES': 'abc',
'reseller_request': True})
res = req.get_response(app)
self.assertEqual(res.status_int, 400)
@ -395,13 +464,13 @@ class TestAccountQuota(unittest.TestCase):
self.assertEqual(res.status_int, 400)
self.assertEqual(self.app.calls, [])
def test_valid_quotas_admin(self):
def test_valid_quotas_non_admin_fails(self):
app = account_quotas.AccountQuotaMiddleware(self.app)
cache = FakeCache(None)
req = Request.blank('/v1/a',
environ={'REQUEST_METHOD': 'POST',
'swift.cache': cache,
'HTTP_X_ACCOUNT_META_QUOTA_BYTES': '100'})
'HTTP_X_ACCOUNT_QUOTA_BYTES': '100'})
res = req.get_response(app)
self.assertEqual(res.status_int, 403)
self.assertEqual(self.app.calls, [])
@ -418,7 +487,7 @@ class TestAccountQuota(unittest.TestCase):
self.assertEqual(res.status_int, 403)
self.assertEqual(self.app.calls, [])
def test_valid_quotas_reseller(self):
def test_valid_quotas_reseller_legacy(self):
app = account_quotas.AccountQuotaMiddleware(self.app)
cache = FakeCache(None)
req = Request.blank('/v1/a',
@ -430,7 +499,42 @@ class TestAccountQuota(unittest.TestCase):
self.assertEqual(res.status_int, 200)
self.assertEqual(self.app.calls_with_headers, [
('POST', '/v1/a', {'Host': 'localhost:80',
'X-Account-Meta-Quota-Bytes': '100'})])
'X-Account-Quota-Bytes': '100',
'X-Account-Meta-Quota-Bytes': '100',
'X-Account-Sysmeta-Quota-Bytes': '100'})])
def test_valid_quotas_reseller_legacy_loses(self):
# if we have both meta (legacy) and sysmeta, ensure sysmeta wins
app = account_quotas.AccountQuotaMiddleware(self.app)
cache = FakeCache(None)
req = Request.blank('/v1/a',
environ={'REQUEST_METHOD': 'POST',
'swift.cache': cache,
'HTTP_X_ACCOUNT_QUOTA_BYTES': '100',
'HTTP_X_ACCOUNT_META_QUOTA_BYTES': '200',
'reseller_request': True})
res = req.get_response(app)
self.assertEqual(res.status_int, 200)
self.assertEqual(self.app.calls_with_headers, [
('POST', '/v1/a', {'Host': 'localhost:80',
'X-Account-Quota-Bytes': '100',
'X-Account-Meta-Quota-Bytes': '200',
'X-Account-Sysmeta-Quota-Bytes': '100'})])
def test_valid_quotas_reseller(self):
app = account_quotas.AccountQuotaMiddleware(self.app)
cache = FakeCache(None)
req = Request.blank('/v1/a',
environ={'REQUEST_METHOD': 'POST',
'swift.cache': cache,
'HTTP_X_ACCOUNT_QUOTA_BYTES': '100',
'reseller_request': True})
res = req.get_response(app)
self.assertEqual(res.status_int, 200)
self.assertEqual(self.app.calls_with_headers, [
('POST', '/v1/a', {'Host': 'localhost:80',
'X-Account-Quota-Bytes': '100',
'X-Account-Sysmeta-Quota-Bytes': '100'})])
@patch_policies
def test_valid_policy_quota_reseller(self):
@ -454,7 +558,7 @@ class TestAccountQuota(unittest.TestCase):
req = Request.blank('/v1/a',
environ={'REQUEST_METHOD': 'POST',
'swift.cache': cache,
'HTTP_X_ACCOUNT_META_QUOTA_BYTES': ''})
'HTTP_X_ACCOUNT_QUOTA_BYTES': ''})
res = req.get_response(app)
self.assertEqual(res.status_int, 403)
@ -464,7 +568,7 @@ class TestAccountQuota(unittest.TestCase):
req = Request.blank('/v1/a', environ={
'REQUEST_METHOD': 'POST',
'swift.cache': cache,
'HTTP_X_REMOVE_ACCOUNT_META_QUOTA_BYTES': 'True'})
'HTTP_X_REMOVE_ACCOUNT_QUOTA_BYTES': 'True'})
res = req.get_response(app)
self.assertEqual(res.status_int, 403)
@ -472,7 +576,7 @@ class TestAccountQuota(unittest.TestCase):
app = account_quotas.AccountQuotaMiddleware(self.app)
req = Request.blank('/v1/a',
environ={'REQUEST_METHOD': 'POST',
'HTTP_X_ACCOUNT_META_QUOTA_BYTES': '',
'HTTP_X_ACCOUNT_QUOTA_BYTES': '',
'reseller_request': True})
res = req.get_response(app)
self.assertEqual(res.status_int, 200)
@ -483,7 +587,7 @@ class TestAccountQuota(unittest.TestCase):
req = Request.blank('/v1/a', environ={
'REQUEST_METHOD': 'POST',
'swift.cache': cache,
'HTTP_X_REMOVE_ACCOUNT_META_QUOTA_BYTES': 'True',
'HTTP_X_REMOVE_ACCOUNT_QUOTA_BYTES': 'True',
'reseller_request': True})
res = req.get_response(app)
self.assertEqual(res.status_int, 200)
@ -541,7 +645,7 @@ class TestAccountQuota(unittest.TestCase):
def test_exceed_count_quota(self):
self.app.register('HEAD', '/v1/a', HTTPOk, {
'x-account-object-count': '10',
'x-account-meta-quota-count': '10'})
'x-account-sysmeta-quota-count': '10'})
app = account_quotas.AccountQuotaMiddleware(self.app)
cache = FakeCache(None)
req = Request.blank('/v1/a/c/o',
@ -553,7 +657,7 @@ class TestAccountQuota(unittest.TestCase):
def test_exceed_quota_count_not_authorized(self):
self.app.register('HEAD', '/v1/a', HTTPOk, {
'x-account-meta-quota-count': '0'})
'x-account-sysmeta-quota-count': '0'})
app = FakeAuthFilter(account_quotas.AccountQuotaMiddleware(self.app))
cache = FakeCache(None)
req = Request.blank('/v1/a/c/o', method='PUT',
@ -564,7 +668,7 @@ class TestAccountQuota(unittest.TestCase):
def test_exceed_count_quota_authorized(self):
self.app.register('HEAD', '/v1/a', HTTPOk, {
'x-account-meta-quota-count': '0'})
'x-account-sysmeta-quota-count': '0'})
app = FakeAuthFilter(account_quotas.AccountQuotaMiddleware(self.app))
cache = FakeCache(None)
req = Request.blank('/v1/a/c/o', method='PUT',
@ -576,7 +680,7 @@ class TestAccountQuota(unittest.TestCase):
def test_under_quota_count_not_authorized(self):
self.app.register('HEAD', '/v1/a', HTTPOk, {
'x-account-object-count': '0',
'x-account-meta-quota-count': '5'})
'x-account-sysmeta-quota-count': '5'})
app = FakeAuthFilter(account_quotas.AccountQuotaMiddleware(self.app))
cache = FakeCache(None)
req = Request.blank('/v1/a/c/o', method='PUT',
@ -588,7 +692,7 @@ class TestAccountQuota(unittest.TestCase):
def test_under_quota_count_authorized(self):
self.app.register('HEAD', '/v1/a', HTTPOk, {
'x-account-object-count': '0',
'x-account-meta-quota-count': '5'})
'x-account-sysmeta-quota-count': '5'})
app = FakeAuthFilter(account_quotas.AccountQuotaMiddleware(self.app))
cache = FakeCache(None)
req = Request.blank('/v1/a/c/o', method='PUT',
@ -600,7 +704,7 @@ class TestAccountQuota(unittest.TestCase):
def test_exceed_quota_count_on_empty_account_not_authorized(self):
self.app.register('HEAD', '/v1/a', HTTPOk, {
'x-account-object-count': '0',
'x-account-meta-quota-count': '0'})
'x-account-sysmeta-quota-count': '0'})
app = FakeAuthFilter(account_quotas.AccountQuotaMiddleware(self.app))
cache = FakeCache(None)
req = Request.blank('/v1/a/c/o', method='PUT',
@ -612,7 +716,7 @@ class TestAccountQuota(unittest.TestCase):
def test_exceed_quota_count_authorized(self):
self.app.register('HEAD', '/v1/a', HTTPOk, {
'x-account-object-count': '5',
'x-account-meta-quota-count': '5'})
'x-account-sysmeta-quota-count': '5'})
app = FakeAuthFilter(account_quotas.AccountQuotaMiddleware(self.app))
cache = FakeCache(None)
req = Request.blank('/v1/a/c/o', method='PUT',
@ -625,7 +729,7 @@ class TestAccountQuota(unittest.TestCase):
def test_over_quota_count_container_create_still_works(self):
self.app.register('HEAD', '/v1/a', HTTPOk, {
'x-account-object-count': '6',
'x-account-meta-quota-count': '5'})
'x-account-sysmeta-quota-count': '5'})
self.app.register('PUT', '/v1/a/new_container', HTTPOk, {})
app = account_quotas.AccountQuotaMiddleware(self.app)
cache = FakeCache(None)
@ -639,7 +743,7 @@ class TestAccountQuota(unittest.TestCase):
def test_over_quota_count_container_post_still_works(self):
self.app.register('HEAD', '/v1/a', HTTPOk, {
'x-account-quota-count': '6',
'x-account-meta-quota-count': '5'})
'x-account-sysmeta-quota-count': '5'})
self.app.register('POST', '/v1/a/new_container', HTTPOk, {})
app = account_quotas.AccountQuotaMiddleware(self.app)
cache = FakeCache(None)
@ -653,7 +757,7 @@ class TestAccountQuota(unittest.TestCase):
def test_over_count_quota_obj_post_still_works(self):
self.app.register('HEAD', '/v1/a', HTTPOk, {
'x-account-object-count': '101',
'x-account-meta-quota-count': '100'})
'x-account-sysmeta-quota-count': '100'})
self.app.register('POST', '/v1/a/c/o', HTTPOk, {})
app = account_quotas.AccountQuotaMiddleware(self.app)
cache = FakeCache(None)
@ -667,7 +771,7 @@ class TestAccountQuota(unittest.TestCase):
def test_exceed_count_quota_reseller(self):
self.app.register('HEAD', '/v1/a', HTTPOk, {
'x-account-object-count': '1000',
'x-account-meta-quota-count': '0'})
'x-account-sysmeta-quota-count': '0'})
self.app.register('PUT', '/v1/a', HTTPOk, {})
app = account_quotas.AccountQuotaMiddleware(self.app)
cache = FakeCache(None)
@ -681,7 +785,7 @@ class TestAccountQuota(unittest.TestCase):
def test_exceed_count_quota_reseller_copy_from(self):
self.app.register('HEAD', '/v1/a', HTTPOk, {
'x-account-object-count': '10',
'x-account-meta-quota-count': '10'})
'x-account-sysmeta-quota-count': '10'})
self.app.register('GET', '/v1/a/c2/o2', HTTPOk, {
'content-length': '1000'}, b'a' * 1000)
app = copy.filter_factory({})(
@ -698,7 +802,7 @@ class TestAccountQuota(unittest.TestCase):
def test_exceed_count_quota_reseller_copy_verb(self):
self.app.register('HEAD', '/v1/a', HTTPOk, {
'x-account-object-count': '99',
'x-account-meta-quota-count': '100'})
'x-account-sysmeta-quota-count': '100'})
self.app.register('GET', '/v1/a/c2/o2', HTTPOk, {
'content-length': '1000'}, b'a' * 1000)
app = copy.filter_factory({})(
@ -715,7 +819,7 @@ class TestAccountQuota(unittest.TestCase):
def test_not_exceed_count_quota(self):
self.app.register('HEAD', '/v1/a', HTTPOk, {
'x-account-object-count': '10',
'x-account-meta-quota-count': '20'})
'x-account-sysmeta-quota-count': '20'})
app = account_quotas.AccountQuotaMiddleware(self.app)
cache = FakeCache(None)
req = Request.blank('/v1/a/c/o',
@ -730,7 +834,7 @@ class TestAccountQuota(unittest.TestCase):
req = Request.blank('/v1/a',
environ={'REQUEST_METHOD': 'POST',
'swift.cache': cache,
'HTTP_X_ACCOUNT_META_QUOTA_COUNT': 'abc',
'HTTP_X_ACCOUNT_QUOTA_COUNT': 'abc',
'reseller_request': True})
res = req.get_response(app)
self.assertEqual(res.status_int, 400)
@ -742,7 +846,7 @@ class TestAccountQuota(unittest.TestCase):
req = Request.blank('/v1/a',
environ={'REQUEST_METHOD': 'POST',
'swift.cache': cache,
'HTTP_X_ACCOUNT_META_QUOTA_COUNT': '100'})
'HTTP_X_ACCOUNT_QUOTA_COUNT': '100'})
res = req.get_response(app)
self.assertEqual(res.status_int, 403)
self.assertEqual(self.app.calls, [])
@ -765,13 +869,14 @@ class TestAccountQuota(unittest.TestCase):
req = Request.blank('/v1/a',
environ={'REQUEST_METHOD': 'POST',
'swift.cache': cache,
'HTTP_X_ACCOUNT_META_QUOTA_COUNT': '100',
'HTTP_X_ACCOUNT_QUOTA_COUNT': '100',
'reseller_request': True})
res = req.get_response(app)
self.assertEqual(res.status_int, 200)
self.assertEqual(self.app.calls_with_headers, [
('POST', '/v1/a', {'Host': 'localhost:80',
'X-Account-Meta-Quota-Count': '100'})])
'X-Account-Quota-Count': '100',
'X-Account-Sysmeta-Quota-Count': '100'})])
@patch_policies
def test_valid_policy_count_quota_reseller(self):
@ -795,7 +900,7 @@ class TestAccountQuota(unittest.TestCase):
req = Request.blank('/v1/a',
environ={'REQUEST_METHOD': 'POST',
'swift.cache': cache,
'HTTP_X_ACCOUNT_META_QUOTA_COUNT': ''})
'HTTP_X_ACCOUNT_QUOTA_COUNT': ''})
res = req.get_response(app)
self.assertEqual(res.status_int, 403)
@ -805,7 +910,7 @@ class TestAccountQuota(unittest.TestCase):
req = Request.blank('/v1/a', environ={
'REQUEST_METHOD': 'POST',
'swift.cache': cache,
'HTTP_X_REMOVE_ACCOUNT_META_QUOTA_COUNT': 'True'})
'HTTP_X_REMOVE_ACCOUNT_QUOTA_COUNT': 'True'})
res = req.get_response(app)
self.assertEqual(res.status_int, 403)
@ -813,7 +918,7 @@ class TestAccountQuota(unittest.TestCase):
app = account_quotas.AccountQuotaMiddleware(self.app)
req = Request.blank('/v1/a',
environ={'REQUEST_METHOD': 'POST',
'HTTP_X_ACCOUNT_META_QUOTA_COUNT': '',
'HTTP_X_ACCOUNT_QUOTA_COUNT': '',
'reseller_request': True})
res = req.get_response(app)
self.assertEqual(res.status_int, 200)
@ -824,7 +929,7 @@ class TestAccountQuota(unittest.TestCase):
req = Request.blank('/v1/a', environ={
'REQUEST_METHOD': 'POST',
'swift.cache': cache,
'HTTP_X_REMOVE_ACCOUNT_META_QUOTA_COUNT': 'True',
'HTTP_X_REMOVE_ACCOUNT_QUOTA_COUNT': 'True',
'reseller_request': True})
res = req.get_response(app)
self.assertEqual(res.status_int, 200)
@ -844,7 +949,7 @@ class AccountQuotaCopyingTestCases(unittest.TestCase):
def test_exceed_bytes_quota_copy_from(self):
self.app.register('HEAD', '/v1/a', HTTPOk,
[('x-account-bytes-used', '500'),
('x-account-meta-quota-bytes', '1000')])
('x-account-sysmeta-quota-bytes', '1000')])
cache = FakeCache(None)
req = Request.blank('/v1/a/c/o',
environ={'REQUEST_METHOD': 'PUT',
@ -857,7 +962,7 @@ class AccountQuotaCopyingTestCases(unittest.TestCase):
def test_exceed_bytes_quota_copy_verb(self):
self.app.register('HEAD', '/v1/a', HTTPOk,
[('x-account-bytes-used', '500'),
('x-account-meta-quota-bytes', '1000')])
('x-account-sysmeta-quota-bytes', '1000')])
cache = FakeCache(None)
req = Request.blank('/v1/a/c2/o2',
environ={'REQUEST_METHOD': 'COPY',
@ -871,7 +976,7 @@ class AccountQuotaCopyingTestCases(unittest.TestCase):
self.app.register('PUT', '/v1/a/c/o', HTTPOk, {})
self.app.register('HEAD', '/v1/a', HTTPOk,
[('x-account-bytes-used', '0'),
('x-account-meta-quota-bytes', '1000')])
('x-account-sysmeta-quota-bytes', '1000')])
cache = FakeCache(None)
req = Request.blank('/v1/a/c/o',
environ={'REQUEST_METHOD': 'PUT',
@ -884,7 +989,7 @@ class AccountQuotaCopyingTestCases(unittest.TestCase):
self.app.register('PUT', '/v1/a/c/o', HTTPOk, {})
self.app.register('HEAD', '/v1/a', HTTPOk,
[('x-account-bytes-used', '0'),
('x-account-meta-quota-bytes', '1000')])
('x-account-sysmeta-quota-bytes', '1000')])
cache = FakeCache(None)
req = Request.blank('/v1/a/c2/o2',
environ={'REQUEST_METHOD': 'COPY',
@ -896,7 +1001,7 @@ class AccountQuotaCopyingTestCases(unittest.TestCase):
def test_quota_copy_from_bad_src(self):
self.app.register('HEAD', '/v1/a', HTTPOk,
[('x-account-bytes-used', '0'),
('x-account-meta-quota-bytes', '1000')])
('x-account-sysmeta-quota-bytes', '1000')])
cache = FakeCache(None)
req = Request.blank('/v1/a/c/o',
environ={'REQUEST_METHOD': 'PUT',
@ -907,14 +1012,14 @@ class AccountQuotaCopyingTestCases(unittest.TestCase):
self.app.register('HEAD', '/v1/a', HTTPOk,
[('x-account-bytes-used', '1000'),
('x-account-meta-quota-bytes', '0')])
('x-account-sysmeta-quota-bytes', '0')])
res = req.get_response(self.copy_filter)
self.assertEqual(res.status_int, 412)
def test_exceed_bytes_count_quota_copy_from(self):
self.app.register('HEAD', '/v1/a', HTTPOk,
[('x-account-object-count', '5'),
('x-account-meta-quota-count', '5')])
('x-account-sysmeta-quota-count', '5')])
cache = FakeCache(None)
req = Request.blank('/v1/a/c/o',
environ={'REQUEST_METHOD': 'PUT',
@ -927,7 +1032,7 @@ class AccountQuotaCopyingTestCases(unittest.TestCase):
def test_exceed_bytes_count_quota_copy_verb(self):
self.app.register('HEAD', '/v1/a', HTTPOk,
[('x-account-object-count', '5'),
('x-account-meta-quota-count', '5')])
('x-account-sysmeta-quota-count', '5')])
cache = FakeCache(None)
req = Request.blank('/v1/a/c2/o2',
environ={'REQUEST_METHOD': 'COPY',
@ -941,7 +1046,7 @@ class AccountQuotaCopyingTestCases(unittest.TestCase):
self.app.register('PUT', '/v1/a/c/o', HTTPOk, {})
self.app.register('HEAD', '/v1/a', HTTPOk,
[('x-account-object-count', '5'),
('x-account-meta-quota-count', '6')])
('x-account-sysmeta-quota-count', '6')])
cache = FakeCache(None)
req = Request.blank('/v1/a/c/o',
environ={'REQUEST_METHOD': 'PUT',
@ -954,7 +1059,7 @@ class AccountQuotaCopyingTestCases(unittest.TestCase):
self.app.register('PUT', '/v1/a/c/o', HTTPOk, {})
self.app.register('HEAD', '/v1/a', HTTPOk,
[('x-account-object-count', '5'),
('x-account-meta-quota-count', '6')])
('x-account-sysmeta-quota-count', '6')])
cache = FakeCache(None)
req = Request.blank('/v1/a/c2/o2',
environ={'REQUEST_METHOD': 'COPY',
@ -966,7 +1071,7 @@ class AccountQuotaCopyingTestCases(unittest.TestCase):
def test_count_quota_copy_from_bad_src(self):
self.app.register('HEAD', '/v1/a', HTTPOk,
[('x-account-object-count', '0'),
('x-account-meta-quota-count', '1')])
('x-account-sysmeta-quota-count', '1')])
cache = FakeCache(None)
req = Request.blank('/v1/a/c/o',
environ={'REQUEST_METHOD': 'PUT',
@ -977,7 +1082,7 @@ class AccountQuotaCopyingTestCases(unittest.TestCase):
self.app.register('HEAD', '/v1/a', HTTPOk,
[('x-account-object-count', '1'),
('x-account-meta-quota-count', '0')])
('x-account-sysmeta-quota-count', '0')])
res = req.get_response(self.copy_filter)
self.assertEqual(res.status_int, 412)