/* Package system runs system-level testing of rqlite. This includes testing of single nodes, and multi-node clusters. */ package system import ( "compress/gzip" "fmt" "io" "os" "path/filepath" "regexp" "strings" "sync" "testing" "time" "github.com/rqlite/rqlite/v8/cluster" httpd "github.com/rqlite/rqlite/v8/http" "github.com/rqlite/rqlite/v8/random" "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_SingleNodeQueryTimeout(t *testing.T) { node := mustNewLeaderNode("leader1") defer node.Deprovision() sql := `CREATE TABLE IF NOT EXISTS test_table ( key1 VARCHAR(64) PRIMARY KEY, key_id VARCHAR(64) NOT NULL, key2 VARCHAR(64) NOT NULL, key3 VARCHAR(64) NOT NULL, key4 VARCHAR(64) NOT NULL, key5 VARCHAR(64) NOT NULL, key6 VARCHAR(64) NOT NULL, data BLOB NOT NULL )` if _, err := node.Execute(sql); err != nil { t.Fatalf("failed to create table: %s", err.Error()) } // Bulk insert rows (for speed and to avoid snapshotting) sqls := make([]string, 5000) for i := 0; i < cap(sqls); i++ { args := []any{ random.String(), fmt.Sprint(i), random.String(), random.String(), random.String(), random.String(), random.String(), random.String(), } sql := fmt.Sprintf(`INSERT INTO test_table (key1, key_id, key2, key3, key4, key5, key6, data) VALUES (%q, %q, %q, %q, %q, %q, %q, %q);`, args...) sqls[i] = sql } if _, err := node.ExecuteMulti(sqls); err != nil { t.Fatalf("failed to insert data: %s", err.Error()) } r, err := node.Query(`SELECT COUNT(*) FROM test_table`) if err != nil { t.Fatalf("failed to count records: %s", err.Error()) } exp := fmt.Sprintf(`{"results":[{"columns":["COUNT(*)"],"types":["integer"],"values":[[%d]]}]}`, len(sqls)) if r != exp { t.Fatalf("test received wrong result\nexp: %s\ngot: %s\n", exp, r) } q := `SELECT key1, key_id, key2, key3, key4, key5, key6, data FROM test_table ORDER BY key2 ASC` r, err = node.QueryWithTimeout(q, 1*time.Millisecond) if err != nil { t.Fatalf("failed to query with timeout: %s", err.Error()) } if !strings.Contains(r, `"error":"query timeout"`) { // This test is brittle, but it's the best we can do, as we can't be sure // how much of the query will actually be executed. We just know it should // time out at some point. t.Fatalf("query ran to completion, but should have timed out") } } 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_SingleNodeBackup(t *testing.T) { node := mustNewLeaderNode("leader1") defer node.Deprovision() // create a table and insert a row _, 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()) } _, err = node.Execute(`INSERT INTO foo(name) VALUES("fiona")`) if err != nil { t.Fatalf(`INSERT failed: %s`, err.Error()) } backup := mustTempFile() defer os.Remove(backup) if err := node.Backup(backup, false); err != nil { t.Fatalf(`backup failed: %s`, err.Error()) } // Create new node, and restore from backup. newNode := mustNewLeaderNode("leader2") defer newNode.Deprovision() _, err = newNode.Boot(backup) if err != nil { t.Fatalf(`boot failed: %s`, err.Error()) } r, err := newNode.Query(`SELECT * FROM foo`) if err != nil { t.Fatalf(`query failed: %s`, err.Error()) } if r != `{"results":[{"columns":["id","name"],"types":["integer","text"],"values":[[1,"fiona"]]}]}` { t.Fatalf("test received wrong result got %s", r) } // Get a compressed backup, test it again. if err := node.Backup(backup, true); err != nil { t.Fatalf(`backup failed: %s`, err.Error()) } // decompress backup and check it. decompressedBackup := mustTempFile() defer os.Remove(decompressedBackup) f, err := os.Open(backup) if err != nil { t.Fatalf(`failed to open backup: %s`, err.Error()) } defer f.Close() gzr, err := gzip.NewReader(f) if err != nil { t.Fatalf(`failed to create gzip reader: %s`, err.Error()) } defer gzr.Close() w, err := os.Create(decompressedBackup) if err != nil { t.Fatalf(`failed to create decompressed backup: %s`, err.Error()) } defer w.Close() if _, err := io.Copy(w, gzr); err != nil { t.Fatalf(`failed to decompress backup: %s`, err.Error()) } // Create new node, and restore from backup. newNode2 := mustNewLeaderNode("leader3") defer newNode2.Deprovision() _, err = newNode2.Boot(decompressedBackup) if err != nil { t.Fatalf(`boot failed: %s`, err.Error()) } r, err = newNode2.Query(`SELECT * FROM foo`) if err != nil { t.Fatalf(`query failed: %s`, err.Error()) } if r != `{"results":[{"columns":["id","name"],"types":["integer","text"],"values":[[1,"fiona"]]}]}` { t.Fatalf("test received wrong result got %s", r) } } 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") } }