From 5182c1eb21c28a61d0a43a4f4615073470bcd0a5 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 10 Apr 2012 21:53:28 -0700 Subject: [PATCH] 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 --- glance/api/v2/image_access.py | 84 ++++-- glance/api/v2/images.py | 75 ++++-- glance/api/v2/schemas.py | 83 +++--- glance/common/exception.py | 4 - glance/tests/unit/utils.py | 42 ++- .../unit/v2/test_image_access_resource.py | 244 ++++++++++++++---- glance/tests/unit/v2/test_images_resource.py | 119 ++++++--- glance/tests/unit/v2/test_schemas_resource.py | 43 +-- tools/pip-requires | 1 + 9 files changed, 463 insertions(+), 232 deletions(-) diff --git a/glance/api/v2/image_access.py b/glance/api/v2/image_access.py index 5ef4627638..2e3797d7e8 100644 --- a/glance/api/v2/image_access.py +++ b/glance/api/v2/image_access.py @@ -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) diff --git a/glance/api/v2/images.py b/glance/api/v2/images.py index a301001199..bcaaeb1462 100644 --- a/glance/api/v2/images.py +++ b/glance/api/v2/images.py @@ -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): diff --git a/glance/api/v2/schemas.py b/glance/api/v2/schemas.py index e8859a03de..39673e1644 100644 --- a/glance/api/v2/schemas.py +++ b/glance/api/v2/schemas.py @@ -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): diff --git a/glance/common/exception.py b/glance/common/exception.py index 907dde4d5d..980158f32e 100644 --- a/glance/common/exception.py +++ b/glance/common/exception.py @@ -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") diff --git a/glance/tests/unit/utils.py b/glance/tests/unit/utils.py index 97ee9007ba..e215c6e748 100644 --- a/glance/tests/unit/utils.py +++ b/glance/tests/unit/utils.py @@ -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 diff --git a/glance/tests/unit/v2/test_image_access_resource.py b/glance/tests/unit/v2/test_image_access_resource.py index 707ba4a1cb..75b63af186 100644 --- a/glance/tests/unit/v2/test_image_access_resource.py +++ b/glance/tests/unit/v2/test_image_access_resource.py @@ -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) diff --git a/glance/tests/unit/v2/test_images_resource.py b/glance/tests/unit/v2/test_images_resource.py index fe9866c7c3..4c9a69ac60 100644 --- a/glance/tests/unit/v2/test_images_resource.py +++ b/glance/tests/unit/v2/test_images_resource.py @@ -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)) diff --git a/glance/tests/unit/v2/test_schemas_resource.py b/glance/tests/unit/v2/test_schemas_resource.py index dba02f9565..13306e4f7d 100644 --- a/glance/tests/unit/v2/test_schemas_resource.py +++ b/glance/tests/unit/v2/test_schemas_resource.py @@ -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) diff --git a/tools/pip-requires b/tools/pip-requires index 07432ad82d..6e049dee36 100644 --- a/tools/pip-requires +++ b/tools/pip-requires @@ -39,3 +39,4 @@ lxml Paste passlib +jsonschema