Adds an admin tool to Glance (bin/glance-admin) that allows

a user to administer the Glance 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.
This commit is contained in:
jaypipes@gmail.com 2011-02-23 21:50:24 -05:00
parent 43c8e2a2b6
commit 5c03271048
10 changed files with 896 additions and 17 deletions

539
bin/glance-admin Executable file

@ -0,0 +1,539 @@
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 OpenStack, LLC
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
This is the administration program for Glance. It is simply a command-line
interface for adding, modifying, and retrieving information about the images
stored in one or more Glance nodes.
"""
import optparse
import os
import re
import sys
import time
# If ../glance/__init__.py exists, add ../ to Python search path, so that
# it will override what happens to be installed in /usr/(local/)lib/python...
possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
os.pardir,
os.pardir))
if os.path.exists(os.path.join(possible_topdir, 'glance', '__init__.py')):
sys.path.insert(0, possible_topdir)
from glance import client
from glance import version
from glance.common import exception
from glance.common import utils
SUCCESS = 0
FAILURE = 1
def get_image_fields_from_args(args):
"""
Validate the set of arguments passed as field name/value pairs
and return them as a mapping.
"""
fields = {}
for arg in args:
pieces = arg.strip(',').split('=')
if len(pieces) != 2:
msg = ("Arguments should be in the form of field=value. "
"You specified %s." % arg)
raise RuntimeError(msg)
fields[pieces[0]] = pieces[1]
fields = dict([(k.lower().replace('-', '_'), v)
for k, v in fields.items()])
return fields
def print_image_formatted(client, image):
"""
Formatted print of image metadata.
:param client: The Glance client object
:param image: The image metadata
"""
print "URI: %s://%s/images/%s" % (client.use_ssl and "https" or "http",
client.host,
image['id'])
print "Id: %s" % image['id']
print "Public? " + (image['is_public'] and "Yes" or "No")
print "Name: %s" % image['name']
print "Size: %d" % int(image['size'])
print "Location: %s" % image['location']
print "Disk format: %s" % image['disk_format']
print "Container format: %s" % image['container_format']
if len(image['properties']) > 0:
print "Custom properties:",
cur_prop = 1
for k, v in image['properties'].items():
if cur_prop == 1:
print "%s=%s" % (k, v)
else:
print (' ' * 12) + "%s=%s" % (k, v)
cur_prop += 1
def image_add(options, args):
"""
%(prog)s add [options] <field1=value1 field2=value2 ...> [ < /path/to/image ]
Adds a new image to Glance. Specify metadata fields as arguments.
SPECIFYING IMAGE METADATA
===============================================================================
All field/value pairs are converted into a mapping that is passed
to Glance that represents the metadata for an image.
Field names of note:
id Optional. If not specified, an image identifier will be
automatically assigned.
name Required. A name for the image.
size Optional. Should be size in bytes of the image if
specified.
is_public Optional. If specified, interpreted as a boolean value
and sets or unsets the image's availability to the public.
The default value is False.
disk_format Optional. Possible values are 'vhd','vmdk','raw', 'qcow2',
and 'ami'. Default value is 'raw'.
container_format Optional. Possible values are 'ovf' and 'ami'.
Default value is 'ovf'.
location Optional. When specified, should be a readable location
in the form of a URI: $STORE://LOCATION. For example, if
the image data is stored in a file on the local
filesystem at /usr/share/images/some.image.tar.gz
you would specify:
location=file:///usr/share/images/some.image.tar.gz
Any other field names are considered to be custom properties so be careful
to spell field names correctly. :)
STREAMING IMAGE DATA
===============================================================================
If the location field is not specified, you can stream an image file on
the command line using standard redirection. For example:
%(prog)s add name="Ubuntu 10.04 LTS 5GB" < /tmp/images/myimage.tar.gz
EXAMPLES
===============================================================================
%(prog)s add name="My Image" disk_format=raw container_format=ovf \\
location=http://images.ubuntu.org/images/lucid-10.04-i686.iso \\
distro="Ubuntu 10.04 LTS"
%(prog)s add name="My Image" distro="Ubuntu 10.04 LTS" < /tmp/myimage.iso"""
c = get_client(options)
try:
fields = get_image_fields_from_args(args)
except RuntimeError, e:
print e
return FAILURE
if 'name' not in fields.keys():
print "Please specify a name for the image using name=VALUE"
return FAILURE
image_meta = {'name': fields.pop('name'),
'is_public': utils.bool_from_string(
fields.pop('is_public', False)),
'disk_format': fields.pop('disk_format', 'raw'),
'container_format': fields.pop('container_format', 'ovf')}
# Strip any args that are not supported
unsupported_fields = ['status']
for field in unsupported_fields:
if field in fields.keys():
print 'Found non-settable field %s. Removing.' % field
fields.pop(field)
if 'location' in fields.keys():
image_meta['location'] = fields.pop('location')
# We need either a location or image data/stream to add...
image_location = image_meta.get('location')
image_data = None
if not image_location:
# Grab the image data stream from stdin or redirect,
# otherwise error out
image_data = sys.stdin
else:
# Ensure no image data has been given
if not sys.stdin.isatty():
print "Either supply a location=LOCATION argument or supply image "
print "data via a redirect. You have supplied BOTH image data "
print "AND a location."
return FAILURE
# Add custom attributes, which are all the arguments remaining
image_meta['properties'] = fields
if not options.dry_run:
try:
new_image_meta = c.add_image(image_meta, image_data)
new_image_id = new_image_meta['id']
print "Added new image with ID: %s" % new_image_id
if options.verbose:
print "Returned the following metadata for the new image:"
for k, v in sorted(new_image_meta.items()):
print " %(k)30s => %(v)s" % locals()
except client.ClientConnectionError, e:
host = options.host
port = options.port
print ("Failed to connect to the Glance API server "
"%(host)s:%(port)d. Is the server running?" % locals())
if options.verbose:
pieces = str(e).split('\n')
for piece in pieces:
print piece
return FAILURE
except Exception, e:
print "Failed to add image. Got error:"
pieces = str(e).split('\n')
for piece in pieces:
print piece
return FAILURE
else:
print "Dry run. We would have done the following:"
print "Add new image with metadata:"
for k, v in sorted(image_meta.items()):
print " %(k)30s => %(v)s" % locals()
return SUCCESS
def image_update(options, args):
"""
%(prog)s update [options] <ID> <field1=value1 field2=value2 ...>
Updates an image's metadata in Glance. Specify metadata fields as arguments.
All field/value pairs are converted into a mapping that is passed
to Glance that represents the metadata for an image.
Field names that can be specified:
name A name for the image.
is_public If specified, interpreted as a boolean value
and sets or unsets the image's availability to the public.
disk_format Format of the disk image
container_format Format of the container
All other field names are considered to be custom properties so be careful
to spell field names correctly. :)"""
c = get_client(options)
try:
image_id = args.pop(0)
except IndexError:
print "Please specify the ID of the image you wish to update "
print "as the first argument"
return FAILURE
try:
fields = get_image_fields_from_args(args)
except RuntimeError, e:
print e
return FAILURE
image_meta = {}
# Strip any args that are not supported
nonmodifiable_fields = ['created_on', 'deleted_on', 'deleted',
'updated_on', 'size', 'status']
for field in nonmodifiable_fields:
if field in fields.keys():
print 'Found non-modifiable field %s. Removing.' % field
fields.pop(field)
base_image_fields = ['disk_format', 'container_format', 'is_public',
'location']
for field in base_image_fields:
fvalue = fields.pop(field, None)
if fvalue:
image_meta[field] = fvalue
# Add custom attributes, which are all the arguments remaining
image_meta['properties'] = fields
try:
c.update_image(image_id, image_meta=image_meta)
return SUCCESS
except exception.NotFound:
print "No image with ID %s was found" % image_id
return FAILURE
def image_delete(options, args):
"""
%(prog)s delete [options] <ID>
Deletes an image from Glance"""
c = get_client(options)
try:
image_id = args.pop()
except IndexError:
print "Please specify the ID of the image you wish to delete "
print "as the first argument"
return FAILURE
c.delete_image(image_id)
return SUCCESS
def image_show(options, args):
"""
%(prog)s show [options] <ID>
Shows image metadata for an image in Glance"""
c = get_client(options)
try:
if len(args) > 0:
image_id = args[0]
else:
print "Please specify the image identifier as the "
print "first argument. Example: "
print "$> glance-admin show 12345"
return FAILURE
image = c.get_image_meta(image_id)
print_image_formatted(c, image)
return SUCCESS
except Exception, e:
print e
return e
def images_index(options, args):
"""
%(prog)s index [options]
Returns basic information for all public images
a Glance server knows about"""
c = get_client(options)
try:
images = c.get_images()
if len(images) == 0:
print "No public images found."
return SUCCESS
print "Found %d public images..." % len(images)
print "%-32s %-30s %-14s" % (("ID"),
("Name"),
("Size"))
print ('-' * 32) + " " + ('-' * 30) + " "\
+ ('-' * 14)
for image in images:
print "%-32s %-30s %14d" % (image['id'],
image['name'],
int(image['size']))
return SUCCESS
except Exception, e:
print e
return e
def images_detailed(options, args):
"""
%(prog)s details [options]
Returns detailed information for all public images
a Glance server knows about"""
c = get_client(options)
try:
images = c.get_images_detailed()
if len(images) == 0:
print "No public images found."
return SUCCESS
num_images = len(images)
print "Found %d public images..." % num_images
cur_image = 1
for image in images:
print "=" * 80
print_image_formatted(c, image)
if cur_image == num_images:
print "=" * 80
cur_image += 1
return SUCCESS
except Exception, e:
print e
return e
def images_clear(options, args):
"""
%(prog)s clear [options]
Deletes all images from a Glance server"""
c = get_client(options)
images = c.get_images()
for image in images:
if options.verbose:
print 'Deleting image %s "%s" ...' % (image['id'], image['name']),
try:
c.delete_image(image['id'])
if options.verbose:
print 'done'
except Exception, e:
print 'Failed to delete image %s' % image['id']
print e
return FAILURE
return SUCCESS
def get_client(options):
"""
Returns a new client object to a Glance server
specified by the --host and --port options
supplied to the CLI
"""
try:
return client.Client(host=options.host,
port=options.port)
except Exception, e:
print e
sys.exit(1)
def create_options(parser):
"""
Sets up the CLI and config-file options that may be
parsed and program commands.
:param parser: The option parser
"""
parser.add_option('-v', '--verbose', default=False, action="store_true",
help="Print more verbose output")
parser.add_option('-H', '--host', metavar="ADDRESS", default="0.0.0.0",
help="Address of Glance API host. "
"Default: %default")
parser.add_option('-p', '--port', dest="port", metavar="PORT",
type=int, default=9292,
help="Port the Glance API host listens on. "
"Default: %default")
parser.add_option('--dry-run', default=False, action="store_true",
help="Don't actually execute the command, just print "
"output showing what WOULD happen.")
def parse_options(parser, cli_args):
"""
Returns the parsed CLI options, command to run and its arguments, merged
with any same-named options found in a configuration file
:param parser: The option parser
"""
COMMANDS = {'help': print_help,
'add': image_add,
'update': image_update,
'delete': image_delete,
'index': images_index,
'details': images_detailed,
'show': image_show,
'clear': images_clear}
if not cli_args:
cli_args.append('-h') # Show options in usage output...
(options, args) = parser.parse_args(cli_args)
if not args:
parser.print_usage()
sys.exit(0)
else:
command_name = args.pop(0)
if command_name not in COMMANDS.keys():
sys.exit("Unknown command: %s" % command_name)
command = COMMANDS[command_name]
return (options, command, args)
def print_help(options, args):
"""
Print help specific to a command
"""
COMMANDS = {'add': image_add,
'update': image_update,
'delete': image_delete,
'index': images_index,
'details': images_detailed,
'show': image_show,
'clear': images_clear}
if len(args) != 1:
sys.exit("Please specify a command")
command = args.pop()
if command not in COMMANDS.keys():
parser.print_usage()
if args:
sys.exit("Unknown command: %s" % command)
print COMMANDS[command].__doc__ % {'prog': os.path.basename(sys.argv[0])}
if __name__ == '__main__':
usage = """
%prog <command> [options] [args]
Commands:
help <command> Output help for one of the commands below
add Adds a new image to Glance
update Updates an image's metadata in Glance
delete Deletes an image from Glance
index Return brief information about images in Glance
details Return detailed information about images in
Glance
show Show detailed information about an image in
Glance
clear Removes all images and metadata from Glance
"""
oparser = optparse.OptionParser(version='%%prog %s'
% version.version_string(),
usage=usage.strip())
create_options(oparser)
(options, command, args) = parse_options(oparser, sys.argv[1:])
try:
start_time = time.time()
result = command(options, args)
end_time = time.time()
if options.verbose:
print "Completed in %-0.4f sec." % (end_time - start_time)
sys.exit(result)
except (RuntimeError, NotImplementedError), e:
print "ERROR: ", e

279
doc/source/glanceadmin.rst Normal file

@ -0,0 +1,279 @@
..
Copyright 2011 OpenStack, LLC
All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may
not use this file except in compliance with the License. You may obtain
a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
Using the Glance Admin Tool
===========================
Glance ships with a command-line tool for administering Glance called
``glance-admin``. It has a fairly simple but powerful interface of the
form::
Usage: glance-admin <command> [options] [args]
Where ``<command>`` is one of the following:
* help
Show detailed help information about a specific command
* add
Adds an image to Glance
* update
Updates an image's stored metadata in Glance
* delete
Deletes an image and its metadata from Glance
* index
Lists brief information about *public* images that Glance knows about
* details
Lists detailed information about *public* images that Glance knows about
* show
Lists detailed information about a specific image
* clear
Destroys *all* images and their associated metadata
This document describes how to use the ``glance-admin`` tool for each of
the above commands.
The ``help`` command
--------------------
Issuing the ``help`` command with a ``<COMMAND>`` argument shows detailed help
about a specific command. Running ``glance-admin`` without any arguments shows
a brief help message, like so::
$> glance-admin
Usage: glance-admin <command> [options] [args]
Commands:
help <command> Output help for one of the commands below
add Adds a new image to Glance
update Updates an image's metadata in Glance
delete Deletes an image from Glance
index Return brief information about images in Glance
details Return detailed information about images in
Glance
show Show detailed information about an image in
Glance
clear Removes all images and metadata from Glance
Options:
--version show program's version number and exit
-h, --help show this help message and exit
-v, --verbose Print more verbose output
-H ADDRESS, --host=ADDRESS
Address of Glance API host. Default: 0.0.0.0
-p PORT, --port=PORT Port the Glance API host listens on. Default: 9292
--dry-run Don't actually execute the command, just print output
showing what WOULD happen.
With a ``<COMMAND>`` argument, more information on the command is shown,
like so::
$> glance-admin help update
glance-admin update [options] <ID> <field1=value1 field2=value2 ...>
Updates an image's metadata in Glance. Specify metadata fields as arguments.
All field/value pairs are converted into a mapping that is passed
to Glance that represents the metadata for an image.
Field names that can be specified:
name A name for the image.
is_public If specified, interpreted as a boolean value
and sets or unsets the image's availability to the public.
disk_format Format of the disk image
container_format Format of the container
All other field names are considered to be custom properties so be careful
to spell field names correctly. :)
The ``add`` command
-------------------
The ``add`` command is used to do both of the following:
* Store virtual machine image data and metadata about that image in Glance
* Let Glance know about an existing virtual machine image that may be stored
somewhere else
We cover both use cases below.
Store virtual machine image data and metadata
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When adding an actual virtual machine image to Glance, you use the ``add``
command. You will pass metadata about the VM image on the command line, and
you will use a standard shell redirect to stream the image data file to
``glance-admin``.
Let's walk through a simple example. Suppose we have an image stored on our
local filesystem that we wish to "upload" to Glance. This image is stored
on our local filesystem in ``/tmp/images/myimage.tar.gz``.
We'd also like to tell Glance that this image should be called "My Image", and
that the image should be public -- anyone should be able to fetch it.
Here is how we'd upload this image to Glance::
$> glance-admin add name="My Image" is_public=true < /tmp/images/myimage.tar.gz
If Glance was able to successfully upload and store your VM image data and
metadata attributes, you would see something like this::
$> glance-admin add name="My Image" is_public=true < /tmp/images/myimage.tar.gz
Added new image with ID: 2
You can use the ``--verbose`` (or ``-v``) command-line option to print some more
information about the metadata that was saved with the image::
$> glance-admin --verbose add name="My Image" is_public=true < /tmp/images/myimage.tar.gz
Added new image with ID: 4
Returned the following metadata for the new image:
container_format => ovf
created_at => 2011-02-22T19:20:53.298556
deleted => False
deleted_at => None
disk_format => raw
id => 4
is_public => True
location => file:///tmp/images/4
name => My Image
properties => {}
size => 58520278
status => active
updated_at => None
Completed in 0.6141 sec.
If you are unsure about what will be added, you can use the ``--dry-run``
command-line option, which will simply show you what *would* have happened::
$> glance-admin --dry-run add name="Foo" distro="Ubuntu" is_publi=True < /tmp/images/myimage.tar.gz
Dry run. We would have done the following:
Add new image with metadata:
container_format => ovf
disk_format => raw
is_public => False
name => Foo
properties => {'is_publi': 'True', 'distro': 'Ubuntu'}
This is useful for detecting problems and for seeing what the default field
values supplied by ``glance-admin`` are. For instance, there was a typo in
the command above (the ``is_public`` field was incorrectly spelled ``is_publi``
which resulted in the image having an ``is_publi`` custom property added to
the image and the *real* ``is_public`` field value being `False` (the default)
and not `True`...
Register a virtual machine image in another location
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Sometimes, you already have stored the virtual machine image in some non-Glance
location -- perhaps even a location you have no write access to -- and you want
to tell Glance where this virtual machine image is located and some metadata
about it. The ``add`` command can do this for you.
When registering an image in this way, the only difference is that you do not
use a shell redirect to stream a virtual machine image file into Glance, but
instead, you tell Glance where to find the existing virtual machine image by
setting the ``location`` field. Below is an example of doing this.
Let's assume that there is a virtual machine image located at the URL
``http://example.com/images/myimage.tar.gz``. We can register this image with
Glance using the following::
$> glance-admin --verbose add name="Some web image" location="http://example.com/images/myimage.tar.gz"
Added new image with ID: 1
Returned the following metadata for the new image:
container_format => ovf
created_at => 2011-02-23T00:42:04.688890
deleted => False
deleted_at => None
disk_format => vhd
id => 1
is_public => True
location => http://example.com/images/myimage.tar.gz
name => Some web image
properties => {}
size => 0
status => active
updated_at => None
Completed in 0.0356 sec.
The ``update`` command
----------------------
After uploading/adding a virtual machine image to Glance, it is not possible to
modify the actual virtual machine image -- images are read-only after all --
however, it *is* possible to update any metadata about the image after you add
it to Glance.
The ``update`` command allows you to update the metadata fields of a stored
image. You use this command like so::
glance-admin update <ID> [field1=value1 field2=value2 ...]
Let's say we have an image with identifier 4 that we wish to change the is_public
attribute of the image from True to False. The following would accomplish this::
The ``delete`` command
----------------------
The ``index`` command
---------------------
The ``details`` command
-----------------------
The ``show`` command
--------------------
The ``clear`` command
---------------------
The ``clear`` command is an administrative command that deletes **ALL** images
and all image metadata. Passing the ``--verbose`` command will print brief
information about all the images that were deleted, as shown below::
$> glance-admin --verbose clear
Deleting image 1 "Some web image" ... done
Deleting image 2 "Some other web image" ... done
Completed in 0.0328 sec.

@ -61,6 +61,7 @@ Using Glance
installing
controllingservers
configuring
glanceadmin
glanceapi
client

@ -245,10 +245,8 @@ class Client(BaseClient):
:retval The newly-stored image's metadata.
"""
if image_meta is None:
image_meta = {}
headers = utils.image_meta_to_http_headers(image_meta)
headers = utils.image_meta_to_http_headers(image_meta or {})
if image_data:
body = image_data
@ -264,9 +262,6 @@ class Client(BaseClient):
"""
Updates Glance's information about an image
"""
if image_meta:
if 'image' not in image_meta:
image_meta = dict(image=image_meta)
headers = utils.image_meta_to_http_headers(image_meta or {})

