Apply limit to list versioned containers
Change-Id: I28e062273d673c4f07cd3c5da088aa790b77a599 Closes-Bug: #1863841
This commit is contained in:
parent
26b867f87d
commit
f2ffd90059
@ -27,7 +27,7 @@ from swift.account.utils import account_listing_response, get_response_headers
|
|||||||
from swift.common.db import DatabaseConnectionError, DatabaseAlreadyExists
|
from swift.common.db import DatabaseConnectionError, DatabaseAlreadyExists
|
||||||
from swift.common.request_helpers import get_param, \
|
from swift.common.request_helpers import get_param, \
|
||||||
split_and_validate_path, validate_internal_account, \
|
split_and_validate_path, validate_internal_account, \
|
||||||
validate_internal_container
|
validate_internal_container, constrain_req_limit
|
||||||
from swift.common.utils import get_logger, hash_path, public, \
|
from swift.common.utils import get_logger, hash_path, public, \
|
||||||
Timestamp, storage_directory, config_true_value, \
|
Timestamp, storage_directory, config_true_value, \
|
||||||
timing_stats, replication, get_log_line, \
|
timing_stats, replication, get_log_line, \
|
||||||
@ -247,16 +247,8 @@ class AccountController(BaseStorageServer):
|
|||||||
drive, part, account = get_account_name_and_placement(req)
|
drive, part, account = get_account_name_and_placement(req)
|
||||||
prefix = get_param(req, 'prefix')
|
prefix = get_param(req, 'prefix')
|
||||||
delimiter = get_param(req, 'delimiter')
|
delimiter = get_param(req, 'delimiter')
|
||||||
limit = constraints.ACCOUNT_LISTING_LIMIT
|
|
||||||
given_limit = get_param(req, 'limit')
|
|
||||||
reverse = config_true_value(get_param(req, 'reverse'))
|
reverse = config_true_value(get_param(req, 'reverse'))
|
||||||
if given_limit and given_limit.isdigit():
|
limit = constrain_req_limit(req, constraints.ACCOUNT_LISTING_LIMIT)
|
||||||
limit = int(given_limit)
|
|
||||||
if limit > constraints.ACCOUNT_LISTING_LIMIT:
|
|
||||||
return HTTPPreconditionFailed(
|
|
||||||
request=req,
|
|
||||||
body='Maximum limit is %d' %
|
|
||||||
constraints.ACCOUNT_LISTING_LIMIT)
|
|
||||||
marker = get_param(req, 'marker', '')
|
marker = get_param(req, 'marker', '')
|
||||||
end_marker = get_param(req, 'end_marker')
|
end_marker = get_param(req, 'end_marker')
|
||||||
out_content_type = listing_formats.get_listing_content_type(req)
|
out_content_type = listing_formats.get_listing_content_type(req)
|
||||||
|
@ -151,11 +151,13 @@ import time
|
|||||||
from cgi import parse_header
|
from cgi import parse_header
|
||||||
from six.moves.urllib.parse import unquote
|
from six.moves.urllib.parse import unquote
|
||||||
|
|
||||||
from swift.common.constraints import MAX_FILE_SIZE, valid_api_version
|
from swift.common.constraints import MAX_FILE_SIZE, valid_api_version, \
|
||||||
|
ACCOUNT_LISTING_LIMIT
|
||||||
from swift.common.http import is_success, is_client_error, HTTP_NOT_FOUND, \
|
from swift.common.http import is_success, is_client_error, HTTP_NOT_FOUND, \
|
||||||
HTTP_CONFLICT
|
HTTP_CONFLICT
|
||||||
from swift.common.request_helpers import get_sys_meta_prefix, \
|
from swift.common.request_helpers import get_sys_meta_prefix, \
|
||||||
copy_header_subset, get_reserved_name, split_reserved_name
|
copy_header_subset, get_reserved_name, split_reserved_name, \
|
||||||
|
constrain_req_limit
|
||||||
from swift.common.middleware.symlink import TGT_OBJ_SYMLINK_HDR, \
|
from swift.common.middleware.symlink import TGT_OBJ_SYMLINK_HDR, \
|
||||||
TGT_ETAG_SYSMETA_SYMLINK_HDR, SYMLOOP_EXTEND, ALLOW_RESERVED_NAMES, \
|
TGT_ETAG_SYSMETA_SYMLINK_HDR, SYMLOOP_EXTEND, ALLOW_RESERVED_NAMES, \
|
||||||
TGT_BYTES_SYSMETA_SYMLINK_HDR, TGT_ACCT_SYMLINK_HDR
|
TGT_BYTES_SYSMETA_SYMLINK_HDR, TGT_ACCT_SYMLINK_HDR
|
||||||
@ -183,6 +185,7 @@ SYSMETA_VERSIONS_SYMLINK = get_sys_meta_prefix('object') + 'versions-symlink'
|
|||||||
|
|
||||||
def build_listing(*to_splice, **kwargs):
|
def build_listing(*to_splice, **kwargs):
|
||||||
reverse = kwargs.pop('reverse')
|
reverse = kwargs.pop('reverse')
|
||||||
|
limit = kwargs.pop('limit')
|
||||||
if kwargs:
|
if kwargs:
|
||||||
raise TypeError('Invalid keyword arguments received: %r' % kwargs)
|
raise TypeError('Invalid keyword arguments received: %r' % kwargs)
|
||||||
|
|
||||||
@ -195,7 +198,7 @@ def build_listing(*to_splice, **kwargs):
|
|||||||
itertools.chain(*to_splice),
|
itertools.chain(*to_splice),
|
||||||
key=merge_key,
|
key=merge_key,
|
||||||
reverse=reverse,
|
reverse=reverse,
|
||||||
)).encode('ascii')
|
)[:limit]).encode('ascii')
|
||||||
|
|
||||||
|
|
||||||
def non_expiry_header(header):
|
def non_expiry_header(header):
|
||||||
@ -1197,9 +1200,11 @@ class ContainerContext(ObjectVersioningContext):
|
|||||||
'hash': item['hash'],
|
'hash': item['hash'],
|
||||||
'last_modified': item['last_modified'],
|
'last_modified': item['last_modified'],
|
||||||
})
|
})
|
||||||
|
limit = constrain_req_limit(req, ACCOUNT_LISTING_LIMIT)
|
||||||
body = build_listing(
|
body = build_listing(
|
||||||
null_listing, subdir_listing, broken_listing,
|
null_listing, subdir_listing, broken_listing,
|
||||||
reverse=config_true_value(params.get('reverse', 'no')))
|
reverse=config_true_value(params.get('reverse', 'no')),
|
||||||
|
limit=limit)
|
||||||
self.update_content_length(len(body))
|
self.update_content_length(len(body))
|
||||||
app_resp = [body]
|
app_resp = [body]
|
||||||
close_if_possible(versions_resp.app_iter)
|
close_if_possible(versions_resp.app_iter)
|
||||||
@ -1260,10 +1265,13 @@ class ContainerContext(ObjectVersioningContext):
|
|||||||
'last_modified': item['last_modified'],
|
'last_modified': item['last_modified'],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
limit = constrain_req_limit(req, ACCOUNT_LISTING_LIMIT)
|
||||||
body = build_listing(
|
body = build_listing(
|
||||||
null_listing, versions_listing,
|
null_listing, versions_listing,
|
||||||
subdir_listing, broken_listing,
|
subdir_listing, broken_listing,
|
||||||
reverse=config_true_value(params.get('reverse', 'no')))
|
reverse=config_true_value(params.get('reverse', 'no')),
|
||||||
|
limit=limit,
|
||||||
|
)
|
||||||
self.update_content_length(len(body))
|
self.update_content_length(len(body))
|
||||||
app_resp = [body]
|
app_resp = [body]
|
||||||
else:
|
else:
|
||||||
@ -1333,9 +1341,8 @@ class AccountContext(ObjectVersioningContext):
|
|||||||
# look-up by name. Ignore 'subdir' items
|
# look-up by name. Ignore 'subdir' items
|
||||||
for item in [item for item in versions_listing
|
for item in [item for item in versions_listing
|
||||||
if 'name' in item]:
|
if 'name' in item]:
|
||||||
name = self._split_versions_container_name(
|
container_name = self._split_versions_container_name(
|
||||||
item['name'])
|
item['name'])
|
||||||
container_name = bytes_to_wsgi(name.encode('utf-8'))
|
|
||||||
versions_dict[container_name] = item
|
versions_dict[container_name] = item
|
||||||
|
|
||||||
# update bytes from original listing with bytes from
|
# update bytes from original listing with bytes from
|
||||||
@ -1343,10 +1350,8 @@ class AccountContext(ObjectVersioningContext):
|
|||||||
if len(versions_dict) > 0:
|
if len(versions_dict) > 0:
|
||||||
# ignore 'subdir' items
|
# ignore 'subdir' items
|
||||||
for item in [item for item in listing if 'name' in item]:
|
for item in [item for item in listing if 'name' in item]:
|
||||||
container_name = bytes_to_wsgi(
|
if item['name'] in versions_dict:
|
||||||
item['name'].encode('utf-8'))
|
v_info = versions_dict.pop(item['name'])
|
||||||
if container_name in versions_dict:
|
|
||||||
v_info = versions_dict.pop(container_name)
|
|
||||||
item['bytes'] = item['bytes'] + v_info['bytes']
|
item['bytes'] = item['bytes'] + v_info['bytes']
|
||||||
|
|
||||||
# if there are items left in versions_dict, it indicates an
|
# if there are items left in versions_dict, it indicates an
|
||||||
@ -1360,9 +1365,12 @@ class AccountContext(ObjectVersioningContext):
|
|||||||
item['count'] = 0 # None of these are current
|
item['count'] = 0 # None of these are current
|
||||||
listing.append(item)
|
listing.append(item)
|
||||||
|
|
||||||
|
limit = constrain_req_limit(req, ACCOUNT_LISTING_LIMIT)
|
||||||
body = build_listing(
|
body = build_listing(
|
||||||
listing,
|
listing,
|
||||||
reverse=config_true_value(params.get('reverse', 'no')))
|
reverse=config_true_value(params.get('reverse', 'no')),
|
||||||
|
limit=limit,
|
||||||
|
)
|
||||||
self.update_content_length(len(body))
|
self.update_content_length(len(body))
|
||||||
app_resp = [body]
|
app_resp = [body]
|
||||||
|
|
||||||
|
@ -94,6 +94,17 @@ def get_param(req, name, default=None):
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def constrain_req_limit(req, constrained_limit):
|
||||||
|
given_limit = get_param(req, 'limit')
|
||||||
|
limit = constrained_limit
|
||||||
|
if given_limit and given_limit.isdigit():
|
||||||
|
limit = int(given_limit)
|
||||||
|
if limit > constrained_limit:
|
||||||
|
raise HTTPPreconditionFailed(
|
||||||
|
request=req, body='Maximum limit is %d' % constrained_limit)
|
||||||
|
return limit
|
||||||
|
|
||||||
|
|
||||||
def _validate_internal_name(name, type_='name'):
|
def _validate_internal_name(name, type_='name'):
|
||||||
if RESERVED in name and not name.startswith(RESERVED):
|
if RESERVED in name and not name.startswith(RESERVED):
|
||||||
raise HTTPBadRequest(body='Invalid reserved-namespace %s' % (type_))
|
raise HTTPBadRequest(body='Invalid reserved-namespace %s' % (type_))
|
||||||
|
@ -34,7 +34,7 @@ from swift.common.db import DatabaseAlreadyExists
|
|||||||
from swift.common.container_sync_realms import ContainerSyncRealms
|
from swift.common.container_sync_realms import ContainerSyncRealms
|
||||||
from swift.common.request_helpers import get_param, \
|
from swift.common.request_helpers import get_param, \
|
||||||
split_and_validate_path, is_sys_or_user_meta, \
|
split_and_validate_path, is_sys_or_user_meta, \
|
||||||
validate_internal_container, validate_internal_obj
|
validate_internal_container, validate_internal_obj, constrain_req_limit
|
||||||
from swift.common.utils import get_logger, hash_path, public, \
|
from swift.common.utils import get_logger, hash_path, public, \
|
||||||
Timestamp, storage_directory, validate_sync_to, \
|
Timestamp, storage_directory, validate_sync_to, \
|
||||||
config_true_value, timing_stats, replication, \
|
config_true_value, timing_stats, replication, \
|
||||||
@ -689,16 +689,8 @@ class ContainerController(BaseStorageServer):
|
|||||||
delimiter = get_param(req, 'delimiter')
|
delimiter = get_param(req, 'delimiter')
|
||||||
marker = get_param(req, 'marker', '')
|
marker = get_param(req, 'marker', '')
|
||||||
end_marker = get_param(req, 'end_marker')
|
end_marker = get_param(req, 'end_marker')
|
||||||
limit = constraints.CONTAINER_LISTING_LIMIT
|
limit = constrain_req_limit(req, constraints.CONTAINER_LISTING_LIMIT)
|
||||||
given_limit = get_param(req, 'limit')
|
|
||||||
reverse = config_true_value(get_param(req, 'reverse'))
|
reverse = config_true_value(get_param(req, 'reverse'))
|
||||||
if given_limit and given_limit.isdigit():
|
|
||||||
limit = int(given_limit)
|
|
||||||
if limit > constraints.CONTAINER_LISTING_LIMIT:
|
|
||||||
return HTTPPreconditionFailed(
|
|
||||||
request=req,
|
|
||||||
body='Maximum limit is %d'
|
|
||||||
% constraints.CONTAINER_LISTING_LIMIT)
|
|
||||||
out_content_type = listing_formats.get_listing_content_type(req)
|
out_content_type = listing_formats.get_listing_content_type(req)
|
||||||
try:
|
try:
|
||||||
check_drive(self.root, drive, self.mount_check)
|
check_drive(self.root, drive, self.mount_check)
|
||||||
|
@ -209,6 +209,40 @@ class TestObjectVersioning(TestObjectVersioningBase):
|
|||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
config_true_value(self.env.container.info()['versions_enabled']))
|
config_true_value(self.env.container.info()['versions_enabled']))
|
||||||
|
|
||||||
|
def test_account_list_containers(self):
|
||||||
|
cont_listing = self.env.account.containers()
|
||||||
|
self.assertEqual(cont_listing, [self.env.container.name,
|
||||||
|
self.env.unversioned_container.name])
|
||||||
|
self.env.account.delete_containers()
|
||||||
|
prefix = Utils.create_name()
|
||||||
|
|
||||||
|
def get_name(i):
|
||||||
|
return prefix + '-%02d' % i
|
||||||
|
|
||||||
|
num_container = [15, 20]
|
||||||
|
for i in range(num_container[1]):
|
||||||
|
name = get_name(i)
|
||||||
|
container = self.env.account.container(name)
|
||||||
|
container.create()
|
||||||
|
|
||||||
|
limit = 5
|
||||||
|
cont_listing = self.env.account.containers(parms={'limit': limit})
|
||||||
|
self.assertEqual(cont_listing, [get_name(i) for i in range(limit)])
|
||||||
|
|
||||||
|
for i in range(num_container[0], num_container[1]):
|
||||||
|
name = get_name(i)
|
||||||
|
container = self.env.account.container(name)
|
||||||
|
container.update_metadata(
|
||||||
|
hdrs={self.env.versions_header_key: 'True'})
|
||||||
|
|
||||||
|
cont_listing = self.env.account.containers(parms={'limit': limit})
|
||||||
|
self.assertEqual(cont_listing, [get_name(i) for i in range(limit)])
|
||||||
|
|
||||||
|
# we're in charge of getting everything back to normal
|
||||||
|
self.env.account.delete_containers()
|
||||||
|
self.env.container.create()
|
||||||
|
self.env.unversioned_container.create()
|
||||||
|
|
||||||
def assert_previous_version(self, object_name, version_id, content,
|
def assert_previous_version(self, object_name, version_id, content,
|
||||||
content_type, expected_headers={},
|
content_type, expected_headers={},
|
||||||
not_expected_header_keys=[],
|
not_expected_header_keys=[],
|
||||||
|
@ -3067,6 +3067,20 @@ class ObjectVersioningTestAccountOperations(ObjectVersioningBaseTestCase):
|
|||||||
}]
|
}]
|
||||||
self.assertEqual(expected, json.loads(body))
|
self.assertEqual(expected, json.loads(body))
|
||||||
|
|
||||||
|
req.query_string = 'limit=1'
|
||||||
|
status, headers, body = self.call_ov(req)
|
||||||
|
self.assertEqual(status, '200 OK')
|
||||||
|
self.assertEqual(1, len(json.loads(body)))
|
||||||
|
|
||||||
|
req.query_string = 'limit=foo'
|
||||||
|
status, headers, body = self.call_ov(req)
|
||||||
|
self.assertEqual(status, '200 OK')
|
||||||
|
self.assertEqual(2, len(json.loads(body)))
|
||||||
|
|
||||||
|
req.query_string = 'limit=100000000000000000000000'
|
||||||
|
status, headers, body = self.call_ov(req)
|
||||||
|
self.assertEqual(status, '412 Precondition Failed')
|
||||||
|
|
||||||
def test_list_containers_prefix(self):
|
def test_list_containers_prefix(self):
|
||||||
listing_body = [{
|
listing_body = [{
|
||||||
'bytes': 0,
|
'bytes': 0,
|
||||||
|
@ -29,6 +29,19 @@ server_types = ['account', 'container', 'object']
|
|||||||
|
|
||||||
|
|
||||||
class TestRequestHelpers(unittest.TestCase):
|
class TestRequestHelpers(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_constrain_req_limit(self):
|
||||||
|
req = Request.blank('')
|
||||||
|
self.assertEqual(10, rh.constrain_req_limit(req, 10))
|
||||||
|
req = Request.blank('', query_string='limit=1')
|
||||||
|
self.assertEqual(1, rh.constrain_req_limit(req, 10))
|
||||||
|
req = Request.blank('', query_string='limit=1.0')
|
||||||
|
self.assertEqual(10, rh.constrain_req_limit(req, 10))
|
||||||
|
req = Request.blank('', query_string='limit=11')
|
||||||
|
with self.assertRaises(HTTPException) as raised:
|
||||||
|
rh.constrain_req_limit(req, 10)
|
||||||
|
self.assertEqual(raised.exception.status_int, 412)
|
||||||
|
|
||||||
def test_is_user_meta(self):
|
def test_is_user_meta(self):
|
||||||
m_type = 'meta'
|
m_type = 'meta'
|
||||||
for st in server_types:
|
for st in server_types:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user