|
Pete Zaitcev |
ac36771 |
commit 1f4ec235cdfd8c868f2d6458532f9dc32c00b8ca
|
|
Pete Zaitcev |
ac36771 |
Author: Peter Portante <peter.portante@redhat.com>
|
|
Pete Zaitcev |
ac36771 |
Date: Fri Jul 26 15:03:34 2013 -0400
|
|
Pete Zaitcev |
ac36771 |
|
|
Pete Zaitcev |
ac36771 |
Fix handling of DELETE obj reqs with old timestamp
|
|
Pete Zaitcev |
ac36771 |
|
|
Pete Zaitcev |
ac36771 |
The DELETE object REST API was creating tombstone files with old
|
|
Pete Zaitcev |
ac36771 |
timestamps, potentially filling up the disk, as well as sending
|
|
Pete Zaitcev |
ac36771 |
container updates.
|
|
Pete Zaitcev |
ac36771 |
|
|
Pete Zaitcev |
ac36771 |
Here we now make DELETEs with a request timestamp return a 409 (HTTP
|
|
Pete Zaitcev |
ac36771 |
Conflict) if a data file exists with a newer timestamp, only creating
|
|
Pete Zaitcev |
ac36771 |
tombstones if they have a newer timestamp.
|
|
Pete Zaitcev |
ac36771 |
|
|
Pete Zaitcev |
ac36771 |
The key fix is to actually read the timestamp metadata from an
|
|
Pete Zaitcev |
ac36771 |
existing tombstone file (thanks to Pete Zaitcev for catching this),
|
|
Pete Zaitcev |
ac36771 |
and then only create tombstone files with newer timestamps.
|
|
Pete Zaitcev |
ac36771 |
|
|
Pete Zaitcev |
ac36771 |
We also prevent PUT and POST operations using old timestamps as well.
|
|
Pete Zaitcev |
ac36771 |
|
|
Pete Zaitcev |
ac36771 |
Change-Id: I631957029d17c6578bca5779367df5144ba01fc9
|
|
Pete Zaitcev |
ac36771 |
Signed-off-by: Peter Portante <peter.portante@redhat.com>
|
|
Pete Zaitcev |
ac36771 |
|
|
Pete Zaitcev |
ac36771 |
diff --git a/swift/obj/server.py b/swift/obj/server.py
|
|
Pete Zaitcev |
ac36771 |
index fc23ea2..f416162 100644
|
|
Pete Zaitcev |
ac36771 |
--- a/swift/obj/server.py
|
|
Pete Zaitcev |
ac36771 |
+++ b/swift/obj/server.py
|
|
Pete Zaitcev |
ac36771 |
@@ -46,7 +46,7 @@ from swift.common.swob import HTTPAccepted, HTTPBadRequest, HTTPCreated, \
|
|
Pete Zaitcev |
ac36771 |
HTTPInternalServerError, HTTPNoContent, HTTPNotFound, HTTPNotModified, \
|
|
Pete Zaitcev |
ac36771 |
HTTPPreconditionFailed, HTTPRequestTimeout, HTTPUnprocessableEntity, \
|
|
Pete Zaitcev |
ac36771 |
HTTPClientDisconnect, HTTPMethodNotAllowed, Request, Response, UTC, \
|
|
Pete Zaitcev |
ac36771 |
- HTTPInsufficientStorage, multi_range_iterator
|
|
Pete Zaitcev |
ac36771 |
+ HTTPInsufficientStorage, multi_range_iterator, HTTPConflict
|
|
Pete Zaitcev |
ac36771 |
|
|
Pete Zaitcev |
ac36771 |
|
|
Pete Zaitcev |
ac36771 |
DATADIR = 'objects'
|
|
Pete Zaitcev |
ac36771 |
@@ -121,7 +121,6 @@ class DiskFile(object):
|
|
Pete Zaitcev |
ac36771 |
self.tmppath = None
|
|
Pete Zaitcev |
ac36771 |
self.logger = logger
|
|
Pete Zaitcev |
ac36771 |
self.metadata = {}
|
|
Pete Zaitcev |
ac36771 |
- self.meta_file = None
|
|
Pete Zaitcev |
ac36771 |
self.data_file = None
|
|
Pete Zaitcev |
ac36771 |
self.fp = None
|
|
Pete Zaitcev |
ac36771 |
self.iter_etag = None
|
|
Pete Zaitcev |
ac36771 |
@@ -133,15 +132,18 @@ class DiskFile(object):
|
|
Pete Zaitcev |
ac36771 |
if not os.path.exists(self.datadir):
|
|
Pete Zaitcev |
ac36771 |
return
|
|
Pete Zaitcev |
ac36771 |
files = sorted(os.listdir(self.datadir), reverse=True)
|
|
Pete Zaitcev |
ac36771 |
- for file in files:
|
|
Pete Zaitcev |
ac36771 |
- if file.endswith('.ts'):
|
|
Pete Zaitcev |
ac36771 |
- self.data_file = self.meta_file = None
|
|
Pete Zaitcev |
ac36771 |
- self.metadata = {'deleted': True}
|
|
Pete Zaitcev |
ac36771 |
- return
|
|
Pete Zaitcev |
ac36771 |
- if file.endswith('.meta') and not self.meta_file:
|
|
Pete Zaitcev |
ac36771 |
- self.meta_file = os.path.join(self.datadir, file)
|
|
Pete Zaitcev |
ac36771 |
- if file.endswith('.data') and not self.data_file:
|
|
Pete Zaitcev |
ac36771 |
- self.data_file = os.path.join(self.datadir, file)
|
|
Pete Zaitcev |
ac36771 |
+ meta_file = None
|
|
Pete Zaitcev |
ac36771 |
+ for afile in files:
|
|
Pete Zaitcev |
ac36771 |
+ if afile.endswith('.ts'):
|
|
Pete Zaitcev |
ac36771 |
+ self.data_file = None
|
|
Pete Zaitcev |
ac36771 |
+ with open(os.path.join(self.datadir, afile)) as mfp:
|
|
Pete Zaitcev |
ac36771 |
+ self.metadata = read_metadata(mfp)
|
|
Pete Zaitcev |
ac36771 |
+ self.metadata['deleted'] = True
|
|
Pete Zaitcev |
ac36771 |
+ break
|
|
Pete Zaitcev |
ac36771 |
+ if afile.endswith('.meta') and not meta_file:
|
|
Pete Zaitcev |
ac36771 |
+ meta_file = os.path.join(self.datadir, afile)
|
|
Pete Zaitcev |
ac36771 |
+ if afile.endswith('.data') and not self.data_file:
|
|
Pete Zaitcev |
ac36771 |
+ self.data_file = os.path.join(self.datadir, afile)
|
|
Pete Zaitcev |
ac36771 |
break
|
|
Pete Zaitcev |
ac36771 |
if not self.data_file:
|
|
Pete Zaitcev |
ac36771 |
return
|
|
Pete Zaitcev |
ac36771 |
@@ -149,8 +151,8 @@ class DiskFile(object):
|
|
Pete Zaitcev |
ac36771 |
self.metadata = read_metadata(self.fp)
|
|
Pete Zaitcev |
ac36771 |
if not keep_data_fp:
|
|
Pete Zaitcev |
ac36771 |
self.close(verify_file=False)
|
|
Pete Zaitcev |
ac36771 |
- if self.meta_file:
|
|
Pete Zaitcev |
ac36771 |
- with open(self.meta_file) as mfp:
|
|
Pete Zaitcev |
ac36771 |
+ if meta_file:
|
|
Pete Zaitcev |
ac36771 |
+ with open(meta_file) as mfp:
|
|
Pete Zaitcev |
ac36771 |
for key in self.metadata.keys():
|
|
Pete Zaitcev |
ac36771 |
if key.lower() not in DISALLOWED_HEADERS:
|
|
Pete Zaitcev |
ac36771 |
del self.metadata[key]
|
|
Pete Zaitcev |
ac36771 |
@@ -594,6 +596,9 @@ class ObjectController(object):
|
|
Pete Zaitcev |
ac36771 |
except (DiskFileError, DiskFileNotExist):
|
|
Pete Zaitcev |
ac36771 |
file.quarantine()
|
|
Pete Zaitcev |
ac36771 |
return HTTPNotFound(request=request)
|
|
Pete Zaitcev |
ac36771 |
+ orig_timestamp = file.metadata.get('X-Timestamp', '0')
|
|
Pete Zaitcev |
ac36771 |
+ if orig_timestamp >= request.headers['x-timestamp']:
|
|
Pete Zaitcev |
ac36771 |
+ return HTTPConflict(request=request)
|
|
Pete Zaitcev |
ac36771 |
metadata = {'X-Timestamp': request.headers['x-timestamp']}
|
|
Pete Zaitcev |
ac36771 |
metadata.update(val for val in request.headers.iteritems()
|
|
Pete Zaitcev |
ac36771 |
if val[0].lower().startswith('x-object-meta-'))
|
|
Pete Zaitcev |
ac36771 |
@@ -639,6 +644,8 @@ class ObjectController(object):
|
|
Pete Zaitcev |
ac36771 |
file = DiskFile(self.devices, device, partition, account, container,
|
|
Pete Zaitcev |
ac36771 |
obj, self.logger, disk_chunk_size=self.disk_chunk_size)
|
|
Pete Zaitcev |
ac36771 |
orig_timestamp = file.metadata.get('X-Timestamp')
|
|
Pete Zaitcev |
ac36771 |
+ if orig_timestamp and orig_timestamp >= request.headers['x-timestamp']:
|
|
Pete Zaitcev |
ac36771 |
+ return HTTPConflict(request=request)
|
|
Pete Zaitcev |
ac36771 |
upload_expiration = time.time() + self.max_upload_time
|
|
Pete Zaitcev |
ac36771 |
etag = md5()
|
|
Pete Zaitcev |
ac36771 |
upload_size = 0
|
|
Pete Zaitcev |
ac36771 |
@@ -863,23 +870,26 @@ class ObjectController(object):
|
|
Pete Zaitcev |
ac36771 |
return HTTPPreconditionFailed(
|
|
Pete Zaitcev |
ac36771 |
request=request,
|
|
Pete Zaitcev |
ac36771 |
body='X-If-Delete-At and X-Delete-At do not match')
|
|
Pete Zaitcev |
ac36771 |
- orig_timestamp = file.metadata.get('X-Timestamp')
|
|
Pete Zaitcev |
ac36771 |
- if file.is_deleted() or file.is_expired():
|
|
Pete Zaitcev |
ac36771 |
- response_class = HTTPNotFound
|
|
Pete Zaitcev |
ac36771 |
- metadata = {
|
|
Pete Zaitcev |
ac36771 |
- 'X-Timestamp': request.headers['X-Timestamp'], 'deleted': True,
|
|
Pete Zaitcev |
ac36771 |
- }
|
|
Pete Zaitcev |
ac36771 |
old_delete_at = int(file.metadata.get('X-Delete-At') or 0)
|
|
Pete Zaitcev |
ac36771 |
if old_delete_at:
|
|
Pete Zaitcev |
ac36771 |
self.delete_at_update('DELETE', old_delete_at, account,
|
|
Pete Zaitcev |
ac36771 |
container, obj, request.headers, device)
|
|
Pete Zaitcev |
ac36771 |
- file.put_metadata(metadata, tombstone=True)
|
|
Pete Zaitcev |
ac36771 |
- file.unlinkold(metadata['X-Timestamp'])
|
|
Pete Zaitcev |
ac36771 |
- if not orig_timestamp or \
|
|
Pete Zaitcev |
ac36771 |
- orig_timestamp < request.headers['x-timestamp']:
|
|
Pete Zaitcev |
ac36771 |
+ orig_timestamp = file.metadata.get('X-Timestamp', 0)
|
|
Pete Zaitcev |
ac36771 |
+ req_timestamp = request.headers['X-Timestamp']
|
|
Pete Zaitcev |
ac36771 |
+ if file.is_deleted() or file.is_expired():
|
|
Pete Zaitcev |
ac36771 |
+ response_class = HTTPNotFound
|
|
Pete Zaitcev |
ac36771 |
+ else:
|
|
Pete Zaitcev |
ac36771 |
+ if orig_timestamp < req_timestamp:
|
|
Pete Zaitcev |
ac36771 |
+ response_class = HTTPNoContent
|
|
Pete Zaitcev |
ac36771 |
+ else:
|
|
Pete Zaitcev |
ac36771 |
+ response_class = HTTPConflict
|
|
Pete Zaitcev |
ac36771 |
+ if orig_timestamp < req_timestamp:
|
|
Pete Zaitcev |
ac36771 |
+ file.put_metadata({'X-Timestamp': req_timestamp},
|
|
Pete Zaitcev |
ac36771 |
+ tombstone=True)
|
|
Pete Zaitcev |
ac36771 |
+ file.unlinkold(req_timestamp)
|
|
Pete Zaitcev |
ac36771 |
self.container_update(
|
|
Pete Zaitcev |
ac36771 |
'DELETE', account, container, obj, request.headers,
|
|
Pete Zaitcev |
ac36771 |
- {'x-timestamp': metadata['X-Timestamp'],
|
|
Pete Zaitcev |
ac36771 |
+ {'x-timestamp': req_timestamp,
|
|
Pete Zaitcev |
ac36771 |
'x-trans-id': request.headers.get('x-trans-id', '-')},
|
|
Pete Zaitcev |
ac36771 |
device)
|
|
Pete Zaitcev |
ac36771 |
resp = response_class(request=request)
|
|
Pete Zaitcev |
ac36771 |
diff --git a/test/unit/obj/test_server.py b/test/unit/obj/test_server.py
|
|
Pete Zaitcev |
ac36771 |
index 8ee266b..b354b97 100755
|
|
Pete Zaitcev |
ac36771 |
--- a/test/unit/obj/test_server.py
|
|
Pete Zaitcev |
ac36771 |
+++ b/test/unit/obj/test_server.py
|
|
Pete Zaitcev |
ac36771 |
@@ -509,6 +509,41 @@ class TestObjectController(unittest.TestCase):
|
|
Pete Zaitcev |
ac36771 |
"X-Object-Meta-3" in resp.headers)
|
|
Pete Zaitcev |
ac36771 |
self.assertEquals(resp.headers['Content-Type'], 'application/x-test')
|
|
Pete Zaitcev |
ac36771 |
|
|
Pete Zaitcev |
ac36771 |
+ def test_POST_old_timestamp(self):
|
|
Pete Zaitcev |
ac36771 |
+ ts = time()
|
|
Pete Zaitcev |
ac36771 |
+ timestamp = normalize_timestamp(ts)
|
|
Pete Zaitcev |
ac36771 |
+ req = Request.blank('/sda1/p/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
|
|
Pete Zaitcev |
ac36771 |
+ headers={'X-Timestamp': timestamp,
|
|
Pete Zaitcev |
ac36771 |
+ 'Content-Type': 'application/x-test',
|
|
Pete Zaitcev |
ac36771 |
+ 'X-Object-Meta-1': 'One',
|
|
Pete Zaitcev |
ac36771 |
+ 'X-Object-Meta-Two': 'Two'})
|
|
Pete Zaitcev |
ac36771 |
+ req.body = 'VERIFY'
|
|
Pete Zaitcev |
ac36771 |
+ resp = self.object_controller.PUT(req)
|
|
Pete Zaitcev |
ac36771 |
+ self.assertEquals(resp.status_int, 201)
|
|
Pete Zaitcev |
ac36771 |
+
|
|
Pete Zaitcev |
ac36771 |
+ # Same timestamp should result in 409
|
|
Pete Zaitcev |
ac36771 |
+ req = Request.blank('/sda1/p/a/c/o',
|
|
Pete Zaitcev |
ac36771 |
+ environ={'REQUEST_METHOD': 'POST'},
|
|
Pete Zaitcev |
ac36771 |
+ headers={'X-Timestamp': timestamp,
|
|
Pete Zaitcev |
ac36771 |
+ 'X-Object-Meta-3': 'Three',
|
|
Pete Zaitcev |
ac36771 |
+ 'X-Object-Meta-4': 'Four',
|
|
Pete Zaitcev |
ac36771 |
+ 'Content-Encoding': 'gzip',
|
|
Pete Zaitcev |
ac36771 |
+ 'Content-Type': 'application/x-test'})
|
|
Pete Zaitcev |
ac36771 |
+ resp = self.object_controller.POST(req)
|
|
Pete Zaitcev |
ac36771 |
+ self.assertEquals(resp.status_int, 409)
|
|
Pete Zaitcev |
ac36771 |
+
|
|
Pete Zaitcev |
ac36771 |
+ # Earlier timestamp should result in 409
|
|
Pete Zaitcev |
ac36771 |
+ timestamp = normalize_timestamp(ts - 1)
|
|
Pete Zaitcev |
ac36771 |
+ req = Request.blank('/sda1/p/a/c/o',
|
|
Pete Zaitcev |
ac36771 |
+ environ={'REQUEST_METHOD': 'POST'},
|
|
Pete Zaitcev |
ac36771 |
+ headers={'X-Timestamp': timestamp,
|
|
Pete Zaitcev |
ac36771 |
+ 'X-Object-Meta-5': 'Five',
|
|
Pete Zaitcev |
ac36771 |
+ 'X-Object-Meta-6': 'Six',
|
|
Pete Zaitcev |
ac36771 |
+ 'Content-Encoding': 'gzip',
|
|
Pete Zaitcev |
ac36771 |
+ 'Content-Type': 'application/x-test'})
|
|
Pete Zaitcev |
ac36771 |
+ resp = self.object_controller.POST(req)
|
|
Pete Zaitcev |
ac36771 |
+ self.assertEquals(resp.status_int, 409)
|
|
Pete Zaitcev |
ac36771 |
+
|
|
Pete Zaitcev |
ac36771 |
def test_POST_not_exist(self):
|
|
Pete Zaitcev |
ac36771 |
timestamp = normalize_timestamp(time())
|
|
Pete Zaitcev |
ac36771 |
req = Request.blank('/sda1/p/a/c/fail',
|
|
Pete Zaitcev |
ac36771 |
@@ -555,11 +590,15 @@ class TestObjectController(unittest.TestCase):
|
|
Pete Zaitcev |
ac36771 |
|
|
Pete Zaitcev |
ac36771 |
old_http_connect = object_server.http_connect
|
|
Pete Zaitcev |
ac36771 |
try:
|
|
Pete Zaitcev |
ac36771 |
- timestamp = normalize_timestamp(time())
|
|
Pete Zaitcev |
ac36771 |
+ ts = time()
|
|
Pete Zaitcev |
ac36771 |
+ timestamp = normalize_timestamp(ts)
|
|
Pete Zaitcev |
ac36771 |
req = Request.blank('/sda1/p/a/c/o', environ={'REQUEST_METHOD':
|
|
Pete Zaitcev |
ac36771 |
'POST'}, headers={'X-Timestamp': timestamp, 'Content-Type':
|
|
Pete Zaitcev |
ac36771 |
'text/plain', 'Content-Length': '0'})
|
|
Pete Zaitcev |
ac36771 |
resp = self.object_controller.PUT(req)
|
|
Pete Zaitcev |
ac36771 |
+ self.assertEquals(resp.status_int, 201)
|
|
Pete Zaitcev |
ac36771 |
+
|
|
Pete Zaitcev |
ac36771 |
+ timestamp = normalize_timestamp(ts + 1)
|
|
Pete Zaitcev |
ac36771 |
req = Request.blank('/sda1/p/a/c/o',
|
|
Pete Zaitcev |
ac36771 |
environ={'REQUEST_METHOD': 'POST'},
|
|
Pete Zaitcev |
ac36771 |
headers={'X-Timestamp': timestamp,
|
|
Pete Zaitcev |
ac36771 |
@@ -571,6 +610,8 @@ class TestObjectController(unittest.TestCase):
|
|
Pete Zaitcev |
ac36771 |
object_server.http_connect = mock_http_connect(202)
|
|
Pete Zaitcev |
ac36771 |
resp = self.object_controller.POST(req)
|
|
Pete Zaitcev |
ac36771 |
self.assertEquals(resp.status_int, 202)
|
|
Pete Zaitcev |
ac36771 |
+
|
|
Pete Zaitcev |
ac36771 |
+ timestamp = normalize_timestamp(ts + 2)
|
|
Pete Zaitcev |
ac36771 |
req = Request.blank('/sda1/p/a/c/o',
|
|
Pete Zaitcev |
ac36771 |
environ={'REQUEST_METHOD': 'POST'},
|
|
Pete Zaitcev |
ac36771 |
headers={'X-Timestamp': timestamp,
|
|
Pete Zaitcev |
ac36771 |
@@ -582,6 +623,8 @@ class TestObjectController(unittest.TestCase):
|
|
Pete Zaitcev |
ac36771 |
object_server.http_connect = mock_http_connect(202, with_exc=True)
|
|
Pete Zaitcev |
ac36771 |
resp = self.object_controller.POST(req)
|
|
Pete Zaitcev |
ac36771 |
self.assertEquals(resp.status_int, 202)
|
|
Pete Zaitcev |
ac36771 |
+
|
|
Pete Zaitcev |
ac36771 |
+ timestamp = normalize_timestamp(ts + 3)
|
|
Pete Zaitcev |
ac36771 |
req = Request.blank('/sda1/p/a/c/o',
|
|
Pete Zaitcev |
ac36771 |
environ={'REQUEST_METHOD': 'POST'},
|
|
Pete Zaitcev |
ac36771 |
headers={'X-Timestamp': timestamp,
|
|
Pete Zaitcev |
ac36771 |
@@ -718,6 +761,32 @@ class TestObjectController(unittest.TestCase):
|
|
Pete Zaitcev |
ac36771 |
'name': '/a/c/o',
|
|
Pete Zaitcev |
ac36771 |
'Content-Encoding': 'gzip'})
|
|
Pete Zaitcev |
ac36771 |
|
|
Pete Zaitcev |
ac36771 |
+ def test_PUT_old_timestamp(self):
|
|
Pete Zaitcev |
ac36771 |
+ ts = time()
|
|
Pete Zaitcev |
ac36771 |
+ req = Request.blank('/sda1/p/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
|
|
Pete Zaitcev |
ac36771 |
+ headers={'X-Timestamp': normalize_timestamp(ts),
|
|
Pete Zaitcev |
ac36771 |
+ 'Content-Length': '6',
|
|
Pete Zaitcev |
ac36771 |
+ 'Content-Type': 'application/octet-stream'})
|
|
Pete Zaitcev |
ac36771 |
+ req.body = 'VERIFY'
|
|
Pete Zaitcev |
ac36771 |
+ resp = self.object_controller.PUT(req)
|
|
Pete Zaitcev |
ac36771 |
+ self.assertEquals(resp.status_int, 201)
|
|
Pete Zaitcev |
ac36771 |
+
|
|
Pete Zaitcev |
ac36771 |
+ req = Request.blank('/sda1/p/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
|
|
Pete Zaitcev |
ac36771 |
+ headers={'X-Timestamp': normalize_timestamp(ts),
|
|
Pete Zaitcev |
ac36771 |
+ 'Content-Type': 'text/plain',
|
|
Pete Zaitcev |
ac36771 |
+ 'Content-Encoding': 'gzip'})
|
|
Pete Zaitcev |
ac36771 |
+ req.body = 'VERIFY TWO'
|
|
Pete Zaitcev |
ac36771 |
+ resp = self.object_controller.PUT(req)
|
|
Pete Zaitcev |
ac36771 |
+ self.assertEquals(resp.status_int, 409)
|
|
Pete Zaitcev |
ac36771 |
+
|
|
Pete Zaitcev |
ac36771 |
+ req = Request.blank('/sda1/p/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
|
|
Pete Zaitcev |
ac36771 |
+ headers={'X-Timestamp': normalize_timestamp(ts - 1),
|
|
Pete Zaitcev |
ac36771 |
+ 'Content-Type': 'text/plain',
|
|
Pete Zaitcev |
ac36771 |
+ 'Content-Encoding': 'gzip'})
|
|
Pete Zaitcev |
ac36771 |
+ req.body = 'VERIFY THREE'
|
|
Pete Zaitcev |
ac36771 |
+ resp = self.object_controller.PUT(req)
|
|
Pete Zaitcev |
ac36771 |
+ self.assertEquals(resp.status_int, 409)
|
|
Pete Zaitcev |
ac36771 |
+
|
|
Pete Zaitcev |
ac36771 |
def test_PUT_no_etag(self):
|
|
Pete Zaitcev |
ac36771 |
req = Request.blank('/sda1/p/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
|
|
Pete Zaitcev |
ac36771 |
headers={'X-Timestamp': normalize_timestamp(time()),
|
|
Pete Zaitcev |
ac36771 |
@@ -1306,12 +1375,32 @@ class TestObjectController(unittest.TestCase):
|
|
Pete Zaitcev |
ac36771 |
self.assertEquals(resp.status_int, 400)
|
|
Pete Zaitcev |
ac36771 |
# self.assertRaises(KeyError, self.object_controller.DELETE, req)
|
|
Pete Zaitcev |
ac36771 |
|
|
Pete Zaitcev |
ac36771 |
+ # The following should have created a tombstone file
|
|
Pete Zaitcev |
ac36771 |
timestamp = normalize_timestamp(time())
|
|
Pete Zaitcev |
ac36771 |
req = Request.blank('/sda1/p/a/c/o',
|
|
Pete Zaitcev |
ac36771 |
environ={'REQUEST_METHOD': 'DELETE'},
|
|
Pete Zaitcev |
ac36771 |
headers={'X-Timestamp': timestamp})
|
|
Pete Zaitcev |
ac36771 |
resp = self.object_controller.DELETE(req)
|
|
Pete Zaitcev |
ac36771 |
self.assertEquals(resp.status_int, 404)
|
|
Pete Zaitcev |
ac36771 |
+ objfile = os.path.join(self.testdir, 'sda1',
|
|
Pete Zaitcev |
ac36771 |
+ storage_directory(object_server.DATADIR, 'p',
|
|
Pete Zaitcev |
ac36771 |
+ hash_path('a', 'c', 'o')),
|
|
Pete Zaitcev |
ac36771 |
+ timestamp + '.ts')
|
|
Pete Zaitcev |
ac36771 |
+ self.assert_(os.path.isfile(objfile))
|
|
Pete Zaitcev |
ac36771 |
+
|
|
Pete Zaitcev |
ac36771 |
+ # The following should *not* have created a tombstone file.
|
|
Pete Zaitcev |
ac36771 |
+ timestamp = normalize_timestamp(float(timestamp) - 1)
|
|
Pete Zaitcev |
ac36771 |
+ req = Request.blank('/sda1/p/a/c/o',
|
|
Pete Zaitcev |
ac36771 |
+ environ={'REQUEST_METHOD': 'DELETE'},
|
|
Pete Zaitcev |
ac36771 |
+ headers={'X-Timestamp': timestamp})
|
|
Pete Zaitcev |
ac36771 |
+ resp = self.object_controller.DELETE(req)
|
|
Pete Zaitcev |
ac36771 |
+ self.assertEquals(resp.status_int, 404)
|
|
Pete Zaitcev |
ac36771 |
+ objfile = os.path.join(self.testdir, 'sda1',
|
|
Pete Zaitcev |
ac36771 |
+ storage_directory(object_server.DATADIR, 'p',
|
|
Pete Zaitcev |
ac36771 |
+ hash_path('a', 'c', 'o')),
|
|
Pete Zaitcev |
ac36771 |
+ timestamp + '.ts')
|
|
Pete Zaitcev |
ac36771 |
+ self.assertFalse(os.path.exists(objfile))
|
|
Pete Zaitcev |
ac36771 |
+ self.assertEquals(len(os.listdir(os.path.dirname(objfile))), 1)
|
|
Pete Zaitcev |
ac36771 |
|
|
Pete Zaitcev |
ac36771 |
sleep(.00001)
|
|
Pete Zaitcev |
ac36771 |
timestamp = normalize_timestamp(time())
|
|
Pete Zaitcev |
ac36771 |
@@ -1325,17 +1414,19 @@ class TestObjectController(unittest.TestCase):
|
|
Pete Zaitcev |
ac36771 |
resp = self.object_controller.PUT(req)
|
|
Pete Zaitcev |
ac36771 |
self.assertEquals(resp.status_int, 201)
|
|
Pete Zaitcev |
ac36771 |
|
|
Pete Zaitcev |
ac36771 |
+ # The following should *not* have created a tombstone file.
|
|
Pete Zaitcev |
ac36771 |
timestamp = normalize_timestamp(float(timestamp) - 1)
|
|
Pete Zaitcev |
ac36771 |
req = Request.blank('/sda1/p/a/c/o',
|
|
Pete Zaitcev |
ac36771 |
environ={'REQUEST_METHOD': 'DELETE'},
|
|
Pete Zaitcev |
ac36771 |
headers={'X-Timestamp': timestamp})
|
|
Pete Zaitcev |
ac36771 |
resp = self.object_controller.DELETE(req)
|
|
Pete Zaitcev |
ac36771 |
- self.assertEquals(resp.status_int, 204)
|
|
Pete Zaitcev |
ac36771 |
+ self.assertEquals(resp.status_int, 409)
|
|
Pete Zaitcev |
ac36771 |
objfile = os.path.join(self.testdir, 'sda1',
|
|
Pete Zaitcev |
ac36771 |
storage_directory(object_server.DATADIR, 'p',
|
|
Pete Zaitcev |
ac36771 |
hash_path('a', 'c', 'o')),
|
|
Pete Zaitcev |
ac36771 |
timestamp + '.ts')
|
|
Pete Zaitcev |
ac36771 |
- self.assert_(os.path.isfile(objfile))
|
|
Pete Zaitcev |
ac36771 |
+ self.assertFalse(os.path.exists(objfile))
|
|
Pete Zaitcev |
ac36771 |
+ self.assertEquals(len(os.listdir(os.path.dirname(objfile))), 1)
|
|
Pete Zaitcev |
ac36771 |
|
|
Pete Zaitcev |
ac36771 |
sleep(.00001)
|
|
Pete Zaitcev |
ac36771 |
timestamp = normalize_timestamp(time())
|
|
Pete Zaitcev |
ac36771 |
@@ -1350,6 +1441,103 @@ class TestObjectController(unittest.TestCase):
|
|
Pete Zaitcev |
ac36771 |
timestamp + '.ts')
|
|
Pete Zaitcev |
ac36771 |
self.assert_(os.path.isfile(objfile))
|
|
Pete Zaitcev |
ac36771 |
|
|
Pete Zaitcev |
ac36771 |
+ def test_DELETE_container_updates(self):
|
|
Pete Zaitcev |
ac36771 |
+ # Test swift.object_server.ObjectController.DELETE and container
|
|
Pete Zaitcev |
ac36771 |
+ # updates, making sure container update is called in the correct
|
|
Pete Zaitcev |
ac36771 |
+ # state.
|
|
Pete Zaitcev |
ac36771 |
+ timestamp = normalize_timestamp(time())
|
|
Pete Zaitcev |
ac36771 |
+ req = Request.blank('/sda1/p/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
|
|
Pete Zaitcev |
ac36771 |
+ headers={
|
|
Pete Zaitcev |
ac36771 |
+ 'X-Timestamp': timestamp,
|
|
Pete Zaitcev |
ac36771 |
+ 'Content-Type': 'application/octet-stream',
|
|
Pete Zaitcev |
ac36771 |
+ 'Content-Length': '4',
|
|
Pete Zaitcev |
ac36771 |
+ })
|
|
Pete Zaitcev |
ac36771 |
+ req.body = 'test'
|
|
Pete Zaitcev |
ac36771 |
+ resp = self.object_controller.PUT(req)
|
|
Pete Zaitcev |
ac36771 |
+ self.assertEquals(resp.status_int, 201)
|
|
Pete Zaitcev |
ac36771 |
+
|
|
Pete Zaitcev |
ac36771 |
+ calls_made = [0]
|
|
Pete Zaitcev |
ac36771 |
+
|
|
Pete Zaitcev |
ac36771 |
+ def our_container_update(*args, **kwargs):
|
|
Pete Zaitcev |
ac36771 |
+ calls_made[0] += 1
|
|
Pete Zaitcev |
ac36771 |
+
|
|
Pete Zaitcev |
ac36771 |
+ orig_cu = self.object_controller.container_update
|
|
Pete Zaitcev |
ac36771 |
+ self.object_controller.container_update = our_container_update
|
|
Pete Zaitcev |
ac36771 |
+ try:
|
|
Pete Zaitcev |
ac36771 |
+ # The following request should return 409 (HTTP Conflict). A
|
|
Pete Zaitcev |
ac36771 |
+ # tombstone file should not have been created with this timestamp.
|
|
Pete Zaitcev |
ac36771 |
+ timestamp = normalize_timestamp(float(timestamp) - 1)
|
|
Pete Zaitcev |
ac36771 |
+ req = Request.blank('/sda1/p/a/c/o',
|
|
Pete Zaitcev |
ac36771 |
+ environ={'REQUEST_METHOD': 'DELETE'},
|
|
Pete Zaitcev |
ac36771 |
+ headers={'X-Timestamp': timestamp})
|
|
Pete Zaitcev |
ac36771 |
+ resp = self.object_controller.DELETE(req)
|
|
Pete Zaitcev |
ac36771 |
+ self.assertEquals(resp.status_int, 409)
|
|
Pete Zaitcev |
ac36771 |
+ objfile = os.path.join(self.testdir, 'sda1',
|
|
Pete Zaitcev |
ac36771 |
+ storage_directory(object_server.DATADIR, 'p',
|
|
Pete Zaitcev |
ac36771 |
+ hash_path('a', 'c', 'o')),
|
|
Pete Zaitcev |
ac36771 |
+ timestamp + '.ts')
|
|
Pete Zaitcev |
ac36771 |
+ self.assertFalse(os.path.isfile(objfile))
|
|
Pete Zaitcev |
ac36771 |
+ self.assertEquals(len(os.listdir(os.path.dirname(objfile))), 1)
|
|
Pete Zaitcev |
ac36771 |
+ self.assertEquals(0, calls_made[0])
|
|
Pete Zaitcev |
ac36771 |
+
|
|
Pete Zaitcev |
ac36771 |
+ # The following request should return 204, and the object should
|
|
Pete Zaitcev |
ac36771 |
+ # be truly deleted (container update is performed) because this
|
|
Pete Zaitcev |
ac36771 |
+ # timestamp is newer. A tombstone file should have been created
|
|
Pete Zaitcev |
ac36771 |
+ # with this timestamp.
|
|
Pete Zaitcev |
ac36771 |
+ sleep(.00001)
|
|
Pete Zaitcev |
ac36771 |
+ timestamp = normalize_timestamp(time())
|
|
Pete Zaitcev |
ac36771 |
+ req = Request.blank('/sda1/p/a/c/o',
|
|
Pete Zaitcev |
ac36771 |
+ environ={'REQUEST_METHOD': 'DELETE'},
|
|
Pete Zaitcev |
ac36771 |
+ headers={'X-Timestamp': timestamp})
|
|
Pete Zaitcev |
ac36771 |
+ resp = self.object_controller.DELETE(req)
|
|
Pete Zaitcev |
ac36771 |
+ self.assertEquals(resp.status_int, 204)
|
|
Pete Zaitcev |
ac36771 |
+ objfile = os.path.join(self.testdir, 'sda1',
|
|
Pete Zaitcev |
ac36771 |
+ storage_directory(object_server.DATADIR, 'p',
|
|
Pete Zaitcev |
ac36771 |
+ hash_path('a', 'c', 'o')),
|
|
Pete Zaitcev |
ac36771 |
+ timestamp + '.ts')
|
|
Pete Zaitcev |
ac36771 |
+ self.assert_(os.path.isfile(objfile))
|
|
Pete Zaitcev |
ac36771 |
+ self.assertEquals(1, calls_made[0])
|
|
Pete Zaitcev |
ac36771 |
+ self.assertEquals(len(os.listdir(os.path.dirname(objfile))), 1)
|
|
Pete Zaitcev |
ac36771 |
+
|
|
Pete Zaitcev |
ac36771 |
+ # The following request should return a 404, as the object should
|
|
Pete Zaitcev |
ac36771 |
+ # already have been deleted, but it should have also performed a
|
|
Pete Zaitcev |
ac36771 |
+ # container update because the timestamp is newer, and a tombstone
|
|
Pete Zaitcev |
ac36771 |
+ # file should also exist with this timestamp.
|
|
Pete Zaitcev |
ac36771 |
+ sleep(.00001)
|
|
Pete Zaitcev |
ac36771 |
+ timestamp = normalize_timestamp(time())
|
|
Pete Zaitcev |
ac36771 |
+ req = Request.blank('/sda1/p/a/c/o',
|
|
Pete Zaitcev |
ac36771 |
+ environ={'REQUEST_METHOD': 'DELETE'},
|
|
Pete Zaitcev |
ac36771 |
+ headers={'X-Timestamp': timestamp})
|
|
Pete Zaitcev |
ac36771 |
+ resp = self.object_controller.DELETE(req)
|
|
Pete Zaitcev |
ac36771 |
+ self.assertEquals(resp.status_int, 404)
|
|
Pete Zaitcev |
ac36771 |
+ objfile = os.path.join(self.testdir, 'sda1',
|
|
Pete Zaitcev |
ac36771 |
+ storage_directory(object_server.DATADIR, 'p',
|
|
Pete Zaitcev |
ac36771 |
+ hash_path('a', 'c', 'o')),
|
|
Pete Zaitcev |
ac36771 |
+ timestamp + '.ts')
|
|
Pete Zaitcev |
ac36771 |
+ self.assert_(os.path.isfile(objfile))
|
|
Pete Zaitcev |
ac36771 |
+ self.assertEquals(2, calls_made[0])
|
|
Pete Zaitcev |
ac36771 |
+ self.assertEquals(len(os.listdir(os.path.dirname(objfile))), 1)
|
|
Pete Zaitcev |
ac36771 |
+
|
|
Pete Zaitcev |
ac36771 |
+ # The following request should return a 404, as the object should
|
|
Pete Zaitcev |
ac36771 |
+ # already have been deleted, and it should not have performed a
|
|
Pete Zaitcev |
ac36771 |
+ # container update because the timestamp is older, or created a
|
|
Pete Zaitcev |
ac36771 |
+ # tombstone file with this timestamp.
|
|
Pete Zaitcev |
ac36771 |
+ timestamp = normalize_timestamp(float(timestamp) - 1)
|
|
Pete Zaitcev |
ac36771 |
+ req = Request.blank('/sda1/p/a/c/o',
|
|
Pete Zaitcev |
ac36771 |
+ environ={'REQUEST_METHOD': 'DELETE'},
|
|
Pete Zaitcev |
ac36771 |
+ headers={'X-Timestamp': timestamp})
|
|
Pete Zaitcev |
ac36771 |
+ resp = self.object_controller.DELETE(req)
|
|
Pete Zaitcev |
ac36771 |
+ self.assertEquals(resp.status_int, 404)
|
|
Pete Zaitcev |
ac36771 |
+ objfile = os.path.join(self.testdir, 'sda1',
|
|
Pete Zaitcev |
ac36771 |
+ storage_directory(object_server.DATADIR, 'p',
|
|
Pete Zaitcev |
ac36771 |
+ hash_path('a', 'c', 'o')),
|
|
Pete Zaitcev |
ac36771 |
+ timestamp + '.ts')
|
|
Pete Zaitcev |
ac36771 |
+ self.assertFalse(os.path.isfile(objfile))
|
|
Pete Zaitcev |
ac36771 |
+ self.assertEquals(2, calls_made[0])
|
|
Pete Zaitcev |
ac36771 |
+ self.assertEquals(len(os.listdir(os.path.dirname(objfile))), 1)
|
|
Pete Zaitcev |
ac36771 |
+ finally:
|
|
Pete Zaitcev |
ac36771 |
+ self.object_controller.container_update = orig_cu
|
|
Pete Zaitcev |
ac36771 |
+
|
|
Pete Zaitcev |
ac36771 |
def test_call(self):
|
|
Pete Zaitcev |
ac36771 |
""" Test swift.object_server.ObjectController.__call__ """
|
|
Pete Zaitcev |
ac36771 |
inbuf = StringIO()
|