diff options
| author | jaypipes@gmail.com <> | 2010-10-13 16:17:23 -0400 |
|---|---|---|
| committer | jaypipes@gmail.com <> | 2010-10-13 16:17:23 -0400 |
| commit | 52600e6bee170ac6d78eff004ecc98394c43ff6f (patch) | |
| tree | 8b740736329862bc066381d7924f965ac016ba37 | |
| parent | 088bd29f53eaac48574fa59c251b3564a380d4d9 (diff) | |
Adds unit test for WSGI image controller for OpenStack API using Glance Service.
| -rw-r--r-- | nova/api/openstack/images.py | 9 | ||||
| -rw-r--r-- | nova/image/service.py | 87 | ||||
| -rw-r--r-- | nova/tests/api/openstack/fakes.py | 38 | ||||
| -rw-r--r-- | nova/tests/api/openstack/test_images.py | 87 |
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)) |
