github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/dm/master/openapi_view.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  // this file implement all of the APIs of the DataMigration service.
    15  
    16  package master
    17  
    18  import (
    19  	"crypto/tls"
    20  	"encoding/json"
    21  	"fmt"
    22  	"net"
    23  	"net/http"
    24  	"net/http/httputil"
    25  	"strconv"
    26  
    27  	ginmiddleware "github.com/deepmap/oapi-codegen/pkg/gin-middleware"
    28  	"github.com/gin-gonic/gin"
    29  	"github.com/pingcap/failpoint"
    30  	"github.com/pingcap/tidb/pkg/util/dbutil"
    31  	"github.com/pingcap/tiflow/dm/config"
    32  	"github.com/pingcap/tiflow/dm/master/workerrpc"
    33  	"github.com/pingcap/tiflow/dm/openapi"
    34  	"github.com/pingcap/tiflow/dm/pb"
    35  	"github.com/pingcap/tiflow/dm/pkg/conn"
    36  	"github.com/pingcap/tiflow/dm/pkg/ha"
    37  	"github.com/pingcap/tiflow/dm/pkg/log"
    38  	"github.com/pingcap/tiflow/dm/pkg/terror"
    39  	"go.uber.org/zap"
    40  )
    41  
    42  const (
    43  	docJSONBasePath = "/api/v1/dm.json"
    44  )
    45  
    46  // reverseRequestToLeaderMW reverses request to leader.
    47  func (s *Server) reverseRequestToLeaderMW(tlsCfg *tls.Config) gin.HandlerFunc {
    48  	return func(c *gin.Context) {
    49  		ctx2 := c.Request.Context()
    50  		isLeader, _ := s.isLeaderAndNeedForward(ctx2)
    51  		if isLeader {
    52  			c.Next()
    53  		} else {
    54  			// nolint:dogsled
    55  			_, _, leaderOpenAPIAddr, err := s.election.LeaderInfo(ctx2)
    56  			if err != nil {
    57  				_ = c.AbortWithError(http.StatusBadRequest, err)
    58  				return
    59  			}
    60  
    61  			failpoint.Inject("MockNotSetTls", func() {
    62  				tlsCfg = nil
    63  			})
    64  			// simpleProxy just reverses to leader host
    65  			simpleProxy := httputil.ReverseProxy{
    66  				Director: func(req *http.Request) {
    67  					if tlsCfg != nil {
    68  						req.URL.Scheme = "https"
    69  					} else {
    70  						req.URL.Scheme = "http"
    71  					}
    72  					req.URL.Host = leaderOpenAPIAddr
    73  					req.Host = leaderOpenAPIAddr
    74  				},
    75  			}
    76  			if tlsCfg != nil {
    77  				transport := http.DefaultTransport.(*http.Transport).Clone()
    78  				transport.TLSClientConfig = tlsCfg
    79  				simpleProxy.Transport = transport
    80  			}
    81  			log.L().Info("reverse request to leader", zap.String("Request URL", c.Request.URL.String()), zap.String("leader", leaderOpenAPIAddr), zap.Bool("hasTLS", tlsCfg != nil))
    82  			simpleProxy.ServeHTTP(c.Writer, c.Request)
    83  			c.Abort()
    84  		}
    85  	}
    86  }
    87  
    88  // InitOpenAPIHandles init openapi handlers.
    89  func (s *Server) InitOpenAPIHandles(tlsCfg *tls.Config) error {
    90  	swagger, err := openapi.GetSwagger()
    91  	if err != nil {
    92  		return err
    93  	}
    94  	// disables swagger server name validation. it seems to work poorly
    95  	swagger.Servers = nil
    96  	gin.SetMode(gin.ReleaseMode)
    97  	r := gin.New()
    98  	// middlewares
    99  	r.Use(gin.Recovery())
   100  	r.Use(openapi.ZapLogger(log.L().WithFields(zap.String("component", "openapi")).Logger))
   101  	r.Use(s.reverseRequestToLeaderMW(tlsCfg))
   102  	r.Use(terrorHTTPErrorHandler())
   103  	// use validation middleware to check all requests against the OpenAPI schema.
   104  	r.Use(ginmiddleware.OapiRequestValidator(swagger))
   105  	// register handlers
   106  	openapi.RegisterHandlers(r, s)
   107  	s.openapiHandles = r
   108  	return nil
   109  }
   110  
   111  // GetDocJSON url is:(GET /api/v1/dm.json).
   112  func (s *Server) GetDocJSON(c *gin.Context) {
   113  	var masterURL string
   114  	if info, err := s.getClusterInfo(c.Request.Context()); err != nil {
   115  		_ = c.Error(err)
   116  		return
   117  	} else if info.Topology != nil && info.Topology.MasterTopologyList != nil && len(*info.Topology.MasterTopologyList) > 0 {
   118  		masterTopos := *info.Topology.MasterTopologyList
   119  		protocol := "http"
   120  		if useTLS.Load() {
   121  			protocol = "https"
   122  		}
   123  		hostPort := net.JoinHostPort(masterTopos[0].Host, strconv.Itoa(masterTopos[0].Port))
   124  		masterURL = fmt.Sprintf("%s://%s", protocol, hostPort)
   125  	}
   126  	swagger, err := openapi.GetSwagger()
   127  	if err != nil {
   128  		_ = c.Error(err)
   129  		return
   130  	} else if masterURL != "" {
   131  		for idx := range swagger.Servers {
   132  			swagger.Servers[idx].URL = masterURL
   133  		}
   134  	}
   135  	c.JSON(http.StatusOK, swagger)
   136  }
   137  
   138  // GetDocHTML url is:(GET /api/v1/docs).
   139  func (s *Server) GetDocHTML(c *gin.Context) {
   140  	html, err := openapi.GetSwaggerHTML(openapi.NewSwaggerConfig(docJSONBasePath, ""))
   141  	if err != nil {
   142  		_ = c.Error(err)
   143  		return
   144  	}
   145  	c.Writer.WriteHeader(http.StatusOK)
   146  	_, err = c.Writer.Write([]byte(html))
   147  	if err != nil {
   148  		_ = c.Error(err)
   149  	}
   150  }
   151  
   152  // DMAPIGetClusterMasterList get cluster master node list url is:(GET /api/v1/cluster/masters).
   153  func (s *Server) DMAPIGetClusterMasterList(c *gin.Context) {
   154  	newCtx := c.Request.Context()
   155  	memberMasters, err := s.listMemberMaster(newCtx, nil)
   156  	if err != nil {
   157  		_ = c.Error(err)
   158  		return
   159  	}
   160  	masterCnt := len(memberMasters.Master.Masters)
   161  	masters := make([]openapi.ClusterMaster, masterCnt)
   162  	for idx, master := range memberMasters.Master.Masters {
   163  		masters[idx] = openapi.ClusterMaster{
   164  			Name:   master.GetName(),
   165  			Alive:  master.GetAlive(),
   166  			Addr:   master.GetPeerURLs()[0],
   167  			Leader: master.GetName() == s.cfg.Name, // only leader can handle request
   168  		}
   169  	}
   170  	resp := &openapi.GetClusterMasterListResponse{Total: masterCnt, Data: masters}
   171  	c.IndentedJSON(http.StatusOK, resp)
   172  }
   173  
   174  // DMAPIOfflineMasterNode offline master node url is: (DELETE /api/v1/cluster/masters/{master-name}).
   175  func (s *Server) DMAPIOfflineMasterNode(c *gin.Context, masterName string) {
   176  	newCtx := c.Request.Context()
   177  	if err := s.deleteMasterByName(newCtx, masterName); err != nil {
   178  		_ = c.Error(err)
   179  		return
   180  	}
   181  	c.Status(http.StatusNoContent)
   182  }
   183  
   184  // DMAPIGetClusterWorkerList get cluster worker node list url is: (GET /api/v1/cluster/workers).
   185  func (s *Server) DMAPIGetClusterWorkerList(c *gin.Context) {
   186  	memberWorkers := s.listMemberWorker(nil)
   187  	workerCnt := len(memberWorkers.Worker.Workers)
   188  	workers := make([]openapi.ClusterWorker, workerCnt)
   189  	for idx, worker := range memberWorkers.Worker.Workers {
   190  		workers[idx] = openapi.ClusterWorker{
   191  			Name:            worker.GetName(),
   192  			Addr:            worker.GetAddr(),
   193  			BoundSourceName: worker.GetSource(),
   194  			BoundStage:      worker.GetStage(),
   195  		}
   196  	}
   197  	resp := &openapi.GetClusterWorkerListResponse{Total: workerCnt, Data: workers}
   198  	c.IndentedJSON(http.StatusOK, resp)
   199  }
   200  
   201  // DMAPIOfflineWorkerNode offline worker node url is: (DELETE /api/v1/cluster/workers/{worker-name}).
   202  func (s *Server) DMAPIOfflineWorkerNode(c *gin.Context, workerName string) {
   203  	if err := s.scheduler.RemoveWorker(workerName); err != nil {
   204  		_ = c.Error(err)
   205  		return
   206  	}
   207  	c.Status(http.StatusNoContent)
   208  }
   209  
   210  // DMAPIGetClusterInfo return cluster id of dm cluster url is: (GET /api/v1/cluster/info).
   211  func (s *Server) DMAPIGetClusterInfo(c *gin.Context) {
   212  	info, err := s.getClusterInfo(c.Request.Context())
   213  	if err != nil {
   214  		_ = c.Error(err)
   215  		return
   216  	}
   217  	c.IndentedJSON(http.StatusOK, info)
   218  }
   219  
   220  // DMAPIGetClusterInfo return cluster id of dm cluster url is: (PUT /api/v1/cluster/info).
   221  func (s *Server) DMAPIUpdateClusterInfo(c *gin.Context) {
   222  	var req openapi.ClusterTopology
   223  	if err := c.Bind(&req); err != nil {
   224  		_ = c.Error(err)
   225  		return
   226  	}
   227  	info, err := s.updateClusterInfo(c.Request.Context(), &req)
   228  	if err != nil {
   229  		_ = c.Error(err)
   230  		return
   231  	}
   232  	c.IndentedJSON(http.StatusOK, info)
   233  }
   234  
   235  // DMAPICreateSource url is:(POST /api/v1/sources).
   236  func (s *Server) DMAPICreateSource(c *gin.Context) {
   237  	var req openapi.CreateSourceRequest
   238  	if err := c.Bind(&req); err != nil {
   239  		_ = c.Error(err)
   240  		return
   241  	}
   242  	ctx := c.Request.Context()
   243  	source, err := s.createSource(ctx, req)
   244  	if err != nil {
   245  		_ = c.Error(err)
   246  		return
   247  	}
   248  	c.IndentedJSON(http.StatusCreated, source)
   249  }
   250  
   251  // DMAPIGetSourceList url is:(GET /api/v1/sources).
   252  func (s *Server) DMAPIGetSourceList(c *gin.Context, params openapi.DMAPIGetSourceListParams) {
   253  	ctx := c.Request.Context()
   254  	sourceList, err := s.listSource(ctx, params)
   255  	if err != nil {
   256  		_ = c.Error(err)
   257  		return
   258  	}
   259  	resp := openapi.GetSourceListResponse{Total: len(sourceList), Data: sourceList}
   260  	c.IndentedJSON(http.StatusOK, resp)
   261  }
   262  
   263  // DMAPIGetSource url is:(GET /api/v1/sources/{source-name}).
   264  func (s *Server) DMAPIGetSource(c *gin.Context, sourceName string, params openapi.DMAPIGetSourceParams) {
   265  	ctx := c.Request.Context()
   266  	source, err := s.getSource(ctx, sourceName, params)
   267  	if err != nil {
   268  		if terror.ErrSchedulerSourceCfgNotExist.Equal(err) {
   269  			c.Status(http.StatusNotFound)
   270  			return
   271  		}
   272  		_ = c.Error(err)
   273  		return
   274  	}
   275  	c.IndentedJSON(http.StatusOK, source)
   276  }
   277  
   278  // DMAPIGetSourceStatus url is: (GET /api/v1/sources/{source-id}/status).
   279  func (s *Server) DMAPIGetSourceStatus(c *gin.Context, sourceName string) {
   280  	ctx := c.Request.Context()
   281  	withStatus := true
   282  	source, err := s.getSource(ctx, sourceName, openapi.DMAPIGetSourceParams{WithStatus: &withStatus})
   283  	if err != nil {
   284  		_ = c.Error(err)
   285  		return
   286  	}
   287  	var resp openapi.GetSourceStatusResponse
   288  	// current this source not bound to any worker
   289  	if worker := s.scheduler.GetWorkerBySource(sourceName); worker == nil {
   290  		resp.Data = append(resp.Data, openapi.SourceStatus{SourceName: sourceName})
   291  		resp.Total = len(resp.Data)
   292  		c.IndentedJSON(http.StatusOK, resp)
   293  		return
   294  	}
   295  	resp.Data = append(resp.Data, *source.StatusList...)
   296  	resp.Total = len(resp.Data)
   297  	c.IndentedJSON(http.StatusOK, resp)
   298  }
   299  
   300  // DMAPIUpdateSource url is:(PUT /api/v1/sources/{source-name}).
   301  func (s *Server) DMAPIUpdateSource(c *gin.Context, sourceName string) {
   302  	var req openapi.UpdateSourceRequest
   303  	if err := c.Bind(&req); err != nil {
   304  		_ = c.Error(err)
   305  		return
   306  	}
   307  	source, err := s.updateSource(c.Request.Context(), sourceName, req)
   308  	if err != nil {
   309  		_ = c.Error(err)
   310  		return
   311  	}
   312  	c.IndentedJSON(http.StatusOK, source)
   313  }
   314  
   315  // DMAPIDeleteSource url is:(DELETE /api/v1/sources/{source-name}).
   316  func (s *Server) DMAPIDeleteSource(c *gin.Context, sourceName string, params openapi.DMAPIDeleteSourceParams) {
   317  	ctx := c.Request.Context()
   318  	var force bool
   319  	// force means delete source and stop all task of this source
   320  	if params.Force != nil && *params.Force {
   321  		force = *params.Force
   322  	}
   323  	if err := s.deleteSource(ctx, sourceName, force); err != nil {
   324  		_ = c.Error(err)
   325  		return
   326  	}
   327  	c.Status(http.StatusNoContent)
   328  }
   329  
   330  // DMAPIEnableSource url is:(POST /api/v1/sources/{source-name}/enable).
   331  func (s *Server) DMAPIEnableSource(c *gin.Context, sourceName string) {
   332  	ctx := c.Request.Context()
   333  	if _, err := s.getSource(ctx, sourceName, openapi.DMAPIGetSourceParams{}); err != nil {
   334  		_ = c.Error(err)
   335  		return
   336  	}
   337  	if err := s.enableSource(ctx, sourceName); err != nil {
   338  		_ = c.Error(err)
   339  		return
   340  	}
   341  	c.Status(http.StatusOK)
   342  }
   343  
   344  // DMAPIDisableSource url is:(POST /api/v1/sources/{source-name}/disable).
   345  func (s *Server) DMAPIDisableSource(c *gin.Context, sourceName string) {
   346  	ctx := c.Request.Context()
   347  	if _, err := s.getSource(ctx, sourceName, openapi.DMAPIGetSourceParams{}); err != nil {
   348  		_ = c.Error(err)
   349  		return
   350  	}
   351  	if err := s.disableSource(ctx, sourceName); err != nil {
   352  		_ = c.Error(err)
   353  		return
   354  	}
   355  	c.Status(http.StatusOK)
   356  }
   357  
   358  // DMAPITransferSource transfer source to another free worker url is: (POST /api/v1/sources/{source-name}/transfer).
   359  func (s *Server) DMAPITransferSource(c *gin.Context, sourceName string) {
   360  	var req openapi.WorkerNameRequest
   361  	if err := c.Bind(&req); err != nil {
   362  		_ = c.Error(err)
   363  		return
   364  	}
   365  	if err := s.transferSource(c.Request.Context(), sourceName, req.WorkerName); err != nil {
   366  		_ = c.Error(err)
   367  	}
   368  }
   369  
   370  // DMAPIGetSourceSchemaList get source schema list url is: (GET /api/v1/sources/{source-name}/schemas).
   371  func (s *Server) DMAPIGetSourceSchemaList(c *gin.Context, sourceName string) {
   372  	baseDB, err := s.getBaseDBBySourceName(sourceName)
   373  	if err != nil {
   374  		_ = c.Error(err)
   375  		return
   376  	}
   377  	defer baseDB.Close()
   378  	schemaList, err := dbutil.GetSchemas(c.Request.Context(), baseDB.DB)
   379  	if err != nil {
   380  		_ = c.Error(err)
   381  		return
   382  	}
   383  	c.IndentedJSON(http.StatusOK, schemaList)
   384  }
   385  
   386  // DMAPIGetSourceTableList get source table list url is: (GET /api/v1/sources/{source-name}/schemas/{schema-name}).
   387  func (s *Server) DMAPIGetSourceTableList(c *gin.Context, sourceName string, schemaName string) {
   388  	baseDB, err := s.getBaseDBBySourceName(sourceName)
   389  	if err != nil {
   390  		_ = c.Error(err)
   391  		return
   392  	}
   393  	defer baseDB.Close()
   394  	tableList, err := dbutil.GetTables(c.Request.Context(), baseDB.DB, schemaName)
   395  	if err != nil {
   396  		_ = c.Error(err)
   397  		return
   398  	}
   399  	c.IndentedJSON(http.StatusOK, tableList)
   400  }
   401  
   402  // DMAPIEnableRelay url is:(POST /api/v1/relay/enable).
   403  func (s *Server) DMAPIEnableRelay(c *gin.Context, sourceName string) {
   404  	var req openapi.EnableRelayRequest
   405  	if err := c.Bind(&req); err != nil {
   406  		_ = c.Error(err)
   407  		return
   408  	}
   409  	if err := s.enableRelay(c.Request.Context(), sourceName, req); err != nil {
   410  		_ = c.Error(err)
   411  		return
   412  	}
   413  	c.Status(http.StatusOK)
   414  }
   415  
   416  // DMAPIEnableRelay url is:(POST /api/v1/relay/disable).
   417  func (s *Server) DMAPIDisableRelay(c *gin.Context, sourceName string) {
   418  	var req openapi.DisableRelayRequest
   419  	if err := c.Bind(&req); err != nil {
   420  		_ = c.Error(err)
   421  		return
   422  	}
   423  	if err := s.disableRelay(c.Request.Context(), sourceName, req); err != nil {
   424  		_ = c.Error(err)
   425  	}
   426  	c.Status(http.StatusOK)
   427  }
   428  
   429  // DMAPIPurgeRelay url is:(POST /api/v1/relay/purge).
   430  func (s *Server) DMAPIPurgeRelay(c *gin.Context, sourceName string) {
   431  	var req openapi.PurgeRelayRequest
   432  	if err := c.Bind(&req); err != nil {
   433  		_ = c.Error(err)
   434  		return
   435  	}
   436  	if err := s.purgeRelay(c.Request.Context(), sourceName, req); err != nil {
   437  		_ = c.Error(err)
   438  	}
   439  	c.Status(http.StatusOK)
   440  }
   441  
   442  func (s *Server) getBaseDBBySourceName(sourceName string) (*conn.BaseDB, error) {
   443  	sourceCfg := s.scheduler.GetSourceCfgByID(sourceName)
   444  	if sourceCfg == nil {
   445  		return nil, terror.ErrSchedulerSourceCfgNotExist.Generate(sourceName)
   446  	}
   447  	dbCfg := sourceCfg.GenerateDBConfig()
   448  	return conn.GetUpstreamDB(dbCfg)
   449  }
   450  
   451  // DMAPICreateTask url is:(POST /api/v1/tasks).
   452  func (s *Server) DMAPICreateTask(c *gin.Context) {
   453  	var req openapi.CreateTaskRequest
   454  	if err := c.Bind(&req); err != nil {
   455  		_ = c.Error(err)
   456  		return
   457  	}
   458  	res, err := s.createTask(c.Request.Context(), req)
   459  	if err != nil {
   460  		_ = c.Error(err)
   461  		return
   462  	}
   463  	c.IndentedJSON(http.StatusCreated, res)
   464  }
   465  
   466  // DMAPIUpdateTask url is: (PUT /api/v1/tasks/{task-name}).
   467  func (s *Server) DMAPIUpdateTask(c *gin.Context, taskName string) {
   468  	var req openapi.UpdateTaskRequest
   469  	if err := c.Bind(&req); err != nil {
   470  		_ = c.Error(err)
   471  		return
   472  	}
   473  	res, err := s.updateTask(c.Request.Context(), req)
   474  	if err != nil {
   475  		_ = c.Error(err)
   476  		return
   477  	}
   478  	c.IndentedJSON(http.StatusOK, res)
   479  }
   480  
   481  // DMAPIDeleteTask url is:(DELETE /api/v1/tasks).
   482  func (s *Server) DMAPIDeleteTask(c *gin.Context, taskName string, params openapi.DMAPIDeleteTaskParams) {
   483  	ctx := c.Request.Context()
   484  	var force bool
   485  	if params.Force != nil && *params.Force {
   486  		force = *params.Force
   487  	}
   488  	if err := s.deleteTask(ctx, taskName, force); err != nil {
   489  		_ = c.Error(err)
   490  		return
   491  	}
   492  	c.Status(http.StatusNoContent)
   493  }
   494  
   495  // DMAPIGetTask url is:(GET /api/v1/tasks/{task-name}).
   496  func (s *Server) DMAPIGetTask(c *gin.Context, taskName string, params openapi.DMAPIGetTaskParams) {
   497  	ctx := c.Request.Context()
   498  	task, err := s.getTask(ctx, taskName, params)
   499  	if err != nil {
   500  		if terror.ErrSchedulerSourceCfgNotExist.Equal(err) {
   501  			c.Status(http.StatusNotFound)
   502  			return
   503  		}
   504  		_ = c.Error(err)
   505  		return
   506  	}
   507  	c.IndentedJSON(http.StatusOK, task)
   508  }
   509  
   510  // DMAPIGetTaskStatus url is:(GET /api/v1/tasks/{task-name}/status).
   511  func (s *Server) DMAPIGetTaskStatus(c *gin.Context, taskName string, params openapi.DMAPIGetTaskStatusParams) {
   512  	ctx := c.Request.Context()
   513  	withStatus := true
   514  	task, err := s.getTask(ctx, taskName, openapi.DMAPIGetTaskParams{WithStatus: &withStatus})
   515  	if err != nil {
   516  		_ = c.Error(err)
   517  		return
   518  	}
   519  	resp := openapi.GetTaskStatusResponse{Total: len(*task.StatusList), Data: *task.StatusList}
   520  	c.IndentedJSON(http.StatusOK, resp)
   521  }
   522  
   523  // DMAPIGetTaskList url is:(GET /api/v1/tasks).
   524  func (s *Server) DMAPIGetTaskList(c *gin.Context, params openapi.DMAPIGetTaskListParams) {
   525  	ctx := c.Request.Context()
   526  	taskList, err := s.listTask(ctx, params)
   527  	if err != nil {
   528  		_ = c.Error(err)
   529  		return
   530  	}
   531  	resp := openapi.GetTaskListResponse{Total: len(taskList), Data: taskList}
   532  	c.IndentedJSON(http.StatusOK, resp)
   533  }
   534  
   535  // DMAPIStartTask url is: (POST /api/v1/tasks/{task-name}/start).
   536  func (s *Server) DMAPIStartTask(c *gin.Context, taskName string) {
   537  	var req openapi.StartTaskRequest
   538  	if err := c.Bind(&req); err != nil {
   539  		_ = c.Error(err)
   540  		return
   541  	}
   542  	ctx := c.Request.Context()
   543  	if err := s.startTask(ctx, taskName, req); err != nil {
   544  		_ = c.Error(err)
   545  	}
   546  	c.Status(http.StatusOK)
   547  }
   548  
   549  // DMAPIStopTask url is: (POST /api/v1/tasks/{task-name}/stop).
   550  func (s *Server) DMAPIStopTask(c *gin.Context, taskName string) {
   551  	var req openapi.StopTaskRequest
   552  	if err := c.Bind(&req); err != nil {
   553  		_ = c.Error(err)
   554  		return
   555  	}
   556  	ctx := c.Request.Context()
   557  	if err := s.stopTask(ctx, taskName, req); err != nil {
   558  		_ = c.Error(err)
   559  	}
   560  	c.Status(http.StatusOK)
   561  }
   562  
   563  // DMAPIGetSchemaListByTaskAndSource get task source schema list url is: (GET /api/v1/tasks/{task-name}/sources/{source-name}/schemas).
   564  func (s *Server) DMAPIGetSchemaListByTaskAndSource(c *gin.Context, taskName string, sourceName string) {
   565  	worker := s.scheduler.GetWorkerBySource(sourceName)
   566  	if worker == nil {
   567  		_ = c.Error(terror.ErrWorkerNoStart)
   568  		return
   569  	}
   570  	workerReq := workerrpc.Request{
   571  		Type: workerrpc.CmdOperateSchema,
   572  		OperateSchema: &pb.OperateWorkerSchemaRequest{
   573  			Op:     pb.SchemaOp_ListSchema,
   574  			Task:   taskName,
   575  			Source: sourceName,
   576  		},
   577  	}
   578  	newCtx := c.Request.Context()
   579  	resp, err := worker.SendRequest(newCtx, &workerReq, s.cfg.RPCTimeout)
   580  	if err != nil {
   581  		_ = c.Error(err)
   582  		return
   583  	}
   584  	if !resp.OperateSchema.Result {
   585  		_ = c.Error(terror.ErrOpenAPICommonError.New(resp.OperateSchema.Msg))
   586  		return
   587  	}
   588  	schemaList := openapi.SchemaNameList{}
   589  	if err := json.Unmarshal([]byte(resp.OperateSchema.Msg), &schemaList); err != nil {
   590  		_ = c.Error(terror.ErrSchemaTrackerUnMarshalJSON.Delegate(err, resp.OperateSchema.Msg))
   591  		return
   592  	}
   593  	c.IndentedJSON(http.StatusOK, schemaList)
   594  }
   595  
   596  // DMAPIGetTaskMigrateTargets get task migrate targets list url is: (GET /api/v1/tasks/{task-name}/sources/{source-name}/migrate_targets).
   597  func (s *Server) DMAPIGetTaskMigrateTargets(c *gin.Context, taskName string, sourceName string, params openapi.DMAPIGetTaskMigrateTargetsParams) {
   598  	worker := s.scheduler.GetWorkerBySource(sourceName)
   599  	if worker == nil {
   600  		_ = c.Error(terror.ErrWorkerNoStart)
   601  		return
   602  	}
   603  	var schemaPattern, tablePattern string
   604  	if params.SchemaPattern != nil {
   605  		schemaPattern = *params.SchemaPattern
   606  	}
   607  	if params.TablePattern != nil {
   608  		tablePattern = *params.TablePattern
   609  	}
   610  	workerReq := workerrpc.Request{
   611  		Type: workerrpc.CmdOperateSchema,
   612  		OperateSchema: &pb.OperateWorkerSchemaRequest{
   613  			Op:     pb.SchemaOp_ListMigrateTargets,
   614  			Task:   taskName,
   615  			Source: sourceName,
   616  			Schema: schemaPattern,
   617  			Table:  tablePattern,
   618  		},
   619  	}
   620  	newCtx := c.Request.Context()
   621  	resp, err := worker.SendRequest(newCtx, &workerReq, s.cfg.RPCTimeout)
   622  	if err != nil {
   623  		_ = c.Error(err)
   624  		return
   625  	}
   626  	if !resp.OperateSchema.Result {
   627  		_ = c.Error(terror.ErrOpenAPICommonError.New(resp.OperateSchema.Msg))
   628  		return
   629  	}
   630  	targets := []openapi.TaskMigrateTarget{}
   631  	if err := json.Unmarshal([]byte(resp.OperateSchema.Msg), &targets); err != nil {
   632  		_ = c.Error(terror.ErrSchemaTrackerUnMarshalJSON.Delegate(err, resp.OperateSchema.Msg))
   633  		return
   634  	}
   635  	c.IndentedJSON(http.StatusOK, openapi.GetTaskMigrateTargetsResponse{Data: targets, Total: len(targets)})
   636  }
   637  
   638  // DMAPIGetTableListByTaskAndSource get task source table list url is: (GET /api/v1/tasks/{task-name}/sources/{source-name}/schemas/{schema-name}).
   639  func (s *Server) DMAPIGetTableListByTaskAndSource(c *gin.Context, taskName string, sourceName string, schemaName string) {
   640  	worker := s.scheduler.GetWorkerBySource(sourceName)
   641  	if worker == nil {
   642  		_ = c.Error(terror.ErrWorkerNoStart)
   643  		return
   644  	}
   645  	workerReq := workerrpc.Request{
   646  		Type: workerrpc.CmdOperateSchema,
   647  		OperateSchema: &pb.OperateWorkerSchemaRequest{
   648  			Op:       pb.SchemaOp_ListTable,
   649  			Task:     taskName,
   650  			Source:   sourceName,
   651  			Database: schemaName,
   652  		},
   653  	}
   654  	newCtx := c.Request.Context()
   655  	resp, err := worker.SendRequest(newCtx, &workerReq, s.cfg.RPCTimeout)
   656  	if err != nil {
   657  		_ = c.Error(err)
   658  		return
   659  	}
   660  	if !resp.OperateSchema.Result {
   661  		_ = c.Error(terror.ErrOpenAPICommonError.New(resp.OperateSchema.Msg))
   662  		return
   663  	}
   664  	tableList := openapi.TableNameList{}
   665  	if err := json.Unmarshal([]byte(resp.OperateSchema.Msg), &tableList); err != nil {
   666  		_ = c.Error(terror.ErrSchemaTrackerUnMarshalJSON.Delegate(err, resp.OperateSchema.Msg))
   667  		return
   668  	}
   669  	c.IndentedJSON(http.StatusOK, tableList)
   670  }
   671  
   672  // DMAPIGetTableStructure get task source table structure url is: (GET /api/v1/tasks/{task-name}/sources/{source-name}/schemas/{schema-name}/{table-name}).
   673  func (s *Server) DMAPIGetTableStructure(c *gin.Context, taskName string, sourceName string, schemaName string, tableName string) {
   674  	worker := s.scheduler.GetWorkerBySource(sourceName)
   675  	if worker == nil {
   676  		_ = c.Error(terror.ErrWorkerNoStart)
   677  		return
   678  	}
   679  	workerReq := workerrpc.Request{
   680  		Type: workerrpc.CmdOperateSchema,
   681  		OperateSchema: &pb.OperateWorkerSchemaRequest{
   682  			Op:       pb.SchemaOp_GetSchema,
   683  			Task:     taskName,
   684  			Source:   sourceName,
   685  			Database: schemaName,
   686  			Table:    tableName,
   687  		},
   688  	}
   689  	newCtx := c.Request.Context()
   690  	resp, err := worker.SendRequest(newCtx, &workerReq, s.cfg.RPCTimeout)
   691  	if err != nil {
   692  		_ = c.Error(err)
   693  		return
   694  	}
   695  	if !resp.OperateSchema.Result {
   696  		_ = c.Error(terror.ErrOpenAPICommonError.New(resp.OperateSchema.Msg))
   697  		return
   698  	}
   699  	taskTableStruct := openapi.GetTaskTableStructureResponse{
   700  		SchemaCreateSql: &resp.OperateSchema.Msg,
   701  		SchemaName:      &schemaName,
   702  		TableName:       tableName,
   703  	}
   704  	c.IndentedJSON(http.StatusOK, taskTableStruct)
   705  }
   706  
   707  // DMAPIDeleteTableStructure delete task source table structure url is: (DELETE /api/v1/tasks/{task-name}/sources/{source-name}/schemas/{schema-name}/{table-name}).
   708  func (s *Server) DMAPIDeleteTableStructure(c *gin.Context, taskName string, sourceName string, schemaName string, tableName string) {
   709  	worker := s.scheduler.GetWorkerBySource(sourceName)
   710  	if worker == nil {
   711  		_ = c.Error(terror.ErrWorkerNoStart)
   712  		return
   713  	}
   714  	workerReq := workerrpc.Request{
   715  		Type: workerrpc.CmdOperateSchema,
   716  		OperateSchema: &pb.OperateWorkerSchemaRequest{
   717  			Op:       pb.SchemaOp_RemoveSchema,
   718  			Task:     taskName,
   719  			Source:   sourceName,
   720  			Database: schemaName,
   721  			Table:    tableName,
   722  		},
   723  	}
   724  	newCtx := c.Request.Context()
   725  	resp, err := worker.SendRequest(newCtx, &workerReq, s.cfg.RPCTimeout)
   726  	if err != nil {
   727  		_ = c.Error(err)
   728  		return
   729  	}
   730  	if !resp.OperateSchema.Result {
   731  		_ = c.Error(terror.ErrOpenAPICommonError.New(resp.OperateSchema.Msg))
   732  		return
   733  	}
   734  	c.Status(http.StatusNoContent)
   735  }
   736  
   737  // DMAPIOperateTableStructure operate task source table structure url is: (PUT /api/v1/tasks/{task-name}/sources/{source-name}/schemas/{schema-name}/{table-name}).
   738  func (s *Server) DMAPIOperateTableStructure(c *gin.Context, taskName string, sourceName string, schemaName string, tableName string) {
   739  	var req openapi.OperateTaskTableStructureRequest
   740  	if err := c.Bind(&req); err != nil {
   741  		_ = c.Error(err)
   742  		return
   743  	}
   744  	worker := s.scheduler.GetWorkerBySource(sourceName)
   745  	if worker == nil {
   746  		_ = c.Error(terror.ErrWorkerNoStart)
   747  		return
   748  	}
   749  	opReq := &pb.OperateWorkerSchemaRequest{
   750  		Op:       pb.SchemaOp_SetSchema,
   751  		Task:     taskName,
   752  		Source:   sourceName,
   753  		Database: schemaName,
   754  		Table:    tableName,
   755  		Schema:   req.SqlContent,
   756  		Sync:     *req.Sync,
   757  		Flush:    *req.Flush,
   758  	}
   759  	if req.Sync != nil {
   760  		opReq.Sync = *req.Sync
   761  	}
   762  	if req.Flush != nil {
   763  		opReq.Flush = *req.Flush
   764  	}
   765  	workerReq := workerrpc.Request{Type: workerrpc.CmdOperateSchema, OperateSchema: opReq}
   766  	newCtx := c.Request.Context()
   767  	resp, err := worker.SendRequest(newCtx, &workerReq, s.cfg.RPCTimeout)
   768  	if err != nil {
   769  		_ = c.Error(err)
   770  		return
   771  	}
   772  	if !resp.OperateSchema.Result {
   773  		_ = c.Error(terror.ErrOpenAPICommonError.New(resp.OperateSchema.Msg))
   774  		return
   775  	}
   776  }
   777  
   778  // DMAPIConvertTask turns task into the format of a configuration file or vice versa url is: (POST /api/v1/tasks/,).
   779  func (s *Server) DMAPIConvertTask(c *gin.Context) {
   780  	var req openapi.ConverterTaskRequest
   781  	if err := c.Bind(&req); err != nil {
   782  		_ = c.Error(err)
   783  		return
   784  	}
   785  	if req.Task == nil && req.TaskConfigFile == nil {
   786  		_ = c.Error(terror.ErrOpenAPICommonError.Generate("request body is invalid one of `task` or `task_config_file` must be entered."))
   787  		return
   788  	}
   789  	task, taskCfg, err := s.convertTaskConfig(c.Request.Context(), req)
   790  	if err != nil {
   791  		_ = c.Error(err)
   792  		return
   793  	}
   794  	c.IndentedJSON(http.StatusOK, openapi.ConverterTaskResponse{Task: *task, TaskConfigFile: taskCfg.String()})
   795  }
   796  
   797  // DMAPIImportTaskTemplate create task_config_template url is: (POST /api/v1/tasks/templates/import).
   798  func (s *Server) DMAPIImportTaskTemplate(c *gin.Context) {
   799  	var req openapi.TaskTemplateRequest
   800  	if err := c.Bind(&req); err != nil {
   801  		_ = c.Error(err)
   802  		return
   803  	}
   804  	resp := openapi.TaskTemplateResponse{
   805  		FailedTaskList: []struct {
   806  			ErrorMsg string `json:"error_msg"`
   807  			TaskName string `json:"task_name"`
   808  		}{},
   809  		SuccessTaskList: []string{},
   810  	}
   811  	for _, task := range config.SubTaskConfigsToOpenAPITaskList(s.scheduler.GetALlSubTaskCfgs()) {
   812  		if err := ha.PutOpenAPITaskTemplate(s.etcdClient, *task, req.Overwrite); err != nil {
   813  			resp.FailedTaskList = append(resp.FailedTaskList, struct {
   814  				ErrorMsg string `json:"error_msg"`
   815  				TaskName string `json:"task_name"`
   816  			}{
   817  				ErrorMsg: err.Error(),
   818  				TaskName: task.Name,
   819  			})
   820  		} else {
   821  			resp.SuccessTaskList = append(resp.SuccessTaskList, task.Name)
   822  		}
   823  	}
   824  	c.IndentedJSON(http.StatusAccepted, resp)
   825  }
   826  
   827  // DMAPICreateTaskTemplate create task_config_template url is: (POST /api/tasks/templates).
   828  func (s *Server) DMAPICreateTaskTemplate(c *gin.Context) {
   829  	task := &openapi.Task{}
   830  	if err := c.Bind(task); err != nil {
   831  		_ = c.Error(err)
   832  		return
   833  	}
   834  	if err := task.Adjust(); err != nil {
   835  		_ = c.Error(err)
   836  		return
   837  	}
   838  	// prepare target db config
   839  	newCtx := c.Request.Context()
   840  	toDBCfg := config.GetTargetDBCfgFromOpenAPITask(task)
   841  	if adjustDBErr := AdjustTargetDBSessionCfg(newCtx, toDBCfg); adjustDBErr != nil {
   842  		_ = c.Error(terror.WithClass(adjustDBErr, terror.ClassDMMaster))
   843  		return
   844  	}
   845  	if err := ha.PutOpenAPITaskTemplate(s.etcdClient, *task, false); err != nil {
   846  		_ = c.Error(err)
   847  		return
   848  	}
   849  	c.IndentedJSON(http.StatusCreated, task)
   850  }
   851  
   852  // DMAPIGetTaskTemplateList get task_config_template list url is: (GET /api/v1/tasks/templates).
   853  func (s *Server) DMAPIGetTaskTemplateList(c *gin.Context) {
   854  	TaskConfigList, err := ha.GetAllOpenAPITaskTemplate(s.etcdClient)
   855  	if err != nil {
   856  		_ = c.Error(err)
   857  		return
   858  	}
   859  	taskList := make([]openapi.Task, len(TaskConfigList))
   860  	for i, TaskConfig := range TaskConfigList {
   861  		taskList[i] = *TaskConfig
   862  	}
   863  	resp := openapi.GetTaskListResponse{Total: len(TaskConfigList), Data: taskList}
   864  	c.IndentedJSON(http.StatusOK, resp)
   865  }
   866  
   867  // DMAPIDeleteTaskTemplate delete task_config_template url is: (DELETE /api/v1/tasks/templates/{task-name}).
   868  func (s *Server) DMAPIDeleteTaskTemplate(c *gin.Context, taskName string) {
   869  	if err := ha.DeleteOpenAPITaskTemplate(s.etcdClient, taskName); err != nil {
   870  		_ = c.Error(err)
   871  		return
   872  	}
   873  	c.Status(http.StatusNoContent)
   874  }
   875  
   876  // DMAPIGetTaskTemplate get task_config_template url is: (GET /api/v1/tasks/templates/{task-name}).
   877  func (s *Server) DMAPIGetTaskTemplate(c *gin.Context, taskName string) {
   878  	task, err := ha.GetOpenAPITaskTemplate(s.etcdClient, taskName)
   879  	if err != nil {
   880  		_ = c.Error(err)
   881  		return
   882  	}
   883  	if task == nil {
   884  		_ = c.Error(terror.ErrOpenAPITaskConfigNotExist.Generate(taskName))
   885  		return
   886  	}
   887  	c.IndentedJSON(http.StatusOK, task)
   888  }
   889  
   890  // DMAPUpdateTaskTemplate update task_config_template url is: (PUT /api/v1/tasks/templates/{task-name}).
   891  func (s *Server) DMAPUpdateTaskTemplate(c *gin.Context, taskName string) {
   892  	task := &openapi.Task{}
   893  	if err := c.Bind(task); err != nil {
   894  		_ = c.Error(err)
   895  		return
   896  	}
   897  	if err := task.Adjust(); err != nil {
   898  		_ = c.Error(err)
   899  		return
   900  	}
   901  	newCtx := c.Request.Context()
   902  	toDBCfg := config.GetTargetDBCfgFromOpenAPITask(task)
   903  	if adjustDBErr := AdjustTargetDBSessionCfg(newCtx, toDBCfg); adjustDBErr != nil {
   904  		_ = c.Error(terror.WithClass(adjustDBErr, terror.ClassDMMaster))
   905  		return
   906  	}
   907  	if err := ha.UpdateOpenAPITaskTemplate(s.etcdClient, *task); err != nil {
   908  		_ = c.Error(err)
   909  		return
   910  	}
   911  	c.IndentedJSON(http.StatusOK, task)
   912  }
   913  
   914  func terrorHTTPErrorHandler() gin.HandlerFunc {
   915  	return func(c *gin.Context) {
   916  		c.Next()
   917  		gErr := c.Errors.Last()
   918  		if gErr == nil {
   919  			return
   920  		}
   921  		var code int
   922  		var msg string
   923  		if tErr, ok := gErr.Err.(*terror.Error); ok {
   924  			code = int(tErr.Code())
   925  			msg = tErr.Error()
   926  		} else {
   927  			msg = gErr.Error()
   928  		}
   929  		c.IndentedJSON(http.StatusBadRequest, openapi.ErrorWithMessage{ErrorMsg: msg, ErrorCode: code})
   930  	}
   931  }