Added better schemas for image members, revised tests.
Related to bp glance-api-v2-image-sharing Change-Id: I01c7fdb78177aaee97f6e7836dea3326b57be33c
This commit is contained in:
@@ -13,6 +13,7 @@
|
|||||||
# 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 copy
|
||||||
import json
|
import json
|
||||||
import webob
|
import webob
|
||||||
|
|
||||||
@@ -25,6 +26,7 @@ import glance.domain
|
|||||||
import glance.gateway
|
import glance.gateway
|
||||||
import glance.notifier
|
import glance.notifier
|
||||||
from glance.openstack.common import timeutils
|
from glance.openstack.common import timeutils
|
||||||
|
import glance.schema
|
||||||
import glance.store
|
import glance.store
|
||||||
|
|
||||||
|
|
||||||
@@ -50,6 +52,7 @@ class ImageMembersController(object):
|
|||||||
|
|
||||||
{'member_id': <MEMBER>,
|
{'member_id': <MEMBER>,
|
||||||
'image_id': <IMAGE>,
|
'image_id': <IMAGE>,
|
||||||
|
'status': <MEMBER_STATUS>
|
||||||
'created_at': ..,
|
'created_at': ..,
|
||||||
'updated_at': ..}
|
'updated_at': ..}
|
||||||
|
|
||||||
@@ -82,6 +85,7 @@ class ImageMembersController(object):
|
|||||||
|
|
||||||
{'member_id': <MEMBER>,
|
{'member_id': <MEMBER>,
|
||||||
'image_id': <IMAGE>,
|
'image_id': <IMAGE>,
|
||||||
|
'status': <MEMBER_STATUS>
|
||||||
'created_at': ..,
|
'created_at': ..,
|
||||||
'updated_at': ..}
|
'updated_at': ..}
|
||||||
|
|
||||||
@@ -115,6 +119,7 @@ class ImageMembersController(object):
|
|||||||
{'members': [
|
{'members': [
|
||||||
{'member_id': <MEMBER>,
|
{'member_id': <MEMBER>,
|
||||||
'image_id': <IMAGE>,
|
'image_id': <IMAGE>,
|
||||||
|
'status': <MEMBER_STATUS>
|
||||||
'created_at': ..,
|
'created_at': ..,
|
||||||
'updated_at': ..}, ..
|
'updated_at': ..}, ..
|
||||||
]}
|
]}
|
||||||
@@ -207,6 +212,7 @@ class RequestDeserializer(wsgi.JSONRequestDeserializer):
|
|||||||
class ResponseSerializer(wsgi.JSONResponseSerializer):
|
class ResponseSerializer(wsgi.JSONResponseSerializer):
|
||||||
def __init__(self, schema=None):
|
def __init__(self, schema=None):
|
||||||
super(ResponseSerializer, self).__init__()
|
super(ResponseSerializer, self).__init__()
|
||||||
|
self.schema = schema or get_schema()
|
||||||
|
|
||||||
def _format_image_member(self, member):
|
def _format_image_member(self, member):
|
||||||
member_view = {}
|
member_view = {}
|
||||||
@@ -215,6 +221,8 @@ class ResponseSerializer(wsgi.JSONResponseSerializer):
|
|||||||
member_view[key] = getattr(member, key)
|
member_view[key] = getattr(member, key)
|
||||||
member_view['created_at'] = timeutils.isotime(member.created_at)
|
member_view['created_at'] = timeutils.isotime(member.created_at)
|
||||||
member_view['updated_at'] = timeutils.isotime(member.updated_at)
|
member_view['updated_at'] = timeutils.isotime(member.updated_at)
|
||||||
|
member_view['schema'] = '/v2/schemas/member'
|
||||||
|
member_view = self.schema.filter(member_view)
|
||||||
return member_view
|
return member_view
|
||||||
|
|
||||||
def create(self, response, image_member):
|
def create(self, response, image_member):
|
||||||
@@ -235,11 +243,60 @@ class ResponseSerializer(wsgi.JSONResponseSerializer):
|
|||||||
for image_member in image_members:
|
for image_member in image_members:
|
||||||
image_member_view = self._format_image_member(image_member)
|
image_member_view = self._format_image_member(image_member)
|
||||||
image_members_view.append(image_member_view)
|
image_members_view.append(image_member_view)
|
||||||
body = json.dumps(dict(members=image_members_view), ensure_ascii=False)
|
totalview = dict(members=image_members_view)
|
||||||
|
totalview['schema'] = '/v2/schemas/members'
|
||||||
|
body = json.dumps(totalview, ensure_ascii=False)
|
||||||
response.unicode_body = unicode(body)
|
response.unicode_body = unicode(body)
|
||||||
response.content_type = 'application/json'
|
response.content_type = 'application/json'
|
||||||
|
|
||||||
|
|
||||||
|
_MEMBER_SCHEMA = {
|
||||||
|
'member_id': {
|
||||||
|
'type': 'string',
|
||||||
|
'description': _('An identifier for the image member (tenantId)')
|
||||||
|
},
|
||||||
|
'image_id': {
|
||||||
|
'type': 'string',
|
||||||
|
'description': _('An identifier for the image'),
|
||||||
|
'pattern': ('^([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}'
|
||||||
|
'-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}$'),
|
||||||
|
},
|
||||||
|
'created_at': {
|
||||||
|
'type': 'string',
|
||||||
|
'description': _('Date and time of image member creation'),
|
||||||
|
#TODO(brian-rosmaita): our jsonschema library doesn't seem to like the
|
||||||
|
# format attribute, figure out why (and also fix in images.py)
|
||||||
|
#'format': 'date-time',
|
||||||
|
},
|
||||||
|
'updated_at': {
|
||||||
|
'type': 'string',
|
||||||
|
'description': _('Date and time of last modification of image member'),
|
||||||
|
#'format': 'date-time',
|
||||||
|
},
|
||||||
|
'status': {
|
||||||
|
'type': 'string',
|
||||||
|
'description': _('The status of this image member'),
|
||||||
|
'enum': [
|
||||||
|
'pending',
|
||||||
|
'accepted',
|
||||||
|
'rejected'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'schema': {'type': 'string'}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_schema():
|
||||||
|
properties = copy.deepcopy(_MEMBER_SCHEMA)
|
||||||
|
schema = glance.schema.Schema('member', properties)
|
||||||
|
return schema
|
||||||
|
|
||||||
|
|
||||||
|
def get_collection_schema():
|
||||||
|
member_schema = get_schema()
|
||||||
|
return glance.schema.CollectionSchema('members', member_schema)
|
||||||
|
|
||||||
|
|
||||||
def create_resource():
|
def create_resource():
|
||||||
"""Image Members resource factory method"""
|
"""Image Members resource factory method"""
|
||||||
deserializer = RequestDeserializer()
|
deserializer = RequestDeserializer()
|
||||||
|
|||||||
@@ -39,6 +39,14 @@ class API(wsgi.Router):
|
|||||||
controller=schemas_resource,
|
controller=schemas_resource,
|
||||||
action='images',
|
action='images',
|
||||||
conditions={'method': ['GET']})
|
conditions={'method': ['GET']})
|
||||||
|
mapper.connect('/schemas/member',
|
||||||
|
controller=schemas_resource,
|
||||||
|
action='member',
|
||||||
|
conditions={'method': ['GET']})
|
||||||
|
mapper.connect('/schemas/members',
|
||||||
|
controller=schemas_resource,
|
||||||
|
action='members',
|
||||||
|
conditions={'method': ['GET']})
|
||||||
|
|
||||||
images_resource = images.create_resource(custom_image_properties)
|
images_resource = images.create_resource(custom_image_properties)
|
||||||
mapper.connect('/images',
|
mapper.connect('/images',
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from glance.api.v2 import images
|
from glance.api.v2 import images
|
||||||
|
from glance.api.v2 import image_members
|
||||||
from glance.common import wsgi
|
from glance.common import wsgi
|
||||||
|
|
||||||
|
|
||||||
@@ -22,6 +23,8 @@ class Controller(object):
|
|||||||
self.image_schema = images.get_schema(custom_image_properties)
|
self.image_schema = images.get_schema(custom_image_properties)
|
||||||
self.image_collection_schema = images.get_collection_schema(
|
self.image_collection_schema = images.get_collection_schema(
|
||||||
custom_image_properties)
|
custom_image_properties)
|
||||||
|
self.member_schema = image_members.get_schema()
|
||||||
|
self.member_collection_schema = image_members.get_collection_schema()
|
||||||
|
|
||||||
def image(self, req):
|
def image(self, req):
|
||||||
return self.image_schema.raw()
|
return self.image_schema.raw()
|
||||||
@@ -29,6 +32,12 @@ class Controller(object):
|
|||||||
def images(self, req):
|
def images(self, req):
|
||||||
return self.image_collection_schema.raw()
|
return self.image_collection_schema.raw()
|
||||||
|
|
||||||
|
def member(self, req):
|
||||||
|
return self.member_schema.minimal()
|
||||||
|
|
||||||
|
def members(self, req):
|
||||||
|
return self.member_collection_schema.minimal()
|
||||||
|
|
||||||
|
|
||||||
def create_resource(custom_image_properties=None):
|
def create_resource(custom_image_properties=None):
|
||||||
controller = Controller(custom_image_properties)
|
controller = Controller(custom_image_properties)
|
||||||
|
|||||||
@@ -69,6 +69,13 @@ class Schema(object):
|
|||||||
raw['links'] = self.links
|
raw['links'] = self.links
|
||||||
return raw
|
return raw
|
||||||
|
|
||||||
|
def minimal(self):
|
||||||
|
minimal = {
|
||||||
|
'name': self.name,
|
||||||
|
'properties': self.properties
|
||||||
|
}
|
||||||
|
return minimal
|
||||||
|
|
||||||
|
|
||||||
class PermissiveSchema(Schema):
|
class PermissiveSchema(Schema):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -80,6 +87,10 @@ class PermissiveSchema(Schema):
|
|||||||
raw['additionalProperties'] = {'type': 'string'}
|
raw['additionalProperties'] = {'type': 'string'}
|
||||||
return raw
|
return raw
|
||||||
|
|
||||||
|
def minimal(self):
|
||||||
|
minimal = super(PermissiveSchema, self).raw()
|
||||||
|
return minimal
|
||||||
|
|
||||||
|
|
||||||
class CollectionSchema(object):
|
class CollectionSchema(object):
|
||||||
|
|
||||||
@@ -105,3 +116,18 @@ class CollectionSchema(object):
|
|||||||
{'rel': 'describedby', 'href': '{schema}'},
|
{'rel': 'describedby', 'href': '{schema}'},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def minimal(self):
|
||||||
|
return {
|
||||||
|
'name': self.name,
|
||||||
|
'properties': {
|
||||||
|
self.name: {
|
||||||
|
'type': 'array',
|
||||||
|
'items': self.item_schema.minimal(),
|
||||||
|
},
|
||||||
|
'schema': {'type': 'string'},
|
||||||
|
},
|
||||||
|
'links': [
|
||||||
|
{'rel': 'describedby', 'href': '{schema}'},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|||||||
@@ -294,6 +294,7 @@ class TestImageMembersSerializer(test_utils.BaseTestCase):
|
|||||||
'status': 'accepted',
|
'status': 'accepted',
|
||||||
'created_at': ISOTIME,
|
'created_at': ISOTIME,
|
||||||
'updated_at': ISOTIME,
|
'updated_at': ISOTIME,
|
||||||
|
'schema': '/v2/schemas/member',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'image_id': UUID2,
|
'image_id': UUID2,
|
||||||
@@ -301,8 +302,10 @@ class TestImageMembersSerializer(test_utils.BaseTestCase):
|
|||||||
'status': 'pending',
|
'status': 'pending',
|
||||||
'created_at': ISOTIME,
|
'created_at': ISOTIME,
|
||||||
'updated_at': ISOTIME,
|
'updated_at': ISOTIME,
|
||||||
|
'schema': '/v2/schemas/member',
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
|
'schema': '/v2/schemas/members',
|
||||||
}
|
}
|
||||||
request = webob.Request.blank('/v2/images/%s/members' % UUID2)
|
request = webob.Request.blank('/v2/images/%s/members' % UUID2)
|
||||||
response = webob.Response(request=request)
|
response = webob.Response(request=request)
|
||||||
@@ -316,6 +319,7 @@ class TestImageMembersSerializer(test_utils.BaseTestCase):
|
|||||||
expected = {'image_id': UUID2,
|
expected = {'image_id': UUID2,
|
||||||
'member_id': TENANT1,
|
'member_id': TENANT1,
|
||||||
'status': 'accepted',
|
'status': 'accepted',
|
||||||
|
'schema': '/v2/schemas/member',
|
||||||
'created_at': ISOTIME,
|
'created_at': ISOTIME,
|
||||||
'updated_at': ISOTIME}
|
'updated_at': ISOTIME}
|
||||||
request = webob.Request.blank('/v2/images/%s/members/%s'
|
request = webob.Request.blank('/v2/images/%s/members/%s'
|
||||||
@@ -331,6 +335,7 @@ class TestImageMembersSerializer(test_utils.BaseTestCase):
|
|||||||
expected = {'image_id': UUID2,
|
expected = {'image_id': UUID2,
|
||||||
'member_id': TENANT1,
|
'member_id': TENANT1,
|
||||||
'status': 'accepted',
|
'status': 'accepted',
|
||||||
|
'schema': '/v2/schemas/member',
|
||||||
'created_at': ISOTIME,
|
'created_at': ISOTIME,
|
||||||
'updated_at': ISOTIME}
|
'updated_at': ISOTIME}
|
||||||
request = webob.Request.blank('/v2/images/%s/members/%s'
|
request = webob.Request.blank('/v2/images/%s/members/%s'
|
||||||
|
|||||||
Reference in New Issue
Block a user