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:
Rick Harris
2011-01-12 16:31:29 +00:00
committed by Tarmac
16 changed files with 238 additions and 173 deletions

View File

@@ -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`

View File

@@ -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.

View File

@@ -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):
"""

View File

@@ -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))

View File

@@ -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):
"""

View File

@@ -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', {})

View File

@@ -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

View File

@@ -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:

View File

@@ -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):
"""

View File

@@ -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]

View File

@@ -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]

View File

@@ -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):

View File

@@ -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" %

View File

@@ -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"""

View File

@@ -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)

View File

@@ -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"""