diff --git a/etc/glance-api.conf b/etc/glance-api.conf index 1465156b20..4dd6e5f556 100644 --- a/etc/glance-api.conf +++ b/etc/glance-api.conf @@ -261,6 +261,12 @@ s3_store_create_bucket_on_put = False # will be used. If required, an alternative directory can be specified here. #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 ============================= # Ceph configuration file path diff --git a/glance/store/s3.py b/glance/store/s3.py index ab1186cad4..87090fb033 100644 --- a/glance/store/s3.py +++ b/glance/store/s3.py @@ -40,6 +40,7 @@ s3_opts = [ cfg.StrOpt('s3_store_bucket'), cfg.StrOpt('s3_store_object_buffer_dir'), cfg.BoolOpt('s3_store_create_bucket_on_put', default=False), + cfg.StrOpt('s3_store_bucket_url_format', default='subdomain'), ] CONF = cfg.CONF @@ -288,7 +289,8 @@ class Store(glance.store.base.Store): s3_conn = S3Connection(loc.accesskey, loc.secretkey, 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) key = get_key(bucket_obj, loc.key) @@ -336,7 +338,8 @@ class Store(glance.store.base.Store): s3_conn = S3Connection(loc.accesskey, loc.secretkey, 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) @@ -415,7 +418,8 @@ class Store(glance.store.base.Store): from boto.s3.connection import S3Connection s3_conn = S3Connection(loc.accesskey, loc.secretkey, 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) # Close the key when we're through. @@ -510,3 +514,13 @@ def get_key(bucket, obj): LOG.error(msg) raise exception.NotFound(msg) 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() diff --git a/glance/tests/functional/__init__.py b/glance/tests/functional/__init__.py index fe63851d81..952d40c6fe 100644 --- a/glance/tests/functional/__init__.py +++ b/glance/tests/functional/__init__.py @@ -210,6 +210,7 @@ class ApiServer(Server): self.s3_store_access_key = "" self.s3_store_secret_key = "" self.s3_store_bucket = "" + self.s3_store_bucket_url_format = "" self.swift_store_auth_address = "" self.swift_store_user = "" 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_secret_key = %(s3_store_secret_key)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_user = %(swift_store_user)s swift_store_key = %(swift_store_key)s diff --git a/glance/tests/functional/store_utils.py b/glance/tests/functional/store_utils.py index c93f09553c..b098460e6a 100644 --- a/glance/tests/functional/store_utils.py +++ b/glance/tests/functional/store_utils.py @@ -27,7 +27,7 @@ import os import random 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 @@ -243,7 +243,11 @@ def setup_s3(test): test.disabled = True 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 try: diff --git a/glance/tests/unit/test_s3_store.py b/glance/tests/unit/test_s3_store.py index 9fd9788cc3..f7f5db6e7c 100644 --- a/glance/tests/unit/test_s3_store.py +++ b/glance/tests/unit/test_s3_store.py @@ -27,6 +27,7 @@ from glance.common import exception from glance.common import utils from glance.openstack.common import cfg 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 import UnsupportedBackend from glance.tests.unit import base @@ -187,6 +188,37 @@ class TestStore(base.StoreClearingUnitTest): data += chunk 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): """ 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: 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))