diff --git a/swift3/controllers/obj.py b/swift3/controllers/obj.py index 98d965e8..09af7045 100644 --- a/swift3/controllers/obj.py +++ b/swift3/controllers/obj.py @@ -17,8 +17,7 @@ from swift.common.http import HTTP_OK from swift.common.swob import Range, content_range_header_value from swift3.controllers.base import Controller -from swift3.response import HTTPOk, S3NotImplemented, InvalidRange,\ - HTTPPartialContent +from swift3.response import S3NotImplemented, InvalidRange, HTTPPartialContent from swift3.etree import Element, SubElement, tostring @@ -99,9 +98,12 @@ class ObjectController(Controller): if 'X-Amz-Copy-Source' in req.headers: elem = Element('CopyObjectResult') + SubElement(elem, 'LastModified').text = \ + resp.last_modified.isoformat()[:-6] + '.000Z' SubElement(elem, 'ETag').text = '"%s"' % resp.etag - body = tostring(elem, use_s3ns=False) - return HTTPOk(body=body, headers=resp.headers) + resp.body = tostring(elem) + resp.headers['Content-Type'] = 'application/xml' + resp.etag = None resp.status = HTTP_OK return resp diff --git a/swift3/test/unit/test_obj.py b/swift3/test/unit/test_obj.py index 73993632..d0aeb4f9 100644 --- a/swift3/test/unit/test_obj.py +++ b/swift3/test/unit/test_obj.py @@ -24,6 +24,7 @@ from swift.common.swob import Request from swift3.test.unit import Swift3TestCase from swift3.test.unit.test_s3_acl import s3acl from swift3.subresource import ACL, User, encode_acl, Owner, Grant +from swift3.etree import fromstring class TestSwift3Obj(Swift3TestCase): @@ -32,17 +33,23 @@ class TestSwift3Obj(Swift3TestCase): super(TestSwift3Obj, self).setUp() self.object_body = 'hello' - etag = hashlib.md5(self.object_body).hexdigest() + self.etag = hashlib.md5(self.object_body).hexdigest() + self.last_modified = 'Fri, 01 Apr 2014 12:00:00 GMT' self.response_headers = {'Content-Type': 'text/html', 'Content-Length': len(self.object_body), 'x-object-meta-test': 'swift', - 'etag': etag, - 'last-modified': '2011-01-05T02:19:14.275290'} + 'etag': self.etag, + 'last-modified': self.last_modified} self.swift.register('GET', '/v1/AUTH_test/bucket/object', swob.HTTPOk, self.response_headers, self.object_body) + self.swift.register('PUT', '/v1/AUTH_test/bucket/object', + swob.HTTPCreated, + {'etag': self.etag, + 'last-modified': self.last_modified}, + None) def _test_object_GETorHEAD(self, method): req = Request.blank('/bucket/object', @@ -332,15 +339,10 @@ class TestSwift3Obj(Swift3TestCase): self.assertEquals(headers['etag'], etag) def test_object_PUT_headers(self): - etag = '7dfa07a8e59ddbcd1dc84d4c4f82aea1' - content_md5 = etag.decode('hex').encode('base64').strip() + content_md5 = self.etag.decode('hex').encode('base64').strip() self.swift.register('HEAD', '/v1/AUTH_test/some/source', swob.HTTPOk, {}, None) - self.swift.register('PUT', '/v1/AUTH_test/bucket/object', - swob.HTTPCreated, - {'etag': etag}, - None) req = Request.blank( '/bucket/object', environ={'REQUEST_METHOD': 'PUT'}, @@ -352,17 +354,19 @@ class TestSwift3Obj(Swift3TestCase): req.date = datetime.now() req.content_type = 'text/plain' status, headers, body = self.call_swift3(req) - # Check that swift3 returns an etag header. - self.assertEquals(headers['etag'], '"%s"' % etag) + # Check that swift3 dones not return an etag header, + # sepcified copy source. + self.assertTrue(headers.get('etag') is None) + self.assertEquals(headers['last-modified'], self.last_modified) _, _, headers = self.swift.calls_with_headers[-1] # Check that swift3 converts a Content-MD5 header into an etag. - self.assertEquals(headers['ETag'], etag) + self.assertEquals(headers['ETag'], self.etag) self.assertEquals(headers['X-Object-Meta-Something'], 'oh hai') self.assertEquals(headers['X-Copy-From'], '/some/source') self.assertEquals(headers['Content-Length'], '0') - def _test_object_PUT_copy_headers(self, head_resp, put_header): + def _test_object_PUT_copy(self, head_resp, put_header={}): account = 'test:tester' grants = [Grant(User(account), 'FULL_CONTROL')] head_headers = \ @@ -370,8 +374,6 @@ class TestSwift3Obj(Swift3TestCase): ACL(Owner(account, account), grants)) self.swift.register('HEAD', '/v1/AUTH_test/some/source', head_resp, head_headers, None) - self.swift.register('PUT', '/v1/AUTH_test/bucket/object', - swob.HTTPCreated, {}, None) put_headers = {'Authorization': 'AWS test:tester:hmac', 'X-Amz-Copy-Source': '/some/source'} @@ -385,6 +387,22 @@ class TestSwift3Obj(Swift3TestCase): req.content_type = 'text/plain' return self.call_swift3(req) + @s3acl + def test_object_PUT_copy(self): + iso_format = '2014-04-01T12:00:00.000Z' + status, headers, body = \ + self._test_object_PUT_copy(swob.HTTPOk) + self.assertEquals(status.split()[0], '200') + self.assertEquals(headers['Content-Type'], 'application/xml') + self.assertTrue(headers.get('etag') is None) + elem = fromstring(body, 'CopyObjectResult') + self.assertEquals(elem.find('LastModified').text, iso_format) + self.assertEquals(elem.find('ETag').text, '"%s"' % self.etag) + + _, _, headers = self.swift.calls_with_headers[-1] + self.assertEquals(headers['X-Copy-From'], '/some/source') + self.assertEquals(headers['Content-Length'], '0') + @s3acl def test_object_PUT_copy_headers_error(self): etag = '7dfa07a8e59ddbcd1dc84d4c4f82aea1' @@ -392,27 +410,27 @@ class TestSwift3Obj(Swift3TestCase): header = {'X-Amz-Copy-Source-If-Match': etag} status, header, body = \ - self._test_object_PUT_copy_headers(swob.HTTPPreconditionFailed, - header) + self._test_object_PUT_copy(swob.HTTPPreconditionFailed, + header) self.assertEquals(self._get_error_code(body), 'PreconditionFailed') header = {'X-Amz-Copy-Source-If-None-Match': etag} status, header, body = \ - self._test_object_PUT_copy_headers(swob.HTTPNotModified, - header) + self._test_object_PUT_copy(swob.HTTPNotModified, + header) self.assertEquals(self._get_error_code(body), 'PreconditionFailed') header = {'X-Amz-Copy-Source-If-Modified-Since': last_modified_since} status, header, body = \ - self._test_object_PUT_copy_headers(swob.HTTPNotModified, - header) + self._test_object_PUT_copy(swob.HTTPNotModified, + header) self.assertEquals(self._get_error_code(body), 'PreconditionFailed') header = \ {'X-Amz-Copy-Source-If-Unmodified-Since': last_modified_since} status, header, body = \ - self._test_object_PUT_copy_headers(swob.HTTPPreconditionFailed, - header) + self._test_object_PUT_copy(swob.HTTPPreconditionFailed, + header) self.assertEquals(self._get_error_code(body), 'PreconditionFailed') def test_object_PUT_copy_headers_with_match(self): @@ -422,7 +440,7 @@ class TestSwift3Obj(Swift3TestCase): header = {'X-Amz-Copy-Source-If-Match': etag, 'X-Amz-Copy-Source-If-Modified-Since': last_modified_since} status, header, body = \ - self._test_object_PUT_copy_headers(swob.HTTPOk, header) + self._test_object_PUT_copy(swob.HTTPOk, header) self.assertEquals(status.split()[0], '200') self.assertEquals(len(self.swift.calls_with_headers), 2) _, _, headers = self.swift.calls_with_headers[-1] @@ -440,7 +458,7 @@ class TestSwift3Obj(Swift3TestCase): header = {'X-Amz-Copy-Source-If-Match': etag, 'X-Amz-Copy-Source-If-Modified-Since': last_modified_since} status, header, body = \ - self._test_object_PUT_copy_headers(swob.HTTPOk, header) + self._test_object_PUT_copy(swob.HTTPOk, header) self.assertEquals(status.split()[0], '200') self.assertEquals(len(self.swift.calls_with_headers), 3) @@ -463,7 +481,7 @@ class TestSwift3Obj(Swift3TestCase): header = {'X-Amz-Copy-Source-If-None-Match': etag, 'X-Amz-Copy-Source-If-Unmodified-Since': last_modified_since} status, header, body = \ - self._test_object_PUT_copy_headers(swob.HTTPOk, header) + self._test_object_PUT_copy(swob.HTTPOk, header) self.assertEquals(status.split()[0], '200') self.assertEquals(len(self.swift.calls_with_headers), 2) @@ -482,7 +500,7 @@ class TestSwift3Obj(Swift3TestCase): header = {'X-Amz-Copy-Source-If-None-Match': etag, 'X-Amz-Copy-Source-If-Unmodified-Since': last_modified_since} status, header, body = \ - self._test_object_PUT_copy_headers(swob.HTTPOk, header) + self._test_object_PUT_copy(swob.HTTPOk, header) self.assertEquals(status.split()[0], '200') # After the check of the copy source in the case of s3acl is valid, # Swift3 check the bucket write permissions of the destination.