github.com/pingcap/ticdc@v0.0.0-20220526033649-485a10ef2652/cdc/processor/pipeline/sink_test.go (about) 1 // Copyright 2020 PingCAP, Inc. 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 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package pipeline 15 16 import ( 17 "context" 18 "sync" 19 "testing" 20 "time" 21 22 "github.com/pingcap/check" 23 "github.com/pingcap/ticdc/cdc/model" 24 "github.com/pingcap/ticdc/pkg/config" 25 cdcContext "github.com/pingcap/ticdc/pkg/context" 26 cerrors "github.com/pingcap/ticdc/pkg/errors" 27 "github.com/pingcap/ticdc/pkg/pipeline" 28 "github.com/pingcap/ticdc/pkg/util/testleak" 29 "github.com/pingcap/tidb/store/tikv/oracle" 30 ) 31 32 func TestSuite(t *testing.T) { 33 check.TestingT(t) 34 } 35 36 type mockSink struct { 37 received []struct { 38 resolvedTs model.Ts 39 row *model.RowChangedEvent 40 } 41 } 42 43 // mockFlowController is created because a real tableFlowController cannot be used 44 // we are testing sinkNode by itself. 45 type mockFlowController struct{} 46 47 func (c *mockFlowController) Consume(commitTs uint64, size uint64, blockCallBack func() error) error { 48 return nil 49 } 50 51 func (c *mockFlowController) Release(resolvedTs uint64) { 52 } 53 54 func (c *mockFlowController) Abort() { 55 } 56 57 func (c *mockFlowController) GetConsumption() uint64 { 58 return 0 59 } 60 61 func (s *mockSink) Initialize(ctx context.Context, tableInfo []*model.SimpleTableInfo) error { 62 return nil 63 } 64 65 func (s *mockSink) EmitRowChangedEvents(ctx context.Context, rows ...*model.RowChangedEvent) error { 66 for _, row := range rows { 67 s.received = append(s.received, struct { 68 resolvedTs model.Ts 69 row *model.RowChangedEvent 70 }{row: row}) 71 } 72 return nil 73 } 74 75 func (s *mockSink) EmitDDLEvent(ctx context.Context, ddl *model.DDLEvent) error { 76 panic("unreachable") 77 } 78 79 func (s *mockSink) FlushRowChangedEvents(ctx context.Context, resolvedTs uint64) (uint64, error) { 80 s.received = append(s.received, struct { 81 resolvedTs model.Ts 82 row *model.RowChangedEvent 83 }{resolvedTs: resolvedTs}) 84 return resolvedTs, nil 85 } 86 87 func (s *mockSink) EmitCheckpointTs(ctx context.Context, ts uint64) error { 88 panic("unreachable") 89 } 90 91 func (s *mockSink) Close(ctx context.Context) error { 92 return nil 93 } 94 95 func (s *mockSink) Barrier(ctx context.Context) error { 96 return nil 97 } 98 99 func (s *mockSink) Check(c *check.C, expected []struct { 100 resolvedTs model.Ts 101 row *model.RowChangedEvent 102 }) { 103 c.Assert(s.received, check.DeepEquals, expected) 104 } 105 106 func (s *mockSink) Reset() { 107 s.received = s.received[:0] 108 } 109 110 type mockCloseControlSink struct { 111 mockSink 112 closeCh chan interface{} 113 } 114 115 func (s *mockCloseControlSink) Close(ctx context.Context) error { 116 select { 117 case <-ctx.Done(): 118 return ctx.Err() 119 case <-s.closeCh: 120 return nil 121 } 122 } 123 124 type outputSuite struct{} 125 126 var _ = check.Suite(&outputSuite{}) 127 128 func (s *outputSuite) TestStatus(c *check.C) { 129 defer testleak.AfterTest(c)() 130 ctx := cdcContext.NewContext(context.Background(), &cdcContext.GlobalVars{}) 131 ctx = cdcContext.WithChangefeedVars(ctx, &cdcContext.ChangefeedVars{ 132 ID: "changefeed-id-test-status", 133 Info: &model.ChangeFeedInfo{ 134 StartTs: oracle.GoTimeToTS(time.Now()), 135 Config: config.GetDefaultReplicaConfig(), 136 }, 137 }) 138 139 // test stop at targetTs 140 node := newSinkNode(&mockSink{}, 0, 10, &mockFlowController{}) 141 c.Assert(node.Init(pipeline.MockNodeContext4Test(ctx, pipeline.Message{}, nil)), check.IsNil) 142 c.Assert(node.Status(), check.Equals, TableStatusInitializing) 143 144 c.Assert(node.Receive(pipeline.MockNodeContext4Test(ctx, pipeline.BarrierMessage(20), nil)), check.IsNil) 145 c.Assert(node.Status(), check.Equals, TableStatusInitializing) 146 147 c.Assert(node.Receive(pipeline.MockNodeContext4Test(ctx, 148 pipeline.PolymorphicEventMessage(&model.PolymorphicEvent{CRTs: 1, RawKV: &model.RawKVEntry{OpType: model.OpTypePut}, Row: &model.RowChangedEvent{}}), nil)), check.IsNil) 149 c.Assert(node.Status(), check.Equals, TableStatusInitializing) 150 151 c.Assert(node.Receive(pipeline.MockNodeContext4Test(ctx, 152 pipeline.PolymorphicEventMessage(&model.PolymorphicEvent{CRTs: 2, RawKV: &model.RawKVEntry{OpType: model.OpTypePut}, Row: &model.RowChangedEvent{}}), nil)), check.IsNil) 153 c.Assert(node.Status(), check.Equals, TableStatusInitializing) 154 155 c.Assert(node.Receive(pipeline.MockNodeContext4Test(ctx, 156 pipeline.PolymorphicEventMessage(&model.PolymorphicEvent{CRTs: 2, RawKV: &model.RawKVEntry{OpType: model.OpTypeResolved}, Row: &model.RowChangedEvent{}}), nil)), check.IsNil) 157 c.Assert(node.Status(), check.Equals, TableStatusRunning) 158 159 err := node.Receive(pipeline.MockNodeContext4Test(ctx, 160 pipeline.PolymorphicEventMessage(&model.PolymorphicEvent{CRTs: 15, RawKV: &model.RawKVEntry{OpType: model.OpTypeResolved}, Row: &model.RowChangedEvent{}}), nil)) 161 c.Assert(cerrors.ErrTableProcessorStoppedSafely.Equal(err), check.IsTrue) 162 c.Assert(node.Status(), check.Equals, TableStatusStopped) 163 c.Assert(node.CheckpointTs(), check.Equals, uint64(10)) 164 165 // test the stop at ts command 166 node = newSinkNode(&mockSink{}, 0, 10, &mockFlowController{}) 167 c.Assert(node.Init(pipeline.MockNodeContext4Test(ctx, pipeline.Message{}, nil)), check.IsNil) 168 c.Assert(node.Status(), check.Equals, TableStatusInitializing) 169 170 c.Assert(node.Receive(pipeline.MockNodeContext4Test(ctx, pipeline.BarrierMessage(20), nil)), check.IsNil) 171 c.Assert(node.Status(), check.Equals, TableStatusInitializing) 172 173 c.Assert(node.Receive(pipeline.MockNodeContext4Test(ctx, 174 pipeline.PolymorphicEventMessage(&model.PolymorphicEvent{CRTs: 2, RawKV: &model.RawKVEntry{OpType: model.OpTypeResolved}, Row: &model.RowChangedEvent{}}), nil)), check.IsNil) 175 c.Assert(node.Status(), check.Equals, TableStatusRunning) 176 177 err = node.Receive(pipeline.MockNodeContext4Test(ctx, 178 pipeline.CommandMessage(&pipeline.Command{Tp: pipeline.CommandTypeStop}), nil)) 179 c.Assert(cerrors.ErrTableProcessorStoppedSafely.Equal(err), check.IsTrue) 180 c.Assert(node.Status(), check.Equals, TableStatusStopped) 181 182 err = node.Receive(pipeline.MockNodeContext4Test(ctx, 183 pipeline.PolymorphicEventMessage(&model.PolymorphicEvent{CRTs: 7, RawKV: &model.RawKVEntry{OpType: model.OpTypeResolved}, Row: &model.RowChangedEvent{}}), nil)) 184 c.Assert(cerrors.ErrTableProcessorStoppedSafely.Equal(err), check.IsTrue) 185 c.Assert(node.Status(), check.Equals, TableStatusStopped) 186 c.Assert(node.CheckpointTs(), check.Equals, uint64(2)) 187 188 // test the stop at ts command is after then resolvedTs and checkpointTs is greater than stop ts 189 node = newSinkNode(&mockSink{}, 0, 10, &mockFlowController{}) 190 c.Assert(node.Init(pipeline.MockNodeContext4Test(ctx, pipeline.Message{}, nil)), check.IsNil) 191 c.Assert(node.Status(), check.Equals, TableStatusInitializing) 192 193 c.Assert(node.Receive(pipeline.MockNodeContext4Test(ctx, pipeline.BarrierMessage(20), nil)), check.IsNil) 194 c.Assert(node.Status(), check.Equals, TableStatusInitializing) 195 196 c.Assert(node.Receive(pipeline.MockNodeContext4Test(ctx, 197 pipeline.PolymorphicEventMessage(&model.PolymorphicEvent{CRTs: 7, RawKV: &model.RawKVEntry{OpType: model.OpTypeResolved}, Row: &model.RowChangedEvent{}}), nil)), check.IsNil) 198 c.Assert(node.Status(), check.Equals, TableStatusRunning) 199 200 err = node.Receive(pipeline.MockNodeContext4Test(ctx, 201 pipeline.CommandMessage(&pipeline.Command{Tp: pipeline.CommandTypeStop}), nil)) 202 c.Assert(cerrors.ErrTableProcessorStoppedSafely.Equal(err), check.IsTrue) 203 c.Assert(node.Status(), check.Equals, TableStatusStopped) 204 205 err = node.Receive(pipeline.MockNodeContext4Test(ctx, 206 pipeline.PolymorphicEventMessage(&model.PolymorphicEvent{CRTs: 7, RawKV: &model.RawKVEntry{OpType: model.OpTypeResolved}, Row: &model.RowChangedEvent{}}), nil)) 207 c.Assert(cerrors.ErrTableProcessorStoppedSafely.Equal(err), check.IsTrue) 208 c.Assert(node.Status(), check.Equals, TableStatusStopped) 209 c.Assert(node.CheckpointTs(), check.Equals, uint64(7)) 210 } 211 212 // TestStopStatus tests the table status of a pipeline is not set to stopped 213 // until the underlying sink is closed 214 func (s *outputSuite) TestStopStatus(c *check.C) { 215 defer testleak.AfterTest(c)() 216 ctx := cdcContext.NewContext(context.Background(), &cdcContext.GlobalVars{}) 217 ctx = cdcContext.WithChangefeedVars(ctx, &cdcContext.ChangefeedVars{ 218 ID: "changefeed-id-test-status", 219 Info: &model.ChangeFeedInfo{ 220 StartTs: oracle.GoTimeToTS(time.Now()), 221 Config: config.GetDefaultReplicaConfig(), 222 }, 223 }) 224 225 closeCh := make(chan interface{}, 1) 226 node := newSinkNode(&mockCloseControlSink{mockSink: mockSink{}, closeCh: closeCh}, 0, 100, &mockFlowController{}) 227 c.Assert(node.Init(pipeline.MockNodeContext4Test(ctx, pipeline.Message{}, nil)), check.IsNil) 228 c.Assert(node.Status(), check.Equals, TableStatusInitializing) 229 c.Assert(node.Receive(pipeline.MockNodeContext4Test(ctx, 230 pipeline.PolymorphicEventMessage(&model.PolymorphicEvent{CRTs: 2, RawKV: &model.RawKVEntry{OpType: model.OpTypeResolved}, Row: &model.RowChangedEvent{}}), nil)), check.IsNil) 231 c.Assert(node.Status(), check.Equals, TableStatusRunning) 232 233 var wg sync.WaitGroup 234 wg.Add(1) 235 go func() { 236 defer wg.Done() 237 // This will block until sink Close returns 238 err := node.Receive(pipeline.MockNodeContext4Test(ctx, 239 pipeline.CommandMessage(&pipeline.Command{Tp: pipeline.CommandTypeStop}), nil)) 240 c.Assert(cerrors.ErrTableProcessorStoppedSafely.Equal(err), check.IsTrue) 241 c.Assert(node.Status(), check.Equals, TableStatusStopped) 242 }() 243 // wait to ensure stop message is sent to the sink node 244 time.Sleep(time.Millisecond * 50) 245 c.Assert(node.Status(), check.Equals, TableStatusRunning) 246 closeCh <- struct{}{} 247 wg.Wait() 248 } 249 250 func (s *outputSuite) TestManyTs(c *check.C) { 251 defer testleak.AfterTest(c)() 252 ctx := cdcContext.NewContext(context.Background(), &cdcContext.GlobalVars{}) 253 ctx = cdcContext.WithChangefeedVars(ctx, &cdcContext.ChangefeedVars{ 254 ID: "changefeed-id-test-many-ts", 255 Info: &model.ChangeFeedInfo{ 256 StartTs: oracle.GoTimeToTS(time.Now()), 257 Config: config.GetDefaultReplicaConfig(), 258 }, 259 }) 260 sink := &mockSink{} 261 node := newSinkNode(sink, 0, 10, &mockFlowController{}) 262 c.Assert(node.Init(pipeline.MockNodeContext4Test(ctx, pipeline.Message{}, nil)), check.IsNil) 263 c.Assert(node.Status(), check.Equals, TableStatusInitializing) 264 265 c.Assert(node.Receive(pipeline.MockNodeContext4Test(ctx, 266 pipeline.PolymorphicEventMessage(&model.PolymorphicEvent{CRTs: 1, RawKV: &model.RawKVEntry{OpType: model.OpTypePut}, Row: &model.RowChangedEvent{CommitTs: 1}}), nil)), check.IsNil) 267 c.Assert(node.Status(), check.Equals, TableStatusInitializing) 268 269 c.Assert(node.Receive(pipeline.MockNodeContext4Test(ctx, 270 pipeline.PolymorphicEventMessage(&model.PolymorphicEvent{CRTs: 2, RawKV: &model.RawKVEntry{OpType: model.OpTypePut}, Row: &model.RowChangedEvent{CommitTs: 2}}), nil)), check.IsNil) 271 c.Assert(node.Status(), check.Equals, TableStatusInitializing) 272 273 c.Assert(node.Receive(pipeline.MockNodeContext4Test(ctx, 274 pipeline.PolymorphicEventMessage(&model.PolymorphicEvent{CRTs: 2, RawKV: &model.RawKVEntry{OpType: model.OpTypeResolved}, Row: &model.RowChangedEvent{}}), nil)), check.IsNil) 275 c.Assert(node.Status(), check.Equals, TableStatusRunning) 276 277 sink.Check(c, nil) 278 279 c.Assert(node.Receive(pipeline.MockNodeContext4Test(ctx, pipeline.BarrierMessage(1), nil)), check.IsNil) 280 c.Assert(node.Status(), check.Equals, TableStatusRunning) 281 282 sink.Check(c, []struct { 283 resolvedTs model.Ts 284 row *model.RowChangedEvent 285 }{ 286 {row: &model.RowChangedEvent{CommitTs: 1}}, 287 {row: &model.RowChangedEvent{CommitTs: 2}}, 288 {resolvedTs: 1}, 289 }) 290 sink.Reset() 291 c.Assert(node.ResolvedTs(), check.Equals, uint64(2)) 292 c.Assert(node.CheckpointTs(), check.Equals, uint64(1)) 293 294 c.Assert(node.Receive(pipeline.MockNodeContext4Test(ctx, pipeline.BarrierMessage(5), nil)), check.IsNil) 295 c.Assert(node.Status(), check.Equals, TableStatusRunning) 296 sink.Check(c, []struct { 297 resolvedTs model.Ts 298 row *model.RowChangedEvent 299 }{ 300 {resolvedTs: 2}, 301 }) 302 sink.Reset() 303 c.Assert(node.ResolvedTs(), check.Equals, uint64(2)) 304 c.Assert(node.CheckpointTs(), check.Equals, uint64(2)) 305 } 306 307 func (s *outputSuite) TestSplitUpdateEventWhenEnableOldValue(c *check.C) { 308 defer testleak.AfterTest(c)() 309 ctx := cdcContext.NewContext(context.Background(), &cdcContext.GlobalVars{}) 310 ctx = cdcContext.WithChangefeedVars(ctx, &cdcContext.ChangefeedVars{ 311 ID: "changefeed-id-test-split-update-event", 312 Info: &model.ChangeFeedInfo{ 313 StartTs: oracle.GoTimeToTS(time.Now()), 314 Config: config.GetDefaultReplicaConfig(), 315 }, 316 }) 317 sink := &mockSink{} 318 node := newSinkNode(sink, 0, 10, &mockFlowController{}) 319 c.Assert(node.Init(pipeline.MockNodeContext4Test(ctx, pipeline.Message{}, nil)), check.IsNil) 320 321 // nil row. 322 c.Assert(node.Receive(pipeline.MockNodeContext4Test(ctx, 323 pipeline.PolymorphicEventMessage(&model.PolymorphicEvent{CRTs: 1, RawKV: &model.RawKVEntry{OpType: model.OpTypePut}}), nil)), check.IsNil) 324 c.Assert(node.eventBuffer, check.HasLen, 0) 325 326 columns := []*model.Column{ 327 { 328 Name: "col1", 329 Flag: model.BinaryFlag, 330 Value: "col1-value-updated", 331 }, 332 { 333 Name: "col2", 334 Flag: model.HandleKeyFlag, 335 Value: "col2-value", 336 }, 337 } 338 preColumns := []*model.Column{ 339 { 340 Name: "col1", 341 Flag: model.BinaryFlag, 342 Value: "col1-value", 343 }, 344 { 345 Name: "col2", 346 Flag: model.HandleKeyFlag, 347 Value: "col2-value", 348 }, 349 } 350 c.Assert(node.Receive(pipeline.MockNodeContext4Test( 351 ctx, 352 pipeline.PolymorphicEventMessage(&model.PolymorphicEvent{ 353 CRTs: 1, 354 RawKV: &model.RawKVEntry{OpType: model.OpTypePut}, 355 Row: &model.RowChangedEvent{CommitTs: 1, Columns: columns, PreColumns: preColumns}, 356 }), nil)), 357 check.IsNil, 358 ) 359 c.Assert(node.eventBuffer, check.HasLen, 1) 360 c.Assert(node.eventBuffer[0].Row.Columns, check.HasLen, 2) 361 c.Assert(node.eventBuffer[0].Row.PreColumns, check.HasLen, 2) 362 } 363 364 func (s *outputSuite) TestSplitUpdateEventWhenDisableOldValue(c *check.C) { 365 defer testleak.AfterTest(c)() 366 ctx := cdcContext.NewContext(context.Background(), &cdcContext.GlobalVars{}) 367 cfg := config.GetDefaultReplicaConfig() 368 cfg.EnableOldValue = false 369 ctx = cdcContext.WithChangefeedVars(ctx, &cdcContext.ChangefeedVars{ 370 ID: "changefeed-id-test-split-update-event", 371 Info: &model.ChangeFeedInfo{ 372 StartTs: oracle.GoTimeToTS(time.Now()), 373 Config: cfg, 374 }, 375 }) 376 sink := &mockSink{} 377 node := newSinkNode(sink, 0, 10, &mockFlowController{}) 378 c.Assert(node.Init(pipeline.MockNodeContext4Test(ctx, pipeline.Message{}, nil)), check.IsNil) 379 380 // nil row. 381 c.Assert(node.Receive(pipeline.MockNodeContext4Test(ctx, 382 pipeline.PolymorphicEventMessage(&model.PolymorphicEvent{CRTs: 1, RawKV: &model.RawKVEntry{OpType: model.OpTypePut}}), nil)), check.IsNil) 383 c.Assert(node.eventBuffer, check.HasLen, 0) 384 385 // No update to the handle key column. 386 columns := []*model.Column{ 387 { 388 Name: "col1", 389 Flag: model.BinaryFlag, 390 Value: "col1-value-updated", 391 }, 392 { 393 Name: "col2", 394 Flag: model.HandleKeyFlag, 395 Value: "col2-value", 396 }, 397 } 398 preColumns := []*model.Column{ 399 { 400 Name: "col1", 401 Flag: model.BinaryFlag, 402 Value: "col1-value", 403 }, 404 { 405 Name: "col2", 406 Flag: model.HandleKeyFlag, 407 Value: "col2-value", 408 }, 409 } 410 411 c.Assert(node.Receive(pipeline.MockNodeContext4Test( 412 ctx, 413 pipeline.PolymorphicEventMessage(&model.PolymorphicEvent{ 414 CRTs: 1, 415 RawKV: &model.RawKVEntry{OpType: model.OpTypePut}, 416 Row: &model.RowChangedEvent{CommitTs: 1, Columns: columns, PreColumns: preColumns}, 417 }), nil)), 418 check.IsNil, 419 ) 420 c.Assert(node.eventBuffer, check.HasLen, 1) 421 c.Assert(node.eventBuffer[0].Row.Columns, check.HasLen, 2) 422 c.Assert(node.eventBuffer[0].Row.PreColumns, check.HasLen, 0) 423 424 // Cleanup. 425 node.eventBuffer = []*model.PolymorphicEvent{} 426 // Update to the handle key column. 427 columns = []*model.Column{ 428 { 429 Name: "col1", 430 Flag: model.BinaryFlag, 431 Value: "col1-value-updated", 432 }, 433 { 434 Name: "col2", 435 Flag: model.HandleKeyFlag, 436 Value: "col2-value-updated", 437 }, 438 } 439 preColumns = []*model.Column{ 440 { 441 Name: "col1", 442 Flag: model.BinaryFlag, 443 Value: "col1-value", 444 }, 445 { 446 Name: "col2", 447 Flag: model.HandleKeyFlag, 448 Value: "col2-value", 449 }, 450 } 451 452 c.Assert(node.Receive(pipeline.MockNodeContext4Test( 453 ctx, 454 pipeline.PolymorphicEventMessage(&model.PolymorphicEvent{ 455 CRTs: 1, 456 RawKV: &model.RawKVEntry{OpType: model.OpTypePut}, 457 Row: &model.RowChangedEvent{CommitTs: 1, Columns: columns, PreColumns: preColumns}, 458 }), nil)), 459 check.IsNil, 460 ) 461 // Split an update event into a delete and an insert event. 462 c.Assert(node.eventBuffer, check.HasLen, 2) 463 464 deleteEventIndex := 0 465 c.Assert(node.eventBuffer[deleteEventIndex].Row.Columns, check.HasLen, 0) 466 c.Assert(node.eventBuffer[deleteEventIndex].Row.PreColumns, check.HasLen, 2) 467 nonHandleKeyColIndex := 0 468 handleKeyColIndex := 1 469 // NOTICE: When old value disabled, we only keep the handle key pre cols. 470 c.Assert(node.eventBuffer[deleteEventIndex].Row.PreColumns[nonHandleKeyColIndex], check.IsNil) 471 c.Assert(node.eventBuffer[deleteEventIndex].Row.PreColumns[handleKeyColIndex].Name, check.Equals, "col2") 472 c.Assert(node.eventBuffer[deleteEventIndex].Row.PreColumns[handleKeyColIndex].Flag.IsHandleKey(), check.IsTrue) 473 474 insertEventIndex := 1 475 c.Assert(node.eventBuffer[insertEventIndex].Row.Columns, check.HasLen, 2) 476 c.Assert(node.eventBuffer[insertEventIndex].Row.PreColumns, check.HasLen, 0) 477 }