Add a --limit parameter to list operations

In v2, we use the link header during paginations to know when there are
more images available in the server that could be returned in the same
request. This way, it's possible to iterate over the generator returned
by list and consume the images in the server.

However, it's currently not possible to tell glanceclient the exact
number of images we want, which basically means that it'll *always* go
through the whole list of images in the server unless the limit is
implemented by the consumer.

DocImpact
Change-Id: I9f65a40d4eafda6320e5c7d94d03fcd1bbc93e33
Closes-bug: #1415035
This commit is contained in:
Flavio Percoco
2015-01-27 14:20:27 +01:00
committed by Flavio Percoco
parent 363b170851
commit 93c9bc1fe0
4 changed files with 70 additions and 8 deletions

View File

@@ -46,7 +46,19 @@ class Controller(object):
ori_validate_fun = self.model.validate ori_validate_fun = self.model.validate
empty_fun = lambda *args, **kwargs: None empty_fun = lambda *args, **kwargs: None
def paginate(url): limit = kwargs.get('limit')
# NOTE(flaper87): Don't use `get('page_size', DEFAULT_SIZE)` otherwise,
# it could be possible to send invalid data to the server by passing
# page_size=None.
page_size = kwargs.get('page_size') or DEFAULT_PAGE_SIZE
def paginate(url, page_size, limit=None):
if limit and page_size > limit:
# NOTE(flaper87): Avoid requesting 2000 images when limit is 1
url = url.replace("limit=%s" % page_size,
"limit=%s" % limit)
resp, body = self.http_client.get(url) resp, body = self.http_client.get(url)
for image in body['images']: for image in body['images']:
# NOTE(bcwaldon): remove 'self' for now until we have # NOTE(bcwaldon): remove 'self' for now until we have
@@ -60,6 +72,11 @@ class Controller(object):
# image entry for each page. # image entry for each page.
self.model.validate = empty_fun self.model.validate = empty_fun
if limit:
limit -= 1
if limit <= 0:
raise StopIteration
# NOTE(zhiyan); Reset validation function. # NOTE(zhiyan); Reset validation function.
self.model.validate = ori_validate_fun self.model.validate = ori_validate_fun
@@ -68,15 +85,13 @@ class Controller(object):
except KeyError: except KeyError:
return return
else: else:
for image in paginate(next_url): for image in paginate(next_url, page_size, limit):
yield image yield image
filters = kwargs.get('filters', {}) filters = kwargs.get('filters', {})
# NOTE(flaper87): We paginate in the client, hence we use
if not kwargs.get('page_size'): # the page_size as Glance's limit.
filters['limit'] = DEFAULT_PAGE_SIZE filters['limit'] = page_size
else:
filters['limit'] = kwargs['page_size']
tags = filters.pop('tag', []) tags = filters.pop('tag', [])
tags_url_params = [] tags_url_params = []
@@ -94,7 +109,7 @@ class Controller(object):
for param in tags_url_params: for param in tags_url_params:
url = '%s&%s' % (url, parse.urlencode(param)) url = '%s&%s' % (url, parse.urlencode(param))
for image in paginate(url): for image in paginate(url, page_size, limit):
yield image yield image
def get(self, image_id): def get(self, image_id):

View File

@@ -107,6 +107,8 @@ def do_image_update(gc, args):
utils.print_image(image) utils.print_image(image)
@utils.arg('--limit', metavar='<LIMIT>', default=None, type=int,
help='Maximum number of images to get.')
@utils.arg('--page-size', metavar='<SIZE>', default=None, type=int, @utils.arg('--page-size', metavar='<SIZE>', default=None, type=int,
help='Number of images to request in each paginated request.') help='Number of images to request in each paginated request.')
@utils.arg('--visibility', metavar='<VISIBILITY>', @utils.arg('--visibility', metavar='<VISIBILITY>',
@@ -135,6 +137,8 @@ def do_image_list(gc, args):
filters = dict([item for item in filter_items if item[1] is not None]) filters = dict([item for item in filter_items if item[1] is not None])
kwargs = {'filters': filters} kwargs = {'filters': filters}
if args.limit is not None:
kwargs['limit'] = args.page_size
if args.page_size is not None: if args.page_size is not None:
kwargs['page_size'] = args.page_size kwargs['page_size'] = args.page_size

View File

@@ -78,6 +78,25 @@ data_fixtures = {
]}, ]},
), ),
}, },
'/v2/images?limit=2': {
'GET': (
{},
{
'images': [
{
'id': '3a4560a1-e585-443e-9b39-553b46ec92d1',
'name': 'image-1',
},
{
'id': '6f99bf80-2ee6-47cf-acfe-1f1fabb7e810',
'name': 'image-2',
},
],
'next': ('/v2/images?limit=2&'
'marker=6f99bf80-2ee6-47cf-acfe-1f1fabb7e810'),
},
),
},
'/v2/images?limit=1': { '/v2/images?limit=1': {
'GET': ( 'GET': (
{}, {},
@@ -104,6 +123,17 @@ data_fixtures = {
]}, ]},
), ),
}, },
('/v2/images?limit=1&marker=6f99bf80-2ee6-47cf-acfe-1f1fabb7e810'): {
'GET': (
{},
{'images': [
{
'id': '3f99bf80-2ee6-47cf-acfe-1f1fabb7e811',
'name': 'image-3',
},
]},
),
},
'/v2/images/3a4560a1-e585-443e-9b39-553b46ec92d1': { '/v2/images/3a4560a1-e585-443e-9b39-553b46ec92d1': {
'GET': ( 'GET': (
{}, {},
@@ -420,6 +450,17 @@ class TestController(testtools.TestCase):
self.assertEqual('6f99bf80-2ee6-47cf-acfe-1f1fabb7e810', images[1].id) self.assertEqual('6f99bf80-2ee6-47cf-acfe-1f1fabb7e810', images[1].id)
self.assertEqual('image-2', images[1].name) self.assertEqual('image-2', images[1].name)
def test_list_images_paginated_with_limit(self):
# NOTE(bcwaldon):cast to list since the controller returns a generator
images = list(self.controller.list(limit=3, page_size=2))
self.assertEqual('3a4560a1-e585-443e-9b39-553b46ec92d1', images[0].id)
self.assertEqual('image-1', images[0].name)
self.assertEqual('6f99bf80-2ee6-47cf-acfe-1f1fabb7e810', images[1].id)
self.assertEqual('image-2', images[1].name)
self.assertEqual('3f99bf80-2ee6-47cf-acfe-1f1fabb7e811', images[2].id)
self.assertEqual('image-3', images[2].name)
self.assertEqual(3, len(images))
def test_list_images_visibility_public(self): def test_list_images_visibility_public(self):
filters = {'filters': {'visibility': 'public'}} filters = {'filters': {'visibility': 'public'}}
images = list(self.controller.list(**filters)) images = list(self.controller.list(**filters))

View File

@@ -61,6 +61,7 @@ class ShellV2Test(testtools.TestCase):
def test_do_image_list(self): def test_do_image_list(self):
input = { input = {
'limit': None,
'page_size': 18, 'page_size': 18,
'visibility': True, 'visibility': True,
'member_status': 'Fake', 'member_status': 'Fake',
@@ -88,6 +89,7 @@ class ShellV2Test(testtools.TestCase):
def test_do_image_list_with_property_filter(self): def test_do_image_list_with_property_filter(self):
input = { input = {
'limit': None,
'page_size': 1, 'page_size': 1,
'visibility': True, 'visibility': True,
'member_status': 'Fake', 'member_status': 'Fake',