Merge lp:~rackspace-titan/glance/glance-cli-filters into lp:~hudson-openstack/glance/trunk

Proposed by Jay Pipes
Status: Merged
Approved by: Jay Pipes
Approved revision: 152
Merged at revision: 177
Proposed branch: lp:~rackspace-titan/glance/glance-cli-filters
Merge into: lp:~hudson-openstack/glance/trunk
Prerequisite: lp:~rackspace-titan/glance/registry-marker-lp819551
Diff against target: 653 lines (+435/-70)
3 files modified
bin/glance (+142/-50)
doc/source/glance.rst (+26/-2)
tests/functional/test_bin_glance.py (+267/-18)
To merge this branch: bzr merge lp:~rackspace-titan/glance/glance-cli-filters
Reviewer Review Type Date Requested Status
Jason Kölker (community) Approve
Jay Pipes (community) Approve
Review via email: mp+70197@code.launchpad.net

This proposal supersedes a proposal from 2011-06-19.

Description of the change

Add filter support to bin/glance index and details calls

To post a comment you must log in.
Revision history for this message
Ed Leafe (ed-leafe) wrote : Posted in a previous version of this proposal

Is there any reason that 'SUPPORTED_FILTERS' (line 16) is all caps? Usually that style is only for constants that are defined in one place and used in another.

Revision history for this message
Brian Waldon (bcwaldon) wrote : Posted in a previous version of this proposal

> Is there any reason that 'SUPPORTED_FILTERS' (line 16) is all caps? Usually
> that style is only for constants that are defined in one place and used in
> another.

As far as I can tell, it isn't modified. It matches capitalization in other sections of the code, as well. Would you prefer I change it?

Revision history for this message
Jay Pipes (jaypipes) wrote : Posted in a previous version of this proposal

On Mon, Jun 20, 2011 at 5:42 PM, Brian Waldon
<email address hidden> wrote:
>> Is there any reason that 'SUPPORTED_FILTERS' (line 16) is all caps? Usually
>> that style is only for constants that are defined in one place and used in
>> another.
>
> As far as I can tell, it isn't modified. It matches capitalization in other sections of the code, as well. Would you prefer I change it?

No, please leave it. It's a constant. :)

-jay

Revision history for this message
Brian Waldon (bcwaldon) wrote : Posted in a previous version of this proposal

Setting to WIP until I get the results ordering stuff in here.

Revision history for this message
Brian Waldon (bcwaldon) wrote : Posted in a previous version of this proposal

I finally took the time to figure out what was up. This branch is ready to go in once lp:~rackspace-titan/glance/registry-marker-lp819551 merges.

Revision history for this message
Jay Pipes (jaypipes) wrote :

I love this. Can't wait to see it go in...

review: Approve
Revision history for this message
Jason Kölker (jason-koelker) wrote :

Is bueno.

I like that it now returns nothing instead of 'No images found', my scripts thank you. One day I'll get around to changing that to a non-zero status code, will make scripting easier.

review: Approve
Revision history for this message
Jay Pipes (jaypipes) wrote :

No images found should still return 0, though. :) It's a perfectly successful result.

Revision history for this message
OpenStack Infra (hudson-openstack) wrote :

Attempt to merge into lp:glance failed due to conflicts:

text conflict in bin/glance

Revision history for this message
Jason Kölker (jason-koelker) wrote :

> No images found should still return 0, though. :) It's a perfectly successful
> result.

Yea that makes sense since its successfully contacting the server and all. [ '' ] will do just fine for scripting ;)

Revision history for this message
Brian Waldon (bcwaldon) wrote :

