github.com/matrixorigin/matrixone@v0.7.0/pkg/vm/engine/tae/logtail/service/session_test.go (about) 1 // Copyright 2021 Matrix Origin 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package service 16 17 import ( 18 "context" 19 "sync" 20 "testing" 21 "time" 22 23 "github.com/stretchr/testify/require" 24 "go.uber.org/zap" 25 26 "github.com/matrixorigin/matrixone/pkg/common/log" 27 "github.com/matrixorigin/matrixone/pkg/common/moerr" 28 "github.com/matrixorigin/matrixone/pkg/common/morpc" 29 "github.com/matrixorigin/matrixone/pkg/logutil" 30 "github.com/matrixorigin/matrixone/pkg/pb/api" 31 "github.com/matrixorigin/matrixone/pkg/pb/logtail" 32 "github.com/matrixorigin/matrixone/pkg/pb/metadata" 33 ) 34 35 func TestSessionManger(t *testing.T) { 36 sm := NewSessionManager() 37 38 ctx := context.Background() 39 40 // constructs mocker 41 logger := mockMOLogger() 42 pooler := NewResponsePool() 43 notifier := mockSessionErrorNotifier(logger.RawLogger()) 44 sendTimeout := 5 * time.Second 45 poisionTime := 10 * time.Millisecond 46 chunkSize := 1024 47 48 /* ---- 1. register sessioin A ---- */ 49 csA := mockNormalClientSession(logger.RawLogger()) 50 streamA := mockMorpcStream(csA, 10, chunkSize) 51 sessionA := sm.GetSession(ctx, logger, sendTimeout, pooler, notifier, streamA, poisionTime) 52 require.NotNil(t, sessionA) 53 require.Equal(t, 1, len(sm.ListSession())) 54 55 /* ---- 2. register sessioin B ---- */ 56 csB := mockNormalClientSession(logger.RawLogger()) 57 streamB := mockMorpcStream(csB, 11, chunkSize) 58 sessionB := sm.GetSession(ctx, logger, sendTimeout, pooler, notifier, streamB, poisionTime) 59 require.NotNil(t, sessionB) 60 require.Equal(t, 2, len(sm.ListSession())) 61 62 /* ---- 3. delete sessioin ---- */ 63 sm.DeleteSession(streamA) 64 require.Equal(t, 1, len(sm.ListSession())) 65 sm.DeleteSession(streamB) 66 require.Equal(t, 0, len(sm.ListSession())) 67 } 68 69 func TestSessionError(t *testing.T) { 70 ctx, cancel := context.WithCancel(context.Background()) 71 defer cancel() 72 73 // constructs mocker 74 logger := mockMOLogger() 75 pooler := NewResponsePool() 76 notifier := mockSessionErrorNotifier(logger.RawLogger()) 77 cs := mockBrokenClientSession() 78 stream := mockMorpcStream(cs, 10, 1024) 79 sendTimeout := 5 * time.Second 80 poisionTime := 10 * time.Millisecond 81 82 tableA := mockTable(1, 2, 3) 83 ss := NewSession(ctx, logger, sendTimeout, pooler, notifier, stream, poisionTime) 84 85 /* ---- 1. send subscription response ---- */ 86 err := ss.SendSubscriptionResponse( 87 context.Background(), 88 logtail.TableLogtail{ 89 Table: &tableA, 90 }, 91 ) 92 require.NoError(t, err) 93 94 // wait session cleaned 95 <-ss.sessionCtx.Done() 96 97 /* ---- 2. send subscription response ---- */ 98 err = ss.SendSubscriptionResponse( 99 context.Background(), 100 logtail.TableLogtail{ 101 Table: &tableA, 102 }, 103 ) 104 require.Error(t, err) 105 } 106 107 func TestPoisionSession(t *testing.T) { 108 ctx, cancel := context.WithCancel(context.Background()) 109 defer cancel() 110 111 // constructs mocker 112 logger := mockMOLogger() 113 pooler := NewResponsePool() 114 notifier := mockSessionErrorNotifier(logger.RawLogger()) 115 cs := mockBlockStream() 116 stream := mockMorpcStream(cs, 10, 1024) 117 sendTimeout := 5 * time.Second 118 poisionTime := 10 * time.Millisecond 119 120 tableA := mockTable(1, 2, 3) 121 ss := NewSession(ctx, logger, sendTimeout, pooler, notifier, stream, poisionTime) 122 123 /* ---- 1. send response repeatedly ---- */ 124 for i := 0; i < cap(ss.sendChan)+2; i++ { 125 err := ss.SendUpdateResponse( 126 context.Background(), 127 mockTimestamp(int64(i), 0), 128 mockTimestamp(int64(i+1), 0), 129 logtail.TableLogtail{ 130 Table: &tableA, 131 }, 132 ) 133 if err != nil { 134 require.True(t, moerr.IsMoErrCode(err, moerr.ErrStreamClosed)) 135 break 136 } 137 } 138 } 139 140 func TestSession(t *testing.T) { 141 ctx, cancel := context.WithCancel(context.Background()) 142 defer cancel() 143 144 // constructs mocker 145 logger := mockMOLogger() 146 pooler := NewResponsePool() 147 notifier := mockSessionErrorNotifier(logger.RawLogger()) 148 cs := mockNormalClientSession(logger.RawLogger()) 149 stream := mockMorpcStream(cs, 10, 1024) 150 sendTimeout := 5 * time.Second 151 poisionTime := 10 * time.Millisecond 152 153 // constructs tables 154 tableA := mockTable(1, 2, 3) 155 idA := TableID(tableA.String()) 156 tableB := mockTable(1, 4, 3) 157 idB := TableID(tableB.String()) 158 159 ss := NewSession(ctx, logger, sendTimeout, pooler, notifier, stream, poisionTime) 160 defer ss.PostClean() 161 162 // no table resigered now 163 require.Equal(t, 0, len(ss.ListSubscribedTable())) 164 165 /* ---- 1. register table ---- */ 166 require.False(t, ss.Register(idA, tableA)) 167 require.True(t, ss.Register(idA, tableA)) 168 169 /* ---- 2. unregister table ---- */ 170 require.Equal(t, TableOnSubscription, ss.Unregister(idA)) 171 require.Equal(t, TableNotFound, ss.Unregister(idA)) 172 173 /* ---- 3. register more table ---- */ 174 require.False(t, ss.Register(idA, tableA)) 175 require.False(t, ss.Register(idB, tableB)) 176 require.Equal(t, 0, len(ss.ListSubscribedTable())) 177 178 /* ---- 4. filter logtail ---- */ 179 // promote state for table A 180 ss.AdvanceState(idA) 181 require.Equal(t, 1, len(ss.ListSubscribedTable())) 182 // promote state for non-exist table 183 ss.AdvanceState(TableID("non-exist")) 184 require.Equal(t, 1, len(ss.ListSubscribedTable())) 185 // filter logtail for subscribed table 186 qualified := ss.FilterLogtail( 187 mockWrapLogtail(tableA), 188 mockWrapLogtail(tableB), 189 ) 190 require.Equal(t, 1, len(qualified)) 191 require.Equal(t, tableA.String(), qualified[0].Table.String()) 192 193 // promote state for table B 194 ss.AdvanceState(idB) 195 require.Equal(t, 2, len(ss.ListSubscribedTable())) 196 // filter logtail for subscribed table 197 qualified = ss.FilterLogtail( 198 mockWrapLogtail(tableA), 199 mockWrapLogtail(tableB), 200 ) 201 require.Equal(t, 2, len(qualified)) 202 203 /* ---- 5. send error response ---- */ 204 err := ss.SendErrorResponse( 205 context.Background(), 206 tableA, 207 moerr.ErrInternal, 208 "interval error", 209 ) 210 require.NoError(t, err) 211 212 /* ---- 6. send subscription response ---- */ 213 err = ss.SendSubscriptionResponse( 214 context.Background(), 215 logtail.TableLogtail{ 216 Table: &tableA, 217 }, 218 ) 219 require.NoError(t, err) 220 221 /* ---- 7. send unsubscription response ---- */ 222 err = ss.SendUnsubscriptionResponse( 223 context.Background(), 224 tableA, 225 ) 226 require.NoError(t, err) 227 228 /* ---- 8. send update response ---- */ 229 err = ss.SendUpdateResponse( 230 context.Background(), 231 mockTimestamp(1, 0), 232 mockTimestamp(2, 0), 233 mockLogtail(tableA), 234 mockLogtail(tableB), 235 ) 236 require.NoError(t, err) 237 238 /* ---- 9. publish update response ---- */ 239 err = ss.Publish( 240 context.Background(), 241 mockTimestamp(2, 0), 242 mockTimestamp(3, 0), 243 mockWrapLogtail(tableA), 244 mockWrapLogtail(tableB), 245 ) 246 require.NoError(t, err) 247 } 248 249 type blockStream struct { 250 once sync.Once 251 ch chan bool 252 } 253 254 func mockBlockStream() morpc.ClientSession { 255 return &blockStream{ 256 ch: make(chan bool), 257 } 258 } 259 260 func (m *blockStream) Write(ctx context.Context, message morpc.Message) error { 261 <-m.ch 262 return moerr.NewStreamClosedNoCtx() 263 } 264 265 func (m *blockStream) Close() error { 266 m.once.Do(func() { 267 close(m.ch) 268 }) 269 return nil 270 } 271 272 type brokenStream struct{} 273 274 func mockBrokenClientSession() morpc.ClientSession { 275 return &brokenStream{} 276 } 277 278 func (m *brokenStream) Write(ctx context.Context, message morpc.Message) error { 279 return moerr.NewStreamClosedNoCtx() 280 } 281 282 func (m *brokenStream) Close() error { 283 return nil 284 } 285 286 type normalStream struct { 287 logger *zap.Logger 288 } 289 290 func mockNormalClientSession(logger *zap.Logger) morpc.ClientSession { 291 return &normalStream{ 292 logger: logger, 293 } 294 } 295 296 func (m *normalStream) Write(ctx context.Context, message morpc.Message) error { 297 response := message.(*LogtailResponseSegment) 298 m.logger.Info("write response segment:", zap.String("segment", response.String())) 299 return nil 300 } 301 302 func (m *normalStream) Close() error { 303 return nil 304 } 305 306 type notifySessionError struct { 307 logger *zap.Logger 308 } 309 310 func mockSessionErrorNotifier(logger *zap.Logger) SessionErrorNotifier { 311 return ¬ifySessionError{ 312 logger: logger, 313 } 314 } 315 316 func (m *notifySessionError) NotifySessionError(ss *Session, err error) { 317 if err != nil { 318 m.logger.Error("receive session error", zap.Error(err)) 319 ss.PostClean() 320 } 321 } 322 323 func mockWrapLogtail(table api.TableID) wrapLogtail { 324 return wrapLogtail{ 325 id: TableID(table.String()), 326 tail: logtail.TableLogtail{ 327 Table: &table, 328 }, 329 } 330 } 331 332 func mockLogtail(table api.TableID) logtail.TableLogtail { 333 return logtail.TableLogtail{ 334 CkpLocation: "checkpoint", 335 Table: &table, 336 } 337 } 338 339 func mockMorpcStream( 340 cs morpc.ClientSession, id uint64, maxMessageSize int, 341 ) morpcStream { 342 segments := NewSegmentPool(maxMessageSize) 343 344 return morpcStream{ 345 streamID: id, 346 limit: segments.LeastEffectiveCapacity(), 347 logger: mockMOLogger(), 348 cs: cs, 349 segments: segments, 350 } 351 } 352 353 func mockMOLogger() *log.MOLogger { 354 return log.GetServiceLogger( 355 logutil.GetGlobalLogger().Named(LogtailServiceRPCName), 356 metadata.ServiceType_DN, 357 "uuid", 358 ) 359 }