Merge "Allow storage drivers to add metadata to locations"

This commit is contained in:
Jenkins 2013-07-12 19:13:04 +00:00 committed by Gerrit Code Review
commit de463e9ff8
36 changed files with 918 additions and 238 deletions

View File

@ -150,6 +150,7 @@ class CacheFilter(wsgi.Middleware):
# Don't display location
if 'location' in image_meta:
del image_meta['location']
image_meta.pop('location_data', None)
self._verify_metadata(image_meta)
response = webob.Response(request=request)

View File

@ -99,17 +99,18 @@ def validate_image_meta(req, values):
return values
def redact_loc(image_meta):
def redact_loc(image_meta, copy_dict=True):
"""
Create a shallow copy of image meta with 'location' removed
for security (as it can contain credentials).
"""
if 'location' in image_meta:
tmp_image_meta = copy.copy(image_meta)
del tmp_image_meta['location']
return tmp_image_meta
return image_meta
if copy_dict:
new_image_meta = copy.copy(image_meta)
else:
new_image_meta = image_meta
new_image_meta.pop('location', None)
new_image_meta.pop('location_data', None)
return new_image_meta
class Controller(controller.BaseController):
@ -210,7 +211,7 @@ class Controller(controller.BaseController):
# to it, however we do not return this potential security
# information to the API end user...
for image in images:
del image['location']
redact_loc(image, copy_dict=False)
except exception.Invalid as e:
raise HTTPBadRequest(explanation="%s" % e)
return dict(images=images)
@ -263,7 +264,7 @@ class Controller(controller.BaseController):
"""
self._enforce(req, 'get_image')
image_meta = self.get_image_meta_or_404(req, id)
del image_meta['location']
image_meta = redact_loc(image_meta)
return {
'image_meta': image_meta
}
@ -329,7 +330,7 @@ class Controller(controller.BaseController):
image_iterator = utils.cooperative_iter(image_iterator)
image_meta['size'] = size or image_meta['size']
del image_meta['location']
image_meta = redact_loc(image_meta)
return {
'image_iterator': image_iterator,
'image_meta': image_meta,
@ -443,17 +444,14 @@ class Controller(controller.BaseController):
self.notifier.info("image.prepare", redact_loc(image_meta))
image_meta, location = upload_utils.upload_data_to_store(req,
image_meta,
image_data,
store,
self.notifier)
image_meta, location, loc_meta = upload_utils.upload_data_to_store(
req, image_meta, image_data, store, self.notifier)
self.notifier.info('image.upload', redact_loc(image_meta))
return location
return location, loc_meta
def _activate(self, req, image_id, location):
def _activate(self, req, image_id, location, location_metadata=None):
"""
Sets the image status to `active` and the image's location
attribute.
@ -461,10 +459,14 @@ class Controller(controller.BaseController):
:param req: The WSGI/Webob Request object
:param image_id: Opaque image identifier
:param location: Location of where Glance stored this image
:param location_metadata: a dictionary of storage specfic information
"""
image_meta = {}
image_meta['location'] = location
image_meta['status'] = 'active'
if location_metadata:
image_meta['location_data'] = [{'url': location,
'metadata': location_metadata}]
try:
image_meta_data = registry.update_image_metadata(req.context,
@ -497,8 +499,11 @@ class Controller(controller.BaseController):
# See: https://bitbucket.org/ianb/webob/
# issue/12/fix-for-issue-6-broke-chunked-transfer
req.is_body_readable = True
location = self._upload(req, image_meta)
return self._activate(req, image_id, location) if location else None
location, location_metadata = self._upload(req, image_meta)
return self._activate(req,
image_id,
location,
location_metadata) if location else None
def _get_size(self, context, image_meta, location):
# retrieve the image size from remote store (if not provided)
@ -604,7 +609,7 @@ class Controller(controller.BaseController):
# Prevent client from learning the location, as it
# could contain security credentials
image_meta.pop('location', None)
image_meta = redact_loc(image_meta)
return {'image_meta': image_meta}
@ -710,7 +715,7 @@ class Controller(controller.BaseController):
# Prevent client from learning the location, as it
# could contain security credentials
image_meta.pop('location', None)
image_meta = redact_loc(image_meta)
return {'image_meta': image_meta}

View File

@ -80,10 +80,14 @@ def upload_data_to_store(req, image_meta, image_data, store, notifier):
"""
image_id = image_meta['id']
try:
location, size, checksum = store.add(
image_meta['id'],
utils.CooperativeReader(image_data),
image_meta['size'])
(location,
size,
checksum,
locations_metadata) = glance.store.store_add_to_backend(
image_meta['id'],
utils.CooperativeReader(image_data),
image_meta['size'],
store)
def _kill_mismatched(image_meta, attr, actual):
supplied = image_meta.get(attr)
@ -185,4 +189,4 @@ def upload_data_to_store(req, image_meta, image_data, store, notifier):
request=req,
content_type='text/plain')
return image_meta, location
return image_meta, location, locations_metadata

View File

