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:
parent
f5bf488c06
commit
8a0b4c7e47
@ -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)
|
||||
|
@ -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,)
|
||||
|
@ -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"]
|
@ -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>
|
||||
|
@ -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>")
|
||||
|
@ -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:
|
||||
|
@ -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()
|
||||
|
@ -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([])
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user