storj/storage/redis/redisserver/server.go
Egon Elbre 83df0ee1b0
Implement ListV2 with storage rework (#303)
1. Added KeyValueStore.Iterate for implementing the different List, ListV2 etc. implementations. This allows for more efficient use of memory depending on the situation.
2. Implemented an inmemory teststore for running tests. This should allow to replace MockKeyValueStore in most places.
3. Rewrote tests
4. Pulled out logger from bolt implementation so it can be used for all other storage implementations.
5. Fixed multiple things in bolt and redis implementations.
2018-09-05 19:10:35 +03:00

156 lines
3.2 KiB
Go

// Copyright (C) 2018 Storj Labs, Inc.
// 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"
"github.com/alicebob/miniredis"
"github.com/go-redis/redis"
)
const (
fallbackAddr = "localhost:6379"
fallbackPort = 6379
)
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)
return Mini()
}
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)
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() {
_ = cmd.Process.Kill()
_ = 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()
}
// Mini starts miniredis server
func Mini() (addr string, cleanup func(), err error) {
server, err := miniredis.Run()
if err != nil {
return "", nil, err
}
return server.Addr(), func() {
server.Close()
}, nil
}