summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJohannes Erdfelt <johannes.erdfelt@rackspace.com>2011-09-13 15:47:25 +0000
committerJohannes Erdfelt <johannes.erdfelt@rackspace.com>2011-09-13 15:47:25 +0000
commit542ea00d6fd22253f50d8bd5fd5319aa42ba9e04 (patch)
tree9e647291cff684f0421d55446d0cb5684101fdb4
parentcc86ca1ddc3c5b33d1469619ae491bf09c1ac6b5 (diff)
parent61e5825a43fff1ad60dcd26454dc4881bdc13ef6 (diff)
Merge with trunk
-rw-r--r--MANIFEST.in2
-rw-r--r--nova/api/ec2/cloud.py2
-rw-r--r--nova/api/openstack/image_metadata.py58
-rw-r--r--nova/api/openstack/images.py2
-rw-r--r--nova/image/fake.py3
-rw-r--r--nova/image/glance.py80
-rw-r--r--nova/image/s3.py3
-rw-r--r--nova/image/service.py200
-rw-r--r--nova/tests/api/ec2/public_key/dummy.fingerprint (renamed from nova/tests/public_key/dummy.fingerprint)0
-rw-r--r--nova/tests/api/ec2/public_key/dummy.pub (renamed from nova/tests/public_key/dummy.pub)0
-rw-r--r--nova/tests/api/ec2/test_cloud.py (renamed from nova/tests/test_cloud.py)0
-rw-r--r--nova/tests/api/openstack/fakes.py127
-rw-r--r--nova/tests/api/openstack/test_image_metadata.py163
-rw-r--r--nova/tests/api/openstack/test_images.py652
-rw-r--r--nova/tests/glance/stubs.py68
-rw-r--r--nova/tests/image/test_glance.py637
-rw-r--r--nova/tests/test_direct.py2
17 files changed, 850 insertions, 1149 deletions
diff --git a/MANIFEST.in b/MANIFEST.in
index 883aba8a1..5451ace4b 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -37,7 +37,7 @@ include nova/tests/bundle/1mb.manifest.xml
include nova/tests/bundle/1mb.no_kernel_or_ramdisk.manifest.xml
include nova/tests/bundle/1mb.part.0
include nova/tests/bundle/1mb.part.1
-include nova/tests/public_key/*
+include nova/tests/api/ec2/public_key/*
include nova/tests/db/nova.austin.sqlite
include plugins/xenapi/README
include plugins/xenapi/etc/xapi.d/plugins/objectstore
diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py
index 4f7030a5a..eacfdc0df 100644
--- a/nova/api/ec2/cloud.py
+++ b/nova/api/ec2/cloud.py
@@ -1467,7 +1467,7 @@ class CloudController(object):
return image
def _format_image(self, image):
- """Convert from format defined by BaseImageService to S3 format."""
+ """Convert from format defined by GlanceImageService to S3 format."""
i = {}
image_type = self._image_type(image.get('container_format'))
ec2_id = self.image_ec2_id(image.get('id'), image_type)
diff --git a/nova/api/openstack/image_metadata.py b/nova/api/openstack/image_metadata.py
index 4d615ea96..adb6bee4b 100644
--- a/nova/api/openstack/image_metadata.py
+++ b/nova/api/openstack/image_metadata.py
@@ -17,6 +17,7 @@
from webob import exc
+from nova import exception
from nova import flags
from nova import image
from nova import utils
@@ -33,21 +34,22 @@ class Controller(object):
def __init__(self):
self.image_service = image.get_default_image_service()
- def _get_metadata(self, context, image_id, image=None):
- if not image:
- image = self.image_service.show(context, image_id)
- metadata = image.get('properties', {})
- return metadata
+ def _get_image(self, context, image_id):
+ try:
+ return self.image_service.show(context, image_id)
+ except exception.NotFound:
+ msg = _("Image not found.")
+ raise exc.HTTPNotFound(explanation=msg)
def index(self, req, image_id):
"""Returns the list of metadata for a given instance"""
context = req.environ['nova.context']
- metadata = self._get_metadata(context, image_id)
+ metadata = self._get_image(context, image_id)['properties']
return dict(metadata=metadata)
def show(self, req, image_id, id):
context = req.environ['nova.context']
- metadata = self._get_metadata(context, image_id)
+ metadata = self._get_image(context, image_id)['properties']
if id in metadata:
return {'meta': {id: metadata[id]}}
else:
@@ -55,15 +57,13 @@ class Controller(object):
def create(self, req, image_id, body):
context = req.environ['nova.context']
- img = self.image_service.show(context, image_id)
- metadata = self._get_metadata(context, image_id, img)
+ image = self._get_image(context, image_id)
if 'metadata' in body:
for key, value in body['metadata'].iteritems():
- metadata[key] = value
- common.check_img_metadata_quota_limit(context, metadata)
- img['properties'] = metadata
- self.image_service.update(context, image_id, img, None)
- return dict(metadata=metadata)
+ image['properties'][key] = value
+ common.check_img_metadata_quota_limit(context, image['properties'])
+ self.image_service.update(context, image_id, image, None)
+ return dict(metadata=image['properties'])
def update(self, req, image_id, id, body):
context = req.environ['nova.context']
@@ -80,32 +80,30 @@ class Controller(object):
if len(meta) > 1:
expl = _('Request body contains too many items')
raise exc.HTTPBadRequest(explanation=expl)
- img = self.image_service.show(context, image_id)
- metadata = self._get_metadata(context, image_id, img)
- metadata[id] = meta[id]
- common.check_img_metadata_quota_limit(context, metadata)
- img['properties'] = metadata
- self.image_service.update(context, image_id, img, None)
+
+ image = self._get_image(context, image_id)
+ image['properties'][id] = meta[id]
+ common.check_img_metadata_quota_limit(context, image['properties'])
+ self.image_service.update(context, image_id, image, None)
return dict(meta=meta)
def update_all(self, req, image_id, body):
context = req.environ['nova.context']
- img = self.image_service.show(context, image_id)
+ image = self._get_image(context, image_id)
metadata = body.get('metadata', {})
common.check_img_metadata_quota_limit(context, metadata)
- img['properties'] = metadata
- self.image_service.update(context, image_id, img, None)
+ image['properties'] = metadata
+ self.image_service.update(context, image_id, image, None)
return dict(metadata=metadata)
def delete(self, req, image_id, id):
context = req.environ['nova.context']
- img = self.image_service.show(context, image_id)
- metadata = self._get_metadata(context, image_id)
- if not id in metadata:
- raise exc.HTTPNotFound()
- metadata.pop(id)
- img['properties'] = metadata
- self.image_service.update(context, image_id, img, None)
+ image = self._get_image(context, image_id)
+ if not id in image['properties']:
+ msg = _("Invalid metadata key")
+ raise exc.HTTPNotFound(explanation=msg)
+ image['properties'].pop(id)
+ self.image_service.update(context, image_id, image, None)
def create_resource():
diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py
index 1c8fc10c9..fcaa94651 100644
--- a/nova/api/openstack/images.py
+++ b/nova/api/openstack/images.py
@@ -50,7 +50,7 @@ class Controller(object):
"""Initialize new `ImageController`.
:param compute_service: `nova.compute.api:API`
- :param image_service: `nova.image.service:BaseImageService`
+ :param image_service: `nova.image.glance:GlancemageService`
"""
self._compute_service = compute_service or compute.API()
diff --git a/nova/image/fake.py b/nova/image/fake.py
index 97af81711..4eceabc11 100644
--- a/nova/image/fake.py
+++ b/nova/image/fake.py
@@ -24,7 +24,6 @@ import random
from nova import exception
from nova import flags
from nova import log as logging
-from nova.image import service
LOG = logging.getLogger('nova.image.fake')
@@ -33,7 +32,7 @@ LOG = logging.getLogger('nova.image.fake')
FLAGS = flags.FLAGS
-class _FakeImageService(service.BaseImageService):
+class _FakeImageService(object):
"""Mock (fake) image service for unit testing."""
def __init__(self):
diff --git a/nova/image/glance.py b/nova/image/glance.py
index 13c8ff843..5ee1d2b8a 100644
--- a/nova/image/glance.py
+++ b/nova/image/glance.py
@@ -31,7 +31,6 @@ from nova import exception
from nova import flags
from nova import log as logging
from nova import utils
-from nova.image import service
LOG = logging.getLogger('nova.image.glance')
@@ -114,17 +113,9 @@ def get_glance_client(context, image_href):
return (glance_client, image_id)
-class GlanceImageService(service.BaseImageService):
+class GlanceImageService(object):
"""Provides storage and retrieval of disk image objects within Glance."""
- GLANCE_ONLY_ATTRS = ['size', 'location', 'disk_format',
- 'container_format', 'checksum']
-
- # NOTE(sirp): Overriding to use _translate_to_service provided by
- # BaseImageService
- SERVICE_IMAGE_ATTRS = service.BaseImageService.BASE_IMAGE_ATTRS +\
- GLANCE_ONLY_ATTRS
-
def __init__(self, client=None):
self._client = client
@@ -160,7 +151,7 @@ class GlanceImageService(service.BaseImageService):
images = []
for image_meta in image_metas:
if self._is_image_available(context, image_meta):
- base_image_meta = self._translate_to_base(image_meta)
+ base_image_meta = self._translate_from_glance(image_meta)
images.append(base_image_meta)
return images
@@ -224,7 +215,7 @@ class GlanceImageService(service.BaseImageService):
if not self._is_image_available(context, image_meta):
raise exception.ImageNotFound(image_id=image_id)
- base_image_meta = self._translate_to_base(image_meta)
+ base_image_meta = self._translate_from_glance(image_meta)
return base_image_meta
def show_by_name(self, context, name):
@@ -248,7 +239,7 @@ class GlanceImageService(service.BaseImageService):
for chunk in image_chunks:
data.write(chunk)
- base_image_meta = self._translate_to_base(image_meta)
+ base_image_meta = self._translate_from_glance(image_meta)
return base_image_meta
def create(self, context, image_meta, data=None):
@@ -260,7 +251,7 @@ class GlanceImageService(service.BaseImageService):
# Translate Base -> Service
LOG.debug(_('Creating image in Glance. Metadata passed in %s'),
image_meta)
- sent_service_image_meta = self._translate_to_service(image_meta)
+ sent_service_image_meta = self._translate_to_glance(image_meta)
LOG.debug(_('Metadata after formatting for Glance %s'),
sent_service_image_meta)
@@ -268,7 +259,7 @@ class GlanceImageService(service.BaseImageService):
sent_service_image_meta, data)
# Translate Service -> Base
- base_image_meta = self._translate_to_base(recv_service_image_meta)
+ base_image_meta = self._translate_from_glance(recv_service_image_meta)
LOG.debug(_('Metadata returned from Glance formatted for Base %s'),
base_image_meta)
return base_image_meta
@@ -281,14 +272,14 @@ class GlanceImageService(service.BaseImageService):
"""
# NOTE(vish): show is to check if image is available
self.show(context, image_id)
- image_meta = _convert_to_string(image_meta)
+ image_meta = self._translate_to_glance(image_meta)
try:
client = self._get_client(context)
image_meta = client.update_image(image_id, image_meta, data)
except glance_exception.NotFound:
raise exception.ImageNotFound(image_id=image_id)
- base_image_meta = self._translate_to_base(image_meta)
+ base_image_meta = self._translate_from_glance(image_meta)
return base_image_meta
def delete(self, context, image_id):
@@ -310,17 +301,14 @@ class GlanceImageService(service.BaseImageService):
pass
@classmethod
- def _translate_to_service(cls, image_meta):
- image_meta = super(GlanceImageService,
- cls)._translate_to_service(image_meta)
+ def _translate_to_glance(cls, image_meta):
image_meta = _convert_to_string(image_meta)
+ image_meta = _remove_read_only(image_meta)
return image_meta
@classmethod
- def _translate_to_base(cls, image_meta):
- """Override translation to handle conversion to datetime objects."""
- image_meta = service.BaseImageService._propertify_metadata(
- image_meta, cls.SERVICE_IMAGE_ATTRS)
+ def _translate_from_glance(cls, image_meta):
+ image_meta = _limit_attributes(image_meta)
image_meta = _convert_timestamps_to_datetimes(image_meta)
image_meta = _convert_from_string(image_meta)
return image_meta
@@ -330,14 +318,26 @@ class GlanceImageService(service.BaseImageService):
"""Check image availability.
Under Glance, images are always available if the context has
- an auth_token. Otherwise, we fall back to the superclass
- method.
+ an auth_token.
"""
if hasattr(context, 'auth_token') and context.auth_token:
return True
- return service.BaseImageService._is_image_available(context,
- image_meta)
+
+ if image_meta['is_public'] or context.is_admin:
+ return True
+
+ properties = image_meta['properties']
+
+ if context.project_id and ('project_id' in properties):
+ return str(properties['project_id']) == str(context.project_id)
+
+ try:
+ user_id = properties['user_id']
+ except KeyError:
+ return False
+
+ return str(user_id) == str(context.user_id)
# utility functions
@@ -397,3 +397,27 @@ def _convert_from_string(metadata):
def _convert_to_string(metadata):
return _convert(_json_dumps, metadata)
+
+
+def _limit_attributes(image_meta):
+ IMAGE_ATTRIBUTES = ['size', 'location', 'disk_format',
+ 'container_format', 'checksum', 'id',
+ 'name', 'created_at', 'updated_at',
+ 'deleted_at', 'deleted', 'status',
+ 'is_public']
+ output = {}
+ for attr in IMAGE_ATTRIBUTES:
+ output[attr] = image_meta.get(attr)
+
+ output['properties'] = image_meta.get('properties', {})
+
+ return output
+
+
+def _remove_read_only(image_meta):
+ IMAGE_ATTRIBUTES = ['updated_at', 'created_at', 'deleted_at']
+ output = copy.deepcopy(image_meta)
+ for attr in IMAGE_ATTRIBUTES:
+ if attr in output:
+ del output[attr]
+ return output
diff --git a/nova/image/s3.py b/nova/image/s3.py
index abf01a942..343555887 100644
--- a/nova/image/s3.py
+++ b/nova/image/s3.py
@@ -34,7 +34,6 @@ from nova import flags
from nova import image
from nova import log as logging
from nova import utils
-from nova.image import service
from nova.api.ec2 import ec2utils
@@ -48,7 +47,7 @@ flags.DEFINE_string('s3_secret_key', 'notchecked',
'secret key to use for s3 server for images')
-class S3ImageService(service.BaseImageService):
+class S3ImageService(object):
"""Wraps an existing image service to support s3 based register."""
def __init__(self, service=None, *args, **kwargs):
diff --git a/nova/image/service.py b/nova/image/service.py
deleted file mode 100644
index 5361cfc89..000000000
--- a/nova/image/service.py
+++ /dev/null
@@ -1,200 +0,0 @@
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
-# Copyright 2010 OpenStack LLC.
-# All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-
-from nova import utils
-
-
-class BaseImageService(object):
- """Base class for providing image search and retrieval services.
-
- ImageService exposes two concepts of metadata:
-
- 1. First-class attributes: This is metadata that is common to all
- ImageService subclasses and is shared across all hypervisors. These
- attributes are defined by IMAGE_ATTRS.
-
- 2. Properties: This is metdata that is specific to an ImageService,
- and Image, or a particular hypervisor. Any attribute not present in
- BASE_IMAGE_ATTRS should be considered an image property.
-
- This means that ImageServices will return BASE_IMAGE_ATTRS as keys in the
- metadata dict, all other attributes will be returned as keys in the nested
- 'properties' dict.
-
- """
-
- BASE_IMAGE_ATTRS = ['id', 'name', 'created_at', 'updated_at',
- 'deleted_at', 'deleted', 'status', 'is_public']
-
- # NOTE(sirp): ImageService subclasses may override this to aid translation
- # between BaseImageService attributes and additional metadata stored by
- # the ImageService subclass
- SERVICE_IMAGE_ATTRS = []
-
- def index(self, context, *args, **kwargs):
- """List images.
-
- :returns: a sequence of mappings with the following signature
- {'id': opaque id of image, 'name': name of image}
-
- """
- raise NotImplementedError
-
- def detail(self, context, *args, **kwargs):
- """Detailed information about an images.
-
- :returns: a sequence of mappings with the following signature
- {'id': opaque id of image,
- 'name': name of image,
- 'created_at': creation datetime object,
- 'updated_at': modification datetime object,
- 'deleted_at': deletion datetime object or None,
- 'deleted': boolean indicating if image has been deleted,
- 'status': string description of image status,
- 'is_public': boolean indicating if image is public
- }
-
- If the service does not implement a method that provides a detailed
- set of information about images, then the method should raise
- NotImplementedError, in which case Nova will emulate this method
- with repeated calls to show() for each image received from the
- index() method.
-
- """
- raise NotImplementedError
-
- def show(self, context, image_id):
- """Detailed information about an image.
-
- :returns: a mapping with the following signature:
- {'id': opaque id of image,
- 'name': name of image,
- 'created_at': creation datetime object,
- 'updated_at': modification datetime object,
- 'deleted_at': deletion datetime object or None,
- 'deleted': boolean indicating if image has been deleted,
- 'status': string description of image status,
- 'is_public': boolean indicating if image is public
- }, ...
-
- :raises: NotFound if the image does not exist
-
- """
- raise NotImplementedError
-
- def get(self, context, data):
- """Get an image.
-
- :param data: a file-like object to hold binary image data
- :returns: a dict containing image metadata, writes image data to data.
- :raises: NotFound if the image does not exist
-
- """
- raise NotImplementedError
-
- def create(self, context, metadata, data=None):
- """Store the image metadata and data.
-
- :returns: the new image metadata.
- :raises: AlreadyExists if the image already exist.
-
- """
- raise NotImplementedError
-
- def update(self, context, image_id, metadata, data=None):
- """Update the given image metadata and data and return the metadata.
-
- :raises: NotFound if the image does not exist.
-
- """
- raise NotImplementedError
-
- def delete(self, context, image_id):
- """Delete the given image.
-
- :raises: NotFound if the image does not exist.
-
- """
- raise NotImplementedError
-
- @staticmethod
- def _is_image_available(context, image_meta):
- """Check image availability.
-
- Images are always available if they are public or if the user is an
- admin.
-
- Otherwise, we filter by project_id (if present) and then fall-back to
- images owned by user.
-
- """
- # FIXME(sirp): We should be filtering by user_id on the Glance side
- # for security; however, we can't do that until we get authn/authz
- # sorted out. Until then, filtering in Nova.
- if image_meta['is_public'] or context.is_admin:
- return True
-
- properties = image_meta['properties']
-
- if context.project_id and ('project_id' in properties):
- return str(properties['project_id']) == str(context.project_id)
-
- try:
- user_id = properties['user_id']
- except KeyError:
- return False
-
- return str(user_id) == str(context.user_id)
-
- @classmethod
- def _translate_to_base(cls, metadata):
- """Return a metadata dictionary that is BaseImageService compliant.
-
- This is used by subclasses to expose only a metadata dictionary that
- is the same across ImageService implementations.
-
- """
- return cls._propertify_metadata(metadata, cls.BASE_IMAGE_ATTRS)
-
- @classmethod
- def _translate_to_service(cls, metadata):
- """Return a metadata dict that is usable by the ImageService subclass.
-
- As an example, Glance has additional attributes (like 'location'); the
- BaseImageService considers these properties, but we need to translate
- these back to first-class attrs for sending to Glance. This method
- handles this by allowing you to specify the attributes an ImageService
- considers first-class.
-
- """
- if not cls.SERVICE_IMAGE_ATTRS:
- raise NotImplementedError(_('Cannot use this without specifying '
- 'SERVICE_IMAGE_ATTRS for subclass'))
- return cls._propertify_metadata(metadata, cls.SERVICE_IMAGE_ATTRS)
-
- @staticmethod
- def _propertify_metadata(metadata, keys):
- """Move unknown keys to a nested 'properties' dict.
-
- :returns: a new dict with the keys moved.
-
- """
- flattened = utils.flatten_dict(metadata)
- attributes, properties = utils.partition_dict(flattened, keys)
- attributes['properties'] = properties
- return attributes
diff --git a/nova/tests/public_key/dummy.fingerprint b/nova/tests/api/ec2/public_key/dummy.fingerprint
index 715bca27a..715bca27a 100644
--- a/nova/tests/public_key/dummy.fingerprint
+++ b/nova/tests/api/ec2/public_key/dummy.fingerprint
diff --git a/nova/tests/public_key/dummy.pub b/nova/tests/api/ec2/public_key/dummy.pub
index d4cf2bc0d..d4cf2bc0d 100644
--- a/nova/tests/public_key/dummy.pub
+++ b/nova/tests/api/ec2/public_key/dummy.pub
diff --git a/nova/tests/test_cloud.py b/nova/tests/api/ec2/test_cloud.py
index 7fe353b3d..7fe353b3d 100644
--- a/nova/tests/test_cloud.py
+++ b/nova/tests/api/ec2/test_cloud.py
diff --git a/nova/tests/api/openstack/fakes.py b/nova/tests/api/openstack/fakes.py
index 098b1e284..3a567f0cc 100644
--- a/nova/tests/api/openstack/fakes.py
+++ b/nova/tests/api/openstack/fakes.py
@@ -40,8 +40,8 @@ from nova.api.openstack import limits
from nova.auth.manager import User, Project
import nova.image.fake
from nova.image import glance
-from nova.image import service
from nova.tests import fake_flags
+from nova.tests.glance import stubs as glance_stubs
class Context(object):
@@ -83,7 +83,7 @@ def wsgi_app(inner_app10=None, inner_app11=None, fake_auth=True,
if fake_auth_context is not None:
ctxt = fake_auth_context
else:
- ctxt = context.RequestContext('fake', 'fake')
+ ctxt = context.RequestContext('fake', 'fake', auth_token=True)
api10 = openstack.FaultWrapper(api_auth.InjectContext(ctxt,
limits.RateLimitingMiddleware(inner_app10)))
api11 = openstack.FaultWrapper(api_auth.InjectContext(ctxt,
@@ -177,6 +177,39 @@ def stub_out_compute_api_backup(stubs):
stubs.Set(nova.compute.API, 'backup', backup)
+def _make_image_fixtures():
+ NOW_GLANCE_FORMAT = "2010-10-11T10:30:22"
+
+ image_id = 123
+ base_attrs = {'deleted': False}
+
+ fixtures = []
+
+ def add_fixture(**kwargs):
+ kwargs.update(base_attrs)
+ fixtures.append(kwargs)
+
+ # Public image
+ add_fixture(id=image_id, name='public image', is_public=True,
+ status='active', properties={'key1': 'value1'})
+ image_id += 1
+
+ # Snapshot for User 1
+ server_ref = 'http://localhost/v1.1/servers/42'
+ snapshot_properties = {'instance_ref': server_ref, 'user_id': 'fake'}
+ for status in ('queued', 'saving', 'active', 'killed',
+ 'deleted', 'pending_delete'):
+ add_fixture(id=image_id, name='%s snapshot' % status,
+ is_public=False, status=status,
+ properties=snapshot_properties)
+ image_id += 1
+
+ # Image without a name
+ add_fixture(id=image_id, is_public=True, status='active', properties={})
+
+ return fixtures
+
+
def stub_out_glance_add_image(stubs, sent_to_glance):
"""
We return the metadata sent to glance by modifying the sent_to_glance dict
@@ -192,91 +225,11 @@ def stub_out_glance_add_image(stubs, sent_to_glance):
stubs.Set(glance_client.Client, 'add_image', fake_add_image)
-def stub_out_glance(stubs, initial_fixtures=None):
-
- class FakeGlanceClient:
-
- def __init__(self, initial_fixtures):
- self.fixtures = initial_fixtures or []
-
- def _filter_images(self, filters=None, marker=None, limit=None):
- found = True
- if marker:
- found = False
- if limit == 0:
- limit = None
-
- fixtures = []
- count = 0
- for f in self.fixtures:
- if limit and count >= limit:
- break
- if found:
- fixtures.append(f)
- count = count + 1
- if f['id'] == marker:
- found = True
-
- return fixtures
-
- def fake_get_images(self, filters=None, marker=None, limit=None):
- fixtures = self._filter_images(filters, marker, limit)
- return [dict(id=f['id'], name=f['name'])
- for f in fixtures]
-
- def fake_get_images_detailed(self, filters=None,
- marker=None, limit=None):
- return self._filter_images(filters, marker, limit)
-
- def fake_get_image_meta(self, image_id):
- image = self._find_image(image_id)
- if image:
- return copy.deepcopy(image)
- raise glance_exc.NotFound
-
- def fake_add_image(self, image_meta, data=None):
- image_meta = copy.deepcopy(image_meta)
- image_id = ''.join(random.choice(string.letters)
- for _ in range(20))
- image_meta['id'] = image_id
- self.fixtures.append(image_meta)
- return copy.deepcopy(image_meta)
-
- def fake_update_image(self, image_id, image_meta, data=None):
- for attr in ('created_at', 'updated_at', 'deleted_at', 'deleted'):
- if attr in image_meta:
- del image_meta[attr]
-
- f = self._find_image(image_id)
- if not f:
- raise glance_exc.NotFound
-
- f.update(image_meta)
- return copy.deepcopy(f)
-
- def fake_delete_image(self, image_id):
- f = self._find_image(image_id)
- if not f:
- raise glance_exc.NotFound
-
- self.fixtures.remove(f)
-
- def _find_image(self, image_id):
- for f in self.fixtures:
- if str(f['id']) == str(image_id):
- return f
- return None
-
- GlanceClient = glance_client.Client
- fake = FakeGlanceClient(initial_fixtures)
-
- stubs.Set(GlanceClient, 'get_images', fake.fake_get_images)
- stubs.Set(GlanceClient, 'get_images_detailed',
- fake.fake_get_images_detailed)
- stubs.Set(GlanceClient, 'get_image_meta', fake.fake_get_image_meta)
- stubs.Set(GlanceClient, 'add_image', fake.fake_add_image)
- stubs.Set(GlanceClient, 'update_image', fake.fake_update_image)
- stubs.Set(GlanceClient, 'delete_image', fake.fake_delete_image)
+def stub_out_glance(stubs):
+ def fake_get_image_service():
+ client = glance_stubs.StubGlanceClient(_make_image_fixtures())
+ return nova.image.glance.GlanceImageService(client)
+ stubs.Set(nova.image, 'get_default_image_service', fake_get_image_service)
class FakeToken(object):
diff --git a/nova/tests/api/openstack/test_image_metadata.py b/nova/tests/api/openstack/test_image_metadata.py
index fe42e35e5..314c3c38e 100644
--- a/nova/tests/api/openstack/test_image_metadata.py
+++ b/nova/tests/api/openstack/test_image_metadata.py
@@ -23,7 +23,6 @@ from nova import flags
from nova.api import openstack
from nova import test
from nova.tests.api.openstack import fakes
-import nova.wsgi
FLAGS = flags.FLAGS
@@ -31,76 +30,20 @@ FLAGS = flags.FLAGS
class ImageMetaDataTest(test.TestCase):
- IMAGE_FIXTURES = [
- {'status': 'active',
- 'name': 'image1',
- 'deleted': False,
- 'container_format': None,
- 'checksum': None,
- 'created_at': '2011-03-22T17:40:15',
- 'disk_format': None,
- 'updated_at': '2011-03-22T17:40:15',
- 'id': '1',
- 'location': 'file:///var/lib/glance/images/1',
- 'is_public': True,
- 'deleted_at': None,
- 'properties': {
- 'key1': 'value1',
- 'key2': 'value2'},
- 'size': 5882349},
- {'status': 'active',
- 'name': 'image2',
- 'deleted': False,
- 'container_format': None,
- 'checksum': None,
- 'created_at': '2011-03-22T17:40:15',
- 'disk_format': None,
- 'updated_at': '2011-03-22T17:40:15',
- 'id': '2',
- 'location': 'file:///var/lib/glance/images/2',
- 'is_public': True,
- 'deleted_at': None,
- 'properties': {
- 'key1': 'value1',
- 'key2': 'value2'},
- 'size': 5882349},
- {'status': 'active',
- 'name': 'image3',
- 'deleted': False,
- 'container_format': None,
- 'checksum': None,
- 'created_at': '2011-03-22T17:40:15',
- 'disk_format': None,
- 'updated_at': '2011-03-22T17:40:15',
- 'id': '3',
- 'location': 'file:///var/lib/glance/images/2',
- 'is_public': True,
- 'deleted_at': None,
- 'properties': {},
- 'size': 5882349},
- ]
-
def setUp(self):
super(ImageMetaDataTest, self).setUp()
- self.flags(image_service='nova.image.glance.GlanceImageService')
- # NOTE(dprince) max out properties/metadata in image 3 for testing
- img3 = self.IMAGE_FIXTURES[2]
- for num in range(FLAGS.quota_metadata_items):
- img3['properties']['key%i' % num] = "blah"
- fakes.stub_out_glance(self.stubs, self.IMAGE_FIXTURES)
+ fakes.stub_out_glance(self.stubs)
def test_index(self):
- req = webob.Request.blank('/v1.1/123/images/1/metadata')
+ req = webob.Request.blank('/v1.1/123/images/123/metadata')
res = req.get_response(fakes.wsgi_app())
res_dict = json.loads(res.body)
self.assertEqual(200, res.status_int)
- expected = self.IMAGE_FIXTURES[0]['properties']
- self.assertEqual(len(expected), len(res_dict['metadata']))
- for (key, value) in res_dict['metadata'].items():
- self.assertEqual(value, res_dict['metadata'][key])
+ expected = {'metadata': {'key1': 'value1'}}
+ self.assertEqual(res_dict, expected)
def test_show(self):
- req = webob.Request.blank('/v1.1/fake/images/1/metadata/key1')
+ req = webob.Request.blank('/v1.1/fake/images/123/metadata/key1')
res = req.get_response(fakes.wsgi_app())
res_dict = json.loads(res.body)
self.assertEqual(200, res.status_int)
@@ -109,32 +52,38 @@ class ImageMetaDataTest(test.TestCase):
self.assertEqual('value1', res_dict['meta']['key1'])
def test_show_not_found(self):
- req = webob.Request.blank('/v1.1/fake/images/1/metadata/key9')
+ req = webob.Request.blank('/v1.1/fake/images/123/metadata/key9')
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(404, res.status_int)
+
+ def test_show_image_not_found(self):
+ req = webob.Request.blank('/v1.1/fake/images/100/metadata/key1')
res = req.get_response(fakes.wsgi_app())
self.assertEqual(404, res.status_int)
def test_create(self):
- req = webob.Request.blank('/v1.1/fake/images/2/metadata')
+ req = webob.Request.blank('/v1.1/fake/images/123/metadata')
req.method = 'POST'
- req.body = '{"metadata": {"key9": "value9"}}'
+ req.body = '{"metadata": {"key7": "value7"}}'
req.headers["content-type"] = "application/json"
res = req.get_response(fakes.wsgi_app())
self.assertEqual(200, res.status_int)
actual_output = json.loads(res.body)
+ expected_output = {'metadata': {'key1': 'value1', 'key7': 'value7'}}
+ self.assertEqual(expected_output, actual_output)
- expected_output = {
- 'metadata': {
- 'key1': 'value1',
- 'key2': 'value2',
- 'key9': 'value9',
- },
- }
+ def test_create_image_not_found(self):
+ req = webob.Request.blank('/v1.1/fake/images/100/metadata')
+ req.method = 'POST'
+ req.body = '{"metadata": {"key7": "value7"}}'
+ req.headers["content-type"] = "application/json"
+ res = req.get_response(fakes.wsgi_app())
- self.assertEqual(expected_output, actual_output)
+ self.assertEqual(404, res.status_int)
def test_update_all(self):
- req = webob.Request.blank('/v1.1/fake/images/1/metadata')
+ req = webob.Request.blank('/v1.1/fake/images/123/metadata')
req.method = 'PUT'
req.body = '{"metadata": {"key9": "value9"}}'
req.headers["content-type"] = "application/json"
@@ -142,17 +91,20 @@ class ImageMetaDataTest(test.TestCase):
self.assertEqual(200, res.status_int)
actual_output = json.loads(res.body)
+ expected_output = {'metadata': {'key9': 'value9'}}
+ self.assertEqual(expected_output, actual_output)
- expected_output = {
- 'metadata': {
- 'key9': 'value9',
- },
- }
+ def test_update_all_image_not_found(self):
+ req = webob.Request.blank('/v1.1/fake/images/100/metadata')
+ req.method = 'PUT'
+ req.body = '{"metadata": {"key9": "value9"}}'
+ req.headers["content-type"] = "application/json"
+ res = req.get_response(fakes.wsgi_app())
- self.assertEqual(expected_output, actual_output)
+ self.assertEqual(404, res.status_int)
def test_update_item(self):
- req = webob.Request.blank('/v1.1/fake/images/1/metadata/key1')
+ req = webob.Request.blank('/v1.1/fake/images/123/metadata/key1')
req.method = 'PUT'
req.body = '{"meta": {"key1": "zz"}}'
req.headers["content-type"] = "application/json"
@@ -160,15 +112,20 @@ class ImageMetaDataTest(test.TestCase):
self.assertEqual(200, res.status_int)
actual_output = json.loads(res.body)
- expected_output = {
- 'meta': {
- 'key1': 'zz',
- },
- }
+ expected_output = {'meta': {'key1': 'zz'}}
self.assertEqual(actual_output, expected_output)
+ def test_update_item_image_not_found(self):
+ req = webob.Request.blank('/v1.1/fake/images/100/metadata/key1')
+ req.method = 'PUT'
+ req.body = '{"meta": {"key1": "zz"}}'
+ req.headers["content-type"] = "application/json"
+ res = req.get_response(fakes.wsgi_app())
+
+ self.assertEqual(404, res.status_int)
+
def test_update_item_bad_body(self):
- req = webob.Request.blank('/v1.1/fake/images/1/metadata/key1')
+ req = webob.Request.blank('/v1.1/fake/images/123/metadata/key1')
req.method = 'PUT'
req.body = '{"key1": "zz"}'
req.headers["content-type"] = "application/json"
@@ -176,15 +133,18 @@ class ImageMetaDataTest(test.TestCase):
self.assertEqual(400, res.status_int)
def test_update_item_too_many_keys(self):
- req = webob.Request.blank('/v1.1/fake/images/1/metadata/key1')
+ req = webob.Request.blank('/v1.1/fake/images/123/metadata/key1')
req.method = 'PUT'
- req.body = '{"meta": {"key1": "value1", "key2": "value2"}}'
+ overload = {}
+ for num in range(FLAGS.quota_metadata_items + 1):
+ overload['key%s' % num] = 'value%s' % num
+ req.body = json.dumps({'meta': overload})
req.headers["content-type"] = "application/json"
res = req.get_response(fakes.wsgi_app())
self.assertEqual(400, res.status_int)
def test_update_item_body_uri_mismatch(self):
- req = webob.Request.blank('/v1.1/fake/images/1/metadata/bad')
+ req = webob.Request.blank('/v1.1/fake/images/123/metadata/bad')
req.method = 'PUT'
req.body = '{"meta": {"key1": "value1"}}'
req.headers["content-type"] = "application/json"
@@ -192,7 +152,7 @@ class ImageMetaDataTest(test.TestCase):
self.assertEqual(400, res.status_int)
def test_update_item_xml(self):
- req = webob.Request.blank('/v1.1/fake/images/1/metadata/key1')
+ req = webob.Request.blank('/v1.1/fake/images/123/metadata/key1')
req.method = 'PUT'
req.body = '<meta key="key1">five</meta>'
req.headers["content-type"] = "application/xml"
@@ -200,22 +160,24 @@ class ImageMetaDataTest(test.TestCase):
self.assertEqual(200, res.status_int)
actual_output = json.loads(res.body)
- expected_output = {
- 'meta': {
- 'key1': 'five',
- },
- }
+ expected_output = {'meta': {'key1': 'five'}}
self.assertEqual(actual_output, expected_output)
def test_delete(self):
- req = webob.Request.blank('/v1.1/fake/images/2/metadata/key1')
+ req = webob.Request.blank('/v1.1/fake/images/123/metadata/key1')
req.method = 'DELETE'
res = req.get_response(fakes.wsgi_app())
self.assertEqual(204, res.status_int)
self.assertEqual('', res.body)
def test_delete_not_found(self):
- req = webob.Request.blank('/v1.1/fake/images/2/metadata/blah')
+ req = webob.Request.blank('/v1.1/fake/images/123/metadata/blah')
+ req.method = 'DELETE'
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(404, res.status_int)
+
+ def test_delete_image_not_found(self):
+ req = webob.Request.blank('/v1.1/fake/images/100/metadata/key1')
req.method = 'DELETE'
res = req.get_response(fakes.wsgi_app())
self.assertEqual(404, res.status_int)
@@ -225,7 +187,7 @@ class ImageMetaDataTest(test.TestCase):
for num in range(FLAGS.quota_metadata_items + 1):
data['metadata']['key%i' % num] = "blah"
json_string = str(data).replace("\'", "\"")
- req = webob.Request.blank('/v1.1/fake/images/2/metadata')
+ req = webob.Request.blank('/v1.1/fake/images/123/metadata')
req.method = 'POST'
req.body = json_string
req.headers["content-type"] = "application/json"
@@ -233,7 +195,8 @@ class ImageMetaDataTest(test.TestCase):
self.assertEqual(413, res.status_int)
def test_too_many_metadata_items_on_put(self):
- req = webob.Request.blank('/v1.1/fake/images/3/metadata/blah')
+ FLAGS.quota_metadata_items = 1
+ req = webob.Request.blank('/v1.1/fake/images/123/metadata/blah')
req.method = 'PUT'
req.body = '{"meta": {"blah": "blah"}}'
req.headers["content-type"] = "application/json"
diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py
index 10c7663ff..7759b52ef 100644
--- a/nova/tests/api/openstack/test_images.py
+++ b/nova/tests/api/openstack/test_images.py
@@ -22,358 +22,72 @@ and as a WSGI layer
import copy
import json
-import os
-import shutil
-import tempfile
import xml.dom.minidom as minidom
import mox
import stubout
import webob
-from glance import client as glance_client
from nova import context
-from nova import exception
-from nova import test
-from nova import utils
import nova.api.openstack
from nova.api.openstack import images
+from nova import test
from nova.tests.api.openstack import fakes
-class _BaseImageServiceTests(test.TestCase):
- """Tasks to test for all image services"""
-
- def __init__(self, *args, **kwargs):
- super(_BaseImageServiceTests, self).__init__(*args, **kwargs)
- self.service = None
- self.context = None
-
- def test_create(self):
- fixture = self._make_fixture('test image')
- num_images = len(self.service.index(self.context))
-
- image_id = self.service.create(self.context, fixture)['id']
-
- self.assertNotEquals(None, image_id)
- self.assertEquals(num_images + 1,
- len(self.service.index(self.context)))
-
- def test_create_and_show_non_existing_image(self):
- fixture = self._make_fixture('test image')
- num_images = len(self.service.index(self.context))
-
- image_id = self.service.create(self.context, fixture)['id']
-
- self.assertNotEquals(None, image_id)
- self.assertRaises(exception.NotFound,
- self.service.show,
- self.context,
- 'bad image id')
-
- def test_create_and_show_non_existing_image_by_name(self):
- fixture = self._make_fixture('test image')
- num_images = len(self.service.index(self.context))
-
- image_id = self.service.create(self.context, fixture)['id']
-
- self.assertNotEquals(None, image_id)
- self.assertRaises(exception.ImageNotFound,
- self.service.show_by_name,
- self.context,
- 'bad image id')
-
- def test_update(self):
- fixture = self._make_fixture('test image')
- image_id = self.service.create(self.context, fixture)['id']
- fixture['status'] = 'in progress'
-
- self.service.update(self.context, image_id, fixture)
-
- new_image_data = self.service.show(self.context, image_id)
- self.assertEquals('in progress', new_image_data['status'])
-
- def test_delete(self):
- fixture1 = self._make_fixture('test image 1')
- fixture2 = self._make_fixture('test image 2')
- fixtures = [fixture1, fixture2]
-
- num_images = len(self.service.index(self.context))
- self.assertEquals(0, num_images, str(self.service.index(self.context)))
+NOW_API_FORMAT = "2010-10-11T10:30:22Z"
- ids = []
- for fixture in fixtures:
- new_id = self.service.create(self.context, fixture)['id']
- ids.append(new_id)
- num_images = len(self.service.index(self.context))
- self.assertEquals(2, num_images, str(self.service.index(self.context)))
-
- self.service.delete(self.context, ids[0])
-
- num_images = len(self.service.index(self.context))
- self.assertEquals(1, num_images)
-
- def test_index(self):
- fixture = self._make_fixture('test image')
- image_id = self.service.create(self.context, fixture)['id']
- image_metas = self.service.index(self.context)
- expected = [{'id': 'DONTCARE', 'name': 'test image'}]
- self.assertDictListMatch(image_metas, expected)
-
- @staticmethod
- def _make_fixture(name):
- fixture = {'name': name,
- 'updated': None,
- 'created': None,
- 'status': None,
- 'is_public': True}
- return fixture
-
-
-class GlanceImageServiceTest(_BaseImageServiceTests):
-
- """Tests the Glance image service, in particular that metadata translation
- works properly.
-
- At a high level, the translations involved are:
-
- 1. Glance -> ImageService - This is needed so we can support
- multple ImageServices (Glance, Local, etc)
-
- 2. ImageService -> API - This is needed so we can support multple
- APIs (OpenStack, EC2)
- """
- def setUp(self):
- super(GlanceImageServiceTest, self).setUp()
- self.stubs = stubout.StubOutForTesting()
- fakes.stub_out_glance(self.stubs)
- fakes.stub_out_compute_api_snapshot(self.stubs)
- service_class = 'nova.image.glance.GlanceImageService'
- self.service = utils.import_object(service_class)
- self.context = context.RequestContext('fake', 'fake')
- self.service.delete_all()
- self.sent_to_glance = {}
- fakes.stub_out_glance_add_image(self.stubs, self.sent_to_glance)
-
- def tearDown(self):
- self.stubs.UnsetAll()
- super(GlanceImageServiceTest, self).tearDown()
-
- def test_create_with_instance_id(self):
- """Ensure instance_id is persisted as an image-property"""
- fixture = {'name': 'test image',
- 'is_public': False,
- 'properties': {'instance_id': '42', 'user_id': 'fake'}}
-
- image_id = self.service.create(self.context, fixture)['id']
- expected = fixture
- self.assertDictMatch(self.sent_to_glance['metadata'], expected)
-
- image_meta = self.service.show(self.context, image_id)
- expected = {'id': image_id,
- 'name': 'test image',
- 'is_public': False,
- 'properties': {'instance_id': '42', 'user_id': 'fake'}}
- self.assertDictMatch(image_meta, expected)
-
- image_metas = self.service.detail(self.context)
- self.assertDictMatch(image_metas[0], expected)
-
- def test_create_without_instance_id(self):
- """
- Ensure we can create an image without having to specify an
- instance_id. Public images are an example of an image not tied to an
- instance.
- """
- fixture = {'name': 'test image'}
- image_id = self.service.create(self.context, fixture)['id']
-
- expected = {'name': 'test image', 'properties': {}}
- self.assertDictMatch(self.sent_to_glance['metadata'], expected)
-
- def test_index_default_limit(self):
- fixtures = []
- ids = []
- for i in range(10):
- fixture = self._make_fixture('TestImage %d' % (i))
- fixtures.append(fixture)
- ids.append(self.service.create(self.context, fixture)['id'])
-
- image_metas = self.service.index(self.context)
- i = 0
- for meta in image_metas:
- expected = {'id': 'DONTCARE',
- 'name': 'TestImage %d' % (i)}
- self.assertDictMatch(meta, expected)
- i = i + 1
-
- def test_index_marker(self):
- fixtures = []
- ids = []
- for i in range(10):
- fixture = self._make_fixture('TestImage %d' % (i))
- fixtures.append(fixture)
- ids.append(self.service.create(self.context, fixture)['id'])
-
- image_metas = self.service.index(self.context, marker=ids[1])
- self.assertEquals(len(image_metas), 8)
- i = 2
- for meta in image_metas:
- expected = {'id': 'DONTCARE',
- 'name': 'TestImage %d' % (i)}
- self.assertDictMatch(meta, expected)
- i = i + 1
-
- def test_index_limit(self):
- fixtures = []
- ids = []
- for i in range(10):
- fixture = self._make_fixture('TestImage %d' % (i))
- fixtures.append(fixture)
- ids.append(self.service.create(self.context, fixture)['id'])
-
- image_metas = self.service.index(self.context, limit=3)
- self.assertEquals(len(image_metas), 3)
-
- def test_index_marker_and_limit(self):
- fixtures = []
- ids = []
- for i in range(10):
- fixture = self._make_fixture('TestImage %d' % (i))
- fixtures.append(fixture)
- ids.append(self.service.create(self.context, fixture)['id'])
-
- image_metas = self.service.index(self.context, marker=ids[3], limit=1)
- self.assertEquals(len(image_metas), 1)
- i = 4
- for meta in image_metas:
- expected = {'id': 'DONTCARE',
- 'name': 'TestImage %d' % (i)}
- self.assertDictMatch(meta, expected)
- i = i + 1
-
- def test_detail_marker(self):
- fixtures = []
- ids = []
- for i in range(10):
- fixture = self._make_fixture('TestImage %d' % (i))
- fixtures.append(fixture)
- ids.append(self.service.create(self.context, fixture)['id'])
-
- image_metas = self.service.detail(self.context, marker=ids[1])
- self.assertEquals(len(image_metas), 8)
- i = 2
- for meta in image_metas:
- expected = {
- 'id': 'DONTCARE',
- 'status': None,
- 'is_public': True,
- 'name': 'TestImage %d' % (i),
- 'properties': {
- 'updated': None,
- 'created': None,
- },
- }
-
- self.assertDictMatch(meta, expected)
- i = i + 1
-
- def test_detail_limit(self):
- fixtures = []
- ids = []
- for i in range(10):
- fixture = self._make_fixture('TestImage %d' % (i))
- fixtures.append(fixture)
- ids.append(self.service.create(self.context, fixture)['id'])
-
- image_metas = self.service.detail(self.context, limit=3)
- self.assertEquals(len(image_metas), 3)
-
- def test_detail_marker_and_limit(self):
- fixtures = []
- ids = []
- for i in range(10):
- fixture = self._make_fixture('TestImage %d' % (i))
- fixtures.append(fixture)
- ids.append(self.service.create(self.context, fixture)['id'])
-
- image_metas = self.service.detail(self.context, marker=ids[3], limit=3)
- self.assertEquals(len(image_metas), 3)
- i = 4
- for meta in image_metas:
- expected = {
- 'id': 'DONTCARE',
- 'status': None,
- 'is_public': True,
- 'name': 'TestImage %d' % (i),
- 'properties': {
- 'updated': None, 'created': None},
- }
- self.assertDictMatch(meta, expected)
- i = i + 1
-
-
-class ImageControllerWithGlanceServiceTest(test.TestCase):
+class ImagesTest(test.TestCase):
"""
Test of the OpenStack API /images application controller w/Glance.
"""
- NOW_GLANCE_FORMAT = "2010-10-11T10:30:22"
- NOW_API_FORMAT = "2010-10-11T10:30:22Z"
def setUp(self):
"""Run before each test."""
- super(ImageControllerWithGlanceServiceTest, self).setUp()
- self.flags(image_service='nova.image.glance.GlanceImageService')
+ super(ImagesTest, self).setUp()
self.stubs = stubout.StubOutForTesting()
fakes.stub_out_networking(self.stubs)
fakes.stub_out_rate_limiting(self.stubs)
fakes.stub_out_key_pair_funcs(self.stubs)
- self.fixtures = self._make_image_fixtures()
- fakes.stub_out_glance(self.stubs, initial_fixtures=self.fixtures)
fakes.stub_out_compute_api_snapshot(self.stubs)
fakes.stub_out_compute_api_backup(self.stubs)
+ fakes.stub_out_glance(self.stubs)
def tearDown(self):
"""Run after each test."""
self.stubs.UnsetAll()
- super(ImageControllerWithGlanceServiceTest, self).tearDown()
+ super(ImagesTest, self).tearDown()
def _get_fake_context(self):
class Context(object):
project_id = 'fake'
+ auth_token = True
return Context()
- def _applicable_fixture(self, fixture, user_id):
- """Determine if this fixture is applicable for given user id."""
- is_public = fixture["is_public"]
- try:
- uid = fixture["properties"]["user_id"]
- except KeyError:
- uid = None
- return uid == user_id or is_public
-
def test_get_image_index(self):
request = webob.Request.blank('/v1.0/images')
- response = request.get_response(fakes.wsgi_app())
+ app = fakes.wsgi_app(fake_auth_context=self._get_fake_context())
+ response = request.get_response(app)
response_dict = json.loads(response.body)
response_list = response_dict["images"]
- expected = [{'id': 123, 'name': 'public image'},
- {'id': 124, 'name': 'queued snapshot'},
- {'id': 125, 'name': 'saving snapshot'},
- {'id': 126, 'name': 'active snapshot'},
- {'id': 127, 'name': 'killed snapshot'},
- {'id': 128, 'name': 'deleted snapshot'},
- {'id': 129, 'name': 'pending_delete snapshot'},
- {'id': 131, 'name': None}]
+ expected = [{'id': '123', 'name': 'public image'},
+ {'id': '124', 'name': 'queued snapshot'},
+ {'id': '125', 'name': 'saving snapshot'},
+ {'id': '126', 'name': 'active snapshot'},
+ {'id': '127', 'name': 'killed snapshot'},
+ {'id': '128', 'name': 'deleted snapshot'},
+ {'id': '129', 'name': 'pending_delete snapshot'},
+ {'id': '130', 'name': None}]
self.assertDictListMatch(response_list, expected)
def test_get_image(self):
request = webob.Request.blank('/v1.0/images/123')
- response = request.get_response(fakes.wsgi_app())
+ app = fakes.wsgi_app(fake_auth_context=self._get_fake_context())
+ response = request.get_response(app)
self.assertEqual(200, response.status_int)
@@ -381,20 +95,21 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
expected_image = {
"image": {
- "id": 123,
+ "id": "123",
"name": "public image",
- "updated": self.NOW_API_FORMAT,
- "created": self.NOW_API_FORMAT,
+ "updated": NOW_API_FORMAT,
+ "created": NOW_API_FORMAT,
"status": "ACTIVE",
"progress": 100,
},
}
- self.assertEqual(expected_image, actual_image)
+ self.assertDictMatch(expected_image, actual_image)
def test_get_image_v1_1(self):
request = webob.Request.blank('/v1.1/fake/images/124')
- response = request.get_response(fakes.wsgi_app())
+ app = fakes.wsgi_app(fake_auth_context=self._get_fake_context())
+ response = request.get_response(app)
actual_image = json.loads(response.body)
@@ -405,10 +120,10 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
expected_image = {
"image": {
- "id": 124,
+ "id": "124",
"name": "queued snapshot",
- "updated": self.NOW_API_FORMAT,
- "created": self.NOW_API_FORMAT,
+ "updated": NOW_API_FORMAT,
+ "created": NOW_API_FORMAT,
"status": "SAVING",
"progress": 0,
'server': {
@@ -442,11 +157,12 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
def test_get_image_xml(self):
request = webob.Request.blank('/v1.0/images/123')
request.accept = "application/xml"
- response = request.get_response(fakes.wsgi_app())
+ app = fakes.wsgi_app(fake_auth_context=self._get_fake_context())
+ response = request.get_response(app)
actual_image = minidom.parseString(response.body.replace(" ", ""))
- expected_now = self.NOW_API_FORMAT
+ expected_now = NOW_API_FORMAT
expected_image = minidom.parseString("""
<image id="123"
name="public image"
@@ -460,15 +176,16 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
self.assertEqual(expected_image.toxml(), actual_image.toxml())
def test_get_image_xml_no_name(self):
- request = webob.Request.blank('/v1.0/images/131')
+ request = webob.Request.blank('/v1.0/images/130')
request.accept = "application/xml"
- response = request.get_response(fakes.wsgi_app())
+ app = fakes.wsgi_app(fake_auth_context=self._get_fake_context())
+ response = request.get_response(app)
actual_image = minidom.parseString(response.body.replace(" ", ""))
- expected_now = self.NOW_API_FORMAT
+ expected_now = NOW_API_FORMAT
expected_image = minidom.parseString("""
- <image id="131"
+ <image id="130"
name="None"
updated="%(expected_now)s"
created="%(expected_now)s"
@@ -553,106 +270,198 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
def test_get_image_index_v1_1(self):
request = webob.Request.blank('/v1.1/fake/images')
- response = request.get_response(fakes.wsgi_app())
+ app = fakes.wsgi_app(fake_auth_context=self._get_fake_context())
+ response = request.get_response(app)
response_dict = json.loads(response.body)
response_list = response_dict["images"]
- fixtures = copy.copy(self.fixtures)
-
- for image in fixtures:
- if not self._applicable_fixture(image, "fake"):
- fixtures.remove(image)
- continue
-
- href = "http://localhost/v1.1/fake/images/%s" % image["id"]
- bookmark = "http://localhost/fake/images/%s" % image["id"]
- test_image = {
- "id": image["id"],
- "name": image["name"],
+ expected = [
+ {
+ "id": "123",
+ "name": "public image",
+ "links": [
+ {
+ "rel": "self",
+ "href": "http://localhost/v1.1/fake/images/123",
+ },
+ {
+ "rel": "bookmark",
+ "href": "http://localhost/fake/images/123",
+ },
+ ],
+ },
+ {
+ "id": "124",
+ "name": "queued snapshot",
+ "links": [
+ {
+ "rel": "self",
+ "href": "http://localhost/v1.1/fake/images/124",
+ },
+ {
+ "rel": "bookmark",
+ "href": "http://localhost/fake/images/124",
+ },
+ ],
+ },
+ {
+ "id": "125",
+ "name": "saving snapshot",
+ "links": [
+ {
+ "rel": "self",
+ "href": "http://localhost/v1.1/fake/images/125",
+ },
+ {
+ "rel": "bookmark",
+ "href": "http://localhost/fake/images/125",
+ },
+ ],
+ },
+ {
+ "id": "126",
+ "name": "active snapshot",
+ "links": [
+ {
+ "rel": "self",
+ "href": "http://localhost/v1.1/fake/images/126",
+ },
+ {
+ "rel": "bookmark",
+ "href": "http://localhost/fake/images/126",
+ },
+ ],
+ },
+ {
+ "id": "127",
+ "name": "killed snapshot",
"links": [
{
"rel": "self",
- "href": href,
+ "href": "http://localhost/v1.1/fake/images/127",
},
{
"rel": "bookmark",
- "href": bookmark,
+ "href": "http://localhost/fake/images/127",
},
],
- }
- self.assertTrue(test_image in response_list)
+ },
+ {
+ "id": "128",
+ "name": "deleted snapshot",
+ "links": [
+ {
+ "rel": "self",
+ "href": "http://localhost/v1.1/fake/images/128",
+ },
+ {
+ "rel": "bookmark",
+ "href": "http://localhost/fake/images/128",
+ },
+ ],
+ },
+ {
+ "id": "129",
+ "name": "pending_delete snapshot",
+ "links": [
+ {
+ "rel": "self",
+ "href": "http://localhost/v1.1/fake/images/129",
+ },
+ {
+ "rel": "bookmark",
+ "href": "http://localhost/fake/images/129",
+ },
+ ],
+ },
+ {
+ "id": "130",
+ "name": None,
+ "links": [
+ {
+ "rel": "self",
+ "href": "http://localhost/v1.1/fake/images/130",
+ },
+ {
+ "rel": "bookmark",
+ "href": "http://localhost/fake/images/130",
+ },
+ ],
+ },
+ ]
- self.assertEqual(len(response_list), len(fixtures))
+ self.assertDictListMatch(response_list, expected)
def test_get_image_details(self):
request = webob.Request.blank('/v1.0/images/detail')
- response = request.get_response(fakes.wsgi_app())
+ app = fakes.wsgi_app(fake_auth_context=self._get_fake_context())
+ response = request.get_response(app)
response_dict = json.loads(response.body)
response_list = response_dict["images"]
expected = [{
- 'id': 123,
+ 'id': '123',
'name': 'public image',
- 'updated': self.NOW_API_FORMAT,
- 'created': self.NOW_API_FORMAT,
+ 'updated': NOW_API_FORMAT,
+ 'created': NOW_API_FORMAT,
'status': 'ACTIVE',
'progress': 100,
},
{
- 'id': 124,
+ 'id': '124',
'name': 'queued snapshot',
- 'updated': self.NOW_API_FORMAT,
- 'created': self.NOW_API_FORMAT,
+ 'updated': NOW_API_FORMAT,
+ 'created': NOW_API_FORMAT,
'status': 'SAVING',
'progress': 0,
},
{
- 'id': 125,
+ 'id': '125',
'name': 'saving snapshot',
- 'updated': self.NOW_API_FORMAT,
- 'created': self.NOW_API_FORMAT,
+ 'updated': NOW_API_FORMAT,
+ 'created': NOW_API_FORMAT,
'status': 'SAVING',
'progress': 0,
},
{
- 'id': 126,
+ 'id': '126',
'name': 'active snapshot',
- 'updated': self.NOW_API_FORMAT,
- 'created': self.NOW_API_FORMAT,
+ 'updated': NOW_API_FORMAT,
+ 'created': NOW_API_FORMAT,
'status': 'ACTIVE',
'progress': 100,
},
{
- 'id': 127,
+ 'id': '127',
'name': 'killed snapshot',
- 'updated': self.NOW_API_FORMAT,
- 'created': self.NOW_API_FORMAT,
+ 'updated': NOW_API_FORMAT,
+ 'created': NOW_API_FORMAT,
'status': 'ERROR',
'progress': 0,
},
{
- 'id': 128,
+ 'id': '128',
'name': 'deleted snapshot',
- 'updated': self.NOW_API_FORMAT,
- 'created': self.NOW_API_FORMAT,
+ 'updated': NOW_API_FORMAT,
+ 'created': NOW_API_FORMAT,
'status': 'DELETED',
'progress': 0,
},
{
- 'id': 129,
+ 'id': '129',
'name': 'pending_delete snapshot',
- 'updated': self.NOW_API_FORMAT,
- 'created': self.NOW_API_FORMAT,
+ 'updated': NOW_API_FORMAT,
+ 'created': NOW_API_FORMAT,
'status': 'DELETED',
'progress': 0,
},
{
- 'id': 131,
+ 'id': '130',
'name': None,
- 'updated': self.NOW_API_FORMAT,
- 'created': self.NOW_API_FORMAT,
+ 'updated': NOW_API_FORMAT,
+ 'created': NOW_API_FORMAT,
'status': 'ACTIVE',
'progress': 100,
}]
@@ -661,7 +470,8 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
def test_get_image_details_v1_1(self):
request = webob.Request.blank('/v1.1/fake/images/detail')
- response = request.get_response(fakes.wsgi_app())
+ app = fakes.wsgi_app(fake_auth_context=self._get_fake_context())
+ response = request.get_response(app)
response_dict = json.loads(response.body)
response_list = response_dict["images"]
@@ -669,11 +479,11 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
server_bookmark = "http://localhost/servers/42"
expected = [{
- 'id': 123,
+ 'id': '123',
'name': 'public image',
- 'metadata': {},
- 'updated': self.NOW_API_FORMAT,
- 'created': self.NOW_API_FORMAT,
+ 'metadata': {'key1': 'value1'},
+ 'updated': NOW_API_FORMAT,
+ 'created': NOW_API_FORMAT,
'status': 'ACTIVE',
'progress': 100,
"links": [{
@@ -686,14 +496,14 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
}],
},
{
- 'id': 124,
+ 'id': '124',
'name': 'queued snapshot',
'metadata': {
u'instance_ref': u'http://localhost/v1.1/servers/42',
u'user_id': u'fake',
},
- 'updated': self.NOW_API_FORMAT,
- 'created': self.NOW_API_FORMAT,
+ 'updated': NOW_API_FORMAT,
+ 'created': NOW_API_FORMAT,
'status': 'SAVING',
'progress': 0,
'server': {
@@ -717,14 +527,14 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
}],
},
{
- 'id': 125,
+ 'id': '125',
'name': 'saving snapshot',
'metadata': {
u'instance_ref': u'http://localhost/v1.1/servers/42',
u'user_id': u'fake',
},
- 'updated': self.NOW_API_FORMAT,
- 'created': self.NOW_API_FORMAT,
+ 'updated': NOW_API_FORMAT,
+ 'created': NOW_API_FORMAT,
'status': 'SAVING',
'progress': 0,
'server': {
@@ -748,14 +558,14 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
}],
},
{
- 'id': 126,
+ 'id': '126',
'name': 'active snapshot',
'metadata': {
u'instance_ref': u'http://localhost/v1.1/servers/42',
u'user_id': u'fake',
},
- 'updated': self.NOW_API_FORMAT,
- 'created': self.NOW_API_FORMAT,
+ 'updated': NOW_API_FORMAT,
+ 'created': NOW_API_FORMAT,
'status': 'ACTIVE',
'progress': 100,
'server': {
@@ -779,14 +589,14 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
}],
},
{
- 'id': 127,
+ 'id': '127',
'name': 'killed snapshot',
'metadata': {
u'instance_ref': u'http://localhost/v1.1/servers/42',
u'user_id': u'fake',
},
- 'updated': self.NOW_API_FORMAT,
- 'created': self.NOW_API_FORMAT,
+ 'updated': NOW_API_FORMAT,
+ 'created': NOW_API_FORMAT,
'status': 'ERROR',
'progress': 0,
'server': {
@@ -810,14 +620,14 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
}],
},
{
- 'id': 128,
+ 'id': '128',
'name': 'deleted snapshot',
'metadata': {
u'instance_ref': u'http://localhost/v1.1/servers/42',
u'user_id': u'fake',
},
- 'updated': self.NOW_API_FORMAT,
- 'created': self.NOW_API_FORMAT,
+ 'updated': NOW_API_FORMAT,
+ 'created': NOW_API_FORMAT,
'status': 'DELETED',
'progress': 0,
'server': {
@@ -841,14 +651,14 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
}],
},
{
- 'id': 129,
+ 'id': '129',
'name': 'pending_delete snapshot',
'metadata': {
u'instance_ref': u'http://localhost/v1.1/servers/42',
u'user_id': u'fake',
},
- 'updated': self.NOW_API_FORMAT,
- 'created': self.NOW_API_FORMAT,
+ 'updated': NOW_API_FORMAT,
+ 'created': NOW_API_FORMAT,
'status': 'DELETED',
'progress': 0,
'server': {
@@ -872,20 +682,20 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
}],
},
{
- 'id': 131,
+ 'id': '130',
'name': None,
'metadata': {},
- 'updated': self.NOW_API_FORMAT,
- 'created': self.NOW_API_FORMAT,
+ 'updated': NOW_API_FORMAT,
+ 'created': NOW_API_FORMAT,
'status': 'ACTIVE',
'progress': 100,
"links": [{
"rel": "self",
- "href": "http://localhost/v1.1/fake/images/131",
+ "href": "http://localhost/v1.1/fake/images/130",
},
{
"rel": "bookmark",
- "href": "http://localhost/fake/images/131",
+ "href": "http://localhost/fake/images/130",
}],
},
]
@@ -1097,11 +907,12 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
def test_get_image_found(self):
req = webob.Request.blank('/v1.0/images/123')
- res = req.get_response(fakes.wsgi_app())
+ app = fakes.wsgi_app(fake_auth_context=self._get_fake_context())
+ res = req.get_response(app)
image_meta = json.loads(res.body)['image']
- expected = {'id': 123, 'name': 'public image',
- 'updated': self.NOW_API_FORMAT,
- 'created': self.NOW_API_FORMAT, 'status': 'ACTIVE',
+ expected = {'id': '123', 'name': 'public image',
+ 'updated': NOW_API_FORMAT,
+ 'created': NOW_API_FORMAT, 'status': 'ACTIVE',
'progress': 100}
self.assertDictMatch(image_meta, expected)
@@ -1110,14 +921,6 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 404)
- def test_get_image_not_owned(self):
- """We should return a 404 if we request an image that doesn't belong
- to us
- """
- req = webob.Request.blank('/v1.0/images/130')
- res = req.get_response(fakes.wsgi_app())
- self.assertEqual(res.status_int, 404)
-
def test_create_image(self):
body = dict(image=dict(serverId='123', name='Snapshot 1'))
req = webob.Request.blank('/v1.0/images')
@@ -1160,51 +963,6 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
response = req.get_response(fakes.wsgi_app())
self.assertEqual(400, response.status_int)
- @classmethod
- def _make_image_fixtures(cls):
- image_id = 123
- base_attrs = {'created_at': cls.NOW_GLANCE_FORMAT,
- 'updated_at': cls.NOW_GLANCE_FORMAT,
- 'deleted_at': None,
- 'deleted': False}
-
- fixtures = []
-
- def add_fixture(**kwargs):
- kwargs.update(base_attrs)
- fixtures.append(kwargs)
-
- # Public image
- add_fixture(id=image_id, name='public image', is_public=True,
- status='active', properties={})
- image_id += 1
-
- # Snapshot for User 1
- server_ref = 'http://localhost/v1.1/servers/42'
- snapshot_properties = {'instance_ref': server_ref, 'user_id': 'fake'}
- statuses = ('queued', 'saving', 'active', 'killed',
- 'deleted', 'pending_delete')
- for status in statuses:
- add_fixture(id=image_id, name='%s snapshot' % status,
- is_public=False, status=status,
- properties=snapshot_properties)
- image_id += 1
-
- # Snapshot for User 2
- other_snapshot_properties = {'instance_id': '43', 'user_id': 'other'}
- add_fixture(id=image_id, name='someone elses snapshot',
- is_public=False, status='active',
- properties=other_snapshot_properties)
-
- image_id += 1
-
- # Image without a name
- add_fixture(id=image_id, is_public=True, status='active',
- properties={})
- image_id += 1
-
- return fixtures
-
class ImageXMLSerializationTest(test.TestCase):
diff --git a/nova/tests/glance/stubs.py b/nova/tests/glance/stubs.py
index 6b74e671c..1567393e3 100644
--- a/nova/tests/glance/stubs.py
+++ b/nova/tests/glance/stubs.py
@@ -16,6 +16,7 @@
import StringIO
+from nova import exception
from nova.image import glance
@@ -78,3 +79,70 @@ class FakeGlance(object):
def get_image(self, image_id):
image = self.IMAGE_FIXTURES[int(image_id)]
return image['image_meta'], image['image_data']
+
+
+NOW_GLANCE_FORMAT = "2010-10-11T10:30:22"
+
+
+class StubGlanceClient(object):
+
+ def __init__(self, images=None):
+ self.images = []
+ _images = images or []
+ map(lambda image: self.add_image(image, None), _images)
+
+ def set_auth_token(self, auth_tok):
+ pass
+
+ def get_image_meta(self, image_id):
+ for image in self.images:
+ if image['id'] == str(image_id):
+ return image
+ raise exception.ImageNotFound(image_id=image_id)
+
+ #TODO(bcwaldon): implement filters
+ def get_images_detailed(self, filters=None, marker=None, limit=3):
+ if marker is None:
+ index = 0
+ else:
+ for index, image in enumerate(self.images):
+ if image['id'] == str(marker):
+ index += 1
+ break
+
+ return self.images[index:index + limit]
+
+ def get_image(self, image_id):
+ return self.get_image_meta(image_id), []
+
+ def add_image(self, metadata, data):
+ metadata['created_at'] = NOW_GLANCE_FORMAT
+ metadata['updated_at'] = NOW_GLANCE_FORMAT
+
+ self.images.append(metadata)
+
+ try:
+ image_id = str(metadata['id'])
+ except KeyError:
+ # auto-generate an id if one wasn't provided
+ image_id = str(len(self.images))
+
+ self.images[-1]['id'] = image_id
+
+ return self.images[-1]
+
+ def update_image(self, image_id, metadata, data):
+ for i, image in enumerate(self.images):
+ if image['id'] == str(image_id):
+ if 'id' in metadata:
+ metadata['id'] = str(metadata['id'])
+ self.images[i].update(metadata)
+ return self.images[i]
+ raise exception.ImageNotFound(image_id=image_id)
+
+ def delete_image(self, image_id):
+ for i, image in enumerate(self.images):
+ if image['id'] == image_id:
+ del self.images[i]
+ return
+ raise exception.ImageNotFound(image_id=image_id)
diff --git a/nova/tests/image/test_glance.py b/nova/tests/image/test_glance.py
index b1ebd8436..290c9a04a 100644
--- a/nova/tests/image/test_glance.py
+++ b/nova/tests/image/test_glance.py
@@ -17,47 +17,14 @@
import datetime
-import unittest
+import stubout
+from nova.tests.api.openstack import fakes
from nova import context
from nova import exception
-from nova import test
from nova.image import glance
-
-
-class StubGlanceClient(object):
-
- def __init__(self, images, add_response=None, update_response=None):
- self.images = images
- self.add_response = add_response
- self.update_response = update_response
-
- def set_auth_token(self, auth_tok):
- pass
-
- def get_image_meta(self, image_id):
- return self.images[image_id]
-
- def get_images_detailed(self, filters=None, marker=None, limit=None):
- images = self.images.values()
- if marker is None:
- index = 0
- else:
- for index, image in enumerate(images):
- if image['id'] == marker:
- index += 1
- break
- # default to a page size of 3 to ensure we flex the pagination code
- return images[index:index + 3]
-
- def get_image(self, image_id):
- return self.images[image_id], []
-
- def add_image(self, metadata, data):
- return self.add_response
-
- def update_image(self, image_id, metadata, data):
- return self.update_response
+from nova import test
+from nova.tests.glance import stubs as glance_stubs
class NullWriter(object):
@@ -67,218 +34,7 @@ class NullWriter(object):
pass
-class BaseGlanceTest(unittest.TestCase):
- NOW_GLANCE_OLD_FORMAT = "2010-10-11T10:30:22"
- NOW_GLANCE_FORMAT = "2010-10-11T10:30:22.000000"
- NOW_DATETIME = datetime.datetime(2010, 10, 11, 10, 30, 22)
-
- def setUp(self):
- self.client = StubGlanceClient(None)
- self.service = glance.GlanceImageService(client=self.client)
- self.context = context.RequestContext(None, None)
-
- def assertDateTimesFilled(self, image_meta):
- self.assertEqual(image_meta['created_at'], self.NOW_DATETIME)
- self.assertEqual(image_meta['updated_at'], self.NOW_DATETIME)
- self.assertEqual(image_meta['deleted_at'], self.NOW_DATETIME)
-
- def assertDateTimesEmpty(self, image_meta):
- self.assertEqual(image_meta['updated_at'], None)
- self.assertEqual(image_meta['deleted_at'], None)
-
- def assertDateTimesBlank(self, image_meta):
- self.assertEqual(image_meta['updated_at'], '')
- self.assertEqual(image_meta['deleted_at'], '')
-
-
-class TestGlanceImageServiceProperties(BaseGlanceTest):
- def test_show_passes_through_to_client(self):
- """Ensure attributes which aren't BASE_IMAGE_ATTRS are stored in the
- properties dict
- """
- fixtures = {'image1': {'id': '1', 'name': 'image1', 'is_public': True,
- 'foo': 'bar',
- 'properties': {'prop1': 'propvalue1'}}}
- self.client.images = fixtures
- image_meta = self.service.show(self.context, 'image1')
-
- expected = {'id': '1', 'name': 'image1', 'is_public': True,
- 'properties': {'prop1': 'propvalue1', 'foo': 'bar'}}
- self.assertEqual(image_meta, expected)
-
- def test_show_raises_when_no_authtoken_in_the_context(self):
- fixtures = {'image1': {'name': 'image1', 'is_public': False,
- 'foo': 'bar',
- 'properties': {'prop1': 'propvalue1'}}}
- self.client.images = fixtures
- self.context.auth_token = False
-
- expected = {'name': 'image1', 'is_public': True,
- 'properties': {'prop1': 'propvalue1', 'foo': 'bar'}}
- self.assertRaises(exception.ImageNotFound,
- self.service.show, self.context, 'image1')
-
- def test_show_passes_through_to_client_with_authtoken_in_context(self):
- fixtures = {'image1': {'name': 'image1', 'is_public': False,
- 'foo': 'bar',
- 'properties': {'prop1': 'propvalue1'}}}
- self.client.images = fixtures
- self.context.auth_token = True
-
- expected = {'name': 'image1', 'is_public': False,
- 'properties': {'prop1': 'propvalue1', 'foo': 'bar'}}
-
- image_meta = self.service.show(self.context, 'image1')
- self.assertEqual(image_meta, expected)
-
- def test_detail_passes_through_to_client(self):
- fixtures = {'image1': {'id': '1', 'name': 'image1', 'is_public': True,
- 'foo': 'bar',
- 'properties': {'prop1': 'propvalue1'}}}
- self.client.images = fixtures
- image_meta = self.service.detail(self.context)
- expected = [{'id': '1', 'name': 'image1', 'is_public': True,
- 'properties': {'prop1': 'propvalue1', 'foo': 'bar'}}]
- self.assertEqual(image_meta, expected)
-
-
-class TestGetterDateTimeNoneTests(BaseGlanceTest):
-
- def test_show_handles_none_datetimes(self):
- self.client.images = self._make_none_datetime_fixtures()
- image_meta = self.service.show(self.context, 'image1')
- self.assertDateTimesEmpty(image_meta)
-
- def test_show_handles_blank_datetimes(self):
- self.client.images = self._make_blank_datetime_fixtures()
- image_meta = self.service.show(self.context, 'image1')
- self.assertDateTimesBlank(image_meta)
-
- def test_detail_handles_none_datetimes(self):
- self.client.images = self._make_none_datetime_fixtures()
- image_meta = self.service.detail(self.context)[0]
- self.assertDateTimesEmpty(image_meta)
-
- def test_detail_handles_blank_datetimes(self):
- self.client.images = self._make_blank_datetime_fixtures()
- image_meta = self.service.detail(self.context)[0]
- self.assertDateTimesBlank(image_meta)
-
- def test_get_handles_none_datetimes(self):
- self.client.images = self._make_none_datetime_fixtures()
- writer = NullWriter()
- image_meta = self.service.get(self.context, 'image1', writer)
- self.assertDateTimesEmpty(image_meta)
-
- def test_get_handles_blank_datetimes(self):
- self.client.images = self._make_blank_datetime_fixtures()
- writer = NullWriter()
- image_meta = self.service.get(self.context, 'image1', writer)
- self.assertDateTimesBlank(image_meta)
-
- def test_show_makes_datetimes(self):
- self.client.images = self._make_datetime_fixtures()
- image_meta = self.service.show(self.context, 'image1')
- self.assertDateTimesFilled(image_meta)
- image_meta = self.service.show(self.context, 'image2')
- self.assertDateTimesFilled(image_meta)
-
- def test_detail_makes_datetimes(self):
- self.client.images = self._make_datetime_fixtures()
- image_meta = self.service.detail(self.context)[0]
- self.assertDateTimesFilled(image_meta)
- image_meta = self.service.detail(self.context)[1]
- self.assertDateTimesFilled(image_meta)
-
- def test_get_makes_datetimes(self):
- self.client.images = self._make_datetime_fixtures()
- writer = NullWriter()
- image_meta = self.service.get(self.context, 'image1', writer)
- self.assertDateTimesFilled(image_meta)
- image_meta = self.service.get(self.context, 'image2', writer)
- self.assertDateTimesFilled(image_meta)
-
- def _make_datetime_fixtures(self):
- fixtures = {
- 'image1': {
- 'id': '1',
- 'name': 'image1',
- 'is_public': True,
- 'created_at': self.NOW_GLANCE_FORMAT,
- 'updated_at': self.NOW_GLANCE_FORMAT,
- 'deleted_at': self.NOW_GLANCE_FORMAT,
- },
- 'image2': {
- 'id': '2',
- 'name': 'image2',
- 'is_public': True,
- 'created_at': self.NOW_GLANCE_OLD_FORMAT,
- 'updated_at': self.NOW_GLANCE_OLD_FORMAT,
- 'deleted_at': self.NOW_GLANCE_OLD_FORMAT,
- },
- }
- return fixtures
-
- def _make_none_datetime_fixtures(self):
- fixtures = {'image1': {'id': '1',
- 'name': 'image1',
- 'is_public': True,
- 'updated_at': None,
- 'deleted_at': None}}
- return fixtures
-
- def _make_blank_datetime_fixtures(self):
- fixtures = {'image1': {'id': '1',
- 'name': 'image1',
- 'is_public': True,
- 'updated_at': '',
- 'deleted_at': ''}}
- return fixtures
-
-
-class TestMutatorDateTimeTests(BaseGlanceTest):
- """Tests create(), update()"""
-
- def test_create_handles_datetimes(self):
- self.client.add_response = self._make_datetime_fixture()
- image_meta = self.service.create(self.context, {})
- self.assertDateTimesFilled(image_meta)
-
- def test_create_handles_none_datetimes(self):
- self.client.add_response = self._make_none_datetime_fixture()
- dummy_meta = {}
- image_meta = self.service.create(self.context, dummy_meta)
- self.assertDateTimesEmpty(image_meta)
-
- def test_update_handles_datetimes(self):
- self.client.images = {'image1': self._make_datetime_fixture()}
- self.client.update_response = self._make_datetime_fixture()
- dummy_meta = {}
- image_meta = self.service.update(self.context, 'image1', dummy_meta)
- self.assertDateTimesFilled(image_meta)
-
- def test_update_handles_none_datetimes(self):
- self.client.images = {'image1': self._make_datetime_fixture()}
- self.client.update_response = self._make_none_datetime_fixture()
- dummy_meta = {}
- image_meta = self.service.update(self.context, 'image1', dummy_meta)
- self.assertDateTimesEmpty(image_meta)
-
- def _make_datetime_fixture(self):
- fixture = {'id': 'image1', 'name': 'image1', 'is_public': True,
- 'created_at': self.NOW_GLANCE_FORMAT,
- 'updated_at': self.NOW_GLANCE_FORMAT,
- 'deleted_at': self.NOW_GLANCE_FORMAT}
- return fixture
-
- def _make_none_datetime_fixture(self):
- fixture = {'id': 'image1', 'name': 'image1', 'is_public': True,
- 'updated_at': None,
- 'deleted_at': None}
- return fixture
-
-
-class TestGlanceSerializer(unittest.TestCase):
+class TestGlanceSerializer(test.TestCase):
def test_serialize(self):
metadata = {'name': 'image1',
'is_public': True,
@@ -312,3 +68,386 @@ class TestGlanceSerializer(unittest.TestCase):
converted = glance._convert_to_string(metadata)
self.assertEqual(converted, converted_expected)
self.assertEqual(glance._convert_from_string(converted), metadata)
+
+
+class TestGlanceImageService(test.TestCase):
+ """
+ Tests the Glance image service.
+
+ At a high level, the translations involved are:
+
+ 1. Glance -> ImageService - This is needed so we can support
+ multple ImageServices (Glance, Local, etc)
+
+ 2. ImageService -> API - This is needed so we can support multple
+ APIs (OpenStack, EC2)
+
+ """
+ NOW_GLANCE_OLD_FORMAT = "2010-10-11T10:30:22"
+ NOW_GLANCE_FORMAT = "2010-10-11T10:30:22.000000"
+ NOW_DATETIME = datetime.datetime(2010, 10, 11, 10, 30, 22)
+
+ def setUp(self):
+ super(TestGlanceImageService, self).setUp()
+ self.stubs = stubout.StubOutForTesting()
+ fakes.stub_out_compute_api_snapshot(self.stubs)
+ client = glance_stubs.StubGlanceClient()
+ self.service = glance.GlanceImageService(client=client)
+ self.context = context.RequestContext('fake', 'fake', auth_token=True)
+ self.service.delete_all()
+
+ def tearDown(self):
+ self.stubs.UnsetAll()
+ super(TestGlanceImageService, self).tearDown()
+
+ @staticmethod
+ def _make_fixture(**kwargs):
+ fixture = {'name': None,
+ 'properties': {},
+ 'status': None,
+ 'is_public': None}
+ fixture.update(kwargs)
+ return fixture
+
+ def _make_datetime_fixture(self):
+ return self._make_fixture(created_at=self.NOW_GLANCE_FORMAT,
+ updated_at=self.NOW_GLANCE_FORMAT,
+ deleted_at=self.NOW_GLANCE_FORMAT)
+
+ def test_create_with_instance_id(self):
+ """Ensure instance_id is persisted as an image-property"""
+ fixture = {'name': 'test image',
+ 'is_public': False,
+ 'properties': {'instance_id': '42', 'user_id': 'fake'}}
+
+ image_id = self.service.create(self.context, fixture)['id']
+ image_meta = self.service.show(self.context, image_id)
+ expected = {
+ 'id': image_id,
+ 'name': 'test image',
+ 'is_public': False,
+ 'size': None,
+ 'location': None,
+ 'disk_format': None,
+ 'container_format': None,
+ 'checksum': None,
+ 'created_at': self.NOW_DATETIME,
+ 'updated_at': self.NOW_DATETIME,
+ 'deleted_at': None,
+ 'deleted': None,
+ 'status': None,
+ 'properties': {'instance_id': '42', 'user_id': 'fake'},
+ }
+ self.assertDictMatch(image_meta, expected)
+
+ image_metas = self.service.detail(self.context)
+ self.assertDictMatch(image_metas[0], expected)
+
+ def test_create_without_instance_id(self):
+ """
+ Ensure we can create an image without having to specify an
+ instance_id. Public images are an example of an image not tied to an
+ instance.
+ """
+ fixture = {'name': 'test image', 'is_public': False}
+ image_id = self.service.create(self.context, fixture)['id']
+
+ expected = {
+ 'id': image_id,
+ 'name': 'test image',
+ 'is_public': False,
+ 'size': None,
+ 'location': None,
+ 'disk_format': None,
+ 'container_format': None,
+ 'checksum': None,
+ 'created_at': self.NOW_DATETIME,
+ 'updated_at': self.NOW_DATETIME,
+ 'deleted_at': None,
+ 'deleted': None,
+ 'status': None,
+ 'properties': {},
+ }
+ actual = self.service.show(self.context, image_id)
+ self.assertDictMatch(actual, expected)
+
+ def test_create(self):
+ fixture = self._make_fixture(name='test image')
+ num_images = len(self.service.index(self.context))
+ image_id = self.service.create(self.context, fixture)['id']
+
+ self.assertNotEquals(None, image_id)
+ self.assertEquals(num_images + 1,
+ len(self.service.index(self.context)))
+
+ def test_create_and_show_non_existing_image(self):
+ fixture = self._make_fixture(name='test image')
+ image_id = self.service.create(self.context, fixture)['id']
+
+ self.assertNotEquals(None, image_id)
+ self.assertRaises(exception.NotFound,
+ self.service.show,
+ self.context,
+ 'bad image id')
+
+ def test_create_and_show_non_existing_image_by_name(self):
+ fixture = self._make_fixture(name='test image')
+ image_id = self.service.create(self.context, fixture)['id']
+
+ self.assertNotEquals(None, image_id)
+ self.assertRaises(exception.ImageNotFound,
+ self.service.show_by_name,
+ self.context,
+ 'bad image id')
+
+ def test_index(self):
+ fixture = self._make_fixture(name='test image')
+ image_id = self.service.create(self.context, fixture)['id']
+ image_metas = self.service.index(self.context)
+ expected = [{'id': image_id, 'name': 'test image'}]
+ self.assertDictListMatch(image_metas, expected)
+
+ def test_index_default_limit(self):
+ fixtures = []
+ ids = []
+ for i in range(10):
+ fixture = self._make_fixture(name='TestImage %d' % (i))
+ fixtures.append(fixture)
+ ids.append(self.service.create(self.context, fixture)['id'])
+
+ image_metas = self.service.index(self.context)
+ i = 0
+ for meta in image_metas:
+ expected = {'id': 'DONTCARE',
+ 'name': 'TestImage %d' % (i)}
+ self.assertDictMatch(meta, expected)
+ i = i + 1
+
+ def test_index_marker(self):
+ fixtures = []
+ ids = []
+ for i in range(10):
+ fixture = self._make_fixture(name='TestImage %d' % (i))
+ fixtures.append(fixture)
+ ids.append(self.service.create(self.context, fixture)['id'])
+
+ image_metas = self.service.index(self.context, marker=ids[1])
+ self.assertEquals(len(image_metas), 8)
+ i = 2
+ for meta in image_metas:
+ expected = {'id': 'DONTCARE',
+ 'name': 'TestImage %d' % (i)}
+ self.assertDictMatch(meta, expected)
+ i = i + 1
+
+ def test_index_limit(self):
+ fixtures = []
+ ids = []
+ for i in range(10):
+ fixture = self._make_fixture(name='TestImage %d' % (i))
+ fixtures.append(fixture)
+ ids.append(self.service.create(self.context, fixture)['id'])
+
+ image_metas = self.service.index(self.context, limit=5)
+ self.assertEquals(len(image_metas), 5)
+
+ def test_index_marker_and_limit(self):
+ fixtures = []
+ ids = []
+ for i in range(10):
+ fixture = self._make_fixture(name='TestImage %d' % (i))
+ fixtures.append(fixture)
+ ids.append(self.service.create(self.context, fixture)['id'])
+
+ image_metas = self.service.index(self.context, marker=ids[3], limit=1)
+ self.assertEquals(len(image_metas), 1)
+ i = 4
+ for meta in image_metas:
+ expected = {'id': ids[i],
+ 'name': 'TestImage %d' % (i)}
+ self.assertDictMatch(meta, expected)
+ i = i + 1
+
+ def test_detail_marker(self):
+ fixtures = []
+ ids = []
+ for i in range(10):
+ fixture = self._make_fixture(name='TestImage %d' % (i))
+ fixtures.append(fixture)
+ ids.append(self.service.create(self.context, fixture)['id'])
+
+ image_metas = self.service.detail(self.context, marker=ids[1])
+ self.assertEquals(len(image_metas), 8)
+ i = 2
+ for meta in image_metas:
+ expected = {
+ 'id': ids[i],
+ 'status': None,
+ 'is_public': None,
+ 'name': 'TestImage %d' % (i),
+ 'properties': {},
+ 'size': None,
+ 'location': None,
+ 'disk_format': None,
+ 'container_format': None,
+ 'checksum': None,
+ 'created_at': self.NOW_DATETIME,
+ 'updated_at': self.NOW_DATETIME,
+ 'deleted_at': None,
+ 'deleted': None
+ }
+
+ self.assertDictMatch(meta, expected)
+ i = i + 1
+
+ def test_detail_limit(self):
+ fixtures = []
+ ids = []
+ for i in range(10):
+ fixture = self._make_fixture(name='TestImage %d' % (i))
+ fixtures.append(fixture)
+ ids.append(self.service.create(self.context, fixture)['id'])
+
+ image_metas = self.service.detail(self.context, limit=5)
+ self.assertEquals(len(image_metas), 5)
+
+ def test_detail_marker_and_limit(self):
+ fixtures = []
+ ids = []
+ for i in range(10):
+ fixture = self._make_fixture(name='TestImage %d' % (i))
+ fixtures.append(fixture)
+ ids.append(self.service.create(self.context, fixture)['id'])
+
+ image_metas = self.service.detail(self.context, marker=ids[3], limit=5)
+ self.assertEquals(len(image_metas), 5)
+ i = 4
+ for meta in image_metas:
+ expected = {
+ 'id': ids[i],
+ 'status': None,
+ 'is_public': None,
+ 'name': 'TestImage %d' % (i),
+ 'properties': {},
+ 'size': None,
+ 'location': None,
+ 'disk_format': None,
+ 'container_format': None,
+ 'checksum': None,
+ 'created_at': self.NOW_DATETIME,
+ 'updated_at': self.NOW_DATETIME,
+ 'deleted_at': None,
+ 'deleted': None
+ }
+ self.assertDictMatch(meta, expected)
+ i = i + 1
+
+ def test_update(self):
+ fixture = self._make_fixture(name='test image')
+ image_id = self.service.create(self.context, fixture)['id']
+ fixture['name'] = 'new image name'
+ self.service.update(self.context, image_id, fixture)
+
+ new_image_data = self.service.show(self.context, image_id)
+ self.assertEquals('new image name', new_image_data['name'])
+
+ def test_delete(self):
+ fixture1 = self._make_fixture(name='test image 1')
+ fixture2 = self._make_fixture(name='test image 2')
+ fixtures = [fixture1, fixture2]
+
+ num_images = len(self.service.index(self.context))
+ self.assertEquals(0, num_images, str(self.service.index(self.context)))
+
+ ids = []
+ for fixture in fixtures:
+ new_id = self.service.create(self.context, fixture)['id']
+ ids.append(new_id)
+
+ num_images = len(self.service.index(self.context))
+ self.assertEquals(2, num_images, str(self.service.index(self.context)))
+
+ self.service.delete(self.context, ids[0])
+
+ num_images = len(self.service.index(self.context))
+ self.assertEquals(1, num_images)
+
+ def test_show_passes_through_to_client(self):
+ fixture = self._make_fixture(name='image1', is_public=True)
+ image_id = self.service.create(self.context, fixture)['id']
+
+ image_meta = self.service.show(self.context, image_id)
+ expected = {
+ 'id': image_id,
+ 'name': 'image1',
+ 'is_public': True,
+ 'size': None,
+ 'location': None,
+ 'disk_format': None,
+ 'container_format': None,
+ 'checksum': None,
+ 'created_at': self.NOW_DATETIME,
+ 'updated_at': self.NOW_DATETIME,
+ 'deleted_at': None,
+ 'deleted': None,
+ 'status': None,
+ 'properties': {},
+ }
+ self.assertEqual(image_meta, expected)
+
+ def test_show_raises_when_no_authtoken_in_the_context(self):
+ fixture = self._make_fixture(name='image1',
+ is_public=False,
+ properties={'one': 'two'})
+ image_id = self.service.create(self.context, fixture)['id']
+ self.context.auth_token = False
+ self.assertRaises(exception.ImageNotFound,
+ self.service.show,
+ self.context,
+ image_id)
+
+ def test_detail_passes_through_to_client(self):
+ fixture = self._make_fixture(name='image10', is_public=True)
+ image_id = self.service.create(self.context, fixture)['id']
+ image_metas = self.service.detail(self.context)
+ expected = [
+ {
+ 'id': image_id,
+ 'name': 'image10',
+ 'is_public': True,
+ 'size': None,
+ 'location': None,
+ 'disk_format': None,
+ 'container_format': None,
+ 'checksum': None,
+ 'created_at': self.NOW_DATETIME,
+ 'updated_at': self.NOW_DATETIME,
+ 'deleted_at': None,
+ 'deleted': None,
+ 'status': None,
+ 'properties': {},
+ },
+ ]
+ self.assertEqual(image_metas, expected)
+
+ def test_show_makes_datetimes(self):
+ fixture = self._make_datetime_fixture()
+ image_id = self.service.create(self.context, fixture)['id']
+ image_meta = self.service.show(self.context, image_id)
+ self.assertEqual(image_meta['created_at'], self.NOW_DATETIME)
+ self.assertEqual(image_meta['updated_at'], self.NOW_DATETIME)
+
+ def test_detail_makes_datetimes(self):
+ fixture = self._make_datetime_fixture()
+ self.service.create(self.context, fixture)
+ image_meta = self.service.detail(self.context)[0]
+ self.assertEqual(image_meta['created_at'], self.NOW_DATETIME)
+ self.assertEqual(image_meta['updated_at'], self.NOW_DATETIME)
+
+ def test_get_makes_datetimes(self):
+ fixture = self._make_datetime_fixture()
+ image_id = self.service.create(self.context, fixture)['id']
+ writer = NullWriter()
+ image_meta = self.service.get(self.context, image_id, writer)
+ self.assertEqual(image_meta['created_at'], self.NOW_DATETIME)
+ self.assertEqual(image_meta['updated_at'], self.NOW_DATETIME)
diff --git a/nova/tests/test_direct.py b/nova/tests/test_direct.py
index 4ed0c2aa5..8d856dc4b 100644
--- a/nova/tests/test_direct.py
+++ b/nova/tests/test_direct.py
@@ -30,7 +30,7 @@ from nova import test
from nova import volume
from nova import utils
from nova.api import direct
-from nova.tests import test_cloud
+from nova.tests.api.ec2 import test_cloud
class ArbitraryObject(object):