Merge lp:~jaypipes/glance/glance-cli-tool into lp:~glance-coresec/glance/cactus-trunk

Proposed by Jay Pipes
Status: Merged
Approved by: Jay Pipes
Approved revision: 54
Merged at revision: 85
Proposed branch: lp:~jaypipes/glance/glance-cli-tool
Merge into: lp:~glance-coresec/glance/cactus-trunk
Prerequisite: lp:~jaypipes/glance/api-image-format
Diff against target: 1300 lines (+1058/-26)
14 files modified
bin/glance (+572/-0)
doc/source/glance.rst (+374/-0)
doc/source/index.rst (+1/-0)
glance/client.py (+1/-3)
glance/common/utils.py (+31/-0)
glance/registry/__init__.py (+51/-12)
glance/registry/db/api.py (+2/-2)
glance/registry/server.py (+4/-0)
glance/server.py (+2/-1)
glance/store/__init__.py (+2/-1)
glance/utils.py (+4/-3)
setup.py (+2/-1)
tests/stubs.py (+4/-1)
tests/unit/test_api.py (+8/-2)
To merge this branch: bzr merge lp:~jaypipes/glance/glance-cli-tool
Reviewer Review Type Date Requested Status
Jay Pipes (community) Approve
Rick Harris (community) Approve
Cory Wright (community) Approve
Sandy Walsh (community) Approve
Devin Carlen (community) Approve
Sandy Walsh Pending
Review via email: mp+51322@code.launchpad.net

Commit message

Add Glance CLI tool

Description of the change

Adds a CLI tool to Glance (bin/glance) that allows a user to
interact with the Glance API server:

* add images
* update image metadata
* delete images and metadata
* delete all images (clear)
* show an image
* list public images
* show detailed info on public images

Adds documentation for the tool and cleans up a few issues
that came up in initial testing.

To post a comment you must log in.
Revision history for this message
Devin Carlen (devcamcar) wrote :

Looks great, and excellent docs!

review: Approve
Revision history for this message
Sandy Walsh (sandy-walsh) wrote :

Looks great. I thought I'd pass along some things I like in novatools:

1. the use of PrettyTable to make the output look nice. It's a small, flexible package and would remove the need for the '-' * 16 stuff. http://code.google.com/p/prettytable/

2. Novatools goes through great pain to enforce Don't Repeat Yourself (DRY). All of the command line options are decorated so that they produce the --help output and we don't have to run around to update 3-4 different files whenever a command signature changes. It would be great to see that in this client shell as well.

These aren't critical for this merge, but something that would be nice to see for subsequent refactorings.

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

> Looks great. I thought I'd pass along some things I like in novatools:
>
> 1. the use of PrettyTable to make the output look nice. It's a small, flexible
> package and would remove the need for the '-' * 16 stuff.
> http://code.google.com/p/prettytable/

Hmm, I'll check that out. Thanks for the heads up!

> 2. Novatools goes through great pain to enforce Don't Repeat Yourself (DRY).
> All of the command line options are decorated so that they produce the --help
> output and we don't have to run around to update 3-4 different files whenever
> a command signature changes. It would be great to see that in this client
> shell as well.

All --help documentation is generated from the relevant function's docstrings. Other than the actual RST docs, there's only a single place to change. Does Novatools have RST docs that are auto-generated from the decorators somehow? Is the --help output for Novatools only the function signature (because there's a lot more to the help output in bin/glance, including example usage...)?

-jay

Revision history for this message
Cory Wright (corywright) wrote :

Looks like setup.py needs to be updated to install bin/glance as well.

Hurry up and get this in, I can't wait to use it. :)

Revision history for this message
Cory Wright (corywright) wrote :

The 'details' view doesn't appear to be working properly. It finds my 7 images, but only prints details on the first one of them.

  cwright@maverick:~/openstack/glance-cli-tool$ ./bin/glance details
  Found 7 public images...
  ================================================================================
  URI: http://0.0.0.0/images/1
  Id: 1
  Public? Yes
  Name: ramdisk
  Size: 3976716
  Location: file:///home/cwright/openstack/images/glance/1
  'disk_format'
  'disk_format'
  cwright@maverick:~/openstack/glance-cli-tool$

From the code it looks like it should print image properties, but it isn't for one of my images. Also, notice the duplicate 'disk_format' lines in the output.

It would also be nice to be able to specify a particular image to get details for instead of having it print all of them. glance details could accept an optional image id.

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

back from jury duty... will check out Cory's concerns.

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

> The 'details' view doesn't appear to be working properly. It finds my 7
> images, but only prints details on the first one of them.
>
> cwright@maverick:~/openstack/glance-cli-tool$ ./bin/glance details
> Found 7 public images...
> ============================================================================
> ====
> URI: http://0.0.0.0/images/1
> Id: 1
> Public? Yes
> Name: ramdisk
> Size: 3976716
> Location: file:///home/cwright/openstack/images/glance/1
> 'disk_format'
> 'disk_format'
> cwright@maverick:~/openstack/glance-cli-tool$
>
> From the code it looks like it should print image properties, but it isn't for
> one of my images. Also, notice the duplicate 'disk_format' lines in the
> output.

Hmm, looks like this might be because of the admittedly missing database migration file. Looks like disk_format isn't a column returned from the db because the schema hasn't been migrated. Are you using an existing db?

> It would also be nice to be able to specify a particular image to get details
> for instead of having it print all of them. glance details could accept an
> optional image id.

You mean glance show <ID>? :)

-jay

Revision history for this message
Cory Wright (corywright) wrote :

> Hmm, looks like this might be because of the admittedly missing database
> migration file. Looks like disk_format isn't a column returned from the db
> because the schema hasn't been migrated. Are you using an existing db?

Yes, I was using my existing db. Sorry about that, I'll try again after migrating the db.

> > It would also be nice to be able to specify a particular image to get
> details
> > for instead of having it print all of them. glance details could accept an
> > optional image id.
>
> You mean glance show <ID>? :)

So 'details' is equivalent to 'show' for all images? I would expect 'show' to give basic information on an image, 'details' to give more information on an image such as custom image properties, and something else (showall?) to list everything.

One other nitpick, in the 'show' output is there a good reason to use 'Public?' instead of 'Public:' ? Using colons as the delimiter everywhere would make this output easy to parse in shell scripts and such.

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

On Tue, Mar 1, 2011 at 10:57 AM, Cory Wright <email address hidden> wrote:
>> Hmm, looks like this might be because of the admittedly missing database
>> migration file.  Looks like disk_format isn't a column returned from the db
>> because the schema hasn't been migrated. Are you using an existing db?
>
> Yes, I was using my existing db.  Sorry about that, I'll try again after migrating the db.
>
>> > It would also be nice to be able to specify a particular image to get
>> details
>> > for instead of having it print all of them.  glance details could accept an
>> > optional image id.
>>
>> You mean glance show <ID>? :)
>
> So 'details' is equivalent to 'show' for all images?  I would expect 'show' to give basic information on an image, 'details' to give more information on an image such as custom image properties, and something else (showall?) to list everything.

