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
This commit is contained in:
parent
cc4c402f09
commit
fc79467ff6
|
@ -26,6 +26,10 @@ from glanceclient.v2 import schemas
|
||||||
|
|
||||||
DEFAULT_PAGE_SIZE = 20
|
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):
|
class Controller(object):
|
||||||
def __init__(self, http_client, schema_client):
|
def __init__(self, http_client, schema_client):
|
||||||
|
@ -94,6 +98,10 @@ class Controller(object):
|
||||||
# the page_size as Glance's limit.
|
# the page_size as Glance's limit.
|
||||||
filters['limit'] = page_size
|
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 = filters.pop('tag', [])
|
||||||
tags_url_params = []
|
tags_url_params = []
|
||||||
|
|
||||||
|
@ -110,6 +118,13 @@ 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))
|
||||||
|
|
||||||
|
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):
|
for image in paginate(url, page_size, limit):
|
||||||
yield image
|
yield image
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
from glanceclient.common import progressbar
|
from glanceclient.common import progressbar
|
||||||
from glanceclient.common import utils
|
from glanceclient.common import utils
|
||||||
from glanceclient import exc
|
from glanceclient import exc
|
||||||
|
from glanceclient.v2 import images
|
||||||
from glanceclient.v2 import tasks
|
from glanceclient.v2 import tasks
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
@ -124,6 +125,12 @@ def do_image_update(gc, args):
|
||||||
help='Displays images that match the checksum.')
|
help='Displays images that match the checksum.')
|
||||||
@utils.arg('--tag', metavar='<TAG>', action='append',
|
@utils.arg('--tag', metavar='<TAG>', action='append',
|
||||||
help="Filter images by a user-defined tag.")
|
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):
|
def do_image_list(gc, args):
|
||||||
"""List images you can access."""
|
"""List images you can access."""
|
||||||
filter_keys = ['visibility', 'member_status', 'owner', 'checksum', 'tag']
|
filter_keys = ['visibility', 'member_status', 'owner', 'checksum', 'tag']
|
||||||
|
@ -141,6 +148,11 @@ def do_image_list(gc, args):
|
||||||
kwargs['limit'] = args.limit
|
kwargs['limit'] = args.limit
|
||||||
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
|
||||||
|
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)
|
images = gc.images.list(**kwargs)
|
||||||
columns = ['ID', 'Name']
|
columns = ['ID', 'Name']
|
||||||
|
|
|
@ -394,9 +394,55 @@ data_fixtures = {
|
||||||
{'images': []},
|
{'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 = {
|
schema_fixtures = {
|
||||||
'image': {
|
'image': {
|
||||||
'GET': (
|
'GET': (
|
||||||
|
@ -559,6 +605,29 @@ class TestController(testtools.TestCase):
|
||||||
images = list(self.controller.list(**filters))
|
images = list(self.controller.list(**filters))
|
||||||
self.assertEqual(0, len(images))
|
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):
|
def test_list_images_for_property(self):
|
||||||
filters = {'filters': dict([('os_distro', 'NixOS')])}
|
filters = {'filters': dict([('os_distro', 'NixOS')])}
|
||||||
images = list(self.controller.list(**filters))
|
images = list(self.controller.list(**filters))
|
||||||
|
|
|
@ -68,7 +68,9 @@ class ShellV2Test(testtools.TestCase):
|
||||||
'owner': 'test',
|
'owner': 'test',
|
||||||
'checksum': 'fake_checksum',
|
'checksum': 'fake_checksum',
|
||||||
'tag': 'fake tag',
|
'tag': 'fake tag',
|
||||||
'properties': []
|
'properties': [],
|
||||||
|
'sort_key': ['name', 'id'],
|
||||||
|
'sort_dir': 'desc'
|
||||||
}
|
}
|
||||||
args = self._make_args(input)
|
args = self._make_args(input)
|
||||||
with mock.patch.object(self.gc.images, 'list') as mocked_list:
|
with mock.patch.object(self.gc.images, 'list') as mocked_list:
|
||||||
|
@ -84,6 +86,40 @@ class ShellV2Test(testtools.TestCase):
|
||||||
'tag': 'fake tag'
|
'tag': 'fake tag'
|
||||||
}
|
}
|
||||||
mocked_list.assert_called_once_with(page_size=18,
|
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)
|
filters=exp_img_filters)
|
||||||
utils.print_list.assert_called_once_with({}, ['ID', 'Name'])
|
utils.print_list.assert_called_once_with({}, ['ID', 'Name'])
|
||||||
|
|
||||||
|
@ -96,7 +132,9 @@ class ShellV2Test(testtools.TestCase):
|
||||||
'owner': 'test',
|
'owner': 'test',
|
||||||
'checksum': 'fake_checksum',
|
'checksum': 'fake_checksum',
|
||||||
'tag': 'fake tag',
|
'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)
|
args = self._make_args(input)
|
||||||
with mock.patch.object(self.gc.images, 'list') as mocked_list:
|
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,
|
mocked_list.assert_called_once_with(page_size=1,
|
||||||
|
sort_key=['name'],
|
||||||
|
sort_dir='desc',
|
||||||
filters=exp_img_filters)
|
filters=exp_img_filters)
|
||||||
utils.print_list.assert_called_once_with({}, ['ID', 'Name'])
|
utils.print_list.assert_called_once_with({}, ['ID', 'Name'])
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue