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 }