influxdb/services/snapshotter/service_test.go

449 lines
10 KiB
Go

package snapshotter_test
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net"
"os"
"reflect"
"testing"
"time"
"github.com/davecgh/go-spew/spew"
"github.com/influxdata/influxdb/internal"
"github.com/influxdata/influxdb/logger"
"github.com/influxdata/influxdb/services/meta"
"github.com/influxdata/influxdb/services/snapshotter"
"github.com/influxdata/influxdb/tcp"
"github.com/influxdata/influxdb/tsdb"
"github.com/influxdata/influxql"
)
var data = meta.Data{
Databases: []meta.DatabaseInfo{
{
Name: "db0",
DefaultRetentionPolicy: "autogen",
RetentionPolicies: []meta.RetentionPolicyInfo{
{
Name: "rp0",
ReplicaN: 1,
Duration: 24 * 7 * time.Hour,
ShardGroupDuration: 24 * time.Hour,
ShardGroups: []meta.ShardGroupInfo{
{
ID: 1,
StartTime: time.Unix(0, 0).UTC(),
EndTime: time.Unix(0, 0).UTC().Add(24 * time.Hour),
Shards: []meta.ShardInfo{
{ID: 2},
},
},
},
},
{
Name: "autogen",
ReplicaN: 1,
ShardGroupDuration: 24 * 7 * time.Hour,
ShardGroups: []meta.ShardGroupInfo{
{
ID: 3,
StartTime: time.Unix(0, 0).UTC(),
EndTime: time.Unix(0, 0).UTC().Add(24 * time.Hour),
Shards: []meta.ShardInfo{
{ID: 4},
},
},
},
},
},
},
},
Users: []meta.UserInfo{
{
Name: "admin",
Hash: "abcxyz",
Admin: true,
Privileges: map[string]influxql.Privilege{},
},
},
}
func init() {
// Set the admin privilege on the user using this method so the meta.Data's check for
// an admin user is set properly.
data.SetAdminPrivilege("admin", true)
}
func TestSnapshotter_Open(t *testing.T) {
s, l, err := NewTestService()
if err != nil {
t.Fatal(err)
}
defer l.Close()
if err := s.Open(); err != nil {
t.Fatalf("unexpected open error: %s", err)
}
if err := s.Close(); err != nil {
t.Fatalf("unexpected close error: %s", err)
}
}
func TestSnapshotter_RequestShardBackup(t *testing.T) {
s, l, err := NewTestService()
if err != nil {
t.Fatal(err)
}
defer l.Close()
var tsdb internal.TSDBStoreMock
tsdb.BackupShardFn = func(id uint64, since time.Time, w io.Writer) error {
if id != 5 {
t.Errorf("unexpected shard id: got=%#v want=%#v", id, 5)
}
if got, want := since, time.Unix(0, 0).UTC(); !got.Equal(want) {
t.Errorf("unexpected time since: got=%#v want=%#v", got, want)
}
// Write some nonsense data so we can check that it gets returned.
w.Write([]byte(`{"status":"ok"}`))
return nil
}
s.TSDBStore = &tsdb
if err := s.Open(); err != nil {
t.Fatalf("unexpected open error: %s", err)
}
defer s.Close()
conn, err := net.Dial("tcp", l.Addr().String())
if err != nil {
t.Errorf("unexpected error: %s", err)
return
}
defer conn.Close()
req := snapshotter.Request{
Type: snapshotter.RequestShardBackup,
ShardID: 5,
Since: time.Unix(0, 0),
}
conn.Write([]byte{snapshotter.MuxHeader})
_, err = conn.Write([]byte{byte(req.Type)})
if err != nil {
t.Errorf("could not encode request type to conn: %v", err)
}
enc := json.NewEncoder(conn)
if err := enc.Encode(&req); err != nil {
t.Errorf("unable to encode request: %s", err)
return
}
// Read the result.
out, err := ioutil.ReadAll(conn)
if err != nil {
t.Errorf("unexpected error reading shard backup: %s", err)
return
}
if got, want := string(out), `{"status":"ok"}`; got != want {
t.Errorf("unexpected shard data: got=%#v want=%#v", got, want)
return
}
}
func TestSnapshotter_RequestMetastoreBackup(t *testing.T) {
s, l, err := NewTestService()
if err != nil {
t.Fatal(err)
}
defer l.Close()
s.MetaClient = &MetaClient{Data: data}
if err := s.Open(); err != nil {
t.Fatalf("unexpected open error: %s", err)
}
defer s.Close()
conn, err := net.Dial("tcp", l.Addr().String())
if err != nil {
t.Errorf("unexpected error: %s", err)
return
}
defer conn.Close()
c := snapshotter.NewClient(l.Addr().String())
if got, err := c.MetastoreBackup(); err != nil {
t.Errorf("unable to obtain metastore backup: %s", err)
return
} else if want := &data; !reflect.DeepEqual(got, want) {
t.Errorf("unexpected data backup:\n\ngot=%s\nwant=%s", spew.Sdump(got), spew.Sdump(want))
return
}
}
func TestSnapshotter_RequestDatabaseInfo(t *testing.T) {
s, l, err := NewTestService()
if err != nil {
t.Fatal(err)
}
defer l.Close()
var tsdbStore internal.TSDBStoreMock
tsdbStore.ShardFn = func(id uint64) *tsdb.Shard {
if id != 2 && id != 4 {
t.Errorf("unexpected shard id: %d", id)
return nil
} else if id == 4 {
return nil
}
return &tsdb.Shard{}
}
tsdbStore.ShardRelativePathFn = func(id uint64) (string, error) {
if id == 2 {
return "db0/rp0", nil
} else if id == 4 {
t.Errorf("unexpected relative path request for shard id: %d", id)
}
return "", fmt.Errorf("no such shard id: %d", id)
}
s.MetaClient = &MetaClient{Data: data}
s.TSDBStore = &tsdbStore
if err := s.Open(); err != nil {
t.Fatalf("unexpected open error: %s", err)
}
defer s.Close()
conn, err := net.Dial("tcp", l.Addr().String())
if err != nil {
t.Errorf("unexpected error: %s", err)
return
}
defer conn.Close()
req := snapshotter.Request{
Type: snapshotter.RequestDatabaseInfo,
BackupDatabase: "db0",
}
conn.Write([]byte{snapshotter.MuxHeader})
_, err = conn.Write([]byte{byte(req.Type)})
if err != nil {
t.Errorf("could not encode request type to conn: %v", err)
}
enc := json.NewEncoder(conn)
if err := enc.Encode(&req); err != nil {
t.Errorf("unable to encode request: %s", err)
return
}
// Read the result.
out, err := ioutil.ReadAll(conn)
if err != nil {
t.Errorf("unexpected error reading database info: %s", err)
return
}
// Unmarshal the response.
var resp snapshotter.Response
if err := json.Unmarshal(out, &resp); err != nil {
t.Errorf("error unmarshaling response: %s", err)
return
}
if got, want := resp.Paths, []string{"db0/rp0"}; !reflect.DeepEqual(got, want) {
t.Errorf("unexpected paths: got=%#v want=%#v", got, want)
}
}
func TestSnapshotter_RequestDatabaseInfo_ErrDatabaseNotFound(t *testing.T) {
s, l, err := NewTestService()
if err != nil {
t.Fatal(err)
}
defer l.Close()
s.MetaClient = &MetaClient{Data: data}
if err := s.Open(); err != nil {
t.Fatalf("unexpected open error: %s", err)
}
defer s.Close()
conn, err := net.Dial("tcp", l.Addr().String())
if err != nil {
t.Errorf("unexpected error: %s", err)
return
}
defer conn.Close()
req := snapshotter.Request{
Type: snapshotter.RequestDatabaseInfo,
BackupDatabase: "doesnotexist",
}
conn.Write([]byte{snapshotter.MuxHeader})
_, err = conn.Write([]byte{byte(req.Type)})
if err != nil {
t.Errorf("could not encode request type to conn: %v", err)
}
enc := json.NewEncoder(conn)
if err := enc.Encode(&req); err != nil {
t.Errorf("unable to encode request: %s", err)
return
}
// Read the result.
out, err := ioutil.ReadAll(conn)
if err != nil {
t.Errorf("unexpected error reading database info: %s", err)
return
}
// There should be no response.
if got, want := string(out), ""; got != want {
t.Errorf("expected no message, got: %s", got)
}
}
func TestSnapshotter_RequestRetentionPolicyInfo(t *testing.T) {
s, l, err := NewTestService()
if err != nil {
t.Fatal(err)
}
defer l.Close()
var tsdbStore internal.TSDBStoreMock
tsdbStore.ShardFn = func(id uint64) *tsdb.Shard {
if id != 2 {
t.Errorf("unexpected shard id: %d", id)
return nil
}
return &tsdb.Shard{}
}
tsdbStore.ShardRelativePathFn = func(id uint64) (string, error) {
if id == 2 {
return "db0/rp0", nil
}
return "", fmt.Errorf("no such shard id: %d", id)
}
s.MetaClient = &MetaClient{Data: data}
s.TSDBStore = &tsdbStore
if err := s.Open(); err != nil {
t.Fatalf("unexpected open error: %s", err)
}
defer s.Close()
conn, err := net.Dial("tcp", l.Addr().String())
if err != nil {
t.Errorf("unexpected error: %s", err)
return
}
defer conn.Close()
req := snapshotter.Request{
Type: snapshotter.RequestRetentionPolicyInfo,
BackupDatabase: "db0",
BackupRetentionPolicy: "rp0",
}
conn.Write([]byte{snapshotter.MuxHeader})
_, err = conn.Write([]byte{byte(req.Type)})
if err != nil {
t.Errorf("could not encode request type to conn: %v", err)
}
enc := json.NewEncoder(conn)
if err := enc.Encode(&req); err != nil {
t.Errorf("unable to encode request: %s", err)
return
}
// Read the result.
out, err := ioutil.ReadAll(conn)
if err != nil {
t.Errorf("unexpected error reading database info: %s", err)
return
}
// Unmarshal the response.
var resp snapshotter.Response
if err := json.Unmarshal(out, &resp); err != nil {
t.Errorf("error unmarshaling response: %s", err)
return
}
if got, want := resp.Paths, []string{"db0/rp0"}; !reflect.DeepEqual(got, want) {
t.Errorf("unexpected paths: got=%#v want=%#v", got, want)
}
}
func TestSnapshotter_InvalidRequest(t *testing.T) {
s, l, err := NewTestService()
if err != nil {
t.Fatal(err)
}
defer l.Close()
if err := s.Open(); err != nil {
t.Fatalf("unexpected open error: %s", err)
}
defer s.Close()
conn, err := net.Dial("tcp", l.Addr().String())
if err != nil {
t.Errorf("unexpected error: %s", err)
return
}
defer conn.Close()
conn.Write([]byte{snapshotter.MuxHeader})
conn.Write([]byte(`["invalid request"]`))
// Read the result.
out, err := ioutil.ReadAll(conn)
if err != nil {
t.Errorf("unexpected error reading database info: %s", err)
return
}
// There should be no response.
if got, want := string(out), ""; got != want {
t.Errorf("expected no message, got: %s", got)
}
}
func NewTestService() (*snapshotter.Service, net.Listener, error) {
s := snapshotter.NewService()
s.WithLogger(logger.New(os.Stderr))
l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
return nil, nil, err
}
// The snapshotter needs to be used with a tcp.Mux listener.
mux := tcp.NewMux()
go mux.Serve(l)
s.Listener = mux.Listen(snapshotter.MuxHeader)
return s, l, nil
}
type MetaClient struct {
Data meta.Data
}
func (m *MetaClient) MarshalBinary() ([]byte, error) {
return m.Data.MarshalBinary()
}
func (m *MetaClient) Database(name string) *meta.DatabaseInfo {
for _, dbi := range m.Data.Databases {
if dbi.Name == name {
return &dbi
}
}
return nil
}