Skip to content

Commit

Permalink
awesomeness. merging into trunk since my parallax-api is already in t…
Browse files Browse the repository at this point in the history
…runk I believe. :)
  • Loading branch information
[email protected] committed Oct 18, 2010
2 parents b335ff2 + 98dc44c commit a6962d6
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 35 deletions.
1 change: 1 addition & 0 deletions .bzrignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
*.pyc
glance.egg-info
glance.sqlite
*.glance-venv
47 changes: 32 additions & 15 deletions glance/parallax/controllers.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,16 @@ def show(self, req, id):
return dict(image=self._make_image_dict(image))

def delete(self, req, id):
"""Delete is not currently supported """
raise exc.HTTPNotImplemented()
"""Deletes an existing image with the registry.
:param req: Request body. Ignored.
:param id: The opaque internal identifier for the image
:retval Returns 200 if delete was successful, a fault if not.
"""
context = None
updated_image = db.image_destroy(context, id)

def create(self, req):
"""Registers a new image with the registry.
Expand All @@ -88,42 +96,51 @@ def create(self, req):
in the 'id' field
"""
image_data = json.loads(req.body)
image_data = json.loads(req.body)['image']

# Ensure the image has a status set
image_data.setdefault('status', 'available')

context = None
new_image = db.image_create(context, image_data)
return dict(new_image)
return dict(image=new_image)

def update(self, req, id):
"""Update is not currently supported """
raise exc.HTTPNotImplemented()
"""Updates an existing image with the registry.
:param req: Request body. A JSON-ified dict of information about
the image. This will replace the information in the
registry about this image
:param id: The opaque internal identifier for the image
:retval Returns the updated image information as a mapping,
"""
image_data = json.loads(req.body)['image']

context = None
updated_image = db.image_update(context, id, image_data)
return dict(image=updated_image)

@staticmethod
def _make_image_dict(image):
""" Create a dict represenation of an image which we can use to
"""Create a dict representation of an image which we can use to
serialize the image.
"""

def _fetch_attrs(d, attrs):
return dict([(a, d[a]) for a in attrs])

# attributes common to all models
base_attrs = set(['id', 'created_at', 'updated_at', 'deleted_at',
'deleted'])

file_attrs = base_attrs | set(['location', 'size'])
files = [_fetch_attrs(f, file_attrs) for f in image['files']]
files = [_fetch_attrs(f, db.IMAGE_FILE_ATTRS) for f in image['files']]

# TODO(sirp): should this be a dict, or a list of dicts?
# A plain dict is more convenient, but list of dicts would provide
# access to created_at, etc
metadata = dict((m['key'], m['value']) for m in image['metadata']
if not m['deleted'])

image_attrs = base_attrs | set(['name', 'image_type', 'status', 'is_public'])
image_dict = _fetch_attrs(image, image_attrs)
image_dict = _fetch_attrs(image, db.IMAGE_ATTRS)

image_dict['files'] = files
image_dict['metadata'] = metadata
Expand Down
9 changes: 9 additions & 0 deletions glance/parallax/db/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,12 @@
"""

from glance.parallax.db.api import *

# attributes common to all models
BASE_MODEL_ATTRS = set(['id', 'created_at', 'updated_at', 'deleted_at',
'deleted'])

IMAGE_FILE_ATTRS = BASE_MODEL_ATTRS | set(['location', 'size'])

IMAGE_ATTRS = BASE_MODEL_ATTRS | set(['name', 'image_type', 'status',
'is_public'])
6 changes: 2 additions & 4 deletions glance/parallax/db/sqlalchemy/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,7 @@ def _deleted(context):

def image_create(_context, values):
image_ref = models.Image()
for (key, value) in values.iteritems():
image_ref[key] = value
image_ref.update(values)
image_ref.save()
return image_ref

Expand Down Expand Up @@ -107,8 +106,7 @@ def image_update(_context, image_id, values):
session = get_session()
with session.begin():
image_ref = models.Image.find(image_id, session=session)
for (key, value) in values.iteritems():
image_ref[key] = value
image_ref.update(values)
image_ref.save(session=session)


Expand Down
5 changes: 5 additions & 0 deletions glance/parallax/db/sqlalchemy/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,11 @@ def delete(self, session=None):
self.deleted_at = datetime.datetime.utcnow()
self.save(session=session)

def update(self, values):
"""dict.update() behaviour."""
for k, v in values.iteritems():
self[k] = v

def __setitem__(self, key, value):
setattr(self, key, value)

Expand Down
32 changes: 21 additions & 11 deletions tests/stubs.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import datetime
import httplib
import StringIO
import sys

import stubout

Expand Down Expand Up @@ -191,7 +192,7 @@ class FakeDatastore(object):
VALID_STATUSES = ('available', 'disabled', 'pending')

def __init__(self):
self.images = self.FIXTURES
self.images = FakeDatastore.FIXTURES
self.next_id = 3

