From dec4876e2414dc48052ad95939081cc183bfef54 Mon Sep 17 00:00:00 2001 From: Tihomir Trifonov Date: Mon, 23 Jul 2012 13:24:55 +0300 Subject: [PATCH] Fixed the bug with images returned as generator After a fix in Glance to support pagination, the list of images is returned as generator, while api.glance expects a list. PATCHSET 2: removed optional kwargs if None PATCHSET 3: fixed snapshot_list_detailed. Fixed bug 1027210 Change-Id: I1c68e3625c22e591776e83c742e7b94dc893f196 --- horizon/api/glance.py | 23 ++--- horizon/dashboards/syspanel/images/tests.py | 97 +++++++++++++++++++ horizon/tests/api_tests/glance_tests.py | 32 +++++- horizon/tests/test_data/glance_data.py | 29 +++++- .../local/local_settings.py.example | 6 +- 5 files changed, 166 insertions(+), 21 deletions(-) diff --git a/horizon/api/glance.py b/horizon/api/glance.py index dbf2a8e5d..60fe2ebbc 100644 --- a/horizon/api/glance.py +++ b/horizon/api/glance.py @@ -55,12 +55,16 @@ def image_get(request, image_id): def image_list_detailed(request, marker=None, filters=None): - filters = filters or {} limit = getattr(settings, 'API_RESULT_LIMIT', 1000) - images = glanceclient(request).images.list(limit=limit + 1, - marker=marker, - filters=filters) - if(len(images) > limit): + page_size = getattr(settings, 'API_RESULT_PAGE_SIZE', 20) + kwargs = {'filters': filters or {}} + if marker: + kwargs['marker'] = marker + images = list(glanceclient(request).images.list(page_size=page_size, + limit=limit, + **kwargs)) + # Glance returns (page_size + 1) items if more items are available + if(len(images) > page_size): return (images[0:-1], True) else: return (images, False) @@ -89,11 +93,4 @@ def image_create(request, **kwargs): def snapshot_list_detailed(request, marker=None, extra_filters=None): filters = {'property-image_type': 'snapshot'} filters.update(extra_filters or {}) - limit = getattr(settings, 'API_RESULT_LIMIT', 1000) - images = glanceclient(request).images.list(limit=limit + 1, - marker=marker, - filters=filters) - if len(images) > limit: - return (images[0:-1], True) - else: - return (images, False) + return image_list_detailed(request, marker, filters) diff --git a/horizon/dashboards/syspanel/images/tests.py b/horizon/dashboards/syspanel/images/tests.py index e69de29bb..22aaad370 100644 --- a/horizon/dashboards/syspanel/images/tests.py +++ b/horizon/dashboards/syspanel/images/tests.py @@ -0,0 +1,97 @@ +# 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. + +from django import http +from django.conf import settings +from django.core.urlresolvers import reverse +from mox import IsA + +from horizon import api +from horizon import test +from .tables import AdminImagesTable + + +class ImagesViewTest(test.BaseAdminViewTests): + @test.create_stubs({api: ('image_list_detailed',)}) + def test_images_list(self): + api.image_list_detailed(IsA(http.HttpRequest), + marker=None) \ + .AndReturn([self.images.list(), + False]) + self.mox.ReplayAll() + + res = self.client.get( + reverse('horizon:syspanel:images:index')) + self.assertTemplateUsed(res, 'syspanel/images/index.html') + self.assertEqual(len(res.context['images_table'].data), + len(self.images.list())) + + @test.create_stubs({api: ('image_list_detailed',)}) + def test_images_list_get_pagination(self): + api.image_list_detailed(IsA(http.HttpRequest), + marker=None) \ + .AndReturn([self.images.list(), + True]) + api.image_list_detailed(IsA(http.HttpRequest), + marker=None) \ + .AndReturn([self.images.list()[:2], + True]) + api.image_list_detailed(IsA(http.HttpRequest), + marker=self.images.list()[2].id) \ + .AndReturn([self.images.list()[2:4], + True]) + api.image_list_detailed(IsA(http.HttpRequest), + marker=self.images.list()[4].id) \ + .AndReturn([self.images.list()[4:], + True]) + self.mox.ReplayAll() + + url = reverse('horizon:syspanel:images:index') + res = self.client.get(url) + # get all + self.assertEqual(len(res.context['images_table'].data), + len(self.images.list())) + self.assertTemplateUsed(res, 'syspanel/images/index.html') + + page_size = getattr(settings, "API_RESULT_PAGE_SIZE", None) + settings.API_RESULT_PAGE_SIZE = 2 + + res = self.client.get(url) + # get first page with 2 items + self.assertEqual(len(res.context['images_table'].data), + settings.API_RESULT_PAGE_SIZE) + + url = "?".join([reverse('horizon:syspanel:images:index'), + "=".join([AdminImagesTable._meta.pagination_param, + self.images.list()[2].id])]) + res = self.client.get(url) + # get second page (items 2-4) + self.assertEqual(len(res.context['images_table'].data), + settings.API_RESULT_PAGE_SIZE) + + url = "?".join([reverse('horizon:syspanel:images:index'), + "=".join([AdminImagesTable._meta.pagination_param, + self.images.list()[4].id])]) + res = self.client.get(url) + # get third page (item 5) + self.assertEqual(len(res.context['images_table'].data), + 1) + + # restore API_RESULT_PAGE_SIZE + if page_size: + settings.API_RESULT_PAGE_SIZE = page_size + else: + del settings.API_RESULT_PAGE_SIZE diff --git a/horizon/tests/api_tests/glance_tests.py b/horizon/tests/api_tests/glance_tests.py index fb028daf2..4206a25d5 100644 --- a/horizon/tests/api_tests/glance_tests.py +++ b/horizon/tests/api_tests/glance_tests.py @@ -29,13 +29,39 @@ class GlanceApiTests(test.APITestCase): 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(filters=filters, - limit=limit + 1, - marker=None).AndReturn(images) + glanceclient.images.list(page_size=page_size, + limit=limit, + filters=filters,).AndReturn(images) self.mox.ReplayAll() # No assertions are necessary. Verification is handled by mox. api.glance.snapshot_list_detailed(self.request) + + def test_snapshot_list_detailed_pagination(self): + images = self.images.list() + filters = {'property-image_type': 'snapshot'} + page_size = 2 + temp_page_size = getattr(settings, 'API_RESULT_PAGE_SIZE', None) + settings.API_RESULT_PAGE_SIZE = page_size + limit = getattr(settings, 'API_RESULT_LIMIT', 1000) + + glanceclient = self.stub_glanceclient() + glanceclient.images = self.mox.CreateMockAnything() + glanceclient.images.list(limit=limit, + page_size=page_size, + filters=filters,) \ + .AndReturn(images[0:page_size]) + self.mox.ReplayAll() + + # No assertions are necessary. Verification is handled by mox. + api.glance.snapshot_list_detailed(self.request) + + # Restore + if temp_page_size: + settings.API_RESULT_PAGE_SIZE = temp_page_size + else: + del settings.API_RESULT_PAGE_SIZE diff --git a/horizon/tests/test_data/glance_data.py b/horizon/tests/test_data/glance_data.py index d09e2ee89..6fa67b82d 100644 --- a/horizon/tests/test_data/glance_data.py +++ b/horizon/tests/test_data/glance_data.py @@ -48,7 +48,7 @@ def data(TEST): TEST.snapshots.add(snapshot) # Images - image_dict = {'id': '1', + image_dict = {'id': '007e7d55-fe1e-4c5c-bf08-44b4a4964822', 'name': 'public_image', 'status': "active", 'owner': TEST.tenant.id, @@ -56,11 +56,34 @@ def data(TEST): 'properties': {'image_type': u'image'}} public_image = Image(ImageManager(None), image_dict) - image_dict = {'id': '2', + image_dict = {'id': 'a001c047-22f8-47d0-80a1-8ec94a9524fe', 'name': 'private_image', 'status': "active", 'owner': TEST.tenant.id, 'container_format': 'aki'} private_image = Image(ImageManager(None), image_dict) - TEST.images.add(public_image, private_image) + image_dict = {'id': '278905a6-4b52-4d1e-98f9-8c57bb25ba32', + 'name': 'public_image 2', + 'status': "active", + 'owner': TEST.tenant.id, + 'container_format': 'novaImage', + 'properties': {'image_type': u'image'}} + public_image2 = Image(ImageManager(None), image_dict) + + image_dict = {'id': '710a1acf-a3e3-41dd-a32d-5d6b6c86ea10', + 'name': 'private_image 2', + 'status': "active", + 'owner': TEST.tenant.id, + 'container_format': 'aki'} + private_image2 = Image(ImageManager(None), image_dict) + + image_dict = {'id': '7cd892fd-5652-40f3-a450-547615680132', + 'name': 'private_image 3', + 'status': "active", + 'owner': TEST.tenant.id, + 'container_format': 'aki'} + private_image3 = Image(ImageManager(None), image_dict) + + TEST.images.add(public_image, private_image, + public_image2, private_image2, private_image3) diff --git a/openstack_dashboard/local/local_settings.py.example b/openstack_dashboard/local/local_settings.py.example index 9dddfcb93..fa25263c1 100644 --- a/openstack_dashboard/local/local_settings.py.example +++ b/openstack_dashboard/local/local_settings.py.example @@ -76,9 +76,11 @@ OPENSTACK_KEYSTONE_BACKEND = { # external to the OpenStack environment. The default is 'internalURL'. #OPENSTACK_ENDPOINT_TYPE = "publicURL" -# The number of Swift containers and objects to display on a single page before -# providing a paging element (a "more" link) to paginate results. +# The number of objects (Swift containers/objects or images) to display +# on a single page before providing a paging element (a "more" link) +# to paginate results. API_RESULT_LIMIT = 1000 +API_RESULT_PAGE_SIZE = 20 # The timezone of the server. This should correspond with the timezone # of your entire OpenStack installation, and hopefully be in UTC.