2019-01-24 20:15:10 +00:00
|
|
|
// Copyright (C) 2019 Storj Labs, Inc.
|
2018-09-05 17:10:35 +01:00
|
|
|
// See LICENSE for copying information.
|
|
|
|
|
|
|
|
// Package redisserver is package for starting a redis test server
|
|
|
|
package redisserver
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"log"
|
|
|
|
"net"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"path/filepath"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
2020-02-06 12:56:23 +00:00
|
|
|
"github.com/alicebob/miniredis/v2"
|
2018-09-05 17:10:35 +01:00
|
|
|
"github.com/go-redis/redis"
|
2018-10-16 12:43:44 +01:00
|
|
|
|
2019-11-14 19:46:15 +00:00
|
|
|
"storj.io/storj/private/processgroup"
|
2018-09-05 17:10:35 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
fallbackAddr = "localhost:6379"
|
|
|
|
fallbackPort = 6379
|
|
|
|
)
|
|
|
|
|
2019-10-31 17:27:38 +00:00
|
|
|
// Mini is a wrapper for *miniredis.MiniRedis which implements the io.Closer interface.
|
|
|
|
type Mini struct {
|
|
|
|
server *miniredis.Miniredis
|
|
|
|
}
|
|
|
|
|
2018-09-05 17:10:35 +01:00
|
|
|
func freeport() (addr string, port int) {
|
|
|
|
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
|
|
|
if err != nil {
|
|
|
|
return fallbackAddr, fallbackPort
|
|
|
|
}
|
|
|
|
|
|
|
|
netaddr := listener.Addr().(*net.TCPAddr)
|
|
|
|
addr = netaddr.String()
|
|
|
|
port = netaddr.Port
|
|
|
|
_ = listener.Close()
|
|
|
|
time.Sleep(time.Second)
|
|
|
|
return addr, port
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start starts a redis-server when available, otherwise falls back to miniredis
|
|
|
|
func Start() (addr string, cleanup func(), err error) {
|
|
|
|
addr, cleanup, err = Process()
|
|
|
|
if err != nil {
|
|
|
|
log.Println("failed to start redis-server: ", err)
|
2019-10-31 17:27:38 +00:00
|
|
|
mini := NewMini()
|
|
|
|
return mini.Run()
|
2018-09-05 17:10:35 +01:00
|
|
|
}
|
|
|
|
return addr, cleanup, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Process starts a redis-server test process
|
|
|
|
func Process() (addr string, cleanup func(), err error) {
|
|
|
|
tmpdir, err := ioutil.TempDir("", "storj-redis")
|
|
|
|
if err != nil {
|
|
|
|
return "", nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// find a suitable port for listening
|
|
|
|
var port int
|
|
|
|
addr, port = freeport()
|
|
|
|
|
|
|
|
// write a configuration file, because redis doesn't support flags
|
|
|
|
confpath := filepath.Join(tmpdir, "test.conf")
|
|
|
|
arguments := []string{
|
|
|
|
"daemonize no",
|
|
|
|
"bind 127.0.0.1",
|
|
|
|
"port " + strconv.Itoa(port),
|
|
|
|
"timeout 0",
|
|
|
|
"databases 2",
|
|
|
|
"dbfilename dump.rdb",
|
|
|
|
"dir " + tmpdir,
|
|
|
|
}
|
|
|
|
|
|
|
|
conf := strings.Join(arguments, "\n") + "\n"
|
|
|
|
err = ioutil.WriteFile(confpath, []byte(conf), 0755)
|
|
|
|
if err != nil {
|
|
|
|
return "", nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// start the process
|
|
|
|
cmd := exec.Command("redis-server", confpath)
|
2018-09-11 14:57:12 +01:00
|
|
|
processgroup.Setup(cmd)
|
2018-09-05 17:10:35 +01:00
|
|
|
|
|
|
|
read, write, err := os.Pipe()
|
|
|
|
if err != nil {
|
|
|
|
return "", nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
cmd.Stdout = write
|
|
|
|
if err := cmd.Start(); err != nil {
|
|
|
|
return "", nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
cleanup = func() {
|
2018-09-11 14:57:12 +01:00
|
|
|
processgroup.Kill(cmd)
|
2018-09-05 17:10:35 +01:00
|
|
|
_ = os.RemoveAll(tmpdir)
|
|
|
|
}
|
|
|
|
|
|
|
|
// wait for redis to become ready
|
|
|
|
waitForReady := make(chan error, 1)
|
|
|
|
go func() {
|
|
|
|
// wait for the message that looks like
|
|
|
|
// v3 "The server is now ready to accept connections on port 6379"
|
|
|
|
// v4 "Ready to accept connections"
|
|
|
|
scanner := bufio.NewScanner(read)
|
|
|
|
for scanner.Scan() {
|
|
|
|
line := scanner.Text()
|
|
|
|
if strings.Contains(line, "to accept") {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
waitForReady <- scanner.Err()
|
|
|
|
_, _ = io.Copy(ioutil.Discard, read)
|
|
|
|
}()
|
|
|
|
|
|
|
|
select {
|
|
|
|
case err := <-waitForReady:
|
|
|
|
if err != nil {
|
|
|
|
cleanup()
|
|
|
|
return "", nil, err
|
|
|
|
}
|
|
|
|
case <-time.After(3 * time.Second):
|
|
|
|
cleanup()
|
|
|
|
return "", nil, errors.New("redis timeout")
|
|
|
|
}
|
|
|
|
|
|
|
|
// test whether we can actually connect
|
|
|
|
if err := pingServer(addr); err != nil {
|
|
|
|
cleanup()
|
|
|
|
return "", nil, fmt.Errorf("unable to ping: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return addr, cleanup, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func pingServer(addr string) error {
|
|
|
|
client := redis.NewClient(&redis.Options{Addr: addr, DB: 1})
|
|
|
|
defer func() { _ = client.Close() }()
|
|
|
|
return client.Ping().Err()
|
|
|
|
}
|
|
|
|
|
2019-10-31 17:27:38 +00:00
|
|
|
// NewMini creates a new Mini.
|
|
|
|
func NewMini() *Mini {
|
|
|
|
return &Mini{
|
|
|
|
server: miniredis.NewMiniRedis(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Run starts the miniredis server.
|
|
|
|
func (mini *Mini) Run() (addr string, cleanup func(), err error) {
|
|
|
|
err = mini.server.Start()
|
2018-09-05 17:10:35 +01:00
|
|
|
if err != nil {
|
|
|
|
return "", nil, err
|
|
|
|
}
|
2019-10-31 17:27:38 +00:00
|
|
|
return mini.server.Addr(), func() {
|
|
|
|
mini.server.Close()
|
2018-09-05 17:10:35 +01:00
|
|
|
}, nil
|
|
|
|
}
|
2019-10-31 17:27:38 +00:00
|
|
|
|
|
|
|
// Close closes the miniredis server.
|
|
|
|
func (mini *Mini) Close() error {
|
|
|
|
mini.server.Close()
|
|
|
|
return nil
|
|
|
|
}
|