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:
parent
bbf3eeaa83
commit
92b17a0d10
@ -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):
|
||||||
|
@ -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()
|
||||||
|
@ -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'])
|
||||||
|
@ -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',
|
||||||
|
Loading…
Reference in New Issue
Block a user