Skip to content
This repository has been archived by the owner on Sep 26, 2019. It is now read-only.

Commit

Permalink
Ensure X-Timestamp always
Browse files Browse the repository at this point in the history
Current swift3 will try to use HTTP_X_TIMESTAMP in *response*
environ. However, that's not ensured because swob.Response class
doesn't force the request environ for its args. (i.e. thirdparty
middleware could instantiate the response w/o the request instance)

To ensure the X-Timestamp existence and to make swift3 more robust,
this patch makes swift3 to genarate timestamp by itself and to add
it into req.headers.

*bonus*
Create a new S3Timestamp class inherits swift.common.utils.Timestamp
to make timestamp string for s3 xml format which floors
mili/microseconds.

Change-Id: I4dacc11ec086fdd2e9bb3f68295f2ebaea68bede
  • Loading branch information
Kota Tsuyuzaki committed Dec 10, 2015
1 parent 4fce274 commit d8ffe5a
Show file tree
Hide file tree
Showing 8 changed files with 49 additions and 31 deletions.
15 changes: 5 additions & 10 deletions swift3/controllers/multi_upload.py
Expand Up @@ -45,7 +45,7 @@
import os
import re
import sys
import datetime
import time

from swift.common.utils import json
from swift.common.db import utf8encode
Expand All @@ -57,7 +57,7 @@
InvalidRequest, HTTPOk, HTTPNoContent, NoSuchKey, NoSuchUpload, \
NoSuchBucket
from swift3.exception import BadSwiftRequest
from swift3.utils import LOGGER, unique_id, MULTIUPLOAD_SUFFIX
from swift3.utils import LOGGER, unique_id, MULTIUPLOAD_SUFFIX, S3Timestamp
from swift3.etree import Element, SubElement, fromstring, tostring, \
XMLSyntaxError, DocumentInvalid
from swift3.cfg import CONF
Expand Down Expand Up @@ -121,19 +121,14 @@ def PUT(self, req):
req.object_name = '%s/%s/%d' % (req.object_name, upload_id,
part_number)

req_timestamp = S3Timestamp(time.time())
req.headers['X-Timestamp'] = req_timestamp.internal
req.check_copy_source(self.app)
resp = req.get_response(self.app)

if 'X-Amz-Copy-Source' in req.headers:
obj_timestamp = (datetime.datetime.fromtimestamp(
float(resp.environ['HTTP_X_TIMESTAMP']))
.isoformat())
if len(obj_timestamp) is 19:
obj_timestamp += '.000Z'
else:
obj_timestamp = obj_timestamp[:-3] + 'Z'
resp.append_copy_resp_body(req.controller_name,
obj_timestamp)
req_timestamp.s3xmlformat)

resp.status = 200
return resp
Expand Down
15 changes: 6 additions & 9 deletions swift3/controllers/obj.py
Expand Up @@ -14,11 +14,12 @@
# limitations under the License.

import sys
import datetime
import time

from swift.common.http import HTTP_OK, HTTP_PARTIAL_CONTENT, HTTP_NO_CONTENT
from swift.common.swob import Range, content_range_header_value

from swift3.utils import S3Timestamp
from swift3.controllers.base import Controller
from swift3.response import S3NotImplemented, InvalidRange, NoSuchKey

Expand Down Expand Up @@ -96,19 +97,15 @@ def PUT(self, req):
"""
Handle PUT Object and PUT Object (Copy) request
"""
# set X-Timestamp by swift3 to use at copy resp body
req_timestamp = S3Timestamp(time.time())
req.headers['X-Timestamp'] = req_timestamp.internal
req.check_copy_source(self.app)
resp = req.get_response(self.app)

if 'X-Amz-Copy-Source' in req.headers:
obj_timestamp = (datetime.datetime.fromtimestamp(
float(resp.environ['HTTP_X_TIMESTAMP']))
.isoformat())
if len(obj_timestamp) is 19:
obj_timestamp += '.000Z'
else:
obj_timestamp = obj_timestamp[:-3] + 'Z'
resp.append_copy_resp_body(req.controller_name,
obj_timestamp)
req_timestamp.s3xmlformat)

# delete object metadata from response
for key in list(resp.headers.keys()):
Expand Down
6 changes: 4 additions & 2 deletions swift3/test/functional/test_multi_upload.py
Expand Up @@ -191,9 +191,11 @@ def test_object_multi_upload(self):
self.conn.make_request('GET', bucket, key, query=query)

