github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/dm/pkg/shardddl/optimism/info_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 optimism 15 16 import ( 17 "context" 18 "encoding/json" 19 "sync" 20 "testing" 21 "time" 22 23 . "github.com/pingcap/check" 24 tiddl "github.com/pingcap/tidb/pkg/ddl" 25 "github.com/pingcap/tidb/pkg/parser" 26 "github.com/pingcap/tidb/pkg/parser/ast" 27 "github.com/pingcap/tidb/pkg/parser/model" 28 "github.com/pingcap/tidb/pkg/sessionctx" 29 "github.com/pingcap/tidb/pkg/util/mock" 30 "github.com/pingcap/tiflow/dm/common" 31 "github.com/pingcap/tiflow/dm/pkg/etcdutil" 32 clientv3 "go.etcd.io/etcd/client/v3" 33 "go.etcd.io/etcd/tests/v3/integration" 34 ) 35 36 var etcdTestCli *clientv3.Client 37 38 func TestInfo(t *testing.T) { 39 integration.BeforeTestExternal(t) 40 mockCluster := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 1}) 41 defer mockCluster.Terminate(t) 42 43 etcdTestCli = mockCluster.RandClient() 44 45 TestingT(t) 46 } 47 48 // clear keys in etcd test cluster. 49 func clearTestInfoOperation(c *C) { 50 c.Assert(ClearTestInfoOperationColumn(etcdTestCli), IsNil) 51 } 52 53 func createTableInfo(c *C, p *parser.Parser, se sessionctx.Context, tableID int64, sql string) *model.TableInfo { 54 node, err := p.ParseOneStmt(sql, "utf8mb4", "utf8mb4_bin") 55 if err != nil { 56 c.Fatalf("fail to parse stmt, %v", err) 57 } 58 createStmtNode, ok := node.(*ast.CreateTableStmt) 59 if !ok { 60 c.Fatalf("%s is not a CREATE TABLE statement", sql) 61 } 62 info, err := tiddl.MockTableInfo(se, createStmtNode, tableID) 63 if err != nil { 64 c.Fatalf("fail to create table info, %v", err) 65 } 66 return info 67 } 68 69 type testForEtcd struct{} 70 71 var _ = Suite(&testForEtcd{}) 72 73 func (t *testForEtcd) TestInfoJSON(c *C) { 74 i1 := NewInfo("test", "mysql-replica-1", 75 "db-1", "tbl-1", "db", "tbl", []string{ 76 "ALTER TABLE tbl ADD COLUMN c1 INT", 77 "ALTER TABLE tbl ADD COLUMN c2 INT", 78 }, nil, nil) 79 80 j, err := i1.toJSON() 81 c.Assert(err, IsNil) 82 c.Assert(j, Equals, `{"task":"test","source":"mysql-replica-1","up-schema":"db-1","up-table":"tbl-1","down-schema":"db","down-table":"tbl","ddls":["ALTER TABLE tbl ADD COLUMN c1 INT","ALTER TABLE tbl ADD COLUMN c2 INT"],"table-info-before":null,"table-info-after":null,"ignore-conflict":false}`) 83 c.Assert(j, Equals, i1.String()) 84 j = i1.ShortString() 85 c.Assert(j, Equals, `{"task":"test","source":"mysql-replica-1","up-schema":"db-1","up-table":"tbl-1","down-schema":"db","down-table":"tbl","ddls":["ALTER TABLE tbl ADD COLUMN c1 INT","ALTER TABLE tbl ADD COLUMN c2 INT"],"table-before":"","table-after":"","is-deleted":false,"version":0,"revision":0,"ignore-conflict":false}`) 86 87 i2, err := infoFromJSON(j) 88 c.Assert(err, IsNil) 89 c.Assert(i2, DeepEquals, i1) 90 } 91 92 func (t *testForEtcd) TestEtcdInfoUpgrade(c *C) { 93 defer clearTestInfoOperation(c) 94 95 var ( 96 source1 = "mysql-replica-1" 97 source2 = "mysql-replica-2" 98 task1 = "task-1" 99 task2 = "task-2" 100 upSchema = "foo_1" 101 upTable = "bar_1" 102 downSchema = "foo" 103 downTable = "bar" 104 p = parser.New() 105 se = mock.NewContext() 106 tblID int64 = 222 107 tblI1 = createTableInfo(c, p, se, tblID, `CREATE TABLE bar (id INT PRIMARY KEY)`) 108 tblI2 = createTableInfo(c, p, se, tblID, `CREATE TABLE bar (id INT PRIMARY KEY, c1 INT)`) 109 tblI3 = createTableInfo(c, p, se, tblID, `CREATE TABLE bar (id INT PRIMARY KEY, c1 INT, c2 INT)`) 110 tblI4 = createTableInfo(c, p, se, tblID, `CREATE TABLE bar (id INT PRIMARY KEY, c1 INT, c2 INT, c3 INT)`) 111 i11 = NewInfo(task1, source1, upSchema, upTable, downSchema, downTable, []string{"ALTER TABLE bar ADD COLUMN c1 INT"}, tblI1, []*model.TableInfo{tblI2}) 112 i12 = NewInfo(task1, source2, upSchema, upTable, downSchema, downTable, []string{"ALTER TABLE bar ADD COLUMN c2 INT"}, tblI2, []*model.TableInfo{tblI3}) 113 i21 = NewInfo(task2, source1, upSchema, upTable, downSchema, downTable, []string{"ALTER TABLE bar ADD COLUMN c3 INT"}, tblI3, []*model.TableInfo{tblI4}) 114 oi11 = newOldInfo(task1, source1, upSchema, upTable, downSchema, downTable, []string{"ALTER TABLE bar ADD COLUMN c1 INT"}, tblI1, tblI2) 115 oi12 = newOldInfo(task1, source2, upSchema, upTable, downSchema, downTable, []string{"ALTER TABLE bar ADD COLUMN c2 INT"}, tblI2, tblI3) 116 oi21 = newOldInfo(task2, source1, upSchema, upTable, downSchema, downTable, []string{"ALTER TABLE bar ADD COLUMN c3 INT"}, tblI3, tblI4) 117 ) 118 119 // put the oldInfo 120 rev1, err := putOldInfo(etcdTestCli, oi11) 121 c.Assert(err, IsNil) 122 rev2, err := putOldInfo(etcdTestCli, oi11) 123 c.Assert(err, IsNil) 124 c.Assert(rev2, Greater, rev1) 125 126 // put another key and get again with 2 info. 127 rev3, err := putOldInfo(etcdTestCli, oi12) 128 c.Assert(err, IsNil) 129 c.Assert(rev3, Greater, rev2) 130 131 // get all infos. 132 ifm, rev4, err := GetAllInfo(etcdTestCli) 133 c.Assert(err, IsNil) 134 c.Assert(rev4, Equals, rev3) 135 c.Assert(ifm, HasLen, 1) 136 c.Assert(ifm, HasKey, task1) 137 c.Assert(ifm[task1], HasLen, 2) 138 c.Assert(ifm[task1][source1], HasLen, 1) 139 c.Assert(ifm[task1][source1][upSchema], HasLen, 1) 140 c.Assert(ifm[task1][source2], HasLen, 1) 141 c.Assert(ifm[task1][source2][upSchema], HasLen, 1) 142 143 i11WithVer := i11 144 i11WithVer.Version = 2 145 i11WithVer.Revision = rev2 146 i12WithVer := i12 147 i12WithVer.Version = 1 148 i12WithVer.Revision = rev4 149 c.Assert(ifm[task1][source1][upSchema][upTable], DeepEquals, i11WithVer) 150 c.Assert(ifm[task1][source2][upSchema][upTable], DeepEquals, i12WithVer) 151 152 // start the watcher. 153 wch := make(chan Info, 10) 154 ech := make(chan error, 10) 155 var wg sync.WaitGroup 156 wg.Add(1) 157 watchCtx, watchCancel := context.WithCancel(context.Background()) 158 defer watchCancel() 159 go func() { 160 defer wg.Done() 161 WatchInfo(watchCtx, etcdTestCli, rev4+1, wch, ech) // revision+1 162 }() 163 164 // put another oldInfo for a different task. 165 // version start from 1 166 // simulate v2.0.1 worker and v2.0.2 master 167 rev5, err := putOldInfo(etcdTestCli, oi21) 168 c.Assert(err, IsNil) 169 infoWithVer := <-wch 170 i21WithVer := i21 171 i21WithVer.Version = 1 172 i21WithVer.Revision = rev5 173 c.Assert(infoWithVer, DeepEquals, i21WithVer) 174 c.Assert(len(ech), Equals, 0) 175 } 176 177 func (t *testForEtcd) TestInfoEtcd(c *C) { 178 defer clearTestInfoOperation(c) 179 180 var ( 181 watchTimeout = 2 * time.Second 182 source1 = "mysql-replica-1" 183 source2 = "mysql-replica-2" 184 task1 = "task-1" 185 task2 = "task-2" 186 upSchema = "foo_1" 187 upTable = "bar_1" 188 downSchema = "foo" 189 downTable = "bar" 190 p = parser.New() 191 se = mock.NewContext() 192 tblID int64 = 222 193 tblI1 = createTableInfo(c, p, se, tblID, `CREATE TABLE bar (id INT PRIMARY KEY)`) 194 tblI2 = createTableInfo(c, p, se, tblID, `CREATE TABLE bar (id INT PRIMARY KEY, c1 INT)`) 195 tblI3 = createTableInfo(c, p, se, tblID, `CREATE TABLE bar (id INT PRIMARY KEY, c1 INT, c2 INT)`) 196 tblI4 = createTableInfo(c, p, se, tblID, `CREATE TABLE bar (id INT PRIMARY KEY, c1 INT, c2 INT, c3 INT)`) 197 i11 = NewInfo(task1, source1, upSchema, upTable, downSchema, downTable, []string{"ALTER TABLE bar ADD COLUMN c1 INT"}, tblI1, []*model.TableInfo{tblI2}) 198 i12 = NewInfo(task1, source2, upSchema, upTable, downSchema, downTable, []string{"ALTER TABLE bar ADD COLUMN c2 INT"}, tblI2, []*model.TableInfo{tblI3}) 199 i21 = NewInfo(task2, source1, upSchema, upTable, downSchema, downTable, []string{"ALTER TABLE bar ADD COLUMN c3 INT"}, tblI3, []*model.TableInfo{tblI4}) 200 ) 201 202 // put the same key twice. 203 rev1, err := PutInfo(etcdTestCli, i11) 204 c.Assert(err, IsNil) 205 rev2, err := PutInfo(etcdTestCli, i11) 206 c.Assert(err, IsNil) 207 c.Assert(rev2, Greater, rev1) 208 209 // get with only 1 info. 210 ifm, rev3, err := GetAllInfo(etcdTestCli) 211 c.Assert(err, IsNil) 212 c.Assert(rev3, Equals, rev2) 213 c.Assert(ifm, HasLen, 1) 214 c.Assert(ifm, HasKey, task1) 215 c.Assert(ifm[task1], HasLen, 1) 216 c.Assert(ifm[task1][source1], HasLen, 1) 217 c.Assert(ifm[task1][source1][upSchema], HasLen, 1) 218 i11WithVer := i11 219 i11WithVer.Version = 2 220 i11WithVer.Revision = rev2 221 c.Assert(ifm[task1][source1][upSchema][upTable], DeepEquals, i11WithVer) 222 223 // put another key and get again with 2 info. 224 rev4, err := PutInfo(etcdTestCli, i12) 225 c.Assert(err, IsNil) 226 ifm, _, err = GetAllInfo(etcdTestCli) 227 c.Assert(err, IsNil) 228 c.Assert(ifm, HasLen, 1) 229 c.Assert(ifm, HasKey, task1) 230 c.Assert(ifm[task1], HasLen, 2) 231 c.Assert(ifm[task1][source1][upSchema][upTable], DeepEquals, i11WithVer) 232 i12WithVer := i12 233 i12WithVer.Version = 1 234 i12WithVer.Revision = rev4 235 c.Assert(ifm[task1][source2][upSchema][upTable], DeepEquals, i12WithVer) 236 237 // start the watcher. 238 wch := make(chan Info, 10) 239 ech := make(chan error, 10) 240 var wg sync.WaitGroup 241 wg.Add(1) 242 watchCtx, watchCancel := context.WithCancel(context.Background()) 243 defer watchCancel() 244 go func() { 245 defer wg.Done() 246 WatchInfo(watchCtx, etcdTestCli, rev4+1, wch, ech) // revision+1 247 }() 248 249 // put another key for a different task. 250 // version start from 1 251 rev5, err := PutInfo(etcdTestCli, i21) 252 c.Assert(err, IsNil) 253 infoWithVer := <-wch 254 i21WithVer := i21 255 i21WithVer.Version = 1 256 i21WithVer.Revision = rev5 257 c.Assert(infoWithVer, DeepEquals, i21WithVer) 258 c.Assert(len(ech), Equals, 0) 259 260 // put again 261 // version increase 262 rev6, err := PutInfo(etcdTestCli, i21) 263 c.Assert(err, IsNil) 264 infoWithVer = <-wch 265 i21WithVer.Version++ 266 i21WithVer.Revision = rev6 267 c.Assert(infoWithVer, DeepEquals, i21WithVer) 268 c.Assert(len(ech), Equals, 0) 269 270 // delete i21. 271 deleteOp := deleteInfoOp(i21) 272 resp, err := etcdTestCli.Txn(context.Background()).Then(deleteOp).Commit() 273 c.Assert(err, IsNil) 274 c.Assert(resp.Succeeded, IsTrue) 275 select { 276 case err2 := <-ech: 277 c.Fatal(err2) 278 case <-wch: 279 } 280 281 // put again 282 // version reset to 1 283 rev7, err := PutInfo(etcdTestCli, i21) 284 c.Assert(err, IsNil) 285 infoWithVer = <-wch 286 i21WithVer.Version = 1 287 i21WithVer.Revision = rev7 288 c.Assert(infoWithVer, DeepEquals, i21WithVer) 289 c.Assert(len(ech), Equals, 0) 290 291 watchCancel() 292 wg.Wait() 293 close(wch) // close the chan 294 close(ech) 295 296 // delete i12. 297 deleteOp = deleteInfoOp(i12) 298 resp, err = etcdTestCli.Txn(context.Background()).Then(deleteOp).Commit() 299 c.Assert(err, IsNil) 300 301 // get again. 302 ifm, _, err = GetAllInfo(etcdTestCli) 303 c.Assert(err, IsNil) 304 c.Assert(ifm, HasLen, 2) 305 c.Assert(ifm, HasKey, task1) 306 c.Assert(ifm, HasKey, task2) 307 c.Assert(ifm[task1], HasLen, 1) 308 i11WithVer.Revision = ifm[task1][source1][upSchema][upTable].Revision 309 c.Assert(ifm[task1][source1][upSchema][upTable], DeepEquals, i11WithVer) 310 c.Assert(ifm[task2], HasLen, 1) 311 i21WithVer.Revision = ifm[task2][source1][upSchema][upTable].Revision 312 c.Assert(ifm[task2][source1][upSchema][upTable], DeepEquals, i21WithVer) 313 314 // watch the deletion for i12. 315 wch = make(chan Info, 10) 316 ech = make(chan error, 10) 317 ctx, cancel := context.WithTimeout(context.Background(), watchTimeout) 318 WatchInfo(ctx, etcdTestCli, resp.Header.Revision, wch, ech) 319 cancel() 320 close(wch) 321 close(ech) 322 c.Assert(len(wch), Equals, 1) 323 info := <-wch 324 i12c := i12 325 i12c.IsDeleted = true 326 c.Assert(info, DeepEquals, i12c) 327 c.Assert(len(ech), Equals, 0) 328 } 329 330 func newOldInfo(task, source, upSchema, upTable, downSchema, downTable string, 331 ddls []string, tableInfoBefore *model.TableInfo, tableInfoAfter *model.TableInfo, 332 ) OldInfo { 333 return OldInfo{ 334 Task: task, 335 Source: source, 336 UpSchema: upSchema, 337 UpTable: upTable, 338 DownSchema: downSchema, 339 DownTable: downTable, 340 DDLs: ddls, 341 TableInfoBefore: tableInfoBefore, 342 TableInfoAfter: tableInfoAfter, 343 } 344 } 345 346 func putOldInfo(cli *clientv3.Client, oldInfo OldInfo) (int64, error) { 347 data, err := json.Marshal(oldInfo) 348 if err != nil { 349 return 0, err 350 } 351 key := common.ShardDDLOptimismInfoKeyAdapter.Encode(oldInfo.Task, oldInfo.Source, oldInfo.UpSchema, oldInfo.UpTable) 352 353 ctx, cancel := context.WithTimeout(cli.Ctx(), etcdutil.DefaultRequestTimeout) 354 defer cancel() 355 356 resp, err := cli.Put(ctx, key, string(data)) 357 if err != nil { 358 return 0, err 359 } 360 return resp.Header.Revision, nil 361 }