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

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