@ -36,6 +36,21 @@ from glance.common.exception import ProcessExecutionError
TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
def bool_from_string(subject):
"""
Interpret a string as a boolean.
Any string value in:
('True', 'true', 'On', 'on', '1')
is interpreted as a boolean True.
Useful for JSON-decoded stuff and config file parsing
"""
if subject.strip().lower() in ('true', 'on', '1'):
return True
return False
def import_class(import_str):
"""Returns a class from a string including module and class"""
mod_str, _sep, class_str = import_str.rpartition('.')

@ -1,7 +1,6 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# Copyright 2010-2011 OpenStack, LLC
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@ -20,10 +19,16 @@
Registry API
"""
import logging
from glance.registry import client
logger = logging.getLogger('glance.registry')
def get_registry_client(options):
host = options['registry_host']
port = int(options['registry_port'])
return client.RegistryClient(options['registry_host'],
int(options['registry_port']))
@ -43,16 +48,50 @@ def get_image_metadata(options, image_id):
return c.get_image(image_id)
def add_image_metadata(options, image_data):
def add_image_metadata(options, image_meta):
if options['debug']:
logger.debug("Adding image metadata...")
_debug_print_metadata(image_meta)
c = get_registry_client(options)
return c.add_image(image_data)
new_image_meta = c.add_image(image_meta)
if options['debug']:
logger.debug("Returned image metadata from call to "
"RegistryClient.add_image():")
_debug_print_metadata(new_image_meta)
return new_image_meta
def update_image_metadata(options, image_id, image_data):
def update_image_metadata(options, image_id, image_meta):
if options['debug']:
logger.debug("Updating image metadata for image %s...", image_id)
_debug_print_metadata(image_meta)
c = get_registry_client(options)
return c.update_image(image_id, image_data)
new_image_meta = c.update_image(image_id, image_meta)
if options['debug']:
logger.debug("Returned image metadata from call to "
"RegistryClient.update_image():")
_debug_print_metadata(new_image_meta)
return new_image_meta
def delete_image_metadata(options, image_id):
logger.debug("Deleting image metadata for image %s...", image_id)
c = get_registry_client(options)
return c.delete_image(image_id)
def _debug_print_metadata(image_meta):
properties = image_meta.pop('properties', None)
for key, value in sorted(image_meta.items()):
logger.debug(" %(key)20s: %(value)s" % locals())
if properties:
logger.debug(" %d custom properties...",
len(properties))
for key, value in properties.items():
logger.debug(" %(key)20s: %(value)s" % locals())

@ -24,6 +24,7 @@ Defines interface for DB access
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import exc
from sqlalchemy.orm import joinedload
from sqlalchemy.orm import sessionmaker
@ -122,8 +123,7 @@ def image_get(context, image_id, session=None):
filter_by(id=image_id).\
one()
except exc.NoResultFound:
new_exc = exception.NotFound("No model for id %s" % image_id)
raise new_exc.__class__, new_exc, sys.exc_info()[2]
raise exception.NotFound("No image found with ID %s" % image_id)
def image_get_all_public(context):
@ -193,7 +193,13 @@ def _image_update(context, values, image_id):
if 'size' in values:
values['size'] = int(values['size'])
if image_id is None:
# Only set is_public to False is missing in new image
# creation. On update, we don't set to False if the
# is_public key is missing from the supplied keys to
# update, since only fields to update are supplied.
values['is_public'] = bool(values.get('is_public', False))
properties = values.pop('properties', {})
if image_id:

@ -111,6 +111,7 @@ class Controller(wsgi.Controller):
in the 'id' field
"""
logger.debug("Got req.body: %r", req.body)
image_data = json.loads(req.body)['image']
# Ensure the image has a status set
@ -140,10 +141,12 @@ class Controller(wsgi.Controller):
:retval Returns the updated image information as a mapping,
"""
logger.debug("Got req.body: %r", req.body)
image_data = json.loads(req.body)['image']
context = None
try:
logger.debug("Updating image %(id)s with metadata: %(image_data)r" % locals())
updated_image = db_api.image_update(context, id, image_data)
return dict(image=make_image_dict(updated_image))
except exception.NotFound:

@ -90,6 +90,7 @@ def delete_from_backend(uri, **kwargs):
backend_class = get_backend_class(scheme)
if hasattr(backend_class, 'delete'):
return backend_class.delete(parsed_uri, **kwargs)

@ -35,6 +35,7 @@ def image_meta_to_http_headers(image_meta):
headers["x-image-meta-property-%s"
% pk.lower()] = pv
else:
headers["x-image-meta-%s" % k.lower()] = v
return headers