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:
Brian Waldon 2012-04-10 21:53:28 -07:00
parent fc816ee029
commit 5182c1eb21
9 changed files with 463 additions and 232 deletions

@ -13,46 +13,84 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import webob.exc import json
import glance.api.v2.base import jsonschema
from glance.common import exception
from glance.api.v2 import base
from glance.api.v2 import schemas
from glance.common import wsgi
import glance.registry.db.api import glance.registry.db.api
class ImageAccessController(glance.api.v2.base.Controller): class ImageAccessController(base.Controller):
def __init__(self, conf, db=None): def __init__(self, conf, db=None):
super(ImageAccessController, self).__init__(conf) super(ImageAccessController, self).__init__(conf)
self.db_api = db or glance.registry.db.api self.db_api = db or glance.registry.db.api
self.db_api.configure_db(conf) self.db_api.configure_db(conf)
def _format_access_record(self, image_member): def index(self, req, image_id):
return { image = self.db_api.image_get(req.context, image_id)
'image_id': image_member['image_id'], return image['members']
'tenant_id': image_member['member'],
'can_share': image_member['can_share'],
'links': self._get_access_record_links(image_member),
}
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'] image_id = image_member['image_id']
tenant_id = image_member['member'] 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 [ return [
{'rel': 'self', 'href': self_href}, {'rel': 'self', 'href': self._get_access_href(access)},
{'rel': 'describedby', 'href': '/v2/schemas/image/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): def _get_container_links(self, image_id):
return [{'rel': 'self', 'href': '/v2/images/%s/access' % image_id}] return [{'rel': 'self', 'href': '/v2/images/%s/access' % image_id}]
def index(self, req, image_id): def show(self, response, access):
try: response.body = json.dumps({'access': self._format_access(access)})
members = self.db_api.get_image_members(req.context, image_id)
except exception.NotFound: def index(self, response, access_records):
raise webob.exc.HTTPNotFound() body = {
records = [self._format_access_record(m) for m in members] 'access_records': [self._format_access(a) for a in access_records],
return { 'links': [],
'access_records': records,
'links': self._get_container_links(image_id),
} }
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 # License for the specific language governing permissions and limitations
# under the License. # under the License.
import webob.exc import json
import glance.api.v2.base import jsonschema
from glance.common import exception
from glance.api.v2 import base
from glance.api.v2 import schemas
from glance.common import wsgi from glance.common import wsgi
import glance.registry.db.api import glance.registry.db.api
class ImagesController(glance.api.v2.base.Controller): class ImagesController(base.Controller):
"""WSGI controller for images resource in Glance v2 API."""
def __init__(self, conf, db=None): def __init__(self, conf, db=None):
super(ImagesController, self).__init__(conf) super(ImagesController, self).__init__(conf)
self.db_api = db or glance.registry.db.api self.db_api = db or glance.registry.db.api
self.db_api.configure_db(conf) 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): def _format_image(self, image):
props = ['id', 'name'] props = ['id', 'name']
items = filter(lambda item: item[0] in props, image.iteritems()) 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) obj['links'] = self._get_image_links(image)
return obj return obj
def _get_image_links(self, image): def create(self, response, image):
image_id = image['id'] response.body = json.dumps({'image': self._format_image(image)})
return [ response.location = self._get_image_href(image)
{'rel': 'self', 'href': '/v2/images/%s' % image_id},
{'rel': 'access', 'href': '/v2/images/%s/access' % image_id},
{'rel': 'describedby', 'href': '/v2/schemas/image'},
]
def _get_container_links(self, images): def show(self, response, image):
return [] response.body = json.dumps({'image': self._format_image(image)})
def index(self, req): def index(self, response, images):
images = self.db_api.image_get_all(req.context) body = {
return {
'images': [self._format_image(i) for i in images], 'images': [self._format_image(i) for i in images],
'links': self._get_container_links(images), 'links': [],
} }
response.body = json.dumps(body)
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)
def create_resource(conf): def create_resource(conf):

