deb-heat/heat/engine/template_files.py
Steve Baker 7a1a65bac2 Create an admin context to refresh template files
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
2016-07-11 10:10:42 +12:00

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)