github.com/pingcap/ticdc@v0.0.0-20220526033649-485a10ef2652/cmd/client.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 cmd
    15  
    16  import (
    17  	"context"
    18  	"fmt"
    19  	"io"
    20  	"os"
    21  	"strings"
    22  	"time"
    23  
    24  	"github.com/chzyer/readline"
    25  	_ "github.com/go-sql-driver/mysql" // mysql driver
    26  	"github.com/mattn/go-shellwords"
    27  	"github.com/pingcap/errors"
    28  	"github.com/pingcap/ticdc/cdc"
    29  	"github.com/pingcap/ticdc/cdc/kv"
    30  	"github.com/pingcap/ticdc/cdc/model"
    31  	"github.com/pingcap/ticdc/pkg/logutil"
    32  	"github.com/pingcap/ticdc/pkg/version"
    33  	"github.com/spf13/cobra"
    34  	pd "github.com/tikv/pd/client"
    35  	"go.etcd.io/etcd/clientv3"
    36  	etcdlogutil "go.etcd.io/etcd/pkg/logutil"
    37  	"go.uber.org/zap"
    38  	"go.uber.org/zap/zapcore"
    39  	"google.golang.org/grpc"
    40  	"google.golang.org/grpc/backoff"
    41  )
    42  
    43  func init() {
    44  	cliCmd := newCliCommand()
    45  	cliCmd.PersistentFlags().StringVar(&cliPdAddr, "pd", "http://127.0.0.1:2379", "PD address, use ',' to separate multiple PDs")
    46  	cliCmd.PersistentFlags().BoolVarP(&interact, "interact", "i", false, "Run cdc cli with readline")
    47  	cliCmd.PersistentFlags().StringVar(&cliLogLevel, "log-level", "warn", "log level (etc: debug|info|warn|error)")
    48  	addSecurityFlags(cliCmd.PersistentFlags(), false /* isServer */)
    49  	rootCmd.AddCommand(cliCmd)
    50  }
    51  
    52  var (
    53  	opts       []string
    54  	startTs    uint64
    55  	targetTs   uint64
    56  	sinkURI    string
    57  	configFile string
    58  	cliPdAddr  string
    59  	noConfirm  bool
    60  	sortEngine string
    61  	sortDir    string
    62  	timezone   string
    63  
    64  	cyclicReplicaID        uint64
    65  	cyclicFilterReplicaIDs []uint
    66  	cyclicSyncDDL          bool
    67  	cyclicUpstreamDSN      string
    68  
    69  	cdcEtcdCli kv.CDCEtcdClient
    70  	pdCli      pd.Client
    71  
    72  	interact          bool
    73  	simplified        bool
    74  	cliLogLevel       string
    75  	changefeedListAll bool
    76  
    77  	changefeedID            string
    78  	captureID               string
    79  	interval                uint
    80  	disableGCSafePointCheck bool
    81  
    82  	syncPointEnabled  bool
    83  	syncPointInterval time.Duration
    84  
    85  	optForceRemove bool
    86  
    87  	defaultContext context.Context
    88  )
    89  
    90  // changefeedCommonInfo holds some common used information of a changefeed
    91  type changefeedCommonInfo struct {
    92  	ID      string              `json:"id"`
    93  	Summary *cdc.ChangefeedResp `json:"summary"`
    94  }
    95  
    96  // capture holds capture information
    97  type capture struct {
    98  	ID            string `json:"id"`
    99  	IsOwner       bool   `json:"is-owner"`
   100  	AdvertiseAddr string `json:"address"`
   101  }
   102  
   103  // cfMeta holds changefeed info and changefeed status
   104  type cfMeta struct {
   105  	Info       *model.ChangeFeedInfo   `json:"info"`
   106  	Status     *model.ChangeFeedStatus `json:"status"`
   107  	Count      uint64                  `json:"count"`
   108  	TaskStatus []captureTaskStatus     `json:"task-status"`
   109  }
   110  
   111  type captureTaskStatus struct {
   112  	CaptureID  string            `json:"capture-id"`
   113  	TaskStatus *model.TaskStatus `json:"status"`
   114  }
   115  
   116  type profileStatus struct {
   117  	OPS            uint64 `json:"ops"`
   118  	Count          uint64 `json:"count"`
   119  	SinkGap        string `json:"sink_gap"`
   120  	ReplicationGap string `json:"replication_gap"`
   121  }
   122  
   123  type processorMeta struct {
   124  	Status   *model.TaskStatus   `json:"status"`
   125  	Position *model.TaskPosition `json:"position"`
   126  }
   127  
   128  func newCliCommand() *cobra.Command {
   129  	command := &cobra.Command{
   130  		Use:   "cli",
   131  		Short: "Manage replication task and TiCDC cluster",
   132  		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
   133  			initCmd(cmd, &logutil.Config{Level: cliLogLevel})
   134  
   135  			credential := getCredential()
   136  			tlsConfig, err := credential.ToTLSConfig()
   137  			if err != nil {
   138  				return errors.Annotate(err, "fail to validate TLS settings")
   139  			}
   140  
   141  			if err := verifyPdEndpoint(cliPdAddr, tlsConfig != nil); err != nil {
   142  				return errors.Trace(err)
   143  			}
   144  
   145  			grpcTLSOption, err := credential.ToGRPCDialOption()
   146  			if err != nil {
   147  				return errors.Annotate(err, "fail to validate TLS settings")
   148  			}
   149  
   150  			pdEndpoints := strings.Split(cliPdAddr, ",")
   151  			logConfig := etcdlogutil.DefaultZapLoggerConfig
   152  			logConfig.Level = zap.NewAtomicLevelAt(zapcore.ErrorLevel)
   153  
   154  			logHTTPProxies()
   155  			etcdCli, err := clientv3.New(clientv3.Config{
   156  				Context:     defaultContext,
   157  				Endpoints:   pdEndpoints,
   158  				TLS:         tlsConfig,
   159  				LogConfig:   &logConfig,
   160  				DialTimeout: 30 * time.Second,
   161  				DialOptions: []grpc.DialOption{
   162  					grpcTLSOption,
   163  					grpc.WithBlock(),
   164  					grpc.WithConnectParams(grpc.ConnectParams{
   165  						Backoff: backoff.Config{
   166  							BaseDelay:  time.Second,
   167  							Multiplier: 1.1,
   168  							Jitter:     0.1,
   169  							MaxDelay:   3 * time.Second,
   170  						},
   171  						MinConnectTimeout: 3 * time.Second,
   172  					}),
   173  				},
   174  			})
   175  			if err != nil {
   176  				// PD embeds an etcd server.
   177  				return errors.Annotatef(err, "fail to open PD etcd client, pd=\"%s\"", cliPdAddr)
   178  			}
   179  			cdcEtcdCli = kv.NewCDCEtcdClient(defaultContext, etcdCli)
   180  			pdCli, err = pd.NewClientWithContext(
   181  				defaultContext, pdEndpoints, credential.PDSecurityOption(),
   182  				pd.WithGRPCDialOptions(
   183  					grpcTLSOption,
   184  					grpc.WithBlock(),
   185  					grpc.WithConnectParams(grpc.ConnectParams{
   186  						Backoff: backoff.Config{
   187  							BaseDelay:  time.Second,
   188  							Multiplier: 1.1,
   189  							Jitter:     0.1,
   190  							MaxDelay:   3 * time.Second,
   191  						},
   192  						MinConnectTimeout: 3 * time.Second,
   193  					}),
   194  				))
   195  			if err != nil {
   196  				return errors.Annotatef(err, "fail to open PD client, pd=\"%s\"", cliPdAddr)
   197  			}
   198  			ctx := defaultContext
   199  			errorTiKVIncompatible := true // Error if TiKV is incompatible.
   200  			for _, pdEndpoint := range pdEndpoints {
   201  				err = version.CheckClusterVersion(ctx, pdCli, pdEndpoint, credential, errorTiKVIncompatible)
   202  				if err == nil {
   203  					break
   204  				}
   205  			}
   206  			if err != nil {
   207  				return err
   208  			}
   209  
   210  			return nil
   211  		},
   212  		Run: func(cmd *cobra.Command, args []string) {
   213  			if interact {
   214  				loop()
   215  			}
   216  		},
   217  	}
   218  	command.AddCommand(
   219  		newCaptureCommand(),
   220  		newChangefeedCommand(),
   221  		newProcessorCommand(),
   222  		newUnsafeCommand(),
   223  		newTsoCommand(),
   224  	)
   225  
   226  	return command
   227  }
   228  
   229  func loop() {
   230  	l, err := readline.NewEx(&readline.Config{
   231  		Prompt:            "\033[31m»\033[0m ",
   232  		HistoryFile:       "/tmp/readline.tmp",
   233  		InterruptPrompt:   "^C",
   234  		EOFPrompt:         "^D",
   235  		HistorySearchFold: true,
   236  	})
   237  	if err != nil {
   238  		panic(err)
   239  	}
   240  	defer l.Close()
   241  
   242  	for {
   243  		line, err := l.Readline()
   244  		if err != nil {
   245  			if err == readline.ErrInterrupt {
   246  				break
   247  			} else if err == io.EOF {
   248  				break
   249  			}
   250  			continue
   251  		}
   252  		if line == "exit" {
   253  			os.Exit(0)
   254  		}
   255  		args, err := shellwords.Parse(line)
   256  		if err != nil {
   257  			fmt.Printf("parse command err: %v\n", err)
   258  			continue
   259  		}
   260  
   261  		command := newCliCommand()
   262  		command.SetArgs(args)
   263  		_ = command.ParseFlags(args)
   264  		command.SetOut(os.Stdout)
   265  		command.SetErr(os.Stdout)
   266  		if err = command.Execute(); err != nil {
   267  			command.Println(err)
   268  		}
   269  	}
   270  }