Consolidating and standardizing x-delete-at format

Change-Id: Idc916da1c7fe1cc43a2c26f7f7ee1d4fcdd52c89
This commit is contained in:
gholt 2014-01-13 17:45:56 +00:00
parent d698c21ab3
commit a3f2400cba
5 changed files with 95 additions and 26 deletions

View File

@ -535,6 +535,29 @@ def normalize_timestamp(timestamp):
return "%016.05f" % (float(timestamp)) return "%016.05f" % (float(timestamp))
def normalize_delete_at_timestamp(timestamp):
"""
Format a timestamp (string or numeric) into a standardized
xxxxxxxxxx (10) format.
Note that timestamps less than 0000000000 are raised to
0000000000 and values greater than November 20th, 2286 at
17:46:39 UTC will be capped at that date and time, resulting in
no return value exceeding 9999999999.
This cap is because the expirer is already working through a
sorted list of strings that were all a length of 10. Adding
another digit would mess up the sort and cause the expirer to
break from processing early. By 2286, this problem will need to
be fixed, probably by creating an additional .expiring_objects
account to work from with 11 (or more) digit container names.
:param timestamp: unix timestamp
:returns: normalized timestamp as a string
"""
return '%010d' % min(max(0, float(timestamp)), 9999999999)
def mkdirs(path): def mkdirs(path):
""" """
Ensures the path is a directory or makes it if not. Errors if the path Ensures the path is a directory or makes it if not. Errors if the path

View File

