Move the particulars of v2 schemas under v2

Implements blueprint api-v2-refactor-schemas

Change-Id: I6ddb0aa9a8fdac2d9a7210e0d0b0fb91e8220396
This commit is contained in:
Mark J. Washenberger 2012-06-14 13:18:18 -04:00 committed by Mark Washenberger
parent 5a45f02152
commit 5599c951e0
11 changed files with 259 additions and 477 deletions

View File

@ -21,6 +21,7 @@ from glance.common import exception
from glance.common import utils
from glance.common import wsgi
import glance.db
import glance.schema
class Controller(object):
@ -76,14 +77,14 @@ class Controller(object):
class RequestDeserializer(wsgi.JSONRequestDeserializer):
def __init__(self, schema_api):
def __init__(self):
super(RequestDeserializer, self).__init__()
self.schema_api = schema_api
self.schema = get_schema()
def create(self, request):
output = super(RequestDeserializer, self).default(request)
body = output.pop('body')
self.schema_api.validate('access', body)
self.schema.validate(body)
body['member'] = body.pop('tenant_id')
output['access_record'] = body
return output
@ -135,9 +136,24 @@ class ResponseSerializer(wsgi.JSONResponseSerializer):
response.status_int = 204
def create_resource(schema_api):
def get_schema():
properties = {
'tenant_id': {
'type': 'string',
'description': 'The tenant identifier',
},
'can_share': {
'type': 'boolean',
'description': 'Ability of tenant to share with others',
'default': False,
},
}
return glance.schema.Schema('access', properties)
def create_resource():
"""Image access resource factory method"""
deserializer = RequestDeserializer(schema_api)
deserializer = RequestDeserializer()
serializer = ResponseSerializer()
controller = Controller()
return wsgi.Resource(controller, deserializer, serializer)

View File

@ -15,6 +15,7 @@
import datetime
import json
import logging
import webob.exc
@ -24,8 +25,11 @@ from glance.common import wsgi
import glance.db
from glance.openstack.common import cfg
from glance.openstack.common import timeutils
import glance.schema
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
@ -136,14 +140,14 @@ class ImagesController(object):
class RequestDeserializer(wsgi.JSONRequestDeserializer):
def __init__(self, schema_api):
def __init__(self, schema=None):
super(RequestDeserializer, self).__init__()
self.schema_api = schema_api
self.schema = schema or get_schema()
def _parse_image(self, request):
output = super(RequestDeserializer, self).default(request)
body = output.pop('body')
self.schema_api.validate('image', body)
self.schema.validate(body)
# Create a dict of base image properties, with user- and deployer-
# defined properties contained in a 'properties' dictionary
@ -212,9 +216,9 @@ class RequestDeserializer(wsgi.JSONRequestDeserializer):
class ResponseSerializer(wsgi.JSONResponseSerializer):
def __init__(self, schema_api):
def __init__(self, schema=None):
super(ResponseSerializer, self).__init__()
self.schema_api = schema_api
self.schema = schema or get_schema()
def _get_image_href(self, image, subcollection=''):
base_href = '/v2/images/%s' % image['id']
@ -229,19 +233,12 @@ class ResponseSerializer(wsgi.JSONResponseSerializer):
{'rel': 'describedby', 'href': '/v2/schemas/image'},
]
def _filter_allowed_image_attributes(self, image):
schema = self.schema_api.get_schema('image')
if schema.get('additionalProperties', True):
return dict(image.iteritems())
attrs = schema['properties'].keys()
return dict((k, v) for (k, v) in image.iteritems() if k in attrs)
def _format_image(self, image):
_image = image['properties']
_image = self._filter_allowed_image_attributes(_image)
for key in ['id', 'name', 'created_at', 'updated_at', 'tags']:
_image[key] = image[key]
_image['visibility'] = 'public' if image['is_public'] else 'private'
_image = self.schema.filter(_image)
_image['links'] = self._get_image_links(image)
self._serialize_datetimes(_image)
return _image
@ -273,9 +270,73 @@ class ResponseSerializer(wsgi.JSONResponseSerializer):
response.status_int = 204
def create_resource(schema_api):
_BASE_PROPERTIES = {
'id': {
'type': 'string',
'description': 'An identifier for the image',
'maxLength': 36,
},
'name': {
'type': 'string',
'description': 'Descriptive name for the image',
'maxLength': 255,
},
'visibility': {
'type': 'string',
'description': 'Scope of image accessibility',
'enum': ['public', 'private'],
},
'created_at': {
'type': 'string',
'description': 'Date and time of image registration',
#TODO(bcwaldon): our jsonschema library doesn't seem to like the
# format attribute, figure out why!
#'format': 'date-time',
},
'updated_at': {
'type': 'string',
'description': 'Date and time of the last image modification',
#'format': 'date-time',
},
'tags': {
'type': 'array',
'description': 'List of strings related to the image',
'items': {
'type': 'string',
'maxLength': 255,
},
},
}
def get_schema(custom_properties=None):
properties = dict(_BASE_PROPERTIES)
if CONF.allow_additional_image_properties:
schema = glance.schema.PermissiveSchema('image', properties)
else:
schema = glance.schema.Schema('image', properties)
schema.merge_properties(custom_properties or {})
return schema
def load_custom_properties():
"""Find the schema properties files and load them into a dict."""
match = CONF.find_file('schema-image.json')
if match:
schema_file = open(match)
schema_data = schema_file.read()
return json.loads(schema_data)
else:
msg = _('Could not find schema properties file %s. Continuing '
'without custom properties')
LOG.warn(msg % filename)
return {}
def create_resource(custom_properties=None):
"""Images resource factory method"""
deserializer = RequestDeserializer(schema_api)
serializer = ResponseSerializer(schema_api)
schema = get_schema(custom_properties)
deserializer = RequestDeserializer(schema)
serializer = ResponseSerializer(schema)
controller = ImagesController()
return wsgi.Resource(controller, deserializer, serializer)

