99package kvserver
1010
1111import (
12+ "cmp"
1213 "context"
1314 "fmt"
1415 "strings"
1516 "testing"
17+ "time"
1618
19+ "github.com/cockroachdb/cockroach/pkg/kv/kvserver/abortspan"
20+ "github.com/cockroachdb/cockroach/pkg/kv/kvserver/batcheval"
1721 "github.com/cockroachdb/cockroach/pkg/kv/kvserver/kvserverpb"
1822 "github.com/cockroachdb/cockroach/pkg/kv/kvserver/kvstorage"
1923 "github.com/cockroachdb/cockroach/pkg/kv/kvserver/print"
2024 "github.com/cockroachdb/cockroach/pkg/raft/raftpb"
2125 "github.com/cockroachdb/cockroach/pkg/roachpb"
2226 "github.com/cockroachdb/cockroach/pkg/settings/cluster"
2327 "github.com/cockroachdb/cockroach/pkg/storage"
28+ "github.com/cockroachdb/cockroach/pkg/storage/enginepb"
2429 "github.com/cockroachdb/cockroach/pkg/testutils/dd"
30+ "github.com/cockroachdb/cockroach/pkg/util/hlc"
2531 "github.com/cockroachdb/cockroach/pkg/util/leaktest"
2632 "github.com/cockroachdb/cockroach/pkg/util/log"
33+ "github.com/cockroachdb/cockroach/pkg/util/timeutil"
2734 "github.com/cockroachdb/datadriven"
2835 "github.com/cockroachdb/errors"
2936 "github.com/stretchr/testify/require"
@@ -50,10 +57,36 @@ import (
5057// Creates a replica on n1/s1 for the specified range ID. The created replica
5158// may be initialized or uninitialized.
5259//
53- // print- range-state
60+ // create-split range-id=<int> split-key=<key>
5461// ----
5562//
56- // Prints the current range state in the test context.
63+ // Creates a split for the specified range at the given split key, which
64+ // entails creating a SplitTrigger with both the LHS and RHS descriptors.
65+ // Much like how things work in CRDB, the LHS descriptor is created by
66+ // narrowing the original range and a new range descriptor is created for
67+ // the RHS with the same replica set.
68+ //
69+ // set-lease range-id=<int> replica=<int> [lease-type=leader-lease|epoch|expiration]
70+ // ----
71+ //
72+ // Sets the lease for the specified range to the supplied replica. Note that
73+ // the replica parameter specifies NodeIDs, not to be confused with
74+ // ReplicaIDs. By default, the lease is of the leader-lease variety, but
75+ // this may be overriden to an epoch or expiration based lease by using the
76+ // lease-type parameter. For now, we treat the associated lease metadata as
77+ // uninteresting.
78+ //
79+ // run-split-trigger range-id=<int>
80+ // ----
81+ //
82+ // Executes the split trigger for the specified range on n1.
83+ //
84+ // print-range-state [sort-keys=<bool>]
85+ // ----
86+ //
87+ // Prints the current range state in the test context. By default, ranges are
88+ // sorted by range ID. If sort-keys is set to true, ranges are sorted by their
89+ // descriptor's start key instead.
5790func TestReplicaLifecycleDataDriven (t * testing.T ) {
5891 defer leaktest .AfterTest (t )()
5992 defer log .Scope (t ).Close (t )
@@ -116,7 +149,7 @@ func TestReplicaLifecycleDataDriven(t *testing.T) {
116149 if rs .replica != nil {
117150 return errors .New ("initialized replica already exists on n1/s1" ).Error ()
118151 }
119- repl := rs .getReplicaDescriptor (t )
152+ repl := rs .getReplicaDescriptor (t , roachpb . NodeID ( 1 ) )
120153
121154 batch := tc .storage .NewBatch ()
122155 defer batch .Close ()
@@ -148,14 +181,130 @@ func TestReplicaLifecycleDataDriven(t *testing.T) {
148181 require .NoError (t , err , "error committing batch" )
149182 return sb .String ()
150183
184+ case "create-split" :
185+ rangeID := dd .ScanArg [int ](t , d , "range-id" )
186+ splitKey := dd .ScanArg [string ](t , d , "split-key" )
187+ rs := tc .mustGetRangeState (t , roachpb .RangeID (rangeID ))
188+ desc := rs .desc
189+ require .True (
190+ t ,
191+ roachpb .RKey (splitKey ).Compare (desc .StartKey ) > 0 &&
192+ roachpb .RKey (splitKey ).Compare (desc .EndKey ) < 0 ,
193+ "split key not within range" ,
194+ )
195+ leftDesc := desc
196+ leftDesc .EndKey = roachpb .RKey (splitKey )
197+ rightDesc := desc
198+ rightDesc .RangeID = tc .nextRangeID
199+ tc .nextRangeID ++
200+ rightDesc .StartKey = roachpb .RKey (splitKey )
201+ split := & roachpb.SplitTrigger {
202+ LeftDesc : leftDesc ,
203+ RightDesc : rightDesc ,
204+ }
205+ tc .splits [roachpb .RangeID (rangeID )] = split
206+ return "ok"
207+
208+ case "set-lease" :
209+ rangeID := dd .ScanArg [int ](t , d , "range-id" )
210+ replicaNodeID := dd .ScanArg [int ](t , d , "replica" )
211+ leaseType := "leader-lease" // default to a leader-lease
212+ if d .HasArg ("lease-type" ) {
213+ leaseType = dd .ScanArg [string ](t , d , "lease-type" )
214+ }
215+ rs := tc .mustGetRangeState (t , roachpb .RangeID (rangeID ))
216+ targetReplica := rs .getReplicaDescriptor (t , roachpb .NodeID (replicaNodeID ))
217+ // NB: The details of the lease are not important to the test;
218+ // only the type is.
219+ var lease roachpb.Lease
220+ switch leaseType {
221+ case "leader-lease" :
222+ lease = roachpb.Lease {
223+ Replica : * targetReplica ,
224+ Term : 10 ,
225+ MinExpiration : hlc.Timestamp {WallTime : 100 },
226+ }
227+ case "epoch" :
228+ lease = roachpb.Lease {
229+ Replica : * targetReplica ,
230+ Epoch : 20 ,
231+ }
232+ case "expiration" :
233+ lease = roachpb.Lease {
234+ Replica : * targetReplica ,
235+ Expiration : & hlc.Timestamp {WallTime : 300 },
236+ }
237+ default :
238+ t .Fatalf ("unknown lease type: %s" , leaseType )
239+ }
240+ rs .lease = lease
241+ return "ok"
242+
243+ case "run-split-trigger" :
244+ rangeID := dd .ScanArg [int ](t , d , "range-id" )
245+ split , ok := tc .splits [roachpb .RangeID (rangeID )]
246+ require .True (t , ok , "split trigger not found for range-id %d" , rangeID )
247+ rs := tc .mustGetRangeState (t , roachpb .RangeID (rangeID ))
248+ desc := rs .desc
249+ batch := tc .storage .NewBatch ()
250+ defer batch .Close ()
251+
252+ rec := (& batcheval.MockEvalCtx {
253+ ClusterSettings : tc .st ,
254+ Desc : & desc ,
255+ Clock : tc .clock ,
256+ AbortSpan : rs .abortspan ,
257+ LastReplicaGCTimestamp : rs .lastGCTimestamp ,
258+ RangeLeaseDuration : tc .rangeLeaseDuration ,
259+ }).EvalContext ()
260+
261+ in := batcheval.SplitTriggerHelperInput {
262+ LeftLease : rs .lease ,
263+ GCThreshold : & rs .gcThreshold ,
264+ GCHint : & rs .gcHint ,
265+ ReplicaVersion : rs .version ,
266+ }
267+ // Actually run the split trigger.
268+ _ , _ , err := batcheval .TestingSplitTrigger (
269+ ctx , rec , batch /* bothDeltaMS */ , enginepb.MVCCStats {}, split , in , hlc.Timestamp {},
270+ )
271+ require .NoError (t , err )
272+
273+ // Update the test context's notion of the range state after the
274+ // split.
275+ tc .updatePostSplitRangeState (t , ctx , batch , roachpb .RangeID (rangeID ), split )
276+ // Print the state of the batch (all keys/values written as part
277+ // of the split trigger).
278+ output , err := print .DecodeWriteBatch (batch .Repr ())
279+ require .NoError (t , err )
280+ // Commit the batch.
281+ err = batch .Commit (true )
282+ require .NoError (t , err , "error committing batch" )
283+ // TODO(arul): There are double lines in the output (see tryTxn
284+ // in debug_print.go) that we need to strip out for the benefit
285+ // of the datadriven test driver. Until that TODO is addressed,
286+ // we manually split things out here.
287+ return strings .ReplaceAll (output , "\n \n " , "\n " )
288+
151289 case "print-range-state" :
152290 var sb strings.Builder
153291 if len (tc .ranges ) == 0 {
154292 return "no ranges in test context"
155293 }
156- // Sort by range IDs for consistent output.
294+
295+ sortByKeys := false
296+ if d .HasArg ("sort-keys" ) {
297+ sortByKeys = dd .ScanArg [bool ](t , d , "sort-keys" )
298+ }
299+
157300 rangeIDs := maps .Keys (tc .ranges )
158- slices .Sort (rangeIDs )
301+ slices .SortFunc (rangeIDs , func (a , b roachpb.RangeID ) int {
302+ if sortByKeys {
303+ return tc .ranges [a ].desc .StartKey .Compare (tc .ranges [b ].desc .StartKey )
304+ }
305+ // Else sort by range IDs for consistent output.
306+ return cmp .Compare (a , b )
307+ })
159308
160309 for _ , rangeID := range rangeIDs {
161310 rs := tc .ranges [rangeID ]
@@ -172,9 +321,14 @@ func TestReplicaLifecycleDataDriven(t *testing.T) {
172321
173322// rangeState represents the state of a single range in the test context.
174323type rangeState struct {
175- desc roachpb.RangeDescriptor
176- version roachpb.Version
177- replica * replicaInfo // replica on n1/s1.
324+ desc roachpb.RangeDescriptor
325+ version roachpb.Version
326+ lease roachpb.Lease
327+ gcThreshold hlc.Timestamp
328+ gcHint roachpb.GCHint
329+ abortspan * abortspan.AbortSpan
330+ lastGCTimestamp hlc.Timestamp
331+ replica * replicaInfo // replica on n1/s1.
178332}
179333
180334// replicaInfo contains the basic info about a replica, used for managing its
@@ -188,20 +342,30 @@ type replicaInfo struct {
188342// testCtx is a single test's context. It tracks the state of all ranges and any
189343// intermediate steps when performing replica lifecycle events.
190344type testCtx struct {
191- ranges map [roachpb.RangeID ]* rangeState
345+ st * cluster.Settings
346+ clock * hlc.Clock
347+ rangeLeaseDuration time.Duration
348+
192349 nextRangeID roachpb.RangeID // monotonically-increasing rangeID
193- st * cluster.Settings
350+ ranges map [roachpb.RangeID ]* rangeState
351+ splits map [roachpb.RangeID ]* roachpb.SplitTrigger
194352 // The storage engine corresponds to a single store, (n1, s1).
195353 storage storage.Engine
196354}
197355
198356// newTestCtx constructs and returns a new testCtx.
199357func newTestCtx () * testCtx {
200358 st := cluster .MakeTestingClusterSettings ()
359+ manual := timeutil .NewManualTime (timeutil .Unix (0 , 10 ))
360+ clock := hlc .NewClockForTesting (manual )
201361 return & testCtx {
202- ranges : make (map [roachpb.RangeID ]* rangeState ),
362+ st : st ,
363+ clock : clock ,
364+ rangeLeaseDuration : 99 * time .Nanosecond ,
365+
203366 nextRangeID : 1 ,
204- st : st ,
367+ ranges : make (map [roachpb.RangeID ]* rangeState ),
368+ splits : make (map [roachpb.RangeID ]* roachpb.SplitTrigger ),
205369 storage : storage .NewDefaultInMemForTesting (),
206370 }
207371}
@@ -213,9 +377,16 @@ func (tc *testCtx) close() {
213377
214378// newRangeState constructs a new rangeState for the supplied descriptor.
215379func newRangeState (desc roachpb.RangeDescriptor ) * rangeState {
380+ gcThreshold := hlc.Timestamp {WallTime : 4 }
381+ gcHint := roachpb.GCHint {GCTimestamp : gcThreshold }
382+
216383 return & rangeState {
217- desc : desc ,
218- version : roachpb.Version {Major : 10 , Minor : 8 , Internal : 7 }, // dummy version to avoid churn
384+ desc : desc ,
385+ version : roachpb.Version {Major : 10 , Minor : 8 , Internal : 7 }, // dummy version to avoid churn
386+ gcThreshold : gcThreshold ,
387+ gcHint : gcHint ,
388+ abortspan : abortspan .New (desc .RangeID ),
389+ lastGCTimestamp : hlc.Timestamp {},
219390 }
220391}
221392
@@ -248,13 +419,42 @@ func (tc *testCtx) updatePostReplicaCreateState(
248419 }
249420}
250421
251- func (rs * rangeState ) getReplicaDescriptor (t * testing.T ) * roachpb.ReplicaDescriptor {
422+ // updatePostSplitRangeState updates the range state after a split.
423+ func (tc * testCtx ) updatePostSplitRangeState (
424+ t * testing.T ,
425+ ctx context.Context ,
426+ reader storage.Reader ,
427+ lhsRangeID roachpb.RangeID ,
428+ split * roachpb.SplitTrigger ,
429+ ) {
430+ originalRangeState := tc .mustGetRangeState (t , lhsRangeID )
431+ // The range ID should not change for LHS since it's the same range.
432+ require .Equal (t , lhsRangeID , split .LeftDesc .RangeID )
433+ // Update LHS by just updating the descriptor.
434+ originalRangeState .desc = split .LeftDesc
435+ tc .ranges [lhsRangeID ] = originalRangeState
436+ rhsRangeState := newRangeState (split .RightDesc )
437+ // Create RHS range state by reading from the reader.
438+ rhsSl := kvstorage .MakeStateLoader (split .RightDesc .RangeID )
439+ rhsState , err := rhsSl .Load (ctx , reader , & split .RightDesc )
440+ require .NoError (t , err )
441+ rhsRangeState .lease = * rhsState .Lease
442+ rhsRangeState .gcThreshold = * rhsState .GCThreshold
443+ rhsRangeState .gcHint = * rhsState .GCHint
444+ rhsRangeState .version = * rhsState .Version
445+
446+ tc .ranges [split .RightDesc .RangeID ] = rhsRangeState
447+ }
448+
449+ func (rs * rangeState ) getReplicaDescriptor (
450+ t * testing.T , nodeID roachpb.NodeID ,
451+ ) * roachpb.ReplicaDescriptor {
252452 for i , repl := range rs .desc .InternalReplicas {
253- if repl .NodeID == roachpb . NodeID ( 1 ) {
453+ if repl .NodeID == nodeID {
254454 return & rs .desc .InternalReplicas [i ]
255455 }
256456 }
257- t .Fatal ("replica not found" )
457+ t .Fatalf ("replica with NodeID %d not found in range descriptor" , nodeID )
258458 return nil // unreachable
259459}
260460
@@ -264,6 +464,9 @@ func (rs *rangeState) String() string {
264464 if rs .replica != nil {
265465 sb .WriteString (fmt .Sprintf ("\n replica (n1/s1): %s" , rs .replica ))
266466 }
467+ if (rs .lease != roachpb.Lease {}) {
468+ sb .WriteString (fmt .Sprintf ("\n lease: %s" , rs .lease ))
469+ }
267470 return sb .String ()
268471}
269472
0 commit comments