Well, show(), index(), and details() do exactly the same as they do in
the OpenStack API controller. That's where the method names come from.

> One other nitpick, in the 'show' output is there a good reason to use 'Public?' instead of 'Public:' ?   Using colons as the delimiter everywhere would make this output easy to parse in shell scripts and such.

No, I can change that. I was planning on adding a -m/--machine option
to make the output ideal for machine-reading.

-jay

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

Changed Public? => Public: per Cory's comment and added some more robust exception handling in a few of the bin/glance methods... also all merged up with trunk and resolved any conflicts. Please re-review at your earliest.

Revision history for this message
Cory Wright (corywright) :
review: Approve
Revision history for this message
Rick Harris (rconradharris) wrote :

Looks good, great work Jay!

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

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

text conflict in glance/utils.py

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

Resolved conflict after merging trunk.

review: Approve
Revision history for this message
OpenStack Infra (hudson-openstack) wrote :
Download full text (6.3 KiB)

The attempt to merge lp:~jaypipes/glance/glance-cli-tool into lp:glance failed. Below is the output from the failed tests.

running test
running egg_info
creating glance.egg-info
writing glance.egg-info/PKG-INFO
writing top-level names to glance.egg-info/top_level.txt
writing dependency_links to glance.egg-info/dependency_links.txt
writing manifest file 'glance.egg-info/SOURCES.txt'
reading manifest file 'glance.egg-info/SOURCES.txt'
reading manifest template 'MANIFEST.in'
warning: no files found matching 'LICENSE'
warning: no files found matching 'ChangeLog'
warning: no files found matching 'tests/test_data.py'
writing manifest file 'glance.egg-info/SOURCES.txt'
running build_ext

Tests raises BadRequest for invalid store header ... ok
Tests to add a basic image in the file store ... ok
Tests creates a queued image for no body and no loc header ... ok
test_bad_container_format (tests.unit.test_api.TestGlanceAPI) ... ok
test_bad_disk_format (tests.unit.test_api.TestGlanceAPI) ... ok
test_delete_image (tests.unit.test_api.TestGlanceAPI) ... ok
test_delete_non_exists_image (tests.unit.test_api.TestGlanceAPI) ... ok
Test for HEAD /images/<ID> ... ok
test_show_image_basic (tests.unit.test_api.TestGlanceAPI) ... ok
test_show_non_exists_image (tests.unit.test_api.TestGlanceAPI) ... ok
Tests that the /images POST registry API creates the image ... ok
Tests proper exception is raised if a bad disk_format is set ... ok
Tests proper exception is raised if a bad disk_format is set ... ok
Tests proper exception is raised if a bad status is set ... ok
Tests that exception raised for bad matching disk and container ... ok
Tests that the /images DELETE registry API deletes the image ... ok
Tests proper exception is raised if attempt to delete non-existing ... ok
Tests that the /images/detail registry API returns ... ok
Tests that the /images registry API returns list of ... ok
Tests that the root registry API returns "index", ... ok
Tests that the /images PUT registry API updates the image ... ok
Tests proper exception is raised if attempt to update non-existing ... ok
Tests that exception raised trying to set a bad container_format ... ok
Tests that exception raised trying to set a bad disk_format ... ok
Tests that exception raised trying to set a bad status ... ok
Tests that exception raised for bad matching disk and container ... ok
Test ClientConnectionError raised ... ok
Tests proper exception is raised if image with ID already exists ... ok
Tests that we can add image metadata and returns the new id ... ok
Tests a bad status is set to a proper one by server ... ok
Tests BadRequest raised when supplying bad store name in meta ... ok
Tests can add image by passing image data as file ... ok
Tests can add image by passing image data as string ... ok
Tests add image by passing image data as string w/ no size attr ... ok
Tests that we can add image metadata with properties ... ok
Tests client returns image as queued ... ok
Tests that image metadata is deleted properly ... ok
Tests cannot delete non-existing image ... ok
Test a simple file backend retrieval works as expected ... ok
Tests that the detailed info about public images returned ... ok
Test correct...

Read more...

Revision history for this message
Vish Ishaya (vishvananda) wrote :

looks like you mismerged here:

=== modified file 'glance/utils.py'
1200 --- glance/utils.py 2011-03-08 20:12:41 +0000
1201 +++ glance/utils.py 2011-03-09 00:10:38 +0000
1202 @@ -36,9 +36,10 @@
1203 pv = ''
1204 headers["x-image-meta-property-%s"
1205 % pk.lower()] = unicode(pv)
1206 + else:
1207 + headers["x-image-meta-%s" % k.lower()] = unicode(v)
1208 if v is None:
1209 v = ''
1210 - headers["x-image-meta-%s" % k.lower()] = unicode(v)
1211 return headers

the if v is None block needs to be inside the else and above the headers line.

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

Well, duh :) Fixing now :)

On Tue, Mar 8, 2011 at 9:22 PM, Vish Ishaya <email address hidden> wrote:
> looks like you mismerged here:
>
> === modified file 'glance/utils.py'
> 1200    --- glance/utils.py     2011-03-08 20:12:41 +0000
> 1201    +++ glance/utils.py     2011-03-09 00:10:38 +0000
> 1202    @@ -36,9 +36,10 @@
> 1203                         pv = ''
> 1204                     headers["x-image-meta-property-%s"
> 1205                             % pk.lower()] = unicode(pv)
> 1206    +        else:
> 1207    +            headers["x-image-meta-%s" % k.lower()] = unicode(v)
> 1208             if v is None:
> 1209                 v = ''
> 1210    -        headers["x-image-meta-%s" % k.lower()] = unicode(v)
> 1211         return headers
>
> the if v is None block needs to be inside the else and above the headers line.
>
> --
> https://code.launchpad.net/~jaypipes/glance/glance-cli-tool/+merge/51322
> You are the owner of lp:~jaypipes/glance/glance-cli-tool.
>

lp:~jaypipes/glance/glance-cli-tool updated
54. By Jay Pipes

Silly mistake when resolving merge conflict...fixed

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

