github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/dm/master/shardddl/optimist_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 shardddl 15 16 import ( 17 "context" 18 "fmt" 19 "testing" 20 "time" 21 22 tiddl "github.com/pingcap/tidb/pkg/ddl" 23 "github.com/pingcap/tidb/pkg/parser" 24 "github.com/pingcap/tidb/pkg/parser/ast" 25 "github.com/pingcap/tidb/pkg/parser/model" 26 "github.com/pingcap/tidb/pkg/sessionctx" 27 "github.com/pingcap/tidb/pkg/util/dbutil" 28 "github.com/pingcap/tidb/pkg/util/mock" 29 "github.com/pingcap/tiflow/dm/config" 30 "github.com/pingcap/tiflow/dm/config/dbconfig" 31 "github.com/pingcap/tiflow/dm/pb" 32 "github.com/pingcap/tiflow/dm/pkg/log" 33 "github.com/pingcap/tiflow/dm/pkg/shardddl/optimism" 34 "github.com/stretchr/testify/require" 35 "github.com/stretchr/testify/suite" 36 clientv3 "go.etcd.io/etcd/client/v3" 37 "go.etcd.io/etcd/tests/v3/integration" 38 ) 39 40 func TestOptimistSuite(t *testing.T) { 41 suite.Run(t, new(testOptimistSuite)) 42 } 43 44 type testOptimistSuite struct { 45 suite.Suite 46 mockCluster *integration.ClusterV3 47 etcdTestCli *clientv3.Client 48 } 49 50 func (t *testOptimistSuite) SetupSuite() { 51 require.NoError(t.T(), log.InitLogger(&log.Config{})) 52 53 integration.BeforeTestExternal(t.T()) 54 t.mockCluster = integration.NewClusterV3(t.T(), &integration.ClusterConfig{Size: 1}) 55 t.etcdTestCli = t.mockCluster.RandClient() 56 } 57 58 func (t *testOptimistSuite) TearDownSuite() { 59 t.mockCluster.Terminate(t.T()) 60 } 61 62 func (t *testOptimistSuite) TearDownTest() { 63 t.clearOptimistTestSourceInfoOperation() 64 } 65 66 // clear keys in etcd test cluster. 67 func (t *testOptimistSuite) clearOptimistTestSourceInfoOperation() { 68 require.NoError(t.T(), optimism.ClearTestInfoOperationColumn(t.etcdTestCli)) 69 } 70 71 func createTableInfo(t *testing.T, p *parser.Parser, se sessionctx.Context, tableID int64, sql string) *model.TableInfo { 72 t.Helper() 73 node, err := p.ParseOneStmt(sql, "utf8mb4", "utf8mb4_bin") 74 if err != nil { 75 t.Fatalf("fail to parse stmt, %v", err) 76 } 77 createStmtNode, ok := node.(*ast.CreateTableStmt) 78 if !ok { 79 t.Fatalf("%s is not a CREATE TABLE statement", sql) 80 } 81 info, err := tiddl.MockTableInfo(se, createStmtNode, tableID) 82 if err != nil { 83 t.Fatalf("fail to create table info, %v", err) 84 } 85 return info 86 } 87 88 func watchExactOneOperation( 89 ctx context.Context, 90 cli *clientv3.Client, 91 task, source, upSchema, upTable string, 92 revision int64, 93 ) (optimism.Operation, error) { 94 opCh := make(chan optimism.Operation, 10) 95 errCh := make(chan error, 10) 96 done := make(chan struct{}) 97 subCtx, cancel := context.WithCancel(ctx) 98 go func() { 99 optimism.WatchOperationPut(subCtx, cli, task, source, upSchema, upTable, revision, opCh, errCh) 100 close(done) 101 }() 102 defer func() { 103 cancel() 104 <-done 105 }() 106 107 var op optimism.Operation 108 select { 109 case op = <-opCh: 110 case err := <-errCh: 111 return op, err 112 case <-ctx.Done(): 113 return op, ctx.Err() 114 } 115 116 // Wait 100ms to check if there is unexpected operation. 117 select { 118 case extraOp := <-opCh: 119 return op, fmt.Errorf("unpexecped operation %s", extraOp) 120 case <-time.After(time.Millisecond * 100): 121 } 122 return op, nil 123 } 124 125 func checkLocks(t *testing.T, o *Optimist, expectedLocks []*pb.DDLLock, task string, sources []string) { 126 t.Helper() 127 lock, err := o.ShowLocks(task, sources) 128 require.NoError(t, err) 129 if expectedLocks == nil { 130 require.Len(t, lock, 0) 131 } else { 132 require.Equal(t, expectedLocks, lock) 133 } 134 } 135 136 func checkLocksByMap(t *testing.T, o *Optimist, expectedLocks map[string]*pb.DDLLock, sources []string, lockIDs ...string) { 137 t.Helper() 138 lock, err := o.ShowLocks("", sources) 139 require.NoError(t, err) 140 require.Len(t, lock, len(lockIDs)) 141 lockIDMap := make(map[string]struct{}) 142 for _, lockID := range lockIDs { 143 lockIDMap[lockID] = struct{}{} 144 } 145 for i := range lockIDs { 146 _, ok := lockIDMap[lock[i].ID] 147 require.True(t, ok) 148 delete(lockIDMap, lock[i].ID) 149 require.Equal(t, expectedLocks[lock[i].ID], lock[i]) 150 } 151 } 152 153 func (t *testOptimistSuite) TestOptimistSourceTables() { 154 var ( 155 logger = log.L() 156 o = NewOptimist(&logger, getDownstreamMeta) 157 task = "task" 158 source1 = "mysql-replica-1" 159 source2 = "mysql-replica-2" 160 downSchema = "db" 161 downTable = "tbl" 162 st1 = optimism.NewSourceTables(task, source1) 163 st2 = optimism.NewSourceTables(task, source2) 164 ) 165 166 st1.AddTable("db", "tbl-1", downSchema, downTable) 167 st1.AddTable("db", "tbl-2", downSchema, downTable) 168 st2.AddTable("db", "tbl-1", downSchema, downTable) 169 st2.AddTable("db", "tbl-2", downSchema, downTable) 170 171 ctx, cancel := context.WithCancel(context.Background()) 172 defer cancel() 173 174 // CASE 1: start without any previous kv and no etcd operation. 175 require.NoError(t.T(), o.Start(ctx, t.etcdTestCli)) 176 require.Nil(t.T(), o.tk.FindTables(task, downSchema, downTable)) 177 o.Close() 178 o.Close() // close multiple times. 179 180 // CASE 2: start again without any previous kv. 181 require.NoError(t.T(), o.Start(ctx, t.etcdTestCli)) 182 require.Nil(t.T(), o.tk.FindTables(task, downSchema, downTable)) 183 184 // PUT st1, should find tables. 185 _, err := optimism.PutSourceTables(t.etcdTestCli, st1) 186 require.NoError(t.T(), err) 187 require.Eventually(t.T(), func() bool { 188 tts := o.tk.FindTables(task, downSchema, downTable) 189 return len(tts) == 1 190 }, 30*100*time.Millisecond, 100*time.Millisecond) 191 tts := o.tk.FindTables(task, downSchema, downTable) 192 require.Len(t.T(), tts, 1) 193 require.Equal(t.T(), st1.TargetTable(downSchema, downTable), tts[0]) 194 o.Close() 195 196 // CASE 3: start again with previous source tables. 197 require.NoError(t.T(), o.Start(ctx, t.etcdTestCli)) 198 tts = o.tk.FindTables(task, downSchema, downTable) 199 require.Len(t.T(), tts, 1) 200 require.Equal(t.T(), st1.TargetTable(downSchema, downTable), tts[0]) 201 202 // PUT st2, should find more tables. 203 _, err = optimism.PutSourceTables(t.etcdTestCli, st2) 204 require.NoError(t.T(), err) 205 require.Eventually(t.T(), func() bool { 206 tts = o.tk.FindTables(task, downSchema, downTable) 207 return len(tts) == 2 208 }, 30*100*time.Millisecond, 100*time.Millisecond) 209 tts = o.tk.FindTables(task, downSchema, downTable) 210 require.Len(t.T(), tts, 2) 211 require.Equal(t.T(), st1.TargetTable(downSchema, downTable), tts[0]) 212 require.Equal(t.T(), st2.TargetTable(downSchema, downTable), tts[1]) 213 o.Close() 214 215 // CASE 4: create (not re-start) a new optimist with previous source tables. 216 o = NewOptimist(&logger, getDownstreamMeta) 217 require.NoError(t.T(), o.Start(ctx, t.etcdTestCli)) 218 tts = o.tk.FindTables(task, downSchema, downTable) 219 require.Len(t.T(), tts, 2) 220 require.Equal(t.T(), st1.TargetTable(downSchema, downTable), tts[0]) 221 require.Equal(t.T(), st2.TargetTable(downSchema, downTable), tts[1]) 222 223 // DELETE st1, should find less tables. 224 _, err = optimism.DeleteSourceTables(t.etcdTestCli, st1) 225 require.NoError(t.T(), err) 226 require.Eventually(t.T(), func() bool { 227 tts = o.tk.FindTables(task, downSchema, downTable) 228 return len(tts) == 1 229 }, 30*100*time.Millisecond, 100*time.Millisecond) 230 tts = o.tk.FindTables(task, downSchema, downTable) 231 require.Len(t.T(), tts, 1) 232 require.Equal(t.T(), st2.TargetTable(downSchema, downTable), tts[0]) 233 o.Close() 234 } 235 236 func (t *testOptimistSuite) TestOptimist() { 237 t.testOptimist(t.etcdTestCli, noRestart) 238 t.testOptimist(t.etcdTestCli, restartOnly) 239 t.testOptimist(t.etcdTestCli, restartNewInstance) 240 t.testSortInfos(t.etcdTestCli) 241 } 242 243 func (t *testOptimistSuite) testOptimist(cli *clientv3.Client, restart int) { 244 defer func() { 245 require.NoError(t.T(), optimism.ClearTestInfoOperationColumn(cli)) 246 }() 247 248 var ( 249 tick = 100 * time.Millisecond 250 waitFor = 30 * tick 251 logger = log.L() 252 o = NewOptimist(&logger, getDownstreamMeta) 253 254 rebuildOptimist = func(ctx context.Context) { 255 switch restart { 256 case restartOnly: 257 o.Close() 258 require.NoError(t.T(), o.Start(ctx, cli)) 259 case restartNewInstance: 260 o.Close() 261 o = NewOptimist(&logger, getDownstreamMeta) 262 require.NoError(t.T(), o.Start(ctx, cli)) 263 } 264 } 265 266 task = "task-test-optimist" 267 source1 = "mysql-replica-1" 268 source2 = "mysql-replica-2" 269 downSchema = "foo" 270 downTable = "bar" 271 lockID = fmt.Sprintf("%s-`%s`.`%s`", task, downSchema, downTable) 272 st1 = optimism.NewSourceTables(task, source1) 273 st31 = optimism.NewSourceTables(task, source1) 274 st32 = optimism.NewSourceTables(task, source2) 275 p = parser.New() 276 se = mock.NewContext() 277 tblID int64 = 111 278 DDLs1 = []string{"ALTER TABLE bar ADD COLUMN c1 INT"} 279 DDLs2 = []string{"ALTER TABLE bar ADD COLUMN c2 INT"} 280 DDLs3 = []string{"ALTER TABLE bar DROP COLUMN c2"} 281 ti0 = createTableInfo(t.T(), p, se, tblID, `CREATE TABLE bar (id INT PRIMARY KEY)`) 282 ti1 = createTableInfo(t.T(), p, se, tblID, `CREATE TABLE bar (id INT PRIMARY KEY, c1 INT)`) 283 ti2 = createTableInfo(t.T(), p, se, tblID, `CREATE TABLE bar (id INT PRIMARY KEY, c1 INT, c2 INT)`) 284 ti3 = ti1 285 i11 = optimism.NewInfo(task, source1, "foo", "bar-1", downSchema, downTable, DDLs1, ti0, []*model.TableInfo{ti1}) 286 i12 = optimism.NewInfo(task, source1, "foo", "bar-2", downSchema, downTable, DDLs1, ti0, []*model.TableInfo{ti1}) 287 i21 = optimism.NewInfo(task, source1, "foo", "bar-1", downSchema, downTable, DDLs2, ti1, []*model.TableInfo{ti2}) 288 i23 = optimism.NewInfo(task, source2, "foo-2", "bar-3", downSchema, downTable, DDLs2, ti1, []*model.TableInfo{ti2}) 289 i31 = optimism.NewInfo(task, source1, "foo", "bar-1", downSchema, downTable, DDLs3, ti2, []*model.TableInfo{ti3}) 290 i33 = optimism.NewInfo(task, source2, "foo-2", "bar-3", downSchema, downTable, DDLs3, ti2, []*model.TableInfo{ti3}) 291 ) 292 293 st1.AddTable("foo", "bar-1", downSchema, downTable) 294 st1.AddTable("foo", "bar-2", downSchema, downTable) 295 st31.AddTable("foo", "bar-1", downSchema, downTable) 296 st32.AddTable("foo-2", "bar-3", downSchema, downTable) 297 298 // put source tables first. 299 _, err := optimism.PutSourceTables(cli, st1) 300 require.NoError(t.T(), err) 301 302 ctx, cancel := context.WithCancel(context.Background()) 303 defer cancel() 304 305 // CASE 1: start without any previous shard DDL info. 306 require.NoError(t.T(), o.Start(ctx, cli)) 307 require.Len(t.T(), o.Locks(), 0) 308 o.Close() 309 o.Close() // close multiple times. 310 311 // CASE 2: start again without any previous shard DDL info. 312 require.NoError(t.T(), o.Start(ctx, cli)) 313 require.Len(t.T(), o.Locks(), 0) 314 315 // PUT i11, will create a lock but not synced. 316 rev1, err := optimism.PutInfo(cli, i11) 317 require.NoError(t.T(), err) 318 require.Eventually(t.T(), func() bool { 319 return len(o.Locks()) == 1 320 }, waitFor, tick) 321 require.Contains(t.T(), o.Locks(), lockID) 322 synced, remain := o.Locks()[lockID].IsSynced() 323 require.False(t.T(), synced) 324 require.Equal(t.T(), 1, remain) 325 326 // check ShowLocks. 327 expectedLock := []*pb.DDLLock{ 328 { 329 ID: lockID, 330 Task: task, 331 Mode: config.ShardOptimistic, 332 Owner: "", 333 DDLs: nil, 334 Synced: []string{ 335 fmt.Sprintf("%s-%s", i11.Source, dbutil.TableName(i11.UpSchema, i11.UpTable)), 336 }, 337 Unsynced: []string{ 338 fmt.Sprintf("%s-%s", i12.Source, dbutil.TableName(i12.UpSchema, i12.UpTable)), 339 }, 340 }, 341 } 342 checkLocks(t.T(), o, expectedLock, "", []string{}) 343 344 // wait operation for i11 become available. 345 op11, err := watchExactOneOperation(ctx, cli, i11.Task, i11.Source, i11.UpSchema, i11.UpTable, rev1) 346 require.NoError(t.T(), err) 347 require.Equal(t.T(), DDLs1, op11.DDLs) 348 require.Equal(t.T(), optimism.ConflictNone, op11.ConflictStage) 349 checkLocks(t.T(), o, expectedLock, "", []string{}) 350 351 // mark op11 as done. 352 op11c := op11 353 op11c.Done = true 354 _, putted, err := optimism.PutOperation(cli, false, op11c, 0) 355 require.NoError(t.T(), err) 356 require.True(t.T(), putted) 357 require.Eventually(t.T(), func() bool { 358 lock := o.Locks()[lockID] 359 if lock == nil { 360 return false 361 } 362 return lock.IsDone(op11.Source, op11.UpSchema, op11.UpTable) 363 }, waitFor, tick) 364 require.False(t.T(), o.Locks()[lockID].IsDone(i12.Source, i12.UpSchema, i12.UpTable)) 365 checkLocks(t.T(), o, expectedLock, "", []string{}) 366 367 // PUT i12, the lock will be synced. 368 rev2, err := optimism.PutInfo(cli, i12) 369 require.NoError(t.T(), err) 370 require.Eventually(t.T(), func() bool { 371 synced, _ = o.Locks()[lockID].IsSynced() 372 return synced 373 }, waitFor, tick) 374 375 expectedLock = []*pb.DDLLock{ 376 { 377 ID: lockID, 378 Task: task, 379 Mode: config.ShardOptimistic, 380 Owner: "", 381 DDLs: nil, 382 Synced: []string{ 383 fmt.Sprintf("%s-%s", i11.Source, dbutil.TableName(i11.UpSchema, i11.UpTable)), 384 fmt.Sprintf("%s-%s", i12.Source, dbutil.TableName(i12.UpSchema, i12.UpTable)), 385 }, 386 Unsynced: []string{}, 387 }, 388 } 389 checkLocks(t.T(), o, expectedLock, "", []string{}) 390 391 // wait operation for i12 become available. 392 op12, err := watchExactOneOperation(ctx, cli, i12.Task, i12.Source, i12.UpSchema, i12.UpTable, rev2) 393 require.NoError(t.T(), err) 394 require.Equal(t.T(), DDLs1, op12.DDLs) 395 require.Equal(t.T(), optimism.ConflictNone, op12.ConflictStage) 396 checkLocks(t.T(), o, expectedLock, "", []string{}) 397 398 // mark op12 as done, the lock should be resolved. 399 op12c := op12 400 op12c.Done = true 401 _, putted, err = optimism.PutOperation(cli, false, op12c, 0) 402 require.NoError(t.T(), err) 403 require.True(t.T(), putted) 404 require.Eventually(t.T(), func() bool { 405 _, ok := o.Locks()[lockID] 406 return !ok 407 }, waitFor, tick) 408 require.Len(t.T(), o.Locks(), 0) 409 checkLocks(t.T(), o, nil, "", nil) 410 411 // no shard DDL info or lock operation exists. 412 ifm, _, err := optimism.GetAllInfo(cli) 413 require.NoError(t.T(), err) 414 require.Len(t.T(), ifm, 0) 415 opm, _, err := optimism.GetAllOperations(cli) 416 require.NoError(t.T(), err) 417 require.Len(t.T(), opm, 0) 418 419 // put another table info. 420 rev1, err = optimism.PutInfo(cli, i21) 421 require.NoError(t.T(), err) 422 require.Eventually(t.T(), func() bool { 423 return len(o.Locks()) == 1 424 }, waitFor, tick) 425 require.Contains(t.T(), o.Locks(), lockID) 426 synced, remain = o.Locks()[lockID].IsSynced() 427 require.False(t.T(), synced) 428 require.Equal(t.T(), 1, remain) 429 430 // wait operation for i21 become available. 431 op21, err := watchExactOneOperation(ctx, cli, i21.Task, i21.Source, i21.UpSchema, i21.UpTable, rev1) 432 require.NoError(t.T(), err) 433 require.Equal(t.T(), i21.DDLs, op21.DDLs) 434 require.Equal(t.T(), optimism.ConflictNone, op12.ConflictStage) 435 436 // CASE 3: start again with some previous shard DDL info and the lock is un-synced. 437 rebuildOptimist(ctx) 438 require.Len(t.T(), o.Locks(), 1) 439 require.Contains(t.T(), o.Locks(), lockID) 440 synced, remain = o.Locks()[lockID].IsSynced() 441 require.False(t.T(), synced) 442 require.Equal(t.T(), 1, remain) 443 444 // put table info for a new table (to simulate `CREATE TABLE`). 445 // lock will fetch the new table info from downstream. 446 // here will use ti1 447 _, err = optimism.PutSourceTables(cli, st32) 448 require.NoError(t.T(), err) 449 require.Eventually(t.T(), func() bool { 450 ready := o.Locks()[lockID].Ready() 451 return !ready[source2][i23.UpSchema][i23.UpTable] 452 }, waitFor, tick) 453 synced, remain = o.Locks()[lockID].IsSynced() 454 require.False(t.T(), synced) 455 require.Equal(t.T(), remain, 2) 456 tts := o.tk.FindTables(task, downSchema, downTable) 457 require.Len(t.T(), tts, 2) 458 require.Equal(t.T(), source2, tts[1].Source) 459 require.Contains(t.T(), tts[1].UpTables, i23.UpSchema) 460 require.Contains(t.T(), tts[1].UpTables[i23.UpSchema], i23.UpTable) 461 462 // ddl for new table 463 rev3, err := optimism.PutInfo(cli, i23) 464 require.NoError(t.T(), err) 465 // wait operation for i23 become available. 466 op23, err := watchExactOneOperation(ctx, cli, i23.Task, i23.Source, i23.UpSchema, i23.UpTable, rev3) 467 require.NoError(t.T(), err) 468 require.Equal(t.T(), op23.DDLs, i23.DDLs) 469 require.Equal(t.T(), op23.ConflictStage, optimism.ConflictNone) 470 471 require.Contains(t.T(), o.Locks(), lockID) 472 synced, remain = o.Locks()[lockID].IsSynced() 473 require.False(t.T(), synced) 474 require.Equal(t.T(), remain, 1) 475 476 // check ShowLocks. 477 expectedLock = []*pb.DDLLock{ 478 { 479 ID: lockID, 480 Task: task, 481 Mode: config.ShardOptimistic, 482 Owner: "", 483 DDLs: nil, 484 Synced: []string{ 485 fmt.Sprintf("%s-%s", source1, dbutil.TableName(i21.UpSchema, i21.UpTable)), 486 fmt.Sprintf("%s-%s", source2, dbutil.TableName(i23.UpSchema, i23.UpTable)), 487 }, 488 Unsynced: []string{ 489 fmt.Sprintf("%s-%s", source1, dbutil.TableName(i12.UpSchema, i12.UpTable)), 490 }, 491 }, 492 } 493 checkLocks(t.T(), o, expectedLock, "", []string{}) 494 checkLocks(t.T(), o, expectedLock, task, []string{}) 495 checkLocks(t.T(), o, expectedLock, "", []string{source1}) 496 checkLocks(t.T(), o, expectedLock, "", []string{source2}) 497 checkLocks(t.T(), o, expectedLock, "", []string{source1, source2}) 498 checkLocks(t.T(), o, expectedLock, task, []string{source1, source2}) 499 checkLocks(t.T(), o, nil, "not-exist", []string{}) 500 checkLocks(t.T(), o, nil, "", []string{"not-exist"}) 501 502 // delete i12 for a table (to simulate `DROP TABLE`), the lock should become synced again. 503 rev2, err = optimism.PutInfo(cli, i12) // put i12 first to trigger DELETE for i12. 504 require.NoError(t.T(), err) 505 // wait until operation for i12 ready. 506 _, err = watchExactOneOperation(ctx, cli, i12.Task, i12.Source, i12.UpSchema, i12.UpTable, rev2) 507 require.NoError(t.T(), err) 508 509 _, err = optimism.PutSourceTables(cli, st31) 510 require.NoError(t.T(), err) 511 require.Eventually(t.T(), func() bool { 512 synced, _ = o.Locks()[lockID].IsSynced() 513 return synced 514 }, waitFor, tick) 515 tts = o.tk.FindTables(task, downSchema, downTable) 516 require.Len(t.T(), tts, 2) 517 require.Equal(t.T(), source1, tts[0].Source) 518 require.Len(t.T(), tts[0].UpTables, 1) 519 require.Contains(t.T(), tts[0].UpTables[i21.UpSchema], i21.UpTable) 520 require.Equal(t.T(), source2, tts[1].Source) 521 require.Len(t.T(), tts[1].UpTables, 1) 522 require.Contains(t.T(), tts[1].UpTables[i23.UpSchema], i23.UpTable) 523 require.False(t.T(), o.Locks()[lockID].IsResolved()) 524 require.False(t.T(), o.Locks()[lockID].IsDone(i21.Source, i21.UpSchema, i21.UpTable)) 525 require.False(t.T(), o.Locks()[lockID].IsDone(i23.Source, i23.UpSchema, i23.UpTable)) 526 527 // CASE 4: start again with some previous shard DDL info and non-`done` operation. 528 rebuildOptimist(ctx) 529 require.Len(t.T(), o.Locks(), 1) 530 require.Contains(t.T(), o.Locks(), lockID) 531 synced, remain = o.Locks()[lockID].IsSynced() 532 require.True(t.T(), synced) 533 require.Equal(t.T(), 0, remain) 534 require.False(t.T(), o.Locks()[lockID].IsDone(i21.Source, i21.UpSchema, i21.UpTable)) 535 require.False(t.T(), o.Locks()[lockID].IsDone(i23.Source, i23.UpSchema, i23.UpTable)) 536 537 // mark op21 as done. 538 op21c := op21 539 op21c.Done = true 540 _, putted, err = optimism.PutOperation(cli, false, op21c, 0) 541 require.NoError(t.T(), err) 542 require.True(t.T(), putted) 543 require.Eventually(t.T(), func() bool { 544 return o.Locks()[lockID].IsDone(i21.Source, i21.UpSchema, i21.UpTable) 545 }, waitFor, tick) 546 547 // CASE 5: start again with some previous shard DDL info and `done` operation. 548 rebuildOptimist(ctx) 549 require.Len(t.T(), o.Locks(), 1) 550 require.Contains(t.T(), o.Locks(), lockID) 551 synced, remain = o.Locks()[lockID].IsSynced() 552 require.True(t.T(), synced) 553 require.Equal(t.T(), 0, remain) 554 require.True(t.T(), o.Locks()[lockID].IsDone(i21.Source, i21.UpSchema, i21.UpTable)) 555 require.False(t.T(), o.Locks()[lockID].IsDone(i23.Source, i23.UpSchema, i23.UpTable)) 556 557 // mark op23 as done. 558 op23c := op23 559 op23c.Done = true 560 _, putted, err = optimism.PutOperation(cli, false, op23c, 0) 561 require.NoError(t.T(), err) 562 require.True(t.T(), putted) 563 require.Eventually(t.T(), func() bool { 564 _, ok := o.Locks()[lockID] 565 return !ok 566 }, waitFor, tick) 567 require.Len(t.T(), o.Locks(), 0) 568 569 // PUT i31, will create a lock but not synced (to test `DROP COLUMN`) 570 rev1, err = optimism.PutInfo(cli, i31) 571 require.NoError(t.T(), err) 572 require.Eventually(t.T(), func() bool { 573 return len(o.Locks()) == 1 574 }, waitFor, tick) 575 require.Contains(t.T(), o.Locks(), lockID) 576 synced, remain = o.Locks()[lockID].IsSynced() 577 require.False(t.T(), synced) 578 require.Equal(t.T(), 1, remain) 579 // check ShowLocks. 580 expectedLock = []*pb.DDLLock{ 581 { 582 ID: lockID, 583 Task: task, 584 Mode: config.ShardOptimistic, 585 Owner: "", 586 DDLs: nil, 587 Synced: []string{ // for `DROP COLUMN`, un-dropped is synced (the same with the joined schema) 588 fmt.Sprintf("%s-%s", i33.Source, dbutil.TableName(i33.UpSchema, i33.UpTable)), 589 }, 590 Unsynced: []string{ // for `DROP COLUMN`, dropped is un-synced (not the same with the joined schema) 591 fmt.Sprintf("%s-%s", i31.Source, dbutil.TableName(i31.UpSchema, i31.UpTable)), 592 }, 593 }, 594 } 595 checkLocks(t.T(), o, expectedLock, "", []string{}) 596 597 // wait operation for i31 become available. 598 op31, err := watchExactOneOperation(ctx, cli, i31.Task, i31.Source, i31.UpSchema, i31.UpTable, rev1) 599 require.NoError(t.T(), err) 600 require.Equal(t.T(), []string{}, op31.DDLs) 601 require.Equal(t.T(), optimism.ConflictNone, op31.ConflictStage) 602 checkLocks(t.T(), o, expectedLock, "", []string{}) 603 604 // mark op31 as done. 605 op31c := op31 606 op31c.Done = true 607 _, putted, err = optimism.PutOperation(cli, false, op31c, 0) 608 require.NoError(t.T(), err) 609 require.True(t.T(), putted) 610 require.Eventually(t.T(), func() bool { 611 return o.Locks()[lockID].IsDone(op31c.Source, op31c.UpSchema, op31c.UpTable) 612 }, waitFor, tick) 613 checkLocks(t.T(), o, expectedLock, "", []string{}) 614 615 // PUT i33, the lock will be synced. 616 rev3, err = optimism.PutInfo(cli, i33) 617 require.NoError(t.T(), err) 618 require.Eventually(t.T(), func() bool { 619 synced, _ = o.Locks()[lockID].IsSynced() 620 return synced 621 }, waitFor, tick) 622 623 expectedLock = []*pb.DDLLock{ 624 { 625 ID: lockID, 626 Task: task, 627 Mode: config.ShardOptimistic, 628 Owner: "", 629 DDLs: nil, 630 Synced: []string{ 631 fmt.Sprintf("%s-%s", i31.Source, dbutil.TableName(i31.UpSchema, i31.UpTable)), 632 fmt.Sprintf("%s-%s", i33.Source, dbutil.TableName(i33.UpSchema, i33.UpTable)), 633 }, 634 Unsynced: []string{}, 635 }, 636 } 637 checkLocks(t.T(), o, expectedLock, "", []string{}) 638 639 // wait operation for i33 become available. 640 op33, err := watchExactOneOperation(ctx, cli, i33.Task, i33.Source, i33.UpSchema, i33.UpTable, rev3) 641 require.NoError(t.T(), err) 642 require.Equal(t.T(), DDLs3, op33.DDLs) 643 require.Equal(t.T(), optimism.ConflictNone, op33.ConflictStage) 644 checkLocks(t.T(), o, expectedLock, "", []string{}) 645 646 // mark op33 as done, the lock should be resolved. 647 op33c := op33 648 op33c.Done = true 649 _, putted, err = optimism.PutOperation(cli, false, op33c, 0) 650 require.NoError(t.T(), err) 651 require.True(t.T(), putted) 652 require.Eventually(t.T(), func() bool { 653 _, ok := o.Locks()[lockID] 654 return !ok 655 }, waitFor, tick) 656 require.Len(t.T(), o.Locks(), 0) 657 checkLocks(t.T(), o, nil, "", nil) 658 659 // CASE 6: start again after all shard DDL locks have been resolved. 660 rebuildOptimist(ctx) 661 require.Len(t.T(), o.Locks(), 0) 662 o.Close() 663 } 664 665 func (t *testOptimistSuite) TestOptimistLockConflict() { 666 var ( 667 watchTimeout = 5 * time.Second 668 logger = log.L() 669 o = NewOptimist(&logger, getDownstreamMeta) 670 task = "task-test-optimist" 671 source1 = "mysql-replica-1" 672 downSchema = "foo" 673 downTable = "bar" 674 st1 = optimism.NewSourceTables(task, source1) 675 p = parser.New() 676 se = mock.NewContext() 677 tblID int64 = 222 678 DDLs1 = []string{"ALTER TABLE bar ADD COLUMN c1 TEXT"} 679 DDLs2 = []string{"ALTER TABLE bar ADD COLUMN c1 DATETIME"} 680 ti0 = createTableInfo(t.T(), p, se, tblID, `CREATE TABLE bar (id INT PRIMARY KEY)`) 681 ti1 = createTableInfo(t.T(), p, se, tblID, `CREATE TABLE bar (id INT PRIMARY KEY, c1 TEXT)`) 682 ti2 = createTableInfo(t.T(), p, se, tblID, `CREATE TABLE bar (id INT PRIMARY KEY, c1 DATETIME)`) 683 ti3 = ti0 684 i1 = optimism.NewInfo(task, source1, "foo", "bar-1", downSchema, downTable, DDLs1, ti0, []*model.TableInfo{ti1}) 685 i2 = optimism.NewInfo(task, source1, "foo", "bar-2", downSchema, downTable, DDLs2, ti0, []*model.TableInfo{ti2}) 686 i3 = optimism.NewInfo(task, source1, "foo", "bar-2", downSchema, downTable, DDLs1, ti0, []*model.TableInfo{ti3}) 687 ) 688 689 st1.AddTable("foo", "bar-1", downSchema, downTable) 690 st1.AddTable("foo", "bar-2", downSchema, downTable) 691 692 // put source tables first. 693 _, err := optimism.PutSourceTables(t.etcdTestCli, st1) 694 require.NoError(t.T(), err) 695 696 ctx, cancel := context.WithCancel(context.Background()) 697 defer func() { 698 cancel() 699 o.Close() 700 }() 701 702 require.NoError(t.T(), o.Start(ctx, t.etcdTestCli)) 703 require.Len(t.T(), o.Locks(), 0) 704 705 // PUT i1, will create a lock but not synced. 706 rev1, err := optimism.PutInfo(t.etcdTestCli, i1) 707 require.NoError(t.T(), err) 708 // wait operation for i1 become available. 709 opCh := make(chan optimism.Operation, 10) 710 errCh := make(chan error, 10) 711 ctx2, cancel2 := context.WithCancel(ctx) 712 go optimism.WatchOperationPut(ctx2, t.etcdTestCli, i1.Task, i1.Source, i1.UpSchema, i1.UpTable, rev1, opCh, errCh) 713 select { 714 case <-time.After(watchTimeout): 715 t.T().Fatal("timeout") 716 case op1 := <-opCh: 717 require.Equal(t.T(), DDLs1, op1.DDLs) 718 require.Equal(t.T(), optimism.ConflictNone, op1.ConflictStage) 719 } 720 721 cancel2() 722 close(opCh) 723 close(errCh) 724 require.Equal(t.T(), 0, len(opCh)) 725 require.Equal(t.T(), 0, len(errCh)) 726 727 // PUT i2, conflict will be detected. 728 rev2, err := optimism.PutInfo(t.etcdTestCli, i2) 729 require.NoError(t.T(), err) 730 // wait operation for i2 become available. 731 opCh = make(chan optimism.Operation, 10) 732 errCh = make(chan error, 10) 733 734 ctx2, cancel2 = context.WithCancel(ctx) 735 go optimism.WatchOperationPut(ctx2, t.etcdTestCli, i2.Task, i2.Source, i2.UpSchema, i2.UpTable, rev2, opCh, errCh) 736 select { 737 case <-time.After(watchTimeout): 738 t.T().Fatal("timeout") 739 case op2 := <-opCh: 740 require.Equal(t.T(), []string{}, op2.DDLs) 741 require.Equal(t.T(), optimism.ConflictDetected, op2.ConflictStage) 742 } 743 744 cancel2() 745 close(opCh) 746 close(errCh) 747 require.Equal(t.T(), 0, len(opCh)) 748 require.Equal(t.T(), 0, len(errCh)) 749 750 // PUT i3, no conflict now. 751 // case for handle-error replace 752 rev3, err := optimism.PutInfo(t.etcdTestCli, i3) 753 require.NoError(t.T(), err) 754 // wait operation for i3 become available. 755 opCh = make(chan optimism.Operation, 10) 756 errCh = make(chan error, 10) 757 ctx2, cancel2 = context.WithCancel(ctx) 758 go optimism.WatchOperationPut(ctx2, t.etcdTestCli, i3.Task, i3.Source, i3.UpSchema, i3.UpTable, rev3, opCh, errCh) 759 select { 760 case <-time.After(watchTimeout): 761 t.T().Fatal("timeout") 762 case op3 := <-opCh: 763 require.Equal(t.T(), DDLs1, op3.DDLs) 764 require.Equal(t.T(), optimism.ConflictNone, op3.ConflictStage) 765 } 766 cancel2() 767 close(opCh) 768 close(errCh) 769 require.Equal(t.T(), 0, len(opCh)) 770 require.Equal(t.T(), 0, len(errCh)) 771 } 772 773 func (t *testOptimistSuite) TestOptimistLockMultipleTarget() { 774 var ( 775 tick = 100 * time.Millisecond 776 waitFor = 30 * tick 777 watchTimeout = 5 * time.Second 778 logger = log.L() 779 o = NewOptimist(&logger, getDownstreamMeta) 780 task = "test-optimist-lock-multiple-target" 781 source = "mysql-replica-1" 782 upSchema = "foo" 783 upTables = []string{"bar-1", "bar-2", "bar-3", "bar-4"} 784 downSchema = "foo" 785 downTable1 = "bar" 786 downTable2 = "rab" 787 lockID1 = fmt.Sprintf("%s-`%s`.`%s`", task, downSchema, downTable1) 788 lockID2 = fmt.Sprintf("%s-`%s`.`%s`", task, downSchema, downTable2) 789 sts = optimism.NewSourceTables(task, source) 790 p = parser.New() 791 se = mock.NewContext() 792 tblID int64 = 111 793 DDLs = []string{"ALTER TABLE bar ADD COLUMN c1 TEXT"} 794 ti0 = createTableInfo(t.T(), p, se, tblID, `CREATE TABLE bar (id INT PRIMARY KEY)`) 795 ti1 = createTableInfo(t.T(), p, se, tblID, `CREATE TABLE bar (id INT PRIMARY KEY, c1 TEXT)`) 796 i11 = optimism.NewInfo(task, source, upSchema, upTables[0], downSchema, downTable1, DDLs, ti0, []*model.TableInfo{ti1}) 797 i12 = optimism.NewInfo(task, source, upSchema, upTables[1], downSchema, downTable1, DDLs, ti0, []*model.TableInfo{ti1}) 798 i21 = optimism.NewInfo(task, source, upSchema, upTables[2], downSchema, downTable2, DDLs, ti0, []*model.TableInfo{ti1}) 799 i22 = optimism.NewInfo(task, source, upSchema, upTables[3], downSchema, downTable2, DDLs, ti0, []*model.TableInfo{ti1}) 800 ) 801 802 sts.AddTable(upSchema, upTables[0], downSchema, downTable1) 803 sts.AddTable(upSchema, upTables[1], downSchema, downTable1) 804 sts.AddTable(upSchema, upTables[2], downSchema, downTable2) 805 sts.AddTable(upSchema, upTables[3], downSchema, downTable2) 806 807 // put source tables first. 808 _, err := optimism.PutSourceTables(t.etcdTestCli, sts) 809 require.NoError(t.T(), err) 810 811 ctx, cancel := context.WithCancel(context.Background()) 812 defer func() { 813 cancel() 814 o.Close() 815 }() 816 817 require.NoError(t.T(), o.Start(ctx, t.etcdTestCli)) 818 require.Len(t.T(), o.Locks(), 0) 819 820 // PUT i11 and i21, will create two locks but no synced. 821 _, err = optimism.PutInfo(t.etcdTestCli, i11) 822 require.NoError(t.T(), err) 823 _, err = optimism.PutInfo(t.etcdTestCli, i21) 824 require.NoError(t.T(), err) 825 require.Eventually(t.T(), func() bool { 826 return len(o.Locks()) == 2 827 }, waitFor, tick) 828 require.Contains(t.T(), o.Locks(), lockID1) 829 require.Contains(t.T(), o.Locks(), lockID2) 830 831 // check ShowLocks 832 expectedLock := map[string]*pb.DDLLock{ 833 lockID1: { 834 ID: lockID1, 835 Task: task, 836 Mode: config.ShardOptimistic, 837 Owner: "", 838 DDLs: nil, 839 Synced: []string{ 840 fmt.Sprintf("%s-%s", i11.Source, dbutil.TableName(i11.UpSchema, i11.UpTable)), 841 }, 842 Unsynced: []string{ 843 fmt.Sprintf("%s-%s", i12.Source, dbutil.TableName(i12.UpSchema, i12.UpTable)), 844 }, 845 }, 846 lockID2: { 847 ID: lockID2, 848 Task: task, 849 Mode: config.ShardOptimistic, 850 Owner: "", 851 DDLs: nil, 852 Synced: []string{ 853 fmt.Sprintf("%s-%s", i21.Source, dbutil.TableName(i21.UpSchema, i21.UpTable)), 854 }, 855 Unsynced: []string{ 856 fmt.Sprintf("%s-%s", i22.Source, dbutil.TableName(i22.UpSchema, i22.UpTable)), 857 }, 858 }, 859 } 860 checkLocksByMap(t.T(), o, expectedLock, []string{}, lockID1, lockID2) 861 862 // put i12 and i22, both of locks will be synced. 863 rev1, err := optimism.PutInfo(t.etcdTestCli, i12) 864 require.NoError(t.T(), err) 865 rev2, err := optimism.PutInfo(t.etcdTestCli, i22) 866 require.NoError(t.T(), err) 867 require.Eventually(t.T(), func() bool { 868 synced1, _ := o.Locks()[lockID1].IsSynced() 869 synced2, _ := o.Locks()[lockID2].IsSynced() 870 return synced1 && synced2 871 }, waitFor, tick) 872 873 expectedLock[lockID1].Synced = []string{ 874 fmt.Sprintf("%s-%s", i11.Source, dbutil.TableName(i11.UpSchema, i11.UpTable)), 875 fmt.Sprintf("%s-%s", i12.Source, dbutil.TableName(i12.UpSchema, i12.UpTable)), 876 } 877 expectedLock[lockID1].Unsynced = []string{} 878 expectedLock[lockID2].Synced = []string{ 879 fmt.Sprintf("%s-%s", i21.Source, dbutil.TableName(i21.UpSchema, i21.UpTable)), 880 fmt.Sprintf("%s-%s", i22.Source, dbutil.TableName(i22.UpSchema, i22.UpTable)), 881 } 882 expectedLock[lockID2].Unsynced = []string{} 883 checkLocksByMap(t.T(), o, expectedLock, []string{}, lockID1, lockID2) 884 885 // wait operation for i12 become available. 886 opCh := make(chan optimism.Operation, 10) 887 errCh := make(chan error, 10) 888 var op12 optimism.Operation 889 ctx2, cancel2 := context.WithCancel(ctx) 890 go optimism.WatchOperationPut(ctx2, t.etcdTestCli, i12.Task, i12.Source, i12.UpSchema, i12.UpTable, rev1, opCh, errCh) 891 select { 892 case <-time.After(watchTimeout): 893 t.T().Fatal("timeout") 894 case op12 = <-opCh: 895 require.Equal(t.T(), DDLs, op12.DDLs) 896 require.Equal(t.T(), optimism.ConflictNone, op12.ConflictStage) 897 } 898 cancel2() 899 close(opCh) 900 close(errCh) 901 require.Equal(t.T(), 0, len(opCh)) 902 require.Equal(t.T(), 0, len(errCh)) 903 904 // mark op11 and op12 as done, the lock should be resolved. 905 op11c := op12 906 op11c.Done = true 907 op11c.UpTable = i11.UpTable // overwrite `UpTable`. 908 _, putted, err := optimism.PutOperation(t.etcdTestCli, false, op11c, 0) 909 require.NoError(t.T(), err) 910 require.True(t.T(), putted) 911 op12c := op12 912 op12c.Done = true 913 _, putted, err = optimism.PutOperation(t.etcdTestCli, false, op12c, 0) 914 require.NoError(t.T(), err) 915 require.True(t.T(), putted) 916 require.Eventually(t.T(), func() bool { 917 _, ok := o.Locks()[lockID1] 918 return !ok 919 }, waitFor, tick) 920 require.Len(t.T(), o.Locks(), 1) 921 checkLocksByMap(t.T(), o, expectedLock, nil, lockID2) 922 923 // wait operation for i22 become available. 924 opCh = make(chan optimism.Operation, 10) 925 errCh = make(chan error, 10) 926 var op22 optimism.Operation 927 ctx2, cancel2 = context.WithCancel(ctx) 928 go optimism.WatchOperationPut(ctx2, t.etcdTestCli, i22.Task, i22.Source, i22.UpSchema, i22.UpTable, rev2, opCh, errCh) 929 select { 930 case <-time.After(watchTimeout): 931 t.T().Fatal("timeout") 932 case op22 = <-opCh: 933 require.Equal(t.T(), DDLs, op22.DDLs) 934 require.Equal(t.T(), optimism.ConflictNone, op22.ConflictStage) 935 } 936 cancel2() 937 close(opCh) 938 close(errCh) 939 require.Equal(t.T(), 0, len(opCh)) 940 require.Equal(t.T(), 0, len(errCh)) 941 942 // mark op21 and op22 as done, the lock should be resolved. 943 op21c := op22 944 op21c.Done = true 945 op21c.UpTable = i21.UpTable // overwrite `UpTable`. 946 _, putted, err = optimism.PutOperation(t.etcdTestCli, false, op21c, 0) 947 require.NoError(t.T(), err) 948 require.True(t.T(), putted) 949 op22c := op22 950 op22c.Done = true 951 _, putted, err = optimism.PutOperation(t.etcdTestCli, false, op22c, 0) 952 require.NoError(t.T(), err) 953 require.True(t.T(), putted) 954 require.Eventually(t.T(), func() bool { 955 _, ok := o.Locks()[lockID2] 956 return !ok 957 }, waitFor, tick) 958 require.Len(t.T(), o.Locks(), 0) 959 checkLocksByMap(t.T(), o, expectedLock, nil) 960 } 961 962 func (t *testOptimistSuite) TestOptimistInitSchema() { 963 var ( 964 tick = 100 * time.Millisecond 965 waitFor = 30 * tick 966 watchTimeout = 5 * time.Second 967 logger = log.L() 968 o = NewOptimist(&logger, getDownstreamMeta) 969 task = "test-optimist-init-schema" 970 source = "mysql-replica-1" 971 upSchema = "foo" 972 upTables = []string{"bar-1", "bar-2"} 973 downSchema = "foo" 974 downTable = "bar" 975 st = optimism.NewSourceTables(task, source) 976 977 p = parser.New() 978 se = mock.NewContext() 979 tblID int64 = 111 980 DDLs1 = []string{"ALTER TABLE bar ADD COLUMN c1 TEXT"} 981 DDLs2 = []string{"ALTER TABLE bar ADD COLUMN c2 INT"} 982 ti0 = createTableInfo(t.T(), p, se, tblID, `CREATE TABLE bar (id INT PRIMARY KEY)`) 983 ti1 = createTableInfo(t.T(), p, se, tblID, `CREATE TABLE bar (id INT PRIMARY KEY, c1 TEXT)`) 984 ti2 = createTableInfo(t.T(), p, se, tblID, `CREATE TABLE bar (id INT PRIMARY KEY, c1 TEXT, c2 INT)`) 985 i11 = optimism.NewInfo(task, source, upSchema, upTables[0], downSchema, downTable, DDLs1, ti0, []*model.TableInfo{ti1}) 986 i12 = optimism.NewInfo(task, source, upSchema, upTables[1], downSchema, downTable, DDLs1, ti0, []*model.TableInfo{ti1}) 987 i21 = optimism.NewInfo(task, source, upSchema, upTables[0], downSchema, downTable, DDLs2, ti1, []*model.TableInfo{ti2}) 988 ) 989 990 st.AddTable(upSchema, upTables[0], downSchema, downTable) 991 st.AddTable(upSchema, upTables[1], downSchema, downTable) 992 993 // put source tables first. 994 _, err := optimism.PutSourceTables(t.etcdTestCli, st) 995 require.NoError(t.T(), err) 996 997 ctx, cancel := context.WithCancel(context.Background()) 998 defer func() { 999 cancel() 1000 o.Close() 1001 }() 1002 1003 require.NoError(t.T(), o.Start(ctx, t.etcdTestCli)) 1004 require.Len(t.T(), o.Locks(), 0) 1005 1006 // PUT i11, will creat a lock. 1007 _, err = optimism.PutInfo(t.etcdTestCli, i11) 1008 require.NoError(t.T(), err) 1009 require.Eventually(t.T(), func() bool { 1010 return len(o.Locks()) == 1 1011 }, waitFor, tick) 1012 time.Sleep(tick) // sleep one more time to wait for update of init schema. 1013 1014 // PUT i12, the lock will be synced. 1015 rev1, err := optimism.PutInfo(t.etcdTestCli, i12) 1016 require.NoError(t.T(), err) 1017 1018 // wait operation for i12 become available. 1019 opCh := make(chan optimism.Operation, 10) 1020 errCh := make(chan error, 10) 1021 var op12 optimism.Operation 1022 ctx2, cancel2 := context.WithCancel(ctx) 1023 go optimism.WatchOperationPut(ctx2, t.etcdTestCli, i12.Task, i12.Source, i12.UpSchema, i12.UpTable, rev1, opCh, errCh) 1024 select { 1025 case <-time.After(watchTimeout): 1026 t.T().Fatal("timeout") 1027 case op12 = <-opCh: 1028 require.Equal(t.T(), DDLs1, op12.DDLs) 1029 require.Equal(t.T(), optimism.ConflictNone, op12.ConflictStage) 1030 } 1031 cancel2() 1032 close(opCh) 1033 close(errCh) 1034 require.Equal(t.T(), 0, len(opCh)) 1035 require.Equal(t.T(), 0, len(errCh)) 1036 1037 // mark op11 and op12 as done, the lock should be resolved. 1038 op11c := op12 1039 op11c.Done = true 1040 op11c.UpTable = i11.UpTable // overwrite `UpTable`. 1041 _, putted, err := optimism.PutOperation(t.etcdTestCli, false, op11c, 0) 1042 require.NoError(t.T(), err) 1043 require.True(t.T(), putted) 1044 op12c := op12 1045 op12c.Done = true 1046 _, putted, err = optimism.PutOperation(t.etcdTestCli, false, op12c, 0) 1047 require.NoError(t.T(), err) 1048 require.True(t.T(), putted) 1049 require.Eventually(t.T(), func() bool { 1050 return len(o.Locks()) == 0 1051 }, waitFor, tick) 1052 1053 // PUT i21 to create the lock again. 1054 _, err = optimism.PutInfo(t.etcdTestCli, i21) 1055 require.NoError(t.T(), err) 1056 require.Eventually(t.T(), func() bool { 1057 return len(o.Locks()) == 1 1058 }, waitFor, tick) 1059 time.Sleep(tick) // sleep one more time to wait for update of init schema. 1060 } 1061 1062 func (t *testOptimistSuite) testSortInfos(cli *clientv3.Client) { 1063 defer func() { 1064 require.NoError(t.T(), optimism.ClearTestInfoOperationColumn(cli)) 1065 }() 1066 1067 var ( 1068 task = "test-optimist-init-schema" 1069 sources = []string{"mysql-replica-1", "mysql-replica-2"} 1070 upSchema = "foo" 1071 upTables = []string{"bar-1", "bar-2"} 1072 downSchema = "foo" 1073 downTable = "bar" 1074 1075 p = parser.New() 1076 se = mock.NewContext() 1077 tblID int64 = 111 1078 DDLs1 = []string{"ALTER TABLE bar ADD COLUMN c1 TEXT"} 1079 DDLs2 = []string{"ALTER TABLE bar ADD COLUMN c2 INT"} 1080 ti0 = createTableInfo(t.T(), p, se, tblID, `CREATE TABLE bar (id INT PRIMARY KEY)`) 1081 ti1 = createTableInfo(t.T(), p, se, tblID, `CREATE TABLE bar (id INT PRIMARY KEY, c1 TEXT)`) 1082 ti2 = createTableInfo(t.T(), p, se, tblID, `CREATE TABLE bar (id INT PRIMARY KEY, c1 TEXT, c2 INT)`) 1083 i11 = optimism.NewInfo(task, sources[0], upSchema, upTables[0], downSchema, downTable, DDLs1, ti0, []*model.TableInfo{ti1}) 1084 i12 = optimism.NewInfo(task, sources[0], upSchema, upTables[1], downSchema, downTable, DDLs1, ti0, []*model.TableInfo{ti1}) 1085 i21 = optimism.NewInfo(task, sources[1], upSchema, upTables[1], downSchema, downTable, DDLs2, ti1, []*model.TableInfo{ti2}) 1086 ) 1087 1088 rev1, err := optimism.PutInfo(cli, i11) 1089 require.NoError(t.T(), err) 1090 ifm, _, err := optimism.GetAllInfo(cli) 1091 require.NoError(t.T(), err) 1092 infos := sortInfos(ifm) 1093 require.Equal(t.T(), 1, len(infos)) 1094 i11.Version = 1 1095 i11.Revision = rev1 1096 require.Equal(t.T(), i11, infos[0]) 1097 1098 rev2, err := optimism.PutInfo(cli, i12) 1099 require.NoError(t.T(), err) 1100 ifm, _, err = optimism.GetAllInfo(cli) 1101 require.NoError(t.T(), err) 1102 infos = sortInfos(ifm) 1103 require.Equal(t.T(), 2, len(infos)) 1104 i11.Version = 1 1105 i11.Revision = rev1 1106 i12.Version = 1 1107 i12.Revision = rev2 1108 require.Equal(t.T(), i11, infos[0]) 1109 require.Equal(t.T(), i12, infos[1]) 1110 1111 rev3, err := optimism.PutInfo(cli, i21) 1112 require.NoError(t.T(), err) 1113 rev4, err := optimism.PutInfo(cli, i11) 1114 require.NoError(t.T(), err) 1115 ifm, _, err = optimism.GetAllInfo(cli) 1116 require.NoError(t.T(), err) 1117 infos = sortInfos(ifm) 1118 require.Equal(t.T(), 3, len(infos)) 1119 1120 i11.Version = 2 1121 i11.Revision = rev4 1122 i12.Version = 1 1123 i12.Revision = rev2 1124 i21.Version = 1 1125 i21.Revision = rev3 1126 require.Equal(t.T(), i12, infos[0]) 1127 require.Equal(t.T(), i21, infos[1]) 1128 require.Equal(t.T(), i11, infos[2]) 1129 } 1130 1131 func (t *testOptimistSuite) TestBuildLockJoinedAndTable() { 1132 var ( 1133 logger = log.L() 1134 o = NewOptimist(&logger, getDownstreamMeta) 1135 task = "task-build-lock-joined-and-table" 1136 source1 = "mysql-replica-1" 1137 source2 = "mysql-replica-2" 1138 downSchema = "db" 1139 downTable = "tbl" 1140 st1 = optimism.NewSourceTables(task, source1) 1141 st2 = optimism.NewSourceTables(task, source2) 1142 DDLs1 = []string{"ALTER TABLE bar ADD COLUMN c1 INT"} 1143 DDLs2 = []string{"ALTER TABLE bar DROP COLUMN c1"} 1144 p = parser.New() 1145 se = mock.NewContext() 1146 tblID int64 = 111 1147 ti0 = createTableInfo(t.T(), p, se, tblID, `CREATE TABLE bar (id INT PRIMARY KEY)`) 1148 ti1 = createTableInfo(t.T(), p, se, tblID, `CREATE TABLE bar (id INT PRIMARY KEY, c1 INT)`) 1149 ti2 = createTableInfo(t.T(), p, se, tblID, `CREATE TABLE bar (id INT PRIMARY KEY, c1 INT, c2 INT)`) 1150 ti3 = createTableInfo(t.T(), p, se, tblID, `CREATE TABLE bar (id INT PRIMARY KEY, c2 INT)`) 1151 1152 i11 = optimism.NewInfo(task, source1, "foo", "bar-1", downSchema, downTable, DDLs1, ti0, []*model.TableInfo{ti1}) 1153 i21 = optimism.NewInfo(task, source2, "foo", "bar-1", downSchema, downTable, DDLs2, ti2, []*model.TableInfo{ti3}) 1154 ) 1155 1156 ctx, cancel := context.WithCancel(context.Background()) 1157 defer func() { 1158 cancel() 1159 o.Close() 1160 }() 1161 1162 st1.AddTable("foo", "bar-1", downSchema, downTable) 1163 st2.AddTable("foo", "bar-1", downSchema, downTable) 1164 1165 require.NoError(t.T(), o.Start(ctx, t.etcdTestCli)) 1166 _, err := optimism.PutSourceTables(t.etcdTestCli, st1) 1167 require.NoError(t.T(), err) 1168 _, err = optimism.PutSourceTables(t.etcdTestCli, st2) 1169 require.NoError(t.T(), err) 1170 1171 _, err = optimism.PutInfo(t.etcdTestCli, i21) 1172 require.NoError(t.T(), err) 1173 _, err = optimism.PutInfo(t.etcdTestCli, i11) 1174 require.NoError(t.T(), err) 1175 1176 stm, _, err := optimism.GetAllSourceTables(t.etcdTestCli) 1177 require.NoError(t.T(), err) 1178 o.tk.Init(stm) 1179 } 1180 1181 func (t *testOptimistSuite) TestBuildLockWithInitSchema() { 1182 var ( 1183 logger = log.L() 1184 o = NewOptimist(&logger, getDownstreamMeta) 1185 task = "task-lock-with-init-schema" 1186 source1 = "mysql-replica-1" 1187 source2 = "mysql-replica-2" 1188 downSchema = "db" 1189 downTable = "tbl" 1190 st1 = optimism.NewSourceTables(task, source1) 1191 st2 = optimism.NewSourceTables(task, source2) 1192 p = parser.New() 1193 se = mock.NewContext() 1194 tblID = int64(111) 1195 1196 ti0 = createTableInfo(t.T(), p, se, tblID, `CREATE TABLE bar (a INT PRIMARY KEY, b INT, c INT)`) 1197 ti1 = createTableInfo(t.T(), p, se, tblID, `CREATE TABLE bar (a INT PRIMARY KEY, b INT)`) 1198 ti2 = createTableInfo(t.T(), p, se, tblID, `CREATE TABLE bar (a INT PRIMARY KEY)`) 1199 1200 ddlDropB = "ALTER TABLE bar DROP COLUMN b" 1201 ddlDropC = "ALTER TABLE bar DROP COLUMN c" 1202 infoDropB = optimism.NewInfo(task, source1, "foo", "bar-1", downSchema, downTable, []string{ddlDropC}, ti0, []*model.TableInfo{ti1}) 1203 infoDropC = optimism.NewInfo(task, source1, "foo", "bar-1", downSchema, downTable, []string{ddlDropB}, ti1, []*model.TableInfo{ti2}) 1204 ) 1205 1206 ctx, cancel := context.WithCancel(context.Background()) 1207 defer func() { 1208 cancel() 1209 o.Close() 1210 }() 1211 1212 st1.AddTable("foo", "bar-1", downSchema, downTable) 1213 st2.AddTable("foo", "bar-1", downSchema, downTable) 1214 1215 require.NoError(t.T(), o.Start(ctx, t.etcdTestCli)) 1216 _, err := optimism.PutSourceTables(t.etcdTestCli, st1) 1217 require.NoError(t.T(), err) 1218 _, err = optimism.PutSourceTables(t.etcdTestCli, st2) 1219 require.NoError(t.T(), err) 1220 1221 _, err = optimism.PutInfo(t.etcdTestCli, infoDropB) 1222 require.NoError(t.T(), err) 1223 _, err = optimism.PutInfo(t.etcdTestCli, infoDropC) 1224 require.NoError(t.T(), err) 1225 1226 stm, _, err := optimism.GetAllSourceTables(t.etcdTestCli) 1227 require.NoError(t.T(), err) 1228 o.tk.Init(stm) 1229 } 1230 1231 func getDownstreamMeta(string) (*dbconfig.DBConfig, string) { 1232 return nil, "" 1233 }