Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge "API v2 controller/serialization separation"

  • Loading branch information...
commit 05a6124ea2fb48494d5c0b50a4917ad42b470052 2 parents c34bf10 + 5182c1e
Jenkins authored openstack-gerrit committed
View
84 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 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
- def _get_access_record_links(self, image_member):
+
+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)
View
75 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 @@ def _format_image(self, image):
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):
View
83 glance/api/v2/schemas.py
@@ -17,6 +17,48 @@
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 @@ def index(self, req):
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):
View
4 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")
View
42 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 @@
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 @@ def __init__(self):
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 @@ def _image_member_format(self, image_id, tenant_id, can_share):
}
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
View
198 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,34 +36,166 @@ def setUp(self):
def test_index(self):
req = test_utils.FakeRequest()
output = self.controller.index(req, 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 = []
+ self.assertEqual(expected, output)
+
+ def test_index_nonexistant_image(self):
+ req = test_utils.FakeRequest()
+ image_id = utils.generate_uuid()
+ 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': True,
+ 'can_share': False,
'links': [
{
'rel': 'self',
'href': ('/v2/images/%s/access/%s' %
- (test_utils.UUID1, test_utils.TENANT1)),
+ (test_utils.UUID1, test_utils.TENANT1))
},
{
'rel': 'describedby',
'href': '/v2/schemas/image/access',
},
-
],
},
{
- 'image_id': test_utils.UUID1,
+ 'image_id': test_utils.UUID2,
'tenant_id': test_utils.TENANT2,
- 'can_share': False,
+ 'can_share': True,
'links': [
{
'rel': 'self',
'href': ('/v2/images/%s/access/%s' %
- (test_utils.UUID1, test_utils.TENANT2)),
+ (test_utils.UUID2, test_utils.TENANT2))
},
{
'rel': 'describedby',
@@ -69,31 +204,32 @@ def test_index(self):
],
},
],
- 'links': [
- {
- 'rel': 'self',
- 'href': '/v2/images/%s/access' % test_utils.UUID1,
- },
- ],
+ 'links': [],
}
- self.assertEqual(expected, output)
+ response = webob.Response()
+ self.serializer.index(response, fixtures)
+ self.assertEqual(expected, json.loads(response.body))
- def test_index_zero_records(self):
- req = test_utils.FakeRequest()
- output = self.controller.index(req, test_utils.UUID2)
+ 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_records': [],
- 'links': [
- {
- 'rel': 'self',
- 'href': '/v2/images/%s/access' % test_utils.UUID2,
- },
- ],
+ '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'},
+ ],
+ },
}
- 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.controller.index, req, image_id)
+ response = webob.Response()
+ self.serializer.create(response, fixture)
+ self.assertEqual(expected, json.loads(response.body))
+ self.assertEqual(self_href, response.location)
View
119 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))
View
43 glance/tests/unit/v2/test_schemas_resource.py
@@ -37,48 +37,9 @@ def test_index(self):
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)
View
1  tools/pip-requires
@@ -39,3 +39,4 @@ lxml
Paste
passlib
+jsonschema
Please sign in to comment.
Something went wrong with that request. Please try again.