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

     1  // Copyright (c) 2020 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  
    22  package service
    23  
    24  import (
    25  	"testing"
    26  
    27  	"github.com/m3db/m3/src/cluster/placement"
    28  	"github.com/stretchr/testify/assert"
    29  	"github.com/stretchr/testify/require"
    30  )
    31  
    32  func TestOperator(t *testing.T) {
    33  	type testDeps struct {
    34  		options placement.Options
    35  		op      placement.Operator
    36  	}
    37  	setup := func(t *testing.T) testDeps {
    38  		options := placement.NewOptions().SetAllowAllZones(true)
    39  		return testDeps{
    40  			options: options,
    41  			op:      NewPlacementOperator(nil, WithPlacementOptions(options)),
    42  		}
    43  	}
    44  
    45  	t.Run("errors when operations called on unset placement", func(t *testing.T) {
    46  		tdeps := setup(t)
    47  		_, _, err := tdeps.op.AddInstances([]placement.Instance{newTestInstance()})
    48  		require.Error(t, err)
    49  	})
    50  
    51  	t.Run("BuildInitialPlacement twice errors", func(t *testing.T) {
    52  		tdeps := setup(t)
    53  		_, err := tdeps.op.BuildInitialPlacement([]placement.Instance{newTestInstance()}, 10, 1)
    54  		require.NoError(t, err)
    55  
    56  		_, err = tdeps.op.BuildInitialPlacement([]placement.Instance{newTestInstance()}, 10, 1)
    57  		assertErrContains(t, err, "placement already exists and can't be rebuilt")
    58  	})
    59  
    60  	t.Run("end-to-end flow", func(t *testing.T) {
    61  		tdeps := setup(t)
    62  		op := NewPlacementOperator(nil, WithPlacementOptions(tdeps.options))
    63  		store := newMockStorage()
    64  
    65  		pl, err := op.BuildInitialPlacement([]placement.Instance{newTestInstance()}, 10, 1)
    66  		require.NoError(t, err)
    67  
    68  		initialVersion := pl.Version()
    69  
    70  		_, _, err = op.AddInstances([]placement.Instance{newTestInstance()})
    71  		require.NoError(t, err)
    72  
    73  		_, err = op.MarkAllShardsAvailable()
    74  		require.NoError(t, err)
    75  
    76  		_, err = op.BalanceShards()
    77  		require.NoError(t, err)
    78  
    79  		_, err = store.SetIfNotExist(op.Placement())
    80  		require.NoError(t, err)
    81  
    82  		pl, err = store.Placement()
    83  		require.NoError(t, err)
    84  
    85  		// expect exactly one version increment, from store.SetIfNotExist
    86  		assert.Equal(t, initialVersion+1, pl.Version())
    87  
    88  		// spot check the results
    89  		allAvailable := true
    90  		instances := pl.Instances()
    91  		assert.Len(t, instances, 2)
    92  		for _, inst := range instances {
    93  			allAvailable = allAvailable && inst.IsAvailable()
    94  		}
    95  		assert.True(t, allAvailable)
    96  	})
    97  }
    98  
    99  type dummyStoreTestDeps struct {
   100  	store *dummyStore
   101  	pl    placement.Placement
   102  }
   103  
   104  func dummyStoreSetup(t *testing.T) dummyStoreTestDeps {
   105  	return dummyStoreTestDeps{
   106  		store: newDummyStore(nil),
   107  		pl:    placement.NewPlacement(),
   108  	}
   109  }
   110  
   111  func TestDummyStore_Set(t *testing.T) {
   112  	t.Run("sets without touching version", func(t *testing.T) {
   113  		tdeps := dummyStoreSetup(t)
   114  		testSetsCorrectly(t, tdeps, tdeps.store.Set)
   115  	})
   116  }
   117  
   118  func TestDummyStore_Placement(t *testing.T) {
   119  	t.Run("returns placement", func(t *testing.T) {
   120  		tdeps := dummyStoreSetup(t)
   121  
   122  		store := newDummyStore(tdeps.pl)
   123  		actual, err := store.Placement()
   124  		require.NoError(t, err)
   125  		assert.Equal(t, actual, tdeps.pl)
   126  	})
   127  
   128  	t.Run("errors when nil", func(t *testing.T) {
   129  		tdeps := dummyStoreSetup(t)
   130  		_, err := tdeps.store.Placement()
   131  		assertErrContains(t, err, "no initial placement specified at operator construction")
   132  	})
   133  }
   134  
   135  func TestDummyStore_CheckAndSet(t *testing.T) {
   136  	t.Run("sets without touching version", func(t *testing.T) {
   137  		tdeps := dummyStoreSetup(t)
   138  		testSetsCorrectly(t, tdeps, func(pl placement.Placement) (placement.Placement, error) {
   139  			return tdeps.store.CheckAndSet(pl, 5)
   140  		})
   141  	})
   142  
   143  	t.Run("ignores version mismatches", func(t *testing.T) {
   144  		tdeps := dummyStoreSetup(t)
   145  		_, err := tdeps.store.CheckAndSet(tdeps.pl, 2)
   146  		require.NoError(t, err)
   147  
   148  		_, err = tdeps.store.CheckAndSet(tdeps.pl.SetVersion(5), 3)
   149  		require.NoError(t, err)
   150  
   151  		pl, err := tdeps.store.Placement()
   152  		require.NoError(t, err)
   153  		assert.Equal(t, tdeps.pl, pl)
   154  	})
   155  }
   156  
   157  func TestDummyStore_SetIfNotExists(t *testing.T) {
   158  	t.Run("sets without touching version", func(t *testing.T) {
   159  		tdeps := dummyStoreSetup(t)
   160  		testSetsCorrectly(t, tdeps, func(pl placement.Placement) (placement.Placement, error) {
   161  			return tdeps.store.SetIfNotExist(pl)
   162  		})
   163  	})
   164  
   165  	t.Run("errors if placement exists", func(t *testing.T) {
   166  		tdeps := dummyStoreSetup(t)
   167  		_, err := tdeps.store.SetIfNotExist(tdeps.pl)
   168  		require.NoError(t, err)
   169  		_, err = tdeps.store.SetIfNotExist(tdeps.pl)
   170  		assertErrContains(t, err, "placement already exists and can't be rebuilt")
   171  	})
   172  }
   173  
   174  // Run a *Set* function and check that it returned the right thing, didn't touch the version,
   175  // and actually set the value.
   176  func testSetsCorrectly(t *testing.T, tdeps dummyStoreTestDeps, set func(pl placement.Placement) (placement.Placement, error)) {
   177  	_, err := tdeps.store.Placement()
   178  	// should not be set yet
   179  	require.Error(t, err)
   180  
   181  	curVersion := tdeps.pl.Version()
   182  	rtn, err := set(tdeps.pl)
   183  	require.NoError(t, err)
   184  
   185  	assert.Equal(t, curVersion, rtn.Version())
   186  	assert.Equal(t, tdeps.pl, rtn)
   187  	curState, err := tdeps.store.Placement()
   188  	require.NoError(t, err)
   189  	assert.Equal(t, tdeps.pl, curState)
   190  }
   191  
   192  func assertErrContains(t *testing.T, err error, contained string) {
   193  	require.Error(t, err)
   194  	assert.Contains(t, err.Error(), contained)
   195  }