From fef0f491ff38cb2e4481b9d08d915f39709ec016 Mon Sep 17 00:00:00 2001 From: Kun Huang Date: Wed, 24 Apr 2013 09:32:31 -0400 Subject: [PATCH] copy X-Delete-At unless X-Fresh-Metadata: true is supplied on an object copy Current codes will copy metadata headers when x-fresh-metadata:false, we still need copy "x-delete-at" header and ensure expiring work at the same time. Change-Id: Ie31326b5f7b565e51e5aa249279bc1786f7bc847 Fixes: bug #1067528 --- .mailmap | 1 + swift/proxy/controllers/obj.py | 43 ++++++++++++++++++---------------- test/unit/__init__.py | 1 + test/unit/proxy/test_server.py | 25 ++++++++++++++++++++ 4 files changed, 50 insertions(+), 20 deletions(-) diff --git a/.mailmap b/.mailmap index 664d4a6cca..6dc93feaa4 100644 --- a/.mailmap +++ b/.mailmap @@ -42,3 +42,4 @@ David Hadas Yaguang Wang ywang19 Liu Siqi dk647 James E. Blair +Kun Huang diff --git a/swift/proxy/controllers/obj.py b/swift/proxy/controllers/obj.py index 2cd00f49c9..1d8f7690f3 100644 --- a/swift/proxy/controllers/obj.py +++ b/swift/proxy/controllers/obj.py @@ -71,8 +71,9 @@ def copy_headers_into(from_r, to_r): :params from_r: a swob Request or Response :params to_r: a swob Request or Response """ + pass_headers = ['x-delete-at'] for k, v in from_r.headers.items(): - if k.lower().startswith('x-object-meta-'): + if k.lower().startswith('x-object-meta-') or k.lower() in pass_headers: to_r.headers[k] = v @@ -712,25 +713,6 @@ class ObjectController(Controller): content_type='text/plain', body='Non-integer X-Delete-After') req.headers['x-delete-at'] = '%d' % (time.time() + x_delete_after) - if 'x-delete-at' in req.headers: - try: - x_delete_at = int(req.headers['x-delete-at']) - if x_delete_at < time.time(): - return HTTPBadRequest( - body='X-Delete-At in past', request=req, - content_type='text/plain') - except ValueError: - return HTTPBadRequest(request=req, content_type='text/plain', - body='Non-integer X-Delete-At') - delete_at_container = str( - x_delete_at / - self.app.expiring_objects_container_divisor * - self.app.expiring_objects_container_divisor) - delete_at_part, delete_at_nodes = \ - self.app.container_ring.get_nodes( - self.app.expiring_objects_account, delete_at_container) - else: - delete_at_part = delete_at_nodes = None partition, nodes = self.app.object_ring.get_nodes( self.account_name, self.container_name, self.object_name) # do a HEAD request for container sync and checking object versions @@ -863,6 +845,27 @@ class ObjectController(Controller): source_resp.headers['X-Static-Large-Object'] req = new_req + + if 'x-delete-at' in req.headers: + try: + x_delete_at = int(req.headers['x-delete-at']) + if x_delete_at < time.time(): + return HTTPBadRequest( + body='X-Delete-At in past', request=req, + content_type='text/plain') + except ValueError: + return HTTPBadRequest(request=req, content_type='text/plain', + body='Non-integer X-Delete-At') + delete_at_container = str( + x_delete_at / + self.app.expiring_objects_container_divisor * + self.app.expiring_objects_container_divisor) + delete_at_part, delete_at_nodes = \ + self.app.container_ring.get_nodes( + self.app.expiring_objects_account, delete_at_container) + else: + delete_at_part = delete_at_nodes = None + node_iter = self.iter_nodes(self.app.object_ring, partition) pile = GreenPile(len(nodes)) chunked = req.headers.get('transfer-encoding') diff --git a/test/unit/__init__.py b/test/unit/__init__.py index 9d3577276d..46b77be288 100644 --- a/test/unit/__init__.py +++ b/test/unit/__init__.py @@ -318,6 +318,7 @@ def fake_http_connect(*code_iter, **kwargs): 'x-timestamp': self.timestamp, 'last-modified': self.timestamp, 'x-object-meta-test': 'testing', + 'x-delete-at': '9876543210', 'etag': etag, 'x-works': 'yes', 'x-account-container-count': kwargs.get('count', 12345)} diff --git a/test/unit/proxy/test_server.py b/test/unit/proxy/test_server.py index adea5e54ff..5ed66820c9 100644 --- a/test/unit/proxy/test_server.py +++ b/test/unit/proxy/test_server.py @@ -2510,6 +2510,7 @@ class TestObjectController(unittest.TestCase): self.assertEquals(resp.headers.get('x-object-meta-test'), 'testing') self.assertEquals(resp.headers.get('x-object-meta-ours'), 'okay') + self.assertEquals(resp.headers.get('x-delete-at'), '9876543210') # copy-from object is too large to fit in target object req = Request.blank('/a/c/o', environ={'REQUEST_METHOD': 'PUT'}, @@ -2641,6 +2642,7 @@ class TestObjectController(unittest.TestCase): self.assertEquals(resp.headers.get('x-object-meta-test'), 'testing') self.assertEquals(resp.headers.get('x-object-meta-ours'), 'okay') + self.assertEquals(resp.headers.get('x-delete-at'), '9876543210') req = Request.blank('/a/c/o', environ={'REQUEST_METHOD': 'COPY'}, headers={'Destination': '/c/o'}) @@ -2678,6 +2680,29 @@ class TestObjectController(unittest.TestCase): self.assertEquals(resp.headers['x-copied-from-last-modified'], '3') + def test_COPY_delete_at(self): + with save_globals(): + given_headers = {} + + def fake_connect_put_node(nodes, part, path, headers, + logger_thread_locals): + given_headers.update(headers) + + controller = proxy_server.ObjectController(self.app, 'a', + 'c', 'o') + controller._connect_put_node = fake_connect_put_node + set_http_connect(200, 200, 200, 200, 200, 201, 201, 201) + self.app.memcache.store = {} + req = Request.blank('/a/c/o', environ={'REQUEST_METHOD': 'COPY'}, + headers={'Destination': '/c/o'}) + + self.app.update_request(req) + controller.COPY(req) + self.assertEquals(given_headers.get('X-Delete-At'), '9876543210') + self.assertTrue('X-Delete-At-Host' in given_headers) + self.assertTrue('X-Delete-At-Device' in given_headers) + self.assertTrue('X-Delete-At-Partition' in given_headers) + def test_chunked_put(self): class ChunkedFile():