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  }