diff --git a/swift/common/middleware/slo.py b/swift/common/middleware/slo.py index d8df829981..6a6b8294b8 100644 --- a/swift/common/middleware/slo.py +++ b/swift/common/middleware/slo.py @@ -537,7 +537,8 @@ class StaticLargeObject(object): def slo_hook(source_req, source_resp, sink_req): x_slo = source_resp.headers.get('X-Static-Large-Object') if (config_true_value(x_slo) - and source_req.params.get('multipart-manifest') != 'get'): + and source_req.params.get('multipart-manifest') != 'get' + and 'swift.post_as_copy' not in source_req.environ): source_resp = SloGetContext(self).get_or_head_response( source_req, source_resp.headers.items(), source_resp.app_iter) diff --git a/swift/proxy/controllers/obj.py b/swift/proxy/controllers/obj.py index a83242b5f0..5b7c00c4aa 100644 --- a/swift/proxy/controllers/obj.py +++ b/swift/proxy/controllers/obj.py @@ -268,12 +268,8 @@ class BaseObjectController(Controller): req.headers['Content-Length'] = 0 req.headers['X-Copy-From'] = quote('/%s/%s' % (self.container_name, self.object_name)) - req.headers['X-Fresh-Metadata'] = 'true' + req.environ['swift.post_as_copy'] = True req.environ['swift_versioned_copy'] = True - if req.environ.get('QUERY_STRING'): - req.environ['QUERY_STRING'] += '&multipart-manifest=get' - else: - req.environ['QUERY_STRING'] = 'multipart-manifest=get' resp = self.PUT(req) # Older editions returned 202 Accepted on object POSTs, so we'll # convert any 201 Created responses to that for compatibility with @@ -577,8 +573,11 @@ class BaseObjectController(Controller): if not req.content_type_manually_set: sink_req.headers['Content-Type'] = \ source_resp.headers['Content-Type'] - if config_true_value( - sink_req.headers.get('x-fresh-metadata', 'false')): + + fresh_meta_flag = config_true_value( + sink_req.headers.get('x-fresh-metadata', 'false')) + + if fresh_meta_flag or 'swift.post_as_copy' in sink_req.environ: # post-as-copy: ignore new sysmeta, copy existing sysmeta condition = lambda k: is_sys_meta('object', k) remove_items(sink_req.headers, condition) @@ -590,7 +589,8 @@ class BaseObjectController(Controller): # copy over x-static-large-object for POSTs and manifest copies if 'X-Static-Large-Object' in source_resp.headers and \ - req.params.get('multipart-manifest') == 'get': + (req.params.get('multipart-manifest') == 'get' or + 'swift.post_as_copy' in req.environ): sink_req.headers['X-Static-Large-Object'] = \ source_resp.headers['X-Static-Large-Object'] diff --git a/test/functional/swift_test_client.py b/test/functional/swift_test_client.py index 4d77bcced0..695ea202d7 100644 --- a/test/functional/swift_test_client.py +++ b/test/functional/swift_test_client.py @@ -851,7 +851,7 @@ class File(Base): finally: fobj.close() - def sync_metadata(self, metadata=None, cfg=None): + def sync_metadata(self, metadata=None, cfg=None, parms=None): if metadata is None: metadata = {} if cfg is None: @@ -868,7 +868,8 @@ class File(Base): else: headers['Content-Length'] = 0 - self.conn.make_request('POST', self.path, hdrs=headers, cfg=cfg) + self.conn.make_request('POST', self.path, hdrs=headers, + parms=parms, cfg=cfg) if self.conn.response.status not in (201, 202): raise ResponseError(self.conn.response, 'POST', diff --git a/test/functional/tests.py b/test/functional/tests.py index 3fbbdd784e..3f6f08b8d8 100644 --- a/test/functional/tests.py +++ b/test/functional/tests.py @@ -2151,6 +2151,7 @@ class TestSloEnv(object): 'manifest-bcd-submanifest')}, seg_info['seg_e']]), parms={'multipart-manifest': 'put'}) + cls.seg_info = seg_info class TestSlo(Base): @@ -2356,6 +2357,58 @@ class TestSlo(Base): except ValueError: self.fail("COPY didn't copy the manifest (invalid json on GET)") + def _make_manifest(self): + # To avoid the bug 1453807 on fast-post, make a new manifest + # for post test. + file_item = self.env.container.file("manifest-post") + seg_info = self.env.seg_info + file_item.write( + json.dumps([seg_info['seg_a'], seg_info['seg_b'], + seg_info['seg_c'], seg_info['seg_d'], + seg_info['seg_e']]), + parms={'multipart-manifest': 'put'}) + return file_item + + def test_slo_post_the_manifest_metadata_update(self): + file_item = self._make_manifest() + # sanity check, check the object is an SLO manifest + file_item.info() + file_item.header_fields([('slo', 'x-static-large-object')]) + + # POST a user metadata (i.e. x-object-meta-post) + file_item.sync_metadata({'post': 'update'}) + + updated = self.env.container.file("manifest-post") + updated.info() + updated.header_fields([('user-meta', 'x-object-meta-post')]) # sanity + updated_contents = updated.read(parms={'multipart-manifest': 'get'}) + try: + json.loads(updated_contents) + except ValueError: + self.fail("Unexpected content on GET, expected a json body") + + def test_slo_post_the_manifest_metadata_update_with_qs(self): + # multipart-manifest query should be ignored on post + for verb in ('put', 'get', 'delete'): + file_item = self._make_manifest() + # sanity check, check the object is an SLO manifest + file_item.info() + file_item.header_fields([('slo', 'x-static-large-object')]) + # POST a user metadata (i.e. x-object-meta-post) + file_item.sync_metadata(metadata={'post': 'update'}, + parms={'multipart-manifest': verb}) + updated = self.env.container.file("manifest-post") + updated.info() + updated.header_fields( + [('user-meta', 'x-object-meta-post')]) # sanity + updated_contents = updated.read( + parms={'multipart-manifest': 'get'}) + try: + json.loads(updated_contents) + except ValueError: + self.fail( + "Unexpected content on GET, expected a json body") + def test_slo_get_the_manifest(self): manifest = self.env.container.file("manifest-abcde") got_body = manifest.read(parms={'multipart-manifest': 'get'}) diff --git a/test/unit/proxy/controllers/test_obj.py b/test/unit/proxy/controllers/test_obj.py index a38e753ae0..b0e614a0dd 100755 --- a/test/unit/proxy/controllers/test_obj.py +++ b/test/unit/proxy/controllers/test_obj.py @@ -598,13 +598,31 @@ class TestReplicatedObjController(BaseObjectControllerMixin, def test_POST_as_COPY_simple(self): req = swift.common.swob.Request.blank('/v1/a/c/o', method='POST') - head_resp = [200] * self.obj_ring.replicas + \ + get_resp = [200] * self.obj_ring.replicas + \ [404] * self.obj_ring.max_more_nodes put_resp = [201] * self.obj_ring.replicas - codes = head_resp + put_resp + codes = get_resp + put_resp with set_http_connect(*codes): resp = req.get_response(self.app) self.assertEquals(resp.status_int, 202) + self.assertEquals(req.environ['QUERY_STRING'], '') + self.assertTrue('swift.post_as_copy' in req.environ) + + def test_POST_as_COPY_static_large_object(self): + req = swift.common.swob.Request.blank('/v1/a/c/o', method='POST') + get_resp = [200] * self.obj_ring.replicas + \ + [404] * self.obj_ring.max_more_nodes + put_resp = [201] * self.obj_ring.replicas + codes = get_resp + put_resp + slo_headers = \ + [{'X-Static-Large-Object': True}] * self.obj_ring.replicas + get_headers = slo_headers + [{}] * (len(codes) - len(slo_headers)) + headers = {'headers': get_headers} + with set_http_connect(*codes, **headers): + resp = req.get_response(self.app) + self.assertEquals(resp.status_int, 202) + self.assertEquals(req.environ['QUERY_STRING'], '') + self.assertTrue('swift.post_as_copy' in req.environ) def test_POST_delete_at(self): t = str(int(time.time() + 100)) @@ -624,6 +642,9 @@ class TestReplicatedObjController(BaseObjectControllerMixin, with set_http_connect(*codes, give_connect=capture_headers): resp = req.get_response(self.app) self.assertEquals(resp.status_int, 200) + self.assertEquals(req.environ['QUERY_STRING'], '') # sanity + self.assertTrue('swift.post_as_copy' in req.environ) + for given_headers in post_headers: self.assertEquals(given_headers.get('X-Delete-At'), t) self.assertTrue('X-Delete-At-Host' in given_headers)