Merge lp:~rackspace-titan/glance/glance-cli-filters into lp:~hudson-openstack/glance/trunk
- glance-cli-filters
- Merge into trunk
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 |
Related bugs: | |
Related blueprints: |
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.
Commit message
Description of the change
Add filter support to bin/glance index and details calls
Ed Leafe (ed-leafe) wrote : Posted in a previous version of this proposal | # |
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?
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
Brian Waldon (bcwaldon) wrote : Posted in a previous version of this proposal | # |
Setting to WIP until I get the results ordering stuff in here.
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.
Jay Pipes (jaypipes) wrote : | # |
I love this. Can't wait to see it go in...
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.
Jay Pipes (jaypipes) wrote : | # |
No images found should still return 0, though. :) It's a perfectly successful result.
OpenStack Infra (hudson-openstack) wrote : | # |
Attempt to merge into lp:glance failed due to conflicts:
text conflict in bin/glance
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 ;)
Brian Waldon (bcwaldon) wrote : | # |
Resolved merge conflicts. Ready to go again.
Preview Diff
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')) |
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.