7a1a65bac2
Its impractical to pass in the current context to TemplateFiles._refresh, and it will soon be mandatory for all db api context arguments to be not None. This change creates a short-lived context just for the refresh. It will have the same effect as the current behaviour of creating a new session, since the a new session will be associated with the context, so there is no danger of returning stale data from the current session's identity map. This change also makes the context argument mandatory for any raw templates operations that might need one. Change-Id: I156096d736aac4999a3eebe077ed50ef7384ca02
137 lines
4.7 KiB
Python
137 lines
4.7 KiB
Python
#
|
|
# 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
|
|
import weakref
|
|
|
|
from heat.common import context
|
|
from heat.common.i18n import _
|
|
from heat.db import api as db_api
|
|
from heat.objects import raw_template_files
|
|
|
|
_d = weakref.WeakValueDictionary()
|
|
|
|
|
|
class ReadOnlyDict(dict):
|
|
def __setitem__(self, key):
|
|
raise ValueError("Attempted to write to internal TemplateFiles cache")
|
|
|
|
|
|
class TemplateFiles(collections.Mapping):
|
|
|
|
def __init__(self, files):
|
|
self.files = None
|
|
self.files_id = None
|
|
if files is None:
|
|
return
|
|
if isinstance(files, TemplateFiles):
|
|
self.files_id = files.files_id
|
|
self.files = files.files
|
|
return
|
|
if isinstance(files, six.integer_types):
|
|
self.files_id = files
|
|
if self.files_id in _d:
|
|
self.files = _d[self.files_id]
|
|
return
|
|
if not isinstance(files, dict):
|
|
raise ValueError(_('Expected dict, got %(cname)s for files, '
|
|
'(value is %(val)s)') %
|
|
{'cname': files.__class__,
|
|
'val': str(files)})
|
|
# the dict has not been persisted as a raw_template_files db obj
|
|
# yet, so no self.files_id
|
|
self.files = ReadOnlyDict(files)
|
|
|
|
def __getitem__(self, key):
|
|
self._refresh_if_needed()
|
|
if self.files is None:
|
|
raise KeyError
|
|
return self.files[key]
|
|
|
|
def __setitem__(self, key, value):
|
|
self.update({key: value})
|
|
|
|
def __len__(self):
|
|
self._refresh_if_needed()
|
|
if not self.files:
|
|
return 0
|
|
return len(self.files)
|
|
|
|
def __contains__(self, key):
|
|
self._refresh_if_needed()
|
|
if not self.files:
|
|
return False
|
|
return key in self.files
|
|
|
|
def __iter__(self):
|
|
self._refresh_if_needed()
|
|
if self.files_id is None:
|
|
return iter(ReadOnlyDict({}))
|
|
return iter(self.files)
|
|
|
|
def _refresh_if_needed(self):
|
|
# retrieve files from db if needed
|
|
if self.files_id is None:
|
|
return
|
|
if self.files_id in _d:
|
|
self.files = _d[self.files_id]
|
|
return
|
|
self._refresh()
|
|
|
|
def _refresh(self):
|
|
ctxt = context.get_admin_context()
|
|
rtf_obj = db_api.raw_template_files_get(ctxt, self.files_id)
|
|
_files_dict = ReadOnlyDict(rtf_obj.files)
|
|
self.files = _files_dict
|
|
_d[self.files_id] = _files_dict
|
|
|
|
def store(self, ctxt):
|
|
if not self.files or self.files_id is not None:
|
|
# Do not to persist an empty raw_template_files obj. If we
|
|
# already have a not null self.files_id, the (immutable)
|
|
# raw_templated_object has already been persisted so just
|
|
# return the id.
|
|
return self.files_id
|
|
rtf_obj = raw_template_files.RawTemplateFiles.create(
|
|
ctxt, {'files': self.files})
|
|
self.files_id = rtf_obj.id
|
|
_d[self.files_id] = self.files
|
|
return self.files_id
|
|
|
|
def update(self, files):
|
|
# Sets up the next call to store() to create a new
|
|
# raw_template_files db obj. It seems like we *could* just
|
|
# update the existing raw_template_files obj, but the problem
|
|
# with that is other heat-engine processes' _d dictionaries
|
|
# would have stale data for a given raw_template_files.id with
|
|
# no way of knowing whether that data should be refreshed or
|
|
# not. So, just avoid the potential for weird race conditions
|
|
# and create another db obj in the next store().
|
|
if len(files) == 0:
|
|
return
|
|
if not isinstance(files, dict):
|
|
raise ValueError(_('Expected dict, got %(cname)s for files, '
|
|
'(value is %(val)s)') %
|
|
{'cname': files.__class__,
|
|
'val': str(files)})
|
|
|
|
self._refresh_if_needed()
|
|
if self.files:
|
|
new_files = self.files.copy()
|
|
new_files.update(files)
|
|
else:
|
|
new_files = files
|
|
self.files_id = None # not persisted yet
|
|
self.files = ReadOnlyDict(new_files)
|