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  }