github.com/m3db/m3@v1.5.0/src/cluster/placement/selector/non_mirrored_test.go (about)

     1  // Copyright (c) 2017 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package selector
    22  
    23  import (
    24  	"math/rand"
    25  	"sort"
    26  	"testing"
    27  
    28  	"github.com/m3db/m3/src/cluster/placement"
    29  
    30  	"github.com/stretchr/testify/assert"
    31  	"github.com/stretchr/testify/require"
    32  )
    33  
    34  func TestGroupInstancesByConflict(t *testing.T) {
    35  	i1 := placement.NewEmptyInstance("i1", "", "", "endpoint", 1)
    36  	i2 := placement.NewEmptyInstance("i2", "", "", "endpoint", 1)
    37  	i3 := placement.NewEmptyInstance("i3", "", "", "endpoint", 1)
    38  	i4 := placement.NewEmptyInstance("i4", "", "", "endpoint", 2)
    39  	instanceConflicts := []sortableValue{
    40  		sortableValue{value: i1, weight: 1},
    41  		sortableValue{value: i2, weight: 0},
    42  		sortableValue{value: i3, weight: 3},
    43  		sortableValue{value: i4, weight: 2},
    44  	}
    45  
    46  	testCases := []struct {
    47  		opts     placement.Options
    48  		expected [][]placement.Instance
    49  	}{
    50  		{
    51  			opts: placement.NewOptions().SetAllowPartialReplace(true),
    52  			expected: [][]placement.Instance{
    53  				[]placement.Instance{i2},
    54  				[]placement.Instance{i1},
    55  				[]placement.Instance{i4},
    56  				[]placement.Instance{i3},
    57  			},
    58  		},
    59  		{
    60  			opts: placement.NewOptions().SetAllowPartialReplace(false),
    61  			expected: [][]placement.Instance{
    62  				[]placement.Instance{i2},
    63  			},
    64  		},
    65  	}
    66  	for _, test := range testCases {
    67  		assert.Equal(t, test.expected, groupInstancesByConflict(instanceConflicts, test.opts))
    68  	}
    69  }
    70  
    71  func TestKnapSack(t *testing.T) {
    72  	i1 := placement.NewEmptyInstance("i1", "", "", "endpoint", 40000)
    73  	i2 := placement.NewEmptyInstance("i2", "", "", "endpoint", 20000)
    74  	i3 := placement.NewEmptyInstance("i3", "", "", "endpoint", 80000)
    75  	i4 := placement.NewEmptyInstance("i4", "", "", "endpoint", 50000)
    76  	i5 := placement.NewEmptyInstance("i5", "", "", "endpoint", 190000)
    77  	instances := []placement.Instance{i1, i2, i3, i4, i5}
    78  
    79  	res, leftWeight := knapsack(instances, 10000)
    80  	assert.Equal(t, -10000, leftWeight)
    81  	assert.Equal(t, []placement.Instance{i2}, res)
    82  
    83  	res, leftWeight = knapsack(instances, 20000)
    84  	assert.Equal(t, 0, leftWeight)
    85  	assert.Equal(t, []placement.Instance{i2}, res)
    86  
    87  	res, leftWeight = knapsack(instances, 30000)
    88  	assert.Equal(t, -10000, leftWeight)
    89  	assert.Equal(t, []placement.Instance{i1}, res)
    90  
    91  	res, leftWeight = knapsack(instances, 60000)
    92  	assert.Equal(t, 0, leftWeight)
    93  	assert.Equal(t, []placement.Instance{i1, i2}, res)
    94  
    95  	res, leftWeight = knapsack(instances, 120000)
    96  	assert.Equal(t, 0, leftWeight)
    97  	assert.Equal(t, []placement.Instance{i1, i3}, res)
    98  
    99  	res, leftWeight = knapsack(instances, 170000)
   100  	assert.Equal(t, 0, leftWeight)
   101  	assert.Equal(t, []placement.Instance{i1, i3, i4}, res)
   102  
   103  	res, leftWeight = knapsack(instances, 190000)
   104  	assert.Equal(t, 0, leftWeight)
   105  	// will prefer i5 than i1+i2+i3+i4
   106  	assert.Equal(t, []placement.Instance{i5}, res)
   107  
   108  	res, leftWeight = knapsack(instances, 200000)
   109  	assert.Equal(t, -10000, leftWeight)
   110  	assert.Equal(t, []placement.Instance{i2, i5}, res)
   111  
   112  	res, leftWeight = knapsack(instances, 210000)
   113  	assert.Equal(t, 0, leftWeight)
   114  	assert.Equal(t, []placement.Instance{i2, i5}, res)
   115  
   116  	res, leftWeight = knapsack(instances, 400000)
   117  	assert.Equal(t, 20000, leftWeight)
   118  	assert.Equal(t, []placement.Instance{i1, i2, i3, i4, i5}, res)
   119  }
   120  
   121  func TestFillWeight(t *testing.T) {
   122  	i1 := placement.NewEmptyInstance("i1", "", "", "endpoint", 4)
   123  	i2 := placement.NewEmptyInstance("i2", "", "", "endpoint", 2)
   124  	i3 := placement.NewEmptyInstance("i3", "", "", "endpoint", 8)
   125  	i4 := placement.NewEmptyInstance("i4", "", "", "endpoint", 5)
   126  	i5 := placement.NewEmptyInstance("i5", "", "", "endpoint", 19)
   127  
   128  	i6 := placement.NewEmptyInstance("i6", "", "", "endpoint", 3)
   129  	i7 := placement.NewEmptyInstance("i7", "", "", "endpoint", 7)
   130  	groups := [][]placement.Instance{
   131  		[]placement.Instance{i1, i2, i3, i4, i5},
   132  		[]placement.Instance{i6, i7},
   133  	}
   134  
   135  	// When targetWeight is smaller than 38, the first group will satisfy
   136  	res, leftWeight := fillWeight(groups, 1)
   137  	assert.Equal(t, -1, leftWeight)
   138  	assert.Equal(t, []placement.Instance{i2}, res)
   139  
   140  	res, leftWeight = fillWeight(groups, 2)
   141  	assert.Equal(t, 0, leftWeight)
   142  	assert.Equal(t, []placement.Instance{i2}, res)
   143  
   144  	res, leftWeight = fillWeight(groups, 17)
   145  	assert.Equal(t, 0, leftWeight)
   146  	assert.Equal(t, []placement.Instance{i1, i3, i4}, res)
   147  
   148  	res, leftWeight = fillWeight(groups, 20)
   149  	assert.Equal(t, -1, leftWeight)
   150  	assert.Equal(t, []placement.Instance{i2, i5}, res)
   151  
   152  	// When targetWeight is bigger than 38, need to get instance from group 2
   153  	res, leftWeight = fillWeight(groups, 40)
   154  	assert.Equal(t, -1, leftWeight)
   155  	assert.Equal(t, []placement.Instance{i1, i2, i3, i4, i5, i6}, res)
   156  
   157  	res, leftWeight = fillWeight(groups, 41)
   158  	assert.Equal(t, 0, leftWeight)
   159  	assert.Equal(t, []placement.Instance{i1, i2, i3, i4, i5, i6}, res)
   160  
   161  	res, leftWeight = fillWeight(groups, 47)
   162  	assert.Equal(t, -1, leftWeight)
   163  	assert.Equal(t, []placement.Instance{i1, i2, i3, i4, i5, i6, i7}, res)
   164  
   165  	res, leftWeight = fillWeight(groups, 48)
   166  	assert.Equal(t, 0, leftWeight)
   167  	assert.Equal(t, []placement.Instance{i1, i2, i3, i4, i5, i6, i7}, res)
   168  
   169  	res, leftWeight = fillWeight(groups, 50)
   170  	assert.Equal(t, 2, leftWeight)
   171  	assert.Equal(t, []placement.Instance{i1, i2, i3, i4, i5, i6, i7}, res)
   172  }
   173  
   174  func TestFillWeightDeterministic(t *testing.T) {
   175  	i1 := placement.NewEmptyInstance("i1", "", "", "endpoint", 1)
   176  	i2 := placement.NewEmptyInstance("i2", "", "", "endpoint", 1)
   177  	i3 := placement.NewEmptyInstance("i3", "", "", "endpoint", 1)
   178  	i4 := placement.NewEmptyInstance("i4", "", "", "endpoint", 3)
   179  	i5 := placement.NewEmptyInstance("i5", "", "", "endpoint", 4)
   180  
   181  	i6 := placement.NewEmptyInstance("i6", "", "", "endpoint", 1)
   182  	i7 := placement.NewEmptyInstance("i7", "", "", "endpoint", 1)
   183  	i8 := placement.NewEmptyInstance("i8", "", "", "endpoint", 1)
   184  	i9 := placement.NewEmptyInstance("i9", "", "", "endpoint", 2)
   185  	groups := [][]placement.Instance{
   186  		[]placement.Instance{i1, i2, i3, i4, i5},
   187  		[]placement.Instance{i6, i7, i8, i9},
   188  	}
   189  
   190  	for i := 1; i < 17; i++ {
   191  		testResultDeterministic(t, groups, i)
   192  	}
   193  }
   194  
   195  func testResultDeterministic(t *testing.T, groups [][]placement.Instance, targetWeight int) {
   196  	res, _ := fillWeight(groups, targetWeight)
   197  
   198  	// shuffle the order of of each group of instances
   199  	for _, group := range groups {
   200  		for i := range group {
   201  			j := rand.Intn(i + 1)
   202  			group[i], group[j] = group[j], group[i]
   203  		}
   204  	}
   205  	res1, _ := fillWeight(groups, targetWeight)
   206  	assert.Equal(t, res, res1)
   207  }
   208  
   209  func TestIsolationGroupLenSort(t *testing.T) {
   210  	r1 := sortableValue{value: "r1", weight: 1}
   211  	r2 := sortableValue{value: "r2", weight: 2}
   212  	r3 := sortableValue{value: "r3", weight: 3}
   213  	r4 := sortableValue{value: "r4", weight: 2}
   214  	r5 := sortableValue{value: "r5", weight: 1}
   215  	r6 := sortableValue{value: "r6", weight: 2}
   216  	r7 := sortableValue{value: "r7", weight: 3}
   217  	rs := sortableValues{r1, r2, r3, r4, r5, r6, r7}
   218  	sort.Sort(rs)
   219  
   220  	seen := 0
   221  	for _, rl := range rs {
   222  		assert.True(t, seen <= rl.weight)
   223  		seen = rl.weight
   224  	}
   225  }
   226  
   227  func TestFilterZones(t *testing.T) {
   228  	i1 := placement.NewInstance().SetID("i1").SetZone("z1")
   229  	i2 := placement.NewInstance().SetID("i2").SetZone("z1")
   230  	i3 := placement.NewInstance().SetID("i2").SetZone("z1")
   231  	i4 := placement.NewInstance().SetID("i3").SetZone("z2")
   232  
   233  	_, _ = i2, i3
   234  
   235  	tests := map[*struct {
   236  		p          placement.Placement
   237  		candidates []placement.Instance
   238  		opts       placement.Options
   239  	}][]placement.Instance{
   240  		{
   241  			p:          placement.NewPlacement().SetInstances([]placement.Instance{i1}),
   242  			candidates: []placement.Instance{},
   243  			opts:       nil,
   244  		}: []placement.Instance{},
   245  		{
   246  			p:          placement.NewPlacement().SetInstances([]placement.Instance{i1}),
   247  			candidates: []placement.Instance{i2},
   248  			opts:       nil,
   249  		}: []placement.Instance{i2},
   250  		{
   251  			p:          placement.NewPlacement().SetInstances([]placement.Instance{i1}),
   252  			candidates: []placement.Instance{i2, i4},
   253  			opts:       nil,
   254  		}: []placement.Instance{i2},
   255  		{
   256  			p:          placement.NewPlacement().SetInstances([]placement.Instance{i1}),
   257  			candidates: []placement.Instance{i2, i3},
   258  			opts:       nil,
   259  		}: []placement.Instance{i2, i3},
   260  		{
   261  			p:          placement.NewPlacement(),
   262  			candidates: []placement.Instance{i2},
   263  			opts:       nil,
   264  		}: []placement.Instance{},
   265  		{
   266  			p:          placement.NewPlacement(),
   267  			candidates: []placement.Instance{i2},
   268  			opts:       placement.NewOptions().SetValidZone("z1"),
   269  		}: []placement.Instance{i2},
   270  	}
   271  
   272  	for args, exp := range tests {
   273  		res := filterZones(args.p, args.candidates, args.opts)
   274  		assert.Equal(t, exp, res)
   275  	}
   276  }
   277  
   278  func TestSelectAddingInstanceForNonMirrored(t *testing.T) {
   279  	i1 := placement.NewInstance().
   280  		SetID("i1").
   281  		SetIsolationGroup("r1").
   282  		SetWeight(3)
   283  	i2 := placement.NewInstance().
   284  		SetID("i2").
   285  		SetIsolationGroup("r2").
   286  		SetWeight(1)
   287  	i3 := placement.NewInstance().
   288  		SetID("i3").
   289  		SetIsolationGroup("r1").
   290  		SetWeight(1)
   291  	i4 := placement.NewInstance().
   292  		SetID("i4").
   293  		SetIsolationGroup("r2").
   294  		SetWeight(2)
   295  
   296  	tests := []struct {
   297  		name        string
   298  		placement   placement.Placement
   299  		opts        placement.Options
   300  		candidates  []placement.Instance
   301  		expectAdded []placement.Instance
   302  	}{
   303  		{
   304  			name:        "New Isolation Group",
   305  			placement:   placement.NewPlacement().SetInstances([]placement.Instance{i1}),
   306  			opts:        placement.NewOptions().SetAddAllCandidates(false),
   307  			candidates:  []placement.Instance{i2, i3},
   308  			expectAdded: []placement.Instance{i2},
   309  		},
   310  		{
   311  			name:        "Least Weighted Isolation Group",
   312  			placement:   placement.NewPlacement().SetInstances([]placement.Instance{i1, i4}),
   313  			opts:        placement.NewOptions().SetAddAllCandidates(false),
   314  			candidates:  []placement.Instance{i2, i3},
   315  			expectAdded: []placement.Instance{i2},
   316  		},
   317  		{
   318  			name:        "Add All Candidates",
   319  			placement:   placement.NewPlacement().SetInstances([]placement.Instance{i1}),
   320  			opts:        placement.NewOptions().SetAddAllCandidates(true),
   321  			candidates:  []placement.Instance{i2, i3, i4},
   322  			expectAdded: []placement.Instance{i2, i3, i4},
   323  		},
   324  	}
   325  
   326  	for _, test := range tests {
   327  		t.Run(test.name, func(t *testing.T) {
   328  			selector := NewNonMirroredSelector(test.opts)
   329  			added, err := selector.SelectAddingInstances(test.candidates, test.placement)
   330  			require.NoError(t, err)
   331  			require.Equal(t, test.expectAdded, added)
   332  		})
   333  	}
   334  }
   335  
   336  func TestSelectReplaceInstanceForNonMirrored(t *testing.T) {
   337  	i1 := placement.NewInstance().
   338  		SetID("i1").
   339  		SetIsolationGroup("r1").
   340  		SetWeight(4)
   341  	i2 := placement.NewInstance().
   342  		SetID("i2").
   343  		SetIsolationGroup("r2").
   344  		SetWeight(1)
   345  	i3 := placement.NewInstance().
   346  		SetID("i3").
   347  		SetIsolationGroup("r1").
   348  		SetWeight(1)
   349  	i4 := placement.NewInstance().
   350  		SetID("i4").
   351  		SetIsolationGroup("r2").
   352  		SetWeight(2)
   353  
   354  	tests := []struct {
   355  		name        string
   356  		placement   placement.Placement
   357  		opts        placement.Options
   358  		candidates  []placement.Instance
   359  		leavingIDs  []string
   360  		expectErr   bool
   361  		expectAdded []placement.Instance
   362  	}{
   363  		{
   364  			name:        "Replace With Instance of Same Weight",
   365  			placement:   placement.NewPlacement().SetInstances([]placement.Instance{i1, i2}),
   366  			opts:        placement.NewOptions().SetAddAllCandidates(false).SetAllowPartialReplace(false),
   367  			candidates:  []placement.Instance{i3, i4},
   368  			leavingIDs:  []string{"i2"},
   369  			expectErr:   false,
   370  			expectAdded: []placement.Instance{i3},
   371  		},
   372  		{
   373  			name:        "Add All Candidates",
   374  			placement:   placement.NewPlacement().SetInstances([]placement.Instance{i1, i2}),
   375  			opts:        placement.NewOptions().SetAddAllCandidates(true).SetAllowPartialReplace(false),
   376  			candidates:  []placement.Instance{i3, i4},
   377  			leavingIDs:  []string{"i2"},
   378  			expectErr:   false,
   379  			expectAdded: []placement.Instance{i3, i4},
   380  		},
   381  		{
   382  			name:        "Not Enough Weight With Partial Replace",
   383  			placement:   placement.NewPlacement().SetInstances([]placement.Instance{i1, i2}),
   384  			opts:        placement.NewOptions().SetAddAllCandidates(false).SetAllowPartialReplace(true),
   385  			candidates:  []placement.Instance{i3, i4},
   386  			leavingIDs:  []string{"i1"},
   387  			expectErr:   false,
   388  			expectAdded: []placement.Instance{i3, i4},
   389  		},
   390  		{
   391  			name:       "Not Enough Weight Without Partial Replace",
   392  			placement:  placement.NewPlacement().SetInstances([]placement.Instance{i1, i2}),
   393  			opts:       placement.NewOptions().SetAddAllCandidates(false).SetAllowPartialReplace(false),
   394  			candidates: []placement.Instance{i3, i4},
   395  			leavingIDs: []string{"i1"},
   396  			expectErr:  true,
   397  		},
   398  	}
   399  
   400  	for _, test := range tests {
   401  		t.Run(test.name, func(t *testing.T) {
   402  			selector := NewNonMirroredSelector(test.opts)
   403  			added, err := selector.SelectReplaceInstances(test.candidates, test.leavingIDs, test.placement)
   404  			if test.expectErr {
   405  				require.Error(t, err)
   406  				return
   407  			}
   408  			require.NoError(t, err)
   409  			require.Equal(t, test.expectAdded, added)
   410  		})
   411  	}
   412  }