From fc79467ff6813e0b1fe02d078ac55de79ea526b2 Mon Sep 17 00:00:00 2001 From: Mike Fedosin Date: Thu, 11 Sep 2014 16:58:45 +0400 Subject: [PATCH] Adds the ability to sort images with multiple keys Adds client code to consume API modified in change Ib7a6aeb2df3bc5d23fe8e070290b5bfcab00c0f5 Extends CLI for v2 with multiple sort keys Example: glance --os-image-api-version 2 image-list --sort-key name --sort-key size Implements-blueprint: glance-sorting-enhancements Change-Id: If79779a4c52c8dc5c4f39192d3d247335a76ba24 DocImpact Closes-Bug: 1221274 --- glanceclient/v2/images.py | 15 +++++++++ glanceclient/v2/shell.py | 12 +++++++ tests/v2/test_images.py | 71 ++++++++++++++++++++++++++++++++++++++- tests/v2/test_shell_v2.py | 44 ++++++++++++++++++++++-- 4 files changed, 139 insertions(+), 3 deletions(-) diff --git a/glanceclient/v2/images.py b/glanceclient/v2/images.py index 7f1d8bce..a1b0397a 100644 --- a/glanceclient/v2/images.py +++ b/glanceclient/v2/images.py @@ -26,6 +26,10 @@ from glanceclient.v2 import schemas DEFAULT_PAGE_SIZE = 20 +SORT_DIR_VALUES = ('asc', 'desc') +SORT_KEY_VALUES = ('name', 'status', 'container_format', 'disk_format', + 'size', 'id', 'created_at', 'updated_at') + class Controller(object): def __init__(self, http_client, schema_client): @@ -94,6 +98,10 @@ class Controller(object): # the page_size as Glance's limit. filters['limit'] = page_size + sort_dir = kwargs.get('sort_dir') + if sort_dir is not None: + filters['sort_dir'] = sort_dir + tags = filters.pop('tag', []) tags_url_params = [] @@ -110,6 +118,13 @@ class Controller(object): for param in tags_url_params: url = '%s&%s' % (url, parse.urlencode(param)) + sort_key = kwargs.get('sort_key') + if sort_key is not None: + if isinstance(sort_key, six.string_types): + sort_key = [sort_key] + for key in sort_key: + url = '%s&sort_key=%s' % (url, key) + for image in paginate(url, page_size, limit): yield image diff --git a/glanceclient/v2/shell.py b/glanceclient/v2/shell.py index b54be7ef..b598539b 100644 --- a/glanceclient/v2/shell.py +++ b/glanceclient/v2/shell.py @@ -16,6 +16,7 @@ from glanceclient.common import progressbar from glanceclient.common import utils from glanceclient import exc +from glanceclient.v2 import images from glanceclient.v2 import tasks import json import os @@ -124,6 +125,12 @@ def do_image_update(gc, args): help='Displays images that match the checksum.') @utils.arg('--tag', metavar='', action='append', help="Filter images by a user-defined tag.") +@utils.arg('--sort-key', default=[], action='append', + choices=images.SORT_KEY_VALUES, + help='Sort image list by specified fields.') +@utils.arg('--sort-dir', default='asc', + choices=images.SORT_DIR_VALUES, + help='Sort image list in specified direction.') def do_image_list(gc, args): """List images you can access.""" filter_keys = ['visibility', 'member_status', 'owner', 'checksum', 'tag'] @@ -141,6 +148,11 @@ def do_image_list(gc, args): kwargs['limit'] = args.limit if args.page_size is not None: kwargs['page_size'] = args.page_size + if args.sort_key: + kwargs['sort_key'] = args.sort_key + else: + kwargs['sort_key'] = ['name'] + kwargs['sort_dir'] = args.sort_dir images = gc.images.list(**kwargs) columns = ['ID', 'Name'] diff --git a/tests/v2/test_images.py b/tests/v2/test_images.py index da3c0b18..d49ff7d7 100644 --- a/tests/v2/test_images.py +++ b/tests/v2/test_images.py @@ -394,9 +394,55 @@ data_fixtures = { {'images': []}, ), }, + '/v2/images?limit=%d&sort_key=name' % images.DEFAULT_PAGE_SIZE: { + 'GET': ( + {}, + {'images': [ + { + 'id': '2a4560b2-e585-443e-9b39-553b46ec92d1', + 'name': 'image-1', + }, + { + 'id': '6f99bf80-2ee6-47cf-acfe-1f1fabb7e810', + 'name': 'image-2', + }, + ]}, + ), + }, + '/v2/images?limit=%d&sort_key=name&sort_key=id' + % images.DEFAULT_PAGE_SIZE: { + 'GET': ( + {}, + {'images': [ + { + 'id': '2a4560b2-e585-443e-9b39-553b46ec92d1', + 'name': 'image', + }, + { + 'id': '6f99bf80-2ee6-47cf-acfe-1f1fabb7e810', + 'name': 'image', + }, + ]}, + ), + }, + '/v2/images?limit=%d&sort_dir=desc&sort_key=id' + % images.DEFAULT_PAGE_SIZE: { + 'GET': ( + {}, + {'images': [ + { + 'id': '6f99bf80-2ee6-47cf-acfe-1f1fabb7e810', + 'name': 'image-2', + }, + { + 'id': '2a4560b2-e585-443e-9b39-553b46ec92d1', + 'name': 'image-1', + }, + ]}, + ), + } } - schema_fixtures = { 'image': { 'GET': ( @@ -559,6 +605,29 @@ class TestController(testtools.TestCase): images = list(self.controller.list(**filters)) self.assertEqual(0, len(images)) + def test_list_images_with_single_sort_key(self): + img_id1 = '2a4560b2-e585-443e-9b39-553b46ec92d1' + sort_key = 'name' + images = list(self.controller.list(sort_key=sort_key)) + self.assertEqual(2, len(images)) + self.assertEqual('%s' % img_id1, images[0].id) + + def test_list_with_multiple_sort_keys(self): + img_id1 = '2a4560b2-e585-443e-9b39-553b46ec92d1' + sort_key = ['name', 'id'] + images = list(self.controller.list(sort_key=sort_key)) + self.assertEqual(2, len(images)) + self.assertEqual('%s' % img_id1, images[0].id) + + def test_list_images_with_desc_sort_dir(self): + img_id1 = '2a4560b2-e585-443e-9b39-553b46ec92d1' + sort_key = 'id' + sort_dir = 'desc' + images = list(self.controller.list(sort_key=sort_key, + sort_dir=sort_dir)) + self.assertEqual(2, len(images)) + self.assertEqual('%s' % img_id1, images[1].id) + def test_list_images_for_property(self): filters = {'filters': dict([('os_distro', 'NixOS')])} images = list(self.controller.list(**filters)) diff --git a/tests/v2/test_shell_v2.py b/tests/v2/test_shell_v2.py index 25d7d99e..84137e9e 100644 --- a/tests/v2/test_shell_v2.py +++ b/tests/v2/test_shell_v2.py @@ -68,7 +68,9 @@ class ShellV2Test(testtools.TestCase): 'owner': 'test', 'checksum': 'fake_checksum', 'tag': 'fake tag', - 'properties': [] + 'properties': [], + 'sort_key': ['name', 'id'], + 'sort_dir': 'desc' } args = self._make_args(input) with mock.patch.object(self.gc.images, 'list') as mocked_list: @@ -84,6 +86,40 @@ class ShellV2Test(testtools.TestCase): 'tag': 'fake tag' } mocked_list.assert_called_once_with(page_size=18, + sort_key=['name', 'id'], + sort_dir='desc', + filters=exp_img_filters) + utils.print_list.assert_called_once_with({}, ['ID', 'Name']) + + def test_do_image_list_with_single_sort_key(self): + input = { + 'limit': None, + 'page_size': 18, + 'visibility': True, + 'member_status': 'Fake', + 'owner': 'test', + 'checksum': 'fake_checksum', + 'tag': 'fake tag', + 'properties': [], + 'sort_key': ['name'], + 'sort_dir': 'desc' + } + args = self._make_args(input) + with mock.patch.object(self.gc.images, 'list') as mocked_list: + mocked_list.return_value = {} + + test_shell.do_image_list(self.gc, args) + + exp_img_filters = { + 'owner': 'test', + 'member_status': 'Fake', + 'visibility': True, + 'checksum': 'fake_checksum', + 'tag': 'fake tag' + } + mocked_list.assert_called_once_with(page_size=18, + sort_key=['name'], + sort_dir='desc', filters=exp_img_filters) utils.print_list.assert_called_once_with({}, ['ID', 'Name']) @@ -96,7 +132,9 @@ class ShellV2Test(testtools.TestCase): 'owner': 'test', 'checksum': 'fake_checksum', 'tag': 'fake tag', - 'properties': ['os_distro=NixOS', 'architecture=x86_64'] + 'properties': ['os_distro=NixOS', 'architecture=x86_64'], + 'sort_key': ['name'], + 'sort_dir': 'desc' } args = self._make_args(input) with mock.patch.object(self.gc.images, 'list') as mocked_list: @@ -115,6 +153,8 @@ class ShellV2Test(testtools.TestCase): } mocked_list.assert_called_once_with(page_size=1, + sort_key=['name'], + sort_dir='desc', filters=exp_img_filters) utils.print_list.assert_called_once_with({}, ['ID', 'Name'])