diff --git a/bin/glance b/bin/glance deleted file mode 100755 index 62a218546c..0000000000 --- a/bin/glance +++ /dev/null @@ -1,1087 +0,0 @@ -#!/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 functools -import gettext -import optparse -import os -import sys -import time -import warnings -from urlparse import urlparse - -# 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) - -gettext.install('glance', unicode=1) - -with warnings.catch_warnings(): - warnings.simplefilter("ignore") - from glance import client as glance_client - -from glance.common import exception -from glance.common import utils -from glance.version import version_info as version - - -SUCCESS = 0 -FAILURE = 1 - -DEFAULT_PORT = 9292 - - -#TODO(sirp): make more of the actions use this decorator -def catch_error(action): - """Decorator to provide sensible default error handling for actions.""" - def wrap(func): - @functools.wraps(func) - def wrapper(*args, **kwargs): - try: - ret = func(*args, **kwargs) - return SUCCESS if ret is None else ret - except exception.Forbidden: - print ("Not authorized to make this request. Check " - "your credentials (OS_AUTH_USER, OS_AUTH_KEY, ...).") - return FAILURE - except exception.ClientConfigurationError: - raise - except Exception, e: - options = args[0] - if options.debug: - raise - print "Failed to %s. Got error:" % action - pieces = unicode(e).split('\n') - for piece in pieces: - print piece - return FAILURE - - return wrapper - return wrap - - -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] - - return fields - - -def get_image_filters_from_args(args): - """Build a dictionary of query filters based on the supplied args.""" - try: - fields = get_image_fields_from_args(args) - except RuntimeError, e: - print e - return FAILURE - - SUPPORTED_FILTERS = ['name', 'disk_format', 'container_format', 'status', - 'min_ram', 'min_disk', 'size_min', 'size_max', - 'changes-since'] - filters = {} - for (key, value) in fields.items(): - if key not in SUPPORTED_FILTERS: - key = 'property-%s' % (key,) - filters[key] = value - - return filters - - -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:%s/v1/images/%s" % ( - client.use_ssl and "https" or "http", - client.host, - client.port, - image['id']) - print "Id: %s" % image['id'] - print "Public: " + (image['is_public'] and "Yes" or "No") - print "Protected: " + (image['protected'] and "Yes" or "No") - print "Name: %s" % image['name'] - print "Status: %s" % image['status'] - print "Size: %d" % int(image['size']) - print "Disk format: %s" % image['disk_format'] - print "Container format: %s" % image['container_format'] - print "Minimum Ram Required (MB): %s" % image['min_ram'] - print "Minimum Disk Required (GB): %s" % image['min_disk'] - if image.get('owner'): - print "Owner: %s" % image['owner'] - if len(image['properties']) > 0: - for k, v in image['properties'].items(): - print "Property '%s': %s" % (k, v) - print "Created at: %s" % image['created_at'] - if image.get('deleted_at'): - print "Deleted at: %s" % image['deleted_at'] - if image.get('updated_at'): - print "Updated at: %s" % image['updated_at'] - - -def image_add(options, args): - """ -%(prog)s add [options] [ < /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 Optional. 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. -protected Optional. If specified, interpreted as a boolean value - and enables or disables deletion protection. - The default value is False. -min_disk Optional. The minimum disk size in gigabytes required to - boot the image. If unspecified, this value defaults to 0 - (no minimum). -min_ram Optional. The minimum ram size in megabytes required to - boot the image. If unspecified, this value defaults to 0 - (no minimum). -disk_format Required. If unspecified and container_format is specified - as 'ami', 'ari' or 'aki', disk_format will default to the - container_format value. Possible values are 'ami', 'ari', - 'aki', 'vhd', 'vmk', 'raw', 'qcow2' and 'vdi'. -container_format Required. If unspecified and disk_format is specified as - 'ami', 'ari' or 'aki', container_format will default to the - disk_format value. Possible values are 'ami', 'ari', 'aki', - 'bare' and '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 -copy_from Optional. An external location (HTTP, S3 or Swift URI) to - copy image content from. For example, if the image data is - stored as an object called fedora16 in an S3 bucket named - images, you would specify (with the approriate access and - secret keys): - copy_from=s3://akey:skey@s3.amazonaws.com/images/fedora16 - -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" disk_format=raw container_format=ovf \ - 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 - - image_meta = {'id': fields.pop('id', None), - 'name': fields.pop('name', None), - 'is_public': utils.bool_from_string( - fields.pop('is_public', False)), - 'protected': utils.bool_from_string( - fields.pop('protected', False)), - 'min_disk': fields.pop('min_disk', 0), - 'min_ram': fields.pop('min_ram', 0), - } - - for format in ['disk_format', 'container_format']: - if format in fields: - image_meta[format] = fields.pop(format) - - # Strip any args that are not supported - unsupported_fields = ['status', 'size'] - for field in unsupported_fields: - if field in fields.keys(): - print 'Found non-settable field %s. Removing.' % field - fields.pop(field) - - def _external_source(fields, image_data): - source = None - features = {} - if 'location' in fields.keys(): - source = fields.pop('location') - image_meta['location'] = source - if 'checksum' in fields.keys(): - image_meta['checksum'] = fields.pop('checksum') - elif 'copy_from' in fields.keys(): - source = fields.pop('copy_from') - features['x-glance-api-copy-from'] = source - return source, features - - # We need either a location or image data/stream to add... - location, features = _external_source(fields, image_meta) - image_data = None - if not location: - # Grab the image data stream from stdin or redirect, - # otherwise error out - image_data = sys.stdin - - # allow owner to be set when image is created - if 'owner' in fields.keys(): - image_meta['owner'] = fields.pop('owner') - - # Add custom attributes, which are all the arguments remaining - image_meta['properties'] = fields - - if not options.dry_run: - try: - image_meta = c.add_image(image_meta, image_data, - features=features) - image_id = image_meta['id'] - print "Added new image with ID: %s" % image_id - if options.verbose: - print "Returned the following metadata for the new image:" - for k, v in sorted(image_meta.items()): - print " %(k)30s => %(v)s" % locals() - except exception.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 = unicode(e).split('\n') - for piece in pieces: - print piece - return FAILURE - except Exception, e: - print "Failed to add image. Got error:" - pieces = unicode(e).split('\n') - for piece in pieces: - print piece - print ("Note: Your image metadata may still be in the registry, " - "but the image's status will likely be 'killed'.") - return FAILURE - else: - print "Dry run. We would have done the following:" - - def _dump(dict): - for k, v in sorted(dict.items()): - print " %(k)30s => %(v)s" % locals() - - print "Add new image with metadata:" - _dump(image_meta) - - if features: - print "with features enabled:" - _dump(features) - - return SUCCESS - - -def image_update(options, args): - """ -%(prog)s update [options] - -Updates an image's metadata in Glance. Specify metadata fields as arguments. - -Metadata fields that are not specified in the update command will be deleted. - -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. -location An external location to serve out from. -copy_from An external location (HTTP, S3 or Swift URI) to copy image - content from. -is_public If specified, interpreted as a boolean value - and sets or unsets the image's availability to the public. -protected If specified, interpreted as a boolean value - and enables or disables deletion protection for the image. -disk_format Format of the disk image -container_format Format of the container -min_disk If specified, gives the minimum number of gigabytes of - space a disk must have to successfully boot the image. -min_ram If specified, gives the minimum number of megabytes of - ram required to successfully boot the image. - -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) - - features = {} - if 'location' not in fields and 'copy_from' in fields: - source = fields.pop('copy_from') - features['x-glance-api-copy-from'] = source - - base_image_fields = ['disk_format', 'container_format', 'name', - 'min_disk', 'min_ram', 'location', 'owner'] - for field in base_image_fields: - fvalue = fields.pop(field, None) - if fvalue is not None: - image_meta[field] = fvalue - - # Have to handle "boolean" values specially... - if 'is_public' in fields: - image_meta['is_public'] = utils.bool_from_string( - fields.pop('is_public')) - if 'protected' in fields: - image_meta['protected'] = utils.bool_from_string( - fields.pop('protected')) - - # Add custom attributes, which are all the arguments remaining - image_meta['properties'] = fields - - if not options.dry_run: - try: - image_meta = c.update_image(image_id, image_meta=image_meta, - features=features) - print "Updated image %s" % image_id - - if options.verbose: - print "Updated image metadata for image %s:" % image_id - print_image_formatted(c, image_meta) - except exception.NotFound: - print "No image with ID %s was found" % image_id - return FAILURE - except exception.Forbidden: - print "You do not have permission to update image %s" % image_id - return FAILURE - except Exception, e: - print "Failed to update image. Got error:" - pieces = unicode(e).split('\n') - for piece in pieces: - print piece - return FAILURE - else: - def _dump(dict): - for k, v in sorted(dict.items()): - print " %(k)30s => %(v)s" % locals() - - print "Dry run. We would have done the following:" - print "Update existing image with metadata:" - _dump(image_meta) - - if features: - print "with features enabled:" - _dump(features) - - return SUCCESS - - -def image_delete(options, args): - """ -%(prog)s delete [options] - -Deletes an image from Glance""" - 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 - - if not (options.force or - user_confirm("Delete image %s?" % (image_id,), default=False)): - print 'Not deleting image %s' % (image_id,) - return FAILURE - - c = get_client(options) - - try: - c.delete_image(image_id) - print "Deleted image %s" % image_id - return SUCCESS - except exception.NotFound: - print "No image with ID %s was found" % image_id - return FAILURE - except exception.Forbidden: - print "You do not have permission to delete image %s" % image_id - return FAILURE - - -def image_show(options, args): - """ -%(prog)s show [options] - -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.NotFound: - print "No image with ID %s was found" % image_id - return FAILURE - except Exception, e: - print "Failed to show image. Got error:" - pieces = unicode(e).split('\n') - for piece in pieces: - print piece - return FAILURE - - -def _images_index(client, filters, limit, print_header=False, **kwargs): - """Driver function for images_index""" - parameters = { - "filters": filters, - "limit": limit, - } - - optional_kwargs = ['marker', 'sort_key', 'sort_dir'] - for kwarg in optional_kwargs: - if kwarg in kwargs: - parameters[kwarg] = kwargs[kwarg] - - images = client.get_images(**parameters) - - if not images: - return SUCCESS - - pretty_table = utils.PrettyTable() - pretty_table.add_column(36, label="ID") - pretty_table.add_column(30, label="Name") - pretty_table.add_column(20, label="Disk Format") - pretty_table.add_column(20, label="Container Format") - pretty_table.add_column(14, label="Size", just="r") - - if print_header: - print pretty_table.make_header() - - for image in images: - print pretty_table.make_row(image['id'], - image['name'], - image['disk_format'], - image['container_format'], - image['size']) - - # suppress pagination when output is redirected - suppress_pagination = (options.force or - (getattr(os, 'isatty') and not os.isatty(sys.stdout.fileno()))) - - if not (suppress_pagination or len(images) != limit or - user_confirm("Fetch next page?", True)): - return SUCCESS - - parameters['marker'] = images[-1]['id'] - return _images_index(client, **parameters) - - -@catch_error('show index') -def images_index(options, args): - """ -%(prog)s index [options] - -Returns basic information for all public images -a Glance server knows about. Provided fields are -handled as query filters. Supported filters -include 'name', 'disk_format', 'container_format', -'status', 'size_min', 'size_max' and 'changes-since.' -Any extra fields are treated as image metadata properties""" - client = get_client(options) - filters = get_image_filters_from_args(args) - limit = options.limit - marker = options.marker - sort_key = options.sort_key - sort_dir = options.sort_dir - - return _images_index(client, - filters, - limit, - marker=marker, - sort_key=sort_key, - sort_dir=sort_dir, - print_header=True) - - -def _images_details(client, filters, limit, print_header=False, **kwargs): - """Driver function for images_details""" - parameters = { - "filters": filters, - "limit": limit, - } - - optional_kwargs = ['marker', 'sort_key', 'sort_dir'] - for kwarg in optional_kwargs: - if kwarg in kwargs: - parameters[kwarg] = kwargs[kwarg] - - images = client.get_images_detailed(**parameters) - - if len(images) == 0: - return SUCCESS - - if print_header: - print "=" * 80 - - for image in images: - print_image_formatted(client, image) - print "=" * 80 - - if not (options.force or len(images) != limit or - user_confirm("Fetch next page?", True)): - return SUCCESS - - parameters["marker"] = images[-1]['id'] - return _images_details(client, **parameters) - - -@catch_error('show details') -def images_details(options, args): - """ -%(prog)s details [options] - -Returns detailed information for all public images -a Glance server knows about. Provided fields are -handled as query filters. Supported filters -include 'name', 'disk_format', 'container_format', -'status', 'size_min', 'size_max' and 'changes-since.' -Any extra fields are treated as image metadata properties""" - client = get_client(options) - filters = get_image_filters_from_args(args) - limit = options.limit - marker = options.marker - sort_key = options.sort_key - sort_dir = options.sort_dir - - return _images_details(client, - filters, - limit, - marker=marker, - sort_key=sort_key, - sort_dir=sort_dir, - print_header=True) - - -def images_clear(options, args): - """ -%(prog)s clear [options] - -Deletes all images from a Glance server""" - if not (options.force or - user_confirm("Delete all images?", default=False)): - print 'Not deleting any images' - return FAILURE - - 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 - - -@catch_error('show image members') -def image_members(options, args): - """ -%(prog)s image-members [options] - -Displays a list of members with which an image is shared""" - try: - image_id = args.pop() - except IndexError: - print "Please specify the ID of the image as the first argument" - return FAILURE - - c = get_client(options) - - members = c.get_image_members(image_id) - sharers = 0 - # Output the list of members - for memb in members: - can_share = '' - if 'can_share' in memb and memb['can_share']: - can_share = ' *' - sharers += 1 - print "%s%s" % (memb['member_id'], can_share) - - # Emit a footnote - if sharers > 0: - print "\n(*: Can share image)" - - -@catch_error('show member images') -def member_images(options, args): - """ -%(prog)s member-images [options] - -Displays a list of images shared with a given member""" - try: - member_id = args.pop() - except IndexError: - print "Please specify the ID of the member as the first argument" - return FAILURE - - c = get_client(options) - - try: - members = c.get_member_images(member_id) - except exception.NotFound: - print "No images shared with member %s" % member_id - return SUCCESS - - sharers = 0 - # Output the list of images - for memb in members: - can_share = '' - if 'can_share' in memb and memb['can_share']: - can_share = ' *' - sharers += 1 - print "%s%s" % (memb['image_id'], can_share) - - # Emit a footnote - if sharers > 0: - print "\n(*: Can share image)" - - -@catch_error('update image members') -def members_replace(options, args): - """ -%(prog)s members-replace [options] - -Replaces the members of the image to be solely . If the -"--can-share" option is given, will be able to further share -the image.""" - try: - member_id = args.pop() - image_id = args.pop() - except IndexError: - print "Please specify the image ID and the member name" - return FAILURE - - c = get_client(options) - - # Update members - if not options.dry_run: - c.replace_members(image_id, dict(member_id=member_id, - can_share=options.can_share)) - else: - print "Dry run. We would have done the following:" - print ('Replace members of image %(image_id)s with "%(member_id)s"' - % locals()) - if options.can_share: - print "New member would have been able to further share image." - - -@catch_error('add image member') -def member_add(options, args): - """ -%(prog)s member-add [options] - -Adds the member to the image . If the "--can-share" -option is given, will be able to further share the image.""" - try: - member_id = args.pop() - image_id = args.pop() - except IndexError: - print "Please specify the image ID and the member name" - return FAILURE - - c = get_client(options) - - # Replace members - if not options.dry_run: - c.add_member(image_id, member_id, options.can_share) - else: - print "Dry run. We would have done the following:" - print ('Add "%(member_id)s" to membership of image %(image_id)s' - % locals()) - if options.can_share: - print "New member would have been able to further share image." - - -@catch_error('delete image member') -def member_delete(options, args): - """ -%(prog)s member-delete [options] - -Deletes the specified member of the image .""" - try: - member_id = args.pop() - image_id = args.pop() - except IndexError: - print "Please specify the image ID and the member name" - return FAILURE - - c = get_client(options) - - # Delete member - if not options.dry_run: - c.delete_member(image_id, member_id) - else: - print "Dry run. We would have done the following:" - print ('Remove "%(member_id)s" from the member list of image ' - '"%(image_id)s"' % locals()) - - -def get_client(options): - """ - Returns a new client object to a Glance server - specified by the --host and --port options - supplied to the CLI - """ - return glance_client.get_client(host=options.host, - port=options.port, - timeout=options.timeout, - use_ssl=options.use_ssl, - username=options.os_username, - password=options.os_password, - tenant=options.os_tenant_name, - auth_url=options.os_auth_url, - auth_strategy=options.os_auth_strategy, - auth_token=options.os_auth_token, - region=options.os_region_name, - is_silent_upload=options.is_silent_upload, - insecure=options.insecure) - - -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('--silent-upload', default=False, action="store_true", - dest="is_silent_upload", - help="disable progress bar animation and information " - "during upload") - parser.add_option('-v', '--verbose', default=False, action="store_true", - help="Print more verbose output") - parser.add_option('-d', '--debug', default=False, action="store_true", - help="Print debugging 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=DEFAULT_PORT, - help="Port the Glance API host listens on. " - "Default: %default") - parser.add_option('-t', '--timeout', dest="timeout", metavar="TIMEOUT", - type=int, default=None, - help="Connection timeout.") - parser.add_option('--ssl', dest='use_ssl', - default=False, action="store_true", - help="Use SSL when talking to Glance API host") - parser.add_option('-U', '--url', metavar="URL", default=None, - help="URL of Glance service. This option can be used " - "to specify the hostname, port and protocol " - "(http/https) of the glance server, for example " - "-U https://localhost:" + str(DEFAULT_PORT) + - "/v1 Default: None. If given, this option will " - "override settings for --host, --port, and --ssl.") - parser.add_option('-k', '--insecure', dest="insecure", - default=False, action="store_true", - help="Explicitly allow glance to perform \"insecure\" " - "SSL (https) requests. The server's certificate will " - "not be verified against any certificate authorities. " - "This option should be used with caution.") - parser.add_option('-A', '--os_auth_token', '--auth_token', - dest="os_auth_token", metavar="TOKEN", default=None, - help="Authentication token to use to identify the " - "client to the glance server. --auth_token " - "is deprecated and will be removed") - parser.add_option('-I', '--os_username', dest="os_username", - metavar="USER", default=None, - help="User name used to acquire an authentication token") - parser.add_option('-K', '--os_password', dest="os_password", - metavar="PASSWORD", default=None, - help="Password used to acquire an authentication token") - parser.add_option('-R', '--os_region_name', dest="os_region_name", - metavar="REGION", default=None, - help="Region name. When using keystone authentication " - "version 2.0 or later this identifies the region " - "name to use when selecting the service endpoint. A " - "region name must be provided if more than one " - "region endpoint is available") - parser.add_option('-T', '--os_tenant_name', dest="os_tenant_name", - metavar="TENANT", default=None, - help="Tenant name") - parser.add_option('-N', '--os_auth_url', dest="os_auth_url", - metavar="AUTH_URL", default=None, - help="Authentication URL") - parser.add_option('-S', '--os_auth_strategy', dest="os_auth_strategy", - metavar="STRATEGY", default=None, - help="Authentication strategy (keystone or noauth)") - parser.add_option('--limit', dest="limit", metavar="LIMIT", default=10, - type="int", help="Page size to use while " - "requesting image metadata") - parser.add_option('--marker', dest="marker", metavar="MARKER", - default=None, help="Image index after which to " - "begin pagination") - parser.add_option('--sort_key', dest="sort_key", metavar="KEY", - help="Sort results by this image attribute.") - parser.add_option('--sort_dir', dest="sort_dir", metavar="[desc|asc]", - help="Sort results in this direction.") - parser.add_option('-f', '--force', dest="force", - default=False, action="store_true", - help="Prevent select actions from requesting " - "user confirmation") - parser.add_option('--dry-run', default=False, action="store_true", - help="Don't actually execute the command, just print " - "output showing what WOULD happen.") - parser.add_option('--can-share', default=False, action="store_true", - help="Allow member to further share image.") - - -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 - """ - if not cli_args: - cli_args.append('-h') # Show options in usage output... - - (options, args) = parser.parse_args(cli_args) - if options.url is not None: - u = urlparse(options.url) - options.port = u.port - options.host = u.hostname - options.use_ssl = (u.scheme == 'https') - - # HACK(sirp): Make the parser available to the print_help method - # print_help is a command, so it only accepts (options, args); we could - # one-off have it take (parser, options, args), however, for now, I think - # this little hack will suffice - options.__parser = parser - - if not args: - parser.print_usage() - sys.exit(0) - - command_name = args.pop(0) - command = lookup_command(parser, command_name) - - return (options, command, args) - - -def print_help(options, args): - """ - Print help specific to a command - """ - if len(args) != 1: - sys.exit("Please specify a command") - - parser = options.__parser - command_name = args.pop() - command = lookup_command(parser, command_name) - - print command.__doc__ % {'prog': os.path.basename(sys.argv[0])} - - -def lookup_command(parser, command_name): - BASE_COMMANDS = {'help': print_help} - - IMAGE_COMMANDS = { - 'add': image_add, - 'update': image_update, - 'delete': image_delete, - 'index': images_index, - 'details': images_details, - 'show': image_show, - 'clear': images_clear} - - MEMBER_COMMANDS = { - 'image-members': image_members, - 'member-images': member_images, - 'members-replace': members_replace, - 'member-add': member_add, - 'member-delete': member_delete} - - commands = {} - for command_set in (BASE_COMMANDS, IMAGE_COMMANDS, - MEMBER_COMMANDS): - commands.update(command_set) - - try: - command = commands[command_name] - except KeyError: - parser.print_usage() - sys.exit("Unknown command: %s" % command_name) - - return command - - -def user_confirm(prompt, default=False): - """ - Yes/No question dialog with user. - - :param prompt: question/statement to present to user (string) - :param default: boolean value to return if empty string - is received as response to prompt - - """ - if default: - prompt_default = "[Y/n]" - else: - prompt_default = "[y/N]" - - # for bug 884116, don't issue the prompt if stdin isn't a tty - if not (hasattr(sys.stdin, 'isatty') and sys.stdin.isatty()): - return default - - answer = raw_input("%s %s " % (prompt, prompt_default)) - - if answer == "": - return default - else: - return answer.lower() in ("yes", "y") - - -if __name__ == '__main__': - usage = """ -%prog [options] [args] - -WARNING! This tool is deprecated in favor of python-glanceclient (see -http://github.com/openstack/python-glanceclient). - -Commands: - - help 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 - - -Member Commands: - - image-members List members an image is shared with - - member-images List images shared with a member - - member-add Grants a member access to an image - - member-delete Revokes a member's access to an image - - members-replace Replaces all membership for an image -""" - - print >> sys.stderr, ("WARNING! This tool is deprecated in favor of " - "python-glanceclient (see " - "http://github.com/openstack/python-glanceclient).") - - 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, - exception.ClientConfigurationError), e: - oparser.print_usage() - print >> sys.stderr, "ERROR: ", e - sys.exit(1) diff --git a/glance/client.py b/glance/client.py deleted file mode 100644 index 287a259be7..0000000000 --- a/glance/client.py +++ /dev/null @@ -1,451 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2010-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. - -""" -Client classes for callers of a Glance system -""" - -import errno -import httplib -import json -import os -import socket -import sys -import warnings - -import glance.api.v1 -from glance.common import animation -from glance.common import client as base_client -from glance.common import exception -from glance.common import utils - -SUPPORTED_PARAMS = glance.api.v1.SUPPORTED_PARAMS -SUPPORTED_FILTERS = glance.api.v1.SUPPORTED_FILTERS - -warn_msg = ("The 'glance.client' module is deprecated in favor of the " - "'glanceclient' module provided by python-glanceclient (see " - "http://github.com/openstack/python-glanceclient).") -warnings.warn(warn_msg, stacklevel=2) - - -class V1Client(base_client.BaseClient): - - """Main client class for accessing Glance resources""" - - DEFAULT_PORT = 9292 - DEFAULT_DOC_ROOT = "/v1" - - def get_images(self, **kwargs): - """ - Returns a list of image id/name mappings from Registry - - :param filters: dictionary of attributes by which the resulting - collection of images should be filtered - :param marker: id after which to start the page of images - :param limit: maximum number of items to return - :param sort_key: results will be ordered by this image attribute - :param sort_dir: direction in which to to order results (asc, desc) - """ - params = self._extract_params(kwargs, SUPPORTED_PARAMS) - res = self.do_request("GET", "/images", params=params) - data = json.loads(res.read())['images'] - return data - - def get_images_detailed(self, **kwargs): - """ - Returns a list of detailed image data mappings from Registry - - :param filters: dictionary of attributes by which the resulting - collection of images should be filtered - :param marker: id after which to start the page of images - :param limit: maximum number of items to return - :param sort_key: results will be ordered by this image attribute - :param sort_dir: direction in which to to order results (asc, desc) - """ - params = self._extract_params(kwargs, SUPPORTED_PARAMS) - res = self.do_request("GET", "/images/detail", params=params) - data = json.loads(res.read())['images'] - return data - - def get_image(self, image_id): - """ - Returns a tuple with the image's metadata and the raw disk image as - a mime-encoded blob stream for the supplied opaque image identifier. - - :param image_id: The opaque image identifier - - :retval Tuple containing (image_meta, image_blob) - :raises exception.NotFound if image is not found - """ - res = self.do_request("GET", "/images/%s" % image_id) - - image = utils.get_image_meta_from_headers(res) - return image, base_client.ImageBodyIterator(res) - - def get_image_meta(self, image_id): - """ - Returns a mapping of image metadata from Registry - - :raises exception.NotFound if image is not in registry - """ - res = self.do_request("HEAD", "/images/%s" % image_id) - - image = utils.get_image_meta_from_headers(res) - return image - - def _get_image_size(self, image_data): - """ - Analyzes the incoming image file and attempts to determine - its size. - - :param image_data: The input to the client, typically a file - redirected from stdin. - :retval The image file's size or None if it cannot be determined. - """ - # For large images, we need to supply the size of the - # image file. See LP Bugs #827660 and #845788. - if hasattr(image_data, 'seek') and hasattr(image_data, 'tell'): - try: - image_data.seek(0, os.SEEK_END) - image_size = image_data.tell() - image_data.seek(0) - return image_size - except IOError, e: - if e.errno == errno.ESPIPE: - # Illegal seek. This means the user is trying - # to pipe image data to the client, e.g. - # echo testdata | bin/glance add blah..., or - # that stdin is empty - return None - else: - raise - - def add_image(self, image_meta=None, image_data=None, features=None): - """ - Tells Glance about an image's metadata as well - as optionally the image_data itself - - :param image_meta: Optional Mapping of information about the - image - :param image_data: Optional string of raw image data - or file-like object that can be - used to read the image data - :param features: Optional map of features - - :retval The newly-stored image's metadata. - """ - headers = utils.image_meta_to_http_headers(image_meta or {}) - - if image_data: - body = image_data - headers['content-type'] = 'application/octet-stream' - image_size = self._get_image_size(image_data) - if image_size: - headers['x-image-meta-size'] = image_size - headers['content-length'] = image_size - else: - body = None - - utils.add_features_to_http_headers(features, headers) - - res = self.do_request("POST", "/images", body, headers) - data = json.loads(res.read()) - return data['image'] - - def update_image(self, image_id, image_meta=None, image_data=None, - features=None): - """ - Updates Glance's information about an image - - :param image_id: Required image ID - :param image_meta: Optional Mapping of information about the - image - :param image_data: Optional string of raw image data - or file-like object that can be - used to read the image data - :param features: Optional map of features - """ - if image_meta is None: - image_meta = {} - - headers = utils.image_meta_to_http_headers(image_meta) - - if image_data: - body = image_data - headers['content-type'] = 'application/octet-stream' - image_size = self._get_image_size(image_data) - if image_size: - headers['x-image-meta-size'] = image_size - headers['content-length'] = image_size - else: - body = None - - utils.add_features_to_http_headers(features, headers) - - res = self.do_request("PUT", "/images/%s" % image_id, body, headers) - data = json.loads(res.read()) - return data['image'] - - def delete_image(self, image_id): - """ - Deletes Glance's information about an image - """ - self.do_request("DELETE", "/images/%s" % image_id) - return True - - def get_cached_images(self, **kwargs): - """ - Returns a list of images stored in the image cache. - """ - res = self.do_request("GET", "/cached_images") - data = json.loads(res.read())['cached_images'] - return data - - def get_queued_images(self, **kwargs): - """ - Returns a list of images queued for caching - """ - res = self.do_request("GET", "/queued_images") - data = json.loads(res.read())['queued_images'] - return data - - def delete_cached_image(self, image_id): - """ - Delete a specified image from the cache - """ - self.do_request("DELETE", "/cached_images/%s" % image_id) - return True - - def delete_all_cached_images(self): - """ - Delete all cached images - """ - res = self.do_request("DELETE", "/cached_images") - data = json.loads(res.read()) - num_deleted = data['num_deleted'] - return num_deleted - - def queue_image_for_caching(self, image_id): - """ - Queue an image for prefetching into cache - """ - self.do_request("PUT", "/queued_images/%s" % image_id) - return True - - def delete_queued_image(self, image_id): - """ - Delete a specified image from the cache queue - """ - self.do_request("DELETE", "/queued_images/%s" % image_id) - return True - - def delete_all_queued_images(self): - """ - Delete all queued images - """ - res = self.do_request("DELETE", "/queued_images") - data = json.loads(res.read()) - num_deleted = data['num_deleted'] - return num_deleted - - def get_image_members(self, image_id): - """Returns a mapping of image memberships from Registry""" - res = self.do_request("GET", "/images/%s/members" % image_id) - data = json.loads(res.read())['members'] - return data - - def get_member_images(self, member_id): - """Returns a mapping of image memberships from Registry""" - res = self.do_request("GET", "/shared-images/%s" % member_id) - data = json.loads(res.read())['shared_images'] - return data - - def _validate_assocs(self, assocs): - """ - Validates membership associations and returns an appropriate - list of associations to send to the server. - """ - validated = [] - for assoc in assocs: - assoc_data = dict(member_id=assoc['member_id']) - if 'can_share' in assoc: - assoc_data['can_share'] = bool(assoc['can_share']) - validated.append(assoc_data) - return validated - - def replace_members(self, image_id, *assocs): - """ - Replaces the membership associations for a given image_id. - Each subsequent argument is a dictionary mapping containing a - 'member_id' that should have access to the image_id. A - 'can_share' boolean can also be specified to allow the member - to further share the image. An example invocation allowing - 'rackspace' to access image 1 and 'google' to access image 1 - with permission to share:: - - c = glance.client.Client(...) - c.update_members(1, {'member_id': 'rackspace'}, - {'member_id': 'google', 'can_share': True}) - """ - # Understand the associations - body = json.dumps(self._validate_assocs(assocs)) - self.do_request("PUT", "/images/%s/members" % image_id, body, - {'content-type': 'application/json'}) - return True - - def add_member(self, image_id, member_id, can_share=None): - """ - Adds a membership association between image_id and member_id. - If can_share is not specified and the association already - exists, no change is made; if the association does not already - exist, one is created with can_share defaulting to False. - When can_share is specified, the association is created if it - doesn't already exist, and the can_share attribute is set - accordingly. Example invocations allowing 'rackspace' to - access image 1 and 'google' to access image 1 with permission - to share:: - - c = glance.client.Client(...) - c.add_member(1, 'rackspace') - c.add_member(1, 'google', True) - """ - body = None - headers = {} - # Generate the body if appropriate - if can_share is not None: - body = json.dumps(dict(member=dict(can_share=bool(can_share)))) - headers['content-type'] = 'application/json' - - self.do_request("PUT", "/images/%s/members/%s" % - (image_id, member_id), body, headers) - return True - - def delete_member(self, image_id, member_id): - """ - Deletes the membership assocation. If the - association does not exist, no action is taken; otherwise, the - indicated association is deleted. An example invocation - removing the accesses of 'rackspace' to image 1 and 'google' - to image 2:: - - c = glance.client.Client(...) - c.delete_member(1, 'rackspace') - c.delete_member(2, 'google') - """ - self.do_request("DELETE", "/images/%s/members/%s" % - (image_id, member_id)) - return True - - -class ProgressIteratorWrapper(object): - - def __init__(self, wrapped, transfer_info): - self.wrapped = wrapped - self.transfer_info = transfer_info - self.prev_len = 0L - - def __iter__(self): - for chunk in self.wrapped: - if self.prev_len: - self.transfer_info['so_far'] += self.prev_len - self.prev_len = len(chunk) - yield chunk - # report final chunk - self.transfer_info['so_far'] += self.prev_len - - -class ProgressClient(V1Client): - - """ - Specialized class that adds progress bar output/interaction into the - TTY of the calling client - """ - def image_iterator(self, connection, headers, body): - wrapped = super(ProgressClient, self).image_iterator(connection, - headers, - body) - try: - # spawn the animation thread if the connection is good - connection.connect() - return ProgressIteratorWrapper(wrapped, - self.start_animation(headers)) - except (httplib.HTTPResponse, socket.error): - # the connection is out, just "pass" - # and let the "glance add" fail with [Errno 111] Connection refused - pass - - def start_animation(self, headers): - transfer_info = { - 'so_far': 0L, - 'size': headers.get('x-image-meta-size', 0L) - } - pg = animation.UploadProgressStatus(transfer_info) - if transfer_info['size'] == 0L: - sys.stdout.write("The progressbar doesn't show-up because " - "the headers[x-meta-size] is zero or missing\n") - sys.stdout.write("Uploading image '%s'\n" % - headers.get('x-image-meta-name', '')) - pg.start() - return transfer_info - -Client = V1Client - - -def get_client(host, port=None, timeout=None, use_ssl=False, username=None, - password=None, tenant=None, - auth_url=None, auth_strategy=None, - auth_token=None, region=None, - is_silent_upload=False, insecure=False): - """ - Returns a new client Glance client object based on common kwargs. - If an option isn't specified falls back to common environment variable - defaults. - """ - - if auth_url or os.getenv('OS_AUTH_URL'): - force_strategy = 'keystone' - else: - force_strategy = None - - creds = dict(username=username or - os.getenv('OS_AUTH_USER', os.getenv('OS_USERNAME')), - password=password or - os.getenv('OS_AUTH_KEY', os.getenv('OS_PASSWORD')), - tenant=tenant or - os.getenv('OS_AUTH_TENANT', - os.getenv('OS_TENANT_NAME')), - auth_url=auth_url or os.getenv('OS_AUTH_URL'), - strategy=force_strategy or auth_strategy or - os.getenv('OS_AUTH_STRATEGY', 'noauth'), - region=region or os.getenv('OS_REGION_NAME'), - ) - - if creds['strategy'] == 'keystone' and not creds['auth_url']: - msg = ("--os_auth_url option or OS_AUTH_URL environment variable " - "required when keystone authentication strategy is enabled\n") - raise exception.ClientConfigurationError(msg) - - client = (ProgressClient if not is_silent_upload else Client) - - return client(host=host, - port=port, - timeout=timeout, - use_ssl=use_ssl, - auth_tok=auth_token or - os.getenv('OS_TOKEN'), - creds=creds, - insecure=insecure) diff --git a/glance/common/client.py b/glance/common/client.py index 88dbda7f37..4425bb8fe5 100644 --- a/glance/common/client.py +++ b/glance/common/client.py @@ -86,76 +86,6 @@ def handle_redirects(func): return wrapped -class ImageBodyIterator(object): - - """ - A class that acts as an iterator over an image file's - chunks of data. This is returned as part of the result - tuple from `glance.client.Client.get_image` - """ - - def __init__(self, source): - """ - Constructs the object from a readable image source - (such as an HTTPResponse or file-like object) - """ - self.source = source - - def __iter__(self): - """ - Exposes an iterator over the chunks of data in the - image file. - """ - while True: - chunk = self.source.read(CHUNKSIZE) - if chunk: - yield chunk - else: - break - - -class SendFileIterator: - """ - Emulate iterator pattern over sendfile, in order to allow - send progress be followed by wrapping the iteration. - """ - def __init__(self, connection, body): - self.connection = connection - self.body = body - self.offset = 0 - self.sending = True - - def __iter__(self): - class OfLength: - def __init__(self, len): - self.len = len - - def __len__(self): - return self.len - - while self.sending: - try: - sent = sendfile.sendfile(self.connection.sock.fileno(), - self.body.fileno(), - self.offset, - CHUNKSIZE) - except OSError as e: - # suprisingly, sendfile may fail transiently instead of - # blocking, in which case we select on the socket in order - # to wait on its return to a writeable state before resuming - # the send loop - if e.errno in (errno.EAGAIN, errno.EBUSY): - wlist = [self.connection.sock.fileno()] - rfds, wfds, efds = select.select([], wlist, []) - if wfds: - continue - raise - - self.sending = (sent != 0) - self.offset += sent - yield OfLength(sent) - - class HTTPSClientAuthConnection(httplib.HTTPSConnection): """ Class to make a HTTPS connection, with support for diff --git a/glance/tests/functional/test_bin_glance.py b/glance/tests/functional/test_bin_glance.py deleted file mode 100644 index 0eb85383f5..0000000000 --- a/glance/tests/functional/test_bin_glance.py +++ /dev/null @@ -1,1272 +0,0 @@ -# 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. - -"""Functional test case that utilizes the bin/glance CLI tool""" - -import BaseHTTPServer -import datetime -import httplib2 -import json -import os -import tempfile -import thread -import time - -from glance.openstack.common import timeutils -from glance.tests import functional -from glance.tests.functional.store_utils import (setup_http, - teardown_http, - get_http_uri) -from glance.tests.utils import execute, requires, minimal_add_command - - -class TestBinGlance(functional.FunctionalTest): - """Functional tests for the bin/glance CLI tool""" - - def setUp(self): - super(TestBinGlance, self).setUp() - - # NOTE(sirp): This is needed in case we are running the tests under an - # environment in which OS_AUTH_STRATEGY=keystone. The test server we - # spin up won't have keystone support, so we need to switch to the - # NoAuth strategy. - os.environ['OS_AUTH_STRATEGY'] = 'noauth' - os.environ['OS_AUTH_URL'] = '' - - def _assertStartsWith(self, str, prefix): - msg = 'expected "%s" to start with "%s"' % (str, prefix) - self.assertTrue(str.startswith(prefix), msg) - - def _assertNotIn(self, key, bag): - msg = 'Expected not to find substring "%s" in "%s"' % (key, bag) - self.assertFalse(key in bag, msg) - - def test_index_with_https(self): - self.cleanup() - self.start_servers(**self.__dict__.copy()) - - cmd = ("bin/glance -N https://auth/ --port=%d index") % self.api_port - exitcode, out, err = execute(cmd, raise_error=False) - - self.assertNotEqual(0, exitcode) - self._assertNotIn('SSL23_GET_SERVER_HELLO', out) - - self.stop_servers() - - def test_add_with_location_and_id(self): - self.cleanup() - self.start_servers(**self.__dict__.copy()) - - api_port = self.api_port - registry_port = self.registry_port - - # 0. Verify no public images - cmd = "bin/glance --port=%d index" % api_port - - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - self.assertEqual('', out.strip()) - - image_id = "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa" - - # 1a. Add public image - cmd = minimal_add_command(api_port, - 'MyImage', - 'id=%s' % image_id, - 'location=http://example.com') - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - expected = 'Added new image with ID: %s' % image_id - self.assertTrue(expected in out) - - # 1b. Add public image with non-uuid id - cmd = minimal_add_command(api_port, - 'MyImage', - 'id=12345', - 'location=http://example.com') - exitcode, out, err = execute(cmd, expected_exitcode=1) - - self.assertEqual(1, exitcode) - self.assertTrue('Invalid image id format' in out) - - # 2. Verify image added as public image - cmd = "bin/glance --port=%d index" % api_port - - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - lines = out.split("\n")[2:-1] - self.assertEqual(1, len(lines)) - - line = lines[0] - - image_id, name, disk_format, container_format, size = \ - [c.strip() for c in line.split()] - self.assertEqual('MyImage', name) - - self.assertEqual('0', size, "Expected image to be 0 bytes in size, " - "but got %s. " % size) - - self.stop_servers() - - def test_add_with_location(self): - self.cleanup() - self.start_servers(**self.__dict__.copy()) - - api_port = self.api_port - registry_port = self.registry_port - - # 0. Verify no public images - cmd = "bin/glance --port=%d index" % api_port - - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - self.assertEqual('', out.strip()) - - # 1. Add public image - cmd = minimal_add_command(api_port, - 'MyImage', - 'location=http://localhost:0') - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - self.assertTrue(out.strip().startswith('Added new image with ID:')) - - # 2. Verify image added as public image - cmd = "bin/glance --port=%d index" % api_port - - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - lines = out.split("\n")[2:-1] - self.assertEqual(1, len(lines)) - - line = lines[0] - - img_info = [c.strip() for c in line.split()] - image_id, name, disk_format, container_format, size = img_info - self.assertEqual('MyImage', name) - - self.assertEqual('0', size, "Expected image to be 0 bytes in size, " - "but got %s. " % size) - - self.stop_servers() - - def test_add_no_name(self): - self.cleanup() - self.start_servers(**self.__dict__.copy()) - - api_port = self.api_port - registry_port = self.registry_port - - # 0. Verify no public images - cmd = "bin/glance --port=%d index" % api_port - - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - self.assertEqual('', out.strip()) - - # 1. Add public image - # Can't use minimal_add_command since that uses - # name... - cmd = ("bin/glance --port=%d add is_public=True" - " disk_format=raw container_format=ovf" - " %s" % (api_port, 'location=http://localhost:0')) - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - self.assertTrue(out.strip().startswith('Added new image with ID:')) - - # 2. Verify image added as public image - cmd = "bin/glance --port=%d index" % api_port - - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - lines = out.split("\n")[2:-1] - self.assertEqual(1, len(lines)) - - line = lines[0] - - image_id, name, disk_format, container_format, size = \ - [c.strip() for c in line.split()] - self.assertEqual('None', name) - - self.stop_servers() - - @requires(teardown=teardown_http) - def test_add_copying_from(self): - self.cleanup() - self.start_servers(**self.__dict__.copy()) - - setup_http(self) - - api_port = self.api_port - registry_port = self.registry_port - - # 0. Verify no public images - cmd = "bin/glance --port=%d index" % api_port - - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - self.assertEqual('', out.strip()) - - # 1. Add public image - suffix = 'copy_from=%s' % get_http_uri(self, 'foobar') - cmd = minimal_add_command(api_port, 'MyImage', suffix) - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - self.assertTrue(out.strip().startswith('Added new image with ID:')) - - # 2. Verify image added as public image - cmd = "bin/glance --port=%d index" % api_port - - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - lines = out.split("\n")[2:-1] - self.assertEqual(1, len(lines)) - - line = lines[0] - - image_id, name, disk_format, container_format, size = \ - [c.strip() for c in line.split()] - self.assertEqual('MyImage', name) - - self.assertEqual('5120', size, "Expected image to be 5120 bytes " - " in size, but got %s. " % size) - - self.stop_servers() - - def _do_test_update_external_source(self, source): - self.cleanup() - self.start_servers(**self.__dict__.copy()) - - setup_http(self) - - api_port = self.api_port - registry_port = self.registry_port - - # 1. Add public image with no image content - headers = {'X-Image-Meta-Name': 'MyImage', - 'X-Image-Meta-disk_format': 'raw', - 'X-Image-Meta-container_format': 'ovf', - 'X-Image-Meta-Is-Public': 'True'} - path = "http://%s:%d/v1/images" % ("0.0.0.0", api_port) - http = httplib2.Http() - response, content = http.request(path, 'POST', headers=headers) - self.assertEqual(response.status, 201) - data = json.loads(content) - self.assertEqual(data['image']['name'], 'MyImage') - image_id = data['image']['id'] - - # 2. Update image with external source - source = '%s=%s' % (source, get_http_uri(self, 'foobar')) - cmd = "bin/glance update %s %s -p %d" % (image_id, source, api_port) - exitcode, out, err = execute(cmd, raise_error=False) - - self.assertEqual(0, exitcode) - self.assertTrue(out.strip().endswith('Updated image %s' % image_id)) - - # 3. Verify image is now active and of the correct size - cmd = "bin/glance --port=%d show %s" % (api_port, image_id) - - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - - expected_lines = [ - 'URI: http://0.0.0.0:%s/v1/images/%s' % (api_port, image_id), - 'Id: %s' % image_id, - 'Public: Yes', - 'Name: MyImage', - 'Status: active', - 'Size: 5120', - 'Disk format: raw', - 'Container format: ovf', - 'Minimum Ram Required (MB): 0', - 'Minimum Disk Required (GB): 0', - ] - lines = out.split("\n") - self.assertTrue(set(lines) >= set(expected_lines)) - - self.stop_servers() - - @requires(teardown=teardown_http) - def test_update_copying_from(self): - """ - Tests creating an queued image then subsequently updating - with a copy-from source - """ - self._do_test_update_external_source('copy_from') - - @requires(teardown=teardown_http) - def test_update_location(self): - """ - Tests creating an queued image then subsequently updating - with a location source - """ - self._do_test_update_external_source('location') - - def test_add_with_location_and_stdin(self): - self.cleanup() - self.start_servers(**self.__dict__.copy()) - - api_port = self.api_port - registry_port = self.registry_port - - # 0. Verify no public images - cmd = "bin/glance --port=%d index" % api_port - - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - self.assertEqual('', out.strip()) - - # 1. Add public image - with tempfile.NamedTemporaryFile() as image_file: - image_file.write("XXX") - image_file.flush() - file_name = image_file.name - cmd = minimal_add_command(api_port, - 'MyImage', - 'location=http://localhost:0 < %s' % - file_name) - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - self.assertTrue(out.strip().startswith('Added new image with ID:')) - - # 2. Verify image added as public image - cmd = "bin/glance --port=%d index" % api_port - - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - lines = out.split("\n")[2:-1] - self.assertEqual(1, len(lines)) - - line = lines[0] - - img_info = [c.strip() for c in line.split()] - image_id, name, disk_format, container_format, size = img_info - self.assertEqual('MyImage', name) - - self.assertEqual('0', size, "Expected image to be 0 bytes in size, " - "but got %s. " % size) - - self.stop_servers() - - def test_add_list_delete_list(self): - """ - We test the following: - - 0. Verify no public images in index - 1. Add a public image - 2. Check that image exists in index - 3. Delete the image - 4. Verify no longer in index - """ - self.cleanup() - self.start_servers(**self.__dict__.copy()) - - api_port = self.api_port - registry_port = self.registry_port - - # 0. Verify no public images - cmd = "bin/glance --port=%d index" % api_port - - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - self.assertEqual('', out.strip()) - - # 1. Add public image - with tempfile.NamedTemporaryFile() as image_file: - image_file.write("XXX") - image_file.flush() - image_file_name = image_file.name - suffix = '--silent-upload < %s' % image_file_name - cmd = minimal_add_command(api_port, 'MyImage', suffix) - - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - msg = out.split("\n") - - self._assertStartsWith(msg[0], 'Added new image with ID:') - - # 2. Verify image added as public image - cmd = "bin/glance --port=%d index" % api_port - - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - lines = out.split("\n")[2:-1] - self.assertEqual(1, len(lines)) - - line = lines[0] - - img_info = [c.strip() for c in line.split()] - image_id, name, disk_format, container_format, size = img_info - self.assertEqual('MyImage', name) - - self.assertEqual('3', size, - "Expected image to be 3 bytes in size, but got %s. " - "Make sure you're running the correct version " - "of webob." % size) - - # 3. Delete the image - cmd = "bin/glance --port=%d --force delete %s" % (api_port, image_id) - - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - self.assertEqual('Deleted image %s' % image_id, out.strip()) - - # 4. Verify no public images - cmd = "bin/glance --port=%d index" % api_port - - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - self.assertEqual('', out.strip()) - - self.stop_servers() - - def test_add_list_update_list(self): - """ - Test for LP Bugs #736295, #767203 - We test the following: - - 0. Verify no public images in index - 1. Add a NON-public image - 2. Check that image does not appear in index - 3. Update the image to be public - 4. Check that image now appears in index - 5. Update the image's Name attribute - 6. Verify the updated name is shown - """ - self.cleanup() - self.start_servers(**self.__dict__.copy()) - - api_port = self.api_port - registry_port = self.registry_port - - # 0. Verify no public images - cmd = "bin/glance --port=%d index" % api_port - - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - self.assertEqual('', out.strip()) - - # 1. Add public image - cmd = minimal_add_command(api_port, - 'MyImage', - 'location=http://localhost:0', - public=False) - - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - msg = out.split('\n') - self.assertTrue(msg[0].startswith('Added new image with ID:')) - - image_id = out.strip().split(':')[1].strip() - - # 2. Verify image does not appear as a public image - cmd = "bin/glance --port=%d index" % api_port - - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - self.assertEqual('', out.strip()) - - # 3. Update the image to make it public - cmd = "bin/glance --port=%d update %s is_public=True" % ( - api_port, image_id) - - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - self.assertEqual('Updated image %s' % image_id, out.strip()) - - # 4. Verify image 1 in list of public images - cmd = "bin/glance --port=%d index" % api_port - - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - lines = out.split("\n")[2:-1] - self.assertEqual(len(lines), 1) - self.assertTrue('MyImage' in lines[0]) - - # 5. Update the image's Name attribute - updated_image_name = "Updated image name" - cmd = ("bin/glance --port=%d update %s is_public=True name=\"%s\"" % - (api_port, image_id, updated_image_name)) - - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - self.assertEqual('Updated image %s' % image_id, out.strip()) - - # 6. Verify updated name shown - cmd = "bin/glance --port=%d index" % api_port - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - self.assertTrue(updated_image_name in out, - "%s not found in %s" % (updated_image_name, out)) - - self.stop_servers() - - def test_killed_image_not_in_index(self): - """ - We test conditions that produced LP Bug #768969, where an image - in the 'killed' status is displayed in the output of glance index, - and the status column is not displayed in the output of - glance show . - - Start servers with Swift backend and a bad auth URL, and then: - 0. Verify no public images in index - 1. Attempt to add an image - 2. Verify the image does NOT appear in the index output - """ - self.cleanup() - - # Start servers with a Swift backend and a bad auth URL - override_options = { - 'default_store': 'swift', - 'swift_store_auth_address': 'badurl', - } - options = self.__dict__.copy() - options.update(override_options) - self.start_servers(**options) - - api_port = self.api_port - registry_port = self.registry_port - - # 0. Verify no public images - cmd = "bin/glance --port=%d index" % api_port - - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - self.assertEqual('', out.strip()) - - # 1. Attempt to add an image - with tempfile.NamedTemporaryFile() as image_file: - image_file.write("XXX") - image_file.flush() - image_file_name = image_file.name - cmd = ("bin/glance --port=%d add name=Jonas is_public=True " - "disk_format=qcow2 container_format=bare < %s" - % (api_port, image_file_name)) - - exitcode, out, err = execute(cmd, raise_error=False) - - self.assertNotEqual(0, exitcode) - self.assertTrue('Failed to add image.' in out) - - # 2. Verify image does not appear as public image - cmd = "bin/glance --port=%d index" % api_port - - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - self.assertEqual('', out.strip()) - - self.stop_servers() - - def test_add_location_with_checksum(self): - """ - We test the following: - - 1. Add an image with location and checksum - 2. Run SQL against DB to verify checksum was entered correctly - """ - self.cleanup() - self.start_servers(**self.__dict__.copy()) - - api_port = self.api_port - registry_port = self.registry_port - - # 1. Add public image - cmd = minimal_add_command(api_port, - 'MyImage', - 'location=http://localhost:0 checksum=1') - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - self.assertTrue(out.strip().startswith('Added new image with ID:')) - - image_id = out.split(":")[1].strip() - - sql = 'SELECT checksum FROM images WHERE id = "%s"' % image_id - recs = self.run_sql_cmd(sql) - - self.assertEqual('1', recs.first()[0]) - - self.stop_servers() - - def test_add_location_without_checksum(self): - """ - We test the following: - - 1. Add an image with location and no checksum - 2. Run SQL against DB to verify checksum is NULL - """ - self.cleanup() - self.start_servers(**self.__dict__.copy()) - - api_port = self.api_port - registry_port = self.registry_port - - # 1. Add public image - cmd = minimal_add_command(api_port, - 'MyImage', - 'location=http://localhost:0') - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - self.assertTrue(out.strip().startswith('Added new image with ID:')) - - image_id = out.split(":")[1].strip() - - sql = 'SELECT checksum FROM images WHERE id = "%s"' % image_id - recs = self.run_sql_cmd(sql) - - self.assertEqual(None, recs.first()[0]) - - self.stop_servers() - - def test_add_clear(self): - """ - We test the following: - - 1. Add a couple images with metadata - 2. Clear the images - 3. Verify no public images found - 4. Run SQL against DB to verify no undeleted properties - """ - self.cleanup() - self.start_servers(**self.__dict__.copy()) - - api_port = self.api_port - registry_port = self.registry_port - - # 1. Add some images - for i in range(1, 5): - cmd = minimal_add_command(api_port, - 'MyImage', - 'foo=bar') - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - self.assertTrue(out.strip().find('Added new image with ID:') > -1) - - # 2. Clear all images - cmd = "bin/glance --port=%d --force clear" % api_port - exitcode, out, err = execute(cmd) - self.assertEqual(0, exitcode) - - # 3. Verify no public images are found - cmd = "bin/glance --port=%d index" % api_port - - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - lines = out.split("\n") - first_line = lines[0] - self.assertEqual('', first_line) - - # 4. Lastly we manually verify with SQL that image properties are - # also getting marked as deleted. - sql = "SELECT COUNT(*) FROM image_properties WHERE deleted = 0" - recs = self.run_sql_cmd(sql) - for rec in recs: - self.assertEqual(0, rec[0]) - - self.stop_servers() - - def test_results_filtering(self): - self.cleanup() - self.start_servers(**self.__dict__.copy()) - - api_port = self.api_port - registry_port = self.registry_port - - # 1. Add some images - _add_cmd = "bin/glance --port=%d add is_public=True" % api_port - _add_args = [ - "name=Name1 disk_format=vhd container_format=ovf foo=bar", - "name=Name2 disk_format=ami container_format=ami foo=bar", - "name=Name3 disk_format=vhd container_format=ovf foo=baz " - "min_disk=7 min_ram=256", - ] - - image_ids = [] - for i, args in enumerate(_add_args): - cmd = "%s %s" % (_add_cmd, args) - exitcode, out, err = execute(cmd) - self.assertEqual(0, exitcode) - self.assertTrue(out.strip().find('Added new image with ID:') > -1) - image_ids.append(out.strip().split(':')[1].strip()) - - _base_cmd = "bin/glance --port=%d" % api_port - _index_cmd = "%s index -f" % (_base_cmd,) - - # 2. Check name filter - cmd = "name=Name2" - exitcode, out, err = execute("%s %s" % (_index_cmd, cmd)) - image_lines = out.split("\n")[2:-1] - - self.assertEqual(0, exitcode) - self.assertEqual(1, len(image_lines)) - self.assertEqual(image_lines[0].split()[0], image_ids[1]) - - # 3. Check disk_format filter - cmd = "disk_format=vhd" - exitcode, out, err = execute("%s %s" % (_index_cmd, cmd)) - - self.assertEqual(0, exitcode) - image_lines = out.split("\n")[2:-1] - self.assertEqual(2, len(image_lines)) - self.assertEqual(image_lines[0].split()[0], image_ids[2]) - self.assertEqual(image_lines[1].split()[0], image_ids[0]) - - # 4. Check container_format filter - cmd = "container_format=ami" - exitcode, out, err = execute("%s %s" % (_index_cmd, cmd)) - - self.assertEqual(0, exitcode) - image_lines = out.split("\n")[2:-1] - self.assertEqual(1, len(image_lines)) - self.assertEqual(image_lines[0].split()[0], image_ids[1]) - - # 5. Check container_format filter - cmd = "container_format=ami" - exitcode, out, err = execute("%s %s" % (_index_cmd, cmd)) - - self.assertEqual(0, exitcode) - image_lines = out.split("\n")[2:-1] - self.assertEqual(1, len(image_lines)) - self.assertEqual(image_lines[0].split()[0], image_ids[1]) - - # 6. Check status filter - cmd = "status=killed" - exitcode, out, err = execute("%s %s" % (_index_cmd, cmd)) - - self.assertEqual(0, exitcode) - image_lines = out.split("\n")[2:-1] - self.assertEqual(0, len(image_lines)) - - # 7. Check property filter - cmd = "foo=bar" - exitcode, out, err = execute("%s %s" % (_index_cmd, cmd)) - - self.assertEqual(0, exitcode) - image_lines = out.split("\n")[2:-1] - self.assertEqual(2, len(image_lines)) - self.assertEqual(image_lines[0].split()[0], image_ids[1]) - self.assertEqual(image_lines[1].split()[0], image_ids[0]) - - # 8. Check multiple filters - cmd = "name=Name2 foo=bar" - exitcode, out, err = execute("%s %s" % (_index_cmd, cmd)) - - self.assertEqual(0, exitcode) - image_lines = out.split("\n")[2:-1] - self.assertEqual(1, len(image_lines)) - self.assertEqual(image_lines[0].split()[0], image_ids[1]) - - # 9. Check past changes-since - dt1 = timeutils.utcnow() - datetime.timedelta(1) - iso1 = timeutils.isotime(dt1) - cmd = "changes-since=%s" % iso1 - exitcode, out, err = execute("%s %s" % (_index_cmd, cmd)) - - self.assertEqual(0, exitcode) - image_lines = out.split("\n")[2:-1] - self.assertEqual(3, len(image_lines)) - self.assertEqual(image_lines[0].split()[0], image_ids[2]) - self.assertEqual(image_lines[1].split()[0], image_ids[1]) - self.assertEqual(image_lines[2].split()[0], image_ids[0]) - - # 10. Check future changes-since - dt2 = timeutils.utcnow() + datetime.timedelta(1) - iso2 = timeutils.isotime(dt2) - cmd = "changes-since=%s" % iso2 - exitcode, out, err = execute("%s %s" % (_index_cmd, cmd)) - - self.assertEqual(0, exitcode) - image_lines = out.split("\n")[2:-1] - self.assertEqual(0, len(image_lines)) - - # 11. Ensure details call also respects filters - _details_cmd = "%s details" % (_base_cmd,) - cmd = "foo=bar" - exitcode, out, err = execute("%s %s" % (_details_cmd, cmd)) - - self.assertEqual(0, exitcode) - image_lines = out.split("\n")[1:-1] - self.assertEqual(30, len(image_lines)) - self.assertEqual(image_lines[1].split()[1], image_ids[1]) - self.assertEqual(image_lines[16].split()[1], image_ids[0]) - - # 12. Check min_ram filter - cmd = "min_ram=256" - exitcode, out, err = execute("%s %s" % (_details_cmd, cmd)) - - self.assertEqual(0, exitcode) - image_lines = out.split("\n")[2:-1] - self.assertEqual(14, len(image_lines)) - self.assertEqual(image_lines[0].split()[1], image_ids[2]) - - # 13. Check min_disk filter - cmd = "min_disk=7" - exitcode, out, err = execute("%s %s" % (_details_cmd, cmd)) - - self.assertEqual(0, exitcode) - image_lines = out.split("\n")[2:-1] - self.assertEqual(14, len(image_lines)) - self.assertEqual(image_lines[0].split()[1], image_ids[2]) - - self.stop_servers() - - def test_results_pagination(self): - self.cleanup() - self.start_servers(**self.__dict__.copy()) - - _base_cmd = "bin/glance --port=%d" % self.api_port - index_cmd = "%s index -f" % _base_cmd - details_cmd = "%s details -f" % _base_cmd - - # 1. Add some images - _add_cmd = "bin/glance --port=%d add is_public=True" % self.api_port - _add_args = [ - "name=Name1 disk_format=ami container_format=ami", - "name=Name2 disk_format=vhd container_format=ovf", - "name=Name3 disk_format=ami container_format=ami", - "name=Name4 disk_format=ami container_format=ami", - "name=Name5 disk_format=vhd container_format=ovf", - ] - - image_ids = [] - - for i, args in enumerate(_add_args): - cmd = "%s %s" % (_add_cmd, args) - exitcode, out, err = execute(cmd) - self.assertEqual(0, exitcode) - expected_out = 'Added new image with ID: %d' % (i + 1,) - self.assertTrue(out.strip().find('Added new image with ID:') > -1) - image_ids.append(out.strip().split(':')[1].strip()) - - # 2. Limit less than total - cmd = "--limit=3" - exitcode, out, err = execute("%s %s" % (index_cmd, cmd)) - - self.assertEqual(0, exitcode) - image_lines = out.split("\n")[2:-1] - self.assertEqual(5, len(image_lines)) - self.assertTrue(image_lines[0].split()[0], image_ids[0]) - self.assertTrue(image_lines[1].split()[0], image_ids[1]) - self.assertTrue(image_lines[2].split()[0], image_ids[2]) - self.assertTrue(image_lines[3].split()[0], image_ids[3]) - self.assertTrue(image_lines[4].split()[0], image_ids[4]) - - # 3. With a marker - cmd = "--marker=%s" % image_ids[3] - exitcode, out, err = execute("%s %s" % (index_cmd, cmd)) - - self.assertEqual(0, exitcode) - image_lines = out.split("\n")[2:-1] - self.assertEqual(3, len(image_lines)) - self.assertTrue(image_lines[0].split()[0], image_ids[1]) - self.assertTrue(image_lines[1].split()[0], image_ids[2]) - self.assertTrue(image_lines[2].split()[0], image_ids[3]) - - # 3. With a marker and limit - cmd = "--marker=%s --limit=1" % image_ids[2] - exitcode, out, err = execute("%s %s" % (index_cmd, cmd)) - - self.assertEqual(0, exitcode) - image_lines = out.split("\n")[2:-1] - self.assertEqual(2, len(image_lines)) - self.assertTrue(image_lines[0].split()[0], image_ids[1]) - self.assertTrue(image_lines[1].split()[0], image_ids[2]) - - # 4. Pagination params with filtered results - cmd = "--marker=%s --limit=1 container_format=ami" % image_ids[3] - exitcode, out, err = execute("%s %s" % (index_cmd, cmd)) - - self.assertEqual(0, exitcode) - image_lines = out.split("\n")[2:-1] - self.assertEqual(2, len(image_lines)) - self.assertTrue(image_lines[0].split()[0], image_ids[2]) - self.assertTrue(image_lines[1].split()[0], image_ids[1]) - - # 5. Pagination params with filtered results in a details call - cmd = "--marker=%s --limit=1 container_format=ami" % image_ids[3] - exitcode, out, err = execute("%s %s" % (details_cmd, cmd)) - - self.assertEqual(0, exitcode) - image_lines = out.split("\n")[1:-1] - self.assertEqual(28, len(image_lines)) - self.assertTrue(image_lines[1].split()[1], image_ids[2]) - self.assertTrue(image_lines[15].split()[1], image_ids[1]) - - self.stop_servers() - - def test_results_sorting(self): - self.cleanup() - self.start_servers(**self.__dict__.copy()) - - _base_cmd = "bin/glance --port=%d" % self.api_port - index_cmd = "%s index -f" % _base_cmd - details_cmd = "%s details -f" % _base_cmd - - # 1. Add some images - _add_cmd = "bin/glance --port=%d add is_public=True" % self.api_port - _add_args = [ - "name=Name1 disk_format=ami container_format=ami", - "name=Name4 disk_format=vhd container_format=ovf", - "name=Name3 disk_format=ami container_format=ami", - "name=Name2 disk_format=ami container_format=ami", - "name=Name5 disk_format=vhd container_format=ovf", - ] - - image_ids = [] - for i, args in enumerate(_add_args): - cmd = "%s %s" % (_add_cmd, args) - exitcode, out, err = execute(cmd) - self.assertEqual(0, exitcode) - expected_out = 'Added new image with ID: %d' % (i + 1,) - self.assertTrue(out.strip().find('Added new image with ID:') > -1) - image_ids.append(out.strip().split(':')[1].strip()) - - # 2. Sort by name asc - cmd = "--sort_key=name --sort_dir=asc" - exitcode, out, err = execute("%s %s" % (index_cmd, cmd)) - - self.assertEqual(0, exitcode) - image_lines = out.split("\n")[2:-1] - self.assertEqual(5, len(image_lines)) - self.assertTrue(image_lines[0].split()[0], image_ids[0]) - self.assertTrue(image_lines[1].split()[0], image_ids[1]) - self.assertTrue(image_lines[2].split()[0], image_ids[2]) - self.assertTrue(image_lines[3].split()[0], image_ids[3]) - self.assertTrue(image_lines[4].split()[0], image_ids[4]) - - # 3. Sort by name asc with a marker - cmd = "--sort_key=name --sort_dir=asc --marker=%s" % image_ids[3] - exitcode, out, err = execute("%s %s" % (index_cmd, cmd)) - - self.assertEqual(0, exitcode) - image_lines = out.split("\n")[2:-1] - self.assertEqual(3, len(image_lines)) - self.assertTrue(image_lines[0].split()[0], image_ids[2]) - self.assertTrue(image_lines[1].split()[0], image_ids[1]) - self.assertTrue(image_lines[2].split()[0], image_ids[4]) - - # 4. Sort by container_format desc - cmd = "--sort_key=container_format --sort_dir=desc --limit=10" - exitcode, out, err = execute("%s %s" % (index_cmd, cmd)) - - self.assertEqual(0, exitcode) - image_lines = out.split("\n")[2:-1] - self.assertEqual(5, len(image_lines)) - self.assertTrue(image_lines[0].split()[0], image_ids[4]) - self.assertTrue(image_lines[1].split()[0], image_ids[1]) - self.assertTrue(image_lines[2].split()[0], image_ids[3]) - self.assertTrue(image_lines[3].split()[0], image_ids[2]) - self.assertTrue(image_lines[4].split()[0], image_ids[0]) - - # 5. Sort by name asc with a marker (details) - cmd = "--sort_key=name --sort_dir=asc --marker=%s" % image_ids[3] - exitcode, out, err = execute("%s %s" % (details_cmd, cmd)) - - self.assertEqual(0, exitcode) - image_lines = out.split("\n")[1:-1] - self.assertEqual(42, len(image_lines)) - self.assertTrue(image_lines[1].split()[1], image_ids[2]) - self.assertTrue(image_lines[15].split()[1], image_ids[1]) - self.assertTrue(image_lines[29].split()[1], image_ids[4]) - - self.stop_servers() - - def test_show_image_format(self): - self.cleanup() - self.start_servers(**self.__dict__.copy()) - - api_port = self.api_port - registry_port = self.registry_port - - # 1. Add public image - with tempfile.NamedTemporaryFile() as image_file: - image_file.write("XXX") - image_file.flush() - image_file_name = image_file.name - suffix = ' --silent-upload < %s' % image_file_name - cmd = minimal_add_command(api_port, 'MyImage', suffix) - - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - image_id = out.strip().rsplit(' ', 1)[1] - - # 2. Verify image added as public image - cmd = "bin/glance --port=%d show %s" % (api_port, image_id) - - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - lines = out.split("\n")[:-1] - - expected_lines = [ - 'URI: http://0.0.0.0:%s/v1/images/%s' % (api_port, image_id), - 'Id: %s' % image_id, - 'Public: Yes', - 'Name: MyImage', - 'Status: active', - 'Size: 3', - 'Disk format: raw', - 'Container format: ovf', - 'Minimum Ram Required (MB): 0', - 'Minimum Disk Required (GB): 0', - ] - - self.assertTrue(set(lines) >= set(expected_lines)) - - # 3. Delete the image - cmd = "bin/glance --port=%d --force delete %s" % (api_port, image_id) - - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - self.assertEqual('Deleted image %s' % image_id, out.strip()) - - self.stop_servers() - - def test_protected_image(self): - """ - We test the following: - - 0. Verify no public images in index - 1. Add a public image with a location attr - protected and no image data - 2. Check that image exists in index - 3. Attempt to delete the image - 4. Remove protection from image - 5. Delete the image - 6. Verify no longer in index - """ - self.cleanup() - self.start_servers() - - api_port = self.api_port - registry_port = self.registry_port - - # 0. Verify no public images - cmd = "bin/glance --port=%d index" % api_port - - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - self.assertEqual('', out.strip()) - - # 1. Add public image - cmd = ("echo testdata | " + - minimal_add_command(api_port, - 'MyImage', - 'protected=True')) - - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - msg = out.split("\n") - self.assertTrue(msg[3].startswith('Added new image with ID:')) - - # 2. Verify image added as public image - cmd = "bin/glance --port=%d index" % api_port - - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - lines = out.split("\n")[2:-1] - self.assertEqual(1, len(lines)) - - line = lines[0] - - img_info = [c.strip() for c in line.split()] - image_id, name, disk_format, container_format, size = img_info - self.assertEqual('MyImage', name) - - # 3. Delete the image - cmd = "bin/glance --port=%d --force delete %s" % (api_port, image_id) - - exitcode, out, err = execute(cmd, raise_error=False) - - self.assertNotEqual(0, exitcode) - self.assertTrue(out.startswith('You do not have permission')) - - # 4. Remove image protection - cmd = ("bin/glance --port=%d --force update %s " - "protected=False" % (api_port, image_id)) - - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - self.assertTrue(out.strip().startswith('Updated image')) - - # 5. Delete the image - cmd = "bin/glance --port=%d --force delete %s" % (api_port, image_id) - - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - self.assertTrue(out.strip().startswith('Deleted image')) - - # 6. Verify no public images - cmd = "bin/glance --port=%d index" % api_port - - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - self.assertEqual('', out.strip()) - - self.stop_servers() - - def test_timeout(self): - self.cleanup() - - keep_sleeping = True - - #start a simple HTTP server in a thread that hangs for a bit - class RemoteImageHandler(BaseHTTPServer.BaseHTTPRequestHandler): - def do_GET(self): - cnt = 1 - while (keep_sleeping): - cnt += 1 - time.sleep(0.1) - if cnt > 100: - break - - server_class = BaseHTTPServer.HTTPServer - local_server = server_class(('127.0.0.1', 0), RemoteImageHandler) - local_ip, local_port = local_server.server_address - - def serve_requests(httpd): - httpd.serve_forever() - - thread.start_new_thread(serve_requests, (local_server,)) - - cmd = ("bin/glance --port=%d index --timeout=1") % local_port - exitcode, out, err = execute(cmd, raise_error=False) - - keep_sleeping = False - local_server.shutdown() - self.assertNotEqual(0, exitcode) - self.assertTrue("timed out" in out) - - def test_add_member(self): - self.cleanup() - self.start_servers(**self.__dict__.copy()) - - api_port = self.api_port - registry_port = self.registry_port - - image_id = "11111111-1111-1111-1111-111111111111" - member_id = "21111111-2111-2111-2111-211111111111" - member2_id = "31111111-3111-3111-3111-311111111111" - - # 0. Add an image - cmd = minimal_add_command(api_port, - 'MyImage', - 'id=%s' % image_id, - 'location=http://example.com') - exitcode, out, err = execute(cmd) - - # 1. Add an image member - cmd = "bin/glance --port=%d member-add %s %s" % (api_port, image_id, - member_id) - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - self.assertEqual('', out.strip()) - - # 2. Verify image-members - cmd = "bin/glance --port=%d image-members %s " % (api_port, image_id) - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - self.assertTrue(member_id in out) - - # 3. Verify member-images - cmd = "bin/glance --port=%d member-images %s " % (api_port, member_id) - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - self.assertTrue(image_id in out) - - # 4. Replace image members - cmd = "bin/glance --port=%d members-replace %s %s" % (api_port, - image_id, - member2_id) - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - self.assertEqual('', out.strip()) - - # 5. Verify member-images again for member2 - cmd = "bin/glance --port=%d member-images %s " % (api_port, member2_id) - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - self.assertTrue(image_id in out) - - # 6. Verify member-images again for member1 (should not be present) - cmd = "bin/glance --port=%d member-images %s " % (api_port, member_id) - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - self.assertTrue(image_id not in out) - - # 7. Delete the member - cmd = "bin/glance --port=%d member-delete %s %s" % (api_port, image_id, - member2_id) - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - self.assertEqual('', out.strip()) - - # 8. Verify image-members is empty - cmd = "bin/glance --port=%d image-members %s " % (api_port, image_id) - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - self.assertEqual('', out.strip()) - - self.stop_servers() diff --git a/glance/tests/functional/test_scrubber.py b/glance/tests/functional/test_scrubber.py index 2741854153..af07098fb4 100644 --- a/glance/tests/functional/test_scrubber.py +++ b/glance/tests/functional/test_scrubber.py @@ -15,12 +15,12 @@ # License for the specific language governing permissions and limitations # under the License. +import json import time -from glance.tests import functional +import httplib2 -from glance import client -from glance.registry import client as registry_client +from glance.tests import functional from glance.tests.utils import execute @@ -35,95 +35,55 @@ class TestScrubber(functional.FunctionalTest): """Test that delayed_delete works and the scrubber deletes""" - def _get_client(self): - return client.Client("localhost", self.api_port) - - def _get_registry_client(self): - return registry_client.RegistryClient('localhost', - self.registry_port) - - def test_immediate_delete(self): - """ - test that images get deleted immediately by default - """ - - self.cleanup() - self.start_servers(**self.__dict__.copy()) - - client = self._get_client() - registry = self._get_registry_client() - meta = client.add_image(TEST_IMAGE_META, TEST_IMAGE_DATA) - id = meta['id'] - - filters = {'deleted': True, 'is_public': 'none', - 'status': 'pending_delete'} - recs = registry.get_images_detailed(filters=filters) - self.assertFalse(recs) - - client.delete_image(id) - recs = registry.get_images_detailed(filters=filters) - self.assertFalse(recs) - - filters = {'deleted': True, 'is_public': 'none', 'status': 'deleted'} - recs = registry.get_images_detailed(filters=filters) - self.assertTrue(recs) - for rec in recs: - self.assertEqual(rec['status'], 'deleted') - - self.stop_servers() - def test_delayed_delete(self): """ test that images don't get deleted immediatly and that the scrubber scrubs them """ - self.cleanup() self.start_servers(delayed_delete=True, daemon=True) - client = self._get_client() - registry = self._get_registry_client() - meta = client.add_image(TEST_IMAGE_META, TEST_IMAGE_DATA) - id = meta['id'] + headers = { + 'x-image-meta-name': 'test_image', + 'x-image-meta-is_public': 'true', + 'x-image-meta-disk_format': 'raw', + 'x-image-meta-container_format': 'ovf', + 'content-type': 'application/octet-stream', + } + path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port) + http = httplib2.Http() + response, content = http.request(path, 'POST', body='XXX', + headers=headers) + self.assertEqual(response.status, 201) + image = json.loads(content)['image'] + self.assertEqual('active', image['status']) + image_id = image['id'] - filters = {'deleted': True, 'is_public': 'none', - 'status': 'pending_delete'} - recs = registry.get_images_detailed(filters=filters) - self.assertFalse(recs) + path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port, + image_id) + http = httplib2.Http() + response, content = http.request(path, 'DELETE') + self.assertEqual(response.status, 200) - client.delete_image(id) - recs = registry.get_images_detailed(filters=filters) - self.assertTrue(recs) - - filters = {'deleted': True, 'is_public': 'none'} - recs = registry.get_images_detailed(filters=filters) - self.assertTrue(recs) - for rec in recs: - self.assertEqual(rec['status'], 'pending_delete') + response, content = http.request(path, 'HEAD') + self.assertEqual(response.status, 200) + self.assertEqual('pending_delete', response['x-image-meta-status']) # NOTE(jkoelker) The build servers sometimes take longer than # 15 seconds to scrub. Give it up to 5 min, checking # checking every 15 seconds. When/if it flips to # deleted, bail immediatly. - deleted = set() - recs = [] for _ in xrange(3): time.sleep(5) - recs = registry.get_images_detailed(filters=filters) - self.assertTrue(recs) - - # NOTE(jkoelker) Reset the deleted set for this loop - deleted = set() - for rec in recs: - deleted.add(rec['status'] == 'deleted') - - if False not in deleted: + response, content = http.request(path, 'HEAD') + if response['x-image-meta-status'] == 'deleted' and \ + response['x-image-meta-deleted'] == 'True': break - - self.assertTrue(recs) - for rec in recs: - self.assertEqual(rec['status'], 'deleted') + else: + continue + else: + self.fail('image was never scrubbed') self.stop_servers() @@ -135,31 +95,31 @@ class TestScrubber(functional.FunctionalTest): self.cleanup() self.start_servers(delayed_delete=True, daemon=False) - client = self._get_client() - registry = self._get_registry_client() + headers = { + 'x-image-meta-name': 'test_image', + 'x-image-meta-is_public': 'true', + 'x-image-meta-disk_format': 'raw', + 'x-image-meta-container_format': 'ovf', + 'content-type': 'application/octet-stream', + } + path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port) + http = httplib2.Http() + response, content = http.request(path, 'POST', body='XXX', + headers=headers) + self.assertEqual(response.status, 201) + image = json.loads(content)['image'] + self.assertEqual('active', image['status']) + image_id = image['id'] - # add some images and ensure it was successful - img_ids = [] - for i in range(0, 3): - meta = client.add_image(TEST_IMAGE_META, TEST_IMAGE_DATA) - id = meta['id'] - img_ids.append(id) - filters = {'deleted': True, 'is_public': 'none', - 'status': 'pending_delete'} - recs = registry.get_images_detailed(filters=filters) - self.assertFalse(recs) + path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port, + image_id) + http = httplib2.Http() + response, content = http.request(path, 'DELETE') + self.assertEqual(response.status, 200) - # delete those images - for img_id in img_ids: - client.delete_image(img_id) - recs = registry.get_images_detailed(filters=filters) - self.assertTrue(recs) - - filters = {'deleted': True, 'is_public': 'none'} - recs = registry.get_images_detailed(filters=filters) - self.assertTrue(recs) - for rec in recs: - self.assertEqual(rec['status'], 'pending_delete') + response, content = http.request(path, 'HEAD') + self.assertEqual(response.status, 200) + self.assertEqual('pending_delete', response['x-image-meta-status']) # wait for the scrub time on the image to pass time.sleep(self.api_server.scrub_time) @@ -170,10 +130,20 @@ class TestScrubber(functional.FunctionalTest): exitcode, out, err = execute(cmd, raise_error=False) self.assertEqual(0, exitcode) - filters = {'deleted': True, 'is_public': 'none'} - recs = registry.get_images_detailed(filters=filters) - self.assertTrue(recs) - for rec in recs: - self.assertEqual(rec['status'], 'deleted') + # NOTE(jkoelker) The build servers sometimes take longer than + # 15 seconds to scrub. Give it up to 5 min, checking + # checking every 15 seconds. When/if it flips to + # deleted, bail immediatly. + for _ in xrange(3): + time.sleep(5) + + response, content = http.request(path, 'HEAD') + if response['x-image-meta-status'] == 'deleted' and \ + response['x-image-meta-deleted'] == 'True': + break + else: + continue + else: + self.fail('image was never scrubbed') self.stop_servers() diff --git a/glance/tests/functional/v1/test_misc.py b/glance/tests/functional/v1/test_misc.py index b9c0b6b323..14063b07e4 100644 --- a/glance/tests/functional/v1/test_misc.py +++ b/glance/tests/functional/v1/test_misc.py @@ -124,39 +124,3 @@ class TestMiscellaneous(functional.FunctionalTest): "in output: %s" % out) self.stop_servers() - - def test_api_treats_size_as_a_normal_property(self): - """ - A test for LP bug #825024 -- glance client currently - treats size as a normal property. - """ - - self.cleanup() - self.start_servers() - - # 1. POST /images with public image named Image1 - # attribute and no custom properties. Verify a 200 OK is returned - with tempfile.NamedTemporaryFile() as image_file: - image_file.write("XXX") - image_file.flush() - image_file_name = image_file.name - suffix = 'size=12345 --silent-upload < %s' % image_file_name - cmd = minimal_add_command(self.api_port, 'MyImage', suffix) - - exitcode, out, err = execute(cmd) - - image_id = out.strip().split(':')[1].strip() - self.assertEqual(0, exitcode) - self.assertTrue('Found non-settable field size. Removing.' in out) - self.assertTrue('Added new image with ID: %s' % image_id in out) - - # 2. Verify image added as public image - cmd = "bin/glance --port=%d show %s" % (self.api_port, image_id) - - exitcode, out, err = execute(cmd) - - self.assertEqual(0, exitcode) - lines = out.split("\n")[2:-1] - self.assertFalse("12345" in out) - - self.stop_servers() diff --git a/glance/tests/functional/v1/test_ssl.py b/glance/tests/functional/v1/test_ssl.py index 936254c34a..3689ec3883 100644 --- a/glance/tests/functional/v1/test_ssl.py +++ b/glance/tests/functional/v1/test_ssl.py @@ -37,8 +37,6 @@ import json import os import tempfile -from glance import client as glance_client -from glance.common import exception from glance.common import utils from glance.openstack.common import timeutils from glance.tests import functional @@ -1234,96 +1232,3 @@ class TestSSL(functional.FunctionalTest): self.assertEqual(response.status, 404) self.stop_servers() - - @skip_if_disabled - def test_certificate_validation(self): - """ - Check SSL client cerificate verification - """ - self.cleanup() - self.start_servers(**self.__dict__.copy()) - - # 0. GET /images - # Verify no public images - path = "https://%s:%d/v1/images" % ("127.0.0.1", self.api_port) - https = httplib2.Http(disable_ssl_certificate_validation=True) - response, content = https.request(path, 'GET') - self.assertEqual(response.status, 200) - self.assertEqual(content, '{"images": []}') - - # 1. POST /images with public image named Image1 - headers = {'Content-Type': 'application/octet-stream', - 'X-Image-Meta-Name': 'Image1', - 'X-Image-Meta-Status': 'active', - 'X-Image-Meta-Container-Format': 'ovf', - 'X-Image-Meta-Disk-Format': 'vdi', - 'X-Image-Meta-Size': '19', - 'X-Image-Meta-Is-Public': 'True'} - path = "https://%s:%d/v1/images" % ("127.0.0.1", self.api_port) - https = httplib2.Http(disable_ssl_certificate_validation=True) - response, content = https.request(path, 'POST', headers=headers) - self.assertEqual(response.status, 201) - data = json.loads(content) - - image_id = data['image']['id'] - - # 2. Attempt to delete the image *without* CA file - path = "https://%s:%d/v1/images" % ("127.0.0.1", self.api_port) - secure_cli = glance_client.Client(host="127.0.0.1", port=self.api_port, - use_ssl=True, insecure=False) - try: - secure_cli.delete_image(image_id) - self.fail("Client with no CA file deleted image %s" % image_id) - except exception.ClientConnectionError, e: - pass - - # 3. Delete the image with a secure client *with* CA file - secure_cli2 = glance_client.Client(host="127.0.0.1", - port=self.api_port, use_ssl=True, - ca_file=self.ca_file, - insecure=False) - try: - secure_cli2.delete_image(image_id) - except exception.ClientConnectionError, e: - self.fail("Secure client failed to delete image %s" % image_id) - - # Verify image is deleted - path = "https://%s:%d/v1/images" % ("127.0.0.1", self.api_port) - https = httplib2.Http(disable_ssl_certificate_validation=True) - response, content = https.request(path, 'GET') - self.assertEqual(response.status, 200) - self.assertEqual(content, '{"images": []}') - - # 4. POST another image - headers = {'Content-Type': 'application/octet-stream', - 'X-Image-Meta-Name': 'Image1', - 'X-Image-Meta-Status': 'active', - 'X-Image-Meta-Container-Format': 'ovf', - 'X-Image-Meta-Disk-Format': 'vdi', - 'X-Image-Meta-Size': '19', - 'X-Image-Meta-Is-Public': 'True'} - path = "https://%s:%d/v1/images" % ("127.0.0.1", self.api_port) - https = httplib2.Http(disable_ssl_certificate_validation=True) - response, content = https.request(path, 'POST', headers=headers) - self.assertEqual(response.status, 201) - data = json.loads(content) - - image_id = data['image']['id'] - - # 5. Delete the image with an insecure client - insecure_cli = glance_client.Client(host="127.0.0.1", - port=self.api_port, use_ssl=True, - insecure=True) - try: - insecure_cli.delete_image(image_id) - except exception.ClientConnectionError, e: - self.fail("Insecure client failed to delete image") - - # Verify image is deleted - path = "https://%s:%d/v1/images" % ("127.0.0.1", self.api_port) - https = httplib2.Http(disable_ssl_certificate_validation=True) - response, content = https.request(path, 'GET') - self.assertEqual(response.status, 200) - self.assertEqual(content, '{"images": []}') - - self.stop_servers() diff --git a/glance/tests/stubs.py b/glance/tests/stubs.py index 669c9a9fd9..18ba27f628 100644 --- a/glance/tests/stubs.py +++ b/glance/tests/stubs.py @@ -186,8 +186,6 @@ def stub_out_registry_and_store_server(stubs, base_dir): glance.common.client.BaseClient._sendable) stubs.Set(glance.common.client.BaseClient, '_sendable', fake_sendable) - stubs.Set(glance.common.client.ImageBodyIterator, '__iter__', - fake_image_iter) def stub_out_registry_server(stubs, **kwargs): @@ -212,5 +210,3 @@ def stub_out_registry_server(stubs, **kwargs): stubs.Set(glance.common.client.BaseClient, 'get_connection_type', fake_get_connection_type) - stubs.Set(glance.common.client.ImageBodyIterator, '__iter__', - fake_image_iter) diff --git a/glance/tests/unit/test_clients.py b/glance/tests/unit/test_clients.py index 20200b3a32..f50d3a510c 100644 --- a/glance/tests/unit/test_clients.py +++ b/glance/tests/unit/test_clients.py @@ -16,11 +16,7 @@ # under the License. import datetime -import os -import tempfile -from glance import client -from glance.common import client as base_client from glance.common import config from glance.common import exception from glance.common import utils @@ -30,7 +26,6 @@ from glance.db.sqlalchemy import models as db_models from glance.openstack.common import timeutils from glance.registry import client as rclient from glance.tests.unit import base -from glance.tests import utils as test_utils _gen_uuid = utils.generate_uuid @@ -42,105 +37,6 @@ UUID2 = _gen_uuid() config.parse_args() -class TestBadClients(test_utils.BaseTestCase): - - """Test exceptions raised for bad clients""" - - def test_bad_address(self): - """Test ClientConnectionError raised""" - c = client.Client("127.999.1.1") - self.assertRaises(exception.ClientConnectionError, - c.get_image, - 1) - - def test_ssl_no_key_file(self): - """ - Test that when doing SSL connection, a key file is - required if a cert file has been specified - """ - try: - with tempfile.NamedTemporaryFile() as cert_file: - cert_file.write("bogus-cert") - cert_file.flush() - c = client.Client("0.0.0.0", use_ssl=True, - cert_file=cert_file.name) - except exception.ClientConnectionError: - return - self.fail("Did not raise ClientConnectionError") - - def test_ssl_non_existing_key_file(self): - """ - Test that when doing SSL connection, a specified key - file is required to exist - """ - try: - c = client.Client("0.0.0.0", use_ssl=True, - key_file='nonexistingfile') - except exception.ClientConnectionError: - return - self.fail("Did not raise ClientConnectionError") - - def test_ssl_no_cert_file(self): - """ - Test that when doing SSL connection, a cert file is - required if a key file has been specified - """ - try: - with tempfile.NamedTemporaryFile() as key_file: - key_file.write("bogus-key") - key_file.flush() - c = client.Client("0.0.0.0", use_ssl=True, - key_file=key_file.name) - except exception.ClientConnectionError: - return - self.fail("Did not raise ClientConnectionError") - - def test_ssl_non_existing_cert_file(self): - """ - Test that when doing SSL connection, a cert file is - required to exist if specified - """ - try: - with tempfile.NamedTemporaryFile() as key_file: - key_file.write("bogus-key") - key_file.flush() - c = client.Client("0.0.0.0", use_ssl=True, - key_file=key_file.name, - cert_file='nonexistingfile') - except exception.ClientConnectionError: - return - self.fail("Did not raise ClientConnectionError") - - def test_ssl_non_existing_ca_file(self): - """ - Test that when doing SSL connection, a specified CA file exists - """ - try: - c = client.Client("0.0.0.0", use_ssl=True, - ca_file='nonexistingfile') - except exception.ClientConnectionError: - return - self.fail("Did not raise ClientConnectionError") - - def test_ssl_optional_ca_file(self): - """ - Test that when doing SSL connection, a cert file and key file are - required to exist if specified, but a CA file is optional. - """ - try: - with tempfile.NamedTemporaryFile() as key_file: - key_file.write("bogus-key") - key_file.flush() - with tempfile.NamedTemporaryFile() as cert_file: - cert_file.write("bogus-cert") - cert_file.flush() - c = client.Client("0.0.0.0", use_ssl=True, - key_file=key_file.name, - cert_file=cert_file.name) - except exception.ClientConnectionError: - self.fail("Raised ClientConnectionError when it should not") - - class TestRegistryClient(base.IsolatedUnitTest): """ @@ -861,7 +757,6 @@ class TestRegistryClient(base.IsolatedUnitTest): # Check a standard list, 4 images in db (2 deleted) images = self.client.get_images_detailed(filters={}) - self.assertEquals(len(images), 2) self.assertEqual(images[0]['id'], UUID4) self.assertEqual(images[1]['id'], UUID2) @@ -1137,980 +1032,3 @@ class TestRegistryClient(base.IsolatedUnitTest): """Tests deleting image members""" self.client.add_member(UUID2, 'pattieblack') self.assertTrue(self.client.delete_member(UUID2, 'pattieblack')) - - -class TestClient(base.IsolatedUnitTest): - - """ - Test proper actions made for both valid and invalid requests - against a Glance service - """ - - def setUp(self): - """Establish a clean test environment""" - super(TestClient, self).setUp() - db_api.configure_db() - self.client = client.Client("0.0.0.0") - self.FIXTURES = [ - {'id': UUID1, - 'name': 'fake image #1', - 'status': 'active', - 'disk_format': 'ami', - 'container_format': 'ami', - 'is_public': False, - 'created_at': timeutils.utcnow(), - 'updated_at': timeutils.utcnow(), - 'deleted_at': None, - 'deleted': False, - 'checksum': None, - 'size': 13, - 'location': "file:///%s/%s" % (self.test_dir, UUID1), - 'properties': {'type': 'kernel'}}, - {'id': UUID2, - 'name': 'fake image #2', - 'status': 'active', - 'disk_format': 'vhd', - 'container_format': 'ovf', - 'is_public': True, - 'created_at': timeutils.utcnow(), - 'updated_at': timeutils.utcnow(), - 'deleted_at': None, - 'deleted': False, - 'checksum': None, - 'size': 19, - 'location': "file:///%s/%s" % (self.test_dir, UUID2), - 'properties': {}}] - self.context = context.RequestContext(is_admin=True) - self.destroy_fixtures() - self.create_fixtures() - - def tearDown(self): - """Clear the test environment""" - super(TestClient, self).tearDown() - self.destroy_fixtures() - - def create_fixtures(self): - for fixture in self.FIXTURES: - db_api.image_create(self.context, fixture) - # We write a fake image file to the filesystem - with open("%s/%s" % (self.test_dir, fixture['id']), 'wb') as image: - image.write("chunk00000remainder") - image.flush() - - def destroy_fixtures(self): - # Easiest to just drop the models and re-create them... - db_models.unregister_models(db_api._ENGINE) - db_models.register_models(db_api._ENGINE) - - def test_get_image(self): - """Test a simple file backend retrieval works as expected""" - expected_image = 'chunk00000remainder' - expected_meta = {'id': UUID2, - 'name': 'fake image #2', - 'is_public': True, - 'disk_format': 'vhd', - 'container_format': 'ovf', - 'status': 'active', - 'size': 19, - 'properties': {}} - meta, image_chunks = self.client.get_image(UUID2) - - image_data = "" - for image_chunk in image_chunks: - image_data += image_chunk - - self.assertEquals(expected_image, image_data) - for k, v in expected_meta.items(): - self.assertEquals(v, meta[k]) - - def test_get_image_not_existing(self): - """Test retrieval of a non-existing image returns a 404""" - self.assertRaises(exception.NotFound, - self.client.get_image, - _gen_uuid()) - - def test_get_image_index_sort_container_format_desc(self): - """ - Tests that the client returns list of public images - sorted alphabetically by container_format in - descending order. - """ - UUID3 = _gen_uuid() - extra_fixture = {'id': UUID3, - 'status': 'active', - 'is_public': True, - 'disk_format': 'ami', - 'container_format': 'ami', - 'name': 'asdf', - 'size': 19, - 'checksum': None} - - db_api.image_create(self.context, extra_fixture) - - UUID4 = _gen_uuid() - extra_fixture = {'id': UUID4, - 'status': 'active', - 'is_public': True, - 'disk_format': 'iso', - 'container_format': 'bare', - 'name': 'xyz', - 'size': 20, - 'checksum': None} - - db_api.image_create(self.context, extra_fixture) - - images = self.client.get_images(sort_key='container_format', - sort_dir='desc') - - self.assertEquals(len(images), 3) - self.assertEquals(images[0]['id'], UUID2) - self.assertEquals(images[1]['id'], UUID4) - self.assertEquals(images[2]['id'], UUID3) - - def test_get_image_index(self): - """Test correct set of public image returned""" - fixture = {'id': UUID2, - 'name': 'fake image #2'} - images = self.client.get_images() - self.assertEquals(len(images), 1) - - for k, v in fixture.items(): - self.assertEquals(v, images[0][k]) - - def test_get_image_index_marker(self): - """Test correct set of public images returned with marker param.""" - UUID3 = _gen_uuid() - extra_fixture = {'id': UUID3, - 'status': 'saving', - 'is_public': True, - 'disk_format': 'vhd', - 'container_format': 'ovf', - 'name': 'new name! #123', - 'size': 19, - 'checksum': None} - - db_api.image_create(self.context, extra_fixture) - - UUID4 = _gen_uuid() - extra_fixture = {'id': UUID4, - 'status': 'saving', - 'is_public': True, - 'disk_format': 'vhd', - 'container_format': 'ovf', - 'name': 'new name! #125', - 'size': 19, - 'checksum': None} - - db_api.image_create(self.context, extra_fixture) - - images = self.client.get_images(marker=UUID4) - - self.assertEquals(len(images), 2) - self.assertEquals(images[0]['id'], UUID3) - self.assertEquals(images[1]['id'], UUID2) - - def test_get_image_index_invalid_marker(self): - """Test exception is raised when marker is invalid""" - self.assertRaises(exception.Invalid, - self.client.get_images, - marker=_gen_uuid()) - - def test_get_image_index_limit(self): - """Test correct number of public images returned with limit param.""" - extra_fixture = {'id': _gen_uuid(), - 'status': 'saving', - 'is_public': True, - 'disk_format': 'vhd', - 'container_format': 'ovf', - 'name': 'new name! #123', - 'size': 19, - 'checksum': None} - - db_api.image_create(self.context, extra_fixture) - - extra_fixture = {'id': _gen_uuid(), - 'status': 'saving', - 'is_public': True, - 'disk_format': 'vhd', - 'container_format': 'ovf', - 'name': 'new name! #125', - 'size': 19, - 'checksum': None} - - db_api.image_create(self.context, extra_fixture) - - images = self.client.get_images(limit=2) - self.assertEquals(len(images), 2) - - def test_get_image_index_marker_limit(self): - """Test correct set of images returned with marker/limit params.""" - UUID3 = _gen_uuid() - extra_fixture = {'id': UUID3, - 'status': 'saving', - 'is_public': True, - 'disk_format': 'vhd', - 'container_format': 'ovf', - 'name': 'new name! #123', - 'size': 19, - 'checksum': None} - - db_api.image_create(self.context, extra_fixture) - - extra_fixture = {'id': _gen_uuid(), - 'status': 'saving', - 'is_public': True, - 'disk_format': 'vhd', - 'container_format': 'ovf', - 'name': 'new name! #125', - 'size': 19, - 'checksum': None} - - db_api.image_create(self.context, extra_fixture) - - images = self.client.get_images(marker=UUID3, limit=1) - self.assertEquals(len(images), 1) - - self.assertEquals(images[0]['id'], UUID2) - - def test_get_image_index_by_base_attribute(self): - """Tests that an index call can be filtered by a base attribute""" - extra_fixture = {'id': _gen_uuid(), - 'status': 'active', - 'is_public': True, - 'disk_format': 'vhd', - 'container_format': 'ovf', - 'name': 'new name! #123', - 'size': 19, - 'checksum': None} - - db_api.image_create(self.context, extra_fixture) - - filters = {'name': 'new name! #123'} - images = self.client.get_images(filters=filters) - - self.assertEquals(len(images), 1) - self.assertEquals('new name! #123', images[0]['name']) - - def test_get_image_index_by_property(self): - """Tests that an index call can be filtered by a property""" - UUID3 = _gen_uuid() - extra_fixture = {'id': UUID3, - 'status': 'saving', - 'is_public': True, - 'disk_format': 'vhd', - 'container_format': 'ovf', - 'name': 'new name! #123', - 'size': 19, - 'checksum': None, - 'properties': {'p a': 'v a'}} - - db_api.image_create(self.context, extra_fixture) - - filters = {'property-p a': 'v a'} - images = self.client.get_images(filters=filters) - - self.assertEquals(len(images), 1) - self.assertEquals(images[0]['id'], UUID3) - - def test_get_image_details(self): - """Tests that the detailed info about public images returned""" - expected = {'id': UUID2, - 'name': 'fake image #2', - 'is_public': True, - 'disk_format': 'vhd', - 'container_format': 'ovf', - 'status': 'active', - 'size': 19, - 'properties': {}} - - images = self.client.get_images_detailed() - self.assertEquals(len(images), 1) - - for k, v in expected.items(): - self.assertEquals(v, images[0][k]) - - def test_get_image_details_marker_limit(self): - """Test detailed calls are filtered by marker/limit params.""" - UUID3 = _gen_uuid() - extra_fixture = {'id': UUID3, - 'status': 'saving', - 'is_public': True, - 'disk_format': 'vhd', - 'container_format': 'ovf', - 'name': 'new name! #123', - 'size': 19, - 'checksum': None} - - db_api.image_create(self.context, extra_fixture) - - extra_fixture = {'id': _gen_uuid(), - 'status': 'saving', - 'is_public': True, - 'disk_format': 'vhd', - 'container_format': 'ovf', - 'name': 'new name! #125', - 'size': 19, - 'checksum': None} - - db_api.image_create(self.context, extra_fixture) - - images = self.client.get_images_detailed(marker=UUID3, limit=1) - self.assertEquals(len(images), 1) - - self.assertEquals(images[0]['id'], UUID2) - - def test_get_image_details_invalid_marker(self): - """Test exception is raised when marker is invalid""" - self.assertRaises(exception.Invalid, - self.client.get_images_detailed, - marker=_gen_uuid()) - - def test_get_image_details_by_base_attribute(self): - """Tests that a detailed call can be filtered by a base attribute""" - extra_fixture = {'id': _gen_uuid(), - 'status': 'active', - 'is_public': True, - 'disk_format': 'vhd', - 'container_format': 'ovf', - 'name': 'new name! #123', - 'size': 19, - 'checksum': None} - - db_api.image_create(self.context, extra_fixture) - - filters = {'name': 'new name! #123'} - images = self.client.get_images_detailed(filters=filters) - self.assertEquals(len(images), 1) - - for image in images: - self.assertEquals('new name! #123', image['name']) - - def test_get_image_details_with_changes_since(self): - """Tests that a detailed call can be filtered by size_min""" - dt1 = timeutils.utcnow() - datetime.timedelta(1) - iso1 = timeutils.isotime(dt1) - - dt2 = timeutils.utcnow() + datetime.timedelta(1) - iso2 = timeutils.isotime(dt2) - - dt3 = timeutils.utcnow() + datetime.timedelta(2) - iso3 = timeutils.isotime(dt3) - - dt4 = timeutils.utcnow() + datetime.timedelta(3) - iso4 = timeutils.isotime(dt4) - - UUID3 = _gen_uuid() - extra_fixture = {'id': UUID3, - 'status': 'active', - 'is_public': True, - 'disk_format': 'vhd', - 'container_format': 'ovf', - 'name': 'fake image #3', - 'size': 18, - 'checksum': None} - - db_api.image_create(self.context, extra_fixture) - db_api.image_destroy(self.context, UUID3) - - UUID4 = _gen_uuid() - extra_fixture = {'id': UUID4, - 'status': 'active', - 'is_public': True, - 'disk_format': 'ami', - 'container_format': 'ami', - 'name': 'fake image #4', - 'size': 20, - 'checksum': None, - 'created_at': dt3, - 'updated_at': dt3} - - db_api.image_create(self.context, extra_fixture) - - # Check a standard list, 4 images in db (2 deleted) - images = self.client.get_images_detailed(filters={}) - self.assertEquals(len(images), 2) - self.assertEqual(images[0]['id'], UUID4) - self.assertEqual(images[1]['id'], UUID2) - - # Expect 3 images (1 deleted) - filters = {'changes-since': iso1} - images = self.client.get_images(filters=filters) - self.assertEquals(len(images), 3) - self.assertEqual(images[0]['id'], UUID4) - self.assertEqual(images[1]['id'], UUID3) # deleted - self.assertEqual(images[2]['id'], UUID2) - - # Expect 1 images (0 deleted) - filters = {'changes-since': iso2} - images = self.client.get_images_detailed(filters=filters) - self.assertEquals(len(images), 1) - self.assertEqual(images[0]['id'], UUID4) - - # Expect 0 images (0 deleted) - filters = {'changes-since': iso4} - images = self.client.get_images(filters=filters) - self.assertEquals(len(images), 0) - - def test_get_image_details_by_property(self): - """Tests that a detailed call can be filtered by a property""" - extra_fixture = {'id': _gen_uuid(), - 'status': 'saving', - 'is_public': True, - 'disk_format': 'vhd', - 'container_format': 'ovf', - 'name': 'new name! #123', - 'size': 19, - 'checksum': None, - 'properties': {'p a': 'v a'}} - - db_api.image_create(self.context, extra_fixture) - - filters = {'property-p a': 'v a'} - images = self.client.get_images_detailed(filters=filters) - self.assertEquals(len(images), 1) - - for image in images: - self.assertEquals('v a', image['properties']['p a']) - - def test_get_image_bad_filters_with_other_params(self): - """Tests that a detailed call can be filtered by a property""" - extra_fixture = {'id': _gen_uuid(), - 'status': 'saving', - 'is_public': True, - 'disk_format': 'vhd', - 'container_format': 'ovf', - 'name': 'new name! #123', - 'size': 19, - 'checksum': None, - 'properties': {'p a': 'v a'}} - - db_api.image_create(self.context, extra_fixture) - - images = self.client.get_images_detailed(filters=None, limit=1) - self.assertEquals(len(images), 1) - - def test_get_image_meta(self): - """Tests that the detailed info about an image returned""" - fixture = {'id': UUID2, - 'name': 'fake image #2', - 'is_public': True, - 'disk_format': 'vhd', - 'container_format': 'ovf', - 'status': 'active', - 'size': 19, - 'properties': {}} - - data = self.client.get_image_meta(UUID2) - - for k, v in fixture.items(): - self.assertEquals(v, data[k]) - - def test_get_image_iso_meta(self): - """Tests that the detailed info about an ISO image is returned""" - fixture = {'name': 'fake iso image', - 'is_public': False, - 'disk_format': 'iso', - 'container_format': 'bare', - 'status': 'active', - 'size': 19, - 'location': "http://localhost/glance-tests/3", - 'properties': {}} - - new_image = self.client.add_image(fixture) - new_image_id = new_image['id'] - - # Test all other attributes set - data = self.client.get_image_meta(new_image_id) - - del fixture['location'] - for k, v in fixture.items(): - self.assertEquals(v, data[k]) - - def test_get_image_non_existing(self): - """Tests that NotFound is raised when getting a non-existing image""" - self.assertRaises(exception.NotFound, - self.client.get_image, - _gen_uuid()) - - def test_add_image_without_location_or_raw_data(self): - """Tests client returns image as queued""" - fixture = {'name': 'fake public image', - 'is_public': True, - 'disk_format': 'vhd', - 'container_format': 'ovf', - } - image_meta = self.client.add_image(fixture) - self.assertEquals('queued', image_meta['status']) - self.assertEquals(0, image_meta['size']) - - def test_add_image_basic(self): - """Tests that we can add image metadata and returns the new id""" - fixture = {'name': 'fake public image', - 'is_public': True, - 'disk_format': 'vhd', - 'container_format': 'ovf', - 'size': 19, - 'location': "http://localhost/glance-tests/2", - } - new_image = self.client.add_image(fixture) - new_image_id = new_image['id'] - - # Test all other attributes set - data = self.client.get_image_meta(new_image_id) - - del fixture['location'] - for k, v in fixture.items(): - self.assertEquals(v, data[k]) - - # Test status was updated properly - self.assertTrue('status' in data.keys()) - self.assertEquals('active', data['status']) - - def test_add_image_with_properties(self): - """Tests that we can add image metadata with properties""" - fixture = {'name': 'fake public image', - 'is_public': True, - 'disk_format': 'vhd', - 'container_format': 'ovf', - 'size': 19, - 'location': "http://localhost/glance-tests/2", - 'properties': {'distro': 'Ubuntu 10.04 LTS'}, - } - new_image = self.client.add_image(fixture) - new_image_id = new_image['id'] - - # Test all other attributes set - data = self.client.get_image_meta(new_image_id) - - del fixture['location'] - for k, v in fixture.items(): - self.assertEquals(v, data[k]) - - # Test status was updated properly - self.assertTrue('status' in data) - self.assertEquals('active', data['status']) - - def test_add_image_with_iso_properties(self): - """Tests that we can add image metadata with ISO disk format""" - fixture = {'name': 'fake public iso', - 'is_public': True, - 'disk_format': 'iso', - 'container_format': 'bare', - 'size': 19, - 'location': "http://localhost/glance-tests/2", - 'properties': {'install': 'Bindows Heaven'}, - } - new_image = self.client.add_image(fixture) - new_image_id = new_image['id'] - - # Test all other attributes set - data = self.client.get_image_meta(new_image_id) - - del fixture['location'] - for k, v in fixture.items(): - self.assertEquals(v, data[k]) - - # Test status was updated properly - self.assertTrue('status' in data) - self.assertEquals('active', data['status']) - - def test_add_image_with_bad_iso_properties(self): - """ - Verify that ISO with invalid container format is rejected. - Intended to exercise error path once rather than be exhaustive - set of mismatches - """ - fixture = {'name': 'fake public iso', - 'is_public': True, - 'disk_format': 'iso', - 'container_format': 'vhd', - 'size': 19, - 'location': "http://localhost/glance-tests/3", - 'properties': {'install': 'Bindows Heaven'}, - } - - self.assertRaises(exception.Invalid, - self.client.add_image, - fixture) - - def test_add_image_already_exists(self): - """Tests proper exception is raised if image with ID already exists""" - fixture = {'id': UUID2, - 'name': 'fake public image', - 'is_public': True, - 'disk_format': 'vhd', - 'container_format': 'ovf', - 'status': 'bad status', - 'size': 19, - 'location': "http://localhost/glance-tests/2", - } - - self.assertRaises(exception.Duplicate, - self.client.add_image, - fixture) - - def test_add_image_with_bad_status(self): - """Tests a bad status is set to a proper one by server""" - fixture = {'name': 'fake public image', - 'is_public': True, - 'disk_format': 'vhd', - 'container_format': 'ovf', - 'status': 'bad status', - 'size': 19, - 'location': "http://localhost/glance-tests/2", - } - - new_image = self.client.add_image(fixture) - self.assertEquals(new_image['status'], 'active') - - def test_add_image_with_image_data_as_string(self): - """Tests can add image by passing image data as string""" - fixture = {'name': 'fake public image', - 'is_public': True, - 'disk_format': 'vhd', - 'container_format': 'ovf', - 'size': 19, - 'properties': {'distro': 'Ubuntu 10.04 LTS'}, - } - - image_data_fixture = r"chunk00000remainder" - - new_image = self.client.add_image(fixture, image_data_fixture) - new_image_id = new_image['id'] - - new_meta, new_image_chunks = self.client.get_image(new_image_id) - - new_image_data = "" - for image_chunk in new_image_chunks: - new_image_data += image_chunk - - self.assertEquals(image_data_fixture, new_image_data) - for k, v in fixture.items(): - self.assertEquals(v, new_meta[k]) - - def add_image_with_image_data_as_file(self, sendfile): - """Tests can add image by passing image data as file""" - fixture = {'name': 'fake public image', - 'is_public': True, - 'disk_format': 'vhd', - 'container_format': 'ovf', - 'size': 19, - 'properties': {'distro': 'Ubuntu 10.04 LTS'}, - } - - self.client.stub_force_sendfile = sendfile - - image_data_fixture = r"chunk00000remainder" - - tmp_image_filepath = '/tmp/rubbish-image' - - if os.path.exists(tmp_image_filepath): - os.unlink(tmp_image_filepath) - - tmp_file = open(tmp_image_filepath, 'wb') - tmp_file.write(image_data_fixture) - tmp_file.close() - - new_image = self.client.add_image(fixture, open(tmp_image_filepath)) - new_image_id = new_image['id'] - - if os.path.exists(tmp_image_filepath): - os.unlink(tmp_image_filepath) - - new_meta, new_image_chunks = self.client.get_image(new_image_id) - - new_image_data = "" - for image_chunk in new_image_chunks: - new_image_data += image_chunk - - self.assertEquals(image_data_fixture, new_image_data) - for k, v in fixture.items(): - self.assertEquals(v, new_meta[k]) - - def test_added_image_notdoubled(self): - """Tests contents of an added small seekable image, when using ssl""" - fixture = {'name': 'fake public image', - 'disk_format': 'vhd', - 'container_format': 'ovf' - } - - tmp_fp = tempfile.TemporaryFile('w+') - image_data_fixture = _gen_uuid() - tmp_fp.write(image_data_fixture) - tmp_fp.seek(0) - - self.client.use_ssl = True - new_image = self.client.add_image(fixture, tmp_fp) - new_image_id = new_image['id'] - - tmp_fp.close() - - new_meta, new_image_chunks = self.client.get_image(new_image_id) - new_image_data = "" - for chunk in new_image_chunks: - new_image_data += chunk - - self.assertEquals(image_data_fixture, new_image_data) - - @test_utils.skip_if(not base_client.SENDFILE_SUPPORTED, - 'sendfile not supported') - def test_add_image_with_image_data_as_file_with_sendfile(self): - self.add_image_with_image_data_as_file(sendfile=True) - - def test_add_image_with_image_data_as_file_without_sendfile(self): - self.add_image_with_image_data_as_file(sendfile=False) - - def _add_image_as_iterable(self): - fixture = {'name': 'fake public image', - 'is_public': True, - 'disk_format': 'vhd', - 'container_format': 'ovf', - 'size': 10 * 65536, - 'properties': {'distro': 'Ubuntu 10.04 LTS'}, - } - - class Zeros(object): - def __init__(self, chunks): - self.chunks = chunks - self.zeros = open('/dev/zero', 'rb') - - def __iter__(self): - while self.chunks > 0: - self.chunks -= 1 - chunk = self.zeros.read(65536) - yield chunk - - new_image = self.client.add_image(fixture, Zeros(10)) - new_image_id = new_image['id'] - - new_meta, new_image_chunks = self.client.get_image(new_image_id) - - return (fixture, new_meta, new_image_chunks) - - def _verify_image_iterable(self, fixture, meta, chunks): - image_data_len = 0 - for image_chunk in chunks: - image_data_len += len(image_chunk) - self.assertEquals(10 * 65536, image_data_len) - - for k, v in fixture.items(): - self.assertEquals(v, meta[k]) - - def test_add_image_with_image_data_as_iterable(self): - """Tests we can add image by passing image data as an iterable""" - fixture, new_meta, new_chunks = self._add_image_as_iterable() - - self._verify_image_iterable(fixture, new_meta, new_chunks) - - def test_roundtrip_image_with_image_data_as_iterable(self): - """Tests we can roundtrip image as an iterable""" - fixture, new_meta, new_chunks = self._add_image_as_iterable() - - # duplicate directly from iterable returned from get - dup_image = self.client.add_image(fixture, new_chunks) - dup_image_id = dup_image['id'] - - roundtrip_meta, roundtrip_chunks = self.client.get_image(dup_image_id) - - self._verify_image_iterable(fixture, roundtrip_meta, roundtrip_chunks) - - def test_add_image_with_image_data_as_string_and_no_size(self): - """Tests add image by passing image data as string w/ no size attr""" - fixture = {'name': 'fake public image', - 'is_public': True, - 'disk_format': 'vhd', - 'container_format': 'ovf', - 'properties': {'distro': 'Ubuntu 10.04 LTS'}, - } - - image_data_fixture = r"chunk00000remainder" - - new_image = self.client.add_image(fixture, image_data_fixture) - new_image_id = new_image['id'] - new_meta, new_image_chunks = self.client.get_image(new_image_id) - - new_image_data = "" - for image_chunk in new_image_chunks: - new_image_data += image_chunk - - self.assertEquals(image_data_fixture, new_image_data) - for k, v in fixture.items(): - self.assertEquals(v, new_meta[k]) - - self.assertEquals(19, new_meta['size']) - - def test_add_image_with_bad_store(self): - """Tests BadRequest raised when supplying bad store name in meta""" - fixture = {'name': 'fake public image', - 'is_public': True, - 'disk_format': 'vhd', - 'container_format': 'ovf', - 'size': 19, - 'store': 'bad', - 'properties': {'distro': 'Ubuntu 10.04 LTS'}, - } - - image_data_fixture = r"chunk00000remainder" - - self.assertRaises(exception.Invalid, - self.client.add_image, - fixture, - image_data_fixture) - - def test_add_image_with_unsupported_feature(self): - """Tests that UnsupportedHeaderFeature is raised when image is added""" - fixture = { - 'name': 'fake public image', - 'is_public': True, - 'disk_format': 'vhd', - 'container_format': 'ovf', - 'size': 19, - 'location': "http://localhost/glance-tests/2" - } - - feature_fixture = { - 'content-type': 'bad content type', - 'content-length': '0', - 'x-image-meta-size': '0' - } - - for k, v in feature_fixture.items(): - self.assertRaises(exception.UnsupportedHeaderFeature, - self.client.add_image, - None, - features={k: v}) - - def test_update_image(self): - """Tests that the /images PUT registry API updates the image""" - fixture = {'name': 'fake public image #2', - 'disk_format': 'vmdk'} - - self.assertTrue(self.client.update_image(UUID2, fixture)) - - # Test all other attributes set - data = self.client.get_image_meta(UUID2) - - for k, v in fixture.items(): - self.assertEquals(v, data[k]) - - def test_update_image_not_existing(self): - """Tests non existing image update doesn't work""" - fixture = {'name': 'fake public image', - 'is_public': True, - 'disk_format': 'vhd', - 'container_format': 'ovf', - 'status': 'bad status', - } - - self.assertRaises(exception.NotFound, - self.client.update_image, - _gen_uuid(), - fixture) - - def test_update_image_with_unsupported_feature(self): - """Tests that UnsupportedHeaderFeature is raised during update""" - fixture = { - 'name': 'fake public image #2' - } - - feature_fixture = { - 'content-type': 'bad content-type', - 'content-length': '0', - 'x-image-meta-size': '0' - } - - for k, v in feature_fixture.items(): - self.assertRaises(exception.UnsupportedHeaderFeature, - self.client.update_image, - UUID2, - image_meta=fixture, - features={k: v}) - - def test_delete_image(self): - """Tests that image metadata is deleted properly""" - # Grab the original number of images - orig_num_images = len(self.client.get_images()) - - # Delete image #2 - self.assertTrue(self.client.delete_image(UUID2)) - - # Verify one less image - new_num_images = len(self.client.get_images()) - - self.assertEquals(new_num_images, orig_num_images - 1) - - def test_delete_image_not_existing(self): - """Tests cannot delete non-existing image""" - self.assertRaises(exception.NotFound, - self.client.delete_image, - _gen_uuid()) - - def test_get_member_images(self): - """Tests getting image members""" - memb_list = self.client.get_image_members(UUID2) - num_members = len(memb_list) - self.assertEquals(num_members, 0) - - def test_get_image_members_not_existing(self): - """Tests getting non-existent image members""" - self.assertRaises(exception.NotFound, - self.client.get_image_members, - _gen_uuid()) - - def test_get_member_images(self): - """Tests getting member images""" - memb_list = self.client.get_member_images('pattieblack') - num_members = len(memb_list) - self.assertEquals(num_members, 0) - - def test_add_replace_members(self): - """Tests replacing image members""" - self.assertTrue(self.client.add_member(UUID2, 'pattieblack')) - self.assertTrue(self.client.replace_members(UUID2, - dict(member_id='pattieblack2'))) - - def test_add_delete_member(self): - """Tests deleting image members""" - self.assertTrue(self.client.add_member(UUID2, 'pattieblack')) - self.assertTrue(self.client.delete_member(UUID2, 'pattieblack')) - - -class TestConfigureClientFromURL(test_utils.BaseTestCase): - - def setUp(self): - super(TestConfigureClientFromURL, self).setUp() - self.client = client.Client("0.0.0.0") - - def assertConfiguration(self, url, host, port, use_ssl, doc_root): - self.client.configure_from_url(url) - self.assertEquals(host, self.client.host) - self.assertEquals(port, self.client.port) - self.assertEquals(use_ssl, self.client.use_ssl) - self.assertEquals(doc_root, self.client.doc_root) - - def test_version_specified_in_url(self): - self.assertConfiguration( - url='http://www.example.com/v1', - host='www.example.com', - port=80, - use_ssl=False, - doc_root='/v1' - ) - - def test_no_port_no_ssl_no_doc_root(self): - self.assertConfiguration( - url='http://www.example.com', - host='www.example.com', - port=80, - use_ssl=False, - doc_root='/v1' - ) - - def test_port_ssl_doc_root(self): - self.assertConfiguration( - url='https://www.example.com:8000/prefix/', - host='www.example.com', - port=8000, - use_ssl=True, - doc_root='/prefix/v1' - ) diff --git a/setup.py b/setup.py index cfaacd27c0..884aa87bf4 100644 --- a/setup.py +++ b/setup.py @@ -44,8 +44,7 @@ setuptools.setup( 'Programming Language :: Python :: 2.6', 'Environment :: No Input/Output (Daemon)', ], - scripts=['bin/glance', - 'bin/glance-api', + scripts=['bin/glance-api', 'bin/glance-cache-prefetcher', 'bin/glance-cache-pruner', 'bin/glance-cache-manage',