Have the enforcer have its own file cache

Instead of having a library have a global (and not garbage
collectable) file cache that is only used by enforcers and
no other code have the file cache be per-enforcer so that it
can be garbage collected when the enforcer drops from the
scope its used in.

This also removes one more usage of global state with instance
specific state, which makes things easier to test and easier
to isolate problems in (the less things that can be 'touched
at a distance' the better).

Change-Id: I208b08ae00e0337b594ece0f2b335d4de9f7392b
This commit is contained in:
Joshua Harlow 2015-08-05 12:36:39 -07:00 committed by Steve Martinelli
parent d7d6301131
commit 7ee4f409e1
3 changed files with 16 additions and 14 deletions

View File

@ -17,24 +17,22 @@ import logging
import os import os
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
_FILE_CACHE = {}
def read_cached_file(filename, force_reload=False): def read_cached_file(cache, filename, force_reload=False):
"""Read from a file if it has been modified. """Read from a file if it has been modified.
:param force_reload: Whether to reload the file. :param force_reload: Whether to reload the file.
:returns: A tuple with a boolean specifying if the data is fresh :returns: A tuple with a boolean specifying if the data is fresh
or not. or not.
""" """
global _FILE_CACHE
if force_reload: if force_reload:
delete_cached_file(filename) delete_cached_file(cache, filename)
reloaded = False reloaded = False
mtime = os.path.getmtime(filename) mtime = os.path.getmtime(filename)
cache_info = _FILE_CACHE.setdefault(filename, {}) cache_info = cache.setdefault(filename, {})
if not cache_info or mtime > cache_info.get('mtime', 0): if not cache_info or mtime > cache_info.get('mtime', 0):
LOG.debug("Reloading cached file %s", filename) LOG.debug("Reloading cached file %s", filename)
@ -45,12 +43,13 @@ def read_cached_file(filename, force_reload=False):
return (reloaded, cache_info['data']) return (reloaded, cache_info['data'])
def delete_cached_file(filename): def delete_cached_file(cache, filename):
"""Delete cached file if present. """Delete cached file if present.
:param filename: filename to delete :param filename: filename to delete
""" """
global _FILE_CACHE
if filename in _FILE_CACHE: try:
del _FILE_CACHE[filename] del cache[filename]
except KeyError:
pass

View File

@ -344,6 +344,7 @@ class Enforcer(object):
self.overwrite = overwrite self.overwrite = overwrite
self._loaded_files = [] self._loaded_files = []
self._policy_dir_mtimes = {} self._policy_dir_mtimes = {}
self._file_cache = {}
def set_rules(self, rules, overwrite=True, use_conf=False): def set_rules(self, rules, overwrite=True, use_conf=False):
"""Create a new :class:`Rules` based on the provided dict of rules. """Create a new :class:`Rules` based on the provided dict of rules.
@ -364,13 +365,17 @@ class Enforcer(object):
self.rules.update(rules) self.rules.update(rules)
def clear(self): def clear(self):
"""Clears :class:`Enforcer` rules, policy's cache and policy's path.""" """Clears :class:`Enforcer` contents.
This will clear this instances rules, policy's cache, file cache
and policy's path.
"""
self.set_rules({}) self.set_rules({})
_cache_handler.delete_cached_file(self.policy_path)
self.default_rule = None self.default_rule = None
self.policy_path = None self.policy_path = None
self._loaded_files = [] self._loaded_files = []
self._policy_dir_mtimes = {} self._policy_dir_mtimes = {}
self._file_cache.clear()
def load_rules(self, force_reload=False): def load_rules(self, force_reload=False):
"""Loads policy_path's rules. """Loads policy_path's rules.
@ -428,7 +433,7 @@ class Enforcer(object):
def _load_policy_file(self, path, force_reload, overwrite=True): def _load_policy_file(self, path, force_reload, overwrite=True):
reloaded, data = _cache_handler.read_cached_file( reloaded, data = _cache_handler.read_cached_file(
path, force_reload=force_reload) self._file_cache, path, force_reload=force_reload)
if reloaded or not self.rules or not overwrite: if reloaded or not self.rules or not overwrite:
rules = Rules.load_json(data, self.default_rule) rules = Rules.load_json(data, self.default_rule)
self.set_rules(rules, overwrite=overwrite, use_conf=True) self.set_rules(rules, overwrite=overwrite, use_conf=True)

View File

@ -264,12 +264,10 @@ class EnforcerTest(base.PolicyBaseTestCase):
def test_clear(self): def test_clear(self):
# Make sure the rules are reset # Make sure the rules are reset
self.enforcer.rules = 'spam' self.enforcer.rules = 'spam'
filename = self.enforcer.policy_path
self.enforcer.clear() self.enforcer.clear()
self.assertEqual({}, self.enforcer.rules) self.assertEqual({}, self.enforcer.rules)
self.assertEqual(None, self.enforcer.default_rule) self.assertEqual(None, self.enforcer.default_rule)
self.assertEqual(None, self.enforcer.policy_path) self.assertEqual(None, self.enforcer.policy_path)
_cache_handler.delete_cached_file.assert_called_once_with(filename)
def test_rule_with_check(self): def test_rule_with_check(self):
rules_json = """{ rules_json = """{