github.com/m3db/m3@v1.5.0/src/cluster/placement/service/service_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 service 22 23 import ( 24 "errors" 25 "testing" 26 27 "github.com/m3db/m3/src/cluster/kv/mem" 28 "github.com/m3db/m3/src/cluster/placement" 29 "github.com/m3db/m3/src/cluster/placement/algo" 30 "github.com/m3db/m3/src/cluster/placement/storage" 31 "github.com/m3db/m3/src/cluster/shard" 32 "github.com/stretchr/testify/assert" 33 "github.com/stretchr/testify/require" 34 ) 35 36 func TestGoodWorkflow(t *testing.T) { 37 p := NewPlacementService(newMockStorage(), 38 WithPlacementOptions(placement.NewOptions().SetValidZone("z1"))) 39 testGoodWorkflow(t, p) 40 } 41 42 func testGoodWorkflow(t *testing.T, ps placement.Service) { 43 i1 := placement.NewEmptyInstance("i1", "r1", "z1", "endpoint", 2) 44 i2 := placement.NewEmptyInstance("i2", "r2", "z1", "endpoint", 2) 45 i3 := placement.NewEmptyInstance("i3", "r3", "z1", "endpoint", 2) 46 _, err := ps.BuildInitialPlacement([]placement.Instance{i1, i2}, 10, 1) 47 assert.NoError(t, err) 48 49 _, err = ps.AddReplica() 50 assert.NoError(t, err) 51 52 for _, instance := range []placement.Instance{i1, i2} { 53 _, err = ps.MarkInstanceAvailable(instance.ID()) 54 assert.NoError(t, err) 55 } 56 _, ai, err := ps.AddInstances([]placement.Instance{i3}) 57 assert.NoError(t, err) 58 assertPlacementInstanceEqualExceptShards(t, i3, ai[0]) 59 60 _, err = ps.MarkInstanceAvailable(i3.ID()) 61 assert.NoError(t, err) 62 63 _, err = ps.RemoveInstances([]string{i1.ID()}) 64 assert.NoError(t, err) 65 66 markAllInstancesAvailable(t, ps) 67 68 var ( 69 i21 = placement.NewEmptyInstance("i21", "r2", "z1", "endpoint", 1) 70 i4 = placement.NewEmptyInstance("i4", "r4", "z1", "endpoint", 1) 71 ) 72 _, usedInstances, err := ps.ReplaceInstances( 73 []string{i2.ID()}, 74 []placement.Instance{i21, i4, 75 i3, // already in placement 76 placement.NewEmptyInstance("i31", "r3", "z1", "endpoint", 1), // conflict 77 }, 78 ) 79 assert.NoError(t, err) 80 assert.Equal(t, 2, len(usedInstances)) 81 assertPlacementInstanceEqualExceptShards(t, i21, usedInstances[0]) 82 assertPlacementInstanceEqualExceptShards(t, i4, usedInstances[1]) 83 84 for _, id := range []string{"i21", "i4"} { 85 _, err = ps.MarkInstanceAvailable(id) 86 assert.NoError(t, err) 87 } 88 89 s, err := ps.Placement() 90 assert.NoError(t, err) 91 assert.Equal(t, 3, s.NumInstances()) 92 _, exist := s.Instance("i21") 93 assert.True(t, exist) 94 _, exist = s.Instance("i4") 95 assert.True(t, exist) 96 97 _, ai, err = ps.AddInstances([]placement.Instance{i1}) 98 assert.NoError(t, err) 99 assertPlacementInstanceEqualExceptShards(t, i1, ai[0]) 100 101 i24 := placement.NewEmptyInstance("i24", "r2", "z1", "endpoint", 1) 102 _, ai, err = ps.AddInstances([]placement.Instance{i24}) 103 assert.NoError(t, err) 104 assertPlacementInstanceEqualExceptShards(t, i24, ai[0]) 105 106 i34 := placement.NewEmptyInstance("i34", "r3", "z1", "endpoint", 1) 107 _, ai, err = ps.AddInstances([]placement.Instance{i34}) 108 assert.NoError(t, err) 109 assertPlacementInstanceEqualExceptShards(t, i34, ai[0]) 110 111 i35 := placement.NewEmptyInstance("i35", "r3", "z1", "endpoint", 1) 112 _, ai, err = ps.AddInstances([]placement.Instance{i35}) 113 assert.NoError(t, err) 114 assertPlacementInstanceEqualExceptShards(t, i35, ai[0]) 115 116 i41 := placement.NewEmptyInstance("i41", "r4", "z1", "endpoint", 1) 117 instances := []placement.Instance{ 118 placement.NewEmptyInstance("i15", "r1", "z1", "endpoint", 1), 119 placement.NewEmptyInstance("i34", "r3", "z1", "endpoint", 1), 120 placement.NewEmptyInstance("i35", "r3", "z1", "endpoint", 1), 121 placement.NewEmptyInstance("i36", "r3", "z1", "endpoint", 1), 122 placement.NewEmptyInstance("i23", "r2", "z1", "endpoint", 1), 123 i41, 124 } 125 _, ai, err = ps.AddInstances(instances) 126 assert.NoError(t, err) 127 assertPlacementInstanceEqualExceptShards(t, i41, ai[0]) 128 s, err = ps.Placement() 129 assert.NoError(t, err) 130 // Instance added from least weighted isolation group. 131 _, exist = s.Instance("i41") 132 assert.True(t, exist) 133 } 134 135 func assertPlacementInstanceEqualExceptShards( 136 t *testing.T, 137 expected placement.Instance, 138 observed placement.Instance, 139 ) { 140 assert.Equal(t, expected.ID(), observed.ID()) 141 assert.Equal(t, expected.IsolationGroup(), observed.IsolationGroup()) 142 assert.Equal(t, expected.Zone(), observed.Zone()) 143 assert.Equal(t, expected.Weight(), observed.Weight()) 144 assert.Equal(t, expected.Endpoint(), observed.Endpoint()) 145 assert.True(t, len(observed.Shards().All()) > 0) 146 } 147 148 func TestNonShardedWorkflow(t *testing.T) { 149 ps := NewPlacementService(newMockStorage(), 150 WithPlacementOptions(placement.NewOptions().SetValidZone("z1").SetIsSharded(false))) 151 152 _, err := ps.BuildInitialPlacement([]placement.Instance{ 153 placement.NewEmptyInstance("i1", "r1", "z1", "e1", 1), 154 placement.NewEmptyInstance("i2", "r1", "z1", "e2", 1), 155 }, 10, 1) 156 assert.Error(t, err) 157 158 p, err := ps.BuildInitialPlacement([]placement.Instance{ 159 placement.NewEmptyInstance("i1", "r1", "z1", "e1", 1), 160 placement.NewEmptyInstance("i2", "r1", "z1", "e2", 1), 161 }, 0, 1) 162 assert.NoError(t, err) 163 assert.Equal(t, 2, p.NumInstances()) 164 assert.Equal(t, 0, p.NumShards()) 165 assert.Equal(t, 1, p.ReplicaFactor()) 166 assert.False(t, p.IsSharded()) 167 168 p, err = ps.AddReplica() 169 assert.NoError(t, err) 170 assert.Equal(t, 2, p.NumInstances()) 171 assert.Equal(t, 0, p.NumShards()) 172 assert.Equal(t, 2, p.ReplicaFactor()) 173 assert.False(t, p.IsSharded()) 174 175 i3 := placement.NewEmptyInstance("i3", "r1", "z1", "e3", 1) 176 i4 := placement.NewEmptyInstance("i4", "r1", "z1", "e4", 1) 177 p, ai, err := ps.AddInstances([]placement.Instance{i3, i4}) 178 assert.NoError(t, err) 179 assert.Equal(t, i3, ai[0]) 180 assert.Equal(t, 3, p.NumInstances()) 181 assert.Equal(t, 0, p.NumShards()) 182 assert.Equal(t, 2, p.ReplicaFactor()) 183 assert.False(t, p.IsSharded()) 184 185 p, err = ps.RemoveInstances([]string{"i1"}) 186 assert.NoError(t, err) 187 assert.Equal(t, 2, p.NumInstances()) 188 assert.Equal(t, 0, p.NumShards()) 189 assert.Equal(t, 2, p.ReplicaFactor()) 190 assert.False(t, p.IsSharded()) 191 192 p, usedInstances, err := ps.ReplaceInstances([]string{"i2"}, []placement.Instance{i3, i4}) 193 assert.NoError(t, err) 194 assert.Equal(t, 1, len(usedInstances)) 195 assert.Equal(t, i4, usedInstances[0]) 196 assert.Equal(t, 2, p.NumInstances()) 197 assert.Equal(t, 0, p.NumShards()) 198 assert.Equal(t, 2, p.ReplicaFactor()) 199 assert.False(t, p.IsSharded()) 200 201 // nothing happens because i3 has no shards 202 _, err = ps.MarkInstanceAvailable("i3") 203 assert.NoError(t, err) 204 assert.Equal(t, 2, p.NumInstances()) 205 assert.Equal(t, 0, p.NumShards()) 206 assert.Equal(t, 2, p.ReplicaFactor()) 207 assert.False(t, p.IsSharded()) 208 209 // nothing happens because there are no shards 210 _, err = ps.BalanceShards() 211 assert.NoError(t, err) 212 assert.Equal(t, 2, p.NumInstances()) 213 assert.Equal(t, 0, p.NumShards()) 214 assert.Equal(t, 2, p.ReplicaFactor()) 215 assert.False(t, p.IsSharded()) 216 } 217 218 func TestBadInitialPlacement(t *testing.T) { 219 p := NewPlacementService(newMockStorage(), 220 WithPlacementOptions(placement.NewOptions().SetValidZone("z1").SetIsSharded(false))) 221 222 // invalid numShards 223 _, err := p.BuildInitialPlacement([]placement.Instance{ 224 placement.NewEmptyInstance("i1", "r1", "z1", "endpoint", 1), 225 placement.NewEmptyInstance("i2", "r1", "z1", "endpoint", 1), 226 }, -1, 1) 227 assert.Error(t, err) 228 229 // invalid rf 230 _, err = p.BuildInitialPlacement([]placement.Instance{ 231 placement.NewEmptyInstance("i1", "r1", "z1", "endpoint", 1), 232 placement.NewEmptyInstance("i2", "r1", "z1", "endpoint", 1), 233 }, 10, 0) 234 assert.Error(t, err) 235 236 // numshards > 0 && sharded == false 237 _, err = p.BuildInitialPlacement([]placement.Instance{ 238 placement.NewEmptyInstance("i1", "r1", "z1", "endpoint", 1), 239 placement.NewEmptyInstance("i2", "r1", "z1", "endpoint", 1), 240 }, 10, 1) 241 assert.Error(t, err) 242 243 p = NewPlacementService(newMockStorage(), 244 WithPlacementOptions(placement.NewOptions().SetValidZone("z1"))) 245 246 // Not enough instances. 247 _, err = p.BuildInitialPlacement([]placement.Instance{}, 10, 1) 248 assert.Error(t, err) 249 250 // Error: rf == 0 && sharded == true. 251 _, err = p.BuildInitialPlacement([]placement.Instance{ 252 placement.NewEmptyInstance("i1", "r1", "z1", "endpoint", 1), 253 placement.NewEmptyInstance("i2", "r1", "z1", "endpoint", 1), 254 }, 10, 0) 255 assert.Error(t, err) 256 257 // Not enough isolation groups. 258 _, err = p.BuildInitialPlacement([]placement.Instance{ 259 placement.NewEmptyInstance("i1", "r1", "z1", "endpoint", 1), 260 placement.NewEmptyInstance("i2", "r1", "z1", "endpoint", 1), 261 }, 100, 2) 262 assert.Error(t, err) 263 264 _, err = p.BuildInitialPlacement([]placement.Instance{ 265 placement.NewEmptyInstance("i1", "r1", "z1", "endpoint", 1), 266 placement.NewEmptyInstance("i2", "r2", "z1", "endpoint", 1), 267 }, 100, 2) 268 assert.NoError(t, err) 269 270 // Placement already exist. 271 _, err = p.BuildInitialPlacement([]placement.Instance{ 272 placement.NewEmptyInstance("i1", "r1", "z1", "endpoint", 1), 273 placement.NewEmptyInstance("i2", "r2", "z1", "endpoint", 1), 274 }, 100, 2) 275 assert.Error(t, err) 276 } 277 278 func TestBadAddReplica(t *testing.T) { 279 p := NewPlacementService(newMockStorage(), 280 WithPlacementOptions(placement.NewOptions().SetValidZone("z1"))) 281 282 _, err := p.BuildInitialPlacement( 283 []placement.Instance{placement.NewEmptyInstance("i1", "r1", "z1", "endpoint", 1)}, 284 10, 1) 285 assert.NoError(t, err) 286 287 // Not enough isolation groups/instances. 288 _, err = p.AddReplica() 289 assert.Error(t, err) 290 291 // Could not find placement for service. 292 p = NewPlacementService(newMockStorage(), 293 WithPlacementOptions(placement.NewOptions().SetValidZone("z1"))) 294 _, err = p.AddReplica() 295 assert.Error(t, err) 296 } 297 298 func TestBadAddInstance(t *testing.T) { 299 ms := newMockStorage() 300 p := NewPlacementService(ms, 301 WithPlacementOptions(placement.NewOptions().SetValidZone("z1"))) 302 303 _, err := p.BuildInitialPlacement( 304 []placement.Instance{placement.NewEmptyInstance("i1", "r1", "z1", "endpoint", 1)}, 305 10, 1) 306 assert.NoError(t, err) 307 308 // adding instance already exist 309 _, _, err = p.AddInstances([]placement.Instance{placement.NewEmptyInstance("i1", "r1", "z1", "endpoint", 1)}) 310 assert.Error(t, err) 311 312 // too many zones 313 _, _, err = p.AddInstances([]placement.Instance{placement.NewEmptyInstance("i2", "r2", "z2", "endpoint", 1)}) 314 assert.Error(t, err) 315 316 p = NewPlacementService(ms, 317 WithPlacementOptions(placement.NewOptions().SetValidZone("z1"))) 318 _, _, err = p.AddInstances([]placement.Instance{placement.NewEmptyInstance("i1", "r1", "z1", "endpoint", 1)}) 319 assert.Error(t, err) 320 321 // could not find placement for service 322 p = NewPlacementService(newMockStorage(), 323 WithPlacementOptions(placement.NewOptions().SetValidZone("z1"))) 324 _, _, err = p.AddInstances([]placement.Instance{placement.NewEmptyInstance("i2", "r2", "z1", "endpoint", 1)}) 325 assert.Error(t, err) 326 } 327 328 func TestBadRemoveInstance(t *testing.T) { 329 p := NewPlacementService(newMockStorage(), 330 WithPlacementOptions(placement.NewOptions().SetValidZone("z1"))) 331 332 _, err := p.BuildInitialPlacement( 333 []placement.Instance{placement.NewEmptyInstance("i1", "r1", "z1", "endpoint", 1)}, 334 10, 1) 335 assert.NoError(t, err) 336 337 // Leaving instance not exist. 338 _, err = p.RemoveInstances([]string{"not_exist"}) 339 assert.Error(t, err) 340 341 // Not enough isolation groups/instances after removal. 342 _, err = p.RemoveInstances([]string{"i1"}) 343 assert.Error(t, err) 344 345 // Could not find placement for service. 346 p = NewPlacementService(newMockStorage(), 347 WithPlacementOptions(placement.NewOptions().SetValidZone("z1"))) 348 _, err = p.RemoveInstances([]string{"i1"}) 349 assert.Error(t, err) 350 } 351 352 func TestBadReplaceInstance(t *testing.T) { 353 p := NewPlacementService(newMockStorage(), 354 WithPlacementOptions(placement.NewOptions().SetValidZone("z1"))) 355 356 _, err := p.BuildInitialPlacement([]placement.Instance{ 357 placement.NewEmptyInstance("i1", "r1", "z1", "endpoint", 1), 358 placement.NewEmptyInstance("i4", "r4", "z1", "endpoint", 1), 359 }, 10, 1) 360 assert.NoError(t, err) 361 362 // Leaving instance not exist. 363 _, _, err = p.ReplaceInstances( 364 []string{"not_exist"}, 365 []placement.Instance{placement.NewEmptyInstance("i2", "r2", "z1", "endpoint", 1)}, 366 ) 367 assert.Error(t, err) 368 369 // Adding instance already exist. 370 _, _, err = p.ReplaceInstances( 371 []string{"i1"}, 372 []placement.Instance{placement.NewEmptyInstance("i4", "r4", "z1", "endpoint", 1)}, 373 ) 374 assert.Error(t, err) 375 376 // Not enough isolation groups after replace. 377 _, err = p.AddReplica() 378 assert.NoError(t, err) 379 _, _, err = p.ReplaceInstances( 380 []string{"i4"}, 381 []placement.Instance{placement.NewEmptyInstance("i12", "r1", "z1", "endpoint", 1)}, 382 ) 383 assert.Error(t, err) 384 385 // Could not find placement for service. 386 p = NewPlacementService(newMockStorage(), 387 WithPlacementOptions(placement.NewOptions().SetValidZone("z1"))) 388 _, _, err = p.ReplaceInstances( 389 []string{"i1"}, 390 []placement.Instance{placement.NewEmptyInstance("i2", "r2", "z1", "endpoint", 1)}, 391 ) 392 assert.Error(t, err) 393 } 394 395 func TestMarkShard(t *testing.T) { 396 ms := newMockStorage() 397 398 i1 := placement.NewEmptyInstance("i1", "r1", "z1", "endpoint", 1) 399 i1.Shards().Add(shard.NewShard(1).SetState(shard.Leaving)) 400 i1.Shards().Add(shard.NewShard(2).SetState(shard.Available)) 401 i1.Shards().Add(shard.NewShard(3).SetState(shard.Available)) 402 403 i2 := placement.NewEmptyInstance("i2", "r1", "z1", "endpoint", 1) 404 i2.Shards().Add(shard.NewShard(4).SetState(shard.Available)) 405 i2.Shards().Add(shard.NewShard(5).SetState(shard.Available)) 406 i2.Shards().Add(shard.NewShard(0).SetState(shard.Available)) 407 408 i3 := placement.NewEmptyInstance("i3", "r2", "z1", "endpoint", 1) 409 i3.Shards().Add(shard.NewShard(1).SetState(shard.Available)) 410 i3.Shards().Add(shard.NewShard(3).SetState(shard.Available)) 411 i3.Shards().Add(shard.NewShard(5).SetState(shard.Available)) 412 413 i4 := placement.NewEmptyInstance("i4", "r2", "z1", "endpoint", 1) 414 i4.Shards().Add(shard.NewShard(2).SetState(shard.Available)) 415 i4.Shards().Add(shard.NewShard(4).SetState(shard.Available)) 416 i4.Shards().Add(shard.NewShard(0).SetState(shard.Available)) 417 418 i5 := placement.NewEmptyInstance("i5", "r2", "z1", "endpoint", 1) 419 i5.Shards().Add(shard.NewShard(1).SetState(shard.Initializing).SetSourceID("i1")) 420 421 instances := []placement.Instance{i1, i2, i3, i4, i5} 422 p := placement.NewPlacement(). 423 SetInstances(instances). 424 SetShards([]uint32{0, 1, 2, 3, 4, 5}). 425 SetReplicaFactor(2). 426 SetIsSharded(true) 427 _, err := ms.SetIfNotExist(p) 428 assert.NoError(t, err) 429 430 ps := NewPlacementService(ms, 431 WithPlacementOptions(placement.NewOptions().SetValidZone("z1"))) 432 _, err = ps.MarkShardsAvailable("i5", 1) 433 assert.NoError(t, err) 434 p, err = ms.Placement() 435 assert.NoError(t, err) 436 assert.NoError(t, placement.Validate(p)) 437 for _, instance := range p.Instances() { 438 for _, s := range instance.Shards().All() { 439 assert.Equal(t, shard.Available, s.State()) 440 } 441 } 442 443 _, err = ps.MarkShardsAvailable("i1", 1) 444 assert.Error(t, err) 445 446 _, err = ps.MarkShardsAvailable("i5", 5) 447 assert.Error(t, err) 448 } 449 450 func TestMarkInstance(t *testing.T) { 451 ms := newMockStorage() 452 453 i1 := placement.NewEmptyInstance("i1", "r1", "z1", "endpoint", 1) 454 i1.Shards().Add(shard.NewShard(1).SetState(shard.Leaving)) 455 i1.Shards().Add(shard.NewShard(2).SetState(shard.Leaving)) 456 i1.Shards().Add(shard.NewShard(3).SetState(shard.Available)) 457 458 i2 := placement.NewEmptyInstance("i2", "r1", "z1", "endpoint", 1) 459 i2.Shards().Add(shard.NewShard(4).SetState(shard.Available)) 460 i2.Shards().Add(shard.NewShard(5).SetState(shard.Available)) 461 i2.Shards().Add(shard.NewShard(0).SetState(shard.Available)) 462 463 i3 := placement.NewEmptyInstance("i3", "r2", "z1", "endpoint", 1) 464 i3.Shards().Add(shard.NewShard(1).SetState(shard.Available)) 465 i3.Shards().Add(shard.NewShard(3).SetState(shard.Available)) 466 i3.Shards().Add(shard.NewShard(5).SetState(shard.Available)) 467 468 i4 := placement.NewEmptyInstance("i4", "r2", "z1", "endpoint", 1) 469 i4.Shards().Add(shard.NewShard(2).SetState(shard.Available)) 470 i4.Shards().Add(shard.NewShard(4).SetState(shard.Available)) 471 i4.Shards().Add(shard.NewShard(0).SetState(shard.Available)) 472 473 i5 := placement.NewEmptyInstance("i5", "r2", "z1", "endpoint", 1) 474 i5.Shards().Add(shard.NewShard(1).SetState(shard.Initializing).SetSourceID("i1")) 475 i5.Shards().Add(shard.NewShard(2).SetState(shard.Initializing).SetSourceID("i1")) 476 477 instances := []placement.Instance{i1, i2, i3, i4, i5} 478 p := placement.NewPlacement(). 479 SetInstances(instances). 480 SetShards([]uint32{0, 1, 2, 3, 4, 5}). 481 SetReplicaFactor(2). 482 SetIsSharded(true) 483 _, err := ms.SetIfNotExist(p) 484 assert.NoError(t, err) 485 486 ps := NewPlacementService(ms, WithPlacementOptions(placement.NewOptions().SetValidZone("z1"))) 487 488 // instance not exist 489 _, err = ps.MarkInstanceAvailable("i6") 490 assert.Error(t, err) 491 492 _, err = ps.MarkInstanceAvailable("i5") 493 assert.NoError(t, err) 494 p, err = ps.Placement() 495 assert.NoError(t, err) 496 for _, instance := range p.Instances() { 497 assert.True(t, instance.IsAvailable()) 498 } 499 } 500 501 func TestFindReplaceInstance(t *testing.T) { 502 i1 := placement.NewEmptyInstance("i1", "r11", "z1", "endpoint", 1) 503 i1.Shards().Add(shard.NewShard(1).SetState(shard.Available)) 504 i1.Shards().Add(shard.NewShard(2).SetState(shard.Available)) 505 i1.Shards().Add(shard.NewShard(3).SetState(shard.Available)) 506 507 i10 := placement.NewEmptyInstance("i10", "r11", "z1", "endpoint", 1) 508 i10.Shards().Add(shard.NewShard(4).SetState(shard.Available)) 509 i10.Shards().Add(shard.NewShard(5).SetState(shard.Available)) 510 511 i2 := placement.NewEmptyInstance("i2", "r12", "z1", "endpoint", 1) 512 i2.Shards().Add(shard.NewShard(6).SetState(shard.Available)) 513 i2.Shards().Add(shard.NewShard(7).SetState(shard.Available)) 514 i2.Shards().Add(shard.NewShard(8).SetState(shard.Available)) 515 i2.Shards().Add(shard.NewShard(9).SetState(shard.Available)) 516 517 i3 := placement.NewEmptyInstance("i3", "r13", "z1", "endpoint", 3) 518 i3.Shards().Add(shard.NewShard(1).SetState(shard.Available)) 519 i3.Shards().Add(shard.NewShard(3).SetState(shard.Available)) 520 i3.Shards().Add(shard.NewShard(4).SetState(shard.Available)) 521 i3.Shards().Add(shard.NewShard(5).SetState(shard.Available)) 522 i3.Shards().Add(shard.NewShard(6).SetState(shard.Available)) 523 524 i4 := placement.NewEmptyInstance("i4", "r14", "z1", "endpoint", 1) 525 i4.Shards().Add(shard.NewShard(2).SetState(shard.Available)) 526 i4.Shards().Add(shard.NewShard(7).SetState(shard.Available)) 527 i4.Shards().Add(shard.NewShard(8).SetState(shard.Available)) 528 i4.Shards().Add(shard.NewShard(9).SetState(shard.Available)) 529 530 instances := []placement.Instance{i1, i2, i3, i4, i10} 531 532 ids := []uint32{1, 2, 3, 4, 5, 6, 7, 8} 533 s := placement.NewPlacement().SetInstances(instances).SetShards(ids).SetReplicaFactor(2) 534 535 candidates := []placement.Instance{ 536 placement.NewEmptyInstance("i11", "r11", "z1", "endpoint", 1), 537 placement.NewEmptyInstance("i22", "r22", "z2", "endpoint", 1), 538 } 539 noConflictCandidates := []placement.Instance{ 540 placement.NewEmptyInstance("i11", "r0", "z1", "endpoint", 1), 541 placement.NewEmptyInstance("i22", "r0", "z2", "endpoint", 1), 542 } 543 544 testCases := []struct { 545 opts placement.Options 546 input []placement.Instance 547 replaceIDs []string 548 expectRes []placement.Instance 549 expectErr bool 550 }{ 551 { 552 opts: placement.NewOptions().SetValidZone("z1"), 553 input: candidates, 554 replaceIDs: []string{i4.ID()}, 555 expectRes: []placement.Instance{candidates[0]}, 556 }, 557 { 558 opts: placement.NewOptions().SetValidZone("z1").SetAllowPartialReplace(false), 559 input: candidates, 560 replaceIDs: []string{i4.ID()}, 561 expectErr: true, 562 }, 563 { 564 opts: placement.NewOptions().SetValidZone("z1"), 565 input: noConflictCandidates, 566 replaceIDs: []string{i3.ID()}, 567 expectRes: []placement.Instance{noConflictCandidates[0]}, 568 }, 569 { 570 // Disable AllowPartialReplace, so the weight from the candidate instances must be more than the replacing instance. 571 opts: placement.NewOptions().SetValidZone("z1").SetAllowPartialReplace(false), 572 input: noConflictCandidates, 573 replaceIDs: []string{i3.ID()}, 574 expectErr: true, 575 }, 576 } 577 for _, test := range testCases { 578 p := NewPlacementService(nil, WithPlacementOptions(test.opts)).(*placementService) 579 res, err := p.selector.SelectReplaceInstances(test.input, test.replaceIDs, s) 580 if test.expectErr { 581 assert.Error(t, err) 582 continue 583 } 584 assert.Equal(t, test.expectRes, res) 585 } 586 } 587 588 func TestMirrorWorkflow(t *testing.T) { 589 h1p1 := placement.NewInstance(). 590 SetID("h1p1"). 591 SetHostname("h1"). 592 SetPort(1). 593 SetIsolationGroup("r1"). 594 SetZone("z1"). 595 SetEndpoint("h1p1e"). 596 SetWeight(1) 597 h1p2 := placement.NewInstance(). 598 SetID("h1p2"). 599 SetHostname("h1"). 600 SetPort(2). 601 SetIsolationGroup("r1"). 602 SetZone("z1"). 603 SetEndpoint("h1p2e"). 604 SetWeight(1) 605 h1p3 := placement.NewInstance(). 606 SetID("h1p3"). 607 SetHostname("h1"). 608 SetPort(3). 609 SetIsolationGroup("r1"). 610 SetZone("z1"). 611 SetEndpoint("h1p3e"). 612 SetWeight(1) 613 h2p1 := placement.NewInstance(). 614 SetID("h2p1"). 615 SetHostname("h2"). 616 SetPort(1). 617 SetIsolationGroup("r2"). 618 SetZone("z1"). 619 SetEndpoint("h2p1e"). 620 SetWeight(1) 621 h2p2 := placement.NewInstance(). 622 SetID("h2p2"). 623 SetHostname("h2"). 624 SetPort(2). 625 SetIsolationGroup("r2"). 626 SetZone("z1"). 627 SetEndpoint("h2p2e"). 628 SetWeight(1) 629 h2p3 := placement.NewInstance(). 630 SetID("h2p3"). 631 SetHostname("h2"). 632 SetPort(3). 633 SetIsolationGroup("r2"). 634 SetZone("z1"). 635 SetEndpoint("h2p3e"). 636 SetWeight(1) 637 h3p1 := placement.NewInstance(). 638 SetID("h3p1"). 639 SetHostname("h3"). 640 SetPort(1). 641 SetIsolationGroup("r1"). 642 SetZone("z1"). 643 SetEndpoint("h3p1e"). 644 SetWeight(2) 645 h3p2 := placement.NewInstance(). 646 SetID("h3p2"). 647 SetHostname("h3"). 648 SetPort(2). 649 SetIsolationGroup("r1"). 650 SetZone("z1"). 651 SetEndpoint("h3p2e"). 652 SetWeight(2) 653 h3p3 := placement.NewInstance(). 654 SetID("h3p3"). 655 SetHostname("h3"). 656 SetPort(3). 657 SetIsolationGroup("r1"). 658 SetZone("z1"). 659 SetEndpoint("h3p3e"). 660 SetWeight(2) 661 h4p1 := placement.NewInstance(). 662 SetID("h4p1"). 663 SetHostname("h4"). 664 SetPort(1). 665 SetIsolationGroup("r2"). 666 SetZone("z1"). 667 SetEndpoint("h4p1e"). 668 SetWeight(2) 669 h4p2 := placement.NewInstance(). 670 SetID("h4p2"). 671 SetHostname("h4"). 672 SetPort(2). 673 SetIsolationGroup("r2"). 674 SetZone("z1"). 675 SetEndpoint("h4p2e"). 676 SetWeight(2) 677 h4p3 := placement.NewInstance(). 678 SetID("h4p3"). 679 SetHostname("h4"). 680 SetPort(3). 681 SetIsolationGroup("r2"). 682 SetZone("z1"). 683 SetEndpoint("h4p3e"). 684 SetWeight(2) 685 686 ps := NewPlacementService( 687 newMockStorage(), 688 WithPlacementOptions(placement.NewOptions().SetValidZone("z1").SetIsMirrored(true)), 689 ) 690 691 p, err := ps.BuildInitialPlacement( 692 []placement.Instance{h1p1, h1p2, h1p3, h2p1, h2p2, h2p3, h3p1, h3p2, h3p3, h4p1, h4p2, h4p3}, 693 20, 694 2, 695 ) 696 assert.NoError(t, err) 697 assert.Equal(t, h1p1.ShardSetID(), h2p1.ShardSetID()) 698 assert.Equal(t, h1p2.ShardSetID(), h2p2.ShardSetID()) 699 assert.Equal(t, h1p3.ShardSetID(), h2p3.ShardSetID()) 700 assert.Equal(t, h3p1.ShardSetID(), h4p1.ShardSetID()) 701 assert.Equal(t, h3p2.ShardSetID(), h4p2.ShardSetID()) 702 assert.Equal(t, h3p3.ShardSetID(), h4p3.ShardSetID()) 703 assert.Equal(t, 12, p.NumInstances()) 704 705 h5p1 := placement.NewInstance(). 706 SetID("h5p1"). 707 SetHostname("h5"). 708 SetPort(1). 709 SetIsolationGroup("r1"). 710 SetZone("z1"). 711 SetEndpoint("h5p1e"). 712 SetWeight(2) 713 h6p1 := placement.NewInstance(). 714 SetID("h6p1"). 715 SetHostname("h6"). 716 SetPort(1). 717 SetIsolationGroup("r2"). 718 SetZone("z1"). 719 SetEndpoint("h6p1e"). 720 SetWeight(2) 721 722 _, addedInstances, err := ps.AddInstances([]placement.Instance{h5p1, h6p1}) 723 assert.NoError(t, err) 724 assert.Equal(t, 2, len(addedInstances)) 725 assert.Equal(t, addedInstances[0].ShardSetID(), addedInstances[1].ShardSetID()) 726 assert.Equal(t, uint32(7), addedInstances[0].ShardSetID()) 727 728 _, err = ps.RemoveInstances([]string{h5p1.ID(), h6p1.ID()}) 729 assert.NoError(t, err) 730 731 // Make sure reverting the removed instances reuses the old shard set id. 732 _, addedInstances, err = ps.AddInstances([]placement.Instance{h5p1.SetShardSetID(0), h6p1.SetShardSetID(0)}) 733 for _, instance := range addedInstances { 734 assert.Equal(t, uint32(7), instance.ShardSetID()) 735 } 736 assert.NoError(t, err) 737 738 h7p1 := placement.NewInstance(). 739 SetID("h7p1"). 740 SetHostname("h7"). 741 SetPort(1). 742 SetIsolationGroup("r2"). 743 SetZone("z1"). 744 SetEndpoint("h7p1e"). 745 SetWeight(2) 746 h7p2 := placement.NewInstance(). 747 SetID("h7p2"). 748 SetHostname("h7"). 749 SetPort(2). 750 SetIsolationGroup("r2"). 751 SetZone("z1"). 752 SetEndpoint("h7p2e"). 753 SetWeight(2) 754 h7p3 := placement.NewInstance(). 755 SetID("h7p3"). 756 SetHostname("h7"). 757 SetPort(3). 758 SetIsolationGroup("r2"). 759 SetZone("z1"). 760 SetEndpoint("h7p3e"). 761 SetWeight(2) 762 763 p, addedInstances, err = ps.ReplaceInstances( 764 []string{h4p1.ID(), h4p2.ID(), h4p3.ID()}, 765 []placement.Instance{h3p1, h3p2, h3p3, h7p1, h7p2, h7p3}, 766 ) 767 require.NoError(t, err) 768 h4p1, ok := p.Instance(h4p1.ID()) 769 assert.True(t, ok) 770 h4p2, ok = p.Instance(h4p2.ID()) 771 assert.True(t, ok) 772 h4p3, ok = p.Instance(h4p3.ID()) 773 assert.True(t, ok) 774 assert.Equal(t, h4p1.ShardSetID(), addedInstances[0].ShardSetID()) 775 assert.Equal(t, h4p1.Shards().AllIDs(), addedInstances[0].Shards().AllIDs()) 776 assert.Equal(t, h4p2.ShardSetID(), addedInstances[1].ShardSetID()) 777 assert.Equal(t, h4p2.Shards().AllIDs(), addedInstances[1].Shards().AllIDs()) 778 assert.Equal(t, h4p3.ShardSetID(), addedInstances[2].ShardSetID()) 779 assert.Equal(t, h4p3.Shards().AllIDs(), addedInstances[2].Shards().AllIDs()) 780 } 781 782 func TestManyShards(t *testing.T) { 783 p := NewPlacementService(newMockStorage(), 784 WithPlacementOptions(placement.NewOptions().SetValidZone("z1"))) 785 i1 := placement.NewEmptyInstance("i1", "r1", "z1", "endpoint", 2) 786 i2 := placement.NewEmptyInstance("i2", "r2", "z1", "endpoint", 2) 787 i3 := placement.NewEmptyInstance("i3", "r3", "z1", "endpoint", 2) 788 i4 := placement.NewEmptyInstance("i4", "r1", "z1", "endpoint", 2) 789 i5 := placement.NewEmptyInstance("i5", "r2", "z1", "endpoint", 2) 790 i6 := placement.NewEmptyInstance("i6", "r3", "z1", "endpoint", 2) 791 _, err := p.BuildInitialPlacement([]placement.Instance{i1, i2, i3, i4, i5, i6}, 8192, 1) 792 assert.NoError(t, err) 793 } 794 795 func TestAddMultipleInstances(t *testing.T) { 796 i1 := placement.NewInstance(). 797 SetID("i1"). 798 SetIsolationGroup("r1"). 799 SetEndpoint("i1"). 800 SetWeight(3) 801 i2 := placement.NewInstance(). 802 SetID("i2"). 803 SetIsolationGroup("r2"). 804 SetEndpoint("i2"). 805 SetWeight(1) 806 i3 := placement.NewInstance(). 807 SetID("i3"). 808 SetIsolationGroup("r2"). 809 SetEndpoint("i3"). 810 SetWeight(1) 811 i4 := placement.NewInstance(). 812 SetID("i4"). 813 SetIsolationGroup("r4"). 814 SetEndpoint("i4"). 815 SetWeight(2) 816 817 tests := []struct { 818 name string 819 opts placement.Options 820 initialInstances []placement.Instance 821 candidateInstances []placement.Instance 822 expectAdded []placement.Instance 823 }{ 824 { 825 name: "Add Single Candidate", 826 opts: placement.NewOptions().SetAddAllCandidates(false), 827 initialInstances: []placement.Instance{i1.Clone(), i2.Clone()}, 828 candidateInstances: []placement.Instance{i3.Clone(), i4.Clone()}, 829 expectAdded: []placement.Instance{i4}, // Prefer instance from different isolation group. 830 }, 831 { 832 name: "Add All Candidates", 833 opts: placement.NewOptions().SetAddAllCandidates(true), 834 initialInstances: []placement.Instance{i1.Clone(), i2.Clone()}, 835 candidateInstances: []placement.Instance{i3.Clone(), i4.Clone()}, 836 expectAdded: []placement.Instance{i3, i4}, 837 }, 838 } 839 840 for _, test := range tests { 841 t.Run(test.name, func(t *testing.T) { 842 ps := NewPlacementService(newMockStorage(), WithPlacementOptions(test.opts)) 843 _, err := ps.BuildInitialPlacement(test.initialInstances, 4, 2) 844 require.NoError(t, err) 845 846 _, added, err := ps.AddInstances(test.candidateInstances) 847 require.NoError(t, err) 848 require.True(t, compareInstances(test.expectAdded, added)) 849 }) 850 } 851 } 852 853 func TestReplaceInstances(t *testing.T) { 854 i1 := placement.NewInstance(). 855 SetID("i1"). 856 SetIsolationGroup("r1"). 857 SetEndpoint("i1"). 858 SetWeight(4) 859 i2 := placement.NewInstance(). 860 SetID("i2"). 861 SetIsolationGroup("r2"). 862 SetEndpoint("i2"). 863 SetWeight(2) 864 i3 := placement.NewInstance(). 865 SetID("i3"). 866 SetIsolationGroup("r2"). 867 SetEndpoint("i3"). 868 SetWeight(2) 869 i4 := placement.NewInstance(). 870 SetID("i4"). 871 SetIsolationGroup("r4"). 872 SetEndpoint("i4"). 873 SetWeight(1) 874 875 tests := []struct { 876 name string 877 opts placement.Options 878 initialInstances []placement.Instance 879 candidateInstances []placement.Instance 880 leavingIDs []string 881 expectErr bool 882 expectAdded []placement.Instance 883 }{ 884 { 885 name: "Replace With Instance of Same Weight", 886 opts: placement.NewOptions().SetAddAllCandidates(false).SetAllowPartialReplace(false), 887 initialInstances: []placement.Instance{i1.Clone(), i2.Clone()}, 888 candidateInstances: []placement.Instance{i3.Clone(), i4.Clone()}, 889 leavingIDs: []string{"i2"}, 890 expectErr: false, 891 expectAdded: []placement.Instance{i3}, 892 }, 893 { 894 name: "Add All Candidates", 895 opts: placement.NewOptions().SetAddAllCandidates(true).SetAllowPartialReplace(false), 896 initialInstances: []placement.Instance{i1.Clone(), i2.Clone()}, 897 candidateInstances: []placement.Instance{i3.Clone(), i4.Clone()}, 898 leavingIDs: []string{"i2"}, 899 expectErr: false, 900 expectAdded: []placement.Instance{i3, i4}, 901 }, 902 { 903 name: "Not Enough Weight With Partial Replace", 904 opts: placement.NewOptions().SetAddAllCandidates(false).SetAllowPartialReplace(true), 905 initialInstances: []placement.Instance{i1.Clone(), i2.Clone()}, 906 candidateInstances: []placement.Instance{i3.Clone(), i4.Clone()}, 907 leavingIDs: []string{"i1"}, 908 expectErr: false, 909 expectAdded: []placement.Instance{i3, i4}, 910 }, 911 { 912 name: "Not Enough Weight Without Partial Replace", 913 opts: placement.NewOptions().SetAddAllCandidates(false).SetAllowPartialReplace(false), 914 initialInstances: []placement.Instance{i1.Clone(), i2.Clone()}, 915 candidateInstances: []placement.Instance{i3.Clone(), i4.Clone()}, 916 leavingIDs: []string{"i1"}, 917 expectErr: true, 918 }, 919 } 920 921 for _, test := range tests { 922 t.Run(test.name, func(t *testing.T) { 923 ps := NewPlacementService(newMockStorage(), WithPlacementOptions(test.opts)) 924 _, err := ps.BuildInitialPlacement(test.initialInstances, 4, 2) 925 require.NoError(t, err) 926 927 _, added, err := ps.ReplaceInstances(test.leavingIDs, test.candidateInstances) 928 if test.expectErr { 929 require.Error(t, err) 930 return 931 } 932 require.NoError(t, err) 933 require.True(t, compareInstances(test.expectAdded, added)) 934 }) 935 } 936 } 937 938 func TestValidateFnBeforeUpdate(t *testing.T) { 939 p := NewPlacementService(newMockStorage(), 940 WithPlacementOptions(placement.NewOptions().SetValidZone("z1"))).(*placementService) 941 942 _, err := p.BuildInitialPlacement( 943 []placement.Instance{placement.NewEmptyInstance("i1", "r1", "z1", "endpoint1", 1)}, 944 10, 1) 945 assert.NoError(t, err) 946 947 expectErr := errors.New("err") 948 p.opts = p.opts.SetValidateFnBeforeUpdate(func(placement.Placement) error { return expectErr }) 949 _, _, err = p.AddInstances([]placement.Instance{placement.NewEmptyInstance("i2", "r2", "z1", "endpoint2", 1)}) 950 assert.Error(t, err) 951 assert.Equal(t, expectErr, err) 952 } 953 954 func TestPlacementServiceImplOptions(t *testing.T) { 955 placementOptions := placement.NewOptions().SetValidZone("foozone").SetIsSharded(true) 956 al := algo.NewAlgorithm(placementOptions.SetIsSharded(false)) 957 958 defaultImpl := newPlacementServiceImpl(nil) 959 require.NotNil(t, defaultImpl) 960 assert.NotNil(t, defaultImpl.opts) 961 assert.NotNil(t, defaultImpl.algo) 962 assert.NotEqual(t, placementOptions.ValidZone(), defaultImpl.opts.ValidZone()) 963 964 customImpl := newPlacementServiceImpl(nil, 965 WithPlacementOptions(placementOptions), 966 WithAlgorithm(al)) 967 assert.Equal(t, placementOptions.ValidZone(), customImpl.opts.ValidZone()) 968 assert.Equal(t, al, customImpl.algo) 969 } 970 971 func TestBalanceShards(t *testing.T) { 972 ms := newMockStorage() 973 974 i1 := placement.NewEmptyInstance("i1", "r1", "z1", "endpoint", 1) 975 i1.Shards().Add(shard.NewShard(0).SetState(shard.Available)) 976 i1.Shards().Add(shard.NewShard(1).SetState(shard.Available)) 977 i1.Shards().Add(shard.NewShard(2).SetState(shard.Available)) 978 979 i2 := placement.NewEmptyInstance("i2", "r1", "z1", "endpoint", 1) 980 i2.Shards().Add(shard.NewShard(3).SetState(shard.Available)) 981 982 instances := []placement.Instance{i1, i2} 983 p := placement.NewPlacement(). 984 SetInstances(instances). 985 SetShards([]uint32{0, 1, 2, 3}). 986 SetReplicaFactor(1). 987 SetIsSharded(true) 988 989 _, err := ms.SetIfNotExist(p) 990 assert.NoError(t, err) 991 992 ps := NewPlacementService(ms, WithPlacementOptions(placement.NewOptions())) 993 994 p, err = ps.BalanceShards() 995 assert.NoError(t, err) 996 997 bi1 := placement.NewEmptyInstance("i1", "r1", "z1", "endpoint", 1) 998 bi1.Shards().Add(shard.NewShard(0).SetState(shard.Leaving)) 999 bi1.Shards().Add(shard.NewShard(1).SetState(shard.Available)) 1000 bi1.Shards().Add(shard.NewShard(2).SetState(shard.Available)) 1001 1002 bi2 := placement.NewEmptyInstance("i2", "r1", "z1", "endpoint", 1) 1003 bi2.Shards().Add(shard.NewShard(0).SetState(shard.Initializing).SetSourceID("i1")) 1004 bi2.Shards().Add(shard.NewShard(3).SetState(shard.Available)) 1005 1006 expectedInstances := []placement.Instance{bi1, bi2} 1007 assert.Equal(t, expectedInstances, p.Instances()) 1008 } 1009 1010 func newMockStorage() placement.Storage { 1011 return storage.NewPlacementStorage(mem.NewStore(), "", nil) 1012 } 1013 1014 func markAllInstancesAvailable( 1015 t *testing.T, 1016 ps placement.Service, 1017 ) { 1018 p, err := ps.Placement() 1019 require.NoError(t, err) 1020 for _, i := range p.Instances() { 1021 if len(i.Shards().ShardsForState(shard.Initializing)) == 0 { 1022 continue 1023 } 1024 _, err := ps.MarkInstanceAvailable(i.ID()) 1025 require.NoError(t, err) 1026 } 1027 } 1028 1029 func compareInstances(left, right []placement.Instance) bool { 1030 if len(left) != len(right) { 1031 return false 1032 } 1033 1034 ids := make(map[string]struct{}, len(left)) 1035 for _, intce := range left { 1036 ids[intce.ID()] = struct{}{} 1037 } 1038 1039 for _, intce := range right { 1040 if _, ok := ids[intce.ID()]; !ok { 1041 return false 1042 } 1043 } 1044 return true 1045 }