2014-07-02 19:04:38 +04:00
|
|
|
package redis_test
|
|
|
|
|
|
|
|
import (
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"path/filepath"
|
|
|
|
"testing"
|
|
|
|
"text/template"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"gopkg.in/redis.v2"
|
|
|
|
)
|
|
|
|
|
|
|
|
func startRedis(port string) (*exec.Cmd, error) {
|
|
|
|
cmd := exec.Command("redis-server", "--port", port)
|
2014-07-04 14:36:49 +04:00
|
|
|
if false {
|
2014-07-02 19:04:38 +04:00
|
|
|
cmd.Stdout = os.Stdout
|
|
|
|
cmd.Stderr = os.Stderr
|
|
|
|
}
|
|
|
|
if err := cmd.Start(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return cmd, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func startRedisSlave(port, slave string) (*exec.Cmd, error) {
|
|
|
|
cmd := exec.Command("redis-server", "--port", port, "--slaveof", "127.0.0.1", slave)
|
2014-07-04 14:36:49 +04:00
|
|
|
if false {
|
2014-07-02 19:04:38 +04:00
|
|
|
cmd.Stdout = os.Stdout
|
|
|
|
cmd.Stderr = os.Stderr
|
|
|
|
}
|
|
|
|
if err := cmd.Start(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return cmd, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func startRedisSentinel(port, masterName, masterPort string) (*exec.Cmd, error) {
|
|
|
|
dir, err := ioutil.TempDir("", "sentinel")
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
sentinelConfFilepath := filepath.Join(dir, "sentinel.conf")
|
|
|
|
tpl, err := template.New("sentinel.conf").Parse(sentinelConf)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
data := struct {
|
|
|
|
Port string
|
|
|
|
MasterName string
|
|
|
|
MasterPort string
|
|
|
|
}{
|
|
|
|
Port: port,
|
|
|
|
MasterName: masterName,
|
|
|
|
MasterPort: masterPort,
|
|
|
|
}
|
|
|
|
if err := writeTemplateToFile(sentinelConfFilepath, tpl, data); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
cmd := exec.Command("redis-server", sentinelConfFilepath, "--sentinel")
|
2014-07-04 14:32:32 +04:00
|
|
|
if true {
|
2014-07-02 19:04:38 +04:00
|
|
|
cmd.Stdout = os.Stdout
|
|
|
|
cmd.Stderr = os.Stderr
|
|
|
|
}
|
|
|
|
if err := cmd.Start(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return cmd, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func writeTemplateToFile(path string, t *template.Template, data interface{}) error {
|
|
|
|
f, err := os.Create(path)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
return t.Execute(f, data)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestSentinel(t *testing.T) {
|
|
|
|
masterName := "mymaster"
|
|
|
|
masterPort := "8123"
|
|
|
|
slavePort := "8124"
|
|
|
|
sentinelPort := "26379"
|
|
|
|
|
|
|
|
_, err := startRedis(masterPort)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
time.Sleep(200 * time.Millisecond)
|
|
|
|
|
|
|
|
master := redis.NewTCPClient(&redis.Options{
|
|
|
|
Addr: ":" + masterPort,
|
|
|
|
})
|
|
|
|
if err := master.Ping().Err(); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
defer master.Shutdown()
|
|
|
|
|
|
|
|
_, err = startRedisSlave(slavePort, masterPort)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
time.Sleep(200 * time.Millisecond)
|
|
|
|
|
|
|
|
slave := redis.NewTCPClient(&redis.Options{
|
|
|
|
Addr: ":" + slavePort,
|
|
|
|
})
|
|
|
|
if err := slave.Ping().Err(); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
defer slave.Shutdown()
|
|
|
|
|
|
|
|
_, err = startRedisSentinel(sentinelPort, masterName, masterPort)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
time.Sleep(200 * time.Millisecond)
|
|
|
|
|
|
|
|
sentinel := redis.NewTCPClient(&redis.Options{
|
|
|
|
Addr: ":" + sentinelPort,
|
|
|
|
})
|
|
|
|
if err := sentinel.Ping().Err(); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
defer sentinel.Shutdown()
|
|
|
|
|
|
|
|
client := redis.NewFailoverClient(&redis.FailoverOptions{
|
|
|
|
MasterName: masterName,
|
|
|
|
SentinelAddrs: []string{":" + sentinelPort},
|
|
|
|
})
|
|
|
|
|
|
|
|
if err := client.Set("foo", "master").Err(); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
val, err := master.Get("foo").Result()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if val != "master" {
|
|
|
|
t.Fatalf(`got %q, expected "master"`, val)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Kill master.
|
|
|
|
master.Shutdown()
|
|
|
|
|
|
|
|
// Wait for Redis sentinel to elect new master.
|
2014-07-04 14:36:49 +04:00
|
|
|
time.Sleep(5 * time.Second)
|
2014-07-02 19:04:38 +04:00
|
|
|
|
|
|
|
// Check that client picked up new master.
|
|
|
|
val, err = client.Get("foo").Result()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if val != "master" {
|
|
|
|
t.Fatalf(`got %q, expected "master"`, val)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var sentinelConf = `
|
|
|
|
port {{ .Port }}
|
|
|
|
|
|
|
|
sentinel monitor {{ .MasterName }} 127.0.0.1 {{ .MasterPort }} 1
|
2014-07-04 14:36:49 +04:00
|
|
|
sentinel down-after-milliseconds {{ .MasterName }} 1000
|
|
|
|
sentinel failover-timeout {{ .MasterName }} 2000
|
2014-07-02 19:04:38 +04:00
|
|
|
sentinel parallel-syncs {{ .MasterName }} 1
|
|
|
|
`
|