@ -17,6 +17,48 @@ import glance.api.v2.base
from glance.common import wsgi 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): class SchemasController(glance.api.v2.base.Controller):
def index(self, req): def index(self, req):
links = [ links = [
@ -26,47 +68,10 @@ class SchemasController(glance.api.v2.base.Controller):
return {'links': links} return {'links': links}
def image(self, req): def image(self, req):
return { return IMAGE_SCHEMA
"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,
},
},
}
def access(self, req): def access(self, req):
return { return ACCESS_SCHEMA
'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,
},
},
}
def create_resource(conf): def create_resource(conf):

@ -72,10 +72,6 @@ class NotFound(GlanceException):
message = _("An object with the specified identifier was not found.") message = _("An object with the specified identifier was not found.")
class ImageNotFound(NotFound):
message = _("Image %(image_id)s was not found.")
class UnknownScheme(GlanceException): class UnknownScheme(GlanceException):
message = _("Unknown scheme '%(scheme)s' found in URI") message = _("Unknown scheme '%(scheme)s' found in URI")

@ -13,6 +13,8 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import webob
from glance.common import exception from glance.common import exception
@ -23,7 +25,11 @@ TENANT1 = '6838eb7b-6ded-434a-882c-b344c77fe8df'
TENANT2 = '2c014f32-55eb-467d-8fcb-4bd706012f81' 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 @property
def context(self): def context(self):
return return
@ -44,6 +50,9 @@ class FakeDB(object):
UUID2: [], UUID2: [],
} }
self.images[UUID1]['members'] = self.members[UUID1]
self.images[UUID2]['members'] = self.members[UUID2]
def reset(self): def reset(self):
self.images = {} self.images = {}
self.members = {} self.members = {}
@ -59,21 +68,38 @@ class FakeDB(object):
} }
def _image_format(self, image_id): 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): def image_get(self, context, image_id):
try: try:
return self.images[image_id] image = self.images[image_id]
except KeyError: 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): def image_get_all(self, context):
return self.images.values() return self.images.values()
def get_image_members(self, context, image_id): def image_member_find(self, context, image_id, tenant_id):
try: try:
self.images[image_id] self.images[image_id]
except KeyError: except KeyError:
raise exception.ImageNotFound() raise exception.NotFound()
else:
return self.members.get(image_id, []) 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 # License for the specific language governing permissions and limitations
# under the License. # under the License.
import json
import unittest import unittest
import webob.exc import jsonschema
import webob
import glance.api.v2.image_access import glance.api.v2.image_access
from glance.common import exception
from glance.common import utils from glance.common import utils
import glance.tests.unit.utils as test_utils import glance.tests.unit.utils as test_utils
@ -33,67 +36,200 @@ class TestImageAccessController(unittest.TestCase):
def test_index(self): def test_index(self):
req = test_utils.FakeRequest() req = test_utils.FakeRequest()
output = self.controller.index(req, test_utils.UUID1) output = self.controller.index(req, test_utils.UUID1)
expected = { expected = [
'access_records': [ {
{ 'image_id': test_utils.UUID1,
'image_id': test_utils.UUID1, 'member': test_utils.TENANT1,
'tenant_id': test_utils.TENANT1, 'can_share': True,
'can_share': True, },
'links': [ {
{ 'image_id': test_utils.UUID1,
'rel': 'self', 'member': test_utils.TENANT2,
'href': ('/v2/images/%s/access/%s' % 'can_share': False,
(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,
},
],
}
self.assertEqual(expected, output) self.assertEqual(expected, output)
def test_index_zero_records(self): def test_index_zero_records(self):
req = test_utils.FakeRequest() req = test_utils.FakeRequest()
output = self.controller.index(req, test_utils.UUID2) output = self.controller.index(req, test_utils.UUID2)
expected = { expected = []
'access_records': [],
'links': [
{
'rel': 'self',
'href': '/v2/images/%s/access' % test_utils.UUID2,
},
],
}
self.assertEqual(expected, output) self.assertEqual(expected, output)
def test_index_nonexistant_image(self): def test_index_nonexistant_image(self):
req = test_utils.FakeRequest() req = test_utils.FakeRequest()
image_id = utils.generate_uuid() image_id = utils.generate_uuid()
self.assertRaises(webob.exc.HTTPNotFound, self.assertRaises(exception.NotFound,
self.controller.index, req, image_id) 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 # License for the specific language governing permissions and limitations
# under the License. # under the License.
import json
import unittest import unittest
import jsonschema
import webob import webob
import glance.api.v2.images import glance.api.v2.images
from glance.common import exception
from glance.common import utils from glance.common import utils
import glance.tests.unit.utils as test_utils import glance.tests.unit.utils as test_utils
class TestImagesController(unittest.TestCase): class TestImagesController(unittest.TestCase):
def setUp(self): def setUp(self):
super(TestImagesController, self).setUp() super(TestImagesController, self).setUp()
self.db = test_utils.FakeDB() self.db = test_utils.FakeDB()
self.controller = glance.api.v2.images.ImagesController({}, self.db) self.controller = glance.api.v2.images.ImagesController({}, self.db)
def test_index(self): def test_index(self):
req = test_utils.FakeRequest() request = test_utils.FakeRequest()
output = self.controller.index(req) 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 = { expected = {
'images': [ 'images': [
{ {
'id': test_utils.UUID1, 'id': test_utils.UUID1,
'name': 'image-name', 'name': 'image-1',
'links': [ 'links': [
{ {
'rel': 'self', 'rel': 'self',
'href': '/v2/images/%s' % test_utils.UUID1, 'href': '/v2/images/%s' % test_utils.UUID1,
}, },
{
'rel': 'access',
'href': '/v2/images/%s/access' % test_utils.UUID1,
},
{'rel': 'describedby', 'href': '/v2/schemas/image'} {'rel': 'describedby', 'href': '/v2/schemas/image'}
], ],
}, },
{ {
'id': test_utils.UUID2, 'id': test_utils.UUID2,
'name': 'image-name', 'name': 'image-2',
'links': [ 'links': [
{ {
'rel': 'self', 'rel': 'self',
'href': '/v2/images/%s' % test_utils.UUID2, 'href': '/v2/images/%s' % test_utils.UUID2,
}, },
{
'rel': 'access',
'href': '/v2/images/%s/access' % test_utils.UUID2,
},
{'rel': 'describedby', 'href': '/v2/schemas/image'} {'rel': 'describedby', 'href': '/v2/schemas/image'}
], ],
}, },
], ],
'links': [], 'links': [],
} }
self.assertEqual(expected, output) response = webob.Response()
self.serializer.index(response, fixtures)
def test_index_zero_images(self): self.assertEqual(expected, json.loads(response.body))
self.db.reset()
req = test_utils.FakeRequest()
output = self.controller.index(req)
self.assertEqual({'images': [], 'links': []}, output)
def test_show(self): def test_show(self):
req = test_utils.FakeRequest() fixture = {'id': test_utils.UUID2, 'name': 'image-2'}
output = self.controller.show(req, id=test_utils.UUID2)
expected = { expected = {
'id': test_utils.UUID2, 'image': {
'name': 'image-name', 'id': test_utils.UUID2,
'links': [ 'name': 'image-2',
{'rel': 'self', 'href': '/v2/images/%s' % test_utils.UUID2}, 'links': [
{ {
'rel': 'access', 'rel': 'self',
'href': '/v2/images/%s/access' % test_utils.UUID2, 'href': '/v2/images/%s' % test_utils.UUID2,
}, },
{'rel': 'describedby', 'href': '/v2/schemas/image'} {'rel': 'describedby', 'href': '/v2/schemas/image'}
], ],
},
} }
self.assertEqual(expected, output) response = webob.Response()
self.serializer.show(response, fixture)
def test_show_non_existant(self): self.assertEqual(expected, json.loads(response.body))
self.assertRaises(webob.exc.HTTPNotFound, self.controller.show,
test_utils.FakeRequest(), id=utils.generate_uuid())

@ -37,48 +37,9 @@ class TestSchemasController(unittest.TestCase):
def test_image(self): def test_image(self):
req = test_utils.FakeRequest() req = test_utils.FakeRequest()
output = self.controller.image(req) output = self.controller.image(req)
expected = { self.assertEqual(glance.api.v2.schemas.IMAGE_SCHEMA, output)
'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)
def test_access(self): def test_access(self):
req = test_utils.FakeRequest() req = test_utils.FakeRequest()
output = self.controller.access(req) output = self.controller.access(req)
expected = { self.assertEqual(glance.api.v2.schemas.ACCESS_SCHEMA, output)
'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)

@ -39,3 +39,4 @@ lxml
Paste Paste
passlib passlib
jsonschema