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_StoreLogTruncationMultinode(t *testing.T) { s0 := mustNewStore(true) defer os.RemoveAll(s0.Path()) s0.SnapshotThreshold = 4 s0.SnapshotInterval = 100 * time.Millisecond if err := s0.Open(true); err != nil { t.Fatalf("failed to open single-node store: %s", err.Error()) } defer s0.Close(true) s0.WaitForLeader(10 * time.Second) nSnaps := stats.Get(numSnaphots).String() // Write more than s.SnapshotThreshold statements. queries := []string{ `CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)`, `INSERT INTO foo(id, name) VALUES(1, "fiona")`, `INSERT INTO foo(id, name) VALUES(2, "fiona")`, `INSERT INTO foo(id, name) VALUES(3, "fiona")`, `INSERT INTO foo(id, name) VALUES(4, "fiona")`, `INSERT INTO foo(id, name) VALUES(5, "fiona")`, } for i := range queries { _, err := s0.Execute(&ExecuteRequest{[]string{queries[i]}, false, false}) if err != nil { t.Fatalf("failed to execute on single node: %s", err.Error()) } } // Wait for the snapshot to happen and log to be truncated. for { time.Sleep(1000 * time.Millisecond) if stats.Get(numSnaphots).String() != nSnaps { // It's changed, so a snap and truncate has happened. break } } // Fire up new node and ensure it picks up all changes. This will // involve getting a snapshot and truncated log. s1 := mustNewStore(true) if err := s1.Open(true); err != nil { t.Fatalf("failed to open single-node store: %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(), err.Error()) } s1.WaitForLeader(10 * time.Second) // Wait until the log entries have been applied to the follower, // and then query. if err := s1.WaitForAppliedIndex(8, 5*time.Second); err != nil { t.Fatalf("error waiting for follower to apply index: %s:", err.Error()) } r, err := s1.Query(&QueryRequest{[]string{`SELECT count(*) FROM foo`}, false, true, None}) 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 := `[[5]]`, asJSON(r[0].Values); 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_SingleNodeLoadAbortOnError(t *testing.T) { t.Parallel() 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); COMMIT; ` r, err := s.Execute(&ExecuteRequest{[]string{dump}, false, false}) if err != nil { t.Fatalf("failed to load commands: %s", err.Error()) } if r[0].Error != "" { t.Fatalf("error received creating table: %s", r[0].Error) } r, err = s.Execute(&ExecuteRequest{[]string{dump}, false, false}) if err != nil { t.Fatalf("failed to load commands: %s", err.Error()) } if r[0].Error != "table foo already exists" { t.Fatalf("received wrong error message: %s", r[0].Error) } r, err = s.Execute(&ExecuteRequest{[]string{dump}, false, false}) if err != nil { t.Fatalf("failed to load commands: %s", err.Error()) } if r[0].Error != "cannot start a transaction within a transaction" { t.Fatalf("received wrong error message: %s", r[0].Error) } r, err = s.ExecuteOrAbort(&ExecuteRequest{[]string{dump}, false, false}) if err != nil { t.Fatalf("failed to load commands: %s", err.Error()) } if r[0].Error != "cannot start a transaction within a transaction" { t.Fatalf("received wrong error message: %s", r[0].Error) } r, err = s.Execute(&ExecuteRequest{[]string{dump}, false, false}) if err != nil { t.Fatalf("failed to load commands: %s", err.Error()) } if r[0].Error != "table foo already exists" { t.Fatalf("received wrong error message: %s", r[0].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) }