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  }