Add s3_store_bucket_url_format config option
Swift's implementation of the S3 interface doesn't support subdomain access to containers. It requires that the bucket be prepended to the request path. The option 's3_store_bucket_url_format' can be set to either 'path' or 'subdomain' (default) to control how boto forms the bucket url. Fixes bug 997658 Change-Id: Ia6e1e7356eef7ac2267f7738e2f4a7c70dc12eeb
This commit is contained in:
parent
3bfd7bd56c
commit
4de8670bcb
@ -261,6 +261,12 @@ s3_store_create_bucket_on_put = False
|
|||||||
# will be used. If required, an alternative directory can be specified here.
|
# will be used. If required, an alternative directory can be specified here.
|
||||||
#s3_store_object_buffer_dir = /path/to/dir
|
#s3_store_object_buffer_dir = /path/to/dir
|
||||||
|
|
||||||
|
# When forming a bucket url, boto will either set the bucket name as the
|
||||||
|
# subdomain or as the first token of the path. Amazon's S3 service will
|
||||||
|
# accept it as the subdomain, but Swift's S3 middleware requires it be
|
||||||
|
# in the path. Set this to 'path' or 'subdomain' - defaults to 'subdomain'.
|
||||||
|
#s3_store_bucket_url_format = subdomain
|
||||||
|
|
||||||
# ============ RBD Store Options =============================
|
# ============ RBD Store Options =============================
|
||||||
|
|
||||||
# Ceph configuration file path
|
# Ceph configuration file path
|
||||||
|
@ -40,6 +40,7 @@ s3_opts = [
|
|||||||
cfg.StrOpt('s3_store_bucket'),
|
cfg.StrOpt('s3_store_bucket'),
|
||||||
cfg.StrOpt('s3_store_object_buffer_dir'),
|
cfg.StrOpt('s3_store_object_buffer_dir'),
|
||||||
cfg.BoolOpt('s3_store_create_bucket_on_put', default=False),
|
cfg.BoolOpt('s3_store_create_bucket_on_put', default=False),
|
||||||
|
cfg.StrOpt('s3_store_bucket_url_format', default='subdomain'),
|
||||||
]
|
]
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
@ -288,7 +289,8 @@ class Store(glance.store.base.Store):
|
|||||||
|
|
||||||
s3_conn = S3Connection(loc.accesskey, loc.secretkey,
|
s3_conn = S3Connection(loc.accesskey, loc.secretkey,
|
||||||
host=loc.s3serviceurl,
|
host=loc.s3serviceurl,
|
||||||
is_secure=(loc.scheme == 's3+https'))
|
is_secure=(loc.scheme == 's3+https'),
|
||||||
|
calling_format=get_calling_format())
|
||||||
bucket_obj = get_bucket(s3_conn, loc.bucket)
|
bucket_obj = get_bucket(s3_conn, loc.bucket)
|
||||||
|
|
||||||
key = get_key(bucket_obj, loc.key)
|
key = get_key(bucket_obj, loc.key)
|
||||||
@ -336,7 +338,8 @@ class Store(glance.store.base.Store):
|
|||||||
|
|
||||||
s3_conn = S3Connection(loc.accesskey, loc.secretkey,
|
s3_conn = S3Connection(loc.accesskey, loc.secretkey,
|
||||||
host=loc.s3serviceurl,
|
host=loc.s3serviceurl,
|
||||||
is_secure=(loc.scheme == 's3+https'))
|
is_secure=(loc.scheme == 's3+https'),
|
||||||
|
calling_format=get_calling_format())
|
||||||
|
|
||||||
create_bucket_if_missing(self.bucket, s3_conn)
|
create_bucket_if_missing(self.bucket, s3_conn)
|
||||||
|
|
||||||
@ -415,7 +418,8 @@ class Store(glance.store.base.Store):
|
|||||||
from boto.s3.connection import S3Connection
|
from boto.s3.connection import S3Connection
|
||||||
s3_conn = S3Connection(loc.accesskey, loc.secretkey,
|
s3_conn = S3Connection(loc.accesskey, loc.secretkey,
|
||||||
host=loc.s3serviceurl,
|
host=loc.s3serviceurl,
|
||||||
is_secure=(loc.scheme == 's3+https'))
|
is_secure=(loc.scheme == 's3+https'),
|
||||||
|
calling_format=get_calling_format())
|
||||||
bucket_obj = get_bucket(s3_conn, loc.bucket)
|
bucket_obj = get_bucket(s3_conn, loc.bucket)
|
||||||
|
|
||||||
# Close the key when we're through.
|
# Close the key when we're through.
|
||||||
@ -510,3 +514,13 @@ def get_key(bucket, obj):
|
|||||||
LOG.error(msg)
|
LOG.error(msg)
|
||||||
raise exception.NotFound(msg)
|
raise exception.NotFound(msg)
|
||||||
return key
|
return key
|
||||||
|
|
||||||
|
|
||||||
|
def get_calling_format(bucket_format=None):
|
||||||
|
import boto.s3.connection
|
||||||
|
if bucket_format is None:
|
||||||
|
bucket_format = CONF.s3_store_bucket_url_format
|
||||||
|
if bucket_format.lower() == 'path':
|
||||||
|
return boto.s3.connection.OrdinaryCallingFormat()
|
||||||
|
else:
|
||||||
|
return boto.s3.connection.SubdomainCallingFormat()
|
||||||
|
@ -210,6 +210,7 @@ class ApiServer(Server):
|
|||||||
self.s3_store_access_key = ""
|
self.s3_store_access_key = ""
|
||||||
self.s3_store_secret_key = ""
|
self.s3_store_secret_key = ""
|
||||||
self.s3_store_bucket = ""
|
self.s3_store_bucket = ""
|
||||||
|
self.s3_store_bucket_url_format = ""
|
||||||
self.swift_store_auth_address = ""
|
self.swift_store_auth_address = ""
|
||||||
self.swift_store_user = ""
|
self.swift_store_user = ""
|
||||||
self.swift_store_key = ""
|
self.swift_store_key = ""
|
||||||
@ -254,6 +255,7 @@ s3_store_host = %(s3_store_host)s
|
|||||||
s3_store_access_key = %(s3_store_access_key)s
|
s3_store_access_key = %(s3_store_access_key)s
|
||||||
s3_store_secret_key = %(s3_store_secret_key)s
|
s3_store_secret_key = %(s3_store_secret_key)s
|
||||||
s3_store_bucket = %(s3_store_bucket)s
|
s3_store_bucket = %(s3_store_bucket)s
|
||||||
|
s3_store_bucket_url_format = %(s3_store_bucket_url_format)s
|
||||||
swift_store_auth_address = %(swift_store_auth_address)s
|
swift_store_auth_address = %(swift_store_auth_address)s
|
||||||
swift_store_user = %(swift_store_user)s
|
swift_store_user = %(swift_store_user)s
|
||||||
swift_store_key = %(swift_store_key)s
|
swift_store_key = %(swift_store_key)s
|
||||||
|
@ -27,7 +27,7 @@ import os
|
|||||||
import random
|
import random
|
||||||
import thread
|
import thread
|
||||||
|
|
||||||
from glance.store.s3 import get_s3_location
|
from glance.store.s3 import get_s3_location, get_calling_format
|
||||||
|
|
||||||
|
|
||||||
FIVE_KB = 5 * 1024
|
FIVE_KB = 5 * 1024
|
||||||
@ -243,7 +243,11 @@ def setup_s3(test):
|
|||||||
test.disabled = True
|
test.disabled = True
|
||||||
return
|
return
|
||||||
|
|
||||||
s3_conn = S3Connection(access_key, secret_key, host=s3_host)
|
calling_format = get_calling_format(test.s3_store_bucket_url_format)
|
||||||
|
s3_conn = S3Connection(access_key, secret_key,
|
||||||
|
host=s3_host,
|
||||||
|
is_secure=False,
|
||||||
|
calling_format=calling_format)
|
||||||
|
|
||||||
test.bucket = None
|
test.bucket = None
|
||||||
try:
|
try:
|
||||||
|
@ -27,6 +27,7 @@ from glance.common import exception
|
|||||||
from glance.common import utils
|
from glance.common import utils
|
||||||
from glance.openstack.common import cfg
|
from glance.openstack.common import cfg
|
||||||
from glance.store.location import get_location_from_uri
|
from glance.store.location import get_location_from_uri
|
||||||
|
import glance.store.s3
|
||||||
from glance.store.s3 import Store, get_s3_location
|
from glance.store.s3 import Store, get_s3_location
|
||||||
from glance.store import UnsupportedBackend
|
from glance.store import UnsupportedBackend
|
||||||
from glance.tests.unit import base
|
from glance.tests.unit import base
|
||||||
@ -187,6 +188,37 @@ class TestStore(base.StoreClearingUnitTest):
|
|||||||
data += chunk
|
data += chunk
|
||||||
self.assertEqual(expected_data, data)
|
self.assertEqual(expected_data, data)
|
||||||
|
|
||||||
|
def test_get_calling_format_path(self):
|
||||||
|
"""Test a "normal" retrieval of an image in chunks"""
|
||||||
|
self.config(s3_store_bucket_url_format='path')
|
||||||
|
|
||||||
|
def fake_S3Connection_init(*args, **kwargs):
|
||||||
|
expected_cls = boto.s3.connection.OrdinaryCallingFormat
|
||||||
|
self.assertTrue(isinstance(kwargs.get('calling_format'),
|
||||||
|
expected_cls))
|
||||||
|
|
||||||
|
self.stubs.Set(boto.s3.connection.S3Connection, '__init__',
|
||||||
|
fake_S3Connection_init)
|
||||||
|
|
||||||
|
loc = get_location_from_uri(
|
||||||
|
"s3://user:key@auth_address/glance/%s" % FAKE_UUID)
|
||||||
|
(image_s3, image_size) = self.store.get(loc)
|
||||||
|
|
||||||
|
def test_get_calling_format_default(self):
|
||||||
|
"""Test a "normal" retrieval of an image in chunks"""
|
||||||
|
|
||||||
|
def fake_S3Connection_init(*args, **kwargs):
|
||||||
|
expected_cls = boto.s3.connection.SubdomainCallingFormat
|
||||||
|
self.assertTrue(isinstance(kwargs.get('calling_format'),
|
||||||
|
expected_cls))
|
||||||
|
|
||||||
|
self.stubs.Set(boto.s3.connection.S3Connection, '__init__',
|
||||||
|
fake_S3Connection_init)
|
||||||
|
|
||||||
|
loc = get_location_from_uri(
|
||||||
|
"s3://user:key@auth_address/glance/%s" % FAKE_UUID)
|
||||||
|
(image_s3, image_size) = self.store.get(loc)
|
||||||
|
|
||||||
def test_get_non_existing(self):
|
def test_get_non_existing(self):
|
||||||
"""
|
"""
|
||||||
Test that trying to retrieve a s3 that doesn't exist
|
Test that trying to retrieve a s3 that doesn't exist
|
||||||
@ -373,3 +405,17 @@ class TestStore(base.StoreClearingUnitTest):
|
|||||||
]
|
]
|
||||||
for (url, expected) in bad_locations:
|
for (url, expected) in bad_locations:
|
||||||
self._do_test_get_s3_location(url, expected)
|
self._do_test_get_s3_location(url, expected)
|
||||||
|
|
||||||
|
def test_calling_format_path(self):
|
||||||
|
self.config(s3_store_bucket_url_format='path')
|
||||||
|
self.assertTrue(isinstance(glance.store.s3.get_calling_format(),
|
||||||
|
boto.s3.connection.OrdinaryCallingFormat))
|
||||||
|
|
||||||
|
def test_calling_format_subdomain(self):
|
||||||
|
self.config(s3_store_bucket_url_format='subdomain')
|
||||||
|
self.assertTrue(isinstance(glance.store.s3.get_calling_format(),
|
||||||
|
boto.s3.connection.SubdomainCallingFormat))
|
||||||
|
|
||||||
|
def test_calling_format_default(self):
|
||||||
|
self.assertTrue(isinstance(glance.store.s3.get_calling_format(),
|
||||||
|
boto.s3.connection.SubdomainCallingFormat))
|
||||||
|
Loading…
Reference in New Issue
Block a user