glance/glance/common/glare/serialization.py

329 lines
13 KiB
Python

# Copyright (c) 2015 Mirantis, Inc.
#
# 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 collections
import six
from glance.common import exception
from glance.common.glare import declarative
from glance.common.glare import definitions
from glance import glare as ga
from glance.i18n import _
COMMON_ARTIFACT_PROPERTIES = ['id',
'type_name',
'type_version',
'name',
'version',
'description',
'visibility',
'state',
'tags',
'owner',
'created_at',
'updated_at',
'published_at',
'deleted_at']
def _serialize_list_prop(prop, values):
"""
A helper func called to correctly serialize an Array property.
Returns a dict {'type': some_supported_db_type, 'value': serialized_data}
"""
# FIXME(Due to a potential bug in declarative framework, for Arrays, that
# are values to some dict items (Dict(properties={"foo": Array()})),
# prop.get_value(artifact) returns not the real list of items, but the
# whole dict). So we can't rely on prop.get_value(artifact) and will pass
# correctly retrieved values to this function
serialized_value = []
for i, val in enumerate(values or []):
db_type = prop.get_item_definition_at_index(i).DB_TYPE
if db_type is None:
continue
serialized_value.append({
'type': db_type,
'value': val
})
return serialized_value
def _serialize_dict_prop(artifact, prop, key, value, save_prop_func):
key_to_save = prop.name + '.' + key
dict_key_prop = prop.get_prop_definition_at_key(key)
db_type = dict_key_prop.DB_TYPE
if (db_type is None and
not isinstance(dict_key_prop,
declarative.ListAttributeDefinition)):
# nothing to do here, don't know how to deal with this type
return
elif isinstance(dict_key_prop,
declarative.ListAttributeDefinition):
serialized = _serialize_list_prop(
dict_key_prop,
# FIXME(see comment for _serialize_list_prop func)
values=(dict_key_prop.get_value(artifact) or {}).get(key, []))
save_prop_func(key_to_save, 'array', serialized)
else:
save_prop_func(key_to_save, db_type, value)
def _serialize_dependencies(artifact):
"""Returns a dict of serialized dependencies for given artifact"""
dependencies = {}
for relation in artifact.metadata.attributes.dependencies.values():
serialized_dependency = []
if isinstance(relation, declarative.ListAttributeDefinition):
for dep in relation.get_value(artifact):
serialized_dependency.append(dep.id)
else:
relation_data = relation.get_value(artifact)
if relation_data:
serialized_dependency.append(relation.get_value(artifact).id)
dependencies[relation.name] = serialized_dependency
return dependencies
def _serialize_blobs(artifact):
"""Return a dict of serialized blobs for given artifact"""
blobs = {}
for blob in artifact.metadata.attributes.blobs.values():
serialized_blob = []
if isinstance(blob, declarative.ListAttributeDefinition):
for b in blob.get_value(artifact) or []:
serialized_blob.append({
'size': b.size,
'locations': b.locations,
'checksum': b.checksum,
'item_key': b.item_key
})
else:
b = blob.get_value(artifact)
# if no value for blob has been set -> continue
if not b:
continue
serialized_blob.append({
'size': b.size,
'locations': b.locations,
'checksum': b.checksum,
'item_key': b.item_key
})
blobs[blob.name] = serialized_blob
return blobs
def serialize_for_db(artifact):
result = {}
custom_properties = {}
def _save_prop(prop_key, prop_type, value):
custom_properties[prop_key] = {
'type': prop_type,
'value': value
}
for prop in artifact.metadata.attributes.properties.values():
if prop.name in COMMON_ARTIFACT_PROPERTIES:
result[prop.name] = prop.get_value(artifact)
continue
if isinstance(prop, declarative.ListAttributeDefinition):
serialized_value = _serialize_list_prop(prop,
prop.get_value(artifact))
_save_prop(prop.name, 'array', serialized_value)
elif isinstance(prop, declarative.DictAttributeDefinition):
fields_to_set = prop.get_value(artifact) or {}
# if some keys are not present (like in prop == {}), then have to
# set their values to None.
# XXX FIXME prop.properties may be a dict ({'foo': '', 'bar': ''})
# or String\Integer\whatsoever, limiting the possible dict values.
# In the latter case have no idea how to remove old values during
# serialization process.
if isinstance(prop.properties, dict):
for key in [k for k in prop.properties
if k not in fields_to_set.keys()]:
_serialize_dict_prop(artifact, prop, key, None, _save_prop)
# serialize values of properties present
for key, value in six.iteritems(fields_to_set):
_serialize_dict_prop(artifact, prop, key, value, _save_prop)
elif prop.DB_TYPE is not None:
_save_prop(prop.name, prop.DB_TYPE, prop.get_value(artifact))
result['properties'] = custom_properties
result['dependencies'] = _serialize_dependencies(artifact)
result['blobs'] = _serialize_blobs(artifact)
return result
def _deserialize_blobs(artifact_type, blobs_from_db, artifact_properties):
"""Retrieves blobs from database"""
for blob_name, blob_value in six.iteritems(blobs_from_db):
if not blob_value:
continue
if isinstance(artifact_type.metadata.attributes.blobs.get(blob_name),
declarative.ListAttributeDefinition):
val = []
for v in blob_value:
b = definitions.Blob(size=v['size'],
locations=v['locations'],
checksum=v['checksum'],
item_key=v['item_key'])
val.append(b)
elif len(blob_value) == 1:
val = definitions.Blob(size=blob_value[0]['size'],
locations=blob_value[0]['locations'],
checksum=blob_value[0]['checksum'],
item_key=blob_value[0]['item_key'])
else:
raise exception.InvalidArtifactPropertyValue(
message=_('Blob %(name)s may not have multiple values'),
name=blob_name)
artifact_properties[blob_name] = val
def _deserialize_dependencies(artifact_type, deps_from_db,
artifact_properties, plugins):
"""Retrieves dependencies from database"""
for dep_name, dep_value in six.iteritems(deps_from_db):
if not dep_value:
continue
if isinstance(
artifact_type.metadata.attributes.dependencies.get(dep_name),
declarative.ListAttributeDefinition):
val = []
for v in dep_value:
val.append(deserialize_from_db(v, plugins))
elif len(dep_value) == 1:
val = deserialize_from_db(dep_value[0], plugins)
else:
raise exception.InvalidArtifactPropertyValue(
message=_('Relation %(name)s may not have multiple values'),
name=dep_name)
artifact_properties[dep_name] = val
def deserialize_from_db(db_dict, plugins):
artifact_properties = {}
type_name = None
type_version = None
for prop_name in COMMON_ARTIFACT_PROPERTIES:
prop_value = db_dict.pop(prop_name, None)
if prop_name == 'type_name':
type_name = prop_value
elif prop_name == 'type_version':
type_version = prop_value
else:
artifact_properties[prop_name] = prop_value
try:
artifact_type = plugins.get_class_by_typename(type_name, type_version)
except exception.ArtifactPluginNotFound:
raise exception.UnknownArtifactType(name=type_name,
version=type_version)
type_specific_properties = db_dict.pop('properties', {})
for prop_name, prop_value in six.iteritems(type_specific_properties):
prop_type = prop_value.get('type')
prop_value = prop_value.get('value')
if prop_value is None:
continue
if '.' in prop_name: # dict-based property
name, key = prop_name.split('.', 1)
artifact_properties.setdefault(name, {})
if prop_type == 'array':
artifact_properties[name][key] = [item.get('value') for item in
prop_value]
else:
artifact_properties[name][key] = prop_value
elif prop_type == 'array': # list-based property
artifact_properties[prop_name] = [item.get('value') for item in
prop_value]
else:
artifact_properties[prop_name] = prop_value
blobs = db_dict.pop('blobs', {})
_deserialize_blobs(artifact_type, blobs, artifact_properties)
dependencies = db_dict.pop('dependencies', {})
_deserialize_dependencies(artifact_type, dependencies,
artifact_properties, plugins)
return artifact_type(**artifact_properties)
def _process_blobs_for_client(artifact, result):
"""Processes artifact's blobs: adds download links and pretty-printed data.
The result is stored in 'result' dict.
"""
def build_uri(blob_attr, position=None):
"""A helper func to build download uri"""
template = "/artifacts/%(type)s/v%(version)s/%(id)s/%(prop)s/download"
format_dict = {
"type": artifact.metadata.endpoint,
"version": artifact.type_version,
"id": artifact.id,
"prop": blob_attr.name
}
if position is not None:
template = ("/artifacts/%(type)s/v%(version)s/"
"%(id)s/%(prop)s/%(position)s/download")
format_dict["position"] = position
return template % format_dict
for blob_attr in artifact.metadata.attributes.blobs.values():
value = blob_attr.get_value(artifact)
if value is None:
result[blob_attr.name] = None
elif isinstance(value, collections.Iterable):
res_list = []
for pos, blob in enumerate(value):
blob_dict = blob.to_dict()
blob_dict["download_link"] = build_uri(blob_attr, pos)
res_list.append(blob_dict)
result[blob_attr.name] = res_list
else:
result[blob_attr.name] = value.to_dict()
result[blob_attr.name]["download_link"] = build_uri(blob_attr)
def serialize_for_client(artifact, show_level=ga.Showlevel.NONE):
# use serialize_for_db and modify some fields
# (like properties, show only value, not type)
result = {}
for prop in artifact.metadata.attributes.properties.values():
result[prop.name] = prop.get_value(artifact)
if show_level > ga.Showlevel.NONE:
for dep in artifact.metadata.attributes.dependencies.values():
inner_show_level = (ga.Showlevel.DIRECT
if show_level == ga.Showlevel.DIRECT
else ga.Showlevel.NONE)
value = dep.get_value(artifact)
if value is None:
result[dep.name] = None
elif isinstance(value, list):
result[dep.name] = [serialize_for_client(v, inner_show_level)
for v in value]
else:
result[dep.name] = serialize_for_client(value,
inner_show_level)
_process_blobs_for_client(artifact, result)
return result