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
This commit is contained in:
Kun Huang 2013-04-24 09:32:31 -04:00
parent 5d52d2d1cc
commit fef0f491ff
4 changed files with 50 additions and 20 deletions

View File

@ -42,3 +42,4 @@ David Hadas <davidh@il.ibm.com> <david.hadas@gmail.com>
Yaguang Wang <yaguang.wang@intel.com> ywang19 <yaguang.wang@intel.com> Yaguang Wang <yaguang.wang@intel.com> ywang19 <yaguang.wang@intel.com>
Liu Siqi <meizu647@gmail.com> dk647 <meizu647@gmail.com> Liu Siqi <meizu647@gmail.com> dk647 <meizu647@gmail.com>
James E. Blair <jeblair@openstack.org> <james.blair@rackspace.com> James E. Blair <jeblair@openstack.org> <james.blair@rackspace.com>
Kun Huang <gareth@unitedstack.com> <academicgareth@gmail.com>

View File

@ -71,8 +71,9 @@ def copy_headers_into(from_r, to_r):
:params from_r: a swob Request or Response :params from_r: a swob Request or Response
:params to_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(): 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 to_r.headers[k] = v
@ -712,25 +713,6 @@ class ObjectController(Controller):
content_type='text/plain', content_type='text/plain',
body='Non-integer X-Delete-After') body='Non-integer X-Delete-After')
req.headers['x-delete-at'] = '%d' % (time.time() + 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( partition, nodes = self.app.object_ring.get_nodes(
self.account_name, self.container_name, self.object_name) self.account_name, self.container_name, self.object_name)
# do a HEAD request for container sync and checking object versions # 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'] source_resp.headers['X-Static-Large-Object']
req = new_req 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) node_iter = self.iter_nodes(self.app.object_ring, partition)
pile = GreenPile(len(nodes)) pile = GreenPile(len(nodes))
chunked = req.headers.get('transfer-encoding') chunked = req.headers.get('transfer-encoding')

View File

@ -318,6 +318,7 @@ def fake_http_connect(*code_iter, **kwargs):
'x-timestamp': self.timestamp, 'x-timestamp': self.timestamp,
'last-modified': self.timestamp, 'last-modified': self.timestamp,
'x-object-meta-test': 'testing', 'x-object-meta-test': 'testing',
'x-delete-at': '9876543210',
'etag': etag, 'etag': etag,
'x-works': 'yes', 'x-works': 'yes',
'x-account-container-count': kwargs.get('count', 12345)} 'x-account-container-count': kwargs.get('count', 12345)}

View File

@ -2510,6 +2510,7 @@ class TestObjectController(unittest.TestCase):
self.assertEquals(resp.headers.get('x-object-meta-test'), self.assertEquals(resp.headers.get('x-object-meta-test'),
'testing') 'testing')
self.assertEquals(resp.headers.get('x-object-meta-ours'), 'okay') 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 # copy-from object is too large to fit in target object
req = Request.blank('/a/c/o', environ={'REQUEST_METHOD': 'PUT'}, 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'), self.assertEquals(resp.headers.get('x-object-meta-test'),
'testing') 'testing')
self.assertEquals(resp.headers.get('x-object-meta-ours'), 'okay') 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'}, req = Request.blank('/a/c/o', environ={'REQUEST_METHOD': 'COPY'},
headers={'Destination': '/c/o'}) headers={'Destination': '/c/o'})
@ -2678,6 +2680,29 @@ class TestObjectController(unittest.TestCase):
self.assertEquals(resp.headers['x-copied-from-last-modified'], self.assertEquals(resp.headers['x-copied-from-last-modified'],
'3') '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): def test_chunked_put(self):
class ChunkedFile(): class ChunkedFile():