@ -431,8 +431,11 @@ class ResponseSerializer(wsgi.JSONResponseSerializer):
image_view['id'] = image.image_id
image_view['created_at'] = timeutils.isotime(image.created_at)
image_view['updated_at'] = timeutils.isotime(image.updated_at)
if CONF.show_image_direct_url and image.locations: # domain
image_view['direct_url'] = image.locations[0]
if image.locations:
if CONF.show_multiple_locations:
image_view['locations'] = list(image.locations)
if CONF.show_image_direct_url:
image_view['direct_url'] = image.locations[0]['url']
image_view['tags'] = list(image.tags)
image_view['self'] = self._get_image_href(image)
image_view['file'] = self._get_image_href(image, 'file')
@ -566,6 +569,19 @@ _BASE_PROPERTIES = {
'self': {'type': 'string'},
'file': {'type': 'string'},
'schema': {'type': 'string'},
'locations': {
'type': 'array',
'items': {
'url': {
'type': 'string',
'maxLength': 255,
},
'metadata': {},
},
'description': _('A set of URLs to access the image file kept in '
'external store'),
},
}
_BASE_LINKS = [

View File

@ -57,6 +57,11 @@ common_opts = [
help=_('Whether to include the backend image storage location '
'in image properties. Revealing storage location can be a '
'security risk, so use this setting with caution!')),
cfg.BoolOpt('show_multiple_locations', default=False,
help=_('Whether to include the backend image locations '
'in image properties. Revealing storage location can '
'be a security risk, so use this setting with '
'caution! The overrides show_image_direct_url.')),
cfg.IntOpt('image_size_cap', default=1099511627776,
help=_("Maximum size of image a user can upload in bytes. "
"Defaults to 1099511627776 bytes (1 TB).")),

View File

@ -86,7 +86,11 @@ class ImageRepo(object):
locations = db_image['locations']
if CONF.metadata_encryption_key:
key = CONF.metadata_encryption_key
locations = [crypt.urlsafe_decrypt(key, l) for l in locations]
ld = []
for l in locations:
url = crypt.urlsafe_decrypt(key, l['url'])
ld.append({'url': url, 'metadata': l['metadata']})
locations = ld
return glance.domain.Image(
image_id=db_image['id'],
name=db_image['name'],
@ -111,7 +115,11 @@ class ImageRepo(object):
locations = image.locations
if CONF.metadata_encryption_key:
key = CONF.metadata_encryption_key
locations = [crypt.urlsafe_encrypt(key, l) for l in locations]
ld = []
for l in locations:
url = crypt.urlsafe_encrypt(key, l['url'])
ld.append({'url': url, 'metadata': l['metadata']})
locations = ld
return {
'id': image.image_id,
'name': image.name,

View File

@ -382,8 +382,10 @@ def image_create(context, image_values):
'created_at', 'updated_at', 'deleted_at', 'deleted',
'properties', 'tags'])
if set(image_values.keys()) - allowed_keys:
raise exception.Invalid()
incorrect_keys = set(image_values.keys()) - allowed_keys
if incorrect_keys:
raise exception.Invalid(
'The keys %s are not valid' % str(incorrect_keys))
image = _image_format(image_id, **image_values)
DATA['images'][image_id] = image

View File

@ -300,7 +300,9 @@ def image_destroy(context, image_id):
def _normalize_locations(image):
undeleted_locations = filter(lambda x: not x.deleted, image['locations'])
image['locations'] = [loc['value'] for loc in undeleted_locations]
image['locations'] = [{'url': loc['value'],
'metadata': loc['meta_data']}
for loc in undeleted_locations]
return image
@ -707,6 +709,10 @@ def _image_update(context, values, image_id, purge_props=False):
:param values: A dict of attributes to set
:param image_id: If None, create the image, otherwise, find and update it
"""
#NOTE(jbresnah) values is altered in this so a copy is needed
values = values.copy()
session = _get_session()
with session.begin():
@ -718,11 +724,7 @@ def _image_update(context, values, image_id, purge_props=False):
# not a dict.
properties = values.pop('properties', {})
try:
locations = values.pop('locations')
locations_provided = True
except KeyError:
locations_provided = False
location_data = values.pop('locations', None)
if image_id:
image_ref = _image_get(context, image_id, session=session)
@ -771,8 +773,8 @@ def _image_update(context, values, image_id, purge_props=False):
_set_properties_for_image(context, image_ref, properties, purge_props,
session)
if locations_provided:
_image_locations_set(image_ref.id, locations, session)
if location_data is not None:
_image_locations_set(image_ref.id, location_data, session)
return image_get(context, image_ref.id)
@ -786,7 +788,9 @@ def _image_locations_set(image_id, locations, session):
location_ref.delete(session=session)
for location in locations:
location_ref = models.ImageLocation(image_id=image_id, value=location)
location_ref = models.ImageLocation(image_id=image_id,
value=location['url'],
meta_data=location['metadata'])
location_ref.save()

View File

@ -52,6 +52,42 @@ SUPPORTED_SORT_DIRS = ('asc', 'desc')
SUPPORTED_PARAMS = ('limit', 'marker', 'sort_key', 'sort_dir')
def _normalize_image_location_for_db(image_data):
"""
This function takes the legacy locations field and the newly added
location_data field from the image_data values dictionary which flows
over the wire between the registry and API servers and converts it
into the location_data format only which is then consumable by the
Image object.
:param image_data: a dict of values representing information in the image
:return: a new image data dict
"""
if 'locations' not in image_data and 'location_data' not in image_data:
image_data['locations'] = None
return image_data
locations = image_data.pop('locations', [])
location_data = image_data.pop('location_data', [])
location_data_dict = {}
for l in locations:
location_data_dict[l] = {}
for l in location_data:
location_data_dict[l['url']] = l['metadata']
# NOTE(jbresnah) preserve original order. tests assume original order,
# should that be defined functionality
ordered_keys = locations[:]
for ld in location_data:
if ld['url'] not in ordered_keys:
ordered_keys.append(ld['url'])
location_data = [{'url': l, 'metadata': location_data_dict[l]}
for l in ordered_keys]
image_data['locations'] = location_data
return image_data
class Controller(object):
def __init__(self):
@ -359,6 +395,7 @@ class Controller(object):
image_data['locations'] = [image_data.pop('location')]
try:
image_data = _normalize_image_location_for_db(image_data)
image_data = self.db_api.image_create(req.context, image_data)
msg = _("Successfully created image %(id)s")
LOG.info(msg % {'id': image_id})
@ -397,6 +434,7 @@ class Controller(object):
try:
LOG.debug(_("Updating image %(id)s with metadata: "
"%(image_data)r") % locals())
image_data = _normalize_image_location_for_db(image_data)
if purge_props == "true":
updated_image = self.db_api.image_update(req.context, id,
image_data, True)
@ -434,9 +472,10 @@ class Controller(object):
def _limit_locations(image):
locations = image.pop('locations', [])
try:
image['location'] = locations[0]
image['location'] = locations[0]['url']
except IndexError:
image['location'] = None
image['location_data'] = locations
def make_image_dict(image):

View File

@ -304,9 +304,58 @@ def delete_image_from_backend(context, store_api, image_id, uri):
store_api.safe_delete_from_backend(uri, context, image_id)
def _check_meta_data(val, key=''):
t = type(val)
if t == dict:
for key in val:
_check_meta_data(val[key], key=key)
elif t == list:
ndx = 0
for v in val:
_check_meta_data(v, key='%s[%d]' % (key, ndx))
ndx = ndx + 1
elif t != unicode:
raise BackendException(_("The image metadata key %s has an invalid "
"type of %s. Only dict, list, and unicode "
"are supported." % (key, str(t))))
def store_add_to_backend(image_id, data, size, store):
"""
A wrapper around a call to each stores add() method. This gives glance
a common place to check the output
:param image_id: The image add to which data is added
:param data: The data to be stored
:param size: The length of the data in bytes
:param store: The store to which the data is being added
:return: The url location of the file,
the size amount of data,
the checksum of the data
the storage systems metadata dictionary for the location
"""
(location, size, checksum, metadata) = store.add(image_id, data, size)
if metadata is not None:
if type(metadata) != dict:
msg = _("The storage driver %s returned invalid metadata %s"
"This must be a dictionary type" %
(str(store), str(metadata)))
LOG.error(msg)
raise BackendException(msg)
try:
_check_meta_data(metadata)
except BackendException as e:
e_msg = _("A bad metadata structure was returned from the "
"%s storage driver: %s. %s." %
(str(store), str(metadata), str(e)))
LOG.error(e_msg)
raise BackendException(e_msg)
return (location, size, checksum, metadata)
def add_to_backend(context, scheme, image_id, data, size):
store = get_store_from_scheme(context, scheme)
return store.add(image_id, data, size)
return store_add_to_backend(image_id, data, size, store)
def set_acls(context, location_uri, public=False, read_tenants=[],
@ -338,7 +387,7 @@ class ImageRepoProxy(glance.domain.proxy.Repo):
member_repo = image.get_member_repo()
member_ids = [m.member_id for m in member_repo.list()]
for location in image.locations:
self.store_api.set_acls(self.context, location, public,
self.store_api.set_acls(self.context, location['url'], public,
read_tenants=member_ids)
def add(self, image):
@ -381,8 +430,8 @@ class ImageFactoryProxy(glance.domain.proxy.ImageFactory):
proxy_kwargs=proxy_kwargs)
def new_image(self, **kwargs):
for uri in kwargs.get('locations', []):
_check_location_uri(self.context, self.store_api, uri)
for l in kwargs.get('locations', []):
_check_location_uri(self.context, self.store_api, l['url'])
return super(ImageFactoryProxy, self).new_image(**kwargs)
@ -400,49 +449,50 @@ class StoreLocations(collections.MutableSequence):
else:
self.value = list(value)
def append(self, uri):
def append(self, location):
_check_location_uri(self.image_proxy.context,
self.image_proxy.store_api, uri)
self.value.append(uri)
self.image_proxy.store_api, location['url'])
self.value.append(location)
def extend(self, other):
if isinstance(other, StoreLocations):
self.value.extend(other.value)
else:
uris = list(other)
for uri in uris:
locations = list(other)
for location in locations:
_check_location_uri(self.image_proxy.context,
self.image_proxy.store_api, uri)
self.value.extend(uris)
self.image_proxy.store_api,
location['url'])
self.value.extend(locations)
def insert(self, i, uri):
def insert(self, i, location):
_check_location_uri(self.image_proxy.context,
self.image_proxy.store_api, uri)
self.value.insert(i, uri)
self.image_proxy.store_api, location['url'])
self.value.insert(i, location)
def pop(self, i=-1):
uri = self.value.pop(i)
location = self.value.pop(i)
try:
delete_image_from_backend(self.image_proxy.context,
self.image_proxy.store_api,
self.image_proxy.image.image_id,
uri)
location['url'])
except Exception:
self.value.insert(i, uri)
self.value.insert(i, location)
raise
return uri
return location
def count(self, uri):
return self.value.count(uri)
def count(self, location):
return self.value.count(location)
def index(self, uri, *args):
return self.value.index(uri, *args)
def index(self, location, *args):
return self.value.index(location, *args)
def remove(self, uri):
if self.count(uri):
self.pop(self.index(uri))
def remove(self, location):
if self.count(location):
self.pop(self.index(location))
else:
self.value.remove(uri)
self.value.remove(location)
def reverse(self):
self.value.reverse()
@ -453,51 +503,52 @@ class StoreLocations(collections.MutableSequence):
def __getitem__(self, i):
return self.value.__getitem__(i)
def __setitem__(self, i, uri):
def __setitem__(self, i, location):
_check_location_uri(self.image_proxy.context,
self.image_proxy.store_api, uri)
self.value.__setitem__(i, uri)
self.image_proxy.store_api, location)
self.value.__setitem__(i, location)
def __delitem__(self, i):
uri = None
location = None
try:
uri = self.value.__getitem__(i)
location = self.value.__getitem__(i)
except Exception:
return self.value.__delitem__(i)
delete_image_from_backend(self.image_proxy.context,
self.image_proxy.store_api,
self.image_proxy.image.image_id,
uri)
location['url'])
self.value.__delitem__(i)
def __delslice__(self, i, j):
i = max(i, 0)
j = max(j, 0)
uris = []
locations = []
try:
uris = self.value.__getslice__(i, j)
locations = self.value.__getslice__(i, j)
except Exception:
return self.value.__delslice__(i, j)
for uri in uris:
for location in locations:
delete_image_from_backend(self.image_proxy.context,
self.image_proxy.store_api,
self.image_proxy.image.image_id,
uri)
location['url'])
self.value.__delitem__(i)
def __iadd__(self, other):
if isinstance(other, StoreLocations):
self.value += other.value
else:
uris = list(other)
for uri in uris:
locations = list(other)
for location in locations:
_check_location_uri(self.image_proxy.context,
self.image_proxy.store_api, uri)
self.value += uris
self.image_proxy.store_api,
location['url'])
self.value += locations
return self
def __contains__(self, uri):
return uri in self.value
def __contains__(self, location):
return location in self.value
def __len__(self):
return len(self.value)
@ -578,15 +629,15 @@ class ImageProxy(glance.domain.proxy.Image):
self.store_api.delete_image_from_backend(self.context,
self.store_api,
self.image.image_id,
location)
location['url'])
def set_data(self, data, size=None):
if size is None:
size = 0 # NOTE(markwash): zero -> unknown size
location, size, checksum = self.store_api.add_to_backend(
location, size, checksum, loc_meta = self.store_api.add_to_backend(
self.context, CONF.default_store,
self.image.image_id, utils.CooperativeReader(data), size)
self.image.locations = [location]
self.image.locations = [{'url': location, 'metadata': loc_meta}]
self.image.size = size
self.image.checksum = checksum
self.image.status = 'active'
@ -594,8 +645,8 @@ class ImageProxy(glance.domain.proxy.Image):
def get_data(self):
if not self.image.locations:
raise exception.NotFound(_("No image data could be found"))
data, size = self.store_api.get_from_backend(self.context,
self.image.locations[0])
data, size = self.store_api.get_from_backend(
self.context, self.image.locations[0]['url'])
return data
@ -612,7 +663,7 @@ class ImageMemberRepoProxy(glance.domain.proxy.Repo):
if self.image.locations and not public:
member_ids = [m.member_id for m in self.repo.list()]
for location in self.image.locations:
self.store_api.set_acls(self.context, location, public,
self.store_api.set_acls(self.context, location['url'], public,
read_tenants=member_ids)
def add(self, member):

View File

@ -120,7 +120,8 @@ class Store(object):
:param image_file: The image data to write, as a file-like object
:param image_size: The size of the image data to write, in bytes
:retval tuple of URL in backing store, bytes written, and checksum
:retval tuple of URL in backing store, bytes written, checksum
and a dictionary with storage system specific information
:raises `glance.common.exception.Duplicate` if the image already
existed
"""

View File

@ -212,7 +212,8 @@ class Store(glance.store.base.Store):
:param image_file: The image data to write, as a file-like object
:param image_size: The size of the image data to write, in bytes
:retval tuple of URL in backing store, bytes written, and checksum
:retval tuple of URL in backing store, bytes written, checksum
and a dictionary with storage system specific information
:raises `glance.common.exception.Duplicate` if the image already
existed
@ -252,7 +253,7 @@ class Store(glance.store.base.Store):
LOG.debug(_("Wrote %(bytes_written)d bytes to %(filepath)s with "
"checksum %(checksum_hex)s") % locals())
return ('file://%s' % filepath, bytes_written, checksum_hex)
return ('file://%s' % filepath, bytes_written, checksum_hex, {})
@staticmethod
def _delete_partial(filepath, id):

View File

@ -166,7 +166,8 @@ class Store(glance.store.base.Store):
:param image_file: The image data to write, as a file-like object
:param image_size: The size of the image data to write, in bytes
:retval tuple of URL in backing store, bytes written, and checksum
:retval tuple of URL in backing store, bytes written, checksum
and a dictionary with storage system specific information
:raises `glance.common.exception.Duplicate` if the image already
existed
"""
@ -185,7 +186,7 @@ class Store(glance.store.base.Store):
LOG.debug(_("Uploaded image %s, md5 %s, length %s to GridFS") %
(image._id, image.md5, image.length))
return (loc.get_uri(), image.length, image.md5)
return (loc.get_uri(), image.length, image.md5, {})
def delete(self, location):
"""

View File

@ -256,7 +256,8 @@ class Store(glance.store.base.Store):
:param image_file: The image data to write, as a file-like object
:param image_size: The size of the image data to write, in bytes
:retval tuple of URL in backing store, bytes written, and checksum
:retval tuple of URL in backing store, bytes written, checksum
and a dictionary with storage system specific information
:raises `glance.common.exception.Duplicate` if the image already
existed
"""
@ -286,7 +287,7 @@ class Store(glance.store.base.Store):
image.create_snap(location.snapshot)
image.protect_snap(location.snapshot)
return (location.get_uri(), image_size, checksum.hexdigest())
return (location.get_uri(), image_size, checksum.hexdigest(), {})
def delete(self, location):
"""

View File

@ -325,7 +325,8 @@ class Store(glance.store.base.Store):
:param image_file: The image data to write, as a file-like object
:param image_size: The size of the image data to write, in bytes
:retval tuple of URL in backing store, bytes written, and checksum
:retval tuple of URL in backing store, bytes written, checksum
and a dictionary with storage system specific information
:raises `glance.common.exception.Duplicate` if the image already
existed
@ -414,7 +415,7 @@ class Store(glance.store.base.Store):
LOG.debug(_("Wrote %(size)d bytes to S3 key named %(obj_name)s "
"with checksum %(checksum_hex)s") % locals())
return (loc.get_uri(), size, checksum_hex)
return (loc.get_uri(), size, checksum_hex, {})
def delete(self, location):
"""

View File

@ -279,7 +279,7 @@ class Store(glance.store.base.Store):
left -= length
checksum.update(data)
return (location.get_uri(), image_size, checksum.hexdigest())
return (location.get_uri(), image_size, checksum.hexdigest(), {})
def delete(self, location):
"""

View File

@ -424,7 +424,7 @@ class BaseStore(glance.store.base.Store):
# the location attribute from GET /images/<ID> and
# GET /images/details
return (location.get_uri(), image_size, obj_etag)
return (location.get_uri(), image_size, obj_etag, {})
except swiftclient.ClientException as e:
if e.http_status == httplib.CONFLICT:
raise exception.Duplicate(_("Swift already has an image at "

View File

@ -72,6 +72,7 @@ class Server(object):
self.exec_env = None
self.deployment_flavor = ''
self.show_image_direct_url = False
self.show_multiple_locations = False
self.enable_v1_api = True
self.enable_v2_api = True
self.needs_database = False
@ -357,6 +358,7 @@ policy_default_rule = %(policy_default_rule)s
db_auto_create = False
sql_connection = %(sql_connection)s
show_image_direct_url = %(show_image_direct_url)s
show_multiple_locations = %(show_multiple_locations)s
enable_v1_api = %(enable_v1_api)s
enable_v2_api= %(enable_v2_api)s
[paste_deploy]

View File

@ -53,7 +53,7 @@ def build_image_fixture(**kwargs):
'min_disk': 5,
'min_ram': 256,
'size': 19,
'locations': ["file:///tmp/glance-tests/2"],
'locations': [{'url': "file:///tmp/glance-tests/2", 'metadata': {}}],
'properties': {},
}
image.update(kwargs)
@ -146,9 +146,20 @@ class DriverTests(object):
self.context, {'id': UUID1, 'status': 'queued'})
def test_image_create_with_locations(self):
fixture = {'status': 'queued', 'locations': ['a', 'b']}
locations = [{'url': 'a', 'metadata': {}},
{'url': 'b', 'metadata': {}}]
fixture = {'status': 'queued',
'locations': locations}
image = self.db_api.image_create(self.context, fixture)
self.assertEqual(['a', 'b'], image['locations'])
self.assertEqual(locations, image['locations'])
def test_image_create_with_location_data(self):
location_data = [{'url': 'a', 'metadata': {'key': 'value'}},
{'url': 'b', 'metadata': {}}]
fixture = {'status': 'queued', 'locations': location_data}
image = self.db_api.image_create(self.context, fixture)
self.assertEqual(location_data, image['locations'])
def test_image_create_properties(self):
fixture = {'status': 'queued', 'properties': {'ping': 'pong'}}
@ -170,9 +181,18 @@ class DriverTests(object):
self.assertNotEqual(image['created_at'], image['updated_at'])
def test_image_update_with_locations(self):
fixture = {'locations': ['a', 'b']}
locations = [{'url': 'a', 'metadata': {}},
{'url': 'b', 'metadata': {}}]
fixture = {'locations': locations}
image = self.db_api.image_update(self.adm_context, UUID3, fixture)
self.assertEqual(['a', 'b'], image['locations'])
self.assertEqual(locations, image['locations'])
def test_image_update_with_location_data(self):
location_data = [{'url': 'a', 'metadata': {'key': 'value'}},
{'url': 'b', 'metadata': {}}]
fixture = {'locations': location_data}
image = self.db_api.image_update(self.adm_context, UUID3, fixture)
self.assertEqual(location_data, image['locations'])
def test_image_update(self):
fixture = {'status': 'queued', 'properties': {'ping': 'pong'}}

View File

@ -80,7 +80,7 @@ class BaseTestCase(object):
image_data = StringIO.StringIO('XXX')
image_checksum = 'bc9189406be84ec297464a514221406d'
try:
uri, add_size, add_checksum = store.add(image_id, image_data, 3)
uri, add_size, add_checksum, _ = store.add(image_id, image_data, 3)
except NotImplementedError:
msg = 'Configured store can not add images'
self.skipTest(msg)

View File

@ -719,6 +719,51 @@ class TestImageDirectURLVisibility(functional.FunctionalTest):
self.stop_servers()
def test_image_multiple_location_url_visible(self):
self.api_server.show_multiple_locations = True
self.start_servers(**self.__dict__.copy())
# Create an image
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json'})
data = json.dumps({'name': 'image-1', 'type': 'kernel', 'foo': 'bar',
'disk_format': 'aki', 'container_format': 'aki'})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(201, response.status_code)
# Get the image id
image = json.loads(response.text)
image_id = image['id']
# Image locations should not be visible before location is set
path = self._url('/v2/images/%s' % image_id)
headers = self._headers({'Content-Type': 'application/json'})
response = requests.get(path, headers=headers)
self.assertEqual(200, response.status_code)
image = json.loads(response.text)
self.assertFalse('locations' in image)
# Upload some image data, setting the image location
path = self._url('/v2/images/%s/file' % image_id)
headers = self._headers({'Content-Type': 'application/octet-stream'})
response = requests.put(path, headers=headers, data='ZZZZZ')
self.assertEqual(204, response.status_code)
# Image locations should be visible
path = self._url('/v2/images/%s' % image_id)
headers = self._headers({'Content-Type': 'application/json'})
response = requests.get(path, headers=headers)
self.assertEqual(200, response.status_code)
image = json.loads(response.text)
self.assertTrue('locations' in image)
loc = image['locations']
self.assertTrue(len(loc) > 0)
loc = loc[0]
self.assertTrue('url' in loc)
self.assertTrue('metadata' in loc)
self.stop_servers()
def test_image_direct_url_not_visible(self):
self.api_server.show_image_direct_url = False

View File

@ -51,6 +51,7 @@ class TestSchemas(functional.FunctionalTest):
'status',
'schema',
'direct_url',
'locations',
'min_ram',
'min_disk',
'protected',

View File

@ -208,6 +208,32 @@ class TestCacheMiddlewareProcessRequest(utils.BaseTestCase):
request, image_id, dummy_img_iterator)
self.assertEqual(True, actual)
def test_v1_remove_location_image_fetch(self):
class CheckNoLocationDataSerializer(object):
def show(self, response, raw_response):
return 'location_data' in raw_response['image_meta']
def fake_get_image_metadata(context, image_id):
return {'location_data': {'url': "file:///some/path",
'metadata': {}},
'is_public': True, 'deleted': False, 'size': '20'}
def dummy_img_iterator():
for i in range(3):
yield i
image_id = 'test1'
request = webob.Request.blank('/v1/images/%s' % image_id)
request.context = context.RequestContext()
cache_filter = ProcessRequestTestCacheFilter()
cache_filter.serializer = CheckNoLocationDataSerializer()
self.stubs.Set(registry, 'get_image_metadata',
fake_get_image_metadata)
actual = cache_filter._process_v1_request(
request, image_id, dummy_img_iterator)
self.assertFalse(actual)
def test_verify_metadata_deleted_image(self):
"""
Test verify_metadata raises exception.NotFound for a deleted image

View File

@ -39,6 +39,10 @@ TENANT4 = 'c6c87f25-8a94-47ed-8c83-053c25f42df4'
USER1 = '54492ba0-f4df-4e4e-be62-27f4d76b29cf'
UUID1_LOCATION = 'file:///path/to/image'
UUID1_LOCATION_METADATA = {'key': 'value'}
UUID3_LOCATION = 'http://somehost.com/place'
def _db_fixture(id, **kwargs):
obj = {
@ -89,11 +93,15 @@ class TestImageRepo(test_utils.BaseTestCase):
self.db.reset()
self.images = [
_db_fixture(UUID1, owner=TENANT1, name='1', size=256,
is_public=True, status='active'),
is_public=True, status='active',
locations=[{'url': UUID1_LOCATION,
'metadata': UUID1_LOCATION_METADATA}]),
_db_fixture(UUID2, owner=TENANT1, name='2',
size=512, is_public=False),
_db_fixture(UUID3, owner=TENANT3, name='3',
size=1024, is_public=True),
size=1024, is_public=True,
locations=[{'url': UUID3_LOCATION,
'metadata': {}}]),
_db_fixture(UUID4, owner=TENANT4, name='4', size=2048),
]
[self.db.image_create(None, image) for image in self.images]
@ -118,6 +126,20 @@ class TestImageRepo(test_utils.BaseTestCase):
self.assertEquals(image.size, 256)
self.assertEquals(image.owner, TENANT1)
def test_location_value(self):
image = self.image_repo.get(UUID3)
self.assertEqual(image.locations[0]['url'], UUID3_LOCATION)
def test_location_data_value(self):
image = self.image_repo.get(UUID1)
self.assertEqual(image.locations[0]['url'], UUID1_LOCATION)
self.assertEqual(image.locations[0]['metadata'],
UUID1_LOCATION_METADATA)
def test_location_data_exists(self):
image = self.image_repo.get(UUID2)
self.assertEqual(image.locations, [])
def test_get_not_found(self):
self.assertRaises(exception.NotFound, self.image_repo.get,
uuidutils.generate_uuid())
@ -229,47 +251,59 @@ class TestEncryptedLocations(test_utils.BaseTestCase):
self.image_factory = glance.domain.ImageFactory()
self.crypt_key = '0123456789abcdef'
self.config(metadata_encryption_key=self.crypt_key)
self.foo_bar_location = [{'url': 'foo', 'metadata': {}},
{'url': 'bar', 'metadata': {}}]
def test_encrypt_locations_on_add(self):
image = self.image_factory.new_image(UUID1)
image.locations = ['foo', 'bar']
image.locations = self.foo_bar_location
self.image_repo.add(image)
db_data = self.db.image_get(self.context, UUID1)
self.assertNotEqual(db_data['locations'], ['foo', 'bar'])
decrypted_locations = [crypt.urlsafe_decrypt(self.crypt_key, l)
decrypted_locations = [crypt.urlsafe_decrypt(self.crypt_key, l['url'])
for l in db_data['locations']]
self.assertEqual(decrypted_locations, ['foo', 'bar'])
self.assertEqual(decrypted_locations,
[l['url'] for l in self.foo_bar_location])
def test_encrypt_locations_on_save(self):
image = self.image_factory.new_image(UUID1)
self.image_repo.add(image)
image.locations = ['foo', 'bar']
image.locations = self.foo_bar_location
self.image_repo.save(image)
db_data = self.db.image_get(self.context, UUID1)
self.assertNotEqual(db_data['locations'], ['foo', 'bar'])
decrypted_locations = [crypt.urlsafe_decrypt(self.crypt_key, l)
decrypted_locations = [crypt.urlsafe_decrypt(self.crypt_key, l['url'])
for l in db_data['locations']]
self.assertEqual(decrypted_locations, ['foo', 'bar'])
self.assertEqual(decrypted_locations,
[l['url'] for l in self.foo_bar_location])
def test_decrypt_locations_on_get(self):
encrypted_locations = [crypt.urlsafe_encrypt(self.crypt_key, l)
for l in ['ping', 'pong']]
self.assertNotEqual(encrypted_locations, ['ping', 'pong'])
url_loc = ['ping', 'pong']
orig_locations = [{'url': l, 'metadata': {}} for l in url_loc]
encrypted_locs = [crypt.urlsafe_encrypt(self.crypt_key, l)
for l in url_loc]
encrypted_locations = [{'url': l, 'metadata': {}}
for l in encrypted_locs]
self.assertNotEqual(encrypted_locations, orig_locations)
db_data = _db_fixture(UUID1, owner=TENANT1,
locations=encrypted_locations)
self.db.image_create(None, db_data)
image = self.image_repo.get(UUID1)
self.assertEqual(image.locations, ['ping', 'pong'])
self.assertEqual(image.locations, orig_locations)
def test_decrypt_locations_on_list(self):
encrypted_locations = [crypt.urlsafe_encrypt(self.crypt_key, l)
for l in ['ping', 'pong']]
self.assertNotEqual(encrypted_locations, ['ping', 'pong'])
url_loc = ['ping', 'pong']
orig_locations = [{'url': l, 'metadata': {}} for l in url_loc]
encrypted_locs = [crypt.urlsafe_encrypt(self.crypt_key, l)
for l in url_loc]
encrypted_locations = [{'url': l, 'metadata': {}}
for l in encrypted_locs]
self.assertNotEqual(encrypted_locations, orig_locations)
db_data = _db_fixture(UUID1, owner=TENANT1,
locations=encrypted_locations)
self.db.image_create(None, db_data)
image = self.image_repo.list()[0]
self.assertEqual(image.locations, ['ping', 'pong'])
self.assertEqual(image.locations, orig_locations)
class TestImageMemberRepo(test_utils.BaseTestCase):

View File

@ -53,9 +53,9 @@ class TestStore(base.IsolatedUnitTest):
file_contents = "chunk00000remainder"
image_file = StringIO.StringIO(file_contents)
location, size, checksum = self.store.add(image_id,
image_file,
len(file_contents))
location, size, checksum, _ = self.store.add(image_id,
image_file,
len(file_contents))
# Now read it back...
uri = "file:///%s/%s" % (self.test_dir, image_id)
@ -94,9 +94,9 @@ class TestStore(base.IsolatedUnitTest):
expected_image_id)
image_file = StringIO.StringIO(expected_file_contents)
location, size, checksum = self.store.add(expected_image_id,
image_file,
expected_file_size)
location, size, checksum, _ = self.store.add(expected_image_id,
image_file,
expected_file_size)
self.assertEquals(expected_location, location)
self.assertEquals(expected_file_size, size)
@ -126,9 +126,9 @@ class TestStore(base.IsolatedUnitTest):
file_contents = "*" * file_size
image_file = StringIO.StringIO(file_contents)
location, size, checksum = self.store.add(image_id,
image_file,
file_size)
location, size, checksum, _ = self.store.add(image_id,
image_file,
file_size)
image_file = StringIO.StringIO("nevergonnamakeit")
self.assertRaises(exception.Duplicate,
self.store.add,
@ -219,9 +219,9 @@ class TestStore(base.IsolatedUnitTest):
file_contents = "*" * file_size
image_file = StringIO.StringIO(file_contents)
location, size, checksum = self.store.add(image_id,
image_file,
file_size)
location, size, checksum, _ = self.store.add(image_id,
image_file,
file_size)
# Now check that we can delete it
uri = "file:///%s/%s" % (self.test_dir, image_id)

View File

@ -240,9 +240,9 @@ class TestStore(base.StoreClearingUnitTest):
expected_image_id)
image_s3 = StringIO.StringIO(expected_s3_contents)
location, size, checksum = self.store.add(expected_image_id,
image_s3,
expected_s3_size)
location, size, checksum, _ = self.store.add(expected_image_id,
image_s3,
expected_s3_size)
self.assertEquals(expected_location, location)
self.assertEquals(expected_s3_size, size)
@ -290,9 +290,9 @@ class TestStore(base.StoreClearingUnitTest):
self.config(**new_conf)
self.store = Store()
location, size, checksum = self.store.add(expected_image_id,
image_s3,
expected_s3_size)
location, size, checksum, _ = self.store.add(expected_image_id,
image_s3,
expected_s3_size)
self.assertEquals(expected_location, location)
self.assertEquals(expected_s3_size, size)

View File

@ -12,6 +12,7 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mox
from glance.common import exception
import glance.store
@ -78,7 +79,8 @@ class FakeMemberRepo(object):
class TestStoreImage(utils.BaseTestCase):
def setUp(self):
locations = ['%s/%s' % (BASE_URI, UUID1)]
locations = [{'url': '%s/%s' % (BASE_URI, UUID1),
'metadata': {}}]
self.image_stub = ImageStub(UUID1, 'active', locations)
self.store_api = unit_test_utils.FakeStoreAPI()
super(TestStoreImage, self).setUp()
@ -87,11 +89,11 @@ class TestStoreImage(utils.BaseTestCase):
image = glance.store.ImageProxy(self.image_stub, {}, self.store_api)
location = image.locations[0]
self.assertEquals(image.status, 'active')
self.store_api.get_from_backend({}, location)
self.store_api.get_from_backend({}, location['url'])
image.delete()
self.assertEquals(image.status, 'deleted')
self.assertRaises(exception.NotFound,
self.store_api.get_from_backend, {}, location)
self.store_api.get_from_backend, {}, location['url'])
def test_image_delayed_delete(self):
self.config(delayed_delete=True)
@ -99,7 +101,7 @@ class TestStoreImage(utils.BaseTestCase):
self.assertEquals(image.status, 'active')
image.delete()
self.assertEquals(image.status, 'pending_delete')
self.store_api.get_from_backend({}, image.locations[0]) # no exception
self.store_api.get_from_backend({}, image.locations[0]['url'])
def test_image_get_data(self):
image = glance.store.ImageProxy(self.image_stub, {}, self.store_api)
@ -112,14 +114,28 @@ class TestStoreImage(utils.BaseTestCase):
image.set_data('YYYY', 4)
self.assertEquals(image.size, 4)
#NOTE(markwash): FakeStore returns image_id for location
self.assertEquals(image.locations, [UUID2])
self.assertEquals(image.locations[0]['url'], UUID2)
self.assertEquals(image.checksum, 'Z')
self.assertEquals(image.status, 'active')
def test_image_set_data_location_metadata(self):
context = glance.context.RequestContext(user=USER1)
image_stub = ImageStub(UUID2, status='queued', locations=[])
loc_meta = {'key': 'value5032'}
store_api = unit_test_utils.FakeStoreAPI(store_metadata=loc_meta)
image = glance.store.ImageProxy(image_stub, context, store_api)
image.set_data('YYYY', 4)
self.assertEquals(image.size, 4)
location_data = image.locations[0]
self.assertEquals(location_data['url'], UUID2)
self.assertEquals(location_data['metadata'], loc_meta)
self.assertEquals(image.checksum, 'Z')
self.assertEquals(image.status, 'active')
image.delete()
self.assertEquals(image.status, 'deleted')
self.assertRaises(exception.NotFound,
self.store_api.get_from_backend, {},
image.locations[0])
image.locations[0]['url'])
def test_image_set_data_unknown_size(self):
context = glance.context.RequestContext(user=USER1)
@ -128,14 +144,14 @@ class TestStoreImage(utils.BaseTestCase):
image.set_data('YYYY', None)
self.assertEquals(image.size, 4)
#NOTE(markwash): FakeStore returns image_id for location
self.assertEquals(image.locations, [UUID2])
self.assertEquals(image.locations[0]['url'], UUID2)
self.assertEquals(image.checksum, 'Z')
self.assertEquals(image.status, 'active')
image.delete()
self.assertEquals(image.status, 'deleted')
self.assertRaises(exception.NotFound,
self.store_api.get_from_backend, {},
image.locations[0])
image.locations[0]['url'])
def _add_image(self, context, image_id, data, len):
image_stub = ImageStub(image_id, status='queued', locations=[])
@ -144,8 +160,9 @@ class TestStoreImage(utils.BaseTestCase):
image.set_data(data, len)
self.assertEquals(image.size, len)
#NOTE(markwash): FakeStore returns image_id for location
self.assertEquals(image.locations, [image_id])
self.assertEquals(image_stub.locations, [image_id])
location = {'url': image_id, 'metadata': {}}
self.assertEquals(image.locations, [location])
self.assertEquals(image_stub.locations, [location])
self.assertEqual(image.status, 'active')
return (image, image_stub)
@ -155,8 +172,9 @@ class TestStoreImage(utils.BaseTestCase):
context = glance.context.RequestContext(user=USER1)
(image1, image_stub1) = self._add_image(context, UUID2, 'XXXX', 4)
location_bad = {'url': 'unknown://location', 'metadata': {}}
self.assertRaises(exception.BadStoreUri,
image1.locations.append, 'unknown://location')
image1.locations.append, location_bad)
image1.delete()
@ -171,10 +189,13 @@ class TestStoreImage(utils.BaseTestCase):
(image1, image_stub1) = self._add_image(context, UUID2, 'XXXX', 4)
(image2, image_stub2) = self._add_image(context, UUID3, 'YYYY', 4)
image1.locations.append(UUID3)
location2 = {'url': UUID2, 'metadata': {}}
location3 = {'url': UUID3, 'metadata': {}}
self.assertEquals(image_stub1.locations, [UUID2, UUID3])
self.assertEquals(image1.locations, [UUID2, UUID3])
image1.locations.append(location3)
self.assertEquals(image_stub1.locations, [location2, location3])
self.assertEquals(image1.locations, [location2, location3])
image1.delete()
@ -192,15 +213,18 @@ class TestStoreImage(utils.BaseTestCase):
(image1, image_stub1) = self._add_image(context, UUID2, 'XXXX', 4)
(image2, image_stub2) = self._add_image(context, UUID3, 'YYYY', 4)
image1.locations.append(UUID3)
location2 = {'url': UUID2, 'metadata': {}}
location3 = {'url': UUID3, 'metadata': {}}
self.assertEquals(image_stub1.locations, [UUID2, UUID3])
self.assertEquals(image1.locations, [UUID2, UUID3])
image1.locations.append(location3)
self.assertEquals(image_stub1.locations, [location2, location3])
self.assertEquals(image1.locations, [location2, location3])
image1.locations.pop()
self.assertEquals(image_stub1.locations, [UUID2])
self.assertEquals(image1.locations, [UUID2])
self.assertEquals(image_stub1.locations, [location2])
self.assertEquals(image1.locations, [location2])
image1.delete()
@ -218,12 +242,16 @@ class TestStoreImage(utils.BaseTestCase):
(image1, image_stub1) = self._add_image(context, UUID2, 'XXXX', 4)
(image2, image_stub2) = self._add_image(context, UUID3, 'YYYY', 4)
image1.locations.extend([UUID3])
location2 = {'url': UUID2, 'metadata': {}}
location3 = {'url': UUID3, 'metadata': {}}
location_bad = {'url': 'unknown://location', 'metadata': {}}
self.assertEquals(image_stub1.locations, [UUID2, UUID3])
self.assertEquals(image1.locations, [UUID2, UUID3])
image1.locations.extend([location3])
self.assertEquals(image_stub1.locations, [location2, location3])
self.assertEquals(image1.locations, [location2, location3])
self.assertRaises(exception.BadStoreUri,
image1.locations.extend, 'unknown://location')
image1.locations.extend, [location_bad])
image1.delete()
@ -241,13 +269,17 @@ class TestStoreImage(utils.BaseTestCase):
(image1, image_stub1) = self._add_image(context, UUID2, 'XXXX', 4)
(image2, image_stub2) = self._add_image(context, UUID3, 'YYYY', 4)
image1.locations.extend([UUID3])
image1.locations.remove(UUID2)
location2 = {'url': UUID2, 'metadata': {}}
location3 = {'url': UUID3, 'metadata': {}}
location_bad = {'url': 'unknown://location', 'metadata': {}}
self.assertEquals(image_stub1.locations, [UUID3])
self.assertEquals(image1.locations, [UUID3])
image1.locations.extend([location3])
image1.locations.remove(location2)
self.assertEquals(image_stub1.locations, [location3])
self.assertEquals(image1.locations, [location3])
self.assertRaises(ValueError,
image1.locations.remove, 'unknown://location')
image1.locations.remove, location_bad)
image1.delete()
image2.delete()
@ -280,12 +312,16 @@ class TestStoreImage(utils.BaseTestCase):
(image1, image_stub1) = self._add_image(context, UUID2, 'XXXX', 4)
(image2, image_stub2) = self._add_image(context, UUID3, 'YYYY', 4)
image1.locations.insert(0, UUID3)
location2 = {'url': UUID2, 'metadata': {}}
location3 = {'url': UUID3, 'metadata': {}}
location_bad = {'url': 'unknown://location', 'metadata': {}}
self.assertEquals(image_stub1.locations, [UUID3, UUID2])
self.assertEquals(image1.locations, [UUID3, UUID2])
image1.locations.insert(0, location3)
self.assertEquals(image_stub1.locations, [location3, location2])
self.assertEquals(image1.locations, [location3, location2])
self.assertRaises(exception.BadStoreUri,
image1.locations.insert, 0, 'unknown://location')
image1.locations.insert, 0, location_bad)
image1.delete()
@ -303,15 +339,18 @@ class TestStoreImage(utils.BaseTestCase):
(image1, image_stub1) = self._add_image(context, UUID2, 'XXXX', 4)
(image2, image_stub2) = self._add_image(context, UUID3, 'YYYY', 4)
image1.locations.insert(0, UUID3)
location2 = {'url': UUID2, 'metadata': {}}
location3 = {'url': UUID3, 'metadata': {}}
image1.locations.insert(0, location3)
del image1.locations[0:100]
self.assertEquals(image_stub1.locations, [])
self.assertEqual(len(image1.locations), 0)
self.assertRaises(exception.BadStoreUri,
image1.locations.insert, 0, UUID2)
image1.locations.insert, 0, location2)
self.assertRaises(exception.BadStoreUri,
image2.locations.insert, 0, UUID3)
image2.locations.insert, 0, location3)
image1.delete()
image2.delete()
@ -330,15 +369,20 @@ class TestStoreImage(utils.BaseTestCase):
image_stub3 = ImageStub('fake_image_id', status='queued', locations=[])
image3 = glance.store.ImageProxy(image_stub3, context, self.store_api)
image3.locations += [UUID2, UUID3]
self.assertEquals(image_stub3.locations, [UUID2, UUID3])
self.assertEquals(image3.locations, [UUID2, UUID3])
location2 = {'url': UUID2, 'metadata': {}}
location3 = {'url': UUID3, 'metadata': {}}
location_bad = {'url': 'unknown://location', 'metadata': {}}
image3.locations += [location2, location3]
self.assertEquals(image_stub3.locations, [location2, location3])
self.assertEquals(image3.locations, [location2, location3])
self.assertRaises(exception.BadStoreUri,
image3.locations.__iadd__,
[UUID2, 'unknown://location'])
self.assertEquals(image_stub3.locations, [UUID2, UUID3])
self.assertEquals(image3.locations, [UUID2, UUID3])
[location2, location_bad])
self.assertEquals(image_stub3.locations, [location2, location3])
self.assertEquals(image3.locations, [location2, location3])
image3.delete()
@ -358,9 +402,13 @@ class TestStoreImage(utils.BaseTestCase):
(image2, image_stub2) = self._add_image(context, UUID3, 'YYYY', 4)
image_stub3 = ImageStub('fake_image_id', status='queued', locations=[])
image3 = glance.store.ImageProxy(image_stub3, context, self.store_api)
image3.locations += [UUID2, UUID3]
self.assertEqual(image_stub3.locations.index(UUID3), 1)
location2 = {'url': UUID2, 'metadata': {}}
location3 = {'url': UUID3, 'metadata': {}}
image3.locations += [location2, location3]
self.assertEqual(image_stub3.locations.index(location3), 1)
image3.delete()
@ -380,10 +428,14 @@ class TestStoreImage(utils.BaseTestCase):
(image2, image_stub2) = self._add_image(context, UUID3, 'YYYY', 4)
image_stub3 = ImageStub('fake_image_id', status='queued', locations=[])
image3 = glance.store.ImageProxy(image_stub3, context, self.store_api)
image3.locations += [UUID2, UUID3]
self.assertEqual(image_stub3.locations.index(UUID3), 1)
self.assertEqual(image_stub3.locations[0], UUID2)
location2 = {'url': UUID2, 'metadata': {}}
location3 = {'url': UUID3, 'metadata': {}}
image3.locations += [location2, location3]
self.assertEqual(image_stub3.locations.index(location3), 1)
self.assertEqual(image_stub3.locations[0], location2)
image3.delete()
@ -404,10 +456,15 @@ class TestStoreImage(utils.BaseTestCase):
image_stub3 = ImageStub('fake_image_id', status='queued', locations=[])
image3 = glance.store.ImageProxy(image_stub3, context, self.store_api)
image3.locations += [UUID2, UUID3]
self.assertTrue(UUID3 in image_stub3.locations)
self.assertFalse('unknown://location' in image_stub3.locations)
location2 = {'url': UUID2, 'metadata': {}}
location3 = {'url': UUID3, 'metadata': {}}
location_bad = {'url': 'unknown://location', 'metadata': {}}
image3.locations += [location2, location3]
self.assertTrue(location3 in image_stub3.locations)
self.assertFalse(location_bad in image_stub3.locations)
image3.delete()
@ -426,14 +483,17 @@ class TestStoreImage(utils.BaseTestCase):
(image1, image_stub1) = self._add_image(context, UUID2, 'XXXX', 4)
(image2, image_stub2) = self._add_image(context, UUID3, 'YYYY', 4)
location2 = {'url': UUID2, 'metadata': {}}
location3 = {'url': UUID3, 'metadata': {}}
image_stub3 = ImageStub('fake_image_id', status='queued', locations=[])
image3 = glance.store.ImageProxy(image_stub3, context, self.store_api)
image3.locations += [UUID2, UUID3]
image3.locations += [location2, location3]
image_stub3.locations.reverse()
self.assertEquals(image_stub3.locations, [UUID3, UUID2])
self.assertEquals(image3.locations, [UUID3, UUID2])
self.assertEquals(image_stub3.locations, [location3, location2])
self.assertEquals(image3.locations, [location3, location2])
image3.delete()
@ -457,7 +517,8 @@ class TestStoreImageRepo(utils.BaseTestCase):
{}, self.store_api)
def test_add_updates_acls(self):
self.image_stub.locations = ['foo', 'bar']
self.image_stub.locations = [{'url': 'foo', 'metadata': {}},
{'url': 'bar', 'metadata': {}}]
self.image_stub.visibility = 'public'
self.image_repo.add(self.image)
self.assertTrue(self.store_api.acls['foo']['public'])
@ -474,12 +535,12 @@ class TestStoreImageRepo(utils.BaseTestCase):
self.assertEqual(len(self.store_api.acls), 0)
def test_save_updates_acls(self):
self.image_stub.locations = ['foo']
self.image_stub.locations = [{'url': 'foo', 'metadata': {}}]
self.image_repo.save(self.image)
self.assertIn('foo', self.store_api.acls)
def test_add_fetches_members_if_private(self):
self.image_stub.locations = ['glue']
self.image_stub.locations = [{'url': 'glue', 'metadata': {}}]
self.image_stub.visibility = 'private'
self.image_repo.add(self.image)
self.assertIn('glue', self.store_api.acls)
@ -489,7 +550,7 @@ class TestStoreImageRepo(utils.BaseTestCase):
self.assertEquals(acls['read'], [TENANT1, TENANT2])
def test_save_fetches_members_if_private(self):
self.image_stub.locations = ['glue']
self.image_stub.locations = [{'url': 'glue', 'metadata': {}}]
self.image_stub.visibility = 'private'
self.image_repo.save(self.image)
self.assertIn('glue', self.store_api.acls)
@ -499,7 +560,7 @@ class TestStoreImageRepo(utils.BaseTestCase):
self.assertEquals(acls['read'], [TENANT1, TENANT2])
def test_member_addition_updates_acls(self):
self.image_stub.locations = ['glug']
self.image_stub.locations = [{'url': 'glug', 'metadata': {}}]
self.image_stub.visibility = 'private'
member_repo = self.image.get_member_repo()
membership = glance.domain.ImageMembership(
@ -512,7 +573,7 @@ class TestStoreImageRepo(utils.BaseTestCase):
self.assertEquals(acls['read'], [TENANT1, TENANT2, TENANT3])
def test_member_removal_updates_acls(self):
self.image_stub.locations = ['glug']
self.image_stub.locations = [{'url': 'glug', 'metadata': {}}]
self.image_stub.visibility = 'private'
member_repo = self.image.get_member_repo()
membership = glance.domain.ImageMembership(
@ -542,9 +603,171 @@ class TestImageFactory(utils.BaseTestCase):
self.assertEquals(image.locations, [])
def test_new_image_with_location(self):
image = self.image_factory.new_image(locations=['%s/%s' % (BASE_URI,
UUID1)])
self.assertEquals(image.locations, ['%s/%s' % (BASE_URI, UUID1)])
locations = [{'url': '%s/%s' % (BASE_URI, UUID1),
'metadata': {}}]
image = self.image_factory.new_image(locations=locations)
self.assertEquals(image.locations, locations)
location_bad = {'url': 'unknown://location', 'metadata': {}}
self.assertRaises(exception.BadStoreUri,
self.image_factory.new_image,
locations=['unknown://location'])
locations=[location_bad])
class TestStoreMetaDataChecker(utils.BaseTestCase):
def test_empty(self):
glance.store._check_meta_data({})
def test_unicode(self):
m = {'key': u'somevalue'}
glance.store._check_meta_data(m)
def test_unicode_list(self):
m = {'key': [u'somevalue', u'2']}
glance.store._check_meta_data(m)
def test_unicode_dict(self):
inner = {'key1': u'somevalue', 'key2': u'somevalue'}
m = {'topkey': inner}
glance.store._check_meta_data(m)
def test_unicode_dict_list(self):
inner = {'key1': u'somevalue', 'key2': u'somevalue'}
m = {'topkey': inner, 'list': [u'somevalue', u'2'], 'u': u'2'}
glance.store._check_meta_data(m)
def test_nested_dict(self):
inner = {'key1': u'somevalue', 'key2': u'somevalue'}
inner = {'newkey': inner}
inner = {'anotherkey': inner}
m = {'topkey': inner}
glance.store._check_meta_data(m)
def test_simple_bad(self):
m = {'key1': object()}
self.assertRaises(glance.store.BackendException,
glance.store._check_meta_data,
m)
def test_list_bad(self):
m = {'key1': [u'somevalue', object()]}
self.assertRaises(glance.store.BackendException,
glance.store._check_meta_data,
m)
def test_nested_dict_bad(self):
inner = {'key1': u'somevalue', 'key2': object()}
inner = {'newkey': inner}
inner = {'anotherkey': inner}
m = {'topkey': inner}
self.assertRaises(glance.store.BackendException,
glance.store._check_meta_data,
m)
class TestStoreAddToBackend(utils.BaseTestCase):
def setUp(self):
super(TestStoreAddToBackend, self).setUp()
self.image_id = "animage"
self.data = "dataandstuff"
self.size = len(self.data)
self.location = "file:///ab/cde/fgh"
self.checksum = "md5"
self.mox = mox.Mox()
def tearDown(self):
super(TestStoreAddToBackend, self).tearDown()
self.mox.UnsetStubs()
def _bad_metadata(self, in_metadata):
store = self.mox.CreateMockAnything()
store.add(self.image_id, mox.IgnoreArg(), self.size).AndReturn(
(self.location, self.size, self.checksum, in_metadata))
store.__str__().AndReturn(('hello'))
self.mox.ReplayAll()
self.assertRaises(glance.store.BackendException,
glance.store.store_add_to_backend,
self.image_id,
self.data,
self.size,
store)
self.mox.VerifyAll()
def _good_metadata(self, in_metadata):
store = self.mox.CreateMockAnything()
store.add(self.image_id, mox.IgnoreArg(), self.size).AndReturn(
(self.location, self.size, self.checksum, in_metadata))
self.mox.ReplayAll()
(location,
size,
checksum,
metadata) = glance.store.store_add_to_backend(self.image_id,
self.data,
self.size,
store)
self.mox.VerifyAll()
self.assertEqual(self.location, location)
self.assertEqual(self.size, size)
self.assertEqual(self.checksum, checksum)
self.assertEqual(in_metadata, metadata)
def test_empty(self):
metadata = {}
self._good_metadata(metadata)
def test_string(self):
metadata = {'key': u'somevalue'}
self._good_metadata(metadata)
def test_list(self):
m = {'key': [u'somevalue', u'2']}
self._good_metadata(m)
def test_unicode_dict(self):
inner = {'key1': u'somevalue', 'key2': u'somevalue'}
m = {'topkey': inner}
self._good_metadata(m)
def test_unicode_dict_list(self):
inner = {'key1': u'somevalue', 'key2': u'somevalue'}
m = {'topkey': inner, 'list': [u'somevalue', u'2'], 'u': u'2'}
self._good_metadata(m)
def test_nested_dict(self):
inner = {'key1': u'somevalue', 'key2': u'somevalue'}
inner = {'newkey': inner}
inner = {'anotherkey': inner}
m = {'topkey': inner}
self._good_metadata(m)
def test_bad_top_level_nonunicode(self):
metadata = {'key': 'a string'}
self._bad_metadata(metadata)
def test_bad_nonunicode_dict_list(self):
inner = {'key1': u'somevalue', 'key2': u'somevalue',
'k3': [1, object()]}
m = {'topkey': inner, 'list': [u'somevalue', u'2'], 'u': u'2'}
self._bad_metadata(m)
def test_bad_metadata_not_dict(self):
store = self.mox.CreateMockAnything()
store.add(self.image_id, mox.IgnoreArg(), self.size).AndReturn(
(self.location, self.size, self.checksum, []))
store.__str__().AndReturn(('hello'))
self.mox.ReplayAll()
self.assertRaises(glance.store.BackendException,
glance.store.store_add_to_backend,
self.image_id,
self.data,
self.size,
store)
self.mox.VerifyAll()

View File

@ -288,9 +288,9 @@ class SwiftTests(object):
global SWIFT_PUT_OBJECT_CALLS
SWIFT_PUT_OBJECT_CALLS = 0
location, size, checksum = self.store.add(expected_image_id,
image_swift,
expected_swift_size)
location, size, checksum, _ = self.store.add(expected_image_id,
image_swift,
expected_swift_size)
self.assertEquals(expected_location, location)
self.assertEquals(expected_swift_size, size)
@ -347,8 +347,8 @@ class SwiftTests(object):
self.config(swift_store_auth_address=variation)
self.store = Store()
location, size, checksum = self.store.add(image_id, image_swift,
expected_swift_size)
location, size, checksum, _ = self.store.add(image_id, image_swift,
expected_swift_size)
self.assertEquals(expected_location, location)
self.assertEquals(expected_swift_size, size)
@ -410,9 +410,9 @@ class SwiftTests(object):
self.config(swift_store_create_container_on_put=True,
swift_store_container='noexist')
self.store = Store()
location, size, checksum = self.store.add(expected_image_id,
image_swift,
expected_swift_size)
location, size, checksum, _ = self.store.add(expected_image_id,
image_swift,
expected_swift_size)
self.assertEquals(expected_location, location)
self.assertEquals(expected_swift_size, size)
@ -453,9 +453,9 @@ class SwiftTests(object):
try:
self.store.large_object_size = 1024
self.store.large_object_chunk_size = 1024
location, size, checksum = self.store.add(expected_image_id,
image_swift,
expected_swift_size)
location, size, checksum, _ = self.store.add(expected_image_id,
image_swift,
expected_swift_size)
finally:
self.store.large_object_chunk_size = orig_temp_size
self.store.large_object_size = orig_max_size
@ -511,8 +511,8 @@ class SwiftTests(object):
MAX_SWIFT_OBJECT_SIZE = 1024
self.store.large_object_size = 1024
self.store.large_object_chunk_size = 1024
location, size, checksum = self.store.add(expected_image_id,
image_swift, 0)
location, size, checksum, _ = self.store.add(expected_image_id,
image_swift, 0)
finally:
self.store.large_object_chunk_size = orig_temp_size
self.store.large_object_size = orig_max_size

View File

@ -64,7 +64,8 @@ class FakeDB(object):
def init_db():
images = [
{'id': UUID1, 'owner': TENANT1, 'status': 'queued',
'locations': ['%s/%s' % (BASE_URI, UUID1)]},
'locations': [{'url': '%s/%s' % (BASE_URI, UUID1),
'metadata': {}}]},
{'id': UUID2, 'owner': TENANT1, 'status': 'queued'},
]
[simple_db.image_create(None, image) for image in images]
@ -90,11 +91,15 @@ class FakeDB(object):
class FakeStoreAPI(object):
def __init__(self):
def __init__(self, store_metadata=None):
self.data = {
'%s/%s' % (BASE_URI, UUID1): ('XXX', 3),
}
self.acls = {}
if store_metadata is None:
self.store_metadata = {}
else:
self.store_metadata = store_metadata
def create_stores(self):
pass
@ -150,7 +155,7 @@ class FakeStoreAPI(object):
raise exception.StorageWriteDenied()
self.data[image_id] = (data, size)
checksum = 'Z'
return (image_id, size, checksum)
return (image_id, size, checksum, self.store_metadata)
class FakePolicyEnforcer(object):

View File

@ -68,7 +68,8 @@ class TestGlanceAPI(base.IsolatedUnitTest):
'deleted': False,
'checksum': None,
'size': 13,
'locations': ["file:///%s/%s" % (self.test_dir, UUID1)],
'locations': [{'url': "file:///%s/%s" % (self.test_dir, UUID1),
'metadata': {}}],
'properties': {'type': 'kernel'}},
{'id': UUID2,
'name': 'fake image #2',
@ -82,7 +83,8 @@ class TestGlanceAPI(base.IsolatedUnitTest):
'deleted': False,
'checksum': None,
'size': 19,
'locations': ["file:///%s/%s" % (self.test_dir, UUID2)],
'locations': [{'url': "file:///%s/%s" % (self.test_dir, UUID2),
'metadata': {}}],
'properties': {}}]
self.context = glance.context.RequestContext(is_admin=True)
db_api.setup_db_env()
@ -340,6 +342,47 @@ class TestGlanceAPI(base.IsolatedUnitTest):
res = req.get_response(self.api)
self.assertEquals(res.status_int, 400)
def _add_check_no_url_info(self):
fixture_headers = {'x-image-meta-disk-format': 'ami',
'x-image-meta-container-format': 'ami',
'x-image-meta-size': '0',
'x-image-meta-name': 'empty image'}
req = webob.Request.blank("/images")
req.method = 'POST'
for k, v in fixture_headers.iteritems():
req.headers[k] = v
res = req.get_response(self.api)
res_body = json.loads(res.body)['image']
self.assertFalse('locations' in res_body)
self.assertFalse('direct_url' in res_body)
image_id = res_body['id']
# HEAD empty image
req = webob.Request.blank("/images/%s" % image_id)
req.method = 'HEAD'
res = req.get_response(self.api)
self.assertEqual(res.status_int, 200)
self.assertFalse('x-image-meta-locations' in res.headers)
self.assertFalse('x-image-meta-direct_url' in res.headers)
def test_add_check_no_url_info_ml(self):
self.config(show_multiple_locations=True)
self._add_check_no_url_info()
def test_add_check_no_url_info_direct_url(self):
self.config(show_image_direct_url=True)
self._add_check_no_url_info()
def test_add_check_no_url_info_both_on(self):
self.config(show_image_direct_url=True)
self.config(show_multiple_locations=True)
self._add_check_no_url_info()
def test_add_check_no_url_info_both_off(self):
self._add_check_no_url_info()
def test_add_image_zero_size(self):
"""Tests creating an active image with explicitly zero size"""
fixture_headers = {'x-image-meta-disk-format': 'ami',

View File

@ -120,7 +120,8 @@ class TestRegistryAPI(base.IsolatedUnitTest):
'min_ram': 0,
'size': 13,
'owner': '123',
'locations': ["file:///%s/%s" % (self.test_dir, UUID1)],
'locations': [{'url': "file:///%s/%s" % (self.test_dir, UUID1),
'metadata': {}}],
'properties': {'type': 'kernel'}},
{'id': UUID2,
'name': 'fake image #2',
@ -136,7 +137,8 @@ class TestRegistryAPI(base.IsolatedUnitTest):
'min_disk': 5,
'min_ram': 256,
'size': 19,
'locations': ["file:///%s/%s" % (self.test_dir, UUID2)],
'locations': [{'url': "file:///%s/%s" % (self.test_dir, UUID2),
'metadata': {}}],
'properties': {}}]
self.context = glance.context.RequestContext(is_admin=True)
db_api.setup_db_env()
@ -2589,3 +2591,98 @@ class TestRegistryAPI(base.IsolatedUnitTest):
req = webob.Request.blank("/images/%s/members.xxx" % UUID1)
res = req.get_response(self.api)
self.assertEquals(res.status_int, 404)
class TestRegistryAPILocations(base.IsolatedUnitTest):
def setUp(self):
"""Establish a clean test environment"""
super(TestRegistryAPILocations, self).setUp()
self.mapper = routes.Mapper()
self.api = test_utils.FakeAuthMiddleware(rserver.API(self.mapper),
is_admin=True)
self.FIXTURES = [
{'id': UUID1,
'name': 'fake image #1',
'status': 'active',
'disk_format': 'ami',
'container_format': 'ami',
'is_public': False,
'created_at': timeutils.utcnow(),
'updated_at': timeutils.utcnow(),
'deleted_at': None,
'deleted': False,
'checksum': None,
'min_disk': 0,
'min_ram': 0,
'size': 13,
'owner': '123',
'locations': [
{'url': "file:///%s/%s" % (self.test_dir, UUID1),
'metadata': {}}],
'properties': {'type': 'kernel'}},
{'id': UUID2,
'name': 'fake image #2',
'status': 'active',
'disk_format': 'vhd',
'container_format': 'ovf',
'is_public': True,
'created_at': timeutils.utcnow(),
'updated_at': timeutils.utcnow(),
'deleted_at': None,
'deleted': False,
'checksum': None,
'min_disk': 5,
'min_ram': 256,
'size': 19,
'locations': [
{'url': "file:///%s/%s" % (self.test_dir, UUID2),
'metadata': {'key': 'value'}}],
'properties': {}}]
self.context = glance.context.RequestContext(is_admin=True)
db_api.setup_db_env()
db_api.get_engine()
self.destroy_fixtures()
self.create_fixtures()
def tearDown(self):
"""Clear the test environment"""
super(TestRegistryAPILocations, self).tearDown()
self.destroy_fixtures()
def create_fixtures(self):
for fixture in self.FIXTURES:
db_api.image_create(self.context, fixture)
# We write a fake image file to the filesystem
with open("%s/%s" % (self.test_dir, fixture['id']), 'wb') as image:
image.write("chunk00000remainder")
image.flush()
def destroy_fixtures(self):
# Easiest to just drop the models and re-create them...
db_models.unregister_models(db_api._ENGINE)
db_models.register_models(db_api._ENGINE)
def test_show_from_locations(self):
req = webob.Request.blank('/images/%s' % UUID1)
res = req.get_response(self.api)
self.assertEquals(res.status_int, 200)
res_dict = json.loads(res.body)
image = res_dict['image']
self.assertEqual(self.FIXTURES[0]['locations'][0],
image['location_data'][0])
self.assertEqual(self.FIXTURES[0]['locations'][0]['url'],
image['location_data'][0]['url'])
self.assertEqual(image['location_data'][0]['metadata'], {})
def test_show_from_location_data(self):
req = webob.Request.blank('/images/%s' % UUID2)
res = req.get_response(self.api)
self.assertEquals(res.status_int, 200)
res_dict = json.loads(res.body)
image = res_dict['image']
self.assertEqual(self.FIXTURES[1]['locations'][0],
image['location_data'][0])
self.assertEqual(self.FIXTURES[1]['locations'][0]['url'],
image['location_data'][0]['url'])
self.assertEqual(self.FIXTURES[1]['locations'][0]['metadata'],
image['location_data'][0]['metadata'])

View File

@ -1050,6 +1050,25 @@ class TestRegistryV1Client(base.IsolatedUnitTest):
self.assertTrue('status' in new_image.keys())
self.assertEquals('active', new_image['status'])
def test_add_image_with_location_data(self):
"""Tests that we can add image metadata with properties"""
location = "file:///tmp/glance-tests/2"
loc_meta = {'key': 'value'}
fixture = {'name': 'fake public image',
'is_public': True,
'disk_format': 'vmdk',
'container_format': 'ovf',
'size': 19,
'location_data': [{'url': location,
'metadata': loc_meta}],
'properties': {'distro': 'Ubuntu 10.04 LTS'}}
new_image = self.client.add_image(fixture)
self.assertEquals(new_image['location'], location)
self.assertEquals(new_image['location_data'][0]['url'], location)
self.assertEquals(new_image['location_data'][0]['metadata'], loc_meta)
def test_add_image_already_exists(self):
"""Tests proper exception is raised if image with ID already exists"""
fixture = {

View File

@ -108,7 +108,7 @@ class TestUploadUtils(base.StoreClearingUnitTest):
store.add(
image_meta['id'],
mox.IgnoreArg(),
image_meta['size']).AndReturn((location, size, checksum))
image_meta['size']).AndReturn((location, size, checksum, {}))
self.mox.StubOutWithMock(registry, "update_image_metadata")
update_data = {'checksum': checksum,
@ -120,11 +120,8 @@ class TestUploadUtils(base.StoreClearingUnitTest):
image_meta.update(update_data))
self.mox.ReplayAll()
actual_meta, actual_loc = upload_utils.upload_data_to_store(req,
image_meta,
image_data,
store,
notifier)
actual_meta, actual_loc, loc_meta = upload_utils.upload_data_to_store(
req, image_meta, image_data, store, notifier)
self.mox.VerifyAll()
@ -148,7 +145,7 @@ class TestUploadUtils(base.StoreClearingUnitTest):
store.add(
image_meta['id'],
mox.IgnoreArg(),
image_meta['size']).AndReturn((location, size, checksum))
image_meta['size']).AndReturn((location, size, checksum, {}))
self.mox.StubOutWithMock(registry, "update_image_metadata")
update_data = {'checksum': checksum}
@ -182,7 +179,10 @@ class TestUploadUtils(base.StoreClearingUnitTest):
store.add(
image_meta['id'],
mox.IgnoreArg(),
image_meta['size']).AndReturn((location, size, checksum + "NOT"))
image_meta['size']).AndReturn((location,
size,
checksum + "NOT",
{}))
self.mox.StubOutWithMock(registry, "update_image_metadata")
update_data = {'checksum': checksum}
@ -308,7 +308,7 @@ class TestUploadUtils(base.StoreClearingUnitTest):
store.add(
image_meta['id'],
mox.IgnoreArg(),
image_meta['size']).AndReturn((location, size, checksum))
image_meta['size']).AndReturn((location, size, checksum, {}))
self.mox.StubOutWithMock(registry, "update_image_metadata")
update_data = {'checksum': checksum,

View File

@ -122,7 +122,8 @@ class TestImagesController(test_utils.BaseTestCase):
self.images = [
_db_fixture(UUID1, owner=TENANT1, name='1', size=256,
is_public=True,
locations=['%s/%s' % (BASE_URI, UUID1)]),
locations=[{'url': '%s/%s' % (BASE_URI, UUID1),
'metadata': {}}]),
_db_fixture(UUID2, owner=TENANT1, name='2',
size=512, is_public=True),
_db_fixture(UUID3, owner=TENANT3, name='3',
@ -1047,7 +1048,7 @@ class TestImagesDeserializer(test_utils.BaseTestCase):
samples = {
'owner': TENANT1,
'is_public': True,
'locations': ['/a/b/c/d'],
'locations': [{'url': '/a/b/c/d', 'metadata': {}}],
'deleted': False,
'deleted_at': ISOTIME,
}
@ -1877,13 +1878,23 @@ class TestImagesSerializerDirectUrl(test_utils.BaseTestCase):
self.active_image = _domain_fixture(
UUID1, name='image-1', visibility='public',
status='active', size=1024, created_at=DATETIME,
updated_at=DATETIME, locations=['http://some/fake/location'])
updated_at=DATETIME,
locations=[{'url': 'http://some/fake/location',
'metadata': {}}])
self.queued_image = _domain_fixture(
UUID2, name='image-2', status='active',
created_at=DATETIME, updated_at=DATETIME,
checksum='ca425b88f047ce8ec45ee90e813ada91')
self.location_data_image_url = 'http://abc.com/somewhere'
self.location_data_image_meta = {'key': 98231}
self.location_data_image = _domain_fixture(
UUID2, name='image-2', status='active',
created_at=DATETIME, updated_at=DATETIME,
locations=[{'url': self.location_data_image_url,
'metadata': self.location_data_image_meta}])
def _do_index(self):
request = webob.Request.blank('/v2/images')
response = webob.Response(request=request)
@ -1909,6 +1920,17 @@ class TestImagesSerializerDirectUrl(test_utils.BaseTestCase):
self.assertEqual(images[0]['direct_url'], 'http://some/fake/location')
self.assertFalse('direct_url' in images[1])
def test_index_store_multiple_location_enabled(self):
self.config(show_multiple_locations=True)
request = webob.Request.blank('/v2/images')
response = webob.Response(request=request)
self.serializer.index(response,
{'images': [self.location_data_image]}),
images = json.loads(response.body)['images']
location = images[0]['locations'][0]
self.assertEqual(location['url'], self.location_data_image_url)
self.assertEqual(location['metadata'], self.location_data_image_meta)
def test_index_store_location_explicitly_disabled(self):
self.config(show_image_direct_url=False)
images = self._do_index()

View File

@ -67,7 +67,8 @@ class TestRegistryRPC(base.IsolatedUnitTest):
'min_disk': 0,
'min_ram': 0,
'size': 13,
'locations': ["file:///%s/%s" % (self.test_dir, UUID1)],
'locations': [{'url': "file:///%s/%s" % (self.test_dir, UUID1),
'metadata': {}}],
'properties': {'type': 'kernel'}},
{'id': UUID2,
'name': 'fake image #2',
@ -83,7 +84,8 @@ class TestRegistryRPC(base.IsolatedUnitTest):
'min_disk': 5,
'min_ram': 256,
'size': 19,
'locations': ["file:///%s/%s" % (self.test_dir, UUID2)],
'locations': [{'url': "file:///%s/%s" % (self.test_dir, UUID2),
'metadata': {}}],
'properties': {}}]
self.context = glance.context.RequestContext(is_admin=True)

View File

@ -31,7 +31,8 @@ class TestSchemasController(test_utils.BaseTestCase):
expected = set(['status', 'name', 'tags', 'checksum', 'created_at',
'disk_format', 'updated_at', 'visibility', 'self',
'file', 'container_format', 'schema', 'id', 'size',
'direct_url', 'min_ram', 'min_disk', 'protected'])
'direct_url', 'min_ram', 'min_disk', 'protected',
'locations'])
self.assertEqual(set(output['properties'].keys()), expected)
def test_images(self):