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 }