github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/dm/syncer/binlogstream/binlog_locations_test.go (about) 1 // Copyright 2022 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 binlogstream 15 16 import ( 17 "fmt" 18 "testing" 19 "time" 20 21 "github.com/go-mysql-org/go-mysql/mysql" 22 "github.com/go-mysql-org/go-mysql/replication" 23 "github.com/google/uuid" 24 "github.com/pingcap/tiflow/dm/pkg/binlog" 25 "github.com/pingcap/tiflow/dm/pkg/binlog/event" 26 "github.com/pingcap/tiflow/dm/pkg/gtid" 27 "github.com/pingcap/tiflow/dm/pkg/utils" 28 "github.com/stretchr/testify/suite" 29 ) 30 31 type ( 32 mockBinlogEvent struct { 33 typ int 34 args []interface{} 35 } 36 ) 37 38 const ( 39 DBCreate = iota 40 41 Write 42 43 DMLQuery 44 45 Headers 46 Rotate 47 ) 48 49 type testLocationSuite struct { 50 suite.Suite 51 52 eventsGenerator *event.Generator 53 54 serverID uint32 55 binlogFile string 56 nextBinlogFile string 57 binlogPos uint32 58 flavor string 59 prevGSetStr string 60 lastGTIDStr string 61 currGSetStr string 62 63 loc binlog.Location 64 65 prevGSet mysql.GTIDSet 66 lastGTID mysql.GTIDSet 67 currGSet mysql.GTIDSet 68 } 69 70 func TestLocationSuite(t *testing.T) { 71 suite.Run(t, new(testLocationSuite)) 72 } 73 74 func (s *testLocationSuite) SetupTest() { 75 s.serverID = 101 76 s.binlogFile = "mysql-bin.000001" 77 s.nextBinlogFile = "mysql-bin.000002" 78 s.binlogPos = 123 79 s.flavor = mysql.MySQLFlavor 80 s.prevGSetStr = "3ccc475b-2343-11e7-be21-6c0b84d59f30:1-14" 81 s.lastGTIDStr = "3ccc475b-2343-11e7-be21-6c0b84d59f30:14" 82 s.currGSetStr = "3ccc475b-2343-11e7-be21-6c0b84d59f30:1-15" 83 84 var err error 85 s.prevGSet, err = gtid.ParserGTID(s.flavor, s.prevGSetStr) 86 s.Require().NoError(err) 87 s.lastGTID, err = gtid.ParserGTID(s.flavor, s.lastGTIDStr) 88 s.Require().NoError(err) 89 s.currGSet, err = gtid.ParserGTID(s.flavor, s.currGSetStr) 90 s.Require().NoError(err) 91 92 s.loc = binlog.Location{ 93 Position: mysql.Position{ 94 Name: s.binlogFile, 95 Pos: s.binlogPos, 96 }, 97 } 98 prevGSet := s.prevGSet 99 s.Require().NoError(s.loc.SetGTID(prevGSet)) 100 101 s.eventsGenerator, err = event.NewGenerator(s.flavor, s.serverID, s.binlogPos, s.lastGTID, s.prevGSet, 0) 102 s.Require().NoError(err) 103 } 104 105 func (s *testLocationSuite) generateEvents(binlogEvents []mockBinlogEvent) []*replication.BinlogEvent { 106 events := make([]*replication.BinlogEvent, 0, 1024) 107 for _, e := range binlogEvents { 108 switch e.typ { 109 case DBCreate: 110 evs, _, err := s.eventsGenerator.GenCreateDatabaseEvents(e.args[0].(string)) 111 s.Require().NoError(err) 112 events = append(events, evs...) 113 114 case Write: 115 dmlData := []*event.DMLData{ 116 { 117 TableID: e.args[0].(uint64), 118 Schema: e.args[1].(string), 119 Table: e.args[2].(string), 120 ColumnType: e.args[3].([]byte), 121 Rows: e.args[4].([][]interface{}), 122 }, 123 } 124 eventType := replication.WRITE_ROWS_EVENTv2 125 evs, _, err := s.eventsGenerator.GenDMLEvents(eventType, dmlData, 0) 126 s.Require().NoError(err) 127 events = append(events, evs...) 128 129 case DMLQuery: 130 dmlData := []*event.DMLData{ 131 { 132 Schema: e.args[0].(string), 133 Query: e.args[1].(string), 134 }, 135 } 136 evs, _, err := s.eventsGenerator.GenDMLEvents(replication.UNKNOWN_EVENT, dmlData, 0) 137 s.Require().NoError(err) 138 events = append(events, evs...) 139 140 case Headers: 141 filename := e.args[0].(string) 142 fakeRotate, err := utils.GenFakeRotateEvent(filename, uint64(s.binlogPos), s.serverID) 143 s.Require().NoError(err) 144 events = append(events, fakeRotate) 145 146 events1, content, err := event.GenCommonFileHeader(s.flavor, s.serverID, s.prevGSet, true, 0) 147 s.Require().NoError(err) 148 events = append(events, events1...) 149 s.eventsGenerator.LatestPos = uint32(len(content)) 150 151 case Rotate: 152 nextFile := e.args[0].(string) 153 header := &replication.EventHeader{ 154 Timestamp: uint32(time.Now().Unix()), 155 ServerID: s.serverID, 156 } 157 e, err := event.GenRotateEvent(header, s.eventsGenerator.LatestPos, []byte(nextFile), 4) 158 s.Require().NoError(err) 159 events = append(events, e) 160 } 161 } 162 return events 163 } 164 165 // updateLastEventGSet increase the GTID set of last event. 166 func (s *testLocationSuite) updateLastEventGSet(events []*replication.BinlogEvent) { 167 e := events[len(events)-1] 168 switch v := e.Event.(type) { 169 case *replication.XIDEvent: 170 v.GSet = s.currGSet 171 case *replication.QueryEvent: 172 v.GSet = s.currGSet 173 default: 174 s.FailNow("last event is not expected, type %v", e.Header.EventType) 175 } 176 } 177 178 func (s *testLocationSuite) generateDMLEvents() []*replication.BinlogEvent { 179 events := s.generateEvents([]mockBinlogEvent{ 180 {Headers, []interface{}{s.binlogFile}}, 181 {Write, []interface{}{uint64(8), "foo", "bar", []byte{mysql.MYSQL_TYPE_LONG}, [][]interface{}{{int32(1)}}}}, 182 }) 183 184 s.updateLastEventGSet(events) 185 return events 186 } 187 188 func (s *testLocationSuite) generateDDLEvents() []*replication.BinlogEvent { 189 events := s.generateEvents([]mockBinlogEvent{ 190 {Headers, []interface{}{s.binlogFile}}, 191 {DBCreate, []interface{}{"foo1"}}, 192 }) 193 194 s.updateLastEventGSet(events) 195 return events 196 } 197 198 // initAndCheckOneTxnEvents checks locationRecorder.update can correctly track binlog events of one transaction. 199 // the first one of `expected` is the location to reset streamer, the last one is the last event of a transaction. 200 func (s *testLocationSuite) initAndCheckOneTxnEvents(events []*replication.BinlogEvent, expected []binlog.Location) { 201 r := newLocationRecorder() 202 r.reset(expected[0]) 203 s.Require().Equal(expected[0], r.curStartLocation) 204 s.Require().Equal(expected[0], r.curEndLocation) 205 s.Require().Equal(expected[0], r.txnEndLocation) 206 207 s.checkOneTxnEvents(r, events, expected) 208 } 209 210 func (s *testLocationSuite) checkOneTxnEvents(r *locationRecorder, events []*replication.BinlogEvent, expected []binlog.Location) { 211 afterGTID := -1 212 for i, e := range events { 213 r.update(e) 214 s.Require().Equal(expected[i], r.curStartLocation) 215 216 if afterGTID >= 0 { 217 afterGTID++ 218 } 219 if e.Header.EventType == replication.GTID_EVENT || e.Header.EventType == replication.MARIADB_GTID_EVENT { 220 afterGTID = 0 221 } 222 if afterGTID > 0 { 223 s.Require().Equal(expected[i+1].Position, r.curEndLocation.Position) 224 s.Require().Equal(expected[len(expected)-1].GetGTID(), r.curEndLocation.GetGTID()) 225 } else { 226 s.Require().Equal(expected[i+1], r.curEndLocation) 227 } 228 229 if i == len(events)-1 { 230 switch e.Header.EventType { 231 case replication.XID_EVENT, replication.QUERY_EVENT, replication.ROTATE_EVENT: 232 s.Require().Equal(expected[i+1], r.txnEndLocation) 233 default: 234 s.FailNow("type of last event is not expect", e.Header.EventType) 235 } 236 } else { 237 s.Require().Equal(expected[0], r.txnEndLocation) 238 } 239 } 240 } 241 242 // generateExpectedLocations generates binlog position part of location from given event. 243 func (s *testLocationSuite) generateExpectedLocations( 244 initLoc binlog.Location, 245 events []*replication.BinlogEvent, 246 ) []binlog.Location { 247 expected := make([]binlog.Location, len(events)+1) 248 for i := range expected { 249 if i == 0 { 250 // before receive first event, it should be reset location 251 expected[0] = initLoc 252 continue 253 } 254 expected[i] = initLoc 255 // those not-update-position events only occur in first events in these tests 256 e := events[i-1] 257 if shouldUpdatePos(e) && e.Header.EventType != replication.ROTATE_EVENT { 258 expected[i].Position.Pos = e.Header.LogPos 259 } 260 } 261 return expected 262 } 263 264 func (s *testLocationSuite) TestDMLUpdateLocationsGTID() { 265 events := s.generateDMLEvents() 266 267 expected := s.generateExpectedLocations(s.loc, events) 268 269 // check each event, also provide readability 270 s.Require().Len(events, 8) 271 { 272 s.Require().Equal(replication.ROTATE_EVENT, events[0].Header.EventType) 273 s.Require().Equal(uint32(0), events[0].Header.LogPos) 274 } 275 { 276 s.Require().Equal(replication.FORMAT_DESCRIPTION_EVENT, events[1].Header.EventType) 277 s.Require().Equal(uint32(123), events[1].Header.LogPos) 278 } 279 { 280 s.Require().Equal(replication.PREVIOUS_GTIDS_EVENT, events[2].Header.EventType) 281 s.Require().Equal(uint32(194), events[2].Header.LogPos) 282 gset := events[2].Event.(*replication.PreviousGTIDsEvent).GTIDSets 283 s.Require().Equal("3ccc475b-2343-11e7-be21-6c0b84d59f30:1-14", gset) 284 } 285 { 286 s.Require().Equal(replication.GTID_EVENT, events[3].Header.EventType) 287 s.Require().Equal(uint32(259), events[3].Header.LogPos) 288 e := events[3].Event.(*replication.GTIDEvent) 289 gtid := fmt.Sprintf("%s:%d", uuid.Must(uuid.FromBytes(e.SID)), e.GNO) 290 s.Require().Equal("3ccc475b-2343-11e7-be21-6c0b84d59f30:15", gtid) 291 } 292 { 293 s.Require().Equal(replication.QUERY_EVENT, events[4].Header.EventType) 294 s.Require().Equal(uint32(301), events[4].Header.LogPos) 295 } 296 { 297 s.Require().Equal(replication.TABLE_MAP_EVENT, events[5].Header.EventType) 298 s.Require().Equal(uint32(346), events[5].Header.LogPos) 299 } 300 { 301 s.Require().Equal(replication.WRITE_ROWS_EVENTv2, events[6].Header.EventType) 302 s.Require().Equal(uint32(386), events[6].Header.LogPos) 303 } 304 { 305 s.Require().Equal(replication.XID_EVENT, events[7].Header.EventType) 306 s.Require().Equal(uint32(417), events[7].Header.LogPos) 307 } 308 309 err := expected[8].SetGTID(s.currGSet) 310 s.Require().NoError(err) 311 312 s.initAndCheckOneTxnEvents(events, expected) 313 } 314 315 func (s *testLocationSuite) TestDMLUpdateLocationsPos() { 316 loc := s.loc 317 err := loc.SetGTID(gtid.MustZeroGTIDSet(mysql.MySQLFlavor)) 318 s.Require().NoError(err) 319 320 events := s.generateDMLEvents() 321 322 // now we have 8 events, this case doesn't want to test GTID replication, so we remove them 323 s.Require().Len(events, 8) 324 s.Require().Equal(replication.PREVIOUS_GTIDS_EVENT, events[2].Header.EventType) 325 s.Require().Equal(replication.GTID_EVENT, events[3].Header.EventType) 326 s.Require().Equal(replication.XID_EVENT, events[7].Header.EventType) 327 events[7].Event.(*replication.XIDEvent).GSet = nil 328 events = append(events[:2], events[4:]...) 329 330 // check each event, also provide readability 331 // not that we support LogPos-EventSize not equal to previous LogPos 332 s.Require().Len(events, 6) 333 { 334 s.Require().Equal(replication.ROTATE_EVENT, events[0].Header.EventType) 335 s.Require().Equal(uint32(0), events[0].Header.LogPos) 336 } 337 { 338 s.Require().Equal(replication.FORMAT_DESCRIPTION_EVENT, events[1].Header.EventType) 339 s.Require().Equal(uint32(123), events[1].Header.LogPos) 340 } 341 { 342 s.Require().Equal(replication.QUERY_EVENT, events[2].Header.EventType) 343 s.Require().Equal(uint32(301), events[2].Header.LogPos) 344 } 345 { 346 s.Require().Equal(replication.TABLE_MAP_EVENT, events[3].Header.EventType) 347 s.Require().Equal(uint32(346), events[3].Header.LogPos) 348 } 349 { 350 s.Require().Equal(replication.WRITE_ROWS_EVENTv2, events[4].Header.EventType) 351 s.Require().Equal(uint32(386), events[4].Header.LogPos) 352 } 353 { 354 s.Require().Equal(replication.XID_EVENT, events[5].Header.EventType) 355 s.Require().Equal(uint32(417), events[5].Header.LogPos) 356 } 357 358 expected := s.generateExpectedLocations(loc, events) 359 360 s.initAndCheckOneTxnEvents(events, expected) 361 } 362 363 func (s *testLocationSuite) TestDDLUpdateLocationsGTID() { 364 events := s.generateDDLEvents() 365 366 // we have 5 events 367 s.Require().Len(events, 5) 368 { 369 s.Require().Equal(replication.ROTATE_EVENT, events[0].Header.EventType) 370 s.Require().Equal(uint32(0), events[0].Header.LogPos) 371 } 372 { 373 s.Require().Equal(replication.FORMAT_DESCRIPTION_EVENT, events[1].Header.EventType) 374 s.Require().Equal(uint32(123), events[1].Header.LogPos) 375 } 376 { 377 s.Require().Equal(replication.PREVIOUS_GTIDS_EVENT, events[2].Header.EventType) 378 s.Require().Equal(uint32(194), events[2].Header.LogPos) 379 gset := events[2].Event.(*replication.PreviousGTIDsEvent).GTIDSets 380 s.Require().Equal("3ccc475b-2343-11e7-be21-6c0b84d59f30:1-14", gset) 381 } 382 { 383 s.Require().Equal(replication.GTID_EVENT, events[3].Header.EventType) 384 s.Require().Equal(uint32(259), events[3].Header.LogPos) 385 e := events[3].Event.(*replication.GTIDEvent) 386 gtid := fmt.Sprintf("%s:%d", uuid.Must(uuid.FromBytes(e.SID)), e.GNO) 387 s.Require().Equal("3ccc475b-2343-11e7-be21-6c0b84d59f30:15", gtid) 388 } 389 { 390 s.Require().Equal(replication.QUERY_EVENT, events[4].Header.EventType) 391 s.Require().Equal(uint32(322), events[4].Header.LogPos) 392 } 393 394 expected := s.generateExpectedLocations(s.loc, events) 395 396 err := expected[5].SetGTID(s.currGSet) 397 s.Require().NoError(err) 398 399 s.initAndCheckOneTxnEvents(events, expected) 400 } 401 402 func (s *testLocationSuite) TestDDLUpdateLocationsPos() { 403 loc := s.loc 404 err := loc.SetGTID(gtid.MustZeroGTIDSet(mysql.MySQLFlavor)) 405 s.Require().NoError(err) 406 407 events := s.generateDDLEvents() 408 409 // now we have 5 events, this case doesn't want to test GTID replication, so we remove them 410 s.Require().Len(events, 5) 411 412 s.Require().Equal(replication.PREVIOUS_GTIDS_EVENT, events[2].Header.EventType) 413 s.Require().Equal(replication.GTID_EVENT, events[3].Header.EventType) 414 s.Require().Equal(replication.QUERY_EVENT, events[4].Header.EventType) 415 events[4].Event.(*replication.QueryEvent).GSet = nil 416 events = append(events[:2], events[4:]...) 417 418 // check each event, also provide readability 419 // not that we support LogPos-EventSize not equal to previous LogPos 420 s.Require().Len(events, 3) 421 { 422 s.Require().Equal(replication.ROTATE_EVENT, events[0].Header.EventType) 423 s.Require().Equal(uint32(0), events[0].Header.LogPos) 424 } 425 { 426 s.Require().Equal(replication.FORMAT_DESCRIPTION_EVENT, events[1].Header.EventType) 427 s.Require().Equal(uint32(123), events[1].Header.LogPos) 428 } 429 { 430 s.Require().Equal(replication.QUERY_EVENT, events[2].Header.EventType) 431 s.Require().Equal(uint32(322), events[2].Header.LogPos) 432 } 433 434 // now we have 3 events, test about their 4 locations 435 expected := s.generateExpectedLocations(loc, events) 436 437 s.initAndCheckOneTxnEvents(events, expected) 438 } 439 440 func (s *testLocationSuite) generateDMLQueryEvents() []*replication.BinlogEvent { 441 var err error 442 s.eventsGenerator, err = event.NewGenerator(s.flavor, s.serverID, s.binlogPos, s.lastGTID, s.prevGSet, 0) 443 s.Require().NoError(err) 444 events := s.generateEvents([]mockBinlogEvent{ 445 {Headers, []interface{}{s.binlogFile}}, 446 {DMLQuery, []interface{}{"foo", "INSERT INTO v VALUES(1)"}}, 447 }) 448 449 s.updateLastEventGSet(events) 450 return events 451 } 452 453 func (s *testLocationSuite) TestDMLQueryUpdateLocationsGTID() { 454 events := s.generateDMLQueryEvents() 455 456 // we have 7 events 457 s.Require().Len(events, 7) 458 { 459 s.Require().Equal(replication.ROTATE_EVENT, events[0].Header.EventType) 460 s.Require().Equal(uint32(0), events[0].Header.LogPos) 461 } 462 { 463 s.Require().Equal(replication.FORMAT_DESCRIPTION_EVENT, events[1].Header.EventType) 464 s.Require().Equal(uint32(123), events[1].Header.LogPos) 465 } 466 { 467 s.Require().Equal(replication.PREVIOUS_GTIDS_EVENT, events[2].Header.EventType) 468 s.Require().Equal(uint32(194), events[2].Header.LogPos) 469 gset := events[2].Event.(*replication.PreviousGTIDsEvent).GTIDSets 470 s.Require().Equal("3ccc475b-2343-11e7-be21-6c0b84d59f30:1-14", gset) 471 } 472 { 473 s.Require().Equal(replication.GTID_EVENT, events[3].Header.EventType) 474 s.Require().Equal(uint32(259), events[3].Header.LogPos) 475 e := events[3].Event.(*replication.GTIDEvent) 476 gtid := fmt.Sprintf("%s:%d", uuid.Must(uuid.FromBytes(e.SID)), e.GNO) 477 s.Require().Equal("3ccc475b-2343-11e7-be21-6c0b84d59f30:15", gtid) 478 } 479 { 480 s.Require().Equal(replication.QUERY_EVENT, events[4].Header.EventType) 481 s.Require().Equal(uint32(301), events[4].Header.LogPos) 482 } 483 { 484 s.Require().Equal(replication.QUERY_EVENT, events[5].Header.EventType) 485 s.Require().Equal(uint32(364), events[5].Header.LogPos) 486 } 487 { 488 s.Require().Equal(replication.XID_EVENT, events[6].Header.EventType) 489 s.Require().Equal(uint32(395), events[6].Header.LogPos) 490 } 491 492 expected := s.generateExpectedLocations(s.loc, events) 493 494 err := expected[7].SetGTID(s.currGSet) 495 s.Require().NoError(err) 496 497 s.initAndCheckOneTxnEvents(events, expected) 498 } 499 500 func (s *testLocationSuite) generateRotateAndDMLEvents() []*replication.BinlogEvent { 501 var err error 502 s.eventsGenerator, err = event.NewGenerator(s.flavor, s.serverID, s.binlogPos, s.lastGTID, s.prevGSet, 0) 503 s.Require().NoError(err) 504 events := s.generateEvents([]mockBinlogEvent{ 505 {Headers, []interface{}{s.binlogFile}}, 506 {Rotate, []interface{}{s.nextBinlogFile}}, 507 {Headers, []interface{}{s.nextBinlogFile}}, 508 {Write, []interface{}{uint64(8), "foo", "bar", []byte{mysql.MYSQL_TYPE_LONG}, [][]interface{}{{int32(1)}}}}, 509 }) 510 511 s.updateLastEventGSet(events) 512 return events 513 } 514 515 func (s *testLocationSuite) TestRotateEvent() { 516 events := s.generateRotateAndDMLEvents() 517 518 s.Require().Len(events, 12) 519 520 nextLoc := s.loc 521 nextLoc.Position.Name = s.nextBinlogFile 522 expected := s.generateExpectedLocations(nextLoc, events) 523 524 // reset events of first binlog file 525 expected[0].Position.Name = s.binlogFile 526 s.Require().Equal(replication.ROTATE_EVENT, events[0].Header.EventType) 527 expected[1].Position.Name = s.binlogFile 528 s.Require().Equal(replication.FORMAT_DESCRIPTION_EVENT, events[1].Header.EventType) 529 expected[2].Position.Name = s.binlogFile 530 s.Require().Equal(replication.PREVIOUS_GTIDS_EVENT, events[2].Header.EventType) 531 expected[3].Position.Name = s.binlogFile 532 s.Require().Equal(replication.ROTATE_EVENT, events[3].Header.EventType) 533 expected[4].Position.Pos = 4 534 s.Require().Equal(replication.ROTATE_EVENT, events[4].Header.EventType) 535 expected[5].Position.Pos = 4 536 s.Require().Equal(replication.FORMAT_DESCRIPTION_EVENT, events[5].Header.EventType) 537 expected[6].Position.Pos = 4 538 s.Require().Equal(replication.PREVIOUS_GTIDS_EVENT, events[6].Header.EventType) 539 expected[7].Position.Pos = 4 540 541 err := expected[12].SetGTID(s.currGSet) 542 s.Require().NoError(err) 543 544 r := newLocationRecorder() 545 r.reset(expected[0]) 546 s.Require().Equal(expected[0], r.curStartLocation) 547 s.Require().Equal(expected[0], r.curEndLocation) 548 s.Require().Equal(expected[0], r.txnEndLocation) 549 550 s.checkOneTxnEvents(r, events[:4], expected[:5]) 551 s.checkOneTxnEvents(r, events[4:], expected[4:]) 552 }