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:
Brian Waldon 2012-09-10 21:21:51 -07:00
parent 3bfd7bd56c
commit 4de8670bcb
5 changed files with 77 additions and 5 deletions

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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:

View File

@ -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))