Account and container info fixes and improvement.
- Fixes bug 1119282. - Allow middleware accessing metadata of an account without having to store it separately in a new memcache namespace. - Add tests for get_container_info that was previously missed. - Add get_account_info method based on get_container_info, a function for other middleware to query accounts. - Rename container_info['count'] as container_info['object_count']. Change-Id: I43787916c7a812cb08d278edf45370521f12c912
This commit is contained in:
parent
e88ff34685
commit
b62299376a
1
.mailmap
1
.mailmap
@ -17,6 +17,7 @@ Anne Gentle <anne@openstack.org> annegentle
|
|||||||
Fujita Tomonori <fujita.tomonori@lab.ntt.co.jp>
|
Fujita Tomonori <fujita.tomonori@lab.ntt.co.jp>
|
||||||
Greg Lange <greglange@gmail.com> <glange@rackspace.com>
|
Greg Lange <greglange@gmail.com> <glange@rackspace.com>
|
||||||
Greg Lange <greglange@gmail.com> <greglange+launchpad@gmail.com>
|
Greg Lange <greglange@gmail.com> <greglange+launchpad@gmail.com>
|
||||||
|
Chmouel Boudjnah <chmouel@chmouel.com> <chmouel@enovance.com>
|
||||||
Gaurav B. Gangalwar <gaurav@gluster.com> gaurav@gluster.com <>
|
Gaurav B. Gangalwar <gaurav@gluster.com> gaurav@gluster.com <>
|
||||||
Joe Arnold <joe@swiftstack.com> <joe@cloudscaling.com>
|
Joe Arnold <joe@swiftstack.com> <joe@cloudscaling.com>
|
||||||
Kapil Thangavelu <kapil.foss@gmail.com> kapil.foss@gmail.com <>
|
Kapil Thangavelu <kapil.foss@gmail.com> kapil.foss@gmail.com <>
|
||||||
|
@ -42,7 +42,6 @@ set:
|
|||||||
+---------------------------------------------+-------------------------------+
|
+---------------------------------------------+-------------------------------+
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from swift.common.utils import split_path
|
|
||||||
from swift.common.http import is_success
|
from swift.common.http import is_success
|
||||||
from swift.proxy.controllers.base import get_container_info
|
from swift.proxy.controllers.base import get_container_info
|
||||||
from swift.common.swob import Response, HTTPBadRequest, wsgify
|
from swift.common.swob import Response, HTTPBadRequest, wsgify
|
||||||
@ -93,9 +92,9 @@ class ContainerQuotaMiddleware(object):
|
|||||||
if int(container_info['meta']['quota-bytes']) < new_size:
|
if int(container_info['meta']['quota-bytes']) < new_size:
|
||||||
return self.bad_response(req, container_info)
|
return self.bad_response(req, container_info)
|
||||||
if 'quota-count' in container_info.get('meta', {}) and \
|
if 'quota-count' in container_info.get('meta', {}) and \
|
||||||
'count' in container_info and \
|
'object_count' in container_info and \
|
||||||
container_info['meta']['quota-count'].isdigit():
|
container_info['meta']['quota-count'].isdigit():
|
||||||
new_count = int(container_info['count']) + 1
|
new_count = int(container_info['object_count']) + 1
|
||||||
if int(container_info['meta']['quota-count']) < new_count:
|
if int(container_info['meta']['quota-count']) < new_count:
|
||||||
return self.bad_response(req, container_info)
|
return self.bad_response(req, container_info)
|
||||||
|
|
||||||
|
@ -97,17 +97,33 @@ def get_container_memcache_key(account, container):
|
|||||||
return 'container/%s/%s' % (account, container)
|
return 'container/%s/%s' % (account, container)
|
||||||
|
|
||||||
|
|
||||||
|
def headers_to_account_info(headers, status_int=HTTP_OK):
|
||||||
|
"""
|
||||||
|
Construct a cacheable dict of account info based on response headers.
|
||||||
|
"""
|
||||||
|
headers = dict((k.lower(), v) for k, v in dict(headers).iteritems())
|
||||||
|
return {
|
||||||
|
'status': status_int,
|
||||||
|
'container_count': headers.get('x-account-container-count'),
|
||||||
|
'total_object_count': headers.get('x-account-object-count'),
|
||||||
|
'bytes': headers.get('x-account-bytes-used'),
|
||||||
|
'meta': dict((key[15:], value)
|
||||||
|
for key, value in headers.iteritems()
|
||||||
|
if key.startswith('x-account-meta-'))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def headers_to_container_info(headers, status_int=HTTP_OK):
|
def headers_to_container_info(headers, status_int=HTTP_OK):
|
||||||
"""
|
"""
|
||||||
Construct a cacheable dict of container info based on response headers.
|
Construct a cacheable dict of container info based on response headers.
|
||||||
"""
|
"""
|
||||||
headers = dict(headers)
|
headers = dict((k.lower(), v) for k, v in dict(headers).iteritems())
|
||||||
return {
|
return {
|
||||||
'status': status_int,
|
'status': status_int,
|
||||||
'read_acl': headers.get('x-container-read'),
|
'read_acl': headers.get('x-container-read'),
|
||||||
'write_acl': headers.get('x-container-write'),
|
'write_acl': headers.get('x-container-write'),
|
||||||
'sync_key': headers.get('x-container-sync-key'),
|
'sync_key': headers.get('x-container-sync-key'),
|
||||||
'count': headers.get('x-container-object-count'),
|
'object_count': headers.get('x-container-object-count'),
|
||||||
'bytes': headers.get('x-container-bytes-used'),
|
'bytes': headers.get('x-container-bytes-used'),
|
||||||
'versions': headers.get('x-versions-location'),
|
'versions': headers.get('x-versions-location'),
|
||||||
'cors': {
|
'cors': {
|
||||||
@ -120,9 +136,9 @@ def headers_to_container_info(headers, status_int=HTTP_OK):
|
|||||||
'max_age': headers.get(
|
'max_age': headers.get(
|
||||||
'x-container-meta-access-control-max-age')
|
'x-container-meta-access-control-max-age')
|
||||||
},
|
},
|
||||||
'meta': dict((key.lower()[17:], value)
|
'meta': dict((key[17:], value)
|
||||||
for key, value in headers.iteritems()
|
for key, value in headers.iteritems()
|
||||||
if key.lower().startswith('x-container-meta-'))
|
if key.startswith('x-container-meta-'))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -198,8 +214,8 @@ def get_container_info(env, app, swift_source=None):
|
|||||||
cache = cache_from_env(env)
|
cache = cache_from_env(env)
|
||||||
if not cache:
|
if not cache:
|
||||||
return None
|
return None
|
||||||
(version, account, container, obj) = \
|
(version, account, container, _) = \
|
||||||
split_path(env['PATH_INFO'], 2, 4, True)
|
split_path(env['PATH_INFO'], 3, 4, True)
|
||||||
cache_key = get_container_memcache_key(account, container)
|
cache_key = get_container_memcache_key(account, container)
|
||||||
# Use a unique environment cache key per container. If you copy this env
|
# Use a unique environment cache key per container. If you copy this env
|
||||||
# to make a new request, it won't accidentally reuse the old container info
|
# to make a new request, it won't accidentally reuse the old container info
|
||||||
@ -217,6 +233,33 @@ def get_container_info(env, app, swift_source=None):
|
|||||||
return env[env_key]
|
return env[env_key]
|
||||||
|
|
||||||
|
|
||||||
|
def get_account_info(env, app, swift_source=None):
|
||||||
|
"""
|
||||||
|
Get the info structure for an account, based on env and app.
|
||||||
|
This is useful to middlewares.
|
||||||
|
"""
|
||||||
|
cache = cache_from_env(env)
|
||||||
|
if not cache:
|
||||||
|
return None
|
||||||
|
(version, account, container, _) = \
|
||||||
|
split_path(env['PATH_INFO'], 2, 4, True)
|
||||||
|
cache_key = get_account_memcache_key(account)
|
||||||
|
# Use a unique environment cache key per account. If you copy this env
|
||||||
|
# to make a new request, it won't accidentally reuse the old account info
|
||||||
|
env_key = 'swift.%s' % cache_key
|
||||||
|
if env_key not in env:
|
||||||
|
account_info = cache.get(cache_key)
|
||||||
|
if not account_info:
|
||||||
|
resp = make_pre_authed_request(
|
||||||
|
env, 'HEAD', '/%s/%s' % (version, account),
|
||||||
|
swift_source=swift_source,
|
||||||
|
).get_response(app)
|
||||||
|
account_info = headers_to_account_info(
|
||||||
|
resp.headers, resp.status_int)
|
||||||
|
env[env_key] = account_info
|
||||||
|
return env[env_key]
|
||||||
|
|
||||||
|
|
||||||
class Controller(object):
|
class Controller(object):
|
||||||
"""Base WSGI controller class for the proxy"""
|
"""Base WSGI controller class for the proxy"""
|
||||||
server_type = 'Base'
|
server_type = 'Base'
|
||||||
@ -326,6 +369,11 @@ class Controller(object):
|
|||||||
or (None, None, None) if it does not exist
|
or (None, None, None) if it does not exist
|
||||||
"""
|
"""
|
||||||
partition, nodes = self.app.account_ring.get_nodes(account)
|
partition, nodes = self.app.account_ring.get_nodes(account)
|
||||||
|
account_info = {'status': 0,
|
||||||
|
'container_count': 0,
|
||||||
|
'total_object_count': None,
|
||||||
|
'bytes': None,
|
||||||
|
'meta': {}}
|
||||||
# 0 = no responses, 200 = found, 404 = not found, -1 = mixed responses
|
# 0 = no responses, 200 = found, 404 = not found, -1 = mixed responses
|
||||||
if self.app.memcache:
|
if self.app.memcache:
|
||||||
cache_key = get_account_memcache_key(account)
|
cache_key = get_account_memcache_key(account)
|
||||||
@ -341,7 +389,6 @@ class Controller(object):
|
|||||||
elif result_code == HTTP_NOT_FOUND and not autocreate:
|
elif result_code == HTTP_NOT_FOUND and not autocreate:
|
||||||
return None, None, None
|
return None, None, None
|
||||||
result_code = 0
|
result_code = 0
|
||||||
container_count = 0
|
|
||||||
attempts_left = len(nodes)
|
attempts_left = len(nodes)
|
||||||
path = '/%s' % account
|
path = '/%s' % account
|
||||||
headers = {'x-trans-id': self.trans_id, 'Connection': 'close'}
|
headers = {'x-trans-id': self.trans_id, 'Connection': 'close'}
|
||||||
@ -362,8 +409,8 @@ class Controller(object):
|
|||||||
resp.read()
|
resp.read()
|
||||||
if is_success(resp.status):
|
if is_success(resp.status):
|
||||||
result_code = HTTP_OK
|
result_code = HTTP_OK
|
||||||
container_count = int(
|
account_info.update(
|
||||||
resp.getheader('x-account-container-count') or 0)
|
headers_to_account_info(resp.getheaders()))
|
||||||
break
|
break
|
||||||
elif resp.status == HTTP_NOT_FOUND:
|
elif resp.status == HTTP_NOT_FOUND:
|
||||||
if result_code == 0:
|
if result_code == 0:
|
||||||
@ -398,12 +445,12 @@ class Controller(object):
|
|||||||
cache_timeout = self.app.recheck_account_existence
|
cache_timeout = self.app.recheck_account_existence
|
||||||
else:
|
else:
|
||||||
cache_timeout = self.app.recheck_account_existence * 0.1
|
cache_timeout = self.app.recheck_account_existence * 0.1
|
||||||
|
account_info.update(status=result_code)
|
||||||
self.app.memcache.set(cache_key,
|
self.app.memcache.set(cache_key,
|
||||||
{'status': result_code,
|
account_info,
|
||||||
'container_count': container_count},
|
|
||||||
time=cache_timeout)
|
time=cache_timeout)
|
||||||
if result_code == HTTP_OK:
|
if result_code == HTTP_OK:
|
||||||
return partition, nodes, container_count
|
return partition, nodes, account_info['container_count']
|
||||||
return None, None, None
|
return None, None, None
|
||||||
|
|
||||||
def container_info(self, account, container, account_autocreate=False):
|
def container_info(self, account, container, account_autocreate=False):
|
||||||
|
@ -87,7 +87,7 @@ class TestContainerQuotas(unittest.TestCase):
|
|||||||
|
|
||||||
def test_exceed_counts_quota(self):
|
def test_exceed_counts_quota(self):
|
||||||
app = container_quotas.ContainerQuotaMiddleware(FakeApp(), {})
|
app = container_quotas.ContainerQuotaMiddleware(FakeApp(), {})
|
||||||
cache = FakeCache({'count': 1, 'meta': {'quota-count': '1'}})
|
cache = FakeCache({'object_count': 1, 'meta': {'quota-count': '1'}})
|
||||||
req = Request.blank('/v1/a/c/o',
|
req = Request.blank('/v1/a/c/o',
|
||||||
environ={'REQUEST_METHOD': 'PUT', 'swift.cache': cache,
|
environ={'REQUEST_METHOD': 'PUT', 'swift.cache': cache,
|
||||||
'CONTENT_LENGTH': '100'})
|
'CONTENT_LENGTH': '100'})
|
||||||
@ -96,7 +96,7 @@ class TestContainerQuotas(unittest.TestCase):
|
|||||||
|
|
||||||
def test_not_exceed_counts_quota(self):
|
def test_not_exceed_counts_quota(self):
|
||||||
app = container_quotas.ContainerQuotaMiddleware(FakeApp(), {})
|
app = container_quotas.ContainerQuotaMiddleware(FakeApp(), {})
|
||||||
cache = FakeCache({'count': 1, 'meta': {'quota-count': '2'}})
|
cache = FakeCache({'object_count': 1, 'meta': {'quota-count': '2'}})
|
||||||
req = Request.blank('/v1/a/c/o',
|
req = Request.blank('/v1/a/c/o',
|
||||||
environ={'REQUEST_METHOD': 'PUT', 'swift.cache': cache,
|
environ={'REQUEST_METHOD': 'PUT', 'swift.cache': cache,
|
||||||
'CONTENT_LENGTH': '100'})
|
'CONTENT_LENGTH': '100'})
|
||||||
@ -152,7 +152,7 @@ class TestContainerQuotas(unittest.TestCase):
|
|||||||
|
|
||||||
def test_auth_fail(self):
|
def test_auth_fail(self):
|
||||||
app = container_quotas.ContainerQuotaMiddleware(FakeApp(), {})
|
app = container_quotas.ContainerQuotaMiddleware(FakeApp(), {})
|
||||||
cache = FakeCache({'count': 1, 'meta': {'quota-count': '1'},
|
cache = FakeCache({'object_count': 1, 'meta': {'quota-count': '1'},
|
||||||
'write_acl': None})
|
'write_acl': None})
|
||||||
req = Request.blank('/v1/a/c/o',
|
req = Request.blank('/v1/a/c/o',
|
||||||
environ={'REQUEST_METHOD': 'PUT', 'swift.cache': cache,
|
environ={'REQUEST_METHOD': 'PUT', 'swift.cache': cache,
|
||||||
|
@ -14,10 +14,101 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
from swift.proxy.controllers.base import headers_to_container_info
|
|
||||||
|
import swift.proxy.controllers.base
|
||||||
|
from swift.proxy.controllers.base import headers_to_container_info, \
|
||||||
|
headers_to_account_info, get_container_info, get_container_memcache_key, \
|
||||||
|
get_account_info, get_account_memcache_key
|
||||||
|
from swift.common.swob import Request
|
||||||
|
from swift.common.utils import split_path
|
||||||
|
|
||||||
|
|
||||||
|
class FakeResponse(object):
|
||||||
|
def __init__(self, headers):
|
||||||
|
self.headers = headers
|
||||||
|
self.status_int = 201
|
||||||
|
|
||||||
|
|
||||||
|
class FakeRequest(object):
|
||||||
|
def __init__(self, env, method, path, swift_source=None):
|
||||||
|
(version, account,
|
||||||
|
container, obj) = split_path(env['PATH_INFO'], 2, 4, True)
|
||||||
|
stype = container and 'container' or 'account'
|
||||||
|
self.headers = {'x-%s-object-count' % (stype): 1000,
|
||||||
|
'x-%s-bytes-used' % (stype): 6666}
|
||||||
|
|
||||||
|
def get_response(self, app):
|
||||||
|
return FakeResponse(self.headers)
|
||||||
|
|
||||||
|
|
||||||
|
class FakeCache(object):
|
||||||
|
def __init__(self, val):
|
||||||
|
self.val = val
|
||||||
|
|
||||||
|
def get(self, *args):
|
||||||
|
return self.val
|
||||||
|
|
||||||
|
|
||||||
class TestFuncs(unittest.TestCase):
|
class TestFuncs(unittest.TestCase):
|
||||||
|
def test_get_container_info_no_cache(self):
|
||||||
|
swift.proxy.controllers.base.make_pre_authed_request = FakeRequest
|
||||||
|
req = Request.blank("/v1/AUTH_account/cont",
|
||||||
|
environ={'swift.cache': FakeCache({})})
|
||||||
|
resp = get_container_info(req.environ, 'xxx')
|
||||||
|
self.assertEquals(resp['bytes'], 6666)
|
||||||
|
self.assertEquals(resp['object_count'], 1000)
|
||||||
|
|
||||||
|
def test_get_container_info_cache(self):
|
||||||
|
swift.proxy.controllers.base.make_pre_authed_request = FakeRequest
|
||||||
|
cached = {'status': 404,
|
||||||
|
'bytes': 3333,
|
||||||
|
'object_count': 10}
|
||||||
|
req = Request.blank("/v1/account/cont",
|
||||||
|
environ={'swift.cache': FakeCache(cached)})
|
||||||
|
resp = get_container_info(req.environ, 'xxx')
|
||||||
|
self.assertEquals(resp['bytes'], 3333)
|
||||||
|
self.assertEquals(resp['object_count'], 10)
|
||||||
|
self.assertEquals(resp['status'], 404)
|
||||||
|
|
||||||
|
def test_get_container_info_env(self):
|
||||||
|
cache_key = get_container_memcache_key("account", "cont")
|
||||||
|
env_key = 'swift.%s' % cache_key
|
||||||
|
req = Request.blank("/v1/account/cont",
|
||||||
|
environ={ env_key: {'bytes': 3867},
|
||||||
|
'swift.cache': FakeCache({})})
|
||||||
|
resp = get_container_info(req.environ, 'xxx')
|
||||||
|
self.assertEquals(resp['bytes'], 3867)
|
||||||
|
|
||||||
|
def test_get_account_info_no_cache(self):
|
||||||
|
swift.proxy.controllers.base.make_pre_authed_request = FakeRequest
|
||||||
|
req = Request.blank("/v1/AUTH_account",
|
||||||
|
environ={'swift.cache': FakeCache({})})
|
||||||
|
resp = get_account_info(req.environ, 'xxx')
|
||||||
|
print resp
|
||||||
|
self.assertEquals(resp['bytes'], 6666)
|
||||||
|
self.assertEquals(resp['total_object_count'], 1000)
|
||||||
|
|
||||||
|
def test_get_account_info_cache(self):
|
||||||
|
swift.proxy.controllers.base.make_pre_authed_request = FakeRequest
|
||||||
|
cached = {'status': 404,
|
||||||
|
'bytes': 3333,
|
||||||
|
'total_object_count': 10}
|
||||||
|
req = Request.blank("/v1/account/cont",
|
||||||
|
environ={'swift.cache': FakeCache(cached)})
|
||||||
|
resp = get_account_info(req.environ, 'xxx')
|
||||||
|
self.assertEquals(resp['bytes'], 3333)
|
||||||
|
self.assertEquals(resp['total_object_count'], 10)
|
||||||
|
self.assertEquals(resp['status'], 404)
|
||||||
|
|
||||||
|
def test_get_account_info_env(self):
|
||||||
|
cache_key = get_account_memcache_key("account")
|
||||||
|
env_key = 'swift.%s' % cache_key
|
||||||
|
req = Request.blank("/v1/account",
|
||||||
|
environ={ env_key: {'bytes': 3867},
|
||||||
|
'swift.cache': FakeCache({})})
|
||||||
|
resp = get_account_info(req.environ, 'xxx')
|
||||||
|
self.assertEquals(resp['bytes'], 3867)
|
||||||
|
|
||||||
def test_headers_to_container_info_missing(self):
|
def test_headers_to_container_info_missing(self):
|
||||||
resp = headers_to_container_info({}, 404)
|
resp = headers_to_container_info({}, 404)
|
||||||
self.assertEquals(resp['status'], 404)
|
self.assertEquals(resp['status'], 404)
|
||||||
@ -48,3 +139,31 @@ class TestFuncs(unittest.TestCase):
|
|||||||
self.assertEquals(
|
self.assertEquals(
|
||||||
resp,
|
resp,
|
||||||
headers_to_container_info(headers.items(), 200))
|
headers_to_container_info(headers.items(), 200))
|
||||||
|
|
||||||
|
def test_headers_to_account_info_missing(self):
|
||||||
|
resp = headers_to_account_info({}, 404)
|
||||||
|
self.assertEquals(resp['status'], 404)
|
||||||
|
self.assertEquals(resp['bytes'], None)
|
||||||
|
self.assertEquals(resp['container_count'], None)
|
||||||
|
|
||||||
|
def test_headers_to_account_info_meta(self):
|
||||||
|
headers = {'X-Account-Meta-Whatevs': 14,
|
||||||
|
'x-account-meta-somethingelse': 0}
|
||||||
|
resp = headers_to_account_info(headers.items(), 200)
|
||||||
|
self.assertEquals(len(resp['meta']), 2)
|
||||||
|
self.assertEquals(resp['meta']['whatevs'], 14)
|
||||||
|
self.assertEquals(resp['meta']['somethingelse'], 0)
|
||||||
|
|
||||||
|
def test_headers_to_account_info_values(self):
|
||||||
|
headers = {
|
||||||
|
'x-account-object-count': '10',
|
||||||
|
'x-account-container-count': '20',
|
||||||
|
}
|
||||||
|
resp = headers_to_account_info(headers.items(), 200)
|
||||||
|
self.assertEquals(resp['total_object_count'], '10')
|
||||||
|
self.assertEquals(resp['container_count'], '20')
|
||||||
|
|
||||||
|
headers['x-unused-header'] = 'blahblahblah'
|
||||||
|
self.assertEquals(
|
||||||
|
resp,
|
||||||
|
headers_to_account_info(headers.items(), 200))
|
||||||
|
@ -466,7 +466,12 @@ class TestController(unittest.TestCase):
|
|||||||
self.assertEquals(count, 12345)
|
self.assertEquals(count, 12345)
|
||||||
|
|
||||||
cache_key = get_account_memcache_key(self.account)
|
cache_key = get_account_memcache_key(self.account)
|
||||||
self.assertEquals({'status': 200, 'container_count': 12345},
|
container_info = {'status': 200,
|
||||||
|
'container_count': 12345,
|
||||||
|
'total_object_count': None,
|
||||||
|
'bytes': None,
|
||||||
|
'meta': {}}
|
||||||
|
self.assertEquals(container_info,
|
||||||
self.memcache.get(cache_key))
|
self.memcache.get(cache_key))
|
||||||
|
|
||||||
set_http_connect()
|
set_http_connect()
|
||||||
@ -485,7 +490,12 @@ class TestController(unittest.TestCase):
|
|||||||
self.assertEquals(count, None)
|
self.assertEquals(count, None)
|
||||||
|
|
||||||
cache_key = get_account_memcache_key(self.account)
|
cache_key = get_account_memcache_key(self.account)
|
||||||
self.assertEquals({'status': 404, 'container_count': 0},
|
container_info = {'status': 404,
|
||||||
|
'container_count': 0,
|
||||||
|
'total_object_count': None,
|
||||||
|
'bytes': None,
|
||||||
|
'meta': {}}
|
||||||
|
self.assertEquals(container_info,
|
||||||
self.memcache.get(cache_key))
|
self.memcache.get(cache_key))
|
||||||
|
|
||||||
set_http_connect()
|
set_http_connect()
|
||||||
|
Loading…
Reference in New Issue
Block a user