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:
@@ -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()
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user