580677eedc
1.As mentioned in [1], we should avoid using six.iteritems to achieve iterators. We can use dict.items instead, as it will return iterators in PY3 as well. And dict.items/keys will more readable. 2.In py2, the performance about list should be negligible, see the link [2]. [1] https://wiki.openstack.org/wiki/Python3 [2] http://lists.openstack.org/pipermail/openstack-dev/2015-June/066391.html Change-Id: I45fa65427318e1c35bb521de46e81ea12ca7b770
415 lines
16 KiB
Python
415 lines
16 KiB
Python
# Copyright (c) 2014 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.
|
|
|
|
|
|
from murano.dsl import constants
|
|
from murano.dsl import dsl
|
|
from murano.dsl import dsl_types
|
|
from murano.dsl import exceptions
|
|
from murano.dsl import helpers
|
|
from murano.dsl.principal_objects import garbage_collector
|
|
from murano.dsl import yaql_integration
|
|
|
|
|
|
class MuranoObject(dsl_types.MuranoObject):
|
|
def __init__(self, murano_class, owner, object_id=None, name=None,
|
|
known_classes=None, this=None, metadata=None):
|
|
self._initialized = False
|
|
self._destroyed = False
|
|
if known_classes is None:
|
|
known_classes = {}
|
|
if this is None:
|
|
self._owner = owner
|
|
self._object_id = object_id or helpers.generate_id()
|
|
self._type = murano_class
|
|
self._properties = {}
|
|
self._parents = {}
|
|
self._this = this
|
|
self._name = name
|
|
self._extension = None
|
|
self._executor = helpers.weak_ref(helpers.get_executor())
|
|
self._config = murano_class.package.get_class_config(
|
|
murano_class.name)
|
|
self._metadata = metadata
|
|
if not isinstance(self._config, dict):
|
|
self._config = {}
|
|
known_classes[murano_class.name] = self
|
|
for parent_class in murano_class.parents:
|
|
name = parent_class.name
|
|
if name not in known_classes:
|
|
obj = MuranoObject(
|
|
parent_class, owner,
|
|
object_id=self._object_id,
|
|
known_classes=known_classes, this=self.real_this)
|
|
|
|
self._parents[name] = known_classes[name] = obj
|
|
else:
|
|
self._parents[name] = known_classes[name]
|
|
self._destruction_dependencies = []
|
|
|
|
@property
|
|
def extension(self):
|
|
return self._extension
|
|
|
|
@property
|
|
def name(self):
|
|
return self.real_this._name
|
|
|
|
@property
|
|
def metadata(self):
|
|
return self.real_this._metadata
|
|
|
|
@extension.setter
|
|
def extension(self, value):
|
|
self._extension = value
|
|
|
|
def initialize(self, context, params, used_names=None):
|
|
context = context.create_child_context()
|
|
context[constants.CTX_ALLOW_PROPERTY_WRITES] = True
|
|
object_store = helpers.get_object_store()
|
|
for property_name in self.type.properties:
|
|
spec = self.type.properties[property_name]
|
|
if spec.usage == dsl_types.PropertyUsages.Config:
|
|
if property_name in self._config:
|
|
property_value = self._config[property_name]
|
|
else:
|
|
property_value = dsl.NO_VALUE
|
|
self.set_property(property_name, property_value,
|
|
dry_run=self._initialized)
|
|
|
|
init = self.type.methods.get('.init')
|
|
used_names = used_names or set()
|
|
names = set(self.type.properties)
|
|
if init:
|
|
names.update(init.arguments_scheme.keys())
|
|
last_errors = len(names)
|
|
init_args = {}
|
|
while True:
|
|
errors = 0
|
|
for property_name in names:
|
|
if init and property_name in init.arguments_scheme:
|
|
spec = init.arguments_scheme[property_name]
|
|
is_init_arg = True
|
|
else:
|
|
spec = self.type.properties[property_name]
|
|
is_init_arg = False
|
|
|
|
if property_name in used_names:
|
|
continue
|
|
if spec.usage in (dsl_types.PropertyUsages.Config,
|
|
dsl_types.PropertyUsages.Static):
|
|
used_names.add(property_name)
|
|
continue
|
|
if spec.usage == dsl_types.PropertyUsages.Runtime:
|
|
if not spec.has_default:
|
|
used_names.add(property_name)
|
|
continue
|
|
property_value = dsl.NO_VALUE
|
|
else:
|
|
property_value = params.get(property_name, dsl.NO_VALUE)
|
|
try:
|
|
if is_init_arg:
|
|
init_args[property_name] = property_value
|
|
else:
|
|
self.set_property(
|
|
property_name, property_value, context,
|
|
dry_run=self._initialized)
|
|
used_names.add(property_name)
|
|
except exceptions.UninitializedPropertyAccessError:
|
|
errors += 1
|
|
except exceptions.ContractViolationException:
|
|
if spec.usage != dsl_types.PropertyUsages.Runtime:
|
|
raise
|
|
if not errors:
|
|
break
|
|
if errors >= last_errors:
|
|
raise exceptions.CircularExpressionDependenciesError()
|
|
last_errors = errors
|
|
|
|
if (not object_store.initializing and
|
|
self._extension is None and
|
|
not self._initialized and
|
|
not self._destroyed and
|
|
not helpers.is_objects_dry_run_mode()):
|
|
method = self.type.methods.get('__init__')
|
|
if method:
|
|
filtered_params = yaql_integration.filter_parameters(
|
|
method.body, **params)
|
|
yield lambda: method.invoke(
|
|
self, filtered_params[0], filtered_params[1], context)
|
|
|
|
for parent in self._parents.values():
|
|
for t in parent.initialize(context, params, used_names):
|
|
yield t
|
|
|
|
def run_init():
|
|
if init:
|
|
context[constants.CTX_ARGUMENT_OWNER] = self.real_this
|
|
init.invoke(self.real_this, (), init_args,
|
|
context.create_child_context())
|
|
self._initialized = True
|
|
|
|
if (not object_store.initializing and
|
|
not helpers.is_objects_dry_run_mode() and
|
|
not self._initialized and
|
|
not self._destroyed):
|
|
yield run_init
|
|
|
|
@property
|
|
def object_id(self):
|
|
return self._object_id
|
|
|
|
@property
|
|
def type(self):
|
|
return self._type
|
|
|
|
@property
|
|
def owner(self):
|
|
if self._this is None:
|
|
return self._owner
|
|
else:
|
|
return self.real_this.owner
|
|
|
|
@property
|
|
def real_this(self):
|
|
return self._this or self
|
|
|
|
@property
|
|
def executor(self):
|
|
return self._executor()
|
|
|
|
@property
|
|
def initialized(self):
|
|
return self._initialized
|
|
|
|
@property
|
|
def destruction_dependencies(self):
|
|
return self._destruction_dependencies
|
|
|
|
def load_dependencies(self, dependencies):
|
|
self._destruction_dependencies = []
|
|
if not dependencies:
|
|
return
|
|
destruction_dependencies = dependencies.get('onDestruction', [])
|
|
object_store = helpers.get_object_store()
|
|
for record in destruction_dependencies:
|
|
subscriber_id = record['subscriber']
|
|
subscriber = object_store.get(subscriber_id)
|
|
if not subscriber:
|
|
continue
|
|
garbage_collector.GarbageCollector.subscribe_destruction(
|
|
self, subscriber, record.get('handler'))
|
|
|
|
def get_property(self, name, context=None):
|
|
start_type, derived = self.type, False
|
|
caller_class = None if not context else helpers.get_type(context)
|
|
if caller_class is not None and caller_class.is_compatible(self):
|
|
start_type, derived = caller_class, True
|
|
|
|
declared_properties = start_type.find_properties(
|
|
lambda p: p.name == name)
|
|
if len(declared_properties) > 0:
|
|
spec = self.real_this.type.find_single_property(name)
|
|
if spec.usage == dsl_types.PropertyUsages.Static:
|
|
return self.executor.get_static_property(
|
|
spec.declaring_type, name, context)
|
|
else:
|
|
return self.real_this._get_property_value(name)
|
|
elif derived:
|
|
return self.cast(caller_class)._get_property_value(name)
|
|
else:
|
|
raise exceptions.PropertyReadError(name, start_type)
|
|
|
|
def _get_property_value(self, name):
|
|
try:
|
|
return self._properties[name]
|
|
except KeyError:
|
|
raise exceptions.UninitializedPropertyAccessError(
|
|
name, self.type)
|
|
|
|
def set_property(self, name, value, context=None, dry_run=False):
|
|
start_type, derived = self.real_this.type, False
|
|
caller_class = None if not context else helpers.get_type(context)
|
|
if caller_class is not None and caller_class.is_compatible(self):
|
|
start_type, derived = caller_class, True
|
|
if context is None:
|
|
context = self.executor.create_object_context(self)
|
|
declared_properties = start_type.find_properties(
|
|
lambda p: p.name == name)
|
|
if len(declared_properties) > 0:
|
|
ultimate_spec = self.real_this.type.find_single_property(name)
|
|
property_list = list(self._list_properties(name))
|
|
for spec in property_list:
|
|
if (caller_class is not None and not
|
|
helpers.are_property_modifications_allowed(context) and
|
|
(spec.usage not in dsl_types.PropertyUsages.Writable or
|
|
not derived)):
|
|
raise exceptions.NoWriteAccessError(name)
|
|
|
|
if spec.usage == dsl_types.PropertyUsages.Static:
|
|
default = None
|
|
else:
|
|
default = self._config.get(name, spec.default)
|
|
|
|
if spec is ultimate_spec:
|
|
value = spec.transform(
|
|
value, self.real_this,
|
|
self.real_this, context, default=default,
|
|
finalize=len(property_list) == 1)
|
|
else:
|
|
spec.validate(value, self.real_this, context, default)
|
|
if len(property_list) > 1:
|
|
value = ultimate_spec.finalize(value, self.real_this, context)
|
|
if ultimate_spec.usage == dsl_types.PropertyUsages.Static:
|
|
self.executor.set_static_property(
|
|
ultimate_spec.declaring_type, name, value, context,
|
|
dry_run=dry_run)
|
|
elif not dry_run:
|
|
self.real_this._properties[name] = value
|
|
elif derived:
|
|
if not dry_run:
|
|
obj = self.cast(caller_class)
|
|
obj._properties[name] = value
|
|
else:
|
|
raise exceptions.PropertyWriteError(name, start_type)
|
|
|
|
def cast(self, cls):
|
|
for p in helpers.traverse(self, lambda t: t._parents.values()):
|
|
if p.type == cls:
|
|
return p
|
|
raise TypeError('Cannot cast {0} to {1}'.format(self.type, cls))
|
|
|
|
def _list_properties(self, name):
|
|
for p in helpers.traverse(
|
|
self.real_this, lambda t: t._parents.values()):
|
|
if name in p.type.properties:
|
|
yield p.type.properties[name]
|
|
|
|
def __repr__(self):
|
|
return '<{0}/{1} {2} ({3})>'.format(
|
|
self.type.name, self.type.version, self.object_id, id(self))
|
|
|
|
def to_dictionary(self, include_hidden=False,
|
|
serialization_type=dsl_types.DumpTypes.Serializable,
|
|
allow_refs=False, with_destruction_dependencies=False):
|
|
context = helpers.get_context()
|
|
result = {}
|
|
for parent in self._parents.values():
|
|
result.update(parent.to_dictionary(
|
|
include_hidden, dsl_types.DumpTypes.Serializable,
|
|
allow_refs))
|
|
skip_usages = (dsl_types.PropertyUsages.Runtime,
|
|
dsl_types.PropertyUsages.Config)
|
|
for property_name in self.type.properties:
|
|
if property_name in self.real_this._properties:
|
|
spec = self.type.properties[property_name]
|
|
if spec.usage not in skip_usages or include_hidden:
|
|
prop_value = self.real_this._properties[property_name]
|
|
if isinstance(prop_value, MuranoObject) and allow_refs:
|
|
meta = [m for m in spec.get_meta(context)
|
|
if m.type.name == ('io.murano.metadata.'
|
|
'engine.Serialize')]
|
|
if meta and meta[0].get_property(
|
|
'as', context) == 'reference':
|
|
prop_value = prop_value.object_id
|
|
result[property_name] = prop_value
|
|
if serialization_type == dsl_types.DumpTypes.Inline:
|
|
result.pop('?')
|
|
result = {
|
|
self.type: result,
|
|
'id': self.object_id,
|
|
'name': self.name,
|
|
'metadata': self.metadata
|
|
}
|
|
header = result
|
|
else:
|
|
if serialization_type == dsl_types.DumpTypes.Mixed:
|
|
result.update({'?': {
|
|
'type': self.type,
|
|
'id': self.object_id,
|
|
'name': self.name,
|
|
'metadata': self.metadata
|
|
}})
|
|
else:
|
|
result.update({'?': {
|
|
'type': helpers.format_type_string(self.type),
|
|
'id': self.object_id,
|
|
'name': self.name,
|
|
'metadata': self.metadata
|
|
}})
|
|
header = result['?']
|
|
if self.destroyed:
|
|
header['destroyed'] = True
|
|
if with_destruction_dependencies:
|
|
dds = []
|
|
for record in self.destruction_dependencies:
|
|
subscriber = record['subscriber']()
|
|
if not subscriber or self.executor.object_store.is_doomed(
|
|
subscriber):
|
|
continue
|
|
dds.append({
|
|
'subscriber': subscriber.object_id,
|
|
'handler': record['handler']
|
|
})
|
|
if dds:
|
|
header.setdefault('dependencies', {})['onDestruction'] = dds
|
|
return result
|
|
|
|
def mark_destroyed(self, clear_data=False):
|
|
self._destroyed = True
|
|
self._suppress__del__ = None
|
|
if clear_data or not self.initialized:
|
|
self._extension = None
|
|
self._properties = None
|
|
self._owner = None
|
|
self._destruction_dependencies = None
|
|
self._this = None
|
|
for p in self._parents.values():
|
|
p.mark_destroyed(clear_data)
|
|
|
|
@property
|
|
def destroyed(self):
|
|
return self._destroyed
|
|
|
|
|
|
class RecyclableMuranoObject(MuranoObject):
|
|
def __init__(self, *args, **kwargs):
|
|
# Create self-reference to prevent __del__ from being called
|
|
# automatically when there are no other objects referring to this one.
|
|
# Without this reference __del__ will get called immediately after
|
|
# reference counter will go to 0 and the object will put itself into
|
|
# pending list creating another reference to itself and thus preventing
|
|
# its child objects from being deleted. After the .destroy method
|
|
# child objects will become eligible for destruction but will be
|
|
# unable to use find() method since their owner will be destroyed
|
|
# and collected at that point. With this reference gc.collect()
|
|
# will collect the whole object graph at once and then we could
|
|
# sort it and destroy in the correct order so that child objects
|
|
# will be destroyed first.
|
|
|
|
self._suppress__del__ = self
|
|
super(RecyclableMuranoObject, self).__init__(*args, **kwargs)
|
|
|
|
def __del__(self):
|
|
# For Py2 the purpose of __del__ (in combination with _suppress__del__)
|
|
# method is just to prevent object from being released automatically.
|
|
# In Py3 the gc.collect list will be empty and __del__ will be called
|
|
# for objects that were not destroyed yet.
|
|
if self._this is None and self._initialized and not self._destroyed:
|
|
self.executor.object_store.schedule_object_destruction(self)
|
|
|
|
def mark_destroyed(self, clear_data=False):
|
|
self.executor.attribute_store.forget_object(self)
|
|
super(RecyclableMuranoObject, self).mark_destroyed(clear_data)
|