github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/cdc/api/v1/api.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 v1
    15  
    16  import (
    17  	"fmt"
    18  	"net/http"
    19  	"os"
    20  	"sort"
    21  	"strings"
    22  
    23  	"github.com/gin-gonic/gin"
    24  	"github.com/pingcap/log"
    25  	"github.com/pingcap/tiflow/cdc/api"
    26  	"github.com/pingcap/tiflow/cdc/api/middleware"
    27  	"github.com/pingcap/tiflow/cdc/capture"
    28  	"github.com/pingcap/tiflow/cdc/model"
    29  	"github.com/pingcap/tiflow/cdc/owner"
    30  	cerror "github.com/pingcap/tiflow/pkg/errors"
    31  	"github.com/pingcap/tiflow/pkg/logutil"
    32  	"github.com/pingcap/tiflow/pkg/retry"
    33  	"github.com/pingcap/tiflow/pkg/util"
    34  	"github.com/pingcap/tiflow/pkg/version"
    35  	"github.com/tikv/client-go/v2/oracle"
    36  	"go.uber.org/zap"
    37  )
    38  
    39  // OpenAPI provides capture APIs.
    40  type OpenAPI struct {
    41  	capture capture.Capture
    42  	// use for unit test only
    43  	testStatusProvider owner.StatusProvider
    44  }
    45  
    46  // NewOpenAPI creates a new OpenAPI.
    47  func NewOpenAPI(c capture.Capture) OpenAPI {
    48  	return OpenAPI{capture: c}
    49  }
    50  
    51  // NewOpenAPI4Test return a OpenAPI for test
    52  func NewOpenAPI4Test(c capture.Capture, p owner.StatusProvider) OpenAPI {
    53  	return OpenAPI{capture: c, testStatusProvider: p}
    54  }
    55  
    56  func (h *OpenAPI) statusProvider() owner.StatusProvider {
    57  	if h.testStatusProvider != nil {
    58  		return h.testStatusProvider
    59  	}
    60  	return h.capture.StatusProvider()
    61  }
    62  
    63  // RegisterOpenAPIRoutes registers routes for OpenAPI
    64  func RegisterOpenAPIRoutes(router *gin.Engine, api OpenAPI) {
    65  	v1 := router.Group("/api/v1")
    66  
    67  	v1.Use(middleware.CheckServerReadyMiddleware(api.capture))
    68  	v1.Use(middleware.LogMiddleware())
    69  	v1.Use(middleware.ErrorHandleMiddleware())
    70  
    71  	// common API
    72  	v1.GET("/status", api.ServerStatus)
    73  	v1.GET("/health", api.Health)
    74  	v1.POST("/log", SetLogLevel)
    75  
    76  	controllerMiddleware := middleware.ForwardToControllerMiddleware(api.capture)
    77  	changefeedOwnerMiddleware := middleware.
    78  		ForwardToChangefeedOwnerMiddleware(api.capture, getChangefeedFromRequest)
    79  	authenticateMiddleware := middleware.AuthenticateMiddleware(api.capture)
    80  
    81  	// changefeed API
    82  	changefeedGroup := v1.Group("/changefeeds")
    83  	changefeedGroup.GET("", controllerMiddleware, api.ListChangefeed)
    84  	changefeedGroup.GET("/:changefeed_id", changefeedOwnerMiddleware, api.GetChangefeed)
    85  	changefeedGroup.POST("", controllerMiddleware, authenticateMiddleware, api.CreateChangefeed)
    86  	changefeedGroup.PUT("/:changefeed_id", changefeedOwnerMiddleware, authenticateMiddleware, api.UpdateChangefeed)
    87  	changefeedGroup.POST("/:changefeed_id/pause", changefeedOwnerMiddleware, authenticateMiddleware, api.PauseChangefeed)
    88  	changefeedGroup.POST("/:changefeed_id/resume", changefeedOwnerMiddleware, authenticateMiddleware, api.ResumeChangefeed)
    89  	changefeedGroup.DELETE("/:changefeed_id", controllerMiddleware, authenticateMiddleware, api.RemoveChangefeed)
    90  	changefeedGroup.POST("/:changefeed_id/tables/rebalance_table", changefeedOwnerMiddleware, authenticateMiddleware, api.RebalanceTables)
    91  	changefeedGroup.POST("/:changefeed_id/tables/move_table", changefeedOwnerMiddleware, authenticateMiddleware, api.MoveTable)
    92  
    93  	// owner API
    94  	ownerGroup := v1.Group("/owner")
    95  	ownerGroup.POST("/resign", controllerMiddleware, api.ResignController)
    96  
    97  	// processor API
    98  	processorGroup := v1.Group("/processors")
    99  	processorGroup.GET("", controllerMiddleware, api.ListProcessor)
   100  	processorGroup.GET("/:changefeed_id/:capture_id",
   101  		changefeedOwnerMiddleware, api.GetProcessor)
   102  
   103  	// capture API
   104  	captureGroup := v1.Group("/captures")
   105  	captureGroup.Use(controllerMiddleware)
   106  	captureGroup.GET("", api.ListCapture)
   107  	captureGroup.PUT("/drain", api.DrainCapture)
   108  }
   109  
   110  // ListChangefeed lists all changgefeeds in cdc cluster
   111  // @Summary List changefeed
   112  // @Description list all changefeeds in cdc cluster
   113  // @Tags changefeed
   114  // @Accept json
   115  // @Produce json
   116  // @Param state query string false "state"
   117  // @Success 200 {array} model.ChangefeedCommonInfo
   118  // @Failure 500 {object} model.HTTPError
   119  // @Router /api/v1/changefeeds [get]
   120  func (h *OpenAPI) ListChangefeed(c *gin.Context) {
   121  	ctx := c.Request.Context()
   122  	state := c.Query(api.APIOpVarChangefeedState)
   123  	controller, err := h.capture.GetController()
   124  	if err != nil {
   125  		_ = c.Error(err)
   126  		return
   127  	}
   128  	// get all changefeed status
   129  	statuses, err := controller.GetAllChangeFeedCheckpointTs(ctx)
   130  	if err != nil {
   131  		_ = c.Error(err)
   132  		return
   133  	}
   134  	// get all changefeed infos
   135  	infos, err := controller.GetAllChangeFeedInfo(ctx)
   136  	if err != nil {
   137  		// this call will return a parsedError generated by the error we passed in
   138  		// so it is no need to check the parsedError
   139  		_ = c.Error(err)
   140  		return
   141  	}
   142  
   143  	resps := make([]*model.ChangefeedCommonInfo, 0)
   144  	changefeeds := make([]model.ChangeFeedID, 0)
   145  
   146  	for cfID := range infos {
   147  		changefeeds = append(changefeeds, cfID)
   148  	}
   149  	sort.Slice(changefeeds, func(i, j int) bool {
   150  		if changefeeds[i].Namespace == changefeeds[j].Namespace {
   151  			return changefeeds[i].ID < changefeeds[j].ID
   152  		}
   153  
   154  		return changefeeds[i].Namespace < changefeeds[j].Namespace
   155  	})
   156  
   157  	for _, cfID := range changefeeds {
   158  		cfInfo, exist := infos[cfID]
   159  		if !exist {
   160  			// If a changefeed info does not exist, skip it
   161  			continue
   162  		}
   163  
   164  		if !cfInfo.State.IsNeeded(state) {
   165  			continue
   166  		}
   167  
   168  		resp := &model.ChangefeedCommonInfo{
   169  			UpstreamID: cfInfo.UpstreamID,
   170  			Namespace:  cfID.Namespace,
   171  			ID:         cfID.ID,
   172  		}
   173  
   174  		resp.FeedState = cfInfo.State
   175  		resp.RunningError = cfInfo.Error
   176  
   177  		resp.CheckpointTSO = statuses[cfID]
   178  		tm := oracle.GetTimeFromTS(resp.CheckpointTSO)
   179  		resp.CheckpointTime = model.JSONTime(tm)
   180  
   181  		resps = append(resps, resp)
   182  	}
   183  	c.IndentedJSON(http.StatusOK, resps)
   184  }
   185  
   186  // GetChangefeed get detailed info of a changefeed
   187  // @Summary Get changefeed
   188  // @Description get detail information of a changefeed
   189  // @Tags changefeed
   190  // @Accept json
   191  // @Produce json
   192  // @Param changefeed_id  path  string  true  "changefeed_id"
   193  // @Success 200 {object} model.ChangefeedDetail
   194  // @Failure 500,400 {object} model.HTTPError
   195  // @Router /api/v1/changefeeds/{changefeed_id} [get]
   196  func (h *OpenAPI) GetChangefeed(c *gin.Context) {
   197  	ctx := c.Request.Context()
   198  	changefeedID := model.DefaultChangeFeedID(c.Param(api.APIOpVarChangefeedID))
   199  	if err := model.ValidateChangefeedID(changefeedID.ID); err != nil {
   200  		_ = c.Error(cerror.ErrAPIInvalidParam.GenWithStack("invalid changefeed_id: %s",
   201  			changefeedID.ID))
   202  		return
   203  	}
   204  
   205  	info, err := h.statusProvider().GetChangeFeedInfo(ctx, changefeedID)
   206  	if err != nil {
   207  		_ = c.Error(err)
   208  		return
   209  	}
   210  
   211  	status, err := h.statusProvider().GetChangeFeedStatus(ctx, changefeedID)
   212  	if err != nil {
   213  		_ = c.Error(err)
   214  		return
   215  	}
   216  
   217  	taskStatus := make([]model.CaptureTaskStatus, 0)
   218  	if info.State == model.StateNormal {
   219  		processorInfos, err := h.statusProvider().GetAllTaskStatuses(ctx, changefeedID)
   220  		if err != nil {
   221  			_ = c.Error(err)
   222  			return
   223  		}
   224  		for captureID, status := range processorInfos {
   225  			tables := make([]int64, 0)
   226  			for tableID := range status.Tables {
   227  				tables = append(tables, tableID)
   228  			}
   229  			taskStatus = append(taskStatus,
   230  				model.CaptureTaskStatus{
   231  					CaptureID: captureID, Tables: tables,
   232  					Operation: status.Operation,
   233  				})
   234  		}
   235  	}
   236  	sinkURI, err := util.MaskSinkURI(info.SinkURI)
   237  	if err != nil {
   238  		log.Error("failed to mask sink URI", zap.Error(err))
   239  	}
   240  
   241  	changefeedDetail := &model.ChangefeedDetail{
   242  		UpstreamID:     info.UpstreamID,
   243  		Namespace:      changefeedID.Namespace,
   244  		ID:             changefeedID.ID,
   245  		SinkURI:        sinkURI,
   246  		CreateTime:     model.JSONTime(info.CreateTime),
   247  		StartTs:        info.StartTs,
   248  		TargetTs:       info.TargetTs,
   249  		CheckpointTSO:  status.CheckpointTs,
   250  		CheckpointTime: model.JSONTime(oracle.GetTimeFromTS(status.CheckpointTs)),
   251  		ResolvedTs:     status.ResolvedTs,
   252  		Engine:         info.Engine,
   253  		FeedState:      info.State,
   254  		TaskStatus:     taskStatus,
   255  		CreatorVersion: info.CreatorVersion,
   256  	}
   257  
   258  	c.IndentedJSON(http.StatusOK, changefeedDetail)
   259  }
   260  
   261  // CreateChangefeed creates a changefeed
   262  // @Summary Create changefeed
   263  // @Description create a new changefeed
   264  // @Tags changefeed
   265  // @Accept json
   266  // @Produce json
   267  // @Param changefeed body model.ChangefeedConfig true "changefeed config"
   268  // @Success 202
   269  // @Failure 500,400 {object} model.HTTPError
   270  // @Router	/api/v1/changefeeds [post]
   271  func (h *OpenAPI) CreateChangefeed(c *gin.Context) {
   272  	// c does not have a cancel() func and its Done() method always return nil,
   273  	// so we should not use c as a context.
   274  	// Ref:https://github.com/gin-gonic/gin/blob/92eeaa4ebbadec2376e2ca5f5749888da1a42e24/context.go#L1157
   275  	ctx := c.Request.Context()
   276  	var changefeedConfig model.ChangefeedConfig
   277  	if err := c.BindJSON(&changefeedConfig); err != nil {
   278  		_ = c.Error(cerror.ErrAPIInvalidParam.Wrap(err))
   279  		return
   280  	}
   281  
   282  	upManager, err := h.capture.GetUpstreamManager()
   283  	if err != nil {
   284  		_ = c.Error(err)
   285  		return
   286  	}
   287  	up, err := upManager.GetDefaultUpstream()
   288  	if err != nil {
   289  		_ = c.Error(err)
   290  		return
   291  	}
   292  
   293  	ctrl, err := h.capture.GetController()
   294  	if err != nil {
   295  		_ = c.Error(err)
   296  		return
   297  	}
   298  	info, err := verifyCreateChangefeedConfig(ctx, changefeedConfig,
   299  		up, ctrl, h.capture.GetEtcdClient())
   300  	if err != nil {
   301  		_ = c.Error(err)
   302  		return
   303  	}
   304  	upstreamInfo := &model.UpstreamInfo{
   305  		ID:            up.ID,
   306  		PDEndpoints:   strings.Join(up.PdEndpoints, ","),
   307  		KeyPath:       up.SecurityConfig.KeyPath,
   308  		CertPath:      up.SecurityConfig.CertPath,
   309  		CAPath:        up.SecurityConfig.CAPath,
   310  		CertAllowedCN: up.SecurityConfig.CertAllowedCN,
   311  	}
   312  	err = ctrl.CreateChangefeed(
   313  		ctx, upstreamInfo,
   314  		info)
   315  	if err != nil {
   316  		_ = c.Error(err)
   317  		return
   318  	}
   319  
   320  	log.Info("Create changefeed successfully!",
   321  		zap.String("id", changefeedConfig.ID),
   322  		zap.String("changefeed", info.String()))
   323  	c.Status(http.StatusAccepted)
   324  }
   325  
   326  // PauseChangefeed pauses a changefeed
   327  // @Summary Pause a changefeed
   328  // @Description Pause a changefeed
   329  // @Tags changefeed
   330  // @Accept json
   331  // @Produce json
   332  // @Param changefeed_id  path  string  true  "changefeed_id"
   333  // @Success 202
   334  // @Failure 500,400 {object} model.HTTPError
   335  // @Router /api/v1/changefeeds/{changefeed_id}/pause [post]
   336  func (h *OpenAPI) PauseChangefeed(c *gin.Context) {
   337  	ctx := c.Request.Context()
   338  
   339  	changefeedID := model.DefaultChangeFeedID(c.Param(api.APIOpVarChangefeedID))
   340  	if err := model.ValidateChangefeedID(changefeedID.ID); err != nil {
   341  		_ = c.Error(cerror.ErrAPIInvalidParam.GenWithStack("invalid changefeed_id: %s",
   342  			changefeedID.ID))
   343  		return
   344  	}
   345  	// check if the changefeed exists
   346  	_, err := h.statusProvider().GetChangeFeedStatus(ctx, changefeedID)
   347  	if err != nil {
   348  		_ = c.Error(err)
   349  		return
   350  	}
   351  
   352  	job := model.AdminJob{
   353  		CfID: changefeedID,
   354  		Type: model.AdminStop,
   355  	}
   356  
   357  	if err := api.HandleOwnerJob(ctx, h.capture, job); err != nil {
   358  		_ = c.Error(err)
   359  		return
   360  	}
   361  	c.Status(http.StatusAccepted)
   362  }
   363  
   364  // ResumeChangefeed resumes a changefeed
   365  // @Summary Resume a changefeed
   366  // @Description Resume a changefeed
   367  // @Tags changefeed
   368  // @Accept json
   369  // @Produce json
   370  // @Param changefeed_id path string true "changefeed_id"
   371  // @Success 202
   372  // @Failure 500,400 {object} model.HTTPError
   373  // @Router	/api/v1/changefeeds/{changefeed_id}/resume [post]
   374  func (h *OpenAPI) ResumeChangefeed(c *gin.Context) {
   375  	ctx := c.Request.Context()
   376  	changefeedID := model.DefaultChangeFeedID(c.Param(api.APIOpVarChangefeedID))
   377  	if err := model.ValidateChangefeedID(changefeedID.ID); err != nil {
   378  		_ = c.Error(cerror.ErrAPIInvalidParam.GenWithStack("invalid changefeed_id: %s",
   379  			changefeedID.ID))
   380  		return
   381  	}
   382  	// check if the changefeed exists
   383  	_, err := h.capture.StatusProvider().GetChangeFeedStatus(ctx, changefeedID)
   384  	if err != nil {
   385  		_ = c.Error(err)
   386  		return
   387  	}
   388  
   389  	job := model.AdminJob{
   390  		CfID: changefeedID,
   391  		Type: model.AdminResume,
   392  	}
   393  
   394  	if err := api.HandleOwnerJob(ctx, h.capture, job); err != nil {
   395  		_ = c.Error(err)
   396  		return
   397  	}
   398  	c.Status(http.StatusAccepted)
   399  }
   400  
   401  // UpdateChangefeed updates a changefeed
   402  // @Summary Update a changefeed
   403  // @Description Update a changefeed
   404  // @Tags changefeed
   405  // @Accept json
   406  // @Produce json
   407  // @Param changefeed_id  path  string  true  "changefeed_id"
   408  // @Param changefeedConfig body model.ChangefeedConfig true "changefeed config"
   409  // @Success 202
   410  // @Failure 500,400 {object} model.HTTPError
   411  // @Router /api/v1/changefeeds/{changefeed_id} [put]
   412  func (h *OpenAPI) UpdateChangefeed(c *gin.Context) {
   413  	ctx := c.Request.Context()
   414  	changefeedID := model.DefaultChangeFeedID(c.Param(api.APIOpVarChangefeedID))
   415  
   416  	if err := model.ValidateChangefeedID(changefeedID.ID); err != nil {
   417  		_ = c.Error(cerror.ErrAPIInvalidParam.GenWithStack("invalid changefeed_id: %s",
   418  			changefeedID.ID))
   419  		return
   420  	}
   421  
   422  	owner, err := h.capture.GetOwner()
   423  	if err != nil {
   424  		_ = c.Error(err)
   425  		return
   426  	}
   427  
   428  	info, err := h.statusProvider().GetChangeFeedInfo(ctx, changefeedID)
   429  	if err != nil {
   430  		_ = c.Error(err)
   431  		return
   432  	}
   433  
   434  	switch info.State {
   435  	case model.StateFailed, model.StateStopped:
   436  	default:
   437  		_ = c.Error(
   438  			cerror.ErrChangefeedUpdateRefused.GenWithStackByArgs(
   439  				"can only update changefeed config when it is stopped or failed",
   440  			),
   441  		)
   442  		return
   443  	}
   444  
   445  	info.ID = changefeedID.ID
   446  	info.Namespace = changefeedID.Namespace
   447  
   448  	// can only update target-ts, sink-uri
   449  	// filter_rules, ignore_txn_start_ts, mounter_worker_num, sink_config
   450  	var changefeedConfig model.ChangefeedConfig
   451  	if err = c.BindJSON(&changefeedConfig); err != nil {
   452  		_ = c.Error(err)
   453  		return
   454  	}
   455  
   456  	newInfo, err := VerifyUpdateChangefeedConfig(ctx, changefeedConfig, info)
   457  	if err != nil {
   458  		_ = c.Error(err)
   459  		return
   460  	}
   461  
   462  	err = owner.UpdateChangefeed(ctx, newInfo)
   463  	if err != nil {
   464  		_ = c.Error(err)
   465  		return
   466  	}
   467  
   468  	c.Status(http.StatusAccepted)
   469  }
   470  
   471  // RemoveChangefeed removes a changefeed
   472  // @Summary Remove a changefeed
   473  // @Description Remove a changefeed
   474  // @Tags changefeed
   475  // @Accept json
   476  // @Produce json
   477  // @Param changefeed_id path string true "changefeed_id"
   478  // @Success 202
   479  // @Failure 500,400 {object} model.HTTPError
   480  // @Router	/api/v1/changefeeds/{changefeed_id} [delete]
   481  func (h *OpenAPI) RemoveChangefeed(c *gin.Context) {
   482  	ctx := c.Request.Context()
   483  	changefeedID := model.DefaultChangeFeedID(c.Param(api.APIOpVarChangefeedID))
   484  	if err := model.ValidateChangefeedID(changefeedID.ID); err != nil {
   485  		_ = c.Error(cerror.ErrAPIInvalidParam.GenWithStack("invalid changefeed_id: %s",
   486  			changefeedID.ID))
   487  		return
   488  	}
   489  	// check if the changefeed exists
   490  	ctrl, err := h.capture.GetController()
   491  	if err != nil {
   492  		_ = c.Error(err)
   493  		return
   494  	}
   495  	exist, err := ctrl.IsChangefeedExists(ctx, changefeedID)
   496  	if err != nil {
   497  		_ = c.Error(err)
   498  		return
   499  	}
   500  	if !exist {
   501  		_ = c.Error(cerror.ErrChangeFeedNotExists.GenWithStackByArgs(changefeedID))
   502  		return
   503  	}
   504  
   505  	job := model.AdminJob{
   506  		CfID: changefeedID,
   507  		Type: model.AdminRemove,
   508  	}
   509  
   510  	if err := api.HandleOwnerJob(ctx, h.capture, job); err != nil {
   511  		_ = c.Error(err)
   512  		return
   513  	}
   514  
   515  	// Owner needs at least tow ticks to remove a changefeed,
   516  	// we need to wait for it.
   517  	err = retry.Do(ctx, func() error {
   518  		exist, err = ctrl.IsChangefeedExists(ctx, changefeedID)
   519  		if err != nil {
   520  			if strings.Contains(err.Error(), "ErrChangeFeedNotExists") {
   521  				return nil
   522  			}
   523  			return err
   524  		}
   525  		if !exist {
   526  			return nil
   527  		}
   528  		return cerror.ErrChangeFeedDeletionUnfinished.GenWithStackByArgs(changefeedID)
   529  	},
   530  		retry.WithMaxTries(100),         // max retry duration is 1 minute
   531  		retry.WithBackoffBaseDelay(600), // default owner tick interval is 200ms
   532  		retry.WithIsRetryableErr(cerror.IsRetryableError))
   533  
   534  	if err != nil {
   535  		_ = c.Error(err)
   536  		return
   537  	}
   538  
   539  	c.Status(http.StatusAccepted)
   540  }
   541  
   542  // RebalanceTables rebalances tables
   543  // @Summary rebalance tables
   544  // @Description rebalance all tables of a changefeed
   545  // @Tags changefeed
   546  // @Accept json
   547  // @Produce json
   548  // @Param changefeed_id path string true "changefeed_id"
   549  // @Success 202
   550  // @Failure 500,400 {object} model.HTTPError
   551  // @Router /api/v1/changefeeds/{changefeed_id}/tables/rebalance_table [post]
   552  func (h *OpenAPI) RebalanceTables(c *gin.Context) {
   553  	ctx := c.Request.Context()
   554  	changefeedID := model.DefaultChangeFeedID(c.Param(api.APIOpVarChangefeedID))
   555  
   556  	if err := model.ValidateChangefeedID(changefeedID.ID); err != nil {
   557  		_ = c.Error(cerror.ErrAPIInvalidParam.GenWithStack("invalid changefeed_id: %s",
   558  			changefeedID.ID))
   559  		return
   560  	}
   561  	// check if the changefeed exists
   562  	_, err := h.statusProvider().GetChangeFeedStatus(ctx, changefeedID)
   563  	if err != nil {
   564  		_ = c.Error(err)
   565  		return
   566  	}
   567  
   568  	if err := api.HandleOwnerBalance(ctx, h.capture, changefeedID); err != nil {
   569  		_ = c.Error(err)
   570  		return
   571  	}
   572  	c.Status(http.StatusAccepted)
   573  }
   574  
   575  // MoveTable moves a table to target capture
   576  // @Summary move table
   577  // @Description move one table to the target capture
   578  // @Tags changefeed
   579  // @Accept json
   580  // @Produce json
   581  // @Param changefeed_id path string true "changefeed_id"
   582  // @Param MoveTable body model.MoveTableReq true "move table request"
   583  // @Success 202
   584  // @Failure 500,400 {object} model.HTTPError
   585  // @Router /api/v1/changefeeds/{changefeed_id}/tables/move_table [post]
   586  func (h *OpenAPI) MoveTable(c *gin.Context) {
   587  	ctx := c.Request.Context()
   588  	changefeedID := model.DefaultChangeFeedID(c.Param(api.APIOpVarChangefeedID))
   589  	if err := model.ValidateChangefeedID(changefeedID.ID); err != nil {
   590  		_ = c.Error(cerror.ErrAPIInvalidParam.GenWithStack("invalid changefeed_id: %s",
   591  			changefeedID.ID))
   592  		return
   593  	}
   594  	// check if the changefeed exists
   595  	_, err := h.statusProvider().GetChangeFeedStatus(ctx, changefeedID)
   596  	if err != nil {
   597  		_ = c.Error(err)
   598  		return
   599  	}
   600  
   601  	data := model.MoveTableReq{}
   602  	err = c.BindJSON(&data)
   603  	if err != nil {
   604  		_ = c.Error(cerror.ErrAPIInvalidParam.Wrap(err))
   605  		return
   606  	}
   607  
   608  	if err := model.ValidateChangefeedID(data.CaptureID); err != nil {
   609  		_ = c.Error(cerror.ErrAPIInvalidParam.GenWithStack("invalid capture_id: %s", data.CaptureID))
   610  		return
   611  	}
   612  
   613  	err = api.HandleOwnerScheduleTable(
   614  		ctx, h.capture, changefeedID, data.CaptureID, data.TableID)
   615  	if err != nil {
   616  		_ = c.Error(err)
   617  		return
   618  	}
   619  	c.Status(http.StatusAccepted)
   620  }
   621  
   622  // ResignController makes the current controller resign
   623  // @Summary notify the ticdc cluster controller to resign
   624  // @Description notify the current controller to resign
   625  // @Tags owner
   626  // @Accept json
   627  // @Produce json
   628  // @Success 202
   629  // @Failure 500,400 {object} model.HTTPError
   630  // @Router	/api/v1/owner/resign [post]
   631  func (h *OpenAPI) ResignController(c *gin.Context) {
   632  	o, _ := h.capture.GetController()
   633  	if o != nil {
   634  		o.AsyncStop()
   635  	}
   636  
   637  	c.Status(http.StatusAccepted)
   638  }
   639  
   640  // GetProcessor gets the detailed info of a processor
   641  // @Summary Get processor detail information
   642  // @Description get the detail information of a processor
   643  // @Tags processor
   644  // @Accept json
   645  // @Produce json
   646  // @Param   changefeed_id   path    string  true  "changefeed ID"
   647  // @Param   capture_id   path    string  true  "capture ID"
   648  // @Success 200 {object} model.ProcessorDetail
   649  // @Failure 500,400 {object} model.HTTPError
   650  // @Router	/api/v1/processors/{changefeed_id}/{capture_id} [get]
   651  func (h *OpenAPI) GetProcessor(c *gin.Context) {
   652  	ctx := c.Request.Context()
   653  
   654  	changefeedID := model.DefaultChangeFeedID(c.Param(api.APIOpVarChangefeedID))
   655  	if err := model.ValidateChangefeedID(changefeedID.ID); err != nil {
   656  		_ = c.Error(cerror.ErrAPIInvalidParam.GenWithStack("invalid changefeed_id: %s",
   657  			changefeedID.ID))
   658  		return
   659  	}
   660  
   661  	captureID := c.Param(api.APIOpVarCaptureID)
   662  	if err := model.ValidateChangefeedID(captureID); err != nil {
   663  		_ = c.Error(cerror.ErrAPIInvalidParam.GenWithStack("invalid capture_id: %s", captureID))
   664  		return
   665  	}
   666  
   667  	info, err := h.capture.StatusProvider().GetChangeFeedInfo(ctx, changefeedID)
   668  	if err != nil {
   669  		_ = c.Error(err)
   670  		return
   671  	}
   672  	if info.State != model.StateNormal {
   673  		_ = c.Error(cerror.WrapError(cerror.ErrAPIInvalidParam,
   674  			fmt.Errorf("changefeed in abnormal state: %s, "+
   675  				"can't get processors of an abnormal changefeed",
   676  				string(info.State))))
   677  	}
   678  	// check if this captureID exist
   679  	procInfos, err := h.capture.StatusProvider().GetProcessors(ctx)
   680  	if err != nil {
   681  		_ = c.Error(err)
   682  		return
   683  	}
   684  	var found bool
   685  	for _, info := range procInfos {
   686  		if info.CaptureID == captureID {
   687  			found = true
   688  			break
   689  		}
   690  	}
   691  	if !found {
   692  		_ = c.Error(cerror.ErrCaptureNotExist.GenWithStackByArgs(captureID))
   693  		return
   694  	}
   695  
   696  	statuses, err := h.capture.StatusProvider().GetAllTaskStatuses(ctx, changefeedID)
   697  	if err != nil {
   698  		_ = c.Error(err)
   699  		return
   700  	}
   701  	status, captureExist := statuses[captureID]
   702  
   703  	// Note: for the case that no tables are attached to a newly created changefeed,
   704  	//       we just do not report an error.
   705  	var processorDetail model.ProcessorDetail
   706  	if captureExist {
   707  		tables := make([]int64, 0)
   708  		for tableID := range status.Tables {
   709  			tables = append(tables, tableID)
   710  		}
   711  		processorDetail.Tables = tables
   712  	}
   713  	c.IndentedJSON(http.StatusOK, &processorDetail)
   714  }
   715  
   716  // ListProcessor lists all processors in the TiCDC cluster
   717  // @Summary List processors
   718  // @Description list all processors in the TiCDC cluster
   719  // @Tags processor
   720  // @Accept json
   721  // @Produce json
   722  // @Success 200 {array} model.ProcessorCommonInfo
   723  // @Failure 500,400 {object} model.HTTPError
   724  // @Router	/api/v1/processors [get]
   725  func (h *OpenAPI) ListProcessor(c *gin.Context) {
   726  	ctx := c.Request.Context()
   727  	controller, err := h.capture.GetController()
   728  	if err != nil {
   729  		_ = c.Error(err)
   730  		return
   731  	}
   732  	infos, err := controller.GetProcessors(ctx)
   733  	if err != nil {
   734  		_ = c.Error(err)
   735  		return
   736  	}
   737  	resps := make([]*model.ProcessorCommonInfo, len(infos))
   738  	for i, info := range infos {
   739  		resp := &model.ProcessorCommonInfo{
   740  			Namespace: info.CfID.Namespace,
   741  			CfID:      info.CfID.ID,
   742  			CaptureID: info.CaptureID,
   743  		}
   744  		resps[i] = resp
   745  	}
   746  	c.IndentedJSON(http.StatusOK, resps)
   747  }
   748  
   749  // ListCapture lists all captures
   750  // @Summary List captures
   751  // @Description list all captures in cdc cluster
   752  // @Tags capture
   753  // @Accept json
   754  // @Produce json
   755  // @Success 200 {array} model.Capture
   756  // @Failure 500,400 {object} model.HTTPError
   757  // @Router	/api/v1/captures [get]
   758  func (h *OpenAPI) ListCapture(c *gin.Context) {
   759  	ctx := c.Request.Context()
   760  	controller, err := h.capture.GetController()
   761  	if err != nil {
   762  		_ = c.Error(err)
   763  		return
   764  	}
   765  	captureInfos, err := controller.GetCaptures(ctx)
   766  	if err != nil {
   767  		_ = c.Error(err)
   768  		return
   769  	}
   770  	info, err := h.capture.Info()
   771  	if err != nil {
   772  		_ = c.Error(err)
   773  		return
   774  	}
   775  	ownerID := info.ID
   776  
   777  	captures := make([]*model.Capture, 0, len(captureInfos))
   778  	for _, c := range captureInfos {
   779  		isOwner := c.ID == ownerID
   780  		captures = append(captures,
   781  			&model.Capture{
   782  				ID:            c.ID,
   783  				IsOwner:       isOwner,
   784  				AdvertiseAddr: c.AdvertiseAddr,
   785  				ClusterID:     h.capture.GetEtcdClient().GetClusterID(),
   786  			})
   787  	}
   788  
   789  	c.IndentedJSON(http.StatusOK, captures)
   790  }
   791  
   792  // DrainCapture remove all tables at the given capture.
   793  // @Summary Drain captures
   794  // @Description Drain all tables at the target captures in cdc cluster
   795  // @Tags capture
   796  // @Accept json
   797  // @Produce json
   798  // @Success 200,202
   799  // @Failure 503,500,400 {object} model.HTTPError
   800  // @Router	/api/v1/captures/drain [put]
   801  func (h *OpenAPI) DrainCapture(c *gin.Context) {
   802  	var req model.DrainCaptureRequest
   803  	if err := c.ShouldBindJSON(&req); err != nil {
   804  		_ = c.Error(cerror.ErrAPIInvalidParam.Wrap(err))
   805  		return
   806  	}
   807  
   808  	ctx := c.Request.Context()
   809  	captures, err := h.statusProvider().GetCaptures(ctx)
   810  	if err != nil {
   811  		_ = c.Error(err)
   812  		return
   813  	}
   814  
   815  	// drain capture only work if there is at least two alive captures,
   816  	// it cannot work properly if it has only one capture.
   817  	if len(captures) <= 1 {
   818  		_ = c.Error(cerror.ErrSchedulerRequestFailed.
   819  			GenWithStackByArgs("only one capture alive"))
   820  		return
   821  	}
   822  
   823  	target := req.CaptureID
   824  	checkCaptureFound := func() bool {
   825  		// make sure the target capture exist
   826  		for _, capture := range captures {
   827  			if capture.ID == target {
   828  				return true
   829  			}
   830  		}
   831  		return false
   832  	}
   833  
   834  	if !checkCaptureFound() {
   835  		_ = c.Error(cerror.ErrCaptureNotExist.GenWithStackByArgs(target))
   836  		return
   837  	}
   838  
   839  	// only owner handle api request, so this must be the owner.
   840  	ownerInfo, err := h.capture.Info()
   841  	if err != nil {
   842  		_ = c.Error(err)
   843  		return
   844  	}
   845  
   846  	if ownerInfo.ID == target {
   847  		_ = c.Error(cerror.ErrSchedulerRequestFailed.
   848  			GenWithStackByArgs("cannot drain the owner"))
   849  		return
   850  	}
   851  
   852  	resp, err := api.HandleOwnerDrainCapture(ctx, h.capture, target)
   853  	if err != nil {
   854  		_ = c.AbortWithError(http.StatusServiceUnavailable, err)
   855  		return
   856  	}
   857  
   858  	c.JSON(http.StatusAccepted, resp)
   859  }
   860  
   861  // ServerStatus gets the status of server(capture)
   862  // @Summary Get server status
   863  // @Description get the status of a server(capture)
   864  // @Tags common
   865  // @Accept json
   866  // @Produce json
   867  // @Success 200 {object} model.ServerStatus
   868  // @Failure 500,400 {object} model.HTTPError
   869  // @Router	/api/v1/status [get]
   870  func (h *OpenAPI) ServerStatus(c *gin.Context) {
   871  	info, err := h.capture.Info()
   872  	if err != nil {
   873  		_ = c.Error(err)
   874  		return
   875  	}
   876  	status := model.ServerStatus{
   877  		Version:   version.ReleaseVersion,
   878  		GitHash:   version.GitHash,
   879  		Pid:       os.Getpid(),
   880  		ID:        info.ID,
   881  		ClusterID: h.capture.GetEtcdClient().GetClusterID(),
   882  		IsOwner:   h.capture.IsController(),
   883  		Liveness:  h.capture.Liveness(),
   884  	}
   885  	c.IndentedJSON(http.StatusOK, status)
   886  }
   887  
   888  // Health check if cdc cluster is health
   889  // @Summary Check if CDC cluster is health
   890  // @Description check if CDC cluster is health
   891  // @Tags common
   892  // @Accept json
   893  // @Produce json
   894  // @Success 200
   895  // @Failure 500 {object} model.HTTPError
   896  // @Router	/api/v1/health [get]
   897  func (h *OpenAPI) Health(c *gin.Context) {
   898  	if !h.capture.IsController() {
   899  		middleware.ForwardToControllerMiddleware(h.capture)(c)
   900  		return
   901  	}
   902  
   903  	ctx := c.Request.Context()
   904  	health, err := h.statusProvider().IsHealthy(ctx)
   905  	if err != nil {
   906  		c.IndentedJSON(http.StatusInternalServerError, model.NewHTTPError(err))
   907  		return
   908  	}
   909  	if !health {
   910  		err = cerror.ErrClusterIsUnhealthy.FastGenByArgs()
   911  		c.IndentedJSON(http.StatusInternalServerError, model.NewHTTPError(err))
   912  		return
   913  	}
   914  	c.Status(http.StatusOK)
   915  }
   916  
   917  // SetLogLevel changes TiCDC log level dynamically.
   918  // @Summary Change TiCDC log level
   919  // @Description change TiCDC log level dynamically
   920  // @Tags common
   921  // @Accept json
   922  // @Produce json
   923  // @Param log_level body string true "log level"
   924  // @Success 200
   925  // @Failure 400 {object} model.HTTPError
   926  // @Router	/api/v1/log [post]
   927  func SetLogLevel(c *gin.Context) {
   928  	// get json data from request body
   929  	data := struct {
   930  		Level string `json:"log_level"`
   931  	}{}
   932  	err := c.BindJSON(&data)
   933  	if err != nil {
   934  		_ = c.Error(cerror.ErrAPIInvalidParam.GenWithStack("invalid log level: %s", err.Error()))
   935  		return
   936  	}
   937  
   938  	err = logutil.SetLogLevel(data.Level)
   939  	if err != nil {
   940  		_ = c.Error(cerror.ErrAPIInvalidParam.GenWithStack("fail to change log level: %s", data.Level))
   941  		return
   942  	}
   943  	log.Warn("log level changed", zap.String("level", data.Level))
   944  	c.Status(http.StatusOK)
   945  }
   946  
   947  // getChangefeedFromRequest returns the changefeed that parse from request
   948  func getChangefeedFromRequest(ctx *gin.Context) model.ChangeFeedID {
   949  	return model.ChangeFeedID{
   950  		Namespace: model.DefaultNamespace,
   951  		ID:        ctx.Param(api.APIOpVarChangefeedID),
   952  	}
   953  }