github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/dbnode/topology/dynamic_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 topology 22 23 import ( 24 "sync" 25 "testing" 26 "time" 27 28 "github.com/m3db/m3/src/cluster/client" 29 "github.com/m3db/m3/src/cluster/services" 30 "github.com/m3db/m3/src/cluster/shard" 31 32 "github.com/golang/mock/gomock" 33 "github.com/stretchr/testify/assert" 34 "github.com/stretchr/testify/require" 35 ) 36 37 func testSetup(ctrl *gomock.Controller) (DynamicOptions, *testWatch) { 38 opts := NewDynamicOptions() 39 40 watch := newTestWatch(ctrl, time.Millisecond, time.Millisecond, 100, 100) 41 mockCSServices := services.NewMockServices(ctrl) 42 mockCSServices.EXPECT().Watch(opts.ServiceID(), opts.QueryOptions()).Return(watch, nil) 43 44 mockCSClient := client.NewMockClient(ctrl) 45 mockCSClient.EXPECT().Services(gomock.Any()).Return(mockCSServices, nil) 46 opts = opts.SetConfigServiceClient(mockCSClient) 47 return opts, watch 48 } 49 50 func testFinish(ctrl *gomock.Controller, watch *testWatch) { 51 watch.Lock() 52 defer watch.Unlock() 53 // Ensure only single writers to gomock.Controller 54 ctrl.Finish() 55 } 56 57 func TestInitNoTimeout(t *testing.T) { 58 ctrl := gomock.NewController(t) 59 opts, w := testSetup(ctrl) 60 defer testFinish(ctrl, w) 61 62 go w.run() 63 topo, err := newDynamicTopology(opts) 64 65 assert.NoError(t, err) 66 assert.NotNil(t, topo) 67 topo.Close() 68 // safe to close again 69 topo.Close() 70 } 71 72 func TestBack(t *testing.T) { 73 ctrl := gomock.NewController(t) 74 opts, w := testSetup(ctrl) 75 defer testFinish(ctrl, w) 76 77 go w.run() 78 topo, err := newDynamicTopology(opts) 79 assert.NoError(t, err) 80 mw, err := topo.Watch() 81 assert.NoError(t, err) 82 assert.Equal(t, 2, mw.Get().Replicas()) 83 assert.Equal(t, 3, mw.Get().HostsLen()) 84 85 opts, w = testSetup(ctrl) 86 close(w.ch) 87 topo, err = newDynamicTopology(opts) 88 assert.Error(t, err) 89 } 90 91 func TestGet(t *testing.T) { 92 ctrl := gomock.NewController(t) 93 opts, w := testSetup(ctrl) 94 defer testFinish(ctrl, w) 95 96 go w.run() 97 topo, err := newDynamicTopology(opts) 98 assert.NoError(t, err) 99 100 m := topo.Get() 101 assert.Equal(t, 2, m.Replicas()) 102 } 103 104 func TestWatch(t *testing.T) { 105 ctrl := gomock.NewController(t) 106 opts, watch := testSetup(ctrl) 107 defer testFinish(ctrl, watch) 108 109 go watch.run() 110 topo, err := newDynamicTopology(opts) 111 assert.NoError(t, err) 112 113 w, err := topo.Watch() 114 require.NoError(t, err) 115 <-w.C() 116 m := w.Get() 117 assert.Equal(t, 2, m.Replicas()) 118 assert.Equal(t, 2, w.Get().Replicas()) 119 120 for range w.C() { 121 assert.Equal(t, 2, w.Get().Replicas()) 122 } 123 } 124 125 func TestGetUniqueShardsAndReplicas(t *testing.T) { 126 goodInstances := goodInstances() 127 128 shards, err := validateInstances(goodInstances, 2, 3) 129 assert.NoError(t, err) 130 assert.Equal(t, 3, len(shards)) 131 132 goodInstances[0].SetShards(nil) 133 _, err = validateInstances(goodInstances, 2, 3) 134 assert.Equal(t, errInstanceHasNoShardsAssignment, err) 135 136 goodInstances[0].SetShards(shard.NewShards( 137 []shard.Shard{ 138 shard.NewShard(0), 139 shard.NewShard(1), 140 shard.NewShard(3), 141 })) 142 _, err = validateInstances(goodInstances, 2, 3) 143 assert.Equal(t, errUnexpectedShard, err) 144 145 // got h1: 1, h2: 1, 2, h3 0,2, missing a replica for 1 146 goodInstances[0].SetShards(shard.NewShards( 147 []shard.Shard{ 148 shard.NewShard(1), 149 })) 150 _, err = validateInstances(goodInstances, 2, 3) 151 assert.Equal(t, errNotEnoughReplicasForShard, err) 152 153 goodInstances[0].SetShards(shard.NewShards( 154 []shard.Shard{ 155 shard.NewShard(0), 156 })) 157 goodInstances[1].SetShards(shard.NewShards( 158 []shard.Shard{ 159 shard.NewShard(2), 160 })) 161 _, err = validateInstances(goodInstances, 2, 3) 162 // got h1:0, h2: 2, h3 0,2, missing 1 163 assert.Equal(t, errMissingShard, err) 164 } 165 166 type testWatch struct { 167 sync.RWMutex 168 169 ctrl *gomock.Controller 170 data services.Service 171 firstDelay, nextDelay time.Duration 172 errAfter, closeAfter int 173 currentCalled int 174 ch chan struct{} 175 } 176 177 func newTestWatch(ctrl *gomock.Controller, firstDelay, nextDelay time.Duration, errAfter, closeAfter int) *testWatch { 178 w := testWatch{ctrl: ctrl, firstDelay: firstDelay, nextDelay: nextDelay, errAfter: errAfter, closeAfter: closeAfter} 179 w.ch = make(chan struct{}) 180 return &w 181 } 182 183 func (w *testWatch) run() { 184 time.Sleep(w.firstDelay) 185 w.update() 186 for w.currentCalled < w.closeAfter { 187 time.Sleep(w.nextDelay) 188 w.update() 189 } 190 close(w.ch) 191 } 192 193 func (w *testWatch) update() { 194 w.Lock() 195 if w.currentCalled < w.errAfter { 196 w.data = getMockService(w.ctrl) 197 } else { 198 w.data = nil 199 } 200 w.currentCalled++ 201 w.Unlock() 202 w.ch <- struct{}{} 203 } 204 205 func (w *testWatch) Close() {} 206 207 func (w *testWatch) Get() services.Service { 208 w.RLock() 209 defer w.RUnlock() 210 return w.data 211 } 212 213 func (w *testWatch) C() <-chan struct{} { 214 return w.ch 215 } 216 217 func getMockService(ctrl *gomock.Controller) services.Service { 218 mockService := services.NewMockService(ctrl) 219 220 mockReplication := services.NewMockServiceReplication(ctrl) 221 mockReplication.EXPECT().Replicas().Return(2).AnyTimes() 222 mockService.EXPECT().Replication().Return(mockReplication).AnyTimes() 223 224 mockSharding := services.NewMockServiceSharding(ctrl) 225 mockSharding.EXPECT().NumShards().Return(3).AnyTimes() 226 mockService.EXPECT().Sharding().Return(mockSharding).AnyTimes() 227 228 mockService.EXPECT().Instances().Return(goodInstances()).AnyTimes() 229 230 return mockService 231 } 232 233 func goodInstances() []services.ServiceInstance { 234 i1 := services.NewServiceInstance().SetShards(shard.NewShards( 235 []shard.Shard{ 236 shard.NewShard(0), 237 shard.NewShard(1), 238 })).SetInstanceID("h1").SetEndpoint("h1:9000") 239 240 i2 := services.NewServiceInstance().SetShards(shard.NewShards( 241 []shard.Shard{ 242 shard.NewShard(1), 243 shard.NewShard(2), 244 })).SetInstanceID("h2").SetEndpoint("h2:9000") 245 246 i3 := services.NewServiceInstance().SetShards(shard.NewShards( 247 []shard.Shard{ 248 shard.NewShard(2), 249 shard.NewShard(0), 250 })).SetInstanceID("h3").SetEndpoint("h3:9000") 251 252 return []services.ServiceInstance{i1, i2, i3} 253 }