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 }