github.com/m3db/m3@v1.5.0/src/cluster/placement/algo/sharded_helper_test.go (about) 1 // Copyright (c) 2016 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package algo 22 23 import ( 24 "fmt" 25 "sort" 26 "testing" 27 "time" 28 29 "github.com/m3db/m3/src/cluster/placement" 30 "github.com/m3db/m3/src/cluster/shard" 31 32 "github.com/stretchr/testify/assert" 33 "github.com/stretchr/testify/require" 34 ) 35 36 func TestMoveInitializingShard(t *testing.T) { 37 i1 := placement.NewEmptyInstance("i1", "r1", "z1", "endpoint", 1) 38 i1.Shards().Add(shard.NewShard(1).SetState(shard.Available)) 39 i1.Shards().Add(shard.NewShard(3).SetState(shard.Leaving)) 40 41 i2 := placement.NewEmptyInstance("i2", "r2", "z1", "endpoint", 1) 42 i2.Shards().Add(shard.NewShard(2).SetState(shard.Available)) 43 44 i3 := placement.NewEmptyInstance("i3", "r3", "z1", "endpoint", 1) 45 i3.Shards().Add(shard.NewShard(3).SetState(shard.Initializing).SetSourceID("i1")) 46 47 instances := []placement.Instance{i1, i2, i3} 48 p := placement.NewPlacement().SetInstances(instances).SetShards([]uint32{1, 2, 3}).SetReplicaFactor(1) 49 ph := newHelper(p, 3, placement.NewOptions()).(*helper) 50 51 // move an Initializing shard 52 s3, ok := i3.Shards().Shard(3) 53 assert.True(t, ok) 54 assert.True(t, ph.moveShard(s3, i3, i2)) 55 _, ok = i3.Shards().Shard(3) 56 assert.False(t, ok) 57 // i2 now owns it 58 s3, ok = i2.Shards().Shard(3) 59 assert.True(t, ok) 60 assert.Equal(t, "i1", s3.SourceID()) 61 assert.Equal(t, shard.Unknown, s3.State()) 62 } 63 64 func TestMoveInitializingShardBackToSource(t *testing.T) { 65 i1 := placement.NewEmptyInstance("i1", "r1", "z1", "endpoint", 1) 66 i1.Shards().Add(shard.NewShard(1).SetState(shard.Leaving)) 67 68 i2 := placement.NewEmptyInstance("i2", "r2", "z1", "endpoint", 1) 69 i2.Shards().Add(shard.NewShard(1).SetState(shard.Initializing).SetSourceID("i1")) 70 71 instances := []placement.Instance{i1, i2} 72 p := placement.NewPlacement().SetInstances(instances).SetShards([]uint32{1}).SetReplicaFactor(1) 73 ph := newHelper(p, 3, placement.NewOptions()).(*helper) 74 75 s1, ok := i2.Shards().Shard(1) 76 assert.True(t, ok) 77 assert.True(t, ph.moveShard(s1, i2, i1)) 78 _, ok = i2.Shards().Shard(1) 79 assert.False(t, ok) 80 // i1 now owns it 81 s1, ok = i1.Shards().Shard(1) 82 assert.True(t, ok) 83 assert.Equal(t, "", s1.SourceID()) 84 assert.Equal(t, shard.Available, s1.State()) 85 } 86 87 func TestMoveLeavingShard(t *testing.T) { 88 i1 := placement.NewEmptyInstance("i1", "r1", "z1", "endpoint", 1) 89 i1.Shards().Add(shard.NewShard(1).SetState(shard.Available)) 90 i1.Shards().Add(shard.NewShard(3).SetState(shard.Leaving)) 91 92 i2 := placement.NewEmptyInstance("i2", "r2", "z1", "endpoint", 1) 93 i2.Shards().Add(shard.NewShard(2).SetState(shard.Available)) 94 95 i3 := placement.NewEmptyInstance("i3", "r3", "z1", "endpoint", 1) 96 i3.Shards().Add(shard.NewShard(3).SetState(shard.Initializing).SetSourceID("i1")) 97 98 instances := []placement.Instance{i1, i2, i3} 99 p := placement.NewPlacement().SetInstances(instances).SetShards([]uint32{1, 2, 3}).SetReplicaFactor(1) 100 ph := newHelper(p, 3, placement.NewOptions()).(*helper) 101 102 // make sure Leaving shard could not be moved 103 s3, ok := i1.Shards().Shard(3) 104 assert.True(t, ok) 105 assert.False(t, ph.moveShard(s3, i1, i2)) 106 } 107 108 func TestMoveAvailableShard(t *testing.T) { 109 i1 := placement.NewEmptyInstance("i1", "r1", "z1", "endpoint", 1) 110 i1.Shards().Add(shard.NewShard(1).SetState(shard.Available)) 111 112 i2 := placement.NewEmptyInstance("i2", "r2", "z1", "endpoint", 1) 113 i2.Shards().Add(shard.NewShard(2).SetState(shard.Available)) 114 115 i3 := placement.NewEmptyInstance("i3", "r3", "z1", "endpoint", 1) 116 i3.Shards().Add(shard.NewShard(3).SetState(shard.Available)) 117 118 instances := []placement.Instance{i1, i2, i3} 119 p := placement.NewPlacement().SetInstances(instances).SetShards([]uint32{1, 2, 3}).SetReplicaFactor(1) 120 ph := newHelper(p, 3, placement.NewOptions()).(*helper) 121 122 s3, ok := i3.Shards().Shard(3) 123 assert.True(t, ok) 124 assert.True(t, ph.moveShard(s3, i3, i2)) 125 assert.Equal(t, shard.Leaving, s3.State()) 126 assert.Equal(t, "", s3.SourceID()) 127 s3, ok = i2.Shards().Shard(3) 128 assert.True(t, ok) 129 assert.Equal(t, shard.Unknown, s3.State()) 130 assert.Equal(t, "i3", s3.SourceID()) 131 } 132 133 func TestAssignShard(t *testing.T) { 134 i1 := placement.NewEmptyInstance("i1", "r1", "z1", "endpoint", 1) 135 i1.Shards().Add(shard.NewShard(1).SetState(shard.Available)) 136 i1.Shards().Add(shard.NewShard(2).SetState(shard.Available)) 137 i1.Shards().Add(shard.NewShard(3).SetState(shard.Leaving)) 138 139 i2 := placement.NewEmptyInstance("i2", "r1", "z1", "endpoint", 1) 140 i2.Shards().Add(shard.NewShard(4).SetState(shard.Available)) 141 i2.Shards().Add(shard.NewShard(5).SetState(shard.Available)) 142 i2.Shards().Add(shard.NewShard(6).SetState(shard.Available)) 143 144 i3 := placement.NewEmptyInstance("i3", "r2", "z1", "endpoint", 1) 145 i3.Shards().Add(shard.NewShard(1).SetState(shard.Available)) 146 i3.Shards().Add(shard.NewShard(3).SetState(shard.Available)) 147 i3.Shards().Add(shard.NewShard(5).SetState(shard.Available)) 148 149 i4 := placement.NewEmptyInstance("i4", "r2", "z1", "endpoint", 1) 150 i4.Shards().Add(shard.NewShard(2).SetState(shard.Available)) 151 i4.Shards().Add(shard.NewShard(4).SetState(shard.Available)) 152 i4.Shards().Add(shard.NewShard(6).SetState(shard.Available)) 153 154 i5 := placement.NewEmptyInstance("i5", "r3", "z1", "endpoint", 1) 155 i5.Shards().Add(shard.NewShard(5).SetState(shard.Available)) 156 i5.Shards().Add(shard.NewShard(6).SetState(shard.Available)) 157 i5.Shards().Add(shard.NewShard(1).SetState(shard.Available)) 158 i5.Shards().Add(shard.NewShard(3).SetState(shard.Initializing).SetSourceID("i1")) 159 160 i6 := placement.NewEmptyInstance("i6", "r4", "z1", "endpoint", 1) 161 i6.Shards().Add(shard.NewShard(2).SetState(shard.Available)) 162 i6.Shards().Add(shard.NewShard(3).SetState(shard.Available)) 163 i6.Shards().Add(shard.NewShard(4).SetState(shard.Available)) 164 165 instances := []placement.Instance{i1, i2, i3, i4, i5, i6} 166 p := placement.NewPlacement(). 167 SetInstances(instances). 168 SetShards([]uint32{1, 2, 3, 4, 5, 6}). 169 SetReplicaFactor(3) 170 171 ph := newHelper(p, 3, placement.NewOptions()).(*helper) 172 assert.True(t, ph.canAssignInstance(2, i6, i5)) 173 assert.True(t, ph.canAssignInstance(1, i1, i6)) 174 assert.False(t, ph.canAssignInstance(2, i6, i1)) 175 assert.False(t, ph.canAssignInstance(2, i6, i3)) 176 } 177 178 func TestNonLeavingInstances(t *testing.T) { 179 instances := []placement.Instance{ 180 placement.NewInstance(). 181 SetID("i1"). 182 SetShards(shard.NewShards([]shard.Shard{shard.NewShard(1).SetState(shard.Initializing)})), 183 placement.NewInstance(). 184 SetID("i2"). 185 SetShards(shard.NewShards([]shard.Shard{shard.NewShard(1).SetState(shard.Leaving)})), 186 placement.NewInstance(). 187 SetID("i2"), 188 } 189 r := nonLeavingInstances(instances) 190 assert.Equal(t, 2, len(r)) 191 assert.Equal(t, "i1", r[0].ID()) 192 assert.Equal(t, "i2", r[1].ID()) 193 } 194 195 func TestLoadOnInstance(t *testing.T) { 196 i1 := placement.NewEmptyInstance("i1", "r1", "z1", "endpoint", 1) 197 i1.Shards().Add(shard.NewShard(1).SetState(shard.Initializing)) 198 assert.Equal(t, 1, loadOnInstance(i1)) 199 200 i1.Shards().Add(shard.NewShard(2).SetState(shard.Available)) 201 assert.Equal(t, 2, loadOnInstance(i1)) 202 203 i1.Shards().Add(shard.NewShard(3).SetState(shard.Leaving)) 204 assert.Equal(t, 2, loadOnInstance(i1)) 205 } 206 207 func TestReturnInitShardToSource(t *testing.T) { 208 i1 := placement.NewInstance(). 209 SetID("i1"). 210 SetIsolationGroup("r1"). 211 SetEndpoint("e1"). 212 SetWeight(1). 213 SetShards(shard.NewShards( 214 []shard.Shard{ 215 shard.NewShard(0).SetState(shard.Initializing).SetSourceID("i2"), 216 shard.NewShard(1).SetState(shard.Available), 217 }, 218 )) 219 i2 := placement.NewInstance(). 220 SetID("i2"). 221 SetIsolationGroup("r2"). 222 SetEndpoint("e2"). 223 SetWeight(1). 224 SetShards(shard.NewShards( 225 []shard.Shard{ 226 shard.NewShard(0).SetState(shard.Leaving), 227 shard.NewShard(2).SetState(shard.Available), 228 }, 229 )) 230 i3 := placement.NewInstance(). 231 SetID("i3"). 232 SetIsolationGroup("r3"). 233 SetEndpoint("e3"). 234 SetWeight(100). 235 SetShards(shard.NewShards( 236 []shard.Shard{}, 237 )) 238 ph := NewPlacementHelper( 239 placement.NewPlacement(). 240 SetInstances([]placement.Instance{i1, i2, i3}). 241 SetReplicaFactor(1). 242 SetShards([]uint32{0, 1, 2}). 243 SetIsSharded(true), 244 placement.NewOptions(), 245 ).(*helper) 246 247 ph.returnInitializingShardsToSource(getShardMap(i1.Shards().All()), i1, ph.Instances()) 248 249 // Only the Initializing shards are moved 250 assert.Equal(t, 1, i1.Shards().NumShards()) 251 assert.Equal(t, []shard.Shard{shard.NewShard(1).SetState(shard.Available)}, i1.Shards().All()) 252 assert.Equal(t, 2, i2.Shards().NumShards()) 253 assert.Equal(t, []shard.Shard{ 254 shard.NewShard(0).SetState(shard.Available), 255 shard.NewShard(2).SetState(shard.Available), 256 }, i2.Shards().All()) 257 assert.Equal(t, 0, i3.Shards().NumShards()) 258 } 259 260 func TestReturnInitShardToSource_SourceIsLeaving(t *testing.T) { 261 i1 := placement.NewInstance(). 262 SetID("i1"). 263 SetIsolationGroup("r1"). 264 SetEndpoint("e1"). 265 SetWeight(1). 266 SetShards(shard.NewShards( 267 []shard.Shard{shard.NewShard(0).SetState(shard.Initializing).SetSourceID("i2")}, 268 )) 269 i2 := placement.NewInstance(). 270 SetID("i2"). 271 SetIsolationGroup("r2"). 272 SetEndpoint("e2"). 273 SetWeight(1). 274 SetShards(shard.NewShards( 275 []shard.Shard{shard.NewShard(0).SetState(shard.Leaving)}, 276 )) 277 i3 := placement.NewInstance(). 278 SetID("i3"). 279 SetIsolationGroup("r3"). 280 SetEndpoint("e3"). 281 SetWeight(1). 282 SetShards(shard.NewShards( 283 []shard.Shard{}, 284 )) 285 286 ph := NewPlacementHelper( 287 placement.NewPlacement(). 288 SetInstances([]placement.Instance{i1, i2, i3}). 289 SetReplicaFactor(1). 290 SetShards([]uint32{0}). 291 SetIsSharded(true), 292 placement.NewOptions(), 293 ).(*helper) 294 295 ph.returnInitializingShardsToSource(getShardMap(i1.Shards().All()), i1, ph.Instances()) 296 297 assert.Equal(t, 1, i1.Shards().NumShards()) 298 assert.Equal(t, []shard.Shard{shard.NewShard(0).SetState(shard.Initializing).SetSourceID("i2")}, i1.Shards().All()) 299 assert.Equal(t, 1, i2.Shards().NumShards()) 300 assert.Equal(t, []shard.Shard{shard.NewShard(0).SetState(shard.Leaving)}, i2.Shards().All()) 301 assert.Equal(t, 0, i3.Shards().NumShards()) 302 } 303 304 func TestGeneratePlacement(t *testing.T) { 305 i1 := placement.NewInstance(). 306 SetID("i1"). 307 SetIsolationGroup("r1"). 308 SetEndpoint("e1"). 309 SetWeight(1). 310 SetShards(shard.NewShards( 311 []shard.Shard{shard.NewShard(0).SetState(shard.Initializing).SetSourceID("i2")}, 312 )) 313 i2 := placement.NewInstance(). 314 SetID("i2"). 315 SetIsolationGroup("r2"). 316 SetEndpoint("e2"). 317 SetWeight(1). 318 SetShards(shard.NewShards( 319 []shard.Shard{shard.NewShard(0).SetState(shard.Leaving)}, 320 )) 321 i3 := placement.NewInstance(). 322 SetID("i3"). 323 SetIsolationGroup("r3"). 324 SetEndpoint("e3"). 325 SetWeight(1). 326 SetShards(shard.NewShards( 327 []shard.Shard{}, 328 )) 329 330 ph := newHelper( 331 placement.NewPlacement(). 332 SetInstances([]placement.Instance{i1, i2, i3}). 333 SetReplicaFactor(1). 334 SetShards([]uint32{0}). 335 SetIsSharded(true), 336 1, 337 placement.NewOptions(), 338 ) 339 340 p := ph.generatePlacement() 341 assert.Equal(t, 2, p.NumInstances()) 342 } 343 344 func TestReturnInitShardToSource_IsolationGroupConflict(t *testing.T) { 345 i1 := placement.NewInstance(). 346 SetID("i1"). 347 SetIsolationGroup("r1"). 348 SetEndpoint("e1"). 349 SetWeight(1). 350 SetShards(shard.NewShards( 351 []shard.Shard{shard.NewShard(0).SetState(shard.Initializing).SetSourceID("i2")}, 352 )) 353 i2 := placement.NewInstance(). 354 SetID("i2"). 355 SetIsolationGroup("r2"). 356 SetEndpoint("e2"). 357 SetWeight(1). 358 SetShards(shard.NewShards( 359 []shard.Shard{shard.NewShard(0).SetState(shard.Leaving)}, 360 )) 361 i3 := placement.NewInstance(). 362 SetID("i3"). 363 SetIsolationGroup("r3"). 364 SetEndpoint("e3"). 365 SetWeight(1). 366 SetShards(shard.NewShards( 367 []shard.Shard{}, 368 )) 369 i4 := placement.NewInstance(). 370 SetID("i4"). 371 SetIsolationGroup("r2"). 372 SetEndpoint("e4"). 373 SetWeight(1). 374 SetShards(shard.NewShards( 375 []shard.Shard{shard.NewShard(0).SetState(shard.Available)}, 376 )) 377 378 ph := NewPlacementHelper( 379 placement.NewPlacement(). 380 SetInstances([]placement.Instance{i1, i2, i3, i4}). 381 SetReplicaFactor(2). 382 SetShards([]uint32{0}). 383 SetIsSharded(true), 384 placement.NewOptions(), 385 ).(*helper) 386 387 ph.returnInitializingShardsToSource(getShardMap(i1.Shards().All()), i1, ph.Instances()) 388 389 // the Initializing shard will not be returned to i2 390 // because another replica of shard 0 is owned by i4, which is also on r2 391 assert.Equal(t, 1, i1.Shards().NumShards()) 392 assert.Equal(t, []shard.Shard{shard.NewShard(0).SetState(shard.Initializing).SetSourceID("i2")}, i1.Shards().All()) 393 assert.Equal(t, 1, i2.Shards().NumShards()) 394 assert.Equal(t, []shard.Shard{shard.NewShard(0).SetState(shard.Leaving)}, i2.Shards().All()) 395 assert.Equal(t, 0, i3.Shards().NumShards()) 396 assert.Equal(t, 1, i4.Shards().NumShards()) 397 assert.Equal(t, []shard.Shard{shard.NewShard(0).SetState(shard.Available)}, i4.Shards().All()) 398 399 // make sure placeShards will handle the unreturned shards 400 i1 = placement.NewInstance(). 401 SetID("i1"). 402 SetIsolationGroup("r1"). 403 SetEndpoint("e1"). 404 SetWeight(1). 405 SetShards(shard.NewShards( 406 []shard.Shard{shard.NewShard(0).SetState(shard.Initializing).SetSourceID("i2")}, 407 )) 408 i2 = placement.NewInstance(). 409 SetID("i2"). 410 SetIsolationGroup("r2"). 411 SetEndpoint("e2"). 412 SetWeight(1). 413 SetShards(shard.NewShards( 414 []shard.Shard{shard.NewShard(0).SetState(shard.Leaving)}, 415 )) 416 i3 = placement.NewInstance(). 417 SetID("i3"). 418 SetIsolationGroup("r3"). 419 SetEndpoint("e3"). 420 SetWeight(1). 421 SetShards(shard.NewShards( 422 []shard.Shard{}, 423 )) 424 i4 = placement.NewInstance(). 425 SetID("i4"). 426 SetIsolationGroup("r2"). 427 SetEndpoint("e4"). 428 SetWeight(1). 429 SetShards(shard.NewShards( 430 []shard.Shard{shard.NewShard(0).SetState(shard.Available)}, 431 )) 432 433 ph = NewPlacementHelper( 434 placement.NewPlacement(). 435 SetInstances([]placement.Instance{i1, i2, i3, i4}). 436 SetReplicaFactor(2). 437 SetShards([]uint32{0}). 438 SetIsSharded(true), 439 placement.NewOptions(), 440 ).(*helper) 441 442 err := ph.placeShards(i1.Shards().All(), i1, ph.Instances()) 443 assert.NoError(t, err) 444 assert.Equal(t, 0, i1.Shards().NumShards()) 445 assert.Equal(t, 1, i2.Shards().NumShards()) 446 assert.Equal(t, []shard.Shard{shard.NewShard(0).SetState(shard.Leaving)}, i2.Shards().All()) 447 assert.Equal(t, 1, i3.Shards().NumShards()) 448 assert.Equal(t, []shard.Shard{shard.NewShard(0).SetSourceID("i2")}, i3.Shards().All()) 449 assert.Equal(t, 1, i4.Shards().NumShards()) 450 assert.Equal(t, []shard.Shard{shard.NewShard(0).SetState(shard.Available)}, i4.Shards().All()) 451 } 452 453 func TestMarkShardSuccess(t *testing.T) { 454 i1 := placement.NewEmptyInstance("i1", "", "", "endpoint", 1) 455 i1.Shards().Add(shard.NewShard(1).SetState(shard.Initializing)) 456 i1.Shards().Add(shard.NewShard(2).SetState(shard.Initializing).SetSourceID("i2")) 457 458 i2 := placement.NewEmptyInstance("i2", "", "", "endpoint", 1) 459 i2.Shards().Add(shard.NewShard(1).SetState(shard.Initializing)) 460 i2.Shards().Add(shard.NewShard(2).SetState(shard.Leaving)) 461 462 p := placement.NewPlacement(). 463 SetInstances([]placement.Instance{i1, i2}). 464 SetShards([]uint32{1, 2}). 465 SetReplicaFactor(2) 466 467 opts := placement.NewOptions() 468 _, err := markShardsAvailable(p, "i1", []uint32{1}, opts) 469 assert.NoError(t, err) 470 471 _, err = markShardsAvailable(p, "i1", []uint32{2}, opts) 472 assert.NoError(t, err) 473 } 474 475 func TestMarkShardSuccessBulk(t *testing.T) { 476 i1 := placement.NewEmptyInstance("i1", "", "", "endpoint", 1) 477 i1.Shards().Add(shard.NewShard(1).SetState(shard.Initializing)) 478 i1.Shards().Add(shard.NewShard(2).SetState(shard.Initializing).SetSourceID("i2")) 479 480 i2 := placement.NewEmptyInstance("i2", "", "", "endpoint", 1) 481 i2.Shards().Add(shard.NewShard(1).SetState(shard.Initializing)) 482 i2.Shards().Add(shard.NewShard(2).SetState(shard.Leaving)) 483 484 p := placement.NewPlacement(). 485 SetInstances([]placement.Instance{i1, i2}). 486 SetShards([]uint32{1, 2}). 487 SetReplicaFactor(2) 488 489 opts := placement.NewOptions() 490 modifiedPlacement, err := markShardsAvailable(p, "i1", []uint32{1, 2}, opts) 491 assert.NoError(t, err) 492 493 mi1, ok := modifiedPlacement.Instance("i1") 494 require.True(t, ok) 495 496 shards := mi1.Shards().All() 497 require.Len(t, shards, 2) 498 for _, s := range shards { 499 require.Equal(t, shard.Available, s.State()) 500 } 501 } 502 503 func TestMarkShardFailure(t *testing.T) { 504 i1 := placement.NewEmptyInstance("i1", "", "", "endpoint", 1) 505 i1.Shards().Add(shard.NewShard(1).SetState(shard.Available)) 506 i1.Shards().Add(shard.NewShard(2).SetState(shard.Available)) 507 508 i2 := placement.NewEmptyInstance("i2", "", "", "endpoint", 1) 509 i2.Shards().Add(shard.NewShard(1).SetState(shard.Initializing).SetSourceID("i3")) 510 i2.Shards().Add(shard.NewShard(2).SetState(shard.Initializing).SetSourceID("i1")) 511 i2.Shards().Add(shard.NewShard(3).SetState(shard.Initializing).SetSourceID("i1")) 512 513 p := placement.NewPlacement(). 514 SetInstances([]placement.Instance{i1, i2}). 515 SetShards([]uint32{1, 2}). 516 SetReplicaFactor(2) 517 518 opts := placement.NewOptions(). 519 SetIsShardCutoverFn(genShardCutoverFn(time.Now())). 520 SetIsShardCutoffFn(genShardCutoffFn(time.Now(), time.Minute)) 521 _, err := markShardsAvailable(p, "i3", []uint32{1}, opts) 522 assert.Error(t, err) 523 assert.Contains(t, err.Error(), "does not exist in placement") 524 525 _, err = markShardsAvailable(p, "i1", []uint32{3}, opts) 526 assert.Error(t, err) 527 assert.Contains(t, err.Error(), "does not exist in instance") 528 529 _, err = markShardsAvailable(p, "i1", []uint32{1}, opts) 530 assert.Error(t, err) 531 assert.Contains(t, err.Error(), "not in Initializing state") 532 533 _, err = markShardsAvailable(p, "i2", []uint32{1}, opts) 534 assert.Error(t, err) 535 assert.Contains(t, err.Error(), "does not exist in placement") 536 537 _, err = markShardsAvailable(p, "i2", []uint32{3}, opts) 538 assert.Error(t, err) 539 assert.Contains(t, err.Error(), "does not exist in source instance") 540 541 _, err = markShardsAvailable(p, "i2", []uint32{2}, opts) 542 assert.Error(t, err) 543 assert.Contains(t, err.Error(), "not leaving instance") 544 } 545 546 func TestMarkShardAsAvailableWithoutValidation(t *testing.T) { 547 var ( 548 cutoverTime = time.Now().Add(time.Hour) 549 cutoverTimeNanos = cutoverTime.UnixNano() 550 ) 551 i1 := placement.NewEmptyInstance("i1", "", "", "e1", 1) 552 i1.Shards().Add(shard.NewShard(0).SetState(shard.Leaving).SetCutoffNanos(cutoverTimeNanos)) 553 554 i2 := placement.NewEmptyInstance("i2", "", "", "e2", 1) 555 i2.Shards().Add(shard.NewShard(0).SetState(shard.Initializing).SetSourceID("i1").SetCutoverNanos(cutoverTimeNanos)) 556 557 p := placement.NewPlacement(). 558 SetInstances([]placement.Instance{i1, i2}). 559 SetShards([]uint32{0}). 560 SetReplicaFactor(1). 561 SetIsSharded(true). 562 SetIsMirrored(true) 563 564 opts := placement.NewOptions() 565 _, err := markShardsAvailable(p.Clone(), "i2", []uint32{0}, opts) 566 assert.NoError(t, err) 567 568 opts = placement.NewOptions().SetIsShardCutoverFn(nil).SetIsShardCutoffFn(nil) 569 _, err = markShardsAvailable(p.Clone(), "i2", []uint32{0}, opts) 570 assert.NoError(t, err) 571 } 572 573 func TestMarkShardAsAvailableWithValidation(t *testing.T) { 574 var ( 575 cutoverTime = time.Now() 576 cutoverTimeNanos = cutoverTime.UnixNano() 577 maxTimeWindow = time.Hour 578 tenMinutesInThePast = cutoverTime.Add(1 - 0*time.Minute) 579 tenMinutesInTheFuture = cutoverTime.Add(10 * time.Minute) 580 oneHourInTheFuture = cutoverTime.Add(maxTimeWindow) 581 ) 582 i1 := placement.NewEmptyInstance("i1", "", "", "e1", 1) 583 i1.Shards().Add(shard.NewShard(0).SetState(shard.Leaving).SetCutoffNanos(cutoverTimeNanos)) 584 585 i2 := placement.NewEmptyInstance("i2", "", "", "e2", 1) 586 i2.Shards().Add(shard.NewShard(0).SetState(shard.Initializing).SetSourceID("i1").SetCutoverNanos(cutoverTimeNanos)) 587 588 p := placement.NewPlacement(). 589 SetInstances([]placement.Instance{i1, i2}). 590 SetShards([]uint32{0}). 591 SetReplicaFactor(1). 592 SetIsSharded(true). 593 SetIsMirrored(true) 594 595 opts := placement.NewOptions(). 596 SetIsShardCutoverFn(genShardCutoverFn(tenMinutesInThePast)). 597 SetIsShardCutoffFn(genShardCutoffFn(tenMinutesInThePast, time.Hour)) 598 _, err := markShardsAvailable(p.Clone(), "i2", []uint32{0}, opts) 599 assert.Error(t, err) 600 601 opts = placement.NewOptions(). 602 SetIsShardCutoverFn(genShardCutoverFn(tenMinutesInTheFuture)). 603 SetIsShardCutoffFn(genShardCutoffFn(tenMinutesInTheFuture, time.Hour)) 604 _, err = markShardsAvailable(p.Clone(), "i2", []uint32{0}, opts) 605 assert.Error(t, err) 606 607 opts = placement.NewOptions(). 608 SetIsShardCutoverFn(genShardCutoverFn(oneHourInTheFuture)). 609 SetIsShardCutoffFn(genShardCutoffFn(oneHourInTheFuture, time.Hour)) 610 p, err = markShardsAvailable(p.Clone(), "i2", []uint32{0}, opts) 611 assert.NoError(t, err) 612 assert.NoError(t, placement.Validate(p)) 613 } 614 615 func TestRemoveInstanceFromArray(t *testing.T) { 616 instances := []placement.Instance{ 617 placement.NewEmptyInstance("i1", "", "", "endpoint", 1), 618 placement.NewEmptyInstance("i2", "", "", "endpoint", 1), 619 } 620 621 assert.Equal(t, instances, removeInstanceFromList(instances, "not_exist")) 622 assert.Equal(t, []placement.Instance{placement.NewEmptyInstance("i2", "", "", "endpoint", 1)}, removeInstanceFromList(instances, "i1")) 623 } 624 625 func TestMarkAllAsAvailable(t *testing.T) { 626 i1 := placement.NewEmptyInstance("i1", "", "", "endpoint", 1) 627 i1.Shards().Add(shard.NewShard(1).SetState(shard.Initializing)) 628 i1.Shards().Add(shard.NewShard(2).SetState(shard.Initializing)) 629 630 i2 := placement.NewEmptyInstance("i2", "", "", "endpoint", 1) 631 i2.Shards().Add(shard.NewShard(1).SetState(shard.Initializing)) 632 i2.Shards().Add(shard.NewShard(2).SetState(shard.Initializing)) 633 634 p := placement.NewPlacement(). 635 SetInstances([]placement.Instance{i1, i2}). 636 SetShards([]uint32{1, 2}). 637 SetReplicaFactor(2) 638 639 opts := placement.NewOptions() 640 _, _, err := markAllShardsAvailable(p, opts) 641 assert.NoError(t, err) 642 643 i2.Shards().Add(shard.NewShard(3).SetState(shard.Initializing).SetSourceID("i3")) 644 p = placement.NewPlacement(). 645 SetInstances([]placement.Instance{i1, i2}). 646 SetShards([]uint32{1, 2}). 647 SetReplicaFactor(2) 648 _, _, err = markAllShardsAvailable(p, opts) 649 assert.Contains(t, err.Error(), "does not exist in placement") 650 } 651 652 // nolint: dupl 653 func TestOptimize(t *testing.T) { 654 rf := 1 655 tests := []struct { 656 name string 657 shards []uint32 658 instancesBefore []placement.Instance 659 instancesAfter []placement.Instance 660 }{ 661 { 662 name: "empty", 663 instancesBefore: []placement.Instance{}, 664 instancesAfter: []placement.Instance{}, 665 }, 666 { 667 name: "no optimization when instance is off by one", 668 shards: []uint32{1, 2, 3, 4, 5}, 669 instancesBefore: []placement.Instance{ 670 placement.NewInstance().SetID("i1").SetWeight(1).SetShards( 671 shard.NewShards([]shard.Shard{ 672 shard.NewShard(1).SetState(shard.Available), 673 shard.NewShard(2).SetState(shard.Available), 674 })), 675 placement.NewInstance().SetID("i2").SetWeight(1).SetShards( 676 shard.NewShards([]shard.Shard{ 677 shard.NewShard(3).SetState(shard.Available), 678 shard.NewShard(4).SetState(shard.Available), 679 })), 680 placement.NewInstance().SetID("i3").SetWeight(1).SetShards( 681 shard.NewShards([]shard.Shard{ 682 shard.NewShard(5).SetState(shard.Available), 683 })), 684 }, 685 instancesAfter: []placement.Instance{ 686 placement.NewInstance().SetID("i1").SetWeight(1).SetShards( 687 shard.NewShards([]shard.Shard{ 688 shard.NewShard(1).SetState(shard.Available), 689 shard.NewShard(2).SetState(shard.Available), 690 })), 691 placement.NewInstance().SetID("i2").SetWeight(1).SetShards( 692 shard.NewShards([]shard.Shard{ 693 shard.NewShard(3).SetState(shard.Available), 694 shard.NewShard(4).SetState(shard.Available), 695 })), 696 placement.NewInstance().SetID("i3").SetWeight(1).SetShards( 697 shard.NewShards([]shard.Shard{ 698 shard.NewShard(5).SetState(shard.Available), 699 })), 700 }, 701 }, 702 { 703 name: "optimizing one imbalanced instance", 704 shards: []uint32{1, 2, 3, 4, 5, 6}, 705 instancesBefore: []placement.Instance{ 706 placement.NewInstance().SetID("i1").SetWeight(1).SetShards( 707 shard.NewShards([]shard.Shard{ 708 shard.NewShard(1).SetState(shard.Available), 709 shard.NewShard(2).SetState(shard.Available), 710 shard.NewShard(3).SetState(shard.Available), 711 })), 712 placement.NewInstance().SetID("i2").SetWeight(1).SetShards( 713 shard.NewShards([]shard.Shard{ 714 shard.NewShard(4).SetState(shard.Available), 715 shard.NewShard(5).SetState(shard.Available), 716 })), 717 placement.NewInstance().SetID("i3").SetWeight(1).SetShards( 718 shard.NewShards([]shard.Shard{ 719 shard.NewShard(6).SetState(shard.Available), 720 })), 721 }, 722 instancesAfter: []placement.Instance{ 723 placement.NewInstance().SetID("i1").SetWeight(1).SetShards( 724 shard.NewShards([]shard.Shard{ 725 shard.NewShard(1).SetState(shard.Leaving), 726 shard.NewShard(2).SetState(shard.Available), 727 shard.NewShard(3).SetState(shard.Available), 728 })), 729 placement.NewInstance().SetID("i2").SetWeight(1).SetShards( 730 shard.NewShards([]shard.Shard{ 731 shard.NewShard(4).SetState(shard.Available), 732 shard.NewShard(5).SetState(shard.Available), 733 })), 734 placement.NewInstance().SetID("i3").SetWeight(1).SetShards( 735 shard.NewShards([]shard.Shard{ 736 shard.NewShard(1).SetState(shard.Unknown).SetSourceID("i1"), 737 shard.NewShard(6).SetState(shard.Available), 738 })), 739 }, 740 }, 741 { 742 name: "optimizing multiple imbalanced instances", 743 shards: []uint32{1, 2, 3, 4, 5, 6, 7, 8}, 744 instancesBefore: []placement.Instance{ 745 placement.NewInstance().SetID("i1").SetWeight(1).SetShards( 746 shard.NewShards([]shard.Shard{ 747 shard.NewShard(1).SetState(shard.Available), 748 shard.NewShard(2).SetState(shard.Available), 749 shard.NewShard(3).SetState(shard.Available), 750 shard.NewShard(4).SetState(shard.Available), 751 })), 752 placement.NewInstance().SetID("i2").SetWeight(1).SetShards( 753 shard.NewShards([]shard.Shard{ 754 shard.NewShard(5).SetState(shard.Available), 755 shard.NewShard(6).SetState(shard.Available), 756 })), 757 placement.NewInstance().SetID("i3").SetWeight(1).SetShards( 758 shard.NewShards([]shard.Shard{ 759 shard.NewShard(7).SetState(shard.Available), 760 shard.NewShard(8).SetState(shard.Available), 761 })), 762 }, 763 instancesAfter: []placement.Instance{ 764 placement.NewInstance().SetID("i1").SetWeight(1).SetShards( 765 shard.NewShards([]shard.Shard{ 766 shard.NewShard(1).SetState(shard.Leaving), 767 shard.NewShard(2).SetState(shard.Available), 768 shard.NewShard(3).SetState(shard.Available), 769 shard.NewShard(4).SetState(shard.Available), 770 })), 771 placement.NewInstance().SetID("i2").SetWeight(1).SetShards( 772 shard.NewShards([]shard.Shard{ 773 shard.NewShard(1).SetState(shard.Unknown).SetSourceID("i1"), 774 shard.NewShard(5).SetState(shard.Available), 775 shard.NewShard(6).SetState(shard.Available), 776 })), 777 placement.NewInstance().SetID("i3").SetWeight(1).SetShards( 778 shard.NewShards([]shard.Shard{ 779 shard.NewShard(7).SetState(shard.Available), 780 shard.NewShard(8).SetState(shard.Available), 781 })), 782 }, 783 }, 784 { 785 name: "no optimization for balanced instances with different weights", 786 shards: []uint32{1, 2, 3, 4, 5, 6, 7, 8}, 787 instancesBefore: []placement.Instance{ 788 placement.NewInstance().SetID("i1").SetWeight(2).SetShards( 789 shard.NewShards([]shard.Shard{ 790 shard.NewShard(1).SetState(shard.Available), 791 shard.NewShard(2).SetState(shard.Available), 792 shard.NewShard(3).SetState(shard.Available), 793 shard.NewShard(4).SetState(shard.Available), 794 })), 795 placement.NewInstance().SetID("i2").SetWeight(1).SetShards( 796 shard.NewShards([]shard.Shard{ 797 shard.NewShard(5).SetState(shard.Available), 798 shard.NewShard(6).SetState(shard.Available), 799 })), 800 placement.NewInstance().SetID("i3").SetWeight(1).SetShards( 801 shard.NewShards([]shard.Shard{ 802 shard.NewShard(7).SetState(shard.Available), 803 shard.NewShard(8).SetState(shard.Available), 804 })), 805 }, 806 instancesAfter: []placement.Instance{ 807 placement.NewInstance().SetID("i1").SetWeight(2).SetShards( 808 shard.NewShards([]shard.Shard{ 809 shard.NewShard(1).SetState(shard.Available), 810 shard.NewShard(2).SetState(shard.Available), 811 shard.NewShard(3).SetState(shard.Available), 812 shard.NewShard(4).SetState(shard.Available), 813 })), 814 placement.NewInstance().SetID("i2").SetWeight(1).SetShards( 815 shard.NewShards([]shard.Shard{ 816 shard.NewShard(5).SetState(shard.Available), 817 shard.NewShard(6).SetState(shard.Available), 818 })), 819 placement.NewInstance().SetID("i3").SetWeight(1).SetShards( 820 shard.NewShards([]shard.Shard{ 821 shard.NewShard(7).SetState(shard.Available), 822 shard.NewShard(8).SetState(shard.Available), 823 })), 824 }, 825 }, 826 { 827 name: "optimization for imbalanced instances with different weights", 828 shards: []uint32{1, 2, 3, 4, 5, 6, 7, 8}, 829 instancesBefore: []placement.Instance{ 830 placement.NewInstance().SetID("i1").SetWeight(2).SetShards( 831 shard.NewShards([]shard.Shard{ 832 shard.NewShard(1).SetState(shard.Available), 833 shard.NewShard(2).SetState(shard.Available), 834 shard.NewShard(3).SetState(shard.Available), 835 })), 836 placement.NewInstance().SetID("i2").SetWeight(1).SetShards( 837 shard.NewShards([]shard.Shard{ 838 shard.NewShard(4).SetState(shard.Available), 839 shard.NewShard(5).SetState(shard.Available), 840 shard.NewShard(6).SetState(shard.Available), 841 })), 842 placement.NewInstance().SetID("i3").SetWeight(1).SetShards( 843 shard.NewShards([]shard.Shard{ 844 shard.NewShard(7).SetState(shard.Available), 845 shard.NewShard(8).SetState(shard.Available), 846 })), 847 }, 848 instancesAfter: []placement.Instance{ 849 placement.NewInstance().SetID("i1").SetWeight(2).SetShards( 850 shard.NewShards([]shard.Shard{ 851 shard.NewShard(1).SetState(shard.Available), 852 shard.NewShard(2).SetState(shard.Available), 853 shard.NewShard(3).SetState(shard.Available), 854 shard.NewShard(4).SetState(shard.Unknown).SetSourceID("i2"), 855 })), 856 placement.NewInstance().SetID("i2").SetWeight(1).SetShards( 857 shard.NewShards([]shard.Shard{ 858 shard.NewShard(4).SetState(shard.Leaving), 859 shard.NewShard(5).SetState(shard.Available), 860 shard.NewShard(6).SetState(shard.Available), 861 })), 862 placement.NewInstance().SetID("i3").SetWeight(1).SetShards( 863 shard.NewShards([]shard.Shard{ 864 shard.NewShard(7).SetState(shard.Available), 865 shard.NewShard(8).SetState(shard.Available), 866 })), 867 }, 868 }, 869 } 870 871 for _, tt := range tests { 872 t.Run(tt.name, func(t *testing.T) { 873 p := placement.NewPlacement().SetShards(tt.shards).SetReplicaFactor(rf).SetInstances(tt.instancesBefore) 874 ph := newHelper(p, rf, placement.NewOptions()) 875 876 err := ph.optimize(unsafe) 877 assert.NoError(t, err) 878 879 instancesAfter := ph.Instances() 880 sort.Slice(instancesAfter, func(i, j int) bool { 881 return instancesAfter[i].ID() < instancesAfter[j].ID() 882 }) 883 require.Equal(t, len(tt.instancesAfter), len(instancesAfter)) 884 for i, actual := range instancesAfter { 885 assert.Equal(t, tt.instancesAfter[i].String(), actual.String()) 886 } 887 }) 888 } 889 } 890 891 func genShardCutoverFn(now time.Time) placement.ShardValidateFn { 892 return func(s shard.Shard) error { 893 switch s.State() { 894 case shard.Initializing: 895 if s.CutoverNanos() > now.UnixNano() { 896 return fmt.Errorf("could only mark shard %d available after %v", s.ID(), time.Unix(0, s.CutoverNanos())) 897 } 898 return nil 899 default: 900 return fmt.Errorf("could not mark shard %d available, invalid state %s", s.ID(), s.State().String()) 901 } 902 } 903 } 904 905 func genShardCutoffFn(now time.Time, maxWindowSize time.Duration) placement.ShardValidateFn { 906 return func(s shard.Shard) error { 907 switch s.State() { 908 case shard.Leaving: 909 if s.CutoffNanos() > now.UnixNano()-maxWindowSize.Nanoseconds() { 910 return fmt.Errorf("could not return shard %d", s.ID()) 911 } 912 return nil 913 default: 914 return fmt.Errorf("could not mark shard %d available, invalid state %s", s.ID(), s.State().String()) 915 } 916 } 917 }