View File

@ -24,7 +24,6 @@ from glance.api.v2 import images
from glance.api.v2 import root
from glance.api.v2 import schemas
from glance.common import wsgi
import glance.schema
logger = logging.getLogger(__name__)
@ -34,13 +33,12 @@ class API(wsgi.Router):
"""WSGI router for Glance v2 API requests."""
def __init__(self, mapper):
schema_api = glance.schema.API()
glance.schema.load_custom_schema_properties(schema_api)
custom_image_properties = images.load_custom_properties()
root_resource = root.create_resource()
mapper.connect('/', controller=root_resource, action='index')
schemas_resource = schemas.create_resource(schema_api)
schemas_resource = schemas.create_resource(custom_image_properties)
mapper.connect('/schemas',
controller=schemas_resource,
action='index',
@ -54,7 +52,7 @@ class API(wsgi.Router):
action='access',
conditions={'method': ['GET']})
images_resource = images.create_resource(schema_api)
images_resource = images.create_resource(custom_image_properties)
mapper.connect('/images',
controller=images_resource,
action='index',
@ -96,7 +94,7 @@ class API(wsgi.Router):
action='delete',
conditions={'method': ['DELETE']})
image_access_resource = image_access.create_resource(schema_api)
image_access_resource = image_access.create_resource()
mapper.connect('/images/{image_id}/access',
controller=image_access_resource,
action='index',

View File

@ -13,13 +13,15 @@
# License for the specific language governing permissions and limitations
# under the License.
from glance.api.v2 import images
from glance.api.v2 import image_access
from glance.common import wsgi
import glance.schema
class Controller(object):
def __init__(self, schema_api):
self.schema_api = schema_api
def __init__(self, custom_image_properties=None):
self.access_schema = image_access.get_schema()
self.image_schema = images.get_schema(custom_image_properties)
def index(self, req):
links = [
@ -29,12 +31,12 @@ class Controller(object):
return {'links': links}
def image(self, req):
return self.schema_api.get_schema('image')
return self.image_schema.raw()
def access(self, req):
return self.schema_api.get_schema('access')
return self.access_schema.raw()
def create_resource(schema_api):
controller = Controller(schema_api)
def create_resource(custom_image_properties=None):
controller = Controller(custom_image_properties)
return wsgi.Resource(controller)

View File

@ -13,131 +13,60 @@
# License for the specific language governing permissions and limitations
# under the License.
import copy
import json
import logging
import jsonschema
from glance.common import exception
from glance.openstack.common import cfg
logger = logging.getLogger(__name__)
CONF = cfg.CONF
_BASE_SCHEMA_PROPERTIES = {
'image': {
'id': {
'type': 'string',
'description': 'An identifier for the image',
'maxLength': 36,
},
'name': {
'type': 'string',
'description': 'Descriptive name for the image',
'maxLength': 255,
},
'visibility': {
'type': 'string',
'description': 'Scope of image accessibility',
'enum': ['public', 'private'],
},
'created_at': {
'type': 'string',
'description': 'Date and time of image registration',
#TODO(bcwaldon): our jsonschema library doesn't seem to like the
# format attribute, figure out why!
#'format': 'date-time',
},
'updated_at': {
'type': 'string',
'description': 'Date and time of the last image modification',
#'format': 'date-time',
},
'tags': {
'type': 'array',
'description': 'List of strings related to the image',
'items': {
'type': 'string',
'maxLength': 255,
},
},
},
'access': {
'tenant_id': {
'type': 'string',
'description': 'The tenant identifier',
},
'can_share': {
'type': 'boolean',
'description': 'Ability of tenant to share with others',
'default': False,
},
},
}
class API(object):
def __init__(self, base_properties=_BASE_SCHEMA_PROPERTIES):
self.base_properties = base_properties
self.schema_properties = copy.deepcopy(self.base_properties)
class Schema(object):
def get_schema(self, name):
if name == 'image' and CONF.allow_additional_image_properties:
additional = {'type': 'string'}
else:
additional = False
return {
'name': name,
'properties': self.schema_properties[name],
'additionalProperties': additional
}
def __init__(self, name, properties=None):
self.name = name
if properties is None:
properties = {}
self.properties = properties
def set_custom_schema_properties(self, schema_name, custom_properties):
"""Update the custom properties of a schema with those provided."""
schema_properties = copy.deepcopy(self.base_properties[schema_name])
def validate(self, obj):
try:
jsonschema.validate(obj, self.raw())
except jsonschema.ValidationError as e:
raise exception.InvalidObject(schema=self.name, reason=str(e))
def filter(self, obj):
filtered = {}
for key, value in obj.iteritems():
if key in self.properties:
filtered[key] = value
return filtered
def merge_properties(self, properties):
# Ensure custom props aren't attempting to override base props
base_keys = set(schema_properties.keys())
custom_keys = set(custom_properties.keys())
intersecting_keys = base_keys.intersection(custom_keys)
original_keys = set(self.properties.keys())
new_keys = set(properties.keys())
intersecting_keys = original_keys.intersection(new_keys)
conflicting_keys = [k for k in intersecting_keys
if schema_properties[k] != custom_properties[k]]
if self.properties[k] != properties[k]]
if len(conflicting_keys) > 0:
props = ', '.join(conflicting_keys)
reason = _("custom properties (%(props)s) conflict "
"with base properties")
raise exception.SchemaLoadError(reason=reason % {'props': props})
schema_properties.update(copy.deepcopy(custom_properties))
self.schema_properties[schema_name] = schema_properties
self.properties.update(properties)
def validate(self, schema_name, obj):
schema = self.get_schema(schema_name)
try:
jsonschema.validate(obj, schema)
except jsonschema.ValidationError as e:
raise exception.InvalidObject(schema=schema_name, reason=str(e))
def raw(self):
return {
'name': self.name,
'properties': self.properties,
'additionalProperties': False,
}
def read_schema_properties_file(schema_name):
"""Find the schema properties files and load them into a dict."""
schema_filename = 'schema-%s.json' % schema_name
match = CONF.find_file(schema_filename)
if match:
schema_file = open(match)
schema_data = schema_file.read()
return json.loads(schema_data)
else:
msg = _('Could not find schema properties file %s. Continuing '
'without custom properties')
logger.warn(msg % schema_filename)
return {}
class PermissiveSchema(Schema):
def filter(self, obj):
return obj
def load_custom_schema_properties(api):
"""Extend base image and access schemas with custom properties."""
for schema_name in ('image', 'access'):
image_properties = read_schema_properties_file(schema_name)
api.set_custom_schema_properties(schema_name, image_properties)
def raw(self):
raw = super(PermissiveSchema, self).raw()
raw['additionalProperties'] = {'type': 'string'}
return raw

View File

@ -1,43 +0,0 @@
# Copyright 2012 OpenStack LLC.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the 'License'); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import glance.schema
from glance.tests import utils
class TestSchemaAPI(utils.BaseTestCase):
def setUp(self):
super(TestSchemaAPI, self).setUp()
self.schema_api = glance.schema.API()
def test_load_image_schema(self):
output = self.schema_api.get_schema('image')
self.assertEqual('image', output['name'])
expected_keys = set([
'id',
'name',
'visibility',
'created_at',
'updated_at',
'tags',
])
self.assertEqual(expected_keys, set(output['properties'].keys()))
def test_load_access_schema(self):
output = self.schema_api.get_schema('access')
self.assertEqual('access', output['name'])
expected_keys = ['tenant_id', 'can_share']
self.assertEqual(expected_keys, output['properties'].keys())

View File

@ -18,226 +18,94 @@ import glance.schema
from glance.tests import utils as test_utils
FAKE_BASE_PROPERTIES = {
'fake1': {
'id': {
'type': 'string',
'description': 'An identifier for the image',
'required': False,
'maxLength': 36,
},
'name': {
'type': 'string',
'description': 'Descriptive name for the image',
'required': True,
},
},
'image': {
'gazump': {
'type': 'string',
'description': 'overcharge; rip off',
'required': False,
},
'cumulus': {
'type': 'string',
'description': 'a heap; pile',
'required': True,
},
},
}
class TestSchemaAPI(test_utils.BaseTestCase):
class TestBasicSchema(test_utils.BaseTestCase):
def setUp(self):
super(TestSchemaAPI, self).setUp()
self.schema_api = glance.schema.API(FAKE_BASE_PROPERTIES)
def test_get_schema(self):
output = self.schema_api.get_schema('fake1')
expected = {
'name': 'fake1',
'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,
},
},
'additionalProperties': False,
super(TestBasicSchema, self).setUp()
properties = {
'ham': {'type': 'string'},
'eggs': {'type': 'string'},
}
self.assertEqual(output, expected)
self.schema = glance.schema.Schema('basic', properties)
def test_get_schema_after_load(self):
extra_props = {
'prop1': {
'type': 'string',
'description': 'Just some property',
'required': False,
'maxLength': 128,
},
}
def test_validate_passes(self):
obj = {'ham': 'no', 'eggs': 'scrambled'}
self.schema.validate(obj) # No exception raised
self.schema_api.set_custom_schema_properties('fake1', extra_props)
output = self.schema_api.get_schema('fake1')
def test_validate_fails_on_extra_properties(self):
obj = {'ham': 'virginia', 'eggs': 'scrambled', 'bacon': 'crispy'}
self.assertRaises(exception.InvalidObject, self.schema.validate, obj)
expected = {
'name': 'fake1',
'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,
},
'prop1': {
'type': 'string',
'description': 'Just some property',
'required': False,
'maxLength': 128,
},
},
'additionalProperties': False,
}
self.assertEqual(output, expected)
def test_validate_fails_on_bad_type(self):
obj = {'eggs': 2}
self.assertRaises(exception.InvalidObject, self.schema.validate, obj)
def test_get_schema_load_conflict(self):
extra_props = {
'name': {
'type': 'int',
'description': 'Descriptive integer for the image',
'required': False,
},
}
def test_filter_strips_extra_properties(self):
obj = {'ham': 'virginia', 'eggs': 'scrambled', 'bacon': 'crispy'}
filtered = self.schema.filter(obj)
expected = {'ham': 'virginia', 'eggs': 'scrambled'}
self.assertEqual(filtered, expected)
def test_merge_properties(self):
self.schema.merge_properties({'bacon': {'type': 'string'}})
expected = set(['ham', 'eggs', 'bacon'])
actual = set(self.schema.raw()['properties'].keys())
self.assertEqual(actual, expected)
def test_merge_conflicting_properties(self):
conflicts = {'eggs': {'type': 'integer'}}
self.assertRaises(exception.SchemaLoadError,
self.schema_api.set_custom_schema_properties,
'fake1',
extra_props)
self.schema.merge_properties, conflicts)
# Schema should not have changed due to the conflict
output = self.schema_api.get_schema('fake1')
def test_merge_conflicting_but_identical_properties(self):
conflicts = {'ham': {'type': 'string'}}
self.schema.merge_properties(conflicts) # no exception raised
expected = set(['ham', 'eggs'])
actual = set(self.schema.raw()['properties'].keys())
self.assertEqual(actual, expected)
def test_raw_json_schema(self):
expected = {
'name': 'fake1',
'name': 'basic',
'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,
},
'ham': {'type': 'string'},
'eggs': {'type': 'string'},
},
'additionalProperties': False,
}
self.assertEqual(output, expected)
self.assertEqual(self.schema.raw(), expected)
def test_get_schema_load_conflict_base_property(self):
extra_props = {
'name': {
'type': 'string',
'description': 'Descriptive name for the image',
'required': True,
},
class TestPermissiveSchema(test_utils.BaseTestCase):
def setUp(self):
super(TestPermissiveSchema, self).setUp()
properties = {
'ham': {'type': 'string'},
'eggs': {'type': 'string'},
}
self.schema = glance.schema.PermissiveSchema('permissive', properties)
# Schema update should not raise an exception, but it should also
# remain unchanged
self.schema_api.set_custom_schema_properties('fake1', extra_props)
output = self.schema_api.get_schema('fake1')
expected = {
'name': 'fake1',
'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,
},
},
'additionalProperties': False,
}
self.assertEqual(output, expected)
def test_validate_with_additional_properties_allowed(self):
obj = {'ham': 'virginia', 'eggs': 'scrambled', 'bacon': 'crispy'}
self.schema.validate(obj) # No exception raised
def test_get_image_schema_with_additional_properties_disabled(self):
self.config(allow_additional_image_properties=False)
output = self.schema_api.get_schema('image')
expected = {
'name': 'image',
'properties': {
'gazump': {
'type': 'string',
'description': 'overcharge; rip off',
'required': False,
},
'cumulus': {
'type': 'string',
'description': 'a heap; pile',
'required': True,
},
},
'additionalProperties': False,
}
self.assertEqual(output, expected)
def test_validate_rejects_non_string_extra_properties(self):
obj = {'ham': 'virginia', 'eggs': 'scrambled', 'grits': 1000}
self.assertRaises(exception.InvalidObject, self.schema.validate, obj)
def test_get_image_schema_with_additional_properties_enabled(self):
self.config(allow_additional_image_properties=True)
output = self.schema_api.get_schema('image')
def test_filter_passes_extra_properties(self):
obj = {'ham': 'virginia', 'eggs': 'scrambled', 'bacon': 'crispy'}
filtered = self.schema.filter(obj)
self.assertEqual(filtered, obj)
def test_raw_json_schema(self):
expected = {
'name': 'image',
'name': 'permissive',
'properties': {
'gazump': {
'type': 'string',
'description': 'overcharge; rip off',
'required': False,
},
'cumulus': {
'type': 'string',
'description': 'a heap; pile',
'required': True,
},
'ham': {'type': 'string'},
'eggs': {'type': 'string'},
},
'additionalProperties': {'type': 'string'},
}
self.assertEqual(output, expected)
def test_get_other_schema_with_additional_image_properties_enabled(self):
self.config(allow_additional_image_properties=False)
output = self.schema_api.get_schema('fake1')
expected = {
'name': 'fake1',
'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,
},
},
'additionalProperties': False,
}
self.assertEqual(output, expected)
self.assertEqual(self.schema.raw(), expected)

