Merge "Allow storage drivers to add metadata to locations"
This commit is contained in:
commit
de463e9ff8
|
@ -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)
|
||||
|
|
|
@ -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}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 = [
|
||||
|
|
|
@ -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).")),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
"""
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -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 "
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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'}}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -51,6 +51,7 @@ class TestSchemas(functional.FunctionalTest):
|
|||
'status',
|
||||
'schema',
|
||||
'direct_url',
|
||||
'locations',
|
||||
'min_ram',
|
||||
'min_disk',
|
||||
'protected',
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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'])
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
Loading…
Reference in New Issue