You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
734 lines
22 KiB
Go
734 lines
22 KiB
Go
package store
|
|
|
|
import (
|
|
"encoding/json"
|
|
"io/ioutil"
|
|
"net"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"sort"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/rqlite/rqlite/testdata/chinook"
|
|
)
|
|
|
|
type mockSnapshotSink struct {
|
|
*os.File
|
|
}
|
|
|
|
func (m *mockSnapshotSink) ID() string {
|
|
return "1"
|
|
}
|
|
|
|
func (m *mockSnapshotSink) Cancel() error {
|
|
return nil
|
|
}
|
|
|
|
func Test_ClusterMeta(t *testing.T) {
|
|
c := newClusterMeta()
|
|
c.APIPeers["localhost:4002"] = "localhost:4001"
|
|
|
|
if c.AddrForPeer("localhost:4002") != "localhost:4001" {
|
|
t.Fatalf("wrong address returned for localhost:4002")
|
|
}
|
|
|
|
if c.AddrForPeer("127.0.0.1:4002") != "localhost:4001" {
|
|
t.Fatalf("wrong address returned for 127.0.0.1:4002")
|
|
}
|
|
|
|
if c.AddrForPeer("127.0.0.1:4004") != "" {
|
|
t.Fatalf("wrong address returned for 127.0.0.1:4003")
|
|
}
|
|
}
|
|
|
|
func Test_OpenStoreSingleNode(t *testing.T) {
|
|
s := mustNewStore(true)
|
|
defer os.RemoveAll(s.Path())
|
|
|
|
if err := s.Open(true); err != nil {
|
|
t.Fatalf("failed to open single-node store: %s", err.Error())
|
|
}
|
|
}
|
|
|
|
func Test_OpenStoreCloseSingleNode(t *testing.T) {
|
|
s := mustNewStore(true)
|
|
defer os.RemoveAll(s.Path())
|
|
|
|
if err := s.Open(true); err != nil {
|
|
t.Fatalf("failed to open single-node store: %s", err.Error())
|
|
}
|
|
if err := s.Close(true); err != nil {
|
|
t.Fatalf("failed to close single-node store: %s", err.Error())
|
|
}
|
|
}
|
|
|
|
func Test_SingleNodeInMemExecuteQuery(t *testing.T) {
|
|
s := mustNewStore(true)
|
|
defer os.RemoveAll(s.Path())
|
|
|
|
if err := s.Open(true); err != nil {
|
|
t.Fatalf("failed to open single-node store: %s", err.Error())
|
|
}
|
|
defer s.Close(true)
|
|
s.WaitForLeader(10 * time.Second)
|
|
|
|
queries := []string{
|
|
`CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)`,
|
|
`INSERT INTO foo(id, name) VALUES(1, "fiona")`,
|
|
}
|
|
_, err := s.Execute(&ExecuteRequest{queries, false, false})
|
|
if err != nil {
|
|
t.Fatalf("failed to execute on single node: %s", err.Error())
|
|
}
|
|
r, err := s.Query(&QueryRequest{[]string{`SELECT * FROM foo`}, false, false, None})
|
|
if err != nil {
|
|
t.Fatalf("failed to query single node: %s", err.Error())
|
|
}
|
|
r, err = s.Query(&QueryRequest{[]string{`SELECT * FROM foo`}, false, false, None})
|
|
if err != nil {
|
|
t.Fatalf("failed to query single node: %s", err.Error())
|
|
}
|
|
r, err = s.Query(&QueryRequest{[]string{`SELECT * FROM foo`}, false, false, None})
|
|
if err != nil {
|
|
t.Fatalf("failed to query single node: %s", err.Error())
|
|
}
|
|
if exp, got := `["id","name"]`, asJSON(r[0].Columns); exp != got {
|
|
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
|
|
}
|
|
if exp, got := `[[1,"fiona"]]`, asJSON(r[0].Values); exp != got {
|
|
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
|
|
}
|
|
}
|
|
|
|
// Test_SingleNodeInMemExecuteQueryFail ensures database level errors are presented by the store.
|
|
func Test_SingleNodeInMemExecuteQueryFail(t *testing.T) {
|
|
s := mustNewStore(true)
|
|
defer os.RemoveAll(s.Path())
|
|
|
|
if err := s.Open(true); err != nil {
|
|
t.Fatalf("failed to open single-node store: %s", err.Error())
|
|
}
|
|
defer s.Close(true)
|
|
s.WaitForLeader(10 * time.Second)
|
|
|
|
queries := []string{
|
|
`INSERT INTO foo(id, name) VALUES(1, "fiona")`,
|
|
}
|
|
r, err := s.Execute(&ExecuteRequest{queries, false, false})
|
|
if err != nil {
|
|
t.Fatalf("failed to execute on single node: %s", err.Error())
|
|
}
|
|
if exp, got := "no such table: foo", r[0].Error; exp != got {
|
|
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
|
|
}
|
|
}
|
|
|
|
func Test_SingleNodeFileExecuteQuery(t *testing.T) {
|
|
s := mustNewStore(false)
|
|
defer os.RemoveAll(s.Path())
|
|
|
|
if err := s.Open(true); err != nil {
|
|
t.Fatalf("failed to open single-node store: %s", err.Error())
|
|
}
|
|
defer s.Close(true)
|
|
s.WaitForLeader(10 * time.Second)
|
|
|
|
queries := []string{
|
|
`CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)`,
|
|
`INSERT INTO foo(id, name) VALUES(1, "fiona")`,
|
|
}
|
|
_, err := s.Execute(&ExecuteRequest{queries, false, false})
|
|
if err != nil {
|
|
t.Fatalf("failed to execute on single node: %s", err.Error())
|
|
}
|
|
r, err := s.Query(&QueryRequest{[]string{`SELECT * FROM foo`}, false, false, None})
|
|
if err != nil {
|
|
t.Fatalf("failed to query single node: %s", err.Error())
|
|
}
|
|
r, err = s.Query(&QueryRequest{[]string{`SELECT * FROM foo`}, false, false, None})
|
|
if err != nil {
|
|
t.Fatalf("failed to query single node: %s", err.Error())
|
|
}
|
|
r, err = s.Query(&QueryRequest{[]string{`SELECT * FROM foo`}, false, false, None})
|
|
if err != nil {
|
|
t.Fatalf("failed to query single node: %s", err.Error())
|
|
}
|
|
if exp, got := `["id","name"]`, asJSON(r[0].Columns); exp != got {
|
|
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
|
|
}
|
|
if exp, got := `[[1,"fiona"]]`, asJSON(r[0].Values); exp != got {
|
|
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
|
|
}
|
|
}
|
|
|
|
func Test_SingleNodeExecuteQueryTx(t *testing.T) {
|
|
s := mustNewStore(true)
|
|
defer os.RemoveAll(s.Path())
|
|
|
|
if err := s.Open(true); err != nil {
|
|
t.Fatalf("failed to open single-node store: %s", err.Error())
|
|
}
|
|
defer s.Close(true)
|
|
s.WaitForLeader(10 * time.Second)
|
|
|
|
queries := []string{
|
|
`CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)`,
|
|
`INSERT INTO foo(id, name) VALUES(1, "fiona")`,
|
|
}
|
|
_, err := s.Execute(&ExecuteRequest{queries, false, true})
|
|
if err != nil {
|
|
t.Fatalf("failed to execute on single node: %s", err.Error())
|
|
}
|
|
r, err := s.Query(&QueryRequest{[]string{`SELECT * FROM foo`}, false, true, None})
|
|
if err != nil {
|
|
t.Fatalf("failed to query single node: %s", err.Error())
|
|
}
|
|
r, err = s.Query(&QueryRequest{[]string{`SELECT * FROM foo`}, false, true, Weak})
|
|
if err != nil {
|
|
t.Fatalf("failed to query single node: %s", err.Error())
|
|
}
|
|
r, err = s.Query(&QueryRequest{[]string{`SELECT * FROM foo`}, false, true, Strong})
|
|
if err != nil {
|
|
t.Fatalf("failed to query single node: %s", err.Error())
|
|
}
|
|
if exp, got := `["id","name"]`, asJSON(r[0].Columns); exp != got {
|
|
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
|
|
}
|
|
if exp, got := `[[1,"fiona"]]`, asJSON(r[0].Values); exp != got {
|
|
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
|
|
}
|
|
_, err = s.Execute(&ExecuteRequest{queries, false, true})
|
|
if err != nil {
|
|
t.Fatalf("failed to execute on single node: %s", err.Error())
|
|
}
|
|
}
|
|
|
|
func Test_SingleNodeLoad(t *testing.T) {
|
|
s := mustNewStore(true)
|
|
defer os.RemoveAll(s.Path())
|
|
|
|
if err := s.Open(true); err != nil {
|
|
t.Fatalf("failed to open single-node store: %s", err.Error())
|
|
}
|
|
defer s.Close(true)
|
|
s.WaitForLeader(10 * time.Second)
|
|
|
|
dump := `PRAGMA foreign_keys=OFF;
|
|
BEGIN TRANSACTION;
|
|
CREATE TABLE foo (id integer not null primary key, name text);
|
|
INSERT INTO "foo" VALUES(1,'fiona');
|
|
COMMIT;
|
|
`
|
|
_, err := s.Execute(&ExecuteRequest{[]string{dump}, false, false})
|
|
if err != nil {
|
|
t.Fatalf("failed to load simple dump: %s", err.Error())
|
|
}
|
|
|
|
// Check that data were loaded correctly.
|
|
r, err := s.Query(&QueryRequest{[]string{`SELECT * FROM foo`}, false, true, Strong})
|
|
if err != nil {
|
|
t.Fatalf("failed to query single node: %s", err.Error())
|
|
}
|
|
if exp, got := `["id","name"]`, asJSON(r[0].Columns); exp != got {
|
|
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
|
|
}
|
|
if exp, got := `[[1,"fiona"]]`, asJSON(r[0].Values); exp != got {
|
|
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
|
|
}
|
|
}
|
|
|
|
func Test_SingleNodeSingleCommandTrigger(t *testing.T) {
|
|
s := mustNewStore(true)
|
|
defer os.RemoveAll(s.Path())
|
|
|
|
if err := s.Open(true); err != nil {
|
|
t.Fatalf("failed to open single-node store: %s", err.Error())
|
|
}
|
|
defer s.Close(true)
|
|
s.WaitForLeader(10 * time.Second)
|
|
|
|
dump := `PRAGMA foreign_keys=OFF;
|
|
BEGIN TRANSACTION;
|
|
CREATE TABLE foo (id integer primary key asc, name text);
|
|
INSERT INTO "foo" VALUES(1,'bob');
|
|
INSERT INTO "foo" VALUES(2,'alice');
|
|
INSERT INTO "foo" VALUES(3,'eve');
|
|
CREATE TABLE bar (nameid integer, age integer);
|
|
INSERT INTO "bar" VALUES(1,44);
|
|
INSERT INTO "bar" VALUES(2,46);
|
|
INSERT INTO "bar" VALUES(3,8);
|
|
CREATE VIEW foobar as select name as Person, Age as age from foo inner join bar on foo.id == bar.nameid;
|
|
CREATE TRIGGER new_foobar instead of insert on foobar begin insert into foo (name) values (new.Person); insert into bar (nameid, age) values ((select id from foo where name == new.Person), new.Age); end;
|
|
COMMIT;
|
|
`
|
|
_, err := s.Execute(&ExecuteRequest{[]string{dump}, false, false})
|
|
if err != nil {
|
|
t.Fatalf("failed to load dump with trigger: %s", err.Error())
|
|
}
|
|
|
|
// Check that the VIEW and TRIGGER are OK by using both.
|
|
r, err := s.Execute(&ExecuteRequest{[]string{`INSERT INTO foobar VALUES('jason', 16)`}, false, true})
|
|
if err != nil {
|
|
t.Fatalf("failed to insert into view on single node: %s", err.Error())
|
|
}
|
|
if exp, got := int64(3), r[0].LastInsertID; exp != got {
|
|
t.Fatalf("unexpected results for query\nexp: %d\ngot: %d", exp, got)
|
|
}
|
|
}
|
|
|
|
func Test_SingleNodeLoadNoStatements(t *testing.T) {
|
|
s := mustNewStore(true)
|
|
defer os.RemoveAll(s.Path())
|
|
|
|
if err := s.Open(true); err != nil {
|
|
t.Fatalf("failed to open single-node store: %s", err.Error())
|
|
}
|
|
defer s.Close(true)
|
|
s.WaitForLeader(10 * time.Second)
|
|
|
|
dump := `PRAGMA foreign_keys=OFF;
|
|
BEGIN TRANSACTION;
|
|
COMMIT;
|
|
`
|
|
_, err := s.Execute(&ExecuteRequest{[]string{dump}, false, false})
|
|
if err != nil {
|
|
t.Fatalf("failed to load dump with no commands: %s", err.Error())
|
|
}
|
|
}
|
|
|
|
func Test_SingleNodeLoadEmpty(t *testing.T) {
|
|
s := mustNewStore(true)
|
|
defer os.RemoveAll(s.Path())
|
|
|
|
if err := s.Open(true); err != nil {
|
|
t.Fatalf("failed to open single-node store: %s", err.Error())
|
|
}
|
|
defer s.Close(true)
|
|
s.WaitForLeader(10 * time.Second)
|
|
|
|
dump := ``
|
|
_, err := s.Execute(&ExecuteRequest{[]string{dump}, false, false})
|
|
if err != nil {
|
|
t.Fatalf("failed to load empty dump: %s", err.Error())
|
|
}
|
|
}
|
|
|
|
func Test_SingleNodeLoadChinook(t *testing.T) {
|
|
s := mustNewStore(true)
|
|
defer os.RemoveAll(s.Path())
|
|
|
|
if err := s.Open(true); err != nil {
|
|
t.Fatalf("failed to open single-node store: %s", err.Error())
|
|
}
|
|
defer s.Close(true)
|
|
s.WaitForLeader(10 * time.Second)
|
|
|
|
_, err := s.Execute(&ExecuteRequest{[]string{chinook.DB}, false, false})
|
|
if err != nil {
|
|
t.Fatalf("failed to load chinook dump: %s", err.Error())
|
|
}
|
|
|
|
// Check that data were loaded correctly.
|
|
|
|
r, err := s.Query(&QueryRequest{[]string{`SELECT count(*) FROM track`}, false, true, Strong})
|
|
if err != nil {
|
|
t.Fatalf("failed to query single node: %s", err.Error())
|
|
}
|
|
if exp, got := `["count(*)"]`, asJSON(r[0].Columns); exp != got {
|
|
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
|
|
}
|
|
if exp, got := `[[3503]]`, asJSON(r[0].Values); exp != got {
|
|
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
|
|
}
|
|
|
|
r, err = s.Query(&QueryRequest{[]string{`SELECT count(*) FROM album`}, false, true, Strong})
|
|
if err != nil {
|
|
t.Fatalf("failed to query single node: %s", err.Error())
|
|
}
|
|
if exp, got := `["count(*)"]`, asJSON(r[0].Columns); exp != got {
|
|
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
|
|
}
|
|
if exp, got := `[[347]]`, asJSON(r[0].Values); exp != got {
|
|
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
|
|
}
|
|
|
|
r, err = s.Query(&QueryRequest{[]string{`SELECT count(*) FROM artist`}, false, true, Strong})
|
|
if err != nil {
|
|
t.Fatalf("failed to query single node: %s", err.Error())
|
|
}
|
|
if exp, got := `["count(*)"]`, asJSON(r[0].Columns); exp != got {
|
|
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
|
|
}
|
|
if exp, got := `[[275]]`, asJSON(r[0].Values); exp != got {
|
|
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
|
|
}
|
|
|
|
}
|
|
|
|
func Test_MultiNodeJoinRemove(t *testing.T) {
|
|
s0 := mustNewStore(true)
|
|
defer os.RemoveAll(s0.Path())
|
|
if err := s0.Open(true); err != nil {
|
|
t.Fatalf("failed to open node for multi-node test: %s", err.Error())
|
|
}
|
|
defer s0.Close(true)
|
|
s0.WaitForLeader(10 * time.Second)
|
|
|
|
s1 := mustNewStore(true)
|
|
defer os.RemoveAll(s1.Path())
|
|
if err := s1.Open(false); err != nil {
|
|
t.Fatalf("failed to open node for multi-node test: %s", err.Error())
|
|
}
|
|
defer s1.Close(true)
|
|
|
|
// Get sorted list of cluster nodes.
|
|
storeNodes := []string{s0.Addr().String(), s1.Addr().String()}
|
|
sort.StringSlice(storeNodes).Sort()
|
|
|
|
// Join the second node to the first.
|
|
if err := s0.Join(s1.Addr().String()); err != nil {
|
|
t.Fatalf("failed to join to node at %s: %s", s0.Addr().String(), err.Error())
|
|
}
|
|
|
|
nodes, err := s0.Nodes()
|
|
if err != nil {
|
|
t.Fatalf("failed to get nodes: %s", err.Error())
|
|
}
|
|
sort.StringSlice(nodes).Sort()
|
|
|
|
if len(nodes) != len(storeNodes) {
|
|
t.Fatalf("size of cluster is not correct")
|
|
}
|
|
if storeNodes[0] != nodes[0] && storeNodes[1] != nodes[1] {
|
|
t.Fatalf("cluster does not have correct nodes")
|
|
}
|
|
|
|
// Remove a node.
|
|
if err := s0.Remove(s1.Addr().String()); err != nil {
|
|
t.Fatalf("failed to remove %s from cluster: %s", s1.Addr().String(), err.Error())
|
|
}
|
|
|
|
nodes, err = s0.Nodes()
|
|
if err != nil {
|
|
t.Fatalf("failed to get nodes post remove: %s", err.Error())
|
|
}
|
|
if len(nodes) != 1 {
|
|
t.Fatalf("size of cluster is not correct post remove")
|
|
}
|
|
if s0.Addr().String() != nodes[0] {
|
|
t.Fatalf("cluster does not have correct nodes post remove")
|
|
}
|
|
}
|
|
|
|
func Test_MultiNodeExecuteQuery(t *testing.T) {
|
|
s0 := mustNewStore(true)
|
|
defer os.RemoveAll(s0.Path())
|
|
if err := s0.Open(true); err != nil {
|
|
t.Fatalf("failed to open node for multi-node test: %s", err.Error())
|
|
}
|
|
defer s0.Close(true)
|
|
s0.WaitForLeader(10 * time.Second)
|
|
|
|
s1 := mustNewStore(true)
|
|
defer os.RemoveAll(s1.Path())
|
|
if err := s1.Open(false); err != nil {
|
|
t.Fatalf("failed to open node for multi-node test: %s", err.Error())
|
|
}
|
|
defer s1.Close(true)
|
|
|
|
// Join the second node to the first.
|
|
if err := s0.Join(s1.Addr().String()); err != nil {
|
|
t.Fatalf("failed to join to node at %s: %s", s0.Addr().String(), err.Error())
|
|
}
|
|
|
|
queries := []string{
|
|
`CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)`,
|
|
`INSERT INTO foo(id, name) VALUES(1, "fiona")`,
|
|
}
|
|
_, err := s0.Execute(&ExecuteRequest{queries, false, false})
|
|
if err != nil {
|
|
t.Fatalf("failed to execute on single node: %s", err.Error())
|
|
}
|
|
r, err := s0.Query(&QueryRequest{[]string{`SELECT * FROM foo`}, false, false, None})
|
|
if err != nil {
|
|
t.Fatalf("failed to query single node: %s", err.Error())
|
|
}
|
|
if exp, got := `["id","name"]`, asJSON(r[0].Columns); exp != got {
|
|
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
|
|
}
|
|
if exp, got := `[[1,"fiona"]]`, asJSON(r[0].Values); exp != got {
|
|
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
|
|
}
|
|
|
|
// Wait until the 3 log entries have been applied to the follower,
|
|
// and then query.
|
|
if err := s1.WaitForAppliedIndex(3, 5*time.Second); err != nil {
|
|
t.Fatalf("error waiting for follower to apply index: %s:", err.Error())
|
|
}
|
|
r, err = s1.Query(&QueryRequest{[]string{`SELECT * FROM foo`}, false, false, Weak})
|
|
if err == nil {
|
|
t.Fatalf("successfully queried non-leader node")
|
|
}
|
|
r, err = s1.Query(&QueryRequest{[]string{`SELECT * FROM foo`}, false, false, Strong})
|
|
if err == nil {
|
|
t.Fatalf("successfully queried non-leader node")
|
|
}
|
|
r, err = s1.Query(&QueryRequest{[]string{`SELECT * FROM foo`}, false, false, None})
|
|
if err != nil {
|
|
t.Fatalf("failed to query single node: %s", err.Error())
|
|
}
|
|
if exp, got := `["id","name"]`, asJSON(r[0].Columns); exp != got {
|
|
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
|
|
}
|
|
if exp, got := `[[1,"fiona"]]`, asJSON(r[0].Values); exp != got {
|
|
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
|
|
}
|
|
}
|
|
|
|
func Test_SingleNodeSnapshotOnDisk(t *testing.T) {
|
|
s := mustNewStore(false)
|
|
defer os.RemoveAll(s.Path())
|
|
|
|
if err := s.Open(true); err != nil {
|
|
t.Fatalf("failed to open single-node store: %s", err.Error())
|
|
}
|
|
defer s.Close(true)
|
|
s.WaitForLeader(10 * time.Second)
|
|
|
|
queries := []string{
|
|
`CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)`,
|
|
`INSERT INTO foo(id, name) VALUES(1, "fiona")`,
|
|
}
|
|
_, err := s.Execute(&ExecuteRequest{queries, false, false})
|
|
if err != nil {
|
|
t.Fatalf("failed to execute on single node: %s", err.Error())
|
|
}
|
|
_, err = s.Query(&QueryRequest{[]string{`SELECT * FROM foo`}, false, false, None})
|
|
if err != nil {
|
|
t.Fatalf("failed to query single node: %s", err.Error())
|
|
}
|
|
|
|
// Snap the node and write to disk.
|
|
f, err := s.Snapshot()
|
|
if err != nil {
|
|
t.Fatalf("failed to snapshot node: %s", err.Error())
|
|
}
|
|
|
|
snapDir := mustTempDir()
|
|
defer os.RemoveAll(snapDir)
|
|
snapFile, err := os.Create(filepath.Join(snapDir, "snapshot"))
|
|
if err != nil {
|
|
t.Fatalf("failed to create snapshot file: %s", err.Error())
|
|
}
|
|
sink := &mockSnapshotSink{snapFile}
|
|
if err := f.Persist(sink); err != nil {
|
|
t.Fatalf("failed to persist snapshot to disk: %s", err.Error())
|
|
}
|
|
|
|
// Check restoration.
|
|
snapFile, err = os.Open(filepath.Join(snapDir, "snapshot"))
|
|
if err != nil {
|
|
t.Fatalf("failed to open snapshot file: %s", err.Error())
|
|
}
|
|
if err := s.Restore(snapFile); err != nil {
|
|
t.Fatalf("failed to restore snapshot from disk: %s", err.Error())
|
|
}
|
|
|
|
// Ensure database is back in the correct state.
|
|
r, err := s.Query(&QueryRequest{[]string{`SELECT * FROM foo`}, false, false, None})
|
|
if err != nil {
|
|
t.Fatalf("failed to query single node: %s", err.Error())
|
|
}
|
|
if exp, got := `["id","name"]`, asJSON(r[0].Columns); exp != got {
|
|
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
|
|
}
|
|
if exp, got := `[[1,"fiona"]]`, asJSON(r[0].Values); exp != got {
|
|
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
|
|
}
|
|
}
|
|
|
|
func Test_SingleNodeSnapshotInMem(t *testing.T) {
|
|
s := mustNewStore(true)
|
|
defer os.RemoveAll(s.Path())
|
|
|
|
if err := s.Open(true); err != nil {
|
|
t.Fatalf("failed to open single-node store: %s", err.Error())
|
|
}
|
|
defer s.Close(true)
|
|
s.WaitForLeader(10 * time.Second)
|
|
|
|
queries := []string{
|
|
`CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)`,
|
|
`INSERT INTO foo(id, name) VALUES(1, "fiona")`,
|
|
}
|
|
_, err := s.Execute(&ExecuteRequest{queries, false, false})
|
|
if err != nil {
|
|
t.Fatalf("failed to execute on single node: %s", err.Error())
|
|
}
|
|
_, err = s.Query(&QueryRequest{[]string{`SELECT * FROM foo`}, false, false, None})
|
|
if err != nil {
|
|
t.Fatalf("failed to query single node: %s", err.Error())
|
|
}
|
|
|
|
// Snap the node and write to disk.
|
|
f, err := s.Snapshot()
|
|
if err != nil {
|
|
t.Fatalf("failed to snapshot node: %s", err.Error())
|
|
}
|
|
|
|
snapDir := mustTempDir()
|
|
defer os.RemoveAll(snapDir)
|
|
snapFile, err := os.Create(filepath.Join(snapDir, "snapshot"))
|
|
if err != nil {
|
|
t.Fatalf("failed to create snapshot file: %s", err.Error())
|
|
}
|
|
sink := &mockSnapshotSink{snapFile}
|
|
if err := f.Persist(sink); err != nil {
|
|
t.Fatalf("failed to persist snapshot to disk: %s", err.Error())
|
|
}
|
|
|
|
// Check restoration.
|
|
snapFile, err = os.Open(filepath.Join(snapDir, "snapshot"))
|
|
if err != nil {
|
|
t.Fatalf("failed to open snapshot file: %s", err.Error())
|
|
}
|
|
if err := s.Restore(snapFile); err != nil {
|
|
t.Fatalf("failed to restore snapshot from disk: %s", err.Error())
|
|
}
|
|
|
|
// Ensure database is back in the correct state.
|
|
r, err := s.Query(&QueryRequest{[]string{`SELECT * FROM foo`}, false, false, None})
|
|
if err != nil {
|
|
t.Fatalf("failed to query single node: %s", err.Error())
|
|
}
|
|
if exp, got := `["id","name"]`, asJSON(r[0].Columns); exp != got {
|
|
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
|
|
}
|
|
if exp, got := `[[1,"fiona"]]`, asJSON(r[0].Values); exp != got {
|
|
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
|
|
}
|
|
}
|
|
|
|
func Test_APIPeers(t *testing.T) {
|
|
s := mustNewStore(false)
|
|
defer os.RemoveAll(s.Path())
|
|
|
|
if err := s.Open(true); err != nil {
|
|
t.Fatalf("failed to open single-node store: %s", err.Error())
|
|
}
|
|
defer s.Close(true)
|
|
s.WaitForLeader(10 * time.Second)
|
|
|
|
peers := map[string]string{
|
|
"localhost:4002": "localhost:4001",
|
|
"localhost:4004": "localhost:4003",
|
|
}
|
|
if err := s.UpdateAPIPeers(peers); err != nil {
|
|
t.Fatalf("failed to update API peers: %s", err.Error())
|
|
}
|
|
|
|
// Retrieve peers and verify them.
|
|
apiPeers, err := s.APIPeers()
|
|
if err != nil {
|
|
t.Fatalf("failed to retrieve API peers: %s", err.Error())
|
|
}
|
|
if !reflect.DeepEqual(peers, apiPeers) {
|
|
t.Fatalf("set and retrieved API peers not identical, got %v, exp %v",
|
|
apiPeers, peers)
|
|
}
|
|
|
|
if s.Peer("localhost:4002") != "localhost:4001" ||
|
|
s.Peer("localhost:4004") != "localhost:4003" ||
|
|
s.Peer("not exist") != "" {
|
|
t.Fatalf("failed to retrieve correct single API peer")
|
|
}
|
|
}
|
|
|
|
func Test_IsLeader(t *testing.T) {
|
|
s := mustNewStore(true)
|
|
defer os.RemoveAll(s.Path())
|
|
|
|
if err := s.Open(true); err != nil {
|
|
t.Fatalf("failed to open single-node store: %s", err.Error())
|
|
}
|
|
defer s.Close(true)
|
|
s.WaitForLeader(10 * time.Second)
|
|
|
|
if !s.IsLeader() {
|
|
t.Fatalf("single node is not leader!")
|
|
}
|
|
}
|
|
|
|
func Test_State(t *testing.T) {
|
|
s := mustNewStore(true)
|
|
defer os.RemoveAll(s.Path())
|
|
|
|
if err := s.Open(true); err != nil {
|
|
t.Fatalf("failed to open single-node store: %s", err.Error())
|
|
}
|
|
defer s.Close(true)
|
|
s.WaitForLeader(10 * time.Second)
|
|
|
|
state := s.State()
|
|
if state != Leader {
|
|
t.Fatalf("single node returned incorrect state (not Leader): %v", s)
|
|
}
|
|
}
|
|
|
|
func mustNewStore(inmem bool) *Store {
|
|
path := mustTempDir()
|
|
defer os.RemoveAll(path)
|
|
|
|
cfg := NewDBConfig("", inmem)
|
|
s := New(&StoreConfig{
|
|
DBConf: cfg,
|
|
Dir: path,
|
|
Tn: mustMockTransport("localhost:0"),
|
|
})
|
|
if s == nil {
|
|
panic("failed to create new store")
|
|
}
|
|
return s
|
|
}
|
|
|
|
func mustTempDir() string {
|
|
var err error
|
|
path, err := ioutil.TempDir("", "rqlilte-test-")
|
|
if err != nil {
|
|
panic("failed to create temp dir")
|
|
}
|
|
return path
|
|
}
|
|
|
|
type mockTransport struct {
|
|
ln net.Listener
|
|
}
|
|
|
|
func mustMockTransport(addr string) Transport {
|
|
ln, err := net.Listen("tcp", addr)
|
|
if err != nil {
|
|
panic("failed to create new transport")
|
|
}
|
|
return &mockTransport{ln}
|
|
}
|
|
|
|
func (m *mockTransport) Dial(addr string, timeout time.Duration) (net.Conn, error) {
|
|
return net.DialTimeout("tcp", addr, timeout)
|
|
}
|
|
|
|
func (m *mockTransport) Accept() (net.Conn, error) { return m.ln.Accept() }
|
|
|
|
func (m *mockTransport) Close() error { return m.ln.Close() }
|
|
|
|
func (m *mockTransport) Addr() net.Addr { return m.ln.Addr() }
|
|
|
|
func asJSON(v interface{}) string {
|
|
b, err := json.Marshal(v)
|
|
if err != nil {
|
|
panic("failed to JSON marshal value")
|
|
}
|
|
return string(b)
|
|
}
|