329 lines
13 KiB
Python
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
|