github.com/m3db/m3@v1.5.0/src/dbnode/client/replicated_session_test.go (about) 1 // Copyright (c) 2019 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 "testing" 26 "time" 27 28 "github.com/golang/mock/gomock" 29 "github.com/stretchr/testify/suite" 30 31 "github.com/m3db/m3/src/dbnode/environment" 32 "github.com/m3db/m3/src/dbnode/topology" 33 "github.com/m3db/m3/src/m3ninx/doc" 34 "github.com/m3db/m3/src/x/ident" 35 "github.com/m3db/m3/src/x/instrument" 36 xsync "github.com/m3db/m3/src/x/sync" 37 xtime "github.com/m3db/m3/src/x/time" 38 ) 39 40 type replicatedSessionTestSuite struct { 41 suite.Suite 42 mockCtrl *gomock.Controller 43 replicatedSession *replicatedSession 44 } 45 46 func newSessionFnWithSession(s clientSession) newSessionFn { 47 return func(Options) (clientSession, error) { 48 return s, nil 49 } 50 } 51 52 func newTopologyInitializer() topology.Initializer { 53 return topology.NewStaticInitializer(topology.NewStaticOptions()) 54 } 55 56 func optionsWithAsyncSessions(hasSync bool, asyncCount int) Options { 57 topoInits := make([]topology.Initializer, 0, asyncCount) 58 for i := 0; i < asyncCount; i++ { 59 topoInits = append(topoInits, newTopologyInitializer()) 60 } 61 options := NewAdminOptions(). 62 SetAsyncTopologyInitializers(topoInits) 63 if asyncCount > 0 { 64 workerPool, err := xsync.NewPooledWorkerPool(10, 65 xsync.NewPooledWorkerPoolOptions()) 66 if err != nil { 67 panic(err) 68 } 69 workerPool.Init() 70 options = options.SetAsyncWriteWorkerPool(workerPool) 71 } 72 73 if hasSync { 74 options = options.SetTopologyInitializer(newTopologyInitializer()) 75 } 76 return options 77 } 78 79 func (s *replicatedSessionTestSuite) initReplicatedSession(opts Options, newSessionFunc newSessionFn) { 80 topoInits := opts.AsyncTopologyInitializers() 81 overrides := make([]environment.ClientOverrides, len(topoInits)) 82 session, err := newReplicatedSession( 83 opts, 84 NewOptionsForAsyncClusters(opts, topoInits, overrides), 85 withNewSessionFn(newSessionFunc), 86 ) 87 s.replicatedSession = session.(*replicatedSession) 88 s.NoError(err) 89 } 90 91 func (s *replicatedSessionTestSuite) SetupTest() { 92 s.mockCtrl = gomock.NewController(s.T()) 93 } 94 95 func (s *replicatedSessionTestSuite) TearDownTest() { 96 s.mockCtrl.Finish() 97 } 98 99 func TestReplicatedSessionTestSuite(t *testing.T) { 100 suite.Run(t, new(replicatedSessionTestSuite)) 101 } 102 103 var constructorCreatesSessionsTests = []struct { 104 options Options 105 expectedCount int 106 }{ 107 { 108 options: optionsWithAsyncSessions(false, 0), 109 expectedCount: 0, 110 }, 111 { 112 options: optionsWithAsyncSessions(false, 2), 113 expectedCount: 2, 114 }, 115 { 116 options: optionsWithAsyncSessions(true, 3), 117 expectedCount: 4, 118 }, 119 { 120 options: optionsWithAsyncSessions(true, 0), 121 expectedCount: 1, 122 }, 123 } 124 125 func (s *replicatedSessionTestSuite) TestConstructorCreatesSessions() { 126 for _, tt := range constructorCreatesSessionsTests { 127 count := 0 128 var newSessionFunc = func(opts Options) (clientSession, error) { 129 count = count + 1 130 return NewMockclientSession(s.mockCtrl), nil 131 } 132 133 s.initReplicatedSession(tt.options, newSessionFunc) 134 s.Equal(tt.expectedCount, count) 135 } 136 } 137 138 func (s *replicatedSessionTestSuite) TestSetSession() { 139 opts := optionsWithAsyncSessions(false, 0) 140 session := NewMockclientSession(s.mockCtrl) 141 newSessionFunc := newSessionFnWithSession(session) 142 s.initReplicatedSession(opts, newSessionFunc) 143 144 topoInit := newTopologyInitializer() 145 sessionOpts := NewMockAdminOptions(s.mockCtrl) 146 sessionOpts.EXPECT().TopologyInitializer().Return(topoInit) 147 148 s.Nil(s.replicatedSession.session) 149 err := s.replicatedSession.setSession(sessionOpts) 150 s.NoError(err) 151 s.Equal(session, s.replicatedSession.session) 152 } 153 154 func (s *replicatedSessionTestSuite) TestSetAsyncSessions() { 155 opts := optionsWithAsyncSessions(false, 0) 156 session := NewMockclientSession(s.mockCtrl) 157 newSessionFunc := newSessionFnWithSession(session) 158 159 s.initReplicatedSession(opts, newSessionFunc) 160 161 sessionOpts := []Options{} 162 for i := 0; i < 3; i++ { 163 o := NewMockAdminOptions(s.mockCtrl) 164 o.EXPECT().InstrumentOptions().AnyTimes().Return(instrument.NewOptions()) 165 o.EXPECT().SetInstrumentOptions(gomock.Any()).Return(o) 166 sessionOpts = append(sessionOpts, o) 167 } 168 169 s.Len(s.replicatedSession.asyncSessions, 0) 170 err := s.replicatedSession.setAsyncSessions(sessionOpts) 171 s.NoError(err) 172 s.Len(s.replicatedSession.asyncSessions, 3) 173 } 174 175 func (s *replicatedSessionTestSuite) TestReplicate() { 176 var ( 177 asyncCount = 2 178 namespace = ident.StringID("foo") 179 id = ident.StringID("bar") 180 now = xtime.Now() 181 value = float64(123) 182 unit = xtime.Nanosecond 183 annotation = []byte("annotation") 184 ) 185 186 newSessionFunc := func(opts Options) (clientSession, error) { 187 s := NewMockclientSession(s.mockCtrl) 188 s.EXPECT().Write( 189 ident.NewIDMatcher(namespace.String()), 190 ident.NewIDMatcher(id.String()), 191 now, value, unit, annotation, 192 ).Return(nil) 193 return s, nil 194 } 195 196 opts := optionsWithAsyncSessions(true, asyncCount) 197 s.initReplicatedSession(opts, newSessionFunc) 198 s.replicatedSession.outCh = make(chan error) 199 200 err := s.replicatedSession.Write(namespace, id, now, value, unit, annotation) 201 s.NoError(err) 202 203 s.waitForAsyncSessions(asyncCount) 204 } 205 206 func (s *replicatedSessionTestSuite) TestReplicateTagged() { 207 var ( 208 asyncCount = 2 209 namespace = ident.StringID("foo") 210 id = ident.StringID("bar") 211 tags = ident.NewFieldsTagsIterator([]doc.Field{{Name: []byte("k"), Value: []byte("v")}}) 212 now = xtime.Now() 213 value = float64(123) 214 unit = xtime.Nanosecond 215 annotation = []byte("annotation") 216 ) 217 218 newSessionFunc := func(opts Options) (clientSession, error) { 219 s := NewMockclientSession(s.mockCtrl) 220 s.EXPECT().WriteTagged( 221 ident.NewIDMatcher(namespace.String()), 222 ident.NewIDMatcher(id.String()), 223 ident.NewTagIterMatcher(tags), 224 now, value, unit, annotation, 225 ).Return(nil) 226 return s, nil 227 } 228 229 opts := optionsWithAsyncSessions(true, asyncCount) 230 s.initReplicatedSession(opts, newSessionFunc) 231 s.replicatedSession.outCh = make(chan error) 232 233 err := s.replicatedSession.WriteTagged(namespace, id, tags, now, value, unit, annotation) 234 s.NoError(err) 235 236 s.waitForAsyncSessions(asyncCount) 237 } 238 239 func (s *replicatedSessionTestSuite) TestOpenReplicatedSession() { 240 var newSessionFunc = func(opts Options) (clientSession, error) { 241 s := NewMockclientSession(s.mockCtrl) 242 s.EXPECT().Open().Return(nil) 243 return s, nil 244 } 245 246 opts := optionsWithAsyncSessions(true, 2) 247 s.initReplicatedSession(opts, newSessionFunc) 248 s.replicatedSession.Open() 249 } 250 251 func (s *replicatedSessionTestSuite) TestOpenReplicatedSessionSyncError() { 252 sessions := []*MockclientSession{} 253 var newSessionFunc = func(opts Options) (clientSession, error) { 254 s := NewMockclientSession(s.mockCtrl) 255 sessions = append(sessions, s) 256 return s, nil 257 } 258 259 opts := optionsWithAsyncSessions(true, 2) 260 s.initReplicatedSession(opts, newSessionFunc) 261 262 // Early exit if sync session Open() returns an error 263 sessions[0].EXPECT().Open().Return(errors.New("an error")) 264 s.replicatedSession.Open() 265 } 266 267 func (s *replicatedSessionTestSuite) TestOpenReplicatedSessionAsyncError() { 268 sessions := []*MockclientSession{} 269 var newSessionFunc = func(opts Options) (clientSession, error) { 270 s := NewMockclientSession(s.mockCtrl) 271 sessions = append(sessions, s) 272 return s, nil 273 } 274 275 opts := optionsWithAsyncSessions(true, 2) 276 s.initReplicatedSession(opts, newSessionFunc) 277 278 // No early exit if async session Open() returns an error 279 sessions[0].EXPECT().Open().Return(nil) 280 sessions[1].EXPECT().Open().Return(errors.New("an error")) 281 sessions[2].EXPECT().Open().Return(nil) 282 s.replicatedSession.Open() 283 } 284 285 func (s *replicatedSessionTestSuite) waitForAsyncSessions(asyncCount int) { 286 t := time.NewTimer(1 * time.Second) 287 288 // Allow async expectations to occur before ending test. 289 for i := 0; i < asyncCount; i++ { 290 select { 291 case err := <-s.replicatedSession.outCh: 292 s.NoError(err) 293 case <-t.C: 294 return 295 } 296 } 297 }