99 "context"
1010 "fmt"
1111 "testing"
12+ "time"
1213
1314 "github.com/cockroachdb/cockroach/pkg/base"
1415 "github.com/cockroachdb/cockroach/pkg/cloud"
@@ -19,6 +20,7 @@ import (
1920 "github.com/cockroachdb/cockroach/pkg/roachpb"
2021 "github.com/cockroachdb/cockroach/pkg/storage"
2122 "github.com/cockroachdb/cockroach/pkg/storage/enginepb"
23+ "github.com/cockroachdb/cockroach/pkg/testutils"
2224 "github.com/cockroachdb/cockroach/pkg/testutils/skip"
2325 "github.com/cockroachdb/cockroach/pkg/testutils/storageutils"
2426 "github.com/cockroachdb/cockroach/pkg/testutils/testcluster"
@@ -868,3 +870,144 @@ func TestGeneralOperationsWorkAsExpectedOnDeletedExternalSST(t *testing.T) {
868870 })
869871 }
870872}
873+
874+ // TestRangeFeedWithExcise tests that a RangeFeed can be created just before
875+ // excising a range and that the RangeFeed, and the RangeFeed will stop when
876+ // an excise command is issued.
877+ //
878+ // Test setup can be visualized in the following diagram:
879+ /*
880+ a d g z
881+ | | | |
882+ v v v v
883+ Keyspace: |----------------|----------------|----------------|
884+ Range 1 Range 2 Range 3
885+ | | | |
886+ RangeFeeds: |---- RF1 -------|---- RF2 -------|---- RF3 -------|
887+ | | | |
888+ |<-- External SSTable -->|
889+ | |
890+ |<--- Excised Range ---->|
891+ | |
892+ d k
893+ */
894+ func TestRangeFeedWithExcise (t * testing.T ) {
895+ defer leaktest .AfterTest (t )()
896+ defer log .Scope (t ).Close (t )
897+ defer nodelocal .ReplaceNodeLocalForTesting (t .TempDir ())()
898+ const externURI = "nodelocal://1/external-files"
899+
900+ ctx := context .Background ()
901+ etc := externalSSTTestCluster {}
902+
903+ // Create a test cluster with the following ranges: [a, d), [d, g), [g, z).
904+ etc .testSetup (t )
905+ defer etc .tc .Stopper ().Stop (ctx )
906+
907+ externalStorage , err := etc .createExternalStorage (ctx , externURI )
908+ require .NoError (t , err )
909+
910+ deletedStartKey := roachpb .Key ("d" )
911+ deletedEndKey := roachpb .Key ("k" )
912+ firstStartChar := deletedStartKey [0 ]
913+ firstEndChar := deletedEndKey [0 ]
914+
915+ require .NoError (t , etc .createExternalSSTableFile (t , ctx , externalStorage ,
916+ "file1.sst" , firstStartChar , firstEndChar ))
917+ require .NoError (t , etc .linkExternalSSTableToFile (ctx , deletedStartKey ,
918+ deletedEndKey , externURI , "file1.sst" ))
919+
920+ // Create one range feed per range.
921+ rfStream1 , rfErr1 := etc .createRangeFeed (t , roachpb .Key ("a" ), roachpb .Key ("d" ))
922+ defer rfStream1 .Cancel ()
923+
924+ rfStream2 , rfErr2 := etc .createRangeFeed (t , roachpb .Key ("d" ), roachpb .Key ("g" ))
925+ defer rfStream2 .Cancel ()
926+
927+ rfStream3 , rfErr3 := etc .createRangeFeed (t , roachpb .Key ("g" ), roachpb .Key ("z" ))
928+ defer rfStream3 .Cancel ()
929+
930+ waitForCheckpoint := func (rfErr chan error , stream * testStream ) {
931+ require .Eventually (t , func () bool {
932+ select {
933+ case err = <- rfErr :
934+ require .Fail (t , "unexpected RangeFeed error" , "%v" , err )
935+ default :
936+ }
937+
938+ events := stream .Events ()
939+ for _ , event := range events {
940+ require .NotNil (t , event .Checkpoint , "received non-checkpoint event: %v" , event )
941+ }
942+ return len (events ) > 0
943+ }, 5 * time .Second , 100 * time .Millisecond )
944+ }
945+
946+ // Wait for the RangeFeeds to receive the initial checkpoint. This is to avoid
947+ // the RangeFeed racing with the deleted span, which would cause the
948+ // RangeFeed to fail to start.
949+ waitForCheckpoint (rfErr1 , rfStream1 )
950+ waitForCheckpoint (rfErr2 , rfStream2 )
951+ waitForCheckpoint (rfErr3 , rfStream3 )
952+
953+ // Perform some data operations, and delete the external SSTable file.
954+ pendingTxn1 , pendingTxn2 , err := etc .writeIntents (ctx , etc .db )
955+ require .NoError (t , err )
956+
957+ require .NoError (t , externalStorage .Delete (ctx , "file1.sst" ))
958+
959+ require .NoError (t , pendingTxn1 .Commit (ctx ))
960+ require .NoError (t , pendingTxn2 .Commit (ctx ))
961+ require .NoError (t , etc .putHelper (ctx , roachpb .Key ("a" )))
962+ // Requests to the deleted key span should fail.
963+ etc .requireNotFoundError (t , etc .putHelper (ctx , roachpb .Key ("e-15000" )))
964+ etc .requireNotFoundError (t , etc .putHelper (ctx , roachpb .Key ("h-15000" )))
965+ require .NoError (t , etc .putHelper (ctx , roachpb .Key ("y" )))
966+ require .NoError (t , etc .deleteRangeHelper (ctx , roachpb .Key ("a" ), roachpb .Key ("b" )))
967+ require .NoError (t , etc .deleteRangeHelper (ctx , roachpb .Key ("k" ), roachpb .Key ("l" )))
968+
969+ assertNoRangeFeedErr := func (rfErr chan error ) {
970+ select {
971+ case err = <- rfErr :
972+ require .Fail (t , "unexpected RangeFeed error" , "%v" , err )
973+ default :
974+ }
975+ }
976+
977+ // Before running Excise, the RangeFeeds should still be running.
978+ assertNoRangeFeedErr (rfErr1 )
979+ assertNoRangeFeedErr (rfErr2 )
980+ assertNoRangeFeedErr (rfErr3 )
981+
982+ waitForExciseError := func (rfErr chan error ) {
983+ testutils .SucceedsSoon (t , func () error {
984+ select {
985+ case err = <- rfErr :
986+ default :
987+ return errors .Errorf ("no error was found on the range feed" )
988+ }
989+
990+ require .Regexp (t , "Replica applied ExciseRequest" , err )
991+ return nil
992+ })
993+ }
994+
995+ // After we excise, we expect that range [a,d) will not see any errors (since
996+ // it doesn't overlap with the deleted span), but the other ranges should see
997+ // an excise error.
998+ require .NoError (t , etc .exciseHelper (ctx , deletedStartKey , deletedEndKey ))
999+ waitForExciseError (rfErr2 )
1000+ waitForExciseError (rfErr3 )
1001+ require .Equal (t , 0 , len (rfErr1 ))
1002+
1003+ // Requests should work normally after excising.
1004+ require .NoError (t , etc .putHelper (ctx , roachpb .Key ("a" )))
1005+ require .NoError (t , etc .putHelper (ctx , roachpb .Key ("e-15000" )))
1006+ require .NoError (t , etc .putHelper (ctx , roachpb .Key ("h-15000" )))
1007+ require .NoError (t , etc .putHelper (ctx , roachpb .Key ("y" )))
1008+ require .NoError (t , etc .deleteRangeHelper (ctx , roachpb .Key ("a" ), roachpb .Key ("b" )))
1009+ require .NoError (t , etc .deleteRangeHelper (ctx , roachpb .Key ("k" ), roachpb .Key ("l" )))
1010+
1011+ // Make sure that the store is consistent after the excise.
1012+ require .NoError (t , etc .checkConsistency (ctx , roachpb .Key ("a" ), roachpb .Key ("z" )))
1013+ }
0 commit comments