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  }