elem = fromstring(body, 'ListPartsResult')

# FIXME: COPY result drops mili/microseconds but GET doesn't
last_modified_get = elem.find('Part').find('LastModified').text
self.assertEquals(last_modified_get,
last_modified)
self.assertEquals(
last_modified_get[:-6], last_modified[:-6])

# List Parts
key, upload_id = uploads[0]
Expand Down
6 changes: 4 additions & 2 deletions swift3/test/functional/test_object.py
Expand Up @@ -83,8 +83,10 @@ def test_object(self):
self.assertEquals(status, 200)
elem = fromstring(body, 'ListBucketResult')

self.assertEquals(elem.find('Contents').find("LastModified").text,
last_modified_xml)
# FIXME: COPY result drops mili/microseconds but GET doesn't
self.assertEquals(
elem.find('Contents').find("LastModified").text[:-6],
last_modified_xml[:-6])

# GET Object
status, headers, body = \
Expand Down
7 changes: 4 additions & 3 deletions swift3/test/unit/test_multi_upload.py
Expand Up @@ -1114,10 +1114,11 @@ def _test_copy_for_s3acl(self, account, src_permission=None,
put_headers.update(put_header)
req = Request.blank(
'/bucket/object?partNumber=1&uploadId=X',
environ={'REQUEST_METHOD': 'PUT',
'HTTP_X_TIMESTAMP': '1396353600.000000'},
environ={'REQUEST_METHOD': 'PUT'},
headers=put_headers)
return self.call_swift3(req)
with patch('swift3.controllers.multi_upload.time.time') as mock_time:
mock_time.return_value = 1396353600.592270
return self.call_swift3(req)

@s3acl
def test_upload_part_copy(self):
Expand Down
10 changes: 5 additions & 5 deletions swift3/test/unit/test_obj.py
Expand Up @@ -464,13 +464,14 @@ def _test_object_PUT_copy(self, head_resp, put_header={}):
put_headers.update(put_header)

req = Request.blank('/bucket/object',
environ={'REQUEST_METHOD': 'PUT',
'HTTP_X_TIMESTAMP': '1396353600.000000'},
environ={'REQUEST_METHOD': 'PUT'},
headers=put_headers)

req.date = datetime.now()
req.content_type = 'text/plain'
return self.call_swift3(req)
with patch('swift3.controllers.obj.time.time') as mock_time:
mock_time.return_value = 1396353600.000000
return self.call_swift3(req)

@s3acl
def test_object_PUT_copy(self):
Expand Down Expand Up @@ -799,8 +800,7 @@ def _test_object_copy_for_s3acl(self, account, src_permission=None,

req = Request.blank(
'/bucket/object',
environ={'REQUEST_METHOD': 'PUT',
'HTTP_X_TIMESTAMP': '1396353600.000000'},
environ={'REQUEST_METHOD': 'PUT'},
headers={'Authorization': 'AWS %s:hmac' % account,
'X-Amz-Copy-Source': src_path,
'Date': self.get_date_header()})
Expand Down
15 changes: 15 additions & 0 deletions swift3/test/unit/test_utils.py
Expand Up @@ -82,6 +82,21 @@ def test_validate_bucket_name_with_dns_compliant_bucket_names_false(self):
self.assertFalse(utils.validate_bucket_name('bucket.'))
self.assertFalse(utils.validate_bucket_name('a' * 256))

def test_s3timestamp(self):
expected = '1970-01-01T00:00:01.000Z'
# integer
ts = utils.S3Timestamp(1)
self.assertEqual(expected, ts.s3xmlformat)
# miliseconds unit should be floored
ts = utils.S3Timestamp(1.1)
self.assertEqual(expected, ts.s3xmlformat)
# float (microseconds) should be floored too
ts = utils.S3Timestamp(1.000001)
self.assertEqual(expected, ts.s3xmlformat)
# Bigger float (miliseconds) should be floored too
ts = utils.S3Timestamp(1.9)
self.assertEqual(expected, ts.s3xmlformat)


if __name__ == '__main__':
unittest.main()
6 changes: 6 additions & 0 deletions swift3/utils.py
Expand Up @@ -135,3 +135,9 @@ def validate_bucket_name(name):
return False
else:
return True


class S3Timestamp(utils.Timestamp):
@property
def s3xmlformat(self):
return self.isoformat[:-7] + '.000Z'

0 comments on commit d8ffe5a

Please sign in to comment.