github.com/m3db/m3@v1.5.0/src/cluster/placement/algo/sharded_test.go (about) 1 // Copyright (c) 2016 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 algo 22 23 import ( 24 "fmt" 25 "math" 26 "testing" 27 "time" 28 29 "github.com/m3db/m3/src/cluster/placement" 30 "github.com/m3db/m3/src/cluster/shard" 31 32 "github.com/stretchr/testify/assert" 33 "github.com/stretchr/testify/require" 34 ) 35 36 func TestMinorWeightDifference(t *testing.T) { 37 i1 := placement.NewEmptyInstance("i1", "r1", "z1", "endpoint1", 262136) 38 i2 := placement.NewEmptyInstance("i2", "r2", "z1", "endpoint2", 262144) 39 i3 := placement.NewEmptyInstance("i3", "r3", "z1", "endpoint3", 262144) 40 i4 := placement.NewEmptyInstance("i4", "r4", "z1", "endpoint4", 262144) 41 i5 := placement.NewEmptyInstance("i5", "r5", "z1", "endpoint5", 262144) 42 i6 := placement.NewEmptyInstance("i6", "r4", "z1", "endpoint6", 262144) 43 44 numShards := 1024 45 ids := make([]uint32, numShards) 46 for i := 0; i < len(ids); i++ { 47 ids[i] = uint32(i) 48 } 49 50 a := newShardedAlgorithm(placement.NewOptions()) 51 p, err := a.InitialPlacement([]placement.Instance{i1, i2, i3, i4, i5, i6}, ids, 1) 52 require.NoError(t, err) 53 validateDistribution(t, p, 1.01) 54 55 p, err = a.AddReplica(p) 56 require.NoError(t, err) 57 validateDistribution(t, p, 1.01) 58 59 p, err = a.AddReplica(p) 60 require.NoError(t, err) 61 validateDistribution(t, p, 1.01) 62 } 63 64 func TestGoodCase(t *testing.T) { 65 i1 := placement.NewEmptyInstance("i1", "r1", "z1", "endpoint", 1) 66 i2 := placement.NewEmptyInstance("i2", "r1", "z1", "endpoint", 1) 67 i3 := placement.NewEmptyInstance("i3", "r2", "z1", "endpoint", 1) 68 i4 := placement.NewEmptyInstance("i4", "r2", "z1", "endpoint", 1) 69 i5 := placement.NewEmptyInstance("i5", "r3", "z1", "endpoint", 1) 70 i6 := placement.NewEmptyInstance("i6", "r4", "z1", "endpoint", 1) 71 i7 := placement.NewEmptyInstance("i7", "r5", "z1", "endpoint", 1) 72 i8 := placement.NewEmptyInstance("i8", "r6", "z1", "endpoint", 1) 73 i9 := placement.NewEmptyInstance("i9", "r7", "z1", "endpoint", 1) 74 75 instances := []placement.Instance{i1, i2, i3, i4, i5, i6, i7, i8, i9} 76 77 numShards := 1024 78 ids := make([]uint32, numShards) 79 for i := 0; i < len(ids); i++ { 80 ids[i] = uint32(i) 81 } 82 83 opts := placement.NewOptions(). 84 SetPlacementCutoverNanosFn(timeNanosGen(1)). 85 SetShardCutoverNanosFn(timeNanosGen(2)). 86 SetShardCutoffNanosFn(timeNanosGen(3)) 87 a := newShardedAlgorithm(opts) 88 p, err := a.InitialPlacement(instances, ids, 1) 89 require.NoError(t, err) 90 validateCutoverCutoffNanos(t, p, opts) 91 validateDistribution(t, p, 1.01) 92 p, updated := mustMarkAllShardsAsAvailable(t, p, opts) 93 require.True(t, updated) 94 95 opts = placement.NewOptions(). 96 SetPlacementCutoverNanosFn(timeNanosGen(11)). 97 SetShardCutoverNanosFn(timeNanosGen(12)). 98 SetShardCutoffNanosFn(timeNanosGen(13)) 99 a = newShardedAlgorithm(opts) 100 p, err = a.AddInstances(p, []placement.Instance{placement.NewEmptyInstance("i21", "r6", "z1", "endpoint", 1)}) 101 require.NoError(t, err) 102 validateCutoverCutoffNanos(t, p, opts) 103 p, updated = mustMarkAllShardsAsAvailable(t, p, opts) 104 require.True(t, updated) 105 validateCutoverCutoffNanos(t, p, opts) 106 validateDistribution(t, p, 1.01) 107 108 opts = placement.NewOptions(). 109 SetPlacementCutoverNanosFn(timeNanosGen(21)). 110 SetShardCutoverNanosFn(timeNanosGen(22)). 111 SetShardCutoffNanosFn(timeNanosGen(23)) 112 a = newShardedAlgorithm(opts) 113 p, err = a.RemoveInstances(p, []string{i1.ID()}) 114 require.NoError(t, err) 115 validateCutoverCutoffNanos(t, p, opts) 116 p, updated = mustMarkAllShardsAsAvailable(t, p, opts) 117 require.True(t, updated) 118 _, exist := p.Instance(i1.ID()) 119 assert.False(t, exist) 120 validateCutoverCutoffNanos(t, p, opts) 121 validateDistribution(t, p, 1.01) 122 123 opts = placement.NewOptions(). 124 SetPlacementCutoverNanosFn(timeNanosGen(31)). 125 SetShardCutoverNanosFn(timeNanosGen(32)). 126 SetShardCutoffNanosFn(timeNanosGen(33)) 127 a = newShardedAlgorithm(opts) 128 i12 := placement.NewEmptyInstance("i12", "r3", "z1", "endpoint", 1) 129 p, err = a.ReplaceInstances(p, []string{i5.ID()}, []placement.Instance{i12}) 130 require.NoError(t, err) 131 validateCutoverCutoffNanos(t, p, opts) 132 _, exist = p.Instance(i5.ID()) 133 assert.True(t, exist) 134 p, updated = mustMarkAllShardsAsAvailable(t, p, opts) 135 require.True(t, updated) 136 _, exist = p.Instance(i5.ID()) 137 assert.False(t, exist) 138 validateCutoverCutoffNanos(t, p, opts) 139 validateDistribution(t, p, 1.01) 140 141 opts = placement.NewOptions(). 142 SetPlacementCutoverNanosFn(timeNanosGen(41)). 143 SetShardCutoverNanosFn(timeNanosGen(42)). 144 SetShardCutoffNanosFn(timeNanosGen(43)) 145 a = newShardedAlgorithm(opts) 146 p, err = a.RemoveInstances(p, []string{i2.ID()}) 147 require.NoError(t, err) 148 validateCutoverCutoffNanos(t, p, opts) 149 p, updated = mustMarkAllShardsAsAvailable(t, p, opts) 150 require.True(t, updated) 151 validateCutoverCutoffNanos(t, p, opts) 152 validateDistribution(t, p, 1.01) 153 154 opts = placement.NewOptions(). 155 SetPlacementCutoverNanosFn(timeNanosGen(51)). 156 SetShardCutoverNanosFn(timeNanosGen(52)). 157 SetShardCutoffNanosFn(timeNanosGen(53)) 158 a = newShardedAlgorithm(opts) 159 p, err = a.AddReplica(p) 160 require.NoError(t, err) 161 validateCutoverCutoffNanos(t, p, opts) 162 p, updated = mustMarkAllShardsAsAvailable(t, p, opts) 163 require.True(t, updated) 164 validateCutoverCutoffNanos(t, p, opts) 165 validateDistribution(t, p, 1.01) 166 167 p, err = a.AddReplica(p) 168 require.NoError(t, err) 169 validateCutoverCutoffNanos(t, p, opts) 170 p, updated = mustMarkAllShardsAsAvailable(t, p, opts) 171 require.True(t, updated) 172 validateCutoverCutoffNanos(t, p, opts) 173 validateDistribution(t, p, 1.01) 174 175 i10 := placement.NewEmptyInstance("i10", "r4", "z1", "endpoint", 1) 176 i11 := placement.NewEmptyInstance("i11", "r7", "z1", "endpoint", 1) 177 p, err = a.AddInstances(p, []placement.Instance{i10, i11}) 178 require.NoError(t, err) 179 validateCutoverCutoffNanos(t, p, opts) 180 p, updated = mustMarkAllShardsAsAvailable(t, p, opts) 181 require.True(t, updated) 182 validateCutoverCutoffNanos(t, p, opts) 183 validateDistribution(t, p, 1.01) 184 185 i13 := placement.NewEmptyInstance("i13", "r5", "z1", "endpoint", 1) 186 p, err = a.ReplaceInstances(p, []string{i3.ID()}, []placement.Instance{i13}) 187 require.NoError(t, err) 188 validateCutoverCutoffNanos(t, p, opts) 189 p, updated = mustMarkAllShardsAsAvailable(t, p, opts) 190 require.True(t, updated) 191 validateCutoverCutoffNanos(t, p, opts) 192 validateDistribution(t, p, 1.01) 193 194 p, err = a.RemoveInstances(p, []string{i4.ID()}) 195 require.NoError(t, err) 196 validateCutoverCutoffNanos(t, p, opts) 197 p, updated = mustMarkAllShardsAsAvailable(t, p, opts) 198 require.True(t, updated) 199 validateCutoverCutoffNanos(t, p, opts) 200 validateDistribution(t, p, 1.02) 201 } 202 203 func TestGoodCaseWithWeight(t *testing.T) { 204 i1 := placement.NewEmptyInstance("i1", "r1", "z1", "endpoint", 10) 205 i2 := placement.NewEmptyInstance("i2", "r1", "z1", "endpoint", 10) 206 i3 := placement.NewEmptyInstance("i3", "r2", "z1", "endpoint", 20) 207 i4 := placement.NewEmptyInstance("i4", "r3", "z1", "endpoint", 10) 208 i5 := placement.NewEmptyInstance("i5", "r4", "z1", "endpoint", 30) 209 i6 := placement.NewEmptyInstance("i6", "r6", "z1", "endpoint", 40) 210 i7 := placement.NewEmptyInstance("i7", "r7", "z1", "endpoint", 10) 211 i8 := placement.NewEmptyInstance("i8", "r8", "z1", "endpoint", 10) 212 i9 := placement.NewEmptyInstance("i9", "r9", "z1", "endpoint", 10) 213 214 instances := []placement.Instance{i1, i2, i3, i4, i5, i6, i7, i8, i9} 215 216 ids := make([]uint32, 1024) 217 for i := 0; i < len(ids); i++ { 218 ids[i] = uint32(i) 219 } 220 221 opts := placement.NewOptions() 222 a := newShardedAlgorithm(opts) 223 p, err := a.InitialPlacement(instances, ids, 1) 224 require.NoError(t, err) 225 validateDistribution(t, p, 1.01) 226 227 p, err = a.AddInstances(p, []placement.Instance{placement.NewEmptyInstance("h21", "r2", "z1", "endpoint", 10)}) 228 require.NoError(t, err) 229 p, _ = mustMarkAllShardsAsAvailable(t, p, opts) 230 validateDistribution(t, p, 1.01) 231 232 p, err = a.RemoveInstances(p, []string{i1.ID()}) 233 require.NoError(t, err) 234 p, _ = mustMarkAllShardsAsAvailable(t, p, opts) 235 validateDistribution(t, p, 1.01) 236 237 p, err = a.ReplaceInstances(p, []string{i3.ID()}, 238 []placement.Instance{ 239 placement.NewEmptyInstance("h31", "r1", "z1", "endpoint", 10), 240 placement.NewEmptyInstance("h32", "r1", "z1", "endpoint", 10), 241 }) 242 require.NoError(t, err) 243 p, _ = mustMarkAllShardsAsAvailable(t, p, opts) 244 validateDistribution(t, p, 1.01) 245 246 p, err = a.AddReplica(p) 247 require.NoError(t, err) 248 p, _ = mustMarkAllShardsAsAvailable(t, p, opts) 249 validateDistribution(t, p, 1.01) 250 251 p, err = a.AddReplica(p) 252 require.NoError(t, err) 253 p, _ = mustMarkAllShardsAsAvailable(t, p, opts) 254 validateDistribution(t, p, 1.01) 255 256 h10 := placement.NewEmptyInstance("h10", "r10", "z1", "endpoint", 10) 257 h11 := placement.NewEmptyInstance("h11", "r7", "z1", "endpoint", 10) 258 p, err = a.AddInstances(p, []placement.Instance{h10, h11}) 259 require.NoError(t, err) 260 p, _ = mustMarkAllShardsAsAvailable(t, p, opts) 261 validateDistribution(t, p, 1.01) 262 263 h13 := placement.NewEmptyInstance("h13", "r5", "z1", "endpoint", 10) 264 p, err = a.ReplaceInstances(p, []string{h11.ID()}, []placement.Instance{h13}) 265 require.NoError(t, err) 266 p, _ = mustMarkAllShardsAsAvailable(t, p, opts) 267 validateDistribution(t, p, 1.01) 268 } 269 270 func TestPlacementChangeWithoutStateUpdate(t *testing.T) { 271 i1 := placement.NewEmptyInstance("i1", "r1", "z1", "endpoint", 1) 272 i2 := placement.NewEmptyInstance("i2", "r1", "z1", "endpoint", 1) 273 i3 := placement.NewEmptyInstance("i3", "r2", "z1", "endpoint", 1) 274 i4 := placement.NewEmptyInstance("i4", "r2", "z1", "endpoint", 1) 275 i5 := placement.NewEmptyInstance("i5", "r3", "z1", "endpoint", 1) 276 i6 := placement.NewEmptyInstance("i6", "r4", "z1", "endpoint", 1) 277 i7 := placement.NewEmptyInstance("i7", "r5", "z1", "endpoint", 1) 278 i8 := placement.NewEmptyInstance("i8", "r6", "z1", "endpoint", 1) 279 i9 := placement.NewEmptyInstance("i9", "r7", "z1", "endpoint", 1) 280 281 instances := []placement.Instance{i1, i2, i3, i4, i5, i6, i7, i8, i9} 282 283 numShards := 100 284 ids := make([]uint32, numShards) 285 for i := 0; i < len(ids); i++ { 286 ids[i] = uint32(i) 287 } 288 289 a := newShardedAlgorithm(placement.NewOptions()) 290 p, err := a.InitialPlacement(instances, ids, 1) 291 require.NoError(t, err) 292 assert.Equal(t, 1, p.ReplicaFactor()) 293 assert.Equal(t, numShards, p.NumShards()) 294 for _, instance := range p.Instances() { 295 assert.Equal(t, instance.Shards().NumShards(), instance.Shards().NumShardsForState(shard.Initializing)) 296 } 297 validateDistribution(t, p, 1.01) 298 299 p, err = a.AddInstances(p, []placement.Instance{placement.NewEmptyInstance("i21", "r6", "z1", "endpoint", 1)}) 300 require.NoError(t, err) 301 validateDistribution(t, p, 1.01) 302 303 p, err = a.RemoveInstances(p, []string{i1.ID()}) 304 require.NoError(t, err) 305 validateDistribution(t, p, 1.01) 306 307 i12 := placement.NewEmptyInstance("i12", "r3", "z1", "endpoint", 1) 308 p, err = a.ReplaceInstances(p, []string{i5.ID()}, []placement.Instance{i12}) 309 require.NoError(t, err) 310 validateDistribution(t, p, 1.01) 311 312 p, err = a.RemoveInstances(p, []string{i2.ID()}) 313 require.NoError(t, err) 314 validateDistribution(t, p, 1.01) 315 316 p, err = a.AddReplica(p) 317 require.NoError(t, err) 318 validateDistribution(t, p, 1.01) 319 320 p, err = a.AddReplica(p) 321 require.NoError(t, err) 322 validateDistribution(t, p, 1.01) 323 324 i10 := placement.NewEmptyInstance("i10", "r4", "z1", "endpoint", 1) 325 i11 := placement.NewEmptyInstance("i11", "r7", "z1", "endpoint", 1) 326 p, err = a.AddInstances(p, []placement.Instance{i10, i11}) 327 require.NoError(t, err) 328 validateDistribution(t, p, 1.01) 329 330 i13 := placement.NewEmptyInstance("i13", "r5", "z1", "endpoint", 1) 331 p, err = a.ReplaceInstances(p, []string{i3.ID()}, []placement.Instance{i13}) 332 require.NoError(t, err) 333 validateDistribution(t, p, 1.01) 334 335 p, err = a.RemoveInstances(p, []string{i4.ID()}) 336 require.NoError(t, err) 337 validateDistribution(t, p, 1.02) 338 } 339 340 func TestOverSizedIsolationGroup(t *testing.T) { 341 i1 := placement.NewEmptyInstance("i1", "r1", "z1", "endpoint", 1) 342 i6 := placement.NewEmptyInstance("i6", "r1", "z1", "endpoint", 1) 343 i7 := placement.NewEmptyInstance("i7", "r1", "z1", "endpoint", 1) 344 345 i2 := placement.NewEmptyInstance("i2", "r2", "z1", "endpoint", 1) 346 i3 := placement.NewEmptyInstance("i3", "r2", "z1", "endpoint", 1) 347 348 i4 := placement.NewEmptyInstance("i4", "r3", "z1", "endpoint", 1) 349 i8 := placement.NewEmptyInstance("i8", "r3", "z1", "endpoint", 1) 350 351 i5 := placement.NewEmptyInstance("i5", "r4", "z1", "endpoint", 1) 352 353 i9 := placement.NewEmptyInstance("i9", "r5", "z1", "endpoint", 1) 354 355 instances := []placement.Instance{i1, i2, i3, i4, i5, i6, i7, i8, i9} 356 357 ids := make([]uint32, 1024) 358 for i := 0; i < len(ids); i++ { 359 ids[i] = uint32(i) 360 } 361 362 opts := placement.NewOptions().SetAllowPartialReplace(false) 363 a := newShardedAlgorithm(opts) 364 p, err := a.InitialPlacement(instances, ids, 1) 365 require.NoError(t, err) 366 validateDistribution(t, p, 1.01) 367 368 p, err = a.AddReplica(p) 369 require.NoError(t, err) 370 validateDistribution(t, p, 1.01) 371 372 p, err = a.AddReplica(p) 373 require.NoError(t, err) 374 validateDistribution(t, p, 1.01) 375 376 i10 := placement.NewEmptyInstance("i10", "r4", "z1", "endpoint", 1) 377 _, err = a.ReplaceInstances(p, []string{i8.ID()}, []placement.Instance{i10}) 378 assert.Error(t, err) 379 380 p, updated := mustMarkAllShardsAsAvailable(t, p, opts) 381 require.True(t, updated) 382 a = newShardedAlgorithm(placement.NewOptions()) 383 i11 := placement.NewEmptyInstance("i11", "r1", "z1", "endpoint", 1) 384 p, err = a.ReplaceInstances(p, []string{i8.ID(), i2.ID()}, []placement.Instance{i10, i11}) 385 require.NoError(t, err) 386 i8, ok := p.Instance(i8.ID()) 387 assert.True(t, ok) 388 assert.True(t, i8.IsLeaving()) 389 i2, ok = p.Instance(i2.ID()) 390 assert.True(t, ok) 391 assert.True(t, i2.IsLeaving()) 392 i10, ok = p.Instance(i10.ID()) 393 assert.True(t, ok) 394 assert.True(t, i10.IsInitializing()) 395 i11, ok = p.Instance(i11.ID()) 396 assert.True(t, ok) 397 assert.True(t, i11.IsInitializing()) 398 validateDistribution(t, p, 1.22) 399 400 // adding a new instance to relieve the load on the hot instances 401 i12 := placement.NewEmptyInstance("i12", "r4", "z1", "endpoint", 1) 402 p, err = a.AddInstances(p, []placement.Instance{i12}) 403 require.NoError(t, err) 404 validateDistribution(t, p, 1.15) 405 } 406 407 func TestRemoveInitializingInstance(t *testing.T) { 408 i1 := placement.NewEmptyInstance("i1", "r1", "z1", "e1", 1) 409 i2 := placement.NewEmptyInstance("i2", "r1", "z1", "e2", 1) 410 411 a := newShardedAlgorithm(placement.NewOptions()) 412 p, err := a.InitialPlacement([]placement.Instance{i1}, []uint32{1, 2}, 1) 413 require.NoError(t, err) 414 415 instance1, ok := p.Instance("i1") 416 assert.True(t, ok) 417 assert.Equal(t, 2, instance1.Shards().NumShardsForState(shard.Initializing)) 418 419 p, updated := mustMarkAllShardsAsAvailable(t, p, placement.NewOptions()) 420 require.True(t, updated) 421 422 instance1, ok = p.Instance("i1") 423 assert.True(t, ok) 424 assert.Equal(t, 2, instance1.Shards().NumShardsForState(shard.Available)) 425 426 p, err = a.AddInstances(p, []placement.Instance{i2}) 427 require.NoError(t, err) 428 429 instance1, ok = p.Instance("i1") 430 assert.True(t, ok) 431 assert.Equal(t, 1, instance1.Shards().NumShardsForState(shard.Available)) 432 assert.Equal(t, 1, instance1.Shards().NumShardsForState(shard.Leaving)) 433 434 instance2, ok := p.Instance("i2") 435 assert.True(t, ok) 436 assert.Equal(t, 1, instance2.Shards().NumShardsForState(shard.Initializing)) 437 for _, s := range instance2.Shards().All() { 438 assert.Equal(t, "i1", s.SourceID()) 439 } 440 441 p, err = a.RemoveInstances(p, []string{"i2"}) 442 require.NoError(t, err) 443 444 instance1, ok = p.Instance("i1") 445 assert.True(t, ok) 446 for _, s := range instance1.Shards().All() { 447 assert.Equal(t, shard.Available, s.State()) 448 assert.Equal(t, "", s.SourceID()) 449 } 450 451 _, ok = p.Instance("i2") 452 assert.False(t, ok) 453 } 454 455 func TestInitPlacementOnNoInstances(t *testing.T) { 456 instances := []placement.Instance{} 457 458 ids := make([]uint32, 128) 459 for i := 0; i < len(ids); i++ { 460 ids[i] = uint32(i) 461 } 462 463 a := newShardedAlgorithm(placement.NewOptions()) 464 p, err := a.InitialPlacement(instances, ids, 1) 465 assert.Error(t, err) 466 assert.Nil(t, p) 467 } 468 469 func TestOneIsolationGroup(t *testing.T) { 470 i1 := placement.NewEmptyInstance("i1", "r1", "z1", "endpoint", 1) 471 i2 := placement.NewEmptyInstance("i2", "r1", "z1", "endpoint", 1) 472 473 instances := []placement.Instance{i1, i2} 474 475 ids := make([]uint32, 1024) 476 for i := 0; i < len(ids); i++ { 477 ids[i] = uint32(i) 478 } 479 480 a := newShardedAlgorithm(placement.NewOptions()) 481 p, err := a.InitialPlacement(instances, ids, 1) 482 require.NoError(t, err) 483 validateDistribution(t, p, 1.01) 484 485 i6 := placement.NewEmptyInstance("i6", "r1", "z1", "endpoint", 1) 486 p, err = a.AddInstances(p, []placement.Instance{i6}) 487 require.NoError(t, err) 488 validateDistribution(t, p, 1.01) 489 } 490 491 func TestRFGreaterThanNumberOfIsolationGroups(t *testing.T) { 492 i1 := placement.NewEmptyInstance("i1", "r1", "z1", "endpoint", 1) 493 i6 := placement.NewEmptyInstance("i6", "r1", "z1", "endpoint", 1) 494 495 i2 := placement.NewEmptyInstance("i2", "r2", "z1", "endpoint", 1) 496 i3 := placement.NewEmptyInstance("i3", "r2", "z1", "endpoint", 1) 497 498 instances := []placement.Instance{i1, i2, i3, i6} 499 500 ids := make([]uint32, 1024) 501 for i := 0; i < len(ids); i++ { 502 ids[i] = uint32(i) 503 } 504 505 a := newShardedAlgorithm(placement.NewOptions()) 506 p, err := a.InitialPlacement(instances, ids, 1) 507 require.NoError(t, err) 508 validateDistribution(t, p, 1.01) 509 510 p, err = a.AddReplica(p) 511 require.NoError(t, err) 512 validateDistribution(t, p, 1.01) 513 514 p1, err := a.AddReplica(p) 515 assert.Error(t, err) 516 assert.Nil(t, p1) 517 require.NoError(t, placement.Validate(p)) 518 } 519 520 func TestRFGreaterThanNumberOfIsolationGroupAfterInstanceRemoval(t *testing.T) { 521 i1 := placement.NewEmptyInstance("i1", "r1", "z1", "endpoint", 1) 522 523 i2 := placement.NewEmptyInstance("i2", "r2", "z1", "endpoint", 1) 524 525 instances := []placement.Instance{i1, i2} 526 527 ids := make([]uint32, 1024) 528 for i := 0; i < len(ids); i++ { 529 ids[i] = uint32(i) 530 } 531 532 a := newShardedAlgorithm(placement.NewOptions()) 533 p, err := a.InitialPlacement(instances, ids, 1) 534 require.NoError(t, err) 535 validateDistribution(t, p, 1.01) 536 537 p, err = a.AddReplica(p) 538 require.NoError(t, err) 539 validateDistribution(t, p, 1.01) 540 541 p1, err := a.RemoveInstances(p, []string{i2.ID()}) 542 assert.Error(t, err) 543 assert.Nil(t, p1) 544 require.NoError(t, placement.Validate(p)) 545 } 546 547 func TestRFGreaterThanNumberOfIsolationGroupAfterInstanceReplace(t *testing.T) { 548 i1 := placement.NewEmptyInstance("i1", "r1", "z1", "endpoint", 1) 549 550 i2 := placement.NewEmptyInstance("i2", "r2", "z1", "endpoint", 1) 551 552 instances := []placement.Instance{i1, i2} 553 554 ids := make([]uint32, 1024) 555 for i := 0; i < len(ids); i++ { 556 ids[i] = uint32(i) 557 } 558 559 a := newShardedAlgorithm(placement.NewOptions()) 560 p, err := a.InitialPlacement(instances, ids, 1) 561 require.NoError(t, err) 562 validateDistribution(t, p, 1.01) 563 564 p, err = a.AddReplica(p) 565 require.NoError(t, err) 566 validateDistribution(t, p, 1.01) 567 568 i3 := placement.NewEmptyInstance("i3", "r1", "z1", "endpoint", 1) 569 p1, err := a.ReplaceInstances(p, []string{i2.ID()}, []placement.Instance{i3}) 570 assert.Error(t, err) 571 assert.Nil(t, p1) 572 require.NoError(t, placement.Validate(p)) 573 } 574 575 func TestRemoveMultipleInstances(t *testing.T) { 576 i1 := placement.NewEmptyInstance("i1", "r1", "z1", "endpoint", 10) 577 i2 := placement.NewEmptyInstance("i2", "r1", "z1", "endpoint", 10) 578 i3 := placement.NewEmptyInstance("i3", "r2", "z1", "endpoint", 20) 579 i4 := placement.NewEmptyInstance("i4", "r3", "z1", "endpoint", 10) 580 i5 := placement.NewEmptyInstance("i5", "r4", "z1", "endpoint", 30) 581 i6 := placement.NewEmptyInstance("i6", "r6", "z1", "endpoint", 40) 582 i7 := placement.NewEmptyInstance("i7", "r7", "z1", "endpoint", 10) 583 i8 := placement.NewEmptyInstance("i8", "r8", "z1", "endpoint", 10) 584 i9 := placement.NewEmptyInstance("i9", "r9", "z1", "endpoint", 10) 585 586 instances := []placement.Instance{i1, i2, i3, i4, i5, i6, i7, i8, i9} 587 588 ids := make([]uint32, 1024) 589 for i := 0; i < len(ids); i++ { 590 ids[i] = uint32(i) 591 } 592 593 a := newShardedAlgorithm(placement.NewOptions()) 594 p, err := a.InitialPlacement(instances, ids, 2) 595 require.NoError(t, err) 596 597 p, updated := mustMarkAllShardsAsAvailable(t, p, placement.NewOptions()) 598 require.True(t, updated) 599 600 p, err = a.RemoveInstances(p, []string{"i1", "i2", "i3"}) 601 require.NoError(t, err) 602 require.NoError(t, placement.Validate(p)) 603 604 i1, ok := p.Instance("i1") 605 assert.True(t, ok) 606 assert.True(t, i1.IsLeaving()) 607 i2, ok = p.Instance("i2") 608 assert.True(t, ok) 609 assert.True(t, i2.IsLeaving()) 610 i3, ok = p.Instance("i3") 611 assert.True(t, ok) 612 assert.True(t, i3.IsLeaving()) 613 } 614 615 func TestRemoveAbsentInstance(t *testing.T) { 616 i1 := placement.NewEmptyInstance("i1", "r1", "z1", "endpoint", 1) 617 618 i2 := placement.NewEmptyInstance("i2", "r2", "z1", "endpoint", 1) 619 620 instances := []placement.Instance{i1, i2} 621 622 ids := make([]uint32, 1024) 623 for i := 0; i < len(ids); i++ { 624 ids[i] = uint32(i) 625 } 626 627 a := newShardedAlgorithm(placement.NewOptions()) 628 p, err := a.InitialPlacement(instances, ids, 1) 629 require.NoError(t, err) 630 validateDistribution(t, p, 1.01) 631 632 i3 := placement.NewEmptyInstance("i3", "r3", "z1", "endpoint", 1) 633 634 p1, err := a.RemoveInstances(p, []string{i3.ID()}) 635 assert.Error(t, err) 636 assert.Nil(t, p1) 637 require.NoError(t, placement.Validate(p)) 638 } 639 640 func TestReplaceAbsentInstance(t *testing.T) { 641 i1 := placement.NewEmptyInstance("i1", "r1", "z1", "endpoint", 1) 642 643 i2 := placement.NewEmptyInstance("i2", "r2", "z1", "endpoint", 1) 644 645 instances := []placement.Instance{i1, i2} 646 647 ids := make([]uint32, 1024) 648 for i := 0; i < len(ids); i++ { 649 ids[i] = uint32(i) 650 } 651 652 a := newShardedAlgorithm(placement.NewOptions()) 653 p, err := a.InitialPlacement(instances, ids, 1) 654 require.NoError(t, err) 655 validateDistribution(t, p, 1.01) 656 657 i3 := placement.NewEmptyInstance("i3", "r3", "z1", "endpoint", 1) 658 i4 := placement.NewEmptyInstance("i4", "r4", "z1", "endpoint", 1) 659 660 p1, err := a.ReplaceInstances(p, []string{i3.ID()}, []placement.Instance{i4}) 661 assert.Error(t, err) 662 assert.Nil(t, p1) 663 require.NoError(t, placement.Validate(p)) 664 } 665 666 func TestInitWithTransitionalShardStates(t *testing.T) { 667 i1 := placement.NewEmptyInstance("i1", "r1", "", "e1", 1) 668 i2 := placement.NewEmptyInstance("i2", "r2", "", "e2", 1) 669 instances := []placement.Instance{i1, i2} 670 671 numShards := 4 672 ids := make([]uint32, numShards) 673 for i := 0; i < len(ids); i++ { 674 ids[i] = uint32(i) 675 } 676 677 a := newShardedAlgorithm(placement.NewOptions()) 678 p, err := a.InitialPlacement(instances, ids, 1) 679 require.NoError(t, err) 680 assert.Equal(t, 1, p.ReplicaFactor()) 681 assert.Equal(t, numShards, p.NumShards()) 682 i1, _ = p.Instance("i1") 683 i2, _ = p.Instance("i2") 684 assert.Equal(t, 2, loadOnInstance(i1)) 685 assert.Equal(t, 2, i1.Shards().NumShards()) 686 assert.Equal(t, "e1", i1.Endpoint()) 687 assert.Equal(t, 2, loadOnInstance(i2)) 688 assert.Equal(t, 2, i2.Shards().NumShards()) 689 assert.Equal(t, "e2", i2.Endpoint()) 690 for _, instance := range p.Instances() { 691 assert.Equal(t, instance.Shards().NumShards(), instance.Shards().NumShardsForState(shard.Initializing)) 692 } 693 } 694 695 func TestInitWithStableShardStates(t *testing.T) { 696 i1 := placement.NewEmptyInstance("i1", "r1", "", "e1", 1) 697 i2 := placement.NewEmptyInstance("i2", "r2", "", "e2", 1) 698 instances := []placement.Instance{i1, i2} 699 700 numShards := 4 701 ids := make([]uint32, numShards) 702 for i := 0; i < len(ids); i++ { 703 ids[i] = uint32(i) 704 } 705 706 a := newShardedAlgorithm(placement.NewOptions().SetShardStateMode(placement.StableShardStateOnly)) 707 p, err := a.InitialPlacement(instances, ids, 1) 708 require.NoError(t, err) 709 assert.Equal(t, 1, p.ReplicaFactor()) 710 assert.Equal(t, numShards, p.NumShards()) 711 i1, _ = p.Instance("i1") 712 i2, _ = p.Instance("i2") 713 assert.Equal(t, 2, loadOnInstance(i1)) 714 assert.Equal(t, 2, i1.Shards().NumShards()) 715 assert.Equal(t, "e1", i1.Endpoint()) 716 assert.Equal(t, 2, loadOnInstance(i2)) 717 assert.Equal(t, 2, i2.Shards().NumShards()) 718 assert.Equal(t, "e2", i2.Endpoint()) 719 for _, instance := range p.Instances() { 720 assert.Equal(t, instance.Shards().NumShards(), instance.Shards().NumShardsForState(shard.Available)) 721 } 722 } 723 724 func TestAddReplica(t *testing.T) { 725 i1 := placement.NewEmptyInstance("i1", "r1", "", "e1", 1) 726 i1.Shards().Add(shard.NewShard(0).SetState(shard.Available)) 727 i1.Shards().Add(shard.NewShard(1).SetState(shard.Available)) 728 729 i2 := placement.NewEmptyInstance("i2", "r2", "", "e2", 1) 730 i2.Shards().Add(shard.NewShard(2).SetState(shard.Available)) 731 i2.Shards().Add(shard.NewShard(3).SetState(shard.Available)) 732 733 instances := []placement.Instance{i1, i2} 734 735 numShards := 4 736 ids := make([]uint32, numShards) 737 for i := 0; i < len(ids); i++ { 738 ids[i] = uint32(i) 739 } 740 p := placement.NewPlacement(). 741 SetInstances(instances). 742 SetShards(ids). 743 SetReplicaFactor(1). 744 SetIsSharded(true) 745 746 a := newShardedAlgorithm(placement.NewOptions()) 747 p, err := a.AddReplica(p) 748 require.NoError(t, err) 749 assert.Equal(t, 2, p.ReplicaFactor()) 750 assert.Equal(t, numShards, p.NumShards()) 751 i1, _ = p.Instance("i1") 752 i2, _ = p.Instance("i2") 753 assert.Equal(t, 4, loadOnInstance(i1)) 754 assert.Equal(t, 4, i1.Shards().NumShards()) 755 assert.Equal(t, "e1", i1.Endpoint()) 756 assert.Equal(t, 4, loadOnInstance(i2)) 757 assert.Equal(t, 4, i2.Shards().NumShards()) 758 assert.Equal(t, "e2", i2.Endpoint()) 759 for _, instance := range p.Instances() { 760 availableTotal := 0 761 initTotal := 0 762 for _, s := range instance.Shards().All() { 763 assert.NotEqual(t, shard.Leaving, s.State()) 764 assert.Equal(t, "", s.SourceID()) 765 if s.State() == shard.Available { 766 availableTotal++ 767 } 768 if s.State() == shard.Initializing { 769 initTotal++ 770 } 771 } 772 assert.Equal(t, 2, availableTotal) 773 assert.Equal(t, 2, initTotal) 774 } 775 } 776 777 func TestAddInstance(t *testing.T) { 778 i1 := placement.NewEmptyInstance("i1", "r1", "", "e1", 1) 779 i1.Shards().Add(shard.NewShard(0).SetState(shard.Available)) 780 i1.Shards().Add(shard.NewShard(1).SetState(shard.Available)) 781 782 instances := []placement.Instance{i1} 783 784 numShards := 2 785 ids := make([]uint32, numShards) 786 for i := 0; i < len(ids); i++ { 787 ids[i] = uint32(i) 788 } 789 p := placement.NewPlacement(). 790 SetInstances(instances). 791 SetShards(ids). 792 SetReplicaFactor(1). 793 SetIsSharded(true) 794 795 a := newShardedAlgorithm(placement.NewOptions()) 796 i2 := placement.NewEmptyInstance("i2", "r2", "", "e2", 1) 797 p, err := a.AddInstances(p, []placement.Instance{i2}) 798 require.NoError(t, err) 799 assert.Equal(t, 1, p.ReplicaFactor()) 800 i1, _ = p.Instance("i1") 801 i2, _ = p.Instance("i2") 802 assert.Equal(t, 1, loadOnInstance(i1)) 803 assert.Equal(t, 2, i1.Shards().NumShards()) 804 assert.Equal(t, "e1", i1.Endpoint()) 805 assert.Equal(t, 1, loadOnInstance(i2)) 806 assert.Equal(t, 1, i2.Shards().NumShards()) 807 assert.Equal(t, "e2", i2.Endpoint()) 808 for _, s := range i2.Shards().All() { 809 assert.Equal(t, shard.Initializing, s.State()) 810 assert.Equal(t, "i1", s.SourceID()) 811 } 812 } 813 814 func TestAddInstance_ExistNonLeaving(t *testing.T) { 815 i1 := placement.NewEmptyInstance("i1", "r1", "z1", "endpoint", 1) 816 i2 := placement.NewEmptyInstance("i2", "r2", "z1", "endpoint", 1) 817 818 instances := []placement.Instance{i1, i2} 819 820 ids := make([]uint32, 10) 821 for i := 0; i < len(ids); i++ { 822 ids[i] = uint32(i) 823 } 824 825 a := newShardedAlgorithm(placement.NewOptions()) 826 p, err := a.InitialPlacement(instances, ids, 1) 827 require.NoError(t, err) 828 829 p1, err := a.AddInstances(p, []placement.Instance{i2}) 830 assert.Error(t, err) 831 assert.Nil(t, p1) 832 require.NoError(t, placement.Validate(p)) 833 } 834 835 func TestAddInstance_ExistAndLeaving(t *testing.T) { 836 i1 := placement.NewEmptyInstance("i1", "r1", "z1", "endpoint", 1) 837 838 i2 := placement.NewEmptyInstance("i2", "r2", "z1", "endpoint", 1) 839 840 instances := []placement.Instance{i1, i2} 841 842 ids := make([]uint32, 10) 843 for i := 0; i < len(ids); i++ { 844 ids[i] = uint32(i) 845 } 846 847 a := newShardedAlgorithm(placement.NewOptions()) 848 p, err := a.InitialPlacement(instances, ids, 1) 849 require.NoError(t, err) 850 851 p, updated := mustMarkAllShardsAsAvailable(t, p, placement.NewOptions()) 852 require.True(t, updated) 853 854 p, err = a.RemoveInstances(p, []string{"i2"}) 855 require.NoError(t, err) 856 857 i2, ok := p.Instance("i2") 858 assert.True(t, ok) 859 assert.True(t, i2.IsLeaving()) 860 861 p, err = a.AddInstances(p, []placement.Instance{i2}) 862 require.NoError(t, err) 863 require.NoError(t, placement.Validate(p)) 864 } 865 866 func TestAddInstance_ExistAndLeavingIsolationGroupConflict(t *testing.T) { 867 i1 := placement.NewInstance().SetID("i1").SetEndpoint("e1").SetIsolationGroup("r1").SetZone("z1").SetWeight(1). 868 SetShards(shard.NewShards([]shard.Shard{ 869 shard.NewShard(0).SetState(shard.Leaving), 870 shard.NewShard(1).SetState(shard.Leaving), 871 shard.NewShard(3).SetState(shard.Leaving), 872 })) 873 i2 := placement.NewInstance().SetID("i2").SetEndpoint("e2").SetIsolationGroup("r1").SetZone("z2").SetWeight(1). 874 SetShards(shard.NewShards([]shard.Shard{ 875 shard.NewShard(0).SetState(shard.Available), 876 shard.NewShard(1).SetState(shard.Available), 877 })) 878 i3 := placement.NewInstance().SetID("i3").SetEndpoint("e3").SetIsolationGroup("r3").SetZone("z3").SetWeight(1). 879 SetShards(shard.NewShards([]shard.Shard{ 880 shard.NewShard(2).SetState(shard.Available), 881 shard.NewShard(3).SetState(shard.Available), 882 })) 883 i4 := placement.NewInstance().SetID("i4").SetEndpoint("e4").SetIsolationGroup("r4").SetZone("z4").SetWeight(1). 884 SetShards(shard.NewShards([]shard.Shard{ 885 shard.NewShard(0).SetState(shard.Initializing).SetSourceID("i1"), 886 shard.NewShard(1).SetState(shard.Initializing).SetSourceID("i1"), 887 shard.NewShard(2).SetState(shard.Initializing).SetSourceID("i1"), 888 shard.NewShard(3).SetState(shard.Initializing).SetSourceID("i1"), 889 })) 890 891 p := placement.NewPlacement(). 892 SetInstances([]placement.Instance{i1, i2, i3, i4}). 893 SetIsSharded(true). 894 SetReplicaFactor(2). 895 SetShards([]uint32{0, 1, 2, 3}) 896 897 a := newShardedAlgorithm(placement.NewOptions()) 898 p, err := a.AddInstances(p, []placement.Instance{i1}) 899 require.NoError(t, err) 900 i1, ok := p.Instance("i1") 901 assert.True(t, ok) 902 assert.Equal(t, 2, loadOnInstance(i1)) 903 } 904 905 func TestRemoveInstance(t *testing.T) { 906 i1 := placement.NewEmptyInstance("i1", "r1", "", "e1", 1) 907 i1.Shards().Add(shard.NewShard(0).SetState(shard.Available)) 908 i1.Shards().Add(shard.NewShard(1).SetState(shard.Available)) 909 910 i2 := placement.NewEmptyInstance("i2", "r2", "", "e2", 1) 911 i2.Shards().Add(shard.NewShard(2).SetState(shard.Available)) 912 i2.Shards().Add(shard.NewShard(3).SetState(shard.Available)) 913 914 instances := []placement.Instance{i1, i2} 915 916 numShards := 4 917 ids := make([]uint32, numShards) 918 for i := 0; i < len(ids); i++ { 919 ids[i] = uint32(i) 920 } 921 p := placement.NewPlacement(). 922 SetInstances(instances). 923 SetShards(ids). 924 SetReplicaFactor(1). 925 SetIsSharded(true) 926 927 a := newShardedAlgorithm(placement.NewOptions()) 928 p, err := a.RemoveInstances(p, []string{i2.ID()}) 929 require.NoError(t, err) 930 assert.Equal(t, 1, p.ReplicaFactor()) 931 assert.Equal(t, numShards, p.NumShards()) 932 i1, _ = p.Instance("i1") 933 i2, _ = p.Instance("i2") 934 assert.Equal(t, 4, loadOnInstance(i1)) 935 assert.Equal(t, 4, i1.Shards().NumShards()) 936 assert.Equal(t, "e1", i1.Endpoint()) 937 assert.Equal(t, 0, loadOnInstance(i2)) 938 assert.Equal(t, 2, i2.Shards().NumShards()) 939 assert.Equal(t, "e2", i2.Endpoint()) 940 for _, s := range i2.Shards().All() { 941 assert.Equal(t, shard.Leaving, s.State()) 942 assert.Equal(t, "", s.SourceID()) 943 } 944 availableTotal := 0 945 initTotal := 0 946 for _, s := range i1.Shards().All() { 947 if s.State() == shard.Available { 948 availableTotal++ 949 } 950 if s.State() == shard.Initializing { 951 initTotal++ 952 } 953 } 954 assert.Equal(t, 2, availableTotal) 955 assert.Equal(t, 2, initTotal) 956 } 957 958 func TestReplaceInstance(t *testing.T) { 959 i1 := placement.NewEmptyInstance("i1", "r1", "", "e1", 1) 960 i1.Shards().Add(shard.NewShard(0).SetState(shard.Available)) 961 i1.Shards().Add(shard.NewShard(1).SetState(shard.Available)) 962 963 i2 := placement.NewEmptyInstance("i2", "r2", "", "e2", 1) 964 i2.Shards().Add(shard.NewShard(2).SetState(shard.Available)) 965 i2.Shards().Add(shard.NewShard(3).SetState(shard.Available)) 966 967 instances := []placement.Instance{i1, i2} 968 969 numShards := 4 970 ids := make([]uint32, numShards) 971 for i := 0; i < len(ids); i++ { 972 ids[i] = uint32(i) 973 } 974 p := placement.NewPlacement(). 975 SetInstances(instances). 976 SetShards(ids). 977 SetReplicaFactor(1). 978 SetIsSharded(true) 979 980 a := newShardedAlgorithm(placement.NewOptions()) 981 i3 := placement.NewEmptyInstance("i3", "r3", "", "e3", 1) 982 p, err := a.ReplaceInstances(p, []string{i2.ID()}, []placement.Instance{i3}) 983 require.NoError(t, err) 984 i1, _ = p.Instance("i1") 985 i2, _ = p.Instance("i2") 986 i3, _ = p.Instance("i3") 987 assert.Equal(t, 2, loadOnInstance(i1)) 988 assert.Equal(t, 2, i1.Shards().NumShards()) 989 assert.Equal(t, "e1", i1.Endpoint()) 990 assert.Equal(t, 0, loadOnInstance(i2)) 991 assert.Equal(t, 2, i2.Shards().NumShards()) 992 assert.Equal(t, "e2", i2.Endpoint()) 993 assert.Equal(t, 2, loadOnInstance(i3)) 994 assert.Equal(t, 2, i3.Shards().NumShards()) 995 assert.Equal(t, "e3", i3.Endpoint()) 996 for _, s := range i1.Shards().All() { 997 assert.Equal(t, shard.Available, s.State()) 998 assert.Equal(t, "", s.SourceID()) 999 } 1000 for _, s := range i2.Shards().All() { 1001 assert.Equal(t, shard.Leaving, s.State()) 1002 assert.Equal(t, "", s.SourceID()) 1003 } 1004 for _, s := range i3.Shards().All() { 1005 assert.Equal(t, shard.Initializing, s.State()) 1006 assert.Equal(t, "i2", s.SourceID()) 1007 } 1008 } 1009 1010 func TestReplaceInstance_Backout(t *testing.T) { 1011 i1 := placement.NewEmptyInstance("i1", "r1", "", "e1", 1) 1012 i1.Shards().Add(shard.NewShard(0).SetState(shard.Available)) 1013 1014 i2 := placement.NewEmptyInstance("i2", "r2", "", "e2", 1) 1015 i2.Shards().Add(shard.NewShard(1).SetState(shard.Available)) 1016 1017 instances := []placement.Instance{i1, i2} 1018 1019 p := placement.NewPlacement(). 1020 SetInstances(instances). 1021 SetShards([]uint32{0, 1}). 1022 SetReplicaFactor(1). 1023 SetIsSharded(true) 1024 1025 a := newShardedAlgorithm(placement.NewOptions()) 1026 i3 := placement.NewEmptyInstance("i3", "r3", "", "e3", 1) 1027 p, err := a.ReplaceInstances(p, []string{i2.ID()}, []placement.Instance{i3}) 1028 require.NoError(t, err) 1029 i1, _ = p.Instance("i1") 1030 i2, _ = p.Instance("i2") 1031 i3, _ = p.Instance("i3") 1032 assert.Equal(t, 1, loadOnInstance(i1)) 1033 assert.Equal(t, 1, i1.Shards().NumShards()) 1034 assert.Equal(t, "e1", i1.Endpoint()) 1035 assert.Equal(t, 0, loadOnInstance(i2)) 1036 assert.Equal(t, 1, i2.Shards().NumShards()) 1037 assert.Equal(t, "e2", i2.Endpoint()) 1038 assert.Equal(t, 1, loadOnInstance(i3)) 1039 assert.Equal(t, 1, i3.Shards().NumShards()) 1040 assert.Equal(t, "e3", i3.Endpoint()) 1041 for _, s := range i1.Shards().All() { 1042 assert.Equal(t, shard.Available, s.State()) 1043 assert.Equal(t, "", s.SourceID()) 1044 } 1045 for _, s := range i2.Shards().All() { 1046 assert.Equal(t, shard.Leaving, s.State()) 1047 assert.Equal(t, "", s.SourceID()) 1048 } 1049 for _, s := range i3.Shards().All() { 1050 assert.Equal(t, shard.Initializing, s.State()) 1051 assert.Equal(t, "i2", s.SourceID()) 1052 } 1053 1054 p, err = a.AddInstances(p, []placement.Instance{i2}) 1055 require.NoError(t, err) 1056 _, ok := p.Instance("i3") 1057 assert.False(t, ok) 1058 1059 i1, _ = p.Instance("i1") 1060 i2, _ = p.Instance("i2") 1061 assert.Equal(t, 1, loadOnInstance(i1)) 1062 assert.Equal(t, 1, i1.Shards().NumShards()) 1063 assert.Equal(t, 1, i1.Shards().NumShardsForState(shard.Available)) 1064 assert.Equal(t, "e1", i1.Endpoint()) 1065 assert.Equal(t, 1, loadOnInstance(i2)) 1066 assert.Equal(t, 1, i2.Shards().NumShards()) 1067 assert.Equal(t, 1, i2.Shards().NumShardsForState(shard.Available)) 1068 assert.Equal(t, "e2", i2.Endpoint()) 1069 } 1070 1071 func TestIncompatibleWithShardedAlgo(t *testing.T) { 1072 i1 := placement.NewInstance().SetID("i1").SetEndpoint("e1") 1073 i2 := placement.NewInstance().SetID("i2").SetEndpoint("e2") 1074 i3 := placement.NewInstance().SetID("i3").SetEndpoint("e3") 1075 i4 := placement.NewInstance().SetID("i4").SetEndpoint("e4") 1076 1077 p, err := newNonShardedAlgorithm().InitialPlacement([]placement.Instance{i1, i2}, []uint32{}, 1) 1078 require.NoError(t, err) 1079 1080 a := newShardedAlgorithm(placement.NewOptions()) 1081 _, err = a.AddReplica(p) 1082 assert.Error(t, err) 1083 assert.Equal(t, errIncompatibleWithShardedAlgo, err) 1084 1085 _, err = a.AddInstances(p, []placement.Instance{i3}) 1086 assert.Error(t, err) 1087 assert.Equal(t, errIncompatibleWithShardedAlgo, err) 1088 1089 _, err = a.RemoveInstances(p, []string{"i1"}) 1090 assert.Error(t, err) 1091 assert.Equal(t, errIncompatibleWithShardedAlgo, err) 1092 1093 _, err = a.ReplaceInstances(p, []string{"i1"}, []placement.Instance{i3, i4}) 1094 assert.Error(t, err) 1095 assert.Equal(t, errIncompatibleWithShardedAlgo, err) 1096 1097 _, err = a.MarkShardsAvailable(p, "i2", 0) 1098 assert.Error(t, err) 1099 assert.Equal(t, errIncompatibleWithShardedAlgo, err) 1100 } 1101 1102 func TestMarkShardAsAvailableWithShardedAlgo(t *testing.T) { 1103 var ( 1104 cutoverTime = time.Now() 1105 cutoverTimeNanos = cutoverTime.UnixNano() 1106 maxTimeWindow = time.Hour 1107 tenMinutesInThePast = cutoverTime.Add(1 - 0*time.Minute) 1108 tenMinutesInTheFuture = cutoverTime.Add(10 * time.Minute) 1109 oneHourInTheFuture = cutoverTime.Add(maxTimeWindow) 1110 ) 1111 i1 := placement.NewEmptyInstance("i1", "", "", "e1", 1) 1112 i1.Shards().Add(shard.NewShard(0).SetState(shard.Leaving).SetCutoffNanos(cutoverTimeNanos)) 1113 1114 i2 := placement.NewEmptyInstance("i2", "", "", "e2", 1) 1115 i2.Shards().Add(shard.NewShard(0).SetState(shard.Initializing).SetSourceID("i1").SetCutoverNanos(cutoverTimeNanos)) 1116 1117 p := placement.NewPlacement(). 1118 SetInstances([]placement.Instance{i1, i2}). 1119 SetShards([]uint32{0}). 1120 SetReplicaFactor(1). 1121 SetIsSharded(true). 1122 SetIsMirrored(true) 1123 1124 a := newShardedAlgorithm(placement.NewOptions(). 1125 SetIsShardCutoverFn(genShardCutoverFn(tenMinutesInThePast)). 1126 SetIsShardCutoffFn(genShardCutoffFn(tenMinutesInThePast, time.Hour))) 1127 _, err := a.MarkShardsAvailable(p, "i2", 0) 1128 assert.Error(t, err) 1129 1130 a = newShardedAlgorithm(placement.NewOptions(). 1131 SetIsShardCutoverFn(genShardCutoverFn(tenMinutesInTheFuture)). 1132 SetIsShardCutoffFn(genShardCutoffFn(tenMinutesInTheFuture, time.Hour))) 1133 _, err = a.MarkShardsAvailable(p, "i2", 0) 1134 assert.Error(t, err) 1135 1136 a = newShardedAlgorithm(placement.NewOptions(). 1137 SetIsShardCutoverFn(genShardCutoverFn(oneHourInTheFuture)). 1138 SetIsShardCutoffFn(genShardCutoffFn(oneHourInTheFuture, time.Hour))) 1139 p, err = a.MarkShardsAvailable(p, "i2", 0) 1140 assert.NoError(t, err) 1141 assert.NoError(t, placement.Validate(p)) 1142 } 1143 1144 func TestMarkShardAsAvailableBulkWithShardedAlgo(t *testing.T) { 1145 var ( 1146 cutoverTime = time.Now() 1147 cutoverTimeNanos = cutoverTime.UnixNano() 1148 maxTimeWindow = time.Hour 1149 oneHourInTheFuture = cutoverTime.Add(maxTimeWindow) 1150 ) 1151 i1 := placement.NewEmptyInstance("i1", "", "", "e1", 1) 1152 i1.Shards().Add(shard.NewShard(0).SetState(shard.Leaving).SetCutoffNanos(cutoverTimeNanos)) 1153 i1.Shards().Add(shard.NewShard(1).SetState(shard.Leaving).SetCutoffNanos(cutoverTimeNanos)) 1154 1155 i2 := placement.NewEmptyInstance("i2", "", "", "e2", 1) 1156 i2.Shards().Add(shard.NewShard(0).SetState(shard.Initializing).SetSourceID("i1").SetCutoverNanos(cutoverTimeNanos)) 1157 i2.Shards().Add(shard.NewShard(1).SetState(shard.Initializing).SetSourceID("i1").SetCutoverNanos(cutoverTimeNanos)) 1158 1159 p := placement.NewPlacement(). 1160 SetInstances([]placement.Instance{i1, i2}). 1161 SetShards([]uint32{0, 1}). 1162 SetReplicaFactor(1). 1163 SetIsSharded(true). 1164 SetIsMirrored(true) 1165 1166 a := newShardedAlgorithm(placement.NewOptions(). 1167 SetIsShardCutoverFn(genShardCutoverFn(oneHourInTheFuture)). 1168 SetIsShardCutoffFn(genShardCutoffFn(oneHourInTheFuture, time.Hour))) 1169 p, err := a.MarkShardsAvailable(p, "i2", 0, 1) 1170 assert.NoError(t, err) 1171 assert.NoError(t, placement.Validate(p)) 1172 1173 mi2, ok := p.Instance("i2") 1174 assert.True(t, ok) 1175 shards := mi2.Shards().All() 1176 assert.Len(t, shards, 2) 1177 for _, s := range shards { 1178 assert.Equal(t, shard.Available, s.State()) 1179 } 1180 } 1181 1182 func TestGoodCaseWithSimpleShardStateType(t *testing.T) { 1183 i1 := placement.NewEmptyInstance("i1", "r1", "z1", "endpoint", 1) 1184 i2 := placement.NewEmptyInstance("i2", "r1", "z1", "endpoint", 1) 1185 i3 := placement.NewEmptyInstance("i3", "r2", "z1", "endpoint", 1) 1186 i4 := placement.NewEmptyInstance("i4", "r2", "z1", "endpoint", 1) 1187 i5 := placement.NewEmptyInstance("i5", "r3", "z1", "endpoint", 1) 1188 i6 := placement.NewEmptyInstance("i6", "r4", "z1", "endpoint", 1) 1189 i7 := placement.NewEmptyInstance("i7", "r5", "z1", "endpoint", 1) 1190 i8 := placement.NewEmptyInstance("i8", "r6", "z1", "endpoint", 1) 1191 i9 := placement.NewEmptyInstance("i9", "r7", "z1", "endpoint", 1) 1192 1193 instances := []placement.Instance{i1, i2, i3, i4, i5, i6, i7, i8, i9} 1194 1195 numShards := 1024 1196 ids := make([]uint32, numShards) 1197 for i := 0; i < len(ids); i++ { 1198 ids[i] = uint32(i) 1199 } 1200 1201 opts := placement.NewOptions(). 1202 SetPlacementCutoverNanosFn(timeNanosGen(1)). 1203 SetShardCutoverNanosFn(timeNanosGen(2)). 1204 SetShardCutoffNanosFn(timeNanosGen(3)). 1205 SetShardStateMode(placement.StableShardStateOnly) 1206 a := newShardedAlgorithm(opts) 1207 p, err := a.InitialPlacement(instances, ids, 1) 1208 require.NoError(t, err) 1209 verifyAllShardsInAvailableState(t, p) 1210 1211 p, err = a.AddInstances(p, []placement.Instance{placement.NewEmptyInstance("i21", "r6", "z1", "endpoint", 1)}) 1212 require.NoError(t, err) 1213 verifyAllShardsInAvailableState(t, p) 1214 1215 p, err = a.RemoveInstances(p, []string{i1.ID()}) 1216 require.NoError(t, err) 1217 verifyAllShardsInAvailableState(t, p) 1218 1219 i12 := placement.NewEmptyInstance("i12", "r3", "z1", "endpoint", 1) 1220 p, err = a.ReplaceInstances(p, []string{i5.ID()}, []placement.Instance{i12}) 1221 require.NoError(t, err) 1222 verifyAllShardsInAvailableState(t, p) 1223 1224 p, err = a.AddReplica(p) 1225 require.NoError(t, err) 1226 verifyAllShardsInAvailableState(t, p) 1227 1228 p, err = a.BalanceShards(p) 1229 require.NoError(t, err) 1230 verifyAllShardsInAvailableState(t, p) 1231 } 1232 1233 func TestBalanceShardsForShardedWhenBalanced(t *testing.T) { 1234 i1 := newTestInstance("i1"). 1235 SetWeight(1). 1236 SetShards(shard.NewShards([]shard.Shard{ 1237 shard.NewShard(0).SetState(shard.Available), 1238 })) 1239 i2 := newTestInstance("i2"). 1240 SetWeight(1). 1241 SetShards(shard.NewShards([]shard.Shard{ 1242 shard.NewShard(0).SetState(shard.Available), 1243 })) 1244 i3 := newTestInstance("i3"). 1245 SetWeight(1). 1246 SetShards(shard.NewShards([]shard.Shard{ 1247 shard.NewShard(1).SetState(shard.Available), 1248 })) 1249 i4 := newTestInstance("i4"). 1250 SetWeight(1). 1251 SetShards(shard.NewShards([]shard.Shard{ 1252 shard.NewShard(1).SetState(shard.Available), 1253 })) 1254 initialPlacement := placement.NewPlacement(). 1255 SetReplicaFactor(2). 1256 SetShards([]uint32{0, 1}). 1257 SetInstances([]placement.Instance{i1, i2, i3, i4}). 1258 SetIsSharded(true) 1259 1260 expectedPlacement := initialPlacement.Clone() 1261 1262 a := NewAlgorithm(placement.NewOptions()) 1263 1264 balancedPlacement, err := a.BalanceShards(initialPlacement) 1265 assert.NoError(t, err) 1266 assert.Equal(t, expectedPlacement, balancedPlacement) 1267 } 1268 1269 func TestBalanceShardsForShardedWhenImbalanced(t *testing.T) { 1270 i1 := newTestInstance("i1"). 1271 SetWeight(1). 1272 SetShards(shard.NewShards([]shard.Shard{ 1273 shard.NewShard(0).SetState(shard.Available), 1274 shard.NewShard(1).SetState(shard.Available), 1275 })) 1276 i2 := newTestInstance("i2"). 1277 SetWeight(2). 1278 SetShards(shard.NewShards([]shard.Shard{ 1279 shard.NewShard(2).SetState(shard.Available), 1280 })) 1281 p := placement.NewPlacement(). 1282 SetReplicaFactor(1). 1283 SetShards([]uint32{0, 1, 2}). 1284 SetInstances([]placement.Instance{i1, i2}). 1285 SetIsSharded(true) 1286 1287 a := NewAlgorithm(placement.NewOptions()) 1288 1289 balancedPlacement, err := a.BalanceShards(p) 1290 assert.NoError(t, err) 1291 1292 bi1 := newTestInstance("i1"). 1293 SetWeight(1). 1294 SetShards(shard.NewShards([]shard.Shard{ 1295 shard.NewShard(0).SetState(shard.Leaving), 1296 shard.NewShard(1).SetState(shard.Available), 1297 })) 1298 bi2 := newTestInstance("i2"). 1299 SetWeight(2). 1300 SetShards(shard.NewShards([]shard.Shard{ 1301 shard.NewShard(0).SetState(shard.Initializing).SetSourceID("i1"), 1302 shard.NewShard(2).SetState(shard.Available), 1303 })) 1304 expectedInstances := []placement.Instance{bi1, bi2} 1305 1306 assert.Equal(t, expectedInstances, balancedPlacement.Instances()) 1307 } 1308 1309 func verifyAllShardsInAvailableState(t *testing.T, p placement.Placement) { 1310 for _, instance := range p.Instances() { 1311 s := instance.Shards() 1312 require.Equal(t, len(s.All()), len(s.ShardsForState(shard.Available))) 1313 require.Empty(t, s.ShardsForState(shard.Initializing)) 1314 require.Empty(t, s.ShardsForState(shard.Leaving)) 1315 } 1316 } 1317 1318 func mustMarkAllShardsAsAvailable(t *testing.T, p placement.Placement, opts placement.Options) (placement.Placement, bool) { 1319 if opts == nil { 1320 opts = placement.NewOptions() 1321 } 1322 p, updated, err := markAllShardsAvailable(p, opts) 1323 require.NoError(t, err) 1324 return p, updated 1325 } 1326 1327 func validateCutoverCutoffNanos(t *testing.T, p placement.Placement, opts placement.Options) { 1328 for _, i := range p.Instances() { 1329 for _, s := range i.Shards().All() { 1330 switch s.State() { 1331 case shard.Available: 1332 assert.Equal(t, shard.DefaultShardCutoverNanos, s.CutoverNanos()) 1333 assert.Equal(t, shard.DefaultShardCutoffNanos, s.CutoffNanos()) 1334 case shard.Initializing: 1335 assert.Equal(t, opts.ShardCutoverNanosFn()(), s.CutoverNanos()) 1336 assert.Equal(t, shard.DefaultShardCutoffNanos, s.CutoffNanos()) 1337 case shard.Leaving: 1338 assert.Equal(t, shard.DefaultShardCutoverNanos, s.CutoverNanos()) 1339 assert.Equal(t, opts.ShardCutoffNanosFn()(), s.CutoffNanos()) 1340 case shard.Unknown: 1341 assert.Fail(t, "invalid shard state") 1342 } 1343 } 1344 } 1345 assert.Equal(t, opts.PlacementCutoverNanosFn()(), p.CutoverNanos()) 1346 } 1347 1348 func validateDistribution(t *testing.T, p placement.Placement, expectPeakOverAvg float64) { 1349 assert.NoError(t, placement.Validate(p), "placement validation failed") 1350 ph := NewPlacementHelper(p, placement.NewOptions()).(*helper) 1351 total := 0 1352 for _, i := range p.Instances() { 1353 load := loadOnInstance(i) 1354 total += load 1355 avgLoad := getWeightedLoad(ph, i.Weight()) 1356 instanceOverAvg := float64(load) / float64(avgLoad) 1357 if math.Abs(float64(load-avgLoad)) > 1 { 1358 assert.True(t, instanceOverAvg <= expectPeakOverAvg, fmt.Sprintf("Bad shard distribution, peak/Avg on %s is too high: %v, expecting %v, load on instance: %v, avg load: %v", 1359 i.ID(), instanceOverAvg, expectPeakOverAvg, load, avgLoad)) 1360 } 1361 1362 targetLoad := ph.targetLoadForInstance(i.ID()) 1363 if targetLoad == 0 { 1364 continue 1365 } 1366 instanceOverTarget := float64(load) / float64(targetLoad) 1367 if math.Abs(float64(load-targetLoad)) > 1 { 1368 assert.True(t, instanceOverTarget <= 1.03, fmt.Sprintf("Bad shard distribution, peak/Target on %s is too high: %v, load on instance: %v, target load: %v", 1369 i.ID(), instanceOverTarget, load, targetLoad)) 1370 } 1371 } 1372 assert.Equal(t, p.ReplicaFactor()*p.NumShards(), total, fmt.Sprintf("Wrong total shards: expecting %v, but got %v", p.ReplicaFactor()*p.NumShards(), total)) 1373 } 1374 1375 func getWeightedLoad(ph *helper, weight uint32) int { 1376 return ph.rf * len(ph.shardToInstanceMap) * int(weight) / int(ph.totalWeight) 1377 } 1378 1379 func timeNanosGen(v int64) placement.TimeNanosFn { 1380 return func() int64 { 1381 return v 1382 } 1383 }