Filter out None values from v2 API image entity

Prevent core image attributes with None values from being returned
to v2 API clients. Most attribute schemas are of type 'string' and
returning None is invalid.

Fixes bug 1034790

Change-Id: I2885cabf14307e46ff711bdabfcd3ff3253e6628
This commit is contained in:
Brian Waldon 2012-08-10 14:59:56 -07:00
parent bbf3eeaa83
commit 92b17a0d10
4 changed files with 86 additions and 60 deletions

View File

@ -261,24 +261,30 @@ class ResponseSerializer(wsgi.JSONResponseSerializer):
] ]
def _format_image(self, image): def _format_image(self, image):
_image = image['properties'] #NOTE(bcwaldon): merge the contained properties dict with the
for key in ['id', 'name', 'created_at', 'updated_at', 'tags', 'size', # top-level image object
'checksum', 'status']: image_view = image['properties']
_image[key] = image[key] attributes = ['id', 'name', 'disk_format', 'container_format',
if CONF.show_image_direct_url and image['location']: 'size', 'status', 'checksum', 'tags',
_image['direct_url'] = image['location'] 'created_at', 'updated_at']
for key in attributes:
image_view[key] = image[key]
for key in ['container_format', 'disk_format']: location = image['location']
if image.get(key): if CONF.show_image_direct_url and location is not None:
_image[key] = image[key] image_view['direct_url'] = location
_image['visibility'] = 'public' if image['is_public'] else 'private' visibility = 'public' if image['is_public'] else 'private'
_image = self.schema.filter(_image) image_view['visibility'] = visibility
_image['self'] = self._get_image_href(image)
_image['file'] = self._get_image_href(image, 'file') image_view['self'] = self._get_image_href(image)
_image['schema'] = '/v2/schemas/image' image_view['file'] = self._get_image_href(image, 'file')
self._serialize_datetimes(_image) image_view['schema'] = '/v2/schemas/image'
return _image
self._serialize_datetimes(image_view)
image_view = self.schema.filter(image_view)
return image_view
@staticmethod @staticmethod
def _serialize_datetimes(image): def _serialize_datetimes(image):

View File

@ -36,10 +36,14 @@ class Schema(object):
def filter(self, obj): def filter(self, obj):
filtered = {} filtered = {}
for key, value in obj.iteritems(): for key, value in obj.iteritems():
if key in self.properties: if self._filter_func(self.properties, key) and value is not None:
filtered[key] = value filtered[key] = value
return filtered return filtered
@staticmethod
def _filter_func(properties, key):
return key in properties
def merge_properties(self, properties): def merge_properties(self, properties):
# Ensure custom props aren't attempting to override base props # Ensure custom props aren't attempting to override base props
original_keys = set(self.properties.keys()) original_keys = set(self.properties.keys())
@ -67,8 +71,9 @@ class Schema(object):
class PermissiveSchema(Schema): class PermissiveSchema(Schema):
def filter(self, obj): @staticmethod
return obj def _filter_func(properties, key):
return True
def raw(self): def raw(self):
raw = super(PermissiveSchema, self).raw() raw = super(PermissiveSchema, self).raw()

View File

@ -85,8 +85,8 @@ class TestImages(functional.FunctionalTest):
self.assertEqual(200, response.status_code) self.assertEqual(200, response.status_code)
image = json.loads(response.text) image = json.loads(response.text)
self.assertEqual(image_id, image['id']) self.assertEqual(image_id, image['id'])
self.assertEqual(None, image['checksum']) self.assertFalse('checksum' in image)
self.assertEqual(None, image['size']) self.assertFalse('size' in image)
self.assertEqual('bar', image['foo']) self.assertEqual('bar', image['foo'])
self.assertTrue(image['created_at']) self.assertTrue(image['created_at'])
self.assertTrue(image['updated_at']) self.assertTrue(image['updated_at'])

View File

