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

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