Skip to content

Commit 1c2d48d

Browse files
craig[bot]iskettaneh
andcommitted
Merge #143821
143821: kvserver: add test TestRangeFeedWithExcise r=iskettaneh a=iskettaneh This commit adds the test "TestRangeFeedWithExcise" which verifies that RangeFeeds will disconnect when the range (or part of the range) gets excised. References: #143135 Release note: None Co-authored-by: Ibrahim Kettaneh <[email protected]>
2 parents 6e53270 + bb41d44 commit 1c2d48d

File tree

1 file changed

+143
-0
lines changed

1 file changed

+143
-0
lines changed

pkg/kv/kvserver/deleted_external_sstable_test.go

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
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

Comments
 (0)