Add Redis Sentinel support
Co-authored-by: Romain <rtribotte@users.noreply.github.com>
This commit is contained in:
parent
60123a8f3f
commit
3dfaa3d5fa
12 changed files with 543 additions and 12 deletions
|
@ -4,12 +4,18 @@ import (
|
|||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/fatih/structs"
|
||||
"github.com/go-check/check"
|
||||
"github.com/kvtools/redis"
|
||||
"github.com/kvtools/valkeyrie"
|
||||
|
@ -23,24 +29,36 @@ import (
|
|||
// Redis test suites.
|
||||
type RedisSuite struct {
|
||||
BaseSuite
|
||||
kvClient store.Store
|
||||
redisAddr string
|
||||
kvClient store.Store
|
||||
redisEndpoints []string
|
||||
}
|
||||
|
||||
func (s *RedisSuite) TearDownSuite(c *check.C) {
|
||||
s.composeDown(c)
|
||||
|
||||
for _, filename := range []string{"sentinel1.conf", "sentinel2.conf", "sentinel3.conf"} {
|
||||
err := os.Remove(filepath.Join(".", "resources", "compose", "config", filename))
|
||||
if err != nil && !errors.Is(err, fs.ErrNotExist) {
|
||||
c.Fatal("unable to clean configuration file for sentinel: ", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *RedisSuite) setupStore(c *check.C) {
|
||||
s.createComposeProject(c, "redis")
|
||||
s.composeUp(c)
|
||||
|
||||
s.redisAddr = net.JoinHostPort(s.getComposeServiceIP(c, "redis"), "6379")
|
||||
s.redisEndpoints = []string{}
|
||||
s.redisEndpoints = append(s.redisEndpoints, net.JoinHostPort(s.getComposeServiceIP(c, "redis"), "6379"))
|
||||
|
||||
kv, err := valkeyrie.NewStore(
|
||||
context.Background(),
|
||||
redis.StoreName,
|
||||
[]string{s.redisAddr},
|
||||
s.redisEndpoints,
|
||||
&redis.Config{},
|
||||
)
|
||||
if err != nil {
|
||||
c.Fatal("Cannot create store redis")
|
||||
c.Fatal("Cannot create store redis: ", err)
|
||||
}
|
||||
s.kvClient = kv
|
||||
|
||||
|
@ -52,7 +70,173 @@ func (s *RedisSuite) setupStore(c *check.C) {
|
|||
func (s *RedisSuite) TestSimpleConfiguration(c *check.C) {
|
||||
s.setupStore(c)
|
||||
|
||||
file := s.adaptFile(c, "fixtures/redis/simple.toml", struct{ RedisAddress string }{s.redisAddr})
|
||||
file := s.adaptFile(c, "fixtures/redis/simple.toml", struct{ RedisAddress string }{
|
||||
RedisAddress: strings.Join(s.redisEndpoints, ","),
|
||||
})
|
||||
defer os.Remove(file)
|
||||
|
||||
data := map[string]string{
|
||||
"traefik/http/routers/Router0/entryPoints/0": "web",
|
||||
"traefik/http/routers/Router0/middlewares/0": "compressor",
|
||||
"traefik/http/routers/Router0/middlewares/1": "striper",
|
||||
"traefik/http/routers/Router0/service": "simplesvc",
|
||||
"traefik/http/routers/Router0/rule": "Host(`kv1.localhost`)",
|
||||
"traefik/http/routers/Router0/priority": "42",
|
||||
"traefik/http/routers/Router0/tls": "true",
|
||||
|
||||
"traefik/http/routers/Router1/rule": "Host(`kv2.localhost`)",
|
||||
"traefik/http/routers/Router1/priority": "42",
|
||||
"traefik/http/routers/Router1/tls/domains/0/main": "aaa.localhost",
|
||||
"traefik/http/routers/Router1/tls/domains/0/sans/0": "aaa.aaa.localhost",
|
||||
"traefik/http/routers/Router1/tls/domains/0/sans/1": "bbb.aaa.localhost",
|
||||
"traefik/http/routers/Router1/tls/domains/1/main": "bbb.localhost",
|
||||
"traefik/http/routers/Router1/tls/domains/1/sans/0": "aaa.bbb.localhost",
|
||||
"traefik/http/routers/Router1/tls/domains/1/sans/1": "bbb.bbb.localhost",
|
||||
"traefik/http/routers/Router1/entryPoints/0": "web",
|
||||
"traefik/http/routers/Router1/service": "simplesvc",
|
||||
|
||||
"traefik/http/services/simplesvc/loadBalancer/servers/0/url": "http://10.0.1.1:8888",
|
||||
"traefik/http/services/simplesvc/loadBalancer/servers/1/url": "http://10.0.1.1:8889",
|
||||
|
||||
"traefik/http/services/srvcA/loadBalancer/servers/0/url": "http://10.0.1.2:8888",
|
||||
"traefik/http/services/srvcA/loadBalancer/servers/1/url": "http://10.0.1.2:8889",
|
||||
|
||||
"traefik/http/services/srvcB/loadBalancer/servers/0/url": "http://10.0.1.3:8888",
|
||||
"traefik/http/services/srvcB/loadBalancer/servers/1/url": "http://10.0.1.3:8889",
|
||||
|
||||
"traefik/http/services/mirror/mirroring/service": "simplesvc",
|
||||
"traefik/http/services/mirror/mirroring/mirrors/0/name": "srvcA",
|
||||
"traefik/http/services/mirror/mirroring/mirrors/0/percent": "42",
|
||||
"traefik/http/services/mirror/mirroring/mirrors/1/name": "srvcB",
|
||||
"traefik/http/services/mirror/mirroring/mirrors/1/percent": "42",
|
||||
|
||||
"traefik/http/services/Service03/weighted/services/0/name": "srvcA",
|
||||
"traefik/http/services/Service03/weighted/services/0/weight": "42",
|
||||
"traefik/http/services/Service03/weighted/services/1/name": "srvcB",
|
||||
"traefik/http/services/Service03/weighted/services/1/weight": "42",
|
||||
|
||||
"traefik/http/middlewares/compressor/compress": "true",
|
||||
"traefik/http/middlewares/striper/stripPrefix/prefixes/0": "foo",
|
||||
"traefik/http/middlewares/striper/stripPrefix/prefixes/1": "bar",
|
||||
"traefik/http/middlewares/striper/stripPrefix/forceSlash": "true",
|
||||
}
|
||||
|
||||
for k, v := range data {
|
||||
err := s.kvClient.Put(context.Background(), k, []byte(v), nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
|
||||
cmd, display := s.traefikCmd(withConfigFile(file))
|
||||
defer display(c)
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer s.killCmd(cmd)
|
||||
|
||||
// wait for traefik
|
||||
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 2*time.Second,
|
||||
try.BodyContains(`"striper@redis":`, `"compressor@redis":`, `"srvcA@redis":`, `"srvcB@redis":`),
|
||||
)
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
resp, err := http.Get("http://127.0.0.1:8080/api/rawdata")
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
var obtained api.RunTimeRepresentation
|
||||
err = json.NewDecoder(resp.Body).Decode(&obtained)
|
||||
c.Assert(err, checker.IsNil)
|
||||
got, err := json.MarshalIndent(obtained, "", " ")
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
expectedJSON := filepath.FromSlash("testdata/rawdata-redis.json")
|
||||
|
||||
if *updateExpected {
|
||||
err = os.WriteFile(expectedJSON, got, 0o666)
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
|
||||
expected, err := os.ReadFile(expectedJSON)
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
if !bytes.Equal(expected, got) {
|
||||
diff := difflib.UnifiedDiff{
|
||||
FromFile: "Expected",
|
||||
A: difflib.SplitLines(string(expected)),
|
||||
ToFile: "Got",
|
||||
B: difflib.SplitLines(string(got)),
|
||||
Context: 3,
|
||||
}
|
||||
|
||||
text, err := difflib.GetUnifiedDiffString(diff)
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Error(text)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *RedisSuite) setupSentinelStore(c *check.C) {
|
||||
s.setupSentinelConfiguration(c, []string{"26379", "36379", "46379"})
|
||||
|
||||
s.createComposeProject(c, "redis_sentinel")
|
||||
s.composeUp(c)
|
||||
|
||||
s.redisEndpoints = []string{
|
||||
net.JoinHostPort(s.getComposeServiceIP(c, "sentinel1"), "26379"),
|
||||
net.JoinHostPort(s.getComposeServiceIP(c, "sentinel2"), "36379"),
|
||||
net.JoinHostPort(s.getComposeServiceIP(c, "sentinel3"), "46379"),
|
||||
}
|
||||
|
||||
kv, err := valkeyrie.NewStore(
|
||||
context.Background(),
|
||||
redis.StoreName,
|
||||
s.redisEndpoints,
|
||||
&redis.Config{
|
||||
Sentinel: &redis.Sentinel{
|
||||
MasterName: "mymaster",
|
||||
},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
c.Fatal("Cannot create store redis sentinel")
|
||||
}
|
||||
s.kvClient = kv
|
||||
|
||||
// wait for redis
|
||||
err = try.Do(60*time.Second, try.KVExists(kv, "test"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
|
||||
func (s *RedisSuite) setupSentinelConfiguration(c *check.C, ports []string) {
|
||||
for i, port := range ports {
|
||||
templateValue := struct{ SentinelPort string }{SentinelPort: port}
|
||||
|
||||
// Load file
|
||||
templateFile := "resources/compose/config/sentinel_template.conf"
|
||||
tmpl, err := template.ParseFiles(templateFile)
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
folder, prefix := filepath.Split(templateFile)
|
||||
|
||||
fileName := fmt.Sprintf("%s/sentinel%d.conf", folder, i+1)
|
||||
tmpFile, err := os.Create(fileName)
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer tmpFile.Close()
|
||||
|
||||
model := structs.Map(templateValue)
|
||||
model["SelfFilename"] = tmpFile.Name()
|
||||
|
||||
err = tmpl.ExecuteTemplate(tmpFile, prefix, model)
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
err = tmpFile.Sync()
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *RedisSuite) TestSentinelConfiguration(c *check.C) {
|
||||
s.setupSentinelStore(c)
|
||||
|
||||
file := s.adaptFile(c, "fixtures/redis/sentinel.toml", struct{ RedisAddress string }{
|
||||
RedisAddress: strings.Join(s.redisEndpoints, `","`),
|
||||
})
|
||||
defer os.Remove(file)
|
||||
|
||||
data := map[string]string{
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue