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:
parent
43c8e2a2b6
commit
5c03271048
bin
doc/source
glance
539
bin/glance-admin
Executable file
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
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
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user