github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/cdc/api/owner/owner.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 owner
    15  
    16  import (
    17  	"context"
    18  	"encoding/json"
    19  	"io"
    20  	"net/http"
    21  	"strconv"
    22  	"time"
    23  
    24  	"github.com/gin-gonic/gin"
    25  	"github.com/pingcap/errors"
    26  	"github.com/pingcap/log"
    27  	"github.com/pingcap/tiflow/cdc/api"
    28  	"github.com/pingcap/tiflow/cdc/api/middleware"
    29  	"github.com/pingcap/tiflow/cdc/capture"
    30  	"github.com/pingcap/tiflow/cdc/model"
    31  	cerror "github.com/pingcap/tiflow/pkg/errors"
    32  	"github.com/pingcap/tiflow/pkg/logutil"
    33  	"github.com/tikv/client-go/v2/oracle"
    34  	"go.etcd.io/etcd/client/v3/concurrency"
    35  	"go.uber.org/zap"
    36  )
    37  
    38  type commonResp struct {
    39  	Status  bool   `json:"status"`
    40  	Message string `json:"message"`
    41  }
    42  
    43  // ChangefeedResp holds the most common usage information for a changefeed
    44  type ChangefeedResp struct {
    45  	FeedState    string              `json:"state"`
    46  	TSO          uint64              `json:"tso"`
    47  	Checkpoint   string              `json:"checkpoint"`
    48  	RunningError *model.RunningError `json:"error"`
    49  }
    50  
    51  // MarshalJSON use to marshal ChangefeedResp
    52  func (c ChangefeedResp) MarshalJSON() ([]byte, error) {
    53  	// alias the original type to prevent recursive call of MarshalJSON
    54  	type Alias ChangefeedResp
    55  	if c.FeedState == string(model.StateNormal) {
    56  		c.RunningError = nil
    57  	}
    58  	return json.Marshal(struct {
    59  		Alias
    60  	}{
    61  		Alias: Alias(c),
    62  	})
    63  }
    64  
    65  // ownerAPI provides owner APIs.
    66  type ownerAPI struct {
    67  	capture capture.Capture
    68  }
    69  
    70  // RegisterOwnerAPIRoutes registers routes for owner APIs.
    71  func RegisterOwnerAPIRoutes(router *gin.Engine, capture capture.Capture) {
    72  	ownerAPI := ownerAPI{capture: capture}
    73  	owner := router.Group("/capture/owner")
    74  
    75  	owner.Use(middleware.ErrorHandleMiddleware())
    76  	owner.Use(middleware.LogMiddleware())
    77  
    78  	owner.POST("/resign", gin.WrapF(ownerAPI.handleResignOwner))
    79  	owner.POST("/admin", gin.WrapF(ownerAPI.handleChangefeedAdmin))
    80  	owner.POST("/rebalance_trigger", gin.WrapF(ownerAPI.handleRebalanceTrigger))
    81  	owner.POST("/move_table", gin.WrapF(ownerAPI.handleMoveTable))
    82  	owner.POST("/changefeed/query", gin.WrapF(ownerAPI.handleChangefeedQuery))
    83  }
    84  
    85  func handleOwnerResp(w http.ResponseWriter, err error) {
    86  	if err != nil {
    87  		if errors.Cause(err) == concurrency.ErrElectionNotLeader {
    88  			api.WriteError(w, http.StatusBadRequest, err)
    89  			return
    90  		}
    91  		api.WriteError(w, http.StatusInternalServerError, err)
    92  		return
    93  	}
    94  	api.WriteData(w, commonResp{Status: true})
    95  }
    96  
    97  func (h *ownerAPI) handleResignOwner(w http.ResponseWriter, req *http.Request) {
    98  	if h.capture == nil {
    99  		// for test only
   100  		handleOwnerResp(w, concurrency.ErrElectionNotLeader)
   101  		return
   102  	}
   103  	o, err := h.capture.GetOwner()
   104  	if o != nil {
   105  		o.AsyncStop()
   106  	}
   107  	handleOwnerResp(w, err)
   108  }
   109  
   110  func (h *ownerAPI) handleChangefeedAdmin(w http.ResponseWriter, req *http.Request) {
   111  	if h.capture == nil {
   112  		// for test only
   113  		handleOwnerResp(w, concurrency.ErrElectionNotLeader)
   114  		return
   115  	}
   116  
   117  	err := req.ParseForm()
   118  	if err != nil {
   119  		api.WriteError(w, http.StatusInternalServerError, err)
   120  		return
   121  	}
   122  	typeStr := req.Form.Get(api.OpVarAdminJob)
   123  	typ, err := strconv.ParseInt(typeStr, 10, 64)
   124  	if err != nil {
   125  		api.WriteError(w, http.StatusBadRequest,
   126  			cerror.ErrAPIInvalidParam.GenWithStack("invalid admin job type: %s", typeStr))
   127  		return
   128  	}
   129  	job := model.AdminJob{
   130  		CfID: model.DefaultChangeFeedID(req.Form.Get(api.OpVarChangefeedID)),
   131  		Type: model.AdminJobType(typ),
   132  	}
   133  
   134  	err = api.HandleOwnerJob(req.Context(), h.capture, job)
   135  	handleOwnerResp(w, err)
   136  }
   137  
   138  func (h *ownerAPI) handleRebalanceTrigger(w http.ResponseWriter, req *http.Request) {
   139  	if h.capture == nil {
   140  		// for test only
   141  		handleOwnerResp(w, concurrency.ErrElectionNotLeader)
   142  		return
   143  	}
   144  
   145  	err := req.ParseForm()
   146  	if err != nil {
   147  		api.WriteError(w, http.StatusInternalServerError, err)
   148  		return
   149  	}
   150  	changefeedID := model.DefaultChangeFeedID(req.Form.Get(api.OpVarChangefeedID))
   151  	if err := model.ValidateChangefeedID(changefeedID.ID); err != nil {
   152  		api.WriteError(w, http.StatusBadRequest,
   153  			cerror.ErrAPIInvalidParam.GenWithStack("invalid changefeed id: %s", changefeedID.ID))
   154  		return
   155  	}
   156  
   157  	err = api.HandleOwnerBalance(req.Context(), h.capture, changefeedID)
   158  	handleOwnerResp(w, err)
   159  }
   160  
   161  func (h *ownerAPI) handleMoveTable(w http.ResponseWriter, req *http.Request) {
   162  	if h.capture == nil {
   163  		// for test only
   164  		handleOwnerResp(w, concurrency.ErrElectionNotLeader)
   165  		return
   166  	}
   167  
   168  	err := req.ParseForm()
   169  	if err != nil {
   170  		api.WriteError(w, http.StatusInternalServerError,
   171  			cerror.WrapError(cerror.ErrInternalServerError, err))
   172  		return
   173  	}
   174  	changefeedID := model.DefaultChangeFeedID(req.Form.Get(api.OpVarChangefeedID))
   175  	if err := model.ValidateChangefeedID(changefeedID.ID); err != nil {
   176  		api.WriteError(w, http.StatusBadRequest,
   177  			cerror.ErrAPIInvalidParam.GenWithStack("invalid changefeed id: %s", changefeedID.ID))
   178  		return
   179  	}
   180  	to := req.Form.Get(api.OpVarTargetCaptureID)
   181  	if err := model.ValidateChangefeedID(to); err != nil {
   182  		api.WriteError(w, http.StatusBadRequest,
   183  			cerror.ErrAPIInvalidParam.GenWithStack("invalid target capture id: %s", to))
   184  		return
   185  	}
   186  	tableIDStr := req.Form.Get(api.OpVarTableID)
   187  	tableID, err := strconv.ParseInt(tableIDStr, 10, 64)
   188  	if err != nil {
   189  		api.WriteError(w, http.StatusBadRequest,
   190  			cerror.ErrAPIInvalidParam.GenWithStack("invalid tableID: %s", tableIDStr))
   191  		return
   192  	}
   193  
   194  	err = api.HandleOwnerScheduleTable(
   195  		req.Context(), h.capture, changefeedID, to, tableID)
   196  	handleOwnerResp(w, err)
   197  }
   198  
   199  func (h *ownerAPI) handleChangefeedQuery(w http.ResponseWriter, req *http.Request) {
   200  	if h.capture == nil {
   201  		// for test only
   202  		handleOwnerResp(w, concurrency.ErrElectionNotLeader)
   203  		return
   204  	}
   205  
   206  	err := req.ParseForm()
   207  	if err != nil {
   208  		api.WriteError(w, http.StatusInternalServerError, err)
   209  		return
   210  	}
   211  	changefeedID := model.DefaultChangeFeedID(req.Form.Get(api.OpVarChangefeedID))
   212  	if err := model.ValidateChangefeedID(changefeedID.ID); err != nil {
   213  		api.WriteError(w, http.StatusBadRequest,
   214  			cerror.ErrAPIInvalidParam.GenWithStack("invalid changefeed id: %s", changefeedID.ID))
   215  		return
   216  	}
   217  	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
   218  	defer cancel()
   219  	cfInfo, err := h.capture.GetEtcdClient().GetChangeFeedInfo(ctx, changefeedID)
   220  	if err != nil && cerror.ErrChangeFeedNotExists.NotEqual(err) {
   221  		api.WriteError(w, http.StatusBadRequest,
   222  			cerror.ErrAPIInvalidParam.GenWithStack("invalid changefeed id: %s", changefeedID))
   223  		return
   224  	}
   225  	cfStatus, _, err := h.capture.GetEtcdClient().GetChangeFeedStatus(ctx, changefeedID)
   226  	if err != nil && cerror.ErrChangeFeedNotExists.NotEqual(err) {
   227  		api.WriteError(w, http.StatusBadRequest, err)
   228  		return
   229  	}
   230  
   231  	resp := &ChangefeedResp{}
   232  	if cfInfo != nil {
   233  		resp.FeedState = string(cfInfo.State)
   234  		resp.RunningError = cfInfo.Error
   235  	}
   236  	if cfStatus != nil {
   237  		resp.TSO = cfStatus.CheckpointTs
   238  		tm := oracle.GetTimeFromTS(cfStatus.CheckpointTs)
   239  		resp.Checkpoint = tm.Format("2006-01-02 15:04:05.000")
   240  	}
   241  	api.WriteData(w, resp)
   242  }
   243  
   244  // HandleAdminLogLevel handles requests to set the log level.
   245  func HandleAdminLogLevel(w http.ResponseWriter, r *http.Request) {
   246  	var level string
   247  	data, err := io.ReadAll(r.Body)
   248  	r.Body.Close()
   249  	if err != nil {
   250  		api.WriteError(w, http.StatusInternalServerError, err)
   251  		return
   252  	}
   253  	err = json.Unmarshal(data, &level)
   254  	if err != nil {
   255  		api.WriteError(w, http.StatusBadRequest,
   256  			cerror.ErrAPIInvalidParam.GenWithStack("invalid log level: %s", err))
   257  		return
   258  	}
   259  
   260  	err = logutil.SetLogLevel(level)
   261  	if err != nil {
   262  		api.WriteError(w, http.StatusBadRequest,
   263  			cerror.ErrAPIInvalidParam.GenWithStack("fail to change log level: %s", err))
   264  		return
   265  	}
   266  	log.Warn("log level changed", zap.String("level", level))
   267  
   268  	api.WriteData(w, struct{}{})
   269  }