diff --git a/server/schedule/operator/builder.go b/server/schedule/operator/builder.go index 1073211e8dd..85c892a787e 100644 --- a/server/schedule/operator/builder.go +++ b/server/schedule/operator/builder.go @@ -29,11 +29,13 @@ import ( ) // Builder is used to create operators. Usage: -// op, err := NewBuilder(desc, cluster, region). -// RemovePeer(store1). -// AddPeer(peer1). -// SetLeader(store2). -// Build(kind) +// +// op, err := NewBuilder(desc, cluster, region). +// RemovePeer(store1). +// AddPeer(peer1). +// SetLeader(store2). +// Build(kind) +// // The generated Operator will choose the most appropriate execution order // according to various constraints. type Builder struct { @@ -53,8 +55,9 @@ type Builder struct { targetLeaderStoreID uint64 err error - // skip origin check flags + // skip check flags skipOriginJointStateCheck bool + skipPlacementRulesCheck bool // build flags useJointConsensus bool @@ -80,6 +83,11 @@ func SkipOriginJointStateCheck(b *Builder) { b.skipOriginJointStateCheck = true } +// SkipPlacementRulesCheck lets the builder skip the placement rules check for origin and target peers. +func SkipPlacementRulesCheck(b *Builder) { + b.skipPlacementRulesCheck = true +} + // NewBuilder creates a Builder. func NewBuilder(desc string, cluster opt.Cluster, region *core.RegionInfo, opts ...BuilderOption) *Builder { b := &Builder{ @@ -123,7 +131,7 @@ func NewBuilder(desc string, cluster opt.Cluster, region *core.RegionInfo, opts // placement rules var rules []*placement.Rule - if err == nil && cluster.GetOpts().IsPlacementRulesEnabled() { + if err == nil && !b.skipPlacementRulesCheck && cluster.GetOpts().IsPlacementRulesEnabled() { fit := cluster.GetRuleManager().FitRegion(cluster, region) for _, rf := range fit.RuleFits { rules = append(rules, rf.Rule) @@ -737,7 +745,7 @@ func (b *Builder) allowLeader(peer *metapb.Peer, ignoreClusterLimit bool) bool { } // placement rules - if len(b.rules) == 0 { + if b.skipPlacementRulesCheck || len(b.rules) == 0 { return true } for _, r := range b.rules { diff --git a/server/schedule/operator/create_operator.go b/server/schedule/operator/create_operator.go index 1a42b59921d..b1810f593eb 100644 --- a/server/schedule/operator/create_operator.go +++ b/server/schedule/operator/create_operator.go @@ -67,7 +67,7 @@ func CreateTransferLeaderOperator(desc string, cluster opt.Cluster, region *core // CreateForceTransferLeaderOperator creates an operator that transfers the leader from a source store to a target store forcible. func CreateForceTransferLeaderOperator(desc string, cluster opt.Cluster, region *core.RegionInfo, sourceStoreID uint64, targetStoreID uint64, kind OpKind) (*Operator, error) { - return NewBuilder(desc, cluster, region, SkipOriginJointStateCheck). + return NewBuilder(desc, cluster, region, SkipOriginJointStateCheck, SkipPlacementRulesCheck). SetLeader(targetStoreID). EnableForceTargetLeader(). Build(kind) @@ -219,7 +219,7 @@ func CreateScatterRegionOperator(desc string, cluster opt.Cluster, origin *core. // CreateLeaveJointStateOperator creates an operator that let region leave joint state. func CreateLeaveJointStateOperator(desc string, cluster opt.Cluster, origin *core.RegionInfo) (*Operator, error) { - b := NewBuilder(desc, cluster, origin, SkipOriginJointStateCheck) + b := NewBuilder(desc, cluster, origin, SkipOriginJointStateCheck, SkipPlacementRulesCheck) if b.err == nil && !core.IsInJointState(origin.GetPeers()...) { b.err = errors.Errorf("cannot build leave joint state operator for region which is not in joint state") diff --git a/server/schedule/operator/create_operator_test.go b/server/schedule/operator/create_operator_test.go index b365025674b..27da5b2fa35 100644 --- a/server/schedule/operator/create_operator_test.go +++ b/server/schedule/operator/create_operator_test.go @@ -16,6 +16,7 @@ package operator import ( "context" + "encoding/hex" "strings" . "github.com/pingcap/check" @@ -1073,3 +1074,65 @@ func (s *testCreateOperatorSuite) TestMoveRegionWithoutJointConsensus(c *C) { } } } + +var _ = Suite(&testCreateOperatorWithRulesSuite{}) + +type testCreateOperatorWithRulesSuite struct{} + +// Ref https://github.com/tikv/pd/issues/5401 +func (s *testCreateOperatorWithRulesSuite) TestCreateLeaveJointStateOperatorWithoutFitRules(c *C) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + opts := config.NewTestOptions() + cluster := mockcluster.NewCluster(ctx, opts) + err := cluster.SetRules([]*placement.Rule{ + { + GroupID: "pd", + ID: "default", + StartKeyHex: hex.EncodeToString([]byte("")), + EndKeyHex: hex.EncodeToString([]byte("")), + Role: placement.Voter, + Count: 1, + }, + { + GroupID: "t1", + ID: "t1", + StartKeyHex: hex.EncodeToString([]byte("a")), + EndKeyHex: hex.EncodeToString([]byte("b")), + Role: placement.Voter, + Count: 1, + }, + { + GroupID: "t2", + ID: "t2", + StartKeyHex: hex.EncodeToString([]byte("b")), + EndKeyHex: hex.EncodeToString([]byte("c")), + Role: placement.Voter, + Count: 1, + }, + }) + c.Assert(err, IsNil) + cluster.AddRegionStore(1, 1) + cluster.AddRegionStore(2, 1) + cluster.AddRegionStore(3, 1) + cluster.AddRegionStore(4, 1) + originPeers := []*metapb.Peer{ + {Id: 3, StoreId: 3, Role: metapb.PeerRole_DemotingVoter}, + {Id: 4, StoreId: 4, Role: metapb.PeerRole_IncomingVoter}, + } + + region := core.NewRegionInfo(&metapb.Region{Id: 1, Peers: originPeers, StartKey: []byte("a"), EndKey: []byte("c")}, originPeers[0]) + op, err := CreateLeaveJointStateOperator("test", cluster, region) + c.Assert(err, IsNil) + c.Assert(op.kind, Equals, OpLeader) + c.Assert(op.steps, HasLen, 2) + step0 := op.steps[0].(TransferLeader) + c.Assert(step0.FromStore, Equals, uint64(3)) + c.Assert(step0.ToStore, Equals, uint64(4)) + step1 := op.steps[1].(ChangePeerV2Leave) + c.Assert(step1.PromoteLearners, HasLen, 1) + c.Assert(step1.DemoteVoters, HasLen, 1) + c.Assert(step1.PromoteLearners[0].ToStore, Equals, uint64(4)) + c.Assert(step1.DemoteVoters[0].ToStore, Equals, uint64(3)) +}