From 3a46e291416f453c612fe27a31d0b57918aaf276 Mon Sep 17 00:00:00 2001 From: Artem Goncharov Date: Thu, 10 Sep 2020 18:21:37 +0200 Subject: Fix cache dir flooding when running from /tmp Ansible invokes modules with executable placed under /tmp. This causes stevedore caching to create bazillions of useless cache files. When we can identify we run with executable under /tmp or if in the target cache directory an empty file '.disable' is present - skip writing cache. Change-Id: Ic483ac68027505402ba32d7f612631e15a678d09 --- stevedore/_cache.py | 25 ++++++++++++------- stevedore/tests/test_cache.py | 56 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 8 deletions(-) create mode 100644 stevedore/tests/test_cache.py (limited to 'stevedore') diff --git a/stevedore/_cache.py b/stevedore/_cache.py index 28a45fa..dfba178 100644 --- a/stevedore/_cache.py +++ b/stevedore/_cache.py @@ -136,6 +136,14 @@ class Cache: cache_dir = _get_cache_dir() self._dir = cache_dir self._internal = {} + self._disable_caching = False + + # Caching can be disabled by either placing .disable file into the + # target directory or when python executable is under /tmp (this is the + # case when executed from ansible) + if any([os.path.isfile(os.path.join(self._dir, '.disable')), + sys.executable[0:4] == '/tmp']): + self._disable_caching = True def _get_data_for_path(self, path): if path is None: @@ -154,14 +162,15 @@ class Cache: except (IOError, json.JSONDecodeError): data = _build_cacheable_data(path) data['path_values'] = path_values - try: - log.debug('writing to %s', filename) - os.makedirs(self._dir, exist_ok=True) - with open(filename, 'w') as f: - json.dump(data, f) - except (IOError, OSError): - # Could not create cache dir or write file. - pass + if not self._disable_caching: + try: + log.debug('writing to %s', filename) + os.makedirs(self._dir, exist_ok=True) + with open(filename, 'w') as f: + json.dump(data, f) + except (IOError, OSError): + # Could not create cache dir or write file. + pass self._internal[internal_key] = data return data diff --git a/stevedore/tests/test_cache.py b/stevedore/tests/test_cache.py new file mode 100644 index 0000000..8bf49c8 --- /dev/null +++ b/stevedore/tests/test_cache.py @@ -0,0 +1,56 @@ +# 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. + +"""Tests for stevedore._cache +""" +import sys + +from unittest import mock + +from stevedore import _cache +from stevedore.tests import utils + + +class TestCache(utils.TestCase): + + def test_disable_caching_executable(self): + """Test caching is disabled if python interpreter is located under /tmp + directory (Ansible) + """ + with mock.patch.object(sys, 'executable', '/tmp/fake'): + sot = _cache.Cache() + self.assertTrue(sot._disable_caching) + + def test_disable_caching_file(self): + """Test caching is disabled if .disable file is present in target + dir + """ + cache_dir = _cache._get_cache_dir() + + with mock.patch('os.path.isfile') as mock_path: + mock_path.return_value = True + sot = _cache.Cache() + mock_path.assert_called_with('%s/.disable' % cache_dir) + self.assertTrue(sot._disable_caching) + + mock_path.return_value = False + sot = _cache.Cache() + self.assertFalse(sot._disable_caching) + + @mock.patch('os.makedirs') + @mock.patch('builtins.open') + def test__get_data_for_path_no_write(self, mock_open, mock_mkdir): + sot = _cache.Cache() + sot._disable_caching = True + mock_open.side_effect = IOError + sot._get_data_for_path('fake') + mock_mkdir.assert_not_called() -- cgit v1.2.1