github.com/pingcap/ticdc@v0.0.0-20220526033649-485a10ef2652/tests/move_table/main.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  // This is a program that drives the CDC cluster to move a table
    15  package main
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"flag"
    21  	"fmt"
    22  	"io/ioutil"
    23  	"net/http"
    24  	"strings"
    25  	"time"
    26  
    27  	"github.com/pingcap/errors"
    28  	"github.com/pingcap/log"
    29  	"github.com/pingcap/ticdc/cdc/kv"
    30  	cerrors "github.com/pingcap/ticdc/pkg/errors"
    31  	"github.com/pingcap/ticdc/pkg/retry"
    32  	"go.etcd.io/etcd/clientv3"
    33  	"go.etcd.io/etcd/pkg/logutil"
    34  	"go.uber.org/zap"
    35  	"go.uber.org/zap/zapcore"
    36  	"google.golang.org/grpc"
    37  	"google.golang.org/grpc/backoff"
    38  )
    39  
    40  var (
    41  	pd       = flag.String("pd", "http://127.0.0.1:2379", "PD address and port")
    42  	logLevel = flag.String("log-level", "debug", "Set log level of the logger")
    43  )
    44  
    45  func main() {
    46  	flag.Parse()
    47  	if strings.ToLower(*logLevel) == "debug" {
    48  		log.SetLevel(zapcore.DebugLevel)
    49  	}
    50  
    51  	log.Info("table mover started")
    52  	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
    53  	defer cancel()
    54  
    55  	cluster, err := newCluster(ctx, *pd)
    56  	if err != nil {
    57  		log.Fatal("failed to create cluster info", zap.Error(err))
    58  	}
    59  	err = retry.Do(ctx, func() error {
    60  		err := cluster.refreshInfo(ctx)
    61  		if err != nil {
    62  			log.Warn("error refreshing cluster info", zap.Error(err))
    63  		}
    64  
    65  		log.Info("task status", zap.Reflect("status", cluster.captures))
    66  
    67  		if len(cluster.captures) <= 1 {
    68  			return errors.New("too few captures")
    69  		}
    70  		return nil
    71  	}, retry.WithBackoffBaseDelay(100), retry.WithMaxTries(20), retry.WithIsRetryableErr(cerrors.IsRetryableError))
    72  
    73  	if err != nil {
    74  		log.Fatal("Fail to get captures", zap.Error(err))
    75  	}
    76  
    77  	var sourceCapture string
    78  
    79  	for capture, tables := range cluster.captures {
    80  		if len(tables) == 0 {
    81  			continue
    82  		}
    83  		sourceCapture = capture
    84  		break
    85  	}
    86  
    87  	var targetCapture string
    88  
    89  	for candidateCapture := range cluster.captures {
    90  		if candidateCapture != sourceCapture {
    91  			targetCapture = candidateCapture
    92  		}
    93  	}
    94  
    95  	if targetCapture == "" {
    96  		log.Fatal("no target, unexpected")
    97  	}
    98  
    99  	// move all tables to another capture
   100  	for _, table := range cluster.captures[sourceCapture] {
   101  		err = moveTable(ctx, cluster.ownerAddr, table.Changefeed, targetCapture, table.ID)
   102  		if err != nil {
   103  			log.Warn("failed to move table", zap.Error(err))
   104  			continue
   105  		}
   106  
   107  		log.Info("moved table successful", zap.Int64("tableID", table.ID))
   108  	}
   109  
   110  	log.Info("all tables are moved", zap.String("sourceCapture", sourceCapture), zap.String("targetCapture", targetCapture))
   111  
   112  	for counter := 0; counter < 30; counter++ {
   113  		err := retry.Do(ctx, func() error {
   114  			return cluster.refreshInfo(ctx)
   115  		}, retry.WithBackoffBaseDelay(100), retry.WithMaxTries(5+1), retry.WithIsRetryableErr(cerrors.IsRetryableError))
   116  		if err != nil {
   117  			log.Warn("error refreshing cluster info", zap.Error(err))
   118  		}
   119  
   120  		tables, ok := cluster.captures[sourceCapture]
   121  		if !ok {
   122  			log.Warn("source capture is gone", zap.String("sourceCapture", sourceCapture))
   123  			break
   124  		}
   125  
   126  		if len(tables) == 0 {
   127  			log.Info("source capture is now empty", zap.String("sourceCapture", sourceCapture))
   128  			break
   129  		}
   130  
   131  		if counter != 30 {
   132  			log.Debug("source capture is not empty, will try again", zap.String("sourceCapture", sourceCapture))
   133  			time.Sleep(time.Second * 10)
   134  		}
   135  	}
   136  }
   137  
   138  type tableInfo struct {
   139  	ID         int64
   140  	Changefeed string
   141  }
   142  
   143  type cluster struct {
   144  	ownerAddr  string
   145  	captures   map[string][]*tableInfo
   146  	cdcEtcdCli kv.CDCEtcdClient
   147  }
   148  
   149  func newCluster(ctx context.Context, pd string) (*cluster, error) {
   150  	logConfig := logutil.DefaultZapLoggerConfig
   151  	logConfig.Level = zap.NewAtomicLevelAt(zapcore.ErrorLevel)
   152  
   153  	etcdCli, err := clientv3.New(clientv3.Config{
   154  		Endpoints:   []string{pd},
   155  		TLS:         nil,
   156  		Context:     ctx,
   157  		LogConfig:   &logConfig,
   158  		DialTimeout: 5 * time.Second,
   159  		DialOptions: []grpc.DialOption{
   160  			grpc.WithInsecure(),
   161  			grpc.WithBlock(),
   162  			grpc.WithConnectParams(grpc.ConnectParams{
   163  				Backoff: backoff.Config{
   164  					BaseDelay:  time.Second,
   165  					Multiplier: 1.1,
   166  					Jitter:     0.1,
   167  					MaxDelay:   3 * time.Second,
   168  				},
   169  				MinConnectTimeout: 3 * time.Second,
   170  			}),
   171  		},
   172  	})
   173  	if err != nil {
   174  		return nil, errors.Trace(err)
   175  	}
   176  
   177  	ret := &cluster{
   178  		ownerAddr:  "",
   179  		captures:   nil,
   180  		cdcEtcdCli: kv.NewCDCEtcdClient(ctx, etcdCli),
   181  	}
   182  
   183  	log.Info("new cluster initialized")
   184  
   185  	return ret, nil
   186  }
   187  
   188  func (c *cluster) refreshInfo(ctx context.Context) error {
   189  	ownerID, err := c.cdcEtcdCli.GetOwnerID(ctx, kv.CaptureOwnerKey)
   190  	if err != nil {
   191  		return errors.Trace(err)
   192  	}
   193  
   194  	log.Debug("retrieved owner ID", zap.String("ownerID", ownerID))
   195  
   196  	captureInfo, err := c.cdcEtcdCli.GetCaptureInfo(ctx, ownerID)
   197  	if err != nil {
   198  		return errors.Trace(err)
   199  	}
   200  
   201  	log.Debug("retrieved owner addr", zap.String("ownerAddr", captureInfo.AdvertiseAddr))
   202  	c.ownerAddr = captureInfo.AdvertiseAddr
   203  
   204  	_, changefeeds, err := c.cdcEtcdCli.GetChangeFeeds(ctx)
   205  	if err != nil {
   206  		return errors.Trace(err)
   207  	}
   208  	if len(changefeeds) == 0 {
   209  		return errors.New("No changefeed")
   210  	}
   211  
   212  	log.Debug("retrieved changefeeds", zap.Reflect("changefeeds", changefeeds))
   213  
   214  	var changefeed string
   215  	for k := range changefeeds {
   216  		changefeed = k
   217  		break
   218  	}
   219  
   220  	c.captures = make(map[string][]*tableInfo)
   221  	_, captures, err := c.cdcEtcdCli.GetCaptures(ctx)
   222  	if err != nil {
   223  		return errors.Trace(err)
   224  	}
   225  	for _, capture := range captures {
   226  		c.captures[capture.ID] = make([]*tableInfo, 0)
   227  	}
   228  
   229  	allTasks, err := c.cdcEtcdCli.GetAllTaskStatus(ctx, changefeed)
   230  	if err != nil {
   231  		return errors.Trace(err)
   232  	}
   233  
   234  	log.Debug("retrieved all tasks", zap.Reflect("tasks", allTasks))
   235  
   236  	for capture, taskInfo := range allTasks {
   237  		if _, ok := c.captures[capture]; !ok {
   238  			c.captures[capture] = make([]*tableInfo, 0, len(taskInfo.Tables))
   239  		}
   240  
   241  		for tableID := range taskInfo.Tables {
   242  			c.captures[capture] = append(c.captures[capture], &tableInfo{
   243  				ID:         tableID,
   244  				Changefeed: changefeed,
   245  			})
   246  		}
   247  	}
   248  
   249  	return nil
   250  }
   251  
   252  func moveTable(ctx context.Context, ownerAddr string, changefeed string, target string, tableID int64) error {
   253  	formStr := fmt.Sprintf("cf-id=%s&target-cp-id=%s&table-id=%d", changefeed, target, tableID)
   254  	log.Debug("preparing HTTP API call to owner", zap.String("formStr", formStr))
   255  	rd := bytes.NewReader([]byte(formStr))
   256  	req, err := http.NewRequestWithContext(ctx, "POST", "http://"+ownerAddr+"/capture/owner/move_table", rd)
   257  	if err != nil {
   258  		return errors.Trace(err)
   259  	}
   260  
   261  	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
   262  	resp, err := http.DefaultClient.Do(req)
   263  	if err != nil {
   264  		return errors.Trace(err)
   265  	}
   266  
   267  	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
   268  		body, err := ioutil.ReadAll(resp.Body)
   269  		if err != nil {
   270  			return errors.Trace(err)
   271  		}
   272  		log.Warn("http error", zap.ByteString("body", body))
   273  		return errors.New(resp.Status)
   274  	}
   275  
   276  	return nil
   277  }