diff options
Diffstat (limited to 'test/functional')
-rw-r--r-- | test/functional/__init__.py | 16 | ||||
-rw-r--r-- | test/functional/s3api/__init__.py | 9 | ||||
-rw-r--r-- | test/functional/s3api/s3_test_client.py | 40 | ||||
-rw-r--r-- | test/functional/s3api/test_acl.py | 6 | ||||
-rw-r--r-- | test/functional/s3api/test_bucket.py | 16 | ||||
-rw-r--r-- | test/functional/s3api/test_multi_delete.py | 2 | ||||
-rw-r--r-- | test/functional/s3api/test_multi_upload.py | 19 | ||||
-rw-r--r-- | test/functional/s3api/test_object.py | 10 | ||||
-rw-r--r-- | test/functional/s3api/test_service.py | 2 | ||||
-rw-r--r-- | test/functional/test_object.py | 9 | ||||
-rw-r--r-- | test/functional/test_object_versioning.py | 106 | ||||
-rw-r--r-- | test/functional/test_slo.py | 31 | ||||
-rwxr-xr-x | test/functional/test_symlink.py | 79 | ||||
-rw-r--r-- | test/functional/test_versioned_writes.py | 11 | ||||
-rw-r--r-- | test/functional/tests.py | 75 |
15 files changed, 292 insertions, 139 deletions
diff --git a/test/functional/__init__.py b/test/functional/__init__.py index 707f2239a..a4f4c5a6e 100644 --- a/test/functional/__init__.py +++ b/test/functional/__init__.py @@ -322,12 +322,16 @@ def _load_encryption(proxy_conf_file, swift_conf_file, **kwargs): pipeline = pipeline.replace( "proxy-logging proxy-server", "keymaster encryption proxy-logging proxy-server") + pipeline = pipeline.replace( + "cache listing_formats", + "cache etag-quoter listing_formats") conf.set(section, 'pipeline', pipeline) root_secret = base64.b64encode(os.urandom(32)) if not six.PY2: root_secret = root_secret.decode('ascii') conf.set('filter:keymaster', 'encryption_root_secret', root_secret) conf.set('filter:versioned_writes', 'allow_object_versioning', 'true') + conf.set('filter:etag-quoter', 'enable_by_default', 'true') except NoSectionError as err: msg = 'Error problem with proxy conf file %s: %s' % \ (proxy_conf_file, err) @@ -512,8 +516,6 @@ def _load_losf_as_default_policy(proxy_conf_file, swift_conf_file, **kwargs): conf_loaders = { 'encryption': _load_encryption, 'ec': _load_ec_as_default_policy, - 'domain_remap_staticweb': _load_domain_remap_staticweb, - 's3api': _load_s3api, 'losf': _load_losf_as_default_policy, } @@ -552,6 +554,11 @@ def in_process_setup(the_object_server=object_server): swift_conf = _in_process_setup_swift_conf(swift_conf_src, _testdir) _info('prepared swift.conf: %s' % swift_conf) + # load s3api and staticweb configs + proxy_conf, swift_conf = _load_s3api(proxy_conf, swift_conf) + proxy_conf, swift_conf = _load_domain_remap_staticweb(proxy_conf, + swift_conf) + # Call the associated method for the value of # 'SWIFT_TEST_IN_PROCESS_CONF_LOADER', if one exists conf_loader_label = os.environ.get( @@ -621,6 +628,7 @@ def in_process_setup(the_object_server=object_server): # Below are values used by the functional test framework, as well as # by the various in-process swift servers 'auth_uri': 'http://127.0.0.1:%d/auth/v1.0/' % prolis.getsockname()[1], + 's3_storage_url': 'http://%s:%d/' % prolis.getsockname(), # Primary functional test account (needs admin access to the # account) 'account': 'test', @@ -902,6 +910,8 @@ def setup_package(): 443 if parsed.scheme == 'https' else 80), 'auth_prefix': parsed.path, }) + config.setdefault('s3_storage_url', + urlunsplit(parsed[:2] + ('', None, None))) elif 'auth_host' in config: scheme = 'http' if config_true_value(config.get('auth_ssl', 'no')): @@ -914,6 +924,8 @@ def setup_package(): auth_prefix += 'v1.0' config['auth_uri'] = swift_test_auth = urlunsplit( (scheme, netloc, auth_prefix, None, None)) + config.setdefault('s3_storage_url', urlunsplit( + (scheme, netloc, '', None, None))) # else, neither auth_uri nor auth_host; swift_test_auth will be unset # and we'll skip everything later diff --git a/test/functional/s3api/__init__.py b/test/functional/s3api/__init__.py index db443db80..7ad2c077b 100644 --- a/test/functional/s3api/__init__.py +++ b/test/functional/s3api/__init__.py @@ -37,7 +37,11 @@ class S3ApiBase(unittest.TestCase): if 's3api' not in tf.cluster_info: raise tf.SkipTest('s3api middleware is not enabled') try: - self.conn = Connection() + self.conn = Connection( + tf.config['s3_access_key'], tf.config['s3_secret_key'], + user_id='%s:%s' % (tf.config['account'], + tf.config['username'])) + self.conn.reset() except Exception: message = '%s got an error during initialize process.\n\n%s' % \ @@ -67,7 +71,8 @@ class S3ApiBaseBoto3(S3ApiBase): if 's3api' not in tf.cluster_info: raise tf.SkipTest('s3api middleware is not enabled') try: - self.conn = get_boto3_conn() + self.conn = get_boto3_conn( + tf.config['s3_access_key'], tf.config['s3_secret_key']) self.endpoint_url = self.conn._endpoint.host self.access_key = self.conn._request_signer._credentials.access_key self.region = self.conn._client_config.region_name diff --git a/test/functional/s3api/s3_test_client.py b/test/functional/s3api/s3_test_client.py index 6eea9dc69..a5d22bce4 100644 --- a/test/functional/s3api/s3_test_client.py +++ b/test/functional/s3api/s3_test_client.py @@ -15,6 +15,7 @@ import logging import os +from six.moves.urllib.parse import urlparse import test.functional as tf import boto3 from botocore.exceptions import ClientError @@ -46,9 +47,9 @@ class Connection(object): """ Connection class used for S3 functional testing. """ - def __init__(self, aws_access_key='test:tester', - aws_secret_key='testing', - user_id='test:tester'): + def __init__(self, aws_access_key, + aws_secret_key, + user_id=None): """ Initialize method. @@ -64,15 +65,16 @@ class Connection(object): """ self.aws_access_key = aws_access_key self.aws_secret_key = aws_secret_key - self.user_id = user_id - # NOTE: auth_host and auth_port can be different from storage location - self.host = tf.config['auth_host'] - self.port = int(tf.config['auth_port']) + self.user_id = user_id or aws_access_key + parsed = urlparse(tf.config['s3_storage_url']) + self.host = parsed.hostname + self.port = parsed.port self.conn = \ - S3Connection(aws_access_key, aws_secret_key, is_secure=False, + S3Connection(aws_access_key, aws_secret_key, + is_secure=(parsed.scheme == 'https'), host=self.host, port=self.port, calling_format=OrdinaryCallingFormat()) - self.conn.auth_region_name = 'us-east-1' + self.conn.auth_region_name = tf.config.get('s3_region', 'us-east-1') def reset(self): """ @@ -140,22 +142,26 @@ class Connection(object): url = self.conn.generate_url(expires_in, method, bucket, obj) if os.environ.get('S3_USE_SIGV4') == "True": # V4 signatures are known-broken in boto, but we can work around it - if url.startswith('https://'): + if url.startswith('https://') and not tf.config[ + 's3_storage_url'].startswith('https://'): url = 'http://' + url[8:] - return url, {'Host': '%(host)s:%(port)d:%(port)d' % { - 'host': self.host, 'port': self.port}} + if self.port is None: + return url, {} + else: + return url, {'Host': '%(host)s:%(port)d:%(port)d' % { + 'host': self.host, 'port': self.port}} return url, {} -def get_boto3_conn(aws_access_key='test:tester', aws_secret_key='testing'): - host = tf.config['auth_host'] - port = int(tf.config['auth_port']) +def get_boto3_conn(aws_access_key, aws_secret_key): + endpoint_url = tf.config['s3_storage_url'] config = boto3.session.Config(s3={'addressing_style': 'path'}) return boto3.client( 's3', aws_access_key_id=aws_access_key, aws_secret_access_key=aws_secret_key, - config=config, region_name='us-east-1', use_ssl=False, - endpoint_url='http://{}:{}'.format(host, port)) + config=config, region_name=tf.config.get('s3_region', 'us-east-1'), + use_ssl=endpoint_url.startswith('https:'), + endpoint_url=endpoint_url) def tear_down_s3(conn): diff --git a/test/functional/s3api/test_acl.py b/test/functional/s3api/test_acl.py index 610efe0a9..7a3d4f98d 100644 --- a/test/functional/s3api/test_acl.py +++ b/test/functional/s3api/test_acl.py @@ -93,7 +93,7 @@ class TestS3Acl(S3ApiBase): def test_put_bucket_acl_error(self): req_headers = {'x-amz-acl': 'public-read'} - aws_error_conn = Connection(aws_secret_key='invalid') + aws_error_conn = Connection(tf.config['s3_access_key'], 'invalid') status, headers, body = \ aws_error_conn.make_request('PUT', self.bucket, headers=req_headers, query='acl') @@ -110,7 +110,7 @@ class TestS3Acl(S3ApiBase): self.assertEqual(get_error_code(body), 'AccessDenied') def test_get_bucket_acl_error(self): - aws_error_conn = Connection(aws_secret_key='invalid') + aws_error_conn = Connection(tf.config['s3_access_key'], 'invalid') status, headers, body = \ aws_error_conn.make_request('GET', self.bucket, query='acl') self.assertEqual(get_error_code(body), 'SignatureDoesNotMatch') @@ -126,7 +126,7 @@ class TestS3Acl(S3ApiBase): def test_get_object_acl_error(self): self.conn.make_request('PUT', self.bucket, self.obj) - aws_error_conn = Connection(aws_secret_key='invalid') + aws_error_conn = Connection(tf.config['s3_access_key'], 'invalid') status, headers, body = \ aws_error_conn.make_request('GET', self.bucket, self.obj, query='acl') diff --git a/test/functional/s3api/test_bucket.py b/test/functional/s3api/test_bucket.py index 2197ce823..1e427434d 100644 --- a/test/functional/s3api/test_bucket.py +++ b/test/functional/s3api/test_bucket.py @@ -42,11 +42,15 @@ class TestS3ApiBucket(S3ApiBaseBoto3): self.assertIn('ETag', obj) self.assertIn('Size', obj) self.assertEqual(obj['StorageClass'], 'STANDARD') - if expect_owner: + if not expect_owner: + self.assertNotIn('Owner', obj) + elif tf.cluster_info['s3api'].get('s3_acl'): self.assertEqual(obj['Owner']['ID'], self.access_key) self.assertEqual(obj['Owner']['DisplayName'], self.access_key) else: - self.assertNotIn('Owner', obj) + self.assertIn('Owner', obj) + self.assertIn('ID', obj['Owner']) + self.assertIn('DisplayName', obj['Owner']) def test_bucket(self): bucket = 'bucket' @@ -128,7 +132,7 @@ class TestS3ApiBucket(S3ApiBaseBoto3): self.assertEqual( ctx.exception.response['Error']['Code'], 'InvalidBucketName') - auth_error_conn = get_boto3_conn(aws_secret_key='invalid') + auth_error_conn = get_boto3_conn(tf.config['s3_access_key'], 'invalid') with self.assertRaises(botocore.exceptions.ClientError) as ctx: auth_error_conn.create_bucket(Bucket='bucket') self.assertEqual( @@ -201,7 +205,7 @@ class TestS3ApiBucket(S3ApiBaseBoto3): self.assertEqual( ctx.exception.response['Error']['Code'], 'InvalidBucketName') - auth_error_conn = get_boto3_conn(aws_secret_key='invalid') + auth_error_conn = get_boto3_conn(tf.config['s3_access_key'], 'invalid') with self.assertRaises(botocore.exceptions.ClientError) as ctx: auth_error_conn.list_objects(Bucket='bucket') self.assertEqual( @@ -388,7 +392,7 @@ class TestS3ApiBucket(S3ApiBaseBoto3): ctx.exception.response[ 'ResponseMetadata']['HTTPHeaders']['content-length'], '0') - auth_error_conn = get_boto3_conn(aws_secret_key='invalid') + auth_error_conn = get_boto3_conn(tf.config['s3_access_key'], 'invalid') with self.assertRaises(botocore.exceptions.ClientError) as ctx: auth_error_conn.head_bucket(Bucket='bucket') self.assertEqual( @@ -419,7 +423,7 @@ class TestS3ApiBucket(S3ApiBaseBoto3): self.assertEqual( ctx.exception.response['Error']['Code'], 'InvalidBucketName') - auth_error_conn = get_boto3_conn(aws_secret_key='invalid') + auth_error_conn = get_boto3_conn(tf.config['s3_access_key'], 'invalid') with self.assertRaises(botocore.exceptions.ClientError) as ctx: auth_error_conn.delete_bucket(Bucket='bucket') self.assertEqual( diff --git a/test/functional/s3api/test_multi_delete.py b/test/functional/s3api/test_multi_delete.py index 31e18bb5f..1489d5477 100644 --- a/test/functional/s3api/test_multi_delete.py +++ b/test/functional/s3api/test_multi_delete.py @@ -134,7 +134,7 @@ class TestS3ApiMultiDelete(S3ApiBase): content_md5 = calculate_md5(xml) query = 'delete' - auth_error_conn = Connection(aws_secret_key='invalid') + auth_error_conn = Connection(tf.config['s3_access_key'], 'invalid') status, headers, body = \ auth_error_conn.make_request('POST', bucket, body=xml, headers={ diff --git a/test/functional/s3api/test_multi_upload.py b/test/functional/s3api/test_multi_upload.py index de61551e0..c2e1c0f93 100644 --- a/test/functional/s3api/test_multi_upload.py +++ b/test/functional/s3api/test_multi_upload.py @@ -304,9 +304,8 @@ class TestS3ApiMultiUpload(S3ApiBase): self.assertTrue(lines[0].startswith(b'<?xml'), body) self.assertTrue(lines[0].endswith(b'?>'), body) elem = fromstring(body, 'CompleteMultipartUploadResult') - # TODO: use tf.config value self.assertEqual( - 'http://%s:%s/bucket/obj1' % (self.conn.host, self.conn.port), + '%s/bucket/obj1' % tf.config['s3_storage_url'].rstrip('/'), elem.find('Location').text) self.assertEqual(elem.find('Bucket').text, bucket) self.assertEqual(elem.find('Key').text, key) @@ -428,7 +427,7 @@ class TestS3ApiMultiUpload(S3ApiBase): self.conn.make_request('PUT', bucket) query = 'uploads' - auth_error_conn = Connection(aws_secret_key='invalid') + auth_error_conn = Connection(tf.config['s3_access_key'], 'invalid') status, headers, body = \ auth_error_conn.make_request('POST', bucket, key, query=query) self.assertEqual(get_error_code(body), 'SignatureDoesNotMatch') @@ -442,7 +441,7 @@ class TestS3ApiMultiUpload(S3ApiBase): self.conn.make_request('PUT', bucket) query = 'uploads' - auth_error_conn = Connection(aws_secret_key='invalid') + auth_error_conn = Connection(tf.config['s3_access_key'], 'invalid') status, headers, body = \ auth_error_conn.make_request('GET', bucket, query=query) self.assertEqual(get_error_code(body), 'SignatureDoesNotMatch') @@ -462,7 +461,7 @@ class TestS3ApiMultiUpload(S3ApiBase): upload_id = elem.find('UploadId').text query = 'partNumber=%s&uploadId=%s' % (1, upload_id) - auth_error_conn = Connection(aws_secret_key='invalid') + auth_error_conn = Connection(tf.config['s3_access_key'], 'invalid') status, headers, body = \ auth_error_conn.make_request('PUT', bucket, key, query=query) self.assertEqual(get_error_code(body), 'SignatureDoesNotMatch') @@ -500,7 +499,7 @@ class TestS3ApiMultiUpload(S3ApiBase): upload_id = elem.find('UploadId').text query = 'partNumber=%s&uploadId=%s' % (1, upload_id) - auth_error_conn = Connection(aws_secret_key='invalid') + auth_error_conn = Connection(tf.config['s3_access_key'], 'invalid') status, headers, body = \ auth_error_conn.make_request('PUT', bucket, key, headers={ @@ -541,7 +540,7 @@ class TestS3ApiMultiUpload(S3ApiBase): upload_id = elem.find('UploadId').text query = 'uploadId=%s' % upload_id - auth_error_conn = Connection(aws_secret_key='invalid') + auth_error_conn = Connection(tf.config['s3_access_key'], 'invalid') status, headers, body = \ auth_error_conn.make_request('GET', bucket, key, query=query) @@ -568,7 +567,7 @@ class TestS3ApiMultiUpload(S3ApiBase): self._upload_part(bucket, key, upload_id) query = 'uploadId=%s' % upload_id - auth_error_conn = Connection(aws_secret_key='invalid') + auth_error_conn = Connection(tf.config['s3_access_key'], 'invalid') status, headers, body = \ auth_error_conn.make_request('DELETE', bucket, key, query=query) self.assertEqual(get_error_code(body), 'SignatureDoesNotMatch') @@ -612,7 +611,7 @@ class TestS3ApiMultiUpload(S3ApiBase): self.assertEqual(get_error_code(body), 'EntityTooSmall') # invalid credentials - auth_error_conn = Connection(aws_secret_key='invalid') + auth_error_conn = Connection(tf.config['s3_access_key'], 'invalid') status, headers, body = \ auth_error_conn.make_request('POST', bucket, keys[0], body=xml, query=query) @@ -881,6 +880,8 @@ class TestS3ApiMultiUpload(S3ApiBase): self.assertEqual(headers['content-length'], '0') def test_object_multi_upload_part_copy_version(self): + if 'object_versioning' not in tf.cluster_info: + self.skipTest('Object Versioning not enabled') bucket = 'bucket' keys = ['obj1'] uploads = [] diff --git a/test/functional/s3api/test_object.py b/test/functional/s3api/test_object.py index 5b518eaa8..8079c157b 100644 --- a/test/functional/s3api/test_object.py +++ b/test/functional/s3api/test_object.py @@ -147,7 +147,7 @@ class TestS3ApiObject(S3ApiBase): self.assertCommonResponseHeaders(headers) def test_put_object_error(self): - auth_error_conn = Connection(aws_secret_key='invalid') + auth_error_conn = Connection(tf.config['s3_access_key'], 'invalid') status, headers, body = \ auth_error_conn.make_request('PUT', self.bucket, 'object') self.assertEqual(get_error_code(body), 'SignatureDoesNotMatch') @@ -166,7 +166,7 @@ class TestS3ApiObject(S3ApiBase): dst_obj = 'dst_object' headers = {'x-amz-copy-source': '/%s/%s' % (self.bucket, obj)} - auth_error_conn = Connection(aws_secret_key='invalid') + auth_error_conn = Connection(tf.config['s3_access_key'], 'invalid') status, headers, body = \ auth_error_conn.make_request('PUT', dst_bucket, dst_obj, headers) self.assertEqual(get_error_code(body), 'SignatureDoesNotMatch') @@ -197,7 +197,7 @@ class TestS3ApiObject(S3ApiBase): obj = 'object' self.conn.make_request('PUT', self.bucket, obj) - auth_error_conn = Connection(aws_secret_key='invalid') + auth_error_conn = Connection(tf.config['s3_access_key'], 'invalid') status, headers, body = \ auth_error_conn.make_request('GET', self.bucket, obj) self.assertEqual(get_error_code(body), 'SignatureDoesNotMatch') @@ -216,7 +216,7 @@ class TestS3ApiObject(S3ApiBase): obj = 'object' self.conn.make_request('PUT', self.bucket, obj) - auth_error_conn = Connection(aws_secret_key='invalid') + auth_error_conn = Connection(tf.config['s3_access_key'], 'invalid') status, headers, body = \ auth_error_conn.make_request('HEAD', self.bucket, obj) self.assertEqual(status, 403) @@ -239,7 +239,7 @@ class TestS3ApiObject(S3ApiBase): obj = 'object' self.conn.make_request('PUT', self.bucket, obj) - auth_error_conn = Connection(aws_secret_key='invalid') + auth_error_conn = Connection(tf.config['s3_access_key'], 'invalid') status, headers, body = \ auth_error_conn.make_request('DELETE', self.bucket, obj) self.assertEqual(get_error_code(body), 'SignatureDoesNotMatch') diff --git a/test/functional/s3api/test_service.py b/test/functional/s3api/test_service.py index 3eb75dff5..77779cba0 100644 --- a/test/functional/s3api/test_service.py +++ b/test/functional/s3api/test_service.py @@ -69,7 +69,7 @@ class TestS3ApiService(S3ApiBase): self.assertTrue(b.find('CreationDate') is not None) def test_service_error_signature_not_match(self): - auth_error_conn = Connection(aws_secret_key='invalid') + auth_error_conn = Connection(tf.config['s3_access_key'], 'invalid') status, headers, body = auth_error_conn.make_request('GET') self.assertEqual(get_error_code(body), 'SignatureDoesNotMatch') self.assertEqual(headers['content-type'], 'application/xml') diff --git a/test/functional/test_object.py b/test/functional/test_object.py index dbc72acef..6145d4a98 100644 --- a/test/functional/test_object.py +++ b/test/functional/test_object.py @@ -1726,7 +1726,7 @@ class TestObject(unittest.TestCase): if 'etag_quoter' not in tf.cluster_info: raise SkipTest("etag-quoter middleware is not enabled") - def do_head(expect_quoted=False): + def do_head(expect_quoted=None): def head(url, token, parsed, conn): conn.request('HEAD', '%s/%s/%s' % ( parsed.path, self.container, self.obj), '', @@ -1736,6 +1736,11 @@ class TestObject(unittest.TestCase): resp = retry(head) resp.read() self.assertEqual(resp.status, 200) + + if expect_quoted is None: + expect_quoted = tf.cluster_info.get('etag_quoter', {}).get( + 'enable_by_default', False) + expected_etag = hashlib.md5(b'test').hexdigest() if expect_quoted: expected_etag = '"%s"' % expected_etag @@ -1771,7 +1776,7 @@ class TestObject(unittest.TestCase): post_container('') do_head(expect_quoted=True) post_container('f') - do_head() + do_head(expect_quoted=False) finally: # Don't leave a dirty account post_account('') diff --git a/test/functional/test_object_versioning.py b/test/functional/test_object_versioning.py index ebfca68f6..d7db187c0 100644 --- a/test/functional/test_object_versioning.py +++ b/test/functional/test_object_versioning.py @@ -26,6 +26,7 @@ from six.moves.urllib.parse import quote, unquote import test.functional as tf +from swift.common.swob import normalize_etag from swift.common.utils import MD5_OF_EMPTY_STRING, config_true_value from swift.common.middleware.versioned_writes.object_versioning import \ DELETE_MARKER_CONTENT_TYPE @@ -331,6 +332,80 @@ class TestObjectVersioning(TestObjectVersioningBase): # listing, though, we'll only ever have the two entries. self.assertTotalVersions(container, 2) + def test_get_if_match(self): + body = b'data' + oname = Utils.create_name() + obj = self.env.unversioned_container.file(oname) + resp = obj.write(body, return_resp=True) + etag = resp.getheader('etag') + self.assertEqual(md5(body).hexdigest(), normalize_etag(etag)) + + # un-versioned object is cool with with if-match + self.assertEqual(body, obj.read(hdrs={'if-match': etag})) + with self.assertRaises(ResponseError) as cm: + obj.read(hdrs={'if-match': 'not-the-etag'}) + self.assertEqual(412, cm.exception.status) + + v_obj = self.env.container.file(oname) + resp = v_obj.write(body, return_resp=True) + self.assertEqual(resp.getheader('etag'), etag) + + # versioned object is too with with if-match + self.assertEqual(body, v_obj.read(hdrs={ + 'if-match': normalize_etag(etag)})) + # works quoted, too + self.assertEqual(body, v_obj.read(hdrs={ + 'if-match': '"%s"' % normalize_etag(etag)})) + with self.assertRaises(ResponseError) as cm: + v_obj.read(hdrs={'if-match': 'not-the-etag'}) + self.assertEqual(412, cm.exception.status) + + def test_container_acls(self): + if tf.skip3: + raise SkipTest('Username3 not set') + + obj = self.env.container.file(Utils.create_name()) + resp = obj.write(b"data", return_resp=True) + version_id = resp.getheader('x-object-version-id') + self.assertIsNotNone(version_id) + + with self.assertRaises(ResponseError) as cm: + obj.read(hdrs={'X-Auth-Token': self.env.conn3.storage_token}) + self.assertEqual(403, cm.exception.status) + + # Container ACLs work more or less like they always have + self.env.container.update_metadata( + hdrs={'X-Container-Read': self.env.conn3.user_acl}) + self.assertEqual(b"data", obj.read(hdrs={ + 'X-Auth-Token': self.env.conn3.storage_token})) + + # But the version-specifc GET still requires a swift owner + with self.assertRaises(ResponseError) as cm: + obj.read(hdrs={'X-Auth-Token': self.env.conn3.storage_token}, + parms={'version-id': version_id}) + self.assertEqual(403, cm.exception.status) + + # If it's pointing to a symlink that points elsewhere, that still needs + # to be authed + tgt_name = Utils.create_name() + self.env.unversioned_container.file(tgt_name).write(b'link') + sym_tgt_header = quote(unquote('%s/%s' % ( + self.env.unversioned_container.name, tgt_name))) + obj.write(hdrs={'X-Symlink-Target': sym_tgt_header}) + + # So, user1's good... + self.assertEqual(b'link', obj.read()) + # ...but user3 can't + with self.assertRaises(ResponseError) as cm: + obj.read(hdrs={'X-Auth-Token': self.env.conn3.storage_token}) + self.assertEqual(403, cm.exception.status) + + # unless we add the acl to the unversioned_container + self.env.unversioned_container.update_metadata( + hdrs={'X-Container-Read': self.env.conn3.user_acl}) + self.assertEqual(b'link', obj.read( + hdrs={'X-Auth-Token': self.env.conn3.storage_token})) + def _test_overwriting_setup(self, obj_name=None): # sanity container = self.env.container @@ -919,13 +994,13 @@ class TestObjectVersioning(TestObjectVersioningBase): 'Content-Type': 'text/jibberish32' }, return_resp=True) v1_version_id = resp.getheader('x-object-version-id') - v1_etag = resp.getheader('etag') + v1_etag = normalize_etag(resp.getheader('etag')) resp = obj.write(b'version2', hdrs={ 'Content-Type': 'text/jibberish33' }, return_resp=True) v2_version_id = resp.getheader('x-object-version-id') - v2_etag = resp.getheader('etag') + v2_etag = normalize_etag(resp.getheader('etag')) # sanity self.assertEqual(b'version2', obj.read()) @@ -992,7 +1067,7 @@ class TestObjectVersioning(TestObjectVersioningBase): self.assertEqual(b'version1', obj.read()) obj_info = obj.info() self.assertEqual('text/jibberish32', obj_info['content_type']) - self.assertEqual(v1_etag, obj_info['etag']) + self.assertEqual(v1_etag, normalize_etag(obj_info['etag'])) def test_delete_with_version_api_old_object(self): versioned_obj_name = Utils.create_name() @@ -2308,7 +2383,7 @@ class TestSloWithVersioning(TestObjectVersioningBase): expected = { 'bytes': file_info['content_length'], 'content_type': 'application/octet-stream', - 'hash': manifest_info['etag'], + 'hash': normalize_etag(manifest_info['etag']), 'name': 'my-slo-manifest', 'slo_etag': file_info['etag'], 'version_symlink': True, @@ -2340,7 +2415,7 @@ class TestSloWithVersioning(TestObjectVersioningBase): expected = { 'bytes': file_info['content_length'], 'content_type': 'application/octet-stream', - 'hash': manifest_info['etag'], + 'hash': normalize_etag(manifest_info['etag']), 'name': 'my-slo-manifest', 'slo_etag': file_info['etag'], 'version_symlink': True, @@ -2688,16 +2763,11 @@ class TestVersioningContainerTempurl(TestObjectVersioningBase): obj.write(b"version2") # get v2 object (reading from versions container) - # cross container tempurl does not work for container tempurl key - try: - obj.read(parms=get_parms, cfg={'no_auth_token': True}) - except ResponseError as e: - self.assertEqual(e.status, 401) - else: - self.fail('request did not error') - try: - obj.info(parms=get_parms, cfg={'no_auth_token': True}) - except ResponseError as e: - self.assertEqual(e.status, 401) - else: - self.fail('request did not error') + # versioning symlink allows us to bypass the normal + # container-tempurl-key scoping + contents = obj.read(parms=get_parms, cfg={'no_auth_token': True}) + self.assert_status([200]) + self.assertEqual(contents, b"version2") + # HEAD works, too + obj.info(parms=get_parms, cfg={'no_auth_token': True}) + self.assert_status([200]) diff --git a/test/functional/test_slo.py b/test/functional/test_slo.py index c055f7bbd..8003a2d70 100644 --- a/test/functional/test_slo.py +++ b/test/functional/test_slo.py @@ -23,6 +23,8 @@ from copy import deepcopy import six +from swift.common.swob import normalize_etag + import test.functional as tf from test.functional import cluster_info, SkipTest from test.functional.tests import Utils, Base, Base2, BaseEnv @@ -299,8 +301,14 @@ class TestSlo(Base): # a POST. file_item.initialize(parms={'multipart-manifest': 'get'}) manifest_etag = file_item.etag - self.assertFalse(manifest_etag.startswith('"')) - self.assertFalse(manifest_etag.endswith('"')) + if tf.cluster_info.get('etag_quoter', {}).get('enable_by_default'): + self.assertTrue(manifest_etag.startswith('"')) + self.assertTrue(manifest_etag.endswith('"')) + # ...but in the listing, it'll be stripped + manifest_etag = manifest_etag[1:-1] + else: + self.assertFalse(manifest_etag.startswith('"')) + self.assertFalse(manifest_etag.endswith('"')) file_item.initialize() slo_etag = file_item.etag @@ -715,6 +723,8 @@ class TestSlo(Base): source_contents = source.read(parms={'multipart-manifest': 'get'}) source_json = json.loads(source_contents) manifest_etag = hashlib.md5(source_contents).hexdigest() + if tf.cluster_info.get('etag_quoter', {}).get('enable_by_default'): + manifest_etag = '"%s"' % manifest_etag self.assertEqual(manifest_etag, source.etag) source.initialize() @@ -752,14 +762,14 @@ class TestSlo(Base): actual = names['manifest-abcde'] self.assertEqual(4 * 1024 * 1024 + 1, actual['bytes']) self.assertEqual('application/octet-stream', actual['content_type']) - self.assertEqual(manifest_etag, actual['hash']) + self.assertEqual(normalize_etag(manifest_etag), actual['hash']) self.assertEqual(slo_etag, actual['slo_etag']) self.assertIn('copied-abcde-manifest-only', names) actual = names['copied-abcde-manifest-only'] self.assertEqual(4 * 1024 * 1024 + 1, actual['bytes']) self.assertEqual('application/octet-stream', actual['content_type']) - self.assertEqual(manifest_etag, actual['hash']) + self.assertEqual(normalize_etag(manifest_etag), actual['hash']) self.assertEqual(slo_etag, actual['slo_etag']) # Test copy manifest including data segments @@ -789,6 +799,8 @@ class TestSlo(Base): source_contents = source.read(parms={'multipart-manifest': 'get'}) source_json = json.loads(source_contents) manifest_etag = hashlib.md5(source_contents).hexdigest() + if tf.cluster_info.get('etag_quoter', {}).get('enable_by_default'): + manifest_etag = '"%s"' % manifest_etag self.assertEqual(manifest_etag, source.etag) source.initialize() @@ -831,14 +843,14 @@ class TestSlo(Base): self.assertEqual(4 * 1024 * 1024 + 1, actual['bytes']) self.assertEqual('application/octet-stream', actual['content_type']) # the container listing should have the etag of the manifest contents - self.assertEqual(manifest_etag, actual['hash']) + self.assertEqual(normalize_etag(manifest_etag), actual['hash']) self.assertEqual(slo_etag, actual['slo_etag']) self.assertIn('copied-abcde-manifest-only', names) actual = names['copied-abcde-manifest-only'] self.assertEqual(4 * 1024 * 1024 + 1, actual['bytes']) self.assertEqual('image/jpeg', actual['content_type']) - self.assertEqual(manifest_etag, actual['hash']) + self.assertEqual(normalize_etag(manifest_etag), actual['hash']) self.assertEqual(slo_etag, actual['slo_etag']) def test_slo_copy_the_manifest_account(self): @@ -1098,12 +1110,7 @@ class TestSlo(Base): manifest = self.env.container.file("manifest-db") got_body = manifest.read(parms={'multipart-manifest': 'get', 'format': 'raw'}) - body_md5 = hashlib.md5(got_body).hexdigest() - headers = dict( - (h.lower(), v) - for h, v in manifest.conn.response.getheaders()) - self.assertIn('etag', headers) - self.assertEqual(headers['etag'], body_md5) + self.assert_etag(hashlib.md5(got_body).hexdigest()) # raw format should have the actual manifest object content-type self.assertEqual('application/octet-stream', manifest.content_type) diff --git a/test/functional/test_symlink.py b/test/functional/test_symlink.py index 5cd66d510..1b6ec820f 100755 --- a/test/functional/test_symlink.py +++ b/test/functional/test_symlink.py @@ -25,6 +25,7 @@ from six.moves import urllib from uuid import uuid4 from swift.common.http import is_success +from swift.common.swob import normalize_etag from swift.common.utils import json, MD5_OF_EMPTY_STRING from swift.common.middleware.slo import SloGetContext from test.functional import check_response, retry, requires_acls, \ @@ -1135,7 +1136,7 @@ class TestSymlink(Base): etag=self.env.tgt_etag) # overwrite tgt object - old_tgt_etag = self.env.tgt_etag + old_tgt_etag = normalize_etag(self.env.tgt_etag) self.env._create_tgt_object(body='updated target body') # sanity @@ -1380,7 +1381,7 @@ class TestSymlink(Base): object_list[0]['symlink_path']) obj_info = object_list[0] self.assertIn('symlink_etag', obj_info) - self.assertEqual(self.env.tgt_etag, + self.assertEqual(normalize_etag(self.env.tgt_etag), obj_info['symlink_etag']) self.assertEqual(int(self.env.tgt_length), obj_info['symlink_bytes']) @@ -1550,7 +1551,7 @@ class TestSymlinkSlo(Base): 'symlink_path': '/v1/%s/%s/manifest-abcde' % ( self.account_name, self.env.container2.name), 'symlink_bytes': 4 * 2 ** 20 + 1, - 'symlink_etag': manifest_etag, + 'symlink_etag': normalize_etag(manifest_etag), }) def test_static_link_target_slo_manifest_wrong_etag(self): @@ -1740,7 +1741,11 @@ class TestSymlinkToSloSegments(Base): self.assertEqual(1024 * 1024, f_dict['bytes']) self.assertEqual('application/octet-stream', f_dict['content_type']) - self.assertEqual(manifest_etag, f_dict['hash']) + if tf.cluster_info.get('etag_quoter', {}).get( + 'enable_by_default'): + self.assertEqual(manifest_etag, '"%s"' % f_dict['hash']) + else: + self.assertEqual(manifest_etag, f_dict['hash']) self.assertEqual(slo_etag, f_dict['slo_etag']) break else: @@ -1759,7 +1764,11 @@ class TestSymlinkToSloSegments(Base): self.assertEqual(1024 * 1024, f_dict['bytes']) self.assertEqual(file_item.content_type, f_dict['content_type']) - self.assertEqual(manifest_etag, f_dict['hash']) + if tf.cluster_info.get('etag_quoter', {}).get( + 'enable_by_default'): + self.assertEqual(manifest_etag, '"%s"' % f_dict['hash']) + else: + self.assertEqual(manifest_etag, f_dict['hash']) self.assertEqual(slo_etag, f_dict['slo_etag']) break else: @@ -1778,7 +1787,11 @@ class TestSymlinkToSloSegments(Base): self.assertEqual(1024 * 1024, f_dict['bytes']) self.assertEqual(file_item.content_type, f_dict['content_type']) - self.assertEqual(manifest_etag, f_dict['hash']) + if tf.cluster_info.get('etag_quoter', {}).get( + 'enable_by_default'): + self.assertEqual(manifest_etag, '"%s"' % f_dict['hash']) + else: + self.assertEqual(manifest_etag, f_dict['hash']) self.assertEqual(slo_etag, f_dict['slo_etag']) break else: @@ -1811,6 +1824,8 @@ class TestSymlinkToSloSegments(Base): source_contents = source.read(parms={'multipart-manifest': 'get'}) source_json = json.loads(source_contents) manifest_etag = hashlib.md5(source_contents).hexdigest() + if tf.cluster_info.get('etag_quoter', {}).get('enable_by_default'): + manifest_etag = '"%s"' % manifest_etag source.initialize() slo_etag = source.etag @@ -1857,14 +1872,20 @@ class TestSymlinkToSloSegments(Base): actual = names['manifest-linkto-ab'] self.assertEqual(2 * 1024 * 1024, actual['bytes']) self.assertEqual('application/octet-stream', actual['content_type']) - self.assertEqual(manifest_etag, actual['hash']) + if tf.cluster_info.get('etag_quoter', {}).get('enable_by_default'): + self.assertEqual(manifest_etag, '"%s"' % actual['hash']) + else: + self.assertEqual(manifest_etag, actual['hash']) self.assertEqual(slo_etag, actual['slo_etag']) self.assertIn('copied-ab-manifest-only', names) actual = names['copied-ab-manifest-only'] self.assertEqual(2 * 1024 * 1024, actual['bytes']) self.assertEqual('application/octet-stream', actual['content_type']) - self.assertEqual(manifest_etag, actual['hash']) + if tf.cluster_info.get('etag_quoter', {}).get('enable_by_default'): + self.assertEqual(manifest_etag, '"%s"' % actual['hash']) + else: + self.assertEqual(manifest_etag, actual['hash']) self.assertEqual(slo_etag, actual['slo_etag']) @@ -2000,13 +2021,13 @@ class TestSymlinkTargetObjectComparison(Base): else: self.assertEqual(b'', body) self.assert_status(200) - self.assert_header('etag', md5) + self.assert_etag(md5) hdrs = {'If-Match': 'bogus'} self.assertRaises(ResponseError, file_symlink.read, hdrs=hdrs, parms=self.env.parms) self.assert_status(412) - self.assert_header('etag', md5) + self.assert_etag(md5) def testIfMatchMultipleEtags(self): for file_item in self.env.files: @@ -2022,13 +2043,13 @@ class TestSymlinkTargetObjectComparison(Base): else: self.assertEqual(b'', body) self.assert_status(200) - self.assert_header('etag', md5) + self.assert_etag(md5) hdrs = {'If-Match': '"bogus1", "bogus2", "bogus3"'} self.assertRaises(ResponseError, file_symlink.read, hdrs=hdrs, parms=self.env.parms) self.assert_status(412) - self.assert_header('etag', md5) + self.assert_etag(md5) def testIfNoneMatch(self): for file_item in self.env.files: @@ -2044,13 +2065,13 @@ class TestSymlinkTargetObjectComparison(Base): else: self.assertEqual(b'', body) self.assert_status(200) - self.assert_header('etag', md5) + self.assert_etag(md5) hdrs = {'If-None-Match': md5} self.assertRaises(ResponseError, file_symlink.read, hdrs=hdrs, parms=self.env.parms) self.assert_status(304) - self.assert_header('etag', md5) + self.assert_etag(md5) self.assert_header('accept-ranges', 'bytes') def testIfNoneMatchMultipleEtags(self): @@ -2067,14 +2088,14 @@ class TestSymlinkTargetObjectComparison(Base): else: self.assertEqual(b'', body) self.assert_status(200) - self.assert_header('etag', md5) + self.assert_etag(md5) hdrs = {'If-None-Match': '"bogus1", "bogus2", "%s"' % md5} self.assertRaises(ResponseError, file_symlink.read, hdrs=hdrs, parms=self.env.parms) self.assert_status(304) - self.assert_header('etag', md5) + self.assert_etag(md5) self.assert_header('accept-ranges', 'bytes') def testIfModifiedSince(self): @@ -2091,19 +2112,19 @@ class TestSymlinkTargetObjectComparison(Base): else: self.assertEqual(b'', body) self.assert_status(200) - self.assert_header('etag', md5) + self.assert_etag(md5) self.assertTrue(file_symlink.info(hdrs=hdrs, parms=self.env.parms)) hdrs = {'If-Modified-Since': self.env.time_new} self.assertRaises(ResponseError, file_symlink.read, hdrs=hdrs, parms=self.env.parms) self.assert_status(304) - self.assert_header('etag', md5) + self.assert_etag(md5) self.assert_header('accept-ranges', 'bytes') self.assertRaises(ResponseError, file_symlink.info, hdrs=hdrs, parms=self.env.parms) self.assert_status(304) - self.assert_header('etag', md5) + self.assert_etag(md5) self.assert_header('accept-ranges', 'bytes') def testIfUnmodifiedSince(self): @@ -2120,18 +2141,18 @@ class TestSymlinkTargetObjectComparison(Base): else: self.assertEqual(b'', body) self.assert_status(200) - self.assert_header('etag', md5) + self.assert_etag(md5) self.assertTrue(file_symlink.info(hdrs=hdrs, parms=self.env.parms)) hdrs = {'If-Unmodified-Since': self.env.time_old_f2} self.assertRaises(ResponseError, file_symlink.read, hdrs=hdrs, parms=self.env.parms) self.assert_status(412) - self.assert_header('etag', md5) + self.assert_etag(md5) self.assertRaises(ResponseError, file_symlink.info, hdrs=hdrs, parms=self.env.parms) self.assert_status(412) - self.assert_header('etag', md5) + self.assert_etag(md5) def testIfMatchAndUnmodified(self): for file_item in self.env.files: @@ -2148,21 +2169,21 @@ class TestSymlinkTargetObjectComparison(Base): else: self.assertEqual(b'', body) self.assert_status(200) - self.assert_header('etag', md5) + self.assert_etag(md5) hdrs = {'If-Match': 'bogus', 'If-Unmodified-Since': self.env.time_new} self.assertRaises(ResponseError, file_symlink.read, hdrs=hdrs, parms=self.env.parms) self.assert_status(412) - self.assert_header('etag', md5) + self.assert_etag(md5) hdrs = {'If-Match': md5, 'If-Unmodified-Since': self.env.time_old_f3} self.assertRaises(ResponseError, file_symlink.read, hdrs=hdrs, parms=self.env.parms) self.assert_status(412) - self.assert_header('etag', md5) + self.assert_etag(md5) def testLastModified(self): file_item = self.env.container.file(Utils.create_name()) @@ -2186,7 +2207,7 @@ class TestSymlinkTargetObjectComparison(Base): hdrs = {'If-Modified-Since': last_modified} self.assertRaises(ResponseError, file_symlink.read, hdrs=hdrs) self.assert_status(304) - self.assert_header('etag', md5) + self.assert_etag(md5) self.assert_header('accept-ranges', 'bytes') hdrs = {'If-Unmodified-Since': last_modified} @@ -2227,20 +2248,20 @@ class TestSymlinkComparison(TestSymlinkTargetObjectComparison): body = file_symlink.read(hdrs=hdrs, parms=self.env.parms) self.assertEqual(b'', body) self.assert_status(200) - self.assert_header('etag', md5) + self.assert_etag(md5) hdrs = {'If-Modified-Since': last_modified} self.assertRaises(ResponseError, file_symlink.read, hdrs=hdrs, parms=self.env.parms) self.assert_status(304) - self.assert_header('etag', md5) + self.assert_etag(md5) self.assert_header('accept-ranges', 'bytes') hdrs = {'If-Unmodified-Since': last_modified} body = file_symlink.read(hdrs=hdrs, parms=self.env.parms) self.assertEqual(b'', body) self.assert_status(200) - self.assert_header('etag', md5) + self.assert_etag(md5) class TestSymlinkAccountTempurl(Base): diff --git a/test/functional/test_versioned_writes.py b/test/functional/test_versioned_writes.py index d58da88e6..7521825f2 100644 --- a/test/functional/test_versioned_writes.py +++ b/test/functional/test_versioned_writes.py @@ -684,7 +684,11 @@ class TestObjectVersioning(Base): prev_version = versions_container.file(versioned_obj_name) prev_version_info = prev_version.info(parms={'symlink': 'get'}) self.assertEqual(b"aaaaa", prev_version.read()) - self.assertEqual(MD5_OF_EMPTY_STRING, prev_version_info['etag']) + symlink_etag = prev_version_info['etag'] + if symlink_etag.startswith('"') and symlink_etag.endswith('"') and \ + symlink_etag[1:-1]: + symlink_etag = symlink_etag[1:-1] + self.assertEqual(MD5_OF_EMPTY_STRING, symlink_etag) self.assertEqual(sym_tgt_header, prev_version_info['x_symlink_target']) return symlink, tgt_a @@ -698,7 +702,10 @@ class TestObjectVersioning(Base): symlink.delete() sym_info = symlink.info(parms={'symlink': 'get'}) self.assertEqual(b"aaaaa", symlink.read()) - self.assertEqual(MD5_OF_EMPTY_STRING, sym_info['etag']) + if tf.cluster_info.get('etag_quoter', {}).get('enable_by_default'): + self.assertEqual('"%s"' % MD5_OF_EMPTY_STRING, sym_info['etag']) + else: + self.assertEqual(MD5_OF_EMPTY_STRING, sym_info['etag']) self.assertEqual( quote(unquote('%s/%s' % (self.env.container.name, target.name))), sym_info['x_symlink_target']) diff --git a/test/functional/tests.py b/test/functional/tests.py index dc149cffa..51b4c663f 100644 --- a/test/functional/tests.py +++ b/test/functional/tests.py @@ -27,6 +27,7 @@ import uuid from copy import deepcopy import eventlet from swift.common.http import is_success, is_client_error +from swift.common.swob import normalize_etag from email.utils import parsedate if six.PY2: @@ -131,6 +132,13 @@ class Base(unittest.TestCase): 'Expected header name %r not found in response.' % header_name) self.assertEqual(expected_value, actual_value) + def assert_etag(self, unquoted_value): + if tf.cluster_info.get('etag_quoter', {}).get('enable_by_default'): + expected = '"%s"' % unquoted_value + else: + expected = unquoted_value + self.assert_header('etag', expected) + class Base2(object): @classmethod @@ -874,7 +882,11 @@ class TestContainer(Base): for actual in file_list: name = actual['name'] self.assertIn(name, expected) - self.assertEqual(expected[name]['etag'], actual['hash']) + if tf.cluster_info.get('etag_quoter', {}).get('enable_by_default'): + self.assertEqual(expected[name]['etag'], + '"%s"' % actual['hash']) + else: + self.assertEqual(expected[name]['etag'], actual['hash']) self.assertEqual( expected[name]['content_type'], actual['content_type']) self.assertEqual( @@ -1365,6 +1377,8 @@ class TestFile(Base): 'x-delete-at': mock.ANY, 'x-trans-id': mock.ANY, 'x-openstack-request-id': mock.ANY} + if tf.cluster_info.get('etag_quoter', {}).get('enable_by_default'): + expected_headers['etag'] = '"%s"' % expected_headers['etag'] unexpected_headers = ['connection', 'x-delete-after'] do_test(put_headers, {}, expected_headers, unexpected_headers) @@ -1420,7 +1434,7 @@ class TestFile(Base): self.fail('Failed to find %s in listing' % dest_filename) self.assertEqual(file_item.size, obj['bytes']) - self.assertEqual(file_item.etag, obj['hash']) + self.assertEqual(normalize_etag(file_item.etag), obj['hash']) self.assertEqual(file_item.content_type, obj['content_type']) file_copy = cont.file(dest_filename) @@ -1470,7 +1484,7 @@ class TestFile(Base): self.fail('Failed to find %s in listing' % dest_filename) self.assertEqual(file_item.size, obj['bytes']) - self.assertEqual(file_item.etag, obj['hash']) + self.assertEqual(normalize_etag(file_item.etag), obj['hash']) self.assertEqual( 'application/test-changed', obj['content_type']) @@ -1505,7 +1519,7 @@ class TestFile(Base): self.fail('Failed to find %s in listing' % dest_filename) self.assertEqual(file_item.size, obj['bytes']) - self.assertEqual(file_item.etag, obj['hash']) + self.assertEqual(normalize_etag(file_item.etag), obj['hash']) self.assertEqual( 'application/test-updated', obj['content_type']) @@ -2088,7 +2102,7 @@ class TestFile(Base): self.assertEqual(file_item.read(hdrs=hdrs), data[-i:]) self.assert_header('content-range', 'bytes %d-%d/%d' % ( file_length - i, file_length - 1, file_length)) - self.assert_header('etag', file_item.md5) + self.assert_etag(file_item.md5) self.assert_header('accept-ranges', 'bytes') range_string = 'bytes=%d-' % (i) @@ -2102,7 +2116,7 @@ class TestFile(Base): self.assertRaises(ResponseError, file_item.read, hdrs=hdrs) self.assert_status(416) self.assert_header('content-range', 'bytes */%d' % file_length) - self.assert_header('etag', file_item.md5) + self.assert_etag(file_item.md5) self.assert_header('accept-ranges', 'bytes') range_string = 'bytes=%d-%d' % (file_length - 1000, file_length + 2000) @@ -2416,14 +2430,16 @@ class TestFile(Base): file_item.content_type = content_type file_item.write_random(self.env.file_size) - md5 = file_item.md5 + expected_etag = file_item.md5 + if tf.cluster_info.get('etag_quoter', {}).get('enable_by_default'): + expected_etag = '"%s"' % expected_etag file_item = self.env.container.file(file_name) info = file_item.info() self.assert_status(200) self.assertEqual(info['content_length'], self.env.file_size) - self.assertEqual(info['etag'], md5) + self.assertEqual(info['etag'], expected_etag) self.assertEqual(info['content_type'], content_type) self.assertIn('last_modified', info) @@ -2612,14 +2628,7 @@ class TestFile(Base): file_item = self.env.container.file(Utils.create_name()) data = io.BytesIO(file_item.write_random(512)) - etag = File.compute_md5sum(data) - - headers = dict((h.lower(), v) - for h, v in self.env.conn.response.getheaders()) - self.assertIn('etag', headers.keys()) - - header_etag = headers['etag'].strip('"') - self.assertEqual(etag, header_etag) + self.assert_etag(File.compute_md5sum(data)) def testChunkedPut(self): if (tf.web_front_end == 'apache2'): @@ -2645,7 +2654,7 @@ class TestFile(Base): self.assertEqual(data, file_item.read()) info = file_item.info() - self.assertEqual(etag, info['etag']) + self.assertEqual(normalize_etag(info['etag']), etag) def test_POST(self): # verify consistency between object and container listing metadata @@ -2670,7 +2679,10 @@ class TestFile(Base): self.fail('Failed to find file %r in listing' % file_name) self.assertEqual(1024, f_dict['bytes']) self.assertEqual('text/foobar', f_dict['content_type']) - self.assertEqual(etag, f_dict['hash']) + if tf.cluster_info.get('etag_quoter', {}).get('enable_by_default'): + self.assertEqual(etag, '"%s"' % f_dict['hash']) + else: + self.assertEqual(etag, f_dict['hash']) put_last_modified = f_dict['last_modified'] # now POST updated content-type to each file @@ -2697,7 +2709,10 @@ class TestFile(Base): self.assertEqual(1024, f_dict['bytes']) self.assertEqual('image/foobarbaz', f_dict['content_type']) self.assertLess(put_last_modified, f_dict['last_modified']) - self.assertEqual(etag, f_dict['hash']) + if tf.cluster_info.get('etag_quoter', {}).get('enable_by_default'): + self.assertEqual(etag, '"%s"' % f_dict['hash']) + else: + self.assertEqual(etag, f_dict['hash']) class TestFileUTF8(Base2, TestFile): @@ -2742,7 +2757,7 @@ class TestFileComparison(Base): hdrs = {'If-Match': 'bogus'} self.assertRaises(ResponseError, file_item.read, hdrs=hdrs) self.assert_status(412) - self.assert_header('etag', file_item.md5) + self.assert_etag(file_item.md5) def testIfMatchMultipleEtags(self): for file_item in self.env.files: @@ -2752,7 +2767,7 @@ class TestFileComparison(Base): hdrs = {'If-Match': '"bogus1", "bogus2", "bogus3"'} self.assertRaises(ResponseError, file_item.read, hdrs=hdrs) self.assert_status(412) - self.assert_header('etag', file_item.md5) + self.assert_etag(file_item.md5) def testIfNoneMatch(self): for file_item in self.env.files: @@ -2762,7 +2777,7 @@ class TestFileComparison(Base): hdrs = {'If-None-Match': file_item.md5} self.assertRaises(ResponseError, file_item.read, hdrs=hdrs) self.assert_status(304) - self.assert_header('etag', file_item.md5) + self.assert_etag(file_item.md5) self.assert_header('accept-ranges', 'bytes') def testIfNoneMatchMultipleEtags(self): @@ -2774,7 +2789,7 @@ class TestFileComparison(Base): '"bogus1", "bogus2", "%s"' % file_item.md5} self.assertRaises(ResponseError, file_item.read, hdrs=hdrs) self.assert_status(304) - self.assert_header('etag', file_item.md5) + self.assert_etag(file_item.md5) self.assert_header('accept-ranges', 'bytes') def testIfModifiedSince(self): @@ -2786,11 +2801,11 @@ class TestFileComparison(Base): hdrs = {'If-Modified-Since': self.env.time_new} self.assertRaises(ResponseError, file_item.read, hdrs=hdrs) self.assert_status(304) - self.assert_header('etag', file_item.md5) + self.assert_etag(file_item.md5) self.assert_header('accept-ranges', 'bytes') self.assertRaises(ResponseError, file_item.info, hdrs=hdrs) self.assert_status(304) - self.assert_header('etag', file_item.md5) + self.assert_etag(file_item.md5) self.assert_header('accept-ranges', 'bytes') def testIfUnmodifiedSince(self): @@ -2802,10 +2817,10 @@ class TestFileComparison(Base): hdrs = {'If-Unmodified-Since': self.env.time_old_f2} self.assertRaises(ResponseError, file_item.read, hdrs=hdrs) self.assert_status(412) - self.assert_header('etag', file_item.md5) + self.assert_etag(file_item.md5) self.assertRaises(ResponseError, file_item.info, hdrs=hdrs) self.assert_status(412) - self.assert_header('etag', file_item.md5) + self.assert_etag(file_item.md5) def testIfMatchAndUnmodified(self): for file_item in self.env.files: @@ -2817,13 +2832,13 @@ class TestFileComparison(Base): 'If-Unmodified-Since': self.env.time_new} self.assertRaises(ResponseError, file_item.read, hdrs=hdrs) self.assert_status(412) - self.assert_header('etag', file_item.md5) + self.assert_etag(file_item.md5) hdrs = {'If-Match': file_item.md5, 'If-Unmodified-Since': self.env.time_old_f3} self.assertRaises(ResponseError, file_item.read, hdrs=hdrs) self.assert_status(412) - self.assert_header('etag', file_item.md5) + self.assert_etag(file_item.md5) def testLastModified(self): file_name = Utils.create_name() @@ -2844,7 +2859,7 @@ class TestFileComparison(Base): hdrs = {'If-Modified-Since': last_modified} self.assertRaises(ResponseError, file_item.read, hdrs=hdrs) self.assert_status(304) - self.assert_header('etag', etag) + self.assert_etag(etag) self.assert_header('accept-ranges', 'bytes') hdrs = {'If-Unmodified-Since': last_modified} |