Merge "Use X-Timestamp when checking object expiration"
This commit is contained in:
commit
7f970597ec
@ -2203,7 +2203,7 @@ class BaseDiskFile(object):
|
||||
return cls(mgr, device_path, None, partition, _datadir=hash_dir_path,
|
||||
policy=policy)
|
||||
|
||||
def open(self, modernize=False):
|
||||
def open(self, modernize=False, current_time=None):
|
||||
"""
|
||||
Open the object.
|
||||
|
||||
@ -2215,6 +2215,9 @@ class BaseDiskFile(object):
|
||||
Currently, this means adding metadata checksums if none are
|
||||
present.
|
||||
|
||||
:param current_time: Unix time used in checking expiration. If not
|
||||
present, the current time will be used.
|
||||
|
||||
.. note::
|
||||
|
||||
An implementation is allowed to raise any of the following
|
||||
@ -2254,7 +2257,7 @@ class BaseDiskFile(object):
|
||||
if not self._data_file:
|
||||
raise self._construct_exception_from_ts_file(**file_info)
|
||||
self._fp = self._construct_from_data_file(
|
||||
modernize=modernize, **file_info)
|
||||
current_time=current_time, modernize=modernize, **file_info)
|
||||
# This method must populate the internal _metadata attribute.
|
||||
self._metadata = self._metadata or {}
|
||||
return self
|
||||
@ -2353,7 +2356,7 @@ class BaseDiskFile(object):
|
||||
data_file,
|
||||
"Hash of name in metadata does not match directory name")
|
||||
|
||||
def _verify_data_file(self, data_file, fp):
|
||||
def _verify_data_file(self, data_file, fp, current_time):
|
||||
"""
|
||||
Verify the metadata's name value matches what we think the object is
|
||||
named.
|
||||
@ -2362,6 +2365,7 @@ class BaseDiskFile(object):
|
||||
occur
|
||||
:param fp: open file pointer so that we can `fstat()` the file to
|
||||
verify the on-disk size with Content-Length metadata value
|
||||
:param current_time: Unix time used in checking expiration
|
||||
:raises DiskFileCollision: if the metadata stored name does not match
|
||||
the referenced name of the file
|
||||
:raises DiskFileExpired: if the object has expired
|
||||
@ -2392,7 +2396,9 @@ class BaseDiskFile(object):
|
||||
data_file, "bad metadata x-delete-at value %s" % (
|
||||
self._metadata['X-Delete-At']))
|
||||
else:
|
||||
if x_delete_at <= time.time() and not self._open_expired:
|
||||
if current_time is None:
|
||||
current_time = time.time()
|
||||
if x_delete_at <= current_time and not self._open_expired:
|
||||
raise DiskFileExpired(metadata=self._metadata)
|
||||
try:
|
||||
metadata_size = int(self._metadata['Content-Length'])
|
||||
@ -2466,7 +2472,7 @@ class BaseDiskFile(object):
|
||||
ctypefile_metadata.get('Content-Type-Timestamp')
|
||||
|
||||
def _construct_from_data_file(self, data_file, meta_file, ctype_file,
|
||||
modernize=False,
|
||||
current_time, modernize=False,
|
||||
**kwargs):
|
||||
"""
|
||||
Open the `.data` file to fetch its metadata, and fetch the metadata
|
||||
@ -2477,6 +2483,7 @@ class BaseDiskFile(object):
|
||||
:param meta_file: on-disk fast-POST `.meta` file being considered
|
||||
:param ctype_file: on-disk fast-POST `.meta` file being considered that
|
||||
contains content-type and content-type timestamp
|
||||
:param current_time: Unix time used in checking expiration
|
||||
:param modernize: whether to update the on-disk files to the newest
|
||||
format
|
||||
:returns: an opened data file pointer
|
||||
@ -2524,7 +2531,7 @@ class BaseDiskFile(object):
|
||||
# to us
|
||||
self._name = self._metadata['name']
|
||||
self._verify_name_matches_hash(data_file)
|
||||
self._verify_data_file(data_file, fp)
|
||||
self._verify_data_file(data_file, fp, current_time)
|
||||
return fp
|
||||
|
||||
def get_metafile_metadata(self):
|
||||
@ -2571,16 +2578,18 @@ class BaseDiskFile(object):
|
||||
raise DiskFileNotOpen()
|
||||
return self._metadata
|
||||
|
||||
def read_metadata(self):
|
||||
def read_metadata(self, current_time=None):
|
||||
"""
|
||||
Return the metadata for an object without requiring the caller to open
|
||||
the object first.
|
||||
|
||||
:param current_time: Unix time used in checking expiration. If not
|
||||
present, the current time will be used.
|
||||
:returns: metadata dictionary for an object
|
||||
:raises DiskFileError: this implementation will raise the same
|
||||
errors as the `open()` method.
|
||||
"""
|
||||
with self.open():
|
||||
with self.open(current_time=current_time):
|
||||
return self.get_metadata()
|
||||
|
||||
def reader(self, keep_cache=False,
|
||||
|
@ -273,11 +273,14 @@ class DiskFile(object):
|
||||
self._filesystem = fs
|
||||
self.fragments = None
|
||||
|
||||
def open(self, modernize=False):
|
||||
def open(self, modernize=False, current_time=None):
|
||||
"""
|
||||
Open the file and read the metadata.
|
||||
|
||||
This method must populate the _metadata attribute.
|
||||
|
||||
:param current_time: Unix time used in checking expiration. If not
|
||||
present, the current time will be used.
|
||||
:raises DiskFileCollision: on name mis-match with metadata
|
||||
:raises DiskFileDeleted: if it does not exist, or a tombstone is
|
||||
present
|
||||
@ -287,7 +290,7 @@ class DiskFile(object):
|
||||
fp, self._metadata = self._filesystem.get_object(self._name)
|
||||
if fp is None:
|
||||
raise DiskFileDeleted()
|
||||
self._fp = self._verify_data_file(fp)
|
||||
self._fp = self._verify_data_file(fp, current_time)
|
||||
self._metadata = self._metadata or {}
|
||||
return self
|
||||
|
||||
@ -313,7 +316,7 @@ class DiskFile(object):
|
||||
self._filesystem.del_object(name)
|
||||
return DiskFileQuarantined(msg)
|
||||
|
||||
def _verify_data_file(self, fp):
|
||||
def _verify_data_file(self, fp, current_time):
|
||||
"""
|
||||
Verify the metadata's name value matches what we think the object is
|
||||
named.
|
||||
@ -344,7 +347,9 @@ class DiskFile(object):
|
||||
self._name, "bad metadata x-delete-at value %s" % (
|
||||
self._metadata['X-Delete-At']))
|
||||
else:
|
||||
if x_delete_at <= time.time():
|
||||
if current_time is None:
|
||||
current_time = time.time()
|
||||
if x_delete_at <= current_time:
|
||||
raise DiskFileNotExist('Expired')
|
||||
try:
|
||||
metadata_size = int(self._metadata['Content-Length'])
|
||||
@ -381,13 +386,15 @@ class DiskFile(object):
|
||||
raise DiskFileNotOpen()
|
||||
return self._metadata
|
||||
|
||||
def read_metadata(self):
|
||||
def read_metadata(self, current_time=None):
|
||||
"""
|
||||
Return the metadata for an object.
|
||||
|
||||
:param current_time: Unix time used in checking expiration. If not
|
||||
present, the current time will be used.
|
||||
:returns: metadata dictionary for an object
|
||||
"""
|
||||
with self.open():
|
||||
with self.open(current_time=current_time):
|
||||
return self.get_metadata()
|
||||
|
||||
def reader(self, keep_cache=False):
|
||||
|
@ -35,7 +35,7 @@ from swift.common.utils import public, get_logger, \
|
||||
normalize_delete_at_timestamp, get_log_line, Timestamp, \
|
||||
get_expirer_container, parse_mime_headers, \
|
||||
iter_multipart_mime_documents, extract_swift_bytes, safe_json_loads, \
|
||||
config_auto_int_value, split_path, get_redirect_data
|
||||
config_auto_int_value, split_path, get_redirect_data, normalize_timestamp
|
||||
from swift.common.bufferedhttp import http_connect
|
||||
from swift.common.constraints import check_object_creation, \
|
||||
valid_timestamp, check_utf8
|
||||
@ -595,7 +595,7 @@ class ObjectController(BaseStorageServer):
|
||||
get_name_and_placement(request, 5, 5, True)
|
||||
req_timestamp = valid_timestamp(request)
|
||||
new_delete_at = int(request.headers.get('X-Delete-At') or 0)
|
||||
if new_delete_at and new_delete_at < time.time():
|
||||
if new_delete_at and new_delete_at < req_timestamp:
|
||||
return HTTPBadRequest(body='X-Delete-At in past', request=request,
|
||||
content_type='text/plain')
|
||||
next_part_power = request.headers.get('X-Backend-Next-Part-Power')
|
||||
@ -608,7 +608,7 @@ class ObjectController(BaseStorageServer):
|
||||
except DiskFileDeviceUnavailable:
|
||||
return HTTPInsufficientStorage(drive=device, request=request)
|
||||
try:
|
||||
orig_metadata = disk_file.read_metadata()
|
||||
orig_metadata = disk_file.read_metadata(current_time=req_timestamp)
|
||||
except DiskFileXattrNotSupported:
|
||||
return HTTPInsufficientStorage(drive=device, request=request)
|
||||
except (DiskFileNotExist, DiskFileQuarantined):
|
||||
@ -770,7 +770,7 @@ class ObjectController(BaseStorageServer):
|
||||
except DiskFileDeviceUnavailable:
|
||||
return HTTPInsufficientStorage(drive=device, request=request)
|
||||
try:
|
||||
orig_metadata = disk_file.read_metadata()
|
||||
orig_metadata = disk_file.read_metadata(current_time=req_timestamp)
|
||||
orig_timestamp = disk_file.data_timestamp
|
||||
except DiskFileXattrNotSupported:
|
||||
return HTTPInsufficientStorage(drive=device, request=request)
|
||||
@ -958,6 +958,9 @@ class ObjectController(BaseStorageServer):
|
||||
"""Handle HTTP GET requests for the Swift Object Server."""
|
||||
device, partition, account, container, obj, policy = \
|
||||
get_name_and_placement(request, 5, 5, True)
|
||||
request.headers.setdefault('X-Timestamp',
|
||||
normalize_timestamp(time.time()))
|
||||
req_timestamp = valid_timestamp(request)
|
||||
frag_prefs = safe_json_loads(
|
||||
request.headers.get('X-Backend-Fragment-Preferences'))
|
||||
try:
|
||||
@ -969,7 +972,7 @@ class ObjectController(BaseStorageServer):
|
||||
except DiskFileDeviceUnavailable:
|
||||
return HTTPInsufficientStorage(drive=device, request=request)
|
||||
try:
|
||||
with disk_file.open():
|
||||
with disk_file.open(current_time=req_timestamp):
|
||||
metadata = disk_file.get_metadata()
|
||||
obj_size = int(metadata['Content-Length'])
|
||||
file_x_ts = Timestamp(metadata['X-Timestamp'])
|
||||
@ -1022,6 +1025,9 @@ class ObjectController(BaseStorageServer):
|
||||
"""Handle HTTP HEAD requests for the Swift Object Server."""
|
||||
device, partition, account, container, obj, policy = \
|
||||
get_name_and_placement(request, 5, 5, True)
|
||||
request.headers.setdefault('X-Timestamp',
|
||||
normalize_timestamp(time.time()))
|
||||
req_timestamp = valid_timestamp(request)
|
||||
frag_prefs = safe_json_loads(
|
||||
request.headers.get('X-Backend-Fragment-Preferences'))
|
||||
try:
|
||||
@ -1033,7 +1039,7 @@ class ObjectController(BaseStorageServer):
|
||||
except DiskFileDeviceUnavailable:
|
||||
return HTTPInsufficientStorage(drive=device, request=request)
|
||||
try:
|
||||
metadata = disk_file.read_metadata()
|
||||
metadata = disk_file.read_metadata(current_time=req_timestamp)
|
||||
except DiskFileXattrNotSupported:
|
||||
return HTTPInsufficientStorage(drive=device, request=request)
|
||||
except (DiskFileNotExist, DiskFileQuarantined) as e:
|
||||
@ -1087,7 +1093,7 @@ class ObjectController(BaseStorageServer):
|
||||
except DiskFileDeviceUnavailable:
|
||||
return HTTPInsufficientStorage(drive=device, request=request)
|
||||
try:
|
||||
orig_metadata = disk_file.read_metadata()
|
||||
orig_metadata = disk_file.read_metadata(current_time=req_timestamp)
|
||||
except DiskFileXattrNotSupported:
|
||||
return HTTPInsufficientStorage(drive=device, request=request)
|
||||
except DiskFileExpired as e:
|
||||
|
@ -1338,7 +1338,6 @@ class TestObjectController(unittest.TestCase):
|
||||
inital_put = next(self.ts)
|
||||
put_before_expire = next(self.ts)
|
||||
delete_at_timestamp = int(next(self.ts))
|
||||
time_after_expire = next(self.ts)
|
||||
put_after_expire = next(self.ts)
|
||||
delete_at_container = str(
|
||||
delete_at_timestamp /
|
||||
@ -1363,9 +1362,7 @@ class TestObjectController(unittest.TestCase):
|
||||
'Content-Type': 'application/octet-stream',
|
||||
'If-None-Match': '*'})
|
||||
req.body = 'TEST'
|
||||
with mock.patch("swift.obj.server.time.time",
|
||||
lambda: float(put_before_expire.normal)):
|
||||
resp = req.get_response(self.object_controller)
|
||||
resp = req.get_response(self.object_controller)
|
||||
self.assertEqual(resp.status_int, 412)
|
||||
|
||||
# PUT again after object has expired should succeed
|
||||
@ -1376,9 +1373,7 @@ class TestObjectController(unittest.TestCase):
|
||||
'Content-Type': 'application/octet-stream',
|
||||
'If-None-Match': '*'})
|
||||
req.body = 'TEST'
|
||||
with mock.patch("swift.obj.server.time.time",
|
||||
lambda: float(time_after_expire.normal)):
|
||||
resp = req.get_response(self.object_controller)
|
||||
resp = req.get_response(self.object_controller)
|
||||
self.assertEqual(resp.status_int, 201)
|
||||
|
||||
def test_PUT_common(self):
|
||||
@ -3880,7 +3875,7 @@ class TestObjectController(unittest.TestCase):
|
||||
self.assertEqual(resp.status_int, 201)
|
||||
disk_file = self.df_mgr.get_diskfile('sda1', 'p', 'a', 'c', 'o',
|
||||
policy=POLICIES.legacy)
|
||||
disk_file.open()
|
||||
disk_file.open(timestamp)
|
||||
file_name = os.path.basename(disk_file._data_file)
|
||||
with open(disk_file._data_file) as fp:
|
||||
metadata = diskfile.read_metadata(fp)
|
||||
@ -3909,7 +3904,7 @@ class TestObjectController(unittest.TestCase):
|
||||
self.assertEqual(resp.status_int, 201)
|
||||
disk_file = self.df_mgr.get_diskfile('sda1', 'p', 'a', 'c', 'o',
|
||||
policy=POLICIES.legacy)
|
||||
disk_file.open()
|
||||
disk_file.open(timestamp)
|
||||
file_name = os.path.basename(disk_file._data_file)
|
||||
etag = md5()
|
||||
etag.update('VERIF')
|
||||
@ -4624,8 +4619,10 @@ class TestObjectController(unittest.TestCase):
|
||||
self.assertEqual(headers[:len(exp)], exp)
|
||||
sock = connect_tcp(('localhost', port))
|
||||
fd = sock.makefile()
|
||||
fd.write('GET /sda1/p/a/c/o HTTP/1.1\r\nHost: localhost\r\n'
|
||||
'Connection: close\r\n\r\n')
|
||||
fd.write('GET /sda1/p/a/c/o HTTP/1.1\r\n'
|
||||
'Host: localhost\r\n'
|
||||
'X-Timestamp: %s\r\n'
|
||||
'Connection: close\r\n\r\n' % normalize_timestamp(2.0))
|
||||
fd.flush()
|
||||
headers = readuntil2crlfs(fd)
|
||||
exp = 'HTTP/1.1 200'
|
||||
@ -6090,8 +6087,7 @@ class TestObjectController(unittest.TestCase):
|
||||
req = Request.blank(
|
||||
'/sda1/p/a/c/o', environ={'REQUEST_METHOD': 'GET'},
|
||||
headers={'X-Timestamp': normalize_timestamp(now)})
|
||||
with mock.patch('swift.obj.server.time.time', return_value=now):
|
||||
resp = req.get_response(self.object_controller)
|
||||
resp = req.get_response(self.object_controller)
|
||||
self.assertEqual(resp.status_int, 200)
|
||||
|
||||
# It expires in the past, so it's not accessible via GET...
|
||||
@ -6100,29 +6096,25 @@ class TestObjectController(unittest.TestCase):
|
||||
environ={'REQUEST_METHOD': 'GET'},
|
||||
headers={'X-Timestamp': normalize_timestamp(
|
||||
delete_at_timestamp + 1)})
|
||||
with mock.patch('swift.obj.server.time.time',
|
||||
return_value=delete_at_timestamp + 1):
|
||||
resp = req.get_response(self.object_controller)
|
||||
resp = req.get_response(self.object_controller)
|
||||
self.assertEqual(resp.status_int, 404)
|
||||
self.assertEqual(resp.headers['X-Backend-Timestamp'],
|
||||
utils.Timestamp(now))
|
||||
|
||||
with mock.patch('swift.obj.server.time.time',
|
||||
return_value=delete_at_timestamp + 1):
|
||||
# ...unless X-Backend-Replication is sent
|
||||
expected = {
|
||||
'GET': 'TEST',
|
||||
'HEAD': '',
|
||||
}
|
||||
for meth, expected_body in expected.items():
|
||||
req = Request.blank(
|
||||
'/sda1/p/a/c/o', method=meth,
|
||||
headers={'X-Timestamp':
|
||||
normalize_timestamp(delete_at_timestamp + 1),
|
||||
'X-Backend-Replication': 'True'})
|
||||
resp = req.get_response(self.object_controller)
|
||||
self.assertEqual(resp.status_int, 200)
|
||||
self.assertEqual(expected_body, resp.body)
|
||||
# ...unless X-Backend-Replication is sent
|
||||
expected = {
|
||||
'GET': 'TEST',
|
||||
'HEAD': '',
|
||||
}
|
||||
for meth, expected_body in expected.items():
|
||||
req = Request.blank(
|
||||
'/sda1/p/a/c/o', method=meth,
|
||||
headers={'X-Timestamp':
|
||||
normalize_timestamp(delete_at_timestamp + 1),
|
||||
'X-Backend-Replication': 'True'})
|
||||
resp = req.get_response(self.object_controller)
|
||||
self.assertEqual(resp.status_int, 200)
|
||||
self.assertEqual(expected_body, resp.body)
|
||||
|
||||
def test_HEAD_but_expired(self):
|
||||
# We have an object that expires in the future
|
||||
@ -6148,8 +6140,7 @@ class TestObjectController(unittest.TestCase):
|
||||
'/sda1/p/a/c/o',
|
||||
environ={'REQUEST_METHOD': 'HEAD'},
|
||||
headers={'X-Timestamp': normalize_timestamp(now)})
|
||||
with mock.patch('swift.obj.server.time.time', return_value=now):
|
||||
resp = req.get_response(self.object_controller)
|
||||
resp = req.get_response(self.object_controller)
|
||||
self.assertEqual(resp.status_int, 200)
|
||||
|
||||
# It's not accessible now since it expires in the past
|
||||
@ -6158,9 +6149,7 @@ class TestObjectController(unittest.TestCase):
|
||||
environ={'REQUEST_METHOD': 'HEAD'},
|
||||
headers={'X-Timestamp': normalize_timestamp(
|
||||
delete_at_timestamp + 1)})
|
||||
with mock.patch('swift.obj.server.time.time',
|
||||
return_value=delete_at_timestamp + 1):
|
||||
resp = req.get_response(self.object_controller)
|
||||
resp = req.get_response(self.object_controller)
|
||||
self.assertEqual(resp.status_int, 404)
|
||||
self.assertEqual(resp.headers['X-Backend-Timestamp'],
|
||||
utils.Timestamp(now))
|
||||
@ -6195,8 +6184,7 @@ class TestObjectController(unittest.TestCase):
|
||||
'/sda1/p/a/c/o',
|
||||
environ={'REQUEST_METHOD': 'POST'},
|
||||
headers={'X-Timestamp': normalize_timestamp(the_time)})
|
||||
with mock.patch('swift.obj.server.time.time', return_value=the_time):
|
||||
resp = req.get_response(self.object_controller)
|
||||
resp = req.get_response(self.object_controller)
|
||||
self.assertEqual(resp.status_int, 202)
|
||||
|
||||
# You cannot POST to an expired object
|
||||
@ -6207,8 +6195,7 @@ class TestObjectController(unittest.TestCase):
|
||||
'/sda1/p/a/c/o',
|
||||
environ={'REQUEST_METHOD': 'POST'},
|
||||
headers={'X-Timestamp': normalize_timestamp(the_time)})
|
||||
with mock.patch('swift.obj.server.time.time', return_value=the_time):
|
||||
resp = req.get_response(self.object_controller)
|
||||
resp = req.get_response(self.object_controller)
|
||||
self.assertEqual(resp.status_int, 404)
|
||||
|
||||
def test_DELETE_can_skip_updating_expirer_queue(self):
|
||||
@ -6331,24 +6318,21 @@ class TestObjectController(unittest.TestCase):
|
||||
resp = req.get_response(self.object_controller)
|
||||
self.assertEqual(resp.status_int, 201)
|
||||
|
||||
orig_time = object_server.time.time
|
||||
try:
|
||||
t = test_time + 100
|
||||
object_server.time.time = lambda: float(t)
|
||||
req = Request.blank(
|
||||
'/sda1/p/a/c/o',
|
||||
environ={'REQUEST_METHOD': 'DELETE'},
|
||||
headers={'X-Timestamp': normalize_timestamp(time())})
|
||||
resp = req.get_response(self.object_controller)
|
||||
self.assertEqual(resp.status_int, 404)
|
||||
finally:
|
||||
object_server.time.time = orig_time
|
||||
req = Request.blank(
|
||||
'/sda1/p/a/c/o',
|
||||
environ={'REQUEST_METHOD': 'DELETE'},
|
||||
headers={'X-Timestamp': normalize_timestamp(
|
||||
delete_at_timestamp + 1)})
|
||||
resp = req.get_response(self.object_controller)
|
||||
self.assertEqual(resp.status_int, 404)
|
||||
|
||||
def test_DELETE_if_delete_at_expired_still_deletes(self):
|
||||
test_time = time() + 10
|
||||
test_timestamp = normalize_timestamp(test_time)
|
||||
delete_at_time = int(test_time + 10)
|
||||
delete_at_timestamp = str(delete_at_time)
|
||||
expired_time = delete_at_time + 1
|
||||
expired_timestamp = normalize_timestamp(expired_time)
|
||||
delete_at_container = str(
|
||||
delete_at_time /
|
||||
self.object_controller.expiring_objects_container_divisor *
|
||||
@ -6379,73 +6363,71 @@ class TestObjectController(unittest.TestCase):
|
||||
self.assertTrue(os.path.isfile(objfile))
|
||||
|
||||
# move time past expiry
|
||||
with mock.patch('swift.obj.diskfile.time') as mock_time:
|
||||
mock_time.time.return_value = test_time + 100
|
||||
req = Request.blank(
|
||||
'/sda1/p/a/c/o', environ={'REQUEST_METHOD': 'GET'},
|
||||
headers={'X-Timestamp': test_timestamp})
|
||||
resp = req.get_response(self.object_controller)
|
||||
# request will 404
|
||||
self.assertEqual(resp.status_int, 404)
|
||||
# but file still exists
|
||||
self.assertTrue(os.path.isfile(objfile))
|
||||
req = Request.blank(
|
||||
'/sda1/p/a/c/o', environ={'REQUEST_METHOD': 'GET'},
|
||||
headers={'X-Timestamp': expired_timestamp})
|
||||
resp = req.get_response(self.object_controller)
|
||||
# request will 404
|
||||
self.assertEqual(resp.status_int, 404)
|
||||
# but file still exists
|
||||
self.assertTrue(os.path.isfile(objfile))
|
||||
|
||||
# make the x-if-delete-at with some wrong bits
|
||||
req = Request.blank(
|
||||
'/sda1/p/a/c/o',
|
||||
environ={'REQUEST_METHOD': 'DELETE'},
|
||||
headers={'X-Timestamp': delete_at_timestamp,
|
||||
'X-If-Delete-At': int(time() + 1)})
|
||||
resp = req.get_response(self.object_controller)
|
||||
self.assertEqual(resp.status_int, 412)
|
||||
self.assertTrue(os.path.isfile(objfile))
|
||||
# make the x-if-delete-at with some wrong bits
|
||||
req = Request.blank(
|
||||
'/sda1/p/a/c/o',
|
||||
environ={'REQUEST_METHOD': 'DELETE'},
|
||||
headers={'X-Timestamp': delete_at_timestamp,
|
||||
'X-If-Delete-At': int(delete_at_time + 1)})
|
||||
resp = req.get_response(self.object_controller)
|
||||
self.assertEqual(resp.status_int, 412)
|
||||
self.assertTrue(os.path.isfile(objfile))
|
||||
|
||||
# make the x-if-delete-at with all the right bits
|
||||
req = Request.blank(
|
||||
'/sda1/p/a/c/o',
|
||||
environ={'REQUEST_METHOD': 'DELETE'},
|
||||
headers={'X-Timestamp': delete_at_timestamp,
|
||||
'X-If-Delete-At': delete_at_timestamp})
|
||||
resp = req.get_response(self.object_controller)
|
||||
self.assertEqual(resp.status_int, 204)
|
||||
self.assertFalse(os.path.isfile(objfile))
|
||||
# make the x-if-delete-at with all the right bits
|
||||
req = Request.blank(
|
||||
'/sda1/p/a/c/o',
|
||||
environ={'REQUEST_METHOD': 'DELETE'},
|
||||
headers={'X-Timestamp': delete_at_timestamp,
|
||||
'X-If-Delete-At': delete_at_timestamp})
|
||||
resp = req.get_response(self.object_controller)
|
||||
self.assertEqual(resp.status_int, 204)
|
||||
self.assertFalse(os.path.isfile(objfile))
|
||||
|
||||
# make the x-if-delete-at with all the right bits (again)
|
||||
req = Request.blank(
|
||||
'/sda1/p/a/c/o', environ={'REQUEST_METHOD': 'DELETE'},
|
||||
headers={'X-Timestamp': delete_at_timestamp,
|
||||
'X-If-Delete-At': delete_at_timestamp})
|
||||
resp = req.get_response(self.object_controller)
|
||||
self.assertEqual(resp.status_int, 409)
|
||||
self.assertFalse(os.path.isfile(objfile))
|
||||
# make the x-if-delete-at with all the right bits (again)
|
||||
req = Request.blank(
|
||||
'/sda1/p/a/c/o', environ={'REQUEST_METHOD': 'DELETE'},
|
||||
headers={'X-Timestamp': delete_at_timestamp,
|
||||
'X-If-Delete-At': delete_at_timestamp})
|
||||
resp = req.get_response(self.object_controller)
|
||||
self.assertEqual(resp.status_int, 409)
|
||||
self.assertFalse(os.path.isfile(objfile))
|
||||
|
||||
# overwrite with new content
|
||||
req = Request.blank(
|
||||
'/sda1/p/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
|
||||
headers={
|
||||
'X-Timestamp': str(test_time + 100),
|
||||
'Content-Length': '0',
|
||||
'Content-Type': 'application/octet-stream'})
|
||||
resp = req.get_response(self.object_controller)
|
||||
self.assertEqual(resp.status_int, 201, resp.body)
|
||||
# overwrite with new content
|
||||
req = Request.blank(
|
||||
'/sda1/p/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
|
||||
headers={
|
||||
'X-Timestamp': str(test_time + 100),
|
||||
'Content-Length': '0',
|
||||
'Content-Type': 'application/octet-stream'})
|
||||
resp = req.get_response(self.object_controller)
|
||||
self.assertEqual(resp.status_int, 201, resp.body)
|
||||
|
||||
# simulate processing a stale expirer queue entry
|
||||
req = Request.blank(
|
||||
'/sda1/p/a/c/o',
|
||||
environ={'REQUEST_METHOD': 'DELETE'},
|
||||
headers={'X-Timestamp': delete_at_timestamp,
|
||||
'X-If-Delete-At': delete_at_timestamp})
|
||||
resp = req.get_response(self.object_controller)
|
||||
self.assertEqual(resp.status_int, 409)
|
||||
# simulate processing a stale expirer queue entry
|
||||
req = Request.blank(
|
||||
'/sda1/p/a/c/o',
|
||||
environ={'REQUEST_METHOD': 'DELETE'},
|
||||
headers={'X-Timestamp': delete_at_timestamp,
|
||||
'X-If-Delete-At': delete_at_timestamp})
|
||||
resp = req.get_response(self.object_controller)
|
||||
self.assertEqual(resp.status_int, 409)
|
||||
|
||||
# make the x-if-delete-at for some not found
|
||||
req = Request.blank(
|
||||
'/sda1/p/a/c/o-not-found',
|
||||
environ={'REQUEST_METHOD': 'DELETE'},
|
||||
headers={'X-Timestamp': delete_at_timestamp,
|
||||
'X-If-Delete-At': delete_at_timestamp})
|
||||
resp = req.get_response(self.object_controller)
|
||||
self.assertEqual(resp.status_int, 404)
|
||||
# make the x-if-delete-at for some not found
|
||||
req = Request.blank(
|
||||
'/sda1/p/a/c/o-not-found',
|
||||
environ={'REQUEST_METHOD': 'DELETE'},
|
||||
headers={'X-Timestamp': delete_at_timestamp,
|
||||
'X-If-Delete-At': delete_at_timestamp})
|
||||
resp = req.get_response(self.object_controller)
|
||||
self.assertEqual(resp.status_int, 404)
|
||||
|
||||
def test_DELETE_if_delete_at(self):
|
||||
test_time = time() + 10000
|
||||
@ -6755,6 +6737,38 @@ class TestObjectController(unittest.TestCase):
|
||||
self.assertEqual(resp.status_int, 400)
|
||||
self.assertTrue('X-Delete-At in past' in resp.body)
|
||||
|
||||
def test_POST_delete_at_in_past_with_skewed_clock(self):
|
||||
proxy_server_put_time = 1000
|
||||
proxy_server_post_time = 1001
|
||||
delete_at = 1050
|
||||
obj_server_put_time = 1100
|
||||
obj_server_post_time = 1101
|
||||
|
||||
# test setup: make an object for us to POST to
|
||||
req = Request.blank(
|
||||
'/sda1/p/a/c/o',
|
||||
environ={'REQUEST_METHOD': 'PUT'},
|
||||
headers={'X-Timestamp': normalize_timestamp(proxy_server_put_time),
|
||||
'Content-Length': '4',
|
||||
'Content-Type': 'application/octet-stream'})
|
||||
req.body = 'TEST'
|
||||
with mock.patch('swift.obj.server.time.time',
|
||||
return_value=obj_server_put_time):
|
||||
resp = req.get_response(self.object_controller)
|
||||
self.assertEqual(resp.status_int, 201)
|
||||
|
||||
# then POST to it
|
||||
req = Request.blank(
|
||||
'/sda1/p/a/c/o',
|
||||
environ={'REQUEST_METHOD': 'POST'},
|
||||
headers={'X-Timestamp':
|
||||
normalize_timestamp(proxy_server_post_time),
|
||||
'X-Delete-At': str(delete_at)})
|
||||
with mock.patch('swift.obj.server.time.time',
|
||||
return_value=obj_server_post_time):
|
||||
resp = req.get_response(self.object_controller)
|
||||
self.assertEqual(resp.status_int, 202)
|
||||
|
||||
def test_REPLICATE_works(self):
|
||||
|
||||
def fake_get_hashes(*args, **kwargs):
|
||||
@ -7223,14 +7237,12 @@ class TestObjectController(unittest.TestCase):
|
||||
'/sda1/p/a/c/o',
|
||||
environ={'REQUEST_METHOD': 'HEAD', 'REMOTE_ADDR': '1.2.3.4'})
|
||||
self.object_controller.logger = self.logger
|
||||
with mock.patch(
|
||||
'time.gmtime', mock.MagicMock(side_effect=[gmtime(10001.0)])):
|
||||
with mock.patch(
|
||||
with mock.patch('time.gmtime', side_effect=[gmtime(10001.0)]), \
|
||||
mock.patch(
|
||||
'time.time',
|
||||
mock.MagicMock(side_effect=[10000.0, 10001.0, 10002.0])):
|
||||
with mock.patch(
|
||||
'os.getpid', mock.MagicMock(return_value=1234)):
|
||||
req.get_response(self.object_controller)
|
||||
side_effect=[10000.0, 10000.0, 10001.0, 10002.0]), \
|
||||
mock.patch('os.getpid', return_value=1234):
|
||||
req.get_response(self.object_controller)
|
||||
self.assertEqual(
|
||||
self.logger.get_lines_for_level('info'),
|
||||
['1.2.3.4 - - [01/Jan/1970:02:46:41 +0000] "HEAD /sda1/p/a/c/o" '
|
||||
@ -7319,6 +7331,7 @@ class TestObjectController(unittest.TestCase):
|
||||
existing_timestamp = normalize_timestamp(time())
|
||||
delete_timestamp = normalize_timestamp(time() + 1)
|
||||
put_timestamp = normalize_timestamp(time() + 2)
|
||||
head_timestamp = normalize_timestamp(time() + 3)
|
||||
|
||||
# make a .ts
|
||||
req = Request.blank(
|
||||
@ -7355,7 +7368,8 @@ class TestObjectController(unittest.TestCase):
|
||||
self.assertFalse(os.path.exists(qdir))
|
||||
|
||||
req = Request.blank('/sda1/p/a/c/o',
|
||||
environ={'REQUEST_METHOD': 'HEAD'})
|
||||
environ={'REQUEST_METHOD': 'HEAD'},
|
||||
headers={'X-Timestamp': head_timestamp})
|
||||
resp = req.get_response(self.object_controller)
|
||||
self.assertEqual(resp.status_int, 200)
|
||||
self.assertEqual(resp.headers['X-Timestamp'], put_timestamp)
|
||||
|
Loading…
x
Reference in New Issue
Block a user