github.com/m3db/m3@v1.5.0/src/aggregator/client/writer_mgr_test.go (about) 1 // Copyright (c) 2018 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 client 22 23 import ( 24 "errors" 25 "strings" 26 "testing" 27 "time" 28 29 "github.com/golang/mock/gomock" 30 "github.com/stretchr/testify/assert" 31 "github.com/stretchr/testify/require" 32 "go.uber.org/atomic" 33 "go.uber.org/goleak" 34 35 "github.com/m3db/m3/src/cluster/placement" 36 "github.com/m3db/m3/src/x/clock" 37 ) 38 39 var ( 40 testPlacementInstance = placement.NewInstance(). 41 SetID("testInstanceID"). 42 SetEndpoint("testInstanceAddress") 43 ) 44 45 func TestWriterManagerAddInstancesClosed(t *testing.T) { 46 mgr := mustMakeInstanceWriterManager(testOptions()) 47 mgr.Lock() 48 mgr.closed = true 49 mgr.Unlock() 50 require.Equal(t, errInstanceWriterManagerClosed, mgr.AddInstances(nil)) 51 } 52 53 func TestWriterManagerAddInstancesSingleRef(t *testing.T) { 54 mgr := mustMakeInstanceWriterManager(testOptions()) 55 56 // Add instance lists twice and assert the writer refcount matches expectation. 57 for i := 0; i < 2; i++ { 58 require.NoError(t, mgr.AddInstances([]placement.Instance{testPlacementInstance})) 59 } 60 mgr.Lock() 61 require.Equal(t, 1, len(mgr.writers)) 62 w, exists := mgr.writers[testPlacementInstance.ID()] 63 mgr.Unlock() 64 require.True(t, exists) 65 require.Equal(t, int32(2), w.refCount.n) 66 } 67 68 func TestWriterManagerRemoveInstancesClosed(t *testing.T) { 69 mgr := mustMakeInstanceWriterManager(testOptions()) 70 mgr.Lock() 71 mgr.closed = true 72 mgr.Unlock() 73 require.Equal(t, errInstanceWriterManagerClosed, mgr.RemoveInstances(nil)) 74 } 75 76 func TestWriterManagerRemoveInstancesSuccess(t *testing.T) { 77 mgr := mustMakeInstanceWriterManager(testOptions()) 78 79 // Add instance lists twice. 80 for i := 0; i < 2; i++ { 81 require.NoError(t, mgr.AddInstances([]placement.Instance{testPlacementInstance})) 82 } 83 mgr.Lock() 84 require.Equal(t, 1, len(mgr.writers)) 85 mgr.Unlock() 86 87 // Remove the instance list once and assert they are not closed. 88 require.NoError(t, mgr.RemoveInstances([]placement.Instance{testPlacementInstance})) 89 90 mgr.Lock() 91 require.Equal(t, 1, len(mgr.writers)) 92 w := mgr.writers[testPlacementInstance.ID()].instanceWriter.(*writer) 93 require.False(t, w.closed) 94 mgr.Unlock() 95 96 // Remove the instance list again and assert the writer is now removed. 97 nonexistent := placement.NewInstance(). 98 SetID("nonexistent"). 99 SetEndpoint("nonexistentAddress") 100 toRemove := append([]placement.Instance{nonexistent, testPlacementInstance}) 101 require.NoError(t, mgr.RemoveInstances(toRemove)) 102 require.Equal(t, 0, len(mgr.writers)) 103 require.True(t, clock.WaitUntil(func() bool { 104 w.Lock() 105 defer w.Unlock() 106 return w.closed 107 }, 3*time.Second)) 108 } 109 110 func TestWriterManagerRemoveInstancesNonBlocking(t *testing.T) { 111 var ( 112 opts = testOptions().SetInstanceQueueSize(200) 113 mgr = mustMakeInstanceWriterManager(opts) 114 ) 115 require.NoError(t, mgr.AddInstances([]placement.Instance{testPlacementInstance})) 116 117 mgr.Lock() 118 require.Equal(t, 1, len(mgr.writers)) 119 w := mgr.writers[testPlacementInstance.ID()].instanceWriter.(*writer) 120 121 w.queue.(*queue).writeFn = func([]byte) error { 122 time.Sleep(time.Second) 123 return nil 124 } 125 mgr.Unlock() 126 127 data := []byte("foo") 128 for i := 0; i < opts.InstanceQueueSize(); i++ { 129 require.NoError(t, w.queue.Enqueue(testNewBuffer(data))) 130 } 131 132 go mgr.RemoveInstances([]placement.Instance{testPlacementInstance}) 133 require.True(t, clock.WaitUntil(func() bool { 134 mgr.Lock() 135 defer mgr.Unlock() 136 return len(mgr.writers) == 0 137 }, 3*time.Second)) 138 } 139 140 func TestWriterManagerWriteUntimedClosed(t *testing.T) { 141 payload := payloadUnion{ 142 payloadType: untimedType, 143 untimed: untimedPayload{ 144 metric: testCounter, 145 metadatas: testStagedMetadatas, 146 }, 147 } 148 mgr := mustMakeInstanceWriterManager(testOptions()) 149 mgr.Lock() 150 mgr.closed = true 151 mgr.Unlock() 152 err := mgr.Write(testPlacementInstance, 0, payload) 153 require.Equal(t, errInstanceWriterManagerClosed, err) 154 } 155 156 func TestWriterManagerWriteUntimedNoInstances(t *testing.T) { 157 payload := payloadUnion{ 158 payloadType: untimedType, 159 untimed: untimedPayload{ 160 metric: testCounter, 161 metadatas: testStagedMetadatas, 162 }, 163 } 164 mgr := mustMakeInstanceWriterManager(testOptions()) 165 err := mgr.Write(testPlacementInstance, 0, payload) 166 require.Error(t, err) 167 require.NoError(t, mgr.Close()) 168 } 169 170 func TestWriterManagerWriteUntimedSuccess(t *testing.T) { 171 ctrl := gomock.NewController(t) 172 defer ctrl.Finish() 173 174 var ( 175 instances = []placement.Instance{ 176 testPlacementInstance, 177 placement.NewInstance(). 178 SetID("foo"). 179 SetEndpoint("fooAddr"), 180 } 181 shardRes uint32 182 payloadRes payloadUnion 183 ) 184 writer := NewMockinstanceWriter(ctrl) 185 writer.EXPECT().QueueSize().AnyTimes() 186 writer.EXPECT(). 187 Write(gomock.Any(), gomock.Any()). 188 DoAndReturn(func( 189 shard uint32, 190 payload payloadUnion, 191 ) error { 192 shardRes = shard 193 payloadRes = payload 194 return nil 195 }) 196 mgr := mustMakeInstanceWriterManager(testOptions()) 197 mgr.Lock() 198 mgr.writers[instances[0].ID()] = &refCountedWriter{ 199 refCount: refCount{n: 1}, 200 instanceWriter: writer, 201 } 202 mgr.Unlock() 203 204 payload := payloadUnion{ 205 payloadType: untimedType, 206 untimed: untimedPayload{ 207 metric: testCounter, 208 metadatas: testStagedMetadatas, 209 }, 210 } 211 require.NoError(t, mgr.Write(testPlacementInstance, 0, payload)) 212 mgr.Lock() 213 assert.Equal(t, 1, len(mgr.writers)) 214 mgr.Unlock() 215 require.Equal(t, uint32(0), shardRes) 216 require.Equal(t, untimedType, payloadRes.payloadType) 217 require.Equal(t, testCounter, payloadRes.untimed.metric) 218 require.Equal(t, testStagedMetadatas, payloadRes.untimed.metadatas) 219 } 220 221 func TestWriterManagerFlushClosed(t *testing.T) { 222 mgr := mustMakeInstanceWriterManager(testOptions()) 223 mgr.closed = true 224 require.Equal(t, errInstanceWriterManagerClosed, mgr.Flush()) 225 } 226 227 func TestWriterManagerFlushPartialError(t *testing.T) { 228 ctrl := gomock.NewController(t) 229 defer ctrl.Finish() 230 231 var ( 232 numFlushes atomic.Int64 233 instances = []placement.Instance{ 234 testPlacementInstance, 235 placement.NewInstance(). 236 SetID("foo"). 237 SetEndpoint("fooAddr"), 238 } 239 ) 240 241 writer1 := NewMockinstanceWriter(ctrl) 242 writer1.EXPECT().QueueSize().AnyTimes() 243 writer1.EXPECT().Write(gomock.Any(), gomock.Any()) 244 writer1.EXPECT(). 245 Flush(). 246 DoAndReturn(func() error { 247 numFlushes.Inc() 248 return nil 249 }) 250 errTestFlush := errors.New("test flush error") 251 writer2 := NewMockinstanceWriter(ctrl) 252 writer2.EXPECT().QueueSize().AnyTimes() 253 writer2.EXPECT().Write(gomock.Any(), gomock.Any()) 254 writer2.EXPECT(). 255 Flush(). 256 DoAndReturn(func() error { 257 return errTestFlush 258 }) 259 mgr := mustMakeInstanceWriterManager(testOptions()) 260 mgr.Lock() 261 mgr.writers[instances[0].ID()] = &refCountedWriter{ 262 refCount: refCount{n: 1}, 263 instanceWriter: writer1, 264 } 265 mgr.writers[instances[1].ID()] = &refCountedWriter{ 266 refCount: refCount{n: 1}, 267 instanceWriter: writer2, 268 } 269 mgr.Unlock() 270 mgr.Write(instances[0], 0, payloadUnion{}) //nolint:errcheck 271 mgr.Write(instances[1], 0, payloadUnion{}) //nolint:errcheck 272 err := mgr.Flush() 273 require.Error(t, err) 274 require.True(t, strings.Contains(err.Error(), errTestFlush.Error())) 275 require.Equal(t, int64(1), numFlushes.Load()) 276 } 277 278 func TestWriterManagerCloseAlreadyClosed(t *testing.T) { 279 mgr := mustMakeInstanceWriterManager(testOptions()) 280 mgr.Lock() 281 mgr.closed = true 282 mgr.Unlock() 283 require.Equal(t, errInstanceWriterManagerClosed, mgr.Close()) 284 } 285 286 func TestWriterManagerCloseSuccess(t *testing.T) { 287 // TODO: other tests don't clean up properly, and pool has no Shutdown method 288 defer goleak.VerifyNone( 289 t, 290 goleak.IgnoreCurrent(), 291 goleak.IgnoreTopFunction("github.com/m3db/m3/src/x/sync.(*pooledWorkerPool).spawnWorker.func1"), 292 ) 293 294 mgr := mustMakeInstanceWriterManager(testOptions()) 295 296 // Add instance list and close. 297 require.NoError(t, mgr.AddInstances([]placement.Instance{testPlacementInstance})) 298 require.NoError(t, mgr.Close()) 299 mgr.Lock() 300 require.True(t, mgr.closed) 301 mgr.Unlock() 302 303 require.True(t, clock.WaitUntil(func() bool { 304 for _, w := range mgr.writers { 305 wr := w.instanceWriter.(*writer) 306 wr.Lock() 307 closed := wr.closed 308 wr.Unlock() 309 310 if !closed { 311 return false 312 } 313 } 314 return true 315 }, 3*time.Second)) 316 } 317 318 func mustMakeInstanceWriterManager(opts Options) *writerManager { 319 wm, err := newInstanceWriterManager(opts) 320 if err != nil { 321 panic(err) 322 } 323 324 return wm.(*writerManager) 325 }