github.com/m3db/m3@v1.5.0/src/cluster/placement/algo/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 algo 22 23 import ( 24 "errors" 25 "testing" 26 "time" 27 28 "github.com/m3db/m3/src/cluster/placement" 29 "github.com/m3db/m3/src/cluster/shard" 30 31 "github.com/stretchr/testify/assert" 32 "github.com/stretchr/testify/require" 33 ) 34 35 func TestMirrorWorkflow(t *testing.T) { 36 i1 := newTestInstance("i1").SetShardSetID(1).SetWeight(1) 37 i2 := newTestInstance("i2").SetShardSetID(1).SetWeight(1) 38 i3 := newTestInstance("i3").SetShardSetID(2).SetWeight(2) 39 i4 := newTestInstance("i4").SetShardSetID(2).SetWeight(2) 40 i5 := newTestInstance("i5").SetShardSetID(3).SetWeight(3) 41 i6 := newTestInstance("i6").SetShardSetID(3).SetWeight(3) 42 instances := []placement.Instance{i1, i2, i3, i4, i5, i6} 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 := NewAlgorithm(placement.NewOptions().SetIsMirrored(true). 51 SetPlacementCutoverNanosFn(timeNanosGen(1)). 52 SetShardCutoverNanosFn(timeNanosGen(2)). 53 SetShardCutoffNanosFn(timeNanosGen(3))) 54 p, err := a.InitialPlacement(instances, ids, 2) 55 assert.NoError(t, err) 56 assert.NoError(t, placement.Validate(p)) 57 assert.True(t, p.IsMirrored()) 58 assert.Equal(t, 2, p.ReplicaFactor()) 59 assert.Equal(t, int64(1), p.CutoverNanos()) 60 assert.Equal(t, uint32(3), p.MaxShardSetID()) 61 62 _, err = a.AddInstances(p, []placement.Instance{placement.NewEmptyInstance("xxx", "rrr", "zzz", "endpoint", 1)}) 63 assert.Error(t, err) 64 65 p, err = a.AddInstances(p, []placement.Instance{}) 66 assert.NoError(t, err) 67 assert.NoError(t, placement.Validate(p)) 68 69 i7 := newTestInstance("i7").SetShardSetID(4).SetWeight(4) 70 i8 := newTestInstance("i8").SetShardSetID(4).SetWeight(4) 71 p, err = a.AddInstances(p, []placement.Instance{i7, i8}) 72 assert.NoError(t, err) 73 assert.Equal(t, uint32(4), p.MaxShardSetID()) 74 validateDistribution(t, p, 1.01) 75 76 // validate InstanceMetadata is still set on all instances 77 var zero placement.InstanceMetadata 78 for _, inst := range p.Instances() { 79 assert.NotEqual(t, zero, inst.Metadata()) 80 } 81 82 newI1, ok := p.Instance("i1") 83 assert.True(t, ok) 84 assert.Equal(t, i1.SetShards(newI1.Shards()), newI1) 85 newI2, ok := p.Instance("i2") 86 assert.True(t, ok) 87 assert.Equal(t, i2.SetShards(newI2.Shards()), newI2) 88 newI3, ok := p.Instance("i3") 89 assert.True(t, ok) 90 assert.Equal(t, i3.SetShards(newI3.Shards()), newI3) 91 newI4, ok := p.Instance("i4") 92 assert.True(t, ok) 93 assert.Equal(t, i4.SetShards(newI4.Shards()), newI4) 94 newI5, ok := p.Instance("i5") 95 assert.True(t, ok) 96 assert.Equal(t, i5.SetShards(newI5.Shards()), newI5) 97 newI6, ok := p.Instance("i6") 98 assert.True(t, ok) 99 assert.Equal(t, i6.SetShards(newI6.Shards()), newI6) 100 newI7, ok := p.Instance("i7") 101 assert.True(t, ok) 102 assert.Equal(t, i7.SetShards(newI7.Shards()), newI7) 103 newI8, ok := p.Instance("i8") 104 assert.True(t, ok) 105 assert.Equal(t, i8.SetShards(newI8.Shards()), newI8) 106 107 _, err = a.RemoveInstances(p, []string{i1.ID()}) 108 assert.Error(t, err) 109 assert.Equal(t, uint32(4), p.MaxShardSetID()) 110 111 p, err = a.RemoveInstances(p, []string{i7.ID(), i8.ID()}) 112 assert.NoError(t, err) 113 assert.Equal(t, uint32(4), p.MaxShardSetID()) 114 assert.NoError(t, placement.Validate(p)) 115 116 i16 := newTestInstance("i16").SetShardSetID(3).SetWeight(3) 117 p, err = a.ReplaceInstances(p, []string{"i6"}, []placement.Instance{i16}) 118 assert.NoError(t, err) 119 assert.Equal(t, uint32(4), p.MaxShardSetID()) 120 assert.NoError(t, placement.Validate(p)) 121 122 i9 := newTestInstance("i9").SetShardSetID(5).SetWeight(1) 123 i10 := newTestInstance("i10").SetShardSetID(5).SetWeight(1) 124 p, err = a.AddInstances(p, []placement.Instance{i9, i10}) 125 assert.NoError(t, err) 126 assert.Equal(t, uint32(5), p.MaxShardSetID()) 127 validateDistribution(t, p, 1.01) 128 129 p, err = a.ReplaceInstances(p, []string{"i9"}, []placement.Instance{ 130 placement.NewInstance(). 131 SetID("i19"). 132 SetIsolationGroup("r9"). 133 SetEndpoint("endpoint19"). 134 SetShardSetID(5). 135 SetWeight(1), 136 }) 137 assert.NoError(t, err) 138 assert.Equal(t, uint32(5), p.MaxShardSetID()) 139 assert.NoError(t, placement.Validate(p)) 140 141 p, err = a.RemoveInstances(p, []string{"i19", "i10"}) 142 assert.NoError(t, err) 143 assert.Equal(t, uint32(5), p.MaxShardSetID()) 144 assert.NoError(t, placement.Validate(p)) 145 146 _, err = a.ReplaceInstances(p, []string{"foo"}, []placement.Instance{i1}) 147 assert.Error(t, err) 148 assert.Contains(t, err.Error(), "instance foo does not exist in placement") 149 } 150 151 func TestMirrorAddAndRevertBeforeCutover(t *testing.T) { 152 i1 := newTestInstance("i1").SetShardSetID(1).SetWeight(1) 153 i2 := newTestInstance("i2").SetShardSetID(1).SetWeight(1) 154 i3 := newTestInstance("i3").SetShardSetID(2).SetWeight(2) 155 i4 := newTestInstance("i4").SetShardSetID(2).SetWeight(2) 156 i5 := newTestInstance("i5").SetShardSetID(3).SetWeight(3) 157 i6 := newTestInstance("i6").SetShardSetID(3).SetWeight(3) 158 instances := []placement.Instance{i1, i2, i3, i4} 159 160 numShards := 100 161 ids := make([]uint32, numShards) 162 for i := 0; i < len(ids); i++ { 163 ids[i] = uint32(i) 164 } 165 166 now := time.Now() 167 nowNanos := now.UnixNano() 168 shardCutoverTime := now.Add(time.Hour).UnixNano() 169 opts := placement.NewOptions(). 170 SetIsMirrored(true). 171 SetShardCutoverNanosFn(func() int64 { return shardCutoverTime }). 172 SetShardCutoffNanosFn(func() int64 { return shardCutoverTime }) 173 a := NewAlgorithm(opts) 174 p, err := a.InitialPlacement(instances, ids, 2) 175 assert.NoError(t, err) 176 assert.Equal(t, uint32(2), p.MaxShardSetID()) 177 assert.NoError(t, placement.Validate(p)) 178 179 p, _, err = a.MarkAllShardsAvailable(p) 180 assert.NoError(t, err) 181 assert.NoError(t, placement.Validate(p)) 182 183 p1, err := a.AddInstances(p, []placement.Instance{i5, i6}) 184 assert.NoError(t, err) 185 assert.Equal(t, uint32(3), p1.MaxShardSetID()) 186 assert.NoError(t, placement.Validate(p1)) 187 assert.True(t, globalChecker.allInitializing(p1, []string{"i5", "i6"}, nowNanos)) 188 189 p2, err := a.RemoveInstances(p1, []string{"i5", "i6"}) 190 assert.NoError(t, err) 191 assert.Equal(t, uint32(3), p2.MaxShardSetID()) 192 assert.NoError(t, placement.Validate(p2)) 193 _, ok := p2.Instance("i5") 194 assert.False(t, ok) 195 _, ok = p2.Instance("i6") 196 assert.False(t, ok) 197 198 assert.NoError(t, err) 199 assert.Equal(t, p.SetMaxShardSetID(3), p2) 200 } 201 202 func TestMirrorAddMultiplePairs(t *testing.T) { 203 i1 := newTestInstance("i1").SetShardSetID(1).SetWeight(1) 204 i2 := newTestInstance("i2").SetShardSetID(1).SetWeight(1) 205 i3 := newTestInstance("i3").SetShardSetID(2).SetWeight(2) 206 i4 := newTestInstance("i4").SetShardSetID(2).SetWeight(2) 207 i5 := newTestInstance("i5").SetShardSetID(3).SetWeight(3) 208 i6 := newTestInstance("i6").SetShardSetID(3).SetWeight(3) 209 i7 := newTestInstance("i7").SetShardSetID(4).SetWeight(3) 210 i8 := newTestInstance("i8").SetShardSetID(4).SetWeight(3) 211 instances := []placement.Instance{i1, i2, i3, i4} 212 213 numShards := 100 214 ids := make([]uint32, numShards) 215 for i := 0; i < len(ids); i++ { 216 ids[i] = uint32(i) 217 } 218 219 now := time.Now() 220 nowNanos := now.UnixNano() 221 shardCutoverTime := now.Add(time.Hour).UnixNano() 222 opts := placement.NewOptions(). 223 SetIsMirrored(true). 224 SetShardCutoverNanosFn(func() int64 { return shardCutoverTime }). 225 SetShardCutoffNanosFn(func() int64 { return shardCutoverTime }) 226 a := NewAlgorithm(opts) 227 p, err := a.InitialPlacement(instances, ids, 2) 228 assert.NoError(t, err) 229 assert.Equal(t, uint32(2), p.MaxShardSetID()) 230 assert.NoError(t, placement.Validate(p)) 231 232 p, _, err = a.MarkAllShardsAvailable(p) 233 assert.NoError(t, err) 234 235 p1, err := a.AddInstances(p, []placement.Instance{i5, i6, i7, i8}) 236 assert.NoError(t, err) 237 assert.Equal(t, uint32(4), p1.MaxShardSetID()) 238 assert.NoError(t, placement.Validate(p1)) 239 assert.True(t, globalChecker.allInitializing(p1, []string{"i5", "i6", "i7", "i8"}, nowNanos)) 240 i5, ok := p1.Instance("i5") 241 assert.True(t, ok) 242 i6, ok = p1.Instance("i6") 243 assert.True(t, ok) 244 assertInstancesArePeers(t, i5, i6) 245 i7, ok = p1.Instance("i7") 246 assert.True(t, ok) 247 i8, ok = p1.Instance("i8") 248 assert.True(t, ok) 249 assertInstancesArePeers(t, i7, i8) 250 251 // Removing all initializing nodes will trigger the revert path. 252 p2, err := a.RemoveInstances(p1.Clone(), []string{"i5", "i6", "i7", "i8"}) 253 assert.NoError(t, err) 254 assert.Equal(t, uint32(4), p2.MaxShardSetID()) 255 assert.NoError(t, placement.Validate(p2)) 256 _, ok = p2.Instance("i5") 257 assert.False(t, ok) 258 _, ok = p2.Instance("i7") 259 assert.False(t, ok) 260 261 assert.NoError(t, err) 262 assert.Equal(t, p.SetMaxShardSetID(4), p2) 263 264 // Removing part of the initializing nodes will not trigger the revert path 265 // and will only do a normal revert. 266 p3, err := a.RemoveInstances(p1.Clone(), []string{"i7", "i8"}) 267 assert.NoError(t, err) 268 assert.Equal(t, uint32(4), p3.MaxShardSetID()) 269 assert.NoError(t, placement.Validate(p3)) 270 _, ok = p3.Instance("i5") 271 assert.True(t, ok) 272 newI7, ok := p3.Instance("i7") 273 assert.True(t, ok) 274 assert.True(t, newI7.IsLeaving()) 275 276 assert.NoError(t, err) 277 assert.Equal(t, p.SetMaxShardSetID(4), p2) 278 } 279 280 func TestMirrorAddMultiplePairsAndPartialRevert(t *testing.T) { 281 i1 := newTestInstance("i1").SetShardSetID(1).SetWeight(1) 282 i2 := newTestInstance("i2").SetShardSetID(1).SetWeight(1) 283 i3 := newTestInstance("i3").SetShardSetID(2).SetWeight(2) 284 i4 := newTestInstance("i4").SetShardSetID(2).SetWeight(2) 285 i5 := newTestInstance("i5").SetShardSetID(3).SetWeight(3) 286 i6 := newTestInstance("i6").SetShardSetID(3).SetWeight(3) 287 i7 := newTestInstance("i7").SetShardSetID(4).SetWeight(3) 288 i8 := newTestInstance("i8").SetShardSetID(4).SetWeight(3) 289 instances := []placement.Instance{i1, i2, i3, i4} 290 291 numShards := 100 292 ids := make([]uint32, numShards) 293 for i := 0; i < len(ids); i++ { 294 ids[i] = uint32(i) 295 } 296 297 now := time.Now() 298 nowNanos := now.UnixNano() 299 shardCutoverTime := now.Add(time.Hour).UnixNano() 300 opts := placement.NewOptions(). 301 SetIsMirrored(true). 302 SetShardCutoverNanosFn(func() int64 { return shardCutoverTime }). 303 SetShardCutoffNanosFn(func() int64 { return shardCutoverTime }) 304 a := NewAlgorithm(opts) 305 p, err := a.InitialPlacement(instances, ids, 2) 306 require.NoError(t, err) 307 assert.Equal(t, uint32(2), p.MaxShardSetID()) 308 assert.NoError(t, placement.Validate(p)) 309 310 p, _, err = a.MarkAllShardsAvailable(p) 311 require.NoError(t, err) 312 313 p1, err := a.AddInstances(p, []placement.Instance{i5, i6, i7, i8}) 314 require.NoError(t, err) 315 assert.Equal(t, uint32(4), p1.MaxShardSetID()) 316 require.NoError(t, placement.Validate(p1)) 317 assert.True(t, globalChecker.allInitializing(p1, []string{"i5", "i6", "i7", "i8"}, nowNanos)) 318 i5, ok := p1.Instance("i5") 319 assert.True(t, ok) 320 i6, ok = p1.Instance("i6") 321 assert.True(t, ok) 322 assertInstancesArePeers(t, i5, i6) 323 i7, ok = p1.Instance("i7") 324 assert.True(t, ok) 325 i8, ok = p1.Instance("i8") 326 assert.True(t, ok) 327 assertInstancesArePeers(t, i7, i8) 328 329 // Removing instances that are not peers and are pending add must fail. 330 _, err = a.RemoveInstances(p1.Clone(), []string{"i5", "i7"}) 331 assert.Error(t, err) 332 } 333 334 func TestMirrorAddAndRevertAfterCutover(t *testing.T) { 335 i1 := newTestInstance("i1").SetShardSetID(1).SetWeight(1) 336 i2 := newTestInstance("i2").SetShardSetID(1).SetWeight(1) 337 i3 := newTestInstance("i3").SetShardSetID(2).SetWeight(2) 338 i4 := newTestInstance("i4").SetShardSetID(2).SetWeight(2) 339 i5 := newTestInstance("i5").SetShardSetID(3).SetWeight(3) 340 i6 := newTestInstance("i6").SetShardSetID(3).SetWeight(3) 341 instances := []placement.Instance{i1, i2, i3, i4} 342 343 numShards := 100 344 ids := make([]uint32, numShards) 345 for i := 0; i < len(ids); i++ { 346 ids[i] = uint32(i) 347 } 348 349 now := time.Now() 350 nowNanos := now.UnixNano() 351 shardCutoverTime := now.Add(-time.Hour).UnixNano() 352 a := NewAlgorithm(placement.NewOptions(). 353 SetIsMirrored(true). 354 SetShardCutoverNanosFn(func() int64 { return shardCutoverTime }). 355 SetShardCutoffNanosFn(func() int64 { return shardCutoverTime })) 356 p, err := a.InitialPlacement(instances, ids, 2) 357 assert.Equal(t, uint32(2), p.MaxShardSetID()) 358 assert.NoError(t, err) 359 assert.NoError(t, placement.Validate(p)) 360 361 p1, err := a.AddInstances(p, []placement.Instance{i5, i6}) 362 assert.NoError(t, err) 363 assert.Equal(t, uint32(3), p1.MaxShardSetID()) 364 assert.NoError(t, placement.Validate(p1)) 365 assert.False(t, globalChecker.allInitializing(p1, []string{"i5", "i6"}, nowNanos)) 366 367 p2, err := a.RemoveInstances(p1, []string{"i5", "i6"}) 368 assert.NoError(t, err) 369 assert.Equal(t, uint32(3), p2.MaxShardSetID()) 370 assert.NoError(t, placement.Validate(p2)) 371 i5, ok := p2.Instance("i5") 372 assert.True(t, ok) 373 assert.True(t, i5.IsLeaving()) 374 i6, ok = p2.Instance("i6") 375 assert.True(t, ok) 376 assert.True(t, i6.IsLeaving()) 377 } 378 379 func TestMirrorRemoveAndRevertBeforeCutover(t *testing.T) { 380 i1 := newTestInstance("i1").SetShardSetID(1).SetWeight(1) 381 i2 := newTestInstance("i2").SetShardSetID(1).SetWeight(1) 382 i3 := newTestInstance("i3").SetShardSetID(2).SetWeight(2) 383 i4 := newTestInstance("i4").SetShardSetID(2).SetWeight(2) 384 i5 := newTestInstance("i5").SetShardSetID(3).SetWeight(3) 385 i6 := newTestInstance("i6").SetShardSetID(3).SetWeight(3) 386 instances := []placement.Instance{i1, i2, i3, i4, i5, i6} 387 388 numShards := 100 389 ids := make([]uint32, numShards) 390 for i := 0; i < len(ids); i++ { 391 ids[i] = uint32(i) 392 } 393 394 now := time.Now() 395 nowNanos := now.UnixNano() 396 shardCutoverTime := now.Add(time.Hour).UnixNano() 397 opts := placement.NewOptions(). 398 SetIsMirrored(true). 399 SetShardCutoverNanosFn(func() int64 { return shardCutoverTime }). 400 SetShardCutoffNanosFn(func() int64 { return shardCutoverTime }) 401 a := NewAlgorithm(opts) 402 p, err := a.InitialPlacement(instances, ids, 2) 403 assert.NoError(t, err) 404 assert.Equal(t, uint32(3), p.MaxShardSetID()) 405 assert.NoError(t, placement.Validate(p)) 406 407 p, _, err = a.MarkAllShardsAvailable(p) 408 assert.NoError(t, err) 409 410 p1, err := a.RemoveInstances(p, []string{"i5", "i6"}) 411 assert.NoError(t, err) 412 assert.Equal(t, uint32(3), p1.MaxShardSetID()) 413 assert.NoError(t, placement.Validate(p1)) 414 assert.True(t, globalChecker.allLeaving(p1, []placement.Instance{i5, i6}, nowNanos)) 415 416 p2, err := a.AddInstances(p1, []placement.Instance{i5, i6}) 417 assert.NoError(t, err) 418 assert.Equal(t, uint32(3), p2.MaxShardSetID()) 419 assert.NoError(t, placement.Validate(p2)) 420 i5, ok := p2.Instance("i5") 421 assert.True(t, ok) 422 assert.Equal(t, i5.Shards().NumShards(), i5.Shards().NumShardsForState(shard.Available)) 423 i6, ok = p2.Instance("i6") 424 assert.True(t, ok) 425 assert.Equal(t, i6.Shards().NumShards(), i6.Shards().NumShardsForState(shard.Available)) 426 427 assert.NoError(t, err) 428 assert.Equal(t, p, p2) 429 } 430 431 func TestMirrorRemoveAndRevertAfterCutover(t *testing.T) { 432 i1 := newTestInstance("i1").SetShardSetID(1).SetWeight(1) 433 i2 := newTestInstance("i2").SetShardSetID(1).SetWeight(1) 434 i3 := newTestInstance("i3").SetShardSetID(2).SetWeight(2) 435 i4 := newTestInstance("i4").SetShardSetID(2).SetWeight(2) 436 i5 := newTestInstance("i5").SetShardSetID(3).SetWeight(3) 437 i6 := newTestInstance("i6").SetShardSetID(3).SetWeight(3) 438 instances := []placement.Instance{i1, i2, i3, i4, i5, i6} 439 440 numShards := 10 441 ids := make([]uint32, numShards) 442 for i := 0; i < len(ids); i++ { 443 ids[i] = uint32(i) 444 } 445 446 now := time.Now() 447 nowNanos := now.UnixNano() 448 shardCutoverTime := now.Add(-time.Hour).UnixNano() 449 a := NewAlgorithm(placement.NewOptions(). 450 SetIsMirrored(true). 451 SetShardCutoverNanosFn(func() int64 { return shardCutoverTime }). 452 SetShardCutoffNanosFn(func() int64 { return shardCutoverTime })) 453 p, err := a.InitialPlacement(instances, ids, 2) 454 assert.NoError(t, err) 455 assert.Equal(t, uint32(3), p.MaxShardSetID()) 456 assert.NoError(t, placement.Validate(p)) 457 458 p1, err := a.RemoveInstances(p, []string{"i5", "i6"}) 459 assert.NoError(t, err) 460 assert.NoError(t, placement.Validate(p1)) 461 assert.Equal(t, uint32(3), p1.MaxShardSetID()) 462 assert.False(t, globalChecker.allLeaving(p1, []placement.Instance{i5, i6}, nowNanos)) 463 464 p2, err := a.AddInstances(p1, []placement.Instance{i5.SetShards(shard.NewShards(nil)), i6.SetShards(shard.NewShards(nil))}) 465 assert.NoError(t, err) 466 assert.NoError(t, placement.Validate(p2)) 467 assert.Equal(t, uint32(3), p2.MaxShardSetID()) 468 469 i5, ok := p2.Instance("i5") 470 assert.True(t, ok) 471 assert.True(t, i5.IsInitializing()) 472 i6, ok = p2.Instance("i6") 473 assert.True(t, ok) 474 assert.True(t, i6.IsInitializing()) 475 } 476 477 func TestMirrorReplaceAndRevertBeforeCutover(t *testing.T) { 478 i1 := newTestInstance("i1").SetShardSetID(1).SetWeight(1) 479 i2 := newTestInstance("i2").SetShardSetID(1).SetWeight(1) 480 i3 := newTestInstance("i3").SetShardSetID(2).SetWeight(2) 481 i4 := newTestInstance("i4").SetShardSetID(2).SetWeight(2) 482 i5 := newTestInstance("i5").SetShardSetID(2).SetWeight(2) 483 instances := []placement.Instance{i1, i2, i3, i4} 484 485 numShards := 12 486 ids := make([]uint32, numShards) 487 for i := 0; i < len(ids); i++ { 488 ids[i] = uint32(i) 489 } 490 491 now := time.Now() 492 nowNanos := now.UnixNano() 493 shardCutoverTime := now.Add(time.Hour).UnixNano() 494 a := NewAlgorithm(placement.NewOptions(). 495 SetIsMirrored(true). 496 SetShardCutoverNanosFn(func() int64 { return shardCutoverTime }). 497 SetShardCutoffNanosFn(func() int64 { return shardCutoverTime })) 498 p, err := a.InitialPlacement(instances, ids, 2) 499 assert.NoError(t, err) 500 assert.NoError(t, placement.Validate(p)) 501 assert.Equal(t, uint32(2), p.MaxShardSetID()) 502 503 p1, err := a.ReplaceInstances(p, []string{"i4"}, []placement.Instance{i5}) 504 assert.NoError(t, err) 505 assert.NoError(t, placement.Validate(p1)) 506 assert.Equal(t, uint32(2), p1.MaxShardSetID()) 507 assert.True(t, globalChecker.allLeaving(p1, []placement.Instance{i4}, nowNanos)) 508 assert.True(t, localChecker.allInitializing(p1, []string{"i5"}, nowNanos)) 509 510 p2, err := a.ReplaceInstances(p1, []string{"i5"}, []placement.Instance{i4}) 511 assert.NoError(t, err) 512 assert.NoError(t, placement.Validate(p2)) 513 assert.Equal(t, uint32(2), p2.MaxShardSetID()) 514 i4, ok := p2.Instance("i4") 515 assert.True(t, ok) 516 assert.Equal(t, i4.Shards().NumShards(), i4.Shards().NumShardsForState(shard.Available)) 517 _, ok = p2.Instance("i5") 518 assert.False(t, ok) 519 } 520 521 func TestMirrorReplaceAndRevertAfterCutover(t *testing.T) { 522 i1 := newTestInstance("i1").SetShardSetID(1).SetWeight(1) 523 i2 := newTestInstance("i2").SetShardSetID(1).SetWeight(1) 524 i3 := newTestInstance("i3").SetShardSetID(2).SetWeight(2) 525 i4 := newTestInstance("i4").SetShardSetID(2).SetWeight(2) 526 i5 := newTestInstance("i5").SetShardSetID(2).SetWeight(2) 527 instances := []placement.Instance{i1, i2, i3, i4} 528 529 numShards := 100 530 ids := make([]uint32, numShards) 531 for i := 0; i < len(ids); i++ { 532 ids[i] = uint32(i) 533 } 534 535 now := time.Now() 536 nowNanos := now.UnixNano() 537 shardCutoverTime := now.Add(-time.Hour).UnixNano() 538 a := NewAlgorithm(placement.NewOptions(). 539 SetIsMirrored(true). 540 SetShardCutoverNanosFn(func() int64 { return shardCutoverTime }). 541 SetShardCutoffNanosFn(func() int64 { return shardCutoverTime })) 542 p, err := a.InitialPlacement(instances, ids, 2) 543 assert.NoError(t, err) 544 assert.NoError(t, placement.Validate(p)) 545 assert.Equal(t, uint32(2), p.MaxShardSetID()) 546 547 p1, err := a.ReplaceInstances(p, []string{"i4"}, []placement.Instance{i5}) 548 assert.NoError(t, err) 549 assert.NoError(t, placement.Validate(p1)) 550 assert.Equal(t, uint32(2), p1.MaxShardSetID()) 551 assert.False(t, globalChecker.allLeaving(p1, []placement.Instance{i4}, nowNanos)) 552 assert.False(t, globalChecker.allInitializing(p1, []string{"i5"}, nowNanos)) 553 554 i4, ok := p1.Instance("i4") 555 assert.True(t, ok) 556 assert.True(t, i4.IsLeaving()) 557 ssI4 := i4.Shards() 558 i5, ok = p1.Instance("i5") 559 assert.True(t, ok) 560 assert.True(t, i5.IsInitializing()) 561 ssI5 := i5.Shards() 562 563 p2, err := a.ReplaceInstances(p1, []string{"i5"}, []placement.Instance{i4}) 564 assert.NoError(t, err) 565 assert.Equal(t, uint32(2), p2.MaxShardSetID()) 566 assert.NoError(t, placement.Validate(p2)) 567 568 i4, ok = p2.Instance("i4") 569 assert.True(t, ok) 570 assert.True(t, i4.IsInitializing()) 571 i5, ok = p2.Instance("i5") 572 assert.True(t, ok) 573 assert.True(t, i5.IsLeaving()) 574 575 assert.True(t, ssI4.Equals(i5.Shards())) 576 // Can't directly compare shards.Equals because the shards in ssI5 will be having "i4" 577 // as sourceID and shards in i4 will be having "i5" as sourceID. 578 assert.Equal(t, ssI5.NumShardsForState(shard.Initializing), i4.Shards().NumShardsForState(shard.Initializing)) 579 } 580 581 func TestMirrorMultipleNonOverlappingReplaces(t *testing.T) { 582 i1 := newTestInstance("i1").SetShardSetID(1) 583 i2 := newTestInstance("i2").SetShardSetID(1) 584 i3 := newTestInstance("i3").SetShardSetID(2) 585 i4 := newTestInstance("i4").SetShardSetID(2) 586 instances := []placement.Instance{i1, i2, i3, i4} 587 588 numShards := 100 589 ids := make([]uint32, numShards) 590 for i := 0; i < len(ids); i++ { 591 ids[i] = uint32(i) 592 } 593 594 now := time.Now() 595 nowNanos := now.UnixNano() 596 shardCutoverTime := now.Add(time.Hour).UnixNano() 597 a := NewAlgorithm(placement.NewOptions(). 598 SetIsMirrored(true). 599 SetShardCutoverNanosFn(func() int64 { return shardCutoverTime }). 600 SetShardCutoffNanosFn(func() int64 { return shardCutoverTime })) 601 p, err := a.InitialPlacement(instances, ids, 2) 602 assert.NoError(t, err) 603 assert.NoError(t, placement.Validate(p)) 604 assert.Equal(t, uint32(2), p.MaxShardSetID()) 605 606 p, _, err = a.MarkAllShardsAvailable(p) 607 assert.NoError(t, err) 608 609 // First replace: i1 replaced by i5. 610 i5 := newTestInstance("i5").SetShardSetID(1) 611 p1, err := a.ReplaceInstances(p, []string{"i1"}, []placement.Instance{i5}) 612 require.NoError(t, err) 613 require.NoError(t, placement.Validate(p1)) 614 assert.Equal(t, uint32(2), p1.MaxShardSetID()) 615 assert.True(t, globalChecker.allLeaving(p1, []placement.Instance{i1}, nowNanos)) 616 assert.True(t, globalChecker.allInitializing(p1, []string{"i5"}, nowNanos)) 617 618 // Second replace that does not overlap with first replace: i3 replaced by i6. 619 i6 := newTestInstance("i6").SetShardSetID(2) 620 p2, err := a.ReplaceInstances(p1, []string{"i3"}, []placement.Instance{i6}) 621 require.NoError(t, err) 622 require.NoError(t, placement.Validate(p2)) 623 624 assert.Equal(t, uint32(2), p2.MaxShardSetID()) 625 assert.True(t, globalChecker.allLeaving(p2, []placement.Instance{i3, i1}, nowNanos)) 626 assert.True(t, globalChecker.allInitializing(p2, []string{"i6", "i5"}, nowNanos)) 627 } 628 629 func TestMirrorReplacesCannotOverlap(t *testing.T) { 630 i1 := newTestInstance("i1").SetShardSetID(1) 631 i2 := newTestInstance("i2").SetShardSetID(1) 632 i3 := newTestInstance("i3").SetShardSetID(2) 633 i4 := newTestInstance("i4").SetShardSetID(2) 634 instances := []placement.Instance{i1, i2, i3, i4} 635 636 numShards := 100 637 ids := make([]uint32, numShards) 638 for i := 0; i < len(ids); i++ { 639 ids[i] = uint32(i) 640 } 641 642 now := time.Now() 643 nowNanos := now.UnixNano() 644 shardCutoverTime := now.Add(2 * time.Hour).UnixNano() 645 a := NewAlgorithm(placement.NewOptions(). 646 SetIsMirrored(true). 647 SetShardCutoverNanosFn(func() int64 { return shardCutoverTime }). 648 SetShardCutoffNanosFn(func() int64 { return shardCutoverTime })) 649 p, err := a.InitialPlacement(instances, ids, 2) 650 require.NoError(t, err) 651 require.NoError(t, placement.Validate(p)) 652 assert.Equal(t, uint32(2), p.MaxShardSetID()) 653 654 p, _, err = a.MarkAllShardsAvailable(p) 655 require.NoError(t, err) 656 657 a = NewAlgorithm(placement.NewOptions(). 658 SetIsMirrored(true). 659 SetShardCutoverNanosFn(func() int64 { return shardCutoverTime }). 660 SetShardCutoffNanosFn(func() int64 { return shardCutoverTime }). 661 SetIsShardCutoffFn(func(shard.Shard) error { return errors.New("Not cutoff") }). 662 SetIsShardCutoverFn(func(shard.Shard) error { return errors.New("Not cutover") })) 663 664 // First replace: i1 replaced by i5. 665 i5 := newTestInstance("i5").SetShardSetID(1) 666 p1, err := a.ReplaceInstances(p, []string{"i1"}, []placement.Instance{i5}) 667 require.NoError(t, err) 668 require.NoError(t, placement.Validate(p1)) 669 assert.Equal(t, uint32(2), p1.MaxShardSetID()) 670 assert.True(t, globalChecker.allLeaving(p1, []placement.Instance{i1}, nowNanos)) 671 assert.True(t, globalChecker.allInitializing(p1, []string{"i5"}, nowNanos)) 672 673 // Second replace: i5 replaced by i6, is overlapping with first replace 674 // because i5 has initializing shards that are not cutover. 675 i6 := newTestInstance("i6").SetShardSetID(1) 676 p2, err := a.ReplaceInstances(p1, []string{"i5"}, []placement.Instance{i6}) 677 require.Error(t, err) 678 assert.Contains(t, err.Error(), "Not cutover") 679 assert.Nil(t, p2) 680 681 assert.Equal(t, uint32(2), p1.MaxShardSetID()) 682 assert.True(t, globalChecker.allLeaving(p1, []placement.Instance{i1}, nowNanos)) 683 assert.True(t, globalChecker.allInitializing(p1, []string{"i5"}, nowNanos)) 684 685 // Third replace: i1 replaced by i6, is overlapping with first replace 686 // because i1 has a pending replacement peer i5 with initializing shards that are not cutover. 687 p2, err = a.ReplaceInstances(p1, []string{"i1"}, []placement.Instance{i6}) 688 require.Error(t, err) 689 assert.Contains(t, err.Error(), "Not cutover") 690 assert.Nil(t, p2) 691 692 assert.Equal(t, uint32(2), p1.MaxShardSetID()) 693 assert.True(t, globalChecker.allLeaving(p1, []placement.Instance{i1}, nowNanos)) 694 assert.True(t, globalChecker.allInitializing(p1, []string{"i5"}, nowNanos)) 695 696 // Fourth replace: i3 replaced by i6, is not overlapping with first replace and must go through. 697 i6.SetShardSetID(i3.ShardSetID()) 698 p2, err = a.ReplaceInstances(p1, []string{"i3"}, []placement.Instance{i6}) 699 require.NoError(t, err) 700 require.NoError(t, placement.Validate(p2)) 701 assert.Equal(t, uint32(2), p2.MaxShardSetID()) 702 assert.True(t, globalChecker.allLeaving(p2, []placement.Instance{i1, i3}, nowNanos)) 703 assert.True(t, globalChecker.allInitializing(p2, []string{"i5", "i6"}, nowNanos)) 704 } 705 706 func TestMirrorRevertOfReplaceMustMatch(t *testing.T) { 707 i1 := newTestInstance("i1").SetShardSetID(1) 708 i2 := newTestInstance("i2").SetShardSetID(1) 709 i3 := newTestInstance("i3").SetShardSetID(2) 710 i4 := newTestInstance("i4").SetShardSetID(2) 711 instances := []placement.Instance{i1, i2, i3, i4} 712 713 numShards := 100 714 ids := make([]uint32, numShards) 715 for i := 0; i < len(ids); i++ { 716 ids[i] = uint32(i) 717 } 718 719 now := time.Now() 720 nowNanos := now.UnixNano() 721 shardCutoverTime := now.Add(time.Hour).UnixNano() 722 a := NewAlgorithm(placement.NewOptions(). 723 SetIsMirrored(true). 724 SetShardCutoverNanosFn(func() int64 { return shardCutoverTime }). 725 SetShardCutoffNanosFn(func() int64 { return shardCutoverTime })) 726 p, err := a.InitialPlacement(instances, ids, 2) 727 require.NoError(t, err) 728 require.NoError(t, placement.Validate(p)) 729 assert.Equal(t, uint32(2), p.MaxShardSetID()) 730 731 p, _, err = a.MarkAllShardsAvailable(p) 732 assert.NoError(t, err) 733 734 // First replace: i1 replaced by i1a. 735 i1a := newTestInstance("i1a"). 736 SetShardSetID(i1.ShardSetID()). 737 SetWeight(i1.Weight()) 738 p1, err := a.ReplaceInstances(p, []string{"i1"}, []placement.Instance{i1a}) 739 require.NoError(t, err) 740 require.NoError(t, placement.Validate(p1)) 741 assert.Equal(t, uint32(2), p1.MaxShardSetID()) 742 assert.True(t, globalChecker.allLeaving(p1, []placement.Instance{i1}, nowNanos)) 743 assert.True(t, globalChecker.allInitializing(p1, []string{"i1a"}, nowNanos)) 744 745 // Second replace: i3 replaced by i3a. 746 i3a := newTestInstance("i3a"). 747 SetShardSetID(i3.ShardSetID()). 748 SetWeight(i3.Weight()) 749 p2, err := a.ReplaceInstances(p1, []string{"i3"}, []placement.Instance{i3a}) 750 require.NoError(t, err) 751 require.NoError(t, placement.Validate(p2)) 752 assert.Equal(t, uint32(2), p2.MaxShardSetID()) 753 assert.True(t, globalChecker.allLeaving(p2, []placement.Instance{i1, i3}, nowNanos)) 754 assert.True(t, globalChecker.allInitializing(p2, []string{"i1a", "i3a"}, nowNanos)) 755 756 t.Run("revert_of_non_matching_replace_must_fail", func(t *testing.T) { 757 newI1, ok := p2.Instance("i1") 758 assert.True(t, ok) 759 _, err = a.ReplaceInstances(p2, []string{"i3a"}, []placement.Instance{newI1}) 760 assert.Error(t, err) 761 762 newI3, ok := p2.Instance("i3") 763 assert.True(t, ok) 764 _, err = a.ReplaceInstances(p2, []string{"i1a"}, []placement.Instance{newI3}) 765 assert.Error(t, err) 766 }) 767 768 t.Run("revert_of_matching_replace_must_succeed", func(t *testing.T) { 769 newI3, ok := p2.Instance("i3") 770 assert.True(t, ok) 771 p3, err := a.ReplaceInstances(p2, []string{"i3a"}, []placement.Instance{newI3}) 772 require.NoError(t, err) 773 _, ok = p3.Instance("i3a") 774 assert.False(t, ok) 775 _, ok = p3.Instance("i3") 776 assert.True(t, ok) 777 assert.True(t, localChecker.allAvailable(p3, []string{"i3"}, nowNanos)) 778 779 newI1, ok := p2.Instance("i1") 780 assert.True(t, ok) 781 p4, err := a.ReplaceInstances(p3, []string{"i1a"}, []placement.Instance{newI1}) 782 require.NoError(t, err) 783 _, ok = p4.Instance("i1a") 784 assert.False(t, ok) 785 _, ok = p4.Instance("i1") 786 assert.True(t, ok) 787 assert.True(t, localChecker.allAvailable(p4, []string{"i1"}, nowNanos)) 788 }) 789 } 790 791 func TestMirrorReplaceDuringPendingAddMustFail(t *testing.T) { 792 i1 := newTestInstance("i1").SetShardSetID(1) 793 i2 := newTestInstance("i2").SetShardSetID(1) 794 i3 := newTestInstance("i3").SetShardSetID(2) 795 i4 := newTestInstance("i4").SetShardSetID(2) 796 instances := []placement.Instance{i1, i2, i3, i4} 797 798 numShards := 100 799 ids := make([]uint32, numShards) 800 for i := 0; i < len(ids); i++ { 801 ids[i] = uint32(i) 802 } 803 804 now := time.Now() 805 nowNanos := now.UnixNano() 806 shardCutoverTime := now.Add(time.Hour).UnixNano() 807 a := NewAlgorithm(placement.NewOptions(). 808 SetIsMirrored(true). 809 SetShardCutoverNanosFn(func() int64 { return shardCutoverTime }). 810 SetShardCutoffNanosFn(func() int64 { return shardCutoverTime })) 811 p, err := a.InitialPlacement(instances, ids, 2) 812 require.NoError(t, err) 813 require.NoError(t, placement.Validate(p)) 814 assert.Equal(t, uint32(2), p.MaxShardSetID()) 815 816 p, _, err = a.MarkAllShardsAvailable(p) 817 require.NoError(t, err) 818 819 a = NewAlgorithm(placement.NewOptions(). 820 SetIsMirrored(true). 821 SetShardCutoverNanosFn(func() int64 { return shardCutoverTime }). 822 SetShardCutoffNanosFn(func() int64 { return shardCutoverTime }). 823 SetIsShardCutoffFn(func(shard.Shard) error { return errors.New("Not cutoff") }). 824 SetIsShardCutoverFn(func(shard.Shard) error { return errors.New("Not cutover") })) 825 826 // Add a new pair 827 i5 := newTestInstance("i5").SetShardSetID(3) 828 i6 := newTestInstance("i6").SetShardSetID(3) 829 p1, err := a.AddInstances(p, []placement.Instance{i5, i6}) 830 require.NoError(t, err) 831 require.NoError(t, placement.Validate(p1)) 832 833 i1, ok := p1.Instance("i1") 834 assert.True(t, ok) 835 assert.True(t, i1.Shards().NumShardsForState(shard.Leaving) > 0) 836 assert.True(t, i1.Shards().NumShardsForState(shard.Available) > i1.Shards().NumShardsForState(shard.Leaving)) 837 838 assert.Equal(t, uint32(3), p1.MaxShardSetID()) 839 assert.True(t, globalChecker.allInitializing(p1, []string{"i5", "i6"}, nowNanos)) 840 i5, ok = p1.Instance("i5") 841 assert.True(t, ok) 842 i6, ok = p1.Instance("i6") 843 assert.True(t, ok) 844 assertInstancesArePeers(t, i5, i6) 845 846 // Replace of instance that is pending add must fail. 847 i5a := newTestInstance("i5a"). 848 SetShardSetID(i5.ShardSetID()). 849 SetWeight(i5.Weight()) 850 _, err = a.ReplaceInstances(p1, []string{"i5"}, []placement.Instance{i5a}) 851 require.Error(t, err) 852 assert.Contains(t, err.Error(), "Not cutover") 853 854 // Replace of instance that has some shards available 855 // and some shards leaving to newly added instance must fail. 856 i1, ok = p1.Instance("i1") 857 assert.True(t, ok) 858 i1ShardsAvailableBeforeReplace := i1.Shards().NumShardsForState(shard.Available) 859 assert.True(t, i1ShardsAvailableBeforeReplace > 0) 860 861 i1a := newTestInstance("i1a"). 862 SetShardSetID(i1.ShardSetID()). 863 SetWeight(i1.Weight()) 864 _, err = a.ReplaceInstances(p1, []string{"i1"}, []placement.Instance{i1a}) 865 require.Error(t, err) 866 assert.Contains(t, err.Error(), "replaced instances must have all their shards available") 867 } 868 869 func TestMirrorReplaceDuringPendingRemoveMustFail(t *testing.T) { 870 i1 := newTestInstance("i1").SetShardSetID(1) 871 i2 := newTestInstance("i2").SetShardSetID(1) 872 i3 := newTestInstance("i3").SetShardSetID(2) 873 i4 := newTestInstance("i4").SetShardSetID(2) 874 instances := []placement.Instance{i1, i2, i3, i4} 875 876 numShards := 100 877 ids := make([]uint32, numShards) 878 for i := 0; i < len(ids); i++ { 879 ids[i] = uint32(i) 880 } 881 882 now := time.Now() 883 nowNanos := now.UnixNano() 884 shardCutoverTime := now.Add(time.Hour).UnixNano() 885 a := NewAlgorithm(placement.NewOptions(). 886 SetIsMirrored(true). 887 SetShardCutoverNanosFn(func() int64 { return shardCutoverTime }). 888 SetShardCutoffNanosFn(func() int64 { return shardCutoverTime })) 889 p, err := a.InitialPlacement(instances, ids, 2) 890 require.NoError(t, err) 891 require.NoError(t, placement.Validate(p)) 892 assert.Equal(t, uint32(2), p.MaxShardSetID()) 893 894 p, _, err = a.MarkAllShardsAvailable(p) 895 assert.NoError(t, err) 896 897 a = NewAlgorithm(placement.NewOptions(). 898 SetIsMirrored(true). 899 SetShardCutoverNanosFn(func() int64 { return shardCutoverTime }). 900 SetShardCutoffNanosFn(func() int64 { return shardCutoverTime }). 901 SetIsShardCutoffFn(func(shard.Shard) error { return errors.New("Not cutoff") }). 902 SetIsShardCutoverFn(func(shard.Shard) error { return errors.New("Not cutover") })) 903 904 p1, err := a.RemoveInstances(p, []string{"i3", "i4"}) 905 require.NoError(t, err) 906 require.NoError(t, placement.Validate(p1)) 907 i3, ok := p1.Instance("i3") 908 assert.True(t, ok) 909 i4, ok = p1.Instance("i4") 910 assert.True(t, ok) 911 assert.True(t, globalChecker.allLeaving(p1, []placement.Instance{i3, i4}, nowNanos)) 912 i1, ok = p1.Instance("i1") 913 assert.True(t, ok) 914 assert.True(t, i1.Shards().NumShardsForState(shard.Initializing) > 0) 915 assert.Equal(t, i1.Shards().NumShardsForState(shard.Available), i1.Shards().NumShardsForState(shard.Initializing)) 916 917 // Replace of instance that is pending remove must fail. 918 i3a := newTestInstance("i3a"). 919 SetShardSetID(i3.ShardSetID()). 920 SetWeight(i3.Weight()) 921 _, err = a.ReplaceInstances(p1, []string{"i3"}, []placement.Instance{i3a}) 922 require.Error(t, err) 923 assert.Contains(t, err.Error(), "replaced instances must have all their shards available") 924 925 // Replace of instance that has some shards available 926 // and some shards initializing from another isntance pending remove must fail. 927 i1, ok = p1.Instance("i1") 928 assert.True(t, ok) 929 i1ShardsAvailableBeforeReplace := i1.Shards().NumShardsForState(shard.Available) 930 assert.True(t, i1ShardsAvailableBeforeReplace > 0) 931 932 i1a := newTestInstance("i1a"). 933 SetShardSetID(i1.ShardSetID()). 934 SetWeight(i1.Weight()) 935 _, err = a.ReplaceInstances(p1, []string{"i1"}, []placement.Instance{i1a}) 936 require.Error(t, err) 937 assert.Contains(t, err.Error(), "Not cutover") 938 } 939 940 func TestMirrorInitError(t *testing.T) { 941 i1 := newTestInstance("i1").SetShardSetID(1).SetWeight(1) 942 i2 := newTestInstance("i2").SetShardSetID(1).SetWeight(1) 943 i3 := newTestInstance("i3").SetShardSetID(2).SetWeight(2) 944 instances := []placement.Instance{i1, i2, i3} 945 946 numShards := 100 947 ids := make([]uint32, numShards) 948 for i := 0; i < len(ids); i++ { 949 ids[i] = uint32(i) 950 } 951 952 a := NewAlgorithm(placement.NewOptions().SetIsMirrored(true)) 953 _, err := a.InitialPlacement(instances, ids, 2) 954 assert.Error(t, err) 955 } 956 957 func TestMirrorAddInstancesError(t *testing.T) { 958 i1 := newTestInstance("i1").SetShardSetID(1).SetWeight(1) 959 i2 := newTestInstance("i2").SetShardSetID(1).SetWeight(1) 960 i3 := newTestInstance("i3").SetShardSetID(2).SetWeight(2) 961 i4 := newTestInstance("i4").SetShardSetID(2).SetWeight(2) 962 i5 := newTestInstance("i5").SetShardSetID(3).SetWeight(3) 963 i6 := newTestInstance("i6").SetShardSetID(3).SetWeight(3) 964 instances := []placement.Instance{i1, i2, i3, i4} 965 966 numShards := 100 967 ids := make([]uint32, numShards) 968 for i := 0; i < len(ids); i++ { 969 ids[i] = uint32(i) 970 } 971 972 a := NewAlgorithm(placement.NewOptions().SetIsMirrored(true)) 973 p, err := a.InitialPlacement(instances, ids, 2) 974 assert.NoError(t, err) 975 assert.NoError(t, placement.Validate(p)) 976 assert.Equal(t, uint32(2), p.MaxShardSetID()) 977 978 _, err = a.AddInstances(p.Clone().SetIsMirrored(false), []placement.Instance{i5, i6}) 979 assert.Error(t, err) 980 assert.Equal(t, uint32(2), p.MaxShardSetID()) 981 982 _, err = a.AddInstances(p.Clone().SetReplicaFactor(1), []placement.Instance{i5, i6}) 983 assert.Error(t, err) 984 assert.Equal(t, uint32(2), p.MaxShardSetID()) 985 986 // Allow adding back leaving instances. 987 p, err = a.RemoveInstances(p, []string{i3.ID(), i4.ID()}) 988 assert.NoError(t, err) 989 assert.NoError(t, placement.Validate(p)) 990 assert.Equal(t, uint32(2), p.MaxShardSetID()) 991 992 p, err = a.AddInstances(p, []placement.Instance{ 993 newTestInstance("i3").SetShardSetID(2).SetWeight(2), 994 newTestInstance("i4").SetShardSetID(2).SetWeight(2), 995 }) 996 assert.NoError(t, err) 997 assert.NoError(t, placement.Validate(p)) 998 assert.Equal(t, uint32(2), p.MaxShardSetID()) 999 1000 // Duplicated shardset id. 1001 _, err = a.AddInstances(p, []placement.Instance{ 1002 newTestInstance("i7").SetShardSetID(1).SetWeight(3), 1003 newTestInstance("i7").SetShardSetID(1).SetWeight(3), 1004 }) 1005 assert.Error(t, err) 1006 assert.Equal(t, uint32(2), p.MaxShardSetID()) 1007 } 1008 1009 func TestMirrorRemoveInstancesError(t *testing.T) { 1010 i1 := newTestInstance("i1").SetShardSetID(1).SetWeight(1) 1011 i2 := newTestInstance("i2").SetShardSetID(1).SetWeight(1) 1012 i3 := newTestInstance("i3").SetShardSetID(2).SetWeight(2) 1013 i4 := newTestInstance("i4").SetShardSetID(2).SetWeight(2) 1014 instances := []placement.Instance{i1, i2, i3, i4} 1015 1016 numShards := 100 1017 ids := make([]uint32, numShards) 1018 for i := 0; i < len(ids); i++ { 1019 ids[i] = uint32(i) 1020 } 1021 1022 a := NewAlgorithm(placement.NewOptions().SetIsMirrored(true)) 1023 p, err := a.InitialPlacement(instances, ids, 2) 1024 assert.NoError(t, err) 1025 assert.NoError(t, placement.Validate(p)) 1026 assert.Equal(t, uint32(2), p.MaxShardSetID()) 1027 1028 _, err = a.RemoveInstances(p.SetIsMirrored(false), []string{"i1", "i2"}) 1029 assert.Error(t, err) 1030 assert.Equal(t, uint32(2), p.MaxShardSetID()) 1031 1032 _, err = a.RemoveInstances(p.SetReplicaFactor(1), []string{"i1", "i2"}) 1033 assert.Error(t, err) 1034 assert.Equal(t, uint32(2), p.MaxShardSetID()) 1035 1036 _, err = a.RemoveInstances(p, []string{"i1"}) 1037 assert.Error(t, err) 1038 assert.Equal(t, uint32(2), p.MaxShardSetID()) 1039 1040 _, err = a.RemoveInstances(p, []string{"bad"}) 1041 assert.Error(t, err) 1042 assert.Equal(t, uint32(2), p.MaxShardSetID()) 1043 } 1044 1045 func TestMirrorReplaceInstancesError(t *testing.T) { 1046 i1 := newTestInstance("i1").SetShardSetID(1).SetWeight(1) 1047 i2 := newTestInstance("i2").SetShardSetID(1).SetWeight(1) 1048 i3 := newTestInstance("i3").SetShardSetID(2).SetWeight(2) 1049 i4 := newTestInstance("i4").SetShardSetID(2).SetWeight(2) 1050 instances := []placement.Instance{i1, i2, i3, i4} 1051 1052 numShards := 100 1053 ids := make([]uint32, numShards) 1054 for i := 0; i < len(ids); i++ { 1055 ids[i] = uint32(i) 1056 } 1057 1058 a := NewAlgorithm(placement.NewOptions().SetIsMirrored(true)) 1059 p, err := a.InitialPlacement(instances, ids, 2) 1060 assert.NoError(t, err) 1061 assert.NoError(t, placement.Validate(p)) 1062 assert.Equal(t, uint32(2), p.MaxShardSetID()) 1063 1064 _, err = a.ReplaceInstances(p.SetIsMirrored(false), []string{"i1"}, []placement.Instance{ 1065 newTestInstance("i11").SetShardSetID(0).SetWeight(1), 1066 }) 1067 assert.Error(t, err) 1068 assert.Equal(t, uint32(2), p.MaxShardSetID()) 1069 1070 _, err = a.ReplaceInstances(p, []string{"i1"}, []placement.Instance{ 1071 newTestInstance("i11").SetShardSetID(0).SetWeight(1), 1072 newTestInstance("i12").SetShardSetID(0).SetWeight(1), 1073 }) 1074 assert.Error(t, err) 1075 assert.Equal(t, uint32(2), p.MaxShardSetID()) 1076 1077 _, err = a.ReplaceInstances(p, []string{"bad"}, []placement.Instance{ 1078 placement.NewInstance(). 1079 SetID("i11"). 1080 SetIsolationGroup("r1"). 1081 SetEndpoint("endpoint1"). 1082 SetShardSetID(0). 1083 SetWeight(1), 1084 }) 1085 assert.Error(t, err) 1086 assert.Equal(t, uint32(2), p.MaxShardSetID()) 1087 } 1088 1089 func TestMirrorReplaceWithLeavingShards(t *testing.T) { 1090 i1 := newTestInstance("i1"). 1091 SetShardSetID(1). 1092 SetWeight(1). 1093 SetShards(shard.NewShards([]shard.Shard{ 1094 shard.NewShard(0).SetState(shard.Leaving), 1095 shard.NewShard(1).SetState(shard.Available), 1096 })) 1097 i2 := newTestInstance("i2"). 1098 SetShardSetID(1). 1099 SetWeight(1). 1100 SetShards(shard.NewShards([]shard.Shard{ 1101 shard.NewShard(0).SetState(shard.Leaving), 1102 shard.NewShard(1).SetState(shard.Available), 1103 })) 1104 i3 := newTestInstance("i3"). 1105 SetShardSetID(2). 1106 SetWeight(2). 1107 SetShards(shard.NewShards([]shard.Shard{ 1108 shard.NewShard(0).SetState(shard.Initializing).SetSourceID("i1"), 1109 shard.NewShard(2).SetState(shard.Available), 1110 })) 1111 i4 := newTestInstance("i4"). 1112 SetShardSetID(2). 1113 SetWeight(2). 1114 SetShards(shard.NewShards([]shard.Shard{ 1115 shard.NewShard(0).SetState(shard.Initializing).SetSourceID("i2"), 1116 shard.NewShard(2).SetState(shard.Available), 1117 })) 1118 p := placement.NewPlacement(). 1119 SetReplicaFactor(2). 1120 SetShards([]uint32{0, 1, 2}). 1121 SetInstances([]placement.Instance{i1, i2, i3, i4}). 1122 SetIsMirrored(true). 1123 SetIsSharded(true). 1124 SetMaxShardSetID(2) 1125 1126 opts := placement.NewOptions().SetIsMirrored(true) 1127 a := NewAlgorithm(opts) 1128 1129 replaceI1 := newTestInstance("newI1").SetShardSetID(1).SetWeight(1) 1130 replaceI4 := newTestInstance("newI4").SetShardSetID(2).SetWeight(2) 1131 p2, err := a.ReplaceInstances(p, []string{"i1", "i4"}, []placement.Instance{replaceI1, replaceI4}) 1132 assert.NoError(t, err) 1133 assert.NoError(t, placement.Validate(p)) 1134 assert.Equal(t, uint32(2), p2.MaxShardSetID()) 1135 1136 _, ok := p2.Instance("i1") 1137 assert.True(t, ok) 1138 newI1, ok := p2.Instance("newI1") 1139 assert.True(t, ok) 1140 assert.Equal(t, replaceI1.SetShards( 1141 shard.NewShards([]shard.Shard{ 1142 shard.NewShard(1).SetState(shard.Initializing).SetSourceID("i1"), 1143 }), 1144 ), newI1) 1145 _, ok = p2.Instance("i4") 1146 assert.True(t, ok) 1147 newI4, ok := p2.Instance("newI4") 1148 assert.True(t, ok) 1149 assert.Equal(t, replaceI4.SetShards( 1150 shard.NewShards([]shard.Shard{ 1151 shard.NewShard(0).SetState(shard.Initializing).SetSourceID("i4"), 1152 shard.NewShard(2).SetState(shard.Initializing).SetSourceID("i4"), 1153 }), 1154 ), newI4) 1155 assert.NoError(t, placement.Validate(p2)) 1156 } 1157 1158 func TestIncompatibleWithMirroredAlgo(t *testing.T) { 1159 a := newMirroredAlgorithm(placement.NewOptions()) 1160 p := placement.NewPlacement() 1161 1162 err := a.IsCompatibleWith(p) 1163 assert.Error(t, err) 1164 assert.Equal(t, errIncompatibleWithMirrorAlgo, err) 1165 1166 err = a.IsCompatibleWith(p.SetIsSharded(true)) 1167 assert.Error(t, err) 1168 assert.Equal(t, errIncompatibleWithMirrorAlgo, err) 1169 1170 err = a.IsCompatibleWith(p.SetIsSharded(true).SetIsMirrored(true)) 1171 assert.Nil(t, err) 1172 } 1173 1174 func TestGroupInstanceByShardSetID(t *testing.T) { 1175 i1 := placement.NewInstance(). 1176 SetID("i1"). 1177 SetIsolationGroup("r1"). 1178 SetEndpoint("endpoint1"). 1179 SetShardSetID(1). 1180 SetWeight(1). 1181 SetShards(shard.NewShards([]shard.Shard{ 1182 shard.NewShard(0).SetState(shard.Available), 1183 })) 1184 i2 := placement.NewInstance(). 1185 SetID("i2"). 1186 SetIsolationGroup("r2"). 1187 SetEndpoint("endpoint2"). 1188 SetShardSetID(1). 1189 SetWeight(1). 1190 SetShards(shard.NewShards([]shard.Shard{ 1191 shard.NewShard(0).SetState(shard.Available), 1192 })) 1193 1194 res, err := groupInstancesByShardSetID([]placement.Instance{i1, i2}, 2) 1195 assert.NoError(t, err) 1196 assert.Equal(t, 1, len(res)) 1197 assert.Equal(t, placement.NewInstance(). 1198 SetID("1"). 1199 SetIsolationGroup("1"). 1200 SetShardSetID(1). 1201 SetWeight(1). 1202 SetShards(shard.NewShards([]shard.Shard{ 1203 shard.NewShard(0).SetState(shard.Available), 1204 })), res[0]) 1205 1206 _, err = groupInstancesByShardSetID([]placement.Instance{i1, i2.Clone().SetWeight(2)}, 2) 1207 assert.Error(t, err) 1208 1209 _, err = groupInstancesByShardSetID([]placement.Instance{i1, i2.Clone().SetIsolationGroup("r1")}, 2) 1210 assert.Error(t, err) 1211 } 1212 1213 func TestReturnInitializingShards(t *testing.T) { 1214 i1 := placement.NewInstance(). 1215 SetID("i1"). 1216 SetIsolationGroup("r1"). 1217 SetEndpoint("endpoint1"). 1218 SetShardSetID(1). 1219 SetWeight(1). 1220 SetShards(shard.NewShards([]shard.Shard{ 1221 shard.NewShard(0).SetState(shard.Leaving), 1222 shard.NewShard(1).SetState(shard.Available), 1223 })) 1224 i2 := placement.NewInstance(). 1225 SetID("i2"). 1226 SetIsolationGroup("r2"). 1227 SetEndpoint("endpoint2"). 1228 SetShardSetID(1). 1229 SetWeight(1). 1230 SetShards(shard.NewShards([]shard.Shard{ 1231 shard.NewShard(0).SetState(shard.Leaving), 1232 shard.NewShard(1).SetState(shard.Available), 1233 })) 1234 i3 := placement.NewInstance(). 1235 SetID("i3"). 1236 SetIsolationGroup("r3"). 1237 SetEndpoint("endpoint3"). 1238 SetShardSetID(1). 1239 SetWeight(1). 1240 SetShards(shard.NewShards([]shard.Shard{ 1241 shard.NewShard(0).SetState(shard.Initializing).SetSourceID("i1"), 1242 })) 1243 i4 := placement.NewInstance(). 1244 SetID("i4"). 1245 SetIsolationGroup("r1"). // Same isolation group with i1. 1246 SetEndpoint("endpoint4"). 1247 SetShardSetID(1). 1248 SetWeight(1). 1249 SetShards(shard.NewShards([]shard.Shard{ 1250 shard.NewShard(0).SetState(shard.Initializing).SetSourceID("i2"), 1251 })) 1252 1253 p := placement.NewPlacement().SetInstances([]placement.Instance{i1, i2, i3, i4}).SetReplicaFactor(2).SetShards([]uint32{0, 1}) 1254 1255 a := NewAlgorithm(placement.NewOptions().SetIsMirrored(true)).(mirroredAlgorithm) 1256 p1, err := a.returnInitializingShards(p.Clone(), []string{i3.ID(), i4.ID()}) 1257 assert.NoError(t, err) 1258 assert.Equal(t, 2, p1.NumInstances()) 1259 assert.NoError(t, placement.Validate(p1)) 1260 assert.Equal(t, uint32(1), p1.MaxShardSetID()) 1261 p2, err := a.returnInitializingShards(p.Clone(), []string{i4.ID(), i3.ID()}) 1262 assert.NoError(t, err) 1263 assert.Equal(t, 2, p2.NumInstances()) 1264 assert.NoError(t, placement.Validate(p2)) 1265 assert.Equal(t, uint32(1), p2.MaxShardSetID()) 1266 } 1267 1268 func TestReclaimLeavingShards(t *testing.T) { 1269 i1 := placement.NewInstance(). 1270 SetID("i1"). 1271 SetIsolationGroup("r1"). 1272 SetEndpoint("endpoint1"). 1273 SetShardSetID(1). 1274 SetWeight(1). 1275 SetShards(shard.NewShards([]shard.Shard{ 1276 shard.NewShard(0).SetState(shard.Leaving), 1277 })) 1278 i2 := placement.NewInstance(). 1279 SetID("i2"). 1280 SetIsolationGroup("r2"). 1281 SetEndpoint("endpoint2"). 1282 SetShardSetID(1). 1283 SetWeight(1). 1284 SetShards(shard.NewShards([]shard.Shard{ 1285 shard.NewShard(0).SetState(shard.Leaving), 1286 })) 1287 i3 := placement.NewInstance(). 1288 SetID("i3"). 1289 SetIsolationGroup("r3"). 1290 SetEndpoint("endpoint3"). 1291 SetShardSetID(1). 1292 SetWeight(1). 1293 SetShards(shard.NewShards([]shard.Shard{ 1294 shard.NewShard(0).SetState(shard.Initializing).SetSourceID("i1"), 1295 })) 1296 i4 := placement.NewInstance(). 1297 SetID("i4"). 1298 SetIsolationGroup("r1"). // Same isolation group with i1. 1299 SetEndpoint("endpoint4"). 1300 SetShardSetID(1). 1301 SetWeight(1). 1302 SetShards(shard.NewShards([]shard.Shard{ 1303 shard.NewShard(0).SetState(shard.Initializing).SetSourceID("i2"), 1304 })) 1305 1306 p := placement.NewPlacement().SetInstances([]placement.Instance{i1, i2, i3, i4}).SetReplicaFactor(2).SetShards([]uint32{0}) 1307 1308 a := NewAlgorithm(placement.NewOptions().SetIsMirrored(true)).(mirroredAlgorithm) 1309 p1, err := a.reclaimLeavingShards(p.Clone(), []placement.Instance{i2, i1}) 1310 assert.NoError(t, err) 1311 assert.Equal(t, 2, p1.NumInstances()) 1312 assert.NoError(t, placement.Validate(p1)) 1313 assert.Equal(t, uint32(1), p1.MaxShardSetID()) 1314 p2, err := a.reclaimLeavingShards(p.Clone(), []placement.Instance{i1, i2}) 1315 assert.NoError(t, err) 1316 assert.Equal(t, 2, p2.NumInstances()) 1317 assert.NoError(t, placement.Validate(p2)) 1318 assert.Equal(t, uint32(1), p2.MaxShardSetID()) 1319 } 1320 1321 func TestReclaimLeavingShardsWithAvailable(t *testing.T) { 1322 i1 := placement.NewInstance(). 1323 SetID("i1"). 1324 SetIsolationGroup("r1"). 1325 SetEndpoint("endpoint1"). 1326 SetShardSetID(1). 1327 SetWeight(1). 1328 SetShards(shard.NewShards([]shard.Shard{ 1329 shard.NewShard(0).SetState(shard.Leaving), 1330 })) 1331 i2 := placement.NewInstance(). 1332 SetID("i2"). 1333 SetIsolationGroup("r2"). 1334 SetEndpoint("endpoint2"). 1335 SetShardSetID(1). 1336 SetWeight(1). 1337 SetShards(shard.NewShards([]shard.Shard{ 1338 shard.NewShard(0).SetState(shard.Leaving), 1339 })) 1340 i3 := placement.NewInstance(). 1341 SetID("i3"). 1342 SetIsolationGroup("r3"). 1343 SetEndpoint("endpoint3"). 1344 SetShardSetID(2). 1345 SetWeight(1). 1346 SetShards(shard.NewShards([]shard.Shard{ 1347 shard.NewShard(0).SetState(shard.Initializing).SetSourceID("i1"), 1348 shard.NewShard(1).SetState(shard.Available), 1349 })) 1350 i4 := placement.NewInstance(). 1351 SetID("i4"). 1352 SetIsolationGroup("r1"). // Same isolation group with i1. 1353 SetEndpoint("endpoint4"). 1354 SetShardSetID(2). 1355 SetWeight(1). 1356 SetShards(shard.NewShards([]shard.Shard{ 1357 shard.NewShard(0).SetState(shard.Initializing).SetSourceID("i2"), 1358 shard.NewShard(1).SetState(shard.Available), 1359 })) 1360 1361 p := placement.NewPlacement().SetInstances([]placement.Instance{i1, i2, i3, i4}).SetReplicaFactor(2).SetShards([]uint32{0, 1}) 1362 1363 a := NewAlgorithm(placement.NewOptions().SetIsMirrored(true)).(mirroredAlgorithm) 1364 p1, err := a.reclaimLeavingShards(p.Clone(), []placement.Instance{i2, i1}) 1365 assert.NoError(t, err) 1366 assert.Equal(t, 4, p1.NumInstances()) 1367 assert.NoError(t, placement.Validate(p1)) 1368 assert.Equal(t, uint32(2), p1.MaxShardSetID()) 1369 p2, err := a.reclaimLeavingShards(p.Clone(), []placement.Instance{i1, i2}) 1370 assert.NoError(t, err) 1371 assert.Equal(t, 4, p2.NumInstances()) 1372 assert.NoError(t, placement.Validate(p2)) 1373 assert.Equal(t, uint32(2), p2.MaxShardSetID()) 1374 } 1375 1376 func TestMarkShardAsAvailableWithMirroredAlgo(t *testing.T) { 1377 var ( 1378 cutoverTime = time.Now() 1379 cutoverTimeNanos = cutoverTime.UnixNano() 1380 maxTimeWindow = time.Hour 1381 tenMinutesInThePast = cutoverTime.Add(1 - 0*time.Minute) 1382 tenMinutesInTheFuture = cutoverTime.Add(10 * time.Minute) 1383 oneHourInTheFuture = cutoverTime.Add(maxTimeWindow) 1384 ) 1385 i1 := placement.NewEmptyInstance("i1", "", "", "e1", 1) 1386 i1.Shards().Add(shard.NewShard(0).SetState(shard.Leaving).SetCutoffNanos(cutoverTimeNanos)) 1387 1388 i2 := placement.NewEmptyInstance("i2", "", "", "e2", 1) 1389 i2.Shards().Add(shard.NewShard(0).SetState(shard.Initializing).SetSourceID("i1").SetCutoverNanos(cutoverTimeNanos)) 1390 1391 p := placement.NewPlacement(). 1392 SetInstances([]placement.Instance{i1, i2}). 1393 SetShards([]uint32{0}). 1394 SetReplicaFactor(1). 1395 SetIsSharded(true). 1396 SetIsMirrored(true) 1397 1398 a := newMirroredAlgorithm(placement.NewOptions(). 1399 SetIsShardCutoverFn(genShardCutoverFn(tenMinutesInThePast)). 1400 SetIsShardCutoffFn(genShardCutoffFn(tenMinutesInThePast, time.Hour))) 1401 _, err := a.MarkShardsAvailable(p, "i2", 0) 1402 assert.Error(t, err) 1403 1404 a = newMirroredAlgorithm(placement.NewOptions(). 1405 SetIsShardCutoverFn(genShardCutoverFn(tenMinutesInTheFuture)). 1406 SetIsShardCutoffFn(genShardCutoffFn(tenMinutesInTheFuture, time.Hour))) 1407 _, err = a.MarkShardsAvailable(p, "i2", 0) 1408 assert.Error(t, err) 1409 1410 a = newMirroredAlgorithm(placement.NewOptions(). 1411 SetIsShardCutoverFn(genShardCutoverFn(oneHourInTheFuture)). 1412 SetIsShardCutoffFn(genShardCutoffFn(oneHourInTheFuture, time.Hour))) 1413 p, err = a.MarkShardsAvailable(p, "i2", 0) 1414 assert.NoError(t, err) 1415 assert.NoError(t, placement.Validate(p)) 1416 } 1417 1418 func TestMarkShardAsAvailableBulkWithMirroredAlgo(t *testing.T) { 1419 var ( 1420 cutoverTime = time.Now() 1421 cutoverTimeNanos = cutoverTime.UnixNano() 1422 maxTimeWindow = time.Hour 1423 oneHourInTheFuture = cutoverTime.Add(maxTimeWindow) 1424 ) 1425 i1 := placement.NewEmptyInstance("i1", "", "", "e1", 1) 1426 i1.Shards().Add(shard.NewShard(0).SetState(shard.Leaving).SetCutoffNanos(cutoverTimeNanos)) 1427 i1.Shards().Add(shard.NewShard(1).SetState(shard.Leaving).SetCutoffNanos(cutoverTimeNanos)) 1428 1429 i2 := placement.NewEmptyInstance("i2", "", "", "e2", 1) 1430 i2.Shards().Add(shard.NewShard(0).SetState(shard.Initializing).SetSourceID("i1").SetCutoverNanos(cutoverTimeNanos)) 1431 i2.Shards().Add(shard.NewShard(1).SetState(shard.Initializing).SetSourceID("i1").SetCutoverNanos(cutoverTimeNanos)) 1432 1433 p := placement.NewPlacement(). 1434 SetInstances([]placement.Instance{i1, i2}). 1435 SetShards([]uint32{0, 1}). 1436 SetReplicaFactor(1). 1437 SetIsSharded(true). 1438 SetIsMirrored(true) 1439 1440 a := newMirroredAlgorithm(placement.NewOptions(). 1441 SetIsShardCutoverFn(genShardCutoverFn(oneHourInTheFuture)). 1442 SetIsShardCutoffFn(genShardCutoffFn(oneHourInTheFuture, time.Hour))) 1443 p, err := a.MarkShardsAvailable(p, "i2", 0, 1) 1444 assert.NoError(t, err) 1445 assert.NoError(t, placement.Validate(p)) 1446 1447 mi2, ok := p.Instance("i2") 1448 assert.True(t, ok) 1449 shards := mi2.Shards().All() 1450 assert.Len(t, shards, 2) 1451 for _, s := range shards { 1452 assert.Equal(t, shard.Available, s.State()) 1453 } 1454 } 1455 1456 func TestMirrorAlgoWithSimpleShardStateType(t *testing.T) { 1457 i1 := newTestInstance("i1").SetShardSetID(1).SetWeight(1) 1458 i2 := newTestInstance("i2").SetShardSetID(1).SetWeight(1) 1459 i3 := newTestInstance("i3").SetShardSetID(2).SetWeight(2) 1460 i4 := newTestInstance("i4").SetShardSetID(2).SetWeight(2) 1461 i5 := newTestInstance("i5").SetShardSetID(3).SetWeight(3) 1462 i6 := newTestInstance("i6").SetShardSetID(3).SetWeight(3) 1463 instances := []placement.Instance{i1, i2, i3, i4, i5, i6} 1464 1465 numShards := 1024 1466 ids := make([]uint32, numShards) 1467 for i := 0; i < len(ids); i++ { 1468 ids[i] = uint32(i) 1469 } 1470 1471 a := NewAlgorithm(placement.NewOptions().SetIsMirrored(true). 1472 SetPlacementCutoverNanosFn(timeNanosGen(1)). 1473 SetShardStateMode(placement.StableShardStateOnly)) 1474 p, err := a.InitialPlacement(instances, ids, 2) 1475 assert.NoError(t, err) 1476 assert.NoError(t, placement.Validate(p)) 1477 verifyAllShardsInAvailableState(t, p) 1478 1479 i7 := newTestInstance("i7").SetShardSetID(4).SetWeight(4) 1480 i8 := newTestInstance("i8").SetShardSetID(4).SetWeight(4) 1481 p, err = a.AddInstances(p, []placement.Instance{i7, i8}) 1482 assert.NoError(t, err) 1483 assert.NoError(t, placement.Validate(p)) 1484 verifyAllShardsInAvailableState(t, p) 1485 1486 p, err = a.RemoveInstances(p, []string{i7.ID(), i8.ID()}) 1487 assert.NoError(t, err) 1488 assert.NoError(t, placement.Validate(p)) 1489 verifyAllShardsInAvailableState(t, p) 1490 1491 p, err = a.ReplaceInstances(p, []string{"i6"}, []placement.Instance{ 1492 newTestInstance("i16").SetShardSetID(3).SetWeight(3), 1493 }) 1494 assert.NoError(t, err) 1495 assert.NoError(t, placement.Validate(p)) 1496 verifyAllShardsInAvailableState(t, p) 1497 1498 i9 := newTestInstance("i9").SetShardSetID(5).SetWeight(1) 1499 i10 := newTestInstance("i10").SetShardSetID(5).SetWeight(1) 1500 p, err = a.AddInstances(p, []placement.Instance{i9, i10}) 1501 assert.NoError(t, err) 1502 assert.NoError(t, placement.Validate(p)) 1503 verifyAllShardsInAvailableState(t, p) 1504 1505 p, err = a.ReplaceInstances(p, []string{"i9"}, []placement.Instance{ 1506 newTestInstance("i19").SetShardSetID(5).SetWeight(1), 1507 }) 1508 assert.NoError(t, err) 1509 assert.NoError(t, placement.Validate(p)) 1510 verifyAllShardsInAvailableState(t, p) 1511 1512 p, err = a.RemoveInstances(p, []string{"i19", "i10"}) 1513 assert.NoError(t, err) 1514 assert.NoError(t, placement.Validate(p)) 1515 verifyAllShardsInAvailableState(t, p) 1516 1517 p, err = a.BalanceShards(p) 1518 assert.NoError(t, err) 1519 assert.NoError(t, placement.Validate(p)) 1520 verifyAllShardsInAvailableState(t, p) 1521 } 1522 1523 func TestMarkInstanceAndItsPeersAvailable(t *testing.T) { 1524 i1 := newTestInstance("i1").SetShardSetID(1) 1525 i2 := newTestInstance("i2").SetShardSetID(1) 1526 i3 := newTestInstance("i3").SetShardSetID(2) 1527 i4 := newTestInstance("i4").SetShardSetID(2) 1528 instances := []placement.Instance{i1, i2, i3, i4} 1529 1530 numShards := 12 1531 ids := make([]uint32, numShards) 1532 for i := 0; i < len(ids); i++ { 1533 ids[i] = uint32(i) 1534 } 1535 1536 now := time.Now() 1537 nowNanos := now.UnixNano() 1538 shardCutoverTime := now.Add(time.Hour).UnixNano() 1539 a := NewAlgorithm(placement.NewOptions(). 1540 SetIsMirrored(true). 1541 SetShardCutoverNanosFn(func() int64 { return shardCutoverTime }). 1542 SetShardCutoffNanosFn(func() int64 { return shardCutoverTime })) 1543 p, err := a.InitialPlacement(instances, ids, 2) 1544 require.NoError(t, err) 1545 require.NoError(t, placement.Validate(p)) 1546 require.Equal(t, uint32(2), p.MaxShardSetID()) 1547 require.True(t, globalChecker.allInitializing(p, []string{"i1", "i2", "i3", "i4"}, nowNanos)) 1548 1549 mirroredAlgo, ok := a.(mirroredAlgorithm) 1550 require.True(t, ok) 1551 1552 t.Run("empty_input", func(t *testing.T) { 1553 _, err := mirroredAlgo.markInstanceAndItsPeersAvailable(p, "") 1554 require.Error(t, err) 1555 }) 1556 1557 t.Run("invalid_instance", func(t *testing.T) { 1558 _, err := mirroredAlgo.markInstanceAndItsPeersAvailable(p, "non-existent") 1559 require.Error(t, err) 1560 }) 1561 1562 t.Run("noop_when_shards_already_available", func(t *testing.T) { 1563 p1, _, err := mirroredAlgo.MarkAllShardsAvailable(p) 1564 require.NoError(t, err) 1565 require.True(t, globalChecker.allAvailable(p1, []string{"i1", "i2", "i3", "i4"}, nowNanos)) 1566 1567 for i := 1; i < 3; i++ { 1568 p2, err := mirroredAlgo.markInstanceAndItsPeersAvailable(p1, "i1") 1569 require.NoError(t, err) 1570 require.Equal(t, p1, p2) 1571 } 1572 }) 1573 1574 t.Run("mark_first_pair", func(t *testing.T) { 1575 p1, err := mirroredAlgo.markInstanceAndItsPeersAvailable(p.Clone(), "i1") 1576 require.NoError(t, err) 1577 require.True(t, globalChecker.allAvailable(p1, []string{"i1", "i2"}, nowNanos)) 1578 require.True(t, globalChecker.allInitializing(p1, []string{"i3", "i4"}, nowNanos)) 1579 1580 // Shouldn't matter which of the peers is marked. 1581 p2, err := mirroredAlgo.markInstanceAndItsPeersAvailable(p.Clone(), "i2") 1582 require.NoError(t, err) 1583 require.Equal(t, p1, p2) 1584 }) 1585 1586 t.Run("mark_second_pair", func(t *testing.T) { 1587 p1, err := mirroredAlgo.markInstanceAndItsPeersAvailable(p.Clone(), "i4") 1588 require.NoError(t, err) 1589 require.True(t, globalChecker.allAvailable(p1, []string{"i3", "i4"}, nowNanos)) 1590 require.True(t, globalChecker.allInitializing(p1, []string{"i1", "i2"}, nowNanos)) 1591 1592 // Shouldn't matter which of the peers is marked. 1593 p2, err := mirroredAlgo.markInstanceAndItsPeersAvailable(p.Clone(), "i3") 1594 require.NoError(t, err) 1595 require.Equal(t, p1, p2) 1596 }) 1597 1598 t.Run("mark_added_pair", func(t *testing.T) { 1599 i5 := newTestInstance("i5").SetShardSetID(3) 1600 i6 := newTestInstance("i6").SetShardSetID(3) 1601 p1, err := mirroredAlgo.AddInstances(p, []placement.Instance{i5, i6}) 1602 require.NoError(t, err) 1603 require.True(t, globalChecker.allInitializing(p1, []string{"i5", "i6"}, nowNanos)) 1604 newI1, exists := p1.Instance("i1") 1605 require.True(t, exists) 1606 require.True(t, newI1.Shards().NumShardsForState(shard.Leaving) > 0) 1607 require.True(t, newI1.Shards().NumShardsForState(shard.Available) > newI1.Shards().NumShardsForState(shard.Leaving)) 1608 newI2, exists := p1.Instance("i2") 1609 require.True(t, exists) 1610 require.Equal(t, newI1.Shards().NumShardsForState(shard.Leaving), newI2.Shards().NumShardsForState(shard.Leaving)) 1611 require.Equal(t, newI1.Shards().NumShardsForState(shard.Available), newI2.Shards().NumShardsForState(shard.Available)) 1612 newI3, exists := p1.Instance("i3") 1613 require.True(t, exists) 1614 require.True(t, newI3.Shards().NumShardsForState(shard.Leaving) > 0) 1615 require.True(t, newI3.Shards().NumShardsForState(shard.Available) > newI3.Shards().NumShardsForState(shard.Leaving)) 1616 newI4, exists := p1.Instance("i4") 1617 require.True(t, exists) 1618 require.Equal(t, newI3.Shards().NumShardsForState(shard.Leaving), newI4.Shards().NumShardsForState(shard.Leaving)) 1619 require.Equal(t, newI3.Shards().NumShardsForState(shard.Available), newI4.Shards().NumShardsForState(shard.Available)) 1620 1621 p2, err := mirroredAlgo.markInstanceAndItsPeersAvailable(p1, "i5") 1622 require.NoError(t, err) 1623 require.True(t, globalChecker.allAvailable(p2, []string{"i1", "i2", "i3", "i4", "i5", "i6"}, nowNanos)) 1624 }) 1625 } 1626 1627 func TestBalanceShardsForMirroredWhenBalanced(t *testing.T) { 1628 i1 := newTestInstance("i1"). 1629 SetShardSetID(1). 1630 SetWeight(1). 1631 SetShards(shard.NewShards([]shard.Shard{ 1632 shard.NewShard(0).SetState(shard.Available), 1633 })) 1634 i2 := newTestInstance("i2"). 1635 SetShardSetID(1). 1636 SetWeight(1). 1637 SetShards(shard.NewShards([]shard.Shard{ 1638 shard.NewShard(0).SetState(shard.Available), 1639 })) 1640 i3 := newTestInstance("i3"). 1641 SetShardSetID(2). 1642 SetWeight(1). 1643 SetShards(shard.NewShards([]shard.Shard{ 1644 shard.NewShard(1).SetState(shard.Available), 1645 })) 1646 i4 := newTestInstance("i4"). 1647 SetShardSetID(2). 1648 SetWeight(1). 1649 SetShards(shard.NewShards([]shard.Shard{ 1650 shard.NewShard(1).SetState(shard.Available), 1651 })) 1652 initialPlacement := placement.NewPlacement(). 1653 SetReplicaFactor(2). 1654 SetShards([]uint32{0, 1}). 1655 SetInstances([]placement.Instance{i1, i2, i3, i4}). 1656 SetIsMirrored(true). 1657 SetIsSharded(true). 1658 SetMaxShardSetID(2) 1659 1660 expectedPlacement := initialPlacement.Clone() 1661 1662 a := NewAlgorithm(placement.NewOptions().SetIsMirrored(true)) 1663 1664 balancedPlacement, err := a.BalanceShards(initialPlacement) 1665 assert.NoError(t, err) 1666 assert.Equal(t, expectedPlacement, balancedPlacement) 1667 } 1668 1669 func TestBalanceShardsForMirroredWhenImbalanced(t *testing.T) { 1670 i1 := newTestInstance("i1"). 1671 SetShardSetID(1). 1672 SetWeight(1). 1673 SetShards(shard.NewShards([]shard.Shard{ 1674 shard.NewShard(0).SetState(shard.Available), 1675 shard.NewShard(1).SetState(shard.Available), 1676 })) 1677 i2 := newTestInstance("i2"). 1678 SetShardSetID(1). 1679 SetWeight(1). 1680 SetShards(shard.NewShards([]shard.Shard{ 1681 shard.NewShard(0).SetState(shard.Available), 1682 shard.NewShard(1).SetState(shard.Available), 1683 })) 1684 i3 := newTestInstance("i3"). 1685 SetShardSetID(2). 1686 SetWeight(2). 1687 SetShards(shard.NewShards([]shard.Shard{ 1688 shard.NewShard(2).SetState(shard.Available), 1689 })) 1690 i4 := newTestInstance("i4"). 1691 SetShardSetID(2). 1692 SetWeight(2). 1693 SetShards(shard.NewShards([]shard.Shard{ 1694 shard.NewShard(2).SetState(shard.Available), 1695 })) 1696 p := placement.NewPlacement(). 1697 SetReplicaFactor(2). 1698 SetShards([]uint32{0, 1, 2, 3}). 1699 SetInstances([]placement.Instance{i1, i2, i3, i4}). 1700 SetIsMirrored(true). 1701 SetIsSharded(true). 1702 SetMaxShardSetID(2) 1703 1704 a := NewAlgorithm(placement.NewOptions().SetIsMirrored(true)) 1705 1706 balancedPlacement, err := a.BalanceShards(p) 1707 assert.NoError(t, err) 1708 1709 bi1 := newTestInstance("i1"). 1710 SetShardSetID(1). 1711 SetWeight(1). 1712 SetShards(shard.NewShards([]shard.Shard{ 1713 shard.NewShard(0).SetState(shard.Leaving), 1714 shard.NewShard(1).SetState(shard.Available), 1715 })) 1716 bi2 := newTestInstance("i2"). 1717 SetShardSetID(1). 1718 SetWeight(1). 1719 SetShards(shard.NewShards([]shard.Shard{ 1720 shard.NewShard(0).SetState(shard.Leaving), 1721 shard.NewShard(1).SetState(shard.Available), 1722 })) 1723 bi3 := newTestInstance("i3"). 1724 SetShardSetID(2). 1725 SetWeight(2). 1726 SetShards(shard.NewShards([]shard.Shard{ 1727 shard.NewShard(0).SetState(shard.Initializing).SetSourceID("i1"), 1728 shard.NewShard(2).SetState(shard.Available), 1729 })) 1730 bi4 := newTestInstance("i4"). 1731 SetShardSetID(2). 1732 SetWeight(2). 1733 SetShards(shard.NewShards([]shard.Shard{ 1734 shard.NewShard(0).SetState(shard.Initializing).SetSourceID("i2"), 1735 shard.NewShard(2).SetState(shard.Available), 1736 })) 1737 expectedInstances := []placement.Instance{bi1, bi2, bi3, bi4} 1738 1739 assert.Equal(t, expectedInstances, balancedPlacement.Instances()) 1740 } 1741 1742 func newTestInstance(id string) placement.Instance { 1743 return placement.NewInstance(). 1744 SetID(id). 1745 SetIsolationGroup("rack-" + id). 1746 SetEndpoint("endpoint-" + id). 1747 SetMetadata(placement.InstanceMetadata{DebugPort: 80}). 1748 SetWeight(1) 1749 } 1750 1751 func assertInstancesArePeers(t *testing.T, i1, i2 placement.Instance) { 1752 assert.Equal(t, i1.Shards().AllIDs(), i1.Shards().AllIDs()) 1753 assert.Equal(t, i2.ShardSetID(), i2.ShardSetID()) 1754 }