From 7b526b6a7e0e118ca10bede834b3ad48a0e0feed Mon Sep 17 00:00:00 2001 From: John Postlethwait Date: Sun, 11 Mar 2012 18:37:18 -0700 Subject: [PATCH] When the glance client throws an Exception at Horizon, catch it and determine if it is an HTTP exception. If it is, recast it as a glance_client.ClientConnectionError so that Horizon can deal with it in a better manner. Fixes 951200 Change-Id: I7cbfa74d340d25d523ab8fb18138f5cf7d33a9d9 --- horizon/api/glance.py | 33 +++++++++++++++++++++++++ horizon/tests/api_tests/glance_tests.py | 31 +++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/horizon/api/glance.py b/horizon/api/glance.py index 694705da6..1f8f8cd31 100644 --- a/horizon/api/glance.py +++ b/horizon/api/glance.py @@ -20,10 +20,14 @@ from __future__ import absolute_import +import functools import logging import urlparse +from django.utils.decorators import available_attrs + from glance import client as glance_client +from glance.common import exception as glance_exception from horizon.api.base import APIDictWrapper, url_for @@ -31,6 +35,27 @@ from horizon.api.base import APIDictWrapper, url_for LOG = logging.getLogger(__name__) +def catch_glance_exception(func): + """ + The glance client sometimes throws ``Exception`` classed exceptions for + HTTP communication issues. Catch those, and rethrow them as + ``glance_client.ClientConnectionErrors`` so that we can do something + useful with them. + """ + # TODO(johnp): Remove this once Bug 952618 is fixed in the glance client. + @functools.wraps(func, assigned=available_attrs(func)) + def inner_func(*args, **kwargs): + try: + return func(*args, **kwargs) + except Exception as exc: + exc_message = str(exc) + if('Unknown error occurred' in exc_message or + 'Internal Server error' in exc_message): + raise glance_exception.ClientConnectionError(exc_message) + raise + return inner_func + + class Image(APIDictWrapper): """ Wrapper around glance image dictionary to make it object-like and provide @@ -58,6 +83,7 @@ class ImageProperties(APIDictWrapper): 'project_id', 'ramdisk_id', 'image_type'] +@catch_glance_exception def glanceclient(request): o = urlparse.urlparse(url_for(request, 'image')) LOG.debug('glanceclient connection created for host "%s:%d"' % @@ -67,14 +93,17 @@ def glanceclient(request): auth_tok=request.user.token) +@catch_glance_exception def image_create(request, image_meta, image_file): return Image(glanceclient(request).add_image(image_meta, image_file)) +@catch_glance_exception def image_delete(request, image_id): return glanceclient(request).delete_image(image_id) +@catch_glance_exception def image_get(request, image_id): """ Returns the actual image file from Glance for image with @@ -83,6 +112,7 @@ def image_get(request, image_id): return glanceclient(request).get_image(image_id)[1] +@catch_glance_exception def image_get_meta(request, image_id): """ Returns an Image object populated with metadata for image @@ -91,16 +121,19 @@ def image_get_meta(request, image_id): return Image(glanceclient(request).get_image_meta(image_id)) +@catch_glance_exception def image_list_detailed(request): return [Image(i) for i in glanceclient(request).get_images_detailed()] +@catch_glance_exception def image_update(request, image_id, image_meta=None): image_meta = image_meta and image_meta or {} return Image(glanceclient(request).update_image(image_id, image_meta=image_meta)) +@catch_glance_exception def snapshot_list_detailed(request): filters = {} filters['property-image_type'] = 'snapshot' diff --git a/horizon/tests/api_tests/glance_tests.py b/horizon/tests/api_tests/glance_tests.py index 7a1b0423a..f6960971f 100644 --- a/horizon/tests/api_tests/glance_tests.py +++ b/horizon/tests/api_tests/glance_tests.py @@ -18,6 +18,8 @@ # License for the specific language governing permissions and limitations # under the License. +from glance.common import exception as glance_exception + from horizon import api from horizon import test @@ -56,6 +58,35 @@ class GlanceApiTests(test.APITestCase): self.assertIsInstance(image, api.glance.Image) self.assertIn(image._apidict, images) + def test_glance_exception_wrapping_for_internal_server_errors(self): + """ + Verify that generic "Exception" classed exceptions from the glance + client's HTTP Internal Service Errors get converted to + ClientConnectionError's. + """ + # TODO(johnp): Remove once Bug 952618 is fixed in the glance client. + glanceclient = self.stub_glanceclient() + glanceclient.get_images_detailed().AndRaise( + Exception("Internal Server error: ")) + self.mox.ReplayAll() + + with self.assertRaises(glance_exception.ClientConnectionError): + api.image_list_detailed(self.request) + + def test_glance_exception_wrapping_for_generic_http_errors(self): + """ + Verify that generic "Exception" classed exceptions from the glance + client's HTTP errors get converted to ClientConnectionError's. + """ + # TODO(johnp): Remove once Bug 952618 is fixed in the glance client. + glanceclient = self.stub_glanceclient() + glanceclient.get_images_detailed().AndRaise( + Exception("Unknown error occurred! 503 Service Unavailable")) + self.mox.ReplayAll() + + with self.assertRaises(glance_exception.ClientConnectionError): + api.image_list_detailed(self.request) + class ImageWrapperTests(test.TestCase): """ Tests for wrapper classes since they have extra logic attached. """