diff --git a/swift/obj/server.py b/swift/obj/server.py index 47248d952a..55aa3eb9cc 100644 --- a/swift/obj/server.py +++ b/swift/obj/server.py @@ -447,7 +447,8 @@ class ObjectController(BaseStorageServer): except ValueError: raise HTTPBadRequest("invalid JSON for footer doc") - def _check_container_override(self, update_headers, metadata): + def _check_container_override(self, update_headers, metadata, + footers=None): """ Applies any overrides to the container update headers. @@ -463,7 +464,10 @@ class ObjectController(BaseStorageServer): :param update_headers: a dict of headers used in the container update :param metadata: a dict that may container override items + :param footers: another dict that may container override items, at a + higher priority than metadata """ + footers = footers or {} # the order of this list is significant: # x-object-sysmeta-container-update-override-* headers take precedence # over x-backend-container-update-override-* headers @@ -474,6 +478,12 @@ class ObjectController(BaseStorageServer): if key.lower().startswith(override_prefix): override = key.lower().replace(override_prefix, 'x-') update_headers[override] = val + # apply x-backend-container-update-override* from footers *before* + # x-object-sysmeta-container-update-override-* from headers + for key, val in footers.items(): + if key.lower().startswith(override_prefix): + override = key.lower().replace(override_prefix, 'x-') + update_headers[override] = val def _preserve_slo_manifest(self, update_metadata, orig_metadata): if 'X-Static-Large-Object' in orig_metadata: @@ -829,8 +839,8 @@ class ObjectController(BaseStorageServer): 'x-timestamp': metadata['X-Timestamp'], 'x-etag': metadata['ETag']}) # apply any container update header overrides sent with request - self._check_container_override(update_headers, request.headers) - self._check_container_override(update_headers, footer_meta) + self._check_container_override(update_headers, request.headers, + footer_meta) self.container_update( 'PUT', account, container, obj, request, update_headers, diff --git a/test/unit/obj/test_server.py b/test/unit/obj/test_server.py index 79fc1b32f4..536a746037 100755 --- a/test/unit/obj/test_server.py +++ b/test/unit/obj/test_server.py @@ -1435,6 +1435,86 @@ class TestObjectController(unittest.TestCase): with open(objfile) as fh: self.assertEqual(fh.read(), "obj data") + def test_PUT_container_override_etag_in_footer(self): + ts_iter = make_timestamp_iter() + + def do_test(override_headers, override_footers): + def mock_container_update(ctlr, op, account, container, obj, req, + headers_out, objdevice, policy): + calls_made.append((headers_out, policy)) + calls_made = [] + ts_put = next(ts_iter) + + headers = { + 'X-Timestamp': ts_put.internal, + 'Content-Type': 'text/plain', + 'Transfer-Encoding': 'chunked', + 'Etag': 'other-etag', + 'X-Backend-Obj-Metadata-Footer': 'yes', + 'X-Backend-Obj-Multipart-Mime-Boundary': 'boundary'} + headers.update(override_headers) + req = Request.blank( + '/sda1/p/a/c/o', headers=headers, + environ={'REQUEST_METHOD': 'PUT'}) + + obj_etag = md5("obj data").hexdigest() + footers = {'Etag': obj_etag} + footers.update(override_footers) + footer_meta = json.dumps(footers) + footer_meta_cksum = md5(footer_meta).hexdigest() + + req.body = "\r\n".join(( + "--boundary", + "", + "obj data", + "--boundary", + "Content-MD5: " + footer_meta_cksum, + "", + footer_meta, + "--boundary--", + )) + req.headers.pop("Content-Length", None) + + with mock.patch( + 'swift.obj.server.ObjectController.container_update', + mock_container_update): + resp = req.get_response(self.object_controller) + self.assertEqual(resp.etag, obj_etag) + self.assertEqual(resp.status_int, 201) + self.assertEqual(1, len(calls_made)) + self.assertEqual({ + 'X-Size': str(len('obj data')), + 'X-Etag': 'update-etag', + 'X-Content-Type': 'text/plain', + 'X-Timestamp': ts_put.internal, + }, calls_made[0][0]) + self.assertEqual(POLICIES[0], calls_made[0][1]) + + # lone headers/footers work + do_test({'X-Backend-Container-Update-Override-Etag': 'update-etag'}, + {}) + do_test({}, + {'X-Backend-Container-Update-Override-Etag': 'update-etag'}) + do_test({'X-Object-Sysmeta-Container-Update-Override-Etag': + 'update-etag'}, + {}) + do_test({}, + {'X-Object-Sysmeta-Container-Update-Override-Etag': + 'update-etag'}) + + # footer trumps header + do_test({'X-Backend-Container-Update-Override-Etag': 'ignored-etag'}, + {'X-Backend-Container-Update-Override-Etag': 'update-etag'}) + do_test({'X-Object-Sysmeta-Container-Update-Override-Etag': + 'ignored-etag'}, + {'X-Object-Sysmeta-Container-Update-Override-Etag': + 'update-etag'}) + + # but sysmeta header trumps backend footer + do_test({'X-Object-Sysmeta-Container-Update-Override-Etag': + 'update-etag'}, + {'X-Backend-Container-Update-Override-Etag': 'ignored-etag'}) + def test_PUT_etag_in_footer_mismatch(self): timestamp = normalize_timestamp(time()) req = Request.blank(