github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/cdc/api/util.go (about) 1 // Copyright 2021 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 api 15 16 import ( 17 "bufio" 18 "context" 19 "encoding/json" 20 "net/http" 21 "strconv" 22 "strings" 23 24 "github.com/gin-gonic/gin" 25 "github.com/pingcap/errors" 26 "github.com/pingcap/log" 27 "github.com/pingcap/tiflow/cdc/capture" 28 "github.com/pingcap/tiflow/cdc/model" 29 "github.com/pingcap/tiflow/cdc/scheduler" 30 "github.com/pingcap/tiflow/pkg/config" 31 cerror "github.com/pingcap/tiflow/pkg/errors" 32 "github.com/pingcap/tiflow/pkg/httputil" 33 "go.uber.org/zap" 34 ) 35 36 // httpBadRequestError is some errors that will cause a BadRequestError in http handler 37 var httpBadRequestError = []*errors.Error{ 38 cerror.ErrAPIInvalidParam, cerror.ErrSinkURIInvalid, cerror.ErrStartTsBeforeGC, 39 cerror.ErrChangeFeedNotExists, cerror.ErrTargetTsBeforeStartTs, cerror.ErrTableIneligible, 40 cerror.ErrFilterRuleInvalid, cerror.ErrChangefeedUpdateRefused, cerror.ErrMySQLConnectionError, 41 cerror.ErrMySQLInvalidConfig, cerror.ErrCaptureNotExist, cerror.ErrSchedulerRequestFailed, 42 } 43 44 const ( 45 // OpVarAdminJob is the key of admin job in HTTP API 46 OpVarAdminJob = "admin-job" 47 // OpVarChangefeedID is the key of changefeed ID in HTTP API 48 OpVarChangefeedID = "cf-id" 49 // OpVarTargetCaptureID is the key of to-capture ID in HTTP API 50 OpVarTargetCaptureID = "target-cp-id" 51 // OpVarTableID is the key of table ID in HTTP API 52 OpVarTableID = "table-id" 53 54 // APIOpVarChangefeedState is the key of changefeed state in HTTP API. 55 APIOpVarChangefeedState = "state" 56 // APIOpVarChangefeedID is the key of changefeed ID in HTTP API. 57 APIOpVarChangefeedID = "changefeed_id" 58 // APIOpVarCaptureID is the key of capture ID in HTTP API. 59 APIOpVarCaptureID = "capture_id" 60 // APIOpVarNamespace is the key of changefeed namespace in HTTP API. 61 APIOpVarNamespace = "namespace" 62 // APIOpVarTiCDCUser is the key of ticdc user in HTTP API. 63 APIOpVarTiCDCUser = "user" 64 // APIOpVarTiCDCPassword is the key of ticdc password in HTTP API. 65 APIOpVarTiCDCPassword = "password" 66 67 // forwardFromCapture is a header to be set when forwarding requests to owner 68 forwardFromCapture = "TiCDC-ForwardFromCapture" 69 // forwardTimes is a header to identify how many times the request has been forwarded 70 forwardTimes = "TiCDC-ForwardTimes" 71 // maxForwardTimes is the max time a request can be forwarded, non-controller->controller->changefeed owner 72 maxForwardTimes = 2 73 ) 74 75 // IsHTTPBadRequestError check if a error is a http bad request error 76 func IsHTTPBadRequestError(err error) bool { 77 if err == nil { 78 return false 79 } 80 for _, e := range httpBadRequestError { 81 if e.Equal(err) { 82 return true 83 } 84 85 rfcCode, ok := cerror.RFCCode(err) 86 if ok && e.RFCCode() == rfcCode { 87 return true 88 } 89 90 if strings.Contains(err.Error(), string(e.RFCCode())) { 91 return true 92 } 93 } 94 return false 95 } 96 97 // WriteError write error message to response 98 func WriteError(w http.ResponseWriter, statusCode int, err error) { 99 w.WriteHeader(statusCode) 100 _, err = w.Write([]byte(err.Error())) 101 if err != nil { 102 log.Error("write error", zap.Error(err)) 103 } 104 } 105 106 // WriteData write data to response with http status code 200 107 func WriteData(w http.ResponseWriter, data interface{}) { 108 js, err := json.MarshalIndent(data, "", " ") 109 if err != nil { 110 log.Error("invalid json data", zap.Any("data", data), zap.Error(err)) 111 WriteError(w, http.StatusInternalServerError, err) 112 return 113 } 114 w.Header().Set("Content-Type", "application/json") 115 w.WriteHeader(http.StatusOK) 116 _, err = w.Write(js) 117 if err != nil { 118 log.Error("fail to write data", zap.Error(err)) 119 } 120 } 121 122 // HandleOwnerJob enqueue the admin job 123 func HandleOwnerJob( 124 ctx context.Context, capture capture.Capture, job model.AdminJob, 125 ) error { 126 // Use buffered channel to prevent blocking owner from happening. 127 done := make(chan error, 1) 128 o, err := capture.GetOwner() 129 if err != nil { 130 return errors.Trace(err) 131 } 132 o.EnqueueJob(job, done) 133 select { 134 case <-ctx.Done(): 135 return errors.Trace(ctx.Err()) 136 case err := <-done: 137 return errors.Trace(err) 138 } 139 } 140 141 // HandleOwnerBalance balance the changefeed tables 142 func HandleOwnerBalance( 143 ctx context.Context, capture capture.Capture, changefeedID model.ChangeFeedID, 144 ) error { 145 // Use buffered channel to prevernt blocking owner. 146 done := make(chan error, 1) 147 o, err := capture.GetOwner() 148 if err != nil { 149 return errors.Trace(err) 150 } 151 o.RebalanceTables(changefeedID, done) 152 select { 153 case <-ctx.Done(): 154 return errors.Trace(ctx.Err()) 155 case err := <-done: 156 return errors.Trace(err) 157 } 158 } 159 160 // HandleOwnerScheduleTable schedule tables 161 func HandleOwnerScheduleTable( 162 ctx context.Context, capture capture.Capture, 163 changefeedID model.ChangeFeedID, captureID string, tableID int64, 164 ) error { 165 // Use buffered channel to prevent blocking owner. 166 done := make(chan error, 1) 167 o, err := capture.GetOwner() 168 if err != nil { 169 return errors.Trace(err) 170 } 171 o.ScheduleTable(changefeedID, captureID, tableID, done) 172 select { 173 case <-ctx.Done(): 174 return errors.Trace(ctx.Err()) 175 case err := <-done: 176 return errors.Trace(err) 177 } 178 } 179 180 // ForwardToController forwards a request to the controller 181 func ForwardToController(c *gin.Context, p capture.Capture) { 182 ctx := c.Request.Context() 183 info, err := p.Info() 184 if err != nil { 185 _ = c.Error(err) 186 return 187 } 188 189 var controller *model.CaptureInfo 190 // get controller info 191 controller, err = p.GetControllerCaptureInfo(ctx) 192 if err != nil { 193 log.Info("get controller failed", zap.Error(err)) 194 _ = c.Error(err) 195 return 196 } 197 ForwardToCapture(c, info.ID, controller.AdvertiseAddr) 198 } 199 200 // ForwardToCapture forward request to another 201 func ForwardToCapture(c *gin.Context, fromID, toAddr string) { 202 ctx := c.Request.Context() 203 204 timeStr := c.GetHeader(forwardTimes) 205 var ( 206 err error 207 lastForwardTimes uint64 208 ) 209 if len(timeStr) != 0 { 210 lastForwardTimes, err = strconv.ParseUint(timeStr, 10, 64) 211 if err != nil { 212 _ = c.Error(cerror.ErrRequestForwardErr.FastGenByArgs()) 213 return 214 } 215 if lastForwardTimes > maxForwardTimes { 216 _ = c.Error(cerror.ErrRequestForwardErr.FastGenByArgs()) 217 return 218 } 219 } 220 221 security := config.GetGlobalServerConfig().Security 222 223 // init a request 224 req, err := http.NewRequestWithContext( 225 ctx, c.Request.Method, c.Request.RequestURI, c.Request.Body) 226 if err != nil { 227 _ = c.Error(err) 228 return 229 } 230 231 req.URL.Host = toAddr 232 // we should check tls config instead of security here because 233 // security will never be nil 234 if tls, _ := security.ToTLSConfigWithVerify(); tls != nil { 235 req.URL.Scheme = "https" 236 } else { 237 req.URL.Scheme = "http" 238 } 239 for k, v := range c.Request.Header { 240 for _, vv := range v { 241 req.Header.Add(k, vv) 242 } 243 } 244 log.Info("forwarding request to capture", 245 zap.String("url", c.Request.RequestURI), 246 zap.String("method", c.Request.Method), 247 zap.String("fromID", fromID), 248 zap.String("toAddr", toAddr), 249 zap.String("forwardTimes", timeStr)) 250 251 req.Header.Add(forwardFromCapture, fromID) 252 lastForwardTimes++ 253 req.Header.Add(forwardTimes, strconv.Itoa(int(lastForwardTimes))) 254 // forward toAddr owner 255 cli, err := httputil.NewClient(security) 256 if err != nil { 257 _ = c.Error(err) 258 return 259 } 260 resp, err := cli.Do(req) 261 if err != nil { 262 _ = c.Error(err) 263 return 264 } 265 266 // write header 267 for k, values := range resp.Header { 268 for _, v := range values { 269 c.Header(k, v) 270 } 271 } 272 273 // write status code 274 c.Status(resp.StatusCode) 275 276 // write response body 277 defer resp.Body.Close() 278 _, err = bufio.NewReader(resp.Body).WriteTo(c.Writer) 279 if err != nil { 280 _ = c.Error(err) 281 return 282 } 283 } 284 285 // HandleOwnerDrainCapture schedule drain the target capture 286 func HandleOwnerDrainCapture( 287 ctx context.Context, capture capture.Capture, captureID string, 288 ) (*model.DrainCaptureResp, error) { 289 // Use buffered channel to prevent blocking owner. 290 done := make(chan error, 1) 291 o, err := capture.GetOwner() 292 if err != nil { 293 return nil, errors.Trace(err) 294 } 295 296 query := scheduler.Query{ 297 CaptureID: captureID, 298 } 299 300 o.DrainCapture(&query, done) 301 302 select { 303 case <-ctx.Done(): 304 err = ctx.Err() 305 case err = <-done: 306 } 307 308 return query.Resp.(*model.DrainCaptureResp), errors.Trace(err) 309 }