diff --git a/pkg/gc/safepoint.go b/pkg/gc/safepoint.go index e7de32abb17..dee7ebede84 100644 --- a/pkg/gc/safepoint.go +++ b/pkg/gc/safepoint.go @@ -151,7 +151,7 @@ func (m *GCStateManager) advanceGCSafePointImpl(keyspaceID uint32, target uint64 return } -func (m *GCStateManager) AdvanceTxnSafePoint(keyspaceID uint32, target uint64) (AdvanceTxnSafePointResult, error) { +func (m *GCStateManager) AdvanceTxnSafePoint(keyspaceID uint32, target uint64, now time.Time) (AdvanceTxnSafePointResult, error) { keyspaceID, err := m.redirectKeyspace(keyspaceID, false) if err != nil { return AdvanceTxnSafePointResult{}, err @@ -159,12 +159,12 @@ func (m *GCStateManager) AdvanceTxnSafePoint(keyspaceID uint32, target uint64) ( m.lock.Lock() m.lock.Unlock() - return m.advanceTxnSafePointImpl(keyspaceID, target) + return m.advanceTxnSafePointImpl(keyspaceID, target, now) } // advanceTxnSafePointImpl is the internal implementation of AdvanceTxnSafePoint, assuming keyspaceID has been checked // and the mutex has been acquired. -func (m *GCStateManager) advanceTxnSafePointImpl(keyspaceID uint32, target uint64) (AdvanceTxnSafePointResult, error) { +func (m *GCStateManager) advanceTxnSafePointImpl(keyspaceID uint32, target uint64, now time.Time) (AdvanceTxnSafePointResult, error) { isCompatibleMode := false // A helper function for handling the compatibility of the service safe point of "gc_worker", which is needed @@ -204,7 +204,6 @@ func (m *GCStateManager) advanceTxnSafePointImpl(keyspaceID uint32, target uint6 return err1 } - now := time.Now() for _, barrier := range barriers { if barrier.BarrierID == keypath.GCWorkerServiceSafePointID { err1 = keepGCWorkerServiceSafePointCompatible(wb, barrier) @@ -503,7 +502,7 @@ func (m *GCStateManager) CompatibleUpdateServiceGCSafePoint(serviceID string, ne return nil, false, errors.New("TTL of gc_worker's service safe point must be infinity") } - res, err := m.advanceTxnSafePointImpl(keyspaceID, newServiceSafePoint) + res, err := m.advanceTxnSafePointImpl(keyspaceID, newServiceSafePoint, now) if err != nil { return nil, false, err } diff --git a/pkg/gc/safepoint_test.go b/pkg/gc/safepoint_test.go index 1c7df22f38c..4beb60adb5a 100644 --- a/pkg/gc/safepoint_test.go +++ b/pkg/gc/safepoint_test.go @@ -17,11 +17,14 @@ package gc import ( "context" "math" + "slices" "sync" "testing" "time" + "github.com/pingcap/errors" "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" "github.com/tikv/pd/pkg/errs" "github.com/tikv/pd/pkg/id" @@ -35,7 +38,38 @@ import ( "github.com/tikv/pd/server/config" ) -func newGCStateManager(t *testing.T) (provider endpoint.GCStateProvider, gccStateManager *GCStateManager, clean func()) { +type gcManagerTestSuite struct { + suite.Suite + + provider endpoint.GCStateProvider + manager *GCStateManager + clean func() + + keyspacePresets struct { + // A set of shortcuts for different kinds of keyspaces. Initialized in SetupTest. + // Tests are suggested to iterate over all these possibilities. + + // All valid keyspaces. + all []uint32 + // Subset of `all` that can manage their own GC. Includes NullKeyspace and keyspaces configured keyspace-level GC. + manageable []uint32 + // all - manageable. + unmanageable []uint32 + // Subset of `all` that uses unified GC (equals to unmanageable + NullKeyspace). + unifiedGC []uint32 + // A set of not existing keyspace IDs. GC methods are mostly expected to fail on them. + notExisting []uint32 + // A set of different keyspaceIDs that are expected to be regarded the same as NullKeyspaceID (0xffffffff). + // NullKeyspaceID is included. + nullSynonyms []uint32 + } +} + +func TestGCManager(t *testing.T) { + suite.Run(t, new(gcManagerTestSuite)) +} + +func newGCStateManager(t *testing.T) (provider endpoint.GCStateProvider, gcStateManager *GCStateManager, clean func()) { cfg := config.NewConfig() re := require.New(t) @@ -55,13 +89,15 @@ func newGCStateManager(t *testing.T) (provider endpoint.GCStateProvider, gccStat }) kgm := keyspace.NewKeyspaceGroupManager(context.Background(), s, client) keyspaceManager := keyspace.NewKeyspaceManager(context.Background(), s, mockcluster.NewCluster(context.Background(), config.NewPersistOptions(cfg)), allocator, &config.KeyspaceConfig{}, kgm) - gcStateManager := NewGCStateManager(s.GetGCStateProvider(), cfg.PDServerCfg, keyspaceManager) + gcStateManager = NewGCStateManager(s.GetGCStateProvider(), cfg.PDServerCfg, keyspaceManager) err = kgm.Bootstrap(context.Background()) re.NoError(err) err = keyspaceManager.Bootstrap() re.NoError(err) + // keyspaceID 0 exists automatically after bootstrapping. + ks1, err := keyspaceManager.CreateKeyspace(&keyspace.CreateKeyspaceRequest{ Name: "ks1", Config: map[string]string{"gc_management_type": "global_gc"}, @@ -92,8 +128,172 @@ func newGCStateManager(t *testing.T) (provider endpoint.GCStateProvider, gccStat return s.GetGCStateProvider(), gcStateManager, clean } -func TestAdvanceGCSafePointBasic(t *testing.T) { +func (s *gcManagerTestSuite) SetupTest() { + s.provider, s.manager, s.clean = newGCStateManager(s.T()) + + s.keyspacePresets.all = []uint32{constant.NullKeyspaceID, 0, 1, 2, 3} + s.keyspacePresets.manageable = []uint32{constant.NullKeyspaceID, 2} + s.keyspacePresets.unmanageable = []uint32{0, 1, 3} + s.keyspacePresets.unifiedGC = []uint32{constant.NullKeyspaceID, 0, 1, 3} + s.keyspacePresets.notExisting = []uint32{4, 0xffffff} + s.keyspacePresets.nullSynonyms = []uint32{constant.NullKeyspaceID, 0x1000000, 0xfffffffe} +} + +func (s *gcManagerTestSuite) TearDownTest() { + s.clean() +} + +func (s *gcManagerTestSuite) TestAdvanceTxnSafePointBasic() { + re := s.Require() + now := time.Now() + + checkTxnSafePoint := func(keyspaceID uint32, expectedTxnSafePoint uint64) { + state, err := s.manager.GetGCState(keyspaceID) + re.NoError(err) + re.Equal(expectedTxnSafePoint, state.TxnSafePoint) + } + + for _, keyspaceID := range s.keyspacePresets.all { + checkTxnSafePoint(keyspaceID, 0) + } + + for _, keyspaceID := range s.keyspacePresets.manageable { + res, err := s.manager.AdvanceTxnSafePoint(keyspaceID, 10, now) + re.NoError(err) + re.Equal(uint64(0), res.OldTxnSafePoint) + re.Equal(uint64(10), res.NewTxnSafePoint) + re.Equal(uint64(10), res.Target) + re.Empty(res.BlockerDescription) + + checkTxnSafePoint(keyspaceID, 10) + + // Allows updating with the same value (no effect). + res, err = s.manager.AdvanceTxnSafePoint(keyspaceID, 10, now) + re.NoError(err) + re.Equal(uint64(10), res.OldTxnSafePoint) + re.Equal(uint64(10), res.NewTxnSafePoint) + re.Equal(uint64(10), res.Target) + re.Empty(res.BlockerDescription) + + // Does not allow decreasing. + _, err = s.manager.AdvanceTxnSafePoint(keyspaceID, 9, now) + re.Error(err) + re.ErrorIs(err, errs.ErrDecreasingTxnSafePoint) + + // Does not test blocking by GC barriers here. It will be separated in another test case. + } + + for _, keyspaceID := range s.keyspacePresets.unmanageable { + _, err := s.manager.AdvanceTxnSafePoint(keyspaceID, 20, now) + re.Error(err) + re.ErrorIs(err, errs.ErrGCOnInvalidKeyspace) + // Updated in previous loop when updating NullKeyspaceID. + checkTxnSafePoint(keyspaceID, 10) + } + + for _, keyspaceID := range s.keyspacePresets.notExisting { + _, err := s.manager.AdvanceTxnSafePoint(keyspaceID, 30, now) + re.Error(err) + re.ErrorIs(err, errs.ErrKeyspaceNotFound) + } + + for i, keyspaceID := range s.keyspacePresets.nullSynonyms { + // Previously updated to 10. Update to 10+i+1 in i-th loop. + res, err := s.manager.AdvanceTxnSafePoint(keyspaceID, uint64(10+i+1), now) + re.NoError(err) + re.Equal(uint64(10+i), res.OldTxnSafePoint) + re.Equal(uint64(10+i+1), res.NewTxnSafePoint) + + for _, checkingKeyspaceID := range s.keyspacePresets.unifiedGC { + checkTxnSafePoint(checkingKeyspaceID, uint64(10+i)) + } + for _, checkingKeyspaceID := range s.keyspacePresets.nullSynonyms { + checkTxnSafePoint(checkingKeyspaceID, uint64(10+i)) + } + } +} + +func (s *gcManagerTestSuite) TestAdvanceGCSafePointBasic() { + re := s.Require() + + checkGCSafePoint := func(keyspaceID uint32, expectedGCSafePoint uint64) { + state, err := s.manager.GetGCState(keyspaceID) + re.NoError(err) + re.Equal(expectedGCSafePoint, state.GCSafePoint) + } + + for _, keyspaceID := range s.keyspacePresets.all { + checkGCSafePoint(keyspaceID, 0) + } + + for _, keyspaceID := range slices.Concat(s.keyspacePresets.manageable, s.keyspacePresets.nullSynonyms) { + // Txn safe point is not set yet. It should fail first. + _, _, err := s.manager.AdvanceGCSafePoint(keyspaceID, 10) + re.Error(err) + re.ErrorIs(err, errs.ErrGCSafePointExceedsTxnSafePoint) + + // Check there's no effect. + checkGCSafePoint(keyspaceID, 0) + } + + for _, keyspaceID := range s.keyspacePresets.unmanageable { + // Keyspace check is prior to all other errors. + _, _, err := s.manager.AdvanceGCSafePoint(keyspaceID, 10) + re.Error(err) + re.ErrorIs(err, errs.ErrGCOnInvalidKeyspace) + } + + for _, keyspaceID := range s.keyspacePresets.notExisting { + _, _, err := s.manager.AdvanceGCSafePoint(keyspaceID, 10) + re.Error(err) + re.ErrorIs(err, errs.ErrKeyspaceNotFound) + } + + for _, keyspaceID := range s.keyspacePresets.manageable { + _, err := s.manager.AdvanceTxnSafePoint(keyspaceID, 10, time.Now()) + re.NoError(err) + + oldValue, newValue, err := s.manager.AdvanceGCSafePoint(keyspaceID, 5) + re.NoError(err) + re.Equal(uint64(0), oldValue) + re.Equal(uint64(5), newValue) + checkGCSafePoint(keyspaceID, 5) + + oldValue, newValue, err = s.manager.AdvanceGCSafePoint(keyspaceID, 10) + re.NoError(err) + re.Equal(uint64(5), oldValue) + re.Equal(uint64(10), newValue) + checkGCSafePoint(keyspaceID, 10) + oldValue, newValue, err = s.manager.AdvanceGCSafePoint(keyspaceID, 11) + re.Error(err) + re.ErrorIs(err, errs.ErrGCSafePointExceedsTxnSafePoint) + + // Allows updating with the same value (no effect). + oldValue, newValue, err = s.manager.AdvanceGCSafePoint(keyspaceID, 10) + re.NoError(err) + re.Equal(uint64(10), oldValue) + re.Equal(uint64(10), newValue) + + // Does not allow decreasing. + oldValue, newValue, err = s.manager.AdvanceGCSafePoint(keyspaceID, 9) + re.Error(err) + re.ErrorIs(err, errs.ErrDecreasingGCSafePoint) + } + + _, err := s.manager.AdvanceTxnSafePoint(constant.NullKeyspaceID, 30, time.Now()) + re.NoError(err) + for i, keyspaceID := range s.keyspacePresets.nullSynonyms { + // The GC safe point in Already updated to 10 in previous check. So in i-th loop here, we update from 10+i to + // 10+i+1. + oldValue, newValue, err := s.manager.AdvanceGCSafePoint(keyspaceID, uint64(10+i+1)) + re.NoError(err) + re.Equal(uint64(10+i), oldValue) + re.Equal(uint64(10+i+1), newValue) + for _, checkingKeyspaceID := range slices.Concat(s.keyspacePresets.unifiedGC, s.keyspacePresets.nullSynonyms) { + checkGCSafePoint(checkingKeyspaceID, uint64(10+i+1)) + } + } } func testGCSafePointUpdateSequentiallyImpl(t *testing.T, @@ -301,22 +501,225 @@ func TestLegacyServiceGCSafePointRoundingTTL(t *testing.T) { re.False(state.GCBarriers[0].IsExpired(time.Now().Add(time.Hour*24*365*10)), state.GCBarriers[0]) } -func TestAdvanceTxnSafePoint(t *testing.T) { - //_, manager, clean := newGCStateManager(t) - //defer clean() - // - //re := require.New(t) - // - //checkTxnSafePoint := func(keyspaceID uint32, expectedTxnSafePoint uint64) { - // state, err := manager.GetGCState(keyspaceID) - // re.NoError(err) - // re.Equal(expectedTxnSafePoint, state.TxnSafePoint) - //} +func (s *gcManagerTestSuite) TestGCBarriers() { + re := s.Require() -} + getGCBarrier := func(keyspaceID uint32, barrierID string) *endpoint.GCBarrier { + state, err := s.manager.GetGCState(keyspaceID) + re.NoError(err) + idx := slices.IndexFunc(state.GCBarriers, func(b *endpoint.GCBarrier) bool { + return b.BarrierID == barrierID + }) + if idx == -1 { + return nil + } + return state.GCBarriers[idx] + } -func TestGCBarriers(t *testing.T) { + getAllGCBarriers := func(keyspaceID uint32) []*endpoint.GCBarrier { + state, err := s.manager.GetGCState(keyspaceID) + re.NoError(err) + return state.GCBarriers + } + + checkTxnSafePoint := func(keyspaceID uint32, expectedTxnSafePoint uint64) { + state, err := s.manager.GetGCState(keyspaceID) + re.NoError(err) + re.Equal(expectedTxnSafePoint, state.TxnSafePoint) + } + + // Helper for getting pointer of time. + ptime := func(t time.Time) *time.Time { return &t } + + now := time.Date(2025, 03, 06, 11, 50, 30, 0, time.Local) + + for _, keyspaceID := range s.keyspacePresets.all { + re.Empty(getAllGCBarriers(keyspaceID)) + } + + // Test basic functionality within a single keyspace. + for _, keyspaceID := range s.keyspacePresets.manageable { + b, err := s.manager.SetGCBarrier(keyspaceID, "b1", 10, time.Hour, now) + re.NoError(err) + expected := endpoint.NewGCBarrier("b1", 10, ptime(now.Add(time.Hour))) + re.Equal(expected, b) + re.Len(getAllGCBarriers(keyspaceID), 1) + re.Equal(expected, getGCBarrier(keyspaceID, "b1")) + + // Updating the value of the existing GC barrier + b, err = s.manager.SetGCBarrier(keyspaceID, "b1", 15, time.Hour, now) + re.NoError(err) + expected = endpoint.NewGCBarrier("b1", 15, ptime(now.Add(time.Hour))) + re.Equal(expected, b) + re.Len(getAllGCBarriers(keyspaceID), 1) + re.Equal(expected, getGCBarrier(keyspaceID, "b1")) + + b, err = s.manager.SetGCBarrier(keyspaceID, "b1", 15, time.Hour*2, now) + re.NoError(err) + expected = endpoint.NewGCBarrier("b1", 15, ptime(now.Add(time.Hour*2))) + re.Equal(expected, b) + re.Len(getAllGCBarriers(keyspaceID), 1) + re.Equal(expected, getGCBarrier(keyspaceID, "b1")) + + // Allows shrinking the barrier ts. + b, err = s.manager.SetGCBarrier(keyspaceID, "b1", 10, time.Hour, now) + re.NoError(err) + expected = endpoint.NewGCBarrier("b1", 10, ptime(now.Add(time.Hour))) + re.Equal(expected, b) + re.Len(getAllGCBarriers(keyspaceID), 1) + re.Equal(expected, getGCBarrier(keyspaceID, "b1")) + + // Never expiring + b, err = s.manager.SetGCBarrier(keyspaceID, "b1", 10, time.Duration(math.MaxInt64), now) + re.NoError(err) + expected = endpoint.NewGCBarrier("b1", 10, nil) + re.Equal(expected, b) + re.Len(getAllGCBarriers(keyspaceID), 1) + re.Equal(expected, getGCBarrier(keyspaceID, "b1")) + + // GC barriers blocks the txn safe point. + res, err := s.manager.AdvanceTxnSafePoint(keyspaceID, 5, now) + re.NoError(err) + re.Equal(uint64(0), res.OldTxnSafePoint) + re.Equal(uint64(5), res.NewTxnSafePoint) + re.Empty(res.BlockerDescription) + res, err = s.manager.AdvanceTxnSafePoint(keyspaceID, 10, now) + re.NoError(err) + re.Equal(uint64(5), res.OldTxnSafePoint) + re.Equal(uint64(10), res.NewTxnSafePoint) + re.Empty(res.BlockerDescription) + res, err = s.manager.AdvanceTxnSafePoint(keyspaceID, 15, now) + re.NoError(err) + re.Equal(uint64(10), res.OldTxnSafePoint) + re.Equal(uint64(10), res.NewTxnSafePoint) + re.Equal(uint64(15), res.Target) + re.Contains(res.BlockerDescription, "BarrierID: \"b1\"") + checkTxnSafePoint(keyspaceID, 10) + + _, err = s.manager.SetGCBarrier(keyspaceID, "b1", 15, time.Hour, now) + re.NoError(err) + // AdvanceTxnSafePoint advances the txn safe point as much as possible. + res, err = s.manager.AdvanceTxnSafePoint(keyspaceID, 20, now) + re.NoError(err) + re.Equal(uint64(10), res.OldTxnSafePoint) + re.Equal(uint64(15), res.NewTxnSafePoint) + re.Equal(uint64(20), res.Target) + re.Contains(res.BlockerDescription, "BarrierID: \"b1\"") + + // Multiple GC barriers + _, err = s.manager.SetGCBarrier(keyspaceID, "b1", 20, time.Hour, now) + re.NoError(err) + _, err = s.manager.SetGCBarrier(keyspaceID, "b2", 20, time.Hour, now) + re.NoError(err) + re.Len(getAllGCBarriers(keyspaceID), 2) + expected = endpoint.NewGCBarrier("b1", 20, ptime(now.Add(time.Hour))) + re.Equal(expected, getGCBarrier(keyspaceID, "b1")) + expected = endpoint.NewGCBarrier("b2", 20, ptime(now.Add(time.Hour))) + re.Equal(expected, getGCBarrier(keyspaceID, "b2")) + + res, err = s.manager.AdvanceTxnSafePoint(keyspaceID, 25, now) + re.NoError(err) + re.Equal(uint64(15), res.OldTxnSafePoint) + re.Equal(uint64(20), res.NewTxnSafePoint) + re.Equal(uint64(25), res.Target) + re.NotEmpty(res.BlockerDescription) + + // When there are different GC barriers, block with the minimum one. + _, err = s.manager.SetGCBarrier(keyspaceID, "b1", 25, time.Hour, now) + re.NoError(err) + _, err = s.manager.SetGCBarrier(keyspaceID, "b2", 27, time.Hour, now) + re.NoError(err) + re.Len(getAllGCBarriers(keyspaceID), 2) + expected = endpoint.NewGCBarrier("b1", 25, ptime(now.Add(time.Hour))) + re.Equal(expected, getGCBarrier(keyspaceID, "b1")) + expected = endpoint.NewGCBarrier("b2", 27, ptime(now.Add(time.Hour))) + re.Equal(expected, getGCBarrier(keyspaceID, "b2")) + + res, err = s.manager.AdvanceTxnSafePoint(keyspaceID, 30, now) + re.NoError(err) + re.Equal(uint64(20), res.OldTxnSafePoint) + re.Equal(uint64(25), res.NewTxnSafePoint) + re.Equal(uint64(30), res.Target) + re.Contains(res.BlockerDescription, "BarrierID: \"b1\"") + + // Deleting GC barriers + b, err = s.manager.DeleteGCBarrier(keyspaceID, "b1") + re.NoError(err) + expected = endpoint.NewGCBarrier("b1", 25, ptime(now.Add(time.Hour))) + re.Equal(expected, b) + re.Len(getAllGCBarriers(keyspaceID), 1) + + res, err = s.manager.AdvanceTxnSafePoint(keyspaceID, 30, now) + re.NoError(err) + re.Equal(uint64(25), res.OldTxnSafePoint) + re.Equal(uint64(27), res.NewTxnSafePoint) + re.Equal(uint64(30), res.Target) + re.Contains(res.BlockerDescription, "BarrierID: \"b2\"") + b, err = s.manager.DeleteGCBarrier(keyspaceID, "b2") + re.NoError(err) + expected = endpoint.NewGCBarrier("b2", 27, ptime(now.Add(time.Hour))) + re.Equal(expected, b) + re.Empty(getAllGCBarriers(keyspaceID)) + + res, err = s.manager.AdvanceTxnSafePoint(keyspaceID, 30, now) + re.NoError(err) + re.Equal(uint64(27), res.OldTxnSafePoint) + re.Equal(uint64(30), res.NewTxnSafePoint) + re.Equal(uint64(30), res.Target) + re.Empty(res.BlockerDescription) + + // Deleting non-existing GC barrier. + b, err = s.manager.DeleteGCBarrier(keyspaceID, "b1") + re.NoError(err) + re.Nil(b) + + // Test TTL + _, err = s.manager.SetGCBarrier(keyspaceID, "b3", 40, time.Minute, now) + re.NoError(err) + _, err = s.manager.SetGCBarrier(keyspaceID, "b4", 45, time.Minute*2, now) + re.NoError(err) + _, err = s.manager.SetGCBarrier(keyspaceID, "b5", 50, time.Duration(math.MaxInt64), now) + re.NoError(err) + + // Not expiring + for _, t := range []time.Time{now, now.Add(time.Second * 59), now.Add(time.Minute)} { + res, err = s.manager.AdvanceTxnSafePoint(keyspaceID, 60, t) + re.NoError(err) + re.Equal(uint64(40), res.NewTxnSafePoint) + re.Contains(res.BlockerDescription, "BarrierID: \"b3\"") + checkTxnSafePoint(keyspaceID, 40) + re.Len(getAllGCBarriers(keyspaceID), 3) + } + + // b3 expires + res, err = s.manager.AdvanceTxnSafePoint(keyspaceID, 60, now.Add(time.Minute*2)) + re.NoError(err) + re.Equal(uint64(45), res.NewTxnSafePoint) + re.Contains(res.BlockerDescription, "BarrierID: \"b4\"") + checkTxnSafePoint(keyspaceID, 45) + re.Len(getAllGCBarriers(keyspaceID), 2) + + // b4 expires, but b5 never expires. + res, err = s.manager.AdvanceTxnSafePoint(keyspaceID, 60, now.Add(time.Hour*24*365*100)) + re.NoError(err) + re.Equal(uint64(50), res.NewTxnSafePoint) + re.Contains(res.BlockerDescription, "BarrierID: \"b5\"") + checkTxnSafePoint(keyspaceID, 50) + re.Len(getAllGCBarriers(keyspaceID), 1) + + // Manually delete b5 + b, err = s.manager.DeleteGCBarrier(keyspaceID, "b5") + re.NoError(err) + re.Equal("b5", b.BarrierID) + + res, err = s.manager.AdvanceTxnSafePoint(keyspaceID, 60, now.Add(time.Hour*24*365*100)) + re.NoError(err) + re.Equal(uint64(60), res.NewTxnSafePoint) + checkTxnSafePoint(keyspaceID, 60) + + re.Empty(getAllGCBarriers(keyspaceID)) + } } func TestGCStateConstraints(t *testing.T) { @@ -331,87 +734,93 @@ func TestServiceGCSafePointCompatibility(t *testing.T) { } -func TestRedirectKeyspace(t *testing.T) { - _, manager, clean := newGCStateManager(t) - defer clean() - - re := require.New(t) +func (s *gcManagerTestSuite) TestRedirectKeyspace() { + re := s.Require() - keyspaces := []uint32{constant.NullKeyspaceID, 1, 2, 3, 0x1000000, 0xffffff} - expectError := []error{nil, nil, nil, nil, nil, errs.ErrKeyspaceNotFound} - redirectTarget := []uint32{constant.NullKeyspaceID, constant.NullKeyspaceID, 2, constant.NullKeyspaceID, constant.NullKeyspaceID, 0} - systemAPIAllowed := []bool{true, false, true, false, true, false} - - for i, keyspaceID := range keyspaces { - target, err := manager.redirectKeyspace(keyspaceID, true) - if expectError[i] != nil { - re.Error(err, "index: %d", i) - re.ErrorIs(err, expectError[i], "index: %d", i) - } else { - re.NoError(err, "index: %d", i) - re.Equal(redirectTarget[i], target, "index: %d", i) + for _, keyspaceID := range s.keyspacePresets.manageable { + for _, isUserAPI := range []bool{true, false} { + redirected, err := s.manager.redirectKeyspace(keyspaceID, isUserAPI) + re.NoError(err, "keyspaceID: %d, isUserAPI: %v", keyspaceID, isUserAPI) + re.Equal(keyspaceID, redirected, "keyspaceID: %d, isUserAPI: %v", keyspaceID, isUserAPI) } + } + + for _, keyspaceID := range s.keyspacePresets.unmanageable { + redirected, err := s.manager.redirectKeyspace(keyspaceID, true) + re.NoError(err, "keyspaceID: %d", keyspaceID) + re.Equal(constant.NullKeyspaceID, redirected, "keyspaceID: %d", keyspaceID) - target, err = manager.redirectKeyspace(keyspaceID, false) - if expectError[i] != nil { - re.Error(err, "index: %d", i) - re.ErrorIs(err, expectError[i], "index: %d", i) - } else if systemAPIAllowed[i] { - re.NoError(err, "index: %d", i) - re.Equal(redirectTarget[i], target, "index: %d", i) - } else { - re.Error(err, "index: %d", i) - re.ErrorIs(err, errs.ErrGCOnInvalidKeyspace, "index: %d", i) + _, err = s.manager.redirectKeyspace(keyspaceID, false) + re.Error(err, "keyspaceID: %d", keyspaceID) + re.ErrorIs(err, errs.ErrGCOnInvalidKeyspace, "keyspaceID: %d", keyspaceID) + } + + for _, keyspaceID := range s.keyspacePresets.notExisting { + for _, isUserAPI := range []bool{true, false} { + _, err := s.manager.redirectKeyspace(keyspaceID, isUserAPI) + re.Error(err, "keyspaceID: %d, isUserAPI: %v", keyspaceID, isUserAPI) + re.ErrorIs(err, errs.ErrKeyspaceNotFound, "keyspaceID: %d, isUserAPI: %v", keyspaceID, isUserAPI) } } - // Non-null but not existing keyspace id. - _, err := manager.redirectKeyspace(0xffffff, true) - re.Error(err) - re.ErrorIs(err, errs.ErrKeyspaceNotFound) + for _, keyspaceID := range s.keyspacePresets.nullSynonyms { + for _, isUserAPI := range []bool{true, false} { + redirected, err := s.manager.redirectKeyspace(keyspaceID, isUserAPI) + re.NoError(err) + re.Equal(constant.NullKeyspaceID, redirected) + } + } - // Check all public methods that accepts keyspaceID is correctly redirected. + // Check all public methods that accepts keyspaceID are all correctly redirected. testedFunc := []func(keyspaceID uint32) error{ func(keyspaceID uint32) error { - _, err1 := manager.GetGCState(keyspaceID) - return err1 + _, err1 := s.manager.GetGCState(keyspaceID) + return errors.AddStack(err1) }, func(keyspaceID uint32) error { - _, err1 := manager.AdvanceTxnSafePoint(keyspaceID, 10) - return err1 + _, err1 := s.manager.AdvanceTxnSafePoint(keyspaceID, 10, time.Now()) + return errors.AddStack(err1) }, func(keyspaceID uint32) error { - _, _, err1 := manager.AdvanceGCSafePoint(keyspaceID, 10) - return err1 + _, _, err1 := s.manager.AdvanceGCSafePoint(keyspaceID, 10) + return errors.AddStack(err1) }, func(keyspaceID uint32) error { - _, err1 := manager.SetGCBarrier(keyspaceID, "b", 15, time.Hour, time.Now()) - return err1 + _, err1 := s.manager.SetGCBarrier(keyspaceID, "b", 15, time.Hour, time.Now()) + return errors.AddStack(err1) }, func(keyspaceID uint32) error { - _, err1 := manager.DeleteGCBarrier(keyspaceID, "b") - return err1 + _, err1 := s.manager.DeleteGCBarrier(keyspaceID, "b") + return errors.AddStack(err1) }, } isUserAPI := []bool{true, false, false, true, true} - for keyspaceIndex, keyspaceID := range keyspaces { - for funcIndex, f := range testedFunc { - err = f(keyspaceID) - if expectError[keyspaceIndex] != nil { - // Report error no matter it is user API or not. - re.Error(err) - re.ErrorIs(err, expectError[keyspaceIndex]) - } else if isUserAPI[funcIndex] { - // User API is always redirected. - re.NoError(err) - } else if systemAPIAllowed[keyspaceIndex] { - // It's not a user API, and the current keyspace allows running system API. + + for funcIndex, f := range testedFunc { + for _, keyspaceID := range s.keyspacePresets.manageable { + err := f(keyspaceID) + re.NoError(err) + } + + for _, keyspaceID := range s.keyspacePresets.unmanageable { + err := f(keyspaceID) + if isUserAPI[funcIndex] { re.NoError(err) } else { - // It's not a user API and the current keyspace doesn't allow running system API. re.Error(err) re.ErrorIs(err, errs.ErrGCOnInvalidKeyspace) } } + + for _, keyspaceID := range s.keyspacePresets.notExisting { + err := f(keyspaceID) + re.Error(err) + re.ErrorIs(err, errs.ErrKeyspaceNotFound) + } + + for _, keyspaceID := range s.keyspacePresets.nullSynonyms { + err := f(keyspaceID) + re.NoError(err) + } } } diff --git a/server/gc_service.go b/server/gc_service.go index 45191a4c817..5e0758c7eee 100644 --- a/server/gc_service.go +++ b/server/gc_service.go @@ -19,6 +19,7 @@ import ( "encoding/json" "fmt" "math" + "time" clientv3 "go.etcd.io/etcd/client/v3" "go.uber.org/zap" @@ -286,7 +287,7 @@ func (s *GrpcServer) AdvanceTxnSafePoint(ctx context.Context, request *pdpb.Adva target := request.GetTarget() keyspaceID := getKeyspaceIDFromReq(request) - res, err := s.gcStateManager.AdvanceTxnSafePoint(keyspaceID, target) + res, err := s.gcStateManager.AdvanceTxnSafePoint(keyspaceID, target, time.Now()) if err != nil { return nil, err }