This merge is in conjunction with lp:~rconradharris/nova/xs-snap-return-image-id-before-snapshot
The patch does the following: * Image Create (POST) is broken up into 3 steps (reserve, upload and activate); reserve is used to allow the OpenStack API to return metadata about an image where the data has not been uploaded yet * Image Update (PUT) now takes image data as the request body and metadata as headers (just like POST); state is enforced so that image data can only be uploaded once. * Image statuses were changed to match the OpenStack API (queued, saving, active, killed); NOTE: preparing is not used * update_image and add_image client calls now return metadata instead of just the id
This commit is contained in:
@@ -133,6 +133,10 @@ The method signature is as follows::
|
||||
The `image_meta` argument is a mapping containing various image metadata. The
|
||||
`image_data` argument is the disk image data.
|
||||
|
||||
If the data is not yet available, but you would like to reserve a slot in
|
||||
Glance to hold the image, you can create a 'queued' by omitting the
|
||||
`image_data` parameter.
|
||||
|
||||
The list of metadata that `image_meta` can contain are listed below.
|
||||
|
||||
* `name`
|
||||
|
@@ -292,3 +292,19 @@ The list of metadata headers that Glance accepts are listed below.
|
||||
be attached to the image. However, keep in mind that the 8K limit on the
|
||||
size of all HTTP headers sent in a request will effectively limit the number
|
||||
of image properties.
|
||||
|
||||
|
||||
Updating an Image
|
||||
*****************
|
||||
|
||||
Glance will view as image metadata any HTTP header that it receives in a
|
||||
`PUT` request where the header key is prefixed with the strings
|
||||
`x-image-meta-` and `x-image-meta-property-`.
|
||||
|
||||
If an image was previously reserved, and thus is in the `queued` state, then
|
||||
image data can be added by including it as the request body. If the image
|
||||
already as data associated with it (e.g. not in the `queued` state), then
|
||||
including a request body will result in a `409 Conflict` exception.
|
||||
|
||||
On success, the `PUT` request will return the image metadata encoded as `HTTP`
|
||||
headers.
|
||||
|
@@ -120,11 +120,11 @@ class BaseClient(object):
|
||||
elif status_code == httplib.NOT_FOUND:
|
||||
raise exception.NotFound
|
||||
elif status_code == httplib.CONFLICT:
|
||||
raise exception.Duplicate
|
||||
raise exception.Duplicate(res.read())
|
||||
elif status_code == httplib.BAD_REQUEST:
|
||||
raise exception.BadInputError
|
||||
raise exception.BadInputError(res.read())
|
||||
else:
|
||||
raise Exception("Unknown error occurred! %d" % status_code)
|
||||
raise Exception("Unknown error occurred! %s" % res.__dict__)
|
||||
|
||||
except (socket.error, IOError), e:
|
||||
raise ClientConnectionError("Unable to connect to "
|
||||
@@ -203,12 +203,12 @@ class Client(BaseClient):
|
||||
image = util.get_image_meta_from_headers(res)
|
||||
return image
|
||||
|
||||
def add_image(self, image_meta, image_data=None):
|
||||
def add_image(self, image_meta=None, image_data=None):
|
||||
"""
|
||||
Tells Glance about an image's metadata as well
|
||||
as optionally the image_data itself
|
||||
|
||||
:param image_meta: Mapping of information about the
|
||||
: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
|
||||
@@ -216,11 +216,9 @@ class Client(BaseClient):
|
||||
|
||||
:retval The newly-stored image's metadata.
|
||||
"""
|
||||
if not image_data and 'location' not in image_meta.keys():
|
||||
raise exception.Invalid("You must either specify a location "
|
||||
"for the image or supply the actual "
|
||||
"image data when adding an image to "
|
||||
"Glance")
|
||||
if image_meta is None:
|
||||
image_meta = {}
|
||||
|
||||
if image_data:
|
||||
if hasattr(image_data, 'read'):
|
||||
# TODO(jaypipes): This is far from efficient. Implement
|
||||
@@ -242,17 +240,22 @@ class Client(BaseClient):
|
||||
|
||||
res = self.do_request("POST", "/images", body, headers)
|
||||
data = json.loads(res.read())
|
||||
return data['image']['id']
|
||||
return data['image']
|
||||
|
||||
def update_image(self, image_id, image_metadata):
|
||||
def update_image(self, image_id, image_meta=None, image_data=None):
|
||||
"""
|
||||
Updates Glance's information about an image
|
||||
"""
|
||||
if 'image' not in image_metadata.keys():
|
||||
image_metadata = dict(image=image_metadata)
|
||||
body = json.dumps(image_metadata)
|
||||
self.do_request("PUT", "/images/%s" % image_id, body)
|
||||
return True
|
||||
if image_meta:
|
||||
if 'image' not in image_meta:
|
||||
image_meta = dict(image=image_meta)
|
||||
|
||||
headers = util.image_meta_to_http_headers(image_meta or {})
|
||||
|
||||
body = image_data
|
||||
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):
|
||||
"""
|
||||
|
@@ -141,8 +141,7 @@ def generate_uid(topic, size=8):
|
||||
|
||||
def generate_mac():
|
||||
mac = [0x02, 0x16, 0x3e, random.randint(0x00, 0x7f),
|
||||
random.randint(0x00, 0xff), random.randint(0x00, 0xff)
|
||||
]
|
||||
random.randint(0x00, 0xff), random.randint(0x00, 0xff)]
|
||||
return ':'.join(map(lambda x: "%02x" % x, mac))
|
||||
|
||||
|
||||
|
@@ -93,9 +93,13 @@ class RegistryClient(BaseClient):
|
||||
"""
|
||||
if 'image' not in image_metadata.keys():
|
||||
image_metadata = dict(image=image_metadata)
|
||||
|
||||
body = json.dumps(image_metadata)
|
||||
self.do_request("PUT", "/images/%s" % image_id, body)
|
||||
return True
|
||||
|
||||
res = self.do_request("PUT", "/images/%s" % image_id, body)
|
||||
data = json.loads(res.read())
|
||||
image = data['image']
|
||||
return image
|
||||
|
||||
def delete_image(self, image_id):
|
||||
"""
|
||||
|
@@ -65,11 +65,11 @@ def image_destroy(_context, image_id):
|
||||
def image_get(context, image_id):
|
||||
session = get_session()
|
||||
try:
|
||||
return session.query(models.Image
|
||||
).options(joinedload(models.Image.properties)
|
||||
).filter_by(deleted=_deleted(context)
|
||||
).filter_by(id=image_id
|
||||
).one()
|
||||
return session.query(models.Image).\
|
||||
options(joinedload(models.Image.properties)).\
|
||||
filter_by(deleted=_deleted(context)).\
|
||||
filter_by(id=image_id).\
|
||||
one()
|
||||
except exc.NoResultFound:
|
||||
new_exc = exception.NotFound("No model for id %s" % image_id)
|
||||
raise new_exc.__class__, new_exc, sys.exc_info()[2]
|
||||
@@ -77,19 +77,19 @@ def image_get(context, image_id):
|
||||
|
||||
def image_get_all(context):
|
||||
session = get_session()
|
||||
return session.query(models.Image
|
||||
).options(joinedload(models.Image.properties)
|
||||
).filter_by(deleted=_deleted(context)
|
||||
).all()
|
||||
return session.query(models.Image).\
|
||||
options(joinedload(models.Image.properties)).\
|
||||
filter_by(deleted=_deleted(context)).\
|
||||
all()
|
||||
|
||||
|
||||
def image_get_all_public(context, public):
|
||||
session = get_session()
|
||||
return session.query(models.Image
|
||||
).options(joinedload(models.Image.properties)
|
||||
).filter_by(deleted=_deleted(context)
|
||||
).filter_by(is_public=public
|
||||
).all()
|
||||
return session.query(models.Image).\
|
||||
options(joinedload(models.Image.properties)).\
|
||||
filter_by(deleted=_deleted(context)).\
|
||||
filter_by(is_public=public).\
|
||||
all()
|
||||
|
||||
|
||||
def image_get_by_str(context, str_id):
|
||||
@@ -129,7 +129,9 @@ def _image_update(_context, values, image_id):
|
||||
with session.begin():
|
||||
_drop_protected_attrs(models.Image, values)
|
||||
|
||||
values['size'] = int(values['size'])
|
||||
if 'size' in values:
|
||||
values['size'] = int(values['size'])
|
||||
|
||||
values['is_public'] = bool(values.get('is_public', False))
|
||||
properties = values.pop('properties', {})
|
||||
|
||||
|
@@ -58,18 +58,18 @@ class ModelBase(object):
|
||||
"""Get all objects of this type"""
|
||||
if not session:
|
||||
session = get_session()
|
||||
return session.query(cls
|
||||
).filter_by(deleted=deleted
|
||||
).all()
|
||||
return session.query(cls).\
|
||||
filter_by(deleted=deleted).\
|
||||
all()
|
||||
|
||||
@classmethod
|
||||
def count(cls, session=None, deleted=False):
|
||||
"""Count objects of this type"""
|
||||
if not session:
|
||||
session = get_session()
|
||||
return session.query(cls
|
||||
).filter_by(deleted=deleted
|
||||
).count()
|
||||
return session.query(cls).\
|
||||
filter_by(deleted=deleted).\
|
||||
count()
|
||||
|
||||
@classmethod
|
||||
def find(cls, obj_id, session=None, deleted=False):
|
||||
@@ -77,10 +77,10 @@ class ModelBase(object):
|
||||
if not session:
|
||||
session = get_session()
|
||||
try:
|
||||
return session.query(cls
|
||||
).filter_by(id=obj_id
|
||||
).filter_by(deleted=deleted
|
||||
).one()
|
||||
return session.query(cls).\
|
||||
filter_by(id=obj_id).\
|
||||
filter_by(deleted=deleted).\
|
||||
one()
|
||||
except exc.NoResultFound:
|
||||
new_exc = exception.NotFound("No model for id %s" % obj_id)
|
||||
raise new_exc.__class__, new_exc, sys.exc_info()[2]
|
||||
@@ -160,7 +160,7 @@ class Image(BASE, ModelBase):
|
||||
|
||||
@validates('status')
|
||||
def validate_status(self, key, status):
|
||||
if not status in ('available', 'pending', 'disabled'):
|
||||
if not status in ('active', 'queued', 'killed', 'saving'):
|
||||
raise exception.Invalid("Invalid status '%s' for image." % status)
|
||||
return status
|
||||
|
||||
|
@@ -106,7 +106,7 @@ class ImageController(wsgi.Controller):
|
||||
image_data = json.loads(req.body)['image']
|
||||
|
||||
# Ensure the image has a status set
|
||||
image_data.setdefault('status', 'available')
|
||||
image_data.setdefault('status', 'active')
|
||||
|
||||
context = None
|
||||
try:
|
||||
|
172
glance/server.py
172
glance/server.py
@@ -162,17 +162,96 @@ class Controller(wsgi.Controller):
|
||||
util.inject_image_meta_into_headers(res, image)
|
||||
return req.get_response(res)
|
||||
|
||||
def _reserve(self, req):
|
||||
image_meta = util.get_image_meta_from_headers(req)
|
||||
image_meta['status'] = 'queued'
|
||||
|
||||
try:
|
||||
image_meta = registry.add_image_metadata(image_meta)
|
||||
return image_meta
|
||||
except exception.Duplicate:
|
||||
msg = "An image with identifier %s already exists"\
|
||||
% image_meta['id']
|
||||
logging.error(msg)
|
||||
raise HTTPConflict(msg, request=req)
|
||||
except exception.Invalid:
|
||||
raise HTTPBadRequest()
|
||||
|
||||
def _upload(self, req, image_meta):
|
||||
content_type = req.headers.get('content-type', 'notset')
|
||||
if content_type != 'application/octet-stream':
|
||||
raise HTTPBadRequest(
|
||||
"Content-Type must be application/octet-stream")
|
||||
|
||||
image_store = req.headers.get(
|
||||
'x-image-meta-store', FLAGS.default_store)
|
||||
|
||||
store = self.get_store_or_400(req, image_store)
|
||||
|
||||
image_meta['status'] = 'saving'
|
||||
registry.update_image_metadata(image_meta['id'], image_meta)
|
||||
|
||||
try:
|
||||
location = store.add(image_meta['id'], req.body)
|
||||
return location
|
||||
except exception.Duplicate, e:
|
||||
logging.error("Error adding image to store: %s", str(e))
|
||||
raise HTTPConflict(str(e), request=req)
|
||||
|
||||
def _activate(self, req, image_meta, location):
|
||||
image_meta['location'] = location
|
||||
image_meta['status'] = 'active'
|
||||
registry.update_image_metadata(image_meta['id'], image_meta)
|
||||
|
||||
def _kill(self, req, image_meta):
|
||||
image_meta['status'] = 'killed'
|
||||
registry.update_image_metadata(image_meta['id'], image_meta)
|
||||
|
||||
def _safe_kill(self, req, image_meta):
|
||||
"""Mark image killed without raising exceptions if it fails.
|
||||
|
||||
Since _kill is meant to be called from exceptions handlers, it should
|
||||
not raise itself, rather it should just log its error.
|
||||
"""
|
||||
try:
|
||||
self._kill(req, image_meta)
|
||||
except Exception, e:
|
||||
logging.error("Unable to kill image %s: %s",
|
||||
image_meta['id'], repr(e))
|
||||
|
||||
def _upload_and_activate(self, req, image_meta):
|
||||
try:
|
||||
location = self._upload(req, image_meta)
|
||||
self._activate(req, image_meta, location)
|
||||
except Exception, e:
|
||||
# NOTE(sirp): _safe_kill uses httplib which, in turn, uses
|
||||
# Eventlet's GreenSocket. Eventlet subsequently clears exceptions
|
||||
# by calling `sys.exc_clear()`. This is why we have to `raise e`
|
||||
# instead of `raise`
|
||||
self._safe_kill(req, image_meta)
|
||||
raise e
|
||||
|
||||
def create(self, req):
|
||||
"""
|
||||
Adds a new image to Glance. The body of the request may be a
|
||||
mime-encoded image data. Metadata about the image is sent via
|
||||
HTTP Headers.
|
||||
Adds a new image to Glance. Three scenarios exist when creating an
|
||||
image:
|
||||
|
||||
If the metadata about the image does not include a location
|
||||
to find the image, or if the image location is not valid,
|
||||
the request body *must* be encoded as application/octet-stream
|
||||
and be the image data itself, otherwise an HTTPBadRequest is
|
||||
returned.
|
||||
1. If the image data is available for upload, create can be passed the
|
||||
image data as the request body and the metadata as the request
|
||||
headers. The image will initially be 'queued', during upload it
|
||||
will be in the 'saving' status, and then 'killed' or 'active'
|
||||
depending on whether the upload completed successfully.
|
||||
|
||||
2. If the image data exists somewhere else, you can pass in the source
|
||||
using the x-image-meta-location header
|
||||
|
||||
3. If the image data is not available yet, but you'd like reserve a
|
||||
spot for it, you can omit the data and a record will be created in
|
||||
the 'queued' state. This exists primarily to maintain backwards
|
||||
compatibility with OpenStack/Rackspace API semantics.
|
||||
|
||||
The request body *must* be encoded as application/octet-stream,
|
||||
otherwise an HTTPBadRequest is returned.
|
||||
|
||||
Upon a successful save of the image data and metadata, a response
|
||||
containing metadata about the image is returned, including its
|
||||
@@ -184,62 +263,16 @@ class Controller(wsgi.Controller):
|
||||
and the request body is not application/octet-stream
|
||||
image data.
|
||||
"""
|
||||
image_meta = self._reserve(req)
|
||||
|
||||
# Verify the request and headers before we generate a new id
|
||||
|
||||
image_in_body = False
|
||||
image_store = None
|
||||
header_keys = [k.lower() for k in req.headers.keys()]
|
||||
if 'x-image-meta-location' not in header_keys:
|
||||
if ('content-type' not in header_keys or
|
||||
req.headers['content-type'] != 'application/octet-stream'):
|
||||
raise HTTPBadRequest("Image location was not specified in "
|
||||
"headers and the request body was not "
|
||||
"mime-encoded as application/"
|
||||
"octet-stream.", request=req)
|
||||
else:
|
||||
if 'x-image-meta-store' in header_keys:
|
||||
image_store = req.headers['x-image-meta-store']
|
||||
image_status = 'pending' # set to available when stored...
|
||||
image_in_body = True
|
||||
if req.body:
|
||||
self._upload_and_activate(req, image_meta)
|
||||
else:
|
||||
image_location = req.headers['x-image-meta-location']
|
||||
image_store = get_store_from_location(image_location)
|
||||
image_status = 'available'
|
||||
if 'x-image-meta-location' in req.headers:
|
||||
location = req.headers['x-image-meta-location']
|
||||
self._activate(req, image_meta, location)
|
||||
|
||||
# If image is the request body, validate that the requested
|
||||
# or default store is capable of storing the image data...
|
||||
if not image_store:
|
||||
image_store = FLAGS.default_store
|
||||
if image_in_body:
|
||||
store = self.get_store_or_400(req, image_store)
|
||||
|
||||
image_meta = util.get_image_meta_from_headers(req)
|
||||
|
||||
image_meta['status'] = image_status
|
||||
image_meta['store'] = image_store
|
||||
try:
|
||||
image_meta = registry.add_image_metadata(image_meta)
|
||||
|
||||
if image_in_body:
|
||||
try:
|
||||
location = store.add(image_meta['id'], req.body)
|
||||
except exception.Duplicate, e:
|
||||
logging.error("Error adding image to store: %s", str(e))
|
||||
return HTTPConflict(str(e), request=req)
|
||||
image_meta['status'] = 'available'
|
||||
image_meta['location'] = location
|
||||
registry.update_image_metadata(image_meta['id'], image_meta)
|
||||
|
||||
return dict(image=image_meta)
|
||||
|
||||
except exception.Duplicate:
|
||||
msg = "An image with identifier %s already exists"\
|
||||
% image_meta['id']
|
||||
logging.error(msg)
|
||||
return HTTPConflict(msg, request=req)
|
||||
except exception.Invalid:
|
||||
return HTTPBadRequest()
|
||||
return dict(image=image_meta)
|
||||
|
||||
def update(self, req, id):
|
||||
"""
|
||||
@@ -248,14 +281,21 @@ class Controller(wsgi.Controller):
|
||||
:param request: The WSGI/Webob Request object
|
||||
:param id: The opaque image identifier
|
||||
|
||||
:retval Returns the updated image information as a mapping,
|
||||
:retval Returns the updated image information as a mapping
|
||||
|
||||
"""
|
||||
image = self.get_image_meta_or_404(req, id)
|
||||
orig_image_meta = self.get_image_meta_or_404(req, id)
|
||||
new_image_meta = util.get_image_meta_from_headers(req)
|
||||
|
||||
image_data = json.loads(req.body)['image']
|
||||
updated_image = registry.update_image_metadata(id, image_data)
|
||||
return dict(image=updated_image)
|
||||
if req.body and (orig_image_meta['status'] != 'queued'):
|
||||
raise HTTPConflict("Cannot upload to an unqueued image")
|
||||
|
||||
image_meta = registry.update_image_metadata(id, new_image_meta)
|
||||
|
||||
if req.body:
|
||||
self._upload_and_activate(req, image_meta)
|
||||
|
||||
return dict(image=image_meta)
|
||||
|
||||
def delete(self, req, id):
|
||||
"""
|
||||
|
@@ -56,12 +56,10 @@ def get_backend_class(backend):
|
||||
from glance.store.swift import SwiftBackend
|
||||
from glance.store.filesystem import FilesystemBackend
|
||||
|
||||
BACKENDS = {
|
||||
"file": FilesystemBackend,
|
||||
"http": HTTPBackend,
|
||||
"https": HTTPBackend,
|
||||
"swift": SwiftBackend
|
||||
}
|
||||
BACKENDS = {"file": FilesystemBackend,
|
||||
"http": HTTPBackend,
|
||||
"https": HTTPBackend,
|
||||
"swift": SwiftBackend}
|
||||
|
||||
try:
|
||||
return BACKENDS[backend]
|
||||
|
@@ -87,12 +87,10 @@ def get_backend_class(backend):
|
||||
from glance.store.backends.http import HTTPBackend
|
||||
from glance.store.backends.swift import SwiftBackend
|
||||
|
||||
BACKENDS = {
|
||||
"file": FilesystemBackend,
|
||||
"http": HTTPBackend,
|
||||
"https": HTTPBackend,
|
||||
"swift": SwiftBackend
|
||||
}
|
||||
BACKENDS = {"file": FilesystemBackend,
|
||||
"http": HTTPBackend,
|
||||
"https": HTTPBackend,
|
||||
"swift": SwiftBackend}
|
||||
|
||||
try:
|
||||
return BACKENDS[backend]
|
||||
|
@@ -114,7 +114,6 @@ class FilesystemBackend(glance.store.Backend):
|
||||
|
||||
:retval The location that was written, with file:// scheme prepended
|
||||
"""
|
||||
|
||||
datadir = FLAGS.filesystem_store_datadir
|
||||
|
||||
if not os.path.exists(datadir):
|
||||
|
@@ -279,7 +279,7 @@ def stub_out_registry_db_image_api(stubs):
|
||||
FIXTURES = [
|
||||
{'id': 1,
|
||||
'name': 'fake image #1',
|
||||
'status': 'available',
|
||||
'status': 'active',
|
||||
'type': 'kernel',
|
||||
'is_public': False,
|
||||
'created_at': datetime.datetime.utcnow(),
|
||||
@@ -291,7 +291,7 @@ def stub_out_registry_db_image_api(stubs):
|
||||
'properties': []},
|
||||
{'id': 2,
|
||||
'name': 'fake image #2',
|
||||
'status': 'available',
|
||||
'status': 'active',
|
||||
'type': 'kernel',
|
||||
'is_public': True,
|
||||
'created_at': datetime.datetime.utcnow(),
|
||||
@@ -302,7 +302,7 @@ def stub_out_registry_db_image_api(stubs):
|
||||
'location': "file:///tmp/glance-tests/2",
|
||||
'properties': []}]
|
||||
|
||||
VALID_STATUSES = ('available', 'disabled', 'pending')
|
||||
VALID_STATUSES = ('active', 'killed', 'queued', 'saving')
|
||||
|
||||
def __init__(self):
|
||||
self.images = FakeDatastore.FIXTURES
|
||||
@@ -317,7 +317,7 @@ def stub_out_registry_db_image_api(stubs):
|
||||
values['id'])
|
||||
|
||||
if 'status' not in values.keys():
|
||||
values['status'] = 'available'
|
||||
values['status'] = 'active'
|
||||
else:
|
||||
if not values['status'] in self.VALID_STATUSES:
|
||||
raise exception.Invalid("Invalid status '%s' for image" %
|
||||
|
@@ -15,6 +15,7 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import httplib
|
||||
import json
|
||||
import unittest
|
||||
|
||||
@@ -87,7 +88,7 @@ class TestRegistryAPI(unittest.TestCase):
|
||||
'name': 'fake image #2',
|
||||
'is_public': True,
|
||||
'type': 'kernel',
|
||||
'status': 'available'
|
||||
'status': 'active'
|
||||
}
|
||||
req = webob.Request.blank('/images/detail')
|
||||
res = req.get_response(rserver.API())
|
||||
@@ -125,7 +126,7 @@ class TestRegistryAPI(unittest.TestCase):
|
||||
self.assertEquals(3, res_dict['image']['id'])
|
||||
|
||||
# Test status was updated properly
|
||||
self.assertEquals('available', res_dict['image']['status'])
|
||||
self.assertEquals('active', res_dict['image']['status'])
|
||||
|
||||
def test_create_image_with_bad_status(self):
|
||||
"""Tests proper exception is raised if a bad status is set"""
|
||||
@@ -251,7 +252,7 @@ class TestGlanceAPI(unittest.TestCase):
|
||||
self.stubs.UnsetAll()
|
||||
|
||||
def test_add_image_no_location_no_image_as_body(self):
|
||||
"""Tests raises BadRequest for no body and no loc header"""
|
||||
"""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'}
|
||||
|
||||
@@ -260,7 +261,10 @@ class TestGlanceAPI(unittest.TestCase):
|
||||
for k, v in fixture_headers.iteritems():
|
||||
req.headers[k] = v
|
||||
res = req.get_response(server.API())
|
||||
self.assertEquals(res.status_int, webob.exc.HTTPBadRequest.code)
|
||||
self.assertEquals(res.status_int, httplib.OK)
|
||||
|
||||
res_body = json.loads(res.body)['image']
|
||||
self.assertEquals('queued', res_body['status'])
|
||||
|
||||
def test_add_image_bad_store(self):
|
||||
"""Tests raises BadRequest for invalid store header"""
|
||||
|
@@ -78,7 +78,7 @@ class TestRegistryClient(unittest.TestCase):
|
||||
'name': 'fake image #2',
|
||||
'is_public': True,
|
||||
'type': 'kernel',
|
||||
'status': 'available',
|
||||
'status': 'active',
|
||||
'size': 19,
|
||||
'location': "file:///tmp/glance-tests/2",
|
||||
'properties': {}}
|
||||
@@ -87,7 +87,7 @@ class TestRegistryClient(unittest.TestCase):
|
||||
'name': 'fake image #2',
|
||||
'is_public': True,
|
||||
'type': 'kernel',
|
||||
'status': 'available',
|
||||
'status': 'active',
|
||||
'size': 19,
|
||||
'location': "file:///tmp/glance-tests/2",
|
||||
'properties': {}}
|
||||
@@ -104,7 +104,7 @@ class TestRegistryClient(unittest.TestCase):
|
||||
'name': 'fake image #2',
|
||||
'is_public': True,
|
||||
'type': 'kernel',
|
||||
'status': 'available',
|
||||
'status': 'active',
|
||||
'size': 19,
|
||||
'location': "file:///tmp/glance-tests/2",
|
||||
'properties': {}}
|
||||
@@ -113,7 +113,7 @@ class TestRegistryClient(unittest.TestCase):
|
||||
'name': 'fake image #2',
|
||||
'is_public': True,
|
||||
'type': 'kernel',
|
||||
'status': 'available',
|
||||
'status': 'active',
|
||||
'size': 19,
|
||||
'location': "file:///tmp/glance-tests/2",
|
||||
'properties': {}}
|
||||
@@ -152,7 +152,7 @@ class TestRegistryClient(unittest.TestCase):
|
||||
|
||||
# Test status was updated properly
|
||||
self.assertTrue('status' in data.keys())
|
||||
self.assertEquals('available', data['status'])
|
||||
self.assertEquals('active', data['status'])
|
||||
|
||||
def test_add_image_with_properties(self):
|
||||
"""Tests that we can add image metadata with properties"""
|
||||
@@ -181,7 +181,7 @@ class TestRegistryClient(unittest.TestCase):
|
||||
|
||||
# Test status was updated properly
|
||||
self.assertTrue('status' in new_image.keys())
|
||||
self.assertEquals('available', new_image['status'])
|
||||
self.assertEquals('active', new_image['status'])
|
||||
|
||||
def test_add_image_already_exists(self):
|
||||
"""Tests proper exception is raised if image with ID already exists"""
|
||||
@@ -293,7 +293,7 @@ class TestClient(unittest.TestCase):
|
||||
'name': 'fake image #2',
|
||||
'is_public': True,
|
||||
'type': 'kernel',
|
||||
'status': 'available',
|
||||
'status': 'active',
|
||||
'size': 19,
|
||||
'location': "file:///tmp/glance-tests/2",
|
||||
'properties': {}}
|
||||
@@ -330,7 +330,7 @@ class TestClient(unittest.TestCase):
|
||||
'name': 'fake image #2',
|
||||
'is_public': True,
|
||||
'type': 'kernel',
|
||||
'status': 'available',
|
||||
'status': 'active',
|
||||
'size': 19,
|
||||
'location': "file:///tmp/glance-tests/2",
|
||||
'properties': {}}
|
||||
@@ -339,7 +339,7 @@ class TestClient(unittest.TestCase):
|
||||
'name': 'fake image #2',
|
||||
'is_public': True,
|
||||
'type': 'kernel',
|
||||
'status': 'available',
|
||||
'status': 'active',
|
||||
'size': 19,
|
||||
'location': "file:///tmp/glance-tests/2",
|
||||
'properties': {}}
|
||||
@@ -356,7 +356,7 @@ class TestClient(unittest.TestCase):
|
||||
'name': 'fake image #2',
|
||||
'is_public': True,
|
||||
'type': 'kernel',
|
||||
'status': 'available',
|
||||
'status': 'active',
|
||||
'size': 19,
|
||||
'location': "file:///tmp/glance-tests/2",
|
||||
'properties': {}}
|
||||
@@ -365,7 +365,7 @@ class TestClient(unittest.TestCase):
|
||||
'name': 'fake image #2',
|
||||
'is_public': True,
|
||||
'type': 'kernel',
|
||||
'status': 'available',
|
||||
'status': 'active',
|
||||
'size': 19,
|
||||
'location': "file:///tmp/glance-tests/2",
|
||||
'properties': {}}
|
||||
@@ -383,15 +383,14 @@ class TestClient(unittest.TestCase):
|
||||
42)
|
||||
|
||||
def test_add_image_without_location_or_raw_data(self):
|
||||
"""Tests client throws Invalid if missing both location and raw data"""
|
||||
"""Tests client returns image as queued"""
|
||||
fixture = {'name': 'fake public image',
|
||||
'is_public': True,
|
||||
'type': 'kernel'
|
||||
}
|
||||
|
||||
self.assertRaises(exception.Invalid,
|
||||
self.client.add_image,
|
||||
fixture)
|
||||
|
||||
image_meta = self.client.add_image(fixture)
|
||||
self.assertEquals('queued', image_meta['status'])
|
||||
|
||||
def test_add_image_basic(self):
|
||||
"""Tests that we can add image metadata and returns the new id"""
|
||||
@@ -401,11 +400,12 @@ class TestClient(unittest.TestCase):
|
||||
'size': 19,
|
||||
'location': "file:///tmp/glance-tests/2",
|
||||
}
|
||||
|
||||
new_id = self.client.add_image(fixture)
|
||||
|
||||
new_image = self.client.add_image(fixture)
|
||||
new_image_id = new_image['id']
|
||||
|
||||
# Test ID auto-assigned properly
|
||||
self.assertEquals(3, new_id)
|
||||
self.assertEquals(3, new_image_id)
|
||||
|
||||
# Test all other attributes set
|
||||
data = self.client.get_image_meta(3)
|
||||
@@ -415,7 +415,7 @@ class TestClient(unittest.TestCase):
|
||||
|
||||
# Test status was updated properly
|
||||
self.assertTrue('status' in data.keys())
|
||||
self.assertEquals('available', data['status'])
|
||||
self.assertEquals('active', data['status'])
|
||||
|
||||
def test_add_image_with_properties(self):
|
||||
"""Tests that we can add image metadata with properties"""
|
||||
@@ -433,11 +433,12 @@ class TestClient(unittest.TestCase):
|
||||
'location': "file:///tmp/glance-tests/2",
|
||||
'properties': {'distro': 'Ubuntu 10.04 LTS'}
|
||||
}
|
||||
|
||||
new_id = self.client.add_image(fixture)
|
||||
|
||||
new_image = self.client.add_image(fixture)
|
||||
new_image_id = new_image['id']
|
||||
|
||||
# Test ID auto-assigned properly
|
||||
self.assertEquals(3, new_id)
|
||||
self.assertEquals(3, new_image_id)
|
||||
|
||||
# Test all other attributes set
|
||||
data = self.client.get_image_meta(3)
|
||||
@@ -446,8 +447,8 @@ class TestClient(unittest.TestCase):
|
||||
self.assertEquals(v, data[k])
|
||||
|
||||
# Test status was updated properly
|
||||
self.assertTrue('status' in data.keys())
|
||||
self.assertEquals('available', data['status'])
|
||||
self.assertTrue('status' in data)
|
||||
self.assertEquals('active', data['status'])
|
||||
|
||||
def test_add_image_already_exists(self):
|
||||
"""Tests proper exception is raised if image with ID already exists"""
|
||||
@@ -474,11 +475,8 @@ class TestClient(unittest.TestCase):
|
||||
'location': "file:///tmp/glance-tests/2",
|
||||
}
|
||||
|
||||
new_id = self.client.add_image(fixture)
|
||||
|
||||
data = self.client.get_image_meta(new_id)
|
||||
|
||||
self.assertEquals(data['status'], 'available')
|
||||
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"""
|
||||
@@ -491,9 +489,9 @@ class TestClient(unittest.TestCase):
|
||||
|
||||
image_data_fixture = r"chunk0000remainder"
|
||||
|
||||
new_id = self.client.add_image(fixture, image_data_fixture)
|
||||
|
||||
self.assertEquals(3, new_id)
|
||||
new_image = self.client.add_image(fixture, image_data_fixture)
|
||||
new_image_id = new_image['id']
|
||||
self.assertEquals(3, new_image_id)
|
||||
|
||||
new_meta, new_image_chunks = self.client.get_image(3)
|
||||
|
||||
@@ -525,9 +523,9 @@ class TestClient(unittest.TestCase):
|
||||
tmp_file.write(image_data_fixture)
|
||||
tmp_file.close()
|
||||
|
||||
new_id = self.client.add_image(fixture, open(tmp_image_filepath))
|
||||
|
||||
self.assertEquals(3, new_id)
|
||||
new_image = self.client.add_image(fixture, open(tmp_image_filepath))
|
||||
new_image_id = new_image['id']
|
||||
self.assertEquals(3, new_image_id)
|
||||
|
||||
if os.path.exists(tmp_image_filepath):
|
||||
os.unlink(tmp_image_filepath)
|
||||
|
@@ -80,7 +80,7 @@ class TestImageController(unittest.TestCase):
|
||||
'name': 'fake image #2',
|
||||
'is_public': True,
|
||||
'type': 'kernel',
|
||||
'status': 'available'
|
||||
'status': 'active'
|
||||
}
|
||||
req = webob.Request.blank('/images/detail')
|
||||
res = req.get_response(server.API())
|
||||
@@ -118,7 +118,7 @@ class TestImageController(unittest.TestCase):
|
||||
self.assertEquals(3, res_dict['image']['id'])
|
||||
|
||||
# Test status was updated properly
|
||||
self.assertEquals('available', res_dict['image']['status'])
|
||||
self.assertEquals('active', res_dict['image']['status'])
|
||||
|
||||
def test_create_image_with_bad_status(self):
|
||||
"""Tests proper exception is raised if a bad status is set"""
|
||||
|
Reference in New Issue
Block a user