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 }