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 }