summaryrefslogtreecommitdiff
path: root/test/functional
diff options
context:
space:
mode:
Diffstat (limited to 'test/functional')
-rw-r--r--test/functional/__init__.py16
-rw-r--r--test/functional/s3api/__init__.py9
-rw-r--r--test/functional/s3api/s3_test_client.py40
-rw-r--r--test/functional/s3api/test_acl.py6
-rw-r--r--test/functional/s3api/test_bucket.py16
-rw-r--r--test/functional/s3api/test_multi_delete.py2
-rw-r--r--test/functional/s3api/test_multi_upload.py19
-rw-r--r--test/functional/s3api/test_object.py10
-rw-r--r--test/functional/s3api/test_service.py2
-rw-r--r--test/functional/test_object.py9
-rw-r--r--test/functional/test_object_versioning.py106
-rw-r--r--test/functional/test_slo.py31
-rwxr-xr-xtest/functional/test_symlink.py79
-rw-r--r--test/functional/test_versioned_writes.py11
-rw-r--r--test/functional/tests.py75
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}