@ -12,6 +12,100 @@ import (
"github.com/rqlite/rqlite/v8/command/proto"
)
// Test_MultiNodeSimple tests that a the core operation of a multi-node
// cluster works as expected. That is, with a two node cluster, writes
// actually replicate, and reads are consistent.
func Test_MultiNodeSimple ( t * testing . T ) {
s0 , ln := mustNewStore ( t )
defer ln . Close ( )
if err := s0 . Open ( ) ; err != nil {
t . Fatalf ( "failed to open single-node store: %s" , err . Error ( ) )
}
defer s0 . Close ( true )
if err := s0 . Bootstrap ( NewServer ( s0 . ID ( ) , s0 . Addr ( ) , true ) ) ; err != nil {
t . Fatalf ( "failed to bootstrap single-node store: %s" , err . Error ( ) )
}
if _ , err := s0 . WaitForLeader ( 10 * time . Second ) ; err != nil {
t . Fatalf ( "Error waiting for leader: %s" , err )
}
s1 , ln1 := mustNewStore ( t )
defer ln1 . Close ( )
if err := s1 . Open ( ) ; err != nil {
t . Fatalf ( "failed to open single-node store: %s" , err . Error ( ) )
}
defer s1 . Close ( true )
if err := s0 . Join ( joinRequest ( s1 . ID ( ) , s1 . Addr ( ) , true ) ) ; err != nil {
t . Fatalf ( "failed to join single-node store: %s" , err . Error ( ) )
}
if _ , err := s1 . WaitForLeader ( 10 * time . Second ) ; err != nil {
t . Fatalf ( "Error waiting for leader: %s" , err )
}
// Write some data.
er := executeRequestFromStrings ( [ ] string {
` CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT) ` ,
` INSERT INTO foo(id, name) VALUES(1, "fiona") ` ,
} , false , false )
_ , err := s0 . Execute ( er )
if err != nil {
t . Fatalf ( "failed to execute on single node: %s" , err . Error ( ) )
}
if _ , err := s0 . WaitForAppliedFSM ( 5 * time . Second ) ; err != nil {
t . Fatalf ( "failed to wait for FSM to apply on leader" )
}
testPoll ( t , func ( ) bool {
return s0 . DBAppliedIndex ( ) == s1 . DBAppliedIndex ( )
} , 250 * time . Millisecond , 3 * time . Second )
// Now, do a NONE consistency query on each node, to actually confirm the data
// has been replicated.
testFn1 := func ( t * testing . T , s * Store ) {
t . Helper ( )
qr := queryRequestFromString ( "SELECT * FROM foo" , false , false )
qr . Level = proto . QueryRequest_QUERY_REQUEST_LEVEL_NONE
r , err := s . Query ( qr )
if err != nil {
t . Fatalf ( "failed to query single node: %s" , err . Error ( ) )
}
if exp , got := ` [ { "columns":["id","name"],"types":["integer","text"],"values":[[1,"fiona"]]}] ` , asJSON ( r ) ; exp != got {
t . Fatalf ( "unexpected results for query\nexp: %s\ngot: %s" , exp , got )
}
}
testFn1 ( t , s0 )
testFn1 ( t , s1 )
// Write another row using Request
rr := executeQueryRequestFromString ( "INSERT INTO foo(id, name) VALUES(2, 'fiona')" , proto . QueryRequest_QUERY_REQUEST_LEVEL_STRONG , false , false )
_ , err = s0 . Request ( rr )
if err != nil {
t . Fatalf ( "failed to execute on single node: %s" , err . Error ( ) )
}
testPoll ( t , func ( ) bool {
return s0 . DBAppliedIndex ( ) == s1 . DBAppliedIndex ( )
} , 250 * time . Millisecond , 3 * time . Second )
// Now, do a NONE consistency query on each node, to actually confirm the data
// has been replicated.
testFn2 := func ( t * testing . T , s * Store ) {
t . Helper ( )
qr := queryRequestFromString ( "SELECT COUNT(*) FROM foo" , false , false )
qr . Level = proto . QueryRequest_QUERY_REQUEST_LEVEL_NONE
r , err := s . Query ( qr )
if err != nil {
t . Fatalf ( "failed to query single node: %s" , err . Error ( ) )
}
if exp , got := ` [ { "columns":["COUNT(*)"],"types":["integer"],"values":[[2]]}] ` , asJSON ( r ) ; exp != got {
t . Fatalf ( "unexpected results for query\nexp: %s\ngot: %s" , exp , got )
}
}
testFn2 ( t , s0 )
testFn2 ( t , s1 )
}
// Test_MultiNodeSnapshot_ErrorMessage tests that a snapshot fails with a specific
// error message when the snapshot is attempted too soon after joining a cluster.
// Hashicorp Raft doesn't expose a typed error, so we have to check the error