Resolved merge conflicts. Ready to go again.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bin/glance'
2--- bin/glance 2011-07-30 03:26:00 +0000
3+++ bin/glance 2011-08-02 21:12:37 +0000
4@@ -95,6 +95,25 @@
5 return fields
6
7
8+def get_image_filters_from_args(args):
9+ """Build a dictionary of query filters based on the supplied args."""
10+ try:
11+ fields = get_image_fields_from_args(args)
12+ except RuntimeError, e:
13+ print e
14+ return FAILURE
15+
16+ SUPPORTED_FILTERS = ['name', 'disk_format', 'container_format', 'status',
17+ 'size_min', 'size_max']
18+ filters = {}
19+ for (key, value) in fields.items():
20+ if key not in SUPPORTED_FILTERS:
21+ key = 'property-%s' % (key,)
22+ filters[key] = value
23+
24+ return filters
25+
26+
27 def print_image_formatted(client, image):
28 """
29 Formatted print of image metadata.
30@@ -395,69 +414,132 @@
31 return FAILURE
32
33
34+def _images_index(client, filters, limit, print_header=False, **kwargs):
35+ """Driver function for images_index"""
36+ parameters = {
37+ "filters": filters,
38+ "limit": limit,
39+ }
40+
41+ optional_kwargs = ['marker', 'sort_key', 'sort_dir']
42+ for kwarg in optional_kwargs:
43+ if kwarg in kwargs:
44+ parameters[kwarg] = kwargs[kwarg]
45+
46+ images = client.get_images(**parameters)
47+
48+ if not images:
49+ return SUCCESS
50+
51+ pretty_table = utils.PrettyTable()
52+ pretty_table.add_column(16, label="ID")
53+ pretty_table.add_column(30, label="Name")
54+ pretty_table.add_column(20, label="Disk Format")
55+ pretty_table.add_column(20, label="Container Format")
56+ pretty_table.add_column(14, label="Size", just="r")
57+
58+ if print_header:
59+ print pretty_table.make_header()
60+
61+ for image in images:
62+ print pretty_table.make_row(image['id'],
63+ image['name'],
64+ image['disk_format'],
65+ image['container_format'],
66+ image['size'])
67+
68+ if not options.force and len(images) == limit and \
69+ not user_confirm("Fetch next page?", True):
70+ return SUCCESS
71+
72+ parameters['marker'] = images[-1]['id']
73+ return _images_index(client, **parameters)
74+
75+
76 @catch_error('show index')
77 def images_index(options, args):
78 """
79-%(prog)s index [options]
80+%(prog)s index [options] <field1=value1 field2=value2 ...>
81
82 Returns basic information for all public images
83-a Glance server knows about"""
84+a Glance server knows about. Provided fields are
85+handled as query filters. Supported filters
86+include 'name', 'disk_format', 'container_format',
87+'status', 'size_min', and 'size_max.' Any extra
88+fields are treated as image metadata properties"""
89 client = get_client(options)
90- images = client.get_images()
91- if not images:
92- print "No public images found."
93+ filters = get_image_filters_from_args(args)
94+ limit = options.limit
95+ marker = options.marker
96+ sort_key = options.sort_key
97+ sort_dir = options.sort_dir
98+
99+ return _images_index(client,
100+ filters,
101+ limit,
102+ marker=marker,
103+ sort_key=sort_key,
104+ sort_dir=sort_dir,
105+ print_header=True)
106+
107+
108+def _images_details(client, filters, limit, print_header=False, **kwargs):
109+ """Driver function for images_details"""
110+ parameters = {
111+ "filters": filters,
112+ "limit": limit,
113+ }
114+
115+ optional_kwargs = ['marker', 'sort_key', 'sort_dir']
116+ for kwarg in optional_kwargs:
117+ if kwarg in kwargs:
118+ parameters[kwarg] = kwargs[kwarg]
119+
120+ images = client.get_images_detailed(**parameters)
121+
122+ if len(images) == 0:
123 return SUCCESS
124
125- print "Found %d public images..." % len(images)
126-
127- pretty_table = utils.PrettyTable()
128- pretty_table.add_column(16, label="ID")
129- pretty_table.add_column(30, label="Name")
130- pretty_table.add_column(20, label="Disk Format")
131- pretty_table.add_column(20, label="Container Format")
132- pretty_table.add_column(14, label="Size", just="r")
133-
134- print pretty_table.make_header()
135+ if print_header:
136+ print "=" * 80
137
138 for image in images:
139- print pretty_table.make_row(
140- image['id'],
141- image['name'],
142- image['disk_format'],
143- image['container_format'],
144- image['size'])
145-
146-
147-def images_detailed(options, args):
148+ print_image_formatted(client, image)
149+ print "=" * 80
150+
151+ if not options.force and len(images) == limit and \
152+ not user_confirm("Fetch next page?", True):
153+ return SUCCESS
154+
155+ parameters["marker"] = images[-1]['id']
156+ return _images_details(client, **parameters)
157+
158+
159+@catch_error('show details')
160+def images_details(options, args):
161 """
162 %(prog)s details [options]
163
164 Returns detailed information for all public images
165-a Glance server knows about"""
166- c = get_client(options)
167- try:
168- images = c.get_images_detailed()
169- if len(images) == 0:
170- print "No public images found."
171- return SUCCESS
172-
173- num_images = len(images)
174- print "Found %d public images..." % num_images
175- cur_image = 1
176- for image in images:
177- print "=" * 80
178- print_image_formatted(c, image)
179- if cur_image == num_images:
180- print "=" * 80
181- cur_image += 1
182-
183- return SUCCESS
184- except Exception, e:
185- print "Failed to show details. Got error:"
186- pieces = unicode(e).split('\n')
187- for piece in pieces:
188- print piece
189- return FAILURE
190+a Glance server knows about. Provided fields are
191+handled as query filters. Supported filters
192+include 'name', 'disk_format', 'container_format',
193+'status', 'size_min', and 'size_max.' Any extra
194+fields are treated as image metadata properties"""
195+ client = get_client(options)
196+ filters = get_image_filters_from_args(args)
197+ limit = options.limit
198+ marker = options.marker
199+ sort_key = options.sort_key
200+ sort_dir = options.sort_dir
201+
202+ return _images_details(client,
203+ filters,
204+ limit,
205+ marker=marker,
206+ sort_key=sort_key,
207+ sort_dir=sort_dir,
208+ print_header=True)
209
210
211 def images_clear(options, args):
212@@ -755,6 +837,16 @@
213 type=int, default=9292,
214 help="Port the Glance API host listens on. "
215 "Default: %default")
216+ parser.add_option('--limit', dest="limit", metavar="LIMIT", default=10,
217+ type="int", help="Page size to use while "
218+ "requesting image metadata")
219+ parser.add_option('--marker', dest="marker", metavar="MARKER",
220+ default=None, help="Image index after which to "
221+ "begin pagination")
222+ parser.add_option('--sort_key', dest="sort_key", metavar="KEY",
223+ help="Sort results by this image attribute.")
224+ parser.add_option('--sort_dir', dest="sort_dir", metavar="[desc|asc]",
225+ help="Sort results in this direction.")
226 parser.add_option('-f', '--force', dest="force", metavar="FORCE",
227 default=False, action="store_true",
228 help="Prevent select actions from requesting "
229@@ -814,7 +906,7 @@
230 'update': image_update,
231 'delete': image_delete,
232 'index': images_index,
233- 'details': images_detailed,
234+ 'details': images_details,
235 'show': image_show,
236 'clear': images_clear}
237
238
239=== modified file 'doc/source/glance.rst'
240--- doc/source/glance.rst 2011-04-22 18:54:24 +0000
241+++ doc/source/glance.rst 2011-08-02 21:12:37 +0000
242@@ -96,6 +96,13 @@
243 -H ADDRESS, --host=ADDRESS
244 Address of Glance API host. Default: example.com
245 -p PORT, --port=PORT Port the Glance API host listens on. Default: 9292
246+ --limit=LIMIT Page size to use while requesting image metadata
247+ --marker=MARKER Image index after which to begin pagination
248+ --sort_key=KEY Sort results by this image attribute.
249+ --sort_dir=[desc|asc]
250+ Sort results in this direction.
251+ -f, --force Prevent select actions from requesting user
252+ confirmation
253 --dry-run Don't actually execute the command, just print output
254 showing what WOULD happen.
255
256@@ -301,7 +308,6 @@
257 available in Glance, as shown below::
258
259 $> glance index --host=65.114.169.29
260- Found 4 public images...
261 ID Name Disk Format Container Format Size
262 ---------------- ------------------------------ -------------------- -------------------- --------------
263 1 Ubuntu 10.10 vhd ovf 58520278
264@@ -309,6 +315,25 @@
265 3 Fedora 9 vdi bare 3040
266 4 Vanilla Linux 2.6.22 qcow2 bare 0
267
268+Image metadata such as 'name', 'disk_format', 'container_format' and 'status'
269+may be used to filter the results of an index or details command. These
270+commands also accept 'size_min' and 'size_max' as lower and upper bounds
271+of the image metadata 'size.' Any unrecognized fields are handled as
272+custom image properties.
273+
274+The 'limit' and 'marker' options are used by the index and details commands
275+to control pagination. The 'marker' indicates the last record that was seen
276+by the user. The page of results returned will begin after the provided image
277+ID. The 'limit' param indicates the page size. Each request to the api will be
278+restricted to returning a maximum number of results. Without the 'force'
279+option, the user will be prompted before each page of results is fetched
280+from the API.
281+
282+Results from index and details commands may be ordered using the 'sort_key'
283+and 'sort_dir' options. Any image attribute may be used for 'sort_key',
284+while only 'asc' or 'desc' are allowed for 'sort_dir'.
285+
286+
287 The ``details`` command
288 -----------------------
289
290@@ -316,7 +341,6 @@
291 available in Glance, as shown below::
292
293 $> glance details --host=65.114.169.29
294- Found 4 public images...
295 ================================================================================
296 URI: http://example.com/images/1
297 Id: 1
298
299=== modified file 'tests/functional/test_bin_glance.py'
300--- tests/functional/test_bin_glance.py 2011-06-28 14:37:31 +0000
301+++ tests/functional/test_bin_glance.py 2011-08-02 21:12:37 +0000
302@@ -52,7 +52,7 @@
303 exitcode, out, err = execute(cmd)
304
305 self.assertEqual(0, exitcode)
306- self.assertEqual('No public images found.', out.strip())
307+ self.assertEqual('', out.strip())
308
309 # 1. Add public image
310 cmd = "bin/glance --port=%d add is_public=True name=MyImage" % api_port
311@@ -68,11 +68,9 @@
312 exitcode, out, err = execute(cmd)
313
314 self.assertEqual(0, exitcode)
315- lines = out.split("\n")
316- first_line = lines[0]
317- image_data_line = lines[3]
318- self.assertEqual('Found 1 public images...', first_line)
319- self.assertTrue('MyImage' in image_data_line)
320+ lines = out.split("\n")[2:-1]
321+ self.assertEqual(1, len(lines))
322+ self.assertTrue('MyImage' in lines[0])
323
324 # 3. Delete the image
325 cmd = "bin/glance --port=%d --force delete 1" % api_port
326@@ -88,7 +86,7 @@
327 exitcode, out, err = execute(cmd)
328
329 self.assertEqual(0, exitcode)
330- self.assertEqual('No public images found.', out.strip())
331+ self.assertEqual('', out.strip())
332
333 self.stop_servers()
334
335@@ -117,7 +115,7 @@
336 exitcode, out, err = execute(cmd)
337
338 self.assertEqual(0, exitcode)
339- self.assertEqual('No public images found.', out.strip())
340+ self.assertEqual('', out.strip())
341
342 # 1. Add public image
343 cmd = "bin/glance --port=%d add name=MyImage" % api_port
344@@ -133,7 +131,7 @@
345 exitcode, out, err = execute(cmd)
346
347 self.assertEqual(0, exitcode)
348- self.assertEqual('No public images found.', out.strip())
349+ self.assertEqual('', out.strip())
350
351 # 3. Update the image to make it public
352 cmd = "bin/glance --port=%d update 1 is_public=True" % api_port
353@@ -149,12 +147,9 @@
354 exitcode, out, err = execute(cmd)
355
356 self.assertEqual(0, exitcode)
357- lines = out.split("\n")
358- first_line = lines[0]
359- self.assertEqual('Found 1 public images...', first_line)
360-
361- image_data_line = lines[3]
362- self.assertTrue('MyImage' in image_data_line)
363+ lines = out.split("\n")[2:-1]
364+ self.assertEqual(len(lines), 1)
365+ self.assertTrue('MyImage' in lines[0])
366
367 # 5. Update the image's Name attribute
368 updated_image_name = "Updated image name"
369@@ -206,7 +201,7 @@
370 exitcode, out, err = execute(cmd)
371
372 self.assertEqual(0, exitcode)
373- self.assertEqual('No public images found.', out.strip())
374+ self.assertEqual('', out.strip())
375
376 # 1. Attempt to add an image
377 with tempfile.NamedTemporaryFile() as image_file:
378@@ -228,7 +223,7 @@
379 exitcode, out, err = execute(cmd)
380
381 self.assertEqual(0, exitcode)
382- self.assertEqual('No public images found.', out.strip())
383+ self.assertEqual('', out.strip())
384
385 # 3. Verify image status in show is 'killed'
386 cmd = "bin/glance --port=%d show 1" % api_port
387@@ -278,7 +273,7 @@
388 self.assertEqual(0, exitcode)
389 lines = out.split("\n")
390 first_line = lines[0]
391- self.assertEqual('No public images found.', first_line)
392+ self.assertEqual('', first_line)
393
394 # 4. Lastly we manually verify with SQL that image properties are
395 # also getting marked as deleted.
396@@ -288,3 +283,257 @@
397 self.assertEqual(0, rec[0])
398
399 self.stop_servers()
400+
401+ def test_results_filtering(self):
402+ self.cleanup()
403+ self.start_servers()
404+
405+ api_port = self.api_port
406+ registry_port = self.registry_port
407+
408+ # 1. Add some images
409+ _add_cmd = "bin/glance --port=%d add is_public=True" % api_port
410+ _add_args = [
411+ "name=Name1 disk_format=vhd container_format=ovf foo=bar",
412+ "name=Name2 disk_format=ami container_format=ami foo=bar",
413+ "name=Name3 disk_format=vhd container_format=ovf foo=baz",
414+ ]
415+
416+ for i, args in enumerate(_add_args):
417+ cmd = "%s %s" % (_add_cmd, args)
418+ exitcode, out, err = execute(cmd)
419+ self.assertEqual(0, exitcode)
420+ expected_out = 'Added new image with ID: %d' % (i + 1,)
421+ self.assertEqual(expected_out, out.strip())
422+
423+ _base_cmd = "bin/glance --port=%d" % api_port
424+ _index_cmd = "%s index -f" % (_base_cmd,)
425+
426+ # 2. Check name filter
427+ cmd = "name=Name2"
428+ exitcode, out, err = execute("%s %s" % (_index_cmd, cmd))
429+
430+ self.assertEqual(0, exitcode)
431+ image_lines = out.split("\n")[2:-1]
432+ self.assertEqual(1, len(image_lines))
433+ self.assertTrue(image_lines[0].startswith('2'))
434+
435+ # 3. Check disk_format filter
436+ cmd = "disk_format=vhd"
437+ exitcode, out, err = execute("%s %s" % (_index_cmd, cmd))
438+
439+ self.assertEqual(0, exitcode)
440+ image_lines = out.split("\n")[2:-1]
441+ self.assertEqual(2, len(image_lines))
442+ self.assertTrue(image_lines[0].startswith('3'))
443+ self.assertTrue(image_lines[1].startswith('1'))
444+
445+ # 4. Check container_format filter
446+ cmd = "container_format=ami"
447+ exitcode, out, err = execute("%s %s" % (_index_cmd, cmd))
448+
449+ self.assertEqual(0, exitcode)
450+ image_lines = out.split("\n")[2:-1]
451+ self.assertEqual(1, len(image_lines))
452+ self.assertTrue(image_lines[0].startswith('2'))
453+
454+ # 5. Check container_format filter
455+ cmd = "container_format=ami"
456+ exitcode, out, err = execute("%s %s" % (_index_cmd, cmd))
457+
458+ self.assertEqual(0, exitcode)
459+ image_lines = out.split("\n")[2:-1]
460+ self.assertEqual(1, len(image_lines))
461+ self.assertTrue(image_lines[0].startswith('2'))
462+
463+ # 6. Check status filter
464+ cmd = "status=killed"
465+ exitcode, out, err = execute("%s %s" % (_index_cmd, cmd))
466+
467+ self.assertEqual(0, exitcode)
468+ image_lines = out.split("\n")[2:-1]
469+ self.assertEqual(0, len(image_lines))
470+
471+ # 7. Check property filter
472+ cmd = "foo=bar"
473+ exitcode, out, err = execute("%s %s" % (_index_cmd, cmd))
474+
475+ self.assertEqual(0, exitcode)
476+ image_lines = out.split("\n")[2:-1]
477+ self.assertEqual(2, len(image_lines))
478+ self.assertTrue(image_lines[0].startswith('2'))
479+ self.assertTrue(image_lines[1].startswith('1'))
480+
481+ # 8. Check multiple filters
482+ cmd = "name=Name2 foo=bar"
483+ exitcode, out, err = execute("%s %s" % (_index_cmd, cmd))
484+
485+ self.assertEqual(0, exitcode)
486+ image_lines = out.split("\n")[2:-1]
487+ self.assertEqual(1, len(image_lines))
488+ self.assertTrue(image_lines[0].startswith('2'))
489+
490+ # 9. Ensure details call also respects filters
491+ _details_cmd = "%s details" % (_base_cmd,)
492+ cmd = "foo=bar"
493+ exitcode, out, err = execute("%s %s" % (_details_cmd, cmd))
494+
495+ self.assertEqual(0, exitcode)
496+ image_lines = out.split("\n")[1:-1]
497+ self.assertEqual(22, len(image_lines))
498+ self.assertTrue(image_lines[1].startswith('Id: 2'))
499+ self.assertTrue(image_lines[12].startswith('Id: 1'))
500+
501+ self.stop_servers()
502+
503+ def test_results_pagination(self):
504+ self.cleanup()
505+ self.start_servers()
506+
507+ _base_cmd = "bin/glance --port=%d" % self.api_port
508+ index_cmd = "%s index -f" % _base_cmd
509+ details_cmd = "%s details -f" % _base_cmd
510+
511+ # 1. Add some images
512+ _add_cmd = "bin/glance --port=%d add is_public=True" % self.api_port
513+ _add_args = [
514+ "name=Name1 disk_format=ami container_format=ami",
515+ "name=Name2 disk_format=vhd container_format=ovf",
516+ "name=Name3 disk_format=ami container_format=ami",
517+ "name=Name4 disk_format=ami container_format=ami",
518+ "name=Name5 disk_format=vhd container_format=ovf",
519+ ]
520+
521+ for i, args in enumerate(_add_args):
522+ cmd = "%s %s" % (_add_cmd, args)
523+ exitcode, out, err = execute(cmd)
524+ self.assertEqual(0, exitcode)
525+ expected_out = 'Added new image with ID: %d' % (i + 1,)
526+ self.assertEqual(expected_out, out.strip())
527+
528+ # 2. Limit less than total
529+ cmd = "--limit=3"
530+ exitcode, out, err = execute("%s %s" % (index_cmd, cmd))
531+
532+ self.assertEqual(0, exitcode)
533+ image_lines = out.split("\n")[2:-1]
534+ self.assertEqual(5, len(image_lines))
535+ self.assertTrue(image_lines[0].startswith('5'))
536+ self.assertTrue(image_lines[1].startswith('4'))
537+ self.assertTrue(image_lines[2].startswith('3'))
538+ self.assertTrue(image_lines[3].startswith('2'))
539+ self.assertTrue(image_lines[4].startswith('1'))
540+
541+ # 3. With a marker
542+ cmd = "--marker=4"
543+ exitcode, out, err = execute("%s %s" % (index_cmd, cmd))
544+
545+ self.assertEqual(0, exitcode)
546+ image_lines = out.split("\n")[2:-1]
547+ self.assertEqual(3, len(image_lines))
548+ self.assertTrue(image_lines[0].startswith('3'))
549+ self.assertTrue(image_lines[1].startswith('2'))
550+ self.assertTrue(image_lines[2].startswith('1'))
551+
552+ # 3. With a marker and limit
553+ cmd = "--marker=3 --limit=1"
554+ exitcode, out, err = execute("%s %s" % (index_cmd, cmd))
555+
556+ self.assertEqual(0, exitcode)
557+ image_lines = out.split("\n")[2:-1]
558+ self.assertEqual(2, len(image_lines))
559+ self.assertTrue(image_lines[0].startswith('2'))
560+ self.assertTrue(image_lines[1].startswith('1'))
561+
562+ # 4. Pagination params with filtered results
563+ cmd = "--marker=4 --limit=1 container_format=ami"
564+ exitcode, out, err = execute("%s %s" % (index_cmd, cmd))
565+
566+ self.assertEqual(0, exitcode)
567+ image_lines = out.split("\n")[2:-1]
568+ self.assertEqual(2, len(image_lines))
569+ self.assertTrue(image_lines[0].startswith('3'))
570+ self.assertTrue(image_lines[1].startswith('1'))
571+
572+ # 5. Pagination params with filtered results in a details call
573+ cmd = "--marker=4 --limit=1 container_format=ami"
574+ exitcode, out, err = execute("%s %s" % (details_cmd, cmd))
575+
576+ self.assertEqual(0, exitcode)
577+ image_lines = out.split("\n")[1:-1]
578+ self.assertEqual(20, len(image_lines))
579+ self.assertTrue(image_lines[1].startswith('Id: 3'))
580+ self.assertTrue(image_lines[11].startswith('Id: 1'))
581+
582+ def test_results_sorting(self):
583+ self.cleanup()
584+ self.start_servers()
585+
586+ _base_cmd = "bin/glance --port=%d" % self.api_port
587+ index_cmd = "%s index -f" % _base_cmd
588+ details_cmd = "%s details -f" % _base_cmd
589+
590+ # 1. Add some images
591+ _add_cmd = "bin/glance --port=%d add is_public=True" % self.api_port
592+ _add_args = [
593+ "name=Name1 disk_format=ami container_format=ami",
594+ "name=Name4 disk_format=vhd container_format=ovf",
595+ "name=Name3 disk_format=ami container_format=ami",
596+ "name=Name2 disk_format=ami container_format=ami",
597+ "name=Name5 disk_format=vhd container_format=ovf",
598+ ]
599+
600+ for i, args in enumerate(_add_args):
601+ cmd = "%s %s" % (_add_cmd, args)
602+ exitcode, out, err = execute(cmd)
603+ self.assertEqual(0, exitcode)
604+ expected_out = 'Added new image with ID: %d' % (i + 1,)
605+ self.assertEqual(expected_out, out.strip())
606+
607+ # 2. Sort by name asc
608+ cmd = "--sort_key=name --sort_dir=asc"
609+ exitcode, out, err = execute("%s %s" % (index_cmd, cmd))
610+
611+ self.assertEqual(0, exitcode)
612+ image_lines = out.split("\n")[2:-1]
613+ self.assertEqual(5, len(image_lines))
614+ self.assertTrue(image_lines[0].startswith('1'))
615+ self.assertTrue(image_lines[1].startswith('4'))
616+ self.assertTrue(image_lines[2].startswith('3'))
617+ self.assertTrue(image_lines[3].startswith('2'))
618+ self.assertTrue(image_lines[4].startswith('5'))
619+
620+ # 3. Sort by name asc with a marker
621+ cmd = "--sort_key=name --sort_dir=asc --marker=4"
622+ exitcode, out, err = execute("%s %s" % (index_cmd, cmd))
623+
624+ self.assertEqual(0, exitcode)
625+ image_lines = out.split("\n")[2:-1]
626+ self.assertEqual(3, len(image_lines))
627+ self.assertTrue(image_lines[0].startswith('3'))
628+ self.assertTrue(image_lines[1].startswith('2'))
629+ self.assertTrue(image_lines[2].startswith('5'))
630+
631+ # 4. Sort by container_format desc
632+ cmd = "--sort_key=container_format --sort_dir=desc"
633+ exitcode, out, err = execute("%s %s" % (index_cmd, cmd))
634+
635+ self.assertEqual(0, exitcode)
636+ image_lines = out.split("\n")[2:-1]
637+ self.assertEqual(5, len(image_lines))
638+ self.assertTrue(image_lines[0].startswith('5'))
639+ self.assertTrue(image_lines[1].startswith('2'))
640+ self.assertTrue(image_lines[2].startswith('4'))
641+ self.assertTrue(image_lines[3].startswith('3'))
642+ self.assertTrue(image_lines[4].startswith('1'))
643+
644+ # 5. Sort by name asc with a marker (details)
645+ cmd = "--sort_key=name --sort_dir=asc --marker=4"
646+ exitcode, out, err = execute("%s %s" % (details_cmd, cmd))
647+
648+ self.assertEqual(0, exitcode)
649+ image_lines = out.split("\n")[1:-1]
650+ self.assertEqual(30, len(image_lines))
651+ self.assertTrue(image_lines[1].startswith('Id: 3'))
652+ self.assertTrue(image_lines[11].startswith('Id: 2'))
653+ self.assertTrue(image_lines[21].startswith('Id: 5'))

Subscribers

People subscribed via source and target branches