Skip to content

Commit 6db625f

Browse files
Jenkinsopenstack-gerrit
authored andcommitted
Merge "Extend images CLI v2 with new sorting syntax"
2 parents 82143e2 + 3f3066b commit 6db625f

File tree

4 files changed

+140
-22
lines changed

4 files changed

+140
-22
lines changed

glanceclient/v2/images.py

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,29 @@ def _wrap(value):
4747
return [value]
4848
return value
4949

50+
@staticmethod
51+
def _validate_sort_param(sort):
52+
"""Validates sorting argument for invalid keys and directions values.
53+
54+
:param sort: comma-separated list of sort keys with optional <:dir>
55+
after each key
56+
"""
57+
for sort_param in sort.strip().split(','):
58+
key, _sep, dir = sort_param.partition(':')
59+
if dir and dir not in SORT_DIR_VALUES:
60+
msg = ('Invalid sort direction: %(sort_dir)s.'
61+
' It must be one of the following: %(available)s.'
62+
) % {'sort_dir': dir,
63+
'available': ', '.join(SORT_DIR_VALUES)}
64+
raise exc.HTTPBadRequest(msg)
65+
if key not in SORT_KEY_VALUES:
66+
msg = ('Invalid sort key: %(sort_key)s.'
67+
' It must be one of the following: %(available)s.'
68+
) % {'sort_key': key,
69+
'available': ', '.join(SORT_KEY_VALUES)}
70+
raise exc.HTTPBadRequest(msg)
71+
return sort
72+
5073
def list(self, **kwargs):
5174
"""Retrieve a listing of Image objects
5275
@@ -104,16 +127,6 @@ def paginate(url, page_size, limit=None):
104127
# the page_size as Glance's limit.
105128
filters['limit'] = page_size
106129

107-
sort_dir = self._wrap(kwargs.get('sort_dir', []))
108-
sort_key = self._wrap(kwargs.get('sort_key', []))
109-
110-
if len(sort_key) != len(sort_dir) and len(sort_dir) > 1:
111-
raise exc.HTTPBadRequest("Unexpected number of sort directions: "
112-
"provide only one default sorting "
113-
"direction for each key or make sure "
114-
"that sorting keys number matches with a "
115-
"number of sorting directions.")
116-
117130
tags = filters.pop('tag', [])
118131
tags_url_params = []
119132

@@ -130,11 +143,27 @@ def paginate(url, page_size, limit=None):
130143
for param in tags_url_params:
131144
url = '%s&%s' % (url, parse.urlencode(param))
132145

133-
for key in sort_key:
134-
url = '%s&sort_key=%s' % (url, key)
135-
136-
for dir in sort_dir:
137-
url = '%s&sort_dir=%s' % (url, dir)
146+
if 'sort' in kwargs:
147+
if 'sort_key' in kwargs or 'sort_dir' in kwargs:
148+
raise exc.HTTPBadRequest("The 'sort' argument is not supported"
149+
" with 'sort_key' or 'sort_dir'.")
150+
url = '%s&sort=%s' % (url,
151+
self._validate_sort_param(
152+
kwargs['sort']))
153+
else:
154+
sort_dir = self._wrap(kwargs.get('sort_dir', []))
155+
sort_key = self._wrap(kwargs.get('sort_key', []))
156+
157+
if len(sort_key) != len(sort_dir) and len(sort_dir) > 1:
158+
raise exc.HTTPBadRequest(
159+
"Unexpected number of sort directions: "
160+
"either provide a single sort direction or an equal "
161+
"number of sort keys and sort directions.")
162+
for key in sort_key:
163+
url = '%s&sort_key=%s' % (url, key)
164+
165+
for dir in sort_dir:
166+
url = '%s&sort_dir=%s' % (url, dir)
138167

139168
for image in paginate(url, page_size, limit):
140169
yield image

glanceclient/v2/shell.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,10 @@ def do_image_update(gc, args):
131131
@utils.arg('--sort-dir', default=[], action='append',
132132
choices=images.SORT_DIR_VALUES,
133133
help='Sort image list in specified directions.')
134+
@utils.arg('--sort', metavar='<key>[:<direction>]', default=None,
135+
help=(("Comma-separated list of sort keys and directions in the "
136+
"form of <key>[:<asc|desc>]. Valid keys: %s. OPTIONAL: "
137+
"Default='name:asc'.") % ', '.join(images.SORT_KEY_VALUES)))
134138
def do_image_list(gc, args):
135139
"""List images you can access."""
136140
filter_keys = ['visibility', 'member_status', 'owner', 'checksum', 'tag']
@@ -148,14 +152,15 @@ def do_image_list(gc, args):
148152
kwargs['limit'] = args.limit
149153
if args.page_size is not None:
150154
kwargs['page_size'] = args.page_size
155+
151156
if args.sort_key:
152157
kwargs['sort_key'] = args.sort_key
153-
else:
154-
kwargs['sort_key'] = ['name']
155158
if args.sort_dir:
156159
kwargs['sort_dir'] = args.sort_dir
157-
else:
158-
kwargs['sort_dir'] = ['asc']
160+
if args.sort is not None:
161+
kwargs['sort'] = args.sort
162+
elif not args.sort_dir and not args.sort_key:
163+
kwargs['sort'] = 'name:asc'
159164

160165
images = gc.images.list(**kwargs)
161166
columns = ['ID', 'Name']

tests/v2/test_images.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,22 @@
473473
]},
474474
),
475475
},
476+
'/v2/images?limit=%d&sort=name%%3Adesc%%2Csize%%3Aasc'
477+
% images.DEFAULT_PAGE_SIZE: {
478+
'GET': (
479+
{},
480+
{'images': [
481+
{
482+
'id': '6f99bf80-2ee6-47cf-acfe-1f1fabb7e810',
483+
'name': 'image-2',
484+
},
485+
{
486+
'id': '2a4560b2-e585-443e-9b39-553b46ec92d1',
487+
'name': 'image-1',
488+
},
489+
]},
490+
),
491+
},
476492
}
477493

478494
schema_fixtures = {
@@ -679,6 +695,13 @@ def test_list_images_with_multiple_sort_dirs(self):
679695
self.assertEqual(2, len(images))
680696
self.assertEqual('%s' % img_id1, images[1].id)
681697

698+
def test_list_images_with_new_sorting_syntax(self):
699+
img_id1 = '2a4560b2-e585-443e-9b39-553b46ec92d1'
700+
sort = 'name:desc,size:asc'
701+
images = list(self.controller.list(sort=sort))
702+
self.assertEqual(2, len(images))
703+
self.assertEqual('%s' % img_id1, images[1].id)
704+
682705
def test_list_images_sort_dirs_fewer_than_keys(self):
683706
sort_key = ['name', 'id', 'created_at']
684707
sort_dir = ['desc', 'asc']
@@ -688,6 +711,31 @@ def test_list_images_sort_dirs_fewer_than_keys(self):
688711
sort_key=sort_key,
689712
sort_dir=sort_dir))
690713

714+
def test_list_images_combined_syntax(self):
715+
sort_key = ['name', 'id']
716+
sort_dir = ['desc', 'asc']
717+
sort = 'name:asc'
718+
self.assertRaises(exc.HTTPBadRequest,
719+
list,
720+
self.controller.list(
721+
sort=sort,
722+
sort_key=sort_key,
723+
sort_dir=sort_dir))
724+
725+
def test_list_images_new_sorting_syntax_invalid_key(self):
726+
sort = 'INVALID:asc'
727+
self.assertRaises(exc.HTTPBadRequest,
728+
list,
729+
self.controller.list(
730+
sort=sort))
731+
732+
def test_list_images_new_sorting_syntax_invalid_direction(self):
733+
sort = 'name:INVALID'
734+
self.assertRaises(exc.HTTPBadRequest,
735+
list,
736+
self.controller.list(
737+
sort=sort))
738+
691739
def test_list_images_for_property(self):
692740
filters = {'filters': dict([('os_distro', 'NixOS')])}
693741
images = list(self.controller.list(**filters))

tests/v2/test_shell_v2.py

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@ def test_do_image_list(self):
7070
'tag': 'fake tag',
7171
'properties': [],
7272
'sort_key': ['name', 'id'],
73-
'sort_dir': ['desc', 'asc']
73+
'sort_dir': ['desc', 'asc'],
74+
'sort': None
7475
}
7576
args = self._make_args(input)
7677
with mock.patch.object(self.gc.images, 'list') as mocked_list:
@@ -102,7 +103,8 @@ def test_do_image_list_with_single_sort_key(self):
102103
'tag': 'fake tag',
103104
'properties': [],
104105
'sort_key': ['name'],
105-
'sort_dir': ['desc']
106+
'sort_dir': ['desc'],
107+
'sort': None
106108
}
107109
args = self._make_args(input)
108110
with mock.patch.object(self.gc.images, 'list') as mocked_list:
@@ -123,6 +125,39 @@ def test_do_image_list_with_single_sort_key(self):
123125
filters=exp_img_filters)
124126
utils.print_list.assert_called_once_with({}, ['ID', 'Name'])
125127

128+
def test_do_image_list_new_sorting_syntax(self):
129+
input = {
130+
'limit': None,
131+
'page_size': 18,
132+
'visibility': True,
133+
'member_status': 'Fake',
134+
'owner': 'test',
135+
'checksum': 'fake_checksum',
136+
'tag': 'fake tag',
137+
'properties': [],
138+
'sort': 'name:desc,size:asc',
139+
'sort_key': [],
140+
'sort_dir': []
141+
}
142+
args = self._make_args(input)
143+
with mock.patch.object(self.gc.images, 'list') as mocked_list:
144+
mocked_list.return_value = {}
145+
146+
test_shell.do_image_list(self.gc, args)
147+
148+
exp_img_filters = {
149+
'owner': 'test',
150+
'member_status': 'Fake',
151+
'visibility': True,
152+
'checksum': 'fake_checksum',
153+
'tag': 'fake tag'
154+
}
155+
mocked_list.assert_called_once_with(
156+
page_size=18,
157+
sort='name:desc,size:asc',
158+
filters=exp_img_filters)
159+
utils.print_list.assert_called_once_with({}, ['ID', 'Name'])
160+
126161
def test_do_image_list_with_property_filter(self):
127162
input = {
128163
'limit': None,
@@ -134,7 +169,8 @@ def test_do_image_list_with_property_filter(self):
134169
'tag': 'fake tag',
135170
'properties': ['os_distro=NixOS', 'architecture=x86_64'],
136171
'sort_key': ['name'],
137-
'sort_dir': ['desc']
172+
'sort_dir': ['desc'],
173+
'sort': None
138174
}
139175
args = self._make_args(input)
140176
with mock.patch.object(self.gc.images, 'list') as mocked_list:

0 commit comments

Comments
 (0)