From 95015ad42a1f6355af095a6dca2ecd39ae2cc735 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Mon, 30 Jul 2012 13:12:18 -0700 Subject: Rewrite image code to use python-glanceclient This patch rewrites the Glance-specific code within Nova to use the new python-glanceclient library. The old client is deprecated in Folsom and should not be used in conjunction with python-glanceclient. This removes the dependency on installing 'glance' through pip. Implements bp integrate-python-glanceclient Fixes bug 880964 Change-Id: Ifb9e75593abd36a2d1c08773d02f192e5e5627fc --- .../api/openstack/compute/test_server_actions.py | 2 +- nova/tests/api/openstack/fakes.py | 10 +-- nova/tests/glance/stubs.py | 98 ++++++++++++++-------- nova/tests/image/fake.py | 8 +- nova/tests/image/test_glance.py | 45 ++++------ nova/tests/image/test_s3.py | 3 +- 6 files changed, 89 insertions(+), 77 deletions(-) (limited to 'nova/tests') diff --git a/nova/tests/api/openstack/compute/test_server_actions.py b/nova/tests/api/openstack/compute/test_server_actions.py index f062dd5a9..f18c7d4ca 100644 --- a/nova/tests/api/openstack/compute/test_server_actions.py +++ b/nova/tests/api/openstack/compute/test_server_actions.py @@ -74,7 +74,7 @@ class ServerActionsControllerTest(test.TestCase): service_class = 'nova.image.glance.GlanceImageService' self.service = importutils.import_object(service_class) self.sent_to_glance = {} - fakes.stub_out_glance_add_image(self.stubs, self.sent_to_glance) + fakes.stub_out_glanceclient_create(self.stubs, self.sent_to_glance) self.flags(allow_instance_snapshots=True, enable_instance_password=True) self.uuid = FAKE_UUID diff --git a/nova/tests/api/openstack/fakes.py b/nova/tests/api/openstack/fakes.py index a5eb57b1b..eb3e7852a 100644 --- a/nova/tests/api/openstack/fakes.py +++ b/nova/tests/api/openstack/fakes.py @@ -17,7 +17,7 @@ import datetime -from glance import client as glance_client +import glanceclient.v1.images import routes import webob import webob.dec @@ -245,19 +245,19 @@ def _make_image_fixtures(): return fixtures -def stub_out_glance_add_image(stubs, sent_to_glance): +def stub_out_glanceclient_create(stubs, sent_to_glance): """ We return the metadata sent to glance by modifying the sent_to_glance dict in place. """ - orig_add_image = glance_client.Client.add_image + orig_add_image = glanceclient.v1.images.ImageManager.create - def fake_add_image(context, metadata, data=None): + def fake_create(context, metadata, data=None): sent_to_glance['metadata'] = metadata sent_to_glance['data'] = data return orig_add_image(metadata, data) - stubs.Set(glance_client.Client, 'add_image', fake_add_image) + stubs.Set(glanceclient.v1.images.ImageManager, 'create', fake_create) def stub_out_glance(stubs): diff --git a/nova/tests/glance/stubs.py b/nova/tests/glance/stubs.py index b3cef0ff3..076afeffc 100644 --- a/nova/tests/glance/stubs.py +++ b/nova/tests/glance/stubs.py @@ -14,7 +14,7 @@ # License for the specific language governing permissions and limitations # under the License. -from glance.common import exception as glance_exception +import glanceclient.exc NOW_GLANCE_FORMAT = "2010-10-11T10:30:22" @@ -23,64 +23,90 @@ NOW_GLANCE_FORMAT = "2010-10-11T10:30:22" class StubGlanceClient(object): def __init__(self, images=None): - self.images = [] + self._images = [] _images = images or [] - map(lambda image: self.add_image(image, None), _images) + map(lambda image: self.create(**image), _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 glance_exception.NotFound() + #NOTE(bcwaldon): HACK to get client.images.* to work + self.images = lambda: None + for fn in ('list', 'get', 'data', 'create', 'update', 'delete'): + setattr(self.images, fn, getattr(self, fn)) #TODO(bcwaldon): implement filters - def get_images_detailed(self, filters=None, marker=None, limit=3): + def list(self, filters=None, marker=None, limit=30): if marker is None: index = 0 else: - for index, image in enumerate(self.images): - if image['id'] == str(marker): + for index, image in enumerate(self._images): + if image.id == str(marker): index += 1 break else: - raise glance_exception.Invalid() + raise glanceclient.exc.BadRequest('Marker not found') + + return self._images[index:index + limit] - return self.images[index:index + limit] + def get(self, image_id): + for image in self._images: + if image.id == str(image_id): + return image + raise glanceclient.exc.NotFound(image_id) - def get_image(self, image_id): - return self.get_image_meta(image_id), [] + def data(self, image_id): + self.get(image_id) + return [] - def add_image(self, metadata, data): + def create(self, **metadata): metadata['created_at'] = NOW_GLANCE_FORMAT metadata['updated_at'] = NOW_GLANCE_FORMAT - self.images.append(metadata) + self._images.append(FakeImage(metadata)) try: image_id = str(metadata['id']) except KeyError: # auto-generate an id if one wasn't provided - image_id = str(len(self.images)) + image_id = str(len(self._images)) - self.images[-1]['id'] = image_id + self._images[-1].id = image_id - return self.images[-1] + return self._images[-1] - def update_image(self, image_id, metadata, data, features): - 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 glance_exception.NotFound() + def update(self, image_id, **metadata): + for i, image in enumerate(self._images): + if image.id == str(image_id): + for k, v in metadata.items(): + setattr(self._images[i], k, v) + return self._images[i] + raise glanceclient.exc.NotFound(image_id) - def delete_image(self, image_id): - for i, image in enumerate(self.images): - if image['id'] == image_id: - del self.images[i] + def delete(self, image_id): + for i, image in enumerate(self._images): + if image.id == image_id: + del self._images[i] return - raise glance_exception.NotFound() + raise glanceclient.exc.NotFound(image_id) + + +class FakeImage(object): + def __init__(self, metadata): + IMAGE_ATTRIBUTES = ['size', 'disk_format', 'owner', + 'container_format', 'checksum', 'id', + 'name', 'created_at', 'updated_at', + 'deleted', 'status', + 'min_disk', 'min_ram', 'is_public'] + raw = dict.fromkeys(IMAGE_ATTRIBUTES) + raw.update(metadata) + self.__dict__['raw'] = raw + + def __getattr__(self, key): + try: + return self.__dict__['raw'][key] + except KeyError: + raise AttributeError(key) + + def __setattr__(self, key, value): + try: + self.__dict__['raw'][key] = value + except KeyError: + raise AttributeError(key) diff --git a/nova/tests/image/fake.py b/nova/tests/image/fake.py index dea9c14e9..9d07f4c0d 100644 --- a/nova/tests/image/fake.py +++ b/nova/tests/image/fake.py @@ -188,7 +188,7 @@ class _FakeImageService(object): return self.images[image_id] def update(self, context, image_id, metadata, data=None, - headers=None): + purge_props=False): """Replace the contents of the given image with the new data. :raises: ImageNotFound if the image does not exist. @@ -196,11 +196,7 @@ class _FakeImageService(object): """ if not self.images.get(image_id): raise exception.ImageNotFound(image_id=image_id) - try: - purge = headers['x-glance-registry-purge-props'] - except Exception: - purge = True - if purge: + if purge_props: self.images[image_id] = copy.deepcopy(metadata) else: image = self.images[image_id] diff --git a/nova/tests/image/test_glance.py b/nova/tests/image/test_glance.py index fadea5a8c..f34e24c0b 100644 --- a/nova/tests/image/test_glance.py +++ b/nova/tests/image/test_glance.py @@ -20,7 +20,7 @@ import datetime import random import time -import glance.common.exception as glance_exception +import glanceclient.exc from nova import context from nova import exception @@ -331,7 +331,9 @@ class TestGlanceImageService(test.TestCase): def test_update(self): fixture = self._make_fixture(name='test image') - image_id = self.service.create(self.context, fixture)['id'] + image = self.service.create(self.context, fixture) + print image + image_id = image['id'] fixture['name'] = 'new image name' self.service.update(self.context, image_id, fixture) @@ -395,17 +397,6 @@ class TestGlanceImageService(test.TestCase): self.context, image_id) - def test_show_raises_on_missing_credential(self): - def raise_missing_credentials(*args, **kwargs): - raise glance_exception.MissingCredentialError() - - self.stubs.Set(glance_stubs.StubGlanceClient, 'get_image_meta', - raise_missing_credentials) - self.assertRaises(exception.ImageNotAuthorized, - self.service.show, - self.context, - 'test-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'] @@ -451,12 +442,12 @@ class TestGlanceImageService(test.TestCase): class MyGlanceStubClient(glance_stubs.StubGlanceClient): """A client that fails the first time, then succeeds.""" - def get_image(self, image_id): + def get(self, image_id): if tries[0] == 0: tries[0] = 1 - raise glance_exception.ClientConnectionError() + raise glanceclient.exc.ServiceUnavailable('') else: - return {}, [] + return {} client = MyGlanceStubClient() service = self._create_image_service(client) @@ -476,8 +467,8 @@ class TestGlanceImageService(test.TestCase): def test_client_raises_forbidden(self): class MyGlanceStubClient(glance_stubs.StubGlanceClient): """A client that fails the first time, then succeeds.""" - def get_image(self, image_id): - raise glance_exception.Forbidden() + def get(self, image_id): + raise glanceclient.exc.Forbidden(image_id) client = MyGlanceStubClient() service = self._create_image_service(client) @@ -507,11 +498,11 @@ class TestGlanceImageService(test.TestCase): def _create_failing_glance_client(info): class MyGlanceStubClient(glance_stubs.StubGlanceClient): """A client that fails the first time, then succeeds.""" - def get_image(self, image_id): + def get(self, image_id): info['num_calls'] += 1 if info['num_calls'] == 1: - raise glance_exception.ClientConnectionError() - return {}, [] + raise glanceclient.exc.ServiceUnavailable('') + return {} return MyGlanceStubClient() @@ -548,7 +539,7 @@ class TestGlanceClientWrapper(test.TestCase): client = glance.GlanceClientWrapper(context=ctxt, host=fake_host, port=fake_port) self.assertRaises(exception.GlanceConnectionFailed, - client.call, ctxt, 'get_image', 'meow') + client.call, ctxt, 'get', 'meow') self.assertEqual(info['num_calls'], 1) def test_default_client_without_retries(self): @@ -576,7 +567,7 @@ class TestGlanceClientWrapper(test.TestCase): client = glance.GlanceClientWrapper() client2 = glance.GlanceClientWrapper() self.assertRaises(exception.GlanceConnectionFailed, - client.call, ctxt, 'get_image', 'meow') + client.call, ctxt, 'get', 'meow') self.assertEqual(info['num_calls'], 1) info = {'num_calls': 0, @@ -590,7 +581,7 @@ class TestGlanceClientWrapper(test.TestCase): self.stubs.Set(random, 'shuffle', _fake_shuffle2) self.assertRaises(exception.GlanceConnectionFailed, - client2.call, ctxt, 'get_image', 'meow') + client2.call, ctxt, 'get', 'meow') self.assertEqual(info['num_calls'], 1) def test_static_client_with_retries(self): @@ -612,7 +603,7 @@ class TestGlanceClientWrapper(test.TestCase): client = glance.GlanceClientWrapper(context=ctxt, host=fake_host, port=fake_port) - client.call(ctxt, 'get_image', 'meow') + client.call(ctxt, 'get', 'meow') self.assertEqual(info['num_calls'], 2) def test_default_client_with_retries(self): @@ -642,7 +633,7 @@ class TestGlanceClientWrapper(test.TestCase): client = glance.GlanceClientWrapper() client2 = glance.GlanceClientWrapper() - client.call(ctxt, 'get_image', 'meow') + client.call(ctxt, 'get', 'meow') self.assertEqual(info['num_calls'], 2) def _fake_shuffle2(servers): @@ -657,5 +648,5 @@ class TestGlanceClientWrapper(test.TestCase): 'host1': 'host3', 'port1': 9294} - client2.call(ctxt, 'get_image', 'meow') + client2.call(ctxt, 'get', 'meow') self.assertEqual(info['num_calls'], 2) diff --git a/nova/tests/image/test_s3.py b/nova/tests/image/test_s3.py index 27a47fc55..5002be16f 100644 --- a/nova/tests/image/test_s3.py +++ b/nova/tests/image/test_s3.py @@ -191,8 +191,7 @@ class TestS3ImageService(test.TestCase): uuid = translated['id'] image_service = fake.FakeImageService() updated_image = image_service.update(self.context, uuid, - {'is_public': True}, None, - {'x-glance-registry-purge-props': False}) + {'is_public': True}, purge_props=False) self.assertTrue(updated_image['is_public']) self.assertEqual(updated_image['status'], 'active') self.assertEqual(updated_image['properties']['image_state'], -- cgit