API v2 controller/serialization separation
* images and access are now separated into deserializers, controllers, and serializers * now using schemas to valide incoming requests * adding create methods for image access * removing ImageNotFound as it was only being generated by fakes, using NotFound until the backend is updated Change-Id: Ida3c1b117ee0147e818b74518e84ef9101cbdfc3
This commit is contained in:
parent
fc816ee029
commit
5182c1eb21
@ -13,46 +13,84 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import webob.exc
|
||||
import json
|
||||
|
||||
import glance.api.v2.base
|
||||
from glance.common import exception
|
||||
import jsonschema
|
||||
|
||||
from glance.api.v2 import base
|
||||
from glance.api.v2 import schemas
|
||||
from glance.common import wsgi
|
||||
import glance.registry.db.api
|
||||
|
||||
|
||||
class ImageAccessController(glance.api.v2.base.Controller):
|
||||
class ImageAccessController(base.Controller):
|
||||
def __init__(self, conf, db=None):
|
||||
super(ImageAccessController, self).__init__(conf)
|
||||
self.db_api = db or glance.registry.db.api
|
||||
self.db_api.configure_db(conf)
|
||||
|
||||
def _format_access_record(self, image_member):
|
||||
return {
|
||||
'image_id': image_member['image_id'],
|
||||
'tenant_id': image_member['member'],
|
||||
'can_share': image_member['can_share'],
|
||||
'links': self._get_access_record_links(image_member),
|
||||
}
|
||||
def index(self, req, image_id):
|
||||
image = self.db_api.image_get(req.context, image_id)
|
||||
return image['members']
|
||||
|
||||
def _get_access_record_links(self, image_member):
|
||||
def show(self, req, image_id, tenant_id):
|
||||
return self.db_api.image_member_find(req.context, image_id, tenant_id)
|
||||
|
||||
def create(self, req, access):
|
||||
return self.db_api.image_member_create(req.context, access)
|
||||
|
||||
|
||||
class RequestDeserializer(wsgi.JSONRequestDeserializer):
|
||||
def __init__(self, conf):
|
||||
super(RequestDeserializer, self).__init__()
|
||||
self.conf = conf
|
||||
|
||||
def _validate(self, request, obj):
|
||||
schema = schemas.SchemasController(self.conf).access(request)
|
||||
jsonschema.validate(obj, schema)
|
||||
|
||||
def create(self, request):
|
||||
output = super(RequestDeserializer, self).default(request)
|
||||
body = output.pop('body')
|
||||
self._validate(request, body)
|
||||
body['member'] = body.pop('tenant_id')
|
||||
output['access'] = body
|
||||
return output
|
||||
|
||||
|
||||
class ResponseSerializer(wsgi.JSONResponseSerializer):
|
||||
def _get_access_href(self, image_member):
|
||||
image_id = image_member['image_id']
|
||||
tenant_id = image_member['member']
|
||||
self_href = '/v2/images/%s/access/%s' % (image_id, tenant_id)
|
||||
return '/v2/images/%s/access/%s' % (image_id, tenant_id)
|
||||
|
||||
def _get_access_links(self, access):
|
||||
return [
|
||||
{'rel': 'self', 'href': self_href},
|
||||
{'rel': 'self', 'href': self._get_access_href(access)},
|
||||
{'rel': 'describedby', 'href': '/v2/schemas/image/access'},
|
||||
]
|
||||
|
||||
def _format_access(self, access):
|
||||
return {
|
||||
'image_id': access['image_id'],
|
||||
'tenant_id': access['member'],
|
||||
'can_share': access['can_share'],
|
||||
'links': self._get_access_links(access),
|
||||
}
|
||||
|
||||
def _get_container_links(self, image_id):
|
||||
return [{'rel': 'self', 'href': '/v2/images/%s/access' % image_id}]
|
||||
|
||||
def index(self, req, image_id):
|
||||
try:
|
||||
members = self.db_api.get_image_members(req.context, image_id)
|
||||
except exception.NotFound:
|
||||
raise webob.exc.HTTPNotFound()
|
||||
records = [self._format_access_record(m) for m in members]
|
||||
return {
|
||||
'access_records': records,
|
||||
'links': self._get_container_links(image_id),
|
||||
def show(self, response, access):
|
||||
response.body = json.dumps({'access': self._format_access(access)})
|
||||
|
||||
def index(self, response, access_records):
|
||||
body = {
|
||||
'access_records': [self._format_access(a) for a in access_records],
|
||||
'links': [],
|
||||
}
|
||||
response.body = json.dumps(body)
|
||||
|
||||
def create(self, response, access):
|
||||
response.body = json.dumps({'access': self._format_access(access)})
|
||||
response.location = self._get_access_href(access)
|
||||
|
@ -13,22 +13,56 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import webob.exc
|
||||
import json
|
||||
|
||||
import glance.api.v2.base
|
||||
from glance.common import exception
|
||||
import jsonschema
|
||||
|
||||
from glance.api.v2 import base
|
||||
from glance.api.v2 import schemas
|
||||
from glance.common import wsgi
|
||||
import glance.registry.db.api
|
||||
|
||||
|
||||
class ImagesController(glance.api.v2.base.Controller):
|
||||
"""WSGI controller for images resource in Glance v2 API."""
|
||||
|
||||
class ImagesController(base.Controller):
|
||||
def __init__(self, conf, db=None):
|
||||
super(ImagesController, self).__init__(conf)
|
||||
self.db_api = db or glance.registry.db.api
|
||||
self.db_api.configure_db(conf)
|
||||
|
||||
def index(self, req):
|
||||
return self.db_api.image_get_all(req.context)
|
||||
|
||||
def show(self, req, id):
|
||||
return self.db_api.image_get(req.context, id)
|
||||
|
||||
|
||||
class RequestDeserializer(wsgi.JSONRequestDeserializer):
|
||||
def __init__(self, conf):
|
||||
super(RequestDeserializer, self).__init__()
|
||||
self.conf = conf
|
||||
|
||||
def _validate(self, request, obj):
|
||||
schema = schemas.SchemasController(self.conf).image(request)
|
||||
jsonschema.validate(obj, schema)
|
||||
|
||||
def create(self, request):
|
||||
output = super(RequestDeserializer, self).default(request)
|
||||
body = output.pop('body')
|
||||
self._validate(request, body)
|
||||
output['image'] = body
|
||||
return output
|
||||
|
||||
|
||||
class ResponseSerializer(wsgi.JSONResponseSerializer):
|
||||
def _get_image_href(self, image):
|
||||
return '/v2/images/%s' % image['id']
|
||||
|
||||
def _get_image_links(self, image):
|
||||
return [
|
||||
{'rel': 'self', 'href': self._get_image_href(image)},
|
||||
{'rel': 'describedby', 'href': '/v2/schemas/image'},
|
||||
]
|
||||
|
||||
def _format_image(self, image):
|
||||
props = ['id', 'name']
|
||||
items = filter(lambda item: item[0] in props, image.iteritems())
|
||||
@ -36,30 +70,19 @@ class ImagesController(glance.api.v2.base.Controller):
|
||||
obj['links'] = self._get_image_links(image)
|
||||
return obj
|
||||
|
||||
def _get_image_links(self, image):
|
||||
image_id = image['id']
|
||||
return [
|
||||
{'rel': 'self', 'href': '/v2/images/%s' % image_id},
|
||||
{'rel': 'access', 'href': '/v2/images/%s/access' % image_id},
|
||||
{'rel': 'describedby', 'href': '/v2/schemas/image'},
|
||||
]
|
||||
def create(self, response, image):
|
||||
response.body = json.dumps({'image': self._format_image(image)})
|
||||
response.location = self._get_image_href(image)
|
||||
|
||||
def _get_container_links(self, images):
|
||||
return []
|
||||
def show(self, response, image):
|
||||
response.body = json.dumps({'image': self._format_image(image)})
|
||||
|
||||
def index(self, req):
|
||||
images = self.db_api.image_get_all(req.context)
|
||||
return {
|
||||
def index(self, response, images):
|
||||
body = {
|
||||
'images': [self._format_image(i) for i in images],
|
||||
'links': self._get_container_links(images),
|
||||
'links': [],
|
||||
}
|
||||
|
||||
def show(self, req, id):
|
||||
try:
|
||||
image = self.db_api.image_get(req.context, id)
|
||||
except exception.ImageNotFound:
|
||||
raise webob.exc.HTTPNotFound()
|
||||
return self._format_image(image)
|
||||
response.body = json.dumps(body)
|
||||
|
||||
|
||||
def create_resource(conf):
|
||||
|
@ -17,6 +17,48 @@ import glance.api.v2.base
|
||||
from glance.common import wsgi
|
||||
|
||||
|
||||
#NOTE(bcwaldon): this is temporary until we generate them on the fly
|
||||
IMAGE_SCHEMA = {
|
||||
"name": "image",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "An identifier for the image",
|
||||
"required": False,
|
||||
"maxLength": 36,
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Descriptive name for the image",
|
||||
"required": True,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
ACCESS_SCHEMA = {
|
||||
'name': 'access',
|
||||
'properties': {
|
||||
"image_id": {
|
||||
"type": "string",
|
||||
"description": "The image identifier",
|
||||
"required": True,
|
||||
"maxLength": 36,
|
||||
},
|
||||
"tenant_id": {
|
||||
"type": "string",
|
||||
"description": "The tenant identifier",
|
||||
"required": True,
|
||||
},
|
||||
"can_share": {
|
||||
"type": "boolean",
|
||||
"description": "Ability of tenant to share with others",
|
||||
"required": True,
|
||||
"default": False,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class SchemasController(glance.api.v2.base.Controller):
|
||||
def index(self, req):
|
||||
links = [
|
||||
@ -26,47 +68,10 @@ class SchemasController(glance.api.v2.base.Controller):
|
||||
return {'links': links}
|
||||
|
||||
def image(self, req):
|
||||
return {
|
||||
"name": "image",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "An identifier for the image",
|
||||
"required": True,
|
||||
"maxLength": 32,
|
||||
"readonly": True
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Descriptive name for the image",
|
||||
"required": True,
|
||||
},
|
||||
},
|
||||
}
|
||||
return IMAGE_SCHEMA
|
||||
|
||||
def access(self, req):
|
||||
return {
|
||||
'name': 'access',
|
||||
'properties': {
|
||||
"image_id": {
|
||||
"type": "string",
|
||||
"description": "The image identifier",
|
||||
"required": True,
|
||||
"maxLength": 32,
|
||||
},
|
||||
"tenant_id": {
|
||||
"type": "string",
|
||||
"description": "The tenant identifier",
|
||||
"required": True,
|
||||
},
|
||||
"can_share": {
|
||||
"type": "boolean",
|
||||
"description": "Ability of tenant to share with others",
|
||||
"required": True,
|
||||
"default": False,
|
||||
},
|
||||
},
|
||||
}
|
||||
return ACCESS_SCHEMA
|
||||
|
||||
|
||||
def create_resource(conf):
|
||||
|
@ -72,10 +72,6 @@ class NotFound(GlanceException):
|
||||
message = _("An object with the specified identifier was not found.")
|
||||
|
||||
|
||||
class ImageNotFound(NotFound):
|
||||
message = _("Image %(image_id)s was not found.")
|
||||
|
||||
|
||||
class UnknownScheme(GlanceException):
|
||||
message = _("Unknown scheme '%(scheme)s' found in URI")
|
||||
|
||||
|
@ -13,6 +13,8 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import webob
|
||||
|
||||
from glance.common import exception
|
||||
|
||||
|
||||
@ -23,7 +25,11 @@ TENANT1 = '6838eb7b-6ded-434a-882c-b344c77fe8df'
|
||||
TENANT2 = '2c014f32-55eb-467d-8fcb-4bd706012f81'
|
||||
|
||||
|
||||
class FakeRequest(object):
|
||||
class FakeRequest(webob.Request):
|
||||
def __init__(self):
|
||||
#TODO(bcwaldon): figure out how to fake this out cleanly
|
||||
super(FakeRequest, self).__init__({'REQUEST_METHOD': 'POST'})
|
||||
|
||||
@property
|
||||
def context(self):
|
||||
return
|
||||
@ -44,6 +50,9 @@ class FakeDB(object):
|
||||
UUID2: [],
|
||||
}
|
||||
|
||||
self.images[UUID1]['members'] = self.members[UUID1]
|
||||
self.images[UUID2]['members'] = self.members[UUID2]
|
||||
|
||||
def reset(self):
|
||||
self.images = {}
|
||||
self.members = {}
|
||||
@ -59,21 +68,38 @@ class FakeDB(object):
|
||||
}
|
||||
|
||||
def _image_format(self, image_id):
|
||||
return {'id': image_id, 'name': 'image-name', 'foo': 'bar'}
|
||||
return {'id': image_id, 'name': 'image-name'}
|
||||
|
||||
def image_get(self, context, image_id):
|
||||
try:
|
||||
return self.images[image_id]
|
||||
image = self.images[image_id]
|
||||
except KeyError:
|
||||
raise exception.ImageNotFound(image_id=image_id)
|
||||
raise exception.NotFound(image_id=image_id)
|
||||
|
||||
#NOTE(bcwaldon: this is a hack until we can get image members with
|
||||
# a direct db call
|
||||
image['members'] = self.members.get(image_id, [])
|
||||
|
||||
return image
|
||||
|
||||
def image_get_all(self, context):
|
||||
return self.images.values()
|
||||
|
||||
def get_image_members(self, context, image_id):
|
||||
def image_member_find(self, context, image_id, tenant_id):
|
||||
try:
|
||||
self.images[image_id]
|
||||
except KeyError:
|
||||
raise exception.ImageNotFound()
|
||||
else:
|
||||
return self.members.get(image_id, [])
|
||||
raise exception.NotFound()
|
||||
|
||||
for member in self.members.get(image_id, []):
|
||||
if member['member'] == tenant_id:
|
||||
return member
|
||||
|
||||
raise exception.NotFound()
|
||||
|
||||
def image_member_create(self, context, values):
|
||||
member = self._image_member_format(values['image_id'],
|
||||
values['member'],
|
||||
values['can_share'])
|
||||
self.members[values['image_id']] = member
|
||||
return member
|
||||
|
@ -13,11 +13,14 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import json
|
||||
import unittest
|
||||
|
||||
import webob.exc
|
||||
import jsonschema
|
||||
import webob
|
||||
|
||||
import glance.api.v2.image_access
|
||||
from glance.common import exception
|
||||
from glance.common import utils
|
||||
import glance.tests.unit.utils as test_utils
|
||||
|
||||
@ -33,67 +36,200 @@ class TestImageAccessController(unittest.TestCase):
|
||||
def test_index(self):
|
||||
req = test_utils.FakeRequest()
|
||||
output = self.controller.index(req, test_utils.UUID1)
|
||||
expected = {
|
||||
'access_records': [
|
||||
{
|
||||
'image_id': test_utils.UUID1,
|
||||
'tenant_id': test_utils.TENANT1,
|
||||
'can_share': True,
|
||||
'links': [
|
||||
{
|
||||
'rel': 'self',
|
||||
'href': ('/v2/images/%s/access/%s' %
|
||||
(test_utils.UUID1, test_utils.TENANT1)),
|
||||
},
|
||||
{
|
||||
'rel': 'describedby',
|
||||
'href': '/v2/schemas/image/access',
|
||||
},
|
||||
|
||||
],
|
||||
},
|
||||
{
|
||||
'image_id': test_utils.UUID1,
|
||||
'tenant_id': test_utils.TENANT2,
|
||||
'can_share': False,
|
||||
'links': [
|
||||
{
|
||||
'rel': 'self',
|
||||
'href': ('/v2/images/%s/access/%s' %
|
||||
(test_utils.UUID1, test_utils.TENANT2)),
|
||||
},
|
||||
{
|
||||
'rel': 'describedby',
|
||||
'href': '/v2/schemas/image/access',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
'links': [
|
||||
{
|
||||
'rel': 'self',
|
||||
'href': '/v2/images/%s/access' % test_utils.UUID1,
|
||||
},
|
||||
],
|
||||
}
|
||||
expected = [
|
||||
{
|
||||
'image_id': test_utils.UUID1,
|
||||
'member': test_utils.TENANT1,
|
||||
'can_share': True,
|
||||
},
|
||||
{
|
||||
'image_id': test_utils.UUID1,
|
||||
'member': test_utils.TENANT2,
|
||||
'can_share': False,
|
||||
},
|
||||
]
|
||||
self.assertEqual(expected, output)
|
||||
|
||||
def test_index_zero_records(self):
|
||||
req = test_utils.FakeRequest()
|
||||
output = self.controller.index(req, test_utils.UUID2)
|
||||
expected = {
|
||||
'access_records': [],
|
||||
'links': [
|
||||
{
|
||||
'rel': 'self',
|
||||
'href': '/v2/images/%s/access' % test_utils.UUID2,
|
||||
},
|
||||
],
|
||||
}
|
||||
expected = []
|
||||
self.assertEqual(expected, output)
|
||||
|
||||
def test_index_nonexistant_image(self):
|
||||
req = test_utils.FakeRequest()
|
||||
image_id = utils.generate_uuid()
|
||||
self.assertRaises(webob.exc.HTTPNotFound,
|
||||
self.assertRaises(exception.NotFound,
|
||||
self.controller.index, req, image_id)
|
||||
|
||||
def test_show(self):
|
||||
req = test_utils.FakeRequest()
|
||||
image_id = test_utils.UUID1
|
||||
tenant_id = test_utils.TENANT1
|
||||
output = self.controller.show(req, image_id, tenant_id)
|
||||
expected = {
|
||||
'image_id': image_id,
|
||||
'member': tenant_id,
|
||||
'can_share': True,
|
||||
}
|
||||
self.assertEqual(expected, output)
|
||||
|
||||
def test_show_nonexistant_image(self):
|
||||
req = test_utils.FakeRequest()
|
||||
image_id = utils.generate_uuid()
|
||||
tenant_id = test_utils.TENANT1
|
||||
self.assertRaises(exception.NotFound,
|
||||
self.controller.show, req, image_id, tenant_id)
|
||||
|
||||
def test_show_nonexistant_tenant(self):
|
||||
req = test_utils.FakeRequest()
|
||||
image_id = test_utils.UUID1
|
||||
tenant_id = utils.generate_uuid()
|
||||
self.assertRaises(exception.NotFound,
|
||||
self.controller.show, req, image_id, tenant_id)
|
||||
|
||||
def test_create(self):
|
||||
fixture = {
|
||||
'image_id': test_utils.UUID1,
|
||||
'member': utils.generate_uuid(),
|
||||
'can_share': True,
|
||||
}
|
||||
req = test_utils.FakeRequest()
|
||||
output = self.controller.create(req, fixture)
|
||||
self.assertEqual(fixture, output)
|
||||
|
||||
|
||||
class TestImageAccessDeserializer(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.deserializer = glance.api.v2.image_access.RequestDeserializer({})
|
||||
|
||||
def test_create(self):
|
||||
fixture = {
|
||||
'image_id': test_utils.UUID1,
|
||||
'tenant_id': test_utils.TENANT1,
|
||||
'can_share': False,
|
||||
}
|
||||
expected = {
|
||||
'image_id': test_utils.UUID1,
|
||||
'member': test_utils.TENANT1,
|
||||
'can_share': False,
|
||||
}
|
||||
request = test_utils.FakeRequest()
|
||||
request.body = json.dumps(fixture)
|
||||
output = self.deserializer.create(request)
|
||||
self.assertEqual(output, {'access': expected})
|
||||
|
||||
def _test_create_fails(self, fixture):
|
||||
request = test_utils.FakeRequest()
|
||||
request.body = json.dumps(fixture)
|
||||
self.assertRaises(jsonschema.ValidationError,
|
||||
self.deserializer.create, request)
|
||||
|
||||
def test_create_no_image(self):
|
||||
fixture = {'tenant_id': test_utils.TENANT1, 'can_share': True}
|
||||
self._test_create_fails(fixture)
|
||||
|
||||
|
||||
class TestImageAccessSerializer(unittest.TestCase):
|
||||
serializer = glance.api.v2.image_access.ResponseSerializer()
|
||||
|
||||
def test_show(self):
|
||||
fixture = {
|
||||
'member': test_utils.TENANT1,
|
||||
'image_id': test_utils.UUID1,
|
||||
'can_share': False,
|
||||
}
|
||||
self_href = ('/v2/images/%s/access/%s' %
|
||||
(test_utils.UUID1, test_utils.TENANT1))
|
||||
expected = {
|
||||
'access': {
|
||||
'image_id': test_utils.UUID1,
|
||||
'tenant_id': test_utils.TENANT1,
|
||||
'can_share': False,
|
||||
'links': [
|
||||
{'rel': 'self', 'href': self_href},
|
||||
{'rel': 'describedby', 'href': '/v2/schemas/image/access'},
|
||||
],
|
||||
},
|
||||
}
|
||||
response = webob.Response()
|
||||
self.serializer.show(response, fixture)
|
||||
self.assertEqual(expected, json.loads(response.body))
|
||||
|
||||
def test_index(self):
|
||||
fixtures = [
|
||||
{
|
||||
'member': test_utils.TENANT1,
|
||||
'image_id': test_utils.UUID1,
|
||||
'can_share': False,
|
||||
},
|
||||
{
|
||||
'member': test_utils.TENANT2,
|
||||
'image_id': test_utils.UUID2,
|
||||
'can_share': True,
|
||||
},
|
||||
]
|
||||
expected = {
|
||||
'access_records': [
|
||||
{
|
||||
'image_id': test_utils.UUID1,
|
||||
'tenant_id': test_utils.TENANT1,
|
||||
'can_share': False,
|
||||
'links': [
|
||||
{
|
||||
'rel': 'self',
|
||||
'href': ('/v2/images/%s/access/%s' %
|
||||
(test_utils.UUID1, test_utils.TENANT1))
|
||||
},
|
||||
{
|
||||
'rel': 'describedby',
|
||||
'href': '/v2/schemas/image/access',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
'image_id': test_utils.UUID2,
|
||||
'tenant_id': test_utils.TENANT2,
|
||||
'can_share': True,
|
||||
'links': [
|
||||
{
|
||||
'rel': 'self',
|
||||
'href': ('/v2/images/%s/access/%s' %
|
||||
(test_utils.UUID2, test_utils.TENANT2))
|
||||
},
|
||||
{
|
||||
'rel': 'describedby',
|
||||
'href': '/v2/schemas/image/access',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
'links': [],
|
||||
}
|
||||
response = webob.Response()
|
||||
self.serializer.index(response, fixtures)
|
||||
self.assertEqual(expected, json.loads(response.body))
|
||||
|
||||
def test_create(self):
|
||||
fixture = {
|
||||
'member': test_utils.TENANT1,
|
||||
'image_id': test_utils.UUID1,
|
||||
'can_share': False,
|
||||
}
|
||||
self_href = ('/v2/images/%s/access/%s' %
|
||||
(test_utils.UUID1, test_utils.TENANT1))
|
||||
expected = {
|
||||
'access': {
|
||||
'image_id': test_utils.UUID1,
|
||||
'tenant_id': test_utils.TENANT1,
|
||||
'can_share': False,
|
||||
'links': [
|
||||
{'rel': 'self', 'href': self_href},
|
||||
{'rel': 'describedby', 'href': '/v2/schemas/image/access'},
|
||||
],
|
||||
},
|
||||
}
|
||||
response = webob.Response()
|
||||
self.serializer.create(response, fixture)
|
||||
self.assertEqual(expected, json.loads(response.body))
|
||||
self.assertEqual(self_href, response.location)
|
||||
|
@ -13,85 +13,130 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import json
|
||||
import unittest
|
||||
|
||||
import jsonschema
|
||||
import webob
|
||||
|
||||
import glance.api.v2.images
|
||||
from glance.common import exception
|
||||
from glance.common import utils
|
||||
import glance.tests.unit.utils as test_utils
|
||||
|
||||
|
||||
class TestImagesController(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestImagesController, self).setUp()
|
||||
self.db = test_utils.FakeDB()
|
||||
self.controller = glance.api.v2.images.ImagesController({}, self.db)
|
||||
|
||||
def test_index(self):
|
||||
req = test_utils.FakeRequest()
|
||||
output = self.controller.index(req)
|
||||
request = test_utils.FakeRequest()
|
||||
output = self.controller.index(request)
|
||||
self.assertEqual(2, len(output))
|
||||
self.assertEqual(output[0]['id'], test_utils.UUID1)
|
||||
self.assertEqual(output[1]['id'], test_utils.UUID2)
|
||||
|
||||
def test_index_zero_images(self):
|
||||
self.db.reset()
|
||||
request = test_utils.FakeRequest()
|
||||
output = self.controller.index(request)
|
||||
self.assertEqual([], output)
|
||||
|
||||
def test_show(self):
|
||||
request = test_utils.FakeRequest()
|
||||
output = self.controller.show(request, id=test_utils.UUID2)
|
||||
self.assertEqual(output['id'], test_utils.UUID2)
|
||||
|
||||
def test_show_non_existant(self):
|
||||
self.assertRaises(exception.NotFound, self.controller.show,
|
||||
test_utils.FakeRequest(), id=utils.generate_uuid())
|
||||
|
||||
|
||||
class TestImagesDeserializer(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.deserializer = glance.api.v2.images.RequestDeserializer({})
|
||||
|
||||
def test_create(self):
|
||||
request = test_utils.FakeRequest()
|
||||
request.body = json.dumps({'name': 'image-1'})
|
||||
output = self.deserializer.create(request)
|
||||
self.assertEqual(output, {'image': {'name': 'image-1'}})
|
||||
|
||||
def test_create_with_id(self):
|
||||
request = test_utils.FakeRequest()
|
||||
image_id = utils.generate_uuid()
|
||||
request.body = json.dumps({'id': image_id, 'name': 'image-1'})
|
||||
output = self.deserializer.create(request)
|
||||
self.assertEqual(output,
|
||||
{'image': {'id': image_id, 'name': 'image-1'}})
|
||||
|
||||
def _test_create_fails(self, body):
|
||||
request = test_utils.FakeRequest()
|
||||
request.body = json.dumps(body)
|
||||
self.assertRaises(jsonschema.ValidationError,
|
||||
self.deserializer.create, request)
|
||||
|
||||
def test_create_no_name(self):
|
||||
self._test_create_fails({})
|
||||
|
||||
|
||||
class TestImagesSerializer(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.serializer = glance.api.v2.images.ResponseSerializer()
|
||||
|
||||
def test_index(self):
|
||||
fixtures = [
|
||||
{'id': test_utils.UUID1, 'name': 'image-1'},
|
||||
{'id': test_utils.UUID2, 'name': 'image-2'},
|
||||
]
|
||||
expected = {
|
||||
'images': [
|
||||
{
|
||||
'id': test_utils.UUID1,
|
||||
'name': 'image-name',
|
||||
'name': 'image-1',
|
||||
'links': [
|
||||
{
|
||||
'rel': 'self',
|
||||
'href': '/v2/images/%s' % test_utils.UUID1,
|
||||
},
|
||||
{
|
||||
'rel': 'access',
|
||||
'href': '/v2/images/%s/access' % test_utils.UUID1,
|
||||
},
|
||||
{'rel': 'describedby', 'href': '/v2/schemas/image'}
|
||||
],
|
||||
},
|
||||
{
|
||||
'id': test_utils.UUID2,
|
||||
'name': 'image-name',
|
||||
'name': 'image-2',
|
||||
'links': [
|
||||
{
|
||||
'rel': 'self',
|
||||
'href': '/v2/images/%s' % test_utils.UUID2,
|
||||
},
|
||||
{
|
||||
'rel': 'access',
|
||||
'href': '/v2/images/%s/access' % test_utils.UUID2,
|
||||
},
|
||||
{'rel': 'describedby', 'href': '/v2/schemas/image'}
|
||||
],
|
||||
},
|
||||
],
|
||||
'links': [],
|
||||
}
|
||||
self.assertEqual(expected, output)
|
||||
|
||||
def test_index_zero_images(self):
|
||||
self.db.reset()
|
||||
req = test_utils.FakeRequest()
|
||||
output = self.controller.index(req)
|
||||
self.assertEqual({'images': [], 'links': []}, output)
|
||||
response = webob.Response()
|
||||
self.serializer.index(response, fixtures)
|
||||
self.assertEqual(expected, json.loads(response.body))
|
||||
|
||||
def test_show(self):
|
||||
req = test_utils.FakeRequest()
|
||||
output = self.controller.show(req, id=test_utils.UUID2)
|
||||
fixture = {'id': test_utils.UUID2, 'name': 'image-2'}
|
||||
expected = {
|
||||
'id': test_utils.UUID2,
|
||||
'name': 'image-name',
|
||||
'links': [
|
||||
{'rel': 'self', 'href': '/v2/images/%s' % test_utils.UUID2},
|
||||
{
|
||||
'rel': 'access',
|
||||
'href': '/v2/images/%s/access' % test_utils.UUID2,
|
||||
},
|
||||
{'rel': 'describedby', 'href': '/v2/schemas/image'}
|
||||
],
|
||||
'image': {
|
||||
'id': test_utils.UUID2,
|
||||
'name': 'image-2',
|
||||
'links': [
|
||||
{
|
||||
'rel': 'self',
|
||||
'href': '/v2/images/%s' % test_utils.UUID2,
|
||||
},
|
||||
{'rel': 'describedby', 'href': '/v2/schemas/image'}
|
||||
],
|
||||
},
|
||||
}
|
||||
self.assertEqual(expected, output)
|
||||
|
||||
def test_show_non_existant(self):
|
||||
self.assertRaises(webob.exc.HTTPNotFound, self.controller.show,
|
||||
test_utils.FakeRequest(), id=utils.generate_uuid())
|
||||
response = webob.Response()
|
||||
self.serializer.show(response, fixture)
|
||||
self.assertEqual(expected, json.loads(response.body))
|
||||
|
@ -37,48 +37,9 @@ class TestSchemasController(unittest.TestCase):
|
||||
def test_image(self):
|
||||
req = test_utils.FakeRequest()
|
||||
output = self.controller.image(req)
|
||||
expected = {
|
||||
'name': 'image',
|
||||
'properties': {
|
||||
'id': {
|
||||
'type': 'string',
|
||||
'description': 'An identifier for the image',
|
||||
'required': True,
|
||||
'maxLength': 32,
|
||||
'readonly': True
|
||||
},
|
||||
'name': {
|
||||
'type': 'string',
|
||||
'description': 'Descriptive name for the image',
|
||||
'required': True,
|
||||
},
|
||||
},
|
||||
}
|
||||
self.assertEqual(expected, output)
|
||||
self.assertEqual(glance.api.v2.schemas.IMAGE_SCHEMA, output)
|
||||
|
||||
def test_access(self):
|
||||
req = test_utils.FakeRequest()
|
||||
output = self.controller.access(req)
|
||||
expected = {
|
||||
'name': 'access',
|
||||
'properties': {
|
||||
"image_id": {
|
||||
"type": "string",
|
||||
"description": "The image identifier",
|
||||
"required": True,
|
||||
"maxLength": 32,
|
||||
},
|
||||
"tenant_id": {
|
||||
"type": "string",
|
||||
"description": "The tenant identifier",
|
||||
"required": True,
|
||||
},
|
||||
"can_share": {
|
||||
"type": "boolean",
|
||||
"description": "Ability of tenant to share with others",
|
||||
"required": True,
|
||||
"default": False,
|
||||
},
|
||||
},
|
||||
}
|
||||
self.assertEqual(output, expected)
|
||||
self.assertEqual(glance.api.v2.schemas.ACCESS_SCHEMA, output)
|
||||
|
@ -39,3 +39,4 @@ lxml
|
||||
Paste
|
||||
|
||||
passlib
|
||||
jsonschema
|
||||
|
Loading…
Reference in New Issue
Block a user