summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjaypipes@gmail.com <>2010-10-13 16:17:23 -0400
committerjaypipes@gmail.com <>2010-10-13 16:17:23 -0400
commit52600e6bee170ac6d78eff004ecc98394c43ff6f (patch)
tree8b740736329862bc066381d7924f965ac016ba37
parent088bd29f53eaac48574fa59c251b3564a380d4d9 (diff)
Adds unit test for WSGI image controller for OpenStack API using Glance Service.
-rw-r--r--nova/api/openstack/images.py9
-rw-r--r--nova/image/service.py87
-rw-r--r--nova/tests/api/openstack/fakes.py38
-rw-r--r--nova/tests/api/openstack/test_images.py87
4 files changed, 196 insertions, 25 deletions
diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py
index aa438739c..5f2d71dc2 100644
--- a/nova/api/openstack/images.py
+++ b/nova/api/openstack/images.py
@@ -48,8 +48,13 @@ class Controller(wsgi.Controller):
def detail(self, req):
"""Return all public images in detail."""
- data = self._service.index()
- data = nova.api.openstack.limited(data, req)
+ try:
+ data = self._service.detail()
+ except NotImplementedError:
+ # Emulate detail() using repeated calls to show()
+ images = self._service.index()
+ images = nova.api.openstack.limited(images, req)
+ data = [self._service.show(i['id']) for i in images]
return dict(images=data)
def show(self, req, id):
diff --git a/nova/image/service.py b/nova/image/service.py
index 5276e1312..9eb2c20c5 100644
--- a/nova/image/service.py
+++ b/nova/image/service.py
@@ -50,7 +50,43 @@ class BaseImageService(object):
def index(self):
"""
- Return a dict from opaque image id to image data.
+ Returns a sequence of mappings of id and name information about
+ images.
+
+ :retval a sequence of mappings with the following signature:
+
+ [
+ {'id': opaque id of image,
+ 'name': name of image
+ }, ...
+ ]
+
+ """
+ raise NotImplementedError
+
+ def detail(self):
+ """
+ Returns a sequence of mappings of detailed information about images.
+
+ :retval a sequence of mappings with the following signature:
+
+ [
+ {'id': opaque id of image,
+ 'name': name of image,
+ 'created_at': creation timestamp,
+ 'updated_at': modification timestamp,
+ 'deleted_at': deletion timestamp 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
@@ -58,6 +94,18 @@ class BaseImageService(object):
"""
Returns a dict containing image data for the given opaque image id.
+ :retval a mapping with the following signature:
+
+ {'id': opaque id of image,
+ 'name': name of image,
+ 'created_at': creation timestamp,
+ 'updated_at': modification timestamp,
+ 'deleted_at': deletion timestamp 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
@@ -110,9 +158,9 @@ class ParallaxClient(object):
self.connection_type = {'http': httplib.HTTPConnection,
'https': httplib.HTTPSConnection}[url.scheme]
- def get_images(self):
+ def get_image_index(self):
"""
- Returns a list of image data mappings from Parallax
+ Returns a list of image id/name mappings from Parallax
"""
try:
c = self.connection_type(self.netloc, self.port)
@@ -129,6 +177,25 @@ class ParallaxClient(object):
finally:
c.close()
+ def get_image_details(self):
+ """
+ Returns a list of detailed image data mappings from Parallax
+ """
+ try:
+ c = self.connection_type(self.netloc, self.port)
+ c.request("GET", "images/detail")
+ res = c.getresponse()
+ if res.status == 200:
+ # Parallax returns a JSONified dict(images=image_list)
+ data = json.loads(res.read())['images']
+ return data
+ else:
+ logging.warn("Parallax returned HTTP error %d from "
+ "request for /images/detail", res.status_int)
+ return []
+ finally:
+ c.close()
+
def get_image_metadata(self, image_id):
"""
Returns a mapping of image metadata from Parallax
@@ -178,7 +245,14 @@ class GlanceImageService(BaseImageService):
"""
Calls out to Parallax for a list of images available
"""
- images = self.parallax.get_images()
+ images = self.parallax.get_image_index()
+ return images
+
+ def detail(self):
+ """
+ Calls out to Parallax for a list of detailed image information
+ """
+ images = self.parallax.get_image_details()
return images
def show(self, id):
@@ -244,7 +318,10 @@ class LocalImageService(BaseImageService):
return [int(i) for i in os.listdir(self._path)]
def index(self):
- return [ self.show(id) for id in self._ids() ]
+ return [dict(id=i['id'], name=i['name']) for i in self.detail()]
+
+ def detail(self):
+ return [self.show(id) for id in self._ids()]
def show(self, id):
try:
diff --git a/nova/tests/api/openstack/fakes.py b/nova/tests/api/openstack/fakes.py
index 34bc1f2a9..1d4cddffc 100644
--- a/nova/tests/api/openstack/fakes.py
+++ b/nova/tests/api/openstack/fakes.py
@@ -108,48 +108,56 @@ def stub_out_networking(stubs):
FLAGS.FAKE_subdomain = 'api'
-def stub_out_glance(stubs):
+def stub_out_glance(stubs, initial_fixtures=[]):
class FakeParallaxClient:
- def __init__(self):
- self.fixtures = {}
+ def __init__(self, initial_fixtures):
+ self.fixtures = initial_fixtures
- def fake_get_images(self):
+ def fake_get_image_index(self):
+ return [dict(id=f['id'], name=f['name'])
+ for f in self.fixtures]
+
+ def fake_get_image_details(self):
return self.fixtures
def fake_get_image_metadata(self, image_id):
- for k, f in self.fixtures.iteritems():
- if k == image_id:
+ for f in self.fixtures:
+ if f['id'] == image_id:
return f
return None
def fake_add_image_metadata(self, image_data):
id = ''.join(random.choice(string.letters) for _ in range(20))
image_data['id'] = id
- self.fixtures[id] = image_data
+ self.fixtures.append(image_data)
return id
def fake_update_image_metadata(self, image_id, image_data):
- if image_id not in self.fixtures.keys():
+ f = self.fake_get_image_metadata(image_id)
+ if not f:
raise exc.NotFound
- self.fixtures[image_id].update(image_data)
+ f.update(image_data)
def fake_delete_image_metadata(self, image_id):
- if image_id not in self.fixtures.keys():
+ f = self.fake_get_image_metadata(image_id)
+ if not f:
raise exc.NotFound
- del self.fixtures[image_id]
+ self.fixtures.remove(f)
def fake_delete_all(self):
- self.fixtures = {}
+ self.fixtures = []
- fake_parallax_client = FakeParallaxClient()
- stubs.Set(nova.image.service.ParallaxClient, 'get_images',
- fake_parallax_client.fake_get_images)
+ fake_parallax_client = FakeParallaxClient(initial_fixtures)
+ stubs.Set(nova.image.service.ParallaxClient, 'get_image_index',
+ fake_parallax_client.fake_get_image_index)
+ stubs.Set(nova.image.service.ParallaxClient, 'get_image_details',
+ fake_parallax_client.fake_get_image_details)
stubs.Set(nova.image.service.ParallaxClient, 'get_image_metadata',
fake_parallax_client.fake_get_image_metadata)
stubs.Set(nova.image.service.ParallaxClient, 'add_image_metadata',
diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py
index 505fea3e2..4cbc01841 100644
--- a/nova/tests/api/openstack/test_images.py
+++ b/nova/tests/api/openstack/test_images.py
@@ -15,18 +15,31 @@
# License for the specific language governing permissions and limitations
# under the License.
+"""
+Tests of the new image services, both as a service layer,
+and as a WSGI layer
+"""
+
+import json
+import datetime
import logging
import unittest
import stubout
+import webob
from nova import exception
+from nova import flags
from nova import utils
+import nova.api.openstack
from nova.api.openstack import images
from nova.tests.api.openstack import fakes
-class BaseImageServiceTests():
+FLAGS = flags.FLAGS
+
+
+class BaseImageServiceTests(object):
"""Tasks to test for all image services"""
@@ -98,13 +111,16 @@ class BaseImageServiceTests():
'serverId': None,
'progress': None}]
+ num_images = len(self.service.index())
+ self.assertEquals(0, num_images, str(self.service.index()))
+
ids = []
for fixture in fixtures:
new_id = self.service.create(fixture)
ids.append(new_id)
num_images = len(self.service.index())
- self.assertEquals(2, num_images)
+ self.assertEquals(2, num_images, str(self.service.index()))
self.service.delete(ids[0])
@@ -135,7 +151,72 @@ class GlanceImageServiceTest(unittest.TestCase,
self.stubs = stubout.StubOutForTesting()
fakes.stub_out_glance(self.stubs)
self.service = utils.import_object('nova.image.service.GlanceImageService')
+ self.service.delete_all()
+
+ def tearDown(self):
+ self.stubs.UnsetAll()
+
+
+class ImageControllerWithGlanceServiceTest(unittest.TestCase):
+
+ """Test of the OpenStack API /images application controller"""
+
+ # Registered images at start of each test.
+
+ IMAGE_FIXTURES = [
+ {'id': '23g2ogk23k4hhkk4k42l',
+ 'name': 'public image #1',
+ 'created_at': str(datetime.datetime.utcnow()),
+ 'modified_at': str(datetime.datetime.utcnow()),
+ 'deleted_at': None,
+ 'deleted': False,
+ 'is_public': True,
+ 'status': 'available',
+ 'image_type': 'kernel'
+ },
+ {'id': 'slkduhfas73kkaskgdas',
+ 'name': 'public image #2',
+ 'created_at': str(datetime.datetime.utcnow()),
+ 'modified_at': str(datetime.datetime.utcnow()),
+ 'deleted_at': None,
+ 'deleted': False,
+ 'is_public': True,
+ 'status': 'available',
+ 'image_type': 'ramdisk'
+ },
+ ]
+
+ def setUp(self):
+ self.orig_image_service = FLAGS.image_service
+ FLAGS.image_service = 'nova.image.service.GlanceImageService'
+ self.stubs = stubout.StubOutForTesting()
+ fakes.FakeAuthManager.auth_data = {}
+ fakes.FakeAuthDatabase.data = {}
+ fakes.stub_out_networking(self.stubs)
+ fakes.stub_out_rate_limiting(self.stubs)
+ fakes.stub_out_auth(self.stubs)
+ fakes.stub_out_key_pair_funcs(self.stubs)
+ fakes.stub_out_glance(self.stubs, initial_fixtures=self.IMAGE_FIXTURES)
def tearDown(self):
- self.service.delete_all()
self.stubs.UnsetAll()
+ FLAGS.image_service = self.orig_image_service
+
+ def test_get_image_index(self):
+ req = webob.Request.blank('/v1.0/images')
+ res = req.get_response(nova.api.API())
+ res_dict = json.loads(res.body)
+
+ fixture_index = [dict(id=f['id'], name=f['name']) for f
+ in self.IMAGE_FIXTURES]
+
+ for image in res_dict['images']:
+ self.assertEquals(1, fixture_index.count(image), "image %s not in fixture index!" % str(image))
+
+ def test_get_image_details(self):
+ req = webob.Request.blank('/v1.0/images/detail')
+ res = req.get_response(nova.api.API())
+ res_dict = json.loads(res.body)
+
+ for image in res_dict['images']:
+ self.assertEquals(1, self.IMAGE_FIXTURES.count(image), "image %s not in fixtures!" % str(image))