@ -29,7 +29,7 @@ from hashlib import md5
from eventlet import sleep, Timeout from eventlet import sleep, Timeout
from swift.common.utils import public, get_logger, \ from swift.common.utils import public, get_logger, \
config_true_value, timing_stats, replication config_true_value, timing_stats, replication, normalize_delete_at_timestamp
from swift.common.bufferedhttp import http_connect from swift.common.bufferedhttp import http_connect
from swift.common.constraints import check_object_creation, \ from swift.common.constraints import check_object_creation, \
check_float, check_utf8 check_float, check_utf8
@ -254,10 +254,7 @@ class ObjectController(object):
:param request: the original request driving the update :param request: the original request driving the update
:param objdevice: device name that the object is in :param objdevice: device name that the object is in
""" """
# Quick cap that will work from now until Sat Nov 20 17:46:39 2286 delete_at = normalize_delete_at_timestamp(delete_at)
# At that time, Swift will be so popular and pervasive I will have
# created income for thousands of future programmers.
delete_at = max(min(delete_at, 9999999999), 0)
updates = [(None, None)] updates = [(None, None)]
partition = None partition = None
@ -276,8 +273,8 @@ class ObjectController(object):
'best guess as to the container name for now.' % op) 'best guess as to the container name for now.' % op)
# TODO(gholt): In a future release, change the above warning to # TODO(gholt): In a future release, change the above warning to
# a raised exception and remove the guess code below. # a raised exception and remove the guess code below.
delete_at_container = str( delete_at_container = (
delete_at / self.expiring_objects_container_divisor * int(delete_at) / self.expiring_objects_container_divisor *
self.expiring_objects_container_divisor) self.expiring_objects_container_divisor)
partition = headers_in.get('X-Delete-At-Partition', None) partition = headers_in.get('X-Delete-At-Partition', None)
hosts = headers_in.get('X-Delete-At-Host', '') hosts = headers_in.get('X-Delete-At-Host', '')
@ -300,8 +297,10 @@ class ObjectController(object):
# it will be ignored when the expirer eventually tries to issue the # it will be ignored when the expirer eventually tries to issue the
# object DELETE later since the X-Delete-At value won't match up. # object DELETE later since the X-Delete-At value won't match up.
delete_at_container = str( delete_at_container = str(
delete_at / self.expiring_objects_container_divisor * int(delete_at) / self.expiring_objects_container_divisor *
self.expiring_objects_container_divisor) self.expiring_objects_container_divisor)
delete_at_container = normalize_delete_at_timestamp(
delete_at_container)
for host, contdevice in updates: for host, contdevice in updates:
self.async_update( self.async_update(

View File

@ -41,7 +41,7 @@ from eventlet.timeout import Timeout
from swift.common.utils import ContextPool, normalize_timestamp, \ from swift.common.utils import ContextPool, normalize_timestamp, \
config_true_value, public, json, csv_append, GreenthreadSafeIterator, \ config_true_value, public, json, csv_append, GreenthreadSafeIterator, \
quorum_size, GreenAsyncPile quorum_size, GreenAsyncPile, normalize_delete_at_timestamp
from swift.common.bufferedhttp import http_connect from swift.common.bufferedhttp import http_connect
from swift.common.constraints import check_metadata, check_object_creation, \ from swift.common.constraints import check_metadata, check_object_creation, \
CONTAINER_LISTING_LIMIT, MAX_FILE_SIZE CONTAINER_LISTING_LIMIT, MAX_FILE_SIZE
@ -549,7 +549,8 @@ class ObjectController(Controller):
return HTTPBadRequest(request=req, return HTTPBadRequest(request=req,
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'] = normalize_delete_at_timestamp(
time.time() + x_delete_after)
if self.app.object_post_as_copy: if self.app.object_post_as_copy:
req.method = 'PUT' req.method = 'PUT'
req.path_info = '/v1/%s/%s/%s' % ( req.path_info = '/v1/%s/%s/%s' % (
@ -587,8 +588,9 @@ class ObjectController(Controller):
return HTTPNotFound(request=req) return HTTPNotFound(request=req)
if 'x-delete-at' in req.headers: if 'x-delete-at' in req.headers:
try: try:
x_delete_at = int(req.headers['x-delete-at']) x_delete_at = normalize_delete_at_timestamp(
if x_delete_at < time.time(): int(req.headers['x-delete-at']))
if int(x_delete_at) < time.time():
return HTTPBadRequest( return HTTPBadRequest(
body='X-Delete-At in past', request=req, body='X-Delete-At in past', request=req,
content_type='text/plain') content_type='text/plain')
@ -597,9 +599,9 @@ class ObjectController(Controller):
content_type='text/plain', content_type='text/plain',
body='Non-integer X-Delete-At') body='Non-integer X-Delete-At')
req.environ.setdefault('swift.log_info', []).append( req.environ.setdefault('swift.log_info', []).append(
'x-delete-at:%d' % x_delete_at) 'x-delete-at:%s' % x_delete_at)
delete_at_container = str( delete_at_container = normalize_delete_at_timestamp(
x_delete_at / int(x_delete_at) /
self.app.expiring_objects_container_divisor * self.app.expiring_objects_container_divisor *
self.app.expiring_objects_container_divisor) self.app.expiring_objects_container_divisor)
delete_at_part, delete_at_nodes = \ delete_at_part, delete_at_nodes = \
@ -777,7 +779,8 @@ class ObjectController(Controller):
return HTTPBadRequest(request=req, return HTTPBadRequest(request=req,
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'] = normalize_delete_at_timestamp(
time.time() + x_delete_after)
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
@ -928,8 +931,9 @@ class ObjectController(Controller):
if 'x-delete-at' in req.headers: if 'x-delete-at' in req.headers:
try: try:
x_delete_at = int(req.headers['x-delete-at']) x_delete_at = normalize_delete_at_timestamp(
if x_delete_at < time.time(): int(req.headers['x-delete-at']))
if int(x_delete_at) < time.time():
return HTTPBadRequest( return HTTPBadRequest(
body='X-Delete-At in past', request=req, body='X-Delete-At in past', request=req,
content_type='text/plain') content_type='text/plain')
@ -937,9 +941,9 @@ class ObjectController(Controller):
return HTTPBadRequest(request=req, content_type='text/plain', return HTTPBadRequest(request=req, content_type='text/plain',
body='Non-integer X-Delete-At') body='Non-integer X-Delete-At')
req.environ.setdefault('swift.log_info', []).append( req.environ.setdefault('swift.log_info', []).append(
'x-delete-at:%d' % x_delete_at) 'x-delete-at:%s' % x_delete_at)
delete_at_container = str( delete_at_container = normalize_delete_at_timestamp(
x_delete_at / int(x_delete_at) /
self.app.expiring_objects_container_divisor * self.app.expiring_objects_container_divisor *
self.app.expiring_objects_container_divisor) self.app.expiring_objects_container_divisor)
delete_at_part, delete_at_nodes = \ delete_at_part, delete_at_nodes = \

View File

@ -210,6 +210,46 @@ class TestUtils(unittest.TestCase):
self.assertRaises(ValueError, utils.normalize_timestamp, '') self.assertRaises(ValueError, utils.normalize_timestamp, '')
self.assertRaises(ValueError, utils.normalize_timestamp, 'abc') self.assertRaises(ValueError, utils.normalize_timestamp, 'abc')
def test_normalize_delete_at_timestamp(self):
self.assertEquals(
utils.normalize_delete_at_timestamp(1253327593),
'1253327593')
self.assertEquals(
utils.normalize_delete_at_timestamp(1253327593.67890),
'1253327593')
self.assertEquals(
utils.normalize_delete_at_timestamp('1253327593'),
'1253327593')
self.assertEquals(
utils.normalize_delete_at_timestamp('1253327593.67890'),
'1253327593')
self.assertEquals(
utils.normalize_delete_at_timestamp(-1253327593),
'0000000000')
self.assertEquals(
utils.normalize_delete_at_timestamp(-1253327593.67890),
'0000000000')
self.assertEquals(
utils.normalize_delete_at_timestamp('-1253327593'),
'0000000000')
self.assertEquals(
utils.normalize_delete_at_timestamp('-1253327593.67890'),
'0000000000')
self.assertEquals(
utils.normalize_delete_at_timestamp(71253327593),
'9999999999')
self.assertEquals(
utils.normalize_delete_at_timestamp(71253327593.67890),
'9999999999')
self.assertEquals(
utils.normalize_delete_at_timestamp('71253327593'),
'9999999999')
self.assertEquals(
utils.normalize_delete_at_timestamp('71253327593.67890'),
'9999999999')
self.assertRaises(ValueError, utils.normalize_timestamp, '')
self.assertRaises(ValueError, utils.normalize_timestamp, 'abc')
def test_backwards(self): def test_backwards(self):
# Test swift.common.utils.backward # Test swift.common.utils.backward

View File

@ -2270,8 +2270,8 @@ class TestObjectController(unittest.TestCase):
'DELETE', 2, 'a', 'c', 'o', req, 'sda1') 'DELETE', 2, 'a', 'c', 'o', req, 'sda1')
self.assertEquals( self.assertEquals(
given_args, [ given_args, [
'DELETE', '.expiring_objects', '0', 'DELETE', '.expiring_objects', '0000000000',
'2-a/c/o', None, None, None, '0000000002-a/c/o', None, None, None,
HeaderKeyDict({ HeaderKeyDict({
'x-timestamp': '1', 'x-timestamp': '1',
'x-trans-id': '123', 'x-trans-id': '123',
@ -2296,7 +2296,8 @@ class TestObjectController(unittest.TestCase):
self.object_controller.delete_at_update( self.object_controller.delete_at_update(
'DELETE', -2, 'a', 'c', 'o', req, 'sda1') 'DELETE', -2, 'a', 'c', 'o', req, 'sda1')
self.assertEquals(given_args, [ self.assertEquals(given_args, [
'DELETE', '.expiring_objects', '0', '0-a/c/o', None, None, None, 'DELETE', '.expiring_objects', '0000000000', '0000000000-a/c/o',
None, None, None,
HeaderKeyDict({ HeaderKeyDict({
'x-timestamp': '1', 'x-timestamp': '1',
'x-trans-id': '1234', 'x-trans-id': '1234',
@ -2352,7 +2353,8 @@ class TestObjectController(unittest.TestCase):
req, 'sda1') req, 'sda1')
self.assertEquals( self.assertEquals(
given_args, [ given_args, [
'PUT', '.expiring_objects', '0', '2-a/c/o', '127.0.0.1:1234', 'PUT', '.expiring_objects', '0000000000', '0000000002-a/c/o',
'127.0.0.1:1234',
'3', 'sdc1', HeaderKeyDict({ '3', 'sdc1', HeaderKeyDict({
'x-size': '0', 'x-size': '0',
'x-etag': 'd41d8cd98f00b204e9800998ecf8427e', 'x-etag': 'd41d8cd98f00b204e9800998ecf8427e',
@ -2404,7 +2406,8 @@ class TestObjectController(unittest.TestCase):
req, 'sda1') req, 'sda1')
self.assertEquals( self.assertEquals(
given_args, [ given_args, [
'DELETE', '.expiring_objects', '0', '2-a/c/o', None, None, 'DELETE', '.expiring_objects', '0000000000',
'0000000002-a/c/o', None, None,
None, HeaderKeyDict({ None, HeaderKeyDict({
'x-timestamp': '1', 'x-trans-id': '1234', 'x-timestamp': '1', 'x-trans-id': '1234',
'referer': 'DELETE http://localhost/v1/a/c/o'}), 'referer': 'DELETE http://localhost/v1/a/c/o'}),