diff --git a/swift/obj/diskfile.py b/swift/obj/diskfile.py index 323ee5a991..1cf9fa1e46 100644 --- a/swift/obj/diskfile.py +++ b/swift/obj/diskfile.py @@ -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, diff --git a/swift/obj/mem_diskfile.py b/swift/obj/mem_diskfile.py index eaa3a7d533..bbafb81e55 100644 --- a/swift/obj/mem_diskfile.py +++ b/swift/obj/mem_diskfile.py @@ -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): diff --git a/swift/obj/server.py b/swift/obj/server.py index 2f584bb319..815f902918 100644 --- a/swift/obj/server.py +++ b/swift/obj/server.py @@ -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 @@ -591,7 +591,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') @@ -604,7 +604,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): @@ -766,7 +766,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) @@ -954,6 +954,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: @@ -965,7 +968,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']) @@ -1018,6 +1021,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: @@ -1029,7 +1035,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: @@ -1083,7 +1089,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: diff --git a/test/unit/obj/test_server.py b/test/unit/obj/test_server.py index 7a77603f4c..8b0bae0da5 100644 --- a/test/unit/obj/test_server.py +++ b/test/unit/obj/test_server.py @@ -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)