Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support external redis sentinel cluster #2886

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions configuration/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,9 @@ type Configuration struct {
// Addr specifies the the redis instance available to the application.
Addr string `yaml:"addr,omitempty"`

// SentinelMasterSet specifies the the redis sentinel master set name.
SentinelMasterSet string `yaml:"sentinelMasterSet,omitempty"`

// Password string to use when making a connection.
Password string `yaml:"password,omitempty"`

Expand Down
65 changes: 52 additions & 13 deletions registry/handlers/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package handlers
import (
"context"
cryptorand "crypto/rand"
"errors"
"expvar"
"fmt"
"math/rand"
Expand All @@ -15,6 +16,8 @@ import (
"strings"
"time"

"github.com/FZambia/sentinel"

"github.com/docker/distribution"
"github.com/docker/distribution/configuration"
dcontext "github.com/docker/distribution/context"
Expand All @@ -24,7 +27,7 @@ import (
"github.com/docker/distribution/notifications"
"github.com/docker/distribution/reference"
"github.com/docker/distribution/registry/api/errcode"
"github.com/docker/distribution/registry/api/v2"
v2 "github.com/docker/distribution/registry/api/v2"
"github.com/docker/distribution/registry/auth"
registrymiddleware "github.com/docker/distribution/registry/middleware/registry"
repositorymiddleware "github.com/docker/distribution/registry/middleware/repository"
Expand Down Expand Up @@ -498,6 +501,44 @@ func (app *App) configureRedis(configuration *configuration.Configuration) {
return
}

var getRedisAddr func() (string, error)
var testOnBorrow func(c redis.Conn, t time.Time) error
if configuration.Redis.SentinelMasterSet != "" {
sntnl := &sentinel.Sentinel{
Addrs: strings.Split(configuration.Redis.Addr, ","),
MasterName: configuration.Redis.SentinelMasterSet,
Dial: func(addr string) (redis.Conn, error) {
c, err := redis.DialTimeout("tcp", addr,
configuration.Redis.DialTimeout,
configuration.Redis.ReadTimeout,
configuration.Redis.WriteTimeout)
if err != nil {
return nil, err
}
return c, nil
},
}
getRedisAddr = func() (string, error) {
return sntnl.MasterAddr()
}
testOnBorrow = func(c redis.Conn, t time.Time) error {
if !sentinel.TestRole(c, "master") {
return errors.New("role check failed")
}
return nil
}

} else {
getRedisAddr = func() (string, error) {
return configuration.Redis.Addr, nil
}
testOnBorrow = func(c redis.Conn, t time.Time) error {
// TODO(stevvooe): We can probably do something more interesting
// here with the health package.
_, err := c.Do("PING")
return err
}
}
pool := &redis.Pool{
Dial: func() (redis.Conn, error) {
// TODO(stevvooe): Yet another use case for contextual timing.
Expand All @@ -513,8 +554,11 @@ func (app *App) configureRedis(configuration *configuration.Configuration) {
}
}

conn, err := redis.DialTimeout("tcp",
configuration.Redis.Addr,
redisAddr, err := getRedisAddr()
if err != nil {
return nil, err
}
conn, err := redis.DialTimeout("tcp", redisAddr,
configuration.Redis.DialTimeout,
configuration.Redis.ReadTimeout,
configuration.Redis.WriteTimeout)
Expand Down Expand Up @@ -546,16 +590,11 @@ func (app *App) configureRedis(configuration *configuration.Configuration) {
done(nil)
return conn, nil
},
MaxIdle: configuration.Redis.Pool.MaxIdle,
MaxActive: configuration.Redis.Pool.MaxActive,
IdleTimeout: configuration.Redis.Pool.IdleTimeout,
TestOnBorrow: func(c redis.Conn, t time.Time) error {
// TODO(stevvooe): We can probably do something more interesting
// here with the health package.
_, err := c.Do("PING")
return err
},
Wait: false, // if a connection is not available, proceed without cache.
MaxIdle: configuration.Redis.Pool.MaxIdle,
MaxActive: configuration.Redis.Pool.MaxActive,
IdleTimeout: configuration.Redis.Pool.IdleTimeout,
TestOnBorrow: testOnBorrow,
Wait: false, // if a connection is not available, proceed without cache.
}

app.redis = pool
Expand Down
57 changes: 55 additions & 2 deletions registry/handlers/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
"github.com/docker/distribution/configuration"
"github.com/docker/distribution/context"
"github.com/docker/distribution/registry/api/errcode"
"github.com/docker/distribution/registry/api/v2"
v2 "github.com/docker/distribution/registry/api/v2"
"github.com/docker/distribution/registry/auth"
_ "github.com/docker/distribution/registry/auth/silly"
"github.com/docker/distribution/registry/storage"
Expand Down Expand Up @@ -140,7 +140,29 @@ func TestAppDispatcher(t *testing.T) {
// TestNewApp covers the creation of an application via NewApp with a
// configuration.
func TestNewApp(t *testing.T) {
ctx := context.Background()

config := configuration.Configuration{
Storage: configuration.Storage{
"testdriver": nil,
"maintenance": configuration.Parameters{"uploadpurging": map[interface{}]interface{}{
"enabled": false,
}},
},
Auth: configuration.Auth{
// For now, we simply test that new auth results in a viable
// application.
"silly": {
"realm": "realm-test",
"service": "service-test",
},
},
}
runAppWithConfig(t, config)
}

// TestNewApp covers the creation of an application via NewApp with a
// configuration(with redis).
func TestNewAppWithRedis(t *testing.T) {
config := configuration.Configuration{
Storage: configuration.Storage{
"testdriver": nil,
Expand All @@ -157,7 +179,38 @@ func TestNewApp(t *testing.T) {
},
},
}
config.Redis.Addr = "127.0.0.1:6379"
config.Redis.DB = 0
runAppWithConfig(t, config)
}

// TestNewApp covers the creation of an application via NewApp with a
// configuration(with redis sentinel cluster).
func TestNewAppWithRedisSentinelCluster(t *testing.T) {
config := configuration.Configuration{
Storage: configuration.Storage{
"testdriver": nil,
"maintenance": configuration.Parameters{"uploadpurging": map[interface{}]interface{}{
"enabled": false,
}},
},
Auth: configuration.Auth{
// For now, we simply test that new auth results in a viable
// application.
"silly": {
"realm": "realm-test",
"service": "service-test",
},
},
}
config.Redis.Addr = "192.168.0.11:26379,192.168.0.12:26379"
config.Redis.DB = 0
config.Redis.SentinelMasterSet = "mymaster"
runAppWithConfig(t, config)
}

func runAppWithConfig(t *testing.T, config configuration.Configuration) {
ctx := context.Background()
// Mostly, with this test, given a sane configuration, we are simply
// ensuring that NewApp doesn't panic. We might want to tweak this
// behavior.
Expand Down
1 change: 1 addition & 0 deletions vendor.conf
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,4 @@ gopkg.in/yaml.v2 v2.2.1
rsc.io/letsencrypt e770c10b0f1a64775ae91d240407ce00d1a5bdeb https://github.com/dmcgowan/letsencrypt.git
github.com/opencontainers/go-digest a6d0ee40d4207ea02364bd3b9e8e77b9159ba1eb
github.com/opencontainers/image-spec ab7389ef9f50030c9b245bc16b981c7ddf192882
github.com/FZambia/sentinel 5585739eb4b6478aa30161866ccf9ce0ef5847c7 https://github.com/jeremyxu2010/sentinel.git