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 }