def image_create(self, _context, values):
Expand All @@ -201,25 +202,32 @@ def image_create(self, _context, values):
values['status'] = 'available'
else:
if not values['status'] in self.VALID_STATUSES:
raise exception.Invalid("Invalid status '%s' for image" % values['status'])
raise exception.Invalid("Invalid status '%s' for image" %
values['status'])

self.next_id += 1
self.images.extend(values)
self.images.append(values)
return values

def image_update(self, _context, image_id, values):
image = self.image_get(_context, image_id)
image.update(values)
return image

def image_destroy(self, _context, image_id):
try:
del self.images[image_id]
except KeyError:
new_exc = exception.NotFound("No model for id %s" % image_id)
raise new_exc.__class__, new_exc, sys.exc_info()[2]
image = self.image_get(_context, image_id)
self.images.remove(image)

def image_get(self, _context, image_id):
if image_id not in self.images.keys() or self.images[image_id]['deleted']:
new_exc = exception.NotFound("No model for id %s" % image_id)

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]
else:
return self.images[image_id]
return images[0]

def image_get_all_public(self, _context, public):
return [f for f in self.images
Expand All @@ -228,6 +236,8 @@ def image_get_all_public(self, _context, public):
fake_datastore = FakeDatastore()
stubs.Set(glance.parallax.db.sqlalchemy.api, 'image_create',
fake_datastore.image_create)
stubs.Set(glance.parallax.db.sqlalchemy.api, 'image_update',
fake_datastore.image_update)
stubs.Set(glance.parallax.db.sqlalchemy.api, 'image_destroy',
fake_datastore.image_destroy)
stubs.Set(glance.parallax.db.sqlalchemy.api, 'image_get',
Expand Down
92 changes: 87 additions & 5 deletions tests/unit/test_parallax_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ def test_create_image(self):
req = webob.Request.blank('/images')

req.method = 'POST'
req.body = json.dumps(fixture)
req.body = json.dumps(dict(image=fixture))

res = req.get_response(controllers.API())

Expand All @@ -114,13 +114,13 @@ def test_create_image(self):
res_dict = json.loads(res.body)

for k,v in fixture.iteritems():
self.assertEquals(v, res_dict[k])
self.assertEquals(v, res_dict['image'][k])

# Test ID auto-assigned properly
self.assertEquals(3, res_dict['id'])
self.assertEquals(3, res_dict['image']['id'])

# Test status was updated properly
self.assertEquals('available', res_dict['status'])
self.assertEquals('available', res_dict['image']['status'])

def test_create_image_with_bad_status(self):
"""Tests proper exception is raised if a bad status is set"""
Expand All @@ -134,9 +134,91 @@ def test_create_image_with_bad_status(self):
req = webob.Request.blank('/images')

req.method = 'POST'
req.body = json.dumps(fixture)
req.body = json.dumps(dict(image=fixture))

# TODO(jaypipes): Port Nova's Fault infrastructure
# over to Glance to support exception catching into
# standard HTTP errors.
self.assertRaises(exception.Invalid, req.get_response, controllers.API())

def test_update_image(self):
"""Tests that the /images PUT parallax API updates the image"""
fixture = {'name': 'fake public image #2',
'image_type': 'ramdisk'
}

req = webob.Request.blank('/images/2')

req.method = 'PUT'
req.body = json.dumps(dict(image=fixture))

res = req.get_response(controllers.API())

self.assertEquals(res.status_int, 200)

res_dict = json.loads(res.body)

for k,v in fixture.iteritems():
self.assertEquals(v, res_dict['image'][k])

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,
'image_type': 'kernel',
'status': 'bad status'
}

req = webob.Request.blank('/images/3')

req.method = 'PUT'
req.body = json.dumps(dict(image=fixture))

# TODO(jaypipes): Port Nova's Fault infrastructure
# over to Glance to support exception catching into
# standard HTTP errors.
self.assertRaises(exception.NotFound, req.get_response, controllers.API())

def test_delete_image(self):
"""Tests that the /images DELETE parallax API deletes the image"""

# Grab the original number of images
req = webob.Request.blank('/images')
res = req.get_response(controllers.API())
res_dict = json.loads(res.body)
self.assertEquals(res.status_int, 200)

orig_num_images = len(res_dict['images'])

# Delete image #2
req = webob.Request.blank('/images/2')

req.method = 'DELETE'

res = req.get_response(controllers.API())

self.assertEquals(res.status_int, 200)

# Verify one less image
req = webob.Request.blank('/images')
res = req.get_response(controllers.API())
res_dict = json.loads(res.body)
self.assertEquals(res.status_int, 200)

new_num_images = len(res_dict['images'])
self.assertEquals(new_num_images, orig_num_images - 1)

def test_delete_image_not_existing(self):
"""Tests proper exception is raised if attempt to delete non-existing
image"""

req = webob.Request.blank('/images/3')

req.method = 'DELETE'

# TODO(jaypipes): Port Nova's Fault infrastructure
# over to Glance to support exception catching into
# standard HTTP errors.
self.assertRaises(exception.NotFound, req.get_response, controllers.API())

0 comments on commit a6962d6

Please sign in to comment.