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
This commit is contained in:
parent
bf217e8381
commit
7b526b6a7e
@ -20,10 +20,14 @@
|
|||||||
|
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
import functools
|
||||||
import logging
|
import logging
|
||||||
import urlparse
|
import urlparse
|
||||||
|
|
||||||
|
from django.utils.decorators import available_attrs
|
||||||
|
|
||||||
from glance import client as glance_client
|
from glance import client as glance_client
|
||||||
|
from glance.common import exception as glance_exception
|
||||||
|
|
||||||
from horizon.api.base import APIDictWrapper, url_for
|
from horizon.api.base import APIDictWrapper, url_for
|
||||||
|
|
||||||
@ -31,6 +35,27 @@ from horizon.api.base import APIDictWrapper, url_for
|
|||||||
LOG = logging.getLogger(__name__)
|
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):
|
class Image(APIDictWrapper):
|
||||||
"""
|
"""
|
||||||
Wrapper around glance image dictionary to make it object-like and provide
|
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']
|
'project_id', 'ramdisk_id', 'image_type']
|
||||||
|
|
||||||
|
|
||||||
|
@catch_glance_exception
|
||||||
def glanceclient(request):
|
def glanceclient(request):
|
||||||
o = urlparse.urlparse(url_for(request, 'image'))
|
o = urlparse.urlparse(url_for(request, 'image'))
|
||||||
LOG.debug('glanceclient connection created for host "%s:%d"' %
|
LOG.debug('glanceclient connection created for host "%s:%d"' %
|
||||||
@ -67,14 +93,17 @@ def glanceclient(request):
|
|||||||
auth_tok=request.user.token)
|
auth_tok=request.user.token)
|
||||||
|
|
||||||
|
|
||||||
|
@catch_glance_exception
|
||||||
def image_create(request, image_meta, image_file):
|
def image_create(request, image_meta, image_file):
|
||||||
return Image(glanceclient(request).add_image(image_meta, image_file))
|
return Image(glanceclient(request).add_image(image_meta, image_file))
|
||||||
|
|
||||||
|
|
||||||
|
@catch_glance_exception
|
||||||
def image_delete(request, image_id):
|
def image_delete(request, image_id):
|
||||||
return glanceclient(request).delete_image(image_id)
|
return glanceclient(request).delete_image(image_id)
|
||||||
|
|
||||||
|
|
||||||
|
@catch_glance_exception
|
||||||
def image_get(request, image_id):
|
def image_get(request, image_id):
|
||||||
"""
|
"""
|
||||||
Returns the actual image file from Glance for image with
|
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]
|
return glanceclient(request).get_image(image_id)[1]
|
||||||
|
|
||||||
|
|
||||||
|
@catch_glance_exception
|
||||||
def image_get_meta(request, image_id):
|
def image_get_meta(request, image_id):
|
||||||
"""
|
"""
|
||||||
Returns an Image object populated with metadata for image
|
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))
|
return Image(glanceclient(request).get_image_meta(image_id))
|
||||||
|
|
||||||
|
|
||||||
|
@catch_glance_exception
|
||||||
def image_list_detailed(request):
|
def image_list_detailed(request):
|
||||||
return [Image(i) for i in glanceclient(request).get_images_detailed()]
|
return [Image(i) for i in glanceclient(request).get_images_detailed()]
|
||||||
|
|
||||||
|
|
||||||
|
@catch_glance_exception
|
||||||
def image_update(request, image_id, image_meta=None):
|
def image_update(request, image_id, image_meta=None):
|
||||||
image_meta = image_meta and image_meta or {}
|
image_meta = image_meta and image_meta or {}
|
||||||
return Image(glanceclient(request).update_image(image_id,
|
return Image(glanceclient(request).update_image(image_id,
|
||||||
image_meta=image_meta))
|
image_meta=image_meta))
|
||||||
|
|
||||||
|
|
||||||
|
@catch_glance_exception
|
||||||
def snapshot_list_detailed(request):
|
def snapshot_list_detailed(request):
|
||||||
filters = {}
|
filters = {}
|
||||||
filters['property-image_type'] = 'snapshot'
|
filters['property-image_type'] = 'snapshot'
|
||||||
|
@ -18,6 +18,8 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
from glance.common import exception as glance_exception
|
||||||
|
|
||||||
from horizon import api
|
from horizon import api
|
||||||
from horizon import test
|
from horizon import test
|
||||||
|
|
||||||
@ -56,6 +58,35 @@ class GlanceApiTests(test.APITestCase):
|
|||||||
self.assertIsInstance(image, api.glance.Image)
|
self.assertIsInstance(image, api.glance.Image)
|
||||||
self.assertIn(image._apidict, images)
|
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):
|
class ImageWrapperTests(test.TestCase):
|
||||||
""" Tests for wrapper classes since they have extra logic attached. """
|
""" Tests for wrapper classes since they have extra logic attached. """
|
||||||
|
Loading…
Reference in New Issue
Block a user