diff options
author | Alistair Coles <alistairncoles@gmail.com> | 2018-04-19 18:58:44 +0100 |
---|---|---|
committer | Alistair Coles <alistairncoles@gmail.com> | 2018-04-23 14:44:20 +0100 |
commit | f47daf1131e8e45e736b693c635a7b10805c00d1 (patch) | |
tree | 9d9b66a2eb427134cd7d76f3c1d0c0f07dd70aa5 | |
parent | c0ffbd5eee04bc94a4fdeb8bbdeccf1e7338867d (diff) | |
download | swift-f47daf1131e8e45e736b693c635a7b10805c00d1.tar.gz |
Make first GET to root return objects or shard ranges as appropriate
Before the first backend container GET request was always for
objects. The proxy then deduced the container's sharding state from
the response headers and if appropriate made a second request for
shard ranges. This was inefficient.
With this patch the first backend container GET request has
X-Backend-Record-Type set to 'auto' which causes the container server
to return shard ranges if it is sharding or sharded, or objects
otherwise. The proxy can deduce which record type it has received from
the response header X-Backend-Record-Type and behave accordingly.
Change-Id: If507d5b5d3e315007bf78f38ebdda8580cbc45cc
-rw-r--r-- | swift/container/server.py | 6 | ||||
-rw-r--r-- | swift/proxy/controllers/container.py | 44 | ||||
-rw-r--r-- | test/probe/test_sharder.py | 22 | ||||
-rw-r--r-- | test/unit/container/test_server.py | 140 | ||||
-rw-r--r-- | test/unit/proxy/controllers/test_container.py | 324 | ||||
-rw-r--r-- | test/unit/proxy/test_server.py | 4 |
6 files changed, 320 insertions, 220 deletions
diff --git a/swift/container/server.py b/swift/container/server.py index b75b79ba2..bcb7cd602 100644 --- a/swift/container/server.py +++ b/swift/container/server.py @@ -25,7 +25,8 @@ from eventlet import Timeout import swift.common.db from swift.container.sync_store import ContainerSyncStore from swift.container.backend import ContainerBroker, DATADIR, \ - RECORD_TYPE_SHARD_NODE, UNSHARDED, SHARD_UPDATE_STATES, db_state_text + RECORD_TYPE_SHARD_NODE, UNSHARDED, SHARD_UPDATE_STATES, db_state_text, \ + SHARDING, SHARDED from swift.container.replicator import ContainerReplicatorRpc from swift.common.db import DatabaseAlreadyExists from swift.common.container_sync_realms import ContainerSyncRealms @@ -584,6 +585,9 @@ class ContainerController(BaseStorageServer): record_type = req.headers.get('x-backend-record-type', '').lower() include_deleted = config_true_value( req.headers.get('x-backend-include-deleted', False)) + if record_type == 'auto' and info.get('db_state') in (SHARDING, + SHARDED): + record_type = 'shard' if record_type == 'shard': resp_headers = gen_resp_headers(info) override_deleted = config_true_value( diff --git a/swift/proxy/controllers/container.py b/swift/proxy/controllers/container.py index acec6b878..d3e3fad7b 100644 --- a/swift/proxy/controllers/container.py +++ b/swift/proxy/controllers/container.py @@ -18,7 +18,7 @@ import json from six.moves.urllib.parse import unquote from swift.common.utils import public, csv_append, Timestamp, \ - config_true_value + config_true_value, ShardRange from swift.common.constraints import check_metadata, CONTAINER_LISTING_LIMIT from swift.common.http import HTTP_ACCEPTED, is_success from swift.common.request_helpers import get_sys_meta_prefix @@ -106,30 +106,26 @@ class ContainerController(Controller): node_iter = self.app.iter_nodes(self.app.container_ring, part) params = req.params params['format'] = 'json' + record_type = req.headers.get('X-Backend-Record-Type', '').lower() + if not record_type: + record_type = 'auto' + req.headers['X-Backend-Record-Type'] = 'auto' + params['states'] = 'listing' req.params = params - # TODO: if cached container info tells us container is sharded then - # skip straight to _get_sharded resp = self.GETorHEAD_base( req, _('Container'), node_iter, part, req.swift_entity_path, concurrency) - sharding_state = resp.headers.get( - 'X-Backend-Sharding-State', 'unsharded') - record_type = req.headers.get('X-Backend-Record-Type', '').lower() - self.app.logger.debug('GET for container in state %s' % sharding_state) - if all([req.method == "GET", - sharding_state in ('sharding', 'sharded'), - record_type not in ('object', 'shard')]): + resp_record_type = resp.headers.get('X-Backend-Record-Type', '') + if all((req.method == "GET", record_type == 'auto', + resp_record_type.lower() == 'shard')): resp = self._get_from_shards(req, resp) # Cache this. We just made a request to a storage node and got # up-to-date information for the container. resp.headers['X-Backend-Recheck-Container-Existence'] = str( self.app.recheck_container_existence) - # TODO: seems like a good idea to not set cache for shard/all - # requests, but revisit this at some point - if record_type != 'shard': - set_info_cache(self.app, req.environ, self.account_name, - self.container_name, resp) + set_info_cache(self.app, req.environ, self.account_name, + self.container_name, resp) if 'swift.authorize' in req.environ: req.acl = resp.headers.get('x-container-read') aresp = req.environ['swift.authorize'](req) @@ -149,11 +145,12 @@ class ContainerController(Controller): return resp def _get_from_shards(self, req, resp): - # get the list of ShardRanges that contain the requested listing range - # by using original request params - ranges = self._get_shard_ranges( - req, self.account_name, self.container_name, states='listing') - if not ranges: + # construct listing using shards described by the response body + shard_ranges = [ShardRange.from_dict(data) + for data in json.loads(resp.body)] + self.app.logger.debug('GET listing from %s shards for: %s', + len(shard_ranges), req.path_qs) + if not shard_ranges: # can't find ranges or there was a problem getting the ranges. So # return what we have. return resp @@ -161,12 +158,14 @@ class ContainerController(Controller): objects = [] req_limit = int(req.params.get('limit', CONTAINER_LISTING_LIMIT)) params = req.params.copy() + params.pop('states', None) + req.headers.pop('X-Backend-Record-Type', None) reverse = config_true_value(params.get('reverse')) marker = params.get('marker') end_marker = params.get('end_marker') limit = req_limit - for shard_range in ranges: + for shard_range in shard_ranges: params['limit'] = limit # Always set marker to ensure that object names less than or equal # to those already in the listing are not fetched @@ -194,9 +193,12 @@ class ContainerController(Controller): if (shard_range.account == self.account_name and shard_range.container == self.container_name): + # directed back to same container - force GET of objects headers = {'X-Backend-Record-Type': 'object'} else: headers = None + self.app.logger.debug('Getting from %s %s with %s', + shard_range, shard_range.name, headers) objs, shard_resp = self._get_container_listing( req, shard_range.account, shard_range.container, headers=headers, params=params) diff --git a/test/probe/test_sharder.py b/test/probe/test_sharder.py index a78e2a35f..0aeb9dcf2 100644 --- a/test/probe/test_sharder.py +++ b/test/probe/test_sharder.py @@ -608,9 +608,9 @@ class TestContainerSharding(ReplProbeTest): self.run_sharders(shard_1) self.assert_container_object_count(len(more_obj_names + obj_names)) - # we've added objects enough that we need to shard *again* into three - # new shards, but nothing happens until the root leader identifies - # shard candidate... + # we've added objects enough that we need to shard the first shard + # *again* into three new sub-shards, but nothing happens until the root + # leader identifies shard candidate... root_shard_ranges = self.direct_get_container_shard_ranges() for node, (hdrs, root_shards) in root_shard_ranges.items(): self.assertLengthEqual(root_shards, 2) @@ -628,7 +628,7 @@ class TestContainerSharding(ReplProbeTest): self.sharders.once(number=self.brain.node_numbers[0], additional_args='--partitions=%s' % self.brain.part) - # ... so third shard replica state is not moved to sharding + # ... so third replica of first shard state is not moved to sharding found_for_shard = self.categorize_container_dir_content( shard_1.account, shard_1.container) self.assertLengthEqual(found_for_shard['normal_dbs'], 3) @@ -637,15 +637,15 @@ class TestContainerSharding(ReplProbeTest): [ContainerBroker(db_file).get_own_shard_range().state for db_file in found_for_shard['normal_dbs']]) - # ...then run first cycle of shard sharders in order, leader first, to - # get to predictable state where all nodes have cleaved 2 out of 3 - # ranges...starting with first two nodes + # ...then run first cycle of first shard sharders in order, leader + # first, to get to predictable state where all nodes have cleaved 2 out + # of 3 ranges...starting with first two nodes for node_number in shard_1_nodes[:2]: self.sharders.once( number=node_number, additional_args='--partitions=%s' % shard_1_part) - # ... first two replicas start sharding + # ... first two replicas start sharding to sub-shards found_for_shard = self.categorize_container_dir_content( shard_1.account, shard_1.container) self.assertLengthEqual(found_for_shard['shard_dbs'], 2) @@ -682,7 +682,7 @@ class TestContainerSharding(ReplProbeTest): number=shard_1_nodes[2], additional_args='--partitions=%s' % shard_1_part) - # third replica is sharding but has no shard ranges yet... + # third replica is sharding but has no sub-shard ranges yet... found_for_shard = self.categorize_container_dir_content( shard_1.account, shard_1.container) self.assertLengthEqual(found_for_shard['shard_dbs'], 2) @@ -693,7 +693,7 @@ class TestContainerSharding(ReplProbeTest): ShardRange.SHARDING, broker.get_own_shard_range().state) self.assertFalse(broker.get_shard_ranges()) - # ...until shard ranges are replicated from another shard replica; + # ...until sub-shard ranges are replicated from another shard replica; # there may also be a sub-shard replica missing so run replicators on # all nodes to fix that if necessary self.brain.servers.start(number=shard_1_nodes[2]) @@ -704,7 +704,7 @@ class TestContainerSharding(ReplProbeTest): number=shard_1_nodes[2], additional_args='--partitions=%s' % shard_1_part) - # check original first shard range state and shards - all replicas + # check original first shard range state and sub-shards - all replicas # should now be in consistent state found_for_shard = self.categorize_container_dir_content( shard_1.account, shard_1.container) diff --git a/test/unit/container/test_server.py b/test/unit/container/test_server.py index e8014b43e..ef3136fe2 100644 --- a/test/unit/container/test_server.py +++ b/test/unit/container/test_server.py @@ -2783,6 +2783,146 @@ class TestContainerController(unittest.TestCase): do_test(False, []) do_test(True, shard_ranges) + def test_GET_auto_record_type(self): + # make a container + ts_iter = make_timestamp_iter() + ts_now = Timestamp.now() # used when mocking Timestamp.now() + headers = {'X-Timestamp': next(ts_iter).normal} + req = Request.blank('/sda1/p/a/c', method='PUT', headers=headers) + self.assertEqual(201, req.get_response(self.controller).status_int) + # PUT some objects + objects = [{'name': 'obj_%d' % i, + 'x-timestamp': next(ts_iter).normal, + 'x-content-type': 'text/plain', + 'x-etag': 'etag_%d' % i, + 'x-size': 1024 * i + } for i in range(2)] + for obj in objects: + req = Request.blank('/sda1/p/a/c/%s' % obj['name'], method='PUT', + headers=obj) + self._update_object_put_headers(req) + resp = req.get_response(self.controller) + self.assertEqual(201, resp.status_int) + # PUT some shard ranges + shard_bounds = [('', 'm', ShardRange.CLEAVED), + ('m', '', ShardRange.CREATED)] + shard_ranges = [ + ShardRange('.sharded_a/_%s' % upper, next(ts_iter), + lower, upper, + i * 100, i * 1000, meta_timestamp=next(ts_iter), + state=state, state_timestamp=next(ts_iter)) + for i, (lower, upper, state) in enumerate(shard_bounds)] + for shard_range in shard_ranges: + self._put_shard_range(shard_range) + + broker = self.controller._get_container_broker('sda1', 'p', 'a', 'c') + + def assert_GET_objects(req, expected_objects): + resp = req.get_response(self.controller) + self.assertEqual(resp.status_int, 200) + self.assertEqual(resp.content_type, 'application/json') + expected = [ + dict(hash=obj['x-etag'], bytes=obj['x-size'], + content_type=obj['x-content-type'], + last_modified=Timestamp(obj['x-timestamp']).isoformat, + name=obj['name']) for obj in expected_objects] + self.assertEqual(expected, json.loads(resp.body)) + self.assertIn('X-Backend-Record-Type', resp.headers) + self.assertEqual( + 'object', resp.headers.pop('X-Backend-Record-Type')) + resp.headers.pop('Content-Length') + return resp + + def assert_GET_shard_ranges(req, expected_shard_ranges): + with mock.patch('swift.common.utils.Timestamp.now', + classmethod(lambda ts_cls: ts_now)): + resp = req.get_response(self.controller) + self.assertEqual(resp.status_int, 200) + self.assertEqual(resp.content_type, 'application/json') + expected = [ + dict(sr, last_modified=Timestamp(sr.timestamp).isoformat) + for sr in expected_shard_ranges] + self.assertEqual(expected, json.loads(resp.body)) + self.assertIn('X-Backend-Record-Type', resp.headers) + self.assertEqual( + 'shard', resp.headers.pop('X-Backend-Record-Type')) + resp.headers.pop('Content-Length') + return resp + + # unsharded + req = Request.blank('/sda1/p/a/c?format=json', method='GET', + headers={'X-Backend-Record-Type': 'auto'}) + resp = assert_GET_objects(req, objects) + headers = resp.headers + req = Request.blank('/sda1/p/a/c?format=json', method='GET', + headers={'X-Backend-Record-Type': 'shard'}) + resp = assert_GET_shard_ranges(req, shard_ranges) + self.assertEqual(headers, resp.headers) + req = Request.blank('/sda1/p/a/c?format=json', method='GET', + headers={'X-Backend-Record-Type': 'object'}) + resp = assert_GET_objects(req, objects) + self.assertEqual(headers, resp.headers) + req = Request.blank('/sda1/p/a/c?format=json', method='GET') + resp = assert_GET_objects(req, objects) + self.assertEqual(headers, resp.headers) + + # move to sharding state + own_sr = broker.get_own_shard_range() + own_sr.update_state(ShardRange.SHARDING, state_timestamp=next(ts_iter)) + own_sr.epoch = next(ts_iter) + broker.merge_shard_ranges(own_sr) + self.assertTrue(broker.set_sharding_state()) + req = Request.blank('/sda1/p/a/c?format=json', method='GET', + headers={'X-Backend-Record-Type': 'auto'}) + resp = assert_GET_shard_ranges(req, shard_ranges) + headers = resp.headers + req = Request.blank('/sda1/p/a/c?format=json', method='GET', + headers={'X-Backend-Record-Type': 'shard'}) + resp = assert_GET_shard_ranges(req, shard_ranges) + self.assertEqual(headers, resp.headers) + req = Request.blank('/sda1/p/a/c?format=json', method='GET', + headers={'X-Backend-Record-Type': 'object'}) + resp = assert_GET_objects(req, objects) + self.assertEqual(headers, resp.headers) + req = Request.blank('/sda1/p/a/c?format=json', method='GET') + resp = assert_GET_objects(req, objects) + self.assertEqual(headers, resp.headers) + + # limit is applied to objects but not shard ranges + req = Request.blank('/sda1/p/a/c?format=json&limit=1', method='GET', + headers={'X-Backend-Record-Type': 'auto'}) + resp = assert_GET_shard_ranges(req, shard_ranges) + headers = resp.headers + req = Request.blank('/sda1/p/a/c?format=json&limit=1', method='GET', + headers={'X-Backend-Record-Type': 'shard'}) + resp = assert_GET_shard_ranges(req, shard_ranges) + self.assertEqual(headers, resp.headers) + req = Request.blank('/sda1/p/a/c?format=json&limit=1', method='GET', + headers={'X-Backend-Record-Type': 'object'}) + resp = assert_GET_objects(req, objects[:1]) + self.assertEqual(headers, resp.headers) + req = Request.blank('/sda1/p/a/c?format=json&limit=1', method='GET') + resp = assert_GET_objects(req, objects[:1]) + self.assertEqual(headers, resp.headers) + + # move to sharded state + self.assertTrue(broker.set_sharded_state()) + req = Request.blank('/sda1/p/a/c?format=json', method='GET', + headers={'X-Backend-Record-Type': 'auto'}) + resp = assert_GET_shard_ranges(req, shard_ranges) + headers = resp.headers + req = Request.blank('/sda1/p/a/c?format=json', method='GET', + headers={'X-Backend-Record-Type': 'shard'}) + resp = assert_GET_shard_ranges(req, shard_ranges) + self.assertEqual(headers, resp.headers) + req = Request.blank('/sda1/p/a/c?format=json', method='GET', + headers={'X-Backend-Record-Type': 'object'}) + resp = assert_GET_objects(req, []) + self.assertEqual(headers, resp.headers) + req = Request.blank('/sda1/p/a/c?format=json', method='GET') + resp = assert_GET_objects(req, []) + self.assertEqual(headers, resp.headers) + def test_PUT_GET_to_sharding_container(self): broker = self.controller._get_container_broker('sda1', 'p', 'a', 'c') ts_iter = make_timestamp_iter() diff --git a/test/unit/proxy/controllers/test_container.py b/test/unit/proxy/controllers/test_container.py index 33af90d2d..ff84f7b57 100644 --- a/test/unit/proxy/controllers/test_container.py +++ b/test/unit/proxy/controllers/test_container.py @@ -498,8 +498,9 @@ class TestContainerController(TestRingBase): def test_GET_sharded_container(self): shard_bounds = (('', 'ham'), ('ham', 'pie'), ('pie', '')) - shard_ranges = [ShardRange('a/c', Timestamp.now(), lower, upper) - for lower, upper in shard_bounds] + shard_ranges = [ + ShardRange('.shards_a/c_%s' % upper, Timestamp.now(), lower, upper) + for lower, upper in shard_bounds] sr_dicts = [dict(sr) for sr in shard_ranges] sr_objs = [self._make_shard_objects(sr) for sr in shard_ranges] shard_resp_hdrs = [ @@ -532,8 +533,6 @@ class TestContainerController(TestRingBase): mock_responses = [ # status, body, headers (404, '', {}), - (200, {}, root_resp_hdrs), - (404, '', {}), (200, sr_dicts, root_shard_resp_hdrs), (200, sr_objs[0], shard_resp_hdrs[0]), (200, sr_objs[1], shard_resp_hdrs[1]), @@ -541,19 +540,18 @@ class TestContainerController(TestRingBase): ] expected_requests = [ # path, headers, params - ('a/c', {}, {}), # 404 - ('a/c', {}, {}), # 200 - ('a/c', {'X-Backend-Record-Type': 'shard'}, + ('a/c', {'X-Backend-Record-Type': 'auto'}, dict(states='listing')), # 404 - ('a/c', {'X-Backend-Record-Type': 'shard'}, + ('a/c', {'X-Backend-Record-Type': 'auto'}, dict(states='listing')), # 200 - (shard_ranges[0].name, {}, - dict(marker='', end_marker='ham\x00', limit=str(limit))), # 200 - (shard_ranges[1].name, {}, - dict(marker='h', end_marker='pie\x00', + (shard_ranges[0].name, {'X-Backend-Record-Type': 'auto'}, + dict(marker='', end_marker='ham\x00', limit=str(limit), + states='listing')), # 200 + (shard_ranges[1].name, {'X-Backend-Record-Type': 'auto'}, + dict(marker='h', end_marker='pie\x00', states='listing', limit=str(limit - len(sr_objs[0])))), # 200 - (shard_ranges[2].name, {}, - dict(marker='p', end_marker='', + (shard_ranges[2].name, {'X-Backend-Record-Type': 'auto'}, + dict(marker='p', end_marker='', states='listing', limit=str(limit - len(sr_objs[0] + sr_objs[1])))) # 200 ] @@ -567,7 +565,6 @@ class TestContainerController(TestRingBase): root_range = ShardRange('a/c', Timestamp.now(), 'pie', '') mock_responses = [ # status, body, headers - (200, {}, root_resp_hdrs), (200, sr_dicts[:2] + [dict(root_range)], root_shard_resp_hdrs), (200, sr_objs[0], shard_resp_hdrs[0]), (200, sr_objs[1], shard_resp_hdrs[1]), @@ -575,13 +572,13 @@ class TestContainerController(TestRingBase): ] expected_requests = [ # path, headers, params - ('a/c', {}, {}), # 200 - ('a/c', {'X-Backend-Record-Type': 'shard'}, + ('a/c', {'X-Backend-Record-Type': 'auto'}, dict(states='listing')), # 200 - (shard_ranges[0].name, {}, - dict(marker='', end_marker='ham\x00', limit=str(limit))), # 200 - (shard_ranges[1].name, {}, - dict(marker='h', end_marker='pie\x00', + (shard_ranges[0].name, {'X-Backend-Record-Type': 'auto'}, + dict(marker='', end_marker='ham\x00', limit=str(limit), + states='listing')), # 200 + (shard_ranges[1].name, {'X-Backend-Record-Type': 'auto'}, + dict(marker='h', end_marker='pie\x00', states='listing', limit=str(limit - len(sr_objs[0])))), # 200 (root_range.name, {'X-Backend-Record-Type': 'object'}, dict(marker='p', end_marker='', @@ -597,7 +594,6 @@ class TestContainerController(TestRingBase): # GET all objects in reverse mock_responses = [ # status, body, headers - (200, {}, root_resp_hdrs), (200, list(reversed(sr_dicts)), root_shard_resp_hdrs), (200, list(reversed(sr_objs[2])), shard_resp_hdrs[2]), (200, list(reversed(sr_objs[1])), shard_resp_hdrs[1]), @@ -605,18 +601,16 @@ class TestContainerController(TestRingBase): ] expected_requests = [ # path, headers, params - ('a/c', {}, dict(reverse='true')), # 200 - ('a/c', {'X-Backend-Record-Type': 'shard'}, - dict(states='listing', reverse='true')), # 404 - (shard_ranges[2].name, {}, + ('a/c', {'X-Backend-Record-Type': 'auto'}, + dict(states='listing', reverse='true')), + (shard_ranges[2].name, {'X-Backend-Record-Type': 'auto'}, dict(marker='', end_marker='pie', reverse='true', - limit=str(limit))), # 200 - (shard_ranges[1].name, {}, - dict(marker='q', end_marker='ham', + limit=str(limit), states='listing')), # 200 + (shard_ranges[1].name, {'X-Backend-Record-Type': 'auto'}, + dict(marker='q', end_marker='ham', states='listing', reverse='true', limit=str(limit - len(sr_objs[2])))), # 200 - (shard_ranges[0].name, {}, - dict(marker='i', end_marker='', - reverse='true', + (shard_ranges[0].name, {'X-Backend-Record-Type': 'auto'}, + dict(marker='i', end_marker='', states='listing', reverse='true', limit=str(limit - len(sr_objs[2] + sr_objs[1])))), # 200 ] @@ -632,28 +626,24 @@ class TestContainerController(TestRingBase): expected_objects = all_objects[:limit] mock_responses = [ (404, '', {}), - (200, {}, root_resp_hdrs), - (404, '', {}), (200, sr_dicts, root_shard_resp_hdrs), (200, sr_objs[0], shard_resp_hdrs[0]), (200, sr_objs[1], shard_resp_hdrs[1]), (200, sr_objs[2][:1], shard_resp_hdrs[2]) ] expected_requests = [ - ('a/c', {}, dict(limit=str(limit))), # 404 - ('a/c', {}, dict(limit=str(limit))), # 200 - ('a/c', {'X-Backend-Record-Type': 'shard'}, - dict(states='listing')), # 404 - ('a/c', {'X-Backend-Record-Type': 'shard'}, - dict(states='listing')), # 200 - (shard_ranges[0].name, {}, # 200 - dict(marker='', end_marker='ham\x00', + ('a/c', {'X-Backend-Record-Type': 'auto'}, + dict(limit=str(limit), states='listing')), # 404 + ('a/c', {'X-Backend-Record-Type': 'auto'}, + dict(limit=str(limit), states='listing')), # 200 + (shard_ranges[0].name, {'X-Backend-Record-Type': 'auto'}, # 200 + dict(marker='', end_marker='ham\x00', states='listing', limit=str(limit))), - (shard_ranges[1].name, {}, # 200 - dict(marker='h', end_marker='pie\x00', + (shard_ranges[1].name, {'X-Backend-Record-Type': 'auto'}, # 200 + dict(marker='h', end_marker='pie\x00', states='listing', limit=str(limit - len(sr_objs[0])))), - (shard_ranges[2].name, {}, # 200 - dict(marker='p', end_marker='', + (shard_ranges[2].name, {'X-Backend-Record-Type': 'auto'}, # 200 + dict(marker='p', end_marker='', states='listing', limit=str(limit - len(sr_objs[0] + sr_objs[1])))) ] resp = self._check_GET_shard_listing( @@ -668,25 +658,24 @@ class TestContainerController(TestRingBase): expected_objects = all_objects[first_included:] mock_responses = [ (404, '', {}), - (200, {}, root_resp_hdrs), (200, sr_dicts[1:], root_shard_resp_hdrs), (404, '', {}), (200, sr_objs[1][2:], shard_resp_hdrs[1]), (200, sr_objs[2], shard_resp_hdrs[2]) ] expected_requests = [ - ('a/c', {}, dict(marker=marker)), # 404 - ('a/c', {}, dict(marker=marker)), # 200 - ('a/c', {'X-Backend-Record-Type': 'shard'}, + ('a/c', {'X-Backend-Record-Type': 'auto'}, + dict(marker=marker, states='listing')), # 404 + ('a/c', {'X-Backend-Record-Type': 'auto'}, dict(marker=marker, states='listing')), # 200 - (shard_ranges[1].name, {}, # 404 - dict(marker=marker, end_marker='pie\x00', + (shard_ranges[1].name, {'X-Backend-Record-Type': 'auto'}, # 404 + dict(marker=marker, end_marker='pie\x00', states='listing', limit=str(limit))), - (shard_ranges[1].name, {}, # 200 - dict(marker=marker, end_marker='pie\x00', + (shard_ranges[1].name, {'X-Backend-Record-Type': 'auto'}, # 200 + dict(marker=marker, end_marker='pie\x00', states='listing', limit=str(limit))), - (shard_ranges[2].name, {}, # 200 - dict(marker='p', end_marker='', + (shard_ranges[2].name, {'X-Backend-Record-Type': 'auto'}, # 200 + dict(marker='p', end_marker='', states='listing', limit=str(limit - len(sr_objs[1][2:])))), ] resp = self._check_GET_shard_listing( @@ -700,25 +689,24 @@ class TestContainerController(TestRingBase): expected_objects = all_objects[:first_excluded] mock_responses = [ (404, '', {}), - (200, {}, root_resp_hdrs), (200, sr_dicts[:2], root_shard_resp_hdrs), (200, sr_objs[0], shard_resp_hdrs[0]), (404, '', {}), (200, sr_objs[1][:6], shard_resp_hdrs[1]) ] expected_requests = [ - ('a/c', {}, dict(end_marker=end_marker)), # 404 - ('a/c', {}, dict(end_marker=end_marker)), # 200 - ('a/c', {'X-Backend-Record-Type': 'shard'}, + ('a/c', {'X-Backend-Record-Type': 'auto'}, + dict(end_marker=end_marker, states='listing')), # 404 + ('a/c', {'X-Backend-Record-Type': 'auto'}, dict(end_marker=end_marker, states='listing')), # 200 - (shard_ranges[0].name, {}, # 200 - dict(marker='', end_marker='ham\x00', + (shard_ranges[0].name, {'X-Backend-Record-Type': 'auto'}, # 200 + dict(marker='', end_marker='ham\x00', states='listing', limit=str(limit))), - (shard_ranges[1].name, {}, # 404 - dict(marker='h', end_marker=end_marker, + (shard_ranges[1].name, {'X-Backend-Record-Type': 'auto'}, # 404 + dict(marker='h', end_marker=end_marker, states='listing', limit=str(limit - len(sr_objs[0])))), - (shard_ranges[1].name, {}, # 200 - dict(marker='h', end_marker=end_marker, + (shard_ranges[1].name, {'X-Backend-Record-Type': 'auto'}, # 200 + dict(marker='h', end_marker=end_marker, states='listing', limit=str(limit - len(sr_objs[0])))), ] resp = self._check_GET_shard_listing( @@ -730,27 +718,15 @@ class TestContainerController(TestRingBase): limit = 2 expected_objects = all_objects[first_included:first_excluded] mock_responses = [ - (404, '', {}), - (200, {}, root_resp_hdrs), - (404, '', {}), (200, sr_dicts[1:2], root_shard_resp_hdrs), (200, sr_objs[1][2:6], shard_resp_hdrs[1]) ] expected_requests = [ - ('a/c', {}, - dict(marker=marker, end_marker=end_marker, - limit=str(limit))), # 404 - ('a/c', {}, - dict(marker=marker, end_marker=end_marker, - limit=str(limit))), # 200 - ('a/c', {'X-Backend-Record-Type': 'shard'}, - dict(states='listing', - marker=marker, end_marker=end_marker)), # 404 - ('a/c', {'X-Backend-Record-Type': 'shard'}, - dict(states='listing', + ('a/c', {'X-Backend-Record-Type': 'auto'}, + dict(states='listing', limit=str(limit), marker=marker, end_marker=end_marker)), # 200 - (shard_ranges[1].name, {}, # 200 - dict(marker=marker, end_marker=end_marker, + (shard_ranges[1].name, {'X-Backend-Record-Type': 'auto'}, # 200 + dict(marker=marker, end_marker=end_marker, states='listing', limit=str(limit))), ] resp = self._check_GET_shard_listing( @@ -762,27 +738,15 @@ class TestContainerController(TestRingBase): # reverse with marker, end_marker expected_objects.reverse() mock_responses = [ - (404, '', {}), - (200, {}, root_resp_hdrs), - (404, '', {}), (200, sr_dicts[1:2], root_shard_resp_hdrs), (200, list(reversed(sr_objs[1][2:6])), shard_resp_hdrs[1]) ] expected_requests = [ - ('a/c', {}, + ('a/c', {'X-Backend-Record-Type': 'auto'}, dict(marker=end_marker, reverse='true', end_marker=marker, - limit=str(limit))), # 404 - ('a/c', {}, - dict(marker=end_marker, reverse='true', end_marker=marker, - limit=str(limit))), # 200 - ('a/c', {'X-Backend-Record-Type': 'shard'}, - dict(states='listing', marker=end_marker, - end_marker=marker, reverse='true')), # 404 - ('a/c', {'X-Backend-Record-Type': 'shard'}, - dict(states='listing', marker=end_marker, - end_marker=marker, reverse='true')), # 200 - (shard_ranges[1].name, {}, # 200 - dict(marker=end_marker, end_marker=marker, + limit=str(limit), states='listing',)), # 200 + (shard_ranges[1].name, {'X-Backend-Record-Type': 'auto'}, # 200 + dict(marker=end_marker, end_marker=marker, states='listing', limit=str(limit), reverse='true')), ] self._check_GET_shard_listing( @@ -802,7 +766,8 @@ class TestContainerController(TestRingBase): ('', 'pie', ShardRange.ACTIVE), ('lemon', '', ShardRange.ACTIVE)) shard_ranges = [ - ShardRange('a/c', Timestamp.now(), lower, upper, state=state) + ShardRange('.shards_a/c_' + upper, Timestamp.now(), lower, upper, + state=state) for lower, upper, state in shard_bounds] sr_dicts = [dict(sr) for sr in shard_ranges] sr_objs = [self._make_shard_objects(sr) for sr in shard_ranges] @@ -838,7 +803,6 @@ class TestContainerController(TestRingBase): objs_2 = [o for o in sr_objs[2] if o['name'] > sr_objs[1][-1]['name']] mock_responses = [ # status, body, headers - (200, {}, root_resp_hdrs), (200, sr_dicts, root_shard_resp_hdrs), (200, sr_objs[0], shard_resp_hdrs[0]), (200, objs_1, shard_resp_hdrs[1]), @@ -847,16 +811,16 @@ class TestContainerController(TestRingBase): # NB marker always advances to last object name expected_requests = [ # path, headers, params - ('a/c', {}, {}), # 200 - ('a/c', {'X-Backend-Record-Type': 'shard'}, + ('a/c', {'X-Backend-Record-Type': 'auto'}, dict(states='listing')), # 200 - (shard_ranges[0].name, {}, - dict(marker='', end_marker='ham\x00', limit=str(limit))), # 200 - (shard_ranges[1].name, {}, - dict(marker='h', end_marker='pie\x00', + (shard_ranges[0].name, {'X-Backend-Record-Type': 'auto'}, + dict(marker='', end_marker='ham\x00', states='listing', + limit=str(limit))), # 200 + (shard_ranges[1].name, {'X-Backend-Record-Type': 'auto'}, + dict(marker='h', end_marker='pie\x00', states='listing', limit=str(limit - len(sr_objs[0])))), # 200 - (shard_ranges[2].name, {}, - dict(marker='p', end_marker='', + (shard_ranges[2].name, {'X-Backend-Record-Type': 'auto'}, + dict(marker='p', end_marker='', states='listing', limit=str(limit - len(sr_objs[0] + objs_1)))) # 200 ] @@ -875,7 +839,6 @@ class TestContainerController(TestRingBase): objs_1 = [o for o in sr_objs[1] if o['name'] < sr_objs[2][0]['name']] mock_responses = [ # status, body, headers - (200, {}, root_resp_hdrs), (200, list(reversed(sr_dicts)), root_shard_resp_hdrs), (200, list(reversed(sr_objs[2])), shard_resp_hdrs[2]), (200, list(reversed(objs_1)), shard_resp_hdrs[1]), @@ -884,17 +847,17 @@ class TestContainerController(TestRingBase): # NB marker always advances to last object name expected_requests = [ # path, headers, params - ('a/c', {}, dict(reverse='true')), # 200 - ('a/c', {'X-Backend-Record-Type': 'shard'}, + ('a/c', {'X-Backend-Record-Type': 'auto'}, dict(states='listing', reverse='true')), # 200 - (shard_ranges[2].name, {}, - dict(marker='', end_marker='lemon', limit=str(limit), + (shard_ranges[2].name, {'X-Backend-Record-Type': 'auto'}, + dict(marker='', end_marker='lemon', states='listing', + limit=str(limit), reverse='true')), # 200 - (shard_ranges[1].name, {}, - dict(marker='m', end_marker='', reverse='true', + (shard_ranges[1].name, {'X-Backend-Record-Type': 'auto'}, + dict(marker='m', end_marker='', reverse='true', states='listing', limit=str(limit - len(sr_objs[2])))), # 200 - (shard_ranges[0].name, {}, - dict(marker='A', end_marker='', reverse='true', + (shard_ranges[0].name, {'X-Backend-Record-Type': 'auto'}, + dict(marker='A', end_marker='', reverse='true', states='listing', limit=str(limit - len(sr_objs[2] + objs_1)))) # 200 ] @@ -909,8 +872,9 @@ class TestContainerController(TestRingBase): def test_GET_sharded_container_gap_in_shards(self): # verify ordered listing even if unexpected gap between shard ranges shard_bounds = (('', 'ham'), ('onion', 'pie'), ('rhubarb', '')) - shard_ranges = [ShardRange('a/c', Timestamp.now(), lower, upper) - for lower, upper in shard_bounds] + shard_ranges = [ + ShardRange('.shards_a/c_' + upper, Timestamp.now(), lower, upper) + for lower, upper in shard_bounds] sr_dicts = [dict(sr) for sr in shard_ranges] sr_objs = [self._make_shard_objects(sr) for sr in shard_ranges] shard_resp_hdrs = [ @@ -938,7 +902,6 @@ class TestContainerController(TestRingBase): mock_responses = [ # status, body, headers - (200, {}, root_resp_hdrs), (200, sr_dicts, root_shard_resp_hdrs), (200, sr_objs[0], shard_resp_hdrs[0]), (200, sr_objs[1], shard_resp_hdrs[1]), @@ -947,16 +910,16 @@ class TestContainerController(TestRingBase): # NB marker always advances to last object name expected_requests = [ # path, headers, params - ('a/c', {}, {}), # 200 - ('a/c', {'X-Backend-Record-Type': 'shard'}, + ('a/c', {'X-Backend-Record-Type': 'auto'}, dict(states='listing')), # 200 - (shard_ranges[0].name, {}, - dict(marker='', end_marker='ham\x00', limit=str(limit))), # 200 - (shard_ranges[1].name, {}, - dict(marker='h', end_marker='pie\x00', + (shard_ranges[0].name, {'X-Backend-Record-Type': 'auto'}, + dict(marker='', end_marker='ham\x00', states='listing', + limit=str(limit))), # 200 + (shard_ranges[1].name, {'X-Backend-Record-Type': 'auto'}, + dict(marker='h', end_marker='pie\x00', states='listing', limit=str(limit - len(sr_objs[0])))), # 200 - (shard_ranges[2].name, {}, - dict(marker='p', end_marker='', + (shard_ranges[2].name, {'X-Backend-Record-Type': 'auto'}, + dict(marker='p', end_marker='', states='listing', limit=str(limit - len(sr_objs[0] + sr_objs[1])))) # 200 ] @@ -968,8 +931,9 @@ class TestContainerController(TestRingBase): def test_GET_sharded_container_empty_shard(self): # verify ordered listing when a shard is empty shard_bounds = (('', 'ham'), ('ham', 'pie'), ('lemon', '')) - shard_ranges = [ShardRange('a/c', Timestamp.now(), lower, upper) - for lower, upper in shard_bounds] + shard_ranges = [ + ShardRange('.shards_a/c_%s' % upper, Timestamp.now(), lower, upper) + for lower, upper in shard_bounds] sr_dicts = [dict(sr) for sr in shard_ranges] sr_objs = [self._make_shard_objects(sr) for sr in shard_ranges] # empty second shard range @@ -999,7 +963,6 @@ class TestContainerController(TestRingBase): mock_responses = [ # status, body, headers - (200, {}, root_resp_hdrs), (200, sr_dicts, root_shard_resp_hdrs), (200, sr_objs[0], shard_resp_hdrs[0]), (200, sr_objs[1], shard_resp_hdrs[1]), @@ -1008,16 +971,16 @@ class TestContainerController(TestRingBase): # NB marker always advances to last object name expected_requests = [ # path, headers, params - ('a/c', {}, {}), # 200 - ('a/c', {'X-Backend-Record-Type': 'shard'}, + ('a/c', {'X-Backend-Record-Type': 'auto'}, dict(states='listing')), # 200 - (shard_ranges[0].name, {}, - dict(marker='', end_marker='ham\x00', limit=str(limit))), # 200 - (shard_ranges[1].name, {}, - dict(marker='h', end_marker='pie\x00', + (shard_ranges[0].name, {'X-Backend-Record-Type': 'auto'}, + dict(marker='', end_marker='ham\x00', states='listing', + limit=str(limit))), # 200 + (shard_ranges[1].name, {'X-Backend-Record-Type': 'auto'}, + dict(marker='h', end_marker='pie\x00', states='listing', limit=str(limit - len(sr_objs[0])))), # 200 - (shard_ranges[2].name, {}, - dict(marker='h', end_marker='', + (shard_ranges[2].name, {'X-Backend-Record-Type': 'auto'}, + dict(marker='h', end_marker='', states='listing', limit=str(limit - len(sr_objs[0] + sr_objs[1])))) # 200 ] @@ -1029,7 +992,6 @@ class TestContainerController(TestRingBase): # marker in empty second range mock_responses = [ # status, body, headers - (200, {}, root_resp_hdrs), (200, sr_dicts[1:], root_shard_resp_hdrs), (200, sr_objs[1], shard_resp_hdrs[1]), (200, sr_objs[2], shard_resp_hdrs[2]) @@ -1037,14 +999,14 @@ class TestContainerController(TestRingBase): # NB marker unchanged when getting from third range expected_requests = [ # path, headers, params - ('a/c', {}, dict(marker='koolaid')), # 200 - ('a/c', {'X-Backend-Record-Type': 'shard'}, + ('a/c', {'X-Backend-Record-Type': 'auto'}, dict(states='listing', marker='koolaid')), # 200 - (shard_ranges[1].name, {}, - dict(marker='koolaid', end_marker='pie\x00', + (shard_ranges[1].name, {'X-Backend-Record-Type': 'auto'}, + dict(marker='koolaid', end_marker='pie\x00', states='listing', limit=str(limit))), # 200 - (shard_ranges[2].name, {}, - dict(marker='koolaid', end_marker='', limit=str(limit))) # 200 + (shard_ranges[2].name, {'X-Backend-Record-Type': 'auto'}, + dict(marker='koolaid', end_marker='', states='listing', + limit=str(limit))) # 200 ] resp = self._check_GET_shard_listing( @@ -1056,7 +1018,6 @@ class TestContainerController(TestRingBase): # marker in empty second range, reverse mock_responses = [ # status, body, headers - (200, {}, root_resp_hdrs), (200, list(reversed(sr_dicts[:2])), root_shard_resp_hdrs), (200, list(reversed(sr_objs[1])), shard_resp_hdrs[1]), (200, list(reversed(sr_objs[0])), shard_resp_hdrs[2]) @@ -1064,15 +1025,14 @@ class TestContainerController(TestRingBase): # NB marker unchanged when getting from first range expected_requests = [ # path, headers, params - ('a/c', {}, dict(marker='koolaid', reverse='true')), # 200 - ('a/c', {'X-Backend-Record-Type': 'shard'}, + ('a/c', {'X-Backend-Record-Type': 'auto'}, dict(states='listing', marker='koolaid', reverse='true')), # 200 - (shard_ranges[1].name, {}, + (shard_ranges[1].name, {'X-Backend-Record-Type': 'auto'}, dict(marker='koolaid', end_marker='ham', reverse='true', - limit=str(limit))), # 200 - (shard_ranges[0].name, {}, + states='listing', limit=str(limit))), # 200 + (shard_ranges[0].name, {'X-Backend-Record-Type': 'auto'}, dict(marker='koolaid', end_marker='', reverse='true', - limit=str(limit))) # 200 + states='listing', limit=str(limit))) # 200 ] resp = self._check_GET_shard_listing( @@ -1084,8 +1044,9 @@ class TestContainerController(TestRingBase): def _check_GET_sharded_container_shard_error(self, error): # verify ordered listing when a shard is empty shard_bounds = (('', 'ham'), ('ham', 'pie'), ('lemon', '')) - shard_ranges = [ShardRange('a/c', Timestamp.now(), lower, upper) - for lower, upper in shard_bounds] + shard_ranges = [ + ShardRange('.shards_a/c_%s' % upper, Timestamp.now(), lower, upper) + for lower, upper in shard_bounds] sr_dicts = [dict(sr) for sr in shard_ranges] sr_objs = [self._make_shard_objects(sr) for sr in shard_ranges] # empty second shard range @@ -1115,7 +1076,6 @@ class TestContainerController(TestRingBase): mock_responses = [ # status, body, headers - (200, {}, root_resp_hdrs), (200, sr_dicts, root_shard_resp_hdrs), (200, sr_objs[0], shard_resp_hdrs[0])] + \ [(error, [], {})] * 2 * self.CONTAINER_REPLICAS + \ @@ -1124,17 +1084,17 @@ class TestContainerController(TestRingBase): # NB marker always advances to last object name expected_requests = [ # path, headers, params - ('a/c', {}, {}), # 200 - ('a/c', {'X-Backend-Record-Type': 'shard'}, + ('a/c', {'X-Backend-Record-Type': 'auto'}, dict(states='listing')), # 200 - (shard_ranges[0].name, {}, - dict(marker='', end_marker='ham\x00', limit=str(limit)))] \ - + [(shard_ranges[1].name, {}, - dict(marker='h', end_marker='pie\x00', + (shard_ranges[0].name, {'X-Backend-Record-Type': 'auto'}, + dict(marker='', end_marker='ham\x00', states='listing', + limit=str(limit)))] \ + + [(shard_ranges[1].name, {'X-Backend-Record-Type': 'auto'}, + dict(marker='h', end_marker='pie\x00', states='listing', limit=str(limit - len(sr_objs[0])))) ] * 2 * self.CONTAINER_REPLICAS \ - + [(shard_ranges[2].name, {}, - dict(marker='h', end_marker='', + + [(shard_ranges[2].name, {'X-Backend-Record-Type': 'auto'}, + dict(marker='h', end_marker='', states='listing', limit=str(limit - len(sr_objs[0] + sr_objs[1]))))] resp = self._check_GET_shard_listing( @@ -1150,7 +1110,7 @@ class TestContainerController(TestRingBase): # one shard is in process of sharding shard_bounds = (('', 'ham'), ('ham', 'pie'), ('pie', '')) shard_ranges = [ - ShardRange('a/c_' + upper, Timestamp.now(), lower, upper) + ShardRange('.shards_a/c_' + upper, Timestamp.now(), lower, upper) for lower, upper in shard_bounds] sr_dicts = [dict(sr) for sr in shard_ranges] sr_objs = [self._make_shard_objects(sr) for sr in shard_ranges] @@ -1198,10 +1158,8 @@ class TestContainerController(TestRingBase): mock_responses = [ # status, body, headers - (200, {}, root_resp_hdrs), (200, sr_dicts, root_shard_resp_hdrs), (200, sr_objs[0], shard_resp_hdrs[0]), - (200, {}, shard_resp_hdrs[1]), (200, sub_sr_dicts + [sr_dicts[1]], shard_1_shard_resp_hdrs), (200, sub_sr_objs[0], sub_shard_resp_hdrs[0]), (200, sub_sr_objs[1], sub_shard_resp_hdrs[1]), @@ -1211,37 +1169,33 @@ class TestContainerController(TestRingBase): ] # NB marker always advances to last object name expected_requests = [ - # path, headers, params - ('a/c', {}, {}), # get root shard ranges - ('a/c', {'X-Backend-Record-Type': 'shard'}, + ('a/c', {'X-Backend-Record-Type': 'auto'}, dict(states='listing')), # 200 # get first shard objects - (shard_ranges[0].name, {}, - dict(marker='', end_marker='ham\x00', limit=str(limit))), # 200 - # get second shard - (shard_ranges[1].name, {}, - dict(marker='h', end_marker='pie\x00', - limit=str(limit - len(sr_objs[0])))), # 200 + (shard_ranges[0].name, {'X-Backend-Record-Type': 'auto'}, + dict(marker='', end_marker='ham\x00', states='listing', + limit=str(limit))), # 200 # get second shard sub-shard ranges - (shard_ranges[1].name, {'X-Backend-Record-Type': 'shard'}, - dict(marker='h', end_marker='pie\x00', states='listing')), + (shard_ranges[1].name, {'X-Backend-Record-Type': 'auto'}, + dict(marker='h', end_marker='pie\x00', states='listing', + limit=str(limit - len(sr_objs[0])))), # get first sub-shard objects - (sub_shard_ranges[0].name, {}, - dict(marker='h', end_marker='juice\x00', + (sub_shard_ranges[0].name, {'X-Backend-Record-Type': 'auto'}, + dict(marker='h', end_marker='juice\x00', states='listing', limit=str(limit - len(sr_objs[0])))), # get second sub-shard objects - (sub_shard_ranges[1].name, {}, - dict(marker='j', end_marker='lemon\x00', + (sub_shard_ranges[1].name, {'X-Backend-Record-Type': 'auto'}, + dict(marker='j', end_marker='lemon\x00', states='listing', limit=str(limit - len(sr_objs[0] + sub_sr_objs[0])))), # get remainder of first shard objects - (shard_ranges[1].name, {}, + (shard_ranges[1].name, {'X-Backend-Record-Type': 'object'}, dict(marker='l', end_marker='pie\x00', limit=str(limit - len(sr_objs[0] + sub_sr_objs[0] + sub_sr_objs[1])))), # 200 # get third shard objects - (shard_ranges[2].name, {}, - dict(marker='p', end_marker='', + (shard_ranges[2].name, {'X-Backend-Record-Type': 'auto'}, + dict(marker='p', end_marker='', states='listing', limit=str(limit - len(sr_objs[0] + sr_objs[1])))) # 200 ] expected_objects = ( diff --git a/test/unit/proxy/test_server.py b/test/unit/proxy/test_server.py index 758ad7d54..743ad6b18 100644 --- a/test/unit/proxy/test_server.py +++ b/test/unit/proxy/test_server.py @@ -3346,7 +3346,7 @@ class TestReplicatedObjectController( 'x-backend-sharding-state': sharding_state, 'X-Backend-Record-Type': 'shard'} shard_range = utils.ShardRange( - '.sharded_a/c_shard', utils.Timestamp.now(), 'l', 'u') + '.shards_a/c_shard', utils.Timestamp.now(), 'l', 'u') body = json.dumps([dict(shard_range)]) with mocked_http_conn(*status_codes, headers=resp_headers, body=body) as fake_conn: @@ -3398,7 +3398,7 @@ class TestReplicatedObjectController( 'Host': 'localhost:80', 'Referer': '%s http://localhost/v1/a/c/o' % method, 'X-Backend-Storage-Policy-Index': '1', - 'X-Backend-Container-Path': '.sharded_a/c_shard' + 'X-Backend-Container-Path': shard_range.name }, } check_request(request, **expectations) |