summaryrefslogtreecommitdiff
path: root/lib/ansible/plugins/cache/memcached.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ansible/plugins/cache/memcached.py')
-rw-r--r--lib/ansible/plugins/cache/memcached.py243
1 files changed, 0 insertions, 243 deletions
diff --git a/lib/ansible/plugins/cache/memcached.py b/lib/ansible/plugins/cache/memcached.py
deleted file mode 100644
index 35dc7bf885..0000000000
--- a/lib/ansible/plugins/cache/memcached.py
+++ /dev/null
@@ -1,243 +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)
-
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
-
-DOCUMENTATION = '''
- cache: memcached
- short_description: Use memcached DB for cache
- description:
- - This cache uses JSON formatted, per host records saved in memcached.
- version_added: "1.9"
- requirements:
- - memcache (python lib)
- options:
- _uri:
- description:
- - List of connection information for the memcached DBs
- default: ['127.0.0.1:11211']
- type: list
- env:
- - name: ANSIBLE_CACHE_PLUGIN_CONNECTION
- ini:
- - key: fact_caching_connection
- section: defaults
- _prefix:
- description: User defined prefix to use when creating the DB entries
- default: ansible_facts
- 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 collections
-import os
-import time
-from multiprocessing import Lock
-from itertools import chain
-
-from ansible import constants as C
-from ansible.errors import AnsibleError
-from ansible.module_utils.common._collections_compat import MutableSet
-from ansible.plugins.cache import BaseCacheModule
-from ansible.utils.display import Display
-
-try:
- import memcache
-except ImportError:
- raise AnsibleError("python-memcached is required for the memcached fact cache")
-
-display = Display()
-
-
-class ProxyClientPool(object):
- """
- Memcached connection pooling for thread/fork safety. Inspired by py-redis
- connection pool.
-
- Available connections are maintained in a deque and released in a FIFO manner.
- """
-
- def __init__(self, *args, **kwargs):
- self.max_connections = kwargs.pop('max_connections', 1024)
- self.connection_args = args
- self.connection_kwargs = kwargs
- self.reset()
-
- def reset(self):
- self.pid = os.getpid()
- self._num_connections = 0
- self._available_connections = collections.deque(maxlen=self.max_connections)
- self._locked_connections = set()
- self._lock = Lock()
-
- def _check_safe(self):
- if self.pid != os.getpid():
- with self._lock:
- if self.pid == os.getpid():
- # bail out - another thread already acquired the lock
- return
- self.disconnect_all()
- self.reset()
-
- def get_connection(self):
- self._check_safe()
- try:
- connection = self._available_connections.popleft()
- except IndexError:
- connection = self.create_connection()
- self._locked_connections.add(connection)
- return connection
-
- def create_connection(self):
- if self._num_connections >= self.max_connections:
- raise RuntimeError("Too many memcached connections")
- self._num_connections += 1
- return memcache.Client(*self.connection_args, **self.connection_kwargs)
-
- def release_connection(self, connection):
- self._check_safe()
- self._locked_connections.remove(connection)
- self._available_connections.append(connection)
-
- def disconnect_all(self):
- for conn in chain(self._available_connections, self._locked_connections):
- conn.disconnect_all()
-
- def __getattr__(self, name):
- def wrapped(*args, **kwargs):
- return self._proxy_client(name, *args, **kwargs)
- return wrapped
-
- def _proxy_client(self, name, *args, **kwargs):
- conn = self.get_connection()
-
- try:
- return getattr(conn, name)(*args, **kwargs)
- finally:
- self.release_connection(conn)
-
-
-class CacheModuleKeys(MutableSet):
- """
- A set subclass that keeps track of insertion time and persists
- the set in memcached.
- """
- PREFIX = 'ansible_cache_keys'
-
- def __init__(self, cache, *args, **kwargs):
- self._cache = cache
- self._keyset = dict(*args, **kwargs)
-
- def __contains__(self, key):
- return key in self._keyset
-
- def __iter__(self):
- return iter(self._keyset)
-
- def __len__(self):
- return len(self._keyset)
-
- def add(self, key):
- self._keyset[key] = time.time()
- self._cache.set(self.PREFIX, self._keyset)
-
- def discard(self, key):
- del self._keyset[key]
- self._cache.set(self.PREFIX, self._keyset)
-
- def remove_by_timerange(self, s_min, s_max):
- for k in self._keyset.keys():
- t = self._keyset[k]
- if s_min < t < s_max:
- del self._keyset[k]
- self._cache.set(self.PREFIX, self._keyset)
-
-
-class CacheModule(BaseCacheModule):
-
- def __init__(self, *args, **kwargs):
- connection = ['127.0.0.1:11211']
-
- try:
- super(CacheModule, self).__init__(*args, **kwargs)
- if self.get_option('_uri'):
- connection = self.get_option('_uri')
- self._timeout = self.get_option('_timeout')
- self._prefix = self.get_option('_prefix')
- except KeyError:
- display.deprecated('Rather than importing CacheModules directly, '
- 'use ansible.plugins.loader.cache_loader', version='2.12')
- if C.CACHE_PLUGIN_CONNECTION:
- connection = C.CACHE_PLUGIN_CONNECTION.split(',')
- self._timeout = C.CACHE_PLUGIN_TIMEOUT
- self._prefix = C.CACHE_PLUGIN_PREFIX
-
- self._cache = {}
- self._db = ProxyClientPool(connection, debug=0)
- self._keys = CacheModuleKeys(self._db, self._db.get(CacheModuleKeys.PREFIX) or [])
-
- def _make_key(self, key):
- return "{0}{1}".format(self._prefix, key)
-
- def _expire_keys(self):
- if self._timeout > 0:
- expiry_age = time.time() - self._timeout
- self._keys.remove_by_timerange(0, expiry_age)
-
- def get(self, key):
- if key not in self._cache:
- value = self._db.get(self._make_key(key))
- # guard against the key not being removed from the keyset;
- # this could happen in cases where the timeout value is changed
- # between invocations
- if value is None:
- self.delete(key)
- raise KeyError
- self._cache[key] = value
-
- return self._cache.get(key)
-
- def set(self, key, value):
- self._db.set(self._make_key(key), value, time=self._timeout, min_compress_len=1)
- self._cache[key] = value
- self._keys.add(key)
-
- def keys(self):
- self._expire_keys()
- return list(iter(self._keys))
-
- def contains(self, key):
- self._expire_keys()
- return key in self._keys
-
- def delete(self, key):
- del self._cache[key]
- self._db.delete(self._make_key(key))
- self._keys.discard(key)
-
- def flush(self):
- for key in self.keys():
- self.delete(key)
-
- def copy(self):
- return self._keys.copy()
-
- def __getstate__(self):
- return dict()
-
- def __setstate__(self, data):
- self.__init__()