heat/heat/engine/template_files.py

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.sqlalchemy 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)