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.
1406 lines
40 KiB
Go
1406 lines
40 KiB
Go
/*
|
|
Package system runs system-level testing of rqlite. This includes testing of single nodes, and multi-node clusters.
|
|
*/
|
|
package system
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/rqlite/rqlite/v8/cluster"
|
|
httpd "github.com/rqlite/rqlite/v8/http"
|
|
"github.com/rqlite/rqlite/v8/store"
|
|
"github.com/rqlite/rqlite/v8/tcp"
|
|
)
|
|
|
|
func Test_SingleNodeBasicEndpoint(t *testing.T) {
|
|
node := mustNewLeaderNode("node1")
|
|
defer node.Deprovision()
|
|
|
|
// Ensure accessing endpoints in basic manner works
|
|
_, err := node.Status()
|
|
if err != nil {
|
|
t.Fatalf(`failed to retrieve status: %s`, err)
|
|
}
|
|
|
|
dir := mustTempDir("node1")
|
|
mux, ln := mustNewOpenMux("")
|
|
defer ln.Close()
|
|
raftDialer := tcp.NewDialer(cluster.MuxRaftHeader, nil)
|
|
clstrDialer := tcp.NewDialer(cluster.MuxClusterHeader, nil)
|
|
node = mustNodeEncrypted("node1", dir, true, false, mux, raftDialer, clstrDialer)
|
|
if _, err := node.WaitForLeader(); err != nil {
|
|
t.Fatalf("node never became leader")
|
|
}
|
|
_, err = node.Status()
|
|
if err != nil {
|
|
t.Fatalf(`failed to retrieve status for: %s`, err)
|
|
}
|
|
|
|
ready, err := node.Ready()
|
|
if err != nil {
|
|
t.Fatalf(`failed to retrieve readiness: %s`, err)
|
|
}
|
|
if !ready {
|
|
t.Fatalf("node is not ready")
|
|
}
|
|
|
|
// Test that registering a ready channel affects readiness as expected.
|
|
ch := make(chan struct{})
|
|
node.Store.RegisterReadyChannel(ch)
|
|
ready, err = node.Ready()
|
|
if err != nil {
|
|
t.Fatalf(`failed to retrieve readiness: %s`, err)
|
|
}
|
|
if ready {
|
|
t.Fatalf("node is ready when registered ready channel not yet closed")
|
|
}
|
|
close(ch)
|
|
testPoll(t, node.Ready, 100*time.Millisecond, 5*time.Second)
|
|
}
|
|
|
|
func Test_SingleNodeNotReadyLive(t *testing.T) {
|
|
node := mustNewNode("node1", false)
|
|
defer node.Deprovision()
|
|
ready, err := node.Ready()
|
|
if err != nil {
|
|
t.Fatalf(`failed to retrieve readiness: %s`, err)
|
|
}
|
|
if ready {
|
|
t.Fatalf("node is ready when it should not be")
|
|
}
|
|
|
|
liveness, err := node.Liveness()
|
|
if err != nil {
|
|
t.Fatalf(`failed to retrieve liveness: %s`, err)
|
|
}
|
|
if !liveness {
|
|
t.Fatalf("node is not live when it should not be")
|
|
}
|
|
|
|
// Confirm that hitting various endpoints with a non-ready node
|
|
// is OK.
|
|
_, err = node.Nodes(false)
|
|
if err != nil {
|
|
t.Fatalf(`failed to retrieve Nodes: %s`, err)
|
|
}
|
|
_, err = node.Status()
|
|
if err != nil {
|
|
t.Fatalf(`failed to retrieve status: %s`, err)
|
|
}
|
|
}
|
|
|
|
func Test_SingleNode(t *testing.T) {
|
|
node := mustNewLeaderNode("node1")
|
|
defer node.Deprovision()
|
|
|
|
tests := []struct {
|
|
stmt string
|
|
expected string
|
|
execute bool
|
|
}{
|
|
{
|
|
stmt: `CREATE TABLE foo (id integer not null primary key, name text)`,
|
|
expected: `{"results":[{}]}`,
|
|
execute: true,
|
|
},
|
|
{
|
|
stmt: `INSERT INTO foo(name) VALUES("fiona")`,
|
|
expected: `{"results":[{"last_insert_id":1,"rows_affected":1}]}`,
|
|
execute: true,
|
|
},
|
|
{
|
|
stmt: `INSERT INTO bar(name) VALUES("fiona")`,
|
|
expected: `{"results":[{"error":"no such table: bar"}]}`,
|
|
execute: true,
|
|
},
|
|
{
|
|
stmt: `INSERT blah blah`,
|
|
expected: `{"results":[{"error":"near \"blah\": syntax error"}]}`,
|
|
execute: true,
|
|
},
|
|
{
|
|
stmt: `SELECT * FROM foo`,
|
|
expected: `{"results":[{"columns":["id","name"],"types":["integer","text"],"values":[[1,"fiona"]]}]}`,
|
|
execute: false,
|
|
},
|
|
{
|
|
stmt: `DROP TABLE bar`,
|
|
expected: `{"results":[{"error":"no such table: bar"}]}`,
|
|
execute: true,
|
|
},
|
|
{
|
|
stmt: `DROP TABLE foo`,
|
|
expected: `{"results":[{"last_insert_id":1,"rows_affected":1}]}`,
|
|
execute: true,
|
|
},
|
|
}
|
|
|
|
for i, tt := range tests {
|
|
var r string
|
|
var err error
|
|
if tt.execute {
|
|
r, err = node.Execute(tt.stmt)
|
|
} else {
|
|
r, err = node.Query(tt.stmt)
|
|
}
|
|
if err != nil {
|
|
t.Fatalf(`test %d failed "%s": %s`, i, tt.stmt, err.Error())
|
|
}
|
|
if r != tt.expected {
|
|
t.Fatalf(`test %d received wrong result "%s" got: %s exp: %s`, i, tt.stmt, r, tt.expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
func Test_SingleNodeRequest(t *testing.T) {
|
|
node := mustNewLeaderNode("leader1")
|
|
defer node.Deprovision()
|
|
|
|
tests := []struct {
|
|
stmt string
|
|
expected string
|
|
}{
|
|
{
|
|
stmt: `CREATE TABLE foo (id integer not null primary key, name text)`,
|
|
expected: `{"results":[{}]}`,
|
|
},
|
|
{
|
|
stmt: `INSERT INTO foo(name) VALUES("fiona")`,
|
|
expected: `{"results":[{"last_insert_id":1,"rows_affected":1}]}`,
|
|
},
|
|
{
|
|
stmt: `INSERT INTO bar(name) VALUES("fiona")`,
|
|
expected: `{"results":[{"error":"no such table: bar"}]}`,
|
|
},
|
|
{
|
|
stmt: `INSERT blah blah`,
|
|
expected: `{"results":[{"error":"near \"blah\": syntax error"}]}`,
|
|
},
|
|
{
|
|
stmt: `SELECT * FROM foo`,
|
|
expected: `{"results":[{"columns":["id","name"],"types":["integer","text"],"values":[[1,"fiona"]]}]}`,
|
|
},
|
|
{
|
|
stmt: `DROP TABLE bar`,
|
|
expected: `{"results":[{"error":"no such table: bar"}]}`,
|
|
},
|
|
{
|
|
stmt: `DROP TABLE foo`,
|
|
expected: `{"results":[{"last_insert_id":1,"rows_affected":1}]}`,
|
|
},
|
|
}
|
|
|
|
for i, tt := range tests {
|
|
r, err := node.Request(tt.stmt)
|
|
if err != nil {
|
|
t.Fatalf(`test %d failed "%s": %s`, i, tt.stmt, err.Error())
|
|
}
|
|
if r != tt.expected {
|
|
t.Fatalf(`test %d received wrong result "%s" got: %s exp: %s`, i, tt.stmt, r, tt.expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
func Test_SingleNodeMulti(t *testing.T) {
|
|
node := mustNewLeaderNode("leader1")
|
|
defer node.Deprovision()
|
|
|
|
tests := []struct {
|
|
stmt string
|
|
expected string
|
|
execute bool
|
|
}{
|
|
{
|
|
stmt: `CREATE TABLE foo (id integer not null primary key, name text)`,
|
|
expected: `{"results":[{}]}`,
|
|
execute: true,
|
|
},
|
|
{
|
|
stmt: `CREATE TABLE bar (id integer not null primary key, sequence integer)`,
|
|
expected: `{"results":[{}]}`,
|
|
execute: true,
|
|
},
|
|
{
|
|
stmt: `INSERT INTO foo(name) VALUES("fiona")`,
|
|
expected: `{"results":[{"last_insert_id":1,"rows_affected":1}]}`,
|
|
execute: true,
|
|
},
|
|
{
|
|
stmt: `INSERT INTO foo(name) VALUES("declan")`,
|
|
expected: `{"results":[{"last_insert_id":2,"rows_affected":1}]}`,
|
|
execute: true,
|
|
},
|
|
{
|
|
stmt: `INSERT INTO bar(sequence) VALUES(5)`,
|
|
expected: `{"results":[{"last_insert_id":1,"rows_affected":1}]}`,
|
|
execute: true,
|
|
},
|
|
}
|
|
|
|
for i, tt := range tests {
|
|
var r string
|
|
var err error
|
|
if tt.execute {
|
|
r, err = node.Execute(tt.stmt)
|
|
} else {
|
|
r, err = node.Query(tt.stmt)
|
|
}
|
|
if err != nil {
|
|
t.Fatalf(`test %d failed "%s": %s`, i, tt.stmt, err.Error())
|
|
}
|
|
if r != tt.expected {
|
|
t.Fatalf(`test %d received wrong result "%s" got: %s exp: %s`, i, tt.stmt, r, tt.expected)
|
|
}
|
|
}
|
|
|
|
r, err := node.QueryMulti([]string{"SELECT * FROM foo", "SELECT * FROM bar"})
|
|
if err != nil {
|
|
t.Fatalf("failed to run multiple queries: %s", err.Error())
|
|
}
|
|
if r != `{"results":[{"columns":["id","name"],"types":["integer","text"],"values":[[1,"fiona"],[2,"declan"]]},{"columns":["id","sequence"],"types":["integer","integer"],"values":[[1,5]]}]}` {
|
|
t.Fatalf("test received wrong result got %s", r)
|
|
}
|
|
}
|
|
|
|
func Test_SingleNodeConcurrentRequests(t *testing.T) {
|
|
var err error
|
|
node := mustNewLeaderNode("leader1")
|
|
node.Store.SetRequestCompression(1024, 1024) // Ensure no compression
|
|
defer node.Deprovision()
|
|
|
|
_, err = node.Execute(`CREATE TABLE foo (id integer not null primary key, name text)`)
|
|
if err != nil {
|
|
t.Fatalf("failed to create table: %s", err.Error())
|
|
}
|
|
|
|
var wg sync.WaitGroup
|
|
for i := 0; i < 200; i++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
resp, err := PostExecuteStmt(node.APIAddr, `INSERT INTO foo(name) VALUES("fiona")`)
|
|
if err != nil {
|
|
t.Logf("failed to insert record: %s %s", err.Error(), resp)
|
|
}
|
|
}()
|
|
}
|
|
|
|
wg.Wait()
|
|
r, err := node.Query("SELECT COUNT(*) FROM foo")
|
|
if err != nil {
|
|
t.Fatalf("failed to count records: %s", err.Error())
|
|
}
|
|
if r != `{"results":[{"columns":["COUNT(*)"],"types":["integer"],"values":[[200]]}]}` {
|
|
t.Fatalf("test received wrong result got %s", r)
|
|
}
|
|
}
|
|
|
|
func Test_SingleNodeConcurrentRequestsCompressed(t *testing.T) {
|
|
var err error
|
|
node := mustNewLeaderNode("leader1")
|
|
node.Store.SetRequestCompression(0, 0) // Ensure compression
|
|
defer node.Deprovision()
|
|
|
|
_, err = node.Execute(`CREATE TABLE foo (id integer not null primary key, name text)`)
|
|
if err != nil {
|
|
t.Fatalf("failed to create table: %s", err.Error())
|
|
}
|
|
|
|
var wg sync.WaitGroup
|
|
for i := 0; i < 200; i++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
resp, err := PostExecuteStmt(node.APIAddr, `INSERT INTO foo(name) VALUES("fiona")`)
|
|
if err != nil {
|
|
t.Logf("failed to insert record: %s %s", err.Error(), resp)
|
|
}
|
|
}()
|
|
}
|
|
|
|
wg.Wait()
|
|
r, err := node.Query("SELECT COUNT(*) FROM foo")
|
|
if err != nil {
|
|
t.Fatalf("failed to count records: %s", err.Error())
|
|
}
|
|
if r != `{"results":[{"columns":["COUNT(*)"],"types":["integer"],"values":[[200]]}]}` {
|
|
t.Fatalf("test received wrong result got %s", r)
|
|
}
|
|
}
|
|
|
|
func Test_SingleNodeParameterized(t *testing.T) {
|
|
node := mustNewLeaderNode("leader1")
|
|
defer node.Deprovision()
|
|
|
|
tests := []struct {
|
|
stmt []interface{}
|
|
expected string
|
|
execute bool
|
|
}{
|
|
{
|
|
stmt: []interface{}{"CREATE TABLE foo (id integer not null primary key, name text, age integer)"},
|
|
expected: `{"results":[{}]}`,
|
|
execute: true,
|
|
},
|
|
{
|
|
stmt: []interface{}{"INSERT INTO foo(name, age) VALUES(?, ?)", "fiona", 20},
|
|
expected: `{"results":[{"last_insert_id":1,"rows_affected":1}]}`,
|
|
execute: true,
|
|
},
|
|
{
|
|
stmt: []interface{}{"SELECT * FROM foo WHERE NAME=?", "fiona"},
|
|
expected: `{"results":[{"columns":["id","name","age"],"types":["integer","text","integer"],"values":[[1,"fiona",20]]}]}`,
|
|
execute: false,
|
|
},
|
|
}
|
|
|
|
for i, tt := range tests {
|
|
var r string
|
|
var err error
|
|
if tt.execute {
|
|
r, err = node.ExecuteParameterized(tt.stmt)
|
|
} else {
|
|
r, err = node.QueryParameterized(tt.stmt)
|
|
}
|
|
if err != nil {
|
|
t.Fatalf(`test %d failed "%s": %s`, i, tt.stmt, err.Error())
|
|
}
|
|
if r != tt.expected {
|
|
t.Fatalf(`test %d received wrong result "%s" got: %s exp: %s`, i, tt.stmt, r, tt.expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
func Test_SingleNodeRequestParameterized(t *testing.T) {
|
|
node := mustNewLeaderNode("leader1")
|
|
defer node.Deprovision()
|
|
|
|
tests := []struct {
|
|
stmt []interface{}
|
|
expected string
|
|
}{
|
|
{
|
|
stmt: []interface{}{"CREATE TABLE foo (id integer not null primary key, name text, age integer)"},
|
|
expected: `{"results":[{}]}`,
|
|
},
|
|
{
|
|
stmt: []interface{}{"INSERT INTO foo(name, age) VALUES(?, ?)", "fiona", 20},
|
|
expected: `{"results":[{"last_insert_id":1,"rows_affected":1}]}`,
|
|
},
|
|
{
|
|
stmt: []interface{}{"SELECT * FROM foo WHERE NAME=?", "fiona"},
|
|
expected: `{"results":[{"columns":["id","name","age"],"types":["integer","text","integer"],"values":[[1,"fiona",20]]}]}`,
|
|
},
|
|
}
|
|
|
|
for i, tt := range tests {
|
|
r, err := node.RequestMultiParameterized(tt.stmt)
|
|
if err != nil {
|
|
t.Fatalf(`test %d failed "%s": %s`, i, tt.stmt, err.Error())
|
|
}
|
|
if r != tt.expected {
|
|
t.Fatalf(`test %d received wrong result "%s" got: %s exp: %s`, i, tt.stmt, r, tt.expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
func Test_SingleNodeParameterizedNull(t *testing.T) {
|
|
node := mustNewLeaderNode("leader1")
|
|
defer node.Deprovision()
|
|
|
|
tests := []struct {
|
|
stmt []interface{}
|
|
expected string
|
|
execute bool
|
|
}{
|
|
{
|
|
stmt: []interface{}{"CREATE TABLE foo (id integer not null primary key, name text, age integer)"},
|
|
expected: `{"results":[{}]}`,
|
|
execute: true,
|
|
},
|
|
{
|
|
stmt: []interface{}{"INSERT INTO foo(name, age) VALUES(?, ?)", "declan", nil},
|
|
expected: `{"results":[{"last_insert_id":1,"rows_affected":1}]}`,
|
|
execute: true,
|
|
},
|
|
{
|
|
stmt: []interface{}{"SELECT * FROM foo WHERE NAME=?", "declan"},
|
|
expected: `{"results":[{"columns":["id","name","age"],"types":["integer","text","integer"],"values":[[1,"declan",null]]}]}`,
|
|
execute: false,
|
|
},
|
|
}
|
|
|
|
for i, tt := range tests {
|
|
var r string
|
|
var err error
|
|
if tt.execute {
|
|
r, err = node.ExecuteParameterized(tt.stmt)
|
|
} else {
|
|
r, err = node.QueryParameterized(tt.stmt)
|
|
}
|
|
if err != nil {
|
|
t.Fatalf(`test %d failed "%s": %s`, i, tt.stmt, err.Error())
|
|
}
|
|
if r != tt.expected {
|
|
t.Fatalf(`test %d received wrong result "%s" got: %s exp: %s`, i, tt.stmt, r, tt.expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
func Test_SingleNodeParameterizedNamed(t *testing.T) {
|
|
node := mustNewLeaderNode("leader1")
|
|
defer node.Deprovision()
|
|
|
|
tests := []struct {
|
|
stmt []interface{}
|
|
expected string
|
|
execute bool
|
|
}{
|
|
{
|
|
stmt: []interface{}{"CREATE TABLE foo (id integer not null primary key, name text, age integer)"},
|
|
expected: `{"results":[{}]}`,
|
|
execute: true,
|
|
},
|
|
{
|
|
stmt: []interface{}{"INSERT INTO foo(name, age) VALUES(?, ?)", "fiona", 20},
|
|
expected: `{"results":[{"last_insert_id":1,"rows_affected":1}]}`,
|
|
execute: true,
|
|
},
|
|
{
|
|
stmt: []interface{}{"SELECT * FROM foo WHERE NAME=:name", map[string]interface{}{"name": "fiona"}},
|
|
expected: `{"results":[{"columns":["id","name","age"],"types":["integer","text","integer"],"values":[[1,"fiona",20]]}]}`,
|
|
execute: false,
|
|
},
|
|
}
|
|
|
|
for i, tt := range tests {
|
|
var r string
|
|
var err error
|
|
if tt.execute {
|
|
r, err = node.ExecuteParameterized(tt.stmt)
|
|
} else {
|
|
r, err = node.QueryParameterized(tt.stmt)
|
|
}
|
|
if err != nil {
|
|
t.Fatalf(`test %d failed "%s": %s`, i, tt.stmt, err.Error())
|
|
}
|
|
if r != tt.expected {
|
|
t.Fatalf(`test %d received wrong result "%s" got: %s exp: %s`, i, tt.stmt, r, tt.expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test_SingleNodeParameterizedNamedConstraints tests that named parameters can be used with constraints
|
|
// See https://github.com/rqlite/rqlite/issues/1177
|
|
func Test_SingleNodeParameterizedNamedConstraints(t *testing.T) {
|
|
node := mustNewLeaderNode("leader1")
|
|
defer node.Deprovision()
|
|
|
|
_, err := node.ExecuteParameterized([]interface{}{"CREATE TABLE [TestTable] ([Id] int primary key, [Col1] int not null, [Col2] varchar(500), [Col3] int not null, [Col4] varchar(500))"})
|
|
if err != nil {
|
|
t.Fatalf("failed to create table: %s", err.Error())
|
|
}
|
|
|
|
for i := 0; i < 100; i++ {
|
|
r, err := node.ExecuteParameterized([]interface{}{"INSERT into TestTable (Col1, Col2, Col3, Col4) values (:Val1, :Val2, :Val3, :Val4)", map[string]interface{}{"Val1": 1, "Val2": "foo", "Val3": 2, "Val4": nil}})
|
|
if err != nil {
|
|
t.Fatalf("failed to insert record on loop %d: %s", i, err.Error())
|
|
}
|
|
if r != fmt.Sprintf(`{"results":[{"last_insert_id":%d,"rows_affected":1}]}`, i+1) {
|
|
t.Fatalf("test received wrong result on loop %d, got %s", i, r)
|
|
}
|
|
}
|
|
}
|
|
|
|
func Test_SingleNodeRewriteRandom(t *testing.T) {
|
|
node := mustNewLeaderNode("leader1")
|
|
defer node.Deprovision()
|
|
|
|
_, err := node.Execute(`CREATE TABLE foo (id integer not null primary key, name text)`)
|
|
if err != nil {
|
|
t.Fatalf(`CREATE TABLE failed: %s`, err.Error())
|
|
}
|
|
|
|
resp, err := node.Execute(`INSERT INTO foo(id, name) VALUES(RANDOM(), "fiona")`)
|
|
if err != nil {
|
|
t.Fatalf(`queued write failed: %s`, err.Error())
|
|
}
|
|
|
|
match := regexp.MustCompile(`{"results":[{"last_insert_id":\-?[0-9]+,"rows_affected":1}]}`)
|
|
if !match.MatchString(resp) {
|
|
t.Fatalf("test received wrong result got %s", resp)
|
|
}
|
|
}
|
|
|
|
func Test_SingleNodeQueued(t *testing.T) {
|
|
node := mustNewLeaderNode("leader1")
|
|
defer node.Deprovision()
|
|
|
|
_, err := node.Execute(`CREATE TABLE foo (id integer not null primary key, name text)`)
|
|
if err != nil {
|
|
t.Fatalf(`CREATE TABLE failed: %s`, err.Error())
|
|
}
|
|
|
|
qWrites := []string{
|
|
`INSERT INTO foo(name) VALUES("fiona")`,
|
|
`INSERT INTO foo(name) VALUES("fiona")`,
|
|
`INSERT INTO foo(name) VALUES("fiona")`,
|
|
}
|
|
resp, err := node.ExecuteQueuedMulti(qWrites, false)
|
|
if err != nil {
|
|
t.Fatalf(`queued write failed: %s`, err.Error())
|
|
}
|
|
if !QueuedResponseRegex.MatchString(resp) {
|
|
t.Fatalf("queued response is not valid: %s", resp)
|
|
}
|
|
|
|
ticker := time.NewTicker(10 * time.Millisecond)
|
|
timer := time.NewTimer(5 * time.Second)
|
|
LOOP:
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
r, err := node.Query(`SELECT COUNT(*) FROM foo`)
|
|
if err != nil {
|
|
t.Fatalf(`query failed: %s`, err.Error())
|
|
}
|
|
if r == `{"results":[{"columns":["COUNT(*)"],"types":["integer"],"values":[[3]]}]}` {
|
|
break LOOP
|
|
}
|
|
case <-timer.C:
|
|
t.Fatalf("timed out waiting for queued writes")
|
|
|
|
}
|
|
}
|
|
|
|
// Waiting for a queue write to complete means we should get the correct
|
|
// results immediately.
|
|
resp, err = node.ExecuteQueuedMulti(qWrites, true)
|
|
if err != nil {
|
|
t.Fatalf(`queued write failed: %s`, err.Error())
|
|
}
|
|
if !QueuedResponseRegex.MatchString(resp) {
|
|
t.Fatalf("queued response is not valid: %s", resp)
|
|
}
|
|
r, err := node.Query(`SELECT COUNT(*) FROM foo`)
|
|
if err != nil {
|
|
t.Fatalf(`query failed: %s`, err.Error())
|
|
}
|
|
if got, exp := r, `{"results":[{"columns":["COUNT(*)"],"types":["integer"],"values":[[6]]}]}`; got != exp {
|
|
t.Fatalf("incorrect results, exp: %s, got: %s", exp, got)
|
|
}
|
|
}
|
|
|
|
// Test_SingleNodeQueuedBadStmt tests that a single bad SQL statement has the right outcome.
|
|
func Test_SingleNodeQueuedBadStmt(t *testing.T) {
|
|
node := mustNewLeaderNode("leader1")
|
|
defer node.Deprovision()
|
|
node.Service.DefaultQueueTx = false
|
|
|
|
ticker := time.NewTicker(10 * time.Millisecond)
|
|
defer ticker.Stop()
|
|
|
|
_, err := node.Execute(`CREATE TABLE foo (id integer not null primary key, name text)`)
|
|
if err != nil {
|
|
t.Fatalf(`CREATE TABLE failed: %s`, err.Error())
|
|
}
|
|
|
|
qWrites := []string{
|
|
`INSERT INTO foo(name) VALUES("fiona")`,
|
|
`INSERT INTO nonsense`,
|
|
`INSERT INTO foo(name) VALUES("fiona")`,
|
|
}
|
|
resp, err := node.ExecuteQueuedMulti(qWrites, false)
|
|
if err != nil {
|
|
t.Fatalf(`queued write failed: %s`, err.Error())
|
|
}
|
|
if !QueuedResponseRegex.MatchString(resp) {
|
|
t.Fatalf("queued response is not valid: %s", resp)
|
|
}
|
|
|
|
timer := time.NewTimer(5 * time.Second)
|
|
defer timer.Stop()
|
|
|
|
LOOP1:
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
r, err := node.Query(`SELECT COUNT(*) FROM foo`)
|
|
if err != nil {
|
|
t.Fatalf(`query failed: %s`, err.Error())
|
|
}
|
|
if r == `{"results":[{"columns":["COUNT(*)"],"types":["integer"],"values":[[2]]}]}` {
|
|
break LOOP1
|
|
}
|
|
case <-timer.C:
|
|
t.Fatalf("timed out waiting for queued writes")
|
|
|
|
}
|
|
}
|
|
|
|
// Enable transactions, so that the next request shouldn't change the data
|
|
// due to the single bad statement.
|
|
node.Service.DefaultQueueTx = true
|
|
resp, err = node.ExecuteQueuedMulti(qWrites, false)
|
|
if err != nil {
|
|
t.Fatalf(`queued write failed: %s`, err.Error())
|
|
}
|
|
if !QueuedResponseRegex.MatchString(resp) {
|
|
t.Fatalf("queued response is not valid: %s", resp)
|
|
}
|
|
|
|
timer.Reset(5 * time.Second)
|
|
LOOP2:
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
r, err := node.Query(`SELECT COUNT(*) FROM foo`)
|
|
if err != nil {
|
|
t.Fatalf(`query failed: %s`, err.Error())
|
|
}
|
|
if r == `{"results":[{"columns":["COUNT(*)"],"types":["integer"],"values":[[2]]}]}` {
|
|
break LOOP2
|
|
}
|
|
case <-timer.C:
|
|
t.Fatalf("timed out waiting for queued writes")
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
func Test_SingleNodeQueuedEmptyNil(t *testing.T) {
|
|
node := mustNewLeaderNode("leader1")
|
|
defer node.Deprovision()
|
|
|
|
_, err := node.Execute(`CREATE TABLE foo (id integer not null primary key, name text)`)
|
|
if err != nil {
|
|
t.Fatalf(`CREATE TABLE failed: %s`, err.Error())
|
|
}
|
|
|
|
qWrites := []string{
|
|
`INSERT INTO foo(name) VALUES("fiona")`,
|
|
`INSERT INTO foo(name) VALUES("fiona")`,
|
|
`INSERT INTO foo(name) VALUES("fiona")`,
|
|
}
|
|
resp, err := node.ExecuteQueuedMulti(qWrites, false)
|
|
if err != nil {
|
|
t.Fatalf(`queued write failed: %s`, err.Error())
|
|
}
|
|
if !QueuedResponseRegex.MatchString(resp) {
|
|
t.Fatalf("queued response is not valid: %s", resp)
|
|
}
|
|
|
|
// Waiting for a queue write to complete means we should get the correct
|
|
// results immediately. But don't add any statements.
|
|
resp, err = node.ExecuteQueuedMulti([]string{}, true)
|
|
if err != nil {
|
|
t.Fatalf(`queued empty write failed: %s`, err.Error())
|
|
}
|
|
if !QueuedResponseRegex.MatchString(resp) {
|
|
t.Fatalf("queued response is not valid: %s", resp)
|
|
}
|
|
r, err := node.Query(`SELECT COUNT(*) FROM foo`)
|
|
if err != nil {
|
|
t.Fatalf(`query failed: %s`, err.Error())
|
|
}
|
|
if got, exp := r, `{"results":[{"columns":["COUNT(*)"],"types":["integer"],"values":[[3]]}]}`; got != exp {
|
|
t.Fatalf("incorrect results, exp: %s, got: %s", exp, got)
|
|
}
|
|
|
|
resp, err = node.ExecuteQueuedMulti(qWrites, false)
|
|
if err != nil {
|
|
t.Fatalf(`queued write failed: %s`, err.Error())
|
|
}
|
|
if !QueuedResponseRegex.MatchString(resp) {
|
|
t.Fatalf("queued response is not valid: %s", resp)
|
|
}
|
|
|
|
// Waiting for a queue write to complete means we should get the correct
|
|
// results immediately. But don't add any statements.
|
|
resp, err = node.ExecuteQueuedMulti(nil, true)
|
|
if err != nil {
|
|
t.Fatalf(`queued nil write failed: %s`, err.Error())
|
|
}
|
|
if !QueuedResponseRegex.MatchString(resp) {
|
|
t.Fatalf("queued response is not valid: %s", resp)
|
|
}
|
|
r, err = node.Query(`SELECT COUNT(*) FROM foo`)
|
|
if err != nil {
|
|
t.Fatalf(`query failed: %s`, err.Error())
|
|
}
|
|
if got, exp := r, `{"results":[{"columns":["COUNT(*)"],"types":["integer"],"values":[[6]]}]}`; got != exp {
|
|
t.Fatalf("incorrect results, exp: %s, got: %s", exp, got)
|
|
}
|
|
}
|
|
|
|
// Test_SingleNodeSQLInjection demonstrates that using the non-parameterized API is vulnerable to
|
|
// SQL injection attacks.
|
|
func Test_SingleNodeSQLInjection(t *testing.T) {
|
|
node := mustNewLeaderNode("leader1")
|
|
defer node.Deprovision()
|
|
|
|
tests := []struct {
|
|
stmt string
|
|
expected string
|
|
execute bool
|
|
}{
|
|
{
|
|
stmt: `CREATE TABLE foo (id integer not null primary key, name text)`,
|
|
expected: `{"results":[{}]}`,
|
|
execute: true,
|
|
},
|
|
{
|
|
stmt: `CREATE TABLE bar (id integer not null primary key, name text)`,
|
|
expected: `{"results":[{}]}`,
|
|
execute: true,
|
|
},
|
|
{
|
|
stmt: `SELECT * FROM foo`,
|
|
expected: `{"results":[{"columns":["id","name"],"types":["integer","text"]}]}`,
|
|
execute: false,
|
|
},
|
|
{
|
|
stmt: fmt.Sprintf(`INSERT INTO foo(name) VALUES(%s)`, `"alice");DROP TABLE foo;INSERT INTO bar(name) VALUES("bob"`),
|
|
expected: `{"results":[{"last_insert_id":1,"rows_affected":1}]}`,
|
|
execute: true,
|
|
},
|
|
{
|
|
stmt: `SELECT * FROM foo`,
|
|
expected: `{"results":[{"error":"no such table: foo"}]}`,
|
|
execute: false,
|
|
},
|
|
}
|
|
|
|
for i, tt := range tests {
|
|
var r string
|
|
var err error
|
|
if tt.execute {
|
|
r, err = node.Execute(tt.stmt)
|
|
} else {
|
|
r, err = node.Query(tt.stmt)
|
|
}
|
|
if err != nil {
|
|
t.Fatalf(`test %d failed "%s": %s`, i, tt.stmt, err.Error())
|
|
}
|
|
if r != tt.expected {
|
|
t.Fatalf(`test %d received wrong result "%s" got: %s exp: %s`, i, tt.stmt, r, tt.expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test_SingleNodeNoSQLInjection demonstrates that using the parameterized API protects
|
|
// against SQL injection attacks.
|
|
func Test_SingleNodeNoSQLInjection(t *testing.T) {
|
|
node := mustNewLeaderNode("leader1")
|
|
defer node.Deprovision()
|
|
|
|
tests := []struct {
|
|
stmt []interface{}
|
|
expected string
|
|
execute bool
|
|
}{
|
|
{
|
|
stmt: []interface{}{"CREATE TABLE foo (id integer not null primary key, name text)"},
|
|
expected: `{"results":[{}]}`,
|
|
execute: true,
|
|
},
|
|
{
|
|
stmt: []interface{}{`SELECT * FROM foo WHERE name="baz"`},
|
|
expected: `{"results":[{"columns":["id","name"],"types":["integer","text"]}]}`,
|
|
execute: false,
|
|
},
|
|
{
|
|
stmt: []interface{}{`SELECT * FROM foo WHERE name=?`, `"baz";DROP TABLE FOO`},
|
|
expected: `{"results":[{"columns":["id","name"],"types":["integer","text"]}]}`,
|
|
execute: false,
|
|
},
|
|
{
|
|
stmt: []interface{}{`SELECT * FROM foo`},
|
|
expected: `{"results":[{"columns":["id","name"],"types":["integer","text"]}]}`,
|
|
execute: false,
|
|
},
|
|
}
|
|
|
|
for i, tt := range tests {
|
|
var r string
|
|
var err error
|
|
if tt.execute {
|
|
r, err = node.ExecuteParameterized(tt.stmt)
|
|
} else {
|
|
r, err = node.QueryParameterized(tt.stmt)
|
|
}
|
|
if err != nil {
|
|
t.Fatalf(`test %d failed "%s": %s`, i, tt.stmt, err.Error())
|
|
}
|
|
if r != tt.expected {
|
|
t.Fatalf(`test %d received wrong result "%s" got: %s exp: %s`, i, tt.stmt, r, tt.expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test_SingleNodeUpgrades_NoSnapshots upgrade from a data created by earlier releases, but which
|
|
// do not have snapshots.
|
|
func Test_SingleNodeUpgrades_NoSnapshots(t *testing.T) {
|
|
versions := []string{
|
|
"v7.0.0-data",
|
|
"v7.9.2-data",
|
|
}
|
|
|
|
upgradeFrom := func(dir string) {
|
|
// Deprovision of a node deletes the node's dir, so make a copy first.
|
|
srcdir := filepath.Join("testdata", dir)
|
|
destdir := mustTempDir("")
|
|
if err := os.Remove(destdir); err != nil {
|
|
t.Fatalf("failed to remove dest dir: %s", err)
|
|
}
|
|
if err := copyDir(srcdir, destdir); err != nil {
|
|
t.Fatalf("failed to copy node test directory: %s", err)
|
|
}
|
|
|
|
mux, ln := mustNewOpenMux("")
|
|
defer ln.Close()
|
|
raftDialer := tcp.NewDialer(cluster.MuxRaftHeader, nil)
|
|
clstrDialer := tcp.NewDialer(cluster.MuxClusterHeader, nil)
|
|
node := mustNodeEncrypted("node1", destdir, true, false, mux, raftDialer, clstrDialer)
|
|
defer node.Deprovision()
|
|
if _, err := node.WaitForLeader(); err != nil {
|
|
t.Fatalf("node never became leader with %s data:", dir)
|
|
}
|
|
|
|
timer := time.NewTimer(5 * time.Second)
|
|
defer timer.Stop()
|
|
ticker := time.NewTicker(100 * time.Millisecond)
|
|
defer ticker.Stop()
|
|
testSuccess := make(chan struct{})
|
|
|
|
for {
|
|
select {
|
|
case <-testSuccess:
|
|
return
|
|
case <-timer.C:
|
|
t.Fatalf(`timeout waiting for correct results with %s data`, dir)
|
|
case <-ticker.C:
|
|
r, err := node.QueryNoneConsistency(`SELECT COUNT(*) FROM foo`)
|
|
if err != nil {
|
|
t.Fatalf("query failed with %s data: %s", dir, err)
|
|
}
|
|
expected := `{"results":[{"columns":["COUNT(*)"],"types":["integer"],"values":[[20]]}]}`
|
|
if r == expected {
|
|
close(testSuccess)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, version := range versions {
|
|
t.Run(version, func(t *testing.T) {
|
|
upgradeFrom(version)
|
|
})
|
|
}
|
|
}
|
|
|
|
// Test_SingleNodeUpgrades_Snapshots upgrade from a data created by earlier releases, but which
|
|
// do have snapshots.
|
|
func Test_SingleNodeUpgrades_Snapshots(t *testing.T) {
|
|
versions := []string{
|
|
"v7.20.3-data-with-snapshots",
|
|
}
|
|
|
|
upgradeFrom := func(dir string) {
|
|
// Deprovision of a node deletes the node's dir, so make a copy first.
|
|
srcdir := filepath.Join("testdata", dir)
|
|
destdir := mustTempDir("")
|
|
if err := os.Remove(destdir); err != nil {
|
|
t.Fatalf("failed to remove dest dir: %s", err)
|
|
}
|
|
if err := copyDir(srcdir, destdir); err != nil {
|
|
t.Fatalf("failed to copy node test directory: %s", err)
|
|
}
|
|
|
|
mux, ln := mustNewOpenMux("")
|
|
defer ln.Close()
|
|
raftDialer := tcp.NewDialer(cluster.MuxRaftHeader, nil)
|
|
clstrDialer := tcp.NewDialer(cluster.MuxClusterHeader, nil)
|
|
node := mustNodeEncrypted("node1", destdir, true, false, mux, raftDialer, clstrDialer)
|
|
defer node.Deprovision()
|
|
if _, err := node.WaitForLeader(); err != nil {
|
|
t.Fatalf("node never became leader with %s data:", dir)
|
|
}
|
|
|
|
timer := time.NewTimer(5 * time.Second)
|
|
defer timer.Stop()
|
|
ticker := time.NewTicker(100 * time.Millisecond)
|
|
defer ticker.Stop()
|
|
testSuccess := make(chan struct{})
|
|
|
|
for {
|
|
select {
|
|
case <-testSuccess:
|
|
return
|
|
case <-timer.C:
|
|
t.Fatalf(`timeout waiting for correct results with %s data`, dir)
|
|
case <-ticker.C:
|
|
r, err := node.QueryNoneConsistency(`SELECT COUNT(*) FROM foo`)
|
|
if err != nil {
|
|
t.Fatalf("query failed with %s data: %s", dir, err)
|
|
}
|
|
expected := `{"results":[{"columns":["COUNT(*)"],"types":["integer"],"values":[[20]]}]}`
|
|
if r == expected {
|
|
close(testSuccess)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, version := range versions {
|
|
t.Run(version, func(t *testing.T) {
|
|
upgradeFrom(version)
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_SingleNodeNodes(t *testing.T) {
|
|
node := mustNewLeaderNode("leader1")
|
|
defer node.Deprovision()
|
|
|
|
// Access endpoints to ensure the code is covered.
|
|
nodes, err := node.Nodes(false)
|
|
if err != nil {
|
|
t.Fatalf("failed to access nodes endpoint: %s", err.Error())
|
|
}
|
|
|
|
if len(nodes) != 1 {
|
|
t.Fatalf("wrong number of nodes in response")
|
|
}
|
|
n := nodes[0]
|
|
if n.Addr != node.RaftAddr {
|
|
t.Fatalf("node has wrong Raft address")
|
|
}
|
|
if got, exp := n.APIAddr, fmt.Sprintf("http://%s", node.APIAddr); exp != got {
|
|
t.Fatalf("node has wrong API address, exp: %s got: %s", exp, got)
|
|
}
|
|
if !n.Leader {
|
|
t.Fatalf("node is not leader")
|
|
}
|
|
if !n.Reachable {
|
|
t.Fatalf("node is not reachable")
|
|
}
|
|
}
|
|
|
|
func Test_SingleNodeCoverage(t *testing.T) {
|
|
node := mustNewLeaderNode("leader1")
|
|
defer node.Deprovision()
|
|
|
|
// Access endpoints to ensure the code is covered.
|
|
var err error
|
|
var str string
|
|
|
|
str, err = node.Status()
|
|
if err != nil {
|
|
t.Fatalf("failed to access status endpoint: %s", err.Error())
|
|
}
|
|
if !isJSON(str) {
|
|
t.Fatalf("output from status endpoint is not valid JSON: %s", str)
|
|
}
|
|
|
|
str, err = node.Expvar()
|
|
if err != nil {
|
|
t.Fatalf("failed to access expvar endpoint: %s", err.Error())
|
|
}
|
|
if !isJSON(str) {
|
|
t.Fatalf("output from expvar endpoint is not valid JSON: %s", str)
|
|
}
|
|
}
|
|
|
|
// Test_SingleNodeReopen tests that a node can be re-opened OK.
|
|
func Test_SingleNodeReopen(t *testing.T) {
|
|
dir := mustTempDir("node1")
|
|
mux, ln := mustNewOpenMux("")
|
|
defer ln.Close()
|
|
raftDialer := tcp.NewDialer(cluster.MuxRaftHeader, nil)
|
|
clstrDialer := tcp.NewDialer(cluster.MuxClusterHeader, nil)
|
|
node := mustNodeEncrypted("node1", dir, true, false, mux, raftDialer, clstrDialer)
|
|
|
|
if _, err := node.WaitForLeader(); err != nil {
|
|
t.Fatalf("node never became leader")
|
|
}
|
|
|
|
if err := node.Close(true); err != nil {
|
|
t.Fatalf("failed to close node")
|
|
}
|
|
|
|
if err := node.Store.Open(); err != nil {
|
|
t.Fatalf("failed to re-open store: %s", err)
|
|
}
|
|
if err := node.Store.Bootstrap(store.NewServer(node.Store.ID(), node.Store.Addr(), true)); err != nil {
|
|
t.Fatalf("failed to bootstrap single-node store: %s", err.Error())
|
|
}
|
|
if err := node.Service.Start(); err != nil {
|
|
t.Fatalf("failed to restart service: %s", err)
|
|
}
|
|
|
|
if _, err := node.WaitForLeader(); err != nil {
|
|
t.Fatalf("node never became leader")
|
|
}
|
|
|
|
node.Deprovision()
|
|
}
|
|
|
|
// Test_SingleNodeReopen tests that a node can be re-opened OK, with
|
|
// a non-database command in the log.
|
|
func Test_SingleNodeNoopReopen(t *testing.T) {
|
|
dir := mustTempDir("node1")
|
|
mux, ln := mustNewOpenMux("")
|
|
defer ln.Close()
|
|
raftDialer := tcp.NewDialer(cluster.MuxRaftHeader, nil)
|
|
clstrDialer := tcp.NewDialer(cluster.MuxClusterHeader, nil)
|
|
node := mustNodeEncrypted("node1", dir, true, false, mux, raftDialer, clstrDialer)
|
|
|
|
if _, err := node.WaitForLeader(); err != nil {
|
|
t.Fatalf("node never became leader")
|
|
}
|
|
|
|
if err := node.Noop("#1"); err != nil {
|
|
t.Fatalf("failed to write noop command: %s", err)
|
|
}
|
|
|
|
if err := node.Close(true); err != nil {
|
|
t.Fatalf("failed to close node")
|
|
}
|
|
|
|
if err := node.Store.Open(); err != nil {
|
|
t.Fatalf("failed to re-open store: %s", err)
|
|
}
|
|
if err := node.Store.Bootstrap(store.NewServer(node.Store.ID(), node.Store.Addr(), true)); err != nil {
|
|
t.Fatalf("failed to bootstrap single-node store: %s", err.Error())
|
|
}
|
|
if err := node.Service.Start(); err != nil {
|
|
t.Fatalf("failed to restart service: %s", err)
|
|
}
|
|
// This testing tells service to restart with localhost:0
|
|
// again, so explicitly set the API address again.
|
|
node.APIAddr = node.Service.Addr().String()
|
|
|
|
if _, err := node.WaitForLeader(); err != nil {
|
|
t.Fatalf("node never became leader")
|
|
}
|
|
|
|
// Ensure node is fully functional after restart.
|
|
tests := []struct {
|
|
stmt []interface{}
|
|
expected string
|
|
execute bool
|
|
}{
|
|
{
|
|
stmt: []interface{}{"CREATE TABLE foo (id integer not null primary key, name text, age integer)"},
|
|
expected: `{"results":[{}]}`,
|
|
execute: true,
|
|
},
|
|
{
|
|
stmt: []interface{}{"INSERT INTO foo(name, age) VALUES(?, ?)", "fiona", 20},
|
|
expected: `{"results":[{"last_insert_id":1,"rows_affected":1}]}`,
|
|
execute: true,
|
|
},
|
|
{
|
|
stmt: []interface{}{"SELECT * FROM foo WHERE NAME=?", "fiona"},
|
|
expected: `{"results":[{"columns":["id","name","age"],"types":["integer","text","integer"],"values":[[1,"fiona",20]]}]}`,
|
|
execute: false,
|
|
},
|
|
}
|
|
|
|
for i, tt := range tests {
|
|
var r string
|
|
var err error
|
|
if tt.execute {
|
|
r, err = node.ExecuteParameterized(tt.stmt)
|
|
} else {
|
|
r, err = node.QueryParameterized(tt.stmt)
|
|
}
|
|
if err != nil {
|
|
t.Fatalf(`test %d failed "%s": %s`, i, tt.stmt, err.Error())
|
|
}
|
|
if r != tt.expected {
|
|
t.Fatalf(`test %d received wrong result "%s" got: %s exp: %s`, i, tt.stmt, r, tt.expected)
|
|
}
|
|
}
|
|
|
|
node.Deprovision()
|
|
}
|
|
|
|
// Test_SingleNodeReopen tests that a node can be re-opened OK, with
|
|
// a snapshot present which was triggered by non-database commands.
|
|
// This tests that the code can handle a snapshot that doesn't
|
|
// contain database data. This shouldn't happen in real systems
|
|
func Test_SingleNodeNoopSnapReopen(t *testing.T) {
|
|
dir := mustTempDir("node1")
|
|
mux, ln := mustNewOpenMux("")
|
|
defer ln.Close()
|
|
raftDialer := tcp.NewDialer(cluster.MuxRaftHeader, nil)
|
|
clstrDialer := tcp.NewDialer(cluster.MuxClusterHeader, nil)
|
|
node := mustNodeEncrypted("node1", dir, true, false, mux, raftDialer, clstrDialer)
|
|
|
|
if _, err := node.WaitForLeader(); err != nil {
|
|
t.Fatalf("node never became leader")
|
|
}
|
|
|
|
for i := 0; i < 150; i++ {
|
|
if err := node.Noop(fmt.Sprintf("%d", i)); err != nil {
|
|
t.Fatalf("failed to write noop command: %s", err)
|
|
}
|
|
}
|
|
|
|
// Wait for a snapshot to happen.
|
|
time.Sleep(5 * time.Second)
|
|
|
|
if err := node.Close(true); err != nil {
|
|
t.Fatalf("failed to close node")
|
|
}
|
|
|
|
if err := node.Store.Open(); err != nil {
|
|
t.Fatalf("failed to re-open store: %s", err)
|
|
}
|
|
if err := node.Store.Bootstrap(store.NewServer(node.Store.ID(), node.Store.Addr(), true)); err != nil {
|
|
t.Fatalf("failed to bootstrap single-node store: %s", err.Error())
|
|
}
|
|
if err := node.Service.Start(); err != nil {
|
|
t.Fatalf("failed to restart service: %s", err)
|
|
}
|
|
// This testing tells service to restart with localhost:0
|
|
// again, so explicitly set the API address again.
|
|
node.APIAddr = node.Service.Addr().String()
|
|
|
|
if _, err := node.WaitForLeader(); err != nil {
|
|
t.Fatalf("node never became leader")
|
|
}
|
|
|
|
// Ensure node is fully functional after restart.
|
|
tests := []struct {
|
|
stmt []interface{}
|
|
expected string
|
|
execute bool
|
|
}{
|
|
{
|
|
stmt: []interface{}{"CREATE TABLE foo (id integer not null primary key, name text, age integer)"},
|
|
expected: `{"results":[{}]}`,
|
|
execute: true,
|
|
},
|
|
{
|
|
stmt: []interface{}{"INSERT INTO foo(name, age) VALUES(?, ?)", "fiona", 20},
|
|
expected: `{"results":[{"last_insert_id":1,"rows_affected":1}]}`,
|
|
execute: true,
|
|
},
|
|
{
|
|
stmt: []interface{}{"SELECT * FROM foo WHERE NAME=?", "fiona"},
|
|
expected: `{"results":[{"columns":["id","name","age"],"types":["integer","text","integer"],"values":[[1,"fiona",20]]}]}`,
|
|
execute: false,
|
|
},
|
|
}
|
|
|
|
for i, tt := range tests {
|
|
var r string
|
|
var err error
|
|
if tt.execute {
|
|
r, err = node.ExecuteParameterized(tt.stmt)
|
|
} else {
|
|
r, err = node.QueryParameterized(tt.stmt)
|
|
}
|
|
if err != nil {
|
|
t.Fatalf(`test %d failed "%s": %s`, i, tt.stmt, err.Error())
|
|
}
|
|
if r != tt.expected {
|
|
t.Fatalf(`test %d received wrong result "%s" got: %s exp: %s`, i, tt.stmt, r, tt.expected)
|
|
}
|
|
}
|
|
|
|
node.Deprovision()
|
|
}
|
|
|
|
// Test_SingleNodeNoopSnapLogsReopen tests that a node can be re-opened OK,
|
|
// with a snapshot present which was triggered by non-database commands.
|
|
// This tests that the code can handle a snapshot that doesn't
|
|
// contain database data. This shouldn't happen in real systems
|
|
func Test_SingleNodeNoopSnapLogsReopen(t *testing.T) {
|
|
dir := mustTempDir("node1")
|
|
mux, ln := mustNewOpenMux("")
|
|
defer ln.Close()
|
|
raftDialer := tcp.NewDialer(cluster.MuxRaftHeader, nil)
|
|
clstrDialer := tcp.NewDialer(cluster.MuxClusterHeader, nil)
|
|
node := mustNodeEncrypted("node1", dir, true, false, mux, raftDialer, clstrDialer)
|
|
|
|
if _, err := node.WaitForLeader(); err != nil {
|
|
t.Fatalf("node never became leader")
|
|
}
|
|
|
|
for i := 0; i < 150; i++ {
|
|
if err := node.Noop(fmt.Sprintf("%d", i)); err != nil {
|
|
t.Fatalf("failed to write noop command: %s", err)
|
|
}
|
|
}
|
|
|
|
// Wait for a snapshot to happen, and then write some more commands.
|
|
time.Sleep(5 * time.Second)
|
|
for i := 0; i < 5; i++ {
|
|
if err := node.Noop(fmt.Sprintf("%d", i)); err != nil {
|
|
t.Fatalf("failed to write noop command: %s", err)
|
|
}
|
|
}
|
|
|
|
if err := node.Close(true); err != nil {
|
|
t.Fatalf("failed to close node")
|
|
}
|
|
|
|
if err := node.Store.Open(); err != nil {
|
|
t.Fatalf("failed to re-open store: %s", err)
|
|
}
|
|
if err := node.Service.Start(); err != nil {
|
|
t.Fatalf("failed to restart service: %s", err)
|
|
}
|
|
// This testing tells service to restart with localhost:0
|
|
// again, so explicitly set the API address again.
|
|
node.APIAddr = node.Service.Addr().String()
|
|
|
|
if _, err := node.WaitForLeader(); err != nil {
|
|
t.Fatalf("node never became leader")
|
|
}
|
|
|
|
// Ensure node is fully functional after restart.
|
|
tests := []struct {
|
|
stmt []interface{}
|
|
expected string
|
|
execute bool
|
|
}{
|
|
{
|
|
stmt: []interface{}{"CREATE TABLE foo (id integer not null primary key, name text, age integer)"},
|
|
expected: `{"results":[{}]}`,
|
|
execute: true,
|
|
},
|
|
{
|
|
stmt: []interface{}{"INSERT INTO foo(name, age) VALUES(?, ?)", "fiona", 20},
|
|
expected: `{"results":[{"last_insert_id":1,"rows_affected":1}]}`,
|
|
execute: true,
|
|
},
|
|
{
|
|
stmt: []interface{}{"SELECT * FROM foo WHERE NAME=?", "fiona"},
|
|
expected: `{"results":[{"columns":["id","name","age"],"types":["integer","text","integer"],"values":[[1,"fiona",20]]}]}`,
|
|
execute: false,
|
|
},
|
|
}
|
|
|
|
for i, tt := range tests {
|
|
var r string
|
|
var err error
|
|
if tt.execute {
|
|
r, err = node.ExecuteParameterized(tt.stmt)
|
|
} else {
|
|
r, err = node.QueryParameterized(tt.stmt)
|
|
}
|
|
if err != nil {
|
|
t.Fatalf(`test %d failed "%s": %s`, i, tt.stmt, err.Error())
|
|
}
|
|
if r != tt.expected {
|
|
t.Fatalf(`test %d received wrong result "%s" got: %s exp: %s`, i, tt.stmt, r, tt.expected)
|
|
}
|
|
}
|
|
|
|
node.Deprovision()
|
|
}
|
|
|
|
func Test_SingleNodeAutoRestore(t *testing.T) {
|
|
dir := mustTempDir("node1")
|
|
node := &Node{
|
|
Dir: dir,
|
|
PeersPath: filepath.Join(dir, "raft/peers.json"),
|
|
}
|
|
defer node.Deprovision()
|
|
|
|
mux, _ := mustNewOpenMux("")
|
|
go mux.Serve()
|
|
|
|
raftLn := mux.Listen(cluster.MuxRaftHeader)
|
|
raftTn := tcp.NewLayer(raftLn, tcp.NewDialer(cluster.MuxRaftHeader, nil))
|
|
node.Store = store.New(raftTn, &store.Config{
|
|
DBConf: store.NewDBConfig(),
|
|
Dir: node.Dir,
|
|
ID: "node1",
|
|
})
|
|
|
|
restoreFile := mustTempFile()
|
|
copyFile(filepath.Join("testdata", "auto-restore.sqlite"), restoreFile)
|
|
if err := node.Store.SetRestorePath(restoreFile); err != nil {
|
|
t.Fatalf("failed to set restore path: %s", err.Error())
|
|
}
|
|
|
|
if err := node.Store.Open(); err != nil {
|
|
t.Fatalf("failed to open store: %s", err.Error())
|
|
}
|
|
if err := node.Store.Bootstrap(store.NewServer(node.Store.ID(), node.Store.Addr(), true)); err != nil {
|
|
t.Fatalf("failed to bootstrap store: %s", err.Error())
|
|
}
|
|
node.RaftAddr = node.Store.Addr()
|
|
node.ID = node.Store.ID()
|
|
|
|
clstr := cluster.New(mux.Listen(cluster.MuxClusterHeader), node.Store, node.Store, mustNewMockCredentialStore())
|
|
if err := clstr.Open(); err != nil {
|
|
t.Fatalf("failed to open Cluster service: %s", err.Error())
|
|
}
|
|
node.Cluster = clstr
|
|
|
|
clstrDialer := tcp.NewDialer(cluster.MuxClusterHeader, nil)
|
|
clstrClient := cluster.NewClient(clstrDialer, 30*time.Second)
|
|
node.Service = httpd.New("localhost:0", node.Store, clstrClient, nil)
|
|
|
|
if err := node.Service.Start(); err != nil {
|
|
t.Fatalf("failed to start HTTP server: %s", err.Error())
|
|
}
|
|
node.APIAddr = node.Service.Addr().String()
|
|
|
|
// Finally, set API address in Cluster service
|
|
clstr.SetAPIAddr(node.APIAddr)
|
|
|
|
if _, err := node.WaitForLeader(); err != nil {
|
|
t.Fatalf("node never became leader")
|
|
}
|
|
testPoll(t, node.Ready, 100*time.Millisecond, 5*time.Second)
|
|
|
|
r, err := node.Query("SELECT * FROM foo WHERE id=2")
|
|
if err != nil {
|
|
t.Fatalf("failed to execute query: %s", err.Error())
|
|
}
|
|
if r != `{"results":[{"columns":["id","name"],"types":["integer","text"],"values":[[2,"fiona"]]}]}` {
|
|
t.Fatalf("test received wrong result got %s", r)
|
|
}
|
|
}
|
|
|
|
func Test_SingleNodeBoot_OK(t *testing.T) {
|
|
node := mustNewLeaderNode("leader1")
|
|
defer node.Deprovision()
|
|
|
|
_, err := node.Boot(filepath.Join("testdata", "auto-restore.sqlite"))
|
|
if err != nil {
|
|
t.Fatalf("failed to load data: %s", err.Error())
|
|
}
|
|
|
|
r, err := node.Query("SELECT * FROM foo WHERE id=2")
|
|
if err != nil {
|
|
t.Fatalf("failed to execute query: %s", err.Error())
|
|
}
|
|
if r != `{"results":[{"columns":["id","name"],"types":["integer","text"],"values":[[2,"fiona"]]}]}` {
|
|
t.Fatalf("test received wrong result got %s", r)
|
|
}
|
|
}
|
|
|
|
func Test_SingleNodeBoot_FailNotLeader(t *testing.T) {
|
|
node := mustNewNode("node1", false)
|
|
defer node.Deprovision()
|
|
_, err := node.Boot(filepath.Join("testdata", "auto-restore.sqlite"))
|
|
if err == nil {
|
|
t.Fatalf("expected error loading data")
|
|
}
|
|
}
|