fixed merge mistake..

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file 'bin/glance'
--- bin/glance 1970-01-01 00:00:00 +0000
+++ bin/glance 2011-03-09 13:40:45 +0000
@@ -0,0 +1,572 @@
1#!/usr/bin/env python
2# vim: tabstop=4 shiftwidth=4 softtabstop=4
3
4# Copyright 2011 OpenStack, LLC
5# All Rights Reserved.
6#
7# Licensed under the Apache License, Version 2.0 (the "License"); you may
8# not use this file except in compliance with the License. You may obtain
9# a copy of the License at
10#
11# http://www.apache.org/licenses/LICENSE-2.0
12#
13# Unless required by applicable law or agreed to in writing, software
14# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16# License for the specific language governing permissions and limitations
17# under the License.
18
19"""
20This is the administration program for Glance. It is simply a command-line
21interface for adding, modifying, and retrieving information about the images
22stored in one or more Glance nodes.
23"""
24
25import optparse
26import os
27import re
28import sys
29import time
30
31# If ../glance/__init__.py exists, add ../ to Python search path, so that
32# it will override what happens to be installed in /usr/(local/)lib/python...
33possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
34 os.pardir,
35 os.pardir))
36if os.path.exists(os.path.join(possible_topdir, 'glance', '__init__.py')):
37 sys.path.insert(0, possible_topdir)
38
39from glance import client
40from glance import version
41from glance.common import exception
42from glance.common import utils
43
44SUCCESS = 0
45FAILURE = 1
46
47
48def get_image_fields_from_args(args):
49 """
50 Validate the set of arguments passed as field name/value pairs
51 and return them as a mapping.
52 """
53 fields = {}
54 for arg in args:
55 pieces = arg.strip(',').split('=')
56 if len(pieces) != 2:
57 msg = ("Arguments should be in the form of field=value. "
58 "You specified %s." % arg)
59 raise RuntimeError(msg)
60 fields[pieces[0]] = pieces[1]
61
62 fields = dict([(k.lower().replace('-', '_'), v)
63 for k, v in fields.items()])
64 return fields
65
66
67def print_image_formatted(client, image):
68 """
69 Formatted print of image metadata.
70
71 :param client: The Glance client object
72 :param image: The image metadata
73 """
74 print "URI: %s://%s/images/%s" % (client.use_ssl and "https" or "http",
75 client.host,
76 image['id'])
77 print "Id: %s" % image['id']
78 print "Public: " + (image['is_public'] and "Yes" or "No")
79 print "Name: %s" % image['name']
80 print "Size: %d" % int(image['size'])
81 print "Location: %s" % image['location']
82 print "Disk format: %s" % image['disk_format']
83 print "Container format: %s" % image['container_format']
84 if len(image['properties']) > 0:
85 for k, v in image['properties'].items():
86 print "Property '%s': %s" % (k, v)
87
88
89def image_add(options, args):
90 """
91%(prog)s add [options] <field1=value1 field2=value2 ...> [ < /path/to/image ]
92
93Adds a new image to Glance. Specify metadata fields as arguments.
94
95SPECIFYING IMAGE METADATA
96===============================================================================
97
98All field/value pairs are converted into a mapping that is passed
99to Glance that represents the metadata for an image.
100
101Field names of note:
102
103id Optional. If not specified, an image identifier will be
104 automatically assigned.
105name Required. A name for the image.
106size Optional. Should be size in bytes of the image if
107 specified.
108is_public Optional. If specified, interpreted as a boolean value
109 and sets or unsets the image's availability to the public.
110 The default value is False.
111disk_format Optional. Possible values are 'vhd','vmdk','raw', 'qcow2',
112 and 'ami'. Default value is 'raw'.
113container_format Optional. Possible values are 'ovf' and 'ami'.
114 Default value is 'ovf'.
115location Optional. When specified, should be a readable location
116 in the form of a URI: $STORE://LOCATION. For example, if
117 the image data is stored in a file on the local
118 filesystem at /usr/share/images/some.image.tar.gz
119 you would specify:
120 location=file:///usr/share/images/some.image.tar.gz
121
122Any other field names are considered to be custom properties so be careful
123to spell field names correctly. :)
124
125STREAMING IMAGE DATA
126===============================================================================
127
128If the location field is not specified, you can stream an image file on
129the command line using standard redirection. For example:
130
131%(prog)s add name="Ubuntu 10.04 LTS 5GB" < /tmp/images/myimage.tar.gz
132
133EXAMPLES
134===============================================================================
135
136%(prog)s add name="My Image" disk_format=raw container_format=ovf \\
137 location=http://images.ubuntu.org/images/lucid-10.04-i686.iso \\
138 distro="Ubuntu 10.04 LTS"
139
140%(prog)s add name="My Image" distro="Ubuntu 10.04 LTS" < /tmp/myimage.iso"""
141 c = get_client(options)
142
143 try:
144 fields = get_image_fields_from_args(args)
145 except RuntimeError, e:
146 print e
147 return FAILURE
148
149 if 'name' not in fields.keys():
150 print "Please specify a name for the image using name=VALUE"
151 return FAILURE
152
153 image_meta = {'name': fields.pop('name'),
154 'is_public': utils.bool_from_string(
155 fields.pop('is_public', False)),
156 'disk_format': fields.pop('disk_format', 'raw'),
157 'container_format': fields.pop('container_format', 'ovf')}
158
159 # Strip any args that are not supported
160 unsupported_fields = ['status']
161 for field in unsupported_fields:
162 if field in fields.keys():
163 print 'Found non-settable field %s. Removing.' % field
164 fields.pop(field)
165
166 if 'location' in fields.keys():
167 image_meta['location'] = fields.pop('location')
168
169 # We need either a location or image data/stream to add...
170 image_location = image_meta.get('location')
171 image_data = None
172 if not image_location:
173 # Grab the image data stream from stdin or redirect,
174 # otherwise error out
175 image_data = sys.stdin
176 else:
177 # Ensure no image data has been given
178 if not sys.stdin.isatty():
179 print "Either supply a location=LOCATION argument or supply image "
180 print "data via a redirect. You have supplied BOTH image data "
181 print "AND a location."
182 return FAILURE
183
184 # Add custom attributes, which are all the arguments remaining
185 image_meta['properties'] = fields
186
187 if not options.dry_run:
188 try:
189 image_meta = c.add_image(image_meta, image_data)
190 image_id = image_meta['id']
191 print "Added new image with ID: %s" % image_id
192 if options.verbose:
193 print "Returned the following metadata for the new image:"
194 for k, v in sorted(image_meta.items()):
195 print " %(k)30s => %(v)s" % locals()
196 except client.ClientConnectionError, e:
197 host = options.host
198 port = options.port
199 print ("Failed to connect to the Glance API server "
200 "%(host)s:%(port)d. Is the server running?" % locals())
201 if options.verbose:
202 pieces = str(e).split('\n')
203 for piece in pieces:
204 print piece
205 return FAILURE
206 except Exception, e:
207 print "Failed to add image. Got error:"
208 pieces = str(e).split('\n')
209 for piece in pieces:
210 print piece
211 return FAILURE
212 else:
213 print "Dry run. We would have done the following:"
214 print "Add new image with metadata:"
215 for k, v in sorted(image_meta.items()):
216 print " %(k)30s => %(v)s" % locals()
217
218 return SUCCESS
219
220
221def image_update(options, args):
222 """
223%(prog)s update [options] <ID> <field1=value1 field2=value2 ...>
224
225Updates an image's metadata in Glance. Specify metadata fields as arguments.
226
227All field/value pairs are converted into a mapping that is passed
228to Glance that represents the metadata for an image.
229
230Field names that can be specified:
231
232name A name for the image.
233is_public If specified, interpreted as a boolean value
234 and sets or unsets the image's availability to the public.
235disk_format Format of the disk image
236container_format Format of the container
237
238All other field names are considered to be custom properties so be careful
239to spell field names correctly. :)"""
240 c = get_client(options)
241 try:
242 image_id = args.pop(0)
243 except IndexError:
244 print "Please specify the ID of the image you wish to update "
245 print "as the first argument"
246 return FAILURE
247
248 try:
249 fields = get_image_fields_from_args(args)
250 except RuntimeError, e:
251 print e
252 return FAILURE
253
254 image_meta = {}
255
256 # Strip any args that are not supported
257 nonmodifiable_fields = ['created_on', 'deleted_on', 'deleted',
258 'updated_on', 'size', 'status']
259 for field in nonmodifiable_fields:
260 if field in fields.keys():
261 print 'Found non-modifiable field %s. Removing.' % field
262 fields.pop(field)
263
264 base_image_fields = ['disk_format', 'container_format',
265 'location']
266 for field in base_image_fields:
267 fvalue = fields.pop(field, None)
268 if fvalue:
269 image_meta[field] = fvalue
270
271 # Have to handle "boolean" values specially...
272 if 'is_public' in fields:
273 image_meta['is_public'] = utils.int_from_bool_as_string(
274 fields.pop('is_public'))
275
276 # Add custom attributes, which are all the arguments remaining
277 image_meta['properties'] = fields
278
279 if not options.dry_run:
280 try:
281 image_meta = c.update_image(image_id, image_meta=image_meta)
282 print "Updated image %s" % image_id
283
284 if options.verbose:
285 print "Updated image metadata for image %s:" % image_id
286 print_image_formatted(c, image_meta)
287 except exception.NotFound:
288 print "No image with ID %s was found" % image_id
289 return FAILURE
290 except Exception, e:
291 print "Failed to update image. Got error:"
292 pieces = str(e).split('\n')
293 for piece in pieces:
294 print piece
295 return FAILURE
296 else:
297 print "Dry run. We would have done the following:"
298 print "Update existing image with metadata:"
299 for k, v in sorted(image_meta.items()):
300 print " %(k)30s => %(v)s" % locals()
301 return SUCCESS
302
303
304def image_delete(options, args):
305 """
306%(prog)s delete [options] <ID>
307
308Deletes an image from Glance"""
309 c = get_client(options)
310 try:
311 image_id = args.pop()
312 except IndexError:
313 print "Please specify the ID of the image you wish to delete "
314 print "as the first argument"
315 return FAILURE
316
317 try:
318 c.delete_image(image_id)
319 print "Deleted image %s" % image_id
320 return SUCCESS
321 except exception.NotFound:
322 print "No image with ID %s was found" % image_id
323 return FAILURE
324
325
326def image_show(options, args):
327 """
328%(prog)s show [options] <ID>
329
330Shows image metadata for an image in Glance"""
331 c = get_client(options)
332 try:
333 if len(args) > 0:
334 image_id = args[0]
335 else:
336 print "Please specify the image identifier as the "
337 print "first argument. Example: "
338 print "$> glance-admin show 12345"
339 return FAILURE
340
341 image = c.get_image_meta(image_id)
342 print_image_formatted(c, image)
343 return SUCCESS
344 except exception.NotFound:
345 print "No image with ID %s was found" % image_id
346 return FAILURE
347 except Exception, e:
348 print "Failed to show image. Got error:"
349 pieces = str(e).split('\n')
350 for piece in pieces:
351 print piece
352 return FAILURE
353
354
355def images_index(options, args):
356 """
357%(prog)s index [options]
358
359Returns basic information for all public images
360a Glance server knows about"""
361 c = get_client(options)
362 try:
363 images = c.get_images()
364 if len(images) == 0:
365 print "No public images found."
366 return SUCCESS
367
368 print "Found %d public images..." % len(images)
369 print "%-16s %-30s %-20s %-20s %-14s" % (("ID"),
370 ("Name"),
371 ("Disk Format"),
372 ("Container Format"),
373 ("Size"))
374 print ('-' * 16) + " " + ('-' * 30) + " "\
375 + ('-' * 20) + " " + ('-' * 20) + " " + ('-' * 14)
376 for image in images:
377 print "%-16s %-30s %-20s %-20s %14d" % (image['id'],
378 image['name'],
379 image['disk_format'],
380 image['container_format'],
381 int(image['size']))
382 return SUCCESS
383 except Exception, e:
384 print "Failed to show index. Got error:"
385 pieces = str(e).split('\n')
386 for piece in pieces:
387 print piece
388 return FAILURE
389
390
391def images_detailed(options, args):
392 """
393%(prog)s details [options]
394
395Returns detailed information for all public images
396a Glance server knows about"""
397 c = get_client(options)
398 try:
399 images = c.get_images_detailed()
400 if len(images) == 0:
401 print "No public images found."
402 return SUCCESS
403
404 num_images = len(images)
405 print "Found %d public images..." % num_images
406 cur_image = 1
407 for image in images:
408 print "=" * 80
409 print_image_formatted(c, image)
410 if cur_image == num_images:
411 print "=" * 80
412 cur_image += 1
413
414 return SUCCESS
415 except Exception, e:
416 print "Failed to show details. Got error:"
417 pieces = str(e).split('\n')
418 for piece in pieces:
419 print piece
420 return FAILURE
421
422
423def images_clear(options, args):
424 """
425%(prog)s clear [options]
426
427Deletes all images from a Glance server"""
428 c = get_client(options)
429 images = c.get_images()
430 for image in images:
431 if options.verbose:
432 print 'Deleting image %s "%s" ...' % (image['id'], image['name']),
433 try:
434 c.delete_image(image['id'])
435 if options.verbose:
436 print 'done'
437 except Exception, e:
438 print 'Failed to delete image %s' % image['id']
439 print e
440 return FAILURE
441 return SUCCESS
442
443
444def get_client(options):
445 """
446 Returns a new client object to a Glance server
447 specified by the --host and --port options
448 supplied to the CLI
449 """
450 return client.Client(host=options.host,
451 port=options.port)
452
453
454def create_options(parser):
455 """
456 Sets up the CLI and config-file options that may be
457 parsed and program commands.
458
459 :param parser: The option parser
460 """
461 parser.add_option('-v', '--verbose', default=False, action="store_true",
462 help="Print more verbose output")
463 parser.add_option('-H', '--host', metavar="ADDRESS", default="0.0.0.0",
464 help="Address of Glance API host. "
465 "Default: %default")
466 parser.add_option('-p', '--port', dest="port", metavar="PORT",
467 type=int, default=9292,
468 help="Port the Glance API host listens on. "
469 "Default: %default")
470 parser.add_option('--dry-run', default=False, action="store_true",
471 help="Don't actually execute the command, just print "
472 "output showing what WOULD happen.")
473
474
475def parse_options(parser, cli_args):
476 """
477 Returns the parsed CLI options, command to run and its arguments, merged
478 with any same-named options found in a configuration file
479
480 :param parser: The option parser
481 """
482 COMMANDS = {'help': print_help,
483 'add': image_add,
484 'update': image_update,
485 'delete': image_delete,
486 'index': images_index,
487 'details': images_detailed,
488 'show': image_show,
489 'clear': images_clear}
490
491 if not cli_args:
492 cli_args.append('-h') # Show options in usage output...
493
494 (options, args) = parser.parse_args(cli_args)
495
496 if not args:
497 parser.print_usage()
498 sys.exit(0)
499 else:
500 command_name = args.pop(0)
501 if command_name not in COMMANDS.keys():
502 sys.exit("Unknown command: %s" % command_name)
503 command = COMMANDS[command_name]
504
505 return (options, command, args)
506
507
508def print_help(options, args):
509 """
510 Print help specific to a command
511 """
512 COMMANDS = {'add': image_add,
513 'update': image_update,
514 'delete': image_delete,
515 'index': images_index,
516 'details': images_detailed,
517 'show': image_show,
518 'clear': images_clear}
519
520 if len(args) != 1:
521 sys.exit("Please specify a command")
522
523 command = args.pop()
524 if command not in COMMANDS.keys():
525 parser.print_usage()
526 if args:
527 sys.exit("Unknown command: %s" % command)
528
529 print COMMANDS[command].__doc__ % {'prog': os.path.basename(sys.argv[0])}
530
531
532if __name__ == '__main__':
533 usage = """
534%prog <command> [options] [args]
535
536Commands:
537
538 help <command> Output help for one of the commands below
539
540 add Adds a new image to Glance
541
542 update Updates an image's metadata in Glance
543
544 delete Deletes an image from Glance
545
546 index Return brief information about images in Glance
547
548 details Return detailed information about images in
549 Glance
550
551 show Show detailed information about an image in
552 Glance
553
554 clear Removes all images and metadata from Glance
555
556"""
557
558 oparser = optparse.OptionParser(version='%%prog %s'
559 % version.version_string(),
560 usage=usage.strip())
561 create_options(oparser)
562 (options, command, args) = parse_options(oparser, sys.argv[1:])
563
564 try:
565 start_time = time.time()
566 result = command(options, args)
567 end_time = time.time()
568 if options.verbose:
569 print "Completed in %-0.4f sec." % (end_time - start_time)
570 sys.exit(result)
571 except (RuntimeError, NotImplementedError), e:
572 print "ERROR: ", e
0573
=== added file 'doc/source/glance.rst'
--- doc/source/glance.rst 1970-01-01 00:00:00 +0000
+++ doc/source/glance.rst 2011-03-09 13:40:45 +0000
@@ -0,0 +1,374 @@
1..
2 Copyright 2011 OpenStack, LLC
3 All Rights Reserved.
4
5 Licensed under the Apache License, Version 2.0 (the "License"); you may
6 not use this file except in compliance with the License. You may obtain
7 a copy of the License at
8
9 http://www.apache.org/licenses/LICENSE-2.0
10
11 Unless required by applicable law or agreed to in writing, software
12 distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13 WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 License for the specific language governing permissions and limitations
15 under the License.
16
17Using the Glance CLI Tool
18=========================
19
20Glance ships with a command-line tool for quering and managing Glance
21It has a fairly simple but powerful interface of the form::
22
23 Usage: glance <command> [options] [args]
24
25Where ``<command>`` is one of the following:
26
27* help
28
29 Show detailed help information about a specific command
30
31* add
32
33 Adds an image to Glance
34
35* update
36
37 Updates an image's stored metadata in Glance
38
39* delete
40
41 Deletes an image and its metadata from Glance
42
43* index
44
45 Lists brief information about *public* images that Glance knows about
46
47* details
48
49 Lists detailed information about *public* images that Glance knows about
50
51* show
52
53 Lists detailed information about a specific image
54
55* clear
56
57 Destroys *all* images and their associated metadata
58
59This document describes how to use the ``glance`` tool for each of
60the above commands.
61
62The ``help`` command
63--------------------
64
65Issuing the ``help`` command with a ``<COMMAND>`` argument shows detailed help
66about a specific command. Running ``glance`` without any arguments shows
67a brief help message, like so::
68
69 $> glance
70 Usage: glance <command> [options] [args]
71
72 Commands:
73
74 help <command> Output help for one of the commands below
75
76 add Adds a new image to Glance
77
78 update Updates an image's metadata in Glance
79
80 delete Deletes an image from Glance
81
82 index Return brief information about images in Glance
83
84 details Return detailed information about images in
85 Glance
86
87 show Show detailed information about an image in
88 Glance
89
90 clear Removes all images and metadata from Glance
91
92 Options:
93 --version show program's version number and exit
94 -h, --help show this help message and exit
95 -v, --verbose Print more verbose output
96 -H ADDRESS, --host=ADDRESS
97 Address of Glance API host. Default: example.com
98 -p PORT, --port=PORT Port the Glance API host listens on. Default: 9292
99 --dry-run Don't actually execute the command, just print output
100 showing what WOULD happen.
101
102With a ``<COMMAND>`` argument, more information on the command is shown,
103like so::
104
105 $> glance help update
106
107 glance update [options] <ID> <field1=value1 field2=value2 ...>
108
109 Updates an image's metadata in Glance. Specify metadata fields as arguments.
110
111 All field/value pairs are converted into a mapping that is passed
112 to Glance that represents the metadata for an image.
113
114 Field names that can be specified:
115
116 name A name for the image.
117 is_public If specified, interpreted as a boolean value
118 and sets or unsets the image's availability to the public.
119 disk_format Format of the disk image
120 container_format Format of the container
121
122 All other field names are considered to be custom properties so be careful
123 to spell field names correctly. :)
124
125The ``add`` command
126-------------------
127
128The ``add`` command is used to do both of the following:
129
130* Store virtual machine image data and metadata about that image in Glance
131
132* Let Glance know about an existing virtual machine image that may be stored
133 somewhere else
134
135We cover both use cases below.
136
137Store virtual machine image data and metadata
138~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
139
140When adding an actual virtual machine image to Glance, you use the ``add``
141command. You will pass metadata about the VM image on the command line, and
142you will use a standard shell redirect to stream the image data file to
143``glance``.
144
145Let's walk through a simple example. Suppose we have an image stored on our
146local filesystem that we wish to "upload" to Glance. This image is stored
147on our local filesystem in ``/tmp/images/myimage.tar.gz``.
148
149We'd also like to tell Glance that this image should be called "My Image", and
150that the image should be public -- anyone should be able to fetch it.
151
152Here is how we'd upload this image to Glance::
153
154 $> glance add name="My Image" is_public=true < /tmp/images/myimage.tar.gz
155
156If Glance was able to successfully upload and store your VM image data and
157metadata attributes, you would see something like this::
158
159 $> glance add name="My Image" is_public=true < /tmp/images/myimage.tar.gz
160 Added new image with ID: 2
161
162You can use the ``--verbose`` (or ``-v``) command-line option to print some more
163information about the metadata that was saved with the image::
164
165 $> glance --verbose add name="My Image" is_public=true < /tmp/images/myimage.tar.gz
166 Added new image with ID: 4
167 Returned the following metadata for the new image:
168 container_format => ovf
169 created_at => 2011-02-22T19:20:53.298556
170 deleted => False
171 deleted_at => None
172 disk_format => raw
173 id => 4
174 is_public => True
175 location => file:///tmp/images/4
176 name => My Image
177 properties => {}
178 size => 58520278
179 status => active
180 updated_at => None
181 Completed in 0.6141 sec.
182
183If you are unsure about what will be added, you can use the ``--dry-run``
184command-line option, which will simply show you what *would* have happened::
185
186 $> glance --dry-run add name="Foo" distro="Ubuntu" is_publi=True < /tmp/images/myimage.tar.gz
187 Dry run. We would have done the following:
188 Add new image with metadata:
189 container_format => ovf
190 disk_format => raw
191 is_public => False
192 name => Foo
193 properties => {'is_publi': 'True', 'distro': 'Ubuntu'}
194
195This is useful for detecting problems and for seeing what the default field
196values supplied by ``glance`` are. For instance, there was a typo in
197the command above (the ``is_public`` field was incorrectly spelled ``is_publi``
198which resulted in the image having an ``is_publi`` custom property added to
199the image and the *real* ``is_public`` field value being `False` (the default)
200and not `True`...
201
202Register a virtual machine image in another location
203~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
204
205Sometimes, you already have stored the virtual machine image in some non-Glance
206location -- perhaps even a location you have no write access to -- and you want
207to tell Glance where this virtual machine image is located and some metadata
208about it. The ``add`` command can do this for you.
209
210When registering an image in this way, the only difference is that you do not
211use a shell redirect to stream a virtual machine image file into Glance, but
212instead, you tell Glance where to find the existing virtual machine image by
213setting the ``location`` field. Below is an example of doing this.
214
215Let's assume that there is a virtual machine image located at the URL
216``http://example.com/images/myimage.tar.gz``. We can register this image with
217Glance using the following::
218
219 $> glance --verbose add name="Some web image" location="http://example.com/images/myimage.tar.gz"
220 Added new image with ID: 1
221 Returned the following metadata for the new image:
222 container_format => ovf
223 created_at => 2011-02-23T00:42:04.688890
224 deleted => False
225 deleted_at => None
226 disk_format => vhd
227 id => 1
228 is_public => True
229 location => http://example.com/images/myimage.tar.gz
230 name => Some web image
231 properties => {}
232 size => 0
233 status => active
234 updated_at => None
235 Completed in 0.0356 sec.
236
237The ``update`` command
238----------------------
239
240After uploading/adding a virtual machine image to Glance, it is not possible to
241modify the actual virtual machine image -- images are read-only after all --
242however, it *is* possible to update any metadata about the image after you add
243it to Glance.
244
245The ``update`` command allows you to update the metadata fields of a stored
246image. You use this command like so::
247
248 glance update <ID> [field1=value1 field2=value2 ...]
249
250Let's say we have an image with identifier 5 that we wish to change the is_public
251attribute of the image from False to True. The following would accomplish this::
252
253 $> glance update 5 is_public=true
254 Updated image 5
255
256Using the ``--verbose`` flag will show you all the updated data about the image::
257
258 $> glance --verbose update 5 is_public=true
259 Updated image 5
260 Updated image metadata for image 5:
261 URI: http://example.com/images/5
262 Id: 5
263 Public? Yes
264 Name: My Image
265 Size: 58520278
266 Location: file:///tmp/images/5
267 Disk format: raw
268 Container format: ovf
269 Completed in 0.0596 sec.
270
271The ``delete`` command
272----------------------
273
274You can delete an image by using the ``delete`` command, shown below::
275
276 $> glance --verbose delete 5
277 Deleted image 5
278
279The ``index`` command
280---------------------
281
282The ``index`` command displays brief information about the *public* images
283available in Glance, as shown below::
284
285 $> glance index
286 Found 4 public images...
287 ID Name Disk Format Container Format Size
288 ---------------- ------------------------------ -------------------- -------------------- --------------
289 1 Ubuntu 10.10 vhd ovf 58520278
290 2 Ubuntu 10.04 ami ami 58520278
291 3 Fedora 9 vdi bare 3040
292 4 Vanilla Linux 2.6.22 qcow2 bare 0
293
294The ``details`` command
295-----------------------
296
297The ``details`` command displays detailed information about the *public* images
298available in Glance, as shown below::
299
300 $> glance details
301 Found 4 public images...
302 ================================================================================
303 URI: http://example.com/images/1
304 Id: 1
305 Public? Yes
306 Name: Ubuntu 10.10
307 Size: 58520278
308 Location: file:///tmp/images/1
309 Disk format: vhd
310 Container format: ovf
311 Property 'distro_version': 10.10
312 Property 'distro': Ubuntu
313 ================================================================================
314 URI: http://example.com/images/2
315 Id: 2
316 Public? Yes
317 Name: Ubuntu 10.04
318 Size: 58520278
319 Location: file:///tmp/images/2
320 Disk format: ami
321 Container format: ami
322 Property 'distro_version': 10.04
323 Property 'distro': Ubuntu
324 ================================================================================
325 URI: http://example.com/images/3
326 Id: 3
327 Public? Yes
328 Name: Fedora 9
329 Size: 3040
330 Location: file:///tmp/images/3
331 Disk format: vdi
332 Container format: bare
333 Property 'distro_version': 9
334 Property 'distro': Fedora
335 ================================================================================
336 URI: http://example.com/images/4
337 Id: 4
338 Public? Yes
339 Name: Vanilla Linux 2.6.22
340 Size: 0
341 Location: http://example.com/images/vanilla.tar.gz
342 Disk format: qcow2
343 Container format: bare
344 ================================================================================
345
346The ``show`` command
347--------------------
348
349The ``show`` command displays detailed information about a specific image, specified
350with ``<ID>``, as shown below::
351
352 $> glance show 3
353 URI: http://example.com/images/3
354 Id: 3
355 Public? Yes
356 Name: Fedora 9
357 Size: 3040
358 Location: file:///tmp/images/3
359 Disk format: vdi
360 Container format: bare
361 Property 'distro_version': 9
362 Property 'distro': Fedora
363
364The ``clear`` command
365---------------------
366
367The ``clear`` command is an administrative command that deletes **ALL** images
368and all image metadata. Passing the ``--verbose`` command will print brief
369information about all the images that were deleted, as shown below::
370
371 $> glance --verbose clear
372 Deleting image 1 "Some web image" ... done
373 Deleting image 2 "Some other web image" ... done
374 Completed in 0.0328 sec.
0375
=== modified file 'doc/source/index.rst'
--- doc/source/index.rst 2011-02-25 14:55:26 +0000
+++ doc/source/index.rst 2011-03-09 13:40:45 +0000
@@ -62,6 +62,7 @@
62 installing62 installing
63 controllingservers63 controllingservers
64 configuring64 configuring
65 glance
65 glanceapi66 glanceapi
66 client67 client
6768
6869
=== modified file 'glance/client.py'
--- glance/client.py 2011-03-06 15:37:28 +0000
+++ glance/client.py 2011-03-09 13:40:45 +0000
@@ -247,10 +247,8 @@
247247
248 :retval The newly-stored image's metadata.248 :retval The newly-stored image's metadata.
249 """249 """
250 if image_meta is None:
251 image_meta = {}
252250
253 headers = utils.image_meta_to_http_headers(image_meta)251 headers = utils.image_meta_to_http_headers(image_meta or {})
254252
255 if image_data:253 if image_data:
256 body = image_data254 body = image_data
257255
=== modified file 'glance/common/utils.py'
--- glance/common/utils.py 2011-01-28 21:54:34 +0000
+++ glance/common/utils.py 2011-03-09 13:40:45 +0000
@@ -36,6 +36,37 @@
36TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ"36TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
3737
3838
39def int_from_bool_as_string(subject):
40 """
41 Interpret a string as a boolean and return either 1 or 0.
42
43 Any string value in:
44 ('True', 'true', 'On', 'on', '1')
45 is interpreted as a boolean True.
46
47 Useful for JSON-decoded stuff and config file parsing
48 """
49 return bool_from_string(subject) and 1 or 0
50
51
52def bool_from_string(subject):
53 """
54 Interpret a string as a boolean.
55
56 Any string value in:
57 ('True', 'true', 'On', 'on', '1')
58 is interpreted as a boolean True.
59
60 Useful for JSON-decoded stuff and config file parsing
61 """
62 if type(subject) == type(bool):
63 return subject
64 if hasattr(subject, 'startswith'): # str or unicode...
65 if subject.strip().lower() in ('true', 'on', '1'):
66 return True
67 return False
68
69
39def import_class(import_str):70def import_class(import_str):
40 """Returns a class from a string including module and class"""71 """Returns a class from a string including module and class"""
41 mod_str, _sep, class_str = import_str.rpartition('.')72 mod_str, _sep, class_str = import_str.rpartition('.')
4273
=== modified file 'glance/registry/__init__.py'
--- glance/registry/__init__.py 2011-01-28 21:54:34 +0000
+++ glance/registry/__init__.py 2011-03-09 13:40:45 +0000
@@ -1,7 +1,6 @@
1# vim: tabstop=4 shiftwidth=4 softtabstop=41# vim: tabstop=4 shiftwidth=4 softtabstop=4
22
3# Copyright 2010 United States Government as represented by the3# Copyright 2010-2011 OpenStack, LLC
4# Administrator of the National Aeronautics and Space Administration.
5# All Rights Reserved.4# All Rights Reserved.
6#5#
7# Licensed under the Apache License, Version 2.0 (the "License"); you may6# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -20,12 +19,17 @@
20Registry API19Registry API
21"""20"""
2221
22import logging
23
23from glance.registry import client24from glance.registry import client
2425
26logger = logging.getLogger('glance.registry')
27
2528
26def get_registry_client(options):29def get_registry_client(options):
27 return client.RegistryClient(options['registry_host'],30 host = options['registry_host']
28 int(options['registry_port']))31 port = int(options['registry_port'])
32 return client.RegistryClient(host, port)
2933
3034
31def get_images_list(options):35def get_images_list(options):
@@ -43,16 +47,51 @@
43 return c.get_image(image_id)47 return c.get_image(image_id)
4448
4549
46def add_image_metadata(options, image_data):50def add_image_metadata(options, image_meta):
47 c = get_registry_client(options)51 if options['debug']:
48 return c.add_image(image_data)52 logger.debug("Adding image metadata...")
4953 _debug_print_metadata(image_meta)
5054
51def update_image_metadata(options, image_id, image_data):55 c = get_registry_client(options)
52 c = get_registry_client(options)56 new_image_meta = c.add_image(image_meta)
53 return c.update_image(image_id, image_data)57
58 if options['debug']:
59 logger.debug("Returned image metadata from call to "
60 "RegistryClient.add_image():")
61 _debug_print_metadata(new_image_meta)
62
63 return new_image_meta
64
65
66def update_image_metadata(options, image_id, image_meta):
67 if options['debug']:
68 logger.debug("Updating image metadata for image %s...", image_id)
69 _debug_print_metadata(image_meta)
70
71 c = get_registry_client(options)
72 new_image_meta = c.update_image(image_id, image_meta)
73
74 if options['debug']:
75 logger.debug("Returned image metadata from call to "
76 "RegistryClient.update_image():")
77 _debug_print_metadata(new_image_meta)
78
79 return new_image_meta
5480
5581
56def delete_image_metadata(options, image_id):82def delete_image_metadata(options, image_id):
83 logger.debug("Deleting image metadata for image %s...", image_id)
57 c = get_registry_client(options)84 c = get_registry_client(options)
58 return c.delete_image(image_id)85 return c.delete_image(image_id)
86
87
88def _debug_print_metadata(image_meta):
89 data = image_meta.copy()
90 properties = data.pop('properties', None)
91 for key, value in sorted(data.items()):
92 logger.debug(" %(key)20s: %(value)s" % locals())
93 if properties:
94 logger.debug(" %d custom properties...",
95 len(properties))
96 for key, value in properties.items():
97 logger.debug(" %(key)20s: %(value)s" % locals())
5998
=== modified file 'glance/registry/db/api.py'
--- glance/registry/db/api.py 2011-03-05 17:04:43 +0000
+++ glance/registry/db/api.py 2011-03-09 13:40:45 +0000
@@ -24,6 +24,7 @@
2424
25from sqlalchemy import create_engine25from sqlalchemy import create_engine
26from sqlalchemy.ext.declarative import declarative_base26from sqlalchemy.ext.declarative import declarative_base
27from sqlalchemy.orm import exc
27from sqlalchemy.orm import joinedload28from sqlalchemy.orm import joinedload
28from sqlalchemy.orm import sessionmaker29from sqlalchemy.orm import sessionmaker
2930
@@ -126,8 +127,7 @@
126 filter_by(id=image_id).\127 filter_by(id=image_id).\
127 one()128 one()
128 except exc.NoResultFound:129 except exc.NoResultFound:
129 new_exc = exception.NotFound("No model for id %s" % image_id)130 raise exception.NotFound("No image found with ID %s" % image_id)
130 raise new_exc.__class__, new_exc, sys.exc_info()[2]
131131
132132
133def image_get_all_public(context):133def image_get_all_public(context):
134134
=== modified file 'glance/registry/server.py'
--- glance/registry/server.py 2011-02-25 14:55:26 +0000
+++ glance/registry/server.py 2011-03-09 13:40:45 +0000
@@ -55,6 +55,8 @@
55 images = db_api.image_get_all_public(None)55 images = db_api.image_get_all_public(None)
56 image_dicts = [dict(id=i['id'],56 image_dicts = [dict(id=i['id'],
57 name=i['name'],57 name=i['name'],
58 disk_format=i['disk_format'],
59 container_format=i['container_format'],
58 size=i['size']) for i in images]60 size=i['size']) for i in images]
59 return dict(images=image_dicts)61 return dict(images=image_dicts)
6062
@@ -144,6 +146,8 @@
144146
145 context = None147 context = None
146 try:148 try:
149 logger.debug("Updating image %(id)s with metadata: %(image_data)r"
150 % locals())
147 updated_image = db_api.image_update(context, id, image_data)151 updated_image = db_api.image_update(context, id, image_data)
148 return dict(image=make_image_dict(updated_image))152 return dict(image=make_image_dict(updated_image))
149 except exception.Invalid, e:153 except exception.Invalid, e:
150154
=== modified file 'glance/server.py'
--- glance/server.py 2011-03-06 15:41:29 +0000
+++ glance/server.py 2011-03-09 13:40:45 +0000
@@ -90,6 +90,8 @@
90 {'images': [90 {'images': [
91 {'id': <ID>,91 {'id': <ID>,
92 'name': <NAME>,92 'name': <NAME>,
93 'disk_format': <DISK_FORMAT>,
94 'container_format': <DISK_FORMAT>,
93 'size': <SIZE>}, ...95 'size': <SIZE>}, ...
94 ]}96 ]}
95 """97 """
@@ -390,7 +392,6 @@
390 image_meta = registry.update_image_metadata(self.options,392 image_meta = registry.update_image_metadata(self.options,
391 id,393 id,
392 new_image_meta)394 new_image_meta)
393
394 if has_body:395 if has_body:
395 self._upload_and_activate(req, image_meta)396 self._upload_and_activate(req, image_meta)
396397
397398
=== modified file 'glance/store/__init__.py'
--- glance/store/__init__.py 2011-02-02 16:40:57 +0000
+++ glance/store/__init__.py 2011-03-09 13:40:45 +0000
@@ -90,7 +90,8 @@
9090
91 backend_class = get_backend_class(scheme)91 backend_class = get_backend_class(scheme)
9292
93 return backend_class.delete(parsed_uri, **kwargs)93 if hasattr(backend_class, 'delete'):
94 return backend_class.delete(parsed_uri, **kwargs)
9495
9596
96def get_store_from_location(location):97def get_store_from_location(location):
9798
=== modified file 'glance/utils.py'
--- glance/utils.py 2011-03-08 20:12:41 +0000
+++ glance/utils.py 2011-03-09 13:40:45 +0000
@@ -30,15 +30,16 @@
30 """30 """
31 headers = {}31 headers = {}
32 for k, v in image_meta.items():32 for k, v in image_meta.items():
33 if v is None:
34 v = ''
33 if k == 'properties':35 if k == 'properties':
34 for pk, pv in v.items():36 for pk, pv in v.items():
35 if pv is None:37 if pv is None:
36 pv = ''38 pv = ''
37 headers["x-image-meta-property-%s"39 headers["x-image-meta-property-%s"
38 % pk.lower()] = unicode(pv)40 % pk.lower()] = unicode(pv)
39 if v is None:41 else:
40 v = ''42 headers["x-image-meta-%s" % k.lower()] = unicode(v)
41 headers["x-image-meta-%s" % k.lower()] = unicode(v)
42 return headers43 return headers
4344
4445
4546
=== modified file 'setup.py'
--- setup.py 2011-02-09 21:56:48 +0000
+++ setup.py 2011-03-09 13:40:45 +0000
@@ -85,7 +85,8 @@
85 'Programming Language :: Python :: 2.6',85 'Programming Language :: Python :: 2.6',
86 'Environment :: No Input/Output (Daemon)',86 'Environment :: No Input/Output (Daemon)',
87 ],87 ],
88 scripts=['bin/glance-api',88 scripts=['bin/glance',
89 'bin/glance-api',
89 'bin/glance-combined',90 'bin/glance-combined',
90 'bin/glance-control',91 'bin/glance-control',
91 'bin/glance-manage',92 'bin/glance-manage',
9293
=== modified file 'tests/stubs.py'
--- tests/stubs.py 2011-03-06 15:41:29 +0000
+++ tests/stubs.py 2011-03-09 13:40:45 +0000
@@ -38,6 +38,7 @@
3838
39FAKE_FILESYSTEM_ROOTDIR = os.path.join('/tmp', 'glance-tests')39FAKE_FILESYSTEM_ROOTDIR = os.path.join('/tmp', 'glance-tests')
40VERBOSE = False40VERBOSE = False
41DEBUG = False
4142
4243
43def stub_out_http_backend(stubs):44def stub_out_http_backend(stubs):
@@ -170,7 +171,8 @@
170 self.req.body = body171 self.req.body = body
171172
172 def getresponse(self):173 def getresponse(self):
173 options = {'sql_connection': 'sqlite://', 'verbose': VERBOSE}174 options = {'sql_connection': 'sqlite://', 'verbose': VERBOSE,
175 'debug': DEBUG}
174 res = self.req.get_response(rserver.API(options))176 res = self.req.get_response(rserver.API(options))
175177
176 # httplib.Response has a read() method...fake it out178 # httplib.Response has a read() method...fake it out
@@ -217,6 +219,7 @@
217219
218 def getresponse(self):220 def getresponse(self):
219 options = {'verbose': VERBOSE,221 options = {'verbose': VERBOSE,
222 'debug': DEBUG,
220 'registry_host': '0.0.0.0',223 'registry_host': '0.0.0.0',
221 'registry_port': '9191',224 'registry_port': '9191',
222 'default_store': 'file',225 'default_store': 'file',
223226
=== modified file 'tests/unit/test_api.py'
--- tests/unit/test_api.py 2011-03-06 15:37:28 +0000
+++ tests/unit/test_api.py 2011-03-09 13:40:45 +0000
@@ -26,6 +26,9 @@
26from glance.registry import server as rserver26from glance.registry import server as rserver
27from tests import stubs27from tests import stubs
2828
29VERBOSE = False
30DEBUG = False
31
2932
30class TestRegistryAPI(unittest.TestCase):33class TestRegistryAPI(unittest.TestCase):
31 def setUp(self):34 def setUp(self):
@@ -34,7 +37,8 @@
34 stubs.stub_out_registry_and_store_server(self.stubs)37 stubs.stub_out_registry_and_store_server(self.stubs)
35 stubs.stub_out_registry_db_image_api(self.stubs)38 stubs.stub_out_registry_db_image_api(self.stubs)
36 stubs.stub_out_filesystem_backend()39 stubs.stub_out_filesystem_backend()
37 self.api = rserver.API({})40 self.api = rserver.API({'verbose': VERBOSE,
41 'debug': DEBUG})
3842
39 def tearDown(self):43 def tearDown(self):
40 """Clear the test environment"""44 """Clear the test environment"""
@@ -332,7 +336,9 @@
332 stubs.stub_out_registry_and_store_server(self.stubs)336 stubs.stub_out_registry_and_store_server(self.stubs)
333 stubs.stub_out_registry_db_image_api(self.stubs)337 stubs.stub_out_registry_db_image_api(self.stubs)
334 stubs.stub_out_filesystem_backend()338 stubs.stub_out_filesystem_backend()
335 options = {'registry_host': '0.0.0.0',339 options = {'verbose': VERBOSE,
340 'debug': DEBUG,
341 'registry_host': '0.0.0.0',
336 'registry_port': '9191',342 'registry_port': '9191',
337 'sql_connection': 'sqlite://',343 'sql_connection': 'sqlite://',
338 'default_store': 'file',344 'default_store': 'file',

Subscribers

People subscribed via source and target branches