github.com/m3db/m3@v1.5.0/src/cluster/placement/algo/sharded_test.go (about)

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