@ -48,7 +48,7 @@ TENANT4 = 'c6c87f25-8a94-47ed-8c83-053c25f42df4'
def _fixture(id, **kwargs): def _fixture(id, **kwargs):
obj = { obj = {
'id': id, 'id': id,
'name': 'image-1', 'name': None,
'is_public': False, 'is_public': False,
'properties': {}, 'properties': {},
'checksum': None, 'checksum': None,
@ -57,11 +57,10 @@ def _fixture(id, **kwargs):
'tags': [], 'tags': [],
'size': None, 'size': None,
'location': None, 'location': None,
'min_ram': None,
'min_disk': None,
'protected': False, 'protected': False,
'disk_format': None, 'disk_format': None,
'container_format': None, 'container_format': None,
'deleted': False,
} }
obj.update(kwargs) obj.update(kwargs)
return obj return obj
@ -635,12 +634,15 @@ class TestImagesSerializer(test_utils.BaseTestCase):
super(TestImagesSerializer, self).setUp() super(TestImagesSerializer, self).setUp()
self.serializer = glance.api.v2.images.ResponseSerializer() self.serializer = glance.api.v2.images.ResponseSerializer()
self.fixtures = [ self.fixtures = [
#NOTE(bcwaldon): This first fixture has every property defined
_fixture(UUID1, name='image-1', size=1024, tags=['one', 'two'], _fixture(UUID1, name='image-1', size=1024, tags=['one', 'two'],
created_at=DATETIME, updated_at=DATETIME, owner=TENANT1, created_at=DATETIME, updated_at=DATETIME, owner=TENANT1,
is_public=True, container_format='ami', disk_format='ami'), is_public=True, container_format='ami', disk_format='ami',
_fixture(UUID2, name='image-2',
created_at=DATETIME, updated_at=DATETIME,
checksum='ca425b88f047ce8ec45ee90e813ada91'), checksum='ca425b88f047ce8ec45ee90e813ada91'),
#NOTE(bcwaldon): This second fixture depends on default behavior
# and sets most values to None
_fixture(UUID2, created_at=DATETIME, updated_at=DATETIME),
] ]
def test_index(self): def test_index(self):
@ -651,30 +653,27 @@ class TestImagesSerializer(test_utils.BaseTestCase):
'name': 'image-1', 'name': 'image-1',
'status': 'queued', 'status': 'queued',
'visibility': 'public', 'visibility': 'public',
'created_at': ISOTIME,
'updated_at': ISOTIME,
'tags': ['one', 'two'], 'tags': ['one', 'two'],
'size': 1024, 'size': 1024,
'checksum': 'ca425b88f047ce8ec45ee90e813ada91',
'container_format': 'ami', 'container_format': 'ami',
'disk_format': 'ami', 'disk_format': 'ami',
'created_at': ISOTIME,
'updated_at': ISOTIME,
'self': '/v2/images/%s' % UUID1, 'self': '/v2/images/%s' % UUID1,
'file': '/v2/images/%s/file' % UUID1, 'file': '/v2/images/%s/file' % UUID1,
'schema': '/v2/schemas/image', 'schema': '/v2/schemas/image',
'checksum': None,
}, },
{ {
'id': UUID2, 'id': UUID2,
'name': 'image-2',
'status': 'queued', 'status': 'queued',
'visibility': 'private', 'visibility': 'private',
'checksum': 'ca425b88f047ce8ec45ee90e813ada91', 'tags': [],
'created_at': ISOTIME, 'created_at': ISOTIME,
'updated_at': ISOTIME, 'updated_at': ISOTIME,
'tags': [],
'self': '/v2/images/%s' % UUID2, 'self': '/v2/images/%s' % UUID2,
'file': '/v2/images/%s/file' % UUID2, 'file': '/v2/images/%s/file' % UUID2,
'schema': '/v2/schemas/image', 'schema': '/v2/schemas/image',
'size': None,
}, },
], ],
'first': '/v2/images', 'first': '/v2/images',
@ -707,44 +706,60 @@ class TestImagesSerializer(test_utils.BaseTestCase):
expect_next = '/v2/images?sort_key=id&sort_dir=asc&limit=10&marker=%s' expect_next = '/v2/images?sort_key=id&sort_dir=asc&limit=10&marker=%s'
self.assertEqual(expect_next % UUID2, output['next']) self.assertEqual(expect_next % UUID2, output['next'])
def test_show(self): def test_show_full_fixture(self):
expected = { expected = {
'id': UUID1, 'id': UUID1,
'name': 'image-1', 'name': 'image-1',
'status': 'queued', 'status': 'queued',
'visibility': 'public', 'visibility': 'public',
'checksum': None,
'created_at': ISOTIME,
'updated_at': ISOTIME,
'tags': ['one', 'two'], 'tags': ['one', 'two'],
'size': 1024, 'size': 1024,
'checksum': 'ca425b88f047ce8ec45ee90e813ada91',
'container_format': 'ami',
'disk_format': 'ami',
'created_at': ISOTIME,
'updated_at': ISOTIME,
'self': '/v2/images/%s' % UUID1, 'self': '/v2/images/%s' % UUID1,
'file': '/v2/images/%s/file' % UUID1, 'file': '/v2/images/%s/file' % UUID1,
'schema': '/v2/schemas/image', 'schema': '/v2/schemas/image',
'container_format': 'ami',
'disk_format': 'ami',
} }
response = webob.Response() response = webob.Response()
self.serializer.show(response, self.fixtures[0]) self.serializer.show(response, self.fixtures[0])
self.assertEqual(expected, json.loads(response.body)) self.assertEqual(expected, json.loads(response.body))
self.assertEqual('application/json', response.content_type) self.assertEqual('application/json', response.content_type)
def test_show_minimal_fixture(self):
expected = {
'id': UUID2,
'status': 'queued',
'visibility': 'private',
'tags': [],
'created_at': ISOTIME,
'updated_at': ISOTIME,
'self': '/v2/images/%s' % UUID2,
'file': '/v2/images/%s/file' % UUID2,
'schema': '/v2/schemas/image',
}
response = webob.Response()
self.serializer.show(response, self.fixtures[1])
self.assertEqual(expected, json.loads(response.body))
def test_create(self): def test_create(self):
expected = { expected = {
'id': UUID1, 'id': UUID1,
'name': 'image-1', 'name': 'image-1',
'status': 'queued', 'status': 'queued',
'visibility': 'public', 'visibility': 'public',
'checksum': None,
'created_at': ISOTIME,
'updated_at': ISOTIME,
'tags': ['one', 'two'], 'tags': ['one', 'two'],
'size': 1024, 'size': 1024,
'checksum': 'ca425b88f047ce8ec45ee90e813ada91',
'container_format': 'ami',
'disk_format': 'ami',
'created_at': ISOTIME,
'updated_at': ISOTIME,
'self': '/v2/images/%s' % UUID1, 'self': '/v2/images/%s' % UUID1,
'file': '/v2/images/%s/file' % UUID1, 'file': '/v2/images/%s/file' % UUID1,
'schema': '/v2/schemas/image', 'schema': '/v2/schemas/image',
'container_format': 'ami',
'disk_format': 'ami',
} }
response = webob.Response() response = webob.Response()
self.serializer.create(response, self.fixtures[0]) self.serializer.create(response, self.fixtures[0])
@ -758,16 +773,16 @@ class TestImagesSerializer(test_utils.BaseTestCase):
'name': 'image-1', 'name': 'image-1',
'status': 'queued', 'status': 'queued',
'visibility': 'public', 'visibility': 'public',
'checksum': None,
'created_at': ISOTIME,
'updated_at': ISOTIME,
'tags': ['one', 'two'], 'tags': ['one', 'two'],
'size': 1024, 'size': 1024,
'checksum': 'ca425b88f047ce8ec45ee90e813ada91',
'container_format': 'ami',
'disk_format': 'ami',
'created_at': ISOTIME,
'updated_at': ISOTIME,
'self': '/v2/images/%s' % UUID1, 'self': '/v2/images/%s' % UUID1,
'file': '/v2/images/%s/file' % UUID1, 'file': '/v2/images/%s/file' % UUID1,
'schema': '/v2/schemas/image', 'schema': '/v2/schemas/image',
'container_format': 'ami',
'disk_format': 'ami',
} }
response = webob.Response() response = webob.Response()
self.serializer.update(response, self.fixtures[0]) self.serializer.update(response, self.fixtures[0])
@ -802,11 +817,11 @@ class TestImagesSerializerWithExtendedSchema(test_utils.BaseTestCase):
'status': 'queued', 'status': 'queued',
'visibility': 'private', 'visibility': 'private',
'checksum': 'ca425b88f047ce8ec45ee90e813ada91', 'checksum': 'ca425b88f047ce8ec45ee90e813ada91',
'created_at': ISOTIME,
'updated_at': ISOTIME,
'tags': [], 'tags': [],
'size': 1024, 'size': 1024,
'color': 'green', 'color': 'green',
'created_at': ISOTIME,
'updated_at': ISOTIME,
'self': '/v2/images/%s' % UUID2, 'self': '/v2/images/%s' % UUID2,
'file': '/v2/images/%s/file' % UUID2, 'file': '/v2/images/%s/file' % UUID2,
'schema': '/v2/schemas/image', 'schema': '/v2/schemas/image',
@ -823,11 +838,11 @@ class TestImagesSerializerWithExtendedSchema(test_utils.BaseTestCase):
'status': 'queued', 'status': 'queued',
'visibility': 'private', 'visibility': 'private',
'checksum': 'ca425b88f047ce8ec45ee90e813ada91', 'checksum': 'ca425b88f047ce8ec45ee90e813ada91',
'created_at': ISOTIME,
'updated_at': ISOTIME,
'tags': [], 'tags': [],
'size': 1024, 'size': 1024,
'color': 'invalid', 'color': 'invalid',
'created_at': ISOTIME,
'updated_at': ISOTIME,
'self': '/v2/images/%s' % UUID2, 'self': '/v2/images/%s' % UUID2,
'file': '/v2/images/%s/file' % UUID2, 'file': '/v2/images/%s/file' % UUID2,
'schema': '/v2/schemas/image', 'schema': '/v2/schemas/image',
@ -855,11 +870,11 @@ class TestImagesSerializerWithAdditionalProperties(test_utils.BaseTestCase):
'status': 'queued', 'status': 'queued',
'visibility': 'private', 'visibility': 'private',
'checksum': 'ca425b88f047ce8ec45ee90e813ada91', 'checksum': 'ca425b88f047ce8ec45ee90e813ada91',
'created_at': ISOTIME,
'updated_at': ISOTIME,
'marx': 'groucho', 'marx': 'groucho',
'tags': [], 'tags': [],
'size': 1024, 'size': 1024,
'created_at': ISOTIME,
'updated_at': ISOTIME,
'self': '/v2/images/%s' % UUID2, 'self': '/v2/images/%s' % UUID2,
'file': '/v2/images/%s/file' % UUID2, 'file': '/v2/images/%s/file' % UUID2,
'schema': '/v2/schemas/image', 'schema': '/v2/schemas/image',
@ -880,11 +895,11 @@ class TestImagesSerializerWithAdditionalProperties(test_utils.BaseTestCase):
'status': 'queued', 'status': 'queued',
'visibility': 'private', 'visibility': 'private',
'checksum': 'ca425b88f047ce8ec45ee90e813ada91', 'checksum': 'ca425b88f047ce8ec45ee90e813ada91',
'created_at': ISOTIME,
'updated_at': ISOTIME,
'marx': 123, 'marx': 123,
'tags': [], 'tags': [],
'size': 1024, 'size': 1024,
'created_at': ISOTIME,
'updated_at': ISOTIME,
'self': '/v2/images/%s' % UUID2, 'self': '/v2/images/%s' % UUID2,
'file': '/v2/images/%s/file' % UUID2, 'file': '/v2/images/%s/file' % UUID2,
'schema': '/v2/schemas/image', 'schema': '/v2/schemas/image',
@ -902,10 +917,10 @@ class TestImagesSerializerWithAdditionalProperties(test_utils.BaseTestCase):
'status': 'queued', 'status': 'queued',
'visibility': 'private', 'visibility': 'private',
'checksum': 'ca425b88f047ce8ec45ee90e813ada91', 'checksum': 'ca425b88f047ce8ec45ee90e813ada91',
'created_at': ISOTIME,
'updated_at': ISOTIME,
'tags': [], 'tags': [],
'size': 1024, 'size': 1024,
'created_at': ISOTIME,
'updated_at': ISOTIME,
'self': '/v2/images/%s' % UUID2, 'self': '/v2/images/%s' % UUID2,
'file': '/v2/images/%s/file' % UUID2, 'file': '/v2/images/%s/file' % UUID2,
'schema': '/v2/schemas/image', 'schema': '/v2/schemas/image',