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 }