Merge trunk
This commit is contained in:
commit
a7007ae849
@ -60,6 +60,7 @@ if os.path.exists(os.path.join(possible_topdir, 'glance', '__init__.py')):
|
||||
sys.path.insert(0, possible_topdir)
|
||||
|
||||
from glance.client import Client
|
||||
from glance.registry.db.api import DISK_FORMATS, CONTAINER_FORMATS
|
||||
|
||||
|
||||
def die(msg):
|
||||
@ -78,6 +79,14 @@ def parse_args():
|
||||
parser.add_argument('--type', metavar='TYPE', default='raw',
|
||||
help='Type of Image [kernel, ramdisk, machine, raw] '
|
||||
'(default: %default)')
|
||||
parser.add_argument('--disk-format', metavar='DISK_FORMAT', default=None,
|
||||
choices=DISK_FORMATS,
|
||||
help='Disk format of Image [%s] '
|
||||
'(default: %%default)' % ','.join(DISK_FORMATS))
|
||||
parser.add_argument('--container-format', metavar='CONTAINER_FORMAT',
|
||||
default=None, choices=CONTAINER_FORMATS,
|
||||
help='Disk format of Image [%s] '
|
||||
'(default: %%default)' % ','.join(CONTAINER_FORMATS))
|
||||
parser.add_argument('--kernel', metavar='KERNEL',
|
||||
help='ID of kernel associated with this machine image')
|
||||
parser.add_argument('--ramdisk', metavar='RAMDISK',
|
||||
@ -89,7 +98,8 @@ def parse_args():
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
meta = {'name': args.name, 'type': args.type, 'is_public': True,
|
||||
meta = {'name': args.name,
|
||||
'is_public': True,
|
||||
'properties': {}}
|
||||
|
||||
if args.kernel:
|
||||
@ -98,9 +108,22 @@ def main():
|
||||
if args.ramdisk:
|
||||
meta['properties']['ramdisk_id'] = args.ramdisk
|
||||
|
||||
if args.type:
|
||||
meta['properties']['type'] = args.type
|
||||
|
||||
if args.disk_format:
|
||||
meta['disk_format'] = args.disk_format
|
||||
|
||||
if args.container_format:
|
||||
meta['container_format'] = args.container_format
|
||||
|
||||
client = Client(args.host, args.port)
|
||||
with open(args.filename) as f:
|
||||
new_meta = client.add_image(meta, f)
|
||||
try:
|
||||
new_meta = client.add_image(meta, f)
|
||||
except Exception, e:
|
||||
print "Failed to add new image. Got error: ", str(e)
|
||||
return 1
|
||||
|
||||
print 'Stored image. Got identifier: %s' % pprint.pformat(new_meta)
|
||||
|
||||
|
99
doc/source/formats.rst
Normal file
99
doc/source/formats.rst
Normal file
@ -0,0 +1,99 @@
|
||||
..
|
||||
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.
|
||||
|
||||
Disk and Container Formats
|
||||
==========================
|
||||
|
||||
When adding an image to Glance, you are may specify what the virtual
|
||||
machine image's *disk format* and *container format* are.
|
||||
|
||||
This document explains exactly what these formats are.
|
||||
|
||||
Disk Format
|
||||
-----------
|
||||
|
||||
The disk format of a virtual machine image is the format of the underlying
|
||||
disk image. Virtual appliance vendors have different formats for laying out
|
||||
the information contained in a virtual machine disk image.
|
||||
|
||||
You can set your image's container format to one of the following:
|
||||
|
||||
* **raw**
|
||||
|
||||
This is an unstructured disk image format
|
||||
|
||||
* **vhd**
|
||||
|
||||
This is the VHD disk format, a common disk format used by virtual machine
|
||||
monitors from VMWare, Xen, Microsoft, VirtualBox, and others
|
||||
|
||||
* **vmdk**
|
||||
|
||||
Another common disk format supported by many common virtual machine monitors
|
||||
|
||||
* **vdi**
|
||||
|
||||
A disk format supported by VirtualBox virtual machine monitor and the QEMU
|
||||
emulator
|
||||
|
||||
* **qcow2**
|
||||
|
||||
A disk format supported by the QEMU emulator that can expand dynamically and
|
||||
supports Copy on Write
|
||||
|
||||
* **aki**
|
||||
|
||||
This indicates what is stored in Glance is an Amazon kernel image
|
||||
|
||||
* **ari**
|
||||
|
||||
This indicates what is stored in Glance is an Amazon ramdisk image
|
||||
|
||||
* **ami**
|
||||
|
||||
This indicates what is stored in Glance is an Amazon machine image
|
||||
|
||||
Container Format
|
||||
----------------
|
||||
|
||||
The container format refers to whether the virtual machine image is in a
|
||||
file format that also contains metadata about the actual virtual machine.
|
||||
|
||||
There are two main types of container formats: OVF and Amazon's AMI. In
|
||||
addition, a virtual machine image may have no container format at all --
|
||||
basically, it's just a blob of unstructured data...
|
||||
|
||||
You can set your image's container format to one of the following:
|
||||
|
||||
* **ovf**
|
||||
|
||||
This is the OVF container format
|
||||
|
||||
* **bare**
|
||||
|
||||
This indicates there is no container or metadata envelope for the image
|
||||
|
||||
* **aki**
|
||||
|
||||
This indicates what is stored in Glance is an Amazon kernel image
|
||||
|
||||
* **ari**
|
||||
|
||||
This indicates what is stored in Glance is an Amazon ramdisk image
|
||||
|
||||
* **ami**
|
||||
|
||||
This indicates what is stored in Glance is an Amazon machine image
|
@ -78,4 +78,6 @@ Glance registry servers are servers that conform to the Glance Registry API.
|
||||
Glance ships with a reference implementation of a registry server that
|
||||
complies with this API (``glance-registry``).
|
||||
|
||||
For more details on Glance's architecture see :doc:`here <architecture>`
|
||||
For more details on Glance's architecture see :doc:`here <architecture>`. For
|
||||
more information on what a Glance registry server is, see
|
||||
:doc:`here <registries>`.
|
||||
|
@ -41,7 +41,8 @@ mapping in the following format::
|
||||
{'images': [
|
||||
{'uri': 'http://glance.example.com/images/1',
|
||||
'name': 'Ubuntu 10.04 Plain',
|
||||
'type': 'kernel',
|
||||
'disk_format': 'vhd',
|
||||
'container_format': 'ovf',
|
||||
'size': '5368709120'}
|
||||
...]}
|
||||
|
||||
@ -63,9 +64,10 @@ JSON-encoded mapping in the following format::
|
||||
{'images': [
|
||||
{'uri': 'http://glance.example.com/images/1',
|
||||
'name': 'Ubuntu 10.04 Plain 5GB',
|
||||
'type': 'kernel',
|
||||
'disk_format': 'vhd',
|
||||
'container_format': 'ovf',
|
||||
'size': '5368709120',
|
||||
'store': 'swift',
|
||||
'location': 'swift://account:key/container/image.tar.gz.0',
|
||||
'created_at': '2010-02-03 09:34:01',
|
||||
'updated_at': '2010-02-03 09:34:01',
|
||||
'deleted_at': '',
|
||||
@ -111,14 +113,15 @@ following shows an example of the HTTP headers returned from the above
|
||||
|
||||
x-image-meta-uri http://glance.example.com/images/1
|
||||
x-image-meta-name Ubuntu 10.04 Plain 5GB
|
||||
x-image-meta-type kernel
|
||||
x-image-meta-disk-format vhd
|
||||
x-image-meta-container-format ovf
|
||||
x-image-meta-size 5368709120
|
||||
x-image-meta-store swift
|
||||
x-image-meta-location swift://account:key/container/image.tar.gz.0
|
||||
x-image-meta-created_at 2010-02-03 09:34:01
|
||||
x-image-meta-updated_at 2010-02-03 09:34:01
|
||||
x-image-meta-deleted_at
|
||||
x-image-meta-status available
|
||||
x-image-meta-is_public True
|
||||
x-image-meta-is-public True
|
||||
x-image-meta-property-distro Ubuntu 10.04 LTS
|
||||
|
||||
.. note::
|
||||
@ -160,14 +163,15 @@ returned from the above ``GET`` request::
|
||||
|
||||
x-image-meta-uri http://glance.example.com/images/1
|
||||
x-image-meta-name Ubuntu 10.04 Plain 5GB
|
||||
x-image-meta-type kernel
|
||||
x-image-meta-disk-format vhd
|
||||
x-image-meta-container-format ovf
|
||||
x-image-meta-size 5368709120
|
||||
x-image-meta-store swift
|
||||
x-image-meta-location swift://account:key/container/image.tar.gz.0
|
||||
x-image-meta-created_at 2010-02-03 09:34:01
|
||||
x-image-meta-updated_at 2010-02-03 09:34:01
|
||||
x-image-meta-deleted_at
|
||||
x-image-meta-status available
|
||||
x-image-meta-is_public True
|
||||
x-image-meta-is-public True
|
||||
x-image-meta-property-distro Ubuntu 10.04 LTS
|
||||
|
||||
.. note::
|
||||
@ -254,10 +258,19 @@ The list of metadata headers that Glance accepts are listed below.
|
||||
store that is marked default. See the configuration option ``default_store``
|
||||
for more information.
|
||||
|
||||
* ``x-image-meta-type``
|
||||
* ``x-image-meta-disk-format``
|
||||
|
||||
This header is required. Valid values are one of ``kernel``, ``machine``,
|
||||
``raw``, or ``ramdisk``.
|
||||
This header is optional. Valid values are one of ``aki``, ``ari``, ``ami``,
|
||||
``raw``, ``vhd``, ``vdi``, ``qcow2``, or ``vmdk``.
|
||||
|
||||
For more information, see :doc:`About Disk and Container Formats <formats>`
|
||||
|
||||
* ``x-image-meta-container-format``
|
||||
|
||||
This header is optional. Valid values are one of ``aki``, ``ari``, ``ami``,
|
||||
``bare``, or ``ovf``.
|
||||
|
||||
For more information, see :doc:`About Disk and Container Formats <formats>`
|
||||
|
||||
* ``x-image-meta-size``
|
||||
|
||||
@ -271,7 +284,7 @@ The list of metadata headers that Glance accepts are listed below.
|
||||
When not present, Glance will calculate the image's size based on the size
|
||||
of the request body.
|
||||
|
||||
* ``x-image-meta-is_public``
|
||||
* ``x-image-meta-is-public``
|
||||
|
||||
This header is optional.
|
||||
|
||||
|
@ -50,6 +50,7 @@ Concepts
|
||||
identifiers
|
||||
registries
|
||||
statuses
|
||||
formats
|
||||
|
||||
Using Glance
|
||||
============
|
||||
|
@ -24,44 +24,9 @@ Glance REST-like API for image metadata.
|
||||
Glance comes with a server program ``glance-registry`` that acts
|
||||
as a reference implementation of a Glance Registry.
|
||||
|
||||
Using ``glance-registry``, the Glance Registry reference implementation
|
||||
-----------------------------------------------------------------------
|
||||
|
||||
As mentioned above, ``glance-registry`` is the reference registry
|
||||
server implementation that ships with Glance. It uses a SQL database
|
||||
to store information about an image, and publishes this information
|
||||
via an HTTP/REST-like interface.
|
||||
|
||||
Starting the server
|
||||
*******************
|
||||
|
||||
Starting the Glance registry server is trivial. Simply call the program
|
||||
from the command line, as the following example shows::
|
||||
|
||||
$> glance-registry
|
||||
(5588) wsgi starting up on http://0.0.0.0:9191/
|
||||
|
||||
Configuring the server
|
||||
**********************
|
||||
|
||||
There are a few options that can be supplied to the registry server when
|
||||
starting it up:
|
||||
|
||||
* ``verbose``
|
||||
|
||||
Show more verbose/debugging output
|
||||
|
||||
* ``sql_connection``
|
||||
|
||||
A proper SQLAlchemy connection string as described `here <http://www.sqlalchemy.org/docs/05/reference/sqlalchemy/connections.html?highlight=engine#sqlalchemy.create_engine>`_
|
||||
|
||||
* ``registry_host``
|
||||
|
||||
Address of the host the registry runs on. Defaults to 0.0.0.0.
|
||||
|
||||
* ``registry_port``
|
||||
|
||||
Port the registry server listens on. Defaults to 9191.
|
||||
Please see the document :doc:`on Controlling Servers <controllingservers>`
|
||||
for more information on starting up the Glance registry server that ships
|
||||
with Glance.
|
||||
|
||||
Glance Registry API
|
||||
-------------------
|
||||
@ -81,6 +46,38 @@ The following is a brief description of the Glance API::
|
||||
PUT /images/<ID> Update metadata about an existing image
|
||||
DELETE /images/<ID> Remove an image's metadata from the registry
|
||||
|
||||
``POST /images``
|
||||
----------------
|
||||
|
||||
The body of the request will be a JSON-encoded set of data about
|
||||
the image to add to the registry. It will be in the following format::
|
||||
|
||||
{'image':
|
||||
{'id': <ID>|None,
|
||||
'name': <NAME>,
|
||||
'status': <STATUS>,
|
||||
'disk_format': <DISK_FORMAT>,
|
||||
'container_format': <CONTAINER_FORMAT>,
|
||||
'properties': [ ... ]
|
||||
}
|
||||
}
|
||||
|
||||
The request shall validate the following conditions and return a
|
||||
``400 Bad request`` when any of the conditions are not met:
|
||||
|
||||
* ``status`` must be non-empty, and must be one of **active**, **saving**,
|
||||
**queued**, or **killed**
|
||||
|
||||
* ``disk_format`` must be non-empty, and must be one of **ari**, **aki**,
|
||||
**ami**, **raw**, **vhd**, **vdi**, **qcow2**, or **vmdk**
|
||||
|
||||
* ``container_format`` must be non-empty, and must be on of **ari**,
|
||||
**aki**, **ami**, **bare**, or **ovf**
|
||||
|
||||
* If ``disk_format`` *or* ``container_format`` is **ari**, **aki**,
|
||||
**ami**, then *both* ``disk_format`` and ``container_format`` must be
|
||||
the same.
|
||||
|
||||
Examples
|
||||
********
|
||||
|
||||
|
@ -154,8 +154,10 @@ class BaseClient(object):
|
||||
raise exception.Duplicate(res.read())
|
||||
elif status_code == httplib.BAD_REQUEST:
|
||||
raise exception.Invalid(res.read())
|
||||
elif status_code == httplib.INTERNAL_SERVER_ERROR:
|
||||
raise Exception("Internal Server error: %s" % res.read())
|
||||
else:
|
||||
raise Exception("Unknown error occurred! %s" % res.__dict__)
|
||||
raise Exception("Unknown error occurred! %s" % res.read())
|
||||
|
||||
except (socket.error, IOError), e:
|
||||
raise ClientConnectionError("Unable to connect to "
|
||||
@ -264,13 +266,17 @@ class Client(BaseClient):
|
||||
"""
|
||||
Updates Glance's information about an image
|
||||
"""
|
||||
if image_meta:
|
||||
if 'image' not in image_meta:
|
||||
image_meta = dict(image=image_meta)
|
||||
if image_meta is None:
|
||||
image_meta = {}
|
||||
|
||||
headers = utils.image_meta_to_http_headers(image_meta or {})
|
||||
headers = utils.image_meta_to_http_headers(image_meta)
|
||||
|
||||
if image_data:
|
||||
body = image_data
|
||||
headers['content-type'] = 'application/octet-stream'
|
||||
else:
|
||||
body = None
|
||||
|
||||
body = image_data
|
||||
res = self.do_request("PUT", "/images/%s" % image_id, body, headers)
|
||||
data = json.loads(res.read())
|
||||
return data['image']
|
||||
|
@ -40,9 +40,14 @@ BASE = models.BASE
|
||||
BASE_MODEL_ATTRS = set(['id', 'created_at', 'updated_at', 'deleted_at',
|
||||
'deleted'])
|
||||
|
||||
IMAGE_ATTRS = BASE_MODEL_ATTRS | set(['name', 'type', 'status', 'size',
|
||||
IMAGE_ATTRS = BASE_MODEL_ATTRS | set(['name', 'status', 'size',
|
||||
'disk_format', 'container_format',
|
||||
'is_public', 'location'])
|
||||
|
||||
CONTAINER_FORMATS = ['ami', 'ari', 'aki', 'bare', 'ovf']
|
||||
DISK_FORMATS = ['ami', 'ari', 'aki', 'vhd', 'vmdk', 'raw', 'qcow2', 'vdi']
|
||||
STATUSES = ['active', 'saving', 'queued', 'killed']
|
||||
|
||||
|
||||
def configure_db(options):
|
||||
"""
|
||||
@ -152,19 +157,36 @@ def validate_image(values):
|
||||
:param values: Mapping of image metadata to check
|
||||
"""
|
||||
|
||||
image_type = values.get('type', None)
|
||||
if image_type and image_type not in (
|
||||
'machine', 'kernel', 'ramdisk', 'raw', 'vhd'):
|
||||
msg = "Invalid image type '%s' for image." % image_type
|
||||
raise exception.Invalid(msg)
|
||||
status = values.get('status')
|
||||
disk_format = values.get('disk_format')
|
||||
container_format = values.get('container_format')
|
||||
|
||||
status = values.get('status', None)
|
||||
if not status:
|
||||
msg = "Image status is required."
|
||||
raise exception.Invalid(msg)
|
||||
if status not in ('active', 'queued', 'killed', 'saving'):
|
||||
|
||||
if status not in STATUSES:
|
||||
msg = "Invalid image status '%s' for image." % status
|
||||
raise exception.Invalid(msg)
|
||||
|
||||
if disk_format and disk_format not in DISK_FORMATS:
|
||||
msg = "Invalid disk format '%s' for image." % disk_format
|
||||
raise exception.Invalid(msg)
|
||||
|
||||
if container_format and container_format not in CONTAINER_FORMATS:
|
||||
msg = "Invalid container format '%s' for image." % container_format
|
||||
raise exception.Invalid(msg)
|
||||
|
||||
if disk_format in ('aki', 'ari', 'ami') or\
|
||||
container_format in ('aki', 'ari', 'ami'):
|
||||
if container_format != disk_format:
|
||||
msg = ("Invalid mix of disk and container formats. "
|
||||
"When setting a disk or container format to "
|
||||
"one of 'ami', 'ari', or 'ami', the container "
|
||||
"and disk formats must match.")
|
||||
raise exception.Invalid(msg)
|
||||
|
||||
|
||||
def _image_update(context, values, image_id):
|
||||
"""Used internally by image_create and image_update
|
||||
@ -174,27 +196,35 @@ def _image_update(context, values, image_id):
|
||||
:param image_id: If None, create the image, otherwise, find and update it
|
||||
"""
|
||||
|
||||
# Validate the attributes before we go any further. From my investigation,
|
||||
# the @validates decorator does not validate on new records, only on
|
||||
# existing records, which is, well, idiotic.
|
||||
validate_image(values)
|
||||
|
||||
session = get_session()
|
||||
with session.begin():
|
||||
_drop_protected_attrs(models.Image, values)
|
||||
|
||||
if 'size' in values:
|
||||
values['size'] = int(values['size'])
|
||||
|
||||
values['is_public'] = bool(values.get('is_public', False))
|
||||
# Remove the properties passed in the values mapping. We
|
||||
# handle properties separately from base image attributes,
|
||||
# and leaving properties in the values mapping will cause
|
||||
# a SQLAlchemy model error because SQLAlchemy expects the
|
||||
# properties attribute of an Image model to be a list and
|
||||
# not a dict.
|
||||
properties = values.pop('properties', {})
|
||||
|
||||
if image_id:
|
||||
image_ref = image_get(context, image_id, session=session)
|
||||
else:
|
||||
if 'size' in values:
|
||||
values['size'] = int(values['size'])
|
||||
|
||||
values['is_public'] = bool(values.get('is_public', False))
|
||||
image_ref = models.Image()
|
||||
|
||||
_drop_protected_attrs(models.Image, values)
|
||||
image_ref.update(values)
|
||||
|
||||
# Validate the attributes before we go any further. From my
|
||||
# investigation, the @validates decorator does not validate
|
||||
# on new records, only on existing records, which is, well,
|
||||
# idiotic.
|
||||
validate_image(image_ref.to_dict())
|
||||
|
||||
image_ref.save(session=session)
|
||||
|
||||
_set_properties_for_image(context, image_ref, properties, session)
|
||||
|
@ -36,7 +36,8 @@ def define_images_table(meta):
|
||||
Column('deleted_at', DateTime()),
|
||||
Column('deleted', Boolean(), nullable=False, default=False,
|
||||
index=True),
|
||||
mysql_engine='InnoDB')
|
||||
mysql_engine='InnoDB',
|
||||
useexisting=True)
|
||||
|
||||
return images
|
||||
|
||||
|
@ -41,7 +41,8 @@ def define_image_properties_table(meta):
|
||||
Column('deleted', Boolean(), nullable=False, default=False,
|
||||
index=True),
|
||||
UniqueConstraint('image_id', 'key'),
|
||||
mysql_engine='InnoDB')
|
||||
mysql_engine='InnoDB',
|
||||
useexisting=True)
|
||||
|
||||
Index('ix_image_properties_image_id_key', image_properties.c.image_id,
|
||||
image_properties.c.key)
|
||||
|
@ -88,6 +88,9 @@ class ModelBase(object):
|
||||
def items(self):
|
||||
return self.__dict__.items()
|
||||
|
||||
def to_dict(self):
|
||||
return self.__dict__.copy()
|
||||
|
||||
|
||||
class Image(BASE, ModelBase):
|
||||
"""Represents an image in the datastore"""
|
||||
@ -95,7 +98,8 @@ class Image(BASE, ModelBase):
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String(255))
|
||||
type = Column(String(30))
|
||||
disk_format = Column(String(20))
|
||||
container_format = Column(String(20))
|
||||
size = Column(Integer)
|
||||
status = Column(String(30), nullable=False)
|
||||
is_public = Column(Boolean, nullable=False, default=False)
|
||||
|
@ -55,7 +55,6 @@ class Controller(wsgi.Controller):
|
||||
images = db_api.image_get_all_public(None)
|
||||
image_dicts = [dict(id=i['id'],
|
||||
name=i['name'],
|
||||
type=i['type'],
|
||||
size=i['size']) for i in images]
|
||||
return dict(images=image_dicts)
|
||||
|
||||
@ -147,6 +146,11 @@ class Controller(wsgi.Controller):
|
||||
try:
|
||||
updated_image = db_api.image_update(context, id, image_data)
|
||||
return dict(image=make_image_dict(updated_image))
|
||||
except exception.Invalid, e:
|
||||
msg = ("Failed to update image metadata. "
|
||||
"Got error: %(e)s" % locals())
|
||||
logger.error(msg)
|
||||
return exc.HTTPBadRequest(msg)
|
||||
except exception.NotFound:
|
||||
raise exc.HTTPNotFound(body='Image not found',
|
||||
request=req,
|
||||
|
@ -83,7 +83,6 @@ class Controller(wsgi.Controller):
|
||||
* id -- The opaque image identifier
|
||||
* name -- The name of the image
|
||||
* size -- Size of image data in bytes
|
||||
* type -- One of 'kernel', 'ramdisk', 'raw', or 'machine'
|
||||
|
||||
:param request: The WSGI/Webob Request object
|
||||
:retval The response body is a mapping of the following form::
|
||||
@ -91,8 +90,7 @@ class Controller(wsgi.Controller):
|
||||
{'images': [
|
||||
{'id': <ID>,
|
||||
'name': <NAME>,
|
||||
'size': <SIZE>,
|
||||
'type': <TYPE>}, ...
|
||||
'size': <SIZE>}, ...
|
||||
]}
|
||||
"""
|
||||
images = registry.get_images_list(self.options)
|
||||
@ -109,7 +107,8 @@ class Controller(wsgi.Controller):
|
||||
{'id': <ID>,
|
||||
'name': <NAME>,
|
||||
'size': <SIZE>,
|
||||
'type': <TYPE>,
|
||||
'disk_format': <DISK_FORMAT>,
|
||||
'container_format': <CONTAINER_FORMAT>,
|
||||
'store': <STORE>,
|
||||
'status': <STATUS>,
|
||||
'created_at': <TIMESTAMP>,
|
||||
|
@ -33,9 +33,8 @@ def image_meta_to_http_headers(image_meta):
|
||||
if k == 'properties':
|
||||
for pk, pv in v.items():
|
||||
headers["x-image-meta-property-%s"
|
||||
% pk.lower()] = pv
|
||||
|
||||
headers["x-image-meta-%s" % k.lower()] = v
|
||||
% pk.lower()] = unicode(pv)
|
||||
headers["x-image-meta-%s" % k.lower()] = unicode(v)
|
||||
return headers
|
||||
|
||||
|
||||
@ -82,6 +81,12 @@ def get_image_meta_from_headers(response):
|
||||
field_name = key[len('x-image-meta-'):].replace('-', '_')
|
||||
result[field_name] = value
|
||||
result['properties'] = properties
|
||||
if 'id' in result:
|
||||
result['id'] = int(result['id'])
|
||||
if 'size' in result:
|
||||
result['size'] = int(result['size'])
|
||||
if 'is_public' in result:
|
||||
result['is_public'] = (result['is_public'] == 'True')
|
||||
return result
|
||||
|
||||
|
||||
|
@ -270,7 +270,8 @@ def stub_out_registry_db_image_api(stubs):
|
||||
{'id': 1,
|
||||
'name': 'fake image #1',
|
||||
'status': 'active',
|
||||
'type': 'kernel',
|
||||
'disk_format': 'ami',
|
||||
'container_format': 'ami',
|
||||
'is_public': False,
|
||||
'created_at': datetime.datetime.utcnow(),
|
||||
'updated_at': datetime.datetime.utcnow(),
|
||||
@ -278,11 +279,14 @@ def stub_out_registry_db_image_api(stubs):
|
||||
'deleted': False,
|
||||
'size': 13,
|
||||
'location': "swift://user:passwd@acct/container/obj.tar.0",
|
||||
'properties': []},
|
||||
'properties': [{'key': 'type',
|
||||
'value': 'kernel',
|
||||
'deleted': False}]},
|
||||
{'id': 2,
|
||||
'name': 'fake image #2',
|
||||
'status': 'active',
|
||||
'type': 'kernel',
|
||||
'disk_format': 'vhd',
|
||||
'container_format': 'ovf',
|
||||
'is_public': True,
|
||||
'created_at': datetime.datetime.utcnow(),
|
||||
'updated_at': datetime.datetime.utcnow(),
|
||||
@ -292,8 +296,6 @@ def stub_out_registry_db_image_api(stubs):
|
||||
'location': "file:///tmp/glance-tests/2",
|
||||
'properties': []}]
|
||||
|
||||
VALID_STATUSES = ('active', 'killed', 'queued', 'saving')
|
||||
|
||||
def __init__(self):
|
||||
self.images = FakeDatastore.FIXTURES
|
||||
self.next_id = 3
|
||||
@ -306,12 +308,7 @@ def stub_out_registry_db_image_api(stubs):
|
||||
raise exception.Duplicate("Duplicate image id: %s" %
|
||||
values['id'])
|
||||
|
||||
if 'status' not in values.keys():
|
||||
values['status'] = 'active'
|
||||
else:
|
||||
if not values['status'] in self.VALID_STATUSES:
|
||||
raise exception.Invalid("Invalid status '%s' for image" %
|
||||
values['status'])
|
||||
glance.registry.db.api.validate_image(values)
|
||||
|
||||
values['size'] = values.get('size', 0)
|
||||
values['deleted'] = False
|
||||
@ -323,7 +320,7 @@ def stub_out_registry_db_image_api(stubs):
|
||||
props = []
|
||||
|
||||
if 'properties' in values.keys():
|
||||
for k, v in values['properties'].iteritems():
|
||||
for k, v in values['properties'].items():
|
||||
p = {}
|
||||
p['key'] = k
|
||||
p['value'] = v
|
||||
@ -341,10 +338,14 @@ def stub_out_registry_db_image_api(stubs):
|
||||
|
||||
def image_update(self, _context, image_id, values):
|
||||
|
||||
image = self.image_get(_context, image_id)
|
||||
copy_image = image.copy()
|
||||
copy_image.update(values)
|
||||
glance.registry.db.api.validate_image(copy_image)
|
||||
props = []
|
||||
|
||||
if 'properties' in values.keys():
|
||||
for k, v in values['properties'].iteritems():
|
||||
for k, v in values['properties'].items():
|
||||
p = {}
|
||||
p['key'] = k
|
||||
p['value'] = v
|
||||
@ -356,7 +357,6 @@ def stub_out_registry_db_image_api(stubs):
|
||||
|
||||
values['properties'] = props
|
||||
|
||||
image = self.image_get(_context, image_id)
|
||||
image.update(values)
|
||||
return image
|
||||
|
||||
@ -369,9 +369,8 @@ def stub_out_registry_db_image_api(stubs):
|
||||
images = [i for i in self.images if str(i['id']) == str(image_id)]
|
||||
|
||||
if len(images) != 1 or images[0]['deleted']:
|
||||
new_exc = exception.NotFound("No model for id %s %s" %
|
||||
(image_id, str(self.images)))
|
||||
raise new_exc.__class__, new_exc, sys.exc_info()[2]
|
||||
raise exception.NotFound("No model for id %s %s" %
|
||||
(image_id, str(self.images)))
|
||||
else:
|
||||
return images[0]
|
||||
|
||||
|
@ -85,7 +85,8 @@ class TestRegistryAPI(unittest.TestCase):
|
||||
fixture = {'id': 2,
|
||||
'name': 'fake image #2',
|
||||
'is_public': True,
|
||||
'type': 'kernel',
|
||||
'disk_format': 'vhd',
|
||||
'container_format': 'ovf',
|
||||
'status': 'active'}
|
||||
|
||||
req = webob.Request.blank('/images/detail')
|
||||
@ -103,7 +104,8 @@ class TestRegistryAPI(unittest.TestCase):
|
||||
"""Tests that the /images POST registry API creates the image"""
|
||||
fixture = {'name': 'fake public image',
|
||||
'is_public': True,
|
||||
'type': 'kernel'}
|
||||
'disk_format': 'vhd',
|
||||
'container_format': 'ovf'}
|
||||
|
||||
req = webob.Request.blank('/images')
|
||||
|
||||
@ -125,12 +127,64 @@ class TestRegistryAPI(unittest.TestCase):
|
||||
# Test status was updated properly
|
||||
self.assertEquals('active', res_dict['image']['status'])
|
||||
|
||||
def test_create_image_with_bad_container_format(self):
|
||||
"""Tests proper exception is raised if a bad disk_format is set"""
|
||||
fixture = {'id': 3,
|
||||
'name': 'fake public image',
|
||||
'is_public': True,
|
||||
'disk_format': 'vhd',
|
||||
'container_format': 'invalid'}
|
||||
|
||||
req = webob.Request.blank('/images')
|
||||
|
||||
req.method = 'POST'
|
||||
req.body = json.dumps(dict(image=fixture))
|
||||
|
||||
res = req.get_response(self.api)
|
||||
self.assertEquals(res.status_int, webob.exc.HTTPBadRequest.code)
|
||||
self.assertTrue('Invalid container format' in res.body)
|
||||
|
||||
def test_create_image_with_bad_disk_format(self):
|
||||
"""Tests proper exception is raised if a bad disk_format is set"""
|
||||
fixture = {'id': 3,
|
||||
'name': 'fake public image',
|
||||
'is_public': True,
|
||||
'disk_format': 'invalid',
|
||||
'container_format': 'ovf'}
|
||||
|
||||
req = webob.Request.blank('/images')
|
||||
|
||||
req.method = 'POST'
|
||||
req.body = json.dumps(dict(image=fixture))
|
||||
|
||||
res = req.get_response(self.api)
|
||||
self.assertEquals(res.status_int, webob.exc.HTTPBadRequest.code)
|
||||
self.assertTrue('Invalid disk format' in res.body)
|
||||
|
||||
def test_create_image_with_mismatched_formats(self):
|
||||
"""Tests that exception raised for bad matching disk and container
|
||||
formats"""
|
||||
fixture = {'name': 'fake public image #3',
|
||||
'container_format': 'aki',
|
||||
'disk_format': 'ari'}
|
||||
|
||||
req = webob.Request.blank('/images')
|
||||
|
||||
req.method = 'POST'
|
||||
req.body = json.dumps(dict(image=fixture))
|
||||
|
||||
res = req.get_response(self.api)
|
||||
self.assertEquals(res.status_int, webob.exc.HTTPBadRequest.code)
|
||||
self.assertTrue('Invalid mix of disk and container formats'
|
||||
in res.body)
|
||||
|
||||
def test_create_image_with_bad_status(self):
|
||||
"""Tests proper exception is raised if a bad status is set"""
|
||||
fixture = {'id': 3,
|
||||
'name': 'fake public image',
|
||||
'is_public': True,
|
||||
'type': 'kernel',
|
||||
'disk_format': 'vhd',
|
||||
'container_format': 'ovf',
|
||||
'status': 'bad status'}
|
||||
|
||||
req = webob.Request.blank('/images')
|
||||
@ -140,11 +194,12 @@ class TestRegistryAPI(unittest.TestCase):
|
||||
|
||||
res = req.get_response(self.api)
|
||||
self.assertEquals(res.status_int, webob.exc.HTTPBadRequest.code)
|
||||
self.assertTrue('Invalid image status' in res.body)
|
||||
|
||||
def test_update_image(self):
|
||||
"""Tests that the /images PUT registry API updates the image"""
|
||||
fixture = {'name': 'fake public image #2',
|
||||
'type': 'ramdisk'}
|
||||
'disk_format': 'raw'}
|
||||
|
||||
req = webob.Request.blank('/images/2')
|
||||
|
||||
@ -163,11 +218,7 @@ class TestRegistryAPI(unittest.TestCase):
|
||||
def test_update_image_not_existing(self):
|
||||
"""Tests proper exception is raised if attempt to update non-existing
|
||||
image"""
|
||||
fixture = {'id': 3,
|
||||
'name': 'fake public image',
|
||||
'is_public': True,
|
||||
'type': 'kernel',
|
||||
'status': 'bad status'}
|
||||
fixture = {'status': 'killed'}
|
||||
|
||||
req = webob.Request.blank('/images/3')
|
||||
|
||||
@ -178,6 +229,60 @@ class TestRegistryAPI(unittest.TestCase):
|
||||
self.assertEquals(res.status_int,
|
||||
webob.exc.HTTPNotFound.code)
|
||||
|
||||
def test_update_image_with_bad_status(self):
|
||||
"""Tests that exception raised trying to set a bad status"""
|
||||
fixture = {'status': 'invalid'}
|
||||
|
||||
req = webob.Request.blank('/images/2')
|
||||
|
||||
req.method = 'PUT'
|
||||
req.body = json.dumps(dict(image=fixture))
|
||||
|
||||
res = req.get_response(self.api)
|
||||
self.assertEquals(res.status_int, webob.exc.HTTPBadRequest.code)
|
||||
self.assertTrue('Invalid image status' in res.body)
|
||||
|
||||
def test_update_image_with_bad_disk_format(self):
|
||||
"""Tests that exception raised trying to set a bad disk_format"""
|
||||
fixture = {'disk_format': 'invalid'}
|
||||
|
||||
req = webob.Request.blank('/images/2')
|
||||
|
||||
req.method = 'PUT'
|
||||
req.body = json.dumps(dict(image=fixture))
|
||||
|
||||
res = req.get_response(self.api)
|
||||
self.assertEquals(res.status_int, webob.exc.HTTPBadRequest.code)
|
||||
self.assertTrue('Invalid disk format' in res.body)
|
||||
|
||||
def test_update_image_with_bad_container_format(self):
|
||||
"""Tests that exception raised trying to set a bad container_format"""
|
||||
fixture = {'container_format': 'invalid'}
|
||||
|
||||
req = webob.Request.blank('/images/2')
|
||||
|
||||
req.method = 'PUT'
|
||||
req.body = json.dumps(dict(image=fixture))
|
||||
|
||||
res = req.get_response(self.api)
|
||||
self.assertEquals(res.status_int, webob.exc.HTTPBadRequest.code)
|
||||
self.assertTrue('Invalid container format' in res.body)
|
||||
|
||||
def test_update_image_with_mismatched_formats(self):
|
||||
"""Tests that exception raised for bad matching disk and container
|
||||
formats"""
|
||||
fixture = {'container_format': 'ari'}
|
||||
|
||||
req = webob.Request.blank('/images/2') # Image 2 has disk format 'vhd'
|
||||
|
||||
req.method = 'PUT'
|
||||
req.body = json.dumps(dict(image=fixture))
|
||||
|
||||
res = req.get_response(self.api)
|
||||
self.assertEquals(res.status_int, webob.exc.HTTPBadRequest.code)
|
||||
self.assertTrue('Invalid mix of disk and container formats'
|
||||
in res.body)
|
||||
|
||||
def test_delete_image(self):
|
||||
"""Tests that the /images DELETE registry API deletes the image"""
|
||||
|
||||
@ -239,10 +344,44 @@ class TestGlanceAPI(unittest.TestCase):
|
||||
stubs.clean_out_fake_filesystem_backend()
|
||||
self.stubs.UnsetAll()
|
||||
|
||||
def test_bad_disk_format(self):
|
||||
fixture_headers = {'x-image-meta-store': 'bad',
|
||||
'x-image-meta-name': 'bogus',
|
||||
'x-image-meta-location': 'http://example.com/image.tar.gz',
|
||||
'x-image-meta-disk-format': 'invalid',
|
||||
'x-image-meta-container-format': 'ami'}
|
||||
|
||||
req = webob.Request.blank("/images")
|
||||
req.method = 'POST'
|
||||
for k, v in fixture_headers.iteritems():
|
||||
req.headers[k] = v
|
||||
|
||||
res = req.get_response(self.api)
|
||||
self.assertEquals(res.status_int, webob.exc.HTTPBadRequest.code)
|
||||
self.assertTrue('Invalid disk format' in res.body, res.body)
|
||||
|
||||
def test_bad_container_format(self):
|
||||
fixture_headers = {'x-image-meta-store': 'bad',
|
||||
'x-image-meta-name': 'bogus',
|
||||
'x-image-meta-location': 'http://example.com/image.tar.gz',
|
||||
'x-image-meta-disk-format': 'vhd',
|
||||
'x-image-meta-container-format': 'invalid'}
|
||||
|
||||
req = webob.Request.blank("/images")
|
||||
req.method = 'POST'
|
||||
for k, v in fixture_headers.iteritems():
|
||||
req.headers[k] = v
|
||||
|
||||
res = req.get_response(self.api)
|
||||
self.assertEquals(res.status_int, webob.exc.HTTPBadRequest.code)
|
||||
self.assertTrue('Invalid container format' in res.body)
|
||||
|
||||
def test_add_image_no_location_no_image_as_body(self):
|
||||
"""Tests creates a queued image for no body and no loc header"""
|
||||
fixture_headers = {'x-image-meta-store': 'file',
|
||||
'x-image-meta-name': 'fake image #3'}
|
||||
'x-image-meta-disk-format': 'vhd',
|
||||
'x-image-meta-container-format': 'ovf',
|
||||
'x-image-meta-name': 'fake image #3'}
|
||||
|
||||
req = webob.Request.blank("/images")
|
||||
req.method = 'POST'
|
||||
@ -270,8 +409,10 @@ class TestGlanceAPI(unittest.TestCase):
|
||||
self.assertEquals(res.status_int, webob.exc.HTTPBadRequest.code)
|
||||
|
||||
def test_add_image_basic_file_store(self):
|
||||
"""Tests raises BadRequest for invalid store header"""
|
||||
"""Tests to add a basic image in the file store"""
|
||||
fixture_headers = {'x-image-meta-store': 'file',
|
||||
'x-image-meta-disk-format': 'vhd',
|
||||
'x-image-meta-container-format': 'ovf',
|
||||
'x-image-meta-name': 'fake image #3'}
|
||||
|
||||
req = webob.Request.blank("/images")
|
||||
@ -298,7 +439,7 @@ class TestGlanceAPI(unittest.TestCase):
|
||||
|
||||
def test_image_meta(self):
|
||||
"""Test for HEAD /images/<ID>"""
|
||||
expected_headers = {'x-image-meta-id': 2,
|
||||
expected_headers = {'x-image-meta-id': '2',
|
||||
'x-image-meta-name': 'fake image #2'}
|
||||
req = webob.Request.blank("/images/2")
|
||||
req.method = 'HEAD'
|
||||
|
@ -66,7 +66,7 @@ class TestRegistryClient(unittest.TestCase):
|
||||
images = self.client.get_images()
|
||||
self.assertEquals(len(images), 1)
|
||||
|
||||
for k, v in fixture.iteritems():
|
||||
for k, v in fixture.items():
|
||||
self.assertEquals(v, images[0][k])
|
||||
|
||||
def test_get_image_details(self):
|
||||
@ -74,16 +74,8 @@ class TestRegistryClient(unittest.TestCase):
|
||||
fixture = {'id': 2,
|
||||
'name': 'fake image #2',
|
||||
'is_public': True,
|
||||
'type': 'kernel',
|
||||
'status': 'active',
|
||||
'size': 19,
|
||||
'location': "file:///tmp/glance-tests/2",
|
||||
'properties': {}}
|
||||
|
||||
expected = {'id': 2,
|
||||
'name': 'fake image #2',
|
||||
'is_public': True,
|
||||
'type': 'kernel',
|
||||
'disk_format': 'vhd',
|
||||
'container_format': 'ovf',
|
||||
'status': 'active',
|
||||
'size': 19,
|
||||
'location': "file:///tmp/glance-tests/2",
|
||||
@ -92,33 +84,28 @@ class TestRegistryClient(unittest.TestCase):
|
||||
images = self.client.get_images_detailed()
|
||||
self.assertEquals(len(images), 1)
|
||||
|
||||
for k, v in expected.iteritems():
|
||||
for k, v in fixture.items():
|
||||
self.assertEquals(v, images[0][k])
|
||||
|
||||
def test_get_image(self):
|
||||
"""Tests that the detailed info about an image returned"""
|
||||
fixture = {'id': 2,
|
||||
'name': 'fake image #2',
|
||||
'is_public': True,
|
||||
'type': 'kernel',
|
||||
fixture = {'id': 1,
|
||||
'name': 'fake image #1',
|
||||
'is_public': False,
|
||||
'disk_format': 'ami',
|
||||
'container_format': 'ami',
|
||||
'status': 'active',
|
||||
'size': 19,
|
||||
'location': "file:///tmp/glance-tests/2",
|
||||
'properties': {}}
|
||||
'size': 13,
|
||||
'location': "swift://user:passwd@acct/container/obj.tar.0",
|
||||
'properties': {'type': 'kernel'}}
|
||||
|
||||
expected = {'id': 2,
|
||||
'name': 'fake image #2',
|
||||
'is_public': True,
|
||||
'type': 'kernel',
|
||||
'status': 'active',
|
||||
'size': 19,
|
||||
'location': "file:///tmp/glance-tests/2",
|
||||
'properties': {}}
|
||||
data = self.client.get_image(1)
|
||||
|
||||
data = self.client.get_image(2)
|
||||
|
||||
for k, v in expected.iteritems():
|
||||
self.assertEquals(v, data[k])
|
||||
for k, v in fixture.items():
|
||||
el = data[k]
|
||||
self.assertEquals(v, data[k],
|
||||
"Failed v != data[k] where v = %(v)s and "
|
||||
"k = %(k)s and data[k] = %(el)s" % locals())
|
||||
|
||||
def test_get_image_non_existing(self):
|
||||
"""Tests that NotFound is raised when getting a non-existing image"""
|
||||
@ -131,7 +118,8 @@ class TestRegistryClient(unittest.TestCase):
|
||||
"""Tests that we can add image metadata and returns the new id"""
|
||||
fixture = {'name': 'fake public image',
|
||||
'is_public': True,
|
||||
'type': 'kernel',
|
||||
'disk_format': 'vmdk',
|
||||
'container_format': 'ovf',
|
||||
'size': 19,
|
||||
'location': "file:///tmp/glance-tests/acct/3.gz.0",
|
||||
}
|
||||
@ -144,7 +132,7 @@ class TestRegistryClient(unittest.TestCase):
|
||||
# Test all other attributes set
|
||||
data = self.client.get_image(3)
|
||||
|
||||
for k, v in fixture.iteritems():
|
||||
for k, v in fixture.items():
|
||||
self.assertEquals(v, data[k])
|
||||
|
||||
# Test status was updated properly
|
||||
@ -155,23 +143,18 @@ class TestRegistryClient(unittest.TestCase):
|
||||
"""Tests that we can add image metadata with properties"""
|
||||
fixture = {'name': 'fake public image',
|
||||
'is_public': True,
|
||||
'type': 'kernel',
|
||||
'disk_format': 'vmdk',
|
||||
'container_format': 'ovf',
|
||||
'size': 19,
|
||||
'location': "file:///tmp/glance-tests/2",
|
||||
'properties': {'distro': 'Ubuntu 10.04 LTS'}}
|
||||
expected = {'name': 'fake public image',
|
||||
'is_public': True,
|
||||
'type': 'kernel',
|
||||
'size': 19,
|
||||
'location': "file:///tmp/glance-tests/2",
|
||||
'properties': {'distro': 'Ubuntu 10.04 LTS'}}
|
||||
|
||||
new_image = self.client.add_image(fixture)
|
||||
|
||||
# Test ID auto-assigned properly
|
||||
self.assertEquals(3, new_image['id'])
|
||||
|
||||
for k, v in expected.iteritems():
|
||||
for k, v in fixture.items():
|
||||
self.assertEquals(v, new_image[k])
|
||||
|
||||
# Test status was updated properly
|
||||
@ -183,7 +166,8 @@ class TestRegistryClient(unittest.TestCase):
|
||||
fixture = {'id': 2,
|
||||
'name': 'fake public image',
|
||||
'is_public': True,
|
||||
'type': 'kernel',
|
||||
'disk_format': 'vmdk',
|
||||
'container_format': 'ovf',
|
||||
'status': 'bad status',
|
||||
'size': 19,
|
||||
'location': "file:///tmp/glance-tests/2",
|
||||
@ -198,7 +182,8 @@ class TestRegistryClient(unittest.TestCase):
|
||||
fixture = {'id': 3,
|
||||
'name': 'fake public image',
|
||||
'is_public': True,
|
||||
'type': 'kernel',
|
||||
'disk_format': 'vmdk',
|
||||
'container_format': 'ovf',
|
||||
'status': 'bad status',
|
||||
'size': 19,
|
||||
'location': "file:///tmp/glance-tests/2",
|
||||
@ -211,14 +196,14 @@ class TestRegistryClient(unittest.TestCase):
|
||||
def test_update_image(self):
|
||||
"""Tests that the /images PUT registry API updates the image"""
|
||||
fixture = {'name': 'fake public image #2',
|
||||
'type': 'ramdisk'}
|
||||
'disk_format': 'vmdk'}
|
||||
|
||||
self.assertTrue(self.client.update_image(2, fixture))
|
||||
|
||||
# Test all other attributes set
|
||||
data = self.client.get_image(2)
|
||||
|
||||
for k, v in fixture.iteritems():
|
||||
for k, v in fixture.items():
|
||||
self.assertEquals(v, data[k])
|
||||
|
||||
def test_update_image_not_existing(self):
|
||||
@ -226,7 +211,8 @@ class TestRegistryClient(unittest.TestCase):
|
||||
fixture = {'id': 3,
|
||||
'name': 'fake public image',
|
||||
'is_public': True,
|
||||
'type': 'kernel',
|
||||
'disk_format': 'vmdk',
|
||||
'container_format': 'ovf',
|
||||
'status': 'bad status',
|
||||
}
|
||||
|
||||
@ -283,7 +269,8 @@ class TestClient(unittest.TestCase):
|
||||
expected_meta = {'id': 2,
|
||||
'name': 'fake image #2',
|
||||
'is_public': True,
|
||||
'type': 'kernel',
|
||||
'disk_format': 'vhd',
|
||||
'container_format': 'ovf',
|
||||
'status': 'active',
|
||||
'size': 19,
|
||||
'location': "file:///tmp/glance-tests/2",
|
||||
@ -295,7 +282,7 @@ class TestClient(unittest.TestCase):
|
||||
image_data += image_chunk
|
||||
|
||||
self.assertEquals(expected_image, image_data)
|
||||
for k, v in expected_meta.iteritems():
|
||||
for k, v in expected_meta.items():
|
||||
self.assertEquals(v, meta[k])
|
||||
|
||||
def test_get_image_not_existing(self):
|
||||
@ -312,7 +299,7 @@ class TestClient(unittest.TestCase):
|
||||
images = self.client.get_images()
|
||||
self.assertEquals(len(images), 1)
|
||||
|
||||
for k, v in fixture.iteritems():
|
||||
for k, v in fixture.items():
|
||||
self.assertEquals(v, images[0][k])
|
||||
|
||||
def test_get_image_details(self):
|
||||
@ -320,7 +307,8 @@ class TestClient(unittest.TestCase):
|
||||
fixture = {'id': 2,
|
||||
'name': 'fake image #2',
|
||||
'is_public': True,
|
||||
'type': 'kernel',
|
||||
'disk_format': 'vhd',
|
||||
'container_format': 'ovf',
|
||||
'status': 'active',
|
||||
'size': 19,
|
||||
'location': "file:///tmp/glance-tests/2",
|
||||
@ -329,7 +317,8 @@ class TestClient(unittest.TestCase):
|
||||
expected = {'id': 2,
|
||||
'name': 'fake image #2',
|
||||
'is_public': True,
|
||||
'type': 'kernel',
|
||||
'disk_format': 'vhd',
|
||||
'container_format': 'ovf',
|
||||
'status': 'active',
|
||||
'size': 19,
|
||||
'location': "file:///tmp/glance-tests/2",
|
||||
@ -338,7 +327,7 @@ class TestClient(unittest.TestCase):
|
||||
images = self.client.get_images_detailed()
|
||||
self.assertEquals(len(images), 1)
|
||||
|
||||
for k, v in expected.iteritems():
|
||||
for k, v in expected.items():
|
||||
self.assertEquals(v, images[0][k])
|
||||
|
||||
def test_get_image_meta(self):
|
||||
@ -346,16 +335,8 @@ class TestClient(unittest.TestCase):
|
||||
fixture = {'id': 2,
|
||||
'name': 'fake image #2',
|
||||
'is_public': True,
|
||||
'type': 'kernel',
|
||||
'status': 'active',
|
||||
'size': 19,
|
||||
'location': "file:///tmp/glance-tests/2",
|
||||
'properties': {}}
|
||||
|
||||
expected = {'id': 2,
|
||||
'name': 'fake image #2',
|
||||
'is_public': True,
|
||||
'type': 'kernel',
|
||||
'disk_format': 'vhd',
|
||||
'container_format': 'ovf',
|
||||
'status': 'active',
|
||||
'size': 19,
|
||||
'location': "file:///tmp/glance-tests/2",
|
||||
@ -363,7 +344,7 @@ class TestClient(unittest.TestCase):
|
||||
|
||||
data = self.client.get_image_meta(2)
|
||||
|
||||
for k, v in expected.iteritems():
|
||||
for k, v in fixture.items():
|
||||
self.assertEquals(v, data[k])
|
||||
|
||||
def test_get_image_non_existing(self):
|
||||
@ -377,7 +358,8 @@ class TestClient(unittest.TestCase):
|
||||
"""Tests client returns image as queued"""
|
||||
fixture = {'name': 'fake public image',
|
||||
'is_public': True,
|
||||
'type': 'kernel',
|
||||
'disk_format': 'vhd',
|
||||
'container_format': 'ovf',
|
||||
}
|
||||
image_meta = self.client.add_image(fixture)
|
||||
self.assertEquals('queued', image_meta['status'])
|
||||
@ -387,7 +369,8 @@ class TestClient(unittest.TestCase):
|
||||
"""Tests that we can add image metadata and returns the new id"""
|
||||
fixture = {'name': 'fake public image',
|
||||
'is_public': True,
|
||||
'type': 'kernel',
|
||||
'disk_format': 'vhd',
|
||||
'container_format': 'ovf',
|
||||
'size': 19,
|
||||
'location': "file:///tmp/glance-tests/2",
|
||||
}
|
||||
@ -400,7 +383,7 @@ class TestClient(unittest.TestCase):
|
||||
# Test all other attributes set
|
||||
data = self.client.get_image_meta(3)
|
||||
|
||||
for k, v in fixture.iteritems():
|
||||
for k, v in fixture.items():
|
||||
self.assertEquals(v, data[k])
|
||||
|
||||
# Test status was updated properly
|
||||
@ -411,18 +394,12 @@ class TestClient(unittest.TestCase):
|
||||
"""Tests that we can add image metadata with properties"""
|
||||
fixture = {'name': 'fake public image',
|
||||
'is_public': True,
|
||||
'type': 'kernel',
|
||||
'disk_format': 'vhd',
|
||||
'container_format': 'ovf',
|
||||
'size': 19,
|
||||
'location': "file:///tmp/glance-tests/2",
|
||||
'properties': {'distro': 'Ubuntu 10.04 LTS'},
|
||||
}
|
||||
expected = {'name': 'fake public image',
|
||||
'is_public': True,
|
||||
'type': 'kernel',
|
||||
'size': 19,
|
||||
'location': "file:///tmp/glance-tests/2",
|
||||
'properties': {'distro': 'Ubuntu 10.04 LTS'},
|
||||
}
|
||||
new_image = self.client.add_image(fixture)
|
||||
new_image_id = new_image['id']
|
||||
|
||||
@ -432,7 +409,7 @@ class TestClient(unittest.TestCase):
|
||||
# Test all other attributes set
|
||||
data = self.client.get_image_meta(3)
|
||||
|
||||
for k, v in expected.iteritems():
|
||||
for k, v in fixture.items():
|
||||
self.assertEquals(v, data[k])
|
||||
|
||||
# Test status was updated properly
|
||||
@ -444,7 +421,8 @@ class TestClient(unittest.TestCase):
|
||||
fixture = {'id': 2,
|
||||
'name': 'fake public image',
|
||||
'is_public': True,
|
||||
'type': 'kernel',
|
||||
'disk_format': 'vhd',
|
||||
'container_format': 'ovf',
|
||||
'status': 'bad status',
|
||||
'size': 19,
|
||||
'location': "file:///tmp/glance-tests/2",
|
||||
@ -458,7 +436,8 @@ class TestClient(unittest.TestCase):
|
||||
"""Tests a bad status is set to a proper one by server"""
|
||||
fixture = {'name': 'fake public image',
|
||||
'is_public': True,
|
||||
'type': 'kernel',
|
||||
'disk_format': 'vhd',
|
||||
'container_format': 'ovf',
|
||||
'status': 'bad status',
|
||||
'size': 19,
|
||||
'location': "file:///tmp/glance-tests/2",
|
||||
@ -471,7 +450,8 @@ class TestClient(unittest.TestCase):
|
||||
"""Tests can add image by passing image data as string"""
|
||||
fixture = {'name': 'fake public image',
|
||||
'is_public': True,
|
||||
'type': 'kernel',
|
||||
'disk_format': 'vhd',
|
||||
'container_format': 'ovf',
|
||||
'size': 19,
|
||||
'properties': {'distro': 'Ubuntu 10.04 LTS'},
|
||||
}
|
||||
@ -489,14 +469,15 @@ class TestClient(unittest.TestCase):
|
||||
new_image_data += image_chunk
|
||||
|
||||
self.assertEquals(image_data_fixture, new_image_data)
|
||||
for k, v in fixture.iteritems():
|
||||
for k, v in fixture.items():
|
||||
self.assertEquals(v, new_meta[k])
|
||||
|
||||
def test_add_image_with_image_data_as_file(self):
|
||||
"""Tests can add image by passing image data as file"""
|
||||
fixture = {'name': 'fake public image',
|
||||
'is_public': True,
|
||||
'type': 'kernel',
|
||||
'disk_format': 'vhd',
|
||||
'container_format': 'ovf',
|
||||
'size': 19,
|
||||
'properties': {'distro': 'Ubuntu 10.04 LTS'},
|
||||
}
|
||||
@ -526,14 +507,15 @@ class TestClient(unittest.TestCase):
|
||||
new_image_data += image_chunk
|
||||
|
||||
self.assertEquals(image_data_fixture, new_image_data)
|
||||
for k, v in fixture.iteritems():
|
||||
for k, v in fixture.items():
|
||||
self.assertEquals(v, new_meta[k])
|
||||
|
||||
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,
|
||||
'type': 'kernel',
|
||||
'disk_format': 'vhd',
|
||||
'container_format': 'ovf',
|
||||
'properties': {'distro': 'Ubuntu 10.04 LTS'},
|
||||
}
|
||||
|
||||
@ -550,7 +532,7 @@ class TestClient(unittest.TestCase):
|
||||
new_image_data += image_chunk
|
||||
|
||||
self.assertEquals(image_data_fixture, new_image_data)
|
||||
for k, v in fixture.iteritems():
|
||||
for k, v in fixture.items():
|
||||
self.assertEquals(v, new_meta[k])
|
||||
|
||||
self.assertEquals(19, new_meta['size'])
|
||||
@ -559,7 +541,8 @@ class TestClient(unittest.TestCase):
|
||||
"""Tests BadRequest raised when supplying bad store name in meta"""
|
||||
fixture = {'name': 'fake public image',
|
||||
'is_public': True,
|
||||
'type': 'kernel',
|
||||
'disk_format': 'vhd',
|
||||
'container_format': 'ovf',
|
||||
'size': 19,
|
||||
'store': 'bad',
|
||||
'properties': {'distro': 'Ubuntu 10.04 LTS'},
|
||||
@ -575,15 +558,14 @@ class TestClient(unittest.TestCase):
|
||||
def test_update_image(self):
|
||||
"""Tests that the /images PUT registry API updates the image"""
|
||||
fixture = {'name': 'fake public image #2',
|
||||
'type': 'ramdisk',
|
||||
}
|
||||
'disk_format': 'vmdk'}
|
||||
|
||||
self.assertTrue(self.client.update_image(2, fixture))
|
||||
|
||||
# Test all other attributes set
|
||||
data = self.client.get_image_meta(2)
|
||||
|
||||
for k, v in fixture.iteritems():
|
||||
for k, v in fixture.items():
|
||||
self.assertEquals(v, data[k])
|
||||
|
||||
def test_update_image_not_existing(self):
|
||||
@ -591,7 +573,8 @@ class TestClient(unittest.TestCase):
|
||||
fixture = {'id': 3,
|
||||
'name': 'fake public image',
|
||||
'is_public': True,
|
||||
'type': 'kernel',
|
||||
'disk_format': 'vhd',
|
||||
'container_format': 'ovf',
|
||||
'status': 'bad status',
|
||||
}
|
||||
|
||||
|
@ -27,6 +27,8 @@ class TestMigrations(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.db_path = "glance_test_migration.sqlite"
|
||||
if os.path.exists(self.db_path):
|
||||
os.unlink(self.db_path)
|
||||
self.options = dict(sql_connection="sqlite:///%s" % self.db_path,
|
||||
verbose=False)
|
||||
config.setup_logging(self.options)
|
||||
|
@ -23,6 +23,8 @@ import tempfile
|
||||
import time
|
||||
import unittest
|
||||
|
||||
from glance import utils
|
||||
|
||||
|
||||
def execute(cmd):
|
||||
env = os.environ.copy()
|
||||
@ -66,6 +68,46 @@ class TestMiscellaneous(unittest.TestCase):
|
||||
pass # Ignore if the process group is dead
|
||||
os.unlink(pid_file)
|
||||
|
||||
def test_headers_are_unicode(self):
|
||||
"""
|
||||
Verifies that the headers returned by conversion code are unicode.
|
||||
|
||||
Headers are passed via http in non-testing mode, which automatically
|
||||
converts them to unicode. Verifying that the method does the
|
||||
conversion proves that we aren't passing data that works in tests
|
||||
but will fail in production.
|
||||
"""
|
||||
fixture = {'name': 'fake public image',
|
||||
'is_public': True,
|
||||
'type': 'kernel',
|
||||
'size': 19,
|
||||
'location': "file:///tmp/glance-tests/2",
|
||||
'properties': {'distro': 'Ubuntu 10.04 LTS'}}
|
||||
headers = utils.image_meta_to_http_headers(fixture)
|
||||
for k, v in headers.iteritems():
|
||||
self.assert_(isinstance(v, unicode), "%s is not unicode" % v)
|
||||
|
||||
def test_data_passed_properly_through_headers(self):
|
||||
"""
|
||||
Verifies that data is the same after being passed through headers
|
||||
"""
|
||||
fixture = {'name': 'fake public image',
|
||||
'is_public': True,
|
||||
'type': 'kernel',
|
||||
'size': 19,
|
||||
'location': "file:///tmp/glance-tests/2",
|
||||
'properties': {'distro': 'Ubuntu 10.04 LTS'}}
|
||||
headers = utils.image_meta_to_http_headers(fixture)
|
||||
|
||||
class FakeResponse():
|
||||
pass
|
||||
|
||||
response = FakeResponse()
|
||||
response.headers = headers
|
||||
result = utils.get_image_meta_from_headers(response)
|
||||
for k, v in fixture.iteritems():
|
||||
self.assertEqual(v, result[k])
|
||||
|
||||
def test_exception_not_eaten_from_registry_to_api(self):
|
||||
"""
|
||||
A test for LP bug #704854 -- Exception thrown by registry
|
||||
@ -145,22 +187,15 @@ sql_idle_timeout = 3600
|
||||
self.assertEquals(0, exitcode)
|
||||
self.assertEquals('{"images": []}', out.strip())
|
||||
|
||||
cmd = "./bin/glance-upload --port=%(api_port)d "\
|
||||
"--type=invalid %(conf_file_name)s "\
|
||||
"'my image'" % locals()
|
||||
cmd = "curl -X POST -H 'Content-Type: application/octet-stream' "\
|
||||
"-H 'X-Image-Meta-Name: ImageName' "\
|
||||
"-H 'X-Image-Meta-Disk-Format: Invalid' "\
|
||||
"http://0.0.0.0:%d/images" % api_port
|
||||
ignored, out, err = execute(cmd)
|
||||
|
||||
# Normally, we would simply self.assertRaises() here, but
|
||||
# we also want to check that the Invalid image type is in
|
||||
# the exception text...
|
||||
hit_exception = False
|
||||
try:
|
||||
ignored, out, err = execute(cmd)
|
||||
except RuntimeError, e:
|
||||
hit_exception = True
|
||||
self.assertTrue('Invalid image type' in str(e),
|
||||
"Could not find 'Invalid image type' in "
|
||||
"result from glance-upload:\n%(e)s" % locals())
|
||||
self.assertTrue(hit_exception)
|
||||
self.assertTrue('Invalid disk format' in out,
|
||||
"Could not find 'Invalid disk format' "
|
||||
"in output: %s" % out)
|
||||
|
||||
# Spin down the API and default registry server
|
||||
cmd = "./bin/glance-control api stop "\
|
||||
|
Loading…
Reference in New Issue
Block a user