@@ -4959,6 +4959,225 @@ func TestAllocatorRebalanceIOOverloadCheck(t *testing.T) {
4959
4959
}
4960
4960
}
4961
4961
4962
+ // TestAllocatorRebalanceMMAConflict tests that the MMA conflict checking logic
4963
+ // in RebalanceVoter works correctly. It should skip MMA conflict checks for
4964
+ // critical rebalances (constraint repair, disk fullness, diversity
4965
+ // improvements) but apply them if it's just for range count based rebalancing.
4966
+ func TestAllocatorRebalanceMMAConflict (t * testing.T ) {
4967
+ defer leaktest .AfterTest (t )()
4968
+ ctx := context .Background ()
4969
+
4970
+ type testCase struct {
4971
+ name string
4972
+ mmaReturnsConflict bool
4973
+ stores []* roachpb.StoreDescriptor
4974
+ conf * roachpb.SpanConfig
4975
+ existingVoters []roachpb.ReplicaDescriptor
4976
+ expectNoAction bool
4977
+ expectedRemoveTarget roachpb.StoreID
4978
+ expectedAddTarget roachpb.StoreID
4979
+ }
4980
+
4981
+ // Stores with locality constraints for testing.
4982
+ constraintRepairConfig := & roachpb.SpanConfig {
4983
+ NumReplicas : 1 ,
4984
+ Constraints : []roachpb.ConstraintsConjunction {{
4985
+ Constraints : []roachpb.Constraint {{Key : "region" , Value : "us-east" , Type : roachpb .Constraint_REQUIRED }},
4986
+ }},
4987
+ }
4988
+ constraintRepairStores := []* roachpb.StoreDescriptor {
4989
+ {
4990
+ StoreID : 1 ,
4991
+ Node : roachpb.NodeDescriptor {
4992
+ NodeID : 1 ,
4993
+ Locality : roachpb.Locality {
4994
+ Tiers : []roachpb.Tier {
4995
+ {Key : "region" , Value : "us-west" },
4996
+ },
4997
+ },
4998
+ },
4999
+ Capacity : roachpb.StoreCapacity {Capacity : 200 , Available : 100 , LogicalBytes : 100 , RangeCount : 40 },
5000
+ },
5001
+ {
5002
+ StoreID : 2 ,
5003
+ Node : roachpb.NodeDescriptor {
5004
+ NodeID : 2 ,
5005
+ Locality : roachpb.Locality {
5006
+ Tiers : []roachpb.Tier {
5007
+ {Key : "region" , Value : "us-east" }, // Has required locality
5008
+ },
5009
+ },
5010
+ },
5011
+ Capacity : roachpb.StoreCapacity {Capacity : 200 , Available : 100 , LogicalBytes : 100 , RangeCount : 50 },
5012
+ },
5013
+ }
5014
+
5015
+ // Stores with disk fullness for testing.
5016
+ diskFullnessStores := []* roachpb.StoreDescriptor {
5017
+ {
5018
+ StoreID : 1 ,
5019
+ Node : roachpb.NodeDescriptor {
5020
+ NodeID : 1 ,
5021
+ },
5022
+ Capacity : roachpb.StoreCapacity {Capacity : 1000 , Available : 10 , LogicalBytes : 990 , RangeCount : 40 }, // Nearly full
5023
+ },
5024
+ {
5025
+ StoreID : 2 ,
5026
+ Node : roachpb.NodeDescriptor {
5027
+ NodeID : 2 ,
5028
+ },
5029
+ Capacity : roachpb.StoreCapacity {Capacity : 1000 , Available : 500 , LogicalBytes : 500 , RangeCount : 50 }, // Plenty of space
5030
+ },
5031
+ }
5032
+
5033
+ // Stores for testing range count rebalancing.
5034
+ rangeCountStores := []* roachpb.StoreDescriptor {
5035
+ {
5036
+ StoreID : 1 ,
5037
+ Node : roachpb.NodeDescriptor {
5038
+ NodeID : 1 ,
5039
+ },
5040
+ Capacity : roachpb.StoreCapacity {Capacity : 1000 , Available : 500 , LogicalBytes : 500 , RangeCount : 100 }, // High range count
5041
+ },
5042
+ {
5043
+ StoreID : 2 ,
5044
+ Node : roachpb.NodeDescriptor {
5045
+ NodeID : 2 ,
5046
+ },
5047
+ Capacity : roachpb.StoreCapacity {Capacity : 1000 , Available : 500 , LogicalBytes : 500 , RangeCount : 50 }, // Low range count
5048
+ },
5049
+ }
5050
+
5051
+ // Stores for testing diversity improvements - different datacenters for
5052
+ // better diversity.
5053
+ diversityStores := []* roachpb.StoreDescriptor {
5054
+ {
5055
+ StoreID : 1 ,
5056
+ Node : roachpb.NodeDescriptor {
5057
+ NodeID : 1 ,
5058
+ Locality : roachpb.Locality {
5059
+ Tiers : []roachpb.Tier {
5060
+ {Key : "datacenter" , Value : "dc1" },
5061
+ },
5062
+ },
5063
+ },
5064
+ Capacity : roachpb.StoreCapacity {Capacity : 1000 , Available : 500 , LogicalBytes : 500 , RangeCount : 50 },
5065
+ },
5066
+ {
5067
+ StoreID : 2 ,
5068
+ Node : roachpb.NodeDescriptor {
5069
+ NodeID : 2 ,
5070
+ Locality : roachpb.Locality {
5071
+ Tiers : []roachpb.Tier {
5072
+ {Key : "datacenter" , Value : "dc2" }, // Different datacenter for better diversity
5073
+ },
5074
+ },
5075
+ },
5076
+ Capacity : roachpb.StoreCapacity {Capacity : 1000 , Available : 500 , LogicalBytes : 500 , RangeCount : 50 },
5077
+ },
5078
+ {
5079
+ StoreID : 3 ,
5080
+ Node : roachpb.NodeDescriptor {
5081
+ NodeID : 3 ,
5082
+ Locality : roachpb.Locality {
5083
+ Tiers : []roachpb.Tier {
5084
+ {Key : "datacenter" , Value : "dc1" }, // Same datacenter as store 1, poor diversity
5085
+ },
5086
+ },
5087
+ },
5088
+ Capacity : roachpb.StoreCapacity {Capacity : 1000 , Available : 500 , LogicalBytes : 500 , RangeCount : 50 },
5089
+ },
5090
+ }
5091
+
5092
+ tests := []testCase {
5093
+ {
5094
+ name : "constraint repair bypasses MMA conflict" ,
5095
+ mmaReturnsConflict : true , // MMA says conflict, but should be ignored
5096
+ stores : constraintRepairStores ,
5097
+ conf : constraintRepairConfig ,
5098
+ existingVoters : replicas (1 ),
5099
+ expectedRemoveTarget : 1 ,
5100
+ expectedAddTarget : 2 ,
5101
+ },
5102
+ {
5103
+ name : "disk fullness bypasses MMA conflict" ,
5104
+ mmaReturnsConflict : true , // MMA says conflict, but should be ignored
5105
+ stores : diskFullnessStores ,
5106
+ conf : emptySpanConfig (),
5107
+ existingVoters : replicas (1 ),
5108
+ expectedRemoveTarget : 1 ,
5109
+ expectedAddTarget : 2 ,
5110
+ },
5111
+ {
5112
+ name : "diversity improvement bypasses MMA conflict" ,
5113
+ mmaReturnsConflict : true , // MMA says conflict, but should be ignored for diversity
5114
+ stores : diversityStores ,
5115
+ conf : emptySpanConfig (),
5116
+ existingVoters : replicas (1 , 3 ), // Both replicas in dc1, poor diversity
5117
+ expectedRemoveTarget : 3 , // Remove one from dc1
5118
+ expectedAddTarget : 2 , // Add to dc2 for better diversity
5119
+ },
5120
+ {
5121
+ name : "range count rebalancing respects MMA conflict" ,
5122
+ mmaReturnsConflict : true , // MMA says conflict, should be respected
5123
+ stores : rangeCountStores ,
5124
+ conf : emptySpanConfig (),
5125
+ existingVoters : replicas (1 ),
5126
+ expectNoAction : true , // Should be blocked by MMA conflict
5127
+ },
5128
+ {
5129
+ name : "range count rebalancing proceeds when MMA allows" ,
5130
+ mmaReturnsConflict : false , // MMA says no conflict
5131
+ stores : rangeCountStores ,
5132
+ conf : emptySpanConfig (),
5133
+ existingVoters : replicas (1 ),
5134
+ expectedRemoveTarget : 1 ,
5135
+ expectedAddTarget : 2 ,
5136
+ },
5137
+ }
5138
+
5139
+ var rangeUsageInfo allocator.RangeUsageInfo
5140
+
5141
+ for i , test := range tests {
5142
+ t .Run (fmt .Sprintf ("%d_%s" , i + 1 , test .name ), func (t * testing.T ) {
5143
+ fmt .Println ("test.name" , test .name )
5144
+ mmaKnobs := & mmaintegration.TestingKnobs {
5145
+ OverrideIsInConflictWithMMA : func (cand roachpb.StoreID ) bool {
5146
+ return test .mmaReturnsConflict
5147
+ },
5148
+ }
5149
+ stopper , g , sp , a , _ := CreateTestAllocatorWithKnobs (
5150
+ ctx , 5 , true /* deterministic */ , nil /* allocatorKnobs */ , mmaKnobs )
5151
+ defer stopper .Stop (ctx )
5152
+ sg := gossiputil .NewStoreGossiper (g )
5153
+ sg .GossipStores (test .stores , t )
5154
+ add , remove , _ , ok := a .RebalanceVoter (
5155
+ ctx ,
5156
+ sp ,
5157
+ test .conf ,
5158
+ nil ,
5159
+ test .existingVoters ,
5160
+ []roachpb.ReplicaDescriptor {},
5161
+ rangeUsageInfo ,
5162
+ storepool .StoreFilterThrottled ,
5163
+ a .ScorerOptions (ctx ),
5164
+ )
5165
+
5166
+ if test .expectNoAction {
5167
+ require .Falsef (t , ok , "expected no rebalance action but got one" )
5168
+ } else {
5169
+ require .Truef (t , ok , "expected rebalance action but got none" )
5170
+ require .Equalf (t , test .expectedAddTarget , add .StoreID ,
5171
+ "the addition target %+v from RebalanceVoter doesn't match expectation %v" ,
5172
+ add , test .expectedAddTarget )
5173
+ require .Equalf (t , test .expectedRemoveTarget , remove .StoreID ,
5174
+ "the removal target %+v from RebalanceVoter doesn't match expectation %v" ,
5175
+ remove , test .expectedRemoveTarget )
5176
+ }
5177
+ })
5178
+ }
5179
+ }
5180
+
4962
5181
// TestVotersCanRebalanceToNonVoterStores ensures that rebalancing of voting
4963
5182
// replicas considers stores that have non-voters as feasible candidates.
4964
5183
func TestVotersCanRebalanceToNonVoterStores (t * testing.T ) {
0 commit comments