github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/kv/kvserver/allocator_scorer_test.go (about) 1 // Copyright 2016 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package kvserver 12 13 import ( 14 "bytes" 15 "context" 16 "fmt" 17 "math" 18 "math/rand" 19 "reflect" 20 "sort" 21 "testing" 22 23 "github.com/cockroachdb/cockroach/pkg/config/zonepb" 24 "github.com/cockroachdb/cockroach/pkg/kv/kvserver/constraint" 25 "github.com/cockroachdb/cockroach/pkg/roachpb" 26 "github.com/cockroachdb/cockroach/pkg/util/leaktest" 27 "github.com/gogo/protobuf/proto" 28 "github.com/kr/pretty" 29 ) 30 31 type storeScore struct { 32 storeID roachpb.StoreID 33 score float64 34 } 35 36 type storeScores []storeScore 37 38 func (s storeScores) Len() int { return len(s) } 39 func (s storeScores) Less(i, j int) bool { 40 if s[i].score == s[j].score { 41 // Ensure a deterministic ordering of equivalent scores 42 return s[i].storeID > s[j].storeID 43 } 44 return s[i].score < s[j].score 45 } 46 func (s storeScores) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 47 48 func TestOnlyValidAndNotFull(t *testing.T) { 49 defer leaktest.AfterTest(t)() 50 51 testCases := []struct { 52 valid, invalid int 53 }{ 54 {0, 0}, 55 {1, 0}, 56 {0, 1}, 57 {1, 1}, 58 {2, 0}, 59 {2, 1}, 60 {2, 2}, 61 {1, 2}, 62 {0, 2}, 63 } 64 65 for _, tc := range testCases { 66 t.Run(fmt.Sprintf("%d,%d", tc.valid, tc.invalid), func(t *testing.T) { 67 var cl candidateList 68 // Order these in backward to ensure sorting works correctly. 69 for i := 0; i < tc.invalid; i++ { 70 cl = append(cl, candidate{}) 71 } 72 for i := 0; i < tc.valid; i++ { 73 cl = append(cl, candidate{valid: true}) 74 } 75 sort.Sort(sort.Reverse(byScore(cl))) 76 77 valid := cl.onlyValidAndNotFull() 78 if a, e := len(valid), tc.valid; a != e { 79 t.Errorf("expected %d valid, actual %d", e, a) 80 } 81 if a, e := len(cl)-len(valid), tc.invalid; a != e { 82 t.Errorf("expected %d invalid, actual %d", e, a) 83 } 84 }) 85 } 86 } 87 88 // TestSelectGoodPanic is a basic regression test against a former panic in 89 // selectGood when called with just invalid/full stores. 90 func TestSelectGoodPanic(t *testing.T) { 91 defer leaktest.AfterTest(t)() 92 93 cl := candidateList{ 94 candidate{ 95 valid: false, 96 }, 97 } 98 allocRand := makeAllocatorRand(rand.NewSource(0)) 99 if good := cl.selectGood(allocRand); good != nil { 100 t.Errorf("cl.selectGood() got %v, want nil", good) 101 } 102 } 103 104 // TestCandidateSelection tests select{good,bad} and {best,worst}constraints. 105 func TestCandidateSelection(t *testing.T) { 106 defer leaktest.AfterTest(t)() 107 108 type scoreTuple struct { 109 diversity int 110 rangeCount int 111 } 112 genCandidates := func(scores []scoreTuple, idShift int) candidateList { 113 var cl candidateList 114 for i, score := range scores { 115 cl = append(cl, candidate{ 116 store: roachpb.StoreDescriptor{ 117 StoreID: roachpb.StoreID(i + idShift), 118 }, 119 // We want to test here that everything works when the deversity score 120 // is not the exact value but very close to it. Nextafter will give us 121 // the closest number to the diversity score in the test case, 122 // that isn't equal to it and is either above or below (at random). 123 diversityScore: math.Nextafter(float64(score.diversity), rand.Float64()), 124 rangeCount: score.rangeCount, 125 valid: true, 126 }) 127 } 128 sort.Sort(sort.Reverse(byScore(cl))) 129 return cl 130 } 131 132 formatter := func(cl candidateList) string { 133 var buffer bytes.Buffer 134 for i, c := range cl { 135 if i != 0 { 136 buffer.WriteRune(',') 137 } 138 buffer.WriteString(fmt.Sprintf("%d:%d", int(c.diversityScore), c.rangeCount)) 139 } 140 return buffer.String() 141 } 142 143 testCases := []struct { 144 candidates []scoreTuple 145 best []scoreTuple 146 worst []scoreTuple 147 good scoreTuple 148 bad scoreTuple 149 }{ 150 { 151 candidates: []scoreTuple{{0, 0}}, 152 best: []scoreTuple{{0, 0}}, 153 worst: []scoreTuple{{0, 0}}, 154 good: scoreTuple{0, 0}, 155 bad: scoreTuple{0, 0}, 156 }, 157 { 158 candidates: []scoreTuple{{0, 0}, {0, 1}}, 159 best: []scoreTuple{{0, 0}, {0, 1}}, 160 worst: []scoreTuple{{0, 0}, {0, 1}}, 161 good: scoreTuple{0, 0}, 162 bad: scoreTuple{0, 1}, 163 }, 164 { 165 candidates: []scoreTuple{{0, 0}, {0, 1}, {0, 2}}, 166 best: []scoreTuple{{0, 0}, {0, 1}, {0, 2}}, 167 worst: []scoreTuple{{0, 0}, {0, 1}, {0, 2}}, 168 good: scoreTuple{0, 1}, 169 bad: scoreTuple{0, 2}, 170 }, 171 { 172 candidates: []scoreTuple{{1, 0}, {0, 1}}, 173 best: []scoreTuple{{1, 0}}, 174 worst: []scoreTuple{{0, 1}}, 175 good: scoreTuple{1, 0}, 176 bad: scoreTuple{0, 1}, 177 }, 178 { 179 candidates: []scoreTuple{{1, 0}, {0, 1}, {0, 2}}, 180 best: []scoreTuple{{1, 0}}, 181 worst: []scoreTuple{{0, 1}, {0, 2}}, 182 good: scoreTuple{1, 0}, 183 bad: scoreTuple{0, 2}, 184 }, 185 { 186 candidates: []scoreTuple{{1, 0}, {1, 1}, {0, 2}}, 187 best: []scoreTuple{{1, 0}, {1, 1}}, 188 worst: []scoreTuple{{0, 2}}, 189 good: scoreTuple{1, 0}, 190 bad: scoreTuple{0, 2}, 191 }, 192 { 193 candidates: []scoreTuple{{1, 0}, {1, 1}, {0, 2}, {0, 3}}, 194 best: []scoreTuple{{1, 0}, {1, 1}}, 195 worst: []scoreTuple{{0, 2}, {0, 3}}, 196 good: scoreTuple{1, 0}, 197 bad: scoreTuple{0, 3}, 198 }, 199 } 200 201 allocRand := makeAllocatorRand(rand.NewSource(0)) 202 for _, tc := range testCases { 203 cl := genCandidates(tc.candidates, 1) 204 t.Run(fmt.Sprintf("best-%s", formatter(cl)), func(t *testing.T) { 205 if a, e := cl.best(), genCandidates(tc.best, 1); !reflect.DeepEqual(a, e) { 206 t.Errorf("expected:%s actual:%s diff:%v", formatter(e), formatter(a), pretty.Diff(e, a)) 207 } 208 }) 209 t.Run(fmt.Sprintf("worst-%s", formatter(cl)), func(t *testing.T) { 210 // Shifting the ids is required to match the end of the list. 211 if a, e := cl.worst(), genCandidates( 212 tc.worst, 213 len(tc.candidates)-len(tc.worst)+1, 214 ); !reflect.DeepEqual(a, e) { 215 t.Errorf("expected:%s actual:%s diff:%v", formatter(e), formatter(a), pretty.Diff(e, a)) 216 } 217 }) 218 t.Run(fmt.Sprintf("good-%s", formatter(cl)), func(t *testing.T) { 219 good := cl.selectGood(allocRand) 220 if good == nil { 221 t.Fatalf("no good candidate found") 222 } 223 actual := scoreTuple{int(good.diversityScore + 0.5), good.rangeCount} 224 if actual != tc.good { 225 t.Errorf("expected:%v actual:%v", tc.good, actual) 226 } 227 }) 228 t.Run(fmt.Sprintf("bad-%s", formatter(cl)), func(t *testing.T) { 229 bad := cl.selectBad(allocRand) 230 if bad == nil { 231 t.Fatalf("no bad candidate found") 232 } 233 actual := scoreTuple{int(bad.diversityScore + 0.5), bad.rangeCount} 234 if actual != tc.bad { 235 t.Errorf("expected:%v actual:%v", tc.bad, actual) 236 } 237 }) 238 } 239 } 240 241 func TestBetterThan(t *testing.T) { 242 defer leaktest.AfterTest(t)() 243 244 testCandidateList := candidateList{ 245 { 246 valid: true, 247 diversityScore: 1, 248 rangeCount: 0, 249 }, 250 { 251 valid: true, 252 diversityScore: 0.9999999999999999, 253 rangeCount: 0, 254 }, 255 { 256 valid: true, 257 diversityScore: 1, 258 rangeCount: 1, 259 }, 260 { 261 valid: true, 262 diversityScore: 1, 263 rangeCount: 1, 264 }, 265 { 266 valid: true, 267 diversityScore: 0, 268 rangeCount: 0, 269 }, 270 { 271 valid: true, 272 diversityScore: 0, 273 rangeCount: 0, 274 }, 275 { 276 valid: true, 277 diversityScore: 0, 278 rangeCount: 1, 279 }, 280 { 281 valid: true, 282 diversityScore: 0, 283 rangeCount: 1, 284 }, 285 { 286 valid: false, 287 diversityScore: 1, 288 rangeCount: 0, 289 }, 290 { 291 valid: false, 292 diversityScore: 0, 293 rangeCount: 0, 294 }, 295 { 296 valid: false, 297 diversityScore: 0, 298 rangeCount: 1, 299 }, 300 } 301 302 expectedResults := []int{0, 0, 2, 2, 4, 4, 6, 6, 8, 8, 8} 303 304 for i := 0; i < len(testCandidateList); i++ { 305 betterThan := testCandidateList.betterThan(testCandidateList[i]) 306 if e, a := expectedResults[i], len(betterThan); e != a { 307 t.Errorf("expected %d results, actual %d", e, a) 308 } 309 } 310 } 311 312 // TestBestRebalanceTarget constructs a hypothetical output of 313 // rebalanceCandidates and verifies that bestRebalanceTarget properly returns 314 // the candidates in the ideal order of preference and omits any that aren't 315 // desirable. 316 func TestBestRebalanceTarget(t *testing.T) { 317 defer leaktest.AfterTest(t)() 318 319 candidates := []rebalanceOptions{ 320 { 321 candidates: []candidate{ 322 { 323 store: roachpb.StoreDescriptor{StoreID: 11}, 324 valid: true, 325 necessary: true, 326 diversityScore: 1.0, 327 rangeCount: 11, 328 }, 329 { 330 store: roachpb.StoreDescriptor{StoreID: 12}, 331 valid: true, 332 necessary: true, 333 diversityScore: 1.0, 334 rangeCount: 12, 335 }, 336 }, 337 existingCandidates: []candidate{ 338 { 339 store: roachpb.StoreDescriptor{StoreID: 1}, 340 valid: true, 341 necessary: true, 342 diversityScore: 0, 343 rangeCount: 1, 344 }, 345 }, 346 }, 347 { 348 candidates: []candidate{ 349 { 350 store: roachpb.StoreDescriptor{StoreID: 13}, 351 valid: true, 352 necessary: true, 353 diversityScore: 1.0, 354 rangeCount: 13, 355 }, 356 { 357 store: roachpb.StoreDescriptor{StoreID: 14}, 358 valid: false, 359 necessary: false, 360 diversityScore: 0.0, 361 rangeCount: 14, 362 }, 363 }, 364 existingCandidates: []candidate{ 365 { 366 store: roachpb.StoreDescriptor{StoreID: 2}, 367 valid: true, 368 necessary: false, 369 diversityScore: 0, 370 rangeCount: 2, 371 }, 372 { 373 store: roachpb.StoreDescriptor{StoreID: 3}, 374 valid: false, 375 necessary: false, 376 diversityScore: 0, 377 rangeCount: 3, 378 }, 379 }, 380 }, 381 } 382 383 expected := []roachpb.StoreID{13, 11, 12} 384 allocRand := makeAllocatorRand(rand.NewSource(0)) 385 var i int 386 for { 387 i++ 388 target, existing := bestRebalanceTarget(allocRand, candidates) 389 if len(expected) == 0 { 390 if target == nil { 391 break 392 } 393 t.Errorf("round %d: expected nil, got target=%+v, existing=%+v", i, target, existing) 394 continue 395 } 396 if target == nil { 397 t.Errorf("round %d: expected s%d, got nil", i, expected[0]) 398 } else if target.store.StoreID != expected[0] { 399 t.Errorf("round %d: expected s%d, got target=%+v, existing=%+v", 400 i, expected[0], target, existing) 401 } 402 expected = expected[1:] 403 } 404 } 405 406 func TestStoreHasReplica(t *testing.T) { 407 defer leaktest.AfterTest(t)() 408 409 var existing []roachpb.ReplicaDescriptor 410 for i := 2; i < 10; i += 2 { 411 existing = append(existing, roachpb.ReplicaDescriptor{StoreID: roachpb.StoreID(i)}) 412 } 413 for i := 1; i < 10; i++ { 414 if e, a := i%2 == 0, storeHasReplica(roachpb.StoreID(i), existing); e != a { 415 t.Errorf("StoreID %d expected to be %t, got %t", i, e, a) 416 } 417 } 418 } 419 420 // testStoreTierSetup returns a tier struct constructed using the passed in values. 421 // If any value is an empty string, it is not included. 422 func testStoreTierSetup(datacenter, floor, rack, slot string) []roachpb.Tier { 423 var tiers []roachpb.Tier 424 if datacenter != "" { 425 tiers = append(tiers, roachpb.Tier{Key: "datacenter", Value: datacenter}) 426 } 427 if floor != "" { 428 tiers = append(tiers, roachpb.Tier{Key: "floor", Value: floor}) 429 } 430 if rack != "" { 431 tiers = append(tiers, roachpb.Tier{Key: "rack", Value: rack}) 432 } 433 if slot != "" { 434 tiers = append(tiers, roachpb.Tier{Key: "slot", Value: slot}) 435 } 436 return tiers 437 } 438 439 // testStoreCapacitySetup returns a store capacity in which the total capacity 440 // is always 100 and available and range count are passed in. 441 func testStoreCapacitySetup(available int64, rangeCount int32) roachpb.StoreCapacity { 442 return roachpb.StoreCapacity{ 443 Capacity: 100, 444 Available: available, 445 LogicalBytes: 100 - available, 446 RangeCount: rangeCount, 447 } 448 } 449 450 // This is a collection of test stores used by a suite of tests. 451 var ( 452 testStoreUSa15 = roachpb.StoreID(1) // us-a-1-5 453 testStoreUSa15Dupe = roachpb.StoreID(2) // us-a-1-5 454 testStoreUSa1 = roachpb.StoreID(3) // us-a-1 455 testStoreUSb = roachpb.StoreID(4) // us-b 456 testStoreEurope = roachpb.StoreID(5) // eur-a-1-5 457 458 testStores = map[roachpb.StoreID]roachpb.StoreDescriptor{ 459 testStoreUSa15: { 460 StoreID: testStoreUSa15, 461 Attrs: roachpb.Attributes{ 462 Attrs: []string{"a"}, 463 }, 464 Node: roachpb.NodeDescriptor{ 465 NodeID: roachpb.NodeID(testStoreUSa15), 466 Locality: roachpb.Locality{ 467 Tiers: testStoreTierSetup("us", "a", "1", "5"), 468 }, 469 }, 470 Capacity: testStoreCapacitySetup(1, 99), 471 }, 472 testStoreUSa15Dupe: { 473 StoreID: testStoreUSa15Dupe, 474 Attrs: roachpb.Attributes{ 475 Attrs: []string{"a"}, 476 }, 477 Node: roachpb.NodeDescriptor{ 478 NodeID: roachpb.NodeID(testStoreUSa15Dupe), 479 Locality: roachpb.Locality{ 480 Tiers: testStoreTierSetup("us", "a", "1", "5"), 481 }, 482 }, 483 Capacity: testStoreCapacitySetup(1, 99), 484 }, 485 testStoreUSa1: { 486 StoreID: testStoreUSa1, 487 Attrs: roachpb.Attributes{ 488 Attrs: []string{"a", "b"}, 489 }, 490 Node: roachpb.NodeDescriptor{ 491 NodeID: roachpb.NodeID(testStoreUSa1), 492 Locality: roachpb.Locality{ 493 Tiers: testStoreTierSetup("us", "a", "1", ""), 494 }, 495 }, 496 Capacity: testStoreCapacitySetup(100, 0), 497 }, 498 testStoreUSb: { 499 StoreID: testStoreUSb, 500 Attrs: roachpb.Attributes{ 501 Attrs: []string{"a", "b", "c"}, 502 }, 503 Node: roachpb.NodeDescriptor{ 504 NodeID: roachpb.NodeID(testStoreUSb), 505 Locality: roachpb.Locality{ 506 Tiers: testStoreTierSetup("us", "b", "", ""), 507 }, 508 }, 509 Capacity: testStoreCapacitySetup(50, 50), 510 }, 511 testStoreEurope: { 512 StoreID: testStoreEurope, 513 Node: roachpb.NodeDescriptor{ 514 NodeID: roachpb.NodeID(testStoreEurope), 515 Locality: roachpb.Locality{ 516 Tiers: testStoreTierSetup("eur", "a", "1", "5"), 517 }, 518 }, 519 Capacity: testStoreCapacitySetup(60, 40), 520 }, 521 } 522 ) 523 524 func getTestStoreDesc(storeID roachpb.StoreID) (roachpb.StoreDescriptor, bool) { 525 desc, ok := testStores[storeID] 526 return desc, ok 527 } 528 529 func testStoreReplicas(storeIDs []roachpb.StoreID) []roachpb.ReplicaDescriptor { 530 var result []roachpb.ReplicaDescriptor 531 for _, storeID := range storeIDs { 532 result = append(result, roachpb.ReplicaDescriptor{ 533 NodeID: roachpb.NodeID(storeID), 534 StoreID: storeID, 535 }) 536 } 537 return result 538 } 539 540 func TestConstraintsCheck(t *testing.T) { 541 defer leaktest.AfterTest(t)() 542 543 testCases := []struct { 544 name string 545 constraints []zonepb.ConstraintsConjunction 546 expected map[roachpb.StoreID]bool 547 }{ 548 { 549 name: "required constraint", 550 constraints: []zonepb.ConstraintsConjunction{ 551 { 552 Constraints: []zonepb.Constraint{ 553 {Value: "b", Type: zonepb.Constraint_REQUIRED}, 554 }, 555 }, 556 }, 557 expected: map[roachpb.StoreID]bool{ 558 testStoreUSa1: true, 559 testStoreUSb: true, 560 }, 561 }, 562 { 563 name: "required locality constraints", 564 constraints: []zonepb.ConstraintsConjunction{ 565 { 566 Constraints: []zonepb.Constraint{ 567 {Key: "datacenter", Value: "us", Type: zonepb.Constraint_REQUIRED}, 568 }, 569 }, 570 }, 571 expected: map[roachpb.StoreID]bool{ 572 testStoreUSa15: true, 573 testStoreUSa15Dupe: true, 574 testStoreUSa1: true, 575 testStoreUSb: true, 576 }, 577 }, 578 { 579 name: "prohibited constraints", 580 constraints: []zonepb.ConstraintsConjunction{ 581 { 582 Constraints: []zonepb.Constraint{ 583 {Value: "b", Type: zonepb.Constraint_PROHIBITED}, 584 }, 585 }, 586 }, 587 expected: map[roachpb.StoreID]bool{ 588 testStoreUSa15: true, 589 testStoreUSa15Dupe: true, 590 testStoreEurope: true, 591 }, 592 }, 593 { 594 name: "prohibited locality constraints", 595 constraints: []zonepb.ConstraintsConjunction{ 596 { 597 Constraints: []zonepb.Constraint{ 598 {Key: "datacenter", Value: "us", Type: zonepb.Constraint_PROHIBITED}, 599 }, 600 }, 601 }, 602 expected: map[roachpb.StoreID]bool{ 603 testStoreEurope: true, 604 }, 605 }, 606 { 607 name: "positive constraints are ignored", 608 constraints: []zonepb.ConstraintsConjunction{ 609 { 610 Constraints: []zonepb.Constraint{ 611 {Value: "a", Type: zonepb.Constraint_DEPRECATED_POSITIVE}, 612 {Value: "b", Type: zonepb.Constraint_DEPRECATED_POSITIVE}, 613 {Value: "c", Type: zonepb.Constraint_DEPRECATED_POSITIVE}, 614 }, 615 }, 616 }, 617 expected: map[roachpb.StoreID]bool{ 618 testStoreUSa15: true, 619 testStoreUSa15Dupe: true, 620 testStoreUSa1: true, 621 testStoreUSb: true, 622 testStoreEurope: true, 623 }, 624 }, 625 { 626 name: "positive locality constraints are ignored", 627 constraints: []zonepb.ConstraintsConjunction{ 628 { 629 Constraints: []zonepb.Constraint{ 630 {Key: "datacenter", Value: "eur", Type: zonepb.Constraint_DEPRECATED_POSITIVE}, 631 }, 632 }, 633 }, 634 expected: map[roachpb.StoreID]bool{ 635 testStoreUSa15: true, 636 testStoreUSa15Dupe: true, 637 testStoreUSa1: true, 638 testStoreUSb: true, 639 testStoreEurope: true, 640 }, 641 }, 642 { 643 name: "NumReplicas doesn't affect constraint checking", 644 constraints: []zonepb.ConstraintsConjunction{ 645 { 646 Constraints: []zonepb.Constraint{ 647 {Key: "datacenter", Value: "eur", Type: zonepb.Constraint_REQUIRED}, 648 }, 649 NumReplicas: 1, 650 }, 651 }, 652 expected: map[roachpb.StoreID]bool{ 653 testStoreEurope: true, 654 }, 655 }, 656 { 657 name: "multiple per-replica constraints are respected", 658 constraints: []zonepb.ConstraintsConjunction{ 659 { 660 Constraints: []zonepb.Constraint{ 661 {Key: "datacenter", Value: "eur", Type: zonepb.Constraint_REQUIRED}, 662 }, 663 NumReplicas: 1, 664 }, 665 { 666 Constraints: []zonepb.Constraint{ 667 {Value: "b", Type: zonepb.Constraint_REQUIRED}, 668 }, 669 NumReplicas: 1, 670 }, 671 }, 672 expected: map[roachpb.StoreID]bool{ 673 testStoreUSa1: true, 674 testStoreUSb: true, 675 testStoreEurope: true, 676 }, 677 }, 678 } 679 680 for _, tc := range testCases { 681 t.Run(tc.name, func(t *testing.T) { 682 for _, s := range testStores { 683 valid := constraintsCheck(s, tc.constraints) 684 ok := tc.expected[s.StoreID] 685 if valid != ok { 686 t.Errorf("expected store %d to be %t, but got %t", s.StoreID, ok, valid) 687 continue 688 } 689 } 690 }) 691 } 692 } 693 694 func TestAllocateConstraintsCheck(t *testing.T) { 695 defer leaktest.AfterTest(t)() 696 697 testCases := []struct { 698 name string 699 constraints []zonepb.ConstraintsConjunction 700 zoneNumReplicas int32 701 existing []roachpb.StoreID 702 expectedValid map[roachpb.StoreID]bool 703 expectedNecessary map[roachpb.StoreID]bool 704 }{ 705 { 706 name: "prohibited constraint", 707 constraints: []zonepb.ConstraintsConjunction{ 708 { 709 Constraints: []zonepb.Constraint{ 710 {Value: "b", Type: zonepb.Constraint_PROHIBITED}, 711 }, 712 }, 713 }, 714 existing: nil, 715 expectedValid: map[roachpb.StoreID]bool{ 716 testStoreUSa15: true, 717 testStoreUSa15Dupe: true, 718 testStoreEurope: true, 719 }, 720 expectedNecessary: map[roachpb.StoreID]bool{}, 721 }, 722 { 723 name: "required constraint", 724 constraints: []zonepb.ConstraintsConjunction{ 725 { 726 Constraints: []zonepb.Constraint{ 727 {Value: "b", Type: zonepb.Constraint_REQUIRED}, 728 }, 729 }, 730 }, 731 existing: nil, 732 expectedValid: map[roachpb.StoreID]bool{ 733 testStoreUSa1: true, 734 testStoreUSb: true, 735 }, 736 expectedNecessary: map[roachpb.StoreID]bool{}, 737 }, 738 { 739 name: "required constraint with NumReplicas", 740 constraints: []zonepb.ConstraintsConjunction{ 741 { 742 Constraints: []zonepb.Constraint{ 743 {Value: "b", Type: zonepb.Constraint_REQUIRED}, 744 }, 745 NumReplicas: 3, 746 }, 747 }, 748 existing: nil, 749 expectedValid: map[roachpb.StoreID]bool{ 750 testStoreUSa1: true, 751 testStoreUSb: true, 752 }, 753 expectedNecessary: map[roachpb.StoreID]bool{ 754 testStoreUSa1: true, 755 testStoreUSb: true, 756 }, 757 }, 758 { 759 name: "multiple required constraints with NumReplicas", 760 constraints: []zonepb.ConstraintsConjunction{ 761 { 762 Constraints: []zonepb.Constraint{ 763 {Value: "a", Type: zonepb.Constraint_REQUIRED}, 764 }, 765 NumReplicas: 1, 766 }, 767 { 768 Constraints: []zonepb.Constraint{ 769 {Value: "b", Type: zonepb.Constraint_REQUIRED}, 770 }, 771 NumReplicas: 1, 772 }, 773 }, 774 existing: nil, 775 expectedValid: map[roachpb.StoreID]bool{ 776 testStoreUSa15: true, 777 testStoreUSa15Dupe: true, 778 testStoreUSa1: true, 779 testStoreUSb: true, 780 }, 781 expectedNecessary: map[roachpb.StoreID]bool{ 782 testStoreUSa15: true, 783 testStoreUSa15Dupe: true, 784 testStoreUSa1: true, 785 testStoreUSb: true, 786 }, 787 }, 788 { 789 name: "multiple required constraints with NumReplicas and existing replicas", 790 constraints: []zonepb.ConstraintsConjunction{ 791 { 792 Constraints: []zonepb.Constraint{ 793 {Value: "a", Type: zonepb.Constraint_REQUIRED}, 794 }, 795 NumReplicas: 1, 796 }, 797 { 798 Constraints: []zonepb.Constraint{ 799 {Value: "b", Type: zonepb.Constraint_REQUIRED}, 800 }, 801 NumReplicas: 1, 802 }, 803 }, 804 existing: []roachpb.StoreID{testStoreUSa1}, 805 expectedValid: map[roachpb.StoreID]bool{ 806 testStoreUSa15: true, 807 testStoreUSa15Dupe: true, 808 testStoreUSa1: true, 809 testStoreUSb: true, 810 }, 811 expectedNecessary: map[roachpb.StoreID]bool{}, 812 }, 813 { 814 name: "multiple required constraints with NumReplicas and not enough existing replicas", 815 constraints: []zonepb.ConstraintsConjunction{ 816 { 817 Constraints: []zonepb.Constraint{ 818 {Value: "a", Type: zonepb.Constraint_REQUIRED}, 819 }, 820 NumReplicas: 1, 821 }, 822 { 823 Constraints: []zonepb.Constraint{ 824 {Value: "b", Type: zonepb.Constraint_REQUIRED}, 825 }, 826 NumReplicas: 2, 827 }, 828 }, 829 existing: []roachpb.StoreID{testStoreUSa1}, 830 expectedValid: map[roachpb.StoreID]bool{ 831 testStoreUSa15: true, 832 testStoreUSa15Dupe: true, 833 testStoreUSa1: true, 834 testStoreUSb: true, 835 }, 836 expectedNecessary: map[roachpb.StoreID]bool{ 837 testStoreUSa1: true, 838 testStoreUSb: true, 839 }, 840 }, 841 { 842 name: "multiple required constraints with NumReplicas and sum(NumReplicas) < zone.NumReplicas", 843 constraints: []zonepb.ConstraintsConjunction{ 844 { 845 Constraints: []zonepb.Constraint{ 846 {Value: "a", Type: zonepb.Constraint_REQUIRED}, 847 }, 848 NumReplicas: 1, 849 }, 850 { 851 Constraints: []zonepb.Constraint{ 852 {Value: "b", Type: zonepb.Constraint_REQUIRED}, 853 }, 854 NumReplicas: 1, 855 }, 856 }, 857 zoneNumReplicas: 3, 858 existing: nil, 859 expectedValid: map[roachpb.StoreID]bool{ 860 testStoreUSa15: true, 861 testStoreUSa15Dupe: true, 862 testStoreUSa1: true, 863 testStoreUSb: true, 864 testStoreEurope: true, 865 }, 866 expectedNecessary: map[roachpb.StoreID]bool{ 867 testStoreUSa15: true, 868 testStoreUSa15Dupe: true, 869 testStoreUSa1: true, 870 testStoreUSb: true, 871 }, 872 }, 873 { 874 name: "multiple required constraints with sum(NumReplicas) < zone.NumReplicas and not enough existing replicas", 875 constraints: []zonepb.ConstraintsConjunction{ 876 { 877 Constraints: []zonepb.Constraint{ 878 {Value: "a", Type: zonepb.Constraint_REQUIRED}, 879 }, 880 NumReplicas: 1, 881 }, 882 { 883 Constraints: []zonepb.Constraint{ 884 {Value: "b", Type: zonepb.Constraint_REQUIRED}, 885 }, 886 NumReplicas: 2, 887 }, 888 }, 889 zoneNumReplicas: 5, 890 existing: []roachpb.StoreID{testStoreUSa1}, 891 expectedValid: map[roachpb.StoreID]bool{ 892 testStoreUSa15: true, 893 testStoreUSa15Dupe: true, 894 testStoreUSa1: true, 895 testStoreUSb: true, 896 testStoreEurope: true, 897 }, 898 expectedNecessary: map[roachpb.StoreID]bool{ 899 testStoreUSa1: true, 900 testStoreUSb: true, 901 }, 902 }, 903 } 904 905 for _, tc := range testCases { 906 t.Run(tc.name, func(t *testing.T) { 907 zone := &zonepb.ZoneConfig{ 908 Constraints: tc.constraints, 909 NumReplicas: proto.Int32(tc.zoneNumReplicas), 910 } 911 analyzed := constraint.AnalyzeConstraints( 912 context.Background(), getTestStoreDesc, testStoreReplicas(tc.existing), zone) 913 for _, s := range testStores { 914 valid, necessary := allocateConstraintsCheck(s, analyzed) 915 if e, a := tc.expectedValid[s.StoreID], valid; e != a { 916 t.Errorf("expected allocateConstraintsCheck(s%d).valid to be %t, but got %t", 917 s.StoreID, e, a) 918 } 919 if e, a := tc.expectedNecessary[s.StoreID], necessary; e != a { 920 t.Errorf("expected allocateConstraintsCheck(s%d).necessary to be %t, but got %t", 921 s.StoreID, e, a) 922 } 923 } 924 }) 925 } 926 } 927 928 func TestRemoveConstraintsCheck(t *testing.T) { 929 defer leaktest.AfterTest(t)() 930 931 type expected struct { 932 valid, necessary bool 933 } 934 testCases := []struct { 935 name string 936 constraints []zonepb.ConstraintsConjunction 937 zoneNumReplicas int32 938 expected map[roachpb.StoreID]expected 939 }{ 940 { 941 name: "prohibited constraint", 942 constraints: []zonepb.ConstraintsConjunction{ 943 { 944 Constraints: []zonepb.Constraint{ 945 {Value: "b", Type: zonepb.Constraint_PROHIBITED}, 946 }, 947 }, 948 }, 949 expected: map[roachpb.StoreID]expected{ 950 testStoreUSa15: {true, false}, 951 testStoreUSa15Dupe: {true, false}, 952 testStoreEurope: {true, false}, 953 testStoreUSa1: {false, false}, 954 }, 955 }, 956 { 957 name: "required constraint", 958 constraints: []zonepb.ConstraintsConjunction{ 959 { 960 Constraints: []zonepb.Constraint{ 961 {Value: "b", Type: zonepb.Constraint_REQUIRED}, 962 }, 963 }, 964 }, 965 expected: map[roachpb.StoreID]expected{ 966 testStoreUSa15: {false, false}, 967 testStoreUSa15Dupe: {false, false}, 968 testStoreEurope: {false, false}, 969 testStoreUSa1: {true, false}, 970 }, 971 }, 972 { 973 name: "required constraint with NumReplicas", 974 constraints: []zonepb.ConstraintsConjunction{ 975 { 976 Constraints: []zonepb.Constraint{ 977 {Value: "b", Type: zonepb.Constraint_REQUIRED}, 978 }, 979 NumReplicas: 2, 980 }, 981 }, 982 expected: map[roachpb.StoreID]expected{ 983 testStoreUSa15: {false, false}, 984 testStoreEurope: {false, false}, 985 testStoreUSa1: {true, true}, 986 testStoreUSb: {true, true}, 987 }, 988 }, 989 { 990 name: "multiple required constraints with NumReplicas", 991 constraints: []zonepb.ConstraintsConjunction{ 992 { 993 Constraints: []zonepb.Constraint{ 994 {Value: "a", Type: zonepb.Constraint_REQUIRED}, 995 }, 996 NumReplicas: 1, 997 }, 998 { 999 Constraints: []zonepb.Constraint{ 1000 {Value: "b", Type: zonepb.Constraint_REQUIRED}, 1001 }, 1002 NumReplicas: 1, 1003 }, 1004 }, 1005 expected: map[roachpb.StoreID]expected{ 1006 testStoreUSa15: {true, false}, 1007 testStoreUSa1: {true, true}, 1008 testStoreEurope: {false, false}, 1009 }, 1010 }, 1011 { 1012 name: "required constraint with NumReplicas and sum(NumReplicas) < zone.NumReplicas", 1013 constraints: []zonepb.ConstraintsConjunction{ 1014 { 1015 Constraints: []zonepb.Constraint{ 1016 {Value: "b", Type: zonepb.Constraint_REQUIRED}, 1017 }, 1018 NumReplicas: 2, 1019 }, 1020 }, 1021 zoneNumReplicas: 3, 1022 expected: map[roachpb.StoreID]expected{ 1023 testStoreUSa15: {true, false}, 1024 testStoreEurope: {true, false}, 1025 testStoreUSa1: {true, true}, 1026 testStoreUSb: {true, true}, 1027 }, 1028 }, 1029 } 1030 1031 for _, tc := range testCases { 1032 t.Run(tc.name, func(t *testing.T) { 1033 var existing []roachpb.ReplicaDescriptor 1034 for storeID := range tc.expected { 1035 existing = append(existing, roachpb.ReplicaDescriptor{ 1036 NodeID: roachpb.NodeID(storeID), 1037 StoreID: storeID, 1038 }) 1039 } 1040 zone := &zonepb.ZoneConfig{ 1041 Constraints: tc.constraints, 1042 NumReplicas: proto.Int32(tc.zoneNumReplicas), 1043 } 1044 analyzed := constraint.AnalyzeConstraints( 1045 context.Background(), getTestStoreDesc, existing, zone) 1046 for storeID, expected := range tc.expected { 1047 valid, necessary := removeConstraintsCheck(testStores[storeID], analyzed) 1048 if e, a := expected.valid, valid; e != a { 1049 t.Errorf("expected removeConstraintsCheck(s%d).valid to be %t, but got %t", 1050 storeID, e, a) 1051 } 1052 if e, a := expected.necessary, necessary; e != a { 1053 t.Errorf("expected removeConstraintsCheck(s%d).necessary to be %t, but got %t", 1054 storeID, e, a) 1055 } 1056 } 1057 }) 1058 } 1059 } 1060 1061 func TestShouldRebalanceDiversity(t *testing.T) { 1062 defer leaktest.AfterTest(t)() 1063 1064 options := scorerOptions{} 1065 newStore := func(id int, locality roachpb.Locality) roachpb.StoreDescriptor { 1066 return roachpb.StoreDescriptor{ 1067 StoreID: roachpb.StoreID(id), 1068 Node: roachpb.NodeDescriptor{ 1069 NodeID: roachpb.NodeID(id), 1070 Locality: locality, 1071 }, 1072 } 1073 } 1074 localityForNodeID := func(sl StoreList, id roachpb.NodeID) roachpb.Locality { 1075 for _, store := range sl.stores { 1076 if store.Node.NodeID == id { 1077 return store.Node.Locality 1078 } 1079 } 1080 t.Fatalf("no locality for n%d in StoreList %+v", id, sl) 1081 return roachpb.Locality{} 1082 } 1083 locUS := roachpb.Locality{ 1084 Tiers: testStoreTierSetup("us", "", "", ""), 1085 } 1086 locEU := roachpb.Locality{ 1087 Tiers: testStoreTierSetup("eu", "", "", ""), 1088 } 1089 locAS := roachpb.Locality{ 1090 Tiers: testStoreTierSetup("as", "", "", ""), 1091 } 1092 locAU := roachpb.Locality{ 1093 Tiers: testStoreTierSetup("au", "", "", ""), 1094 } 1095 sl3by3 := StoreList{ 1096 stores: []roachpb.StoreDescriptor{ 1097 newStore(1, locUS), newStore(2, locUS), newStore(3, locUS), 1098 newStore(4, locEU), newStore(5, locEU), newStore(6, locEU), 1099 newStore(7, locAS), newStore(8, locAS), newStore(9, locAS), 1100 }, 1101 } 1102 sl4by3 := StoreList{ 1103 stores: []roachpb.StoreDescriptor{ 1104 newStore(1, locUS), newStore(2, locUS), newStore(3, locUS), 1105 newStore(4, locEU), newStore(5, locEU), newStore(6, locEU), 1106 newStore(7, locAS), newStore(8, locAS), newStore(9, locAS), 1107 newStore(10, locAU), newStore(11, locAU), newStore(12, locAU), 1108 }, 1109 } 1110 1111 testCases := []struct { 1112 s roachpb.StoreDescriptor 1113 sl StoreList 1114 existingNodeIDs []roachpb.NodeID 1115 expected bool 1116 }{ 1117 { 1118 s: newStore(1, locUS), 1119 sl: sl3by3, 1120 existingNodeIDs: []roachpb.NodeID{1, 2, 3}, 1121 expected: true, 1122 }, 1123 { 1124 s: newStore(1, locUS), 1125 sl: sl3by3, 1126 existingNodeIDs: []roachpb.NodeID{1, 4, 7}, 1127 expected: false, 1128 }, 1129 { 1130 s: newStore(1, locUS), 1131 sl: sl3by3, 1132 existingNodeIDs: []roachpb.NodeID{1, 4, 5}, 1133 expected: false, 1134 }, 1135 { 1136 s: newStore(4, locEU), 1137 sl: sl3by3, 1138 existingNodeIDs: []roachpb.NodeID{1, 4, 5}, 1139 expected: true, 1140 }, 1141 { 1142 s: newStore(1, locUS), 1143 sl: sl4by3, 1144 existingNodeIDs: []roachpb.NodeID{1, 2, 3}, 1145 expected: true, 1146 }, 1147 { 1148 s: newStore(1, locUS), 1149 sl: sl4by3, 1150 existingNodeIDs: []roachpb.NodeID{1, 4, 7}, 1151 expected: false, 1152 }, 1153 { 1154 s: newStore(1, locUS), 1155 sl: sl4by3, 1156 existingNodeIDs: []roachpb.NodeID{1, 4, 7, 10}, 1157 expected: false, 1158 }, 1159 { 1160 s: newStore(1, locUS), 1161 sl: sl4by3, 1162 existingNodeIDs: []roachpb.NodeID{1, 2, 4, 7, 10}, 1163 expected: false, 1164 }, 1165 { 1166 s: newStore(1, locUS), 1167 sl: sl4by3, 1168 existingNodeIDs: []roachpb.NodeID{1, 4, 5, 7, 10}, 1169 expected: false, 1170 }, 1171 } 1172 for i, tc := range testCases { 1173 removeStore := func(sl StoreList, nodeID roachpb.NodeID) StoreList { 1174 for i, s := range sl.stores { 1175 if s.Node.NodeID == nodeID { 1176 return makeStoreList(append(sl.stores[:i], sl.stores[i+1:]...)) 1177 } 1178 } 1179 return sl 1180 } 1181 filteredSL := tc.sl 1182 filteredSL.stores = append([]roachpb.StoreDescriptor(nil), filteredSL.stores...) 1183 existingNodeLocalities := make(map[roachpb.NodeID]roachpb.Locality) 1184 var replicas []roachpb.ReplicaDescriptor 1185 for _, nodeID := range tc.existingNodeIDs { 1186 replicas = append(replicas, roachpb.ReplicaDescriptor{ 1187 NodeID: nodeID, 1188 StoreID: roachpb.StoreID(nodeID), 1189 }) 1190 existingNodeLocalities[nodeID] = localityForNodeID(tc.sl, nodeID) 1191 // For the sake of testing, remove all other existing stores from the 1192 // store list to only test whether we want to remove the replica on tc.s. 1193 if nodeID != tc.s.Node.NodeID { 1194 filteredSL = removeStore(filteredSL, nodeID) 1195 } 1196 } 1197 1198 targets := rebalanceCandidates( 1199 context.Background(), 1200 filteredSL, 1201 constraint.AnalyzedConstraints{}, 1202 replicas, 1203 existingNodeLocalities, 1204 func(nodeID roachpb.NodeID) string { 1205 locality := localityForNodeID(tc.sl, nodeID) 1206 return locality.String() 1207 }, 1208 options) 1209 actual := len(targets) > 0 1210 if actual != tc.expected { 1211 t.Errorf("%d: shouldRebalance on s%d with replicas on %v got %t, expected %t", 1212 i, tc.s.StoreID, tc.existingNodeIDs, actual, tc.expected) 1213 } 1214 } 1215 } 1216 1217 func TestAllocateDiversityScore(t *testing.T) { 1218 defer leaktest.AfterTest(t)() 1219 1220 // Given a range that's located on stores, rank order which of the testStores 1221 // are the best fit for allocating a new replica on. 1222 testCases := []struct { 1223 name string 1224 stores []roachpb.StoreID 1225 expected []roachpb.StoreID 1226 }{ 1227 { 1228 name: "no existing replicas", 1229 expected: []roachpb.StoreID{testStoreUSa15, testStoreUSa15Dupe, testStoreUSa1, testStoreUSb, testStoreEurope}, 1230 }, 1231 { 1232 name: "one existing replicas", 1233 stores: []roachpb.StoreID{testStoreUSa15}, 1234 expected: []roachpb.StoreID{testStoreEurope, testStoreUSb, testStoreUSa1, testStoreUSa15Dupe}, 1235 }, 1236 { 1237 name: "two existing replicas", 1238 stores: []roachpb.StoreID{testStoreUSa15, testStoreEurope}, 1239 expected: []roachpb.StoreID{testStoreUSb, testStoreUSa1, testStoreUSa15Dupe}, 1240 }, 1241 } 1242 1243 for _, tc := range testCases { 1244 t.Run(tc.name, func(t *testing.T) { 1245 existingNodeLocalities := make(map[roachpb.NodeID]roachpb.Locality) 1246 for _, s := range tc.stores { 1247 existingNodeLocalities[testStores[s].Node.NodeID] = testStores[s].Node.Locality 1248 } 1249 var scores storeScores 1250 for _, s := range testStores { 1251 if _, ok := existingNodeLocalities[s.Node.NodeID]; ok { 1252 continue 1253 } 1254 var score storeScore 1255 actualScore := diversityAllocateScore(s, existingNodeLocalities) 1256 score.storeID = s.StoreID 1257 score.score = actualScore 1258 scores = append(scores, score) 1259 } 1260 sort.Sort(sort.Reverse(scores)) 1261 for i := 0; i < len(scores); { 1262 if scores[i].storeID != tc.expected[i] { 1263 t.Fatalf("expected the result store order to be %v, but got %v", tc.expected, scores) 1264 } 1265 i++ 1266 } 1267 }) 1268 } 1269 } 1270 1271 func TestRebalanceToDiversityScore(t *testing.T) { 1272 defer leaktest.AfterTest(t)() 1273 1274 // Given a range that's located on stores, rank order which of the testStores 1275 // are the best fit for rebalancing to. 1276 testCases := []struct { 1277 name string 1278 stores []roachpb.StoreID 1279 expected []roachpb.StoreID 1280 }{ 1281 { 1282 name: "no existing replicas", 1283 expected: []roachpb.StoreID{testStoreUSa15, testStoreUSa15Dupe, testStoreUSa1, testStoreUSb, testStoreEurope}, 1284 }, 1285 { 1286 name: "one existing replica", 1287 stores: []roachpb.StoreID{testStoreUSa15}, 1288 expected: []roachpb.StoreID{testStoreUSa15Dupe, testStoreUSa1, testStoreUSb, testStoreEurope}, 1289 }, 1290 { 1291 name: "two existing replicas", 1292 stores: []roachpb.StoreID{testStoreUSa15, testStoreUSa1}, 1293 expected: []roachpb.StoreID{testStoreEurope, testStoreUSb, testStoreUSa15Dupe}, 1294 }, 1295 { 1296 name: "three existing replicas", 1297 stores: []roachpb.StoreID{testStoreUSa15, testStoreUSa1, testStoreEurope}, 1298 expected: []roachpb.StoreID{testStoreUSb, testStoreUSa15Dupe}, 1299 }, 1300 { 1301 name: "three existing replicas with duplicate", 1302 stores: []roachpb.StoreID{testStoreUSa15, testStoreUSa15Dupe, testStoreUSa1}, 1303 expected: []roachpb.StoreID{testStoreEurope, testStoreUSb}, 1304 }, 1305 { 1306 name: "four existing replicas", 1307 stores: []roachpb.StoreID{testStoreUSa15, testStoreUSa1, testStoreUSb, testStoreEurope}, 1308 expected: []roachpb.StoreID{testStoreUSa15Dupe}, 1309 }, 1310 { 1311 name: "four existing replicas with duplicate", 1312 stores: []roachpb.StoreID{testStoreUSa15, testStoreUSa15Dupe, testStoreUSa1, testStoreUSb}, 1313 expected: []roachpb.StoreID{testStoreEurope}, 1314 }, 1315 } 1316 1317 for _, tc := range testCases { 1318 t.Run(tc.name, func(t *testing.T) { 1319 existingNodeLocalities := make(map[roachpb.NodeID]roachpb.Locality) 1320 for _, s := range tc.stores { 1321 existingNodeLocalities[testStores[s].Node.NodeID] = testStores[s].Node.Locality 1322 } 1323 var scores storeScores 1324 for _, s := range testStores { 1325 if _, ok := existingNodeLocalities[s.Node.NodeID]; ok { 1326 continue 1327 } 1328 var score storeScore 1329 actualScore := diversityRebalanceScore(s, existingNodeLocalities) 1330 score.storeID = s.StoreID 1331 score.score = actualScore 1332 scores = append(scores, score) 1333 } 1334 sort.Sort(sort.Reverse(scores)) 1335 for i := 0; i < len(scores); { 1336 if scores[i].storeID != tc.expected[i] { 1337 t.Fatalf("expected the result store order to be %v, but got %v", tc.expected, scores) 1338 } 1339 i++ 1340 } 1341 }) 1342 } 1343 } 1344 1345 func TestRemovalDiversityScore(t *testing.T) { 1346 defer leaktest.AfterTest(t)() 1347 1348 // Given a range that's located on stores, rank order which of the replicas 1349 // should be removed. 1350 testCases := []struct { 1351 name string 1352 stores []roachpb.StoreID 1353 expected []roachpb.StoreID 1354 }{ 1355 { 1356 name: "four existing replicas", 1357 stores: []roachpb.StoreID{testStoreUSa15, testStoreUSa1, testStoreUSb, testStoreEurope}, 1358 expected: []roachpb.StoreID{testStoreEurope, testStoreUSb, testStoreUSa15, testStoreUSa1}, 1359 }, 1360 { 1361 name: "four existing replicas with duplicate", 1362 stores: []roachpb.StoreID{testStoreUSa15, testStoreUSa15Dupe, testStoreUSb, testStoreEurope}, 1363 expected: []roachpb.StoreID{testStoreEurope, testStoreUSb, testStoreUSa15, testStoreUSa15Dupe}, 1364 }, 1365 { 1366 name: "three existing replicas - excluding testStoreUSa15", 1367 stores: []roachpb.StoreID{testStoreUSa1, testStoreUSb, testStoreEurope}, 1368 expected: []roachpb.StoreID{testStoreEurope, testStoreUSa1, testStoreUSb}, 1369 }, 1370 { 1371 name: "three existing replicas - excluding testStoreUSa1", 1372 stores: []roachpb.StoreID{testStoreUSa15, testStoreUSb, testStoreEurope}, 1373 expected: []roachpb.StoreID{testStoreEurope, testStoreUSa15, testStoreUSb}, 1374 }, 1375 { 1376 name: "three existing replicas - excluding testStoreUSb", 1377 stores: []roachpb.StoreID{testStoreUSa15, testStoreUSa1, testStoreEurope}, 1378 expected: []roachpb.StoreID{testStoreEurope, testStoreUSa15, testStoreUSa1}, 1379 }, 1380 { 1381 name: "three existing replicas - excluding testStoreEurope", 1382 stores: []roachpb.StoreID{testStoreUSa15, testStoreUSa1, testStoreUSb}, 1383 expected: []roachpb.StoreID{testStoreUSb, testStoreUSa15, testStoreUSa1}, 1384 }, 1385 } 1386 1387 for _, tc := range testCases { 1388 t.Run(tc.name, func(t *testing.T) { 1389 existingNodeLocalities := make(map[roachpb.NodeID]roachpb.Locality) 1390 for _, s := range tc.stores { 1391 existingNodeLocalities[testStores[s].Node.NodeID] = testStores[s].Node.Locality 1392 } 1393 var scores storeScores 1394 for _, storeID := range tc.stores { 1395 s := testStores[storeID] 1396 var score storeScore 1397 actualScore := diversityRemovalScore(s.Node.NodeID, existingNodeLocalities) 1398 score.storeID = s.StoreID 1399 score.score = actualScore 1400 scores = append(scores, score) 1401 } 1402 sort.Sort(sort.Reverse(scores)) 1403 for i := 0; i < len(scores); { 1404 if scores[i].storeID != tc.expected[i] { 1405 t.Fatalf("expected the result store order to be %v, but got %v", tc.expected, scores) 1406 } 1407 i++ 1408 } 1409 }) 1410 } 1411 } 1412 1413 func TestDiversityScoreEquivalence(t *testing.T) { 1414 defer leaktest.AfterTest(t)() 1415 1416 testCases := []struct { 1417 stores []roachpb.StoreID 1418 expected float64 1419 }{ 1420 {[]roachpb.StoreID{}, 1.0}, 1421 {[]roachpb.StoreID{testStoreUSa15}, 1.0}, 1422 {[]roachpb.StoreID{testStoreUSa15, testStoreUSa15Dupe}, 0.0}, 1423 {[]roachpb.StoreID{testStoreUSa15, testStoreUSa1}, 0.25}, 1424 {[]roachpb.StoreID{testStoreUSa15, testStoreUSb}, 0.5}, 1425 {[]roachpb.StoreID{testStoreUSa15, testStoreEurope}, 1.0}, 1426 {[]roachpb.StoreID{testStoreUSa15, testStoreUSa15Dupe, testStoreUSa1}, 1.0 / 6.0}, 1427 {[]roachpb.StoreID{testStoreUSa15, testStoreUSa15Dupe, testStoreUSb}, 1.0 / 3.0}, 1428 {[]roachpb.StoreID{testStoreUSa15, testStoreUSa15Dupe, testStoreEurope}, 2.0 / 3.0}, 1429 {[]roachpb.StoreID{testStoreUSa15, testStoreUSa1, testStoreUSb}, 5.0 / 12.0}, 1430 {[]roachpb.StoreID{testStoreUSa15, testStoreUSa1, testStoreEurope}, 3.0 / 4.0}, 1431 {[]roachpb.StoreID{testStoreUSa1, testStoreUSb, testStoreEurope}, 5.0 / 6.0}, 1432 {[]roachpb.StoreID{testStoreUSa15, testStoreUSa15Dupe, testStoreUSa1, testStoreUSb}, 1.0 / 3.0}, 1433 {[]roachpb.StoreID{testStoreUSa15, testStoreUSa15Dupe, testStoreUSa1, testStoreEurope}, 7.0 / 12.0}, 1434 {[]roachpb.StoreID{testStoreUSa15, testStoreUSa15Dupe, testStoreUSb, testStoreEurope}, 2.0 / 3.0}, 1435 {[]roachpb.StoreID{testStoreUSa15, testStoreUSa1, testStoreUSb, testStoreEurope}, 17.0 / 24.0}, 1436 {[]roachpb.StoreID{testStoreUSa15, testStoreUSa15Dupe, testStoreUSa1, testStoreUSb, testStoreEurope}, 3.0 / 5.0}, 1437 } 1438 1439 // Ensure that rangeDiversityScore and diversityRebalanceFromScore return 1440 // the same results for the same configurations, enabling their results 1441 // to be directly compared with each other. The same is not true for 1442 // diversityAllocateScore and diversityRemovalScore as of their initial 1443 // creation or else we would test them here as well. 1444 for _, tc := range testCases { 1445 existingLocalities := make(map[roachpb.NodeID]roachpb.Locality) 1446 for _, storeID := range tc.stores { 1447 s := testStores[storeID] 1448 existingLocalities[s.Node.NodeID] = s.Node.Locality 1449 } 1450 rangeScore := rangeDiversityScore(existingLocalities) 1451 if a, e := rangeScore, tc.expected; a != e { 1452 t.Errorf("rangeDiversityScore(%v) got %f, want %f", existingLocalities, a, e) 1453 } 1454 for _, storeID := range tc.stores { 1455 s := testStores[storeID] 1456 fromNodeID := s.Node.NodeID 1457 s.Node.NodeID = 99 1458 rebalanceScore := diversityRebalanceFromScore(s, fromNodeID, existingLocalities) 1459 if a, e := rebalanceScore, tc.expected; a != e { 1460 t.Errorf("diversityRebalanceFromScore(%v, %d, %v) got %f, want %f", 1461 s, fromNodeID, existingLocalities, a, e) 1462 } 1463 if a, e := rebalanceScore, rangeScore; a != e { 1464 t.Errorf("diversityRebalanceFromScore(%v, %d, %v)=%f not equal to rangeDiversityScore(%v)=%f", 1465 s, fromNodeID, existingLocalities, a, existingLocalities, e) 1466 } 1467 } 1468 } 1469 } 1470 1471 func TestBalanceScore(t *testing.T) { 1472 defer leaktest.AfterTest(t)() 1473 1474 options := scorerOptions{} 1475 storeList := StoreList{ 1476 candidateRanges: stat{mean: 1000}, 1477 } 1478 1479 sEmpty := roachpb.StoreCapacity{ 1480 Capacity: 1024 * 1024 * 1024, 1481 Available: 1024 * 1024 * 1024, 1482 LogicalBytes: 0, 1483 } 1484 sMean := roachpb.StoreCapacity{ 1485 Capacity: 1024 * 1024 * 1024, 1486 Available: 512 * 1024 * 1024, 1487 LogicalBytes: 512 * 1024 * 1024, 1488 RangeCount: 1000, 1489 } 1490 sRangesOverfull := sMean 1491 sRangesOverfull.RangeCount = 1500 1492 sRangesUnderfull := sMean 1493 sRangesUnderfull.RangeCount = 500 1494 1495 testCases := []struct { 1496 sc roachpb.StoreCapacity 1497 expected float64 1498 }{ 1499 {sEmpty, 1}, 1500 {sMean, 0}, 1501 {sRangesOverfull, -1}, 1502 {sRangesUnderfull, 1}, 1503 } 1504 for i, tc := range testCases { 1505 if a, e := balanceScore(storeList, tc.sc, options), tc.expected; a.totalScore() != e { 1506 t.Errorf("%d: balanceScore(storeList, %+v) got %s; want %.2f", i, tc.sc, a, e) 1507 } 1508 } 1509 } 1510 1511 func TestRebalanceConvergesOnMean(t *testing.T) { 1512 defer leaktest.AfterTest(t)() 1513 1514 storeList := StoreList{ 1515 candidateRanges: stat{mean: 1000}, 1516 } 1517 1518 testCases := []struct { 1519 rangeCount int32 1520 toConverges bool 1521 fromConverges bool 1522 }{ 1523 {0, true, false}, 1524 {900, true, false}, 1525 {900, true, false}, 1526 {999, true, false}, 1527 {1000, false, false}, 1528 {1001, false, true}, 1529 {2000, false, true}, 1530 {900, true, false}, 1531 } 1532 1533 for i, tc := range testCases { 1534 sc := roachpb.StoreCapacity{ 1535 RangeCount: tc.rangeCount, 1536 } 1537 if a, e := rebalanceToConvergesOnMean(storeList, sc), tc.toConverges; a != e { 1538 t.Errorf("%d: rebalanceToConvergesOnMean(storeList, %+v) got %t; want %t", i, sc, a, e) 1539 } 1540 if a, e := rebalanceFromConvergesOnMean(storeList, sc), tc.fromConverges; a != e { 1541 t.Errorf("%d: rebalanceFromConvergesOnMean(storeList, %+v) got %t; want %t", i, sc, a, e) 1542 } 1543 } 1544 } 1545 1546 func TestMaxCapacity(t *testing.T) { 1547 defer leaktest.AfterTest(t)() 1548 1549 expectedCheck := map[roachpb.StoreID]bool{ 1550 testStoreUSa15: false, 1551 testStoreUSa1: true, 1552 testStoreUSb: true, 1553 testStoreEurope: true, 1554 } 1555 1556 for _, s := range testStores { 1557 if e, a := expectedCheck[s.StoreID], maxCapacityCheck(s); e != a { 1558 t.Errorf("store %d expected max capacity check: %t, actual %t", s.StoreID, e, a) 1559 } 1560 } 1561 }