Add configurable Redis connection retries and following of cluster redirections (#2045)

Closes #1969
This commit is contained in:
Martin Angers
2021-09-15 08:50:32 -04:00
committed by GitHub
parent 404ae820c9
commit 1fa5ce16b8
12 changed files with 386 additions and 66 deletions
+2
View File
@@ -0,0 +1,2 @@
* Add redis configuration option to retry failed connections.
* Add redis configuration option to follow cluster redirections.
+10 -10
View File
@@ -200,21 +200,21 @@ the way that the Fleet server works.
}
}
redisPool, err := redis.NewRedisPool(
config.Redis.Address,
config.Redis.Password,
config.Redis.Database,
config.Redis.UseTLS,
config.Redis.ConnectTimeout,
config.Redis.KeepAlive,
)
redisPool, err := redis.NewRedisPool(redis.PoolConfig{
Server: config.Redis.Address,
Password: config.Redis.Password,
Database: config.Redis.Database,
UseTLS: config.Redis.UseTLS,
ConnTimeout: config.Redis.ConnectTimeout,
KeepAlive: config.Redis.KeepAlive,
ConnectRetryAttempts: config.Redis.ConnectRetryAttempts,
ClusterFollowRedirections: config.Redis.ClusterFollowRedirections,
})
if err != nil {
initFatal(err, "initialize Redis")
}
resultStore := pubsub.NewRedisQueryResults(redisPool, config.Redis.DuplicateResults)
liveQueryStore := live_query.NewRedisLiveQuery(redisPool)
// TODO: should that only be done when a certain "migrate" flag is set,
// to prevent affecting every startup?
if err := liveQueryStore.MigrateKeys(); err != nil {
level.Info(logger).Log(
"err", err,
+34 -2
View File
@@ -289,7 +289,7 @@ Maximum idle connections to database. This value should be equal to or less than
max_idle_conns: 50
```
###### conn_max_lifetime
###### mysql_conn_max_lifetime
Maximum amount of time, in seconds, a connection may be reused.
@@ -358,7 +358,7 @@ Whether or not to duplicate Live Query results to another Redis channel named `L
###### redis_connect_timeout
Timeout for redis connection.
Timeout for redis connection.
- Default value: 5s
- Environment variable: `FLEET_REDIS_CONNECT_TIMEOUT`
@@ -382,6 +382,38 @@ Interval between keep alive probes.
keep_alive: 30s
```
###### redis_connect_retry_attempts
Maximum number of attempts to retry a failed connection to a redis node. Only
certain type of errors are retried, such as connection timeouts.
- Default value: 0 (no retry)
- Environment variable: `FLEET_REDIS_CONNECT_RETRY_ATTEMPTS`
- Config file format:
```
redis:
connect_retry_attempts: 2
```
###### redis_cluster_follow_redirections
Whether or not to automatically follow redirection errors received from the
Redis server. Applies only to Redis Cluster setups, ignored in standalone
Redis. In Redis Cluster, keys can be moved around to different nodes when the
cluster is unstable and reorganizing the data. With this configuration option
set to true, those (typically short and transient) redirection errors can be
handled transparently instead of ending in an error.
- Default value: false
- Environment variable: `FLEET_REDIS_CLUSTER_FOLLOW_REDIRECTIONS`
- Config file format:
```
redis:
cluster_follow_redirections: true
```
##### Server
###### server_address
+20 -14
View File
@@ -37,13 +37,15 @@ type MysqlConfig struct {
// RedisConfig defines configs related to Redis
type RedisConfig struct {
Address string
Password string
Database int
UseTLS bool `yaml:"use_tls"`
DuplicateResults bool `yaml:"duplicate_results"`
ConnectTimeout time.Duration `yaml:"connect_timeout"`
KeepAlive time.Duration `yaml:"keep_alive"`
Address string
Password string
Database int
UseTLS bool `yaml:"use_tls"`
DuplicateResults bool `yaml:"duplicate_results"`
ConnectTimeout time.Duration `yaml:"connect_timeout"`
KeepAlive time.Duration `yaml:"keep_alive"`
ConnectRetryAttempts int `yaml:"connect_retry_attempts"`
ClusterFollowRedirections bool `yaml:"cluster_follow_redirections"`
}
const (
@@ -243,6 +245,8 @@ func (man Manager) addConfigs() {
man.addConfigBool("redis.duplicate_results", false, "Duplicate Live Query results to another Redis channel")
man.addConfigDuration("redis.connect_timeout", 5*time.Second, "Timeout at connection time")
man.addConfigDuration("redis.keep_alive", 10*time.Second, "Interval between keep alive probes")
man.addConfigInt("redis.connect_retry_attempts", 0, "Number of attempts to retry a failed connection")
man.addConfigBool("redis.cluster_follow_redirections", false, "Automatically follow Redis Cluster redirections")
// Server
man.addConfigString("server.address", "0.0.0.0:8080",
@@ -417,13 +421,15 @@ func (man Manager) LoadConfig() FleetConfig {
Mysql: loadMysqlConfig("mysql"),
MysqlReadReplica: loadMysqlConfig("mysql_read_replica"),
Redis: RedisConfig{
Address: man.getConfigString("redis.address"),
Password: man.getConfigString("redis.password"),
Database: man.getConfigInt("redis.database"),
UseTLS: man.getConfigBool("redis.use_tls"),
DuplicateResults: man.getConfigBool("redis.duplicate_results"),
ConnectTimeout: man.getConfigDuration("redis.connect_timeout"),
KeepAlive: man.getConfigDuration("redis.keep_alive"),
Address: man.getConfigString("redis.address"),
Password: man.getConfigString("redis.password"),
Database: man.getConfigInt("redis.database"),
UseTLS: man.getConfigBool("redis.use_tls"),
DuplicateResults: man.getConfigBool("redis.duplicate_results"),
ConnectTimeout: man.getConfigDuration("redis.connect_timeout"),
KeepAlive: man.getConfigDuration("redis.keep_alive"),
ConnectRetryAttempts: man.getConfigInt("redis.connect_retry_attempts"),
ClusterFollowRedirections: man.getConfigBool("redis.cluster_follow_redirections"),
},
Server: ServerConfig{
Address: man.getConfigString("server.address"),
+94 -28
View File
@@ -1,9 +1,11 @@
package redis
import (
"net"
"strings"
"time"
"github.com/cenkalti/backoff/v4"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/gomodule/redigo/redis"
"github.com/mna/redisc"
@@ -17,29 +19,64 @@ type standalonePool struct {
addr string
}
func (p *standalonePool) ConfigureDoer(conn redis.Conn) redis.Conn {
return conn
}
func (p *standalonePool) Stats() map[string]redis.PoolStats {
return map[string]redis.PoolStats{
p.addr: p.Pool.Stats(),
}
}
type clusterPool struct {
*redisc.Cluster
followRedirs bool
}
// ConfigureDoer configures conn to follow redirections if the redis
// configuration requested it. If the conn is already in error, or
// if it is not a redisc cluster connection, it is returned unaltered.
func (p *clusterPool) ConfigureDoer(conn redis.Conn) redis.Conn {
if err := conn.Err(); err == nil && p.followRedirs {
rc, err := redisc.RetryConn(conn, 3, 300*time.Millisecond)
if err == nil {
return rc
}
}
return conn
}
// PoolConfig holds the redis pool configuration options.
type PoolConfig struct {
Server string
Password string
Database int
UseTLS bool
ConnTimeout time.Duration
KeepAlive time.Duration
ConnectRetryAttempts int
ClusterFollowRedirections bool
// allows for testing dial retries and other dial-related scenarios
testRedisDialFunc func(net, addr string, opts ...redis.DialOption) (redis.Conn, error)
}
// NewRedisPool creates a Redis connection pool using the provided server
// address, password and database.
func NewRedisPool(
server, password string, database int, useTLS bool, connTimeout, keepAlive time.Duration,
) (fleet.RedisPool, error) {
cluster := newCluster(server, password, database, useTLS, connTimeout, keepAlive)
func NewRedisPool(config PoolConfig) (fleet.RedisPool, error) {
cluster := newCluster(config)
if err := cluster.Refresh(); err != nil {
if isClusterDisabled(err) || isClusterCommandUnknown(err) {
// not a Redis Cluster setup, use a standalone Redis pool
pool, _ := cluster.CreatePool(server)
pool, _ := cluster.CreatePool(config.Server)
cluster.Close()
return &standalonePool{pool, server}, nil
return &standalonePool{pool, config.Server}, nil
}
return nil, errors.Wrap(err, "refresh cluster")
}
return cluster, nil
return &clusterPool{cluster, config.ClusterFollowRedirections}, nil
}
// SplitRedisKeysBySlot takes a list of redis keys and groups them by hash slot
@@ -49,7 +86,7 @@ func NewRedisPool(
// simply returns all keys in the same group (i.e. the top-level slice has a
// length of 1).
func SplitRedisKeysBySlot(pool fleet.RedisPool, keys ...string) [][]string {
if _, isCluster := pool.(*redisc.Cluster); isCluster {
if _, isCluster := pool.(*clusterPool); isCluster {
return redisc.SplitBySlot(keys...)
}
return [][]string{keys}
@@ -61,7 +98,7 @@ func SplitRedisKeysBySlot(pool fleet.RedisPool, keys ...string) [][]string {
// of nodes stops and EachRedisNode returns that error. For standalone redis,
// fn is called only once.
func EachRedisNode(pool fleet.RedisPool, fn func(conn redis.Conn) error) error {
if cluster, isCluster := pool.(*redisc.Cluster); isCluster {
if cluster, isCluster := pool.(*clusterPool); isCluster {
return cluster.EachNode(false, func(_ string, conn redis.Conn) error {
return fn(conn)
})
@@ -72,35 +109,64 @@ func EachRedisNode(pool fleet.RedisPool, fn func(conn redis.Conn) error) error {
return fn(conn)
}
func newCluster(server, password string, database int, useTLS bool, connTimeout, keepAlive time.Duration) *redisc.Cluster {
func newCluster(config PoolConfig) *redisc.Cluster {
opts := []redis.DialOption{
redis.DialDatabase(config.Database),
redis.DialUseTLS(config.UseTLS),
redis.DialConnectTimeout(config.ConnTimeout),
redis.DialKeepAlive(config.KeepAlive),
// Read/Write timeouts not set here because we may see results
// only rarely on the pub/sub channel.
}
if config.Password != "" {
opts = append(opts, redis.DialPassword(config.Password))
}
dialFn := redis.Dial
if config.testRedisDialFunc != nil {
dialFn = config.testRedisDialFunc
}
return &redisc.Cluster{
StartupNodes: []string{server},
CreatePool: func(server string, opts ...redis.DialOption) (*redis.Pool, error) {
StartupNodes: []string{config.Server},
CreatePool: func(server string, _ ...redis.DialOption) (*redis.Pool, error) {
return &redis.Pool{
MaxIdle: 3,
IdleTimeout: 240 * time.Second,
Dial: func() (redis.Conn, error) {
c, err := redis.Dial(
"tcp",
server,
redis.DialDatabase(database),
redis.DialUseTLS(useTLS),
redis.DialConnectTimeout(connTimeout),
redis.DialKeepAlive(keepAlive),
// Read/Write timeouts not set here because we may see results
// only rarely on the pub/sub channel.
)
if err != nil {
return nil, err
var conn redis.Conn
op := func() error {
c, err := dialFn("tcp", server, opts...)
var netErr net.Error
if errors.As(err, &netErr) {
if netErr.Temporary() || netErr.Timeout() {
// retryable error
return err
}
}
if err != nil {
// at this point, this is a non-retryable error
return backoff.Permanent(err)
}
// success, store the connection to use
conn = c
return nil
}
if password != "" {
if _, err := c.Do("AUTH", password); err != nil {
c.Close()
if config.ConnectRetryAttempts > 0 {
boff := backoff.WithMaxRetries(backoff.NewExponentialBackOff(), uint64(config.ConnectRetryAttempts))
if err := backoff.Retry(op, boff); err != nil {
return nil, err
}
} else if err := op(); err != nil {
return nil, err
}
return c, err
return conn, nil
},
TestOnBorrow: func(c redis.Conn, t time.Time) error {
if time.Since(t) < time.Minute {
return nil
+172 -4
View File
@@ -2,15 +2,171 @@ package redis
import (
"fmt"
"io"
"runtime"
"testing"
"time"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/gomodule/redigo/redis"
"github.com/mna/redisc"
"github.com/pkg/errors"
"github.com/stretchr/testify/require"
)
type netError struct {
error
timeout bool
temporary bool
allowedCalls int // once this reaches 0, mockDial does not return an error
countCalls int
}
func (t *netError) Timeout() bool { return t.timeout }
func (t *netError) Temporary() bool { return t.temporary }
var errFromConn = errors.New("SUCCESS")
type redisConn struct{}
func (redisConn) Close() error { return errFromConn }
func (redisConn) Err() error { return errFromConn }
func (redisConn) Do(_ string, _ ...interface{}) (interface{}, error) { return nil, errFromConn }
func (redisConn) Send(_ string, _ ...interface{}) error { return errFromConn }
func (redisConn) Flush() error { return errFromConn }
func (redisConn) Receive() (interface{}, error) { return nil, errFromConn }
func TestConnectRetry(t *testing.T) {
mockDial := func(err error) func(net, addr string, opts ...redis.DialOption) (redis.Conn, error) {
return func(net, addr string, opts ...redis.DialOption) (redis.Conn, error) {
var ne *netError
if errors.As(err, &ne) {
ne.countCalls++
if ne.allowedCalls <= 0 {
return redisConn{}, nil
}
ne.allowedCalls--
}
return nil, err
}
}
cases := []struct {
err error
retries int
wantCalls int
min, max time.Duration
}{
// the min-max time intervals are based on the backoff default configuration as
// used in the Dial func of the redis pool. It starts with 500ms interval,
// multiplies by 1.5 on each attempt, and has a randomization of 0.5 that must
// be accounted for. Example ranges of intervals are given at
// https://github.com/fleetdm/fleet/pull/1962#issue-729635664
// and were used to calculate the (approximate) expected range.
{
io.EOF, 0, 1, 0, 100 * time.Millisecond,
}, // non-retryable, no retry configured
{
&netError{error: io.EOF, timeout: true, allowedCalls: 10}, 0, 1, 0, 100 * time.Millisecond,
}, // retryable, but no retry configured
{
io.EOF, 3, 1, 0, 100 * time.Millisecond,
}, // non-retryable, retry configured
{
&netError{error: io.EOF, timeout: true, allowedCalls: 10}, 2, 3, 625 * time.Millisecond, 3500 * time.Millisecond,
}, // retryable, retry configured
{
&netError{error: io.EOF, temporary: true, allowedCalls: 10}, 2, 3, 625 * time.Millisecond, 3500 * time.Millisecond,
}, // retryable, retry configured
{
&netError{error: io.EOF, allowedCalls: 10}, 2, 1, 0, 100 * time.Millisecond,
}, // net error, but non-retryable
{
&netError{error: io.EOF, timeout: true, allowedCalls: 1}, 10, 2, 250 * time.Millisecond, 750 * time.Millisecond,
}, // retryable, but succeeded after one retry
}
for _, c := range cases {
t.Run(c.err.Error(), func(t *testing.T) {
start := time.Now()
_, err := NewRedisPool(PoolConfig{
Server: "127.0.0.1:12345",
ConnectRetryAttempts: c.retries,
testRedisDialFunc: mockDial(c.err),
})
diff := time.Since(start)
require.GreaterOrEqual(t, diff, c.min)
require.LessOrEqual(t, diff, c.max)
require.Error(t, err)
wantErr := io.EOF
var ne *netError
if errors.As(c.err, &ne) {
require.Equal(t, c.wantCalls, ne.countCalls)
if ne.allowedCalls == 0 {
wantErr = errFromConn
}
} else {
require.Equal(t, c.wantCalls, 1)
}
// the error is returned as part of the cluster.Refresh error, hence the
// check with Contains.
require.Contains(t, err.Error(), wantErr.Error())
})
}
}
func TestRedisPoolConfigureDoer(t *testing.T) {
const prefix = "TestRedisPoolConfigureDoer:"
t.Run("standalone", func(t *testing.T) {
pool, teardown := setupRedisForTest(t, false, false)
defer teardown()
c1 := pool.Get()
defer c1.Close()
c2 := pool.ConfigureDoer(pool.Get())
defer c2.Close()
// both conns work equally well, get nil because keys do not exist,
// but no redirection error (this is standalone redis).
_, err := redis.String(c1.Do("GET", prefix+"{a}"))
require.Equal(t, redis.ErrNil, err)
_, err = redis.String(c1.Do("GET", prefix+"{b}"))
require.Equal(t, redis.ErrNil, err)
_, err = redis.String(c2.Do("GET", prefix+"{a}"))
require.Equal(t, redis.ErrNil, err)
_, err = redis.String(c2.Do("GET", prefix+"{b}"))
require.Equal(t, redis.ErrNil, err)
})
t.Run("cluster", func(t *testing.T) {
pool, teardown := setupRedisForTest(t, true, true)
defer teardown()
c1 := pool.Get()
defer c1.Close()
c2 := pool.ConfigureDoer(pool.Get())
defer c2.Close()
// unconfigured conn gets MOVED error on the second key
// (it is bound to {a}, {b} is on a different node)
_, err := redis.String(c1.Do("GET", prefix+"{a}"))
require.Equal(t, redis.ErrNil, err)
_, err = redis.String(c1.Do("GET", prefix+"{b}"))
rerr := redisc.ParseRedir(err)
require.Error(t, rerr)
require.Equal(t, "MOVED", rerr.Type)
// configured conn gets the nil value, it redirected automatically
_, err = redis.String(c2.Do("GET", prefix+"{a}"))
require.Equal(t, redis.ErrNil, err)
_, err = redis.String(c2.Do("GET", prefix+"{b}"))
require.Equal(t, redis.ErrNil, err)
})
}
func TestEachRedisNode(t *testing.T) {
const prefix = "TestEachRedisNode:"
@@ -49,19 +205,23 @@ func TestEachRedisNode(t *testing.T) {
}
t.Run("standalone", func(t *testing.T) {
pool, teardown := setupRedisForTest(t, false)
pool, teardown := setupRedisForTest(t, false, false)
defer teardown()
runTest(t, pool)
})
t.Run("cluster", func(t *testing.T) {
pool, teardown := setupRedisForTest(t, true)
pool, teardown := setupRedisForTest(t, true, false)
defer teardown()
runTest(t, pool)
})
}
func setupRedisForTest(t *testing.T, cluster bool) (pool fleet.RedisPool, teardown func()) {
func setupRedisForTest(t *testing.T, cluster, redir bool) (pool fleet.RedisPool, teardown func()) {
if cluster && (runtime.GOOS == "darwin" || runtime.GOOS == "windows") {
t.Skipf("docker networking limitations prevent running redis cluster tests on %s", runtime.GOOS)
}
var (
addr = "127.0.0.1:"
password = ""
@@ -74,7 +234,15 @@ func setupRedisForTest(t *testing.T, cluster bool) (pool fleet.RedisPool, teardo
}
addr += port
pool, err := NewRedisPool(addr, password, database, useTLS, 5*time.Second, 10*time.Second)
pool, err := NewRedisPool(PoolConfig{
Server: addr,
Password: password,
Database: database,
UseTLS: useTLS,
ConnTimeout: 5 * time.Second,
KeepAlive: 10 * time.Second,
ClusterFollowRedirections: redir,
})
require.NoError(t, err)
conn := pool.Get()
+10
View File
@@ -5,7 +5,17 @@ import "github.com/gomodule/redigo/redis"
// RedisPool is the common interface for redigo's Pool for standalone Redis
// and redisc's Cluster for Redis Cluster.
type RedisPool interface {
// Get returns a redis connection. It must always be closed after use.
Get() redis.Conn
// Close closes the redis connection.
Close() error
// Stats returns a map of redis pool statistics for each server address.
Stats() map[string]redis.PoolStats
// ConfigureDoer returns a redis connection that is properly configured
// to execute Do commands. This should only be called when the actions
// to execute are all done with conn.Do.
ConfigureDoer(redis.Conn) redis.Conn
}
+2 -2
View File
@@ -194,7 +194,7 @@ func (r *redisLiveQuery) RunQuery(name, sql string, hostIDs []uint) error {
}
func (r *redisLiveQuery) StopQuery(name string) error {
conn := r.pool.Get()
conn := r.pool.ConfigureDoer(r.pool.Get())
defer conn.Close()
targetKey, sqlKey := generateKeys(name)
@@ -279,7 +279,7 @@ func (r *redisLiveQuery) collectBatchQueriesForHost(hostID uint, queryKeys []str
}
func (r *redisLiveQuery) QueryCompletedByHost(name string, hostID uint) error {
conn := r.pool.Get()
conn := r.pool.ConfigureDoer(r.pool.Get())
defer conn.Close()
targetKey, _ := generateKeys(name)
+13 -1
View File
@@ -1,6 +1,7 @@
package live_query
import (
"runtime"
"testing"
"time"
@@ -99,6 +100,10 @@ func TestMigrateKeys(t *testing.T) {
}
func setupRedisLiveQuery(t *testing.T, cluster bool) (store *redisLiveQuery, teardown func()) {
if cluster && (runtime.GOOS == "darwin" || runtime.GOOS == "windows") {
t.Skipf("docker networking limitations prevent running redis cluster tests on %s", runtime.GOOS)
}
var (
addr = "127.0.0.1:"
password = ""
@@ -111,7 +116,14 @@ func setupRedisLiveQuery(t *testing.T, cluster bool) (store *redisLiveQuery, tea
}
addr += port
pool, err := redis.NewRedisPool(addr, password, database, useTLS, 5*time.Second, 10*time.Second)
pool, err := redis.NewRedisPool(redis.PoolConfig{
Server: addr,
Password: password,
Database: database,
UseTLS: useTLS,
ConnTimeout: 5 * time.Second,
KeepAlive: 10 * time.Second,
})
require.NoError(t, err)
store = NewRedisLiveQuery(pool)
+13 -1
View File
@@ -1,6 +1,7 @@
package pubsub
import (
"runtime"
"testing"
"time"
@@ -10,6 +11,10 @@ import (
)
func SetupRedisForTest(t *testing.T, cluster bool) (store *redisQueryResults, teardown func()) {
if cluster && (runtime.GOOS == "darwin" || runtime.GOOS == "windows") {
t.Skipf("docker networking limitations prevent running redis cluster tests on %s", runtime.GOOS)
}
var (
addr = "127.0.0.1:"
password = ""
@@ -23,7 +28,14 @@ func SetupRedisForTest(t *testing.T, cluster bool) (store *redisQueryResults, te
}
addr += port
pool, err := redis.NewRedisPool(addr, password, database, useTLS, 5*time.Second, 10*time.Second)
pool, err := redis.NewRedisPool(redis.PoolConfig{
Server: addr,
Password: password,
Database: database,
UseTLS: useTLS,
ConnTimeout: 5 * time.Second,
KeepAlive: 10 * time.Second,
})
require.NoError(t, err)
store = NewRedisQueryResults(pool, dupResults)
+3 -3
View File
@@ -46,7 +46,7 @@ func (s *store) create(requestID, originalURL, metadata string, lifetimeSecs uin
if len(requestID) < 8 {
return errors.New("request id must be 8 or more characters in length")
}
conn := s.pool.Get()
conn := s.pool.ConfigureDoer(s.pool.Get())
defer conn.Close()
sess := Session{OriginalURL: originalURL, Metadata: metadata}
var writer bytes.Buffer
@@ -59,7 +59,7 @@ func (s *store) create(requestID, originalURL, metadata string, lifetimeSecs uin
}
func (s *store) Get(requestID string) (*Session, error) {
conn := s.pool.Get()
conn := s.pool.ConfigureDoer(s.pool.Get())
defer conn.Close()
val, err := redis.String(conn.Do("GET", requestID))
if err != nil {
@@ -81,7 +81,7 @@ func (s *store) Get(requestID string) (*Session, error) {
var ErrSessionNotFound = errors.New("session not found")
func (s *store) Expire(requestID string) error {
conn := s.pool.Get()
conn := s.pool.ConfigureDoer(s.pool.Get())
defer conn.Close()
_, err := conn.Do("DEL", requestID)
return err
+13 -1
View File
@@ -2,6 +2,7 @@ package sso
import (
"os"
"runtime"
"testing"
"time"
@@ -12,6 +13,10 @@ import (
)
func newPool(t *testing.T, cluster bool) fleet.RedisPool {
if cluster && (runtime.GOOS == "darwin" || runtime.GOOS == "windows") {
t.Skipf("docker networking limitations prevent running redis cluster tests on %s", runtime.GOOS)
}
if _, ok := os.LookupEnv("REDIS_TEST"); ok {
var (
addr = "127.0.0.1:"
@@ -25,7 +30,14 @@ func newPool(t *testing.T, cluster bool) fleet.RedisPool {
}
addr += port
pool, err := redis.NewRedisPool(addr, password, database, useTLS, 5*time.Second, 10*time.Second)
pool, err := redis.NewRedisPool(redis.PoolConfig{
Server: addr,
Password: password,
Database: database,
UseTLS: useTLS,
ConnTimeout: 5 * time.Second,
KeepAlive: 10 * time.Second,
})
require.NoError(t, err)
conn := pool.Get()
defer conn.Close()