github.com/pingcap/ticdc@v0.0.0-20220526033649-485a10ef2652/cdc/task_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 cdc
    15  
    16  import (
    17  	"context"
    18  	"math"
    19  	"time"
    20  
    21  	"github.com/pingcap/check"
    22  	"github.com/pingcap/failpoint"
    23  	"github.com/pingcap/ticdc/cdc/kv"
    24  	"github.com/pingcap/ticdc/cdc/model"
    25  	"github.com/pingcap/ticdc/pkg/config"
    26  	"github.com/pingcap/ticdc/pkg/etcd"
    27  	"github.com/pingcap/ticdc/pkg/util/testleak"
    28  	"go.etcd.io/etcd/clientv3"
    29  	"go.etcd.io/etcd/embed"
    30  )
    31  
    32  type taskSuite struct {
    33  	s         *embed.Etcd
    34  	c         *clientv3.Client
    35  	w         *TaskWatcher
    36  	endpoints []string
    37  }
    38  
    39  var _ = check.Suite(&taskSuite{})
    40  
    41  func (s *taskSuite) SetUpTest(c *check.C) {
    42  	dir := c.MkDir()
    43  	url, etcd, err := etcd.SetupEmbedEtcd(dir)
    44  	c.Assert(err, check.IsNil)
    45  
    46  	endpoints := []string{url.String()}
    47  	client, err := clientv3.New(clientv3.Config{
    48  		Endpoints: endpoints,
    49  	})
    50  	c.Assert(err, check.IsNil)
    51  
    52  	// Create a task watcher
    53  	capture := &Capture{
    54  		etcdClient: kv.NewCDCEtcdClient(context.TODO(), client),
    55  		processors: make(map[string]*oldProcessor),
    56  		info:       &model.CaptureInfo{ID: "task-suite-capture", AdvertiseAddr: "task-suite-addr"},
    57  	}
    58  	c.Assert(capture, check.NotNil)
    59  	watcher := NewTaskWatcher(capture, &TaskWatcherConfig{
    60  		Prefix: kv.TaskStatusKeyPrefix + "/" + capture.info.ID,
    61  	})
    62  	c.Assert(watcher, check.NotNil)
    63  
    64  	s.s = etcd
    65  	s.c = client
    66  	s.w = watcher
    67  	s.endpoints = endpoints
    68  }
    69  
    70  func (s *taskSuite) TearDownTest(c *check.C) {
    71  	s.s.Close()
    72  	s.c.Close()
    73  }
    74  
    75  func (s *taskSuite) TestNewTaskWatcher(c *check.C) {
    76  	defer testleak.AfterTest(c)()
    77  	defer s.TearDownTest(c)
    78  	// Create a capture instance by initialize the struct,
    79  	// NewCapture can not be used because it requires to
    80  	// initialize the PD service witch does not support to
    81  	// be embeded.
    82  	if config.NewReplicaImpl {
    83  		c.Skip("this case is designed for old processor")
    84  	}
    85  	capture := &Capture{
    86  		etcdClient: kv.NewCDCEtcdClient(context.TODO(), s.c),
    87  		processors: make(map[string]*oldProcessor),
    88  		info:       &model.CaptureInfo{ID: "task-suite-capture", AdvertiseAddr: "task-suite-addr"},
    89  	}
    90  	c.Assert(capture, check.NotNil)
    91  	c.Assert(NewTaskWatcher(capture, &TaskWatcherConfig{
    92  		Prefix: kv.TaskStatusKeyPrefix + "/" + capture.info.ID,
    93  	}), check.NotNil)
    94  	capture.Close(context.Background())
    95  }
    96  
    97  func (s *taskSuite) setupFeedInfo(c *check.C, changeFeedID string) {
    98  	client := kv.NewCDCEtcdClient(context.TODO(), s.c)
    99  	// Create the change feed
   100  	c.Assert(client.SaveChangeFeedInfo(s.c.Ctx(), &model.ChangeFeedInfo{
   101  		SinkURI:    "mysql://fake",
   102  		StartTs:    0,
   103  		TargetTs:   math.MaxUint64,
   104  		CreateTime: time.Now(),
   105  	}, changeFeedID), check.IsNil)
   106  
   107  	// Fake the change feed status
   108  	c.Assert(client.PutChangeFeedStatus(s.c.Ctx(), changeFeedID,
   109  		&model.ChangeFeedStatus{
   110  			ResolvedTs:   1,
   111  			CheckpointTs: 1,
   112  		}), check.IsNil)
   113  }
   114  
   115  func (s *taskSuite) teardownFeedInfo(c *check.C, changeFeedID string) {
   116  	etcd := s.c
   117  	// Delete change feed info
   118  	resp, err := etcd.Delete(s.c.Ctx(), kv.GetEtcdKeyChangeFeedInfo(changeFeedID), clientv3.WithPrefix())
   119  	c.Assert(err, check.IsNil)
   120  	c.Assert(resp, check.NotNil)
   121  
   122  	// Delete change feed status(job status)
   123  	resp, err = etcd.Delete(s.c.Ctx(), kv.GetEtcdKeyJob(changeFeedID), clientv3.WithPrefix())
   124  	c.Assert(err, check.IsNil)
   125  	c.Assert(resp, check.NotNil)
   126  }
   127  
   128  func (s *taskSuite) TestParseTask(c *check.C) {
   129  	defer testleak.AfterTest(c)()
   130  	defer s.TearDownTest(c)
   131  	ctx, cancel := context.WithCancel(context.Background())
   132  	defer cancel()
   133  	changeFeedID := "task-suite-changefeed"
   134  	s.setupFeedInfo(c, changeFeedID)
   135  	defer s.teardownFeedInfo(c, changeFeedID)
   136  
   137  	tests := []struct {
   138  		Desc     string
   139  		Key      []byte
   140  		Expected *Task
   141  	}{
   142  		{"nil task key", nil, nil},
   143  		{"short task key", []byte("test"), nil},
   144  		{
   145  			"normal task key",
   146  			[]byte(kv.GetEtcdKeyTaskStatus(changeFeedID, s.w.capture.info.ID)),
   147  			&Task{changeFeedID, 1},
   148  		},
   149  	}
   150  	for _, t := range tests {
   151  		c.Log("testing ", t.Desc)
   152  		task, err := s.w.parseTask(ctx, t.Key)
   153  		if t.Expected == nil {
   154  			c.Assert(err, check.NotNil)
   155  			c.Assert(task, check.IsNil)
   156  		} else {
   157  			c.Assert(task, check.DeepEquals, t.Expected)
   158  		}
   159  	}
   160  }
   161  
   162  func (s *taskSuite) TestWatch(c *check.C) {
   163  	defer testleak.AfterTest(c)()
   164  	defer s.TearDownTest(c)
   165  	ctx, cancel := context.WithCancel(context.Background())
   166  	defer cancel()
   167  	client := kv.NewCDCEtcdClient(ctx, s.c)
   168  	defer client.Close() //nolint:errcheck
   169  
   170  	s.setupFeedInfo(c, "changefeed-1")
   171  	defer s.teardownFeedInfo(c, "changefeed-1")
   172  
   173  	// Watch with a canceled context
   174  	failedCtx, cancel := context.WithCancel(context.Background())
   175  	cancel()
   176  	ev := <-s.w.Watch(failedCtx)
   177  	if ev != nil {
   178  		c.Assert(ev.Err, check.NotNil)
   179  	}
   180  
   181  	// Watch with a normal context
   182  	ch := s.w.Watch(context.Background())
   183  
   184  	// Trigger the ErrCompacted error
   185  	c.Assert(failpoint.Enable("github.com/pingcap/ticdc/cdc.restart_task_watch", "50%off"), check.IsNil)
   186  
   187  	// Put task changefeed-1
   188  	c.Assert(client.PutTaskStatus(s.c.Ctx(), "changefeed-1",
   189  		s.w.capture.info.ID,
   190  		&model.TaskStatus{}), check.IsNil)
   191  	ev = <-ch
   192  	c.Assert(len(ch), check.Equals, 0)
   193  	c.Assert(ev, check.NotNil)
   194  	c.Assert(ev.Err, check.IsNil)
   195  	c.Assert(ev.Op, check.Equals, TaskOpCreate)
   196  	c.Assert(ev.Task.ChangeFeedID, check.Equals, "changefeed-1")
   197  	c.Assert(ev.Task.CheckpointTS, check.Equals, uint64(1))
   198  
   199  	// Stop the task changefeed-1
   200  	c.Assert(client.PutTaskStatus(s.c.Ctx(), "changefeed-1",
   201  		s.w.capture.info.ID,
   202  		&model.TaskStatus{AdminJobType: model.AdminStop}), check.IsNil)
   203  	ev = <-ch
   204  	c.Assert(len(ch), check.Equals, 0)
   205  	c.Assert(ev, check.NotNil)
   206  	c.Assert(ev.Err, check.IsNil)
   207  	c.Assert(ev.Op, check.Equals, TaskOpDelete)
   208  	c.Assert(ev.Task.ChangeFeedID, check.Equals, "changefeed-1")
   209  	c.Assert(ev.Task.CheckpointTS, check.Equals, uint64(1))
   210  
   211  	// Resume the task changefeed-1
   212  	c.Assert(client.PutTaskStatus(s.c.Ctx(), "changefeed-1",
   213  		s.w.capture.info.ID,
   214  		&model.TaskStatus{AdminJobType: model.AdminResume}), check.IsNil)
   215  	ev = <-ch
   216  	c.Assert(len(ch), check.Equals, 0)
   217  	c.Assert(ev, check.NotNil)
   218  	c.Assert(ev.Err, check.IsNil)
   219  	c.Assert(ev.Op, check.Equals, TaskOpCreate)
   220  	c.Assert(ev.Task.ChangeFeedID, check.Equals, "changefeed-1")
   221  	c.Assert(ev.Task.CheckpointTS, check.Equals, uint64(1))
   222  
   223  	// Delete the task changefeed-1
   224  	c.Assert(client.DeleteTaskStatus(ctx, "changefeed-1",
   225  		s.w.capture.info.ID), check.IsNil)
   226  	ev = <-ch
   227  	c.Assert(len(ch), check.Equals, 0)
   228  	c.Assert(ev, check.NotNil)
   229  	c.Assert(ev.Err, check.IsNil)
   230  	c.Assert(ev.Op, check.Equals, TaskOpDelete)
   231  	c.Assert(ev.Task.ChangeFeedID, check.Equals, "changefeed-1")
   232  	c.Assert(ev.Task.CheckpointTS, check.Equals, uint64(1))
   233  
   234  	// Put task changefeed-2 which does not exist
   235  	c.Assert(client.PutTaskStatus(s.c.Ctx(), "changefeed-2",
   236  		s.w.capture.info.ID,
   237  		&model.TaskStatus{}), check.IsNil)
   238  	c.Assert(len(ch), check.Equals, 0)
   239  }
   240  
   241  func (s *taskSuite) TestRebuildTaskEvents(c *check.C) {
   242  	defer testleak.AfterTest(c)()
   243  	defer s.TearDownTest(c)
   244  	type T map[string]*TaskEvent
   245  	tests := []struct {
   246  		desc     string
   247  		outdated T
   248  		latest   T
   249  		expected T
   250  	}{
   251  		{
   252  			desc:     "nil outdated",
   253  			outdated: nil,
   254  			latest:   T{"changefeed-1": &TaskEvent{TaskOpCreate, &Task{"changeed-1", 0}, nil}},
   255  			expected: T{"changefeed-1": &TaskEvent{TaskOpCreate, &Task{"changeed-1", 0}, nil}},
   256  		},
   257  		{
   258  			desc:     "empty outdated",
   259  			outdated: nil,
   260  			latest:   T{"changefeed-1": &TaskEvent{TaskOpCreate, &Task{"changeed-1", 0}, nil}},
   261  			expected: T{"changefeed-1": &TaskEvent{TaskOpCreate, &Task{"changeed-1", 0}, nil}},
   262  		},
   263  		{
   264  			desc:     "need to be updated",
   265  			outdated: T{"changefeed-1": &TaskEvent{TaskOpCreate, &Task{"changeed-1", 0}, nil}},
   266  			latest:   T{"changefeed-1": &TaskEvent{TaskOpDelete, &Task{"changeed-1", 0}, nil}},
   267  			expected: T{"changefeed-1": &TaskEvent{TaskOpDelete, &Task{"changeed-1", 0}, nil}},
   268  		},
   269  		{
   270  			desc:     "miss some events",
   271  			outdated: T{"changefeed-1": &TaskEvent{TaskOpCreate, &Task{"changeed-1", 0}, nil}},
   272  			latest: T{
   273  				"changefeed-1": &TaskEvent{TaskOpDelete, &Task{"changeed-1", 0}, nil},
   274  				"changefeed-2": &TaskEvent{TaskOpCreate, &Task{"changefeed-2", 0}, nil},
   275  			},
   276  			expected: T{
   277  				"changefeed-1": &TaskEvent{TaskOpDelete, &Task{"changeed-1", 0}, nil},
   278  				"changefeed-2": &TaskEvent{TaskOpCreate, &Task{"changefeed-2", 0}, nil},
   279  			},
   280  		},
   281  		{
   282  			desc: "left some events",
   283  			outdated: T{
   284  				"changefeed-1": &TaskEvent{TaskOpDelete, &Task{"changeed-1", 0}, nil},
   285  				"changefeed-2": &TaskEvent{TaskOpCreate, &Task{"changefeed-2", 0}, nil},
   286  			},
   287  			latest: T{"changefeed-1": &TaskEvent{TaskOpCreate, &Task{"changeed-1", 0}, nil}},
   288  			expected: T{
   289  				"changefeed-1": &TaskEvent{TaskOpCreate, &Task{"changeed-1", 0}, nil},
   290  				"changefeed-2": &TaskEvent{TaskOpDelete, &Task{"changefeed-2", 0}, nil},
   291  			},
   292  		},
   293  	}
   294  
   295  	for _, t := range tests {
   296  		c.Log("RUN CASE: ", t.desc)
   297  		s.w.events = t.outdated
   298  		got := s.w.rebuildTaskEvents(t.latest)
   299  		c.Assert(len(got), check.Equals, len(t.expected))
   300  		for k, v := range got {
   301  			e := t.expected[k]
   302  			c.Assert(v.Err, check.IsNil)
   303  			c.Assert(v.Op, check.Equals, e.Op)
   304  			c.Assert(v.Task, check.DeepEquals, e.Task)
   305  		}
   306  	}
   307  }