def _test_request_timestamp_sigv4(self, date_header): # signature v4 here environ = { 'REQUEST_METHOD': 'GET'} if 'X-Amz-Date' in date_header: included_header = 'x-amz-date' elif 'Date' in date_header: included_header = 'date' else: self.fail('Invalid date header specified as test') headers = { 'Authorization': 'AWS4-HMAC-SHA256 ' 'Credential=test/%s/US/s3/aws4_request, ' 'SignedHeaders=%s,' 'Signature=X' % ( self.get_v4_amz_date_header().split('T', 1)[0], ';'.join(sorted(['host', included_header]))), 'X-Amz-Content-SHA256': '0123456789'} headers.update(date_header) req = Request.blank('/', environ=environ, headers=headers) sigv4_req = SigV4Request(req.environ) if 'X-Amz-Date' in date_header: timestamp = mktime( date_header['X-Amz-Date'], SIGV4_X_AMZ_DATE_FORMAT) elif 'Date' in date_header: timestamp = mktime(date_header['Date']) self.assertEqual(timestamp, int(sigv4_req.timestamp))
def _test_request_timestamp_sigv4(self, date_header): # signature v4 here environ = { 'REQUEST_METHOD': 'GET'} if 'X-Amz-Date' in date_header: included_header = 'x-amz-date' elif 'Date' in date_header: included_header = 'date' else: self.fail('Invalid date header specified as test') headers = { 'Authorization': 'AWS4-HMAC-SHA256 ' 'Credential=test/%s/us-east-1/s3/aws4_request, ' 'SignedHeaders=%s,' 'Signature=X' % ( self.get_v4_amz_date_header().split('T', 1)[0], ';'.join(sorted(['host', included_header]))), 'X-Amz-Content-SHA256': '0123456789'} headers.update(date_header) req = Request.blank('/', environ=environ, headers=headers) sigv4_req = SigV4Request(req.environ) if 'X-Amz-Date' in date_header: timestamp = mktime( date_header['X-Amz-Date'], SIGV4_X_AMZ_DATE_FORMAT) elif 'Date' in date_header: timestamp = mktime(date_header['Date']) self.assertEqual(timestamp, int(sigv4_req.timestamp))
def _test_request_timestamp_sigv2(self, date_header): # signature v4 here environ = {'REQUEST_METHOD': 'GET'} headers = {'Authorization': 'AWS test:tester:hmac'} headers.update(date_header) req = Request.blank('/', environ=environ, headers=headers) sigv2_req = S3Request(req.environ) if 'X-Amz-Date' in date_header: timestamp = mktime(req.headers.get('X-Amz-Date')) elif 'Date' in date_header: timestamp = mktime(req.headers.get('Date')) else: self.fail('Invalid date header specified as test') self.assertEqual(timestamp, int(sigv2_req.timestamp))
def _test_request_timestamp_sigv2(self, date_header): # signature v4 here environ = { 'REQUEST_METHOD': 'GET'} headers = {'Authorization': 'AWS test:tester:hmac'} headers.update(date_header) req = Request.blank('/', environ=environ, headers=headers) sigv2_req = S3Request(req.environ) if 'X-Amz-Date' in date_header: timestamp = mktime(req.headers.get('X-Amz-Date')) elif 'Date' in date_header: timestamp = mktime(req.headers.get('Date')) else: self.fail('Invalid date header specified as test') self.assertEqual(timestamp, int(sigv2_req.timestamp))
def test_mktime(self): date_headers = [ 'Thu, 01 Jan 1970 00:00:00 -0000', 'Thu, 01 Jan 1970 00:00:00 GMT', 'Thu, 01 Jan 1970 00:00:00 UTC', 'Thu, 01 Jan 1970 08:00:00 +0800', 'Wed, 31 Dec 1969 16:00:00 -0800', 'Wed, 31 Dec 1969 16:00:00 PST', ] for header in date_headers: ts = utils.mktime(header) self.assertEqual(0, ts, 'Got %r for header %s' % (ts, header)) # Last-Modified response style self.assertEqual(0, utils.mktime('1970-01-01T00:00:00')) # X-Amz-Date style self.assertEqual(0, utils.mktime('19700101T000000Z', s3request.SIGV4_X_AMZ_DATE_FORMAT))
def test_object_PUT_copy_self_metadata_replace(self): date_header = self.get_date_header() timestamp = mktime(date_header) last_modified = S3Timestamp(timestamp).s3xmlformat header = {'x-amz-metadata-directive': 'REPLACE', 'Date': date_header} status, headers, body = self._test_object_PUT_copy_self( swob.HTTPOk, header, timestamp=timestamp) self.assertEqual(status.split()[0], '200') self.assertEqual(headers['Content-Type'], 'application/xml') self.assertTrue(headers.get('etag') is None) elem = fromstring(body, 'CopyObjectResult') self.assertEqual(elem.find('LastModified').text, last_modified) self.assertEqual(elem.find('ETag').text, '"%s"' % self.etag) _, _, headers = self.swift.calls_with_headers[-1] self.assertEqual(headers['X-Copy-From'], '/bucket/object') self.assertEqual(headers['Content-Length'], '0')
def test_object_PUT_copy_self_metadata_replace(self): date_header = self.get_date_header() timestamp = mktime(date_header) last_modified = S3Timestamp(timestamp).s3xmlformat header = {'x-amz-metadata-directive': 'REPLACE', 'Date': date_header} status, headers, body = self._test_object_PUT_copy_self( swob.HTTPOk, header, timestamp=timestamp) self.assertEqual(status.split()[0], '200') self.assertEqual(headers['Content-Type'], 'application/xml') self.assertTrue(headers.get('etag') is None) elem = fromstring(body, 'CopyObjectResult') self.assertEqual(elem.find('LastModified').text, last_modified) self.assertEqual(elem.find('ETag').text, '"%s"' % self.etag) _, _, headers = self.swift.calls_with_headers[-1] self.assertEqual(headers['X-Copy-From'], '/bucket/object') self.assertEqual(headers['Content-Length'], '0')
def do_test(src_path=None): date_header = self.get_date_header() timestamp = mktime(date_header) last_modified = S3Timestamp(timestamp).s3xmlformat status, headers, body = self._test_object_PUT_copy( swob.HTTPOk, put_header={'Date': date_header}, timestamp=timestamp, src_path=src_path) self.assertEqual(status.split()[0], '200') self.assertEqual(headers['Content-Type'], 'application/xml') self.assertTrue(headers.get('etag') is None) self.assertTrue(headers.get('x-amz-meta-something') is None) elem = fromstring(body, 'CopyObjectResult') self.assertEqual(elem.find('LastModified').text, last_modified) self.assertEqual(elem.find('ETag').text, '"%s"' % self.etag) _, _, headers = self.swift.calls_with_headers[-1] self.assertEqual(headers['X-Copy-From'], '/some/source') self.assertEqual(headers['Content-Length'], '0')
def do_test(src_path=None): date_header = self.get_date_header() timestamp = mktime(date_header) last_modified = S3Timestamp(timestamp).s3xmlformat status, headers, body = self._test_object_PUT_copy( swob.HTTPOk, put_header={'Date': date_header}, timestamp=timestamp, src_path=src_path) self.assertEqual(status.split()[0], '200') self.assertEqual(headers['Content-Type'], 'application/xml') self.assertTrue(headers.get('etag') is None) self.assertTrue(headers.get('x-amz-meta-something') is None) elem = fromstring(body, 'CopyObjectResult') self.assertEqual(elem.find('LastModified').text, last_modified) self.assertEqual(elem.find('ETag').text, '"%s"' % self.etag) _, _, headers = self.swift.calls_with_headers[-1] self.assertEqual(headers['X-Copy-From'], '/some/source') self.assertEqual(headers['Content-Length'], '0')
def test_object_multi_upload(self): bucket = 'bucket' keys = ['obj1', 'obj2', 'obj3'] headers = [None, {'Content-MD5': base64.b64encode('a' * 16).strip()}, {'Etag': 'nonsense'}] uploads = [] results_generator = self._initiate_multi_uploads_result_generator( bucket, keys, headers=headers) # Initiate Multipart Upload for expected_key, (status, headers, body) in \ izip(keys, results_generator): self.assertEqual(status, 200) self.assertCommonResponseHeaders(headers) self.assertTrue('content-type' in headers) self.assertEqual(headers['content-type'], 'application/xml') self.assertTrue('content-length' in headers) self.assertEqual(headers['content-length'], str(len(body))) elem = fromstring(body, 'InitiateMultipartUploadResult') self.assertEqual(elem.find('Bucket').text, bucket) key = elem.find('Key').text self.assertEqual(expected_key, key) upload_id = elem.find('UploadId').text self.assertTrue(upload_id is not None) self.assertTrue((key, upload_id) not in uploads) uploads.append((key, upload_id)) self.assertEqual(len(uploads), len(keys)) # sanity # List Multipart Uploads query = 'uploads' status, headers, body = \ self.conn.make_request('GET', bucket, query=query) self.assertEqual(status, 200) self.assertCommonResponseHeaders(headers) self.assertTrue('content-type' in headers) self.assertEqual(headers['content-type'], 'application/xml') self.assertTrue('content-length' in headers) self.assertEqual(headers['content-length'], str(len(body))) elem = fromstring(body, 'ListMultipartUploadsResult') self.assertEqual(elem.find('Bucket').text, bucket) self.assertIsNone(elem.find('KeyMarker').text) self.assertEqual(elem.find('NextKeyMarker').text, uploads[-1][0]) self.assertIsNone(elem.find('UploadIdMarker').text) self.assertEqual(elem.find('NextUploadIdMarker').text, uploads[-1][1]) self.assertEqual(elem.find('MaxUploads').text, '1000') self.assertTrue(elem.find('EncodingType') is None) self.assertEqual(elem.find('IsTruncated').text, 'false') self.assertEqual(len(elem.findall('Upload')), 3) for (expected_key, expected_upload_id), u in \ izip(uploads, elem.findall('Upload')): key = u.find('Key').text upload_id = u.find('UploadId').text self.assertEqual(expected_key, key) self.assertEqual(expected_upload_id, upload_id) self.assertEqual(u.find('Initiator/ID').text, self.conn.user_id) self.assertEqual(u.find('Initiator/DisplayName').text, self.conn.user_id) self.assertEqual(u.find('Owner/ID').text, self.conn.user_id) self.assertEqual(u.find('Owner/DisplayName').text, self.conn.user_id) self.assertEqual(u.find('StorageClass').text, 'STANDARD') self.assertTrue(u.find('Initiated').text is not None) # Upload Part key, upload_id = uploads[0] content = 'a' * self.min_segment_size etag = md5(content).hexdigest() status, headers, body = \ self._upload_part(bucket, key, upload_id, content) self.assertEqual(status, 200) self.assertCommonResponseHeaders(headers, etag) self.assertTrue('content-type' in headers) self.assertEqual(headers['content-type'], 'text/html; charset=UTF-8') self.assertTrue('content-length' in headers) self.assertEqual(headers['content-length'], '0') expected_parts_list = [(headers['etag'], mktime(headers['date']))] # Upload Part Copy key, upload_id = uploads[1] src_bucket = 'bucket2' src_obj = 'obj3' src_content = 'b' * self.min_segment_size etag = md5(src_content).hexdigest() # prepare src obj self.conn.make_request('PUT', src_bucket) self.conn.make_request('PUT', src_bucket, src_obj, body=src_content) _, headers, _ = self.conn.make_request('HEAD', src_bucket, src_obj) self.assertCommonResponseHeaders(headers) status, headers, body, resp_etag = \ self._upload_part_copy(src_bucket, src_obj, bucket, key, upload_id) self.assertEqual(status, 200) self.assertCommonResponseHeaders(headers) self.assertTrue('content-type' in headers) self.assertEqual(headers['content-type'], 'application/xml') self.assertTrue('content-length' in headers) self.assertEqual(headers['content-length'], str(len(body))) self.assertTrue('etag' not in headers) elem = fromstring(body, 'CopyPartResult') last_modified = elem.find('LastModified').text self.assertTrue(last_modified is not None) self.assertEqual(resp_etag, etag) # Check last-modified timestamp key, upload_id = uploads[1] query = 'uploadId=%s' % upload_id status, headers, body = \ self.conn.make_request('GET', bucket, key, query=query) self.assertEqual(200, status) elem = fromstring(body, 'ListPartsResult') # FIXME: COPY result drops milli/microseconds but GET doesn't last_modified_gets = [p.find('LastModified').text for p in elem.iterfind('Part')] self.assertEqual( last_modified_gets[0].rsplit('.', 1)[0], last_modified.rsplit('.', 1)[0], '%r != %r' % (last_modified_gets[0], last_modified)) # There should be *exactly* two parts in the result self.assertEqual(1, len(last_modified_gets)) # List Parts key, upload_id = uploads[0] query = 'uploadId=%s' % upload_id status, headers, body = \ self.conn.make_request('GET', bucket, key, query=query) self.assertEqual(status, 200) self.assertCommonResponseHeaders(headers) self.assertTrue('content-type' in headers) self.assertEqual(headers['content-type'], 'application/xml') self.assertTrue('content-length' in headers) self.assertEqual(headers['content-length'], str(len(body))) elem = fromstring(body, 'ListPartsResult') self.assertEqual(elem.find('Bucket').text, bucket) self.assertEqual(elem.find('Key').text, key) self.assertEqual(elem.find('UploadId').text, upload_id) self.assertEqual(elem.find('Initiator/ID').text, self.conn.user_id) self.assertEqual(elem.find('Initiator/DisplayName').text, self.conn.user_id) self.assertEqual(elem.find('Owner/ID').text, self.conn.user_id) self.assertEqual(elem.find('Owner/DisplayName').text, self.conn.user_id) self.assertEqual(elem.find('StorageClass').text, 'STANDARD') self.assertEqual(elem.find('PartNumberMarker').text, '0') self.assertEqual(elem.find('NextPartNumberMarker').text, '1') self.assertEqual(elem.find('MaxParts').text, '1000') self.assertEqual(elem.find('IsTruncated').text, 'false') self.assertEqual(len(elem.findall('Part')), 1) # etags will be used to generate xml for Complete Multipart Upload etags = [] for (expected_etag, expected_date), p in \ izip(expected_parts_list, elem.findall('Part')): last_modified = p.find('LastModified').text self.assertTrue(last_modified is not None) # TODO: sanity check # (kota_) How do we check the sanity? # the last-modified header drops milli-seconds info # by the constraint of the format. # For now, we can do either the format check or round check # last_modified_from_xml = mktime(last_modified) # self.assertEqual(expected_date, # last_modified_from_xml) self.assertEqual(expected_etag, p.find('ETag').text) self.assertEqual(self.min_segment_size, int(p.find('Size').text)) etags.append(p.find('ETag').text) # Abort Multipart Uploads # note that uploads[1] has part data while uploads[2] does not for key, upload_id in uploads[1:]: query = 'uploadId=%s' % upload_id status, headers, body = \ self.conn.make_request('DELETE', bucket, key, query=query) self.assertEqual(status, 204) self.assertCommonResponseHeaders(headers) self.assertTrue('content-type' in headers) self.assertEqual(headers['content-type'], 'text/html; charset=UTF-8') self.assertTrue('content-length' in headers) self.assertEqual(headers['content-length'], '0') # Complete Multipart Upload key, upload_id = uploads[0] xml = self._gen_comp_xml(etags) status, headers, body = \ self._complete_multi_upload(bucket, key, upload_id, xml) self.assertEqual(status, 200) self.assertCommonResponseHeaders(headers) self.assertTrue('content-type' in headers) self.assertEqual(headers['content-type'], 'application/xml') self.assertTrue('content-length' in headers) self.assertEqual(headers['content-length'], str(len(body))) elem = fromstring(body, 'CompleteMultipartUploadResult') # TODO: use tf.config value self.assertEqual( 'http://%s:%s/bucket/obj1' % (self.conn.host, self.conn.port), elem.find('Location').text) self.assertEqual(elem.find('Bucket').text, bucket) self.assertEqual(elem.find('Key').text, key) concatted_etags = ''.join(etag.strip('"') for etag in etags) exp_etag = '"%s-%s"' % ( md5(concatted_etags.decode('hex')).hexdigest(), len(etags)) etag = elem.find('ETag').text self.assertEqual(etag, exp_etag) exp_size = self.min_segment_size * len(etags) swift_etag = '"%s"' % md5(concatted_etags).hexdigest() # TODO: GET via swift api, check against swift_etag # Check object def check_obj(req_headers, exp_status): status, headers, body = \ self.conn.make_request('HEAD', bucket, key, req_headers) self.assertEqual(status, exp_status) self.assertCommonResponseHeaders(headers) self.assertIn('content-length', headers) if exp_status == 412: self.assertNotIn('etag', headers) self.assertEqual(headers['content-length'], '0') else: self.assertIn('etag', headers) self.assertEqual(headers['etag'], exp_etag) if exp_status == 304: self.assertEqual(headers['content-length'], '0') else: self.assertEqual(headers['content-length'], str(exp_size)) check_obj({}, 200) # Sanity check conditionals check_obj({'If-Match': 'some other thing'}, 412) check_obj({'If-None-Match': 'some other thing'}, 200) # More interesting conditional cases check_obj({'If-Match': exp_etag}, 200) check_obj({'If-Match': swift_etag}, 412) check_obj({'If-None-Match': swift_etag}, 200) check_obj({'If-None-Match': exp_etag}, 304) # Check listings status, headers, body = self.conn.make_request('GET', bucket) self.assertEqual(status, 200) elem = fromstring(body, 'ListBucketResult') resp_objects = elem.findall('./Contents') self.assertEqual(len(list(resp_objects)), 1) for o in resp_objects: self.assertEqual(o.find('Key').text, key) self.assertIsNotNone(o.find('LastModified').text) self.assertRegexpMatches( o.find('LastModified').text, r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$') self.assertEqual(o.find('ETag').text, exp_etag) self.assertEqual(o.find('Size').text, str(exp_size)) self.assertIsNotNone(o.find('StorageClass').text is not None) self.assertEqual(o.find('Owner/ID').text, self.conn.user_id) self.assertEqual(o.find('Owner/DisplayName').text, self.conn.user_id)
def test_object_multi_upload(self): bucket = 'bucket' keys = ['obj1', 'obj2', 'obj3'] headers = [None, {'Content-MD5': base64.b64encode('a' * 16).strip()}, {'Etag': 'nonsense'}] uploads = [] results_generator = self._initiate_multi_uploads_result_generator( bucket, keys, headers=headers) # Initiate Multipart Upload for expected_key, (status, headers, body) in \ izip(keys, results_generator): self.assertEqual(status, 200) self.assertCommonResponseHeaders(headers) self.assertTrue('content-type' in headers) self.assertEqual(headers['content-type'], 'application/xml') self.assertTrue('content-length' in headers) self.assertEqual(headers['content-length'], str(len(body))) elem = fromstring(body, 'InitiateMultipartUploadResult') self.assertEqual(elem.find('Bucket').text, bucket) key = elem.find('Key').text self.assertEqual(expected_key, key) upload_id = elem.find('UploadId').text self.assertTrue(upload_id is not None) self.assertTrue((key, upload_id) not in uploads) uploads.append((key, upload_id)) self.assertEqual(len(uploads), len(keys)) # sanity # List Multipart Uploads query = 'uploads' status, headers, body = \ self.conn.make_request('GET', bucket, query=query) self.assertEqual(status, 200) self.assertCommonResponseHeaders(headers) self.assertTrue('content-type' in headers) self.assertEqual(headers['content-type'], 'application/xml') self.assertTrue('content-length' in headers) self.assertEqual(headers['content-length'], str(len(body))) elem = fromstring(body, 'ListMultipartUploadsResult') self.assertEqual(elem.find('Bucket').text, bucket) self.assertIsNone(elem.find('KeyMarker').text) self.assertEqual(elem.find('NextKeyMarker').text, uploads[-1][0]) self.assertIsNone(elem.find('UploadIdMarker').text) self.assertEqual(elem.find('NextUploadIdMarker').text, uploads[-1][1]) self.assertEqual(elem.find('MaxUploads').text, '1000') self.assertTrue(elem.find('EncodingType') is None) self.assertEqual(elem.find('IsTruncated').text, 'false') self.assertEqual(len(elem.findall('Upload')), 3) for (expected_key, expected_upload_id), u in \ izip(uploads, elem.findall('Upload')): key = u.find('Key').text upload_id = u.find('UploadId').text self.assertEqual(expected_key, key) self.assertEqual(expected_upload_id, upload_id) self.assertEqual(u.find('Initiator/ID').text, self.conn.user_id) self.assertEqual(u.find('Initiator/DisplayName').text, self.conn.user_id) self.assertEqual(u.find('Owner/ID').text, self.conn.user_id) self.assertEqual(u.find('Owner/DisplayName').text, self.conn.user_id) self.assertEqual(u.find('StorageClass').text, 'STANDARD') self.assertTrue(u.find('Initiated').text is not None) # Upload Part key, upload_id = uploads[0] content = 'a' * self.min_segment_size etag = md5(content).hexdigest() status, headers, body = \ self._upload_part(bucket, key, upload_id, content) self.assertEqual(status, 200) self.assertCommonResponseHeaders(headers, etag) self.assertTrue('content-type' in headers) self.assertEqual(headers['content-type'], 'text/html; charset=UTF-8') self.assertTrue('content-length' in headers) self.assertEqual(headers['content-length'], '0') expected_parts_list = [(headers['etag'], mktime(headers['date']))] # Upload Part Copy key, upload_id = uploads[1] src_bucket = 'bucket2' src_obj = 'obj3' src_content = 'b' * self.min_segment_size etag = md5(src_content).hexdigest() # prepare src obj self.conn.make_request('PUT', src_bucket) self.conn.make_request('PUT', src_bucket, src_obj, body=src_content) _, headers, _ = self.conn.make_request('HEAD', src_bucket, src_obj) self.assertCommonResponseHeaders(headers) status, headers, body, resp_etag = \ self._upload_part_copy(src_bucket, src_obj, bucket, key, upload_id) self.assertEqual(status, 200) self.assertCommonResponseHeaders(headers) self.assertTrue('content-type' in headers) self.assertEqual(headers['content-type'], 'application/xml') self.assertTrue('content-length' in headers) self.assertEqual(headers['content-length'], str(len(body))) self.assertTrue('etag' not in headers) elem = fromstring(body, 'CopyPartResult') last_modified = elem.find('LastModified').text self.assertTrue(last_modified is not None) self.assertEqual(resp_etag, etag) # Check last-modified timestamp key, upload_id = uploads[1] query = 'uploadId=%s' % upload_id status, headers, body = \ self.conn.make_request('GET', bucket, key, query=query) self.assertEqual(200, status) elem = fromstring(body, 'ListPartsResult') # FIXME: COPY result drops milli/microseconds but GET doesn't last_modified_gets = [p.find('LastModified').text for p in elem.iterfind('Part')] self.assertEqual( last_modified_gets[0].rsplit('.', 1)[0], last_modified.rsplit('.', 1)[0], '%r != %r' % (last_modified_gets[0], last_modified)) # There should be *exactly* two parts in the result self.assertEqual(1, len(last_modified_gets)) # List Parts key, upload_id = uploads[0] query = 'uploadId=%s' % upload_id status, headers, body = \ self.conn.make_request('GET', bucket, key, query=query) self.assertEqual(status, 200) self.assertCommonResponseHeaders(headers) self.assertTrue('content-type' in headers) self.assertEqual(headers['content-type'], 'application/xml') self.assertTrue('content-length' in headers) self.assertEqual(headers['content-length'], str(len(body))) elem = fromstring(body, 'ListPartsResult') self.assertEqual(elem.find('Bucket').text, bucket) self.assertEqual(elem.find('Key').text, key) self.assertEqual(elem.find('UploadId').text, upload_id) self.assertEqual(elem.find('Initiator/ID').text, self.conn.user_id) self.assertEqual(elem.find('Initiator/DisplayName').text, self.conn.user_id) self.assertEqual(elem.find('Owner/ID').text, self.conn.user_id) self.assertEqual(elem.find('Owner/DisplayName').text, self.conn.user_id) self.assertEqual(elem.find('StorageClass').text, 'STANDARD') self.assertEqual(elem.find('PartNumberMarker').text, '0') self.assertEqual(elem.find('NextPartNumberMarker').text, '1') self.assertEqual(elem.find('MaxParts').text, '1000') self.assertEqual(elem.find('IsTruncated').text, 'false') self.assertEqual(len(elem.findall('Part')), 1) # etags will be used to generate xml for Complete Multipart Upload etags = [] for (expected_etag, expected_date), p in \ izip(expected_parts_list, elem.findall('Part')): last_modified = p.find('LastModified').text self.assertTrue(last_modified is not None) # TODO: sanity check # (kota_) How do we check the sanity? # the last-modified header drops milli-seconds info # by the constraint of the format. # For now, we can do either the format check or round check # last_modified_from_xml = mktime(last_modified) # self.assertEqual(expected_date, # last_modified_from_xml) self.assertEqual(expected_etag, p.find('ETag').text) self.assertEqual(self.min_segment_size, int(p.find('Size').text)) etags.append(p.find('ETag').text) # Abort Multipart Uploads # note that uploads[1] has part data while uploads[2] does not for key, upload_id in uploads[1:]: query = 'uploadId=%s' % upload_id status, headers, body = \ self.conn.make_request('DELETE', bucket, key, query=query) self.assertEqual(status, 204) self.assertCommonResponseHeaders(headers) self.assertTrue('content-type' in headers) self.assertEqual(headers['content-type'], 'text/html; charset=UTF-8') self.assertTrue('content-length' in headers) self.assertEqual(headers['content-length'], '0') # Complete Multipart Upload key, upload_id = uploads[0] xml = self._gen_comp_xml(etags) status, headers, body = \ self._complete_multi_upload(bucket, key, upload_id, xml) self.assertEqual(status, 200) self.assertCommonResponseHeaders(headers) self.assertTrue('content-type' in headers) self.assertEqual(headers['content-type'], 'application/xml') self.assertTrue('content-length' in headers) self.assertEqual(headers['content-length'], str(len(body))) elem = fromstring(body, 'CompleteMultipartUploadResult') # TODO: use tf.config value self.assertEqual( 'http://%s:%s/bucket/obj1' % (self.conn.host, self.conn.port), elem.find('Location').text) self.assertEqual(elem.find('Bucket').text, bucket) self.assertEqual(elem.find('Key').text, key) concatted_etags = ''.join(etag.strip('"') for etag in etags) exp_etag = '"%s-%s"' % ( md5(concatted_etags.decode('hex')).hexdigest(), len(etags)) etag = elem.find('ETag').text self.assertEqual(etag, exp_etag) exp_size = self.min_segment_size * len(etags) swift_etag = '"%s"' % md5(concatted_etags).hexdigest() # TODO: GET via swift api, check against swift_etag # Check object def check_obj(req_headers, exp_status): status, headers, body = \ self.conn.make_request('HEAD', bucket, key, req_headers) self.assertEqual(status, exp_status) self.assertCommonResponseHeaders(headers) self.assertIn('content-length', headers) if exp_status == 412: self.assertNotIn('etag', headers) self.assertEqual(headers['content-length'], '0') else: self.assertIn('etag', headers) self.assertEqual(headers['etag'], exp_etag) if exp_status == 304: self.assertEqual(headers['content-length'], '0') else: self.assertEqual(headers['content-length'], str(exp_size)) check_obj({}, 200) # Sanity check conditionals check_obj({'If-Match': 'some other thing'}, 412) check_obj({'If-None-Match': 'some other thing'}, 200) # More interesting conditional cases check_obj({'If-Match': exp_etag}, 200) check_obj({'If-Match': swift_etag}, 412) check_obj({'If-None-Match': swift_etag}, 200) check_obj({'If-None-Match': exp_etag}, 304) # Check listings status, headers, body = self.conn.make_request('GET', bucket) self.assertEqual(status, 200) elem = fromstring(body, 'ListBucketResult') resp_objects = elem.findall('./Contents') self.assertEqual(len(list(resp_objects)), 1) for o in resp_objects: self.assertEqual(o.find('Key').text, key) self.assertIsNotNone(o.find('LastModified').text) self.assertRegexpMatches( o.find('LastModified').text, r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$') self.assertEqual(o.find('ETag').text, exp_etag) self.assertEqual(o.find('Size').text, str(exp_size)) self.assertIsNotNone(o.find('StorageClass').text is not None) self.assertEqual(o.find('Owner/ID').text, self.conn.user_id) self.assertEqual(o.find('Owner/DisplayName').text, self.conn.user_id)