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
1=== added file 'bin/glance'
2--- bin/glance 1970-01-01 00:00:00 +0000
3+++ bin/glance 2011-03-09 13:40:45 +0000
4@@ -0,0 +1,572 @@
5+#!/usr/bin/env python
6+# vim: tabstop=4 shiftwidth=4 softtabstop=4
7+
8+# Copyright 2011 OpenStack, LLC
9+# All Rights Reserved.
10+#
11+# Licensed under the Apache License, Version 2.0 (the "License"); you may
12+# not use this file except in compliance with the License. You may obtain
13+# a copy of the License at
14+#
15+# http://www.apache.org/licenses/LICENSE-2.0
16+#
17+# Unless required by applicable law or agreed to in writing, software
18+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
19+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
20+# License for the specific language governing permissions and limitations
21+# under the License.
22+
23+"""
24+This is the administration program for Glance. It is simply a command-line
25+interface for adding, modifying, and retrieving information about the images
26+stored in one or more Glance nodes.
27+"""
28+
29+import optparse
30+import os
31+import re
32+import sys
33+import time
34+
35+# If ../glance/__init__.py exists, add ../ to Python search path, so that
36+# it will override what happens to be installed in /usr/(local/)lib/python...
37+possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
38+ os.pardir,
39+ os.pardir))
40+if os.path.exists(os.path.join(possible_topdir, 'glance', '__init__.py')):
41+ sys.path.insert(0, possible_topdir)
42+
43+from glance import client
44+from glance import version
45+from glance.common import exception
46+from glance.common import utils
47+
48+SUCCESS = 0
49+FAILURE = 1
50+
51+
52+def get_image_fields_from_args(args):
53+ """
54+ Validate the set of arguments passed as field name/value pairs
55+ and return them as a mapping.
56+ """
57+ fields = {}
58+ for arg in args:
59+ pieces = arg.strip(',').split('=')
60+ if len(pieces) != 2:
61+ msg = ("Arguments should be in the form of field=value. "
62+ "You specified %s." % arg)
63+ raise RuntimeError(msg)
64+ fields[pieces[0]] = pieces[1]
65+
66+ fields = dict([(k.lower().replace('-', '_'), v)
67+ for k, v in fields.items()])
68+ return fields
69+
70+
71+def print_image_formatted(client, image):
72+ """
73+ Formatted print of image metadata.
74+
75+ :param client: The Glance client object
76+ :param image: The image metadata
77+ """
78+ print "URI: %s://%s/images/%s" % (client.use_ssl and "https" or "http",
79+ client.host,
80+ image['id'])
81+ print "Id: %s" % image['id']
82+ print "Public: " + (image['is_public'] and "Yes" or "No")
83+ print "Name: %s" % image['name']
84+ print "Size: %d" % int(image['size'])
85+ print "Location: %s" % image['location']
86+ print "Disk format: %s" % image['disk_format']
87+ print "Container format: %s" % image['container_format']
88+ if len(image['properties']) > 0:
89+ for k, v in image['properties'].items():
90+ print "Property '%s': %s" % (k, v)
91+
92+
93+def image_add(options, args):
94+ """
95+%(prog)s add [options] <field1=value1 field2=value2 ...> [ < /path/to/image ]
96+
97+Adds a new image to Glance. Specify metadata fields as arguments.
98+
99+SPECIFYING IMAGE METADATA
100+===============================================================================
101+
102+All field/value pairs are converted into a mapping that is passed
103+to Glance that represents the metadata for an image.
104+
105+Field names of note:
106+
107+id Optional. If not specified, an image identifier will be
108+ automatically assigned.
109+name Required. A name for the image.
110+size Optional. Should be size in bytes of the image if
111+ specified.
112+is_public Optional. If specified, interpreted as a boolean value
113+ and sets or unsets the image's availability to the public.
114+ The default value is False.
115+disk_format Optional. Possible values are 'vhd','vmdk','raw', 'qcow2',
116+ and 'ami'. Default value is 'raw'.
117+container_format Optional. Possible values are 'ovf' and 'ami'.
118+ Default value is 'ovf'.
119+location Optional. When specified, should be a readable location
120+ in the form of a URI: $STORE://LOCATION. For example, if
121+ the image data is stored in a file on the local
122+ filesystem at /usr/share/images/some.image.tar.gz
123+ you would specify:
124+ location=file:///usr/share/images/some.image.tar.gz
125+
126+Any other field names are considered to be custom properties so be careful
127+to spell field names correctly. :)
128+
129+STREAMING IMAGE DATA
130+===============================================================================
131+
132+If the location field is not specified, you can stream an image file on
133+the command line using standard redirection. For example:
134+
135+%(prog)s add name="Ubuntu 10.04 LTS 5GB" < /tmp/images/myimage.tar.gz
136+
137+EXAMPLES
138+===============================================================================
139+
140+%(prog)s add name="My Image" disk_format=raw container_format=ovf \\
141+ location=http://images.ubuntu.org/images/lucid-10.04-i686.iso \\
142+ distro="Ubuntu 10.04 LTS"
143+
144+%(prog)s add name="My Image" distro="Ubuntu 10.04 LTS" < /tmp/myimage.iso"""
145+ c = get_client(options)
146+
147+ try:
148+ fields = get_image_fields_from_args(args)
149+ except RuntimeError, e:
150+ print e
151+ return FAILURE
152+
153+ if 'name' not in fields.keys():
154+ print "Please specify a name for the image using name=VALUE"
155+ return FAILURE
156+
157+ image_meta = {'name': fields.pop('name'),
158+ 'is_public': utils.bool_from_string(
159+ fields.pop('is_public', False)),
160+ 'disk_format': fields.pop('disk_format', 'raw'),
161+ 'container_format': fields.pop('container_format', 'ovf')}
162+
163+ # Strip any args that are not supported
164+ unsupported_fields = ['status']
165+ for field in unsupported_fields:
166+ if field in fields.keys():
167+ print 'Found non-settable field %s. Removing.' % field
168+ fields.pop(field)
169+
170+ if 'location' in fields.keys():
171+ image_meta['location'] = fields.pop('location')
172+
173+ # We need either a location or image data/stream to add...
174+ image_location = image_meta.get('location')
175+ image_data = None
176+ if not image_location:
177+ # Grab the image data stream from stdin or redirect,
178+ # otherwise error out
179+ image_data = sys.stdin
180+ else:
181+ # Ensure no image data has been given
182+ if not sys.stdin.isatty():
183+ print "Either supply a location=LOCATION argument or supply image "
184+ print "data via a redirect. You have supplied BOTH image data "
185+ print "AND a location."
186+ return FAILURE
187+
188+ # Add custom attributes, which are all the arguments remaining
189+ image_meta['properties'] = fields
190+
191+ if not options.dry_run:
192+ try:
193+ image_meta = c.add_image(image_meta, image_data)
194+ image_id = image_meta['id']
195+ print "Added new image with ID: %s" % image_id
196+ if options.verbose:
197+ print "Returned the following metadata for the new image:"
198+ for k, v in sorted(image_meta.items()):
199+ print " %(k)30s => %(v)s" % locals()
200+ except client.ClientConnectionError, e:
201+ host = options.host
202+ port = options.port
203+ print ("Failed to connect to the Glance API server "
204+ "%(host)s:%(port)d. Is the server running?" % locals())
205+ if options.verbose:
206+ pieces = str(e).split('\n')
207+ for piece in pieces:
208+ print piece
209+ return FAILURE
210+ except Exception, e:
211+ print "Failed to add image. Got error:"
212+ pieces = str(e).split('\n')
213+ for piece in pieces:
214+ print piece
215+ return FAILURE
216+ else:
217+ print "Dry run. We would have done the following:"
218+ print "Add new image with metadata:"
219+ for k, v in sorted(image_meta.items()):
220+ print " %(k)30s => %(v)s" % locals()
221+
222+ return SUCCESS
223+
224+
225+def image_update(options, args):
226+ """
227+%(prog)s update [options] <ID> <field1=value1 field2=value2 ...>
228+
229+Updates an image's metadata in Glance. Specify metadata fields as arguments.
230+
231+All field/value pairs are converted into a mapping that is passed
232+to Glance that represents the metadata for an image.
233+
234+Field names that can be specified:
235+
236+name A name for the image.
237+is_public If specified, interpreted as a boolean value
238+ and sets or unsets the image's availability to the public.
239+disk_format Format of the disk image
240+container_format Format of the container
241+
242+All other field names are considered to be custom properties so be careful
243+to spell field names correctly. :)"""
244+ c = get_client(options)
245+ try:
246+ image_id = args.pop(0)
247+ except IndexError:
248+ print "Please specify the ID of the image you wish to update "
249+ print "as the first argument"
250+ return FAILURE
251+
252+ try:
253+ fields = get_image_fields_from_args(args)
254+ except RuntimeError, e:
255+ print e
256+ return FAILURE
257+
258+ image_meta = {}
259+
260+ # Strip any args that are not supported
261+ nonmodifiable_fields = ['created_on', 'deleted_on', 'deleted',
262+ 'updated_on', 'size', 'status']
263+ for field in nonmodifiable_fields:
264+ if field in fields.keys():
265+ print 'Found non-modifiable field %s. Removing.' % field
266+ fields.pop(field)
267+
268+ base_image_fields = ['disk_format', 'container_format',
269+ 'location']
270+ for field in base_image_fields:
271+ fvalue = fields.pop(field, None)
272+ if fvalue:
273+ image_meta[field] = fvalue
274+
275+ # Have to handle "boolean" values specially...
276+ if 'is_public' in fields:
277+ image_meta['is_public'] = utils.int_from_bool_as_string(
278+ fields.pop('is_public'))
279+
280+ # Add custom attributes, which are all the arguments remaining
281+ image_meta['properties'] = fields
282+
283+ if not options.dry_run:
284+ try:
285+ image_meta = c.update_image(image_id, image_meta=image_meta)
286+ print "Updated image %s" % image_id
287+
288+ if options.verbose:
289+ print "Updated image metadata for image %s:" % image_id
290+ print_image_formatted(c, image_meta)
291+ except exception.NotFound:
292+ print "No image with ID %s was found" % image_id
293+ return FAILURE
294+ except Exception, e:
295+ print "Failed to update image. Got error:"
296+ pieces = str(e).split('\n')
297+ for piece in pieces:
298+ print piece
299+ return FAILURE
300+ else:
301+ print "Dry run. We would have done the following:"
302+ print "Update existing image with metadata:"
303+ for k, v in sorted(image_meta.items()):
304+ print " %(k)30s => %(v)s" % locals()
305+ return SUCCESS
306+
307+
308+def image_delete(options, args):
309+ """
310+%(prog)s delete [options] <ID>
311+
312+Deletes an image from Glance"""
313+ c = get_client(options)
314+ try:
315+ image_id = args.pop()
316+ except IndexError:
317+ print "Please specify the ID of the image you wish to delete "
318+ print "as the first argument"
319+ return FAILURE
320+
321+ try:
322+ c.delete_image(image_id)
323+ print "Deleted image %s" % image_id
324+ return SUCCESS
325+ except exception.NotFound:
326+ print "No image with ID %s was found" % image_id
327+ return FAILURE
328+
329+
330+def image_show(options, args):
331+ """
332+%(prog)s show [options] <ID>
333+
334+Shows image metadata for an image in Glance"""
335+ c = get_client(options)
336+ try:
337+ if len(args) > 0:
338+ image_id = args[0]
339+ else:
340+ print "Please specify the image identifier as the "
341+ print "first argument. Example: "
342+ print "$> glance-admin show 12345"
343+ return FAILURE
344+
345+ image = c.get_image_meta(image_id)
346+ print_image_formatted(c, image)
347+ return SUCCESS
348+ except exception.NotFound:
349+ print "No image with ID %s was found" % image_id
350+ return FAILURE
351+ except Exception, e:
352+ print "Failed to show image. Got error:"
353+ pieces = str(e).split('\n')
354+ for piece in pieces:
355+ print piece
356+ return FAILURE
357+
358+
359+def images_index(options, args):
360+ """
361+%(prog)s index [options]
362+
363+Returns basic information for all public images
364+a Glance server knows about"""
365+ c = get_client(options)
366+ try:
367+ images = c.get_images()
368+ if len(images) == 0:
369+ print "No public images found."
370+ return SUCCESS
371+
372+ print "Found %d public images..." % len(images)
373+ print "%-16s %-30s %-20s %-20s %-14s" % (("ID"),
374+ ("Name"),
375+ ("Disk Format"),
376+ ("Container Format"),
377+ ("Size"))
378+ print ('-' * 16) + " " + ('-' * 30) + " "\
379+ + ('-' * 20) + " " + ('-' * 20) + " " + ('-' * 14)
380+ for image in images:
381+ print "%-16s %-30s %-20s %-20s %14d" % (image['id'],
382+ image['name'],
383+ image['disk_format'],
384+ image['container_format'],
385+ int(image['size']))
386+ return SUCCESS
387+ except Exception, e:
388+ print "Failed to show index. Got error:"
389+ pieces = str(e).split('\n')
390+ for piece in pieces:
391+ print piece
392+ return FAILURE
393+
394+
395+def images_detailed(options, args):
396+ """
397+%(prog)s details [options]
398+
399+Returns detailed information for all public images
400+a Glance server knows about"""
401+ c = get_client(options)
402+ try:
403+ images = c.get_images_detailed()
404+ if len(images) == 0:
405+ print "No public images found."
406+ return SUCCESS
407+
408+ num_images = len(images)
409+ print "Found %d public images..." % num_images
410+ cur_image = 1
411+ for image in images:
412+ print "=" * 80
413+ print_image_formatted(c, image)
414+ if cur_image == num_images:
415+ print "=" * 80
416+ cur_image += 1
417+
418+ return SUCCESS
419+ except Exception, e:
420+ print "Failed to show details. Got error:"
421+ pieces = str(e).split('\n')
422+ for piece in pieces:
423+ print piece
424+ return FAILURE
425+
426+
427+def images_clear(options, args):
428+ """
429+%(prog)s clear [options]
430+
431+Deletes all images from a Glance server"""
432+ c = get_client(options)
433+ images = c.get_images()
434+ for image in images:
435+ if options.verbose:
436+ print 'Deleting image %s "%s" ...' % (image['id'], image['name']),
437+ try:
438+ c.delete_image(image['id'])
439+ if options.verbose:
440+ print 'done'
441+ except Exception, e:
442+ print 'Failed to delete image %s' % image['id']
443+ print e
444+ return FAILURE
445+ return SUCCESS
446+
447+
448+def get_client(options):
449+ """
450+ Returns a new client object to a Glance server
451+ specified by the --host and --port options
452+ supplied to the CLI
453+ """
454+ return client.Client(host=options.host,
455+ port=options.port)
456+
457+
458+def create_options(parser):
459+ """
460+ Sets up the CLI and config-file options that may be
461+ parsed and program commands.
462+
463+ :param parser: The option parser
464+ """
465+ parser.add_option('-v', '--verbose', default=False, action="store_true",
466+ help="Print more verbose output")
467+ parser.add_option('-H', '--host', metavar="ADDRESS", default="0.0.0.0",
468+ help="Address of Glance API host. "
469+ "Default: %default")
470+ parser.add_option('-p', '--port', dest="port", metavar="PORT",
471+ type=int, default=9292,
472+ help="Port the Glance API host listens on. "
473+ "Default: %default")
474+ parser.add_option('--dry-run', default=False, action="store_true",
475+ help="Don't actually execute the command, just print "
476+ "output showing what WOULD happen.")
477+
478+
479+def parse_options(parser, cli_args):
480+ """
481+ Returns the parsed CLI options, command to run and its arguments, merged
482+ with any same-named options found in a configuration file
483+
484+ :param parser: The option parser
485+ """
486+ COMMANDS = {'help': print_help,
487+ 'add': image_add,
488+ 'update': image_update,
489+ 'delete': image_delete,
490+ 'index': images_index,
491+ 'details': images_detailed,
492+ 'show': image_show,
493+ 'clear': images_clear}
494+
495+ if not cli_args:
496+ cli_args.append('-h') # Show options in usage output...
497+
498+ (options, args) = parser.parse_args(cli_args)
499+
500+ if not args:
501+ parser.print_usage()
502+ sys.exit(0)
503+ else:
504+ command_name = args.pop(0)
505+ if command_name not in COMMANDS.keys():
506+ sys.exit("Unknown command: %s" % command_name)
507+ command = COMMANDS[command_name]
508+
509+ return (options, command, args)
510+
511+
512+def print_help(options, args):
513+ """
514+ Print help specific to a command
515+ """
516+ COMMANDS = {'add': image_add,
517+ 'update': image_update,
518+ 'delete': image_delete,
519+ 'index': images_index,
520+ 'details': images_detailed,
521+ 'show': image_show,
522+ 'clear': images_clear}
523+
524+ if len(args) != 1:
525+ sys.exit("Please specify a command")
526+
527+ command = args.pop()
528+ if command not in COMMANDS.keys():
529+ parser.print_usage()
530+ if args:
531+ sys.exit("Unknown command: %s" % command)
532+
533+ print COMMANDS[command].__doc__ % {'prog': os.path.basename(sys.argv[0])}
534+
535+
536+if __name__ == '__main__':
537+ usage = """
538+%prog <command> [options] [args]
539+
540+Commands:
541+
542+ help <command> Output help for one of the commands below
543+
544+ add Adds a new image to Glance
545+
546+ update Updates an image's metadata in Glance
547+
548+ delete Deletes an image from Glance
549+
550+ index Return brief information about images in Glance
551+
552+ details Return detailed information about images in
553+ Glance
554+
555+ show Show detailed information about an image in
556+ Glance
557+
558+ clear Removes all images and metadata from Glance
559+
560+"""
561+
562+ oparser = optparse.OptionParser(version='%%prog %s'
563+ % version.version_string(),
564+ usage=usage.strip())
565+ create_options(oparser)
566+ (options, command, args) = parse_options(oparser, sys.argv[1:])
567+
568+ try:
569+ start_time = time.time()
570+ result = command(options, args)
571+ end_time = time.time()
572+ if options.verbose:
573+ print "Completed in %-0.4f sec." % (end_time - start_time)
574+ sys.exit(result)
575+ except (RuntimeError, NotImplementedError), e:
576+ print "ERROR: ", e
577
578=== added file 'doc/source/glance.rst'
579--- doc/source/glance.rst 1970-01-01 00:00:00 +0000
580+++ doc/source/glance.rst 2011-03-09 13:40:45 +0000
581@@ -0,0 +1,374 @@
582+..
583+ Copyright 2011 OpenStack, LLC
584+ All Rights Reserved.
585+
586+ Licensed under the Apache License, Version 2.0 (the "License"); you may
587+ not use this file except in compliance with the License. You may obtain
588+ a copy of the License at
589+
590+ http://www.apache.org/licenses/LICENSE-2.0
591+
592+ Unless required by applicable law or agreed to in writing, software
593+ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
594+ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
595+ License for the specific language governing permissions and limitations
596+ under the License.
597+
598+Using the Glance CLI Tool
599+=========================
600+
601+Glance ships with a command-line tool for quering and managing Glance
602+It has a fairly simple but powerful interface of the form::
603+
604+ Usage: glance <command> [options] [args]
605+
606+Where ``<command>`` is one of the following:
607+
608+* help
609+
610+ Show detailed help information about a specific command
611+
612+* add
613+
614+ Adds an image to Glance
615+
616+* update
617+
618+ Updates an image's stored metadata in Glance
619+
620+* delete
621+
622+ Deletes an image and its metadata from Glance
623+
624+* index
625+
626+ Lists brief information about *public* images that Glance knows about
627+
628+* details
629+
630+ Lists detailed information about *public* images that Glance knows about
631+
632+* show
633+
634+ Lists detailed information about a specific image
635+
636+* clear
637+
638+ Destroys *all* images and their associated metadata
639+
640+This document describes how to use the ``glance`` tool for each of
641+the above commands.
642+
643+The ``help`` command
644+--------------------
645+
646+Issuing the ``help`` command with a ``<COMMAND>`` argument shows detailed help
647+about a specific command. Running ``glance`` without any arguments shows
648+a brief help message, like so::
649+
650+ $> glance
651+ Usage: glance <command> [options] [args]
652+
653+ Commands:
654+
655+ help <command> Output help for one of the commands below
656+
657+ add Adds a new image to Glance
658+
659+ update Updates an image's metadata in Glance
660+
661+ delete Deletes an image from Glance
662+
663+ index Return brief information about images in Glance
664+
665+ details Return detailed information about images in
666+ Glance
667+
668+ show Show detailed information about an image in
669+ Glance
670+
671+ clear Removes all images and metadata from Glance
672+
673+ Options:
674+ --version show program's version number and exit
675+ -h, --help show this help message and exit
676+ -v, --verbose Print more verbose output
677+ -H ADDRESS, --host=ADDRESS
678+ Address of Glance API host. Default: example.com
679+ -p PORT, --port=PORT Port the Glance API host listens on. Default: 9292
680+ --dry-run Don't actually execute the command, just print output
681+ showing what WOULD happen.
682+
683+With a ``<COMMAND>`` argument, more information on the command is shown,
684+like so::
685+
686+ $> glance help update
687+
688+ glance update [options] <ID> <field1=value1 field2=value2 ...>
689+
690+ Updates an image's metadata in Glance. Specify metadata fields as arguments.
691+
692+ All field/value pairs are converted into a mapping that is passed
693+ to Glance that represents the metadata for an image.
694+
695+ Field names that can be specified:
696+
697+ name A name for the image.
698+ is_public If specified, interpreted as a boolean value
699+ and sets or unsets the image's availability to the public.
700+ disk_format Format of the disk image
701+ container_format Format of the container
702+
703+ All other field names are considered to be custom properties so be careful
704+ to spell field names correctly. :)
705+
706+The ``add`` command
707+-------------------
708+
709+The ``add`` command is used to do both of the following:
710+
711+* Store virtual machine image data and metadata about that image in Glance
712+
713+* Let Glance know about an existing virtual machine image that may be stored
714+ somewhere else
715+
716+We cover both use cases below.
717+
718+Store virtual machine image data and metadata
719+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
720+
721+When adding an actual virtual machine image to Glance, you use the ``add``
722+command. You will pass metadata about the VM image on the command line, and
723+you will use a standard shell redirect to stream the image data file to
724+``glance``.
725+
726+Let's walk through a simple example. Suppose we have an image stored on our
727+local filesystem that we wish to "upload" to Glance. This image is stored
728+on our local filesystem in ``/tmp/images/myimage.tar.gz``.
729+
730+We'd also like to tell Glance that this image should be called "My Image", and
731+that the image should be public -- anyone should be able to fetch it.
732+
733+Here is how we'd upload this image to Glance::
734+
735+ $> glance add name="My Image" is_public=true < /tmp/images/myimage.tar.gz
736+
737+If Glance was able to successfully upload and store your VM image data and
738+metadata attributes, you would see something like this::
739+
740+ $> glance add name="My Image" is_public=true < /tmp/images/myimage.tar.gz
741+ Added new image with ID: 2
742+
743+You can use the ``--verbose`` (or ``-v``) command-line option to print some more
744+information about the metadata that was saved with the image::
745+
746+ $> glance --verbose add name="My Image" is_public=true < /tmp/images/myimage.tar.gz
747+ Added new image with ID: 4
748+ Returned the following metadata for the new image:
749+ container_format => ovf
750+ created_at => 2011-02-22T19:20:53.298556
751+ deleted => False
752+ deleted_at => None
753+ disk_format => raw
754+ id => 4
755+ is_public => True
756+ location => file:///tmp/images/4
757+ name => My Image
758+ properties => {}
759+ size => 58520278
760+ status => active
761+ updated_at => None
762+ Completed in 0.6141 sec.
763+
764+If you are unsure about what will be added, you can use the ``--dry-run``
765+command-line option, which will simply show you what *would* have happened::
766+
767+ $> glance --dry-run add name="Foo" distro="Ubuntu" is_publi=True < /tmp/images/myimage.tar.gz
768+ Dry run. We would have done the following:
769+ Add new image with metadata:
770+ container_format => ovf
771+ disk_format => raw
772+ is_public => False
773+ name => Foo
774+ properties => {'is_publi': 'True', 'distro': 'Ubuntu'}
775+
776+This is useful for detecting problems and for seeing what the default field
777+values supplied by ``glance`` are. For instance, there was a typo in
778+the command above (the ``is_public`` field was incorrectly spelled ``is_publi``
779+which resulted in the image having an ``is_publi`` custom property added to
780+the image and the *real* ``is_public`` field value being `False` (the default)
781+and not `True`...
782+
783+Register a virtual machine image in another location
784+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
785+
786+Sometimes, you already have stored the virtual machine image in some non-Glance
787+location -- perhaps even a location you have no write access to -- and you want
788+to tell Glance where this virtual machine image is located and some metadata
789+about it. The ``add`` command can do this for you.
790+
791+When registering an image in this way, the only difference is that you do not
792+use a shell redirect to stream a virtual machine image file into Glance, but
793+instead, you tell Glance where to find the existing virtual machine image by
794+setting the ``location`` field. Below is an example of doing this.
795+
796+Let's assume that there is a virtual machine image located at the URL
797+``http://example.com/images/myimage.tar.gz``. We can register this image with
798+Glance using the following::
799+
800+ $> glance --verbose add name="Some web image" location="http://example.com/images/myimage.tar.gz"
801+ Added new image with ID: 1
802+ Returned the following metadata for the new image:
803+ container_format => ovf
804+ created_at => 2011-02-23T00:42:04.688890
805+ deleted => False
806+ deleted_at => None
807+ disk_format => vhd
808+ id => 1
809+ is_public => True
810+ location => http://example.com/images/myimage.tar.gz
811+ name => Some web image
812+ properties => {}
813+ size => 0
814+ status => active
815+ updated_at => None
816+ Completed in 0.0356 sec.
817+
818+The ``update`` command
819+----------------------
820+
821+After uploading/adding a virtual machine image to Glance, it is not possible to
822+modify the actual virtual machine image -- images are read-only after all --
823+however, it *is* possible to update any metadata about the image after you add
824+it to Glance.
825+
826+The ``update`` command allows you to update the metadata fields of a stored
827+image. You use this command like so::
828+
829+ glance update <ID> [field1=value1 field2=value2 ...]
830+
831+Let's say we have an image with identifier 5 that we wish to change the is_public
832+attribute of the image from False to True. The following would accomplish this::
833+
834+ $> glance update 5 is_public=true
835+ Updated image 5
836+
837+Using the ``--verbose`` flag will show you all the updated data about the image::
838+
839+ $> glance --verbose update 5 is_public=true
840+ Updated image 5
841+ Updated image metadata for image 5:
842+ URI: http://example.com/images/5
843+ Id: 5
844+ Public? Yes
845+ Name: My Image
846+ Size: 58520278
847+ Location: file:///tmp/images/5
848+ Disk format: raw
849+ Container format: ovf
850+ Completed in 0.0596 sec.
851+
852+The ``delete`` command
853+----------------------
854+
855+You can delete an image by using the ``delete`` command, shown below::
856+
857+ $> glance --verbose delete 5
858+ Deleted image 5
859+
860+The ``index`` command
861+---------------------
862+
863+The ``index`` command displays brief information about the *public* images
864+available in Glance, as shown below::
865+
866+ $> glance index
867+ Found 4 public images...
868+ ID Name Disk Format Container Format Size
869+ ---------------- ------------------------------ -------------------- -------------------- --------------
870+ 1 Ubuntu 10.10 vhd ovf 58520278
871+ 2 Ubuntu 10.04 ami ami 58520278
872+ 3 Fedora 9 vdi bare 3040
873+ 4 Vanilla Linux 2.6.22 qcow2 bare 0
874+
875+The ``details`` command
876+-----------------------
877+
878+The ``details`` command displays detailed information about the *public* images
879+available in Glance, as shown below::
880+
881+ $> glance details
882+ Found 4 public images...
883+ ================================================================================
884+ URI: http://example.com/images/1
885+ Id: 1
886+ Public? Yes
887+ Name: Ubuntu 10.10
888+ Size: 58520278
889+ Location: file:///tmp/images/1
890+ Disk format: vhd
891+ Container format: ovf
892+ Property 'distro_version': 10.10
893+ Property 'distro': Ubuntu
894+ ================================================================================
895+ URI: http://example.com/images/2
896+ Id: 2
897+ Public? Yes
898+ Name: Ubuntu 10.04
899+ Size: 58520278
900+ Location: file:///tmp/images/2
901+ Disk format: ami
902+ Container format: ami
903+ Property 'distro_version': 10.04
904+ Property 'distro': Ubuntu
905+ ================================================================================
906+ URI: http://example.com/images/3
907+ Id: 3
908+ Public? Yes
909+ Name: Fedora 9
910+ Size: 3040
911+ Location: file:///tmp/images/3
912+ Disk format: vdi
913+ Container format: bare
914+ Property 'distro_version': 9
915+ Property 'distro': Fedora
916+ ================================================================================
917+ URI: http://example.com/images/4
918+ Id: 4
919+ Public? Yes
920+ Name: Vanilla Linux 2.6.22
921+ Size: 0
922+ Location: http://example.com/images/vanilla.tar.gz
923+ Disk format: qcow2
924+ Container format: bare
925+ ================================================================================
926+
927+The ``show`` command
928+--------------------
929+
930+The ``show`` command displays detailed information about a specific image, specified
931+with ``<ID>``, as shown below::
932+
933+ $> glance show 3
934+ URI: http://example.com/images/3
935+ Id: 3
936+ Public? Yes
937+ Name: Fedora 9
938+ Size: 3040
939+ Location: file:///tmp/images/3
940+ Disk format: vdi
941+ Container format: bare
942+ Property 'distro_version': 9
943+ Property 'distro': Fedora
944+
945+The ``clear`` command
946+---------------------
947+
948+The ``clear`` command is an administrative command that deletes **ALL** images
949+and all image metadata. Passing the ``--verbose`` command will print brief
950+information about all the images that were deleted, as shown below::
951+
952+ $> glance --verbose clear
953+ Deleting image 1 "Some web image" ... done
954+ Deleting image 2 "Some other web image" ... done
955+ Completed in 0.0328 sec.
956
957=== modified file 'doc/source/index.rst'
958--- doc/source/index.rst 2011-02-25 14:55:26 +0000
959+++ doc/source/index.rst 2011-03-09 13:40:45 +0000
960@@ -62,6 +62,7 @@
961 installing
962 controllingservers
963 configuring
964+ glance
965 glanceapi
966 client
967
968
969=== modified file 'glance/client.py'
970--- glance/client.py 2011-03-06 15:37:28 +0000
971+++ glance/client.py 2011-03-09 13:40:45 +0000
972@@ -247,10 +247,8 @@
973
974 :retval The newly-stored image's metadata.
975 """
976- if image_meta is None:
977- image_meta = {}
978
979- headers = utils.image_meta_to_http_headers(image_meta)
980+ headers = utils.image_meta_to_http_headers(image_meta or {})
981
982 if image_data:
983 body = image_data
984
985=== modified file 'glance/common/utils.py'
986--- glance/common/utils.py 2011-01-28 21:54:34 +0000
987+++ glance/common/utils.py 2011-03-09 13:40:45 +0000
988@@ -36,6 +36,37 @@
989 TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
990
991
992+def int_from_bool_as_string(subject):
993+ """
994+ Interpret a string as a boolean and return either 1 or 0.
995+
996+ Any string value in:
997+ ('True', 'true', 'On', 'on', '1')
998+ is interpreted as a boolean True.
999+
1000+ Useful for JSON-decoded stuff and config file parsing
1001+ """
1002+ return bool_from_string(subject) and 1 or 0
1003+
1004+
1005+def bool_from_string(subject):
1006+ """
1007+ Interpret a string as a boolean.
1008+
1009+ Any string value in:
1010+ ('True', 'true', 'On', 'on', '1')
1011+ is interpreted as a boolean True.
1012+
1013+ Useful for JSON-decoded stuff and config file parsing
1014+ """
1015+ if type(subject) == type(bool):
1016+ return subject
1017+ if hasattr(subject, 'startswith'): # str or unicode...
1018+ if subject.strip().lower() in ('true', 'on', '1'):
1019+ return True
1020+ return False
1021+
1022+
1023 def import_class(import_str):
1024 """Returns a class from a string including module and class"""
1025 mod_str, _sep, class_str = import_str.rpartition('.')
1026
1027=== modified file 'glance/registry/__init__.py'
1028--- glance/registry/__init__.py 2011-01-28 21:54:34 +0000
1029+++ glance/registry/__init__.py 2011-03-09 13:40:45 +0000
1030@@ -1,7 +1,6 @@
1031 # vim: tabstop=4 shiftwidth=4 softtabstop=4
1032
1033-# Copyright 2010 United States Government as represented by the
1034-# Administrator of the National Aeronautics and Space Administration.
1035+# Copyright 2010-2011 OpenStack, LLC
1036 # All Rights Reserved.
1037 #
1038 # Licensed under the Apache License, Version 2.0 (the "License"); you may
1039@@ -20,12 +19,17 @@
1040 Registry API
1041 """
1042
1043+import logging
1044+
1045 from glance.registry import client
1046
1047+logger = logging.getLogger('glance.registry')
1048+
1049
1050 def get_registry_client(options):
1051- return client.RegistryClient(options['registry_host'],
1052- int(options['registry_port']))
1053+ host = options['registry_host']
1054+ port = int(options['registry_port'])
1055+ return client.RegistryClient(host, port)
1056
1057
1058 def get_images_list(options):
1059@@ -43,16 +47,51 @@
1060 return c.get_image(image_id)
1061
1062
1063-def add_image_metadata(options, image_data):
1064- c = get_registry_client(options)
1065- return c.add_image(image_data)
1066-
1067-
1068-def update_image_metadata(options, image_id, image_data):
1069- c = get_registry_client(options)
1070- return c.update_image(image_id, image_data)
1071+def add_image_metadata(options, image_meta):
1072+ if options['debug']:
1073+ logger.debug("Adding image metadata...")
1074+ _debug_print_metadata(image_meta)
1075+
1076+ c = get_registry_client(options)
1077+ new_image_meta = c.add_image(image_meta)
1078+
1079+ if options['debug']:
1080+ logger.debug("Returned image metadata from call to "
1081+ "RegistryClient.add_image():")
1082+ _debug_print_metadata(new_image_meta)
1083+
1084+ return new_image_meta
1085+
1086+
1087+def update_image_metadata(options, image_id, image_meta):
1088+ if options['debug']:
1089+ logger.debug("Updating image metadata for image %s...", image_id)
1090+ _debug_print_metadata(image_meta)
1091+
1092+ c = get_registry_client(options)
1093+ new_image_meta = c.update_image(image_id, image_meta)
1094+
1095+ if options['debug']:
1096+ logger.debug("Returned image metadata from call to "
1097+ "RegistryClient.update_image():")
1098+ _debug_print_metadata(new_image_meta)
1099+
1100+ return new_image_meta
1101
1102
1103 def delete_image_metadata(options, image_id):
1104+ logger.debug("Deleting image metadata for image %s...", image_id)
1105 c = get_registry_client(options)
1106 return c.delete_image(image_id)
1107+
1108+
1109+def _debug_print_metadata(image_meta):
1110+ data = image_meta.copy()
1111+ properties = data.pop('properties', None)
1112+ for key, value in sorted(data.items()):
1113+ logger.debug(" %(key)20s: %(value)s" % locals())
1114+ if properties:
1115+ logger.debug(" %d custom properties...",
1116+ len(properties))
1117+ for key, value in properties.items():
1118+ logger.debug(" %(key)20s: %(value)s" % locals())
1119
1120=== modified file 'glance/registry/db/api.py'
1121--- glance/registry/db/api.py 2011-03-05 17:04:43 +0000
1122+++ glance/registry/db/api.py 2011-03-09 13:40:45 +0000
1123@@ -24,6 +24,7 @@
1124
1125 from sqlalchemy import create_engine
1126 from sqlalchemy.ext.declarative import declarative_base
1127+from sqlalchemy.orm import exc
1128 from sqlalchemy.orm import joinedload
1129 from sqlalchemy.orm import sessionmaker
1130
1131@@ -126,8 +127,7 @@
1132 filter_by(id=image_id).\
1133 one()
1134 except exc.NoResultFound:
1135- new_exc = exception.NotFound("No model for id %s" % image_id)
1136- raise new_exc.__class__, new_exc, sys.exc_info()[2]
1137+ raise exception.NotFound("No image found with ID %s" % image_id)
1138
1139
1140 def image_get_all_public(context):
1141
1142=== modified file 'glance/registry/server.py'
1143--- glance/registry/server.py 2011-02-25 14:55:26 +0000
1144+++ glance/registry/server.py 2011-03-09 13:40:45 +0000
1145@@ -55,6 +55,8 @@
1146 images = db_api.image_get_all_public(None)
1147 image_dicts = [dict(id=i['id'],
1148 name=i['name'],
1149+ disk_format=i['disk_format'],
1150+ container_format=i['container_format'],
1151 size=i['size']) for i in images]
1152 return dict(images=image_dicts)
1153
1154@@ -144,6 +146,8 @@
1155
1156 context = None
1157 try:
1158+ logger.debug("Updating image %(id)s with metadata: %(image_data)r"
1159+ % locals())
1160 updated_image = db_api.image_update(context, id, image_data)
1161 return dict(image=make_image_dict(updated_image))
1162 except exception.Invalid, e:
1163
1164=== modified file 'glance/server.py'
1165--- glance/server.py 2011-03-06 15:41:29 +0000
1166+++ glance/server.py 2011-03-09 13:40:45 +0000
1167@@ -90,6 +90,8 @@
1168 {'images': [
1169 {'id': <ID>,
1170 'name': <NAME>,
1171+ 'disk_format': <DISK_FORMAT>,
1172+ 'container_format': <DISK_FORMAT>,
1173 'size': <SIZE>}, ...
1174 ]}
1175 """
1176@@ -390,7 +392,6 @@
1177 image_meta = registry.update_image_metadata(self.options,
1178 id,
1179 new_image_meta)
1180-
1181 if has_body:
1182 self._upload_and_activate(req, image_meta)
1183
1184
1185=== modified file 'glance/store/__init__.py'
1186--- glance/store/__init__.py 2011-02-02 16:40:57 +0000
1187+++ glance/store/__init__.py 2011-03-09 13:40:45 +0000
1188@@ -90,7 +90,8 @@
1189
1190 backend_class = get_backend_class(scheme)
1191
1192- return backend_class.delete(parsed_uri, **kwargs)
1193+ if hasattr(backend_class, 'delete'):
1194+ return backend_class.delete(parsed_uri, **kwargs)
1195
1196
1197 def get_store_from_location(location):
1198
1199=== modified file 'glance/utils.py'
1200--- glance/utils.py 2011-03-08 20:12:41 +0000
1201+++ glance/utils.py 2011-03-09 13:40:45 +0000
1202@@ -30,15 +30,16 @@
1203 """
1204 headers = {}
1205 for k, v in image_meta.items():
1206+ if v is None:
1207+ v = ''
1208 if k == 'properties':
1209 for pk, pv in v.items():
1210 if pv is None:
1211 pv = ''
1212 headers["x-image-meta-property-%s"
1213 % pk.lower()] = unicode(pv)
1214- if v is None:
1215- v = ''
1216- headers["x-image-meta-%s" % k.lower()] = unicode(v)
1217+ else:
1218+ headers["x-image-meta-%s" % k.lower()] = unicode(v)
1219 return headers
1220
1221
1222
1223=== modified file 'setup.py'
1224--- setup.py 2011-02-09 21:56:48 +0000
1225+++ setup.py 2011-03-09 13:40:45 +0000
1226@@ -85,7 +85,8 @@
1227 'Programming Language :: Python :: 2.6',
1228 'Environment :: No Input/Output (Daemon)',
1229 ],
1230- scripts=['bin/glance-api',
1231+ scripts=['bin/glance',
1232+ 'bin/glance-api',
1233 'bin/glance-combined',
1234 'bin/glance-control',
1235 'bin/glance-manage',
1236
1237=== modified file 'tests/stubs.py'
1238--- tests/stubs.py 2011-03-06 15:41:29 +0000
1239+++ tests/stubs.py 2011-03-09 13:40:45 +0000
1240@@ -38,6 +38,7 @@
1241
1242 FAKE_FILESYSTEM_ROOTDIR = os.path.join('/tmp', 'glance-tests')
1243 VERBOSE = False
1244+DEBUG = False
1245
1246
1247 def stub_out_http_backend(stubs):
1248@@ -170,7 +171,8 @@
1249 self.req.body = body
1250
1251 def getresponse(self):
1252- options = {'sql_connection': 'sqlite://', 'verbose': VERBOSE}
1253+ options = {'sql_connection': 'sqlite://', 'verbose': VERBOSE,
1254+ 'debug': DEBUG}
1255 res = self.req.get_response(rserver.API(options))
1256
1257 # httplib.Response has a read() method...fake it out
1258@@ -217,6 +219,7 @@
1259
1260 def getresponse(self):
1261 options = {'verbose': VERBOSE,
1262+ 'debug': DEBUG,
1263 'registry_host': '0.0.0.0',
1264 'registry_port': '9191',
1265 'default_store': 'file',
1266
1267=== modified file 'tests/unit/test_api.py'
1268--- tests/unit/test_api.py 2011-03-06 15:37:28 +0000
1269+++ tests/unit/test_api.py 2011-03-09 13:40:45 +0000
1270@@ -26,6 +26,9 @@
1271 from glance.registry import server as rserver
1272 from tests import stubs
1273
1274+VERBOSE = False
1275+DEBUG = False
1276+
1277
1278 class TestRegistryAPI(unittest.TestCase):
1279 def setUp(self):
1280@@ -34,7 +37,8 @@
1281 stubs.stub_out_registry_and_store_server(self.stubs)
1282 stubs.stub_out_registry_db_image_api(self.stubs)
1283 stubs.stub_out_filesystem_backend()
1284- self.api = rserver.API({})
1285+ self.api = rserver.API({'verbose': VERBOSE,
1286+ 'debug': DEBUG})
1287
1288 def tearDown(self):
1289 """Clear the test environment"""
1290@@ -332,7 +336,9 @@
1291 stubs.stub_out_registry_and_store_server(self.stubs)
1292 stubs.stub_out_registry_db_image_api(self.stubs)
1293 stubs.stub_out_filesystem_backend()
1294- options = {'registry_host': '0.0.0.0',
1295+ options = {'verbose': VERBOSE,
1296+ 'debug': DEBUG,
1297+ 'registry_host': '0.0.0.0',
1298 'registry_port': '9191',
1299 'sql_connection': 'sqlite://',
1300 'default_store': 'file',

Subscribers

People subscribed via source and target branches