Merge "Copy cache package from oslo-incubator"
This commit is contained in:
commit
2fc7a6781d
0
neutron/openstack/common/cache/__init__.py
vendored
Normal file
0
neutron/openstack/common/cache/__init__.py
vendored
Normal file
0
neutron/openstack/common/cache/_backends/__init__.py
vendored
Normal file
0
neutron/openstack/common/cache/_backends/__init__.py
vendored
Normal file
165
neutron/openstack/common/cache/_backends/memory.py
vendored
Normal file
165
neutron/openstack/common/cache/_backends/memory.py
vendored
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
# Copyright 2013 Red Hat, Inc.
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
|
||||||
|
from neutron.openstack.common.cache import backends
|
||||||
|
from neutron.openstack.common import lockutils
|
||||||
|
from neutron.openstack.common import timeutils
|
||||||
|
|
||||||
|
|
||||||
|
class MemoryBackend(backends.BaseCache):
|
||||||
|
|
||||||
|
def __init__(self, parsed_url, options=None):
|
||||||
|
super(MemoryBackend, self).__init__(parsed_url, options)
|
||||||
|
self._clear()
|
||||||
|
|
||||||
|
def _set_unlocked(self, key, value, ttl=0):
|
||||||
|
expires_at = 0
|
||||||
|
if ttl != 0:
|
||||||
|
expires_at = timeutils.utcnow_ts() + ttl
|
||||||
|
|
||||||
|
self._cache[key] = (expires_at, value)
|
||||||
|
|
||||||
|
if expires_at:
|
||||||
|
self._keys_expires[expires_at].add(key)
|
||||||
|
|
||||||
|
def _set(self, key, value, ttl=0, not_exists=False):
|
||||||
|
with lockutils.lock(key):
|
||||||
|
|
||||||
|
# NOTE(flaper87): This is needed just in `set`
|
||||||
|
# calls, hence it's not in `_set_unlocked`
|
||||||
|
if not_exists and self._exists_unlocked(key):
|
||||||
|
return False
|
||||||
|
|
||||||
|
self._set_unlocked(key, value, ttl)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _get_unlocked(self, key, default=None):
|
||||||
|
now = timeutils.utcnow_ts()
|
||||||
|
|
||||||
|
try:
|
||||||
|
timeout, value = self._cache[key]
|
||||||
|
except KeyError:
|
||||||
|
return (0, default)
|
||||||
|
|
||||||
|
if timeout and now >= timeout:
|
||||||
|
|
||||||
|
# NOTE(flaper87): Record expired,
|
||||||
|
# remove it from the cache but catch
|
||||||
|
# KeyError and ValueError in case
|
||||||
|
# _purge_expired removed this key already.
|
||||||
|
try:
|
||||||
|
del self._cache[key]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
# NOTE(flaper87): Keys with ttl == 0
|
||||||
|
# don't exist in the _keys_expires dict
|
||||||
|
self._keys_expires[timeout].remove(key)
|
||||||
|
except (KeyError, ValueError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
return (0, default)
|
||||||
|
|
||||||
|
return (timeout, value)
|
||||||
|
|
||||||
|
def _get(self, key, default=None):
|
||||||
|
with lockutils.lock(key):
|
||||||
|
return self._get_unlocked(key, default)[1]
|
||||||
|
|
||||||
|
def _exists_unlocked(self, key):
|
||||||
|
now = timeutils.utcnow_ts()
|
||||||
|
try:
|
||||||
|
timeout = self._cache[key][0]
|
||||||
|
return not timeout or now <= timeout
|
||||||
|
except KeyError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def __contains__(self, key):
|
||||||
|
with lockutils.lock(key):
|
||||||
|
return self._exists_unlocked(key)
|
||||||
|
|
||||||
|
def _incr_append(self, key, other):
|
||||||
|
with lockutils.lock(key):
|
||||||
|
timeout, value = self._get_unlocked(key)
|
||||||
|
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
ttl = timeutils.utcnow_ts() - timeout
|
||||||
|
new_value = value + other
|
||||||
|
self._set_unlocked(key, new_value, ttl)
|
||||||
|
return new_value
|
||||||
|
|
||||||
|
def _incr(self, key, delta):
|
||||||
|
if not isinstance(delta, int):
|
||||||
|
raise TypeError('delta must be an int instance')
|
||||||
|
|
||||||
|
return self._incr_append(key, delta)
|
||||||
|
|
||||||
|
def _append_tail(self, key, tail):
|
||||||
|
return self._incr_append(key, tail)
|
||||||
|
|
||||||
|
def _purge_expired(self):
|
||||||
|
"""Removes expired keys from the cache."""
|
||||||
|
|
||||||
|
now = timeutils.utcnow_ts()
|
||||||
|
for timeout in sorted(self._keys_expires.keys()):
|
||||||
|
|
||||||
|
# NOTE(flaper87): If timeout is greater
|
||||||
|
# than `now`, stop the iteration, remaining
|
||||||
|
# keys have not expired.
|
||||||
|
if now < timeout:
|
||||||
|
break
|
||||||
|
|
||||||
|
# NOTE(flaper87): Unset every key in
|
||||||
|
# this set from the cache if its timeout
|
||||||
|
# is equal to `timeout`. (The key might
|
||||||
|
# have been updated)
|
||||||
|
for subkey in self._keys_expires.pop(timeout):
|
||||||
|
try:
|
||||||
|
if self._cache[subkey][0] == timeout:
|
||||||
|
del self._cache[subkey]
|
||||||
|
except KeyError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
def __delitem__(self, key):
|
||||||
|
self._purge_expired()
|
||||||
|
|
||||||
|
# NOTE(flaper87): Delete the key. Using pop
|
||||||
|
# since it could have been deleted already
|
||||||
|
value = self._cache.pop(key, None)
|
||||||
|
|
||||||
|
if value:
|
||||||
|
try:
|
||||||
|
# NOTE(flaper87): Keys with ttl == 0
|
||||||
|
# don't exist in the _keys_expires dict
|
||||||
|
self._keys_expires[value[0]].remove(value[1])
|
||||||
|
except (KeyError, ValueError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _clear(self):
|
||||||
|
self._cache = {}
|
||||||
|
self._keys_expires = collections.defaultdict(set)
|
||||||
|
|
||||||
|
def _get_many(self, keys, default):
|
||||||
|
return super(MemoryBackend, self)._get_many(keys, default)
|
||||||
|
|
||||||
|
def _set_many(self, data, ttl=0):
|
||||||
|
return super(MemoryBackend, self)._set_many(data, ttl)
|
||||||
|
|
||||||
|
def _unset_many(self, keys):
|
||||||
|
return super(MemoryBackend, self)._unset_many(keys)
|
263
neutron/openstack/common/cache/backends.py
vendored
Normal file
263
neutron/openstack/common/cache/backends.py
vendored
Normal file
@ -0,0 +1,263 @@
|
|||||||
|
# Copyright 2013 Red Hat, Inc.
|
||||||
|
#
|
||||||
|
# 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 abc
|
||||||
|
|
||||||
|
import six
|
||||||
|
|
||||||
|
|
||||||
|
NOTSET = object()
|
||||||
|
|
||||||
|
|
||||||
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
|
class BaseCache(object):
|
||||||
|
"""Base Cache Abstraction
|
||||||
|
|
||||||
|
:params parsed_url: Parsed url object.
|
||||||
|
:params options: A dictionary with configuration parameters
|
||||||
|
for the cache. For example:
|
||||||
|
- default_ttl: An integer defining the default ttl
|
||||||
|
for keys.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, parsed_url, options=None):
|
||||||
|
self._parsed_url = parsed_url
|
||||||
|
self._options = options or {}
|
||||||
|
self._default_ttl = int(self._options.get('default_ttl', 0))
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def _set(self, key, value, ttl, not_exists=False):
|
||||||
|
"""Implementations of this class have to override this method."""
|
||||||
|
|
||||||
|
def set(self, key, value, ttl, not_exists=False):
|
||||||
|
"""Sets or updates a cache entry
|
||||||
|
|
||||||
|
NOTE: Thread-safety is required and has to be
|
||||||
|
guaranteed by the backend implementation.
|
||||||
|
|
||||||
|
:params key: Item key as string.
|
||||||
|
:type key: `unicode string`
|
||||||
|
:params value: Value to assign to the key. This
|
||||||
|
can be anything that is handled
|
||||||
|
by the current backend.
|
||||||
|
:params ttl: Key's timeout in seconds. 0 means
|
||||||
|
no timeout.
|
||||||
|
:type ttl: int
|
||||||
|
:params not_exists: If True, the key will be set
|
||||||
|
if it doesn't exist. Otherwise,
|
||||||
|
it'll always be set.
|
||||||
|
:type not_exists: bool
|
||||||
|
|
||||||
|
:returns: True if the operation succeeds, False otherwise.
|
||||||
|
"""
|
||||||
|
if ttl is None:
|
||||||
|
ttl = self._default_ttl
|
||||||
|
|
||||||
|
return self._set(key, value, ttl, not_exists)
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
self.set(key, value, self._default_ttl)
|
||||||
|
|
||||||
|
def setdefault(self, key, value):
|
||||||
|
"""Sets the key value to `value` if it doesn't exist
|
||||||
|
|
||||||
|
:params key: Item key as string.
|
||||||
|
:type key: `unicode string`
|
||||||
|
:params value: Value to assign to the key. This
|
||||||
|
can be anything that is handled
|
||||||
|
by the current backend.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return self[key]
|
||||||
|
except KeyError:
|
||||||
|
self[key] = value
|
||||||
|
return value
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def _get(self, key, default):
|
||||||
|
"""Implementations of this class have to override this method."""
|
||||||
|
|
||||||
|
def get(self, key, default=None):
|
||||||
|
"""Gets one item from the cache
|
||||||
|
|
||||||
|
NOTE: Thread-safety is required and it has to be
|
||||||
|
guaranteed by the backend implementation.
|
||||||
|
|
||||||
|
:params key: Key for the item to retrieve
|
||||||
|
from the cache.
|
||||||
|
:params default: The default value to return.
|
||||||
|
|
||||||
|
:returns: `key`'s value in the cache if it exists,
|
||||||
|
otherwise `default` should be returned.
|
||||||
|
"""
|
||||||
|
return self._get(key, default)
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
value = self.get(key, NOTSET)
|
||||||
|
|
||||||
|
if value is NOTSET:
|
||||||
|
raise KeyError
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def __delitem__(self, key):
|
||||||
|
"""Removes an item from cache.
|
||||||
|
|
||||||
|
NOTE: Thread-safety is required and it has to be
|
||||||
|
guaranteed by the backend implementation.
|
||||||
|
|
||||||
|
:params key: The key to remove.
|
||||||
|
|
||||||
|
:returns: The key value if there's one
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def _clear(self):
|
||||||
|
"""Implementations of this class have to override this method."""
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
"""Removes all items from the cache.
|
||||||
|
|
||||||
|
NOTE: Thread-safety is required and it has to be
|
||||||
|
guaranteed by the backend implementation.
|
||||||
|
"""
|
||||||
|
return self._clear()
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def _incr(self, key, delta):
|
||||||
|
"""Implementations of this class have to override this method."""
|
||||||
|
|
||||||
|
def incr(self, key, delta=1):
|
||||||
|
"""Increments the value for a key
|
||||||
|
|
||||||
|
:params key: The key for the value to be incremented
|
||||||
|
:params delta: Number of units by which to increment
|
||||||
|
the value. Pass a negative number to
|
||||||
|
decrement the value.
|
||||||
|
|
||||||
|
:returns: The new value
|
||||||
|
"""
|
||||||
|
return self._incr(key, delta)
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def _append_tail(self, key, tail):
|
||||||
|
"""Implementations of this class have to override this method."""
|
||||||
|
|
||||||
|
def append_tail(self, key, tail):
|
||||||
|
"""Appends `tail` to `key`'s value.
|
||||||
|
|
||||||
|
:params key: The key of the value to which
|
||||||
|
`tail` should be appended.
|
||||||
|
:params tail: The list of values to append to the
|
||||||
|
original.
|
||||||
|
|
||||||
|
:returns: The new value
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not hasattr(tail, "__iter__"):
|
||||||
|
raise TypeError('Tail must be an iterable')
|
||||||
|
|
||||||
|
if not isinstance(tail, list):
|
||||||
|
# NOTE(flaper87): Make sure we pass a list
|
||||||
|
# down to the implementation. Not all drivers
|
||||||
|
# have support for generators, sets or other
|
||||||
|
# iterables.
|
||||||
|
tail = list(tail)
|
||||||
|
|
||||||
|
return self._append_tail(key, tail)
|
||||||
|
|
||||||
|
def append(self, key, value):
|
||||||
|
"""Appends `value` to `key`'s value.
|
||||||
|
|
||||||
|
:params key: The key of the value to which
|
||||||
|
`tail` should be appended.
|
||||||
|
:params value: The value to append to the
|
||||||
|
original.
|
||||||
|
|
||||||
|
:returns: The new value
|
||||||
|
"""
|
||||||
|
return self.append_tail(key, [value])
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def __contains__(self, key):
|
||||||
|
"""Verifies that a key exists.
|
||||||
|
|
||||||
|
:params key: The key to verify.
|
||||||
|
|
||||||
|
:returns: True if the key exists,
|
||||||
|
otherwise False.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def _get_many(self, keys, default):
|
||||||
|
"""Implementations of this class have to override this method."""
|
||||||
|
return ((k, self.get(k, default=default)) for k in keys)
|
||||||
|
|
||||||
|
def get_many(self, keys, default=NOTSET):
|
||||||
|
"""Gets keys' value from cache
|
||||||
|
|
||||||
|
:params keys: List of keys to retrieve.
|
||||||
|
:params default: The default value to return
|
||||||
|
for each key that is not in
|
||||||
|
the cache.
|
||||||
|
|
||||||
|
:returns: A generator of (key, value)
|
||||||
|
"""
|
||||||
|
return self._get_many(keys, default)
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def _set_many(self, data, ttl):
|
||||||
|
"""Implementations of this class have to override this method."""
|
||||||
|
|
||||||
|
for key, value in data.items():
|
||||||
|
self.set(key, value, ttl=ttl)
|
||||||
|
|
||||||
|
def set_many(self, data, ttl=None):
|
||||||
|
"""Puts several items into the cache at once
|
||||||
|
|
||||||
|
Depending on the backend, this operation may or may
|
||||||
|
not be efficient. The default implementation calls
|
||||||
|
set for each (key, value) pair passed, other backends
|
||||||
|
support set_many operations as part of their protocols.
|
||||||
|
|
||||||
|
:params data: A dictionary like {key: val} to store
|
||||||
|
in the cache.
|
||||||
|
:params ttl: Key's timeout in seconds.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if ttl is None:
|
||||||
|
ttl = self._default_ttl
|
||||||
|
|
||||||
|
self._set_many(data, ttl)
|
||||||
|
|
||||||
|
def update(self, **kwargs):
|
||||||
|
"""Sets several (key, value) paris.
|
||||||
|
|
||||||
|
Refer to the `set_many` docstring.
|
||||||
|
"""
|
||||||
|
self.set_many(kwargs, ttl=self._default_ttl)
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def _unset_many(self, keys):
|
||||||
|
"""Implementations of this class have to override this method."""
|
||||||
|
for key in keys:
|
||||||
|
del self[key]
|
||||||
|
|
||||||
|
def unset_many(self, keys):
|
||||||
|
"""Removes several keys from the cache at once
|
||||||
|
|
||||||
|
:params keys: List of keys to unset.
|
||||||
|
"""
|
||||||
|
self._unset_many(keys)
|
79
neutron/openstack/common/cache/cache.py
vendored
Normal file
79
neutron/openstack/common/cache/cache.py
vendored
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
# Copyright 2013 Red Hat, Inc.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""Cache library.
|
||||||
|
|
||||||
|
Supported configuration options:
|
||||||
|
|
||||||
|
`default_backend`: Name of the cache backend to use.
|
||||||
|
`key_namespace`: Namespace under which keys will be created.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from stevedore import driver
|
||||||
|
|
||||||
|
from neutron.openstack.common.py3kcompat import urlutils
|
||||||
|
|
||||||
|
|
||||||
|
def _get_olso_configs():
|
||||||
|
"""Returns the oslo.config options to register."""
|
||||||
|
# NOTE(flaper87): Oslo config should be
|
||||||
|
# optional. Instead of doing try / except
|
||||||
|
# at the top of this file, lets import cfg
|
||||||
|
# here and assume that the caller of this
|
||||||
|
# function already took care of this dependency.
|
||||||
|
from oslo.config import cfg
|
||||||
|
|
||||||
|
return [
|
||||||
|
cfg.StrOpt('cache_url', default='memory://',
|
||||||
|
help='URL to connect to the cache back end.')
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def register_oslo_configs(conf):
|
||||||
|
"""Registers a cache configuration options
|
||||||
|
|
||||||
|
:params conf: Config object.
|
||||||
|
:type conf: `cfg.ConfigOptions`
|
||||||
|
"""
|
||||||
|
conf.register_opts(_get_olso_configs())
|
||||||
|
|
||||||
|
|
||||||
|
def get_cache(url='memory://'):
|
||||||
|
"""Loads the cache backend
|
||||||
|
|
||||||
|
This function loads the cache backend
|
||||||
|
specified in the given configuration.
|
||||||
|
|
||||||
|
:param conf: Configuration instance to use
|
||||||
|
"""
|
||||||
|
|
||||||
|
parsed = urlutils.urlparse(url)
|
||||||
|
backend = parsed.scheme
|
||||||
|
|
||||||
|
query = parsed.query
|
||||||
|
# NOTE(flaper87): We need the following hack
|
||||||
|
# for python versions < 2.7.5. Previous versions
|
||||||
|
# of python parsed query params just for 'known'
|
||||||
|
# schemes. This was changed in this patch:
|
||||||
|
# http://hg.python.org/cpython/rev/79e6ff3d9afd
|
||||||
|
if not query and '?' in parsed.path:
|
||||||
|
query = parsed.path.split('?', 1)[-1]
|
||||||
|
parameters = urlutils.parse_qsl(query)
|
||||||
|
kwargs = {'options': dict(parameters)}
|
||||||
|
|
||||||
|
mgr = driver.DriverManager('neutron.openstack.common.cache.backends', backend,
|
||||||
|
invoke_on_load=True,
|
||||||
|
invoke_args=[parsed],
|
||||||
|
invoke_kwds=kwargs)
|
||||||
|
return mgr.driver
|
@ -1,5 +1,6 @@
|
|||||||
[DEFAULT]
|
[DEFAULT]
|
||||||
# The list of modules to copy from oslo-incubator.git
|
# The list of modules to copy from oslo-incubator.git
|
||||||
|
module=cache
|
||||||
module=context
|
module=context
|
||||||
module=db
|
module=db
|
||||||
module=db.sqlalchemy
|
module=db.sqlalchemy
|
||||||
|
@ -161,6 +161,8 @@ neutron.ml2.mechanism_drivers =
|
|||||||
cisco_nexus = neutron.plugins.ml2.drivers.cisco.nexus.mech_cisco_nexus:CiscoNexusMechanismDriver
|
cisco_nexus = neutron.plugins.ml2.drivers.cisco.nexus.mech_cisco_nexus:CiscoNexusMechanismDriver
|
||||||
l2population = neutron.plugins.ml2.drivers.l2pop.mech_driver:L2populationMechanismDriver
|
l2population = neutron.plugins.ml2.drivers.l2pop.mech_driver:L2populationMechanismDriver
|
||||||
bigswitch = neutron.plugins.ml2.drivers.mech_bigswitch.driver:BigSwitchMechanismDriver
|
bigswitch = neutron.plugins.ml2.drivers.mech_bigswitch.driver:BigSwitchMechanismDriver
|
||||||
|
neutron.openstack.common.cache.backends =
|
||||||
|
memory = neutron.openstack.common.cache._backends.memory:MemoryBackend
|
||||||
|
|
||||||
[build_sphinx]
|
[build_sphinx]
|
||||||
all_files = 1
|
all_files = 1
|
||||||
|
Loading…
Reference in New Issue
Block a user