Move to python-glanceclient.

This depends on https://review.openstack.org/#/c/6506/ for image
filtering support. Do not merge this until that has been merged.

Change-Id: I12e420f153b7b8323956e741bf9a202e31daa3b5
This commit is contained in:
Gabriel Hurley 2012-04-12 17:13:56 -07:00
parent 5864097f5a
commit 0b94c431ab
16 changed files with 90 additions and 238 deletions

View File

@ -20,123 +20,45 @@
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 glanceclient.v1 import client as glance_client
from glance import client as glance_client from horizon.api.base import url_for
from glance.common import exception as glance_exception
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):
"""
Wrapper around glance image dictionary to make it object-like and provide
access to image properties.
"""
_attrs = ['checksum', 'container_format', 'created_at', 'deleted',
'deleted_at', 'disk_format', 'id', 'is_public', 'location',
'name', 'properties', 'size', 'status', 'updated_at', 'owner']
def __getattr__(self, attrname):
if attrname == "properties":
if not hasattr(self, "_properties"):
properties_dict = super(Image, self).__getattr__(attrname)
self._properties = ImageProperties(properties_dict)
return self._properties
else:
return super(Image, self).__getattr__(attrname)
class ImageProperties(APIDictWrapper):
"""
Wrapper around glance image properties dictionary to make it object-like.
"""
_attrs = ['architecture', 'image_location', 'image_state', 'kernel_id',
'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"' % url = "://".join((o.scheme, o.netloc))
(o.hostname, o.port)) LOG.debug('glanceclient connection created using token "%s" and url "%s"' %
return glance_client.Client(o.hostname, (request.user.token, url))
o.port, return glance_client.Client(endpoint=url, token=request.user.token)
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): def image_delete(request, image_id):
return glanceclient(request).delete_image(image_id) return glanceclient(request).images.delete(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
supplied identifier
"""
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 Returns an Image object populated with metadata for image
with supplied identifier. with supplied identifier.
""" """
return Image(glanceclient(request).get_image_meta(image_id)) return glanceclient(request).images.get(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 glanceclient(request).images.list()
@catch_glance_exception def image_update(request, image_id, **kwargs):
def image_update(request, image_id, image_meta=None): return glanceclient(request).images.update(image_id, **kwargs)
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): def snapshot_list_detailed(request):
filters = {} filters = {'property-image_type': 'snapshot'}
filters['property-image_type'] = 'snapshot' return glanceclient(request).images.list(filters=filters)
filters['is_public'] = 'none'
return [Image(i) for i in glanceclient(request)
.get_images_detailed(filters=filters)]

View File

@ -95,12 +95,12 @@ class Server(APIResourceWrapper):
@property @property
def image_name(self): def image_name(self):
from glance.common import exception as glance_exceptions from glanceclient.common import exceptions as glance_exceptions
from horizon.api import glance from horizon.api import glance
try: try:
image = glance.image_get_meta(self.request, self.image['id']) image = glance.image_get(self.request, self.image['id'])
return image.name return image.name
except glance_exceptions.NotFound: except glance_exceptions.ClientException:
return "(not found)" return "(not found)"
@property @property

View File

@ -66,13 +66,15 @@ class UpdateImageForm(forms.SelfHandlingForm):
widget=forms.TextInput( widget=forms.TextInput(
attrs={'readonly': 'readonly'} attrs={'readonly': 'readonly'}
)) ))
public = forms.BooleanField(label=_("Public"),
required=False)
def handle(self, request, data): def handle(self, request, data):
# TODO add public flag to image meta properties # TODO add public flag to image meta properties
image_id = data['image_id'] image_id = data['image_id']
error_updating = _('Unable to update image "%s".') error_updating = _('Unable to update image "%s".')
meta = {'is_public': True, meta = {'is_public': data['public'],
'disk_format': data['disk_format'], 'disk_format': data['disk_format'],
'container_format': data['container_format'], 'container_format': data['container_format'],
'name': data['name'], 'name': data['name'],
@ -85,7 +87,7 @@ class UpdateImageForm(forms.SelfHandlingForm):
meta['properties']['architecture'] = data['architecture'] meta['properties']['architecture'] = data['architecture']
try: try:
api.image_update(request, image_id, meta) api.image_update(request, image_id, **meta)
messages.success(request, _('Image was successfully updated.')) messages.success(request, _('Image was successfully updated.'))
except: except:
exceptions.handle(request, error_updating % image_id) exceptions.handle(request, error_updating % image_id)

View File

@ -30,7 +30,7 @@ class OverviewTab(tabs.Tab):
def get_context_data(self, request): def get_context_data(self, request):
image_id = self.tab_group.kwargs['image_id'] image_id = self.tab_group.kwargs['image_id']
try: try:
image = api.glance.image_get_meta(self.request, image_id) image = api.glance.image_get(self.request, image_id)
except: except:
redirect = reverse('horizon:nova:images_and_snapshots:index') redirect = reverse('horizon:nova:images_and_snapshots:index')
exceptions.handle(request, exceptions.handle(request,

View File

@ -21,7 +21,7 @@
from django import http from django import http
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from glance.common import exception as glance_exception from glanceclient.common import exceptions as glance_exception
from horizon import api from horizon import api
from horizon import test from horizon import test
@ -39,14 +39,14 @@ class ImageViewTests(test.TestCase):
image = self.images.first() image = self.images.first()
quota_usages = self.quota_usages.first() quota_usages = self.quota_usages.first()
self.mox.StubOutWithMock(api, 'image_get_meta') self.mox.StubOutWithMock(api, 'image_get')
self.mox.StubOutWithMock(api, 'tenant_quota_usages') self.mox.StubOutWithMock(api, 'tenant_quota_usages')
# Two flavor_list calls, however, flavor_list is now memoized. # Two flavor_list calls, however, flavor_list is now memoized.
self.mox.StubOutWithMock(api, 'flavor_list') self.mox.StubOutWithMock(api, 'flavor_list')
self.mox.StubOutWithMock(api, 'flavor_list') self.mox.StubOutWithMock(api, 'flavor_list')
self.mox.StubOutWithMock(api, 'keypair_list') self.mox.StubOutWithMock(api, 'keypair_list')
self.mox.StubOutWithMock(api, 'security_group_list') self.mox.StubOutWithMock(api, 'security_group_list')
api.image_get_meta(IsA(http.HttpRequest), image.id).AndReturn(image) api.image_get(IsA(http.HttpRequest), image.id).AndReturn(image)
api.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(quota_usages) api.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(quota_usages)
api.flavor_list(IsA(http.HttpRequest)).AndReturn(self.flavors.list()) api.flavor_list(IsA(http.HttpRequest)).AndReturn(self.flavors.list())
api.flavor_list(IsA(http.HttpRequest)).AndReturn(self.flavors.list()) api.flavor_list(IsA(http.HttpRequest)).AndReturn(self.flavors.list())
@ -79,7 +79,7 @@ class ImageViewTests(test.TestCase):
volume_choice = "%s:vol" % volume.id volume_choice = "%s:vol" % volume.id
block_device_mapping = {device_name: u"%s::0" % volume_choice} block_device_mapping = {device_name: u"%s::0" % volume_choice}
self.mox.StubOutWithMock(api, 'image_get_meta') self.mox.StubOutWithMock(api, 'image_get')
self.mox.StubOutWithMock(api, 'flavor_list') self.mox.StubOutWithMock(api, 'flavor_list')
self.mox.StubOutWithMock(api, 'keypair_list') self.mox.StubOutWithMock(api, 'keypair_list')
self.mox.StubOutWithMock(api, 'security_group_list') self.mox.StubOutWithMock(api, 'security_group_list')
@ -90,7 +90,7 @@ class ImageViewTests(test.TestCase):
api.keypair_list(IsA(http.HttpRequest)).AndReturn(self.keypairs.list()) api.keypair_list(IsA(http.HttpRequest)).AndReturn(self.keypairs.list())
api.security_group_list(IsA(http.HttpRequest)) \ api.security_group_list(IsA(http.HttpRequest)) \
.AndReturn(self.security_groups.list()) .AndReturn(self.security_groups.list())
api.image_get_meta(IsA(http.HttpRequest), image.id).AndReturn(image) api.image_get(IsA(http.HttpRequest), image.id).AndReturn(image)
api.volume_list(IsA(http.HttpRequest)).AndReturn(self.volumes.list()) api.volume_list(IsA(http.HttpRequest)).AndReturn(self.volumes.list())
api.server_create(IsA(http.HttpRequest), api.server_create(IsA(http.HttpRequest),
server.name, server.name,
@ -124,13 +124,13 @@ class ImageViewTests(test.TestCase):
def test_launch_flavorlist_error(self): def test_launch_flavorlist_error(self):
image = self.images.first() image = self.images.first()
self.mox.StubOutWithMock(api, 'image_get_meta') self.mox.StubOutWithMock(api, 'image_get')
self.mox.StubOutWithMock(api, 'tenant_quota_usages') self.mox.StubOutWithMock(api, 'tenant_quota_usages')
self.mox.StubOutWithMock(api, 'flavor_list') self.mox.StubOutWithMock(api, 'flavor_list')
self.mox.StubOutWithMock(api, 'flavor_list') self.mox.StubOutWithMock(api, 'flavor_list')
self.mox.StubOutWithMock(api, 'keypair_list') self.mox.StubOutWithMock(api, 'keypair_list')
self.mox.StubOutWithMock(api, 'security_group_list') self.mox.StubOutWithMock(api, 'security_group_list')
api.image_get_meta(IsA(http.HttpRequest), api.image_get(IsA(http.HttpRequest),
image.id).AndReturn(image) image.id).AndReturn(image)
api.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn( api.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(
self.quota_usages.first()) self.quota_usages.first())
@ -151,13 +151,13 @@ class ImageViewTests(test.TestCase):
def test_launch_keypairlist_error(self): def test_launch_keypairlist_error(self):
image = self.images.first() image = self.images.first()
self.mox.StubOutWithMock(api, 'image_get_meta') self.mox.StubOutWithMock(api, 'image_get')
self.mox.StubOutWithMock(api, 'tenant_quota_usages') self.mox.StubOutWithMock(api, 'tenant_quota_usages')
self.mox.StubOutWithMock(api, 'flavor_list') self.mox.StubOutWithMock(api, 'flavor_list')
self.mox.StubOutWithMock(api, 'flavor_list') self.mox.StubOutWithMock(api, 'flavor_list')
self.mox.StubOutWithMock(api, 'keypair_list') self.mox.StubOutWithMock(api, 'keypair_list')
self.mox.StubOutWithMock(api, 'security_group_list') self.mox.StubOutWithMock(api, 'security_group_list')
api.image_get_meta(IsA(http.HttpRequest), image.id).AndReturn(image) api.image_get(IsA(http.HttpRequest), image.id).AndReturn(image)
api.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn( api.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(
self.quota_usages.first()) self.quota_usages.first())
api.flavor_list(IsA(http.HttpRequest)).AndReturn(self.flavors.list()) api.flavor_list(IsA(http.HttpRequest)).AndReturn(self.flavors.list())
@ -183,7 +183,7 @@ class ImageViewTests(test.TestCase):
sec_group = self.security_groups.first() sec_group = self.security_groups.first()
USER_DATA = 'userData' USER_DATA = 'userData'
self.mox.StubOutWithMock(api, 'image_get_meta') self.mox.StubOutWithMock(api, 'image_get')
self.mox.StubOutWithMock(api, 'flavor_list') self.mox.StubOutWithMock(api, 'flavor_list')
self.mox.StubOutWithMock(api, 'keypair_list') self.mox.StubOutWithMock(api, 'keypair_list')
self.mox.StubOutWithMock(api, 'security_group_list') self.mox.StubOutWithMock(api, 'security_group_list')
@ -194,7 +194,7 @@ class ImageViewTests(test.TestCase):
api.keypair_list(IgnoreArg()).AndReturn(self.keypairs.list()) api.keypair_list(IgnoreArg()).AndReturn(self.keypairs.list())
api.security_group_list(IsA(http.HttpRequest)) \ api.security_group_list(IsA(http.HttpRequest)) \
.AndReturn(self.security_groups.list()) .AndReturn(self.security_groups.list())
api.image_get_meta(IgnoreArg(), image.id).AndReturn(image) api.image_get(IgnoreArg(), image.id).AndReturn(image)
api.volume_list(IgnoreArg()).AndReturn(self.volumes.list()) api.volume_list(IgnoreArg()).AndReturn(self.volumes.list())
exc = keystone_exceptions.ClientException('Failed') exc = keystone_exceptions.ClientException('Failed')
api.server_create(IsA(http.HttpRequest), api.server_create(IsA(http.HttpRequest),
@ -233,7 +233,7 @@ class ImageViewTests(test.TestCase):
device_name = u'vda' device_name = u'vda'
volume_choice = "%s:vol" % volume.id volume_choice = "%s:vol" % volume.id
self.mox.StubOutWithMock(api, 'image_get_meta') self.mox.StubOutWithMock(api, 'image_get')
self.mox.StubOutWithMock(api, 'flavor_list') self.mox.StubOutWithMock(api, 'flavor_list')
self.mox.StubOutWithMock(api, 'flavor_list') self.mox.StubOutWithMock(api, 'flavor_list')
self.mox.StubOutWithMock(api, 'keypair_list') self.mox.StubOutWithMock(api, 'keypair_list')
@ -247,7 +247,7 @@ class ImageViewTests(test.TestCase):
api.keypair_list(IsA(http.HttpRequest)).AndReturn(self.keypairs.list()) api.keypair_list(IsA(http.HttpRequest)).AndReturn(self.keypairs.list())
api.security_group_list(IsA(http.HttpRequest)) \ api.security_group_list(IsA(http.HttpRequest)) \
.AndReturn(self.security_groups.list()) .AndReturn(self.security_groups.list())
api.image_get_meta(IsA(http.HttpRequest), image.id).AndReturn(image) api.image_get(IsA(http.HttpRequest), image.id).AndReturn(image)
api.volume_list(IsA(http.HttpRequest)).AndReturn(self.volumes.list()) api.volume_list(IsA(http.HttpRequest)).AndReturn(self.volumes.list())
api.volume_snapshot_list(IsA(http.HttpRequest)).AndReturn([]) api.volume_snapshot_list(IsA(http.HttpRequest)).AndReturn([])
api.tenant_quota_usages(IsA(http.HttpRequest)) \ api.tenant_quota_usages(IsA(http.HttpRequest)) \
@ -272,8 +272,8 @@ class ImageViewTests(test.TestCase):
def test_image_detail_get(self): def test_image_detail_get(self):
image = self.images.first() image = self.images.first()
self.mox.StubOutWithMock(api.glance, 'image_get_meta') self.mox.StubOutWithMock(api.glance, 'image_get')
api.glance.image_get_meta(IsA(http.HttpRequest), str(image.id)) \ api.glance.image_get(IsA(http.HttpRequest), str(image.id)) \
.AndReturn(self.images.first()) .AndReturn(self.images.first())
self.mox.ReplayAll() self.mox.ReplayAll()
@ -286,12 +286,12 @@ class ImageViewTests(test.TestCase):
def test_image_detail_get_with_exception(self): def test_image_detail_get_with_exception(self):
image = self.images.first() image = self.images.first()
self.mox.StubOutWithMock(api.glance, 'image_get_meta') self.mox.StubOutWithMock(api.glance, 'image_get')
api.glance.image_get_meta(IsA(http.HttpRequest), str(image.id)) \ api.glance.image_get(IsA(http.HttpRequest), str(image.id)) \
.AndRaise(glance_exception.NotFound) .AndRaise(glance_exception.ClientException('Error'))
self.mox.ReplayAll() self.mox.ReplayAll()
res = self.client.get( url = reverse('horizon:nova:images_and_snapshots:images:detail',
reverse('horizon:nova:images_and_snapshots:images:detail', args=[image.id])
args=[image.id])) res = self.client.get(url)
self.assertRedirectsNoFollow(res, IMAGES_INDEX_URL) self.assertRedirectsNoFollow(res, IMAGES_INDEX_URL)

View File

@ -55,7 +55,7 @@ class LaunchView(forms.ModalFormView):
def get_object(self, *args, **kwargs): def get_object(self, *args, **kwargs):
image_id = self.kwargs["image_id"] image_id = self.kwargs["image_id"]
try: try:
self.object = api.image_get_meta(self.request, image_id) self.object = api.image_get(self.request, image_id)
except: except:
msg = _('Unable to retrieve image "%s".') % image_id msg = _('Unable to retrieve image "%s".') % image_id
redirect = reverse('horizon:nova:images_and_snapshots:index') redirect = reverse('horizon:nova:images_and_snapshots:index')
@ -155,22 +155,26 @@ class UpdateView(forms.ModalFormView):
def get_object(self, *args, **kwargs): def get_object(self, *args, **kwargs):
try: try:
self.object = api.image_get_meta(self.request, kwargs['image_id']) self.object = api.image_get(self.request, kwargs['image_id'])
except: except:
msg = _('Unable to retrieve image "%s".') % kwargs['image_id'] msg = _('Unable to retrieve image.')
redirect = reverse('horizon:nova:images_and_snapshots:index') redirect = reverse('horizon:nova:images_and_snapshots:index')
exceptions.handle(self.request, msg, redirect=redirect) exceptions.handle(self.request, msg, redirect=redirect)
return self.object return self.object
def get_initial(self): def get_initial(self):
properties = self.object['properties'] properties = self.object.properties
# NOTE(gabriel): glanceclient currently treats "is_public" as a string
# rather than a boolean. This should be fixed in the client.
public = self.object.is_public == "True"
return {'image_id': self.kwargs['image_id'], return {'image_id': self.kwargs['image_id'],
'name': self.object.get('name', ''), 'name': self.object.name,
'kernel': properties.get('kernel_id', ''), 'kernel': properties.get('kernel_id', ''),
'ramdisk': properties.get('ramdisk_id', ''), 'ramdisk': properties.get('ramdisk_id', ''),
'architecture': properties.get('architecture', ''), 'architecture': properties.get('architecture', ''),
'container_format': self.object.get('container_format', ''), 'container_format': self.object.container_format,
'disk_format': self.object.get('disk_format', ''), } 'disk_format': self.object.disk_format,
'public': public}
class DetailView(tabs.TabView): class DetailView(tabs.TabView):

View File

@ -22,7 +22,7 @@
from copy import deepcopy from copy import deepcopy
from django import http from django import http
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from glance.common import exception as glance_exception from glanceclient.common import exceptions as glance_exception
from mox import IsA from mox import IsA
from horizon import api from horizon import api
@ -61,10 +61,10 @@ class ImagesAndSnapshotsTests(test.TestCase):
res = self.client.get(INDEX_URL) res = self.client.get(INDEX_URL)
self.assertTemplateUsed(res, 'nova/images_and_snapshots/index.html') self.assertTemplateUsed(res, 'nova/images_and_snapshots/index.html')
def test_index_client_conn_error(self): def test_index_error(self):
self.mox.StubOutWithMock(api, 'image_list_detailed') self.mox.StubOutWithMock(api, 'image_list_detailed')
self.mox.StubOutWithMock(api, 'snapshot_list_detailed') self.mox.StubOutWithMock(api, 'snapshot_list_detailed')
exc = glance_exception.ClientConnectionError('clientConnError') exc = glance_exception.ClientException('error')
api.image_list_detailed(IsA(http.HttpRequest)).AndRaise(exc) api.image_list_detailed(IsA(http.HttpRequest)).AndRaise(exc)
api.snapshot_list_detailed(IsA(http.HttpRequest)) \ api.snapshot_list_detailed(IsA(http.HttpRequest)) \
.AndReturn(self.snapshots.list()) .AndReturn(self.snapshots.list())

View File

@ -46,8 +46,8 @@ class IndexView(tables.MultiTableView):
try: try:
all_images = api.image_list_detailed(self.request) all_images = api.image_list_detailed(self.request)
images = [im for im in all_images images = [im for im in all_images
if im['container_format'] not in ['aki', 'ari'] and if im.container_format not in ['aki', 'ari'] and
getattr(im.properties, "image_type", '') != "snapshot"] im.properties.get("image_type", '') != "snapshot"]
except: except:
images = [] images = []
exceptions.handle(self.request, _("Unable to retrieve images.")) exceptions.handle(self.request, _("Unable to retrieve images."))

View File

@ -25,7 +25,7 @@ from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from cloudfiles import errors as swiftclient from cloudfiles import errors as swiftclient
from glance.common import exception as glanceclient from glanceclient.common import exceptions as glanceclient
from keystoneclient import exceptions as keystoneclient from keystoneclient import exceptions as keystoneclient
from novaclient import exceptions as novaclient from novaclient import exceptions as novaclient
@ -128,7 +128,7 @@ UNAUTHORIZED = (keystoneclient.Unauthorized,
novaclient.Unauthorized, novaclient.Unauthorized,
novaclient.Forbidden, novaclient.Forbidden,
glanceclient.AuthorizationFailure, glanceclient.AuthorizationFailure,
glanceclient.NotAuthorized, glanceclient.Unauthorized,
swiftclient.AuthenticationFailed, swiftclient.AuthenticationFailed,
swiftclient.AuthenticationError) swiftclient.AuthenticationError)
UNAUTHORIZED += tuple(EXCEPTION_CONFIG.get('unauthorized', [])) UNAUTHORIZED += tuple(EXCEPTION_CONFIG.get('unauthorized', []))
@ -146,7 +146,7 @@ RECOVERABLE = (keystoneclient.ClientException,
# AuthorizationFailure is raised when Keystone is "unavailable". # AuthorizationFailure is raised when Keystone is "unavailable".
keystoneclient.AuthorizationFailure, keystoneclient.AuthorizationFailure,
novaclient.ClientException, novaclient.ClientException,
glanceclient.GlanceException, glanceclient.ClientException,
swiftclient.Error, swiftclient.Error,
AlreadyExists) AlreadyExists)
RECOVERABLE += tuple(EXCEPTION_CONFIG.get('recoverable', [])) RECOVERABLE += tuple(EXCEPTION_CONFIG.get('recoverable', []))

View File

@ -27,7 +27,7 @@ from django.conf import settings
from django.contrib.messages.storage import default_storage from django.contrib.messages.storage import default_storage
from django.core.handlers import wsgi from django.core.handlers import wsgi
from django.test.client import RequestFactory from django.test.client import RequestFactory
from glance import client as glance_client from glanceclient.v1 import client as glance_client
from keystoneclient.v2_0 import client as keystone_client from keystoneclient.v2_0 import client as keystone_client
from novaclient.v1_1 import client as nova_client from novaclient.v1_1 import client as nova_client
import httplib2 import httplib2
@ -299,7 +299,6 @@ class APITestCase(TestCase):
if not hasattr(self, "glanceclient"): if not hasattr(self, "glanceclient"):
self.mox.StubOutWithMock(glance_client, 'Client') self.mox.StubOutWithMock(glance_client, 'Client')
self.glanceclient = self.mox.CreateMock(glance_client.Client) self.glanceclient = self.mox.CreateMock(glance_client.Client)
self.glanceclient.token = self.tokens.first().id
return self.glanceclient return self.glanceclient
def stub_swiftclient(self, expected_calls=1): def stub_swiftclient(self, expected_calls=1):

View File

@ -18,100 +18,19 @@
# 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
class GlanceApiTests(test.APITestCase): class GlanceApiTests(test.APITestCase):
def test_get_glanceclient(self): def test_snapshot_list_detailed(self):
""" Verify the client connection method does what we expect. """
# Replace the original client which is stubbed out in setUp()
api.glance.glanceclient = self._original_glanceclient
client = api.glance.glanceclient(self.request)
self.assertEqual(client.auth_tok, self.tokens.first().id)
def test_image_get_meta(self):
""" Verify "get" returns our custom Image class. """
image = self.images.get(id='1')
glanceclient = self.stub_glanceclient()
glanceclient.get_image_meta(image.id).AndReturn(image)
self.mox.ReplayAll()
ret_val = api.image_get_meta(self.request, image.id)
self.assertIsInstance(ret_val, api.glance.Image)
self.assertEqual(ret_val._apidict, image)
def test_image_list_detailed(self):
""" Verify "list" returns our custom Image class. """
images = self.images.list() images = self.images.list()
filters = {'property-image_type': 'snapshot'}
glanceclient = self.stub_glanceclient() glanceclient = self.stub_glanceclient()
glanceclient.get_images_detailed().AndReturn(images) glanceclient.images = self.mox.CreateMockAnything()
glanceclient.images.list(filters=filters).AndReturn(images)
self.mox.ReplayAll() self.mox.ReplayAll()
ret_val = api.image_list_detailed(self.request) # No assertions are necessary. Verification is handled by mox.
for image in ret_val: api.glance.snapshot_list_detailed(self.request)
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. """
WITHOUT_PROPERTIES = {'size': 100}
WITH_PROPERTIES = {'properties': {'image_state': 'running'},
'size': 100}
def test_get_properties(self):
image = api.Image(self.WITH_PROPERTIES)
image_props = image.properties
self.assertIsInstance(image_props, api.ImageProperties)
self.assertEqual(image_props.image_state, 'running')
def test_get_other(self):
image = api.Image(self.WITH_PROPERTIES)
self.assertEqual(image.size, 100)
def test_get_properties_missing(self):
image = api.Image(self.WITHOUT_PROPERTIES)
with self.assertRaises(AttributeError):
image.properties
def test_get_other_missing(self):
image = api.Image(self.WITHOUT_PROPERTIES)
with self.assertRaises(AttributeError):
self.assertNotIn('missing', image._attrs,
msg="Test assumption broken. Find new missing attribute")
image.missing

View File

@ -35,9 +35,9 @@ class ServerWrapperTests(test.TestCase):
self.assertEqual(server.id, self.servers.first().id) self.assertEqual(server.id, self.servers.first().id)
def test_image_name(self): def test_image_name(self):
image = api.Image(self.images.first()) image = self.images.first()
self.mox.StubOutWithMock(api.glance, 'image_get_meta') self.mox.StubOutWithMock(api.glance, 'image_get')
api.glance.image_get_meta(IsA(http.HttpRequest), api.glance.image_get(IsA(http.HttpRequest),
image.id).AndReturn(image) image.id).AndReturn(image)
self.mox.ReplayAll() self.mox.ReplayAll()

View File

@ -12,7 +12,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 horizon.api import glance from glanceclient.v1.images import Image, ImageManager
from .utils import TestDataContainer from .utils import TestDataContainer
@ -23,24 +24,21 @@ def data(TEST):
# Snapshots # Snapshots
snapshot_dict = {'name': u'snapshot', snapshot_dict = {'name': u'snapshot',
'container_format': u'ami', 'container_format': u'ami',
'id': 3} 'id': 3,
snapshot = glance.Image(snapshot_dict) 'properties': {'image_type': u'snapshot'}}
snapshot_properties_dict = {'image_type': u'snapshot'} snapshot = Image(ImageManager(None), snapshot_dict)
snapshot.properties = glance.ImageProperties(snapshot_properties_dict)
TEST.snapshots.add(snapshot) TEST.snapshots.add(snapshot)
# Images # Images
image_properties_dict = {'image_type': u'image'}
image_dict = {'id': '1', image_dict = {'id': '1',
'name': 'public_image', 'name': 'public_image',
'container_format': 'novaImage'} 'container_format': 'novaImage',
public_image = glance.Image(image_dict) 'properties': {'image_type': u'image'}}
public_image.properties = glance.ImageProperties(image_properties_dict) public_image = Image(ImageManager(None), image_dict)
image_dict = {'id': '2', image_dict = {'id': '2',
'name': 'private_image', 'name': 'private_image',
'container_format': 'aki'} 'container_format': 'aki'}
private_image = glance.Image(image_dict) private_image = Image(ImageManager(None), image_dict)
private_image.properties = glance.ImageProperties(image_properties_dict)
TEST.images.add(public_image, private_image) TEST.images.add(public_image, private_image)

View File

@ -101,6 +101,10 @@ LOGGING = {
'handlers': ['console'], 'handlers': ['console'],
'propagate': False, 'propagate': False,
}, },
'openstack_dashboard': {
'handlers': ['console'],
'propagate': False,
},
'novaclient': { 'novaclient': {
'handlers': ['console'], 'handlers': ['console'],
'propagate': False, 'propagate': False,
@ -109,6 +113,10 @@ LOGGING = {
'handlers': ['console'], 'handlers': ['console'],
'propagate': False, 'propagate': False,
}, },
'glanceclient': {
'handlers': ['console'],
'propagate': False,
},
'nose.plugins.manager': { 'nose.plugins.manager': {
'handlers': ['console'], 'handlers': ['console'],
'propagate': False, 'propagate': False,

View File

@ -6,7 +6,7 @@ set -o errexit
# Increment me any time the environment should be rebuilt. # Increment me any time the environment should be rebuilt.
# This includes dependncy changes, directory renames, etc. # This includes dependncy changes, directory renames, etc.
# Simple integer secuence: 1, 2, 3... # Simple integer secuence: 1, 2, 3...
environment_version=15 environment_version=16
#--------------------------------------------------------# #--------------------------------------------------------#
function usage { function usage {

View File

@ -6,4 +6,4 @@ python-dateutil
# Horizon Non-pip Requirements # Horizon Non-pip Requirements
-e git+https://github.com/openstack/python-novaclient.git#egg=python-novaclient -e git+https://github.com/openstack/python-novaclient.git#egg=python-novaclient
-e git+https://github.com/openstack/python-keystoneclient.git#egg=python-keystoneclient -e git+https://github.com/openstack/python-keystoneclient.git#egg=python-keystoneclient
-e git+https://github.com/openstack/glance.git#egg=glance -e git+https://github.com/openstack/python-glanceclient.git#egg=python-glanceclient