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
backend_argument = sentinels:192.0.2.1:6379,192.0.2.2:6379,192.0.2.3:6379
backend_argument = service_name:myservice

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:
Takashi Kajinami 2024-02-01 00:58:06 +09:00
parent 9ddb92c62c
commit 8f1042cb39
4 changed files with 89 additions and 1 deletions

View File

@ -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'],

View File

@ -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 dogpile.cache
@ -100,6 +101,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 ValueError('Malformed sentinel host format')
def _build_cache_config(conf):
"""Build the cache region dictionary configuration.
@ -133,6 +146,19 @@ def _build_cache_config(conf):
in ('dogpile.cache.memcached', 'oslo_cache.memcache_pool') and
argname == 'url'):
argvalue = argvalue.split(',')
if argname == 'sentinels':
if conf.cache.backend != 'dogpile.cache.redis_sentinel':
raise exception.ConfigurationError(
'sentinels in backend arguments is supported only by '
'the %s backend' % conf.cache.backend)
try:
argvalue = [_parse_sentinel(sentinel) for sentinel
in argvalue.split(',')]
except ValueError:
raise exception.ConfigurationError(
'Backend arguments contain malformed sentinels argument')
conf_dict[arg_key] = argvalue
_LOG.debug('Oslo Cache Config: %s', conf_dict)
@ -206,7 +232,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 "
@ -229,6 +256,9 @@ def _build_cache_config(conf):
'ssl_keyfile': conf.cache.tls_keyfile
})
conf_dict['%s.arguments.client_kwargs' % prefix] = client_kwargs
if conf.cache.backend == 'dogpile.cache.redis_sentinel':
conf_dict['%s.arguments.sentinel_kwargs' % prefix] = \
client_kwargs
else:
msg = _(
"TLS setting via [cache] tls_enabled is not supported by this "

View File

@ -357,6 +357,39 @@ class CacheRegionTest(test_cache.BaseTestCase):
'ssl_certfile': 'path_to_cert_file'
},
config_dict['test_prefix.arguments.client_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.client_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.client_kwargs'])
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):
@ -693,6 +726,26 @@ class CacheRegionTest(test_cache.BaseTestCase):
self.assertFalse(config_dict['test_prefix.arguments'
'.pool_flush_on_reconnect'])
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',
backend_argument=[
('sentinels:127.0.0.1:26379,'
'[::1]:26379,localhost:26379')
])
config_dict = cache._build_cache_config(self.config_fixture.conf)
self.assertFalse(self.config_fixture.conf.cache.tls_enabled)
self.assertEqual([
('127.0.0.1', 26379),
('::1', 26379),
('localhost', 26379),
], config_dict['test_prefix.arguments.sentinels'])
def test_cache_debug_proxy(self):
single_value = 'Test Value'
single_key = 'testkey'

View File

@ -0,0 +1,4 @@
---
features:
- |
Now Redis Sentinel is supported as a cache backend.