Add support for Redis Sentinel backend
This introduces support for Redis Sentinel backend. Users can now use Redis Sentinel backend instead of Redis backend by configurations like the example below. [cache] enabled = True backend = dogpile.cache.redis_sentinel redis_password = <password> redis_sentinels = 192.0.2.1:26379,192.0.2.2:26379,192.0.2.3:26379 If tls_enabled option is set to True then all the tls settings are applied for connections to Redis as well as connections to Redis Sentinel. Change-Id: Ic3b84fe6810e08337a884c68625ccfed11665269
This commit is contained in:
parent
28411250da
commit
7bb43bbbd5
@ -44,6 +44,7 @@ FILE_OPTIONS = {
|
||||
'dogpile.cache.bmemcached',
|
||||
'dogpile.cache.dbm',
|
||||
'dogpile.cache.redis',
|
||||
'dogpile.cache.redis_sentinel',
|
||||
'dogpile.cache.memory',
|
||||
'dogpile.cache.memory_pickle',
|
||||
'dogpile.cache.null'],
|
||||
@ -136,10 +137,18 @@ FILE_OPTIONS = {
|
||||
cfg.StrOpt('redis_password',
|
||||
secret=True,
|
||||
help='the password for redis'),
|
||||
cfg.ListOpt('redis_sentinels',
|
||||
default=['localhost:26379'],
|
||||
help='Redis sentinel servers in the format of '
|
||||
'"host:port"'),
|
||||
cfg.FloatOpt('redis_socket_timeout',
|
||||
default=1.0,
|
||||
help='Timeout in seconds for every call to a server.'
|
||||
' (dogpile.cache.redis backend only).'),
|
||||
' (dogpile.cache.redis and dogpile.cache.redis_sentinel '
|
||||
'backends only).'),
|
||||
cfg.StrOpt('redis_sentinel_service_name',
|
||||
default='mymaster',
|
||||
help='Service name of the redis sentinel cluster.'),
|
||||
cfg.BoolOpt('tls_enabled',
|
||||
default=False,
|
||||
help='Global toggle for TLS usage when communicating with'
|
||||
|
@ -34,6 +34,7 @@ The library has special public value for nonexistent or expired keys called
|
||||
from oslo_cache import core
|
||||
NO_VALUE = core.NO_VALUE
|
||||
"""
|
||||
import re
|
||||
import ssl
|
||||
import urllib.parse
|
||||
|
||||
@ -101,6 +102,18 @@ class _DebugProxy(proxy.ProxyBackend):
|
||||
self.proxied.delete_multi(keys)
|
||||
|
||||
|
||||
def _parse_sentinel(sentinel):
|
||||
# IPv6 (eg. [::1]:6379 )
|
||||
match = re.search(r'\[(\S+)\]:(\d+)', sentinel)
|
||||
if match:
|
||||
return (match[1], int(match[2]))
|
||||
# IPv4 or hostname (eg. 127.0.0.1:6379 or localhost:6379)
|
||||
match = re.search(r'(\S+):(\d+)', sentinel)
|
||||
if match:
|
||||
return (match[1], int(match[2]))
|
||||
raise exception.ConfigurationError('Malformed sentinel server format')
|
||||
|
||||
|
||||
def _build_cache_config(conf):
|
||||
"""Build the cache region dictionary configuration.
|
||||
|
||||
@ -161,6 +174,22 @@ def _build_cache_config(conf):
|
||||
for arg in ('socket_timeout',):
|
||||
value = getattr(conf.cache, 'redis_' + arg)
|
||||
conf_dict['%s.arguments.%s' % (prefix, arg)] = value
|
||||
elif conf.cache.backend == 'dogpile.cache.redis_sentinel':
|
||||
for arg in ('password', 'socket_timeout'):
|
||||
value = getattr(conf.cache, 'redis_' + arg)
|
||||
conf_dict['%s.arguments.%s' % (prefix, arg)] = value
|
||||
if conf.cache.redis_username:
|
||||
# TODO(tkajinam): Update dogpile.cache to add username argument,
|
||||
# similarly to password.
|
||||
conf_dict['%s.arguments.connection_kwargs' % prefix] = \
|
||||
{'username': conf.cache.redis_username}
|
||||
conf_dict['%s.arguments.sentinel_kwargs' % prefix] = \
|
||||
{'username': conf.cache.redis_username}
|
||||
conf_dict['%s.arguments.service_name' % prefix] = \
|
||||
conf.cache.redis_sentinel_service_name
|
||||
if conf.cache.redis_sentinels:
|
||||
conf_dict['%s.arguments.sentinels' % prefix] = [
|
||||
_parse_sentinel(s) for s in conf.cache.redis_sentinels]
|
||||
else:
|
||||
# NOTE(yorik-sar): these arguments will be used for memcache-related
|
||||
# backends. Use setdefault for url to support old-style setting through
|
||||
@ -233,7 +262,8 @@ def _build_cache_config(conf):
|
||||
tls_context.set_ciphers(conf.cache.tls_allowed_ciphers)
|
||||
|
||||
conf_dict['%s.arguments.tls_context' % prefix] = tls_context
|
||||
elif conf.cache.backend in ('dogpile.cache.redis',):
|
||||
elif conf.cache.backend in ('dogpile.cache.redis',
|
||||
'dogpile.cache.redis_sentinel'):
|
||||
if conf.cache.tls_allowed_ciphers is not None:
|
||||
raise exception.ConfigurationError(
|
||||
"Limiting allowed ciphers is not supported by "
|
||||
@ -255,7 +285,18 @@ def _build_cache_config(conf):
|
||||
'ssl_certfile': conf.cache.tls_certfile,
|
||||
'ssl_keyfile': conf.cache.tls_keyfile
|
||||
})
|
||||
conf_dict['%s.arguments.connection_kwargs' % prefix] = conn_kwargs
|
||||
if conf.cache.backend == 'dogpile.cache.redis_sentinel':
|
||||
conn_kwargs.update({'ssl': True})
|
||||
conf_dict.setdefault(
|
||||
'%s.arguments.connection_kwargs' % prefix,
|
||||
{}).update(conn_kwargs)
|
||||
conf_dict.setdefault(
|
||||
'%s.arguments.sentinel_kwargs' % prefix,
|
||||
{}).update(conn_kwargs)
|
||||
else:
|
||||
conf_dict.setdefault(
|
||||
'%s.arguments.connection_kwargs' % prefix,
|
||||
{}).update(conn_kwargs)
|
||||
else:
|
||||
msg = _(
|
||||
"TLS setting via [cache] tls_enabled is not supported by this "
|
||||
|
@ -313,6 +313,24 @@ class CacheRegionTest(test_cache.BaseTestCase):
|
||||
self.assertNotIn('test_prefix.arguments.connection_kwargs',
|
||||
config_dict)
|
||||
|
||||
def test_cache_dictionary_config_builder_tls_disabled_redis_sentinel(self):
|
||||
"""Validate the backend is reset to default if caching is disabled."""
|
||||
self.config_fixture.config(group='cache',
|
||||
enabled=True,
|
||||
config_prefix='test_prefix',
|
||||
backend='dogpile.cache.redis_sentinel',
|
||||
tls_cafile='path_to_ca_file',
|
||||
tls_keyfile='path_to_key_file',
|
||||
tls_certfile='path_to_cert_file')
|
||||
|
||||
config_dict = cache._build_cache_config(self.config_fixture.conf)
|
||||
|
||||
self.assertFalse(self.config_fixture.conf.cache.tls_enabled)
|
||||
self.assertNotIn('test_prefix.arguments.connection_kwargs',
|
||||
config_dict)
|
||||
self.assertNotIn('test_prefix.arguments.sentinel_kwargs',
|
||||
config_dict)
|
||||
|
||||
def test_cache_dictionary_config_builder_tls_enabled(self):
|
||||
"""Validate the backend is reset to default if caching is disabled."""
|
||||
self.config_fixture.config(group='cache',
|
||||
@ -363,6 +381,42 @@ class CacheRegionTest(test_cache.BaseTestCase):
|
||||
'ssl_certfile': 'path_to_cert_file'
|
||||
},
|
||||
config_dict['test_prefix.arguments.connection_kwargs'])
|
||||
self.assertNotIn('test_prefix.arguments.sentinel_kwargs', config_dict)
|
||||
|
||||
def test_cache_dictionary_config_builder_tls_enabled_redis_sentinel(self):
|
||||
"""Validate the backend is reset to default if caching is disabled."""
|
||||
self.config_fixture.config(group='cache',
|
||||
enabled=True,
|
||||
config_prefix='test_prefix',
|
||||
backend='dogpile.cache.redis_sentinel',
|
||||
tls_enabled=True,
|
||||
tls_cafile='path_to_ca_file',
|
||||
tls_keyfile='path_to_key_file',
|
||||
tls_certfile='path_to_cert_file')
|
||||
|
||||
config_dict = cache._build_cache_config(self.config_fixture.conf)
|
||||
|
||||
self.assertTrue(self.config_fixture.conf.cache.tls_enabled)
|
||||
self.assertIn('test_prefix.arguments.connection_kwargs',
|
||||
config_dict)
|
||||
self.assertEqual(
|
||||
{
|
||||
'ssl': True,
|
||||
'ssl_ca_certs': 'path_to_ca_file',
|
||||
'ssl_keyfile': 'path_to_key_file',
|
||||
'ssl_certfile': 'path_to_cert_file'
|
||||
},
|
||||
config_dict['test_prefix.arguments.connection_kwargs'])
|
||||
self.assertIn('test_prefix.arguments.sentinel_kwargs',
|
||||
config_dict)
|
||||
self.assertEqual(
|
||||
{
|
||||
'ssl': True,
|
||||
'ssl_ca_certs': 'path_to_ca_file',
|
||||
'ssl_keyfile': 'path_to_key_file',
|
||||
'ssl_certfile': 'path_to_cert_file'
|
||||
},
|
||||
config_dict['test_prefix.arguments.sentinel_kwargs'])
|
||||
|
||||
@mock.patch('oslo_cache.core._LOG')
|
||||
def test_cache_dictionary_config_builder_fips_mode_supported(self, log):
|
||||
@ -729,6 +783,62 @@ class CacheRegionTest(test_cache.BaseTestCase):
|
||||
'redis://user:secrete@[::1]:6379',
|
||||
config_dict['test_prefix.arguments.url'])
|
||||
|
||||
def test_cache_dictionary_config_builder_redis_sentinel(self):
|
||||
"""Validate the backend is reset to default if caching is disabled."""
|
||||
self.config_fixture.config(group='cache',
|
||||
enabled=True,
|
||||
config_prefix='test_prefix',
|
||||
backend='dogpile.cache.redis_sentinel')
|
||||
|
||||
config_dict = cache._build_cache_config(self.config_fixture.conf)
|
||||
|
||||
self.assertFalse(self.config_fixture.conf.cache.tls_enabled)
|
||||
self.assertEqual(
|
||||
'mymaster', config_dict['test_prefix.arguments.service_name'])
|
||||
self.assertEqual([
|
||||
('localhost', 26379)
|
||||
], config_dict['test_prefix.arguments.sentinels'])
|
||||
self.assertEqual(
|
||||
1.0, config_dict['test_prefix.arguments.socket_timeout'])
|
||||
self.assertNotIn('test_prefix.arguments.connection_kwargs',
|
||||
config_dict)
|
||||
self.assertNotIn('test_prefix.arguments.sentinel_kwargs',
|
||||
config_dict)
|
||||
|
||||
def test_cache_dictionary_config_builder_redis_sentinel_with_auth(self):
|
||||
"""Validate the backend is reset to default if caching is disabled."""
|
||||
self.config_fixture.config(group='cache',
|
||||
enabled=True,
|
||||
config_prefix='test_prefix',
|
||||
backend='dogpile.cache.redis_sentinel',
|
||||
redis_username='user',
|
||||
redis_password='secrete',
|
||||
redis_sentinels=[
|
||||
'127.0.0.1:26379',
|
||||
'[::1]:26379',
|
||||
'localhost:26379'
|
||||
],
|
||||
redis_sentinel_service_name='cluster')
|
||||
|
||||
config_dict = cache._build_cache_config(self.config_fixture.conf)
|
||||
|
||||
self.assertFalse(self.config_fixture.conf.cache.tls_enabled)
|
||||
self.assertEqual(
|
||||
'cluster', config_dict['test_prefix.arguments.service_name'])
|
||||
self.assertEqual([
|
||||
('127.0.0.1', 26379),
|
||||
('::1', 26379),
|
||||
('localhost', 26379),
|
||||
], config_dict['test_prefix.arguments.sentinels'])
|
||||
self.assertEqual(
|
||||
'secrete', config_dict['test_prefix.arguments.password'])
|
||||
self.assertEqual({
|
||||
'username': 'user'
|
||||
}, config_dict['test_prefix.arguments.connection_kwargs'])
|
||||
self.assertEqual({
|
||||
'username': 'user'
|
||||
}, config_dict['test_prefix.arguments.sentinel_kwargs'])
|
||||
|
||||
def test_cache_debug_proxy(self):
|
||||
single_value = 'Test Value'
|
||||
single_key = 'testkey'
|
||||
|
4
releasenotes/notes/redis-sentinel-18ba4a0da83dabc7.yaml
Normal file
4
releasenotes/notes/redis-sentinel-18ba4a0da83dabc7.yaml
Normal file
@ -0,0 +1,4 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Now Redis Sentinel is supported as a cache backend.
|
Loading…
Reference in New Issue
Block a user