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:
parent
5864097f5a
commit
0b94c431ab
@ -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)]
|
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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,
|
||||||
|
@ -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)
|
||||||
|
@ -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):
|
||||||
|
@ -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())
|
||||||
|
@ -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."))
|
||||||
|
@ -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', []))
|
||||||
|
@ -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):
|
||||||
|
@ -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
|
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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,
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user