Mix snapshots and images tables together

Don't filter snapshots from images data, display image_type column in images table,
don't translate image string in get_image_type method, don't show LaunchImage button when
image is in different state than active, remove snapshots table from the UI, update tests

Fixes: bug #1140760

Change-Id: Ie6cc5bc98fa97de00b55540fe9f60e90d20acab3
This commit is contained in:
Jiri Tomasek 2013-05-16 09:12:24 +02:00
parent f5bf488c06
commit 8a0b4c7e47
9 changed files with 47 additions and 155 deletions

View File

@ -102,9 +102,3 @@ def image_create(request, **kwargs):
{'copy_from': copy_from})
return image
def snapshot_list_detailed(request, marker=None, extra_filters=None):
filters = {'property-image_type': 'snapshot'}
filters.update(extra_filters or {})
return image_list_detailed(request, marker, filters, paginate=True)

View File

@ -40,10 +40,21 @@ class LaunchImage(tables.LinkAction):
def get_link_url(self, datum):
base_url = reverse(self.url)
params = urlencode({"source_type": "image_id",
if get_image_type(datum) == "image":
source_type = "image_id"
else:
source_type = "instance_snapshot_id"
params = urlencode({"source_type": source_type,
"source_id": self.table.get_object_id(datum)})
return "?".join([base_url, params])
def allowed(self, request, image=None):
if image:
return image.status in ("active",)
return False
class DeleteImage(tables.DeleteAction):
data_type_singular = _("Image")
@ -148,7 +159,7 @@ def get_image_categories(im, user_tenant_id):
def get_image_type(image):
return getattr(image, "properties", {}).get("image_type", _("Image"))
return getattr(image, "properties", {}).get("image_type", "image")
def get_format(image):
@ -212,9 +223,6 @@ class ImagesTable(tables.DataTable):
row_class = UpdateRow
status_columns = ["status"]
verbose_name = _("Images")
# Hide the image_type column. Done this way so subclasses still get
# all the columns by default.
columns = ["name", "status", "public", "protected", "disk_format"]
table_actions = (OwnerFilter, CreateImage, DeleteImage,)
row_actions = (LaunchImage, CreateVolumeFromImage,
EditImage, DeleteImage,)

View File

@ -1,60 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 Nebula, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import logging
from django.core.urlresolvers import reverse
from django.utils.http import urlencode
from django.utils.translation import ugettext_lazy as _
from horizon import tables
from ..images.tables import ImagesTable, EditImage, DeleteImage, UpdateRow
LOG = logging.getLogger(__name__)
class LaunchSnapshot(tables.LinkAction):
name = "launch_snapshot"
verbose_name = _("Launch")
url = "horizon:project:instances:launch"
classes = ("btn-launch", "ajax-modal")
def get_link_url(self, datum):
base_url = reverse(self.url)
params = urlencode({"source_type": "instance_snapshot_id",
"source_id": self.table.get_object_id(datum)})
return "?".join([base_url, params])
def allowed(self, request, snapshot):
return snapshot.status in ("active",)
class DeleteSnapshot(DeleteImage):
data_type_singular = _("Snapshot")
data_type_plural = _("Snapshots")
class SnapshotsTable(ImagesTable):
class Meta(ImagesTable.Meta):
name = "snapshots"
verbose_name = _("Instance Snapshots")
table_actions = (DeleteSnapshot,)
row_actions = (LaunchSnapshot, EditImage, DeleteSnapshot)
pagination_param = "snapshot_marker"
row_class = UpdateRow
status_columns = ["status"]

View File

@ -10,9 +10,6 @@
<div class="images">
{{ images_table.render }}
</div>
<div class="snapshots">
{{ snapshots_table.render }}
</div>
<div class="volume_snapshots">
{{ volume_snapshots_table.render }}
</div>

View File

@ -32,8 +32,7 @@ INDEX_URL = reverse('horizon:project:images_and_snapshots:index')
class ImagesAndSnapshotsTests(test.TestCase):
@test.create_stubs({api.glance: ('image_list_detailed',
'snapshot_list_detailed'),
@test.create_stubs({api.glance: ('image_list_detailed',),
api.cinder: ('volume_snapshot_list', 'volume_get')})
def test_index(self):
images = self.images.list()
@ -54,8 +53,6 @@ class ImagesAndSnapshotsTests(test.TestCase):
.AndReturn(volumes)
api.glance.image_list_detailed(IsA(http.HttpRequest),
marker=None).AndReturn([images, False])
api.glance.snapshot_list_detailed(IsA(http.HttpRequest), marker=None) \
.AndReturn([snapshots, False])
self.mox.ReplayAll()
res = self.client.get(INDEX_URL)
@ -77,8 +74,7 @@ class ImagesAndSnapshotsTests(test.TestCase):
row_actions = images_table.get_row_actions(images[2])
self.assertTrue(len(row_actions), 3)
@test.create_stubs({api.glance: ('image_list_detailed',
'snapshot_list_detailed'),
@test.create_stubs({api.glance: ('image_list_detailed',),
api.cinder: ('volume_snapshot_list', 'volume_get')})
def test_index_no_images(self):
volumes = self.volumes.list()
@ -97,15 +93,12 @@ class ImagesAndSnapshotsTests(test.TestCase):
.AndReturn(volumes)
api.glance.image_list_detailed(IsA(http.HttpRequest),
marker=None).AndReturn([(), False])
api.glance.snapshot_list_detailed(IsA(http.HttpRequest), marker=None) \
.AndReturn([self.snapshots.list(), False])
self.mox.ReplayAll()
res = self.client.get(INDEX_URL)
self.assertTemplateUsed(res, 'project/images_and_snapshots/index.html')
@test.create_stubs({api.glance: ('image_list_detailed',
'snapshot_list_detailed'),
@test.create_stubs({api.glance: ('image_list_detailed',),
api.cinder: ('volume_snapshot_list', 'volume_get')})
def test_index_error(self):
volumes = self.volumes.list()
@ -125,18 +118,14 @@ class ImagesAndSnapshotsTests(test.TestCase):
api.glance.image_list_detailed(IsA(http.HttpRequest),
marker=None) \
.AndRaise(self.exceptions.glance)
api.glance.snapshot_list_detailed(IsA(http.HttpRequest), marker=None) \
.AndReturn([self.snapshots.list(), False])
self.mox.ReplayAll()
res = self.client.get(INDEX_URL)
self.assertTemplateUsed(res, 'project/images_and_snapshots/index.html')
@test.create_stubs({api.glance: ('image_list_detailed',
'snapshot_list_detailed'),
@test.create_stubs({api.glance: ('image_list_detailed',),
api.cinder: ('volume_snapshot_list', 'volume_get')})
def test_queued_snapshot_actions(self):
images = self.images.list()
def test_snapshot_actions(self):
snapshots = self.snapshots.list()
volumes = self.volumes.list()
@ -152,35 +141,35 @@ class ImagesAndSnapshotsTests(test.TestCase):
api.cinder.volume_snapshot_list(IsA(http.HttpRequest)) \
.AndReturn(volumes)
api.glance.image_list_detailed(IsA(http.HttpRequest),
marker=None).AndReturn([images, False])
api.glance.snapshot_list_detailed(IsA(http.HttpRequest), marker=None) \
api.glance.image_list_detailed(IsA(http.HttpRequest), marker=None) \
.AndReturn([snapshots, False])
self.mox.ReplayAll()
res = self.client.get(INDEX_URL)
self.assertTemplateUsed(res, 'project/images_and_snapshots/index.html')
self.assertIn('snapshots_table', res.context)
snaps = res.context['snapshots_table']
self.assertIn('images_table', res.context)
snaps = res.context['images_table']
self.assertEqual(len(snaps.get_rows()), 3)
row_actions = snaps.get_row_actions(snaps.data[0])
# first instance - status active, owned
self.assertEqual(len(row_actions), 3)
self.assertEqual(len(row_actions), 4)
self.assertEqual(row_actions[0].verbose_name, u"Launch")
self.assertEqual(row_actions[1].verbose_name, u"Edit")
self.assertEqual(row_actions[2].verbose_name, u"Delete Snapshot")
self.assertEqual(row_actions[1].verbose_name, u"Create Volume")
self.assertEqual(row_actions[2].verbose_name, u"Edit")
self.assertEqual(row_actions[3].verbose_name, u"Delete Image")
row_actions = snaps.get_row_actions(snaps.data[1])
# second instance - status active, not owned
self.assertEqual(len(row_actions), 1)
self.assertEqual(len(row_actions), 2)
self.assertEqual(row_actions[0].verbose_name, u"Launch")
self.assertEqual(row_actions[1].verbose_name, u"Create Volume")
row_actions = snaps.get_row_actions(snaps.data[2])
# third instance - status queued, only delete is available
self.assertEqual(len(row_actions), 1)
self.assertEqual(unicode(row_actions[0].verbose_name),
u"Delete Snapshot")
self.assertEqual(str(row_actions[0]), "<DeleteSnapshot: delete>")
u"Delete Image")
self.assertEqual(str(row_actions[0]), "<DeleteImage: delete>")

View File

@ -34,7 +34,6 @@ from horizon import tabs
from openstack_dashboard import api
from openstack_dashboard.api.base import is_service_enabled
from .images.tables import ImagesTable
from .snapshots.tables import SnapshotsTable
from .volume_snapshots.tables import VolumeSnapshotsTable
from .volume_snapshots.tabs import SnapshotDetailTabs
@ -42,7 +41,7 @@ LOG = logging.getLogger(__name__)
class IndexView(tables.MultiTableView):
table_classes = (ImagesTable, SnapshotsTable, VolumeSnapshotsTable)
table_classes = (ImagesTable, VolumeSnapshotsTable)
template_name = 'project/images_and_snapshots/index.html'
def has_more_data(self, table):
@ -57,24 +56,12 @@ class IndexView(tables.MultiTableView):
self._more_images) = api.glance.image_list_detailed(self.request,
marker=marker)
images = [im for im in all_images
if im.container_format not in ['aki', 'ari'] and
im.properties.get("image_type", '') != "snapshot"]
if im.container_format not in ['aki', 'ari']]
except:
images = []
exceptions.handle(self.request, _("Unable to retrieve images."))
return images
def get_snapshots_data(self):
req = self.request
marker = req.GET.get(SnapshotsTable._meta.pagination_param, None)
try:
snaps, self._more_snapshots = api.glance.snapshot_list_detailed(
req, marker=marker)
except:
snaps = []
exceptions.handle(req, _("Unable to retrieve snapshots."))
return snaps
def get_volume_snapshots_data(self):
if is_service_enabled(self.request, 'volume'):
try:

View File

@ -68,8 +68,7 @@ class VolumeSnapshotsViewTests(test.TestCase):
res = self.client.post(url, formData)
self.assertRedirectsNoFollow(res, INDEX_URL)
@test.create_stubs({api.glance: ('image_list_detailed',
'snapshot_list_detailed'),
@test.create_stubs({api.glance: ('image_list_detailed',),
api.cinder: ('volume_snapshot_list',
'volume_snapshot_delete')})
def test_delete_volume_snapshot(self):
@ -78,15 +77,11 @@ class VolumeSnapshotsViewTests(test.TestCase):
api.glance.image_list_detailed(IsA(http.HttpRequest),
marker=None).AndReturn(([], False))
api.glance.snapshot_list_detailed(IsA(http.HttpRequest),
marker=None).AndReturn(([], False))
api.cinder.volume_snapshot_list(IsA(http.HttpRequest)). \
AndReturn(vol_snapshots)
api.cinder.volume_snapshot_delete(IsA(http.HttpRequest), snapshot.id)
api.glance.image_list_detailed(IsA(http.HttpRequest),
marker=None).AndReturn(([], False))
api.glance.snapshot_list_detailed(IsA(http.HttpRequest),
marker=None).AndReturn(([], False))
api.cinder.volume_snapshot_list(IsA(http.HttpRequest)). \
AndReturn([])
self.mox.ReplayAll()

View File

@ -613,8 +613,7 @@ class InstanceTests(test.TestCase):
'flavor_list',
'server_delete'),
cinder: ('volume_snapshot_list',),
api.glance: ('snapshot_list_detailed',
'image_list_detailed')})
api.glance: ('image_list_detailed',)})
def test_create_instance_snapshot(self):
server = self.servers.first()
@ -623,8 +622,6 @@ class InstanceTests(test.TestCase):
server.id,
"snapshot1").AndReturn(self.snapshots.first())
api.glance.snapshot_list_detailed(IsA(http.HttpRequest),
marker=None).AndReturn([[], False])
api.glance.image_list_detailed(IsA(http.HttpRequest),
marker=None).AndReturn([[], False])
cinder.volume_snapshot_list(IsA(http.HttpRequest)).AndReturn([])

View File

@ -44,29 +44,11 @@ class GlanceApiTests(test.APITestCase):
self.assertItemsEqual(images, api_images)
self.assertFalse(has_more)
def test_snapshot_list_detailed(self):
# The total image count is under page size, should return all images.
api_images = self.images.list()
filters = {'property-image_type': 'snapshot'}
limit = getattr(settings, 'API_RESULT_LIMIT', 1000)
page_size = getattr(settings, 'API_RESULT_PAGE_SIZE', 20)
glanceclient = self.stub_glanceclient()
glanceclient.images = self.mox.CreateMockAnything()
glanceclient.images.list(page_size=page_size + 1,
limit=limit,
filters=filters,).AndReturn(iter(api_images))
self.mox.ReplayAll()
images, has_more = api.glance.snapshot_list_detailed(self.request)
self.assertItemsEqual(images, api_images)
self.assertFalse(has_more)
@override_settings(API_RESULT_PAGE_SIZE=2)
def test_snapshot_list_detailed_pagination(self):
def test_image_list_detailed_pagination(self):
# The total snapshot count is over page size, should return
# page_size images.
filters = {'property-image_type': 'snapshot'}
filters = {}
page_size = settings.API_RESULT_PAGE_SIZE
limit = getattr(settings, 'API_RESULT_LIMIT', 1000)
@ -78,11 +60,13 @@ class GlanceApiTests(test.APITestCase):
# Pass back all images, ignoring filters
glanceclient.images.list(limit=limit,
page_size=page_size + 1,
filters=filters,) \
.AndReturn(images_iter)
filters=filters,).AndReturn(images_iter)
self.mox.ReplayAll()
images, has_more = api.glance.snapshot_list_detailed(self.request)
images, has_more = api.glance.image_list_detailed(self.request,
marker=None,
filters=filters,
paginate=True)
expected_images = api_images[:page_size]
self.assertItemsEqual(images, expected_images)
self.assertTrue(has_more)
@ -92,9 +76,9 @@ class GlanceApiTests(test.APITestCase):
len(api_images) - len(expected_images) - 1)
@override_settings(API_RESULT_PAGE_SIZE=2)
def test_snapshot_list_detailed_pagination_marker(self):
def test_image_list_detailed_pagination_marker(self):
# Tests getting a second page with a marker.
filters = {'property-image_type': 'snapshot'}
filters = {}
page_size = settings.API_RESULT_PAGE_SIZE
limit = getattr(settings, 'API_RESULT_LIMIT', 1000)
marker = 'nonsense'
@ -108,12 +92,13 @@ class GlanceApiTests(test.APITestCase):
glanceclient.images.list(limit=limit,
page_size=page_size + 1,
filters=filters,
marker=marker) \
.AndReturn(images_iter)
marker=marker).AndReturn(images_iter)
self.mox.ReplayAll()
images, has_more = api.glance.snapshot_list_detailed(self.request,
marker=marker)
images, has_more = api.glance.image_list_detailed(self.request,
marker=marker,
filters=filters,
paginate=True)
expected_images = api_images[:page_size]
self.assertItemsEqual(images, expected_images)
self.assertTrue(has_more)