Merge "s3api: Allow multiple storage domains"
This commit is contained in:
@@ -592,8 +592,8 @@ use = egg:swift#s3api
|
|||||||
# you don't expect.
|
# you don't expect.
|
||||||
# s3_acl = false
|
# s3_acl = false
|
||||||
#
|
#
|
||||||
# Specify a host name of your Swift cluster. This enables virtual-hosted style
|
# Specify a (comma-separated) list of host names for your Swift cluster.
|
||||||
# requests.
|
# This enables virtual-hosted style requests.
|
||||||
# storage_domain =
|
# storage_domain =
|
||||||
#
|
#
|
||||||
# Enable pipeline order check for SLO, s3token, authtoken, keystoneauth
|
# Enable pipeline order check for SLO, s3token, authtoken, keystoneauth
|
||||||
|
@@ -263,7 +263,8 @@ class S3ApiMiddleware(object):
|
|||||||
wsgi_conf.get('multi_delete_concurrency', 2))
|
wsgi_conf.get('multi_delete_concurrency', 2))
|
||||||
self.conf.s3_acl = config_true_value(
|
self.conf.s3_acl = config_true_value(
|
||||||
wsgi_conf.get('s3_acl', False))
|
wsgi_conf.get('s3_acl', False))
|
||||||
self.conf.storage_domain = wsgi_conf.get('storage_domain', '')
|
self.conf.storage_domains = list_from_csv(
|
||||||
|
wsgi_conf.get('storage_domain', ''))
|
||||||
self.conf.auth_pipeline_check = config_true_value(
|
self.conf.auth_pipeline_check = config_true_value(
|
||||||
wsgi_conf.get('auth_pipeline_check', True))
|
wsgi_conf.get('auth_pipeline_check', True))
|
||||||
self.conf.max_upload_part_num = config_positive_int_value(
|
self.conf.max_upload_part_num = config_positive_int_value(
|
||||||
|
@@ -603,23 +603,23 @@ class S3Request(swob.Request):
|
|||||||
return 'AWSAccessKeyId' in self.params
|
return 'AWSAccessKeyId' in self.params
|
||||||
|
|
||||||
def _parse_host(self):
|
def _parse_host(self):
|
||||||
storage_domain = self.conf.storage_domain
|
if not self.conf.storage_domains:
|
||||||
if not storage_domain:
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if not storage_domain.startswith('.'):
|
|
||||||
storage_domain = '.' + storage_domain
|
|
||||||
|
|
||||||
if 'HTTP_HOST' in self.environ:
|
if 'HTTP_HOST' in self.environ:
|
||||||
given_domain = self.environ['HTTP_HOST']
|
given_domain = self.environ['HTTP_HOST']
|
||||||
elif 'SERVER_NAME' in self.environ:
|
elif 'SERVER_NAME' in self.environ:
|
||||||
given_domain = self.environ['SERVER_NAME']
|
given_domain = self.environ['SERVER_NAME']
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
port = ''
|
port = ''
|
||||||
if ':' in given_domain:
|
if ':' in given_domain:
|
||||||
given_domain, port = given_domain.rsplit(':', 1)
|
given_domain, port = given_domain.rsplit(':', 1)
|
||||||
|
|
||||||
|
for storage_domain in self.conf.storage_domains:
|
||||||
|
if not storage_domain.startswith('.'):
|
||||||
|
storage_domain = '.' + storage_domain
|
||||||
|
|
||||||
if given_domain.endswith(storage_domain):
|
if given_domain.endswith(storage_domain):
|
||||||
return given_domain[:-len(storage_domain)]
|
return given_domain[:-len(storage_domain)]
|
||||||
|
|
||||||
|
@@ -153,7 +153,7 @@ def mktime(timestamp_str, time_format='%Y-%m-%dT%H:%M:%S'):
|
|||||||
|
|
||||||
class Config(dict):
|
class Config(dict):
|
||||||
DEFAULTS = {
|
DEFAULTS = {
|
||||||
'storage_domain': '',
|
'storage_domains': [],
|
||||||
'location': 'us-east-1',
|
'location': 'us-east-1',
|
||||||
'force_swift_request_proxy_log': False,
|
'force_swift_request_proxy_log': False,
|
||||||
'dns_compliant_bucket_names': True,
|
'dns_compliant_bucket_names': True,
|
||||||
|
@@ -107,7 +107,7 @@ class TestS3ApiMiddleware(S3ApiTestCase):
|
|||||||
# will be short-circuited
|
# will be short-circuited
|
||||||
|
|
||||||
# check all defaults
|
# check all defaults
|
||||||
expected = Config()
|
expected = dict(Config())
|
||||||
expected.update({
|
expected.update({
|
||||||
'auth_pipeline_check': True,
|
'auth_pipeline_check': True,
|
||||||
'check_bucket_owner': False,
|
'check_bucket_owner': False,
|
||||||
@@ -126,7 +126,7 @@ class TestS3ApiMiddleware(S3ApiTestCase):
|
|||||||
|
|
||||||
# check all non-defaults are loaded
|
# check all non-defaults are loaded
|
||||||
conf = {
|
conf = {
|
||||||
'storage_domain': 'somewhere',
|
'storage_domain': 'somewhere,some.other.where',
|
||||||
'location': 'us-west-1',
|
'location': 'us-west-1',
|
||||||
'force_swift_request_proxy_log': True,
|
'force_swift_request_proxy_log': True,
|
||||||
'dns_compliant_bucket_names': False,
|
'dns_compliant_bucket_names': False,
|
||||||
@@ -148,6 +148,7 @@ class TestS3ApiMiddleware(S3ApiTestCase):
|
|||||||
s3api = S3ApiMiddleware(None, conf)
|
s3api = S3ApiMiddleware(None, conf)
|
||||||
conf['cors_preflight_allow_origin'] = \
|
conf['cors_preflight_allow_origin'] = \
|
||||||
conf['cors_preflight_allow_origin'].split(',')
|
conf['cors_preflight_allow_origin'].split(',')
|
||||||
|
conf['storage_domains'] = conf.pop('storage_domain').split(',')
|
||||||
self.assertEqual(conf, s3api.conf)
|
self.assertEqual(conf, s3api.conf)
|
||||||
|
|
||||||
# test allow_origin list with a '*' fails.
|
# test allow_origin list with a '*' fails.
|
||||||
|
@@ -651,18 +651,51 @@ class TestRequest(S3ApiTestCase):
|
|||||||
# Virtual hosted-style
|
# Virtual hosted-style
|
||||||
req = Request.blank('/', environ=environ, headers=headers)
|
req = Request.blank('/', environ=environ, headers=headers)
|
||||||
sigv2_req = S3Request(
|
sigv2_req = S3Request(
|
||||||
req.environ, conf=Config({'storage_domain': 's3.test.com'}))
|
req.environ, conf=Config({'storage_domains': ['s3.test.com']}))
|
||||||
uri = sigv2_req._canonical_uri()
|
uri = sigv2_req._canonical_uri()
|
||||||
self.assertEqual(uri, '/bucket1/')
|
self.assertEqual(uri, '/bucket1/')
|
||||||
self.assertEqual(req.environ['PATH_INFO'], '/')
|
self.assertEqual(req.environ['PATH_INFO'], '/')
|
||||||
|
|
||||||
req = Request.blank('/obj1', environ=environ, headers=headers)
|
req = Request.blank('/obj1', environ=environ, headers=headers)
|
||||||
sigv2_req = S3Request(
|
sigv2_req = S3Request(
|
||||||
req.environ, conf=Config({'storage_domain': 's3.test.com'}))
|
req.environ, conf=Config({'storage_domains': ['s3.test.com']}))
|
||||||
uri = sigv2_req._canonical_uri()
|
uri = sigv2_req._canonical_uri()
|
||||||
self.assertEqual(uri, '/bucket1/obj1')
|
self.assertEqual(uri, '/bucket1/obj1')
|
||||||
self.assertEqual(req.environ['PATH_INFO'], '/obj1')
|
self.assertEqual(req.environ['PATH_INFO'], '/obj1')
|
||||||
|
|
||||||
|
req = Request.blank('/obj2', environ=environ, headers=headers)
|
||||||
|
sigv2_req = S3Request(
|
||||||
|
req.environ, conf=Config({
|
||||||
|
'storage_domains': ['alternate.domain', 's3.test.com']}))
|
||||||
|
uri = sigv2_req._canonical_uri()
|
||||||
|
self.assertEqual(uri, '/bucket1/obj2')
|
||||||
|
self.assertEqual(req.environ['PATH_INFO'], '/obj2')
|
||||||
|
|
||||||
|
# Now check the other storage_domain
|
||||||
|
environ = {
|
||||||
|
'HTTP_HOST': 'bucket1.alternate.domain',
|
||||||
|
'REQUEST_METHOD': 'GET'}
|
||||||
|
req = Request.blank('/obj2', environ=environ, headers=headers)
|
||||||
|
sigv2_req = S3Request(
|
||||||
|
req.environ, conf=Config({
|
||||||
|
'storage_domains': ['alternate.domain', 's3.test.com']}))
|
||||||
|
uri = sigv2_req._canonical_uri()
|
||||||
|
self.assertEqual(uri, '/bucket1/obj2')
|
||||||
|
self.assertEqual(req.environ['PATH_INFO'], '/obj2')
|
||||||
|
|
||||||
|
# Non existent storage_domain means we can't find the container
|
||||||
|
environ = {
|
||||||
|
'HTTP_HOST': 'bucket1.incorrect.domain',
|
||||||
|
'REQUEST_METHOD': 'GET'}
|
||||||
|
req = Request.blank('/obj2', environ=environ, headers=headers)
|
||||||
|
sigv2_req = S3Request(
|
||||||
|
req.environ, conf=Config({
|
||||||
|
'storage_domains': ['alternate.domain', 's3.test.com']}))
|
||||||
|
uri = sigv2_req._canonical_uri()
|
||||||
|
# uo oh, no bucket
|
||||||
|
self.assertEqual(uri, '/obj2')
|
||||||
|
self.assertEqual(sigv2_req.container_name, 'obj2')
|
||||||
|
|
||||||
environ = {
|
environ = {
|
||||||
'HTTP_HOST': 's3.test.com',
|
'HTTP_HOST': 's3.test.com',
|
||||||
'REQUEST_METHOD': 'GET'}
|
'REQUEST_METHOD': 'GET'}
|
||||||
@@ -701,7 +734,7 @@ class TestRequest(S3ApiTestCase):
|
|||||||
'X-Amz-Date': x_amz_date}
|
'X-Amz-Date': x_amz_date}
|
||||||
|
|
||||||
# Virtual hosted-style
|
# Virtual hosted-style
|
||||||
self.s3api.conf.storage_domain = 's3.test.com'
|
self.s3api.conf.storage_domains = ['s3.test.com']
|
||||||
req = Request.blank('/', environ=environ, headers=headers)
|
req = Request.blank('/', environ=environ, headers=headers)
|
||||||
sigv4_req = SigV4Request(req.environ)
|
sigv4_req = SigV4Request(req.environ)
|
||||||
uri = sigv4_req._canonical_uri()
|
uri = sigv4_req._canonical_uri()
|
||||||
@@ -721,7 +754,7 @@ class TestRequest(S3ApiTestCase):
|
|||||||
'REQUEST_METHOD': 'GET'}
|
'REQUEST_METHOD': 'GET'}
|
||||||
|
|
||||||
# Path-style
|
# Path-style
|
||||||
self.s3api.conf.storage_domain = ''
|
self.s3api.conf.storage_domains = []
|
||||||
req = Request.blank('/', environ=environ, headers=headers)
|
req = Request.blank('/', environ=environ, headers=headers)
|
||||||
sigv4_req = SigV4Request(req.environ)
|
sigv4_req = SigV4Request(req.environ)
|
||||||
uri = sigv4_req._canonical_uri()
|
uri = sigv4_req._canonical_uri()
|
||||||
@@ -749,7 +782,7 @@ class TestRequest(S3ApiTestCase):
|
|||||||
'bWq2s1WEIj+Ydj0vQ697zp+IXMU='),
|
'bWq2s1WEIj+Ydj0vQ697zp+IXMU='),
|
||||||
})
|
})
|
||||||
sigv2_req = S3Request(req.environ, conf=Config({
|
sigv2_req = S3Request(req.environ, conf=Config({
|
||||||
'storage_domain': 's3.amazonaws.com'}))
|
'storage_domains': ['s3.amazonaws.com']}))
|
||||||
expected_sts = b'\n'.join([
|
expected_sts = b'\n'.join([
|
||||||
b'GET',
|
b'GET',
|
||||||
b'',
|
b'',
|
||||||
@@ -769,7 +802,7 @@ class TestRequest(S3ApiTestCase):
|
|||||||
'MyyxeRY7whkBe+bq8fHCL/2kKUg='),
|
'MyyxeRY7whkBe+bq8fHCL/2kKUg='),
|
||||||
})
|
})
|
||||||
sigv2_req = S3Request(req.environ, conf=Config({
|
sigv2_req = S3Request(req.environ, conf=Config({
|
||||||
'storage_domain': 's3.amazonaws.com'}))
|
'storage_domains': ['s3.amazonaws.com']}))
|
||||||
expected_sts = b'\n'.join([
|
expected_sts = b'\n'.join([
|
||||||
b'PUT',
|
b'PUT',
|
||||||
b'',
|
b'',
|
||||||
@@ -790,7 +823,7 @@ class TestRequest(S3ApiTestCase):
|
|||||||
'htDYFYduRNen8P9ZfE/s9SuKy0U='),
|
'htDYFYduRNen8P9ZfE/s9SuKy0U='),
|
||||||
})
|
})
|
||||||
sigv2_req = S3Request(req.environ, conf=Config({
|
sigv2_req = S3Request(req.environ, conf=Config({
|
||||||
'storage_domain': 's3.amazonaws.com'}))
|
'storage_domains': ['s3.amazonaws.com']}))
|
||||||
expected_sts = b'\n'.join([
|
expected_sts = b'\n'.join([
|
||||||
b'GET',
|
b'GET',
|
||||||
b'',
|
b'',
|
||||||
@@ -819,7 +852,7 @@ class TestRequest(S3ApiTestCase):
|
|||||||
'bWq2s1WEIj+Ydj0vQ697zp+IXMU='),
|
'bWq2s1WEIj+Ydj0vQ697zp+IXMU='),
|
||||||
})
|
})
|
||||||
sigv2_req = S3Request(req.environ, Config({
|
sigv2_req = S3Request(req.environ, Config({
|
||||||
'storage_domain': 's3.amazonaws.com'}))
|
'storage_domains': ['s3.amazonaws.com']}))
|
||||||
# This is a failure case with utf-8 non-ascii multi-bytes charactor
|
# This is a failure case with utf-8 non-ascii multi-bytes charactor
|
||||||
# but we expect to return just False instead of exceptions
|
# but we expect to return just False instead of exceptions
|
||||||
self.assertFalse(sigv2_req.check_signature(
|
self.assertFalse(sigv2_req.check_signature(
|
||||||
@@ -837,7 +870,7 @@ class TestRequest(S3ApiTestCase):
|
|||||||
'X-Amz-Date': amz_date_header
|
'X-Amz-Date': amz_date_header
|
||||||
})
|
})
|
||||||
sigv4_req = SigV4Request(
|
sigv4_req = SigV4Request(
|
||||||
req.environ, Config({'storage_domain': 's3.amazonaws.com'}))
|
req.environ, Config({'storage_domains': ['s3.amazonaws.com']}))
|
||||||
self.assertFalse(sigv4_req.check_signature(
|
self.assertFalse(sigv4_req.check_signature(
|
||||||
u'\u30c9\u30e9\u30b4\u30f3'))
|
u'\u30c9\u30e9\u30b4\u30f3'))
|
||||||
|
|
||||||
@@ -858,7 +891,7 @@ class TestRequest(S3ApiTestCase):
|
|||||||
'X-Amz-Date': '20210104T102623Z'}
|
'X-Amz-Date': '20210104T102623Z'}
|
||||||
|
|
||||||
# Virtual hosted-style
|
# Virtual hosted-style
|
||||||
self.s3api.conf.storage_domain = 's3.test.com'
|
self.s3api.conf.storage_domains = ['s3.test.com']
|
||||||
req = Request.blank('/', environ=environ, headers=headers)
|
req = Request.blank('/', environ=environ, headers=headers)
|
||||||
sigv4_req = SigV4Request(req.environ)
|
sigv4_req = SigV4Request(req.environ)
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
@@ -868,7 +901,7 @@ class TestRequest(S3ApiTestCase):
|
|||||||
@patch.object(S3Request, '_validate_dates', lambda *a: None)
|
@patch.object(S3Request, '_validate_dates', lambda *a: None)
|
||||||
def test_check_sigv4_req_zero_content_length_sha256(self):
|
def test_check_sigv4_req_zero_content_length_sha256(self):
|
||||||
# Virtual hosted-style
|
# Virtual hosted-style
|
||||||
self.s3api.conf.storage_domain = 's3.test.com'
|
self.s3api.conf.storage_domains = ['s3.test.com']
|
||||||
|
|
||||||
# bad sha256
|
# bad sha256
|
||||||
environ = {
|
environ = {
|
||||||
|
@@ -133,7 +133,7 @@ class TestS3ApiUtils(unittest.TestCase):
|
|||||||
class TestConfig(unittest.TestCase):
|
class TestConfig(unittest.TestCase):
|
||||||
|
|
||||||
def _assert_defaults(self, conf):
|
def _assert_defaults(self, conf):
|
||||||
self.assertEqual('', conf.storage_domain)
|
self.assertEqual([], conf.storage_domains)
|
||||||
self.assertEqual('us-east-1', conf.location)
|
self.assertEqual('us-east-1', conf.location)
|
||||||
self.assertFalse(conf.force_swift_request_proxy_log)
|
self.assertFalse(conf.force_swift_request_proxy_log)
|
||||||
self.assertTrue(conf.dns_compliant_bucket_names)
|
self.assertTrue(conf.dns_compliant_bucket_names)
|
||||||
@@ -146,7 +146,7 @@ class TestConfig(unittest.TestCase):
|
|||||||
# deliberately brittle so new defaults will need to be added to test
|
# deliberately brittle so new defaults will need to be added to test
|
||||||
conf = utils.Config()
|
conf = utils.Config()
|
||||||
self._assert_defaults(conf)
|
self._assert_defaults(conf)
|
||||||
del conf.storage_domain
|
del conf.storage_domains
|
||||||
del conf.location
|
del conf.location
|
||||||
del conf.force_swift_request_proxy_log
|
del conf.force_swift_request_proxy_log
|
||||||
del conf.dns_compliant_bucket_names
|
del conf.dns_compliant_bucket_names
|
||||||
|
Reference in New Issue
Block a user