128 lines
3.7 KiB
Python
128 lines
3.7 KiB
Python
# -*- encoding: utf-8 -*-
|
|
#
|
|
# Copyright © 2017 Red Hat
|
|
#
|
|
# 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.
|
|
|
|
from __future__ import absolute_import
|
|
|
|
from six.moves.urllib import parse
|
|
|
|
from oslo_utils import strutils
|
|
|
|
try:
|
|
import redis
|
|
from redis import sentinel
|
|
except ImportError:
|
|
redis = None
|
|
sentinel = None
|
|
|
|
|
|
CLIENT_ARGS = frozenset([
|
|
'db',
|
|
'encoding',
|
|
'retry_on_timeout',
|
|
'socket_keepalive',
|
|
'socket_timeout',
|
|
'ssl',
|
|
'ssl_certfile',
|
|
'ssl_keyfile',
|
|
'sentinel',
|
|
'sentinel_fallback',
|
|
])
|
|
"""
|
|
Keys that we allow to proxy from the coordinator configuration into the
|
|
redis client (used to configure the redis client internals so that
|
|
it works as you expect/want it to).
|
|
|
|
See: http://redis-py.readthedocs.org/en/latest/#redis.Redis
|
|
|
|
See: https://github.com/andymccurdy/redis-py/blob/2.10.3/redis/client.py
|
|
"""
|
|
|
|
#: Client arguments that are expected/allowed to be lists.
|
|
CLIENT_LIST_ARGS = frozenset([
|
|
'sentinel_fallback',
|
|
])
|
|
|
|
#: Client arguments that are expected to be boolean convertible.
|
|
CLIENT_BOOL_ARGS = frozenset([
|
|
'retry_on_timeout',
|
|
'ssl',
|
|
])
|
|
|
|
#: Client arguments that are expected to be int convertible.
|
|
CLIENT_INT_ARGS = frozenset([
|
|
'db',
|
|
'socket_keepalive',
|
|
'socket_timeout',
|
|
])
|
|
|
|
#: Default socket timeout to use when none is provided.
|
|
CLIENT_DEFAULT_SOCKET_TO = 30
|
|
|
|
|
|
def get_client(conf):
|
|
if redis is None:
|
|
raise RuntimeError("python-redis unavailable")
|
|
parsed_url = parse.urlparse(conf.redis_url)
|
|
options = parse.parse_qs(parsed_url.query)
|
|
|
|
kwargs = {}
|
|
if parsed_url.hostname:
|
|
kwargs['host'] = parsed_url.hostname
|
|
if parsed_url.port:
|
|
kwargs['port'] = parsed_url.port
|
|
else:
|
|
if not parsed_url.path:
|
|
raise ValueError("Expected socket path in parsed urls path")
|
|
kwargs['unix_socket_path'] = parsed_url.path
|
|
if parsed_url.password:
|
|
kwargs['password'] = parsed_url.password
|
|
|
|
for a in CLIENT_ARGS:
|
|
if a not in options:
|
|
continue
|
|
if a in CLIENT_BOOL_ARGS:
|
|
v = strutils.bool_from_string(options[a][-1])
|
|
elif a in CLIENT_LIST_ARGS:
|
|
v = options[a][-1]
|
|
elif a in CLIENT_INT_ARGS:
|
|
v = int(options[a][-1])
|
|
else:
|
|
v = options[a][-1]
|
|
kwargs[a] = v
|
|
if 'socket_timeout' not in kwargs:
|
|
kwargs['socket_timeout'] = CLIENT_DEFAULT_SOCKET_TO
|
|
|
|
# Ask the sentinel for the current master if there is a
|
|
# sentinel arg.
|
|
if 'sentinel' in kwargs:
|
|
sentinel_hosts = [
|
|
tuple(fallback.split(':'))
|
|
for fallback in kwargs.get('sentinel_fallback', [])
|
|
]
|
|
sentinel_hosts.insert(0, (kwargs['host'], kwargs['port']))
|
|
sentinel_server = sentinel.Sentinel(
|
|
sentinel_hosts,
|
|
socket_timeout=kwargs['socket_timeout'])
|
|
sentinel_name = kwargs['sentinel']
|
|
del kwargs['sentinel']
|
|
if 'sentinel_fallback' in kwargs:
|
|
del kwargs['sentinel_fallback']
|
|
master_client = sentinel_server.master_for(sentinel_name, **kwargs)
|
|
# The master_client is a redis.StrictRedis using a
|
|
# Sentinel managed connection pool.
|
|
return master_client
|
|
return redis.StrictRedis(**kwargs)
|