View File

@ -17,7 +17,7 @@ import json
import webob
from glance.api.v2 import image_access
import glance.api.v2.image_access
from glance.common import exception
from glance.common import utils
import glance.schema
@ -30,7 +30,7 @@ class TestImageAccessController(test_utils.BaseTestCase):
def setUp(self):
super(TestImageAccessController, self).setUp()
self.db = unit_test_utils.FakeDB()
self.controller = image_access.Controller(self.db)
self.controller = glance.api.v2.image_access.Controller(self.db)
def test_index(self):
req = unit_test_utils.get_fake_request()
@ -111,8 +111,7 @@ class TestImageAccessDeserializer(test_utils.BaseTestCase):
def setUp(self):
super(TestImageAccessDeserializer, self).setUp()
schema_api = glance.schema.API()
self.deserializer = image_access.RequestDeserializer(schema_api)
self.deserializer = glance.api.v2.image_access.RequestDeserializer()
def test_create(self):
fixture = {
@ -131,53 +130,8 @@ class TestImageAccessDeserializer(test_utils.BaseTestCase):
self.assertEqual(expected, output)
class TestImageAccessDeserializerWithExtendedSchema(test_utils.BaseTestCase):
def setUp(self):
super(TestImageAccessDeserializerWithExtendedSchema, self).setUp()
schema_api = glance.schema.API()
props = {
'color': {
'type': 'string',
'required': True,
'enum': ['blue', 'red'],
},
}
schema_api.set_custom_schema_properties('access', props)
self.deserializer = image_access.RequestDeserializer(schema_api)
def test_create(self):
fixture = {
'tenant_id': unit_test_utils.TENANT1,
'can_share': False,
'color': 'blue',
}
expected = {
'access_record': {
'member': unit_test_utils.TENANT1,
'can_share': False,
'color': 'blue',
},
}
request = unit_test_utils.get_fake_request()
request.body = json.dumps(fixture)
output = self.deserializer.create(request)
self.assertEqual(expected, output)
def test_create_bad_data(self):
fixture = {
'tenant_id': unit_test_utils.TENANT1,
'can_share': False,
'color': 'purple',
}
request = unit_test_utils.get_fake_request()
request.body = json.dumps(fixture)
self.assertRaises(exception.InvalidObject,
self.deserializer.create, request)
class TestImageAccessSerializer(test_utils.BaseTestCase):
serializer = image_access.ResponseSerializer()
serializer = glance.api.v2.image_access.ResponseSerializer()
def test_show(self):
fixture = {

View File

@ -30,8 +30,7 @@ class TestImagesController(base.StoreClearingUnitTest):
self.config(verbose=True, debug=True)
controller_class = glance.api.v2.image_data.ImageDataController
self.controller = controller_class(
self.controller = glance.api.v2.image_data.ImageDataController(
db_api=unit_test_utils.FakeDB(),
store_api=unit_test_utils.FakeStoreAPI())

View File

@ -16,6 +16,7 @@
import datetime
import json
import stubout
import webob
import glance.api.v2.images
@ -250,9 +251,7 @@ class TestImagesDeserializer(test_utils.BaseTestCase):
def setUp(self):
super(TestImagesDeserializer, self).setUp()
schema_api = glance.schema.API()
self.deserializer = glance.api.v2.images.RequestDeserializer(
schema_api)
self.deserializer = glance.api.v2.images.RequestDeserializer()
def test_create_with_id(self):
request = unit_test_utils.get_fake_request()
@ -392,17 +391,16 @@ class TestImagesDeserializerWithExtendedSchema(test_utils.BaseTestCase):
def setUp(self):
super(TestImagesDeserializerWithExtendedSchema, self).setUp()
schema_api = glance.schema.API()
props = {
self.config(allow_additional_image_properties=False)
custom_image_properties = {
'pants': {
'type': 'string',
'required': True,
'enum': ['on', 'off'],
'type': 'string',
'required': True,
'enum': ['on', 'off'],
},
}
schema_api.set_custom_schema_properties('image', props)
self.deserializer = glance.api.v2.images.RequestDeserializer(
schema_api)
schema = glance.api.v2.images.get_schema(custom_image_properties)
self.deserializer = glance.api.v2.images.RequestDeserializer(schema)
def test_create(self):
request = unit_test_utils.get_fake_request()
@ -446,9 +444,7 @@ class TestImagesDeserializerWithAdditionalProperties(test_utils.BaseTestCase):
def setUp(self):
super(TestImagesDeserializerWithAdditionalProperties, self).setUp()
self.config(allow_additional_image_properties=True)
schema_api = glance.schema.API()
self.deserializer = glance.api.v2.images.RequestDeserializer(
schema_api)
self.deserializer = glance.api.v2.images.RequestDeserializer()
def test_create(self):
request = unit_test_utils.get_fake_request()
@ -457,13 +453,6 @@ class TestImagesDeserializerWithAdditionalProperties(test_utils.BaseTestCase):
expected = {'image': {'properties': {'foo': 'bar'}}}
self.assertEqual(expected, output)
def test_create_with_additional_properties_disallowed(self):
self.config(allow_additional_image_properties=False)
request = unit_test_utils.get_fake_request()
request.body = json.dumps({'foo': 'bar'})
self.assertRaises(exception.InvalidObject,
self.deserializer.create, request)
def test_create_with_numeric_property(self):
request = unit_test_utils.get_fake_request()
request.body = json.dumps({'abc': 123})
@ -483,8 +472,22 @@ class TestImagesDeserializerWithAdditionalProperties(test_utils.BaseTestCase):
expected = {'image': {'properties': {'foo': 'bar'}}}
self.assertEqual(expected, output)
def test_update_with_additional_properties_disallowed(self):
class TestImagesDeserializerNoAdditionalProperties(test_utils.BaseTestCase):
def setUp(self):
super(TestImagesDeserializerNoAdditionalProperties, self).setUp()
self.config(allow_additional_image_properties=False)
self.deserializer = glance.api.v2.images.RequestDeserializer()
def test_create_with_additional_properties_disallowed(self):
self.config(allow_additional_image_properties=False)
request = unit_test_utils.get_fake_request()
request.body = json.dumps({'foo': 'bar'})
self.assertRaises(exception.InvalidObject,
self.deserializer.create, request)
def test_update(self):
request = unit_test_utils.get_fake_request()
request.body = json.dumps({'foo': 'bar'})
self.assertRaises(exception.InvalidObject,
@ -495,8 +498,7 @@ class TestImagesSerializer(test_utils.BaseTestCase):
def setUp(self):
super(TestImagesSerializer, self).setUp()
schema_api = glance.schema.API()
self.serializer = glance.api.v2.images.ResponseSerializer(schema_api)
self.serializer = glance.api.v2.images.ResponseSerializer()
def test_index(self):
fixtures = [
@ -672,15 +674,16 @@ class TestImagesSerializerWithExtendedSchema(test_utils.BaseTestCase):
def setUp(self):
super(TestImagesSerializerWithExtendedSchema, self).setUp()
self.config(allow_additional_image_properties=False)
self.schema_api = glance.schema.API()
props = {
custom_image_properties = {
'color': {
'type': 'string',
'required': True,
'enum': ['red', 'green'],
},
}
self.schema_api.set_custom_schema_properties('image', props)
schema = glance.api.v2.images.get_schema(custom_image_properties)
self.serializer = glance.api.v2.images.ResponseSerializer(schema)
self.fixture = {
'id': unit_test_utils.UUID2,
'name': 'image-2',
@ -692,7 +695,6 @@ class TestImagesSerializerWithExtendedSchema(test_utils.BaseTestCase):
}
def test_show(self):
serializer = glance.api.v2.images.ResponseSerializer(self.schema_api)
expected = {
'image': {
'id': unit_test_utils.UUID2,
@ -716,11 +718,10 @@ class TestImagesSerializerWithExtendedSchema(test_utils.BaseTestCase):
},
}
response = webob.Response()
serializer.show(response, self.fixture)
self.serializer.show(response, self.fixture)
self.assertEqual(expected, json.loads(response.body))
def test_show_reports_invalid_data(self):
serializer = glance.api.v2.images.ResponseSerializer(self.schema_api)
self.fixture['properties']['color'] = 'invalid'
expected = {
'image': {
@ -745,7 +746,7 @@ class TestImagesSerializerWithExtendedSchema(test_utils.BaseTestCase):
},
}
response = webob.Response()
serializer.show(response, self.fixture)
self.serializer.show(response, self.fixture)
self.assertEqual(expected, json.loads(response.body))
@ -754,7 +755,6 @@ class TestImagesSerializerWithAdditionalProperties(test_utils.BaseTestCase):
def setUp(self):
super(TestImagesSerializerWithAdditionalProperties, self).setUp()
self.config(allow_additional_image_properties=True)
self.schema_api = glance.schema.API()
self.fixture = {
'id': unit_test_utils.UUID2,
'name': 'image-2',
@ -768,7 +768,7 @@ class TestImagesSerializerWithAdditionalProperties(test_utils.BaseTestCase):
}
def test_show(self):
serializer = glance.api.v2.images.ResponseSerializer(self.schema_api)
serializer = glance.api.v2.images.ResponseSerializer()
expected = {
'image': {
'id': unit_test_utils.UUID2,
@ -799,7 +799,7 @@ class TestImagesSerializerWithAdditionalProperties(test_utils.BaseTestCase):
"""Ensure that the serializer passes through invalid additional
properties (i.e. non-string) without complaining.
"""
serializer = glance.api.v2.images.ResponseSerializer(self.schema_api)
serializer = glance.api.v2.images.ResponseSerializer()
self.fixture['properties']['marx'] = 123
expected = {
'image': {
@ -829,7 +829,7 @@ class TestImagesSerializerWithAdditionalProperties(test_utils.BaseTestCase):
def test_show_with_additional_properties_disabled(self):
self.config(allow_additional_image_properties=False)
serializer = glance.api.v2.images.ResponseSerializer(self.schema_api)
serializer = glance.api.v2.images.ResponseSerializer()
expected = {
'image': {
'id': unit_test_utils.UUID2,

View File

@ -13,8 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
from glance.api.v2 import schemas
import glance.schema
import glance.api.v2.schemas
import glance.tests.unit.utils as unit_test_utils
import glance.tests.utils as test_utils
@ -23,8 +22,7 @@ class TestSchemasController(test_utils.BaseTestCase):
def setUp(self):
super(TestSchemasController, self).setUp()
self.schema_api = glance.schema.API()
self.controller = schemas.Controller(self.schema_api)
self.controller = glance.api.v2.schemas.Controller()
def test_index(self):
req = unit_test_utils.get_fake_request()
@ -38,9 +36,9 @@ class TestSchemasController(test_utils.BaseTestCase):
def test_image(self):
req = unit_test_utils.get_fake_request()
output = self.controller.image(req)
self.assertEqual(self.schema_api.get_schema('image'), output)
self.assertEqual(output['name'], 'image')
def test_access(self):
req = unit_test_utils.get_fake_request()
output = self.controller.access(req)
self.assertEqual(self.schema_api.get_schema('access'), output)
self.assertEqual(output['name'], 'access')