Store swift images in separate containers.

Updates the swift storage backend so that when using multi tenant
each images gets stored in a separate container.

As part of this change the unit tests were updated to generate unique
swift container names for each test. This acts as a container prefix
in multi-tenant mode so the tests were also updated to cleanup all
swift containers matching the container name prefix as well.

The motivation for this change is so that we can set swift read/write
permissions on each individual image.

Partially implements blueprint: swift-tenant-specific-storage.

Change-Id: I43f33594fb40dee33ef52e8ae873722e20b09a10
This commit is contained in:
Dan Prince
2012-07-01 08:03:56 -04:00
parent c713fb3385
commit 6668a61119
2 changed files with 37 additions and 14 deletions

View File

@@ -426,13 +426,22 @@ class Store(glance.store.base.Store):
self.full_auth_address, self.user, self.key,
storage_url=self.storage_url, token=self.token)
create_container_if_missing(self.container, swift_conn)
obj_name = str(image_id)
if self.multi_tenant:
# NOTE: When using multi-tenant we create containers for each
# image so we can set permissions on each image in swift
container = self.container + '_' + obj_name
auth_or_store_url = self.storage_url
else:
container = self.container
auth_or_store_url = self.auth_address
create_container_if_missing(container, swift_conn)
location = StoreLocation({'scheme': self.scheme,
'container': self.container,
'container': container,
'obj': obj_name,
'auth_or_store_url': self.auth_address,
'auth_or_store_url': auth_or_store_url,
'user': self.user,
'key': self.key})
@@ -442,7 +451,7 @@ class Store(glance.store.base.Store):
if image_size > 0 and image_size < self.large_object_size:
# Image size is known, and is less than large_object_size.
# Send to Swift with regular PUT.
obj_etag = swift_conn.put_object(self.container, obj_name,
obj_etag = swift_conn.put_object(container, obj_name,
image_file,
content_length=image_size)
else:
@@ -477,7 +486,7 @@ class Store(glance.store.base.Store):
chunk_name = "%s-%05d" % (obj_name, chunk_id)
reader = ChunkReader(image_file, checksum, chunk_size)
chunk_etag = swift_conn.put_object(
self.container, chunk_name, reader,
container, chunk_name, reader,
content_length=content_length)
bytes_read = reader.bytes_read
msg = _("Wrote chunk %(chunk_name)s (%(chunk_id)d/"
@@ -490,7 +499,7 @@ class Store(glance.store.base.Store):
# Delete the last chunk, because it's of zero size.
# This will happen if image_size == 0.
LOG.debug(_("Deleting final zero-length chunk"))
swift_conn.delete_object(self.container, chunk_name)
swift_conn.delete_object(container, chunk_name)
break
chunk_id += 1
@@ -503,7 +512,7 @@ class Store(glance.store.base.Store):
# Now we write the object manifest and return the
# manifest's etag...
manifest = "%s/%s" % (self.container, obj_name)
manifest = "%s/%s" % (container, obj_name)
headers = {'ETag': hashlib.md5("").hexdigest(),
'X-Object-Manifest': manifest}
@@ -512,7 +521,7 @@ class Store(glance.store.base.Store):
# of each chunk...so we ignore this result in favour of
# the MD5 of the entire image file contents, so that
# users can verify the image file contents accordingly
swift_conn.put_object(self.container, obj_name,
swift_conn.put_object(container, obj_name,
None, headers=headers)
obj_etag = checksum.hexdigest()
@@ -571,6 +580,11 @@ class Store(glance.store.base.Store):
else:
swift_conn.delete_object(loc.container, loc.obj)
if self.multi_tenant:
#NOTE: In multi-tenant mode containers are specific to
# each object (Glance image)
swift_conn.delete_container(loc.container)
except swiftclient.ClientException, e:
if e.http_status == httplib.NOT_FOUND:
uri = location.get_store_uri()

View File

@@ -97,6 +97,10 @@ def get_http_uri(test, image_id):
return uri
def _uniq(value):
return '%s.%d' % (value, random.randint(0, 99999))
def setup_swift(test):
# Test machines can set the GLANCE_TEST_SWIFT_CONF variable
# to override the location of the config file for migration testing
@@ -114,7 +118,11 @@ def setup_swift(test):
cp.read(CONFIG_FILE_PATH)
defaults = cp.defaults()
for key, value in defaults.items():
test.__dict__[key] = value
if key == 'swift_store_container':
test.__dict__[key] = (_uniq(value))
else:
test.__dict__[key] = value
except ConfigParser.ParsingError, e:
test.disabled_message = ("Failed to read test_swift.conf "
"file. Got error: %s" % e)
@@ -173,7 +181,11 @@ def teardown_swift(test):
if not test.disabled:
import swiftclient
try:
test.swift_conn.delete_container(test.swift_store_container)
_resp_headers, containers = swift_conn.get_account()
# Delete all containers matching the container name prefix
for container in containers:
if container.find(container_name) == 0:
swift_conn.delete_container(container)
except swiftclient.ClientException, e:
if e.http_status == httplib.CONFLICT:
pass
@@ -203,9 +215,6 @@ def setup_s3(test):
test.disabled = True
return
def _uniq(value):
return '%s.%d' % (value, random.randint(0, 99999))
if os.path.exists(CONFIG_FILE_PATH):
cp = ConfigParser.RawConfigParser()
try: