github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/pkg/etcd/etcd.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 etcd 15 16 import ( 17 "bytes" 18 "context" 19 "fmt" 20 "net/url" 21 "strings" 22 "time" 23 24 "github.com/pingcap/log" 25 "github.com/pingcap/tiflow/cdc/model" 26 "github.com/pingcap/tiflow/pkg/config" 27 "github.com/pingcap/tiflow/pkg/errors" 28 "github.com/prometheus/client_golang/prometheus" 29 "github.com/tikv/pd/pkg/utils/tempurl" 30 "go.etcd.io/etcd/api/v3/mvccpb" 31 "go.etcd.io/etcd/api/v3/v3rpc/rpctypes" 32 clientv3 "go.etcd.io/etcd/client/v3" 33 "go.etcd.io/etcd/client/v3/concurrency" 34 "go.etcd.io/etcd/server/v3/embed" 35 "go.uber.org/zap" 36 "google.golang.org/grpc/codes" 37 ) 38 39 // DefaultCDCClusterID is the default value of cdc cluster id 40 const DefaultCDCClusterID = "default" 41 42 // CaptureOwnerKey is the capture owner path that is saved to etcd 43 func CaptureOwnerKey(clusterID string) string { 44 return BaseKey(clusterID) + metaPrefix + "/owner" 45 } 46 47 // CaptureInfoKeyPrefix is the capture info path that is saved to etcd 48 func CaptureInfoKeyPrefix(clusterID string) string { 49 return BaseKey(clusterID) + metaPrefix + captureKey 50 } 51 52 // TaskPositionKeyPrefix is the prefix of task position keys 53 func TaskPositionKeyPrefix(clusterID, namespace string) string { 54 return NamespacedPrefix(clusterID, namespace) + taskPositionKey 55 } 56 57 // ChangefeedStatusKeyPrefix is the prefix of changefeed status keys 58 func ChangefeedStatusKeyPrefix(clusterID, namespace string) string { 59 return NamespacedPrefix(clusterID, namespace) + ChangefeedStatusKey 60 } 61 62 // GetEtcdKeyChangeFeedList returns the prefix key of all changefeed config 63 func GetEtcdKeyChangeFeedList(clusterID, namespace string) string { 64 return fmt.Sprintf("%s/changefeed/info", NamespacedPrefix(clusterID, namespace)) 65 } 66 67 // GetEtcdKeyChangeFeedInfo returns the key of a changefeed config 68 func GetEtcdKeyChangeFeedInfo(clusterID string, changefeedID model.ChangeFeedID) string { 69 return fmt.Sprintf("%s/%s", GetEtcdKeyChangeFeedList(clusterID, 70 changefeedID.Namespace), changefeedID.ID) 71 } 72 73 // GetEtcdKeyTaskPosition returns the key of a task position 74 func GetEtcdKeyTaskPosition(clusterID string, 75 changefeedID model.ChangeFeedID, 76 captureID string, 77 ) string { 78 return TaskPositionKeyPrefix(clusterID, changefeedID.Namespace) + 79 "/" + captureID + "/" + changefeedID.ID 80 } 81 82 // GetEtcdKeyCaptureInfo returns the key of a capture info 83 func GetEtcdKeyCaptureInfo(clusterID, id string) string { 84 return CaptureInfoKeyPrefix(clusterID) + "/" + id 85 } 86 87 // GetEtcdKeyJob returns the key for a job status 88 func GetEtcdKeyJob(clusterID string, changeFeedID model.ChangeFeedID) string { 89 return ChangefeedStatusKeyPrefix(clusterID, changeFeedID.Namespace) + "/" + changeFeedID.ID 90 } 91 92 // MigrateBackupKey is the key of backup data during a migration. 93 func MigrateBackupKey(version int, backupKey string) string { 94 if strings.HasPrefix(backupKey, "/") { 95 return fmt.Sprintf("%s/%d%s", migrateBackupPrefix, version, backupKey) 96 } 97 return fmt.Sprintf("%s/%d/%s", migrateBackupPrefix, version, backupKey) 98 } 99 100 // OwnerCaptureInfoClient is the sub interface of CDCEtcdClient that used for get owner capture information 101 type OwnerCaptureInfoClient interface { 102 GetOwnerID(context.Context) (model.CaptureID, error) 103 104 GetOwnerRevision(context.Context, model.CaptureID) (int64, error) 105 106 GetCaptures(context.Context) (int64, []*model.CaptureInfo, error) 107 } 108 109 // CDCEtcdClient extracts CDCEtcdClients's method used for apiv2. 110 type CDCEtcdClient interface { 111 OwnerCaptureInfoClient 112 113 GetClusterID() string 114 115 GetEtcdClient() *Client 116 117 GetAllCDCInfo(ctx context.Context) ([]*mvccpb.KeyValue, error) 118 119 GetChangeFeedInfo(ctx context.Context, 120 id model.ChangeFeedID, 121 ) (*model.ChangeFeedInfo, error) 122 123 GetAllChangeFeedInfo(ctx context.Context) ( 124 map[model.ChangeFeedID]*model.ChangeFeedInfo, error, 125 ) 126 127 GetChangeFeedStatus(ctx context.Context, 128 id model.ChangeFeedID, 129 ) (*model.ChangeFeedStatus, int64, error) 130 131 GetUpstreamInfo(ctx context.Context, 132 upstreamID model.UpstreamID, 133 namespace string, 134 ) (*model.UpstreamInfo, error) 135 136 GetGCServiceID() string 137 138 GetEnsureGCServiceID(tag string) string 139 140 SaveChangeFeedInfo(ctx context.Context, 141 info *model.ChangeFeedInfo, 142 changeFeedID model.ChangeFeedID, 143 ) error 144 145 CreateChangefeedInfo(context.Context, 146 *model.UpstreamInfo, 147 *model.ChangeFeedInfo, 148 ) error 149 150 UpdateChangefeedAndUpstream(ctx context.Context, 151 upstreamInfo *model.UpstreamInfo, 152 changeFeedInfo *model.ChangeFeedInfo, 153 ) error 154 155 PutCaptureInfo(context.Context, *model.CaptureInfo, clientv3.LeaseID) error 156 157 DeleteCaptureInfo(context.Context, model.CaptureID) error 158 159 CheckMultipleCDCClusterExist(ctx context.Context) error 160 } 161 162 // CDCEtcdClientImpl is a wrap of etcd client 163 type CDCEtcdClientImpl struct { 164 Client *Client 165 ClusterID string 166 etcdClusterID uint64 167 } 168 169 var _ CDCEtcdClient = (*CDCEtcdClientImpl)(nil) 170 171 // NewCDCEtcdClient returns a new CDCEtcdClient 172 func NewCDCEtcdClient(ctx context.Context, 173 cli *clientv3.Client, 174 clusterID string, 175 ) (*CDCEtcdClientImpl, error) { 176 metrics := map[string]prometheus.Counter{ 177 EtcdPut: etcdRequestCounter.WithLabelValues(EtcdPut), 178 EtcdGet: etcdRequestCounter.WithLabelValues(EtcdGet), 179 EtcdDel: etcdRequestCounter.WithLabelValues(EtcdDel), 180 EtcdTxn: etcdRequestCounter.WithLabelValues(EtcdTxn), 181 EtcdGrant: etcdRequestCounter.WithLabelValues(EtcdGrant), 182 EtcdRevoke: etcdRequestCounter.WithLabelValues(EtcdRevoke), 183 } 184 resp, err := cli.MemberList(ctx) 185 if err != nil { 186 return nil, errors.Trace(err) 187 } 188 return &CDCEtcdClientImpl{ 189 etcdClusterID: resp.Header.ClusterId, 190 Client: Wrap(cli, metrics), 191 ClusterID: clusterID, 192 }, nil 193 } 194 195 // Close releases resources in CDCEtcdClient 196 func (c *CDCEtcdClientImpl) Close() error { 197 return c.Client.Unwrap().Close() 198 } 199 200 // ClearAllCDCInfo delete all keys created by CDC 201 func (c *CDCEtcdClientImpl) ClearAllCDCInfo(ctx context.Context) error { 202 _, err := c.Client.Delete(ctx, BaseKey(c.ClusterID), clientv3.WithPrefix()) 203 return errors.WrapError(errors.ErrPDEtcdAPIError, err) 204 } 205 206 // GetClusterID gets CDC cluster ID. 207 func (c *CDCEtcdClientImpl) GetClusterID() string { 208 return c.ClusterID 209 } 210 211 // GetEtcdClient gets Client. 212 func (c *CDCEtcdClientImpl) GetEtcdClient() *Client { 213 return c.Client 214 } 215 216 // GetAllCDCInfo get all keys created by CDC 217 func (c *CDCEtcdClientImpl) GetAllCDCInfo(ctx context.Context) ([]*mvccpb.KeyValue, error) { 218 resp, err := c.Client.Get(ctx, BaseKey(c.ClusterID), clientv3.WithPrefix()) 219 if err != nil { 220 return nil, errors.WrapError(errors.ErrPDEtcdAPIError, err) 221 } 222 return resp.Kvs, nil 223 } 224 225 // CheckMultipleCDCClusterExist checks if other cdc clusters exists, 226 // and returns an error is so, and user should use --server instead 227 func (c *CDCEtcdClientImpl) CheckMultipleCDCClusterExist(ctx context.Context) error { 228 resp, err := c.Client.Get(ctx, BaseKey(""), 229 clientv3.WithPrefix(), 230 clientv3.WithKeysOnly()) 231 if err != nil { 232 return errors.WrapError(errors.ErrPDEtcdAPIError, err) 233 } 234 for _, kv := range resp.Kvs { 235 key := string(kv.Key) 236 if strings.HasPrefix(key, BaseKey(DefaultCDCClusterID)) || 237 strings.HasPrefix(key, migrateBackupPrefix) { 238 continue 239 } 240 // skip the reserved cluster id 241 isReserved := false 242 for _, reserved := range config.ReservedClusterIDs { 243 if strings.HasPrefix(key, BaseKey(reserved)) { 244 isReserved = true 245 break 246 } 247 } 248 if isReserved { 249 continue 250 } 251 return errors.ErrMultipleCDCClustersExist.GenWithStackByArgs() 252 } 253 return nil 254 } 255 256 // GetChangeFeeds returns kv revision and a map mapping from changefeedID to changefeed detail mvccpb.KeyValue 257 func (c *CDCEtcdClientImpl) GetChangeFeeds(ctx context.Context) ( 258 int64, 259 map[model.ChangeFeedID]*mvccpb.KeyValue, error, 260 ) { 261 // todo: support namespace 262 key := GetEtcdKeyChangeFeedList(c.ClusterID, model.DefaultNamespace) 263 264 resp, err := c.Client.Get(ctx, key, clientv3.WithPrefix()) 265 if err != nil { 266 return 0, nil, errors.WrapError(errors.ErrPDEtcdAPIError, err) 267 } 268 revision := resp.Header.Revision 269 details := make(map[model.ChangeFeedID]*mvccpb.KeyValue, resp.Count) 270 for _, kv := range resp.Kvs { 271 id, err := extractKeySuffix(string(kv.Key)) 272 if err != nil { 273 return 0, nil, err 274 } 275 details[model.DefaultChangeFeedID(id)] = kv 276 } 277 return revision, details, nil 278 } 279 280 // GetAllChangeFeedInfo queries all changefeed information 281 func (c *CDCEtcdClientImpl) GetAllChangeFeedInfo(ctx context.Context) ( 282 map[model.ChangeFeedID]*model.ChangeFeedInfo, error, 283 ) { 284 _, details, err := c.GetChangeFeeds(ctx) 285 if err != nil { 286 return nil, errors.Trace(err) 287 } 288 allFeedInfo := make(map[model.ChangeFeedID]*model.ChangeFeedInfo, len(details)) 289 for id, rawDetail := range details { 290 info := &model.ChangeFeedInfo{} 291 if err := info.Unmarshal(rawDetail.Value); err != nil { 292 return nil, errors.Trace(err) 293 } 294 allFeedInfo[id] = info 295 } 296 297 return allFeedInfo, nil 298 } 299 300 // GetChangeFeedInfo queries the config of a given changefeed 301 func (c *CDCEtcdClientImpl) GetChangeFeedInfo(ctx context.Context, 302 id model.ChangeFeedID, 303 ) (*model.ChangeFeedInfo, error) { 304 key := GetEtcdKeyChangeFeedInfo(c.ClusterID, id) 305 resp, err := c.Client.Get(ctx, key) 306 if err != nil { 307 return nil, errors.WrapError(errors.ErrPDEtcdAPIError, err) 308 } 309 if resp.Count == 0 { 310 return nil, errors.ErrChangeFeedNotExists.GenWithStackByArgs(key) 311 } 312 detail := &model.ChangeFeedInfo{} 313 err = detail.Unmarshal(resp.Kvs[0].Value) 314 return detail, errors.Trace(err) 315 } 316 317 // DeleteChangeFeedInfo deletes a changefeed config from etcd 318 func (c *CDCEtcdClientImpl) DeleteChangeFeedInfo(ctx context.Context, 319 id model.ChangeFeedID, 320 ) error { 321 key := GetEtcdKeyChangeFeedInfo(c.ClusterID, id) 322 _, err := c.Client.Delete(ctx, key) 323 return errors.WrapError(errors.ErrPDEtcdAPIError, err) 324 } 325 326 // GetChangeFeedStatus queries the checkpointTs and resovledTs of a given changefeed 327 func (c *CDCEtcdClientImpl) GetChangeFeedStatus(ctx context.Context, 328 id model.ChangeFeedID, 329 ) (*model.ChangeFeedStatus, int64, error) { 330 key := GetEtcdKeyJob(c.ClusterID, id) 331 resp, err := c.Client.Get(ctx, key) 332 if err != nil { 333 return nil, 0, errors.WrapError(errors.ErrPDEtcdAPIError, err) 334 } 335 if resp.Count == 0 { 336 return nil, 0, errors.ErrChangeFeedNotExists.GenWithStackByArgs(key) 337 } 338 info := &model.ChangeFeedStatus{} 339 err = info.Unmarshal(resp.Kvs[0].Value) 340 return info, resp.Kvs[0].ModRevision, errors.Trace(err) 341 } 342 343 // GetCaptures returns kv revision and CaptureInfo list 344 func (c *CDCEtcdClientImpl) GetCaptures(ctx context.Context) (int64, []*model.CaptureInfo, error) { 345 key := CaptureInfoKeyPrefix(c.ClusterID) 346 347 resp, err := c.Client.Get(ctx, key, clientv3.WithPrefix()) 348 if err != nil { 349 return 0, nil, errors.WrapError(errors.ErrPDEtcdAPIError, err) 350 } 351 revision := resp.Header.Revision 352 infos := make([]*model.CaptureInfo, 0, resp.Count) 353 for _, kv := range resp.Kvs { 354 info := &model.CaptureInfo{} 355 err := info.Unmarshal(kv.Value) 356 if err != nil { 357 return 0, nil, errors.Trace(err) 358 } 359 infos = append(infos, info) 360 } 361 return revision, infos, nil 362 } 363 364 // GetCaptureInfo get capture info from etcd. 365 // return ErrCaptureNotExist if the capture not exists. 366 func (c *CDCEtcdClientImpl) GetCaptureInfo( 367 ctx context.Context, id string, 368 ) (info *model.CaptureInfo, err error) { 369 key := GetEtcdKeyCaptureInfo(c.ClusterID, id) 370 371 resp, err := c.Client.Get(ctx, key) 372 if err != nil { 373 return nil, errors.WrapError(errors.ErrPDEtcdAPIError, err) 374 } 375 376 if len(resp.Kvs) == 0 { 377 return nil, errors.ErrCaptureNotExist.GenWithStackByArgs(key) 378 } 379 380 info = new(model.CaptureInfo) 381 err = info.Unmarshal(resp.Kvs[0].Value) 382 if err != nil { 383 return nil, errors.Trace(err) 384 } 385 386 return 387 } 388 389 // GetCaptureLeases returns a map mapping from capture ID to its lease 390 func (c *CDCEtcdClientImpl) GetCaptureLeases(ctx context.Context) (map[string]int64, error) { 391 key := CaptureInfoKeyPrefix(c.ClusterID) 392 393 resp, err := c.Client.Get(ctx, key, clientv3.WithPrefix()) 394 if err != nil { 395 return nil, errors.WrapError(errors.ErrPDEtcdAPIError, err) 396 } 397 leases := make(map[string]int64, resp.Count) 398 for _, kv := range resp.Kvs { 399 captureID, err := extractKeySuffix(string(kv.Key)) 400 if err != nil { 401 return nil, err 402 } 403 leases[captureID] = kv.Lease 404 } 405 return leases, nil 406 } 407 408 // RevokeAllLeases revokes all leases passed from parameter 409 func (c *CDCEtcdClientImpl) RevokeAllLeases(ctx context.Context, leases map[string]int64) error { 410 for _, lease := range leases { 411 _, err := c.Client.Revoke(ctx, clientv3.LeaseID(lease)) 412 if err == nil { 413 continue 414 } else if etcdErr := err.(rpctypes.EtcdError); etcdErr.Code() == codes.NotFound { 415 // it means the etcd lease is already expired or revoked 416 continue 417 } 418 return errors.WrapError(errors.ErrPDEtcdAPIError, err) 419 } 420 return nil 421 } 422 423 // CreateChangefeedInfo creates a change feed info into etcd and fails if it is already exists. 424 func (c *CDCEtcdClientImpl) CreateChangefeedInfo( 425 ctx context.Context, upstreamInfo *model.UpstreamInfo, info *model.ChangeFeedInfo, 426 ) error { 427 return c.saveChangefeedAndUpstreamInfo(ctx, "Create", upstreamInfo, info) 428 } 429 430 // UpdateChangefeedAndUpstream updates the changefeed's info and its upstream info into etcd 431 func (c *CDCEtcdClientImpl) UpdateChangefeedAndUpstream( 432 ctx context.Context, upstreamInfo *model.UpstreamInfo, changeFeedInfo *model.ChangeFeedInfo, 433 ) error { 434 return c.saveChangefeedAndUpstreamInfo(ctx, "Update", upstreamInfo, changeFeedInfo) 435 } 436 437 // saveChangefeedAndUpstreamInfo stores changefeed info and its upstream info into etcd 438 func (c *CDCEtcdClientImpl) saveChangefeedAndUpstreamInfo( 439 ctx context.Context, operation string, 440 upstreamInfo *model.UpstreamInfo, info *model.ChangeFeedInfo, 441 ) error { 442 cmps := []clientv3.Cmp{} 443 opsThen := []clientv3.Op{} 444 445 if upstreamInfo != nil { 446 if info.UpstreamID != upstreamInfo.ID { 447 return errors.ErrUpstreamMissMatch.GenWithStackByArgs(info.UpstreamID, upstreamInfo.ID) 448 } 449 upstreamInfoKey := CDCKey{ 450 Tp: CDCKeyTypeUpStream, 451 ClusterID: c.ClusterID, 452 UpstreamID: upstreamInfo.ID, 453 Namespace: info.Namespace, 454 } 455 upstreamEtcdKeyStr := upstreamInfoKey.String() 456 upstreamResp, err := c.Client.Get(ctx, upstreamEtcdKeyStr) 457 if err != nil { 458 return errors.WrapError(errors.ErrPDEtcdAPIError, err) 459 } 460 upstreamData, err := upstreamInfo.Marshal() 461 if err != nil { 462 return errors.WrapError(errors.ErrPDEtcdAPIError, err) 463 } 464 465 if len(upstreamResp.Kvs) == 0 { 466 cmps = append(cmps, clientv3.Compare(clientv3.ModRevision(upstreamEtcdKeyStr), "=", 0)) 467 opsThen = append(opsThen, clientv3.OpPut(upstreamEtcdKeyStr, string(upstreamData))) 468 } else { 469 cmps = append(cmps, 470 clientv3.Compare(clientv3.ModRevision(upstreamEtcdKeyStr), "=", upstreamResp.Kvs[0].ModRevision)) 471 if !bytes.Equal(upstreamResp.Kvs[0].Value, upstreamData) { 472 opsThen = append(opsThen, clientv3.OpPut(upstreamEtcdKeyStr, string(upstreamData))) 473 } 474 } 475 } 476 477 changeFeedID := model.ChangeFeedID{ 478 Namespace: info.Namespace, 479 ID: info.ID, 480 } 481 infoKey := GetEtcdKeyChangeFeedInfo(c.ClusterID, changeFeedID) 482 jobKey := GetEtcdKeyJob(c.ClusterID, changeFeedID) 483 infoData, err := info.Marshal() 484 if err != nil { 485 return errors.Trace(err) 486 } 487 488 var infoModRevsion, jobModRevision int64 489 if operation == "Update" { 490 infoResp, err := c.Client.Get(ctx, infoKey) 491 if err != nil { 492 return errors.WrapError(errors.ErrPDEtcdAPIError, err) 493 } 494 if len(infoResp.Kvs) == 0 { 495 return errors.ErrChangeFeedNotExists.GenWithStackByArgs(infoKey) 496 } 497 infoModRevsion = infoResp.Kvs[0].ModRevision 498 499 jobResp, err := c.Client.Get(ctx, jobKey) 500 if err != nil { 501 return errors.WrapError(errors.ErrPDEtcdAPIError, err) 502 } 503 if len(jobResp.Kvs) == 0 { 504 // Note that status may not exist, so we don't check it here. 505 log.Debug("job status not exists", zap.Stringer("changefeed", changeFeedID)) 506 } else { 507 jobModRevision = jobResp.Kvs[0].ModRevision 508 } 509 } 510 511 cmps = append(cmps, 512 clientv3.Compare(clientv3.ModRevision(infoKey), "=", infoModRevsion), 513 clientv3.Compare(clientv3.ModRevision(jobKey), "=", jobModRevision), 514 ) 515 opsThen = append(opsThen, clientv3.OpPut(infoKey, infoData)) 516 517 resp, err := c.Client.Txn(ctx, cmps, opsThen, TxnEmptyOpsElse) 518 if err != nil { 519 return errors.WrapError(errors.ErrPDEtcdAPIError, err) 520 } 521 if !resp.Succeeded { 522 log.Warn(fmt.Sprintf("unexpected etcd transaction failure, operation: %s", operation), 523 zap.String("namespace", changeFeedID.Namespace), 524 zap.String("changefeed", changeFeedID.ID)) 525 errMsg := fmt.Sprintf("%s changefeed %s", operation, changeFeedID) 526 return errors.ErrMetaOpFailed.GenWithStackByArgs(errMsg) 527 } 528 return errors.Trace(err) 529 } 530 531 // SaveChangeFeedInfo stores change feed info into etcd 532 // TODO: this should be called from outer system, such as from a TiDB client 533 func (c *CDCEtcdClientImpl) SaveChangeFeedInfo(ctx context.Context, 534 info *model.ChangeFeedInfo, 535 changeFeedID model.ChangeFeedID, 536 ) error { 537 key := GetEtcdKeyChangeFeedInfo(c.ClusterID, changeFeedID) 538 value, err := info.Marshal() 539 if err != nil { 540 return errors.Trace(err) 541 } 542 _, err = c.Client.Put(ctx, key, value) 543 return errors.WrapError(errors.ErrPDEtcdAPIError, err) 544 } 545 546 // PutCaptureInfo put capture info into etcd, 547 // this happens when the capture starts. 548 func (c *CDCEtcdClientImpl) PutCaptureInfo( 549 ctx context.Context, info *model.CaptureInfo, leaseID clientv3.LeaseID, 550 ) error { 551 data, err := info.Marshal() 552 if err != nil { 553 return errors.Trace(err) 554 } 555 556 key := GetEtcdKeyCaptureInfo(c.ClusterID, info.ID) 557 _, err = c.Client.Put(ctx, key, string(data), clientv3.WithLease(leaseID)) 558 return errors.WrapError(errors.ErrPDEtcdAPIError, err) 559 } 560 561 // DeleteCaptureInfo delete all capture related info from etcd. 562 func (c *CDCEtcdClientImpl) DeleteCaptureInfo(ctx context.Context, captureID string) error { 563 key := GetEtcdKeyCaptureInfo(c.ClusterID, captureID) 564 _, err := c.Client.Delete(ctx, key) 565 if err != nil { 566 return errors.WrapError(errors.ErrPDEtcdAPIError, err) 567 } 568 // we need to clean all task position related to this capture when the capture is offline 569 // otherwise the task positions may leak 570 // FIXME (dongmen 2022.9.28): find a way to use changefeed's namespace 571 taskKey := TaskPositionKeyPrefix(c.ClusterID, model.DefaultNamespace) 572 // the taskKey format is /tidb/cdc/{clusterID}/{namespace}/task/position/{captureID} 573 taskKey = fmt.Sprintf("%s/%s", taskKey, captureID) 574 _, err = c.Client.Delete(ctx, taskKey, clientv3.WithPrefix()) 575 if err != nil { 576 log.Warn("delete task position failed", 577 zap.String("clusterID", c.ClusterID), 578 zap.String("captureID", captureID), 579 zap.String("key", key), zap.Error(err)) 580 } 581 return errors.WrapError(errors.ErrPDEtcdAPIError, err) 582 } 583 584 // GetOwnerID returns the owner id by querying etcd 585 func (c *CDCEtcdClientImpl) GetOwnerID(ctx context.Context) (string, error) { 586 resp, err := c.Client.Get(ctx, CaptureOwnerKey(c.ClusterID), 587 clientv3.WithFirstCreate()...) 588 if err != nil { 589 return "", errors.WrapError(errors.ErrPDEtcdAPIError, err) 590 } 591 if len(resp.Kvs) == 0 { 592 return "", concurrency.ErrElectionNoLeader 593 } 594 return string(resp.Kvs[0].Value), nil 595 } 596 597 // GetOwnerRevision gets the Etcd revision for the elected owner. 598 func (c *CDCEtcdClientImpl) GetOwnerRevision( 599 ctx context.Context, captureID string, 600 ) (rev int64, err error) { 601 resp, err := c.Client.Get(ctx, CaptureOwnerKey(c.ClusterID), clientv3.WithFirstCreate()...) 602 if err != nil { 603 return 0, errors.WrapError(errors.ErrPDEtcdAPIError, err) 604 } 605 if len(resp.Kvs) == 0 { 606 return 0, errors.ErrOwnerNotFound.GenWithStackByArgs() 607 } 608 // Checks that the given capture is indeed the owner. 609 if string(resp.Kvs[0].Value) != captureID { 610 return 0, errors.ErrNotOwner.GenWithStackByArgs() 611 } 612 return resp.Kvs[0].ModRevision, nil 613 } 614 615 // GetGCServiceID returns the cdc gc service ID 616 func (c *CDCEtcdClientImpl) GetGCServiceID() string { 617 return fmt.Sprintf("ticdc-%s-%d", c.ClusterID, c.etcdClusterID) 618 } 619 620 // GetEnsureGCServiceID return the prefix for the gc service id when changefeed is creating 621 func (c *CDCEtcdClientImpl) GetEnsureGCServiceID(tag string) string { 622 return c.GetGCServiceID() + tag 623 } 624 625 // GetUpstreamInfo get a upstreamInfo from etcd server 626 func (c *CDCEtcdClientImpl) GetUpstreamInfo(ctx context.Context, 627 upstreamID model.UpstreamID, 628 namespace string, 629 ) (*model.UpstreamInfo, error) { 630 Key := CDCKey{ 631 Tp: CDCKeyTypeUpStream, 632 ClusterID: c.ClusterID, 633 UpstreamID: upstreamID, 634 Namespace: namespace, 635 } 636 KeyStr := Key.String() 637 resp, err := c.Client.Get(ctx, KeyStr) 638 if err != nil { 639 return nil, errors.WrapError(errors.ErrPDEtcdAPIError, err) 640 } 641 if resp.Count == 0 { 642 return nil, errors.ErrUpstreamNotFound.GenWithStackByArgs(KeyStr) 643 } 644 info := &model.UpstreamInfo{} 645 err = info.Unmarshal(resp.Kvs[0].Value) 646 return info, errors.Trace(err) 647 } 648 649 // GcServiceIDForTest returns the gc service ID for tests 650 func GcServiceIDForTest() string { 651 return fmt.Sprintf("ticdc-%s-%d", "default", 0) 652 } 653 654 // getFreeListenURLs get free ports and localhost as url. 655 func getFreeListenURLs(n int) (urls []*url.URL, retErr error) { 656 for i := 0; i < n; i++ { 657 u, err := url.Parse(tempurl.Alloc()) 658 if err != nil { 659 retErr = errors.Trace(err) 660 return 661 } 662 urls = append(urls, u) 663 } 664 665 return 666 } 667 668 // SetupEmbedEtcd starts an embed etcd server 669 func SetupEmbedEtcd(dir string) (clientURL *url.URL, e *embed.Etcd, err error) { 670 cfg := embed.NewConfig() 671 cfg.Dir = dir 672 673 urls, err := getFreeListenURLs(2) 674 if err != nil { 675 return 676 } 677 cfg.ListenPeerUrls = []url.URL{*urls[0]} 678 cfg.ListenClientUrls = []url.URL{*urls[1]} 679 cfg.Logger = "zap" 680 cfg.LogLevel = "error" 681 clientURL = urls[1] 682 683 e, err = embed.StartEtcd(cfg) 684 if err != nil { 685 return 686 } 687 688 select { 689 case <-e.Server.ReadyNotify(): 690 case <-time.After(60 * time.Second): 691 e.Server.Stop() // trigger a shutdown 692 err = errors.New("server took too long to start") 693 } 694 695 return 696 } 697 698 // extractKeySuffix extracts the suffix of an etcd key, such as extracting 699 // "6a6c6dd290bc8732" from /tidb/cdc/cluster/namespace/changefeed/info/6a6c6dd290bc8732 700 func extractKeySuffix(key string) (string, error) { 701 subs := strings.Split(key, "/") 702 if len(subs) < 2 { 703 return "", errors.ErrInvalidEtcdKey.GenWithStackByArgs(key) 704 } 705 return subs[len(subs)-1], nil 706 }