mirror of https://github.com/ansible/ansible.git
Add inventory cache integration tests (#77895)
* Add intentional coverage for incidental_inventory_foreman coverage cache tests ci_complete ci_coveragepull/77976/head
parent
5391150b30
commit
6c6a7851ed
@ -0,0 +1,4 @@
|
||||
plugin: exercise_cache
|
||||
cache: true
|
||||
cache_plugin: jsonfile
|
||||
cache_connection: ./cache
|
@ -0,0 +1,328 @@
|
||||
# Copyright (c) 2022 Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
inventory: exercise_cache
|
||||
short_description: run tests against the specified cache plugin
|
||||
description:
|
||||
- This plugin doesn't modify inventory.
|
||||
- Load a cache plugin and test the inventory cache interface is dict-like.
|
||||
- Most inventory cache write methods only apply to the in-memory cache.
|
||||
- The 'flush' and 'set_cache' methods should be used to apply changes to the backing cache plugin.
|
||||
- The inventory cache read methods prefer the in-memory cache, and fall back to reading from the cache plugin.
|
||||
extends_documentation_fragment:
|
||||
- inventory_cache
|
||||
options:
|
||||
plugin:
|
||||
required: true
|
||||
description: name of the plugin (exercise_cache)
|
||||
cache_timeout:
|
||||
ini: []
|
||||
env: []
|
||||
cli: []
|
||||
default: 0 # never expire
|
||||
'''
|
||||
|
||||
from ansible.errors import AnsibleError
|
||||
from ansible.plugins.inventory import BaseInventoryPlugin, Cacheable
|
||||
from ansible.utils.display import Display
|
||||
|
||||
from time import sleep
|
||||
|
||||
|
||||
display = Display()
|
||||
|
||||
|
||||
class InventoryModule(BaseInventoryPlugin, Cacheable):
|
||||
|
||||
NAME = 'exercise_cache'
|
||||
|
||||
test_cache_methods = [
|
||||
'test_plugin_name',
|
||||
'test_update_cache_if_changed',
|
||||
'test_set_cache',
|
||||
'test_load_whole_cache',
|
||||
'test_iter',
|
||||
'test_len',
|
||||
'test_get_missing_key',
|
||||
'test_get_expired_key',
|
||||
'test_get',
|
||||
'test_items',
|
||||
'test_keys',
|
||||
'test_values',
|
||||
'test_pop',
|
||||
'test_del',
|
||||
'test_set',
|
||||
'test_update',
|
||||
'test_flush',
|
||||
]
|
||||
|
||||
def verify_file(self, path):
|
||||
if not path.endswith(('exercise_cache.yml', 'exercise_cache.yaml',)):
|
||||
return False
|
||||
return super(InventoryModule, self).verify_file(path)
|
||||
|
||||
def parse(self, inventory, loader, path, cache=None):
|
||||
super(InventoryModule, self).parse(inventory, loader, path)
|
||||
self._read_config_data(path)
|
||||
|
||||
try:
|
||||
self.exercise_test_cache()
|
||||
except AnsibleError:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise AnsibleError("Failed to run cache tests: {0}".format(e)) from e
|
||||
|
||||
def exercise_test_cache(self):
|
||||
failed = []
|
||||
for test_name in self.test_cache_methods:
|
||||
try:
|
||||
getattr(self, test_name)()
|
||||
except AssertionError:
|
||||
failed.append(test_name)
|
||||
finally:
|
||||
self.cache.flush()
|
||||
self.cache.update_cache_if_changed()
|
||||
|
||||
if failed:
|
||||
raise AnsibleError(f"Cache tests failed: {', '.join(failed)}")
|
||||
|
||||
def test_equal(self, a, b):
|
||||
try:
|
||||
assert a == b
|
||||
except AssertionError:
|
||||
display.warning(f"Assertion {a} == {b} failed")
|
||||
raise
|
||||
|
||||
def test_plugin_name(self):
|
||||
self.test_equal(self.cache._plugin_name, self.get_option('cache_plugin'))
|
||||
|
||||
def test_update_cache_if_changed(self):
|
||||
self.cache._retrieved = {}
|
||||
self.cache._cache = {'foo': 'bar'}
|
||||
|
||||
self.cache.update_cache_if_changed()
|
||||
|
||||
self.test_equal(self.cache._retrieved, {'foo': 'bar'})
|
||||
self.test_equal(self.cache._cache, {'foo': 'bar'})
|
||||
|
||||
def test_set_cache(self):
|
||||
cache_key1 = 'key1'
|
||||
cache1 = {'hosts': {'h1': {'foo': 'bar'}}}
|
||||
cache_key2 = 'key2'
|
||||
cache2 = {'hosts': {'h2': {}}}
|
||||
|
||||
self.cache._cache = {cache_key1: cache1, cache_key2: cache2}
|
||||
self.cache.set_cache()
|
||||
|
||||
self.test_equal(self.cache._plugin.contains(cache_key1), True)
|
||||
self.test_equal(self.cache._plugin.get(cache_key1), cache1)
|
||||
self.test_equal(self.cache._plugin.contains(cache_key2), True)
|
||||
self.test_equal(self.cache._plugin.get(cache_key2), cache2)
|
||||
|
||||
def test_load_whole_cache(self):
|
||||
cache_data = {
|
||||
'key1': {'hosts': {'h1': {'foo': 'bar'}}},
|
||||
'key2': {'hosts': {'h2': {}}},
|
||||
}
|
||||
self.cache._cache = cache_data
|
||||
self.cache.set_cache()
|
||||
self.cache._cache = {}
|
||||
|
||||
self.cache.load_whole_cache()
|
||||
self.test_equal(self.cache._cache, cache_data)
|
||||
|
||||
def test_iter(self):
|
||||
cache_data = {
|
||||
'key1': {'hosts': {'h1': {'foo': 'bar'}}},
|
||||
'key2': {'hosts': {'h2': {}}},
|
||||
}
|
||||
self.cache._cache = cache_data
|
||||
self.test_equal(sorted(list(self.cache)), ['key1', 'key2'])
|
||||
|
||||
def test_len(self):
|
||||
cache_data = {
|
||||
'key1': {'hosts': {'h1': {'foo': 'bar'}}},
|
||||
'key2': {'hosts': {'h2': {}}},
|
||||
}
|
||||
self.cache._cache = cache_data
|
||||
self.test_equal(len(self.cache), 2)
|
||||
|
||||
def test_get_missing_key(self):
|
||||
# cache should behave like a dictionary
|
||||
# a missing key with __getitem__ should raise a KeyError
|
||||
try:
|
||||
self.cache['keyerror']
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
assert False
|
||||
|
||||
# get should return the default instead
|
||||
self.test_equal(self.cache.get('missing'), None)
|
||||
self.test_equal(self.cache.get('missing', 'default'), 'default')
|
||||
|
||||
def _setup_expired(self):
|
||||
self.cache._cache = {'expired': True}
|
||||
self.cache.set_cache()
|
||||
|
||||
# empty the in-memory info to test loading the key
|
||||
# keys that expire mid-use do not cause errors
|
||||
self.cache._cache = {}
|
||||
self.cache._retrieved = {}
|
||||
self.cache._plugin._cache = {}
|
||||
|
||||
self.cache._plugin.set_option('timeout', 1)
|
||||
self.cache._plugin._timeout = 1
|
||||
sleep(2)
|
||||
|
||||
def _cleanup_expired(self):
|
||||
# Set cache timeout back to never
|
||||
self.cache._plugin.set_option('timeout', 0)
|
||||
self.cache._plugin._timeout = 0
|
||||
|
||||
def test_get_expired_key(self):
|
||||
if not hasattr(self.cache._plugin, '_timeout'):
|
||||
# DB-backed caches do not have a standard timeout interface
|
||||
return
|
||||
|
||||
self._setup_expired()
|
||||
try:
|
||||
self.cache['expired']
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
assert False
|
||||
finally:
|
||||
self._cleanup_expired()
|
||||
|
||||
self._setup_expired()
|
||||
try:
|
||||
self.test_equal(self.cache.get('expired'), None)
|
||||
self.test_equal(self.cache.get('expired', 'default'), 'default')
|
||||
finally:
|
||||
self._cleanup_expired()
|
||||
|
||||
def test_get(self):
|
||||
# test cache behaves like a dictionary
|
||||
|
||||
# set the cache to test getting a key that exists
|
||||
k1 = {'hosts': {'h1': {'foo': 'bar'}}}
|
||||
k2 = {'hosts': {'h2': {}}}
|
||||
self.cache._cache = {'key1': k1, 'key2': k2}
|
||||
self.cache.set_cache()
|
||||
|
||||
self.test_equal(self.cache['key1'], k1)
|
||||
self.test_equal(self.cache.get('key1'), k1)
|
||||
|
||||
# empty the in-memory info to test loading the key from the plugin
|
||||
self.cache._cache = {}
|
||||
self.cache._retrieved = {}
|
||||
self.cache._plugin._cache = {}
|
||||
|
||||
self.test_equal(self.cache['key1'], k1)
|
||||
self.test_equal(self.cache.get('key1'), k1)
|
||||
|
||||
def test_items(self):
|
||||
self.test_equal(self.cache.items(), {}.items())
|
||||
|
||||
test_items = {'hosts': {'host1': {'foo': 'bar'}}}
|
||||
self.cache._cache = test_items
|
||||
self.test_equal(self.cache.items(), test_items.items())
|
||||
|
||||
def test_keys(self):
|
||||
self.test_equal(self.cache.keys(), {}.keys())
|
||||
|
||||
test_items = {'hosts': {'host1': {'foo': 'bar'}}}
|
||||
self.cache._cache = test_items
|
||||
self.test_equal(self.cache.keys(), test_items.keys())
|
||||
|
||||
def test_values(self):
|
||||
self.test_equal(list(self.cache.values()), list({}.values()))
|
||||
|
||||
test_items = {'hosts': {'host1': {'foo': 'bar'}}}
|
||||
self.cache._cache = test_items
|
||||
self.test_equal(list(self.cache.values()), list(test_items.values()))
|
||||
|
||||
def test_pop(self):
|
||||
try:
|
||||
self.cache.pop('missing')
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
assert False
|
||||
|
||||
self.test_equal(self.cache.pop('missing', 'default'), 'default')
|
||||
|
||||
self.cache._cache = {'cache_key': 'cache'}
|
||||
self.test_equal(self.cache.pop('cache_key'), 'cache')
|
||||
|
||||
# test backing plugin cache isn't modified
|
||||
cache_key1 = 'key1'
|
||||
cache1 = {'hosts': {'h1': {'foo': 'bar'}}}
|
||||
cache_key2 = 'key2'
|
||||
cache2 = {'hosts': {'h2': {}}}
|
||||
|
||||
self.cache._cache = {cache_key1: cache1, cache_key2: cache2}
|
||||
self.cache.set_cache()
|
||||
|
||||
self.test_equal(self.cache.pop('key1'), cache1)
|
||||
self.test_equal(self.cache._cache, {cache_key2: cache2})
|
||||
self.test_equal(self.cache._plugin._cache, {cache_key1: cache1, cache_key2: cache2})
|
||||
|
||||
def test_del(self):
|
||||
try:
|
||||
del self.cache['missing']
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
assert False
|
||||
|
||||
cache_key1 = 'key1'
|
||||
cache1 = {'hosts': {'h1': {'foo': 'bar'}}}
|
||||
cache_key2 = 'key2'
|
||||
cache2 = {'hosts': {'h2': {}}}
|
||||
|
||||
self.cache._cache = {cache_key1: cache1, cache_key2: cache2}
|
||||
self.cache.set_cache()
|
||||
|
||||
del self.cache['key1']
|
||||
|
||||
self.test_equal(self.cache._cache, {cache_key2: cache2})
|
||||
self.test_equal(self.cache._plugin._cache, {cache_key1: cache1, cache_key2: cache2})
|
||||
|
||||
def test_set(self):
|
||||
cache_key = 'key1'
|
||||
hosts = {'hosts': {'h1': {'foo': 'bar'}}}
|
||||
self.cache[cache_key] = hosts
|
||||
|
||||
self.test_equal(self.cache._cache, {cache_key: hosts})
|
||||
self.test_equal(self.cache._plugin._cache, {})
|
||||
|
||||
def test_update(self):
|
||||
cache_key1 = 'key1'
|
||||
cache1 = {'hosts': {'h1': {'foo': 'bar'}}}
|
||||
cache_key2 = 'key2'
|
||||
cache2 = {'hosts': {'h2': {}}}
|
||||
|
||||
self.cache._cache = {cache_key1: cache1}
|
||||
self.cache.update({cache_key2: cache2})
|
||||
self.test_equal(self.cache._cache, {cache_key1: cache1, cache_key2: cache2})
|
||||
|
||||
def test_flush(self):
|
||||
cache_key1 = 'key1'
|
||||
cache1 = {'hosts': {'h1': {'foo': 'bar'}}}
|
||||
cache_key2 = 'key2'
|
||||
cache2 = {'hosts': {'h2': {}}}
|
||||
|
||||
self.cache._cache = {cache_key1: cache1, cache_key2: cache2}
|
||||
self.cache.set_cache()
|
||||
|
||||
# Unlike the dict write methods, cache.flush() flushes the backing plugin
|
||||
self.cache.flush()
|
||||
|
||||
self.test_equal(self.cache._cache, {})
|
||||
self.test_equal(self.cache._plugin._cache, {})
|
@ -1,63 +0,0 @@
|
||||
# (c) 2014, Brian Coca, Josh Drake, et al
|
||||
# (c) 2017 Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
# Make coding more python3-ish
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
cache: jsonfile
|
||||
short_description: JSON formatted files.
|
||||
description:
|
||||
- This cache uses JSON formatted, per host, files saved to the filesystem.
|
||||
version_added: "1.9"
|
||||
author: Ansible Core (@ansible-core)
|
||||
options:
|
||||
_uri:
|
||||
required: True
|
||||
description:
|
||||
- Path in which the cache plugin will save the JSON files
|
||||
env:
|
||||
- name: ANSIBLE_CACHE_PLUGIN_CONNECTION
|
||||
ini:
|
||||
- key: fact_caching_connection
|
||||
section: defaults
|
||||
_prefix:
|
||||
description: User defined prefix to use when creating the JSON files
|
||||
env:
|
||||
- name: ANSIBLE_CACHE_PLUGIN_PREFIX
|
||||
ini:
|
||||
- key: fact_caching_prefix
|
||||
section: defaults
|
||||
_timeout:
|
||||
default: 86400
|
||||
description: Expiration timeout in seconds for the cache plugin data. Set to 0 to never expire
|
||||
env:
|
||||
- name: ANSIBLE_CACHE_PLUGIN_TIMEOUT
|
||||
ini:
|
||||
- key: fact_caching_timeout
|
||||
section: defaults
|
||||
type: integer
|
||||
'''
|
||||
|
||||
import codecs
|
||||
import json
|
||||
|
||||
from ansible.parsing.ajson import AnsibleJSONEncoder, AnsibleJSONDecoder
|
||||
from ansible.plugins.cache import BaseFileCacheModule
|
||||
|
||||
|
||||
class CacheModule(BaseFileCacheModule):
|
||||
"""
|
||||
A caching module backed by json files.
|
||||
"""
|
||||
|
||||
def _load(self, filepath):
|
||||
# Valid JSON is always UTF-8 encoded.
|
||||
with codecs.open(filepath, 'r', encoding='utf-8') as f:
|
||||
return json.load(f, cls=AnsibleJSONDecoder)
|
||||
|
||||
def _dump(self, value, filepath):
|
||||
with codecs.open(filepath, 'w', encoding='utf-8') as f:
|
||||
f.write(json.dumps(value, cls=AnsibleJSONEncoder, sort_keys=True, indent=4))
|
Loading…
Reference in New Issue