github.com/pingcap/ticdc@v0.0.0-20220526033649-485a10ef2652/cdc/kv/etcd_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 kv
    15  
    16  import (
    17  	"context"
    18  	"fmt"
    19  	"net/url"
    20  	"sort"
    21  	"time"
    22  
    23  	"github.com/pingcap/check"
    24  	"github.com/pingcap/ticdc/cdc/model"
    25  	cerror "github.com/pingcap/ticdc/pkg/errors"
    26  	"github.com/pingcap/ticdc/pkg/etcd"
    27  	"github.com/pingcap/ticdc/pkg/util"
    28  	"github.com/pingcap/ticdc/pkg/util/testleak"
    29  	"go.etcd.io/etcd/clientv3"
    30  	"go.etcd.io/etcd/clientv3/concurrency"
    31  	"go.etcd.io/etcd/embed"
    32  	"go.etcd.io/etcd/pkg/logutil"
    33  	"go.uber.org/zap"
    34  	"go.uber.org/zap/zapcore"
    35  	"golang.org/x/sync/errgroup"
    36  )
    37  
    38  type etcdSuite struct {
    39  	e         *embed.Etcd
    40  	clientURL *url.URL
    41  	client    CDCEtcdClient
    42  	ctx       context.Context
    43  	cancel    context.CancelFunc
    44  	errg      *errgroup.Group
    45  }
    46  
    47  var _ = check.Suite(&etcdSuite{})
    48  
    49  func (s *etcdSuite) SetUpTest(c *check.C) {
    50  	dir := c.MkDir()
    51  	var err error
    52  	s.clientURL, s.e, err = etcd.SetupEmbedEtcd(dir)
    53  	c.Assert(err, check.IsNil)
    54  	logConfig := logutil.DefaultZapLoggerConfig
    55  	logConfig.Level = zap.NewAtomicLevelAt(zapcore.ErrorLevel)
    56  	client, err := clientv3.New(clientv3.Config{
    57  		Endpoints:   []string{s.clientURL.String()},
    58  		DialTimeout: 3 * time.Second,
    59  		LogConfig:   &logConfig,
    60  	})
    61  	c.Assert(err, check.IsNil)
    62  	s.client = NewCDCEtcdClient(context.TODO(), client)
    63  	s.ctx, s.cancel = context.WithCancel(context.Background())
    64  	s.errg = util.HandleErrWithErrGroup(s.ctx, s.e.Err(), func(e error) { c.Log(e) })
    65  }
    66  
    67  func (s *etcdSuite) TearDownTest(c *check.C) {
    68  	s.e.Close()
    69  	s.cancel()
    70  	err := s.errg.Wait()
    71  	if err != nil {
    72  		c.Errorf("Error group error: %s", err)
    73  	}
    74  	s.client.Close() //nolint:errcheck
    75  }
    76  
    77  func (s *etcdSuite) TestGetChangeFeeds(c *check.C) {
    78  	defer testleak.AfterTest(c)()
    79  	// `TearDownTest` must be called before leak test, so we take advantage of
    80  	// the stack feature of defer. Ditto for all tests with etcdSuite.
    81  	defer s.TearDownTest(c)
    82  	testCases := []struct {
    83  		ids     []string
    84  		details []string
    85  	}{
    86  		{ids: nil, details: nil},
    87  		{ids: []string{"id"}, details: []string{"detail"}},
    88  		{ids: []string{"id", "id1", "id2"}, details: []string{"detail", "detail1", "detail2"}},
    89  	}
    90  	for _, tc := range testCases {
    91  		for i := 0; i < len(tc.ids); i++ {
    92  			_, err := s.client.Client.Put(context.Background(), GetEtcdKeyChangeFeedInfo(tc.ids[i]), tc.details[i])
    93  			c.Assert(err, check.IsNil)
    94  		}
    95  		_, result, err := s.client.GetChangeFeeds(context.Background())
    96  		c.Assert(err, check.IsNil)
    97  		c.Assert(len(result), check.Equals, len(tc.ids))
    98  		for i := 0; i < len(tc.ids); i++ {
    99  			rawKv, ok := result[tc.ids[i]]
   100  			c.Assert(ok, check.IsTrue)
   101  			c.Assert(string(rawKv.Value), check.Equals, tc.details[i])
   102  		}
   103  	}
   104  	_, result, err := s.client.GetChangeFeeds(context.Background())
   105  	c.Assert(err, check.IsNil)
   106  	c.Assert(len(result), check.Equals, 3)
   107  
   108  	err = s.client.ClearAllCDCInfo(context.Background())
   109  	c.Assert(err, check.IsNil)
   110  
   111  	_, result, err = s.client.GetChangeFeeds(context.Background())
   112  	c.Assert(err, check.IsNil)
   113  	c.Assert(len(result), check.Equals, 0)
   114  }
   115  
   116  func (s *etcdSuite) TestGetPutTaskStatus(c *check.C) {
   117  	defer testleak.AfterTest(c)()
   118  	defer s.TearDownTest(c)
   119  	ctx := context.Background()
   120  	info := &model.TaskStatus{
   121  		Tables: map[model.TableID]*model.TableReplicaInfo{
   122  			1: {StartTs: 100},
   123  		},
   124  	}
   125  
   126  	feedID := "feedid"
   127  	captureID := "captureid"
   128  
   129  	err := s.client.PutTaskStatus(ctx, feedID, captureID, info)
   130  	c.Assert(err, check.IsNil)
   131  
   132  	_, getInfo, err := s.client.GetTaskStatus(ctx, feedID, captureID)
   133  	c.Assert(err, check.IsNil)
   134  	c.Assert(getInfo, check.DeepEquals, info)
   135  
   136  	err = s.client.ClearAllCDCInfo(context.Background())
   137  	c.Assert(err, check.IsNil)
   138  	_, _, err = s.client.GetTaskStatus(ctx, feedID, captureID)
   139  	c.Assert(cerror.ErrTaskStatusNotExists.Equal(err), check.IsTrue)
   140  }
   141  
   142  func (s *etcdSuite) TestDeleteTaskStatus(c *check.C) {
   143  	defer testleak.AfterTest(c)()
   144  	defer s.TearDownTest(c)
   145  	ctx := context.Background()
   146  	info := &model.TaskStatus{
   147  		Tables: map[model.TableID]*model.TableReplicaInfo{
   148  			1: {StartTs: 100},
   149  		},
   150  	}
   151  	feedID := "feedid"
   152  	captureID := "captureid"
   153  
   154  	err := s.client.PutTaskStatus(ctx, feedID, captureID, info)
   155  	c.Assert(err, check.IsNil)
   156  
   157  	sess, err := concurrency.NewSession(s.client.Client.Unwrap(), concurrency.WithTTL(2))
   158  	c.Assert(err, check.IsNil)
   159  	err = s.client.LeaseGuardDeleteTaskStatus(ctx, feedID, captureID, sess.Lease())
   160  	c.Assert(err, check.IsNil)
   161  	_, _, err = s.client.GetTaskStatus(ctx, feedID, captureID)
   162  	c.Assert(cerror.ErrTaskStatusNotExists.Equal(err), check.IsTrue)
   163  }
   164  
   165  func (s *etcdSuite) TestGetPutTaskPosition(c *check.C) {
   166  	defer testleak.AfterTest(c)()
   167  	defer s.TearDownTest(c)
   168  	ctx := context.Background()
   169  	info := &model.TaskPosition{
   170  		ResolvedTs:   99,
   171  		CheckPointTs: 77,
   172  	}
   173  
   174  	feedID := "feedid"
   175  	captureID := "captureid"
   176  
   177  	updated, err := s.client.PutTaskPositionOnChange(ctx, feedID, captureID, info)
   178  	c.Assert(err, check.IsNil)
   179  	c.Assert(updated, check.IsTrue)
   180  
   181  	updated, err = s.client.PutTaskPositionOnChange(ctx, feedID, captureID, info)
   182  	c.Assert(err, check.IsNil)
   183  	c.Assert(updated, check.IsFalse)
   184  
   185  	info.CheckPointTs = 99
   186  	updated, err = s.client.PutTaskPositionOnChange(ctx, feedID, captureID, info)
   187  	c.Assert(err, check.IsNil)
   188  	c.Assert(updated, check.IsTrue)
   189  
   190  	_, getInfo, err := s.client.GetTaskPosition(ctx, feedID, captureID)
   191  	c.Assert(err, check.IsNil)
   192  	c.Assert(getInfo, check.DeepEquals, info)
   193  
   194  	err = s.client.ClearAllCDCInfo(ctx)
   195  	c.Assert(err, check.IsNil)
   196  	_, _, err = s.client.GetTaskStatus(ctx, feedID, captureID)
   197  	c.Assert(cerror.ErrTaskStatusNotExists.Equal(err), check.IsTrue)
   198  }
   199  
   200  func (s *etcdSuite) TestDeleteTaskPosition(c *check.C) {
   201  	defer testleak.AfterTest(c)()
   202  	defer s.TearDownTest(c)
   203  	ctx := context.Background()
   204  	info := &model.TaskPosition{
   205  		ResolvedTs:   77,
   206  		CheckPointTs: 88,
   207  	}
   208  	feedID := "feedid"
   209  	captureID := "captureid"
   210  
   211  	_, err := s.client.PutTaskPositionOnChange(ctx, feedID, captureID, info)
   212  	c.Assert(err, check.IsNil)
   213  
   214  	sess, err := concurrency.NewSession(s.client.Client.Unwrap(), concurrency.WithTTL(2))
   215  	c.Assert(err, check.IsNil)
   216  	err = s.client.LeaseGuardDeleteTaskPosition(ctx, feedID, captureID, sess.Lease())
   217  	c.Assert(err, check.IsNil)
   218  	_, _, err = s.client.GetTaskPosition(ctx, feedID, captureID)
   219  	c.Assert(cerror.ErrTaskPositionNotExists.Equal(err), check.IsTrue)
   220  }
   221  
   222  func (s *etcdSuite) TestOpChangeFeedDetail(c *check.C) {
   223  	defer testleak.AfterTest(c)()
   224  	defer s.TearDownTest(c)
   225  	ctx := context.Background()
   226  	detail := &model.ChangeFeedInfo{
   227  		SinkURI: "root@tcp(127.0.0.1:3306)/mysql",
   228  		SortDir: "/old-version/sorter",
   229  	}
   230  	cfID := "test-op-cf"
   231  
   232  	sess, err := concurrency.NewSession(s.client.Client.Unwrap(), concurrency.WithTTL(2))
   233  	c.Assert(err, check.IsNil)
   234  	err = s.client.LeaseGuardSaveChangeFeedInfo(ctx, detail, cfID, sess.Lease())
   235  	c.Assert(err, check.IsNil)
   236  
   237  	d, err := s.client.GetChangeFeedInfo(ctx, cfID)
   238  	c.Assert(err, check.IsNil)
   239  	c.Assert(d.SinkURI, check.Equals, detail.SinkURI)
   240  	c.Assert(d.SortDir, check.Equals, detail.SortDir)
   241  
   242  	err = s.client.LeaseGuardDeleteChangeFeedInfo(ctx, cfID, sess.Lease())
   243  	c.Assert(err, check.IsNil)
   244  
   245  	_, err = s.client.GetChangeFeedInfo(ctx, cfID)
   246  	c.Assert(cerror.ErrChangeFeedNotExists.Equal(err), check.IsTrue)
   247  }
   248  
   249  func (s *etcdSuite) TestRemoveAllTaskXXX(c *check.C) {
   250  	defer testleak.AfterTest(c)()
   251  	defer s.TearDownTest(c)
   252  	ctx := context.Background()
   253  	status := &model.TaskStatus{
   254  		Tables: map[model.TableID]*model.TableReplicaInfo{
   255  			1: {StartTs: 100},
   256  		},
   257  	}
   258  	position := &model.TaskPosition{
   259  		ResolvedTs:   100,
   260  		CheckPointTs: 100,
   261  	}
   262  
   263  	feedID := "feedid"
   264  	captureID := "captureid"
   265  
   266  	sess, err := concurrency.NewSession(s.client.Client.Unwrap(), concurrency.WithTTL(2))
   267  	c.Assert(err, check.IsNil)
   268  
   269  	err = s.client.PutTaskStatus(ctx, feedID, captureID, status)
   270  	c.Assert(err, check.IsNil)
   271  	_, err = s.client.PutTaskPositionOnChange(ctx, feedID, captureID, position)
   272  	c.Assert(err, check.IsNil)
   273  	err = s.client.LeaseGuardRemoveAllTaskStatus(ctx, feedID, sess.Lease())
   274  	c.Assert(err, check.IsNil)
   275  	err = s.client.LeaseGuardRemoveAllTaskPositions(ctx, feedID, sess.Lease())
   276  	c.Assert(err, check.IsNil)
   277  
   278  	_, _, err = s.client.GetTaskStatus(ctx, feedID, captureID)
   279  	c.Assert(cerror.ErrTaskStatusNotExists.Equal(err), check.IsTrue)
   280  	_, _, err = s.client.GetTaskPosition(ctx, feedID, captureID)
   281  	c.Assert(cerror.ErrTaskPositionNotExists.Equal(err), check.IsTrue)
   282  }
   283  
   284  func (s *etcdSuite) TestPutAllChangeFeedStatus(c *check.C) {
   285  	defer testleak.AfterTest(c)()
   286  	defer s.TearDownTest(c)
   287  	var (
   288  		status1 = &model.ChangeFeedStatus{
   289  			ResolvedTs:   2200,
   290  			CheckpointTs: 2000,
   291  		}
   292  		status2 = &model.ChangeFeedStatus{
   293  			ResolvedTs:   2600,
   294  			CheckpointTs: 2500,
   295  		}
   296  		err error
   297  	)
   298  	largeTxnInfo := make(map[string]*model.ChangeFeedStatus, embed.DefaultMaxTxnOps+1)
   299  	for i := 0; i < int(embed.DefaultMaxTxnOps)+1; i++ {
   300  		changefeedID := fmt.Sprintf("changefeed%d", i+1)
   301  		largeTxnInfo[changefeedID] = status1
   302  	}
   303  	testCases := []struct {
   304  		infos map[model.ChangeFeedID]*model.ChangeFeedStatus
   305  	}{
   306  		{infos: nil},
   307  		{infos: map[string]*model.ChangeFeedStatus{"changefeed1": status1}},
   308  		{infos: map[string]*model.ChangeFeedStatus{"changefeed1": status1, "changefeed2": status2}},
   309  		{infos: largeTxnInfo},
   310  	}
   311  
   312  	for _, tc := range testCases {
   313  		for changefeedID := range tc.infos {
   314  			_, err = s.client.Client.Delete(context.Background(), GetEtcdKeyChangeFeedStatus(changefeedID))
   315  			c.Assert(err, check.IsNil)
   316  		}
   317  
   318  		sess, err := concurrency.NewSession(s.client.Client.Unwrap(), concurrency.WithTTL(2))
   319  		c.Assert(err, check.IsNil)
   320  		err = s.client.LeaseGuardPutAllChangeFeedStatus(context.Background(), tc.infos, sess.Lease())
   321  		c.Assert(err, check.IsNil)
   322  
   323  		for changefeedID, info := range tc.infos {
   324  			resp, err := s.client.Client.Get(context.Background(), GetEtcdKeyChangeFeedStatus(changefeedID))
   325  			c.Assert(err, check.IsNil)
   326  			c.Assert(resp.Count, check.Equals, int64(1))
   327  			infoStr, err := info.Marshal()
   328  			c.Assert(err, check.IsNil)
   329  			c.Assert(string(resp.Kvs[0].Value), check.Equals, infoStr)
   330  		}
   331  	}
   332  }
   333  
   334  func (s etcdSuite) TestGetAllChangeFeedStatus(c *check.C) {
   335  	defer testleak.AfterTest(c)()
   336  	defer s.TearDownTest(c)
   337  	changefeeds := map[model.ChangeFeedID]*model.ChangeFeedStatus{
   338  		"cf1": {
   339  			ResolvedTs:   100,
   340  			CheckpointTs: 90,
   341  		},
   342  		"cf2": {
   343  			ResolvedTs:   100,
   344  			CheckpointTs: 70,
   345  		},
   346  	}
   347  	err := s.client.PutAllChangeFeedStatus(context.Background(), changefeeds)
   348  	c.Assert(err, check.IsNil)
   349  	statuses, err := s.client.GetAllChangeFeedStatus(context.Background())
   350  	c.Assert(err, check.IsNil)
   351  	c.Assert(statuses, check.DeepEquals, changefeeds)
   352  }
   353  
   354  func (s *etcdSuite) TestRemoveChangeFeedStatus(c *check.C) {
   355  	defer testleak.AfterTest(c)()
   356  	defer s.TearDownTest(c)
   357  	ctx := context.Background()
   358  	changefeedID := "test-remove-changefeed-status"
   359  	status := &model.ChangeFeedStatus{
   360  		ResolvedTs: 1,
   361  	}
   362  
   363  	sess, err := concurrency.NewSession(s.client.Client.Unwrap(), concurrency.WithTTL(2))
   364  	c.Assert(err, check.IsNil)
   365  	err = s.client.LeaseGuardPutChangeFeedStatus(ctx, changefeedID, status, sess.Lease())
   366  	c.Assert(err, check.IsNil)
   367  	status, _, err = s.client.GetChangeFeedStatus(ctx, changefeedID)
   368  	c.Assert(err, check.IsNil)
   369  	c.Assert(status, check.DeepEquals, status)
   370  
   371  	err = s.client.LeaseGuardRemoveChangeFeedStatus(ctx, changefeedID, sess.Lease())
   372  	c.Assert(err, check.IsNil)
   373  	_, _, err = s.client.GetChangeFeedStatus(ctx, changefeedID)
   374  	c.Assert(cerror.ErrChangeFeedNotExists.Equal(err), check.IsTrue)
   375  }
   376  
   377  func (s *etcdSuite) TestSetChangeFeedStatusTTL(c *check.C) {
   378  	defer testleak.AfterTest(c)()
   379  	defer s.TearDownTest(c)
   380  	ctx := context.Background()
   381  	err := s.client.PutChangeFeedStatus(ctx, "test1", &model.ChangeFeedStatus{
   382  		ResolvedTs: 1,
   383  	})
   384  	c.Assert(err, check.IsNil)
   385  	status, _, err := s.client.GetChangeFeedStatus(ctx, "test1")
   386  	c.Assert(err, check.IsNil)
   387  	c.Assert(status, check.DeepEquals, &model.ChangeFeedStatus{
   388  		ResolvedTs: 1,
   389  	})
   390  	err = s.client.SetChangeFeedStatusTTL(ctx, "test1", 1 /* second */)
   391  	c.Assert(err, check.IsNil)
   392  	status, _, err = s.client.GetChangeFeedStatus(ctx, "test1")
   393  	c.Assert(err, check.IsNil)
   394  	c.Assert(status, check.DeepEquals, &model.ChangeFeedStatus{
   395  		ResolvedTs: 1,
   396  	})
   397  	for i := 0; i < 50; i++ {
   398  		_, _, err = s.client.GetChangeFeedStatus(ctx, "test1")
   399  		if err != nil {
   400  			if cerror.ErrChangeFeedNotExists.Equal(err) {
   401  				return
   402  			}
   403  			c.Fatal("got unexpected error", err)
   404  		}
   405  		time.Sleep(100 * time.Millisecond)
   406  	}
   407  	c.Fatal("the change feed status is still exists after 5 seconds")
   408  }
   409  
   410  func (s *etcdSuite) TestDeleteTaskWorkload(c *check.C) {
   411  	defer testleak.AfterTest(c)()
   412  	defer s.TearDownTest(c)
   413  	ctx := context.Background()
   414  	workload := &model.TaskWorkload{
   415  		1001: model.WorkloadInfo{Workload: 1},
   416  		1002: model.WorkloadInfo{Workload: 3},
   417  	}
   418  	feedID := "feedid"
   419  	captureID := "captureid"
   420  
   421  	err := s.client.PutTaskWorkload(ctx, feedID, captureID, workload)
   422  	c.Assert(err, check.IsNil)
   423  
   424  	sess, err := concurrency.NewSession(s.client.Client.Unwrap(), concurrency.WithTTL(2))
   425  	c.Assert(err, check.IsNil)
   426  	err = s.client.LeaseGuardDeleteTaskWorkload(ctx, feedID, captureID, sess.Lease())
   427  	c.Assert(err, check.IsNil)
   428  
   429  	tw, err := s.client.GetTaskWorkload(ctx, feedID, captureID)
   430  	c.Assert(err, check.IsNil)
   431  	c.Assert(len(tw), check.Equals, 0)
   432  }
   433  
   434  func (s *etcdSuite) TestGetAllTaskWorkload(c *check.C) {
   435  	defer testleak.AfterTest(c)()
   436  	defer s.TearDownTest(c)
   437  	ctx := context.Background()
   438  	feeds := []string{"feed1", "feed2"}
   439  	captures := []string{"capture1", "capture2", "capture3"}
   440  	expected := []map[string]*model.TaskWorkload{
   441  		{
   442  			"capture1": {1000: model.WorkloadInfo{Workload: 1}},
   443  			"capture2": {1001: model.WorkloadInfo{Workload: 1}},
   444  			"capture3": {1002: model.WorkloadInfo{Workload: 1}},
   445  		},
   446  		{
   447  			"capture1": {2000: model.WorkloadInfo{Workload: 1}},
   448  			"capture2": {2001: model.WorkloadInfo{Workload: 1}},
   449  			"capture3": {2002: model.WorkloadInfo{Workload: 1}},
   450  		},
   451  	}
   452  
   453  	for i, feed := range feeds {
   454  		for j, capture := range captures {
   455  			err := s.client.PutTaskWorkload(ctx, feed, capture, &model.TaskWorkload{
   456  				int64(1000*(i+1) + j): model.WorkloadInfo{Workload: 1},
   457  			})
   458  			c.Assert(err, check.IsNil)
   459  		}
   460  	}
   461  	for i := range feeds {
   462  		workloads, err := s.client.GetAllTaskWorkloads(ctx, feeds[i])
   463  		c.Assert(err, check.IsNil)
   464  		c.Assert(workloads, check.DeepEquals, expected[i])
   465  	}
   466  }
   467  
   468  func (s *etcdSuite) TestCreateChangefeed(c *check.C) {
   469  	defer testleak.AfterTest(c)()
   470  	defer s.TearDownTest(c)
   471  	ctx := context.Background()
   472  	detail := &model.ChangeFeedInfo{
   473  		SinkURI: "root@tcp(127.0.0.1:3306)/mysql",
   474  	}
   475  
   476  	err := s.client.CreateChangefeedInfo(ctx, detail, "test-id")
   477  	c.Assert(err, check.IsNil)
   478  
   479  	err = s.client.CreateChangefeedInfo(ctx, detail, "test-id")
   480  	c.Assert(cerror.ErrChangeFeedAlreadyExists.Equal(err), check.IsTrue)
   481  }
   482  
   483  type Captures []*model.CaptureInfo
   484  
   485  func (c Captures) Len() int           { return len(c) }
   486  func (c Captures) Less(i, j int) bool { return c[i].ID < c[j].ID }
   487  func (c Captures) Swap(i, j int)      { c[i], c[j] = c[j], c[i] }
   488  
   489  func (s *etcdSuite) TestGetAllCaptureLeases(c *check.C) {
   490  	defer testleak.AfterTest(c)()
   491  	defer s.TearDownTest(c)
   492  	ctx, cancel := context.WithCancel(context.Background())
   493  	defer cancel()
   494  	testCases := []*model.CaptureInfo{
   495  		{
   496  			ID:            "a3f41a6a-3c31-44f4-aa27-344c1b8cd658",
   497  			AdvertiseAddr: "127.0.0.1:8301",
   498  		},
   499  		{
   500  			ID:            "cdb041d9-ccdd-480d-9975-e97d7adb1185",
   501  			AdvertiseAddr: "127.0.0.1:8302",
   502  		},
   503  		{
   504  			ID:            "e05e5d34-96ea-44af-812d-ca72aa19e1e5",
   505  			AdvertiseAddr: "127.0.0.1:8303",
   506  		},
   507  	}
   508  	leases := make(map[string]int64)
   509  
   510  	for _, cinfo := range testCases {
   511  		sess, err := concurrency.NewSession(s.client.Client.Unwrap(),
   512  			concurrency.WithTTL(10), concurrency.WithContext(ctx))
   513  		c.Assert(err, check.IsNil)
   514  		err = s.client.PutCaptureInfo(ctx, cinfo, sess.Lease())
   515  		c.Assert(err, check.IsNil)
   516  		leases[cinfo.ID] = int64(sess.Lease())
   517  	}
   518  
   519  	_, captures, err := s.client.GetCaptures(ctx)
   520  	c.Assert(err, check.IsNil)
   521  	c.Assert(captures, check.HasLen, len(testCases))
   522  	sort.Sort(Captures(captures))
   523  	c.Assert(captures, check.DeepEquals, testCases)
   524  
   525  	queryLeases, err := s.client.GetCaptureLeases(ctx)
   526  	c.Assert(err, check.IsNil)
   527  	c.Check(queryLeases, check.DeepEquals, leases)
   528  
   529  	// make sure the RevokeAllLeases function can ignore the lease not exist
   530  	leases["/fake/capture/info"] = 200
   531  	err = s.client.RevokeAllLeases(ctx, leases)
   532  	c.Assert(err, check.IsNil)
   533  	queryLeases, err = s.client.GetCaptureLeases(ctx)
   534  	c.Assert(err, check.IsNil)
   535  	c.Check(queryLeases, check.DeepEquals, map[string]int64{})
   536  }
   537  
   538  func (s *etcdSuite) TestGetAllCDCInfo(c *check.C) {
   539  	defer testleak.AfterTest(c)()
   540  	defer s.TearDownTest(c)
   541  	captureID := "CAPTURE_ID"
   542  	changefeedID := "CHANGEFEED_ID"
   543  	ctx := context.Background()
   544  	err := s.client.PutTaskWorkload(ctx, changefeedID, captureID, &model.TaskWorkload{
   545  		11: model.WorkloadInfo{Workload: 1},
   546  		22: model.WorkloadInfo{Workload: 22},
   547  	})
   548  	c.Assert(err, check.IsNil)
   549  	err = s.client.PutTaskStatus(ctx, changefeedID, captureID, &model.TaskStatus{
   550  		Tables: map[model.TableID]*model.TableReplicaInfo{11: {StartTs: 22}},
   551  	})
   552  	c.Assert(err, check.IsNil)
   553  	kvs, err := s.client.GetAllCDCInfo(ctx)
   554  	c.Assert(err, check.IsNil)
   555  	expected := []struct {
   556  		key   string
   557  		value string
   558  	}{{
   559  		key:   "/tidb/cdc/task/status/CAPTURE_ID/CHANGEFEED_ID",
   560  		value: "{\"tables\":{\"11\":{\"start-ts\":22,\"mark-table-id\":0}},\"operation\":null,\"admin-job-type\":0}",
   561  	}, {
   562  		key:   "/tidb/cdc/task/workload/CAPTURE_ID/CHANGEFEED_ID",
   563  		value: "{\"11\":{\"workload\":1},\"22\":{\"workload\":22}}",
   564  	}}
   565  	for i, kv := range kvs {
   566  		c.Assert(string(kv.Key), check.Equals, expected[i].key)
   567  		c.Assert(string(kv.Value), check.Equals, expected[i].value)
   568  	}
   569  }
   570  
   571  func (s *etcdSuite) TestAtomicPutTaskStatus(c *check.C) {
   572  	defer testleak.AfterTest(c)()
   573  	defer s.TearDownTest(c)
   574  	ctx := context.Background()
   575  	status := &model.TaskStatus{
   576  		Tables: map[model.TableID]*model.TableReplicaInfo{
   577  			1: {StartTs: 100},
   578  		},
   579  	}
   580  	feedID := "feedid"
   581  	captureID := "captureid"
   582  
   583  	sess, err := concurrency.NewSession(s.client.Client.Unwrap(), concurrency.WithTTL(2))
   584  	c.Assert(err, check.IsNil)
   585  	err = s.client.PutTaskStatus(ctx, feedID, captureID, status)
   586  	c.Assert(err, check.IsNil)
   587  
   588  	status.Tables[2] = &model.TableReplicaInfo{StartTs: 120}
   589  	_, revision, err := s.client.LeaseGuardAtomicPutTaskStatus(
   590  		ctx, feedID, captureID, sess.Lease(),
   591  		func(modRevision int64, taskStatus *model.TaskStatus) (bool, error) {
   592  			taskStatus.Tables = status.Tables
   593  			taskStatus.Operation = status.Operation
   594  			return true, nil
   595  		},
   596  	)
   597  	c.Assert(err, check.IsNil)
   598  	modRevision, newStatus, err := s.client.GetTaskStatus(ctx, feedID, captureID)
   599  	c.Assert(err, check.IsNil)
   600  	c.Assert(modRevision, check.Equals, revision)
   601  	c.Assert(newStatus, check.DeepEquals, status)
   602  }
   603  
   604  func (s *etcdSuite) TestLeaseGuardWorks(c *check.C) {
   605  	defer testleak.AfterTest(c)()
   606  	defer s.TearDownTest(c)
   607  
   608  	// embed etcd election timeout is 1s, minimum session ttl is 2s
   609  	sess, err := concurrency.NewSession(s.client.Client.Unwrap(), concurrency.WithTTL(2))
   610  	c.Assert(err, check.IsNil)
   611  	ctx, _, err := s.client.contextWithSafeLease(context.Background(), sess.Lease())
   612  	c.Assert(err, check.IsNil)
   613  	time.Sleep(time.Second * 2)
   614  	select {
   615  	case <-ctx.Done():
   616  	case <-time.After(time.Second):
   617  		c.Errorf("context is not done as expected")
   618  	}
   619  
   620  	_, err = s.client.Client.Revoke(context.Background(), sess.Lease())
   621  	c.Assert(err, check.IsNil)
   622  	_, _, err = s.client.contextWithSafeLease(context.Background(), sess.Lease())
   623  	c.Assert(cerror.ErrLeaseTimeout.Equal(err), check.IsTrue)
   624  }