deb-murano/murano/dsl/object_store.py
Stan Lagun 024ac109ef ID references made model not able to load in 2 passes
ObjectStore used to replace object factories with actual objects
only after 2 passes instead of after first pass as it was before then.
Until that happened object could not be obtained from outside
despite it was already created by the 2nd pass so ID reference
resolution that happened on the second pass failed.

With this fix ObjectStore doesn't tries to hide partially-initialized
objects anymore. So if object A references object B by ID
it may get B after B has been loaded but before its .init was called.
That's not a problem because we just loading (relinking) model
and do not depend on anything that could be initialized in B's .init.
However because B is not owned by A and A is not owned by B
it may happen that .init of A will be executed prior to .init of B
so if A's initializer will see object B that wasn't initialized yet.
Generally this is unavoidable because there may be circular
references. So there were 2 options: either to do more that 2
passes and try to to avoid such situations for cases when
there are no circular references or just require classes not
to do anything with objects they don't own from .init.
Because .init is also called from GC and should not have
side effects at all the second options was chosen but
we may reconsider this in the future

Closes-Bug: #1492640

Change-Id: Id38a780f9084323f0806b044262aa3b89eb5da74
2015-09-06 11:09:03 +00:00

111 lines
3.7 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.
import weakref
from murano.dsl import dsl_types
from murano.dsl import helpers
class ObjectStore(object):
def __init__(self, executor, parent_store=None):
self._parent_store = parent_store
self._store = {}
self._designer_attributes_store = {}
self._executor = weakref.ref(executor)
self._initializing = False
@property
def initializing(self):
return self._initializing
@property
def executor(self):
return self._executor()
def get(self, object_id):
if object_id in self._store:
result = self._store[object_id]
if not isinstance(result, dsl_types.MuranoObject):
result = result.object
return result
if self._parent_store:
return self._parent_store.get(object_id)
return None
def has(self, object_id):
return object_id in self._store
def put(self, murano_object):
self._store[murano_object.object_id] = murano_object
def load(self, value, owner, context=None, defaults=None):
if value is None:
return None
if '?' not in value or 'type' not in value['?']:
raise ValueError()
system_key = value['?']
object_id = system_key['id']
obj_type = system_key['type']
version_spec = helpers.parse_version_spec(
system_key.get('classVersion'))
if 'package' not in system_key:
package = self.executor.package_loader.load_class_package(
obj_type, version_spec)
else:
package = self.executor.package_loader.load_package(
system_key['package'], version_spec)
class_obj = package.find_class(obj_type, False)
try:
if owner is None:
self._initializing = True
if object_id in self._store:
factory = self._store[object_id]
if isinstance(factory, dsl_types.MuranoObject):
return factory
else:
factory = class_obj.new(
owner, self,
name=system_key.get('name'),
object_id=object_id, defaults=defaults)
self._store[object_id] = factory
system_value = ObjectStore._get_designer_attributes(system_key)
self._designer_attributes_store[object_id] = system_value
init_context = self.executor.create_object_context(
factory.object, context)
obj = factory(init_context, **value)
if not self._initializing:
self._store[object_id] = obj
if owner is None:
self._initializing = False
self._store[object_id] = factory(init_context, **value)
finally:
if owner is None:
self._initializing = False
return factory.object
@staticmethod
def _get_designer_attributes(header):
return dict((k, v) for k, v in header.iteritems()
if str(k).startswith('_'))
def designer_attributes(self, object_id):
return self._designer_attributes_store.get(object_id, {})