github.com/m3db/m3@v1.5.0/src/cluster/placement/selector/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 selector
    22  
    23  import (
    24  	"testing"
    25  
    26  	"github.com/m3db/m3/src/cluster/placement"
    27  	"github.com/m3db/m3/src/cluster/shard"
    28  
    29  	"github.com/stretchr/testify/assert"
    30  	"github.com/stretchr/testify/require"
    31  )
    32  
    33  func TestSelectInitialInstancesForMirror(t *testing.T) {
    34  	h1p1 := placement.NewInstance().
    35  		SetID("h1p1").
    36  		SetHostname("h1").
    37  		SetPort(1).
    38  		SetIsolationGroup("r1").
    39  		SetZone("z1").
    40  		SetEndpoint("h1p1e").
    41  		SetWeight(1)
    42  	h1p2 := placement.NewInstance().
    43  		SetID("h1p2").
    44  		SetHostname("h1").
    45  		SetPort(2).
    46  		SetIsolationGroup("r1").
    47  		SetZone("z1").
    48  		SetEndpoint("h1p2e").
    49  		SetWeight(1)
    50  	h2p1 := placement.NewInstance().
    51  		SetID("h2p1").
    52  		SetHostname("h2").
    53  		SetPort(1).
    54  		SetIsolationGroup("r2").
    55  		SetZone("z1").
    56  		SetEndpoint("h2p1e").
    57  		SetWeight(1)
    58  	h2p2 := placement.NewInstance().
    59  		SetID("h2p2").
    60  		SetHostname("h2").
    61  		SetPort(2).
    62  		SetIsolationGroup("r2").
    63  		SetZone("z1").
    64  		SetEndpoint("h2p2e").
    65  		SetWeight(1)
    66  	h3p1 := placement.NewInstance().
    67  		SetID("h3p1").
    68  		SetHostname("h3").
    69  		SetPort(1).
    70  		SetIsolationGroup("r1").
    71  		SetZone("z1").
    72  		SetEndpoint("h3p1e").
    73  		SetWeight(1)
    74  	h3p2 := placement.NewInstance().
    75  		SetID("h3p2").
    76  		SetHostname("h3").
    77  		SetPort(2).
    78  		SetIsolationGroup("r1").
    79  		SetZone("z1").
    80  		SetEndpoint("h3p2e").
    81  		SetWeight(1)
    82  	h4p1 := placement.NewInstance().
    83  		SetID("h4p1").
    84  		SetHostname("h4").
    85  		SetPort(1).
    86  		SetIsolationGroup("r3").
    87  		SetZone("z1").
    88  		SetEndpoint("h4p1e").
    89  		SetWeight(1)
    90  	h4p2 := placement.NewInstance().
    91  		SetID("h4p2").
    92  		SetHostname("h4").
    93  		SetPort(2).
    94  		SetIsolationGroup("r3").
    95  		SetZone("z1").
    96  		SetEndpoint("h4p2e").
    97  		SetWeight(1)
    98  
    99  	selector := NewPortMirroredSelector(placement.NewOptions().SetValidZone("z1"))
   100  	res, err := selector.SelectInitialInstances(
   101  		[]placement.Instance{
   102  			h1p1.SetShardSetID(0),
   103  			h1p2.SetShardSetID(0),
   104  			h2p1.SetShardSetID(0),
   105  			h2p2.SetShardSetID(0),
   106  			h3p1.SetShardSetID(0),
   107  			h3p2.SetShardSetID(0),
   108  			h4p1.SetShardSetID(0),
   109  			h4p2.SetShardSetID(0),
   110  		},
   111  		2,
   112  	)
   113  	require.NoError(t, err)
   114  	require.Equal(t, 8, len(res))
   115  
   116  	ssIDs := make(map[uint32]int)
   117  	for i := 1; i <= 4; i++ {
   118  		ssIDs[uint32(i)] = 2
   119  	}
   120  
   121  	for _, instance := range res {
   122  		ssIDs[instance.ShardSetID()] = ssIDs[instance.ShardSetID()] - 1
   123  	}
   124  
   125  	for _, count := range ssIDs {
   126  		require.Equal(t, 0, count)
   127  	}
   128  }
   129  
   130  func TestSelectInitialInstancesForMirrorRF2(t *testing.T) {
   131  	h1p1 := placement.NewInstance().
   132  		SetID("h1p1").
   133  		SetHostname("h1").
   134  		SetPort(1).
   135  		SetIsolationGroup("r1").
   136  		SetZone("z1").
   137  		SetEndpoint("h1p1e").
   138  		SetWeight(1)
   139  	h1p2 := placement.NewInstance().
   140  		SetID("h1p2").
   141  		SetHostname("h1").
   142  		SetPort(2).
   143  		SetIsolationGroup("r1").
   144  		SetZone("z1").
   145  		SetEndpoint("h1p2e").
   146  		SetWeight(1)
   147  	h1p3 := placement.NewInstance().
   148  		SetID("h1p3").
   149  		SetHostname("h1").
   150  		SetPort(3).
   151  		SetIsolationGroup("r1").
   152  		SetZone("z1").
   153  		SetEndpoint("h1p3e").
   154  		SetWeight(1)
   155  	h2p1 := placement.NewInstance().
   156  		SetID("h2p1").
   157  		SetHostname("h2").
   158  		SetPort(1).
   159  		SetIsolationGroup("r2").
   160  		SetZone("z1").
   161  		SetEndpoint("h2p1e").
   162  		SetWeight(1)
   163  	h2p2 := placement.NewInstance().
   164  		SetID("h2p2").
   165  		SetHostname("h2").
   166  		SetPort(2).
   167  		SetIsolationGroup("r2").
   168  		SetZone("z1").
   169  		SetEndpoint("h2p2e").
   170  		SetWeight(1)
   171  	h2p3 := placement.NewInstance().
   172  		SetID("h2p3").
   173  		SetHostname("h2").
   174  		SetPort(3).
   175  		SetIsolationGroup("r2").
   176  		SetZone("z1").
   177  		SetEndpoint("h2p3e").
   178  		SetWeight(1)
   179  	h3p1 := placement.NewInstance().
   180  		SetID("h3p1").
   181  		SetHostname("h3").
   182  		SetPort(1).
   183  		SetIsolationGroup("r1").
   184  		SetZone("z1").
   185  		SetEndpoint("h3p1e").
   186  		SetWeight(2)
   187  	h3p2 := placement.NewInstance().
   188  		SetID("h3p2").
   189  		SetHostname("h3").
   190  		SetPort(2).
   191  		SetIsolationGroup("r1").
   192  		SetZone("z1").
   193  		SetEndpoint("h3p2e").
   194  		SetWeight(2)
   195  	h3p3 := placement.NewInstance().
   196  		SetID("h3p3").
   197  		SetHostname("h3").
   198  		SetPort(3).
   199  		SetIsolationGroup("r1").
   200  		SetZone("z1").
   201  		SetEndpoint("h3p3e").
   202  		SetWeight(2)
   203  
   204  	selector := NewPortMirroredSelector(placement.NewOptions().SetValidZone("z1"))
   205  	res, err := selector.SelectInitialInstances(
   206  		[]placement.Instance{h1p1, h1p2, h1p3, h2p1, h2p2, h2p3, h3p1, h3p2, h3p3},
   207  		2,
   208  	)
   209  	require.NoError(t, err)
   210  	require.Equal(t, 6, len(res))
   211  	require.Equal(t, h1p1.ShardSetID(), h2p1.ShardSetID())
   212  	require.Equal(t, h1p2.ShardSetID(), h2p2.ShardSetID())
   213  	require.Equal(t, h1p3.ShardSetID(), h2p3.ShardSetID())
   214  	ssIDs := make(map[uint32]int)
   215  	for i := 1; i <= 3; i++ {
   216  		ssIDs[uint32(i)] = 2
   217  	}
   218  
   219  	for _, instance := range res {
   220  		ssIDs[instance.ShardSetID()] = ssIDs[instance.ShardSetID()] - 1
   221  	}
   222  
   223  	for _, count := range ssIDs {
   224  		require.Equal(t, 0, count)
   225  	}
   226  
   227  	require.Equal(t, h3p1.ShardSetID(), h3p2.ShardSetID())
   228  	require.Empty(t, h3p1.ShardSetID())
   229  
   230  	h4p1 := placement.NewInstance().
   231  		SetID("h4p1").
   232  		SetHostname("h4").
   233  		SetPort(1).
   234  		SetIsolationGroup("r3").
   235  		SetZone("z1").
   236  		SetEndpoint("h4p1e").
   237  		SetWeight(2)
   238  	h4p2 := placement.NewInstance().
   239  		SetID("h4p2").
   240  		SetHostname("h4").
   241  		SetPort(2).
   242  		SetIsolationGroup("r3").
   243  		SetZone("z1").
   244  		SetEndpoint("h4p2e").
   245  		SetWeight(2)
   246  	h4p3 := placement.NewInstance().
   247  		SetID("h4p3").
   248  		SetHostname("h4").
   249  		SetPort(3).
   250  		SetIsolationGroup("r3").
   251  		SetZone("z1").
   252  		SetEndpoint("h4p3e").
   253  		SetWeight(2)
   254  
   255  	res, err = selector.SelectInitialInstances(
   256  		[]placement.Instance{
   257  			h1p1.SetShardSetID(0),
   258  			h1p2.SetShardSetID(0),
   259  			h1p3.SetShardSetID(0),
   260  			h2p1.SetShardSetID(0),
   261  			h2p2.SetShardSetID(0),
   262  			h2p3.SetShardSetID(0),
   263  			h3p1.SetShardSetID(0),
   264  			h3p2.SetShardSetID(0),
   265  			h3p3.SetShardSetID(0),
   266  			h4p1.SetShardSetID(0),
   267  			h4p2.SetShardSetID(0),
   268  			h4p3.SetShardSetID(0),
   269  		},
   270  		2,
   271  	)
   272  	require.NoError(t, err)
   273  	require.Equal(t, 12, len(res))
   274  	require.Equal(t, h1p1.ShardSetID(), h2p1.ShardSetID())
   275  	require.Equal(t, h1p2.ShardSetID(), h2p2.ShardSetID())
   276  	require.Equal(t, h1p3.ShardSetID(), h2p3.ShardSetID())
   277  	require.Equal(t, h3p1.ShardSetID(), h4p1.ShardSetID())
   278  	require.Equal(t, h3p2.ShardSetID(), h4p2.ShardSetID())
   279  	require.Equal(t, h3p3.ShardSetID(), h4p3.ShardSetID())
   280  
   281  	ssIDs = make(map[uint32]int)
   282  	for i := 1; i <= 6; i++ {
   283  		ssIDs[uint32(i)] = 2
   284  	}
   285  
   286  	for _, instance := range res {
   287  		ssIDs[instance.ShardSetID()] = ssIDs[instance.ShardSetID()] - 1
   288  	}
   289  
   290  	for _, count := range ssIDs {
   291  		require.Equal(t, 0, count)
   292  	}
   293  }
   294  
   295  func TestSelectInitialInstancesForMirrorRF3(t *testing.T) {
   296  	h1p1 := placement.NewInstance().
   297  		SetID("h1p1").
   298  		SetHostname("h1").
   299  		SetPort(1).
   300  		SetIsolationGroup("r1").
   301  		SetZone("z1").
   302  		SetEndpoint("h1p1e").
   303  		SetWeight(1)
   304  	h1p2 := placement.NewInstance().
   305  		SetID("h1p2").
   306  		SetHostname("h1").
   307  		SetPort(2).
   308  		SetIsolationGroup("r1").
   309  		SetZone("z1").
   310  		SetEndpoint("h1p2e").
   311  		SetWeight(1)
   312  	h1p3 := placement.NewInstance().
   313  		SetID("h1p3").
   314  		SetHostname("h1").
   315  		SetPort(3).
   316  		SetIsolationGroup("r1").
   317  		SetZone("z1").
   318  		SetEndpoint("h1p3e").
   319  		SetWeight(1)
   320  	h2p1 := placement.NewInstance().
   321  		SetID("h2p1").
   322  		SetHostname("h2").
   323  		SetPort(1).
   324  		SetIsolationGroup("r2").
   325  		SetZone("z1").
   326  		SetEndpoint("h2p1e").
   327  		SetWeight(1)
   328  	h2p2 := placement.NewInstance().
   329  		SetID("h2p2").
   330  		SetHostname("h2").
   331  		SetPort(2).
   332  		SetIsolationGroup("r2").
   333  		SetZone("z1").
   334  		SetEndpoint("h2p2e").
   335  		SetWeight(1)
   336  	h2p3 := placement.NewInstance().
   337  		SetID("h2p3").
   338  		SetHostname("h2").
   339  		SetPort(3).
   340  		SetIsolationGroup("r2").
   341  		SetZone("z1").
   342  		SetEndpoint("h2p3e").
   343  		SetWeight(1)
   344  	h3p1 := placement.NewInstance().
   345  		SetID("h3p1").
   346  		SetHostname("h3").
   347  		SetPort(1).
   348  		SetIsolationGroup("r3").
   349  		SetZone("z1").
   350  		SetEndpoint("h3p1e").
   351  		SetWeight(1)
   352  	h3p2 := placement.NewInstance().
   353  		SetID("h3p2").
   354  		SetHostname("h3").
   355  		SetPort(2).
   356  		SetIsolationGroup("r3").
   357  		SetZone("z1").
   358  		SetEndpoint("h3p2e").
   359  		SetWeight(1)
   360  	h3p3 := placement.NewInstance().
   361  		SetID("h3p3").
   362  		SetHostname("h3").
   363  		SetPort(3).
   364  		SetIsolationGroup("r3").
   365  		SetZone("z1").
   366  		SetEndpoint("h3p3e").
   367  		SetWeight(1)
   368  
   369  	selector := NewPortMirroredSelector(placement.NewOptions().SetValidZone("z1"))
   370  	res, err := selector.SelectInitialInstances(
   371  		[]placement.Instance{h1p1, h1p2, h1p3, h2p1, h2p2, h2p3, h3p1, h3p2, h3p3},
   372  		3,
   373  	)
   374  	require.NoError(t, err)
   375  	require.Equal(t, 9, len(res))
   376  	require.Equal(t, h1p1.ShardSetID(), h2p1.ShardSetID())
   377  	require.Equal(t, h1p1.ShardSetID(), h3p1.ShardSetID())
   378  	require.Equal(t, h1p2.ShardSetID(), h2p2.ShardSetID())
   379  	require.Equal(t, h1p2.ShardSetID(), h3p2.ShardSetID())
   380  	require.Equal(t, h1p3.ShardSetID(), h2p3.ShardSetID())
   381  	require.Equal(t, h1p3.ShardSetID(), h3p3.ShardSetID())
   382  
   383  	ssIDs := make(map[uint32]int)
   384  	for i := 1; i <= 3; i++ {
   385  		ssIDs[uint32(i)] = 3
   386  	}
   387  
   388  	for _, instance := range res {
   389  		ssIDs[instance.ShardSetID()] = ssIDs[instance.ShardSetID()] - 1
   390  	}
   391  
   392  	for _, count := range ssIDs {
   393  		require.Equal(t, 0, count)
   394  	}
   395  }
   396  
   397  func TestSelectReplaceInstanceForMirror(t *testing.T) {
   398  	h1p1 := placement.NewInstance().
   399  		SetID("h1p1").
   400  		SetHostname("h1").
   401  		SetPort(1).
   402  		SetIsolationGroup("r1").
   403  		SetZone("z1").
   404  		SetEndpoint("h1p1e").
   405  		SetWeight(1).
   406  		SetShardSetID(1)
   407  	h1p2 := placement.NewInstance().
   408  		SetID("h1p2").
   409  		SetHostname("h1").
   410  		SetPort(2).
   411  		SetIsolationGroup("r1").
   412  		SetZone("z1").
   413  		SetEndpoint("h1p2e").
   414  		SetWeight(1).
   415  		SetShardSetID(2)
   416  	h2p1 := placement.NewInstance().
   417  		SetID("h2p1").
   418  		SetHostname("h2").
   419  		SetPort(1).
   420  		SetIsolationGroup("r2").
   421  		SetZone("z1").
   422  		SetEndpoint("h2p1e").
   423  		SetWeight(1).
   424  		SetShardSetID(1)
   425  	h2p2 := placement.NewInstance().
   426  		SetID("h2p2").
   427  		SetHostname("h2").
   428  		SetPort(2).
   429  		SetIsolationGroup("r2").
   430  		SetZone("z1").
   431  		SetEndpoint("h2p2e").
   432  		SetWeight(1).
   433  		SetShardSetID(2)
   434  
   435  	p := placement.NewPlacement().
   436  		SetInstances([]placement.Instance{h1p1, h1p2, h2p1, h2p2}).
   437  		SetIsMirrored(true).
   438  		SetIsSharded(true).
   439  		SetReplicaFactor(2)
   440  
   441  	h3p1 := placement.NewInstance().
   442  		SetID("h3p1").
   443  		SetHostname("h3").
   444  		SetPort(1).
   445  		SetIsolationGroup("r1").
   446  		SetZone("z1").
   447  		SetEndpoint("h3p1e").
   448  		SetWeight(1)
   449  	h3p2 := placement.NewInstance().
   450  		SetID("h3p2").
   451  		SetHostname("h3").
   452  		SetPort(2).
   453  		SetIsolationGroup("r1").
   454  		SetZone("z1").
   455  		SetEndpoint("h3p2e").
   456  		SetWeight(1)
   457  
   458  	selector := NewPortMirroredSelector(placement.NewOptions().SetValidZone("z1"))
   459  	res, err := selector.SelectReplaceInstances(
   460  		[]placement.Instance{h3p1, h3p2},
   461  		[]string{h1p1.ID(), h1p2.ID()},
   462  		p,
   463  	)
   464  	require.NoError(t, err)
   465  	require.Equal(t, 2, len(res))
   466  	require.Equal(t, h3p1.ShardSetID(), res[0].ShardSetID())
   467  	require.Equal(t, h3p2.ShardSetID(), res[1].ShardSetID())
   468  
   469  	// Isolation group conflict.
   470  	_, err = selector.SelectReplaceInstances(
   471  		[]placement.Instance{h3p1, h3p2},
   472  		[]string{h2p1.ID(), h2p2.ID()},
   473  		p,
   474  	)
   475  	require.Error(t, err)
   476  
   477  	// More than 1 host.
   478  	_, err = selector.SelectReplaceInstances(
   479  		[]placement.Instance{h3p1, h3p2},
   480  		[]string{h1p1.ID(), h2p1.ID()},
   481  		p,
   482  	)
   483  	require.Error(t, err)
   484  
   485  	// No matching weight.
   486  	h3p1.SetWeight(2)
   487  	h3p2.SetWeight(2)
   488  
   489  	_, err = selector.SelectReplaceInstances(
   490  		[]placement.Instance{h3p1, h3p2},
   491  		[]string{h1p1.ID(), h1p2.ID()},
   492  		p,
   493  	)
   494  	require.Error(t, err)
   495  }
   496  
   497  func TestSelectReplaceInstancesWithLeaving(t *testing.T) {
   498  	s1 := shard.NewShard(1).SetState(shard.Leaving)
   499  	h1p1 := placement.NewInstance().
   500  		SetID("h1p1").
   501  		SetHostname("h1").
   502  		SetPort(1).
   503  		SetIsolationGroup("r1").
   504  		SetZone("z1").
   505  		SetEndpoint("h1p1e").
   506  		SetWeight(1).
   507  		SetShardSetID(1).
   508  		SetShards(shard.NewShards([]shard.Shard{s1}))
   509  	s2 := shard.NewShard(1).SetState(shard.Initializing)
   510  	h2p1 := placement.NewInstance().
   511  		SetID("h2p1").
   512  		SetHostname("h2").
   513  		SetPort(1).
   514  		SetIsolationGroup("r2").
   515  		SetZone("z1").
   516  		SetEndpoint("h2p1e").
   517  		SetWeight(1).
   518  		SetShardSetID(1).
   519  		SetShards(shard.NewShards([]shard.Shard{s2}))
   520  
   521  	p := placement.NewPlacement().
   522  		SetInstances([]placement.Instance{h1p1, h2p1}).
   523  		SetIsMirrored(true).
   524  		SetIsSharded(true).
   525  		SetReplicaFactor(1)
   526  
   527  	selector := NewPortMirroredSelector(placement.NewOptions().SetValidZone("z1"))
   528  	res, err := selector.SelectReplaceInstances(
   529  		[]placement.Instance{h1p1, h2p1},
   530  		[]string{h2p1.ID()},
   531  		p,
   532  	)
   533  	require.NoError(t, err)
   534  	require.Equal(t, 1, len(res))
   535  	require.Equal(t, h1p1, res[0])
   536  }
   537  
   538  func TestSelectAddingInstanceForMirror(t *testing.T) {
   539  	h1p1 := placement.NewInstance().
   540  		SetID("h1p1").
   541  		SetHostname("h1").
   542  		SetPort(1).
   543  		SetIsolationGroup("r1").
   544  		SetZone("z1").
   545  		SetEndpoint("h1p1e").
   546  		SetWeight(1).
   547  		SetShardSetID(1)
   548  	h1p2 := placement.NewInstance().
   549  		SetID("h1p2").
   550  		SetHostname("h1").
   551  		SetPort(2).
   552  		SetIsolationGroup("r1").
   553  		SetZone("z1").
   554  		SetEndpoint("h1p2e").
   555  		SetWeight(1).
   556  		SetShardSetID(2)
   557  	h2p1 := placement.NewInstance().
   558  		SetID("h2p1").
   559  		SetHostname("h2").
   560  		SetPort(1).
   561  		SetIsolationGroup("r2").
   562  		SetZone("z1").
   563  		SetEndpoint("h2p1e").
   564  		SetWeight(1).
   565  		SetShardSetID(1)
   566  	h2p2 := placement.NewInstance().
   567  		SetID("h2p2").
   568  		SetHostname("h2").
   569  		SetPort(2).
   570  		SetIsolationGroup("r2").
   571  		SetZone("z1").
   572  		SetEndpoint("h2p2e").
   573  		SetWeight(1).
   574  		SetShardSetID(2)
   575  
   576  	p := placement.NewPlacement().
   577  		SetInstances([]placement.Instance{h1p1, h1p2, h2p1, h2p2}).
   578  		SetIsMirrored(true).
   579  		SetIsSharded(true).
   580  		SetReplicaFactor(2).
   581  		SetMaxShardSetID(2)
   582  
   583  	h3p1 := placement.NewInstance().
   584  		SetID("h3p1").
   585  		SetHostname("h3").
   586  		SetPort(1).
   587  		SetIsolationGroup("r1").
   588  		SetZone("z1").
   589  		SetEndpoint("h3p1e").
   590  		SetWeight(1)
   591  	h3p2 := placement.NewInstance().
   592  		SetID("h3p2").
   593  		SetHostname("h3").
   594  		SetPort(2).
   595  		SetIsolationGroup("r1").
   596  		SetZone("z1").
   597  		SetEndpoint("h3p2e").
   598  		SetWeight(1)
   599  	h4p1 := placement.NewInstance().
   600  		SetID("h4p1").
   601  		SetHostname("h4").
   602  		SetPort(1).
   603  		SetIsolationGroup("r3").
   604  		SetZone("z1").
   605  		SetEndpoint("h4p1e").
   606  		SetWeight(1)
   607  	h4p2 := placement.NewInstance().
   608  		SetID("h4p2").
   609  		SetHostname("h4").
   610  		SetPort(2).
   611  		SetIsolationGroup("r3").
   612  		SetZone("z1").
   613  		SetEndpoint("h4p2e").
   614  		SetWeight(1)
   615  
   616  	selector := NewPortMirroredSelector(placement.NewOptions().SetValidZone("z1"))
   617  	res, err := selector.SelectAddingInstances(
   618  		[]placement.Instance{h3p1, h3p2, h4p1, h4p2},
   619  		p,
   620  	)
   621  	require.NoError(t, err)
   622  	require.Equal(t, 4, len(res))
   623  
   624  	// Make sure leaving nodes are eligible for adding.
   625  	h1p1.SetShards(shard.NewShards([]shard.Shard{shard.NewShard(0).SetState(shard.Leaving)}))
   626  	h2p1.SetShards(shard.NewShards([]shard.Shard{shard.NewShard(0).SetState(shard.Leaving)}))
   627  
   628  	require.Equal(t, h1p1.ShardSetID(), h2p1.ShardSetID())
   629  	require.Equal(t, uint32(1), h1p1.ShardSetID())
   630  	res, err = selector.SelectAddingInstances(
   631  		[]placement.Instance{h1p1, h2p1},
   632  		p,
   633  	)
   634  	require.NoError(t, err)
   635  	require.Equal(t, 2, len(res))
   636  	for _, instance := range res {
   637  		require.Equal(t, uint32(1), instance.ShardSetID())
   638  	}
   639  
   640  	res, err = selector.SelectAddingInstances(
   641  		[]placement.Instance{h1p1, h4p1},
   642  		p,
   643  	)
   644  	require.NoError(t, err)
   645  	require.Equal(t, 2, len(res))
   646  	for _, instance := range res {
   647  		require.Equal(t, uint32(3), instance.ShardSetID())
   648  	}
   649  
   650  	_, err = selector.SelectAddingInstances(
   651  		[]placement.Instance{h3p2, h4p1},
   652  		p,
   653  	)
   654  	require.Error(t, err)
   655  
   656  	_, err = selector.SelectAddingInstances(
   657  		[]placement.Instance{h3p1, h3p2},
   658  		p,
   659  	)
   660  	require.Error(t, err)
   661  
   662  	// No matching weight.
   663  	h3p1.SetWeight(2)
   664  	h3p2.SetWeight(2)
   665  
   666  	_, err = selector.SelectAddingInstances(
   667  		[]placement.Instance{h3p1, h3p2, h4p1, h4p2},
   668  		p,
   669  	)
   670  	require.Error(t, err)
   671  }
   672  
   673  func TestSelectAddingInstanceForMirrorWithAddAllCandidates(t *testing.T) {
   674  	h1p1 := placement.NewInstance().
   675  		SetID("h1p1").
   676  		SetHostname("h1").
   677  		SetPort(1).
   678  		SetIsolationGroup("r1").
   679  		SetZone("z1").
   680  		SetEndpoint("h1p1e").
   681  		SetWeight(1).
   682  		SetShardSetID(1)
   683  	h1p2 := placement.NewInstance().
   684  		SetID("h1p2").
   685  		SetHostname("h1").
   686  		SetPort(2).
   687  		SetIsolationGroup("r1").
   688  		SetZone("z1").
   689  		SetEndpoint("h1p2e").
   690  		SetWeight(1).
   691  		SetShardSetID(2)
   692  	h2p1 := placement.NewInstance().
   693  		SetID("h2p1").
   694  		SetHostname("h2").
   695  		SetPort(1).
   696  		SetIsolationGroup("r2").
   697  		SetZone("z1").
   698  		SetEndpoint("h2p1e").
   699  		SetWeight(1).
   700  		SetShardSetID(1)
   701  	h2p2 := placement.NewInstance().
   702  		SetID("h2p2").
   703  		SetHostname("h2").
   704  		SetPort(2).
   705  		SetIsolationGroup("r2").
   706  		SetZone("z1").
   707  		SetEndpoint("h2p2e").
   708  		SetWeight(1).
   709  		SetShardSetID(2)
   710  
   711  	p := placement.NewPlacement().
   712  		SetInstances([]placement.Instance{h1p1, h1p2, h2p1, h2p2}).
   713  		SetIsMirrored(true).
   714  		SetIsSharded(true).
   715  		SetReplicaFactor(2).
   716  		SetMaxShardSetID(2)
   717  
   718  	h3p1 := placement.NewInstance().
   719  		SetID("h3p1").
   720  		SetHostname("h3").
   721  		SetPort(1).
   722  		SetIsolationGroup("r1").
   723  		SetZone("z1").
   724  		SetEndpoint("h3p1e").
   725  		SetWeight(1)
   726  	h3p2 := placement.NewInstance().
   727  		SetID("h3p2").
   728  		SetHostname("h3").
   729  		SetPort(2).
   730  		SetIsolationGroup("r1").
   731  		SetZone("z1").
   732  		SetEndpoint("h3p2e").
   733  		SetWeight(1)
   734  	h4p1 := placement.NewInstance().
   735  		SetID("h4p1").
   736  		SetHostname("h4").
   737  		SetPort(1).
   738  		SetIsolationGroup("r3").
   739  		SetZone("z1").
   740  		SetEndpoint("h4p1e").
   741  		SetWeight(1)
   742  	h4p2 := placement.NewInstance().
   743  		SetID("h4p2").
   744  		SetHostname("h4").
   745  		SetPort(2).
   746  		SetIsolationGroup("r3").
   747  		SetZone("z1").
   748  		SetEndpoint("h4p2e").
   749  		SetWeight(1)
   750  	h5p1 := placement.NewInstance().
   751  		SetID("h5p1").
   752  		SetHostname("h5").
   753  		SetPort(1).
   754  		SetIsolationGroup("r1").
   755  		SetZone("z1").
   756  		SetEndpoint("h5p1e").
   757  		SetWeight(1)
   758  	h5p2 := placement.NewInstance().
   759  		SetID("h5p2").
   760  		SetHostname("h5").
   761  		SetPort(2).
   762  		SetIsolationGroup("r1").
   763  		SetZone("z1").
   764  		SetEndpoint("h5p2e").
   765  		SetWeight(1)
   766  	h6p1 := placement.NewInstance().
   767  		SetID("h6p1").
   768  		SetHostname("h6").
   769  		SetPort(1).
   770  		SetIsolationGroup("r3").
   771  		SetZone("z1").
   772  		SetEndpoint("h6p1e").
   773  		SetWeight(1)
   774  	h6p2 := placement.NewInstance().
   775  		SetID("h6p2").
   776  		SetHostname("h6").
   777  		SetPort(2).
   778  		SetIsolationGroup("r3").
   779  		SetZone("z1").
   780  		SetEndpoint("h6p2e").
   781  		SetWeight(1)
   782  
   783  	selector := NewPortMirroredSelector(placement.NewOptions().SetValidZone("z1").SetAddAllCandidates(true))
   784  	res, err := selector.SelectAddingInstances(
   785  		[]placement.Instance{h3p1, h3p2, h4p1, h4p2, h5p1, h5p2, h6p1, h6p2},
   786  		p,
   787  	)
   788  	require.NoError(t, err)
   789  	require.Equal(t, 8, len(res))
   790  	require.Equal(t, h3p1.ShardSetID(), h4p1.ShardSetID())
   791  	require.Equal(t, h3p2.ShardSetID(), h4p2.ShardSetID())
   792  	require.Equal(t, h5p1.ShardSetID(), h6p1.ShardSetID())
   793  	require.Equal(t, h5p1.ShardSetID(), h6p1.ShardSetID())
   794  }
   795  
   796  func TestGroupInstancesByHostPort(t *testing.T) {
   797  	t.Run("maintains host order with 2 instances", func(t *testing.T) {
   798  		port := uint32(5)
   799  		i1 := newInstanceWithID("i1").SetPort(port).SetHostname("h1")
   800  		i2 := newInstanceWithID("i2").SetPort(port).SetHostname("h2")
   801  
   802  		hosts := [][]host{{{
   803  			name:           "h1",
   804  			isolationGroup: "g1",
   805  			portToInstance: map[uint32]placement.Instance{
   806  				port: i1,
   807  			},
   808  		}, {
   809  			name:           "h2",
   810  			isolationGroup: "g2",
   811  			portToInstance: map[uint32]placement.Instance{
   812  				port: i2,
   813  			},
   814  		}}}
   815  
   816  		groups, err := groupInstancesByHostPort(hosts, false)
   817  		require.NoError(t, err)
   818  
   819  		assert.Equal(t, [][]placement.Instance{{i1, i2}}, groups)
   820  	})
   821  
   822  	t.Run("maintains host order with 2 instances with different ports", func(t *testing.T) {
   823  		port1 := uint32(5)
   824  		i1 := newInstanceWithID("i1").SetPort(port1).SetHostname("h1")
   825  		port2 := uint32(15)
   826  		i2 := newInstanceWithID("i2").SetPort(port2).SetHostname("h2")
   827  
   828  		hosts := [][]host{{{
   829  			name:           "h1",
   830  			isolationGroup: "g1",
   831  			portToInstance: map[uint32]placement.Instance{
   832  				port1: i1,
   833  			},
   834  		}, {
   835  			name:           "h2",
   836  			isolationGroup: "g2",
   837  			portToInstance: map[uint32]placement.Instance{
   838  				port2: i2,
   839  			},
   840  		}}}
   841  
   842  		groups, err := groupInstancesByHostPort(hosts, true)
   843  		require.NoError(t, err)
   844  		assert.Equal(t, [][]placement.Instance{{i1, i2}}, groups)
   845  
   846  		_, err = groupInstancesByHostPort(hosts, false)
   847  		require.NotNil(t, err)
   848  	})
   849  }